Introduce semantic theme variables (e.g. --color-success, --color-danger, --color-warning, --color-info) and a set of overlay/tint/shadow variables (--app-hover-tint, --app-raise-tint, --app-soft-tint, --app-overlay-shadow, --app-menu-shadow, --app-float-shadow, etc.) and use them throughout app.css in place of many hard-coded color, background and shadow values. This change: - replaces literal color tokens used for badges, pills, buttons, menus, toasts, borders and file icons with the new semantic variables - switches several box-shadow and overlay usages to the new shadow vars - harmonizes light-theme surface, border and scrollbar values to explicit variables for easier maintenance No structural or behavioral changes; this is purely a visual/theming refactor to make future theme adjustments and dark/light parity simpler.
144 lines
12 KiB
Svelte
144 lines
12 KiB
Svelte
<script lang="ts">
|
|
import {GitBranch, AlertTriangle, Bug, Check, GitCommitHorizontal, LoaderCircle, Play, RotateCcw, SkipForward, X } from "@lucide/svelte";
|
|
import type { BisectState } from "../types";
|
|
|
|
interface Props {
|
|
language: "en" | "de";
|
|
bisectState: BisectState | null;
|
|
isLoading: boolean;
|
|
isBusy: boolean;
|
|
operation: string;
|
|
error: string;
|
|
onStart: (good: string, bad: string) => void;
|
|
onMark: (verdict: "good" | "bad" | "skip") => void;
|
|
onReset: () => void;
|
|
onClose: () => void;
|
|
}
|
|
|
|
let { language = "en", bisectState = null, isLoading = false, isBusy = false, operation = "", error = "", onStart = () => {}, onMark = () => {}, onReset = () => {}, onClose = () => {} }: Props = $props();
|
|
let good = $state("");
|
|
let bad = $state("HEAD");
|
|
const de = $derived(language === "de");
|
|
const canStart = $derived(Boolean(good.trim() && bad.trim()) && !isLoading && !isBusy);
|
|
</script>
|
|
|
|
<div class="dialog-backdrop app-chrome-backdrop" role="presentation">
|
|
<div class="dialog bisect-dialog" role="dialog" aria-modal="true" aria-label={de ? "Geführtes Git Bisect" : "Guided Git bisect"} tabindex="-1">
|
|
<header class="dialog-header unified-dialog-header">
|
|
<span class="unified-dialog-icon" aria-hidden="true"><GitBranch size={18} /></span>
|
|
<div class="unified-dialog-text"><span class="eyebrow">{de ? "Fehlerursache eingrenzen" : "Find the regression"}</span><h2 class="dialog-title">Git Bisect</h2></div>
|
|
<button class="dialog-close" type="button" onclick={onClose} disabled={isBusy} title={de ? "Schließen" : "Close"}><X size={18} aria-hidden="true" /></button>
|
|
</header>
|
|
|
|
<div class="bisect-body">
|
|
{#if error}<div class="bisect-notice error"><AlertTriangle size={16} /><span>{error}</span></div>{/if}
|
|
|
|
{#if isLoading}
|
|
<div class="bisect-loading"><LoaderCircle class="spin" size={18} />{de ? "Bisect-Status wird geladen …" : "Loading bisect status …"}</div>
|
|
{:else if !bisectState?.active}
|
|
<section class="bisect-intro">
|
|
<div class="bisect-intro-icon"><Bug size={24} /></div>
|
|
<div><h3>{de ? "Welcher Commit hat den Fehler eingeführt?" : "Which commit introduced the bug?"}</h3><p>{de ? "Gitty checkt schrittweise frühere Commits aus. Markiere jede getestete Version als Good, Bad oder Skip." : "Gitty checks out earlier commits step by step. Mark each tested version as Good, Bad, or Skip."}</p></div>
|
|
</section>
|
|
<div class="bisect-range">
|
|
<div class="range-heading">
|
|
<span>{de ? "Suchbereich" : "Search range"}</span>
|
|
<small>{de ? "Von funktionierend bis fehlerhaft" : "From working to broken"}</small>
|
|
</div>
|
|
<div class="bisect-fields">
|
|
<label class="commit-field good-field">
|
|
<span class="field-title"><Check size={14} />{de ? "Funktionierender Commit" : "Known good commit"}<em>Good</em></span>
|
|
<input bind:value={good} placeholder={de ? "z. B. v1.4.0 oder Commit-Hash" : "e.g. v1.4.0 or commit hash"} disabled={isBusy} spellcheck="false" autocomplete="off" />
|
|
<small>{de ? "Hier trat der Fehler noch nicht auf." : "The bug did not occur here."}</small>
|
|
</label>
|
|
<label class="commit-field bad-field">
|
|
<span class="field-title"><Bug size={14} />{de ? "Fehlerhafter Commit" : "Known bad commit"}<em>Bad</em></span>
|
|
<input bind:value={bad} placeholder="HEAD" disabled={isBusy} spellcheck="false" autocomplete="off" />
|
|
<small>{de ? "Meistens HEAD, der aktuelle Commit." : "Usually HEAD, the current commit."}</small>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="bisect-notice"><AlertTriangle size={15} /><span>{de ? "Der Arbeitsbaum muss sauber sein. Während des Bisects wechselt Gitty vorübergehend zwischen Commits." : "The working tree must be clean. Gitty temporarily switches between commits during the bisect."}</span></div>
|
|
{:else if bisectState.finished && bisectState.culprit}
|
|
<section class="bisect-result">
|
|
<div class="result-icon"><Bug size={22} /></div>
|
|
<div><span>{de ? "Erster fehlerhafter Commit gefunden" : "First bad commit found"}</span><h3>{bisectState.culprit.summary}</h3><div class="commit-meta"><code>{bisectState.culprit.short_hash}</code><span>{bisectState.culprit.author_name}</span><span>{new Date(bisectState.culprit.date).toLocaleString()}</span></div></div>
|
|
</section>
|
|
<p class="result-copy">{de ? "Beende den Bisect, um zum ursprünglichen Branch und Arbeitsstand zurückzukehren." : "Finish the bisect to return to the original branch and working state."}</p>
|
|
{:else if bisectState.current}
|
|
<div class="bisect-progress">
|
|
<span>{de ? "Aktueller Test-Commit" : "Current test commit"}</span>
|
|
{#if bisectState.remaining_steps !== null}<strong>{de ? `Noch ungefähr ${bisectState.remaining_steps} Schritte` : `About ${bisectState.remaining_steps} steps remaining`}</strong>{/if}
|
|
</div>
|
|
<section class="bisect-current">
|
|
<GitCommitHorizontal size={22} />
|
|
<div><h3>{bisectState.current.summary}</h3><div class="commit-meta"><code>{bisectState.current.short_hash}</code><span>{bisectState.current.author_name}</span><span>{new Date(bisectState.current.date).toLocaleString()}</span></div></div>
|
|
</section>
|
|
<div class="bisect-question"><strong>{de ? "Tritt der Fehler bei diesem Commit auf?" : "Does the bug occur at this commit?"}</strong><span>{de ? "Teste die Anwendung oder führe deine Prüfschritte aus, bevor du entscheidest." : "Test the application or run your checks before choosing a verdict."}</span></div>
|
|
<div class="verdict-grid">
|
|
<button class="verdict good" type="button" onclick={() => onMark("good")} disabled={isBusy}><Check size={17} /><span><strong>Good</strong><small>{de ? "Fehler tritt nicht auf" : "Bug is absent"}</small></span></button>
|
|
<button class="verdict bad" type="button" onclick={() => onMark("bad")} disabled={isBusy}><Bug size={17} /><span><strong>Bad</strong><small>{de ? "Fehler tritt auf" : "Bug occurs"}</small></span></button>
|
|
<button class="verdict skip" type="button" onclick={() => onMark("skip")} disabled={isBusy}><SkipForward size={17} /><span><strong>Skip</strong><small>{de ? "Nicht testbar" : "Cannot test"}</small></span></button>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
|
|
<footer class="dialog-footer bisect-footer">
|
|
{#if bisectState?.active}
|
|
<button class="btn-secondary reset" type="button" onclick={onReset} disabled={isBusy}><RotateCcw size={15} />{bisectState.finished ? (de ? "Bisect beenden" : "Finish bisect") : (de ? "Bisect abbrechen" : "Abort bisect")}</button>
|
|
<button class="btn-secondary" type="button" onclick={onClose} disabled={isBusy}>{de ? "Später fortsetzen" : "Continue later"}</button>
|
|
{:else}
|
|
<button class="btn-secondary" type="button" onclick={onClose} disabled={isBusy}>{de ? "Abbrechen" : "Cancel"}</button>
|
|
<button class="btn-primary" type="button" onclick={() => onStart(good.trim(), bad.trim())} disabled={!canStart}>{#if operation === "Starting Git bisect"}<LoaderCircle class="spin" size={16} />{:else}<Play size={16} />{/if}{de ? "Bisect starten" : "Start bisect"}</button>
|
|
{/if}
|
|
</footer>
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
.bisect-dialog{grid-template-rows:auto minmax(0,auto) auto;width:min(640px,calc(100vw - 28px));height:auto;max-height:calc(100vh - 40px);overflow:hidden}
|
|
.bisect-body{display:grid;align-content:start;gap:14px;padding:16px 18px;overflow:auto}
|
|
.bisect-loading{display:flex;min-height:150px;align-items:center;justify-content:center;gap:8px;color:var(--color-ink-muted)}
|
|
.bisect-intro,.bisect-current,.bisect-result{display:flex;align-items:flex-start;gap:12px}
|
|
.bisect-intro-icon,.result-icon{display:grid;width:38px;height:38px;flex:0 0 38px;place-items:center;border:1px solid color-mix(in srgb,var(--color-primary) 32%,var(--color-border));border-radius:8px;color:var(--color-primary);background:color-mix(in srgb,var(--color-primary) 9%,var(--color-surface))}
|
|
.bisect-intro h3,.bisect-current h3,.bisect-result h3{margin:0;font-size:13px}
|
|
.bisect-intro p,.result-copy{max-width:520px;margin:4px 0 0;color:var(--color-ink-muted);font-size:12px;line-height:1.45}
|
|
.bisect-range{display:grid;gap:9px;padding:12px;border:1px solid var(--color-border-subtle);border-radius:9px;background:color-mix(in srgb,var(--color-surface) 72%,transparent)}
|
|
.range-heading{display:flex;align-items:baseline;justify-content:space-between;gap:12px}
|
|
.range-heading>span{color:var(--color-ink);font-size:10px;font-weight:800;letter-spacing:.07em;text-transform:uppercase}
|
|
.range-heading>small{color:var(--color-ink-faint);font-size:10px}
|
|
.bisect-fields{display:grid;grid-template-columns:1fr 1fr;gap:10px}
|
|
.commit-field{display:grid;gap:6px;padding:10px;border:1px solid var(--color-border-subtle);border-radius:7px;background:var(--color-surface-raised)}
|
|
.field-title{display:flex;align-items:center;gap:6px;color:var(--color-ink);font-size:11px;font-weight:700}
|
|
.field-title em{margin-left:auto;padding:1px 5px;border-radius:4px;font-size:9px;font-style:normal;font-weight:800;text-transform:uppercase}
|
|
.good-field .field-title :global(svg),.good-field .field-title em{color:var(--color-success)}
|
|
.good-field .field-title em{background:color-mix(in srgb,var(--color-success) 10%,transparent)}
|
|
.bad-field .field-title :global(svg),.bad-field .field-title em{color:var(--color-danger)}
|
|
.bad-field .field-title em{background:color-mix(in srgb,var(--color-danger) 10%,transparent)}
|
|
.bisect-fields input{height:32px;font-family:var(--font-mono);font-size:11.5px}
|
|
.bisect-fields small{color:var(--color-ink-faint);font-size:9.5px;line-height:1.35}
|
|
.bisect-notice{display:flex;align-items:flex-start;gap:8px;padding:9px 11px;border:1px solid color-mix(in srgb,var(--color-warning) 28%,var(--color-border));border-radius:7px;color:var(--color-ink-muted);background:color-mix(in srgb,var(--color-warning) 6%,var(--color-surface));font-size:11.5px;line-height:1.4}
|
|
.bisect-notice :global(svg){flex:0 0 auto;color:var(--color-warning)}
|
|
.bisect-notice.error{border-color:color-mix(in srgb,var(--color-danger) 40%,var(--color-border));color:#ef979d;background:color-mix(in srgb,var(--color-danger) 8%,var(--color-surface))}
|
|
.bisect-notice.error :global(svg){color:var(--color-danger)}
|
|
.bisect-progress{display:flex;align-items:center;justify-content:space-between;color:var(--color-ink-muted);font-size:11px}
|
|
.bisect-progress strong{color:var(--color-primary)}
|
|
.bisect-current{padding:14px;border:1px solid var(--color-border);border-radius:8px;background:var(--color-surface)}
|
|
.bisect-current>:global(svg){flex:0 0 auto;color:var(--color-primary)}
|
|
.commit-meta{display:flex;flex-wrap:wrap;align-items:center;gap:7px 12px;margin-top:7px;color:var(--color-ink-faint);font-size:10px}
|
|
.commit-meta code{padding:2px 5px;border-radius:4px;color:var(--color-primary);background:color-mix(in srgb,var(--color-primary) 10%,transparent)}
|
|
.bisect-question{display:grid;gap:4px}
|
|
.bisect-question span{color:var(--color-ink-muted);font-size:11px}
|
|
.verdict-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:9px}
|
|
.verdict{min-height:58px;justify-content:flex-start;padding:8px 11px;text-align:left}
|
|
.verdict>span{display:grid;gap:2px}
|
|
.verdict small{color:var(--color-ink-faint);font-size:9.5px}
|
|
.verdict.good{color:var(--color-success)}.verdict.bad{color:var(--color-danger)}.verdict.skip{color:var(--color-warning)}
|
|
.bisect-result{padding:16px;border:1px solid color-mix(in srgb,var(--color-danger) 36%,var(--color-border));border-radius:8px;background:color-mix(in srgb,var(--color-danger) 7%,var(--color-surface))}
|
|
.bisect-result>div:last-child>span{color:var(--color-danger);font-size:10px;font-weight:800;text-transform:uppercase}
|
|
.result-copy{margin:0}
|
|
.bisect-footer{justify-content:flex-end;padding-block:9px}
|
|
.bisect-footer .reset{margin-right:auto}
|
|
@media(max-width:620px){.bisect-fields,.verdict-grid{grid-template-columns:1fr}.bisect-body{padding:14px}.range-heading{align-items:flex-start;flex-direction:column;gap:2px}.bisect-progress{align-items:flex-start;flex-direction:column;gap:4px}}
|
|
</style>
|