From b0e00beaac924809339828eecef1b64d1b8d993a Mon Sep 17 00:00:00 2001 From: Vasek Sraier Date: Sat, 3 Oct 2020 23:41:57 +0200 Subject: [PATCH] editor: group selection and group dragging --- frontend/src/Editor.svelte | 3 +- frontend/src/Graph.svelte | 148 ++++++++++++++++++++++++++++++++-- frontend/src/GraphNode.svelte | 43 +++------- 3 files changed, 153 insertions(+), 41 deletions(-) diff --git a/frontend/src/Editor.svelte b/frontend/src/Editor.svelte index a02e762..20a9aef 100644 --- a/frontend/src/Editor.svelte +++ b/frontend/src/Editor.svelte @@ -246,6 +246,7 @@
Last clicked: {clicked.join(' | ')}
Double click na node - otevře detail. Po kliknutí na label se zobrazí možnost rotace. + otevře detail. Po kliknutí na label se zobrazí možnost rotace. Držením pravého tlačítka je možné udělat skupinový výběr.
diff --git a/frontend/src/Graph.svelte b/frontend/src/Graph.svelte index 85452c9..0c41e80 100644 --- a/frontend/src/Graph.svelte +++ b/frontend/src/Graph.svelte @@ -9,7 +9,9 @@ export let tasks: TasksFile; export let nodeDraggingEnabled: boolean = false; + export let selectionToolEnabled: boolean = false; export let showHiddenEdges: boolean = false; + export let selection: Set = new Set(); let hoveredTask: null | string = null; @@ -18,6 +20,8 @@ let clientHeight: number; let clientWidth: number; let svgElement: SVGElement; + let innerSvgGroup: SVGElement; + let selectionRectangle: [[number, number], [number, number]] | null = null; $: nodes = tasks.tasks; $: edges = createEdges(nodes); @@ -25,6 +29,11 @@ const eventDispatcher = createEventDispatcher(); function nodeClick(task: TaskDescriptor) { function eventHandler(e: CustomEvent) { + if (selectionToolEnabled) { + selection.clear(); + selection.add(task); + selection = selection; + } eventDispatcher("selectTask", task); } return eventHandler; @@ -61,6 +70,110 @@ d3.select(container).call(zoomer); } + function groupSelectionHandler(e: MouseEvent) { + // not enabled? + if (!selectionToolEnabled) return; + + // not a right button? + if (e.button != 2) return; + + // setup drag start + const startPos = d3.pointer(e, innerSvgGroup); + + // prevent default + e.preventDefault(); + e.stopPropagation(); + + function updateSelection() { + selection.clear(); + tasks.tasks.forEach((t) => { + if ( + selectionRectangle![0][0] < (t.position ?? [0, 0])[0] && + (t.position ?? [0, 0])[0] < selectionRectangle![1][0] && + selectionRectangle![0][1] < (t.position ?? [0, 0])[1] && + (t.position ?? [0, 0])[1] < selectionRectangle![1][1] + ) + selection.add(t); + }); + selection = selection; + } + + // setup mouse move + function mouseMove(e: MouseEvent) { + const np = d3.pointer(e, innerSvgGroup); + selectionRectangle = [ + [Math.min(np[0], startPos[0]), Math.min(np[1], startPos[1])], + [Math.max(np[0], startPos[0]), Math.max(np[1], startPos[1])], + ]; + updateSelection(); + } + window.addEventListener("mousemove", mouseMove); + window.addEventListener("mouseup", mouseUp, { once: true }); + + // setup mouse down + function mouseUp(e: MouseEvent) { + // not a right button? + if (e.button != 2) return; + + e.preventDefault(); + e.stopPropagation(); + + // save selection + const np = d3.pointer(e, innerSvgGroup); + selectionRectangle = [ + [Math.min(np[0], startPos[0]), Math.min(np[1], startPos[1])], + [Math.max(np[0], startPos[0]), Math.max(np[1], startPos[1])], + ]; + updateSelection(); + selectionRectangle = null; + + // cleanup listeners + window.removeEventListener("mousemove", mouseMove); + } + } + + // dragging + function dragStart(e: MouseEvent) { + if (!nodeDraggingEnabled) return; + + // is the left button pressed? + if (e.button != 0) return; + + e.preventDefault(); + e.stopPropagation(); + + let startPos = d3.pointer(e, innerSvgGroup); + + function drag(e: MouseEvent) { + if (!nodeDraggingEnabled) return; + + const currPos = d3.pointer(e, innerSvgGroup); + const [dx, dy] = [currPos[0] - startPos[0], currPos[1] - startPos[1]]; + for (const [t, _] of selection.entries()) { + t.position = [ + (t.position ?? [0, 0])[0] + dx, + (t.position ?? [0, 0])[1] + dy, + ]; + } + tasks = tasks; + startPos = currPos; + + e.preventDefault(); + e.stopPropagation(); + } + + function dragStop(e: MouseEvent) { + if (!nodeDraggingEnabled) return; + + e.preventDefault(); + e.stopPropagation(); + window.removeEventListener("mousemove", drag); + } + + window.addEventListener("mousemove", drag); + window.addEventListener("mouseup", dragStop, { once: true }); + } + onMount(() => { setupZoom(); }); @@ -85,26 +198,49 @@ :global(#page) { flex-grow: 1; } + + rect { + fill: transparent; + stroke-dasharray: 5, 5; + stroke: gainsboro; + } -
+
{ + if (selectionToolEnabled) { + e.preventDefault(); + return false; + } + }}> - + + {#if selectionRectangle != null} + + {/if} {#each edges as edge} {/each} {#each nodes as task} { - tasks = tasks; - }} status={$taskStatuses.get(task.id)} - draggingEnabled={nodeDraggingEnabled} on:dblclick={nodeDoubleClick(task)} /> {/each} diff --git a/frontend/src/GraphNode.svelte b/frontend/src/GraphNode.svelte index c2b4832..411a5d4 100644 --- a/frontend/src/GraphNode.svelte +++ b/frontend/src/GraphNode.svelte @@ -6,7 +6,7 @@ import type { TaskDescriptor } from "./tasks"; export let task: TaskDescriptor; - export let draggingEnabled: boolean = false; + export let selected: bool = false; export let status: TaskStatus | undefined = undefined; let hovering: boolean = false; @@ -47,36 +47,6 @@ ensureTextFits(); } - // dragging - let dragging: boolean = false; - function dragStart(e: MouseEvent) { - if (!draggingEnabled) return; - - dragging = true; - e.preventDefault(); - e.stopPropagation(); - - window.addEventListener("mousemove", drag) - window.addEventListener("mouseup", dragStop, { once: true }) - } - function drag(e: MouseEvent) { - if (!draggingEnabled) return; - if (!dragging) return; - - task.position = d3.pointer(e, mainGroup); - eventDispatcher("positionChange"); - e.preventDefault(); - e.stopPropagation(); - } - function dragStop(e: MouseEvent) { - if (!draggingEnabled) return; - - dragging = false; - e.preventDefault(); - e.stopPropagation(); - window.removeEventListener("mousemove", drag) - } - function dblclick(e: MouseEvent) { eventDispatcher("dblclick", e); e.stopPropagation(); @@ -112,18 +82,23 @@ .solved .taskNode { fill: green; /* TODO */ } + + .selected > ellipse { + stroke-width: 4px; + stroke: red; + } + class="{status == null ? '' : status.solved ? 'solved' : status.submitted ? 'submitted' : ''} {task.type} {selected ? 'selected' : 'notSelected'}"> {#if task.type == 'label'} - {#if draggingEnabled } + {#if selected } {/if}