CommitDialog.svelte
1<script lang="ts">
2 import { showCommitDialog } from '$lib/stores/ui';
3 import { uncommittedCount, loadUncommittedFiles, vcs } from '$lib/stores/files';
4
5 let message = $state('');
6 let commitMode = $state<'approved' | 'all'>('approved');
7 let committing = $state(false);
8 let result = $state<{ success: boolean; hash?: string; error?: string } | null>(null);
9
10 // Refresh uncommitted file counts when dialog opens
11 $effect(() => {
12 if ($showCommitDialog) {
13 loadUncommittedFiles();
14 }
15 });
16
17 async function handleCommit() {
18 if (!message.trim()) return;
19 committing = true;
20 result = null;
21
22 try {
23 const res = await fetch('/api/commit', {
24 method: 'POST',
25 headers: { 'Content-Type': 'application/json' },
26 body: JSON.stringify({ message: message.trim(), mode: commitMode })
27 });
28 result = await res.json();
29 } catch (err) {
30 result = { success: false, error: String(err) };
31 } finally {
32 committing = false;
33 }
34 }
35
36 function close() {
37 $showCommitDialog = false;
38 message = '';
39 result = null;
40 }
41
42 function handleKeydown(e: KeyboardEvent) {
43 if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
44 e.preventDefault();
45 handleCommit();
46 }
47 if (e.key === 'Escape') close();
48 }
49</script>
50
51{#if $showCommitDialog && $vcs !== 'jj'}
52 <!-- svelte-ignore a11y_click_events_have_key_events -->
53 <!-- svelte-ignore a11y_no_static_element_interactions -->
54 <div class="fixed inset-0 bg-black/50 z-50 flex items-center justify-center" onclick={close}>
55 <!-- svelte-ignore a11y_click_events_have_key_events -->
56 <!-- svelte-ignore a11y_no_static_element_interactions -->
57 <div class="bg-panel border border-border-strong rounded-xl shadow-2xl w-full max-w-lg mx-4" onclick={(e) => e.stopPropagation()}>
58 <div class="px-5 py-4 border-b border-border">
59 <h2 class="text-lg font-semibold text-primary">Commit Changes</h2>
60 </div>
61
62 {#if result?.success}
63 <div class="p-5">
64 <div class="text-center">
65 <div class="text-accent-green text-4xl mb-3">✓</div>
66 <p class="text-primary font-medium">Committed successfully</p>
67 <p class="text-tertiary text-sm mt-1">Hash: {result.hash}</p>
68 </div>
69 </div>
70 <div class="px-5 py-3 border-t border-border flex justify-end">
71 <button
72 class="text-sm px-4 py-2 rounded bg-active text-primary hover:opacity-80 transition-colors"
73 onclick={close}
74 >
75 Done
76 </button>
77 </div>
78 {:else}
79 <div class="p-5 space-y-4">
80 <!-- Commit scope -->
81 <div>
82 <div class="block text-sm text-tertiary mb-2">What to commit</div>
83 <div class="flex gap-2">
84 <button
85 class="flex-1 text-left px-3 py-2 rounded-lg border text-sm transition-colors {commitMode === 'approved' ? 'border-green-600 bg-accent-green/10 text-accent-green' : 'border-border-strong text-tertiary hover:border-border-strong'}"
86 onclick={() => (commitMode = 'approved')}
87 >
88 <div class="font-medium">Approved files only</div>
89 <div class="text-xs mt-0.5 opacity-70">{$uncommittedCount.approved} of {$uncommittedCount.total} files</div>
90 </button>
91 <button
92 class="flex-1 text-left px-3 py-2 rounded-lg border text-sm transition-colors {commitMode === 'all' ? 'border-blue-600 bg-accent-blue/10 text-accent-blue' : 'border-border-strong text-tertiary hover:border-border-strong'}"
93 onclick={() => (commitMode = 'all')}
94 >
95 <div class="font-medium">All changes</div>
96 <div class="text-xs mt-0.5 opacity-70">{$uncommittedCount.total} files</div>
97 </button>
98 </div>
99 </div>
100
101 {#if commitMode === 'approved' && $uncommittedCount.approved === 0}
102 <div class="text-sm rounded-lg px-3 py-2 bg-accent-yellow/10 text-accent-yellow">
103 No files approved yet. Approve files in the sidebar first, or commit all changes.
104 </div>
105 {/if}
106
107 <!-- Commit message -->
108 <div>
109 <label class="block text-sm text-tertiary mb-2" for="commit-msg">Commit message</label>
110 <textarea
111 id="commit-msg"
112 bind:value={message}
113 onkeydown={handleKeydown}
114 class="w-full bg-surface text-primary border border-border-strong rounded-lg px-3 py-2 text-sm resize-y min-h-[100px] focus:outline-none focus:border-blue-500 placeholder:text-muted"
115 placeholder="Describe your changes..."
116 rows="4"
117 ></textarea>
118 </div>
119
120 {#if result?.error}
121 <p class="text-accent-red text-sm">{result.error}</p>
122 {/if}
123 </div>
124
125 <div class="px-5 py-3 border-t border-border flex items-center justify-between">
126 <span class="text-xs text-muted">
127 {navigator?.platform?.includes('Mac') ? '⌘' : 'Ctrl'}+Enter to commit
128 </span>
129 <div class="flex gap-2">
130 <button
131 class="text-sm px-4 py-2 rounded text-tertiary hover:text-primary hover:bg-hover transition-colors"
132 onclick={close}
133 >
134 Cancel
135 </button>
136 <button
137 class="text-sm px-4 py-2 rounded bg-green-600 text-white hover:bg-green-500 transition-colors disabled:opacity-50"
138 disabled={!message.trim() || committing || (commitMode === 'approved' && $uncommittedCount.approved === 0)}
139 onclick={handleCommit}
140 >
141 {committing ? 'Committing...' : commitMode === 'approved' ? `Commit ${$uncommittedCount.approved} files` : 'Commit all'}
142 </button>
143 </div>
144 </div>
145 {/if}
146 </div>
147 </div>
148{/if}