Clean up previous project display
This commit is contained in:
@@ -28,15 +28,18 @@ function renderHighlightedMatch(text: string, query: string) {
|
|||||||
if (!query) return text;
|
if (!query) return text;
|
||||||
let qIndex = 0;
|
let qIndex = 0;
|
||||||
const lowerQuery = query.toLowerCase();
|
const lowerQuery = query.toLowerCase();
|
||||||
return text.split("").map((char, index) => {
|
const counts = new Map<string, number>();
|
||||||
|
return text.split("").map((char) => {
|
||||||
const isMatch =
|
const isMatch =
|
||||||
qIndex < lowerQuery.length && char.toLowerCase() === lowerQuery[qIndex];
|
qIndex < lowerQuery.length && char.toLowerCase() === lowerQuery[qIndex];
|
||||||
if (isMatch) {
|
if (isMatch) {
|
||||||
qIndex += 1;
|
qIndex += 1;
|
||||||
}
|
}
|
||||||
|
const count = counts.get(char) ?? 0;
|
||||||
|
counts.set(char, count + 1);
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
key={`${char}-${index}`}
|
key={`${char}-${count}`}
|
||||||
style={isMatch ? { fontWeight: 600, color: "#222" } : undefined}
|
style={isMatch ? { fontWeight: 600, color: "#222" } : undefined}
|
||||||
>
|
>
|
||||||
{char}
|
{char}
|
||||||
@@ -53,7 +56,6 @@ function App() {
|
|||||||
const [knownProjects, setKnownProjects] = React.useState<string[]>([]);
|
const [knownProjects, setKnownProjects] = React.useState<string[]>([]);
|
||||||
const [homeDir, setHomeDir] = React.useState<string | null>(null);
|
const [homeDir, setHomeDir] = React.useState<string | null>(null);
|
||||||
|
|
||||||
const [suggestion, setSuggestion] = React.useState<string | null>(null);
|
|
||||||
const [suggestionTail, setSuggestionTail] = React.useState("");
|
const [suggestionTail, setSuggestionTail] = React.useState("");
|
||||||
const [completionError, setCompletionError] = React.useState<string | null>(
|
const [completionError, setCompletionError] = React.useState<string | null>(
|
||||||
null,
|
null,
|
||||||
@@ -99,7 +101,6 @@ function App() {
|
|||||||
|
|
||||||
async function computeSuggestion() {
|
async function computeSuggestion() {
|
||||||
setCompletionError(null);
|
setCompletionError(null);
|
||||||
setSuggestion(null);
|
|
||||||
setSuggestionTail("");
|
setSuggestionTail("");
|
||||||
setMatchList([]);
|
setMatchList([]);
|
||||||
setSelectedMatch(0);
|
setSelectedMatch(0);
|
||||||
@@ -174,13 +175,11 @@ function App() {
|
|||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (matchList.length === 0) {
|
if (matchList.length === 0) {
|
||||||
setSuggestion(null);
|
|
||||||
setSuggestionTail("");
|
setSuggestionTail("");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const index = Math.min(selectedMatch, matchList.length - 1);
|
const index = Math.min(selectedMatch, matchList.length - 1);
|
||||||
const next = matchList[index];
|
const next = matchList[index];
|
||||||
setSuggestion(next.path);
|
|
||||||
const trimmed = pathInput.trim();
|
const trimmed = pathInput.trim();
|
||||||
if (next.path.startsWith(trimmed)) {
|
if (next.path.startsWith(trimmed)) {
|
||||||
setSuggestionTail(next.path.slice(trimmed.length));
|
setSuggestionTail(next.path.slice(trimmed.length));
|
||||||
@@ -247,13 +246,23 @@ function App() {
|
|||||||
Recent projects
|
Recent projects
|
||||||
</div>
|
</div>
|
||||||
<ul style={{ listStyle: "none", padding: 0, margin: "8px 0 0" }}>
|
<ul style={{ listStyle: "none", padding: 0, margin: "8px 0 0" }}>
|
||||||
{knownProjects.map((project) => (
|
{knownProjects.map((project) => {
|
||||||
|
const displayName =
|
||||||
|
project.split("/").filter(Boolean).pop() ?? project;
|
||||||
|
return (
|
||||||
<li key={project} style={{ marginBottom: "6px" }}>
|
<li key={project} style={{ marginBottom: "6px" }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
gap: "6px",
|
||||||
|
alignItems: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => void openProject(project)}
|
onClick={() => void openProject(project)}
|
||||||
style={{
|
style={{
|
||||||
width: "100%",
|
flex: 1,
|
||||||
textAlign: "left",
|
textAlign: "left",
|
||||||
padding: "8px 10px",
|
padding: "8px 10px",
|
||||||
borderRadius: "6px",
|
borderRadius: "6px",
|
||||||
@@ -263,11 +272,42 @@ function App() {
|
|||||||
fontFamily: "monospace",
|
fontFamily: "monospace",
|
||||||
fontSize: "0.9em",
|
fontSize: "0.9em",
|
||||||
}}
|
}}
|
||||||
|
title={project}
|
||||||
>
|
>
|
||||||
{project}
|
{displayName}
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
aria-label={`Forget ${displayName}`}
|
||||||
|
onClick={() => {
|
||||||
|
void (async () => {
|
||||||
|
try {
|
||||||
|
await api.forgetKnownProject(project);
|
||||||
|
setKnownProjects((prev) =>
|
||||||
|
prev.filter((p) => p !== project),
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
width: "32px",
|
||||||
|
height: "32px",
|
||||||
|
borderRadius: "6px",
|
||||||
|
border: "1px solid #ddd",
|
||||||
|
background: "#fff",
|
||||||
|
cursor: "pointer",
|
||||||
|
fontSize: "1.1em",
|
||||||
|
lineHeight: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</li>
|
</li>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -326,7 +366,6 @@ function App() {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
setMatchList([]);
|
setMatchList([]);
|
||||||
setSelectedMatch(0);
|
setSelectedMatch(0);
|
||||||
setSuggestion(null);
|
|
||||||
setSuggestionTail("");
|
setSuggestionTail("");
|
||||||
setCompletionError(null);
|
setCompletionError(null);
|
||||||
} else if (event.key === "Enter") {
|
} else if (event.key === "Enter") {
|
||||||
|
|||||||
@@ -89,6 +89,13 @@ export const api = {
|
|||||||
getKnownProjects(baseUrl?: string) {
|
getKnownProjects(baseUrl?: string) {
|
||||||
return requestJson<string[]>("/projects", {}, baseUrl);
|
return requestJson<string[]>("/projects", {}, baseUrl);
|
||||||
},
|
},
|
||||||
|
forgetKnownProject(path: string, baseUrl?: string) {
|
||||||
|
return requestJson<boolean>(
|
||||||
|
"/projects/forget",
|
||||||
|
{ method: "POST", body: JSON.stringify({ path }) },
|
||||||
|
baseUrl,
|
||||||
|
);
|
||||||
|
},
|
||||||
openProject(path: string, baseUrl?: string) {
|
openProject(path: string, baseUrl?: string) {
|
||||||
return requestJson<string>(
|
return requestJson<string>(
|
||||||
"/project",
|
"/project",
|
||||||
|
|||||||
@@ -54,4 +54,11 @@ impl ProjectApi {
|
|||||||
let projects = fs::get_known_projects(self.ctx.store.as_ref()).map_err(bad_request)?;
|
let projects = fs::get_known_projects(self.ctx.store.as_ref()).map_err(bad_request)?;
|
||||||
Ok(Json(projects))
|
Ok(Json(projects))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Forget a known project path.
|
||||||
|
#[oai(path = "/projects/forget", method = "post")]
|
||||||
|
async fn forget_known_project(&self, payload: Json<PathPayload>) -> OpenApiResult<Json<bool>> {
|
||||||
|
fs::forget_known_project(payload.0.path, self.ctx.store.as_ref()).map_err(bad_request)?;
|
||||||
|
Ok(Json(true))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -126,6 +126,21 @@ pub fn get_known_projects(store: &dyn StoreOps) -> Result<Vec<String>, String> {
|
|||||||
Ok(projects)
|
Ok(projects)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn forget_known_project(path: String, store: &dyn StoreOps) -> Result<(), String> {
|
||||||
|
let mut known_projects = get_known_projects(store)?;
|
||||||
|
let original_len = known_projects.len();
|
||||||
|
|
||||||
|
known_projects.retain(|p| p != &path);
|
||||||
|
|
||||||
|
if known_projects.len() == original_len {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
store.set(KEY_KNOWN_PROJECTS, json!(known_projects));
|
||||||
|
store.save()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_model_preference(store: &dyn StoreOps) -> Result<Option<String>, String> {
|
pub fn get_model_preference(store: &dyn StoreOps) -> Result<Option<String>, String> {
|
||||||
if let Some(model) = store
|
if let Some(model) = store
|
||||||
.get(KEY_SELECTED_MODEL)
|
.get(KEY_SELECTED_MODEL)
|
||||||
|
|||||||
Reference in New Issue
Block a user