refactor(ui): standardize more workflow editor hooks to use Safe and OrThrow suffixes for clarity

This commit is contained in:
psychedelicious
2025-03-27 11:53:52 +10:00
parent 0b8f88e554
commit 567fd3e0da
19 changed files with 49 additions and 43 deletions

View File

@@ -1,5 +1,5 @@
import { Handle, Position } from '@xyflow/react';
import { useNodeTemplate } from 'features/nodes/hooks/useNodeTemplate';
import { useNodeTemplateOrThrow } from 'features/nodes/hooks/useNodeTemplateOrThrow';
import { map } from 'lodash-es';
import type { CSSProperties } from 'react';
import { memo } from 'react';
@@ -19,7 +19,7 @@ const collapsedHandleStyles: CSSProperties = {
};
const InvocationNodeCollapsedHandles = ({ nodeId }: Props) => {
const template = useNodeTemplate(nodeId);
const template = useNodeTemplateOrThrow(nodeId);
if (!template) {
return null;

View File

@@ -1,9 +1,9 @@
import { Flex, Icon, Text, Tooltip } from '@invoke-ai/ui-library';
import { compare } from 'compare-versions';
import { useNodeUserTitleSafe } from 'features/nodes/hooks/useNodeUserTitleSafe';
import { useNodeNeedsUpdate } from 'features/nodes/hooks/useNodeNeedsUpdate';
import { useInvocationNodeNotes } from 'features/nodes/hooks/useNodeNotes';
import { useNodeTemplate } from 'features/nodes/hooks/useNodeTemplate';
import { useNodeTemplateOrThrow } from 'features/nodes/hooks/useNodeTemplateOrThrow';
import { useNodeUserTitleSafe } from 'features/nodes/hooks/useNodeUserTitleSafe';
import { useNodeVersion } from 'features/nodes/hooks/useNodeVersion';
import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
@@ -29,7 +29,7 @@ const TooltipContent = memo(({ nodeId }: { nodeId: string }) => {
const notes = useInvocationNodeNotes(nodeId);
const label = useNodeUserTitleSafe(nodeId);
const version = useNodeVersion(nodeId);
const nodeTemplate = useNodeTemplate(nodeId);
const nodeTemplate = useNodeTemplateOrThrow(nodeId);
const { t } = useTranslation();
const title = useMemo(() => {

View File

@@ -5,7 +5,7 @@ import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableCon
import { InvocationNodeNotesTextarea } from 'features/nodes/components/flow/nodes/Invocation/InvocationNodeNotesTextarea';
import { TemplateGate } from 'features/nodes/components/sidePanel/inspector/NodeTemplateGate';
import { useNodeNeedsUpdate } from 'features/nodes/hooks/useNodeNeedsUpdate';
import { useNodeTemplate } from 'features/nodes/hooks/useNodeTemplate';
import { useNodeTemplateOrThrow } from 'features/nodes/hooks/useNodeTemplateOrThrow';
import { useNodeVersion } from 'features/nodes/hooks/useNodeVersion';
import { selectLastSelectedNodeId } from 'features/nodes/store/selectors';
import { memo } from 'react';
@@ -36,7 +36,7 @@ export default memo(InspectorDetailsTab);
const Content = memo(({ nodeId }: { nodeId: string }) => {
const { t } = useTranslation();
const version = useNodeVersion(nodeId);
const template = useNodeTemplate(nodeId);
const template = useNodeTemplateOrThrow(nodeId);
const needsUpdate = useNodeNeedsUpdate(nodeId);
return (

View File

@@ -5,7 +5,7 @@ import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableCon
import DataViewer from 'features/gallery/components/ImageMetadataViewer/DataViewer';
import { TemplateGate } from 'features/nodes/components/sidePanel/inspector/NodeTemplateGate';
import { useNodeExecutionState } from 'features/nodes/hooks/useNodeExecutionState';
import { useNodeTemplate } from 'features/nodes/hooks/useNodeTemplate';
import { useNodeTemplateOrThrow } from 'features/nodes/hooks/useNodeTemplateOrThrow';
import { selectLastSelectedNodeId } from 'features/nodes/store/selectors';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
@@ -37,7 +37,7 @@ const getKey = (result: AnyInvocationOutput, i: number) => `${result.type}-${i}`
const Content = memo(({ nodeId }: { nodeId: string }) => {
const { t } = useTranslation();
const template = useNodeTemplate(nodeId);
const template = useNodeTemplateOrThrow(nodeId);
const nes = useNodeExecutionState(nodeId);
if (!nes || nes.outputs.length === 0) {

View File

@@ -2,7 +2,7 @@ import { useAppSelector } from 'app/store/storeHooks';
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
import DataViewer from 'features/gallery/components/ImageMetadataViewer/DataViewer';
import { TemplateGate } from 'features/nodes/components/sidePanel/inspector/NodeTemplateGate';
import { useNodeTemplate } from 'features/nodes/hooks/useNodeTemplate';
import { useNodeTemplateOrThrow } from 'features/nodes/hooks/useNodeTemplateOrThrow';
import { selectLastSelectedNodeId } from 'features/nodes/store/selectors';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
@@ -29,7 +29,7 @@ export default memo(NodeTemplateInspector);
const Content = memo(({ nodeId }: { nodeId: string }) => {
const { t } = useTranslation();
const template = useNodeTemplate(nodeId);
const template = useNodeTemplateOrThrow(nodeId);
return <DataViewer data={template} label={t('nodes.nodeTemplate')} bg="base.850" color="base.200" />;
});

View File

@@ -1,4 +1,4 @@
import { useNodeTemplateSafe } from 'features/nodes/hooks/useNodeTemplate';
import { useNodeTemplateSafe } from 'features/nodes/hooks/useNodeTemplateSafe';
import type { PropsWithChildren, ReactNode } from 'react';
import { memo } from 'react';

View File

@@ -1,5 +1,5 @@
import { useNodeData } from 'features/nodes/hooks/useNodeData';
import { useNodeTemplate } from 'features/nodes/hooks/useNodeTemplate';
import { useNodeTemplateOrThrow } from './useNodeTemplateOrThrow';
import type { FieldInputTemplate } from 'features/nodes/types/field';
import { isSingleOrCollection } from 'features/nodes/types/field';
import { TEMPLATE_BUILDER_MAP } from 'features/nodes/util/schema/buildFieldInputTemplate';
@@ -19,7 +19,7 @@ const isAnyOrDirectInputField = (field: FieldInputTemplate) => {
};
export const useInputFieldNamesMissing = (nodeId: string) => {
const template = useNodeTemplate(nodeId);
const template = useNodeTemplateOrThrow(nodeId);
const node = useNodeData(nodeId);
const fieldNames = useMemo(() => {
const instanceFields = new Set(Object.keys(node.inputs));
@@ -30,7 +30,7 @@ export const useInputFieldNamesMissing = (nodeId: string) => {
};
export const useInputFieldNamesAnyOrDirect = (nodeId: string) => {
const template = useNodeTemplate(nodeId);
const template = useNodeTemplateOrThrow(nodeId);
const fieldNames = useMemo(() => {
const anyOrDirectFields: string[] = [];
for (const [fieldName, fieldTemplate] of Object.entries(template.inputs)) {
@@ -44,7 +44,7 @@ export const useInputFieldNamesAnyOrDirect = (nodeId: string) => {
};
export const useInputFieldNamesConnection = (nodeId: string) => {
const template = useNodeTemplate(nodeId);
const template = useNodeTemplateOrThrow(nodeId);
const fieldNames = useMemo(() => {
const connectionFields: string[] = [];
for (const [fieldName, fieldTemplate] of Object.entries(template.inputs)) {

View File

@@ -1,4 +1,4 @@
import { useNodeTemplate } from 'features/nodes/hooks/useNodeTemplate';
import { useNodeTemplateOrThrow } from './useNodeTemplateOrThrow';
import type { FieldInputTemplate } from 'features/nodes/types/field';
import { useMemo } from 'react';
import { assert } from 'tsafe';
@@ -13,7 +13,7 @@ import { assert } from 'tsafe';
* @throws Will throw an error if the template for the input field is not found.
*/
export const useInputFieldTemplateOrThrow = (nodeId: string, fieldName: string): FieldInputTemplate => {
const template = useNodeTemplate(nodeId);
const template = useNodeTemplateOrThrow(nodeId);
const fieldTemplate = useMemo(() => {
const _fieldTemplate = template.inputs[fieldName];
assert(_fieldTemplate, `Template for input field ${fieldName} not found.`);

View File

@@ -1,4 +1,4 @@
import { useNodeTemplateSafe } from 'features/nodes/hooks/useNodeTemplate';
import { useNodeTemplateSafe } from 'features/nodes/hooks/useNodeTemplateSafe';
import type { FieldInputTemplate } from 'features/nodes/types/field';
import { useMemo } from 'react';

View File

@@ -1,9 +1,9 @@
import { useNodeTemplate } from 'features/nodes/hooks/useNodeTemplate';
import { useNodeTemplateOrThrow } from './useNodeTemplateOrThrow';
import { useMemo } from 'react';
import { assert } from 'tsafe';
export const useInputFieldTemplateTitleOrThrow = (nodeId: string, fieldName: string): string => {
const template = useNodeTemplate(nodeId);
const template = useNodeTemplateOrThrow(nodeId);
const title = useMemo(() => {
const fieldTemplate = template.inputs[fieldName];

View File

@@ -1,8 +1,8 @@
import { useNodeTemplate } from 'features/nodes/hooks/useNodeTemplate';
import { useNodeTemplateOrThrow } from './useNodeTemplateOrThrow';
import { useMemo } from 'react';
export const useInputFieldTemplateTitleSafe = (nodeId: string, fieldName: string): string => {
const template = useNodeTemplate(nodeId);
const template = useNodeTemplateOrThrow(nodeId);
const title = useMemo(() => template.inputs[fieldName]?.title ?? '', [fieldName, template.inputs]);
return title;
};

View File

@@ -1,9 +1,9 @@
import { useNodeTemplate } from 'features/nodes/hooks/useNodeTemplate';
import { useNodeTemplateOrThrow } from './useNodeTemplateOrThrow';
import { isBatchNodeType, isGeneratorNodeType } from 'features/nodes/types/invocation';
import { useMemo } from 'react';
export const useIsExecutableNode = (nodeId: string) => {
const template = useNodeTemplate(nodeId);
const template = useNodeTemplateOrThrow(nodeId);
const isExecutableNode = useMemo(
() => !isBatchNodeType(template.type) && !isGeneratorNodeType(template.type),
[template]

View File

@@ -1,9 +1,9 @@
import { useNodeTemplate } from 'features/nodes/hooks/useNodeTemplate';
import { useNodeTemplateOrThrow } from './useNodeTemplateOrThrow';
import type { Classification } from 'features/nodes/types/common';
import { useMemo } from 'react';
export const useNodeClassification = (nodeId: string): Classification => {
const template = useNodeTemplate(nodeId);
const template = useNodeTemplateOrThrow(nodeId);
const classification = useMemo(() => template.classification, [template]);
return classification;
};

View File

@@ -1,9 +1,9 @@
import { useNodeTemplate } from 'features/nodes/hooks/useNodeTemplate';
import { useNodeTemplateOrThrow } from './useNodeTemplateOrThrow';
import { some } from 'lodash-es';
import { useMemo } from 'react';
export const useNodeHasImageOutput = (nodeId: string): boolean => {
const template = useNodeTemplate(nodeId);
const template = useNodeTemplateOrThrow(nodeId);
const hasImageOutput = useMemo(
() =>
some(

View File

@@ -1,4 +1,4 @@
import { useNodeTemplate } from 'features/nodes/hooks/useNodeTemplate';
import { useNodeTemplateOrThrow } from './useNodeTemplateOrThrow';
import { useNodeType } from 'features/nodes/hooks/useNodeType';
import { useNodeVersion } from 'features/nodes/hooks/useNodeVersion';
import { useMemo } from 'react';
@@ -6,7 +6,7 @@ import { useMemo } from 'react';
export const useNodeNeedsUpdate = (nodeId: string) => {
const type = useNodeType(nodeId);
const version = useNodeVersion(nodeId);
const template = useNodeTemplate(nodeId);
const template = useNodeTemplateOrThrow(nodeId);
const needsUpdate = useMemo(() => {
if (type !== template.type) {
return true;

View File

@@ -5,7 +5,7 @@ import type { InvocationTemplate } from 'features/nodes/types/invocation';
import { useMemo } from 'react';
import { assert } from 'tsafe';
export const useNodeTemplate = (nodeId: string): InvocationTemplate => {
export const useNodeTemplateOrThrow = (nodeId: string): InvocationTemplate => {
const templates = useStore($templates);
const type = useNodeType(nodeId);
const template = useMemo(() => {
@@ -15,10 +15,3 @@ export const useNodeTemplate = (nodeId: string): InvocationTemplate => {
}, [templates, type]);
return template;
};
export const useNodeTemplateSafe = (nodeId: string): InvocationTemplate | null => {
const templates = useStore($templates);
const type = useNodeType(nodeId);
const template = useMemo(() => templates[type] ?? null, [templates, type]);
return template;
};

View File

@@ -0,0 +1,12 @@
import { useStore } from '@nanostores/react';
import { useNodeType } from 'features/nodes/hooks/useNodeType';
import { $templates } from 'features/nodes/store/nodesSlice';
import type { InvocationTemplate } from 'features/nodes/types/invocation';
import { useMemo } from 'react';
export const useNodeTemplateSafe = (nodeId: string): InvocationTemplate | null => {
const templates = useStore($templates);
const type = useNodeType(nodeId);
const template = useMemo(() => templates[type] ?? null, [templates, type]);
return template;
};

View File

@@ -1,10 +1,11 @@
import { useNodeTemplate } from 'features/nodes/hooks/useNodeTemplate';
import { getSortedFilteredFieldNames } from 'features/nodes/util/node/getSortedFilteredFieldNames';
import { map } from 'lodash-es';
import { useMemo } from 'react';
import { useNodeTemplateOrThrow } from './useNodeTemplateOrThrow';
export const useOutputFieldNames = (nodeId: string): string[] => {
const template = useNodeTemplate(nodeId);
const template = useNodeTemplateOrThrow(nodeId);
const fieldNames = useMemo(() => getSortedFilteredFieldNames(map(template.outputs)), [template.outputs]);
return fieldNames;
};

View File

@@ -1,10 +1,10 @@
import { useNodeTemplate } from 'features/nodes/hooks/useNodeTemplate';
import { useNodeTemplateOrThrow } from './useNodeTemplateOrThrow';
import type { FieldOutputTemplate } from 'features/nodes/types/field';
import { useMemo } from 'react';
import { assert } from 'tsafe';
export const useOutputFieldTemplate = (nodeId: string, fieldName: string): FieldOutputTemplate => {
const template = useNodeTemplate(nodeId);
const template = useNodeTemplateOrThrow(nodeId);
const fieldTemplate = useMemo(() => {
const _fieldTemplate = template.outputs[fieldName];
assert(_fieldTemplate, `Template for output field ${fieldName} not found`);