mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 17:46:45 +00:00
feat(editor): Experimental feature flag to show node settings in the canvas (no-changelog) (#15925)
This commit is contained in:
@@ -65,6 +65,7 @@ const props = withDefaults(
|
||||
blockUI: boolean;
|
||||
executable: boolean;
|
||||
inputSize: number;
|
||||
activeNode?: INodeUi;
|
||||
}>(),
|
||||
{
|
||||
foreignCredentials: () => [],
|
||||
@@ -129,7 +130,7 @@ const isHomeProjectTeam = computed(
|
||||
const isReadOnly = computed(
|
||||
() => props.readOnly || (hasForeignCredential.value && !isHomeProjectTeam.value),
|
||||
);
|
||||
const node = computed(() => ndvStore.activeNode);
|
||||
const node = computed(() => props.activeNode ?? ndvStore.activeNode);
|
||||
|
||||
const isTriggerNode = computed(() => !!node.value && nodeTypesStore.isTriggerNode(node.value.type));
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { useCanvasOperations } from '@/composables/useCanvasOperations';
|
||||
import { useSettingsStore } from '@/stores/settings.store';
|
||||
import { useVueFlow } from '@vue-flow/core';
|
||||
import { useDebounce } from '@vueuse/core';
|
||||
import { computed, type ComputedRef } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
export function useNodeSettingsInCanvas(): ComputedRef<number | undefined> {
|
||||
const settingsStore = useSettingsStore();
|
||||
|
||||
if (
|
||||
Number.isNaN(settingsStore.experimental__minZoomNodeSettingsInCanvas) ||
|
||||
settingsStore.experimental__minZoomNodeSettingsInCanvas <= 0
|
||||
) {
|
||||
return computed(() => undefined);
|
||||
}
|
||||
|
||||
const router = useRouter();
|
||||
const { editableWorkflow } = useCanvasOperations({ router });
|
||||
const viewFlow = useVueFlow({ id: editableWorkflow.value.id });
|
||||
const zoom = computed(() => viewFlow.viewport.value.zoom);
|
||||
const debouncedZoom = useDebounce(zoom, 100);
|
||||
|
||||
return computed(() =>
|
||||
debouncedZoom.value > settingsStore.experimental__minZoomNodeSettingsInCanvas
|
||||
? debouncedZoom.value
|
||||
: undefined,
|
||||
);
|
||||
}
|
||||
@@ -6,6 +6,8 @@ import { useCanvasNode } from '@/composables/useCanvasNode';
|
||||
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';
|
||||
|
||||
const $style = useCssModule();
|
||||
const i18n = useI18n();
|
||||
@@ -48,6 +50,8 @@ const {
|
||||
|
||||
const renderOptions = computed(() => render.value.options as CanvasNodeDefaultRender['options']);
|
||||
|
||||
const nodeSettingsZoom = useNodeSettingsInCanvas();
|
||||
|
||||
const classes = computed(() => {
|
||||
return {
|
||||
[$style.node]: true,
|
||||
@@ -62,6 +66,7 @@ const classes = computed(() => {
|
||||
[$style.configuration]: renderOptions.value.configuration,
|
||||
[$style.trigger]: renderOptions.value.trigger,
|
||||
[$style.warning]: renderOptions.value.dirtiness !== undefined,
|
||||
[$style.settingsView]: nodeSettingsZoom.value !== undefined,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -79,6 +84,10 @@ const styles = computed(() => {
|
||||
stylesObject['--configurable-node--input-count'] = nonMainInputs.value.length + spacerCount;
|
||||
}
|
||||
|
||||
if (nodeSettingsZoom.value !== undefined) {
|
||||
stylesObject['--zoom'] = nodeSettingsZoom.value;
|
||||
}
|
||||
|
||||
stylesObject['--canvas-node--main-input-count'] = mainInputs.value.length;
|
||||
stylesObject['--canvas-node--main-output-count'] = mainOutputs.value.length;
|
||||
|
||||
@@ -143,19 +152,22 @@ function onActivate(event: MouseEvent) {
|
||||
@contextmenu="openContextMenu"
|
||||
@dblclick.stop="onActivate"
|
||||
>
|
||||
<CanvasNodeTooltip v-if="renderOptions.tooltip" :visible="showTooltip" />
|
||||
<NodeIcon :icon-source="iconSource" :size="iconSize" :shrink="false" :disabled="isDisabled" />
|
||||
<CanvasNodeStatusIcons v-if="!isDisabled" :class="$style.statusIcons" />
|
||||
<CanvasNodeDisabledStrikeThrough v-if="isStrikethroughVisible" />
|
||||
<div :class="$style.description">
|
||||
<div v-if="label" :class="$style.label">
|
||||
{{ label }}
|
||||
<CanvasNodeNodeSettings 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" />
|
||||
<CanvasNodeStatusIcons v-if="!isDisabled" :class="$style.statusIcons" />
|
||||
<CanvasNodeDisabledStrikeThrough v-if="isStrikethroughVisible" />
|
||||
<div :class="$style.description">
|
||||
<div v-if="label" :class="$style.label">
|
||||
{{ label }}
|
||||
</div>
|
||||
<div v-if="isDisabled" :class="$style.disabledLabel">
|
||||
({{ i18n.baseText('node.disabled') }})
|
||||
</div>
|
||||
<div v-if="subtitle" :class="$style.subtitle">{{ subtitle }}</div>
|
||||
</div>
|
||||
<div v-if="isDisabled" :class="$style.disabledLabel">
|
||||
({{ i18n.baseText('node.disabled') }})
|
||||
</div>
|
||||
<div v-if="subtitle" :class="$style.subtitle">{{ subtitle }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -193,6 +205,21 @@ function onActivate(event: MouseEvent) {
|
||||
var(--border-radius-large) var(--trigger-node--border-radius);
|
||||
}
|
||||
|
||||
&.settingsView {
|
||||
/*margin-top: calc(var(--canvas-node--width) * 0.8);*/
|
||||
height: calc(var(--canvas-node--height) * 2.4) !important;
|
||||
width: calc(var(--canvas-node--width) * 1.6) !important;
|
||||
align-items: flex-start;
|
||||
justify-content: stretch;
|
||||
overflow: auto;
|
||||
border-radius: var(--border-radius-large) !important;
|
||||
|
||||
& > * {
|
||||
zoom: calc(1 / var(--zoom, 1));
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Node types
|
||||
*/
|
||||
|
||||
@@ -6,6 +6,7 @@ exports[`CanvasNodeDefault > configurable > should render configurable node corr
|
||||
data-test-id="canvas-configurable-node"
|
||||
style="--configurable-node--input-count: 0; --canvas-node--main-input-count: 0; --canvas-node--main-output-count: 0;"
|
||||
>
|
||||
|
||||
<!--v-if-->
|
||||
<div
|
||||
class="n8n-node-icon"
|
||||
@@ -42,6 +43,7 @@ exports[`CanvasNodeDefault > configurable > should render configurable node corr
|
||||
Test Node Subtitle
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -51,6 +53,7 @@ exports[`CanvasNodeDefault > configuration > should render configurable configur
|
||||
data-test-id="canvas-configurable-node"
|
||||
style="--configurable-node--input-count: 0; --canvas-node--main-input-count: 0; --canvas-node--main-output-count: 0;"
|
||||
>
|
||||
|
||||
<!--v-if-->
|
||||
<div
|
||||
class="n8n-node-icon"
|
||||
@@ -87,6 +90,7 @@ exports[`CanvasNodeDefault > configuration > should render configurable configur
|
||||
Test Node Subtitle
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -96,6 +100,7 @@ exports[`CanvasNodeDefault > configuration > should render configuration node co
|
||||
data-test-id="canvas-configuration-node"
|
||||
style="--canvas-node--main-input-count: 0; --canvas-node--main-output-count: 0;"
|
||||
>
|
||||
|
||||
<!--v-if-->
|
||||
<div
|
||||
class="n8n-node-icon"
|
||||
@@ -132,6 +137,7 @@ exports[`CanvasNodeDefault > configuration > should render configuration node co
|
||||
Test Node Subtitle
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -141,6 +147,7 @@ exports[`CanvasNodeDefault > should render node correctly 1`] = `
|
||||
data-test-id="canvas-default-node"
|
||||
style="--canvas-node--main-input-count: 0; --canvas-node--main-output-count: 0;"
|
||||
>
|
||||
|
||||
<!--v-if-->
|
||||
<div
|
||||
class="n8n-node-icon"
|
||||
@@ -177,6 +184,7 @@ exports[`CanvasNodeDefault > should render node correctly 1`] = `
|
||||
Test Node Subtitle
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -186,6 +194,7 @@ exports[`CanvasNodeDefault > trigger > should render trigger node correctly 1`]
|
||||
data-test-id="canvas-trigger-node"
|
||||
style="--canvas-node--main-input-count: 0; --canvas-node--main-output-count: 0;"
|
||||
>
|
||||
|
||||
<!--v-if-->
|
||||
<div
|
||||
class="n8n-node-icon"
|
||||
@@ -222,5 +231,6 @@ exports[`CanvasNodeDefault > trigger > should render trigger node correctly 1`]
|
||||
Test Node Subtitle
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
<script setup lang="ts">
|
||||
import NodeSettings from '@/components/NodeSettings.vue';
|
||||
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 settingsEventBus = createEventBus();
|
||||
const nodeTypesStore = useNodeTypesStore();
|
||||
const workflowsStore = useWorkflowsStore();
|
||||
|
||||
const activeNode = computed(() => workflowsStore.getNodeById(nodeId));
|
||||
const activeNodeType = computed(() => {
|
||||
if (activeNode.value) {
|
||||
return nodeTypesStore.getNodeType(activeNode.value.type, activeNode.value.typeVersion);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NodeSettings
|
||||
:event-bus="settingsEventBus"
|
||||
:dragging="false"
|
||||
:active-node="activeNode"
|
||||
:node-type="activeNodeType"
|
||||
push-ref=""
|
||||
:foreign-credentials="[]"
|
||||
:read-only="false"
|
||||
:block-u-i="false"
|
||||
:executable="false"
|
||||
:input-size="0"
|
||||
/>
|
||||
</template>
|
||||
@@ -490,6 +490,8 @@ export const LOCAL_STORAGE_LOGS_PANEL_OPEN = 'N8N_LOGS_PANEL_OPEN';
|
||||
export const LOCAL_STORAGE_LOGS_SYNC_SELECTION = 'N8N_LOGS_SYNC_SELECTION';
|
||||
export const LOCAL_STORAGE_LOGS_PANEL_DETAILS_PANEL = 'N8N_LOGS_DETAILS_PANEL';
|
||||
export const LOCAL_STORAGE_WORKFLOW_LIST_PREFERENCES_KEY = 'N8N_WORKFLOWS_LIST_PREFERENCES';
|
||||
export const LOCAL_STORAGE_EXPERIMENTAL_MIN_ZOOM_NODE_SETTINGS_IN_CANVAS =
|
||||
'N8N_EXPERIMENTAL_MIN_ZOOM_NODE_SETTINGS_IN_CANVAS';
|
||||
export const BASE_NODE_SURVEY_URL = 'https://n8n-community.typeform.com/to/BvmzxqYv#nodename=';
|
||||
export const COMMUNITY_PLUS_DOCS_URL =
|
||||
'https://docs.n8n.io/hosting/community-edition-features/#registered-community-edition';
|
||||
|
||||
@@ -7,7 +7,10 @@ import * as ldapApi from '@/api/ldap';
|
||||
import * as settingsApi from '@/api/settings';
|
||||
import { testHealthEndpoint } from '@/api/templates';
|
||||
import type { ILdapConfig } from '@/Interface';
|
||||
import { INSECURE_CONNECTION_WARNING } from '@/constants';
|
||||
import {
|
||||
INSECURE_CONNECTION_WARNING,
|
||||
LOCAL_STORAGE_EXPERIMENTAL_MIN_ZOOM_NODE_SETTINGS_IN_CANVAS,
|
||||
} from '@/constants';
|
||||
import { STORES } from '@n8n/stores';
|
||||
import { UserManagementAuthenticationMethod } from '@/Interface';
|
||||
import type { IDataObject, WorkflowSettings } from 'n8n-workflow';
|
||||
@@ -381,6 +384,15 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, () => {
|
||||
settings.value = {} as FrontendSettings;
|
||||
};
|
||||
|
||||
/**
|
||||
* (Experimental) Minimum zoom level of the canvas to render node settings in place of nodes, without opening NDV
|
||||
*/
|
||||
const experimental__minZoomNodeSettingsInCanvas = useLocalStorage(
|
||||
LOCAL_STORAGE_EXPERIMENTAL_MIN_ZOOM_NODE_SETTINGS_IN_CANVAS,
|
||||
0,
|
||||
{ writeDefaults: false },
|
||||
);
|
||||
|
||||
return {
|
||||
settings,
|
||||
userManagement,
|
||||
@@ -445,6 +457,7 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, () => {
|
||||
isAiCreditsEnabled,
|
||||
aiCreditsQuota,
|
||||
isNewLogsEnabled,
|
||||
experimental__minZoomNodeSettingsInCanvas,
|
||||
reset,
|
||||
testLdapConnection,
|
||||
getLdapConfig,
|
||||
|
||||
Reference in New Issue
Block a user