From 9674b7aaa0e7c9e33a1cba92f325558a61da1e19 Mon Sep 17 00:00:00 2001 From: Dean Date: Thu, 31 Oct 2024 16:38:26 +0000 Subject: [PATCH] Merge commit --- .../AutomationBuilder/DraggableCanvas.svelte | 328 +++++++++++++----- .../FlowChart/FlowItem.svelte | 18 +- 2 files changed, 266 insertions(+), 80 deletions(-) diff --git a/packages/builder/src/components/automation/AutomationBuilder/DraggableCanvas.svelte b/packages/builder/src/components/automation/AutomationBuilder/DraggableCanvas.svelte index e5a51203ba..73eb514281 100644 --- a/packages/builder/src/components/automation/AutomationBuilder/DraggableCanvas.svelte +++ b/packages/builder/src/components/automation/AutomationBuilder/DraggableCanvas.svelte @@ -9,9 +9,53 @@ } from "svelte" import { Utils } from "@budibase/frontend-core" import { selectedAutomation, automationStore } from "stores/builder" + import { memo } from "@budibase/frontend-core" + + const getContentTransformOrigin = () => { + const midPoint = { + midX: viewPort.getBoundingClientRect().width / 2, + midY: viewPort.getBoundingClientRect().height / 2, + } + return [ + Math.abs(midPoint.midX - $contentPos.x), + Math.abs(midPoint.midY - $contentPos.y), + ] + } + + // TODO - Remove Debugging + window.markCenterPoint = async () => { + zoomOrigin = [...getContentTransformOrigin()] + + const scale = 1 //0.9 + + const newWidth = contentDims.w * scale + const deltaWidth = contentDims.w - newWidth + + const newHeight = contentDims.h * scale + const deltaHeight = contentDims.h - newHeight + + console.log({ + w: { + widf: contentDims.w, + newWidth, + deltaWidth: parseInt(deltaWidth), + }, + h: { + hif: contentDims.h, + newHeight, + deltaHeight: parseInt(deltaHeight), + }, + }) + } + + export function toFocus() { + viewToFocusEle() + } export function zoomIn() { - const scale = Number(Math.min($view.scale + 0.1, 1.5).toFixed(2)) + const scale = parseFloat(Math.min($view.scale + 0.1, 1.5).toFixed(2)) + // zoomOrigin = [...getContentTransformOrigin()] + view.update(state => ({ ...state, scale, @@ -19,7 +63,9 @@ } export function zoomOut() { - const scale = Number(Math.max($view.scale - 0.1, 0).toFixed(2)) + const scale = parseFloat(Math.max($view.scale - 0.1, 0.1).toFixed(2)) + // zoomOrigin = [...getContentTransformOrigin()] + view.update(state => ({ ...state, scale, @@ -48,18 +94,23 @@ const { width: wViewPort, height: hViewPort } = viewPort.getBoundingClientRect() - const scaleTarget = Math.min( - wViewPort / contentDims.original.w, - hViewPort / contentDims.original.h + const scaleTarget = parseFloat( + Math.min( + wViewPort / contentDims.original.w, + hViewPort / contentDims.original.h + ).toFixed(2) ) - // Smallest ratio determines which dimension needs squeezed - view.update(state => ({ - ...state, - scale: scaleTarget, - })) + // Skip behaviour if the scale target scale is the current scale + if ($view.scale !== scaleTarget) { + // Smallest ratio determines which dimension needs squeezed + view.update(state => ({ + ...state, + scale: parseFloat(scaleTarget.toFixed(2)), + })) - await tick() + await tick() + } const adjustedY = (hViewPort - contentDims.original.h) / 2 @@ -67,6 +118,8 @@ ...state, x: 0, y: parseInt(0 + adjustedY), + scrollX: 0, + scrollY: 0, })) } @@ -79,7 +132,6 @@ dragSpot: null, scale: 1, dropzones: {}, - //focus - node to center on? }) setContext("draggableView", view) @@ -89,9 +141,18 @@ setContext("viewPos", internalPos) // Content pos tracking - const contentPos = writable({ x: 0, y: 0, scrollX: 0, scrollY: 0 }) + const contentPos = writable({ + x: 0, + y: 0, + scrollX: 0, + scrollY: 0, + }) setContext("contentPos", contentPos) + $: if ($view || contentDims) { + window.testValues = { ...$view, contentDims } + } + // Elements let mainContent let viewPort @@ -101,7 +162,11 @@ let down = false // Monitor the size of the viewPort - let observer + let viewObserver + + // Monitor the size of the content + // Always reconfigure when changes occur + let contentObserver // Size of the core display content let contentDims = {} @@ -110,16 +175,21 @@ let viewDims = {} // When dragging the content, maintain the drag start offset - let dragOffset - - // Used when focusing the UI on trigger - let loaded = false + let dragOffset = [] // Edge around the draggable content let contentDragPadding = 200 // Auto scroll - // let scrollInterval + let scrollInterval + + let zoomOrigin = [] + + // Focus element details. Used to move the viewport + let focusElement = memo() + + // Memo Focus + $: focusElement.set($view.focusEle) const onScale = async () => { dispatch("zoom", $view.scale) @@ -127,8 +197,6 @@ } const getDims = async () => { - if (!mainContent) return - if (!contentDims.original) { contentDims.original = { w: parseInt(mainContent.getBoundingClientRect().width), @@ -153,10 +221,17 @@ return { x: Math.max(x, 0), y: Math.max(y, 0) } } - const buildWrapStyles = (pos, scale, dims) => { + const buildWrapStyles = (pos, scale, dims, contentCursor) => { const { x, y } = pos const { w, h } = dims - return `--posX: ${x}px; --posY: ${y}px; --scale: ${scale}; --wrapH: ${h}px; --wrapW: ${w}px` + const [cursorContentX, cursorContentY] = contentCursor + return ` + --posX: ${x}px; --posY: ${y}px; + --scale: ${scale}; + --wrapH: ${h}px; --wrapW: ${w}px; + --ccX: ${cursorContentX}px; + --ccY: ${cursorContentY}px; + ` } const onViewScroll = e => { @@ -186,12 +261,12 @@ if (e.deltaY < 0) { updatedScale = Math.min(1, currentScale + 0.05) } else if (e.deltaY > 0) { - updatedScale = Math.max(0, currentScale - 0.05) + updatedScale = Math.max(0.1, currentScale - 0.05) } view.update(state => ({ ...state, - scale: Number(updatedScale.toFixed(2)), + scale: parseFloat(updatedScale.toFixed(2)), })) } else { yBump = scrollIncrement * (e.deltaY < 0 ? -1 : 1) @@ -226,23 +301,58 @@ })) } - // Needs to handle when the mouse leaves the screen - // Needs to know the direction of movement and accelerate/decelerate - if (y < (viewDims.height / 100) * 20 && $view.dragging) { - // if (!scrollInterval) { - // scrollInterval = setInterval(() => { - // contentPos.update(state => ({ - // ...state, - // x: contentPos.x, - // y: contentPos.y + 35, - // })) - // }, 100) - // } - // } else { - // if (scrollInterval) { - // clearInterval(scrollInterval) - // scrollInterval = undefined - // } + const clearScrollInterval = () => { + if (scrollInterval) { + clearInterval(scrollInterval) + scrollInterval = undefined + } + } + + if ($view.dragging) { + // Need to know the internal offset as well. + const scrollZones = { + top: y < viewDims.height * 0.05, + bottom: + y > viewDims.height - ($view.moveStep.h - $view.moveStep.mouse.y), + left: x < viewDims.width * 0.05, + right: x > viewDims.width - $view.moveStep.w, + } + + // Determine which zones are currently in play + const dragOutEntries = Object.entries(scrollZones).filter(e => e[1]) + if (dragOutEntries.length) { + const dragOut = Object.fromEntries(dragOutEntries) + + if (!scrollInterval) { + const autoScroll = () => { + // Some internal tracking for implying direction + // const lastY = $contentPos.y + // const lastX = $contentPos.x + const bump = 30 + + // Depending on the zone, you want to move the content + // in the opposite direction + const xInterval = dragOut.right ? -bump : dragOut.left ? bump : 0 + const yInterval = dragOut.bottom ? -bump : dragOut.top ? bump : 0 + + return () => { + contentPos.update(state => ({ + ...state, + x: (state.x || 0) + xInterval, + y: (state.y || 0) + yInterval, + scrollX: state.scrollX + xInterval, + scrollY: state.scrollY + yInterval, + })) + } + } + + scrollInterval = setInterval(autoScroll(), 30) + } + } else { + clearScrollInterval() + } + } else { + clearScrollInterval() } } @@ -265,6 +375,35 @@ ) } + // Reset state on mouse up + const globalMouseUp = e => { + down = false + + if ($view.dragging) { + dragOffset = [0, 0] + view.update(state => ({ + ...state, + dragging: false, + moveStep: null, + dragSpot: null, + dropzones: {}, + droptarget: null, + })) + + if (scrollInterval) { + clearInterval(scrollInterval) + scrollInterval = undefined + } + + // Clear the scroll offset for dragging + contentPos.update(state => ({ + ...state, + scrollY: 0, + scrollX: 0, + })) + } + } + const onMouseUp = () => { if ($view.droptarget) { handleDragDrop() @@ -353,9 +492,8 @@ dragOffset = [Math.abs(x - $contentPos.x), Math.abs(y - $contentPos.y)] } - const focusOnLoad = () => { - if ($view.focusEle && !loaded) { - const focusEleDims = $view.focusEle + const viewToFocusEle = () => { + if ($focusElement) { const viewWidth = viewDims.width // The amount to shift the content in order to center the trigger on load. @@ -363,8 +501,8 @@ // The sidebar offset factors into the left positioning of the content here. const targetX = contentWrap.getBoundingClientRect().x - - focusEleDims.x + - (viewWidth / 2 - focusEleDims.width / 2) + $focusElement.x + + (viewWidth / 2 - $focusElement.width / 2) // Update the content position state // Shift the content up slightly to accommodate the padding @@ -373,34 +511,45 @@ x: targetX, y: -(contentDragPadding / 2), })) - - loaded = true } } // Update dims after scaling - $: { - $view.scale + $: viewScale = $view.scale + $: if (viewScale && mainContent) { onScale() } // Focus on a registered element $: { - $view.focusEle - focusOnLoad() + $focusElement + viewToFocusEle() } // Content mouse pos and scale to css variables. // The wrap is set to match the content size - $: wrapStyles = buildWrapStyles($contentPos, $view.scale, contentDims) + $: wrapStyles = buildWrapStyles( + $contentPos, + $view.scale, + contentDims, + zoomOrigin + ) onMount(() => { - observer = new ResizeObserver(getDims) - observer.observe(viewPort) + viewObserver = new ResizeObserver(getDims) + viewObserver.observe(viewPort) + + contentObserver = new ResizeObserver(getDims) + contentObserver.observe(mainContent) + + // Global mouse observer + document.addEventListener("mouseup", globalMouseUp) }) onDestroy(() => { - observer.disconnect() + viewObserver.disconnect() + contentObserver.disconnect() + document.removeEventListener("mouseup", globalMouseUp) }) @@ -414,15 +563,17 @@ on:mousemove={Utils.domDebounce(onMouseMove)} style={`--dragPadding: ${contentDragPadding}px;`} > +
- +
+
View Pos [{$internalPos.x}, {$internalPos.y}] @@ -432,13 +583,16 @@ Dragging [{$view?.moveStep?.id || "no"}] Scale [{$view.scale}] Content [{JSON.stringify($contentPos)}] -
--> + Content Cursor [{JSON.stringify(dragOffset)}] +
+ +
{ - down = false - view.update(state => ({ - ...state, - dragging: false, - moveStep: null, - dragSpot: null, - dropzones: {}, - })) - }} >
@@ -481,6 +625,38 @@ overflow: hidden; position: relative; } + + .reticle { + display: none; + z-index: 10000; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 2px; + height: 2px; + background-color: red; + border-radius: 50%; + } + + .reticle-content { + z-index: 10000; + position: absolute; + top: var(--ccY); + left: var(--ccX); + width: 2px; + height: 2px; + background-color: greenyellow; + border-radius: 50%; + } + + .content { + transform-origin: var(--ccX) var(--ccY); + transform: scale(var(--scale)); + user-select: none; + padding: var(--dragPadding); + } + .content-wrap { min-width: 100%; position: absolute; @@ -491,22 +667,18 @@ cursor: grab; transform: translate(var(--posX), var(--posY)); } - .content { - transform: scale(var(--scale)); - user-select: none; - padding: var(--dragPadding); - } .content-wrap.dragging { cursor: grabbing; } - /* .debug { + .debug { display: flex; align-items: center; gap: 8px; position: fixed; - padding: 8px; + padding: var(--spacing-l); z-index: 2; - } */ + pointer-events: none; + } diff --git a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItem.svelte b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItem.svelte index 286347f0b6..bfbedbc3b2 100644 --- a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItem.svelte +++ b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItem.svelte @@ -51,8 +51,13 @@ if (!blockEle) { return } - const { width, height } = blockEle.getBoundingClientRect() - blockDims = { width: width / $view.scale, height: height / $view.scale } + const { width, height, top, left } = blockEle?.getBoundingClientRect() + blockDims = { + width: width / $view.scale, + height: height / $view.scale, + top, + left, + } } const loadSteps = blockRef => { @@ -174,12 +179,21 @@ e.stopPropagation() + updateBlockDims() + + const { clientX, clientY } = e view.update(state => ({ ...state, moveStep: { id: block.id, offsetX: $pos.x, offsetY: $pos.y, + w: blockDims.width, + h: blockDims.height, + mouse: { + x: Math.max(Math.round(clientX - blockDims.left), 0), + y: Math.max(Math.round(clientY - blockDims.top), 0), + }, }, })) }