feat: implement project management features with routing, including project creation, deletion, renaming, and graph persistence
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
/**
|
||||
* Platform: main entry layout with sidebar (sidebar-07). Projects in sidebar;
|
||||
* when a project is selected, the canvas is shown in the main area.
|
||||
* Platform: main layout with sidebar and header. Renders child routes (projects list or canvas) via Outlet.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import React from 'react'
|
||||
import { Link, Outlet, useParams, useLocation } from 'react-router-dom'
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
@@ -14,79 +14,19 @@ import {
|
||||
} from '@/components/ui/breadcrumb'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { SidebarInset, SidebarProvider, SidebarTrigger } from '@/components/ui/sidebar'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Plus } from 'lucide-react'
|
||||
import { PlatformProvider, usePlatform } from './platformContext'
|
||||
import { AppSidebar } from './AppSidebar'
|
||||
import { CanvasPage } from '../canvas/CanvasPage'
|
||||
import type { Project } from './types'
|
||||
import { NewProjectDialog } from './NewProjectDialog'
|
||||
|
||||
const STORAGE_KEY = 'zui_platform_projects'
|
||||
|
||||
function loadProjects(): Project[] {
|
||||
try {
|
||||
const raw = localStorage.getItem(STORAGE_KEY)
|
||||
if (!raw) return []
|
||||
const parsed = JSON.parse(raw) as unknown
|
||||
if (!Array.isArray(parsed)) return []
|
||||
return parsed.filter(
|
||||
(p): p is Project =>
|
||||
p &&
|
||||
typeof p === 'object' &&
|
||||
typeof (p as Project).id === 'string' &&
|
||||
typeof (p as Project).name === 'string' &&
|
||||
typeof (p as Project).iconId === 'string' &&
|
||||
typeof (p as Project).createdAt === 'number'
|
||||
)
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
function saveProjects(projects: Project[]) {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(projects))
|
||||
}
|
||||
|
||||
export function PlatformPage() {
|
||||
const [projects, setProjects] = useState<Project[]>(loadProjects)
|
||||
const [selectedProjectId, setSelectedProjectId] = useState<string | null>(() => {
|
||||
const list = loadProjects()
|
||||
return list.length > 0 ? list[0].id : null
|
||||
})
|
||||
|
||||
const persist = useCallback((next: Project[]) => {
|
||||
setProjects(next)
|
||||
saveProjects(next)
|
||||
}, [])
|
||||
|
||||
const handleCreateProject = useCallback(
|
||||
(project: Project) => {
|
||||
persist([...projects, project])
|
||||
setSelectedProjectId(project.id)
|
||||
},
|
||||
[projects, persist]
|
||||
)
|
||||
|
||||
const handleDeleteProject = useCallback(
|
||||
(id: string) => {
|
||||
const next = projects.filter((p) => p.id !== id)
|
||||
persist(next)
|
||||
if (selectedProjectId === id) setSelectedProjectId(next[0]?.id ?? null)
|
||||
},
|
||||
[projects, persist, selectedProjectId]
|
||||
)
|
||||
|
||||
const selectedProject = projects.find((p) => p.id === selectedProjectId)
|
||||
function PlatformLayoutInner() {
|
||||
const { projectId } = useParams<{ projectId: string }>()
|
||||
const location = useLocation()
|
||||
const { projects } = usePlatform()
|
||||
const project = projectId ? projects.find((p) => p.id === projectId) : null
|
||||
const isProjectsList = location.pathname === '/projects'
|
||||
|
||||
return (
|
||||
<SidebarProvider>
|
||||
<AppSidebar
|
||||
projects={projects}
|
||||
selectedProjectId={selectedProjectId}
|
||||
onSelectProject={setSelectedProjectId}
|
||||
onDeleteProject={handleDeleteProject}
|
||||
onCreateProject={handleCreateProject}
|
||||
/>
|
||||
<AppSidebar />
|
||||
<SidebarInset className="flex min-h-0 flex-1 flex-col">
|
||||
<header className="flex h-12 shrink-0 items-center gap-2 border-b border-border/40 px-1 transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-12">
|
||||
<div className="flex items-center gap-2 px-2">
|
||||
@@ -94,40 +34,41 @@ export function PlatformPage() {
|
||||
<Separator orientation="vertical" className="mr-2 h-4" />
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem className="hidden md:block">
|
||||
<BreadcrumbLink href="#">Projects</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator className="hidden md:block" />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage className="line-clamp-1">
|
||||
{selectedProject ? selectedProject.name : 'No project'}
|
||||
</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
{isProjectsList ? (
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage className="line-clamp-1">Projects</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
) : (
|
||||
<>
|
||||
<BreadcrumbItem className="hidden md:block">
|
||||
<BreadcrumbLink asChild>
|
||||
<Link to="/projects">Projects</Link>
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator className="hidden md:block" />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage className="line-clamp-1">
|
||||
{project ? project.name : 'Project'}
|
||||
</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
</>
|
||||
)}
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
</div>
|
||||
</header>
|
||||
<div className="flex min-h-0 flex-1 flex-col">
|
||||
{selectedProjectId && selectedProject ? (
|
||||
<div className="flex-1 min-h-0 flex flex-col">
|
||||
<CanvasPage projectId={selectedProjectId} />
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-1 flex-col items-center justify-center gap-4 rounded-xl border border-dashed bg-muted/30 p-8">
|
||||
<p className="text-sm text-muted-foreground">No project selected. Create one to open the canvas.</p>
|
||||
<NewProjectDialog
|
||||
onCreate={handleCreateProject}
|
||||
trigger={
|
||||
<Button>
|
||||
<Plus className="size-4" />
|
||||
New project
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<Outlet />
|
||||
</div>
|
||||
</SidebarInset>
|
||||
</SidebarProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export function PlatformPage() {
|
||||
return (
|
||||
<PlatformProvider>
|
||||
<PlatformLayoutInner />
|
||||
</PlatformProvider>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user