From f6db73bf1f761de126c38bca116cf172552abc33 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Sat, 24 Aug 2024 19:50:36 +1000 Subject: [PATCH] feat(ui): add context menu to entity list --- .../CanvasEntityList.tsx | 2 +- .../CanvasEntityListMenuButton.tsx | 27 +++++++ .../CanvasEntityListMenuItems.tsx | 67 ++++++++++++++++ .../components/CanvasEntityListMenu.tsx | 77 ------------------- .../components/CanvasPanelContent.tsx | 24 +++++- .../components/common/CanvasEntityHeader.tsx | 2 +- .../ParametersPanelTextToImage.tsx | 4 +- 7 files changed, 118 insertions(+), 85 deletions(-) rename invokeai/frontend/web/src/features/controlLayers/components/{ => CanvasEntityList}/CanvasEntityList.tsx (96%) create mode 100644 invokeai/frontend/web/src/features/controlLayers/components/CanvasEntityList/CanvasEntityListMenuButton.tsx create mode 100644 invokeai/frontend/web/src/features/controlLayers/components/CanvasEntityList/CanvasEntityListMenuItems.tsx delete mode 100644 invokeai/frontend/web/src/features/controlLayers/components/CanvasEntityListMenu.tsx diff --git a/invokeai/frontend/web/src/features/controlLayers/components/CanvasEntityList.tsx b/invokeai/frontend/web/src/features/controlLayers/components/CanvasEntityList/CanvasEntityList.tsx similarity index 96% rename from invokeai/frontend/web/src/features/controlLayers/components/CanvasEntityList.tsx rename to invokeai/frontend/web/src/features/controlLayers/components/CanvasEntityList/CanvasEntityList.tsx index 2b28bdc61e..dac827da5d 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/CanvasEntityList.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/CanvasEntityList/CanvasEntityList.tsx @@ -11,7 +11,7 @@ import { memo } from 'react'; export const CanvasEntityList = memo(() => { return ( - + diff --git a/invokeai/frontend/web/src/features/controlLayers/components/CanvasEntityList/CanvasEntityListMenuButton.tsx b/invokeai/frontend/web/src/features/controlLayers/components/CanvasEntityList/CanvasEntityListMenuButton.tsx new file mode 100644 index 0000000000..cb7231c207 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlLayers/components/CanvasEntityList/CanvasEntityListMenuButton.tsx @@ -0,0 +1,27 @@ +import { IconButton, Menu, MenuButton, MenuList } from '@invoke-ai/ui-library'; +import { CanvasEntityListMenuItems } from 'features/controlLayers/components/CanvasEntityList/CanvasEntityListMenuItems'; +import { memo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { PiDotsThreeOutlineFill } from 'react-icons/pi'; + +export const CanvasEntityListMenuButton = memo(() => { + const { t } = useTranslation(); + + return ( + + } + variant="link" + data-testid="control-layers-add-layer-menu-button" + alignSelf="stretch" + /> + + + + + ); +}); + +CanvasEntityListMenuButton.displayName = 'CanvasEntityListMenuButton'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/CanvasEntityList/CanvasEntityListMenuItems.tsx b/invokeai/frontend/web/src/features/controlLayers/components/CanvasEntityList/CanvasEntityListMenuItems.tsx new file mode 100644 index 0000000000..38be1d477d --- /dev/null +++ b/invokeai/frontend/web/src/features/controlLayers/components/CanvasEntityList/CanvasEntityListMenuItems.tsx @@ -0,0 +1,67 @@ +import { MenuDivider, MenuItem } from '@invoke-ai/ui-library'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { + allEntitiesDeleted, + controlLayerAdded, + inpaintMaskAdded, + ipaAdded, + rasterLayerAdded, + rgAdded, +} from 'features/controlLayers/store/canvasV2Slice'; +import { selectEntityCount } from 'features/controlLayers/store/selectors'; +import { memo, useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; +import { PiPlusBold, PiTrashSimpleBold } from 'react-icons/pi'; + +export const CanvasEntityListMenuItems = memo(() => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const hasEntities = useAppSelector((s) => { + const count = selectEntityCount(s); + return count > 0; + }); + const addInpaintMask = useCallback(() => { + dispatch(inpaintMaskAdded({ isSelected: true })); + }, [dispatch]); + const addRegionalGuidance = useCallback(() => { + dispatch(rgAdded({ isSelected: true })); + }, [dispatch]); + const addRasterLayer = useCallback(() => { + dispatch(rasterLayerAdded({ isSelected: true })); + }, [dispatch]); + const addControlLayer = useCallback(() => { + dispatch(controlLayerAdded({ isSelected: true })); + }, [dispatch]); + const addIPAdapter = useCallback(() => { + dispatch(ipaAdded({ isSelected: true })); + }, [dispatch]); + const deleteAll = useCallback(() => { + dispatch(allEntitiesDeleted()); + }, [dispatch]); + + return ( + <> + } onClick={addInpaintMask}> + {t('controlLayers.inpaintMask', { count: 1 })} + + } onClick={addRegionalGuidance}> + {t('controlLayers.regionalGuidance', { count: 1 })} + + } onClick={addRasterLayer}> + {t('controlLayers.rasterLayer', { count: 1 })} + + } onClick={addControlLayer}> + {t('controlLayers.controlLayer', { count: 1 })} + + } onClick={addIPAdapter}> + {t('controlLayers.ipAdapter', { count: 1 })} + + + } color="error.300" isDisabled={!hasEntities}> + {t('controlLayers.deleteAll', { count: 1 })} + + + ); +}); + +CanvasEntityListMenuItems.displayName = 'CanvasEntityListMenu'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/CanvasEntityListMenu.tsx b/invokeai/frontend/web/src/features/controlLayers/components/CanvasEntityListMenu.tsx deleted file mode 100644 index 93577d353e..0000000000 --- a/invokeai/frontend/web/src/features/controlLayers/components/CanvasEntityListMenu.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import { IconButton, Menu, MenuButton, MenuDivider, MenuItem, MenuList } from '@invoke-ai/ui-library'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { - allEntitiesDeleted, - controlLayerAdded, - inpaintMaskAdded, - ipaAdded, - rasterLayerAdded, - rgAdded, -} from 'features/controlLayers/store/canvasV2Slice'; -import { selectEntityCount } from 'features/controlLayers/store/selectors'; -import { memo, useCallback } from 'react'; -import { useTranslation } from 'react-i18next'; -import { PiDotsThreeOutlineFill, PiPlusBold, PiTrashSimpleBold } from 'react-icons/pi'; - -export const CanvasEntityListMenu = memo(() => { - const { t } = useTranslation(); - const dispatch = useAppDispatch(); - const hasEntities = useAppSelector((s) => { - const count = selectEntityCount(s); - return count > 0; - }); - const addInpaintMask = useCallback(() => { - dispatch(inpaintMaskAdded({ isSelected: true })); - }, [dispatch]); - const addRegionalGuidance = useCallback(() => { - dispatch(rgAdded({ isSelected: true })); - }, [dispatch]); - const addRasterLayer = useCallback(() => { - dispatch(rasterLayerAdded({ isSelected: true })); - }, [dispatch]); - const addControlLayer = useCallback(() => { - dispatch(controlLayerAdded({ isSelected: true })); - }, [dispatch]); - const addIPAdapter = useCallback(() => { - dispatch(ipaAdded({ isSelected: true })); - }, [dispatch]); - const deleteAll = useCallback(() => { - dispatch(allEntitiesDeleted()); - }, [dispatch]); - - return ( - - } - variant="link" - data-testid="control-layers-add-layer-menu-button" - alignSelf="stretch" - /> - - } onClick={addInpaintMask}> - {t('controlLayers.inpaintMask', { count: 1 })} - - } onClick={addRegionalGuidance}> - {t('controlLayers.regionalGuidance', { count: 1 })} - - } onClick={addRasterLayer}> - {t('controlLayers.rasterLayer', { count: 1 })} - - } onClick={addControlLayer}> - {t('controlLayers.controlLayer', { count: 1 })} - - } onClick={addIPAdapter}> - {t('controlLayers.ipAdapter', { count: 1 })} - - - } color="error.300" isDisabled={!hasEntities}> - {t('controlLayers.deleteAll', { count: 1 })} - - - - ); -}); - -CanvasEntityListMenu.displayName = 'CanvasEntityListMenu'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/CanvasPanelContent.tsx b/invokeai/frontend/web/src/features/controlLayers/components/CanvasPanelContent.tsx index 3ecf8f2b6d..406cba7d7d 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/CanvasPanelContent.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/CanvasPanelContent.tsx @@ -1,16 +1,32 @@ +import { Box, ContextMenu, MenuList } from '@invoke-ai/ui-library'; import { useAppSelector } from 'app/store/storeHooks'; import { CanvasAddEntityButtons } from 'features/controlLayers/components/CanvasAddEntityButtons'; -import { CanvasEntityList } from 'features/controlLayers/components/CanvasEntityList'; +import { CanvasEntityList } from 'features/controlLayers/components/CanvasEntityList/CanvasEntityList'; +import { CanvasEntityListMenuItems } from 'features/controlLayers/components/CanvasEntityList/CanvasEntityListMenuItems'; import { CanvasManagerProviderGate } from 'features/controlLayers/contexts/CanvasManagerProviderGate'; import { selectEntityCount } from 'features/controlLayers/store/selectors'; -import { memo } from 'react'; +import { memo, useCallback } from 'react'; export const CanvasPanelContent = memo(() => { const hasEntities = useAppSelector((s) => selectEntityCount(s) > 0); + const renderMenu = useCallback( + () => ( + + + + ), + [] + ); return ( - {!hasEntities && } - {hasEntities && } + renderMenu={renderMenu}> + {(ref) => ( + + {!hasEntities && } + {hasEntities && } + + )} + ); }); diff --git a/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityHeader.tsx b/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityHeader.tsx index 4fe8867fd2..1948830a7b 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityHeader.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityHeader.tsx @@ -56,7 +56,7 @@ export const CanvasEntityHeader = memo(({ children, ...rest }: FlexProps) => { }, [entityIdentifier]); return ( - + {(ref) => ( {children} diff --git a/invokeai/frontend/web/src/features/ui/components/ParametersPanels/ParametersPanelTextToImage.tsx b/invokeai/frontend/web/src/features/ui/components/ParametersPanels/ParametersPanelTextToImage.tsx index 1b39f40227..dad4fdc215 100644 --- a/invokeai/frontend/web/src/features/ui/components/ParametersPanels/ParametersPanelTextToImage.tsx +++ b/invokeai/frontend/web/src/features/ui/components/ParametersPanels/ParametersPanelTextToImage.tsx @@ -3,7 +3,7 @@ import { Box, Flex, Spacer, Tab, TabList, TabPanel, TabPanels, Tabs } from '@inv import { useStore } from '@nanostores/react'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { overlayScrollbarsParams } from 'common/components/OverlayScrollbars/constants'; -import { CanvasEntityListMenu } from 'features/controlLayers/components/CanvasEntityListMenu'; +import { CanvasEntityListMenuButton } from 'features/controlLayers/components/CanvasEntityList/CanvasEntityListMenuButton'; import { CanvasPanelContent } from 'features/controlLayers/components/CanvasPanelContent'; import { $isPreviewVisible } from 'features/controlLayers/store/canvasV2Slice'; import { selectEntityCount } from 'features/controlLayers/store/selectors'; @@ -102,7 +102,7 @@ const ParametersPanelTextToImage = () => { {controlLayersTitle} - +