2922 lines
119 KiB
JavaScript
Executable File
2922 lines
119 KiB
JavaScript
Executable File
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/NodeCreation-iNUuiza-.js","assets/index--OJ5nhDf.js","assets/index-DXNU_2Fk.css","assets/NodeCreation-C4gQlPqI.css","assets/NodeDetailsView-BKEGFeZ7.js","assets/TriggerPanel-C-c5cPsc.js","assets/RunDataParsedAiContent-Byf4f3hM.js","assets/core-Br-UFy15.js","assets/RunDataParsedAiContent-wfIiKsq7.css","assets/ConsumedTokensDetails.vue_vue_type_script_setup_true_lang-CSmXlf80.js","assets/InfoAccordion-u7XlbH6a.js","assets/InfoAccordion-dxudNqVC.css","assets/TriggerPanel-DiD8pi0I.css","assets/useWorkflowActivate-7Rw9KyzM.js","assets/global-link-actions--TiC75iP.js","assets/useExecutionDebugging-Bve-aaKO.js","assets/useBeforeUnload-ZtUpNFCu.js","assets/canvas-DbK7UyVG.js","assets/readyToRunWorkflows.store-Dhb8bhvk.js","assets/NodeDetailsView-CjdovDgq.css","assets/NodeDetailsViewV2-XPdbzrLu.js","assets/NodeDetailsViewV2-J_3cfdea.css","assets/SetupWorkflowCredentialsButton-DMIEMB5C.js"])))=>i.map(i=>d[i]);
|
|
import { d as defineComponent, aq as createEventBus, as as useCssModule, cx as useVueFlow, b_ as toRef, x as computed, cy as useCanvasMapping, r as ref, cz as refThrottled, h as createElementBlock, g as openBlock, n as normalizeClass, l as unref, j as createBaseVNode, X as renderSlot, e as createBlock, f as createCommentVNode, cA as Canvas, K as mergeProps, _ as _export_sfc, a1 as useWorkflowsStore, cB as useExpressionResolveCtx, p as N8nText, w as withCtx, k as createTextVNode, t as toDisplayString, cC as _sfc_main$b, i as createVNode, aa as _sfc_main$c, bO as provide, cD as ExpressionLocalResolveContextSymbol, ad as useNodeTypesStore, bX as NodeIcon, F as Fragment, cE as _sfc_main$d, aG as useTemplateRef, bL as useNodeHelpers, cF as useFocusPanelStore, cG as useNodeSettingsParameters, cH as useEnvironmentsStore, cI as useExperimentalNdvStore, aY as useNDVStore, cJ as useDeviceSupport, cv as useActiveElement, cK as useTelemetryContext, cL as HTML_NODE_TYPE, cM as isValueExpression, cN as isResourceLocatorValue, cO as AI_TRANSFORM_NODE_TYPE, cP as useResolvedExpression, a7 as watch, cl as useThrottleFn, B as withModifiers, N as N8nIcon, cQ as InfoTip, c as useI18n, cR as __unplugin_components_2, cS as __unplugin_components_3, cT as __unplugin_components_4, cU as __unplugin_components_5, cV as __unplugin_components_6, cW as __unplugin_components_7, cX as __unplugin_components_8, cY as __unplugin_components_9, cZ as N8nInput, O as N8nRadioButtons, ap as normalizeStyle, ch as N8nResizeWrapper, c_ as getParameterTypeOption, c$ as htmlEditorEventBus, d0 as parseFromExpression, d1 as isValidParameterOption, d2 as formatAsExpression, al as useTelemetry, Y as nextTick, d3 as hasFocusOnInput, d4 as isFocusableEl, bB as isChatNode, d5 as truncateBeforeLast, q as N8nButton, ab as I18nT, cg as KeyboardShortcutTooltip, aM as N8nActionDropdown, d6 as reactive, o as onMounted, c7 as onUnmounted, d7 as _sfc_main$e, D as useI18n$1, b as useRouter, a2 as useRoute, a as useToast, ax as useDocumentTitle, az as useWorkflowHelpers, ay as useWorkflowSaving, Q as useUIStore, af as useSourceControlStore, d8 as useNodeCreatorStore, v as useSettingsStore, d9 as useCredentialsStore, da as useExternalSecretsStore, at as useRootStore, aZ as useExecutionsStore, co as useCanvasStore, aw as useNpsSurveyStore, db as useHistoryStore, au as useProjectsStore, u as useUsersStore, ar as useTagsStore, a0 as usePushConnectionStore, bk as useTemplatesStore, dc as useBuilderStore, av as useFoldersStore, dd as usePostHog, de as useAgentRequestStore, bK as useLogsStore, bM as useRunWorkflow, bP as useCanvasOperations, df as useWorkflowExtraction, ca as useClipboard, bn as useKeybindings, bu as ABOUT_MODAL_KEY, a3 as PLACEHOLDER_EMPTY_WORKFLOW_ID, dg as NEW_WORKFLOW_ID, V as VIEWS, dh as NDV_UI_OVERHAUL_EXPERIMENT, bQ as START_NODE_TYPE, di as getNodeViewTab, L as MAIN_HEADER_TABS, dj as VALID_WORKFLOW_IMPORT_URL_REGEX, am as useMessage, an as MODAL_CONFIRM, dk as jsonParse, aB as getResourcePermissions, bH as CHAT_TRIGGER_NODE_TYPE, bG as MANUAL_CHAT_TRIGGER_NODE_TYPE, dl as EVALUATION_TRIGGER_NODE_TYPE, dm as getBounds, dn as onBeforeRouteLeave, b1 as onBeforeMount, ak as WORKFLOW_SETTINGS_MODAL_KEY, bp as useExternalHooks, dp as onActivated, dq as onDeactivated, W as onBeforeUnmount, dr as Suspense, ds as defineAsyncComponent, z as N8nCallout, aU as __vitePreload, aE as EnterpriseEditionFeature, dt as NODE_CREATOR_OPEN_SOURCES, du as EVALUATION_NODE_TYPE, dv as getSampleWorkflowByTemplateId, dw as tryToParseNumber, aO as nodeViewEventBus, bC as NodeConnectionTypes, dx as createCanvasConnectionHandleString, dy as CanvasConnectionMode, dz as isValidNodeConnectionType, dA as sourceControlEventBus, dB as getNodesWithNormalizedPosition, aW as h, dC as CanvasNodeRenderType, dD as shouldIgnoreCanvasShortcut, b0 as STICKY_NODE_TYPE, dE as needsAgentInput, dF as FROM_AI_PARAMETERS_MODAL_KEY, dG as historyBus, dH as DRAG_EVENT_DATA_KEY } from "./index--OJ5nhDf.js";
|
|
import { g as globalLinkActionsEventBus } from "./global-link-actions--TiC75iP.js";
|
|
import { u as useExecutionDebugging } from "./useExecutionDebugging-Bve-aaKO.js";
|
|
import { u as useBeforeUnload } from "./useBeforeUnload-ZtUpNFCu.js";
|
|
import { c as canvasEventBus } from "./canvas-DbK7UyVG.js";
|
|
import { u as useAITemplatesStarterCollectionStore, a as useReadyToRunWorkflowsStore } from "./readyToRunWorkflows.store-Dhb8bhvk.js";
|
|
const _sfc_main$a = /* @__PURE__ */ defineComponent({
|
|
...{
|
|
inheritAttrs: false
|
|
},
|
|
__name: "WorkflowCanvas",
|
|
props: {
|
|
id: { default: "canvas" },
|
|
workflow: {},
|
|
workflowObject: {},
|
|
fallbackNodes: { default: () => [] },
|
|
showFallbackNodes: { type: Boolean, default: true },
|
|
eventBus: { default: () => createEventBus() },
|
|
readOnly: { type: Boolean },
|
|
executing: { type: Boolean }
|
|
},
|
|
setup(__props) {
|
|
const props = __props;
|
|
const $style = useCssModule();
|
|
const { onNodesInitialized } = useVueFlow(props.id);
|
|
const workflow = toRef(props, "workflow");
|
|
const workflowObject = toRef(props, "workflowObject");
|
|
const nodes = computed(() => {
|
|
return props.showFallbackNodes ? [...props.workflow.nodes, ...props.fallbackNodes] : props.workflow.nodes;
|
|
});
|
|
const connections = computed(() => props.workflow.connections);
|
|
const { nodes: mappedNodes, connections: mappedConnections } = useCanvasMapping({
|
|
nodes,
|
|
connections,
|
|
workflowObject
|
|
});
|
|
const initialFitViewDone = ref(false);
|
|
onNodesInitialized(() => {
|
|
if (!initialFitViewDone.value || props.showFallbackNodes) {
|
|
props.eventBus.emit("fitView");
|
|
initialFitViewDone.value = true;
|
|
}
|
|
});
|
|
const mappedNodesThrottled = refThrottled(mappedNodes, 200);
|
|
const mappedConnectionsThrottled = refThrottled(mappedConnections, 200);
|
|
return (_ctx, _cache) => {
|
|
return openBlock(), createElementBlock("div", {
|
|
class: normalizeClass(unref($style).wrapper),
|
|
"data-test-id": "canvas-wrapper"
|
|
}, [
|
|
createBaseVNode("div", {
|
|
id: "canvas",
|
|
class: normalizeClass(unref($style).canvas)
|
|
}, [
|
|
workflow.value ? (openBlock(), createBlock(Canvas, mergeProps({
|
|
key: 0,
|
|
id: _ctx.id,
|
|
nodes: _ctx.executing ? unref(mappedNodesThrottled) : unref(mappedNodes),
|
|
connections: _ctx.executing ? unref(mappedConnectionsThrottled) : unref(mappedConnections),
|
|
"event-bus": _ctx.eventBus,
|
|
"read-only": _ctx.readOnly,
|
|
executing: _ctx.executing
|
|
}, _ctx.$attrs), null, 16, ["id", "nodes", "connections", "event-bus", "read-only", "executing"])) : createCommentVNode("", true)
|
|
], 2),
|
|
renderSlot(_ctx.$slots, "default")
|
|
], 2);
|
|
};
|
|
}
|
|
});
|
|
const wrapper$2 = "_wrapper_jyurh_123";
|
|
const canvas = "_canvas_jyurh_131";
|
|
const style0$6 = {
|
|
wrapper: wrapper$2,
|
|
canvas
|
|
};
|
|
const cssModules$6 = {
|
|
"$style": style0$6
|
|
};
|
|
const WorkflowCanvas = /* @__PURE__ */ _export_sfc(_sfc_main$a, [["__cssModules", cssModules$6]]);
|
|
function useExecutionData({ node }) {
|
|
const workflowsStore = useWorkflowsStore();
|
|
const workflowExecution = computed(() => {
|
|
return workflowsStore.getWorkflowExecution;
|
|
});
|
|
const workflowRunData = computed(() => {
|
|
if (workflowExecution.value === null) {
|
|
return null;
|
|
}
|
|
const executionData = workflowExecution.value.data;
|
|
if (!executionData?.resultData?.runData) {
|
|
return null;
|
|
}
|
|
return executionData.resultData.runData;
|
|
});
|
|
const hasNodeRun = computed(() => {
|
|
if (workflowsStore.subWorkflowExecutionError) return true;
|
|
return Boolean(
|
|
node.value && workflowRunData.value && Object.prototype.hasOwnProperty.bind(workflowRunData.value)(node.value.name)
|
|
);
|
|
});
|
|
return {
|
|
workflowExecution,
|
|
workflowRunData,
|
|
hasNodeRun
|
|
};
|
|
}
|
|
const _sfc_main$9 = /* @__PURE__ */ defineComponent({
|
|
__name: "ExperimentalNodeDetailsDrawer",
|
|
props: {
|
|
node: {},
|
|
nodes: {}
|
|
},
|
|
emits: ["openNdv"],
|
|
setup(__props, { emit: __emit }) {
|
|
const emit = __emit;
|
|
const expressionResolveCtx = useExpressionResolveCtx(computed(() => __props.node));
|
|
provide(ExpressionLocalResolveContextSymbol, expressionResolveCtx);
|
|
return (_ctx, _cache) => {
|
|
const _component_N8nIconButton = _sfc_main$c;
|
|
return openBlock(), createElementBlock("div", {
|
|
class: normalizeClass(_ctx.$style.component)
|
|
}, [
|
|
_ctx.nodes.length > 1 ? (openBlock(), createBlock(unref(N8nText), {
|
|
key: 0,
|
|
color: "text-base"
|
|
}, {
|
|
default: withCtx(() => [
|
|
createTextVNode(toDisplayString(_ctx.nodes.length) + " nodes selected ", 1)
|
|
]),
|
|
_: 1
|
|
})) : _ctx.node ? (openBlock(), createBlock(_sfc_main$b, {
|
|
key: _ctx.node.id,
|
|
"node-id": _ctx.node.id
|
|
}, {
|
|
actions: withCtx(() => [
|
|
createVNode(_component_N8nIconButton, {
|
|
icon: "maximize-2",
|
|
type: "secondary",
|
|
text: "",
|
|
size: "mini",
|
|
"icon-size": "large",
|
|
"aria-label": "Expand",
|
|
onClick: _cache[0] || (_cache[0] = ($event) => emit("openNdv"))
|
|
})
|
|
]),
|
|
_: 1
|
|
}, 8, ["node-id"])) : createCommentVNode("", true)
|
|
], 2);
|
|
};
|
|
}
|
|
});
|
|
const component$2 = "_component_1u8pj_123";
|
|
const style0$5 = {
|
|
component: component$2
|
|
};
|
|
const cssModules$5 = {
|
|
"$style": style0$5
|
|
};
|
|
const ExperimentalNodeDetailsDrawer = /* @__PURE__ */ _export_sfc(_sfc_main$9, [["__cssModules", cssModules$5]]);
|
|
const _sfc_main$8 = /* @__PURE__ */ defineComponent({
|
|
__name: "ExperimentalFocusPanelHeader",
|
|
props: {
|
|
node: {},
|
|
parameter: {},
|
|
isExecutable: { type: Boolean }
|
|
},
|
|
emits: ["execute", "openNdv", "clearParameter"],
|
|
setup(__props, { emit: __emit }) {
|
|
const nodeTypesStore = useNodeTypesStore();
|
|
const nodeType = computed(() => nodeTypesStore.getNodeType(__props.node.type, __props.node.typeVersion));
|
|
const emit = __emit;
|
|
return (_ctx, _cache) => {
|
|
return openBlock(), createBlock(unref(N8nText), {
|
|
tag: "div",
|
|
size: "small",
|
|
bold: "",
|
|
class: normalizeClass(_ctx.$style.component)
|
|
}, {
|
|
default: withCtx(() => [
|
|
createVNode(NodeIcon, {
|
|
"node-type": nodeType.value,
|
|
size: 16
|
|
}, null, 8, ["node-type"]),
|
|
createBaseVNode("div", {
|
|
class: normalizeClass(_ctx.$style.breadcrumbs)
|
|
}, [
|
|
_ctx.parameter ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [
|
|
createVNode(unref(N8nText), {
|
|
size: "small",
|
|
color: "text-base",
|
|
bold: ""
|
|
}, {
|
|
default: withCtx(() => [
|
|
createTextVNode(toDisplayString(_ctx.node.name), 1)
|
|
]),
|
|
_: 1
|
|
}),
|
|
createVNode(unref(N8nText), {
|
|
size: "small",
|
|
color: "text-light"
|
|
}, {
|
|
default: withCtx(() => _cache[3] || (_cache[3] = [
|
|
createTextVNode("/")
|
|
])),
|
|
_: 1
|
|
}),
|
|
createTextVNode(" " + toDisplayString(_ctx.parameter.displayName), 1)
|
|
], 64)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [
|
|
createTextVNode(toDisplayString(_ctx.node.name), 1)
|
|
], 64))
|
|
], 2),
|
|
_ctx.parameter ? (openBlock(), createBlock(unref(_sfc_main$c), {
|
|
key: 0,
|
|
icon: "x",
|
|
size: "small",
|
|
type: "tertiary",
|
|
text: "",
|
|
onClick: _cache[0] || (_cache[0] = ($event) => emit("clearParameter"))
|
|
})) : (openBlock(), createBlock(unref(_sfc_main$c), {
|
|
key: 1,
|
|
icon: "maximize-2",
|
|
size: "small",
|
|
type: "tertiary",
|
|
text: "",
|
|
onClick: _cache[1] || (_cache[1] = ($event) => emit("openNdv"))
|
|
})),
|
|
_ctx.isExecutable ? (openBlock(), createBlock(_sfc_main$d, {
|
|
key: 2,
|
|
"data-test-id": "node-execute-button",
|
|
"node-name": _ctx.node.name,
|
|
tooltip: `Execute ${_ctx.node.name}`,
|
|
size: "small",
|
|
icon: "play",
|
|
square: true,
|
|
"hide-label": true,
|
|
"telemetry-source": "focus",
|
|
onExecute: _cache[2] || (_cache[2] = ($event) => emit("execute"))
|
|
}, null, 8, ["node-name", "tooltip"])) : createCommentVNode("", true)
|
|
]),
|
|
_: 1
|
|
}, 8, ["class"]);
|
|
};
|
|
}
|
|
});
|
|
const component$1 = "_component_dktdl_123";
|
|
const breadcrumbs = "_breadcrumbs_dktdl_131";
|
|
const style0$4 = {
|
|
component: component$1,
|
|
breadcrumbs
|
|
};
|
|
const cssModules$4 = {
|
|
"$style": style0$4
|
|
};
|
|
const ExperimentalFocusPanelHeader = /* @__PURE__ */ _export_sfc(_sfc_main$8, [["__cssModules", cssModules$4]]);
|
|
const _sfc_main$7 = /* @__PURE__ */ defineComponent({
|
|
...{ name: "FocusPanel" },
|
|
__name: "FocusPanel",
|
|
props: {
|
|
isCanvasReadOnly: { type: Boolean }
|
|
},
|
|
emits: ["focus", "saveKeyboardShortcut"],
|
|
setup(__props, { emit: __emit }) {
|
|
const props = __props;
|
|
const emit = __emit;
|
|
const inputField = ref();
|
|
const wrapperRef = useTemplateRef("wrapper");
|
|
const locale = useI18n();
|
|
const nodeHelpers = useNodeHelpers();
|
|
const focusPanelStore = useFocusPanelStore();
|
|
const workflowsStore = useWorkflowsStore();
|
|
const nodeTypesStore = useNodeTypesStore();
|
|
const telemetry = useTelemetry();
|
|
const nodeSettingsParameters = useNodeSettingsParameters();
|
|
const environmentsStore = useEnvironmentsStore();
|
|
const experimentalNdvStore = useExperimentalNdvStore();
|
|
const ndvStore = useNDVStore();
|
|
const deviceSupport = useDeviceSupport();
|
|
const vueFlow = useVueFlow(workflowsStore.workflowId);
|
|
const activeElement = useActiveElement();
|
|
useTelemetryContext({ view_shown: "focus_panel" });
|
|
const resolvedParameter = computed(() => focusPanelStore.resolvedParameter);
|
|
const inputValue = ref("");
|
|
const focusPanelActive = computed(() => focusPanelStore.focusPanelActive);
|
|
const focusPanelWidth = computed(() => focusPanelStore.focusPanelWidth);
|
|
const isDisabled = computed(() => {
|
|
if (!resolvedParameter.value) return false;
|
|
return !!resolvedParameter.value.parameter.disabledOptions && nodeSettingsParameters.shouldDisplayNodeParameter(
|
|
resolvedParameter.value.node.parameters,
|
|
resolvedParameter.value.node,
|
|
resolvedParameter.value.parameter,
|
|
resolvedParameter.value.parameterPath.split(".").slice(1, -1).join("."),
|
|
"disabledOptions"
|
|
);
|
|
});
|
|
const isDisplayed = computed(() => {
|
|
if (!resolvedParameter.value) return true;
|
|
return nodeSettingsParameters.shouldDisplayNodeParameter(
|
|
resolvedParameter.value.node.parameters,
|
|
resolvedParameter.value.node,
|
|
resolvedParameter.value.parameter,
|
|
resolvedParameter.value.parameterPath.split(".").slice(1, -1).join("."),
|
|
"displayOptions"
|
|
);
|
|
});
|
|
const node = computed(() => {
|
|
if (!experimentalNdvStore.isNdvInFocusPanelEnabled || resolvedParameter.value) {
|
|
return resolvedParameter.value?.node;
|
|
}
|
|
const selected = vueFlow.getSelectedNodes.value[0]?.id;
|
|
return selected ? workflowsStore.allNodes.find((n) => n.id === selected) : void 0;
|
|
});
|
|
const multipleNodesSelected = computed(() => vueFlow.getSelectedNodes.value.length > 1);
|
|
const isExecutable = computed(() => {
|
|
if (!node.value) return false;
|
|
if (!isDisplayed.value) return false;
|
|
const foreignCredentials = nodeHelpers.getForeignCredentialsIfSharingEnabled(
|
|
node.value.credentials
|
|
);
|
|
return nodeHelpers.isNodeExecutable(node.value, !props.isCanvasReadOnly, foreignCredentials);
|
|
});
|
|
const { workflowRunData } = useExecutionData({ node });
|
|
const hasNodeRun = computed(() => {
|
|
if (!node.value) return true;
|
|
const parentNode = workflowsStore.workflowObject.getParentNodes(node.value.name, "main", 1)[0];
|
|
return Boolean(
|
|
parentNode && workflowRunData.value && Object.prototype.hasOwnProperty.bind(workflowRunData.value)(parentNode)
|
|
);
|
|
});
|
|
function getTypeOption(optionName) {
|
|
return resolvedParameter.value ? getParameterTypeOption(resolvedParameter.value.parameter, optionName) : void 0;
|
|
}
|
|
const codeEditorMode = computed(() => {
|
|
return resolvedParameter.value?.node.parameters.mode;
|
|
});
|
|
const editorType = computed(() => {
|
|
return getTypeOption("editor") ?? void 0;
|
|
});
|
|
const editorLanguage = computed(() => {
|
|
if (editorType.value === "json" || resolvedParameter.value?.parameter.type === "json")
|
|
return "json";
|
|
return getTypeOption("editorLanguage") ?? "javaScript";
|
|
});
|
|
const editorRows = computed(() => getTypeOption("rows"));
|
|
const isToolNode = computed(
|
|
() => resolvedParameter.value ? nodeTypesStore.isToolNode(resolvedParameter.value?.node.type) : false
|
|
);
|
|
const isHtmlNode = computed(
|
|
() => !!resolvedParameter.value && resolvedParameter.value.node.type === HTML_NODE_TYPE
|
|
);
|
|
const expressionModeEnabled = computed(
|
|
() => resolvedParameter.value && isValueExpression(resolvedParameter.value.parameter, resolvedParameter.value.value)
|
|
);
|
|
const expression = computed(() => {
|
|
if (!expressionModeEnabled.value) return "";
|
|
return isResourceLocatorValue(resolvedParameter.value) ? resolvedParameter.value.value : resolvedParameter.value;
|
|
});
|
|
const shouldCaptureForPosthog = computed(
|
|
() => resolvedParameter.value?.node.type === AI_TRANSFORM_NODE_TYPE
|
|
);
|
|
const isReadOnly = computed(() => props.isCanvasReadOnly || isDisabled.value);
|
|
const resolvedAdditionalExpressionData = computed(() => {
|
|
return {
|
|
$vars: environmentsStore.variablesAsObject
|
|
};
|
|
});
|
|
const targetNodeParameterContext = computed(() => {
|
|
if (!resolvedParameter.value) return void 0;
|
|
return {
|
|
nodeName: resolvedParameter.value.node.name,
|
|
parameterPath: resolvedParameter.value.parameterPath
|
|
};
|
|
});
|
|
const isNodeExecuting = computed(() => workflowsStore.isNodeExecuting(node.value?.name ?? ""));
|
|
const { resolvedExpression } = useResolvedExpression({
|
|
expression,
|
|
additionalData: resolvedAdditionalExpressionData,
|
|
stringifyObject: resolvedParameter.value && resolvedParameter.value.parameter.type !== "multiOptions"
|
|
});
|
|
function valueChanged(value) {
|
|
if (resolvedParameter.value === void 0) {
|
|
return;
|
|
}
|
|
nodeSettingsParameters.updateNodeParameter(
|
|
toRef(resolvedParameter.value.node.parameters),
|
|
{ value, name: resolvedParameter.value.parameterPath },
|
|
value,
|
|
resolvedParameter.value.node,
|
|
isToolNode.value
|
|
);
|
|
}
|
|
async function setFocus() {
|
|
await nextTick();
|
|
if (inputField.value) {
|
|
if (hasFocusOnInput(inputField.value)) {
|
|
inputField.value.focusOnInput();
|
|
} else if (isFocusableEl(inputField.value)) {
|
|
inputField.value.focus();
|
|
}
|
|
}
|
|
emit("focus");
|
|
}
|
|
function optionSelected(command) {
|
|
if (!resolvedParameter.value) return;
|
|
switch (command) {
|
|
case "resetValue": {
|
|
if (typeof resolvedParameter.value.parameter.default === "string") {
|
|
valueChanged(resolvedParameter.value.parameter.default);
|
|
}
|
|
void setFocus();
|
|
break;
|
|
}
|
|
case "addExpression": {
|
|
const newValue = formatAsExpression(
|
|
resolvedParameter.value.value,
|
|
resolvedParameter.value.parameter.type
|
|
);
|
|
valueChanged(typeof newValue === "string" ? newValue : newValue.value);
|
|
void setFocus();
|
|
break;
|
|
}
|
|
case "removeExpression": {
|
|
const newValue = parseFromExpression(
|
|
resolvedParameter.value.value,
|
|
resolvedExpression.value,
|
|
resolvedParameter.value.parameter.type,
|
|
resolvedParameter.value.parameter.default,
|
|
(resolvedParameter.value.parameter.options ?? []).filter(isValidParameterOption)
|
|
);
|
|
if (typeof newValue === "string") {
|
|
valueChanged(newValue);
|
|
} else if (newValue && typeof newValue.value === "string") {
|
|
valueChanged(newValue.value);
|
|
}
|
|
void setFocus();
|
|
break;
|
|
}
|
|
case "formatHtml":
|
|
htmlEditorEventBus.emit("format-html");
|
|
break;
|
|
}
|
|
}
|
|
function closeFocusPanel() {
|
|
if (experimentalNdvStore.isNdvInFocusPanelEnabled && resolvedParameter.value) {
|
|
focusPanelStore.unsetParameters();
|
|
telemetry.track("User removed focused param", {
|
|
source: "closeIcon",
|
|
parameters: focusPanelStore.focusedNodeParametersInTelemetryFormat
|
|
});
|
|
return;
|
|
}
|
|
telemetry.track("User closed focus panel", {
|
|
source: "closeIcon",
|
|
parameters: focusPanelStore.focusedNodeParametersInTelemetryFormat
|
|
});
|
|
focusPanelStore.closeFocusPanel();
|
|
}
|
|
function onExecute() {
|
|
telemetry.track(
|
|
"User executed node from focus panel",
|
|
focusPanelStore.focusedNodeParametersInTelemetryFormat[0]
|
|
);
|
|
}
|
|
function onInputChange(val) {
|
|
inputValue.value = val;
|
|
valueChanged(val);
|
|
}
|
|
function focusWithDelay() {
|
|
setTimeout(() => {
|
|
void setFocus();
|
|
}, 50);
|
|
}
|
|
function handleKeydown(event) {
|
|
if (event.key === "s" && deviceSupport.isCtrlKeyPressed(event)) {
|
|
event.stopPropagation();
|
|
event.preventDefault();
|
|
if (isReadOnly.value) return;
|
|
emit("saveKeyboardShortcut", event);
|
|
}
|
|
}
|
|
const registerKeyboardListener = () => {
|
|
document.addEventListener("keydown", handleKeydown, true);
|
|
};
|
|
const unregisterKeyboardListener = () => {
|
|
document.removeEventListener("keydown", handleKeydown, true);
|
|
};
|
|
watch(
|
|
[() => focusPanelStore.lastFocusTimestamp, () => expressionModeEnabled.value],
|
|
() => focusWithDelay()
|
|
);
|
|
watch(
|
|
() => focusPanelStore.focusPanelActive,
|
|
(newValue) => {
|
|
if (newValue) {
|
|
registerKeyboardListener();
|
|
} else {
|
|
unregisterKeyboardListener();
|
|
}
|
|
},
|
|
{ immediate: true }
|
|
);
|
|
watch(
|
|
() => resolvedParameter.value,
|
|
(newValue) => {
|
|
if (newValue) {
|
|
const value = newValue.value;
|
|
if (typeof value === "string" && value !== inputValue.value) {
|
|
inputValue.value = value;
|
|
}
|
|
}
|
|
},
|
|
{ immediate: true }
|
|
);
|
|
watch(activeElement, (active) => {
|
|
if (!node.value || !active || !wrapperRef.value?.contains(active)) {
|
|
return;
|
|
}
|
|
const path = active.closest(".parameter-input")?.getAttribute("data-parameter-path");
|
|
if (!path) {
|
|
return;
|
|
}
|
|
telemetry.track("User focused focus panel", {
|
|
node_id: node.value.id,
|
|
node_type: node.value.type,
|
|
parameter_path: path
|
|
});
|
|
});
|
|
function onResize(event) {
|
|
focusPanelStore.updateWidth(event.width);
|
|
}
|
|
const onResizeThrottle = useThrottleFn(onResize, 10);
|
|
function onOpenNdv() {
|
|
if (node.value) {
|
|
ndvStore.setActiveNodeName(node.value.name, "focus_panel");
|
|
}
|
|
}
|
|
return (_ctx, _cache) => {
|
|
const _component_NodeExecuteButton = _sfc_main$d;
|
|
const _component_N8nIcon = N8nIcon;
|
|
const _component_ParameterOptions = __unplugin_components_2;
|
|
const _component_ExpressionEditorModalInput = __unplugin_components_3;
|
|
const _component_CodeNodeEditor = __unplugin_components_4;
|
|
const _component_HtmlEditor = __unplugin_components_5;
|
|
const _component_CssEditor = __unplugin_components_6;
|
|
const _component_SqlEditor = __unplugin_components_7;
|
|
const _component_JsEditor = __unplugin_components_8;
|
|
const _component_JsonEditor = __unplugin_components_9;
|
|
const _component_N8nRadioButtons = N8nRadioButtons;
|
|
return focusPanelActive.value ? (openBlock(), createElementBlock("div", {
|
|
key: 0,
|
|
ref: "wrapper",
|
|
class: normalizeClass(_ctx.$style.wrapper),
|
|
onKeydown: _cache[9] || (_cache[9] = withModifiers(() => {
|
|
}, ["stop"]))
|
|
}, [
|
|
createVNode(unref(N8nResizeWrapper), {
|
|
width: focusPanelWidth.value,
|
|
"supported-directions": ["left"],
|
|
"min-width": 300,
|
|
"max-width": unref(experimentalNdvStore).isNdvInFocusPanelEnabled ? void 0 : 1e3,
|
|
"grid-size": 8,
|
|
style: normalizeStyle({ width: `${focusPanelWidth.value}px` }),
|
|
onResize: unref(onResizeThrottle)
|
|
}, {
|
|
default: withCtx(() => [
|
|
createBaseVNode("div", {
|
|
class: normalizeClass(_ctx.$style.container)
|
|
}, [
|
|
unref(experimentalNdvStore).isNdvInFocusPanelEnabled && node.value && !multipleNodesSelected.value ? (openBlock(), createBlock(ExperimentalFocusPanelHeader, {
|
|
key: 0,
|
|
node: node.value,
|
|
parameter: resolvedParameter.value?.parameter,
|
|
"is-executable": isExecutable.value,
|
|
onExecute,
|
|
onOpenNdv,
|
|
onClearParameter: closeFocusPanel
|
|
}, null, 8, ["node", "parameter", "is-executable"])) : createCommentVNode("", true),
|
|
resolvedParameter.value ? (openBlock(), createElementBlock("div", {
|
|
key: 1,
|
|
class: normalizeClass(_ctx.$style.content)
|
|
}, [
|
|
!unref(experimentalNdvStore).isNdvInFocusPanelEnabled ? (openBlock(), createElementBlock("div", {
|
|
key: 0,
|
|
class: normalizeClass(_ctx.$style.tabHeader)
|
|
}, [
|
|
createBaseVNode("div", {
|
|
class: normalizeClass(_ctx.$style.tabHeaderText)
|
|
}, [
|
|
createVNode(unref(N8nText), {
|
|
color: "text-dark",
|
|
size: "small"
|
|
}, {
|
|
default: withCtx(() => [
|
|
createTextVNode(toDisplayString(resolvedParameter.value.parameter.displayName), 1)
|
|
]),
|
|
_: 1
|
|
}),
|
|
createVNode(unref(N8nText), {
|
|
color: "text-base",
|
|
size: "xsmall"
|
|
}, {
|
|
default: withCtx(() => [
|
|
createTextVNode(toDisplayString(resolvedParameter.value.node.name), 1)
|
|
]),
|
|
_: 1
|
|
})
|
|
], 2),
|
|
createBaseVNode("div", {
|
|
class: normalizeClass(_ctx.$style.buttonWrapper)
|
|
}, [
|
|
createVNode(_component_NodeExecuteButton, {
|
|
"data-test-id": "node-execute-button",
|
|
"node-name": resolvedParameter.value.node.name,
|
|
tooltip: `Execute ${resolvedParameter.value.node.name}`,
|
|
disabled: !isExecutable.value,
|
|
size: "small",
|
|
icon: "play",
|
|
square: true,
|
|
"hide-label": true,
|
|
"telemetry-source": "focus",
|
|
onExecute
|
|
}, null, 8, ["node-name", "tooltip", "disabled"]),
|
|
createVNode(_component_N8nIcon, {
|
|
class: normalizeClass(_ctx.$style.closeButton),
|
|
icon: "x",
|
|
color: "text-base",
|
|
size: "xlarge",
|
|
onClick: closeFocusPanel
|
|
}, null, 8, ["class"])
|
|
], 2)
|
|
], 2)) : createCommentVNode("", true),
|
|
createBaseVNode("div", {
|
|
class: normalizeClass(_ctx.$style.parameterDetailsWrapper)
|
|
}, [
|
|
createBaseVNode("div", {
|
|
class: normalizeClass(_ctx.$style.parameterOptionsWrapper)
|
|
}, [
|
|
createBaseVNode("div", {
|
|
class: normalizeClass(_ctx.$style.noExecutionDataTip)
|
|
}, [
|
|
!hasNodeRun.value && !isNodeExecuting.value ? (openBlock(), createBlock(unref(InfoTip), {
|
|
key: 0,
|
|
class: normalizeClass(_ctx.$style.delayedShow),
|
|
bold: true
|
|
}, {
|
|
default: withCtx(() => [
|
|
createTextVNode(toDisplayString(unref(locale).baseText("nodeView.focusPanel.noExecutionData")), 1)
|
|
]),
|
|
_: 1
|
|
}, 8, ["class"])) : createCommentVNode("", true)
|
|
], 2),
|
|
isDisplayed.value ? (openBlock(), createBlock(_component_ParameterOptions, {
|
|
key: 0,
|
|
parameter: resolvedParameter.value.parameter,
|
|
value: resolvedParameter.value.value,
|
|
"is-read-only": isReadOnly.value,
|
|
"onUpdate:modelValue": optionSelected
|
|
}, null, 8, ["parameter", "value", "is-read-only"])) : createCommentVNode("", true)
|
|
], 2),
|
|
typeof resolvedParameter.value.value === "string" ? (openBlock(), createElementBlock("div", {
|
|
key: 0,
|
|
class: normalizeClass(_ctx.$style.editorContainer)
|
|
}, [
|
|
!isDisplayed.value ? (openBlock(), createElementBlock("div", {
|
|
key: 0,
|
|
class: normalizeClass([_ctx.$style.content, _ctx.$style.emptyContent])
|
|
}, [
|
|
createBaseVNode("div", {
|
|
class: normalizeClass(_ctx.$style.emptyText)
|
|
}, [
|
|
createVNode(unref(N8nText), { color: "text-base" }, {
|
|
default: withCtx(() => [
|
|
createTextVNode(toDisplayString(unref(locale).baseText("nodeView.focusPanel.missingParameter")), 1)
|
|
]),
|
|
_: 1
|
|
})
|
|
], 2)
|
|
], 2)) : expressionModeEnabled.value ? (openBlock(), createBlock(_component_ExpressionEditorModalInput, {
|
|
key: 1,
|
|
ref_key: "inputField",
|
|
ref: inputField,
|
|
modelValue: inputValue.value,
|
|
"onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => inputValue.value = $event),
|
|
class: normalizeClass(_ctx.$style.editor),
|
|
"is-read-only": isReadOnly.value,
|
|
path: resolvedParameter.value.parameterPath,
|
|
"data-test-id": "expression-modal-input",
|
|
"target-node-parameter-context": targetNodeParameterContext.value,
|
|
onChange: _cache[1] || (_cache[1] = ($event) => onInputChange($event.value))
|
|
}, null, 8, ["modelValue", "class", "is-read-only", "path", "target-node-parameter-context"])) : ["json", "string"].includes(resolvedParameter.value.parameter.type) ? (openBlock(), createElementBlock(Fragment, { key: 2 }, [
|
|
editorType.value === "codeNodeEditor" ? (openBlock(), createBlock(_component_CodeNodeEditor, {
|
|
key: 0,
|
|
id: resolvedParameter.value.parameterPath,
|
|
ref_key: "inputField",
|
|
ref: inputField,
|
|
modelValue: inputValue.value,
|
|
"onUpdate:modelValue": [
|
|
_cache[2] || (_cache[2] = ($event) => inputValue.value = $event),
|
|
onInputChange
|
|
],
|
|
class: normalizeClass(_ctx.$style.heightFull),
|
|
mode: codeEditorMode.value,
|
|
"default-value": resolvedParameter.value.parameter.default,
|
|
language: editorLanguage.value,
|
|
"is-read-only": isReadOnly.value,
|
|
"target-node-parameter-context": targetNodeParameterContext.value,
|
|
"fill-parent": "",
|
|
"disable-ask-ai": true
|
|
}, null, 8, ["id", "modelValue", "class", "mode", "default-value", "language", "is-read-only", "target-node-parameter-context"])) : editorType.value === "htmlEditor" ? (openBlock(), createBlock(_component_HtmlEditor, {
|
|
key: 1,
|
|
ref_key: "inputField",
|
|
ref: inputField,
|
|
modelValue: inputValue.value,
|
|
"onUpdate:modelValue": [
|
|
_cache[3] || (_cache[3] = ($event) => inputValue.value = $event),
|
|
onInputChange
|
|
],
|
|
"is-read-only": isReadOnly.value,
|
|
rows: editorRows.value,
|
|
"disable-expression-coloring": !isHtmlNode.value,
|
|
"disable-expression-completions": !isHtmlNode.value,
|
|
fullscreen: "",
|
|
"target-node-parameter-context": targetNodeParameterContext.value
|
|
}, null, 8, ["modelValue", "is-read-only", "rows", "disable-expression-coloring", "disable-expression-completions", "target-node-parameter-context"])) : editorType.value === "cssEditor" ? (openBlock(), createBlock(_component_CssEditor, {
|
|
key: 2,
|
|
ref_key: "inputField",
|
|
ref: inputField,
|
|
modelValue: inputValue.value,
|
|
"onUpdate:modelValue": [
|
|
_cache[4] || (_cache[4] = ($event) => inputValue.value = $event),
|
|
onInputChange
|
|
],
|
|
"is-read-only": isReadOnly.value,
|
|
rows: editorRows.value,
|
|
fullscreen: "",
|
|
"target-node-parameter-context": targetNodeParameterContext.value
|
|
}, null, 8, ["modelValue", "is-read-only", "rows", "target-node-parameter-context"])) : editorType.value === "sqlEditor" ? (openBlock(), createBlock(_component_SqlEditor, {
|
|
key: 3,
|
|
ref_key: "inputField",
|
|
ref: inputField,
|
|
modelValue: inputValue.value,
|
|
"onUpdate:modelValue": [
|
|
_cache[5] || (_cache[5] = ($event) => inputValue.value = $event),
|
|
onInputChange
|
|
],
|
|
dialect: getTypeOption("sqlDialect"),
|
|
"is-read-only": isReadOnly.value,
|
|
rows: editorRows.value,
|
|
fullscreen: "",
|
|
"target-node-parameter-context": targetNodeParameterContext.value
|
|
}, null, 8, ["modelValue", "dialect", "is-read-only", "rows", "target-node-parameter-context"])) : editorType.value === "jsEditor" ? (openBlock(), createBlock(_component_JsEditor, {
|
|
key: 4,
|
|
ref_key: "inputField",
|
|
ref: inputField,
|
|
modelValue: inputValue.value,
|
|
"onUpdate:modelValue": [
|
|
_cache[6] || (_cache[6] = ($event) => inputValue.value = $event),
|
|
onInputChange
|
|
],
|
|
"is-read-only": isReadOnly.value,
|
|
rows: editorRows.value,
|
|
"posthog-capture": shouldCaptureForPosthog.value,
|
|
"fill-parent": ""
|
|
}, null, 8, ["modelValue", "is-read-only", "rows", "posthog-capture"])) : resolvedParameter.value.parameter.type === "json" ? (openBlock(), createBlock(_component_JsonEditor, {
|
|
key: 5,
|
|
ref_key: "inputField",
|
|
ref: inputField,
|
|
modelValue: inputValue.value,
|
|
"onUpdate:modelValue": [
|
|
_cache[7] || (_cache[7] = ($event) => inputValue.value = $event),
|
|
onInputChange
|
|
],
|
|
"is-read-only": isReadOnly.value,
|
|
rows: editorRows.value,
|
|
fullscreen: "",
|
|
"fill-parent": ""
|
|
}, null, 8, ["modelValue", "is-read-only", "rows"])) : (openBlock(), createBlock(unref(N8nInput), {
|
|
key: 6,
|
|
ref_key: "inputField",
|
|
ref: inputField,
|
|
modelValue: inputValue.value,
|
|
"onUpdate:modelValue": [
|
|
_cache[8] || (_cache[8] = ($event) => inputValue.value = $event),
|
|
onInputChange
|
|
],
|
|
class: normalizeClass(_ctx.$style.editor),
|
|
readonly: isReadOnly.value,
|
|
type: "textarea",
|
|
resize: "none"
|
|
}, null, 8, ["modelValue", "class", "readonly"]))
|
|
], 64)) : createCommentVNode("", true)
|
|
], 2)) : createCommentVNode("", true)
|
|
], 2)
|
|
], 2)) : node.value && unref(experimentalNdvStore).isNdvInFocusPanelEnabled ? (openBlock(), createBlock(ExperimentalNodeDetailsDrawer, {
|
|
key: 2,
|
|
node: node.value,
|
|
nodes: unref(vueFlow).getSelectedNodes.value,
|
|
onOpenNdv
|
|
}, null, 8, ["node", "nodes"])) : (openBlock(), createElementBlock("div", {
|
|
key: 3,
|
|
class: normalizeClass([_ctx.$style.content, _ctx.$style.emptyContent])
|
|
}, [
|
|
createBaseVNode("div", {
|
|
class: normalizeClass(_ctx.$style.emptyText)
|
|
}, [
|
|
createBaseVNode("div", {
|
|
class: normalizeClass(_ctx.$style.focusParameterWrapper)
|
|
}, [
|
|
createBaseVNode("div", {
|
|
class: normalizeClass(_ctx.$style.iconWrapper)
|
|
}, [
|
|
createVNode(_component_N8nIcon, {
|
|
class: normalizeClass(_ctx.$style.forceHover),
|
|
icon: "panel-right",
|
|
size: "medium"
|
|
}, null, 8, ["class"]),
|
|
createVNode(_component_N8nIcon, {
|
|
class: normalizeClass(_ctx.$style.pointerIcon),
|
|
icon: "mouse-pointer",
|
|
color: "text-dark",
|
|
size: "large"
|
|
}, null, 8, ["class"])
|
|
], 2),
|
|
createVNode(_component_N8nIcon, {
|
|
icon: "ellipsis-vertical",
|
|
size: "small",
|
|
color: "text-base"
|
|
}),
|
|
createVNode(_component_N8nRadioButtons, {
|
|
size: "small",
|
|
"model-value": "expression",
|
|
disabled: true,
|
|
options: [
|
|
{ label: unref(locale).baseText("parameterInput.fixed"), value: "fixed" },
|
|
{ label: unref(locale).baseText("parameterInput.expression"), value: "expression" }
|
|
]
|
|
}, null, 8, ["options"])
|
|
], 2),
|
|
createVNode(unref(N8nText), {
|
|
color: "text-base",
|
|
size: "medium",
|
|
bold: true
|
|
}, {
|
|
default: withCtx(() => [
|
|
createTextVNode(toDisplayString(unref(locale).baseText("nodeView.focusPanel.noParameters.title")), 1)
|
|
]),
|
|
_: 1
|
|
}),
|
|
createVNode(unref(N8nText), {
|
|
color: "text-base",
|
|
size: "small"
|
|
}, {
|
|
default: withCtx(() => [
|
|
createTextVNode(toDisplayString(unref(locale).baseText("nodeView.focusPanel.noParameters.subtitle")), 1)
|
|
]),
|
|
_: 1
|
|
})
|
|
], 2)
|
|
], 2))
|
|
], 2)
|
|
]),
|
|
_: 1
|
|
}, 8, ["width", "max-width", "style", "onResize"])
|
|
], 34)) : createCommentVNode("", true);
|
|
};
|
|
}
|
|
});
|
|
const wrapper$1 = "_wrapper_1yhze_123";
|
|
const container = "_container_1yhze_135";
|
|
const content = "_content_1yhze_141";
|
|
const emptyContent = "_emptyContent_1yhze_147";
|
|
const emptyText = "_emptyText_1yhze_152";
|
|
const focusParameterWrapper = "_focusParameterWrapper_1yhze_158";
|
|
const iconWrapper$1 = "_iconWrapper_1yhze_165";
|
|
const pointerIcon = "_pointerIcon_1yhze_169";
|
|
const tabHeader = "_tabHeader_1yhze_179";
|
|
const tabHeaderText = "_tabHeaderText_1yhze_186";
|
|
const buttonWrapper = "_buttonWrapper_1yhze_191";
|
|
const parameterDetailsWrapper = "_parameterDetailsWrapper_1yhze_196";
|
|
const parameterOptionsWrapper = "_parameterOptionsWrapper_1yhze_203";
|
|
const noExecutionDataTip = "_noExecutionDataTip_1yhze_207";
|
|
const editorContainer = "_editorContainer_1yhze_210";
|
|
const editor = "_editor_1yhze_210";
|
|
const delayedShow = "_delayedShow_1yhze_225";
|
|
const triggerShow = "_triggerShow_1yhze_1";
|
|
const closeButton = "_closeButton_1yhze_236";
|
|
const heightFull = "_heightFull_1yhze_240";
|
|
const forceHover = "_forceHover_1yhze_244";
|
|
const style0$3 = {
|
|
wrapper: wrapper$1,
|
|
container,
|
|
content,
|
|
emptyContent,
|
|
emptyText,
|
|
focusParameterWrapper,
|
|
iconWrapper: iconWrapper$1,
|
|
pointerIcon,
|
|
tabHeader,
|
|
tabHeaderText,
|
|
buttonWrapper,
|
|
parameterDetailsWrapper,
|
|
parameterOptionsWrapper,
|
|
noExecutionDataTip,
|
|
editorContainer,
|
|
editor,
|
|
delayedShow,
|
|
triggerShow,
|
|
closeButton,
|
|
heightFull,
|
|
forceHover
|
|
};
|
|
const cssModules$3 = {
|
|
"$style": style0$3
|
|
};
|
|
const FocusPanel = /* @__PURE__ */ _export_sfc(_sfc_main$7, [["__cssModules", cssModules$3]]);
|
|
const _sfc_main$6 = /* @__PURE__ */ defineComponent({
|
|
__name: "CanvasRunWorkflowButton",
|
|
props: {
|
|
selectedTriggerNodeName: {},
|
|
triggerNodes: {},
|
|
waitingForWebhook: { type: Boolean },
|
|
executing: { type: Boolean },
|
|
disabled: { type: Boolean },
|
|
getNodeType: { type: Function }
|
|
},
|
|
emits: ["mouseenter", "mouseleave", "execute", "selectTriggerNode"],
|
|
setup(__props, { emit: __emit }) {
|
|
const emit = __emit;
|
|
const props = __props;
|
|
const i18n = useI18n();
|
|
const selectableTriggerNodes = computed(
|
|
() => props.triggerNodes.filter((node) => !node.disabled && !isChatNode(node))
|
|
);
|
|
const label = computed(() => {
|
|
if (!props.executing) {
|
|
return i18n.baseText("nodeView.runButtonText.executeWorkflow");
|
|
}
|
|
if (props.waitingForWebhook) {
|
|
return i18n.baseText("nodeView.runButtonText.waitingForTriggerEvent");
|
|
}
|
|
return i18n.baseText("nodeView.runButtonText.executingWorkflow");
|
|
});
|
|
const actions = computed(
|
|
() => props.triggerNodes.filter((node) => !isChatNode(node)).toSorted((a, b) => {
|
|
const [aX, aY] = a.position;
|
|
const [bX, bY] = b.position;
|
|
return aY === bY ? aX - bX : aY - bY;
|
|
}).map((node) => ({
|
|
label: truncateBeforeLast(node.name, 50),
|
|
disabled: !!node.disabled || props.executing,
|
|
id: node.name,
|
|
checked: props.selectedTriggerNodeName === node.name
|
|
}))
|
|
);
|
|
const isSplitButton = computed(
|
|
() => selectableTriggerNodes.value.length > 1 && props.selectedTriggerNodeName !== void 0
|
|
);
|
|
function getNodeTypeByName(name) {
|
|
const node = props.triggerNodes.find((trigger) => trigger.name === name);
|
|
if (!node) {
|
|
return null;
|
|
}
|
|
return props.getNodeType(node.type, node.typeVersion);
|
|
}
|
|
return (_ctx, _cache) => {
|
|
const _component_NodeIcon = NodeIcon;
|
|
return openBlock(), createElementBlock("div", {
|
|
class: normalizeClass([_ctx.$style.component, isSplitButton.value ? _ctx.$style.split : ""])
|
|
}, [
|
|
createVNode(KeyboardShortcutTooltip, {
|
|
label: label.value,
|
|
shortcut: { metaKey: true, keys: ["↵"] },
|
|
disabled: _ctx.executing
|
|
}, {
|
|
default: withCtx(() => [
|
|
createVNode(unref(N8nButton), {
|
|
class: normalizeClass(_ctx.$style.button),
|
|
loading: _ctx.executing,
|
|
disabled: _ctx.disabled,
|
|
size: "large",
|
|
icon: "flask-conical",
|
|
type: "primary",
|
|
"data-test-id": "execute-workflow-button",
|
|
onMouseenter: _cache[0] || (_cache[0] = ($event) => _ctx.$emit("mouseenter", $event)),
|
|
onMouseleave: _cache[1] || (_cache[1] = ($event) => _ctx.$emit("mouseleave", $event)),
|
|
onClick: _cache[2] || (_cache[2] = ($event) => emit("execute"))
|
|
}, {
|
|
default: withCtx(() => [
|
|
createBaseVNode("span", {
|
|
class: normalizeClass(_ctx.$style.buttonContent)
|
|
}, [
|
|
createTextVNode(toDisplayString(label.value) + " ", 1),
|
|
isSplitButton.value ? (openBlock(), createBlock(unref(N8nText), {
|
|
key: 0,
|
|
class: normalizeClass(_ctx.$style.subText),
|
|
bold: false
|
|
}, {
|
|
default: withCtx(() => [
|
|
createVNode(unref(I18nT), {
|
|
keypath: "nodeView.runButtonText.from",
|
|
scope: "global"
|
|
}, {
|
|
nodeName: withCtx(() => [
|
|
createVNode(unref(N8nText), {
|
|
bold: "",
|
|
size: "mini"
|
|
}, {
|
|
default: withCtx(() => [
|
|
createTextVNode(toDisplayString(unref(truncateBeforeLast)(props.selectedTriggerNodeName ?? "", 25)), 1)
|
|
]),
|
|
_: 1
|
|
})
|
|
]),
|
|
_: 1
|
|
})
|
|
]),
|
|
_: 1
|
|
}, 8, ["class"])) : createCommentVNode("", true)
|
|
], 2)
|
|
]),
|
|
_: 1
|
|
}, 8, ["class", "loading", "disabled"])
|
|
]),
|
|
_: 1
|
|
}, 8, ["label", "disabled"]),
|
|
isSplitButton.value ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [
|
|
createBaseVNode("div", {
|
|
role: "presentation",
|
|
class: normalizeClass(_ctx.$style.divider)
|
|
}, null, 2),
|
|
createVNode(unref(N8nActionDropdown), {
|
|
class: normalizeClass(_ctx.$style.menu),
|
|
items: actions.value,
|
|
disabled: _ctx.disabled,
|
|
placement: "top",
|
|
"extra-popper-class": _ctx.$style.menuPopper,
|
|
onSelect: _cache[3] || (_cache[3] = ($event) => emit("selectTriggerNode", $event))
|
|
}, {
|
|
activator: withCtx(() => [
|
|
createVNode(unref(N8nButton), {
|
|
type: "primary",
|
|
"icon-size": "large",
|
|
disabled: _ctx.disabled,
|
|
class: normalizeClass(_ctx.$style.chevron),
|
|
"aria-label": "Select trigger node",
|
|
icon: "chevron-down"
|
|
}, null, 8, ["disabled", "class"])
|
|
]),
|
|
menuItem: withCtx((item) => [
|
|
createBaseVNode("div", {
|
|
class: normalizeClass([_ctx.$style.menuItem, item.disabled ? _ctx.$style.disabled : ""])
|
|
}, [
|
|
createVNode(_component_NodeIcon, {
|
|
class: normalizeClass(_ctx.$style.menuIcon),
|
|
size: 16,
|
|
"node-type": getNodeTypeByName(item.id)
|
|
}, null, 8, ["class", "node-type"]),
|
|
createBaseVNode("span", null, [
|
|
createVNode(unref(I18nT), {
|
|
keypath: "nodeView.runButtonText.from",
|
|
scope: "global"
|
|
}, {
|
|
nodeName: withCtx(() => [
|
|
createVNode(unref(N8nText), {
|
|
bold: "",
|
|
size: "small"
|
|
}, {
|
|
default: withCtx(() => [
|
|
createTextVNode(toDisplayString(item.label), 1)
|
|
]),
|
|
_: 2
|
|
}, 1024)
|
|
]),
|
|
_: 2
|
|
}, 1024)
|
|
])
|
|
], 2)
|
|
]),
|
|
_: 1
|
|
}, 8, ["class", "items", "disabled", "extra-popper-class"])
|
|
], 64)) : createCommentVNode("", true)
|
|
], 2);
|
|
};
|
|
}
|
|
});
|
|
const component = "_component_3izac_123";
|
|
const split = "_split_3izac_129";
|
|
const button = "_button_3izac_129";
|
|
const divider = "_divider_3izac_137";
|
|
const chevron = "_chevron_3izac_142";
|
|
const menu = "_menu_3izac_148";
|
|
const menuPopper = "_menuPopper_3izac_152";
|
|
const menuItem = "_menuItem_3izac_156";
|
|
const disabled = "_disabled_3izac_162";
|
|
const menuIcon = "_menuIcon_3izac_162";
|
|
const buttonContent = "_buttonContent_3izac_166";
|
|
const subText = "_subText_3izac_173";
|
|
const style0$2 = {
|
|
component,
|
|
split,
|
|
button,
|
|
divider,
|
|
chevron,
|
|
menu,
|
|
menuPopper,
|
|
menuItem,
|
|
disabled,
|
|
menuIcon,
|
|
buttonContent,
|
|
subText
|
|
};
|
|
const cssModules$2 = {
|
|
"$style": style0$2
|
|
};
|
|
const CanvasRunWorkflowButton = /* @__PURE__ */ _export_sfc(_sfc_main$6, [["__cssModules", cssModules$2]]);
|
|
const state = reactive({
|
|
customActions: {},
|
|
delegatedClickHandler: null
|
|
});
|
|
function useGlobalLinkActions() {
|
|
function registerCustomAction({ key, action }) {
|
|
state.customActions[key] = action;
|
|
}
|
|
function unregisterCustomAction(key) {
|
|
const { [key]: _, ...rest } = state.customActions;
|
|
state.customActions = rest;
|
|
}
|
|
function getElementAttributes(element) {
|
|
const attributesObject = {};
|
|
for (let i = 0; i < element.attributes.length; i++) {
|
|
const attr = element.attributes[i];
|
|
if (attr.name.startsWith("data-action-parameter-")) {
|
|
attributesObject[attr.name.replace("data-action-parameter-", "")] = attr.value;
|
|
}
|
|
}
|
|
return attributesObject;
|
|
}
|
|
function delegateClick(e) {
|
|
const clickedElement = e.target;
|
|
if (!(clickedElement instanceof Element) || clickedElement.tagName !== "A") return;
|
|
const actionAttribute = clickedElement.getAttribute("data-action");
|
|
if (actionAttribute && typeof availableActions.value[actionAttribute] === "function") {
|
|
e.preventDefault();
|
|
const elementAttributes = getElementAttributes(clickedElement);
|
|
availableActions.value[actionAttribute](elementAttributes);
|
|
}
|
|
}
|
|
function reload() {
|
|
if (window.top) {
|
|
window.top.location.reload();
|
|
} else {
|
|
window.location.reload();
|
|
}
|
|
}
|
|
const availableActions = computed(() => ({
|
|
reload,
|
|
...state.customActions
|
|
}));
|
|
onMounted(() => {
|
|
if (state.delegatedClickHandler) return;
|
|
state.delegatedClickHandler = delegateClick;
|
|
window.addEventListener("click", delegateClick);
|
|
globalLinkActionsEventBus.on("registerGlobalLinkAction", registerCustomAction);
|
|
});
|
|
onUnmounted(() => {
|
|
window.removeEventListener("click", delegateClick);
|
|
state.delegatedClickHandler = null;
|
|
globalLinkActionsEventBus.off("registerGlobalLinkAction", registerCustomAction);
|
|
});
|
|
return {
|
|
registerCustomAction,
|
|
unregisterCustomAction
|
|
};
|
|
}
|
|
const _sfc_main$5 = /* @__PURE__ */ defineComponent({
|
|
__name: "CanvasStopCurrentExecutionButton",
|
|
props: {
|
|
stopping: { type: Boolean }
|
|
},
|
|
setup(__props) {
|
|
const props = __props;
|
|
const i18n = useI18n();
|
|
const title = computed(
|
|
() => props.stopping ? i18n.baseText("nodeView.stoppingCurrentExecution") : i18n.baseText("nodeView.stopCurrentExecution")
|
|
);
|
|
return (_ctx, _cache) => {
|
|
const _component_N8nIconButton = _sfc_main$c;
|
|
return openBlock(), createBlock(_component_N8nIconButton, {
|
|
icon: "square",
|
|
size: "large",
|
|
class: "stop-execution",
|
|
type: "secondary",
|
|
title: title.value,
|
|
loading: _ctx.stopping,
|
|
"data-test-id": "stop-execution-button"
|
|
}, null, 8, ["title", "loading"]);
|
|
};
|
|
}
|
|
});
|
|
const _sfc_main$4 = /* @__PURE__ */ defineComponent({
|
|
__name: "CanvasStopWaitingForWebhookButton",
|
|
setup(__props) {
|
|
const i18n = useI18n();
|
|
return (_ctx, _cache) => {
|
|
const _component_N8nIconButton = _sfc_main$c;
|
|
return openBlock(), createBlock(_component_N8nIconButton, {
|
|
class: "stop-execution",
|
|
icon: "square",
|
|
size: "large",
|
|
title: unref(i18n).baseText("nodeView.stopWaitingForWebhookCall"),
|
|
type: "secondary",
|
|
"data-test-id": "stop-execution-waiting-for-webhook-button"
|
|
}, null, 8, ["title"]);
|
|
};
|
|
}
|
|
});
|
|
const _sfc_main$3 = /* @__PURE__ */ defineComponent({
|
|
...{
|
|
name: "CanvasThinkingPill"
|
|
},
|
|
__name: "CanvasThinkingPill",
|
|
props: {
|
|
showStop: { type: Boolean }
|
|
},
|
|
emits: ["stop"],
|
|
setup(__props, { emit: __emit }) {
|
|
const emit = __emit;
|
|
const { t } = useI18n$1();
|
|
const $style = useCssModule();
|
|
return (_ctx, _cache) => {
|
|
return openBlock(), createElementBlock("div", {
|
|
class: normalizeClass(unref($style).thinkingPill)
|
|
}, [
|
|
createBaseVNode("div", {
|
|
class: normalizeClass(unref($style).iconWrapper)
|
|
}, [
|
|
createVNode(_sfc_main$e, { theme: "blank" })
|
|
], 2),
|
|
createBaseVNode("span", {
|
|
class: normalizeClass(unref($style).text)
|
|
}, [
|
|
createTextVNode(toDisplayString(unref(t)("aiAssistant.builder.canvas.thinking")) + " ", 1),
|
|
_ctx.showStop ? (openBlock(), createBlock(unref(N8nButton), {
|
|
key: 0,
|
|
class: normalizeClass(unref($style).stopButton),
|
|
label: "Stop",
|
|
type: "secondary",
|
|
size: "mini",
|
|
onClick: _cache[0] || (_cache[0] = ($event) => emit("stop"))
|
|
}, null, 8, ["class"])) : createCommentVNode("", true)
|
|
], 2)
|
|
], 2);
|
|
};
|
|
}
|
|
});
|
|
const thinkingPill$1 = "_thinkingPill_nk220_123";
|
|
const iconWrapper = "_iconWrapper_nk220_139";
|
|
const stopButton = "_stopButton_nk220_149";
|
|
const text = "_text_nk220_153";
|
|
const style0$1 = {
|
|
thinkingPill: thinkingPill$1,
|
|
iconWrapper,
|
|
stopButton,
|
|
text
|
|
};
|
|
const cssModules$1 = {
|
|
"$style": style0$1
|
|
};
|
|
const CanvasThinkingPill = /* @__PURE__ */ _export_sfc(_sfc_main$3, [["__cssModules", cssModules$1]]);
|
|
const _hoisted_1 = { "data-action": "reload" };
|
|
const _hoisted_2 = {
|
|
href: "https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.wait/",
|
|
target: "_blank"
|
|
};
|
|
const _sfc_main$2 = /* @__PURE__ */ defineComponent({
|
|
__name: "NodeViewUnfinishedWorkflowMessage",
|
|
setup(__props) {
|
|
const i18 = useI18n();
|
|
return (_ctx, _cache) => {
|
|
return openBlock(), createElementBlock("div", null, [
|
|
createBaseVNode("a", _hoisted_1, toDisplayString(unref(i18).baseText("nodeView.refresh")), 1),
|
|
createTextVNode(" " + toDisplayString(unref(i18).baseText("nodeView.toSeeTheLatestStatus")) + ". ", 1),
|
|
_cache[0] || (_cache[0] = createBaseVNode("br", null, null, -1)),
|
|
createBaseVNode("a", _hoisted_2, toDisplayString(unref(i18).baseText("nodeView.moreInfo")), 1)
|
|
]);
|
|
};
|
|
}
|
|
});
|
|
const _sfc_main$1 = /* @__PURE__ */ defineComponent({
|
|
__name: "CanvasChatButton",
|
|
props: {
|
|
label: {},
|
|
type: {}
|
|
},
|
|
setup(__props) {
|
|
return (_ctx, _cache) => {
|
|
const _component_N8nButton = N8nButton;
|
|
return openBlock(), createBlock(_component_N8nButton, {
|
|
label: _ctx.label,
|
|
size: "large",
|
|
icon: "message-circle",
|
|
type: _ctx.type,
|
|
"data-test-id": "workflow-chat-button"
|
|
}, null, 8, ["label", "type"]);
|
|
};
|
|
}
|
|
});
|
|
const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
...{
|
|
name: "NodeView"
|
|
},
|
|
__name: "NodeView",
|
|
setup(__props) {
|
|
const LazyNodeCreation = defineAsyncComponent(
|
|
async () => await __vitePreload(() => import("./NodeCreation-iNUuiza-.js"), true ? __vite__mapDeps([0,1,2,3]) : void 0)
|
|
);
|
|
const LazyNodeDetailsView = defineAsyncComponent(
|
|
async () => await __vitePreload(() => import("./NodeDetailsView-BKEGFeZ7.js"), true ? __vite__mapDeps([4,1,2,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19]) : void 0)
|
|
);
|
|
const LazyNodeDetailsViewV2 = defineAsyncComponent(
|
|
async () => await __vitePreload(() => import("./NodeDetailsViewV2-XPdbzrLu.js"), true ? __vite__mapDeps([20,1,2,5,6,7,8,9,10,11,12,13,14,15,16,17,18,21]) : void 0)
|
|
);
|
|
const LazySetupWorkflowCredentialsButton = defineAsyncComponent(
|
|
async () => await __vitePreload(() => import("./SetupWorkflowCredentialsButton-DMIEMB5C.js"), true ? __vite__mapDeps([22,1,2]) : void 0)
|
|
);
|
|
const $style = useCssModule();
|
|
const router = useRouter();
|
|
const route = useRoute();
|
|
const i18n = useI18n();
|
|
const telemetry = useTelemetry();
|
|
const externalHooks = useExternalHooks();
|
|
const toast = useToast();
|
|
const message = useMessage();
|
|
const documentTitle = useDocumentTitle();
|
|
const workflowHelpers = useWorkflowHelpers();
|
|
const workflowSaving = useWorkflowSaving({ router });
|
|
const nodeHelpers = useNodeHelpers();
|
|
const nodeTypesStore = useNodeTypesStore();
|
|
const uiStore = useUIStore();
|
|
const workflowsStore = useWorkflowsStore();
|
|
const sourceControlStore = useSourceControlStore();
|
|
const nodeCreatorStore = useNodeCreatorStore();
|
|
const settingsStore = useSettingsStore();
|
|
const credentialsStore = useCredentialsStore();
|
|
const environmentsStore = useEnvironmentsStore();
|
|
const externalSecretsStore = useExternalSecretsStore();
|
|
const rootStore = useRootStore();
|
|
const executionsStore = useExecutionsStore();
|
|
const canvasStore = useCanvasStore();
|
|
const npsSurveyStore = useNpsSurveyStore();
|
|
const historyStore = useHistoryStore();
|
|
const projectsStore = useProjectsStore();
|
|
const usersStore = useUsersStore();
|
|
const tagsStore = useTagsStore();
|
|
const pushConnectionStore = usePushConnectionStore();
|
|
const ndvStore = useNDVStore();
|
|
const focusPanelStore = useFocusPanelStore();
|
|
const templatesStore = useTemplatesStore();
|
|
const builderStore = useBuilderStore();
|
|
const foldersStore = useFoldersStore();
|
|
const posthogStore = usePostHog();
|
|
const agentRequestStore = useAgentRequestStore();
|
|
const logsStore = useLogsStore();
|
|
const aiTemplatesStarterCollectionStore = useAITemplatesStarterCollectionStore();
|
|
const readyToRunWorkflowsStore = useReadyToRunWorkflowsStore();
|
|
const { addBeforeUnloadEventBindings, removeBeforeUnloadEventBindings } = useBeforeUnload({
|
|
route
|
|
});
|
|
const { registerCustomAction, unregisterCustomAction } = useGlobalLinkActions();
|
|
const { runWorkflow, runEntireWorkflow, stopCurrentExecution, stopWaitingForWebhook } = useRunWorkflow({ router });
|
|
const {
|
|
updateNodePosition,
|
|
updateNodesPosition,
|
|
tidyUp,
|
|
revertUpdateNodePosition,
|
|
renameNode,
|
|
revertRenameNode,
|
|
revertReplaceNodeParameters,
|
|
setNodeActive,
|
|
setNodeSelected,
|
|
toggleNodesDisabled,
|
|
revertToggleNodeDisabled,
|
|
toggleNodesPinned,
|
|
setNodeParameters,
|
|
deleteNode,
|
|
deleteNodes,
|
|
copyNodes,
|
|
cutNodes,
|
|
duplicateNodes,
|
|
revertDeleteNode,
|
|
addNodes,
|
|
importTemplate,
|
|
revertAddNode,
|
|
createConnection,
|
|
revertCreateConnection,
|
|
deleteConnection,
|
|
revertDeleteConnection,
|
|
revalidateNodeInputConnections,
|
|
revalidateNodeOutputConnections,
|
|
setNodeActiveByName,
|
|
clearNodeActive,
|
|
addConnections,
|
|
tryToOpenSubworkflowInNewTab,
|
|
importWorkflowData,
|
|
fetchWorkflowDataFromUrl,
|
|
resetWorkspace,
|
|
initializeWorkspace,
|
|
openExecution,
|
|
editableWorkflow,
|
|
editableWorkflowObject,
|
|
lastClickPosition,
|
|
startChat
|
|
} = useCanvasOperations();
|
|
const { extractWorkflow } = useWorkflowExtraction();
|
|
const { applyExecutionData } = useExecutionDebugging();
|
|
useClipboard({ onPaste: onClipboardPaste });
|
|
useKeybindings({
|
|
ctrl_alt_o: () => uiStore.openModal(ABOUT_MODAL_KEY)
|
|
});
|
|
const isLoading = ref(true);
|
|
const isBlankRedirect = ref(false);
|
|
const readOnlyNotification = ref(null);
|
|
const isProductionExecutionPreview = ref(false);
|
|
const isExecutionPreview = ref(false);
|
|
const canOpenNDV = ref(true);
|
|
const hideNodeIssues = ref(false);
|
|
const fallbackNodes = ref([]);
|
|
const initializedWorkflowId = ref();
|
|
const workflowId = computed(() => {
|
|
const workflowIdParam = route.params.name;
|
|
return [PLACEHOLDER_EMPTY_WORKFLOW_ID, NEW_WORKFLOW_ID].includes(workflowIdParam) ? void 0 : workflowIdParam;
|
|
});
|
|
const routeNodeId = computed(() => route.params.nodeId);
|
|
const isNewWorkflowRoute = computed(() => route.name === VIEWS.NEW_WORKFLOW || !workflowId.value);
|
|
const isWorkflowRoute = computed(() => !!route?.meta?.nodeView || isDemoRoute.value);
|
|
const isDemoRoute = computed(() => route.name === VIEWS.DEMO);
|
|
const isReadOnlyRoute = computed(() => !!route?.meta?.readOnlyCanvas);
|
|
const isReadOnlyEnvironment = computed(() => {
|
|
return sourceControlStore.preferences.branchReadOnly;
|
|
});
|
|
const isNDVV2 = computed(
|
|
() => posthogStore.isVariantEnabled(
|
|
NDV_UI_OVERHAUL_EXPERIMENT.name,
|
|
NDV_UI_OVERHAUL_EXPERIMENT.variant
|
|
)
|
|
);
|
|
const isCanvasReadOnly = computed(() => {
|
|
return isDemoRoute.value || isReadOnlyEnvironment.value || !(workflowPermissions.value.update ?? projectPermissions.value.workflow.update) || editableWorkflow.value.isArchived || builderStore.streaming;
|
|
});
|
|
const showFallbackNodes = computed(() => triggerNodes.value.length === 0);
|
|
const keyBindingsEnabled = computed(() => {
|
|
return !ndvStore.activeNode && uiStore.activeModals.length === 0;
|
|
});
|
|
const isLogsPanelOpen = computed(() => logsStore.isOpen);
|
|
async function initializeData() {
|
|
const loadPromises = (() => {
|
|
if (settingsStore.isPreviewMode && isDemoRoute.value) return [];
|
|
const promises = [
|
|
workflowsStore.fetchActiveWorkflows(),
|
|
credentialsStore.fetchAllCredentials(),
|
|
credentialsStore.fetchCredentialTypes(true)
|
|
];
|
|
if (settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.Variables]) {
|
|
promises.push(environmentsStore.fetchAllVariables());
|
|
}
|
|
if (settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.ExternalSecrets]) {
|
|
promises.push(externalSecretsStore.fetchAllSecrets());
|
|
}
|
|
return promises;
|
|
})();
|
|
if (nodeTypesStore.allNodeTypes.length === 0) {
|
|
loadPromises.push(nodeTypesStore.getNodeTypes());
|
|
}
|
|
try {
|
|
await Promise.all(loadPromises);
|
|
void nodeTypesStore.fetchCommunityNodePreviews();
|
|
} catch (error) {
|
|
toast.showError(
|
|
error,
|
|
i18n.baseText("nodeView.showError.mounted1.title"),
|
|
i18n.baseText("nodeView.showError.mounted1.message") + ":"
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
async function initializeRoute(force = false) {
|
|
if (route.query.action === "workflowSave") {
|
|
uiStore.stateIsDirty = false;
|
|
await router.replace({
|
|
query: { ...route.query, action: void 0 }
|
|
});
|
|
return;
|
|
}
|
|
if (route.query.action === "addEvaluationTrigger") {
|
|
nodeCreatorStore.openNodeCreatorForTriggerNodes(
|
|
NODE_CREATOR_OPEN_SOURCES.ADD_EVALUATION_TRIGGER_BUTTON
|
|
);
|
|
} else if (route.query.action === "addEvaluationNode") {
|
|
nodeCreatorStore.openNodeCreatorForActions(
|
|
EVALUATION_NODE_TYPE,
|
|
NODE_CREATOR_OPEN_SOURCES.ADD_EVALUATION_NODE_BUTTON
|
|
);
|
|
} else if (route.query.action === "executeEvaluation") {
|
|
if (evaluationTriggerNode.value) {
|
|
void runEntireWorkflow("node", evaluationTriggerNode.value.name);
|
|
}
|
|
}
|
|
const isAlreadyInitialized = !force && initializedWorkflowId.value && [NEW_WORKFLOW_ID, workflowId.value].includes(initializedWorkflowId.value);
|
|
if (isBlankRedirect.value) {
|
|
isBlankRedirect.value = false;
|
|
} else if (route.name === VIEWS.TEMPLATE_IMPORT) {
|
|
const loadWorkflowFromJSON = route.query.fromJson === "true";
|
|
const templateId = route.params.id;
|
|
if (!templateId) {
|
|
return;
|
|
}
|
|
if (loadWorkflowFromJSON) {
|
|
const workflow = getSampleWorkflowByTemplateId(templateId.toString());
|
|
if (!workflow) {
|
|
toast.showError(
|
|
new Error(i18n.baseText("nodeView.couldntLoadWorkflow.invalidWorkflowObject")),
|
|
i18n.baseText("nodeView.couldntImportWorkflow")
|
|
);
|
|
await router.replace({ name: VIEWS.NEW_WORKFLOW });
|
|
return;
|
|
}
|
|
await openTemplateFromWorkflowJSON(workflow);
|
|
} else {
|
|
await openWorkflowTemplate(templateId.toString());
|
|
}
|
|
} else if (isWorkflowRoute.value) {
|
|
if (!isAlreadyInitialized) {
|
|
historyStore.reset();
|
|
if (!isDemoRoute.value) {
|
|
await loadCredentials();
|
|
}
|
|
if (isNewWorkflowRoute.value || !workflowId.value) {
|
|
if (route.meta?.nodeView === true) {
|
|
await initializeWorkspaceForNewWorkflow();
|
|
}
|
|
return;
|
|
}
|
|
await initializeWorkspaceForExistingWorkflow(workflowId.value);
|
|
void nextTick(() => {
|
|
updateNodesIssues();
|
|
});
|
|
}
|
|
if (route.name === VIEWS.EXECUTION_DEBUG) {
|
|
await initializeDebugMode();
|
|
}
|
|
}
|
|
}
|
|
async function initializeWorkspaceForNewWorkflow() {
|
|
resetWorkspace();
|
|
const parentFolderId = route.query.parentFolderId;
|
|
await workflowsStore.getNewWorkflowDataAndMakeShareable(
|
|
void 0,
|
|
projectsStore.currentProjectId,
|
|
parentFolderId
|
|
);
|
|
if (projectsStore.currentProjectId) {
|
|
await fetchAndSetProject(projectsStore.currentProjectId);
|
|
}
|
|
await fetchAndSetParentFolder(parentFolderId);
|
|
uiStore.nodeViewInitialized = true;
|
|
initializedWorkflowId.value = NEW_WORKFLOW_ID;
|
|
}
|
|
async function fetchAndSetParentFolder(folderId) {
|
|
if (folderId) {
|
|
let parentFolder = foldersStore.getCachedFolder(folderId);
|
|
if (!parentFolder && projectsStore.currentProjectId) {
|
|
await foldersStore.getFolderPath(projectsStore.currentProjectId, folderId);
|
|
parentFolder = foldersStore.getCachedFolder(folderId);
|
|
}
|
|
if (parentFolder) {
|
|
workflowsStore.setParentFolder({
|
|
...parentFolder,
|
|
parentFolderId: parentFolder.parentFolder ?? null
|
|
});
|
|
}
|
|
}
|
|
}
|
|
async function fetchAndSetProject(projectId) {
|
|
if (!projectsStore.currentProject) {
|
|
const project = await projectsStore.fetchProject(projectId);
|
|
projectsStore.setCurrentProject(project);
|
|
}
|
|
}
|
|
async function initializeWorkspaceForExistingWorkflow(id) {
|
|
try {
|
|
const workflowData = await workflowsStore.fetchWorkflow(id);
|
|
openWorkflow(workflowData);
|
|
if (workflowData.parentFolder) {
|
|
workflowsStore.setParentFolder(workflowData.parentFolder);
|
|
}
|
|
if (workflowData.meta?.onboardingId) {
|
|
trackOpenWorkflowFromOnboardingTemplate();
|
|
}
|
|
if (workflowData.meta?.templateId?.startsWith("035_template_onboarding")) {
|
|
aiTemplatesStarterCollectionStore.trackUserOpenedWorkflow(
|
|
workflowData.meta.templateId.split("-").pop() ?? ""
|
|
);
|
|
}
|
|
if (workflowData.meta?.templateId?.startsWith("37_onboarding_experiments_batch_aug11")) {
|
|
readyToRunWorkflowsStore.trackOpenWorkflow(
|
|
workflowData.meta.templateId.split("-").pop() ?? ""
|
|
);
|
|
}
|
|
await projectsStore.setProjectNavActiveIdByWorkflowHomeProject(workflowData.homeProject);
|
|
} catch (error) {
|
|
if (error.httpStatusCode === 404) {
|
|
return await router.replace({
|
|
name: VIEWS.ENTITY_NOT_FOUND,
|
|
params: { entityType: "workflow" }
|
|
});
|
|
}
|
|
if (error.httpStatusCode === 403) {
|
|
return await router.replace({
|
|
name: VIEWS.ENTITY_UNAUTHORIZED,
|
|
params: { entityType: "workflow" }
|
|
});
|
|
}
|
|
toast.showError(error, i18n.baseText("openWorkflow.workflowNotFoundError"));
|
|
void router.push({
|
|
name: VIEWS.NEW_WORKFLOW
|
|
});
|
|
} finally {
|
|
uiStore.nodeViewInitialized = true;
|
|
initializedWorkflowId.value = workflowId.value;
|
|
}
|
|
}
|
|
function updateNodesIssues() {
|
|
nodeHelpers.updateNodesInputIssues();
|
|
nodeHelpers.updateNodesCredentialsIssues();
|
|
nodeHelpers.updateNodesParameterIssues();
|
|
}
|
|
function openWorkflow(data) {
|
|
resetWorkspace();
|
|
workflowHelpers.setDocumentTitle(data.name, "IDLE");
|
|
initializeWorkspace(data);
|
|
void externalHooks.run("workflow.open", {
|
|
workflowId: data.id,
|
|
workflowName: data.name
|
|
});
|
|
fitView();
|
|
}
|
|
function trackOpenWorkflowFromOnboardingTemplate() {
|
|
telemetry.track(
|
|
`User opened workflow from onboarding template with ID ${editableWorkflow.value.meta?.onboardingId}`,
|
|
{
|
|
workflow_id: workflowId.value
|
|
}
|
|
);
|
|
}
|
|
async function openTemplateFromWorkflowJSON(workflow) {
|
|
if (!workflow.nodes || !workflow.connections) {
|
|
toast.showError(
|
|
new Error(i18n.baseText("nodeView.couldntLoadWorkflow.invalidWorkflowObject")),
|
|
i18n.baseText("nodeView.couldntImportWorkflow")
|
|
);
|
|
await router.replace({ name: VIEWS.NEW_WORKFLOW });
|
|
return;
|
|
}
|
|
resetWorkspace();
|
|
canvasStore.startLoading();
|
|
canvasStore.setLoadingText(i18n.baseText("nodeView.loadingTemplate"));
|
|
workflowsStore.currentWorkflowExecutions = [];
|
|
executionsStore.activeExecution = null;
|
|
isBlankRedirect.value = true;
|
|
const templateId = workflow.meta.templateId;
|
|
const parentFolderId = route.query.parentFolderId;
|
|
await router.replace({
|
|
name: VIEWS.NEW_WORKFLOW,
|
|
query: { templateId, parentFolderId }
|
|
});
|
|
await importTemplate({
|
|
id: templateId,
|
|
name: workflow.name,
|
|
workflow
|
|
});
|
|
uiStore.stateIsDirty = true;
|
|
canvasStore.stopLoading();
|
|
fitView();
|
|
}
|
|
async function openWorkflowTemplate(templateId) {
|
|
resetWorkspace();
|
|
canvasStore.startLoading();
|
|
canvasStore.setLoadingText(i18n.baseText("nodeView.loadingTemplate"));
|
|
workflowsStore.currentWorkflowExecutions = [];
|
|
executionsStore.activeExecution = null;
|
|
let data;
|
|
try {
|
|
void externalHooks.run("template.requested", { templateId });
|
|
data = await templatesStore.getFixedWorkflowTemplate(templateId);
|
|
if (!data) {
|
|
throw new Error(
|
|
i18n.baseText("nodeView.workflowTemplateWithIdCouldNotBeFound", {
|
|
interpolate: { templateId }
|
|
})
|
|
);
|
|
}
|
|
} catch (error) {
|
|
toast.showError(error, i18n.baseText("nodeView.couldntImportWorkflow"));
|
|
await router.replace({ name: VIEWS.NEW_WORKFLOW });
|
|
return;
|
|
}
|
|
trackOpenWorkflowTemplate(templateId);
|
|
isBlankRedirect.value = true;
|
|
await router.replace({ name: VIEWS.NEW_WORKFLOW, query: { templateId } });
|
|
await importTemplate({ id: templateId, name: data.name, workflow: data.workflow });
|
|
uiStore.stateIsDirty = true;
|
|
canvasStore.stopLoading();
|
|
void externalHooks.run("template.open", {
|
|
templateId,
|
|
templateName: data.name,
|
|
workflow: data.workflow
|
|
});
|
|
fitView();
|
|
}
|
|
function trackOpenWorkflowTemplate(templateId) {
|
|
telemetry.track("User inserted workflow template", {
|
|
source: "workflow",
|
|
template_id: tryToParseNumber(templateId),
|
|
wf_template_repo_session_id: templatesStore.previousSessionId
|
|
});
|
|
}
|
|
const triggerNodes = computed(() => {
|
|
return editableWorkflow.value.nodes.filter(
|
|
(node) => node.type === START_NODE_TYPE || nodeTypesStore.isTriggerNode(node.type)
|
|
);
|
|
});
|
|
const containsTriggerNodes = computed(() => triggerNodes.value.length > 0);
|
|
const allTriggerNodesDisabled = computed(() => {
|
|
const disabledTriggerNodes = triggerNodes.value.filter((node) => node.disabled);
|
|
return disabledTriggerNodes.length === triggerNodes.value.length;
|
|
});
|
|
function onTidyUp(event) {
|
|
tidyUp(event);
|
|
}
|
|
function onExtractWorkflow(nodeIds) {
|
|
void extractWorkflow(nodeIds);
|
|
}
|
|
function onUpdateNodesPosition(events) {
|
|
updateNodesPosition(events, { trackHistory: true });
|
|
}
|
|
function onUpdateNodePosition(id, position) {
|
|
updateNodePosition(id, position, { trackHistory: true });
|
|
}
|
|
function onRevertNodePosition({ nodeName, position }) {
|
|
revertUpdateNodePosition(nodeName, { x: position[0], y: position[1] });
|
|
}
|
|
function onDeleteNode(id) {
|
|
const matchedFallbackNode = fallbackNodes.value.findIndex((node) => node.id === id);
|
|
if (matchedFallbackNode >= 0) {
|
|
fallbackNodes.value.splice(matchedFallbackNode, 1);
|
|
} else {
|
|
deleteNode(id, { trackHistory: true });
|
|
}
|
|
}
|
|
function onDeleteNodes(ids) {
|
|
deleteNodes(ids);
|
|
}
|
|
function onRevertDeleteNode({ node }) {
|
|
revertDeleteNode(node);
|
|
}
|
|
function onToggleNodeDisabled(id) {
|
|
if (!checkIfEditingIsAllowed()) {
|
|
return;
|
|
}
|
|
toggleNodesDisabled([id]);
|
|
}
|
|
function onRevertToggleNodeDisabled({ nodeName }) {
|
|
revertToggleNodeDisabled(nodeName);
|
|
}
|
|
function onToggleNodesDisabled(ids) {
|
|
if (!checkIfEditingIsAllowed()) {
|
|
return;
|
|
}
|
|
toggleNodesDisabled(ids);
|
|
}
|
|
function onClickNode(_id, event) {
|
|
lastClickPosition.value = [event.x, event.y];
|
|
closeNodeCreator();
|
|
}
|
|
function onSetNodeActivated(id, event) {
|
|
if (event?.metaKey || event?.ctrlKey) {
|
|
const didOpen = tryToOpenSubworkflowInNewTab(id);
|
|
if (didOpen) {
|
|
return;
|
|
}
|
|
}
|
|
setNodeActive(id, "canvas_default_view");
|
|
}
|
|
function onOpenSubWorkflow(id) {
|
|
tryToOpenSubworkflowInNewTab(id);
|
|
}
|
|
function onSetNodeDeactivated() {
|
|
clearNodeActive();
|
|
}
|
|
function onSetNodeSelected(id) {
|
|
closeNodeCreator();
|
|
setNodeSelected(id);
|
|
}
|
|
async function onCopyNodes(ids) {
|
|
await copyNodes(ids);
|
|
toast.showMessage({ title: i18n.baseText("generic.copiedToClipboard"), type: "success" });
|
|
}
|
|
async function onClipboardPaste(plainTextData) {
|
|
if (getNodeViewTab(route) !== MAIN_HEADER_TABS.WORKFLOW || !keyBindingsEnabled.value || !checkIfEditingIsAllowed()) {
|
|
return;
|
|
}
|
|
let workflowData = null;
|
|
if (plainTextData.match(VALID_WORKFLOW_IMPORT_URL_REGEX)) {
|
|
const importConfirm = await message.confirm(
|
|
i18n.baseText("nodeView.confirmMessage.onClipboardPasteEvent.message", {
|
|
interpolate: { plainTextData }
|
|
}),
|
|
i18n.baseText("nodeView.confirmMessage.onClipboardPasteEvent.headline"),
|
|
{
|
|
type: "warning",
|
|
confirmButtonText: i18n.baseText(
|
|
"nodeView.confirmMessage.onClipboardPasteEvent.confirmButtonText"
|
|
),
|
|
cancelButtonText: i18n.baseText(
|
|
"nodeView.confirmMessage.onClipboardPasteEvent.cancelButtonText"
|
|
)
|
|
}
|
|
);
|
|
if (importConfirm !== MODAL_CONFIRM) {
|
|
return;
|
|
}
|
|
workflowData = await fetchWorkflowDataFromUrl(plainTextData);
|
|
} else {
|
|
workflowData = jsonParse(plainTextData, { fallbackValue: null });
|
|
}
|
|
if (!workflowData) {
|
|
return;
|
|
}
|
|
const result = await importWorkflowData(workflowData, "paste", {
|
|
importTags: false,
|
|
viewport: viewportBoundaries.value
|
|
});
|
|
selectNodes(result.nodes?.map((node) => node.id) ?? []);
|
|
}
|
|
async function onCutNodes(ids) {
|
|
if (isCanvasReadOnly.value) {
|
|
await copyNodes(ids);
|
|
} else {
|
|
await cutNodes(ids);
|
|
}
|
|
}
|
|
async function onDuplicateNodes(ids) {
|
|
if (!checkIfEditingIsAllowed()) {
|
|
return;
|
|
}
|
|
const newIds = await duplicateNodes(ids, {
|
|
viewport: viewportBoundaries.value
|
|
});
|
|
selectNodes(newIds);
|
|
}
|
|
function onPinNodes(ids, source) {
|
|
if (!checkIfEditingIsAllowed()) {
|
|
return;
|
|
}
|
|
toggleNodesPinned(ids, source);
|
|
}
|
|
async function onSaveWorkflow() {
|
|
const workflowIsSaved = !uiStore.stateIsDirty && !workflowsStore.isNewWorkflow;
|
|
const workflowIsArchived = workflowsStore.workflow.isArchived;
|
|
if (workflowIsSaved || workflowIsArchived) {
|
|
return;
|
|
}
|
|
const saved = await workflowSaving.saveCurrentWorkflow();
|
|
if (saved) {
|
|
canvasEventBus.emit("saved:workflow");
|
|
}
|
|
}
|
|
function addWorkflowSavedEventBindings() {
|
|
canvasEventBus.on("saved:workflow", npsSurveyStore.fetchPromptsData);
|
|
canvasEventBus.on("saved:workflow", onSaveFromWithinNDV);
|
|
}
|
|
function removeWorkflowSavedEventBindings() {
|
|
canvasEventBus.off("saved:workflow", npsSurveyStore.fetchPromptsData);
|
|
canvasEventBus.off("saved:workflow", onSaveFromWithinNDV);
|
|
canvasEventBus.off("saved:workflow", onSaveFromWithinExecutionDebug);
|
|
}
|
|
async function onSaveFromWithinNDV() {
|
|
if (ndvStore.activeNodeName) {
|
|
toast.showMessage({
|
|
title: i18n.baseText("generic.workflowSaved"),
|
|
type: "success"
|
|
});
|
|
}
|
|
}
|
|
async function onCreateWorkflow() {
|
|
await router.push({ name: VIEWS.NEW_WORKFLOW });
|
|
}
|
|
function onRenameNode(name) {
|
|
if (ndvStore.activeNode?.name) {
|
|
void renameNode(ndvStore.activeNode.name, name);
|
|
}
|
|
}
|
|
async function onOpenRenameNodeModal(id) {
|
|
const currentName = workflowsStore.getNodeById(id)?.name ?? "";
|
|
const activeElement = document.activeElement;
|
|
if (activeElement && shouldIgnoreCanvasShortcut(activeElement)) {
|
|
return;
|
|
}
|
|
if (!keyBindingsEnabled.value || document.querySelector(".rename-prompt")) return;
|
|
try {
|
|
const promptResponsePromise = message.prompt(
|
|
i18n.baseText("nodeView.prompt.newName") + ":",
|
|
i18n.baseText("nodeView.prompt.renameNode") + `: ${currentName}`,
|
|
{
|
|
customClass: "rename-prompt",
|
|
confirmButtonText: i18n.baseText("nodeView.prompt.rename"),
|
|
cancelButtonText: i18n.baseText("nodeView.prompt.cancel"),
|
|
inputErrorMessage: i18n.baseText("nodeView.prompt.invalidName"),
|
|
inputValue: currentName,
|
|
inputValidator: (value) => {
|
|
if (!value.trim()) {
|
|
return i18n.baseText("nodeView.prompt.invalidName");
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
);
|
|
await nextTick();
|
|
const nameInput = document.querySelector(".rename-prompt .el-input__inner");
|
|
nameInput?.focus();
|
|
nameInput?.select();
|
|
const promptResponse = await promptResponsePromise;
|
|
if (promptResponse.action === MODAL_CONFIRM) {
|
|
await renameNode(currentName, promptResponse.value, { trackHistory: true });
|
|
}
|
|
} catch (e) {
|
|
}
|
|
}
|
|
async function onRevertRenameNode({
|
|
currentName,
|
|
newName
|
|
}) {
|
|
await revertRenameNode(currentName, newName);
|
|
}
|
|
async function onRevertReplaceNodeParameters({
|
|
nodeId,
|
|
currentProperties,
|
|
newProperties
|
|
}) {
|
|
await revertReplaceNodeParameters(nodeId, currentProperties, newProperties);
|
|
}
|
|
function onUpdateNodeParameters(id, parameters) {
|
|
setNodeParameters(id, parameters);
|
|
}
|
|
function onUpdateNodeInputs(id) {
|
|
revalidateNodeInputConnections(id);
|
|
}
|
|
function onUpdateNodeOutputs(id) {
|
|
revalidateNodeOutputConnections(id);
|
|
}
|
|
function onClickNodeAdd(source, sourceHandle) {
|
|
nodeCreatorStore.openNodeCreatorForConnectingNode({
|
|
connection: {
|
|
source,
|
|
sourceHandle
|
|
},
|
|
eventSource: NODE_CREATOR_OPEN_SOURCES.PLUS_ENDPOINT
|
|
});
|
|
}
|
|
async function loadCredentials() {
|
|
let options;
|
|
if (workflowId.value) {
|
|
options = { workflowId: workflowId.value };
|
|
} else {
|
|
const queryParam = typeof route.query?.projectId === "string" ? route.query?.projectId : void 0;
|
|
const projectId = queryParam ?? projectsStore.personalProject?.id;
|
|
if (projectId === void 0) {
|
|
throw new Error(
|
|
"Could not find projectId in the query nor could I find the personal project in the project store"
|
|
);
|
|
}
|
|
options = { projectId };
|
|
}
|
|
await credentialsStore.fetchAllCredentialsForWorkflow(options);
|
|
}
|
|
function onCreateConnection(connection) {
|
|
createConnection(connection, { trackHistory: true });
|
|
}
|
|
function onRevertCreateConnection({ connection }) {
|
|
revertCreateConnection(connection);
|
|
}
|
|
function onCreateConnectionCancelled(event, position, mouseEvent) {
|
|
const preventDefault = (mouseEvent?.target).classList?.contains("clickable");
|
|
if (preventDefault) {
|
|
return;
|
|
}
|
|
uiStore.lastInteractedWithNodeId = event.nodeId;
|
|
uiStore.lastInteractedWithNodeHandle = event.handleId;
|
|
uiStore.lastCancelledConnectionPosition = [position.x, position.y];
|
|
setTimeout(() => {
|
|
if (!event.nodeId) return;
|
|
nodeCreatorStore.openNodeCreatorForConnectingNode({
|
|
connection: {
|
|
source: event.nodeId,
|
|
sourceHandle: event.handleId
|
|
},
|
|
eventSource: NODE_CREATOR_OPEN_SOURCES.NODE_CONNECTION_DROP
|
|
});
|
|
});
|
|
}
|
|
function onDeleteConnection(connection) {
|
|
deleteConnection(connection, { trackHistory: true });
|
|
}
|
|
function onRevertDeleteConnection({ connection }) {
|
|
revertDeleteConnection(connection);
|
|
}
|
|
async function importWorkflowExact({ workflow: workflowData }) {
|
|
if (!workflowData.nodes || !workflowData.connections) {
|
|
throw new Error("Invalid workflow object");
|
|
}
|
|
resetWorkspace();
|
|
await initializeData();
|
|
initializeWorkspace({
|
|
...workflowData,
|
|
nodes: getNodesWithNormalizedPosition(workflowData.nodes)
|
|
});
|
|
fitView();
|
|
}
|
|
async function onImportWorkflowDataEvent(data) {
|
|
const workflowData = data.data;
|
|
await importWorkflowData(workflowData, "file", {
|
|
viewport: viewportBoundaries.value,
|
|
regenerateIds: data.regenerateIds === true || data.regenerateIds === void 0
|
|
});
|
|
fitView();
|
|
selectNodes(workflowData.nodes?.map((node) => node.id) ?? []);
|
|
if (data.tidyUp) {
|
|
const nodesIdsToTidyUp = data.nodesIdsToTidyUp;
|
|
setTimeout(() => {
|
|
canvasEventBus.emit("tidyUp", {
|
|
source: "import-workflow-data",
|
|
nodeIdsFilter: nodesIdsToTidyUp
|
|
});
|
|
}, 0);
|
|
}
|
|
}
|
|
async function onImportWorkflowUrlEvent(data) {
|
|
const workflowData = await fetchWorkflowDataFromUrl(data.url);
|
|
if (!workflowData) {
|
|
return;
|
|
}
|
|
await importWorkflowData(workflowData, "url", {
|
|
viewport: viewportBoundaries.value
|
|
});
|
|
fitView();
|
|
selectNodes(workflowData.nodes?.map((node) => node.id) ?? []);
|
|
}
|
|
function addImportEventBindings() {
|
|
nodeViewEventBus.on("importWorkflowData", onImportWorkflowDataEvent);
|
|
nodeViewEventBus.on("importWorkflowUrl", onImportWorkflowUrlEvent);
|
|
nodeViewEventBus.on("openChat", onOpenChat);
|
|
}
|
|
function removeImportEventBindings() {
|
|
nodeViewEventBus.off("importWorkflowData", onImportWorkflowDataEvent);
|
|
nodeViewEventBus.off("importWorkflowUrl", onImportWorkflowUrlEvent);
|
|
nodeViewEventBus.off("openChat", onOpenChat);
|
|
}
|
|
async function onAddNodesAndConnections({ nodes, connections }, dragAndDrop = false, position) {
|
|
if (!checkIfEditingIsAllowed()) {
|
|
return;
|
|
}
|
|
const addedNodes = await addNodes(nodes, {
|
|
dragAndDrop,
|
|
position,
|
|
viewport: viewportBoundaries.value,
|
|
trackHistory: true,
|
|
telemetry: true
|
|
});
|
|
const offsetIndex = editableWorkflow.value.nodes.length - nodes.length;
|
|
const mappedConnections = connections.map(({ from, to }) => {
|
|
const fromNode = editableWorkflow.value.nodes[offsetIndex + from.nodeIndex];
|
|
const toNode = editableWorkflow.value.nodes[offsetIndex + to.nodeIndex];
|
|
const type = from.type ?? to.type ?? NodeConnectionTypes.Main;
|
|
return {
|
|
source: fromNode.id,
|
|
sourceHandle: createCanvasConnectionHandleString({
|
|
mode: CanvasConnectionMode.Output,
|
|
type: isValidNodeConnectionType(type) ? type : NodeConnectionTypes.Main,
|
|
index: from.outputIndex ?? 0
|
|
}),
|
|
target: toNode.id,
|
|
targetHandle: createCanvasConnectionHandleString({
|
|
mode: CanvasConnectionMode.Input,
|
|
type: isValidNodeConnectionType(type) ? type : NodeConnectionTypes.Main,
|
|
index: to.inputIndex ?? 0
|
|
}),
|
|
data: {
|
|
source: {
|
|
index: from.outputIndex ?? 0,
|
|
type
|
|
},
|
|
target: {
|
|
index: to.inputIndex ?? 0,
|
|
type
|
|
}
|
|
}
|
|
};
|
|
});
|
|
await addConnections(mappedConnections);
|
|
uiStore.resetLastInteractedWith();
|
|
if (addedNodes.length > 0) {
|
|
selectNodes([addedNodes[addedNodes.length - 1].id]);
|
|
}
|
|
}
|
|
async function onRevertAddNode({ node }) {
|
|
await revertAddNode(node.name);
|
|
}
|
|
function onSwitchActiveNode(nodeName) {
|
|
const node = workflowsStore.getNodeByName(nodeName);
|
|
if (!node) return;
|
|
setNodeActiveByName(nodeName, "other");
|
|
selectNodes([node.id]);
|
|
}
|
|
function onOpenSelectiveNodeCreator(node, connectionType, connectionIndex = 0) {
|
|
nodeCreatorStore.openSelectiveNodeCreator({ node, connectionType, connectionIndex });
|
|
}
|
|
function onToggleNodeCreator(options) {
|
|
nodeCreatorStore.setNodeCreatorState(options);
|
|
if (!options.createNodeActive && !options.hasAddedNodes) {
|
|
uiStore.resetLastInteractedWith();
|
|
}
|
|
}
|
|
function onOpenNodeCreatorFromCanvas(source) {
|
|
onToggleNodeCreator({ createNodeActive: true, source });
|
|
}
|
|
function onOpenNodeCreatorForTriggerNodes(source) {
|
|
nodeCreatorStore.openNodeCreatorForTriggerNodes(source);
|
|
}
|
|
function onToggleFocusPanel() {
|
|
focusPanelStore.toggleFocusPanel();
|
|
telemetry.track(`User ${focusPanelStore.focusPanelActive ? "opened" : "closed"} focus panel`, {
|
|
source: "canvasKeyboardShortcut",
|
|
parameters: focusPanelStore.focusedNodeParametersInTelemetryFormat,
|
|
parameterCount: focusPanelStore.focusedNodeParametersInTelemetryFormat.length
|
|
});
|
|
}
|
|
function closeNodeCreator() {
|
|
if (nodeCreatorStore.isCreateNodeActive) {
|
|
nodeCreatorStore.isCreateNodeActive = false;
|
|
}
|
|
}
|
|
function onCreateSticky() {
|
|
void onAddNodesAndConnections({ nodes: [{ type: STICKY_NODE_TYPE }], connections: [] });
|
|
}
|
|
function onClickConnectionAdd(connection) {
|
|
nodeCreatorStore.openNodeCreatorForConnectingNode({
|
|
connection,
|
|
eventSource: NODE_CREATOR_OPEN_SOURCES.NODE_CONNECTION_ACTION
|
|
});
|
|
}
|
|
const workflowPermissions = computed(() => {
|
|
return workflowId.value ? getResourcePermissions(workflowsStore.getWorkflowById(workflowId.value)?.scopes).workflow : {};
|
|
});
|
|
const projectPermissions = computed(() => {
|
|
const project = route.query?.projectId ? projectsStore.myProjects.find((p) => p.id === route.query.projectId) : projectsStore.currentProject ?? projectsStore.personalProject;
|
|
return getResourcePermissions(project?.scopes);
|
|
});
|
|
const isStoppingExecution = ref(false);
|
|
const isWorkflowRunning = computed(() => workflowsStore.isWorkflowRunning);
|
|
const isExecutionWaitingForWebhook = computed(() => workflowsStore.executionWaitingForWebhook);
|
|
const isExecutionDisabled = computed(() => {
|
|
if (containsChatTriggerNodes.value && isOnlyChatTriggerNodeActive.value && !chatTriggerNodePinnedData.value) {
|
|
return true;
|
|
}
|
|
return !containsTriggerNodes.value || allTriggerNodesDisabled.value;
|
|
});
|
|
const isRunWorkflowButtonVisible = computed(
|
|
() => !isOnlyChatTriggerNodeActive.value || chatTriggerNodePinnedData.value
|
|
);
|
|
const isStopExecutionButtonVisible = computed(
|
|
() => isWorkflowRunning.value && !isExecutionWaitingForWebhook.value
|
|
);
|
|
const isStopWaitingForWebhookButtonVisible = computed(
|
|
() => isWorkflowRunning.value && isExecutionWaitingForWebhook.value
|
|
);
|
|
async function onRunWorkflowToNode(id) {
|
|
const node = workflowsStore.getNodeById(id);
|
|
if (!node) return;
|
|
if (needsAgentInput(node) && nodeTypesStore.isToolNode(node.type)) {
|
|
uiStore.openModalWithData({
|
|
name: FROM_AI_PARAMETERS_MODAL_KEY,
|
|
data: {
|
|
nodeName: node.name
|
|
}
|
|
});
|
|
} else {
|
|
trackRunWorkflowToNode(node);
|
|
agentRequestStore.clearAgentRequests(workflowsStore.workflowId, node.id);
|
|
void runWorkflow({ destinationNode: node.name, source: "Node.executeNode" });
|
|
}
|
|
}
|
|
function trackRunWorkflowToNode(node) {
|
|
const telemetryPayload = {
|
|
node_type: node.type,
|
|
workflow_id: workflowsStore.workflowId,
|
|
source: "canvas",
|
|
push_ref: ndvStore.pushRef
|
|
};
|
|
telemetry.track("User clicked execute node button", telemetryPayload);
|
|
void externalHooks.run("nodeView.onRunNode", telemetryPayload);
|
|
}
|
|
async function onOpenExecution(executionId, nodeId) {
|
|
canvasStore.startLoading();
|
|
resetWorkspace();
|
|
await initializeData();
|
|
const data = await openExecution(executionId, nodeId);
|
|
if (!data) {
|
|
return;
|
|
}
|
|
void nextTick(() => {
|
|
updateNodesIssues();
|
|
});
|
|
canvasStore.stopLoading();
|
|
fitView();
|
|
canvasEventBus.emit("open:execution", data);
|
|
void externalHooks.run("execution.open", {
|
|
workflowId: data.workflowData.id,
|
|
workflowName: data.workflowData.name,
|
|
executionId
|
|
});
|
|
telemetry.track("User opened read-only execution", {
|
|
workflow_id: data.workflowData.id,
|
|
execution_mode: data.mode,
|
|
execution_finished: data.finished
|
|
});
|
|
}
|
|
function onExecutionOpenedWithError(data) {
|
|
if (!data.finished && data.data?.resultData?.error) {
|
|
let nodeErrorFound = false;
|
|
if (data.data.resultData.runData) {
|
|
const runData = data.data.resultData.runData;
|
|
errorCheck: for (const nodeName of Object.keys(runData)) {
|
|
for (const taskData of runData[nodeName]) {
|
|
if (taskData.error) {
|
|
nodeErrorFound = true;
|
|
break errorCheck;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!nodeErrorFound && (data.data.resultData.error.stack ?? data.data.resultData.error.message)) {
|
|
console.error(`Execution ${data.id} error:`);
|
|
console.error(data.data.resultData.error.stack);
|
|
toast.showMessage({
|
|
title: i18n.baseText("nodeView.showError.workflowError"),
|
|
message: data.data.resultData.error.message,
|
|
type: "error",
|
|
duration: 0
|
|
});
|
|
}
|
|
}
|
|
}
|
|
function onExecutionOpenedWithWaitTill(data) {
|
|
if (data.waitTill) {
|
|
toast.showMessage({
|
|
title: i18n.baseText("nodeView.thisExecutionHasntFinishedYet"),
|
|
message: h(_sfc_main$2),
|
|
type: "warning",
|
|
duration: 0
|
|
});
|
|
}
|
|
}
|
|
function addExecutionOpenedEventBindings() {
|
|
canvasEventBus.on("open:execution", onExecutionOpenedWithError);
|
|
canvasEventBus.on("open:execution", onExecutionOpenedWithWaitTill);
|
|
}
|
|
function removeExecutionOpenedEventBindings() {
|
|
canvasEventBus.off("open:execution", onExecutionOpenedWithError);
|
|
canvasEventBus.off("open:execution", onExecutionOpenedWithWaitTill);
|
|
}
|
|
async function onStopExecution() {
|
|
isStoppingExecution.value = true;
|
|
await stopCurrentExecution();
|
|
isStoppingExecution.value = false;
|
|
}
|
|
async function onStopWaitingForWebhook() {
|
|
await stopWaitingForWebhook();
|
|
}
|
|
function onRunWorkflowButtonMouseEnter() {
|
|
nodeViewEventBus.emit("runWorkflowButton:mouseenter");
|
|
}
|
|
function onRunWorkflowButtonMouseLeave() {
|
|
nodeViewEventBus.emit("runWorkflowButton:mouseleave");
|
|
}
|
|
const chatTriggerNode = computed(() => {
|
|
return editableWorkflow.value.nodes.find((node) => node.type === CHAT_TRIGGER_NODE_TYPE);
|
|
});
|
|
const containsChatTriggerNodes = computed(() => {
|
|
return !isExecutionWaitingForWebhook.value && !!editableWorkflow.value.nodes.find(
|
|
(node) => [MANUAL_CHAT_TRIGGER_NODE_TYPE, CHAT_TRIGGER_NODE_TYPE].includes(node.type) && node.disabled !== true
|
|
);
|
|
});
|
|
const isOnlyChatTriggerNodeActive = computed(() => {
|
|
return triggerNodes.value.every((node) => node.disabled || node.type === CHAT_TRIGGER_NODE_TYPE);
|
|
});
|
|
const chatTriggerNodePinnedData = computed(() => {
|
|
if (!chatTriggerNode.value) return null;
|
|
return workflowsStore.pinDataByNodeName(chatTriggerNode.value.name);
|
|
});
|
|
function onOpenChat() {
|
|
startChat("main");
|
|
}
|
|
const evaluationTriggerNode = computed(() => {
|
|
return editableWorkflow.value.nodes.find((node) => node.type === EVALUATION_TRIGGER_NODE_TYPE);
|
|
});
|
|
function addUndoRedoEventBindings() {
|
|
historyBus.on("nodeMove", onRevertNodePosition);
|
|
historyBus.on("revertAddNode", onRevertAddNode);
|
|
historyBus.on("revertRemoveNode", onRevertDeleteNode);
|
|
historyBus.on("revertAddConnection", onRevertCreateConnection);
|
|
historyBus.on("revertRemoveConnection", onRevertDeleteConnection);
|
|
historyBus.on("revertRenameNode", onRevertRenameNode);
|
|
historyBus.on("revertReplaceNodeParameters", onRevertReplaceNodeParameters);
|
|
historyBus.on("enableNodeToggle", onRevertToggleNodeDisabled);
|
|
}
|
|
function removeUndoRedoEventBindings() {
|
|
historyBus.off("nodeMove", onRevertNodePosition);
|
|
historyBus.off("revertAddNode", onRevertAddNode);
|
|
historyBus.off("revertRemoveNode", onRevertDeleteNode);
|
|
historyBus.off("revertAddConnection", onRevertCreateConnection);
|
|
historyBus.off("revertRemoveConnection", onRevertDeleteConnection);
|
|
historyBus.off("revertRenameNode", onRevertRenameNode);
|
|
historyBus.off("revertReplaceNodeParameters", onRevertReplaceNodeParameters);
|
|
historyBus.off("enableNodeToggle", onRevertToggleNodeDisabled);
|
|
}
|
|
async function onSourceControlPull() {
|
|
try {
|
|
await Promise.all([
|
|
environmentsStore.fetchAllVariables(),
|
|
tagsStore.fetchAll(),
|
|
loadCredentials()
|
|
]);
|
|
if (workflowId.value && !uiStore.stateIsDirty) {
|
|
const workflowData = await workflowsStore.fetchWorkflow(workflowId.value);
|
|
if (workflowData) {
|
|
workflowHelpers.setDocumentTitle(workflowData.name, "IDLE");
|
|
openWorkflow(workflowData);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error(error);
|
|
}
|
|
}
|
|
function addSourceControlEventBindings() {
|
|
sourceControlEventBus.on("pull", onSourceControlPull);
|
|
}
|
|
function removeSourceControlEventBindings() {
|
|
sourceControlEventBus.off("pull", onSourceControlPull);
|
|
}
|
|
function addPostMessageEventBindings() {
|
|
window.addEventListener("message", onPostMessageReceived);
|
|
}
|
|
function removePostMessageEventBindings() {
|
|
window.removeEventListener("message", onPostMessageReceived);
|
|
}
|
|
function emitPostMessageReady() {
|
|
if (window.parent) {
|
|
window.parent.postMessage(
|
|
JSON.stringify({ command: "n8nReady", version: rootStore.versionCli }),
|
|
"*"
|
|
);
|
|
}
|
|
}
|
|
async function onPostMessageReceived(messageEvent) {
|
|
if (!messageEvent || typeof messageEvent.data !== "string" || !messageEvent.data?.includes?.('"command"')) {
|
|
return;
|
|
}
|
|
try {
|
|
const json = JSON.parse(messageEvent.data);
|
|
if (json && json.command === "openWorkflow") {
|
|
try {
|
|
await importWorkflowExact(json);
|
|
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.showError(e, i18n.baseText("openWorkflow.workflowImportError"));
|
|
}
|
|
} else if (json && json.command === "openExecution") {
|
|
try {
|
|
isProductionExecutionPreview.value = json.executionMode !== "manual" && json.executionMode !== "evaluation";
|
|
await onOpenExecution(json.executionId, json.nodeId);
|
|
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.message,
|
|
type: "error"
|
|
});
|
|
}
|
|
} else if (json?.command === "setActiveExecution") {
|
|
executionsStore.activeExecution = await executionsStore.fetchExecution(
|
|
json.executionId
|
|
);
|
|
}
|
|
} catch (e) {
|
|
}
|
|
}
|
|
function checkIfEditingIsAllowed() {
|
|
if (!initializedWorkflowId.value) {
|
|
return true;
|
|
}
|
|
if (readOnlyNotification.value?.visible) {
|
|
return false;
|
|
}
|
|
if (isReadOnlyRoute.value || isReadOnlyEnvironment.value) {
|
|
const messageContext = isReadOnlyRoute.value ? "executions" : "workflows";
|
|
readOnlyNotification.value = toast.showMessage({
|
|
title: i18n.baseText(
|
|
isReadOnlyEnvironment.value ? `readOnlyEnv.showMessage.${messageContext}.title` : "readOnly.showMessage.executions.title"
|
|
),
|
|
message: i18n.baseText(
|
|
isReadOnlyEnvironment.value ? `readOnlyEnv.showMessage.${messageContext}.message` : "readOnly.showMessage.executions.message"
|
|
),
|
|
type: "info"
|
|
});
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
function checkIfRouteIsAllowed() {
|
|
if (isReadOnlyEnvironment.value && [VIEWS.NEW_WORKFLOW, VIEWS.TEMPLATE_IMPORT].find((view) => view === route.name)) {
|
|
void nextTick(async () => {
|
|
resetWorkspace();
|
|
uiStore.stateIsDirty = false;
|
|
await router.replace({ name: VIEWS.HOMEPAGE });
|
|
});
|
|
}
|
|
}
|
|
async function initializeDebugMode() {
|
|
workflowHelpers.setDocumentTitle(workflowsStore.workflowName, "DEBUG");
|
|
if (!workflowsStore.isInDebugMode) {
|
|
await applyExecutionData(route.params.executionId);
|
|
workflowsStore.isInDebugMode = true;
|
|
}
|
|
canvasEventBus.on("saved:workflow", onSaveFromWithinExecutionDebug);
|
|
}
|
|
async function onSaveFromWithinExecutionDebug() {
|
|
if (route.name !== VIEWS.EXECUTION_DEBUG) return;
|
|
await router.replace({
|
|
name: VIEWS.WORKFLOW,
|
|
params: { name: workflowId.value }
|
|
});
|
|
}
|
|
const viewportTransform = ref({ x: 0, y: 0, zoom: 1 });
|
|
const viewportDimensions = ref({ width: 0, height: 0 });
|
|
const viewportBoundaries = computed(
|
|
() => getBounds(viewportTransform.value, viewportDimensions.value)
|
|
);
|
|
function onViewportChange(viewport, dimensions) {
|
|
viewportTransform.value = viewport;
|
|
viewportDimensions.value = dimensions;
|
|
uiStore.nodeViewOffsetPosition = [viewport.x, viewport.y];
|
|
}
|
|
function fitView() {
|
|
setTimeout(() => canvasEventBus.emit("fitView"));
|
|
}
|
|
function selectNodes(ids) {
|
|
setTimeout(() => canvasEventBus.emit("nodes:select", { ids }));
|
|
}
|
|
function onClickPane(position) {
|
|
lastClickPosition.value = [position.x, position.y];
|
|
onSetNodeSelected();
|
|
}
|
|
function onSelectionEnd(position) {
|
|
lastClickPosition.value = [position.x, position.y];
|
|
}
|
|
async function onDragAndDrop(position, event) {
|
|
if (!event.dataTransfer) {
|
|
return;
|
|
}
|
|
const dropData = jsonParse(
|
|
event.dataTransfer.getData(DRAG_EVENT_DATA_KEY)
|
|
);
|
|
if (dropData) {
|
|
const insertNodePosition = [position.x, position.y];
|
|
await onAddNodesAndConnections(dropData, true, insertNodePosition);
|
|
onToggleNodeCreator({ createNodeActive: false, hasAddedNodes: true });
|
|
}
|
|
}
|
|
function registerCustomActions() {
|
|
registerCustomAction({
|
|
key: "openNodeDetail",
|
|
action: ({ node }) => {
|
|
setNodeActiveByName(node, "other");
|
|
}
|
|
});
|
|
registerCustomAction({
|
|
key: "openSelectiveNodeCreator",
|
|
action: ({
|
|
creatorview: creatorView,
|
|
connectiontype: connectionType,
|
|
node
|
|
}) => {
|
|
nodeCreatorStore.openSelectiveNodeCreator({ node, connectionType, creatorView });
|
|
}
|
|
});
|
|
registerCustomAction({
|
|
key: "showNodeCreator",
|
|
action: () => {
|
|
ndvStore.unsetActiveNodeName();
|
|
void nextTick(() => {
|
|
void onOpenNodeCreatorForTriggerNodes(NODE_CREATOR_OPEN_SOURCES.TAB);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
function unregisterCustomActions() {
|
|
unregisterCustomAction("openNodeDetail");
|
|
unregisterCustomAction("openSelectiveNodeCreator");
|
|
unregisterCustomAction("showNodeCreator");
|
|
}
|
|
function showAddFirstStepIfEnabled() {
|
|
if (uiStore.addFirstStepOnLoad) {
|
|
void onOpenNodeCreatorForTriggerNodes(NODE_CREATOR_OPEN_SOURCES.TRIGGER_PLACEHOLDER_BUTTON);
|
|
uiStore.addFirstStepOnLoad = false;
|
|
}
|
|
}
|
|
function updateNodeRoute(nodeId) {
|
|
const nodeUi = workflowsStore.findNodeByPartialId(nodeId);
|
|
if (nodeUi) {
|
|
setNodeActive(nodeUi.id, "other");
|
|
} else {
|
|
toast.showToast({
|
|
title: i18n.baseText("nodeView.showMessage.ndvUrl.missingNodes.title"),
|
|
message: i18n.baseText("nodeView.showMessage.ndvUrl.missingNodes.content"),
|
|
type: "warning"
|
|
});
|
|
void router.replace({
|
|
name: route.name,
|
|
params: { name: workflowId.value }
|
|
});
|
|
}
|
|
}
|
|
watch(
|
|
() => route.name,
|
|
async (newRouteName, oldRouteName) => {
|
|
const force = newRouteName === VIEWS.NEW_WORKFLOW && oldRouteName === VIEWS.WORKFLOW || newRouteName === VIEWS.WORKFLOW && oldRouteName === VIEWS.NEW_WORKFLOW;
|
|
await initializeRoute(force);
|
|
}
|
|
);
|
|
watch(
|
|
() => {
|
|
return isLoading.value || isCanvasReadOnly.value || editableWorkflow.value.nodes.length !== 0;
|
|
},
|
|
(isReadOnlyOrLoading) => {
|
|
if (isReadOnlyOrLoading) {
|
|
fallbackNodes.value = [];
|
|
return;
|
|
}
|
|
const addNodesItem = {
|
|
id: CanvasNodeRenderType.AddNodes,
|
|
name: CanvasNodeRenderType.AddNodes,
|
|
type: CanvasNodeRenderType.AddNodes,
|
|
typeVersion: 1,
|
|
position: [0, 0],
|
|
parameters: {}
|
|
};
|
|
const aiPromptItem = {
|
|
id: CanvasNodeRenderType.AIPrompt,
|
|
name: CanvasNodeRenderType.AIPrompt,
|
|
type: CanvasNodeRenderType.AIPrompt,
|
|
typeVersion: 1,
|
|
position: [-690, -15],
|
|
parameters: {},
|
|
draggable: false
|
|
};
|
|
fallbackNodes.value = builderStore.isAIBuilderEnabled && builderStore.isAssistantEnabled && builderStore.assistantMessages.length === 0 ? [aiPromptItem] : [addNodesItem];
|
|
}
|
|
);
|
|
watch(
|
|
() => route.params.nodeId,
|
|
async (newId) => {
|
|
if (typeof newId !== "string" || newId === "") ndvStore.unsetActiveNodeName();
|
|
else {
|
|
updateNodeRoute(newId);
|
|
}
|
|
}
|
|
);
|
|
watch(
|
|
() => ndvStore.activeNode,
|
|
async (val) => {
|
|
if (![VIEWS.WORKFLOW].includes(String(route.name))) return;
|
|
const nodeId = val?.id ? workflowsStore.getPartialIdForNode(val?.id) : "";
|
|
if (nodeId !== route.params.nodeId) {
|
|
await router.replace({
|
|
name: route.name,
|
|
params: { name: workflowId.value, nodeId }
|
|
});
|
|
}
|
|
}
|
|
);
|
|
onBeforeRouteLeave(async (to, from, next) => {
|
|
const toNodeViewTab = getNodeViewTab(to);
|
|
if (toNodeViewTab === MAIN_HEADER_TABS.EXECUTIONS || from.name === VIEWS.TEMPLATE_IMPORT || toNodeViewTab === MAIN_HEADER_TABS.WORKFLOW && from.name === VIEWS.EXECUTION_DEBUG || isReadOnlyEnvironment.value) {
|
|
next();
|
|
return;
|
|
}
|
|
await useWorkflowSaving({ router }).promptSaveUnsavedWorkflowChanges(next, {
|
|
async confirm() {
|
|
if (from.name === VIEWS.NEW_WORKFLOW) {
|
|
const savedWorkflowId = workflowsStore.workflowId;
|
|
await router.replace({
|
|
name: VIEWS.WORKFLOW,
|
|
params: { name: savedWorkflowId }
|
|
});
|
|
await router.push(to);
|
|
return false;
|
|
}
|
|
workflowsStore.setWorkflowId(PLACEHOLDER_EMPTY_WORKFLOW_ID);
|
|
return true;
|
|
}
|
|
});
|
|
});
|
|
onBeforeMount(() => {
|
|
if (!isDemoRoute.value) {
|
|
pushConnectionStore.pushConnect();
|
|
}
|
|
addPostMessageEventBindings();
|
|
});
|
|
onMounted(() => {
|
|
canvasStore.startLoading();
|
|
documentTitle.reset();
|
|
resetWorkspace();
|
|
void initializeData().then(() => {
|
|
void initializeRoute().then(() => {
|
|
toast.showNotificationForViews([VIEWS.WORKFLOW, VIEWS.NEW_WORKFLOW]);
|
|
if (route.query.settings) {
|
|
uiStore.openModal(WORKFLOW_SETTINGS_MODAL_KEY);
|
|
void router.replace({ query: { settings: void 0 } });
|
|
}
|
|
}).finally(() => {
|
|
isLoading.value = false;
|
|
canvasStore.stopLoading();
|
|
void externalHooks.run("nodeView.mount").catch(() => {
|
|
});
|
|
setTimeout(() => {
|
|
if (routeNodeId.value) {
|
|
updateNodeRoute(routeNodeId.value);
|
|
}
|
|
}, 500);
|
|
emitPostMessageReady();
|
|
});
|
|
void usersStore.showPersonalizationSurvey();
|
|
checkIfRouteIsAllowed();
|
|
});
|
|
addSourceControlEventBindings();
|
|
addWorkflowSavedEventBindings();
|
|
addBeforeUnloadEventBindings();
|
|
addImportEventBindings();
|
|
addExecutionOpenedEventBindings();
|
|
registerCustomActions();
|
|
});
|
|
onActivated(async () => {
|
|
addUndoRedoEventBindings();
|
|
showAddFirstStepIfEnabled();
|
|
});
|
|
onDeactivated(() => {
|
|
uiStore.closeModal(WORKFLOW_SETTINGS_MODAL_KEY);
|
|
removeUndoRedoEventBindings();
|
|
});
|
|
onBeforeUnmount(() => {
|
|
removeSourceControlEventBindings();
|
|
removePostMessageEventBindings();
|
|
removeWorkflowSavedEventBindings();
|
|
removeBeforeUnloadEventBindings();
|
|
removeImportEventBindings();
|
|
removeExecutionOpenedEventBindings();
|
|
unregisterCustomActions();
|
|
if (!isDemoRoute.value) {
|
|
pushConnectionStore.pushDisconnect();
|
|
}
|
|
});
|
|
return (_ctx, _cache) => {
|
|
return openBlock(), createElementBlock("div", {
|
|
class: normalizeClass(unref($style).wrapper)
|
|
}, [
|
|
unref(editableWorkflow) && unref(editableWorkflowObject) && !isLoading.value ? (openBlock(), createBlock(WorkflowCanvas, {
|
|
key: 0,
|
|
id: unref(editableWorkflow).id,
|
|
workflow: unref(editableWorkflow),
|
|
"workflow-object": unref(editableWorkflowObject),
|
|
"fallback-nodes": fallbackNodes.value,
|
|
"show-fallback-nodes": showFallbackNodes.value,
|
|
"event-bus": unref(canvasEventBus),
|
|
"read-only": isCanvasReadOnly.value,
|
|
executing: isWorkflowRunning.value,
|
|
"key-bindings": keyBindingsEnabled.value,
|
|
"onUpdate:nodes:position": onUpdateNodesPosition,
|
|
"onUpdate:node:position": onUpdateNodePosition,
|
|
"onUpdate:node:activated": onSetNodeActivated,
|
|
"onUpdate:node:deactivated": onSetNodeDeactivated,
|
|
"onUpdate:node:selected": onSetNodeSelected,
|
|
"onUpdate:node:enabled": onToggleNodeDisabled,
|
|
"onUpdate:node:name": onOpenRenameNodeModal,
|
|
"onUpdate:node:parameters": onUpdateNodeParameters,
|
|
"onUpdate:node:inputs": onUpdateNodeInputs,
|
|
"onUpdate:node:outputs": onUpdateNodeOutputs,
|
|
"onUpdate:logsOpen": _cache[3] || (_cache[3] = ($event) => unref(logsStore).toggleOpen($event)),
|
|
"onUpdate:logs:inputOpen": unref(logsStore).toggleInputOpen,
|
|
"onUpdate:logs:outputOpen": unref(logsStore).toggleOutputOpen,
|
|
"onUpdate:hasRangeSelection": unref(canvasStore).setHasRangeSelection,
|
|
"onOpen:subWorkflow": onOpenSubWorkflow,
|
|
"onClick:node": onClickNode,
|
|
"onClick:node:add": onClickNodeAdd,
|
|
"onRun:node": onRunWorkflowToNode,
|
|
"onDelete:node": onDeleteNode,
|
|
"onCreate:connection": onCreateConnection,
|
|
"onCreate:connection:cancelled": onCreateConnectionCancelled,
|
|
"onDelete:connection": onDeleteConnection,
|
|
"onClick:connection:add": onClickConnectionAdd,
|
|
"onClick:pane": onClickPane,
|
|
"onCreate:node": onOpenNodeCreatorFromCanvas,
|
|
"onCreate:sticky": onCreateSticky,
|
|
"onDelete:nodes": onDeleteNodes,
|
|
"onUpdate:nodes:enabled": onToggleNodesDisabled,
|
|
"onUpdate:nodes:pin": onPinNodes,
|
|
"onDuplicate:nodes": onDuplicateNodes,
|
|
"onCopy:nodes": onCopyNodes,
|
|
"onCut:nodes": onCutNodes,
|
|
"onRun:workflow": _cache[4] || (_cache[4] = ($event) => unref(runEntireWorkflow)("main")),
|
|
"onSave:workflow": onSaveWorkflow,
|
|
"onCreate:workflow": onCreateWorkflow,
|
|
"onViewport:change": onViewportChange,
|
|
"onSelection:end": onSelectionEnd,
|
|
onDragAndDrop,
|
|
onTidyUp,
|
|
"onToggle:focusPanel": onToggleFocusPanel,
|
|
onExtractWorkflow,
|
|
onStartChat: _cache[5] || (_cache[5] = ($event) => unref(startChat)())
|
|
}, {
|
|
default: withCtx(() => [
|
|
(openBlock(), createBlock(Suspense, null, {
|
|
default: withCtx(() => [
|
|
createVNode(unref(LazySetupWorkflowCredentialsButton), {
|
|
class: normalizeClass(unref($style).setupCredentialsButtonWrapper)
|
|
}, null, 8, ["class"])
|
|
]),
|
|
_: 1
|
|
})),
|
|
!isCanvasReadOnly.value ? (openBlock(), createElementBlock("div", {
|
|
key: 0,
|
|
class: normalizeClass(unref($style).executionButtons)
|
|
}, [
|
|
isRunWorkflowButtonVisible.value ? (openBlock(), createBlock(CanvasRunWorkflowButton, {
|
|
key: 0,
|
|
"waiting-for-webhook": isExecutionWaitingForWebhook.value,
|
|
disabled: isExecutionDisabled.value,
|
|
executing: isWorkflowRunning.value,
|
|
"trigger-nodes": triggerNodes.value,
|
|
"get-node-type": unref(nodeTypesStore).getNodeType,
|
|
"selected-trigger-node-name": unref(workflowsStore).selectedTriggerNodeName,
|
|
onMouseenter: onRunWorkflowButtonMouseEnter,
|
|
onMouseleave: onRunWorkflowButtonMouseLeave,
|
|
onExecute: _cache[0] || (_cache[0] = ($event) => unref(runEntireWorkflow)("main")),
|
|
onSelectTriggerNode: unref(workflowsStore).setSelectedTriggerNodeName
|
|
}, null, 8, ["waiting-for-webhook", "disabled", "executing", "trigger-nodes", "get-node-type", "selected-trigger-node-name", "onSelectTriggerNode"])) : createCommentVNode("", true),
|
|
containsChatTriggerNodes.value ? (openBlock(), createElementBlock(Fragment, { key: 1 }, [
|
|
isLogsPanelOpen.value ? (openBlock(), createBlock(_sfc_main$1, {
|
|
key: 0,
|
|
type: "tertiary",
|
|
label: unref(i18n).baseText("chat.hide"),
|
|
class: normalizeClass(unref($style).chatButton),
|
|
onClick: _cache[1] || (_cache[1] = ($event) => unref(logsStore).toggleOpen(false))
|
|
}, null, 8, ["label", "class"])) : (openBlock(), createBlock(KeyboardShortcutTooltip, {
|
|
key: 1,
|
|
label: unref(i18n).baseText("chat.open"),
|
|
shortcut: { keys: ["c"] }
|
|
}, {
|
|
default: withCtx(() => [
|
|
createVNode(_sfc_main$1, {
|
|
type: isRunWorkflowButtonVisible.value ? "secondary" : "primary",
|
|
label: unref(i18n).baseText("chat.open"),
|
|
class: normalizeClass(unref($style).chatButton),
|
|
onClick: onOpenChat
|
|
}, null, 8, ["type", "label", "class"])
|
|
]),
|
|
_: 1
|
|
}, 8, ["label"]))
|
|
], 64)) : createCommentVNode("", true),
|
|
isStopExecutionButtonVisible.value ? (openBlock(), createBlock(_sfc_main$5, {
|
|
key: 2,
|
|
stopping: isStoppingExecution.value,
|
|
onClick: onStopExecution
|
|
}, null, 8, ["stopping"])) : createCommentVNode("", true),
|
|
isStopWaitingForWebhookButtonVisible.value ? (openBlock(), createBlock(_sfc_main$4, {
|
|
key: 3,
|
|
onClick: onStopWaitingForWebhook
|
|
})) : createCommentVNode("", true)
|
|
], 2)) : createCommentVNode("", true),
|
|
isReadOnlyEnvironment.value ? (openBlock(), createBlock(unref(N8nCallout), {
|
|
key: 1,
|
|
theme: "warning",
|
|
icon: "lock",
|
|
class: normalizeClass(unref($style).readOnlyEnvironmentNotification)
|
|
}, {
|
|
default: withCtx(() => [
|
|
createTextVNode(toDisplayString(unref(i18n).baseText("readOnlyEnv.cantEditOrRun")), 1)
|
|
]),
|
|
_: 1
|
|
}, 8, ["class"])) : createCommentVNode("", true),
|
|
unref(builderStore).streaming ? (openBlock(), createBlock(CanvasThinkingPill, {
|
|
key: 2,
|
|
class: normalizeClass(unref($style).thinkingPill),
|
|
"show-stop": "",
|
|
onStop: unref(builderStore).stopStreaming
|
|
}, null, 8, ["class", "onStop"])) : createCommentVNode("", true),
|
|
(openBlock(), createBlock(Suspense, null, {
|
|
default: withCtx(() => [
|
|
!isCanvasReadOnly.value ? (openBlock(), createBlock(unref(LazyNodeCreation), {
|
|
key: 0,
|
|
"create-node-active": unref(nodeCreatorStore).isCreateNodeActive,
|
|
"node-view-scale": viewportTransform.value.zoom,
|
|
"focus-panel-active": unref(focusPanelStore).focusPanelActive,
|
|
onToggleNodeCreator,
|
|
onAddNodes: onAddNodesAndConnections
|
|
}, null, 8, ["create-node-active", "node-view-scale", "focus-panel-active"])) : createCommentVNode("", true)
|
|
]),
|
|
_: 1
|
|
})),
|
|
(openBlock(), createBlock(Suspense, null, {
|
|
default: withCtx(() => [
|
|
!isNDVV2.value ? (openBlock(), createBlock(unref(LazyNodeDetailsView), {
|
|
key: 0,
|
|
"workflow-object": unref(editableWorkflowObject),
|
|
"read-only": isCanvasReadOnly.value,
|
|
"is-production-execution-preview": isProductionExecutionPreview.value,
|
|
renaming: false,
|
|
onValueChanged: _cache[2] || (_cache[2] = ($event) => onRenameNode($event.value)),
|
|
onStopExecution,
|
|
onSwitchSelectedNode: onSwitchActiveNode,
|
|
onOpenConnectionNodeCreator: onOpenSelectiveNodeCreator,
|
|
onSaveKeyboardShortcut: onSaveWorkflow
|
|
}, null, 8, ["workflow-object", "read-only", "is-production-execution-preview"])) : createCommentVNode("", true)
|
|
]),
|
|
_: 1
|
|
})),
|
|
(openBlock(), createBlock(Suspense, null, {
|
|
default: withCtx(() => [
|
|
isNDVV2.value ? (openBlock(), createBlock(unref(LazyNodeDetailsViewV2), {
|
|
key: 0,
|
|
"workflow-object": unref(editableWorkflowObject),
|
|
"read-only": isCanvasReadOnly.value,
|
|
"is-production-execution-preview": isProductionExecutionPreview.value,
|
|
onRenameNode,
|
|
onStopExecution,
|
|
onSwitchSelectedNode: onSwitchActiveNode,
|
|
onOpenConnectionNodeCreator: onOpenSelectiveNodeCreator,
|
|
onSaveKeyboardShortcut: onSaveWorkflow
|
|
}, null, 8, ["workflow-object", "read-only", "is-production-execution-preview"])) : createCommentVNode("", true)
|
|
]),
|
|
_: 1
|
|
}))
|
|
]),
|
|
_: 1
|
|
}, 8, ["id", "workflow", "workflow-object", "fallback-nodes", "show-fallback-nodes", "event-bus", "read-only", "executing", "key-bindings", "onUpdate:logs:inputOpen", "onUpdate:logs:outputOpen", "onUpdate:hasRangeSelection"])) : createCommentVNode("", true),
|
|
!isLoading.value ? (openBlock(), createBlock(FocusPanel, {
|
|
key: 1,
|
|
"is-canvas-read-only": isCanvasReadOnly.value,
|
|
onSaveKeyboardShortcut: onSaveWorkflow
|
|
}, null, 8, ["is-canvas-read-only"])) : createCommentVNode("", true)
|
|
], 2);
|
|
};
|
|
}
|
|
});
|
|
const wrapper = "_wrapper_16h3t_123";
|
|
const executionButtons = "_executionButtons_16h3t_128";
|
|
const chatButton = "_chatButton_16h3t_165";
|
|
const setupCredentialsButtonWrapper = "_setupCredentialsButtonWrapper_16h3t_169";
|
|
const readOnlyEnvironmentNotification = "_readOnlyEnvironmentNotification_16h3t_175";
|
|
const thinkingPill = "_thinkingPill_16h3t_182";
|
|
const style0 = {
|
|
wrapper,
|
|
executionButtons,
|
|
chatButton,
|
|
setupCredentialsButtonWrapper,
|
|
readOnlyEnvironmentNotification,
|
|
thinkingPill
|
|
};
|
|
const cssModules = {
|
|
"$style": style0
|
|
};
|
|
const NodeView = /* @__PURE__ */ _export_sfc(_sfc_main, [["__cssModules", cssModules]]);
|
|
const NodeView$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
__proto__: null,
|
|
default: NodeView
|
|
}, Symbol.toStringTag, { value: "Module" }));
|
|
export {
|
|
NodeView$1 as N,
|
|
useExecutionData as u
|
|
};
|