mirror of
https://github.com/stan-smith/FossFLOW.git
synced 2026-08-31 00:39:47 +08:00
fix: lasso wasnt moving nodes if there was also a text item in the selection, now it works
This commit is contained in:
@@ -362,16 +362,21 @@ export const useScene = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const updateTextBox = useCallback(
|
const updateTextBox = useCallback(
|
||||||
(id: string, updates: Partial<TextBox>) => {
|
(id: string, updates: Partial<TextBox>, currentState?: State) => {
|
||||||
if (!model?.actions || !scene?.actions || !currentViewId) return;
|
if (!model?.actions || !scene?.actions || !currentViewId) return currentState || getState();
|
||||||
|
|
||||||
saveToHistoryBeforeChange();
|
if (!transactionInProgress.current) {
|
||||||
|
saveToHistoryBeforeChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
const stateToUse = currentState || getState();
|
||||||
const newState = reducers.view({
|
const newState = reducers.view({
|
||||||
action: 'UPDATE_TEXTBOX',
|
action: 'UPDATE_TEXTBOX',
|
||||||
payload: { id, ...updates },
|
payload: { id, ...updates },
|
||||||
ctx: { viewId: currentViewId, state: getState() }
|
ctx: { viewId: currentViewId, state: stateToUse }
|
||||||
});
|
});
|
||||||
setState(newState);
|
setState(newState);
|
||||||
|
return newState;
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
getState,
|
getState,
|
||||||
@@ -428,16 +433,21 @@ export const useScene = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const updateRectangle = useCallback(
|
const updateRectangle = useCallback(
|
||||||
(id: string, updates: Partial<Rectangle>) => {
|
(id: string, updates: Partial<Rectangle>, currentState?: State) => {
|
||||||
if (!model?.actions || !scene?.actions || !currentViewId) return;
|
if (!model?.actions || !scene?.actions || !currentViewId) return currentState || getState();
|
||||||
|
|
||||||
saveToHistoryBeforeChange();
|
if (!transactionInProgress.current) {
|
||||||
|
saveToHistoryBeforeChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
const stateToUse = currentState || getState();
|
||||||
const newState = reducers.view({
|
const newState = reducers.view({
|
||||||
action: 'UPDATE_RECTANGLE',
|
action: 'UPDATE_RECTANGLE',
|
||||||
payload: { id, ...updates },
|
payload: { id, ...updates },
|
||||||
ctx: { viewId: currentViewId, state: getState() }
|
ctx: { viewId: currentViewId, state: stateToUse }
|
||||||
});
|
});
|
||||||
setState(newState);
|
setState(newState);
|
||||||
|
return newState;
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
getState,
|
getState,
|
||||||
|
|||||||
@@ -17,11 +17,14 @@ const dragItems = (
|
|||||||
delta: Coords,
|
delta: Coords,
|
||||||
scene: ReturnType<typeof useScene>
|
scene: ReturnType<typeof useScene>
|
||||||
) => {
|
) => {
|
||||||
// Separate items from other draggable elements
|
// Separate all item types upfront
|
||||||
const itemRefs = items.filter(item => item.type === 'ITEM');
|
const itemRefs = items.filter(item => item.type === 'ITEM');
|
||||||
const otherRefs = items.filter(item => item.type !== 'ITEM');
|
const textBoxRefs = items.filter(item => item.type === 'TEXTBOX');
|
||||||
|
const rectangleRefs = items.filter(item => item.type === 'RECTANGLE');
|
||||||
|
const anchorRefs = items.filter(item => item.type === 'CONNECTOR_ANCHOR');
|
||||||
|
|
||||||
// If there are items being dragged, find nearest unoccupied tiles for them
|
// Calculate node targets if any nodes are selected
|
||||||
|
let newTiles: Coords[] | null = null;
|
||||||
if (itemRefs.length > 0) {
|
if (itemRefs.length > 0) {
|
||||||
const itemsWithTargets = itemRefs.map(item => {
|
const itemsWithTargets = itemRefs.map(item => {
|
||||||
const node = getItemByIdOrThrow(scene.items, item.id).value;
|
const node = getItemByIdOrThrow(scene.items, item.id).value;
|
||||||
@@ -31,85 +34,93 @@ const dragItems = (
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// Find nearest unoccupied tiles for all items
|
newTiles = findNearestUnoccupiedTilesForGroup(
|
||||||
const newTiles = findNearestUnoccupiedTilesForGroup(
|
|
||||||
itemsWithTargets,
|
itemsWithTargets,
|
||||||
scene,
|
scene,
|
||||||
itemRefs.map(item => item.id) // Exclude the items being dragged
|
itemRefs.map(item => item.id)
|
||||||
);
|
);
|
||||||
|
|
||||||
// If we found valid positions for all items, move them
|
// If nodes can't find valid positions, abort the entire drag operation
|
||||||
if (newTiles) {
|
if (!newTiles) {
|
||||||
// Wrap all updates in a transaction to prevent history issues
|
return;
|
||||||
scene.transaction(() => {
|
}
|
||||||
// Chain state updates to avoid race conditions
|
}
|
||||||
let currentState: State | undefined;
|
|
||||||
|
// Check if there's anything to update
|
||||||
|
const hasUpdates = newTiles || textBoxRefs.length > 0 || rectangleRefs.length > 0;
|
||||||
|
|
||||||
|
if (hasUpdates) {
|
||||||
|
// Wrap ALL updates in a single transaction with state chaining
|
||||||
|
// This ensures each update builds on the previous one's state
|
||||||
|
scene.transaction(() => {
|
||||||
|
let currentState: State | undefined;
|
||||||
|
|
||||||
|
// 1. Update nodes
|
||||||
|
if (newTiles) {
|
||||||
itemRefs.forEach((item, index) => {
|
itemRefs.forEach((item, index) => {
|
||||||
currentState = scene.updateViewItem(item.id, {
|
currentState = scene.updateViewItem(item.id, {
|
||||||
tile: newTiles[index]
|
tile: newTiles[index]
|
||||||
}, currentState);
|
}, currentState);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Update textboxes (chained from node state)
|
||||||
|
textBoxRefs.forEach((item) => {
|
||||||
|
const textBox = getItemByIdOrThrow(scene.textBoxes, item.id).value;
|
||||||
|
currentState = scene.updateTextBox(item.id, {
|
||||||
|
tile: CoordsUtils.add(textBox.tile, delta)
|
||||||
|
}, currentState);
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
// 3. Update rectangles (chained from textbox state)
|
||||||
|
rectangleRefs.forEach((item) => {
|
||||||
|
const rectangle = getItemByIdOrThrow(scene.rectangles, item.id).value;
|
||||||
|
currentState = scene.updateRectangle(item.id, {
|
||||||
|
from: CoordsUtils.add(rectangle.from, delta),
|
||||||
|
to: CoordsUtils.add(rectangle.to, delta)
|
||||||
|
}, currentState);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle non-item references (rectangles, textboxes, connector anchors)
|
// Handle connector anchors separately (they have different update logic)
|
||||||
otherRefs.forEach((item) => {
|
anchorRefs.forEach((item) => {
|
||||||
if (item.type === 'RECTANGLE') {
|
const connector = getAnchorParent(item.id, scene.connectors);
|
||||||
// Skip rectangles if regular items are also being dragged
|
|
||||||
// This is because items use snap-to-grid logic, while rectangles move freely
|
|
||||||
// Moving them together would cause desynchronization
|
|
||||||
if (itemRefs.length > 0) return;
|
|
||||||
|
|
||||||
const rectangle = getItemByIdOrThrow(scene.rectangles, item.id).value;
|
const newConnector = produce(connector, (draft) => {
|
||||||
const newFrom = CoordsUtils.add(rectangle.from, delta);
|
const anchor = getItemByIdOrThrow(connector.anchors, item.id);
|
||||||
const newTo = CoordsUtils.add(rectangle.to, delta);
|
|
||||||
|
|
||||||
scene.updateRectangle(item.id, { from: newFrom, to: newTo });
|
const itemAtTile = getItemAtTile({ tile, scene });
|
||||||
} else if (item.type === 'TEXTBOX') {
|
|
||||||
const textBox = getItemByIdOrThrow(scene.textBoxes, item.id).value;
|
|
||||||
|
|
||||||
scene.updateTextBox(item.id, {
|
switch (itemAtTile?.type) {
|
||||||
tile: CoordsUtils.add(textBox.tile, delta)
|
case 'ITEM':
|
||||||
});
|
draft.anchors[anchor.index] = {
|
||||||
} else if (item.type === 'CONNECTOR_ANCHOR') {
|
...anchor.value,
|
||||||
const connector = getAnchorParent(item.id, scene.connectors);
|
ref: {
|
||||||
|
item: itemAtTile.id
|
||||||
|
}
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case 'CONNECTOR_ANCHOR':
|
||||||
|
draft.anchors[anchor.index] = {
|
||||||
|
...anchor.value,
|
||||||
|
ref: {
|
||||||
|
anchor: itemAtTile.id
|
||||||
|
}
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
draft.anchors[anchor.index] = {
|
||||||
|
...anchor.value,
|
||||||
|
ref: {
|
||||||
|
tile
|
||||||
|
}
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const newConnector = produce(connector, (draft) => {
|
scene.updateConnector(connector.id, newConnector);
|
||||||
const anchor = getItemByIdOrThrow(connector.anchors, item.id);
|
|
||||||
|
|
||||||
const itemAtTile = getItemAtTile({ tile, scene });
|
|
||||||
|
|
||||||
switch (itemAtTile?.type) {
|
|
||||||
case 'ITEM':
|
|
||||||
draft.anchors[anchor.index] = {
|
|
||||||
...anchor.value,
|
|
||||||
ref: {
|
|
||||||
item: itemAtTile.id
|
|
||||||
}
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
case 'CONNECTOR_ANCHOR':
|
|
||||||
draft.anchors[anchor.index] = {
|
|
||||||
...anchor.value,
|
|
||||||
ref: {
|
|
||||||
anchor: itemAtTile.id
|
|
||||||
}
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
draft.anchors[anchor.index] = {
|
|
||||||
...anchor.value,
|
|
||||||
ref: {
|
|
||||||
tile
|
|
||||||
}
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
scene.updateConnector(connector.id, newConnector);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user