Skip to content

Commit 53f415d

Browse files
committed
Fix drag to uncommit
1 parent a26a552 commit 53f415d

File tree

13 files changed

+84
-96
lines changed

13 files changed

+84
-96
lines changed

apps/desktop/src/components/v3/FileListItemWrapper.svelte

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,6 @@
6969
let contextMenu = $state<ReturnType<typeof FileContextMenu>>();
7070
let draggableEl: HTMLDivElement | undefined = $state();
7171
72-
const selectedChanges = $derived(idSelection.treeChanges(projectId, selectionId));
73-
7472
const previousTooltipText = $derived(
7573
(change.status.subject as Rename).previousPath
7674
? `${(change.status.subject as Rename).previousPath} →\n${change.path}`
@@ -97,17 +95,12 @@
9795
9896
const checkStatus = $derived(uncommittedService.fileCheckStatus(stackId, change.path));
9997
100-
function onContextMenu(e: MouseEvent) {
101-
if (
102-
selectedChanges &&
103-
selectedChanges.current.isSuccess &&
104-
idSelection.has(change.path, selectionId)
105-
) {
106-
const changes: TreeChange[] = selectedChanges.current.data;
98+
async function onContextMenu(e: MouseEvent) {
99+
const changes = await idSelection.treeChanges(projectId, selectionId);
100+
if (idSelection.has(change.path, selectionId)) {
107101
contextMenu?.open(e, { changes });
108102
return;
109103
}
110-
111104
contextMenu?.open(e, { changes: [change] });
112105
}
113106
@@ -126,7 +119,7 @@
126119
use:draggableChips={{
127120
label: getFilename(change.path),
128121
filePath: change.path,
129-
data: new ChangeDropData(change, uncommittedService, idSelection, selectionId, stackId || null),
122+
data: new ChangeDropData(projectId, change, idSelection, selectionId, stackId || null),
130123
viewportId: 'board-viewport',
131124
selector: '.selected-draggable',
132125
disabled: draggableDisabled,

apps/desktop/src/components/v3/UnifiedDiffView.svelte

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,7 @@
7777
!draggable || !['commit', 'worktree'].includes(selectionId.type)
7878
);
7979
80-
const assignments = $derived(
81-
uncommittedService.getAssignmentsByPath(stackId || null, change.path)
82-
);
80+
const assignments = $derived(uncommittedService.assignmentsByPath(stackId || null, change.path));
8381
8482
function filter(hunks: DiffHunk[]): DiffHunk[] {
8583
if (selectionId.type !== 'worktree') return hunks;

apps/desktop/src/lib/commits/dropHandler.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { dropDataToDiffSpec } from '$lib/commits/utils';
1+
import { changesToDiffSpec } from '$lib/commits/utils';
22
import {
33
ChangeDropData,
44
FileDropData,
@@ -48,15 +48,15 @@ export class StartCommitDzHandler implements DropzoneHandler {
4848
(data instanceof HunkDropDataV3 && data.uncommitted)
4949
);
5050
}
51-
ondrop(data: ChangeDropData | HunkDropDataV3): void {
51+
async ondrop(data: ChangeDropData | HunkDropDataV3): Promise<void> {
5252
const { projectId, stackId, branchName, uiState, uncommittedService } = this.args;
5353

5454
const projectState = uiState.project(projectId);
5555
const stackState = stackId ? uiState.stack(stackId) : undefined;
5656

5757
uncommittedService.uncheckAll(null);
5858
if (data instanceof ChangeDropData) {
59-
for (const change of data.changes) {
59+
for (const change of await data.treeChanges()) {
6060
uncommittedService.checkFile(null, change.path);
6161
}
6262
} else if (data instanceof HunkDropDataV3) {
@@ -126,14 +126,15 @@ export class AmendCommitWithChangeDzHandler implements DropzoneHandler {
126126
case 'commit': {
127127
const sourceStackId = data.stackId;
128128
const sourceCommitId = data.selectionId.commitId;
129+
const changes = changesToDiffSpec(await data.treeChanges());
129130
if (sourceStackId && sourceCommitId) {
130131
const { replacedCommits } = await this.stackService.moveChangesBetweenCommits({
131132
projectId: this.projectId,
132133
destinationStackId: this.stackId,
133134
destinationCommitId: this.commit.id,
134135
sourceStackId,
135136
sourceCommitId,
136-
changes: dropDataToDiffSpec(data)
137+
changes
137138
});
138139

139140
// Update the project state to point to the new commit if needed.
@@ -147,16 +148,18 @@ export class AmendCommitWithChangeDzHandler implements DropzoneHandler {
147148
case 'branch':
148149
console.warn('Moving a branch into a commit is an invalid operation');
149150
break;
150-
case 'worktree':
151-
this.onresult(
151+
case 'worktree': {
152+
const diffSpec = changesToDiffSpec(await data.treeChanges());
153+
return this.onresult(
152154
await this.trigger({
153155
projectId: this.projectId,
154156
stackId: this.stackId,
155157
branchName: this.branchName,
156158
commitId: this.commit.id,
157-
worktreeChanges: dropDataToDiffSpec(data)
159+
worktreeChanges: diffSpec
158160
})
159161
);
162+
}
160163
}
161164
}
162165
}
@@ -191,11 +194,12 @@ export class UncommitDzHandler implements DropzoneHandler {
191194
const stackId = data.stackId;
192195
const commitId = data.selectionId.commitId;
193196
if (stackId && commitId) {
197+
const changes = changesToDiffSpec(await data.treeChanges());
194198
const { replacedCommits } = await this.stackService.uncommitChanges({
195199
projectId: this.projectId,
196200
stackId,
197201
commitId,
198-
changes: dropDataToDiffSpec(data)
202+
changes
199203
});
200204

201205
// Update the project state to point to the new commit if needed.

apps/desktop/src/lib/commits/utils.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import type { DetailedCommit } from '$lib/commits/commit';
2-
import type { ChangeDropData } from '$lib/dragging/draggables';
32
import type { TreeChange } from '$lib/hunks/change';
43
import type { DiffSpec } from '$lib/hunks/hunk';
54

@@ -47,8 +46,3 @@ export function changesToDiffSpec(changes: TreeChange[]): DiffSpec[] {
4746
};
4847
});
4948
}
50-
/** Helper function that converts `ChangeDropData` to `DiffSpec`. */
51-
export function dropDataToDiffSpec(data: ChangeDropData): DiffSpec[] {
52-
const changes = data.changes;
53-
return changesToDiffSpec(changes);
54-
}

apps/desktop/src/lib/dragging/draggables.ts

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
import { key, readKey, type SelectionId } from '$lib/selection/key';
1+
import { key, type SelectionId } from '$lib/selection/key';
22
import { get, type Readable } from 'svelte/store';
33
import type { AnyCommit } from '$lib/commits/commit';
44
import type { CommitDropData } from '$lib/commits/dropHandler';
55
import type { AnyFile } from '$lib/files/file';
66
import type { TreeChange } from '$lib/hunks/change';
77
import type { Hunk, HunkHeader, HunkLock } from '$lib/hunks/hunk';
88
import type { IdSelection } from '$lib/selection/idSelection.svelte';
9-
import type { UncommittedService } from '$lib/selection/uncommittedService.svelte';
109

1110
export const NON_DRAGGABLE = {
1211
disabled: true
@@ -38,8 +37,8 @@ export class HunkDropDataV3 {
3837

3938
export class ChangeDropData {
4039
constructor(
40+
private projectId: string,
4141
readonly change: TreeChange,
42-
private uncommittedService: UncommittedService,
4342
/**
4443
* When a a file is dragged we compare it to what is already selected,
4544
* if dragged item is part of the selection we consider that to be to
@@ -60,19 +59,17 @@ export class ChangeDropData {
6059
}
6160
}
6261

63-
get filePaths(): string[] {
62+
/**
63+
* If there is more than one selected item, and the item being dragged is
64+
* part of that selection, then a drop handler will take an action on the
65+
* whole selection. If, however, the item being dragged is not part of a
66+
* selection then any action should be taken on that item alone.
67+
*/
68+
async treeChanges(): Promise<TreeChange[]> {
6469
if (this.selection.has(this.change.path, this.selectionId)) {
65-
const selectionKeys = this.selection.keys(this.selectionId);
66-
return selectionKeys.map((key) => readKey(key).path);
67-
} else {
68-
return [this.change.path];
70+
return await this.selection.treeChanges(this.projectId, this.selectionId);
6971
}
70-
}
71-
72-
get changes(): TreeChange[] {
73-
const paths = this.filePaths;
74-
const changes = this.uncommittedService.changesByStackId(this.stackId);
75-
return changes.current.filter((change) => paths.includes(change.path));
72+
return [this.change];
7673
}
7774

7875
get isCommitted(): boolean {

apps/desktop/src/lib/hunks/dropHandler.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,13 @@ export class AssignmentDropHandler implements DropzoneHandler {
2828

2929
async ondrop(data: ChangeDropData | HunkDropDataV3) {
3030
if (data instanceof ChangeDropData) {
31+
const changes = await data.treeChanges();
32+
const assignments = changes
33+
.flatMap((c) => this.uncommittedService.getAssignmentsByPath(data.stackId, c.path))
34+
.map((h) => ({ ...h, stackId: this.stackId }));
3135
await this.diffService.assignHunk({
3236
projectId: this.projectId,
33-
assignments: data.changes
34-
.flatMap(
35-
(c) => this.uncommittedService.getAssignmentsByPath(data.stackId, c.path).current
36-
)
37-
.map((h) => ({ ...h, stackId: this.stackId }))
37+
assignments
3838
});
3939
} else {
4040
const assignment = this.uncommittedService.getAssignmentByHeader(

apps/desktop/src/lib/selection/idSelection.svelte.ts

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ import {
77
type SelectedFile
88
} from '$lib/selection/key';
99
import { SvelteSet } from 'svelte/reactivity';
10+
import type { TreeChange } from '$lib/hunks/change';
11+
import type { UncommittedService } from '$lib/selection/uncommittedService.svelte';
1012
import type { StackService } from '$lib/stacks/stackService.svelte';
11-
import type { WorktreeService } from '$lib/worktree/worktreeService.svelte';
1213

1314
/**
1415
* File selection mechanism based on strings id's.
@@ -25,11 +26,11 @@ export class IdSelection {
2526
>;
2627

2728
constructor(
28-
private worktreeService: WorktreeService,
29-
private stackService: StackService
29+
private stackService: StackService,
30+
private uncommittedService: UncommittedService
3031
) {
3132
this.selections = new Map();
32-
this.selections.set('worktree', {
33+
this.selections.set(selectionKey({ type: 'worktree' }), {
3334
entries: new SvelteSet<SelectedFileKey>()
3435
});
3536
}
@@ -100,23 +101,27 @@ export class IdSelection {
100101
* instead reuses the entry from listing if available.
101102
* TODO: Should this be able to load even if listing hasn't happened?
102103
*/
103-
treeChanges(projectId: string, params: SelectionId) {
104-
const filePaths = this.values(params).map((fileSelection) => {
104+
async treeChanges(projectId: string, params: SelectionId): Promise<TreeChange[]> {
105+
const paths = this.values(params).map((fileSelection) => {
105106
return fileSelection.path;
106107
});
107108

108109
switch (params.type) {
109110
case 'worktree':
110-
return this.worktreeService.treeChangesByPaths(projectId, filePaths);
111+
return this.uncommittedService
112+
.changesByStackId(params.stackId || null)
113+
.current.filter((c) => paths.includes(c.path));
111114
case 'branch':
112-
return this.stackService.branchChangesByPaths({
115+
return await this.stackService.branchChangesByPaths({
113116
projectId,
114117
stackId: params.stackId,
115118
branchName: params.branchName,
116-
paths: filePaths
119+
paths: paths
117120
});
118121
case 'commit':
119-
return this.stackService.commitChangesByPaths(projectId, params.commitId, filePaths);
122+
return await this.stackService.commitChangesByPaths(projectId, params.commitId, paths);
123+
case 'snapshot':
124+
throw new Error('unsupported');
120125
}
121126
}
122127

apps/desktop/src/lib/selection/key.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ export function selectionKey(id: SelectionId): SelectedFileKey {
101101
case 'branch':
102102
return `${id.type}:${id.stackId}:${id.branchName}` as SelectedFileKey;
103103
case 'worktree':
104-
return `${id.type}` as SelectedFileKey;
104+
return `${id.type}:${id.stackId}` as SelectedFileKey;
105105
case 'snapshot':
106106
return `${id.type}:${id.before}:${id.after}` as SelectedFileKey;
107107
}

apps/desktop/src/lib/selection/uncommitted.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,10 @@ export const uncommittedSlice = createSlice({
4848
);
4949
const removedKeys = removedAssignments.map((r) => compositeKey(r));
5050
if (removedKeys.length > 0) {
51-
state.hunkAssignments = hunkAssignmentAdapter.removeMany(
52-
state.hunkAssignments,
53-
removedKeys
54-
);
5551
// The next line requires that assignments and selections share keys.
5652
state.hunkSelection = hunkSelectionAdapter.removeMany(state.hunkSelection, removedKeys);
5753
}
58-
state.hunkAssignments = hunkAssignmentAdapter.upsertMany(
54+
state.hunkAssignments = hunkAssignmentAdapter.addMany(
5955
hunkAssignmentAdapter.getInitialState(),
6056
action.payload.assignments
6157
);

apps/desktop/src/lib/selection/uncommittedService.svelte.ts

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -166,16 +166,6 @@ export class UncommittedService {
166166
return reactive(() => changes);
167167
}
168168

169-
assignmentsByPath(stackId: string | null, path: string): Reactive<HunkAssignment[]> {
170-
const result = $derived(
171-
uncommittedSelectors.hunkAssignments.selectByPrefix(
172-
this.state.hunkAssignments,
173-
stackId + '-' + path + '-'
174-
)
175-
);
176-
return reactive(() => result);
177-
}
178-
179169
assignmentsByStackId(stackId: string | null): Reactive<HunkAssignment[]> {
180170
const result = $derived(
181171
uncommittedSelectors.hunkAssignments.selectByPrefix(this.state.hunkAssignments, stackId + '-')
@@ -193,13 +183,15 @@ export class UncommittedService {
193183
return reactive(() => assignments);
194184
}
195185

196-
getAssignmentsByPath(stackId: string | null, path: string): Reactive<HunkAssignment[]> {
197-
const assignments = $derived(
198-
uncommittedSelectors.hunkAssignments.selectByPrefix(
199-
this.state.hunkAssignments,
200-
partialKey(stackId, path)
201-
)
186+
getAssignmentsByPath(stackId: string | null, path: string): HunkAssignment[] {
187+
return uncommittedSelectors.hunkAssignments.selectByPrefix(
188+
this.state.hunkAssignments,
189+
partialKey(stackId, path)
202190
);
191+
}
192+
193+
assignmentsByPath(stackId: string | null, path: string): Reactive<HunkAssignment[]> {
194+
const assignments = $derived(this.getAssignmentsByPath(stackId, path));
203195
return reactive(() => assignments);
204196
}
205197

0 commit comments

Comments
 (0)