diff --git a/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapterSettings.tsx b/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapterSettings.tsx index 50f7da129d..0d92c1cf70 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapterSettings.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapterSettings.tsx @@ -5,6 +5,7 @@ import { BeginEndStepPct } from 'features/controlLayers/components/common/BeginE import { CanvasEntitySettingsWrapper } from 'features/controlLayers/components/common/CanvasEntitySettingsWrapper'; import { Weight } from 'features/controlLayers/components/common/Weight'; import { IPAdapterMethod } from 'features/controlLayers/components/IPAdapter/IPAdapterMethod'; +import { IPAdapterSettingsEmptyState } from 'features/controlLayers/components/IPAdapter/IPAdapterSettingsEmptyState'; import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext'; import { usePullBboxIntoGlobalReferenceImage } from 'features/controlLayers/hooks/saveCanvasHooks'; import { useCanvasIsBusy } from 'features/controlLayers/hooks/useCanvasIsBusy'; @@ -17,7 +18,7 @@ import { referenceImageIPAdapterWeightChanged, } from 'features/controlLayers/store/canvasSlice'; import { selectIsFLUX } from 'features/controlLayers/store/paramsSlice'; -import { selectCanvasSlice, selectEntityOrThrow } from 'features/controlLayers/store/selectors'; +import { selectCanvasSlice, selectEntity, selectEntityOrThrow } from 'features/controlLayers/store/selectors'; import type { CanvasEntityIdentifier, CLIPVisionModelV2, IPMethodV2 } from 'features/controlLayers/store/types'; import type { SetGlobalReferenceImageDndTargetData } from 'features/dnd/dnd'; import { setGlobalReferenceImageDndTarget } from 'features/dnd/dnd'; @@ -35,7 +36,7 @@ const buildSelectIPAdapter = (entityIdentifier: CanvasEntityIdentifier<'referenc (canvas) => selectEntityOrThrow(canvas, entityIdentifier, 'IPAdapterSettings').ipAdapter ); -export const IPAdapterSettings = memo(() => { +const IPAdapterSettingsContent = memo(() => { const { t } = useTranslation(); const dispatch = useAppDispatch(); const entityIdentifier = useEntityIdentifierContext('reference_image'); @@ -134,4 +135,25 @@ export const IPAdapterSettings = memo(() => { ); }); +IPAdapterSettingsContent.displayName = 'IPAdapterSettingsContent'; + +const buildSelectIPAdapterHasImage = (entityIdentifier: CanvasEntityIdentifier<'reference_image'>) => + createSelector(selectCanvasSlice, (canvas) => { + const referenceImage = selectEntity(canvas, entityIdentifier); + return !!referenceImage && referenceImage.ipAdapter.image !== null; + }); + +export const IPAdapterSettings = memo(() => { + const entityIdentifier = useEntityIdentifierContext('reference_image'); + + const selectIPAdapterHasImage = useMemo(() => buildSelectIPAdapterHasImage(entityIdentifier), [entityIdentifier]); + const hasImage = useAppSelector(selectIPAdapterHasImage); + + if (!hasImage) { + return ; + } + + return ; +}); + IPAdapterSettings.displayName = 'IPAdapterSettings'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapterSettingsEmptyState.tsx b/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapterSettingsEmptyState.tsx new file mode 100644 index 0000000000..4928cf7cb7 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlLayers/components/IPAdapter/IPAdapterSettingsEmptyState.tsx @@ -0,0 +1,64 @@ +import { Button, Flex, Text } from '@invoke-ai/ui-library'; +import { useAppDispatch } from 'app/store/storeHooks'; +import { useImageUploadButton } from 'common/hooks/useImageUploadButton'; +import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext'; +import { useCanvasIsBusy } from 'features/controlLayers/hooks/useCanvasIsBusy'; +import type { SetGlobalReferenceImageDndTargetData } from 'features/dnd/dnd'; +import { setGlobalReferenceImageDndTarget } from 'features/dnd/dnd'; +import { DndDropTarget } from 'features/dnd/DndDropTarget'; +import { setGlobalReferenceImage } from 'features/imageActions/actions'; +import { activeTabCanvasRightPanelChanged } from 'features/ui/store/uiSlice'; +import { memo, useCallback, useMemo } from 'react'; +import { Trans, useTranslation } from 'react-i18next'; +import type { ImageDTO } from 'services/api/types'; + +export const IPAdapterSettingsEmptyState = memo(() => { + const { t } = useTranslation(); + const entityIdentifier = useEntityIdentifierContext('reference_image'); + const dispatch = useAppDispatch(); + const isBusy = useCanvasIsBusy(); + const onUpload = useCallback( + (imageDTO: ImageDTO) => { + setGlobalReferenceImage({ imageDTO, entityIdentifier, dispatch }); + }, + [dispatch, entityIdentifier] + ); + const uploadApi = useImageUploadButton({ onUpload, allowMultiple: false }); + const onClickGalleryButton = useCallback(() => { + dispatch(activeTabCanvasRightPanelChanged('gallery')); + }, [dispatch]); + + const dndTargetData = useMemo( + () => setGlobalReferenceImageDndTarget.getData({ entityIdentifier }), + [entityIdentifier] + ); + + const components = useMemo( + () => ({ + UploadButton: ( +