a very good jj gui
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: add app shell layout components

Implement main application layout structure:
- AppShell: main container with resizable panels
- Sidebar: navigation sidebar component
- Toolbar: top toolbar component
- DetailPanel: right detail panel component
- StatusBar: bottom status bar component

+220
+54
apps/desktop/src/components/AppShell.tsx
··· 1 + import { useState } from "react"; 2 + import { ResizablePanelGroup, ResizablePanel, ResizableHandle } from "@/components/ui/resizable"; 3 + import { Toolbar } from "@/components/Toolbar"; 4 + import { Sidebar } from "@/components/Sidebar"; 5 + import { DetailPanel } from "@/components/DetailPanel"; 6 + import { StatusBar } from "@/components/StatusBar"; 7 + 8 + export function AppShell() { 9 + const [repoPath, setRepoPath] = useState<string | null>(null); 10 + const [isLoading, setIsLoading] = useState(false); 11 + const [lastRefresh, setLastRefresh] = useState<Date | null>(null); 12 + 13 + const handleRefresh = () => { 14 + setIsLoading(true); 15 + setTimeout(() => { 16 + setLastRefresh(new Date()); 17 + setIsLoading(false); 18 + }, 1000); 19 + }; 20 + 21 + const handleOpenRepo = () => { 22 + setRepoPath("/example/repo/path"); 23 + }; 24 + 25 + const handleOpenSettings = () => { 26 + console.log("Open settings"); 27 + }; 28 + 29 + return ( 30 + <div className="flex flex-col h-screen w-screen overflow-hidden"> 31 + <Toolbar 32 + repoPath={repoPath} 33 + isLoading={isLoading} 34 + onRefresh={handleRefresh} 35 + onOpenRepo={handleOpenRepo} 36 + onOpenSettings={handleOpenSettings} 37 + /> 38 + 39 + <ResizablePanelGroup orientation="horizontal" className="flex-1"> 40 + <ResizablePanel id="sidebar" defaultSize="25%"> 41 + <Sidebar /> 42 + </ResizablePanel> 43 + 44 + <ResizableHandle withHandle /> 45 + 46 + <ResizablePanel id="detail" defaultSize="75%"> 47 + <DetailPanel /> 48 + </ResizablePanel> 49 + </ResizablePanelGroup> 50 + 51 + <StatusBar branch={repoPath ? "main" : null} lastRefresh={lastRefresh} isConnected={true} /> 52 + </div> 53 + ); 54 + }
+41
apps/desktop/src/components/DetailPanel.tsx
··· 1 + import { ScrollArea } from "@/components/ui/scroll-area"; 2 + import { Card } from "@/components/ui/card"; 3 + import { Separator } from "@/components/ui/separator"; 4 + 5 + export function DetailPanel() { 6 + return ( 7 + <div className="flex flex-col h-full bg-background"> 8 + <div className="p-4 border-b border-border"> 9 + <h2 className="text-sm font-semibold">Detail Panel</h2> 10 + </div> 11 + <ScrollArea className="flex-1"> 12 + <div className="p-4 space-y-4"> 13 + <div> 14 + <h3 className="text-xs font-semibold mb-2 text-muted-foreground">Status</h3> 15 + <Card className="p-3 text-sm text-muted-foreground"> 16 + Status information will appear here when @ is selected 17 + </Card> 18 + </div> 19 + 20 + <Separator /> 21 + 22 + <div> 23 + <h3 className="text-xs font-semibold mb-2 text-muted-foreground">Changed Files</h3> 24 + <Card className="p-3 text-sm text-muted-foreground"> 25 + List of changed files will appear here 26 + </Card> 27 + </div> 28 + 29 + <Separator /> 30 + 31 + <div> 32 + <h3 className="text-xs font-semibold mb-2 text-muted-foreground">File Diff</h3> 33 + <Card className="p-3 text-sm text-muted-foreground"> 34 + File diff viewer will appear here 35 + </Card> 36 + </div> 37 + </div> 38 + </ScrollArea> 39 + </div> 40 + ); 41 + }
+22
apps/desktop/src/components/Sidebar.tsx
··· 1 + import { ScrollArea } from "@/components/ui/scroll-area"; 2 + import { Card } from "@/components/ui/card"; 3 + 4 + export function Sidebar() { 5 + return ( 6 + <div className="flex flex-col h-full bg-card"> 7 + <div className="p-4 border-b border-border"> 8 + <h2 className="text-sm font-semibold">Log View</h2> 9 + </div> 10 + <ScrollArea className="flex-1"> 11 + <div className="p-4 space-y-2"> 12 + <Card className="p-3 text-sm text-muted-foreground"> 13 + Log view will be implemented here 14 + </Card> 15 + <Card className="p-3 text-sm text-muted-foreground"> 16 + Showing commit history, revisions, etc. 17 + </Card> 18 + </div> 19 + </ScrollArea> 20 + </div> 21 + ); 22 + }
+45
apps/desktop/src/components/StatusBar.tsx
··· 1 + import { Separator } from "@/components/ui/separator"; 2 + import { Circle } from "lucide-react"; 3 + 4 + interface StatusBarProps { 5 + branch: string | null; 6 + lastRefresh: Date | null; 7 + isConnected: boolean; 8 + } 9 + 10 + export function StatusBar({ branch, lastRefresh, isConnected }: StatusBarProps) { 11 + const formatRefreshTime = (date: Date | null) => { 12 + if (!date) return "Never"; 13 + return date.toLocaleTimeString(); 14 + }; 15 + 16 + return ( 17 + <div className="flex items-center h-8 px-4 border-t border-border bg-card text-xs text-muted-foreground"> 18 + <div className="flex items-center gap-3"> 19 + {branch && ( 20 + <> 21 + <div className="flex items-center gap-1.5"> 22 + <span className="font-medium">Branch:</span> 23 + <span>{branch}</span> 24 + </div> 25 + <Separator orientation="vertical" className="h-4" /> 26 + </> 27 + )} 28 + 29 + <div className="flex items-center gap-1.5"> 30 + <span className="font-medium">Last refresh:</span> 31 + <span>{formatRefreshTime(lastRefresh)}</span> 32 + </div> 33 + 34 + <Separator orientation="vertical" className="h-4" /> 35 + 36 + <div className="flex items-center gap-1.5"> 37 + <Circle 38 + className={`h-2 w-2 fill-current ${isConnected ? "text-green-500" : "text-red-500"}`} 39 + /> 40 + <span>{isConnected ? "Connected" : "Disconnected"}</span> 41 + </div> 42 + </div> 43 + </div> 44 + ); 45 + }
+58
apps/desktop/src/components/Toolbar.tsx
··· 1 + import { Button } from "@/components/ui/button"; 2 + import { Tooltip, TooltipTrigger, TooltipContent } from "@/components/ui/tooltip"; 3 + import { Separator } from "@/components/ui/separator"; 4 + import { RefreshCw, Settings, FolderOpen } from "lucide-react"; 5 + 6 + interface ToolbarProps { 7 + repoPath: string | null; 8 + isLoading: boolean; 9 + onRefresh: () => void; 10 + onOpenRepo: () => void; 11 + onOpenSettings: () => void; 12 + } 13 + 14 + export function Toolbar({ 15 + repoPath, 16 + isLoading, 17 + onRefresh, 18 + onOpenRepo, 19 + onOpenSettings, 20 + }: ToolbarProps) { 21 + return ( 22 + <div className="flex items-center h-12 px-4 border-b border-border bg-card"> 23 + <div className="flex items-center gap-2 flex-1"> 24 + <Tooltip> 25 + <TooltipTrigger> 26 + <Button variant="ghost" size="sm" onClick={onOpenRepo} className="gap-2"> 27 + <FolderOpen className="h-4 w-4" /> 28 + {repoPath || "Open Repository"} 29 + </Button> 30 + </TooltipTrigger> 31 + <TooltipContent>Open Repository</TooltipContent> 32 + </Tooltip> 33 + </div> 34 + 35 + <div className="flex items-center gap-1"> 36 + <Tooltip> 37 + <TooltipTrigger> 38 + <Button variant="ghost" size="sm" onClick={onRefresh} disabled={isLoading}> 39 + <RefreshCw className={`h-4 w-4 ${isLoading ? "animate-spin" : ""}`} /> 40 + </Button> 41 + </TooltipTrigger> 42 + <TooltipContent>Refresh</TooltipContent> 43 + </Tooltip> 44 + 45 + <Separator orientation="vertical" className="h-6 mx-1" /> 46 + 47 + <Tooltip> 48 + <TooltipTrigger> 49 + <Button variant="ghost" size="sm" onClick={onOpenSettings}> 50 + <Settings className="h-4 w-4" /> 51 + </Button> 52 + </TooltipTrigger> 53 + <TooltipContent>Settings</TooltipContent> 54 + </Tooltip> 55 + </div> 56 + </div> 57 + ); 58 + }