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",