feat(ui): hide node footer if there is nothing to display

This commit is contained in:
psychedelicious 2023-08-15 18:24:40 +10:00
parent d5f7027597
commit 75ea716c13
7 changed files with 237 additions and 157 deletions

View File

@ -1,9 +1,9 @@
import InvocationNode from './nodes/InvocationNode';
import CurrentImageNode from './nodes/CurrentImageNode'; import CurrentImageNode from './nodes/CurrentImageNode';
import InvocationNodeWrapper from './nodes/InvocationNodeWrapper';
import NotesNode from './nodes/NotesNode'; import NotesNode from './nodes/NotesNode';
export const nodeTypes = { export const nodeTypes = {
invocation: InvocationNode, invocation: InvocationNodeWrapper,
current_image: CurrentImageNode, current_image: CurrentImageNode,
notes: NotesNode, notes: NotesNode,
}; };

View File

@ -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<InvocationNodeData>;
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 (
<NodeWrapper nodeProps={nodeProps}>
<NodeHeader nodeProps={nodeProps} nodeTemplate={nodeTemplate} />
{isOpen && (
<>
<Flex
layerStyle="nodeBody"
className={'nopan'}
sx={{
cursor: 'auto',
flexDirection: 'column',
w: 'full',
h: 'full',
py: 1,
gap: 1,
borderBottomRadius: withFooter ? 0 : 'base',
}}
>
<Flex
className="nopan"
sx={{ flexDir: 'column', px: 2, w: 'full', h: 'full' }}
>
{outputFields.map((field) => (
<OutputField
key={`${nodeId}.${field.id}.input-field`}
nodeProps={nodeProps}
nodeTemplate={nodeTemplate}
field={field}
/>
))}
{inputFields.map((field) => (
<InputField
key={`${nodeId}.${field.id}.input-field`}
nodeProps={nodeProps}
nodeTemplate={nodeTemplate}
field={field}
/>
))}
</Flex>
</Flex>
{withFooter && (
<NodeFooter nodeProps={nodeProps} nodeTemplate={nodeTemplate} />
)}
</>
)}
</NodeWrapper>
);
};
export default memo(InvocationNode);

View File

@ -16,6 +16,9 @@ import { some } from 'lodash-es';
import { ChangeEvent, memo, useCallback, useMemo } from 'react'; import { ChangeEvent, memo, useCallback, useMemo } from 'react';
import { NodeProps } from 'reactflow'; import { NodeProps } from 'reactflow';
export const IMAGE_FIELDS = ['ImageField', 'ImageCollection'];
export const FOOTER_FIELDS = IMAGE_FIELDS;
type Props = { type Props = {
nodeProps: NodeProps<InvocationNodeData>; nodeProps: NodeProps<InvocationNodeData>;
nodeTemplate: InvocationTemplate; nodeTemplate: InvocationTemplate;
@ -28,7 +31,7 @@ const NodeFooter = (props: Props) => {
const hasImageOutput = useMemo( const hasImageOutput = useMemo(
() => () =>
some(nodeTemplate?.outputs, (output) => some(nodeTemplate?.outputs, (output) =>
['ImageField', 'ImageCollection'].includes(output.type) IMAGE_FIELDS.includes(output.type)
), ),
[nodeTemplate?.outputs] [nodeTemplate?.outputs]
); );

View File

@ -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<InvocationNodeData>;
nodeTemplate: InvocationTemplate;
};
const NodeHeader = (props: Props) => {
const { nodeProps, nodeTemplate } = props;
const { isOpen } = nodeProps.data;
return (
<Flex
layerStyle="nodeHeader"
sx={{
borderTopRadius: 'base',
borderBottomRadius: isOpen ? 0 : 'base',
alignItems: 'center',
justifyContent: 'space-between',
h: 8,
textAlign: 'center',
fontWeight: 600,
color: 'base.700',
_dark: { color: 'base.200' },
}}
>
<NodeCollapseButton nodeProps={nodeProps} />
<NodeTitle nodeData={nodeProps.data} title={nodeTemplate.title} />
<Flex alignItems="center">
<NodeStatusIndicator nodeProps={nodeProps} />
<NodeNotesEdit nodeProps={nodeProps} nodeTemplate={nodeTemplate} />
</Flex>
{!isOpen && (
<NodeCollapsedHandles
nodeProps={nodeProps}
nodeTemplate={nodeTemplate}
/>
)}
</Flex>
);
};
export default memo(NodeHeader);

View File

@ -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<InvocationNodeData>;
};
const UnknownNodeFallback = ({ nodeProps }: Props) => {
const { data } = nodeProps;
const { isOpen, label, type } = data;
return (
<NodeWrapper nodeProps={nodeProps}>
<Flex
className={DRAG_HANDLE_CLASSNAME}
layerStyle="nodeHeader"
sx={{
borderTopRadius: 'base',
borderBottomRadius: isOpen ? 0 : 'base',
alignItems: 'center',
h: 8,
fontWeight: 600,
fontSize: 'sm',
}}
>
<NodeCollapseButton nodeProps={nodeProps} />
<Text
sx={{
w: 'full',
textAlign: 'center',
pe: 8,
color: 'error.500',
_dark: { color: 'error.300' },
}}
>
{label ? `${label} (${type})` : type}
</Text>
</Flex>
{isOpen && (
<Flex
layerStyle="nodeBody"
sx={{
userSelect: 'auto',
flexDirection: 'column',
w: 'full',
h: 'full',
p: 4,
gap: 1,
borderBottomRadius: 'base',
fontSize: 'sm',
}}
>
<Box>
<Text as="span">Unknown node type: </Text>
<Text as="span" fontWeight={600}>
{type}
</Text>
</Box>
</Flex>
)}
</NodeWrapper>
);
};
export default memo(UnknownNodeFallback);

View File

@ -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<InvocationNodeData>) => {
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 (
<NodeWrapper nodeProps={props}>
<Flex
className={DRAG_HANDLE_CLASSNAME}
layerStyle="nodeHeader"
sx={{
borderTopRadius: 'base',
borderBottomRadius: isOpen ? 0 : 'base',
alignItems: 'center',
h: 8,
fontWeight: 600,
fontSize: 'sm',
}}
>
<NodeCollapseButton nodeProps={props} />
<Text
sx={{
w: 'full',
textAlign: 'center',
pe: 8,
color: 'error.500',
_dark: { color: 'error.300' },
}}
>
{data.label ? `${data.label} (${data.type})` : data.type}
</Text>
</Flex>
{isOpen && (
<Flex
layerStyle="nodeBody"
sx={{
userSelect: 'auto',
flexDirection: 'column',
w: 'full',
h: 'full',
p: 4,
gap: 1,
borderBottomRadius: 'base',
fontSize: 'sm',
}}
>
<Box>
<Text as="span">Unknown node type: </Text>
<Text as="span" fontWeight={600}>
{data.type}
</Text>
</Box>
</Flex>
)}
</NodeWrapper>
);
}
return (
<NodeWrapper nodeProps={props}>
<Flex
layerStyle="nodeHeader"
sx={{
borderTopRadius: 'base',
borderBottomRadius: isOpen ? 0 : 'base',
alignItems: 'center',
justifyContent: 'space-between',
h: 8,
textAlign: 'center',
fontWeight: 600,
color: 'base.700',
_dark: { color: 'base.200' },
}}
>
<NodeCollapseButton nodeProps={props} />
<NodeTitle nodeData={props.data} title={nodeTemplate.title} />
<Flex alignItems="center">
<NodeStatusIndicator nodeProps={props} />
<NodeNotesEdit nodeProps={props} nodeTemplate={nodeTemplate} />
</Flex>
{!isOpen && (
<NodeCollapsedHandles nodeProps={props} nodeTemplate={nodeTemplate} />
)}
</Flex>
{isOpen && (
<>
<Flex
layerStyle="nodeBody"
className={'nopan'}
sx={{
cursor: 'auto',
flexDirection: 'column',
w: 'full',
h: 'full',
py: 1,
gap: 1,
}}
>
<Flex
className="nopan"
sx={{ flexDir: 'column', px: 2, w: 'full', h: 'full' }}
>
{outputFields.map((field) => (
<OutputField
key={`${nodeId}.${field.id}.input-field`}
nodeProps={props}
nodeTemplate={nodeTemplate}
field={field}
/>
))}
{inputFields.map((field) => (
<InputField
key={`${nodeId}.${field.id}.input-field`}
nodeProps={props}
nodeTemplate={nodeTemplate}
field={field}
/>
))}
</Flex>
</Flex>
<NodeFooter nodeProps={props} nodeTemplate={nodeTemplate} />
</>
)}
</NodeWrapper>
);
};
export default memo(InvocationNode);

View File

@ -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<InvocationNodeData>) => {
const { data } = props;
const { type } = data;
const templateSelector = useMemo(() => makeTemplateSelector(type), [type]);
const nodeTemplate = useAppSelector(templateSelector);
if (!nodeTemplate) {
return <UnknownNodeFallback nodeProps={props} />;
}
return <InvocationNode nodeProps={props} nodeTemplate={nodeTemplate} />;
};
export default memo(InvocationNodeWrapper);