Skip to content

Commit

Permalink
drag n drop, w/ ghost line
Browse files Browse the repository at this point in the history
  • Loading branch information
lovincyrus committed Dec 25, 2024
1 parent 6285ecf commit a6267c6
Show file tree
Hide file tree
Showing 5 changed files with 208 additions and 31 deletions.
29 changes: 28 additions & 1 deletion web-common/src/features/canvas/Canvas.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,40 @@
if ($autoSave) await updateComponentFile();
}
async function handleUpdate(event: CustomEvent) {
const { index, position, dimensions, items } = event.detail;
console.log("[Canvas] Handling update:", {
index,
position,
dimensions,
items,
});
// Update the YAML document
const parsedDocument = parseDocument(
$editorContent ?? $remoteContent ?? "",
);
const docItems = parsedDocument.get("items") as any;
if (!docItems) return;
const node = docItems.get(index);
if (!node) return;
node.set("x", position[0]);
node.set("y", position[1]);
updateEditorContent(parsedDocument.toString(), true);
if ($autoSave) await updateComponentFile();
}
</script>

<CanvasDashboardPreview
{items}
{fileArtifact}
bind:selectedIndex
on:update={handlePreviewUpdate}
on:update={handleUpdate}
on:delete={handleDeleteEvent}
/>

Expand Down
174 changes: 144 additions & 30 deletions web-common/src/features/canvas/CanvasDashboardPreview.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,18 @@
import { getComponentRegistry } from "@rilldata/web-common/features/canvas/components/util";
import type { FileArtifact } from "../entity-management/file-artifact";
import type { CanvasComponentType } from "@rilldata/web-common/features/canvas/components/types";
import GhostLine from "./GhostLine.svelte";
const dispatch = createEventDispatcher();
const zeroVector = [0, 0] as [0, 0];
export let items: V1CanvasItem[];
export let snap = true;
export let selectedIndex: number | null = null;
export let fileArtifact: FileArtifact;
const { canvasName } = getCanvasStateManagers();
let snap = true;
let contentRect: DOMRectReadOnly = new DOMRectReadOnly(0, 0, 0, 0);
let scrollOffset = 0;
let changing = false;
Expand All @@ -37,6 +38,10 @@
width: number;
height: number;
} | null = null;
let dropTarget: {
index: number;
position: "left" | "right";
} | null = null;
// FIXME: there's no way to derive
// Suggest that we add `type: CanvasComponentType` to V1CanvasItem
Expand Down Expand Up @@ -68,38 +73,36 @@
initialElementDimensions,
);
$: finalDrag = vector.multiply(getCell(dragPosition, snap), gridVector);
$: console.log("[CanvasDashboardPreview] items updated:", items);
$: finalResize = vector.multiply(getCell(resizeDimenions, snap), gridVector);
// function handleMouseUp() {
// console.log("[CanvasDashboardPreview] handleMouseUp ", selectedIndex);
function handleMouseUp() {
console.log("[CanvasDashboardPreview] handleMouseUp ", selectedIndex);
// if (selectedIndex === null || !changing) return;
if (selectedIndex === null || !changing) return;
// const cellPosition = getCell(dragPosition, true);
// const dimensions = getCell(resizeDimenions, true);
const cellPosition = getCell(dragPosition, true);
const dimensions = getCell(resizeDimenions, true);
items[selectedIndex].x = Math.max(
0,
dimensions[0] < 0 ? cellPosition[0] + dimensions[0] : cellPosition[0],
);
items[selectedIndex].y = Math.max(
0,
dimensions[1] < 0 ? cellPosition[1] + dimensions[1] : cellPosition[1],
);
// items[selectedIndex].x = Math.max(
// 0,
// dimensions[0] < 0 ? cellPosition[0] + dimensions[0] : cellPosition[0],
// );
// items[selectedIndex].y = Math.max(
// 0,
// dimensions[1] < 0 ? cellPosition[1] + dimensions[1] : cellPosition[1],
// );
items[selectedIndex].width = Math.max(1, Math.abs(dimensions[0]));
items[selectedIndex].height = Math.max(1, Math.abs(dimensions[1]));
// items[selectedIndex].width = Math.max(1, Math.abs(dimensions[0]));
// items[selectedIndex].height = Math.max(1, Math.abs(dimensions[1]));
dispatch("update", {
index: selectedIndex,
position: [items[selectedIndex].x, items[selectedIndex].y],
dimensions: [items[selectedIndex].width, items[selectedIndex].height],
});
// dispatch("update", {
// index: selectedIndex,
// position: [items[selectedIndex].x, items[selectedIndex].y],
// dimensions: [items[selectedIndex].width, items[selectedIndex].height],
// });
reset();
}
// reset();
// }
function reset() {
changing = false;
Expand All @@ -116,6 +119,11 @@
function handleDragStart(e: CustomEvent) {
const { componentIndex, width, height } = e.detail;
console.log("[CanvasDashboardPreview] DragStart: ", {
componentIndex,
width,
height,
});
draggedComponent = {
index: componentIndex,
width,
Expand All @@ -127,9 +135,9 @@
draggedComponent = null;
}
function handleMouseMove(e: MouseEvent) {
// No-op - we'll use drag events instead
}
// function handleMouseMove(e: MouseEvent) {
// // No-op - we'll use drag events instead
// }
function handleScroll(
e: UIEvent & {
Expand All @@ -156,9 +164,95 @@
const bottom = Number(el.height) + Number(el.y);
return Math.max(max, bottom);
}, 0);
function getDropPosition(
e: DragEvent,
targetIndex: number,
): "left" | "right" {
const targetElement = document.querySelector(
`[data-index="${targetIndex}"]`,
);
if (!targetElement) return "left";
const rect = targetElement.getBoundingClientRect();
const mouseX = e.clientX;
return mouseX > rect.left + rect.width / 2 ? "right" : "left";
}
function handleDragOver(e: DragEvent, targetIndex: number) {
e.preventDefault();
e.stopPropagation();
// Don't show ghost line if dragging over self
if (draggedComponent?.index === targetIndex) {
dropTarget = null;
return;
}
const position = getDropPosition(e, targetIndex);
console.log("[DragOver]", {
targetIndex,
position,
mouseX: e.clientX,
});
dropTarget = { index: targetIndex, position };
}
function handleDrop(e: DragEvent) {
e.preventDefault();
e.stopPropagation();
if (!draggedComponent || !dropTarget) return;
const { index: dragIndex } = draggedComponent;
const { index: dropIndex, position } = dropTarget;
const targetItem = items[dropIndex];
if (targetItem.x === undefined || targetItem.width === undefined) return;
// Create new array and remove dragged item
const newItems = [...items];
const [draggedItem] = newItems.splice(dragIndex, 1);
// Calculate insert position based on drop position
const insertIndex = position === "right" ? dropIndex : dropIndex;
// Insert dragged item at new position
newItems.splice(insertIndex, 0, draggedItem);
// Recalculate x positions for all items
newItems.forEach((item, index) => {
if (index === 0) {
item.x = 0;
} else {
const prevItem = newItems[index - 1];
if (prevItem.x === undefined || prevItem.width === undefined) return;
// Round the x position to ensure integer values
item.x = Math.round(
prevItem.x + prevItem.width + defaults.GAP_SIZE / 1000,
);
}
// Keep same y position
item.y = targetItem.y;
});
items = newItems;
dispatch("update", {
index: insertIndex,
position: [newItems[insertIndex].x, newItems[insertIndex].y],
dimensions: [draggedComponent.width, draggedComponent.height],
items: newItems,
});
dropTarget = null;
draggedComponent = null;
}
</script>

<svelte:window on:mousemove={handleMouseMove} on:mouseup={handleMouseUp} />
<!-- <svelte:window on:mousemove={handleMouseMove} on:mouseup={handleMouseUp} /> -->

<DashboardWrapper
bind:contentRect
Expand All @@ -172,6 +266,10 @@
width={defaults.DASHBOARD_WIDTH}
on:click={deselect}
on:scroll={handleScroll}
on:dragover={(e) => {
e.preventDefault();
}}
on:drop={handleDrop}
>
{#each items as component, i (i)}
<PreviewElement
Expand All @@ -187,8 +285,24 @@
height={Number(component.height ?? defaults.COMPONENT_HEIGHT) * gridCell}
top={Number(component.y) * gridCell}
left={Number(component.x) * gridCell}
onDragOver={(e) => handleDragOver(e, i)}
onDrop={(e) => handleDrop(e)}
on:dragstart={handleDragStart}
on:dragend={handleDragEnd}
/>
{/each}

{#if dropTarget && draggedComponent}
{@const targetItem = items[dropTarget.index]}
{#if targetItem && targetItem.x !== undefined && targetItem.y !== undefined && targetItem.width !== undefined && targetItem.height !== undefined}
<GhostLine
height={targetItem.height * gridCell}
top={targetItem.y * gridCell}
left={dropTarget.position === "right"
? (targetItem.x + targetItem.width) * gridCell
: targetItem.x * gridCell}
orientation="vertical"
/>
{/if}
{/if}
</DashboardWrapper>
2 changes: 2 additions & 0 deletions web-common/src/features/canvas/Component.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@
style:height={chartView ? undefined : `${height}px`}
on:dragstart
on:dragend
on:dragover
on:drop
on:contextmenu
>
<div class="size-full relative {draggable ? 'touch-none' : ''}">
Expand Down
28 changes: 28 additions & 0 deletions web-common/src/features/canvas/GhostLine.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<script lang="ts">
export let height: number;
export let top: number;
export let left: number;
export let orientation: "vertical" | "horizontal" = "vertical";
</script>

<div
class="ghost-line"
style:height="{orientation === 'vertical' ? height : '2'}px"
style:width="{orientation === 'horizontal' ? height : '2'}px"
style:top="{top}px"
style:left="{left}px"
data-orientation={orientation}
/>

<style>
.ghost-line {
position: absolute;
background-color: #4299e1;
pointer-events: none;
transition: all 0.1s ease;
z-index: 1000;
box-shadow: 0 0 12px rgba(66, 153, 225, 0.8);
border-radius: 3px;
opacity: 0.8;
}
</style>
6 changes: 6 additions & 0 deletions web-common/src/features/canvas/PreviewElement.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
export let radius: number;
export let scale: number;
export let instanceId: string;
export let onDragOver: (e: CustomEvent<DragEvent> | DragEvent) => void;
export let onDrop: (e: CustomEvent<DragEvent> | DragEvent) => void;
let localZIndex = 0;
Expand Down Expand Up @@ -92,6 +94,8 @@
draggable={true}
on:dragstart={handleDragStart}
on:dragend={handleDragEnd}
on:dragover={onDragOver}
on:drop={onDrop}
on:contextmenu
/>
<!-- </ContextMenu.Trigger>
Expand Down Expand Up @@ -130,6 +134,8 @@
draggable={true}
on:dragstart={handleDragStart}
on:dragend={handleDragEnd}
on:dragover={onDragOver}
on:drop={onDrop}
on:contextmenu
/>
<!-- </ContextMenu.Trigger>
Expand Down

0 comments on commit a6267c6

Please sign in to comment.