2024-05-23 01:42:10 -07:00
|
|
|
<script setup lang="ts">
|
2024-07-08 03:25:18 -07:00
|
|
|
import {
|
|
|
|
computed,
|
|
|
|
defineAsyncComponent,
|
|
|
|
nextTick,
|
|
|
|
onBeforeMount,
|
|
|
|
onBeforeUnmount,
|
|
|
|
onMounted,
|
|
|
|
ref,
|
|
|
|
useCssModule,
|
|
|
|
} from 'vue';
|
2024-05-23 01:42:10 -07:00
|
|
|
import { useRoute, useRouter } from 'vue-router';
|
|
|
|
import WorkflowCanvas from '@/components/canvas/WorkflowCanvas.vue';
|
|
|
|
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
|
|
|
import { useUIStore } from '@/stores/ui.store';
|
|
|
|
import CanvasExecuteWorkflowButton from '@/components/canvas/elements/buttons/CanvasExecuteWorkflowButton.vue';
|
|
|
|
import { useI18n } from '@/composables/useI18n';
|
|
|
|
import { useWorkflowsStore } from '@/stores/workflows.store';
|
|
|
|
import { useRunWorkflow } from '@/composables/useRunWorkflow';
|
|
|
|
import type {
|
|
|
|
AddedNodesAndConnections,
|
|
|
|
INodeUi,
|
2024-06-17 05:46:55 -07:00
|
|
|
IUpdateInformation,
|
|
|
|
IWorkflowDataUpdate,
|
2024-07-08 03:25:18 -07:00
|
|
|
IWorkflowDb,
|
2024-05-23 01:42:10 -07:00
|
|
|
ToggleNodeCreatorOptions,
|
|
|
|
XYPosition,
|
|
|
|
} from '@/Interface';
|
|
|
|
import type { Connection } from '@vue-flow/core';
|
|
|
|
import type { CanvasElement } from '@/types';
|
2024-06-25 02:11:44 -07:00
|
|
|
import {
|
|
|
|
CANVAS_AUTO_ADD_MANUAL_TRIGGER_EXPERIMENT,
|
|
|
|
EnterpriseEditionFeature,
|
|
|
|
MODAL_CONFIRM,
|
|
|
|
VIEWS,
|
|
|
|
} from '@/constants';
|
2024-05-23 01:42:10 -07:00
|
|
|
import { useSourceControlStore } from '@/stores/sourceControl.store';
|
|
|
|
import { useNodeCreatorStore } from '@/stores/nodeCreator.store';
|
|
|
|
import { useExternalHooks } from '@/composables/useExternalHooks';
|
2024-07-08 03:25:18 -07:00
|
|
|
import { TelemetryHelpers } from 'n8n-workflow';
|
|
|
|
import type {
|
|
|
|
NodeConnectionType,
|
|
|
|
ExecutionSummary,
|
|
|
|
IConnection,
|
|
|
|
IWorkflowBase,
|
|
|
|
} from 'n8n-workflow';
|
2024-05-23 01:42:10 -07:00
|
|
|
import { useToast } from '@/composables/useToast';
|
|
|
|
import { useSettingsStore } from '@/stores/settings.store';
|
|
|
|
import { useCredentialsStore } from '@/stores/credentials.store';
|
|
|
|
import useEnvironmentsStore from '@/stores/environments.ee.store';
|
|
|
|
import { useExternalSecretsStore } from '@/stores/externalSecrets.ee.store';
|
2024-06-18 10:15:12 -07:00
|
|
|
import { useRootStore } from '@/stores/root.store';
|
2024-06-04 05:36:27 -07:00
|
|
|
import { historyBus } from '@/models/history';
|
|
|
|
import { useCanvasOperations } from '@/composables/useCanvasOperations';
|
2024-06-17 05:46:55 -07:00
|
|
|
import { useExecutionsStore } from '@/stores/executions.store';
|
2024-06-25 02:11:44 -07:00
|
|
|
import { useCanvasStore } from '@/stores/canvas.store';
|
|
|
|
import { useMessage } from '@/composables/useMessage';
|
|
|
|
import { useTitleChange } from '@/composables/useTitleChange';
|
|
|
|
import { useNpsSurveyStore } from '@/stores/npsSurvey.store';
|
|
|
|
import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers';
|
|
|
|
import { useTelemetry } from '@/composables/useTelemetry';
|
|
|
|
import { useHistoryStore } from '@/stores/history.store';
|
|
|
|
import { useProjectsStore } from '@/stores/projects.store';
|
|
|
|
import { usePostHog } from '@/stores/posthog.store';
|
2024-07-08 03:25:18 -07:00
|
|
|
import useWorkflowsEEStore from '@/stores/workflows.ee.store';
|
|
|
|
import { useNodeHelpers } from '@/composables/useNodeHelpers';
|
|
|
|
import { useExecutionDebugging } from '@/composables/useExecutionDebugging';
|
|
|
|
import type { ProjectSharingData } from '@/types/projects.types';
|
|
|
|
import { useUsersStore } from '@/stores/users.store';
|
|
|
|
import { sourceControlEventBus } from '@/event-bus/source-control';
|
|
|
|
import { useTagsStore } from '@/stores/tags.store';
|
|
|
|
import { usePushConnectionStore } from '@/stores/pushConnection.store';
|
2024-05-23 01:42:10 -07:00
|
|
|
|
|
|
|
const NodeCreation = defineAsyncComponent(
|
|
|
|
async () => await import('@/components/Node/NodeCreation.vue'),
|
|
|
|
);
|
|
|
|
|
2024-06-17 05:46:55 -07:00
|
|
|
const NodeDetailsView = defineAsyncComponent(
|
|
|
|
async () => await import('@/components/NodeDetailsView.vue'),
|
|
|
|
);
|
|
|
|
|
2024-05-23 01:42:10 -07:00
|
|
|
const $style = useCssModule();
|
|
|
|
const router = useRouter();
|
|
|
|
const route = useRoute();
|
|
|
|
const i18n = useI18n();
|
2024-06-25 02:11:44 -07:00
|
|
|
const telemetry = useTelemetry();
|
2024-05-23 01:42:10 -07:00
|
|
|
const externalHooks = useExternalHooks();
|
|
|
|
const toast = useToast();
|
2024-06-25 02:11:44 -07:00
|
|
|
const message = useMessage();
|
|
|
|
const titleChange = useTitleChange();
|
|
|
|
const workflowHelpers = useWorkflowHelpers({ router });
|
2024-07-08 03:25:18 -07:00
|
|
|
const nodeHelpers = useNodeHelpers();
|
|
|
|
const posthog = usePostHog();
|
2024-05-23 01:42:10 -07:00
|
|
|
|
|
|
|
const nodeTypesStore = useNodeTypesStore();
|
|
|
|
const uiStore = useUIStore();
|
|
|
|
const workflowsStore = useWorkflowsStore();
|
2024-07-08 03:25:18 -07:00
|
|
|
const workflowsEEStore = useWorkflowsEEStore();
|
2024-05-23 01:42:10 -07:00
|
|
|
const sourceControlStore = useSourceControlStore();
|
|
|
|
const nodeCreatorStore = useNodeCreatorStore();
|
|
|
|
const settingsStore = useSettingsStore();
|
|
|
|
const credentialsStore = useCredentialsStore();
|
|
|
|
const environmentsStore = useEnvironmentsStore();
|
|
|
|
const externalSecretsStore = useExternalSecretsStore();
|
|
|
|
const rootStore = useRootStore();
|
2024-06-17 05:46:55 -07:00
|
|
|
const executionsStore = useExecutionsStore();
|
2024-06-25 02:11:44 -07:00
|
|
|
const canvasStore = useCanvasStore();
|
|
|
|
const npsSurveyStore = useNpsSurveyStore();
|
|
|
|
const historyStore = useHistoryStore();
|
|
|
|
const projectsStore = useProjectsStore();
|
2024-07-08 03:25:18 -07:00
|
|
|
const usersStore = useUsersStore();
|
|
|
|
const tagsStore = useTagsStore();
|
|
|
|
const pushConnectionStore = usePushConnectionStore();
|
2024-06-25 02:11:44 -07:00
|
|
|
|
|
|
|
const lastClickPosition = ref<XYPosition>([450, 450]);
|
2024-05-23 01:42:10 -07:00
|
|
|
|
|
|
|
const { runWorkflow } = useRunWorkflow({ router });
|
2024-06-04 05:36:27 -07:00
|
|
|
const {
|
|
|
|
updateNodePosition,
|
2024-06-17 05:46:55 -07:00
|
|
|
renameNode,
|
|
|
|
revertRenameNode,
|
|
|
|
setNodeActive,
|
2024-06-25 02:11:44 -07:00
|
|
|
setNodeSelected,
|
2024-06-26 06:56:58 -07:00
|
|
|
toggleNodeDisabled,
|
2024-06-04 05:36:27 -07:00
|
|
|
deleteNode,
|
|
|
|
revertDeleteNode,
|
2024-06-25 02:11:44 -07:00
|
|
|
addNodes,
|
2024-06-04 05:36:27 -07:00
|
|
|
createConnection,
|
|
|
|
deleteConnection,
|
|
|
|
revertDeleteConnection,
|
2024-06-17 05:46:55 -07:00
|
|
|
setNodeActiveByName,
|
2024-06-25 02:11:44 -07:00
|
|
|
addConnections,
|
|
|
|
editableWorkflow,
|
|
|
|
editableWorkflowObject,
|
|
|
|
} = useCanvasOperations({ router, lastClickPosition });
|
2024-07-08 03:25:18 -07:00
|
|
|
const { applyExecutionData } = useExecutionDebugging();
|
2024-05-23 01:42:10 -07:00
|
|
|
|
|
|
|
const isLoading = ref(true);
|
2024-06-25 02:11:44 -07:00
|
|
|
const isBlankRedirect = ref(false);
|
2024-05-23 01:42:10 -07:00
|
|
|
const readOnlyNotification = ref<null | { visible: boolean }>(null);
|
|
|
|
|
2024-06-17 05:46:55 -07:00
|
|
|
const isProductionExecutionPreview = ref(false);
|
|
|
|
const isExecutionPreview = ref(false);
|
2024-06-25 02:11:44 -07:00
|
|
|
const isExecutionWaitingForWebhook = ref(false);
|
2024-06-17 05:46:55 -07:00
|
|
|
|
|
|
|
const canOpenNDV = ref(true);
|
|
|
|
const hideNodeIssues = ref(false);
|
|
|
|
|
2024-05-23 01:42:10 -07:00
|
|
|
const workflowId = computed<string>(() => route.params.workflowId as string);
|
|
|
|
const workflow = computed(() => workflowsStore.workflowsById[workflowId.value]);
|
|
|
|
|
2024-07-08 03:25:18 -07:00
|
|
|
const isNewWorkflowRoute = computed(() => route.name === VIEWS.NEW_WORKFLOW_V2);
|
2024-05-23 01:42:10 -07:00
|
|
|
const isDemoRoute = computed(() => route.name === VIEWS.DEMO);
|
|
|
|
const isReadOnlyRoute = computed(() => route?.meta?.readOnlyCanvas === true);
|
|
|
|
const isReadOnlyEnvironment = computed(() => {
|
|
|
|
return sourceControlStore.preferences.branchReadOnly;
|
|
|
|
});
|
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
/**
|
|
|
|
* Initialization
|
|
|
|
*/
|
2024-05-23 01:42:10 -07:00
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
async function initializeData() {
|
2024-05-23 01:42:10 -07:00
|
|
|
isLoading.value = true;
|
2024-07-08 03:25:18 -07:00
|
|
|
canvasStore.startLoading();
|
2024-05-23 01:42:10 -07:00
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
resetWorkspace();
|
|
|
|
titleChange.titleReset();
|
|
|
|
|
2024-07-08 03:25:18 -07:00
|
|
|
const loadPromises = (() => {
|
|
|
|
if (settingsStore.isPreviewMode && isDemoRoute.value) return [];
|
2024-05-23 01:42:10 -07:00
|
|
|
|
2024-07-08 03:25:18 -07:00
|
|
|
const promises: Array<Promise<unknown>> = [
|
2024-05-23 01:42:10 -07:00
|
|
|
workflowsStore.fetchActiveWorkflows(),
|
|
|
|
credentialsStore.fetchAllCredentials(),
|
|
|
|
credentialsStore.fetchCredentialTypes(true),
|
2024-07-08 03:25:18 -07:00
|
|
|
];
|
2024-05-23 01:42:10 -07:00
|
|
|
|
|
|
|
if (settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Variables)) {
|
2024-07-08 03:25:18 -07:00
|
|
|
promises.push(environmentsStore.fetchAllVariables());
|
2024-05-23 01:42:10 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.ExternalSecrets)) {
|
2024-07-08 03:25:18 -07:00
|
|
|
promises.push(externalSecretsStore.fetchAllSecrets());
|
2024-05-23 01:42:10 -07:00
|
|
|
}
|
2024-07-08 03:25:18 -07:00
|
|
|
|
|
|
|
if (nodeTypesStore.allNodeTypes.length === 0) {
|
|
|
|
promises.push(nodeTypesStore.getNodeTypes());
|
|
|
|
}
|
|
|
|
|
|
|
|
return promises;
|
|
|
|
})();
|
|
|
|
|
|
|
|
// @TODO Implement this
|
|
|
|
// this.clipboard.onPaste.value = this.onClipboardPasteEvent;
|
2024-05-23 01:42:10 -07:00
|
|
|
|
|
|
|
try {
|
|
|
|
await Promise.all(loadPromises);
|
|
|
|
} catch (error) {
|
2024-07-08 03:25:18 -07:00
|
|
|
toast.showError(
|
2024-05-23 01:42:10 -07:00
|
|
|
error,
|
|
|
|
i18n.baseText('nodeView.showError.mounted1.title'),
|
|
|
|
i18n.baseText('nodeView.showError.mounted1.message') + ':',
|
|
|
|
);
|
2024-07-08 03:25:18 -07:00
|
|
|
return;
|
|
|
|
} finally {
|
|
|
|
canvasStore.stopLoading();
|
|
|
|
isLoading.value = false;
|
2024-05-23 01:42:10 -07:00
|
|
|
}
|
|
|
|
|
2024-07-08 03:25:18 -07:00
|
|
|
setTimeout(() => {
|
|
|
|
void usersStore.showPersonalizationSurvey();
|
|
|
|
}, 0);
|
2024-05-23 01:42:10 -07:00
|
|
|
|
2024-07-08 03:25:18 -07:00
|
|
|
// @TODO: This currently breaks since front-end hooks are still not updated to work with pinia store
|
|
|
|
void externalHooks.run('nodeView.mount').catch(() => {});
|
2024-06-25 02:11:44 -07:00
|
|
|
|
2024-07-08 03:25:18 -07:00
|
|
|
// @TODO maybe we can find a better way to handle this
|
|
|
|
canvasStore.isDemo = isDemoRoute.value;
|
2024-05-23 01:42:10 -07:00
|
|
|
}
|
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
async function initializeView() {
|
|
|
|
// In case the workflow got saved we do not have to run init
|
|
|
|
// as only the route changed but all the needed data is already loaded
|
|
|
|
if (route.params.action === 'workflowSave') {
|
|
|
|
uiStore.stateIsDirty = false;
|
|
|
|
return;
|
|
|
|
}
|
2024-06-04 05:36:27 -07:00
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
// This function is called on route change as well, so we need to do the following:
|
|
|
|
// - if the redirect is blank, then do nothing
|
|
|
|
// - if the route is the template import view, then open the template
|
|
|
|
// - if the user is leaving the current view without saving the changes, then show a confirmation modal
|
|
|
|
if (isBlankRedirect.value) {
|
|
|
|
isBlankRedirect.value = false;
|
|
|
|
} else if (route.name === VIEWS.TEMPLATE_IMPORT) {
|
|
|
|
// @TODO Implement template import
|
|
|
|
// const templateId = route.params.id;
|
|
|
|
// await openWorkflowTemplate(templateId.toString());
|
|
|
|
} else {
|
|
|
|
// Get workflow id
|
|
|
|
let workflowIdParam: string | null = null;
|
|
|
|
if (route.params.workflowId) {
|
|
|
|
workflowIdParam = route.params.workflowId.toString();
|
|
|
|
}
|
2024-06-17 05:46:55 -07:00
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
historyStore.reset();
|
|
|
|
|
|
|
|
// If there is no workflow id, treat it as a new workflow
|
2024-07-08 03:25:18 -07:00
|
|
|
if (!workflowIdParam || isNewWorkflowRoute.value) {
|
2024-06-25 02:11:44 -07:00
|
|
|
if (route.meta?.nodeView === true) {
|
|
|
|
await initializeViewForNewWorkflow();
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Load workflow data
|
|
|
|
try {
|
|
|
|
await workflowsStore.fetchWorkflow(workflowIdParam);
|
|
|
|
|
|
|
|
titleChange.titleSet(workflow.value.name, 'IDLE');
|
2024-07-08 03:25:18 -07:00
|
|
|
await openWorkflow(workflow.value);
|
|
|
|
await checkAndInitDebugMode();
|
2024-06-25 02:11:44 -07:00
|
|
|
|
|
|
|
trackOpenWorkflowFromOnboardingTemplate();
|
|
|
|
} catch (error) {
|
|
|
|
toast.showError(error, i18n.baseText('openWorkflow.workflowNotFoundError'));
|
|
|
|
|
|
|
|
void router.push({
|
2024-07-08 03:25:18 -07:00
|
|
|
name: VIEWS.NEW_WORKFLOW_V2,
|
2024-06-25 02:11:44 -07:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-08 03:25:18 -07:00
|
|
|
nodeHelpers.updateNodesInputIssues();
|
|
|
|
nodeHelpers.updateNodesCredentialsIssues();
|
|
|
|
nodeHelpers.updateNodesParameterIssues();
|
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
await loadCredentials();
|
2024-07-08 03:25:18 -07:00
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
uiStore.nodeViewInitialized = true;
|
|
|
|
|
|
|
|
// Once view is initialized, pick up all toast notifications
|
|
|
|
// waiting in the store and display them
|
|
|
|
toast.showNotificationForViews([VIEWS.WORKFLOW, VIEWS.NEW_WORKFLOW]);
|
2024-06-17 05:46:55 -07:00
|
|
|
}
|
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
async function initializeViewForNewWorkflow() {
|
|
|
|
resetWorkspace();
|
|
|
|
|
|
|
|
await workflowsStore.getNewWorkflowData(undefined, projectsStore.currentProjectId);
|
|
|
|
|
|
|
|
workflowsStore.currentWorkflowExecutions = [];
|
|
|
|
executionsStore.activeExecution = null;
|
|
|
|
uiStore.stateIsDirty = false;
|
|
|
|
uiStore.nodeViewInitialized = true;
|
|
|
|
executionsStore.activeExecution = null;
|
|
|
|
|
2024-07-08 03:25:18 -07:00
|
|
|
makeNewWorkflowShareable();
|
|
|
|
await runAutoAddManualTriggerExperiment();
|
|
|
|
}
|
2024-06-25 02:11:44 -07:00
|
|
|
|
2024-07-08 03:25:18 -07:00
|
|
|
/**
|
|
|
|
* Pre-populate the canvas with the manual trigger node
|
|
|
|
* if the experiment is enabled and the user is in the variant group
|
|
|
|
*/
|
|
|
|
async function runAutoAddManualTriggerExperiment() {
|
2024-06-25 02:11:44 -07:00
|
|
|
if (
|
2024-07-08 03:25:18 -07:00
|
|
|
posthog.getVariant(CANVAS_AUTO_ADD_MANUAL_TRIGGER_EXPERIMENT.name) !==
|
2024-06-25 02:11:44 -07:00
|
|
|
CANVAS_AUTO_ADD_MANUAL_TRIGGER_EXPERIMENT.variant
|
|
|
|
) {
|
2024-07-08 03:25:18 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const manualTriggerNode = canvasStore.getAutoAddManualTriggerNode();
|
|
|
|
if (manualTriggerNode) {
|
|
|
|
await addNodes([manualTriggerNode]);
|
|
|
|
uiStore.lastSelectedNode = manualTriggerNode.name;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// @ts-expect-error @TODO Add binding on route leave
|
|
|
|
async function promptSaveOnBeforeRouteLeave() {
|
|
|
|
if (uiStore.stateIsDirty && !isReadOnlyEnvironment.value) {
|
|
|
|
const confirmModal = await message.confirm(
|
|
|
|
i18n.baseText('generic.unsavedWork.confirmMessage.message'),
|
|
|
|
{
|
|
|
|
title: i18n.baseText('generic.unsavedWork.confirmMessage.headline'),
|
|
|
|
type: 'warning',
|
|
|
|
confirmButtonText: i18n.baseText('generic.unsavedWork.confirmMessage.confirmButtonText'),
|
|
|
|
cancelButtonText: i18n.baseText('generic.unsavedWork.confirmMessage.cancelButtonText'),
|
|
|
|
showClose: true,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
if (confirmModal === MODAL_CONFIRM) {
|
|
|
|
const saved = await workflowHelpers.saveCurrentWorkflow();
|
|
|
|
if (saved) {
|
|
|
|
await npsSurveyStore.fetchPromptsData();
|
|
|
|
}
|
2024-06-25 02:11:44 -07:00
|
|
|
}
|
2024-05-23 01:42:10 -07:00
|
|
|
}
|
2024-06-25 02:11:44 -07:00
|
|
|
}
|
2024-05-23 01:42:10 -07:00
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
function resetWorkspace() {
|
|
|
|
onToggleNodeCreator({ createNodeActive: false });
|
|
|
|
nodeCreatorStore.setShowScrim(false);
|
2024-05-23 01:42:10 -07:00
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
// Make sure that if there is a waiting test-webhook that it gets removed
|
|
|
|
if (isExecutionWaitingForWebhook.value) {
|
|
|
|
try {
|
|
|
|
void workflowsStore.removeTestWebhook(workflowsStore.workflowId);
|
|
|
|
} catch (error) {}
|
|
|
|
}
|
2024-07-08 03:25:18 -07:00
|
|
|
workflowsStore.resetWorkflow();
|
2024-06-25 02:11:44 -07:00
|
|
|
workflowsStore.resetState();
|
2024-05-23 01:42:10 -07:00
|
|
|
|
2024-07-08 03:25:18 -07:00
|
|
|
uiStore.removeActiveAction('workflowRunning');
|
2024-06-25 02:11:44 -07:00
|
|
|
uiStore.resetSelectedNodes();
|
|
|
|
uiStore.nodeViewOffsetPosition = [0, 0]; // @TODO Not sure if needed
|
|
|
|
|
|
|
|
// this.credentialsUpdated = false;
|
2024-05-23 01:42:10 -07:00
|
|
|
}
|
|
|
|
|
2024-07-08 03:25:18 -07:00
|
|
|
/**
|
|
|
|
* Workflow
|
|
|
|
*/
|
|
|
|
|
|
|
|
async function openWorkflow(data: IWorkflowDb) {
|
|
|
|
const selectedExecution = executionsStore.activeExecution;
|
|
|
|
|
|
|
|
resetWorkspace();
|
|
|
|
|
|
|
|
await workflowHelpers.initState(data, true);
|
|
|
|
|
|
|
|
if (data.sharedWithProjects) {
|
|
|
|
workflowsEEStore.setWorkflowSharedWith({
|
|
|
|
workflowId: data.id,
|
|
|
|
sharedWithProjects: data.sharedWithProjects,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (data.usedCredentials) {
|
|
|
|
workflowsStore.setUsedCredentials(data.usedCredentials);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!nodeHelpers.credentialsUpdated.value) {
|
|
|
|
uiStore.stateIsDirty = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void externalHooks.run('workflow.open', {
|
|
|
|
workflowId: data.id,
|
|
|
|
workflowName: data.name,
|
|
|
|
});
|
|
|
|
|
|
|
|
if (selectedExecution?.workflowId !== data.id) {
|
|
|
|
executionsStore.activeExecution = null;
|
|
|
|
workflowsStore.currentWorkflowExecutions = [];
|
|
|
|
} else {
|
|
|
|
executionsStore.activeExecution = selectedExecution;
|
|
|
|
}
|
|
|
|
|
|
|
|
await projectsStore.setProjectNavActiveIdByWorkflowHomeProject(workflow.value.homeProject);
|
|
|
|
}
|
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
function trackOpenWorkflowFromOnboardingTemplate() {
|
|
|
|
if (workflow.value.meta?.onboardingId) {
|
|
|
|
telemetry.track(
|
|
|
|
`User opened workflow from onboarding template with ID ${workflow.value.meta.onboardingId}`,
|
|
|
|
{
|
|
|
|
workflow_id: workflowId.value,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
withPostHog: true,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
2024-05-23 01:42:10 -07:00
|
|
|
}
|
|
|
|
|
2024-07-08 03:25:18 -07:00
|
|
|
function makeNewWorkflowShareable() {
|
|
|
|
const { currentProject, personalProject } = projectsStore;
|
|
|
|
const homeProject = currentProject ?? personalProject ?? {};
|
|
|
|
const scopes = currentProject?.scopes ?? personalProject?.scopes ?? [];
|
|
|
|
|
|
|
|
workflowsStore.workflow.homeProject = homeProject as ProjectSharingData;
|
|
|
|
workflowsStore.workflow.scopes = scopes;
|
|
|
|
}
|
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
/**
|
|
|
|
* Nodes
|
|
|
|
*/
|
|
|
|
|
2024-06-04 05:36:27 -07:00
|
|
|
function onUpdateNodePosition(id: string, position: CanvasElement['position']) {
|
|
|
|
updateNodePosition(id, position, { trackHistory: true });
|
|
|
|
}
|
|
|
|
|
|
|
|
function onDeleteNode(id: string) {
|
|
|
|
deleteNode(id, { trackHistory: true });
|
|
|
|
}
|
|
|
|
|
|
|
|
function onRevertDeleteNode({ node }: { node: INodeUi }) {
|
|
|
|
revertDeleteNode(node);
|
2024-05-23 01:42:10 -07:00
|
|
|
}
|
|
|
|
|
2024-06-26 06:56:58 -07:00
|
|
|
function onToggleNodeDisabled(id: string) {
|
|
|
|
if (!checkIfEditingIsAllowed()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
toggleNodeDisabled(id);
|
|
|
|
}
|
|
|
|
|
2024-06-17 05:46:55 -07:00
|
|
|
function onSetNodeActive(id: string) {
|
|
|
|
setNodeActive(id);
|
|
|
|
}
|
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
function onSetNodeSelected(id?: string) {
|
|
|
|
setNodeSelected(id);
|
|
|
|
}
|
|
|
|
|
|
|
|
function onRenameNode(parameterData: IUpdateInformation) {
|
|
|
|
// The name changed. Do not forget to change the connections as well
|
|
|
|
if (parameterData.name === 'name' && parameterData.oldValue) {
|
|
|
|
void renameNode(parameterData.oldValue as string, parameterData.value as string);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async function onRevertRenameNode({
|
|
|
|
currentName,
|
|
|
|
newName,
|
|
|
|
}: {
|
|
|
|
currentName: string;
|
|
|
|
newName: string;
|
|
|
|
}) {
|
|
|
|
await revertRenameNode(currentName, newName);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Credentials
|
|
|
|
*/
|
|
|
|
|
|
|
|
async function loadCredentials() {
|
|
|
|
let options: { workflowId: string } | { projectId: string };
|
|
|
|
|
|
|
|
if (workflow.value) {
|
|
|
|
options = { workflowId: workflow.value.id };
|
|
|
|
} else {
|
|
|
|
const queryParam =
|
|
|
|
typeof route.query?.projectId === 'string' ? route.query?.projectId : undefined;
|
|
|
|
const projectId = queryParam ?? projectsStore.personalProject?.id;
|
|
|
|
if (projectId === undefined) {
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2024-05-23 01:42:10 -07:00
|
|
|
/**
|
2024-06-25 02:11:44 -07:00
|
|
|
* Connections
|
2024-05-23 01:42:10 -07:00
|
|
|
*/
|
2024-06-17 05:46:55 -07:00
|
|
|
|
2024-06-04 05:36:27 -07:00
|
|
|
function onCreateConnection(connection: Connection) {
|
|
|
|
createConnection(connection);
|
|
|
|
}
|
2024-05-23 01:42:10 -07:00
|
|
|
|
2024-06-04 05:36:27 -07:00
|
|
|
function onDeleteConnection(connection: Connection) {
|
|
|
|
deleteConnection(connection, { trackHistory: true });
|
2024-05-23 01:42:10 -07:00
|
|
|
}
|
|
|
|
|
2024-06-04 05:36:27 -07:00
|
|
|
function onRevertDeleteConnection({ connection }: { connection: [IConnection, IConnection] }) {
|
|
|
|
revertDeleteConnection(connection);
|
2024-05-23 01:42:10 -07:00
|
|
|
}
|
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
/**
|
|
|
|
* Import / Export
|
|
|
|
*/
|
|
|
|
|
|
|
|
async function importWorkflowExact(_workflow: IWorkflowDataUpdate) {
|
|
|
|
// @TODO
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Node creator
|
|
|
|
*/
|
|
|
|
|
|
|
|
async function onAddNodesAndConnections(
|
2024-05-23 01:42:10 -07:00
|
|
|
{ nodes, connections }: AddedNodesAndConnections,
|
|
|
|
dragAndDrop = false,
|
|
|
|
position?: XYPosition,
|
|
|
|
) {
|
2024-06-25 02:11:44 -07:00
|
|
|
if (!checkIfEditingIsAllowed()) {
|
|
|
|
return;
|
2024-05-23 01:42:10 -07:00
|
|
|
}
|
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
await addNodes(nodes, { dragAndDrop, position });
|
|
|
|
await addConnections(connections, {
|
|
|
|
offsetIndex: editableWorkflow.value.nodes.length - nodes.length,
|
|
|
|
});
|
|
|
|
}
|
2024-06-17 05:46:55 -07:00
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
async function onSwitchActiveNode(nodeName: string) {
|
|
|
|
setNodeActiveByName(nodeName);
|
2024-05-23 01:42:10 -07:00
|
|
|
}
|
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
async function onOpenConnectionNodeCreator(node: string, connectionType: NodeConnectionType) {
|
|
|
|
nodeCreatorStore.openSelectiveNodeCreator({ node, connectionType });
|
|
|
|
}
|
2024-05-23 01:42:10 -07:00
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
function onToggleNodeCreator(options: ToggleNodeCreatorOptions) {
|
|
|
|
nodeCreatorStore.openNodeCreator(options);
|
|
|
|
}
|
2024-05-23 01:42:10 -07:00
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
/**
|
|
|
|
* Executions
|
|
|
|
*/
|
2024-05-23 01:42:10 -07:00
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
async function onRunWorkflow() {
|
2024-07-08 03:25:18 -07:00
|
|
|
trackRunWorkflow();
|
2024-05-23 01:42:10 -07:00
|
|
|
|
2024-07-08 03:25:18 -07:00
|
|
|
await runWorkflow({});
|
2024-05-23 01:42:10 -07:00
|
|
|
}
|
|
|
|
|
2024-07-08 03:25:18 -07:00
|
|
|
function trackRunWorkflow() {
|
|
|
|
void workflowHelpers.getWorkflowDataToSave().then((workflowData) => {
|
|
|
|
const telemetryPayload = {
|
|
|
|
workflow_id: workflowId.value,
|
|
|
|
node_graph_string: JSON.stringify(
|
|
|
|
TelemetryHelpers.generateNodesGraph(
|
|
|
|
workflowData as IWorkflowBase,
|
|
|
|
workflowHelpers.getNodeTypes(),
|
|
|
|
{ isCloudDeployment: settingsStore.isCloudDeployment },
|
|
|
|
).nodeGraph,
|
|
|
|
),
|
|
|
|
};
|
|
|
|
telemetry.track('User clicked execute workflow button', telemetryPayload);
|
|
|
|
void externalHooks.run('nodeView.onRunWorkflow', telemetryPayload);
|
|
|
|
});
|
2024-06-25 02:11:44 -07:00
|
|
|
}
|
2024-05-23 01:42:10 -07:00
|
|
|
|
2024-07-08 03:25:18 -07:00
|
|
|
async function openExecution(_executionId: string) {
|
|
|
|
// @TODO
|
2024-05-23 01:42:10 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2024-06-25 02:11:44 -07:00
|
|
|
* Keboard
|
2024-05-23 01:42:10 -07:00
|
|
|
*/
|
2024-06-25 02:11:44 -07:00
|
|
|
|
|
|
|
function addKeyboardEventBindings() {
|
|
|
|
// document.addEventListener('keydown', this.keyDown);
|
|
|
|
// document.addEventListener('keyup', this.keyUp);
|
|
|
|
}
|
|
|
|
|
|
|
|
function removeKeyboardEventBindings() {
|
|
|
|
// document.removeEventListener('keydown', this.keyDown);
|
|
|
|
// document.removeEventListener('keyup', this.keyUp);
|
|
|
|
}
|
2024-05-23 01:42:10 -07:00
|
|
|
|
|
|
|
/**
|
2024-06-25 02:11:44 -07:00
|
|
|
* History events
|
2024-05-23 01:42:10 -07:00
|
|
|
*/
|
2024-06-25 02:11:44 -07:00
|
|
|
|
|
|
|
function addUndoRedoEventBindings() {
|
|
|
|
// historyBus.on('nodeMove', onMoveNode);
|
|
|
|
// historyBus.on('revertAddNode', onRevertAddNode);
|
|
|
|
historyBus.on('revertRemoveNode', onRevertDeleteNode);
|
|
|
|
// historyBus.on('revertAddConnection', onRevertAddConnection);
|
|
|
|
historyBus.on('revertRemoveConnection', onRevertDeleteConnection);
|
|
|
|
historyBus.on('revertRenameNode', onRevertRenameNode);
|
|
|
|
// historyBus.on('enableNodeToggle', onRevertEnableToggle);
|
2024-05-23 01:42:10 -07:00
|
|
|
}
|
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
function removeUndoRedoEventBindings() {
|
|
|
|
// historyBus.off('nodeMove', onMoveNode);
|
|
|
|
// historyBus.off('revertAddNode', onRevertAddNode);
|
|
|
|
historyBus.off('revertRemoveNode', onRevertDeleteNode);
|
|
|
|
// historyBus.off('revertAddConnection', onRevertAddConnection);
|
|
|
|
historyBus.off('revertRemoveConnection', onRevertDeleteConnection);
|
|
|
|
historyBus.off('revertRenameNode', onRevertRenameNode);
|
|
|
|
// historyBus.off('enableNodeToggle', onRevertEnableToggle);
|
|
|
|
}
|
2024-05-23 01:42:10 -07:00
|
|
|
|
2024-07-08 03:25:18 -07:00
|
|
|
/**
|
|
|
|
* Source control
|
|
|
|
*/
|
|
|
|
|
|
|
|
async function onSourceControlPull() {
|
|
|
|
try {
|
|
|
|
await Promise.all([
|
|
|
|
environmentsStore.fetchAllVariables(),
|
|
|
|
tagsStore.fetchAll(),
|
|
|
|
loadCredentials(),
|
|
|
|
]);
|
|
|
|
|
|
|
|
if (workflowId.value !== null && !uiStore.stateIsDirty) {
|
|
|
|
const workflowData = await workflowsStore.fetchWorkflow(workflowId.value);
|
|
|
|
if (workflowData) {
|
|
|
|
titleChange.titleSet(workflowData.name, 'IDLE');
|
|
|
|
await openWorkflow(workflowData);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
console.error(error);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function addSourceControlEventBindings() {
|
|
|
|
sourceControlEventBus.on('pull', onSourceControlPull);
|
|
|
|
}
|
|
|
|
|
|
|
|
function removeSourceControlEventBindings() {
|
|
|
|
sourceControlEventBus.off('pull', onSourceControlPull);
|
|
|
|
}
|
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
/**
|
|
|
|
* Post message events
|
|
|
|
*/
|
2024-05-23 01:42:10 -07:00
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
function addPostMessageEventBindings() {
|
|
|
|
window.addEventListener('message', onPostMessageReceived);
|
|
|
|
|
|
|
|
if (window.parent) {
|
|
|
|
window.parent.postMessage(
|
|
|
|
JSON.stringify({ command: 'n8nReady', version: rootStore.versionCli }),
|
|
|
|
'*',
|
|
|
|
);
|
2024-05-23 01:42:10 -07:00
|
|
|
}
|
2024-06-25 02:11:44 -07:00
|
|
|
}
|
2024-05-23 01:42:10 -07:00
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
function removePostMessageEventBindings() {
|
|
|
|
window.removeEventListener('message', onPostMessageReceived);
|
2024-05-23 01:42:10 -07:00
|
|
|
}
|
2024-06-17 05:46:55 -07:00
|
|
|
|
|
|
|
async function onPostMessageReceived(message: MessageEvent) {
|
|
|
|
if (!message || typeof message.data !== 'string' || !message.data?.includes?.('"command"')) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
const json = JSON.parse(message.data);
|
|
|
|
if (json && json.command === 'openWorkflow') {
|
|
|
|
try {
|
|
|
|
await importWorkflowExact(json.data);
|
|
|
|
canOpenNDV.value = json.canOpenNDV ?? true;
|
|
|
|
hideNodeIssues.value = json.hideNodeIssues ?? false;
|
|
|
|
isExecutionPreview.value = false;
|
|
|
|
} catch (e) {
|
|
|
|
if (window.top) {
|
|
|
|
window.top.postMessage(
|
|
|
|
JSON.stringify({
|
|
|
|
command: 'error',
|
|
|
|
message: i18n.baseText('openWorkflow.workflowImportError'),
|
|
|
|
}),
|
|
|
|
'*',
|
|
|
|
);
|
|
|
|
}
|
2024-06-25 02:11:44 -07:00
|
|
|
toast.showError(e, i18n.baseText('openWorkflow.workflowImportError'));
|
2024-06-17 05:46:55 -07:00
|
|
|
}
|
|
|
|
} else if (json && json.command === 'openExecution') {
|
|
|
|
try {
|
|
|
|
// If this NodeView is used in preview mode (in iframe) it will not have access to the main app store
|
|
|
|
// so everything it needs has to be sent using post messages and passed down to child components
|
|
|
|
isProductionExecutionPreview.value = json.executionMode !== 'manual';
|
|
|
|
|
|
|
|
await openExecution(json.executionId);
|
|
|
|
canOpenNDV.value = json.canOpenNDV ?? true;
|
|
|
|
hideNodeIssues.value = json.hideNodeIssues ?? false;
|
|
|
|
isExecutionPreview.value = true;
|
|
|
|
} catch (e) {
|
|
|
|
if (window.top) {
|
|
|
|
window.top.postMessage(
|
|
|
|
JSON.stringify({
|
|
|
|
command: 'error',
|
|
|
|
message: i18n.baseText('nodeView.showError.openExecution.title'),
|
|
|
|
}),
|
|
|
|
'*',
|
|
|
|
);
|
|
|
|
}
|
|
|
|
toast.showMessage({
|
|
|
|
title: i18n.baseText('nodeView.showError.openExecution.title'),
|
|
|
|
message: (e as Error).message,
|
|
|
|
type: 'error',
|
|
|
|
});
|
|
|
|
}
|
|
|
|
} else if (json?.command === 'setActiveExecution') {
|
|
|
|
executionsStore.activeExecution = (await executionsStore.fetchExecution(
|
|
|
|
json.executionId,
|
|
|
|
)) as ExecutionSummary;
|
|
|
|
}
|
|
|
|
} catch (e) {}
|
|
|
|
}
|
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
/**
|
|
|
|
* Permission checks
|
|
|
|
*/
|
2024-06-17 05:46:55 -07:00
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
function checkIfEditingIsAllowed(): boolean {
|
|
|
|
if (readOnlyNotification.value?.visible) {
|
|
|
|
return false;
|
|
|
|
}
|
2024-06-17 05:46:55 -07:00
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
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',
|
|
|
|
dangerouslyUseHTMLString: true,
|
|
|
|
}) as unknown as { visible: boolean };
|
2024-06-17 05:46:55 -07:00
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
return false;
|
|
|
|
}
|
2024-06-17 05:46:55 -07:00
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
return true;
|
2024-06-17 05:46:55 -07:00
|
|
|
}
|
|
|
|
|
2024-07-08 03:25:18 -07:00
|
|
|
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 });
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Debug mode
|
|
|
|
*/
|
|
|
|
|
|
|
|
async function checkAndInitDebugMode() {
|
|
|
|
if (route.name === VIEWS.EXECUTION_DEBUG) {
|
|
|
|
titleChange.titleSet(workflowsStore.workflowName, 'DEBUG');
|
|
|
|
if (!workflowsStore.isInDebugMode) {
|
|
|
|
await applyExecutionData(route.params.executionId as string);
|
|
|
|
workflowsStore.isInDebugMode = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
/**
|
|
|
|
* Mouse events
|
|
|
|
*/
|
2024-06-17 05:46:55 -07:00
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
function onClickPane(position: CanvasElement['position']) {
|
|
|
|
lastClickPosition.value = [position.x, position.y];
|
|
|
|
canvasStore.newNodeInsertPosition = [position.x, position.y];
|
2024-06-17 05:46:55 -07:00
|
|
|
}
|
2024-06-25 02:11:44 -07:00
|
|
|
|
2024-07-08 03:25:18 -07:00
|
|
|
/**
|
|
|
|
* Custom Actions
|
|
|
|
*/
|
|
|
|
|
|
|
|
function registerCustomActions() {
|
|
|
|
// @TODO Implement these
|
|
|
|
// this.registerCustomAction({
|
|
|
|
// key: 'openNodeDetail',
|
|
|
|
// action: ({ node }: { node: string }) => {
|
|
|
|
// this.nodeSelectedByName(node, true);
|
|
|
|
// },
|
|
|
|
// });
|
|
|
|
//
|
|
|
|
// this.registerCustomAction({
|
|
|
|
// key: 'openSelectiveNodeCreator',
|
|
|
|
// action: this.openSelectiveNodeCreator,
|
|
|
|
// });
|
|
|
|
//
|
|
|
|
// this.registerCustomAction({
|
|
|
|
// key: 'showNodeCreator',
|
|
|
|
// action: () => {
|
|
|
|
// this.ndvStore.activeNodeName = null;
|
|
|
|
//
|
|
|
|
// void this.$nextTick(() => {
|
|
|
|
// this.showTriggerCreator(NODE_CREATOR_OPEN_SOURCES.TAB);
|
|
|
|
// });
|
|
|
|
// },
|
|
|
|
// });
|
|
|
|
}
|
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
/**
|
|
|
|
* Lifecycle
|
|
|
|
*/
|
|
|
|
|
2024-07-08 03:25:18 -07:00
|
|
|
onBeforeMount(() => {
|
|
|
|
if (!isDemoRoute.value) {
|
|
|
|
pushConnectionStore.pushConnect();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2024-06-25 02:11:44 -07:00
|
|
|
onMounted(async () => {
|
2024-07-08 03:25:18 -07:00
|
|
|
void initializeData().then(() => {
|
|
|
|
void initializeView();
|
|
|
|
|
|
|
|
checkIfRouteIsAllowed();
|
|
|
|
});
|
2024-06-25 02:11:44 -07:00
|
|
|
|
|
|
|
addUndoRedoEventBindings();
|
|
|
|
addPostMessageEventBindings();
|
|
|
|
addKeyboardEventBindings();
|
2024-07-08 03:25:18 -07:00
|
|
|
addSourceControlEventBindings();
|
|
|
|
|
|
|
|
registerCustomActions();
|
2024-06-25 02:11:44 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
onBeforeUnmount(() => {
|
|
|
|
removeKeyboardEventBindings();
|
|
|
|
removePostMessageEventBindings();
|
|
|
|
removeUndoRedoEventBindings();
|
2024-07-08 03:25:18 -07:00
|
|
|
removeSourceControlEventBindings();
|
2024-06-25 02:11:44 -07:00
|
|
|
});
|
2024-05-23 01:42:10 -07:00
|
|
|
</script>
|
|
|
|
|
|
|
|
<template>
|
|
|
|
<WorkflowCanvas
|
|
|
|
v-if="editableWorkflow && editableWorkflowObject"
|
|
|
|
:workflow="editableWorkflow"
|
|
|
|
:workflow-object="editableWorkflowObject"
|
2024-06-04 05:36:27 -07:00
|
|
|
@update:node:position="onUpdateNodePosition"
|
2024-06-17 05:46:55 -07:00
|
|
|
@update:node:active="onSetNodeActive"
|
2024-06-25 02:11:44 -07:00
|
|
|
@update:node:selected="onSetNodeSelected"
|
2024-06-26 06:56:58 -07:00
|
|
|
@update:node:enabled="onToggleNodeDisabled"
|
2024-06-04 05:36:27 -07:00
|
|
|
@delete:node="onDeleteNode"
|
|
|
|
@create:connection="onCreateConnection"
|
|
|
|
@delete:connection="onDeleteConnection"
|
2024-06-25 02:11:44 -07:00
|
|
|
@click:pane="onClickPane"
|
2024-05-23 01:42:10 -07:00
|
|
|
>
|
|
|
|
<div :class="$style.executionButtons">
|
|
|
|
<CanvasExecuteWorkflowButton @click="onRunWorkflow" />
|
|
|
|
</div>
|
|
|
|
<Suspense>
|
|
|
|
<NodeCreation
|
|
|
|
v-if="!isReadOnlyRoute && !isReadOnlyEnvironment"
|
|
|
|
:create-node-active="uiStore.isCreateNodeActive"
|
|
|
|
:node-view-scale="1"
|
|
|
|
@toggle-node-creator="onToggleNodeCreator"
|
2024-06-25 02:11:44 -07:00
|
|
|
@add-nodes="onAddNodesAndConnections"
|
2024-05-23 01:42:10 -07:00
|
|
|
/>
|
|
|
|
</Suspense>
|
2024-06-17 05:46:55 -07:00
|
|
|
<Suspense>
|
|
|
|
<NodeDetailsView
|
2024-06-27 09:50:09 -07:00
|
|
|
:workflow-object="editableWorkflowObject"
|
2024-06-17 05:46:55 -07:00
|
|
|
:read-only="isReadOnlyRoute || isReadOnlyEnvironment"
|
|
|
|
:is-production-execution-preview="isProductionExecutionPreview"
|
2024-06-18 10:15:12 -07:00
|
|
|
:renaming="false"
|
2024-06-25 02:11:44 -07:00
|
|
|
@value-changed="onRenameNode"
|
|
|
|
@switch-selected-node="onSwitchActiveNode"
|
2024-06-17 05:46:55 -07:00
|
|
|
@open-connection-node-creator="onOpenConnectionNodeCreator"
|
|
|
|
/>
|
|
|
|
<!--
|
|
|
|
:renaming="renamingActive"
|
|
|
|
@stop-execution="stopExecution"
|
|
|
|
@save-keyboard-shortcut="onSaveKeyboardShortcut"
|
|
|
|
-->
|
|
|
|
</Suspense>
|
2024-05-23 01:42:10 -07:00
|
|
|
</WorkflowCanvas>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<style lang="scss" module>
|
|
|
|
.executionButtons {
|
|
|
|
position: absolute;
|
|
|
|
display: flex;
|
|
|
|
justify-content: center;
|
|
|
|
align-items: center;
|
|
|
|
left: 50%;
|
|
|
|
transform: translateX(-50%);
|
|
|
|
bottom: var(--spacing-l);
|
|
|
|
width: auto;
|
|
|
|
|
|
|
|
@media (max-width: $breakpoint-2xs) {
|
|
|
|
bottom: 150px;
|
|
|
|
}
|
|
|
|
|
|
|
|
button {
|
|
|
|
display: flex;
|
|
|
|
justify-content: center;
|
|
|
|
align-items: center;
|
|
|
|
margin-left: 0.625rem;
|
|
|
|
|
|
|
|
&:first-child {
|
|
|
|
margin: 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</style>
|