|
1 | 1 | <!-- This is a V3 replacement for `FileContextMenu.svelte` -->
|
2 | 2 | <script lang="ts">
|
| 3 | + import ReduxResult from '$components/ReduxResult.svelte'; |
3 | 4 | import { ActionService } from '$lib/actions/actionService.svelte';
|
4 | 5 | import { AIService } from '$lib/ai/service';
|
5 | 6 | import { writeClipboard } from '$lib/backend/clipboard';
|
6 | 7 | import { changesToDiffSpec } from '$lib/commits/utils';
|
7 | 8 | import { projectAiExperimentalFeaturesEnabled, projectAiGenEnabled } from '$lib/config/config';
|
8 | 9 | import { isTreeChange, type TreeChange } from '$lib/hunks/change';
|
9 |
| - import { showToast } from '$lib/notifications/toasts'; |
10 | 10 | import { vscodePath } from '$lib/project/project';
|
11 | 11 | import { ProjectsService } from '$lib/project/projectsService';
|
12 | 12 | import { IdSelection } from '$lib/selection/idSelection.svelte';
|
|
76 | 76 | const userSettings = getContextStoreBySymbol<Settings, Writable<Settings>>(SETTINGS);
|
77 | 77 | const isUncommitted = $derived(selectionId.type === 'worktree');
|
78 | 78 | const isBranchFiles = $derived(selectionId.type === 'branch');
|
| 79 | + const selectionBranchName = $derived( |
| 80 | + selectionId.type === 'branch' ? selectionId.branchName : undefined |
| 81 | + ); |
| 82 | +
|
79 | 83 | const user = getContextStore(User);
|
80 | 84 | const isAdmin = $derived($user.role === 'admin');
|
81 | 85 |
|
|
168 | 172 | return;
|
169 | 173 | }
|
170 | 174 |
|
171 |
| - showToast({ |
172 |
| - style: 'neutral', |
173 |
| - title: 'Figuring out where to commit the changes', |
174 |
| - message: 'This may take a few seconds.' |
175 |
| - }); |
176 |
| -
|
177 |
| - await autoCommit({ projectId, changes }); |
178 |
| -
|
179 |
| - showToast({ |
180 |
| - style: 'success', |
181 |
| - title: 'And... done!', |
182 |
| - message: `Now, you're free to continue` |
183 |
| - }); |
| 175 | + await toasts.promise( |
| 176 | + (async () => { |
| 177 | + await autoCommit({ |
| 178 | + projectId, |
| 179 | + changes |
| 180 | + }); |
| 181 | + })(), |
| 182 | + { |
| 183 | + loading: 'Started auto commit', |
| 184 | + success: 'Auto commit succeded', |
| 185 | + error: (error: Error) => `Auto commit failed: ${error.message}` |
| 186 | + } |
| 187 | + ); |
184 | 188 | }
|
185 | 189 |
|
186 | 190 | async function triggerBranchChanges(changes: TreeChange[]) {
|
|
189 | 193 | return;
|
190 | 194 | }
|
191 | 195 |
|
192 |
| - showToast({ |
193 |
| - style: 'neutral', |
194 |
| - title: 'Creating a branch and committing the changes', |
195 |
| - message: 'This may take a few seconds.' |
196 |
| - }); |
197 |
| -
|
198 |
| - await branchChanges({ projectId, changes }); |
199 |
| -
|
200 |
| - showToast({ |
201 |
| - style: 'success', |
202 |
| - title: 'And... done!', |
203 |
| - message: `Now, you're free to continue` |
204 |
| - }); |
| 196 | + await toasts.promise( |
| 197 | + (async () => { |
| 198 | + await branchChanges({ projectId, changes }); |
| 199 | + })(), |
| 200 | + { |
| 201 | + loading: 'Creating a branch and committing changes', |
| 202 | + success: 'Branching changes succeded', |
| 203 | + error: (error: Error) => `Branching changes failed: ${error.message}` |
| 204 | + } |
| 205 | + ); |
205 | 206 | }
|
206 | 207 |
|
207 | 208 | async function triggerAbsorbChanges(changes: TreeChange[]) {
|
|
210 | 211 | return;
|
211 | 212 | }
|
212 | 213 |
|
213 |
| - showToast({ |
214 |
| - style: 'neutral', |
215 |
| - title: 'Looking for the best place to absorb the changes', |
216 |
| - message: 'This may take a few seconds.' |
217 |
| - }); |
218 |
| -
|
219 |
| - await absorbChanges({ projectId, changes }); |
220 |
| -
|
221 |
| - showToast({ |
222 |
| - style: 'success', |
223 |
| - title: 'And... done!', |
224 |
| - message: `Now, you're free to continue` |
225 |
| - }); |
| 214 | + await toasts.promise( |
| 215 | + (async () => { |
| 216 | + await absorbChanges({ projectId, changes }); |
| 217 | + })(), |
| 218 | + { |
| 219 | + loading: 'Looking for the best place to absorb the changes', |
| 220 | + success: 'Absorbing changes succeded', |
| 221 | + error: (error: Error) => `Absorbing changes failed: ${error.message}` |
| 222 | + } |
| 223 | + ); |
226 | 224 | }
|
227 | 225 |
|
228 | 226 | async function split(changes: TreeChange[]) {
|
|
239 | 237 | const branchName = selectionId.branchName;
|
240 | 238 |
|
241 | 239 | const fileNames = changes.map((change) => change.path);
|
242 |
| - const newBranchName = await stackService.fetchNewBranchName(projectId); |
243 | 240 |
|
244 |
| - if (!newBranchName) { |
245 |
| - toasts.error('Failed to generate a new branch name.'); |
246 |
| - return; |
247 |
| - } |
248 |
| -
|
249 |
| - await splitOffChanges({ |
250 |
| - projectId, |
251 |
| - sourceStackId: stackId, |
252 |
| - sourceBranchName: branchName, |
253 |
| - fileChangesToSplitOff: fileNames, |
254 |
| - newBranchName: newBranchName |
255 |
| - }); |
| 241 | + await toasts.promise( |
| 242 | + (async () => { |
| 243 | + const newBranchName = await stackService.fetchNewBranchName(projectId); |
| 244 | +
|
| 245 | + if (!newBranchName) { |
| 246 | + throw new Error('Failed to generate a new branch name.'); |
| 247 | + } |
| 248 | +
|
| 249 | + await splitOffChanges({ |
| 250 | + projectId, |
| 251 | + sourceStackId: stackId, |
| 252 | + sourceBranchName: branchName, |
| 253 | + fileChangesToSplitOff: fileNames, |
| 254 | + newBranchName: newBranchName |
| 255 | + }); |
| 256 | + })(), |
| 257 | + { |
| 258 | + loading: 'Splitting off changes', |
| 259 | + success: 'Changes split off into a new branch', |
| 260 | + error: (error: Error) => `Failed to split off changes: ${error.message}` |
| 261 | + } |
| 262 | + ); |
256 | 263 | }
|
257 | 264 |
|
258 | 265 | async function splitIntoDependentBranch(changes: TreeChange[]) {
|
|
269 | 276 | const branchName = selectionId.branchName;
|
270 | 277 |
|
271 | 278 | const fileNames = changes.map((change) => change.path);
|
272 |
| - const newBranchName = await stackService.fetchNewBranchName(projectId); |
273 |
| -
|
274 |
| - if (!newBranchName) { |
275 |
| - toasts.error('Failed to generate a new branch name.'); |
276 |
| - return; |
277 |
| - } |
278 | 279 |
|
279 |
| - await splitBranchIntoDependentBranch({ |
280 |
| - projectId, |
281 |
| - sourceStackId: stackId, |
282 |
| - sourceBranchName: branchName, |
283 |
| - fileChangesToSplitOff: fileNames, |
284 |
| - newBranchName: newBranchName |
285 |
| - }); |
| 280 | + await toasts.promise( |
| 281 | + (async () => { |
| 282 | + const newBranchName = await stackService.fetchNewBranchName(projectId); |
| 283 | +
|
| 284 | + if (!newBranchName) { |
| 285 | + throw new Error('Failed to generate a new branch name.'); |
| 286 | + } |
| 287 | +
|
| 288 | + await splitBranchIntoDependentBranch({ |
| 289 | + projectId, |
| 290 | + sourceStackId: stackId, |
| 291 | + sourceBranchName: branchName, |
| 292 | + fileChangesToSplitOff: fileNames, |
| 293 | + newBranchName: newBranchName |
| 294 | + }); |
| 295 | + })(), |
| 296 | + { |
| 297 | + loading: 'Splitting into dependent branch', |
| 298 | + success: 'Changes split into a dependent branch', |
| 299 | + error: (error: Error) => `Failed to split into dependent branch: ${error.message}` |
| 300 | + } |
| 301 | + ); |
286 | 302 | }
|
287 | 303 | </script>
|
288 | 304 |
|
|
369 | 385 | }
|
370 | 386 | }}
|
371 | 387 | />
|
372 |
| - {#if isBranchFiles && isAdmin} |
373 |
| - <ContextMenuItem |
374 |
| - label="Split off changes" |
375 |
| - onclick={async () => { |
376 |
| - await split(changes); |
377 |
| - contextMenu.close(); |
378 |
| - }} |
379 |
| - /> |
380 |
| - <ContextMenuItem |
381 |
| - label="Split into dependent branch" |
382 |
| - onclick={async () => { |
383 |
| - await splitIntoDependentBranch(changes); |
384 |
| - contextMenu.close(); |
385 |
| - }} |
386 |
| - /> |
| 388 | + |
| 389 | + {#if isBranchFiles && stackId && selectionBranchName && isAdmin} |
| 390 | + {@const branchIsConflicted = stackService.isBranchConflicted( |
| 391 | + projectId, |
| 392 | + stackId, |
| 393 | + selectionBranchName |
| 394 | + )} |
| 395 | + <ReduxResult {projectId} result={branchIsConflicted?.current}> |
| 396 | + {#snippet children(isConflicted)} |
| 397 | + {#if isConflicted === false} |
| 398 | + <ContextMenuItem |
| 399 | + label="Split off changes" |
| 400 | + onclick={() => { |
| 401 | + split(changes); |
| 402 | + contextMenu.close(); |
| 403 | + }} |
| 404 | + /> |
| 405 | + <ContextMenuItem |
| 406 | + label="Split into dependent branch" |
| 407 | + onclick={() => { |
| 408 | + splitIntoDependentBranch(changes); |
| 409 | + contextMenu.close(); |
| 410 | + }} |
| 411 | + /> |
| 412 | + {/if} |
| 413 | + {/snippet} |
| 414 | + </ReduxResult> |
387 | 415 | {/if}
|
388 | 416 | {/if}
|
389 | 417 | </ContextMenuSection>
|
|
394 | 422 | tooltip="Try to figure out where to commit the changes. Can create new branches too."
|
395 | 423 | onclick={async () => {
|
396 | 424 | contextMenu.close();
|
397 |
| - await triggerAutoCommit(item.changes); |
| 425 | + triggerAutoCommit(item.changes); |
398 | 426 | }}
|
399 | 427 | disabled={autoCommitting.current.isLoading}
|
400 | 428 | />
|
401 | 429 | <ContextMenuItem
|
402 | 430 | label="Branch changes 🧪"
|
403 | 431 | tooltip="Create a new branch and commit the changes into it."
|
404 |
| - onclick={async () => { |
| 432 | + onclick={() => { |
405 | 433 | contextMenu.close();
|
406 |
| - await triggerBranchChanges(item.changes); |
| 434 | + triggerBranchChanges(item.changes); |
407 | 435 | }}
|
408 | 436 | disabled={branchingChanges.current.isLoading}
|
409 | 437 | />
|
410 | 438 | <ContextMenuItem
|
411 | 439 | label="Absorb changes 🧪"
|
412 | 440 | tooltip="Try to find the best place to absorb the changes into."
|
413 |
| - onclick={async () => { |
| 441 | + onclick={() => { |
414 | 442 | contextMenu.close();
|
415 |
| - await triggerAbsorbChanges(item.changes); |
| 443 | + triggerAbsorbChanges(item.changes); |
416 | 444 | }}
|
417 | 445 | disabled={absorbingChanges.current.isLoading}
|
418 | 446 | />
|
|
0 commit comments