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 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,
|
||||||
};
|
};
|
||||||
|
@ -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 { 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]
|
||||||
);
|
);
|
||||||
|
@ -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