111 lines
3.1 KiB
TypeScript
111 lines
3.1 KiB
TypeScript
import type { ThemeMode } from './types';
|
|
|
|
export const PREFERENCES_KEY = '@add-ideas/toolbox-portal:v1:preferences';
|
|
|
|
export interface Preferences {
|
|
version: 1;
|
|
pinned: string[];
|
|
order: string[];
|
|
hidden: string[];
|
|
theme: ThemeMode;
|
|
}
|
|
|
|
export const defaultPreferences: Preferences = {
|
|
version: 1,
|
|
pinned: [],
|
|
order: [],
|
|
hidden: [],
|
|
theme: 'system',
|
|
};
|
|
|
|
function uniqueStrings(value: unknown, field: string): string[] {
|
|
if (
|
|
!Array.isArray(value) ||
|
|
value.some((item) => typeof item !== 'string' || item.length === 0)
|
|
)
|
|
throw new Error(`${field} must be an array of non-empty strings.`);
|
|
return [...new Set(value)];
|
|
}
|
|
|
|
export function parsePreferences(value: unknown): Preferences {
|
|
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
throw new Error('Preferences must be a JSON object.');
|
|
}
|
|
const candidate = value as Record<string, unknown>;
|
|
if (candidate.version !== 1)
|
|
throw new Error('Unsupported preferences version.');
|
|
const theme = candidate.theme;
|
|
if (!['light', 'dark', 'system'].includes(String(theme)))
|
|
throw new Error('Invalid theme preference.');
|
|
return {
|
|
version: 1,
|
|
pinned: uniqueStrings(candidate.pinned, 'pinned'),
|
|
order: uniqueStrings(candidate.order, 'order'),
|
|
hidden: uniqueStrings(candidate.hidden, 'hidden'),
|
|
theme: theme as ThemeMode,
|
|
};
|
|
}
|
|
|
|
export function readPreferences(storage: Storage = localStorage): Preferences {
|
|
const value = storage.getItem(PREFERENCES_KEY);
|
|
if (!value) return { ...defaultPreferences };
|
|
try {
|
|
return parsePreferences(JSON.parse(value));
|
|
} catch {
|
|
return { ...defaultPreferences };
|
|
}
|
|
}
|
|
|
|
export function writePreferences(
|
|
preferences: Preferences,
|
|
storage: Storage = localStorage
|
|
): void {
|
|
storage.setItem(PREFERENCES_KEY, JSON.stringify(preferences));
|
|
}
|
|
|
|
export function cleanPreferences(
|
|
preferences: Preferences,
|
|
appIds: string[]
|
|
): Preferences {
|
|
const known = new Set(appIds);
|
|
return {
|
|
...preferences,
|
|
pinned: preferences.pinned.filter((id) => known.has(id)),
|
|
hidden: preferences.hidden.filter((id) => known.has(id)),
|
|
order: [
|
|
...preferences.order.filter((id) => known.has(id)),
|
|
...appIds.filter((id) => !preferences.order.includes(id)),
|
|
],
|
|
};
|
|
}
|
|
|
|
export function sortAppIds(
|
|
appIds: string[],
|
|
preferences: Preferences
|
|
): string[] {
|
|
const order = new Map(preferences.order.map((id, index) => [id, index]));
|
|
const pinned = new Set(preferences.pinned);
|
|
return [...appIds].sort((left, right) => {
|
|
const pinDifference = Number(pinned.has(right)) - Number(pinned.has(left));
|
|
if (pinDifference) return pinDifference;
|
|
return (
|
|
(order.get(left) ?? Number.MAX_SAFE_INTEGER) -
|
|
(order.get(right) ?? Number.MAX_SAFE_INTEGER)
|
|
);
|
|
});
|
|
}
|
|
|
|
export function moveId(
|
|
order: string[],
|
|
id: string,
|
|
direction: -1 | 1
|
|
): string[] {
|
|
const current = order.indexOf(id);
|
|
if (current < 0) return order;
|
|
const target = current + direction;
|
|
if (target < 0 || target >= order.length) return order;
|
|
const next = [...order];
|
|
[next[current], next[target]] = [next[target]!, next[current]!];
|
|
return next;
|
|
}
|