diff --git a/packages/pluggableWidgets/gallery-web/src/Gallery.editorConfig.ts b/packages/pluggableWidgets/gallery-web/src/Gallery.editorConfig.ts index de7475fd0f..f9b0acf8c7 100644 --- a/packages/pluggableWidgets/gallery-web/src/Gallery.editorConfig.ts +++ b/packages/pluggableWidgets/gallery-web/src/Gallery.editorConfig.ts @@ -18,6 +18,16 @@ export function getProperties(values: GalleryPreviewProps, defaultProperties: Pr hidePropertiesIn(defaultProperties, values, ["onSelectionChange", "itemSelectionMode"]); } + const usePersonalization = values.storeFilters || values.storeSort; + if (!usePersonalization) { + hidePropertiesIn(defaultProperties, values, ["stateStorageType", "stateStorageAttr", "onConfigurationChange"]); + } else if (values.stateStorageType === "localStorage") { + hidePropertyIn(defaultProperties, values, "stateStorageAttr"); + hidePropertyIn(defaultProperties, values, "onConfigurationChange"); + } + + // Hide scrolling settings for now. + hidePropertiesIn(defaultProperties, values, ["showPagingButtons", "showTotalCount"]); /** Pagination */ if (values.pagination === "buttons") { diff --git a/packages/pluggableWidgets/gallery-web/src/Gallery.xml b/packages/pluggableWidgets/gallery-web/src/Gallery.xml index 2ec8e2118a..c613422fa9 100644 --- a/packages/pluggableWidgets/gallery-web/src/Gallery.xml +++ b/packages/pluggableWidgets/gallery-web/src/Gallery.xml @@ -133,6 +133,37 @@ + + + + Store configuration in + When Browser local storage is selected, the configuration is scoped to a browser profile. This configuration is not tied to a Mendix user. + + Attribute + Browser local storage + + + + Attribute + Attribute containing the personalized configuration of the capabilities. This configuration is automatically stored and loaded. The attribute requires Unlimited String. + + + + + + On change + + + + Store filters + + + + Store sort + + + + diff --git a/packages/pluggableWidgets/gallery-web/src/stores/AttributeStorage.ts b/packages/pluggableWidgets/gallery-web/src/stores/AttributeStorage.ts new file mode 100644 index 0000000000..47587c56ff --- /dev/null +++ b/packages/pluggableWidgets/gallery-web/src/stores/AttributeStorage.ts @@ -0,0 +1,51 @@ +import { PlainJs } from "@mendix/filter-commons/typings/settings"; +import { DerivedPropsGate } from "@mendix/widget-plugin-mobx-kit/props-gate"; +import { ReactiveController, ReactiveControllerHost } from "@mendix/widget-plugin-mobx-kit/reactive-controller"; +import { EditableValue } from "mendix"; +import { computed, makeObservable } from "mobx"; +import { ObservableStorage } from "src/typings/storage"; + +type Gate = DerivedPropsGate<{ + stateStorageAttr: EditableValue; +}>; + +export class AttributeStorage implements ObservableStorage, ReactiveController { + private readonly _gate: Gate; + + constructor(host: ReactiveControllerHost, gate: Gate) { + host.addController(this); + + this._gate = gate; + makeObservable(this, { + _attribute: computed, + data: computed.struct + }); + } + + setup(): () => void { + return () => {}; + } + + private get _attribute(): EditableValue { + return this._gate.props.stateStorageAttr; + } + + get data(): PlainJs { + const jsonString = this._attribute.value; + if (!jsonString) { + return null; + } + try { + return JSON.parse(jsonString) as PlainJs; + } catch { + console.warn("Invalid JSON configuration in the attribute. Resetting configuration."); + this._attribute.setValue(""); + return null; + } + } + + setData(data: PlainJs): void { + data = data === "" ? null : data; + this._attribute.setValue(JSON.stringify(data)); + } +} diff --git a/packages/pluggableWidgets/gallery-web/src/stores/BrowserStorage.ts b/packages/pluggableWidgets/gallery-web/src/stores/BrowserStorage.ts new file mode 100644 index 0000000000..f569278940 --- /dev/null +++ b/packages/pluggableWidgets/gallery-web/src/stores/BrowserStorage.ts @@ -0,0 +1,18 @@ +import { PlainJs } from "@mendix/filter-commons/typings/settings"; +import { ObservableStorage } from "src/typings/storage"; + +export class BrowserStorage implements ObservableStorage { + constructor(private readonly _storageKey: string) {} + + get data(): PlainJs { + try { + return JSON.parse(localStorage.getItem(this._storageKey) ?? "null"); + } catch { + return null; + } + } + + setData(data: PlainJs): void { + localStorage.setItem(this._storageKey, JSON.stringify(data)); + } +} diff --git a/packages/pluggableWidgets/gallery-web/src/stores/GalleryPersistentStateController.ts b/packages/pluggableWidgets/gallery-web/src/stores/GalleryPersistentStateController.ts new file mode 100644 index 0000000000..42926aa4e7 --- /dev/null +++ b/packages/pluggableWidgets/gallery-web/src/stores/GalleryPersistentStateController.ts @@ -0,0 +1,104 @@ +import { PlainJs, Serializable } from "@mendix/filter-commons/typings/settings"; +import { disposeBatch } from "@mendix/widget-plugin-mobx-kit/disposeBatch"; +import { ReactiveControllerHost } from "@mendix/widget-plugin-mobx-kit/reactive-controller"; +import { action, comparer, computed, makeObservable, reaction } from "mobx"; +import { ObservableStorage } from "src/typings/storage"; + +interface GalleryPersistentStateControllerSpec { + filtersHost: Serializable; + sortHost: Serializable; + storage: ObservableStorage; + storeFilters: boolean; + storeSort: boolean; +} + +export class GalleryPersistentStateController { + private readonly _storage: ObservableStorage; + private readonly _filtersHost: Serializable; + private readonly _sortHost: Serializable; + readonly storeFilters: boolean; + readonly storeSort: boolean; + + readonly schemaVersion: number = 1; + + constructor(host: ReactiveControllerHost, spec: GalleryPersistentStateControllerSpec) { + host.addController(this); + this._storage = spec.storage; + this._filtersHost = spec.filtersHost; + this._sortHost = spec.sortHost; + this.storeFilters = spec.storeFilters; + this.storeSort = spec.storeSort; + + makeObservable(this, { + _persistentState: computed, + fromJSON: action + }); + } + + setup(): () => void { + const [add, disposeAll] = disposeBatch(); + + // Write state to storage + const clearWrite = reaction( + () => this._persistentState, + data => this._storage.setData(data), + { delay: 250, equals: comparer.structural } + ); + + // Update state from storage + const clearRead = reaction( + () => this._storage.data, + data => { + if (data == null) { + return; + } + if (this._validate(data)) { + this.fromJSON(data); + } else { + console.warn("Invalid gallery settings. Reset storage to avoid conflicts."); + this._storage.setData(null); + } + }, + { fireImmediately: true, equals: comparer.structural } + ); + + add(clearWrite); + add(clearRead); + + return disposeAll; + } + + private get _persistentState(): PlainJs { + return this.toJSON(); + } + + private _validate(data: PlainJs): data is { [key: string]: PlainJs } { + if (data == null || typeof data !== "object" || !("version" in data) || data.version !== this.schemaVersion) { + return false; + } + return true; + } + + fromJSON(data: PlainJs): void { + if (!this._validate(data)) { + return; + } + if (this.storeFilters) { + this._filtersHost.fromJSON(data.filters); + } + if (this.storeSort) { + this._sortHost.fromJSON(data.sort); + } + } + + toJSON(): PlainJs { + const data: PlainJs = { version: 1 }; + if (this.storeFilters) { + data.filters = this._filtersHost.toJSON(); + } + if (this.storeSort) { + data.sort = this._sortHost.toJSON(); + } + return data; + } +} diff --git a/packages/pluggableWidgets/gallery-web/src/stores/GalleryStore.ts b/packages/pluggableWidgets/gallery-web/src/stores/GalleryStore.ts index 1abe292cc6..66089eebe3 100644 --- a/packages/pluggableWidgets/gallery-web/src/stores/GalleryStore.ts +++ b/packages/pluggableWidgets/gallery-web/src/stores/GalleryStore.ts @@ -8,12 +8,17 @@ import { DerivedPropsGate } from "@mendix/widget-plugin-mobx-kit/props-gate"; import { generateUUID } from "@mendix/widget-plugin-platform/framework/generate-uuid"; import { SortAPI } from "@mendix/widget-plugin-sorting/react/context"; import { SortStoreHost } from "@mendix/widget-plugin-sorting/stores/SortStoreHost"; -import { ListValue } from "mendix"; -import { PaginationEnum } from "../../typings/GalleryProps"; +import { EditableValue, ListValue } from "mendix"; +import { AttributeStorage } from "src/stores/AttributeStorage"; +import { BrowserStorage } from "src/stores/BrowserStorage"; +import { GalleryPersistentStateController } from "src/stores/GalleryPersistentStateController"; +import { ObservableStorage } from "src/typings/storage"; +import { PaginationEnum, StateStorageTypeEnum } from "../../typings/GalleryProps"; import { QueryParamsController } from "../controllers/QueryParamsController"; interface DynamicProps { datasource: ListValue; + stateStorageAttr?: EditableValue; } interface StaticProps { @@ -22,6 +27,9 @@ interface StaticProps { showTotalCount: boolean; pageSize: number; name: string; + stateStorageType: StateStorageTypeEnum; + storeFilters: boolean; + storeSort: boolean; } export type GalleryPropsGate = DerivedPropsGate; @@ -32,6 +40,9 @@ type GalleryStoreSpec = StaticProps & { export class GalleryStore extends BaseControllerHost { private readonly _query: DatasourceController; + private readonly _filtersHost: CustomFilterHost; + private readonly _sortHost: SortStoreHost; + private _storage: ObservableStorage | null = null; readonly id: string = `GalleryStore@${generateUUID()}`; readonly name: string; @@ -54,20 +65,20 @@ export class GalleryStore extends BaseControllerHost { showTotalCount: spec.showTotalCount }); - const filterObserver = new CustomFilterHost(); - const sortObserver = new SortStoreHost(); + this._filtersHost = new CustomFilterHost(); + this._sortHost = new SortStoreHost(); - const paramCtrl = new QueryParamsController(this, this._query, filterObserver, sortObserver); + const paramCtrl = new QueryParamsController(this, this._query, this._filtersHost, this._sortHost); this.filterAPI = createContextWithStub({ - filterObserver, + filterObserver: this._filtersHost, parentChannelName: this.id, sharedInitFilter: paramCtrl.unzipFilter(spec.gate.props.datasource.filter) }); this.sortAPI = { version: 1, - host: sortObserver, + host: this._sortHost, initSortOrder: spec.gate.props.datasource.sortOrder }; @@ -75,5 +86,33 @@ export class GalleryStore extends BaseControllerHost { delay: 0, query: this._query.derivedQuery }); + + const useStorage = spec.storeFilters || spec.storeSort; + if (useStorage) { + this.initPersistentStorage(spec, spec.gate); + } + } + + initPersistentStorage(props: StaticProps, gate: GalleryPropsGate): void { + if (props.stateStorageType === "localStorage") { + this._storage = new BrowserStorage(this.name); + } else if (gate.props.stateStorageAttr) { + this._storage = new AttributeStorage( + this, + gate as DerivedPropsGate<{ stateStorageAttr: EditableValue }> + ); + } + + if (!this._storage) { + return; + } + + new GalleryPersistentStateController(this, { + storage: this._storage, + filtersHost: this._filtersHost, + sortHost: this._sortHost, + storeFilters: props.storeFilters, + storeSort: props.storeSort + }); } } diff --git a/packages/pluggableWidgets/gallery-web/src/typings/storage.ts b/packages/pluggableWidgets/gallery-web/src/typings/storage.ts new file mode 100644 index 0000000000..48cfa6cc64 --- /dev/null +++ b/packages/pluggableWidgets/gallery-web/src/typings/storage.ts @@ -0,0 +1,6 @@ +import { PlainJs } from "@mendix/filter-commons/typings/settings"; + +export interface ObservableStorage { + data: PlainJs; + setData(data: PlainJs): void; +} diff --git a/packages/pluggableWidgets/gallery-web/src/utils/test-utils.tsx b/packages/pluggableWidgets/gallery-web/src/utils/test-utils.tsx index 5ce99e604f..3d53682d85 100644 --- a/packages/pluggableWidgets/gallery-web/src/utils/test-utils.tsx +++ b/packages/pluggableWidgets/gallery-web/src/utils/test-utils.tsx @@ -52,7 +52,10 @@ export function createMockGalleryContext(): GalleryRootScope { showPagingButtons: "always", pagingPosition: "bottom", showEmptyPlaceholder: "none", - onClickTrigger: "single" + onClickTrigger: "single", + stateStorageType: "localStorage", + storeFilters: false, + storeSort: false }; // Create a proper gate provider and gate @@ -66,7 +69,10 @@ export function createMockGalleryContext(): GalleryRootScope { pagination: "buttons", showPagingButtons: "always", showTotalCount: false, - pageSize: 10 + pageSize: 10, + stateStorageType: "localStorage", + storeFilters: false, + storeSort: false }); const mockSelectHelper = new SelectActionHandler("None", undefined); diff --git a/packages/pluggableWidgets/gallery-web/typings/GalleryProps.d.ts b/packages/pluggableWidgets/gallery-web/typings/GalleryProps.d.ts index 346da2f835..a1cd505ccb 100644 --- a/packages/pluggableWidgets/gallery-web/typings/GalleryProps.d.ts +++ b/packages/pluggableWidgets/gallery-web/typings/GalleryProps.d.ts @@ -4,7 +4,7 @@ * @author Mendix Widgets Framework Team */ import { ComponentType, CSSProperties, ReactNode } from "react"; -import { ActionValue, DynamicValue, ListValue, ListActionValue, ListExpressionValue, ListWidgetValue, SelectionSingleValue, SelectionMultiValue } from "mendix"; +import { ActionValue, DynamicValue, EditableValue, ListValue, ListActionValue, ListExpressionValue, ListWidgetValue, SelectionSingleValue, SelectionMultiValue } from "mendix"; export type ItemSelectionModeEnum = "toggle" | "clear"; @@ -18,6 +18,8 @@ export type ShowEmptyPlaceholderEnum = "none" | "custom"; export type OnClickTriggerEnum = "single" | "double"; +export type StateStorageTypeEnum = "attribute" | "localStorage"; + export interface GalleryContainerProps { name: string; class: string; @@ -43,6 +45,10 @@ export interface GalleryContainerProps { onClickTrigger: OnClickTriggerEnum; onClick?: ListActionValue; onSelectionChange?: ActionValue; + stateStorageType: StateStorageTypeEnum; + stateStorageAttr?: EditableValue; + storeFilters: boolean; + storeSort: boolean; filterSectionTitle?: DynamicValue; emptyMessageTitle?: DynamicValue; ariaLabelListBox?: DynamicValue; @@ -80,6 +86,11 @@ export interface GalleryPreviewProps { onClickTrigger: OnClickTriggerEnum; onClick: {} | null; onSelectionChange: {} | null; + stateStorageType: StateStorageTypeEnum; + stateStorageAttr: string; + onConfigurationChange: {} | null; + storeFilters: boolean; + storeSort: boolean; filterSectionTitle: string; emptyMessageTitle: string; ariaLabelListBox: string; diff --git a/packages/shared/filter-commons/src/typings/settings.ts b/packages/shared/filter-commons/src/typings/settings.ts index 1b6cb7a373..d34545e724 100644 --- a/packages/shared/filter-commons/src/typings/settings.ts +++ b/packages/shared/filter-commons/src/typings/settings.ts @@ -7,3 +7,10 @@ export type SelectData = string[]; export type FilterData = InputData | SelectData | null | undefined; export type FiltersSettingsMap = Map; + +export type PlainJs = string | number | boolean | null | PlainJs[] | { [key: string]: PlainJs }; + +export interface Serializable { + toJSON(): PlainJs; + fromJSON(data: PlainJs): void; +} diff --git a/packages/shared/widget-plugin-filtering/src/stores/generic/CustomFilterHost.ts b/packages/shared/widget-plugin-filtering/src/stores/generic/CustomFilterHost.ts index 2732aaf64a..8e461dfbae 100644 --- a/packages/shared/widget-plugin-filtering/src/stores/generic/CustomFilterHost.ts +++ b/packages/shared/widget-plugin-filtering/src/stores/generic/CustomFilterHost.ts @@ -1,11 +1,11 @@ import { tag } from "@mendix/filter-commons/condition-utils"; -import { FiltersSettingsMap } from "@mendix/filter-commons/typings/settings"; +import { FilterData, FiltersSettingsMap, PlainJs, Serializable } from "@mendix/filter-commons/typings/settings"; import { FilterCondition } from "mendix/filters"; import { and } from "mendix/filters/builders"; import { autorun, makeAutoObservable } from "mobx"; import { Filter, ObservableFilterHost } from "../../typings/ObservableFilterHost"; -export class CustomFilterHost implements ObservableFilterHost { +export class CustomFilterHost implements ObservableFilterHost, Serializable { private filters: Map void]> = new Map(); private settingsBuffer: FiltersSettingsMap = new Map(); @@ -46,4 +46,16 @@ export class CustomFilterHost implements ObservableFilterHost { this.filters.get(key)?.[1](); } } + + toJSON(): PlainJs { + return [...this.settings.entries()] as PlainJs; + } + + fromJSON(data: PlainJs): void { + if (data == null || !Array.isArray(data)) { + return; + } + + this.settings = new Map(data as Array<[string, FilterData]>); + } } diff --git a/packages/shared/widget-plugin-filtering/src/stores/input/StringInputFilterStore.ts b/packages/shared/widget-plugin-filtering/src/stores/input/StringInputFilterStore.ts index 3bae4046c5..fbb2836b6c 100644 --- a/packages/shared/widget-plugin-filtering/src/stores/input/StringInputFilterStore.ts +++ b/packages/shared/widget-plugin-filtering/src/stores/input/StringInputFilterStore.ts @@ -60,7 +60,7 @@ export class StringInputFilterStore return; } const [fn, s1, s2] = inputData; - this.setState([fn, s1 ? s1 : undefined, s2 ? s2 : undefined]); + this.setState([fn, s1 ?? undefined, s2 ?? undefined]); this.isInitialized = true; } diff --git a/packages/shared/widget-plugin-sorting/package.json b/packages/shared/widget-plugin-sorting/package.json index 83e7f5f73b..b3fdeb2495 100644 --- a/packages/shared/widget-plugin-sorting/package.json +++ b/packages/shared/widget-plugin-sorting/package.json @@ -31,6 +31,7 @@ "test": "jest" }, "dependencies": { + "@mendix/filter-commons": "workspace:^", "@mendix/widget-plugin-component-kit": "workspace:*", "@mendix/widget-plugin-mobx-kit": "workspace:*", "@mendix/widget-plugin-platform": "workspace:*", diff --git a/packages/shared/widget-plugin-sorting/src/__tests__/SortStoreHost.spec.ts b/packages/shared/widget-plugin-sorting/src/__tests__/SortStoreHost.spec.ts index 8afe628c5e..0e350ef6e6 100644 --- a/packages/shared/widget-plugin-sorting/src/__tests__/SortStoreHost.spec.ts +++ b/packages/shared/widget-plugin-sorting/src/__tests__/SortStoreHost.spec.ts @@ -12,7 +12,10 @@ describe("SortStoreHost", () => { sortOrder: [ [attrId("attr1"), "asc"], [attrId("attr2"), "desc"] - ] as SortInstruction[] + ] as SortInstruction[], + setSortOrder: jest.fn(), + toJSON: jest.fn(), + fromJSON: jest.fn() }; }); @@ -58,7 +61,10 @@ describe("SortStoreHost", () => { it("should replace previously observed store", () => { const anotherMockStore: ObservableSortStore = { - sortOrder: [[attrId("attr3"), "asc"]] as SortInstruction[] + sortOrder: [[attrId("attr3"), "asc"]] as SortInstruction[], + setSortOrder: jest.fn(), + toJSON: jest.fn(), + fromJSON: jest.fn() }; sortStoreHost.observe(mockStore); @@ -203,7 +209,10 @@ describe("SortStoreHost", () => { it("should handle store changes after observation", () => { const mutableStore = { - sortOrder: [[attrId("attr1"), "asc"]] as SortInstruction[] + sortOrder: [[attrId("attr1"), "asc"]] as SortInstruction[], + setSortOrder: jest.fn(), + toJSON: jest.fn(), + fromJSON: jest.fn() }; sortStoreHost.observe(mutableStore); diff --git a/packages/shared/widget-plugin-sorting/src/controllers/SingleSortController.ts b/packages/shared/widget-plugin-sorting/src/controllers/SingleSortController.ts index 2e4681253b..5f5e151143 100644 --- a/packages/shared/widget-plugin-sorting/src/controllers/SingleSortController.ts +++ b/packages/shared/widget-plugin-sorting/src/controllers/SingleSortController.ts @@ -47,15 +47,15 @@ export class SingleSortController { toggleDirection = (): void => { this.direction = this.direction === "asc" ? "desc" : "asc"; if (this.selected) { - this._sortOrderStore.replace([this.selected, this.direction]); + this._sortOrderStore.setSortOrder([this.selected, this.direction]); } }; select = (value: string): void => { if (value === "none") { - this._sortOrderStore.replace(); + this._sortOrderStore.setSortOrder(); } else { - this._sortOrderStore.replace([value as ListAttributeId, this.direction]); + this._sortOrderStore.setSortOrder([value as ListAttributeId, this.direction]); } }; diff --git a/packages/shared/widget-plugin-sorting/src/stores/SortOrderStore.ts b/packages/shared/widget-plugin-sorting/src/stores/SortOrderStore.ts index 1cbad5fc6d..ca9a2a1b9f 100644 --- a/packages/shared/widget-plugin-sorting/src/stores/SortOrderStore.ts +++ b/packages/shared/widget-plugin-sorting/src/stores/SortOrderStore.ts @@ -1,23 +1,28 @@ +import { PlainJs, Serializable } from "@mendix/filter-commons/typings/settings"; import { generateUUID } from "@mendix/widget-plugin-platform/framework/generate-uuid"; import { action, computed, makeObservable, observable } from "mobx"; import { BasicSortStore, Option, SortInstruction } from "../types/store"; -export class SortOrderStore implements BasicSortStore { +type StorableState = Array<[number, "asc" | "desc"]>; + +export class SortOrderStore implements BasicSortStore, Serializable { private readonly _sortOrder: SortInstruction[] = []; readonly id = `SortOrderStore@${generateUUID()}`; readonly options: Option[]; + readonly idToIndex: Map; constructor(spec: { options?: Option[]; initSortOrder?: SortInstruction[] } = {}) { const { options = [], initSortOrder = [] } = spec; this.options = [...options]; + this.idToIndex = new Map(options.map((option, index) => [option.value, index])); this._sortOrder = [...initSortOrder]; makeObservable(this, { _sortOrder: observable, sortOrder: computed, - replace: action, + setSortOrder: action, push: action, remove: action }); @@ -27,7 +32,7 @@ export class SortOrderStore implements BasicSortStore { return [...this._sortOrder]; } - replace(...order: SortInstruction[]): void { + setSortOrder(...order: SortInstruction[]): void { this._sortOrder.splice(0, this._sortOrder.length, ...order); } @@ -40,4 +45,25 @@ export class SortOrderStore implements BasicSortStore { this._sortOrder.splice(index, 1); } } + + toJSON(): PlainJs { + const data: StorableState = this.sortOrder.map(inst => { + const index = this.idToIndex.get(inst[0])!; + return [index, inst[1]]; + }); + + return data; + } + + fromJSON(data: PlainJs): void { + if (!Array.isArray(data)) { + return; + } + const sortOrder = (data as StorableState).flatMap(([index, direction]) => { + const value = this.options[index]?.value; + return value ? [[value, direction]] : []; + }); + + this.setSortOrder(...sortOrder); + } } diff --git a/packages/shared/widget-plugin-sorting/src/stores/SortStoreHost.ts b/packages/shared/widget-plugin-sorting/src/stores/SortStoreHost.ts index 16fe5d53e4..823e1fb3c7 100644 --- a/packages/shared/widget-plugin-sorting/src/stores/SortStoreHost.ts +++ b/packages/shared/widget-plugin-sorting/src/stores/SortStoreHost.ts @@ -1,7 +1,8 @@ +import { PlainJs, Serializable } from "@mendix/filter-commons/typings/settings"; import { action, computed, makeObservable, observable } from "mobx"; import { ObservableSortStore, SortInstruction } from "../types/store"; -export class SortStoreHost { +export class SortStoreHost implements Serializable { private _store: ObservableSortStore | null = null; private _usedBy: string[] = []; @@ -22,10 +23,6 @@ export class SortStoreHost { }); } - // TODO: toJSON - - // TODO: fromJSON - observe(store: ObservableSortStore): void { this._store = store; } @@ -55,4 +52,17 @@ export class SortStoreHost { get usedBy(): string | null { return this._usedBy.at(0) ?? null; } + + toJSON(): PlainJs { + return this._store ? this._store.toJSON() : null; + } + + fromJSON(data: PlainJs): void { + if (data == null || !Array.isArray(data)) { + return; + } + if (this._store) { + this._store.fromJSON(data); + } + } } diff --git a/packages/shared/widget-plugin-sorting/src/types/store.ts b/packages/shared/widget-plugin-sorting/src/types/store.ts index 642cc8bdeb..7fc15e699d 100644 --- a/packages/shared/widget-plugin-sorting/src/types/store.ts +++ b/packages/shared/widget-plugin-sorting/src/types/store.ts @@ -1,3 +1,4 @@ +import { Serializable } from "@mendix/filter-commons/typings/settings"; import type { ListAttributeValue } from "mendix"; export type SortDirection = "asc" | "desc"; @@ -11,19 +12,13 @@ export type Option = { value: ListAttributeId; }; -export interface ObservableSortStore { +export interface ObservableSortStore extends Serializable { sortOrder: SortInstruction[]; + setSortOrder(...item: SortInstruction[]): void; } export interface BasicSortStore extends ObservableSortStore { options: Option[]; - sortOrder: SortInstruction[]; push(...item: SortInstruction[]): void; remove(index: number): void; - replace(...item: SortInstruction[]): void; -} - -export interface Serializable { - toJSON(): unknown; - fromJSON(json: unknown): void; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 40d480cc17..a7a3cb2e71 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2805,6 +2805,9 @@ importers: packages/shared/widget-plugin-sorting: dependencies: + '@mendix/filter-commons': + specifier: workspace:^ + version: link:../filter-commons '@mendix/widget-plugin-component-kit': specifier: workspace:* version: link:../widget-plugin-component-kit diff --git a/turbo.json b/turbo.json index 9cb746b9ff..555d009507 100644 --- a/turbo.json +++ b/turbo.json @@ -1,6 +1,6 @@ { "$schema": "https://turborepo.org/schema.json", - "globalPassThroughEnv": ["GITHUB_TOKEN", "GH_PAT", "GH_USERNAME", "GH_EMAIL", "GH_NAME", "CPAPI_PASS", "CPAPI_URL", "CPAPI_USER", "CPAPI_USER_OPENID"], + "globalPassThroughEnv": ["GITHUB_TOKEN", "GH_PAT", "GH_USERNAME", "GH_EMAIL", "GH_NAME", "CPAPI_PASS", "CPAPI_URL", "CPAPI_USER", "CPAPI_USER_OPENID", "MX_PROJECT_PATH"], "globalDependencies": [ "automation/**", ".npmrc",