The file discarding mechanism has been significantly refactored to improve handling of multi-file operations. Instead of processing discards on a per-file basis, the system now supports batch actions for selected files or reverting all tracked modifications simultaneously. This update centralizes complex discard logic into dedicated functions across the component suite. - Updated state management and types to handle arrays of file changes. - Added UI elements and handlers for discarding all staged/unstaged changes. - Enhanced the confirmation dialog to display multiple discarded targets in a list view.
88 lines
3.0 KiB
Svelte
88 lines
3.0 KiB
Svelte
<script lang="ts">
|
|
import { AlertTriangle, LoaderCircle, RotateCcw, X } from "@lucide/svelte";
|
|
import type { GitFileStatus } from "../types";
|
|
|
|
interface Props {
|
|
files: GitFileStatus[];
|
|
staged: boolean | null;
|
|
scope: "file" | "hunk";
|
|
isBusy: boolean;
|
|
onConfirm: () => void | Promise<void>;
|
|
onClose: () => void;
|
|
}
|
|
|
|
let {
|
|
files,
|
|
staged = false,
|
|
scope = "file",
|
|
isBusy = false,
|
|
onConfirm = () => {},
|
|
onClose = () => {},
|
|
}: Props = $props();
|
|
|
|
function targetPath(file: GitFileStatus): string {
|
|
return file.old_path ? `${file.old_path} -> ${file.path}` : file.path;
|
|
}
|
|
|
|
let count = $derived(files.length);
|
|
let title = $derived(
|
|
scope === "hunk" ? "Discard hunk?" : count > 1 ? `Discard changes in ${count} files?` : "Discard file changes?"
|
|
);
|
|
let scopeLabel = $derived(scope === "hunk" ? "selected hunk" : count > 1 ? `${count} files` : "file");
|
|
let sourceLabel = $derived(staged === null ? "staged and unstaged changes" : staged ? "staged changes" : "unstaged changes");
|
|
|
|
</script>
|
|
|
|
<div class="dialog-backdrop" role="presentation">
|
|
<div class="dialog discard-confirm-dialog" role="dialog" aria-modal="true" aria-label={title}>
|
|
<header class="dialog-header">
|
|
<div>
|
|
<span class="eyebrow">Confirm discard</span>
|
|
<p class="dialog-title">{title}</p>
|
|
</div>
|
|
<button class="btn-sm dialog-close" type="button" onclick={onClose} disabled={isBusy} aria-label="Close">
|
|
<X size={16} aria-hidden="true" />
|
|
</button>
|
|
</header>
|
|
|
|
<div class="discard-confirm-body">
|
|
<div class="discard-warning-icon" aria-hidden="true">
|
|
<AlertTriangle size={22} />
|
|
</div>
|
|
|
|
<div class="discard-confirm-copy">
|
|
<p>
|
|
This will reset the {sourceLabel} for the {scopeLabel} below.
|
|
</p>
|
|
{#if count > 1}
|
|
<ul class="discard-target-list">
|
|
{#each files.slice(0, 8) as file (`${file.old_path ?? ""}:${file.path}`)}
|
|
<li><code class="discard-target" title={targetPath(file)}>{targetPath(file)}</code></li>
|
|
{/each}
|
|
{#if files.length > 8}
|
|
<li class="discard-target-more">+{files.length - 8} more</li>
|
|
{/if}
|
|
</ul>
|
|
{:else if count === 1}
|
|
<code class="discard-target" title={targetPath(files[0])}>{targetPath(files[0])}</code>
|
|
{/if}
|
|
<p class="discard-warning-text">
|
|
This cannot be undone. If a file only exists in your working tree, it can be deleted entirely.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<footer class="discard-confirm-actions">
|
|
<button class="btn-secondary" type="button" onclick={onClose} disabled={isBusy}>Cancel</button>
|
|
<button class="btn-danger" type="button" onclick={onConfirm} disabled={isBusy}>
|
|
{#if isBusy}
|
|
<LoaderCircle class="spin" size={15} aria-hidden="true" />
|
|
{:else}
|
|
<RotateCcw size={15} aria-hidden="true" />
|
|
{/if}
|
|
Discard
|
|
</button>
|
|
</footer>
|
|
</div>
|
|
</div>
|