mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
refactor(editor): Encapsulate canvas actions (#4416)
* feat(editor): encapsulating canvas actions * fiz(editor): zoomToFit * fiz(editor): zoomToFit * fiz(editor): fix imoprts in canvas controls * fiz(editor): fix imports in node view * fiz(editor): remove unused props from canvas controls * fiz(editor): fix zoomToFit functionality * fiz(editor): move more functions from NodeView to canvas store * chore(editor): code formatting fixes * fix(editor): adding back some lost refactoring after merge * fix(editor): remove console.log * fix(editor): add missing canvasAddButtonPosition * fix(editor): modify root store env query * fix(editor): modify canvas control position styling * fix(editor): modify canvas control position styling * fix(editor): roll back process.env * fix(editor): fix canvas controls positioning * fix(editor): fix canvas controls positioning * fix(editor): adopting new styles after merge * fix(editor): not storing html element in the store * fix(editor): remove unused variables * fix(editor): update canvas controls after conflict resolution * fix(editor): revert main.ts to reduce change noise * fix(editor): remove old store commit * fix(editor): simplify canvas store * fix(editor): reposition execute workflow button in mobile view * fix(editor): fox mouse scroll zoom in canvas * fix(editor): move canvas scroll handling into canvas controls
This commit is contained in:
@@ -15,7 +15,7 @@
|
||||
@mousedown="mouseDown"
|
||||
v-touch:tap="touchTap"
|
||||
@mouseup="mouseUp"
|
||||
@wheel="wheelScroll"
|
||||
@wheel="canvasStore.wheelScroll"
|
||||
>
|
||||
<div id="node-view-background" class="node-view-background" :style="backgroundStyle" />
|
||||
<div
|
||||
@@ -29,8 +29,8 @@
|
||||
@click="showTriggerCreator('trigger_placeholder_button')"
|
||||
v-show="showCanvasAddButton"
|
||||
:showTooltip="!containsTrigger && showTriggerMissingTooltip"
|
||||
:position="canvasAddButtonPosition"
|
||||
@hook:mounted="setRecenteredCanvasAddButtonPosition"
|
||||
:position="canvasStore.canvasAddButtonPosition"
|
||||
@hook:mounted="canvasStore.setRecenteredCanvasAddButtonPosition"
|
||||
/>
|
||||
<div v-for="nodeData in nodes" :key="nodeData.id">
|
||||
<node
|
||||
@@ -86,17 +86,7 @@
|
||||
@toggleNodeCreator="onToggleNodeCreator"
|
||||
@addNode="onAddNode"
|
||||
/>
|
||||
<div
|
||||
:class="{ 'zoom-menu': true, 'regular-zoom-menu': !isDemo, 'demo-zoom-menu': isDemo, expanded: !uiStore.sidebarMenuCollapsed }">
|
||||
<n8n-icon-button @click="zoomToFit" type="tertiary" size="large" :title="$locale.baseText('nodeView.zoomToFit')"
|
||||
icon="expand" />
|
||||
<n8n-icon-button @click="zoomIn" type="tertiary" size="large" :title="$locale.baseText('nodeView.zoomIn')"
|
||||
icon="search-plus" />
|
||||
<n8n-icon-button @click="zoomOut" type="tertiary" size="large" :title="$locale.baseText('nodeView.zoomOut')"
|
||||
icon="search-minus" />
|
||||
<n8n-icon-button v-if="nodeViewScale !== 1 && !isDemo" @click="resetZoom" type="tertiary" size="large"
|
||||
:title="$locale.baseText('nodeView.resetZoom')" icon="undo" />
|
||||
</div>
|
||||
<canvas-controls />
|
||||
<div
|
||||
class="workflow-execute-wrapper" v-if="!isReadOnly"
|
||||
>
|
||||
@@ -137,11 +127,11 @@
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { mapStores } from 'pinia';
|
||||
import {
|
||||
Connection, Endpoint, N8nPlusEndpoint,
|
||||
OnConnectionBindInfo, Connection, Endpoint, N8nPlusEndpoint, jsPlumbInstance,
|
||||
} from 'jsplumb';
|
||||
import type { MessageBoxInputData } from 'element-ui/types/message-box';
|
||||
import { jsPlumb, OnConnectionBindInfo } from 'jsplumb';
|
||||
import once from 'lodash/once';
|
||||
|
||||
import {
|
||||
@@ -223,14 +213,9 @@ import {
|
||||
IUser,
|
||||
INodeUpdatePropertiesInformation,
|
||||
} 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 { mapStores } from 'pinia';
|
||||
import { useUIStore } from '@/stores/ui';
|
||||
import { useSettingsStore } from '@/stores/settings';
|
||||
import { useUsersStore } from '@/stores/users';
|
||||
@@ -241,12 +226,16 @@ import { useRootStore } from '@/stores/n8nRootStore';
|
||||
import { useNDVStore } from '@/stores/ndv';
|
||||
import { useTemplatesStore } from '@/stores/templates';
|
||||
import { useNodeTypesStore } from '@/stores/nodeTypes';
|
||||
import { useCanvasStore } from '@/stores/canvas';
|
||||
|
||||
interface AddNodeOptions {
|
||||
position?: XYPosition;
|
||||
dragAndDrop?: boolean;
|
||||
}
|
||||
|
||||
const NodeCreator = () => import('@/components/Node/NodeCreator/NodeCreator.vue');
|
||||
const NodeCreation = () => import('@/components/Node/NodeCreation.vue');
|
||||
const CanvasControls = () => import('@/components/CanvasControls.vue');
|
||||
|
||||
export default mixins(
|
||||
copyPaste,
|
||||
@@ -268,11 +257,12 @@ export default mixins(
|
||||
components: {
|
||||
NodeDetailsView,
|
||||
Node,
|
||||
NodeCreator: () => import('@/components/Node/NodeCreator/NodeCreator.vue'),
|
||||
NodeCreator,
|
||||
NodeSettings,
|
||||
Sticky,
|
||||
CanvasAddButton,
|
||||
NodeCreation: () => import('@/components/Node/NodeCreation.vue'),
|
||||
NodeCreation,
|
||||
CanvasControls,
|
||||
},
|
||||
errorCaptured: (err, vm, info) => {
|
||||
console.error('errorCaptured'); // eslint-disable-line no-console
|
||||
@@ -323,9 +313,16 @@ export default mixins(
|
||||
},
|
||||
containsTrigger(containsTrigger) {
|
||||
// Re-center CanvasAddButton if there's no triggers
|
||||
if (containsTrigger === false) this.setRecenteredCanvasAddButtonPosition(this.getNodeViewOffsetPosition);
|
||||
if (containsTrigger === false) this.canvasStore.setRecenteredCanvasAddButtonPosition(this.getNodeViewOffsetPosition);
|
||||
else this.tryToAddWelcomeSticky();
|
||||
},
|
||||
nodeViewScale(newScale) {
|
||||
const element = this.$refs.nodeView as HTMLDivElement;
|
||||
|
||||
if(element) {
|
||||
element.style.transform = `scale(${newScale})`;
|
||||
}
|
||||
},
|
||||
},
|
||||
async beforeRouteLeave(to, from, next) {
|
||||
const nextTab = getNodeViewTab(to);
|
||||
@@ -486,16 +483,21 @@ export default mixins(
|
||||
getNodeViewOffsetPosition(): XYPosition {
|
||||
return this.uiStore.nodeViewOffsetPosition;
|
||||
},
|
||||
...mapStores(useCanvasStore),
|
||||
nodeViewScale(): number {
|
||||
return this.canvasStore.nodeViewScale;
|
||||
},
|
||||
instance(): jsPlumbInstance {
|
||||
return this.canvasStore.jsPlumbInstance;
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
GRID_SIZE: CanvasHelpers.GRID_SIZE,
|
||||
STICKY_NODE_TYPE,
|
||||
createNodeActive: false,
|
||||
instance: jsPlumb.getInstance(),
|
||||
lastSelectedConnection: null as null | Connection,
|
||||
lastClickPosition: [450, 450] as XYPosition,
|
||||
nodeViewScale: 1,
|
||||
ctrlKeyPressed: false,
|
||||
stopExecutionInProgress: false,
|
||||
blankRedirect: false,
|
||||
@@ -508,7 +510,6 @@ export default mixins(
|
||||
showStickyButton: false,
|
||||
isExecutionPreview: false,
|
||||
showTriggerMissingTooltip: false,
|
||||
canvasAddButtonPosition: [1, 1] as XYPosition,
|
||||
workflowData: null as INewWorkflowData | null,
|
||||
};
|
||||
},
|
||||
@@ -677,7 +678,7 @@ export default mixins(
|
||||
|
||||
await this.addNodes(deepCopy(data.workflowData.nodes), deepCopy(data.workflowData.connections));
|
||||
this.$nextTick(() => {
|
||||
this.zoomToFit();
|
||||
this.canvasStore.zoomToFit();
|
||||
this.uiStore.stateIsDirty = false;
|
||||
});
|
||||
this.$externalHooks().run('execution.open', { workflowId: data.workflowData.id, workflowName: data.workflowData.name, executionId });
|
||||
@@ -740,7 +741,7 @@ export default mixins(
|
||||
}
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.zoomToFit();
|
||||
this.canvasStore.zoomToFit();
|
||||
});
|
||||
},
|
||||
async openWorkflowTemplate(templateId: string) {
|
||||
@@ -778,7 +779,7 @@ export default mixins(
|
||||
await this.addNodes(data.workflow.nodes, data.workflow.connections);
|
||||
this.workflowData = await this.workflowsStore.getNewWorkflowData(data.name) || {};
|
||||
this.$nextTick(() => {
|
||||
this.zoomToFit();
|
||||
this.canvasStore.zoomToFit();
|
||||
this.uiStore.stateIsDirty = true;
|
||||
});
|
||||
|
||||
@@ -824,7 +825,7 @@ export default mixins(
|
||||
if (!this.credentialsUpdated) {
|
||||
this.uiStore.stateIsDirty = false;
|
||||
}
|
||||
this.zoomToFit();
|
||||
this.canvasStore.zoomToFit();
|
||||
this.$externalHooks().run('workflow.open', { workflowId, workflowName: data.name });
|
||||
this.workflowsStore.activeWorkflowExecution = null;
|
||||
this.stopLoading();
|
||||
@@ -849,20 +850,6 @@ export default mixins(
|
||||
this.mouseUpMouseSelect(e);
|
||||
this.mouseUpMoveWorkflow(e);
|
||||
},
|
||||
wheelScroll(e: WheelEvent) {
|
||||
//* Control + scroll zoom
|
||||
if (e.ctrlKey) {
|
||||
if (e.deltaY > 0) {
|
||||
this.zoomOut();
|
||||
} else {
|
||||
this.zoomIn();
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
this.wheelMoveWorkflow(e);
|
||||
},
|
||||
keyUp(e: KeyboardEvent) {
|
||||
if (e.key === this.controlKeyCode) {
|
||||
this.ctrlKeyPressed = false;
|
||||
@@ -920,15 +907,7 @@ export default mixins(
|
||||
if (lastSelectedNode !== null && lastSelectedNode.type !== STICKY_NODE_TYPE) {
|
||||
this.callDebounced('renameNodePrompt', { debounceTime: 1500 }, lastSelectedNode.name);
|
||||
}
|
||||
} else if ((e.key === '=' || e.key === '+') && !this.isCtrlKeyPressed(e)) {
|
||||
this.zoomIn();
|
||||
} else if ((e.key === '_' || e.key === '-') && !this.isCtrlKeyPressed(e)) {
|
||||
this.zoomOut();
|
||||
} else if ((e.key === '0') && !this.isCtrlKeyPressed(e)) {
|
||||
this.resetZoom();
|
||||
} else if ((e.key === '1') && !this.isCtrlKeyPressed(e)) {
|
||||
this.zoomToFit();
|
||||
} else if ((e.key === 'a') && this.isCtrlKeyPressed(e)) {
|
||||
} else if ((e.key === 'a') && (this.isCtrlKeyPressed(e) === true)) {
|
||||
// Select all nodes
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
@@ -1206,90 +1185,6 @@ export default mixins(
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
resetZoom() {
|
||||
const { scale, offset } = CanvasHelpers.scaleReset({ scale: this.nodeViewScale, offset: this.getNodeViewOffsetPosition });
|
||||
|
||||
this.setZoomLevel(scale);
|
||||
this.uiStore.nodeViewOffsetPosition = offset;
|
||||
},
|
||||
|
||||
zoomIn() {
|
||||
const { scale, offset: [xOffset, yOffset] } = CanvasHelpers.scaleBigger({ scale: this.nodeViewScale, offset: this.getNodeViewOffsetPosition });
|
||||
|
||||
this.setZoomLevel(scale);
|
||||
this.uiStore.nodeViewOffsetPosition = [xOffset, yOffset];
|
||||
},
|
||||
|
||||
zoomOut() {
|
||||
const { scale, offset: [xOffset, yOffset] } = CanvasHelpers.scaleSmaller({ scale: this.nodeViewScale, offset: this.getNodeViewOffsetPosition });
|
||||
|
||||
this.setZoomLevel(scale);
|
||||
this.uiStore.nodeViewOffsetPosition = [xOffset, yOffset];
|
||||
},
|
||||
|
||||
setZoomLevel(zoomLevel: number) {
|
||||
this.nodeViewScale = zoomLevel; // important for background
|
||||
const element = this.$refs.nodeView as HTMLElement;
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
|
||||
// https://docs.jsplumbtoolkit.com/community/current/articles/zooming.html
|
||||
const scaleString = 'scale(' + zoomLevel + ')';
|
||||
|
||||
['webkit', 'moz', 'ms', 'o'].forEach((prefix) => {
|
||||
// @ts-ignore
|
||||
element.style[prefix + 'Transform'] = scaleString;
|
||||
});
|
||||
element.style['transform'] = scaleString;
|
||||
|
||||
// @ts-ignore
|
||||
this.instance.setZoom(zoomLevel);
|
||||
},
|
||||
setRecenteredCanvasAddButtonPosition (offset?: XYPosition) {
|
||||
|
||||
const position = CanvasHelpers.getMidCanvasPosition(this.nodeViewScale, offset || [0, 0]);
|
||||
|
||||
position[0] -= CanvasHelpers.PLACEHOLDER_TRIGGER_NODE_SIZE / 2;
|
||||
position[1] -= CanvasHelpers.PLACEHOLDER_TRIGGER_NODE_SIZE / 2;
|
||||
|
||||
this.canvasAddButtonPosition = CanvasHelpers.getNewNodePosition(this.nodes, position);
|
||||
},
|
||||
|
||||
getPlaceholderTriggerNodeUI (): INodeUi {
|
||||
this.setRecenteredCanvasAddButtonPosition();
|
||||
|
||||
return {
|
||||
id: uuid(),
|
||||
...CanvasHelpers.DEFAULT_PLACEHOLDER_TRIGGER_BUTTON,
|
||||
position: this.canvasAddButtonPosition,
|
||||
};
|
||||
},
|
||||
// Extend nodes with placeholder trigger button as NodeUI object
|
||||
// with the centered position if canvas doesn't contains trigger node
|
||||
getNodesWithPlaceholderNode(): INodeUi[] {
|
||||
const nodes = this.workflowsStore.allNodes;
|
||||
|
||||
const extendedNodes = this.containsTrigger
|
||||
? nodes
|
||||
: [this.getPlaceholderTriggerNodeUI(), ...nodes];
|
||||
|
||||
return extendedNodes;
|
||||
},
|
||||
zoomToFit() {
|
||||
const nodes = this.getNodesWithPlaceholderNode() as INodeUi[];
|
||||
|
||||
if (nodes.length === 0) { // some unknown workflow executions
|
||||
return;
|
||||
}
|
||||
|
||||
const {zoomLevel, offset} = CanvasHelpers.getZoomToFit(nodes, !this.isDemo);
|
||||
|
||||
this.setZoomLevel(zoomLevel);
|
||||
this.uiStore.nodeViewOffsetPosition = offset;
|
||||
},
|
||||
|
||||
async stopExecution() {
|
||||
const executionId = this.workflowsStore.activeExecutionId;
|
||||
if (executionId === null) {
|
||||
@@ -1699,7 +1594,7 @@ export default mixins(
|
||||
const lastSelectedNode = this.lastSelectedNode;
|
||||
|
||||
if (options.position) {
|
||||
newNodeData.position = CanvasHelpers.getNewNodePosition(this.getNodesWithPlaceholderNode(), options.position);
|
||||
newNodeData.position = CanvasHelpers.getNewNodePosition(this.canvasStore.getNodesWithPlaceholderNode(), options.position);
|
||||
} else if (lastSelectedNode) {
|
||||
const lastSelectedConnection = this.lastSelectedConnection;
|
||||
if (lastSelectedConnection) { // set when injecting into a connection
|
||||
@@ -1741,7 +1636,7 @@ export default mixins(
|
||||
// If added node is a trigger and it's the first one added to the canvas
|
||||
// we place it at canvasAddButtonPosition to replace the canvas add button
|
||||
const position = this.nodeTypesStore.isTriggerNode(nodeTypeName) && !this.containsTrigger
|
||||
? this.canvasAddButtonPosition
|
||||
? this.canvasStore.canvasAddButtonPosition
|
||||
// If no node is active find a free spot
|
||||
: this.lastClickPosition as XYPosition;
|
||||
|
||||
@@ -2199,8 +2094,8 @@ export default mixins(
|
||||
this.workflowsStore.activeWorkflowExecution = null;
|
||||
|
||||
this.uiStore.stateIsDirty = false;
|
||||
this.setZoomLevel(1);
|
||||
this.zoomToFit();
|
||||
this.canvasStore.setZoomLevel(1);
|
||||
this.canvasStore.zoomToFit();
|
||||
},
|
||||
tryToAddWelcomeSticky: once(async function(this: any) {
|
||||
const newWorkflow = this.workflowData;
|
||||
@@ -3398,6 +3293,7 @@ export default mixins(
|
||||
this.$root.$on('importWorkflowUrl', this.onImportWorkflowUrlEvent);
|
||||
dataPinningEventBus.$on('pin-data', this.addPinDataConnections);
|
||||
dataPinningEventBus.$on('unpin-data', this.removePinDataConnections);
|
||||
this.canvasStore.isDemo = this.isDemo;
|
||||
},
|
||||
deactivated () {
|
||||
document.removeEventListener('keydown', this.keyDown);
|
||||
@@ -3425,44 +3321,6 @@ export default mixins(
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.zoom-menu {
|
||||
$--zoom-menu-margin: 15;
|
||||
|
||||
position: absolute;
|
||||
left: $sidebar-width + $--zoom-menu-margin;
|
||||
width: 210px;
|
||||
bottom: 108px;
|
||||
left: 35px;
|
||||
line-height: 25px;
|
||||
color: #444;
|
||||
padding-right: 5px;
|
||||
|
||||
button {
|
||||
border: var(--border-base);
|
||||
}
|
||||
|
||||
>* {
|
||||
+* {
|
||||
margin-left: var(--spacing-3xs);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.regular-zoom-menu {
|
||||
@media (max-width: $breakpoint-2xs) {
|
||||
bottom: 90px;
|
||||
}
|
||||
}
|
||||
|
||||
.demo-zoom-menu {
|
||||
left: 10px;
|
||||
bottom: 10px;
|
||||
}
|
||||
|
||||
.node-view-root {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
@@ -3511,13 +3369,20 @@ export default mixins(
|
||||
position: absolute;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
bottom: 110px;
|
||||
width: auto;
|
||||
|
||||
> * {
|
||||
margin-inline-end: 0.625rem;
|
||||
@media (max-width: $breakpoint-2xs) {
|
||||
bottom: 150px;
|
||||
}
|
||||
|
||||
button {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user