From 75ea716c134a6a5595add4f7c71414c498bdddf2 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Tue, 15 Aug 2023 18:24:40 +1000 Subject: [PATCH] feat(ui): hide node footer if there is nothing to display --- .../features/nodes/components/CustomNodes.tsx | 4 +- .../components/Invocation/InvocationNode.tsx | 84 ++++++++++ .../components/Invocation/NodeFooter.tsx | 5 +- .../components/Invocation/NodeHeader.tsx | 54 ++++++ .../Invocation/UnknownNodeFallback.tsx | 69 ++++++++ .../nodes/components/nodes/InvocationNode.tsx | 154 ------------------ .../nodes/InvocationNodeWrapper.tsx | 24 +++ 7 files changed, 237 insertions(+), 157 deletions(-) create mode 100644 invokeai/frontend/web/src/features/nodes/components/Invocation/InvocationNode.tsx create mode 100644 invokeai/frontend/web/src/features/nodes/components/Invocation/NodeHeader.tsx create mode 100644 invokeai/frontend/web/src/features/nodes/components/Invocation/UnknownNodeFallback.tsx delete mode 100644 invokeai/frontend/web/src/features/nodes/components/nodes/InvocationNode.tsx create mode 100644 invokeai/frontend/web/src/features/nodes/components/nodes/InvocationNodeWrapper.tsx diff --git a/invokeai/frontend/web/src/features/nodes/components/CustomNodes.tsx b/invokeai/frontend/web/src/features/nodes/components/CustomNodes.tsx index 3aacb3cd58..be845df435 100644 --- a/invokeai/frontend/web/src/features/nodes/components/CustomNodes.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/CustomNodes.tsx @@ -1,9 +1,9 @@ -import InvocationNode from './nodes/InvocationNode'; import CurrentImageNode from './nodes/CurrentImageNode'; +import InvocationNodeWrapper from './nodes/InvocationNodeWrapper'; import NotesNode from './nodes/NotesNode'; export const nodeTypes = { - invocation: InvocationNode, + invocation: InvocationNodeWrapper, current_image: CurrentImageNode, notes: NotesNode, }; diff --git a/invokeai/frontend/web/src/features/nodes/components/Invocation/InvocationNode.tsx b/invokeai/frontend/web/src/features/nodes/components/Invocation/InvocationNode.tsx new file mode 100644 index 0000000000..a86b52060b --- /dev/null +++ b/invokeai/frontend/web/src/features/nodes/components/Invocation/InvocationNode.tsx @@ -0,0 +1,84 @@ +import { Flex } from '@chakra-ui/react'; +import { + InvocationNodeData, + InvocationTemplate, +} from 'features/nodes/types/types'; +import { map, some } from 'lodash-es'; +import { memo, useMemo } from 'react'; +import { NodeProps } from 'reactflow'; +import InputField from '../fields/InputField'; +import OutputField from '../fields/OutputField'; +import NodeFooter, { FOOTER_FIELDS } from './NodeFooter'; +import NodeHeader from './NodeHeader'; +import NodeWrapper from './NodeWrapper'; + +type Props = { + nodeProps: NodeProps; + nodeTemplate: InvocationTemplate; +}; + +const InvocationNode = ({ nodeProps, nodeTemplate }: Props) => { + const { id: nodeId, data } = nodeProps; + const { inputs, outputs, isOpen } = data; + + const inputFields = useMemo( + () => map(inputs).filter((i) => i.name !== 'is_intermediate'), + [inputs] + ); + const outputFields = useMemo(() => map(outputs), [outputs]); + + const withFooter = useMemo( + () => some(outputs, (output) => FOOTER_FIELDS.includes(output.type)), + [outputs] + ); + + return ( + + + {isOpen && ( + <> + + + {outputFields.map((field) => ( + + ))} + {inputFields.map((field) => ( + + ))} + + + {withFooter && ( + + )} + + )} + + ); +}; + +export default memo(InvocationNode); diff --git a/invokeai/frontend/web/src/features/nodes/components/Invocation/NodeFooter.tsx b/invokeai/frontend/web/src/features/nodes/components/Invocation/NodeFooter.tsx index 3c513ed29a..38c2001b99 100644 --- a/invokeai/frontend/web/src/features/nodes/components/Invocation/NodeFooter.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/Invocation/NodeFooter.tsx @@ -16,6 +16,9 @@ import { some } from 'lodash-es'; import { ChangeEvent, memo, useCallback, useMemo } from 'react'; import { NodeProps } from 'reactflow'; +export const IMAGE_FIELDS = ['ImageField', 'ImageCollection']; +export const FOOTER_FIELDS = IMAGE_FIELDS; + type Props = { nodeProps: NodeProps; nodeTemplate: InvocationTemplate; @@ -28,7 +31,7 @@ const NodeFooter = (props: Props) => { const hasImageOutput = useMemo( () => some(nodeTemplate?.outputs, (output) => - ['ImageField', 'ImageCollection'].includes(output.type) + IMAGE_FIELDS.includes(output.type) ), [nodeTemplate?.outputs] ); diff --git a/invokeai/frontend/web/src/features/nodes/components/Invocation/NodeHeader.tsx b/invokeai/frontend/web/src/features/nodes/components/Invocation/NodeHeader.tsx new file mode 100644 index 0000000000..a946d21581 --- /dev/null +++ b/invokeai/frontend/web/src/features/nodes/components/Invocation/NodeHeader.tsx @@ -0,0 +1,54 @@ +import { Flex } from '@chakra-ui/react'; +import { + InvocationNodeData, + InvocationTemplate, +} from 'features/nodes/types/types'; +import { memo } from 'react'; +import { NodeProps } from 'reactflow'; +import NodeCollapseButton from '../Invocation/NodeCollapseButton'; +import NodeCollapsedHandles from '../Invocation/NodeCollapsedHandles'; +import NodeNotesEdit from '../Invocation/NodeNotesEdit'; +import NodeStatusIndicator from '../Invocation/NodeStatusIndicator'; +import NodeTitle from '../Invocation/NodeTitle'; + +type Props = { + nodeProps: NodeProps; + nodeTemplate: InvocationTemplate; +}; + +const NodeHeader = (props: Props) => { + const { nodeProps, nodeTemplate } = props; + const { isOpen } = nodeProps.data; + + return ( + + + + + + + + {!isOpen && ( + + )} + + ); +}; + +export default memo(NodeHeader); diff --git a/invokeai/frontend/web/src/features/nodes/components/Invocation/UnknownNodeFallback.tsx b/invokeai/frontend/web/src/features/nodes/components/Invocation/UnknownNodeFallback.tsx new file mode 100644 index 0000000000..a16c6960ec --- /dev/null +++ b/invokeai/frontend/web/src/features/nodes/components/Invocation/UnknownNodeFallback.tsx @@ -0,0 +1,69 @@ +import { Box, Flex, Text } from '@chakra-ui/react'; +import { DRAG_HANDLE_CLASSNAME } from 'features/nodes/types/constants'; +import { InvocationNodeData } from 'features/nodes/types/types'; +import { memo } from 'react'; +import { NodeProps } from 'reactflow'; +import NodeCollapseButton from '../Invocation/NodeCollapseButton'; +import NodeWrapper from '../Invocation/NodeWrapper'; + +type Props = { + nodeProps: NodeProps; +}; + +const UnknownNodeFallback = ({ nodeProps }: Props) => { + const { data } = nodeProps; + const { isOpen, label, type } = data; + return ( + + + + + {label ? `${label} (${type})` : type} + + + {isOpen && ( + + + Unknown node type: + + {type} + + + + )} + + ); +}; + +export default memo(UnknownNodeFallback); diff --git a/invokeai/frontend/web/src/features/nodes/components/nodes/InvocationNode.tsx b/invokeai/frontend/web/src/features/nodes/components/nodes/InvocationNode.tsx deleted file mode 100644 index 4e8369c4ff..0000000000 --- a/invokeai/frontend/web/src/features/nodes/components/nodes/InvocationNode.tsx +++ /dev/null @@ -1,154 +0,0 @@ -import { Box, Flex, Text } from '@chakra-ui/react'; -import { useAppSelector } from 'app/store/storeHooks'; -import { makeTemplateSelector } from 'features/nodes/store/util/makeTemplateSelector'; -import { DRAG_HANDLE_CLASSNAME } from 'features/nodes/types/constants'; -import { InvocationNodeData } from 'features/nodes/types/types'; -import { map } from 'lodash-es'; -import { memo, useMemo } from 'react'; -import { NodeProps } from 'reactflow'; -import NodeCollapseButton from '../Invocation/NodeCollapseButton'; -import NodeCollapsedHandles from '../Invocation/NodeCollapsedHandles'; -import NodeFooter from '../Invocation/NodeFooter'; -import NodeNotesEdit from '../Invocation/NodeNotesEdit'; -import NodeStatusIndicator from '../Invocation/NodeStatusIndicator'; -import NodeTitle from '../Invocation/NodeTitle'; -import NodeWrapper from '../Invocation/NodeWrapper'; -import InputField from '../fields/InputField'; -import OutputField from '../fields/OutputField'; - -const InvocationNode = (props: NodeProps) => { - const { id: nodeId, data } = props; - const { type, inputs, outputs, isOpen } = data; - - const templateSelector = useMemo(() => makeTemplateSelector(type), [type]); - const nodeTemplate = useAppSelector(templateSelector); - const inputFields = useMemo( - () => map(inputs).filter((i) => i.name !== 'is_intermediate'), - [inputs] - ); - const outputFields = useMemo(() => map(outputs), [outputs]); - - if (!nodeTemplate) { - return ( - - - - - {data.label ? `${data.label} (${data.type})` : data.type} - - - {isOpen && ( - - - Unknown node type: - - {data.type} - - - - )} - - ); - } - - return ( - - - - - - - - - {!isOpen && ( - - )} - - {isOpen && ( - <> - - - {outputFields.map((field) => ( - - ))} - {inputFields.map((field) => ( - - ))} - - - - - )} - - ); -}; - -export default memo(InvocationNode); diff --git a/invokeai/frontend/web/src/features/nodes/components/nodes/InvocationNodeWrapper.tsx b/invokeai/frontend/web/src/features/nodes/components/nodes/InvocationNodeWrapper.tsx new file mode 100644 index 0000000000..47bd34c394 --- /dev/null +++ b/invokeai/frontend/web/src/features/nodes/components/nodes/InvocationNodeWrapper.tsx @@ -0,0 +1,24 @@ +import { useAppSelector } from 'app/store/storeHooks'; +import { makeTemplateSelector } from 'features/nodes/store/util/makeTemplateSelector'; +import { InvocationNodeData } from 'features/nodes/types/types'; +import { memo, useMemo } from 'react'; +import { NodeProps } from 'reactflow'; +import InvocationNode from '../Invocation/InvocationNode'; +import UnknownNodeFallback from '../Invocation/UnknownNodeFallback'; + +const InvocationNodeWrapper = (props: NodeProps) => { + const { data } = props; + const { type } = data; + + const templateSelector = useMemo(() => makeTemplateSelector(type), [type]); + + const nodeTemplate = useAppSelector(templateSelector); + + if (!nodeTemplate) { + return ; + } + + return ; +}; + +export default memo(InvocationNodeWrapper);