From bf60be99dc76389ece4ccb60dc7c97baeb0858fc Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 22 Jan 2025 16:47:03 +1100 Subject: [PATCH] feat(ui): iterate on builder (WIP) --- .../builder/ColumnElementComponent.tsx | 80 ----------------- .../builder/ContainerElementComponent.tsx | 75 +++++++++------- .../sidePanel/builder/WorkflowBuilder.tsx | 4 +- .../sidePanel/workflow/WorkflowPanel.tsx | 5 ++ .../web/src/features/nodes/types/workflow.ts | 88 ++++++------------- 5 files changed, 78 insertions(+), 174 deletions(-) delete mode 100644 invokeai/frontend/web/src/features/nodes/components/sidePanel/builder/ColumnElementComponent.tsx diff --git a/invokeai/frontend/web/src/features/nodes/components/sidePanel/builder/ColumnElementComponent.tsx b/invokeai/frontend/web/src/features/nodes/components/sidePanel/builder/ColumnElementComponent.tsx deleted file mode 100644 index 1dfa3a712b..0000000000 --- a/invokeai/frontend/web/src/features/nodes/components/sidePanel/builder/ColumnElementComponent.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import { Flex, Grid, GridItem } from '@invoke-ai/ui-library'; -import { - ContainerElementComponent, - useContainerContext, -} from 'features/nodes/components/sidePanel/builder/ContainerElementComponent'; -import { DividerElementComponent } from 'features/nodes/components/sidePanel/builder/DividerElementComponent'; -import { HeadingElementComponent } from 'features/nodes/components/sidePanel/builder/HeadingElementComponent'; -import { NodeFieldElementComponent } from 'features/nodes/components/sidePanel/builder/NodeFieldElementComponent'; -import { TextElementComponent } from 'features/nodes/components/sidePanel/builder/TextElementComponent'; -import type { ColumnChildElement, ColumnElement } from 'features/nodes/types/workflow'; -import type { PropsWithChildren } from 'react'; -import { createContext, memo, useContext, useMemo } from 'react'; -import type { Equals } from 'tsafe'; -import { assert } from 'tsafe'; - -const _ColumnContext = createContext<{ columnId: string; columnNumber: number } | null>(null); -const ColumnContextProvider = ({ - columnId, - columnNumber, - children, -}: PropsWithChildren<{ columnId: string; columnNumber: number }>) => { - const ctx = useMemo(() => ({ columnId, columnNumber }), [columnId, columnNumber]); - return <_ColumnContext.Provider value={ctx}>{children}; -}; -export const useColumnContext = () => { - const context = useContext(_ColumnContext); - assert(context !== null); - return context; -}; - -const ColumnElementChildComponent = memo(({ element }: { element: ColumnChildElement }) => { - const { type, id } = element; - switch (type) { - case 'container': - return ; - case 'node-field': - return ; - case 'divider': - return ; - case 'heading': - return ; - case 'text': - return ; - default: - assert>(false, `Unhandled type ${type}`); - } -}); -ColumnElementChildComponent.displayName = 'ColumnElementChildComponent'; - -export const ColumnElementComponent = memo(({ element }: { element: ColumnElement }) => { - const containerCtx = useContainerContext(); - const columnNumber = useMemo( - () => containerCtx.columnIds.indexOf(element.id) + 1, - [containerCtx.columnIds, element.id] - ); - const withDivider = useMemo( - () => containerCtx.columnIds.indexOf(element.id) + 1 < containerCtx.columnIds.length, - [containerCtx.columnIds, element.id] - ); - return ( - - <> - - {element.data.elements.map((element) => ( - - ))} - - {withDivider && } - - - ); -}); - -ColumnElementComponent.displayName = 'ColumnElementComponent'; diff --git a/invokeai/frontend/web/src/features/nodes/components/sidePanel/builder/ContainerElementComponent.tsx b/invokeai/frontend/web/src/features/nodes/components/sidePanel/builder/ContainerElementComponent.tsx index 9446e7301b..537b10d5c3 100644 --- a/invokeai/frontend/web/src/features/nodes/components/sidePanel/builder/ContainerElementComponent.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/sidePanel/builder/ContainerElementComponent.tsx @@ -1,29 +1,13 @@ -import { Grid } from '@invoke-ai/ui-library'; -import { ColumnElementComponent } from 'features/nodes/components/sidePanel/builder/ColumnElementComponent'; -import type { ContainerElement } from 'features/nodes/types/workflow'; -import type { PropsWithChildren } from 'react'; -import { createContext, memo, useContext, useMemo } from 'react'; +import { Flex, Grid, GridItem } from '@invoke-ai/ui-library'; +import { DividerElementComponent } from 'features/nodes/components/sidePanel/builder/DividerElementComponent'; +import { HeadingElementComponent } from 'features/nodes/components/sidePanel/builder/HeadingElementComponent'; +import { NodeFieldElementComponent } from 'features/nodes/components/sidePanel/builder/NodeFieldElementComponent'; +import { TextElementComponent } from 'features/nodes/components/sidePanel/builder/TextElementComponent'; +import type { ContainerElement, FormElement } from 'features/nodes/types/workflow'; +import { Fragment, memo } from 'react'; +import type { Equals } from 'tsafe'; import { assert } from 'tsafe'; -const _ContainerContext = createContext<{ containerId: string; columnIds: string[]; depth: number } | null>(null); -const ContainerContextProvider = ({ - containerId, - columnIds, - children, -}: PropsWithChildren<{ containerId: string; columnIds: string[] }>) => { - const parentCtx = useContext(_ContainerContext); - const ctx = useMemo( - () => ({ containerId, columnIds, depth: parentCtx ? parentCtx.depth + 1 : 0 }), - [columnIds, containerId, parentCtx] - ); - return <_ContainerContext.Provider value={ctx}>{children}; -}; -export const useContainerContext = () => { - const context = useContext(_ContainerContext); - assert(context !== null); - return context; -}; - const getGridTemplateColumns = (count: number) => { return Array.from({ length: count }, () => '1fr').join(' auto '); }; @@ -31,16 +15,43 @@ const getGridTemplateColumns = (count: number) => { export const ContainerElementComponent = memo(({ element }: { element: ContainerElement }) => { const { id, data } = element; const { columns } = data; - const columnIds = useMemo(() => columns.map((column) => column.id), [columns]); return ( - - - {columns.map((element, i) => { - return ; - })} - - + + {columns.map((elements, columnIndex) => { + const key = `${element.id}_${columnIndex}`; + const withDivider = columnIndex < columns.length - 1; + return ( + + + {elements.map((element) => ( + + ))} + + {withDivider && } + + ); + })} + ); }); ContainerElementComponent.displayName = 'ContainerElementComponent'; + +export const FormElementComponent = memo(({ element }: { element: FormElement }) => { + const { type, id } = element; + switch (type) { + case 'container': + return ; + case 'node-field': + return ; + case 'divider': + return ; + case 'heading': + return ; + case 'text': + return ; + default: + assert>(false, `Unhandled type ${type}`); + } +}); +FormElementComponent.displayName = 'FormElementComponent'; diff --git a/invokeai/frontend/web/src/features/nodes/components/sidePanel/builder/WorkflowBuilder.tsx b/invokeai/frontend/web/src/features/nodes/components/sidePanel/builder/WorkflowBuilder.tsx index b48288f025..62dedaabad 100644 --- a/invokeai/frontend/web/src/features/nodes/components/sidePanel/builder/WorkflowBuilder.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/sidePanel/builder/WorkflowBuilder.tsx @@ -1,6 +1,6 @@ import { Flex } from '@invoke-ai/ui-library'; import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent'; -import { ContainerElementComponent } from 'features/nodes/components/sidePanel/builder/ContainerElementComponent'; +import { FormElementComponent } from 'features/nodes/components/sidePanel/builder/ContainerElementComponent'; import { data } from 'features/nodes/types/workflow'; import { memo } from 'react'; @@ -9,7 +9,7 @@ export const WorkflowBuilder = memo(() => { - + diff --git a/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowPanel.tsx b/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowPanel.tsx index 4e00f593c5..e21606101b 100644 --- a/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowPanel.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowPanel.tsx @@ -1,5 +1,6 @@ import { Flex, Tab, TabList, TabPanel, TabPanels, Tabs } from '@invoke-ai/ui-library'; import { WorkflowBuilder } from 'features/nodes/components/sidePanel/builder/WorkflowBuilder'; +import WorkflowLinearTab from 'features/nodes/components/sidePanel/workflow/WorkflowLinearTab'; import { memo } from 'react'; import { useTranslation } from 'react-i18next'; @@ -12,6 +13,7 @@ const WorkflowFieldsLinearViewPanel = () => { + Builder {t('common.linear')} {t('common.details')} JSON @@ -21,6 +23,9 @@ const WorkflowFieldsLinearViewPanel = () => { + + + diff --git a/invokeai/frontend/web/src/features/nodes/types/workflow.ts b/invokeai/frontend/web/src/features/nodes/types/workflow.ts index 4c323061d6..a1d72f93ba 100644 --- a/invokeai/frontend/web/src/features/nodes/types/workflow.ts +++ b/invokeai/frontend/web/src/features/nodes/types/workflow.ts @@ -147,40 +147,18 @@ const divider = (): DividerElement => ({ type: 'divider', }); -export type ColumnElement = { - id: string; - type: 'column'; - data: { - elements: ColumnChildElement[]; - }; -}; - -const zColumnElement = zElementBase.extend({ - type: z.literal('column'), - data: z.object({ - elements: z.lazy(() => z.array(zColumnChildElement)), - }), -}); -const column = (elements: ColumnElement['data']['elements']): ColumnElement => ({ - id: nanoid(), - type: 'column', - data: { - elements, - }, -}); - export type ContainerElement = { id: string; type: 'container'; data: { - columns: ColumnElement[]; + columns: FormElement[][]; }; }; const zContainerElement: z.ZodType = zElementBase.extend({ type: z.literal('container'), data: z.object({ - columns: z.lazy(() => z.array(zColumnElement)), + columns: z.lazy(() => z.array(z.array(zFormElement))), }), }); const container = (columns: ContainerElement['data']['columns']): ContainerElement => ({ @@ -191,70 +169,60 @@ const container = (columns: ContainerElement['data']['columns']): ContainerEleme }, }); -// export type CollapsibleElement = { -// id: string; -// type: 'collapsible'; -// columns: BuilderElement[]; -// title: string; -// collapsed: boolean; -// }; +const zFormElement = z.union([zContainerElement, zNodeFieldElement, zHeadingElement, zTextElement, zDividerElement]); -// const zCollapsibleElement: z.ZodType = z.object({ -// type: z.literal('collapsible'), -// columns: z.lazy(() => z.array(zElement)), -// title: z.string(), -// collapsed: z.boolean(), -// }); - -const zColumnChildElement = z.union([ - zContainerElement, - // zCollapsibleElement - zNodeFieldElement, - zHeadingElement, - zTextElement, - zDividerElement, -]); - -export type ColumnChildElement = z.infer; +export type FormElement = z.infer; export const data: ContainerElement = container([ - column([ + [ heading('My Cool Workflow', 1), text('This is a description of what my workflow does. It does things.', 'md'), divider(), heading('First Section', 2), text('The first section includes fields relevant to the first section. This note describes that fact.', 'sm'), container([ - column([nodeField('7aed1a5f-7fd7-4184-abe8-ddea0ea5e706', 'image')]), - column([nodeField('7aed1a5f-7fd7-4184-abe8-ddea0ea5e706', 'image')]), - column([nodeField('7aed1a5f-7fd7-4184-abe8-ddea0ea5e706', 'image')]), + [nodeField('7aed1a5f-7fd7-4184-abe8-ddea0ea5e706', 'image')], + [nodeField('7aed1a5f-7fd7-4184-abe8-ddea0ea5e706', 'image')], + [nodeField('7aed1a5f-7fd7-4184-abe8-ddea0ea5e706', 'image')], ]), nodeField('9c058600-8d73-4702-912b-0ccf37403bfd', 'value'), nodeField('7a8bbab2-6919-4cfc-bd7c-bcfda3c79ecf', 'value'), nodeField('4e16cbf6-457c-46fb-9ab7-9cb262fa1e03', 'value'), nodeField('39cb5272-a9d7-4da9-9c35-32e02b46bb34', 'color'), container([ - column([ + [ nodeField('4f609a81-0e25-47d1-ba0d-f24fedd5273f', 'value'), nodeField('4f609a81-0e25-47d1-ba0d-f24fedd5273f', 'value'), - ]), - column([ + ], + [ nodeField('4f609a81-0e25-47d1-ba0d-f24fedd5273f', 'value'), nodeField('4f609a81-0e25-47d1-ba0d-f24fedd5273f', 'value'), nodeField('4f609a81-0e25-47d1-ba0d-f24fedd5273f', 'value'), nodeField('4f609a81-0e25-47d1-ba0d-f24fedd5273f', 'value'), - ]), + ], + [ + container([ + [ + nodeField('4f609a81-0e25-47d1-ba0d-f24fedd5273f', 'value'), + nodeField('4f609a81-0e25-47d1-ba0d-f24fedd5273f', 'value'), + ], + [ + nodeField('4f609a81-0e25-47d1-ba0d-f24fedd5273f', 'value'), + nodeField('4f609a81-0e25-47d1-ba0d-f24fedd5273f', 'value'), + ], + ]), + ], ]), nodeField('14744f68-9000-4694-b4d6-cbe83ee231ee', 'model'), divider(), text('These are some text that are definitely super helpful.', 'sm'), divider(), container([ - column([ + [ nodeField('7aed1a5f-7fd7-4184-abe8-ddea0ea5e706', 'image'), nodeField('7aed1a5f-7fd7-4184-abe8-ddea0ea5e706', 'image'), - ]), - column([nodeField('7a8bbab2-6919-4cfc-bd7c-bcfda3c79ecf', 'value')]), + ], + [nodeField('7a8bbab2-6919-4cfc-bd7c-bcfda3c79ecf', 'value')], ]), - ]), + ], ]);