mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): hide node footer if there is nothing to display
This commit is contained in:
parent
d5f7027597
commit
75ea716c13
@ -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,
|
||||
};
|
||||
|
@ -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);
|
@ -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<InvocationNodeData>;
|
||||
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]
|
||||
);
|
||||
|
@ -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);
|
@ -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);
|
@ -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);
|
@ -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);
|
Loading…
Reference in New Issue
Block a user