import Sidebar from '@/components/Sidebar' import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '@/components/ui/resizable' import { cn } from '@/lib/utils' import HomePage from '@/pages/secondary/HomePage' import { cloneElement, createContext, useContext, useEffect, useState } from 'react' import { useScreenSize } from './providers/ScreenSizeProvider' import { routes } from './routes' type TPrimaryPageContext = { refresh: () => void } type TSecondaryPageContext = { push: (url: string) => void pop: () => void } type TStackItem = { index: number url: string component: React.ReactNode | null } const PrimaryPageContext = createContext(undefined) const SecondaryPageContext = createContext(undefined) export function usePrimaryPage() { const context = useContext(PrimaryPageContext) if (!context) { throw new Error('usePrimaryPage must be used within a PrimaryPageContext.Provider') } return context } export function useSecondaryPage() { const context = useContext(SecondaryPageContext) if (!context) { throw new Error('usePrimaryPage must be used within a SecondaryPageContext.Provider') } return context } export function PageManager({ children, maxStackSize = 5 }: { children: React.ReactNode maxStackSize?: number }) { const [primaryPageKey, setPrimaryPageKey] = useState(0) const [secondaryStack, setSecondaryStack] = useState([]) const { isSmallScreen } = useScreenSize() useEffect(() => { if (window.location.pathname !== '/') { pushSecondary(window.location.pathname + window.location.search) } const onPopState = (e: PopStateEvent) => { const state = e.state ?? { index: -1, url: '/' } setSecondaryStack((pre) => { const currentItem = pre[pre.length - 1] const currentIndex = currentItem ? currentItem.index : -1 if (state.index === currentIndex) { return pre } if (state.index < currentIndex) { const newStack = pre.filter((item) => item.index <= state.index) const topItem = newStack[newStack.length - 1] if (topItem && !topItem.component) { topItem.component = findAndCreateComponent(topItem.url) } return newStack } const { newStack } = pushNewPageToStack(pre, state.url, maxStackSize) return newStack }) } window.addEventListener('popstate', onPopState) return () => { window.removeEventListener('popstate', onPopState) } }, []) const refreshPrimary = () => setPrimaryPageKey((prevKey) => prevKey + 1) const pushSecondary = (url: string) => { setSecondaryStack((prevStack) => { if (isCurrentPage(prevStack, url)) return prevStack const { newStack, newItem } = pushNewPageToStack(prevStack, url, maxStackSize) if (newItem) { window.history.pushState({ index: newItem.index, url }, '', url) } return newStack }) } const popSecondary = () => { window.history.back() } if (isSmallScreen) { return (
{!!secondaryStack.length && secondaryStack.map((item, index) => (
{item.component}
))}
{children}
) } return (
{children}
{secondaryStack.length ? ( secondaryStack.map((item, index) => (
{item.component}
)) ) : ( )}
) } export function SecondaryPageLink({ to, children, className, onClick }: { to: string children: React.ReactNode className?: string onClick?: (e: React.MouseEvent) => void }) { const { push } = useSecondaryPage() return ( { if (onClick) { onClick(e) } push(to) }} > {children} ) } function isCurrentPage(stack: TStackItem[], url: string) { const currentPage = stack[stack.length - 1] if (!currentPage) return false return currentPage.url === url } function findAndCreateComponent(url: string) { const path = url.split('?')[0] for (const { matcher, element } of routes) { const match = matcher(path) if (!match) continue if (!element) return null return cloneElement(element, match.params) } return null } function pushNewPageToStack(stack: TStackItem[], url: string, maxStackSize = 5) { const component = findAndCreateComponent(url) if (!component) return { newStack: stack, newItem: null } const currentStack = stack[stack.length - 1] const newItem = { component, url, index: currentStack ? currentStack.index + 1 : 0 } const newStack = [...stack, newItem] const lastCachedIndex = newStack.findIndex((stack) => stack.component) if (newStack.length - lastCachedIndex > maxStackSize) { newStack[lastCachedIndex].component = null } return { newStack, newItem } }