117 lines
2.7 KiB
TypeScript
117 lines
2.7 KiB
TypeScript
|
|
import type { KeyboardEvent } from "react";
|
||
|
|
import { ProjectPathInput } from "./ProjectPathInput.tsx";
|
||
|
|
import { RecentProjectsList } from "./RecentProjectsList.tsx";
|
||
|
|
|
||
|
|
export interface RecentProjectMatch {
|
||
|
|
name: string;
|
||
|
|
path: string;
|
||
|
|
}
|
||
|
|
|
||
|
|
export interface SelectionScreenProps {
|
||
|
|
knownProjects: string[];
|
||
|
|
onOpenProject: (path: string) => void;
|
||
|
|
onForgetProject: (path: string) => void;
|
||
|
|
pathInput: string;
|
||
|
|
homeDir?: string | null;
|
||
|
|
onPathInputChange: (value: string) => void;
|
||
|
|
onPathInputKeyDown: (event: KeyboardEvent<HTMLInputElement>) => void;
|
||
|
|
isOpening: boolean;
|
||
|
|
suggestionTail: string;
|
||
|
|
matchList: RecentProjectMatch[];
|
||
|
|
selectedMatch: number;
|
||
|
|
onSelectMatch: (index: number) => void;
|
||
|
|
onAcceptMatch: (path: string) => void;
|
||
|
|
onCloseSuggestions: () => void;
|
||
|
|
completionError: string | null;
|
||
|
|
currentPartial: string;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function SelectionScreen({
|
||
|
|
knownProjects,
|
||
|
|
onOpenProject,
|
||
|
|
onForgetProject,
|
||
|
|
pathInput,
|
||
|
|
homeDir,
|
||
|
|
onPathInputChange,
|
||
|
|
onPathInputKeyDown,
|
||
|
|
isOpening,
|
||
|
|
suggestionTail,
|
||
|
|
matchList,
|
||
|
|
selectedMatch,
|
||
|
|
onSelectMatch,
|
||
|
|
onAcceptMatch,
|
||
|
|
onCloseSuggestions,
|
||
|
|
completionError,
|
||
|
|
currentPartial,
|
||
|
|
}: SelectionScreenProps) {
|
||
|
|
const resolvedHomeDir = homeDir
|
||
|
|
? homeDir.endsWith("/")
|
||
|
|
? homeDir
|
||
|
|
: `${homeDir}/`
|
||
|
|
: "";
|
||
|
|
return (
|
||
|
|
<div
|
||
|
|
className="selection-screen"
|
||
|
|
style={{ padding: "2rem", maxWidth: "800px", margin: "0 auto" }}
|
||
|
|
>
|
||
|
|
<h1>Storkit</h1>
|
||
|
|
<p>Paste or complete a project path to start.</p>
|
||
|
|
|
||
|
|
{knownProjects.length > 0 && (
|
||
|
|
<RecentProjectsList
|
||
|
|
projects={knownProjects}
|
||
|
|
onOpenProject={onOpenProject}
|
||
|
|
onForgetProject={onForgetProject}
|
||
|
|
/>
|
||
|
|
)}
|
||
|
|
|
||
|
|
<ProjectPathInput
|
||
|
|
value={pathInput}
|
||
|
|
onChange={onPathInputChange}
|
||
|
|
onKeyDown={onPathInputKeyDown}
|
||
|
|
suggestionTail={suggestionTail}
|
||
|
|
matchList={matchList}
|
||
|
|
selectedMatch={selectedMatch}
|
||
|
|
onSelectMatch={onSelectMatch}
|
||
|
|
onAcceptMatch={onAcceptMatch}
|
||
|
|
onCloseSuggestions={onCloseSuggestions}
|
||
|
|
currentPartial={currentPartial}
|
||
|
|
/>
|
||
|
|
|
||
|
|
<div
|
||
|
|
style={{
|
||
|
|
display: "flex",
|
||
|
|
gap: "8px",
|
||
|
|
marginTop: "8px",
|
||
|
|
alignItems: "center",
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
<button
|
||
|
|
type="button"
|
||
|
|
onClick={() => onOpenProject(pathInput)}
|
||
|
|
disabled={isOpening}
|
||
|
|
>
|
||
|
|
{isOpening ? "Opening..." : "Open Project"}
|
||
|
|
</button>
|
||
|
|
<button
|
||
|
|
type="button"
|
||
|
|
onClick={() => {
|
||
|
|
onPathInputChange(resolvedHomeDir);
|
||
|
|
onCloseSuggestions();
|
||
|
|
}}
|
||
|
|
disabled={isOpening}
|
||
|
|
>
|
||
|
|
New Project
|
||
|
|
</button>
|
||
|
|
<div style={{ fontSize: "0.85em", color: "#666" }}>
|
||
|
|
Press Tab to complete the next path segment
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{completionError && (
|
||
|
|
<div style={{ color: "red", marginTop: "8px" }}>{completionError}</div>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|