Files
dockhand/lib/stores/grid-preferences.ts
Jarek Krochmalski 62e3c6439e Initial commit
2025-12-28 21:16:03 +01:00

227 lines
5.9 KiB
TypeScript

/**
* 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<AllGridPreferences>(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<string, number> {
const prefs = get({ subscribe });
const gridPrefs = prefs[gridId];
const widths = new Map<string, number>();
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();