Skip to content

Commit 222a775

Browse files
authored
Merge pull request #252 from GREENRAT-K405/dev
2 parents 5eadbb8 + f2ce0ac commit 222a775

5 files changed

Lines changed: 113 additions & 20 deletions

File tree

src/component/fileBrowser.jsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,9 +126,7 @@ const LocalFileBrowser = ({ superState, dispatcher }) => {
126126
const fileObj = await fileHandle.getFile();
127127
readFile(superState, dispatcher, fileObj, fileHandle);
128128
} catch (error) {
129-
if (error.name !== 'AbortError') {
130-
console.error(error);
131-
}
129+
// AbortError is silently ignored (user cancelled)
132130
}
133131
};
134132

src/component/modals/FileEdit.jsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,7 @@ const FileEditModal = ({ superState, dispatcher }) => {
5555
}]);
5656
dispatcher({ type: T.SET_FILE_STATE, payload: fS });
5757
} catch (error) {
58-
if (error.name !== 'AbortError') {
59-
console.error(error);
60-
}
58+
// AbortError is silently ignored (user cancelled)
6159
}
6260
}
6361

src/config/defaultStyles.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
const NodeStyle = {
2-
width: 100,
3-
height: 50,
2+
width: 100, // 5 grid cells (assuming 20px grid)
3+
height: 60, // 3 grid cells (assuming 20px grid)
44
shape: 'rectangle',
55
opacity: 1,
66
backgroundColor: '#ffcc00',

src/graph-builder/graph-core/1-core.js

Lines changed: 107 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ class CoreGraph {
2323

2424
bendNode;
2525

26+
gridSize = 20; // Configurable grid size in pixels
27+
2628
constructor(id, element, dispatcher, superState, projectName, nodeValidator, edgeValidator, authorName) {
2729
if (dispatcher) this.dispatcher = dispatcher;
2830
if (superState) this.superState = superState;
@@ -49,23 +51,92 @@ class CoreGraph {
4951
this.initizialize();
5052
}
5153

54+
// Helper function to snap a value to the nearest grid point
55+
snapToGrid(value) {
56+
return Math.round(value / this.gridSize) * this.gridSize;
57+
}
58+
59+
// Helper function to snap position to grid
60+
snapPositionToGrid(position) {
61+
return {
62+
x: this.snapToGrid(position.x),
63+
y: this.snapToGrid(position.y),
64+
};
65+
}
66+
67+
// Helper function to snap dimension to EVEN grid multiples
68+
// This ensures all borders lie on grid lines when center is on a grid point
69+
snapDimensionToGrid(dimension) {
70+
let gridCells = Math.round(dimension / this.gridSize);
71+
// Ensure at least 2 grid cells
72+
if (gridCells < 2) {
73+
gridCells = 2;
74+
}
75+
// Ensure always an even number
76+
const evenCells = gridCells % 2 === 0 ? gridCells : gridCells + 1;
77+
return evenCells * this.gridSize;
78+
}
79+
5280
initizialize() {
5381
this.cy.nodeEditing({
5482
resizeToContentCueEnabled: () => false,
55-
setWidth(node, width) {
56-
node.data('style', { ...node.data('style'), width });
83+
setWidth: (node, width) => {
84+
// HARD ENFORCEMENT: Snap width every frame during resize
85+
const snappedWidth = this.snapDimensionToGrid(width);
86+
node.data('style', { ...node.data('style'), width: snappedWidth });
87+
88+
// Adjust position to maintain edge alignment based on resize handle
89+
const resizeType = node.scratch('resizeType');
90+
if (resizeType && (resizeType.includes('left') || resizeType.includes('right'))) {
91+
const currentPos = node.position();
92+
const initialPos = node.scratch('resizeInitialPos');
93+
const initialWidth = node.scratch('width');
94+
const widthDelta = snappedWidth - initialWidth;
95+
96+
let newX = currentPos.x;
97+
if (resizeType.includes('left')) {
98+
newX = initialPos.x - widthDelta / 2;
99+
} else if (resizeType.includes('right')) {
100+
newX = initialPos.x + widthDelta / 2;
101+
}
102+
node.position({ x: this.snapToGrid(newX), y: currentPos.y });
103+
}
104+
return snappedWidth;
57105
},
58-
setHeight(node, height) {
59-
node.data('style', { ...node.data('style'), height });
106+
setHeight: (node, height) => {
107+
// HARD ENFORCEMENT: Snap height every frame during resize
108+
const snappedHeight = this.snapDimensionToGrid(height);
109+
node.data('style', { ...node.data('style'), height: snappedHeight });
110+
111+
// Adjust position to maintain edge alignment based on resize handle
112+
const resizeType = node.scratch('resizeType');
113+
if (resizeType && (resizeType.includes('top') || resizeType.includes('bottom'))) {
114+
const currentPos = node.position();
115+
const initialPos = node.scratch('resizeInitialPos');
116+
const initialHeight = node.scratch('height');
117+
const heightDelta = snappedHeight - initialHeight;
118+
119+
let newY = currentPos.y;
120+
if (resizeType.includes('top')) {
121+
newY = initialPos.y - heightDelta / 2;
122+
} else if (resizeType.includes('bottom')) {
123+
newY = initialPos.y + heightDelta / 2;
124+
}
125+
node.position({ x: currentPos.x, y: this.snapToGrid(newY) });
126+
}
127+
return snappedHeight;
60128
},
61129
isNoResizeMode(node) { return node.data('type') !== 'ordin'; },
62130
isNoControlsMode(node) { return node.data('type') !== 'ordin'; },
63131
});
64132

65133
this.cy.gridGuide({
66-
snapToGridOnRelease: false,
134+
snapToGridOnRelease: true,
135+
snapToGridDuringDrag: true,
67136
zoomDash: true,
68137
panGrid: true,
138+
gridSpacing: this.gridSize,
139+
snapToAlignmentLocationOnRelease: true,
69140
});
70141
this.cy.edgehandles({
71142
preview: false,
@@ -164,10 +235,40 @@ class CoreGraph {
164235
});
165236
});
166237

238+
this.cy.on('free', 'node[type = "ordin"]', (e) => {
239+
e.target.forEach((node) => {
240+
const initialPos = node.scratch('position');
241+
const currentPos = node.position();
242+
// Only snap if the node actually moved
243+
const moved = !initialPos || initialPos.x !== currentPos.x || initialPos.y !== currentPos.y;
244+
if (moved) {
245+
const snappedPos = this.snapPositionToGrid(currentPos);
246+
node.position(snappedPos);
247+
}
248+
});
249+
});
250+
167251
this.cy.on('nodeediting.resizestart', (e, type, node) => {
252+
// Store initial state for resize operation
168253
node.scratch('height', node.data('style').height);
169254
node.scratch('width', node.data('style').width);
170-
node.scratch('position', { ...node.position() });
255+
node.scratch('resizeInitialPos', { ...node.position() });
256+
node.scratch('resizeType', type);
257+
});
258+
259+
this.cy.on('nodeediting.resizeend', (e, type, node) => {
260+
// Clean up scratch data
261+
node.removeScratch('resizeType');
262+
node.removeScratch('resizeInitialPos');
263+
264+
// Final enforcement: ensure position and dimensions are grid-aligned
265+
const style = node.data('style') || {};
266+
const snappedWidth = this.snapDimensionToGrid(style.width || 100);
267+
const snappedHeight = this.snapDimensionToGrid(style.height || 50);
268+
node.data('style', { ...style, width: snappedWidth, height: snappedHeight });
269+
270+
const snappedPos = this.snapPositionToGrid(node.position());
271+
node.position(snappedPos);
171272
});
172273

173274
this.cy.on('hide-bend remove', () => {

src/graph-builder/graph-core/5-load-save.js

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -134,9 +134,7 @@ class GraphLoadSave extends GraphUndoRedo {
134134
this.dispatcher({ type: T.SET_FILE_STATE, payload: fS });
135135
toast.success('File saved Successfully');
136136
} catch (error) {
137-
if (error.name !== 'AbortError') {
138-
console.error(error);
139-
}
137+
// AbortError is silently ignored (user cancelled)
140138
}
141139
} else {
142140
// eslint-disable-next-line no-alert
@@ -176,9 +174,7 @@ class GraphLoadSave extends GraphUndoRedo {
176174
await stream.close();
177175
toast.success('File saved Successfully');
178176
} catch (error) {
179-
if (error.name !== 'AbortError') {
180-
console.error(error);
181-
}
177+
// AbortError is silently ignored (user cancelled)
182178
}
183179
}
184180

0 commit comments

Comments
 (0)