Skip to content

Commit f4d6772

Browse files
authored
Merge pull request #920 from ToposInstitute/kb/improve-drag-n-drop
Improve notebook cell drag and drop
2 parents 419a549 + f8f88f4 commit f4d6772

File tree

3 files changed

+56
-16
lines changed

3 files changed

+56
-16
lines changed

packages/frontend/src/notebook/notebook_cell.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
justify-content: space-between;
88
}
99

10+
.cell-dragging {
11+
opacity: 0.4;
12+
}
13+
1014
.cell-gutter {
1115
position: absolute;
1216
top: 50%;

packages/frontend/src/notebook/notebook_cell.tsx

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
draggable,
44
dropTargetForElements,
55
} from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
6+
import { preventUnhandled } from "@atlaskit/pragmatic-drag-and-drop/prevent-unhandled";
67
import { attachClosestEdge } from "@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge";
78
import type { DocHandle, Prop } from "@automerge/automerge-repo";
89
import Popover from "@corvu/popover";
@@ -150,6 +151,7 @@ export function NotebookCell(props: {
150151

151152
const [closestEdge, setClosestEdge] = createSignal<ClosestEdge>(null);
152153
const [dropTarget, setDropTarget] = createSignal(false);
154+
const [isDragging, setIsDragging] = createSignal(false);
153155

154156
const isActiveDropTarget = () => props.currentDropTarget === props.cellId;
155157
createEffect(() => {
@@ -164,14 +166,37 @@ export function NotebookCell(props: {
164166
draggable({
165167
element: handleRef,
166168
getInitialData: () => createCellDragData(props.cellId, props.index),
169+
onGenerateDragPreview({ nativeSetDragImage }) {
170+
if (nativeSetDragImage) {
171+
// Clone the cell content for the drag preview
172+
const cellContent = rootRef.querySelector(".cell-content");
173+
if (cellContent) {
174+
const preview = cellContent.cloneNode(true) as HTMLElement;
175+
preview.style.width = `${cellContent.clientWidth}px`;
176+
preview.style.opacity = "0.8";
177+
preview.style.pointerEvents = "none";
178+
document.body.appendChild(preview);
179+
nativeSetDragImage(preview, 0, 0);
180+
181+
setTimeout(() => {
182+
preview.remove();
183+
}, 0);
184+
}
185+
}
186+
},
187+
onDragStart() {
188+
setIsDragging(true);
189+
preventUnhandled.start();
190+
},
191+
onDrop() {
192+
setIsDragging(false);
193+
preventUnhandled.stop();
194+
},
167195
}),
168196
dropTargetForElements({
169197
element: rootRef,
170198
canDrop({ source }) {
171199
// TODO: Reject if cell belongs to a different notebook.
172-
if (source.data.cellId === props.cellId) {
173-
return false;
174-
}
175200
return isCellDragData(source.data);
176201
},
177202
getData({ input }) {
@@ -185,15 +210,11 @@ export function NotebookCell(props: {
185210
onDragEnter(args) {
186211
const sourceIndex = args.source.data.index as number;
187212
const targetIndex = args.self.data.index as number;
188-
if (sourceIndex === targetIndex) {
189-
setClosestEdge(null);
190-
setDropTarget(false);
191-
} else {
192-
props.setCurrentDropTarget(props.cellId);
193-
const edge = sourceIndex < targetIndex ? "bottom" : "top";
194-
setClosestEdge(edge);
195-
setDropTarget(true);
196-
}
213+
214+
props.setCurrentDropTarget(props.cellId);
215+
const edge = sourceIndex < targetIndex ? "bottom" : "top";
216+
setClosestEdge(edge);
217+
setDropTarget(true);
197218
},
198219
onDrop() {
199220
setDropTarget(false);
@@ -205,7 +226,13 @@ export function NotebookCell(props: {
205226
});
206227

207228
return (
208-
<div class="cell" onMouseEnter={showGutter} onMouseLeave={hideGutter} ref={rootRef}>
229+
<div
230+
class="cell"
231+
classList={{ "cell-dragging": isDragging() }}
232+
onMouseEnter={showGutter}
233+
onMouseLeave={hideGutter}
234+
ref={rootRef}
235+
>
209236
<div class="cell-gutter">
210237
<IconButton
211238
onClick={props.actions.createBelow}

packages/frontend/src/notebook/notebook_editor.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
import type { Cell, Notebook } from "catlog-wasm";
2626
import {
2727
type CellActions,
28+
type CellDragData,
2829
type FormalCellEditorProps,
2930
isCellDragData,
3031
NotebookCell,
@@ -190,16 +191,24 @@ export function NotebookEditor<T>(props: {
190191
);
191192
},
192193
onDrop({ location, source }) {
193-
const target = location.current.dropTargets[0];
194-
if (!(target && isCellDragData(source.data) && isCellDragData(target.data))) {
194+
const target =
195+
location.current.dropTargets[0] ??
196+
(currentDropTarget() ? { data: { cellId: currentDropTarget() } } : null);
197+
if (!(target && isCellDragData(source.data))) {
195198
setCurrentDropTarget(null);
196199
return;
197200
}
198-
const [sourceId, targetId] = [source.data.cellId, target.data.cellId];
201+
const targetData = target.data as CellDragData;
202+
if (!targetData.cellId) {
203+
setCurrentDropTarget(null);
204+
return;
205+
}
206+
const [sourceId, targetId] = [source.data.cellId, targetData.cellId];
199207
const nb = props.notebook;
200208
const sourceIndex = nb.cellOrder.indexOf(sourceId);
201209
const targetIndex = nb.cellOrder.indexOf(targetId);
202210
if (sourceIndex < 0 || targetIndex < 0) {
211+
setCurrentDropTarget(null);
203212
return;
204213
}
205214
const finalIndex = getReorderDestinationIndex({

0 commit comments

Comments
 (0)