feat: add input/checkbox to confirm dialog and multi-selection actions
Introduce richer confirmation dialogs and wire them through the UI so actions
can collect an optional single-line input and a checkbox option.
- ConfirmDialog: support optional input and checkbox (with defaultChecked),
expose onConfirm(result: {checked, value}). Focus/selects input when present
and blocks confirm while required inputs/checkboxes are missing.
- App: add askConfirmation(...) returning {confirmed, value, checked} and keep
requestConfirmation(...) as a convenience boolean wrapper. Update answer flow
to pass the full result. Use the new prompt in stashStatusFiles to collect a
stash message and "include untracked" option before saving.
- Status/Explorer/Worktree: add multi-selection support for stop-tracking and
stash operations. onStopTracking now accepts an array of paths and a new
"selection" kind; status context menu shows selection counts and uses a
CopyCheck icon for selections. Added corresponding i18n messages.
This change keeps existing UX but enables collecting extra confirmation data
and acting on multi-file selections.
This commit is contained in:
+47
-14
@@ -438,8 +438,9 @@
|
||||
let externalToolsDetectionUnavailable = false;
|
||||
let errorMessage = "";
|
||||
let operation = "";
|
||||
type ConfirmAnswer = { confirmed: boolean; value: string; checked: boolean };
|
||||
let confirmDialogRequest: ConfirmRequest | null = null;
|
||||
let confirmDialogResolve: ((confirmed: boolean) => void) | null = null;
|
||||
let confirmDialogResolve: ((answer: ConfirmAnswer) => void) | null = null;
|
||||
let compareFrom = "";
|
||||
let compareTo = "";
|
||||
let comparison: GitCommitComparison | null = null;
|
||||
@@ -1289,15 +1290,20 @@
|
||||
setLanguage(next);
|
||||
}
|
||||
|
||||
/** Show the confirmation dialog and resolve once the user answers. */
|
||||
function requestConfirmation(request: ConfirmRequest): Promise<boolean> {
|
||||
confirmDialogResolve?.(false);
|
||||
/** Show the dialog and resolve with the answer, including input and checkbox. */
|
||||
function askConfirmation(request: ConfirmRequest): Promise<ConfirmAnswer> {
|
||||
confirmDialogResolve?.({ confirmed: false, value: "", checked: false });
|
||||
confirmDialogRequest = request;
|
||||
return new Promise<boolean>((resolve) => {
|
||||
return new Promise<ConfirmAnswer>((resolve) => {
|
||||
confirmDialogResolve = resolve;
|
||||
});
|
||||
}
|
||||
|
||||
/** Yes/no only, for the many confirmations that need nothing else. */
|
||||
async function requestConfirmation(request: ConfirmRequest): Promise<boolean> {
|
||||
return (await askConfirmation(request)).confirmed;
|
||||
}
|
||||
|
||||
/** Confirmation shown before deleting a local or remote branch. */
|
||||
function branchDeleteConfirmRequest(branch: GitBranchInfo, force: boolean): ConfirmRequest {
|
||||
const remoteName = branch.remote ? branch.name.split("/")[0] : "";
|
||||
@@ -1356,11 +1362,11 @@
|
||||
};
|
||||
}
|
||||
|
||||
function answerConfirmation(confirmed: boolean) {
|
||||
function answerConfirmation(confirmed: boolean, value = "", checked = false) {
|
||||
const resolve = confirmDialogResolve;
|
||||
confirmDialogRequest = null;
|
||||
confirmDialogResolve = null;
|
||||
resolve?.(confirmed);
|
||||
resolve?.({ confirmed, value, checked });
|
||||
}
|
||||
|
||||
function applyThemePreference(next: AppTheme) {
|
||||
@@ -4797,8 +4803,34 @@
|
||||
}
|
||||
|
||||
async function stashStatusFiles(files: GitFileStatus[], label: string) {
|
||||
const suffix = files.length === 1 ? files[0].path : `${label} (${files.length} files)`;
|
||||
await saveStash(`Gitty: ${suffix}`, true, files);
|
||||
if (files.length === 0) return;
|
||||
const suffix = files.length === 1
|
||||
? files[0].path
|
||||
: label ? `${label} (${files.length} files)` : `${files.length} files`;
|
||||
const fallbackMessage = `Gitty: ${suffix}`;
|
||||
|
||||
const answer = await askConfirmation({
|
||||
eyebrow: t("confirm.stashFiles.eyebrow"),
|
||||
title: files.length === 1 ? t("confirm.stashFiles.titleOne") : t("confirm.stashFiles.title", { count: files.length }),
|
||||
message: t("confirm.stashFiles.message"),
|
||||
items: files.map((file) => file.path),
|
||||
input: {
|
||||
label: t("confirm.stashFiles.inputLabel"),
|
||||
placeholder: fallbackMessage,
|
||||
value: fallbackMessage,
|
||||
optional: true,
|
||||
},
|
||||
checkbox: {
|
||||
label: t("confirm.stashFiles.untracked"),
|
||||
note: t("confirm.stashFiles.untrackedNote"),
|
||||
defaultChecked: true,
|
||||
},
|
||||
confirmLabel: t("stashes.save"),
|
||||
danger: false,
|
||||
});
|
||||
|
||||
if (!answer.confirmed) return;
|
||||
await saveStash(answer.value.trim() || fallbackMessage, answer.checked, files);
|
||||
}
|
||||
|
||||
async function applyStashEntry(stash: GitStash) {
|
||||
@@ -4884,12 +4916,13 @@
|
||||
});
|
||||
}
|
||||
|
||||
async function stopTrackingTarget(target: string, kind: "file" | "folder") {
|
||||
if (!activeRepoPath || !target) return;
|
||||
async function stopTrackingTarget(targets: string[], kind: "file" | "folder" | "selection") {
|
||||
const paths = targets.filter(Boolean);
|
||||
if (!activeRepoPath || paths.length === 0) return;
|
||||
await runOperation(`Stopping tracking for ${kind}`, async () => {
|
||||
applyStatus(await untrackPaths(activeRepoPath, [target]));
|
||||
applyStatus(await untrackPaths(activeRepoPath, paths));
|
||||
await refreshExplorerFiles(activeRepoPath);
|
||||
trackEvent("git_paths_untracked", { kind });
|
||||
trackEvent("git_paths_untracked", { kind, paths: paths.length });
|
||||
});
|
||||
}
|
||||
|
||||
@@ -6608,7 +6641,7 @@
|
||||
{#if confirmDialogRequest}
|
||||
<ConfirmDialog
|
||||
request={confirmDialogRequest}
|
||||
onConfirm={() => answerConfirmation(true)}
|
||||
onConfirm={(result) => answerConfirmation(true, result.value, result.checked)}
|
||||
onCancel={() => answerConfirmation(false)}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user