From d7218d44d77cbc92e02461c2a6b141d979d3f681 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Sun, 9 Jul 2023 10:40:50 +1000 Subject: [PATCH] feat(ui): add progress image node it is excluded from graph, so you can add it without affecting generation --- .../features/nodes/components/AddNodeMenu.tsx | 6 ++ .../src/features/nodes/components/Flow.tsx | 22 +++-- .../components/IAINode/IAINodeHeader.tsx | 19 ++--- .../nodes/components/InvocationComponent.tsx | 82 +++++-------------- .../features/nodes/components/NodeWrapper.tsx | 32 ++++++++ .../nodes/components/ProgressImageNode.tsx | 64 +++++++++++++++ .../nodes/hooks/useBuildInvocation.ts | 18 +++- .../nodes/store/util/makeTemplateSelector.ts | 24 ++++++ .../util/graphBuilders/buildNodesGraph.ts | 4 +- 9 files changed, 185 insertions(+), 86 deletions(-) create mode 100644 invokeai/frontend/web/src/features/nodes/components/NodeWrapper.tsx create mode 100644 invokeai/frontend/web/src/features/nodes/components/ProgressImageNode.tsx create mode 100644 invokeai/frontend/web/src/features/nodes/store/util/makeTemplateSelector.ts diff --git a/invokeai/frontend/web/src/features/nodes/components/AddNodeMenu.tsx b/invokeai/frontend/web/src/features/nodes/components/AddNodeMenu.tsx index 596c2d5ee1..da462f4735 100644 --- a/invokeai/frontend/web/src/features/nodes/components/AddNodeMenu.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/AddNodeMenu.tsx @@ -28,6 +28,12 @@ const selector = createSelector( }; }); + data.push({ + label: 'Progress Image', + value: 'progress_image', + description: 'Displays the progress image in the Node Editor', + }); + return { data }; }, defaultSelectorOptions diff --git a/invokeai/frontend/web/src/features/nodes/components/Flow.tsx b/invokeai/frontend/web/src/features/nodes/components/Flow.tsx index 629c620934..e29ec38c96 100644 --- a/invokeai/frontend/web/src/features/nodes/components/Flow.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/Flow.tsx @@ -1,14 +1,15 @@ +import { RootState } from 'app/store/store'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { useCallback } from 'react'; import { Background, OnConnect, + OnConnectEnd, + OnConnectStart, OnEdgesChange, OnNodesChange, ReactFlow, - OnConnectStart, - OnConnectEnd, } from 'reactflow'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { RootState } from 'app/store/store'; import { connectionEnded, connectionMade, @@ -16,15 +17,18 @@ import { edgesChanged, nodesChanged, } from '../store/nodesSlice'; -import { useCallback } from 'react'; import { InvocationComponent } from './InvocationComponent'; -import TopLeftPanel from './panels/TopLeftPanel'; -import TopRightPanel from './panels/TopRightPanel'; -import TopCenterPanel from './panels/TopCenterPanel'; +import ProgressImageNode from './ProgressImageNode'; import BottomLeftPanel from './panels/BottomLeftPanel.tsx'; import MinimapPanel from './panels/MinimapPanel'; +import TopCenterPanel from './panels/TopCenterPanel'; +import TopLeftPanel from './panels/TopLeftPanel'; +import TopRightPanel from './panels/TopRightPanel'; -const nodeTypes = { invocation: InvocationComponent }; +const nodeTypes = { + invocation: InvocationComponent, + progress_image: ProgressImageNode, +}; export const Flow = () => { const dispatch = useAppDispatch(); diff --git a/invokeai/frontend/web/src/features/nodes/components/IAINode/IAINodeHeader.tsx b/invokeai/frontend/web/src/features/nodes/components/IAINode/IAINodeHeader.tsx index 74b883b1d6..73705769b6 100644 --- a/invokeai/frontend/web/src/features/nodes/components/IAINode/IAINodeHeader.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/IAINode/IAINodeHeader.tsx @@ -1,15 +1,15 @@ -import { Flex, Heading, Tooltip, Icon } from '@chakra-ui/react'; -import { InvocationTemplate } from 'features/nodes/types/types'; +import { Flex, Heading, Icon, Tooltip } from '@chakra-ui/react'; import { memo } from 'react'; import { FaInfoCircle } from 'react-icons/fa'; interface IAINodeHeaderProps { - nodeId: string; - template: InvocationTemplate; + nodeId?: string; + title?: string; + description?: string; } const IAINodeHeader = (props: IAINodeHeaderProps) => { - const { nodeId, template } = props; + const { nodeId, title, description } = props; return ( { _dark: { color: 'base.100' }, }} > - {template.title} + {title} - + { - const [nodeSelectedOutline, nodeShadow] = useToken('shadows', [ - 'nodeSelectedOutline', - 'dark-lg', - ]); - - return ( - - {props.children} - - ); -}; - -const makeTemplateSelector = (type: AnyInvocationType) => - createSelector( - [(state: RootState) => state.nodes], - (nodes) => { - const template = nodes.invocationTemplates[type]; - if (!template) { - return; - } - return template; - }, - { - memoizeOptions: { - resultEqualityCheck: ( - a: InvocationTemplate | undefined, - b: InvocationTemplate | undefined - ) => a !== undefined && b !== undefined && a.type === b.type, - }, - } - ); +import { memo, useMemo } from 'react'; +import { makeTemplateSelector } from '../store/util/makeTemplateSelector'; +import IAINodeHeader from './IAINode/IAINodeHeader'; +import IAINodeInputs from './IAINode/IAINodeInputs'; +import IAINodeOutputs from './IAINode/IAINodeOutputs'; +import IAINodeResizer from './IAINode/IAINodeResizer'; +import NodeWrapper from './NodeWrapper'; export const InvocationComponent = memo((props: NodeProps) => { const { id: nodeId, data, selected } = props; @@ -70,7 +22,7 @@ export const InvocationComponent = memo((props: NodeProps) => { if (!template) { return ( - + ) => { > - + ); } return ( - - + + ) => { - + ); }); diff --git a/invokeai/frontend/web/src/features/nodes/components/NodeWrapper.tsx b/invokeai/frontend/web/src/features/nodes/components/NodeWrapper.tsx new file mode 100644 index 0000000000..7a76cd5902 --- /dev/null +++ b/invokeai/frontend/web/src/features/nodes/components/NodeWrapper.tsx @@ -0,0 +1,32 @@ +import { Box, useToken } from '@chakra-ui/react'; +import { NODE_MIN_WIDTH } from 'app/constants'; + +import { PropsWithChildren } from 'react'; + +type NodeWrapperProps = PropsWithChildren & { + selected: boolean; +}; + +const NodeWrapper = (props: NodeWrapperProps) => { + const [nodeSelectedOutline, nodeShadow] = useToken('shadows', [ + 'nodeSelectedOutline', + 'dark-lg', + ]); + + return ( + + {props.children} + + ); +}; + +export default NodeWrapper; diff --git a/invokeai/frontend/web/src/features/nodes/components/ProgressImageNode.tsx b/invokeai/frontend/web/src/features/nodes/components/ProgressImageNode.tsx new file mode 100644 index 0000000000..6424d4f76c --- /dev/null +++ b/invokeai/frontend/web/src/features/nodes/components/ProgressImageNode.tsx @@ -0,0 +1,64 @@ +import { Flex, Image } from '@chakra-ui/react'; +import { NodeProps } from 'reactflow'; +import { InvocationValue } from '../types/types'; + +import { useAppSelector } from 'app/store/storeHooks'; +import { IAINoContentFallback } from 'common/components/IAIImageFallback'; +import { memo } from 'react'; +import IAINodeHeader from './IAINode/IAINodeHeader'; +import IAINodeResizer from './IAINode/IAINodeResizer'; +import NodeWrapper from './NodeWrapper'; + +const ProgressImageNode = (props: NodeProps) => { + const progressImage = useAppSelector((state) => state.system.progressImage); + const { selected } = props; + + return ( + + + + + {progressImage ? ( + + ) : ( + + + + )} + + + + ); +}; + +export default memo(ProgressImageNode); diff --git a/invokeai/frontend/web/src/features/nodes/hooks/useBuildInvocation.ts b/invokeai/frontend/web/src/features/nodes/hooks/useBuildInvocation.ts index 0526eee969..5ed631e9fa 100644 --- a/invokeai/frontend/web/src/features/nodes/hooks/useBuildInvocation.ts +++ b/invokeai/frontend/web/src/features/nodes/hooks/useBuildInvocation.ts @@ -24,7 +24,23 @@ export const useBuildInvocation = () => { const flow = useReactFlow(); return useCallback( - (type: AnyInvocationType) => { + (type: AnyInvocationType | 'progress_image') => { + if (type === 'progress_image') { + const { x, y } = flow.project({ + x: window.innerWidth / 2.5, + y: window.innerHeight / 8, + }); + + const node: Node = { + id: 'progress_image', + type: 'progress_image', + position: { x: x, y: y }, + data: {}, + }; + + return node; + } + const template = invocationTemplates[type]; if (template === undefined) { diff --git a/invokeai/frontend/web/src/features/nodes/store/util/makeTemplateSelector.ts b/invokeai/frontend/web/src/features/nodes/store/util/makeTemplateSelector.ts new file mode 100644 index 0000000000..0a314a75de --- /dev/null +++ b/invokeai/frontend/web/src/features/nodes/store/util/makeTemplateSelector.ts @@ -0,0 +1,24 @@ +import { createSelector } from '@reduxjs/toolkit'; +import { RootState } from 'app/store/store'; +import { InvocationTemplate } from 'features/nodes/types/types'; +import { AnyInvocationType } from 'services/events/types'; + +export const makeTemplateSelector = (type: AnyInvocationType) => + createSelector( + [(state: RootState) => state.nodes], + (nodes) => { + const template = nodes.invocationTemplates[type]; + if (!template) { + return; + } + return template; + }, + { + memoizeOptions: { + resultEqualityCheck: ( + a: InvocationTemplate | undefined, + b: InvocationTemplate | undefined + ) => a !== undefined && b !== undefined && a.type === b.type, + }, + } + ); diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildNodesGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildNodesGraph.ts index 12a567b009..ea68ac50fb 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildNodesGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildNodesGraph.ts @@ -54,8 +54,10 @@ export const parseFieldValue = (field: InputFieldValue) => { export const buildNodesGraph = (state: RootState): Graph => { const { nodes, edges } = state.nodes; + const filteredNodes = nodes.filter((n) => n.type !== 'progress_image'); + // Reduce the node editor nodes into invocation graph nodes - const parsedNodes = nodes.reduce>( + const parsedNodes = filteredNodes.reduce>( (nodesAccumulator, node, nodeIndex) => { const { id, data } = node; const { type, inputs } = data;