"use client"; import { useSyncExternalStore } from "react"; import { generateUUID } from "@/utils/id"; import type { NormalizedCubicBezier } from "@/animation/types"; import type { EasingPreset } from "./easing-presets"; const STORAGE_KEY = "graph-editor-presets"; let cachedPresets: EasingPreset[] | null = null; const listeners = new Set<() => void>(); function isValidPresetArray(value: unknown): value is EasingPreset[] { return ( Array.isArray(value) && value.every( (item) => typeof item === "object" && item !== null && typeof item.id === "string" && typeof item.label === "string" && Array.isArray(item.value) && item.value.length === 4 && item.value.every((number: unknown) => typeof number === "number"), ) ); } function readFromStorage(): EasingPreset[] { try { const raw = localStorage.getItem(STORAGE_KEY); if (!raw) return []; const parsed: unknown = JSON.parse(raw); return isValidPresetArray(parsed) ? parsed : []; } catch { // Silently recover — corrupted localStorage shouldn't crash the editor return []; } } function writeToStorage({ presets }: { presets: EasingPreset[] }): void { localStorage.setItem(STORAGE_KEY, JSON.stringify(presets)); } function getSnapshot(): EasingPreset[] { cachedPresets ??= readFromStorage(); return cachedPresets; } function getServerSnapshot(): EasingPreset[] { return []; } function notify(): void { cachedPresets = null; for (const listener of listeners) { listener(); } } function onStorageChange(event: StorageEvent): void { if (event.key === STORAGE_KEY) notify(); } function subscribe(listener: () => void): () => void { if (listeners.size === 0 && typeof window !== "undefined") { window.addEventListener("storage", onStorageChange); } listeners.add(listener); return () => { listeners.delete(listener); if (listeners.size === 0 && typeof window !== "undefined") { window.removeEventListener("storage", onStorageChange); } }; } export function useCustomPresets(): EasingPreset[] { return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot); } export function savePreset({ value }: { value: NormalizedCubicBezier }): void { const current = getSnapshot(); writeToStorage({ presets: [ ...current, { id: generateUUID(), label: `Custom ${current.length + 1}`, value, isCustom: true, }, ], }); notify(); } export function removePreset({ id }: { id: string }): void { writeToStorage({ presets: getSnapshot().filter((preset) => preset.id !== id) }); notify(); }