diff --git a/packages/editor-ui/src/components/CanvasControls.vue b/packages/editor-ui/src/components/CanvasControls.vue
new file mode 100644
index 0000000000000..d2afb4eed2aaf
--- /dev/null
+++ b/packages/editor-ui/src/components/CanvasControls.vue
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/editor-ui/src/components/mixins/moveNodeWorkflow.ts b/packages/editor-ui/src/components/mixins/moveNodeWorkflow.ts
index 0884215d94049..cae47b0116c4f 100644
--- a/packages/editor-ui/src/components/mixins/moveNodeWorkflow.ts
+++ b/packages/editor-ui/src/components/mixins/moveNodeWorkflow.ts
@@ -1,6 +1,4 @@
import mixins from 'vue-typed-mixins';
-// @ts-ignore
-import normalizeWheel from 'normalize-wheel';
import { deviceSupportHelpers } from '@/components/mixins/deviceSupportHelpers';
import { getMousePosition } from '@/views/canvasHelpers';
import { mapStores } from 'pinia';
@@ -88,12 +86,5 @@ export const moveNodeWorkflow = mixins(
this.moveWorkflow(e);
},
- wheelMoveWorkflow (e: WheelEvent) {
- const normalized = normalizeWheel(e);
- const offsetPosition = this.uiStore.nodeViewOffsetPosition;
- const nodeViewOffsetPositionX = offsetPosition[0] - (e.shiftKey ? normalized.pixelY : normalized.pixelX);
- const nodeViewOffsetPositionY = offsetPosition[1] - (e.shiftKey ? normalized.pixelX : normalized.pixelY);
- this.uiStore.nodeViewOffsetPosition = [nodeViewOffsetPositionX, nodeViewOffsetPositionY];
- },
},
});
diff --git a/packages/editor-ui/src/declarations/normalize-wheel.d.ts b/packages/editor-ui/src/declarations/normalize-wheel.d.ts
new file mode 100644
index 0000000000000..a78742bf17e71
--- /dev/null
+++ b/packages/editor-ui/src/declarations/normalize-wheel.d.ts
@@ -0,0 +1,9 @@
+declare module 'normalize-wheel' {
+ function normalizeWheel(e: WheelEvent): { spinX : number,
+ spinY : number,
+ pixelX : number,
+ pixelY : number
+ };
+
+ export = normalizeWheel;
+}
diff --git a/packages/editor-ui/src/stores/canvas.ts b/packages/editor-ui/src/stores/canvas.ts
new file mode 100644
index 0000000000000..0153719755c5c
--- /dev/null
+++ b/packages/editor-ui/src/stores/canvas.ts
@@ -0,0 +1,129 @@
+import { computed, ref } from 'vue';
+import { defineStore } from 'pinia';
+import { jsPlumb } from 'jsplumb';
+import { v4 as uuid } from 'uuid';
+import normalizeWheel from 'normalize-wheel';
+import { useWorkflowsStore } from '@/stores/workflows';
+import { useNodeTypesStore } from '@/stores/nodeTypes';
+import { useUIStore } from '@/stores/ui';
+import { INodeUi, XYPosition } from '@/Interface';
+import * as CanvasHelpers from '@/views/canvasHelpers';
+import { START_NODE_TYPE } from '@/constants';
+import '@/plugins/N8nCustomConnectorType';
+import '@/plugins/PlusEndpointType';
+
+export const useCanvasStore = defineStore('canvas', () => {
+ const workflowStore = useWorkflowsStore();
+ const nodeTypesStore = useNodeTypesStore();
+ const uiStore = useUIStore();
+ const jsPlumbInstance = jsPlumb.getInstance();
+
+ const nodes = computed(() => workflowStore.allNodes);
+ const triggerNodes = computed(
+ () => nodes.value.filter(
+ node => node.type === START_NODE_TYPE || nodeTypesStore.isTriggerNode(node.type),
+ ),
+ );
+ const isDemo = ref(false);
+ const nodeViewScale = ref(1);
+ const canvasAddButtonPosition = ref([1, 1]);
+
+ const setRecenteredCanvasAddButtonPosition = (offset?: XYPosition) => {
+ const position = CanvasHelpers.getMidCanvasPosition(nodeViewScale.value, offset || [0, 0]);
+
+ position[0] -= CanvasHelpers.PLACEHOLDER_TRIGGER_NODE_SIZE / 2;
+ position[1] -= CanvasHelpers.PLACEHOLDER_TRIGGER_NODE_SIZE / 2;
+
+ canvasAddButtonPosition.value = CanvasHelpers.getNewNodePosition(nodes.value, position);
+ };
+
+ const getPlaceholderTriggerNodeUI = (): INodeUi => {
+ setRecenteredCanvasAddButtonPosition();
+
+ return {
+ id: uuid(),
+ ...CanvasHelpers.DEFAULT_PLACEHOLDER_TRIGGER_BUTTON,
+ position: canvasAddButtonPosition.value,
+ };
+ };
+
+ const getNodesWithPlaceholderNode = (): INodeUi[] =>
+ triggerNodes.value.length > 0 ? nodes.value : [getPlaceholderTriggerNodeUI(), ...nodes.value];
+
+ const setZoomLevel = (zoomLevel: number, offset: XYPosition) => {
+ nodeViewScale.value = zoomLevel;
+ jsPlumbInstance.setZoom(zoomLevel);
+ uiStore.nodeViewOffsetPosition = offset;
+ };
+
+ const resetZoom = () => {
+ const {scale, offset} = CanvasHelpers.scaleReset({
+ scale: nodeViewScale.value,
+ offset: uiStore.nodeViewOffsetPosition,
+ });
+ setZoomLevel(scale, offset);
+ };
+
+ const zoomIn = () => {
+ const {scale, offset} = CanvasHelpers.scaleBigger({
+ scale: nodeViewScale.value,
+ offset: uiStore.nodeViewOffsetPosition,
+ });
+ setZoomLevel(scale, offset);
+ };
+
+ const zoomOut = () => {
+ const {scale, offset} = CanvasHelpers.scaleSmaller({
+ scale: nodeViewScale.value,
+ offset: uiStore.nodeViewOffsetPosition,
+ });
+ setZoomLevel(scale, offset);
+ };
+
+ const zoomToFit = () => {
+ const nodes = getNodesWithPlaceholderNode();
+ if (!nodes.length) { // some unknown workflow executions
+ return;
+ }
+ const {zoomLevel, offset} = CanvasHelpers.getZoomToFit(nodes, !isDemo.value);
+ setZoomLevel(zoomLevel, offset);
+ };
+
+ const wheelMoveWorkflow = (e: WheelEvent) => {
+ const normalized = normalizeWheel(e);
+ const offsetPosition = uiStore.nodeViewOffsetPosition;
+ const nodeViewOffsetPositionX = offsetPosition[0] - (e.shiftKey ? normalized.pixelY : normalized.pixelX);
+ const nodeViewOffsetPositionY = offsetPosition[1] - (e.shiftKey ? normalized.pixelX : normalized.pixelY);
+ uiStore.nodeViewOffsetPosition = [nodeViewOffsetPositionX, nodeViewOffsetPositionY];
+ };
+
+ const wheelScroll = (e: WheelEvent) => {
+ //* Control + scroll zoom
+ if (e.ctrlKey) {
+ if (e.deltaY > 0) {
+ zoomOut();
+ } else {
+ zoomIn();
+ }
+
+ e.preventDefault();
+ return;
+ }
+ wheelMoveWorkflow(e);
+ };
+
+ return {
+ jsPlumbInstance,
+ isDemo,
+ nodeViewScale,
+ canvasAddButtonPosition,
+ setRecenteredCanvasAddButtonPosition,
+ getNodesWithPlaceholderNode,
+ setZoomLevel,
+ resetZoom,
+ zoomIn,
+ zoomOut,
+ zoomToFit,
+ wheelScroll,
+ };
+});
diff --git a/packages/editor-ui/src/views/NodeView.vue b/packages/editor-ui/src/views/NodeView.vue
index d8d73aba10ae6..2599589bd5264 100644
--- a/packages/editor-ui/src/views/NodeView.vue
+++ b/packages/editor-ui/src/views/NodeView.vue
@@ -15,7 +15,7 @@
@mousedown="mouseDown"
v-touch:tap="touchTap"
@mouseup="mouseUp"
- @wheel="wheelScroll"
+ @wheel="canvasStore.wheelScroll"
>
-
-
-
-
-
-
+
@@ -137,11 +127,11 @@