mirror of
https://github.com/n8n-io/n8n.git
synced 2025-01-11 21:07:28 -08:00
feat(editor): Enable saving workflow when node details view is open (#5856)
* feat(editor): Enable saving workflow when node details view is open * ⚡ Preventing event propagation * ⚡ Move save event handler to `NodeDetailsView` * ✅ Added e2e tests * 👕 Fixing linting and removing unnecessary event logic --------- Co-authored-by: Oleg Ivaniv <oleg@n8n.io>
This commit is contained in:
parent
83aef17120
commit
0a59002ef8
|
@ -90,6 +90,14 @@ describe('NDV', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should save workflow using keyboard shortcut from NDV', () => {
|
||||||
|
workflowPage.actions.addNodeToCanvas('Manual');
|
||||||
|
workflowPage.actions.addNodeToCanvas('Set', true, true);
|
||||||
|
ndv.getters.container().should('be.visible');
|
||||||
|
workflowPage.actions.saveWorkflowUsingKeyboardShortcut();
|
||||||
|
workflowPage.getters.isWorkflowSaved();
|
||||||
|
})
|
||||||
|
|
||||||
describe('test output schema view', () => {
|
describe('test output schema view', () => {
|
||||||
const schemaKeys = ['id', 'name', 'email', 'notes', 'country', 'created', 'objectValue', 'prop1', 'prop2'];
|
const schemaKeys = ['id', 'name', 'email', 'notes', 'country', 'created', 'objectValue', 'prop1', 'prop2'];
|
||||||
function setupSchemaWorkflow() {
|
function setupSchemaWorkflow() {
|
||||||
|
|
|
@ -28,7 +28,13 @@
|
||||||
</div>
|
</div>
|
||||||
</n8n-tooltip>
|
</n8n-tooltip>
|
||||||
|
|
||||||
<div class="data-display" v-if="activeNode">
|
<div
|
||||||
|
v-if="activeNode"
|
||||||
|
class="data-display"
|
||||||
|
ref="container"
|
||||||
|
@keydown.capture="onKeyDown"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
<div @click="close" :class="$style.modalBackground"></div>
|
<div @click="close" :class="$style.modalBackground"></div>
|
||||||
<NDVDraggablePanels
|
<NDVDraggablePanels
|
||||||
:isTriggerNode="isTriggerNode"
|
:isTriggerNode="isTriggerNode"
|
||||||
|
@ -156,6 +162,7 @@ import { useNDVStore } from '@/stores/ndv';
|
||||||
import { useNodeTypesStore } from '@/stores/nodeTypes';
|
import { useNodeTypesStore } from '@/stores/nodeTypes';
|
||||||
import { useUIStore } from '@/stores/ui';
|
import { useUIStore } from '@/stores/ui';
|
||||||
import { useSettingsStore } from '@/stores/settings';
|
import { useSettingsStore } from '@/stores/settings';
|
||||||
|
import useDeviceSupport from '@/composables/useDeviceSupport';
|
||||||
|
|
||||||
export default mixins(
|
export default mixins(
|
||||||
externalHooks,
|
externalHooks,
|
||||||
|
@ -184,6 +191,11 @@ export default mixins(
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
setup() {
|
||||||
|
return {
|
||||||
|
...useDeviceSupport(),
|
||||||
|
};
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
settingsEventBus: new Vue(),
|
settingsEventBus: new Vue(),
|
||||||
|
@ -469,6 +481,16 @@ export default mixins(
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
onKeyDown(e: KeyboardEvent) {
|
||||||
|
if (e.key === 's' && this.isCtrlKeyPressed(e)) {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (this.readOnly) return;
|
||||||
|
|
||||||
|
this.$emit('saveKeyboardShortcut', e);
|
||||||
|
}
|
||||||
|
},
|
||||||
onInputItemHover(e: { itemIndex: number; outputIndex: number } | null) {
|
onInputItemHover(e: { itemIndex: number; outputIndex: number } | null) {
|
||||||
if (!this.inputNodeName) {
|
if (!this.inputNodeName) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -40,6 +40,7 @@
|
||||||
"generic.unsavedWork.confirmMessage.confirmButtonText": "Save",
|
"generic.unsavedWork.confirmMessage.confirmButtonText": "Save",
|
||||||
"generic.unsavedWork.confirmMessage.cancelButtonText": "Leave without saving",
|
"generic.unsavedWork.confirmMessage.cancelButtonText": "Leave without saving",
|
||||||
"generic.workflow": "Workflow",
|
"generic.workflow": "Workflow",
|
||||||
|
"generic.workflowSaved": "Workflow changes saved",
|
||||||
"generic.editor": "Editor",
|
"generic.editor": "Editor",
|
||||||
"about.aboutN8n": "About n8n",
|
"about.aboutN8n": "About n8n",
|
||||||
"about.close": "Close",
|
"about.close": "Close",
|
||||||
|
|
|
@ -92,6 +92,7 @@
|
||||||
:isProductionExecutionPreview="isProductionExecutionPreview"
|
:isProductionExecutionPreview="isProductionExecutionPreview"
|
||||||
@valueChanged="valueChanged"
|
@valueChanged="valueChanged"
|
||||||
@stopExecution="stopExecution"
|
@stopExecution="stopExecution"
|
||||||
|
@saveKeyboardShortcut="onSaveKeyboardShortcut"
|
||||||
/>
|
/>
|
||||||
<node-creation
|
<node-creation
|
||||||
v-if="!isReadOnly"
|
v-if="!isReadOnly"
|
||||||
|
@ -752,9 +753,24 @@ export default mixins(
|
||||||
|
|
||||||
return uniqueName;
|
return uniqueName;
|
||||||
},
|
},
|
||||||
async onSaveKeyboardShortcut() {
|
async onSaveKeyboardShortcut(e: KeyboardEvent) {
|
||||||
const saved = await this.saveCurrentWorkflow();
|
let saved = await this.saveCurrentWorkflow();
|
||||||
if (saved) await this.settingsStore.fetchPromptsData();
|
if (saved) await this.settingsStore.fetchPromptsData();
|
||||||
|
if (this.activeNode) {
|
||||||
|
// If NDV is open, save will not work from editable input fields
|
||||||
|
// so don't show success message if this is true
|
||||||
|
if (e.target instanceof HTMLInputElement) {
|
||||||
|
saved = e.target.readOnly;
|
||||||
|
} else {
|
||||||
|
saved = true;
|
||||||
|
}
|
||||||
|
if (saved) {
|
||||||
|
this.$showMessage({
|
||||||
|
title: this.$locale.baseText('generic.workflowSaved'),
|
||||||
|
type: 'success',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
showTriggerCreator(source: NodeCreatorOpenSource) {
|
showTriggerCreator(source: NodeCreatorOpenSource) {
|
||||||
if (this.createNodeActive) return;
|
if (this.createNodeActive) return;
|
||||||
|
@ -994,6 +1010,19 @@ export default mixins(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async keyDown(e: KeyboardEvent) {
|
async keyDown(e: KeyboardEvent) {
|
||||||
|
if (e.key === 's' && this.isCtrlKeyPressed(e)) {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (this.isReadOnly) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.callDebounced('onSaveKeyboardShortcut', { debounceTime: 1000 }, e);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const path = e.path || (e.composedPath && e.composedPath());
|
const path = e.path || (e.composedPath && e.composedPath());
|
||||||
|
|
||||||
|
@ -1082,16 +1111,6 @@ export default mixins(
|
||||||
title: this.$locale.baseText('nodeView.showMessage.keyDown.title'),
|
title: this.$locale.baseText('nodeView.showMessage.keyDown.title'),
|
||||||
type: 'success',
|
type: 'success',
|
||||||
});
|
});
|
||||||
} else if (e.key === 's' && this.isCtrlKeyPressed(e)) {
|
|
||||||
// Save workflow
|
|
||||||
e.stopPropagation();
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
if (this.isReadOnly) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.callDebounced('onSaveKeyboardShortcut', { debounceTime: 1000 });
|
|
||||||
} else if (e.key === 'Enter') {
|
} else if (e.key === 'Enter') {
|
||||||
// Activate the last selected node
|
// Activate the last selected node
|
||||||
const lastSelectedNode = this.lastSelectedNode;
|
const lastSelectedNode = this.lastSelectedNode;
|
||||||
|
|
Loading…
Reference in a new issue