import React, { useEffect, useRef, useState } from "react"; import { ArrowUp, ChevronLeft, ChevronRight, Copy, FilePlus, Folder, FolderPlus, Grid3X3, Laptop, Layout, List, Plus, RefreshCw, Rows3, Search, Trash2, Upload, } from "lucide-react"; import { Button, Input, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "@termix/plugin-sdk/ui"; import type { FileItem } from "./host-types"; type SortBy = "name" | "modified" | "size"; type SortOrder = "asc" | "desc"; type ViewMode = "grid" | "list"; type Density = "comfortable" | "compact"; type FileManagerToolbarProps = { t: (key: string) => string; currentPath: string; navIndex: number; navHistoryLength: number; isLoading: boolean; sshSessionId: string | null; selectedFiles: FileItem[]; searchQuery: string; setSearchQuery: (query: string) => void; viewMode: ViewMode; setViewMode: (mode: ViewMode) => void; density: Density; setDensity: (density: Density) => void; sortBy: SortBy; setSortBy: (sortBy: SortBy) => void; sortOrder: SortOrder; setSortOrder: (sortOrder: SortOrder) => void; setMobileSidebarOpen: (updater: (open: boolean) => boolean) => void; goBack: () => void; goForward: () => void; goUp: () => void; navigateTo: (path: string) => void; handleRefreshDirectory: () => void; handleDeleteFiles: (files: FileItem[]) => void; handleCopyFiles: (files: FileItem[]) => void; handleFilesDropped: (fileList: FileList) => void; handleCreateNewFolder: () => void; handleCreateNewFile: () => void; /** Desktop app only: show the Local | Remote split-view toggle. */ showLocalPaneToggle?: boolean; localPaneOpen?: boolean; onToggleLocalPane?: () => void; /** Desktop directories sidebar (mobile uses the overlay instead). */ sidebarOpen?: boolean; onToggleSidebar?: () => void; }; function Breadcrumb({ currentPath, navigateTo, t, }: Pick) { return ( <>
{currentPath.split("/").map((part, i, arr) => ( {part === "" && i === 0 ? ( ) : part !== "" ? ( ) : null} {i < arr.length - 1 && part !== "" && ( )} {i === 0 && arr.length > 1 && part === "" && ( )} ))}
); } function PathBar({ currentPath, navigateTo, t, className, }: Pick & { className: string; }) { const [isEditing, setIsEditing] = useState(false); const [value, setValue] = useState(currentPath); const inputRef = useRef(null); const doneRef = useRef(false); useEffect(() => { if (!isEditing) return; doneRef.current = false; const timer = setTimeout(() => { inputRef.current?.focus(); inputRef.current?.select(); }, 0); return () => clearTimeout(timer); }, [isEditing]); const commit = (path: string) => { if (doneRef.current) return; doneRef.current = true; setIsEditing(false); const trimmed = path.trim(); if (trimmed && trimmed !== currentPath) { navigateTo(trimmed); } }; const cancel = () => { if (doneRef.current) return; doneRef.current = true; setIsEditing(false); }; if (isEditing) { return (
setValue(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") { e.preventDefault(); commit(value); } else if (e.key === "Escape") { e.preventDefault(); cancel(); } }} onBlur={() => commit(value)} className="flex-1 min-w-0 bg-transparent text-xs font-semibold tracking-wide outline-none text-foreground" />
); } return (
{ setValue(currentPath); setIsEditing(true); }} >
); } export function FileManagerToolbar({ t, currentPath, navIndex, navHistoryLength, isLoading, sshSessionId, selectedFiles, searchQuery, setSearchQuery, viewMode, setViewMode, density, setDensity, sortBy, setSortBy, sortOrder, setSortOrder, setMobileSidebarOpen, goBack, goForward, goUp, navigateTo, handleRefreshDirectory, handleDeleteFiles, handleCopyFiles, handleFilesDropped, handleCreateNewFolder, handleCreateNewFile, showLocalPaneToggle = false, localPaneOpen = false, onToggleLocalPane, sidebarOpen = true, onToggleSidebar, }: FileManagerToolbarProps) { const searchInputRef = useRef(null); useEffect(() => { const handleSearchShortcut = (event: KeyboardEvent) => { if ( event.defaultPrevented || !(event.ctrlKey || event.metaKey) || event.altKey || event.shiftKey || event.key.toLowerCase() !== "f" ) return; const input = searchInputRef.current; if (!input) return; const visible = typeof input.checkVisibility === "function" ? input.checkVisibility({ visibilityProperty: true }) : input.offsetParent !== null; if (!visible) return; const active = document.activeElement; if ( active !== input && active?.closest( 'input, textarea, [contenteditable="true"], [role="dialog"]', ) ) return; event.preventDefault(); input.focus(); input.select(); }; document.addEventListener("keydown", handleSearchShortcut); return () => document.removeEventListener("keydown", handleSearchShortcut); }, []); return (
{selectedFiles.length > 0 && (
)}
setSearchQuery(e.target.value)} className="h-8 pl-8 text-xs bg-muted/50 border-border rounded-none focus:ring-1 focus:ring-accent-brand/50" />
{showLocalPaneToggle && ( )}
e.preventDefault()} > { setTimeout(() => handleCreateNewFolder(), 0); }} className="rounded-none text-xs font-semibold gap-2 focus:bg-accent-brand/10 focus:text-accent-brand" > {t("fileManager.newFolder")} { setTimeout(() => handleCreateNewFile(), 0); }} className="rounded-none text-xs font-semibold gap-2 focus:bg-accent-brand/10 focus:text-accent-brand" > {t("fileManager.newFile")} {t("fileManager.sortBy")} setSortBy(value as SortBy)} > {t("fileManager.sortByName")} {t("fileManager.sortByDate")} {t("fileManager.sortBySize")} setSortOrder(value as SortOrder)} > {t("fileManager.ascending")} {t("fileManager.descending")}
); }