mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
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:
parent
f6a44681a8
commit
1d884fb794
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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}`;
|
||||
|
||||
|
@ -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 (
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -16,7 +16,7 @@ export const useGetNodesNeedUpdate = () => {
|
||||
if (!template) {
|
||||
return false;
|
||||
}
|
||||
return getNeedsUpdate(node, template);
|
||||
return getNeedsUpdate(node.data, template);
|
||||
})
|
||||
),
|
||||
[templates]
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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) => {
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user