Skip to content

Commit

Permalink
add vue-grid-layout
Browse files Browse the repository at this point in the history
  • Loading branch information
Nikita-Polyakov committed Mar 4, 2024
1 parent c898450 commit 5251275
Show file tree
Hide file tree
Showing 8 changed files with 453 additions and 41 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"vue": "2.7.14",
"vue-class-component": "^7.2.6",
"vue-echarts": "^6.6.9",
"vue-grid-layout": "^2.4.0",
"vue-i18n": "^8.28.2",
"vue-property-decorator": "^9.1.2",
"vue-router": "^3.6.5",
Expand Down
2 changes: 0 additions & 2 deletions src/components/pages/Swap/Form/Widget.vue
Original file line number Diff line number Diff line change
Expand Up @@ -577,8 +577,6 @@ export default class SwapFormWidget extends Mixins(

<style lang="scss" scoped>
.swap-widget {
max-width: $inner-window-width;
@include buttons;
@include full-width-button('action-button');
@include vertical-divider('el-button--switch-tokens', $inner-spacing-medium);
Expand Down
3 changes: 2 additions & 1 deletion src/components/shared/Widget/Base.vue
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ export default class BaseWidget extends Vue {

<style lang="scss">
.base-widget {
overflow: hidden;
max-width: 100%;
max-height: 100%;
&.s-card.neumorphic.s-size-big {
padding: 0;
Expand Down
137 changes: 137 additions & 0 deletions src/components/shared/Widget/Grid/Grid.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
<template>
<div ref="gridWrapper" class="grid-wrapper">
<grid-layout
:layout="currentLayout"
:col-num="columns"
:cols="cols"
:breakpoints="breakpoints"
:row-height="rowHeight"
:is-draggable="false"
:is-resizable="false"
:is-mirrored="false"
:responsive="true"
:vertical-compact="verticalCompact"
:prevent-collision="true"
:margin="[margin, margin]"
:use-css-transforms="true"
@layout-ready="onReady"
>
<grid-item
v-for="item in currentLayout"
:key="item.i"
:x="item.x"
:y="item.y"
:w="item.w"
:h="item.h"
:i="item.i"
class="grid-item"
>
<template v-if="ready">
<slot v-bind="widgets[item.i]" />
</template>
</grid-item>
</grid-layout>
</div>
</template>

<script lang="ts">
import debounce from 'lodash.debounce';
import { GridLayout, GridItem } from 'vue-grid-layout';
import { Component, Prop, Ref, Vue, Watch } from 'vue-property-decorator';
import { Breakpoint } from '@/consts';
import { compactItems } from './utils';
const DEFAULT_BREAKPOINT = 'lg';
const DEFAULT_BREAKPOINTS = {
lg: Breakpoint.HugeDesktop, // 2092
md: Breakpoint.LargeDesktop, // 1440
sm: Breakpoint.Desktop, // 1024
xs: Breakpoint.LargeMobile, // 528
xss: 0,
};
const DEFAULT_COLS = {
lg: 12,
md: 8,
sm: 8,
xs: 4,
xss: 4,
};
@Component({
components: {
GridLayout,
GridItem,
},
})
export default class WidgetsGrid extends Vue {
@Prop({ required: true, type: Array }) readonly widgets!: any;
@Prop({ default: 12, type: Number }) readonly columns!: number;
@Prop({ default: 100, type: Number }) readonly rowHeight!: number;
@Prop({ default: 16, type: Number }) readonly margin!: number;
@Prop({ default: true, type: Boolean }) readonly verticalCompact!: boolean;
@Prop({ default: () => DEFAULT_COLS, type: Object }) readonly cols!: any;
@Prop({ default: () => DEFAULT_BREAKPOINTS, type: Object }) readonly breakpoints!: any;
@Ref('gridWrapper') readonly gridWrapper!: HTMLDivElement;
// @Watch('breakpoint', { immediate: true })
// private calculateBreakpointLayout() {
// const { gridLayouts, originalLayout, breakpoint, cols, verticalCompact } = this;
// if (gridLayouts[breakpoint]) return;
// const columnsCount = cols[breakpoint];
// const compactLayout = compactItems(originalLayout, columnsCount, verticalCompact);
// gridLayouts[breakpoint] = compactLayout;
// }
ready = false;
onReady = debounce(this.setReady);
gridLayouts = {};
breakpoint = DEFAULT_BREAKPOINT;
originalLayout = [];
created() {
this.originalLayout = this.widgets.map((widget, i) => ({
i,
x: widget.spatialData.x,
y: widget.spatialData.y,
w: widget.spatialData.w,
h: widget.spatialData.h,
}));
}
mounted(): void {
this.updateCurrentBreakpoint();
window.addEventListener('resize', this.updateCurrentBreakpoint);
}
beforeDestroy(): void {
window.removeEventListener('resize', this.updateCurrentBreakpoint);
}
get currentLayout() {
return this.gridLayouts[this.breakpoint] ?? this.originalLayout;
}
updateCurrentBreakpoint(): void {
const { breakpoints, gridWrapper } = this;
if (!gridWrapper) return;
const { clientWidth } = gridWrapper;
const currentBreakpoint = Object.keys(breakpoints).find((key) => clientWidth >= breakpoints[key]);
this.breakpoint = currentBreakpoint ?? DEFAULT_BREAKPOINT;
}
setReady(): void {
this.ready = true;
this.$emit('init');
}
}
</script>
126 changes: 126 additions & 0 deletions src/components/shared/Widget/Grid/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// Implementation of new responsive grid
// Function taken from:
// vue-grid-layout/src/helpers/utils.js
// vue-grid-layout/src/helpers/responsiveUtils.js

// original
const getFirstCollision = (layout, layoutItem) => {
for (let i = 0, len = layout.length; i < len; i++) {
if (collides(layout[i], layoutItem)) return layout[i];
}
};

// original
const getStatics = (layout) => {
return layout.filter((l) => l.static);
};

// changed
const sortLayoutItemsByRowCol = (layout) => {
return [].concat(layout).sort(function (a, b) {
if (a.y > b.y || (a.y === b.y && a.x > b.x)) {
return 1;
}
if (a.y === b.y && a.x === b.x) return 0;
return -1;
});
};

// original
const collides = (l1, l2) => {
if (l1 === l2) return false; // same element
if (l1.x + l1.w <= l2.x) return false; // l1 is left of l2
if (l1.x >= l2.x + l2.w) return false; // l1 is right of l2
if (l1.y + l1.h <= l2.y) return false; // l1 is above l2
if (l1.y >= l2.y + l2.h) return false; // l1 is below l2
return true; // boxes overlap
};

// changed
const compactItem = (compareWith, l, verticalCompact, cols) => {
let collide;
while ((collide = getFirstCollision(compareWith, l))) {
l.x = collide.x + collide.w;
if (l.x + l.w > cols) {
l.x = 0;
l.y = collide.y + collide.h;
}
}

if (verticalCompact) {
do {
l.y--;
} while (l.y > 0 && !getFirstCollision(compareWith, l));
l.y++;
}

return l;
};

// changed
const correctBounds = (layout, cols) => {
const collidesWith = getStatics(layout);
for (let i = 0, len = layout.length; i < len; i++) {
const l = layout[i];
if (l.x + l.w > cols) l.x = 0;
if (l.w > cols) {
l.w = cols;
}

let collide;
while ((collide = getFirstCollision(collidesWith, l))) {
l.x = collide.x + collide.w;
if (l.x + l.w > cols) {
l.x = 0;
l.y = collide.y + collide.h;
}
}

if (!l.static) collidesWith.push(l);
}
return layout;
};

// changed
const compact = (layout, cols, verticalCompact) => {
const compareWith = getStatics(layout);
const sorted = sortLayoutItemsByRowCol(layout);
const out = Array(layout.length);

for (let i = 0, len = sorted.length; i < len; i++) {
let l = sorted[i];

if (!l.static) {
l = compactItem(compareWith, l, verticalCompact, cols);

compareWith.push(l);
}

out[layout.indexOf(l)] = l;

l.moved = false;
}

return out;
};

// original
const cloneLayoutItem = (layoutItem) => {
return JSON.parse(JSON.stringify(layoutItem));
};

// original
const cloneLayout = (layout) => {
const newLayout = Array(layout.length);
for (let i = 0, len = layout.length; i < len; i++) {
newLayout[i] = cloneLayoutItem(layout[i]);
}
return newLayout;
};

// new
export const compactItems = (layout, cols, verticalCompact) => {
const clone = cloneLayout(layout);
const correctedBounds = correctBounds(clone, cols);
return compact(correctedBounds, cols, verticalCompact);
};
1 change: 1 addition & 0 deletions src/consts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ export enum Components {
ValueStatusWrapper = 'shared/ValueStatusWrapper',
// Shared Widgets
BaseWidget = 'shared/Widget/Base',
WidgetsGrid = 'shared/Widget/Grid/Grid',
IFrameWidget = 'shared/Widget/IFrame',
PriceChartWidget = 'shared/Widget/PriceChart',
// Shared Buttons
Expand Down
52 changes: 20 additions & 32 deletions src/views/Swap.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
<template>
<div class="swap-container">
<swap-form-widget :parent-loading="parentLoading" />
<swap-chart-widget :parent-loading="parentLoading" v-if="chartsEnabled" class="swap-chart" />
<swap-transactions-widget :parent-loading="parentLoading" />
</div>
<widgets-grid :widgets="widgets" class="swap-container">
<template v-slot="{ id }">
<template v-if="id === 'form'">
<swap-form-widget :parent-loading="parentLoading" />
</template>
<template v-else-if="id === 'chart'">
<swap-chart-widget :parent-loading="parentLoading" v-if="chartsEnabled" />
</template>
<template v-else-if="id === 'transactions'">
<swap-transactions-widget :parent-loading="parentLoading" />
</template>
</template>
</widgets-grid>
</template>

<script lang="ts">
Expand All @@ -24,6 +32,7 @@ import type { AccountAsset } from '@sora-substrate/util/build/assets/types';
SwapFormWidget: lazyComponent(Components.SwapFormWidget),
SwapChartWidget: lazyComponent(Components.SwapChartWidget),
SwapTransactionsWidget: lazyComponent(Components.SwapTransactionsWidget),
WidgetsGrid: lazyComponent(Components.WidgetsGrid),
},
})
export default class Swap extends Mixins(mixins.LoadingMixin, TranslationMixin, SelectedTokenRouteMixin) {
Expand All @@ -37,6 +46,12 @@ export default class Swap extends Mixins(mixins.LoadingMixin, TranslationMixin,
@action.swap.setTokenFromAddress private setTokenFromAddress!: (address?: string) => Promise<void>;
@action.swap.setTokenToAddress private setTokenToAddress!: (address?: string) => Promise<void>;
widgets = [
{ spatialData: { x: 0, y: 0, w: 3, h: 8 }, id: 'form' },
{ spatialData: { x: 3, y: 0, w: 5, h: 5 }, id: 'chart' },
{ spatialData: { x: 3, y: 5, w: 5, h: 5 }, id: 'transactions' },
];
@Watch('tokenFrom')
@Watch('tokenTo')
private updateRouteTokensParams() {
Expand Down Expand Up @@ -71,30 +86,3 @@ export default class Swap extends Mixins(mixins.LoadingMixin, TranslationMixin,
}
}
</script>

<style lang="scss">
.app-main--has-charts {
.swap-chart {
flex-grow: 1;
max-width: $inner-window-width;
@include desktop {
max-width: initial;
}
}
}
</style>

<style lang="scss" scoped>
.swap-container {
display: flex;
flex-flow: row wrap;
justify-content: center;
align-items: flex-start;
gap: $inner-spacing-medium;
@include desktop {
flex-flow: row nowrap;
}
}
</style>
Loading

0 comments on commit 5251275

Please sign in to comment.