Skip to content

Commit

Permalink
Support custom serializers in table hooks
Browse files Browse the repository at this point in the history
Signed-off-by: Radoslaw Szwajkowski <rszwajko@redhat.com>
  • Loading branch information
rszwajko committed Sep 11, 2024
1 parent de4608e commit e26bcbb
Show file tree
Hide file tree
Showing 11 changed files with 187 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,15 @@ export const useActiveItemState = <
persistTo,
key: "activeItem",
}
: persistTo === "custom"
? {
persistTo,
key: "filters",
serialize: (val) =>
args.customPersistance?.write(JSON.stringify(val)),
deserialize: () =>
JSON.parse(args.customPersistance?.read() ?? "{}") ?? {},
}
: { persistTo }),
});
return { activeItemId, setActiveItemId };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,9 @@ export const useExpansionState = <
? {
persistTo,
keys: ["expandedCells"],
serialize: (expandedCellsObj) => {
serialize: (
expandedCellsObj: Partial<TExpandedCells<TColumnKey>>
) => {
if (!expandedCellsObj || objectKeys(expandedCellsObj).length === 0)
return { expandedCells: null };
return { expandedCells: JSON.stringify(expandedCellsObj) };
Expand All @@ -111,6 +113,15 @@ export const useExpansionState = <
persistTo,
key: "expandedCells",
}
: persistTo === "custom"
? {
persistTo,
key: "filters",
serialize: (val: TExpandedCells<TColumnKey>) =>
args.customPersistance?.write(JSON.stringify(val)),
deserialize: () =>
JSON.parse(args.customPersistance?.read() ?? "{}") ?? {},
}
: { persistTo }),
});
return { expandedCells, setExpandedCells };
Expand Down
19 changes: 19 additions & 0 deletions client/src/app/hooks/table-controls/filtering/useFilterState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ export const useFilterState = <
? args?.initialFilterValues ?? {}
: {};
}
// console.log(
// "useFilter",
// isInitialLoad,
// initialFilterValues,
// isFilterEnabled && args.initialFilterValues
// );

useEffect(() => {
if (isInitialLoad) {
Expand Down Expand Up @@ -104,6 +110,19 @@ export const useFilterState = <
}
: persistTo === "localStorage" || persistTo === "sessionStorage"
? { persistTo, key: "filters" }
: persistTo === "custom"
? {
persistTo,
key: "filters",
serialize: (val) => {
console.log("serialize", val);
args.customPersistance?.write(JSON.stringify(val));
},
deserialize: () => {
console.log("dserialize", args.customPersistance?.read());
return JSON.parse(args.customPersistance?.read() ?? "{}") ?? {};
},
}
: { persistTo }),
});
return { filterValues, setFilterValues };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ export const usePaginationState = <
? {
persistTo,
keys: ["pageNumber", "itemsPerPage"],
serialize: (state) => {
serialize: (state: Partial<IActivePagination>) => {
const { pageNumber, itemsPerPage } = state || {};
return {
pageNumber: pageNumber ? String(pageNumber) : undefined,
Expand All @@ -116,6 +116,15 @@ export const usePaginationState = <
persistTo,
key: "pagination",
}
: persistTo === "custom"
? {
persistTo,
key: "filters",
serialize: (val: IActivePagination) =>
args.customPersistance?.write(JSON.stringify(val)),
deserialize: () =>
JSON.parse(args.customPersistance?.read() ?? "{}") ?? {},
}
: { persistTo }),
});
const { pageNumber, itemsPerPage } = paginationState || defaultValue;
Expand Down
13 changes: 12 additions & 1 deletion client/src/app/hooks/table-controls/sorting/useSortState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,9 @@ export const useSortState = <
? {
persistTo,
keys: ["sortColumn", "sortDirection"],
serialize: (activeSort) => ({
serialize: (
activeSort: Partial<IActiveSort<TSortableColumnKey> | null>
) => ({
sortColumn: activeSort?.columnKey || null,
sortDirection: activeSort?.direction || null,
}),
Expand All @@ -113,6 +115,15 @@ export const useSortState = <
persistTo,
key: "sort",
}
: persistTo === "custom"
? {
persistTo,
key: "sort",
serialize: (val: IActiveSort<TSortableColumnKey> | null) =>
args.customPersistance?.write(JSON.stringify(val)),
deserialize: () =>
JSON.parse(args.customPersistance?.read() ?? "{}") ?? {},
}
: { persistTo }),
});
return { activeSort, setActiveSort };
Expand Down
12 changes: 11 additions & 1 deletion client/src/app/hooks/table-controls/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,14 @@ export type TableFeature =
* - "urlParams" (recommended) - URL query parameters. Persists on page reload, browser history buttons (back/forward) or loading a bookmark. Resets on page navigation.
* - "localStorage" - Browser localStorage API. Persists semi-permanently and is shared across all tabs/windows. Resets only when the user clears their browsing data.
* - "sessionStorage" - Browser sessionStorage API. Persists on page/history navigation/reload. Resets when the tab/window is closed.
* - callback - custom solutions
*/
export type PersistTarget =
| "state"
| "urlParams"
| "localStorage"
| "sessionStorage";
| "sessionStorage"
| "custom";

/**
* Common persistence-specific args
Expand Down Expand Up @@ -107,6 +109,10 @@ export type IFeaturePersistenceArgs<
* Where to persist state for this feature.
*/
persistTo?: PersistTarget;
customPersistance?: {
write: (params: string) => void;
read: () => string;
};
};

export interface ColumnSetting {
Expand All @@ -132,6 +138,10 @@ export type ITablePersistenceArgs<
persistTo?:
| PersistTarget
| Partial<Record<TableFeature | "default", PersistTarget>>;
customPersistance?: {
write: (value: string, key: TableFeature) => void;
read: (key: TableFeature) => string;
};
};

/**
Expand Down
54 changes: 39 additions & 15 deletions client/src/app/hooks/table-controls/useTableControlState.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import {
IFeaturePersistenceArgs,
ITableControlState,
ITablePersistenceArgs,
IUseTableControlStateArgs,
PersistTarget,
TableFeature,
} from "./types";
import { useFilterState } from "./filtering";
Expand All @@ -11,6 +12,30 @@ import { useActiveItemState } from "./active-item";
import { useExpansionState } from "./expansion";
import { useColumnState } from "./column/useColumnState";

const getPersistTo = ({
feature,
persistTo,
customPersistance,
}: {
feature: TableFeature;
persistTo: ITablePersistenceArgs["persistTo"];
customPersistance: ITablePersistenceArgs["customPersistance"];
}): {
persistTo: IFeaturePersistenceArgs["persistTo"];
customPersistance?: IFeaturePersistenceArgs["customPersistance"];
} => ({
persistTo:
!persistTo || typeof persistTo === "string"
? persistTo
: persistTo[feature] || persistTo.default,
customPersistance: customPersistance
? {
read: () => customPersistance?.read(feature) ?? "",
write: (val: string) => customPersistance?.write(val, feature),
}
: undefined,
});

/**
* Provides the "source of truth" state for all table features.
* - State can be persisted in one or more configurable storage targets, either the same for the entire table or different targets per feature.
Expand Down Expand Up @@ -41,31 +66,30 @@ export const useTableControlState = <
TFilterCategoryKey,
TPersistenceKeyPrefix
> => {
const getPersistTo = (feature: TableFeature): PersistTarget | undefined =>
!args.persistTo || typeof args.persistTo === "string"
? args.persistTo
: args.persistTo[feature] || args.persistTo.default;

const { customPersistance, persistTo, ...rest } = args;
const filterState = useFilterState<
TItem,
TFilterCategoryKey,
TPersistenceKeyPrefix
>({ ...args, persistTo: getPersistTo("filter") });
>({
...rest,
...getPersistTo({ feature: "filter", customPersistance, persistTo }),
});
const sortState = useSortState<TSortableColumnKey, TPersistenceKeyPrefix>({
...args,
persistTo: getPersistTo("sort"),
...rest,
...getPersistTo({ feature: "sort", customPersistance, persistTo }),
});
const paginationState = usePaginationState<TPersistenceKeyPrefix>({
...args,
persistTo: getPersistTo("pagination"),
...rest,
...getPersistTo({ customPersistance, persistTo, feature: "pagination" }),
});
const expansionState = useExpansionState<TColumnKey, TPersistenceKeyPrefix>({
...args,
persistTo: getPersistTo("expansion"),
...rest,
...getPersistTo({ customPersistance, persistTo, feature: "expansion" }),
});
const activeItemState = useActiveItemState<TPersistenceKeyPrefix>({
...args,
persistTo: getPersistTo("activeItem"),
...rest,
...getPersistTo({ customPersistance, persistTo, feature: "activeItem" }),
});

const { columnNames, tableName, initialColumns } = args;
Expand Down
41 changes: 41 additions & 0 deletions client/src/app/hooks/usePersistentState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@ import { DisallowCharacters } from "@app/utils/type-utils";

type PersistToStateOptions = { persistTo?: "state" };

type PersistToCustomHookOptions<TValue> = {
persistTo: "custom";
key: string;
defaultValue: TValue;
isEnabled?: boolean;
serialize: (params: TValue) => void;
deserialize: () => TValue;
};

type PersistToUrlParamsOptions<
TValue,
TPersistenceKeyPrefix extends string,
Expand All @@ -33,6 +42,7 @@ export type UsePersistentStateOptions<
| PersistToStateOptions
| PersistToUrlParamsOptions<TValue, TPersistenceKeyPrefix, TURLParamKey>
| PersistToStorageOptions<TValue>
| PersistToCustomHookOptions<TValue>
);

export const usePersistentState = <
Expand Down Expand Up @@ -92,7 +102,38 @@ export const usePersistentState = <
? { ...options, key: prefixKey(options.key) }
: { ...options, isEnabled: false, key: "" }
),
custom: useCustomHook(
isCustomHookOptions(options)
? options
: {
key: "",
serialize: () => {},
deserialize: () => defaultValue,
defaultValue,
isEnabled: false,
persistTo: "custom",
}
),
};
const [value, setValue] = persistence[persistTo || "state"];
return isEnabled ? [value, setValue] : [defaultValue, () => {}];
};

const useCustomHook = <TValue>({
serialize,
deserialize,
defaultValue,
}: PersistToCustomHookOptions<TValue>): [TValue, (val: TValue) => void] => {
// TODO implement default value
return [deserialize(), serialize];
};

export const isCustomHookOptions = <
TValue,
TPersistenceKeyPrefix extends string,
TURLParamKey extends string,
>(
o: Partial<
UsePersistentStateOptions<TValue, TPersistenceKeyPrefix, TURLParamKey>
>
): o is PersistToCustomHookOptions<TValue> => o.persistTo === "custom";
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ export const AnalysisWizard: React.FC<IAnalysisWizard> = ({
mode: "source-code-deps",
formLabels: [],
selectedTargets: [],
targetFilters: { filter: "{}" },
selectedSourceLabels: [],
withKnownLibs: "app",
includedPackages: [],
Expand Down
2 changes: 2 additions & 0 deletions client/src/app/pages/applications/analysis-wizard/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,14 @@ const useModeStepSchema = ({
export interface TargetsStepValues {
formLabels: TargetLabel[];
selectedTargets: Target[];
targetFilters: Record<string, string>;
}

const useTargetsStepSchema = (): yup.SchemaOf<TargetsStepValues> => {
return yup.object({
formLabels: yup.array(),
selectedTargets: yup.array(),
targetFilters: yup.object(),
});
};

Expand Down
33 changes: 31 additions & 2 deletions client/src/app/pages/applications/analysis-wizard/set-targets.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useMemo } from "react";
import React, { useEffect, useMemo } from "react";
import {
Title,
TextContent,
Expand Down Expand Up @@ -173,17 +173,46 @@ const SetTargetsInternal: React.FC<SetTargetsInternalProps> = ({
setValue("formLabels", updatedFormLabels);
};

const storedFilters = getValues().targetFilters["filter"];
console.log("init with", storedFilters, initialFilters);

useEffect(() => {
if (!storedFilters) {
setValue("targetFilters", {
...getValues().targetFilters,
["filter"]: JSON.stringify({ name: initialFilters }),
});
}
}, [storedFilters, initialFilters, setValue, getValues]);

const tableControls = useLocalTableControls({
tableName: "target-cards",
items: targets,
idProperty: "name",
initialFilterValues: { name: initialFilters },
// initialFilterValues: {
// name:
// JSON.parse(getValues().targetFilters["filter"])?.name ?? initialFilters,
// },
columnNames: {
name: "name",
},
isFilterEnabled: true,
isPaginationEnabled: false,
isLoading,
persistTo: { filter: "custom" },
customPersistance: {
write(value, key) {
console.log("write", value, key);
setValue("targetFilters", {
...getValues().targetFilters,
[key]: value,
});
},
read(key) {
console.log("read", key, getValues().targetFilters?.[key]);
return getValues().targetFilters?.[key];
},
},
filterCategories: [
{
selectOptions: languageProviders?.map((language) => ({
Expand Down

0 comments on commit e26bcbb

Please sign in to comment.