mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): add context menu to entity list
This commit is contained in:
parent
bea98438fc
commit
f917cefa84
@ -11,7 +11,7 @@ import { memo } from 'react';
|
|||||||
export const CanvasEntityList = memo(() => {
|
export const CanvasEntityList = memo(() => {
|
||||||
return (
|
return (
|
||||||
<ScrollableContent>
|
<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 />
|
<CanvasEntityOpacity />
|
||||||
<InpaintMaskList />
|
<InpaintMaskList />
|
||||||
<RegionalGuidanceEntityList />
|
<RegionalGuidanceEntityList />
|
@ -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';
|
@ -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';
|
@ -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';
|
|
@ -1,16 +1,32 @@
|
|||||||
|
import { Box, ContextMenu, MenuList } from '@invoke-ai/ui-library';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { CanvasAddEntityButtons } from 'features/controlLayers/components/CanvasAddEntityButtons';
|
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 { CanvasManagerProviderGate } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
|
||||||
import { selectEntityCount } from 'features/controlLayers/store/selectors';
|
import { selectEntityCount } from 'features/controlLayers/store/selectors';
|
||||||
import { memo } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
|
|
||||||
export const CanvasPanelContent = memo(() => {
|
export const CanvasPanelContent = memo(() => {
|
||||||
const hasEntities = useAppSelector((s) => selectEntityCount(s) > 0);
|
const hasEntities = useAppSelector((s) => selectEntityCount(s) > 0);
|
||||||
|
const renderMenu = useCallback(
|
||||||
|
() => (
|
||||||
|
<MenuList>
|
||||||
|
<CanvasEntityListMenuItems />
|
||||||
|
</MenuList>
|
||||||
|
),
|
||||||
|
[]
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<CanvasManagerProviderGate>
|
<CanvasManagerProviderGate>
|
||||||
|
<ContextMenu<HTMLDivElement> renderMenu={renderMenu}>
|
||||||
|
{(ref) => (
|
||||||
|
<Box ref={ref} w="full" h="full">
|
||||||
{!hasEntities && <CanvasAddEntityButtons />}
|
{!hasEntities && <CanvasAddEntityButtons />}
|
||||||
{hasEntities && <CanvasEntityList />}
|
{hasEntities && <CanvasEntityList />}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</ContextMenu>
|
||||||
</CanvasManagerProviderGate>
|
</CanvasManagerProviderGate>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -56,7 +56,7 @@ export const CanvasEntityHeader = memo(({ children, ...rest }: FlexProps) => {
|
|||||||
}, [entityIdentifier]);
|
}, [entityIdentifier]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ContextMenu renderMenu={renderMenu}>
|
<ContextMenu renderMenu={renderMenu} stopImmediatePropagation>
|
||||||
{(ref) => (
|
{(ref) => (
|
||||||
<Flex ref={ref} gap={2} alignItems="center" p={2} {...rest}>
|
<Flex ref={ref} gap={2} alignItems="center" p={2} {...rest}>
|
||||||
{children}
|
{children}
|
||||||
|
@ -3,7 +3,7 @@ import { Box, Flex, Spacer, Tab, TabList, TabPanel, TabPanels, Tabs } from '@inv
|
|||||||
import { useStore } from '@nanostores/react';
|
import { useStore } from '@nanostores/react';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { overlayScrollbarsParams } from 'common/components/OverlayScrollbars/constants';
|
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 { CanvasPanelContent } from 'features/controlLayers/components/CanvasPanelContent';
|
||||||
import { $isPreviewVisible } from 'features/controlLayers/store/canvasV2Slice';
|
import { $isPreviewVisible } from 'features/controlLayers/store/canvasV2Slice';
|
||||||
import { selectEntityCount } from 'features/controlLayers/store/selectors';
|
import { selectEntityCount } from 'features/controlLayers/store/selectors';
|
||||||
@ -102,7 +102,7 @@ const ParametersPanelTextToImage = () => {
|
|||||||
{controlLayersTitle}
|
{controlLayersTitle}
|
||||||
</Tab>
|
</Tab>
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<CanvasEntityListMenu />
|
<CanvasEntityListMenuButton />
|
||||||
</TabList>
|
</TabList>
|
||||||
<TabPanels w="full" h="full">
|
<TabPanels w="full" h="full">
|
||||||
<TabPanel p={0} w="full" h="full">
|
<TabPanel p={0} w="full" h="full">
|
||||||
|
Loading…
x
Reference in New Issue
Block a user