import { computed, ref } from 'vue' import { defineStore } from 'pinia' import type { WorkspaceTab, WorkspaceTabCandidate, WorkspaceTabsSnapshot } from './types' const STORAGE_PREFIX = 'cc-admin-workspace-tabs:v1' export const WORKSPACE_TAB_LIMIT = 12 export const MAX_CACHED_WORKSPACE_VIEWS = 24 function storageKey(userId: string) { return `${STORAGE_PREFIX}:${userId}` } function validTab(value: unknown): value is WorkspaceTab { if (!value || typeof value !== 'object') return false const tab = value as Partial return typeof tab.id === 'string' && Boolean(tab.id) && typeof tab.title === 'string' && typeof tab.path === 'string' && tab.path.startsWith('/') && typeof tab.lastVisitedAt === 'number' && Number.isFinite(tab.lastVisitedAt) } export function readWorkspaceTabsSnapshot(userId: string): WorkspaceTabsSnapshot { if (!userId) return { tabs: [], activeId: null } try { const parsed = JSON.parse(sessionStorage.getItem(storageKey(userId)) || 'null') as Partial | null if (!parsed || !Array.isArray(parsed.tabs)) return { tabs: [], activeId: null } const tabs = parsed.tabs.filter(validTab) const activeId = typeof parsed.activeId === 'string' && tabs.some((tab) => tab.id === parsed.activeId) ? parsed.activeId : null return { tabs, activeId } } catch { return { tabs: [], activeId: null } } } export function readStoredActiveWorkspacePath(userId: string) { const snapshot = readWorkspaceTabsSnapshot(userId) return snapshot.tabs.find((tab) => tab.id === snapshot.activeId)?.path ?? null } export const useWorkspaceTabsStore = defineStore('workspace-tabs', () => { const tabs = ref([]) const activeId = ref(null) const initializedUserId = ref('') const cacheEpochs = ref>({}) const activeTab = computed(() => tabs.value.find((tab) => tab.id === activeId.value) ?? null) function persist() { if (!initializedUserId.value) return sessionStorage.setItem(storageKey(initializedUserId.value), JSON.stringify({ tabs: tabs.value, activeId: activeId.value, } satisfies WorkspaceTabsSnapshot)) } function initialize(userId: string) { if (!userId || initializedUserId.value === userId) return initializedUserId.value = userId const snapshot = readWorkspaceTabsSnapshot(userId) tabs.value = snapshot.tabs activeId.value = snapshot.activeId } function bumpCacheEpoch(tabId: string) { cacheEpochs.value = { ...cacheEpochs.value, [tabId]: (cacheEpochs.value[tabId] ?? 0) + 1, } } function removeByIds(ids: readonly string[]) { const idSet = new Set(ids) if (idSet.size === 0) return activeTab.value const activeWasRemoved = activeId.value !== null && idSet.has(activeId.value) for (const id of idSet) bumpCacheEpoch(id) tabs.value = tabs.value.filter((tab) => !idSet.has(tab.id)) if (activeWasRemoved) { activeId.value = [...tabs.value].sort((left, right) => right.lastVisitedAt - left.lastVisitedAt)[0]?.id ?? null } else if (activeId.value && !tabs.value.some((tab) => tab.id === activeId.value)) { activeId.value = null } persist() return activeTab.value } function enforceLimit(dirtyTabIds: ReadonlySet) { while (tabs.value.length > WORKSPACE_TAB_LIMIT) { const candidate = tabs.value .filter((tab) => tab.id !== activeId.value && !dirtyTabIds.has(tab.id)) .sort((left, right) => left.lastVisitedAt - right.lastVisitedAt)[0] if (!candidate) break removeByIds([candidate.id]) } } function open(candidate: WorkspaceTabCandidate, dirtyTabIds: ReadonlySet = new Set()) { const now = Date.now() const existing = tabs.value.find((tab) => tab.id === candidate.id) if (existing) { existing.title = candidate.title existing.path = candidate.path existing.lastVisitedAt = now } else { tabs.value.push({ ...candidate, lastVisitedAt: now }) } activeId.value = candidate.id enforceLimit(dirtyTabIds) persist() } function prune(keep: (tab: WorkspaceTab) => boolean) { const removedIds = tabs.value.filter((tab) => !keep(tab)).map((tab) => tab.id) return { removedIds, fallback: removeByIds(removedIds) } } function cacheKey(tabId: string | null) { if (!tabId) return 'workspace-route' return `${tabId}:${cacheEpochs.value[tabId] ?? 0}` } function reset(removePersisted = false) { if (removePersisted && initializedUserId.value) sessionStorage.removeItem(storageKey(initializedUserId.value)) tabs.value = [] activeId.value = null cacheEpochs.value = {} initializedUserId.value = '' } return { tabs, activeId, activeTab, initializedUserId, initialize, open, removeByIds, prune, cacheKey, bumpCacheEpoch, reset, } })