From ba692281f0dbfd5c8debdf59c31bac1adfd54e8c Mon Sep 17 00:00:00 2001 From: Suguru Inoue Date: Wed, 9 Jul 2025 11:50:30 +0200 Subject: [PATCH] feat(editor): Zoom into a node to open experimental embedded NDV (no-changelog) (#16912) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Milorad FIlipović --- .../src/components/N8nIcon/icons.ts | 2 + .../frontend/@n8n/i18n/src/locales/en.json | 2 + .../src/components/Node/NodeCreation.vue | 16 ----- .../elements/buttons/CanvasControlButtons.vue | 27 +++++++++ .../CanvasControlButtons.test.ts.snap | 2 + .../canvas/elements/nodes/CanvasNode.vue | 13 +++- .../elements/nodes/CanvasNodeToolbar.vue | 25 +++++++- .../nodes/render-types/CanvasNodeDefault.vue | 3 + .../ExperimentalCanvasNodeSettings.vue | 8 ++- .../ExperimentalEmbeddedNodeDetails.vue | 59 +++++++++---------- .../experimental/experimentalNdv.store.ts | 39 +++++++++++- .../logs/composables/useLogsSelection.ts | 9 ++- 12 files changed, 152 insertions(+), 53 deletions(-) diff --git a/packages/frontend/@n8n/design-system/src/components/N8nIcon/icons.ts b/packages/frontend/@n8n/design-system/src/components/N8nIcon/icons.ts index f090f6a318..6d69c2b567 100644 --- a/packages/frontend/@n8n/design-system/src/components/N8nIcon/icons.ts +++ b/packages/frontend/@n8n/design-system/src/components/N8nIcon/icons.ts @@ -66,6 +66,7 @@ import IconLucideCode from '~icons/lucide/code'; import IconLucideCog from '~icons/lucide/cog'; import IconLucideContrast from '~icons/lucide/contrast'; import IconLucideCopy from '~icons/lucide/copy'; +import IconLucideCrosshair from '~icons/lucide/crosshair'; import IconLucideDatabase from '~icons/lucide/database'; import IconLucideEarth from '~icons/lucide/earth'; import IconLucideEllipsis from '~icons/lucide/ellipsis'; @@ -460,6 +461,7 @@ export const updatedIconSet = { cog: IconLucideCog, contrast: IconLucideContrast, copy: IconLucideCopy, + crosshair: IconLucideCrosshair, database: IconLucideDatabase, earth: IconLucideEarth, ellipsis: IconLucideEllipsis, diff --git a/packages/frontend/@n8n/i18n/src/locales/en.json b/packages/frontend/@n8n/i18n/src/locales/en.json index 412e80ab01..8379ece5c3 100644 --- a/packages/frontend/@n8n/i18n/src/locales/en.json +++ b/packages/frontend/@n8n/i18n/src/locales/en.json @@ -1556,6 +1556,8 @@ "nodeView.zoomToFit": "Zoom to Fit", "nodeView.replaceMe": "Replace Me", "nodeView.setupTemplate": "Set up template", + "nodeView.expandAllNodes": "Expand all nodes", + "nodeView.collapseAllNodes": "Collapse all nodes", "nodeViewV2.showError.editingNotAllowed": "Editing is not allowed", "nodeViewV2.showError.failedToCreateNode": "Failed to create node", "contextMenu.node": "node | nodes", diff --git a/packages/frontend/editor-ui/src/components/Node/NodeCreation.vue b/packages/frontend/editor-ui/src/components/Node/NodeCreation.vue index 8a0acba366..12e17fd589 100644 --- a/packages/frontend/editor-ui/src/components/Node/NodeCreation.vue +++ b/packages/frontend/editor-ui/src/components/Node/NodeCreation.vue @@ -20,7 +20,6 @@ import type { import { useActions } from './NodeCreator/composables/useActions'; import KeyboardShortcutTooltip from '@/components/KeyboardShortcutTooltip.vue'; import { useI18n } from '@n8n/i18n'; -import { useExperimentalNdvStore } from '../canvas/experimental/experimentalNdv.store'; type Props = { nodeViewScale: number; @@ -45,7 +44,6 @@ const uiStore = useUIStore(); const focusPanelStore = useFocusPanelStore(); const posthogStore = usePostHog(); const i18n = useI18n(); -const experimentalNdvStore = useExperimentalNdvStore(); const { getAddedNodesAndConnections } = useActions(); @@ -127,20 +125,6 @@ function nodeTypeSelected(value: NodeTypeSelectedPayload[]) { @click="focusPanelStore.toggleFocusPanel" /> - - props.zoom !== 1); function onResetZoom() { @@ -109,6 +112,30 @@ function onTidyUp() { + + + + + + diff --git a/packages/frontend/editor-ui/src/components/canvas/elements/buttons/__snapshots__/CanvasControlButtons.test.ts.snap b/packages/frontend/editor-ui/src/components/canvas/elements/buttons/__snapshots__/CanvasControlButtons.test.ts.snap index 522002119b..72d49d306f 100644 --- a/packages/frontend/editor-ui/src/components/canvas/elements/buttons/__snapshots__/CanvasControlButtons.test.ts.snap +++ b/packages/frontend/editor-ui/src/components/canvas/elements/buttons/__snapshots__/CanvasControlButtons.test.ts.snap @@ -21,5 +21,7 @@ exports[`CanvasControlButtons > should render correctly 1`] = ` + + " `; diff --git a/packages/frontend/editor-ui/src/components/canvas/elements/nodes/CanvasNode.vue b/packages/frontend/editor-ui/src/components/canvas/elements/nodes/CanvasNode.vue index 96c49131b8..1a87b783b0 100644 --- a/packages/frontend/editor-ui/src/components/canvas/elements/nodes/CanvasNode.vue +++ b/packages/frontend/editor-ui/src/components/canvas/elements/nodes/CanvasNode.vue @@ -35,6 +35,7 @@ import { createEventBus } from '@n8n/utils/event-bus'; import isEqual from 'lodash/isEqual'; import CanvasNodeTrigger from '@/components/canvas/elements/nodes/render-types/parts/CanvasNodeTrigger.vue'; import { CONFIGURATION_NODE_OFFSET, GRID_SIZE } from '@/utils/nodeViewUtils'; +import { useExperimentalNdvStore } from '../../experimental/experimentalNdv.store'; type Props = NodeProps & { readOnly?: boolean; @@ -72,7 +73,9 @@ const props = defineProps(); const contextMenu = useContextMenu(); -const { connectingHandle } = useCanvas(); +const { connectingHandle, viewport } = useCanvas(); + +const experimentalNdvStore = useExperimentalNdvStore(); /* Toolbar slot classes @@ -96,6 +99,10 @@ const { const isDisabled = computed(() => props.data.disabled); +const isExperimentalEmbeddedNdvShown = computed(() => + experimentalNdvStore.isActive(viewport.value.zoom), +); + const classes = computed(() => ({ [style.canvasNode]: true, [style.showToolbar]: showToolbar.value, @@ -187,7 +194,9 @@ const createEndpointMappingFn = const offsetValue = position === Position.Bottom ? `${GRID_SIZE * 2 * (1 + index * 2) + CONFIGURATION_NODE_OFFSET}px` - : `${(100 / (endpoints.length + 1)) * (index + 1)}%`; + : isExperimentalEmbeddedNdvShown.value && endpoints.length === 1 + ? `${(1 + index) * (GRID_SIZE * 2)}px` + : `${(100 / (endpoints.length + 1)) * (index + 1)}%`; return { ...endpoint, diff --git a/packages/frontend/editor-ui/src/components/canvas/elements/nodes/CanvasNodeToolbar.vue b/packages/frontend/editor-ui/src/components/canvas/elements/nodes/CanvasNodeToolbar.vue index 0178ddfa2e..243dfc8b73 100644 --- a/packages/frontend/editor-ui/src/components/canvas/elements/nodes/CanvasNodeToolbar.vue +++ b/packages/frontend/editor-ui/src/components/canvas/elements/nodes/CanvasNodeToolbar.vue @@ -6,6 +6,7 @@ import { CanvasNodeRenderType } from '@/types'; import { useCanvas } from '@/composables/useCanvas'; import { useNodeTypesStore } from '@/stores/nodeTypes.store'; import { useWorkflowsStore } from '@/stores/workflows.store'; +import { useExperimentalNdvStore } from '../../experimental/experimentalNdv.store'; const emit = defineEmits<{ delete: []; @@ -27,8 +28,9 @@ const { isDisabled, render, name } = useCanvasNode(); const workflowsStore = useWorkflowsStore(); const nodeTypesStore = useNodeTypesStore(); +const experimentalNdvStore = useExperimentalNdvStore(); -const node = computed(() => !!name.value && workflowsStore.getNodeByName(name.value)); +const node = computed(() => (name.value ? workflowsStore.getNodeByName(name.value) : null)); const isToolNode = computed(() => !!node.value && nodeTypesStore.isToolNode(node.value.type)); const nodeDisabledTitle = computed(() => { @@ -59,6 +61,13 @@ const isDisableNodeVisible = computed(() => { const isDeleteNodeVisible = computed(() => !props.readOnly); +const isFocusNodeVisible = computed( + () => + experimentalNdvStore.isEnabled && + node.value !== null && + experimentalNdvStore.collapsedNodes[node.value.id] !== false, +); + const isStickyNoteChangeColorVisible = computed( () => !props.readOnly && render.value.type === CanvasNodeRenderType.StickyNote, ); @@ -92,6 +101,12 @@ function onMouseEnter() { function onMouseLeave() { isHovered.value = false; } + +function onFocusNode() { + if (node.value) { + experimentalNdvStore.focusNode(node.value.id); + } +}