Skip to content

Commit 4df7477

Browse files
committed
⚡ Improve support for touch-devices n8n-io#1070
1 parent ada485e commit 4df7477

File tree

8 files changed

+111
-58
lines changed

8 files changed

+111
-58
lines changed

packages/editor-ui/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@
2525
"test:unit": "vue-cli-service test:unit"
2626
},
2727
"dependencies": {
28-
"uuid": "^8.1.0"
28+
"uuid": "^8.1.0",
29+
"vue2-touch-events": "^2.3.2"
2930
},
3031
"devDependencies": {
3132
"@beyonk/google-fonts-webpack-plugin": "^1.2.3",

packages/editor-ui/src/components/Node.vue

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<template>
22
<div class="node-wrapper" :style="nodePosition">
3-
<div class="node-default" :ref="data.name" :style="nodeStyle" :class="nodeClass" @dblclick="setNodeActive" @click.left="mouseLeftClick">
3+
<div class="node-default" :ref="data.name" :style="nodeStyle" :class="nodeClass" @dblclick="setNodeActive" @click.left="mouseLeftClick" v-touch:end="mouseLeftClick">
44
<div v-if="hasIssues" class="node-info-icon node-issues">
55
<el-tooltip placement="top" effect="light">
66
<div slot="content" v-html="nodeIssues"></div>
@@ -13,19 +13,19 @@
1313
<font-awesome-icon icon="sync-alt" spin />
1414
</div>
1515
<div class="node-options" v-if="!isReadOnly">
16-
<div @click.stop.left="deleteNode" class="option" title="Delete Node" >
16+
<div v-touch:tap="deleteNode" class="option" title="Delete Node" >
1717
<font-awesome-icon icon="trash" />
1818
</div>
19-
<div @click.stop.left="disableNode" class="option" title="Activate/Deactivate Node" >
19+
<div v-touch:tap="disableNode" v-touch-options="{disableClick: true}" class="option" title="Activate/Deactivate Node" >
2020
<font-awesome-icon :icon="nodeDisabledIcon" />
2121
</div>
22-
<div @click.stop.left="duplicateNode" class="option" title="Duplicate Node" >
22+
<div v-touch:tap="duplicateNode" class="option" title="Duplicate Node" >
2323
<font-awesome-icon icon="clone" />
2424
</div>
25-
<div @click.stop.left="setNodeActive" class="option touch" title="Edit Node" v-if="!isReadOnly">
25+
<div v-touch:tap="setNodeActive" class="option touch" title="Edit Node" v-if="!isReadOnly">
2626
<font-awesome-icon class="execute-icon" icon="cog" />
2727
</div>
28-
<div @click.stop.left="executeNode" class="option" title="Execute Node" v-if="!isReadOnly && !workflowRunning">
28+
<div v-touch:tap="executeNode" class="option" title="Execute Node" v-if="!isReadOnly && !workflowRunning">
2929
<font-awesome-icon class="execute-icon" icon="play-circle" />
3030
</div>
3131
</div>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import Vue from 'vue';
2+
3+
export const deviceSupportHelpers = Vue.extend({
4+
data() {
5+
return {
6+
isTouchDevice: 'ontouchstart' in window || navigator.msMaxTouchPoints,
7+
isMacOs: /(ipad|iphone|ipod|mac)/i.test(navigator.platform),
8+
};
9+
},
10+
computed: {
11+
// TODO: Check if used anywhere
12+
controlKeyCode(): string {
13+
if (this.isMacOs) {
14+
return 'Meta';
15+
}
16+
return 'Control';
17+
},
18+
},
19+
methods: {
20+
isCtrlKeyPressed(e: MouseEvent | KeyboardEvent): boolean {
21+
if (this.isTouchDevice === true) {
22+
return true;
23+
}
24+
if (this.isMacOs) {
25+
return e.metaKey;
26+
}
27+
return e.ctrlKey;
28+
},
29+
},
30+
});

packages/editor-ui/src/components/mixins/mouseSelect.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,19 @@ import { INodeUi } from '@/Interface';
22

33
import mixins from 'vue-typed-mixins';
44

5+
import { deviceSupportHelpers } from '@/components/mixins/deviceSupportHelpers';
56
import { nodeIndex } from '@/components/mixins/nodeIndex';
67

7-
export const mouseSelect = mixins(nodeIndex).extend({
8+
export const mouseSelect = mixins(
9+
deviceSupportHelpers,
10+
nodeIndex,
11+
).extend({
812
data () {
913
return {
1014
selectActive: false,
1115
selectBox: document.createElement('span'),
1216
};
1317
},
14-
computed: {
15-
isMacOs (): boolean {
16-
return /(ipad|iphone|ipod|mac)/i.test(navigator.platform);
17-
},
18-
},
1918
mounted () {
2019
this.createSelectBox();
2120
},
@@ -34,6 +33,9 @@ export const mouseSelect = mixins(nodeIndex).extend({
3433
this.$el.appendChild(this.selectBox);
3534
},
3635
isCtrlKeyPressed (e: MouseEvent | KeyboardEvent): boolean {
36+
if (this.isTouchDevice === true) {
37+
return true;
38+
}
3739
if (this.isMacOs) {
3840
return e.metaKey;
3941
}
@@ -125,6 +127,13 @@ export const mouseSelect = mixins(nodeIndex).extend({
125127
},
126128
mouseUpMouseSelect (e: MouseEvent) {
127129
if (this.selectActive === false) {
130+
if (this.isTouchDevice === true) {
131+
// @ts-ignore
132+
if (e.target && e.target.id.includes('node-view')) {
133+
// Deselect all nodes
134+
this.deselectAllNodes();
135+
}
136+
}
128137
// If it is not active return direcly.
129138
// Else normal node dragging will not work.
130139
return;

packages/editor-ui/src/components/mixins/moveNodeWorkflow.ts

Lines changed: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,42 @@
11
import mixins from 'vue-typed-mixins';
22

3+
import { deviceSupportHelpers } from '@/components/mixins/deviceSupportHelpers';
34
import { nodeIndex } from '@/components/mixins/nodeIndex';
45

5-
export const moveNodeWorkflow = mixins(nodeIndex).extend({
6+
export const moveNodeWorkflow = mixins(
7+
deviceSupportHelpers,
8+
nodeIndex,
9+
).extend({
610
data () {
711
return {
812
moveLastPosition: [0, 0],
913
};
1014
},
11-
computed: {
12-
controlKeyCode (): string {
13-
if (this.isMacOs) {
14-
return 'Meta';
15-
}
16-
return 'Control';
17-
},
18-
isMacOs (): boolean {
19-
return /(ipad|iphone|ipod|mac)/i.test(navigator.platform);
20-
},
21-
},
15+
2216
methods: {
23-
isCtrlKeyPressed (e: MouseEvent | KeyboardEvent): boolean {
24-
if (this.isMacOs) {
25-
return e.metaKey;
26-
}
27-
return e.ctrlKey;
17+
getMousePosition(e: MouseEvent | TouchEvent) {
18+
// @ts-ignore
19+
const x = e.pageX !== undefined ? e.pageX : (e.touches && e.touches[0] && e.touches[0].pageX ? e.touches[0].pageX : 0);
20+
// @ts-ignore
21+
const y = e.pageY !== undefined ? e.pageY : (e.touches && e.touches[0] && e.touches[0].pageY ? e.touches[0].pageY : 0);
22+
23+
return {
24+
x,
25+
y,
26+
};
2827
},
2928
moveWorkflow (e: MouseEvent) {
3029
const offsetPosition = this.$store.getters.getNodeViewOffsetPosition;
3130

32-
const nodeViewOffsetPositionX = offsetPosition[0] + (e.pageX - this.moveLastPosition[0]);
33-
const nodeViewOffsetPositionY = offsetPosition[1] + (e.pageY - this.moveLastPosition[1]);
31+
const position = this.getMousePosition(e);
32+
33+
const nodeViewOffsetPositionX = offsetPosition[0] + (position.x - this.moveLastPosition[0]);
34+
const nodeViewOffsetPositionY = offsetPosition[1] + (position.y - this.moveLastPosition[1]);
3435
this.$store.commit('setNodeViewOffsetPosition', [nodeViewOffsetPositionX, nodeViewOffsetPositionY]);
3536

3637
// Update the last position
37-
this.moveLastPosition[0] = e.pageX;
38-
this.moveLastPosition[1] = e.pageY;
38+
this.moveLastPosition[0] = position.x;
39+
this.moveLastPosition[1] = position.y;
3940
},
4041
mouseDownMoveWorkflow (e: MouseEvent) {
4142
if (this.isCtrlKeyPressed(e) === false) {
@@ -51,8 +52,10 @@ export const moveNodeWorkflow = mixins(nodeIndex).extend({
5152

5253
this.$store.commit('setNodeViewMoveInProgress', true);
5354

54-
this.moveLastPosition[0] = e.pageX;
55-
this.moveLastPosition[1] = e.pageY;
55+
const position = this.getMousePosition(e);
56+
57+
this.moveLastPosition[0] = position.x;
58+
this.moveLastPosition[1] = position.y;
5659

5760
// @ts-ignore
5861
this.$el.addEventListener('mousemove', this.mouseMoveNodeWorkflow);
@@ -72,6 +75,15 @@ export const moveNodeWorkflow = mixins(nodeIndex).extend({
7275
// Nothing else to do. Simply leave the node view at the current offset
7376
},
7477
mouseMoveNodeWorkflow (e: MouseEvent) {
78+
// @ts-ignore
79+
if (e.target && !e.target.id.includes('node-view')) {
80+
return;
81+
}
82+
83+
if (this.$store.getters.isActionActive('dragActive')) {
84+
return;
85+
}
86+
7587
if (e.buttons === 0) {
7688
// Mouse button is not pressed anymore so stop selection mode
7789
// Happens normally when mouse leave the view pressed and then

packages/editor-ui/src/components/mixins/nodeBase.ts

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,20 @@ import { IConnectionsUi, IEndpointOptions, INodeUi, XYPositon } from '@/Interfac
22

33
import mixins from 'vue-typed-mixins';
44

5+
import { deviceSupportHelpers } from '@/components/mixins/deviceSupportHelpers';
56
import { nodeIndex } from '@/components/mixins/nodeIndex';
67
import { NODE_NAME_PREFIX } from '@/constants';
78

8-
export const nodeBase = mixins(nodeIndex).extend({
9+
export const nodeBase = mixins(
10+
deviceSupportHelpers,
11+
nodeIndex,
12+
).extend({
913
mounted () {
1014
// Initialize the node
1115
if (this.data !== null) {
1216
this.__addNode(this.data);
1317
}
1418
},
15-
data () {
16-
return {
17-
};
18-
},
1919
computed: {
2020
data (): INodeUi {
2121
return this.$store.getters.nodeByName(this.name);
@@ -26,9 +26,6 @@ export const nodeBase = mixins(nodeIndex).extend({
2626
}
2727
return false;
2828
},
29-
isMacOs (): boolean {
30-
return /(ipad|iphone|ipod|mac)/i.test(navigator.platform);
31-
},
3229
nodeName (): string {
3330
return NODE_NAME_PREFIX + this.nodeIndex;
3431
},
@@ -337,13 +334,6 @@ export const nodeBase = mixins(nodeIndex).extend({
337334

338335
},
339336

340-
isCtrlKeyPressed (e: MouseEvent | KeyboardEvent): boolean {
341-
if (this.isMacOs) {
342-
return e.metaKey;
343-
}
344-
return e.ctrlKey;
345-
},
346-
347337
mouseLeftClick (e: MouseEvent) {
348338
if (this.$store.getters.isActionActive('dragActive')) {
349339
this.$store.commit('removeActiveAction', 'dragActive');

packages/editor-ui/src/main.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import Vue from 'vue';
55
import 'prismjs';
66
import 'prismjs/themes/prism.css';
77
import 'vue-prism-editor/dist/VuePrismEditor.css';
8+
import Vue2TouchEvents from 'vue2-touch-events';
89

910
import * as ElementUI from 'element-ui';
1011
// @ts-ignore
@@ -91,6 +92,9 @@ import {
9192
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
9293

9394
import { store } from './store';
95+
96+
Vue.use(Vue2TouchEvents);
97+
9498
Vue.use(ElementUI, { locale });
9599

96100
library.add(faAngleDoubleLeft);

packages/editor-ui/src/views/NodeView.vue

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@
33
<div
44
class="node-view-wrapper"
55
:class="workflowClasses"
6+
@touchstart="mouseDown"
7+
@touchend="mouseUp"
8+
@touchmove="mouseMoveNodeWorkflow"
69
@mousedown="mouseDown"
10+
v-touch:tap="mouseDown"
711
@mouseup="mouseUp"
812
@wheel="wheelScroll"
913
>
10-
<div class="node-view-background" :style="backgroundStyle"></div>
14+
<div id="node-view-background" class="node-view-background" :style="backgroundStyle"></div>
1115
<div id="node-view" class="node-view" :style="workflowStyle">
1216
<node
1317
v-for="nodeData in nodes"
@@ -336,14 +340,17 @@ export default mixins(
336340
337341
await this.addNodes(data.nodes, data.connections);
338342
},
339-
mouseDown (e: MouseEvent) {
343+
mouseDown (e: MouseEvent | TouchEvent) {
344+
console.log('mouseDown');
345+
340346
// Save the location of the mouse click
347+
const position = this.getMousePosition(e);
341348
const offsetPosition = this.$store.getters.getNodeViewOffsetPosition;
342-
this.lastClickPosition[0] = e.pageX - offsetPosition[0];
343-
this.lastClickPosition[1] = e.pageY - offsetPosition[1];
349+
this.lastClickPosition[0] = position.x - offsetPosition[0];
350+
this.lastClickPosition[1] = position.y - offsetPosition[1];
344351
345-
this.mouseDownMouseSelect(e);
346-
this.mouseDownMoveWorkflow(e);
352+
this.mouseDownMouseSelect(e as MouseEvent);
353+
this.mouseDownMoveWorkflow(e as MouseEvent);
347354
348355
// Hide the node-creator
349356
this.createNodeActive = false;
@@ -1680,7 +1687,7 @@ export default mixins(
16801687
const createNodes: INode[] = [];
16811688
16821689
await this.loadNodesProperties(data.nodes.map(node => node.type));
1683-
1690+
16841691
data.nodes.forEach(node => {
16851692
if (nodeTypesCount[node.type] !== undefined) {
16861693
if (nodeTypesCount[node.type].exist >= nodeTypesCount[node.type].max) {

0 commit comments

Comments
 (0)