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)",
|
||||
"addNegativePrompt": "Add $t(common.negativePrompt)",
|
||||
"addIPAdapter": "Add $t(common.ipAdapter)",
|
||||
"regionalGuidance": "Regional Guidance",
|
||||
"regionalGuidanceLayer": "$t(controlLayers.regionalGuidance) $t(unifiedCanvas.layer)",
|
||||
"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",
|
||||
"regionalGuidance_withCount": "Regional Guidance ({{count}})",
|
||||
"controlAdapters_withCount": "Control Adapters ({{count}})",
|
||||
"controlLayer": "Control Layer",
|
||||
"controlLayers_withCount": "Control Layers ({{count}})",
|
||||
"rasterLayers_withCount": "Raster Layers ({{count}})",
|
||||
"ipAdapters_withCount": "IP Adapters ({{count}})",
|
||||
"inpaintMasks_withCount": "Inpaint Masks ({{count}})",
|
||||
"regionalGuidance_withCount_hidden": "Regional Guidance ({{count}} hidden)",
|
||||
"controlAdapters_withCount_hidden": "Control Adapters ({{count}} hidden)",
|
||||
"controlLayers_withCount_hidden": "Control Layers ({{count}} hidden)",
|
||||
"rasterLayers_withCount_hidden": "Raster Layers ({{count}} hidden)",
|
||||
"ipAdapters_withCount_hidden": "IP Adapters ({{count}} hidden)",
|
||||
"inpaintMasks_withCount_hidden": "Inpaint Masks ({{count}} hidden)",
|
||||
"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)",
|
||||
"globalControlAdapterLayer": "Global $t(controlnet.controlAdapter_one) $t(unifiedCanvas.layer)",
|
||||
"globalIPAdapter": "Global $t(common.ipAdapter)",
|
||||
"globalIPAdapterLayer": "Global $t(common.ipAdapter) $t(unifiedCanvas.layer)",
|
||||
"globalInitialImage": "Global Initial Image",
|
||||
"globalInitialImageLayer": "$t(controlLayers.globalInitialImage) $t(unifiedCanvas.layer)",
|
||||
"inpaintMask": "Inpaint Mask",
|
||||
"layer": "Layer",
|
||||
"opacityFilter": "Opacity Filter",
|
||||
"clearProcessor": "Clear Processor",
|
||||
@ -1699,6 +1711,8 @@
|
||||
"convertToRasterLayer": "Convert to Raster Layer",
|
||||
"enableTransparencyEffect": "Enable Transparency Effect",
|
||||
"disableTransparencyEffect": "Disable Transparency Effect",
|
||||
"hidingType": "Hiding {{type}}",
|
||||
"showingType": "Showing {{type}}",
|
||||
"fill": {
|
||||
"fillStyle": "Fill Style",
|
||||
"solid": "Solid",
|
||||
|
@ -5,14 +5,12 @@ import { ControlLayer } from 'features/controlLayers/components/ControlLayer/Con
|
||||
import { mapId } from 'features/controlLayers/konva/util';
|
||||
import { selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
|
||||
return canvasV2.controlLayers.entities.map(mapId).reverse();
|
||||
});
|
||||
|
||||
export const ControlLayerEntityList = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const isSelected = useAppSelector((s) => Boolean(s.canvasV2.selectedEntityIdentifier?.type === 'control_layer'));
|
||||
const layerIds = useAppSelector(selectEntityIds);
|
||||
|
||||
@ -22,11 +20,7 @@ export const ControlLayerEntityList = memo(() => {
|
||||
|
||||
if (layerIds.length > 0) {
|
||||
return (
|
||||
<CanvasEntityGroupList
|
||||
type="control_layer"
|
||||
title={t('controlLayers.controlLayers_withCount', { count: layerIds.length })}
|
||||
isSelected={isSelected}
|
||||
>
|
||||
<CanvasEntityGroupList type="control_layer" isSelected={isSelected}>
|
||||
{layerIds.map((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 { selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
|
||||
return canvasV2.ipAdapters.entities.map(mapId).reverse();
|
||||
});
|
||||
|
||||
export const IPAdapterList = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const isSelected = useAppSelector((s) => Boolean(s.canvasV2.selectedEntityIdentifier?.type === 'ip_adapter'));
|
||||
const ipaIds = useAppSelector(selectEntityIds);
|
||||
|
||||
@ -23,11 +21,7 @@ export const IPAdapterList = memo(() => {
|
||||
|
||||
if (ipaIds.length > 0) {
|
||||
return (
|
||||
<CanvasEntityGroupList
|
||||
type="ip_adapter"
|
||||
title={t('controlLayers.ipAdapters_withCount', { count: ipaIds.length })}
|
||||
isSelected={isSelected}
|
||||
>
|
||||
<CanvasEntityGroupList type="ip_adapter" isSelected={isSelected}>
|
||||
{ipaIds.map((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 { selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
|
||||
return canvasV2.inpaintMasks.entities.map(mapId).reverse();
|
||||
});
|
||||
|
||||
export const InpaintMaskList = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const isSelected = useAppSelector((s) => Boolean(s.canvasV2.selectedEntityIdentifier?.type === 'inpaint_mask'));
|
||||
const entityIds = useAppSelector(selectEntityIds);
|
||||
|
||||
@ -22,11 +20,7 @@ export const InpaintMaskList = memo(() => {
|
||||
|
||||
if (entityIds.length > 0) {
|
||||
return (
|
||||
<CanvasEntityGroupList
|
||||
type="inpaint_mask"
|
||||
title={t('controlLayers.inpaintMasks_withCount', { count: entityIds.length })}
|
||||
isSelected={isSelected}
|
||||
>
|
||||
<CanvasEntityGroupList type="inpaint_mask" isSelected={isSelected}>
|
||||
{entityIds.map((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 { selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
|
||||
return canvasV2.rasterLayers.entities.map(mapId).reverse();
|
||||
});
|
||||
|
||||
export const RasterLayerEntityList = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const isSelected = useAppSelector((s) => Boolean(s.canvasV2.selectedEntityIdentifier?.type === 'raster_layer'));
|
||||
const layerIds = useAppSelector(selectEntityIds);
|
||||
|
||||
@ -22,11 +20,7 @@ export const RasterLayerEntityList = memo(() => {
|
||||
|
||||
if (layerIds.length > 0) {
|
||||
return (
|
||||
<CanvasEntityGroupList
|
||||
type="raster_layer"
|
||||
title={t('controlLayers.rasterLayers_withCount', { count: layerIds.length })}
|
||||
isSelected={isSelected}
|
||||
>
|
||||
<CanvasEntityGroupList type="raster_layer" isSelected={isSelected}>
|
||||
{layerIds.map((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 { selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
|
||||
return canvasV2.regions.entities.map(mapId).reverse();
|
||||
});
|
||||
|
||||
export const RegionalGuidanceEntityList = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const isSelected = useAppSelector((s) => Boolean(s.canvasV2.selectedEntityIdentifier?.type === 'regional_guidance'));
|
||||
const rgIds = useAppSelector(selectEntityIds);
|
||||
|
||||
@ -22,11 +20,7 @@ export const RegionalGuidanceEntityList = memo(() => {
|
||||
|
||||
if (rgIds.length > 0) {
|
||||
return (
|
||||
<CanvasEntityGroupList
|
||||
type="regional_guidance"
|
||||
title={t('controlLayers.regionalGuidance_withCount', { count: rgIds.length })}
|
||||
isSelected={isSelected}
|
||||
>
|
||||
<CanvasEntityGroupList type="regional_guidance" isSelected={isSelected}>
|
||||
{rgIds.map((id) => (
|
||||
<RegionalGuidance key={id} id={id} />
|
||||
))}
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { IconButton } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { stopPropagation } from 'common/util/stopPropagation';
|
||||
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||
import { entityDeleted } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { memo, useCallback } from 'react';
|
||||
@ -22,7 +21,8 @@ export const CanvasEntityDeleteButton = memo(() => {
|
||||
tooltip={t('common.delete')}
|
||||
icon={<PiTrashSimpleBold />}
|
||||
onClick={onClick}
|
||||
onDoubleClick={stopPropagation} // double click expands the layer
|
||||
variant="link"
|
||||
alignSelf="stretch"
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
@ -26,7 +26,7 @@ export const CanvasEntityEnabledToggle = memo(() => {
|
||||
variant="outline"
|
||||
icon={isEnabled ? <PiCheckBold /> : undefined}
|
||||
onClick={onClick}
|
||||
colorScheme="base"
|
||||
colorScheme={isEnabled ? 'invokeBlue' : 'base'}
|
||||
onDoubleClick={stopPropagation} // double click expands the layer
|
||||
/>
|
||||
);
|
||||
|
@ -1,41 +1,25 @@
|
||||
import { Flex, Switch, Text } from '@invoke-ai/ui-library';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import {
|
||||
allEntitiesOfTypeToggled,
|
||||
selectAllEntitiesOfType,
|
||||
selectCanvasV2Slice,
|
||||
} from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { Flex, Spacer, Text } from '@invoke-ai/ui-library';
|
||||
import { CanvasEntityTypeIsHiddenToggle } from 'features/controlLayers/components/common/CanvasEntityTypeIsHiddenToggle';
|
||||
import { useEntityTypeTitle } from 'features/controlLayers/hooks/useEntityTypeTitle';
|
||||
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { memo } from 'react';
|
||||
|
||||
type Props = PropsWithChildren<{
|
||||
title: string;
|
||||
isSelected: boolean;
|
||||
type: CanvasEntityIdentifier['type'];
|
||||
}>;
|
||||
|
||||
export const CanvasEntityGroupList = memo(({ title, isSelected, type, children }: Props) => {
|
||||
const dispatch = useAppDispatch();
|
||||
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]);
|
||||
export const CanvasEntityGroupList = memo(({ isSelected, type, children }: Props) => {
|
||||
const title = useEntityTypeTitle(type);
|
||||
return (
|
||||
<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">
|
||||
{title}
|
||||
</Text>
|
||||
<Switch size="sm" isChecked={areAllEnabled} onChange={onChange} pe={1} />
|
||||
<Spacer />
|
||||
{type !== 'ip_adapter' && <CanvasEntityTypeIsHiddenToggle type={type} />}
|
||||
</Flex>
|
||||
{children}
|
||||
</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[] = [];
|
||||
if (entityIdentifier.type === 'inpaint_mask') {
|
||||
parts.push(t('controlLayers.inpaintMask'));
|
||||
parts.push(t('controlLayers.inpaintMask', { count: 1 }));
|
||||
} else if (entityIdentifier.type === 'control_layer') {
|
||||
parts.push(t('controlLayers.controlLayer'));
|
||||
parts.push(t('controlLayers.controlLayer', { count: 1 }));
|
||||
} else if (entityIdentifier.type === 'raster_layer') {
|
||||
parts.push(t('controlLayers.rasterLayer'));
|
||||
parts.push(t('controlLayers.rasterLayer', { count: 1 }));
|
||||
} else if (entityIdentifier.type === 'ip_adapter') {
|
||||
parts.push(t('common.ipAdapter'));
|
||||
parts.push(t('common.ipAdapter', { count: 1 }));
|
||||
} else if (entityIdentifier.type === 'regional_guidance') {
|
||||
parts.push(t('controlLayers.regionalGuidance'));
|
||||
parts.push(t('controlLayers.regionalGuidance', { count: 1 }));
|
||||
} else {
|
||||
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();
|
||||
}
|
||||
|
||||
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) {
|
||||
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) {
|
||||
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 (
|
||||
isFirstRender ||
|
||||
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 (
|
||||
isFirstRender ||
|
||||
state.inpaintMasks.entities !== prevState.inpaintMasks.entities ||
|
||||
@ -634,10 +658,10 @@ export class CanvasManager {
|
||||
this.log.warn({ id }, 'Raster layer adapter not found');
|
||||
continue;
|
||||
}
|
||||
this.log.trace({ id }, 'Drawing raster layer to composite canvas');
|
||||
const adapterCanvas = adapter.getCanvas(rect);
|
||||
ctx.drawImage(adapterCanvas, 0, 0);
|
||||
}
|
||||
this.log.trace({ id }, 'Drawing raster layer to composite canvas');
|
||||
const adapterCanvas = adapter.getCanvas(rect);
|
||||
ctx.drawImage(adapterCanvas, 0, 0);
|
||||
}
|
||||
this.canvasCache.set(hash, canvas);
|
||||
return canvas;
|
||||
};
|
||||
@ -666,10 +690,10 @@ export class CanvasManager {
|
||||
this.log.warn({ id }, 'Inpaint mask adapter not found');
|
||||
continue;
|
||||
}
|
||||
this.log.trace({ id }, 'Drawing inpaint mask to composite canvas');
|
||||
const adapterCanvas = adapter.getCanvas(rect);
|
||||
ctx.drawImage(adapterCanvas, 0, 0);
|
||||
}
|
||||
this.log.trace({ id }, 'Drawing inpaint mask to composite canvas');
|
||||
const adapterCanvas = adapter.getCanvas(rect);
|
||||
ctx.drawImage(adapterCanvas, 0, 0);
|
||||
}
|
||||
this.canvasCache.set(hash, canvas);
|
||||
return canvas;
|
||||
};
|
||||
|
@ -398,3 +398,13 @@ export const getRectUnion = (...rects: Rect[]): Rect => {
|
||||
}, getEmptyRect());
|
||||
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 { moveOneToEnd, moveOneToStart, moveToEnd, moveToStart } from 'common/util/arrayUtils';
|
||||
import { deepClone } from 'common/util/deepClone';
|
||||
import { exhaustiveCheck } from 'features/controlLayers/konva/util';
|
||||
import { bboxReducers } from 'features/controlLayers/store/bboxReducers';
|
||||
import { compositingReducers } from 'features/controlLayers/store/compositingReducers';
|
||||
import { controlLayersReducers } from 'features/controlLayers/store/controlLayersReducers';
|
||||
@ -24,12 +25,8 @@ import { atom } from 'nanostores';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
import type {
|
||||
CanvasControlLayerState,
|
||||
CanvasEntityIdentifier,
|
||||
CanvasEntityState,
|
||||
CanvasInpaintMaskState,
|
||||
CanvasRasterLayerState,
|
||||
CanvasRegionalGuidanceState,
|
||||
CanvasV2State,
|
||||
Coordinate,
|
||||
EntityBrushLineAddedPayload,
|
||||
@ -46,15 +43,19 @@ const initialState: CanvasV2State = {
|
||||
_version: 3,
|
||||
selectedEntityIdentifier: null,
|
||||
rasterLayers: {
|
||||
isHidden: false,
|
||||
entities: [],
|
||||
},
|
||||
controlLayers: {
|
||||
isHidden: false,
|
||||
entities: [],
|
||||
},
|
||||
inpaintMasks: {
|
||||
isHidden: false,
|
||||
entities: [],
|
||||
},
|
||||
regions: {
|
||||
isHidden: false,
|
||||
entities: [],
|
||||
},
|
||||
loras: [],
|
||||
@ -408,35 +409,28 @@ export const canvasV2Slice = createSlice({
|
||||
}
|
||||
entity.opacity = opacity;
|
||||
},
|
||||
allEntitiesOfTypeToggled: (state, action: PayloadAction<{ type: CanvasEntityIdentifier['type'] }>) => {
|
||||
allEntitiesOfTypeIsHiddenToggled: (state, action: PayloadAction<{ type: CanvasEntityIdentifier['type'] }>) => {
|
||||
const { type } = action.payload;
|
||||
let entities: (
|
||||
| CanvasRasterLayerState
|
||||
| CanvasControlLayerState
|
||||
| CanvasInpaintMaskState
|
||||
| CanvasRegionalGuidanceState
|
||||
)[];
|
||||
|
||||
switch (type) {
|
||||
case 'raster_layer':
|
||||
entities = state.rasterLayers.entities;
|
||||
state.rasterLayers.isHidden = !state.rasterLayers.isHidden;
|
||||
break;
|
||||
case 'control_layer':
|
||||
entities = state.controlLayers.entities;
|
||||
state.controlLayers.isHidden = !state.controlLayers.isHidden;
|
||||
break;
|
||||
case 'inpaint_mask':
|
||||
entities = state.inpaintMasks.entities;
|
||||
state.inpaintMasks.isHidden = !state.inpaintMasks.isHidden;
|
||||
break;
|
||||
case 'regional_guidance':
|
||||
entities = state.regions.entities;
|
||||
state.regions.isHidden = !state.regions.isHidden;
|
||||
break;
|
||||
default:
|
||||
assert(false, 'Not implemented');
|
||||
}
|
||||
|
||||
const allEnabled = entities.every((entity) => entity.isEnabled);
|
||||
for (const entity of entities) {
|
||||
entity.isEnabled = !allEnabled;
|
||||
case 'ip_adapter':
|
||||
// no-op
|
||||
break;
|
||||
default: {
|
||||
exhaustiveCheck(type);
|
||||
}
|
||||
}
|
||||
},
|
||||
allEntitiesDeleted: (state) => {
|
||||
@ -496,7 +490,7 @@ export const {
|
||||
entityArrangedBackwardOne,
|
||||
entityArrangedToBack,
|
||||
entityOpacityChanged,
|
||||
allEntitiesOfTypeToggled,
|
||||
allEntitiesOfTypeIsHiddenToggled,
|
||||
// bbox
|
||||
bboxChanged,
|
||||
bboxScaledSizeChanged,
|
||||
|
@ -843,15 +843,19 @@ export type CanvasV2State = {
|
||||
_version: 3;
|
||||
selectedEntityIdentifier: CanvasEntityIdentifier | null;
|
||||
inpaintMasks: {
|
||||
isHidden: boolean;
|
||||
entities: CanvasInpaintMaskState[];
|
||||
};
|
||||
rasterLayers: {
|
||||
isHidden: boolean;
|
||||
entities: CanvasRasterLayerState[];
|
||||
};
|
||||
controlLayers: {
|
||||
isHidden: boolean;
|
||||
entities: CanvasControlLayerState[];
|
||||
};
|
||||
regions: {
|
||||
isHidden: boolean;
|
||||
entities: CanvasRegionalGuidanceState[];
|
||||
};
|
||||
ipAdapters: {
|
||||
|
Loading…
Reference in New Issue
Block a user