mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-25 04:34:06 -08:00
⚡ Alert before redirect
* 🚧 Resource descriptions * 🚧 Node logic / Genericfunctions setup * 🚧 Tests / changes * 🔧 Prompt User to Save Before Page Unload * ✨ Everything works except refresh * 🎉 Works with ctrl s, now working on a user saving from the side bar * Remove logs * 🚧 Added Vuex dirty state flag as central source of truth for if there are unsaved changes * ⚡ Added asterisk to indicate if workflow is saved or not * Revert "Merge branch 'save-changes-warning' of https://github.com/n8n-io/n8n into save-changes-warning" This reverts commitebc7e76968
, reversing changes made to18c8c408e2
. * ⚡ Returned to using correct uuid lib * ⚡ Reduced dirty state sensetivity - No longer considers opening a node a state change - No longer considers selecting a node a state change * ⚡ Fixed dirty state not being triggered with node connection * ⚡ Build error fixed (let to const) * ⚡ Deconstructed store mutation data parameters for better readability * Revert "⚡ Deconstructed store mutation data parameters for better readability" This reverts commit17ab16d248
. * ⚡ Fix store state dirty when selecting a node * ⚡ Improvements on #911 to display unsaved changes. Now works with back button. (#1098) Co-authored-by: Omar Ajoue <krynble@gmail.com> Co-authored-by: Erin <erin2722@gmail.com> Co-authored-by: Jan <janober@users.noreply.github.com> Co-authored-by: Omar Ajoue <krynble@gmail.com>
This commit is contained in:
commit
bbc34830b0
|
@ -12,14 +12,14 @@
|
|||
<font-awesome-icon icon="check" class="execution-icon success" v-if="executionFinished" title="Execution was successful" />
|
||||
<font-awesome-icon icon="times" class="execution-icon error" v-else title="Execution did fail" />
|
||||
</span>
|
||||
of
|
||||
of
|
||||
<span class="workflow-name clickable" title="Open Workflow">
|
||||
<span @click="openWorkflow(workflowExecution.workflowId)">"{{workflowName}}"</span>
|
||||
</span>
|
||||
workflow
|
||||
</span>
|
||||
<span index="workflow-name" class="current-workflow" v-if="!isReadOnly">
|
||||
<span v-if="currentWorkflow">Workflow: <span class="workflow-name">{{workflowName}}</span></span>
|
||||
<span v-if="currentWorkflow">Workflow: <span class="workflow-name">{{workflowName}}<span v-if="isDirty">*</span></span></span>
|
||||
<span v-else class="workflow-not-saved">Workflow was not saved!</span>
|
||||
</span>
|
||||
|
||||
|
@ -154,6 +154,9 @@ export default mixins(
|
|||
workflowRunning (): boolean {
|
||||
return this.$store.getters.isActionActive('workflowRunning');
|
||||
},
|
||||
isDirty () : boolean {
|
||||
return this.$store.getters.getStateIsDirty;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async openWorkflow (workflowId: string) {
|
||||
|
|
|
@ -398,7 +398,7 @@ export default mixins(
|
|||
return;
|
||||
}
|
||||
|
||||
this.$store.commit('setWorkflowName', workflowName);
|
||||
this.$store.commit('setWorkflowName', {newName: workflowName, setStateDirty: false});
|
||||
|
||||
this.$showMessage({
|
||||
title: 'Workflow renamed',
|
||||
|
@ -448,13 +448,28 @@ export default mixins(
|
|||
} else if (key === 'workflow-settings') {
|
||||
this.workflowSettingsDialogVisible = true;
|
||||
} else if (key === 'workflow-new') {
|
||||
this.$router.push({ name: 'NodeViewNew' });
|
||||
const result = this.$store.getters.getStateIsDirty;
|
||||
if(result) {
|
||||
const importConfirm = await this.confirmMessage(`When you switch workflows your current workflow changes will be lost.`, 'Save your Changes?', 'warning', 'Yes, switch workflows and forget changes');
|
||||
if (importConfirm === true) {
|
||||
this.$store.commit('setStateDirty', false);
|
||||
this.$router.push({ name: 'NodeViewNew' });
|
||||
|
||||
this.$showMessage({
|
||||
title: 'Workflow created',
|
||||
message: 'A new workflow got created!',
|
||||
type: 'success',
|
||||
});
|
||||
this.$showMessage({
|
||||
title: 'Workflow created',
|
||||
message: 'A new workflow got created!',
|
||||
type: 'success',
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.$router.push({ name: 'NodeViewNew' });
|
||||
|
||||
this.$showMessage({
|
||||
title: 'Workflow created',
|
||||
message: 'A new workflow got created!',
|
||||
type: 'success',
|
||||
});
|
||||
}
|
||||
} else if (key === 'credentials-open') {
|
||||
this.credentialOpenDialogVisible = true;
|
||||
} else if (key === 'credentials-new') {
|
||||
|
|
|
@ -33,6 +33,7 @@ import WorkflowActivator from '@/components/WorkflowActivator.vue';
|
|||
|
||||
import { restApi } from '@/components/mixins/restApi';
|
||||
import { genericHelpers } from '@/components/mixins/genericHelpers';
|
||||
import { workflowHelpers } from '@/components/mixins/workflowHelpers';
|
||||
import { showMessage } from '@/components/mixins/showMessage';
|
||||
import { titleChange } from '@/components/mixins/titleChange';
|
||||
import { IWorkflowShortResponse } from '@/Interface';
|
||||
|
@ -43,7 +44,7 @@ export default mixins(
|
|||
genericHelpers,
|
||||
restApi,
|
||||
showMessage,
|
||||
titleChange,
|
||||
workflowHelpers,
|
||||
).extend({
|
||||
name: 'WorkflowOpen',
|
||||
props: [
|
||||
|
@ -89,10 +90,35 @@ export default mixins(
|
|||
this.$emit('closeDialog');
|
||||
return false;
|
||||
},
|
||||
openWorkflow (data: IWorkflowShortResponse, column: any) { // tslint:disable-line:no-any
|
||||
async openWorkflow (data: IWorkflowShortResponse, column: any) { // tslint:disable-line:no-any
|
||||
if (column.label !== 'Active') {
|
||||
this.$titleSet(data.name, 'IDLE');
|
||||
this.$emit('openWorkflow', data.id);
|
||||
|
||||
const currentWorkflowId = this.$store.getters.workflowId;
|
||||
|
||||
if (data.id === currentWorkflowId) {
|
||||
this.$showMessage({
|
||||
title: 'Already open',
|
||||
message: 'This is the current workflow',
|
||||
type: 'error',
|
||||
duration: 1500,
|
||||
});
|
||||
// Do nothing if current workflow is the one user chose to open
|
||||
return;
|
||||
}
|
||||
|
||||
const result = this.$store.getters.getStateIsDirty;
|
||||
if(result) {
|
||||
const importConfirm = await this.confirmMessage(`When you switch workflows your current workflow changes will be lost.`, 'Save your Changes?', 'warning', 'Yes, switch workflows and forget changes');
|
||||
if (importConfirm === false) {
|
||||
return;
|
||||
} else {
|
||||
// This is used to avoid duplicating the message
|
||||
this.$store.commit('setStateDirty', false);
|
||||
this.$emit('openWorkflow', data.id);
|
||||
}
|
||||
} else {
|
||||
this.$emit('openWorkflow', data.id);
|
||||
}
|
||||
}
|
||||
},
|
||||
openDialog () {
|
||||
|
|
|
@ -33,7 +33,7 @@ export const moveNodeWorkflow = mixins(
|
|||
|
||||
const nodeViewOffsetPositionX = offsetPosition[0] + (position.x - this.moveLastPosition[0]);
|
||||
const nodeViewOffsetPositionY = offsetPosition[1] + (position.y - this.moveLastPosition[1]);
|
||||
this.$store.commit('setNodeViewOffsetPosition', [nodeViewOffsetPositionX, nodeViewOffsetPositionY]);
|
||||
this.$store.commit('setNodeViewOffsetPosition', {newOffset: [nodeViewOffsetPositionX, nodeViewOffsetPositionY], setStateDirty: true});
|
||||
|
||||
// Update the last position
|
||||
this.moveLastPosition[0] = position.x;
|
||||
|
@ -101,7 +101,7 @@ export const moveNodeWorkflow = mixins(
|
|||
const offsetPosition = this.$store.getters.getNodeViewOffsetPosition;
|
||||
const nodeViewOffsetPositionX = offsetPosition[0] - normalized.pixelX;
|
||||
const nodeViewOffsetPositionY = offsetPosition[1] - normalized.pixelY;
|
||||
this.$store.commit('setNodeViewOffsetPosition', [nodeViewOffsetPositionX, nodeViewOffsetPositionY]);
|
||||
this.$store.commit('setNodeViewOffsetPosition', {newOffset: [nodeViewOffsetPositionX, nodeViewOffsetPositionY], setStateDirty: true});
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -22,6 +22,7 @@ import {
|
|||
INodeTypesMaxCount,
|
||||
INodeUi,
|
||||
IWorkflowData,
|
||||
IWorkflowDb,
|
||||
IWorkflowDataUpdate,
|
||||
XYPositon,
|
||||
} from '../../Interface';
|
||||
|
@ -30,6 +31,8 @@ import { restApi } from '@/components/mixins/restApi';
|
|||
import { nodeHelpers } from '@/components/mixins/nodeHelpers';
|
||||
import { showMessage } from '@/components/mixins/showMessage';
|
||||
|
||||
import { isEqual } from 'lodash';
|
||||
|
||||
import mixins from 'vue-typed-mixins';
|
||||
|
||||
export const workflowHelpers = mixins(
|
||||
|
@ -417,7 +420,7 @@ export const workflowHelpers = mixins(
|
|||
|
||||
this.$store.commit('setActive', workflowData.active || false);
|
||||
this.$store.commit('setWorkflowId', workflowData.id);
|
||||
this.$store.commit('setWorkflowName', workflowData.name);
|
||||
this.$store.commit('setWorkflowName', {newName: workflowData.name, setStateDirty: false});
|
||||
this.$store.commit('setWorkflowSettings', workflowData.settings || {});
|
||||
} else {
|
||||
// Workflow exists already so update it
|
||||
|
@ -432,7 +435,7 @@ export const workflowHelpers = mixins(
|
|||
}
|
||||
|
||||
this.$store.commit('removeActiveAction', 'workflowSaving');
|
||||
|
||||
this.$store.commit('setStateDirty', false);
|
||||
this.$showMessage({
|
||||
title: 'Workflow saved',
|
||||
message: `The workflow "${workflowData.name}" got saved!`,
|
||||
|
@ -478,5 +481,29 @@ export const workflowHelpers = mixins(
|
|||
node.position[1] += offsetPosition[1];
|
||||
}
|
||||
},
|
||||
async dataHasChanged(id: string) {
|
||||
const currentData = await this.getWorkflowDataToSave();
|
||||
|
||||
let data: IWorkflowDb;
|
||||
data = await this.restApi().getWorkflow(id);
|
||||
|
||||
if(data !== undefined) {
|
||||
const x = {
|
||||
nodes: data.nodes,
|
||||
connections: data.connections,
|
||||
settings: data.settings,
|
||||
name: data.name,
|
||||
};
|
||||
const y = {
|
||||
nodes: currentData.nodes,
|
||||
connections: currentData.connections,
|
||||
settings: currentData.settings,
|
||||
name: currentData.name,
|
||||
};
|
||||
return !isEqual(x, y);
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -74,12 +74,14 @@ export const workflowSave = mixins(
|
|||
|
||||
this.$store.commit('setActive', workflowData.active || false);
|
||||
this.$store.commit('setWorkflowId', workflowData.id);
|
||||
this.$store.commit('setWorkflowName', workflowData.name);
|
||||
this.$store.commit('setWorkflowName', {newName: workflowData.name, setStateDirty: false});
|
||||
this.$store.commit('setWorkflowSettings', workflowData.settings || {});
|
||||
} else {
|
||||
// Workflow exists already so update it
|
||||
await this.restApi().updateWorkflow(currentWorkflow, workflowData);
|
||||
}
|
||||
// Set dirty = false before pushing route so unsaved changes message doesnt trigger.
|
||||
this.$store.commit('setStateDirty', false);
|
||||
|
||||
if (this.$route.params.name !== workflowData.id) {
|
||||
this.$router.push({
|
||||
|
@ -89,7 +91,6 @@ export const workflowSave = mixins(
|
|||
}
|
||||
|
||||
this.$store.commit('removeActiveAction', 'workflowSaving');
|
||||
|
||||
this.$showMessage({
|
||||
title: 'Workflow saved',
|
||||
message: `The workflow "${workflowData.name}" got saved!`,
|
||||
|
|
|
@ -53,6 +53,7 @@ export const store = new Vuex.Store({
|
|||
saveDataSuccessExecution: 'all',
|
||||
saveManualExecutions: false,
|
||||
timezone: 'America/New_York',
|
||||
stateIsDirty: false,
|
||||
executionTimeout: -1,
|
||||
maxExecutionTimeout: Number.MAX_SAFE_INTEGER,
|
||||
versionCli: '0.0.0',
|
||||
|
@ -85,6 +86,7 @@ export const store = new Vuex.Store({
|
|||
state.activeActions.push(action);
|
||||
}
|
||||
},
|
||||
|
||||
removeActiveAction (state, action: string) {
|
||||
const actionIndex = state.activeActions.indexOf(action);
|
||||
if (actionIndex !== -1) {
|
||||
|
@ -94,6 +96,7 @@ export const store = new Vuex.Store({
|
|||
|
||||
// Active Executions
|
||||
addActiveExecution (state, newActiveExecution: IExecutionsCurrentSummaryExtended) {
|
||||
state.stateIsDirty = true;
|
||||
// Check if the execution exists already
|
||||
const activeExecution = state.activeExecutions.find(execution => {
|
||||
return execution.idActive === newActiveExecution.idActive;
|
||||
|
@ -110,6 +113,7 @@ export const store = new Vuex.Store({
|
|||
state.activeExecutions.unshift(newActiveExecution);
|
||||
},
|
||||
finishActiveExecution (state, finishedActiveExecution: IPushDataExecutionFinished) {
|
||||
state.stateIsDirty = true;
|
||||
// Find the execution to set to finished
|
||||
const activeExecution = state.activeExecutions.find(execution => {
|
||||
return execution.idActive === finishedActiveExecution.executionIdActive;
|
||||
|
@ -128,6 +132,7 @@ export const store = new Vuex.Store({
|
|||
Vue.set(activeExecution, 'stoppedAt', finishedActiveExecution.data.stoppedAt);
|
||||
},
|
||||
setActiveExecutions (state, newActiveExecutions: IExecutionsCurrentSummaryExtended[]) {
|
||||
state.stateIsDirty = true;
|
||||
Vue.set(state, 'activeExecutions', newActiveExecutions);
|
||||
},
|
||||
|
||||
|
@ -136,23 +141,31 @@ export const store = new Vuex.Store({
|
|||
state.activeWorkflows = newActiveWorkflows;
|
||||
},
|
||||
setWorkflowActive (state, workflowId: string) {
|
||||
state.stateIsDirty = true;
|
||||
const index = state.activeWorkflows.indexOf(workflowId);
|
||||
if (index === -1) {
|
||||
state.activeWorkflows.push(workflowId);
|
||||
}
|
||||
},
|
||||
setWorkflowInactive (state, workflowId: string) {
|
||||
state.stateIsDirty = true;
|
||||
const index = state.activeWorkflows.indexOf(workflowId);
|
||||
if (index !== -1) {
|
||||
state.selectedNodes.splice(index, 1);
|
||||
}
|
||||
},
|
||||
// Set state condition dirty or not
|
||||
// ** Dirty: if current workflow state has been synchronized with database AKA has it been saved
|
||||
setStateDirty (state, dirty : boolean) {
|
||||
state.stateIsDirty = dirty;
|
||||
},
|
||||
|
||||
// Selected Nodes
|
||||
addSelectedNode (state, node: INodeUi) {
|
||||
state.selectedNodes.push(node);
|
||||
},
|
||||
removeNodeFromSelection (state, node: INodeUi) {
|
||||
state.stateIsDirty = true;
|
||||
let index;
|
||||
for (index in state.selectedNodes) {
|
||||
if (state.selectedNodes[index].name === node.name) {
|
||||
|
@ -178,6 +191,10 @@ export const store = new Vuex.Store({
|
|||
return;
|
||||
}
|
||||
|
||||
if (data.setStateDirty === true) {
|
||||
state.stateIsDirty = true;
|
||||
}
|
||||
|
||||
const sourceData: IConnection = data.connection[0];
|
||||
const destinationData: IConnection = data.connection[1];
|
||||
|
||||
|
@ -213,6 +230,7 @@ export const store = new Vuex.Store({
|
|||
if (connectionExists === false) {
|
||||
state.workflow.connections[sourceData.node][sourceData.type][sourceData.index].push(destinationData);
|
||||
}
|
||||
|
||||
},
|
||||
removeConnection (state, data) {
|
||||
const sourceData = data.connection[0];
|
||||
|
@ -228,6 +246,8 @@ export const store = new Vuex.Store({
|
|||
return;
|
||||
}
|
||||
|
||||
state.stateIsDirty = true;
|
||||
|
||||
const connections = state.workflow.connections[sourceData.node][sourceData.type][sourceData.index];
|
||||
for (const index in connections) {
|
||||
if (connections[index].node === destinationData.node && connections[index].type === destinationData.type && connections[index].index === destinationData.index) {
|
||||
|
@ -235,11 +255,16 @@ export const store = new Vuex.Store({
|
|||
connections.splice(parseInt(index, 10), 1);
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
removeAllConnections (state) {
|
||||
removeAllConnections (state, data) {
|
||||
if (data.setStateDirty === true) {
|
||||
state.stateIsDirty = true;
|
||||
}
|
||||
state.workflow.connections = {};
|
||||
},
|
||||
removeAllNodeConnection (state, node: INodeUi) {
|
||||
state.stateIsDirty = true;
|
||||
// Remove all source connections
|
||||
if (state.workflow.connections.hasOwnProperty(node.name)) {
|
||||
delete state.workflow.connections[node.name];
|
||||
|
@ -277,6 +302,7 @@ export const store = new Vuex.Store({
|
|||
if (state.credentials === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < state.credentials.length; i++) {
|
||||
if (state.credentials[i].id === credentialData.id) {
|
||||
state.credentials.splice(i, 1);
|
||||
|
@ -288,6 +314,7 @@ export const store = new Vuex.Store({
|
|||
if (state.credentials === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < state.credentials.length; i++) {
|
||||
if (state.credentials[i].id === credentialData.id) {
|
||||
state.credentials[i] = credentialData;
|
||||
|
@ -303,6 +330,7 @@ export const store = new Vuex.Store({
|
|||
},
|
||||
|
||||
renameNodeSelectedAndExecution (state, nameData) {
|
||||
state.stateIsDirty = true;
|
||||
// If node has any WorkflowResultData rename also that one that the data
|
||||
// does still get displayed also after node got renamed
|
||||
if (state.workflowExecutionData !== null && state.workflowExecutionData.data.resultData.runData.hasOwnProperty(nameData.old)) {
|
||||
|
@ -320,10 +348,12 @@ export const store = new Vuex.Store({
|
|||
state.workflow.nodes.forEach((node) => {
|
||||
node.issues = undefined;
|
||||
});
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
setNodeIssue (state, nodeIssueData: INodeIssueData) {
|
||||
|
||||
const node = state.workflow.nodes.find(node => {
|
||||
return node.name === nodeIssueData.node;
|
||||
});
|
||||
|
@ -347,6 +377,7 @@ export const store = new Vuex.Store({
|
|||
|
||||
// Set/Overwrite the value
|
||||
Vue.set(node.issues!, nodeIssueData.type, nodeIssueData.value);
|
||||
state.stateIsDirty = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -358,8 +389,11 @@ export const store = new Vuex.Store({
|
|||
},
|
||||
|
||||
// Name
|
||||
setWorkflowName (state, newName: string) {
|
||||
state.workflow.name = newName;
|
||||
setWorkflowName (state, data) {
|
||||
if (data.setStateDirty === true) {
|
||||
state.stateIsDirty = true;
|
||||
}
|
||||
state.workflow.name = data.newName;
|
||||
},
|
||||
|
||||
// Nodes
|
||||
|
@ -376,11 +410,15 @@ export const store = new Vuex.Store({
|
|||
for (let i = 0; i < state.workflow.nodes.length; i++) {
|
||||
if (state.workflow.nodes[i].name === node.name) {
|
||||
state.workflow.nodes.splice(i, 1);
|
||||
state.stateIsDirty = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
removeAllNodes (state) {
|
||||
removeAllNodes (state, data) {
|
||||
if (data.setStateDirty === true) {
|
||||
state.stateIsDirty = true;
|
||||
}
|
||||
state.workflow.nodes.splice(0, state.workflow.nodes.length);
|
||||
},
|
||||
updateNodeProperties (state, updateInformation: INodeUpdatePropertiesInformation) {
|
||||
|
@ -391,6 +429,7 @@ export const store = new Vuex.Store({
|
|||
|
||||
if (node) {
|
||||
for (const key of Object.keys(updateInformation.properties)) {
|
||||
state.stateIsDirty = true;
|
||||
Vue.set(node, key, updateInformation.properties[key]);
|
||||
}
|
||||
}
|
||||
|
@ -405,6 +444,7 @@ export const store = new Vuex.Store({
|
|||
throw new Error(`Node with the name "${updateInformation.name}" could not be found to set parameter.`);
|
||||
}
|
||||
|
||||
state.stateIsDirty = true;
|
||||
Vue.set(node, updateInformation.key, updateInformation.value);
|
||||
},
|
||||
setNodeParameters (state, updateInformation: IUpdateInformation) {
|
||||
|
@ -417,6 +457,7 @@ export const store = new Vuex.Store({
|
|||
throw new Error(`Node with the name "${updateInformation.name}" could not be found to set parameter.`);
|
||||
}
|
||||
|
||||
state.stateIsDirty = true;
|
||||
Vue.set(node, 'parameters', updateInformation.value);
|
||||
},
|
||||
|
||||
|
@ -425,6 +466,7 @@ export const store = new Vuex.Store({
|
|||
state.nodeIndex.push(nodeName);
|
||||
},
|
||||
setNodeIndex (state, newData: { index: number, name: string | null}) {
|
||||
state.stateIsDirty = true;
|
||||
state.nodeIndex[newData.index] = newData.name;
|
||||
},
|
||||
resetNodeIndex (state) {
|
||||
|
@ -435,8 +477,11 @@ export const store = new Vuex.Store({
|
|||
setNodeViewMoveInProgress (state, value: boolean) {
|
||||
state.nodeViewMoveInProgress = value;
|
||||
},
|
||||
setNodeViewOffsetPosition (state, newOffset: XYPositon) {
|
||||
state.nodeViewOffsetPosition = newOffset;
|
||||
setNodeViewOffsetPosition (state, data) {
|
||||
if (data.setStateDirty === true) {
|
||||
state.stateIsDirty = true;
|
||||
}
|
||||
state.nodeViewOffsetPosition = data.newOffset;
|
||||
},
|
||||
|
||||
// Node-Types
|
||||
|
@ -502,7 +547,7 @@ export const store = new Vuex.Store({
|
|||
// TODO: Check if there is an error or whatever that is supposed to be returned
|
||||
return;
|
||||
}
|
||||
|
||||
state.stateIsDirty = true;
|
||||
state.nodeTypes.push(typeData);
|
||||
},
|
||||
|
||||
|
@ -528,7 +573,7 @@ export const store = new Vuex.Store({
|
|||
if (state.workflowExecutionData.data.resultData.runData[pushData.nodeName] === undefined) {
|
||||
Vue.set(state.workflowExecutionData.data.resultData.runData, pushData.nodeName, []);
|
||||
}
|
||||
|
||||
state.stateIsDirty = true;
|
||||
state.workflowExecutionData.data.resultData.runData[pushData.nodeName].push(pushData.data);
|
||||
},
|
||||
|
||||
|
@ -601,6 +646,10 @@ export const store = new Vuex.Store({
|
|||
return `${state.urlBaseWebhook}${state.endpointWebhookTest}`;
|
||||
},
|
||||
|
||||
getStateIsDirty: (state) : boolean => {
|
||||
return state.stateIsDirty;
|
||||
},
|
||||
|
||||
saveDataErrorExecution: (state): string => {
|
||||
return state.saveDataErrorExecution;
|
||||
},
|
||||
|
|
|
@ -131,9 +131,7 @@ import NodeSettings from '@/components/NodeSettings.vue';
|
|||
import RunData from '@/components/RunData.vue';
|
||||
|
||||
import mixins from 'vue-typed-mixins';
|
||||
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { v4 as uuidv4} from 'uuid';
|
||||
import { debounce } from 'lodash';
|
||||
import axios from 'axios';
|
||||
import {
|
||||
|
@ -195,6 +193,41 @@ export default mixins(
|
|||
// When a node gets set as active deactivate the create-menu
|
||||
this.createNodeActive = false;
|
||||
},
|
||||
nodes: {
|
||||
async handler (val, oldVal) {
|
||||
// Load a workflow
|
||||
let workflowId = null as string | null;
|
||||
if (this.$route && this.$route.params.name) {
|
||||
workflowId = this.$route.params.name;
|
||||
}
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
connections: {
|
||||
async handler (val, oldVal) {
|
||||
// Load a workflow
|
||||
let workflowId = null as string | null;
|
||||
if (this.$route && this.$route.params.name) {
|
||||
workflowId = this.$route.params.name;
|
||||
}
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
async beforeRouteLeave(to, from, next) {
|
||||
const result = this.$store.getters.getStateIsDirty;
|
||||
if(result) {
|
||||
const importConfirm = await this.confirmMessage(`When you switch workflows your current workflow changes will be lost.`, 'Save your Changes?', 'warning', 'Yes, switch workflows and forget changes');
|
||||
if (importConfirm === false) {
|
||||
next(false);
|
||||
} else {
|
||||
// Prevent other popups from displaying
|
||||
this.$store.commit('setStateDirty', false);
|
||||
next();
|
||||
}
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
activeNode (): INodeUi | null {
|
||||
|
@ -311,7 +344,7 @@ export default mixins(
|
|||
throw new Error(`Execution with id "${executionId}" could not be found!`);
|
||||
}
|
||||
|
||||
this.$store.commit('setWorkflowName', data.workflowData.name);
|
||||
this.$store.commit('setWorkflowName', {newName: data.workflowData.name, setStateDirty: false});
|
||||
this.$store.commit('setWorkflowId', PLACEHOLDER_EMPTY_WORKFLOW_ID);
|
||||
|
||||
this.$store.commit('setWorkflowExecutionData', data);
|
||||
|
@ -335,10 +368,14 @@ export default mixins(
|
|||
|
||||
this.$store.commit('setActive', data.active || false);
|
||||
this.$store.commit('setWorkflowId', workflowId);
|
||||
this.$store.commit('setWorkflowName', data.name);
|
||||
this.$store.commit('setWorkflowName', {newName: data.name, setStateDirty: false});
|
||||
this.$store.commit('setWorkflowSettings', data.settings || {});
|
||||
|
||||
await this.addNodes(data.nodes, data.connections);
|
||||
|
||||
this.$store.commit('setStateDirty', false);
|
||||
|
||||
return data;
|
||||
},
|
||||
touchTap (e: MouseEvent | TouchEvent) {
|
||||
if (this.isTouchDevice) {
|
||||
|
@ -446,6 +483,8 @@ export default mixins(
|
|||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
this.$store.commit('setStateDirty', false);
|
||||
|
||||
this.callDebounced('saveCurrentWorkflow', 1000);
|
||||
} else if (e.key === 'Enter') {
|
||||
// Activate the last selected node
|
||||
|
@ -1227,7 +1266,6 @@ export default mixins(
|
|||
if (![null, undefined].includes(inputNameOverlay)) {
|
||||
inputNameOverlay.setVisible(false);
|
||||
}
|
||||
|
||||
this.$store.commit('addConnection', {
|
||||
connection: [
|
||||
{
|
||||
|
@ -1241,6 +1279,7 @@ export default mixins(
|
|||
index: targetInfo.index,
|
||||
},
|
||||
],
|
||||
setStateDirty: true,
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -1317,11 +1356,14 @@ export default mixins(
|
|||
];
|
||||
|
||||
await this.addNodes(defaultNodes);
|
||||
this.$store.commit('setStateDirty', false);
|
||||
|
||||
},
|
||||
async initView (): Promise<void> {
|
||||
if (this.$route.params.action === 'workflowSave') {
|
||||
// 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
|
||||
this.$store.commit('setStateDirty', false);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
|
@ -1330,12 +1372,20 @@ export default mixins(
|
|||
const executionId = this.$route.params.id;
|
||||
await this.openExecution(executionId);
|
||||
} else {
|
||||
|
||||
const result = this.$store.getters.getStateIsDirty;
|
||||
if(result) {
|
||||
const importConfirm = await this.confirmMessage(`When you switch workflows your current workflow changes will be lost.`, 'Save your Changes?', 'warning', 'Yes, switch workflows and forget changes');
|
||||
if (importConfirm === false) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
// Load a workflow
|
||||
let workflowId = null as string | null;
|
||||
if (this.$route.params.name) {
|
||||
workflowId = this.$route.params.name;
|
||||
}
|
||||
|
||||
if (workflowId !== null) {
|
||||
const workflow = await this.restApi().getWorkflow(workflowId);
|
||||
this.$titleSet(workflow.name, 'IDLE');
|
||||
|
@ -1349,6 +1399,17 @@ export default mixins(
|
|||
|
||||
document.addEventListener('keydown', this.keyDown);
|
||||
document.addEventListener('keyup', this.keyUp);
|
||||
|
||||
window.addEventListener("beforeunload", (e) => {
|
||||
if(this.$store.getters.getStateIsDirty === true) {
|
||||
const confirmationMessage = 'It looks like you have been editing something. '
|
||||
+ 'If you leave before saving, your changes will be lost.';
|
||||
(e || window.event).returnValue = confirmationMessage; //Gecko + IE
|
||||
return confirmationMessage; //Gecko + Webkit, Safari, Chrome etc.
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
});
|
||||
},
|
||||
__addConnection (connection: [IConnection, IConnection], addVisualConnection = false) {
|
||||
if (addVisualConnection === true) {
|
||||
|
@ -1364,9 +1425,10 @@ export default mixins(
|
|||
detachable: !this.isReadOnly,
|
||||
});
|
||||
} else {
|
||||
const connectionProperties = {connection, setStateDirty: false};
|
||||
// When nodes get connected it gets saved automatically to the storage
|
||||
// so if we do not connect we have to save the connection manually
|
||||
this.$store.commit('addConnection', { connection });
|
||||
this.$store.commit('addConnection', connectionProperties);
|
||||
}
|
||||
},
|
||||
__removeConnection (connection: [IConnection, IConnection], removeVisualConnection = false) {
|
||||
|
@ -1556,7 +1618,7 @@ export default mixins(
|
|||
this.instance.deleteEveryEndpoint();
|
||||
}
|
||||
this.$store.commit('removeAllConnections');
|
||||
this.$store.commit('removeAllNodes');
|
||||
this.$store.commit('removeAllNodes', {setStateDirty: true});
|
||||
|
||||
// Wait a tick that the old nodes had time to get removed
|
||||
await Vue.nextTick();
|
||||
|
@ -1858,8 +1920,8 @@ export default mixins(
|
|||
});
|
||||
}
|
||||
|
||||
this.$store.commit('removeAllConnections');
|
||||
this.$store.commit('removeAllNodes');
|
||||
this.$store.commit('removeAllConnections', {setStateDirty: false});
|
||||
this.$store.commit('removeAllNodes', {setStateDirty: false});
|
||||
|
||||
// Reset workflow execution data
|
||||
this.$store.commit('setWorkflowExecutionData', null);
|
||||
|
@ -1868,7 +1930,7 @@ export default mixins(
|
|||
|
||||
this.$store.commit('setActive', false);
|
||||
this.$store.commit('setWorkflowId', PLACEHOLDER_EMPTY_WORKFLOW_ID);
|
||||
this.$store.commit('setWorkflowName', '');
|
||||
this.$store.commit('setWorkflowName', {newName: '', setStateDirty: false});
|
||||
this.$store.commit('setWorkflowSettings', {});
|
||||
|
||||
this.$store.commit('setActiveExecutionId', null);
|
||||
|
@ -1879,7 +1941,7 @@ export default mixins(
|
|||
this.$store.commit('resetNodeIndex');
|
||||
this.$store.commit('resetSelectedNodes');
|
||||
|
||||
this.$store.commit('setNodeViewOffsetPosition', [0, 0]);
|
||||
this.$store.commit('setNodeViewOffsetPosition', {newOffset: [0, 0], setStateDirty: false});
|
||||
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
@ -1929,13 +1991,13 @@ export default mixins(
|
|||
|
||||
async mounted () {
|
||||
this.$root.$on('importWorkflowData', async (data: IDataObject) => {
|
||||
await this.importWorkflowData(data.data as IWorkflowDataUpdate);
|
||||
const resData = await this.importWorkflowData(data.data as IWorkflowDataUpdate);
|
||||
});
|
||||
|
||||
this.$root.$on('importWorkflowUrl', async (data: IDataObject) => {
|
||||
const workflowData = await this.getWorkflowDataFromUrl(data.url as string);
|
||||
if (workflowData !== undefined) {
|
||||
await this.importWorkflowData(workflowData);
|
||||
const resData = await this.importWorkflowData(workflowData);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in a new issue