Add experimental store change

This commit is contained in:
Charlie Kolb 2025-06-26 16:38:54 +02:00
parent fcf559b93d
commit bbc216ba49
No known key found for this signature in database
8 changed files with 92 additions and 31 deletions

View File

@ -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() {
<n8n-icon icon="arrow-right" color="text-base" />
</div>
</div>
<div v-if="focusedNodeParameter" :class="$style.content">
<div v-if="resolvedParameter" :class="$style.content">
<div :class="$style.tabHeader">
<div :class="$style.tabHeaderText">
<N8nText color="text-dark" size="small">{{
focusedNodeParameter.parameter.displayName
resolvedParameter.parameter.displayName
}}</N8nText>
<N8nText color="text-base" size="xsmall">{{ focusedNodeParameter.nodeName }}</N8nText>
<N8nText color="text-base" size="xsmall">{{ resolvedParameter.node.name }}</N8nText>
</div>
<N8nTooltip>
<n8n-button
@ -71,31 +96,32 @@ function executeFocusedNode() {
<div></div>
<ParameterOptions
:parameter="focusedNodeParameter.parameter"
:value="focusedNodeParameter.value"
:value="resolvedParameter.value"
:is-read-only="false"
@update:model-value="optionSelected"
/>
</div>
<div :class="$style.editorContainer">
<div v-if="typeof resolvedParameter.value === 'string'" :class="$style.editorContainer">
<ExpressionEditorModalInput
v-if="expressionModeEnabled"
:model-value="focusedNodeParameter.value"
:model-value="resolvedParameter.value"
:class="$style.editor"
:is-read-only="false"
:path="focusedNodeParameter.parameterPath"
:path="resolvedParameter.parameterPath"
data-test-id="expression-modal-input"
:target-node-parameter-context="{
nodeName: focusedNodeParameter.nodeName,
nodeName: resolvedParameter.node.name,
parameterPath: focusedNodeParameter.parameterPath,
}"
@change="valueChanged"
@change="valueChanged($event.value)"
/>
<N8nInput
v-else
v-model="focusedNodeParameter.value"
v-model="resolvedParameter.value"
:class="$style.editor"
type="textarea"
resize="none"
@change="valueChanged"
></N8nInput>
</div>
</div>

View File

@ -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.

View File

@ -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) {

View File

@ -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<T extends NodeParameterValueType = NodeParameterValueType>(
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 {

View File

@ -61,7 +61,7 @@ const shouldShowOptions = computed(() => {
}
if (['codeNodeEditor', 'sqlEditor'].includes(props.parameter.typeOptions?.editor ?? '')) {
return false;
// return false;
}
if (props.showOptions) {

View File

@ -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,

View File

@ -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<FocusedNodeParameter[]>([]);
const _focusedNodeParameters = ref<FocusedNodeParameter[]>([]);
// An unenriched parameter indicates a missing nodeId
const focusedNodeParameters = computed<Array<RichFocusedNodeParameter | FocusedNodeParameter>>(
() =>
_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,
};

View File

@ -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);
}