feat(ui): move invocation templates out of redux

Templates are stored in nanostores. All hooks, selectors, etc are reworked to reference the nanostore.
This commit is contained in:
psychedelicious 2024-05-16 17:00:08 +10:00
parent f6a44681a8
commit 1d884fb794
23 changed files with 146 additions and 265 deletions

View File

@ -25,7 +25,7 @@ export const addUpdateAllNodesRequestedListener = (startAppListening: AppStartLi
unableToUpdateCount++;
return;
}
if (!getNeedsUpdate(node, template)) {
if (!getNeedsUpdate(node.data, template)) {
// No need to increment the count here, since we're not actually updating
return;
}

View File

@ -1,6 +1,8 @@
import { Badge, Flex } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import { useAppSelector } from 'app/store/storeHooks';
import { useChakraThemeTokens } from 'common/hooks/useChakraThemeTokens';
import { $templates } from 'features/nodes/store/nodesSlice';
import { memo, useMemo } from 'react';
import type { EdgeProps } from 'reactflow';
import { BaseEdge, EdgeLabelRenderer, getBezierPath } from 'reactflow';
@ -22,9 +24,10 @@ const InvocationCollapsedEdge = ({
sourceHandleId,
targetHandleId,
}: EdgeProps<{ count: number }>) => {
const templates = useStore($templates);
const selector = useMemo(
() => makeEdgeSelector(source, sourceHandleId, target, targetHandleId, selected),
[selected, source, sourceHandleId, target, targetHandleId]
() => makeEdgeSelector(templates, source, sourceHandleId, target, targetHandleId, selected),
[templates, selected, source, sourceHandleId, target, targetHandleId]
);
const { isSelected, shouldAnimate } = useAppSelector(selector);

View File

@ -1,5 +1,7 @@
import { Flex, Text } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import { useAppSelector } from 'app/store/storeHooks';
import { $templates } from 'features/nodes/store/nodesSlice';
import type { CSSProperties } from 'react';
import { memo, useMemo } from 'react';
import type { EdgeProps } from 'reactflow';
@ -21,9 +23,10 @@ const InvocationDefaultEdge = ({
sourceHandleId,
targetHandleId,
}: EdgeProps) => {
const templates = useStore($templates);
const selector = useMemo(
() => makeEdgeSelector(source, sourceHandleId, target, targetHandleId, selected),
[source, sourceHandleId, target, targetHandleId, selected]
() => makeEdgeSelector(templates, source, sourceHandleId, target, targetHandleId, selected),
[templates, source, sourceHandleId, target, targetHandleId, selected]
);
const { isSelected, shouldAnimate, stroke, label } = useAppSelector(selector);

View File

@ -1,8 +1,8 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { colorTokenToCssVar } from 'common/util/colorTokenToCssVar';
import { selectNodesSlice } from 'features/nodes/store/nodesSlice';
import { selectFieldOutputTemplate, selectNodeTemplate } from 'features/nodes/store/selectors';
import { selectWorkflowSettingsSlice } from 'features/nodes/store/workflowSettingsSlice';
import type { InvocationTemplate } from 'features/nodes/types/invocation';
import { isInvocationNode } from 'features/nodes/types/invocation';
import { getFieldColor } from './getEdgeColor';
@ -15,6 +15,7 @@ const defaultReturnValue = {
};
export const makeEdgeSelector = (
templates: Record<string, InvocationTemplate>,
source: string,
sourceHandleId: string | null | undefined,
target: string,
@ -35,13 +36,14 @@ export const makeEdgeSelector = (
return defaultReturnValue;
}
const outputFieldTemplate = selectFieldOutputTemplate(nodes, sourceNode.id, sourceHandleId);
const sourceNodeTemplate = templates[sourceNode.data.type];
const targetNodeTemplate = templates[targetNode.data.type];
const outputFieldTemplate = sourceNodeTemplate?.outputs[sourceHandleId];
const sourceType = isInvocationToInvocationEdge ? outputFieldTemplate?.type : undefined;
const stroke = sourceType && workflowSettings.shouldColorEdges ? getFieldColor(sourceType) : colorTokenToCssVar('base.500');
const sourceNodeTemplate = selectNodeTemplate(nodes, sourceNode.id);
const targetNodeTemplate = selectNodeTemplate(nodes, targetNode.id);
const stroke =
sourceType && workflowSettings.shouldColorEdges ? getFieldColor(sourceType) : colorTokenToCssVar('base.500');
const label = `${sourceNodeTemplate?.title || sourceNode.data?.label} -> ${targetNodeTemplate?.title || targetNode.data?.label}`;

View File

@ -1,4 +1,5 @@
import { useStore } from '@nanostores/react';
import { useAppSelector } from 'app/store/storeHooks';
import InvocationNode from 'features/nodes/components/flow/nodes/Invocation/InvocationNode';
import { $templates } from 'features/nodes/store/nodesSlice';
import type { InvocationNodeData } from 'features/nodes/types/invocation';
@ -12,6 +13,11 @@ const InvocationNodeWrapper = (props: NodeProps<InvocationNodeData>) => {
const { id: nodeId, type, isOpen, label } = data;
const templates = useStore($templates);
const hasTemplate = useMemo(() => Boolean(templates[type]), [templates, type]);
const nodeExists = useAppSelector((s) => Boolean(s.nodes.present.nodes.find((n) => n.id === nodeId)));
if (!nodeExists) {
return null;
}
if (!hasTemplate) {
return (

View File

@ -1,31 +1,19 @@
import { EMPTY_ARRAY } from 'app/store/constants';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks';
import { selectNodesSlice } from 'features/nodes/store/nodesSlice';
import { selectNodeTemplate } from 'features/nodes/store/selectors';
import { useNodeTemplate } from 'features/nodes/hooks/useNodeTemplate';
import { getSortedFilteredFieldNames } from 'features/nodes/util/node/getSortedFilteredFieldNames';
import { TEMPLATE_BUILDER_MAP } from 'features/nodes/util/schema/buildFieldInputTemplate';
import { keys, map } from 'lodash-es';
import { useMemo } from 'react';
export const useAnyOrDirectInputFieldNames = (nodeId: string): string[] => {
const selector = useMemo(
() =>
createMemoizedSelector(selectNodesSlice, (nodes) => {
const template = selectNodeTemplate(nodes, nodeId);
if (!template) {
return EMPTY_ARRAY;
}
const fields = map(template.inputs).filter(
(field) =>
(['any', 'direct'].includes(field.input) || field.type.isCollectionOrScalar) &&
keys(TEMPLATE_BUILDER_MAP).includes(field.type.name)
);
return getSortedFilteredFieldNames(fields);
}),
[nodeId]
);
const template = useNodeTemplate(nodeId);
const fieldNames = useMemo(() => {
const fields = map(template.inputs).filter(
(field) =>
(['any', 'direct'].includes(field.input) || field.type.isCollectionOrScalar) &&
keys(TEMPLATE_BUILDER_MAP).includes(field.type.name)
);
return getSortedFilteredFieldNames(fields);
}, [template]);
const fieldNames = useAppSelector(selector);
return fieldNames;
};

View File

@ -1,34 +1,20 @@
import { EMPTY_ARRAY } from 'app/store/constants';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks';
import { selectNodesSlice } from 'features/nodes/store/nodesSlice';
import { selectNodeTemplate } from 'features/nodes/store/selectors';
import { useNodeTemplate } from 'features/nodes/hooks/useNodeTemplate';
import { getSortedFilteredFieldNames } from 'features/nodes/util/node/getSortedFilteredFieldNames';
import { TEMPLATE_BUILDER_MAP } from 'features/nodes/util/schema/buildFieldInputTemplate';
import { keys, map } from 'lodash-es';
import { useMemo } from 'react';
export const useConnectionInputFieldNames = (nodeId: string): string[] => {
const selector = useMemo(
() =>
createMemoizedSelector(selectNodesSlice, (nodes) => {
const template = selectNodeTemplate(nodes, nodeId);
if (!template) {
return EMPTY_ARRAY;
}
const template = useNodeTemplate(nodeId);
const fieldNames = useMemo(() => {
// get the visible fields
const fields = map(template.inputs).filter(
(field) =>
(field.input === 'connection' && !field.type.isCollectionOrScalar) ||
!keys(TEMPLATE_BUILDER_MAP).includes(field.type.name)
);
// get the visible fields
const fields = map(template.inputs).filter(
(field) =>
(field.input === 'connection' && !field.type.isCollectionOrScalar) ||
!keys(TEMPLATE_BUILDER_MAP).includes(field.type.name)
);
return getSortedFilteredFieldNames(fields);
}),
[nodeId]
);
const fieldNames = useAppSelector(selector);
return getSortedFilteredFieldNames(fields);
}, [template]);
return fieldNames;
};

View File

@ -1,20 +1,9 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks';
import { selectNodesSlice } from 'features/nodes/store/nodesSlice';
import { selectFieldInputTemplate } from 'features/nodes/store/selectors';
import { useNodeTemplate } from 'features/nodes/hooks/useNodeTemplate';
import type { FieldInputTemplate } from 'features/nodes/types/field';
import { useMemo } from 'react';
export const useFieldInputTemplate = (nodeId: string, fieldName: string): FieldInputTemplate | null => {
const selector = useMemo(
() =>
createMemoizedSelector(selectNodesSlice, (nodes) => {
return selectFieldInputTemplate(nodes, nodeId, fieldName);
}),
[fieldName, nodeId]
);
const fieldTemplate = useAppSelector(selector);
const template = useNodeTemplate(nodeId);
const fieldTemplate = useMemo(() => template.inputs[fieldName] ?? null, [fieldName, template.inputs]);
return fieldTemplate;
};

View File

@ -1,20 +1,9 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks';
import { selectNodesSlice } from 'features/nodes/store/nodesSlice';
import { selectFieldOutputTemplate } from 'features/nodes/store/selectors';
import { useNodeTemplate } from 'features/nodes/hooks/useNodeTemplate';
import type { FieldOutputTemplate } from 'features/nodes/types/field';
import { useMemo } from 'react';
export const useFieldOutputTemplate = (nodeId: string, fieldName: string): FieldOutputTemplate | null => {
const selector = useMemo(
() =>
createMemoizedSelector(selectNodesSlice, (nodes) => {
return selectFieldOutputTemplate(nodes, nodeId, fieldName);
}),
[fieldName, nodeId]
);
const fieldTemplate = useAppSelector(selector);
const template = useNodeTemplate(nodeId);
const fieldTemplate = useMemo(() => template.outputs[fieldName] ?? null, [fieldName, template.outputs]);
return fieldTemplate;
};

View File

@ -1,27 +1,36 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useStore } from '@nanostores/react';
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks';
import { selectNodesSlice } from 'features/nodes/store/nodesSlice';
import { selectFieldInputTemplate, selectFieldOutputTemplate } from 'features/nodes/store/selectors';
import { $templates, selectNodesSlice } from 'features/nodes/store/nodesSlice';
import { selectInvocationNodeType } from 'features/nodes/store/selectors';
import type { FieldInputTemplate, FieldOutputTemplate } from 'features/nodes/types/field';
import { useMemo } from 'react';
import { assert } from 'tsafe';
export const useFieldTemplate = (
nodeId: string,
fieldName: string,
kind: 'inputs' | 'outputs'
): FieldInputTemplate | FieldOutputTemplate | null => {
const selector = useMemo(
() =>
createMemoizedSelector(selectNodesSlice, (nodes) => {
if (kind === 'inputs') {
return selectFieldInputTemplate(nodes, nodeId, fieldName);
}
return selectFieldOutputTemplate(nodes, nodeId, fieldName);
}),
[fieldName, kind, nodeId]
): FieldInputTemplate | FieldOutputTemplate => {
const templates = useStore($templates);
const selectNodeType = useMemo(
() => createSelector(selectNodesSlice, (nodes) => selectInvocationNodeType(nodes, nodeId)),
[nodeId]
);
const fieldTemplate = useAppSelector(selector);
const nodeType = useAppSelector(selectNodeType);
const fieldTemplate = useMemo(() => {
const template = templates[nodeType];
assert(template, `Template for node type ${nodeType} not found`);
if (kind === 'inputs') {
const fieldTemplate = template.inputs[fieldName];
assert(fieldTemplate, `Field template for field ${fieldName} not found`);
return fieldTemplate;
} else {
const fieldTemplate = template.outputs[fieldName];
assert(fieldTemplate, `Field template for field ${fieldName} not found`);
return fieldTemplate;
}
}, [fieldName, kind, nodeType, templates]);
return fieldTemplate;
};

View File

@ -1,22 +1,8 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks';
import { selectNodesSlice } from 'features/nodes/store/nodesSlice';
import { selectFieldInputTemplate, selectFieldOutputTemplate } from 'features/nodes/store/selectors';
import { useFieldTemplate } from 'features/nodes/hooks/useFieldTemplate';
import { useMemo } from 'react';
export const useFieldTemplateTitle = (nodeId: string, fieldName: string, kind: 'inputs' | 'outputs'): string | null => {
const selector = useMemo(
() =>
createSelector(selectNodesSlice, (nodes) => {
if (kind === 'inputs') {
return selectFieldInputTemplate(nodes, nodeId, fieldName)?.title ?? null;
}
return selectFieldOutputTemplate(nodes, nodeId, fieldName)?.title ?? null;
}),
[fieldName, kind, nodeId]
);
const fieldTemplateTitle = useAppSelector(selector);
const fieldTemplate = useFieldTemplate(nodeId, fieldName, kind);
const fieldTemplateTitle = useMemo(() => fieldTemplate.title, [fieldTemplate]);
return fieldTemplateTitle;
};

View File

@ -1,23 +1,9 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks';
import { selectNodesSlice } from 'features/nodes/store/nodesSlice';
import { selectFieldInputTemplate, selectFieldOutputTemplate } from 'features/nodes/store/selectors';
import { useFieldTemplate } from 'features/nodes/hooks/useFieldTemplate';
import type { FieldType } from 'features/nodes/types/field';
import { useMemo } from 'react';
export const useFieldType = (nodeId: string, fieldName: string, kind: 'inputs' | 'outputs'): FieldType | null => {
const selector = useMemo(
() =>
createMemoizedSelector(selectNodesSlice, (nodes) => {
if (kind === 'inputs') {
return selectFieldInputTemplate(nodes, nodeId, fieldName)?.type ?? null;
}
return selectFieldOutputTemplate(nodes, nodeId, fieldName)?.type ?? null;
}),
[fieldName, kind, nodeId]
);
const fieldType = useAppSelector(selector);
export const useFieldType = (nodeId: string, fieldName: string, kind: 'inputs' | 'outputs'): FieldType => {
const fieldTemplate = useFieldTemplate(nodeId, fieldName, kind);
const fieldType = useMemo(() => fieldTemplate.type, [fieldTemplate]);
return fieldType;
};

View File

@ -16,7 +16,7 @@ export const useGetNodesNeedUpdate = () => {
if (!template) {
return false;
}
return getNeedsUpdate(node, template);
return getNeedsUpdate(node.data, template);
})
),
[templates]

View File

@ -1,26 +1,20 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks';
import { selectNodesSlice } from 'features/nodes/store/nodesSlice';
import { selectNodeTemplate } from 'features/nodes/store/selectors';
import { useNodeTemplate } from 'features/nodes/hooks/useNodeTemplate';
import { some } from 'lodash-es';
import { useMemo } from 'react';
export const useHasImageOutput = (nodeId: string): boolean => {
const selector = useMemo(
const template = useNodeTemplate(nodeId);
const hasImageOutput = useMemo(
() =>
createMemoizedSelector(selectNodesSlice, (nodes) => {
const template = selectNodeTemplate(nodes, nodeId);
return some(
template?.outputs,
(output) =>
output.type.name === 'ImageField' &&
// the image primitive node (node type "image") does not actually save the image, do not show the image-saving checkboxes
template?.type !== 'image'
);
}),
[nodeId]
some(
template?.outputs,
(output) =>
output.type.name === 'ImageField' &&
// the image primitive node (node type "image") does not actually save the image, do not show the image-saving checkboxes
template?.type !== 'image'
),
[template]
);
const hasImageOutput = useAppSelector(selector);
return hasImageOutput;
};

View File

@ -1,19 +1,9 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks';
import { selectNodesSlice } from 'features/nodes/store/nodesSlice';
import { selectNodeTemplate } from 'features/nodes/store/selectors';
import { useNodeTemplate } from 'features/nodes/hooks/useNodeTemplate';
import type { Classification } from 'features/nodes/types/common';
import { useMemo } from 'react';
export const useNodeClassification = (nodeId: string): Classification | null => {
const selector = useMemo(
() =>
createSelector(selectNodesSlice, (nodes) => {
return selectNodeTemplate(nodes, nodeId)?.classification ?? null;
}),
[nodeId]
);
const title = useAppSelector(selector);
return title;
export const useNodeClassification = (nodeId: string): Classification => {
const template = useNodeTemplate(nodeId);
const classification = useMemo(() => template.classification, [template]);
return classification;
};

View File

@ -5,7 +5,7 @@ import { selectNodeData } from 'features/nodes/store/selectors';
import type { InvocationNodeData } from 'features/nodes/types/invocation';
import { useMemo } from 'react';
export const useNodeData = (nodeId: string): InvocationNodeData | null => {
export const useNodeData = (nodeId: string): InvocationNodeData => {
const selector = useMemo(
() =>
createMemoizedSelector(selectNodesSlice, (nodes) => {

View File

@ -1,25 +1,11 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks';
import { selectNodesSlice } from 'features/nodes/store/nodesSlice';
import { selectInvocationNode, selectNodeTemplate } from 'features/nodes/store/selectors';
import { useNodeData } from 'features/nodes/hooks/useNodeData';
import { useNodeTemplate } from 'features/nodes/hooks/useNodeTemplate';
import { getNeedsUpdate } from 'features/nodes/util/node/nodeUpdate';
import { useMemo } from 'react';
export const useNodeNeedsUpdate = (nodeId: string) => {
const selector = useMemo(
() =>
createMemoizedSelector(selectNodesSlice, (nodes) => {
const node = selectInvocationNode(nodes, nodeId);
const template = selectNodeTemplate(nodes, nodeId);
if (!node || !template) {
return false;
}
return getNeedsUpdate(node, template);
}),
[nodeId]
);
const needsUpdate = useAppSelector(selector);
const data = useNodeData(nodeId);
const template = useNodeTemplate(nodeId);
const needsUpdate = useMemo(() => getNeedsUpdate(data, template), [data, template]);
return needsUpdate;
};

View File

@ -1,20 +1,23 @@
import { useStore } from '@nanostores/react';
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks';
import { selectNodesSlice } from 'features/nodes/store/nodesSlice';
import { selectNodeTemplate } from 'features/nodes/store/selectors';
import { $templates, selectNodesSlice } from 'features/nodes/store/nodesSlice';
import { selectInvocationNodeType } from 'features/nodes/store/selectors';
import type { InvocationTemplate } from 'features/nodes/types/invocation';
import { useMemo } from 'react';
import { assert } from 'tsafe';
export const useNodeTemplate = (nodeId: string): InvocationTemplate | null => {
const selector = useMemo(
() =>
createSelector(selectNodesSlice, (nodes) => {
return selectNodeTemplate(nodes, nodeId);
}),
export const useNodeTemplate = (nodeId: string): InvocationTemplate => {
const templates = useStore($templates);
const selectNodeType = useMemo(
() => createSelector(selectNodesSlice, (nodes) => selectInvocationNodeType(nodes, nodeId)),
[nodeId]
);
const nodeTemplate = useAppSelector(selector);
return nodeTemplate;
const nodeType = useAppSelector(selectNodeType);
const template = useMemo(() => {
const t = templates[nodeType];
assert(t, `Template for node type ${nodeType} not found`);
return t;
}, [nodeType, templates]);
return template;
};

View File

@ -1,18 +1,8 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks';
import { selectNodesSlice } from 'features/nodes/store/nodesSlice';
import { selectNodeTemplate } from 'features/nodes/store/selectors';
import { useNodeTemplate } from 'features/nodes/hooks/useNodeTemplate';
import { useMemo } from 'react';
export const useNodeTemplateTitle = (nodeId: string): string | null => {
const selector = useMemo(
() =>
createSelector(selectNodesSlice, (nodes) => {
return selectNodeTemplate(nodes, nodeId)?.title ?? null;
}),
[nodeId]
);
const title = useAppSelector(selector);
const template = useNodeTemplate(nodeId);
const title = useMemo(() => template.title, [template.title]);
return title;
};

View File

@ -1,26 +1,10 @@
import { EMPTY_ARRAY } from 'app/store/constants';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks';
import { selectNodesSlice } from 'features/nodes/store/nodesSlice';
import { selectNodeTemplate } from 'features/nodes/store/selectors';
import { useNodeTemplate } from 'features/nodes/hooks/useNodeTemplate';
import { getSortedFilteredFieldNames } from 'features/nodes/util/node/getSortedFilteredFieldNames';
import { map } from 'lodash-es';
import { useMemo } from 'react';
export const useOutputFieldNames = (nodeId: string) => {
const selector = useMemo(
() =>
createMemoizedSelector(selectNodesSlice, (nodes) => {
const template = selectNodeTemplate(nodes, nodeId);
if (!template) {
return EMPTY_ARRAY;
}
return getSortedFilteredFieldNames(map(template.outputs));
}),
[nodeId]
);
const fieldNames = useAppSelector(selector);
export const useOutputFieldNames = (nodeId: string): string[] => {
const template = useNodeTemplate(nodeId);
const fieldNames = useMemo(() => getSortedFilteredFieldNames(map(template.outputs)), [template.outputs]);
return fieldNames;
};

View File

@ -1,18 +1,23 @@
import type { NodesState } from 'features/nodes/store/types';
import type { FieldInputInstance, FieldInputTemplate, FieldOutputTemplate } from 'features/nodes/types/field';
import type { FieldInputInstance } from 'features/nodes/types/field';
import type { InvocationNode, InvocationNodeData } from 'features/nodes/types/invocation';
import { isInvocationNode } from 'features/nodes/types/invocation';
import { assert } from 'tsafe';
export const selectInvocationNode = (nodesSlice: NodesState, nodeId: string): InvocationNode | null => {
export const selectInvocationNode = (nodesSlice: NodesState, nodeId: string): InvocationNode => {
const node = nodesSlice.nodes.find((node) => node.id === nodeId);
if (!isInvocationNode(node)) {
return null;
}
assert(isInvocationNode(node), `Node ${nodeId} is not an invocation node`);
return node;
};
export const selectNodeData = (nodesSlice: NodesState, nodeId: string): InvocationNodeData | null => {
return selectInvocationNode(nodesSlice, nodeId)?.data ?? null;
export const selectInvocationNodeType = (nodesSlice: NodesState, nodeId: string): string => {
const node = selectInvocationNode(nodesSlice, nodeId);
return node.data.type;
};
export const selectNodeData = (nodesSlice: NodesState, nodeId: string): InvocationNodeData => {
const node = selectInvocationNode(nodesSlice, nodeId);
return node.data;
};
export const selectFieldInputInstance = (
@ -23,21 +28,3 @@ export const selectFieldInputInstance = (
const data = selectNodeData(nodesSlice, nodeId);
return data?.inputs[fieldName] ?? null;
};
export const selectFieldInputTemplate = (
nodesSlice: NodesState,
nodeId: string,
fieldName: string
): FieldInputTemplate | null => {
const template = selectNodeTemplate(nodesSlice, nodeId);
return template?.inputs[fieldName] ?? null;
};
export const selectFieldOutputTemplate = (
nodesSlice: NodesState,
nodeId: string,
fieldName: string
): FieldOutputTemplate | null => {
const template = selectNodeTemplate(nodesSlice, nodeId);
return template?.outputs[fieldName] ?? null;
};

View File

@ -1,17 +1,17 @@
import { deepClone } from 'common/util/deepClone';
import { satisfies } from 'compare-versions';
import { NodeUpdateError } from 'features/nodes/types/error';
import type { InvocationNode, InvocationTemplate } from 'features/nodes/types/invocation';
import type { InvocationNode, InvocationNodeData, InvocationTemplate } from 'features/nodes/types/invocation';
import { zParsedSemver } from 'features/nodes/types/semver';
import { defaultsDeep, keys, pick } from 'lodash-es';
import { buildInvocationNode } from './buildInvocationNode';
export const getNeedsUpdate = (node: InvocationNode, template: InvocationTemplate): boolean => {
if (node.data.type !== template.type) {
export const getNeedsUpdate = (data: InvocationNodeData, template: InvocationTemplate): boolean => {
if (data.type !== template.type) {
return true;
}
return node.data.version !== template.version;
return data.version !== template.version;
};
/**
@ -20,7 +20,7 @@ export const getNeedsUpdate = (node: InvocationNode, template: InvocationTemplat
* @param template The invocation template to check against.
*/
const getMayUpdateNode = (node: InvocationNode, template: InvocationTemplate): boolean => {
const needsUpdate = getNeedsUpdate(node, template);
const needsUpdate = getNeedsUpdate(node.data, template);
if (!needsUpdate || node.data.type !== template.type) {
return false;
}

View File

@ -68,7 +68,7 @@ export const validateWorkflow = (
return;
}
if (getNeedsUpdate(node, template)) {
if (getNeedsUpdate(node.data, template)) {
// This node needs to be updated, based on comparison of its version to the template version
const message = t('nodes.mismatchedVersion', {
node: node.id,