mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-03-29 13:09:07 +00:00
* Migrate copilot persistence to AppState * Migrate persistence of toggle and history to new infra * Save toggle value as boolean * Fix compile bug * Fix unit tests
109 lines
3.3 KiB
TypeScript
109 lines
3.3 KiB
TypeScript
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
|
|
|
|
// The component name whose state is being saved. Component name must not include special characters.
|
|
export enum AppStateComponentNames {
|
|
DocumentsTab = "DocumentsTab",
|
|
QueryCopilot = "QueryCopilot",
|
|
}
|
|
|
|
export const PATH_SEPARATOR = "/"; // export for testing purposes
|
|
const SCHEMA_VERSION = 1;
|
|
|
|
// Export for testing purposes
|
|
export const MAX_ENTRY_NB = 100_000; // Limit number of entries to 100k
|
|
|
|
export interface StateData {
|
|
schemaVersion: number;
|
|
timestamp: number;
|
|
data: unknown;
|
|
}
|
|
|
|
// Export for testing purposes
|
|
export type StorePath = {
|
|
componentName: AppStateComponentNames;
|
|
subComponentName?: string;
|
|
globalAccountName?: string;
|
|
databaseName?: string;
|
|
containerName?: string;
|
|
};
|
|
|
|
// Load and save state data
|
|
export const loadState = (path: StorePath): unknown => {
|
|
const appState =
|
|
LocalStorageUtility.getEntryObject<ApplicationState>(StorageKey.AppState) || ({} as ApplicationState);
|
|
const key = createKeyFromPath(path);
|
|
return appState[key]?.data;
|
|
};
|
|
export const saveState = (path: StorePath, state: unknown): void => {
|
|
// Retrieve state object
|
|
const appState =
|
|
LocalStorageUtility.getEntryObject<ApplicationState>(StorageKey.AppState) || ({} as ApplicationState);
|
|
const key = createKeyFromPath(path);
|
|
appState[key] = {
|
|
schemaVersion: SCHEMA_VERSION,
|
|
timestamp: Date.now(),
|
|
data: state,
|
|
};
|
|
|
|
if (Object.keys(appState).length > MAX_ENTRY_NB) {
|
|
// Remove the oldest entry
|
|
const oldestKey = Object.keys(appState).reduce((oldest, current) =>
|
|
appState[current].timestamp < appState[oldest].timestamp ? current : oldest,
|
|
);
|
|
delete appState[oldestKey];
|
|
}
|
|
|
|
LocalStorageUtility.setEntryObject(StorageKey.AppState, appState);
|
|
};
|
|
|
|
export const deleteState = (path: StorePath): void => {
|
|
// Retrieve state object
|
|
const appState =
|
|
LocalStorageUtility.getEntryObject<ApplicationState>(StorageKey.AppState) || ({} as ApplicationState);
|
|
const key = createKeyFromPath(path);
|
|
delete appState[key];
|
|
LocalStorageUtility.setEntryObject(StorageKey.AppState, appState);
|
|
};
|
|
|
|
// This is for high-frequency state changes
|
|
let timeoutId: NodeJS.Timeout | undefined;
|
|
export const saveStateDebounced = (path: StorePath, state: unknown, debounceDelayMs = 1000): void => {
|
|
if (timeoutId) {
|
|
clearTimeout(timeoutId);
|
|
}
|
|
timeoutId = setTimeout(() => saveState(path, state), debounceDelayMs);
|
|
};
|
|
|
|
interface ApplicationState {
|
|
[statePath: string]: StateData;
|
|
}
|
|
|
|
const orderedPathSegments: (keyof StorePath)[] = [
|
|
"subComponentName",
|
|
"globalAccountName",
|
|
"databaseName",
|
|
"containerName",
|
|
];
|
|
|
|
/**
|
|
* /componentName/subComponentName/globalAccountName/databaseName/containerName/
|
|
* Any of the path segments can be "" except componentName
|
|
* Export for testing purposes
|
|
* @param path
|
|
*/
|
|
export const createKeyFromPath = (path: StorePath): string => {
|
|
let key = `${PATH_SEPARATOR}${encodeURIComponent(path.componentName)}`; // ComponentName is always there
|
|
orderedPathSegments.forEach((segment) => {
|
|
const segmentValue = path[segment as keyof StorePath];
|
|
key += `${PATH_SEPARATOR}${segmentValue !== undefined ? encodeURIComponent(segmentValue) : ""}`;
|
|
});
|
|
return key;
|
|
};
|
|
|
|
/**
|
|
* Remove the entire app state key from local storage
|
|
*/
|
|
export const deleteAllStates = (): void => {
|
|
LocalStorageUtility.removeEntry(StorageKey.AppState);
|
|
};
|