feat(editor): Add experimental NDV pane in canvas (no-changelog) (#16419)

This commit is contained in:
Suguru Inoue
2025-06-19 09:18:05 +02:00
committed by GitHub
parent c0d1ff6e4c
commit d0eb7a45ad
8 changed files with 141 additions and 10 deletions

View File

@@ -53,7 +53,7 @@ import CanvasBackground from './elements/background/CanvasBackground.vue';
import CanvasArrowHeadMarker from './elements/edges/CanvasArrowHeadMarker.vue';
import Edge from './elements/edges/CanvasEdge.vue';
import Node from './elements/nodes/CanvasNode.vue';
import { useViewportAutoAdjust } from '@/components/canvas/composables/useViewportAutoAdjust';
import { useViewportAutoAdjust } from './composables/useViewportAutoAdjust';
import { isOutsideSelected } from '@/utils/htmlUtils';
const $style = useCssModule();

View File

@@ -9,6 +9,8 @@ import { createEventBus } from '@n8n/utils/event-bus';
import type { CanvasEventBusEvents } from '@/types';
import { useVueFlow } from '@vue-flow/core';
import { throttledRef } from '@vueuse/core';
import { useSettingsStore } from '@/stores/settings.store';
import ExperimentalNodeDetailsDrawer from './components/ExperimentalNodeDetailsDrawer.vue';
defineOptions({
inheritAttrs: false,
@@ -34,8 +36,9 @@ const props = withDefaults(
);
const $style = useCssModule();
const settingsStore = useSettingsStore();
const { onNodesInitialized } = useVueFlow({ id: props.id });
const { onNodesInitialized, getSelectedNodes } = useVueFlow({ id: props.id });
const workflow = toRef(props, 'workflow');
const workflowObject = toRef(props, 'workflowObject');
@@ -79,12 +82,16 @@ const mappedConnectionsThrottled = throttledRef(mappedConnections, 200);
/>
</div>
<slot />
<ExperimentalNodeDetailsDrawer
v-if="settingsStore.experimental__dockedNodeSettingsEnabled && !props.readOnly"
:selected-nodes="getSelectedNodes"
/>
</div>
</template>
<style lang="scss" module>
.wrapper {
display: block;
display: flex;
position: relative;
width: 100%;
height: 100%;
@@ -96,5 +103,7 @@ const mappedConnectionsThrottled = throttledRef(mappedConnections, 200);
height: 100%;
position: relative;
display: block;
align-items: stretch;
justify-content: stretch;
}
</style>

View File

@@ -1,15 +1,20 @@
<script setup lang="ts">
import NodeSettings from '@/components/NodeSettings.vue';
import { useCanvasOperations } from '@/composables/useCanvasOperations';
import { type IUpdateInformation } from '@/Interface';
import { useNDVStore } from '@/stores/ndv.store';
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import { useWorkflowsStore } from '@/stores/workflows.store';
import { createEventBus } from '@n8n/utils/event-bus';
import { computed } from 'vue';
const { nodeId } = defineProps<{ nodeId: string }>();
const { nodeId, canOpenNdv } = defineProps<{ nodeId: string; canOpenNdv?: boolean }>();
const settingsEventBus = createEventBus();
const nodeTypesStore = useNodeTypesStore();
const workflowsStore = useWorkflowsStore();
const { setActiveNodeName } = useNDVStore();
const { renameNode } = useCanvasOperations();
const activeNode = computed(() => workflowsStore.getNodeById(nodeId));
const activeNodeType = computed(() => {
@@ -18,10 +23,23 @@ const activeNodeType = computed(() => {
}
return null;
});
function handleOpenNdv() {
if (activeNode.value) {
setActiveNodeName(activeNode.value.name);
}
}
function handleValueChanged(parameterData: IUpdateInformation) {
if (parameterData.name === 'name' && parameterData.oldValue) {
void renameNode(parameterData.oldValue as string, parameterData.value as string);
}
}
</script>
<template>
<NodeSettings
:can-expand="canOpenNdv"
:event-bus="settingsEventBus"
:dragging="false"
:active-node="activeNode"
@@ -32,5 +50,8 @@ const activeNodeType = computed(() => {
:block-u-i="false"
:executable="false"
:input-size="0"
hide-connections
@expand="handleOpenNdv"
@value-changed="handleValueChanged"
/>
</template>

View File

@@ -0,0 +1,63 @@
<script setup lang="ts">
import { type CanvasNode } from '@/types';
import ExperimentalCanvasNodeSettings from './ExperimentalCanvasNodeSettings.vue';
import { N8nText } from '@n8n/design-system';
import { computed, watch, ref } from 'vue';
const { selectedNodes } = defineProps<{ selectedNodes: CanvasNode[] }>();
const content = computed(() =>
selectedNodes.length > 1
? `${selectedNodes.length} nodes selected`
: selectedNodes.length > 0
? selectedNodes[0]
: undefined,
);
const lastContent = ref<string | CanvasNode | undefined>();
// Sync lastContent to be "last defined content" (for drawer animation)
watch(
content,
(newContent) => {
if (newContent !== undefined) {
lastContent.value = newContent;
}
},
{ immediate: true },
);
</script>
<template>
<div :class="[$style.component, content === undefined ? $style.closed : '']">
<N8nText v-if="typeof lastContent === 'string'" color="text-base">
{{ lastContent }}
</N8nText>
<ExperimentalCanvasNodeSettings
v-else-if="lastContent !== undefined"
:node-id="lastContent.id"
can-open-ndv
/>
</div>
</template>
<style lang="scss" module>
.component {
position: absolute;
right: 0;
z-index: 10;
flex-grow: 0;
flex-shrink: 0;
border-left: var(--border-base);
background-color: var(--color-background-xlight);
width: #{$node-creator-width};
height: 100%;
display: flex;
align-items: center;
justify-content: center;
transition: transform 0.2s ease;
&.closed {
transform: translateX(100%);
}
}
</style>

View File

@@ -7,7 +7,7 @@ import { NODE_INSERT_SPACER_BETWEEN_INPUT_GROUPS } from '@/constants';
import type { CanvasNodeDefaultRender } from '@/types';
import { useCanvas } from '@/composables/useCanvas';
import { useNodeSettingsInCanvas } from '@/components/canvas/composables/useNodeSettingsInCanvas';
import CanvasNodeNodeSettings from './parts/CanvasNodeNodeSettings.vue';
import ExperimentalCanvasNodeSettings from '../../../components/ExperimentalCanvasNodeSettings.vue';
const $style = useCssModule();
const i18n = useI18n();
@@ -153,7 +153,7 @@ function onActivate(event: MouseEvent) {
@contextmenu="openContextMenu"
@dblclick.stop="onActivate"
>
<CanvasNodeNodeSettings v-if="nodeSettingsZoom !== undefined" :node-id="id" />
<ExperimentalCanvasNodeSettings v-if="nodeSettingsZoom !== undefined" :node-id="id" />
<template v-else>
<CanvasNodeTooltip v-if="renderOptions.tooltip" :visible="showTooltip" />
<NodeIcon :icon-source="iconSource" :size="iconSize" :shrink="false" :disabled="isDisabled" />