SPB Git

Repository Public

page.tsx 4.0 KB 79 lines tsx

1'use client';2import { Suspense, useEffect, useRef, useState } from 'react';3import { useRouter, useSearchParams } from 'next/navigation';4import { Group, Panel, Separator } from 'react-resizable-panels';5import { FolderTree } from 'lucide-react';6import { useProjects } from '@/lib/queries';7import { useBreakpoint } from '@/hooks/use-viewport';8import { useCurrentProject } from '@/hooks/use-current';9import { useUi } from '@/stores/ui';10import { Select } from '@/components/ui/select';11import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';12import { EmptyState, Spinner } from '@/components/ui/misc';13import { FilesBrowser } from '@/components/files/files-browser';14import { GitPanel } from '@/components/files/git-panel';15import { EditorPane } from '@/components/editor/editor-pane';16import { useEditor } from '@/components/editor/editor-store';1718function FilesInner() {19  const params = useSearchParams();20  const router = useRouter();21  const bp = useBreakpoint();22  const projects = useProjects();23  const fallback = useCurrentProject();24  const pid = params.get('p');25  const pathParam = params.get('path');26  const project = projects.data?.find((p) => p.id === pid) ?? (pid ? null : fallback);27  const { sizes, setSizes } = useUi();28  const hasTabs = useEditor((s) => s.tabs.length > 0);29  const activePath = useEditor((s) => s.tabs.find((t) => t.key === s.activeKey)?.path ?? null);30  const [mobileEditor, setMobileEditor] = useState(false);31  const opened = useRef<string | null>(null);3233  // `?path=` opens that file once (deep links from the palette, the terminal side panel…).34  useEffect(() => {35    if (!project || !pathParam) return;36    const key = `${project.id}:${pathParam}`;37    if (opened.current === key) return;38    opened.current = key;39    void useEditor.getState().open(project.id, pathParam);40    setMobileEditor(true);41  }, [project, pathParam]);4243  if (projects.isPending) return <div className="flex h-full items-center justify-center"><Spinner /></div>;44  if (!project) return <EmptyState icon={<FolderTree />} title={pid ? 'Project not found' : 'No projects yet'} description="Register a project to browse its files." className="h-full" />;4546  const openFile = (path: string) => {47    void useEditor.getState().open(project.id, path);48    setMobileEditor(true);49  };50  const openDiff = (path: string) => {51    void useEditor.getState().openGitDiff(project.id, path);52    setMobileEditor(true);53  };54  const side = (55    <Tabs defaultValue="files" className={bp === 'mobile' ? 'flex h-full min-w-0 flex-col bg-bg' : 'flex h-full min-w-0 flex-col bg-surface'}>56      <div className="p-2 pointer-coarse:px-4">57        <Select label="Project" value={project.id} onValueChange={(v) => router.replace(`/files/?p=${v}`)} options={(projects.data ?? []).map((p) => ({ value: p.id, label: p.name }))} />58      </div>59      <TabsList>60        <TabsTrigger value="files">Files</TabsTrigger>61        <TabsTrigger value="git">Git</TabsTrigger>62      </TabsList>63      <TabsContent value="files" className="overflow-hidden"><FilesBrowser projectId={project.id} mode={bp === 'mobile' ? 'drill' : 'tree'} wide={bp !== 'mobile'} onOpenFile={openFile} activePath={activePath} /></TabsContent>64      <TabsContent value="git" className="overflow-hidden"><GitPanel projectId={project.id} onOpenDiff={openDiff} /></TabsContent>65    </Tabs>66  );67  if (bp === 'mobile') return mobileEditor && hasTabs ? <EditorPane mobile onBack={() => setMobileEditor(false)} /> : side;68  return (69    <Group id="files" orientation="horizontal" defaultLayout={sizes.files} onLayoutChanged={(l) => setSizes('files', l)} className="h-full">70      <Panel id="tree" defaultSize="28%" minSize="16%" maxSize="55%">{side}</Panel>71      <Separator className="w-px bg-line-2 transition-colors data-[separator=hover]:bg-accent data-[separator=active]:bg-accent focus-visible:bg-accent focus-visible:outline-none" />72      <Panel id="editor" minSize="30%"><EditorPane /></Panel>73    </Group>74  );75}76export default function FilesPage() {77  return <Suspense><FilesInner /></Suspense>;78}79
Files main
Menu

Theme