46e3899945
- Changes (#72): a file git reports `MM` whose working copy equals HEAD again (a staged change since undone on disk) is no longer listed — JetBrains' GitChangesCollector drops it the same way. Mode-only changes stay listed. statusCounts (the dirty-tree safety checks) is unchanged. - Chat (#73): Claude Code runs no hook before a folder is trusted, so a new pane's session has no row; the chat now says the session has not started, offers Open terminal, and asks before typing a message or /model //effort into the pane (an Enter would pick the trust prompt's "No, exit"). - Terminal: Safari + macOS's own Vietnamese Telex replaces the whole word on each key (insertReplacementText), which xterm.js ignores — only each word's first letter reached the pane. The edit is now sent as DELs + the new tail. - Also: Ctrl+V pastes a screenshot into the terminal as a file path; the folder browser fallback lists files for a file field (`files=1`); the macOS native picker opens in front of the browser; a hidden tab is marked while a chat waits or once a turn ended; chat live line shows running calls, turn time and tokens, and "Done in …".
83 lines
2.8 KiB
TypeScript
83 lines
2.8 KiB
TypeScript
/**
|
|
* @file PathPickButton.tsx
|
|
* @description The one "Browse" button beside every path field: it asks the
|
|
* server to open the operating system's own folder/file picker
|
|
* (`POST /api/lanes/pick-path`) and fills the field with the absolute path the
|
|
* user chose. A browser's picker cannot give a page that path, so the dialog
|
|
* opens on the server's desktop — which is only this user's screen when the
|
|
* browser runs on the same machine. Anywhere else (a LAN browser, a server
|
|
* with no desktop or no zenity/kdialog) it falls back to the in-app
|
|
* `FolderBrowseModal` (as it does on any other failure) — for a file field
|
|
* too, which then lists files and picks one on click.
|
|
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
|
*/
|
|
|
|
import { useState, type Ref } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import { FolderOpen } from "lucide-react";
|
|
import { api } from "../lib/api";
|
|
import { FolderBrowseModal } from "./lanes/FolderBrowseModal";
|
|
|
|
export function PathPickButton({
|
|
kind = "dir",
|
|
start,
|
|
onPick,
|
|
showLabel = false,
|
|
buttonRef,
|
|
}: {
|
|
kind?: "dir" | "file";
|
|
/** The field's current value — the picker opens there. */
|
|
start?: string;
|
|
onPick: (path: string) => void;
|
|
showLabel?: boolean;
|
|
/** Lets a field beside it open the same picker (a click on an empty path). */
|
|
buttonRef?: Ref<HTMLButtonElement>;
|
|
}) {
|
|
const { t } = useTranslation(["lanes"]);
|
|
const [busy, setBusy] = useState(false);
|
|
const [fallback, setFallback] = useState(false);
|
|
const title = kind === "dir" ? t("browse.title") : t("browse.fileTitle");
|
|
|
|
const pick = async () => {
|
|
setBusy(true);
|
|
try {
|
|
const r = await api.lanes.pickPath(kind, start?.trim() || undefined);
|
|
if (r.path) onPick(r.path);
|
|
} catch {
|
|
// The in-app browser is the fallback whatever went wrong with the
|
|
// native one: a LAN browser (EREMOTE), no desktop, a crashed dialog.
|
|
setFallback(true);
|
|
} finally {
|
|
setBusy(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<button
|
|
ref={buttonRef}
|
|
type="button"
|
|
onClick={pick}
|
|
disabled={busy}
|
|
title={title}
|
|
aria-label={title}
|
|
className="flex shrink-0 items-center gap-1 self-stretch rounded-md border border-border-light px-2 text-xs text-fg-secondary hover:bg-surface-2 disabled:opacity-50"
|
|
>
|
|
<FolderOpen className="h-3.5 w-3.5" />
|
|
{showLabel && t("browse.button")}
|
|
</button>
|
|
{/* Its clicks stay here: a backdrop click must close only this modal,
|
|
not bubble on to a dialog this button sits in. */}
|
|
<div className="contents" onClick={(e) => e.stopPropagation()}>
|
|
<FolderBrowseModal
|
|
open={fallback}
|
|
kind={kind}
|
|
initialPath={start?.trim() || undefined}
|
|
onSelect={onPick}
|
|
onClose={() => setFallback(false)}
|
|
/>
|
|
</div>
|
|
</>
|
|
);
|
|
}
|