From 5dba2e7c9e242269600ad7a89dbff7df03518462 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesus=20Manuel=20Pi=C3=B1eiro=20Cid?= Date: Wed, 21 May 2025 15:01:15 +0200 Subject: [PATCH 1/3] chore: initial commit From 235f349b6f56ef078c1acf229d1d627498ee112f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesus=20Manuel=20Pi=C3=B1eiro=20Cid?= Date: Wed, 21 May 2025 15:02:35 +0200 Subject: [PATCH 2/3] fix: selection now snapps to guides --- code/packages/sdk/src/nodes/frame/frame.ts | 24 ++-- code/packages/sdk/src/nodes/node.ts | 8 +- .../plugins/nodes-snapping/nodes-snapping.ts | 111 ++++++++++++++++-- code/packages/sdk/src/utils.ts | 2 +- 4 files changed, 121 insertions(+), 24 deletions(-) diff --git a/code/packages/sdk/src/nodes/frame/frame.ts b/code/packages/sdk/src/nodes/frame/frame.ts index 9e236352c..32d0ee4c3 100644 --- a/code/packages/sdk/src/nodes/frame/frame.ts +++ b/code/packages/sdk/src/nodes/frame/frame.ts @@ -91,22 +91,22 @@ export class WeaveFrameNode extends WeaveNode { id, containerId: `${id}-group-internal`, containerOffsetX: 0, - containerOffsetY: titleHeight + borderWidth, - width: props.frameWidth + borderWidth * 2, - height: props.frameHeight + titleHeight + borderWidth * 2, + containerOffsetY: titleHeight, + width: props.frameWidth, + height: props.frameHeight + titleHeight, fill: '#ffffffff', clipX: 0, clipY: 0, - clipWidth: props.frameWidth + borderWidth * 2, - clipHeight: props.frameHeight + titleHeight + borderWidth * 2, + clipWidth: props.frameWidth, + clipHeight: props.frameHeight + titleHeight, name: 'node', }); const background = new Konva.Rect({ id: `${id}-bg`, nodeId: id, - x: borderWidth, - y: titleHeight + borderWidth, + x: 0, + y: titleHeight, width: props.frameWidth, stroke: borderColor, strokeWidth: borderWidth, @@ -141,18 +141,18 @@ export class WeaveFrameNode extends WeaveNode { const frameInternal = new Konva.Group({ id: `${id}-group-internal`, nodeId: id, - x: borderWidth * 2, - y: titleHeight + borderWidth * 2, + x: 0, + y: titleHeight, width: props.frameWidth, height: props.frameHeight, draggable: false, stroke: 'transparent', strokeScaleEnabled: false, - borderWidth: borderWidth, + borderWidth: 0, clipX: 0, clipY: 0, - clipWidth: props.frameWidth - borderWidth * 2, - clipHeight: props.frameHeight - borderWidth * 2, + clipWidth: props.frameWidth, + clipHeight: props.frameHeight, }); frame.add(frameInternal); diff --git a/code/packages/sdk/src/nodes/node.ts b/code/packages/sdk/src/nodes/node.ts index a3856e7d0..8bc5f23fd 100644 --- a/code/packages/sdk/src/nodes/node.ts +++ b/code/packages/sdk/src/nodes/node.ts @@ -168,7 +168,9 @@ export abstract class WeaveNode implements WeaveNodeBase { }); } - this.instance.updateNode(this.serialize(node as WeaveElementInstance)); + this.instance.updateNode( + this.serialize(e.target as WeaveElementInstance) + ); } }); @@ -176,9 +178,9 @@ export abstract class WeaveNode implements WeaveNodeBase { if (this.isSelecting() && this.isNodeSelected(node)) { clearContainerTargets(this.instance); - const layerToMove = moveNodeToContainer(this.instance, e.target); + const containerToMove = moveNodeToContainer(this.instance, e.target); - if (layerToMove) { + if (containerToMove) { return; } diff --git a/code/packages/sdk/src/plugins/nodes-snapping/nodes-snapping.ts b/code/packages/sdk/src/plugins/nodes-snapping/nodes-snapping.ts index 9c81462d4..b3ef99cff 100644 --- a/code/packages/sdk/src/plugins/nodes-snapping/nodes-snapping.ts +++ b/code/packages/sdk/src/plugins/nodes-snapping/nodes-snapping.ts @@ -22,6 +22,7 @@ import { } from './constants'; import type { KonvaEventObject } from 'konva/lib/Node'; import type { WeaveNodesSelectionPlugin } from '../nodes-selection/nodes-selection'; +import type { Vector2d } from 'konva/lib/types'; export class WeaveNodesSnappingPlugin extends WeavePlugin { private guideLineConfig: Konva.LineConfig; @@ -56,6 +57,48 @@ export class WeaveNodesSnappingPlugin extends WeavePlugin { this.enabled = enabled; } + getSelectedNodesMetadata(transformer: Konva.Transformer): { + width: number; + height: number; + nodes: string[]; + } { + const firstNode = transformer.getNodes()[0]; + const firstNodeClientRect = firstNode.getClientRect(); + + const rectCoordsMin: Vector2d = { + x: firstNodeClientRect.x, + y: firstNodeClientRect.y, + }; + const rectCoordsMax: Vector2d = { + x: firstNodeClientRect.x + firstNodeClientRect.width, + y: firstNodeClientRect.y + firstNodeClientRect.height, + }; + + const nodes = []; + for (const node of transformer.getNodes()) { + const clientRect = node.getClientRect(); + if (clientRect.x < rectCoordsMin.x) { + rectCoordsMin.x = clientRect.x; + } + if (clientRect.y < rectCoordsMin.y) { + rectCoordsMin.y = clientRect.y; + } + if (clientRect.x + clientRect.width > rectCoordsMax.x) { + rectCoordsMax.x = clientRect.x + clientRect.width; + } + if (clientRect.y + clientRect.height > rectCoordsMax.y) { + rectCoordsMax.y = clientRect.y + clientRect.height; + } + nodes.push(node.getAttrs().id as string); + } + + return { + width: rectCoordsMax.x - rectCoordsMin.x, + height: rectCoordsMax.y - rectCoordsMin.y, + nodes, + }; + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any evaluateGuidelines(e: KonvaEventObject): void { const utilityLayer = this.instance.getUtilityLayer(); @@ -68,11 +111,31 @@ export class WeaveNodesSnappingPlugin extends WeavePlugin { return; } + const nodesSelectionPlugin = + this.instance.getPlugin('nodesSelection'); + + let skipNodes = []; let node: Konva.Node | undefined = undefined; - if (e.type === 'dragmove' && e.target instanceof Konva.Transformer) { + if ( + e.type === 'dragmove' && + nodesSelectionPlugin && + e.target instanceof Konva.Transformer && + e.target.getNodes().length === 1 + ) { const actualTarget: Konva.Transformer = e.target as unknown as Konva.Transformer; node = actualTarget.getNodes()[0]; + skipNodes.push(node.getAttrs().id ?? ''); + } + if ( + e.type === 'dragmove' && + nodesSelectionPlugin && + e.target instanceof Konva.Transformer && + e.target.getNodes().length > 1 + ) { + const { nodes } = this.getSelectedNodesMetadata(e.target); + node = e.target; + skipNodes = [...nodes]; } if (e.type === 'transform') { node = e.target; @@ -83,13 +146,15 @@ export class WeaveNodesSnappingPlugin extends WeavePlugin { } // find possible snapping lines - const lineGuideStops = this.getLineGuideStops(node); + const lineGuideStops = this.getLineGuideStops(skipNodes); // find snapping points of current object const itemBounds = this.getObjectSnappingEdges(node); // now find where can we snap current object const guides = this.getGuides(lineGuideStops, itemBounds, e.type); + utilityLayer.destroyChildren(); + // do nothing of no snapping if (!guides.length) { return; @@ -101,6 +166,7 @@ export class WeaveNodesSnappingPlugin extends WeavePlugin { this.drawGuides(guides); if (e.type === 'dragmove') { + const orgAbsPos = node.absolutePosition(); const absPos = node.absolutePosition(); // now force object position guides.forEach((lg) => { @@ -115,7 +181,26 @@ export class WeaveNodesSnappingPlugin extends WeavePlugin { } } }); - node.absolutePosition(absPos); + + const vecDiff = { + x: orgAbsPos.x - absPos.x, + y: orgAbsPos.y - absPos.y, + }; + + if (node instanceof Konva.Transformer) { + node.getNodes().forEach((n) => { + const nodeAbsPos = n.getAbsolutePosition(); + + const newPos = { + x: nodeAbsPos.x - vecDiff.x, + y: nodeAbsPos.y - vecDiff.y, + }; + + n.setAbsolutePosition(newPos); + }); + } else { + node.absolutePosition(absPos); + } } if (e.type === 'transform') { const nodesSelectionPlugin = @@ -167,8 +252,7 @@ export class WeaveNodesSnappingPlugin extends WeavePlugin { return; } - // clear all previous lines on the screen - utilityLayer.find(`.${GUIDE_LINE_NAME}`).forEach((l) => l.destroy()); + utilityLayer.destroyChildren(); } private initEvents() { @@ -181,7 +265,7 @@ export class WeaveNodesSnappingPlugin extends WeavePlugin { } } - getLineGuideStops(skipShape: Konva.Node): LineGuideStop { + getLineGuideStops(skipNodes: string[]): LineGuideStop { const stage = this.instance.getStage(); const nodesSelection = @@ -205,9 +289,10 @@ export class WeaveNodesSnappingPlugin extends WeavePlugin { // and we snap over edges and center of each object on the canvas stage.find('.node').forEach((guideItem) => { - if (guideItem === skipShape) { + if (skipNodes.includes(guideItem.getAttrs().id ?? '')) { return; } + const box = guideItem.getClientRect({ skipStroke: true }); // and we can snap to all edges of shapes @@ -226,7 +311,17 @@ export class WeaveNodesSnappingPlugin extends WeavePlugin { } getObjectSnappingEdges(node: Konva.Node): NodeSnappingEdges { - const box = node.getClientRect({ skipStroke: true }); + let box = node.getClientRect({ skipStroke: true }); + + if (node instanceof Konva.Transformer) { + const transformerRect = node.getChildren((node) => { + return node.getAttrs().name === 'back'; + })[0]; + box = transformerRect.getClientRect({ + skipStroke: true, + }); + } + const absPos = node.absolutePosition(); const snappingEdges: NodeSnappingEdges = { diff --git a/code/packages/sdk/src/utils.ts b/code/packages/sdk/src/utils.ts index 9d14b05d4..0fbae7b24 100644 --- a/code/packages/sdk/src/utils.ts +++ b/code/packages/sdk/src/utils.ts @@ -112,7 +112,7 @@ export function moveNodeToContainer( const actualNode = nodeHandler.serialize(node as WeaveElementInstance); instance.removeNode(actualNode); - instance.addNode(actualNode, layerToMove?.getAttrs().id); + instance.addNode(actualNode, layerToMove.getAttrs().id); } return layerToMove; From f76c85c1620b50fbe5add3b1657efdc40f3e307f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesus=20Manuel=20Pi=C3=B1eiro=20Cid?= Date: Wed, 21 May 2025 15:03:15 +0200 Subject: [PATCH 3/3] chore: update changelog and doc --- code/CHANGELOG.md | 1 + docs/content/docs/main/changelog/prerelease/0.14.3.mdx | 1 + 2 files changed, 2 insertions(+) diff --git a/code/CHANGELOG.md b/code/CHANGELOG.md index 6ff821cda..536cf1ea0 100644 --- a/code/CHANGELOG.md +++ b/code/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#248](https://github.com/InditexTech/weavejs/issues/248) Mouse wheel panning only when over stage - [#250](https://github.com/InditexTech/weavejs/issues/250) Copy / paste on frame doesn't set copied element on it +- [#253](https://github.com/InditexTech/weavejs/issues/253) Selected nodes not triggering snapping lines ## [0.14.2] - 2025-05-20 diff --git a/docs/content/docs/main/changelog/prerelease/0.14.3.mdx b/docs/content/docs/main/changelog/prerelease/0.14.3.mdx index 19894208d..695e0b2ec 100644 --- a/docs/content/docs/main/changelog/prerelease/0.14.3.mdx +++ b/docs/content/docs/main/changelog/prerelease/0.14.3.mdx @@ -11,3 +11,4 @@ description: Several bugfixes - [#248](https://github.com/InditexTech/weavejs/issues/248) Mouse wheel panning only when over stage - [#250](https://github.com/InditexTech/weavejs/issues/250) Copy / paste on frame doesn't set copied element on it +- [#253](https://github.com/InditexTech/weavejs/issues/253) Selected nodes not triggering snapping lines