fix(editor): Stop unsaved changes popup display when navigating away from an untouched workflow (#5259)

* fix(editor): Stop unsaved changes popup showing up after loading a workflow

* fix(editor): Fix unsaved change confirmation display
This commit is contained in:
Csaba Tuncsik 2023-02-01 16:50:35 +01:00 committed by GitHub
parent 87ceb6f4b8
commit 6a93aed3a2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 81 additions and 121 deletions

View file

@ -24,6 +24,7 @@
<script lang="ts"> <script lang="ts">
import ExecutionsSidebar from '@/components/ExecutionsView/ExecutionsSidebar.vue'; import ExecutionsSidebar from '@/components/ExecutionsView/ExecutionsSidebar.vue';
import { import {
MAIN_HEADER_TABS,
MODAL_CANCEL, MODAL_CANCEL,
MODAL_CLOSE, MODAL_CLOSE,
MODAL_CONFIRMED, MODAL_CONFIRMED,
@ -121,36 +122,34 @@ export default mixins(
}, },
}, },
async beforeRouteLeave(to, from, next) { async beforeRouteLeave(to, from, next) {
const nextTab = getNodeViewTab(to); if (getNodeViewTab(to) === MAIN_HEADER_TABS.WORKFLOW) {
// When leaving for a page that's not a workflow view tab, ask to save changes next();
if (!nextTab) { return;
const result = this.uiStore.stateIsDirty; }
if (result) { if (this.uiStore.stateIsDirty) {
const confirmModal = await this.confirmModal( const confirmModal = await this.confirmModal(
this.$locale.baseText('generic.unsavedWork.confirmMessage.message'), this.$locale.baseText('generic.unsavedWork.confirmMessage.message'),
this.$locale.baseText('generic.unsavedWork.confirmMessage.headline'), this.$locale.baseText('generic.unsavedWork.confirmMessage.headline'),
'warning', 'warning',
this.$locale.baseText('generic.unsavedWork.confirmMessage.confirmButtonText'), this.$locale.baseText('generic.unsavedWork.confirmMessage.confirmButtonText'),
this.$locale.baseText('generic.unsavedWork.confirmMessage.cancelButtonText'), this.$locale.baseText('generic.unsavedWork.confirmMessage.cancelButtonText'),
true, true,
); );
if (confirmModal === MODAL_CONFIRMED) { if (confirmModal === MODAL_CONFIRMED) {
const saved = await this.saveCurrentWorkflow({}, false); const saved = await this.saveCurrentWorkflow({}, false);
if (saved) this.settingsStore.fetchPromptsData(); if (saved) {
this.uiStore.stateIsDirty = false; await this.settingsStore.fetchPromptsData();
next();
} else if (confirmModal === MODAL_CANCEL) {
this.uiStore.stateIsDirty = false;
next();
} else if (confirmModal === MODAL_CLOSE) {
next(false);
} }
} else { this.uiStore.stateIsDirty = false;
next();
} else if (confirmModal === MODAL_CANCEL) {
this.uiStore.stateIsDirty = false;
next(); next();
} }
} else {
next();
} }
next();
}, },
async mounted() { async mounted() {
this.loading = true; this.loading = true;

View file

@ -315,7 +315,7 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
}, },
setWorkflowName(data: { newName: string; setStateDirty: boolean }): void { setWorkflowName(data: { newName: string; setStateDirty: boolean }): void {
if (data.setStateDirty === true) { if (data.setStateDirty) {
const uiStore = useUIStore(); const uiStore = useUIStore();
uiStore.stateIsDirty = true; uiStore.stateIsDirty = true;
} }
@ -532,16 +532,12 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
dataPinningEventBus.$emit('unpin-data', { [payload.node.name]: undefined }); dataPinningEventBus.$emit('unpin-data', { [payload.node.name]: undefined });
}, },
addConnection(data: { connection: IConnection[]; setStateDirty: boolean }): void { addConnection(data: { connection: IConnection[] }): void {
if (data.connection.length !== 2) { if (data.connection.length !== 2) {
// All connections need two entries // All connections need two entries
// TODO: Check if there is an error or whatever that is supposed to be returned // TODO: Check if there is an error or whatever that is supposed to be returned
return; return;
} }
const uiStore = useUIStore();
if (data.setStateDirty === true) {
uiStore.stateIsDirty = true;
}
const sourceData: IConnection = data.connection[0]; const sourceData: IConnection = data.connection[0];
const destinationData: IConnection = data.connection[1]; const destinationData: IConnection = data.connection[1];
@ -626,7 +622,7 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
}, },
removeAllConnections(data: { setStateDirty: boolean }): void { removeAllConnections(data: { setStateDirty: boolean }): void {
if (data && data.setStateDirty === true) { if (data && data.setStateDirty) {
const uiStore = useUIStore(); const uiStore = useUIStore();
uiStore.stateIsDirty = true; uiStore.stateIsDirty = true;
} }
@ -771,7 +767,7 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
}, },
removeAllNodes(data: { setStateDirty: boolean; removePinData: boolean }): void { removeAllNodes(data: { setStateDirty: boolean; removePinData: boolean }): void {
if (data.setStateDirty === true) { if (data.setStateDirty) {
const uiStore = useUIStore(); const uiStore = useUIStore();
uiStore.stateIsDirty = true; uiStore.stateIsDirty = true;
} }

View file

@ -76,19 +76,14 @@ export const closestNumberDivisibleBy = (inputNumber: number, divisibleBy: numbe
}; };
export const getNodeViewTab = (route: Route): string | null => { export const getNodeViewTab = (route: Route): string | null => {
const routeMeta = route.meta; if (route.meta?.nodeView) {
if (routeMeta && routeMeta.nodeView === true) {
return MAIN_HEADER_TABS.WORKFLOW; return MAIN_HEADER_TABS.WORKFLOW;
} else { } else if (
const executionTabRoutes = [ [VIEWS.WORKFLOW_EXECUTIONS, VIEWS.EXECUTION_PREVIEW, VIEWS.EXECUTION_HOME]
VIEWS.WORKFLOW_EXECUTIONS.toString(), .map(String)
VIEWS.EXECUTION_PREVIEW.toString(), .includes(String(route.name))
VIEWS.EXECUTION_HOME.toString(), ) {
]; return MAIN_HEADER_TABS.EXECUTIONS;
if (executionTabRoutes.includes(route.name || '')) {
return MAIN_HEADER_TABS.EXECUTIONS;
}
} }
return null; return null;
}; };

View file

@ -402,55 +402,45 @@ export default mixins(
}, },
}, },
async beforeRouteLeave(to, from, next) { async beforeRouteLeave(to, from, next) {
const nextTab = getNodeViewTab(to); if (getNodeViewTab(to) === MAIN_HEADER_TABS.EXECUTIONS || from.name === VIEWS.TEMPLATE_IMPORT) {
// Only react if leaving workflow tab and going to a separate page next();
if (!nextTab) { return;
// Skip check if in the middle of template import }
if (from.name === VIEWS.TEMPLATE_IMPORT) { // Make sure workflow id is empty when leaving the editor
next(); this.workflowsStore.setWorkflowId(PLACEHOLDER_EMPTY_WORKFLOW_ID);
return; if (this.uiStore.stateIsDirty) {
} const confirmModal = await this.confirmModal(
// Make sure workflow id is empty when leaving the editor this.$locale.baseText('generic.unsavedWork.confirmMessage.message'),
this.workflowsStore.setWorkflowId(PLACEHOLDER_EMPTY_WORKFLOW_ID); this.$locale.baseText('generic.unsavedWork.confirmMessage.headline'),
const result = this.uiStore.stateIsDirty; 'warning',
if (result) { this.$locale.baseText('generic.unsavedWork.confirmMessage.confirmButtonText'),
const confirmModal = await this.confirmModal( this.$locale.baseText('generic.unsavedWork.confirmMessage.cancelButtonText'),
this.$locale.baseText('generic.unsavedWork.confirmMessage.message'), true,
this.$locale.baseText('generic.unsavedWork.confirmMessage.headline'), );
'warning', if (confirmModal === MODAL_CONFIRMED) {
this.$locale.baseText('generic.unsavedWork.confirmMessage.confirmButtonText'), const saved = await this.saveCurrentWorkflow({}, false);
this.$locale.baseText('generic.unsavedWork.confirmMessage.cancelButtonText'), if (saved) {
true, await this.settingsStore.fetchPromptsData();
);
if (confirmModal === MODAL_CONFIRMED) {
const saved = await this.saveCurrentWorkflow({}, false);
if (saved) await this.settingsStore.fetchPromptsData();
this.uiStore.stateIsDirty = false;
if (from.name === VIEWS.NEW_WORKFLOW) {
// Replace the current route with the new workflow route
// before navigating to the new route when saving new workflow.
this.$router.replace(
{ name: VIEWS.WORKFLOW, params: { name: this.currentWorkflow } },
() => {
// We can't use next() here since vue-router
// would prevent the navigation with an error
this.$router.push(to as RawLocation);
},
);
} else {
next();
}
} else if (confirmModal === MODAL_CANCEL) {
await this.resetWorkspace();
this.uiStore.stateIsDirty = false;
next();
} else if (confirmModal === MODAL_CLOSE) {
next(false);
} }
} else { this.uiStore.stateIsDirty = false;
if (from.name === VIEWS.NEW_WORKFLOW) {
// Replace the current route with the new workflow route
// before navigating to the new route when saving new workflow.
this.$router.replace(
{ name: VIEWS.WORKFLOW, params: { name: this.currentWorkflow } },
() => {
// We can't use next() here since vue-router
// would prevent the navigation with an error
this.$router.push(to as RawLocation);
},
);
} else {
next();
}
} else if (confirmModal === MODAL_CANCEL) {
await this.resetWorkspace();
this.uiStore.stateIsDirty = false;
next(); next();
} }
} else { } else {
@ -1979,8 +1969,9 @@ export default mixins(
sourceNodeOutputIndex: number, sourceNodeOutputIndex: number,
targetNodeName: string, targetNodeName: string,
targetNodeOuputIndex: number, targetNodeOuputIndex: number,
trackHistory = false,
) { ) {
this.uiStore.stateIsDirty = true;
if ( if (
this.getConnection( this.getConnection(
sourceNodeName, sourceNodeName,
@ -2039,17 +2030,11 @@ export default mixins(
const targetNodeName = lastSelectedConnection.__meta.targetNodeName; const targetNodeName = lastSelectedConnection.__meta.targetNodeName;
const targetOutputIndex = lastSelectedConnection.__meta.targetOutputIndex; const targetOutputIndex = lastSelectedConnection.__meta.targetOutputIndex;
this.connectTwoNodes( this.connectTwoNodes(newNodeData.name, 0, targetNodeName, targetOutputIndex);
newNodeData.name,
0,
targetNodeName,
targetOutputIndex,
trackHistory,
);
} }
// Connect active node to the newly created one // Connect active node to the newly created one
this.connectTwoNodes(lastSelectedNode.name, outputIndex, newNodeData.name, 0, trackHistory); this.connectTwoNodes(lastSelectedNode.name, outputIndex, newNodeData.name, 0);
} }
this.historyStore.stopRecordingUndo(); this.historyStore.stopRecordingUndo();
}, },
@ -2102,13 +2087,7 @@ export default mixins(
const sourceNodeName = sourceNode.name; const sourceNodeName = sourceNode.name;
const outputIndex = connection.parameters.index; const outputIndex = connection.parameters.index;
this.connectTwoNodes( this.connectTwoNodes(sourceNodeName, outputIndex, this.pullConnActiveNodeName, 0);
sourceNodeName,
outputIndex,
this.pullConnActiveNodeName,
0,
true,
);
this.pullConnActiveNodeName = null; this.pullConnActiveNodeName = null;
} }
return; return;
@ -2181,10 +2160,6 @@ export default mixins(
}, },
]; ];
this.workflowsStore.addConnection({
connection: connectionData,
setStateDirty: true,
});
this.dropPrevented = true; this.dropPrevented = true;
if (!this.suspendRecordingDetachedConnections) { if (!this.suspendRecordingDetachedConnections) {
this.historyStore.pushCommandToUndo(new AddConnectionCommand(connectionData)); this.historyStore.pushCommandToUndo(new AddConnectionCommand(connectionData));
@ -2331,7 +2306,7 @@ export default mixins(
if (connectionInfo) { if (connectionInfo) {
this.historyStore.pushCommandToUndo(new RemoveConnectionCommand(connectionInfo)); this.historyStore.pushCommandToUndo(new RemoveConnectionCommand(connectionInfo));
} }
this.connectTwoNodes(sourceNodeName, outputIndex, this.pullConnActiveNodeName, 0, true); this.connectTwoNodes(sourceNodeName, outputIndex, this.pullConnActiveNodeName, 0);
this.pullConnActiveNodeName = null; this.pullConnActiveNodeName = null;
await this.$nextTick(); await this.$nextTick();
this.historyStore.stopRecordingUndo(); this.historyStore.stopRecordingUndo();
@ -2553,7 +2528,7 @@ export default mixins(
window.addEventListener('beforeunload', (e) => { window.addEventListener('beforeunload', (e) => {
if (this.isDemo) { if (this.isDemo) {
return; return;
} else if (this.uiStore.stateIsDirty === true) { } else if (this.uiStore.stateIsDirty) {
const confirmationMessage = this.$locale.baseText( const confirmationMessage = this.$locale.baseText(
'nodeView.itLooksLikeYouHaveBeenEditingSomething', 'nodeView.itLooksLikeYouHaveBeenEditingSomething',
); );
@ -2595,12 +2570,8 @@ export default mixins(
uuids: uuid, uuids: uuid,
detachable: !this.isReadOnly, 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.workflowsStore.addConnection(connectionProperties);
} }
this.workflowsStore.addConnection({ connection });
setTimeout(() => { setTimeout(() => {
this.addPinDataConnections(this.workflowsStore.pinData); this.addPinDataConnections(this.workflowsStore.pinData);
@ -2984,7 +2955,6 @@ export default mixins(
sourceNodeOutputIndex, sourceNodeOutputIndex,
targetNodeName, targetNodeName,
targetNodeOuputIndex, targetNodeOuputIndex,
trackHistory,
); );
if (waitForNewConnection) { if (waitForNewConnection) {