Files
MyNote/apps/desktop/src/renderer/features/tabs/tab-store.ts
T

95 lines
3.1 KiB
TypeScript

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<TabState>((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 {}
},
}));