mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +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;
|
blockUI: boolean;
|
||||||
executable: boolean;
|
executable: boolean;
|
||||||
inputSize: number;
|
inputSize: number;
|
||||||
|
activeNode?: INodeUi;
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
foreignCredentials: () => [],
|
foreignCredentials: () => [],
|
||||||
@@ -129,7 +130,7 @@ const isHomeProjectTeam = computed(
|
|||||||
const isReadOnly = computed(
|
const isReadOnly = computed(
|
||||||
() => props.readOnly || (hasForeignCredential.value && !isHomeProjectTeam.value),
|
() => 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));
|
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 { NODE_INSERT_SPACER_BETWEEN_INPUT_GROUPS } from '@/constants';
|
||||||
import type { CanvasNodeDefaultRender } from '@/types';
|
import type { CanvasNodeDefaultRender } from '@/types';
|
||||||
import { useCanvas } from '@/composables/useCanvas';
|
import { useCanvas } from '@/composables/useCanvas';
|
||||||
|
import { useNodeSettingsInCanvas } from '@/components/canvas/composables/useNodeSettingsInCanvas';
|
||||||
|
import CanvasNodeNodeSettings from './parts/CanvasNodeNodeSettings.vue';
|
||||||
|
|
||||||
const $style = useCssModule();
|
const $style = useCssModule();
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
@@ -48,6 +50,8 @@ const {
|
|||||||
|
|
||||||
const renderOptions = computed(() => render.value.options as CanvasNodeDefaultRender['options']);
|
const renderOptions = computed(() => render.value.options as CanvasNodeDefaultRender['options']);
|
||||||
|
|
||||||
|
const nodeSettingsZoom = useNodeSettingsInCanvas();
|
||||||
|
|
||||||
const classes = computed(() => {
|
const classes = computed(() => {
|
||||||
return {
|
return {
|
||||||
[$style.node]: true,
|
[$style.node]: true,
|
||||||
@@ -62,6 +66,7 @@ const classes = computed(() => {
|
|||||||
[$style.configuration]: renderOptions.value.configuration,
|
[$style.configuration]: renderOptions.value.configuration,
|
||||||
[$style.trigger]: renderOptions.value.trigger,
|
[$style.trigger]: renderOptions.value.trigger,
|
||||||
[$style.warning]: renderOptions.value.dirtiness !== undefined,
|
[$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;
|
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-input-count'] = mainInputs.value.length;
|
||||||
stylesObject['--canvas-node--main-output-count'] = mainOutputs.value.length;
|
stylesObject['--canvas-node--main-output-count'] = mainOutputs.value.length;
|
||||||
|
|
||||||
@@ -143,19 +152,22 @@ function onActivate(event: MouseEvent) {
|
|||||||
@contextmenu="openContextMenu"
|
@contextmenu="openContextMenu"
|
||||||
@dblclick.stop="onActivate"
|
@dblclick.stop="onActivate"
|
||||||
>
|
>
|
||||||
<CanvasNodeTooltip v-if="renderOptions.tooltip" :visible="showTooltip" />
|
<CanvasNodeNodeSettings v-if="nodeSettingsZoom !== undefined" :node-id="id" />
|
||||||
<NodeIcon :icon-source="iconSource" :size="iconSize" :shrink="false" :disabled="isDisabled" />
|
<template v-else>
|
||||||
<CanvasNodeStatusIcons v-if="!isDisabled" :class="$style.statusIcons" />
|
<CanvasNodeTooltip v-if="renderOptions.tooltip" :visible="showTooltip" />
|
||||||
<CanvasNodeDisabledStrikeThrough v-if="isStrikethroughVisible" />
|
<NodeIcon :icon-source="iconSource" :size="iconSize" :shrink="false" :disabled="isDisabled" />
|
||||||
<div :class="$style.description">
|
<CanvasNodeStatusIcons v-if="!isDisabled" :class="$style.statusIcons" />
|
||||||
<div v-if="label" :class="$style.label">
|
<CanvasNodeDisabledStrikeThrough v-if="isStrikethroughVisible" />
|
||||||
{{ label }}
|
<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>
|
||||||
<div v-if="isDisabled" :class="$style.disabledLabel">
|
</template>
|
||||||
({{ i18n.baseText('node.disabled') }})
|
|
||||||
</div>
|
|
||||||
<div v-if="subtitle" :class="$style.subtitle">{{ subtitle }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -193,6 +205,21 @@ function onActivate(event: MouseEvent) {
|
|||||||
var(--border-radius-large) var(--trigger-node--border-radius);
|
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
|
* Node types
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ exports[`CanvasNodeDefault > configurable > should render configurable node corr
|
|||||||
data-test-id="canvas-configurable-node"
|
data-test-id="canvas-configurable-node"
|
||||||
style="--configurable-node--input-count: 0; --canvas-node--main-input-count: 0; --canvas-node--main-output-count: 0;"
|
style="--configurable-node--input-count: 0; --canvas-node--main-input-count: 0; --canvas-node--main-output-count: 0;"
|
||||||
>
|
>
|
||||||
|
|
||||||
<!--v-if-->
|
<!--v-if-->
|
||||||
<div
|
<div
|
||||||
class="n8n-node-icon"
|
class="n8n-node-icon"
|
||||||
@@ -42,6 +43,7 @@ exports[`CanvasNodeDefault > configurable > should render configurable node corr
|
|||||||
Test Node Subtitle
|
Test Node Subtitle
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -51,6 +53,7 @@ exports[`CanvasNodeDefault > configuration > should render configurable configur
|
|||||||
data-test-id="canvas-configurable-node"
|
data-test-id="canvas-configurable-node"
|
||||||
style="--configurable-node--input-count: 0; --canvas-node--main-input-count: 0; --canvas-node--main-output-count: 0;"
|
style="--configurable-node--input-count: 0; --canvas-node--main-input-count: 0; --canvas-node--main-output-count: 0;"
|
||||||
>
|
>
|
||||||
|
|
||||||
<!--v-if-->
|
<!--v-if-->
|
||||||
<div
|
<div
|
||||||
class="n8n-node-icon"
|
class="n8n-node-icon"
|
||||||
@@ -87,6 +90,7 @@ exports[`CanvasNodeDefault > configuration > should render configurable configur
|
|||||||
Test Node Subtitle
|
Test Node Subtitle
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -96,6 +100,7 @@ exports[`CanvasNodeDefault > configuration > should render configuration node co
|
|||||||
data-test-id="canvas-configuration-node"
|
data-test-id="canvas-configuration-node"
|
||||||
style="--canvas-node--main-input-count: 0; --canvas-node--main-output-count: 0;"
|
style="--canvas-node--main-input-count: 0; --canvas-node--main-output-count: 0;"
|
||||||
>
|
>
|
||||||
|
|
||||||
<!--v-if-->
|
<!--v-if-->
|
||||||
<div
|
<div
|
||||||
class="n8n-node-icon"
|
class="n8n-node-icon"
|
||||||
@@ -132,6 +137,7 @@ exports[`CanvasNodeDefault > configuration > should render configuration node co
|
|||||||
Test Node Subtitle
|
Test Node Subtitle
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -141,6 +147,7 @@ exports[`CanvasNodeDefault > should render node correctly 1`] = `
|
|||||||
data-test-id="canvas-default-node"
|
data-test-id="canvas-default-node"
|
||||||
style="--canvas-node--main-input-count: 0; --canvas-node--main-output-count: 0;"
|
style="--canvas-node--main-input-count: 0; --canvas-node--main-output-count: 0;"
|
||||||
>
|
>
|
||||||
|
|
||||||
<!--v-if-->
|
<!--v-if-->
|
||||||
<div
|
<div
|
||||||
class="n8n-node-icon"
|
class="n8n-node-icon"
|
||||||
@@ -177,6 +184,7 @@ exports[`CanvasNodeDefault > should render node correctly 1`] = `
|
|||||||
Test Node Subtitle
|
Test Node Subtitle
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -186,6 +194,7 @@ exports[`CanvasNodeDefault > trigger > should render trigger node correctly 1`]
|
|||||||
data-test-id="canvas-trigger-node"
|
data-test-id="canvas-trigger-node"
|
||||||
style="--canvas-node--main-input-count: 0; --canvas-node--main-output-count: 0;"
|
style="--canvas-node--main-input-count: 0; --canvas-node--main-output-count: 0;"
|
||||||
>
|
>
|
||||||
|
|
||||||
<!--v-if-->
|
<!--v-if-->
|
||||||
<div
|
<div
|
||||||
class="n8n-node-icon"
|
class="n8n-node-icon"
|
||||||
@@ -222,5 +231,6 @@ exports[`CanvasNodeDefault > trigger > should render trigger node correctly 1`]
|
|||||||
Test Node Subtitle
|
Test Node Subtitle
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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_SYNC_SELECTION = 'N8N_LOGS_SYNC_SELECTION';
|
||||||
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_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_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 BASE_NODE_SURVEY_URL = 'https://n8n-community.typeform.com/to/BvmzxqYv#nodename=';
|
||||||
export const COMMUNITY_PLUS_DOCS_URL =
|
export const COMMUNITY_PLUS_DOCS_URL =
|
||||||
'https://docs.n8n.io/hosting/community-edition-features/#registered-community-edition';
|
'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 * as settingsApi from '@/api/settings';
|
||||||
import { testHealthEndpoint } from '@/api/templates';
|
import { testHealthEndpoint } from '@/api/templates';
|
||||||
import type { ILdapConfig } from '@/Interface';
|
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 { 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';
|
||||||
@@ -381,6 +384,15 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, () => {
|
|||||||
settings.value = {} as FrontendSettings;
|
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 {
|
return {
|
||||||
settings,
|
settings,
|
||||||
userManagement,
|
userManagement,
|
||||||
@@ -445,6 +457,7 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, () => {
|
|||||||
isAiCreditsEnabled,
|
isAiCreditsEnabled,
|
||||||
aiCreditsQuota,
|
aiCreditsQuota,
|
||||||
isNewLogsEnabled,
|
isNewLogsEnabled,
|
||||||
|
experimental__minZoomNodeSettingsInCanvas,
|
||||||
reset,
|
reset,
|
||||||
testLdapConnection,
|
testLdapConnection,
|
||||||
getLdapConfig,
|
getLdapConfig,
|
||||||
|
|||||||
Reference in New Issue
Block a user