Skip to content

Commit ffa2577

Browse files
Merge pull request #9236 from gitbutlerapp/fix-workspace-reordering
Rewrote stack reordering to use simpler (and not broken) logic
2 parents de0b55b + 65f8fff commit ffa2577

File tree

3 files changed

+43
-46
lines changed

3 files changed

+43
-46
lines changed

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

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,12 @@
44
import MultiStackPagination, { scrollToLane } from '$components/v3/MultiStackPagination.svelte';
55
import StackDraft from '$components/v3/StackDraft.svelte';
66
import StackView from '$components/v3/StackView.svelte';
7-
import { onReorderEnd, onReorderMouseDown, onReorderStart } from '$lib/dragging/reordering';
8-
import { onReorderDragOver } from '$lib/dragging/reordering';
7+
import {
8+
onReorderEnd,
9+
onReorderMouseDown,
10+
onReorderStart,
11+
onDragOver
12+
} from '$lib/dragging/reordering';
913
import { IntelligentScrollingService } from '$lib/intelligentScrolling/service';
1014
import { branchesPath } from '$lib/routes/routes.svelte';
1115
import { type SelectionId } from '$lib/selection/key';
@@ -117,7 +121,7 @@
117121
});
118122
119123
// Throttle calls to the reordering code in order to save some cpu cycles.
120-
const throttledDragOver = throttle(onReorderDragOver, 250);
124+
const throttledDragOver = throttle(onDragOver, 25);
121125
122126
// To support visual reordering of stacks we need a copy of the array
123127
// that can be mutated as the stack is being dragged around.
@@ -130,8 +134,6 @@
130134
mutableStacks = stacks;
131135
}
132136
});
133-
134-
let viewWrapperEl: HTMLDivElement | undefined;
135137
</script>
136138

137139
{#if isNotEnoughHorzSpace}
@@ -154,18 +156,12 @@
154156
<div
155157
role="presentation"
156158
class="lanes-scrollable hide-native-scrollbar"
157-
bind:this={viewWrapperEl}
158159
class:panning={isPanning}
159160
bind:this={lanesScrollableEl}
160161
bind:clientWidth={lanesScrollableWidth}
161162
bind:clientHeight={lanesScrollableHeight}
162163
class:multi={stacks.length < SHOW_PAGINATION_THRESHOLD}
163164
onmousedown={handleMouseDown}
164-
ondragover={(e) => {
165-
// This call will actually mutate the array of stacks, showing
166-
// where the lane will be placed when dropped.
167-
throttledDragOver(e, mutableStacks);
168-
}}
169165
ondrop={() => {
170166
stackService.updateStackOrder({
171167
projectId,
@@ -189,12 +185,13 @@
189185
class="reorderable-stack"
190186
role="presentation"
191187
animate:flip={{ duration: 150 }}
192-
onmousedown={(e) => onReorderMouseDown(e, viewWrapperEl as HTMLDivElement)}
188+
onmousedown={onReorderMouseDown}
193189
ondragstart={(e) =>
194190
onReorderStart(e, stack.id, () => {
195191
selection.set(undefined);
196192
intelligentScrollingService.show(projectId, stack.id, 'stack');
197193
})}
194+
ondragover={(e) => throttledDragOver(e, mutableStacks, stack.id)}
198195
ondragend={onReorderEnd}
199196
>
200197
<StackView

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,8 @@
184184
}}
185185
<div
186186
bind:this={viewWrapperEl}
187+
bind:clientWidth
188+
bind:clientHeight
187189
class="stack-view-wrapper"
188190
role="presentation"
189191
class:dimmed
@@ -210,8 +212,6 @@
210212
<div
211213
class="stack-view"
212214
style:width={$persistedStackWidth + 'rem'}
213-
bind:clientWidth
214-
bind:clientHeight
215215
bind:this={stackViewEl}
216216
{@attach scrollingAttachment(intelligentScrollingService, projectId, stack.id, 'stack')}
217217
>

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

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,9 @@ let dragHandle: any;
2828
let dragged: HTMLDivElement | undefined;
2929
let clone: any;
3030
let draggedId: string | undefined;
31-
let lanesScrollableEl: HTMLElement | undefined;
3231

33-
export function onReorderMouseDown(e: MouseEvent, element: HTMLDivElement | undefined) {
32+
export function onReorderMouseDown(e: MouseEvent) {
3433
dragHandle = e.target;
35-
lanesScrollableEl = element;
3634
}
3735

3836
export function onReorderStart(
@@ -81,43 +79,45 @@ export function onReorderEnd() {
8179
clone?.remove();
8280
}
8381

84-
export function onReorderDragOver(
82+
export function onDragOver(
8583
e: MouseEvent & { currentTarget: HTMLDivElement },
86-
sortedStacks: Stack[]
84+
sortedStacks: Stack[],
85+
thisStackId: string
8786
) {
88-
e.preventDefault();
89-
if (!dragged) {
90-
return; // Something other than a lane is being dragged.
87+
// Return early if we are currently dragging over ourself.
88+
if (draggedId === thisStackId) {
89+
return;
90+
}
91+
92+
const thisIdx = sortedStacks.findIndex((stack) => stack.id === thisStackId);
93+
const draggedIdx = sortedStacks.findIndex((stack) => stack.id === draggedId);
94+
if (draggedIdx === -1 || thisIdx === -1) {
95+
return;
9196
}
9297

93-
const children = Array.from(e.currentTarget.children);
94-
const currentPosition = sortedStacks.findIndex((stack) => stack.id === draggedId);
98+
// If we are dragging over an adjacent stack, only swap if the mouse is half
99+
// way over the adjacent stack.
100+
if (Math.abs(thisIdx - draggedIdx) === 1) {
101+
// The mouse position relative to the LHS of the current stack.
102+
const mouseLeft = e.clientX - (e.currentTarget?.getBoundingClientRect().left ?? 0);
95103

96-
let dropPosition = 0;
97-
const mouseLeft = e.clientX - (lanesScrollableEl?.getBoundingClientRect().left ?? 0);
98-
let cumulativeWidth = lanesScrollableEl?.offsetLeft ?? 0;
104+
const isRightOfTarget = thisIdx > draggedIdx;
99105

100-
for (let i = 0; i < children.length; i++) {
101-
const childWidth = (children[i] as HTMLElement).offsetWidth;
102-
// The commented out code below is necessary if the drag handle is
103-
// aligned with the left side of the stack. Leaving it here until
104-
// we are more certain about the layout.
105-
// if (i === currentPosition) {
106-
// continue;
107-
// }
108-
if (mouseLeft > cumulativeWidth + childWidth / 2) {
109-
// New position depends on drag direction.
110-
dropPosition = i < currentPosition ? i + 1 : i;
111-
cumulativeWidth += childWidth;
106+
const midpoint = (e.currentTarget?.clientWidth ?? 0) / 2;
107+
108+
let pastOfMidpoint = false;
109+
if (isRightOfTarget) {
110+
pastOfMidpoint = mouseLeft > midpoint;
112111
} else {
113-
break;
112+
pastOfMidpoint = mouseLeft < midpoint;
114113
}
115-
}
116114

117-
// Update sorted branch array manually.
118-
if (currentPosition !== dropPosition) {
119-
const el = sortedStacks.splice(currentPosition, 1);
120-
sortedStacks.splice(dropPosition, 0, ...el);
115+
if (pastOfMidpoint) {
116+
const draggedStack = sortedStacks.splice(draggedIdx, 1);
117+
sortedStacks.splice(thisIdx, 0, ...draggedStack);
118+
}
119+
} else {
120+
const draggedStack = sortedStacks.splice(draggedIdx, 1);
121+
sortedStacks.splice(thisIdx, 0, ...draggedStack);
121122
}
122-
return sortedStacks;
123123
}

0 commit comments

Comments
 (0)