mirror of
https://github.com/khoaliber/dockhand.git
synced 2026-03-07 21:29:06 +00:00
Initial commit
This commit is contained in:
226
lib/stores/grid-preferences.ts
Normal file
226
lib/stores/grid-preferences.ts
Normal file
@@ -0,0 +1,226 @@
|
||||
/**
|
||||
* 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();
|
||||
Reference in New Issue
Block a user