mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-10 23:59:45 +08:00
feat: implement app state management and routing system
- Added AppStateService for managing application state in the database, including methods for getting, setting, and deleting state. - Introduced new API endpoint for app state storage with GET and PUT methods. - Integrated TanStack Router for routing, enabling a tabbed interface with dynamic routing and state synchronization. - Created AppShell component to manage layout and routing, including a sidebar and tab management. - Developed useTabs hook for handling tab state, including adding, closing, and switching tabs. - Updated package.json and yarn.lock to include necessary dependencies for routing and state management. This commit enhances the application's architecture by providing a robust state management system and a flexible routing mechanism, improving user experience and maintainability.
This commit is contained in:
parent
cf7b4dd07b
commit
aae39e3365
@ -1,3 +1,4 @@
|
|||||||
|
import { tanstackRouter } from '@tanstack/router-plugin/vite'
|
||||||
import react from '@vitejs/plugin-react-swc'
|
import react from '@vitejs/plugin-react-swc'
|
||||||
import { CodeInspectorPlugin } from 'code-inspector-plugin'
|
import { CodeInspectorPlugin } from 'code-inspector-plugin'
|
||||||
import { defineConfig, externalizeDepsPlugin } from 'electron-vite'
|
import { defineConfig, externalizeDepsPlugin } from 'electron-vite'
|
||||||
@ -80,6 +81,12 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
renderer: {
|
renderer: {
|
||||||
plugins: [
|
plugins: [
|
||||||
|
tanstackRouter({
|
||||||
|
target: 'react',
|
||||||
|
autoCodeSplitting: true,
|
||||||
|
routesDirectory: './src/renderer/src/routes',
|
||||||
|
generatedRouteTree: './src/renderer/src/routeTree.gen.ts'
|
||||||
|
}),
|
||||||
(async () => (await import('@tailwindcss/vite')).default())(),
|
(async () => (await import('@tailwindcss/vite')).default())(),
|
||||||
react({
|
react({
|
||||||
tsDecorators: true,
|
tsDecorators: true,
|
||||||
|
|||||||
@ -182,7 +182,9 @@
|
|||||||
"@swc/plugin-styled-components": "^8.0.4",
|
"@swc/plugin-styled-components": "^8.0.4",
|
||||||
"@tailwindcss/vite": "^4.1.13",
|
"@tailwindcss/vite": "^4.1.13",
|
||||||
"@tanstack/react-query": "^5.85.5",
|
"@tanstack/react-query": "^5.85.5",
|
||||||
|
"@tanstack/react-router": "^1.139.3",
|
||||||
"@tanstack/react-virtual": "^3.13.12",
|
"@tanstack/react-virtual": "^3.13.12",
|
||||||
|
"@tanstack/router-plugin": "^1.139.3",
|
||||||
"@testing-library/dom": "^10.4.0",
|
"@testing-library/dom": "^10.4.0",
|
||||||
"@testing-library/jest-dom": "^6.6.3",
|
"@testing-library/jest-dom": "^6.6.3",
|
||||||
"@testing-library/react": "^16.3.0",
|
"@testing-library/react": "^16.3.0",
|
||||||
@ -220,8 +222,8 @@
|
|||||||
"@types/mime-types": "^3",
|
"@types/mime-types": "^3",
|
||||||
"@types/node": "^22.17.1",
|
"@types/node": "^22.17.1",
|
||||||
"@types/pako": "^1.0.2",
|
"@types/pako": "^1.0.2",
|
||||||
"@types/react": "^19.0.12",
|
"@types/react": "^19.2.7",
|
||||||
"@types/react-dom": "^19.0.4",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@types/react-infinite-scroll-component": "^5.0.0",
|
"@types/react-infinite-scroll-component": "^5.0.0",
|
||||||
"@types/react-transition-group": "^4.4.12",
|
"@types/react-transition-group": "^4.4.12",
|
||||||
"@types/react-window": "^1",
|
"@types/react-window": "^1",
|
||||||
|
|||||||
@ -18,6 +18,25 @@ export type { ConcreteApiPaths } from './apiPaths'
|
|||||||
* enabling full TypeScript type checking across IPC boundaries.
|
* enabling full TypeScript type checking across IPC boundaries.
|
||||||
*/
|
*/
|
||||||
export interface ApiSchemas {
|
export interface ApiSchemas {
|
||||||
|
/**
|
||||||
|
* App State storage endpoint
|
||||||
|
* @example GET /app/state/tabs_state
|
||||||
|
* @example PUT /app/state/tabs_state { "value": {...} }
|
||||||
|
*/
|
||||||
|
'/app/state/:key': {
|
||||||
|
/** Get app state by key */
|
||||||
|
GET: {
|
||||||
|
params: { key: string }
|
||||||
|
response: any
|
||||||
|
}
|
||||||
|
/** Save app state */
|
||||||
|
PUT: {
|
||||||
|
params: { key: string }
|
||||||
|
body: any
|
||||||
|
response: { success: boolean }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test items collection endpoint
|
* Test items collection endpoint
|
||||||
* @example GET /test/items?page=1&limit=10&search=hello
|
* @example GET /test/items?page=1&limit=10&search=hello
|
||||||
|
|||||||
8
packages/shared/data/cache/cacheSchemas.ts
vendored
8
packages/shared/data/cache/cacheSchemas.ts
vendored
@ -26,6 +26,9 @@ export type UseCacheSchema = {
|
|||||||
'topic.active': CacheValueTypes.CacheTopic | null
|
'topic.active': CacheValueTypes.CacheTopic | null
|
||||||
'topic.renaming': string[]
|
'topic.renaming': string[]
|
||||||
'topic.newly_renamed': string[]
|
'topic.newly_renamed': string[]
|
||||||
|
|
||||||
|
// UI State
|
||||||
|
'ui.activeTabId': string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DefaultUseCache: UseCacheSchema = {
|
export const DefaultUseCache: UseCacheSchema = {
|
||||||
@ -56,7 +59,10 @@ export const DefaultUseCache: UseCacheSchema = {
|
|||||||
// Topic management
|
// Topic management
|
||||||
'topic.active': null,
|
'topic.active': null,
|
||||||
'topic.renaming': [],
|
'topic.renaming': [],
|
||||||
'topic.newly_renamed': []
|
'topic.newly_renamed': [],
|
||||||
|
|
||||||
|
// UI State
|
||||||
|
'ui.activeTabId': ''
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -5,6 +5,7 @@
|
|||||||
* TypeScript will error if any endpoint is missing.
|
* TypeScript will error if any endpoint is missing.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { appStateService } from '@data/services/AppStateService'
|
||||||
import { TestService } from '@data/services/TestService'
|
import { TestService } from '@data/services/TestService'
|
||||||
import type { ApiImplementation } from '@shared/data/api/apiSchemas'
|
import type { ApiImplementation } from '@shared/data/api/apiSchemas'
|
||||||
|
|
||||||
@ -16,6 +17,18 @@ const testService = TestService.getInstance()
|
|||||||
* Must implement every path+method combination from ApiSchemas
|
* Must implement every path+method combination from ApiSchemas
|
||||||
*/
|
*/
|
||||||
export const apiHandlers: ApiImplementation = {
|
export const apiHandlers: ApiImplementation = {
|
||||||
|
'/app/state/:key': {
|
||||||
|
GET: async ({ params }) => {
|
||||||
|
const state = await appStateService.getState(params.key)
|
||||||
|
return state
|
||||||
|
},
|
||||||
|
|
||||||
|
PUT: async ({ params, body }) => {
|
||||||
|
await appStateService.setState(params.key, body)
|
||||||
|
return { success: true }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
'/test/items': {
|
'/test/items': {
|
||||||
GET: async ({ query }) => {
|
GET: async ({ query }) => {
|
||||||
return await testService.getItems({
|
return await testService.getItems({
|
||||||
|
|||||||
100
src/main/data/services/AppStateService.ts
Normal file
100
src/main/data/services/AppStateService.ts
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
import { dbService } from '@data/db/DbService'
|
||||||
|
import { appStateTable } from '@data/db/schemas/appState'
|
||||||
|
import { loggerService } from '@logger'
|
||||||
|
import { eq } from 'drizzle-orm'
|
||||||
|
|
||||||
|
const logger = loggerService.withContext('AppStateService')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service for managing application state in the database.
|
||||||
|
* Provides key-value storage for persisting UI state like tabs, window positions, etc.
|
||||||
|
*/
|
||||||
|
export class AppStateService {
|
||||||
|
private static instance: AppStateService
|
||||||
|
|
||||||
|
private constructor() {}
|
||||||
|
|
||||||
|
public static getInstance(): AppStateService {
|
||||||
|
if (!AppStateService.instance) {
|
||||||
|
AppStateService.instance = new AppStateService()
|
||||||
|
}
|
||||||
|
return AppStateService.instance
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get app state by key
|
||||||
|
* @param key - The state key to retrieve
|
||||||
|
* @returns The stored value or null if not found
|
||||||
|
*/
|
||||||
|
async getState<T = unknown>(key: string): Promise<T | null> {
|
||||||
|
try {
|
||||||
|
const db = dbService.getDb()
|
||||||
|
const result = await db.select().from(appStateTable).where(eq(appStateTable.key, key)).limit(1)
|
||||||
|
|
||||||
|
if (result.length === 0) {
|
||||||
|
logger.debug('App state not found', { key })
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug('Retrieved app state', { key })
|
||||||
|
return result[0].value as T
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Failed to get app state', error as Error, { key })
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save app state by key (upsert)
|
||||||
|
* @param key - The state key
|
||||||
|
* @param value - The value to store (will be JSON serialized)
|
||||||
|
* @param description - Optional description of what this state stores
|
||||||
|
*/
|
||||||
|
async setState<T = unknown>(key: string, value: T, description?: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
const db = dbService.getDb()
|
||||||
|
|
||||||
|
await db
|
||||||
|
.insert(appStateTable)
|
||||||
|
.values({
|
||||||
|
key,
|
||||||
|
value: value as any,
|
||||||
|
description
|
||||||
|
})
|
||||||
|
.onConflictDoUpdate({
|
||||||
|
target: appStateTable.key,
|
||||||
|
set: {
|
||||||
|
value: value as any,
|
||||||
|
description,
|
||||||
|
updatedAt: Date.now()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
logger.debug('Saved app state', { key })
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Failed to save app state', error as Error, { key })
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete app state by key
|
||||||
|
* @param key - The state key to delete
|
||||||
|
* @returns true if deleted, false if not found
|
||||||
|
*/
|
||||||
|
async deleteState(key: string): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const db = dbService.getDb()
|
||||||
|
const result = await db.delete(appStateTable).where(eq(appStateTable.key, key))
|
||||||
|
|
||||||
|
const deleted = result.rowsAffected > 0
|
||||||
|
logger.debug('Deleted app state', { key, deleted })
|
||||||
|
return deleted
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Failed to delete app state', error as Error, { key })
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const appStateService = AppStateService.getInstance()
|
||||||
17
src/renderer/src/AppRouter.tsx
Normal file
17
src/renderer/src/AppRouter.tsx
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { createRouter, RouterProvider } from '@tanstack/react-router'
|
||||||
|
|
||||||
|
import { routeTree } from './routeTree.gen'
|
||||||
|
|
||||||
|
// Create a new router instance
|
||||||
|
const router = createRouter({ routeTree })
|
||||||
|
|
||||||
|
// Register the router instance for type safety
|
||||||
|
declare module '@tanstack/react-router' {
|
||||||
|
interface Register {
|
||||||
|
router: typeof router
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AppRouter = () => {
|
||||||
|
return <RouterProvider router={router} />
|
||||||
|
}
|
||||||
187
src/renderer/src/components/layout/AppShell.tsx
Normal file
187
src/renderer/src/components/layout/AppShell.tsx
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
// TODO demo component
|
||||||
|
import { cn } from '@cherrystudio/ui'
|
||||||
|
import { Link, Outlet, useLocation, useNavigate } from '@tanstack/react-router'
|
||||||
|
import { X } from 'lucide-react'
|
||||||
|
import { useEffect } from 'react'
|
||||||
|
|
||||||
|
import { useTabs } from '../../hooks/useTabs'
|
||||||
|
|
||||||
|
// Mock Sidebar component (Replace with actual one later)
|
||||||
|
const Sidebar = ({ onNavigate }: { onNavigate: (id: string) => void }) => {
|
||||||
|
// Helper to render a Sidebar Link that acts as a Tab Switcher
|
||||||
|
const SidebarItem = ({ to, title, id }: { to: string; title: string; id: string }) => (
|
||||||
|
<Link
|
||||||
|
to={to}
|
||||||
|
className="flex h-10 w-10 items-center justify-center rounded-md hover:bg-accent data-[status=active]:bg-primary/20 data-[status=active]:font-bold"
|
||||||
|
activeProps={{
|
||||||
|
'data-status': 'active'
|
||||||
|
}}
|
||||||
|
onClick={(e) => {
|
||||||
|
// Intercept the router navigation!
|
||||||
|
// We want to switch tabs, not just navigate within the current tab.
|
||||||
|
e.preventDefault()
|
||||||
|
onNavigate(id)
|
||||||
|
}}>
|
||||||
|
{title.slice(0, 1).toUpperCase() + title.slice(1, 3)}
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<aside className="flex h-full w-16 flex-col items-center gap-4 border-r bg-muted/10 py-4">
|
||||||
|
<div className="flex h-10 w-10 items-center justify-center rounded-md bg-primary/20 font-bold text-xs">Logo</div>
|
||||||
|
|
||||||
|
<SidebarItem to="/" title="Home" id="home" />
|
||||||
|
<SidebarItem to="/settings" title="Settings" id="settings" />
|
||||||
|
|
||||||
|
<div className="flex-1" />
|
||||||
|
<button type="button" className="flex h-10 w-10 items-center justify-center rounded-md hover:bg-accent">
|
||||||
|
User
|
||||||
|
</button>
|
||||||
|
</aside>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mock MinApp component (Replace with actual implementation)
|
||||||
|
const MinApp = ({ url }: { url: string }) => (
|
||||||
|
<div className="flex h-full w-full flex-col items-center justify-center bg-background">
|
||||||
|
<div className="mb-2 font-bold text-lg">Webview App</div>
|
||||||
|
<code className="rounded bg-muted p-2">{url}</code>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const AppShell = () => {
|
||||||
|
const { tabs, activeTabId, setActiveTab, closeTab, addTab, updateTab } = useTabs()
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const location = useLocation()
|
||||||
|
|
||||||
|
// 1. Sync Route -> Tab (Handle internal navigation & deep links)
|
||||||
|
useEffect(() => {
|
||||||
|
const currentPath = location.pathname
|
||||||
|
const activeTab = tabs.find((t) => t.id === activeTabId)
|
||||||
|
|
||||||
|
if (activeTab?.type === 'url' && activeTab.url !== currentPath) {
|
||||||
|
const existingTab = tabs.find((t) => t.type === 'url' && t.url === currentPath && t.id !== activeTabId)
|
||||||
|
if (existingTab) {
|
||||||
|
setActiveTab(existingTab.id)
|
||||||
|
} else {
|
||||||
|
// Sync URL changes back to DB
|
||||||
|
updateTab(activeTabId, { url: currentPath })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [location.pathname, tabs, activeTabId, setActiveTab, updateTab])
|
||||||
|
|
||||||
|
// 2. Sync Tab -> Route (Handle tab switching)
|
||||||
|
useEffect(() => {
|
||||||
|
const activeTab = tabs.find((t) => t.id === activeTabId)
|
||||||
|
if (!activeTab) return
|
||||||
|
|
||||||
|
if (activeTab.type === 'url') {
|
||||||
|
if (location.pathname !== activeTab.url) {
|
||||||
|
navigate({ to: activeTab.url })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [activeTabId, tabs, navigate, location.pathname])
|
||||||
|
|
||||||
|
const handleSidebarClick = (menuId: string) => {
|
||||||
|
let targetUrl = ''
|
||||||
|
let targetTitle = ''
|
||||||
|
|
||||||
|
switch (menuId) {
|
||||||
|
case 'home':
|
||||||
|
targetUrl = '/'
|
||||||
|
targetTitle = 'Home'
|
||||||
|
break
|
||||||
|
case 'settings':
|
||||||
|
targetUrl = '/settings'
|
||||||
|
targetTitle = 'Settings'
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingTab = tabs.find((t) => t.type === 'url' && t.url === targetUrl)
|
||||||
|
|
||||||
|
if (existingTab) {
|
||||||
|
setActiveTab(existingTab.id)
|
||||||
|
} else {
|
||||||
|
addTab({
|
||||||
|
id: `${menuId}-${Date.now()}`,
|
||||||
|
type: 'url',
|
||||||
|
url: targetUrl,
|
||||||
|
title: targetTitle
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const activeTab = tabs.find((t) => t.id === activeTabId)
|
||||||
|
const isWebviewActive = activeTab?.type === 'webview'
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex h-screen w-screen flex-row overflow-hidden bg-background text-foreground">
|
||||||
|
{/* Zone 1: Sidebar */}
|
||||||
|
<Sidebar onNavigate={handleSidebarClick} />
|
||||||
|
|
||||||
|
<div className="flex h-full min-w-0 flex-1 flex-col">
|
||||||
|
{/* Zone 2: Tab Bar */}
|
||||||
|
<header className="flex h-10 w-full items-center border-b bg-muted/5">
|
||||||
|
<div className="hide-scrollbar flex-1 overflow-x-auto">
|
||||||
|
<div className="flex h-full w-full items-center justify-start">
|
||||||
|
{tabs.map((tab) => (
|
||||||
|
<Link
|
||||||
|
key={tab.id}
|
||||||
|
to={tab.url}
|
||||||
|
onClick={() => setActiveTab(tab.id)}
|
||||||
|
className={cn(
|
||||||
|
'relative flex h-full min-w-[120px] max-w-[200px] items-center justify-between gap-2 border-border/40 border-r px-3 py-2 text-sm transition-colors hover:bg-muted/50',
|
||||||
|
tab.id === activeTabId ? 'bg-background shadow-sm' : 'bg-transparent opacity-70 hover:opacity-100'
|
||||||
|
)}>
|
||||||
|
<span className="truncate text-xs">{tab.title}</span>
|
||||||
|
<div
|
||||||
|
role="button"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
e.preventDefault()
|
||||||
|
closeTab(tab.id)
|
||||||
|
}}
|
||||||
|
className="ml-1 cursor-pointer rounded-sm p-0.5 opacity-50 hover:bg-muted-foreground/20 hover:opacity-100">
|
||||||
|
<X className="size-3" />
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{/* Zone 3: Content Area (Simplified Hybrid Architecture) */}
|
||||||
|
<main className="relative flex-1 overflow-hidden bg-background">
|
||||||
|
{/* Layer A: Standard Router Outlet */}
|
||||||
|
{/* Always rendered, but hidden if a webview is active. This keeps the Router alive. */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: isWebviewActive ? 'none' : 'block',
|
||||||
|
height: '100%',
|
||||||
|
width: '100%'
|
||||||
|
}}>
|
||||||
|
<Outlet />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Layer B: Webview Apps (Overlay) */}
|
||||||
|
{tabs.map((tab) => {
|
||||||
|
if (tab.type !== 'webview') return null
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={tab.id}
|
||||||
|
style={{
|
||||||
|
display: tab.id === activeTabId ? 'block' : 'none',
|
||||||
|
height: '100%',
|
||||||
|
width: '100%'
|
||||||
|
}}>
|
||||||
|
<MinApp url={tab.url} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
107
src/renderer/src/components/layout/router-architecture.md
Normal file
107
src/renderer/src/components/layout/router-architecture.md
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
# Router Architecture & Tab System
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This document describes the routing and tab management architecture for Cherry Studio. The system implements a "Chrome-like" tabbed interface where every tab can be a distinct application state (Chat, Settings, MinApp/Webview).
|
||||||
|
|
||||||
|
The architecture is **Hybrid**, combining:
|
||||||
|
|
||||||
|
1. **TanStack Router (HashRouter)**: Handles URL parsing and standard React page rendering. Standard pages (Home, Settings) are re-mounted on tab switch but optimized via data caching.
|
||||||
|
2. **Webview Overlay System**: Manages persistent processes (Webviews) that live *outside* the router to ensure they are never destroyed during navigation.
|
||||||
|
3. **Bidirectional State Sync**: `AppShell` ensures the URL bar and the Tab Database (`app_state` table) are always in sync.
|
||||||
|
|
||||||
|
## Core Architecture
|
||||||
|
|
||||||
|
### 1. Hybrid Rendering
|
||||||
|
|
||||||
|
We use a "Single Router + Overlay" approach. We do **not** force `KeepAlive` for standard React pages, as it complicates the router logic significantly. Instead, we rely on TanStack Router's fast caching to make re-mounting feel instant.
|
||||||
|
|
||||||
|
* **Layer 1: Standard Router (The Outlet)**
|
||||||
|
* Always present in the DOM.
|
||||||
|
* Renders standard pages (Home, Settings, Native Chat).
|
||||||
|
* **Behavior**: When switching between standard tabs (e.g., Home -> Settings), the router navigates, unmounting the old component and mounting the new one.
|
||||||
|
* **Optimization**: Data loaders are cached, so "re-mounting" is cheap and fast.
|
||||||
|
* **Visibility**: Hidden via `display: none` if a Webview tab is active.
|
||||||
|
|
||||||
|
* **Layer 2: Webview Overlays**
|
||||||
|
* Rendered **outside** the Router.
|
||||||
|
* **Behavior**: These components are *never* unmounted as long as the tab is open.
|
||||||
|
* **Visibility**: Controlled purely by CSS (`display: block` / `none`).
|
||||||
|
* **Purpose**: To keep heavy processes (like MinApps or external websites) alive in the background.
|
||||||
|
|
||||||
|
### 2. State Synchronization (The "Listener" Pattern)
|
||||||
|
|
||||||
|
Since we use a single Router instance, we must manually sync the "Active Tab's URL" with the "Router's URL".
|
||||||
|
|
||||||
|
* **URL -> Database (Passive Sync)**:
|
||||||
|
* A `useEffect` hook in `AppShell` listens to `location.pathname`.
|
||||||
|
* If the URL changes (e.g., user navigates inside a Chat tab), we update the current tab's `url` field in the SQLite database.
|
||||||
|
* *Benefit*: Restores the exact sub-route (e.g., `/chat/session-123`) when the user comes back later.
|
||||||
|
|
||||||
|
* **Tab Switch -> Router (Active Navigation)**:
|
||||||
|
* When the user clicks a tab, we read its stored `url` from the database.
|
||||||
|
* We calling `navigate({ to: storedUrl })` to restore the view.
|
||||||
|
|
||||||
|
### 3. Data Management
|
||||||
|
|
||||||
|
* **Storage**: Tab data (`tabs`, `activeTabId`) is stored in SQLite (`app_state` table).
|
||||||
|
* **Sync**: `useTabs` hook uses SWR (`useQuery`) to sync frontend state with the database.
|
||||||
|
* **Optimistic Updates**: UI updates immediately, background sync handles persistence.
|
||||||
|
|
||||||
|
## Routing & Overlay Mapping
|
||||||
|
|
||||||
|
For detailed route tree definitions and component mappings, please refer to [Router Planning](./router-planning.md).
|
||||||
|
|
||||||
|
### Handling Webview Routes
|
||||||
|
|
||||||
|
The planning document mentions routes like `/apps/$appId` that may correspond to Webview applications. In our Hybrid Architecture, these are handled as follows:
|
||||||
|
|
||||||
|
1. **Router Layer**: The route `/apps/$appId` is still defined in TSR.
|
||||||
|
* Purpose: Maintains URL semantics and supports deep linking.
|
||||||
|
* Rendering: Renders a "shell" component or loading state.
|
||||||
|
2. **Overlay Layer**: `AppShell` detects that the current Tab type is `webview`.
|
||||||
|
* Behavior: Hides the Router's Outlet.
|
||||||
|
* Rendering: Displays the corresponding `<Webview />` instance in the Overlay layer.
|
||||||
|
|
||||||
|
This mechanism ensures that even Webview apps have standard URLs, providing a consistent navigation experience across the application.
|
||||||
|
|
||||||
|
## Key Components
|
||||||
|
|
||||||
|
### `AppShell` (`src/renderer/src/components/layout/AppShell.tsx`)
|
||||||
|
|
||||||
|
The coordinator that manages the two layers.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
return (
|
||||||
|
<div className="app-shell">
|
||||||
|
<Sidebar />
|
||||||
|
<div className="main-content">
|
||||||
|
<TabBar />
|
||||||
|
|
||||||
|
{/* Layer 1: Standard Router (Hidden if Webview is active) */}
|
||||||
|
<div style={{ display: isWebviewActive ? 'none' : 'block' }}>
|
||||||
|
<Outlet />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Layer 2: Webview Overlays (Only for type='webview') */}
|
||||||
|
{tabs.map(tab => {
|
||||||
|
if (tab.type !== 'webview') return null;
|
||||||
|
return (
|
||||||
|
<div style={{ display: isActive ? 'block' : 'none' }}>
|
||||||
|
<Webview url={tab.url} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Trade-offs
|
||||||
|
|
||||||
|
| Feature | Approach | Rationale |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| **Standard Pages** | Re-mount on switch | Simplicity. Reactivity problems with KeepAlive are avoided. TSR caching makes it fast. |
|
||||||
|
| **Webviews** | Keep-Alive (CSS Hide) | Essential. Reloading an external app/website is bad UX. |
|
||||||
|
| **Routing** | HashRouter | Native to Electron file system. Avoids history API complexities. |
|
||||||
|
| **URL Logic** | Single Source of Truth | The address bar always reflects the *active* tab. Background tabs are just state in DB. |
|
||||||
347
src/renderer/src/components/layout/router-planning.md
Normal file
347
src/renderer/src/components/layout/router-planning.md
Normal file
@ -0,0 +1,347 @@
|
|||||||
|
# Router Planning
|
||||||
|
|
||||||
|
> Version: v0.1.0
|
||||||
|
> Updated: 2025-11-25
|
||||||
|
> Status: Draft
|
||||||
|
|
||||||
|
## 1. Overview
|
||||||
|
|
||||||
|
This document defines the routing structure plan for migrating Cherry Studio from React Router to TanStack Router (TSR).
|
||||||
|
|
||||||
|
### 1.1 Core Interaction Model
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Left Sidebar │ Top Tab Bar │
|
||||||
|
│ (Shortcuts) │ [Tab1] [Tab2] [Tab3] [+] │
|
||||||
|
│ ┌───────────┐ ├──────────────────────────────────────────┤
|
||||||
|
│ │ 💬 Chat │ │ │
|
||||||
|
│ │ ⚙️ Settings│ │ Content Area (Outlet) │
|
||||||
|
│ │ 📁 Files │ │ │
|
||||||
|
│ │ 📝 Notes │ │ Rendered based on active Tab's URL │
|
||||||
|
│ │ ... │ │ │
|
||||||
|
│ └───────────┘ │ │
|
||||||
|
└─────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Left Sidebar**: Like a "bookmarks bar", stores shortcuts. Clicking navigates to the URL (may reuse existing Tab or create new Tab)
|
||||||
|
- **Top Tab Bar**: Manages multiple open pages, supports closing and switching
|
||||||
|
- **Content Area**: Rendered by TanStack Router's `<Outlet />`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Route Structure
|
||||||
|
|
||||||
|
### 2.1 Directory Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
src/renderer/src/routes/
|
||||||
|
├── __root.tsx # Root route → AppShell
|
||||||
|
├── index.tsx # / → Welcome page or redirect (TBD)
|
||||||
|
│
|
||||||
|
├── chat/
|
||||||
|
│ ├── route.tsx # /chat layout: sidebar + <Outlet/>
|
||||||
|
│ ├── index.tsx # /chat → Empty state (no topic selected)
|
||||||
|
│ └── $assistantId/
|
||||||
|
│ ├── route.tsx # /chat/$assistantId layout (optional)
|
||||||
|
│ ├── index.tsx # /chat/$assistantId → Assistant home (optional)
|
||||||
|
│ └── $topicId.tsx # /chat/$assistantId/$topicId → Chat view
|
||||||
|
│
|
||||||
|
├── settings/
|
||||||
|
│ ├── route.tsx # /settings layout: menu + <Outlet/>
|
||||||
|
│ ├── index.tsx # /settings → Redirect to default sub-page
|
||||||
|
│ ├── provider.tsx # /settings/provider
|
||||||
|
│ ├── model.tsx # /settings/model
|
||||||
|
│ ├── general.tsx # /settings/general
|
||||||
|
│ ├── display.tsx # /settings/display
|
||||||
|
│ ├── data.tsx # /settings/data
|
||||||
|
│ ├── mcp.tsx # /settings/mcp
|
||||||
|
│ ├── shortcut.tsx # /settings/shortcut
|
||||||
|
│ └── about.tsx # /settings/about
|
||||||
|
│
|
||||||
|
├── knowledge/
|
||||||
|
│ ├── route.tsx # /knowledge layout
|
||||||
|
│ ├── index.tsx # /knowledge → Knowledge base list
|
||||||
|
│ └── $baseId.tsx # /knowledge/$baseId → Knowledge base detail
|
||||||
|
│
|
||||||
|
├── notes/
|
||||||
|
│ ├── route.tsx # /notes layout: tree sidebar + <Outlet/>
|
||||||
|
│ ├── index.tsx # /notes → Empty state
|
||||||
|
│ └── $noteId.tsx # /notes/$noteId → Editor
|
||||||
|
│
|
||||||
|
├── apps/
|
||||||
|
│ ├── route.tsx # /apps layout
|
||||||
|
│ ├── index.tsx # /apps → App list
|
||||||
|
│ └── $appId.tsx # /apps/$appId → App detail (possibly Webview)
|
||||||
|
│
|
||||||
|
├── paintings/
|
||||||
|
│ ├── route.tsx # /paintings layout: provider select + <Outlet/>
|
||||||
|
│ ├── index.tsx # /paintings → Redirect to default provider
|
||||||
|
│ ├── zhipu.tsx # /paintings/zhipu → Zhipu painting
|
||||||
|
│ ├── aihubmix.tsx # /paintings/aihubmix → Aihubmix
|
||||||
|
│ ├── silicon.tsx # /paintings/silicon → Silicon Flow
|
||||||
|
│ ├── dmxapi.tsx # /paintings/dmxapi → Dmxapi
|
||||||
|
│ ├── tokenflux.tsx # /paintings/tokenflux → TokenFlux
|
||||||
|
│ ├── ovms.tsx # /paintings/ovms → OVMS
|
||||||
|
│ └── $providerId.tsx # /paintings/$providerId → Dynamic NewApi provider
|
||||||
|
│
|
||||||
|
├── files.tsx # /files → File management
|
||||||
|
├── translate.tsx # /translate → Translation
|
||||||
|
├── store.tsx # /store → App store
|
||||||
|
└── launchpad.tsx # /launchpad → Launchpad
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 Route Table
|
||||||
|
|
||||||
|
| Route | Component | Loader Data | Description |
|
||||||
|
|-------|-----------|-------------|-------------|
|
||||||
|
| `/` | `WelcomePage` | - | Welcome page or redirect (TBD) |
|
||||||
|
| `/chat` | `ChatLayout` | Assistants, Topics | Chat layout layer |
|
||||||
|
| `/chat/$assistantId/$topicId` | `ChatView` | Topic detail, Messages | Chat main view |
|
||||||
|
| `/settings` | `SettingsLayout` | - | Settings layout layer |
|
||||||
|
| `/settings/provider` | `ProviderSettings` | Provider list | Provider settings |
|
||||||
|
| `/settings/model` | `ModelSettings` | Model list | Model settings |
|
||||||
|
| `/settings/*` | `*Settings` | Respective data | Other settings pages |
|
||||||
|
| `/knowledge` | `KnowledgeLayout` | Knowledge bases | Knowledge layout |
|
||||||
|
| `/knowledge/$baseId` | `KnowledgeDetail` | Knowledge detail | Knowledge detail page |
|
||||||
|
| `/notes` | `NotesLayout` | Notes tree | Notes layout |
|
||||||
|
| `/notes/$noteId` | `NotesEditor` | Note content | Notes editor |
|
||||||
|
| `/apps` | `AppsLayout` | App list | Apps layout |
|
||||||
|
| `/apps/$appId` | `AppDetail` | App detail | App detail/Webview |
|
||||||
|
| `/paintings` | `PaintingsLayout` | Provider list | Paintings layout layer |
|
||||||
|
| `/paintings/zhipu` | `ZhipuPage` | - | Zhipu painting |
|
||||||
|
| `/paintings/aihubmix` | `AihubmixPage` | - | Aihubmix painting |
|
||||||
|
| `/paintings/silicon` | `SiliconPage` | - | Silicon Flow painting |
|
||||||
|
| `/paintings/dmxapi` | `DmxapiPage` | - | Dmxapi painting |
|
||||||
|
| `/paintings/tokenflux` | `TokenFluxPage` | - | TokenFlux painting |
|
||||||
|
| `/paintings/ovms` | `OvmsPage` | - | OVMS painting |
|
||||||
|
| `/paintings/$providerId` | `NewApiPage` | - | Dynamic NewApi provider |
|
||||||
|
| `/files` | `FilesPage` | File list | File management |
|
||||||
|
| `/translate` | `TranslatePage` | - | Translation page |
|
||||||
|
| `/store` | `StorePage` | Store data | App store |
|
||||||
|
| `/launchpad` | `LaunchpadPage` | - | Launchpad |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Chat Route Design
|
||||||
|
|
||||||
|
### 3.1 URL Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
/chat/$assistantId/$topicId
|
||||||
|
│ │
|
||||||
|
│ └── Topic ID (conversation ID)
|
||||||
|
└── Assistant ID
|
||||||
|
```
|
||||||
|
|
||||||
|
**Examples**:
|
||||||
|
|
||||||
|
- `/chat` → Chat home (sidebar + empty state)
|
||||||
|
- `/chat/assistant-1` → Assistant 1's home (optional, may redirect to first topic)
|
||||||
|
- `/chat/assistant-1/topic-123` → Chat view for topic 123 under assistant 1
|
||||||
|
|
||||||
|
### 3.2 Component Structure
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// routes/chat/route.tsx
|
||||||
|
export const Route = createFileRoute('/chat')({
|
||||||
|
component: ChatLayout,
|
||||||
|
loader: async () => ({
|
||||||
|
assistants: await fetchAssistants(),
|
||||||
|
topics: await fetchTopics()
|
||||||
|
}),
|
||||||
|
staleTime: 30_000,
|
||||||
|
})
|
||||||
|
|
||||||
|
function ChatLayout() {
|
||||||
|
const data = Route.useLoaderData()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex h-full">
|
||||||
|
{/* Sidebar: Assistant list + Topic list */}
|
||||||
|
<ChatSidebar assistants={data.assistants} topics={data.topics} />
|
||||||
|
|
||||||
|
{/* Chat content area */}
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<Outlet />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// routes/chat/$assistantId/$topicId.tsx
|
||||||
|
export const Route = createFileRoute('/chat/$assistantId/$topicId')({
|
||||||
|
component: ChatView,
|
||||||
|
loader: async ({ params }) => ({
|
||||||
|
topic: await fetchTopic(params.topicId),
|
||||||
|
messages: await fetchMessages(params.topicId)
|
||||||
|
}),
|
||||||
|
staleTime: 10_000,
|
||||||
|
})
|
||||||
|
|
||||||
|
function ChatView() {
|
||||||
|
const { topic, messages } = Route.useLoaderData()
|
||||||
|
const { assistantId, topicId } = Route.useParams()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col h-full">
|
||||||
|
<ChatNavbar topic={topic} />
|
||||||
|
<Messages messages={messages} />
|
||||||
|
<Inputbar topicId={topicId} assistantId={assistantId} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 Data Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
1. User clicks topic in sidebar
|
||||||
|
↓
|
||||||
|
2. navigate({ to: '/chat/$assistantId/$topicId' })
|
||||||
|
↓
|
||||||
|
3. TSR matches route, checks loader cache
|
||||||
|
↓
|
||||||
|
4. Cache hit → Render directly
|
||||||
|
Cache miss → Execute loader, fetch data
|
||||||
|
↓
|
||||||
|
5. ChatLayout does not re-render (parent route data cached)
|
||||||
|
↓
|
||||||
|
6. Only ChatView updates (child route data changed)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Settings Route Design
|
||||||
|
|
||||||
|
### 4.1 Sub-page List
|
||||||
|
|
||||||
|
| Route | Component | Existing File |
|
||||||
|
|-------|-----------|---------------|
|
||||||
|
| `/settings/provider` | `ProviderSettings` | `ProviderSettings/` |
|
||||||
|
| `/settings/model` | `ModelSettings` | `ModelSettings/` |
|
||||||
|
| `/settings/general` | `GeneralSettings` | `GeneralSettings.tsx` |
|
||||||
|
| `/settings/display` | `DisplaySettings` | `DisplaySettings.tsx` |
|
||||||
|
| `/settings/data` | `DataSettings` | `DataSettings/` |
|
||||||
|
| `/settings/mcp` | `MCPSettings` | `MCPSettings/` |
|
||||||
|
| `/settings/websearch` | `WebSearchSettings` | `WebSearchSettings/` |
|
||||||
|
| `/settings/memory` | `MemorySettings` | `MemorySettings/` |
|
||||||
|
| `/settings/shortcut` | `ShortcutSettings` | `ShortcutSettings.tsx` |
|
||||||
|
| `/settings/quickassistant` | `QuickAssistantSettings` | `QuickAssistantSettings.tsx` |
|
||||||
|
| `/settings/about` | `AboutSettings` | `AboutSettings.tsx` |
|
||||||
|
|
||||||
|
### 4.2 Layout Structure
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// routes/settings/route.tsx
|
||||||
|
function SettingsLayout() {
|
||||||
|
return (
|
||||||
|
<div className="flex h-full">
|
||||||
|
{/* Left menu */}
|
||||||
|
<SettingsMenu />
|
||||||
|
|
||||||
|
{/* Right content */}
|
||||||
|
<div className="flex-1 overflow-auto">
|
||||||
|
<Outlet />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Paintings Route Design
|
||||||
|
|
||||||
|
### 5.1 URL Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
/paintings/$providerId
|
||||||
|
│
|
||||||
|
└── Provider ID (zhipu, aihubmix, silicon, dmxapi, tokenflux, ovms, or dynamic NewApi provider)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Examples**:
|
||||||
|
|
||||||
|
- `/paintings` → Redirect to user's default painting provider
|
||||||
|
- `/paintings/zhipu` → Zhipu painting page
|
||||||
|
- `/paintings/aihubmix` → Aihubmix painting page
|
||||||
|
- `/paintings/my-custom-provider` → User's custom NewApi provider
|
||||||
|
|
||||||
|
### 5.2 Provider List
|
||||||
|
|
||||||
|
| Provider ID | Component | Description |
|
||||||
|
|-------------|-----------|-------------|
|
||||||
|
| `zhipu` | `ZhipuPage` | Zhipu AI Painting |
|
||||||
|
| `aihubmix` | `AihubmixPage` | Aihubmix Aggregation |
|
||||||
|
| `silicon` | `SiliconPage` | Silicon Flow |
|
||||||
|
| `dmxapi` | `DmxapiPage` | Dmxapi |
|
||||||
|
| `tokenflux` | `TokenFluxPage` | TokenFlux |
|
||||||
|
| `ovms` | `OvmsPage` | OVMS (Local Inference) |
|
||||||
|
| `$providerId` | `NewApiPage` | Dynamic NewApi Provider |
|
||||||
|
|
||||||
|
### 5.3 Component Structure
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// routes/paintings/route.tsx
|
||||||
|
export const Route = createFileRoute('/paintings')({
|
||||||
|
component: PaintingsLayout,
|
||||||
|
loader: async () => ({
|
||||||
|
providers: await fetchPaintingProviders(),
|
||||||
|
defaultProvider: await getDefaultPaintingProvider()
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
function PaintingsLayout() {
|
||||||
|
const { providers } = Route.useLoaderData()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col h-full">
|
||||||
|
{/* Provider selector */}
|
||||||
|
<ProviderSelect providers={providers} />
|
||||||
|
|
||||||
|
{/* Painting content area */}
|
||||||
|
<div className="flex-1">
|
||||||
|
<Outlet />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.4 Special Handling
|
||||||
|
|
||||||
|
- **OVMS Provider**: Only shown in options when local OVMS service is running
|
||||||
|
- **Dynamic Providers**: Custom providers added by users via NewApi, captured using `$providerId`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Component Mapping
|
||||||
|
|
||||||
|
| New Route Component | Existing Component | Migration Strategy |
|
||||||
|
|---------------------|-------------------|-------------------|
|
||||||
|
| `ChatLayout` | `HomePage.tsx` | Extract sidebar logic |
|
||||||
|
| `ChatSidebar` | `HomeTabs/index.tsx` | Rename, adjust props |
|
||||||
|
| `ChatView` | `Chat.tsx` | Keep unchanged, adjust data fetching |
|
||||||
|
| `SettingsLayout` | `SettingsPage.tsx` | Extract layout logic |
|
||||||
|
| `NotesLayout` | `NotesSidebar.tsx` | Extract as layout component |
|
||||||
|
| `NotesEditor` | `NotesEditor.tsx` | Keep unchanged |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Open Questions
|
||||||
|
|
||||||
|
- [ ] `/` home behavior: Redirect to `/chat` or standalone welcome page?
|
||||||
|
- [ ] Does `/chat/$assistantId` need a dedicated page? Or redirect to first topic directly?
|
||||||
|
- [ ] Left sidebar interaction: Always create new Tab on click? Or reuse existing Tab?
|
||||||
|
- [ ] Tab bar UI details: Close button position, drag-to-reorder, context menu, etc.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Changelog
|
||||||
|
|
||||||
|
| Version | Date | Changes |
|
||||||
|
|---------|------|---------|
|
||||||
|
| v0.1.0 | 2025-11-25 | Initial version |
|
||||||
@ -2,6 +2,7 @@ import type { BodyForPath, QueryParamsForPath, ResponseForPath } from '@shared/d
|
|||||||
import type { ConcreteApiPaths } from '@shared/data/api/apiSchemas'
|
import type { ConcreteApiPaths } from '@shared/data/api/apiSchemas'
|
||||||
import type { PaginatedResponse } from '@shared/data/api/apiTypes'
|
import type { PaginatedResponse } from '@shared/data/api/apiTypes'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
|
import type { KeyedMutator } from 'swr'
|
||||||
import useSWR, { useSWRConfig } from 'swr'
|
import useSWR, { useSWRConfig } from 'swr'
|
||||||
import useSWRMutation from 'swr/mutation'
|
import useSWRMutation from 'swr/mutation'
|
||||||
|
|
||||||
@ -139,6 +140,8 @@ export function useQuery<TPath extends ConcreteApiPaths>(
|
|||||||
error?: Error
|
error?: Error
|
||||||
/** Function to manually refetch data */
|
/** Function to manually refetch data */
|
||||||
refetch: () => void
|
refetch: () => void
|
||||||
|
/** SWR mutate function for optimistic updates */
|
||||||
|
mutate: KeyedMutator<ResponseForPath<TPath, 'GET'>>
|
||||||
} {
|
} {
|
||||||
// Internal type conversion for SWR compatibility
|
// Internal type conversion for SWR compatibility
|
||||||
const key = options?.enabled !== false ? buildSWRKey(path, options?.query as Record<string, any>) : null
|
const key = options?.enabled !== false ? buildSWRKey(path, options?.query as Record<string, any>) : null
|
||||||
@ -160,7 +163,8 @@ export function useQuery<TPath extends ConcreteApiPaths>(
|
|||||||
data,
|
data,
|
||||||
loading: isLoading || isValidating,
|
loading: isLoading || isValidating,
|
||||||
error: error as Error | undefined,
|
error: error as Error | undefined,
|
||||||
refetch
|
refetch,
|
||||||
|
mutate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
122
src/renderer/src/hooks/useTabs.ts
Normal file
122
src/renderer/src/hooks/useTabs.ts
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
import { loggerService } from '@logger'
|
||||||
|
import { useCallback, useMemo } from 'react'
|
||||||
|
|
||||||
|
import { useMutation, useQuery } from '../data/hooks/useDataApi'
|
||||||
|
|
||||||
|
const logger = loggerService.withContext('useTabs')
|
||||||
|
|
||||||
|
export type TabType = 'webview' | 'url' | 'browser'
|
||||||
|
|
||||||
|
export interface Tab {
|
||||||
|
id: string
|
||||||
|
type: TabType
|
||||||
|
url: string
|
||||||
|
title: string
|
||||||
|
icon?: string
|
||||||
|
isKeepAlive?: boolean
|
||||||
|
metadata?: Record<string, any>
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TabsState {
|
||||||
|
tabs: Tab[]
|
||||||
|
activeTabId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const TABS_STORAGE_KEY = 'tabs_state'
|
||||||
|
const DEFAULT_STATE: TabsState = { tabs: [], activeTabId: '' }
|
||||||
|
|
||||||
|
export function useTabs() {
|
||||||
|
// Load state from DB
|
||||||
|
// We cast the path because we haven't fully updated the concrete path types globally yet
|
||||||
|
const {
|
||||||
|
data: tabsState,
|
||||||
|
mutate: mutateState,
|
||||||
|
loading: isLoading
|
||||||
|
} = useQuery(`/app/state/${TABS_STORAGE_KEY}` as any, {
|
||||||
|
swrOptions: {
|
||||||
|
revalidateOnFocus: false,
|
||||||
|
fallbackData: DEFAULT_STATE
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Ensure we always have a valid object structure
|
||||||
|
const currentState: TabsState = useMemo(
|
||||||
|
() => (tabsState && typeof tabsState === 'object' ? (tabsState as TabsState) : DEFAULT_STATE),
|
||||||
|
[tabsState]
|
||||||
|
)
|
||||||
|
const tabs = useMemo(() => (Array.isArray(currentState.tabs) ? currentState.tabs : []), [currentState.tabs])
|
||||||
|
const activeTabId = currentState.activeTabId || ''
|
||||||
|
|
||||||
|
// Mutation for saving
|
||||||
|
const saveMutation = useMutation('PUT', `/app/state/${TABS_STORAGE_KEY}` as any)
|
||||||
|
|
||||||
|
// Unified update helper
|
||||||
|
const updateState = useCallback(
|
||||||
|
async (newState: TabsState) => {
|
||||||
|
// 1. Optimistic update local cache
|
||||||
|
await mutateState(newState, { revalidate: false })
|
||||||
|
|
||||||
|
// 2. Sync to DB
|
||||||
|
saveMutation.mutate({ body: newState }).catch((err) => logger.error('Failed to save tabs state:', err))
|
||||||
|
},
|
||||||
|
[mutateState, saveMutation]
|
||||||
|
)
|
||||||
|
|
||||||
|
const addTab = useCallback(
|
||||||
|
(tab: Tab) => {
|
||||||
|
const exists = tabs.find((t) => t.id === tab.id)
|
||||||
|
if (exists) {
|
||||||
|
updateState({ ...currentState, activeTabId: tab.id })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const newTabs = [...tabs, tab]
|
||||||
|
updateState({ tabs: newTabs, activeTabId: tab.id })
|
||||||
|
},
|
||||||
|
[tabs, currentState, updateState]
|
||||||
|
)
|
||||||
|
|
||||||
|
const closeTab = useCallback(
|
||||||
|
(id: string) => {
|
||||||
|
const newTabs = tabs.filter((t) => t.id !== id)
|
||||||
|
let newActiveId = activeTabId
|
||||||
|
|
||||||
|
if (activeTabId === id) {
|
||||||
|
// Activate adjacent tab
|
||||||
|
const index = tabs.findIndex((t) => t.id === id)
|
||||||
|
// Try to go left, then right
|
||||||
|
const nextTab = newTabs[index - 1] || newTabs[index]
|
||||||
|
newActiveId = nextTab ? nextTab.id : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
updateState({ tabs: newTabs, activeTabId: newActiveId })
|
||||||
|
},
|
||||||
|
[tabs, activeTabId, updateState]
|
||||||
|
)
|
||||||
|
|
||||||
|
const setActiveTab = useCallback(
|
||||||
|
(id: string) => {
|
||||||
|
if (id !== activeTabId) {
|
||||||
|
updateState({ ...currentState, activeTabId: id })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[activeTabId, currentState, updateState]
|
||||||
|
)
|
||||||
|
|
||||||
|
const updateTab = useCallback(
|
||||||
|
(id: string, updates: Partial<Tab>) => {
|
||||||
|
const newTabs = tabs.map((t) => (t.id === id ? { ...t, ...updates } : t))
|
||||||
|
updateState({ ...currentState, tabs: newTabs })
|
||||||
|
},
|
||||||
|
[tabs, currentState, updateState]
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
tabs,
|
||||||
|
activeTabId,
|
||||||
|
isLoading,
|
||||||
|
addTab,
|
||||||
|
closeTab,
|
||||||
|
setActiveTab,
|
||||||
|
updateTab
|
||||||
|
}
|
||||||
|
}
|
||||||
55
src/renderer/src/routeTree.gen.ts
Normal file
55
src/renderer/src/routeTree.gen.ts
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
|
// noinspection JSUnusedGlobalSymbols
|
||||||
|
|
||||||
|
// This file was automatically generated by TanStack Router.
|
||||||
|
// You should NOT make any changes in this file as it will be overwritten.
|
||||||
|
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
||||||
|
|
||||||
|
import { Route as rootRouteImport } from './routes/__root'
|
||||||
|
import { Route as IndexRouteImport } from './routes/index'
|
||||||
|
|
||||||
|
const IndexRoute = IndexRouteImport.update({
|
||||||
|
id: '/',
|
||||||
|
path: '/',
|
||||||
|
getParentRoute: () => rootRouteImport
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
export interface FileRoutesByFullPath {
|
||||||
|
'/': typeof IndexRoute
|
||||||
|
}
|
||||||
|
export interface FileRoutesByTo {
|
||||||
|
'/': typeof IndexRoute
|
||||||
|
}
|
||||||
|
export interface FileRoutesById {
|
||||||
|
__root__: typeof rootRouteImport
|
||||||
|
'/': typeof IndexRoute
|
||||||
|
}
|
||||||
|
export interface FileRouteTypes {
|
||||||
|
fileRoutesByFullPath: FileRoutesByFullPath
|
||||||
|
fullPaths: '/'
|
||||||
|
fileRoutesByTo: FileRoutesByTo
|
||||||
|
to: '/'
|
||||||
|
id: '__root__' | '/'
|
||||||
|
fileRoutesById: FileRoutesById
|
||||||
|
}
|
||||||
|
export interface RootRouteChildren {
|
||||||
|
IndexRoute: typeof IndexRoute
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '@tanstack/react-router' {
|
||||||
|
interface FileRoutesByPath {
|
||||||
|
'/': {
|
||||||
|
id: '/'
|
||||||
|
path: '/'
|
||||||
|
fullPath: '/'
|
||||||
|
preLoaderRoute: typeof IndexRouteImport
|
||||||
|
parentRoute: typeof rootRouteImport
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const rootRouteChildren: RootRouteChildren = {
|
||||||
|
IndexRoute: IndexRoute
|
||||||
|
}
|
||||||
|
export const routeTree = rootRouteImport._addFileChildren(rootRouteChildren)._addFileTypes<FileRouteTypes>()
|
||||||
7
src/renderer/src/routes/__root.tsx
Normal file
7
src/renderer/src/routes/__root.tsx
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { createRootRoute } from '@tanstack/react-router'
|
||||||
|
|
||||||
|
import { AppShell } from '../components/layout/AppShell'
|
||||||
|
|
||||||
|
export const Route = createRootRoute({
|
||||||
|
component: () => <AppShell />
|
||||||
|
})
|
||||||
14
src/renderer/src/routes/index.tsx
Normal file
14
src/renderer/src/routes/index.tsx
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { createFileRoute } from '@tanstack/react-router'
|
||||||
|
|
||||||
|
export const Route = createFileRoute('/')({
|
||||||
|
component: Index
|
||||||
|
})
|
||||||
|
|
||||||
|
function Index() {
|
||||||
|
return (
|
||||||
|
<div style={{ padding: 20 }}>
|
||||||
|
<h3>Welcome to Cherry Studio!</h3>
|
||||||
|
<p>Select a tab to start.</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
459
yarn.lock
459
yarn.lock
@ -1545,7 +1545,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@babel/core@npm:^7.21.3":
|
"@babel/core@npm:^7.21.3, @babel/core@npm:^7.23.7, @babel/core@npm:^7.27.4":
|
||||||
version: 7.28.5
|
version: 7.28.5
|
||||||
resolution: "@babel/core@npm:7.28.5"
|
resolution: "@babel/core@npm:7.28.5"
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -1591,6 +1591,19 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@babel/generator@npm:^7.27.5, @babel/generator@npm:^7.28.5":
|
||||||
|
version: 7.28.5
|
||||||
|
resolution: "@babel/generator@npm:7.28.5"
|
||||||
|
dependencies:
|
||||||
|
"@babel/parser": "npm:^7.28.5"
|
||||||
|
"@babel/types": "npm:^7.28.5"
|
||||||
|
"@jridgewell/gen-mapping": "npm:^0.3.12"
|
||||||
|
"@jridgewell/trace-mapping": "npm:^0.3.28"
|
||||||
|
jsesc: "npm:^3.0.2"
|
||||||
|
checksum: 10c0/9f219fe1d5431b6919f1a5c60db8d5d34fe546c0d8f5a8511b32f847569234ffc8032beb9e7404649a143f54e15224ecb53a3d11b6bb85c3203e573d91fca752
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@babel/generator@npm:^7.28.0, @babel/generator@npm:^7.28.3":
|
"@babel/generator@npm:^7.28.0, @babel/generator@npm:^7.28.3":
|
||||||
version: 7.28.3
|
version: 7.28.3
|
||||||
resolution: "@babel/generator@npm:7.28.3"
|
resolution: "@babel/generator@npm:7.28.3"
|
||||||
@ -1604,16 +1617,12 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@babel/generator@npm:^7.28.5":
|
"@babel/helper-annotate-as-pure@npm:^7.27.3":
|
||||||
version: 7.28.5
|
version: 7.27.3
|
||||||
resolution: "@babel/generator@npm:7.28.5"
|
resolution: "@babel/helper-annotate-as-pure@npm:7.27.3"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/parser": "npm:^7.28.5"
|
"@babel/types": "npm:^7.27.3"
|
||||||
"@babel/types": "npm:^7.28.5"
|
checksum: 10c0/94996ce0a05b7229f956033e6dcd69393db2b0886d0db6aff41e704390402b8cdcca11f61449cb4f86cfd9e61b5ad3a73e4fa661eeed7846b125bd1c33dbc633
|
||||||
"@jridgewell/gen-mapping": "npm:^0.3.12"
|
|
||||||
"@jridgewell/trace-mapping": "npm:^0.3.28"
|
|
||||||
jsesc: "npm:^3.0.2"
|
|
||||||
checksum: 10c0/9f219fe1d5431b6919f1a5c60db8d5d34fe546c0d8f5a8511b32f847569234ffc8032beb9e7404649a143f54e15224ecb53a3d11b6bb85c3203e573d91fca752
|
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@ -1630,6 +1639,23 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@babel/helper-create-class-features-plugin@npm:^7.28.5":
|
||||||
|
version: 7.28.5
|
||||||
|
resolution: "@babel/helper-create-class-features-plugin@npm:7.28.5"
|
||||||
|
dependencies:
|
||||||
|
"@babel/helper-annotate-as-pure": "npm:^7.27.3"
|
||||||
|
"@babel/helper-member-expression-to-functions": "npm:^7.28.5"
|
||||||
|
"@babel/helper-optimise-call-expression": "npm:^7.27.1"
|
||||||
|
"@babel/helper-replace-supers": "npm:^7.27.1"
|
||||||
|
"@babel/helper-skip-transparent-expression-wrappers": "npm:^7.27.1"
|
||||||
|
"@babel/traverse": "npm:^7.28.5"
|
||||||
|
semver: "npm:^6.3.1"
|
||||||
|
peerDependencies:
|
||||||
|
"@babel/core": ^7.0.0
|
||||||
|
checksum: 10c0/786a6514efcf4514aaad85beed419b9184d059f4c9a9a95108f320142764999827252a851f7071de19f29424d369616573ecbaa347f1ce23fb12fc6827d9ff56
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@babel/helper-globals@npm:^7.28.0":
|
"@babel/helper-globals@npm:^7.28.0":
|
||||||
version: 7.28.0
|
version: 7.28.0
|
||||||
resolution: "@babel/helper-globals@npm:7.28.0"
|
resolution: "@babel/helper-globals@npm:7.28.0"
|
||||||
@ -1637,6 +1663,16 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@babel/helper-member-expression-to-functions@npm:^7.27.1, @babel/helper-member-expression-to-functions@npm:^7.28.5":
|
||||||
|
version: 7.28.5
|
||||||
|
resolution: "@babel/helper-member-expression-to-functions@npm:7.28.5"
|
||||||
|
dependencies:
|
||||||
|
"@babel/traverse": "npm:^7.28.5"
|
||||||
|
"@babel/types": "npm:^7.28.5"
|
||||||
|
checksum: 10c0/4e6e05fbf4dffd0bc3e55e28fcaab008850be6de5a7013994ce874ec2beb90619cda4744b11607a60f8aae0227694502908add6188ceb1b5223596e765b44814
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@babel/helper-module-imports@npm:^7.27.1":
|
"@babel/helper-module-imports@npm:^7.27.1":
|
||||||
version: 7.27.1
|
version: 7.27.1
|
||||||
resolution: "@babel/helper-module-imports@npm:7.27.1"
|
resolution: "@babel/helper-module-imports@npm:7.27.1"
|
||||||
@ -1647,7 +1683,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@babel/helper-module-transforms@npm:^7.28.3":
|
"@babel/helper-module-transforms@npm:^7.27.1, @babel/helper-module-transforms@npm:^7.28.3":
|
||||||
version: 7.28.3
|
version: 7.28.3
|
||||||
resolution: "@babel/helper-module-transforms@npm:7.28.3"
|
resolution: "@babel/helper-module-transforms@npm:7.28.3"
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -1660,6 +1696,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@babel/helper-optimise-call-expression@npm:^7.27.1":
|
||||||
|
version: 7.27.1
|
||||||
|
resolution: "@babel/helper-optimise-call-expression@npm:7.27.1"
|
||||||
|
dependencies:
|
||||||
|
"@babel/types": "npm:^7.27.1"
|
||||||
|
checksum: 10c0/6b861e7fcf6031b9c9fc2de3cd6c005e94a459d6caf3621d93346b52774925800ca29d4f64595a5ceacf4d161eb0d27649ae385110ed69491d9776686fa488e6
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@babel/helper-plugin-utils@npm:^7.27.1":
|
"@babel/helper-plugin-utils@npm:^7.27.1":
|
||||||
version: 7.27.1
|
version: 7.27.1
|
||||||
resolution: "@babel/helper-plugin-utils@npm:7.27.1"
|
resolution: "@babel/helper-plugin-utils@npm:7.27.1"
|
||||||
@ -1667,6 +1712,29 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@babel/helper-replace-supers@npm:^7.27.1":
|
||||||
|
version: 7.27.1
|
||||||
|
resolution: "@babel/helper-replace-supers@npm:7.27.1"
|
||||||
|
dependencies:
|
||||||
|
"@babel/helper-member-expression-to-functions": "npm:^7.27.1"
|
||||||
|
"@babel/helper-optimise-call-expression": "npm:^7.27.1"
|
||||||
|
"@babel/traverse": "npm:^7.27.1"
|
||||||
|
peerDependencies:
|
||||||
|
"@babel/core": ^7.0.0
|
||||||
|
checksum: 10c0/4f2eaaf5fcc196580221a7ccd0f8873447b5d52745ad4096418f6101a1d2e712e9f93722c9a32bc9769a1dc197e001f60d6f5438d4dfde4b9c6a9e4df719354c
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@babel/helper-skip-transparent-expression-wrappers@npm:^7.27.1":
|
||||||
|
version: 7.27.1
|
||||||
|
resolution: "@babel/helper-skip-transparent-expression-wrappers@npm:7.27.1"
|
||||||
|
dependencies:
|
||||||
|
"@babel/traverse": "npm:^7.27.1"
|
||||||
|
"@babel/types": "npm:^7.27.1"
|
||||||
|
checksum: 10c0/f625013bcdea422c470223a2614e90d2c1cc9d832e97f32ca1b4f82b34bb4aa67c3904cb4b116375d3b5b753acfb3951ed50835a1e832e7225295c7b0c24dff7
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@babel/helper-string-parser@npm:^7.27.1":
|
"@babel/helper-string-parser@npm:^7.27.1":
|
||||||
version: 7.27.1
|
version: 7.27.1
|
||||||
resolution: "@babel/helper-string-parser@npm:7.27.1"
|
resolution: "@babel/helper-string-parser@npm:7.27.1"
|
||||||
@ -1705,7 +1773,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.28.3, @babel/parser@npm:^7.28.4, @babel/parser@npm:^7.28.5":
|
"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.6, @babel/parser@npm:^7.28.3, @babel/parser@npm:^7.28.4, @babel/parser@npm:^7.28.5":
|
||||||
version: 7.28.5
|
version: 7.28.5
|
||||||
resolution: "@babel/parser@npm:7.28.5"
|
resolution: "@babel/parser@npm:7.28.5"
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -1727,6 +1795,28 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@babel/plugin-syntax-jsx@npm:^7.27.1":
|
||||||
|
version: 7.27.1
|
||||||
|
resolution: "@babel/plugin-syntax-jsx@npm:7.27.1"
|
||||||
|
dependencies:
|
||||||
|
"@babel/helper-plugin-utils": "npm:^7.27.1"
|
||||||
|
peerDependencies:
|
||||||
|
"@babel/core": ^7.0.0-0
|
||||||
|
checksum: 10c0/bc5afe6a458d5f0492c02a54ad98c5756a0c13bd6d20609aae65acd560a9e141b0876da5f358dce34ea136f271c1016df58b461184d7ae9c4321e0f98588bc84
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@babel/plugin-syntax-typescript@npm:^7.27.1":
|
||||||
|
version: 7.27.1
|
||||||
|
resolution: "@babel/plugin-syntax-typescript@npm:7.27.1"
|
||||||
|
dependencies:
|
||||||
|
"@babel/helper-plugin-utils": "npm:^7.27.1"
|
||||||
|
peerDependencies:
|
||||||
|
"@babel/core": ^7.0.0-0
|
||||||
|
checksum: 10c0/11589b4c89c66ef02d57bf56c6246267851ec0c361f58929327dc3e070b0dab644be625bbe7fb4c4df30c3634bfdfe31244e1f517be397d2def1487dbbe3c37d
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@babel/plugin-transform-arrow-functions@npm:^7.27.1":
|
"@babel/plugin-transform-arrow-functions@npm:^7.27.1":
|
||||||
version: 7.27.1
|
version: 7.27.1
|
||||||
resolution: "@babel/plugin-transform-arrow-functions@npm:7.27.1"
|
resolution: "@babel/plugin-transform-arrow-functions@npm:7.27.1"
|
||||||
@ -1738,6 +1828,48 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@babel/plugin-transform-modules-commonjs@npm:^7.27.1":
|
||||||
|
version: 7.27.1
|
||||||
|
resolution: "@babel/plugin-transform-modules-commonjs@npm:7.27.1"
|
||||||
|
dependencies:
|
||||||
|
"@babel/helper-module-transforms": "npm:^7.27.1"
|
||||||
|
"@babel/helper-plugin-utils": "npm:^7.27.1"
|
||||||
|
peerDependencies:
|
||||||
|
"@babel/core": ^7.0.0-0
|
||||||
|
checksum: 10c0/4def972dcd23375a266ea1189115a4ff61744b2c9366fc1de648b3fab2c650faf1a94092de93a33ff18858d2e6c4dddeeee5384cb42ba0129baeab01a5cdf1e2
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@babel/plugin-transform-typescript@npm:^7.28.5":
|
||||||
|
version: 7.28.5
|
||||||
|
resolution: "@babel/plugin-transform-typescript@npm:7.28.5"
|
||||||
|
dependencies:
|
||||||
|
"@babel/helper-annotate-as-pure": "npm:^7.27.3"
|
||||||
|
"@babel/helper-create-class-features-plugin": "npm:^7.28.5"
|
||||||
|
"@babel/helper-plugin-utils": "npm:^7.27.1"
|
||||||
|
"@babel/helper-skip-transparent-expression-wrappers": "npm:^7.27.1"
|
||||||
|
"@babel/plugin-syntax-typescript": "npm:^7.27.1"
|
||||||
|
peerDependencies:
|
||||||
|
"@babel/core": ^7.0.0-0
|
||||||
|
checksum: 10c0/09e574ba5462e56452b4ceecae65e53c8e697a2d3559ce5d210bed10ac28a18aa69377e7550c30520eb29b40c417ee61997d5d58112657f22983244b78915a7c
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@babel/preset-typescript@npm:^7.27.1":
|
||||||
|
version: 7.28.5
|
||||||
|
resolution: "@babel/preset-typescript@npm:7.28.5"
|
||||||
|
dependencies:
|
||||||
|
"@babel/helper-plugin-utils": "npm:^7.27.1"
|
||||||
|
"@babel/helper-validator-option": "npm:^7.27.1"
|
||||||
|
"@babel/plugin-syntax-jsx": "npm:^7.27.1"
|
||||||
|
"@babel/plugin-transform-modules-commonjs": "npm:^7.27.1"
|
||||||
|
"@babel/plugin-transform-typescript": "npm:^7.28.5"
|
||||||
|
peerDependencies:
|
||||||
|
"@babel/core": ^7.0.0-0
|
||||||
|
checksum: 10c0/b3d55548854c105085dd80f638147aa8295bc186d70492289242d6c857cb03a6c61ec15186440ea10ed4a71cdde7d495f5eb3feda46273f36b0ac926e8409629
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.10.4, @babel/runtime@npm:^7.11.1, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.16.7, @babel/runtime@npm:^7.18.0, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.18.6, @babel/runtime@npm:^7.20.0, @babel/runtime@npm:^7.20.13, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.22.5, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.23.6, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.24.4, @babel/runtime@npm:^7.24.7, @babel/runtime@npm:^7.24.8, @babel/runtime@npm:^7.25.7, @babel/runtime@npm:^7.26.0, @babel/runtime@npm:^7.26.7":
|
"@babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.10.4, @babel/runtime@npm:^7.11.1, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.16.7, @babel/runtime@npm:^7.18.0, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.18.6, @babel/runtime@npm:^7.20.0, @babel/runtime@npm:^7.20.13, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.22.5, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.23.6, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.24.4, @babel/runtime@npm:^7.24.7, @babel/runtime@npm:^7.24.8, @babel/runtime@npm:^7.25.7, @babel/runtime@npm:^7.26.0, @babel/runtime@npm:^7.26.7":
|
||||||
version: 7.28.3
|
version: 7.28.3
|
||||||
resolution: "@babel/runtime@npm:7.28.3"
|
resolution: "@babel/runtime@npm:7.28.3"
|
||||||
@ -1763,6 +1895,21 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@babel/traverse@npm:^7.23.7, @babel/traverse@npm:^7.27.7, @babel/traverse@npm:^7.28.5":
|
||||||
|
version: 7.28.5
|
||||||
|
resolution: "@babel/traverse@npm:7.28.5"
|
||||||
|
dependencies:
|
||||||
|
"@babel/code-frame": "npm:^7.27.1"
|
||||||
|
"@babel/generator": "npm:^7.28.5"
|
||||||
|
"@babel/helper-globals": "npm:^7.28.0"
|
||||||
|
"@babel/parser": "npm:^7.28.5"
|
||||||
|
"@babel/template": "npm:^7.27.2"
|
||||||
|
"@babel/types": "npm:^7.28.5"
|
||||||
|
debug: "npm:^4.3.1"
|
||||||
|
checksum: 10c0/f6c4a595993ae2b73f2d4cd9c062f2e232174d293edd4abe1d715bd6281da8d99e47c65857e8d0917d9384c65972f4acdebc6749a7c40a8fcc38b3c7fb3e706f
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@babel/traverse@npm:^7.27.1, @babel/traverse@npm:^7.28.0, @babel/traverse@npm:^7.28.3, @babel/traverse@npm:^7.28.4":
|
"@babel/traverse@npm:^7.27.1, @babel/traverse@npm:^7.28.0, @babel/traverse@npm:^7.28.3, @babel/traverse@npm:^7.28.4":
|
||||||
version: 7.28.4
|
version: 7.28.4
|
||||||
resolution: "@babel/traverse@npm:7.28.4"
|
resolution: "@babel/traverse@npm:7.28.4"
|
||||||
@ -1778,21 +1925,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@babel/traverse@npm:^7.28.5":
|
|
||||||
version: 7.28.5
|
|
||||||
resolution: "@babel/traverse@npm:7.28.5"
|
|
||||||
dependencies:
|
|
||||||
"@babel/code-frame": "npm:^7.27.1"
|
|
||||||
"@babel/generator": "npm:^7.28.5"
|
|
||||||
"@babel/helper-globals": "npm:^7.28.0"
|
|
||||||
"@babel/parser": "npm:^7.28.5"
|
|
||||||
"@babel/template": "npm:^7.27.2"
|
|
||||||
"@babel/types": "npm:^7.28.5"
|
|
||||||
debug: "npm:^4.3.1"
|
|
||||||
checksum: 10c0/f6c4a595993ae2b73f2d4cd9c062f2e232174d293edd4abe1d715bd6281da8d99e47c65857e8d0917d9384c65972f4acdebc6749a7c40a8fcc38b3c7fb3e706f
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.28.4":
|
"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.28.4":
|
||||||
version: 7.28.4
|
version: 7.28.4
|
||||||
resolution: "@babel/types@npm:7.28.4"
|
resolution: "@babel/types@npm:7.28.4"
|
||||||
@ -1803,7 +1935,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@babel/types@npm:^7.21.3, @babel/types@npm:^7.28.5":
|
"@babel/types@npm:^7.21.3, @babel/types@npm:^7.23.6, @babel/types@npm:^7.27.3, @babel/types@npm:^7.27.7, @babel/types@npm:^7.28.5":
|
||||||
version: 7.28.5
|
version: 7.28.5
|
||||||
resolution: "@babel/types@npm:7.28.5"
|
resolution: "@babel/types@npm:7.28.5"
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -11016,6 +11148,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@tanstack/history@npm:1.139.0":
|
||||||
|
version: 1.139.0
|
||||||
|
resolution: "@tanstack/history@npm:1.139.0"
|
||||||
|
checksum: 10c0/000fe41d3c3d7f0384e74fcfb1ecda25800906220925d1ab715e4fad7dab081e81c738238a3c09bfb1203ffd4ab0e1f24da1384ded322a154b9eba58405e3e90
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@tanstack/query-core@npm:5.85.5":
|
"@tanstack/query-core@npm:5.85.5":
|
||||||
version: 5.85.5
|
version: 5.85.5
|
||||||
resolution: "@tanstack/query-core@npm:5.85.5"
|
resolution: "@tanstack/query-core@npm:5.85.5"
|
||||||
@ -11034,6 +11173,36 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@tanstack/react-router@npm:^1.139.3":
|
||||||
|
version: 1.139.3
|
||||||
|
resolution: "@tanstack/react-router@npm:1.139.3"
|
||||||
|
dependencies:
|
||||||
|
"@tanstack/history": "npm:1.139.0"
|
||||||
|
"@tanstack/react-store": "npm:^0.8.0"
|
||||||
|
"@tanstack/router-core": "npm:1.139.3"
|
||||||
|
isbot: "npm:^5.1.22"
|
||||||
|
tiny-invariant: "npm:^1.3.3"
|
||||||
|
tiny-warning: "npm:^1.0.3"
|
||||||
|
peerDependencies:
|
||||||
|
react: ">=18.0.0 || >=19.0.0"
|
||||||
|
react-dom: ">=18.0.0 || >=19.0.0"
|
||||||
|
checksum: 10c0/1a145ee628ab43c5ce326dc1e5068508b9b71c3570723b2acee6a11d0487221f704489f9f73a93903cb1cba31985fdadf59922ca2148bf91246cbeb71d64aab9
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@tanstack/react-store@npm:^0.8.0":
|
||||||
|
version: 0.8.0
|
||||||
|
resolution: "@tanstack/react-store@npm:0.8.0"
|
||||||
|
dependencies:
|
||||||
|
"@tanstack/store": "npm:0.8.0"
|
||||||
|
use-sync-external-store: "npm:^1.6.0"
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
|
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
|
checksum: 10c0/ecf7ad81d97810336d0a808a41442f235a444e98599c6e7e026efd3c4360548b84af9a23612f1d0da85e32a4d9e207632b2ee2cec6f635109a256209caa3bc59
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@tanstack/react-virtual@npm:3.11.3":
|
"@tanstack/react-virtual@npm:3.11.3":
|
||||||
version: 3.11.3
|
version: 3.11.3
|
||||||
resolution: "@tanstack/react-virtual@npm:3.11.3"
|
resolution: "@tanstack/react-virtual@npm:3.11.3"
|
||||||
@ -11058,6 +11227,99 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@tanstack/router-core@npm:1.139.3":
|
||||||
|
version: 1.139.3
|
||||||
|
resolution: "@tanstack/router-core@npm:1.139.3"
|
||||||
|
dependencies:
|
||||||
|
"@tanstack/history": "npm:1.139.0"
|
||||||
|
"@tanstack/store": "npm:^0.8.0"
|
||||||
|
cookie-es: "npm:^2.0.0"
|
||||||
|
seroval: "npm:^1.4.0"
|
||||||
|
seroval-plugins: "npm:^1.4.0"
|
||||||
|
tiny-invariant: "npm:^1.3.3"
|
||||||
|
tiny-warning: "npm:^1.0.3"
|
||||||
|
checksum: 10c0/1e342c39cb8a9e437671aeaa60c82c11530cd3ae0523983585f7c8d332b5ca471aa73d15ffd242cd4b2c73ecebeee7db8dc1b19cbfc3c211253782df9e4ac9fc
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@tanstack/router-generator@npm:1.139.3":
|
||||||
|
version: 1.139.3
|
||||||
|
resolution: "@tanstack/router-generator@npm:1.139.3"
|
||||||
|
dependencies:
|
||||||
|
"@tanstack/router-core": "npm:1.139.3"
|
||||||
|
"@tanstack/router-utils": "npm:1.139.0"
|
||||||
|
"@tanstack/virtual-file-routes": "npm:1.139.0"
|
||||||
|
prettier: "npm:^3.5.0"
|
||||||
|
recast: "npm:^0.23.11"
|
||||||
|
source-map: "npm:^0.7.4"
|
||||||
|
tsx: "npm:^4.19.2"
|
||||||
|
zod: "npm:^3.24.2"
|
||||||
|
checksum: 10c0/6ee595fc7b3da75016129bbf65d8ee1ec50e3c5d96dbabf56718c5ee3332c4f660aa738a7dd8f8207d6aad4e71f3c6c51572f17192db12ada668dc5e491d6757
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@tanstack/router-plugin@npm:^1.139.3":
|
||||||
|
version: 1.139.3
|
||||||
|
resolution: "@tanstack/router-plugin@npm:1.139.3"
|
||||||
|
dependencies:
|
||||||
|
"@babel/core": "npm:^7.27.7"
|
||||||
|
"@babel/plugin-syntax-jsx": "npm:^7.27.1"
|
||||||
|
"@babel/plugin-syntax-typescript": "npm:^7.27.1"
|
||||||
|
"@babel/template": "npm:^7.27.2"
|
||||||
|
"@babel/traverse": "npm:^7.27.7"
|
||||||
|
"@babel/types": "npm:^7.27.7"
|
||||||
|
"@tanstack/router-core": "npm:1.139.3"
|
||||||
|
"@tanstack/router-generator": "npm:1.139.3"
|
||||||
|
"@tanstack/router-utils": "npm:1.139.0"
|
||||||
|
"@tanstack/virtual-file-routes": "npm:1.139.0"
|
||||||
|
babel-dead-code-elimination: "npm:^1.0.10"
|
||||||
|
chokidar: "npm:^3.6.0"
|
||||||
|
unplugin: "npm:^2.1.2"
|
||||||
|
zod: "npm:^3.24.2"
|
||||||
|
peerDependencies:
|
||||||
|
"@rsbuild/core": ">=1.0.2"
|
||||||
|
"@tanstack/react-router": ^1.139.3
|
||||||
|
vite: ">=5.0.0 || >=6.0.0 || >=7.0.0"
|
||||||
|
vite-plugin-solid: ^2.11.10
|
||||||
|
webpack: ">=5.92.0"
|
||||||
|
peerDependenciesMeta:
|
||||||
|
"@rsbuild/core":
|
||||||
|
optional: true
|
||||||
|
"@tanstack/react-router":
|
||||||
|
optional: true
|
||||||
|
vite:
|
||||||
|
optional: true
|
||||||
|
vite-plugin-solid:
|
||||||
|
optional: true
|
||||||
|
webpack:
|
||||||
|
optional: true
|
||||||
|
checksum: 10c0/5795a880e65b27168fb703aff7254955787edde438fcfb381ddd201d74974806880164785b56f055a3c4186331a71ea0ccfa6f1474351cdeaf2608d10f13d2f9
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@tanstack/router-utils@npm:1.139.0":
|
||||||
|
version: 1.139.0
|
||||||
|
resolution: "@tanstack/router-utils@npm:1.139.0"
|
||||||
|
dependencies:
|
||||||
|
"@babel/core": "npm:^7.27.4"
|
||||||
|
"@babel/generator": "npm:^7.27.5"
|
||||||
|
"@babel/parser": "npm:^7.27.5"
|
||||||
|
"@babel/preset-typescript": "npm:^7.27.1"
|
||||||
|
ansis: "npm:^4.1.0"
|
||||||
|
diff: "npm:^8.0.2"
|
||||||
|
pathe: "npm:^2.0.3"
|
||||||
|
tinyglobby: "npm:^0.2.15"
|
||||||
|
checksum: 10c0/cb8477ab32f16881b8b92db952d0ab343cb77704ada953e422cee4543dc31aa5efae31295d415ff7deea8f9fadd19ad1a88e26d5f3e0d30a2f6b4099e82d19c9
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@tanstack/store@npm:0.8.0, @tanstack/store@npm:^0.8.0":
|
||||||
|
version: 0.8.0
|
||||||
|
resolution: "@tanstack/store@npm:0.8.0"
|
||||||
|
checksum: 10c0/71841a7a7653f744bdea457d2c41768b8d5e5aed1d5ff22bd068e28ced9bf658208c730963809c2223b26b753e19da987c0d98acb7c543abd97de14e0d58991f
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@tanstack/virtual-core@npm:3.11.3":
|
"@tanstack/virtual-core@npm:3.11.3":
|
||||||
version: 3.11.3
|
version: 3.11.3
|
||||||
resolution: "@tanstack/virtual-core@npm:3.11.3"
|
resolution: "@tanstack/virtual-core@npm:3.11.3"
|
||||||
@ -11072,6 +11334,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@tanstack/virtual-file-routes@npm:1.139.0":
|
||||||
|
version: 1.139.0
|
||||||
|
resolution: "@tanstack/virtual-file-routes@npm:1.139.0"
|
||||||
|
checksum: 10c0/abb8173520e133f3e4d8d2eb356059ce0fbc6aa64d55e358cbc810cc323c8848fbd3adf512095bafaf69e7bc01ee588ca5a345d361c42452b8eb41239ce5ed73
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@testing-library/dom@npm:^10.4.0":
|
"@testing-library/dom@npm:^10.4.0":
|
||||||
version: 10.4.0
|
version: 10.4.0
|
||||||
resolution: "@testing-library/dom@npm:10.4.0"
|
resolution: "@testing-library/dom@npm:10.4.0"
|
||||||
@ -12384,12 +12653,12 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@types/react-dom@npm:^19.0.4":
|
"@types/react-dom@npm:^19.0.4, @types/react-dom@npm:^19.2.3":
|
||||||
version: 19.1.2
|
version: 19.2.3
|
||||||
resolution: "@types/react-dom@npm:19.1.2"
|
resolution: "@types/react-dom@npm:19.2.3"
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
"@types/react": ^19.0.0
|
"@types/react": ^19.2.0
|
||||||
checksum: 10c0/100c341cacba9ec8ae1d47ee051072a3450e9573bf8eeb7262490e341cb246ea0f95a07a1f2077e61cf92648f812a0324c602fcd811bd87b7ce41db2811510cd
|
checksum: 10c0/b486ebe0f4e2fb35e2e108df1d8fc0927ca5d6002d5771e8a739de11239fe62d0e207c50886185253c99eb9dedfeeb956ea7429e5ba17f6693c7acb4c02f8cd1
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@ -12420,21 +12689,12 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@types/react@npm:*":
|
"@types/react@npm:*, @types/react@npm:^19.0.12, @types/react@npm:^19.2.7":
|
||||||
version: 19.1.12
|
version: 19.2.7
|
||||||
resolution: "@types/react@npm:19.1.12"
|
resolution: "@types/react@npm:19.2.7"
|
||||||
dependencies:
|
dependencies:
|
||||||
csstype: "npm:^3.0.2"
|
csstype: "npm:^3.2.2"
|
||||||
checksum: 10c0/e35912b43da0caaab5252444bab87a31ca22950cde2822b3b3dc32e39c2d42dad1a4cf7b5dde9783aa2d007f0b2cba6ab9563fc6d2dbcaaa833b35178118767c
|
checksum: 10c0/a7b75f1f9fcb34badd6f84098be5e35a0aeca614bc91f93d2698664c0b2ba5ad128422bd470ada598238cebe4f9e604a752aead7dc6f5a92261d0c7f9b27cfd1
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@types/react@npm:^19.0.12":
|
|
||||||
version: 19.1.2
|
|
||||||
resolution: "@types/react@npm:19.1.2"
|
|
||||||
dependencies:
|
|
||||||
csstype: "npm:^3.0.2"
|
|
||||||
checksum: 10c0/76ffe71395c713d4adc3c759465012d3c956db00af35ab7c6d0d91bd07b274b7ce69caa0478c0760311587bd1e38c78ffc9688ebc629f2b266682a19d8750947
|
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@ -13738,7 +13998,9 @@ __metadata:
|
|||||||
"@swc/plugin-styled-components": "npm:^8.0.4"
|
"@swc/plugin-styled-components": "npm:^8.0.4"
|
||||||
"@tailwindcss/vite": "npm:^4.1.13"
|
"@tailwindcss/vite": "npm:^4.1.13"
|
||||||
"@tanstack/react-query": "npm:^5.85.5"
|
"@tanstack/react-query": "npm:^5.85.5"
|
||||||
|
"@tanstack/react-router": "npm:^1.139.3"
|
||||||
"@tanstack/react-virtual": "npm:^3.13.12"
|
"@tanstack/react-virtual": "npm:^3.13.12"
|
||||||
|
"@tanstack/router-plugin": "npm:^1.139.3"
|
||||||
"@testing-library/dom": "npm:^10.4.0"
|
"@testing-library/dom": "npm:^10.4.0"
|
||||||
"@testing-library/jest-dom": "npm:^6.6.3"
|
"@testing-library/jest-dom": "npm:^6.6.3"
|
||||||
"@testing-library/react": "npm:^16.3.0"
|
"@testing-library/react": "npm:^16.3.0"
|
||||||
@ -13776,8 +14038,8 @@ __metadata:
|
|||||||
"@types/mime-types": "npm:^3"
|
"@types/mime-types": "npm:^3"
|
||||||
"@types/node": "npm:^22.17.1"
|
"@types/node": "npm:^22.17.1"
|
||||||
"@types/pako": "npm:^1.0.2"
|
"@types/pako": "npm:^1.0.2"
|
||||||
"@types/react": "npm:^19.0.12"
|
"@types/react": "npm:^19.2.7"
|
||||||
"@types/react-dom": "npm:^19.0.4"
|
"@types/react-dom": "npm:^19.2.3"
|
||||||
"@types/react-infinite-scroll-component": "npm:^5.0.0"
|
"@types/react-infinite-scroll-component": "npm:^5.0.0"
|
||||||
"@types/react-transition-group": "npm:^4.4.12"
|
"@types/react-transition-group": "npm:^4.4.12"
|
||||||
"@types/react-window": "npm:^1"
|
"@types/react-window": "npm:^1"
|
||||||
@ -14641,6 +14903,18 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"babel-dead-code-elimination@npm:^1.0.10":
|
||||||
|
version: 1.0.10
|
||||||
|
resolution: "babel-dead-code-elimination@npm:1.0.10"
|
||||||
|
dependencies:
|
||||||
|
"@babel/core": "npm:^7.23.7"
|
||||||
|
"@babel/parser": "npm:^7.23.6"
|
||||||
|
"@babel/traverse": "npm:^7.23.7"
|
||||||
|
"@babel/types": "npm:^7.23.6"
|
||||||
|
checksum: 10c0/9503662f28cf8f86e7a27c5cc1fa63fc556100cd3bc6f1a4382aa8e9c6df54b15d2e0fcc073016f315d26a9e4004bc4d70829a395f056172b8f9240314da8973
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"bail@npm:^1.0.0":
|
"bail@npm:^1.0.0":
|
||||||
version: 1.0.5
|
version: 1.0.5
|
||||||
resolution: "bail@npm:1.0.5"
|
resolution: "bail@npm:1.0.5"
|
||||||
@ -15348,7 +15622,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"chokidar@npm:^3.5.2":
|
"chokidar@npm:^3.5.2, chokidar@npm:^3.6.0":
|
||||||
version: 3.6.0
|
version: 3.6.0
|
||||||
resolution: "chokidar@npm:3.6.0"
|
resolution: "chokidar@npm:3.6.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -15947,6 +16221,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"cookie-es@npm:^2.0.0":
|
||||||
|
version: 2.0.0
|
||||||
|
resolution: "cookie-es@npm:2.0.0"
|
||||||
|
checksum: 10c0/3b2459030a5ad2bc715aeb27a32f274340670bfc5031ac29e1fba804212517411bb617880d3fe66ace2b64dfb28f3049e2d1ff40d4bec342154ccdd124deaeaa
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"cookie-signature@npm:^1.2.1":
|
"cookie-signature@npm:^1.2.1":
|
||||||
version: 1.2.2
|
version: 1.2.2
|
||||||
resolution: "cookie-signature@npm:1.2.2"
|
resolution: "cookie-signature@npm:1.2.2"
|
||||||
@ -16198,6 +16479,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"csstype@npm:^3.2.2":
|
||||||
|
version: 3.2.3
|
||||||
|
resolution: "csstype@npm:3.2.3"
|
||||||
|
checksum: 10c0/cd29c51e70fa822f1cecd8641a1445bed7063697469d35633b516e60fe8c1bde04b08f6c5b6022136bb669b64c63d4173af54864510fbb4ee23281801841a3ce
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"csv-parse@npm:^5.6.0":
|
"csv-parse@npm:^5.6.0":
|
||||||
version: 5.6.0
|
version: 5.6.0
|
||||||
resolution: "csv-parse@npm:5.6.0"
|
resolution: "csv-parse@npm:5.6.0"
|
||||||
@ -20602,6 +20890,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"isbot@npm:^5.1.22":
|
||||||
|
version: 5.1.32
|
||||||
|
resolution: "isbot@npm:5.1.32"
|
||||||
|
checksum: 10c0/e5aa9c5c92dae4879cf49956797c46ef77fa919230183cd6254628667ca5e22f15b24bc4d63b0e88cb96da3d7a51e33f847ef7114fa542e3e066f78178c8d97e
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"isexe@npm:^2.0.0":
|
"isexe@npm:^2.0.0":
|
||||||
version: 2.0.0
|
version: 2.0.0
|
||||||
resolution: "isexe@npm:2.0.0"
|
resolution: "isexe@npm:2.0.0"
|
||||||
@ -24725,6 +25020,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"prettier@npm:^3.5.0":
|
||||||
|
version: 3.6.2
|
||||||
|
resolution: "prettier@npm:3.6.2"
|
||||||
|
bin:
|
||||||
|
prettier: bin/prettier.cjs
|
||||||
|
checksum: 10c0/488cb2f2b99ec13da1e50074912870217c11edaddedeadc649b1244c749d15ba94e846423d062e2c4c9ae683e2d65f754de28889ba06e697ac4f988d44f45812
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"pretty-format@npm:^27.0.2":
|
"pretty-format@npm:^27.0.2":
|
||||||
version: 27.5.1
|
version: 27.5.1
|
||||||
resolution: "pretty-format@npm:27.5.1"
|
resolution: "pretty-format@npm:27.5.1"
|
||||||
@ -26153,7 +26457,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"recast@npm:^0.23.5":
|
"recast@npm:^0.23.11, recast@npm:^0.23.5":
|
||||||
version: 0.23.11
|
version: 0.23.11
|
||||||
resolution: "recast@npm:0.23.11"
|
resolution: "recast@npm:0.23.11"
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -27197,6 +27501,22 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"seroval-plugins@npm:^1.4.0":
|
||||||
|
version: 1.4.0
|
||||||
|
resolution: "seroval-plugins@npm:1.4.0"
|
||||||
|
peerDependencies:
|
||||||
|
seroval: ^1.0
|
||||||
|
checksum: 10c0/d774b8a23bec45f1fefe314e38e26d2fffc0733ad50253a760a10f46cbb0be3a28ed9fcf60aadc0b3f1d2873f4118453a47e84145e858736944dbcd93b42437e
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"seroval@npm:^1.4.0":
|
||||||
|
version: 1.4.0
|
||||||
|
resolution: "seroval@npm:1.4.0"
|
||||||
|
checksum: 10c0/020262db5572c16ae5d22ecefa089112a0b1b9a9c78229dbc9c6059c172ed7f0b5005c7990b80714ff8638ac86274195c2084537e0c2a9178690acacff4b705f
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"serve-static@npm:^2.2.0":
|
"serve-static@npm:^2.2.0":
|
||||||
version: 2.2.0
|
version: 2.2.0
|
||||||
resolution: "serve-static@npm:2.2.0"
|
resolution: "serve-static@npm:2.2.0"
|
||||||
@ -28500,6 +28820,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"tiny-warning@npm:^1.0.3":
|
||||||
|
version: 1.0.3
|
||||||
|
resolution: "tiny-warning@npm:1.0.3"
|
||||||
|
checksum: 10c0/ef8531f581b30342f29670cb41ca248001c6fd7975ce22122bd59b8d62b4fc84ad4207ee7faa95cde982fa3357cd8f4be650142abc22805538c3b1392d7084fa
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"tinybench@npm:^2.9.0":
|
"tinybench@npm:^2.9.0":
|
||||||
version: 2.9.0
|
version: 2.9.0
|
||||||
resolution: "tinybench@npm:2.9.0"
|
resolution: "tinybench@npm:2.9.0"
|
||||||
@ -28929,7 +29256,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"tsx@npm:^4.20.3, tsx@npm:^4.20.6":
|
"tsx@npm:^4.19.2, tsx@npm:^4.20.3, tsx@npm:^4.20.6":
|
||||||
version: 4.20.6
|
version: 4.20.6
|
||||||
resolution: "tsx@npm:4.20.6"
|
resolution: "tsx@npm:4.20.6"
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -29348,6 +29675,18 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"unplugin@npm:^2.1.2":
|
||||||
|
version: 2.3.11
|
||||||
|
resolution: "unplugin@npm:2.3.11"
|
||||||
|
dependencies:
|
||||||
|
"@jridgewell/remapping": "npm:^2.3.5"
|
||||||
|
acorn: "npm:^8.15.0"
|
||||||
|
picomatch: "npm:^4.0.3"
|
||||||
|
webpack-virtual-modules: "npm:^0.6.2"
|
||||||
|
checksum: 10c0/273c1eab0eca4470c7317428689295c31dbe8ab0b306504de9f03cd20c156debb4131bef24b27ac615862958c5dd950a3951d26c0723ea774652ab3624149cff
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"unplugin@npm:^2.3.5":
|
"unplugin@npm:^2.3.5":
|
||||||
version: 2.3.10
|
version: 2.3.10
|
||||||
resolution: "unplugin@npm:2.3.10"
|
resolution: "unplugin@npm:2.3.10"
|
||||||
@ -29489,6 +29828,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"use-sync-external-store@npm:^1.6.0":
|
||||||
|
version: 1.6.0
|
||||||
|
resolution: "use-sync-external-store@npm:1.6.0"
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
|
checksum: 10c0/35e1179f872a53227bdf8a827f7911da4c37c0f4091c29b76b1e32473d1670ebe7bcd880b808b7549ba9a5605c233350f800ffab963ee4a4ee346ee983b6019b
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"utf8-byte-length@npm:^1.0.1":
|
"utf8-byte-length@npm:^1.0.1":
|
||||||
version: 1.0.5
|
version: 1.0.5
|
||||||
resolution: "utf8-byte-length@npm:1.0.5"
|
resolution: "utf8-byte-length@npm:1.0.5"
|
||||||
@ -30482,6 +30830,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"zod@npm:^3.24.2":
|
||||||
|
version: 3.25.76
|
||||||
|
resolution: "zod@npm:3.25.76"
|
||||||
|
checksum: 10c0/5718ec35e3c40b600316c5b4c5e4976f7fee68151bc8f8d90ec18a469be9571f072e1bbaace10f1e85cf8892ea12d90821b200e980ab46916a6166a4260a983c
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"zod@npm:^3.25.76 || ^4":
|
"zod@npm:^3.25.76 || ^4":
|
||||||
version: 4.1.12
|
version: 4.1.12
|
||||||
resolution: "zod@npm:4.1.12"
|
resolution: "zod@npm:4.1.12"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user