/** * Grid Preferences Store for Dockhand * * Manages column visibility and ordering preferences with: * - localStorage sync for flash-free loading * - Database persistence via API * - Per-grid configuration */ import { writable, get } from 'svelte/store'; import type { AllGridPreferences, GridId, ColumnPreference, GridColumnPreferences } from '$lib/types'; import { getDefaultColumnPreferences, getConfigurableColumns } from '$lib/config/grid-columns'; const STORAGE_KEY = 'dockhand-grid-preferences'; // Load initial state from localStorage function loadFromStorage(): AllGridPreferences { if (typeof window === 'undefined') return {}; try { const stored = localStorage.getItem(STORAGE_KEY); if (stored) { return JSON.parse(stored); } } catch { // Ignore parse errors } return {}; } // Save to localStorage function saveToStorage(prefs: AllGridPreferences) { if (typeof window === 'undefined') return; try { localStorage.setItem(STORAGE_KEY, JSON.stringify(prefs)); } catch { // Ignore storage errors } } // Create the store function createGridPreferencesStore() { const { subscribe, set, update } = writable(loadFromStorage()); return { subscribe, // Initialize from API (called on mount) async init() { try { const res = await fetch('/api/preferences/grid'); if (res.ok) { const data = await res.json(); const prefs = data.preferences || {}; set(prefs); saveToStorage(prefs); } } catch { // Use localStorage fallback } }, // Get visible columns for a grid (in order) getVisibleColumns(gridId: GridId): ColumnPreference[] { const prefs = get({ subscribe }); const gridPrefs = prefs[gridId]; if (!gridPrefs?.columns?.length) { // Return defaults (all visible) return getDefaultColumnPreferences(gridId); } // Return columns in saved order, filtering to visible ones return gridPrefs.columns.filter((col) => col.visible); }, // Get all columns for a grid (visible and hidden, in order) getAllColumns(gridId: GridId): ColumnPreference[] { const prefs = get({ subscribe }); const gridPrefs = prefs[gridId]; if (!gridPrefs?.columns?.length) { // Return defaults (all visible) return getDefaultColumnPreferences(gridId); } // Merge with defaults to ensure new columns are included const defaults = getDefaultColumnPreferences(gridId); const savedIds = new Set(gridPrefs.columns.map((c) => c.id)); // Start with saved columns, then add any new defaults const result = [...gridPrefs.columns]; for (const def of defaults) { if (!savedIds.has(def.id)) { result.push(def); } } return result; }, // Check if a specific column is visible isColumnVisible(gridId: GridId, columnId: string): boolean { const prefs = get({ subscribe }); const gridPrefs = prefs[gridId]; if (!gridPrefs?.columns?.length) { // Defaults to visible return true; } const col = gridPrefs.columns.find((c) => c.id === columnId); return col ? col.visible : true; }, // Update column visibility/order for a grid async setColumns(gridId: GridId, columns: ColumnPreference[]) { update((prefs) => { const newPrefs = { ...prefs, [gridId]: { columns } }; saveToStorage(newPrefs); return newPrefs; }); // Save to database (async, non-blocking) try { await fetch('/api/preferences/grid', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ gridId, columns }) }); } catch { // Silently fail - localStorage has the value } }, // Toggle a column's visibility async toggleColumn(gridId: GridId, columnId: string) { const allCols = this.getAllColumns(gridId); const newColumns = allCols.map((col) => col.id === columnId ? { ...col, visible: !col.visible } : col ); await this.setColumns(gridId, newColumns); }, // Reset a grid to default columns async resetGrid(gridId: GridId) { const defaults = getDefaultColumnPreferences(gridId); update((prefs) => { const newPrefs = { ...prefs }; delete newPrefs[gridId]; saveToStorage(newPrefs); return newPrefs; }); // Delete from database try { await fetch(`/api/preferences/grid?gridId=${gridId}`, { method: 'DELETE' }); } catch { // Silently fail } }, // Get ordered column IDs for rendering getColumnOrder(gridId: GridId): string[] { const allCols = this.getAllColumns(gridId); return allCols.filter((c) => c.visible).map((c) => c.id); }, // Get saved width for a specific column getColumnWidth(gridId: GridId, columnId: string): number | undefined { const prefs = get({ subscribe }); const gridPrefs = prefs[gridId]; if (!gridPrefs?.columns?.length) return undefined; const col = gridPrefs.columns.find((c) => c.id === columnId); return col?.width; }, // Get all saved widths as a Map getColumnWidths(gridId: GridId): Map { const prefs = get({ subscribe }); const gridPrefs = prefs[gridId]; const widths = new Map(); if (gridPrefs?.columns) { for (const col of gridPrefs.columns) { if (col.width !== undefined) { widths.set(col.id, col.width); } } } return widths; }, // Set width for a specific column (works for both configurable and fixed columns) async setColumnWidth(gridId: GridId, columnId: string, width: number) { const allCols = this.getAllColumns(gridId); let found = false; const newColumns = allCols.map((col) => { if (col.id === columnId) { found = true; return { ...col, width }; } return col; }); // If column wasn't found (e.g., fixed column), add it if (!found) { newColumns.push({ id: columnId, visible: true, width }); } await this.setColumns(gridId, newColumns); }, // Get current preferences get(): AllGridPreferences { return get({ subscribe }); } }; } export const gridPreferencesStore = createGridPreferencesStore();