mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-24 20:24:05 -08:00
fix(editor): Align undo/redo functionality on new canvas (no-changelog) (#11154)
This commit is contained in:
parent
18c0c8612c
commit
ad4cb0ea0c
|
@ -87,6 +87,8 @@ describe('Canvas', () => {
|
|||
});
|
||||
|
||||
it('should handle `update:nodes:position` event', async () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
const nodes = [createCanvasNodeElement()];
|
||||
const { container, emitted } = renderComponent({
|
||||
props: {
|
||||
|
@ -110,22 +112,9 @@ describe('Canvas', () => {
|
|||
});
|
||||
await fireEvent.mouseUp(node, { view: window });
|
||||
|
||||
vi.advanceTimersByTime(250); // Event debounce time
|
||||
|
||||
expect(emitted()['update:nodes:position']).toEqual([
|
||||
[
|
||||
[
|
||||
{
|
||||
id: '1',
|
||||
type: 'position',
|
||||
dragging: true,
|
||||
from: {
|
||||
x: 100,
|
||||
y: 100,
|
||||
z: 0,
|
||||
},
|
||||
position: { x: 80, y: 80 },
|
||||
},
|
||||
],
|
||||
],
|
||||
[
|
||||
[
|
||||
{
|
||||
|
|
|
@ -29,7 +29,7 @@ import type { PinDataSource } from '@/composables/usePinnedData';
|
|||
import { isPresent } from '@/utils/typesUtils';
|
||||
import { GRID_SIZE } from '@/utils/nodeViewUtils';
|
||||
import { CanvasKey } from '@/constants';
|
||||
import { onKeyDown, onKeyUp } from '@vueuse/core';
|
||||
import { onKeyDown, onKeyUp, useDebounceFn } from '@vueuse/core';
|
||||
import CanvasArrowHeadMarker from './elements/edges/CanvasArrowHeadMarker.vue';
|
||||
|
||||
const $style = useCssModule();
|
||||
|
@ -180,21 +180,22 @@ function onClickNodeAdd(id: string, handle: string) {
|
|||
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);
|
||||
}
|
||||
}, 200);
|
||||
|
||||
function onUpdateNodePosition(id: string, position: XYPosition) {
|
||||
emit('update:node:position', id, position);
|
||||
}
|
||||
|
||||
function onNodesChange(events: NodeChange[]) {
|
||||
const isPositionChangeEvent = (event: NodeChange): event is NodePositionChange =>
|
||||
event.type === 'position' && 'position' in event;
|
||||
const isPositionChangeEvent = (event: NodeChange): event is NodePositionChange =>
|
||||
event.type === 'position' && 'position' in event;
|
||||
|
||||
function onNodesChange(events: NodeChange[]) {
|
||||
const positionChangeEndEvents = events.filter(isPositionChangeEvent);
|
||||
if (positionChangeEndEvents.length > 0) {
|
||||
onUpdateNodesPosition(positionChangeEndEvents);
|
||||
void onUpdateNodesPosition(positionChangeEndEvents);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -164,7 +164,7 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
|
|||
updateNodePosition(id, position, { trackHistory });
|
||||
});
|
||||
|
||||
if (trackBulk) {
|
||||
if (trackHistory && trackBulk) {
|
||||
historyStore.stopRecordingUndo();
|
||||
}
|
||||
}
|
||||
|
@ -198,11 +198,16 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
|
|||
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) {
|
||||
return;
|
||||
}
|
||||
if (trackHistory) {
|
||||
|
||||
if (trackHistory && trackBulk) {
|
||||
historyStore.startRecordingUndo();
|
||||
}
|
||||
|
||||
|
@ -227,7 +232,7 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
|
|||
ndvStore.activeNodeName = newName;
|
||||
}
|
||||
|
||||
if (trackHistory) {
|
||||
if (trackHistory && trackBulk) {
|
||||
historyStore.stopRecordingUndo();
|
||||
}
|
||||
}
|
||||
|
@ -327,10 +332,16 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
|
|||
trackDeleteNode(id);
|
||||
}
|
||||
|
||||
function deleteNodes(ids: string[]) {
|
||||
historyStore.startRecordingUndo();
|
||||
ids.forEach((id) => deleteNode(id, { trackHistory: true, trackBulk: false }));
|
||||
historyStore.stopRecordingUndo();
|
||||
function deleteNodes(ids: string[], { trackHistory = true, trackBulk = true } = {}) {
|
||||
if (trackHistory && trackBulk) {
|
||||
historyStore.startRecordingUndo();
|
||||
}
|
||||
|
||||
ids.forEach((id) => deleteNode(id, { trackHistory, trackBulk: false }));
|
||||
|
||||
if (trackHistory && trackBulk) {
|
||||
historyStore.stopRecordingUndo();
|
||||
}
|
||||
}
|
||||
|
||||
function revertDeleteNode(node: INodeUi) {
|
||||
|
@ -401,18 +412,15 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
|
|||
uiStore.lastSelectedNode = node.name;
|
||||
}
|
||||
|
||||
function toggleNodesDisabled(
|
||||
ids: string[],
|
||||
{ trackHistory = true, trackBulk = true }: { trackHistory?: boolean; trackBulk?: boolean } = {},
|
||||
) {
|
||||
if (trackBulk) {
|
||||
function toggleNodesDisabled(ids: string[], { trackHistory = true, trackBulk = true } = {}) {
|
||||
if (trackHistory && trackBulk) {
|
||||
historyStore.startRecordingUndo();
|
||||
}
|
||||
|
||||
const nodes = workflowsStore.getNodesByIds(ids);
|
||||
nodeHelpers.disableNodes(nodes, trackHistory);
|
||||
nodeHelpers.disableNodes(nodes, { trackHistory, trackBulk: false });
|
||||
|
||||
if (trackBulk) {
|
||||
if (trackHistory && trackBulk) {
|
||||
historyStore.stopRecordingUndo();
|
||||
}
|
||||
}
|
||||
|
@ -424,8 +432,14 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
|
|||
}
|
||||
}
|
||||
|
||||
function toggleNodesPinned(ids: string[], source: PinDataSource) {
|
||||
historyStore.startRecordingUndo();
|
||||
function toggleNodesPinned(
|
||||
ids: string[],
|
||||
source: PinDataSource,
|
||||
{ trackHistory = true, trackBulk = true } = {},
|
||||
) {
|
||||
if (trackHistory && trackBulk) {
|
||||
historyStore.startRecordingUndo();
|
||||
}
|
||||
|
||||
const nodes = workflowsStore.getNodesByIds(ids);
|
||||
const nextStatePinned = nodes.some((node) => !workflowsStore.pinDataByNodeName(node.name));
|
||||
|
@ -442,7 +456,9 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
|
|||
}
|
||||
}
|
||||
|
||||
historyStore.stopRecordingUndo();
|
||||
if (trackHistory && trackBulk) {
|
||||
historyStore.stopRecordingUndo();
|
||||
}
|
||||
}
|
||||
|
||||
function requireNodeTypeDescription(
|
||||
|
@ -478,10 +494,6 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
|
|||
let insertPosition = options.position;
|
||||
let lastAddedNode: INodeUi | undefined;
|
||||
|
||||
if (options.trackBulk) {
|
||||
historyStore.startRecordingUndo();
|
||||
}
|
||||
|
||||
const nodesWithTypeVersion = nodes.map((node) => {
|
||||
const typeVersion =
|
||||
node.typeVersion ?? resolveNodeVersion(requireNodeTypeDescription(node.type));
|
||||
|
@ -493,7 +505,7 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
|
|||
|
||||
await loadNodeTypesProperties(nodesWithTypeVersion);
|
||||
|
||||
if (options.trackBulk) {
|
||||
if (options.trackHistory && options.trackBulk) {
|
||||
historyStore.startRecordingUndo();
|
||||
}
|
||||
|
||||
|
@ -532,7 +544,7 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
|
|||
updatePositionForNodeWithMultipleInputs(lastAddedNode);
|
||||
}
|
||||
|
||||
if (options.trackBulk) {
|
||||
if (options.trackHistory && options.trackBulk) {
|
||||
historyStore.stopRecordingUndo();
|
||||
}
|
||||
|
||||
|
@ -588,11 +600,11 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
|
|||
|
||||
workflowsStore.addNode(nodeData);
|
||||
|
||||
void nextTick(() => {
|
||||
if (options.trackHistory) {
|
||||
historyStore.pushCommandToUndo(new AddNodeCommand(nodeData));
|
||||
}
|
||||
if (options.trackHistory) {
|
||||
historyStore.pushCommandToUndo(new AddNodeCommand(nodeData));
|
||||
}
|
||||
|
||||
void nextTick(() => {
|
||||
workflowsStore.setNodePristine(nodeData.name, true);
|
||||
|
||||
nodeHelpers.matchCredentials(nodeData);
|
||||
|
@ -1376,7 +1388,7 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
|
|||
connections: CanvasConnectionCreateData[] | CanvasConnection[],
|
||||
{ trackBulk = true, trackHistory = false } = {},
|
||||
) {
|
||||
if (trackBulk) {
|
||||
if (trackBulk && trackHistory) {
|
||||
historyStore.startRecordingUndo();
|
||||
}
|
||||
|
||||
|
@ -1404,7 +1416,7 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
|
|||
);
|
||||
}
|
||||
|
||||
if (trackBulk) {
|
||||
if (trackBulk && trackHistory) {
|
||||
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
|
||||
if (trackBulk) {
|
||||
if (trackBulk && trackHistory) {
|
||||
historyStore.startRecordingUndo();
|
||||
}
|
||||
|
||||
await addNodes(Object.values(tempWorkflow.nodes), { trackBulk: false, trackHistory: true });
|
||||
await addNodes(Object.values(tempWorkflow.nodes), { trackBulk: false, trackHistory });
|
||||
addConnections(
|
||||
mapLegacyConnectionsToCanvasConnections(
|
||||
tempWorkflow.connectionsBySourceNode,
|
||||
|
@ -1627,7 +1639,7 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
|
|||
{ trackBulk: false, trackHistory },
|
||||
);
|
||||
|
||||
if (trackBulk) {
|
||||
if (trackBulk && trackHistory) {
|
||||
historyStore.stopRecordingUndo();
|
||||
}
|
||||
|
||||
|
@ -1643,6 +1655,7 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
|
|||
workflowData: IWorkflowDataUpdate,
|
||||
source: string,
|
||||
importTags = true,
|
||||
{ trackBulk = true, trackHistory = true } = {},
|
||||
): Promise<IWorkflowDataUpdate> {
|
||||
uiStore.resetLastInteractedWith();
|
||||
|
||||
|
@ -1731,7 +1744,7 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
|
|||
NodeViewUtils.getNewNodePosition(editableWorkflow.value.nodes, lastClickPosition.value),
|
||||
);
|
||||
|
||||
await addImportedNodesToWorkflow(workflowData);
|
||||
await addImportedNodesToWorkflow(workflowData, { trackBulk, trackHistory });
|
||||
|
||||
if (importTags && settingsStore.areTagsEnabled && Array.isArray(workflowData.tags)) {
|
||||
await importWorkflowTags(workflowData);
|
||||
|
|
|
@ -652,10 +652,10 @@ export function useNodeHelpers() {
|
|||
return returnData;
|
||||
}
|
||||
|
||||
function disableNodes(nodes: INodeUi[], trackHistory = false) {
|
||||
function disableNodes(nodes: INodeUi[], { trackHistory = false, trackBulk = true } = {}) {
|
||||
const telemetry = useTelemetry();
|
||||
|
||||
if (trackHistory) {
|
||||
if (trackHistory && trackBulk) {
|
||||
historyStore.startRecordingUndo();
|
||||
}
|
||||
|
||||
|
@ -690,7 +690,8 @@ export function useNodeHelpers() {
|
|||
);
|
||||
}
|
||||
}
|
||||
if (trackHistory) {
|
||||
|
||||
if (trackHistory && trackBulk) {
|
||||
historyStore.stopRecordingUndo();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1534,7 +1534,7 @@ export default defineComponent({
|
|||
return;
|
||||
}
|
||||
|
||||
this.nodeHelpers.disableNodes(nodes, true);
|
||||
this.nodeHelpers.disableNodes(nodes, { trackHistory: true, trackBulk: true });
|
||||
},
|
||||
|
||||
togglePinNodes(nodes: INode[], source: 'keyboard-shortcut' | 'context-menu') {
|
||||
|
|
Loading…
Reference in a new issue