From e0989a01a5d08a8fd25a22cec7cb76cd3e189a0c Mon Sep 17 00:00:00 2001 From: Jamie Pine Date: Tue, 23 Dec 2025 00:34:53 -0800 Subject: [PATCH] Enhance PathBar component with editing functionality - Introduced editing mode in the PathBar component, allowing users to modify paths directly. - Added state management for editing, including handling input changes and keyboard events for submission and cancellation. - Updated the rendering logic to accommodate editing input, improving user interaction. - Refactored device icon retrieval to streamline the process and ensure correct icon display based on device state. - Adjusted width calculations for different states, enhancing the visual responsiveness of the PathBar. --- .../Explorer/components/PathBar.tsx | 134 +++++++++++++++--- .../src/components/SpacesSidebar/index.tsx | 38 ++--- 2 files changed, 138 insertions(+), 34 deletions(-) diff --git a/packages/interface/src/components/Explorer/components/PathBar.tsx b/packages/interface/src/components/Explorer/components/PathBar.tsx index fc00bb2d4..18481b518 100644 --- a/packages/interface/src/components/Explorer/components/PathBar.tsx +++ b/packages/interface/src/components/Explorer/components/PathBar.tsx @@ -10,7 +10,7 @@ import { RadioButtonIcon, } from "@phosphor-icons/react"; import type { SdPath, LibraryDeviceInfo } from "@sd/ts-client"; -import { getDeviceIconBySlug, useLibraryMutation } from "@sd/ts-client"; +import { getDeviceIcon, useLibraryMutation } from "@sd/ts-client"; import { sdPathToUri } from "../utils"; import LaptopIcon from "@sd/assets/icons/Laptop.png"; import { useNormalizedQuery } from "@sd/ts-client"; @@ -250,6 +250,9 @@ function IndexIndicator({ path }: { path: SdPath }) { export function PathBar({ path, devices, onNavigate }: PathBarProps) { const [isExpanded, setIsExpanded] = useState(false); const [isShiftHeld, setIsShiftHeld] = useState(false); + const [isEditing, setIsEditing] = useState(false); + const [editValue, setEditValue] = useState(""); + const [editingAsUri, setEditingAsUri] = useState(false); const { navigateToView } = useExplorer(); const uri = sdPathToUri(path); const currentDir = getCurrentDirectoryName(path); @@ -264,7 +267,7 @@ export function PathBar({ path, devices, onNavigate }: PathBarProps) { (d) => d.slug === deviceSlug, ); return { - icon: getDeviceIconBySlug(deviceSlug, devices), + icon: device ? getDeviceIcon(device) : LaptopIcon, device, }; } @@ -278,6 +281,68 @@ export function PathBar({ path, devices, onNavigate }: PathBarProps) { } }; + const enterEditMode = (initialValue: string, asUri: boolean) => { + setIsEditing(true); + setEditValue(initialValue); + setEditingAsUri(asUri); + }; + + const exitEditMode = () => { + setIsEditing(false); + setEditValue(""); + setEditingAsUri(false); + }; + + const handleContainerClick = (e: React.MouseEvent) => { + // Only enter edit mode if clicking the container itself, not buttons/segments + if (e.target === e.currentTarget || (e.target as HTMLElement).tagName === "INPUT") { + const isUriMode = showUri; + const valueToEdit = isUriMode ? uri : ("Physical" in path ? path.Physical.path : uri); + enterEditMode(valueToEdit, isUriMode); + } + }; + + const handleEditKeyDown = (e: React.KeyboardEvent) => { + if (e.key === "Enter") { + e.preventDefault(); + submitEdit(); + } else if (e.key === "Escape") { + e.preventDefault(); + exitEditMode(); + } + }; + + const submitEdit = () => { + const trimmed = editValue.trim(); + if (!trimmed) { + exitEditMode(); + return; + } + + try { + if (editingAsUri) { + // Try to parse as SdPath JSON + const parsed = JSON.parse(trimmed) as SdPath; + onNavigate(parsed); + } else { + // Parse as file path string + if ("Physical" in path) { + const newPath: SdPath = { + Physical: { + device_slug: path.Physical.device_slug, + path: trimmed.startsWith("/") ? trimmed : `/${trimmed}`, + }, + }; + onNavigate(newPath); + } + } + } catch (error) { + console.error("Failed to parse path:", error); + } + + exitEditMode(); + }; + useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.key === "Shift") setIsShiftHeld(true); @@ -297,7 +362,7 @@ export function PathBar({ path, devices, onNavigate }: PathBarProps) { const showUri = isExpanded && isShiftHeld; - // Calculate widths for three states + // Calculate widths for different states const collapsedWidth = currentDir.length * 8.5 + 70; const breadcrumbsWidth = Math.min( segments.reduce((sum, seg) => sum + seg.name.length * 6.5, 0) + @@ -306,29 +371,37 @@ export function PathBar({ path, devices, onNavigate }: PathBarProps) { 600, ); const uriWidth = Math.min(uri.length * 7 + 70, 600); + const editWidth = Math.max(200, Math.min(editValue.length * 7 + 70, 600)); - const currentWidth = !isExpanded - ? collapsedWidth - : showUri - ? uriWidth - : breadcrumbsWidth; + const currentWidth = isEditing + ? editWidth + : !isExpanded + ? collapsedWidth + : showUri + ? uriWidth + : breadcrumbsWidth; return (
setIsExpanded(true)} - onMouseLeave={() => setIsExpanded(false)} + onMouseEnter={() => !isEditing && setIsExpanded(true)} + onMouseLeave={() => !isEditing && setIsExpanded(false)} + onClick={handleContainerClick} className={clsx( "flex items-center gap-1.5 h-8 px-3 rounded-full", "backdrop-blur-xl border border-sidebar-line/30", "bg-sidebar-box/20 transition-colors", "focus-within:bg-sidebar-box/30 focus-within:border-sidebar-line/40", + !isEditing && "cursor-text", )} > - {showUri ? ( + {isEditing ? ( + setEditValue(e.target.value)} + onKeyDown={handleEditKeyDown} + onBlur={exitEditMode} + autoFocus + className={clsx( + "bg-transparent border-0 outline-none ring-0 flex-1 min-w-0", + "text-xs font-medium text-sidebar-ink", + "placeholder:text-sidebar-inkFaint", + "focus:ring-0 focus:outline-none", + editingAsUri && "font-mono", + )} + placeholder={editingAsUri ? "Enter SdPath JSON..." : "Enter path..."} + /> + ) : showUri ? ( - {!isLast && } + {!isLast && ( + + )}
); })} diff --git a/packages/interface/src/components/SpacesSidebar/index.tsx b/packages/interface/src/components/SpacesSidebar/index.tsx index 8cd0f4161..b0c506b8b 100644 --- a/packages/interface/src/components/SpacesSidebar/index.tsx +++ b/packages/interface/src/components/SpacesSidebar/index.tsx @@ -141,7 +141,7 @@ function SyncButton() { /> } side="top" - align="end" + align="start" sideOffset={8} className="w-[380px] max-h-[520px] z-50 !p-0 !bg-app !rounded-xl" > @@ -247,7 +247,7 @@ function JobsButton({ /> } side="top" - align="end" + align="start" sideOffset={8} className="w-[360px] max-h-[480px] z-50 !p-0 !bg-app !rounded-xl" > @@ -421,22 +421,24 @@ export function SpacesSidebar({ isPreviewActive = false }: SpacesSidebarProps) { {/* Sync Monitor, Job Manager, Customize & Settings (pinned to bottom) */} -
- - - setCustomizePanelOpen(true)} - /> +
+
+ + + setCustomizePanelOpen(true)} + /> +