From 25313663869134cc5c05f524eec7fc90dbf8a434 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 6 Jun 2025 20:05:02 +1000 Subject: [PATCH] feat(ui): simple session initial state --- .../components/CanvasMainPanelContent.tsx | 7 +- .../NoSession/GenerateWithControlImage.tsx | 68 ------------------- .../NoSession/GenerateWithStartingImage.tsx | 65 ------------------ ...enerateWithStartingImageAndInpaintMask.tsx | 65 ------------------ .../components/NoSession/NoSession.tsx | 32 --------- .../components/SimpleSession/InitialState.tsx | 50 ++++++++++++++ .../InitialStateAddAStyleReference.tsx | 39 +++++++++++ .../InitialStateCardGridItem.tsx | 24 +++++++ .../InitialStateEditImageCard.tsx | 39 +++++++++++ .../InitialStateGenerateFromText.tsx | 33 +++++++++ .../InitialStateUseALayoutImageCard.tsx | 39 +++++++++++ .../SimpleSession/SimpleSession.tsx | 6 +- .../DeleteAllExceptCurrentButton.tsx | 32 +++++++++ .../DeleteAllExceptCurrentIconButton.tsx | 25 +++++++ .../queue/components/QueueControls.tsx | 4 +- .../components/QueueTabQueueControls.tsx | 5 +- 16 files changed, 291 insertions(+), 242 deletions(-) delete mode 100644 invokeai/frontend/web/src/features/controlLayers/components/NoSession/GenerateWithControlImage.tsx delete mode 100644 invokeai/frontend/web/src/features/controlLayers/components/NoSession/GenerateWithStartingImage.tsx delete mode 100644 invokeai/frontend/web/src/features/controlLayers/components/NoSession/GenerateWithStartingImageAndInpaintMask.tsx delete mode 100644 invokeai/frontend/web/src/features/controlLayers/components/NoSession/NoSession.tsx create mode 100644 invokeai/frontend/web/src/features/controlLayers/components/SimpleSession/InitialState.tsx create mode 100644 invokeai/frontend/web/src/features/controlLayers/components/SimpleSession/InitialStateAddAStyleReference.tsx create mode 100644 invokeai/frontend/web/src/features/controlLayers/components/SimpleSession/InitialStateCardGridItem.tsx create mode 100644 invokeai/frontend/web/src/features/controlLayers/components/SimpleSession/InitialStateEditImageCard.tsx create mode 100644 invokeai/frontend/web/src/features/controlLayers/components/SimpleSession/InitialStateGenerateFromText.tsx create mode 100644 invokeai/frontend/web/src/features/controlLayers/components/SimpleSession/InitialStateUseALayoutImageCard.tsx create mode 100644 invokeai/frontend/web/src/features/queue/components/DeleteAllExceptCurrentButton.tsx create mode 100644 invokeai/frontend/web/src/features/queue/components/DeleteAllExceptCurrentIconButton.tsx diff --git a/invokeai/frontend/web/src/features/controlLayers/components/CanvasMainPanelContent.tsx b/invokeai/frontend/web/src/features/controlLayers/components/CanvasMainPanelContent.tsx index 242e3e363c..b42a2f716a 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/CanvasMainPanelContent.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/CanvasMainPanelContent.tsx @@ -1,6 +1,5 @@ import { useAppSelector } from 'app/store/storeHooks'; import { AdvancedSession } from 'features/controlLayers/components/AdvancedSession/AdvancedSession'; -import { NoSession } from 'features/controlLayers/components/NoSession/NoSession'; import { SimpleSession } from 'features/controlLayers/components/SimpleSession/SimpleSession'; import { selectCanvasSessionId, selectCanvasSessionType } from 'features/controlLayers/store/canvasStagingAreaSlice'; import { memo } from 'react'; @@ -12,11 +11,7 @@ export const CanvasMainPanelContent = memo(() => { const id = useAppSelector(selectCanvasSessionId); if (type === 'simple') { - if (id === null) { - return ; - } else { - return ; - } + return ; } if (type === 'advanced') { diff --git a/invokeai/frontend/web/src/features/controlLayers/components/NoSession/GenerateWithControlImage.tsx b/invokeai/frontend/web/src/features/controlLayers/components/NoSession/GenerateWithControlImage.tsx deleted file mode 100644 index 14b253330c..0000000000 --- a/invokeai/frontend/web/src/features/controlLayers/components/NoSession/GenerateWithControlImage.tsx +++ /dev/null @@ -1,68 +0,0 @@ -/* eslint-disable i18next/no-literal-string */ - -import { Button, Flex, Text } from '@invoke-ai/ui-library'; -import { useAppStore } from 'app/store/nanostores/store'; -import { useImageUploadButton } from 'common/hooks/useImageUploadButton'; -import { newCanvasFromImageDndTarget } from 'features/dnd/dnd'; -import { DndDropTarget } from 'features/dnd/DndDropTarget'; -import { newCanvasFromImage } from 'features/imageActions/actions'; -import { memo, useMemo } from 'react'; -import { Trans } from 'react-i18next'; -import { PiUploadBold } from 'react-icons/pi'; -import type { ImageDTO } from 'services/api/types'; -import type { Param0 } from 'tsafe'; - -const generateWithControlImageDndTargetData = newCanvasFromImageDndTarget.getData({ - type: 'control_layer', - withResize: true, -}); - -export const GenerateWithControlImage = memo(() => { - const { getState, dispatch } = useAppStore(); - const useImageUploadButtonOptions = useMemo>( - () => ({ - onUpload: (imageDTO: ImageDTO) => { - newCanvasFromImage({ imageDTO, type: 'control_layer', withResize: true, getState, dispatch }); - }, - allowMultiple: false, - }), - [dispatch, getState] - ); - const uploadApi = useImageUploadButton(useImageUploadButtonOptions); - const components = useMemo( - () => ({ - UploadButton: ( - - or - - - - - - - ); -}); -NoSession.displayName = 'NoSession'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/SimpleSession/InitialState.tsx b/invokeai/frontend/web/src/features/controlLayers/components/SimpleSession/InitialState.tsx new file mode 100644 index 0000000000..31dc291cda --- /dev/null +++ b/invokeai/frontend/web/src/features/controlLayers/components/SimpleSession/InitialState.tsx @@ -0,0 +1,50 @@ +/* eslint-disable i18next/no-literal-string */ + +import { Button, Flex, Grid, Heading, Text } from '@invoke-ai/ui-library'; +import { useAppDispatch } from 'app/store/storeHooks'; +import { InitialStateAddAStyleReference } from 'features/controlLayers/components/SimpleSession/InitialStateAddAStyleReference'; +import { InitialStateCardGridItem } from 'features/controlLayers/components/SimpleSession/InitialStateCardGridItem'; +import { InitialStateEditImageCard } from 'features/controlLayers/components/SimpleSession/InitialStateEditImageCard'; +import { InitialStateGenerateFromText } from 'features/controlLayers/components/SimpleSession/InitialStateGenerateFromText'; +import { InitialStateUseALayoutImageCard } from 'features/controlLayers/components/SimpleSession/InitialStateUseALayoutImageCard'; +import { canvasSessionTypeChanged } from 'features/controlLayers/store/canvasStagingAreaSlice'; +import { memo, useCallback } from 'react'; + +export const InitialState = memo(() => { + const dispatch = useAppDispatch(); + const newCanvasSession = useCallback(() => { + dispatch(canvasSessionTypeChanged({ type: 'advanced' })); + }, [dispatch]); + + return ( + + Choose a starting method. + + Drag an image onto a card or click the upload icon. + + + + + + + + + + + + + + + + + + + or{' '} + + + + ); +}); +InitialState.displayName = 'InitialState'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/SimpleSession/InitialStateAddAStyleReference.tsx b/invokeai/frontend/web/src/features/controlLayers/components/SimpleSession/InitialStateAddAStyleReference.tsx new file mode 100644 index 0000000000..f41ad61ffa --- /dev/null +++ b/invokeai/frontend/web/src/features/controlLayers/components/SimpleSession/InitialStateAddAStyleReference.tsx @@ -0,0 +1,39 @@ +/* eslint-disable i18next/no-literal-string */ + +import { Flex, Heading, Icon, Text } from '@invoke-ai/ui-library'; +import { useAppStore } from 'app/store/nanostores/store'; +import { UploadImageIconButton } from 'common/hooks/useImageUploadButton'; +import { newCanvasFromImageDndTarget } from 'features/dnd/dnd'; +import { DndDropTarget } from 'features/dnd/DndDropTarget'; +import { newCanvasFromImage } from 'features/imageActions/actions'; +import { memo, useCallback } from 'react'; +import { PiUserCircleGearBold } from 'react-icons/pi'; +import type { ImageDTO } from 'services/api/types'; + +const NEW_CANVAS_OPTIONS = { type: 'reference_image' } as const; + +const dndTargetData = newCanvasFromImageDndTarget.getData(NEW_CANVAS_OPTIONS); + +export const InitialStateAddAStyleReference = memo(() => { + const { getState, dispatch } = useAppStore(); + + const onUpload = useCallback( + (imageDTO: ImageDTO) => { + newCanvasFromImage({ imageDTO, getState, dispatch, ...NEW_CANVAS_OPTIONS }); + }, + [dispatch, getState] + ); + + return ( + <> + + Add a Style Reference + Add an image to transfer its look. + + + + + + ); +}); +InitialStateAddAStyleReference.displayName = 'InitialStateAddAStyleReference'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/SimpleSession/InitialStateCardGridItem.tsx b/invokeai/frontend/web/src/features/controlLayers/components/SimpleSession/InitialStateCardGridItem.tsx new file mode 100644 index 0000000000..708b005db1 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlLayers/components/SimpleSession/InitialStateCardGridItem.tsx @@ -0,0 +1,24 @@ +import { GridItem } from '@invoke-ai/ui-library'; +import { memo, type PropsWithChildren } from 'react'; + +export const InitialStateCardGridItem = memo((props: PropsWithChildren) => { + return ( + + {props.children} + + ); +}); + +InitialStateCardGridItem.displayName = 'InitialStateCardGridItem'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/SimpleSession/InitialStateEditImageCard.tsx b/invokeai/frontend/web/src/features/controlLayers/components/SimpleSession/InitialStateEditImageCard.tsx new file mode 100644 index 0000000000..d0c7d0032c --- /dev/null +++ b/invokeai/frontend/web/src/features/controlLayers/components/SimpleSession/InitialStateEditImageCard.tsx @@ -0,0 +1,39 @@ +/* eslint-disable i18next/no-literal-string */ + +import { Flex, Heading, Icon, Text } from '@invoke-ai/ui-library'; +import { useAppStore } from 'app/store/nanostores/store'; +import { UploadImageIconButton } from 'common/hooks/useImageUploadButton'; +import { newCanvasFromImageDndTarget } from 'features/dnd/dnd'; +import { DndDropTarget } from 'features/dnd/DndDropTarget'; +import { newCanvasFromImage } from 'features/imageActions/actions'; +import { memo, useCallback } from 'react'; +import { PiPencilBold } from 'react-icons/pi'; +import type { ImageDTO } from 'services/api/types'; + +const NEW_CANVAS_OPTIONS = { type: 'raster_layer', withInpaintMask: true } as const; + +const dndTargetData = newCanvasFromImageDndTarget.getData(NEW_CANVAS_OPTIONS); + +export const InitialStateEditImageCard = memo(() => { + const { getState, dispatch } = useAppStore(); + + const onUpload = useCallback( + (imageDTO: ImageDTO) => { + newCanvasFromImage({ imageDTO, getState, dispatch, ...NEW_CANVAS_OPTIONS }); + }, + [dispatch, getState] + ); + + return ( + <> + + Edit Image + Add an image to refine. + + + + + + ); +}); +InitialStateEditImageCard.displayName = 'InitialStateEditImageCard'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/SimpleSession/InitialStateGenerateFromText.tsx b/invokeai/frontend/web/src/features/controlLayers/components/SimpleSession/InitialStateGenerateFromText.tsx new file mode 100644 index 0000000000..6d4fa5a2cf --- /dev/null +++ b/invokeai/frontend/web/src/features/controlLayers/components/SimpleSession/InitialStateGenerateFromText.tsx @@ -0,0 +1,33 @@ +/* eslint-disable i18next/no-literal-string */ + +import { Flex, Heading, Icon, IconButton, Text } from '@invoke-ai/ui-library'; +import { memo } from 'react'; +import { PiCursorTextBold, PiTextAaBold } from 'react-icons/pi'; + +const focusOnPrompt = () => { + const promptElement = document.getElementById('prompt'); + if (promptElement instanceof HTMLTextAreaElement) { + promptElement.focus(); + promptElement.select(); + } +}; + +export const InitialStateGenerateFromText = memo(() => { + return ( + <> + + Generate from Text + Enter a prompt and Invoke. + + } + variant="link" + h={8} + /> + + + ); +}); +InitialStateGenerateFromText.displayName = 'InitialStateGenerateFromText'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/SimpleSession/InitialStateUseALayoutImageCard.tsx b/invokeai/frontend/web/src/features/controlLayers/components/SimpleSession/InitialStateUseALayoutImageCard.tsx new file mode 100644 index 0000000000..54d2ef6f75 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlLayers/components/SimpleSession/InitialStateUseALayoutImageCard.tsx @@ -0,0 +1,39 @@ +/* eslint-disable i18next/no-literal-string */ + +import { Flex, Heading, Icon, Text } from '@invoke-ai/ui-library'; +import { useAppStore } from 'app/store/nanostores/store'; +import { UploadImageIconButton } from 'common/hooks/useImageUploadButton'; +import { newCanvasFromImageDndTarget } from 'features/dnd/dnd'; +import { DndDropTarget } from 'features/dnd/DndDropTarget'; +import { newCanvasFromImage } from 'features/imageActions/actions'; +import { memo, useCallback } from 'react'; +import { PiRectangleDashedBold } from 'react-icons/pi'; +import type { ImageDTO } from 'services/api/types'; + +const NEW_CANVAS_OPTIONS = { type: 'control_layer', withResize: true } as const; + +const dndTargetData = newCanvasFromImageDndTarget.getData(NEW_CANVAS_OPTIONS); + +export const InitialStateUseALayoutImageCard = memo(() => { + const { getState, dispatch } = useAppStore(); + + const onUpload = useCallback( + (imageDTO: ImageDTO) => { + newCanvasFromImage({ imageDTO, getState, dispatch, ...NEW_CANVAS_OPTIONS }); + }, + [dispatch, getState] + ); + + return ( + <> + + Use a Layout Image + Add an image to control composition. + + + + + + ); +}); +InitialStateUseALayoutImageCard.displayName = 'InitialStateUseALayoutImageCard'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/SimpleSession/SimpleSession.tsx b/invokeai/frontend/web/src/features/controlLayers/components/SimpleSession/SimpleSession.tsx index 621bf95141..750066526e 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/SimpleSession/SimpleSession.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/SimpleSession/SimpleSession.tsx @@ -1,8 +1,12 @@ import { CanvasSessionContextProvider } from 'features/controlLayers/components/SimpleSession/context'; +import { InitialState } from 'features/controlLayers/components/SimpleSession/InitialState'; import { StagingArea } from 'features/controlLayers/components/SimpleSession/StagingArea'; import { memo } from 'react'; -export const SimpleSession = memo(({ id }: { id: string }) => { +export const SimpleSession = memo(({ id }: { id: string | null }) => { + if (id === null) { + return ; + } return ( diff --git a/invokeai/frontend/web/src/features/queue/components/DeleteAllExceptCurrentButton.tsx b/invokeai/frontend/web/src/features/queue/components/DeleteAllExceptCurrentButton.tsx new file mode 100644 index 0000000000..a3ffd7d519 --- /dev/null +++ b/invokeai/frontend/web/src/features/queue/components/DeleteAllExceptCurrentButton.tsx @@ -0,0 +1,32 @@ +import type { ButtonProps } from '@invoke-ai/ui-library'; +import { Button } from '@invoke-ai/ui-library'; +import { useDeleteAllExceptCurrentQueueItemDialog } from 'features/queue/components/DeleteAllExceptCurrentQueueItemConfirmationAlertDialog'; +import { memo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { PiXCircle } from 'react-icons/pi'; + +type Props = ButtonProps; + +export const DeleteAllExceptCurrentButton = memo((props: Props) => { + const { t } = useTranslation(); + const deleteAllExceptCurrent = useDeleteAllExceptCurrentQueueItemDialog(); + + return ( + <> + + + ); +}); + +DeleteAllExceptCurrentButton.displayName = 'DeleteAllExceptCurrentButton'; diff --git a/invokeai/frontend/web/src/features/queue/components/DeleteAllExceptCurrentIconButton.tsx b/invokeai/frontend/web/src/features/queue/components/DeleteAllExceptCurrentIconButton.tsx new file mode 100644 index 0000000000..6cf1b8f435 --- /dev/null +++ b/invokeai/frontend/web/src/features/queue/components/DeleteAllExceptCurrentIconButton.tsx @@ -0,0 +1,25 @@ +import { IconButton } from '@invoke-ai/ui-library'; +import { useDeleteAllExceptCurrentQueueItemDialog } from 'features/queue/components/DeleteAllExceptCurrentQueueItemConfirmationAlertDialog'; +import { memo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { PiXCircle } from 'react-icons/pi'; + +export const DeleteAllExceptCurrentIconButton = memo(() => { + const { t } = useTranslation(); + const deleteAllExceptCurrent = useDeleteAllExceptCurrentQueueItemDialog(); + + return ( + } + colorScheme="error" + onClick={deleteAllExceptCurrent.openDialog} + /> + ); +}); + +DeleteAllExceptCurrentIconButton.displayName = 'DeleteAllExceptCurrentIconButton'; diff --git a/invokeai/frontend/web/src/features/queue/components/QueueControls.tsx b/invokeai/frontend/web/src/features/queue/components/QueueControls.tsx index 040ecc6de9..52fb354d87 100644 --- a/invokeai/frontend/web/src/features/queue/components/QueueControls.tsx +++ b/invokeai/frontend/web/src/features/queue/components/QueueControls.tsx @@ -1,5 +1,5 @@ import { Flex, Spacer, useShiftModifier } from '@invoke-ai/ui-library'; -import { DeleteAllExceptCurrentQueueItemConfirmationAlertDialog } from 'features/queue/components/DeleteAllExceptCurrentQueueItemConfirmationAlertDialog'; +import { DeleteAllExceptCurrentIconButton } from 'features/queue/components/DeleteAllExceptCurrentIconButton'; import { DeleteCurrentQueueItemIconButton } from 'features/queue/components/DeleteCurrentQueueItemIconButton'; import { QueueActionsMenuButton } from 'features/queue/components/QueueActionsMenuButton'; import ProgressBar from 'features/system/components/ProgressBar'; @@ -30,7 +30,7 @@ export const DeleteIconButton = memo(() => { return ; } - return ; + return ; }); DeleteIconButton.displayName = 'DeleteIconButton'; diff --git a/invokeai/frontend/web/src/features/queue/components/QueueTabQueueControls.tsx b/invokeai/frontend/web/src/features/queue/components/QueueTabQueueControls.tsx index 6432b4e913..303687007e 100644 --- a/invokeai/frontend/web/src/features/queue/components/QueueTabQueueControls.tsx +++ b/invokeai/frontend/web/src/features/queue/components/QueueTabQueueControls.tsx @@ -1,6 +1,5 @@ -/* eslint-disable i18next/no-literal-string */ import { ButtonGroup, Flex } from '@invoke-ai/ui-library'; -import { DeleteAllExceptCurrentQueueItemConfirmationAlertDialog } from 'features/queue/components/DeleteAllExceptCurrentQueueItemConfirmationAlertDialog'; +import { DeleteAllExceptCurrentButton } from 'features/queue/components/DeleteAllExceptCurrentButton'; import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; import { memo } from 'react'; @@ -24,7 +23,7 @@ const QueueTabQueueControls = () => { )} - +