mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): revise entity menus
This commit is contained in:
parent
1435557d1d
commit
9f2c815e13
@ -1646,7 +1646,7 @@
|
||||
"storeNotInitialized": "Store is not initialized"
|
||||
},
|
||||
"controlLayers": {
|
||||
"deleteAll": "Delete All",
|
||||
"resetAll": "Reset All",
|
||||
"addLayer": "Add Layer",
|
||||
"moveToFront": "Move to Front",
|
||||
"moveToBack": "Move to Back",
|
||||
@ -1694,7 +1694,10 @@
|
||||
"layers_other": "Layers",
|
||||
"objects_zero": "empty",
|
||||
"objects_one": "{{count}} object",
|
||||
"objects_other": "{{count}} objects"
|
||||
"objects_other": "{{count}} objects",
|
||||
"filter": "Filter",
|
||||
"convertToControlLayer": "Convert to Control Layer",
|
||||
"convertToRasterLayer": "Convert to Raster Layer"
|
||||
},
|
||||
"upscaling": {
|
||||
"upscale": "Upscale",
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Button, Menu, MenuButton, MenuItem, MenuList } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { useAddCALayer, useAddIPALayer } from 'features/controlLayers/hooks/addLayerHooks';
|
||||
import { rasterLayerAdded, rgAdded } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { useDefaultControlAdapter, useDefaultIPAdapter } from 'features/controlLayers/hooks/useLayerControlAdapter';
|
||||
import { controlLayerAdded, ipaAdded, rasterLayerAdded, rgAdded } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiPlusBold } from 'react-icons/pi';
|
||||
@ -9,14 +9,20 @@ import { PiPlusBold } from 'react-icons/pi';
|
||||
export const AddLayerButton = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const [addCALayer, isAddCALayerDisabled] = useAddCALayer();
|
||||
const [addIPALayer, isAddIPALayerDisabled] = useAddIPALayer();
|
||||
const defaultControlAdapter = useDefaultControlAdapter();
|
||||
const defaultIPAdapter = useDefaultIPAdapter();
|
||||
const addRGLayer = useCallback(() => {
|
||||
dispatch(rgAdded());
|
||||
}, [dispatch]);
|
||||
const addRasterLayer = useCallback(() => {
|
||||
dispatch(rasterLayerAdded({ isSelected: true }));
|
||||
}, [dispatch]);
|
||||
const addControlLayer = useCallback(() => {
|
||||
dispatch(controlLayerAdded({ isSelected: true, overrides: { controlAdapter: defaultControlAdapter } }));
|
||||
}, [defaultControlAdapter, dispatch]);
|
||||
const addIPAdapter = useCallback(() => {
|
||||
dispatch(ipaAdded({ config: defaultIPAdapter }));
|
||||
}, [defaultIPAdapter, dispatch]);
|
||||
|
||||
return (
|
||||
<Menu>
|
||||
@ -29,18 +35,10 @@ export const AddLayerButton = memo(() => {
|
||||
{t('controlLayers.addLayer')}
|
||||
</MenuButton>
|
||||
<MenuList>
|
||||
<MenuItem icon={<PiPlusBold />} onClick={addRGLayer}>
|
||||
{t('controlLayers.regionalGuidanceLayer')}
|
||||
</MenuItem>
|
||||
<MenuItem icon={<PiPlusBold />} onClick={addRasterLayer}>
|
||||
{t('controlLayers.rasterLayer')}
|
||||
</MenuItem>
|
||||
<MenuItem icon={<PiPlusBold />} onClick={addCALayer} isDisabled={isAddCALayerDisabled}>
|
||||
{t('controlLayers.globalControlAdapterLayer')}
|
||||
</MenuItem>
|
||||
<MenuItem icon={<PiPlusBold />} onClick={addIPALayer} isDisabled={isAddIPALayerDisabled}>
|
||||
{t('controlLayers.globalIPAdapterLayer')}
|
||||
</MenuItem>
|
||||
<MenuItem onClick={addRGLayer}>{t('controlLayers.regionalGuidanceLayer')}</MenuItem>
|
||||
<MenuItem onClick={addRasterLayer}>{t('controlLayers.rasterLayer')}</MenuItem>
|
||||
<MenuItem onClick={addControlLayer}>{t('controlLayers.controlLayer')}</MenuItem>
|
||||
<MenuItem onClick={addIPAdapter}>{t('controlLayers.globalIPAdapterLayer')}</MenuItem>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
);
|
||||
|
@ -1,8 +1,10 @@
|
||||
import { Button, Flex } from '@invoke-ai/ui-library';
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { useAddIPAdapterToRGLayer } from 'features/controlLayers/hooks/addLayerHooks';
|
||||
import { useDefaultIPAdapter } from 'features/controlLayers/hooks/useLayerControlAdapter';
|
||||
import { nanoid } from 'features/controlLayers/konva/util';
|
||||
import {
|
||||
rgIPAdapterAdded,
|
||||
rgNegativePromptChanged,
|
||||
rgPositivePromptChanged,
|
||||
selectCanvasV2Slice,
|
||||
@ -18,7 +20,7 @@ type AddPromptButtonProps = {
|
||||
export const AddPromptButtons = ({ id }: AddPromptButtonProps) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const [addIPAdapter, isAddIPAdapterDisabled] = useAddIPAdapterToRGLayer(id);
|
||||
const defaultIPAdapter = useDefaultIPAdapter();
|
||||
const selectValidActions = useMemo(
|
||||
() =>
|
||||
createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
|
||||
@ -37,6 +39,11 @@ export const AddPromptButtons = ({ id }: AddPromptButtonProps) => {
|
||||
const addNegativePrompt = useCallback(() => {
|
||||
dispatch(rgNegativePromptChanged({ id, prompt: '' }));
|
||||
}, [dispatch, id]);
|
||||
const addIPAdapter = useCallback(() => {
|
||||
dispatch(
|
||||
rgIPAdapterAdded({ id, ipAdapter: { ...defaultIPAdapter, id: nanoid(), type: 'ip_adapter', isEnabled: true } })
|
||||
);
|
||||
}, [defaultIPAdapter, dispatch, id]);
|
||||
|
||||
return (
|
||||
<Flex w="full" p={2} justifyContent="space-between">
|
||||
@ -58,13 +65,7 @@ export const AddPromptButtons = ({ id }: AddPromptButtonProps) => {
|
||||
>
|
||||
{t('common.negativePrompt')}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
leftIcon={<PiPlusBold />}
|
||||
onClick={addIPAdapter}
|
||||
isDisabled={isAddIPAdapterDisabled}
|
||||
>
|
||||
<Button size="sm" variant="ghost" leftIcon={<PiPlusBold />} onClick={addIPAdapter}>
|
||||
{t('common.ipAdapter')}
|
||||
</Button>
|
||||
</Flex>
|
||||
|
@ -5,7 +5,6 @@ import { CanvasEntityEnabledToggle } from 'features/controlLayers/components/com
|
||||
import { CanvasEntityHeader } from 'features/controlLayers/components/common/CanvasEntityHeader';
|
||||
import { CanvasEntitySettingsWrapper } from 'features/controlLayers/components/common/CanvasEntitySettingsWrapper';
|
||||
import { CanvasEntityTitle } from 'features/controlLayers/components/common/CanvasEntityTitle';
|
||||
import { ControlLayerActionsMenu } from 'features/controlLayers/components/ControlLayer/ControlLayerActionsMenu';
|
||||
import { ControlLayerControlAdapter } from 'features/controlLayers/components/ControlLayer/ControlLayerControlAdapter';
|
||||
import { EntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
|
||||
@ -25,7 +24,6 @@ export const ControlLayer = memo(({ id }: Props) => {
|
||||
<CanvasEntityEnabledToggle />
|
||||
<CanvasEntityTitle />
|
||||
<Spacer />
|
||||
<ControlLayerActionsMenu />
|
||||
<CanvasEntityDeleteButton />
|
||||
</CanvasEntityHeader>
|
||||
<CanvasEntitySettingsWrapper>
|
||||
|
@ -1,17 +0,0 @@
|
||||
import { Menu, MenuList } from '@invoke-ai/ui-library';
|
||||
import { CanvasEntityActionMenuItems } from 'features/controlLayers/components/common/CanvasEntityActionMenuItems';
|
||||
import { CanvasEntityMenuButton } from 'features/controlLayers/components/common/CanvasEntityMenuButton';
|
||||
import { memo } from 'react';
|
||||
|
||||
export const ControlLayerActionsMenu = memo(() => {
|
||||
return (
|
||||
<Menu>
|
||||
<CanvasEntityMenuButton />
|
||||
<MenuList>
|
||||
<CanvasEntityActionMenuItems />
|
||||
</MenuList>
|
||||
</Menu>
|
||||
);
|
||||
});
|
||||
|
||||
ControlLayerActionsMenu.displayName = 'ControlLayerActionsMenu';
|
@ -2,8 +2,8 @@ import { Flex } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { BeginEndStepPct } from 'features/controlLayers/components/common/BeginEndStepPct';
|
||||
import { Weight } from 'features/controlLayers/components/common/Weight';
|
||||
import { ControlAdapterControlModeSelect } from 'features/controlLayers/components/ControlAdapter/ControlAdapterControlModeSelect';
|
||||
import { ControlAdapterModel } from 'features/controlLayers/components/ControlAdapter/ControlAdapterModel';
|
||||
import { ControlLayerControlAdapterControlMode } from 'features/controlLayers/components/ControlLayer/ControlLayerControlAdapterControlMode';
|
||||
import { ControlLayerControlAdapterModel } from 'features/controlLayers/components/ControlLayer/ControlLayerControlAdapterModel';
|
||||
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||
import { useControlLayerControlAdapter } from 'features/controlLayers/hooks/useLayerControlAdapter';
|
||||
import {
|
||||
@ -51,11 +51,11 @@ export const ControlLayerControlAdapter = memo(() => {
|
||||
|
||||
return (
|
||||
<Flex flexDir="column" gap={3} position="relative" w="full">
|
||||
<ControlAdapterModel modelKey={controlAdapter.model?.key ?? null} onChange={onChangeModel} />
|
||||
<ControlLayerControlAdapterModel modelKey={controlAdapter.model?.key ?? null} onChange={onChangeModel} />
|
||||
<Weight weight={controlAdapter.weight} onChange={onChangeWeight} />
|
||||
<BeginEndStepPct beginEndStepPct={controlAdapter.beginEndStepPct} onChange={onChangeBeginEndStepPct} />
|
||||
{controlAdapter.type === 'controlnet' && (
|
||||
<ControlAdapterControlModeSelect controlMode={controlAdapter.controlMode} onChange={onChangeControlMode} />
|
||||
<ControlLayerControlAdapterControlMode controlMode={controlAdapter.controlMode} onChange={onChangeControlMode} />
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
|
@ -12,7 +12,7 @@ type Props = {
|
||||
onChange: (controlMode: ControlModeV2) => void;
|
||||
};
|
||||
|
||||
export const ControlAdapterControlModeSelect = memo(({ controlMode, onChange }: Props) => {
|
||||
export const ControlLayerControlAdapterControlMode = memo(({ controlMode, onChange }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const CONTROL_MODE_DATA = useMemo(
|
||||
() => [
|
||||
@ -57,4 +57,4 @@ export const ControlAdapterControlModeSelect = memo(({ controlMode, onChange }:
|
||||
);
|
||||
});
|
||||
|
||||
ControlAdapterControlModeSelect.displayName = 'ControlAdapterControlModeSelect';
|
||||
ControlLayerControlAdapterControlMode.displayName = 'ControlLayerControlAdapterControlMode';
|
@ -11,7 +11,7 @@ type Props = {
|
||||
onChange: (modelConfig: ControlNetModelConfig | T2IAdapterModelConfig) => void;
|
||||
};
|
||||
|
||||
export const ControlAdapterModel = memo(({ modelKey, onChange: onChangeModel }: Props) => {
|
||||
export const ControlLayerControlAdapterModel = memo(({ modelKey, onChange: onChangeModel }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const currentBaseModel = useAppSelector((s) => s.canvasV2.params.model?.base);
|
||||
const [modelConfigs, { isLoading }] = useControlNetAndT2IAdapterModels();
|
||||
@ -60,4 +60,4 @@ export const ControlAdapterModel = memo(({ modelKey, onChange: onChangeModel }:
|
||||
);
|
||||
});
|
||||
|
||||
ControlAdapterModel.displayName = 'ControlAdapterModel';
|
||||
ControlLayerControlAdapterModel.displayName = 'ControlLayerControlAdapterModel';
|
@ -0,0 +1,23 @@
|
||||
import { MenuDivider } from '@invoke-ai/ui-library';
|
||||
import { CanvasEntityMenuItemsArrange } from 'features/controlLayers/components/common/CanvasEntityMenuItemsArrange';
|
||||
import { CanvasEntityMenuItemsDelete } from 'features/controlLayers/components/common/CanvasEntityMenuItemsDelete';
|
||||
import { CanvasEntityMenuItemsFilter } from 'features/controlLayers/components/common/CanvasEntityMenuItemsFilter';
|
||||
import { CanvasEntityMenuItemsReset } from 'features/controlLayers/components/common/CanvasEntityMenuItemsReset';
|
||||
import { ControlLayerMenuItemsControlToRaster } from 'features/controlLayers/components/ControlLayer/ControlLayerMenuItemsControlToRaster';
|
||||
import { memo } from 'react';
|
||||
|
||||
export const ControlLayerMenuItems = memo(() => {
|
||||
return (
|
||||
<>
|
||||
<CanvasEntityMenuItemsFilter />
|
||||
<ControlLayerMenuItemsControlToRaster />
|
||||
<MenuDivider />
|
||||
<CanvasEntityMenuItemsArrange />
|
||||
<MenuDivider />
|
||||
<CanvasEntityMenuItemsReset />
|
||||
<CanvasEntityMenuItemsDelete />
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
ControlLayerMenuItems.displayName = 'ControlLayerMenuItems';
|
@ -0,0 +1,25 @@
|
||||
import { MenuItem } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||
import { controlLayerConvertedToRasterLayer } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiLightningBold } from 'react-icons/pi';
|
||||
|
||||
export const ControlLayerMenuItemsControlToRaster = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const entityIdentifier = useEntityIdentifierContext();
|
||||
|
||||
const convertControlLayerToRasterLayer = useCallback(() => {
|
||||
dispatch(controlLayerConvertedToRasterLayer({ id: entityIdentifier.id }));
|
||||
}, [dispatch, entityIdentifier.id]);
|
||||
|
||||
return (
|
||||
<MenuItem onClick={convertControlLayerToRasterLayer} icon={<PiLightningBold />}>
|
||||
{t('controlLayers.convertToRasterLayer')}
|
||||
</MenuItem>
|
||||
);
|
||||
});
|
||||
|
||||
ControlLayerMenuItemsControlToRaster.displayName = 'ControlLayerMenuItemsControlToRaster';
|
@ -3,8 +3,8 @@ import { Flex } from '@invoke-ai/ui-library';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { AddLayerButton } from 'features/controlLayers/components/AddLayerButton';
|
||||
import { CanvasEntityList } from 'features/controlLayers/components/CanvasEntityList';
|
||||
import { DeleteAllLayersButton } from 'features/controlLayers/components/DeleteAllLayersButton';
|
||||
import { Filter } from 'features/controlLayers/components/Filters/Filter';
|
||||
import { ResetAllEntitiesButton } from 'features/controlLayers/components/ResetAllEntitiesButton';
|
||||
import { $filteringEntity } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import ResizeHandle from 'features/ui/components/tabs/ResizeHandle';
|
||||
import { memo } from 'react';
|
||||
@ -18,7 +18,7 @@ export const ControlLayersPanelContent = memo(() => {
|
||||
<Flex flexDir="column" gap={2} w="full" h="full">
|
||||
<Flex justifyContent="space-around">
|
||||
<AddLayerButton />
|
||||
<DeleteAllLayersButton />
|
||||
<ResetAllEntitiesButton />
|
||||
</Flex>
|
||||
<CanvasEntityList />
|
||||
</Flex>
|
||||
|
@ -5,7 +5,6 @@ import { CanvasEntityEnabledToggle } from 'features/controlLayers/components/com
|
||||
import { CanvasEntityGroupTitle } from 'features/controlLayers/components/common/CanvasEntityGroupTitle';
|
||||
import { CanvasEntityHeader } from 'features/controlLayers/components/common/CanvasEntityHeader';
|
||||
import { CanvasEntityTitle } from 'features/controlLayers/components/common/CanvasEntityTitle';
|
||||
import { InpaintMaskActionsMenu } from 'features/controlLayers/components/InpaintMask/InpaintMaskActionsMenu';
|
||||
import { EntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
|
||||
import { memo, useMemo } from 'react';
|
||||
@ -28,7 +27,6 @@ export const InpaintMask = memo(() => {
|
||||
<CanvasEntityTitle />
|
||||
<Spacer />
|
||||
<InpaintMaskMaskFillColorPicker />
|
||||
<InpaintMaskActionsMenu />
|
||||
</CanvasEntityHeader>
|
||||
</CanvasEntityContainer>
|
||||
</EntityIdentifierContext.Provider>
|
||||
|
@ -1,17 +0,0 @@
|
||||
import { Menu, MenuList } from '@invoke-ai/ui-library';
|
||||
import { CanvasEntityActionMenuItems } from 'features/controlLayers/components/common/CanvasEntityActionMenuItems';
|
||||
import { CanvasEntityMenuButton } from 'features/controlLayers/components/common/CanvasEntityMenuButton';
|
||||
import { memo } from 'react';
|
||||
|
||||
export const InpaintMaskActionsMenu = memo(() => {
|
||||
return (
|
||||
<Menu>
|
||||
<CanvasEntityMenuButton />
|
||||
<MenuList>
|
||||
<CanvasEntityActionMenuItems />
|
||||
</MenuList>
|
||||
</Menu>
|
||||
);
|
||||
});
|
||||
|
||||
InpaintMaskActionsMenu.displayName = 'InpaintMaskActionsMenu';
|
@ -0,0 +1,12 @@
|
||||
import { CanvasEntityMenuItemsReset } from 'features/controlLayers/components/common/CanvasEntityMenuItemsReset';
|
||||
import { memo } from 'react';
|
||||
|
||||
export const InpaintMaskMenuItems = memo(() => {
|
||||
return (
|
||||
<>
|
||||
<CanvasEntityMenuItemsReset />
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
InpaintMaskMenuItems.displayName = 'InpaintMaskMenuItems';
|
@ -4,7 +4,6 @@ import { CanvasEntityDeleteButton } from 'features/controlLayers/components/comm
|
||||
import { CanvasEntityEnabledToggle } from 'features/controlLayers/components/common/CanvasEntityEnabledToggle';
|
||||
import { CanvasEntityHeader } from 'features/controlLayers/components/common/CanvasEntityHeader';
|
||||
import { CanvasEntityTitle } from 'features/controlLayers/components/common/CanvasEntityTitle';
|
||||
import { RasterLayerActionsMenu } from 'features/controlLayers/components/RasterLayer/RasterLayerActionsMenu';
|
||||
import { EntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
|
||||
import { memo, useMemo } from 'react';
|
||||
@ -23,7 +22,6 @@ export const RasterLayer = memo(({ id }: Props) => {
|
||||
<CanvasEntityEnabledToggle />
|
||||
<CanvasEntityTitle />
|
||||
<Spacer />
|
||||
<RasterLayerActionsMenu />
|
||||
<CanvasEntityDeleteButton />
|
||||
</CanvasEntityHeader>
|
||||
</CanvasEntityContainer>
|
||||
|
@ -1,17 +0,0 @@
|
||||
import { Menu, MenuList } from '@invoke-ai/ui-library';
|
||||
import { CanvasEntityActionMenuItems } from 'features/controlLayers/components/common/CanvasEntityActionMenuItems';
|
||||
import { CanvasEntityMenuButton } from 'features/controlLayers/components/common/CanvasEntityMenuButton';
|
||||
import { memo } from 'react';
|
||||
|
||||
export const RasterLayerActionsMenu = memo(() => {
|
||||
return (
|
||||
<Menu>
|
||||
<CanvasEntityMenuButton />
|
||||
<MenuList>
|
||||
<CanvasEntityActionMenuItems />
|
||||
</MenuList>
|
||||
</Menu>
|
||||
);
|
||||
});
|
||||
|
||||
RasterLayerActionsMenu.displayName = 'RasterLayerActionsMenu';
|
@ -0,0 +1,23 @@
|
||||
import { MenuDivider } from '@invoke-ai/ui-library';
|
||||
import { CanvasEntityMenuItemsArrange } from 'features/controlLayers/components/common/CanvasEntityMenuItemsArrange';
|
||||
import { CanvasEntityMenuItemsDelete } from 'features/controlLayers/components/common/CanvasEntityMenuItemsDelete';
|
||||
import { CanvasEntityMenuItemsFilter } from 'features/controlLayers/components/common/CanvasEntityMenuItemsFilter';
|
||||
import { CanvasEntityMenuItemsReset } from 'features/controlLayers/components/common/CanvasEntityMenuItemsReset';
|
||||
import { RasterLayerMenuItemsRasterToControl } from 'features/controlLayers/components/RasterLayer/RasterLayerMenuItemsRasterToControl';
|
||||
import { memo } from 'react';
|
||||
|
||||
export const RasterLayerMenuItems = memo(() => {
|
||||
return (
|
||||
<>
|
||||
<CanvasEntityMenuItemsFilter />
|
||||
<RasterLayerMenuItemsRasterToControl />
|
||||
<MenuDivider />
|
||||
<CanvasEntityMenuItemsArrange />
|
||||
<MenuDivider />
|
||||
<CanvasEntityMenuItemsReset />
|
||||
<CanvasEntityMenuItemsDelete />
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
RasterLayerMenuItems.displayName = 'RasterLayerMenuItems';
|
@ -0,0 +1,28 @@
|
||||
import { MenuItem } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||
import { useDefaultControlAdapter } from 'features/controlLayers/hooks/useLayerControlAdapter';
|
||||
import { rasterLayerConvertedToControlLayer } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiLightningBold } from 'react-icons/pi';
|
||||
|
||||
export const RasterLayerMenuItemsRasterToControl = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const entityIdentifier = useEntityIdentifierContext();
|
||||
|
||||
const defaultControlAdapter = useDefaultControlAdapter();
|
||||
|
||||
const convertRasterLayerToControlLayer = useCallback(() => {
|
||||
dispatch(rasterLayerConvertedToControlLayer({ id: entityIdentifier.id, controlAdapter: defaultControlAdapter }));
|
||||
}, [dispatch, defaultControlAdapter, entityIdentifier.id]);
|
||||
|
||||
return (
|
||||
<MenuItem onClick={convertRasterLayerToControlLayer} icon={<PiLightningBold />}>
|
||||
{t('controlLayers.convertToControlLayer')}
|
||||
</MenuItem>
|
||||
);
|
||||
});
|
||||
|
||||
RasterLayerMenuItemsRasterToControl.displayName = 'RasterLayerMenuItemsRasterToControl';
|
@ -4,7 +4,6 @@ import { CanvasEntityDeleteButton } from 'features/controlLayers/components/comm
|
||||
import { CanvasEntityEnabledToggle } from 'features/controlLayers/components/common/CanvasEntityEnabledToggle';
|
||||
import { CanvasEntityHeader } from 'features/controlLayers/components/common/CanvasEntityHeader';
|
||||
import { CanvasEntityTitle } from 'features/controlLayers/components/common/CanvasEntityTitle';
|
||||
import { RegionalGuidanceActionsMenu } from 'features/controlLayers/components/RegionalGuidance/RegionalGuidanceActionsMenu';
|
||||
import { RegionalGuidanceBadges } from 'features/controlLayers/components/RegionalGuidance/RegionalGuidanceBadges';
|
||||
import { RegionalGuidanceSettings } from 'features/controlLayers/components/RegionalGuidance/RegionalGuidanceSettings';
|
||||
import { EntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||
@ -30,7 +29,6 @@ export const RegionalGuidance = memo(({ id }: Props) => {
|
||||
<RegionalGuidanceBadges />
|
||||
<RegionalGuidanceMaskFillColorPicker />
|
||||
<RegionalGuidanceSettingsPopover />
|
||||
<RegionalGuidanceActionsMenu />
|
||||
<CanvasEntityDeleteButton />
|
||||
</CanvasEntityHeader>
|
||||
<RegionalGuidanceSettings />
|
||||
|
@ -1,62 +0,0 @@
|
||||
import { Menu, MenuDivider, MenuItem, MenuList } from '@invoke-ai/ui-library';
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { CanvasEntityActionMenuItems } from 'features/controlLayers/components/common/CanvasEntityActionMenuItems';
|
||||
import { CanvasEntityMenuButton } from 'features/controlLayers/components/common/CanvasEntityMenuButton';
|
||||
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||
import { useAddIPAdapterToRGLayer } from 'features/controlLayers/hooks/addLayerHooks';
|
||||
import {
|
||||
rgNegativePromptChanged,
|
||||
rgPositivePromptChanged,
|
||||
selectCanvasV2Slice,
|
||||
} from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { selectRGOrThrow } from 'features/controlLayers/store/regionsReducers';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiPlusBold } from 'react-icons/pi';
|
||||
|
||||
export const RegionalGuidanceActionsMenu = memo(() => {
|
||||
const { id } = useEntityIdentifierContext();
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const [onAddIPAdapter, isAddIPAdapterDisabled] = useAddIPAdapterToRGLayer(id);
|
||||
const selectActionsValidity = useMemo(
|
||||
() =>
|
||||
createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
|
||||
const rg = selectRGOrThrow(canvasV2, id);
|
||||
return {
|
||||
isAddPositivePromptDisabled: rg.positivePrompt === null,
|
||||
isAddNegativePromptDisabled: rg.negativePrompt === null,
|
||||
};
|
||||
}),
|
||||
[id]
|
||||
);
|
||||
const actions = useAppSelector(selectActionsValidity);
|
||||
const onAddPositivePrompt = useCallback(() => {
|
||||
dispatch(rgPositivePromptChanged({ id: id, prompt: '' }));
|
||||
}, [dispatch, id]);
|
||||
const onAddNegativePrompt = useCallback(() => {
|
||||
dispatch(rgNegativePromptChanged({ id: id, prompt: '' }));
|
||||
}, [dispatch, id]);
|
||||
|
||||
return (
|
||||
<Menu>
|
||||
<CanvasEntityMenuButton />
|
||||
<MenuList>
|
||||
<MenuItem onClick={onAddPositivePrompt} isDisabled={actions.isAddPositivePromptDisabled} icon={<PiPlusBold />}>
|
||||
{t('controlLayers.addPositivePrompt')}
|
||||
</MenuItem>
|
||||
<MenuItem onClick={onAddNegativePrompt} isDisabled={actions.isAddNegativePromptDisabled} icon={<PiPlusBold />}>
|
||||
{t('controlLayers.addNegativePrompt')}
|
||||
</MenuItem>
|
||||
<MenuItem onClick={onAddIPAdapter} icon={<PiPlusBold />} isDisabled={isAddIPAdapterDisabled}>
|
||||
{t('controlLayers.addIPAdapter')}
|
||||
</MenuItem>
|
||||
<MenuDivider />
|
||||
<CanvasEntityActionMenuItems />
|
||||
</MenuList>
|
||||
</Menu>
|
||||
);
|
||||
});
|
||||
|
||||
RegionalGuidanceActionsMenu.displayName = 'RegionalGuidanceActionsMenu';
|
@ -0,0 +1,21 @@
|
||||
import { MenuDivider } from '@invoke-ai/ui-library';
|
||||
import { CanvasEntityMenuItemsArrange } from 'features/controlLayers/components/common/CanvasEntityMenuItemsArrange';
|
||||
import { CanvasEntityMenuItemsDelete } from 'features/controlLayers/components/common/CanvasEntityMenuItemsDelete';
|
||||
import { CanvasEntityMenuItemsReset } from 'features/controlLayers/components/common/CanvasEntityMenuItemsReset';
|
||||
import { RegionalGuidanceMenuItemsAddPromptsAndIPAdapter } from 'features/controlLayers/components/RegionalGuidance/RegionalGuidanceMenuItemsAddPromptsAndIPAdapter';
|
||||
import { memo } from 'react';
|
||||
|
||||
export const RegionalGuidanceMenuItems = memo(() => {
|
||||
return (
|
||||
<>
|
||||
<RegionalGuidanceMenuItemsAddPromptsAndIPAdapter />
|
||||
<MenuDivider />
|
||||
<CanvasEntityMenuItemsArrange />
|
||||
<MenuDivider />
|
||||
<CanvasEntityMenuItemsReset />
|
||||
<CanvasEntityMenuItemsDelete />
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
RegionalGuidanceMenuItems.displayName = 'RegionalGuidanceMenuItems';
|
@ -0,0 +1,58 @@
|
||||
import { MenuItem } from '@invoke-ai/ui-library';
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||
import { useDefaultIPAdapter } from 'features/controlLayers/hooks/useLayerControlAdapter';
|
||||
import { nanoid } from 'features/controlLayers/konva/util';
|
||||
import {
|
||||
rgIPAdapterAdded,
|
||||
rgNegativePromptChanged,
|
||||
rgPositivePromptChanged,
|
||||
selectCanvasV2Slice,
|
||||
} from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const RegionalGuidanceMenuItemsAddPromptsAndIPAdapter = memo(() => {
|
||||
const { id } = useEntityIdentifierContext();
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const defaultIPAdapter = useDefaultIPAdapter();
|
||||
const selectValidActions = useMemo(
|
||||
() =>
|
||||
createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
|
||||
const rg = canvasV2.regions.entities.find((rg) => rg.id === id);
|
||||
return {
|
||||
canAddPositivePrompt: rg?.positivePrompt === null,
|
||||
canAddNegativePrompt: rg?.negativePrompt === null,
|
||||
};
|
||||
}),
|
||||
[id]
|
||||
);
|
||||
const validActions = useAppSelector(selectValidActions);
|
||||
const addPositivePrompt = useCallback(() => {
|
||||
dispatch(rgPositivePromptChanged({ id: id, prompt: '' }));
|
||||
}, [dispatch, id]);
|
||||
const addNegativePrompt = useCallback(() => {
|
||||
dispatch(rgNegativePromptChanged({ id: id, prompt: '' }));
|
||||
}, [dispatch, id]);
|
||||
const addIPAdapter = useCallback(() => {
|
||||
dispatch(
|
||||
rgIPAdapterAdded({ id, ipAdapter: { ...defaultIPAdapter, id: nanoid(), type: 'ip_adapter', isEnabled: true } })
|
||||
);
|
||||
}, [defaultIPAdapter, dispatch, id]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<MenuItem onClick={addPositivePrompt} isDisabled={!validActions.canAddPositivePrompt}>
|
||||
{t('controlLayers.addPositivePrompt')}
|
||||
</MenuItem>
|
||||
<MenuItem onClick={addNegativePrompt} isDisabled={!validActions.canAddNegativePrompt}>
|
||||
{t('controlLayers.addNegativePrompt')}
|
||||
</MenuItem>
|
||||
<MenuItem onClick={addIPAdapter}>{t('controlLayers.addIPAdapter')}</MenuItem>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
RegionalGuidanceMenuItemsAddPromptsAndIPAdapter.displayName = 'RegionalGuidanceMenuItemsExtra';
|
@ -1,21 +1,13 @@
|
||||
import { Button } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { allEntitiesDeleted } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiTrashSimpleBold } from 'react-icons/pi';
|
||||
|
||||
export const DeleteAllLayersButton = memo(() => {
|
||||
export const ResetAllEntitiesButton = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const entityCount = useAppSelector((s) => {
|
||||
return (
|
||||
s.canvasV2.regions.entities.length +
|
||||
// s.canvasV2.controlAdapters.entities.length +
|
||||
s.canvasV2.ipAdapters.entities.length +
|
||||
s.canvasV2.rasterLayers.entities.length
|
||||
);
|
||||
});
|
||||
const onClick = useCallback(() => {
|
||||
dispatch(allEntitiesDeleted());
|
||||
}, [dispatch]);
|
||||
@ -26,12 +18,11 @@ export const DeleteAllLayersButton = memo(() => {
|
||||
leftIcon={<PiTrashSimpleBold />}
|
||||
variant="ghost"
|
||||
colorScheme="error"
|
||||
isDisabled={entityCount === 0}
|
||||
data-testid="control-layers-delete-all-layers-button"
|
||||
>
|
||||
{t('controlLayers.deleteAll')}
|
||||
{t('controlLayers.resetAll')}
|
||||
</Button>
|
||||
);
|
||||
});
|
||||
|
||||
DeleteAllLayersButton.displayName = 'DeleteAllLayersButton';
|
||||
ResetAllEntitiesButton.displayName = 'ResetAllEntitiesButton';
|
@ -1,200 +0,0 @@
|
||||
import { MenuDivider, MenuItem } from '@invoke-ai/ui-library';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||
import { useDefaultControlAdapter } from 'features/controlLayers/hooks/useLayerControlAdapter';
|
||||
import { $canvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import {
|
||||
$filteringEntity,
|
||||
controlLayerConvertedToRasterLayer,
|
||||
entityArrangedBackwardOne,
|
||||
entityArrangedForwardOne,
|
||||
entityArrangedToBack,
|
||||
entityArrangedToFront,
|
||||
entityDeleted,
|
||||
entityReset,
|
||||
rasterLayerConvertedToControlLayer,
|
||||
selectCanvasV2Slice,
|
||||
} from 'features/controlLayers/store/canvasV2Slice';
|
||||
import type { CanvasEntityIdentifier, CanvasV2State } from 'features/controlLayers/store/types';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
PiArrowCounterClockwiseBold,
|
||||
PiArrowDownBold,
|
||||
PiArrowLineDownBold,
|
||||
PiArrowLineUpBold,
|
||||
PiArrowUpBold,
|
||||
PiCheckBold,
|
||||
PiQuestionMarkBold,
|
||||
PiStarHalfBold,
|
||||
PiTrashSimpleBold,
|
||||
} from 'react-icons/pi';
|
||||
|
||||
const getIndexAndCount = (
|
||||
canvasV2: CanvasV2State,
|
||||
{ id, type }: CanvasEntityIdentifier
|
||||
): { index: number; count: number } => {
|
||||
if (type === 'raster_layer') {
|
||||
return {
|
||||
index: canvasV2.rasterLayers.entities.findIndex((entity) => entity.id === id),
|
||||
count: canvasV2.rasterLayers.entities.length,
|
||||
};
|
||||
} else if (type === 'control_layer') {
|
||||
return {
|
||||
index: canvasV2.controlLayers.entities.findIndex((entity) => entity.id === id),
|
||||
count: canvasV2.controlLayers.entities.length,
|
||||
};
|
||||
} else if (type === 'regional_guidance') {
|
||||
return {
|
||||
index: canvasV2.regions.entities.findIndex((entity) => entity.id === id),
|
||||
count: canvasV2.regions.entities.length,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
index: -1,
|
||||
count: 0,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const CanvasEntityActionMenuItems = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const canvasManager = useStore($canvasManager);
|
||||
const dispatch = useAppDispatch();
|
||||
const entityIdentifier = useEntityIdentifierContext();
|
||||
const selectValidActions = useMemo(
|
||||
() =>
|
||||
createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
|
||||
const { index, count } = getIndexAndCount(canvasV2, entityIdentifier);
|
||||
return {
|
||||
canMoveForwardOne: index < count - 1,
|
||||
canMoveBackwardOne: index > 0,
|
||||
canMoveToFront: index < count - 1,
|
||||
canMoveToBack: index > 0,
|
||||
};
|
||||
}),
|
||||
[entityIdentifier]
|
||||
);
|
||||
|
||||
const validActions = useAppSelector(selectValidActions);
|
||||
|
||||
const isArrangeable = useMemo(
|
||||
() =>
|
||||
entityIdentifier.type === 'raster_layer' ||
|
||||
entityIdentifier.type === 'control_layer' ||
|
||||
entityIdentifier.type === 'regional_guidance',
|
||||
[entityIdentifier.type]
|
||||
);
|
||||
|
||||
const isDeleteable = useMemo(
|
||||
() =>
|
||||
entityIdentifier.type === 'raster_layer' ||
|
||||
entityIdentifier.type === 'control_layer' ||
|
||||
entityIdentifier.type === 'regional_guidance',
|
||||
[entityIdentifier.type]
|
||||
);
|
||||
|
||||
const isFilterable = useMemo(
|
||||
() => entityIdentifier.type === 'raster_layer' || entityIdentifier.type === 'control_layer',
|
||||
[entityIdentifier.type]
|
||||
);
|
||||
|
||||
const isRasterLayer = useMemo(() => entityIdentifier.type === 'raster_layer', [entityIdentifier.type]);
|
||||
|
||||
const isControlLayer = useMemo(() => entityIdentifier.type === 'control_layer', [entityIdentifier.type]);
|
||||
|
||||
const defaultControlAdapter = useDefaultControlAdapter();
|
||||
|
||||
const convertRasterLayerToControlLayer = useCallback(() => {
|
||||
dispatch(rasterLayerConvertedToControlLayer({ id: entityIdentifier.id, controlAdapter: defaultControlAdapter }));
|
||||
}, [dispatch, defaultControlAdapter, entityIdentifier.id]);
|
||||
|
||||
const convertControlLayerToRasterLayer = useCallback(() => {
|
||||
dispatch(controlLayerConvertedToRasterLayer({ id: entityIdentifier.id }));
|
||||
}, [dispatch, entityIdentifier.id]);
|
||||
|
||||
const deleteEntity = useCallback(() => {
|
||||
dispatch(entityDeleted({ entityIdentifier }));
|
||||
}, [dispatch, entityIdentifier]);
|
||||
const resetEntity = useCallback(() => {
|
||||
dispatch(entityReset({ entityIdentifier }));
|
||||
}, [dispatch, entityIdentifier]);
|
||||
const moveForwardOne = useCallback(() => {
|
||||
dispatch(entityArrangedForwardOne({ entityIdentifier }));
|
||||
}, [dispatch, entityIdentifier]);
|
||||
const moveToFront = useCallback(() => {
|
||||
dispatch(entityArrangedToFront({ entityIdentifier }));
|
||||
}, [dispatch, entityIdentifier]);
|
||||
const moveBackwardOne = useCallback(() => {
|
||||
dispatch(entityArrangedBackwardOne({ entityIdentifier }));
|
||||
}, [dispatch, entityIdentifier]);
|
||||
const moveToBack = useCallback(() => {
|
||||
dispatch(entityArrangedToBack({ entityIdentifier }));
|
||||
}, [dispatch, entityIdentifier]);
|
||||
const filter = useCallback(() => {
|
||||
$filteringEntity.set(entityIdentifier);
|
||||
}, [entityIdentifier]);
|
||||
const debug = useCallback(() => {
|
||||
if (!canvasManager) {
|
||||
return;
|
||||
}
|
||||
const entity = canvasManager.stateApi.getEntity(entityIdentifier);
|
||||
if (!entity) {
|
||||
return;
|
||||
}
|
||||
console.debug(entity);
|
||||
}, [canvasManager, entityIdentifier]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{isArrangeable && (
|
||||
<>
|
||||
<MenuItem onClick={moveToFront} isDisabled={!validActions.canMoveToFront} icon={<PiArrowLineUpBold />}>
|
||||
{t('controlLayers.moveToFront')}
|
||||
</MenuItem>
|
||||
<MenuItem onClick={moveForwardOne} isDisabled={!validActions.canMoveForwardOne} icon={<PiArrowUpBold />}>
|
||||
{t('controlLayers.moveForward')}
|
||||
</MenuItem>
|
||||
<MenuItem onClick={moveBackwardOne} isDisabled={!validActions.canMoveBackwardOne} icon={<PiArrowDownBold />}>
|
||||
{t('controlLayers.moveBackward')}
|
||||
</MenuItem>
|
||||
<MenuItem onClick={moveToBack} isDisabled={!validActions.canMoveToBack} icon={<PiArrowLineDownBold />}>
|
||||
{t('controlLayers.moveToBack')}
|
||||
</MenuItem>
|
||||
</>
|
||||
)}
|
||||
{isFilterable && (
|
||||
<MenuItem onClick={filter} icon={<PiStarHalfBold />}>
|
||||
{t('common.filter')}
|
||||
</MenuItem>
|
||||
)}
|
||||
{isRasterLayer && (
|
||||
<MenuItem onClick={convertRasterLayerToControlLayer} icon={<PiCheckBold />}>
|
||||
{t('common.convertToControlLayer')}
|
||||
</MenuItem>
|
||||
)}
|
||||
{isControlLayer && (
|
||||
<MenuItem onClick={convertControlLayerToRasterLayer} icon={<PiCheckBold />}>
|
||||
{t('common.convertToRasterLayer')}
|
||||
</MenuItem>
|
||||
)}
|
||||
<MenuDivider />
|
||||
<MenuItem onClick={resetEntity} icon={<PiArrowCounterClockwiseBold />}>
|
||||
{t('accessibility.reset')}
|
||||
</MenuItem>
|
||||
{isDeleteable && (
|
||||
<MenuItem onClick={deleteEntity} icon={<PiTrashSimpleBold />} color="error.300">
|
||||
{t('common.delete')}
|
||||
</MenuItem>
|
||||
)}
|
||||
<MenuDivider />
|
||||
<MenuItem onClick={debug} icon={<PiQuestionMarkBold />} color="warn.300">
|
||||
{t('common.debug')}
|
||||
</MenuItem>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
CanvasEntityActionMenuItems.displayName = 'CanvasEntityActionMenuItems';
|
@ -1,16 +1,54 @@
|
||||
import type { FlexProps } from '@invoke-ai/ui-library';
|
||||
import { ContextMenu, Flex, MenuList } from '@invoke-ai/ui-library';
|
||||
import { CanvasEntityActionMenuItems } from 'features/controlLayers/components/common/CanvasEntityActionMenuItems';
|
||||
import { ControlLayerMenuItems } from 'features/controlLayers/components/ControlLayer/ControlLayerMenuItems';
|
||||
import { InpaintMaskMenuItems } from 'features/controlLayers/components/InpaintMask/InpaintMaskMenuItems';
|
||||
import { RasterLayerMenuItems } from 'features/controlLayers/components/RasterLayer/RasterLayerMenuItems';
|
||||
import { RegionalGuidanceMenuItems } from 'features/controlLayers/components/RegionalGuidance/RegionalGuidanceMenuItems';
|
||||
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
export const CanvasEntityHeader = memo(({ children, ...rest }: FlexProps) => {
|
||||
const entityIdentifier = useEntityIdentifierContext();
|
||||
const renderMenu = useCallback(() => {
|
||||
return (
|
||||
<MenuList>
|
||||
<CanvasEntityActionMenuItems />
|
||||
</MenuList>
|
||||
);
|
||||
}, []);
|
||||
if (entityIdentifier.type === 'regional_guidance') {
|
||||
return (
|
||||
<MenuList>
|
||||
<RegionalGuidanceMenuItems />
|
||||
</MenuList>
|
||||
);
|
||||
}
|
||||
|
||||
if (entityIdentifier.type === 'inpaint_mask') {
|
||||
return (
|
||||
<MenuList>
|
||||
<InpaintMaskMenuItems />
|
||||
</MenuList>
|
||||
);
|
||||
}
|
||||
|
||||
if (entityIdentifier.type === 'raster_layer') {
|
||||
return (
|
||||
<MenuList>
|
||||
<RasterLayerMenuItems />
|
||||
</MenuList>
|
||||
);
|
||||
}
|
||||
|
||||
if (entityIdentifier.type === 'control_layer') {
|
||||
return (
|
||||
<MenuList>
|
||||
<ControlLayerMenuItems />
|
||||
</MenuList>
|
||||
);
|
||||
}
|
||||
|
||||
if (entityIdentifier.type === 'ip_adapter') {
|
||||
return <MenuList>{/* <ControlLayerMenuItems /> */}</MenuList>;
|
||||
}
|
||||
|
||||
assert(false, 'Unhandled entity type');
|
||||
}, [entityIdentifier]);
|
||||
|
||||
return (
|
||||
<ContextMenu renderMenu={renderMenu}>
|
||||
|
@ -0,0 +1,95 @@
|
||||
import { MenuItem } from '@invoke-ai/ui-library';
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||
import {
|
||||
entityArrangedBackwardOne,
|
||||
entityArrangedForwardOne,
|
||||
entityArrangedToBack,
|
||||
entityArrangedToFront,
|
||||
selectCanvasV2Slice,
|
||||
} from 'features/controlLayers/store/canvasV2Slice';
|
||||
import type { CanvasEntityIdentifier, CanvasV2State } from 'features/controlLayers/store/types';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiArrowDownBold, PiArrowLineDownBold, PiArrowLineUpBold, PiArrowUpBold } from 'react-icons/pi';
|
||||
|
||||
const getIndexAndCount = (
|
||||
canvasV2: CanvasV2State,
|
||||
{ id, type }: CanvasEntityIdentifier
|
||||
): { index: number; count: number } => {
|
||||
if (type === 'raster_layer') {
|
||||
return {
|
||||
index: canvasV2.rasterLayers.entities.findIndex((entity) => entity.id === id),
|
||||
count: canvasV2.rasterLayers.entities.length,
|
||||
};
|
||||
} else if (type === 'control_layer') {
|
||||
return {
|
||||
index: canvasV2.controlLayers.entities.findIndex((entity) => entity.id === id),
|
||||
count: canvasV2.controlLayers.entities.length,
|
||||
};
|
||||
} else if (type === 'regional_guidance') {
|
||||
return {
|
||||
index: canvasV2.regions.entities.findIndex((entity) => entity.id === id),
|
||||
count: canvasV2.regions.entities.length,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
index: -1,
|
||||
count: 0,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const CanvasEntityMenuItemsArrange = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const entityIdentifier = useEntityIdentifierContext();
|
||||
const selectValidActions = useMemo(
|
||||
() =>
|
||||
createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
|
||||
const { index, count } = getIndexAndCount(canvasV2, entityIdentifier);
|
||||
return {
|
||||
canMoveForwardOne: index < count - 1,
|
||||
canMoveBackwardOne: index > 0,
|
||||
canMoveToFront: index < count - 1,
|
||||
canMoveToBack: index > 0,
|
||||
};
|
||||
}),
|
||||
[entityIdentifier]
|
||||
);
|
||||
|
||||
const validActions = useAppSelector(selectValidActions);
|
||||
|
||||
const moveForwardOne = useCallback(() => {
|
||||
dispatch(entityArrangedForwardOne({ entityIdentifier }));
|
||||
}, [dispatch, entityIdentifier]);
|
||||
const moveToFront = useCallback(() => {
|
||||
dispatch(entityArrangedToFront({ entityIdentifier }));
|
||||
}, [dispatch, entityIdentifier]);
|
||||
const moveBackwardOne = useCallback(() => {
|
||||
dispatch(entityArrangedBackwardOne({ entityIdentifier }));
|
||||
}, [dispatch, entityIdentifier]);
|
||||
const moveToBack = useCallback(() => {
|
||||
dispatch(entityArrangedToBack({ entityIdentifier }));
|
||||
}, [dispatch, entityIdentifier]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<MenuItem onClick={moveToFront} isDisabled={!validActions.canMoveToFront} icon={<PiArrowLineUpBold />}>
|
||||
{t('controlLayers.moveToFront')}
|
||||
</MenuItem>
|
||||
<MenuItem onClick={moveForwardOne} isDisabled={!validActions.canMoveForwardOne} icon={<PiArrowUpBold />}>
|
||||
{t('controlLayers.moveForward')}
|
||||
</MenuItem>
|
||||
<MenuItem onClick={moveBackwardOne} isDisabled={!validActions.canMoveBackwardOne} icon={<PiArrowDownBold />}>
|
||||
{t('controlLayers.moveBackward')}
|
||||
</MenuItem>
|
||||
<MenuItem onClick={moveToBack} isDisabled={!validActions.canMoveToBack} icon={<PiArrowLineDownBold />}>
|
||||
{t('controlLayers.moveToBack')}
|
||||
</MenuItem>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
CanvasEntityMenuItemsArrange.displayName = 'CanvasEntityArrangeMenuItems';
|
@ -0,0 +1,25 @@
|
||||
import { MenuItem } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||
import { entityDeleted } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiTrashSimpleBold } from 'react-icons/pi';
|
||||
|
||||
export const CanvasEntityMenuItemsDelete = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const entityIdentifier = useEntityIdentifierContext();
|
||||
|
||||
const deleteEntity = useCallback(() => {
|
||||
dispatch(entityDeleted({ entityIdentifier }));
|
||||
}, [dispatch, entityIdentifier]);
|
||||
|
||||
return (
|
||||
<MenuItem onClick={deleteEntity} icon={<PiTrashSimpleBold />} color="error.300">
|
||||
{t('common.delete')}
|
||||
</MenuItem>
|
||||
);
|
||||
});
|
||||
|
||||
CanvasEntityMenuItemsDelete.displayName = 'CanvasEntityMenuItemsDelete';
|
@ -0,0 +1,22 @@
|
||||
import { MenuItem } from '@invoke-ai/ui-library';
|
||||
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||
import { $filteringEntity } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiShootingStarBold } from 'react-icons/pi';
|
||||
|
||||
export const CanvasEntityMenuItemsFilter = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const entityIdentifier = useEntityIdentifierContext();
|
||||
const filter = useCallback(() => {
|
||||
$filteringEntity.set(entityIdentifier);
|
||||
}, [entityIdentifier]);
|
||||
|
||||
return (
|
||||
<MenuItem onClick={filter} icon={<PiShootingStarBold />}>
|
||||
{t('controlLayers.filter')}
|
||||
</MenuItem>
|
||||
);
|
||||
});
|
||||
|
||||
CanvasEntityMenuItemsFilter.displayName = 'CanvasEntityMenuItemsFilter';
|
@ -0,0 +1,25 @@
|
||||
import { MenuItem } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||
import { entityReset } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiArrowCounterClockwiseBold } from 'react-icons/pi';
|
||||
|
||||
export const CanvasEntityMenuItemsReset = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const entityIdentifier = useEntityIdentifierContext();
|
||||
|
||||
const resetEntity = useCallback(() => {
|
||||
dispatch(entityReset({ entityIdentifier }));
|
||||
}, [dispatch, entityIdentifier]);
|
||||
|
||||
return (
|
||||
<MenuItem onClick={resetEntity} icon={<PiArrowCounterClockwiseBold />}>
|
||||
{t('accessibility.reset')}
|
||||
</MenuItem>
|
||||
);
|
||||
});
|
||||
|
||||
CanvasEntityMenuItemsReset.displayName = 'CanvasEntityMenuItemsReset';
|
@ -1,89 +0,0 @@
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { deepClone } from 'common/util/deepClone';
|
||||
import { ipaAdded, rgIPAdapterAdded } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import {
|
||||
IMAGE_FILTERS,
|
||||
initialControlNetV2,
|
||||
initialIPAdapterV2,
|
||||
initialT2IAdapterV2,
|
||||
isFilterType,
|
||||
} from 'features/controlLayers/store/types';
|
||||
import { zModelIdentifierField } from 'features/nodes/types/common';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useControlNetAndT2IAdapterModels, useIPAdapterModels } from 'services/api/hooks/modelsByType';
|
||||
import type { ControlNetModelConfig, IPAdapterModelConfig, T2IAdapterModelConfig } from 'services/api/types';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
export const useAddCALayer = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const baseModel = useAppSelector((s) => s.canvasV2.params.model?.base);
|
||||
const [modelConfigs] = useControlNetAndT2IAdapterModels();
|
||||
const model: ControlNetModelConfig | T2IAdapterModelConfig | null = useMemo(() => {
|
||||
// prefer to use a model that matches the base model
|
||||
const compatibleModels = modelConfigs.filter((m) => (baseModel ? m.base === baseModel : true));
|
||||
return compatibleModels[0] ?? modelConfigs[0] ?? null;
|
||||
}, [baseModel, modelConfigs]);
|
||||
const isDisabled = useMemo(() => !model, [model]);
|
||||
const addCALayer = useCallback(() => {
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
|
||||
const defaultPreprocessor = model.default_settings?.preprocessor;
|
||||
const processorConfig = isFilterType(defaultPreprocessor)
|
||||
? IMAGE_FILTERS[defaultPreprocessor].buildDefaults(baseModel)
|
||||
: null;
|
||||
|
||||
const initialConfig = deepClone(model.type === 'controlnet' ? initialControlNetV2 : initialT2IAdapterV2);
|
||||
const config = { ...initialConfig, model: zModelIdentifierField.parse(model), processorConfig };
|
||||
|
||||
// dispatch(caAdded({ config }));
|
||||
}, [dispatch, model, baseModel]);
|
||||
|
||||
return [addCALayer, isDisabled] as const;
|
||||
};
|
||||
|
||||
export const useAddIPALayer = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const baseModel = useAppSelector((s) => s.canvasV2.params.model?.base);
|
||||
const [modelConfigs] = useIPAdapterModels();
|
||||
const model: IPAdapterModelConfig | null = useMemo(() => {
|
||||
// prefer to use a model that matches the base model
|
||||
const compatibleModels = modelConfigs.filter((m) => (baseModel ? m.base === baseModel : true));
|
||||
return compatibleModels[0] ?? modelConfigs[0] ?? null;
|
||||
}, [baseModel, modelConfigs]);
|
||||
const isDisabled = useMemo(() => !model, [model]);
|
||||
const addIPALayer = useCallback(() => {
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
|
||||
const initialConfig = deepClone(initialIPAdapterV2);
|
||||
const config = { ...initialConfig, model: zModelIdentifierField.parse(model) };
|
||||
dispatch(ipaAdded({ config }));
|
||||
}, [dispatch, model]);
|
||||
|
||||
return [addIPALayer, isDisabled] as const;
|
||||
};
|
||||
|
||||
export const useAddIPAdapterToRGLayer = (id: string) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const baseModel = useAppSelector((s) => s.canvasV2.params.model?.base);
|
||||
const [modelConfigs] = useIPAdapterModels();
|
||||
const model: IPAdapterModelConfig | null = useMemo(() => {
|
||||
// prefer to use a model that matches the base model
|
||||
const compatibleModels = modelConfigs.filter((m) => (baseModel ? m.base === baseModel : true));
|
||||
return compatibleModels[0] ?? modelConfigs[0] ?? null;
|
||||
}, [baseModel, modelConfigs]);
|
||||
const isDisabled = useMemo(() => !model, [model]);
|
||||
const addIPAdapter = useCallback(() => {
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
const initialConfig = deepClone(initialIPAdapterV2);
|
||||
const config = { ...initialConfig, model: zModelIdentifierField.parse(model) };
|
||||
dispatch(rgIPAdapterAdded({ id, ipAdapter: { ...config, id: uuidv4(), type: 'ip_adapter', isEnabled: true } }));
|
||||
}, [model, dispatch, id]);
|
||||
|
||||
return [addIPAdapter, isDisabled] as const;
|
||||
};
|
@ -4,10 +4,10 @@ import { deepClone } from 'common/util/deepClone';
|
||||
import { selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { selectControlLayerOrThrow } from 'features/controlLayers/store/controlLayersReducers';
|
||||
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
|
||||
import { initialControlNetV2, initialT2IAdapterV2 } from 'features/controlLayers/store/types';
|
||||
import { initialControlNetV2, initialIPAdapterV2, initialT2IAdapterV2 } from 'features/controlLayers/store/types';
|
||||
import { zModelIdentifierField } from 'features/nodes/types/common';
|
||||
import { useMemo } from 'react';
|
||||
import { useControlNetAndT2IAdapterModels } from 'services/api/hooks/modelsByType';
|
||||
import { useControlNetAndT2IAdapterModels, useIPAdapterModels } from 'services/api/hooks/modelsByType';
|
||||
|
||||
export const useControlLayerControlAdapter = (entityIdentifier: CanvasEntityIdentifier) => {
|
||||
const selectControlAdapter = useMemo(
|
||||
@ -42,3 +42,23 @@ export const useDefaultControlAdapter = () => {
|
||||
|
||||
return defaultControlAdapter;
|
||||
};
|
||||
|
||||
export const useDefaultIPAdapter = () => {
|
||||
const [modelConfigs] = useIPAdapterModels();
|
||||
|
||||
const baseModel = useAppSelector((s) => s.canvasV2.params.model?.base);
|
||||
|
||||
const defaultControlAdapter = useMemo(() => {
|
||||
const compatibleModels = modelConfigs.filter((m) => (baseModel ? m.base === baseModel : true));
|
||||
const model = compatibleModels[0] ?? modelConfigs[0] ?? null;
|
||||
const ipAdapter = deepClone(initialIPAdapterV2);
|
||||
|
||||
if (model) {
|
||||
ipAdapter.model = zModelIdentifierField.parse(model);
|
||||
}
|
||||
|
||||
return ipAdapter;
|
||||
}, [baseModel, modelConfigs]);
|
||||
|
||||
return defaultControlAdapter;
|
||||
};
|
||||
|
@ -44,7 +44,7 @@ import { IMAGE_FILTERS, isDrawableEntity, RGBA_RED } from './types';
|
||||
|
||||
const initialState: CanvasV2State = {
|
||||
_version: 3,
|
||||
selectedEntityIdentifier: null,
|
||||
selectedEntityIdentifier: { id: 'inpaint_mask', type: 'inpaint_mask' },
|
||||
rasterLayers: { entities: [], compositeRasterizationCache: [] },
|
||||
controlLayers: { entities: [] },
|
||||
ipAdapters: { entities: [] },
|
||||
@ -385,10 +385,13 @@ export const canvasV2Slice = createSlice({
|
||||
}
|
||||
},
|
||||
allEntitiesDeleted: (state) => {
|
||||
state.regions.entities = [];
|
||||
state.rasterLayers.entities = [];
|
||||
state.ipAdapters = deepClone(initialState.ipAdapters);
|
||||
state.rasterLayers = deepClone(initialState.rasterLayers);
|
||||
state.rasterLayers.compositeRasterizationCache = [];
|
||||
state.ipAdapters.entities = [];
|
||||
state.controlLayers = deepClone(initialState.controlLayers);
|
||||
state.regions = deepClone(initialState.regions);
|
||||
state.inpaintMask = deepClone(initialState.inpaintMask);
|
||||
state.selectedEntityIdentifier = deepClone(initialState.selectedEntityIdentifier);
|
||||
},
|
||||
filterSelected: (state, action: PayloadAction<{ type: FilterConfig['type'] }>) => {
|
||||
state.filter.config = IMAGE_FILTERS[action.payload.type].buildDefaults();
|
||||
@ -423,6 +426,7 @@ export const canvasV2Slice = createSlice({
|
||||
|
||||
state.ipAdapters = deepClone(initialState.ipAdapters);
|
||||
state.rasterLayers = deepClone(initialState.rasterLayers);
|
||||
state.rasterLayers.compositeRasterizationCache = [];
|
||||
state.controlLayers = deepClone(initialState.controlLayers);
|
||||
state.regions = deepClone(initialState.regions);
|
||||
state.selectedEntityIdentifier = deepClone(initialState.selectedEntityIdentifier);
|
||||
|
Loading…
Reference in New Issue
Block a user