import { defineStore } from 'pinia' import { ref, computed } from 'vue' export interface OpenedTab { id: string title: string path: string icon: string timestamp: number } export const useLayoutStore = defineStore('layout', () => { // Sidebar state const sidebarCollapsed = ref( localStorage.getItem('sidebar-collapsed') === 'true' ) const mobileMenuOpen = ref(false) // Tabs state const openedTabs = ref([]) const activeTabId = ref(null) const maxTabs = ref(8) // Maximum number of open tabs // Notifications const notifications = ref>([]) // Theme const isDarkMode = ref( localStorage.getItem('theme') === 'dark' || (typeof window !== 'undefined' && typeof window.matchMedia === 'function' && window.matchMedia('(prefers-color-scheme: dark)').matches) ) // Save sidebar state const toggleSidebar = () => { sidebarCollapsed.value = !sidebarCollapsed.value localStorage.setItem('sidebar-collapsed', String(sidebarCollapsed.value)) } // Tab management const addTab = (tab: Omit) => { // Standardize tab ID based on path to prevent duplicates const tabId = tab.id || `tab-${tab.path}` const existing = openedTabs.value.find(t => t.path === tab.path) if (existing) { activeTabId.value = existing.id return } // Respect max tabs limit (FIFO eviction) if (openedTabs.value.length >= maxTabs.value) { // Find oldest tab that is NOT currently active if possible const oldestIndex = openedTabs.value.findIndex(t => t.id !== activeTabId.value) if (oldestIndex !== -1) { openedTabs.value.splice(oldestIndex, 1) } else { openedTabs.value.shift() } } const newTab: OpenedTab = { ...tab, id: tabId, timestamp: Date.now(), } openedTabs.value.push(newTab) activeTabId.value = newTab.id saveTabs() } const closeTab = (tabId: string) => { const index = openedTabs.value.findIndex(t => t.id === tabId) if (index === -1) return openedTabs.value.splice(index, 1) // Switch to next tab if closed tab was active if (activeTabId.value === tabId) { activeTabId.value = openedTabs.value[Math.max(0, index - 1)]?.id || null } saveTabs() } const closeAllTabs = () => { openedTabs.value = [] activeTabId.value = null localStorage.removeItem('opened-tabs') } const closeOtherTabs = (tabId: string) => { openedTabs.value = openedTabs.value.filter(t => t.id === tabId) activeTabId.value = tabId saveTabs() } const saveTabs = () => { localStorage.setItem('opened-tabs', JSON.stringify(openedTabs.value)) } const loadTabs = () => { const saved = localStorage.getItem('opened-tabs') if (saved) { try { openedTabs.value = JSON.parse(saved) if (openedTabs.value.length > 0) { activeTabId.value = openedTabs.value[0].id } } catch { openedTabs.value = [] } } } // Notification management const addNotification = ( message: string, type: 'success' | 'error' | 'warning' | 'info' = 'info', duration = 3000 ) => { const id = `notif-${Date.now()}-${Math.random()}` const notif = { id, type, message, timestamp: Date.now(), autoClose: true, duration, } notifications.value.push(notif) if (duration > 0) { setTimeout(() => { removeNotification(id) }, duration) } } const removeNotification = (id: string) => { const index = notifications.value.findIndex(n => n.id === id) if (index !== -1) { notifications.value.splice(index, 1) } } // Theme management const setDarkMode = (dark: boolean) => { isDarkMode.value = dark localStorage.setItem('theme', dark ? 'dark' : 'light') const root = document.documentElement root.setAttribute('data-theme', dark ? 'dark' : 'light') } const toggleTheme = () => { setDarkMode(!isDarkMode.value) } // Computed const hasOpenedTabs = computed(() => openedTabs.value.length > 0) // Load persisted state loadTabs() if (isDarkMode.value) { const root = document.documentElement root.setAttribute('data-theme', 'dark') } return { // Sidebar sidebarCollapsed, mobileMenuOpen, toggleSidebar, // Tabs openedTabs, activeTabId, hasOpenedTabs, addTab, closeTab, closeAllTabs, closeOtherTabs, saveTabs, // Notifications notifications, addNotification, removeNotification, // Theme isDarkMode, setDarkMode, toggleTheme, } })