Skip to content

Commit c969fd6

Browse files
committed
fix: more history support
1 parent 2b0b83e commit c969fd6

5 files changed

Lines changed: 57 additions & 120 deletions

File tree

src/NodeCanvas.jsx

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2191,7 +2191,7 @@ function NodeCanvas() {
21912191
storeActions.updateNodeInstance(activeGraphId, instanceId, draft => {
21922192
draft.x = newX;
21932193
draft.y = newY;
2194-
}, { isDragging: true, phase: 'move' });
2194+
}, { isDragging: true, phase: 'move', type: 'node_position' });
21952195
}
21962196
}, [activeGraphId, nodeById, gridMode, gridSize, storeActions]);
21972197

@@ -5061,7 +5061,7 @@ function NodeCanvas() {
50615061
const mouseCanvasX = (clientX - rect.left - panOffset.x) / zoomLevel + canvasSize.offsetX;
50625062
const mouseCanvasY = (clientY - rect.top - panOffset.y) / zoomLevel + canvasSize.offsetY;
50635063
const offset = { x: mouseCanvasX - nodeData.x, y: mouseCanvasY - nodeData.y };
5064-
setDraggingNodeInfo({ instanceId, offset });
5064+
setDraggingNodeInfo({ instanceId, offset, initialPos: { x: nodeData.x, y: nodeData.y } });
50655065

50665066

50675067

@@ -6727,6 +6727,50 @@ function NodeCanvas() {
67276727

67286728
// Reset scale for dragged nodes
67296729
if (draggingNodeInfo) {
6730+
// --- Manual History Recording for Drag ---
6731+
const patches = [];
6732+
const inversePatches = [];
6733+
const movedNodeCount = 0;
6734+
6735+
// Helper to record patch if moved
6736+
const checkAndRecord = (id, initX, initY) => {
6737+
const node = nodes.find(n => n.id === id);
6738+
if (node && (Math.abs(node.x - initX) > 0.01 || Math.abs(node.y - initY) > 0.01)) {
6739+
patches.push({ op: 'replace', path: ['graphs', activeGraphId, 'instances', id, 'x'], value: node.x });
6740+
patches.push({ op: 'replace', path: ['graphs', activeGraphId, 'instances', id, 'y'], value: node.y });
6741+
inversePatches.push({ op: 'replace', path: ['graphs', activeGraphId, 'instances', id, 'x'], value: initX });
6742+
inversePatches.push({ op: 'replace', path: ['graphs', activeGraphId, 'instances', id, 'y'], value: initY });
6743+
return true;
6744+
}
6745+
return false;
6746+
};
6747+
6748+
if (draggingNodeInfo.relativeOffsets) {
6749+
// Multi-drag
6750+
checkAndRecord(draggingNodeInfo.primaryId, draggingNodeInfo.initialPrimaryPos.x, draggingNodeInfo.initialPrimaryPos.y);
6751+
Object.entries(draggingNodeInfo.relativeOffsets).forEach(([id, rel]) => {
6752+
checkAndRecord(id, draggingNodeInfo.initialPrimaryPos.x + rel.offsetX, draggingNodeInfo.initialPrimaryPos.y + rel.offsetY);
6753+
});
6754+
} else if (draggingNodeInfo.initialPos) { // Ensure we have initialPos (added in startDrag)
6755+
// Single drag
6756+
checkAndRecord(draggingNodeInfo.instanceId, draggingNodeInfo.initialPos.x, draggingNodeInfo.initialPos.y);
6757+
}
6758+
// Group member drag logic is complex and handled via memberOffsets - skipping exact history for grouped drag for now or assuming it relies on standard updates?
6759+
// Group member drag uses 'memberOffsets' in performDragUpdate. It updates positions.
6760+
// If we want to support that, we need initial positions there too.
6761+
// For now, focusing on standard node drag.
6762+
6763+
if (patches.length > 0) {
6764+
useHistoryStore.getState().pushAction({
6765+
domain: `graph-${activeGraphId}`,
6766+
actionType: 'node_position',
6767+
description: `Moved ${patches.length / 2} Node(s)`,
6768+
patches,
6769+
inversePatches
6770+
});
6771+
}
6772+
// -----------------------------------------
6773+
67306774
const instanceIdsToReset = new Set();
67316775
if (draggingNodeInfo.relativeOffsets) {
67326776
instanceIdsToReset.add(draggingNodeInfo.primaryId);
@@ -6751,7 +6795,7 @@ function NodeCanvas() {
67516795
activeGraphId,
67526796
id,
67536797
draft => { draft.scale = 1; },
6754-
{ phase: 'end', isDragging: false, finalize: shouldFinalize }
6798+
{ phase: 'end', isDragging: false, finalize: shouldFinalize, ignore: true } // Ignore this scale update in history
67556799
);
67566800
if (shouldFinalize) finalizeSent = true;
67576801
}

src/RedstringMenu.jsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React, { useState, useEffect, useRef } from 'react';
22
import MaroonSlider from './components/MaroonSlider.jsx';
3-
import { ChevronRight, FileText, FolderOpen, Save, Clock, Globe, Bug, BookOpen, Home, LayoutGrid, Activity, RefreshCw } from 'lucide-react';
3+
import { ChevronRight, FileText, FolderOpen, Save, Clock, Globe, Bug, BookOpen, Home, LayoutGrid, Activity, RefreshCw, Undo2, Redo2 } from 'lucide-react';
44
import './RedstringMenu.css';
55
import DebugOverlay from './DebugOverlay';
66
import * as fileStorage from './store/fileStorage.js';
@@ -369,6 +369,7 @@ const RedstringMenu = ({
369369
}}
370370
style={{ cursor: 'pointer', opacity: useHistoryStore.getState().canUndo() ? 1 : 0.5 }}
371371
>
372+
<Undo2 size={16} style={{ marginRight: '8px', minWidth: '16px', flexShrink: 0 }} />
372373
<div style={{ display: 'flex', justifyContent: 'space-between', width: '100%' }}>
373374
<span>Undo</span>
374375
<span style={{ opacity: 0.5, fontSize: '11px' }}>Ctrl+Z</span>
@@ -383,6 +384,7 @@ const RedstringMenu = ({
383384
}}
384385
style={{ cursor: 'pointer', opacity: useHistoryStore.getState().canRedo() ? 1 : 0.5 }}
385386
>
387+
<Redo2 size={16} style={{ marginRight: '8px', minWidth: '16px', flexShrink: 0 }} />
386388
<div style={{ display: 'flex', justifyContent: 'space-between', width: '100%' }}>
387389
<span>Redo</span>
388390
<span style={{ opacity: 0.5, fontSize: '11px' }}>Ctrl+Shift+Z</span>

src/components/UndoRedoButtons.css

Lines changed: 0 additions & 46 deletions
This file was deleted.

src/components/UndoRedoButtons.jsx

Lines changed: 0 additions & 66 deletions
This file was deleted.

src/store/graphStore.jsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -206,15 +206,18 @@ const saveCoordinatorMiddleware = (config) => {
206206
// --- History Recording ---
207207
// Record significant actions to the history store
208208
const recordableTypes = new Set([
209-
'node_place', 'node_delete', 'node_type_change',
209+
'node_place', 'node_delete', 'node_type_change', 'node_update',
210210
'edge_create', 'edge_delete', 'edge_update', 'edge_type_change',
211211
'group_create', 'group_update', 'group_delete', 'group_convert',
212212
'prototype_create', 'prototype_update', 'prototype_delete',
213213
'position_update', 'node_position',
214214
'graph_create', 'graph_delete'
215215
]);
216216

217-
if (recordableTypes.has(changeContext.type)) {
217+
218+
if (changeContext.ignore) {
219+
// Explicitly skip recording
220+
} else if (recordableTypes.has(changeContext.type)) {
218221
// Special handling for position updates: only record if finalized (drag end)
219222
if ((changeContext.type === 'node_position' || changeContext.type === 'position_update') && !changeContext.finalize) {
220223
// Skip recording intermediate drag states
@@ -1484,7 +1487,7 @@ const useGraphStore = create(saveCoordinatorMiddleware((set, get, api) => {
14841487

14851488
// Update a prototype's data using Immer's recipe. This affects all its instances.
14861489
updateNodePrototype: (prototypeId, recipe) => {
1487-
api.setChangeContext({ type: 'prototype_change', target: 'prototype' });
1490+
api.setChangeContext({ type: 'prototype_update', target: 'prototype' });
14881491
return set(produce((draft) => {
14891492
const prototype = draft.nodePrototypes.get(prototypeId);
14901493
if (prototype) {
@@ -1517,7 +1520,7 @@ const useGraphStore = create(saveCoordinatorMiddleware((set, get, api) => {
15171520

15181521
// Update an instance's unique data (e.g., position)
15191522
updateNodeInstance: (graphId, instanceId, recipe, contextOptions = {}) => {
1520-
api.setChangeContext({ type: 'node_position', target: 'instance', ...contextOptions });
1523+
api.setChangeContext({ type: contextOptions.type || 'node_update', target: 'instance', ...contextOptions });
15211524
return set(produce((draft) => {
15221525
const graph = draft.graphs.get(graphId);
15231526
if (graph && graph.instances) {

0 commit comments

Comments
 (0)