feat(ui): normalize all actions to accept an entityIdentifier

Previously, canvas actions specific to an entity type only needed the id of that entity type. This allowed you to pass in the id of an entity of the wrong type.

All actions for a specific entity now take a full entity identifier, and the entity identifier type can be narrowed.

`selectEntity` and `selectEntityOrThrow` now need a full entity identifier, and narrow their return values to a specific entity type _if_ the entity identifier is narrowed.

The types for canvas entities are updated with optional type parameters for this purpose.

All reducers, actions and components have been updated.
This commit is contained in:
psychedelicious 2024-08-26 18:52:28 +10:00
parent f86b50d18a
commit a3179e7a3f
60 changed files with 588 additions and 430 deletions

View File

@ -2,6 +2,7 @@ import { logger } from 'app/logging/logger';
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
import type { AppDispatch, RootState } from 'app/store/store'; import type { AppDispatch, RootState } from 'app/store/store';
import { entityDeleted, ipaImageChanged } from 'features/controlLayers/store/canvasV2Slice'; import { entityDeleted, ipaImageChanged } from 'features/controlLayers/store/canvasV2Slice';
import { getEntityIdentifier } from 'features/controlLayers/store/types';
import { imageDeletionConfirmed } from 'features/deleteImageModal/store/actions'; import { imageDeletionConfirmed } from 'features/deleteImageModal/store/actions';
import { isModalOpenChanged } from 'features/deleteImageModal/store/slice'; import { isModalOpenChanged } from 'features/deleteImageModal/store/slice';
import { selectListImagesQueryArgs } from 'features/gallery/store/gallerySelectors'; import { selectListImagesQueryArgs } from 'features/gallery/store/gallerySelectors';
@ -51,9 +52,9 @@ const deleteNodesImages = (state: RootState, dispatch: AppDispatch, imageDTO: Im
// }; // };
const deleteIPAdapterImages = (state: RootState, dispatch: AppDispatch, imageDTO: ImageDTO) => { const deleteIPAdapterImages = (state: RootState, dispatch: AppDispatch, imageDTO: ImageDTO) => {
state.canvasV2.ipAdapters.entities.forEach(({ id, ipAdapter }) => { state.canvasV2.ipAdapters.entities.forEach((entity) => {
if (ipAdapter.image?.image_name === imageDTO.image_name) { if (entity.ipAdapter.image?.image_name === imageDTO.image_name) {
dispatch(ipaImageChanged({ id, imageDTO: null })); dispatch(ipaImageChanged({ entityIdentifier: getEntityIdentifier(entity), imageDTO: null }));
} }
}); });
}; };

View File

@ -51,7 +51,9 @@ export const addImageDroppedListener = (startAppListening: AppStartListening) =>
activeData.payload.imageDTO activeData.payload.imageDTO
) { ) {
const { id } = overData.context; const { id } = overData.context;
dispatch(ipaImageChanged({ id, imageDTO: activeData.payload.imageDTO })); dispatch(
ipaImageChanged({ entityIdentifier: { id, type: 'ip_adapter' }, imageDTO: activeData.payload.imageDTO })
);
return; return;
} }
@ -64,7 +66,13 @@ export const addImageDroppedListener = (startAppListening: AppStartListening) =>
activeData.payload.imageDTO activeData.payload.imageDTO
) { ) {
const { id, ipAdapterId } = overData.context; const { id, ipAdapterId } = overData.context;
dispatch(rgIPAdapterImageChanged({ id, ipAdapterId, imageDTO: activeData.payload.imageDTO })); dispatch(
rgIPAdapterImageChanged({
entityIdentifier: { id, type: 'regional_guidance' },
ipAdapterId,
imageDTO: activeData.payload.imageDTO,
})
);
return; return;
} }

View File

@ -89,14 +89,16 @@ export const addImageUploadedFulfilledListener = (startAppListening: AppStartLis
if (postUploadAction?.type === 'SET_IPA_IMAGE') { if (postUploadAction?.type === 'SET_IPA_IMAGE') {
const { id } = postUploadAction; const { id } = postUploadAction;
dispatch(ipaImageChanged({ id, imageDTO })); dispatch(ipaImageChanged({ entityIdentifier: { id, type: 'ip_adapter' }, imageDTO }));
toast({ ...DEFAULT_UPLOADED_TOAST, description: t('toast.setControlImage') }); toast({ ...DEFAULT_UPLOADED_TOAST, description: t('toast.setControlImage') });
return; return;
} }
if (postUploadAction?.type === 'SET_RG_IP_ADAPTER_IMAGE') { if (postUploadAction?.type === 'SET_RG_IP_ADAPTER_IMAGE') {
const { id, ipAdapterId } = postUploadAction; const { id, ipAdapterId } = postUploadAction;
dispatch(rgIPAdapterImageChanged({ id, ipAdapterId, imageDTO })); dispatch(
rgIPAdapterImageChanged({ entityIdentifier: { id, type: 'regional_guidance' }, ipAdapterId, imageDTO })
);
toast({ ...DEFAULT_UPLOADED_TOAST, description: t('toast.setControlImage') }); toast({ ...DEFAULT_UPLOADED_TOAST, description: t('toast.setControlImage') });
return; return;
} }

View File

@ -13,6 +13,7 @@ import {
rgIPAdapterModelChanged, rgIPAdapterModelChanged,
vaeSelected, vaeSelected,
} from 'features/controlLayers/store/canvasV2Slice'; } from 'features/controlLayers/store/canvasV2Slice';
import { getEntityIdentifier } from 'features/controlLayers/store/types';
import { calculateNewSize } from 'features/parameters/components/DocumentSize/calculateNewSize'; import { calculateNewSize } from 'features/parameters/components/DocumentSize/calculateNewSize';
import { postProcessingModelChanged, upscaleModelChanged } from 'features/parameters/store/upscaleSlice'; import { postProcessingModelChanged, upscaleModelChanged } from 'features/parameters/store/upscaleSlice';
import { zParameterModel, zParameterVAEModel } from 'features/parameters/types/parameterSchemas'; import { zParameterModel, zParameterVAEModel } from 'features/parameters/types/parameterSchemas';
@ -178,7 +179,7 @@ const handleControlAdapterModels: ModelHandler = (models, state, dispatch, _log)
if (isModelAvailable) { if (isModelAvailable) {
return; return;
} }
dispatch(controlLayerModelChanged({ id: entity.id, modelConfig: null })); dispatch(controlLayerModelChanged({ entityIdentifier: getEntityIdentifier(entity), modelConfig: null }));
}); });
}; };
@ -189,16 +190,18 @@ const handleIPAdapterModels: ModelHandler = (models, state, dispatch, _log) => {
if (isModelAvailable) { if (isModelAvailable) {
return; return;
} }
dispatch(ipaModelChanged({ id: entity.id, modelConfig: null })); dispatch(ipaModelChanged({ entityIdentifier: getEntityIdentifier(entity), modelConfig: null }));
}); });
state.canvasV2.regions.entities.forEach(({ id, ipAdapters }) => { state.canvasV2.regions.entities.forEach((entity) => {
ipAdapters.forEach(({ id: ipAdapterId, model }) => { entity.ipAdapters.forEach(({ id: ipAdapterId, model }) => {
const isModelAvailable = ipaModels.some((m) => m.key === model?.key); const isModelAvailable = ipaModels.some((m) => m.key === model?.key);
if (isModelAvailable) { if (isModelAvailable) {
return; return;
} }
dispatch(rgIPAdapterModelChanged({ id, ipAdapterId, modelConfig: null })); dispatch(
rgIPAdapterModelChanged({ entityIdentifier: getEntityIdentifier(entity), ipAdapterId, modelConfig: null })
);
}); });
}); });
}; };

View File

@ -2,7 +2,7 @@ import { useStore } from '@nanostores/react';
import { $isConnected } from 'app/hooks/useSocketIO'; import { $isConnected } from 'app/hooks/useSocketIO';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice'; import { selectCanvasV2Slice } from 'features/controlLayers/store/selectors';
import { selectDynamicPromptsSlice } from 'features/dynamicPrompts/store/dynamicPromptsSlice'; import { selectDynamicPromptsSlice } from 'features/dynamicPrompts/store/dynamicPromptsSlice';
import { getShouldProcessPrompt } from 'features/dynamicPrompts/util/getShouldProcessPrompt'; import { getShouldProcessPrompt } from 'features/dynamicPrompts/util/getShouldProcessPrompt';
import { $templates, selectNodesSlice } from 'features/nodes/store/nodesSlice'; import { $templates, selectNodesSlice } from 'features/nodes/store/nodesSlice';

View File

@ -1,15 +1,15 @@
import { Badge } from '@invoke-ai/ui-library'; import { Badge } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext'; import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import { selectControlLayerEntityOrThrow } from 'features/controlLayers/store/controlLayersReducers'; import { selectEntityOrThrow } from 'features/controlLayers/store/selectors';
import { memo } from 'react'; import { memo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
export const ControlLayerBadges = memo(() => { export const ControlLayerBadges = memo(() => {
const { id } = useEntityIdentifierContext(); const entityIdentifier = useEntityIdentifierContext('control_layer');
const { t } = useTranslation(); const { t } = useTranslation();
const withTransparencyEffect = useAppSelector( const withTransparencyEffect = useAppSelector(
(s) => selectControlLayerEntityOrThrow(s.canvasV2, id).withTransparencyEffect (s) => selectEntityOrThrow(s.canvasV2, entityIdentifier).withTransparencyEffect
); );
return ( return (

View File

@ -18,35 +18,35 @@ import type { ControlNetModelConfig, T2IAdapterModelConfig } from 'services/api/
export const ControlLayerControlAdapter = memo(() => { export const ControlLayerControlAdapter = memo(() => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const entityIdentifier = useEntityIdentifierContext(); const entityIdentifier = useEntityIdentifierContext('control_layer');
const controlAdapter = useControlLayerControlAdapter(entityIdentifier); const controlAdapter = useControlLayerControlAdapter(entityIdentifier);
const onChangeBeginEndStepPct = useCallback( const onChangeBeginEndStepPct = useCallback(
(beginEndStepPct: [number, number]) => { (beginEndStepPct: [number, number]) => {
dispatch(controlLayerBeginEndStepPctChanged({ id: entityIdentifier.id, beginEndStepPct })); dispatch(controlLayerBeginEndStepPctChanged({ entityIdentifier, beginEndStepPct }));
}, },
[dispatch, entityIdentifier.id] [dispatch, entityIdentifier]
); );
const onChangeControlMode = useCallback( const onChangeControlMode = useCallback(
(controlMode: ControlModeV2) => { (controlMode: ControlModeV2) => {
dispatch(controlLayerControlModeChanged({ id: entityIdentifier.id, controlMode })); dispatch(controlLayerControlModeChanged({ entityIdentifier, controlMode }));
}, },
[dispatch, entityIdentifier.id] [dispatch, entityIdentifier]
); );
const onChangeWeight = useCallback( const onChangeWeight = useCallback(
(weight: number) => { (weight: number) => {
dispatch(controlLayerWeightChanged({ id: entityIdentifier.id, weight })); dispatch(controlLayerWeightChanged({ entityIdentifier, weight }));
}, },
[dispatch, entityIdentifier.id] [dispatch, entityIdentifier]
); );
const onChangeModel = useCallback( const onChangeModel = useCallback(
(modelConfig: ControlNetModelConfig | T2IAdapterModelConfig) => { (modelConfig: ControlNetModelConfig | T2IAdapterModelConfig) => {
dispatch(controlLayerModelChanged({ id: entityIdentifier.id, modelConfig })); dispatch(controlLayerModelChanged({ entityIdentifier, modelConfig }));
}, },
[dispatch, entityIdentifier.id] [dispatch, entityIdentifier]
); );
return ( return (

View File

@ -3,7 +3,7 @@ import { useAppSelector } from 'app/store/storeHooks';
import { CanvasEntityGroupList } from 'features/controlLayers/components/common/CanvasEntityGroupList'; import { CanvasEntityGroupList } from 'features/controlLayers/components/common/CanvasEntityGroupList';
import { ControlLayer } from 'features/controlLayers/components/ControlLayer/ControlLayer'; import { ControlLayer } from 'features/controlLayers/components/ControlLayer/ControlLayer';
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/selectors';
import { memo } from 'react'; import { memo } from 'react';
const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => { const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {

View File

@ -9,11 +9,11 @@ import { PiLightningBold } from 'react-icons/pi';
export const ControlLayerMenuItemsControlToRaster = memo(() => { export const ControlLayerMenuItemsControlToRaster = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const entityIdentifier = useEntityIdentifierContext(); const entityIdentifier = useEntityIdentifierContext('control_layer');
const convertControlLayerToRasterLayer = useCallback(() => { const convertControlLayerToRasterLayer = useCallback(() => {
dispatch(controlLayerConvertedToRasterLayer({ id: entityIdentifier.id })); dispatch(controlLayerConvertedToRasterLayer({ entityIdentifier }));
}, [dispatch, entityIdentifier.id]); }, [dispatch, entityIdentifier]);
return ( return (
<MenuItem onClick={convertControlLayerToRasterLayer} icon={<PiLightningBold />}> <MenuItem onClick={convertControlLayerToRasterLayer} icon={<PiLightningBold />}>

View File

@ -2,11 +2,8 @@ import { MenuItem } from '@invoke-ai/ui-library';
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext'; import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import { import { controlLayerWithTransparencyEffectToggled } from 'features/controlLayers/store/canvasV2Slice';
controlLayerWithTransparencyEffectToggled, import { selectCanvasV2Slice, selectEntityOrThrow } from 'features/controlLayers/store/selectors';
selectCanvasV2Slice,
} from 'features/controlLayers/store/canvasV2Slice';
import { selectControlLayerEntityOrThrow } from 'features/controlLayers/store/controlLayersReducers';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { PiDropHalfBold } from 'react-icons/pi'; import { PiDropHalfBold } from 'react-icons/pi';
@ -14,18 +11,18 @@ import { PiDropHalfBold } from 'react-icons/pi';
export const ControlLayerMenuItemsTransparencyEffect = memo(() => { export const ControlLayerMenuItemsTransparencyEffect = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const entityIdentifier = useEntityIdentifierContext(); const entityIdentifier = useEntityIdentifierContext('control_layer');
const selectWithTransparencyEffect = useMemo( const selectWithTransparencyEffect = useMemo(
() => () =>
createSelector(selectCanvasV2Slice, (canvasV2) => { createSelector(selectCanvasV2Slice, (canvasV2) => {
const entity = selectControlLayerEntityOrThrow(canvasV2, entityIdentifier.id); const entity = selectEntityOrThrow(canvasV2, entityIdentifier);
return entity.withTransparencyEffect; return entity.withTransparencyEffect;
}), }),
[entityIdentifier.id] [entityIdentifier]
); );
const withTransparencyEffect = useAppSelector(selectWithTransparencyEffect); const withTransparencyEffect = useAppSelector(selectWithTransparencyEffect);
const onToggle = useCallback(() => { const onToggle = useCallback(() => {
dispatch(controlLayerWithTransparencyEffectToggled({ id: entityIdentifier.id })); dispatch(controlLayerWithTransparencyEffectToggled({ entityIdentifier }));
}, [dispatch, entityIdentifier]); }, [dispatch, entityIdentifier]);
return ( return (

View File

@ -4,7 +4,7 @@ import { useAppSelector } from 'app/store/storeHooks';
import { CanvasEntityGroupList } from 'features/controlLayers/components/common/CanvasEntityGroupList'; import { CanvasEntityGroupList } from 'features/controlLayers/components/common/CanvasEntityGroupList';
import { IPAdapter } from 'features/controlLayers/components/IPAdapter/IPAdapter'; 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/selectors';
import { memo } from 'react'; import { memo } from 'react';
const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => { const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {

View File

@ -13,7 +13,7 @@ import {
ipaModelChanged, ipaModelChanged,
ipaWeightChanged, ipaWeightChanged,
} from 'features/controlLayers/store/canvasV2Slice'; } from 'features/controlLayers/store/canvasV2Slice';
import { selectIPAdapterEntityOrThrow } from 'features/controlLayers/store/ipAdaptersReducers'; import { selectEntityOrThrow } from 'features/controlLayers/store/selectors';
import type { CLIPVisionModelV2, IPMethodV2 } from 'features/controlLayers/store/types'; import type { CLIPVisionModelV2, IPMethodV2 } from 'features/controlLayers/store/types';
import type { IPAImageDropData } from 'features/dnd/types'; import type { IPAImageDropData } from 'features/dnd/types';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
@ -24,53 +24,59 @@ import { IPAdapterModel } from './IPAdapterModel';
export const IPAdapterSettings = memo(() => { export const IPAdapterSettings = memo(() => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { id } = useEntityIdentifierContext(); const entityIdentifier = useEntityIdentifierContext('ip_adapter');
const ipAdapter = useAppSelector((s) => selectIPAdapterEntityOrThrow(s.canvasV2, id).ipAdapter); const ipAdapter = useAppSelector((s) => selectEntityOrThrow(s.canvasV2, entityIdentifier).ipAdapter);
const onChangeBeginEndStepPct = useCallback( const onChangeBeginEndStepPct = useCallback(
(beginEndStepPct: [number, number]) => { (beginEndStepPct: [number, number]) => {
dispatch(ipaBeginEndStepPctChanged({ id, beginEndStepPct })); dispatch(ipaBeginEndStepPctChanged({ entityIdentifier, beginEndStepPct }));
}, },
[dispatch, id] [dispatch, entityIdentifier]
); );
const onChangeWeight = useCallback( const onChangeWeight = useCallback(
(weight: number) => { (weight: number) => {
dispatch(ipaWeightChanged({ id, weight })); dispatch(ipaWeightChanged({ entityIdentifier, weight }));
}, },
[dispatch, id] [dispatch, entityIdentifier]
); );
const onChangeIPMethod = useCallback( const onChangeIPMethod = useCallback(
(method: IPMethodV2) => { (method: IPMethodV2) => {
dispatch(ipaMethodChanged({ id, method })); dispatch(ipaMethodChanged({ entityIdentifier, method }));
}, },
[dispatch, id] [dispatch, entityIdentifier]
); );
const onChangeModel = useCallback( const onChangeModel = useCallback(
(modelConfig: IPAdapterModelConfig) => { (modelConfig: IPAdapterModelConfig) => {
dispatch(ipaModelChanged({ id, modelConfig })); dispatch(ipaModelChanged({ entityIdentifier, modelConfig }));
}, },
[dispatch, id] [dispatch, entityIdentifier]
); );
const onChangeCLIPVisionModel = useCallback( const onChangeCLIPVisionModel = useCallback(
(clipVisionModel: CLIPVisionModelV2) => { (clipVisionModel: CLIPVisionModelV2) => {
dispatch(ipaCLIPVisionModelChanged({ id, clipVisionModel })); dispatch(ipaCLIPVisionModelChanged({ entityIdentifier, clipVisionModel }));
}, },
[dispatch, id] [dispatch, entityIdentifier]
); );
const onChangeImage = useCallback( const onChangeImage = useCallback(
(imageDTO: ImageDTO | null) => { (imageDTO: ImageDTO | null) => {
dispatch(ipaImageChanged({ id, imageDTO })); dispatch(ipaImageChanged({ entityIdentifier, imageDTO }));
}, },
[dispatch, id] [dispatch, entityIdentifier]
); );
const droppableData = useMemo<IPAImageDropData>(() => ({ actionType: 'SET_IPA_IMAGE', context: { id }, id }), [id]); const droppableData = useMemo<IPAImageDropData>(
const postUploadAction = useMemo<IPALayerImagePostUploadAction>(() => ({ type: 'SET_IPA_IMAGE', id }), [id]); () => ({ actionType: 'SET_IPA_IMAGE', context: { id: entityIdentifier.id }, id: entityIdentifier.id }),
[entityIdentifier.id]
);
const postUploadAction = useMemo<IPALayerImagePostUploadAction>(
() => ({ type: 'SET_IPA_IMAGE', id: entityIdentifier.id }),
[entityIdentifier.id]
);
return ( return (
<CanvasEntitySettingsWrapper> <CanvasEntitySettingsWrapper>
@ -95,7 +101,7 @@ export const IPAdapterSettings = memo(() => {
<IPAdapterImagePreview <IPAdapterImagePreview
image={ipAdapter.image ?? null} image={ipAdapter.image ?? null}
onChangeImage={onChangeImage} onChangeImage={onChangeImage}
ipAdapterId={id} ipAdapterId={entityIdentifier.id}
droppableData={droppableData} droppableData={droppableData}
postUploadAction={postUploadAction} postUploadAction={postUploadAction}
/> />

View File

@ -3,7 +3,7 @@ import { useAppSelector } from 'app/store/storeHooks';
import { CanvasEntityGroupList } from 'features/controlLayers/components/common/CanvasEntityGroupList'; import { CanvasEntityGroupList } from 'features/controlLayers/components/common/CanvasEntityGroupList';
import { InpaintMask } from 'features/controlLayers/components/InpaintMask/InpaintMask'; import { InpaintMask } from 'features/controlLayers/components/InpaintMask/InpaintMask';
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/selectors';
import { memo } from 'react'; import { memo } from 'react';
const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => { const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {

View File

@ -5,7 +5,7 @@ import { rgbColorToString } from 'common/util/colorCodeTransformers';
import { MaskFillStyle } from 'features/controlLayers/components/common/MaskFillStyle'; import { MaskFillStyle } from 'features/controlLayers/components/common/MaskFillStyle';
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext'; import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import { inpaintMaskFillColorChanged, inpaintMaskFillStyleChanged } from 'features/controlLayers/store/canvasV2Slice'; import { inpaintMaskFillColorChanged, inpaintMaskFillStyleChanged } from 'features/controlLayers/store/canvasV2Slice';
import { selectInpaintMaskEntityOrThrow } from 'features/controlLayers/store/inpaintMaskReducers'; import { selectEntityOrThrow } from 'features/controlLayers/store/selectors';
import type { FillStyle, RgbColor } from 'features/controlLayers/store/types'; import type { FillStyle, RgbColor } from 'features/controlLayers/store/types';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -13,8 +13,8 @@ import { useTranslation } from 'react-i18next';
export const InpaintMaskMaskFillColorPicker = memo(() => { export const InpaintMaskMaskFillColorPicker = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const entityIdentifier = useEntityIdentifierContext(); const entityIdentifier = useEntityIdentifierContext('inpaint_mask');
const fill = useAppSelector((s) => selectInpaintMaskEntityOrThrow(s.canvasV2, entityIdentifier.id).fill); const fill = useAppSelector((s) => selectEntityOrThrow(s.canvasV2, entityIdentifier).fill);
const onChangeFillColor = useCallback( const onChangeFillColor = useCallback(
(color: RgbColor) => { (color: RgbColor) => {

View File

@ -3,7 +3,7 @@ import { useAppSelector } from 'app/store/storeHooks';
import { CanvasEntityGroupList } from 'features/controlLayers/components/common/CanvasEntityGroupList'; import { CanvasEntityGroupList } from 'features/controlLayers/components/common/CanvasEntityGroupList';
import { RasterLayer } from 'features/controlLayers/components/RasterLayer/RasterLayer'; import { RasterLayer } from 'features/controlLayers/components/RasterLayer/RasterLayer';
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/selectors';
import { memo } from 'react'; import { memo } from 'react';
const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => { const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {

View File

@ -9,11 +9,11 @@ import { PiLightningBold } from 'react-icons/pi';
export const RasterLayerMenuItemsRasterToControl = memo(() => { export const RasterLayerMenuItemsRasterToControl = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const entityIdentifier = useEntityIdentifierContext(); const entityIdentifier = useEntityIdentifierContext('raster_layer');
const convertRasterLayerToControlLayer = useCallback(() => { const convertRasterLayerToControlLayer = useCallback(() => {
dispatch(rasterLayerConvertedToControlLayer({ id: entityIdentifier.id })); dispatch(rasterLayerConvertedToControlLayer({ entityIdentifier }));
}, [dispatch, entityIdentifier.id]); }, [dispatch, entityIdentifier]);
return ( return (
<MenuItem onClick={convertRasterLayerToControlLayer} icon={<PiLightningBold />}> <MenuItem onClick={convertRasterLayerToControlLayer} icon={<PiLightningBold />}>

View File

@ -1,44 +1,42 @@
import { Button, Flex } from '@invoke-ai/ui-library'; import { Button, Flex } from '@invoke-ai/ui-library';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import { import {
rgIPAdapterAdded, rgIPAdapterAdded,
rgNegativePromptChanged, rgNegativePromptChanged,
rgPositivePromptChanged, rgPositivePromptChanged,
selectCanvasV2Slice,
} from 'features/controlLayers/store/canvasV2Slice'; } from 'features/controlLayers/store/canvasV2Slice';
import { selectCanvasV2Slice, selectEntityOrThrow } from 'features/controlLayers/store/selectors';
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { PiPlusBold } from 'react-icons/pi'; import { PiPlusBold } from 'react-icons/pi';
type AddPromptButtonProps = { export const RegionalGuidanceAddPromptsIPAdapterButtons = () => {
id: string; const entityIdentifier = useEntityIdentifierContext('regional_guidance');
};
export const RegionalGuidanceAddPromptsIPAdapterButtons = ({ id }: AddPromptButtonProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const selectValidActions = useMemo( const selectValidActions = useMemo(
() => () =>
createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => { createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
const rg = canvasV2.regions.entities.find((rg) => rg.id === id); const entity = selectEntityOrThrow(canvasV2, entityIdentifier);
return { return {
canAddPositivePrompt: rg?.positivePrompt === null, canAddPositivePrompt: entity?.positivePrompt === null,
canAddNegativePrompt: rg?.negativePrompt === null, canAddNegativePrompt: entity?.negativePrompt === null,
}; };
}), }),
[id] [entityIdentifier]
); );
const validActions = useAppSelector(selectValidActions); const validActions = useAppSelector(selectValidActions);
const addPositivePrompt = useCallback(() => { const addPositivePrompt = useCallback(() => {
dispatch(rgPositivePromptChanged({ id, prompt: '' })); dispatch(rgPositivePromptChanged({ entityIdentifier, prompt: '' }));
}, [dispatch, id]); }, [dispatch, entityIdentifier]);
const addNegativePrompt = useCallback(() => { const addNegativePrompt = useCallback(() => {
dispatch(rgNegativePromptChanged({ id, prompt: '' })); dispatch(rgNegativePromptChanged({ entityIdentifier, prompt: '' }));
}, [dispatch, id]); }, [dispatch, entityIdentifier]);
const addIPAdapter = useCallback(() => { const addIPAdapter = useCallback(() => {
dispatch(rgIPAdapterAdded({ id })); dispatch(rgIPAdapterAdded({ entityIdentifier }));
}, [dispatch, id]); }, [dispatch, entityIdentifier]);
return ( return (
<Flex w="full" p={2} justifyContent="space-between"> <Flex w="full" p={2} justifyContent="space-between">

View File

@ -1,14 +1,14 @@
import { Badge } from '@invoke-ai/ui-library'; import { Badge } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext'; import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import { selectRegionalGuidanceEntityOrThrow } from 'features/controlLayers/store/regionsReducers'; import { selectEntityOrThrow } from 'features/controlLayers/store/selectors';
import { memo } from 'react'; import { memo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
export const RegionalGuidanceBadges = memo(() => { export const RegionalGuidanceBadges = memo(() => {
const { id } = useEntityIdentifierContext(); const entityIdentifier = useEntityIdentifierContext('regional_guidance');
const { t } = useTranslation(); const { t } = useTranslation();
const autoNegative = useAppSelector((s) => selectRegionalGuidanceEntityOrThrow(s.canvasV2, id).autoNegative); const autoNegative = useAppSelector((s) => selectEntityOrThrow(s.canvasV2, entityIdentifier).autoNegative);
return ( return (
<> <>

View File

@ -3,7 +3,7 @@ import { useAppSelector } from 'app/store/storeHooks';
import { CanvasEntityGroupList } from 'features/controlLayers/components/common/CanvasEntityGroupList'; import { CanvasEntityGroupList } from 'features/controlLayers/components/common/CanvasEntityGroupList';
import { RegionalGuidance } from 'features/controlLayers/components/RegionalGuidance/RegionalGuidance'; import { RegionalGuidance } from 'features/controlLayers/components/RegionalGuidance/RegionalGuidance';
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/selectors';
import { memo } from 'react'; import { memo } from 'react';
const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => { const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {

View File

@ -5,6 +5,7 @@ import { Weight } from 'features/controlLayers/components/common/Weight';
import { IPAdapterImagePreview } from 'features/controlLayers/components/IPAdapter/IPAdapterImagePreview'; import { IPAdapterImagePreview } from 'features/controlLayers/components/IPAdapter/IPAdapterImagePreview';
import { IPAdapterMethod } from 'features/controlLayers/components/IPAdapter/IPAdapterMethod'; import { IPAdapterMethod } from 'features/controlLayers/components/IPAdapter/IPAdapterMethod';
import { IPAdapterModel } from 'features/controlLayers/components/IPAdapter/IPAdapterModel'; import { IPAdapterModel } from 'features/controlLayers/components/IPAdapter/IPAdapterModel';
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import { import {
rgIPAdapterBeginEndStepPctChanged, rgIPAdapterBeginEndStepPctChanged,
rgIPAdapterCLIPVisionModelChanged, rgIPAdapterCLIPVisionModelChanged,
@ -14,7 +15,7 @@ import {
rgIPAdapterModelChanged, rgIPAdapterModelChanged,
rgIPAdapterWeightChanged, rgIPAdapterWeightChanged,
} from 'features/controlLayers/store/canvasV2Slice'; } from 'features/controlLayers/store/canvasV2Slice';
import { selectRegionalGuidanceEntityOrThrow } from 'features/controlLayers/store/regionsReducers'; import { selectRegionalGuidanceIPAdapter } from 'features/controlLayers/store/selectors';
import type { CLIPVisionModelV2, IPMethodV2 } from 'features/controlLayers/store/types'; import type { CLIPVisionModelV2, IPMethodV2 } from 'features/controlLayers/store/types';
import type { RGIPAdapterImageDropData } from 'features/dnd/types'; import type { RGIPAdapterImageDropData } from 'features/dnd/types';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
@ -23,71 +24,75 @@ import type { ImageDTO, IPAdapterModelConfig, RGIPAdapterImagePostUploadAction }
import { assert } from 'tsafe'; import { assert } from 'tsafe';
type Props = { type Props = {
id: string;
ipAdapterId: string; ipAdapterId: string;
ipAdapterNumber: number; ipAdapterNumber: number;
}; };
export const RegionalGuidanceIPAdapterSettings = memo(({ id, ipAdapterId, ipAdapterNumber }: Props) => { export const RegionalGuidanceIPAdapterSettings = memo(({ ipAdapterId, ipAdapterNumber }: Props) => {
const entityIdentifier = useEntityIdentifierContext('regional_guidance');
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const onDeleteIPAdapter = useCallback(() => { const onDeleteIPAdapter = useCallback(() => {
dispatch(rgIPAdapterDeleted({ id, ipAdapterId })); dispatch(rgIPAdapterDeleted({ entityIdentifier, ipAdapterId }));
}, [dispatch, ipAdapterId, id]); }, [dispatch, entityIdentifier, ipAdapterId]);
const ipAdapter = useAppSelector((s) => { const ipAdapter = useAppSelector((s) => {
const ipa = selectRegionalGuidanceEntityOrThrow(s.canvasV2, id).ipAdapters.find((ipa) => ipa.id === ipAdapterId); const ipa = selectRegionalGuidanceIPAdapter(s.canvasV2, entityIdentifier, ipAdapterId);
assert(ipa, `Regional GuidanceIP Adapter with id ${ipAdapterId} not found`); assert(ipa, `Regional GuidanceIP Adapter with id ${ipAdapterId} not found`);
return ipa; return ipa;
}); });
const onChangeBeginEndStepPct = useCallback( const onChangeBeginEndStepPct = useCallback(
(beginEndStepPct: [number, number]) => { (beginEndStepPct: [number, number]) => {
dispatch(rgIPAdapterBeginEndStepPctChanged({ id, ipAdapterId, beginEndStepPct })); dispatch(rgIPAdapterBeginEndStepPctChanged({ entityIdentifier, ipAdapterId, beginEndStepPct }));
}, },
[dispatch, ipAdapterId, id] [dispatch, entityIdentifier, ipAdapterId]
); );
const onChangeWeight = useCallback( const onChangeWeight = useCallback(
(weight: number) => { (weight: number) => {
dispatch(rgIPAdapterWeightChanged({ id, ipAdapterId, weight })); dispatch(rgIPAdapterWeightChanged({ entityIdentifier, ipAdapterId, weight }));
}, },
[dispatch, ipAdapterId, id] [dispatch, entityIdentifier, ipAdapterId]
); );
const onChangeIPMethod = useCallback( const onChangeIPMethod = useCallback(
(method: IPMethodV2) => { (method: IPMethodV2) => {
dispatch(rgIPAdapterMethodChanged({ id, ipAdapterId, method })); dispatch(rgIPAdapterMethodChanged({ entityIdentifier, ipAdapterId, method }));
}, },
[dispatch, ipAdapterId, id] [dispatch, entityIdentifier, ipAdapterId]
); );
const onChangeModel = useCallback( const onChangeModel = useCallback(
(modelConfig: IPAdapterModelConfig) => { (modelConfig: IPAdapterModelConfig) => {
dispatch(rgIPAdapterModelChanged({ id, ipAdapterId, modelConfig })); dispatch(rgIPAdapterModelChanged({ entityIdentifier, ipAdapterId, modelConfig }));
}, },
[dispatch, ipAdapterId, id] [dispatch, entityIdentifier, ipAdapterId]
); );
const onChangeCLIPVisionModel = useCallback( const onChangeCLIPVisionModel = useCallback(
(clipVisionModel: CLIPVisionModelV2) => { (clipVisionModel: CLIPVisionModelV2) => {
dispatch(rgIPAdapterCLIPVisionModelChanged({ id, ipAdapterId, clipVisionModel })); dispatch(rgIPAdapterCLIPVisionModelChanged({ entityIdentifier, ipAdapterId, clipVisionModel }));
}, },
[dispatch, ipAdapterId, id] [dispatch, entityIdentifier, ipAdapterId]
); );
const onChangeImage = useCallback( const onChangeImage = useCallback(
(imageDTO: ImageDTO | null) => { (imageDTO: ImageDTO | null) => {
dispatch(rgIPAdapterImageChanged({ id, ipAdapterId, imageDTO })); dispatch(rgIPAdapterImageChanged({ entityIdentifier, ipAdapterId, imageDTO }));
}, },
[dispatch, ipAdapterId, id] [dispatch, entityIdentifier, ipAdapterId]
); );
const droppableData = useMemo<RGIPAdapterImageDropData>( const droppableData = useMemo<RGIPAdapterImageDropData>(
() => ({ actionType: 'SET_RG_IP_ADAPTER_IMAGE', context: { id, ipAdapterId }, id }), () => ({
[ipAdapterId, id] actionType: 'SET_RG_IP_ADAPTER_IMAGE',
context: { id: entityIdentifier.id, ipAdapterId },
id: entityIdentifier.id,
}),
[entityIdentifier.id, ipAdapterId]
); );
const postUploadAction = useMemo<RGIPAdapterImagePostUploadAction>( const postUploadAction = useMemo<RGIPAdapterImagePostUploadAction>(
() => ({ type: 'SET_RG_IP_ADAPTER_IMAGE', id, ipAdapterId }), () => ({ type: 'SET_RG_IP_ADAPTER_IMAGE', id: entityIdentifier.id, ipAdapterId }),
[ipAdapterId, id] [entityIdentifier.id, ipAdapterId]
); );
return ( return (

View File

@ -3,25 +3,23 @@ import { EMPTY_ARRAY } from 'app/store/constants';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { RegionalGuidanceIPAdapterSettings } from 'features/controlLayers/components/RegionalGuidance/RegionalGuidanceIPAdapterSettings'; import { RegionalGuidanceIPAdapterSettings } from 'features/controlLayers/components/RegionalGuidance/RegionalGuidanceIPAdapterSettings';
import { selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice'; import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import { selectRegionalGuidanceEntityOrThrow } from 'features/controlLayers/store/regionsReducers'; import { selectCanvasV2Slice, selectEntityOrThrow } from 'features/controlLayers/store/selectors';
import { Fragment, memo, useMemo } from 'react'; import { Fragment, memo, useMemo } from 'react';
type Props = { export const RegionalGuidanceIPAdapters = memo(() => {
id: string; const entityIdentifier = useEntityIdentifierContext('regional_guidance');
};
export const RegionalGuidanceIPAdapters = memo(({ id }: Props) => {
const selectIPAdapterIds = useMemo( const selectIPAdapterIds = useMemo(
() => () =>
createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => { createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
const ipAdapterIds = selectRegionalGuidanceEntityOrThrow(canvasV2, id).ipAdapters.map(({ id }) => id); const ipAdapterIds = selectEntityOrThrow(canvasV2, entityIdentifier).ipAdapters.map(({ id }) => id);
if (ipAdapterIds.length === 0) { if (ipAdapterIds.length === 0) {
return EMPTY_ARRAY; return EMPTY_ARRAY;
} }
return ipAdapterIds; return ipAdapterIds;
}), }),
[id] [entityIdentifier]
); );
const ipAdapterIds = useAppSelector(selectIPAdapterIds); const ipAdapterIds = useAppSelector(selectIPAdapterIds);
@ -35,7 +33,7 @@ export const RegionalGuidanceIPAdapters = memo(({ id }: Props) => {
{ipAdapterIds.map((ipAdapterId, index) => ( {ipAdapterIds.map((ipAdapterId, index) => (
<Fragment key={ipAdapterId}> <Fragment key={ipAdapterId}>
{index > 0 && <Divider />} {index > 0 && <Divider />}
<RegionalGuidanceIPAdapterSettings id={id} ipAdapterId={ipAdapterId} ipAdapterNumber={index + 1} /> <RegionalGuidanceIPAdapterSettings ipAdapterId={ipAdapterId} ipAdapterNumber={index + 1} />
</Fragment> </Fragment>
))} ))}
</> </>

View File

@ -5,27 +5,27 @@ import { rgbColorToString } from 'common/util/colorCodeTransformers';
import { MaskFillStyle } from 'features/controlLayers/components/common/MaskFillStyle'; import { MaskFillStyle } from 'features/controlLayers/components/common/MaskFillStyle';
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext'; import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import { rgFillColorChanged, rgFillStyleChanged } from 'features/controlLayers/store/canvasV2Slice'; import { rgFillColorChanged, rgFillStyleChanged } from 'features/controlLayers/store/canvasV2Slice';
import { selectRegionalGuidanceEntityOrThrow } from 'features/controlLayers/store/regionsReducers'; import { selectEntityOrThrow } from 'features/controlLayers/store/selectors';
import type { FillStyle, RgbColor } from 'features/controlLayers/store/types'; import type { FillStyle, RgbColor } from 'features/controlLayers/store/types';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
export const RegionalGuidanceMaskFillColorPicker = memo(() => { export const RegionalGuidanceMaskFillColorPicker = memo(() => {
const entityIdentifier = useEntityIdentifierContext(); const entityIdentifier = useEntityIdentifierContext('regional_guidance');
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const fill = useAppSelector((s) => selectRegionalGuidanceEntityOrThrow(s.canvasV2, entityIdentifier.id).fill); const fill = useAppSelector((s) => selectEntityOrThrow(s.canvasV2, entityIdentifier).fill);
const onChangeFillColor = useCallback( const onChangeFillColor = useCallback(
(color: RgbColor) => { (color: RgbColor) => {
dispatch(rgFillColorChanged({ id: entityIdentifier.id, color })); dispatch(rgFillColorChanged({ entityIdentifier, color }));
}, },
[dispatch, entityIdentifier.id] [dispatch, entityIdentifier]
); );
const onChangeFillStyle = useCallback( const onChangeFillStyle = useCallback(
(style: FillStyle) => { (style: FillStyle) => {
dispatch(rgFillStyleChanged({ id: entityIdentifier.id, style })); dispatch(rgFillStyleChanged({ entityIdentifier, style }));
}, },
[dispatch, entityIdentifier.id] [dispatch, entityIdentifier]
); );
return ( return (
<Popover isLazy> <Popover isLazy>

View File

@ -6,36 +6,36 @@ import {
rgIPAdapterAdded, rgIPAdapterAdded,
rgNegativePromptChanged, rgNegativePromptChanged,
rgPositivePromptChanged, rgPositivePromptChanged,
selectCanvasV2Slice,
} from 'features/controlLayers/store/canvasV2Slice'; } from 'features/controlLayers/store/canvasV2Slice';
import { selectCanvasV2Slice, selectEntity } from 'features/controlLayers/store/selectors';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
export const RegionalGuidanceMenuItemsAddPromptsAndIPAdapter = memo(() => { export const RegionalGuidanceMenuItemsAddPromptsAndIPAdapter = memo(() => {
const { id } = useEntityIdentifierContext(); const entityIdentifier = useEntityIdentifierContext('regional_guidance');
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const selectValidActions = useMemo( const selectValidActions = useMemo(
() => () =>
createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => { createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
const rg = canvasV2.regions.entities.find((rg) => rg.id === id); const entity = selectEntity(canvasV2, entityIdentifier);
return { return {
canAddPositivePrompt: rg?.positivePrompt === null, canAddPositivePrompt: entity?.positivePrompt === null,
canAddNegativePrompt: rg?.negativePrompt === null, canAddNegativePrompt: entity?.negativePrompt === null,
}; };
}), }),
[id] [entityIdentifier]
); );
const validActions = useAppSelector(selectValidActions); const validActions = useAppSelector(selectValidActions);
const addPositivePrompt = useCallback(() => { const addPositivePrompt = useCallback(() => {
dispatch(rgPositivePromptChanged({ id: id, prompt: '' })); dispatch(rgPositivePromptChanged({ entityIdentifier, prompt: '' }));
}, [dispatch, id]); }, [dispatch, entityIdentifier]);
const addNegativePrompt = useCallback(() => { const addNegativePrompt = useCallback(() => {
dispatch(rgNegativePromptChanged({ id: id, prompt: '' })); dispatch(rgNegativePromptChanged({ entityIdentifier, prompt: '' }));
}, [dispatch, id]); }, [dispatch, entityIdentifier]);
const addIPAdapter = useCallback(() => { const addIPAdapter = useCallback(() => {
dispatch(rgIPAdapterAdded({ id })); dispatch(rgIPAdapterAdded({ entityIdentifier }));
}, [dispatch, id]); }, [dispatch, entityIdentifier]);
return ( return (
<> <>

View File

@ -2,19 +2,19 @@ import { MenuItem } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext'; import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import { rgAutoNegativeToggled } from 'features/controlLayers/store/canvasV2Slice'; import { rgAutoNegativeToggled } from 'features/controlLayers/store/canvasV2Slice';
import { selectRegionalGuidanceEntityOrThrow } from 'features/controlLayers/store/regionsReducers'; import { selectEntityOrThrow } from 'features/controlLayers/store/selectors';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { PiSelectionInverseBold } from 'react-icons/pi'; import { PiSelectionInverseBold } from 'react-icons/pi';
export const RegionalGuidanceMenuItemsAutoNegative = memo(() => { export const RegionalGuidanceMenuItemsAutoNegative = memo(() => {
const { id } = useEntityIdentifierContext(); const entityIdentifier = useEntityIdentifierContext('regional_guidance');
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const autoNegative = useAppSelector((s) => selectRegionalGuidanceEntityOrThrow(s.canvasV2, id).autoNegative); const autoNegative = useAppSelector((s) => selectEntityOrThrow(s.canvasV2, entityIdentifier).autoNegative);
const onClick = useCallback(() => { const onClick = useCallback(() => {
dispatch(rgAutoNegativeToggled({ id })); dispatch(rgAutoNegativeToggled({ entityIdentifier }));
}, [dispatch, id]); }, [dispatch, entityIdentifier]);
return ( return (
<MenuItem icon={<PiSelectionInverseBold />} onClick={onClick}> <MenuItem icon={<PiSelectionInverseBold />} onClick={onClick}>

View File

@ -1,8 +1,9 @@
import { Box, Textarea } from '@invoke-ai/ui-library'; import { Box, Textarea } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { RegionalGuidanceDeletePromptButton } from 'features/controlLayers/components/RegionalGuidance/RegionalGuidanceDeletePromptButton'; import { RegionalGuidanceDeletePromptButton } from 'features/controlLayers/components/RegionalGuidance/RegionalGuidanceDeletePromptButton';
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import { rgNegativePromptChanged } from 'features/controlLayers/store/canvasV2Slice'; import { rgNegativePromptChanged } from 'features/controlLayers/store/canvasV2Slice';
import { selectRegionalGuidanceEntityOrThrow } from 'features/controlLayers/store/regionsReducers'; import { selectEntityOrThrow } from 'features/controlLayers/store/selectors';
import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper'; import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper';
import { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton'; import { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton';
import { PromptPopover } from 'features/prompt/PromptPopover'; import { PromptPopover } from 'features/prompt/PromptPopover';
@ -10,24 +11,21 @@ import { usePrompt } from 'features/prompt/usePrompt';
import { memo, useCallback, useRef } from 'react'; import { memo, useCallback, useRef } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
type Props = { export const RegionalGuidanceNegativePrompt = memo(() => {
id: string; const entityIdentifier = useEntityIdentifierContext('regional_guidance');
}; const prompt = useAppSelector((s) => selectEntityOrThrow(s.canvasV2, entityIdentifier).negativePrompt ?? '');
export const RegionalGuidanceNegativePrompt = memo(({ id }: Props) => {
const prompt = useAppSelector((s) => selectRegionalGuidanceEntityOrThrow(s.canvasV2, id).negativePrompt ?? '');
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const textareaRef = useRef<HTMLTextAreaElement>(null); const textareaRef = useRef<HTMLTextAreaElement>(null);
const { t } = useTranslation(); const { t } = useTranslation();
const _onChange = useCallback( const _onChange = useCallback(
(v: string) => { (v: string) => {
dispatch(rgNegativePromptChanged({ id, prompt: v })); dispatch(rgNegativePromptChanged({ entityIdentifier, prompt: v }));
}, },
[dispatch, id] [dispatch, entityIdentifier]
); );
const onDeletePrompt = useCallback(() => { const onDeletePrompt = useCallback(() => {
dispatch(rgNegativePromptChanged({ id, prompt: null })); dispatch(rgNegativePromptChanged({ entityIdentifier, prompt: null }));
}, [dispatch, id]); }, [dispatch, entityIdentifier]);
const { onChange, isOpen, onClose, onOpen, onSelect, onKeyDown } = usePrompt({ const { onChange, isOpen, onClose, onOpen, onSelect, onKeyDown } = usePrompt({
prompt, prompt,
textareaRef, textareaRef,

View File

@ -1,8 +1,9 @@
import { Box, Textarea } from '@invoke-ai/ui-library'; import { Box, Textarea } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { RegionalGuidanceDeletePromptButton } from 'features/controlLayers/components/RegionalGuidance/RegionalGuidanceDeletePromptButton'; import { RegionalGuidanceDeletePromptButton } from 'features/controlLayers/components/RegionalGuidance/RegionalGuidanceDeletePromptButton';
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import { rgPositivePromptChanged } from 'features/controlLayers/store/canvasV2Slice'; import { rgPositivePromptChanged } from 'features/controlLayers/store/canvasV2Slice';
import { selectRegionalGuidanceEntityOrThrow } from 'features/controlLayers/store/regionsReducers'; import { selectEntityOrThrow } from 'features/controlLayers/store/selectors';
import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper'; import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper';
import { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton'; import { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton';
import { PromptPopover } from 'features/prompt/PromptPopover'; import { PromptPopover } from 'features/prompt/PromptPopover';
@ -10,24 +11,21 @@ import { usePrompt } from 'features/prompt/usePrompt';
import { memo, useCallback, useRef } from 'react'; import { memo, useCallback, useRef } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
type Props = { export const RegionalGuidancePositivePrompt = memo(() => {
id: string; const entityIdentifier = useEntityIdentifierContext('regional_guidance');
}; const prompt = useAppSelector((s) => selectEntityOrThrow(s.canvasV2, entityIdentifier).positivePrompt ?? '');
export const RegionalGuidancePositivePrompt = memo(({ id }: Props) => {
const prompt = useAppSelector((s) => selectRegionalGuidanceEntityOrThrow(s.canvasV2, id).positivePrompt ?? '');
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const textareaRef = useRef<HTMLTextAreaElement>(null); const textareaRef = useRef<HTMLTextAreaElement>(null);
const { t } = useTranslation(); const { t } = useTranslation();
const _onChange = useCallback( const _onChange = useCallback(
(v: string) => { (v: string) => {
dispatch(rgPositivePromptChanged({ id, prompt: v })); dispatch(rgPositivePromptChanged({ entityIdentifier, prompt: v }));
}, },
[dispatch, id] [dispatch, entityIdentifier]
); );
const onDeletePrompt = useCallback(() => { const onDeletePrompt = useCallback(() => {
dispatch(rgPositivePromptChanged({ id, prompt: null })); dispatch(rgPositivePromptChanged({ entityIdentifier, prompt: null }));
}, [dispatch, id]); }, [dispatch, entityIdentifier]);
const { onChange, isOpen, onClose, onOpen, onSelect, onKeyDown } = usePrompt({ const { onChange, isOpen, onClose, onOpen, onSelect, onKeyDown } = usePrompt({
prompt, prompt,
textareaRef, textareaRef,

View File

@ -3,7 +3,7 @@ import { useAppSelector } from 'app/store/storeHooks';
import { CanvasEntitySettingsWrapper } from 'features/controlLayers/components/common/CanvasEntitySettingsWrapper'; import { CanvasEntitySettingsWrapper } from 'features/controlLayers/components/common/CanvasEntitySettingsWrapper';
import { RegionalGuidanceAddPromptsIPAdapterButtons } from 'features/controlLayers/components/RegionalGuidance/RegionalGuidanceAddPromptsIPAdapterButtons'; import { RegionalGuidanceAddPromptsIPAdapterButtons } from 'features/controlLayers/components/RegionalGuidance/RegionalGuidanceAddPromptsIPAdapterButtons';
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext'; import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import { selectRegionalGuidanceEntityOrThrow } from 'features/controlLayers/store/regionsReducers'; import { selectEntityOrThrow } from 'features/controlLayers/store/selectors';
import { memo } from 'react'; import { memo } from 'react';
import { RegionalGuidanceIPAdapters } from './RegionalGuidanceIPAdapters'; import { RegionalGuidanceIPAdapters } from './RegionalGuidanceIPAdapters';
@ -11,35 +11,31 @@ import { RegionalGuidanceNegativePrompt } from './RegionalGuidanceNegativePrompt
import { RegionalGuidancePositivePrompt } from './RegionalGuidancePositivePrompt'; import { RegionalGuidancePositivePrompt } from './RegionalGuidancePositivePrompt';
export const RegionalGuidanceSettings = memo(() => { export const RegionalGuidanceSettings = memo(() => {
const { id } = useEntityIdentifierContext(); const entityIdentifier = useEntityIdentifierContext('regional_guidance');
const hasPositivePrompt = useAppSelector( const hasPositivePrompt = useAppSelector(
(s) => selectRegionalGuidanceEntityOrThrow(s.canvasV2, id).positivePrompt !== null (s) => selectEntityOrThrow(s.canvasV2, entityIdentifier).positivePrompt !== null
); );
const hasNegativePrompt = useAppSelector( const hasNegativePrompt = useAppSelector(
(s) => selectRegionalGuidanceEntityOrThrow(s.canvasV2, id).negativePrompt !== null (s) => selectEntityOrThrow(s.canvasV2, entityIdentifier).negativePrompt !== null
);
const hasIPAdapters = useAppSelector(
(s) => selectRegionalGuidanceEntityOrThrow(s.canvasV2, id).ipAdapters.length > 0
); );
const hasIPAdapters = useAppSelector((s) => selectEntityOrThrow(s.canvasV2, entityIdentifier).ipAdapters.length > 0);
return ( return (
<CanvasEntitySettingsWrapper> <CanvasEntitySettingsWrapper>
{!hasPositivePrompt && !hasNegativePrompt && !hasIPAdapters && ( {!hasPositivePrompt && !hasNegativePrompt && !hasIPAdapters && <RegionalGuidanceAddPromptsIPAdapterButtons />}
<RegionalGuidanceAddPromptsIPAdapterButtons id={id} />
)}
{hasPositivePrompt && ( {hasPositivePrompt && (
<> <>
<RegionalGuidancePositivePrompt id={id} /> <RegionalGuidancePositivePrompt />
{(hasNegativePrompt || hasIPAdapters) && <Divider />} {(hasNegativePrompt || hasIPAdapters) && <Divider />}
</> </>
)} )}
{hasNegativePrompt && ( {hasNegativePrompt && (
<> <>
<RegionalGuidanceNegativePrompt id={id} /> <RegionalGuidanceNegativePrompt />
{hasIPAdapters && <Divider />} {hasIPAdapters && <Divider />}
</> </>
)} )}
{hasIPAdapters && <RegionalGuidanceIPAdapters id={id} />} {hasIPAdapters && <RegionalGuidanceIPAdapters />}
</CanvasEntitySettingsWrapper> </CanvasEntitySettingsWrapper>
); );
}); });

View File

@ -7,8 +7,8 @@ import {
entityArrangedForwardOne, entityArrangedForwardOne,
entityArrangedToBack, entityArrangedToBack,
entityArrangedToFront, entityArrangedToFront,
selectCanvasV2Slice,
} from 'features/controlLayers/store/canvasV2Slice'; } from 'features/controlLayers/store/canvasV2Slice';
import { selectCanvasV2Slice } from 'features/controlLayers/store/selectors';
import type { CanvasEntityIdentifier, CanvasV2State } from 'features/controlLayers/store/types'; import type { CanvasEntityIdentifier, CanvasV2State } from 'features/controlLayers/store/types';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';

View File

@ -15,7 +15,8 @@ import {
} from '@invoke-ai/ui-library'; } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { snapToNearest } from 'features/controlLayers/konva/util'; import { snapToNearest } from 'features/controlLayers/konva/util';
import { entityOpacityChanged, selectEntity } from 'features/controlLayers/store/canvasV2Slice'; import { entityOpacityChanged } from 'features/controlLayers/store/canvasV2Slice';
import { selectEntity } from 'features/controlLayers/store/selectors';
import { isDrawableEntity } from 'features/controlLayers/store/types'; import { isDrawableEntity } from 'features/controlLayers/store/types';
import { clamp, round } from 'lodash-es'; import { clamp, round } from 'lodash-es';
import type { KeyboardEvent } from 'react'; import type { KeyboardEvent } from 'react';

View File

@ -5,7 +5,7 @@ import { rgbColorToString } from 'common/util/colorCodeTransformers';
import { useEntityAdapter } from 'features/controlLayers/contexts/EntityAdapterContext'; import { useEntityAdapter } from 'features/controlLayers/contexts/EntityAdapterContext';
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext'; import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import { TRANSPARENCY_CHECKER_PATTERN } from 'features/controlLayers/konva/constants'; import { TRANSPARENCY_CHECKER_PATTERN } from 'features/controlLayers/konva/constants';
import { selectCanvasV2Slice, selectEntity } from 'features/controlLayers/store/canvasV2Slice'; import { selectCanvasV2Slice, selectEntity } from 'features/controlLayers/store/selectors';
import { memo, useEffect, useMemo, useRef } from 'react'; import { memo, useEffect, useMemo, useRef } from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';

View File

@ -1,11 +1,16 @@
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types'; import type { CanvasEntityIdentifier, CanvasEntityType } from 'features/controlLayers/store/types';
import { createContext, useContext } from 'react'; import { createContext, useContext } from 'react';
import { assert } from 'tsafe'; import { assert } from 'tsafe';
export const EntityIdentifierContext = createContext<CanvasEntityIdentifier | null>(null); export const EntityIdentifierContext = createContext<CanvasEntityIdentifier | null>(null);
export const useEntityIdentifierContext = (): CanvasEntityIdentifier => { export const useEntityIdentifierContext = <T extends CanvasEntityType | undefined = CanvasEntityType>(
type?: T
): CanvasEntityIdentifier<T extends undefined ? CanvasEntityType : T> => {
const entityIdentifier = useContext(EntityIdentifierContext); const entityIdentifier = useContext(EntityIdentifierContext);
assert(entityIdentifier, 'useEntityIdentifier must be used within a EntityIdentifierProvider'); assert(entityIdentifier, 'useEntityIdentifier must be used within a EntityIdentifierProvider');
return entityIdentifier; if (type) {
assert(entityIdentifier.type === type, 'useEntityIdentifier must be used with the correct type');
}
return entityIdentifier as CanvasEntityIdentifier<T extends undefined ? CanvasEntityType : T>;
}; };

View File

@ -1,7 +1,8 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { useAssertSingleton } from 'common/hooks/useAssertSingleton'; import { useAssertSingleton } from 'common/hooks/useAssertSingleton';
import { entityDeleted, selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice'; import { entityDeleted } from 'features/controlLayers/store/canvasV2Slice';
import { selectCanvasV2Slice } from 'features/controlLayers/store/selectors';
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';

View File

@ -1,7 +1,8 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { useAssertSingleton } from 'common/hooks/useAssertSingleton'; import { useAssertSingleton } from 'common/hooks/useAssertSingleton';
import { entityReset, selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice'; import { entityReset } from 'features/controlLayers/store/canvasV2Slice';
import { selectCanvasV2Slice } from 'features/controlLayers/store/selectors';
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';

View File

@ -1,6 +1,6 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { selectCanvasV2Slice, selectEntity } from 'features/controlLayers/store/canvasV2Slice'; import { selectCanvasV2Slice, selectEntity } from 'features/controlLayers/store/selectors';
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types'; import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
import { useMemo } from 'react'; import { useMemo } from 'react';

View File

@ -1,6 +1,6 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { selectCanvasV2Slice, selectEntity } from 'features/controlLayers/store/canvasV2Slice'; import { selectCanvasV2Slice, selectEntity } from 'features/controlLayers/store/selectors';
import { type CanvasEntityIdentifier, isDrawableEntity } from 'features/controlLayers/store/types'; import { type CanvasEntityIdentifier, isDrawableEntity } from 'features/controlLayers/store/types';
import { useMemo } from 'react'; import { useMemo } from 'react';

View File

@ -1,7 +1,7 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { rgbColorToString } from 'common/util/colorCodeTransformers'; import { rgbColorToString } from 'common/util/colorCodeTransformers';
import { selectCanvasV2Slice, selectEntity } from 'features/controlLayers/store/canvasV2Slice'; import { selectCanvasV2Slice, selectEntity } from 'features/controlLayers/store/selectors';
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types'; import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
import { useMemo } from 'react'; import { useMemo } from 'react';

View File

@ -1,7 +1,7 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { useEntityObjectCount } from 'features/controlLayers/hooks/useEntityObjectCount'; import { useEntityObjectCount } from 'features/controlLayers/hooks/useEntityObjectCount';
import { selectCanvasV2Slice, selectEntity } from 'features/controlLayers/store/canvasV2Slice'; import { selectCanvasV2Slice, selectEntity } from 'features/controlLayers/store/selectors';
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types'; import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';

View File

@ -1,6 +1,6 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice'; import { selectCanvasV2Slice } from 'features/controlLayers/store/selectors';
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types'; import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
import { useMemo } from 'react'; import { useMemo } from 'react';

View File

@ -1,6 +1,6 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice'; import { selectCanvasV2Slice } from 'features/controlLayers/store/selectors';
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types'; import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
import { useMemo } from 'react'; import { useMemo } from 'react';

View File

@ -1,8 +1,7 @@
import { createMemoizedAppSelector } from 'app/store/createMemoizedSelector'; import { createMemoizedAppSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { deepClone } from 'common/util/deepClone'; import { deepClone } from 'common/util/deepClone';
import { selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice'; import { selectCanvasV2Slice, selectEntityOrThrow } from 'features/controlLayers/store/selectors';
import { selectControlLayerEntityOrThrow } from 'features/controlLayers/store/controlLayersReducers';
import type { import type {
CanvasEntityIdentifier, CanvasEntityIdentifier,
ControlNetConfig, ControlNetConfig,
@ -14,11 +13,11 @@ import { zModelIdentifierField } from 'features/nodes/types/common';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useControlNetAndT2IAdapterModels, useIPAdapterModels } from 'services/api/hooks/modelsByType'; import { useControlNetAndT2IAdapterModels, useIPAdapterModels } from 'services/api/hooks/modelsByType';
export const useControlLayerControlAdapter = (entityIdentifier: CanvasEntityIdentifier) => { export const useControlLayerControlAdapter = (entityIdentifier: CanvasEntityIdentifier<'control_layer'>) => {
const selectControlAdapter = useMemo( const selectControlAdapter = useMemo(
() => () =>
createMemoizedAppSelector(selectCanvasV2Slice, (canvasV2) => { createMemoizedAppSelector(selectCanvasV2Slice, (canvasV2) => {
const layer = selectControlLayerEntityOrThrow(canvasV2, entityIdentifier.id); const layer = selectEntityOrThrow(canvasV2, entityIdentifier);
return layer.controlAdapter; return layer.controlAdapter;
}), }),
[entityIdentifier] [entityIdentifier]

View File

@ -25,10 +25,10 @@ import {
entitySelected, entitySelected,
eraserWidthChanged, eraserWidthChanged,
fillChanged, fillChanged,
selectAllRenderableEntities,
toolBufferChanged, toolBufferChanged,
toolChanged, toolChanged,
} from 'features/controlLayers/store/canvasV2Slice'; } from 'features/controlLayers/store/canvasV2Slice';
import { selectAllRenderableEntities } from 'features/controlLayers/store/selectors';
import type { import type {
CanvasControlLayerState, CanvasControlLayerState,
CanvasEntityIdentifier, CanvasEntityIdentifier,

View File

@ -1,6 +1,6 @@
import type { PayloadAction } from '@reduxjs/toolkit'; import type { PayloadAction } from '@reduxjs/toolkit';
import { createAction, createSlice } from '@reduxjs/toolkit'; import { createAction, createSlice } from '@reduxjs/toolkit';
import type { PersistConfig, RootState } from 'app/store/store'; import type { PersistConfig } 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 { getPrefixedId } from 'features/controlLayers/konva/util'; import { getPrefixedId } from 'features/controlLayers/konva/util';
@ -13,6 +13,7 @@ import { lorasReducers } from 'features/controlLayers/store/lorasReducers';
import { paramsReducers } from 'features/controlLayers/store/paramsReducers'; import { paramsReducers } from 'features/controlLayers/store/paramsReducers';
import { rasterLayersReducers } from 'features/controlLayers/store/rasterLayersReducers'; import { rasterLayersReducers } from 'features/controlLayers/store/rasterLayersReducers';
import { regionsReducers } from 'features/controlLayers/store/regionsReducers'; import { regionsReducers } from 'features/controlLayers/store/regionsReducers';
import { selectAllEntities, selectAllEntitiesOfType, selectEntity } from 'features/controlLayers/store/selectors';
import { sessionReducers } from 'features/controlLayers/store/sessionReducers'; import { sessionReducers } from 'features/controlLayers/store/sessionReducers';
import { settingsReducers } from 'features/controlLayers/store/settingsReducers'; import { settingsReducers } from 'features/controlLayers/store/settingsReducers';
import { toolReducers } from 'features/controlLayers/store/toolReducers'; import { toolReducers } from 'features/controlLayers/store/toolReducers';
@ -25,12 +26,7 @@ import { atom } from 'nanostores';
import { assert } from 'tsafe'; import { assert } from 'tsafe';
import type { import type {
CanvasControlLayerState,
CanvasEntityIdentifier, CanvasEntityIdentifier,
CanvasEntityState,
CanvasInpaintMaskState,
CanvasRasterLayerState,
CanvasRegionalGuidanceState,
CanvasV2State, CanvasV2State,
Coordinate, Coordinate,
EntityBrushLineAddedPayload, EntityBrushLineAddedPayload,
@ -143,60 +139,6 @@ const initialState: CanvasV2State = {
}, },
}; };
export function selectEntity(state: CanvasV2State, { id, type }: CanvasEntityIdentifier) {
switch (type) {
case 'raster_layer':
return state.rasterLayers.entities.find((entity) => entity.id === id);
case 'control_layer':
return state.controlLayers.entities.find((entity) => entity.id === id);
case 'inpaint_mask':
return state.inpaintMasks.entities.find((entity) => entity.id === id);
case 'regional_guidance':
return state.regions.entities.find((entity) => entity.id === id);
case 'ip_adapter':
return state.ipAdapters.entities.find((entity) => entity.id === id);
default:
return;
}
}
function selectAllEntitiesOfType(state: CanvasV2State, type: CanvasEntityState['type']): CanvasEntityState[] {
switch (type) {
case 'raster_layer':
return state.rasterLayers.entities;
case 'control_layer':
return state.controlLayers.entities;
case 'inpaint_mask':
return state.inpaintMasks.entities;
case 'regional_guidance':
return state.regions.entities;
case 'ip_adapter':
return state.ipAdapters.entities;
}
}
function selectAllEntities(state: CanvasV2State): CanvasEntityState[] {
// These are in the same order as they are displayed in the list!
return [
...state.inpaintMasks.entities.toReversed(),
...state.regions.entities.toReversed(),
...state.ipAdapters.entities.toReversed(),
...state.controlLayers.entities.toReversed(),
...state.rasterLayers.entities.toReversed(),
];
}
export function selectAllRenderableEntities(
state: CanvasV2State
): (CanvasRasterLayerState | CanvasControlLayerState | CanvasInpaintMaskState | CanvasRegionalGuidanceState)[] {
return [
...state.rasterLayers.entities,
...state.controlLayers.entities,
...state.inpaintMasks.entities,
...state.regions.entities,
];
}
export const canvasV2Slice = createSlice({ export const canvasV2Slice = createSlice({
name: 'canvasV2', name: 'canvasV2',
initialState, initialState,
@ -217,7 +159,7 @@ export const canvasV2Slice = createSlice({
const { entityIdentifier } = action.payload; const { entityIdentifier } = action.payload;
state.selectedEntityIdentifier = entityIdentifier; state.selectedEntityIdentifier = entityIdentifier;
}, },
entityNameChanged: (state, action: PayloadAction<EntityIdentifierPayload & { name: string | null }>) => { entityNameChanged: (state, action: PayloadAction<EntityIdentifierPayload<{ name: string | null }>>) => {
const { entityIdentifier, name } = action.payload; const { entityIdentifier, name } = action.payload;
const entity = selectEntity(state, entityIdentifier); const entity = selectEntity(state, entityIdentifier);
if (!entity) { if (!entity) {
@ -617,8 +559,6 @@ export const {
sessionModeChanged, sessionModeChanged,
} = canvasV2Slice.actions; } = canvasV2Slice.actions;
export const selectCanvasV2Slice = (state: RootState) => state.canvasV2;
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
const migrate = (state: any): any => { const migrate = (state: any): any => {
return state; return state;

View File

@ -1,10 +1,10 @@
import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit'; import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit';
import { deepClone } from 'common/util/deepClone'; import { deepClone } from 'common/util/deepClone';
import { getPrefixedId } from 'features/controlLayers/konva/util'; import { getPrefixedId } from 'features/controlLayers/konva/util';
import { selectEntity } from 'features/controlLayers/store/selectors';
import { zModelIdentifierField } from 'features/nodes/types/common'; import { zModelIdentifierField } from 'features/nodes/types/common';
import { merge, omit } from 'lodash-es'; import { merge, omit } from 'lodash-es';
import type { ControlNetModelConfig, T2IAdapterModelConfig } from 'services/api/types'; import type { ControlNetModelConfig, T2IAdapterModelConfig } from 'services/api/types';
import { assert } from 'tsafe';
import type { import type {
CanvasControlLayerState, CanvasControlLayerState,
@ -12,18 +12,11 @@ import type {
CanvasV2State, CanvasV2State,
ControlModeV2, ControlModeV2,
ControlNetConfig, ControlNetConfig,
EntityIdentifierPayload,
T2IAdapterConfig, T2IAdapterConfig,
} from './types'; } from './types';
import { getEntityIdentifier, initialControlNet } from './types'; import { getEntityIdentifier, initialControlNet } from './types';
const selectControlLayerEntity = (state: CanvasV2State, id: string) =>
state.controlLayers.entities.find((entity) => entity.id === id);
export const selectControlLayerEntityOrThrow = (state: CanvasV2State, id: string) => {
const layer = selectControlLayerEntity(state, id);
assert(layer, `Layer with id ${id} not found`);
return layer;
};
export const controlLayersReducers = { export const controlLayersReducers = {
controlLayerAdded: { controlLayerAdded: {
reducer: ( reducer: (
@ -58,9 +51,9 @@ export const controlLayersReducers = {
state.selectedEntityIdentifier = { type: 'control_layer', id: data.id }; state.selectedEntityIdentifier = { type: 'control_layer', id: data.id };
}, },
controlLayerConvertedToRasterLayer: { controlLayerConvertedToRasterLayer: {
reducer: (state, action: PayloadAction<{ id: string; newId: string }>) => { reducer: (state, action: PayloadAction<EntityIdentifierPayload<{ newId: string }, 'control_layer'>>) => {
const { id, newId } = action.payload; const { entityIdentifier, newId } = action.payload;
const layer = selectControlLayerEntity(state, id); const layer = selectEntity(state, entityIdentifier);
if (!layer) { if (!layer) {
return; return;
} }
@ -73,26 +66,30 @@ export const controlLayersReducers = {
}; };
// Remove the control layer // Remove the control layer
state.controlLayers.entities = state.controlLayers.entities.filter((layer) => layer.id !== id); state.controlLayers.entities = state.controlLayers.entities.filter((layer) => layer.id !== entityIdentifier.id);
// Add the new raster layer // Add the new raster layer
state.rasterLayers.entities.push(rasterLayerState); state.rasterLayers.entities.push(rasterLayerState);
state.selectedEntityIdentifier = { type: rasterLayerState.type, id: rasterLayerState.id }; state.selectedEntityIdentifier = { type: rasterLayerState.type, id: rasterLayerState.id };
}, },
prepare: (payload: { id: string }) => ({ prepare: (payload: EntityIdentifierPayload<void, 'control_layer'>) => ({
payload: { ...payload, newId: getPrefixedId('raster_layer') }, payload: { ...payload, newId: getPrefixedId('raster_layer') },
}), }),
}, },
controlLayerModelChanged: ( controlLayerModelChanged: (
state, state,
action: PayloadAction<{ action: PayloadAction<
id: string; EntityIdentifierPayload<
modelConfig: ControlNetModelConfig | T2IAdapterModelConfig | null; {
}> modelConfig: ControlNetModelConfig | T2IAdapterModelConfig | null;
},
'control_layer'
>
>
) => { ) => {
const { id, modelConfig } = action.payload; const { entityIdentifier, modelConfig } = action.payload;
const layer = selectControlLayerEntity(state, id); const layer = selectEntity(state, entityIdentifier);
if (!layer || !layer.controlAdapter) { if (!layer || !layer.controlAdapter) {
return; return;
} }
@ -118,17 +115,23 @@ export const controlLayersReducers = {
layer.controlAdapter = t2iAdapterConfig; layer.controlAdapter = t2iAdapterConfig;
} }
}, },
controlLayerControlModeChanged: (state, action: PayloadAction<{ id: string; controlMode: ControlModeV2 }>) => { controlLayerControlModeChanged: (
const { id, controlMode } = action.payload; state,
const layer = selectControlLayerEntity(state, id); action: PayloadAction<EntityIdentifierPayload<{ controlMode: ControlModeV2 }, 'control_layer'>>
) => {
const { entityIdentifier, controlMode } = action.payload;
const layer = selectEntity(state, entityIdentifier);
if (!layer || !layer.controlAdapter || layer.controlAdapter.type !== 'controlnet') { if (!layer || !layer.controlAdapter || layer.controlAdapter.type !== 'controlnet') {
return; return;
} }
layer.controlAdapter.controlMode = controlMode; layer.controlAdapter.controlMode = controlMode;
}, },
controlLayerWeightChanged: (state, action: PayloadAction<{ id: string; weight: number }>) => { controlLayerWeightChanged: (
const { id, weight } = action.payload; state,
const layer = selectControlLayerEntity(state, id); action: PayloadAction<EntityIdentifierPayload<{ weight: number }, 'control_layer'>>
) => {
const { entityIdentifier, weight } = action.payload;
const layer = selectEntity(state, entityIdentifier);
if (!layer || !layer.controlAdapter) { if (!layer || !layer.controlAdapter) {
return; return;
} }
@ -136,18 +139,21 @@ export const controlLayersReducers = {
}, },
controlLayerBeginEndStepPctChanged: ( controlLayerBeginEndStepPctChanged: (
state, state,
action: PayloadAction<{ id: string; beginEndStepPct: [number, number] }> action: PayloadAction<EntityIdentifierPayload<{ beginEndStepPct: [number, number] }, 'control_layer'>>
) => { ) => {
const { id, beginEndStepPct } = action.payload; const { entityIdentifier, beginEndStepPct } = action.payload;
const layer = selectControlLayerEntity(state, id); const layer = selectEntity(state, entityIdentifier);
if (!layer || !layer.controlAdapter) { if (!layer || !layer.controlAdapter) {
return; return;
} }
layer.controlAdapter.beginEndStepPct = beginEndStepPct; layer.controlAdapter.beginEndStepPct = beginEndStepPct;
}, },
controlLayerWithTransparencyEffectToggled: (state, action: PayloadAction<{ id: string }>) => { controlLayerWithTransparencyEffectToggled: (
const { id } = action.payload; state,
const layer = selectControlLayerEntity(state, id); action: PayloadAction<EntityIdentifierPayload<void, 'control_layer'>>
) => {
const { entityIdentifier } = action.payload;
const layer = selectEntity(state, entityIdentifier);
if (!layer) { if (!layer) {
return; return;
} }

View File

@ -1,23 +1,15 @@
import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit'; import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit';
import { getPrefixedId } from 'features/controlLayers/konva/util'; import { getPrefixedId } from 'features/controlLayers/konva/util';
import { import { selectEntity } from 'features/controlLayers/store/selectors';
type CanvasInpaintMaskState, import type {
type CanvasV2State, CanvasInpaintMaskState,
type EntityIdentifierPayload, CanvasV2State,
type FillStyle, EntityIdentifierPayload,
getEntityIdentifier, FillStyle,
type RgbColor, RgbColor,
} from 'features/controlLayers/store/types'; } from 'features/controlLayers/store/types';
import { getEntityIdentifier } from 'features/controlLayers/store/types';
import { merge } from 'lodash-es'; import { merge } from 'lodash-es';
import { assert } from 'tsafe';
const selectInpaintMaskEntity = (state: CanvasV2State, id: string) =>
state.inpaintMasks.entities.find((layer) => layer.id === id);
export const selectInpaintMaskEntityOrThrow = (state: CanvasV2State, id: string) => {
const entity = selectInpaintMaskEntity(state, id);
assert(entity, `Inpaint mask with id ${id} not found`);
return entity;
};
export const inpaintMaskReducers = { export const inpaintMaskReducers = {
inpaintMaskAdded: { inpaintMaskAdded: {
@ -54,17 +46,23 @@ export const inpaintMaskReducers = {
state.inpaintMasks.entities = [data]; state.inpaintMasks.entities = [data];
state.selectedEntityIdentifier = { type: 'inpaint_mask', id: data.id }; state.selectedEntityIdentifier = { type: 'inpaint_mask', id: data.id };
}, },
inpaintMaskFillColorChanged: (state, action: PayloadAction<EntityIdentifierPayload<{ color: RgbColor }>>) => { inpaintMaskFillColorChanged: (
state,
action: PayloadAction<EntityIdentifierPayload<{ color: RgbColor }, 'inpaint_mask'>>
) => {
const { color, entityIdentifier } = action.payload; const { color, entityIdentifier } = action.payload;
const entity = selectInpaintMaskEntity(state, entityIdentifier.id); const entity = selectEntity(state, entityIdentifier);
if (!entity) { if (!entity) {
return; return;
} }
entity.fill.color = color; entity.fill.color = color;
}, },
inpaintMaskFillStyleChanged: (state, action: PayloadAction<EntityIdentifierPayload<{ style: FillStyle }>>) => { inpaintMaskFillStyleChanged: (
state,
action: PayloadAction<EntityIdentifierPayload<{ style: FillStyle }, 'inpaint_mask'>>
) => {
const { style, entityIdentifier } = action.payload; const { style, entityIdentifier } = action.payload;
const entity = selectInpaintMaskEntity(state, entityIdentifier.id); const entity = selectEntity(state, entityIdentifier);
if (!entity) { if (!entity) {
return; return;
} }

View File

@ -1,23 +1,20 @@
import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit'; import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit';
import { deepClone } from 'common/util/deepClone'; import { deepClone } from 'common/util/deepClone';
import { getPrefixedId } from 'features/controlLayers/konva/util'; import { getPrefixedId } from 'features/controlLayers/konva/util';
import { selectEntity } from 'features/controlLayers/store/selectors';
import { zModelIdentifierField } from 'features/nodes/types/common'; import { zModelIdentifierField } from 'features/nodes/types/common';
import { merge } from 'lodash-es'; import { merge } from 'lodash-es';
import type { ImageDTO, IPAdapterModelConfig } from 'services/api/types'; import type { ImageDTO, IPAdapterModelConfig } from 'services/api/types';
import { assert } from 'tsafe';
import { v4 as uuidv4 } from 'uuid';
import type { CanvasIPAdapterState, CanvasV2State, CLIPVisionModelV2, IPMethodV2 } from './types'; import type {
CanvasIPAdapterState,
CanvasV2State,
CLIPVisionModelV2,
EntityIdentifierPayload,
IPMethodV2,
} from './types';
import { getEntityIdentifier, imageDTOToImageWithDims, initialIPAdapter } from './types'; import { getEntityIdentifier, imageDTOToImageWithDims, initialIPAdapter } from './types';
const selectIPAdapterEntity = (state: CanvasV2State, id: string) =>
state.ipAdapters.entities.find((ipa) => ipa.id === id);
export const selectIPAdapterEntityOrThrow = (state: CanvasV2State, id: string) => {
const entity = selectIPAdapterEntity(state, id);
assert(entity, `IP Adapter with id ${id} not found`);
return entity;
};
export const ipAdaptersReducers = { export const ipAdaptersReducers = {
ipaAdded: { ipaAdded: {
reducer: ( reducer: (
@ -47,52 +44,61 @@ export const ipAdaptersReducers = {
state.ipAdapters.entities.push(data); state.ipAdapters.entities.push(data);
state.selectedEntityIdentifier = { type: 'ip_adapter', id: data.id }; state.selectedEntityIdentifier = { type: 'ip_adapter', id: data.id };
}, },
ipaImageChanged: { ipaImageChanged: (
reducer: (state, action: PayloadAction<{ id: string; imageDTO: ImageDTO | null }>) => { state,
const { id, imageDTO } = action.payload; action: PayloadAction<EntityIdentifierPayload<{ imageDTO: ImageDTO | null }, 'ip_adapter'>>
const entity = selectIPAdapterEntity(state, id); ) => {
if (!entity) { const { entityIdentifier, imageDTO } = action.payload;
return; const entity = selectEntity(state, entityIdentifier);
} if (!entity) {
entity.ipAdapter.image = imageDTO ? imageDTOToImageWithDims(imageDTO) : null; return;
}, }
prepare: (payload: { id: string; imageDTO: ImageDTO | null }) => ({ payload: { ...payload, objectId: uuidv4() } }), entity.ipAdapter.image = imageDTO ? imageDTOToImageWithDims(imageDTO) : null;
}, },
ipaMethodChanged: (state, action: PayloadAction<{ id: string; method: IPMethodV2 }>) => { ipaMethodChanged: (state, action: PayloadAction<EntityIdentifierPayload<{ method: IPMethodV2 }, 'ip_adapter'>>) => {
const { id, method } = action.payload; const { entityIdentifier, method } = action.payload;
const entity = selectIPAdapterEntity(state, id); const entity = selectEntity(state, entityIdentifier);
if (!entity) { if (!entity) {
return; return;
} }
entity.ipAdapter.method = method; entity.ipAdapter.method = method;
}, },
ipaModelChanged: (state, action: PayloadAction<{ id: string; modelConfig: IPAdapterModelConfig | null }>) => { ipaModelChanged: (
const { id, modelConfig } = action.payload; state,
const entity = selectIPAdapterEntity(state, id); action: PayloadAction<EntityIdentifierPayload<{ modelConfig: IPAdapterModelConfig | null }, 'ip_adapter'>>
) => {
const { entityIdentifier, modelConfig } = action.payload;
const entity = selectEntity(state, entityIdentifier);
if (!entity) { if (!entity) {
return; return;
} }
entity.ipAdapter.model = modelConfig ? zModelIdentifierField.parse(modelConfig) : null; entity.ipAdapter.model = modelConfig ? zModelIdentifierField.parse(modelConfig) : null;
}, },
ipaCLIPVisionModelChanged: (state, action: PayloadAction<{ id: string; clipVisionModel: CLIPVisionModelV2 }>) => { ipaCLIPVisionModelChanged: (
const { id, clipVisionModel } = action.payload; state,
const entity = selectIPAdapterEntity(state, id); action: PayloadAction<EntityIdentifierPayload<{ clipVisionModel: CLIPVisionModelV2 }, 'ip_adapter'>>
) => {
const { entityIdentifier, clipVisionModel } = action.payload;
const entity = selectEntity(state, entityIdentifier);
if (!entity) { if (!entity) {
return; return;
} }
entity.ipAdapter.clipVisionModel = clipVisionModel; entity.ipAdapter.clipVisionModel = clipVisionModel;
}, },
ipaWeightChanged: (state, action: PayloadAction<{ id: string; weight: number }>) => { ipaWeightChanged: (state, action: PayloadAction<EntityIdentifierPayload<{ weight: number }, 'ip_adapter'>>) => {
const { id, weight } = action.payload; const { entityIdentifier, weight } = action.payload;
const entity = selectIPAdapterEntity(state, id); const entity = selectEntity(state, entityIdentifier);
if (!entity) { if (!entity) {
return; return;
} }
entity.ipAdapter.weight = weight; entity.ipAdapter.weight = weight;
}, },
ipaBeginEndStepPctChanged: (state, action: PayloadAction<{ id: string; beginEndStepPct: [number, number] }>) => { ipaBeginEndStepPctChanged: (
const { id, beginEndStepPct } = action.payload; state,
const entity = selectIPAdapterEntity(state, id); action: PayloadAction<EntityIdentifierPayload<{ beginEndStepPct: [number, number] }, 'ip_adapter'>>
) => {
const { entityIdentifier, beginEndStepPct } = action.payload;
const entity = selectEntity(state, entityIdentifier);
if (!entity) { if (!entity) {
return; return;
} }

View File

@ -1,14 +1,12 @@
import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit'; import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit';
import { deepClone } from 'common/util/deepClone'; import { deepClone } from 'common/util/deepClone';
import { getPrefixedId } from 'features/controlLayers/konva/util'; import { getPrefixedId } from 'features/controlLayers/konva/util';
import { selectEntity } from 'features/controlLayers/store/selectors';
import { merge } from 'lodash-es'; import { merge } from 'lodash-es';
import type { CanvasControlLayerState, CanvasRasterLayerState, CanvasV2State } from './types'; import type { CanvasControlLayerState, CanvasRasterLayerState, CanvasV2State, EntityIdentifierPayload } from './types';
import { getEntityIdentifier, initialControlNet } from './types'; import { getEntityIdentifier, initialControlNet } from './types';
const selectRasterLayerEntity = (state: CanvasV2State, id: string) =>
state.rasterLayers.entities.find((layer) => layer.id === id);
export const rasterLayersReducers = { export const rasterLayersReducers = {
rasterLayerAdded: { rasterLayerAdded: {
reducer: ( reducer: (
@ -38,12 +36,12 @@ export const rasterLayersReducers = {
rasterLayerRecalled: (state, action: PayloadAction<{ data: CanvasRasterLayerState }>) => { rasterLayerRecalled: (state, action: PayloadAction<{ data: CanvasRasterLayerState }>) => {
const { data } = action.payload; const { data } = action.payload;
state.rasterLayers.entities.push(data); state.rasterLayers.entities.push(data);
state.selectedEntityIdentifier = { type: 'raster_layer', id: data.id }; state.selectedEntityIdentifier = getEntityIdentifier(data);
}, },
rasterLayerConvertedToControlLayer: { rasterLayerConvertedToControlLayer: {
reducer: (state, action: PayloadAction<{ id: string; newId: string }>) => { reducer: (state, action: PayloadAction<EntityIdentifierPayload<{ newId: string }, 'raster_layer'>>) => {
const { id, newId } = action.payload; const { entityIdentifier, newId } = action.payload;
const layer = selectRasterLayerEntity(state, id); const layer = selectEntity(state, entityIdentifier);
if (!layer) { if (!layer) {
return; return;
} }
@ -58,14 +56,14 @@ export const rasterLayersReducers = {
}; };
// Remove the raster layer // Remove the raster layer
state.rasterLayers.entities = state.rasterLayers.entities.filter((layer) => layer.id !== id); state.rasterLayers.entities = state.rasterLayers.entities.filter((layer) => layer.id !== entityIdentifier.id);
// Add the converted control layer // Add the converted control layer
state.controlLayers.entities.push(controlLayerState); state.controlLayers.entities.push(controlLayerState);
state.selectedEntityIdentifier = { type: controlLayerState.type, id: controlLayerState.id }; state.selectedEntityIdentifier = { type: controlLayerState.type, id: controlLayerState.id };
}, },
prepare: (payload: { id: string }) => ({ prepare: (payload: EntityIdentifierPayload<void, 'raster_layer'>) => ({
payload: { ...payload, newId: getPrefixedId('control_layer') }, payload: { ...payload, newId: getPrefixedId('control_layer') },
}), }),
}, },

View File

@ -1,9 +1,11 @@
import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit'; import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit';
import { deepClone } from 'common/util/deepClone'; import { deepClone } from 'common/util/deepClone';
import { getPrefixedId } from 'features/controlLayers/konva/util'; import { getPrefixedId } from 'features/controlLayers/konva/util';
import { selectEntity, selectRegionalGuidanceIPAdapter } from 'features/controlLayers/store/selectors';
import type { import type {
CanvasV2State, CanvasV2State,
CLIPVisionModelV2, CLIPVisionModelV2,
EntityIdentifierPayload,
FillStyle, FillStyle,
IPMethodV2, IPMethodV2,
RegionalGuidanceIPAdapterConfig, RegionalGuidanceIPAdapterConfig,
@ -17,22 +19,6 @@ import { assert } from 'tsafe';
import type { CanvasRegionalGuidanceState } from './types'; import type { CanvasRegionalGuidanceState } from './types';
const selectRegionalGuidanceEntity = (state: CanvasV2State, id: string) => {
return state.regions.entities.find((rg) => rg.id === id);
};
const selectRegionalGuidanceIPAdapter = (state: CanvasV2State, id: string, ipAdapterId: string) => {
const entity = state.regions.entities.find((rg) => rg.id === id);
if (!entity) {
return;
}
return entity.ipAdapters.find((ipa) => ipa.id === ipAdapterId);
};
export const selectRegionalGuidanceEntityOrThrow = (state: CanvasV2State, id: string) => {
const rg = selectRegionalGuidanceEntity(state, id);
assert(rg, `Region with id ${id} not found`);
return rg;
};
const DEFAULT_MASK_COLORS: RgbColor[] = [ const DEFAULT_MASK_COLORS: RgbColor[] = [
{ r: 121, g: 157, b: 219 }, // rgb(121, 157, 219) { r: 121, g: 157, b: 219 }, // rgb(121, 157, 219)
{ r: 131, g: 214, b: 131 }, // rgb(131, 214, 131) { r: 131, g: 214, b: 131 }, // rgb(131, 214, 131)
@ -94,42 +80,54 @@ export const regionsReducers = {
state.regions.entities.push(data); state.regions.entities.push(data);
state.selectedEntityIdentifier = { type: 'regional_guidance', id: data.id }; state.selectedEntityIdentifier = { type: 'regional_guidance', id: data.id };
}, },
rgPositivePromptChanged: (state, action: PayloadAction<{ id: string; prompt: string | null }>) => { rgPositivePromptChanged: (
const { id, prompt } = action.payload; state,
const entity = selectRegionalGuidanceEntity(state, id); action: PayloadAction<EntityIdentifierPayload<{ prompt: string | null }, 'regional_guidance'>>
) => {
const { entityIdentifier, prompt } = action.payload;
const entity = selectEntity(state, entityIdentifier);
if (!entity) { if (!entity) {
return; return;
} }
entity.positivePrompt = prompt; entity.positivePrompt = prompt;
}, },
rgNegativePromptChanged: (state, action: PayloadAction<{ id: string; prompt: string | null }>) => { rgNegativePromptChanged: (
const { id, prompt } = action.payload; state,
const entity = selectRegionalGuidanceEntity(state, id); action: PayloadAction<EntityIdentifierPayload<{ prompt: string | null }, 'regional_guidance'>>
) => {
const { entityIdentifier, prompt } = action.payload;
const entity = selectEntity(state, entityIdentifier);
if (!entity) { if (!entity) {
return; return;
} }
entity.negativePrompt = prompt; entity.negativePrompt = prompt;
}, },
rgFillColorChanged: (state, action: PayloadAction<{ id: string; color: RgbColor }>) => { rgFillColorChanged: (
const { id, color } = action.payload; state,
const entity = selectRegionalGuidanceEntity(state, id); action: PayloadAction<EntityIdentifierPayload<{ color: RgbColor }, 'regional_guidance'>>
) => {
const { entityIdentifier, color } = action.payload;
const entity = selectEntity(state, entityIdentifier);
if (!entity) { if (!entity) {
return; return;
} }
entity.fill.color = color; entity.fill.color = color;
}, },
rgFillStyleChanged: (state, action: PayloadAction<{ id: string; style: FillStyle }>) => { rgFillStyleChanged: (
const { id, style } = action.payload; state,
const entity = selectRegionalGuidanceEntity(state, id); action: PayloadAction<EntityIdentifierPayload<{ style: FillStyle }, 'regional_guidance'>>
) => {
const { entityIdentifier, style } = action.payload;
const entity = selectEntity(state, entityIdentifier);
if (!entity) { if (!entity) {
return; return;
} }
entity.fill.style = style; entity.fill.style = style;
}, },
rgAutoNegativeToggled: (state, action: PayloadAction<{ id: string }>) => { rgAutoNegativeToggled: (state, action: PayloadAction<EntityIdentifierPayload<void, 'regional_guidance'>>) => {
const { id } = action.payload; const { entityIdentifier } = action.payload;
const rg = selectRegionalGuidanceEntity(state, id); const rg = selectEntity(state, entityIdentifier);
if (!rg) { if (!rg) {
return; return;
} }
@ -138,10 +136,15 @@ export const regionsReducers = {
rgIPAdapterAdded: { rgIPAdapterAdded: {
reducer: ( reducer: (
state, state,
action: PayloadAction<{ id: string; ipAdapterId: string; overrides?: Partial<RegionalGuidanceIPAdapterConfig> }> action: PayloadAction<
EntityIdentifierPayload<
{ ipAdapterId: string; overrides?: Partial<RegionalGuidanceIPAdapterConfig> },
'regional_guidance'
>
>
) => { ) => {
const { id, overrides, ipAdapterId } = action.payload; const { entityIdentifier, overrides, ipAdapterId } = action.payload;
const entity = selectRegionalGuidanceEntity(state, id); const entity = selectEntity(state, entityIdentifier);
if (!entity) { if (!entity) {
return; return;
} }
@ -149,13 +152,18 @@ export const regionsReducers = {
merge(ipAdapter, overrides); merge(ipAdapter, overrides);
entity.ipAdapters.push(ipAdapter); entity.ipAdapters.push(ipAdapter);
}, },
prepare: (payload: { id: string; overrides?: Partial<RegionalGuidanceIPAdapterConfig> }) => ({ prepare: (
payload: EntityIdentifierPayload<{ overrides?: Partial<RegionalGuidanceIPAdapterConfig> }, 'regional_guidance'>
) => ({
payload: { ...payload, ipAdapterId: getPrefixedId('regional_guidance_ip_adapter') }, payload: { ...payload, ipAdapterId: getPrefixedId('regional_guidance_ip_adapter') },
}), }),
}, },
rgIPAdapterDeleted: (state, action: PayloadAction<{ id: string; ipAdapterId: string }>) => { rgIPAdapterDeleted: (
const { id, ipAdapterId } = action.payload; state,
const entity = selectRegionalGuidanceEntity(state, id); action: PayloadAction<EntityIdentifierPayload<{ ipAdapterId: string }, 'regional_guidance'>>
) => {
const { entityIdentifier, ipAdapterId } = action.payload;
const entity = selectEntity(state, entityIdentifier);
if (!entity) { if (!entity) {
return; return;
} }
@ -163,18 +171,23 @@ export const regionsReducers = {
}, },
rgIPAdapterImageChanged: ( rgIPAdapterImageChanged: (
state, state,
action: PayloadAction<{ id: string; ipAdapterId: string; imageDTO: ImageDTO | null }> action: PayloadAction<
EntityIdentifierPayload<{ ipAdapterId: string; imageDTO: ImageDTO | null }, 'regional_guidance'>
>
) => { ) => {
const { id, ipAdapterId, imageDTO } = action.payload; const { entityIdentifier, ipAdapterId, imageDTO } = action.payload;
const ipAdapter = selectRegionalGuidanceIPAdapter(state, id, ipAdapterId); const ipAdapter = selectRegionalGuidanceIPAdapter(state, entityIdentifier, ipAdapterId);
if (!ipAdapter) { if (!ipAdapter) {
return; return;
} }
ipAdapter.image = imageDTO ? imageDTOToImageWithDims(imageDTO) : null; ipAdapter.image = imageDTO ? imageDTOToImageWithDims(imageDTO) : null;
}, },
rgIPAdapterWeightChanged: (state, action: PayloadAction<{ id: string; ipAdapterId: string; weight: number }>) => { rgIPAdapterWeightChanged: (
const { id, ipAdapterId, weight } = action.payload; state,
const ipAdapter = selectRegionalGuidanceIPAdapter(state, id, ipAdapterId); action: PayloadAction<EntityIdentifierPayload<{ ipAdapterId: string; weight: number }, 'regional_guidance'>>
) => {
const { entityIdentifier, ipAdapterId, weight } = action.payload;
const ipAdapter = selectRegionalGuidanceIPAdapter(state, entityIdentifier, ipAdapterId);
if (!ipAdapter) { if (!ipAdapter) {
return; return;
} }
@ -182,18 +195,23 @@ export const regionsReducers = {
}, },
rgIPAdapterBeginEndStepPctChanged: ( rgIPAdapterBeginEndStepPctChanged: (
state, state,
action: PayloadAction<{ id: string; ipAdapterId: string; beginEndStepPct: [number, number] }> action: PayloadAction<
EntityIdentifierPayload<{ ipAdapterId: string; beginEndStepPct: [number, number] }, 'regional_guidance'>
>
) => { ) => {
const { id, ipAdapterId, beginEndStepPct } = action.payload; const { entityIdentifier, ipAdapterId, beginEndStepPct } = action.payload;
const ipAdapter = selectRegionalGuidanceIPAdapter(state, id, ipAdapterId); const ipAdapter = selectRegionalGuidanceIPAdapter(state, entityIdentifier, ipAdapterId);
if (!ipAdapter) { if (!ipAdapter) {
return; return;
} }
ipAdapter.beginEndStepPct = beginEndStepPct; ipAdapter.beginEndStepPct = beginEndStepPct;
}, },
rgIPAdapterMethodChanged: (state, action: PayloadAction<{ id: string; ipAdapterId: string; method: IPMethodV2 }>) => { rgIPAdapterMethodChanged: (
const { id, ipAdapterId, method } = action.payload; state,
const ipAdapter = selectRegionalGuidanceIPAdapter(state, id, ipAdapterId); action: PayloadAction<EntityIdentifierPayload<{ ipAdapterId: string; method: IPMethodV2 }, 'regional_guidance'>>
) => {
const { entityIdentifier, ipAdapterId, method } = action.payload;
const ipAdapter = selectRegionalGuidanceIPAdapter(state, entityIdentifier, ipAdapterId);
if (!ipAdapter) { if (!ipAdapter) {
return; return;
} }
@ -201,14 +219,18 @@ export const regionsReducers = {
}, },
rgIPAdapterModelChanged: ( rgIPAdapterModelChanged: (
state, state,
action: PayloadAction<{ action: PayloadAction<
id: string; EntityIdentifierPayload<
ipAdapterId: string; {
modelConfig: IPAdapterModelConfig | null; ipAdapterId: string;
}> modelConfig: IPAdapterModelConfig | null;
},
'regional_guidance'
>
>
) => { ) => {
const { id, ipAdapterId, modelConfig } = action.payload; const { entityIdentifier, ipAdapterId, modelConfig } = action.payload;
const ipAdapter = selectRegionalGuidanceIPAdapter(state, id, ipAdapterId); const ipAdapter = selectRegionalGuidanceIPAdapter(state, entityIdentifier, ipAdapterId);
if (!ipAdapter) { if (!ipAdapter) {
return; return;
} }
@ -216,10 +238,12 @@ export const regionsReducers = {
}, },
rgIPAdapterCLIPVisionModelChanged: ( rgIPAdapterCLIPVisionModelChanged: (
state, state,
action: PayloadAction<{ id: string; ipAdapterId: string; clipVisionModel: CLIPVisionModelV2 }> action: PayloadAction<
EntityIdentifierPayload<{ ipAdapterId: string; clipVisionModel: CLIPVisionModelV2 }, 'regional_guidance'>
>
) => { ) => {
const { id, ipAdapterId, clipVisionModel } = action.payload; const { entityIdentifier, ipAdapterId, clipVisionModel } = action.payload;
const ipAdapter = selectRegionalGuidanceIPAdapter(state, id, ipAdapterId); const ipAdapter = selectRegionalGuidanceIPAdapter(state, entityIdentifier, ipAdapterId);
if (!ipAdapter) { if (!ipAdapter) {
return; return;
} }

View File

@ -1,7 +1,32 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice'; import type { RootState } from 'app/store/store';
import type {
CanvasControlLayerState,
CanvasEntityIdentifier,
CanvasEntityState,
CanvasInpaintMaskState,
CanvasRasterLayerState,
CanvasRegionalGuidanceState,
CanvasV2State,
} from 'features/controlLayers/store/types';
import { getOptimalDimension } from 'features/parameters/util/optimalDimension'; import { getOptimalDimension } from 'features/parameters/util/optimalDimension';
import { assert } from 'tsafe';
/**
* Selects the canvasV2 slice from the root state
*/
export const selectCanvasV2Slice = (state: RootState) => state.canvasV2;
/**
* Selects the total canvas entity count:
* - Regions
* - IP adapters
* - Raster layers
* - Control layers
* - Inpaint masks
*
* It does not check for validity of the entities.
*/
export const selectEntityCount = createSelector(selectCanvasV2Slice, (canvasV2) => { export const selectEntityCount = createSelector(selectCanvasV2Slice, (canvasV2) => {
return ( return (
canvasV2.regions.entities.length + canvasV2.regions.entities.length +
@ -12,6 +37,134 @@ export const selectEntityCount = createSelector(selectCanvasV2Slice, (canvasV2)
); );
}); });
/**
* Selects the optimal dimension for the canvas based on the currently-model
*/
export const selectOptimalDimension = createSelector(selectCanvasV2Slice, (canvasV2) => { export const selectOptimalDimension = createSelector(selectCanvasV2Slice, (canvasV2) => {
return getOptimalDimension(canvasV2.params.model); return getOptimalDimension(canvasV2.params.model);
}); });
/**
* Selects a single entity from the canvasV2 slice. If the entity identifier is narrowed to a specific type, the
* return type will be narrowed as well.
*/
export function selectEntity<T extends CanvasEntityIdentifier>(
state: CanvasV2State,
entityIdentifier: T
): Extract<CanvasEntityState, T> | undefined {
const { id, type } = entityIdentifier;
let entity: CanvasEntityState | undefined = undefined;
switch (type) {
case 'raster_layer':
entity = state.rasterLayers.entities.find((entity) => entity.id === id);
break;
case 'control_layer':
entity = state.controlLayers.entities.find((entity) => entity.id === id);
break;
case 'inpaint_mask':
entity = state.inpaintMasks.entities.find((entity) => entity.id === id);
break;
case 'regional_guidance':
entity = state.regions.entities.find((entity) => entity.id === id);
break;
case 'ip_adapter':
entity = state.ipAdapters.entities.find((entity) => entity.id === id);
break;
}
// This cast is safe, but TS seems to be unable to infer the type
return entity as Extract<CanvasEntityState, T>;
}
/**
* Selected an entity from the canvasV2 slice. If the entity is not found, an error is thrown.
* Wrapper around {@link selectEntity}.
*/
export function selectEntityOrThrow<T extends CanvasEntityIdentifier>(
state: CanvasV2State,
entityIdentifier: T
): Extract<CanvasEntityState, T> {
const entity = selectEntity(state, entityIdentifier);
assert(entity, `Entity with id ${entityIdentifier.id} not found`);
return entity;
}
/**
* Selects all entities of the given type.
*/
export function selectAllEntitiesOfType<T extends CanvasEntityState['type']>(
state: CanvasV2State,
type: T
): Extract<CanvasEntityState, { type: T }>[] {
let entities: CanvasEntityState[] = [];
switch (type) {
case 'raster_layer':
entities = state.rasterLayers.entities;
break;
case 'control_layer':
entities = state.controlLayers.entities;
break;
case 'inpaint_mask':
entities = state.inpaintMasks.entities;
break;
case 'regional_guidance':
entities = state.regions.entities;
break;
case 'ip_adapter':
entities = state.ipAdapters.entities;
break;
}
// This cast is safe, but TS seems to be unable to infer the type
return entities as Extract<CanvasEntityState, { type: T }>[];
}
/**
* Selects all entities, in the order they are displayed in the list.
*/
export function selectAllEntities(state: CanvasV2State): CanvasEntityState[] {
// These are in the same order as they are displayed in the list!
return [
...state.inpaintMasks.entities.toReversed(),
...state.regions.entities.toReversed(),
...state.ipAdapters.entities.toReversed(),
...state.controlLayers.entities.toReversed(),
...state.rasterLayers.entities.toReversed(),
];
}
/**
* Selects all _renderable_ entities:
* - Raster layers
* - Control layers
* - Inpaint masks
* - Regional guidance
*/
export function selectAllRenderableEntities(
state: CanvasV2State
): (CanvasRasterLayerState | CanvasControlLayerState | CanvasInpaintMaskState | CanvasRegionalGuidanceState)[] {
return [
...state.rasterLayers.entities,
...state.controlLayers.entities,
...state.inpaintMasks.entities,
...state.regions.entities,
];
}
/**
* Selects the IP adapter for the specific Regional Guidance layer.
*/
export function selectRegionalGuidanceIPAdapter(
state: CanvasV2State,
entityIdentifier: CanvasEntityIdentifier<'regional_guidance'>,
ipAdapterId: string
) {
const entity = selectEntity(state, entityIdentifier);
if (!entity) {
return undefined;
}
return entity.ipAdapters.find((ipAdapter) => ipAdapter.id === ipAdapterId);
}

View File

@ -1,3 +1,4 @@
import type { SerializableObject } from 'common/types';
import { getPrefixedId } from 'features/controlLayers/konva/util'; import { getPrefixedId } from 'features/controlLayers/konva/util';
import { zModelIdentifierField } from 'features/nodes/types/common'; import { zModelIdentifierField } from 'features/nodes/types/common';
import type { AspectRatioState } from 'features/parameters/components/DocumentSize/types'; import type { AspectRatioState } from 'features/parameters/components/DocumentSize/types';
@ -692,7 +693,9 @@ export type CanvasEntityState =
| CanvasRegionalGuidanceState | CanvasRegionalGuidanceState
| CanvasInpaintMaskState | CanvasInpaintMaskState
| CanvasIPAdapterState; | CanvasIPAdapterState;
export type CanvasEntityIdentifier = Pick<CanvasEntityState, 'id' | 'type'>;
export type CanvasEntityType = CanvasEntityState['type'];
export type CanvasEntityIdentifier<T extends CanvasEntityType = CanvasEntityType> = { id: string; type: T };
export type LoRA = { export type LoRA = {
id: string; id: string;
@ -819,7 +822,17 @@ export type StageAttrs = {
scale: number; scale: number;
}; };
export type EntityIdentifierPayload<T = object> = { entityIdentifier: CanvasEntityIdentifier } & T; export type EntityIdentifierPayload<
T extends SerializableObject | void = void,
U extends CanvasEntityType = CanvasEntityType,
> = T extends void
? {
entityIdentifier: CanvasEntityIdentifier<U>;
}
: {
entityIdentifier: CanvasEntityIdentifier<U>;
} & T;
export type EntityMovedPayload = EntityIdentifierPayload<{ position: Coordinate }>; export type EntityMovedPayload = EntityIdentifierPayload<{ position: Coordinate }>;
export type EntityBrushLineAddedPayload = EntityIdentifierPayload<{ brushLine: CanvasBrushLineState }>; export type EntityBrushLineAddedPayload = EntityIdentifierPayload<{ brushLine: CanvasBrushLineState }>;
export type EntityEraserLineAddedPayload = EntityIdentifierPayload<{ eraserLine: CanvasEraserLineState }>; export type EntityEraserLineAddedPayload = EntityIdentifierPayload<{ eraserLine: CanvasEraserLineState }>;
@ -858,6 +871,8 @@ export function isDrawableEntity(
return isDrawableEntityType(entity.type); return isDrawableEntityType(entity.type);
} }
export const getEntityIdentifier = (entity: CanvasEntityState): CanvasEntityIdentifier => { export const getEntityIdentifier = <T extends CanvasEntityType>(
entity: Extract<CanvasEntityState, { type: T }>
): CanvasEntityIdentifier<T> => {
return { id: entity.id, type: entity.type }; return { id: entity.id, type: entity.type };
}; };

View File

@ -1,7 +1,7 @@
import { ConfirmationAlertDialog, Divider, Flex, FormControl, FormLabel, Switch, Text } from '@invoke-ai/ui-library'; import { ConfirmationAlertDialog, Divider, Flex, FormControl, FormLabel, Switch, Text } from '@invoke-ai/ui-library';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice'; import { selectCanvasV2Slice } from 'features/controlLayers/store/selectors';
import { imageDeletionConfirmed } from 'features/deleteImageModal/store/actions'; import { imageDeletionConfirmed } from 'features/deleteImageModal/store/actions';
import { getImageUsage, selectImageUsage } from 'features/deleteImageModal/store/selectors'; import { getImageUsage, selectImageUsage } from 'features/deleteImageModal/store/selectors';
import { import {

View File

@ -1,5 +1,5 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice'; import { selectCanvasV2Slice } from 'features/controlLayers/store/selectors';
import type { CanvasV2State } from 'features/controlLayers/store/types'; import type { CanvasV2State } from 'features/controlLayers/store/types';
import { selectDeleteImageModalSlice } from 'features/deleteImageModal/store/slice'; import { selectDeleteImageModalSlice } from 'features/deleteImageModal/store/slice';
import { selectNodesSlice } from 'features/nodes/store/nodesSlice'; import { selectNodesSlice } from 'features/nodes/store/nodesSlice';

View File

@ -13,7 +13,7 @@ import {
import { skipToken } from '@reduxjs/toolkit/query'; import { skipToken } from '@reduxjs/toolkit/query';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice'; import { selectCanvasV2Slice } from 'features/controlLayers/store/selectors';
import ImageUsageMessage from 'features/deleteImageModal/components/ImageUsageMessage'; import ImageUsageMessage from 'features/deleteImageModal/components/ImageUsageMessage';
import { getImageUsage } from 'features/deleteImageModal/store/selectors'; import { getImageUsage } from 'features/deleteImageModal/store/selectors';
import type { ImageUsage } from 'features/deleteImageModal/store/types'; import type { ImageUsage } from 'features/deleteImageModal/store/types';

View File

@ -1,7 +1,7 @@
import { Flex } from '@invoke-ai/ui-library'; import { Flex } from '@invoke-ai/ui-library';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice'; import { selectCanvasV2Slice } from 'features/controlLayers/store/selectors';
import { LoRACard } from 'features/lora/components/LoRACard'; import { LoRACard } from 'features/lora/components/LoRACard';
import { memo } from 'react'; import { memo } from 'react';

View File

@ -4,7 +4,8 @@ import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover'; import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { useGroupedModelCombobox } from 'common/hooks/useGroupedModelCombobox'; import { useGroupedModelCombobox } from 'common/hooks/useGroupedModelCombobox';
import { loraAdded, selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice'; import { loraAdded } from 'features/controlLayers/store/canvasV2Slice';
import { selectCanvasV2Slice } from 'features/controlLayers/store/selectors';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useLoRAModels } from 'services/api/hooks/modelsByType'; import { useLoRAModels } from 'services/api/hooks/modelsByType';

View File

@ -1,7 +1,7 @@
import { Flex } from '@invoke-ai/ui-library'; import { Flex } from '@invoke-ai/ui-library';
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice'; import { selectCanvasV2Slice } from 'features/controlLayers/store/selectors';
import { ParamNegativePrompt } from 'features/parameters/components/Core/ParamNegativePrompt'; import { ParamNegativePrompt } from 'features/parameters/components/Core/ParamNegativePrompt';
import { ParamPositivePrompt } from 'features/parameters/components/Core/ParamPositivePrompt'; import { ParamPositivePrompt } from 'features/parameters/components/Core/ParamPositivePrompt';
import { ParamSDXLNegativeStylePrompt } from 'features/sdxl/components/SDXLPrompts/ParamSDXLNegativeStylePrompt'; import { ParamSDXLNegativeStylePrompt } from 'features/sdxl/components/SDXLPrompts/ParamSDXLNegativeStylePrompt';

View File

@ -2,7 +2,7 @@ import { Divider, Flex, ListItem, Text, Tooltip, UnorderedList } from '@invoke-a
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { useIsReadyToEnqueue } from 'common/hooks/useIsReadyToEnqueue'; import { useIsReadyToEnqueue } from 'common/hooks/useIsReadyToEnqueue';
import { selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice'; import { selectCanvasV2Slice } from 'features/controlLayers/store/selectors';
import { selectDynamicPromptsSlice } from 'features/dynamicPrompts/store/dynamicPromptsSlice'; import { selectDynamicPromptsSlice } from 'features/dynamicPrompts/store/dynamicPromptsSlice';
import { getShouldProcessPrompt } from 'features/dynamicPrompts/util/getShouldProcessPrompt'; import { getShouldProcessPrompt } from 'features/dynamicPrompts/util/getShouldProcessPrompt';
import type { PropsWithChildren } from 'react'; import type { PropsWithChildren } from 'react';

View File

@ -3,7 +3,7 @@ import { Flex, FormControlGroup, StandaloneAccordion } from '@invoke-ai/ui-libra
import { skipToken } from '@reduxjs/toolkit/query'; import { skipToken } from '@reduxjs/toolkit/query';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice'; import { selectCanvasV2Slice } from 'features/controlLayers/store/selectors';
import ParamCFGRescaleMultiplier from 'features/parameters/components/Advanced/ParamCFGRescaleMultiplier'; import ParamCFGRescaleMultiplier from 'features/parameters/components/Advanced/ParamCFGRescaleMultiplier';
import ParamClipSkip from 'features/parameters/components/Advanced/ParamClipSkip'; import ParamClipSkip from 'features/parameters/components/Advanced/ParamClipSkip';
import ParamSeamlessXAxis from 'features/parameters/components/Seamless/ParamSeamlessXAxis'; import ParamSeamlessXAxis from 'features/parameters/components/Seamless/ParamSeamlessXAxis';

View File

@ -3,7 +3,7 @@ import { Box, Expander, Flex, FormControlGroup, StandaloneAccordion } from '@inv
import { EMPTY_ARRAY } from 'app/store/constants'; import { EMPTY_ARRAY } from 'app/store/constants';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice'; import { selectCanvasV2Slice } from 'features/controlLayers/store/selectors';
import { LoRAList } from 'features/lora/components/LoRAList'; import { LoRAList } from 'features/lora/components/LoRAList';
import LoRASelect from 'features/lora/components/LoRASelect'; import LoRASelect from 'features/lora/components/LoRASelect';
import ParamCFGScale from 'features/parameters/components/Core/ParamCFGScale'; import ParamCFGScale from 'features/parameters/components/Core/ParamCFGScale';

View File

@ -2,7 +2,7 @@ import type { FormLabelProps } from '@invoke-ai/ui-library';
import { Expander, Flex, FormControlGroup, StandaloneAccordion } from '@invoke-ai/ui-library'; import { Expander, Flex, FormControlGroup, StandaloneAccordion } from '@invoke-ai/ui-library';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice'; import { selectCanvasV2Slice } from 'features/controlLayers/store/selectors';
import { HrfSettings } from 'features/hrf/components/HrfSettings'; import { HrfSettings } from 'features/hrf/components/HrfSettings';
import { selectHrfSlice } from 'features/hrf/store/hrfSlice'; import { selectHrfSlice } from 'features/hrf/store/hrfSlice';
import ParamScaleBeforeProcessing from 'features/parameters/components/Canvas/InfillAndScaling/ParamScaleBeforeProcessing'; import ParamScaleBeforeProcessing from 'features/parameters/components/Canvas/InfillAndScaling/ParamScaleBeforeProcessing';

View File

@ -2,7 +2,7 @@ import type { FormLabelProps } from '@invoke-ai/ui-library';
import { Flex, FormControlGroup, StandaloneAccordion, Text } from '@invoke-ai/ui-library'; import { Flex, FormControlGroup, StandaloneAccordion, Text } from '@invoke-ai/ui-library';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { selectCanvasV2Slice } from 'features/controlLayers/store/canvasV2Slice'; import { selectCanvasV2Slice } from 'features/controlLayers/store/selectors';
import ParamSDXLRefinerCFGScale from 'features/sdxl/components/SDXLRefiner/ParamSDXLRefinerCFGScale'; import ParamSDXLRefinerCFGScale from 'features/sdxl/components/SDXLRefiner/ParamSDXLRefinerCFGScale';
import ParamSDXLRefinerModelSelect from 'features/sdxl/components/SDXLRefiner/ParamSDXLRefinerModelSelect'; import ParamSDXLRefinerModelSelect from 'features/sdxl/components/SDXLRefiner/ParamSDXLRefinerModelSelect';
import ParamSDXLRefinerNegativeAestheticScore from 'features/sdxl/components/SDXLRefiner/ParamSDXLRefinerNegativeAestheticScore'; import ParamSDXLRefinerNegativeAestheticScore from 'features/sdxl/components/SDXLRefiner/ParamSDXLRefinerNegativeAestheticScore';