Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(ui): crash when using notes nodes or missing node/field templates #6412

Merged
merged 5 commits into from
May 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Flex, Grid, GridItem } from '@invoke-ai/ui-library';
import NodeWrapper from 'features/nodes/components/flow/nodes/common/NodeWrapper';
import { useAnyOrDirectInputFieldNames } from 'features/nodes/hooks/useAnyOrDirectInputFieldNames';
import { useConnectionInputFieldNames } from 'features/nodes/hooks/useConnectionInputFieldNames';
import { InvocationInputFieldCheck } from 'features/nodes/components/flow/nodes/Invocation/fields/InvocationFieldCheck';
import { useFieldNames } from 'features/nodes/hooks/useFieldNames';
import { useOutputFieldNames } from 'features/nodes/hooks/useOutputFieldNames';
import { useWithFooter } from 'features/nodes/hooks/useWithFooter';
import { memo } from 'react';
Expand All @@ -20,8 +20,7 @@ type Props = {
};

const InvocationNode = ({ nodeId, isOpen, label, type, selected }: Props) => {
const inputConnectionFieldNames = useConnectionInputFieldNames(nodeId);
const inputAnyOrDirectFieldNames = useAnyOrDirectInputFieldNames(nodeId);
const fieldNames = useFieldNames(nodeId);
const withFooter = useWithFooter(nodeId);
const outputFieldNames = useOutputFieldNames(nodeId);

Expand All @@ -41,9 +40,11 @@ const InvocationNode = ({ nodeId, isOpen, label, type, selected }: Props) => {
>
<Flex flexDir="column" px={2} w="full" h="full">
<Grid gridTemplateColumns="1fr auto" gridAutoRows="1fr">
{inputConnectionFieldNames.map((fieldName, i) => (
{fieldNames.connectionFields.map((fieldName, i) => (
<GridItem gridColumnStart={1} gridRowStart={i + 1} key={`${nodeId}.${fieldName}.input-field`}>
<InputField nodeId={nodeId} fieldName={fieldName} />
<InvocationInputFieldCheck nodeId={nodeId} fieldName={fieldName}>
<InputField nodeId={nodeId} fieldName={fieldName} />
</InvocationInputFieldCheck>
</GridItem>
))}
{outputFieldNames.map((fieldName, i) => (
Expand All @@ -52,8 +53,23 @@ const InvocationNode = ({ nodeId, isOpen, label, type, selected }: Props) => {
</GridItem>
))}
</Grid>
{inputAnyOrDirectFieldNames.map((fieldName) => (
<InputField key={`${nodeId}.${fieldName}.input-field`} nodeId={nodeId} fieldName={fieldName} />
{fieldNames.anyOrDirectFields.map((fieldName) => (
<InvocationInputFieldCheck
key={`${nodeId}.${fieldName}.input-field`}
nodeId={nodeId}
fieldName={fieldName}
>
<InputField nodeId={nodeId} fieldName={fieldName} />
</InvocationInputFieldCheck>
))}
{fieldNames.missingFields.map((fieldName) => (
<InvocationInputFieldCheck
key={`${nodeId}.${fieldName}.input-field`}
nodeId={nodeId}
fieldName={fieldName}
>
<InputField nodeId={nodeId} fieldName={fieldName} />
</InvocationInputFieldCheck>
))}
</Flex>
</Flex>
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,26 +1,22 @@
import { Flex, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { Flex, FormControl } from '@invoke-ai/ui-library';
import { useConnectionState } from 'features/nodes/hooks/useConnectionState';
import { useDoesInputHaveValue } from 'features/nodes/hooks/useDoesInputHaveValue';
import { useFieldInputInstance } from 'features/nodes/hooks/useFieldInputInstance';
import { useFieldInputTemplate } from 'features/nodes/hooks/useFieldInputTemplate';
import type { PropsWithChildren } from 'react';
import { memo, useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';

import EditableFieldTitle from './EditableFieldTitle';
import FieldHandle from './FieldHandle';
import FieldLinearViewToggle from './FieldLinearViewToggle';
import InputFieldRenderer from './InputFieldRenderer';
import { InputFieldWrapper } from './InputFieldWrapper';

interface Props {
nodeId: string;
fieldName: string;
}

const InputField = ({ nodeId, fieldName }: Props) => {
const { t } = useTranslation();
const fieldTemplate = useFieldInputTemplate(nodeId, fieldName);
const fieldInstance = useFieldInputInstance(nodeId, fieldName);
const doesFieldHaveValue = useDoesInputHaveValue(nodeId, fieldName);
const [isHovered, setIsHovered] = useState(false);

Expand Down Expand Up @@ -55,20 +51,6 @@ const InputField = ({ nodeId, fieldName }: Props) => {
setIsHovered(false);
}, []);

if (!fieldTemplate || !fieldInstance) {
return (
<InputFieldWrapper shouldDim={shouldDim}>
<FormControl alignItems="stretch" justifyContent="space-between" flexDir="column" gap={2} h="full" w="full">
<FormLabel display="flex" alignItems="center" mb={0} px={1} gap={2} h="full">
{t('nodes.unknownInput', {
name: fieldInstance?.label ?? fieldTemplate?.title ?? fieldName,
})}
</FormLabel>
</FormControl>
</InputFieldWrapper>
);
}

if (fieldTemplate.input === 'connection' || isConnected) {
return (
<InputFieldWrapper shouldDim={shouldDim}>
Expand Down Expand Up @@ -134,27 +116,3 @@ const InputField = ({ nodeId, fieldName }: Props) => {
};

export default memo(InputField);

type InputFieldWrapperProps = PropsWithChildren<{
shouldDim: boolean;
}>;

const InputFieldWrapper = memo(({ shouldDim, children }: InputFieldWrapperProps) => {
return (
<Flex
position="relative"
minH={8}
py={0.5}
alignItems="center"
opacity={shouldDim ? 0.5 : 1}
transitionProperty="opacity"
transitionDuration="0.1s"
w="full"
h="full"
>
{children}
</Flex>
);
});

InputFieldWrapper.displayName = 'InputFieldWrapper';
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Flex } from '@invoke-ai/ui-library';
import type { PropsWithChildren } from 'react';
import { memo } from 'react';

type InputFieldWrapperProps = PropsWithChildren<{
shouldDim: boolean;
}>;

export const InputFieldWrapper = memo(({ shouldDim, children }: InputFieldWrapperProps) => {
return (
<Flex
position="relative"
minH={8}
py={0.5}
alignItems="center"
opacity={shouldDim ? 0.5 : 1}
transitionProperty="opacity"
transitionDuration="0.1s"
w="full"
h="full"
>
{children}
</Flex>
);
});

InputFieldWrapper.displayName = 'InputFieldWrapper';
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { Flex, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks';
import { $templates, selectNodesSlice } from 'features/nodes/store/nodesSlice';
import { selectInvocationNode } from 'features/nodes/store/selectors';
import type { PropsWithChildren } from 'react';
import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next';

type Props = PropsWithChildren<{
nodeId: string;
fieldName: string;
}>;

export const InvocationInputFieldCheck = memo(({ nodeId, fieldName, children }: Props) => {
const { t } = useTranslation();
const templates = useStore($templates);
const selector = useMemo(
() =>
createSelector(selectNodesSlice, (nodesSlice) => {
const node = selectInvocationNode(nodesSlice, nodeId);
const instance = node.data.inputs[fieldName];
const template = templates[node.data.type];
const fieldTemplate = template?.inputs[fieldName];
return {
name: instance?.label || fieldTemplate?.title || fieldName,
hasInstance: Boolean(instance),
hasTemplate: Boolean(fieldTemplate),
};
}),
[fieldName, nodeId, templates]
);
const { hasInstance, hasTemplate, name } = useAppSelector(selector);

if (!hasTemplate || !hasInstance) {
return (
<Flex position="relative" minH={8} py={0.5} alignItems="center" w="full" h="full">
<FormControl
isInvalid={true}
alignItems="stretch"
justifyContent="center"
flexDir="column"
gap={2}
h="full"
w="full"
>
<FormLabel display="flex" mb={0} px={1} py={2} gap={2}>
{t('nodes.unknownInput', { name })}
</FormLabel>
</FormControl>
</Flex>
);
}

return children;
});

InvocationInputFieldCheck.displayName = 'InvocationInputFieldCheck';
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { CSS } from '@dnd-kit/utilities';
import { Flex, Icon, IconButton, Spacer, Tooltip } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks';
import NodeSelectionOverlay from 'common/components/NodeSelectionOverlay';
import { MissingFallback } from 'features/nodes/components/flow/nodes/Invocation/MissingFallback';
import { InvocationInputFieldCheck } from 'features/nodes/components/flow/nodes/Invocation/fields/InvocationFieldCheck';
import { useFieldOriginalValue } from 'features/nodes/hooks/useFieldOriginalValue';
import { useMouseOverNode } from 'features/nodes/hooks/useMouseOverNode';
import { workflowExposedFieldRemoved } from 'features/nodes/store/workflowSlice';
Expand Down Expand Up @@ -102,9 +102,9 @@ const LinearViewFieldInternal = ({ nodeId, fieldName }: Props) => {

const LinearViewField = ({ nodeId, fieldName }: Props) => {
return (
<MissingFallback nodeId={nodeId} fieldName={fieldName}>
<InvocationInputFieldCheck nodeId={nodeId} fieldName={fieldName}>
<LinearViewFieldInternal nodeId={nodeId} fieldName={fieldName} />
</MissingFallback>
</InvocationInputFieldCheck>
);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ const NotesNode = (props: NodeProps<NotesNodeData>) => {
gap={1}
>
<Flex className="nopan" w="full" h="full" flexDir="column">
<Textarea value={notes} onChange={handleChange} rows={8} resize="none" fontSize="sm" />
<Textarea className="nodrag" value={notes} onChange={handleChange} rows={8} resize="none" fontSize="sm" />
</Flex>
</Flex>
</>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Flex, FormLabel, Icon, IconButton, Spacer, Tooltip } from '@invoke-ai/ui-library';
import FieldTooltipContent from 'features/nodes/components/flow/nodes/Invocation/fields/FieldTooltipContent';
import InputFieldRenderer from 'features/nodes/components/flow/nodes/Invocation/fields/InputFieldRenderer';
import { MissingFallback } from 'features/nodes/components/flow/nodes/Invocation/MissingFallback';
import { InvocationInputFieldCheck } from 'features/nodes/components/flow/nodes/Invocation/fields/InvocationFieldCheck';
import { useFieldLabel } from 'features/nodes/hooks/useFieldLabel';
import { useFieldOriginalValue } from 'features/nodes/hooks/useFieldOriginalValue';
import { useFieldTemplateTitle } from 'features/nodes/hooks/useFieldTemplateTitle';
Expand Down Expand Up @@ -53,9 +53,9 @@ const WorkflowFieldInternal = ({ nodeId, fieldName }: Props) => {

const WorkflowField = ({ nodeId, fieldName }: Props) => {
return (
<MissingFallback nodeId={nodeId} fieldName={fieldName}>
<InvocationInputFieldCheck nodeId={nodeId} fieldName={fieldName}>
<WorkflowFieldInternal nodeId={nodeId} fieldName={fieldName} />
</MissingFallback>
</InvocationInputFieldCheck>
);
};

Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { useNodeTemplate } from 'features/nodes/hooks/useNodeTemplate';
import type { FieldInputTemplate } from 'features/nodes/types/field';
import { useMemo } from 'react';
import { assert } from 'tsafe';

export const useFieldInputTemplate = (nodeId: string, fieldName: string): FieldInputTemplate | null => {
export const useFieldInputTemplate = (nodeId: string, fieldName: string): FieldInputTemplate => {
const template = useNodeTemplate(nodeId);
const fieldTemplate = useMemo(() => template.inputs[fieldName] ?? null, [fieldName, template.inputs]);
const fieldTemplate = useMemo(() => {
const _fieldTemplate = template.inputs[fieldName];
assert(_fieldTemplate, `Field template for field ${fieldName} not found`);
return _fieldTemplate;
}, [fieldName, template.inputs]);
return fieldTemplate;
};
Loading
Loading