diff --git a/packages/frontend/editor-ui/src/components/FocusPanel.vue b/packages/frontend/editor-ui/src/components/FocusPanel.vue index 6f545343259..9bd1f833b0e 100644 --- a/packages/frontend/editor-ui/src/components/FocusPanel.vue +++ b/packages/frontend/editor-ui/src/components/FocusPanel.vue @@ -4,29 +4,54 @@ import { N8nText, N8nInput } from '@n8n/design-system'; import { computed } from 'vue'; import { useI18n } from '@n8n/i18n'; import { isValueExpression } from '@/utils/nodeTypesUtils'; +import { useNodeSettingsParameters } from '@/composables/useNodeSettingsParameters'; +import { useWorkflowsStore } from '@/stores/workflows.store'; +import { useNodeTypesStore } from '@/stores/nodeTypes.store'; defineOptions({ name: 'FocusPanel' }); const locale = useI18n(); const focusPanelStore = useFocusPanelStore(); +const nodeTypesStore = useNodeTypesStore(); +const nodeSettingsParameters = useNodeSettingsParameters(); const focusedNodeParameter = computed(() => focusPanelStore.focusedNodeParameters[0]); +const resolvedParameter = computed(() => + focusPanelStore.isRichParameter(focusedNodeParameter.value) + ? focusedNodeParameter.value + : undefined, +); const focusPanelActive = computed(() => focusPanelStore.focusPanelActive); +const isCodeBlock = computed(() => resolvedParameter.value?.parameterPath === 'parameters.jsCode'); + const expressionModeEnabled = computed( () => - focusedNodeParameter.value && - isValueExpression(focusedNodeParameter.value.parameter, focusedNodeParameter.value.value), + resolvedParameter.value && + isValueExpression(resolvedParameter.value.parameter, resolvedParameter.value.value), +); + +const isToolNode = computed(() => + resolvedParameter.value ? nodeTypesStore.isToolNode(resolvedParameter.value?.node.type) : false, ); function optionSelected() { // TODO: Handle the option selected (command: string) from the dropdown } -function valueChanged() { - // TODO: Update parameter value +function valueChanged(value: string) { + if (resolvedParameter.value === undefined) { + return; + } + + nodeSettingsParameters.updateNodeParameter( + { value, name: `parameters.${focusedNodeParameter.value.parameter.name}` }, + value, + resolvedParameter.value.node, + isToolNode.value, + ); } function executeFocusedNode() { @@ -44,13 +69,13 @@ function executeFocusedNode() { -
+
{{ - focusedNodeParameter.parameter.displayName + resolvedParameter.parameter.displayName }} - {{ focusedNodeParameter.nodeName }} + {{ resolvedParameter.node.name }}
-
+
diff --git a/packages/frontend/editor-ui/src/components/Node/NodeCreator/utils.ts b/packages/frontend/editor-ui/src/components/Node/NodeCreator/utils.ts index a362b12014b..bc1dcab1c54 100644 --- a/packages/frontend/editor-ui/src/components/Node/NodeCreator/utils.ts +++ b/packages/frontend/editor-ui/src/components/Node/NodeCreator/utils.ts @@ -121,6 +121,7 @@ export function searchNodes(searchFilter: string, items: INodeCreateElement[]) { } const trimmedFilter = removeTrailingTrigger(searchFilter).toLowerCase(); + debugger; // We have a snapshot of this call in sublimeSearch.test.ts to assert practical order for some cases // Please update the snapshots per the README next to the the snapshots if you modify items significantly. diff --git a/packages/frontend/editor-ui/src/components/ParameterInput.vue b/packages/frontend/editor-ui/src/components/ParameterInput.vue index b92e9313eea..52ad88e9ea3 100644 --- a/packages/frontend/editor-ui/src/components/ParameterInput.vue +++ b/packages/frontend/editor-ui/src/components/ParameterInput.vue @@ -1048,10 +1048,9 @@ async function optionSelected(command: string) { if (node.value && command === 'focus') { focusPanelStore.setFocusedNodeParameter({ - nodeName: node.value.name, + nodeId: node.value.id, parameterPath: props.path, parameter: props.parameter, - value: modelValueString.value, }); if (ndvStore.activeNode) { diff --git a/packages/frontend/editor-ui/src/components/ParameterInputList.vue b/packages/frontend/editor-ui/src/components/ParameterInputList.vue index f4aae2b43e3..1060f3195aa 100644 --- a/packages/frontend/editor-ui/src/components/ParameterInputList.vue +++ b/packages/frontend/editor-ui/src/components/ParameterInputList.vue @@ -6,7 +6,7 @@ import type { NodeParameterValue, NodeParameterValueType, } from 'n8n-workflow'; -import { ADD_FORM_NOTICE, deepCopy, NodeHelpers } from 'n8n-workflow'; +import { ADD_FORM_NOTICE, deepCopy, getParameterValueByPath, NodeHelpers } from 'n8n-workflow'; import { computed, defineAsyncComponent, onErrorCaptured, ref, watch, type WatchSource } from 'vue'; import type { IUpdateInformation } from '@/Interface'; @@ -538,7 +538,7 @@ function getDependentParametersValues(parameter: INodeProperties): string | null function getParameterValue( name: string, ): T { - return nodeHelpers.getParameterValue(props.nodeValues, name, props.path) as T; + return getParameterValueByPath(props.nodeValues, name, props.path) as T; } function isRagStarterCallout(parameter: INodeProperties): boolean { diff --git a/packages/frontend/editor-ui/src/components/ParameterOptions.vue b/packages/frontend/editor-ui/src/components/ParameterOptions.vue index 416fb9e40ea..8955811785c 100644 --- a/packages/frontend/editor-ui/src/components/ParameterOptions.vue +++ b/packages/frontend/editor-ui/src/components/ParameterOptions.vue @@ -61,7 +61,7 @@ const shouldShowOptions = computed(() => { } if (['codeNodeEditor', 'sqlEditor'].includes(props.parameter.typeOptions?.editor ?? '')) { - return false; + // return false; } if (props.showOptions) { diff --git a/packages/frontend/editor-ui/src/composables/useNodeHelpers.ts b/packages/frontend/editor-ui/src/composables/useNodeHelpers.ts index 163e42523f4..f26b1c3b546 100644 --- a/packages/frontend/editor-ui/src/composables/useNodeHelpers.ts +++ b/packages/frontend/editor-ui/src/composables/useNodeHelpers.ts @@ -41,7 +41,6 @@ import { isObject } from '@/utils/objectUtils'; import { useWorkflowsStore } from '@/stores/workflows.store'; import { useNodeTypesStore } from '@/stores/nodeTypes.store'; import { useCredentialsStore } from '@/stores/credentials.store'; -import get from 'lodash/get'; import { useI18n } from '@n8n/i18n'; import { EnableNodeToggleCommand } from '@/models/history'; import { useTelemetry } from './useTelemetry'; @@ -92,10 +91,6 @@ export function useNodeHelpers() { return false; } - function getParameterValue(nodeValues: INodeParameters, parameterName: string, path: string) { - return get(nodeValues, path ? path + '.' + parameterName : parameterName); - } - // Returns if the given parameter should be displayed or not function displayParameter( nodeValues: INodeParameters, @@ -1005,7 +1000,6 @@ export function useNodeHelpers() { return { hasProxyAuth, isCustomApiCallSelected, - getParameterValue, displayParameter, getNodeIssues, updateNodesInputIssues, diff --git a/packages/frontend/editor-ui/src/stores/focusPanel.store.ts b/packages/frontend/editor-ui/src/stores/focusPanel.store.ts index 5ce64c1f94d..87ebc227ca6 100644 --- a/packages/frontend/editor-ui/src/stores/focusPanel.store.ts +++ b/packages/frontend/editor-ui/src/stores/focusPanel.store.ts @@ -1,22 +1,48 @@ import { STORES } from '@n8n/stores'; import { defineStore } from 'pinia'; -import { ref } from 'vue'; +import { computed, ref } from 'vue'; -import type { INodeProperties } from 'n8n-workflow'; +import { + getParameterValueByPath, + type NodeParameterValueType, + type INode, + type INodeProperties, +} from 'n8n-workflow'; +import { useWorkflowsStore } from './workflows.store'; type FocusedNodeParameter = { - nodeName: string; + nodeId: string; parameter: INodeProperties; parameterPath: string; - value: string; +}; + +export type RichFocusedNodeParameter = FocusedNodeParameter & { + node: INode; + value: NodeParameterValueType; }; export const useFocusPanelStore = defineStore(STORES.FOCUS_PANEL, () => { + const workflowsStore = useWorkflowsStore(); + const focusPanelActive = ref(false); - const focusedNodeParameters = ref([]); + const _focusedNodeParameters = ref([]); + + // An unenriched parameter indicates a missing nodeId + const focusedNodeParameters = computed>( + () => + _focusedNodeParameters.value.map((x) => { + const node = workflowsStore.getNodeById(x.nodeId); + if (!node) return x; + return { + ...x, + node, + value: getParameterValueByPath(node?.parameters ?? {}, x.parameter.name, x.parameterPath), + } satisfies RichFocusedNodeParameter; + }), + ); const setFocusedNodeParameter = (nodeParameter: FocusedNodeParameter) => { - focusedNodeParameters.value = [ + _focusedNodeParameters.value = [ nodeParameter, // Uncomment when tabs are implemented // ...focusedNodeParameters.value.filter((p) => p.parameterPath !== nodeParameter.parameterPath), @@ -31,10 +57,17 @@ export const useFocusPanelStore = defineStore(STORES.FOCUS_PANEL, () => { focusPanelActive.value = !focusPanelActive.value; }; + function isRichParameter( + p: RichFocusedNodeParameter | FocusedNodeParameter, + ): p is RichFocusedNodeParameter { + return 'value' in p && 'node' in p; + } + return { focusPanelActive, focusedNodeParameters, setFocusedNodeParameter, + isRichParameter, closeFocusPanel, toggleFocusPanel, }; diff --git a/packages/workflow/src/node-helpers.ts b/packages/workflow/src/node-helpers.ts index 0fc0f59cf3c..d6f0250c42b 100644 --- a/packages/workflow/src/node-helpers.ts +++ b/packages/workflow/src/node-helpers.ts @@ -1758,3 +1758,11 @@ export function getSubworkflowId(node: INode): string | undefined { } return; } + +export function getParameterValue( + nodeValues: INodeParameters, + parameterName: string, + path: string, +) { + return get(nodeValues, path ? path + '.' + parameterName : parameterName); +}