mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
feat(editor): implement executions preview via the new executions tab in node view (#4311)
* ✨ Added main header tabs with current workflow execution count * ⚡ feat(editor): header tab navigation (no-changelog) (#4244) * ✨ Adding current workflow execution list to the Vuex store * ✨ Updating current workflow executions after running a workflow from the node view * ✨ Keeping the tab view content alive when switching tabs in main header * ✨ Updating main header controls to work with current workflow regardless of active tab * 🐛 Fixing a bug with previous WF executions still visible after creating a new WF * ⚡ Updating saved status when new WF is created * ✨ Implemented initial version of execution perview * ✨ Keeping the WF view alive when switching to executions tab in new navigation * ✨ Implemented executions landing page * ✨ Simplifying node view navigation * ✨ Updating executions view zoom and selection to work with the new layout * ✨ Using N8nRadioButtons component for main header tabs * 💄 Implementing executions page states. Minor refactoring. * ⚡ Merge conflict fixes and pieces of code that were left behind * ⚡ Fixing layout and scrolling changes introduced after sync with master branch * ⚡ Removing keep-alive from node view which broke template opening and some more leftover code * ✔️ Fixing linting errors * ✔️ One more lint error * ⚡ Implemented executions preview using iframes * ⚡ Fixing zoom menu positioning in iframe and adding different loading types to workflow preview * ⚡ Fixing navigation to and from WF templates and template loading * ⚡ Updating and fixing navigation to and from node view * 👌 Addressing previous PR comments * 🐛 Fixing infinite loading when saving a new workflow * 🐛 Handling opening already opened WF when not on Node view * ✨ Implemented empty states for executions view * ⚡ Adding execute button shake flag to the store so it doesn't mess up navigation by modifying route params * 💄 Started adding new styles to execution sidebar * 💄 Adding hover style for execution list * ⚡ Added ExecutionsCard component and added executions helper mixin * ✔️ Fixing leftover conflict * ✔️ One more conflict * ✨ Implemented retry execution menu and manual execution icon. Other minor updates * ✨ Implemented executions filtering * 💄 Updating running executions details in preview * ⚡ Added info accordion to executions sidebar * ✨ Implemented auto-refresh for executions sidebar * 💄 Adding running execution landing page, minor fixes * 💄 General refactoring * ✔️ Adding leftover conflict changes * ✔️ Updating `InfoTip` component test snapshots * ✔️ Fixing linting error * ✔️ Fixing lint errors in vuex store module * 👌 Started addressing review feedback * ⚡ Updating executions preview behaviour when filters are applied * 🐛 Fixing a bug where nodes and connections disappear if something is saved from executions view before loading WF in the main NodeView * 🐛 Fixing pasting in executions view and wrong workflow activator state * ⚡ Improved workflow switching and navigation, updated error message when trying to paste into execution * ⚡ Some more navigation updates * 💄 Fixing tab centering, execution filter button layout, added auto-refresh checkbox * 🐛 Fixing a bug when saving workflow using save button * 💄 Addressing design feedback, added delete execution button * ⚡ Moving main execution logic to the root executions view * ⚡ Implemented execution delete function * ⚡ Updating how switching tabs for new unsaved workflows work * ⚡ Remembering active execution when switching tabs * 💄 Addressing design feedback regarding info accordion * 💄 Updating execution card styling * ⚡ Resetting executions when creating new workflow * Fixing lint error * ⚡ Hiding executions preview is active execution is not in the results. Updated execution list spacing * ⚡ Fixing navigation to and from templates and executions * ⚡ Implemented execution lazy loading and added new background to execution preview * 💄 Disabling import when on executions tab * ⚡ Handling opening executions from different workflow * ⚡ Updating active execution on route change * ⚡ Updating execution tab detection * ⚡ Simplifying and updating navigation. Adding new route for new workflows * ⚡ Updating workflow saving logic to work with new routes * 🐛 Fixing a bug when returning to executions from different workflow * 💄 Updating executions info accordion and node details view modal in execution preview * 💄 Updating workflow activated modal to point to new executions view * ⚡ Implemented opening new executions view from execution modal * ⚡ Handling jsplumb init errors, updating unknown executions style * ⚡ Updating main sidebar after syncing branch * ⚡ Opening new trigger menu from executions view * 💄 Updating sidebar resize behaviour * ✔️ Fixing lint errors * ⚡ Loading executions when mounting executions view * ⚡ Resetting execution data when creating a new workflow * 💄 Minor wording updates * ⚡ Not reloading node view when new workflows are saved * Removing leftover console log * 🐛 Fixed a bug with save dialog not appearing when leaving executions tab * ⚡ Updating manual execution settings detection in info accordion * 💄 Addressing UI issues found during bug bash * Fixing workflow saving logic * ⚡ Preventing navigation if clicked tab is already opened * ⚡ Updating lazy loading behaviour * ⚡ Updating delete executions flow * ⚡ Added retry executions button to the execution preview * ⚡ Adding empty execution state, updating trigger detection logic, removing listeners when node view is not active * 💄 Cosmetic code improvements * ⚡ Trying the performance fix for nodeBase * ⚡ Removing the `NodeBase`fix * 🐛 Fixing a bug when saving the current workflow * 👌 Addressing code review feedback
This commit is contained in:
committed by
GitHub
parent
99157cf581
commit
d833345092
@@ -1,8 +1,22 @@
|
||||
<template>
|
||||
<div class="node-view-root" @dragover="onDragOver" @drop="onDrop">
|
||||
<div class="node-view-wrapper" :class="workflowClasses" @touchstart="mouseDown" @touchend="mouseUp"
|
||||
@touchmove="mouseMoveNodeWorkflow" @mousedown="mouseDown" v-touch:tap="touchTap" @mouseup="mouseUp"
|
||||
@wheel="wheelScroll">
|
||||
<div :class="$style['content']">
|
||||
<div
|
||||
class="node-view-root"
|
||||
id="node-view-root"
|
||||
@dragover="onDragOver"
|
||||
@drop="onDrop"
|
||||
>
|
||||
<div
|
||||
class="node-view-wrapper"
|
||||
:class="workflowClasses"
|
||||
@touchstart="mouseDown"
|
||||
@touchend="mouseUp"
|
||||
@touchmove="mouseMoveNodeWorkflow"
|
||||
@mousedown="mouseDown"
|
||||
v-touch:tap="touchTap"
|
||||
@mouseup="mouseUp"
|
||||
@wheel="wheelScroll"
|
||||
>
|
||||
<div id="node-view-background" class="node-view-background" :style="backgroundStyle" />
|
||||
<div
|
||||
id="node-view"
|
||||
@@ -12,7 +26,7 @@
|
||||
>
|
||||
<canvas-add-button
|
||||
:style="canvasAddButtonStyle"
|
||||
@click="showTriggerCreator('tirger_placeholder_button')"
|
||||
@click="showTriggerCreator('trigger_placeholder_button')"
|
||||
v-show="showCanvasAddButton"
|
||||
:showTooltip="!containsTrigger && showTriggerMissingTooltip"
|
||||
:position="canvasAddButtonPosition"
|
||||
@@ -117,6 +131,7 @@
|
||||
@click.stop="clearExecutionData()" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
@@ -130,6 +145,7 @@ import once from 'lodash/once';
|
||||
|
||||
import {
|
||||
FIRST_ONBOARDING_PROMPT_TIMEOUT,
|
||||
MAIN_HEADER_TABS,
|
||||
MODAL_CANCEL,
|
||||
MODAL_CLOSE,
|
||||
MODAL_CONFIRMED,
|
||||
@@ -205,11 +221,14 @@ import {
|
||||
IWorkflowToShare,
|
||||
} from '@/Interface';
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
import '../plugins/N8nCustomConnectorType';
|
||||
import '../plugins/PlusEndpointType';
|
||||
import { getAccountAge } from '@/modules/userHelpers';
|
||||
import { dataPinningEventBus } from "@/event-bus/data-pinning-event-bus";
|
||||
import { debounceHelper } from '@/components/mixins/debounce';
|
||||
import { getNodeViewTab } from '@/components/helpers';
|
||||
import { Route } from 'vue-router';
|
||||
|
||||
interface AddNodeOptions {
|
||||
position?: XYPosition;
|
||||
@@ -249,8 +268,44 @@ export default mixins(
|
||||
},
|
||||
watch: {
|
||||
// Listen to route changes and load the workflow accordingly
|
||||
'$route': 'initView',
|
||||
activeNode() {
|
||||
'$route' (to: Route, from: Route) {
|
||||
const currentTab = getNodeViewTab(to);
|
||||
const nodeViewNotInitialized = !this.$store.getters['ui/isNodeViewInitialized'];
|
||||
let workflowChanged =
|
||||
from.params.name !== to.params.name &&
|
||||
// Both 'new' and __EMPTY__ are new workflow names, so ignore them when detecting if wf changed
|
||||
!(from.params.name === 'new' && this.currentWorkflow === PLACEHOLDER_EMPTY_WORKFLOW_ID) &&
|
||||
// Also ignore if workflow id changes when saving new workflow
|
||||
to.params.action !== 'workflowSave';
|
||||
const isOpeningTemplate = to.name === VIEWS.TEMPLATE_IMPORT;
|
||||
|
||||
// When entering this tab:
|
||||
if (currentTab === MAIN_HEADER_TABS.WORKFLOW || isOpeningTemplate) {
|
||||
if (workflowChanged || nodeViewNotInitialized || isOpeningTemplate) {
|
||||
this.startLoading();
|
||||
if (nodeViewNotInitialized) {
|
||||
const previousDirtyState = this.$store.getters.getStateIsDirty;
|
||||
this.resetWorkspace();
|
||||
this.$store.commit('setStateDirty', previousDirtyState);
|
||||
}
|
||||
this.initView().then(() => {
|
||||
this.stopLoading();
|
||||
if (this.blankRedirect) {
|
||||
this.blankRedirect = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
// Also, when landing on executions tab, check if workflow data is changed
|
||||
if (currentTab === MAIN_HEADER_TABS.EXECUTIONS) {
|
||||
workflowChanged = from.params.name !== to.params.name && !(to.params.name === 'new' && from.params.name === undefined);
|
||||
if (workflowChanged) {
|
||||
// This will trigger node view to update next time workflow tab is opened
|
||||
this.$store.commit('ui/setNodeViewInitialized', false);
|
||||
}
|
||||
}
|
||||
},
|
||||
activeNode () {
|
||||
// When a node gets set as active deactivate the create-menu
|
||||
this.createNodeActive = false;
|
||||
},
|
||||
@@ -261,30 +316,40 @@ export default mixins(
|
||||
},
|
||||
},
|
||||
async beforeRouteLeave(to, from, next) {
|
||||
this.$store.commit('setSubworkflowExecutionError', null);
|
||||
const result = this.$store.getters.getStateIsDirty;
|
||||
if (result) {
|
||||
const confirmModal = await this.confirmModal(
|
||||
this.$locale.baseText('generic.unsavedWork.confirmMessage.message'),
|
||||
this.$locale.baseText('generic.unsavedWork.confirmMessage.headline'),
|
||||
'warning',
|
||||
this.$locale.baseText('generic.unsavedWork.confirmMessage.confirmButtonText'),
|
||||
this.$locale.baseText('generic.unsavedWork.confirmMessage.cancelButtonText'),
|
||||
true,
|
||||
);
|
||||
|
||||
if (confirmModal === MODAL_CONFIRMED) {
|
||||
const saved = await this.saveCurrentWorkflow({}, false);
|
||||
if (saved) this.$store.dispatch('settings/fetchPromptsData');
|
||||
this.$store.commit('setStateDirty', false);
|
||||
const nextTab = getNodeViewTab(to);
|
||||
// Only react if leaving workflow tab and going to a separate page
|
||||
if (!nextTab) {
|
||||
// Skip check if in the middle of template import
|
||||
if (from.name === VIEWS.TEMPLATE_IMPORT) {
|
||||
next();
|
||||
} else if (confirmModal === MODAL_CANCEL) {
|
||||
this.$store.commit('setStateDirty', false);
|
||||
next();
|
||||
} else if (confirmModal === MODAL_CLOSE) {
|
||||
next(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const result = this.$store.getters.getStateIsDirty;
|
||||
if (result) {
|
||||
const confirmModal = await this.confirmModal(
|
||||
this.$locale.baseText('generic.unsavedWork.confirmMessage.message'),
|
||||
this.$locale.baseText('generic.unsavedWork.confirmMessage.headline'),
|
||||
'warning',
|
||||
this.$locale.baseText('generic.unsavedWork.confirmMessage.confirmButtonText'),
|
||||
this.$locale.baseText('generic.unsavedWork.confirmMessage.cancelButtonText'),
|
||||
true,
|
||||
);
|
||||
|
||||
if (confirmModal === MODAL_CONFIRMED) {
|
||||
const saved = await this.saveCurrentWorkflow({}, false);
|
||||
if (saved) this.$store.dispatch('settings/fetchPromptsData');
|
||||
this.$store.commit('setStateDirty', false);
|
||||
next();
|
||||
} else if (confirmModal === MODAL_CANCEL) {
|
||||
this.$store.commit('setStateDirty', false);
|
||||
next();
|
||||
} else if (confirmModal === MODAL_CLOSE) {
|
||||
next(false);
|
||||
}
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
@@ -351,7 +416,11 @@ export default mixins(
|
||||
};
|
||||
},
|
||||
backgroundStyle(): object {
|
||||
return CanvasHelpers.getBackgroundStyles(this.nodeViewScale, this.getNodeViewOffsetPosition);
|
||||
return CanvasHelpers.getBackgroundStyles(
|
||||
this.nodeViewScale,
|
||||
this.$store.getters.getNodeViewOffsetPosition,
|
||||
this.isExecutionPreview,
|
||||
);
|
||||
},
|
||||
workflowClasses() {
|
||||
const returnClasses = [];
|
||||
@@ -374,9 +443,14 @@ export default mixins(
|
||||
workflowRunning(): boolean {
|
||||
return this.$store.getters.isActionActive('workflowRunning');
|
||||
},
|
||||
currentWorkflow (): string {
|
||||
return this.$route.params.name || this.$store.getters.workflowId;
|
||||
},
|
||||
workflowName (): string {
|
||||
return this.$store.getters.workflowName;
|
||||
},
|
||||
allTriggersDisabled(): boolean {
|
||||
const disabledTriggerNodes = this.triggerNodes.filter(node => node.disabled);
|
||||
|
||||
return disabledTriggerNodes.length === this.triggerNodes.length;
|
||||
},
|
||||
triggerNodes(): INodeUi[] {
|
||||
@@ -414,6 +488,7 @@ export default mixins(
|
||||
dropPrevented: false,
|
||||
renamingActive: false,
|
||||
showStickyButton: false,
|
||||
isExecutionPreview: false,
|
||||
showTriggerMissingTooltip: false,
|
||||
canvasAddButtonPosition: [1, 1] as XYPosition,
|
||||
workflowData: null as INewWorkflowData | null,
|
||||
@@ -442,7 +517,7 @@ export default mixins(
|
||||
this.$externalHooks().run('nodeView.onRunNode', telemetryPayload);
|
||||
this.runWorkflow(nodeName, source);
|
||||
},
|
||||
onRunWorkflow() {
|
||||
async onRunWorkflow() {
|
||||
this.getWorkflowDataToSave().then((workflowData) => {
|
||||
const telemetryPayload = {
|
||||
workflow_id: this.$store.getters.workflowId,
|
||||
@@ -450,9 +525,10 @@ export default mixins(
|
||||
};
|
||||
this.$telemetry.track('User clicked execute workflow button', telemetryPayload);
|
||||
this.$externalHooks().run('nodeView.onRunWorkflow', telemetryPayload);
|
||||
|
||||
});
|
||||
|
||||
this.runWorkflow();
|
||||
await this.runWorkflow();
|
||||
},
|
||||
onRunContainerClick() {
|
||||
if (this.containsTrigger && !this.allTriggersDisabled) return;
|
||||
@@ -559,8 +635,8 @@ export default mixins(
|
||||
this.$nextTick(() => this.$store.commit('nodeCreator/setShowTabs', false));
|
||||
},
|
||||
async openExecution(executionId: string) {
|
||||
this.startLoading();
|
||||
this.resetWorkspace();
|
||||
|
||||
let data: IExecutionResponse | undefined;
|
||||
try {
|
||||
data = await this.restApi().getExecution(executionId);
|
||||
@@ -571,14 +647,11 @@ export default mixins(
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data === undefined) {
|
||||
throw new Error(`Execution with id "${executionId}" could not be found!`);
|
||||
}
|
||||
|
||||
this.$store.commit('setWorkflowName', { newName: data.workflowData.name, setStateDirty: false });
|
||||
this.$store.commit('setWorkflowId', PLACEHOLDER_EMPTY_WORKFLOW_ID);
|
||||
|
||||
this.$store.commit('setWorkflowExecutionData', data);
|
||||
this.$store.commit('setWorkflowPinData', data.workflowData.pinData);
|
||||
|
||||
@@ -587,8 +660,6 @@ export default mixins(
|
||||
this.zoomToFit();
|
||||
this.$store.commit('setStateDirty', false);
|
||||
});
|
||||
|
||||
|
||||
this.$externalHooks().run('execution.open', { workflowId: data.workflowData.id, workflowName: data.workflowData.name, executionId });
|
||||
this.$telemetry.track('User opened read-only execution', { workflow_id: data.workflowData.id, execution_mode: data.mode, execution_finished: data.finished });
|
||||
|
||||
@@ -617,7 +688,6 @@ export default mixins(
|
||||
message: errorMessage,
|
||||
type: 'error',
|
||||
}, shouldTrack);
|
||||
|
||||
if (data.data.resultData.error.stack) {
|
||||
// Display some more information for now in console to make debugging easier
|
||||
// TODO: Improve this in the future by displaying in UI
|
||||
@@ -626,7 +696,6 @@ export default mixins(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((data as IExecutionsSummary).waitTill) {
|
||||
this.$showMessage({
|
||||
title: this.$locale.baseText('nodeView.thisExecutionHasntFinishedYet'),
|
||||
@@ -635,6 +704,7 @@ export default mixins(
|
||||
duration: 0,
|
||||
});
|
||||
}
|
||||
this.stopLoading();
|
||||
},
|
||||
async importWorkflowExact(data: { workflow: IWorkflowDataUpdate }) {
|
||||
if (!data.workflow.nodes || !data.workflow.connections) {
|
||||
@@ -654,9 +724,13 @@ export default mixins(
|
||||
});
|
||||
},
|
||||
async openWorkflowTemplate(templateId: string) {
|
||||
this.startLoading();
|
||||
this.setLoadingText(this.$locale.baseText('nodeView.loadingTemplate'));
|
||||
this.resetWorkspace();
|
||||
|
||||
this.$store.commit('workflows/setCurrentWorkflowExecutions', []);
|
||||
this.$store.commit('workflows/setActiveWorkflowExecution', null);
|
||||
|
||||
let data: IWorkflowTemplate | undefined;
|
||||
try {
|
||||
this.$externalHooks().run('template.requested', { templateId });
|
||||
@@ -685,14 +759,16 @@ export default mixins(
|
||||
this.workflowData = await this.$store.dispatch('workflows/getNewWorkflowData', data.name);
|
||||
this.$nextTick(() => {
|
||||
this.zoomToFit();
|
||||
|
||||
this.$store.commit('setStateDirty', true);
|
||||
});
|
||||
|
||||
this.$externalHooks().run('template.open', { templateId, templateName: data.name, workflow: data.workflow });
|
||||
this.stopLoading();
|
||||
},
|
||||
async openWorkflow(workflowId: string) {
|
||||
this.startLoading();
|
||||
this.resetWorkspace();
|
||||
|
||||
let data: IWorkflowDb | undefined;
|
||||
try {
|
||||
data = await this.restApi().getWorkflow(workflowId);
|
||||
@@ -712,28 +788,23 @@ export default mixins(
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
this.$store.commit('setActive', data.active || false);
|
||||
this.$store.commit('setWorkflowId', workflowId);
|
||||
this.$store.commit('setWorkflowName', { newName: data.name, setStateDirty: false });
|
||||
this.$store.commit('setWorkflowSettings', data.settings || {});
|
||||
this.$store.commit('setWorkflowPinData', data.pinData || {});
|
||||
|
||||
const tags = (data.tags || []) as ITag[];
|
||||
this.$store.commit('tags/upsertTags', tags);
|
||||
|
||||
const tagIds = tags.map((tag) => tag.id);
|
||||
this.$store.commit('setWorkflowTagIds', tagIds || []);
|
||||
|
||||
await this.addNodes(data.nodes, data.connections);
|
||||
if (!this.credentialsUpdated) {
|
||||
this.$store.commit('setStateDirty', false);
|
||||
}
|
||||
|
||||
this.zoomToFit();
|
||||
|
||||
this.$externalHooks().run('workflow.open', { workflowId, workflowName: data.name });
|
||||
|
||||
this.$store.commit('workflows/setActiveWorkflowExecution', null);
|
||||
this.stopLoading();
|
||||
return data;
|
||||
},
|
||||
touchTap(e: MouseEvent | TouchEvent) {
|
||||
@@ -1188,7 +1259,7 @@ export default mixins(
|
||||
return;
|
||||
}
|
||||
|
||||
const { zoomLevel, offset } = CanvasHelpers.getZoomToFit(nodes);
|
||||
const {zoomLevel, offset} = CanvasHelpers.getZoomToFit(nodes, !this.isDemo);
|
||||
|
||||
this.setZoomLevel(zoomLevel);
|
||||
this.$store.commit('setNodeViewOffsetPosition', { newOffset: offset });
|
||||
@@ -1263,13 +1334,15 @@ export default mixins(
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* This method gets called when data got pasted into the window
|
||||
*/
|
||||
async receivedCopyPasteData(plainTextData: string): Promise<void> {
|
||||
let workflowData: IWorkflowDataUpdate | undefined;
|
||||
|
||||
if (this.editAllowedCheck() === false) {
|
||||
return;
|
||||
}
|
||||
// Check if it is an URL which could contain workflow data
|
||||
if (plainTextData.match(/^http[s]?:\/\/.*\.json$/i)) {
|
||||
// Pasted data points to a possible workflow JSON file
|
||||
@@ -2089,8 +2162,11 @@ export default mixins(
|
||||
});
|
||||
},
|
||||
async newWorkflow(): Promise<void> {
|
||||
this.startLoading();
|
||||
await this.resetWorkspace();
|
||||
this.workflowData = await this.$store.dispatch('workflows/getNewWorkflowData');
|
||||
this.$store.commit('workflows/setCurrentWorkflowExecutions', []);
|
||||
this.$store.commit('workflows/setActiveWorkflowExecution', null);
|
||||
|
||||
this.$store.commit('setStateDirty', false);
|
||||
this.setZoomLevel(1);
|
||||
@@ -2123,6 +2199,9 @@ export default mixins(
|
||||
this.$telemetry.track('welcome note inserted');
|
||||
}
|
||||
}
|
||||
this.$store.commit('ui/setNodeViewInitialized', true);
|
||||
this.$store.commit('workflows/setActiveWorkflowExecution', null);
|
||||
this.stopLoading();
|
||||
}),
|
||||
async initView(): Promise<void> {
|
||||
if (this.$route.params.action === 'workflowSave') {
|
||||
@@ -2131,7 +2210,6 @@ export default mixins(
|
||||
this.$store.commit('setStateDirty', false);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
if (this.blankRedirect) {
|
||||
this.blankRedirect = false;
|
||||
}
|
||||
@@ -2144,7 +2222,6 @@ export default mixins(
|
||||
const executionId = this.$route.params.id;
|
||||
await this.openExecution(executionId);
|
||||
} else {
|
||||
|
||||
const result = this.$store.getters.getStateIsDirty;
|
||||
if (result) {
|
||||
const confirmModal = await this.confirmModal(
|
||||
@@ -2155,7 +2232,6 @@ export default mixins(
|
||||
this.$locale.baseText('generic.unsavedWork.confirmMessage.cancelButtonText'),
|
||||
true,
|
||||
);
|
||||
|
||||
if (confirmModal === MODAL_CONFIRMED) {
|
||||
const saved = await this.saveCurrentWorkflow();
|
||||
if (saved) this.$store.dispatch('settings/fetchPromptsData');
|
||||
@@ -2163,7 +2239,6 @@ export default mixins(
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
// Load a workflow
|
||||
let workflowId = null as string | null;
|
||||
if (this.$route.params.name) {
|
||||
@@ -2185,15 +2260,14 @@ export default mixins(
|
||||
// Open existing workflow
|
||||
await this.openWorkflow(workflowId);
|
||||
}
|
||||
} else {
|
||||
} else if (this.$route.meta?.nodeView === true) {
|
||||
// Create new workflow
|
||||
await this.newWorkflow();
|
||||
}
|
||||
}
|
||||
|
||||
this.$store.commit('ui/setNodeViewInitialized', true);
|
||||
document.addEventListener('keydown', this.keyDown);
|
||||
document.addEventListener('keyup', this.keyUp);
|
||||
|
||||
window.addEventListener("beforeunload", (e) => {
|
||||
if (this.isDemo){
|
||||
return;
|
||||
@@ -2206,7 +2280,6 @@ export default mixins(
|
||||
this.startLoading(
|
||||
this.$locale.baseText('nodeView.redirecting'),
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
});
|
||||
@@ -3097,6 +3170,7 @@ export default mixins(
|
||||
if (json && json.command === 'openWorkflow') {
|
||||
try {
|
||||
await this.importWorkflowExact(json);
|
||||
this.isExecutionPreview = false;
|
||||
} catch (e) {
|
||||
if (window.top) {
|
||||
window.top.postMessage(JSON.stringify({ command: 'error', message: this.$locale.baseText('openWorkflow.workflowImportError') }), '*');
|
||||
@@ -3107,6 +3181,20 @@ export default mixins(
|
||||
type: 'error',
|
||||
});
|
||||
}
|
||||
} else if (json && json.command === 'openExecution') {
|
||||
try {
|
||||
await this.openExecution(json.executionId);
|
||||
this.isExecutionPreview = true;
|
||||
} catch (e) {
|
||||
if (window.top) {
|
||||
window.top.postMessage(JSON.stringify({ command: 'error', message: this.$locale.baseText('nodeView.showError.openExecution.title') }), '*');
|
||||
}
|
||||
this.$showMessage({
|
||||
title: this.$locale.baseText('nodeView.showError.openExecution.title'),
|
||||
message: (e as Error).message,
|
||||
type: 'error',
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
}
|
||||
@@ -3169,7 +3257,6 @@ export default mixins(
|
||||
this.addNode(nodeTypeName, { position });
|
||||
},
|
||||
},
|
||||
|
||||
async mounted() {
|
||||
this.$titleReset();
|
||||
window.addEventListener('message', this.onPostMessageReceived);
|
||||
@@ -3178,6 +3265,7 @@ export default mixins(
|
||||
this.$root.$on('importWorkflowUrl', this.onImportWorkflowUrlEvent);
|
||||
|
||||
this.startLoading();
|
||||
this.resetWorkspace();
|
||||
|
||||
const loadPromises = [
|
||||
this.loadActiveWorkflows(),
|
||||
@@ -3202,7 +3290,9 @@ export default mixins(
|
||||
|
||||
this.instance.ready(async () => {
|
||||
try {
|
||||
this.initNodeView();
|
||||
try {
|
||||
this.initNodeView();
|
||||
} catch {} // This will break if mounted after jsplumb has been initiated from executions preview, so continue if it breaks
|
||||
await this.initView();
|
||||
if (window.top) {
|
||||
window.top.postMessage(JSON.stringify({ command: 'n8nReady', version: this.$store.getters.versionCli }), '*');
|
||||
@@ -3257,7 +3347,33 @@ export default mixins(
|
||||
dataPinningEventBus.$on('pin-data', this.addPinDataConnections);
|
||||
dataPinningEventBus.$on('unpin-data', this.removePinDataConnections);
|
||||
},
|
||||
activated() {
|
||||
const openSideMenu = this.$store.getters['ui/getAddFirstStepOnLoad'];
|
||||
if (openSideMenu) {
|
||||
this.showTriggerCreator('trigger_placeholder_button');
|
||||
}
|
||||
this.$store.commit('ui/setAddFirstStepOnLoad', false);
|
||||
|
||||
document.addEventListener('keydown', this.keyDown);
|
||||
document.addEventListener('keyup', this.keyUp);
|
||||
window.addEventListener('message', this.onPostMessageReceived);
|
||||
this.$root.$on('newWorkflow', this.newWorkflow);
|
||||
this.$root.$on('importWorkflowData', this.onImportWorkflowDataEvent);
|
||||
this.$root.$on('importWorkflowUrl', this.onImportWorkflowUrlEvent);
|
||||
dataPinningEventBus.$on('pin-data', this.addPinDataConnections);
|
||||
dataPinningEventBus.$on('unpin-data', this.removePinDataConnections);
|
||||
},
|
||||
deactivated () {
|
||||
document.removeEventListener('keydown', this.keyDown);
|
||||
document.removeEventListener('keyup', this.keyUp);
|
||||
window.removeEventListener('message', this.onPostMessageReceived);
|
||||
this.$root.$off('newWorkflow', this.newWorkflow);
|
||||
this.$root.$off('importWorkflowData', this.onImportWorkflowDataEvent);
|
||||
this.$root.$off('importWorkflowUrl', this.onImportWorkflowUrlEvent);
|
||||
|
||||
dataPinningEventBus.$off('pin-data', this.addPinDataConnections);
|
||||
dataPinningEventBus.$off('unpin-data', this.removePinDataConnections);
|
||||
},
|
||||
destroyed() {
|
||||
this.resetWorkspace();
|
||||
this.$store.commit('setStateDirty', false);
|
||||
@@ -3276,18 +3392,15 @@ export default mixins(
|
||||
.zoom-menu {
|
||||
$--zoom-menu-margin: 15;
|
||||
|
||||
position: fixed;
|
||||
position: absolute;
|
||||
left: $sidebar-width + $--zoom-menu-margin;
|
||||
width: 210px;
|
||||
bottom: 44px;
|
||||
bottom: 108px;
|
||||
left: 35px;
|
||||
line-height: 25px;
|
||||
color: #444;
|
||||
padding-right: 5px;
|
||||
|
||||
&:not(.demo-zoom-menu).expanded {
|
||||
left: $sidebar-expanded-width + $--zoom-menu-margin;
|
||||
}
|
||||
|
||||
button {
|
||||
border: var(--border-base);
|
||||
}
|
||||
@@ -3315,6 +3428,8 @@ export default mixins(
|
||||
}
|
||||
|
||||
.node-view-root {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
background-color: var(--color-canvas-background);
|
||||
width: 100%;
|
||||
@@ -3474,3 +3589,36 @@ export default mixins(
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style module lang="scss">
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
display: flex;
|
||||
overflow: auto;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.shake {
|
||||
animation: 1s 200ms shake;
|
||||
}
|
||||
|
||||
@keyframes shake {
|
||||
10%, 90% {
|
||||
transform: translate3d(-1px, 0, 0);
|
||||
}
|
||||
|
||||
20%, 80% {
|
||||
transform: translate3d(2px, 0, 0);
|
||||
}
|
||||
|
||||
30%, 50%, 70% {
|
||||
transform: translate3d(-4px, 0, 0);
|
||||
}
|
||||
|
||||
40%, 60% {
|
||||
transform: translate3d(4px, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user