feat(ui): add context menu to entity list

This commit is contained in:
psychedelicious 2024-08-24 19:50:36 +10:00
parent bea98438fc
commit f917cefa84
7 changed files with 118 additions and 85 deletions

View File

@ -11,7 +11,7 @@ import { memo } from 'react';
export const CanvasEntityList = memo(() => {
return (
<ScrollableContent>
<Flex flexDir="column" gap={4} pt={2} data-testid="control-layers-layer-list">
<Flex flexDir="column" gap={4} pt={2} data-testid="control-layers-layer-list" w="full" h="full">
<CanvasEntityOpacity />
<InpaintMaskList />
<RegionalGuidanceEntityList />

View File

@ -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 (
<Menu>
<MenuButton
as={IconButton}
aria-label={t('accessibility.menu')}
icon={<PiDotsThreeOutlineFill />}
variant="link"
data-testid="control-layers-add-layer-menu-button"
alignSelf="stretch"
/>
<MenuList>
<CanvasEntityListMenuItems />
</MenuList>
</Menu>
);
});
CanvasEntityListMenuButton.displayName = 'CanvasEntityListMenuButton';

View File

@ -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 (
<>
<MenuItem icon={<PiPlusBold />} onClick={addInpaintMask}>
{t('controlLayers.inpaintMask', { count: 1 })}
</MenuItem>
<MenuItem icon={<PiPlusBold />} onClick={addRegionalGuidance}>
{t('controlLayers.regionalGuidance', { count: 1 })}
</MenuItem>
<MenuItem icon={<PiPlusBold />} onClick={addRasterLayer}>
{t('controlLayers.rasterLayer', { count: 1 })}
</MenuItem>
<MenuItem icon={<PiPlusBold />} onClick={addControlLayer}>
{t('controlLayers.controlLayer', { count: 1 })}
</MenuItem>
<MenuItem icon={<PiPlusBold />} onClick={addIPAdapter}>
{t('controlLayers.ipAdapter', { count: 1 })}
</MenuItem>
<MenuDivider />
<MenuItem onClick={deleteAll} icon={<PiTrashSimpleBold />} color="error.300" isDisabled={!hasEntities}>
{t('controlLayers.deleteAll', { count: 1 })}
</MenuItem>
</>
);
});
CanvasEntityListMenuItems.displayName = 'CanvasEntityListMenu';

View File

@ -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 (
<Menu>
<MenuButton
as={IconButton}
aria-label={t('accessibility.menu')}
icon={<PiDotsThreeOutlineFill />}
variant="link"
data-testid="control-layers-add-layer-menu-button"
alignSelf="stretch"
/>
<MenuList>
<MenuItem icon={<PiPlusBold />} onClick={addInpaintMask}>
{t('controlLayers.inpaintMask', { count: 1 })}
</MenuItem>
<MenuItem icon={<PiPlusBold />} onClick={addRegionalGuidance}>
{t('controlLayers.regionalGuidance', { count: 1 })}
</MenuItem>
<MenuItem icon={<PiPlusBold />} onClick={addRasterLayer}>
{t('controlLayers.rasterLayer', { count: 1 })}
</MenuItem>
<MenuItem icon={<PiPlusBold />} onClick={addControlLayer}>
{t('controlLayers.controlLayer', { count: 1 })}
</MenuItem>
<MenuItem icon={<PiPlusBold />} onClick={addIPAdapter}>
{t('controlLayers.ipAdapter', { count: 1 })}
</MenuItem>
<MenuDivider />
<MenuItem onClick={deleteAll} icon={<PiTrashSimpleBold />} color="error.300" isDisabled={!hasEntities}>
{t('controlLayers.deleteAll', { count: 1 })}
</MenuItem>
</MenuList>
</Menu>
);
});
CanvasEntityListMenu.displayName = 'CanvasEntityListMenu';

View File

@ -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(
() => (
<MenuList>
<CanvasEntityListMenuItems />
</MenuList>
),
[]
);
return (
<CanvasManagerProviderGate>
{!hasEntities && <CanvasAddEntityButtons />}
{hasEntities && <CanvasEntityList />}
<ContextMenu<HTMLDivElement> renderMenu={renderMenu}>
{(ref) => (
<Box ref={ref} w="full" h="full">
{!hasEntities && <CanvasAddEntityButtons />}
{hasEntities && <CanvasEntityList />}
</Box>
)}
</ContextMenu>
</CanvasManagerProviderGate>
);
});

View File

@ -56,7 +56,7 @@ export const CanvasEntityHeader = memo(({ children, ...rest }: FlexProps) => {
}, [entityIdentifier]);
return (
<ContextMenu renderMenu={renderMenu}>
<ContextMenu renderMenu={renderMenu} stopImmediatePropagation>
{(ref) => (
<Flex ref={ref} gap={2} alignItems="center" p={2} {...rest}>
{children}

View File

@ -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}
</Tab>
<Spacer />
<CanvasEntityListMenu />
<CanvasEntityListMenuButton />
</TabList>
<TabPanels w="full" h="full">
<TabPanel p={0} w="full" h="full">