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';
interface DeviceSupportHelpers {
isTouchDevice: boolean;
isMacOs: boolean;
controlKeyCode: string;
isCtrlKeyPressed: (e: MouseEvent | KeyboardEvent) => boolean;
}
export function useDeviceSupport(): DeviceSupportHelpers {
const isTouchDevice = ref('ontouchstart' in window || navigator.maxTouchPoints > 0);
export function useDeviceSupport() {
const isTouchDevice = ref(window.hasOwnProperty('ontouchstart') || navigator.maxTouchPoints > 0);
const userAgent = ref(navigator.userAgent.toLowerCase());
const isMacOs = ref(
userAgent.value.includes('macintosh') ||

View file

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

View file

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

View file

@ -169,7 +169,7 @@ import { useNDVStore } from '@/stores/ndv.store';
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import { useUIStore } from '@/stores/ui.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 { useMessage } from '@/composables/useMessage';
import { useExternalHooks } from '@/composables/useExternalHooks';

View file

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

View file

@ -1,6 +1,6 @@
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 { useWorkflowsStore } from '@/stores/workflows.store';
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 { 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 type { Route } from 'vue-router';
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 { mapStores } from 'pinia';
import { deviceSupportHelpers } from '@/mixins/deviceSupportHelpers';
import { getMousePosition } from '@/utils/nodeViewUtils';
import { useUIStore } from '@/stores/ui.store';
import { useDeviceSupport } from 'n8n-design-system';
export const moveNodeWorkflow = defineComponent({
mixins: [deviceSupportHelpers],
data() {
return {
moveLastPosition: [0, 0],
@ -30,7 +29,9 @@ export const moveNodeWorkflow = defineComponent({
this.moveLastPosition[1] = y;
},
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.
// So we exit when it is not pressed.
return;

View file

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

View file

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