mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-25 12:44:07 -08:00
refactor(editor): Migrate mouseSelect & deviceSupportHelpers mixins to composables (no-changelog) (#5775)
* refactor(editor): Migrate mouseSelect & deviceSupportHelpers mixins to composables (no-changelog) * Fix node drop position and correct event listeners
This commit is contained in:
parent
c9d9069c0e
commit
78c9707fa7
234
packages/editor-ui/src/composables/useCanvasMouseSelect.ts
Normal file
234
packages/editor-ui/src/composables/useCanvasMouseSelect.ts
Normal file
|
@ -0,0 +1,234 @@
|
||||||
|
import { INodeUi, XYPosition } from '@/Interface';
|
||||||
|
|
||||||
|
import useDeviceSupport from './useDeviceSupport';
|
||||||
|
import { useUIStore } from '@/stores/ui';
|
||||||
|
import { useWorkflowsStore } from '@/stores/workflows';
|
||||||
|
import {
|
||||||
|
getMousePosition,
|
||||||
|
getRelativePosition,
|
||||||
|
HEADER_HEIGHT,
|
||||||
|
SIDEBAR_WIDTH,
|
||||||
|
SIDEBAR_WIDTH_EXPANDED,
|
||||||
|
} from '@/utils/nodeViewUtils';
|
||||||
|
import { ref, watchEffect, onMounted, computed, onUnmounted } from 'vue';
|
||||||
|
import { useCanvasStore } from '@/stores/canvas';
|
||||||
|
|
||||||
|
interface ExtendedHTMLSpanElement extends HTMLSpanElement {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function useCanvasMouseSelect() {
|
||||||
|
const selectActive = ref(false);
|
||||||
|
const selectBox = ref(document.createElement('span') as ExtendedHTMLSpanElement);
|
||||||
|
|
||||||
|
const { isTouchDevice, isCtrlKeyPressed } = useDeviceSupport();
|
||||||
|
const uiStore = useUIStore();
|
||||||
|
const canvasStore = useCanvasStore();
|
||||||
|
const workflowsStore = useWorkflowsStore();
|
||||||
|
|
||||||
|
function _setSelectBoxStyle(styles: Record<string, string>) {
|
||||||
|
Object.assign(selectBox.value.style, styles);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _showSelectBox(event: MouseEvent) {
|
||||||
|
const [x, y] = getMousePositionWithinNodeView(event);
|
||||||
|
selectBox.value = Object.assign(selectBox.value, { x, y });
|
||||||
|
|
||||||
|
_setSelectBoxStyle({
|
||||||
|
left: selectBox.value.x + 'px',
|
||||||
|
top: selectBox.value.y + 'px',
|
||||||
|
visibility: 'visible',
|
||||||
|
});
|
||||||
|
selectActive.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _updateSelectBox(event: MouseEvent) {
|
||||||
|
const selectionBox = _getSelectionBox(event);
|
||||||
|
|
||||||
|
_setSelectBoxStyle({
|
||||||
|
left: selectionBox.x + 'px',
|
||||||
|
top: selectionBox.y + 'px',
|
||||||
|
width: selectionBox.width + 'px',
|
||||||
|
height: selectionBox.height + 'px',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function _hideSelectBox() {
|
||||||
|
selectBox.value.x = 0;
|
||||||
|
selectBox.value.y = 0;
|
||||||
|
|
||||||
|
_setSelectBoxStyle({
|
||||||
|
visibility: 'hidden',
|
||||||
|
left: '0px',
|
||||||
|
top: '0px',
|
||||||
|
width: '0px',
|
||||||
|
height: '0px',
|
||||||
|
});
|
||||||
|
selectActive.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _getSelectionBox(event: MouseEvent) {
|
||||||
|
const [x, y] = getMousePositionWithinNodeView(event);
|
||||||
|
return {
|
||||||
|
x: Math.min(x, selectBox.value.x),
|
||||||
|
y: Math.min(y, selectBox.value.y),
|
||||||
|
width: Math.abs(x - selectBox.value.x),
|
||||||
|
height: Math.abs(y - selectBox.value.y),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function _getNodesInSelection(event: MouseEvent): INodeUi[] {
|
||||||
|
const returnNodes: INodeUi[] = [];
|
||||||
|
const selectionBox = _getSelectionBox(event);
|
||||||
|
|
||||||
|
// Go through all nodes and check if they are selected
|
||||||
|
workflowsStore.allNodes.forEach((node: INodeUi) => {
|
||||||
|
// TODO: Currently always uses the top left corner for checking. Should probably use the center instead
|
||||||
|
if (
|
||||||
|
node.position[0] < selectionBox.x ||
|
||||||
|
node.position[0] > selectionBox.x + selectionBox.width
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
node.position[1] < selectionBox.y ||
|
||||||
|
node.position[1] > selectionBox.y + selectionBox.height
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
returnNodes.push(node);
|
||||||
|
});
|
||||||
|
|
||||||
|
return returnNodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _createSelectBox() {
|
||||||
|
selectBox.value.id = 'select-box';
|
||||||
|
_setSelectBoxStyle({
|
||||||
|
margin: '0px auto',
|
||||||
|
border: '2px dotted #FF0000',
|
||||||
|
// Positioned absolutely within #node-view. This is consistent with how nodes are positioned.
|
||||||
|
position: 'absolute',
|
||||||
|
zIndex: '100',
|
||||||
|
visibility: 'hidden',
|
||||||
|
});
|
||||||
|
|
||||||
|
selectBox.value.addEventListener('mouseup', mouseUpMouseSelect);
|
||||||
|
|
||||||
|
const nodeViewEl = document.querySelector('#node-view') as HTMLDivElement;
|
||||||
|
nodeViewEl.appendChild(selectBox.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _mouseMoveSelect(e: MouseEvent) {
|
||||||
|
if (e.buttons === 0) {
|
||||||
|
// Mouse button is not pressed anymore so stop selection mode
|
||||||
|
// Happens normally when mouse leave the view pressed and then
|
||||||
|
// comes back unpressed.
|
||||||
|
mouseUpMouseSelect(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateSelectBox(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
function mouseUpMouseSelect(e: MouseEvent) {
|
||||||
|
if (selectActive.value === false) {
|
||||||
|
if (isTouchDevice === true && e.target instanceof HTMLElement) {
|
||||||
|
if (e.target && e.target.id.includes('node-view')) {
|
||||||
|
// Deselect all nodes
|
||||||
|
deselectAllNodes();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If it is not active return directly.
|
||||||
|
// Else normal node dragging will not work.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
document.removeEventListener('mousemove', _mouseMoveSelect);
|
||||||
|
|
||||||
|
// Deselect all nodes
|
||||||
|
deselectAllNodes();
|
||||||
|
|
||||||
|
// Select the nodes which are in the selection box
|
||||||
|
const selectedNodes = _getNodesInSelection(e);
|
||||||
|
selectedNodes.forEach((node) => {
|
||||||
|
nodeSelected(node);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (selectedNodes.length === 1) {
|
||||||
|
uiStore.lastSelectedNode = selectedNodes[0].name;
|
||||||
|
}
|
||||||
|
|
||||||
|
_hideSelectBox();
|
||||||
|
}
|
||||||
|
function mouseDownMouseSelect(e: MouseEvent, moveButtonPressed: boolean) {
|
||||||
|
if (isCtrlKeyPressed(e) === true || moveButtonPressed) {
|
||||||
|
// We only care about it when the ctrl key is not pressed at the same time.
|
||||||
|
// So we exit when it is pressed.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uiStore.isActionActive('dragActive')) {
|
||||||
|
// If a node does currently get dragged we do not activate the selection
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_showSelectBox(e);
|
||||||
|
|
||||||
|
// Leave like this.
|
||||||
|
// Do not add an anonymous function because then remove would not work anymore
|
||||||
|
document.addEventListener('mousemove', _mouseMoveSelect);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMousePositionWithinNodeView(event: MouseEvent | TouchEvent): XYPosition {
|
||||||
|
const [mouseX, mouseY] = getMousePosition(event);
|
||||||
|
|
||||||
|
const sidebarWidth = canvasStore.isDemo
|
||||||
|
? 0
|
||||||
|
: uiStore.sidebarMenuCollapsed
|
||||||
|
? SIDEBAR_WIDTH
|
||||||
|
: SIDEBAR_WIDTH_EXPANDED;
|
||||||
|
const headerHeight = canvasStore.isDemo ? 0 : HEADER_HEIGHT;
|
||||||
|
|
||||||
|
const relativeX = mouseX - sidebarWidth;
|
||||||
|
const relativeY = mouseY - headerHeight;
|
||||||
|
const nodeViewScale = canvasStore.nodeViewScale;
|
||||||
|
const nodeViewOffsetPosition = uiStore.nodeViewOffsetPosition;
|
||||||
|
|
||||||
|
return getRelativePosition(relativeX, relativeY, nodeViewScale, nodeViewOffsetPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
function nodeDeselected(node: INodeUi) {
|
||||||
|
uiStore.removeNodeFromSelection(node);
|
||||||
|
instance.value.removeFromDragSelection(instance.value.getManagedElement(node?.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
function nodeSelected(node: INodeUi) {
|
||||||
|
uiStore.addSelectedNode(node);
|
||||||
|
instance.value.addToDragSelection(instance.value.getManagedElement(node?.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
function deselectAllNodes() {
|
||||||
|
instance.value.clearDragSelection();
|
||||||
|
uiStore.resetSelectedNodes();
|
||||||
|
uiStore.lastSelectedNode = null;
|
||||||
|
uiStore.lastSelectedNodeOutputIndex = null;
|
||||||
|
|
||||||
|
canvasStore.lastSelectedConnection = null;
|
||||||
|
canvasStore.newNodeInsertPosition = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const instance = computed(() => canvasStore.jsPlumbInstance);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
_createSelectBox();
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
getMousePositionWithinNodeView,
|
||||||
|
mouseUpMouseSelect,
|
||||||
|
mouseDownMouseSelect,
|
||||||
|
nodeDeselected,
|
||||||
|
nodeSelected,
|
||||||
|
deselectAllNodes,
|
||||||
|
};
|
||||||
|
}
|
37
packages/editor-ui/src/composables/useDeviceSupport.ts
Normal file
37
packages/editor-ui/src/composables/useDeviceSupport.ts
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
interface DeviceSupportHelpers {
|
||||||
|
isTouchDevice: boolean;
|
||||||
|
isMacOs: boolean;
|
||||||
|
controlKeyCode: string;
|
||||||
|
isCtrlKeyPressed: (e: MouseEvent | KeyboardEvent) => boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function useDeviceSupportHelpers(): DeviceSupportHelpers {
|
||||||
|
const isTouchDevice = ref('ontouchstart' in window || navigator.maxTouchPoints > 0);
|
||||||
|
const userAgent = ref(navigator.userAgent.toLowerCase());
|
||||||
|
const isMacOs = ref(
|
||||||
|
userAgent.value.includes('macintosh') ||
|
||||||
|
userAgent.value.includes('ipad') ||
|
||||||
|
userAgent.value.includes('iphone') ||
|
||||||
|
userAgent.value.includes('ipod'),
|
||||||
|
);
|
||||||
|
const controlKeyCode = ref(isMacOs.value ? 'Meta' : 'Control');
|
||||||
|
|
||||||
|
function isCtrlKeyPressed(e: MouseEvent | KeyboardEvent): boolean {
|
||||||
|
if (isTouchDevice.value === true) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (isMacOs.value) {
|
||||||
|
return (e as KeyboardEvent).metaKey;
|
||||||
|
}
|
||||||
|
return (e as KeyboardEvent).ctrlKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
isTouchDevice: isTouchDevice.value,
|
||||||
|
isMacOs: isMacOs.value,
|
||||||
|
controlKeyCode: controlKeyCode.value,
|
||||||
|
isCtrlKeyPressed,
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,225 +0,0 @@
|
||||||
import { INodeUi, XYPosition } from '@/Interface';
|
|
||||||
|
|
||||||
import mixins from 'vue-typed-mixins';
|
|
||||||
|
|
||||||
import { deviceSupportHelpers } from '@/mixins/deviceSupportHelpers';
|
|
||||||
import { VIEWS } from '@/constants';
|
|
||||||
import { mapStores } from 'pinia';
|
|
||||||
import { useUIStore } from '@/stores/ui';
|
|
||||||
import { useWorkflowsStore } from '@/stores/workflows';
|
|
||||||
import {
|
|
||||||
getMousePosition,
|
|
||||||
getRelativePosition,
|
|
||||||
HEADER_HEIGHT,
|
|
||||||
SIDEBAR_WIDTH,
|
|
||||||
SIDEBAR_WIDTH_EXPANDED,
|
|
||||||
} from '@/utils/nodeViewUtils';
|
|
||||||
|
|
||||||
export const mouseSelect = mixins(deviceSupportHelpers).extend({
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
selectActive: false,
|
|
||||||
selectBox: document.createElement('span'),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.createSelectBox();
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapStores(useUIStore, useWorkflowsStore),
|
|
||||||
isDemo(): boolean {
|
|
||||||
return this.$route.name === VIEWS.DEMO;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
createSelectBox() {
|
|
||||||
this.selectBox.id = 'select-box';
|
|
||||||
this.selectBox.style.margin = '0px auto';
|
|
||||||
this.selectBox.style.border = '2px dotted #FF0000';
|
|
||||||
// Positioned absolutely within #node-view. This is consistent with how nodes are positioned.
|
|
||||||
this.selectBox.style.position = 'absolute';
|
|
||||||
this.selectBox.style.zIndex = '100';
|
|
||||||
this.selectBox.style.visibility = 'hidden';
|
|
||||||
|
|
||||||
this.selectBox.addEventListener('mouseup', this.mouseUpMouseSelect);
|
|
||||||
|
|
||||||
const nodeViewEl = this.$el.querySelector('#node-view') as HTMLDivElement;
|
|
||||||
nodeViewEl.appendChild(this.selectBox);
|
|
||||||
},
|
|
||||||
isCtrlKeyPressed(e: MouseEvent | KeyboardEvent): boolean {
|
|
||||||
if (this.isTouchDevice === true) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (this.isMacOs) {
|
|
||||||
return e.metaKey;
|
|
||||||
}
|
|
||||||
return e.ctrlKey;
|
|
||||||
},
|
|
||||||
getMousePositionWithinNodeView(event: MouseEvent | TouchEvent): XYPosition {
|
|
||||||
const [x, y] = getMousePosition(event);
|
|
||||||
const sidebarOffset = this.isDemo
|
|
||||||
? 0
|
|
||||||
: this.uiStore.sidebarMenuCollapsed
|
|
||||||
? SIDEBAR_WIDTH
|
|
||||||
: SIDEBAR_WIDTH_EXPANDED;
|
|
||||||
const headerOffset = this.isDemo ? 0 : HEADER_HEIGHT;
|
|
||||||
// @ts-ignore
|
|
||||||
return getRelativePosition(
|
|
||||||
x - sidebarOffset,
|
|
||||||
y - headerOffset,
|
|
||||||
this.nodeViewScale,
|
|
||||||
this.uiStore.nodeViewOffsetPosition,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
showSelectBox(event: MouseEvent) {
|
|
||||||
const [x, y] = this.getMousePositionWithinNodeView(event);
|
|
||||||
this.selectBox = Object.assign(this.selectBox, { x, y });
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
this.selectBox.style.left = this.selectBox.x + 'px';
|
|
||||||
// @ts-ignore
|
|
||||||
this.selectBox.style.top = this.selectBox.y + 'px';
|
|
||||||
this.selectBox.style.visibility = 'visible';
|
|
||||||
|
|
||||||
this.selectActive = true;
|
|
||||||
},
|
|
||||||
updateSelectBox(event: MouseEvent) {
|
|
||||||
const selectionBox = this.getSelectionBox(event);
|
|
||||||
this.selectBox.style.left = selectionBox.x + 'px';
|
|
||||||
this.selectBox.style.top = selectionBox.y + 'px';
|
|
||||||
|
|
||||||
this.selectBox.style.width = selectionBox.width + 'px';
|
|
||||||
this.selectBox.style.height = selectionBox.height + 'px';
|
|
||||||
},
|
|
||||||
hideSelectBox() {
|
|
||||||
this.selectBox.style.visibility = 'hidden';
|
|
||||||
// @ts-ignore
|
|
||||||
this.selectBox.x = 0;
|
|
||||||
// @ts-ignore
|
|
||||||
this.selectBox.y = 0;
|
|
||||||
this.selectBox.style.left = '0px';
|
|
||||||
this.selectBox.style.top = '0px';
|
|
||||||
this.selectBox.style.width = '0px';
|
|
||||||
this.selectBox.style.height = '0px';
|
|
||||||
|
|
||||||
this.selectActive = false;
|
|
||||||
},
|
|
||||||
getSelectionBox(event: MouseEvent) {
|
|
||||||
const [x, y] = this.getMousePositionWithinNodeView(event);
|
|
||||||
return {
|
|
||||||
// @ts-ignore
|
|
||||||
x: Math.min(x, this.selectBox.x),
|
|
||||||
// @ts-ignore
|
|
||||||
y: Math.min(y, this.selectBox.y),
|
|
||||||
// @ts-ignore
|
|
||||||
width: Math.abs(x - this.selectBox.x),
|
|
||||||
// @ts-ignore
|
|
||||||
height: Math.abs(y - this.selectBox.y),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
getNodesInSelection(event: MouseEvent): INodeUi[] {
|
|
||||||
const returnNodes: INodeUi[] = [];
|
|
||||||
const selectionBox = this.getSelectionBox(event);
|
|
||||||
|
|
||||||
// Go through all nodes and check if they are selected
|
|
||||||
this.workflowsStore.allNodes.forEach((node: INodeUi) => {
|
|
||||||
// TODO: Currently always uses the top left corner for checking. Should probably use the center instead
|
|
||||||
if (
|
|
||||||
node.position[0] < selectionBox.x ||
|
|
||||||
node.position[0] > selectionBox.x + selectionBox.width
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
node.position[1] < selectionBox.y ||
|
|
||||||
node.position[1] > selectionBox.y + selectionBox.height
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
returnNodes.push(node);
|
|
||||||
});
|
|
||||||
|
|
||||||
return returnNodes;
|
|
||||||
},
|
|
||||||
mouseDownMouseSelect(e: MouseEvent, moveButtonPressed: boolean) {
|
|
||||||
if (this.isCtrlKeyPressed(e) === true || moveButtonPressed) {
|
|
||||||
// We only care about it when the ctrl key is not pressed at the same time.
|
|
||||||
// So we exit when it is pressed.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.uiStore.isActionActive('dragActive')) {
|
|
||||||
// If a node does currently get dragged we do not activate the selection
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.showSelectBox(e);
|
|
||||||
|
|
||||||
// @ts-ignore // Leave like this. Do not add a anonymous function because then remove would not work anymore
|
|
||||||
this.$el.addEventListener('mousemove', this.mouseMoveSelect);
|
|
||||||
},
|
|
||||||
mouseUpMouseSelect(e: MouseEvent) {
|
|
||||||
if (this.selectActive === false) {
|
|
||||||
if (this.isTouchDevice === true) {
|
|
||||||
// @ts-ignore
|
|
||||||
if (e.target && e.target.id.includes('node-view')) {
|
|
||||||
// Deselect all nodes
|
|
||||||
this.deselectAllNodes();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If it is not active return directly.
|
|
||||||
// Else normal node dragging will not work.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
this.$el.removeEventListener('mousemove', this.mouseMoveSelect);
|
|
||||||
|
|
||||||
// Deselect all nodes
|
|
||||||
this.deselectAllNodes();
|
|
||||||
|
|
||||||
// Select the nodes which are in the selection box
|
|
||||||
const selectedNodes = this.getNodesInSelection(e);
|
|
||||||
selectedNodes.forEach((node) => {
|
|
||||||
this.nodeSelected(node);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (selectedNodes.length === 1) {
|
|
||||||
this.uiStore.lastSelectedNode = selectedNodes[0].name;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.hideSelectBox();
|
|
||||||
},
|
|
||||||
mouseMoveSelect(e: MouseEvent) {
|
|
||||||
if (e.buttons === 0) {
|
|
||||||
// Mouse button is not pressed anymore so stop selection mode
|
|
||||||
// Happens normally when mouse leave the view pressed and then
|
|
||||||
// comes back unpressed.
|
|
||||||
this.mouseUpMouseSelect(e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updateSelectBox(e);
|
|
||||||
},
|
|
||||||
nodeDeselected(node: INodeUi) {
|
|
||||||
this.uiStore.removeNodeFromSelection(node);
|
|
||||||
// @ts-ignore
|
|
||||||
this.instance.removeFromDragSelection(this.instance.getManagedElement(node?.id));
|
|
||||||
},
|
|
||||||
nodeSelected(node: INodeUi) {
|
|
||||||
this.uiStore.addSelectedNode(node);
|
|
||||||
// @ts-ignore
|
|
||||||
this.instance.addToDragSelection(this.instance.getManagedElement(node?.id));
|
|
||||||
},
|
|
||||||
deselectAllNodes() {
|
|
||||||
// @ts-ignore
|
|
||||||
this.instance.clearDragSelection();
|
|
||||||
this.uiStore.resetSelectedNodes();
|
|
||||||
this.uiStore.lastSelectedNode = null;
|
|
||||||
this.uiStore.lastSelectedNodeOutputIndex = null;
|
|
||||||
// @ts-ignore
|
|
||||||
this.lastSelectedConnection = null;
|
|
||||||
// @ts-ignore
|
|
||||||
this.newNodeInsertPosition = null;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -18,7 +18,7 @@ import { newInstance } from '@jsplumb/browser-ui';
|
||||||
import { N8nPlusEndpointHandler } from '@/plugins/endpoints/N8nPlusEndpointType';
|
import { N8nPlusEndpointHandler } from '@/plugins/endpoints/N8nPlusEndpointType';
|
||||||
import * as N8nPlusEndpointRenderer from '@/plugins/endpoints/N8nPlusEndpointRenderer';
|
import * as N8nPlusEndpointRenderer from '@/plugins/endpoints/N8nPlusEndpointRenderer';
|
||||||
import { N8nConnector } from '@/plugins/connectors/N8nCustomConnector';
|
import { N8nConnector } from '@/plugins/connectors/N8nCustomConnector';
|
||||||
import { EndpointFactory, Connectors } from '@jsplumb/core';
|
import { EndpointFactory, Connectors, Connection } from '@jsplumb/core';
|
||||||
import { MoveNodeCommand } from '@/models/history';
|
import { MoveNodeCommand } from '@/models/history';
|
||||||
import {
|
import {
|
||||||
DEFAULT_PLACEHOLDER_TRIGGER_BUTTON,
|
DEFAULT_PLACEHOLDER_TRIGGER_BUTTON,
|
||||||
|
@ -42,6 +42,8 @@ export const useCanvasStore = defineStore('canvas', () => {
|
||||||
|
|
||||||
const jsPlumbInstanceRef = ref<BrowserJsPlumbInstance>();
|
const jsPlumbInstanceRef = ref<BrowserJsPlumbInstance>();
|
||||||
const isDragging = ref<boolean>(false);
|
const isDragging = ref<boolean>(false);
|
||||||
|
const lastSelectedConnection = ref<Connection | null>(null);
|
||||||
|
const newNodeInsertPosition = ref<XYPosition | null>(null);
|
||||||
|
|
||||||
const nodes = computed<INodeUi[]>(() => workflowStore.allNodes);
|
const nodes = computed<INodeUi[]>(() => workflowStore.allNodes);
|
||||||
const triggerNodes = computed<INodeUi[]>(() =>
|
const triggerNodes = computed<INodeUi[]>(() =>
|
||||||
|
@ -256,6 +258,9 @@ export const useCanvasStore = defineStore('canvas', () => {
|
||||||
isDemo,
|
isDemo,
|
||||||
nodeViewScale,
|
nodeViewScale,
|
||||||
canvasAddButtonPosition,
|
canvasAddButtonPosition,
|
||||||
|
lastSelectedConnection,
|
||||||
|
newNodeInsertPosition,
|
||||||
|
jsPlumbInstance,
|
||||||
setRecenteredCanvasAddButtonPosition,
|
setRecenteredCanvasAddButtonPosition,
|
||||||
getNodesWithPlaceholderNode,
|
getNodesWithPlaceholderNode,
|
||||||
setZoomLevel,
|
setZoomLevel,
|
||||||
|
@ -265,6 +270,5 @@ export const useCanvasStore = defineStore('canvas', () => {
|
||||||
zoomToFit,
|
zoomToFit,
|
||||||
wheelScroll,
|
wheelScroll,
|
||||||
initInstance,
|
initInstance,
|
||||||
jsPlumbInstance,
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
@ -203,10 +203,10 @@ import {
|
||||||
import { copyPaste } from '@/mixins/copyPaste';
|
import { copyPaste } from '@/mixins/copyPaste';
|
||||||
import { externalHooks } from '@/mixins/externalHooks';
|
import { externalHooks } from '@/mixins/externalHooks';
|
||||||
import { genericHelpers } from '@/mixins/genericHelpers';
|
import { genericHelpers } from '@/mixins/genericHelpers';
|
||||||
import { mouseSelect } from '@/mixins/mouseSelect';
|
|
||||||
import { moveNodeWorkflow } from '@/mixins/moveNodeWorkflow';
|
import { moveNodeWorkflow } from '@/mixins/moveNodeWorkflow';
|
||||||
import { restApi } from '@/mixins/restApi';
|
import { restApi } from '@/mixins/restApi';
|
||||||
import useGlobalLinkActions from '@/composables/useGlobalLinkActions';
|
import useGlobalLinkActions from '@/composables/useGlobalLinkActions';
|
||||||
|
import useCanvasMouseSelect from '@/composables/useCanvasMouseSelect';
|
||||||
import { showMessage } from '@/mixins/showMessage';
|
import { showMessage } from '@/mixins/showMessage';
|
||||||
import { titleChange } from '@/mixins/titleChange';
|
import { titleChange } from '@/mixins/titleChange';
|
||||||
|
|
||||||
|
@ -321,7 +321,6 @@ export default mixins(
|
||||||
copyPaste,
|
copyPaste,
|
||||||
externalHooks,
|
externalHooks,
|
||||||
genericHelpers,
|
genericHelpers,
|
||||||
mouseSelect,
|
|
||||||
moveNodeWorkflow,
|
moveNodeWorkflow,
|
||||||
restApi,
|
restApi,
|
||||||
showMessage,
|
showMessage,
|
||||||
|
@ -342,10 +341,9 @@ export default mixins(
|
||||||
CanvasControls,
|
CanvasControls,
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
const { registerCustomAction, unregisterCustomAction } = useGlobalLinkActions();
|
|
||||||
return {
|
return {
|
||||||
registerCustomAction,
|
...useCanvasMouseSelect(),
|
||||||
unregisterCustomAction,
|
...useGlobalLinkActions(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
errorCaptured: (err, vm, info) => {
|
errorCaptured: (err, vm, info) => {
|
||||||
|
@ -592,14 +590,12 @@ export default mixins(
|
||||||
GRID_SIZE: NodeViewUtils.GRID_SIZE,
|
GRID_SIZE: NodeViewUtils.GRID_SIZE,
|
||||||
STICKY_NODE_TYPE,
|
STICKY_NODE_TYPE,
|
||||||
createNodeActive: false,
|
createNodeActive: false,
|
||||||
lastSelectedConnection: null as null | Connection,
|
|
||||||
lastClickPosition: [450, 450] as XYPosition,
|
lastClickPosition: [450, 450] as XYPosition,
|
||||||
ctrlKeyPressed: false,
|
ctrlKeyPressed: false,
|
||||||
moveCanvasKeyPressed: false,
|
moveCanvasKeyPressed: false,
|
||||||
stopExecutionInProgress: false,
|
stopExecutionInProgress: false,
|
||||||
blankRedirect: false,
|
blankRedirect: false,
|
||||||
credentialsUpdated: false,
|
credentialsUpdated: false,
|
||||||
newNodeInsertPosition: null as XYPosition | null,
|
|
||||||
pullConnActiveNodeName: null as string | null,
|
pullConnActiveNodeName: null as string | null,
|
||||||
pullConnActive: false,
|
pullConnActive: false,
|
||||||
dropPrevented: false,
|
dropPrevented: false,
|
||||||
|
@ -1682,7 +1678,8 @@ export default mixins(
|
||||||
// If adding more than one node, offset the X position
|
// If adding more than one node, offset the X position
|
||||||
mousePosition[0] -
|
mousePosition[0] -
|
||||||
NodeViewUtils.NODE_SIZE / 2 +
|
NodeViewUtils.NODE_SIZE / 2 +
|
||||||
NodeViewUtils.NODE_SIZE * (index * 2 + NodeViewUtils.GRID_SIZE),
|
NodeViewUtils.NODE_SIZE * index * 2 +
|
||||||
|
NodeViewUtils.GRID_SIZE,
|
||||||
mousePosition[1] - NodeViewUtils.NODE_SIZE / 2,
|
mousePosition[1] - NodeViewUtils.NODE_SIZE / 2,
|
||||||
] as XYPosition,
|
] as XYPosition,
|
||||||
dragAndDrop: true,
|
dragAndDrop: true,
|
||||||
|
@ -1711,8 +1708,8 @@ export default mixins(
|
||||||
this.nodeSelected(node);
|
this.nodeSelected(node);
|
||||||
this.uiStore.lastSelectedNode = node.name;
|
this.uiStore.lastSelectedNode = node.name;
|
||||||
this.uiStore.lastSelectedNodeOutputIndex = null;
|
this.uiStore.lastSelectedNodeOutputIndex = null;
|
||||||
this.lastSelectedConnection = null;
|
this.canvasStore.lastSelectedConnection = null;
|
||||||
this.newNodeInsertPosition = null;
|
this.canvasStore.newNodeInsertPosition = null;
|
||||||
|
|
||||||
if (setActive) {
|
if (setActive) {
|
||||||
this.ndvStore.activeNodeName = node.name;
|
this.ndvStore.activeNodeName = node.name;
|
||||||
|
@ -1854,7 +1851,7 @@ export default mixins(
|
||||||
options.position,
|
options.position,
|
||||||
);
|
);
|
||||||
} else if (lastSelectedNode) {
|
} else if (lastSelectedNode) {
|
||||||
const lastSelectedConnection = this.lastSelectedConnection;
|
const lastSelectedConnection = this.canvasStore.lastSelectedConnection;
|
||||||
if (lastSelectedConnection) {
|
if (lastSelectedConnection) {
|
||||||
// set when injecting into a connection
|
// set when injecting into a connection
|
||||||
const [diffX] = NodeViewUtils.getConnectorLengths(lastSelectedConnection);
|
const [diffX] = NodeViewUtils.getConnectorLengths(lastSelectedConnection);
|
||||||
|
@ -1868,12 +1865,12 @@ export default mixins(
|
||||||
}
|
}
|
||||||
|
|
||||||
// set when pulling connections
|
// set when pulling connections
|
||||||
if (this.newNodeInsertPosition) {
|
if (this.canvasStore.newNodeInsertPosition) {
|
||||||
newNodeData.position = NodeViewUtils.getNewNodePosition(this.nodes, [
|
newNodeData.position = NodeViewUtils.getNewNodePosition(this.nodes, [
|
||||||
this.newNodeInsertPosition[0] + NodeViewUtils.GRID_SIZE,
|
this.canvasStore.newNodeInsertPosition[0] + NodeViewUtils.GRID_SIZE,
|
||||||
this.newNodeInsertPosition[1] - NodeViewUtils.NODE_SIZE / 2,
|
this.canvasStore.newNodeInsertPosition[1] - NodeViewUtils.NODE_SIZE / 2,
|
||||||
]);
|
]);
|
||||||
this.newNodeInsertPosition = null;
|
this.canvasStore.newNodeInsertPosition = null;
|
||||||
} else {
|
} else {
|
||||||
let yOffset = 0;
|
let yOffset = 0;
|
||||||
|
|
||||||
|
@ -2035,7 +2032,7 @@ export default mixins(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const lastSelectedConnection = this.lastSelectedConnection;
|
const lastSelectedConnection = this.canvasStore.lastSelectedConnection;
|
||||||
const lastSelectedNode = this.lastSelectedNode;
|
const lastSelectedNode = this.lastSelectedNode;
|
||||||
const lastSelectedNodeOutputIndex = this.uiStore.lastSelectedNodeOutputIndex;
|
const lastSelectedNodeOutputIndex = this.uiStore.lastSelectedNodeOutputIndex;
|
||||||
|
|
||||||
|
@ -2087,10 +2084,10 @@ export default mixins(
|
||||||
|
|
||||||
this.uiStore.lastSelectedNode = sourceNode.name;
|
this.uiStore.lastSelectedNode = sourceNode.name;
|
||||||
this.uiStore.lastSelectedNodeOutputIndex = info.index;
|
this.uiStore.lastSelectedNodeOutputIndex = info.index;
|
||||||
this.newNodeInsertPosition = null;
|
this.canvasStore.newNodeInsertPosition = null;
|
||||||
|
|
||||||
if (info.connection) {
|
if (info.connection) {
|
||||||
this.lastSelectedConnection = info.connection;
|
this.canvasStore.lastSelectedConnection = info.connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.onToggleNodeCreator({
|
this.onToggleNodeCreator({
|
||||||
|
@ -2363,7 +2360,7 @@ export default mixins(
|
||||||
try {
|
try {
|
||||||
this.pullConnActiveNodeName = null;
|
this.pullConnActiveNodeName = null;
|
||||||
this.pullConnActive = true;
|
this.pullConnActive = true;
|
||||||
this.newNodeInsertPosition = null;
|
this.canvasStore.newNodeInsertPosition = null;
|
||||||
NodeViewUtils.resetConnection(connection);
|
NodeViewUtils.resetConnection(connection);
|
||||||
|
|
||||||
const nodes = [...document.querySelectorAll('.node-wrapper')];
|
const nodes = [...document.querySelectorAll('.node-wrapper')];
|
||||||
|
@ -2414,7 +2411,7 @@ export default mixins(
|
||||||
|
|
||||||
const onMouseUp = (e: MouseEvent | TouchEvent) => {
|
const onMouseUp = (e: MouseEvent | TouchEvent) => {
|
||||||
this.pullConnActive = false;
|
this.pullConnActive = false;
|
||||||
this.newNodeInsertPosition = this.getMousePositionWithinNodeView(e);
|
this.canvasStore.newNodeInsertPosition = this.getMousePositionWithinNodeView(e);
|
||||||
NodeViewUtils.resetConnectionAfterPull(connection);
|
NodeViewUtils.resetConnectionAfterPull(connection);
|
||||||
window.removeEventListener('mousemove', onMouseMove);
|
window.removeEventListener('mousemove', onMouseMove);
|
||||||
window.removeEventListener('mouseup', onMouseUp);
|
window.removeEventListener('mouseup', onMouseUp);
|
||||||
|
|
Loading…
Reference in a new issue