mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): add entity group hiding
This commit is contained in:
parent
27e829b955
commit
3136d89d52
@ -1666,25 +1666,37 @@
|
|||||||
"addPositivePrompt": "Add $t(common.positivePrompt)",
|
"addPositivePrompt": "Add $t(common.positivePrompt)",
|
||||||
"addNegativePrompt": "Add $t(common.negativePrompt)",
|
"addNegativePrompt": "Add $t(common.negativePrompt)",
|
||||||
"addIPAdapter": "Add $t(common.ipAdapter)",
|
"addIPAdapter": "Add $t(common.ipAdapter)",
|
||||||
"regionalGuidance": "Regional Guidance",
|
|
||||||
"regionalGuidanceLayer": "$t(controlLayers.regionalGuidance) $t(unifiedCanvas.layer)",
|
"regionalGuidanceLayer": "$t(controlLayers.regionalGuidance) $t(unifiedCanvas.layer)",
|
||||||
"raster": "Raster",
|
"raster": "Raster",
|
||||||
"rasterLayer": "$t(controlLayers.raster) $t(unifiedCanvas.layer)",
|
"rasterLayer_one": "Raster Layer",
|
||||||
|
"controlLayer_one": "Control Layer",
|
||||||
|
"inpaintMask_one": "Inpaint Mask",
|
||||||
|
"regionalGuidance_one": "Regional Guidance",
|
||||||
|
"ipAdapter_one": "IP Adapter",
|
||||||
|
"rasterLayer_other": "Raster Layers",
|
||||||
|
"controlLayer_other": "Control Layers",
|
||||||
|
"inpaintMask_other": "Inpaint Masks",
|
||||||
|
"regionalGuidance_other": "Regional Guidance",
|
||||||
|
"ipAdapter_other": "IP Adapters",
|
||||||
"opacity": "Opacity",
|
"opacity": "Opacity",
|
||||||
"regionalGuidance_withCount": "Regional Guidance ({{count}})",
|
"regionalGuidance_withCount_hidden": "Regional Guidance ({{count}} hidden)",
|
||||||
"controlAdapters_withCount": "Control Adapters ({{count}})",
|
"controlAdapters_withCount_hidden": "Control Adapters ({{count}} hidden)",
|
||||||
"controlLayer": "Control Layer",
|
"controlLayers_withCount_hidden": "Control Layers ({{count}} hidden)",
|
||||||
"controlLayers_withCount": "Control Layers ({{count}})",
|
"rasterLayers_withCount_hidden": "Raster Layers ({{count}} hidden)",
|
||||||
"rasterLayers_withCount": "Raster Layers ({{count}})",
|
"ipAdapters_withCount_hidden": "IP Adapters ({{count}} hidden)",
|
||||||
"ipAdapters_withCount": "IP Adapters ({{count}})",
|
"inpaintMasks_withCount_hidden": "Inpaint Masks ({{count}} hidden)",
|
||||||
"inpaintMasks_withCount": "Inpaint Masks ({{count}})",
|
"regionalGuidance_withCount_visible": "Regional Guidance ({{count}})",
|
||||||
|
"controlAdapters_withCount_visible": "Control Adapters ({{count}})",
|
||||||
|
"controlLayers_withCount_visible": "Control Layers ({{count}})",
|
||||||
|
"rasterLayers_withCount_visible": "Raster Layers ({{count}})",
|
||||||
|
"ipAdapters_withCount_visible": "IP Adapters ({{count}})",
|
||||||
|
"inpaintMasks_withCount_visible": "Inpaint Masks ({{count}})",
|
||||||
"globalControlAdapter": "Global $t(controlnet.controlAdapter_one)",
|
"globalControlAdapter": "Global $t(controlnet.controlAdapter_one)",
|
||||||
"globalControlAdapterLayer": "Global $t(controlnet.controlAdapter_one) $t(unifiedCanvas.layer)",
|
"globalControlAdapterLayer": "Global $t(controlnet.controlAdapter_one) $t(unifiedCanvas.layer)",
|
||||||
"globalIPAdapter": "Global $t(common.ipAdapter)",
|
"globalIPAdapter": "Global $t(common.ipAdapter)",
|
||||||
"globalIPAdapterLayer": "Global $t(common.ipAdapter) $t(unifiedCanvas.layer)",
|
"globalIPAdapterLayer": "Global $t(common.ipAdapter) $t(unifiedCanvas.layer)",
|
||||||
"globalInitialImage": "Global Initial Image",
|
"globalInitialImage": "Global Initial Image",
|
||||||
"globalInitialImageLayer": "$t(controlLayers.globalInitialImage) $t(unifiedCanvas.layer)",
|
"globalInitialImageLayer": "$t(controlLayers.globalInitialImage) $t(unifiedCanvas.layer)",
|
||||||
"inpaintMask": "Inpaint Mask",
|
|
||||||
"layer": "Layer",
|
"layer": "Layer",
|
||||||
"opacityFilter": "Opacity Filter",
|
"opacityFilter": "Opacity Filter",
|
||||||
"clearProcessor": "Clear Processor",
|
"clearProcessor": "Clear Processor",
|
||||||
@ -1699,6 +1711,8 @@
|
|||||||
"convertToRasterLayer": "Convert to Raster Layer",
|
"convertToRasterLayer": "Convert to Raster Layer",
|
||||||
"enableTransparencyEffect": "Enable Transparency Effect",
|
"enableTransparencyEffect": "Enable Transparency Effect",
|
||||||
"disableTransparencyEffect": "Disable Transparency Effect",
|
"disableTransparencyEffect": "Disable Transparency Effect",
|
||||||
|
"hidingType": "Hiding {{type}}",
|
||||||
|
"showingType": "Showing {{type}}",
|
||||||
"fill": {
|
"fill": {
|
||||||
"fillStyle": "Fill Style",
|
"fillStyle": "Fill Style",
|
||||||
"solid": "Solid",
|
"solid": "Solid",
|
||||||
|
@ -5,14 +5,12 @@ import { ControlLayer } from 'features/controlLayers/components/ControlLayer/Con
|
|||||||
import { mapId } from 'features/controlLayers/konva/util';
|
import { mapId } from 'features/controlLayers/konva/util';
|
||||||
import { selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice';
|
import { selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
|
const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
|
||||||
return canvasV2.controlLayers.entities.map(mapId).reverse();
|
return canvasV2.controlLayers.entities.map(mapId).reverse();
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ControlLayerEntityList = memo(() => {
|
export const ControlLayerEntityList = memo(() => {
|
||||||
const { t } = useTranslation();
|
|
||||||
const isSelected = useAppSelector((s) => Boolean(s.canvasV2.selectedEntityIdentifier?.type === 'control_layer'));
|
const isSelected = useAppSelector((s) => Boolean(s.canvasV2.selectedEntityIdentifier?.type === 'control_layer'));
|
||||||
const layerIds = useAppSelector(selectEntityIds);
|
const layerIds = useAppSelector(selectEntityIds);
|
||||||
|
|
||||||
@ -22,11 +20,7 @@ export const ControlLayerEntityList = memo(() => {
|
|||||||
|
|
||||||
if (layerIds.length > 0) {
|
if (layerIds.length > 0) {
|
||||||
return (
|
return (
|
||||||
<CanvasEntityGroupList
|
<CanvasEntityGroupList type="control_layer" isSelected={isSelected}>
|
||||||
type="control_layer"
|
|
||||||
title={t('controlLayers.controlLayers_withCount', { count: layerIds.length })}
|
|
||||||
isSelected={isSelected}
|
|
||||||
>
|
|
||||||
{layerIds.map((id) => (
|
{layerIds.map((id) => (
|
||||||
<ControlLayer key={id} id={id} />
|
<ControlLayer key={id} id={id} />
|
||||||
))}
|
))}
|
||||||
|
@ -6,14 +6,12 @@ import { IPAdapter } from 'features/controlLayers/components/IPAdapter/IPAdapter
|
|||||||
import { mapId } from 'features/controlLayers/konva/util';
|
import { mapId } from 'features/controlLayers/konva/util';
|
||||||
import { selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice';
|
import { selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
|
const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
|
||||||
return canvasV2.ipAdapters.entities.map(mapId).reverse();
|
return canvasV2.ipAdapters.entities.map(mapId).reverse();
|
||||||
});
|
});
|
||||||
|
|
||||||
export const IPAdapterList = memo(() => {
|
export const IPAdapterList = memo(() => {
|
||||||
const { t } = useTranslation();
|
|
||||||
const isSelected = useAppSelector((s) => Boolean(s.canvasV2.selectedEntityIdentifier?.type === 'ip_adapter'));
|
const isSelected = useAppSelector((s) => Boolean(s.canvasV2.selectedEntityIdentifier?.type === 'ip_adapter'));
|
||||||
const ipaIds = useAppSelector(selectEntityIds);
|
const ipaIds = useAppSelector(selectEntityIds);
|
||||||
|
|
||||||
@ -23,11 +21,7 @@ export const IPAdapterList = memo(() => {
|
|||||||
|
|
||||||
if (ipaIds.length > 0) {
|
if (ipaIds.length > 0) {
|
||||||
return (
|
return (
|
||||||
<CanvasEntityGroupList
|
<CanvasEntityGroupList type="ip_adapter" isSelected={isSelected}>
|
||||||
type="ip_adapter"
|
|
||||||
title={t('controlLayers.ipAdapters_withCount', { count: ipaIds.length })}
|
|
||||||
isSelected={isSelected}
|
|
||||||
>
|
|
||||||
{ipaIds.map((id) => (
|
{ipaIds.map((id) => (
|
||||||
<IPAdapter key={id} id={id} />
|
<IPAdapter key={id} id={id} />
|
||||||
))}
|
))}
|
||||||
|
@ -5,14 +5,12 @@ import { InpaintMask } from 'features/controlLayers/components/InpaintMask/Inpai
|
|||||||
import { mapId } from 'features/controlLayers/konva/util';
|
import { mapId } from 'features/controlLayers/konva/util';
|
||||||
import { selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice';
|
import { selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
|
const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
|
||||||
return canvasV2.inpaintMasks.entities.map(mapId).reverse();
|
return canvasV2.inpaintMasks.entities.map(mapId).reverse();
|
||||||
});
|
});
|
||||||
|
|
||||||
export const InpaintMaskList = memo(() => {
|
export const InpaintMaskList = memo(() => {
|
||||||
const { t } = useTranslation();
|
|
||||||
const isSelected = useAppSelector((s) => Boolean(s.canvasV2.selectedEntityIdentifier?.type === 'inpaint_mask'));
|
const isSelected = useAppSelector((s) => Boolean(s.canvasV2.selectedEntityIdentifier?.type === 'inpaint_mask'));
|
||||||
const entityIds = useAppSelector(selectEntityIds);
|
const entityIds = useAppSelector(selectEntityIds);
|
||||||
|
|
||||||
@ -22,11 +20,7 @@ export const InpaintMaskList = memo(() => {
|
|||||||
|
|
||||||
if (entityIds.length > 0) {
|
if (entityIds.length > 0) {
|
||||||
return (
|
return (
|
||||||
<CanvasEntityGroupList
|
<CanvasEntityGroupList type="inpaint_mask" isSelected={isSelected}>
|
||||||
type="inpaint_mask"
|
|
||||||
title={t('controlLayers.inpaintMasks_withCount', { count: entityIds.length })}
|
|
||||||
isSelected={isSelected}
|
|
||||||
>
|
|
||||||
{entityIds.map((id) => (
|
{entityIds.map((id) => (
|
||||||
<InpaintMask key={id} id={id} />
|
<InpaintMask key={id} id={id} />
|
||||||
))}
|
))}
|
||||||
|
@ -5,14 +5,12 @@ import { RasterLayer } from 'features/controlLayers/components/RasterLayer/Raste
|
|||||||
import { mapId } from 'features/controlLayers/konva/util';
|
import { mapId } from 'features/controlLayers/konva/util';
|
||||||
import { selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice';
|
import { selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
|
const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
|
||||||
return canvasV2.rasterLayers.entities.map(mapId).reverse();
|
return canvasV2.rasterLayers.entities.map(mapId).reverse();
|
||||||
});
|
});
|
||||||
|
|
||||||
export const RasterLayerEntityList = memo(() => {
|
export const RasterLayerEntityList = memo(() => {
|
||||||
const { t } = useTranslation();
|
|
||||||
const isSelected = useAppSelector((s) => Boolean(s.canvasV2.selectedEntityIdentifier?.type === 'raster_layer'));
|
const isSelected = useAppSelector((s) => Boolean(s.canvasV2.selectedEntityIdentifier?.type === 'raster_layer'));
|
||||||
const layerIds = useAppSelector(selectEntityIds);
|
const layerIds = useAppSelector(selectEntityIds);
|
||||||
|
|
||||||
@ -22,11 +20,7 @@ export const RasterLayerEntityList = memo(() => {
|
|||||||
|
|
||||||
if (layerIds.length > 0) {
|
if (layerIds.length > 0) {
|
||||||
return (
|
return (
|
||||||
<CanvasEntityGroupList
|
<CanvasEntityGroupList type="raster_layer" isSelected={isSelected}>
|
||||||
type="raster_layer"
|
|
||||||
title={t('controlLayers.rasterLayers_withCount', { count: layerIds.length })}
|
|
||||||
isSelected={isSelected}
|
|
||||||
>
|
|
||||||
{layerIds.map((id) => (
|
{layerIds.map((id) => (
|
||||||
<RasterLayer key={id} id={id} />
|
<RasterLayer key={id} id={id} />
|
||||||
))}
|
))}
|
||||||
|
@ -5,14 +5,12 @@ import { RegionalGuidance } from 'features/controlLayers/components/RegionalGuid
|
|||||||
import { mapId } from 'features/controlLayers/konva/util';
|
import { mapId } from 'features/controlLayers/konva/util';
|
||||||
import { selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice';
|
import { selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
|
const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
|
||||||
return canvasV2.regions.entities.map(mapId).reverse();
|
return canvasV2.regions.entities.map(mapId).reverse();
|
||||||
});
|
});
|
||||||
|
|
||||||
export const RegionalGuidanceEntityList = memo(() => {
|
export const RegionalGuidanceEntityList = memo(() => {
|
||||||
const { t } = useTranslation();
|
|
||||||
const isSelected = useAppSelector((s) => Boolean(s.canvasV2.selectedEntityIdentifier?.type === 'regional_guidance'));
|
const isSelected = useAppSelector((s) => Boolean(s.canvasV2.selectedEntityIdentifier?.type === 'regional_guidance'));
|
||||||
const rgIds = useAppSelector(selectEntityIds);
|
const rgIds = useAppSelector(selectEntityIds);
|
||||||
|
|
||||||
@ -22,11 +20,7 @@ export const RegionalGuidanceEntityList = memo(() => {
|
|||||||
|
|
||||||
if (rgIds.length > 0) {
|
if (rgIds.length > 0) {
|
||||||
return (
|
return (
|
||||||
<CanvasEntityGroupList
|
<CanvasEntityGroupList type="regional_guidance" isSelected={isSelected}>
|
||||||
type="regional_guidance"
|
|
||||||
title={t('controlLayers.regionalGuidance_withCount', { count: rgIds.length })}
|
|
||||||
isSelected={isSelected}
|
|
||||||
>
|
|
||||||
{rgIds.map((id) => (
|
{rgIds.map((id) => (
|
||||||
<RegionalGuidance key={id} id={id} />
|
<RegionalGuidance key={id} id={id} />
|
||||||
))}
|
))}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { IconButton } from '@invoke-ai/ui-library';
|
import { IconButton } from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch } from 'app/store/storeHooks';
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
import { stopPropagation } from 'common/util/stopPropagation';
|
|
||||||
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||||
import { entityDeleted } from 'features/controlLayers/store/canvasV2Slice';
|
import { entityDeleted } from 'features/controlLayers/store/canvasV2Slice';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
@ -22,7 +21,8 @@ export const CanvasEntityDeleteButton = memo(() => {
|
|||||||
tooltip={t('common.delete')}
|
tooltip={t('common.delete')}
|
||||||
icon={<PiTrashSimpleBold />}
|
icon={<PiTrashSimpleBold />}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
onDoubleClick={stopPropagation} // double click expands the layer
|
variant="link"
|
||||||
|
alignSelf="stretch"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -26,7 +26,7 @@ export const CanvasEntityEnabledToggle = memo(() => {
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
icon={isEnabled ? <PiCheckBold /> : undefined}
|
icon={isEnabled ? <PiCheckBold /> : undefined}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
colorScheme="base"
|
colorScheme={isEnabled ? 'invokeBlue' : 'base'}
|
||||||
onDoubleClick={stopPropagation} // double click expands the layer
|
onDoubleClick={stopPropagation} // double click expands the layer
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -1,41 +1,25 @@
|
|||||||
import { Flex, Switch, Text } from '@invoke-ai/ui-library';
|
import { Flex, Spacer, Text } from '@invoke-ai/ui-library';
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { CanvasEntityTypeIsHiddenToggle } from 'features/controlLayers/components/common/CanvasEntityTypeIsHiddenToggle';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useEntityTypeTitle } from 'features/controlLayers/hooks/useEntityTypeTitle';
|
||||||
import {
|
|
||||||
allEntitiesOfTypeToggled,
|
|
||||||
selectAllEntitiesOfType,
|
|
||||||
selectCanvasV2Slice,
|
|
||||||
} from 'features/controlLayers/store/canvasV2Slice';
|
|
||||||
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
|
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
|
||||||
import type { PropsWithChildren } from 'react';
|
import type { PropsWithChildren } from 'react';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo } from 'react';
|
||||||
|
|
||||||
type Props = PropsWithChildren<{
|
type Props = PropsWithChildren<{
|
||||||
title: string;
|
|
||||||
isSelected: boolean;
|
isSelected: boolean;
|
||||||
type: CanvasEntityIdentifier['type'];
|
type: CanvasEntityIdentifier['type'];
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export const CanvasEntityGroupList = memo(({ title, isSelected, type, children }: Props) => {
|
export const CanvasEntityGroupList = memo(({ isSelected, type, children }: Props) => {
|
||||||
const dispatch = useAppDispatch();
|
const title = useEntityTypeTitle(type);
|
||||||
const selectAreAllEnabled = useMemo(
|
|
||||||
() =>
|
|
||||||
createSelector(selectCanvasV2Slice, (canvasV2) => {
|
|
||||||
return selectAllEntitiesOfType(canvasV2, type).every((entity) => entity.isEnabled);
|
|
||||||
}),
|
|
||||||
[type]
|
|
||||||
);
|
|
||||||
const areAllEnabled = useAppSelector(selectAreAllEnabled);
|
|
||||||
const onChange = useCallback(() => {
|
|
||||||
dispatch(allEntitiesOfTypeToggled({ type }));
|
|
||||||
}, [dispatch, type]);
|
|
||||||
return (
|
return (
|
||||||
<Flex flexDir="column" gap={2}>
|
<Flex flexDir="column" gap={2}>
|
||||||
<Flex justifyContent="space-between" alignItems="center">
|
<Flex justifyContent="space-between" alignItems="center" gap={3}>
|
||||||
<Text color={isSelected ? 'base.200' : 'base.500'} fontWeight="semibold" userSelect="none">
|
<Text color={isSelected ? 'base.200' : 'base.500'} fontWeight="semibold" userSelect="none">
|
||||||
{title}
|
{title}
|
||||||
</Text>
|
</Text>
|
||||||
<Switch size="sm" isChecked={areAllEnabled} onChange={onChange} pe={1} />
|
<Spacer />
|
||||||
|
{type !== 'ip_adapter' && <CanvasEntityTypeIsHiddenToggle type={type} />}
|
||||||
</Flex>
|
</Flex>
|
||||||
{children}
|
{children}
|
||||||
</Flex>
|
</Flex>
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
import { IconButton } from '@invoke-ai/ui-library';
|
||||||
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
|
import { useEntityTypeIsHidden } from 'features/controlLayers/hooks/useEntityTypeIsHidden';
|
||||||
|
import { useEntityTypeString } from 'features/controlLayers/hooks/useEntityTypeString';
|
||||||
|
import { allEntitiesOfTypeIsHiddenToggled } from 'features/controlLayers/store/canvasV2Slice';
|
||||||
|
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
|
||||||
|
import { memo, useCallback } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { PiEyeBold, PiEyeClosedBold } from 'react-icons/pi';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
type: CanvasEntityIdentifier['type'];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CanvasEntityTypeIsHiddenToggle = memo(({ type }: Props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const isHidden = useEntityTypeIsHidden(type);
|
||||||
|
const typeString = useEntityTypeString(type);
|
||||||
|
const onClick = useCallback(() => {
|
||||||
|
dispatch(allEntitiesOfTypeIsHiddenToggled({ type }));
|
||||||
|
}, [dispatch, type]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IconButton
|
||||||
|
size="sm"
|
||||||
|
aria-label={t(isHidden ? 'controlLayers.hidingType' : 'controlLayers.showingType', { type: typeString })}
|
||||||
|
tooltip={t(isHidden ? 'controlLayers.hidingType' : 'controlLayers.showingType', { type: typeString })}
|
||||||
|
variant="link"
|
||||||
|
icon={isHidden ? <PiEyeClosedBold /> : <PiEyeBold />}
|
||||||
|
onClick={onClick}
|
||||||
|
alignSelf="stretch"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
CanvasEntityTypeIsHiddenToggle.displayName = 'CanvasEntityTypeIsHiddenToggle';
|
@ -0,0 +1,30 @@
|
|||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice';
|
||||||
|
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
export const useEntityCount = (type: CanvasEntityIdentifier['type']): number => {
|
||||||
|
const selectEntityCount = useMemo(
|
||||||
|
() =>
|
||||||
|
createSelector(selectCanvasV2Slice, (canvasV2) => {
|
||||||
|
switch (type) {
|
||||||
|
case 'control_layer':
|
||||||
|
return canvasV2.controlLayers.entities.length;
|
||||||
|
case 'raster_layer':
|
||||||
|
return canvasV2.rasterLayers.entities.length;
|
||||||
|
case 'inpaint_mask':
|
||||||
|
return canvasV2.inpaintMasks.entities.length;
|
||||||
|
case 'regional_guidance':
|
||||||
|
return canvasV2.regions.entities.length;
|
||||||
|
case 'ip_adapter':
|
||||||
|
return canvasV2.ipAdapters.entities.length;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
[type]
|
||||||
|
);
|
||||||
|
const entityCount = useAppSelector(selectEntityCount);
|
||||||
|
return entityCount;
|
||||||
|
};
|
@ -29,15 +29,15 @@ export const useEntityTitle = (entityIdentifier: CanvasEntityIdentifier) => {
|
|||||||
|
|
||||||
const parts: string[] = [];
|
const parts: string[] = [];
|
||||||
if (entityIdentifier.type === 'inpaint_mask') {
|
if (entityIdentifier.type === 'inpaint_mask') {
|
||||||
parts.push(t('controlLayers.inpaintMask'));
|
parts.push(t('controlLayers.inpaintMask', { count: 1 }));
|
||||||
} else if (entityIdentifier.type === 'control_layer') {
|
} else if (entityIdentifier.type === 'control_layer') {
|
||||||
parts.push(t('controlLayers.controlLayer'));
|
parts.push(t('controlLayers.controlLayer', { count: 1 }));
|
||||||
} else if (entityIdentifier.type === 'raster_layer') {
|
} else if (entityIdentifier.type === 'raster_layer') {
|
||||||
parts.push(t('controlLayers.rasterLayer'));
|
parts.push(t('controlLayers.rasterLayer', { count: 1 }));
|
||||||
} else if (entityIdentifier.type === 'ip_adapter') {
|
} else if (entityIdentifier.type === 'ip_adapter') {
|
||||||
parts.push(t('common.ipAdapter'));
|
parts.push(t('common.ipAdapter', { count: 1 }));
|
||||||
} else if (entityIdentifier.type === 'regional_guidance') {
|
} else if (entityIdentifier.type === 'regional_guidance') {
|
||||||
parts.push(t('controlLayers.regionalGuidance'));
|
parts.push(t('controlLayers.regionalGuidance', { count: 1 }));
|
||||||
} else {
|
} else {
|
||||||
assert(false, 'Unexpected entity type');
|
assert(false, 'Unexpected entity type');
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice';
|
||||||
|
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
export const useEntityTypeIsHidden = (type: CanvasEntityIdentifier['type']): boolean => {
|
||||||
|
const selectIsHidden = useMemo(
|
||||||
|
() =>
|
||||||
|
createSelector(selectCanvasV2Slice, (canvasV2) => {
|
||||||
|
switch (type) {
|
||||||
|
case 'control_layer':
|
||||||
|
return canvasV2.controlLayers.isHidden;
|
||||||
|
case 'raster_layer':
|
||||||
|
return canvasV2.rasterLayers.isHidden;
|
||||||
|
case 'inpaint_mask':
|
||||||
|
return canvasV2.inpaintMasks.isHidden;
|
||||||
|
case 'regional_guidance':
|
||||||
|
return canvasV2.regions.isHidden;
|
||||||
|
case 'ip_adapter':
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
[type]
|
||||||
|
);
|
||||||
|
const isHidden = useAppSelector(selectIsHidden);
|
||||||
|
return isHidden;
|
||||||
|
};
|
@ -0,0 +1,26 @@
|
|||||||
|
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
export const useEntityTypeString = (type: CanvasEntityIdentifier['type']): string => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const typeString = useMemo(() => {
|
||||||
|
switch (type) {
|
||||||
|
case 'control_layer':
|
||||||
|
return t('controlLayers.controlLayer', { count: 0 });
|
||||||
|
case 'raster_layer':
|
||||||
|
return t('controlLayers.rasterLayer', { count: 0 });
|
||||||
|
case 'inpaint_mask':
|
||||||
|
return t('controlLayers.inpaintMask', { count: 0 });
|
||||||
|
case 'regional_guidance':
|
||||||
|
return t('controlLayers.regionalGuidance', { count: 0 });
|
||||||
|
case 'ip_adapter':
|
||||||
|
return t('controlLayers.ipAdapter', { count: 0 });
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}, [type, t]);
|
||||||
|
|
||||||
|
return typeString;
|
||||||
|
};
|
@ -0,0 +1,32 @@
|
|||||||
|
import { useEntityCount } from 'features/controlLayers/hooks/useEntityCount';
|
||||||
|
import { useEntityTypeIsHidden } from 'features/controlLayers/hooks/useEntityTypeIsHidden';
|
||||||
|
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
export const useEntityTypeTitle = (type: CanvasEntityIdentifier['type']): string => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const isHidden = useEntityTypeIsHidden(type);
|
||||||
|
const count = useEntityCount(type);
|
||||||
|
|
||||||
|
const title = useMemo(() => {
|
||||||
|
const context = isHidden ? 'hidden' : 'visible';
|
||||||
|
switch (type) {
|
||||||
|
case 'control_layer':
|
||||||
|
return t('controlLayers.controlLayers_withCount', { count, context });
|
||||||
|
case 'raster_layer':
|
||||||
|
return t('controlLayers.rasterLayers_withCount', { count, context });
|
||||||
|
case 'inpaint_mask':
|
||||||
|
return t('controlLayers.inpaintMasks_withCount', { count, context });
|
||||||
|
case 'regional_guidance':
|
||||||
|
return t('controlLayers.regionalGuidance_withCount', { count, context });
|
||||||
|
case 'ip_adapter':
|
||||||
|
return t('controlLayers.ipAdapters_withCount', { count, context });
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}, [type, t, count, isHidden]);
|
||||||
|
|
||||||
|
return title;
|
||||||
|
};
|
@ -319,6 +319,12 @@ export class CanvasManager {
|
|||||||
this.background.render();
|
this.background.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isFirstRender || state.rasterLayers.isHidden !== prevState.rasterLayers.isHidden) {
|
||||||
|
for (const adapter of this.rasterLayerAdapters.values()) {
|
||||||
|
adapter.renderer.updateOpacity(state.rasterLayers.isHidden ? 0 : adapter.state.opacity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (isFirstRender || state.rasterLayers.entities !== prevState.rasterLayers.entities) {
|
if (isFirstRender || state.rasterLayers.entities !== prevState.rasterLayers.entities) {
|
||||||
this.log.debug('Rendering raster layers');
|
this.log.debug('Rendering raster layers');
|
||||||
|
|
||||||
@ -344,6 +350,12 @@ export class CanvasManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isFirstRender || state.controlLayers.isHidden !== prevState.controlLayers.isHidden) {
|
||||||
|
for (const adapter of this.controlLayerAdapters.values()) {
|
||||||
|
adapter.renderer.updateOpacity(state.controlLayers.isHidden ? 0 : adapter.state.opacity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (isFirstRender || state.controlLayers.entities !== prevState.controlLayers.entities) {
|
if (isFirstRender || state.controlLayers.entities !== prevState.controlLayers.entities) {
|
||||||
this.log.debug('Rendering control layers');
|
this.log.debug('Rendering control layers');
|
||||||
|
|
||||||
@ -369,6 +381,12 @@ export class CanvasManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isFirstRender || state.regions.isHidden !== prevState.regions.isHidden) {
|
||||||
|
for (const adapter of this.regionalGuidanceAdapters.values()) {
|
||||||
|
adapter.renderer.updateOpacity(state.regions.isHidden ? 0 : adapter.state.opacity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
isFirstRender ||
|
isFirstRender ||
|
||||||
state.regions.entities !== prevState.regions.entities ||
|
state.regions.entities !== prevState.regions.entities ||
|
||||||
@ -400,6 +418,12 @@ export class CanvasManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isFirstRender || state.inpaintMasks.isHidden !== prevState.inpaintMasks.isHidden) {
|
||||||
|
for (const adapter of this.inpaintMaskAdapters.values()) {
|
||||||
|
adapter.renderer.updateOpacity(state.inpaintMasks.isHidden ? 0 : adapter.state.opacity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
isFirstRender ||
|
isFirstRender ||
|
||||||
state.inpaintMasks.entities !== prevState.inpaintMasks.entities ||
|
state.inpaintMasks.entities !== prevState.inpaintMasks.entities ||
|
||||||
@ -634,10 +658,10 @@ export class CanvasManager {
|
|||||||
this.log.warn({ id }, 'Raster layer adapter not found');
|
this.log.warn({ id }, 'Raster layer adapter not found');
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
this.log.trace({ id }, 'Drawing raster layer to composite canvas');
|
this.log.trace({ id }, 'Drawing raster layer to composite canvas');
|
||||||
const adapterCanvas = adapter.getCanvas(rect);
|
const adapterCanvas = adapter.getCanvas(rect);
|
||||||
ctx.drawImage(adapterCanvas, 0, 0);
|
ctx.drawImage(adapterCanvas, 0, 0);
|
||||||
}
|
}
|
||||||
this.canvasCache.set(hash, canvas);
|
this.canvasCache.set(hash, canvas);
|
||||||
return canvas;
|
return canvas;
|
||||||
};
|
};
|
||||||
@ -666,10 +690,10 @@ export class CanvasManager {
|
|||||||
this.log.warn({ id }, 'Inpaint mask adapter not found');
|
this.log.warn({ id }, 'Inpaint mask adapter not found');
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
this.log.trace({ id }, 'Drawing inpaint mask to composite canvas');
|
this.log.trace({ id }, 'Drawing inpaint mask to composite canvas');
|
||||||
const adapterCanvas = adapter.getCanvas(rect);
|
const adapterCanvas = adapter.getCanvas(rect);
|
||||||
ctx.drawImage(adapterCanvas, 0, 0);
|
ctx.drawImage(adapterCanvas, 0, 0);
|
||||||
}
|
}
|
||||||
this.canvasCache.set(hash, canvas);
|
this.canvasCache.set(hash, canvas);
|
||||||
return canvas;
|
return canvas;
|
||||||
};
|
};
|
||||||
|
@ -398,3 +398,13 @@ export const getRectUnion = (...rects: Rect[]): Rect => {
|
|||||||
}, getEmptyRect());
|
}, getEmptyRect());
|
||||||
return rect;
|
return rect;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that the value is never reached. Used for exhaustive checks in switch statements or conditional logic to ensure
|
||||||
|
* that all possible values are handled.
|
||||||
|
* @param value The value that should never be reached
|
||||||
|
* @throws An error with the value that was not handled
|
||||||
|
*/
|
||||||
|
export const exhaustiveCheck = (value: never): never => {
|
||||||
|
assert(false, `Unhandled value: ${value}`);
|
||||||
|
};
|
||||||
|
@ -3,6 +3,7 @@ import { createAction, createSlice } from '@reduxjs/toolkit';
|
|||||||
import type { PersistConfig, RootState } from 'app/store/store';
|
import type { PersistConfig, RootState } from 'app/store/store';
|
||||||
import { moveOneToEnd, moveOneToStart, moveToEnd, moveToStart } from 'common/util/arrayUtils';
|
import { moveOneToEnd, moveOneToStart, moveToEnd, moveToStart } from 'common/util/arrayUtils';
|
||||||
import { deepClone } from 'common/util/deepClone';
|
import { deepClone } from 'common/util/deepClone';
|
||||||
|
import { exhaustiveCheck } from 'features/controlLayers/konva/util';
|
||||||
import { bboxReducers } from 'features/controlLayers/store/bboxReducers';
|
import { bboxReducers } from 'features/controlLayers/store/bboxReducers';
|
||||||
import { compositingReducers } from 'features/controlLayers/store/compositingReducers';
|
import { compositingReducers } from 'features/controlLayers/store/compositingReducers';
|
||||||
import { controlLayersReducers } from 'features/controlLayers/store/controlLayersReducers';
|
import { controlLayersReducers } from 'features/controlLayers/store/controlLayersReducers';
|
||||||
@ -24,12 +25,8 @@ import { atom } from 'nanostores';
|
|||||||
import { assert } from 'tsafe';
|
import { assert } from 'tsafe';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
CanvasControlLayerState,
|
|
||||||
CanvasEntityIdentifier,
|
CanvasEntityIdentifier,
|
||||||
CanvasEntityState,
|
CanvasEntityState,
|
||||||
CanvasInpaintMaskState,
|
|
||||||
CanvasRasterLayerState,
|
|
||||||
CanvasRegionalGuidanceState,
|
|
||||||
CanvasV2State,
|
CanvasV2State,
|
||||||
Coordinate,
|
Coordinate,
|
||||||
EntityBrushLineAddedPayload,
|
EntityBrushLineAddedPayload,
|
||||||
@ -46,15 +43,19 @@ const initialState: CanvasV2State = {
|
|||||||
_version: 3,
|
_version: 3,
|
||||||
selectedEntityIdentifier: null,
|
selectedEntityIdentifier: null,
|
||||||
rasterLayers: {
|
rasterLayers: {
|
||||||
|
isHidden: false,
|
||||||
entities: [],
|
entities: [],
|
||||||
},
|
},
|
||||||
controlLayers: {
|
controlLayers: {
|
||||||
|
isHidden: false,
|
||||||
entities: [],
|
entities: [],
|
||||||
},
|
},
|
||||||
inpaintMasks: {
|
inpaintMasks: {
|
||||||
|
isHidden: false,
|
||||||
entities: [],
|
entities: [],
|
||||||
},
|
},
|
||||||
regions: {
|
regions: {
|
||||||
|
isHidden: false,
|
||||||
entities: [],
|
entities: [],
|
||||||
},
|
},
|
||||||
loras: [],
|
loras: [],
|
||||||
@ -408,35 +409,28 @@ export const canvasV2Slice = createSlice({
|
|||||||
}
|
}
|
||||||
entity.opacity = opacity;
|
entity.opacity = opacity;
|
||||||
},
|
},
|
||||||
allEntitiesOfTypeToggled: (state, action: PayloadAction<{ type: CanvasEntityIdentifier['type'] }>) => {
|
allEntitiesOfTypeIsHiddenToggled: (state, action: PayloadAction<{ type: CanvasEntityIdentifier['type'] }>) => {
|
||||||
const { type } = action.payload;
|
const { type } = action.payload;
|
||||||
let entities: (
|
|
||||||
| CanvasRasterLayerState
|
|
||||||
| CanvasControlLayerState
|
|
||||||
| CanvasInpaintMaskState
|
|
||||||
| CanvasRegionalGuidanceState
|
|
||||||
)[];
|
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'raster_layer':
|
case 'raster_layer':
|
||||||
entities = state.rasterLayers.entities;
|
state.rasterLayers.isHidden = !state.rasterLayers.isHidden;
|
||||||
break;
|
break;
|
||||||
case 'control_layer':
|
case 'control_layer':
|
||||||
entities = state.controlLayers.entities;
|
state.controlLayers.isHidden = !state.controlLayers.isHidden;
|
||||||
break;
|
break;
|
||||||
case 'inpaint_mask':
|
case 'inpaint_mask':
|
||||||
entities = state.inpaintMasks.entities;
|
state.inpaintMasks.isHidden = !state.inpaintMasks.isHidden;
|
||||||
break;
|
break;
|
||||||
case 'regional_guidance':
|
case 'regional_guidance':
|
||||||
entities = state.regions.entities;
|
state.regions.isHidden = !state.regions.isHidden;
|
||||||
break;
|
break;
|
||||||
default:
|
case 'ip_adapter':
|
||||||
assert(false, 'Not implemented');
|
// no-op
|
||||||
}
|
break;
|
||||||
|
default: {
|
||||||
const allEnabled = entities.every((entity) => entity.isEnabled);
|
exhaustiveCheck(type);
|
||||||
for (const entity of entities) {
|
}
|
||||||
entity.isEnabled = !allEnabled;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
allEntitiesDeleted: (state) => {
|
allEntitiesDeleted: (state) => {
|
||||||
@ -496,7 +490,7 @@ export const {
|
|||||||
entityArrangedBackwardOne,
|
entityArrangedBackwardOne,
|
||||||
entityArrangedToBack,
|
entityArrangedToBack,
|
||||||
entityOpacityChanged,
|
entityOpacityChanged,
|
||||||
allEntitiesOfTypeToggled,
|
allEntitiesOfTypeIsHiddenToggled,
|
||||||
// bbox
|
// bbox
|
||||||
bboxChanged,
|
bboxChanged,
|
||||||
bboxScaledSizeChanged,
|
bboxScaledSizeChanged,
|
||||||
|
@ -843,15 +843,19 @@ export type CanvasV2State = {
|
|||||||
_version: 3;
|
_version: 3;
|
||||||
selectedEntityIdentifier: CanvasEntityIdentifier | null;
|
selectedEntityIdentifier: CanvasEntityIdentifier | null;
|
||||||
inpaintMasks: {
|
inpaintMasks: {
|
||||||
|
isHidden: boolean;
|
||||||
entities: CanvasInpaintMaskState[];
|
entities: CanvasInpaintMaskState[];
|
||||||
};
|
};
|
||||||
rasterLayers: {
|
rasterLayers: {
|
||||||
|
isHidden: boolean;
|
||||||
entities: CanvasRasterLayerState[];
|
entities: CanvasRasterLayerState[];
|
||||||
};
|
};
|
||||||
controlLayers: {
|
controlLayers: {
|
||||||
|
isHidden: boolean;
|
||||||
entities: CanvasControlLayerState[];
|
entities: CanvasControlLayerState[];
|
||||||
};
|
};
|
||||||
regions: {
|
regions: {
|
||||||
|
isHidden: boolean;
|
||||||
entities: CanvasRegionalGuidanceState[];
|
entities: CanvasRegionalGuidanceState[];
|
||||||
};
|
};
|
||||||
ipAdapters: {
|
ipAdapters: {
|
||||||
|
Loading…
Reference in New Issue
Block a user