fix(editor): Fix canvas moving check (#17856)

This commit is contained in:
Suguru Inoue
2025-07-31 12:28:46 +02:00
committed by GitHub
parent 34991d6f58
commit ddc4c0b7d9
5 changed files with 27 additions and 38 deletions

View File

@@ -142,6 +142,7 @@ export function createCanvasProvide({
connectingHandle: ref(connectingHandle), connectingHandle: ref(connectingHandle),
viewport: ref(viewport), viewport: ref(viewport),
isExperimentalNdvActive: computed(() => false), isExperimentalNdvActive: computed(() => false),
isPaneMoving: ref(false),
} satisfies CanvasInjectionData, } satisfies CanvasInjectionData,
}; };
} }

View File

@@ -196,15 +196,18 @@ const classes = computed(() => ({
const panningKeyCode = ref<string[] | true>(isMobileDevice ? true : [' ', controlKeyCode]); const panningKeyCode = ref<string[] | true>(isMobileDevice ? true : [' ', controlKeyCode]);
const panningMouseButton = ref<number[] | true>(isMobileDevice ? true : [1]); const panningMouseButton = ref<number[] | true>(isMobileDevice ? true : [1]);
const selectionKeyCode = ref<string | true | null>(isMobileDevice ? 'Shift' : true); const selectionKeyCode = ref<string | true | null>(isMobileDevice ? 'Shift' : true);
const isInPanningMode = ref(false);
function switchToPanningMode() { function switchToPanningMode() {
selectionKeyCode.value = null; selectionKeyCode.value = null;
panningMouseButton.value = [0, 1]; panningMouseButton.value = [0, 1];
isInPanningMode.value = true;
} }
function switchToSelectionMode() { function switchToSelectionMode() {
selectionKeyCode.value = true; selectionKeyCode.value = true;
panningMouseButton.value = [1]; panningMouseButton.value = [1];
isInPanningMode.value = false;
} }
onKeyDown(panningKeyCode.value, switchToPanningMode, { onKeyDown(panningKeyCode.value, switchToPanningMode, {
@@ -651,8 +654,13 @@ function setReadonly(value: boolean) {
elementsSelectable.value = true; elementsSelectable.value = true;
} }
function onPaneMoveStart() { function onPaneMove({ event }: { event: unknown }) {
isPaneMoving.value = true; // The event object is either D3ZoomEvent or WheelEvent.
// Here I'm ignoring D3ZoomEvent because it's not necessarily followed by a moveEnd event.
// This can be simplified once https://github.com/bcakmakoglu/vue-flow/issues/1908 is resolved
if (isInPanningMode.value || event instanceof WheelEvent) {
isPaneMoving.value = true;
}
} }
function onPaneMoveEnd() { function onPaneMoveEnd() {
@@ -894,6 +902,7 @@ provide(CanvasKey, {
initialized, initialized,
viewport, viewport,
isExperimentalNdvActive, isExperimentalNdvActive,
isPaneMoving,
}); });
</script> </script>
@@ -923,7 +932,7 @@ provide(CanvasKey, {
@connect-end="onConnectEnd" @connect-end="onConnectEnd"
@pane-click="onClickPane" @pane-click="onClickPane"
@pane-context-menu="onOpenContextMenu" @pane-context-menu="onOpenContextMenu"
@move-start="onPaneMoveStart" @move="onPaneMove"
@move-end="onPaneMoveEnd" @move-end="onPaneMoveEnd"
@node-drag-stop="onNodeDragStop" @node-drag-stop="onNodeDragStop"
@node-click="onNodeClick" @node-click="onNodeClick"

View File

@@ -1,5 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import InputPanel from '@/components/InputPanel.vue'; import InputPanel from '@/components/InputPanel.vue';
import { useCanvas } from '@/composables/useCanvas';
import type { INodeUi } from '@/Interface'; import type { INodeUi } from '@/Interface';
import { useNDVStore } from '@/stores/ndv.store'; import { useNDVStore } from '@/stores/ndv.store';
import { N8nText } from '@n8n/design-system'; import { N8nText } from '@n8n/design-system';
@@ -7,7 +8,7 @@ import { useVueFlow } from '@vue-flow/core';
import { useActiveElement } from '@vueuse/core'; import { useActiveElement } from '@vueuse/core';
import { ElPopover } from 'element-plus'; import { ElPopover } from 'element-plus';
import type { Workflow } from 'n8n-workflow'; import type { Workflow } from 'n8n-workflow';
import { onBeforeUnmount, ref, useTemplateRef, watch } from 'vue'; import { ref, useTemplateRef, watch } from 'vue';
const { node, container } = defineProps<{ const { node, container } = defineProps<{
workflow: Workflow; workflow: Workflow;
@@ -18,23 +19,12 @@ const { node, container } = defineProps<{
const ndvStore = useNDVStore(); const ndvStore = useNDVStore();
const vf = useVueFlow(); const vf = useVueFlow();
const { isPaneMoving, viewport } = useCanvas();
const activeElement = useActiveElement(); const activeElement = useActiveElement();
const inputPanelRef = useTemplateRef('inputPanel'); const inputPanelRef = useTemplateRef('inputPanel');
const shouldShowInputPanel = ref(false); const shouldShowInputPanel = ref(false);
const moveStartListener = vf.onMoveStart(() => {
shouldShowInputPanel.value = false;
});
const moveEndListener = vf.onMoveEnd(() => {
shouldShowInputPanel.value = getShouldShowInputPanel();
});
const viewportChangeListener = vf.onViewportChange(() => {
shouldShowInputPanel.value = false;
});
function getShouldShowInputPanel() { function getShouldShowInputPanel() {
const active = activeElement.value; const active = activeElement.value;
@@ -59,10 +49,12 @@ watch([activeElement, vf.getSelectedNodes], ([active, selected]) => {
} }
}); });
onBeforeUnmount(() => { watch(isPaneMoving, (moving) => {
moveStartListener.off(); shouldShowInputPanel.value = !moving && getShouldShowInputPanel();
moveEndListener.off(); });
viewportChangeListener.off();
watch(viewport, () => {
shouldShowInputPanel.value = false;
}); });
</script> </script>

View File

@@ -8,13 +8,14 @@ import type { ExpressionLocalResolveContext } from '@/types/expressions';
import { N8nText } from '@n8n/design-system'; import { N8nText } from '@n8n/design-system';
import { useVueFlow } from '@vue-flow/core'; import { useVueFlow } from '@vue-flow/core';
import { watchOnce } from '@vueuse/core'; import { watchOnce } from '@vueuse/core';
import { computed, onBeforeUnmount, provide, ref, useTemplateRef } from 'vue'; import { computed, provide, ref, useTemplateRef } from 'vue';
import { useExperimentalNdvStore } from '../experimentalNdv.store'; import { useExperimentalNdvStore } from '../experimentalNdv.store';
import ExperimentalCanvasNodeSettings from './ExperimentalCanvasNodeSettings.vue'; import ExperimentalCanvasNodeSettings from './ExperimentalCanvasNodeSettings.vue';
import { useI18n } from '@n8n/i18n'; import { useI18n } from '@n8n/i18n';
import NodeIcon from '@/components/NodeIcon.vue'; import NodeIcon from '@/components/NodeIcon.vue';
import { getNodeSubTitleText } from '@/components/canvas/experimental/experimentalNdv.utils'; import { getNodeSubTitleText } from '@/components/canvas/experimental/experimentalNdv.utils';
import ExperimentalEmbeddedNdvActions from '@/components/canvas/experimental/components/ExperimentalEmbeddedNdvActions.vue'; import ExperimentalEmbeddedNdvActions from '@/components/canvas/experimental/components/ExperimentalEmbeddedNdvActions.vue';
import { useCanvas } from '@/composables/useCanvas';
const { nodeId, isReadOnly, isConfigurable } = defineProps<{ const { nodeId, isReadOnly, isConfigurable } = defineProps<{
nodeId: string; nodeId: string;
@@ -36,22 +37,7 @@ const nodeType = computed(() => {
return null; return null;
}); });
const vf = useVueFlow(); const vf = useVueFlow();
const { isPaneMoving } = useCanvas();
const isMoving = ref(false);
const moveStartListener = vf.onMoveStart(() => {
isMoving.value = true;
});
const moveEndListener = vf.onMoveEnd(() => {
isMoving.value = false;
});
onBeforeUnmount(() => {
moveStartListener.off();
moveEndListener.off();
});
const isVisible = computed(() => const isVisible = computed(() =>
vf.isNodeIntersecting( vf.isNodeIntersecting(
{ id: nodeId }, { id: nodeId },
@@ -150,7 +136,7 @@ watchOnce(isVisible, (visible) => {
'--zoom': `${1 / experimentalNdvStore.maxCanvasZoom}`, '--zoom': `${1 / experimentalNdvStore.maxCanvasZoom}`,
'--node-width-scaler': isConfigurable ? 1 : 1.5, '--node-width-scaler': isConfigurable ? 1 : 1.5,
'--max-height-on-focus': `${(vf.dimensions.value.height * 0.8) / experimentalNdvStore.maxCanvasZoom}px`, '--max-height-on-focus': `${(vf.dimensions.value.height * 0.8) / experimentalNdvStore.maxCanvasZoom}px`,
pointerEvents: isMoving ? 'none' : 'auto', // Don't interrupt canvas panning pointerEvents: isPaneMoving ? 'none' : 'auto', // Don't interrupt canvas panning
}" }"
> >
<template v-if="!node || !isOnceVisible" /> <template v-if="!node || !isOnceVisible" />

View File

@@ -164,6 +164,7 @@ export interface CanvasInjectionData {
connectingHandle: Ref<ConnectStartEvent | undefined>; connectingHandle: Ref<ConnectStartEvent | undefined>;
viewport: Ref<ViewportTransform>; viewport: Ref<ViewportTransform>;
isExperimentalNdvActive: ComputedRef<boolean>; isExperimentalNdvActive: ComputedRef<boolean>;
isPaneMoving: Ref<boolean>;
} }
export type CanvasNodeEventBusEvents = { export type CanvasNodeEventBusEvents = {