mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
feat(editor): Add capability to open NDV and rename node (no-changelog) (#9712)
This commit is contained in:
parent
87cb199745
commit
12604fe1da
|
@ -45,23 +45,37 @@ export function createTestNodeTypes(data: INodeTypeData = {}): INodeTypes {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createTestWorkflowObject(options: {
|
export function createTestWorkflowObject({
|
||||||
|
id = uuid(),
|
||||||
|
name = 'Test Workflow',
|
||||||
|
nodes = [],
|
||||||
|
connections = {},
|
||||||
|
active = false,
|
||||||
|
nodeTypes = {},
|
||||||
|
staticData = {},
|
||||||
|
settings = {},
|
||||||
|
pinData = {},
|
||||||
|
}: {
|
||||||
id?: string;
|
id?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
nodes: INode[];
|
nodes?: INode[];
|
||||||
connections: IConnections;
|
connections?: IConnections;
|
||||||
active?: boolean;
|
active?: boolean;
|
||||||
nodeTypes?: INodeTypeData;
|
nodeTypes?: INodeTypeData;
|
||||||
staticData?: IDataObject;
|
staticData?: IDataObject;
|
||||||
settings?: IWorkflowSettings;
|
settings?: IWorkflowSettings;
|
||||||
pinData?: IPinData;
|
pinData?: IPinData;
|
||||||
}) {
|
} = {}) {
|
||||||
return new Workflow({
|
return new Workflow({
|
||||||
...options,
|
id,
|
||||||
id: options.id ?? uuid(),
|
name,
|
||||||
active: options.active ?? false,
|
nodes,
|
||||||
nodeTypes: createTestNodeTypes(options.nodeTypes),
|
connections,
|
||||||
connections: options.connections ?? {},
|
active,
|
||||||
|
staticData,
|
||||||
|
settings,
|
||||||
|
pinData,
|
||||||
|
nodeTypes: createTestNodeTypes(nodeTypes),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ const $style = useCssModule();
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
'update:modelValue': [elements: CanvasElement[]];
|
'update:modelValue': [elements: CanvasElement[]];
|
||||||
'update:node:position': [id: string, position: { x: number; y: number }];
|
'update:node:position': [id: string, position: { x: number; y: number }];
|
||||||
|
'update:node:active': [id: string];
|
||||||
'delete:node': [id: string];
|
'delete:node': [id: string];
|
||||||
'delete:connection': [connection: Connection];
|
'delete:connection': [connection: Connection];
|
||||||
'create:connection': [connection: Connection];
|
'create:connection': [connection: Connection];
|
||||||
|
@ -52,6 +53,10 @@ function onNodeDragStop(e: NodeDragEvent) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onSetNodeActive(id: string) {
|
||||||
|
emit('update:node:active', id);
|
||||||
|
}
|
||||||
|
|
||||||
function onDeleteNode(id: string) {
|
function onDeleteNode(id: string) {
|
||||||
emit('delete:node', id);
|
emit('delete:node', id);
|
||||||
}
|
}
|
||||||
|
@ -97,7 +102,7 @@ function onMouseLeaveEdge(event: EdgeMouseEvent) {
|
||||||
@connect="onConnect"
|
@connect="onConnect"
|
||||||
>
|
>
|
||||||
<template #node-canvas-node="canvasNodeProps">
|
<template #node-canvas-node="canvasNodeProps">
|
||||||
<CanvasNode v-bind="canvasNodeProps" @delete="onDeleteNode" />
|
<CanvasNode v-bind="canvasNodeProps" @delete="onDeleteNode" @activate="onSetNodeActive" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #edge-canvas-edge="canvasEdgeProps">
|
<template #edge-canvas-edge="canvasEdgeProps">
|
||||||
|
|
|
@ -17,6 +17,7 @@ import type { NodeProps } from '@vue-flow/core';
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
delete: [id: string];
|
delete: [id: string];
|
||||||
|
activate: [id: string];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const props = defineProps<NodeProps<CanvasElementData>>();
|
const props = defineProps<NodeProps<CanvasElementData>>();
|
||||||
|
@ -97,6 +98,10 @@ provide(CanvasNodeKey, {
|
||||||
function onDelete() {
|
function onDelete() {
|
||||||
emit('delete', props.id);
|
emit('delete', props.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onActivate() {
|
||||||
|
emit('activate', props.id);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -132,7 +137,7 @@ function onDelete() {
|
||||||
@delete="onDelete"
|
@delete="onDelete"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<CanvasNodeRenderer v-if="nodeType">
|
<CanvasNodeRenderer v-if="nodeType" @dblclick="onActivate">
|
||||||
<NodeIcon :node-type="nodeType" :size="40" :shrink="false" />
|
<NodeIcon :node-type="nodeType" :size="40" :shrink="false" />
|
||||||
<!-- :color-default="iconColorDefault"-->
|
<!-- :color-default="iconColorDefault"-->
|
||||||
<!-- :disabled="data.disabled"-->
|
<!-- :disabled="data.disabled"-->
|
||||||
|
|
|
@ -6,14 +6,16 @@ import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||||
import { useUIStore } from '@/stores/ui.store';
|
import { useUIStore } from '@/stores/ui.store';
|
||||||
import { useHistoryStore } from '@/stores/history.store';
|
import { useHistoryStore } from '@/stores/history.store';
|
||||||
import { createPinia, setActivePinia } from 'pinia';
|
import { createPinia, setActivePinia } from 'pinia';
|
||||||
import { createTestNode } from '@/__tests__/mocks';
|
import { createTestNode, createTestWorkflowObject } from '@/__tests__/mocks';
|
||||||
import type { Connection } from '@vue-flow/core';
|
import type { Connection } from '@vue-flow/core';
|
||||||
import type { IConnection } from 'n8n-workflow';
|
import type { IConnection } from 'n8n-workflow';
|
||||||
import { NodeConnectionType } from 'n8n-workflow';
|
import { NodeConnectionType } from 'n8n-workflow';
|
||||||
|
import { useNDVStore } from '@/stores/ndv.store';
|
||||||
|
|
||||||
describe('useCanvasOperations', () => {
|
describe('useCanvasOperations', () => {
|
||||||
let workflowsStore: ReturnType<typeof useWorkflowsStore>;
|
let workflowsStore: ReturnType<typeof useWorkflowsStore>;
|
||||||
let uiStore: ReturnType<typeof useUIStore>;
|
let uiStore: ReturnType<typeof useUIStore>;
|
||||||
|
let ndvStore: ReturnType<typeof useNDVStore>;
|
||||||
let historyStore: ReturnType<typeof useHistoryStore>;
|
let historyStore: ReturnType<typeof useHistoryStore>;
|
||||||
let canvasOperations: ReturnType<typeof useCanvasOperations>;
|
let canvasOperations: ReturnType<typeof useCanvasOperations>;
|
||||||
|
|
||||||
|
@ -23,6 +25,7 @@ describe('useCanvasOperations', () => {
|
||||||
|
|
||||||
workflowsStore = useWorkflowsStore();
|
workflowsStore = useWorkflowsStore();
|
||||||
uiStore = useUIStore();
|
uiStore = useUIStore();
|
||||||
|
ndvStore = useNDVStore();
|
||||||
historyStore = useHistoryStore();
|
historyStore = useHistoryStore();
|
||||||
canvasOperations = useCanvasOperations();
|
canvasOperations = useCanvasOperations();
|
||||||
});
|
});
|
||||||
|
@ -134,6 +137,93 @@ describe('useCanvasOperations', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('renameNode', () => {
|
||||||
|
it('should rename node', async () => {
|
||||||
|
const oldName = 'Old Node';
|
||||||
|
const newName = 'New Node';
|
||||||
|
|
||||||
|
const workflowObject = createTestWorkflowObject();
|
||||||
|
workflowObject.renameNode = vi.fn();
|
||||||
|
|
||||||
|
vi.spyOn(workflowsStore, 'getCurrentWorkflow').mockReturnValue(workflowObject);
|
||||||
|
|
||||||
|
workflowsStore.getNodeByName = vi.fn().mockReturnValue({ name: oldName });
|
||||||
|
ndvStore.activeNodeName = oldName;
|
||||||
|
|
||||||
|
await canvasOperations.renameNode(oldName, newName);
|
||||||
|
|
||||||
|
expect(workflowObject.renameNode).toHaveBeenCalledWith(oldName, newName);
|
||||||
|
expect(ndvStore.activeNodeName).toBe(newName);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not rename node when new name is same as old name', async () => {
|
||||||
|
const oldName = 'Old Node';
|
||||||
|
workflowsStore.getNodeByName = vi.fn().mockReturnValue({ name: oldName });
|
||||||
|
ndvStore.activeNodeName = oldName;
|
||||||
|
|
||||||
|
await canvasOperations.renameNode(oldName, oldName);
|
||||||
|
|
||||||
|
expect(ndvStore.activeNodeName).toBe(oldName);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('revertRenameNode', () => {
|
||||||
|
it('should revert node renaming', async () => {
|
||||||
|
const oldName = 'Old Node';
|
||||||
|
const currentName = 'New Node';
|
||||||
|
workflowsStore.getNodeByName = vi.fn().mockReturnValue({ name: currentName });
|
||||||
|
ndvStore.activeNodeName = currentName;
|
||||||
|
|
||||||
|
await canvasOperations.revertRenameNode(currentName, oldName);
|
||||||
|
|
||||||
|
expect(ndvStore.activeNodeName).toBe(oldName);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not revert node renaming when old name is same as new name', async () => {
|
||||||
|
const oldName = 'Old Node';
|
||||||
|
workflowsStore.getNodeByName = vi.fn().mockReturnValue({ name: oldName });
|
||||||
|
ndvStore.activeNodeName = oldName;
|
||||||
|
|
||||||
|
await canvasOperations.revertRenameNode(oldName, oldName);
|
||||||
|
|
||||||
|
expect(ndvStore.activeNodeName).toBe(oldName);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('setNodeActive', () => {
|
||||||
|
it('should set active node name when node exists', () => {
|
||||||
|
const nodeId = 'node1';
|
||||||
|
const nodeName = 'Node 1';
|
||||||
|
workflowsStore.getNodeById = vi.fn().mockReturnValue({ name: nodeName });
|
||||||
|
ndvStore.activeNodeName = '';
|
||||||
|
|
||||||
|
canvasOperations.setNodeActive(nodeId);
|
||||||
|
|
||||||
|
expect(ndvStore.activeNodeName).toBe(nodeName);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not change active node name when node does not exist', () => {
|
||||||
|
const nodeId = 'node1';
|
||||||
|
workflowsStore.getNodeById = vi.fn().mockReturnValue(undefined);
|
||||||
|
ndvStore.activeNodeName = 'Existing Node';
|
||||||
|
|
||||||
|
canvasOperations.setNodeActive(nodeId);
|
||||||
|
|
||||||
|
expect(ndvStore.activeNodeName).toBe('Existing Node');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('setNodeActiveByName', () => {
|
||||||
|
it('should set active node name', () => {
|
||||||
|
const nodeName = 'Node 1';
|
||||||
|
ndvStore.activeNodeName = '';
|
||||||
|
|
||||||
|
canvasOperations.setNodeActiveByName(nodeName);
|
||||||
|
|
||||||
|
expect(ndvStore.activeNodeName).toBe(nodeName);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('createConnection', () => {
|
describe('createConnection', () => {
|
||||||
it('should not create a connection if source node does not exist', () => {
|
it('should not create a connection if source node does not exist', () => {
|
||||||
const addConnectionSpy = vi
|
const addConnectionSpy = vi
|
||||||
|
|
|
@ -6,15 +6,22 @@ import { useHistoryStore } from '@/stores/history.store';
|
||||||
import { useUIStore } from '@/stores/ui.store';
|
import { useUIStore } from '@/stores/ui.store';
|
||||||
import { useTelemetry } from '@/composables/useTelemetry';
|
import { useTelemetry } from '@/composables/useTelemetry';
|
||||||
import { useExternalHooks } from '@/composables/useExternalHooks';
|
import { useExternalHooks } from '@/composables/useExternalHooks';
|
||||||
import { MoveNodeCommand, RemoveConnectionCommand, RemoveNodeCommand } from '@/models/history';
|
import {
|
||||||
|
MoveNodeCommand,
|
||||||
|
RemoveConnectionCommand,
|
||||||
|
RemoveNodeCommand,
|
||||||
|
RenameNodeCommand,
|
||||||
|
} from '@/models/history';
|
||||||
import type { Connection } from '@vue-flow/core';
|
import type { Connection } from '@vue-flow/core';
|
||||||
import { mapCanvasConnectionToLegacyConnection } from '@/utils/canvasUtilsV2';
|
import { getUniqueNodeName, mapCanvasConnectionToLegacyConnection } from '@/utils/canvasUtilsV2';
|
||||||
import type { IConnection } from 'n8n-workflow';
|
import type { IConnection } from 'n8n-workflow';
|
||||||
|
import { useNDVStore } from '@/stores/ndv.store';
|
||||||
|
|
||||||
export function useCanvasOperations() {
|
export function useCanvasOperations() {
|
||||||
const workflowsStore = useWorkflowsStore();
|
const workflowsStore = useWorkflowsStore();
|
||||||
const historyStore = useHistoryStore();
|
const historyStore = useHistoryStore();
|
||||||
const uiStore = useUIStore();
|
const uiStore = useUIStore();
|
||||||
|
const ndvStore = useNDVStore();
|
||||||
|
|
||||||
const telemetry = useTelemetry();
|
const telemetry = useTelemetry();
|
||||||
const externalHooks = useExternalHooks();
|
const externalHooks = useExternalHooks();
|
||||||
|
@ -51,6 +58,45 @@ export function useCanvasOperations() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function renameNode(currentName: string, newName: string, { trackHistory = false } = {}) {
|
||||||
|
if (currentName === newName) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trackHistory) {
|
||||||
|
historyStore.startRecordingUndo();
|
||||||
|
}
|
||||||
|
|
||||||
|
newName = getUniqueNodeName(newName, workflowsStore.canvasNames);
|
||||||
|
|
||||||
|
// Rename the node and update the connections
|
||||||
|
const workflow = workflowsStore.getCurrentWorkflow(true);
|
||||||
|
workflow.renameNode(currentName, newName);
|
||||||
|
|
||||||
|
if (trackHistory) {
|
||||||
|
historyStore.pushCommandToUndo(new RenameNodeCommand(currentName, newName));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update also last selected node and execution data
|
||||||
|
workflowsStore.renameNodeSelectedAndExecution({ old: currentName, new: newName });
|
||||||
|
|
||||||
|
workflowsStore.setNodes(Object.values(workflow.nodes));
|
||||||
|
workflowsStore.setConnections(workflow.connectionsBySourceNode);
|
||||||
|
|
||||||
|
const isRenamingActiveNode = ndvStore.activeNodeName === currentName;
|
||||||
|
if (isRenamingActiveNode) {
|
||||||
|
ndvStore.activeNodeName = newName;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trackHistory) {
|
||||||
|
historyStore.stopRecordingUndo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function revertRenameNode(currentName: string, previousName: string) {
|
||||||
|
await renameNode(currentName, previousName);
|
||||||
|
}
|
||||||
|
|
||||||
function deleteNode(id: string, { trackHistory = false, trackBulk = true } = {}) {
|
function deleteNode(id: string, { trackHistory = false, trackBulk = true } = {}) {
|
||||||
const node = workflowsStore.getNodeById(id);
|
const node = workflowsStore.getNodeById(id);
|
||||||
if (!node) {
|
if (!node) {
|
||||||
|
@ -100,6 +146,19 @@ export function useCanvasOperations() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setNodeActive(id: string) {
|
||||||
|
const node = workflowsStore.getNodeById(id);
|
||||||
|
if (!node) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ndvStore.activeNodeName = node.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setNodeActiveByName(name: string) {
|
||||||
|
ndvStore.activeNodeName = name;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Connection operations
|
* Connection operations
|
||||||
*/
|
*/
|
||||||
|
@ -204,6 +263,10 @@ export function useCanvasOperations() {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
updateNodePosition,
|
updateNodePosition,
|
||||||
|
setNodeActive,
|
||||||
|
setNodeActiveByName,
|
||||||
|
renameNode,
|
||||||
|
revertRenameNode,
|
||||||
deleteNode,
|
deleteNode,
|
||||||
revertDeleteNode,
|
revertDeleteNode,
|
||||||
trackDeleteNode,
|
trackDeleteNode,
|
||||||
|
|
|
@ -1,16 +1,41 @@
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { STORES, TRIGGER_NODE_CREATOR_VIEW } from '@/constants';
|
import {
|
||||||
|
AI_NODE_CREATOR_VIEW,
|
||||||
|
NODE_CREATOR_OPEN_SOURCES,
|
||||||
|
REGULAR_NODE_CREATOR_VIEW,
|
||||||
|
STORES,
|
||||||
|
TRIGGER_NODE_CREATOR_VIEW,
|
||||||
|
} from '@/constants';
|
||||||
import type {
|
import type {
|
||||||
NodeFilterType,
|
NodeFilterType,
|
||||||
NodeCreatorOpenSource,
|
NodeCreatorOpenSource,
|
||||||
SimplifiedNodeType,
|
SimplifiedNodeType,
|
||||||
ActionsRecord,
|
ActionsRecord,
|
||||||
|
ToggleNodeCreatorOptions,
|
||||||
|
AIAssistantConnectionInfo,
|
||||||
} from '@/Interface';
|
} from '@/Interface';
|
||||||
|
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import { transformNodeType } from '@/components/Node/NodeCreator/utils';
|
import { transformNodeType } from '@/components/Node/NodeCreator/utils';
|
||||||
|
import type { INodeInputConfiguration } from 'n8n-workflow';
|
||||||
|
import { NodeConnectionType, nodeConnectionTypes, NodeHelpers } from 'n8n-workflow';
|
||||||
|
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||||
|
import { useUIStore } from '@/stores/ui.store';
|
||||||
|
import { useNDVStore } from '@/stores/ndv.store';
|
||||||
|
import { useExternalHooks } from '@/composables/useExternalHooks';
|
||||||
|
import { useTelemetry } from '@/composables/useTelemetry';
|
||||||
|
import { useViewStacks } from '@/components/Node/NodeCreator/composables/useViewStacks';
|
||||||
|
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||||
|
|
||||||
export const useNodeCreatorStore = defineStore(STORES.NODE_CREATOR, () => {
|
export const useNodeCreatorStore = defineStore(STORES.NODE_CREATOR, () => {
|
||||||
|
const workflowsStore = useWorkflowsStore();
|
||||||
|
const ndvStore = useNDVStore();
|
||||||
|
const uiStore = useUIStore();
|
||||||
|
const nodeTypesStore = useNodeTypesStore();
|
||||||
|
|
||||||
|
const externalHooks = useExternalHooks();
|
||||||
|
const telemetry = useTelemetry();
|
||||||
|
|
||||||
const selectedView = ref<NodeFilterType>(TRIGGER_NODE_CREATOR_VIEW);
|
const selectedView = ref<NodeFilterType>(TRIGGER_NODE_CREATOR_VIEW);
|
||||||
const mergedNodes = ref<SimplifiedNodeType[]>([]);
|
const mergedNodes = ref<SimplifiedNodeType[]>([]);
|
||||||
const actions = ref<ActionsRecord<typeof mergedNodes.value>>({});
|
const actions = ref<ActionsRecord<typeof mergedNodes.value>>({});
|
||||||
|
@ -42,6 +67,173 @@ export const useNodeCreatorStore = defineStore(STORES.NODE_CREATOR, () => {
|
||||||
openSource.value = view;
|
openSource.value = view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openNodeCreator({
|
||||||
|
source,
|
||||||
|
createNodeActive,
|
||||||
|
nodeCreatorView,
|
||||||
|
}: ToggleNodeCreatorOptions) {
|
||||||
|
if (createNodeActive === uiStore.isCreateNodeActive) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!nodeCreatorView) {
|
||||||
|
nodeCreatorView =
|
||||||
|
workflowsStore.workflowTriggerNodes.length > 0
|
||||||
|
? REGULAR_NODE_CREATOR_VIEW
|
||||||
|
: TRIGGER_NODE_CREATOR_VIEW;
|
||||||
|
}
|
||||||
|
// Default to the trigger tab in node creator if there's no trigger node yet
|
||||||
|
setSelectedView(nodeCreatorView);
|
||||||
|
|
||||||
|
let mode;
|
||||||
|
switch (selectedView.value) {
|
||||||
|
case AI_NODE_CREATOR_VIEW:
|
||||||
|
mode = 'ai';
|
||||||
|
break;
|
||||||
|
case REGULAR_NODE_CREATOR_VIEW:
|
||||||
|
mode = 'regular';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
mode = 'regular';
|
||||||
|
}
|
||||||
|
|
||||||
|
uiStore.isCreateNodeActive = createNodeActive;
|
||||||
|
if (createNodeActive && source) {
|
||||||
|
setOpenSource(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
void externalHooks.run('nodeView.createNodeActiveChanged', {
|
||||||
|
source,
|
||||||
|
mode,
|
||||||
|
createNodeActive,
|
||||||
|
});
|
||||||
|
|
||||||
|
trackNodesPanelActiveChanged({
|
||||||
|
source,
|
||||||
|
mode,
|
||||||
|
createNodeActive,
|
||||||
|
workflowId: workflowsStore.workflowId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function trackNodesPanelActiveChanged({
|
||||||
|
source,
|
||||||
|
mode,
|
||||||
|
createNodeActive,
|
||||||
|
workflowId,
|
||||||
|
}: {
|
||||||
|
source?: string;
|
||||||
|
mode?: string;
|
||||||
|
createNodeActive?: boolean;
|
||||||
|
workflowId?: string;
|
||||||
|
}) {
|
||||||
|
telemetry.trackNodesPanel('nodeView.createNodeActiveChanged', {
|
||||||
|
source,
|
||||||
|
mode,
|
||||||
|
createNodeActive,
|
||||||
|
workflow_id: workflowId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function openSelectiveNodeCreator({
|
||||||
|
connectionType,
|
||||||
|
node,
|
||||||
|
creatorView,
|
||||||
|
}: {
|
||||||
|
connectionType: NodeConnectionType;
|
||||||
|
node: string;
|
||||||
|
creatorView?: NodeFilterType;
|
||||||
|
}) {
|
||||||
|
const nodeName = node ?? ndvStore.activeNodeName;
|
||||||
|
const nodeData = nodeName ? workflowsStore.getNodeByName(nodeName) : null;
|
||||||
|
|
||||||
|
ndvStore.activeNodeName = null;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
if (creatorView) {
|
||||||
|
openNodeCreator({
|
||||||
|
createNodeActive: true,
|
||||||
|
nodeCreatorView: creatorView,
|
||||||
|
});
|
||||||
|
} else if (connectionType && nodeData) {
|
||||||
|
insertNodeAfterSelected({
|
||||||
|
index: 0,
|
||||||
|
endpointUuid: `${nodeData.id}-input${connectionType}0`,
|
||||||
|
eventSource: NODE_CREATOR_OPEN_SOURCES.NOTICE_ERROR_MESSAGE,
|
||||||
|
outputType: connectionType,
|
||||||
|
sourceId: nodeData.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function insertNodeAfterSelected(info: AIAssistantConnectionInfo) {
|
||||||
|
const type = info.outputType ?? NodeConnectionType.Main;
|
||||||
|
// Get the node and set it as active that new nodes
|
||||||
|
// which get created get automatically connected
|
||||||
|
// to it.
|
||||||
|
const sourceNode = workflowsStore.getNodeById(info.sourceId);
|
||||||
|
if (!sourceNode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uiStore.lastSelectedNode = sourceNode.name;
|
||||||
|
uiStore.lastSelectedNodeEndpointUuid =
|
||||||
|
info.endpointUuid ?? info.connection?.target.jtk?.endpoint.uuid;
|
||||||
|
uiStore.lastSelectedNodeOutputIndex = info.index;
|
||||||
|
// canvasStore.newNodeInsertPosition = null;
|
||||||
|
|
||||||
|
// @TODO Add connection to store
|
||||||
|
// if (info.connection) {
|
||||||
|
// canvasStore.setLastSelectedConnection(info.connection);
|
||||||
|
// }
|
||||||
|
|
||||||
|
openNodeCreator({
|
||||||
|
source: info.eventSource,
|
||||||
|
createNodeActive: true,
|
||||||
|
nodeCreatorView: info.nodeCreatorView,
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: The animation is a bit glitchy because we're updating view stack immediately
|
||||||
|
// after the node creator is opened
|
||||||
|
const isOutput = info.connection?.endpoints[0].parameters.connection === 'source';
|
||||||
|
const isScopedConnection =
|
||||||
|
type !== NodeConnectionType.Main && nodeConnectionTypes.includes(type);
|
||||||
|
|
||||||
|
if (isScopedConnection) {
|
||||||
|
useViewStacks()
|
||||||
|
.gotoCompatibleConnectionView(type, isOutput, getNodeCreatorFilter(sourceNode.name, type))
|
||||||
|
.catch(() => {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNodeCreatorFilter(nodeName: string, outputType?: NodeConnectionType) {
|
||||||
|
let filter;
|
||||||
|
const workflow = workflowsStore.getCurrentWorkflow();
|
||||||
|
const workflowNode = workflow.getNode(nodeName);
|
||||||
|
if (!workflowNode) return { nodes: [] };
|
||||||
|
|
||||||
|
const nodeType = nodeTypesStore.getNodeType(workflowNode?.type, workflowNode.typeVersion);
|
||||||
|
if (nodeType) {
|
||||||
|
const inputs = NodeHelpers.getNodeInputs(workflow, workflowNode, nodeType);
|
||||||
|
|
||||||
|
const filterFound = inputs.filter((input) => {
|
||||||
|
if (typeof input === 'string' || input.type !== outputType || !input.filter) {
|
||||||
|
// No filters defined or wrong connection type
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}) as INodeInputConfiguration[];
|
||||||
|
|
||||||
|
if (filterFound.length) {
|
||||||
|
filter = filterFound[0].filter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return filter;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
openSource,
|
openSource,
|
||||||
selectedView,
|
selectedView,
|
||||||
|
@ -53,6 +245,8 @@ export const useNodeCreatorStore = defineStore(STORES.NODE_CREATOR, () => {
|
||||||
setOpenSource,
|
setOpenSource,
|
||||||
setActions,
|
setActions,
|
||||||
setMergeNodes,
|
setMergeNodes,
|
||||||
|
openNodeCreator,
|
||||||
|
openSelectiveNodeCreator,
|
||||||
allNodeCreatorNodes,
|
allNodeCreatorNodes,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
@ -938,6 +938,14 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setNodes(nodes: INodeUi[]): void {
|
||||||
|
workflow.value.nodes = nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setConnections(connections: IConnections): void {
|
||||||
|
workflow.value.connections = connections;
|
||||||
|
}
|
||||||
|
|
||||||
function resetAllNodesIssues(): boolean {
|
function resetAllNodesIssues(): boolean {
|
||||||
workflow.value.nodes.forEach((node) => {
|
workflow.value.nodes.forEach((node) => {
|
||||||
node.issues = undefined;
|
node.issues = undefined;
|
||||||
|
@ -1650,5 +1658,7 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => {
|
||||||
removeNodeById,
|
removeNodeById,
|
||||||
removeNodeConnectionsById,
|
removeNodeConnectionsById,
|
||||||
removeNodeExecutionDataById,
|
removeNodeExecutionDataById,
|
||||||
|
setNodes,
|
||||||
|
setConnections,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,26 +12,20 @@ import type {
|
||||||
AddedNodesAndConnections,
|
AddedNodesAndConnections,
|
||||||
INodeUi,
|
INodeUi,
|
||||||
ITag,
|
ITag,
|
||||||
|
IUpdateInformation,
|
||||||
|
IWorkflowDataUpdate,
|
||||||
ToggleNodeCreatorOptions,
|
ToggleNodeCreatorOptions,
|
||||||
XYPosition,
|
XYPosition,
|
||||||
} from '@/Interface';
|
} from '@/Interface';
|
||||||
import useWorkflowsEEStore from '@/stores/workflows.ee.store';
|
|
||||||
import { useTagsStore } from '@/stores/tags.store';
|
import { useTagsStore } from '@/stores/tags.store';
|
||||||
import type { Connection } from '@vue-flow/core';
|
import type { Connection } from '@vue-flow/core';
|
||||||
import type { CanvasElement } from '@/types';
|
import type { CanvasElement } from '@/types';
|
||||||
import {
|
import { EnterpriseEditionFeature, VIEWS } from '@/constants';
|
||||||
EnterpriseEditionFeature,
|
|
||||||
AI_NODE_CREATOR_VIEW,
|
|
||||||
REGULAR_NODE_CREATOR_VIEW,
|
|
||||||
TRIGGER_NODE_CREATOR_VIEW,
|
|
||||||
VIEWS,
|
|
||||||
} from '@/constants';
|
|
||||||
import { useSourceControlStore } from '@/stores/sourceControl.store';
|
import { useSourceControlStore } from '@/stores/sourceControl.store';
|
||||||
import { useNodeCreatorStore } from '@/stores/nodeCreator.store';
|
import { useNodeCreatorStore } from '@/stores/nodeCreator.store';
|
||||||
import { useTelemetry } from '@/composables/useTelemetry';
|
|
||||||
import { useExternalHooks } from '@/composables/useExternalHooks';
|
import { useExternalHooks } from '@/composables/useExternalHooks';
|
||||||
import * as NodeViewUtils from '@/utils/nodeViewUtils';
|
import * as NodeViewUtils from '@/utils/nodeViewUtils';
|
||||||
import type { IConnection, INodeTypeDescription } from 'n8n-workflow';
|
import type { ExecutionSummary, IConnection, INodeTypeDescription } from 'n8n-workflow';
|
||||||
import { NodeConnectionType } from 'n8n-workflow';
|
import { NodeConnectionType } from 'n8n-workflow';
|
||||||
import { useToast } from '@/composables/useToast';
|
import { useToast } from '@/composables/useToast';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
@ -44,24 +38,27 @@ import { useCollaborationStore } from '@/stores/collaboration.store';
|
||||||
import { getUniqueNodeName } from '@/utils/canvasUtilsV2';
|
import { getUniqueNodeName } from '@/utils/canvasUtilsV2';
|
||||||
import { historyBus } from '@/models/history';
|
import { historyBus } from '@/models/history';
|
||||||
import { useCanvasOperations } from '@/composables/useCanvasOperations';
|
import { useCanvasOperations } from '@/composables/useCanvasOperations';
|
||||||
|
import { useExecutionsStore } from '@/stores/executions.store';
|
||||||
|
|
||||||
const NodeCreation = defineAsyncComponent(
|
const NodeCreation = defineAsyncComponent(
|
||||||
async () => await import('@/components/Node/NodeCreation.vue'),
|
async () => await import('@/components/Node/NodeCreation.vue'),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const NodeDetailsView = defineAsyncComponent(
|
||||||
|
async () => await import('@/components/NodeDetailsView.vue'),
|
||||||
|
);
|
||||||
|
|
||||||
const $style = useCssModule();
|
const $style = useCssModule();
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const telemetry = useTelemetry();
|
|
||||||
const externalHooks = useExternalHooks();
|
const externalHooks = useExternalHooks();
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
|
||||||
const nodeTypesStore = useNodeTypesStore();
|
const nodeTypesStore = useNodeTypesStore();
|
||||||
const uiStore = useUIStore();
|
const uiStore = useUIStore();
|
||||||
const workflowsStore = useWorkflowsStore();
|
const workflowsStore = useWorkflowsStore();
|
||||||
const workflowsEEStore = useWorkflowsEEStore();
|
|
||||||
const tagsStore = useTagsStore();
|
const tagsStore = useTagsStore();
|
||||||
const sourceControlStore = useSourceControlStore();
|
const sourceControlStore = useSourceControlStore();
|
||||||
const nodeCreatorStore = useNodeCreatorStore();
|
const nodeCreatorStore = useNodeCreatorStore();
|
||||||
|
@ -71,20 +68,31 @@ const environmentsStore = useEnvironmentsStore();
|
||||||
const externalSecretsStore = useExternalSecretsStore();
|
const externalSecretsStore = useExternalSecretsStore();
|
||||||
const rootStore = useRootStore();
|
const rootStore = useRootStore();
|
||||||
const collaborationStore = useCollaborationStore();
|
const collaborationStore = useCollaborationStore();
|
||||||
|
const executionsStore = useExecutionsStore();
|
||||||
|
|
||||||
const { runWorkflow } = useRunWorkflow({ router });
|
const { runWorkflow } = useRunWorkflow({ router });
|
||||||
const {
|
const {
|
||||||
updateNodePosition,
|
updateNodePosition,
|
||||||
|
renameNode,
|
||||||
|
revertRenameNode,
|
||||||
|
setNodeActive,
|
||||||
deleteNode,
|
deleteNode,
|
||||||
revertDeleteNode,
|
revertDeleteNode,
|
||||||
createConnection,
|
createConnection,
|
||||||
deleteConnection,
|
deleteConnection,
|
||||||
revertDeleteConnection,
|
revertDeleteConnection,
|
||||||
|
setNodeActiveByName,
|
||||||
} = useCanvasOperations();
|
} = useCanvasOperations();
|
||||||
|
|
||||||
const isLoading = ref(true);
|
const isLoading = ref(true);
|
||||||
const readOnlyNotification = ref<null | { visible: boolean }>(null);
|
const readOnlyNotification = ref<null | { visible: boolean }>(null);
|
||||||
|
|
||||||
|
const isProductionExecutionPreview = ref(false);
|
||||||
|
const isExecutionPreview = ref(false);
|
||||||
|
|
||||||
|
const canOpenNDV = ref(true);
|
||||||
|
const hideNodeIssues = ref(false);
|
||||||
|
|
||||||
const workflowId = computed<string>(() => route.params.workflowId as string);
|
const workflowId = computed<string>(() => route.params.workflowId as string);
|
||||||
const workflow = computed(() => workflowsStore.workflowsById[workflowId.value]);
|
const workflow = computed(() => workflowsStore.workflowsById[workflowId.value]);
|
||||||
|
|
||||||
|
@ -151,6 +159,7 @@ async function initialize() {
|
||||||
initializeEditableWorkflow(workflowId.value);
|
initializeEditableWorkflow(workflowId.value);
|
||||||
|
|
||||||
addUndoRedoEventBindings();
|
addUndoRedoEventBindings();
|
||||||
|
addPostMessageEventBindings();
|
||||||
|
|
||||||
if (window.parent) {
|
if (window.parent) {
|
||||||
window.parent.postMessage(
|
window.parent.postMessage(
|
||||||
|
@ -163,6 +172,7 @@ async function initialize() {
|
||||||
}
|
}
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
|
removePostMessageEventBindings();
|
||||||
removeUndoRedoEventBindings();
|
removeUndoRedoEventBindings();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -172,7 +182,7 @@ function addUndoRedoEventBindings() {
|
||||||
historyBus.on('revertRemoveNode', onRevertDeleteNode);
|
historyBus.on('revertRemoveNode', onRevertDeleteNode);
|
||||||
// historyBus.on('revertAddConnection', onRevertAddConnection);
|
// historyBus.on('revertAddConnection', onRevertAddConnection);
|
||||||
historyBus.on('revertRemoveConnection', onRevertDeleteConnection);
|
historyBus.on('revertRemoveConnection', onRevertDeleteConnection);
|
||||||
// historyBus.on('revertRenameNode', onRevertNameChange);
|
historyBus.on('revertRenameNode', onRevertRenameNode);
|
||||||
// historyBus.on('enableNodeToggle', onRevertEnableToggle);
|
// historyBus.on('enableNodeToggle', onRevertEnableToggle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -182,10 +192,18 @@ function removeUndoRedoEventBindings() {
|
||||||
historyBus.off('revertRemoveNode', onRevertDeleteNode);
|
historyBus.off('revertRemoveNode', onRevertDeleteNode);
|
||||||
// historyBus.off('revertAddConnection', onRevertAddConnection);
|
// historyBus.off('revertAddConnection', onRevertAddConnection);
|
||||||
historyBus.off('revertRemoveConnection', onRevertDeleteConnection);
|
historyBus.off('revertRemoveConnection', onRevertDeleteConnection);
|
||||||
// historyBus.off('revertRenameNode', onRevertNameChange);
|
historyBus.off('revertRenameNode', onRevertRenameNode);
|
||||||
// historyBus.off('enableNodeToggle', onRevertEnableToggle);
|
// historyBus.off('enableNodeToggle', onRevertEnableToggle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addPostMessageEventBindings() {
|
||||||
|
window.addEventListener('message', onPostMessageReceived);
|
||||||
|
}
|
||||||
|
|
||||||
|
function removePostMessageEventBindings() {
|
||||||
|
window.removeEventListener('message', onPostMessageReceived);
|
||||||
|
}
|
||||||
|
|
||||||
// @TODO Maybe move this to the store
|
// @TODO Maybe move this to the store
|
||||||
function initializeEditableWorkflow(id: string) {
|
function initializeEditableWorkflow(id: string) {
|
||||||
const targetWorkflow = workflowsStore.workflowsById[id];
|
const targetWorkflow = workflowsStore.workflowsById[id];
|
||||||
|
@ -245,11 +263,16 @@ function onRevertDeleteNode({ node }: { node: INodeUi }) {
|
||||||
revertDeleteNode(node);
|
revertDeleteNode(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onSetNodeActive(id: string) {
|
||||||
|
setNodeActive(id);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map new node connection format to the old one and add it to the store
|
* Map new node connection format to the old one and add it to the store
|
||||||
*
|
*
|
||||||
* @param connection
|
* @param connection
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function onCreateConnection(connection: Connection) {
|
function onCreateConnection(connection: Connection) {
|
||||||
createConnection(connection);
|
createConnection(connection);
|
||||||
}
|
}
|
||||||
|
@ -262,53 +285,6 @@ function onRevertDeleteConnection({ connection }: { connection: [IConnection, IC
|
||||||
revertDeleteConnection(connection);
|
revertDeleteConnection(connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onToggleNodeCreator({
|
|
||||||
source,
|
|
||||||
createNodeActive,
|
|
||||||
nodeCreatorView,
|
|
||||||
}: ToggleNodeCreatorOptions) {
|
|
||||||
if (createNodeActive === uiStore.isCreateNodeActive) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!nodeCreatorView) {
|
|
||||||
nodeCreatorView =
|
|
||||||
triggerNodes.value.length > 0 ? REGULAR_NODE_CREATOR_VIEW : TRIGGER_NODE_CREATOR_VIEW;
|
|
||||||
}
|
|
||||||
// Default to the trigger tab in node creator if there's no trigger node yet
|
|
||||||
nodeCreatorStore.setSelectedView(nodeCreatorView);
|
|
||||||
|
|
||||||
let mode;
|
|
||||||
switch (nodeCreatorStore.selectedView) {
|
|
||||||
case AI_NODE_CREATOR_VIEW:
|
|
||||||
mode = 'ai';
|
|
||||||
break;
|
|
||||||
case REGULAR_NODE_CREATOR_VIEW:
|
|
||||||
mode = 'regular';
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
mode = 'regular';
|
|
||||||
}
|
|
||||||
|
|
||||||
uiStore.isCreateNodeActive = createNodeActive;
|
|
||||||
if (createNodeActive && source) {
|
|
||||||
nodeCreatorStore.setOpenSource(source);
|
|
||||||
}
|
|
||||||
|
|
||||||
void externalHooks.run('nodeView.createNodeActiveChanged', {
|
|
||||||
source,
|
|
||||||
mode,
|
|
||||||
createNodeActive,
|
|
||||||
});
|
|
||||||
|
|
||||||
telemetry.trackNodesPanel('nodeView.createNodeActiveChanged', {
|
|
||||||
source,
|
|
||||||
mode,
|
|
||||||
createNodeActive,
|
|
||||||
workflow_id: workflowId.value,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function onAddNodes(
|
async function onAddNodes(
|
||||||
{ nodes, connections }: AddedNodesAndConnections,
|
{ nodes, connections }: AddedNodesAndConnections,
|
||||||
dragAndDrop = false,
|
dragAndDrop = false,
|
||||||
|
@ -355,23 +331,21 @@ async function onAddNodes(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// @TODO Implement this
|
const lastAddedNode = editableWorkflow.value.nodes[editableWorkflow.value.nodes.length - 1];
|
||||||
// const lastAddedNode = editableWorkflow.value.nodes[editableWorkflow.value.nodes.length - 1];
|
const lastNodeInputs = editableWorkflowObject.value.getParentNodesByDepth(lastAddedNode.name, 1);
|
||||||
// const workflow = editableWorkflowObject.value;
|
|
||||||
// const lastNodeInputs = workflow.getParentNodesByDepth(lastAddedNode.name, 1);
|
// If the last added node has multiple inputs, move them down
|
||||||
//
|
if (lastNodeInputs.length > 1) {
|
||||||
// // If the last added node has multiple inputs, move them down
|
lastNodeInputs.slice(1).forEach((node, index) => {
|
||||||
// if (lastNodeInputs.length > 1) {
|
const nodeUi = workflowsStore.getNodeByName(node.name);
|
||||||
// lastNodeInputs.slice(1).forEach((node, index) => {
|
if (!nodeUi) return;
|
||||||
// const nodeUi = workflowsStore.getNodeByName(node.name);
|
|
||||||
// if (!nodeUi) return;
|
updateNodePosition(nodeUi.id, {
|
||||||
//
|
x: nodeUi.position[0],
|
||||||
// // onMoveNode({
|
y: nodeUi.position[1] + 100 * (index + 1),
|
||||||
// // nodeName: nodeUi.name,
|
});
|
||||||
// // position: [nodeUi.position[0], nodeUi.position[1] + 100 * (index + 1)],
|
});
|
||||||
// // });
|
}
|
||||||
// });
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type AddNodeData = {
|
type AddNodeData = {
|
||||||
|
@ -408,6 +382,7 @@ async function onNodeCreate(node: AddNodeData, _options: AddNodeOptions = {}): P
|
||||||
// @TODO Figure out why this is needed and if we can do better...
|
// @TODO Figure out why this is needed and if we can do better...
|
||||||
// this.matchCredentials(node);
|
// this.matchCredentials(node);
|
||||||
|
|
||||||
|
// @TODO Connect added node to last selected node
|
||||||
// const lastSelectedNode = uiStore.getLastSelectedNode;
|
// const lastSelectedNode = uiStore.getLastSelectedNode;
|
||||||
// const lastSelectedNodeOutputIndex = uiStore.lastSelectedNodeOutputIndex;
|
// const lastSelectedNodeOutputIndex = uiStore.lastSelectedNodeOutputIndex;
|
||||||
// const lastSelectedNodeEndpointUuid = uiStore.lastSelectedNodeEndpointUuid;
|
// const lastSelectedNodeEndpointUuid = uiStore.lastSelectedNodeEndpointUuid;
|
||||||
|
@ -496,7 +471,7 @@ async function createNodeWithDefaultCredentials(node: Partial<INodeUi>) {
|
||||||
) as INodeTypeDescription;
|
) as INodeTypeDescription;
|
||||||
|
|
||||||
let nodeVersion = nodeTypeDescription.defaultVersion;
|
let nodeVersion = nodeTypeDescription.defaultVersion;
|
||||||
if (nodeVersion === undefined) {
|
if (typeof nodeVersion === 'undefined') {
|
||||||
nodeVersion = Array.isArray(nodeTypeDescription.version)
|
nodeVersion = Array.isArray(nodeTypeDescription.version)
|
||||||
? nodeTypeDescription.version.slice(-1)[0]
|
? nodeTypeDescription.version.slice(-1)[0]
|
||||||
: nodeTypeDescription.version;
|
: nodeTypeDescription.version;
|
||||||
|
@ -529,7 +504,7 @@ async function createNodeWithDefaultCredentials(node: Partial<INodeUi>) {
|
||||||
// );
|
// );
|
||||||
// } catch (e) {
|
// } catch (e) {
|
||||||
// console.error(
|
// console.error(
|
||||||
// this.$locale.baseText('nodeView.thereWasAProblemLoadingTheNodeParametersOfNode') +
|
// i18n.baseText('nodeView.thereWasAProblemLoadingTheNodeParametersOfNode') +
|
||||||
// `: "${node.name}"`,
|
// `: "${node.name}"`,
|
||||||
// );
|
// );
|
||||||
// console.error(e);
|
// console.error(e);
|
||||||
|
@ -654,8 +629,8 @@ async function injectNode(
|
||||||
//
|
//
|
||||||
// if (nodeTypeData === null) {
|
// if (nodeTypeData === null) {
|
||||||
// this.showMessage({
|
// this.showMessage({
|
||||||
// title: this.$locale.baseText('nodeView.showMessage.addNodeButton.title'),
|
// title: i18n.baseText('nodeView.showMessage.addNodeButton.title'),
|
||||||
// message: this.$locale.baseText('nodeView.showMessage.addNodeButton.message', {
|
// message: i18n.baseText('nodeView.showMessage.addNodeButton.message', {
|
||||||
// interpolate: { nodeTypeName },
|
// interpolate: { nodeTypeName },
|
||||||
// }),
|
// }),
|
||||||
// type: 'error',
|
// type: 'error',
|
||||||
|
@ -897,6 +872,106 @@ function checkIfEditingIsAllowed(): boolean {
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function onPostMessageReceived(message: MessageEvent) {
|
||||||
|
if (!message || typeof message.data !== 'string' || !message.data?.includes?.('"command"')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const json = JSON.parse(message.data);
|
||||||
|
if (json && json.command === 'openWorkflow') {
|
||||||
|
try {
|
||||||
|
await importWorkflowExact(json.data);
|
||||||
|
canOpenNDV.value = json.canOpenNDV ?? true;
|
||||||
|
hideNodeIssues.value = json.hideNodeIssues ?? false;
|
||||||
|
isExecutionPreview.value = false;
|
||||||
|
} catch (e) {
|
||||||
|
if (window.top) {
|
||||||
|
window.top.postMessage(
|
||||||
|
JSON.stringify({
|
||||||
|
command: 'error',
|
||||||
|
message: i18n.baseText('openWorkflow.workflowImportError'),
|
||||||
|
}),
|
||||||
|
'*',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
toast.showMessage({
|
||||||
|
title: i18n.baseText('openWorkflow.workflowImportError'),
|
||||||
|
message: (e as Error).message,
|
||||||
|
type: 'error',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (json && json.command === 'openExecution') {
|
||||||
|
try {
|
||||||
|
// If this NodeView is used in preview mode (in iframe) it will not have access to the main app store
|
||||||
|
// so everything it needs has to be sent using post messages and passed down to child components
|
||||||
|
isProductionExecutionPreview.value = json.executionMode !== 'manual';
|
||||||
|
|
||||||
|
await openExecution(json.executionId);
|
||||||
|
canOpenNDV.value = json.canOpenNDV ?? true;
|
||||||
|
hideNodeIssues.value = json.hideNodeIssues ?? false;
|
||||||
|
isExecutionPreview.value = true;
|
||||||
|
} catch (e) {
|
||||||
|
if (window.top) {
|
||||||
|
window.top.postMessage(
|
||||||
|
JSON.stringify({
|
||||||
|
command: 'error',
|
||||||
|
message: i18n.baseText('nodeView.showError.openExecution.title'),
|
||||||
|
}),
|
||||||
|
'*',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
toast.showMessage({
|
||||||
|
title: i18n.baseText('nodeView.showError.openExecution.title'),
|
||||||
|
message: (e as Error).message,
|
||||||
|
type: 'error',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (json?.command === 'setActiveExecution') {
|
||||||
|
executionsStore.activeExecution = (await executionsStore.fetchExecution(
|
||||||
|
json.executionId,
|
||||||
|
)) as ExecutionSummary;
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onSwitchSelectedNode(nodeName: string) {
|
||||||
|
setNodeActiveByName(nodeName);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onOpenConnectionNodeCreator(node: string, connectionType: NodeConnectionType) {
|
||||||
|
nodeCreatorStore.openSelectiveNodeCreator({ node, connectionType });
|
||||||
|
}
|
||||||
|
|
||||||
|
function onToggleNodeCreator(options: ToggleNodeCreatorOptions) {
|
||||||
|
nodeCreatorStore.openNodeCreator(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function openExecution(_executionId: string) {
|
||||||
|
// @TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
async function importWorkflowExact(_workflow: IWorkflowDataUpdate) {
|
||||||
|
// @TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onRevertRenameNode({
|
||||||
|
currentName,
|
||||||
|
newName,
|
||||||
|
}: {
|
||||||
|
currentName: string;
|
||||||
|
newName: string;
|
||||||
|
}) {
|
||||||
|
await revertRenameNode(currentName, newName);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onUpdateNodeValue(parameterData: IUpdateInformation) {
|
||||||
|
if (parameterData.name === 'name' && parameterData.oldValue) {
|
||||||
|
// The name changed so we have to take care that
|
||||||
|
// the connections get changed.
|
||||||
|
void renameNode(parameterData.oldValue as string, parameterData.value as string);
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -905,6 +980,7 @@ function checkIfEditingIsAllowed(): boolean {
|
||||||
:workflow="editableWorkflow"
|
:workflow="editableWorkflow"
|
||||||
:workflow-object="editableWorkflowObject"
|
:workflow-object="editableWorkflowObject"
|
||||||
@update:node:position="onUpdateNodePosition"
|
@update:node:position="onUpdateNodePosition"
|
||||||
|
@update:node:active="onSetNodeActive"
|
||||||
@delete:node="onDeleteNode"
|
@delete:node="onDeleteNode"
|
||||||
@create:connection="onCreateConnection"
|
@create:connection="onCreateConnection"
|
||||||
@delete:connection="onDeleteConnection"
|
@delete:connection="onDeleteConnection"
|
||||||
|
@ -921,6 +997,20 @@ function checkIfEditingIsAllowed(): boolean {
|
||||||
@add-nodes="onAddNodes"
|
@add-nodes="onAddNodes"
|
||||||
/>
|
/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
<Suspense>
|
||||||
|
<NodeDetailsView
|
||||||
|
:read-only="isReadOnlyRoute || isReadOnlyEnvironment"
|
||||||
|
:is-production-execution-preview="isProductionExecutionPreview"
|
||||||
|
@value-changed="onUpdateNodeValue"
|
||||||
|
@switch-selected-node="onSwitchSelectedNode"
|
||||||
|
@open-connection-node-creator="onOpenConnectionNodeCreator"
|
||||||
|
/>
|
||||||
|
<!--
|
||||||
|
:renaming="renamingActive"
|
||||||
|
@stop-execution="stopExecution"
|
||||||
|
@save-keyboard-shortcut="onSaveKeyboardShortcut"
|
||||||
|
-->
|
||||||
|
</Suspense>
|
||||||
</WorkflowCanvas>
|
</WorkflowCanvas>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue