import { Component, createSignal, createEffect, For, Show, onMount } from "solid-js" import { Dialog } from "@kobalte/core/dialog" interface FileItem { path: string added?: number removed?: number isGitFile: boolean } interface FilePickerProps { open: boolean onClose: () => void onSelect: (path: string) => void instanceId: string instanceClient: any searchQuery?: string } const FilePicker: Component = (props) => { const [files, setFiles] = createSignal([]) const [selectedIndex, setSelectedIndex] = createSignal(0) const [query, setQuery] = createSignal(props.searchQuery || "") const [loading, setLoading] = createSignal(false) let inputRef: HTMLInputElement | undefined async function fetchFiles(searchQuery: string) { if (!props.instanceClient) return setLoading(true) try { const gitFilesPromise = props.instanceClient.file.status() const searchFilesPromise = searchQuery ? props.instanceClient.find.files({ query: { query: searchQuery } }) : Promise.resolve({ data: [] }) const [gitResponse, searchResponse] = await Promise.all([gitFilesPromise, searchFilesPromise]) const gitFiles: FileItem[] = (gitResponse.data || []).map((file: any) => ({ path: file.path, added: file.added, removed: file.removed, isGitFile: true, })) const searchFiles: FileItem[] = (searchResponse.data || []) .filter((path: string) => !gitFiles.some((gf) => gf.path === path)) .map((path: string) => ({ path, isGitFile: false, })) const allFiles = searchQuery ? [...gitFiles.filter((f) => f.path.includes(searchQuery)), ...searchFiles] : gitFiles setFiles(allFiles) setSelectedIndex(0) } catch (error) { console.error("Failed to fetch files:", error) setFiles([]) } finally { setLoading(false) } } createEffect(() => { if (props.open) { fetchFiles(query()) } }) createEffect(() => { if (props.searchQuery !== undefined) { setQuery(props.searchQuery) } }) onMount(() => { if (props.open && inputRef) { setTimeout(() => inputRef?.focus(), 50) } }) function handleKeyDown(e: KeyboardEvent) { const fileList = files() if (fileList.length === 0) return switch (e.key) { case "ArrowDown": e.preventDefault() setSelectedIndex((prev) => Math.min(prev + 1, fileList.length - 1)) scrollToSelected() break case "ArrowUp": e.preventDefault() setSelectedIndex((prev) => Math.max(prev - 1, 0)) scrollToSelected() break case "Enter": e.preventDefault() if (fileList[selectedIndex()]) { handleSelect(fileList[selectedIndex()].path) } break case "Escape": e.preventDefault() props.onClose() break } } function scrollToSelected() { setTimeout(() => { const selectedElement = document.querySelector('[data-file-selected="true"]') if (selectedElement) { selectedElement.scrollIntoView({ block: "nearest", behavior: "smooth" }) } }, 0) } function handleSelect(path: string) { props.onSelect(path) props.onClose() } function handleQueryChange(value: string) { setQuery(value) fetchFiles(value) } return ( !open && props.onClose()}>
handleQueryChange(e.currentTarget.value)} class="w-full border-0 bg-transparent text-base outline-none placeholder:text-gray-400 dark:placeholder:text-gray-500" />
Loading files...
} > 0} fallback={
No matching files
} > {(file, index) => (
handleSelect(file.path)} >
{file.path}
+{file.added} -{file.removed}
)}
↑↓ Navigate • Enter Select • Esc Close
) } export default FilePicker