mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-18 16:03:24 +00:00
feat: extract CreateWorktreeDialog component and fix Enter to confirm
Extract inline create dialog into its own component. Wrap both dialogs in forms so Enter submits, and autofocus the confirm button in ConfirmDialog so Enter triggers confirm instead of cancel. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
fa2f65e512
commit
8490f4435d
@@ -4,24 +4,17 @@
|
||||
import TopBar from "./lib/TopBar.svelte";
|
||||
import Terminal from "./lib/Terminal.svelte";
|
||||
import ConfirmDialog from "./lib/ConfirmDialog.svelte";
|
||||
import CreateWorktreeDialog from "./lib/CreateWorktreeDialog.svelte";
|
||||
import type { WorktreeInfo } from "./lib/types";
|
||||
import type { Profile } from "./lib/api";
|
||||
import * as api from "./lib/api";
|
||||
|
||||
const PROFILES: { value: Profile; label: string }[] = [
|
||||
{ value: "agent-only", label: "Agent only" },
|
||||
{ value: "agent-yolo", label: "Agent (skip permissions)" },
|
||||
{ value: "full", label: "Full (agent + backend + frontend)" },
|
||||
];
|
||||
|
||||
let worktrees = $state<WorktreeInfo[]>([]);
|
||||
let selectedBranch = $state<string | null>(null);
|
||||
let removeBranch = $state<string | null>(null);
|
||||
let removingBranches = $state<Set<string>>(new Set());
|
||||
let showCreateDialog = $state(false);
|
||||
let creating = $state(false);
|
||||
let createProfile = $state<Profile>("agent-only");
|
||||
let createName = $state("");
|
||||
|
||||
let visibleWorktrees = $derived(
|
||||
worktrees.filter((w) => w.path === "(here)" || w.branch === "main" || w.mux === "✓")
|
||||
@@ -47,14 +40,13 @@
|
||||
return result;
|
||||
}
|
||||
|
||||
async function handleCreate() {
|
||||
const branch = createName.trim() || randomName(8);
|
||||
async function handleCreate(name: string, profile: Profile) {
|
||||
const branch = name || randomName(8);
|
||||
creating = true;
|
||||
try {
|
||||
await api.createWorktree(branch, createProfile);
|
||||
await api.createWorktree(branch, profile);
|
||||
await api.openWorktree(branch);
|
||||
showCreateDialog = false;
|
||||
createName = "";
|
||||
await refresh();
|
||||
selectedBranch = branch;
|
||||
} catch (err) {
|
||||
@@ -135,51 +127,11 @@
|
||||
</div>
|
||||
|
||||
{#if showCreateDialog}
|
||||
{@const btn = "px-3 py-1.5 rounded-md border border-edge bg-surface text-primary text-xs cursor-pointer hover:bg-hover"}
|
||||
<dialog
|
||||
open
|
||||
class="fixed inset-0 z-50 bg-sidebar text-primary border border-edge rounded-xl p-6 max-w-[380px] w-[90%] m-auto"
|
||||
>
|
||||
<h2 class="text-base mb-4">New Worktree</h2>
|
||||
<div class="mb-4">
|
||||
<label class="block text-xs text-muted mb-1.5" for="wt-name">Name <span class="opacity-60">(optional)</span></label>
|
||||
<input
|
||||
id="wt-name"
|
||||
type="text"
|
||||
class="w-full px-2.5 py-1.5 rounded-md border border-edge bg-surface text-primary text-[13px] placeholder:text-muted/50 outline-none focus:border-accent"
|
||||
placeholder="auto-generated if empty"
|
||||
bind:value={createName}
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2 mb-6">
|
||||
{#each PROFILES as profile}
|
||||
<label
|
||||
class="flex items-center gap-2.5 p-2.5 rounded-lg border cursor-pointer text-[13px] transition-colors
|
||||
{createProfile === profile.value ? 'border-accent bg-accent/10' : 'border-edge hover:bg-hover'}"
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name="profile"
|
||||
value={profile.value}
|
||||
checked={createProfile === profile.value}
|
||||
onchange={() => (createProfile = profile.value)}
|
||||
class="accent-[var(--accent)]"
|
||||
/>
|
||||
{profile.label}
|
||||
</label>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="flex justify-end gap-2">
|
||||
<button type="button" class={btn} onclick={() => (showCreateDialog = false)} disabled={creating}>Cancel</button>
|
||||
<button
|
||||
type="button"
|
||||
class="{btn} !bg-accent !text-white !border-accent hover:!opacity-90 disabled:!opacity-50 disabled:!cursor-not-allowed flex items-center gap-1.5"
|
||||
onclick={handleCreate}
|
||||
disabled={creating}
|
||||
>{#if creating}<span class="spinner"></span>{/if} Create</button>
|
||||
</div>
|
||||
</dialog>
|
||||
<div class="fixed inset-0 bg-black/50 z-40" onclick={() => (showCreateDialog = false)}></div>
|
||||
<CreateWorktreeDialog
|
||||
loading={creating}
|
||||
oncreate={handleCreate}
|
||||
oncancel={() => (showCreateDialog = false)}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if removeBranch}
|
||||
|
||||
@@ -16,15 +16,17 @@
|
||||
</script>
|
||||
|
||||
<dialog bind:this={dialogEl} onclose={oncancel} class="bg-sidebar text-primary border border-edge rounded-xl p-6 max-w-[380px] w-[90%]">
|
||||
<h2 class="text-base mb-4">Confirm</h2>
|
||||
<p class="text-[13px] text-muted mb-6">{message}</p>
|
||||
<div class="flex justify-end gap-2">
|
||||
<button type="button" class={btn} onclick={oncancel} disabled={loading}>Cancel</button>
|
||||
<button
|
||||
type="button"
|
||||
class="{btn} !bg-danger !text-white !border-danger hover:!opacity-90 disabled:!opacity-50 disabled:!cursor-not-allowed flex items-center gap-1.5"
|
||||
onclick={onconfirm}
|
||||
disabled={loading}
|
||||
>{#if loading}<span class="spinner"></span>{/if} Remove</button>
|
||||
</div>
|
||||
<form method="dialog" onsubmit={(e) => { e.preventDefault(); onconfirm(); }}>
|
||||
<h2 class="text-base mb-4">Confirm</h2>
|
||||
<p class="text-[13px] text-muted mb-6">{message}</p>
|
||||
<div class="flex justify-end gap-2">
|
||||
<button type="button" class={btn} onclick={oncancel} disabled={loading}>Cancel</button>
|
||||
<button
|
||||
autofocus
|
||||
type="submit"
|
||||
class="{btn} !bg-danger !text-white !border-danger hover:!opacity-90 disabled:!opacity-50 disabled:!cursor-not-allowed flex items-center gap-1.5"
|
||||
disabled={loading}
|
||||
>{#if loading}<span class="spinner"></span>{/if} Remove</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
<script lang="ts">
|
||||
import type { Profile } from "./api";
|
||||
|
||||
const PROFILES: { value: Profile; label: string }[] = [
|
||||
{ value: "agent-only", label: "Agent only" },
|
||||
{ value: "agent-yolo", label: "Agent (skip permissions)" },
|
||||
{ value: "full", label: "Full (agent + backend + frontend)" },
|
||||
];
|
||||
|
||||
let { loading = false, oncreate, oncancel }: {
|
||||
loading?: boolean;
|
||||
oncreate: (name: string, profile: Profile) => void;
|
||||
oncancel: () => void;
|
||||
} = $props();
|
||||
|
||||
let name = $state("");
|
||||
let profile = $state<Profile>("agent-only");
|
||||
|
||||
let dialogEl: HTMLDialogElement;
|
||||
|
||||
$effect(() => {
|
||||
dialogEl?.showModal();
|
||||
});
|
||||
|
||||
const btn = "px-3 py-1.5 rounded-md border border-edge bg-surface text-primary text-xs cursor-pointer hover:bg-hover";
|
||||
</script>
|
||||
|
||||
<dialog bind:this={dialogEl} onclose={oncancel} class="bg-sidebar text-primary border border-edge rounded-xl p-6 max-w-[380px] w-[90%]">
|
||||
<form method="dialog" onsubmit={(e) => { e.preventDefault(); oncreate(name.trim(), profile); }}>
|
||||
<h2 class="text-base mb-4">New Worktree</h2>
|
||||
<div class="mb-4">
|
||||
<label class="block text-xs text-muted mb-1.5" for="wt-name">Name <span class="opacity-60">(optional)</span></label>
|
||||
<input
|
||||
id="wt-name"
|
||||
type="text"
|
||||
class="w-full px-2.5 py-1.5 rounded-md border border-edge bg-surface text-primary text-[13px] placeholder:text-muted/50 outline-none focus:border-accent"
|
||||
placeholder="auto-generated if empty"
|
||||
bind:value={name}
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2 mb-6">
|
||||
{#each PROFILES as p}
|
||||
<label
|
||||
class="flex items-center gap-2.5 p-2.5 rounded-lg border cursor-pointer text-[13px] transition-colors
|
||||
{profile === p.value ? 'border-accent bg-accent/10' : 'border-edge hover:bg-hover'}"
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name="profile"
|
||||
value={p.value}
|
||||
checked={profile === p.value}
|
||||
onchange={() => (profile = p.value)}
|
||||
class="accent-[var(--accent)]"
|
||||
/>
|
||||
{p.label}
|
||||
</label>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="flex justify-end gap-2">
|
||||
<button type="button" class={btn} onclick={oncancel} disabled={loading}>Cancel</button>
|
||||
<button
|
||||
type="submit"
|
||||
class="{btn} !bg-accent !text-white !border-accent hover:!opacity-90 disabled:!opacity-50 disabled:!cursor-not-allowed flex items-center gap-1.5"
|
||||
disabled={loading}
|
||||
>{#if loading}<span class="spinner"></span>{/if} Create</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
Reference in New Issue
Block a user