Skip to content

Commit

Permalink
wip widgets layout container
Browse files Browse the repository at this point in the history
  • Loading branch information
Nikita-Polyakov committed Mar 14, 2024
1 parent cc64135 commit 54d6aa7
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 62 deletions.
10 changes: 7 additions & 3 deletions src/components/shared/Widget/Base.vue
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ import { debouncedInputHandler } from '@/utils';
@Component
export default class BaseWidget extends Vue {
/**
* Widget ID
*/
@Prop({ default: '', type: String }) readonly id!: string;
/**
* The widget title has a large font-size
*/
Expand Down Expand Up @@ -72,7 +76,7 @@ export default class BaseWidget extends Vue {
*/
@Prop({ default: false, type: Boolean }) readonly loading!: boolean;
@Prop({ default: () => {}, type: Function }) readonly onResize!: (rect: Partial<DOMRect>) => void;
@Prop({ default: () => {}, type: Function }) readonly onResize!: (id: string, rect: Partial<DOMRect>) => void;
@Ref('container') readonly container!: Vue;
@Ref('content') readonly content!: HTMLDivElement;
Expand Down Expand Up @@ -120,10 +124,10 @@ export default class BaseWidget extends Vue {
onContentResize(): void {
const { width, height } = this.getClientRect();
const rect = { width, height };
// check necessary update
if (!isEqual(rect)(this.rect)) {
this.updateRect(rect);
this.onResize(this.rect);
this.onResize(this.id, this.rect);
}
}
}
Expand Down
46 changes: 17 additions & 29 deletions src/components/shared/Widget/Grid/Grid.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<grid-layout
class="widgets-grid"
:layout.sync="gridLayout"
:responsive-layouts="responsiveLayouts"
:responsive-layouts="layouts"
:cols="cols"
:breakpoints="breakpoints"
:row-height="rowHeight"
Expand All @@ -22,7 +22,7 @@
v-bind="{
id: widget.i,
loading,
onResize: ($event) => onWidgetResize($event, widget.i),
onResize,
}"
/>
</grid-item>
Expand All @@ -37,22 +37,22 @@ import { GridLayout, GridItem } from 'vue-grid-layout';
import { Component, Prop, Vue, Watch } from 'vue-property-decorator';
import { Breakpoint, BreakpointKey } from '@/consts/layout';
import type { Layout, LayoutConfig, LayoutWidget, LayoutWidgetConfig, ResponsiveLayouts } from '@/types/layout';
import type { Layout, LayoutConfig, ResponsiveLayouts } from '@/types/layout';
const DEFAULT_BREAKPOINTS: LayoutConfig = {
[BreakpointKey.lg]: Breakpoint.HugeDesktop, // 2092
[BreakpointKey.md]: Breakpoint.LargeDesktop, // 1440
[BreakpointKey.sm]: Breakpoint.Desktop, // 1024
[BreakpointKey.xs]: Breakpoint.Tablet, // 900
[BreakpointKey.xss]: Breakpoint.Mobile, // 464
[BreakpointKey.xss]: 0,
};
const DEFAULT_COLS: LayoutConfig = {
[BreakpointKey.lg]: 24, // 2092
[BreakpointKey.md]: 16, // 1440
[BreakpointKey.sm]: 12, // 1024
[BreakpointKey.xs]: 8, // 900
[BreakpointKey.xss]: 4, // 464
[BreakpointKey.xss]: 4,
};
@Component({
Expand All @@ -62,42 +62,30 @@ const DEFAULT_COLS: LayoutConfig = {
},
})
export default class WidgetsGrid extends Vue {
@Prop({ required: true, type: Object }) readonly layouts!: ResponsiveLayouts<LayoutWidgetConfig>;
@Prop({ required: true, type: Object }) readonly layouts!: ResponsiveLayouts;
@Prop({ default: 10, type: Number }) readonly rowHeight!: number;
@Prop({ default: 16, type: Number }) readonly margin!: number;
@Prop({ default: false, type: Boolean }) readonly draggable!: boolean;
@Prop({ default: false, type: Boolean }) readonly resizable!: boolean;
@Prop({ default: false, type: Boolean }) readonly compact!: boolean;
@Prop({ default: false, type: Boolean }) readonly loading!: boolean;
@Prop({ default: false, type: Boolean }) readonly lines!: boolean;
@Prop({ default: false, type: Boolean }) readonly loading!: boolean;
@Prop({ default: () => DEFAULT_COLS, type: Object }) readonly cols!: LayoutConfig;
@Prop({ default: () => DEFAULT_BREAKPOINTS, type: Object }) readonly breakpoints!: LayoutConfig;
private breakpoint: BreakpointKey = BreakpointKey.lg;
private layout: Layout<LayoutWidget> = [];
private layout: Layout = [];
private onUpdate = debounce(this.emitUpdate, 500);
get gridLayout() {
get gridLayout(): Layout {
return this.layout;
}
set gridLayout(updated) {
set gridLayout(updated: Layout) {
this.layout = updated;
this.onUpdate(this.layout);
}
onUpdate = debounce(this.emitUpdate, 500);
get responsiveLayouts(): ResponsiveLayouts<LayoutWidget> {
return Object.entries(this.layouts).reduce<ResponsiveLayouts<LayoutWidget>>((acc, [key, layout]) => {
acc[key] = layout.map((widget) => ({
...widget,
minW: widget.minW ?? widget.w,
minH: widget.minH ?? widget.h,
}));
return acc;
}, {});
}
get gridLinesStyle(): Partial<CSSStyleDeclaration> {
const r = this.rowHeight;
const m = this.margin / 2;
Expand All @@ -111,20 +99,20 @@ export default class WidgetsGrid extends Vue {
};
}
@Watch('responsiveLayouts')
private updateCurrentLayout(updatedLayouts: ResponsiveLayouts<LayoutWidget>): void {
@Watch('layouts', { deep: true })
private updateCurrentLayout(updatedLayouts: ResponsiveLayouts): void {
const updatedLayout = updatedLayouts[this.breakpoint];
if (!updatedLayout || isEqual(this.layout)(updatedLayout)) return;
this.layout = cloneDeep(updatedLayout) as Layout<LayoutWidget>;
this.layout = cloneDeep(updatedLayout) as Layout;

Check warning on line 108 in src/components/shared/Widget/Grid/Grid.vue

View check run for this annotation

Soramitsu-Sonar-PR-decoration / polkaswap-exchange-web Sonarqube Results

src/components/shared/Widget/Grid/Grid.vue#L108

This assertion is unnecessary since it does not change the type of the expression.
}
onBreakpointChanged(newBreakpoint: BreakpointKey): void {
this.breakpoint = newBreakpoint;
}
onWidgetResize(rect: DOMRect, id: string): void {
onResize(id: string, rect: DOMRect): void {
const layout = cloneDeep(this.gridLayout);
const widget = layout.find((item) => item.i === id);
Expand All @@ -141,8 +129,8 @@ export default class WidgetsGrid extends Vue {
this.gridLayout = layout;
}
private emitUpdate(layout: Layout<LayoutWidget>): void {
const update: ResponsiveLayouts<LayoutWidget> = {
private emitUpdate(layout: Layout): void {
const update: ResponsiveLayouts = {
[this.breakpoint]: layout,
};
Expand Down
82 changes: 82 additions & 0 deletions src/components/shared/Widget/Grid/Layout.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<template>
<widgets-grid :layouts="layouts" @update="updateLayouts">
<template v-for="(_, scopedSlotName) in $scopedSlots" v-slot:[scopedSlotName]="slotData">
<slot :name="scopedSlotName" v-bind="slotData" />
</template>
</widgets-grid>
</template>

<script lang="ts">
import { mixins } from '@soramitsu/soraneo-wallet-web';
import cloneDeep from 'lodash/fp/cloneDeep';
import isEqual from 'lodash/fp/isEqual';
import { Component, Mixins, Prop, ModelSync } from 'vue-property-decorator';
import { Components } from '@/consts';
import { lazyComponent } from '@/router';
import type { ResponsiveLayouts } from '@/types/layout';
@Component({
components: {
WidgetsGrid: lazyComponent(Components.WidgetsGrid),
},
})
export default class Swap extends Mixins(mixins.LoadingMixin) {
/**
* Layout ID to sync it with storage
*/
@Prop({ default: '', type: String }) readonly id!: string;
/**
* Layout widgets IDs
*/
@Prop({ default: () => [], type: Array }) readonly widgets!: string[];
/**
* Default layouts
*/
@Prop({ default: () => ({}), type: Object }) readonly defaultLayouts!: ResponsiveLayouts;
/**
* Layout Widgets visibility by widget ID
*/
@ModelSync('visibility', 'update:visibility', { type: Object })
widgetsVisibility!: Record<string, boolean>;
layouts!: ResponsiveLayouts;
created(): void {
if (this.id) {
// load layouts from storage
}
// or use default
this.saveLayouts(this.defaultLayouts);
}
updateLayouts(updated: ResponsiveLayouts): void {
if (!isEqual(this.layouts, updated)) {
this.saveLayouts({ ...this.layouts, ...updated });
}
}
saveLayouts(layouts: ResponsiveLayouts) {
this.layouts = cloneDeep(layouts);
}
toggleWidget(widgetId: string, isVisible: boolean): void {
const layouts = cloneDeep(this.layouts);
Object.keys(layouts).forEach((key) => {
if (isVisible) {
const widget = this.defaultLayouts[key].find((widget) => widget.i === widgetId);
if (widget) {
layouts[key] = [...layouts[key], { ...widget }];
}
} else {
layouts[key] = layouts[key].filter((widget) => widget.i !== widgetId);
}
});
this.saveLayouts(layouts);
}
}
</script>
18 changes: 11 additions & 7 deletions src/types/layout.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,28 @@
import type { BreakpointKey } from '@/consts/layout';

export type WidgetRect = {
export type WidgetSize = {
w: number;
h: number;
minW?: number;
minH?: number;
maxW?: number;
maxH?: number;
};

export type LayoutWidgetConfig = WidgetRect & {
i: string;
export type WidgetPosition = {
x: number;
y: number;
};

export type BreakpointConfig<T> = Record<BreakpointKey, T>;
export type LayoutWidget = WidgetSize &
WidgetPosition & {
i: string;
};

export type LayoutWidget = Required<LayoutWidgetConfig>;
export type BreakpointConfig<T> = Record<BreakpointKey, T>;

export type LayoutConfig = BreakpointConfig<number>;

export type Layout<T extends LayoutWidgetConfig> = T[];
export type Layout = LayoutWidget[];

export type ResponsiveLayouts<T extends LayoutWidgetConfig> = Partial<BreakpointConfig<Layout<T>>>;
export type ResponsiveLayouts = Partial<BreakpointConfig<Layout>>;
45 changes: 22 additions & 23 deletions src/views/Swap.vue
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
<template>
<div class="s-flex" style="flex-flow: column; height: 100%">
<div class="controls s-flex" style="gap: 16px; justify-content: space-between">
<div class="controls s-flex" style="gap: 16px; justify-content: space-between; flex-flow: row wrap">
<div class="s-flex">
<s-checkbox v-model="draggable" label="Draggable" />
<s-checkbox v-model="resizable" label="Resizable" />
<s-checkbox v-model="compact" label="Compact" />
<s-checkbox v-model="lines" label="Show grid" />
</div>
<div class="s-flex">
<s-checkbox v-model="form" @change="updateWidget(SwapWidgets.Form, $event)" label="Form" />
<s-checkbox v-model="chart" @change="updateWidget(SwapWidgets.Chart, $event)" label="Chart" />
<s-checkbox
v-model="transactions"
@change="updateWidget(SwapWidgets.Transactions, $event)"
label="Transactions"
v-for="(_, key) in widgets"
:key="key"
:label="key"
v-model="widgets[key]"
@change="toggleWidget(key, $event)"
/>
<s-checkbox v-model="distribution" @change="updateWidget(SwapWidgets.Distribution, $event)" label="Route" />
</div>
</div>
<widgets-grid
Expand Down Expand Up @@ -62,7 +61,7 @@ import TranslationMixin from '@/components/mixins/TranslationMixin';
import { Components, PageNames } from '@/consts';
import { lazyComponent } from '@/router';
import { action, getter, state } from '@/store/decorators';
import type { LayoutWidget, LayoutWidgetConfig, ResponsiveLayouts } from '@/types/layout';
import type { ResponsiveLayouts } from '@/types/layout';
import type { AccountAsset } from '@sora-substrate/util/build/assets/types';
Expand All @@ -73,7 +72,7 @@ enum SwapWidgets {
Distribution = 'distribution',
}
const LayoutsConfigDefault: ResponsiveLayouts<LayoutWidgetConfig> = {
const DefaultLayouts: ResponsiveLayouts = {
md: [
{ x: 0, y: 0, w: 4, h: 20, minW: 4, minH: 20, i: SwapWidgets.Form },
{ x: 4, y: 0, w: 12, h: 20, minW: 4, minH: 20, i: SwapWidgets.Chart },
Expand All @@ -87,10 +86,10 @@ const LayoutsConfigDefault: ResponsiveLayouts<LayoutWidgetConfig> = {
{ x: 4, y: 20, w: 8, h: 24, minW: 4, minH: 24, i: SwapWidgets.Transactions },
],
xs: [
{ x: 0, y: 0, w: 4, h: 20, i: SwapWidgets.Form },
{ x: 4, y: 0, w: 4, h: 20, i: SwapWidgets.Chart },
{ x: 0, y: 20, w: 4, h: 12, i: SwapWidgets.Distribution },
{ x: 4, y: 20, w: 4, h: 24, i: SwapWidgets.Transactions },
{ x: 0, y: 0, w: 4, h: 20, minW: 4, minH: 20, i: SwapWidgets.Form },
{ x: 4, y: 0, w: 4, h: 20, minW: 4, minH: 20, i: SwapWidgets.Chart },
{ x: 0, y: 20, w: 4, h: 12, minW: 4, minH: 12, i: SwapWidgets.Distribution },
{ x: 4, y: 20, w: 4, h: 24, minW: 4, minH: 24, i: SwapWidgets.Transactions },
],
};
Expand Down Expand Up @@ -120,23 +119,25 @@ export default class Swap extends Mixins(mixins.LoadingMixin, TranslationMixin,
compact = false;
lines = false;
form = true;
chart = true;
transactions = true;
distribution = true;
widgets = {
[SwapWidgets.Form]: true,
[SwapWidgets.Chart]: true,
[SwapWidgets.Distribution]: true,
[SwapWidgets.Transactions]: true,
};
layouts: ResponsiveLayouts<LayoutWidgetConfig> = cloneDeep(LayoutsConfigDefault);
layouts: ResponsiveLayouts = cloneDeep(DefaultLayouts);
updateLayoutsConfig(updated: ResponsiveLayouts<LayoutWidget>): void {
updateLayoutsConfig(updated: ResponsiveLayouts): void {
if (!isEqual(this.layouts, updated)) {
this.layouts = { ...this.layouts, ...updated };
}
}
updateWidget(id: SwapWidgets, flag: boolean): void {
toggleWidget(id: SwapWidgets, flag: boolean): void {
Object.keys(this.layouts).forEach((key) => {
if (flag) {
const widget = LayoutsConfigDefault[key].find((widget) => widget.i === id);
const widget = DefaultLayouts[key].find((widget) => widget.i === id);
if (widget) {
this.layouts[key] = [...this.layouts[key], { ...widget }];
Expand Down Expand Up @@ -184,8 +185,6 @@ export default class Swap extends Mixins(mixins.LoadingMixin, TranslationMixin,

<style lang="scss" scoped>
.swap-container {
flex: 1;
#form,
#chart {
min-height: 502px;
Expand Down

0 comments on commit 54d6aa7

Please sign in to comment.