import { Component, createSignal, Show, For, onMount, onCleanup, createEffect } from "solid-js" import { Folder, Clock, Trash2, FolderPlus, Settings, ChevronRight } from "lucide-solid" import { useConfig } from "../stores/preferences" import AdvancedSettingsModal from "./advanced-settings-modal" import DirectoryBrowserDialog from "./directory-browser-dialog" import Kbd from "./kbd" const codeNomadLogo = new URL("../images/CodeNomad-Icon.png", import.meta.url).href interface FolderSelectionViewProps { onSelectFolder: (folder: string, binaryPath?: string) => void isLoading?: boolean advancedSettingsOpen?: boolean onAdvancedSettingsOpen?: () => void onAdvancedSettingsClose?: () => void } const FolderSelectionView: Component = (props) => { const { recentFolders, removeRecentFolder, preferences } = useConfig() const [selectedIndex, setSelectedIndex] = createSignal(0) const [focusMode, setFocusMode] = createSignal<"recent" | "new" | null>("recent") const [selectedBinary, setSelectedBinary] = createSignal(preferences().lastUsedBinary || "opencode") const [isFolderBrowserOpen, setIsFolderBrowserOpen] = createSignal(false) let recentListRef: HTMLDivElement | undefined const folders = () => recentFolders() const isLoading = () => Boolean(props.isLoading) // Update selected binary when preferences change createEffect(() => { const lastUsed = preferences().lastUsedBinary if (lastUsed && lastUsed !== selectedBinary()) { setSelectedBinary(lastUsed) } }) function scrollToIndex(index: number) { const container = recentListRef if (!container) return const element = container.querySelector(`[data-folder-index="${index}"]`) as HTMLElement | null if (!element) return const containerRect = container.getBoundingClientRect() const elementRect = element.getBoundingClientRect() if (elementRect.top < containerRect.top) { container.scrollTop -= containerRect.top - elementRect.top } else if (elementRect.bottom > containerRect.bottom) { container.scrollTop += elementRect.bottom - containerRect.bottom } } function handleKeyDown(e: KeyboardEvent) { const normalizedKey = e.key.toLowerCase() const isBrowseShortcut = (e.metaKey || e.ctrlKey) && !e.shiftKey && normalizedKey === "n" const blockedKeys = [ "ArrowDown", "ArrowUp", "PageDown", "PageUp", "Home", "End", "Enter", "Backspace", "Delete", ] if (isLoading()) { if (isBrowseShortcut || blockedKeys.includes(e.key)) { e.preventDefault() } return } const folderList = folders() if (isBrowseShortcut) { e.preventDefault() handleBrowse() return } if (folderList.length === 0) return if (e.key === "ArrowDown") { e.preventDefault() const newIndex = Math.min(selectedIndex() + 1, folderList.length - 1) setSelectedIndex(newIndex) setFocusMode("recent") scrollToIndex(newIndex) } else if (e.key === "ArrowUp") { e.preventDefault() const newIndex = Math.max(selectedIndex() - 1, 0) setSelectedIndex(newIndex) setFocusMode("recent") scrollToIndex(newIndex) } else if (e.key === "PageDown") { e.preventDefault() const pageSize = 5 const newIndex = Math.min(selectedIndex() + pageSize, folderList.length - 1) setSelectedIndex(newIndex) setFocusMode("recent") scrollToIndex(newIndex) } else if (e.key === "PageUp") { e.preventDefault() const pageSize = 5 const newIndex = Math.max(selectedIndex() - pageSize, 0) setSelectedIndex(newIndex) setFocusMode("recent") scrollToIndex(newIndex) } else if (e.key === "Home") { e.preventDefault() setSelectedIndex(0) setFocusMode("recent") scrollToIndex(0) } else if (e.key === "End") { e.preventDefault() const newIndex = folderList.length - 1 setSelectedIndex(newIndex) setFocusMode("recent") scrollToIndex(newIndex) } else if (e.key === "Enter") { e.preventDefault() handleEnterKey() } else if (e.key === "Backspace" || e.key === "Delete") { e.preventDefault() if (folderList.length > 0 && focusMode() === "recent") { const folder = folderList[selectedIndex()] if (folder) { handleRemove(folder.path) } } } } function handleEnterKey() { if (isLoading()) return const folderList = folders() const index = selectedIndex() const folder = folderList[index] if (folder) { handleFolderSelect(folder.path) } } onMount(() => { window.addEventListener("keydown", handleKeyDown) onCleanup(() => { window.removeEventListener("keydown", handleKeyDown) }) }) function formatRelativeTime(timestamp: number): string { const seconds = Math.floor((Date.now() - timestamp) / 1000) const minutes = Math.floor(seconds / 60) const hours = Math.floor(minutes / 60) const days = Math.floor(hours / 24) if (days > 0) return `${days}d ago` if (hours > 0) return `${hours}h ago` if (minutes > 0) return `${minutes}m ago` return "just now" } function handleFolderSelect(path: string) { if (isLoading()) return props.onSelectFolder(path, selectedBinary()) } function handleBrowse() { if (isLoading()) return setFocusMode("new") setIsFolderBrowserOpen(true) } function handleBrowserSelect(path: string) { setIsFolderBrowserOpen(false) handleFolderSelect(path) } function handleBinaryChange(binary: string) { setSelectedBinary(binary) } function handleRemove(path: string, e?: Event) { if (isLoading()) return e?.stopPropagation() removeRecentFolder(path) const folderList = folders() if (selectedIndex() >= folderList.length && folderList.length > 0) { setSelectedIndex(folderList.length - 1) } } function getDisplayPath(path: string): string { if (path.startsWith("/Users/")) { return path.replace(/^\/Users\/[^/]+/, "~") } return path } return ( <>
CodeNomad logo

CodeNomad

Select a folder to start coding with AI

0} fallback={

No Recent Folders

Browse for a folder to get started

} >

Recent Folders

{folders().length} {folders().length === 1 ? "folder" : "folders"} available

(recentListRef = el)}> {(folder, index) => (
)}

Browse for Folder

Select any folder on your computer

{/* Advanced settings section */}

Starting instance…

Hang tight while we prepare your workspace.

props.onAdvancedSettingsClose?.()} selectedBinary={selectedBinary()} onBinaryChange={handleBinaryChange} isLoading={props.isLoading} /> setIsFolderBrowserOpen(false)} onSelect={handleBrowserSelect} /> ) } export default FolderSelectionView