diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/ControlAdapterLayerListItem.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/ControlAdapterLayerListItem.tsx index 5f78843aab..ef94b4c75a 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/ControlAdapterLayerListItem.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/ControlAdapterLayerListItem.tsx @@ -4,6 +4,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import ControlAdapterLayerConfig from 'features/regionalPrompts/components/controlAdapterOverrides/ControlAdapterLayerConfig'; import { LayerTitle } from 'features/regionalPrompts/components/LayerTitle'; import { RPLayerDeleteButton } from 'features/regionalPrompts/components/RPLayerDeleteButton'; +import { RPLayerMenu } from 'features/regionalPrompts/components/RPLayerMenu'; import { RPLayerVisibilityToggle } from 'features/regionalPrompts/components/RPLayerVisibilityToggle'; import { isControlAdapterLayer, @@ -51,6 +52,7 @@ export const ControlAdapterLayerListItem = memo(({ layerId }: Props) => { + diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/IPAdapterLayerListItem.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/IPAdapterLayerListItem.tsx index bf4245f26b..f94865c305 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/IPAdapterLayerListItem.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/IPAdapterLayerListItem.tsx @@ -4,6 +4,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import ControlAdapterLayerConfig from 'features/regionalPrompts/components/controlAdapterOverrides/ControlAdapterLayerConfig'; import { LayerTitle } from 'features/regionalPrompts/components/LayerTitle'; import { RPLayerDeleteButton } from 'features/regionalPrompts/components/RPLayerDeleteButton'; +import { RPLayerMenu } from 'features/regionalPrompts/components/RPLayerMenu'; import { RPLayerVisibilityToggle } from 'features/regionalPrompts/components/RPLayerVisibilityToggle'; import { isIPAdapterLayer, @@ -51,6 +52,7 @@ export const IPAdapterLayerListItem = memo(({ layerId }: Props) => { + diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerMenu.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerMenu.tsx index ebfa399227..16619e924e 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerMenu.tsx +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerMenu.tsx @@ -1,78 +1,19 @@ import { IconButton, Menu, MenuButton, MenuDivider, MenuItem, MenuList } from '@invoke-ai/ui-library'; -import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; -import { guidanceLayerIPAdapterAdded } from 'app/store/middleware/listenerMiddleware/listeners/regionalControlToControlAdapterBridge'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { - isMaskedGuidanceLayer, - layerDeleted, - layerMovedBackward, - layerMovedForward, - layerMovedToBack, - layerMovedToFront, - layerReset, - maskLayerNegativePromptChanged, - maskLayerPositivePromptChanged, - selectRegionalPromptsSlice, -} from 'features/regionalPrompts/store/regionalPromptsSlice'; -import { memo, useCallback, useMemo } from 'react'; +import { useAppDispatch } from 'app/store/storeHooks'; +import { RPLayerMenuArrangeActions } from 'features/regionalPrompts/components/RPLayerMenuArrangeActions'; +import { RPLayerMenuMaskedGuidanceActions } from 'features/regionalPrompts/components/RPLayerMenuMaskedGuidanceActions'; +import { useLayerType } from 'features/regionalPrompts/hooks/layerStateHooks'; +import { layerDeleted, layerReset } from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; -import { - PiArrowCounterClockwiseBold, - PiArrowDownBold, - PiArrowLineDownBold, - PiArrowLineUpBold, - PiArrowUpBold, - PiDotsThreeVerticalBold, - PiPlusBold, - PiTrashSimpleBold, -} from 'react-icons/pi'; -import { assert } from 'tsafe'; +import { PiArrowCounterClockwiseBold, PiDotsThreeVerticalBold, PiTrashSimpleBold } from 'react-icons/pi'; type Props = { layerId: string }; export const RPLayerMenu = memo(({ layerId }: Props) => { const dispatch = useAppDispatch(); const { t } = useTranslation(); - const selectValidActions = useMemo( - () => - createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => { - const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); - assert(isMaskedGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`); - const layerIndex = regionalPrompts.present.layers.findIndex((l) => l.id === layerId); - const layerCount = regionalPrompts.present.layers.length; - return { - canAddPositivePrompt: layer.positivePrompt === null, - canAddNegativePrompt: layer.negativePrompt === null, - canMoveForward: layerIndex < layerCount - 1, - canMoveBackward: layerIndex > 0, - canMoveToFront: layerIndex < layerCount - 1, - canMoveToBack: layerIndex > 0, - }; - }), - [layerId] - ); - const validActions = useAppSelector(selectValidActions); - const addPositivePrompt = useCallback(() => { - dispatch(maskLayerPositivePromptChanged({ layerId, prompt: '' })); - }, [dispatch, layerId]); - const addNegativePrompt = useCallback(() => { - dispatch(maskLayerNegativePromptChanged({ layerId, prompt: '' })); - }, [dispatch, layerId]); - const addIPAdapter = useCallback(() => { - dispatch(guidanceLayerIPAdapterAdded(layerId)); - }, [dispatch, layerId]); - const moveForward = useCallback(() => { - dispatch(layerMovedForward(layerId)); - }, [dispatch, layerId]); - const moveToFront = useCallback(() => { - dispatch(layerMovedToFront(layerId)); - }, [dispatch, layerId]); - const moveBackward = useCallback(() => { - dispatch(layerMovedBackward(layerId)); - }, [dispatch, layerId]); - const moveToBack = useCallback(() => { - dispatch(layerMovedToBack(layerId)); - }, [dispatch, layerId]); + const layerType = useLayerType(layerId); const resetLayer = useCallback(() => { dispatch(layerReset(layerId)); }, [dispatch, layerId]); @@ -83,32 +24,23 @@ export const RPLayerMenu = memo(({ layerId }: Props) => { } /> - }> - {t('regionalPrompts.addPositivePrompt')} - - }> - {t('regionalPrompts.addNegativePrompt')} - - }> - {t('regionalPrompts.addIPAdapter')} - - - }> - {t('regionalPrompts.moveToFront')} - - }> - {t('regionalPrompts.moveForward')} - - }> - {t('regionalPrompts.moveBackward')} - - }> - {t('regionalPrompts.moveToBack')} - - - }> - {t('accessibility.reset')} - + {layerType === 'masked_guidance_layer' && ( + <> + + + + )} + {(layerType === 'masked_guidance_layer' || layerType === 'control_adapter_layer') && ( + <> + + + + )} + {layerType === 'masked_guidance_layer' && ( + }> + {t('accessibility.reset')} + + )} } color="error.300"> {t('common.delete')} diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerMenuArrangeActions.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerMenuArrangeActions.tsx new file mode 100644 index 0000000000..e3fe918ff0 --- /dev/null +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerMenuArrangeActions.tsx @@ -0,0 +1,74 @@ +import { MenuItem } from '@invoke-ai/ui-library'; +import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { + isRenderableLayer, + layerMovedBackward, + layerMovedForward, + layerMovedToBack, + layerMovedToFront, + selectRegionalPromptsSlice, +} from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { memo, useCallback, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { + PiArrowDownBold, + PiArrowLineDownBold, + PiArrowLineUpBold, + PiArrowUpBold, +} from 'react-icons/pi'; +import { assert } from 'tsafe'; + +type Props = { layerId: string }; + +export const RPLayerMenuArrangeActions = memo(({ layerId }: Props) => { + const dispatch = useAppDispatch(); + const { t } = useTranslation(); + const selectValidActions = useMemo( + () => + createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => { + const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); + assert(isRenderableLayer(layer), `Layer ${layerId} not found or not an RP layer`); + const layerIndex = regionalPrompts.present.layers.findIndex((l) => l.id === layerId); + const layerCount = regionalPrompts.present.layers.length; + return { + canMoveForward: layerIndex < layerCount - 1, + canMoveBackward: layerIndex > 0, + canMoveToFront: layerIndex < layerCount - 1, + canMoveToBack: layerIndex > 0, + }; + }), + [layerId] + ); + const validActions = useAppSelector(selectValidActions); + const moveForward = useCallback(() => { + dispatch(layerMovedForward(layerId)); + }, [dispatch, layerId]); + const moveToFront = useCallback(() => { + dispatch(layerMovedToFront(layerId)); + }, [dispatch, layerId]); + const moveBackward = useCallback(() => { + dispatch(layerMovedBackward(layerId)); + }, [dispatch, layerId]); + const moveToBack = useCallback(() => { + dispatch(layerMovedToBack(layerId)); + }, [dispatch, layerId]); + return ( + <> + }> + {t('regionalPrompts.moveToFront')} + + }> + {t('regionalPrompts.moveForward')} + + }> + {t('regionalPrompts.moveBackward')} + + }> + {t('regionalPrompts.moveToBack')} + + + ); +}); + +RPLayerMenuArrangeActions.displayName = 'RPLayerMenuArrangeActions'; diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerMenuMaskedGuidanceActions.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerMenuMaskedGuidanceActions.tsx new file mode 100644 index 0000000000..542f08c379 --- /dev/null +++ b/invokeai/frontend/web/src/features/regionalPrompts/components/RPLayerMenuMaskedGuidanceActions.tsx @@ -0,0 +1,58 @@ +import { MenuItem } from '@invoke-ai/ui-library'; +import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; +import { guidanceLayerIPAdapterAdded } from 'app/store/middleware/listenerMiddleware/listeners/regionalControlToControlAdapterBridge'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { + isMaskedGuidanceLayer, + maskLayerNegativePromptChanged, + maskLayerPositivePromptChanged, + selectRegionalPromptsSlice, +} from 'features/regionalPrompts/store/regionalPromptsSlice'; +import { memo, useCallback, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { PiPlusBold } from 'react-icons/pi'; +import { assert } from 'tsafe'; + +type Props = { layerId: string }; + +export const RPLayerMenuMaskedGuidanceActions = memo(({ layerId }: Props) => { + const dispatch = useAppDispatch(); + const { t } = useTranslation(); + const selectValidActions = useMemo( + () => + createMemoizedSelector(selectRegionalPromptsSlice, (regionalPrompts) => { + const layer = regionalPrompts.present.layers.find((l) => l.id === layerId); + assert(isMaskedGuidanceLayer(layer), `Layer ${layerId} not found or not an RP layer`); + return { + canAddPositivePrompt: layer.positivePrompt === null, + canAddNegativePrompt: layer.negativePrompt === null, + }; + }), + [layerId] + ); + const validActions = useAppSelector(selectValidActions); + const addPositivePrompt = useCallback(() => { + dispatch(maskLayerPositivePromptChanged({ layerId, prompt: '' })); + }, [dispatch, layerId]); + const addNegativePrompt = useCallback(() => { + dispatch(maskLayerNegativePromptChanged({ layerId, prompt: '' })); + }, [dispatch, layerId]); + const addIPAdapter = useCallback(() => { + dispatch(guidanceLayerIPAdapterAdded(layerId)); + }, [dispatch, layerId]); + return ( + <> + }> + {t('regionalPrompts.addPositivePrompt')} + + }> + {t('regionalPrompts.addNegativePrompt')} + + }> + {t('regionalPrompts.addIPAdapter')} + + + ); +}); + +RPLayerMenuMaskedGuidanceActions.displayName = 'RPLayerMenuMaskedGuidanceActions';