fix(editor): Align undo/redo functionality on new canvas (no-changelog) (#11154)

This commit is contained in:
Alex Grozav 2024-10-08 16:41:54 +03:00 committed by GitHub
parent 18c0c8612c
commit ad4cb0ea0c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 64 additions and 60 deletions

View file

@ -87,6 +87,8 @@ describe('Canvas', () => {
}); });
it('should handle `update:nodes:position` event', async () => { it('should handle `update:nodes:position` event', async () => {
vi.useFakeTimers();
const nodes = [createCanvasNodeElement()]; const nodes = [createCanvasNodeElement()];
const { container, emitted } = renderComponent({ const { container, emitted } = renderComponent({
props: { props: {
@ -110,22 +112,9 @@ describe('Canvas', () => {
}); });
await fireEvent.mouseUp(node, { view: window }); await fireEvent.mouseUp(node, { view: window });
vi.advanceTimersByTime(250); // Event debounce time
expect(emitted()['update:nodes:position']).toEqual([ expect(emitted()['update:nodes:position']).toEqual([
[
[
{
id: '1',
type: 'position',
dragging: true,
from: {
x: 100,
y: 100,
z: 0,
},
position: { x: 80, y: 80 },
},
],
],
[ [
[ [
{ {

View file

@ -29,7 +29,7 @@ import type { PinDataSource } from '@/composables/usePinnedData';
import { isPresent } from '@/utils/typesUtils'; import { isPresent } from '@/utils/typesUtils';
import { GRID_SIZE } from '@/utils/nodeViewUtils'; import { GRID_SIZE } from '@/utils/nodeViewUtils';
import { CanvasKey } from '@/constants'; import { CanvasKey } from '@/constants';
import { onKeyDown, onKeyUp } from '@vueuse/core'; import { onKeyDown, onKeyUp, useDebounceFn } from '@vueuse/core';
import CanvasArrowHeadMarker from './elements/edges/CanvasArrowHeadMarker.vue'; import CanvasArrowHeadMarker from './elements/edges/CanvasArrowHeadMarker.vue';
const $style = useCssModule(); const $style = useCssModule();
@ -180,21 +180,22 @@ function onClickNodeAdd(id: string, handle: string) {
emit('click:node:add', id, handle); emit('click:node:add', id, handle);
} }
function onUpdateNodesPosition(events: NodePositionChange[]) { // Debounced to prevent emitting too many events, necessary for undo/redo
const onUpdateNodesPosition = useDebounceFn((events: NodePositionChange[]) => {
emit('update:nodes:position', events); emit('update:nodes:position', events);
} }, 200);
function onUpdateNodePosition(id: string, position: XYPosition) { function onUpdateNodePosition(id: string, position: XYPosition) {
emit('update:node:position', id, position); emit('update:node:position', id, position);
} }
function onNodesChange(events: NodeChange[]) { const isPositionChangeEvent = (event: NodeChange): event is NodePositionChange =>
const isPositionChangeEvent = (event: NodeChange): event is NodePositionChange =>
event.type === 'position' && 'position' in event; event.type === 'position' && 'position' in event;
function onNodesChange(events: NodeChange[]) {
const positionChangeEndEvents = events.filter(isPositionChangeEvent); const positionChangeEndEvents = events.filter(isPositionChangeEvent);
if (positionChangeEndEvents.length > 0) { if (positionChangeEndEvents.length > 0) {
onUpdateNodesPosition(positionChangeEndEvents); void onUpdateNodesPosition(positionChangeEndEvents);
} }
} }

View file

@ -164,7 +164,7 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
updateNodePosition(id, position, { trackHistory }); updateNodePosition(id, position, { trackHistory });
}); });
if (trackBulk) { if (trackHistory && trackBulk) {
historyStore.stopRecordingUndo(); historyStore.stopRecordingUndo();
} }
} }
@ -198,11 +198,16 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
updateNodePosition(node.id, position); updateNodePosition(node.id, position);
} }
async function renameNode(currentName: string, newName: string, { trackHistory = false } = {}) { async function renameNode(
currentName: string,
newName: string,
{ trackHistory = false, trackBulk = true } = {},
) {
if (currentName === newName) { if (currentName === newName) {
return; return;
} }
if (trackHistory) {
if (trackHistory && trackBulk) {
historyStore.startRecordingUndo(); historyStore.startRecordingUndo();
} }
@ -227,7 +232,7 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
ndvStore.activeNodeName = newName; ndvStore.activeNodeName = newName;
} }
if (trackHistory) { if (trackHistory && trackBulk) {
historyStore.stopRecordingUndo(); historyStore.stopRecordingUndo();
} }
} }
@ -327,11 +332,17 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
trackDeleteNode(id); trackDeleteNode(id);
} }
function deleteNodes(ids: string[]) { function deleteNodes(ids: string[], { trackHistory = true, trackBulk = true } = {}) {
if (trackHistory && trackBulk) {
historyStore.startRecordingUndo(); historyStore.startRecordingUndo();
ids.forEach((id) => deleteNode(id, { trackHistory: true, trackBulk: false })); }
ids.forEach((id) => deleteNode(id, { trackHistory, trackBulk: false }));
if (trackHistory && trackBulk) {
historyStore.stopRecordingUndo(); historyStore.stopRecordingUndo();
} }
}
function revertDeleteNode(node: INodeUi) { function revertDeleteNode(node: INodeUi) {
workflowsStore.addNode(node); workflowsStore.addNode(node);
@ -401,18 +412,15 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
uiStore.lastSelectedNode = node.name; uiStore.lastSelectedNode = node.name;
} }
function toggleNodesDisabled( function toggleNodesDisabled(ids: string[], { trackHistory = true, trackBulk = true } = {}) {
ids: string[], if (trackHistory && trackBulk) {
{ trackHistory = true, trackBulk = true }: { trackHistory?: boolean; trackBulk?: boolean } = {},
) {
if (trackBulk) {
historyStore.startRecordingUndo(); historyStore.startRecordingUndo();
} }
const nodes = workflowsStore.getNodesByIds(ids); const nodes = workflowsStore.getNodesByIds(ids);
nodeHelpers.disableNodes(nodes, trackHistory); nodeHelpers.disableNodes(nodes, { trackHistory, trackBulk: false });
if (trackBulk) { if (trackHistory && trackBulk) {
historyStore.stopRecordingUndo(); historyStore.stopRecordingUndo();
} }
} }
@ -424,8 +432,14 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
} }
} }
function toggleNodesPinned(ids: string[], source: PinDataSource) { function toggleNodesPinned(
ids: string[],
source: PinDataSource,
{ trackHistory = true, trackBulk = true } = {},
) {
if (trackHistory && trackBulk) {
historyStore.startRecordingUndo(); historyStore.startRecordingUndo();
}
const nodes = workflowsStore.getNodesByIds(ids); const nodes = workflowsStore.getNodesByIds(ids);
const nextStatePinned = nodes.some((node) => !workflowsStore.pinDataByNodeName(node.name)); const nextStatePinned = nodes.some((node) => !workflowsStore.pinDataByNodeName(node.name));
@ -442,8 +456,10 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
} }
} }
if (trackHistory && trackBulk) {
historyStore.stopRecordingUndo(); historyStore.stopRecordingUndo();
} }
}
function requireNodeTypeDescription( function requireNodeTypeDescription(
type: INodeUi['type'], type: INodeUi['type'],
@ -478,10 +494,6 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
let insertPosition = options.position; let insertPosition = options.position;
let lastAddedNode: INodeUi | undefined; let lastAddedNode: INodeUi | undefined;
if (options.trackBulk) {
historyStore.startRecordingUndo();
}
const nodesWithTypeVersion = nodes.map((node) => { const nodesWithTypeVersion = nodes.map((node) => {
const typeVersion = const typeVersion =
node.typeVersion ?? resolveNodeVersion(requireNodeTypeDescription(node.type)); node.typeVersion ?? resolveNodeVersion(requireNodeTypeDescription(node.type));
@ -493,7 +505,7 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
await loadNodeTypesProperties(nodesWithTypeVersion); await loadNodeTypesProperties(nodesWithTypeVersion);
if (options.trackBulk) { if (options.trackHistory && options.trackBulk) {
historyStore.startRecordingUndo(); historyStore.startRecordingUndo();
} }
@ -532,7 +544,7 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
updatePositionForNodeWithMultipleInputs(lastAddedNode); updatePositionForNodeWithMultipleInputs(lastAddedNode);
} }
if (options.trackBulk) { if (options.trackHistory && options.trackBulk) {
historyStore.stopRecordingUndo(); historyStore.stopRecordingUndo();
} }
@ -588,11 +600,11 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
workflowsStore.addNode(nodeData); workflowsStore.addNode(nodeData);
void nextTick(() => {
if (options.trackHistory) { if (options.trackHistory) {
historyStore.pushCommandToUndo(new AddNodeCommand(nodeData)); historyStore.pushCommandToUndo(new AddNodeCommand(nodeData));
} }
void nextTick(() => {
workflowsStore.setNodePristine(nodeData.name, true); workflowsStore.setNodePristine(nodeData.name, true);
nodeHelpers.matchCredentials(nodeData); nodeHelpers.matchCredentials(nodeData);
@ -1376,7 +1388,7 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
connections: CanvasConnectionCreateData[] | CanvasConnection[], connections: CanvasConnectionCreateData[] | CanvasConnection[],
{ trackBulk = true, trackHistory = false } = {}, { trackBulk = true, trackHistory = false } = {},
) { ) {
if (trackBulk) { if (trackBulk && trackHistory) {
historyStore.startRecordingUndo(); historyStore.startRecordingUndo();
} }
@ -1404,7 +1416,7 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
); );
} }
if (trackBulk) { if (trackBulk && trackHistory) {
historyStore.stopRecordingUndo(); historyStore.stopRecordingUndo();
} }
} }
@ -1614,11 +1626,11 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
} }
// Add the nodes with the changed node names, expressions and connections // Add the nodes with the changed node names, expressions and connections
if (trackBulk) { if (trackBulk && trackHistory) {
historyStore.startRecordingUndo(); historyStore.startRecordingUndo();
} }
await addNodes(Object.values(tempWorkflow.nodes), { trackBulk: false, trackHistory: true }); await addNodes(Object.values(tempWorkflow.nodes), { trackBulk: false, trackHistory });
addConnections( addConnections(
mapLegacyConnectionsToCanvasConnections( mapLegacyConnectionsToCanvasConnections(
tempWorkflow.connectionsBySourceNode, tempWorkflow.connectionsBySourceNode,
@ -1627,7 +1639,7 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
{ trackBulk: false, trackHistory }, { trackBulk: false, trackHistory },
); );
if (trackBulk) { if (trackBulk && trackHistory) {
historyStore.stopRecordingUndo(); historyStore.stopRecordingUndo();
} }
@ -1643,6 +1655,7 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
workflowData: IWorkflowDataUpdate, workflowData: IWorkflowDataUpdate,
source: string, source: string,
importTags = true, importTags = true,
{ trackBulk = true, trackHistory = true } = {},
): Promise<IWorkflowDataUpdate> { ): Promise<IWorkflowDataUpdate> {
uiStore.resetLastInteractedWith(); uiStore.resetLastInteractedWith();
@ -1731,7 +1744,7 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
NodeViewUtils.getNewNodePosition(editableWorkflow.value.nodes, lastClickPosition.value), NodeViewUtils.getNewNodePosition(editableWorkflow.value.nodes, lastClickPosition.value),
); );
await addImportedNodesToWorkflow(workflowData); await addImportedNodesToWorkflow(workflowData, { trackBulk, trackHistory });
if (importTags && settingsStore.areTagsEnabled && Array.isArray(workflowData.tags)) { if (importTags && settingsStore.areTagsEnabled && Array.isArray(workflowData.tags)) {
await importWorkflowTags(workflowData); await importWorkflowTags(workflowData);

View file

@ -652,10 +652,10 @@ export function useNodeHelpers() {
return returnData; return returnData;
} }
function disableNodes(nodes: INodeUi[], trackHistory = false) { function disableNodes(nodes: INodeUi[], { trackHistory = false, trackBulk = true } = {}) {
const telemetry = useTelemetry(); const telemetry = useTelemetry();
if (trackHistory) { if (trackHistory && trackBulk) {
historyStore.startRecordingUndo(); historyStore.startRecordingUndo();
} }
@ -690,7 +690,8 @@ export function useNodeHelpers() {
); );
} }
} }
if (trackHistory) {
if (trackHistory && trackBulk) {
historyStore.stopRecordingUndo(); historyStore.stopRecordingUndo();
} }
} }

View file

@ -1534,7 +1534,7 @@ export default defineComponent({
return; return;
} }
this.nodeHelpers.disableNodes(nodes, true); this.nodeHelpers.disableNodes(nodes, { trackHistory: true, trackBulk: true });
}, },
togglePinNodes(nodes: INode[], source: 'keyboard-shortcut' | 'context-menu') { togglePinNodes(nodes: INode[], source: 'keyboard-shortcut' | 'context-menu') {