feat(editor): Migrate deviceSupportHelpers mixin to useDeviceSupport composable (no-changelog) (#8289)

This commit is contained in:
Alex Grozav 2024-01-10 16:42:01 +02:00 committed by GitHub
parent 8a7c629ea1
commit d32e6a60da
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 126 additions and 65 deletions

View file

@ -0,0 +1,80 @@
import { useDeviceSupport } from '@/composables/useDeviceSupport';
describe('useDeviceSupport()', () => {
beforeEach(() => {
global.window = Object.create(window);
global.navigator = { userAgent: 'test-agent', maxTouchPoints: 0 };
});
describe('isTouchDevice', () => {
it('should be true if ontouchstart is in window', () => {
Object.defineProperty(window, 'ontouchstart', {});
const { isTouchDevice } = useDeviceSupport();
expect(isTouchDevice).toEqual(true);
});
it('should be true if navigator.maxTouchPoints > 0', () => {
Object.defineProperty(navigator, 'maxTouchPoints', { value: 1 });
const { isTouchDevice } = useDeviceSupport();
expect(isTouchDevice).toEqual(true);
});
it('should be false if no touch support', () => {
delete window.ontouchstart;
Object.defineProperty(navigator, 'maxTouchPoints', { value: 0 });
const { isTouchDevice } = useDeviceSupport();
expect(isTouchDevice).toEqual(false);
});
});
describe('isMacOs', () => {
it('should be true for macOS user agent', () => {
Object.defineProperty(navigator, 'userAgent', { value: 'macintosh' });
const { isMacOs } = useDeviceSupport();
expect(isMacOs).toEqual(true);
});
it('should be false for non-macOS user agent', () => {
Object.defineProperty(navigator, 'userAgent', { value: 'windows' });
const { isMacOs } = useDeviceSupport();
expect(isMacOs).toEqual(false);
});
});
describe('controlKeyCode', () => {
it('should return Meta on macOS', () => {
Object.defineProperty(navigator, 'userAgent', { value: 'macintosh' });
const { controlKeyCode } = useDeviceSupport();
expect(controlKeyCode).toEqual('Meta');
});
it('should return Control on non-macOS', () => {
Object.defineProperty(navigator, 'userAgent', { value: 'windows' });
const { controlKeyCode } = useDeviceSupport();
expect(controlKeyCode).toEqual('Control');
});
});
describe('isCtrlKeyPressed()', () => {
it('should return true for metaKey press on macOS', () => {
Object.defineProperty(navigator, 'userAgent', { value: 'macintosh' });
const { isCtrlKeyPressed } = useDeviceSupport();
const event = new KeyboardEvent('keydown', { metaKey: true });
expect(isCtrlKeyPressed(event)).toEqual(true);
});
it('should return true for ctrlKey press on non-macOS', () => {
Object.defineProperty(navigator, 'userAgent', { value: 'windows' });
const { isCtrlKeyPressed } = useDeviceSupport();
const event = new KeyboardEvent('keydown', { ctrlKey: true });
expect(isCtrlKeyPressed(event)).toEqual(true);
});
it('should return true for touch device on MouseEvent', () => {
Object.defineProperty(window, 'ontouchstart', { value: {} });
const { isCtrlKeyPressed } = useDeviceSupport();
const mockEvent = new MouseEvent('click');
expect(isCtrlKeyPressed(mockEvent)).toEqual(true);
});
});
});

View file

@ -1,14 +1,7 @@
import { ref } from 'vue'; import { ref } from 'vue';
interface DeviceSupportHelpers { export function useDeviceSupport() {
isTouchDevice: boolean; const isTouchDevice = ref(window.hasOwnProperty('ontouchstart') || navigator.maxTouchPoints > 0);
isMacOs: boolean;
controlKeyCode: string;
isCtrlKeyPressed: (e: MouseEvent | KeyboardEvent) => boolean;
}
export function useDeviceSupport(): DeviceSupportHelpers {
const isTouchDevice = ref('ontouchstart' in window || navigator.maxTouchPoints > 0);
const userAgent = ref(navigator.userAgent.toLowerCase()); const userAgent = ref(navigator.userAgent.toLowerCase());
const isMacOs = ref( const isMacOs = ref(
userAgent.value.includes('macintosh') || userAgent.value.includes('macintosh') ||

View file

@ -62,13 +62,15 @@ import { onBeforeMount, onBeforeUnmount } from 'vue';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useCanvasStore } from '@/stores/canvas.store'; import { useCanvasStore } from '@/stores/canvas.store';
import KeyboardShortcutTooltip from '@/components/KeyboardShortcutTooltip.vue'; import KeyboardShortcutTooltip from '@/components/KeyboardShortcutTooltip.vue';
import { useDeviceSupport } from 'n8n-design-system';
const canvasStore = useCanvasStore(); const canvasStore = useCanvasStore();
const { zoomToFit, zoomIn, zoomOut, resetZoom } = canvasStore; const { zoomToFit, zoomIn, zoomOut, resetZoom } = canvasStore;
const { nodeViewScale, isDemo } = storeToRefs(canvasStore); const { nodeViewScale, isDemo } = storeToRefs(canvasStore);
const deviceSupport = useDeviceSupport();
const keyDown = (e: KeyboardEvent) => { const keyDown = (e: KeyboardEvent) => {
const isCtrlKeyPressed = e.metaKey || e.ctrlKey; const isCtrlKeyPressed = deviceSupport.isCtrlKeyPressed(e);
if ((e.key === '=' || e.key === '+') && !isCtrlKeyPressed) { if ((e.key === '=' || e.key === '+') && !isCtrlKeyPressed) {
zoomIn(); zoomIn();
} else if ((e.key === '_' || e.key === '-') && !isCtrlKeyPressed) { } else if ((e.key === '_' || e.key === '-') && !isCtrlKeyPressed) {

View file

@ -14,7 +14,7 @@
:class="{ :class="{
'node-default': true, 'node-default': true,
'touch-active': isTouchActive, 'touch-active': isTouchActive,
'is-touch-device': isTouchDevice, 'is-touch-device': deviceSupport.isTouchDevice,
'menu-open': isContextMenuOpen, 'menu-open': isContextMenuOpen,
'disable-pointer-events': disablePointerEvents, 'disable-pointer-events': disablePointerEvents,
}" }"
@ -187,6 +187,7 @@ import { type ContextMenuTarget, useContextMenu } from '@/composables/useContext
import { useNodeHelpers } from '@/composables/useNodeHelpers'; import { useNodeHelpers } from '@/composables/useNodeHelpers';
import { useExternalHooks } from '@/composables/useExternalHooks'; import { useExternalHooks } from '@/composables/useExternalHooks';
import { usePinnedData } from '@/composables/usePinnedData'; import { usePinnedData } from '@/composables/usePinnedData';
import { useDeviceSupport } from 'n8n-design-system';
import { useDebounce } from '@/composables/useDebounce'; import { useDebounce } from '@/composables/useDebounce';
export default defineComponent({ export default defineComponent({
@ -218,9 +219,17 @@ export default defineComponent({
const nodeHelpers = useNodeHelpers(); const nodeHelpers = useNodeHelpers();
const node = workflowsStore.getNodeByName(props.name); const node = workflowsStore.getNodeByName(props.name);
const pinnedData = usePinnedData(node); const pinnedData = usePinnedData(node);
const deviceSupport = useDeviceSupport();
const { callDebounced } = useDebounce(); const { callDebounced } = useDebounce();
return { contextMenu, externalHooks, nodeHelpers, pinnedData, callDebounced }; return {
contextMenu,
externalHooks,
nodeHelpers,
pinnedData,
deviceSupport,
callDebounced,
};
}, },
computed: { computed: {
...mapStores(useNodeTypesStore, useNDVStore, useUIStore, useWorkflowsStore), ...mapStores(useNodeTypesStore, useNDVStore, useUIStore, useWorkflowsStore),
@ -698,7 +707,7 @@ export default defineComponent({
this.pinDataDiscoveryTooltipVisible = false; this.pinDataDiscoveryTooltipVisible = false;
}, },
touchStart() { touchStart() {
if (this.isTouchDevice === true && !this.isMacOs && !this.isTouchActive) { if (this.deviceSupport.isTouchDevice && !this.deviceSupport.isMacOs && !this.isTouchActive) {
this.isTouchActive = true; this.isTouchActive = true;
setTimeout(() => { setTimeout(() => {
this.isTouchActive = false; this.isTouchActive = false;

View file

@ -169,7 +169,7 @@ import { useNDVStore } from '@/stores/ndv.store';
import { useNodeTypesStore } from '@/stores/nodeTypes.store'; import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import { useUIStore } from '@/stores/ui.store'; import { useUIStore } from '@/stores/ui.store';
import { useSettingsStore } from '@/stores/settings.store'; import { useSettingsStore } from '@/stores/settings.store';
import { useDeviceSupport } from 'n8n-design-system/composables/useDeviceSupport'; import { useDeviceSupport } from 'n8n-design-system';
import { useNodeHelpers } from '@/composables/useNodeHelpers'; import { useNodeHelpers } from '@/composables/useNodeHelpers';
import { useMessage } from '@/composables/useMessage'; import { useMessage } from '@/composables/useMessage';
import { useExternalHooks } from '@/composables/useExternalHooks'; import { useExternalHooks } from '@/composables/useExternalHooks';

View file

@ -11,7 +11,7 @@
:class="{ :class="{
'sticky-default': true, 'sticky-default': true,
'touch-active': isTouchActive, 'touch-active': isTouchActive,
'is-touch-device': isTouchDevice, 'is-touch-device': deviceSupport.isTouchDevice,
'is-read-only': isReadOnly, 'is-read-only': isReadOnly,
}" }"
:style="stickySize" :style="stickySize"
@ -122,6 +122,7 @@ import { useWorkflowsStore } from '@/stores/workflows.store';
import { useNDVStore } from '@/stores/ndv.store'; import { useNDVStore } from '@/stores/ndv.store';
import { useNodeTypesStore } from '@/stores/nodeTypes.store'; import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import { useContextMenu } from '@/composables/useContextMenu'; import { useContextMenu } from '@/composables/useContextMenu';
import { useDeviceSupport } from 'n8n-design-system';
export default defineComponent({ export default defineComponent({
name: 'Sticky', name: 'Sticky',
@ -135,6 +136,7 @@ export default defineComponent({
}, },
}, },
setup() { setup() {
const deviceSupport = useDeviceSupport();
const colorPopoverTrigger = ref<HTMLDivElement>(); const colorPopoverTrigger = ref<HTMLDivElement>();
const forceActions = ref(false); const forceActions = ref(false);
const setForceActions = (value: boolean) => { const setForceActions = (value: boolean) => {
@ -147,7 +149,7 @@ export default defineComponent({
} }
}); });
return { colorPopoverTrigger, contextMenu, forceActions, setForceActions }; return { deviceSupport, colorPopoverTrigger, contextMenu, forceActions, setForceActions };
}, },
computed: { computed: {
...mapStores(useNodeTypesStore, useNDVStore, useUIStore, useWorkflowsStore), ...mapStores(useNodeTypesStore, useNDVStore, useUIStore, useWorkflowsStore),
@ -318,7 +320,7 @@ export default defineComponent({
this.workflowsStore.updateNodeProperties(updateInformation); this.workflowsStore.updateNodeProperties(updateInformation);
}, },
touchStart() { touchStart() {
if (this.isTouchDevice === true && !this.isMacOs && !this.isTouchActive) { if (this.deviceSupport.isTouchDevice === true && !this.isMacOs && !this.isTouchActive) {
this.isTouchActive = true; this.isTouchActive = true;
setTimeout(() => { setTimeout(() => {
this.isTouchActive = false; this.isTouchActive = false;

View file

@ -1,6 +1,6 @@
import type { INodeUi, XYPosition } from '@/Interface'; import type { INodeUi, XYPosition } from '@/Interface';
import { useDeviceSupport } from 'n8n-design-system/composables/useDeviceSupport'; import { useDeviceSupport } from 'n8n-design-system';
import { useUIStore } from '@/stores/ui.store'; import { useUIStore } from '@/stores/ui.store';
import { useWorkflowsStore } from '@/stores/workflows.store'; import { useWorkflowsStore } from '@/stores/workflows.store';
import { getMousePosition, getRelativePosition } from '@/utils/nodeViewUtils'; import { getMousePosition, getRelativePosition } from '@/utils/nodeViewUtils';

View file

@ -6,7 +6,7 @@ import { useHistoryStore } from '@/stores/history.store';
import { useUIStore } from '@/stores/ui.store'; import { useUIStore } from '@/stores/ui.store';
import { onMounted, onUnmounted, nextTick } from 'vue'; import { onMounted, onUnmounted, nextTick } from 'vue';
import { useDeviceSupport } from 'n8n-design-system/composables/useDeviceSupport'; import { useDeviceSupport } from 'n8n-design-system';
import { getNodeViewTab } from '@/utils/canvasUtils'; import { getNodeViewTab } from '@/utils/canvasUtils';
import type { Route } from 'vue-router'; import type { Route } from 'vue-router';
import { useTelemetry } from './useTelemetry'; import { useTelemetry } from './useTelemetry';

View file

@ -1,31 +0,0 @@
import { defineComponent } from 'vue';
export const deviceSupportHelpers = defineComponent({
data() {
return {
// @ts-ignore msMaxTouchPoints is deprecated but must fix tablet bugs before fixing this.. otherwise breaks touchscreen computers
isTouchDevice: 'ontouchstart' in window || navigator.msMaxTouchPoints,
isMacOs: /(ipad|iphone|ipod|mac)/i.test(navigator.platform), // TODO: `platform` deprecated
};
},
computed: {
// TODO: Check if used anywhere
controlKeyCode(): string {
if (this.isMacOs) {
return 'Meta';
}
return 'Control';
},
},
methods: {
isCtrlKeyPressed(e: MouseEvent | KeyboardEvent): boolean {
if (this.isTouchDevice === true && e instanceof MouseEvent) {
return true;
}
if (this.isMacOs) {
return e.metaKey;
}
return e.ctrlKey;
},
},
});

View file

@ -1,12 +1,11 @@
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import { mapStores } from 'pinia'; import { mapStores } from 'pinia';
import { deviceSupportHelpers } from '@/mixins/deviceSupportHelpers';
import { getMousePosition } from '@/utils/nodeViewUtils'; import { getMousePosition } from '@/utils/nodeViewUtils';
import { useUIStore } from '@/stores/ui.store'; import { useUIStore } from '@/stores/ui.store';
import { useDeviceSupport } from 'n8n-design-system';
export const moveNodeWorkflow = defineComponent({ export const moveNodeWorkflow = defineComponent({
mixins: [deviceSupportHelpers],
data() { data() {
return { return {
moveLastPosition: [0, 0], moveLastPosition: [0, 0],
@ -30,7 +29,9 @@ export const moveNodeWorkflow = defineComponent({
this.moveLastPosition[1] = y; this.moveLastPosition[1] = y;
}, },
mouseDownMoveWorkflow(e: MouseEvent, moveButtonPressed: boolean) { mouseDownMoveWorkflow(e: MouseEvent, moveButtonPressed: boolean) {
if (!this.isCtrlKeyPressed(e) && !moveButtonPressed) { const deviceSupport = useDeviceSupport();
if (!deviceSupport.isCtrlKeyPressed(e) && !moveButtonPressed) {
// We only care about it when the ctrl key is pressed at the same time. // We only care about it when the ctrl key is pressed at the same time.
// So we exit when it is not pressed. // So we exit when it is not pressed.
return; return;

View file

@ -3,7 +3,6 @@ import type { PropType } from 'vue';
import { mapStores } from 'pinia'; import { mapStores } from 'pinia';
import type { INodeUi } from '@/Interface'; import type { INodeUi } from '@/Interface';
import { deviceSupportHelpers } from '@/mixins/deviceSupportHelpers';
import { import {
NO_OP_NODE_TYPE, NO_OP_NODE_TYPE,
NODE_CONNECTION_TYPE_ALLOW_MULTIPLE, NODE_CONNECTION_TYPE_ALLOW_MULTIPLE,
@ -28,6 +27,7 @@ import * as NodeViewUtils from '@/utils/nodeViewUtils';
import { useHistoryStore } from '@/stores/history.store'; import { useHistoryStore } from '@/stores/history.store';
import { useCanvasStore } from '@/stores/canvas.store'; import { useCanvasStore } from '@/stores/canvas.store';
import type { EndpointSpec } from '@jsplumb/common'; import type { EndpointSpec } from '@jsplumb/common';
import { useDeviceSupport } from 'n8n-design-system';
const createAddInputEndpointSpec = ( const createAddInputEndpointSpec = (
connectionName: NodeConnectionType, connectionName: NodeConnectionType,
@ -56,7 +56,6 @@ const createDiamondOutputEndpointSpec = (): EndpointSpec => ({
}); });
export const nodeBase = defineComponent({ export const nodeBase = defineComponent({
mixins: [deviceSupportHelpers],
data() { data() {
return { return {
inputs: [] as Array<ConnectionTypes | INodeInputConfiguration>, inputs: [] as Array<ConnectionTypes | INodeInputConfiguration>,
@ -615,13 +614,16 @@ export const nodeBase = defineComponent({
return createSupplementalConnectionType(connectionType); return createSupplementalConnectionType(connectionType);
}, },
touchEnd(e: MouseEvent) { touchEnd(e: MouseEvent) {
if (this.isTouchDevice) { const deviceSupport = useDeviceSupport();
if (deviceSupport.isTouchDevice) {
if (this.uiStore.isActionActive('dragActive')) { if (this.uiStore.isActionActive('dragActive')) {
this.uiStore.removeActiveAction('dragActive'); this.uiStore.removeActiveAction('dragActive');
} }
} }
}, },
mouseLeftClick(e: MouseEvent) { mouseLeftClick(e: MouseEvent) {
const deviceSupport = useDeviceSupport();
// @ts-ignore // @ts-ignore
const path = e.path || (e.composedPath && e.composedPath()); const path = e.path || (e.composedPath && e.composedPath());
for (let index = 0; index < path.length; index++) { for (let index = 0; index < path.length; index++) {
@ -634,11 +636,11 @@ export const nodeBase = defineComponent({
} }
} }
if (!this.isTouchDevice) { if (!deviceSupport.isTouchDevice) {
if (this.uiStore.isActionActive('dragActive')) { if (this.uiStore.isActionActive('dragActive')) {
this.uiStore.removeActiveAction('dragActive'); this.uiStore.removeActiveAction('dragActive');
} else { } else {
if (!this.isCtrlKeyPressed(e)) { if (!deviceSupport.isCtrlKeyPressed(e)) {
this.$emit('deselectAllNodes'); this.$emit('deselectAllNodes');
} }

View file

@ -377,6 +377,7 @@ import { useExternalHooks } from '@/composables/useExternalHooks';
import { useClipboard } from '@/composables/useClipboard'; import { useClipboard } from '@/composables/useClipboard';
import { usePinnedData } from '@/composables/usePinnedData'; import { usePinnedData } from '@/composables/usePinnedData';
import { useSourceControlStore } from '@/stores/sourceControl.store'; import { useSourceControlStore } from '@/stores/sourceControl.store';
import { useDeviceSupport } from 'n8n-design-system';
import { useDebounce } from '@/composables/useDebounce'; import { useDebounce } from '@/composables/useDebounce';
interface AddNodeOptions { interface AddNodeOptions {
@ -476,6 +477,7 @@ export default defineComponent({
const clipboard = useClipboard(); const clipboard = useClipboard();
const { activeNode } = storeToRefs(ndvStore); const { activeNode } = storeToRefs(ndvStore);
const pinnedData = usePinnedData(activeNode); const pinnedData = usePinnedData(activeNode);
const deviceSupport = useDeviceSupport();
const { callDebounced } = useDebounce(); const { callDebounced } = useDebounce();
return { return {
@ -486,6 +488,7 @@ export default defineComponent({
externalHooks, externalHooks,
clipboard, clipboard,
pinnedData, pinnedData,
deviceSupport,
callDebounced, callDebounced,
...useCanvasMouseSelect(), ...useCanvasMouseSelect(),
...useGlobalLinkActions(), ...useGlobalLinkActions(),
@ -1378,7 +1381,7 @@ export default defineComponent({
this.collaborationStore.notifyWorkflowOpened(workflow.id); this.collaborationStore.notifyWorkflowOpened(workflow.id);
}, },
touchTap(e: MouseEvent | TouchEvent) { touchTap(e: MouseEvent | TouchEvent) {
if (this.isTouchDevice) { if (this.deviceSupport.isTouchDevice) {
this.mouseDown(e); this.mouseDown(e);
} }
}, },
@ -1403,7 +1406,7 @@ export default defineComponent({
this.mouseUpMoveWorkflow(e); this.mouseUpMoveWorkflow(e);
}, },
keyUp(e: KeyboardEvent) { keyUp(e: KeyboardEvent) {
if (e.key === this.controlKeyCode) { if (e.key === this.deviceSupport.controlKeyCode) {
this.ctrlKeyPressed = false; this.ctrlKeyPressed = false;
} }
if (e.key === ' ') { if (e.key === ' ') {
@ -1413,10 +1416,10 @@ export default defineComponent({
async keyDown(e: KeyboardEvent) { async keyDown(e: KeyboardEvent) {
this.contextMenu.close(); this.contextMenu.close();
const ctrlModifier = this.isCtrlKeyPressed(e) && !e.shiftKey && !e.altKey; const ctrlModifier = this.deviceSupport.isCtrlKeyPressed(e) && !e.shiftKey && !e.altKey;
const shiftModifier = e.shiftKey && !e.altKey && !this.isCtrlKeyPressed(e); const shiftModifier = e.shiftKey && !e.altKey && !this.deviceSupport.isCtrlKeyPressed(e);
const ctrlAltModifier = this.isCtrlKeyPressed(e) && e.altKey && !e.shiftKey; const ctrlAltModifier = this.deviceSupport.isCtrlKeyPressed(e) && e.altKey && !e.shiftKey;
const noModifierKeys = !this.isCtrlKeyPressed(e) && !e.shiftKey && !e.altKey; const noModifierKeys = !this.deviceSupport.isCtrlKeyPressed(e) && !e.shiftKey && !e.altKey;
const readOnly = this.isReadOnlyRoute || this.readOnlyEnv; const readOnly = this.isReadOnlyRoute || this.readOnlyEnv;
if (e.key === 's' && ctrlModifier && !readOnly) { if (e.key === 's' && ctrlModifier && !readOnly) {
@ -1497,7 +1500,7 @@ export default defineComponent({
void this.onRunWorkflow(); void this.onRunWorkflow();
} else if (e.key === 'S' && shiftModifier && !readOnly) { } else if (e.key === 'S' && shiftModifier && !readOnly) {
void this.onAddNodes({ nodes: [{ type: STICKY_NODE_TYPE }], connections: [] }); void this.onAddNodes({ nodes: [{ type: STICKY_NODE_TYPE }], connections: [] });
} else if (e.key === this.controlKeyCode) { } else if (e.key === this.deviceSupport.controlKeyCode) {
this.ctrlKeyPressed = true; this.ctrlKeyPressed = true;
} else if (e.key === ' ') { } else if (e.key === ' ') {
this.moveCanvasKeyPressed = true; this.moveCanvasKeyPressed = true;