import { computed, ref } from 'vue'; import { useDebounceFn } from '@vueuse/core'; import { type WorkbenchColumnId, type WorkbenchModuleKey, useWorkbenchModules } from './use-workbench-modules'; import { buildDefaultLayout } from './workbench-layout-default'; import type { LayoutStorage } from './layout-storage'; import { LocalStorageAdapter } from './layout-storage-local'; import { reconcileLayout } from './workbench-layout-reconcile'; import { WORKBENCH_LAYOUT_VERSION, type WorkbenchLayout } from './workbench-layout-types'; export type WorkbenchMode = 'normal' | 'editing'; interface UseWorkbenchLayoutOptions { userId: string; storage?: LayoutStorage; } export function useWorkbenchLayout(options: UseWorkbenchLayoutOptions) { const { getAllModules } = useWorkbenchModules(); const storage = options.storage ?? new LocalStorageAdapter(); const layout = ref(buildDefaultLayout(getAllModules())); const mode = ref('normal'); const dirty = ref(false); const saving = ref(false); const error = ref(null); let snapshotBeforeEdit: WorkbenchLayout | null = null; async function load() { const fromStorage = await storage.load(options.userId); if (fromStorage && fromStorage.version === WORKBENCH_LAYOUT_VERSION) { layout.value = reconcileLayout(fromStorage, getAllModules()); return; } // 版本不匹配 / 无存储:走新默认布局;旧 settings 迁移过来,避免用户偏好(如 shortcut.menuKeys)被 version bump 清空 const fresh = buildDefaultLayout(getAllModules()); if (fromStorage?.settings) { fresh.settings = { ...fromStorage.settings }; } layout.value = fresh; } const persist = useDebounceFn(async () => { saving.value = true; error.value = null; try { await storage.save(options.userId, layout.value); } catch (err) { error.value = err as Error; } finally { saving.value = false; } }, 500); function markDirty() { if (mode.value === 'editing') { dirty.value = true; } else { // 非编辑态写(如折叠)直接落盘 persist(); } } function enterEditing() { snapshotBeforeEdit = JSON.parse(JSON.stringify(layout.value)); mode.value = 'editing'; dirty.value = false; } async function saveEditing() { saving.value = true; try { await storage.save(options.userId, layout.value); mode.value = 'normal'; dirty.value = false; snapshotBeforeEdit = null; } catch (err) { error.value = err as Error; } finally { saving.value = false; } } function cancelEditing() { if (snapshotBeforeEdit) { layout.value = snapshotBeforeEdit; } mode.value = 'normal'; dirty.value = false; snapshotBeforeEdit = null; } function hideModule(key: WorkbenchModuleKey) { for (const col of layout.value.columns) { col.modules = col.modules.filter(k => k !== key); } if (!layout.value.hidden.includes(key)) layout.value.hidden.push(key); markDirty(); } function showModule(key: WorkbenchModuleKey, columnId: WorkbenchColumnId = 'left') { layout.value.hidden = layout.value.hidden.filter(k => k !== key); const target = layout.value.columns.find(c => c.id === columnId); if (target && !target.modules.includes(key)) target.modules.push(key); markDirty(); } function setColumnModules(columnId: WorkbenchColumnId, modules: WorkbenchModuleKey[]) { const target = layout.value.columns.find(c => c.id === columnId); if (target) target.modules = modules; markDirty(); } function toggleCollapse(key: WorkbenchModuleKey) { if (layout.value.collapsed.includes(key)) { layout.value.collapsed = layout.value.collapsed.filter(k => k !== key); } else { layout.value.collapsed.push(key); } markDirty(); } function updateModuleSettings( key: K, value: WorkbenchLayout['settings'][K] ) { layout.value.settings = { ...layout.value.settings, [key]: value }; markDirty(); } async function resetToDefault() { layout.value = buildDefaultLayout(getAllModules()); mode.value = 'normal'; dirty.value = false; snapshotBeforeEdit = null; await storage.save(options.userId, layout.value); } const isCollapsed = (key: WorkbenchModuleKey) => layout.value.collapsed.includes(key); const hiddenMetas = computed(() => { const allMeta = getAllModules(); return layout.value.hidden .map(k => allMeta.find(m => m.key === k)) .filter((m): m is NonNullable => Boolean(m)); }); return { layout, mode, dirty, saving, error, hiddenMetas, isCollapsed, load, enterEditing, saveEditing, cancelEditing, hideModule, showModule, setColumnModules, toggleCollapse, updateModuleSettings, resetToDefault }; }