Merge commit
This commit is contained in:
parent
4f93043b03
commit
9674b7aaa0
|
@ -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)
|
||||
})
|
||||
</script>
|
||||
|
||||
|
@ -414,15 +563,17 @@
|
|||
on:mousemove={Utils.domDebounce(onMouseMove)}
|
||||
style={`--dragPadding: ${contentDragPadding}px;`}
|
||||
>
|
||||
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
|
||||
<div
|
||||
class="draggable-view"
|
||||
bind:this={viewPort}
|
||||
on:wheel={Utils.domDebounce(onViewScroll)}
|
||||
on:mousemove={Utils.domDebounce(onViewMouseMove)}
|
||||
on:mouseup={onViewDragEnd}
|
||||
on:mouseleave={onViewDragEnd}
|
||||
>
|
||||
<!-- <div class="debug">
|
||||
<!--delete me-->
|
||||
<div class="reticle" />
|
||||
<div class="debug">
|
||||
<span>
|
||||
View Pos [{$internalPos.x}, {$internalPos.y}]
|
||||
</span>
|
||||
|
@ -432,13 +583,16 @@
|
|||
<span>Dragging [{$view?.moveStep?.id || "no"}]</span>
|
||||
<span>Scale [{$view.scale}]</span>
|
||||
<span>Content [{JSON.stringify($contentPos)}]</span>
|
||||
</div> -->
|
||||
<span>Content Cursor [{JSON.stringify(dragOffset)}]</span>
|
||||
</div>
|
||||
<div
|
||||
class="content-wrap"
|
||||
style={wrapStyles}
|
||||
bind:this={contentWrap}
|
||||
class:dragging={down}
|
||||
>
|
||||
<!--delete me-->
|
||||
<div class="reticle-content" />
|
||||
<div
|
||||
class="content"
|
||||
bind:this={mainContent}
|
||||
|
@ -453,16 +607,6 @@
|
|||
down = false
|
||||
}
|
||||
}}
|
||||
on:mouseleave={() => {
|
||||
down = false
|
||||
view.update(state => ({
|
||||
...state,
|
||||
dragging: false,
|
||||
moveStep: null,
|
||||
dragSpot: null,
|
||||
dropzones: {},
|
||||
}))
|
||||
}}
|
||||
>
|
||||
<slot name="content" />
|
||||
</div>
|
||||
|
@ -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;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -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),
|
||||
},
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue