mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 17:46:45 +00:00
feat(editor): Merge experimental params pane into focus pane (no-changelog) (#18337)
This commit is contained in:
@@ -16,7 +16,8 @@ import { startCompletion } from '@codemirror/autocomplete';
|
||||
import type { EditorState, SelectionRange } from '@codemirror/state';
|
||||
import type { IDataObject } from 'n8n-workflow';
|
||||
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 segments = ref<Segment[]>([]);
|
||||
@@ -56,8 +57,7 @@ const ndvStore = useNDVStore();
|
||||
const workflowsStore = useWorkflowsStore();
|
||||
|
||||
const canvas = inject(CanvasKey, undefined);
|
||||
const expressionLocalResolveCtx = inject(ExpressionLocalResolveContextSymbol, undefined);
|
||||
const isInExperimentalNdv = computed(() => expressionLocalResolveCtx?.value !== undefined);
|
||||
const isInExperimentalNdv = useIsInExperimentalNdv();
|
||||
|
||||
const isDragging = computed(() => ndvStore.isDraggableDragging);
|
||||
const isOutputPopoverVisible = computed(
|
||||
@@ -236,7 +236,7 @@ defineExpose({ focus, select });
|
||||
:segments="segments"
|
||||
:is-read-only="isReadOnly"
|
||||
:virtual-ref="container"
|
||||
:append-to="isInExperimentalNdv ? '#canvas' : undefined"
|
||||
:append-to="isInExperimentalNdv ? 'body' : undefined"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -26,12 +26,16 @@ import {
|
||||
import { useEnvironmentsStore } from '@/stores/environments.ee.store';
|
||||
import { htmlEditorEventBus } from '@/event-bus';
|
||||
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 { useThrottleFn } from '@vueuse/core';
|
||||
import { useStyles } from '@/composables/useStyles';
|
||||
import { useExecutionData } from '@/composables/useExecutionData';
|
||||
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' });
|
||||
|
||||
@@ -56,8 +60,10 @@ const nodeTypesStore = useNodeTypesStore();
|
||||
const telemetry = useTelemetry();
|
||||
const nodeSettingsParameters = useNodeSettingsParameters();
|
||||
const environmentsStore = useEnvironmentsStore();
|
||||
const experimentalNdvStore = useExperimentalNdvStore();
|
||||
const ndvStore = useNDVStore();
|
||||
const deviceSupport = useDeviceSupport();
|
||||
const styles = useStyles();
|
||||
const vueFlow = useVueFlow(workflowsStore.workflowId);
|
||||
|
||||
const focusedNodeParameter = computed(() => focusPanelStore.focusedNodeParameters[0]);
|
||||
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(() => {
|
||||
if (!resolvedParameter.value) return false;
|
||||
if (!node.value) return false;
|
||||
|
||||
if (!isDisplayed.value) return false;
|
||||
|
||||
const foreignCredentials = nodeHelpers.getForeignCredentialsIfSharingEnabled(
|
||||
resolvedParameter.value.node.credentials,
|
||||
);
|
||||
return nodeHelpers.isNodeExecutable(
|
||||
resolvedParameter.value.node,
|
||||
!props.isCanvasReadOnly,
|
||||
foreignCredentials,
|
||||
node.value.credentials,
|
||||
);
|
||||
return nodeHelpers.isNodeExecutable(node.value, !props.isCanvasReadOnly, foreignCredentials);
|
||||
});
|
||||
|
||||
const node = computed(() => resolvedParameter.value?.node);
|
||||
|
||||
const { workflowRunData } = useExecutionData({ node });
|
||||
|
||||
const hasNodeRun = computed(() => {
|
||||
@@ -275,6 +286,11 @@ function optionSelected(command: string) {
|
||||
}
|
||||
|
||||
function closeFocusPanel() {
|
||||
if (experimentalNdvStore.isNdvInFocusPanelEnabled && resolvedParameter.value) {
|
||||
focusPanelStore.unsetParameters();
|
||||
return;
|
||||
}
|
||||
|
||||
telemetry.track('User closed focus panel', {
|
||||
source: 'closeIcon',
|
||||
parameters: focusPanelStore.focusedNodeParametersInTelemetryFormat,
|
||||
@@ -354,6 +370,12 @@ function onResize(event: ResizeData) {
|
||||
}
|
||||
|
||||
const onResizeThrottle = useThrottleFn(onResize, 10);
|
||||
|
||||
function onOpenNdv() {
|
||||
if (node.value) {
|
||||
ndvStore.setActiveNodeName(node.value.name);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -362,14 +384,23 @@ const onResizeThrottle = useThrottleFn(onResize, 10);
|
||||
:width="focusPanelWidth"
|
||||
:supported-directions="['left']"
|
||||
:min-width="300"
|
||||
:max-width="1000"
|
||||
:max-width="experimentalNdvStore.isNdvInFocusPanelEnabled ? undefined : 1000"
|
||||
:grid-size="8"
|
||||
:style="{ width: `${focusPanelWidth}px`, zIndex: styles.APP_Z_INDEXES.FOCUS_PANEL }"
|
||||
:style="{ width: `${focusPanelWidth}px` }"
|
||||
@resize="onResizeThrottle"
|
||||
>
|
||||
<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 :class="$style.tabHeader">
|
||||
<div v-if="!experimentalNdvStore.isNdvInFocusPanelEnabled" :class="$style.tabHeader">
|
||||
<div :class="$style.tabHeaderText">
|
||||
<N8nText color="text-dark" size="small">
|
||||
{{ resolvedParameter.parameter.displayName }}
|
||||
@@ -513,6 +544,12 @@ const onResizeThrottle = useThrottleFn(onResize, 10);
|
||||
</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 :class="$style.emptyText">
|
||||
<div :class="$style.focusParameterWrapper">
|
||||
@@ -552,11 +589,14 @@ const onResizeThrottle = useThrottleFn(onResize, 10);
|
||||
<style lang="scss" module>
|
||||
.wrapper {
|
||||
display: flex;
|
||||
flex-direction: row nowrap;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
border-left: 1px solid var(--color-foreground-base);
|
||||
background: var(--color-background-xlight);
|
||||
overflow-y: hidden;
|
||||
height: 100%;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.container {
|
||||
|
||||
@@ -25,6 +25,7 @@ import { useAssistantStore } from '@/stores/assistant.store';
|
||||
type Props = {
|
||||
nodeViewScale: number;
|
||||
createNodeActive?: boolean;
|
||||
focusPanelActive: boolean;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
@@ -135,7 +136,14 @@ function onAskAssistantButtonClick() {
|
||||
:shortcut="{ keys: ['f'], shiftKey: true }"
|
||||
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>
|
||||
<n8n-tooltip v-if="assistantStore.canShowAssistantButtonsOnCanvas" placement="left">
|
||||
<template #content> {{ i18n.baseText('aiAssistant.tooltip') }}</template>
|
||||
@@ -185,4 +193,8 @@ function onAskAssistantButtonClick() {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.activeButton {
|
||||
background-color: var(--button-hover-background-color) !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -152,7 +152,7 @@ describe('NodeDetailsView', () => {
|
||||
|
||||
test('should unregister keydown listener on unmount', async () => {
|
||||
const { pinia, workflowObject, nodeName } = await createPiniaStore(false);
|
||||
const ndvStore = useNDVStore();
|
||||
const ndvStore = useNDVStore(pinia);
|
||||
|
||||
const renderComponent = createComponentRenderer(NodeDetailsView, {
|
||||
props: {
|
||||
|
||||
@@ -799,6 +799,7 @@ onBeforeUnmount(() => {
|
||||
:executable="!readOnly"
|
||||
:input-size="inputSize"
|
||||
:class="$style.settings"
|
||||
is-ndv-v2
|
||||
@execute="onNodeExecute"
|
||||
@stop-execution="onStopExecution"
|
||||
@activate="onWorkflowActivate"
|
||||
|
||||
@@ -15,7 +15,7 @@ import type {
|
||||
IUpdateInformation,
|
||||
} 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 NodeCredentials from '@/components/NodeCredentials.vue';
|
||||
@@ -50,7 +50,6 @@ import { useTelemetry } from '@/composables/useTelemetry';
|
||||
import { importCurlEventBus, ndvEventBus } from '@/event-bus';
|
||||
import { ProjectTypes } from '@/types/projects.types';
|
||||
import FreeAiCreditsCallout from '@/components/FreeAiCreditsCallout.vue';
|
||||
import { usePostHog } from '@/stores/posthog.store';
|
||||
import { useResizeObserver } from '@vueuse/core';
|
||||
import { useNodeSettingsParameters } from '@/composables/useNodeSettingsParameters';
|
||||
import { N8nBlockUi, N8nIcon, N8nNotice, N8nText } from '@n8n/design-system';
|
||||
@@ -75,12 +74,20 @@ const props = withDefaults(
|
||||
subTitle?: string;
|
||||
extraTabsClassName?: string;
|
||||
extraParameterWrapperClassName?: string;
|
||||
isNdvV2?: boolean;
|
||||
hideExecute?: boolean;
|
||||
hideDocs?: boolean;
|
||||
hideSubConnections?: boolean;
|
||||
}>(),
|
||||
{
|
||||
inputSize: 0,
|
||||
activeNode: undefined,
|
||||
isEmbeddedInCanvas: false,
|
||||
subTitle: undefined,
|
||||
isNdvV2: false,
|
||||
hideExecute: false,
|
||||
hideDocs: true,
|
||||
hideSubConnections: false,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -108,7 +115,6 @@ const ndvStore = useNDVStore();
|
||||
const workflowsStore = useWorkflowsStore();
|
||||
const credentialsStore = useCredentialsStore();
|
||||
const historyStore = useHistoryStore();
|
||||
const posthogStore = usePostHog();
|
||||
|
||||
const telemetry = useTelemetry();
|
||||
const nodeHelpers = useNodeHelpers();
|
||||
@@ -252,13 +258,6 @@ const credentialOwnerName = computed(() => {
|
||||
return credentialsStore.getCredentialOwnerName(credential);
|
||||
});
|
||||
|
||||
const isNDVV2 = computed(() =>
|
||||
posthogStore.isVariantEnabled(
|
||||
NDV_UI_OVERHAUL_EXPERIMENT.name,
|
||||
NDV_UI_OVERHAUL_EXPERIMENT.variant,
|
||||
),
|
||||
);
|
||||
|
||||
const featureRequestUrl = computed(() => {
|
||||
if (!nodeType.value) {
|
||||
return '';
|
||||
@@ -611,7 +610,7 @@ function handleSelectAction(params: INodeParameters) {
|
||||
<slot name="actions" />
|
||||
</template>
|
||||
</ExperimentalEmbeddedNdvHeader>
|
||||
<div v-else-if="!isNDVV2" :class="$style.header">
|
||||
<div v-else-if="!isNdvV2" :class="$style.header">
|
||||
<div class="header-side-menu">
|
||||
<NodeTitle
|
||||
v-if="node"
|
||||
@@ -648,9 +647,10 @@ function handleSelectAction(params: INodeParameters) {
|
||||
:node-name="node.name"
|
||||
:node-type="nodeType"
|
||||
:execute-button-tooltip="executeButtonTooltip"
|
||||
:hide-execute="!isExecutable || blockUI || !node || !nodeValid"
|
||||
:hide-execute="props.hideExecute || !isExecutable || blockUI || !node || !nodeValid"
|
||||
:disable-execute="outputPanelEditMode.enabled && !isTriggerNode"
|
||||
:hide-tabs="!nodeValid"
|
||||
:hide-docs="props.hideDocs"
|
||||
:push-ref="pushRef"
|
||||
@execute="onNodeExecute"
|
||||
@stop-execution="onStopExecution"
|
||||
@@ -666,7 +666,7 @@ function handleSelectAction(params: INodeParameters) {
|
||||
:class="[
|
||||
'node-parameters-wrapper',
|
||||
shouldShowStaticScrollbar ? 'with-static-scrollbar' : '',
|
||||
{ 'ndv-v2': isNDVV2 },
|
||||
{ 'ndv-v2': isNdvV2 },
|
||||
extraParameterWrapperClassName ?? '',
|
||||
]"
|
||||
data-test-id="node-parameters"
|
||||
@@ -782,7 +782,7 @@ function handleSelectAction(params: INodeParameters) {
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="isNDVV2 && featureRequestUrl && !isEmbeddedInCanvas"
|
||||
v-if="isNdvV2 && featureRequestUrl && !isEmbeddedInCanvas"
|
||||
:class="$style.featureRequest"
|
||||
>
|
||||
<a target="_blank" @click="onFeatureRequestClick">
|
||||
@@ -792,7 +792,7 @@ function handleSelectAction(params: INodeParameters) {
|
||||
</div>
|
||||
</div>
|
||||
<NDVSubConnections
|
||||
v-if="node && !props.isEmbeddedInCanvas"
|
||||
v-if="node && !hideSubConnections"
|
||||
ref="subConnections"
|
||||
:root-node="node"
|
||||
@switch-selected-node="onSwitchSelectedNode"
|
||||
|
||||
@@ -8,6 +8,7 @@ import type { NodeSettingsTab } from '@/types/nodeSettings';
|
||||
type Props = {
|
||||
nodeName: string;
|
||||
hideExecute: boolean;
|
||||
hideDocs: boolean;
|
||||
hideTabs: boolean;
|
||||
disableExecute: boolean;
|
||||
executeButtonTooltip: string;
|
||||
@@ -30,7 +31,7 @@ const emit = defineEmits<{
|
||||
<div :class="$style.header">
|
||||
<NodeSettingsTabs
|
||||
v-if="!hideTabs"
|
||||
hide-docs
|
||||
:hide-docs="hideDocs"
|
||||
:model-value="selectedTab"
|
||||
:node-type="nodeType"
|
||||
:push-ref="pushRef"
|
||||
@@ -61,11 +62,14 @@ const emit = defineEmits<{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 40px;
|
||||
padding-right: var(--spacing-s);
|
||||
|
||||
border-bottom: var(--border-base);
|
||||
}
|
||||
|
||||
.execute {
|
||||
margin-right: var(--spacing-s);
|
||||
}
|
||||
|
||||
.tabs {
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import { computed } from 'vue';
|
||||
import { useNDVStore } from '@/stores/ndv.store';
|
||||
import { AI_TRANSFORM_NODE_TYPE } from '@/constants';
|
||||
import { getParameterTypeOption } from '@/utils/nodeSettingsUtils';
|
||||
import { useIsInExperimentalNdv } from '@/components/canvas/experimental/composables/useIsInExperimentalNdv';
|
||||
|
||||
interface Props {
|
||||
parameter: INodeProperties;
|
||||
@@ -51,13 +52,14 @@ const isHtmlEditor = computed(
|
||||
const shouldShowExpressionSelector = computed(
|
||||
() => !props.parameter.noDataExpression && props.showExpressionSelector && !props.isReadOnly,
|
||||
);
|
||||
const isInEmbeddedNdv = useIsInExperimentalNdv();
|
||||
|
||||
const canBeOpenedInFocusPanel = computed(
|
||||
() =>
|
||||
!props.parameter.isNodeSetting &&
|
||||
!props.isReadOnly &&
|
||||
!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'),
|
||||
);
|
||||
|
||||
|
||||
@@ -922,7 +922,7 @@ provide(CanvasKey, {
|
||||
snap-to-grid
|
||||
:snap-grid="[GRID_SIZE, GRID_SIZE]"
|
||||
:min-zoom="0"
|
||||
:max-zoom="experimentalNdvStore.isEnabled ? experimentalNdvStore.maxCanvasZoom : 4"
|
||||
:max-zoom="experimentalNdvStore.isZoomedViewEnabled ? experimentalNdvStore.maxCanvasZoom : 4"
|
||||
:selection-key-code="selectionKeyCode"
|
||||
:zoom-activation-key-code="panningKeyCode"
|
||||
:pan-activation-key-code="panningKeyCode"
|
||||
|
||||
@@ -9,8 +9,6 @@ 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 './experimental/components/ExperimentalNodeDetailsDrawer.vue';
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
@@ -36,9 +34,8 @@ const props = withDefaults(
|
||||
);
|
||||
|
||||
const $style = useCssModule();
|
||||
const settingsStore = useSettingsStore();
|
||||
|
||||
const { onNodesInitialized, getSelectedNodes } = useVueFlow(props.id);
|
||||
const { onNodesInitialized } = useVueFlow(props.id);
|
||||
|
||||
const workflow = toRef(props, 'workflow');
|
||||
const workflowObject = toRef(props, 'workflowObject');
|
||||
@@ -83,10 +80,6 @@ const mappedConnectionsThrottled = throttledRef(mappedConnections, 200);
|
||||
/>
|
||||
</div>
|
||||
<slot />
|
||||
<ExperimentalNodeDetailsDrawer
|
||||
v-if="settingsStore.experimental__dockedNodeSettingsEnabled && !props.readOnly"
|
||||
:selected-nodes="getSelectedNodes"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ const experimentalNdvStore = useExperimentalNdvStore();
|
||||
|
||||
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);
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ const isDisableNodeVisible = computed(() => {
|
||||
|
||||
const isDeleteNodeVisible = computed(() => !props.readOnly);
|
||||
|
||||
const isFocusNodeVisible = computed(() => experimentalNdvStore.isEnabled);
|
||||
const isFocusNodeVisible = computed(() => experimentalNdvStore.isZoomedViewEnabled);
|
||||
|
||||
const isStickyNoteChangeColorVisible = computed(
|
||||
() => !props.readOnly && render.value.type === CanvasNodeRenderType.StickyNote,
|
||||
|
||||
@@ -8,10 +8,11 @@ import { useUIStore } from '@/stores/ui.store';
|
||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||
import { computed } from 'vue';
|
||||
|
||||
const { nodeId, isReadOnly, subTitle } = defineProps<{
|
||||
const { nodeId, isReadOnly, subTitle, isEmbeddedInCanvas } = defineProps<{
|
||||
nodeId: string;
|
||||
isReadOnly?: boolean;
|
||||
subTitle?: string;
|
||||
isEmbeddedInCanvas?: boolean;
|
||||
}>();
|
||||
|
||||
defineSlots<{ actions?: {} }>();
|
||||
@@ -75,10 +76,14 @@ function handleCaptureWheelEvent(event: WheelEvent) {
|
||||
:read-only="isReadOnly"
|
||||
:block-u-i="blockUi"
|
||||
:executable="!isReadOnly"
|
||||
is-embedded-in-canvas
|
||||
:is-embedded-in-canvas="isEmbeddedInCanvas"
|
||||
:sub-title="subTitle"
|
||||
extra-tabs-class-name="nodrag"
|
||||
extra-parameter-wrapper-class-name="nodrag"
|
||||
is-ndv-v2
|
||||
hide-execute
|
||||
:hide-docs="false"
|
||||
hide-sub-connections
|
||||
@value-changed="handleValueChanged"
|
||||
@capture-wheel-body="handleCaptureWheelEvent"
|
||||
@dblclick-header="handleDoubleClickHeader"
|
||||
|
||||
@@ -41,7 +41,7 @@ defineExpose({
|
||||
:popper-class="$style.component"
|
||||
:width="360"
|
||||
:offset="8"
|
||||
append-to="#canvas"
|
||||
append-to="body"
|
||||
:popper-options="{
|
||||
modifiers: [{ name: 'flip', enabled: false }],
|
||||
}"
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import { ExpressionLocalResolveContextSymbol } from '@/constants';
|
||||
import { useEnvironmentsStore } from '@/stores/environments.ee.store';
|
||||
import { useNDVStore } from '@/stores/ndv.store';
|
||||
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||
import type { ExpressionLocalResolveContext } from '@/types/expressions';
|
||||
import { N8nText } from '@n8n/design-system';
|
||||
import { useVueFlow } from '@vue-flow/core';
|
||||
import { watchOnce } from '@vueuse/core';
|
||||
@@ -12,11 +10,11 @@ import { computed, provide, ref } from 'vue';
|
||||
import { useExperimentalNdvStore } from '../experimentalNdv.store';
|
||||
import ExperimentalCanvasNodeSettings from './ExperimentalCanvasNodeSettings.vue';
|
||||
import { useI18n } from '@n8n/i18n';
|
||||
import type { Workflow } from 'n8n-workflow';
|
||||
import NodeIcon from '@/components/NodeIcon.vue';
|
||||
import { getNodeSubTitleText } from '@/components/canvas/experimental/experimentalNdv.utils';
|
||||
import ExperimentalEmbeddedNdvActions from '@/components/canvas/experimental/components/ExperimentalEmbeddedNdvActions.vue';
|
||||
import { useCanvas } from '@/composables/useCanvas';
|
||||
import { useExpressionResolveCtx } from '@/components/canvas/experimental/composables/useExpressionResolveCtx';
|
||||
|
||||
const { nodeId, isReadOnly } = defineProps<{
|
||||
nodeId: string;
|
||||
@@ -56,54 +54,10 @@ const subTitle = computed(() =>
|
||||
? getNodeSubTitleText(node.value, nodeType.value, !isExpanded.value, i18n)
|
||||
: 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 workflowObject = computed(() => workflowsStore.workflowObject as Workflow);
|
||||
|
||||
const expressionResolveCtx = useExpressionResolveCtx(node);
|
||||
|
||||
function handleToggleExpand() {
|
||||
experimentalNdvStore.setNodeExpanded(nodeId);
|
||||
@@ -143,6 +97,7 @@ watchOnce(isVisible, (visible) => {
|
||||
:is-read-only="isReadOnly"
|
||||
:sub-title="subTitle"
|
||||
:input-node-name="expressionResolveCtx?.inputNode?.name"
|
||||
is-embedded-in-canvas
|
||||
>
|
||||
<template #actions>
|
||||
<ExperimentalEmbeddedNdvActions
|
||||
|
||||
@@ -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>
|
||||
@@ -1,50 +1,25 @@
|
||||
<script setup lang="ts">
|
||||
import { type CanvasNode } from '@/types';
|
||||
import ExperimentalCanvasNodeSettings from './ExperimentalCanvasNodeSettings.vue';
|
||||
import { useExpressionResolveCtx } from '@/components/canvas/experimental/composables/useExpressionResolveCtx';
|
||||
import { ExpressionLocalResolveContextSymbol } from '@/constants';
|
||||
import { type INodeUi } from '@/Interface';
|
||||
import { N8nText } from '@n8n/design-system';
|
||||
import { computed, watch, ref } from 'vue';
|
||||
import { useNDVStore } from '@/stores/ndv.store';
|
||||
import { type GraphNode } from '@vue-flow/core';
|
||||
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(() =>
|
||||
selectedNodes.length > 1
|
||||
? `${selectedNodes.length} nodes selected`
|
||||
: selectedNodes.length > 0
|
||||
? selectedNodes[0]
|
||||
: undefined,
|
||||
);
|
||||
const lastContent = ref<string | CanvasNode | undefined>();
|
||||
const { setActiveNodeName } = useNDVStore();
|
||||
const emit = defineEmits<{ openNdv: [] }>();
|
||||
|
||||
function handleOpenNdv() {
|
||||
if (typeof content.value === 'object' && content.value.data) {
|
||||
setActiveNodeName(content.value.data.name);
|
||||
}
|
||||
}
|
||||
const expressionResolveCtx = useExpressionResolveCtx(computed(() => node));
|
||||
|
||||
// Sync lastContent to be "last defined content" (for drawer animation)
|
||||
watch(
|
||||
content,
|
||||
(newContent) => {
|
||||
if (newContent !== undefined) {
|
||||
lastContent.value = newContent;
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
provide(ExpressionLocalResolveContextSymbol, expressionResolveCtx);
|
||||
</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"
|
||||
:key="lastContent.id"
|
||||
:node-id="lastContent.id"
|
||||
>
|
||||
<div :class="$style.component">
|
||||
<N8nText v-if="nodes.length > 1" color="text-base"> {{ nodes.length }} nodes selected </N8nText>
|
||||
<ExperimentalCanvasNodeSettings v-else-if="node" :key="node.id" :node-id="node.id">
|
||||
<template #actions>
|
||||
<N8nIconButton
|
||||
icon="maximize-2"
|
||||
@@ -53,7 +28,7 @@ watch(
|
||||
size="mini"
|
||||
icon-size="large"
|
||||
aria-label="Expand"
|
||||
@click="handleOpenNdv"
|
||||
@click="emit('openNdv')"
|
||||
/>
|
||||
</template>
|
||||
</ExperimentalCanvasNodeSettings>
|
||||
@@ -62,22 +37,10 @@ watch(
|
||||
|
||||
<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%);
|
||||
}
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -12,17 +12,22 @@ import {
|
||||
} from '@vue-flow/core';
|
||||
import { CanvasNodeRenderType, type CanvasNodeData } from '@/types';
|
||||
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', () => {
|
||||
const workflowStore = useWorkflowsStore();
|
||||
const postHogStore = usePostHog();
|
||||
const isEnabled = computed(
|
||||
const isZoomedViewEnabled = computed(
|
||||
() =>
|
||||
postHogStore.getVariant(CANVAS_ZOOMED_VIEW_EXPERIMENT.name) ===
|
||||
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 collapsedNodes = shallowRef<Partial<Record<string, boolean>>>({});
|
||||
@@ -50,7 +55,7 @@ export const useExperimentalNdvStore = defineStore('experimentalNdv', () => {
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -139,7 +144,8 @@ export const useExperimentalNdvStore = defineStore('experimentalNdv', () => {
|
||||
}
|
||||
|
||||
return {
|
||||
isEnabled,
|
||||
isZoomedViewEnabled,
|
||||
isNdvInFocusPanelEnabled,
|
||||
maxCanvasZoom,
|
||||
previousZoom: computed(() => previousViewport.value),
|
||||
collapsedNodes: computed(() => collapsedNodes.value),
|
||||
|
||||
@@ -113,6 +113,7 @@ import { isChatNode } from '@/utils/aiUtils';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import uniq from 'lodash/uniq';
|
||||
import { useExperimentalNdvStore } from '@/components/canvas/experimental/experimentalNdv.store';
|
||||
import { useFocusPanelStore } from '@/stores/focusPanel.store';
|
||||
|
||||
type AddNodeData = Partial<INodeUi> & {
|
||||
type: string;
|
||||
@@ -158,6 +159,7 @@ export function useCanvasOperations() {
|
||||
const projectsStore = useProjectsStore();
|
||||
const logsStore = useLogsStore();
|
||||
const experimentalNdvStore = useExperimentalNdvStore();
|
||||
const focusPanelStore = useFocusPanelStore();
|
||||
|
||||
const i18n = useI18n();
|
||||
const toast = useToast();
|
||||
@@ -793,7 +795,13 @@ export function useCanvasOperations() {
|
||||
void externalHooks.run('nodeView.addNodeButton', { nodeTypeName: nodeData.type });
|
||||
|
||||
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);
|
||||
} else {
|
||||
ndvStore.setActiveNodeName(nodeData.name);
|
||||
|
||||
@@ -7,7 +7,6 @@ const APP_Z_INDEXES = {
|
||||
APP_SIDEBAR: 999,
|
||||
CANVAS_SELECT_BOX: 100,
|
||||
TOP_BANNERS: 999,
|
||||
FOCUS_PANEL: 1600,
|
||||
NODE_CREATOR: 1700,
|
||||
NDV: 1800,
|
||||
MODALS: 2000,
|
||||
|
||||
@@ -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_SUB_NODE = 'N8N_LOGS_DETAILS_PANEL_SUB_NODE';
|
||||
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_DISMISSED_WHATS_NEW_CALLOUT = 'N8N_DISMISSED_WHATS_NEW_CALLOUT';
|
||||
export const LOCAL_STORAGE_NDV_PANEL_WIDTH = 'N8N_NDV_PANEL_WIDTH';
|
||||
@@ -756,6 +754,12 @@ export const CANVAS_ZOOMED_VIEW_EXPERIMENT = {
|
||||
variant: 'variant',
|
||||
};
|
||||
|
||||
export const NDV_IN_FOCUS_PANEL_EXPERIMENT = {
|
||||
name: 'ndv_in_focus_panel',
|
||||
control: 'control',
|
||||
variant: 'variant',
|
||||
};
|
||||
|
||||
export const NDV_UI_OVERHAUL_EXPERIMENT = {
|
||||
name: '029_ndv_ui_overhaul',
|
||||
control: 'control',
|
||||
|
||||
@@ -147,6 +147,10 @@ export const useFocusPanelStore = defineStore(STORES.FOCUS_PANEL, () => {
|
||||
_setOptions({ isActive: false });
|
||||
}
|
||||
|
||||
function unsetParameters() {
|
||||
_setOptions({ parameters: [] });
|
||||
}
|
||||
|
||||
function toggleFocusPanel() {
|
||||
_setOptions({ isActive: !focusPanelActive.value });
|
||||
}
|
||||
@@ -193,5 +197,6 @@ export const useFocusPanelStore = defineStore(STORES.FOCUS_PANEL, () => {
|
||||
toggleFocusPanel,
|
||||
onNewWorkflowSave,
|
||||
updateWidth,
|
||||
unsetParameters,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -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 moduleSettingsApi from '@n8n/rest-api-client/api/module-settings';
|
||||
import { testHealthEndpoint } from '@n8n/rest-api-client/api/templates';
|
||||
import {
|
||||
INSECURE_CONNECTION_WARNING,
|
||||
LOCAL_STORAGE_EXPERIMENTAL_DOCKED_NODE_SETTINGS,
|
||||
} from '@/constants';
|
||||
import { INSECURE_CONNECTION_WARNING } from '@/constants';
|
||||
import { STORES } from '@n8n/stores';
|
||||
import { UserManagementAuthenticationMethod } from '@/Interface';
|
||||
import type { IDataObject, WorkflowSettings } from 'n8n-workflow';
|
||||
@@ -315,15 +312,6 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, () => {
|
||||
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 {
|
||||
settings,
|
||||
userManagement,
|
||||
@@ -380,7 +368,6 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, () => {
|
||||
isAskAiEnabled,
|
||||
isAiCreditsEnabled,
|
||||
aiCreditsQuota,
|
||||
experimental__dockedNodeSettingsEnabled,
|
||||
partialExecutionVersion,
|
||||
reset,
|
||||
getTimezones,
|
||||
|
||||
@@ -2160,6 +2160,7 @@ onBeforeUnmount(() => {
|
||||
v-if="!isCanvasReadOnly"
|
||||
:create-node-active="nodeCreatorStore.isCreateNodeActive"
|
||||
:node-view-scale="viewportTransform.zoom"
|
||||
:focus-panel-active="focusPanelStore.focusPanelActive"
|
||||
@toggle-node-creator="onToggleNodeCreator"
|
||||
@add-nodes="onAddNodesAndConnections"
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user