import { create } from 'zustand'; export interface Tab { id: string; path: string; pinned: boolean; scrollTop: number; } interface TabState { tabs: Tab[]; activeId: string | null; openTab: (path: string) => void; closeTab: (id: string) => void; setActive: (id: string) => void; togglePin: (id: string) => void; updateScroll: (id: string, scrollTop: number) => void; // FR-T5:保存滚动位置 closeAll: () => void; // FR-V6: 切库时清空全部标签页 renamePath: (oldPath: string, newPath: string) => void; // FR-T7: 重命名/移动后同步标签页路径 activePath: () => string | null; persist: () => void; restore: () => void; } const SESSION_KEY = 'dsh-session'; export const useTabs = create((set, get) => ({ tabs: [], activeId: null, openTab: (path) => { const existing = get().tabs.find(t => t.path === path); if (existing) { set({ activeId: existing.id }); return; } const id = 'tab-' + Date.now() + '-' + Math.random().toString(36).slice(2, 6); set(s => ({ tabs: [...s.tabs, { id, path, pinned: false, scrollTop: 0 }], activeId: id })); get().persist(); }, closeTab: (id) => { const { tabs, activeId } = get(); const idx = tabs.findIndex(t => t.id === id); const remaining = tabs.filter(t => t.id !== id); let newActive = activeId; if (activeId === id) { // 切到相邻(优先未固定的相邻) const neighbor = remaining[idx] ?? remaining[idx - 1] ?? remaining[remaining.length - 1]; newActive = neighbor?.id ?? null; } set({ tabs: remaining, activeId: newActive }); get().persist(); }, setActive: (id) => { set({ activeId: id }); get().persist(); }, // FR-T5:更新标签页滚动位置(不触发 persist,避免频繁写入) updateScroll: (id, scrollTop) => { set(s => ({ tabs: s.tabs.map(t => t.id === id ? { ...t, scrollTop } : t) })); }, togglePin: (id) => { set(s => { const tabs = s.tabs.map(t => t.id === id ? { ...t, pinned: !t.pinned } : t); // 固定的排前 tabs.sort((a, b) => (a.pinned === b.pinned ? 0 : a.pinned ? -1 : 1)); return { tabs }; }); get().persist(); }, closeAll: () => { set({ tabs: [], activeId: null }); get().persist(); }, renamePath: (oldPath, newPath) => { set(s => ({ tabs: s.tabs.map(t => { if (t.path === oldPath) return { ...t, path: newPath }; // 文件夹重命名/移动:子文档标签页路径前缀替换 if (oldPath && t.path.startsWith(oldPath + '/')) return { ...t, path: newPath + t.path.slice(oldPath.length) }; return t; }), })); get().persist(); }, activePath: () => { const { tabs, activeId } = get(); return tabs.find(t => t.id === activeId)?.path ?? null; }, persist: () => { const { tabs, activeId } = get(); try { localStorage.setItem(SESSION_KEY, JSON.stringify({ tabs, activeId })); } catch {} }, restore: () => { try { const raw = localStorage.getItem(SESSION_KEY); if (!raw) return; const saved = JSON.parse(raw); set({ tabs: saved.tabs ?? [], activeId: saved.activeId ?? null }); } catch {} }, }));