From 3130fad6cba8095b6d73991edbacd9cb19df3623 Mon Sep 17 00:00:00 2001 From: GREENRAT-K405 Date: Tue, 30 Dec 2025 22:56:01 +0530 Subject: [PATCH 1/4] node aligns with the grids of graph --- src/config/defaultStyles.js | 4 +- src/graph-builder/graph-core/1-core.js | 59 ++++++++++++++++++++++++-- 2 files changed, 58 insertions(+), 5 deletions(-) diff --git a/src/config/defaultStyles.js b/src/config/defaultStyles.js index fd0b0f5..ff7d2dc 100644 --- a/src/config/defaultStyles.js +++ b/src/config/defaultStyles.js @@ -1,6 +1,6 @@ const NodeStyle = { - width: 100, - height: 50, + width: 100, // 5 grid cells (assuming 20px grid) + height: 60, // 3 grid cells (assuming 20px grid) shape: 'rectangle', opacity: 1, backgroundColor: '#ffcc00', diff --git a/src/graph-builder/graph-core/1-core.js b/src/graph-builder/graph-core/1-core.js index fcfc83d..640026a 100644 --- a/src/graph-builder/graph-core/1-core.js +++ b/src/graph-builder/graph-core/1-core.js @@ -23,6 +23,8 @@ class CoreGraph { bendNode; + gridSize = 20; // Configurable grid size in pixels + constructor(id, element, dispatcher, superState, projectName, nodeValidator, edgeValidator, authorName) { if (dispatcher) this.dispatcher = dispatcher; if (superState) this.superState = superState; @@ -49,13 +51,41 @@ class CoreGraph { this.initizialize(); } + // Helper function to snap a value to the nearest grid point + snapToGrid(value) { + return Math.round(value / this.gridSize) * this.gridSize; + } + + // Helper function to snap position to grid + snapPositionToGrid(position) { + return { + x: this.snapToGrid(position.x), + y: this.snapToGrid(position.y), + }; + } + + // Helper function to snap dimension to EVEN grid multiples + // This ensures all borders lie on grid lines when center is on a grid point + snapDimensionToGrid(dimension) { + let gridCells = Math.round(dimension / this.gridSize); + // Ensure at least 2 grid cells + if (gridCells < 2) { + gridCells = 2; + } + // Ensure always an even number + const evenCells = gridCells % 2 === 0 ? gridCells : gridCells + 1; + return evenCells * this.gridSize; + } + initizialize() { this.cy.nodeEditing({ resizeToContentCueEnabled: () => false, - setWidth(node, width) { + setWidth: (node, width) => { + // Allow smooth resizing - don't snap during drag node.data('style', { ...node.data('style'), width }); }, - setHeight(node, height) { + setHeight: (node, height) => { + // Allow smooth resizing - don't snap during drag node.data('style', { ...node.data('style'), height }); }, isNoResizeMode(node) { return node.data('type') !== 'ordin'; }, @@ -63,9 +93,12 @@ class CoreGraph { }); this.cy.gridGuide({ - snapToGridOnRelease: false, + snapToGridOnRelease: true, + snapToGridDuringDrag: true, zoomDash: true, panGrid: true, + gridSpacing: this.gridSize, + snapToAlignmentLocationOnRelease: true, }); this.cy.edgehandles({ preview: false, @@ -164,12 +197,32 @@ class CoreGraph { }); }); + this.cy.on('free', 'node[type = "ordin"]', (e) => { + e.target.forEach((node) => { + const currentPos = node.position(); + const snappedPos = this.snapPositionToGrid(currentPos); + node.position(snappedPos); + }); + }); + this.cy.on('nodeediting.resizestart', (e, type, node) => { node.scratch('height', node.data('style').height); node.scratch('width', node.data('style').width); node.scratch('position', { ...node.position() }); }); + this.cy.on('nodeediting.resizeend', (e, type, node) => { + // Snap dimensions to grid multiples + const style = node.data('style') || {}; + const snappedWidth = this.snapDimensionToGrid(style.width || 100); + const snappedHeight = this.snapDimensionToGrid(style.height || 50); + node.data('style', { ...style, width: snappedWidth, height: snappedHeight }); + + // Snap position to ensure all corners align to grid + const snappedPos = this.snapPositionToGrid(node.position()); + node.position(snappedPos); + }); + this.cy.on('hide-bend remove', () => { this.bendNode.removeListener('drag grab dragfree'); this.bendNode.addClass('hidden'); }); From 4962e0569a082bd7fa1dee59ba5fafefcaf1e5c4 Mon Sep 17 00:00:00 2001 From: GREENRAT-K405 Date: Tue, 30 Dec 2025 23:05:37 +0530 Subject: [PATCH 2/4] stricter logic for snapping of nodes with grids --- src/graph-builder/graph-core/1-core.js | 68 ++++++++++++++++++++++---- 1 file changed, 58 insertions(+), 10 deletions(-) diff --git a/src/graph-builder/graph-core/1-core.js b/src/graph-builder/graph-core/1-core.js index 640026a..165ceac 100644 --- a/src/graph-builder/graph-core/1-core.js +++ b/src/graph-builder/graph-core/1-core.js @@ -81,12 +81,50 @@ class CoreGraph { this.cy.nodeEditing({ resizeToContentCueEnabled: () => false, setWidth: (node, width) => { - // Allow smooth resizing - don't snap during drag - node.data('style', { ...node.data('style'), width }); + // HARD ENFORCEMENT: Snap width every frame during resize + const snappedWidth = this.snapDimensionToGrid(width); + node.data('style', { ...node.data('style'), width: snappedWidth }); + + // Adjust position to maintain edge alignment based on resize handle + const resizeType = node.scratch('resizeType'); + if (resizeType && (resizeType.includes('left') || resizeType.includes('right'))) { + const currentPos = node.position(); + const initialPos = node.scratch('resizeInitialPos'); + const initialWidth = node.scratch('width'); + const widthDelta = snappedWidth - initialWidth; + + let newX = currentPos.x; + if (resizeType.includes('left')) { + newX = initialPos.x - widthDelta / 2; + } else if (resizeType.includes('right')) { + newX = initialPos.x + widthDelta / 2; + } + node.position({ x: this.snapToGrid(newX), y: currentPos.y }); + } + return snappedWidth; }, setHeight: (node, height) => { - // Allow smooth resizing - don't snap during drag - node.data('style', { ...node.data('style'), height }); + // HARD ENFORCEMENT: Snap height every frame during resize + const snappedHeight = this.snapDimensionToGrid(height); + node.data('style', { ...node.data('style'), height: snappedHeight }); + + // Adjust position to maintain edge alignment based on resize handle + const resizeType = node.scratch('resizeType'); + if (resizeType && (resizeType.includes('top') || resizeType.includes('bottom'))) { + const currentPos = node.position(); + const initialPos = node.scratch('resizeInitialPos'); + const initialHeight = node.scratch('height'); + const heightDelta = snappedHeight - initialHeight; + + let newY = currentPos.y; + if (resizeType.includes('top')) { + newY = initialPos.y - heightDelta / 2; + } else if (resizeType.includes('bottom')) { + newY = initialPos.y + heightDelta / 2; + } + node.position({ x: currentPos.x, y: this.snapToGrid(newY) }); + } + return snappedHeight; }, isNoResizeMode(node) { return node.data('type') !== 'ordin'; }, isNoControlsMode(node) { return node.data('type') !== 'ordin'; }, @@ -199,26 +237,36 @@ class CoreGraph { this.cy.on('free', 'node[type = "ordin"]', (e) => { e.target.forEach((node) => { + const initialPos = node.scratch('position'); const currentPos = node.position(); - const snappedPos = this.snapPositionToGrid(currentPos); - node.position(snappedPos); + // Only snap if the node actually moved + const moved = !initialPos || initialPos.x !== currentPos.x || initialPos.y !== currentPos.y; + if (moved) { + const snappedPos = this.snapPositionToGrid(currentPos); + node.position(snappedPos); + } }); }); this.cy.on('nodeediting.resizestart', (e, type, node) => { + // Store initial state for resize operation node.scratch('height', node.data('style').height); node.scratch('width', node.data('style').width); - node.scratch('position', { ...node.position() }); + node.scratch('resizeInitialPos', { ...node.position() }); + node.scratch('resizeType', type); }); this.cy.on('nodeediting.resizeend', (e, type, node) => { - // Snap dimensions to grid multiples + // Clean up scratch data + node.removeScratch('resizeType'); + node.removeScratch('resizeInitialPos'); + + // Final enforcement: ensure position and dimensions are grid-aligned const style = node.data('style') || {}; const snappedWidth = this.snapDimensionToGrid(style.width || 100); const snappedHeight = this.snapDimensionToGrid(style.height || 50); node.data('style', { ...style, width: snappedWidth, height: snappedHeight }); - - // Snap position to ensure all corners align to grid + const snappedPos = this.snapPositionToGrid(node.position()); node.position(snappedPos); }); From 4e66b2608aa49c6fc708fd67b356e2a3fccec1a3 Mon Sep 17 00:00:00 2001 From: GREENRAT-K405 Date: Wed, 31 Dec 2025 00:23:17 +0530 Subject: [PATCH 3/4] remove trailing space error --- src/graph-builder/graph-core/1-core.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/graph-builder/graph-core/1-core.js b/src/graph-builder/graph-core/1-core.js index 165ceac..68978da 100644 --- a/src/graph-builder/graph-core/1-core.js +++ b/src/graph-builder/graph-core/1-core.js @@ -84,7 +84,7 @@ class CoreGraph { // HARD ENFORCEMENT: Snap width every frame during resize const snappedWidth = this.snapDimensionToGrid(width); node.data('style', { ...node.data('style'), width: snappedWidth }); - + // Adjust position to maintain edge alignment based on resize handle const resizeType = node.scratch('resizeType'); if (resizeType && (resizeType.includes('left') || resizeType.includes('right'))) { @@ -92,7 +92,7 @@ class CoreGraph { const initialPos = node.scratch('resizeInitialPos'); const initialWidth = node.scratch('width'); const widthDelta = snappedWidth - initialWidth; - + let newX = currentPos.x; if (resizeType.includes('left')) { newX = initialPos.x - widthDelta / 2; @@ -107,7 +107,7 @@ class CoreGraph { // HARD ENFORCEMENT: Snap height every frame during resize const snappedHeight = this.snapDimensionToGrid(height); node.data('style', { ...node.data('style'), height: snappedHeight }); - + // Adjust position to maintain edge alignment based on resize handle const resizeType = node.scratch('resizeType'); if (resizeType && (resizeType.includes('top') || resizeType.includes('bottom'))) { @@ -115,7 +115,7 @@ class CoreGraph { const initialPos = node.scratch('resizeInitialPos'); const initialHeight = node.scratch('height'); const heightDelta = snappedHeight - initialHeight; - + let newY = currentPos.y; if (resizeType.includes('top')) { newY = initialPos.y - heightDelta / 2; @@ -260,13 +260,13 @@ class CoreGraph { // Clean up scratch data node.removeScratch('resizeType'); node.removeScratch('resizeInitialPos'); - + // Final enforcement: ensure position and dimensions are grid-aligned const style = node.data('style') || {}; const snappedWidth = this.snapDimensionToGrid(style.width || 100); const snappedHeight = this.snapDimensionToGrid(style.height || 50); node.data('style', { ...style, width: snappedWidth, height: snappedHeight }); - + const snappedPos = this.snapPositionToGrid(node.position()); node.position(snappedPos); }); From b0aaf163cde392f744212322ac7dc3bbc509a969 Mon Sep 17 00:00:00 2001 From: GREENRAT-K405 Date: Wed, 31 Dec 2025 00:33:34 +0530 Subject: [PATCH 4/4] remove logs from previous pull req used for testing --- src/component/fileBrowser.jsx | 2 +- src/component/modals/FileEdit.jsx | 2 +- src/graph-builder/graph-core/5-load-save.js | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/component/fileBrowser.jsx b/src/component/fileBrowser.jsx index 768dd01..4c02c61 100644 --- a/src/component/fileBrowser.jsx +++ b/src/component/fileBrowser.jsx @@ -127,7 +127,7 @@ const LocalFileBrowser = ({ superState, dispatcher }) => { readFile(superState, dispatcher, fileObj, fileHandle); } catch (error) { if (error.name !== 'AbortError') { - console.error(error); + // console.error(error); } } }; diff --git a/src/component/modals/FileEdit.jsx b/src/component/modals/FileEdit.jsx index 2ea6c31..1f0a29a 100644 --- a/src/component/modals/FileEdit.jsx +++ b/src/component/modals/FileEdit.jsx @@ -56,7 +56,7 @@ const FileEditModal = ({ superState, dispatcher }) => { dispatcher({ type: T.SET_FILE_STATE, payload: fS }); } catch (error) { if (error.name !== 'AbortError') { - console.error(error); + // console.error(error); } } } diff --git a/src/graph-builder/graph-core/5-load-save.js b/src/graph-builder/graph-core/5-load-save.js index 1b6f9f8..e57ebc1 100644 --- a/src/graph-builder/graph-core/5-load-save.js +++ b/src/graph-builder/graph-core/5-load-save.js @@ -135,7 +135,7 @@ class GraphLoadSave extends GraphUndoRedo { toast.success('File saved Successfully'); } catch (error) { if (error.name !== 'AbortError') { - console.error(error); + // console.error(error); } } } else { @@ -177,7 +177,7 @@ class GraphLoadSave extends GraphUndoRedo { toast.success('File saved Successfully'); } catch (error) { if (error.name !== 'AbortError') { - console.error(error); + // console.error(error); } } }