mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 17:46:45 +00:00
feat(editor): Show the right editor in focus panel (#17062)
Co-authored-by: Charlie Kolb <charlie@n8n.io>
This commit is contained in:
@@ -1497,6 +1497,7 @@
|
||||
"nodeView.deletesTheCurrentExecutionData": "Deletes the current execution data",
|
||||
"nodeView.focusPanel.title": "Focus",
|
||||
"nodeView.focusPanel.noParameters": "No parameters focused. Focus a parameter by clicking on the action dropdown in the node detail view.",
|
||||
"nodeView.focusPanel.missingParameter": "This parameter is no longer visible on the node. A related parameter was likely changed, removing this one.",
|
||||
"nodeView.itLooksLikeYouHaveBeenEditingSomething": "It looks like you made some edits. If you leave before saving, your changes will be lost.",
|
||||
"nodeView.loadingTemplate": "Loading template",
|
||||
"nodeView.moreInfo": "More info",
|
||||
|
||||
@@ -19,6 +19,7 @@ import { CODE_PLACEHOLDERS } from './constants';
|
||||
import { useLinter } from './linter';
|
||||
import { useSettingsStore } from '@/stores/settings.store';
|
||||
import { dropInCodeEditor } from '@/plugins/codemirror/dragAndDrop';
|
||||
import type { TargetNodeParameterContext } from '@/Interface';
|
||||
|
||||
type Props = {
|
||||
mode: CodeExecutionMode;
|
||||
@@ -29,6 +30,8 @@ type Props = {
|
||||
isReadOnly?: boolean;
|
||||
rows?: number;
|
||||
id?: string;
|
||||
targetNodeParameterContext?: TargetNodeParameterContext;
|
||||
disableAskAi?: boolean;
|
||||
};
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
@@ -38,6 +41,8 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
isReadOnly: false,
|
||||
rows: 4,
|
||||
id: () => crypto.randomUUID(),
|
||||
targetNodeParameterContext: undefined,
|
||||
disableAskAi: false,
|
||||
});
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: string];
|
||||
@@ -81,6 +86,7 @@ const { highlightLine, readEditorValue, editor } = useCodeEditor({
|
||||
rows: props.rows,
|
||||
},
|
||||
onChange: onEditorUpdate,
|
||||
targetNodeParameterContext: () => props.targetNodeParameterContext,
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
@@ -98,7 +104,9 @@ onBeforeUnmount(() => {
|
||||
});
|
||||
|
||||
const askAiEnabled = computed(() => {
|
||||
return settingsStore.isAskAiEnabled && props.language === 'javaScript';
|
||||
return (
|
||||
props.disableAskAi !== true && settingsStore.isAskAiEnabled && props.language === 'javaScript'
|
||||
);
|
||||
});
|
||||
|
||||
watch([() => props.language, () => props.mode], (_, [prevLanguage, prevMode]) => {
|
||||
|
||||
@@ -26,6 +26,7 @@ type Props = {
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
isReadOnly: false,
|
||||
targetNodeParameterContext: undefined,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -85,7 +86,7 @@ onMounted(() => {
|
||||
focus();
|
||||
});
|
||||
|
||||
defineExpose({ editor });
|
||||
defineExpose({ editor, focus });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -2,23 +2,53 @@
|
||||
import { useFocusPanelStore } from '@/stores/focusPanel.store';
|
||||
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||
import { N8nText, N8nInput } from '@n8n/design-system';
|
||||
import { computed } from 'vue';
|
||||
import { computed, nextTick, ref } from 'vue';
|
||||
import { useI18n } from '@n8n/i18n';
|
||||
import {
|
||||
formatAsExpression,
|
||||
getParameterTypeOption,
|
||||
isValidParameterOption,
|
||||
parseFromExpression,
|
||||
} from '@/utils/nodeSettingsUtils';
|
||||
import { isValueExpression } from '@/utils/nodeTypesUtils';
|
||||
import { useNodeHelpers } from '@/composables/useNodeHelpers';
|
||||
import { useNodeSettingsParameters } from '@/composables/useNodeSettingsParameters';
|
||||
import { useResolvedExpression } from '@/composables/useResolvedExpression';
|
||||
import {
|
||||
AI_TRANSFORM_NODE_TYPE,
|
||||
type CodeExecutionMode,
|
||||
type CodeNodeEditorLanguage,
|
||||
type EditorType,
|
||||
HTML_NODE_TYPE,
|
||||
isResourceLocatorValue,
|
||||
} from 'n8n-workflow';
|
||||
import { useEnvironmentsStore } from '@/stores/environments.ee.store';
|
||||
import { useDebounce } from '@/composables/useDebounce';
|
||||
import { htmlEditorEventBus } from '@/event-bus';
|
||||
import { hasFocusOnInput, isFocusableEl } from '@/utils/typesUtils';
|
||||
import type { TargetNodeParameterContext } from '@/Interface';
|
||||
|
||||
defineOptions({ name: 'FocusPanel' });
|
||||
|
||||
const props = defineProps<{
|
||||
executable: boolean;
|
||||
isCanvasReadOnly: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
focus: [];
|
||||
}>();
|
||||
|
||||
// ESLint: false positive
|
||||
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
|
||||
const inputField = ref<InstanceType<typeof N8nInput> | HTMLElement>();
|
||||
|
||||
const locale = useI18n();
|
||||
const nodeHelpers = useNodeHelpers();
|
||||
const focusPanelStore = useFocusPanelStore();
|
||||
const nodeTypesStore = useNodeTypesStore();
|
||||
const nodeSettingsParameters = useNodeSettingsParameters();
|
||||
const environmentsStore = useEnvironmentsStore();
|
||||
const { debounce } = useDebounce();
|
||||
|
||||
const focusedNodeParameter = computed(() => focusPanelStore.focusedNodeParameters[0]);
|
||||
const resolvedParameter = computed(() =>
|
||||
@@ -29,32 +59,119 @@ const resolvedParameter = computed(() =>
|
||||
|
||||
const focusPanelActive = computed(() => focusPanelStore.focusPanelActive);
|
||||
|
||||
const isDisabled = computed(() => {
|
||||
if (!resolvedParameter.value) return false;
|
||||
|
||||
// shouldDisplayNodeParameter returns true if disabledOptions exists and matches, OR if disabledOptions doesn't exist
|
||||
return (
|
||||
!!resolvedParameter.value.parameter.disabledOptions &&
|
||||
nodeSettingsParameters.shouldDisplayNodeParameter(
|
||||
resolvedParameter.value.node.parameters,
|
||||
resolvedParameter.value.node,
|
||||
resolvedParameter.value.parameter,
|
||||
'',
|
||||
'disabledOptions',
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
const isDisplayed = computed(() => {
|
||||
if (!resolvedParameter.value) return true;
|
||||
|
||||
return nodeSettingsParameters.shouldDisplayNodeParameter(
|
||||
resolvedParameter.value.node.parameters,
|
||||
resolvedParameter.value.node,
|
||||
resolvedParameter.value.parameter,
|
||||
'',
|
||||
'displayOptions',
|
||||
);
|
||||
});
|
||||
|
||||
const isExecutable = computed(() => {
|
||||
if (!resolvedParameter.value) return false;
|
||||
|
||||
if (!isDisplayed.value) return false;
|
||||
|
||||
const foreignCredentials = nodeHelpers.getForeignCredentialsIfSharingEnabled(
|
||||
resolvedParameter.value.node.credentials,
|
||||
);
|
||||
return nodeHelpers.isNodeExecutable(
|
||||
resolvedParameter.value.node,
|
||||
props.executable,
|
||||
!props.isCanvasReadOnly,
|
||||
foreignCredentials,
|
||||
);
|
||||
});
|
||||
|
||||
function getTypeOption<T>(optionName: string): T | undefined {
|
||||
return resolvedParameter.value
|
||||
? getParameterTypeOption<T>(resolvedParameter.value.parameter, optionName)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
const codeEditorMode = computed<CodeExecutionMode>(() => {
|
||||
return resolvedParameter.value?.node.parameters.mode as CodeExecutionMode;
|
||||
});
|
||||
|
||||
const editorType = computed<EditorType | 'json' | 'code' | 'cssEditor' | undefined>(() => {
|
||||
return getTypeOption('editor') ?? undefined;
|
||||
});
|
||||
|
||||
const editorLanguage = computed<CodeNodeEditorLanguage>(() => {
|
||||
if (editorType.value === 'json' || resolvedParameter.value?.parameter.type === 'json')
|
||||
return 'json' as CodeNodeEditorLanguage;
|
||||
|
||||
return getTypeOption('editorLanguage') ?? 'javaScript';
|
||||
});
|
||||
|
||||
const editorRows = computed(() => getTypeOption<number>('rows'));
|
||||
|
||||
const isToolNode = computed(() =>
|
||||
resolvedParameter.value ? nodeTypesStore.isToolNode(resolvedParameter.value?.node.type) : false,
|
||||
);
|
||||
|
||||
const isHtmlNode = computed(
|
||||
() => !!resolvedParameter.value && resolvedParameter.value.node.type === HTML_NODE_TYPE,
|
||||
);
|
||||
|
||||
const expressionModeEnabled = computed(
|
||||
() =>
|
||||
resolvedParameter.value &&
|
||||
isValueExpression(resolvedParameter.value.parameter, resolvedParameter.value.value),
|
||||
);
|
||||
|
||||
function optionSelected() {
|
||||
// TODO: Handle the option selected (command: string) from the dropdown
|
||||
}
|
||||
const expression = computed(() => {
|
||||
if (!expressionModeEnabled.value) return '';
|
||||
return isResourceLocatorValue(resolvedParameter.value)
|
||||
? resolvedParameter.value.value
|
||||
: resolvedParameter.value;
|
||||
});
|
||||
|
||||
const shouldCaptureForPosthog = computed(
|
||||
() => resolvedParameter.value?.node.type === AI_TRANSFORM_NODE_TYPE,
|
||||
);
|
||||
|
||||
const isReadOnly = computed(() => props.isCanvasReadOnly || isDisabled.value);
|
||||
|
||||
const resolvedAdditionalExpressionData = computed(() => {
|
||||
return {
|
||||
$vars: environmentsStore.variablesAsObject,
|
||||
};
|
||||
});
|
||||
|
||||
const targetNodeParameterContext = computed<TargetNodeParameterContext | undefined>(() => {
|
||||
if (!resolvedParameter.value) return undefined;
|
||||
return {
|
||||
nodeName: resolvedParameter.value.node.name,
|
||||
parameterPath: resolvedParameter.value.parameterPath,
|
||||
};
|
||||
});
|
||||
|
||||
const { resolvedExpression } = useResolvedExpression({
|
||||
expression,
|
||||
additionalData: resolvedAdditionalExpressionData,
|
||||
stringifyObject:
|
||||
resolvedParameter.value && resolvedParameter.value.parameter.type !== 'multiOptions',
|
||||
});
|
||||
|
||||
function valueChanged(value: string) {
|
||||
if (resolvedParameter.value === undefined) {
|
||||
@@ -68,6 +185,65 @@ function valueChanged(value: string) {
|
||||
isToolNode.value,
|
||||
);
|
||||
}
|
||||
|
||||
async function setFocus() {
|
||||
await nextTick();
|
||||
|
||||
if (inputField.value) {
|
||||
if (hasFocusOnInput(inputField.value)) {
|
||||
inputField.value.focusOnInput();
|
||||
} else if (isFocusableEl(inputField.value)) {
|
||||
inputField.value.focus();
|
||||
}
|
||||
}
|
||||
|
||||
emit('focus');
|
||||
}
|
||||
|
||||
function optionSelected(command: string) {
|
||||
if (!resolvedParameter.value) return;
|
||||
|
||||
switch (command) {
|
||||
case 'resetValue':
|
||||
return (
|
||||
typeof resolvedParameter.value.parameter.default === 'string' &&
|
||||
valueChanged(resolvedParameter.value.parameter.default)
|
||||
);
|
||||
|
||||
case 'addExpression': {
|
||||
const newValue = formatAsExpression(
|
||||
resolvedParameter.value.value,
|
||||
resolvedParameter.value.parameter.type,
|
||||
);
|
||||
valueChanged(typeof newValue === 'string' ? newValue : newValue.value);
|
||||
void setFocus();
|
||||
break;
|
||||
}
|
||||
|
||||
case 'removeExpression': {
|
||||
const newValue = parseFromExpression(
|
||||
resolvedParameter.value.value,
|
||||
resolvedExpression.value,
|
||||
resolvedParameter.value.parameter.type,
|
||||
resolvedParameter.value.parameter.default,
|
||||
(resolvedParameter.value.parameter.options ?? []).filter(isValidParameterOption),
|
||||
);
|
||||
if (typeof newValue === 'string') {
|
||||
valueChanged(newValue);
|
||||
} else if (newValue && typeof (newValue as { value?: unknown }).value === 'string') {
|
||||
valueChanged((newValue as { value: string }).value);
|
||||
}
|
||||
void setFocus();
|
||||
break;
|
||||
}
|
||||
|
||||
case 'formatHtml':
|
||||
htmlEditorEventBus.emit('format-html');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const valueChangedDebounced = debounce(valueChanged, { debounceTime: 0 });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -104,34 +280,96 @@ function valueChanged(value: string) {
|
||||
<div :class="$style.parameterOptionsWrapper">
|
||||
<div></div>
|
||||
<ParameterOptions
|
||||
v-if="isDisplayed"
|
||||
:parameter="resolvedParameter.parameter"
|
||||
:value="resolvedParameter.value"
|
||||
:is-read-only="false"
|
||||
:is-read-only="isReadOnly"
|
||||
@update:model-value="optionSelected"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="typeof resolvedParameter.value === 'string'" :class="$style.editorContainer">
|
||||
<div v-if="!isDisplayed" :class="[$style.content, $style.emptyContent]">
|
||||
<div :class="$style.emptyText">
|
||||
<N8nText color="text-base">
|
||||
{{ locale.baseText('nodeView.focusPanel.missingParameter') }}
|
||||
</N8nText>
|
||||
</div>
|
||||
</div>
|
||||
<ExpressionEditorModalInput
|
||||
v-if="expressionModeEnabled"
|
||||
v-else-if="expressionModeEnabled"
|
||||
ref="inputField"
|
||||
:model-value="resolvedParameter.value"
|
||||
:class="$style.editor"
|
||||
:is-read-only="false"
|
||||
:is-read-only="isReadOnly"
|
||||
:path="resolvedParameter.parameterPath"
|
||||
data-test-id="expression-modal-input"
|
||||
:target-node-parameter-context="{
|
||||
nodeName: resolvedParameter.node.name,
|
||||
parameterPath: resolvedParameter.parameterPath,
|
||||
}"
|
||||
@change="valueChanged($event.value)"
|
||||
:target-node-parameter-context="targetNodeParameterContext"
|
||||
@change="valueChangedDebounced($event.value)"
|
||||
/>
|
||||
<N8nInput
|
||||
v-else
|
||||
:model-value="resolvedParameter.value"
|
||||
:class="$style.editor"
|
||||
type="textarea"
|
||||
resize="none"
|
||||
@update:model-value="valueChanged($event)"
|
||||
></N8nInput>
|
||||
<template v-else-if="['json', 'string'].includes(resolvedParameter.parameter.type)">
|
||||
<CodeNodeEditor
|
||||
v-if="editorType === 'codeNodeEditor'"
|
||||
:id="resolvedParameter.parameterPath"
|
||||
:mode="codeEditorMode"
|
||||
:model-value="resolvedParameter.value"
|
||||
:default-value="resolvedParameter.parameter.default"
|
||||
:language="editorLanguage"
|
||||
:is-read-only="isReadOnly"
|
||||
:target-node-parameter-context="targetNodeParameterContext"
|
||||
fill-parent
|
||||
:disable-ask-ai="true"
|
||||
@update:model-value="valueChangedDebounced" />
|
||||
<HtmlEditor
|
||||
v-else-if="editorType === 'htmlEditor'"
|
||||
:model-value="resolvedParameter.value"
|
||||
:is-read-only="isReadOnly"
|
||||
:rows="editorRows"
|
||||
:disable-expression-coloring="!isHtmlNode"
|
||||
:disable-expression-completions="!isHtmlNode"
|
||||
fullscreen
|
||||
@update:model-value="valueChangedDebounced" />
|
||||
<CssEditor
|
||||
v-else-if="editorType === 'cssEditor'"
|
||||
:model-value="resolvedParameter.value"
|
||||
:is-read-only="isReadOnly"
|
||||
:rows="editorRows"
|
||||
fullscreen
|
||||
@update:model-value="valueChangedDebounced" />
|
||||
<SqlEditor
|
||||
v-else-if="editorType === 'sqlEditor'"
|
||||
:model-value="resolvedParameter.value"
|
||||
:dialect="getTypeOption('sqlDialect')"
|
||||
:is-read-only="isReadOnly"
|
||||
:rows="editorRows"
|
||||
fullscreen
|
||||
@update:model-value="valueChangedDebounced" />
|
||||
<JsEditor
|
||||
v-else-if="editorType === 'jsEditor'"
|
||||
:model-value="resolvedParameter.value"
|
||||
:is-read-only="isReadOnly"
|
||||
:rows="editorRows"
|
||||
:posthog-capture="shouldCaptureForPosthog"
|
||||
fill-parent
|
||||
@update:model-value="valueChangedDebounced" />
|
||||
<JsonEditor
|
||||
v-else-if="resolvedParameter.parameter.type === 'json'"
|
||||
:model-value="resolvedParameter.value"
|
||||
:is-read-only="isReadOnly"
|
||||
:rows="editorRows"
|
||||
fullscreen
|
||||
fill-parent
|
||||
@update:model-value="valueChangedDebounced" />
|
||||
<N8nInput
|
||||
v-else
|
||||
ref="inputField"
|
||||
:model-value="resolvedParameter.value"
|
||||
:class="$style.editor"
|
||||
:readonly="isReadOnly"
|
||||
type="textarea"
|
||||
resize="none"
|
||||
@update:model-value="valueChangedDebounced"
|
||||
></N8nInput
|
||||
></template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -151,7 +389,7 @@ function valueChanged(value: string) {
|
||||
flex-direction: column;
|
||||
width: 528px;
|
||||
border-left: 1px solid var(--color-foreground-base);
|
||||
background: var(--color-background-base);
|
||||
background: var(--color-foreground-light);
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
@@ -164,7 +402,7 @@ function valueChanged(value: string) {
|
||||
padding: var(--spacing-2xs);
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid var(--color-foreground-base);
|
||||
background: var(--color-background-xlight);
|
||||
background: var(--color-foreground-xlight);
|
||||
}
|
||||
|
||||
.content {
|
||||
@@ -193,7 +431,7 @@ function valueChanged(value: string) {
|
||||
.tabHeaderText {
|
||||
display: flex;
|
||||
gap: var(--spacing-4xs);
|
||||
align-items: center;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.buttonWrapper {
|
||||
@@ -216,7 +454,6 @@ function valueChanged(value: string) {
|
||||
}
|
||||
|
||||
.editorContainer {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
|
||||
@@ -224,7 +461,7 @@ function valueChanged(value: string) {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
font-size: var(--font-size-xs);
|
||||
font-size: var(--font-size-2xs);
|
||||
|
||||
:global(.cm-editor) {
|
||||
width: 100%;
|
||||
|
||||
@@ -3,10 +3,9 @@ import type {
|
||||
CalloutActionType,
|
||||
INodeParameters,
|
||||
INodeProperties,
|
||||
NodeParameterValue,
|
||||
NodeParameterValueType,
|
||||
} from 'n8n-workflow';
|
||||
import { ADD_FORM_NOTICE, deepCopy, getParameterValueByPath, NodeHelpers } from 'n8n-workflow';
|
||||
import { ADD_FORM_NOTICE, getParameterValueByPath, NodeHelpers } from 'n8n-workflow';
|
||||
import { computed, defineAsyncComponent, onErrorCaptured, ref, watch, type WatchSource } from 'vue';
|
||||
|
||||
import type { INodeUi, IUpdateInformation } from '@/Interface';
|
||||
@@ -19,7 +18,7 @@ import MultipleParameter from '@/components/MultipleParameter.vue';
|
||||
import ParameterInputFull from '@/components/ParameterInputFull.vue';
|
||||
import ResourceMapper from '@/components/ResourceMapper/ResourceMapper.vue';
|
||||
import { useI18n } from '@n8n/i18n';
|
||||
import { useNodeHelpers } from '@/composables/useNodeHelpers';
|
||||
import { useNodeSettingsParameters } from '@/composables/useNodeSettingsParameters';
|
||||
import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers';
|
||||
import { useMessage } from '@/composables/useMessage';
|
||||
import {
|
||||
@@ -32,15 +31,9 @@ import {
|
||||
import { useNDVStore } from '@/stores/ndv.store';
|
||||
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||
|
||||
import {
|
||||
getMainAuthField,
|
||||
getNodeAuthFields,
|
||||
isAuthRelatedParameter,
|
||||
} from '@/utils/nodeTypesUtils';
|
||||
import { captureException } from '@sentry/vue';
|
||||
import { computedWithControl } from '@vueuse/core';
|
||||
import get from 'lodash/get';
|
||||
import set from 'lodash/set';
|
||||
import {
|
||||
N8nCallout,
|
||||
N8nIcon,
|
||||
@@ -52,6 +45,7 @@ import {
|
||||
} from '@n8n/design-system';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useCalloutHelpers } from '@/composables/useCalloutHelpers';
|
||||
import { getParameterTypeOption } from '@/utils/nodeSettingsUtils';
|
||||
|
||||
const LazyFixedCollectionParameter = defineAsyncComponent(
|
||||
async () => await import('./FixedCollectionParameter.vue'),
|
||||
@@ -86,7 +80,7 @@ const nodeTypesStore = useNodeTypesStore();
|
||||
const ndvStore = useNDVStore();
|
||||
|
||||
const message = useMessage();
|
||||
const nodeHelpers = useNodeHelpers();
|
||||
const nodeSettingsParameters = useNodeSettingsParameters();
|
||||
const asyncLoadingError = ref(false);
|
||||
const workflowHelpers = useWorkflowHelpers();
|
||||
const i18n = useI18n();
|
||||
@@ -114,6 +108,8 @@ onErrorCaptured((e, component) => {
|
||||
return false;
|
||||
});
|
||||
|
||||
const node = computed(() => props.node ?? ndvStore.activeNode);
|
||||
|
||||
const nodeType = computed(() => {
|
||||
if (node.value) {
|
||||
return nodeTypesStore.getNodeType(node.value.type, node.value.typeVersion);
|
||||
@@ -121,13 +117,11 @@ const nodeType = computed(() => {
|
||||
return null;
|
||||
});
|
||||
|
||||
const node = computed(() => props.node ?? ndvStore.activeNode);
|
||||
|
||||
const filteredParameters = computedWithControl(
|
||||
[() => props.parameters, () => props.nodeValues] as WatchSource[],
|
||||
() => {
|
||||
const parameters = props.parameters.filter((parameter: INodeProperties) =>
|
||||
displayNodeParameter(parameter),
|
||||
shouldDisplayNodeParameter(parameter),
|
||||
);
|
||||
|
||||
if (node.value && node.value.type === FORM_TRIGGER_NODE_TYPE) {
|
||||
@@ -154,10 +148,6 @@ const filteredParameterNames = computed(() => {
|
||||
return filteredParameters.value.map((parameter) => parameter.name);
|
||||
});
|
||||
|
||||
const nodeAuthFields = computed(() => {
|
||||
return getNodeAuthFields(nodeType.value);
|
||||
});
|
||||
|
||||
const credentialsParameterIndex = computed(() => {
|
||||
return filteredParameters.value.findIndex((parameter) => parameter.type === 'credentials');
|
||||
});
|
||||
@@ -182,10 +172,6 @@ const indexToShowSlotAt = computed(() => {
|
||||
return Math.min(index, filteredParameters.value.length - 1);
|
||||
});
|
||||
|
||||
const mainNodeAuthField = computed(() => {
|
||||
return getMainAuthField(nodeType.value || null);
|
||||
});
|
||||
|
||||
watch(filteredParameterNames, (newValue, oldValue) => {
|
||||
if (newValue === undefined) {
|
||||
return;
|
||||
@@ -210,8 +196,8 @@ function updateFormTriggerParameters(parameters: INodeProperties[], triggerName:
|
||||
const connectedNodes = workflow.getChildNodes(triggerName);
|
||||
|
||||
const hasFormPage = connectedNodes.some((nodeName) => {
|
||||
const node = workflow.getNode(nodeName);
|
||||
return node && node.type === FORM_NODE_TYPE;
|
||||
const _node = workflow.getNode(nodeName);
|
||||
return _node && _node.type === FORM_NODE_TYPE;
|
||||
});
|
||||
|
||||
if (hasFormPage) {
|
||||
@@ -255,15 +241,15 @@ function updateWaitParameters(parameters: INodeProperties[], nodeName: string) {
|
||||
const parentNodes = workflow.getParentNodes(nodeName);
|
||||
|
||||
const formTriggerName = parentNodes.find(
|
||||
(node) => workflow.nodes[node].type === FORM_TRIGGER_NODE_TYPE,
|
||||
(_node) => workflow.nodes[_node].type === FORM_TRIGGER_NODE_TYPE,
|
||||
);
|
||||
if (!formTriggerName) return parameters;
|
||||
|
||||
const connectedNodes = workflow.getChildNodes(formTriggerName);
|
||||
|
||||
const hasFormPage = connectedNodes.some((nodeName) => {
|
||||
const node = workflow.getNode(nodeName);
|
||||
return node && node.type === FORM_NODE_TYPE;
|
||||
const hasFormPage = connectedNodes.some((_nodeName) => {
|
||||
const _node = workflow.getNode(_nodeName);
|
||||
return _node && _node.type === FORM_NODE_TYPE;
|
||||
});
|
||||
|
||||
if (hasFormPage) {
|
||||
@@ -294,7 +280,7 @@ function updateFormParameters(parameters: INodeProperties[], nodeName: string) {
|
||||
const parentNodes = workflow.getParentNodes(nodeName);
|
||||
|
||||
const formTriggerName = parentNodes.find(
|
||||
(node) => workflow.nodes[node].type === FORM_TRIGGER_NODE_TYPE,
|
||||
(_node) => workflow.nodes[_node].type === FORM_TRIGGER_NODE_TYPE,
|
||||
);
|
||||
|
||||
if (formTriggerName) return parameters.filter((parameter) => parameter.name !== 'triggerNotice');
|
||||
@@ -321,22 +307,7 @@ function getCredentialsDependencies() {
|
||||
}
|
||||
|
||||
function multipleValues(parameter: INodeProperties): boolean {
|
||||
return getArgument('multipleValues', parameter) === true;
|
||||
}
|
||||
|
||||
function getArgument(
|
||||
argumentName: string,
|
||||
parameter: INodeProperties,
|
||||
): string | string[] | number | boolean | undefined {
|
||||
if (parameter.typeOptions === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (parameter.typeOptions[argumentName] === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return parameter.typeOptions[argumentName];
|
||||
return getParameterTypeOption(parameter, 'multipleValues') === true;
|
||||
}
|
||||
|
||||
function getPath(parameterName: string): string {
|
||||
@@ -354,116 +325,15 @@ function deleteOption(optionName: string): void {
|
||||
emit('valueChanged', parameterData);
|
||||
}
|
||||
|
||||
function mustHideDuringCustomApiCall(
|
||||
parameter: INodeProperties,
|
||||
nodeValues: INodeParameters,
|
||||
): boolean {
|
||||
if (parameter?.displayOptions?.hide) return true;
|
||||
|
||||
const MUST_REMAIN_VISIBLE = [
|
||||
'authentication',
|
||||
'resource',
|
||||
'operation',
|
||||
...Object.keys(nodeValues),
|
||||
];
|
||||
|
||||
return !MUST_REMAIN_VISIBLE.includes(parameter.name);
|
||||
}
|
||||
|
||||
function displayNodeParameter(
|
||||
function shouldDisplayNodeParameter(
|
||||
parameter: INodeProperties,
|
||||
displayKey: 'displayOptions' | 'disabledOptions' = 'displayOptions',
|
||||
): boolean {
|
||||
if (parameter.type === 'hidden') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
nodeHelpers.isCustomApiCallSelected(props.nodeValues) &&
|
||||
mustHideDuringCustomApiCall(parameter, props.nodeValues)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Hide authentication related fields since it will now be part of credentials modal
|
||||
if (
|
||||
!KEEP_AUTH_IN_NDV_FOR_NODES.includes(node.value?.type || '') &&
|
||||
mainNodeAuthField.value &&
|
||||
(parameter.name === mainNodeAuthField.value?.name || shouldHideAuthRelatedParameter(parameter))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parameter[displayKey] === undefined) {
|
||||
// If it is not defined no need to do a proper check
|
||||
return true;
|
||||
}
|
||||
|
||||
const nodeValues: INodeParameters = {};
|
||||
let rawValues = props.nodeValues;
|
||||
if (props.path) {
|
||||
rawValues = get(props.nodeValues, props.path) as INodeParameters;
|
||||
}
|
||||
|
||||
if (!rawValues) {
|
||||
return false;
|
||||
}
|
||||
// Resolve expressions
|
||||
const resolveKeys = Object.keys(rawValues);
|
||||
let key: string;
|
||||
let i = 0;
|
||||
let parameterGotResolved = false;
|
||||
do {
|
||||
key = resolveKeys.shift() as string;
|
||||
const value = rawValues[key];
|
||||
if (typeof value === 'string' && value?.charAt(0) === '=') {
|
||||
// Contains an expression that
|
||||
if (
|
||||
value.includes('$parameter') &&
|
||||
resolveKeys.some((parameterName) => value.includes(parameterName))
|
||||
) {
|
||||
// Contains probably an expression of a missing parameter so skip
|
||||
resolveKeys.push(key);
|
||||
continue;
|
||||
} else {
|
||||
// Contains probably no expression with a missing parameter so resolve
|
||||
try {
|
||||
nodeValues[key] = workflowHelpers.resolveExpression(
|
||||
value,
|
||||
nodeValues,
|
||||
) as NodeParameterValue;
|
||||
} catch (e) {
|
||||
// If expression is invalid ignore
|
||||
nodeValues[key] = '';
|
||||
}
|
||||
parameterGotResolved = true;
|
||||
}
|
||||
} else {
|
||||
// Does not contain an expression, add directly
|
||||
nodeValues[key] = rawValues[key];
|
||||
}
|
||||
// TODO: Think about how to calculate this best
|
||||
if (i++ > 50) {
|
||||
// Make sure we do not get caught
|
||||
break;
|
||||
}
|
||||
} while (resolveKeys.length !== 0);
|
||||
|
||||
if (parameterGotResolved) {
|
||||
if (props.path) {
|
||||
rawValues = deepCopy(props.nodeValues);
|
||||
set(rawValues, props.path, nodeValues);
|
||||
return nodeHelpers.displayParameter(rawValues, parameter, props.path, node.value, displayKey);
|
||||
} else {
|
||||
return nodeHelpers.displayParameter(nodeValues, parameter, '', node.value, displayKey);
|
||||
}
|
||||
}
|
||||
|
||||
return nodeHelpers.displayParameter(
|
||||
return nodeSettingsParameters.shouldDisplayNodeParameter(
|
||||
props.nodeValues,
|
||||
node.value,
|
||||
parameter,
|
||||
props.path,
|
||||
node.value,
|
||||
displayKey,
|
||||
);
|
||||
}
|
||||
@@ -493,26 +363,15 @@ function getParameterIssues(parameter: INodeProperties): string[] {
|
||||
return issues.parameters?.[parameter.name] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles default node button parameter type actions
|
||||
* @param parameter
|
||||
*/
|
||||
|
||||
function shouldHideAuthRelatedParameter(parameter: INodeProperties): boolean {
|
||||
// TODO: For now, hide all fields that are used in authentication fields displayOptions
|
||||
// Ideally, we should check if any non-auth field depends on it before hiding it but
|
||||
// since there is no such case, omitting it to avoid additional computation
|
||||
return isAuthRelatedParameter(nodeAuthFields.value, parameter);
|
||||
}
|
||||
|
||||
function shouldShowOptions(parameter: INodeProperties): boolean {
|
||||
return parameter.type !== 'resourceMapper';
|
||||
}
|
||||
|
||||
function getDependentParametersValues(parameter: INodeProperties): string | null {
|
||||
const loadOptionsDependsOn = getArgument('loadOptionsDependsOn', parameter) as
|
||||
| string[]
|
||||
| undefined;
|
||||
const loadOptionsDependsOn = getParameterTypeOption<string[] | undefined>(
|
||||
parameter,
|
||||
'loadOptionsDependsOn',
|
||||
);
|
||||
|
||||
if (loadOptionsDependsOn === undefined) {
|
||||
return null;
|
||||
@@ -529,7 +388,7 @@ function getDependentParametersValues(parameter: INodeProperties): string | null
|
||||
}
|
||||
|
||||
return returnValues.join('|');
|
||||
} catch (error) {
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -792,7 +651,7 @@ const onCalloutDismiss = async (parameter: INodeProperties) => {
|
||||
:path="getPath(parameter.name)"
|
||||
:is-read-only="
|
||||
isReadOnly ||
|
||||
(parameter.disabledOptions && displayNodeParameter(parameter, 'disabledOptions'))
|
||||
(parameter.disabledOptions && shouldDisplayNodeParameter(parameter, 'disabledOptions'))
|
||||
"
|
||||
:hide-label="false"
|
||||
:node-values="nodeValues"
|
||||
|
||||
@@ -79,7 +79,7 @@ export const useCodeEditor = <L extends CodeEditorLanguage>({
|
||||
language: MaybeRefOrGetter<L>;
|
||||
editorValue?: MaybeRefOrGetter<string>;
|
||||
placeholder?: MaybeRefOrGetter<string>;
|
||||
targetNodeParameterContext?: MaybeRefOrGetter<TargetNodeParameterContext>;
|
||||
targetNodeParameterContext?: MaybeRefOrGetter<TargetNodeParameterContext | undefined>;
|
||||
extensions?: MaybeRefOrGetter<Extension[]>;
|
||||
isReadOnly?: MaybeRefOrGetter<boolean>;
|
||||
theme?: MaybeRefOrGetter<{
|
||||
|
||||
@@ -19,7 +19,7 @@ import { EXPRESSION_EDITOR_PARSER_TIMEOUT } from '@/constants';
|
||||
import { useNDVStore } from '@/stores/ndv.store';
|
||||
|
||||
import type { TargetItem, TargetNodeParameterContext } from '@/Interface';
|
||||
import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers';
|
||||
import { type ResolveParameterOptions, useWorkflowHelpers } from '@/composables/useWorkflowHelpers';
|
||||
import { highlighter } from '@/plugins/codemirror/resolvableHighlighter';
|
||||
import { closeCursorInfoBox } from '@/plugins/codemirror/tooltips/InfoBoxTooltip';
|
||||
import type { Html, Plaintext, RawSegment, Resolvable, Segment } from '@/types/expressions';
|
||||
@@ -311,9 +311,9 @@ export const useExpressionEditor = ({
|
||||
// e.g. credential modal
|
||||
result.resolved = Expression.resolveWithoutWorkflow(resolvable, toValue(additionalData));
|
||||
} else {
|
||||
let opts: Record<string, unknown> = {
|
||||
let opts: ResolveParameterOptions = {
|
||||
additionalKeys: toValue(additionalData),
|
||||
targetNodeParameterContext,
|
||||
contextNodeName: toValue(targetNodeParameterContext)?.nodeName,
|
||||
};
|
||||
if (
|
||||
toValue(targetNodeParameterContext) === undefined &&
|
||||
|
||||
@@ -11,22 +11,33 @@ import {
|
||||
} from 'n8n-workflow';
|
||||
import { useTelemetry } from './useTelemetry';
|
||||
import { useNodeHelpers } from './useNodeHelpers';
|
||||
import { useWorkflowHelpers } from './useWorkflowHelpers';
|
||||
import { useCanvasOperations } from './useCanvasOperations';
|
||||
import { useExternalHooks } from './useExternalHooks';
|
||||
import type { INodeUi, IUpdateInformation } from '@/Interface';
|
||||
import { updateDynamicConnections, updateParameterByPath } from '@/utils/nodeSettingsUtils';
|
||||
import {
|
||||
mustHideDuringCustomApiCall,
|
||||
updateDynamicConnections,
|
||||
updateParameterByPath,
|
||||
} from '@/utils/nodeSettingsUtils';
|
||||
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||
import { useFocusPanelStore } from '@/stores/focusPanel.store';
|
||||
import { useNDVStore } from '@/stores/ndv.store';
|
||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||
import { CUSTOM_API_CALL_KEY } from '@/constants';
|
||||
import { CUSTOM_API_CALL_KEY, KEEP_AUTH_IN_NDV_FOR_NODES } from '@/constants';
|
||||
import { omitKey } from '@/utils/objectUtils';
|
||||
import {
|
||||
getMainAuthField,
|
||||
getNodeAuthFields,
|
||||
isAuthRelatedParameter,
|
||||
} from '@/utils/nodeTypesUtils';
|
||||
|
||||
export function useNodeSettingsParameters() {
|
||||
const workflowsStore = useWorkflowsStore();
|
||||
const nodeTypesStore = useNodeTypesStore();
|
||||
const telemetry = useTelemetry();
|
||||
const nodeHelpers = useNodeHelpers();
|
||||
const workflowHelpers = useWorkflowHelpers();
|
||||
const canvasOperations = useCanvasOperations();
|
||||
const externalHooks = useExternalHooks();
|
||||
|
||||
@@ -236,6 +247,113 @@ export function useNodeSettingsParameters() {
|
||||
focusPanelStore.focusPanelActive = true;
|
||||
}
|
||||
|
||||
function shouldDisplayNodeParameter(
|
||||
nodeParameters: INodeParameters,
|
||||
node: INodeUi | null,
|
||||
parameter: INodeProperties,
|
||||
path: string | undefined = '',
|
||||
displayKey: 'displayOptions' | 'disabledOptions' = 'displayOptions',
|
||||
): boolean {
|
||||
if (parameter.type === 'hidden') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
nodeHelpers.isCustomApiCallSelected(nodeParameters) &&
|
||||
mustHideDuringCustomApiCall(parameter, nodeParameters)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const nodeType = !node ? null : nodeTypesStore.getNodeType(node.type, node.typeVersion);
|
||||
|
||||
// TODO: For now, hide all fields that are used in authentication fields displayOptions
|
||||
// Ideally, we should check if any non-auth field depends on it before hiding it but
|
||||
// since there is no such case, omitting it to avoid additional computation
|
||||
const shouldHideAuthRelatedParameter = isAuthRelatedParameter(
|
||||
getNodeAuthFields(nodeType),
|
||||
parameter,
|
||||
);
|
||||
|
||||
const mainNodeAuthField = getMainAuthField(nodeType);
|
||||
|
||||
// Hide authentication related fields since it will now be part of credentials modal
|
||||
if (
|
||||
!KEEP_AUTH_IN_NDV_FOR_NODES.includes(node?.type ?? '') &&
|
||||
mainNodeAuthField &&
|
||||
(parameter.name === mainNodeAuthField.name || shouldHideAuthRelatedParameter)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parameter[displayKey] === undefined) {
|
||||
// If it is not defined no need to do a proper check
|
||||
return true;
|
||||
}
|
||||
|
||||
const nodeParams: INodeParameters = {};
|
||||
let rawValues = nodeParameters;
|
||||
if (path) {
|
||||
rawValues = get(nodeParameters, path) as INodeParameters;
|
||||
}
|
||||
|
||||
if (!rawValues) {
|
||||
return false;
|
||||
}
|
||||
// Resolve expressions
|
||||
const resolveKeys = Object.keys(rawValues);
|
||||
let key: string;
|
||||
let i = 0;
|
||||
let parameterGotResolved = false;
|
||||
do {
|
||||
key = resolveKeys.shift() as string;
|
||||
const value = rawValues[key];
|
||||
if (typeof value === 'string' && value?.charAt(0) === '=') {
|
||||
// Contains an expression that
|
||||
if (
|
||||
value.includes('$parameter') &&
|
||||
resolveKeys.some((parameterName) => value.includes(parameterName))
|
||||
) {
|
||||
// Contains probably an expression of a missing parameter so skip
|
||||
resolveKeys.push(key);
|
||||
continue;
|
||||
} else {
|
||||
// Contains probably no expression with a missing parameter so resolve
|
||||
try {
|
||||
nodeParams[key] = workflowHelpers.resolveExpression(
|
||||
value,
|
||||
nodeParams,
|
||||
) as NodeParameterValue;
|
||||
} catch (e) {
|
||||
// If expression is invalid ignore
|
||||
nodeParams[key] = '';
|
||||
}
|
||||
parameterGotResolved = true;
|
||||
}
|
||||
} else {
|
||||
// Does not contain an expression, add directly
|
||||
nodeParams[key] = rawValues[key];
|
||||
}
|
||||
// TODO: Think about how to calculate this best
|
||||
if (i++ > 50) {
|
||||
// Make sure we do not get caught
|
||||
break;
|
||||
}
|
||||
} while (resolveKeys.length !== 0);
|
||||
|
||||
if (parameterGotResolved) {
|
||||
if (path) {
|
||||
rawValues = deepCopy(nodeParameters);
|
||||
set(rawValues, path, nodeParams);
|
||||
return nodeHelpers.displayParameter(rawValues, parameter, path, node, displayKey);
|
||||
} else {
|
||||
return nodeHelpers.displayParameter(nodeParams, parameter, '', node, displayKey);
|
||||
}
|
||||
}
|
||||
|
||||
return nodeHelpers.displayParameter(nodeParameters, parameter, path, node, displayKey);
|
||||
}
|
||||
|
||||
function shouldSkipParamValidation(value: string | number | boolean | null) {
|
||||
return typeof value === 'string' && value.includes(CUSTOM_API_CALL_KEY);
|
||||
}
|
||||
@@ -243,6 +361,7 @@ export function useNodeSettingsParameters() {
|
||||
return {
|
||||
nodeValues,
|
||||
setValue,
|
||||
shouldDisplayNodeParameter,
|
||||
updateParameterByPath,
|
||||
updateNodeParameter,
|
||||
handleFocus,
|
||||
|
||||
@@ -126,18 +126,20 @@ export function dollarOptions(context: CompletionContext): Completion[] {
|
||||
|
||||
if (receivesNoBinaryData(targetNodeParameterContext?.nodeName)) SKIP.add('$binary');
|
||||
|
||||
const previousNodesCompletions = autocompletableNodeNames().map((nodeName) => {
|
||||
const label = `$('${escapeMappingString(nodeName)}')`;
|
||||
return {
|
||||
label,
|
||||
info: createInfoBoxRenderer({
|
||||
name: label,
|
||||
returnType: 'Object',
|
||||
description: i18n.baseText('codeNodeEditor.completer.$()', { interpolate: { nodeName } }),
|
||||
}),
|
||||
section: PREVIOUS_NODES_SECTION,
|
||||
};
|
||||
});
|
||||
const previousNodesCompletions = autocompletableNodeNames(targetNodeParameterContext).map(
|
||||
(nodeName) => {
|
||||
const label = `$('${escapeMappingString(nodeName)}')`;
|
||||
return {
|
||||
label,
|
||||
info: createInfoBoxRenderer({
|
||||
name: label,
|
||||
returnType: 'Object',
|
||||
description: i18n.baseText('codeNodeEditor.completer.$()', { interpolate: { nodeName } }),
|
||||
}),
|
||||
section: PREVIOUS_NODES_SECTION,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
return recommendedCompletions
|
||||
.concat(ROOT_DOLLAR_COMPLETIONS)
|
||||
|
||||
@@ -215,11 +215,11 @@ export const hasActiveNode = (targetNodeParameterContext?: TargetNodeParameterCo
|
||||
export const isSplitInBatchesAbsent = () =>
|
||||
!useWorkflowsStore().workflow.nodes.some((node) => node.type === SPLIT_IN_BATCHES_NODE_TYPE);
|
||||
|
||||
export function autocompletableNodeNames(contextNodeName?: string) {
|
||||
export function autocompletableNodeNames(targetNodeParameterContext?: TargetNodeParameterContext) {
|
||||
const activeNode =
|
||||
contextNodeName === undefined
|
||||
targetNodeParameterContext === undefined
|
||||
? useNDVStore().activeNode
|
||||
: useWorkflowsStore().getNodeByName(contextNodeName);
|
||||
: useWorkflowsStore().getNodeByName(targetNodeParameterContext.nodeName);
|
||||
|
||||
if (!activeNode) return [];
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
} from '../../completions/utils';
|
||||
import { typescriptWorkerFacet } from './facet';
|
||||
import { blockCommentSnippet, snippets } from './snippets';
|
||||
import { TARGET_NODE_PARAMETER_FACET } from '../../completions/constants';
|
||||
|
||||
const START_CHARACTERS = ['"', "'", '(', '.', '@'];
|
||||
const START_CHARACTERS_REGEX = /[\.\(\'\"\@]/;
|
||||
@@ -30,7 +31,7 @@ export const matchText = (context: CompletionContext) => {
|
||||
|
||||
export const typescriptCompletionSource: CompletionSource = async (context) => {
|
||||
const { worker } = context.state.facet(typescriptWorkerFacet);
|
||||
|
||||
const targetNodeParameter = context.state.facet(TARGET_NODE_PARAMETER_FACET);
|
||||
const word = matchText(context);
|
||||
|
||||
const blockComment = context.matchBefore(/\/\*?\*?/);
|
||||
@@ -55,7 +56,7 @@ export const typescriptCompletionSource: CompletionSource = async (context) => {
|
||||
if (opt.label === '$()') {
|
||||
return [
|
||||
opt,
|
||||
...autocompletableNodeNames().map((name) => ({
|
||||
...autocompletableNodeNames(targetNodeParameter).map((name) => ({
|
||||
...opt,
|
||||
label: `$('${escapeMappingString(name)}')`,
|
||||
})),
|
||||
|
||||
@@ -28,7 +28,7 @@ export function useTypescript(
|
||||
view: MaybeRefOrGetter<EditorView | undefined>,
|
||||
mode: MaybeRefOrGetter<CodeExecutionMode>,
|
||||
id: MaybeRefOrGetter<string>,
|
||||
targetNodeParameterContext?: MaybeRefOrGetter<TargetNodeParameterContext>,
|
||||
targetNodeParameterContext?: MaybeRefOrGetter<TargetNodeParameterContext | undefined>,
|
||||
) {
|
||||
const { getInputDataWithPinned, getSchemaForExecutionData } = useDataSchema();
|
||||
const ndvStore = useNDVStore();
|
||||
@@ -47,7 +47,7 @@ export function useTypescript(
|
||||
{
|
||||
id: toValue(id),
|
||||
content: Comlink.proxy((toValue(view)?.state.doc ?? Text.empty).toJSON()),
|
||||
allNodeNames: autocompletableNodeNames(),
|
||||
allNodeNames: autocompletableNodeNames(toValue(targetNodeParameterContext)),
|
||||
variables: useEnvironmentsStore().variables.map((v) => v.key),
|
||||
inputNodeNames: activeNodeName
|
||||
? workflowsStore
|
||||
|
||||
@@ -259,6 +259,22 @@ export function isValidParameterOption(
|
||||
return 'value' in option && isPresent(option.value) && isPresent(option.name);
|
||||
}
|
||||
|
||||
export function mustHideDuringCustomApiCall(
|
||||
parameter: INodeProperties,
|
||||
nodeParameters: INodeParameters,
|
||||
): boolean {
|
||||
if (parameter?.displayOptions?.hide) return true;
|
||||
|
||||
const MUST_REMAIN_VISIBLE = [
|
||||
'authentication',
|
||||
'resource',
|
||||
'operation',
|
||||
...Object.keys(nodeParameters),
|
||||
];
|
||||
|
||||
return !MUST_REMAIN_VISIBLE.includes(parameter.name);
|
||||
}
|
||||
|
||||
export function nameIsParameter(
|
||||
parameterData: IUpdateInformation,
|
||||
): parameterData is IUpdateInformation & { name: `parameters.${string}` } {
|
||||
|
||||
@@ -2157,7 +2157,7 @@ onBeforeUnmount(() => {
|
||||
/>
|
||||
</Suspense>
|
||||
</WorkflowCanvas>
|
||||
<FocusPanel v-if="isFocusPanelFeatureEnabled" :executable="!isCanvasReadOnly" />
|
||||
<FocusPanel v-if="isFocusPanelFeatureEnabled" :is-canvas-read-only="isCanvasReadOnly" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user