import React from 'react'; import { DndContext, closestCenter, PointerSensor, useSensor, useSensors, type DragEndEvent, type Modifier, } from '@dnd-kit/core'; import { SortableContext, useSortable, verticalListSortingStrategy, } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; import { RiFolderAddLine, RiSettings3Line, RiQuestionLine, RiDownloadLine, RiInformationLine, RiPencilLine, RiCloseLine, RiMenuFoldLine, RiMenuUnfoldLine, } from '@remixicon/react'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import { toast } from '@/components/ui'; import { UpdateDialog } from '@/components/ui/UpdateDialog'; import { ProjectEditDialog } from '@/components/layout/ProjectEditDialog'; import { useUIStore } from '@/stores/useUIStore'; import { useProjectsStore } from '@/stores/useProjectsStore'; import { useSessionStore } from '@/stores/useSessionStore'; import { useDirectoryStore } from '@/stores/useDirectoryStore'; import { useUpdateStore } from '@/stores/useUpdateStore'; import { cn, formatDirectoryName, hasModifier } from '@/lib/utils'; import { PROJECT_ICON_MAP, PROJECT_COLOR_MAP } from '@/lib/projectMeta'; import { isDesktopLocalOriginActive, isDesktopShell, isTauriShell } from '@/lib/desktop'; import { useLongPress } from '@/hooks/useLongPress'; import { formatShortcutForDisplay, getEffectiveShortcutCombo } from '@/lib/shortcuts'; import { sessionEvents } from '@/lib/sessionEvents'; import type { ProjectEntry } from '@/lib/api/types'; const normalize = (value: string): string => { if (!value) return ''; const replaced = value.replace(/\\/g, '/'); return replaced === '/' ? '/' : replaced.replace(/\/+$/, ''); }; const NAV_RAIL_WIDTH = 56; const NAV_RAIL_EXPANDED_WIDTH = 200; const NAV_RAIL_TEXT_FADE_MS = 180; const PROJECT_TEXT_FADE_IN_DELAY_MS = 24; const ACTION_TEXT_FADE_IN_DELAY_MS = 60; /** Tinted background for project tiles — uses project color at low opacity, or neutral fallback */ const TileBackground: React.FC<{ colorVar: string | null; children: React.ReactNode }> = ({ colorVar, children, }) => ( {colorVar && ( )} {children} ); /** First-letter avatar fallback */ const LetterAvatar: React.FC<{ label: string; color?: string | null }> = ({ label, color, }) => { const letter = label.charAt(0).toUpperCase() || '?'; const colorVar = color ? (PROJECT_COLOR_MAP[color] ?? null) : null; return ( {letter} ); }; const ProjectStatusDots: React.FC<{ color: string; variant?: 'streaming' | 'attention' | 'none'; size?: 'sm' | 'md'; }> = ({ color, variant = 'none', size = 'md' }) => (