feat(editor): Merge experimental params pane into focus pane (no-changelog) (#18337)

This commit is contained in:
Suguru Inoue
2025-08-27 11:04:24 +02:00
committed by GitHub
parent 98bde4f478
commit 6d306c53dd
26 changed files with 317 additions and 179 deletions

View File

@@ -16,7 +16,8 @@ import { startCompletion } from '@codemirror/autocomplete';
import type { EditorState, SelectionRange } from '@codemirror/state'; import type { EditorState, SelectionRange } from '@codemirror/state';
import type { IDataObject } from 'n8n-workflow'; import type { IDataObject } from 'n8n-workflow';
import { createEventBus, type EventBus } from '@n8n/utils/event-bus'; import { createEventBus, type EventBus } from '@n8n/utils/event-bus';
import { CanvasKey, ExpressionLocalResolveContextSymbol } from '@/constants'; import { CanvasKey } from '@/constants';
import { useIsInExperimentalNdv } from '@/components/canvas/experimental/composables/useIsInExperimentalNdv';
const isFocused = ref(false); const isFocused = ref(false);
const segments = ref<Segment[]>([]); const segments = ref<Segment[]>([]);
@@ -56,8 +57,7 @@ const ndvStore = useNDVStore();
const workflowsStore = useWorkflowsStore(); const workflowsStore = useWorkflowsStore();
const canvas = inject(CanvasKey, undefined); const canvas = inject(CanvasKey, undefined);
const expressionLocalResolveCtx = inject(ExpressionLocalResolveContextSymbol, undefined); const isInExperimentalNdv = useIsInExperimentalNdv();
const isInExperimentalNdv = computed(() => expressionLocalResolveCtx?.value !== undefined);
const isDragging = computed(() => ndvStore.isDraggableDragging); const isDragging = computed(() => ndvStore.isDraggableDragging);
const isOutputPopoverVisible = computed( const isOutputPopoverVisible = computed(
@@ -236,7 +236,7 @@ defineExpose({ focus, select });
:segments="segments" :segments="segments"
:is-read-only="isReadOnly" :is-read-only="isReadOnly"
:virtual-ref="container" :virtual-ref="container"
:append-to="isInExperimentalNdv ? '#canvas' : undefined" :append-to="isInExperimentalNdv ? 'body' : undefined"
/> />
</div> </div>
</template> </template>

View File

@@ -26,12 +26,16 @@ import {
import { useEnvironmentsStore } from '@/stores/environments.ee.store'; import { useEnvironmentsStore } from '@/stores/environments.ee.store';
import { htmlEditorEventBus } from '@/event-bus'; import { htmlEditorEventBus } from '@/event-bus';
import { hasFocusOnInput, isFocusableEl } from '@/utils/typesUtils'; import { hasFocusOnInput, isFocusableEl } from '@/utils/typesUtils';
import type { ResizeData, TargetNodeParameterContext } from '@/Interface'; import type { INodeUi, ResizeData, TargetNodeParameterContext } from '@/Interface';
import { useTelemetry } from '@/composables/useTelemetry'; import { useTelemetry } from '@/composables/useTelemetry';
import { useThrottleFn } from '@vueuse/core'; import { useThrottleFn } from '@vueuse/core';
import { useStyles } from '@/composables/useStyles';
import { useExecutionData } from '@/composables/useExecutionData'; import { useExecutionData } from '@/composables/useExecutionData';
import { useWorkflowsStore } from '@/stores/workflows.store'; import { useWorkflowsStore } from '@/stores/workflows.store';
import ExperimentalNodeDetailsDrawer from '@/components/canvas/experimental/components/ExperimentalNodeDetailsDrawer.vue';
import { useExperimentalNdvStore } from '@/components/canvas/experimental/experimentalNdv.store';
import { useNDVStore } from '@/stores/ndv.store';
import { useVueFlow } from '@vue-flow/core';
import ExperimentalFocusPanelHeader from '@/components/canvas/experimental/components/ExperimentalFocusPanelHeader.vue';
defineOptions({ name: 'FocusPanel' }); defineOptions({ name: 'FocusPanel' });
@@ -56,8 +60,10 @@ const nodeTypesStore = useNodeTypesStore();
const telemetry = useTelemetry(); const telemetry = useTelemetry();
const nodeSettingsParameters = useNodeSettingsParameters(); const nodeSettingsParameters = useNodeSettingsParameters();
const environmentsStore = useEnvironmentsStore(); const environmentsStore = useEnvironmentsStore();
const experimentalNdvStore = useExperimentalNdvStore();
const ndvStore = useNDVStore();
const deviceSupport = useDeviceSupport(); const deviceSupport = useDeviceSupport();
const styles = useStyles(); const vueFlow = useVueFlow(workflowsStore.workflowId);
const focusedNodeParameter = computed(() => focusPanelStore.focusedNodeParameters[0]); const focusedNodeParameter = computed(() => focusPanelStore.focusedNodeParameters[0]);
const resolvedParameter = computed(() => const resolvedParameter = computed(() =>
@@ -99,23 +105,28 @@ const isDisplayed = computed(() => {
); );
}); });
const node = computed<INodeUi | undefined>(() => {
if (!experimentalNdvStore.isNdvInFocusPanelEnabled || resolvedParameter.value) {
return resolvedParameter.value?.node;
}
const selected = vueFlow.getSelectedNodes.value[0]?.id;
return selected ? workflowsStore.allNodes.find((n) => n.id === selected) : undefined;
});
const multipleNodesSelected = computed(() => vueFlow.getSelectedNodes.value.length > 1);
const isExecutable = computed(() => { const isExecutable = computed(() => {
if (!resolvedParameter.value) return false; if (!node.value) return false;
if (!isDisplayed.value) return false; if (!isDisplayed.value) return false;
const foreignCredentials = nodeHelpers.getForeignCredentialsIfSharingEnabled( const foreignCredentials = nodeHelpers.getForeignCredentialsIfSharingEnabled(
resolvedParameter.value.node.credentials, node.value.credentials,
);
return nodeHelpers.isNodeExecutable(
resolvedParameter.value.node,
!props.isCanvasReadOnly,
foreignCredentials,
); );
return nodeHelpers.isNodeExecutable(node.value, !props.isCanvasReadOnly, foreignCredentials);
}); });
const node = computed(() => resolvedParameter.value?.node);
const { workflowRunData } = useExecutionData({ node }); const { workflowRunData } = useExecutionData({ node });
const hasNodeRun = computed(() => { const hasNodeRun = computed(() => {
@@ -275,6 +286,11 @@ function optionSelected(command: string) {
} }
function closeFocusPanel() { function closeFocusPanel() {
if (experimentalNdvStore.isNdvInFocusPanelEnabled && resolvedParameter.value) {
focusPanelStore.unsetParameters();
return;
}
telemetry.track('User closed focus panel', { telemetry.track('User closed focus panel', {
source: 'closeIcon', source: 'closeIcon',
parameters: focusPanelStore.focusedNodeParametersInTelemetryFormat, parameters: focusPanelStore.focusedNodeParametersInTelemetryFormat,
@@ -354,6 +370,12 @@ function onResize(event: ResizeData) {
} }
const onResizeThrottle = useThrottleFn(onResize, 10); const onResizeThrottle = useThrottleFn(onResize, 10);
function onOpenNdv() {
if (node.value) {
ndvStore.setActiveNodeName(node.value.name);
}
}
</script> </script>
<template> <template>
@@ -362,14 +384,23 @@ const onResizeThrottle = useThrottleFn(onResize, 10);
:width="focusPanelWidth" :width="focusPanelWidth"
:supported-directions="['left']" :supported-directions="['left']"
:min-width="300" :min-width="300"
:max-width="1000" :max-width="experimentalNdvStore.isNdvInFocusPanelEnabled ? undefined : 1000"
:grid-size="8" :grid-size="8"
:style="{ width: `${focusPanelWidth}px`, zIndex: styles.APP_Z_INDEXES.FOCUS_PANEL }" :style="{ width: `${focusPanelWidth}px` }"
@resize="onResizeThrottle" @resize="onResizeThrottle"
> >
<div :class="$style.container"> <div :class="$style.container">
<ExperimentalFocusPanelHeader
v-if="experimentalNdvStore.isNdvInFocusPanelEnabled && node && !multipleNodesSelected"
:node="node"
:parameter="resolvedParameter?.parameter"
:is-executable="isExecutable"
@execute="onExecute"
@open-ndv="onOpenNdv"
@clear-parameter="closeFocusPanel"
/>
<div v-if="resolvedParameter" :class="$style.content"> <div v-if="resolvedParameter" :class="$style.content">
<div :class="$style.tabHeader"> <div v-if="!experimentalNdvStore.isNdvInFocusPanelEnabled" :class="$style.tabHeader">
<div :class="$style.tabHeaderText"> <div :class="$style.tabHeaderText">
<N8nText color="text-dark" size="small"> <N8nText color="text-dark" size="small">
{{ resolvedParameter.parameter.displayName }} {{ resolvedParameter.parameter.displayName }}
@@ -513,6 +544,12 @@ const onResizeThrottle = useThrottleFn(onResize, 10);
</div> </div>
</div> </div>
</div> </div>
<ExperimentalNodeDetailsDrawer
v-else-if="node && experimentalNdvStore.isNdvInFocusPanelEnabled"
:node="node"
:nodes="vueFlow.getSelectedNodes.value"
@open-ndv="onOpenNdv"
/>
<div v-else :class="[$style.content, $style.emptyContent]"> <div v-else :class="[$style.content, $style.emptyContent]">
<div :class="$style.emptyText"> <div :class="$style.emptyText">
<div :class="$style.focusParameterWrapper"> <div :class="$style.focusParameterWrapper">
@@ -552,11 +589,14 @@ const onResizeThrottle = useThrottleFn(onResize, 10);
<style lang="scss" module> <style lang="scss" module>
.wrapper { .wrapper {
display: flex; display: flex;
flex-direction: row nowrap; flex-direction: row;
flex-wrap: nowrap;
border-left: 1px solid var(--color-foreground-base); border-left: 1px solid var(--color-foreground-base);
background: var(--color-background-xlight); background: var(--color-background-xlight);
overflow-y: hidden; overflow-y: hidden;
height: 100%; height: 100%;
flex-grow: 0;
flex-shrink: 0;
} }
.container { .container {

View File

@@ -25,6 +25,7 @@ import { useAssistantStore } from '@/stores/assistant.store';
type Props = { type Props = {
nodeViewScale: number; nodeViewScale: number;
createNodeActive?: boolean; createNodeActive?: boolean;
focusPanelActive: boolean;
}; };
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
@@ -135,7 +136,14 @@ function onAskAssistantButtonClick() {
:shortcut="{ keys: ['f'], shiftKey: true }" :shortcut="{ keys: ['f'], shiftKey: true }"
placement="left" placement="left"
> >
<n8n-icon-button type="tertiary" size="large" icon="panel-right" @click="toggleFocusPanel" /> <n8n-icon-button
type="tertiary"
size="large"
icon="panel-right"
:class="focusPanelActive ? $style.activeButton : ''"
:active="focusPanelActive"
@click="toggleFocusPanel"
/>
</KeyboardShortcutTooltip> </KeyboardShortcutTooltip>
<n8n-tooltip v-if="assistantStore.canShowAssistantButtonsOnCanvas" placement="left"> <n8n-tooltip v-if="assistantStore.canShowAssistantButtonsOnCanvas" placement="left">
<template #content> {{ i18n.baseText('aiAssistant.tooltip') }}</template> <template #content> {{ i18n.baseText('aiAssistant.tooltip') }}</template>
@@ -185,4 +193,8 @@ function onAskAssistantButtonClick() {
display: block; display: block;
} }
} }
.activeButton {
background-color: var(--button-hover-background-color) !important;
}
</style> </style>

View File

@@ -152,7 +152,7 @@ describe('NodeDetailsView', () => {
test('should unregister keydown listener on unmount', async () => { test('should unregister keydown listener on unmount', async () => {
const { pinia, workflowObject, nodeName } = await createPiniaStore(false); const { pinia, workflowObject, nodeName } = await createPiniaStore(false);
const ndvStore = useNDVStore(); const ndvStore = useNDVStore(pinia);
const renderComponent = createComponentRenderer(NodeDetailsView, { const renderComponent = createComponentRenderer(NodeDetailsView, {
props: { props: {

View File

@@ -799,6 +799,7 @@ onBeforeUnmount(() => {
:executable="!readOnly" :executable="!readOnly"
:input-size="inputSize" :input-size="inputSize"
:class="$style.settings" :class="$style.settings"
is-ndv-v2
@execute="onNodeExecute" @execute="onNodeExecute"
@stop-execution="onStopExecution" @stop-execution="onStopExecution"
@activate="onWorkflowActivate" @activate="onWorkflowActivate"

View File

@@ -15,7 +15,7 @@ import type {
IUpdateInformation, IUpdateInformation,
} from '@/Interface'; } from '@/Interface';
import { BASE_NODE_SURVEY_URL, NDV_UI_OVERHAUL_EXPERIMENT } from '@/constants'; import { BASE_NODE_SURVEY_URL } from '@/constants';
import ParameterInputList from '@/components/ParameterInputList.vue'; import ParameterInputList from '@/components/ParameterInputList.vue';
import NodeCredentials from '@/components/NodeCredentials.vue'; import NodeCredentials from '@/components/NodeCredentials.vue';
@@ -50,7 +50,6 @@ import { useTelemetry } from '@/composables/useTelemetry';
import { importCurlEventBus, ndvEventBus } from '@/event-bus'; import { importCurlEventBus, ndvEventBus } from '@/event-bus';
import { ProjectTypes } from '@/types/projects.types'; import { ProjectTypes } from '@/types/projects.types';
import FreeAiCreditsCallout from '@/components/FreeAiCreditsCallout.vue'; import FreeAiCreditsCallout from '@/components/FreeAiCreditsCallout.vue';
import { usePostHog } from '@/stores/posthog.store';
import { useResizeObserver } from '@vueuse/core'; import { useResizeObserver } from '@vueuse/core';
import { useNodeSettingsParameters } from '@/composables/useNodeSettingsParameters'; import { useNodeSettingsParameters } from '@/composables/useNodeSettingsParameters';
import { N8nBlockUi, N8nIcon, N8nNotice, N8nText } from '@n8n/design-system'; import { N8nBlockUi, N8nIcon, N8nNotice, N8nText } from '@n8n/design-system';
@@ -75,12 +74,20 @@ const props = withDefaults(
subTitle?: string; subTitle?: string;
extraTabsClassName?: string; extraTabsClassName?: string;
extraParameterWrapperClassName?: string; extraParameterWrapperClassName?: string;
isNdvV2?: boolean;
hideExecute?: boolean;
hideDocs?: boolean;
hideSubConnections?: boolean;
}>(), }>(),
{ {
inputSize: 0, inputSize: 0,
activeNode: undefined, activeNode: undefined,
isEmbeddedInCanvas: false, isEmbeddedInCanvas: false,
subTitle: undefined, subTitle: undefined,
isNdvV2: false,
hideExecute: false,
hideDocs: true,
hideSubConnections: false,
}, },
); );
@@ -108,7 +115,6 @@ const ndvStore = useNDVStore();
const workflowsStore = useWorkflowsStore(); const workflowsStore = useWorkflowsStore();
const credentialsStore = useCredentialsStore(); const credentialsStore = useCredentialsStore();
const historyStore = useHistoryStore(); const historyStore = useHistoryStore();
const posthogStore = usePostHog();
const telemetry = useTelemetry(); const telemetry = useTelemetry();
const nodeHelpers = useNodeHelpers(); const nodeHelpers = useNodeHelpers();
@@ -252,13 +258,6 @@ const credentialOwnerName = computed(() => {
return credentialsStore.getCredentialOwnerName(credential); return credentialsStore.getCredentialOwnerName(credential);
}); });
const isNDVV2 = computed(() =>
posthogStore.isVariantEnabled(
NDV_UI_OVERHAUL_EXPERIMENT.name,
NDV_UI_OVERHAUL_EXPERIMENT.variant,
),
);
const featureRequestUrl = computed(() => { const featureRequestUrl = computed(() => {
if (!nodeType.value) { if (!nodeType.value) {
return ''; return '';
@@ -611,7 +610,7 @@ function handleSelectAction(params: INodeParameters) {
<slot name="actions" /> <slot name="actions" />
</template> </template>
</ExperimentalEmbeddedNdvHeader> </ExperimentalEmbeddedNdvHeader>
<div v-else-if="!isNDVV2" :class="$style.header"> <div v-else-if="!isNdvV2" :class="$style.header">
<div class="header-side-menu"> <div class="header-side-menu">
<NodeTitle <NodeTitle
v-if="node" v-if="node"
@@ -648,9 +647,10 @@ function handleSelectAction(params: INodeParameters) {
:node-name="node.name" :node-name="node.name"
:node-type="nodeType" :node-type="nodeType"
:execute-button-tooltip="executeButtonTooltip" :execute-button-tooltip="executeButtonTooltip"
:hide-execute="!isExecutable || blockUI || !node || !nodeValid" :hide-execute="props.hideExecute || !isExecutable || blockUI || !node || !nodeValid"
:disable-execute="outputPanelEditMode.enabled && !isTriggerNode" :disable-execute="outputPanelEditMode.enabled && !isTriggerNode"
:hide-tabs="!nodeValid" :hide-tabs="!nodeValid"
:hide-docs="props.hideDocs"
:push-ref="pushRef" :push-ref="pushRef"
@execute="onNodeExecute" @execute="onNodeExecute"
@stop-execution="onStopExecution" @stop-execution="onStopExecution"
@@ -666,7 +666,7 @@ function handleSelectAction(params: INodeParameters) {
:class="[ :class="[
'node-parameters-wrapper', 'node-parameters-wrapper',
shouldShowStaticScrollbar ? 'with-static-scrollbar' : '', shouldShowStaticScrollbar ? 'with-static-scrollbar' : '',
{ 'ndv-v2': isNDVV2 }, { 'ndv-v2': isNdvV2 },
extraParameterWrapperClassName ?? '', extraParameterWrapperClassName ?? '',
]" ]"
data-test-id="node-parameters" data-test-id="node-parameters"
@@ -782,7 +782,7 @@ function handleSelectAction(params: INodeParameters) {
</div> </div>
</div> </div>
<div <div
v-if="isNDVV2 && featureRequestUrl && !isEmbeddedInCanvas" v-if="isNdvV2 && featureRequestUrl && !isEmbeddedInCanvas"
:class="$style.featureRequest" :class="$style.featureRequest"
> >
<a target="_blank" @click="onFeatureRequestClick"> <a target="_blank" @click="onFeatureRequestClick">
@@ -792,7 +792,7 @@ function handleSelectAction(params: INodeParameters) {
</div> </div>
</div> </div>
<NDVSubConnections <NDVSubConnections
v-if="node && !props.isEmbeddedInCanvas" v-if="node && !hideSubConnections"
ref="subConnections" ref="subConnections"
:root-node="node" :root-node="node"
@switch-selected-node="onSwitchSelectedNode" @switch-selected-node="onSwitchSelectedNode"

View File

@@ -8,6 +8,7 @@ import type { NodeSettingsTab } from '@/types/nodeSettings';
type Props = { type Props = {
nodeName: string; nodeName: string;
hideExecute: boolean; hideExecute: boolean;
hideDocs: boolean;
hideTabs: boolean; hideTabs: boolean;
disableExecute: boolean; disableExecute: boolean;
executeButtonTooltip: string; executeButtonTooltip: string;
@@ -30,7 +31,7 @@ const emit = defineEmits<{
<div :class="$style.header"> <div :class="$style.header">
<NodeSettingsTabs <NodeSettingsTabs
v-if="!hideTabs" v-if="!hideTabs"
hide-docs :hide-docs="hideDocs"
:model-value="selectedTab" :model-value="selectedTab"
:node-type="nodeType" :node-type="nodeType"
:push-ref="pushRef" :push-ref="pushRef"
@@ -61,11 +62,14 @@ const emit = defineEmits<{
display: flex; display: flex;
align-items: center; align-items: center;
min-height: 40px; min-height: 40px;
padding-right: var(--spacing-s);
border-bottom: var(--border-base); border-bottom: var(--border-base);
} }
.execute {
margin-right: var(--spacing-s);
}
.tabs { .tabs {
align-self: flex-end; align-self: flex-end;
} }

View File

@@ -10,6 +10,7 @@ import { computed } from 'vue';
import { useNDVStore } from '@/stores/ndv.store'; import { useNDVStore } from '@/stores/ndv.store';
import { AI_TRANSFORM_NODE_TYPE } from '@/constants'; import { AI_TRANSFORM_NODE_TYPE } from '@/constants';
import { getParameterTypeOption } from '@/utils/nodeSettingsUtils'; import { getParameterTypeOption } from '@/utils/nodeSettingsUtils';
import { useIsInExperimentalNdv } from '@/components/canvas/experimental/composables/useIsInExperimentalNdv';
interface Props { interface Props {
parameter: INodeProperties; parameter: INodeProperties;
@@ -51,13 +52,14 @@ const isHtmlEditor = computed(
const shouldShowExpressionSelector = computed( const shouldShowExpressionSelector = computed(
() => !props.parameter.noDataExpression && props.showExpressionSelector && !props.isReadOnly, () => !props.parameter.noDataExpression && props.showExpressionSelector && !props.isReadOnly,
); );
const isInEmbeddedNdv = useIsInExperimentalNdv();
const canBeOpenedInFocusPanel = computed( const canBeOpenedInFocusPanel = computed(
() => () =>
!props.parameter.isNodeSetting && !props.parameter.isNodeSetting &&
!props.isReadOnly && !props.isReadOnly &&
!props.isContentOverridden && !props.isContentOverridden &&
activeNode.value && // checking that it's inside ndv (activeNode.value || isInEmbeddedNdv.value) && // checking that it's inside ndv
(props.parameter.type === 'string' || props.parameter.type === 'json'), (props.parameter.type === 'string' || props.parameter.type === 'json'),
); );

View File

@@ -922,7 +922,7 @@ provide(CanvasKey, {
snap-to-grid snap-to-grid
:snap-grid="[GRID_SIZE, GRID_SIZE]" :snap-grid="[GRID_SIZE, GRID_SIZE]"
:min-zoom="0" :min-zoom="0"
:max-zoom="experimentalNdvStore.isEnabled ? experimentalNdvStore.maxCanvasZoom : 4" :max-zoom="experimentalNdvStore.isZoomedViewEnabled ? experimentalNdvStore.maxCanvasZoom : 4"
:selection-key-code="selectionKeyCode" :selection-key-code="selectionKeyCode"
:zoom-activation-key-code="panningKeyCode" :zoom-activation-key-code="panningKeyCode"
:pan-activation-key-code="panningKeyCode" :pan-activation-key-code="panningKeyCode"

View File

@@ -9,8 +9,6 @@ import { createEventBus } from '@n8n/utils/event-bus';
import type { CanvasEventBusEvents } from '@/types'; import type { CanvasEventBusEvents } from '@/types';
import { useVueFlow } from '@vue-flow/core'; import { useVueFlow } from '@vue-flow/core';
import { throttledRef } from '@vueuse/core'; import { throttledRef } from '@vueuse/core';
import { useSettingsStore } from '@/stores/settings.store';
import ExperimentalNodeDetailsDrawer from './experimental/components/ExperimentalNodeDetailsDrawer.vue';
defineOptions({ defineOptions({
inheritAttrs: false, inheritAttrs: false,
@@ -36,9 +34,8 @@ const props = withDefaults(
); );
const $style = useCssModule(); const $style = useCssModule();
const settingsStore = useSettingsStore();
const { onNodesInitialized, getSelectedNodes } = useVueFlow(props.id); const { onNodesInitialized } = useVueFlow(props.id);
const workflow = toRef(props, 'workflow'); const workflow = toRef(props, 'workflow');
const workflowObject = toRef(props, 'workflowObject'); const workflowObject = toRef(props, 'workflowObject');
@@ -83,10 +80,6 @@ const mappedConnectionsThrottled = throttledRef(mappedConnections, 200);
/> />
</div> </div>
<slot /> <slot />
<ExperimentalNodeDetailsDrawer
v-if="settingsStore.experimental__dockedNodeSettingsEnabled && !props.readOnly"
:selected-nodes="getSelectedNodes"
/>
</div> </div>
</template> </template>

View File

@@ -33,7 +33,7 @@ const experimentalNdvStore = useExperimentalNdvStore();
const isExperimentalNdvActive = computed(() => experimentalNdvStore.isActive(props.zoom)); const isExperimentalNdvActive = computed(() => experimentalNdvStore.isActive(props.zoom));
const isToggleZoomVisible = computed(() => experimentalNdvStore.isEnabled); const isToggleZoomVisible = computed(() => experimentalNdvStore.isZoomedViewEnabled);
const isResetZoomVisible = computed(() => !isToggleZoomVisible.value && props.zoom !== 1); const isResetZoomVisible = computed(() => !isToggleZoomVisible.value && props.zoom !== 1);

View File

@@ -66,7 +66,7 @@ const isDisableNodeVisible = computed(() => {
const isDeleteNodeVisible = computed(() => !props.readOnly); const isDeleteNodeVisible = computed(() => !props.readOnly);
const isFocusNodeVisible = computed(() => experimentalNdvStore.isEnabled); const isFocusNodeVisible = computed(() => experimentalNdvStore.isZoomedViewEnabled);
const isStickyNoteChangeColorVisible = computed( const isStickyNoteChangeColorVisible = computed(
() => !props.readOnly && render.value.type === CanvasNodeRenderType.StickyNote, () => !props.readOnly && render.value.type === CanvasNodeRenderType.StickyNote,

View File

@@ -8,10 +8,11 @@ import { useUIStore } from '@/stores/ui.store';
import { useWorkflowsStore } from '@/stores/workflows.store'; import { useWorkflowsStore } from '@/stores/workflows.store';
import { computed } from 'vue'; import { computed } from 'vue';
const { nodeId, isReadOnly, subTitle } = defineProps<{ const { nodeId, isReadOnly, subTitle, isEmbeddedInCanvas } = defineProps<{
nodeId: string; nodeId: string;
isReadOnly?: boolean; isReadOnly?: boolean;
subTitle?: string; subTitle?: string;
isEmbeddedInCanvas?: boolean;
}>(); }>();
defineSlots<{ actions?: {} }>(); defineSlots<{ actions?: {} }>();
@@ -75,10 +76,14 @@ function handleCaptureWheelEvent(event: WheelEvent) {
:read-only="isReadOnly" :read-only="isReadOnly"
:block-u-i="blockUi" :block-u-i="blockUi"
:executable="!isReadOnly" :executable="!isReadOnly"
is-embedded-in-canvas :is-embedded-in-canvas="isEmbeddedInCanvas"
:sub-title="subTitle" :sub-title="subTitle"
extra-tabs-class-name="nodrag" extra-tabs-class-name="nodrag"
extra-parameter-wrapper-class-name="nodrag" extra-parameter-wrapper-class-name="nodrag"
is-ndv-v2
hide-execute
:hide-docs="false"
hide-sub-connections
@value-changed="handleValueChanged" @value-changed="handleValueChanged"
@capture-wheel-body="handleCaptureWheelEvent" @capture-wheel-body="handleCaptureWheelEvent"
@dblclick-header="handleDoubleClickHeader" @dblclick-header="handleDoubleClickHeader"

View File

@@ -41,7 +41,7 @@ defineExpose({
:popper-class="$style.component" :popper-class="$style.component"
:width="360" :width="360"
:offset="8" :offset="8"
append-to="#canvas" append-to="body"
:popper-options="{ :popper-options="{
modifiers: [{ name: 'flip', enabled: false }], modifiers: [{ name: 'flip', enabled: false }],
}" }"

View File

@@ -1,10 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { ExpressionLocalResolveContextSymbol } from '@/constants'; import { ExpressionLocalResolveContextSymbol } from '@/constants';
import { useEnvironmentsStore } from '@/stores/environments.ee.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 { useWorkflowsStore } from '@/stores/workflows.store'; import { useWorkflowsStore } from '@/stores/workflows.store';
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';
@@ -12,11 +10,11 @@ import { computed, provide, ref } 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 type { Workflow } from 'n8n-workflow';
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'; import { useCanvas } from '@/composables/useCanvas';
import { useExpressionResolveCtx } from '@/components/canvas/experimental/composables/useExpressionResolveCtx';
const { nodeId, isReadOnly } = defineProps<{ const { nodeId, isReadOnly } = defineProps<{
nodeId: string; nodeId: string;
@@ -56,54 +54,10 @@ const subTitle = computed(() =>
? getNodeSubTitleText(node.value, nodeType.value, !isExpanded.value, i18n) ? getNodeSubTitleText(node.value, nodeType.value, !isExpanded.value, i18n)
: undefined, : undefined,
); );
const expressionResolveCtx = computed<ExpressionLocalResolveContext | undefined>(() => {
if (!node.value) {
return undefined;
}
const runIndex = 0; // not changeable for now
const execution = workflowsStore.workflowExecutionData;
const nodeName = node.value.name;
function findInputNode(): ExpressionLocalResolveContext['inputNode'] {
const taskData = (execution?.data?.resultData.runData[nodeName] ?? [])[runIndex];
const source = taskData?.source[0];
if (source) {
return {
name: source.previousNode,
branchIndex: source.previousNodeOutput ?? 0,
runIndex: source.previousNodeRun ?? 0,
};
}
const inputs = workflowObject.value.getParentNodesByDepth(nodeName, 1);
if (inputs.length > 0) {
return {
name: inputs[0].name,
branchIndex: inputs[0].indicies[0] ?? 0,
runIndex: 0,
};
}
return undefined;
}
return {
localResolve: true,
envVars: useEnvironmentsStore().variablesAsObject,
workflow: workflowObject.value,
execution,
nodeName,
additionalKeys: {},
inputNode: findInputNode(),
connections: workflowsStore.connectionsBySourceNode,
};
});
const maxHeightOnFocus = computed(() => vf.dimensions.value.height * 0.8); const maxHeightOnFocus = computed(() => vf.dimensions.value.height * 0.8);
const workflowObject = computed(() => workflowsStore.workflowObject as Workflow);
const expressionResolveCtx = useExpressionResolveCtx(node);
function handleToggleExpand() { function handleToggleExpand() {
experimentalNdvStore.setNodeExpanded(nodeId); experimentalNdvStore.setNodeExpanded(nodeId);
@@ -143,6 +97,7 @@ watchOnce(isVisible, (visible) => {
:is-read-only="isReadOnly" :is-read-only="isReadOnly"
:sub-title="subTitle" :sub-title="subTitle"
:input-node-name="expressionResolveCtx?.inputNode?.name" :input-node-name="expressionResolveCtx?.inputNode?.name"
is-embedded-in-canvas
> >
<template #actions> <template #actions>
<ExperimentalEmbeddedNdvActions <ExperimentalEmbeddedNdvActions

View File

@@ -0,0 +1,86 @@
<script lang="ts" setup>
import NodeExecuteButton from '@/components/NodeExecuteButton.vue';
import NodeIcon from '@/components/NodeIcon.vue';
import { type INodeUi } from '@/Interface';
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import { N8nIconButton, N8nText } from '@n8n/design-system';
import { type INodeProperties } from 'n8n-workflow';
import { computed } from 'vue';
const { node, parameter, isExecutable } = defineProps<{
node: INodeUi;
parameter?: INodeProperties;
isExecutable: boolean;
}>();
const nodeTypesStore = useNodeTypesStore();
const nodeType = computed(() => nodeTypesStore.getNodeType(node.type, node.typeVersion));
const emit = defineEmits<{
execute: [];
openNdv: [];
clearParameter: [];
}>();
</script>
<template>
<N8nText tag="div" size="small" bold :class="$style.component">
<NodeIcon :node-type="nodeType" :size="16" />
<div :class="$style.breadcrumbs">
<template v-if="parameter">
<N8nText size="small" color="text-base" bold>
{{ node.name }}
</N8nText>
<N8nText size="small" color="text-light">/</N8nText>
{{ parameter.displayName }}
</template>
<template v-else>{{ node.name }}</template>
</div>
<N8nIconButton
v-if="parameter"
icon="x"
size="small"
type="tertiary"
text
@click="emit('clearParameter')"
/>
<N8nIconButton
v-else
icon="maximize-2"
size="small"
type="tertiary"
text
@click="emit('openNdv')"
/>
<NodeExecuteButton
v-if="isExecutable"
data-test-id="node-execute-button"
:node-name="node.name"
:tooltip="`Execute ${node.name}`"
size="small"
icon="play"
:square="true"
:hide-label="true"
telemetry-source="focus"
@execute="emit('execute')"
/>
</N8nText>
</template>
<style lang="scss" module>
.component {
display: flex;
align-items: center;
padding: var(--spacing-2xs);
gap: var(--spacing-2xs);
border-bottom: var(--border-base);
}
.breadcrumbs {
display: flex;
align-items: center;
gap: var(--spacing-4xs);
flex-grow: 1;
flex-shrink: 1;
}
</style>

View File

@@ -1,50 +1,25 @@
<script setup lang="ts"> <script setup lang="ts">
import { type CanvasNode } from '@/types'; import { useExpressionResolveCtx } from '@/components/canvas/experimental/composables/useExpressionResolveCtx';
import ExperimentalCanvasNodeSettings from './ExperimentalCanvasNodeSettings.vue'; import { ExpressionLocalResolveContextSymbol } from '@/constants';
import { type INodeUi } from '@/Interface';
import { N8nText } from '@n8n/design-system'; import { N8nText } from '@n8n/design-system';
import { computed, watch, ref } from 'vue'; import { type GraphNode } from '@vue-flow/core';
import { useNDVStore } from '@/stores/ndv.store'; import { computed, provide } from 'vue';
import ExperimentalCanvasNodeSettings from './ExperimentalCanvasNodeSettings.vue';
const { selectedNodes } = defineProps<{ selectedNodes: CanvasNode[] }>(); const { node, nodes } = defineProps<{ node: INodeUi; nodes: GraphNode[] }>();
const content = computed(() => const emit = defineEmits<{ openNdv: [] }>();
selectedNodes.length > 1
? `${selectedNodes.length} nodes selected`
: selectedNodes.length > 0
? selectedNodes[0]
: undefined,
);
const lastContent = ref<string | CanvasNode | undefined>();
const { setActiveNodeName } = useNDVStore();
function handleOpenNdv() { const expressionResolveCtx = useExpressionResolveCtx(computed(() => node));
if (typeof content.value === 'object' && content.value.data) {
setActiveNodeName(content.value.data.name);
}
}
// Sync lastContent to be "last defined content" (for drawer animation) provide(ExpressionLocalResolveContextSymbol, expressionResolveCtx);
watch(
content,
(newContent) => {
if (newContent !== undefined) {
lastContent.value = newContent;
}
},
{ immediate: true },
);
</script> </script>
<template> <template>
<div :class="[$style.component, content === undefined ? $style.closed : '']"> <div :class="$style.component">
<N8nText v-if="typeof lastContent === 'string'" color="text-base"> <N8nText v-if="nodes.length > 1" color="text-base"> {{ nodes.length }} nodes selected </N8nText>
{{ lastContent }} <ExperimentalCanvasNodeSettings v-else-if="node" :key="node.id" :node-id="node.id">
</N8nText>
<ExperimentalCanvasNodeSettings
v-else-if="lastContent !== undefined"
:key="lastContent.id"
:node-id="lastContent.id"
>
<template #actions> <template #actions>
<N8nIconButton <N8nIconButton
icon="maximize-2" icon="maximize-2"
@@ -53,7 +28,7 @@ watch(
size="mini" size="mini"
icon-size="large" icon-size="large"
aria-label="Expand" aria-label="Expand"
@click="handleOpenNdv" @click="emit('openNdv')"
/> />
</template> </template>
</ExperimentalCanvasNodeSettings> </ExperimentalCanvasNodeSettings>
@@ -62,22 +37,10 @@ watch(
<style lang="scss" module> <style lang="scss" module>
.component { .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; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
transition: transform 0.2s ease; height: 100%;
overflow: auto;
&.closed {
transform: translateX(100%);
}
} }
</style> </style>

View File

@@ -0,0 +1,58 @@
import type { INodeUi } from '@/Interface';
import useEnvironmentsStore from '@/stores/environments.ee.store';
import { useWorkflowsStore } from '@/stores/workflows.store';
import type { ExpressionLocalResolveContext } from '@/types/expressions';
import type { Workflow } from 'n8n-workflow';
import { computed, type ComputedRef } from 'vue';
export function useExpressionResolveCtx(node: ComputedRef<INodeUi | null | undefined>) {
const environmentsStore = useEnvironmentsStore();
const workflowsStore = useWorkflowsStore();
const workflowObject = computed(() => workflowsStore.workflowObject as Workflow);
return computed<ExpressionLocalResolveContext | undefined>(() => {
if (!node.value) {
return undefined;
}
const runIndex = 0; // not changeable for now
const execution = workflowsStore.workflowExecutionData;
const nodeName = node.value.name;
function findInputNode(): ExpressionLocalResolveContext['inputNode'] {
const taskData = (execution?.data?.resultData.runData[nodeName] ?? [])[runIndex];
const source = taskData?.source[0];
if (source) {
return {
name: source.previousNode,
branchIndex: source.previousNodeOutput ?? 0,
runIndex: source.previousNodeRun ?? 0,
};
}
const inputs = workflowObject.value.getParentNodesByDepth(nodeName, 1);
if (inputs.length > 0) {
return {
name: inputs[0].name,
branchIndex: inputs[0].indicies[0] ?? 0,
runIndex: 0,
};
}
return undefined;
}
return {
localResolve: true,
envVars: environmentsStore.variablesAsObject,
workflow: workflowObject.value,
execution,
nodeName,
additionalKeys: {},
inputNode: findInputNode(),
connections: workflowsStore.connectionsBySourceNode,
};
});
}

View File

@@ -0,0 +1,9 @@
import { ExpressionLocalResolveContextSymbol } from '@/constants';
import { computed, inject } from 'vue';
export function useIsInExperimentalNdv() {
const expressionLocalResolveCtx = inject(ExpressionLocalResolveContextSymbol, undefined);
// This condition is correct as long as ExpressionLocalResolveContext is used only in experimental NDV
return computed(() => expressionLocalResolveCtx?.value !== undefined);
}

View File

@@ -12,17 +12,22 @@ import {
} from '@vue-flow/core'; } from '@vue-flow/core';
import { CanvasNodeRenderType, type CanvasNodeData } from '@/types'; import { CanvasNodeRenderType, type CanvasNodeData } from '@/types';
import { usePostHog } from '@/stores/posthog.store'; import { usePostHog } from '@/stores/posthog.store';
import { CANVAS_ZOOMED_VIEW_EXPERIMENT } from '@/constants'; import { CANVAS_ZOOMED_VIEW_EXPERIMENT, NDV_IN_FOCUS_PANEL_EXPERIMENT } from '@/constants';
export const useExperimentalNdvStore = defineStore('experimentalNdv', () => { export const useExperimentalNdvStore = defineStore('experimentalNdv', () => {
const workflowStore = useWorkflowsStore(); const workflowStore = useWorkflowsStore();
const postHogStore = usePostHog(); const postHogStore = usePostHog();
const isEnabled = computed( const isZoomedViewEnabled = computed(
() => () =>
postHogStore.getVariant(CANVAS_ZOOMED_VIEW_EXPERIMENT.name) === postHogStore.getVariant(CANVAS_ZOOMED_VIEW_EXPERIMENT.name) ===
CANVAS_ZOOMED_VIEW_EXPERIMENT.variant, CANVAS_ZOOMED_VIEW_EXPERIMENT.variant,
); );
const maxCanvasZoom = computed(() => (isEnabled.value ? 2 : 4)); const isNdvInFocusPanelEnabled = computed(
() =>
postHogStore.getVariant(NDV_IN_FOCUS_PANEL_EXPERIMENT.name) ===
NDV_IN_FOCUS_PANEL_EXPERIMENT.variant,
);
const maxCanvasZoom = computed(() => (isZoomedViewEnabled.value ? 2 : 4));
const previousViewport = ref<ViewportTransform>(); const previousViewport = ref<ViewportTransform>();
const collapsedNodes = shallowRef<Partial<Record<string, boolean>>>({}); const collapsedNodes = shallowRef<Partial<Record<string, boolean>>>({});
@@ -50,7 +55,7 @@ export const useExperimentalNdvStore = defineStore('experimentalNdv', () => {
} }
function isActive(canvasZoom: number) { function isActive(canvasZoom: number) {
return isEnabled.value && Math.abs(canvasZoom - maxCanvasZoom.value) < 0.000001; return isZoomedViewEnabled.value && Math.abs(canvasZoom - maxCanvasZoom.value) < 0.000001;
} }
function setNodeNameToBeFocused(nodeName: string) { function setNodeNameToBeFocused(nodeName: string) {
@@ -139,7 +144,8 @@ export const useExperimentalNdvStore = defineStore('experimentalNdv', () => {
} }
return { return {
isEnabled, isZoomedViewEnabled,
isNdvInFocusPanelEnabled,
maxCanvasZoom, maxCanvasZoom,
previousZoom: computed(() => previousViewport.value), previousZoom: computed(() => previousViewport.value),
collapsedNodes: computed(() => collapsedNodes.value), collapsedNodes: computed(() => collapsedNodes.value),

View File

@@ -113,6 +113,7 @@ import { isChatNode } from '@/utils/aiUtils';
import cloneDeep from 'lodash/cloneDeep'; import cloneDeep from 'lodash/cloneDeep';
import uniq from 'lodash/uniq'; import uniq from 'lodash/uniq';
import { useExperimentalNdvStore } from '@/components/canvas/experimental/experimentalNdv.store'; import { useExperimentalNdvStore } from '@/components/canvas/experimental/experimentalNdv.store';
import { useFocusPanelStore } from '@/stores/focusPanel.store';
type AddNodeData = Partial<INodeUi> & { type AddNodeData = Partial<INodeUi> & {
type: string; type: string;
@@ -158,6 +159,7 @@ export function useCanvasOperations() {
const projectsStore = useProjectsStore(); const projectsStore = useProjectsStore();
const logsStore = useLogsStore(); const logsStore = useLogsStore();
const experimentalNdvStore = useExperimentalNdvStore(); const experimentalNdvStore = useExperimentalNdvStore();
const focusPanelStore = useFocusPanelStore();
const i18n = useI18n(); const i18n = useI18n();
const toast = useToast(); const toast = useToast();
@@ -793,7 +795,13 @@ export function useCanvasOperations() {
void externalHooks.run('nodeView.addNodeButton', { nodeTypeName: nodeData.type }); void externalHooks.run('nodeView.addNodeButton', { nodeTypeName: nodeData.type });
if (options.openNDV && !preventOpeningNDV) { if (options.openNDV && !preventOpeningNDV) {
if (experimentalNdvStore.isEnabled) { if (
experimentalNdvStore.isNdvInFocusPanelEnabled &&
focusPanelStore.focusPanelActive &&
focusPanelStore.focusedNodeParameters.length === 0
) {
// Do nothing. The added node get selected and the details are shown in the focus panel
} else if (experimentalNdvStore.isZoomedViewEnabled) {
experimentalNdvStore.setNodeNameToBeFocused(nodeData.name); experimentalNdvStore.setNodeNameToBeFocused(nodeData.name);
} else { } else {
ndvStore.setActiveNodeName(nodeData.name); ndvStore.setActiveNodeName(nodeData.name);

View File

@@ -7,7 +7,6 @@ const APP_Z_INDEXES = {
APP_SIDEBAR: 999, APP_SIDEBAR: 999,
CANVAS_SELECT_BOX: 100, CANVAS_SELECT_BOX: 100,
TOP_BANNERS: 999, TOP_BANNERS: 999,
FOCUS_PANEL: 1600,
NODE_CREATOR: 1700, NODE_CREATOR: 1700,
NDV: 1800, NDV: 1800,
MODALS: 2000, MODALS: 2000,

View File

@@ -506,8 +506,6 @@ export const LOCAL_STORAGE_LOGS_SYNC_SELECTION = 'N8N_LOGS_SYNC_SELECTION_ENABLE
export const LOCAL_STORAGE_LOGS_PANEL_DETAILS_PANEL = 'N8N_LOGS_DETAILS_PANEL'; export const LOCAL_STORAGE_LOGS_PANEL_DETAILS_PANEL = 'N8N_LOGS_DETAILS_PANEL';
export const LOCAL_STORAGE_LOGS_PANEL_DETAILS_PANEL_SUB_NODE = 'N8N_LOGS_DETAILS_PANEL_SUB_NODE'; export const LOCAL_STORAGE_LOGS_PANEL_DETAILS_PANEL_SUB_NODE = 'N8N_LOGS_DETAILS_PANEL_SUB_NODE';
export const LOCAL_STORAGE_WORKFLOW_LIST_PREFERENCES_KEY = 'N8N_WORKFLOWS_LIST_PREFERENCES'; export const LOCAL_STORAGE_WORKFLOW_LIST_PREFERENCES_KEY = 'N8N_WORKFLOWS_LIST_PREFERENCES';
export const LOCAL_STORAGE_EXPERIMENTAL_DOCKED_NODE_SETTINGS =
'N8N_EXPERIMENTAL_DOCKED_NODE_SETTINGS';
export const LOCAL_STORAGE_READ_WHATS_NEW_ARTICLES = 'N8N_READ_WHATS_NEW_ARTICLES'; export const LOCAL_STORAGE_READ_WHATS_NEW_ARTICLES = 'N8N_READ_WHATS_NEW_ARTICLES';
export const LOCAL_STORAGE_DISMISSED_WHATS_NEW_CALLOUT = 'N8N_DISMISSED_WHATS_NEW_CALLOUT'; export const LOCAL_STORAGE_DISMISSED_WHATS_NEW_CALLOUT = 'N8N_DISMISSED_WHATS_NEW_CALLOUT';
export const LOCAL_STORAGE_NDV_PANEL_WIDTH = 'N8N_NDV_PANEL_WIDTH'; export const LOCAL_STORAGE_NDV_PANEL_WIDTH = 'N8N_NDV_PANEL_WIDTH';
@@ -756,6 +754,12 @@ export const CANVAS_ZOOMED_VIEW_EXPERIMENT = {
variant: 'variant', variant: 'variant',
}; };
export const NDV_IN_FOCUS_PANEL_EXPERIMENT = {
name: 'ndv_in_focus_panel',
control: 'control',
variant: 'variant',
};
export const NDV_UI_OVERHAUL_EXPERIMENT = { export const NDV_UI_OVERHAUL_EXPERIMENT = {
name: '029_ndv_ui_overhaul', name: '029_ndv_ui_overhaul',
control: 'control', control: 'control',

View File

@@ -147,6 +147,10 @@ export const useFocusPanelStore = defineStore(STORES.FOCUS_PANEL, () => {
_setOptions({ isActive: false }); _setOptions({ isActive: false });
} }
function unsetParameters() {
_setOptions({ parameters: [] });
}
function toggleFocusPanel() { function toggleFocusPanel() {
_setOptions({ isActive: !focusPanelActive.value }); _setOptions({ isActive: !focusPanelActive.value });
} }
@@ -193,5 +197,6 @@ export const useFocusPanelStore = defineStore(STORES.FOCUS_PANEL, () => {
toggleFocusPanel, toggleFocusPanel,
onNewWorkflowSave, onNewWorkflowSave,
updateWidth, updateWidth,
unsetParameters,
}; };
}); });

View File

@@ -10,10 +10,7 @@ import * as eventsApi from '@n8n/rest-api-client/api/events';
import * as settingsApi from '@n8n/rest-api-client/api/settings'; import * as settingsApi from '@n8n/rest-api-client/api/settings';
import * as moduleSettingsApi from '@n8n/rest-api-client/api/module-settings'; import * as moduleSettingsApi from '@n8n/rest-api-client/api/module-settings';
import { testHealthEndpoint } from '@n8n/rest-api-client/api/templates'; import { testHealthEndpoint } from '@n8n/rest-api-client/api/templates';
import { import { INSECURE_CONNECTION_WARNING } from '@/constants';
INSECURE_CONNECTION_WARNING,
LOCAL_STORAGE_EXPERIMENTAL_DOCKED_NODE_SETTINGS,
} from '@/constants';
import { STORES } from '@n8n/stores'; import { STORES } from '@n8n/stores';
import { UserManagementAuthenticationMethod } from '@/Interface'; import { UserManagementAuthenticationMethod } from '@/Interface';
import type { IDataObject, WorkflowSettings } from 'n8n-workflow'; import type { IDataObject, WorkflowSettings } from 'n8n-workflow';
@@ -315,15 +312,6 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, () => {
moduleSettings.value = fetched; moduleSettings.value = fetched;
}; };
/**
* (Experimental) If set to true, show node settings for a selected node in docked pane
*/
const experimental__dockedNodeSettingsEnabled = useLocalStorage(
LOCAL_STORAGE_EXPERIMENTAL_DOCKED_NODE_SETTINGS,
false,
{ writeDefaults: false },
);
return { return {
settings, settings,
userManagement, userManagement,
@@ -380,7 +368,6 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, () => {
isAskAiEnabled, isAskAiEnabled,
isAiCreditsEnabled, isAiCreditsEnabled,
aiCreditsQuota, aiCreditsQuota,
experimental__dockedNodeSettingsEnabled,
partialExecutionVersion, partialExecutionVersion,
reset, reset,
getTimezones, getTimezones,

View File

@@ -2160,6 +2160,7 @@ onBeforeUnmount(() => {
v-if="!isCanvasReadOnly" v-if="!isCanvasReadOnly"
:create-node-active="nodeCreatorStore.isCreateNodeActive" :create-node-active="nodeCreatorStore.isCreateNodeActive"
:node-view-scale="viewportTransform.zoom" :node-view-scale="viewportTransform.zoom"
:focus-panel-active="focusPanelStore.focusPanelActive"
@toggle-node-creator="onToggleNodeCreator" @toggle-node-creator="onToggleNodeCreator"
@add-nodes="onAddNodesAndConnections" @add-nodes="onAddNodesAndConnections"
/> />