mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
tidy(ui): massive cleanup
- create a context for entity identifiers, massively simplifying UI for each entity int he list - consolidate common redux actions - remove now-unused code
This commit is contained in:
parent
8436a44973
commit
894b8a29b9
@ -4,8 +4,8 @@ import type { AppDispatch, RootState } from 'app/store/store';
|
||||
import {
|
||||
caImageChanged,
|
||||
caProcessedImageChanged,
|
||||
entityDeleted,
|
||||
ipaImageChanged,
|
||||
layerDeleted,
|
||||
} from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { imageDeletionConfirmed } from 'features/deleteImageModal/store/actions';
|
||||
import { isModalOpenChanged } from 'features/deleteImageModal/store/slice';
|
||||
@ -66,7 +66,7 @@ const deleteLayerImages = (state: RootState, dispatch: AppDispatch, imageDTO: Im
|
||||
}
|
||||
}
|
||||
if (shouldDelete) {
|
||||
dispatch(layerDeleted({ id }));
|
||||
dispatch(entityDeleted({ entityIdentifier: { id, type: 'layer' } }));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -1,6 +1,11 @@
|
||||
import { logger } from 'app/logging/logger';
|
||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||
import { caIsEnabledToggled, loraDeleted, modelChanged, vaeSelected } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import {
|
||||
entityIsEnabledToggled,
|
||||
loraDeleted,
|
||||
modelChanged,
|
||||
vaeSelected,
|
||||
} from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { modelSelected } from 'features/parameters/store/actions';
|
||||
import { zParameterModel } from 'features/parameters/types/parameterSchemas';
|
||||
import { toast } from 'features/toast/toast';
|
||||
@ -49,7 +54,7 @@ export const addModelSelectedListener = (startAppListening: AppStartListening) =
|
||||
if (ca.model?.base !== newBaseModel) {
|
||||
modelsCleared += 1;
|
||||
if (ca.isEnabled) {
|
||||
dispatch(caIsEnabledToggled({ id: ca.id }));
|
||||
dispatch(entityIsEnabledToggled({ entityIdentifier: { id: ca.id, type: 'control_adapter' } }));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -1,28 +1,26 @@
|
||||
import { useDisclosure } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { CanvasEntityContainer } from 'features/controlLayers/components/common/CanvasEntityContainer';
|
||||
import { CAHeader } from 'features/controlLayers/components/ControlAdapter/CAEntityHeader';
|
||||
import { CASettings } from 'features/controlLayers/components/ControlAdapter/CASettings';
|
||||
import { entitySelected } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { EntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
|
||||
import { memo, useMemo } from 'react';
|
||||
|
||||
type Props = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
export const CA = memo(({ id }: Props) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const isSelected = useAppSelector((s) => s.canvasV2.selectedEntityIdentifier?.id === id);
|
||||
const entityIdentifier = useMemo<CanvasEntityIdentifier>(() => ({ id, type: 'control_adapter' }), [id]);
|
||||
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true });
|
||||
const onSelect = useCallback(() => {
|
||||
dispatch(entitySelected({ id, type: 'control_adapter' }));
|
||||
}, [dispatch, id]);
|
||||
|
||||
return (
|
||||
<CanvasEntityContainer isSelected={isSelected} onSelect={onSelect}>
|
||||
<CAHeader id={id} isSelected={isSelected} onToggleVisibility={onToggle} />
|
||||
{isOpen && <CASettings id={id} />}
|
||||
</CanvasEntityContainer>
|
||||
<EntityIdentifierContext.Provider value={entityIdentifier}>
|
||||
<CanvasEntityContainer>
|
||||
<CAHeader onToggleVisibility={onToggle} />
|
||||
{isOpen && <CASettings id={id} />}
|
||||
</CanvasEntityContainer>
|
||||
</EntityIdentifierContext.Provider>
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -1,84 +1,14 @@
|
||||
import { Menu, MenuItem, MenuList } from '@invoke-ai/ui-library';
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { Menu, MenuList } from '@invoke-ai/ui-library';
|
||||
import { CanvasEntityActionMenuItems } from 'features/controlLayers/components/common/CanvasEntityActionMenuItems';
|
||||
import { CanvasEntityMenuButton } from 'features/controlLayers/components/common/CanvasEntityMenuButton';
|
||||
import {
|
||||
caDeleted,
|
||||
caMovedBackwardOne,
|
||||
caMovedForwardOne,
|
||||
caMovedToBack,
|
||||
caMovedToFront,
|
||||
selectCanvasV2Slice,
|
||||
} from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { selectCAOrThrow } from 'features/controlLayers/store/controlAdaptersReducers';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
PiArrowDownBold,
|
||||
PiArrowLineDownBold,
|
||||
PiArrowLineUpBold,
|
||||
PiArrowUpBold,
|
||||
PiTrashSimpleBold,
|
||||
} from 'react-icons/pi';
|
||||
|
||||
type Props = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
export const CAActionsMenu = memo(({ id }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const selectValidActions = useMemo(
|
||||
() =>
|
||||
createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
|
||||
const ca = selectCAOrThrow(canvasV2, id);
|
||||
const caIndex = canvasV2.controlAdapters.entities.indexOf(ca);
|
||||
const caCount = canvasV2.controlAdapters.entities.length;
|
||||
return {
|
||||
canMoveForward: caIndex < caCount - 1,
|
||||
canMoveBackward: caIndex > 0,
|
||||
canMoveToFront: caIndex < caCount - 1,
|
||||
canMoveToBack: caIndex > 0,
|
||||
};
|
||||
}),
|
||||
[id]
|
||||
);
|
||||
const validActions = useAppSelector(selectValidActions);
|
||||
const onDelete = useCallback(() => {
|
||||
dispatch(caDeleted({ id }));
|
||||
}, [dispatch, id]);
|
||||
const moveForwardOne = useCallback(() => {
|
||||
dispatch(caMovedForwardOne({ id }));
|
||||
}, [dispatch, id]);
|
||||
const moveToFront = useCallback(() => {
|
||||
dispatch(caMovedToFront({ id }));
|
||||
}, [dispatch, id]);
|
||||
const moveBackwardOne = useCallback(() => {
|
||||
dispatch(caMovedBackwardOne({ id }));
|
||||
}, [dispatch, id]);
|
||||
const moveToBack = useCallback(() => {
|
||||
dispatch(caMovedToBack({ id }));
|
||||
}, [dispatch, id]);
|
||||
import { memo } from 'react';
|
||||
|
||||
export const CAActionsMenu = memo(() => {
|
||||
return (
|
||||
<Menu>
|
||||
<CanvasEntityMenuButton />
|
||||
<MenuList>
|
||||
<MenuItem onClick={moveToFront} isDisabled={!validActions.canMoveToFront} icon={<PiArrowLineUpBold />}>
|
||||
{t('controlLayers.moveToFront')}
|
||||
</MenuItem>
|
||||
<MenuItem onClick={moveForwardOne} isDisabled={!validActions.canMoveForward} icon={<PiArrowUpBold />}>
|
||||
{t('controlLayers.moveForward')}
|
||||
</MenuItem>
|
||||
<MenuItem onClick={moveBackwardOne} isDisabled={!validActions.canMoveBackward} icon={<PiArrowDownBold />}>
|
||||
{t('controlLayers.moveBackward')}
|
||||
</MenuItem>
|
||||
<MenuItem onClick={moveToBack} isDisabled={!validActions.canMoveToBack} icon={<PiArrowLineDownBold />}>
|
||||
{t('controlLayers.moveToBack')}
|
||||
</MenuItem>
|
||||
<MenuItem onClick={onDelete} icon={<PiTrashSimpleBold />} color="error.300">
|
||||
{t('common.delete')}
|
||||
</MenuItem>
|
||||
<CanvasEntityActionMenuItems />
|
||||
</MenuList>
|
||||
</Menu>
|
||||
);
|
||||
|
@ -1,41 +1,25 @@
|
||||
import { Spacer } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { CanvasEntityDeleteButton } from 'features/controlLayers/components/common/CanvasEntityDeleteButton';
|
||||
import { CanvasEntityEnabledToggle } from 'features/controlLayers/components/common/CanvasEntityEnabledToggle';
|
||||
import { CanvasEntityHeader } from 'features/controlLayers/components/common/CanvasEntityHeader';
|
||||
import { CanvasEntityTitle } from 'features/controlLayers/components/common/CanvasEntityTitle';
|
||||
import { CAActionsMenu } from 'features/controlLayers/components/ControlAdapter/CAActionsMenu';
|
||||
import { CAOpacityAndFilter } from 'features/controlLayers/components/ControlAdapter/CAOpacityAndFilter';
|
||||
import { caDeleted, caIsEnabledToggled } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { selectCAOrThrow } from 'features/controlLayers/store/controlAdaptersReducers';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { memo } from 'react';
|
||||
|
||||
type Props = {
|
||||
id: string;
|
||||
isSelected: boolean;
|
||||
onToggleVisibility: () => void;
|
||||
};
|
||||
|
||||
export const CAHeader = memo(({ id, isSelected, onToggleVisibility }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const isEnabled = useAppSelector((s) => selectCAOrThrow(s.canvasV2, id).isEnabled);
|
||||
const onToggleIsEnabled = useCallback(() => {
|
||||
dispatch(caIsEnabledToggled({ id }));
|
||||
}, [dispatch, id]);
|
||||
const onDelete = useCallback(() => {
|
||||
dispatch(caDeleted({ id }));
|
||||
}, [dispatch, id]);
|
||||
|
||||
export const CAHeader = memo(({ onToggleVisibility }: Props) => {
|
||||
return (
|
||||
<CanvasEntityHeader onToggle={onToggleVisibility}>
|
||||
<CanvasEntityEnabledToggle isEnabled={isEnabled} onToggle={onToggleIsEnabled} />
|
||||
<CanvasEntityTitle title={t('controlLayers.globalControlAdapter')} isSelected={isSelected} />
|
||||
<CanvasEntityEnabledToggle />
|
||||
<CanvasEntityTitle />
|
||||
<Spacer />
|
||||
<CAOpacityAndFilter id={id} />
|
||||
<CAActionsMenu id={id} />
|
||||
<CanvasEntityDeleteButton onDelete={onDelete} />
|
||||
<CAOpacityAndFilter />
|
||||
<CAActionsMenu />
|
||||
<CanvasEntityDeleteButton />
|
||||
</CanvasEntityHeader>
|
||||
);
|
||||
});
|
||||
|
@ -14,6 +14,7 @@ import {
|
||||
} from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { stopPropagation } from 'common/util/stopPropagation';
|
||||
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||
import { caFilterChanged, caOpacityChanged } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { selectCAOrThrow } from 'features/controlLayers/store/controlAdaptersReducers';
|
||||
import type { ChangeEvent } from 'react';
|
||||
@ -21,14 +22,11 @@ import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiDropHalfFill } from 'react-icons/pi';
|
||||
|
||||
type Props = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
const marks = [0, 25, 50, 75, 100];
|
||||
const formatPct = (v: number | string) => `${v} %`;
|
||||
|
||||
export const CAOpacityAndFilter = memo(({ id }: Props) => {
|
||||
export const CAOpacityAndFilter = memo(() => {
|
||||
const { id } = useEntityIdentifierContext();
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const opacity = useAppSelector((s) => Math.round(selectCAOrThrow(s.canvasV2, id).opacity * 100));
|
||||
|
@ -15,7 +15,7 @@ export const IPA = memo(({ id }: Props) => {
|
||||
const isSelected = useAppSelector((s) => s.canvasV2.selectedEntityIdentifier?.id === id);
|
||||
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true });
|
||||
const onSelect = useCallback(() => {
|
||||
dispatch(entitySelected({ id, type: 'ip_adapter' }));
|
||||
dispatch(entitySelected({ entityIdentifier: { id, type: 'ip_adapter' } }));
|
||||
}, [dispatch, id]);
|
||||
|
||||
return (
|
||||
|
@ -1,25 +1,21 @@
|
||||
import { useDisclosure } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { rgbColorToString } from 'common/util/colorCodeTransformers';
|
||||
import { CanvasEntityContainer } from 'features/controlLayers/components/common/CanvasEntityContainer';
|
||||
import { IMHeader } from 'features/controlLayers/components/InpaintMask/IMHeader';
|
||||
import { IMSettings } from 'features/controlLayers/components/InpaintMask/IMSettings';
|
||||
import { entitySelected } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { EntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
|
||||
import { memo, useMemo } from 'react';
|
||||
|
||||
export const IM = memo(() => {
|
||||
const dispatch = useAppDispatch();
|
||||
const selectedBorderColor = useAppSelector((s) => rgbColorToString(s.canvasV2.inpaintMask.fill));
|
||||
const isSelected = useAppSelector((s) => s.canvasV2.selectedEntityIdentifier?.id === 'inpaint_mask');
|
||||
const entityIdentifier = useMemo<CanvasEntityIdentifier>(() => ({ id: 'inpaint_mask', type: 'inpaint_mask' }), []);
|
||||
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: false });
|
||||
const onSelect = useCallback(() => {
|
||||
dispatch(entitySelected({ id: 'inpaint_mask', type: 'inpaint_mask' }));
|
||||
}, [dispatch]);
|
||||
return (
|
||||
<CanvasEntityContainer isSelected={isSelected} onSelect={onSelect} selectedBorderColor={selectedBorderColor}>
|
||||
<IMHeader isSelected={isSelected} onToggleVisibility={onToggle} />
|
||||
{isOpen && <IMSettings />}
|
||||
</CanvasEntityContainer>
|
||||
<EntityIdentifierContext.Provider value={entityIdentifier}>
|
||||
<CanvasEntityContainer>
|
||||
<IMHeader onToggleVisibility={onToggle} />
|
||||
{isOpen && <IMSettings />}
|
||||
</CanvasEntityContainer>
|
||||
</EntityIdentifierContext.Provider>
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -1,25 +1,14 @@
|
||||
import { Menu, MenuItem, MenuList } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { Menu, MenuList } from '@invoke-ai/ui-library';
|
||||
import { CanvasEntityActionMenuItems } from 'features/controlLayers/components/common/CanvasEntityActionMenuItems';
|
||||
import { CanvasEntityMenuButton } from 'features/controlLayers/components/common/CanvasEntityMenuButton';
|
||||
import { imReset } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiArrowCounterClockwiseBold } from 'react-icons/pi';
|
||||
import { memo } from 'react';
|
||||
|
||||
export const IMActionsMenu = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const onReset = useCallback(() => {
|
||||
dispatch(imReset());
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<Menu>
|
||||
<CanvasEntityMenuButton />
|
||||
<MenuList>
|
||||
<MenuItem onClick={onReset} icon={<PiArrowCounterClockwiseBold />}>
|
||||
{t('accessibility.reset')}
|
||||
</MenuItem>
|
||||
<CanvasEntityActionMenuItems />
|
||||
</MenuList>
|
||||
</Menu>
|
||||
);
|
||||
|
@ -1,32 +1,21 @@
|
||||
import { Spacer } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { CanvasEntityEnabledToggle } from 'features/controlLayers/components/common/CanvasEntityEnabledToggle';
|
||||
import { CanvasEntityHeader } from 'features/controlLayers/components/common/CanvasEntityHeader';
|
||||
import { CanvasEntityTitle } from 'features/controlLayers/components/common/CanvasEntityTitle';
|
||||
import { IMActionsMenu } from 'features/controlLayers/components/InpaintMask/IMActionsMenu';
|
||||
import { imIsEnabledToggled } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { memo } from 'react';
|
||||
|
||||
import { IMMaskFillColorPicker } from './IMMaskFillColorPicker';
|
||||
|
||||
type Props = {
|
||||
isSelected: boolean;
|
||||
onToggleVisibility: () => void;
|
||||
};
|
||||
|
||||
export const IMHeader = memo(({ isSelected, onToggleVisibility }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const isEnabled = useAppSelector((s) => s.canvasV2.inpaintMask.isEnabled);
|
||||
const onToggleIsEnabled = useCallback(() => {
|
||||
dispatch(imIsEnabledToggled());
|
||||
}, [dispatch]);
|
||||
|
||||
export const IMHeader = memo(({ onToggleVisibility }: Props) => {
|
||||
return (
|
||||
<CanvasEntityHeader onToggle={onToggleVisibility}>
|
||||
<CanvasEntityEnabledToggle isEnabled={isEnabled} onToggle={onToggleIsEnabled} />
|
||||
<CanvasEntityTitle title={t('controlLayers.inpaintMask')} isSelected={isSelected} />
|
||||
<CanvasEntityEnabledToggle />
|
||||
<CanvasEntityTitle />
|
||||
<Spacer />
|
||||
<IMMaskFillColorPicker />
|
||||
<IMActionsMenu />
|
||||
|
@ -1,35 +1,33 @@
|
||||
import { useDisclosure } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAIDroppable from 'common/components/IAIDroppable';
|
||||
import { CanvasEntityContainer } from 'features/controlLayers/components/common/CanvasEntityContainer';
|
||||
import { LayerHeader } from 'features/controlLayers/components/Layer/LayerHeader';
|
||||
import { LayerSettings } from 'features/controlLayers/components/Layer/LayerSettings';
|
||||
import { entitySelected } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { EntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
|
||||
import type { LayerImageDropData } from 'features/dnd/types';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { memo, useMemo } from 'react';
|
||||
|
||||
type Props = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
export const Layer = memo(({ id }: Props) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const isSelected = useAppSelector((s) => s.canvasV2.selectedEntityIdentifier?.id === id);
|
||||
const entityIdentifier = useMemo<CanvasEntityIdentifier>(() => ({ id, type: 'layer' }), [id]);
|
||||
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: false });
|
||||
const onSelect = useCallback(() => {
|
||||
dispatch(entitySelected({ id, type: 'layer' }));
|
||||
}, [dispatch, id]);
|
||||
const droppableData = useMemo<LayerImageDropData>(
|
||||
() => ({ id, actionType: 'ADD_LAYER_IMAGE', context: { id } }),
|
||||
[id]
|
||||
);
|
||||
|
||||
return (
|
||||
<CanvasEntityContainer isSelected={isSelected} onSelect={onSelect}>
|
||||
<LayerHeader id={id} onToggleVisibility={onToggle} isSelected={isSelected} />
|
||||
{isOpen && <LayerSettings id={id} />}
|
||||
<IAIDroppable data={droppableData} />
|
||||
</CanvasEntityContainer>
|
||||
<EntityIdentifierContext.Provider value={entityIdentifier}>
|
||||
<CanvasEntityContainer>
|
||||
<LayerHeader onToggleVisibility={onToggle} />
|
||||
{isOpen && <LayerSettings />}
|
||||
<IAIDroppable data={droppableData} />
|
||||
</CanvasEntityContainer>
|
||||
</EntityIdentifierContext.Provider>
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -1,84 +1,14 @@
|
||||
import { Menu, MenuItem, MenuList } from '@invoke-ai/ui-library';
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { Menu, MenuList } from '@invoke-ai/ui-library';
|
||||
import { CanvasEntityActionMenuItems } from 'features/controlLayers/components/common/CanvasEntityActionMenuItems';
|
||||
import { CanvasEntityMenuButton } from 'features/controlLayers/components/common/CanvasEntityMenuButton';
|
||||
import {
|
||||
layerDeleted,
|
||||
layerMovedBackwardOne,
|
||||
layerMovedForwardOne,
|
||||
layerMovedToBack,
|
||||
layerMovedToFront,
|
||||
selectCanvasV2Slice,
|
||||
} from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { selectLayerOrThrow } from 'features/controlLayers/store/layersReducers';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
PiArrowDownBold,
|
||||
PiArrowLineDownBold,
|
||||
PiArrowLineUpBold,
|
||||
PiArrowUpBold,
|
||||
PiTrashSimpleBold,
|
||||
} from 'react-icons/pi';
|
||||
|
||||
type Props = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
export const LayerActionsMenu = memo(({ id }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const selectValidActions = useMemo(
|
||||
() =>
|
||||
createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
|
||||
const layer = selectLayerOrThrow(canvasV2, id);
|
||||
const layerIndex = canvasV2.layers.entities.indexOf(layer);
|
||||
const layerCount = canvasV2.layers.entities.length;
|
||||
return {
|
||||
canMoveForward: layerIndex < layerCount - 1,
|
||||
canMoveBackward: layerIndex > 0,
|
||||
canMoveToFront: layerIndex < layerCount - 1,
|
||||
canMoveToBack: layerIndex > 0,
|
||||
};
|
||||
}),
|
||||
[id]
|
||||
);
|
||||
const validActions = useAppSelector(selectValidActions);
|
||||
const onDelete = useCallback(() => {
|
||||
dispatch(layerDeleted({ id }));
|
||||
}, [dispatch, id]);
|
||||
const moveForwardOne = useCallback(() => {
|
||||
dispatch(layerMovedForwardOne({ id }));
|
||||
}, [dispatch, id]);
|
||||
const moveToFront = useCallback(() => {
|
||||
dispatch(layerMovedToFront({ id }));
|
||||
}, [dispatch, id]);
|
||||
const moveBackwardOne = useCallback(() => {
|
||||
dispatch(layerMovedBackwardOne({ id }));
|
||||
}, [dispatch, id]);
|
||||
const moveToBack = useCallback(() => {
|
||||
dispatch(layerMovedToBack({ id }));
|
||||
}, [dispatch, id]);
|
||||
import { memo } from 'react';
|
||||
|
||||
export const LayerActionsMenu = memo(() => {
|
||||
return (
|
||||
<Menu>
|
||||
<CanvasEntityMenuButton />
|
||||
<MenuList>
|
||||
<MenuItem onClick={moveToFront} isDisabled={!validActions.canMoveToFront} icon={<PiArrowLineUpBold />}>
|
||||
{t('controlLayers.moveToFront')}
|
||||
</MenuItem>
|
||||
<MenuItem onClick={moveForwardOne} isDisabled={!validActions.canMoveForward} icon={<PiArrowUpBold />}>
|
||||
{t('controlLayers.moveForward')}
|
||||
</MenuItem>
|
||||
<MenuItem onClick={moveBackwardOne} isDisabled={!validActions.canMoveBackward} icon={<PiArrowDownBold />}>
|
||||
{t('controlLayers.moveBackward')}
|
||||
</MenuItem>
|
||||
<MenuItem onClick={moveToBack} isDisabled={!validActions.canMoveToBack} icon={<PiArrowLineDownBold />}>
|
||||
{t('controlLayers.moveToBack')}
|
||||
</MenuItem>
|
||||
<MenuItem onClick={onDelete} icon={<PiTrashSimpleBold />} color="error.300">
|
||||
{t('common.delete')}
|
||||
</MenuItem>
|
||||
<CanvasEntityActionMenuItems />
|
||||
</MenuList>
|
||||
</Menu>
|
||||
);
|
||||
|
@ -1,46 +1,26 @@
|
||||
import { Spacer } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { CanvasEntityDeleteButton } from 'features/controlLayers/components/common/CanvasEntityDeleteButton';
|
||||
import { CanvasEntityEnabledToggle } from 'features/controlLayers/components/common/CanvasEntityEnabledToggle';
|
||||
import { CanvasEntityHeader } from 'features/controlLayers/components/common/CanvasEntityHeader';
|
||||
import { CanvasEntityTitle } from 'features/controlLayers/components/common/CanvasEntityTitle';
|
||||
import { LayerActionsMenu } from 'features/controlLayers/components/Layer/LayerActionsMenu';
|
||||
import { layerDeleted, layerIsEnabledToggled } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { selectLayerOrThrow } from 'features/controlLayers/store/layersReducers';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { memo } from 'react';
|
||||
|
||||
import { LayerOpacity } from './LayerOpacity';
|
||||
|
||||
type Props = {
|
||||
id: string;
|
||||
isSelected: boolean;
|
||||
onToggleVisibility: () => void;
|
||||
};
|
||||
|
||||
export const LayerHeader = memo(({ id, isSelected, onToggleVisibility }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const isEnabled = useAppSelector((s) => selectLayerOrThrow(s.canvasV2, id).isEnabled);
|
||||
const objectCount = useAppSelector((s) => selectLayerOrThrow(s.canvasV2, id).objects.length);
|
||||
const onToggleIsEnabled = useCallback(() => {
|
||||
dispatch(layerIsEnabledToggled({ id }));
|
||||
}, [dispatch, id]);
|
||||
const onDelete = useCallback(() => {
|
||||
dispatch(layerDeleted({ id }));
|
||||
}, [dispatch, id]);
|
||||
const title = useMemo(() => {
|
||||
return `${t('controlLayers.layer')} (${t('controlLayers.objects', { count: objectCount })})`;
|
||||
}, [objectCount, t]);
|
||||
|
||||
export const LayerHeader = memo(({ onToggleVisibility }: Props) => {
|
||||
return (
|
||||
<CanvasEntityHeader onToggle={onToggleVisibility}>
|
||||
<CanvasEntityEnabledToggle isEnabled={isEnabled} onToggle={onToggleIsEnabled} />
|
||||
<CanvasEntityTitle title={title} isSelected={isSelected} />
|
||||
<CanvasEntityEnabledToggle />
|
||||
<CanvasEntityTitle />
|
||||
<Spacer />
|
||||
<LayerOpacity id={id} />
|
||||
<LayerActionsMenu id={id} />
|
||||
<CanvasEntityDeleteButton onDelete={onDelete} />
|
||||
<LayerOpacity />
|
||||
<LayerActionsMenu />
|
||||
<CanvasEntityDeleteButton />
|
||||
</CanvasEntityHeader>
|
||||
);
|
||||
});
|
||||
|
@ -13,28 +13,26 @@ import {
|
||||
} from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { stopPropagation } from 'common/util/stopPropagation';
|
||||
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||
import { layerOpacityChanged } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { selectLayerOrThrow } from 'features/controlLayers/store/layersReducers';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiDropHalfFill } from 'react-icons/pi';
|
||||
|
||||
type Props = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
const marks = [0, 25, 50, 75, 100];
|
||||
const formatPct = (v: number | string) => `${v} %`;
|
||||
|
||||
export const LayerOpacity = memo(({ id }: Props) => {
|
||||
export const LayerOpacity = memo(() => {
|
||||
const entityIdentifier = useEntityIdentifierContext();
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const opacity = useAppSelector((s) => Math.round(selectLayerOrThrow(s.canvasV2, id).opacity * 100));
|
||||
const opacity = useAppSelector((s) => Math.round(selectLayerOrThrow(s.canvasV2, entityIdentifier.id).opacity * 100));
|
||||
const onChangeOpacity = useCallback(
|
||||
(v: number) => {
|
||||
dispatch(layerOpacityChanged({ id, opacity: v / 100 }));
|
||||
dispatch(layerOpacityChanged({ id: entityIdentifier.id, opacity: v / 100 }));
|
||||
},
|
||||
[dispatch, id]
|
||||
[dispatch, entityIdentifier.id]
|
||||
);
|
||||
return (
|
||||
<Popover isLazy>
|
||||
|
@ -1,11 +1,9 @@
|
||||
import { CanvasEntitySettings } from 'features/controlLayers/components/common/CanvasEntitySettings';
|
||||
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||
import { memo } from 'react';
|
||||
|
||||
type Props = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
export const LayerSettings = memo(({ id }: Props) => {
|
||||
export const LayerSettings = memo(() => {
|
||||
const entityIdentifier = useEntityIdentifierContext();
|
||||
return <CanvasEntitySettings>PLACEHOLDER</CanvasEntitySettings>;
|
||||
});
|
||||
|
||||
|
@ -1,30 +1,25 @@
|
||||
import { useDisclosure } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { rgbColorToString } from 'common/util/colorCodeTransformers';
|
||||
import { CanvasEntityContainer } from 'features/controlLayers/components/common/CanvasEntityContainer';
|
||||
import { RGHeader } from 'features/controlLayers/components/RegionalGuidance/RGHeader';
|
||||
import { RGSettings } from 'features/controlLayers/components/RegionalGuidance/RGSettings';
|
||||
import { entitySelected } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { selectRGOrThrow } from 'features/controlLayers/store/regionsReducers';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { EntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
|
||||
import { memo, useMemo } from 'react';
|
||||
|
||||
type Props = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
export const RG = memo(({ id }: Props) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const selectedBorderColor = useAppSelector((s) => rgbColorToString(selectRGOrThrow(s.canvasV2, id).fill));
|
||||
const isSelected = useAppSelector((s) => s.canvasV2.selectedEntityIdentifier?.id === id);
|
||||
const entityIdentifier = useMemo<CanvasEntityIdentifier>(() => ({ id, type: 'regional_guidance' }), [id]);
|
||||
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true });
|
||||
const onSelect = useCallback(() => {
|
||||
dispatch(entitySelected({ id, type: 'regional_guidance' }));
|
||||
}, [dispatch, id]);
|
||||
return (
|
||||
<CanvasEntityContainer isSelected={isSelected} onSelect={onSelect} selectedBorderColor={selectedBorderColor}>
|
||||
<RGHeader id={id} isSelected={isSelected} onToggleVisibility={onToggle} />
|
||||
{isOpen && <RGSettings id={id} />}
|
||||
</CanvasEntityContainer>
|
||||
<EntityIdentifierContext.Provider value={entityIdentifier}>
|
||||
<CanvasEntityContainer>
|
||||
<RGHeader onToggleVisibility={onToggle} />
|
||||
{isOpen && <RGSettings />}
|
||||
</CanvasEntityContainer>
|
||||
</EntityIdentifierContext.Provider>
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -1,37 +1,22 @@
|
||||
import { Menu, MenuDivider, MenuItem, MenuList } from '@invoke-ai/ui-library';
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { CanvasEntityActionMenuItems } from 'features/controlLayers/components/common/CanvasEntityActionMenuItems';
|
||||
import { CanvasEntityMenuButton } from 'features/controlLayers/components/common/CanvasEntityMenuButton';
|
||||
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||
import { useAddIPAdapterToRGLayer } from 'features/controlLayers/hooks/addLayerHooks';
|
||||
import {
|
||||
rgDeleted,
|
||||
rgMovedBackwardOne,
|
||||
rgMovedForwardOne,
|
||||
rgMovedToBack,
|
||||
rgMovedToFront,
|
||||
rgNegativePromptChanged,
|
||||
rgPositivePromptChanged,
|
||||
rgReset,
|
||||
selectCanvasV2Slice,
|
||||
} from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { selectRGOrThrow } from 'features/controlLayers/store/regionsReducers';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
PiArrowCounterClockwiseBold,
|
||||
PiArrowDownBold,
|
||||
PiArrowLineDownBold,
|
||||
PiArrowLineUpBold,
|
||||
PiArrowUpBold,
|
||||
PiPlusBold,
|
||||
PiTrashSimpleBold,
|
||||
} from 'react-icons/pi';
|
||||
import { PiPlusBold } from 'react-icons/pi';
|
||||
|
||||
type Props = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
export const RGActionsMenu = memo(({ id }: Props) => {
|
||||
export const RGActionsMenu = memo(() => {
|
||||
const { id } = useEntityIdentifierContext();
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const [onAddIPAdapter, isAddIPAdapterDisabled] = useAddIPAdapterToRGLayer(id);
|
||||
@ -39,13 +24,7 @@ export const RGActionsMenu = memo(({ id }: Props) => {
|
||||
() =>
|
||||
createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
|
||||
const rg = selectRGOrThrow(canvasV2, id);
|
||||
const rgIndex = canvasV2.regions.entities.indexOf(rg);
|
||||
const rgCount = canvasV2.regions.entities.length;
|
||||
return {
|
||||
isMoveForwardOneDisabled: rgIndex < rgCount - 1,
|
||||
isMoveBackardOneDisabled: rgIndex > 0,
|
||||
isMoveToFrontDisabled: rgIndex < rgCount - 1,
|
||||
isMoveToBackDisabled: rgIndex > 0,
|
||||
isAddPositivePromptDisabled: rg.positivePrompt === null,
|
||||
isAddNegativePromptDisabled: rg.negativePrompt === null,
|
||||
};
|
||||
@ -53,29 +32,11 @@ export const RGActionsMenu = memo(({ id }: Props) => {
|
||||
[id]
|
||||
);
|
||||
const actions = useAppSelector(selectActionsValidity);
|
||||
const onDelete = useCallback(() => {
|
||||
dispatch(rgDeleted({ id }));
|
||||
}, [dispatch, id]);
|
||||
const onReset = useCallback(() => {
|
||||
dispatch(rgReset({ id }));
|
||||
}, [dispatch, id]);
|
||||
const onMoveForwardOne = useCallback(() => {
|
||||
dispatch(rgMovedForwardOne({ id }));
|
||||
}, [dispatch, id]);
|
||||
const onMoveToFront = useCallback(() => {
|
||||
dispatch(rgMovedToFront({ id }));
|
||||
}, [dispatch, id]);
|
||||
const onMoveBackwardOne = useCallback(() => {
|
||||
dispatch(rgMovedBackwardOne({ id }));
|
||||
}, [dispatch, id]);
|
||||
const onMoveToBack = useCallback(() => {
|
||||
dispatch(rgMovedToBack({ id }));
|
||||
}, [dispatch, id]);
|
||||
const onAddPositivePrompt = useCallback(() => {
|
||||
dispatch(rgPositivePromptChanged({ id, prompt: '' }));
|
||||
dispatch(rgPositivePromptChanged({ id: id, prompt: '' }));
|
||||
}, [dispatch, id]);
|
||||
const onAddNegativePrompt = useCallback(() => {
|
||||
dispatch(rgNegativePromptChanged({ id, prompt: '' }));
|
||||
dispatch(rgNegativePromptChanged({ id: id, prompt: '' }));
|
||||
}, [dispatch, id]);
|
||||
|
||||
return (
|
||||
@ -92,25 +53,7 @@ export const RGActionsMenu = memo(({ id }: Props) => {
|
||||
{t('controlLayers.addIPAdapter')}
|
||||
</MenuItem>
|
||||
<MenuDivider />
|
||||
<MenuItem onClick={onMoveToFront} isDisabled={actions.isMoveToFrontDisabled} icon={<PiArrowLineUpBold />}>
|
||||
{t('controlLayers.moveToFront')}
|
||||
</MenuItem>
|
||||
<MenuItem onClick={onMoveForwardOne} isDisabled={actions.isMoveForwardOneDisabled} icon={<PiArrowUpBold />}>
|
||||
{t('controlLayers.moveForward')}
|
||||
</MenuItem>
|
||||
<MenuItem onClick={onMoveBackwardOne} isDisabled={actions.isMoveBackardOneDisabled} icon={<PiArrowDownBold />}>
|
||||
{t('controlLayers.moveBackward')}
|
||||
</MenuItem>
|
||||
<MenuItem onClick={onMoveToBack} isDisabled={actions.isMoveToBackDisabled} icon={<PiArrowLineDownBold />}>
|
||||
{t('controlLayers.moveToBack')}
|
||||
</MenuItem>
|
||||
<MenuDivider />
|
||||
<MenuItem onClick={onReset} icon={<PiArrowCounterClockwiseBold />}>
|
||||
{t('accessibility.reset')}
|
||||
</MenuItem>
|
||||
<MenuItem onClick={onDelete} icon={<PiTrashSimpleBold />} color="error.300">
|
||||
{t('common.delete')}
|
||||
</MenuItem>
|
||||
<CanvasEntityActionMenuItems />
|
||||
</MenuList>
|
||||
</Menu>
|
||||
);
|
||||
|
@ -1,50 +1,41 @@
|
||||
import { Badge, Spacer } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { CanvasEntityDeleteButton } from 'features/controlLayers/components/common/CanvasEntityDeleteButton';
|
||||
import { CanvasEntityEnabledToggle } from 'features/controlLayers/components/common/CanvasEntityEnabledToggle';
|
||||
import { CanvasEntityHeader } from 'features/controlLayers/components/common/CanvasEntityHeader';
|
||||
import { CanvasEntityTitle } from 'features/controlLayers/components/common/CanvasEntityTitle';
|
||||
import { RGActionsMenu } from 'features/controlLayers/components/RegionalGuidance/RGActionsMenu';
|
||||
import { rgDeleted, rgIsEnabledToggled } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||
import { selectRGOrThrow } from 'features/controlLayers/store/regionsReducers';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { RGMaskFillColorPicker } from './RGMaskFillColorPicker';
|
||||
import { RGSettingsPopover } from './RGSettingsPopover';
|
||||
|
||||
type Props = {
|
||||
id: string;
|
||||
isSelected: boolean;
|
||||
onToggleVisibility: () => void;
|
||||
};
|
||||
|
||||
export const RGHeader = memo(({ id, isSelected, onToggleVisibility }: Props) => {
|
||||
export const RGHeader = memo(({ onToggleVisibility }: Props) => {
|
||||
const { id } = useEntityIdentifierContext();
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const isEnabled = useAppSelector((s) => selectRGOrThrow(s.canvasV2, id).isEnabled);
|
||||
const autoNegative = useAppSelector((s) => selectRGOrThrow(s.canvasV2, id).autoNegative);
|
||||
const onToggleIsEnabled = useCallback(() => {
|
||||
dispatch(rgIsEnabledToggled({ id }));
|
||||
}, [dispatch, id]);
|
||||
const onDelete = useCallback(() => {
|
||||
dispatch(rgDeleted({ id }));
|
||||
}, [dispatch, id]);
|
||||
|
||||
return (
|
||||
<CanvasEntityHeader onToggle={onToggleVisibility}>
|
||||
<CanvasEntityEnabledToggle isEnabled={isEnabled} onToggle={onToggleIsEnabled} />
|
||||
<CanvasEntityTitle title={t('controlLayers.regionalGuidance')} isSelected={isSelected} />
|
||||
<CanvasEntityEnabledToggle />
|
||||
<CanvasEntityTitle />
|
||||
<Spacer />
|
||||
{autoNegative === 'invert' && (
|
||||
<Badge color="base.300" bg="transparent" borderWidth={1} userSelect="none">
|
||||
{t('controlLayers.autoNegative')}
|
||||
</Badge>
|
||||
)}
|
||||
<RGMaskFillColorPicker id={id} />
|
||||
<RGSettingsPopover id={id} />
|
||||
<RGActionsMenu id={id} />
|
||||
<CanvasEntityDeleteButton onDelete={onDelete} />
|
||||
<RGMaskFillColorPicker />
|
||||
<RGSettingsPopover />
|
||||
<RGActionsMenu />
|
||||
<CanvasEntityDeleteButton />
|
||||
</CanvasEntityHeader>
|
||||
);
|
||||
});
|
||||
|
@ -3,25 +3,23 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import RgbColorPicker from 'common/components/RgbColorPicker';
|
||||
import { rgbColorToString } from 'common/util/colorCodeTransformers';
|
||||
import { stopPropagation } from 'common/util/stopPropagation';
|
||||
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||
import { rgFillChanged } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { selectRGOrThrow } from 'features/controlLayers/store/regionsReducers';
|
||||
import { memo, useCallback } from 'react';
|
||||
import type { RgbColor } from 'react-colorful';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
type Props = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
export const RGMaskFillColorPicker = memo(({ id }: Props) => {
|
||||
export const RGMaskFillColorPicker = memo(() => {
|
||||
const entityIdentifier = useEntityIdentifierContext();
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const fill = useAppSelector((s) => selectRGOrThrow(s.canvasV2, id).fill);
|
||||
const fill = useAppSelector((s) => selectRGOrThrow(s.canvasV2, entityIdentifier.id).fill);
|
||||
const onChange = useCallback(
|
||||
(fill: RgbColor) => {
|
||||
dispatch(rgFillChanged({ id, fill }));
|
||||
dispatch(rgFillChanged({ id: entityIdentifier.id, fill }));
|
||||
},
|
||||
[dispatch, id]
|
||||
[dispatch, entityIdentifier.id]
|
||||
);
|
||||
return (
|
||||
<Popover isLazy>
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { AddPromptButtons } from 'features/controlLayers/components/AddPromptButtons';
|
||||
import { CanvasEntitySettings } from 'features/controlLayers/components/common/CanvasEntitySettings';
|
||||
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||
import { selectRGOrThrow } from 'features/controlLayers/store/regionsReducers';
|
||||
import { memo } from 'react';
|
||||
|
||||
@ -8,11 +9,8 @@ import { RGIPAdapters } from './RGIPAdapters';
|
||||
import { RGNegativePrompt } from './RGNegativePrompt';
|
||||
import { RGPositivePrompt } from './RGPositivePrompt';
|
||||
|
||||
type Props = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
export const RGSettings = memo(({ id }: Props) => {
|
||||
export const RGSettings = memo(() => {
|
||||
const { id } = useEntityIdentifierContext();
|
||||
const hasPositivePrompt = useAppSelector((s) => selectRGOrThrow(s.canvasV2, id).positivePrompt !== null);
|
||||
const hasNegativePrompt = useAppSelector((s) => selectRGOrThrow(s.canvasV2, id).negativePrompt !== null);
|
||||
const hasIPAdapters = useAppSelector((s) => selectRGOrThrow(s.canvasV2, id).ipAdapters.length > 0);
|
||||
|
@ -12,6 +12,7 @@ import {
|
||||
} from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { stopPropagation } from 'common/util/stopPropagation';
|
||||
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||
import { rgAutoNegativeChanged } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { selectRGOrThrow } from 'features/controlLayers/store/regionsReducers';
|
||||
import type { ChangeEvent } from 'react';
|
||||
@ -19,19 +20,16 @@ import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiGearSixBold } from 'react-icons/pi';
|
||||
|
||||
type Props = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
export const RGSettingsPopover = memo(({ id }: Props) => {
|
||||
export const RGSettingsPopover = memo(() => {
|
||||
const entityIdentifier = useEntityIdentifierContext();
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const autoNegative = useAppSelector((s) => selectRGOrThrow(s.canvasV2, id).autoNegative);
|
||||
const autoNegative = useAppSelector((s) => selectRGOrThrow(s.canvasV2, entityIdentifier.id).autoNegative);
|
||||
const onChange = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
dispatch(rgAutoNegativeChanged({ id, autoNegative: e.target.checked ? 'invert' : 'off' }));
|
||||
dispatch(rgAutoNegativeChanged({ id: entityIdentifier.id, autoNegative: e.target.checked ? 'invert' : 'off' }));
|
||||
},
|
||||
[dispatch, id]
|
||||
[dispatch, entityIdentifier.id]
|
||||
);
|
||||
|
||||
return (
|
||||
|
@ -13,32 +13,19 @@ import { useCanvasResetLayerHotkey } from 'features/controlLayers/hooks/useCanva
|
||||
export const ToolChooser: React.FC = () => {
|
||||
useCanvasResetLayerHotkey();
|
||||
useCanvasDeleteLayerHotkey();
|
||||
const isCanvasSessionActive = useAppSelector((s) => s.canvasV2.session.isActive);
|
||||
const isTransforming = useAppSelector((s) => s.canvasV2.tool.isTransforming);
|
||||
|
||||
if (isCanvasSessionActive) {
|
||||
return (
|
||||
<>
|
||||
<ButtonGroup isAttached isDisabled={isTransforming}>
|
||||
<BrushToolButton />
|
||||
<EraserToolButton />
|
||||
<RectToolButton />
|
||||
<MoveToolButton />
|
||||
<ViewToolButton />
|
||||
<BboxToolButton />
|
||||
</ButtonGroup>
|
||||
<TransformToolButton />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ButtonGroup isAttached isDisabled={isTransforming}>
|
||||
<BrushToolButton />
|
||||
<EraserToolButton />
|
||||
<RectToolButton />
|
||||
<MoveToolButton />
|
||||
<ViewToolButton />
|
||||
</ButtonGroup>
|
||||
<>
|
||||
<ButtonGroup isAttached isDisabled={isTransforming}>
|
||||
<BrushToolButton />
|
||||
<EraserToolButton />
|
||||
<RectToolButton />
|
||||
<MoveToolButton />
|
||||
<ViewToolButton />
|
||||
<BboxToolButton />
|
||||
</ButtonGroup>
|
||||
<TransformToolButton />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -0,0 +1,127 @@
|
||||
import { MenuItem } from '@invoke-ai/ui-library';
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||
import {
|
||||
entityArrangedBackwardOne,
|
||||
entityArrangedForwardOne,
|
||||
entityArrangedToBack,
|
||||
entityArrangedToFront,
|
||||
entityDeleted,
|
||||
entityReset,
|
||||
selectCanvasV2Slice,
|
||||
} from 'features/controlLayers/store/canvasV2Slice';
|
||||
import type { CanvasEntityIdentifier, CanvasV2State } from 'features/controlLayers/store/types';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
PiArrowCounterClockwiseBold,
|
||||
PiArrowDownBold,
|
||||
PiArrowLineDownBold,
|
||||
PiArrowLineUpBold,
|
||||
PiArrowUpBold,
|
||||
PiTrashSimpleBold,
|
||||
} from 'react-icons/pi';
|
||||
|
||||
const getIndexAndCount = (
|
||||
canvasV2: CanvasV2State,
|
||||
{ id, type }: CanvasEntityIdentifier
|
||||
): { index: number; count: number } => {
|
||||
if (type === 'layer') {
|
||||
return {
|
||||
index: canvasV2.layers.entities.findIndex((entity) => entity.id === id),
|
||||
count: canvasV2.layers.entities.length,
|
||||
};
|
||||
} else if (type === 'control_adapter') {
|
||||
return {
|
||||
index: canvasV2.controlAdapters.entities.findIndex((entity) => entity.id === id),
|
||||
count: canvasV2.controlAdapters.entities.length,
|
||||
};
|
||||
} else if (type === 'regional_guidance') {
|
||||
return {
|
||||
index: canvasV2.regions.entities.findIndex((entity) => entity.id === id),
|
||||
count: canvasV2.regions.entities.length,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
index: -1,
|
||||
count: 0,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const CanvasEntityActionMenuItems = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const entityIdentifier = useEntityIdentifierContext();
|
||||
const selectValidActions = useMemo(
|
||||
() =>
|
||||
createMemoizedSelector(selectCanvasV2Slice, (canvasV2) => {
|
||||
const { index, count } = getIndexAndCount(canvasV2, entityIdentifier);
|
||||
return {
|
||||
isArrangeable:
|
||||
entityIdentifier.type === 'layer' ||
|
||||
entityIdentifier.type === 'control_adapter' ||
|
||||
entityIdentifier.type === 'regional_guidance',
|
||||
isDeleteable: entityIdentifier.type !== 'inpaint_mask',
|
||||
canMoveForwardOne: index < count - 1,
|
||||
canMoveBackwardOne: index > 0,
|
||||
canMoveToFront: index < count - 1,
|
||||
canMoveToBack: index > 0,
|
||||
};
|
||||
}),
|
||||
[entityIdentifier]
|
||||
);
|
||||
|
||||
const validActions = useAppSelector(selectValidActions);
|
||||
|
||||
const deleteEntity = useCallback(() => {
|
||||
dispatch(entityDeleted({ entityIdentifier }));
|
||||
}, [dispatch, entityIdentifier]);
|
||||
const resetEntity = useCallback(() => {
|
||||
dispatch(entityReset({ entityIdentifier }));
|
||||
}, [dispatch, entityIdentifier]);
|
||||
const moveForwardOne = useCallback(() => {
|
||||
dispatch(entityArrangedForwardOne({ entityIdentifier }));
|
||||
}, [dispatch, entityIdentifier]);
|
||||
const moveToFront = useCallback(() => {
|
||||
dispatch(entityArrangedToFront({ entityIdentifier }));
|
||||
}, [dispatch, entityIdentifier]);
|
||||
const moveBackwardOne = useCallback(() => {
|
||||
dispatch(entityArrangedBackwardOne({ entityIdentifier }));
|
||||
}, [dispatch, entityIdentifier]);
|
||||
const moveToBack = useCallback(() => {
|
||||
dispatch(entityArrangedToBack({ entityIdentifier }));
|
||||
}, [dispatch, entityIdentifier]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{validActions.isArrangeable && (
|
||||
<>
|
||||
<MenuItem onClick={moveToFront} isDisabled={!validActions.canMoveToFront} icon={<PiArrowLineUpBold />}>
|
||||
{t('controlLayers.moveToFront')}
|
||||
</MenuItem>
|
||||
<MenuItem onClick={moveForwardOne} isDisabled={!validActions.canMoveForwardOne} icon={<PiArrowUpBold />}>
|
||||
{t('controlLayers.moveForward')}
|
||||
</MenuItem>
|
||||
<MenuItem onClick={moveBackwardOne} isDisabled={!validActions.canMoveBackwardOne} icon={<PiArrowDownBold />}>
|
||||
{t('controlLayers.moveBackward')}
|
||||
</MenuItem>
|
||||
<MenuItem onClick={moveToBack} isDisabled={!validActions.canMoveToBack} icon={<PiArrowLineDownBold />}>
|
||||
{t('controlLayers.moveToBack')}
|
||||
</MenuItem>
|
||||
</>
|
||||
)}
|
||||
<MenuItem onClick={resetEntity} icon={<PiArrowCounterClockwiseBold />}>
|
||||
{t('accessibility.reset')}
|
||||
</MenuItem>
|
||||
{validActions.isDeleteable && (
|
||||
<MenuItem onClick={deleteEntity} icon={<PiTrashSimpleBold />} color="error.300">
|
||||
{t('common.delete')}
|
||||
</MenuItem>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
CanvasEntityActionMenuItems.displayName = 'CanvasEntityActionMenuItems';
|
@ -1,22 +1,23 @@
|
||||
import type { ChakraProps } from '@invoke-ai/ui-library';
|
||||
import { Flex } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||
import { useEntitySelectionColor } from 'features/controlLayers/hooks/useEntitySelectionColor';
|
||||
import { useIsEntitySelected } from 'features/controlLayers/hooks/useIsEntitySelected';
|
||||
import { entitySelected } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
|
||||
type Props = PropsWithChildren<{
|
||||
isSelected: boolean;
|
||||
onSelect: () => void;
|
||||
selectedBorderColor?: ChakraProps['bg'];
|
||||
}>;
|
||||
|
||||
export const CanvasEntityContainer = memo((props: Props) => {
|
||||
const { isSelected, onSelect, selectedBorderColor = 'base.400', children } = props;
|
||||
const _onSelect = useCallback(() => {
|
||||
export const CanvasEntityContainer = memo((props: PropsWithChildren) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const entityIdentifier = useEntityIdentifierContext();
|
||||
const isSelected = useIsEntitySelected(entityIdentifier);
|
||||
const selectionColor = useEntitySelectionColor(entityIdentifier);
|
||||
const onClick = useCallback(() => {
|
||||
if (isSelected) {
|
||||
return;
|
||||
}
|
||||
onSelect();
|
||||
}, [isSelected, onSelect]);
|
||||
dispatch(entitySelected({ entityIdentifier }));
|
||||
}, [dispatch, entityIdentifier, isSelected]);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
@ -24,12 +25,12 @@ export const CanvasEntityContainer = memo((props: Props) => {
|
||||
flexDir="column"
|
||||
w="full"
|
||||
bg={isSelected ? 'base.800' : 'base.850'}
|
||||
onClick={_onSelect}
|
||||
onClick={onClick}
|
||||
borderInlineStartWidth={5}
|
||||
borderColor={isSelected ? selectedBorderColor : 'base.800'}
|
||||
borderColor={isSelected ? selectionColor : 'base.800'}
|
||||
borderRadius="base"
|
||||
>
|
||||
{children}
|
||||
{props.children}
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
|
@ -1,13 +1,19 @@
|
||||
import { IconButton } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { stopPropagation } from 'common/util/stopPropagation';
|
||||
import { memo } from 'react';
|
||||
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||
import { entityDeleted } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiTrashSimpleBold } from 'react-icons/pi';
|
||||
|
||||
type Props = { onDelete: () => void };
|
||||
|
||||
export const CanvasEntityDeleteButton = memo(({ onDelete }: Props) => {
|
||||
export const CanvasEntityDeleteButton = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const entityIdentifier = useEntityIdentifierContext();
|
||||
const onClick = useCallback(() => {
|
||||
dispatch(entityDeleted({ entityIdentifier }));
|
||||
}, [dispatch, entityIdentifier]);
|
||||
return (
|
||||
<IconButton
|
||||
size="sm"
|
||||
@ -15,7 +21,7 @@ export const CanvasEntityDeleteButton = memo(({ onDelete }: Props) => {
|
||||
aria-label={t('common.delete')}
|
||||
tooltip={t('common.delete')}
|
||||
icon={<PiTrashSimpleBold />}
|
||||
onClick={onDelete}
|
||||
onClick={onClick}
|
||||
onDoubleClick={stopPropagation} // double click expands the layer
|
||||
/>
|
||||
);
|
||||
|
@ -1,16 +1,21 @@
|
||||
import { IconButton } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { stopPropagation } from 'common/util/stopPropagation';
|
||||
import { memo } from 'react';
|
||||
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||
import { useEntityIsEnabled } from 'features/controlLayers/hooks/useEntityIsEnabled';
|
||||
import { entityIsEnabledToggled } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiCheckBold } from 'react-icons/pi';
|
||||
|
||||
type Props = {
|
||||
isEnabled: boolean;
|
||||
onToggle: () => void;
|
||||
};
|
||||
|
||||
export const CanvasEntityEnabledToggle = memo(({ isEnabled, onToggle }: Props) => {
|
||||
export const CanvasEntityEnabledToggle = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const entityIdentifier = useEntityIdentifierContext();
|
||||
const isEnabled = useEntityIsEnabled(entityIdentifier);
|
||||
const dispatch = useAppDispatch();
|
||||
const onClick = useCallback(() => {
|
||||
dispatch(entityIsEnabledToggled({ entityIdentifier }));
|
||||
}, [dispatch, entityIdentifier]);
|
||||
|
||||
return (
|
||||
<IconButton
|
||||
@ -19,7 +24,7 @@ export const CanvasEntityEnabledToggle = memo(({ isEnabled, onToggle }: Props) =
|
||||
tooltip={t(isEnabled ? 'common.enabled' : 'common.disabled')}
|
||||
variant="outline"
|
||||
icon={isEnabled ? <PiCheckBold /> : undefined}
|
||||
onClick={onToggle}
|
||||
onClick={onClick}
|
||||
colorScheme="base"
|
||||
onDoubleClick={stopPropagation} // double click expands the layer
|
||||
/>
|
||||
|
@ -1,12 +1,30 @@
|
||||
import { Text } from '@invoke-ai/ui-library';
|
||||
import { memo } from 'react';
|
||||
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||
import { useIsEntitySelected } from 'features/controlLayers/hooks/useIsEntitySelected';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
type Props = {
|
||||
title: string;
|
||||
isSelected: boolean;
|
||||
};
|
||||
export const CanvasEntityTitle = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const entityIdentifier = useEntityIdentifierContext();
|
||||
const isSelected = useIsEntitySelected(entityIdentifier);
|
||||
const title = useMemo(() => {
|
||||
if (entityIdentifier.type === 'inpaint_mask') {
|
||||
return t('controlLayers.inpaintMask');
|
||||
} else if (entityIdentifier.type === 'control_adapter') {
|
||||
return t('controlLayers.globalControlAdapter');
|
||||
} else if (entityIdentifier.type === 'layer') {
|
||||
return t('controlLayers.layer');
|
||||
} else if (entityIdentifier.type === 'ip_adapter') {
|
||||
return t('controlLayers.ipAdapter');
|
||||
} else if (entityIdentifier.type === 'regional_guidance') {
|
||||
return t('controlLayers.regionalGuidance');
|
||||
} else {
|
||||
assert(false, 'Unexpected entity type');
|
||||
}
|
||||
}, [entityIdentifier.type, t]);
|
||||
|
||||
export const CanvasEntityTitle = memo(({ title, isSelected }: Props) => {
|
||||
return (
|
||||
<Text size="sm" fontWeight="semibold" userSelect="none" color={isSelected ? 'base.100' : 'base.300'}>
|
||||
{title}
|
||||
|
@ -0,0 +1,11 @@
|
||||
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
|
||||
import { createContext, useContext } from 'react';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
export const EntityIdentifierContext = createContext<CanvasEntityIdentifier | null>(null);
|
||||
|
||||
export const useEntityIdentifierContext = (): CanvasEntityIdentifier => {
|
||||
const entityIdentifier = useContext(EntityIdentifierContext);
|
||||
assert(entityIdentifier, 'useEntityIdentifier must be used within a EntityIdentifierProvider');
|
||||
return entityIdentifier;
|
||||
};
|
@ -2,10 +2,7 @@ import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { useAssertSingleton } from 'common/hooks/useAssertSingleton';
|
||||
import {
|
||||
caDeleted,
|
||||
ipaDeleted,
|
||||
layerDeleted,
|
||||
rgDeleted,
|
||||
entityDeleted,
|
||||
selectCanvasV2Slice,
|
||||
} from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
@ -26,19 +23,7 @@ export function useCanvasDeleteLayerHotkey() {
|
||||
if (selectedEntityIdentifier === null) {
|
||||
return;
|
||||
}
|
||||
const { type, id } = selectedEntityIdentifier;
|
||||
if (type === 'layer') {
|
||||
dispatch(layerDeleted({ id }));
|
||||
}
|
||||
if (type === 'regional_guidance') {
|
||||
dispatch(rgDeleted({ id }));
|
||||
}
|
||||
if (type === 'control_adapter') {
|
||||
dispatch(caDeleted({ id }));
|
||||
}
|
||||
if (type === 'ip_adapter') {
|
||||
dispatch(ipaDeleted({ id }));
|
||||
}
|
||||
dispatch(entityDeleted({ entityIdentifier: selectedEntityIdentifier }));
|
||||
}, [dispatch, selectedEntityIdentifier]);
|
||||
|
||||
const isDeleteEnabled = useMemo(
|
||||
|
@ -2,9 +2,7 @@ import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { useAssertSingleton } from 'common/hooks/useAssertSingleton';
|
||||
import {
|
||||
imReset,
|
||||
layerReset,
|
||||
rgReset,
|
||||
entityReset,
|
||||
selectCanvasV2Slice,
|
||||
} from 'features/controlLayers/store/canvasV2Slice';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
@ -25,16 +23,7 @@ export function useCanvasResetLayerHotkey() {
|
||||
if (selectedEntityIdentifier === null) {
|
||||
return;
|
||||
}
|
||||
const { type, id } = selectedEntityIdentifier;
|
||||
if (type === 'layer') {
|
||||
dispatch(layerReset({ id }));
|
||||
}
|
||||
if (type === 'regional_guidance') {
|
||||
dispatch(rgReset({ id }));
|
||||
}
|
||||
if (type === 'inpaint_mask') {
|
||||
dispatch(imReset());
|
||||
}
|
||||
dispatch(entityReset({ entityIdentifier: selectedEntityIdentifier }));
|
||||
}, [dispatch, selectedEntityIdentifier]);
|
||||
|
||||
const isResetEnabled = useMemo(
|
||||
|
@ -0,0 +1,22 @@
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { selectCanvasV2Slice, selectEntity } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export const useEntityIsEnabled = (entityIdentifier: CanvasEntityIdentifier) => {
|
||||
const selectIsEnabled = useMemo(
|
||||
() =>
|
||||
createSelector(selectCanvasV2Slice, (canvasV2) => {
|
||||
const entity = selectEntity(canvasV2, entityIdentifier);
|
||||
if (!entity) {
|
||||
return false;
|
||||
} else {
|
||||
return entity.isEnabled;
|
||||
}
|
||||
}),
|
||||
[entityIdentifier]
|
||||
);
|
||||
const isEnabled = useAppSelector(selectIsEnabled);
|
||||
return isEnabled;
|
||||
};
|
@ -0,0 +1,27 @@
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { rgbColorToString } from 'common/util/colorCodeTransformers';
|
||||
import { selectCanvasV2Slice, selectEntity } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export const useEntitySelectionColor = (entityIdentifier: CanvasEntityIdentifier) => {
|
||||
const selectSelectionColor = useMemo(
|
||||
() =>
|
||||
createSelector(selectCanvasV2Slice, (canvasV2) => {
|
||||
const entity = selectEntity(canvasV2, entityIdentifier);
|
||||
if (!entity) {
|
||||
return 'base.400';
|
||||
} else if (entity.type === 'inpaint_mask') {
|
||||
return rgbColorToString(entity.fill);
|
||||
} else if (entity.type === 'regional_guidance') {
|
||||
return rgbColorToString(entity.fill);
|
||||
} else {
|
||||
return 'base.400';
|
||||
}
|
||||
}),
|
||||
[entityIdentifier]
|
||||
);
|
||||
const selectionColor = useAppSelector(selectSelectionColor);
|
||||
return selectionColor;
|
||||
};
|
@ -0,0 +1,12 @@
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export const useIsEntitySelected = (entityIdentifier: CanvasEntityIdentifier) => {
|
||||
const selectedEntityIdentifier = useAppSelector((s) => s.canvasV2.selectedEntityIdentifier);
|
||||
const isSelected = useMemo(() => {
|
||||
return selectedEntityIdentifier?.id === entityIdentifier.id;
|
||||
}, [selectedEntityIdentifier, entityIdentifier.id]);
|
||||
|
||||
return isSelected;
|
||||
};
|
@ -218,16 +218,9 @@ export class CanvasBbox {
|
||||
}
|
||||
|
||||
render() {
|
||||
const session = this.manager.stateApi.getSession();
|
||||
const bbox = this.manager.stateApi.getBbox();
|
||||
const toolState = this.manager.stateApi.getToolState();
|
||||
|
||||
if (!session.isActive) {
|
||||
this.konva.group.listening(false);
|
||||
this.konva.group.visible(false);
|
||||
return;
|
||||
}
|
||||
|
||||
this.konva.group.visible(true);
|
||||
this.konva.group.listening(toolState.selected === 'bbox');
|
||||
this.konva.rect.setAttrs({
|
||||
|
@ -2,7 +2,12 @@ import { deepClone } from 'common/util/deepClone';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import { CanvasObjectRenderer } from 'features/controlLayers/konva/CanvasObjectRenderer';
|
||||
import { CanvasTransformer } from 'features/controlLayers/konva/CanvasTransformer';
|
||||
import type { CanvasLayerState, CanvasV2State, GetLoggingContext } from 'features/controlLayers/store/types';
|
||||
import type {
|
||||
CanvasEntityIdentifier,
|
||||
CanvasLayerState,
|
||||
CanvasV2State,
|
||||
GetLoggingContext,
|
||||
} from 'features/controlLayers/store/types';
|
||||
import Konva from 'konva';
|
||||
import { get } from 'lodash-es';
|
||||
import type { Logger } from 'roarr';
|
||||
@ -48,6 +53,13 @@ export class CanvasLayerAdapter {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get this entity's entity identifier
|
||||
*/
|
||||
getEntityIdentifier = (): CanvasEntityIdentifier => {
|
||||
return { id: this.id, type: this.type };
|
||||
};
|
||||
|
||||
destroy = (): void => {
|
||||
this.log.debug('Destroying layer');
|
||||
// We need to call the destroy method on all children so they can do their own cleanup.
|
||||
|
@ -2,6 +2,7 @@ import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import { CanvasObjectRenderer } from 'features/controlLayers/konva/CanvasObjectRenderer';
|
||||
import { CanvasTransformer } from 'features/controlLayers/konva/CanvasTransformer';
|
||||
import type {
|
||||
CanvasEntityIdentifier,
|
||||
CanvasInpaintMaskState,
|
||||
CanvasRegionalGuidanceState,
|
||||
CanvasV2State,
|
||||
@ -54,6 +55,13 @@ export class CanvasMaskAdapter {
|
||||
this.maskOpacity = this.manager.stateApi.getMaskOpacity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get this entity's entity identifier
|
||||
*/
|
||||
getEntityIdentifier = (): CanvasEntityIdentifier => {
|
||||
return { id: this.id, type: this.type };
|
||||
};
|
||||
|
||||
destroy = (): void => {
|
||||
this.log.debug('Destroying mask');
|
||||
// We need to call the destroy method on all children so they can do their own cleanup.
|
||||
|
@ -8,11 +8,7 @@ import type { CanvasLayerAdapter } from 'features/controlLayers/konva/CanvasLaye
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import type { CanvasMaskAdapter } from 'features/controlLayers/konva/CanvasMaskAdapter';
|
||||
import { CanvasRectRenderer } from 'features/controlLayers/konva/CanvasRect';
|
||||
import {
|
||||
getPrefixedId,
|
||||
konvaNodeToBlob,
|
||||
previewBlob,
|
||||
} from 'features/controlLayers/konva/util';
|
||||
import { getPrefixedId, konvaNodeToBlob, previewBlob } from 'features/controlLayers/konva/util';
|
||||
import {
|
||||
type CanvasBrushLineState,
|
||||
type CanvasEraserLineState,
|
||||
@ -299,11 +295,17 @@ export class CanvasObjectRenderer {
|
||||
this.buffer.id = getPrefixedId(this.buffer.type);
|
||||
|
||||
if (this.buffer.type === 'brush_line') {
|
||||
this.manager.stateApi.addBrushLine({ id: this.parent.id, brushLine: this.buffer }, this.parent.type);
|
||||
this.manager.stateApi.addBrushLine({
|
||||
entityIdentifier: this.parent.getEntityIdentifier(),
|
||||
brushLine: this.buffer,
|
||||
});
|
||||
} else if (this.buffer.type === 'eraser_line') {
|
||||
this.manager.stateApi.addEraserLine({ id: this.parent.id, eraserLine: this.buffer }, this.parent.type);
|
||||
this.manager.stateApi.addEraserLine({
|
||||
entityIdentifier: this.parent.getEntityIdentifier(),
|
||||
eraserLine: this.buffer,
|
||||
});
|
||||
} else if (this.buffer.type === 'rect') {
|
||||
this.manager.stateApi.addRect({ id: this.parent.id, rect: this.buffer }, this.parent.type);
|
||||
this.manager.stateApi.addRect({ entityIdentifier: this.parent.getEntityIdentifier(), rect: this.buffer });
|
||||
} else {
|
||||
this.log.warn({ buffer: this.buffer }, 'Invalid buffer object type');
|
||||
}
|
||||
@ -356,10 +358,11 @@ export class CanvasObjectRenderer {
|
||||
const imageDTO = await uploadImage(blob, `${this.id}_rasterized.png`, 'other', true);
|
||||
const imageObject = imageDTOToImageObject(imageDTO);
|
||||
await this.renderObject(imageObject, true);
|
||||
this.manager.stateApi.rasterizeEntity(
|
||||
{ id: this.parent.id, imageObject, position: { x: Math.round(rect.x), y: Math.round(rect.y) } },
|
||||
this.parent.type
|
||||
);
|
||||
this.manager.stateApi.rasterizeEntity({
|
||||
entityIdentifier: this.parent.getEntityIdentifier(),
|
||||
imageObject,
|
||||
position: { x: Math.round(rect.x), y: Math.round(rect.y) },
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -15,44 +15,31 @@ import {
|
||||
$stageAttrs,
|
||||
bboxChanged,
|
||||
brushWidthChanged,
|
||||
caTranslated,
|
||||
entityBrushLineAdded,
|
||||
entityEraserLineAdded,
|
||||
entityMoved,
|
||||
entityRasterized,
|
||||
entityRectAdded,
|
||||
entityReset,
|
||||
entitySelected,
|
||||
eraserWidthChanged,
|
||||
imBrushLineAdded,
|
||||
imEraserLineAdded,
|
||||
imImageCacheChanged,
|
||||
imMoved,
|
||||
imRectAdded,
|
||||
inpaintMaskRasterized,
|
||||
layerBrushLineAdded,
|
||||
layerEraserLineAdded,
|
||||
layerImageCacheChanged,
|
||||
layerRasterized,
|
||||
layerRectAdded,
|
||||
layerReset,
|
||||
layerTranslated,
|
||||
rgBrushLineAdded,
|
||||
rgEraserLineAdded,
|
||||
rgImageCacheChanged,
|
||||
rgMoved,
|
||||
rgRasterized,
|
||||
rgRectAdded,
|
||||
toolBufferChanged,
|
||||
toolChanged,
|
||||
} from 'features/controlLayers/store/canvasV2Slice';
|
||||
import type {
|
||||
CanvasBrushLineState,
|
||||
CanvasEntityIdentifier,
|
||||
CanvasEntityState,
|
||||
CanvasEraserLineState,
|
||||
CanvasRectState,
|
||||
EntityRasterizedArg,
|
||||
PositionChangedArg,
|
||||
EntityBrushLineAddedPayload,
|
||||
EntityEraserLineAddedPayload,
|
||||
EntityIdentifierPayload,
|
||||
EntityMovedPayload,
|
||||
EntityRasterizedPayload,
|
||||
EntityRectAddedPayload,
|
||||
Rect,
|
||||
Tool,
|
||||
} from 'features/controlLayers/store/types';
|
||||
import type { ImageDTO } from 'services/api/types';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
const log = logger('canvas');
|
||||
|
||||
@ -69,67 +56,31 @@ export class CanvasStateApi {
|
||||
getState = () => {
|
||||
return this._store.getState().canvasV2;
|
||||
};
|
||||
resetEntity = (arg: { id: string }, entityType: CanvasEntityState['type']) => {
|
||||
log.trace({ arg, entityType }, 'Resetting entity');
|
||||
if (entityType === 'layer') {
|
||||
this._store.dispatch(layerReset(arg));
|
||||
}
|
||||
resetEntity = (arg: EntityIdentifierPayload) => {
|
||||
log.trace(arg, 'Resetting entity');
|
||||
this._store.dispatch(entityReset(arg));
|
||||
};
|
||||
setEntityPosition = (arg: PositionChangedArg, entityType: CanvasEntityState['type']) => {
|
||||
log.trace({ arg, entityType }, 'Setting entity position');
|
||||
if (entityType === 'layer') {
|
||||
this._store.dispatch(layerTranslated(arg));
|
||||
} else if (entityType === 'regional_guidance') {
|
||||
this._store.dispatch(rgMoved(arg));
|
||||
} else if (entityType === 'inpaint_mask') {
|
||||
this._store.dispatch(imMoved(arg));
|
||||
} else if (entityType === 'control_adapter') {
|
||||
this._store.dispatch(caTranslated(arg));
|
||||
}
|
||||
setEntityPosition = (arg: EntityMovedPayload) => {
|
||||
log.trace(arg, 'Setting entity position');
|
||||
this._store.dispatch(entityMoved(arg));
|
||||
};
|
||||
addBrushLine = (arg: { id: string; brushLine: CanvasBrushLineState }, entityType: CanvasEntityState['type']) => {
|
||||
log.trace({ arg, entityType }, 'Adding brush line');
|
||||
if (entityType === 'layer') {
|
||||
this._store.dispatch(layerBrushLineAdded(arg));
|
||||
} else if (entityType === 'regional_guidance') {
|
||||
this._store.dispatch(rgBrushLineAdded(arg));
|
||||
} else if (entityType === 'inpaint_mask') {
|
||||
this._store.dispatch(imBrushLineAdded(arg));
|
||||
}
|
||||
addBrushLine = (arg: EntityBrushLineAddedPayload) => {
|
||||
log.trace(arg, 'Adding brush line');
|
||||
this._store.dispatch(entityBrushLineAdded(arg));
|
||||
};
|
||||
addEraserLine = (arg: { id: string; eraserLine: CanvasEraserLineState }, entityType: CanvasEntityState['type']) => {
|
||||
log.trace({ arg, entityType }, 'Adding eraser line');
|
||||
if (entityType === 'layer') {
|
||||
this._store.dispatch(layerEraserLineAdded(arg));
|
||||
} else if (entityType === 'regional_guidance') {
|
||||
this._store.dispatch(rgEraserLineAdded(arg));
|
||||
} else if (entityType === 'inpaint_mask') {
|
||||
this._store.dispatch(imEraserLineAdded(arg));
|
||||
}
|
||||
addEraserLine = (arg: EntityEraserLineAddedPayload) => {
|
||||
log.trace(arg, 'Adding eraser line');
|
||||
this._store.dispatch(entityEraserLineAdded(arg));
|
||||
};
|
||||
addRect = (arg: { id: string; rect: CanvasRectState }, entityType: CanvasEntityState['type']) => {
|
||||
log.trace({ arg, entityType }, 'Adding rect');
|
||||
if (entityType === 'layer') {
|
||||
this._store.dispatch(layerRectAdded(arg));
|
||||
} else if (entityType === 'regional_guidance') {
|
||||
this._store.dispatch(rgRectAdded(arg));
|
||||
} else if (entityType === 'inpaint_mask') {
|
||||
this._store.dispatch(imRectAdded(arg));
|
||||
}
|
||||
addRect = (arg: EntityRectAddedPayload) => {
|
||||
log.trace(arg, 'Adding rect');
|
||||
this._store.dispatch(entityRectAdded(arg));
|
||||
};
|
||||
rasterizeEntity = (arg: EntityRasterizedArg, entityType: CanvasEntityState['type']) => {
|
||||
log.trace({ arg, entityType }, 'Rasterizing entity');
|
||||
if (entityType === 'layer') {
|
||||
this._store.dispatch(layerRasterized(arg));
|
||||
} else if (entityType === 'inpaint_mask') {
|
||||
this._store.dispatch(inpaintMaskRasterized(arg));
|
||||
} else if (entityType === 'regional_guidance') {
|
||||
this._store.dispatch(rgRasterized(arg));
|
||||
} else {
|
||||
assert(false, 'Rasterizing not supported for this entity type');
|
||||
}
|
||||
rasterizeEntity = (arg: EntityRasterizedPayload) => {
|
||||
log.trace(arg, 'Rasterizing entity');
|
||||
this._store.dispatch(entityRasterized(arg));
|
||||
};
|
||||
setSelectedEntity = (arg: CanvasEntityIdentifier) => {
|
||||
setSelectedEntity = (arg: EntityIdentifierPayload) => {
|
||||
log.trace({ arg }, 'Setting selected entity');
|
||||
this._store.dispatch(entitySelected(arg));
|
||||
};
|
||||
|
@ -356,7 +356,7 @@ export class CanvasTransformer {
|
||||
};
|
||||
|
||||
this.log.trace({ position }, 'Position changed');
|
||||
this.manager.stateApi.setEntityPosition({ id: this.parent.id, position }, this.parent.type);
|
||||
this.manager.stateApi.setEntityPosition({ entityIdentifier: this.parent.getEntityIdentifier(), position });
|
||||
});
|
||||
|
||||
this.subscriptions.add(
|
||||
@ -600,7 +600,7 @@ export class CanvasTransformer {
|
||||
// We shouldn't reset on the first render - the bbox will be calculated on the next render
|
||||
if (!this.parent.renderer.hasObjects()) {
|
||||
// The layer is fully transparent but has objects - reset it
|
||||
this.manager.stateApi.resetEntity({ id: this.parent.id }, this.parent.type);
|
||||
this.manager.stateApi.resetEntity({ entityIdentifier: this.parent.getEntityIdentifier() });
|
||||
}
|
||||
this.syncInteractionState();
|
||||
return;
|
||||
|
@ -1,6 +1,7 @@
|
||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||
import { createAction, createSlice } from '@reduxjs/toolkit';
|
||||
import type { PersistConfig, RootState } from 'app/store/store';
|
||||
import { moveOneToEnd, moveOneToStart, moveToEnd, moveToStart } from 'common/util/arrayUtils';
|
||||
import { deepClone } from 'common/util/deepClone';
|
||||
import { bboxReducers } from 'features/controlLayers/store/bboxReducers';
|
||||
import { compositingReducers } from 'features/controlLayers/store/compositingReducers';
|
||||
@ -20,8 +21,20 @@ import { getOptimalDimension } from 'features/parameters/util/optimalDimension';
|
||||
import { pick } from 'lodash-es';
|
||||
import { atom } from 'nanostores';
|
||||
import type { InvocationDenoiseProgressEvent } from 'services/events/types';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
import type { CanvasEntityIdentifier, CanvasV2State, Coordinate, StageAttrs } from './types';
|
||||
import type {
|
||||
CanvasEntityIdentifier,
|
||||
CanvasV2State,
|
||||
Coordinate,
|
||||
EntityBrushLineAddedPayload,
|
||||
EntityEraserLineAddedPayload,
|
||||
EntityIdentifierPayload,
|
||||
EntityMovedPayload,
|
||||
EntityRasterizedPayload,
|
||||
EntityRectAddedPayload,
|
||||
StageAttrs,
|
||||
} from './types';
|
||||
import { RGBA_RED } from './types';
|
||||
|
||||
const initialState: CanvasV2State = {
|
||||
@ -122,6 +135,23 @@ const initialState: CanvasV2State = {
|
||||
},
|
||||
};
|
||||
|
||||
export function selectEntity(state: CanvasV2State, { id, type }: CanvasEntityIdentifier) {
|
||||
switch (type) {
|
||||
case 'layer':
|
||||
return state.layers.entities.find((layer) => layer.id === id);
|
||||
case 'control_adapter':
|
||||
return state.controlAdapters.entities.find((ca) => ca.id === id);
|
||||
case 'inpaint_mask':
|
||||
return state.inpaintMask;
|
||||
case 'regional_guidance':
|
||||
return state.regions.entities.find((rg) => rg.id === id);
|
||||
case 'ip_adapter':
|
||||
return state.ipAdapters.entities.find((ip) => ip.id === id);
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
export const canvasV2Slice = createSlice({
|
||||
name: 'canvasV2',
|
||||
initialState,
|
||||
@ -138,8 +168,184 @@ export const canvasV2Slice = createSlice({
|
||||
...bboxReducers,
|
||||
...inpaintMaskReducers,
|
||||
...sessionReducers,
|
||||
entitySelected: (state, action: PayloadAction<CanvasEntityIdentifier>) => {
|
||||
state.selectedEntityIdentifier = action.payload;
|
||||
entitySelected: (state, action: PayloadAction<EntityIdentifierPayload>) => {
|
||||
const { entityIdentifier } = action.payload;
|
||||
state.selectedEntityIdentifier = entityIdentifier;
|
||||
},
|
||||
entityReset: (state, action: PayloadAction<EntityIdentifierPayload>) => {
|
||||
const { entityIdentifier } = action.payload;
|
||||
const entity = selectEntity(state, entityIdentifier);
|
||||
if (!entity) {
|
||||
return;
|
||||
} else if (entity.type === 'layer') {
|
||||
entity.isEnabled = true;
|
||||
entity.objects = [];
|
||||
entity.position = { x: 0, y: 0 };
|
||||
state.layers.imageCache = null;
|
||||
} else if (entity.type === 'inpaint_mask' || entity.type === 'regional_guidance') {
|
||||
entity.isEnabled = true;
|
||||
entity.objects = [];
|
||||
entity.position = { x: 0, y: 0 };
|
||||
entity.imageCache = null;
|
||||
} else {
|
||||
assert(false, 'Not implemented');
|
||||
}
|
||||
},
|
||||
entityIsEnabledToggled: (state, action: PayloadAction<EntityIdentifierPayload>) => {
|
||||
const { entityIdentifier } = action.payload;
|
||||
const entity = selectEntity(state, entityIdentifier);
|
||||
if (!entity) {
|
||||
return;
|
||||
}
|
||||
entity.isEnabled = !entity.isEnabled;
|
||||
},
|
||||
entityMoved: (state, action: PayloadAction<EntityMovedPayload>) => {
|
||||
const { entityIdentifier, position } = action.payload;
|
||||
const entity = selectEntity(state, entityIdentifier);
|
||||
if (!entity) {
|
||||
return;
|
||||
} else if (entity.type === 'layer') {
|
||||
entity.position = position;
|
||||
state.layers.imageCache = null;
|
||||
} else if (entity.type === 'inpaint_mask' || entity.type === 'regional_guidance') {
|
||||
entity.position = position;
|
||||
entity.imageCache = null;
|
||||
} else {
|
||||
assert(false, 'Not implemented');
|
||||
}
|
||||
},
|
||||
entityRasterized: (state, action: PayloadAction<EntityRasterizedPayload>) => {
|
||||
const { entityIdentifier, imageObject, position } = action.payload;
|
||||
const entity = selectEntity(state, entityIdentifier);
|
||||
if (!entity) {
|
||||
return;
|
||||
} else if (entity.type === 'layer') {
|
||||
entity.objects = [imageObject];
|
||||
entity.position = position;
|
||||
state.layers.imageCache = null;
|
||||
} else if (entity.type === 'inpaint_mask' || entity.type === 'regional_guidance') {
|
||||
entity.objects = [imageObject];
|
||||
entity.position = position;
|
||||
entity.imageCache = null;
|
||||
} else {
|
||||
assert(false, 'Not implemented');
|
||||
}
|
||||
},
|
||||
entityBrushLineAdded: (state, action: PayloadAction<EntityBrushLineAddedPayload>) => {
|
||||
const { entityIdentifier, brushLine } = action.payload;
|
||||
const entity = selectEntity(state, entityIdentifier);
|
||||
if (!entity) {
|
||||
return;
|
||||
} else if (entity.type === 'layer') {
|
||||
entity.objects.push(brushLine);
|
||||
state.layers.imageCache = null;
|
||||
} else if (entity.type === 'inpaint_mask' || entity.type === 'regional_guidance') {
|
||||
entity.objects.push(brushLine);
|
||||
entity.imageCache = null;
|
||||
} else {
|
||||
assert(false, 'Not implemented');
|
||||
}
|
||||
},
|
||||
entityEraserLineAdded: (state, action: PayloadAction<EntityEraserLineAddedPayload>) => {
|
||||
const { entityIdentifier, eraserLine } = action.payload;
|
||||
const entity = selectEntity(state, entityIdentifier);
|
||||
if (!entity) {
|
||||
return;
|
||||
} else if (entity.type === 'layer') {
|
||||
entity.objects.push(eraserLine);
|
||||
state.layers.imageCache = null;
|
||||
} else if (entity.type === 'inpaint_mask' || entity.type === 'regional_guidance') {
|
||||
entity.objects.push(eraserLine);
|
||||
entity.imageCache = null;
|
||||
} else {
|
||||
assert(false, 'Not implemented');
|
||||
}
|
||||
},
|
||||
entityRectAdded: (state, action: PayloadAction<EntityRectAddedPayload>) => {
|
||||
const { entityIdentifier, rect } = action.payload;
|
||||
const entity = selectEntity(state, entityIdentifier);
|
||||
if (!entity) {
|
||||
return;
|
||||
} else if (entity.type === 'layer') {
|
||||
entity.objects.push(rect);
|
||||
state.layers.imageCache = null;
|
||||
} else if (entity.type === 'inpaint_mask' || entity.type === 'regional_guidance') {
|
||||
entity.objects.push(rect);
|
||||
entity.imageCache = null;
|
||||
} else {
|
||||
assert(false, 'Not implemented');
|
||||
}
|
||||
},
|
||||
entityDeleted: (state, action: PayloadAction<EntityIdentifierPayload>) => {
|
||||
const { entityIdentifier } = action.payload;
|
||||
if (entityIdentifier.type === 'layer') {
|
||||
state.layers.entities = state.layers.entities.filter((layer) => layer.id !== entityIdentifier.id);
|
||||
state.layers.imageCache = null;
|
||||
} else if (entityIdentifier.type === 'regional_guidance') {
|
||||
state.regions.entities = state.regions.entities.filter((rg) => rg.id !== entityIdentifier.id);
|
||||
} else {
|
||||
assert(false, 'Not implemented');
|
||||
}
|
||||
},
|
||||
entityArrangedForwardOne: (state, action: PayloadAction<EntityIdentifierPayload>) => {
|
||||
const { entityIdentifier } = action.payload;
|
||||
const entity = selectEntity(state, entityIdentifier);
|
||||
if (!entity) {
|
||||
return;
|
||||
}
|
||||
if (entity.type === 'layer') {
|
||||
moveOneToEnd(state.layers.entities, entity);
|
||||
state.layers.imageCache = null;
|
||||
} else if (entity.type === 'regional_guidance') {
|
||||
moveOneToEnd(state.regions.entities, entity);
|
||||
} else if (entity.type === 'control_adapter') {
|
||||
moveOneToEnd(state.controlAdapters.entities, entity);
|
||||
}
|
||||
},
|
||||
entityArrangedToFront: (state, action: PayloadAction<EntityIdentifierPayload>) => {
|
||||
const { entityIdentifier } = action.payload;
|
||||
const entity = selectEntity(state, entityIdentifier);
|
||||
if (!entity) {
|
||||
return;
|
||||
}
|
||||
if (entity.type === 'layer') {
|
||||
moveToEnd(state.layers.entities, entity);
|
||||
state.layers.imageCache = null;
|
||||
} else if (entity.type === 'regional_guidance') {
|
||||
moveToEnd(state.regions.entities, entity);
|
||||
} else if (entity.type === 'control_adapter') {
|
||||
moveToEnd(state.controlAdapters.entities, entity);
|
||||
}
|
||||
},
|
||||
entityArrangedBackwardOne: (state, action: PayloadAction<EntityIdentifierPayload>) => {
|
||||
const { entityIdentifier } = action.payload;
|
||||
const entity = selectEntity(state, entityIdentifier);
|
||||
if (!entity) {
|
||||
return;
|
||||
}
|
||||
if (entity.type === 'layer') {
|
||||
moveOneToStart(state.layers.entities, entity);
|
||||
state.layers.imageCache = null;
|
||||
} else if (entity.type === 'regional_guidance') {
|
||||
moveOneToStart(state.regions.entities, entity);
|
||||
} else if (entity.type === 'control_adapter') {
|
||||
moveOneToStart(state.controlAdapters.entities, entity);
|
||||
}
|
||||
},
|
||||
entityArrangedToBack: (state, action: PayloadAction<EntityIdentifierPayload>) => {
|
||||
const { entityIdentifier } = action.payload;
|
||||
const entity = selectEntity(state, entityIdentifier);
|
||||
if (!entity) {
|
||||
return;
|
||||
}
|
||||
if (entity.type === 'layer') {
|
||||
moveToStart(state.layers.entities, entity);
|
||||
state.layers.imageCache = null;
|
||||
} else if (entity.type === 'regional_guidance') {
|
||||
moveToStart(state.regions.entities, entity);
|
||||
} else if (entity.type === 'control_adapter') {
|
||||
moveToStart(state.controlAdapters.entities, entity);
|
||||
}
|
||||
},
|
||||
allEntitiesDeleted: (state) => {
|
||||
state.regions.entities = [];
|
||||
@ -176,10 +382,23 @@ export const {
|
||||
toolChanged,
|
||||
toolBufferChanged,
|
||||
maskOpacityChanged,
|
||||
entitySelected,
|
||||
allEntitiesDeleted,
|
||||
clipToBboxChanged,
|
||||
canvasReset,
|
||||
// All entities
|
||||
entitySelected,
|
||||
entityReset,
|
||||
entityIsEnabledToggled,
|
||||
entityMoved,
|
||||
entityRasterized,
|
||||
entityBrushLineAdded,
|
||||
entityEraserLineAdded,
|
||||
entityRectAdded,
|
||||
entityDeleted,
|
||||
entityArrangedForwardOne,
|
||||
entityArrangedToFront,
|
||||
entityArrangedBackwardOne,
|
||||
entityArrangedToBack,
|
||||
// bbox
|
||||
bboxChanged,
|
||||
bboxScaledSizeChanged,
|
||||
@ -193,23 +412,10 @@ export const {
|
||||
// layers
|
||||
layerAdded,
|
||||
layerRecalled,
|
||||
layerDeleted,
|
||||
layerReset,
|
||||
layerMovedForwardOne,
|
||||
layerMovedToFront,
|
||||
layerMovedBackwardOne,
|
||||
layerMovedToBack,
|
||||
layerIsEnabledToggled,
|
||||
layerOpacityChanged,
|
||||
layerTranslated,
|
||||
layerBboxChanged,
|
||||
layerImageAdded,
|
||||
layerAllDeleted,
|
||||
layerImageCacheChanged,
|
||||
layerBrushLineAdded,
|
||||
layerEraserLineAdded,
|
||||
layerRectAdded,
|
||||
layerRasterized,
|
||||
// IP Adapters
|
||||
ipaAdded,
|
||||
ipaRecalled,
|
||||
@ -224,16 +430,8 @@ export const {
|
||||
ipaBeginEndStepPctChanged,
|
||||
// Control Adapters
|
||||
caAdded,
|
||||
caBboxChanged,
|
||||
caDeleted,
|
||||
caAllDeleted,
|
||||
caIsEnabledToggled,
|
||||
caMovedBackwardOne,
|
||||
caMovedForwardOne,
|
||||
caMovedToBack,
|
||||
caMovedToFront,
|
||||
caOpacityChanged,
|
||||
caTranslated,
|
||||
caRecalled,
|
||||
caImageChanged,
|
||||
caProcessedImageChanged,
|
||||
@ -244,19 +442,10 @@ export const {
|
||||
caProcessorPendingBatchIdChanged,
|
||||
caWeightChanged,
|
||||
caBeginEndStepPctChanged,
|
||||
caScaled,
|
||||
// Regions
|
||||
rgAdded,
|
||||
rgRecalled,
|
||||
rgReset,
|
||||
rgIsEnabledToggled,
|
||||
rgMoved,
|
||||
rgDeleted,
|
||||
rgAllDeleted,
|
||||
rgMovedForwardOne,
|
||||
rgMovedToFront,
|
||||
rgMovedBackwardOne,
|
||||
rgMovedToBack,
|
||||
rgPositivePromptChanged,
|
||||
rgNegativePromptChanged,
|
||||
rgFillChanged,
|
||||
@ -270,10 +459,6 @@ export const {
|
||||
rgIPAdapterMethodChanged,
|
||||
rgIPAdapterModelChanged,
|
||||
rgIPAdapterCLIPVisionModelChanged,
|
||||
rgBrushLineAdded,
|
||||
rgEraserLineAdded,
|
||||
rgRectAdded,
|
||||
rgRasterized,
|
||||
// Compositing
|
||||
setInfillMethod,
|
||||
setInfillTileSize,
|
||||
@ -319,16 +504,9 @@ export const {
|
||||
loraIsEnabledChanged,
|
||||
loraAllDeleted,
|
||||
// Inpaint mask
|
||||
imReset,
|
||||
imRecalled,
|
||||
imIsEnabledToggled,
|
||||
imMoved,
|
||||
imFillChanged,
|
||||
imImageCacheChanged,
|
||||
imBrushLineAdded,
|
||||
imEraserLineAdded,
|
||||
imRectAdded,
|
||||
inpaintMaskRasterized,
|
||||
// Staging
|
||||
sessionStartedStaging,
|
||||
sessionImageStaged,
|
||||
|
@ -1,24 +1,20 @@
|
||||
import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit';
|
||||
import { moveOneToEnd, moveOneToStart, moveToEnd, moveToStart } from 'common/util/arrayUtils';
|
||||
import { zModelIdentifierField } from 'features/nodes/types/common';
|
||||
import type { IRect } from 'konva/lib/types';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import type { ControlNetModelConfig, ImageDTO, T2IAdapterModelConfig } from 'services/api/types';
|
||||
import { assert } from 'tsafe';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import type {
|
||||
CanvasV2State,
|
||||
CanvasControlAdapterState,
|
||||
CanvasControlNetState,
|
||||
CanvasT2IAdapterState,
|
||||
CanvasV2State,
|
||||
ControlModeV2,
|
||||
ControlNetConfig,
|
||||
CanvasControlNetState,
|
||||
Filter,
|
||||
PositionChangedArg,
|
||||
ProcessorConfig,
|
||||
ScaleChangedArg,
|
||||
T2IAdapterConfig,
|
||||
CanvasT2IAdapterState,
|
||||
} from './types';
|
||||
import { buildControlAdapterProcessorV2, imageDTOToImageObject } from './types';
|
||||
|
||||
@ -56,58 +52,6 @@ export const controlAdaptersReducers = {
|
||||
state.controlAdapters.entities.push(data);
|
||||
state.selectedEntityIdentifier = { type: 'control_adapter', id: data.id };
|
||||
},
|
||||
caIsEnabledToggled: (state, action: PayloadAction<{ id: string }>) => {
|
||||
const { id } = action.payload;
|
||||
const ca = selectCA(state, id);
|
||||
if (!ca) {
|
||||
return;
|
||||
}
|
||||
ca.isEnabled = !ca.isEnabled;
|
||||
},
|
||||
caTranslated: (state, action: PayloadAction<PositionChangedArg>) => {
|
||||
const { id, position } = action.payload;
|
||||
const ca = selectCA(state, id);
|
||||
if (!ca) {
|
||||
return;
|
||||
}
|
||||
ca.position = position;
|
||||
},
|
||||
caScaled: (state, action: PayloadAction<ScaleChangedArg>) => {
|
||||
const { id, scale, position } = action.payload;
|
||||
const ca = selectCA(state, id);
|
||||
if (!ca) {
|
||||
return;
|
||||
}
|
||||
if (ca.imageObject) {
|
||||
ca.imageObject.x *= scale;
|
||||
ca.imageObject.y *= scale;
|
||||
ca.imageObject.height *= scale;
|
||||
ca.imageObject.width *= scale;
|
||||
}
|
||||
|
||||
if (ca.processedImageObject) {
|
||||
ca.processedImageObject.x *= scale;
|
||||
ca.processedImageObject.y *= scale;
|
||||
ca.processedImageObject.height *= scale;
|
||||
ca.processedImageObject.width *= scale;
|
||||
}
|
||||
ca.position = position;
|
||||
ca.bboxNeedsUpdate = true;
|
||||
state.layers.imageCache = null;
|
||||
},
|
||||
caBboxChanged: (state, action: PayloadAction<{ id: string; bbox: IRect | null }>) => {
|
||||
const { id, bbox } = action.payload;
|
||||
const ca = selectCA(state, id);
|
||||
if (!ca) {
|
||||
return;
|
||||
}
|
||||
ca.bbox = bbox;
|
||||
ca.bboxNeedsUpdate = false;
|
||||
},
|
||||
caDeleted: (state, action: PayloadAction<{ id: string }>) => {
|
||||
const { id } = action.payload;
|
||||
state.controlAdapters.entities = state.controlAdapters.entities.filter((ca) => ca.id !== id);
|
||||
},
|
||||
caAllDeleted: (state) => {
|
||||
state.controlAdapters.entities = [];
|
||||
},
|
||||
@ -119,38 +63,6 @@ export const controlAdaptersReducers = {
|
||||
}
|
||||
ca.opacity = opacity;
|
||||
},
|
||||
caMovedForwardOne: (state, action: PayloadAction<{ id: string }>) => {
|
||||
const { id } = action.payload;
|
||||
const ca = selectCA(state, id);
|
||||
if (!ca) {
|
||||
return;
|
||||
}
|
||||
moveOneToEnd(state.controlAdapters.entities, ca);
|
||||
},
|
||||
caMovedToFront: (state, action: PayloadAction<{ id: string }>) => {
|
||||
const { id } = action.payload;
|
||||
const ca = selectCA(state, id);
|
||||
if (!ca) {
|
||||
return;
|
||||
}
|
||||
moveToEnd(state.controlAdapters.entities, ca);
|
||||
},
|
||||
caMovedBackwardOne: (state, action: PayloadAction<{ id: string }>) => {
|
||||
const { id } = action.payload;
|
||||
const ca = selectCA(state, id);
|
||||
if (!ca) {
|
||||
return;
|
||||
}
|
||||
moveOneToStart(state.controlAdapters.entities, ca);
|
||||
},
|
||||
caMovedToBack: (state, action: PayloadAction<{ id: string }>) => {
|
||||
const { id } = action.payload;
|
||||
const ca = selectCA(state, id);
|
||||
if (!ca) {
|
||||
return;
|
||||
}
|
||||
moveToStart(state.controlAdapters.entities, ca);
|
||||
},
|
||||
caImageChanged: {
|
||||
reducer: (state, action: PayloadAction<{ id: string; imageDTO: ImageDTO | null; objectId: string }>) => {
|
||||
const { id, imageDTO, objectId } = action.payload;
|
||||
|
@ -1,35 +1,16 @@
|
||||
import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit';
|
||||
import type {
|
||||
CanvasBrushLineState,
|
||||
CanvasEraserLineState,
|
||||
CanvasInpaintMaskState,
|
||||
CanvasRectState,
|
||||
CanvasV2State,
|
||||
Coordinate,
|
||||
EntityRasterizedArg,
|
||||
} from 'features/controlLayers/store/types';
|
||||
import type { CanvasInpaintMaskState, CanvasV2State } from 'features/controlLayers/store/types';
|
||||
import { imageDTOToImageWithDims } from 'features/controlLayers/store/types';
|
||||
import type { ImageDTO } from 'services/api/types';
|
||||
|
||||
import type { RgbColor } from './types';
|
||||
|
||||
export const inpaintMaskReducers = {
|
||||
imReset: (state) => {
|
||||
state.inpaintMask.objects = [];
|
||||
state.inpaintMask.imageCache = null;
|
||||
},
|
||||
imRecalled: (state, action: PayloadAction<{ data: CanvasInpaintMaskState }>) => {
|
||||
const { data } = action.payload;
|
||||
state.inpaintMask = data;
|
||||
state.selectedEntityIdentifier = { type: 'inpaint_mask', id: data.id };
|
||||
},
|
||||
imIsEnabledToggled: (state) => {
|
||||
state.inpaintMask.isEnabled = !state.inpaintMask.isEnabled;
|
||||
},
|
||||
imMoved: (state, action: PayloadAction<{ position: Coordinate }>) => {
|
||||
const { position } = action.payload;
|
||||
state.inpaintMask.position = position;
|
||||
},
|
||||
imFillChanged: (state, action: PayloadAction<{ fill: RgbColor }>) => {
|
||||
const { fill } = action.payload;
|
||||
state.inpaintMask.fill = fill;
|
||||
@ -38,25 +19,4 @@ export const inpaintMaskReducers = {
|
||||
const { imageDTO } = action.payload;
|
||||
state.inpaintMask.imageCache = imageDTO ? imageDTOToImageWithDims(imageDTO) : null;
|
||||
},
|
||||
imBrushLineAdded: (state, action: PayloadAction<{ brushLine: CanvasBrushLineState }>) => {
|
||||
const { brushLine } = action.payload;
|
||||
state.inpaintMask.objects.push(brushLine);
|
||||
state.layers.imageCache = null;
|
||||
},
|
||||
imEraserLineAdded: (state, action: PayloadAction<{ eraserLine: CanvasEraserLineState }>) => {
|
||||
const { eraserLine } = action.payload;
|
||||
state.inpaintMask.objects.push(eraserLine);
|
||||
state.layers.imageCache = null;
|
||||
},
|
||||
imRectAdded: (state, action: PayloadAction<{ rect: CanvasRectState }>) => {
|
||||
const { rect } = action.payload;
|
||||
state.inpaintMask.objects.push(rect);
|
||||
state.layers.imageCache = null;
|
||||
},
|
||||
inpaintMaskRasterized: (state, action: PayloadAction<EntityRasterizedArg>) => {
|
||||
const { imageObject, position } = action.payload;
|
||||
state.inpaintMask.objects = [imageObject];
|
||||
state.inpaintMask.position = position;
|
||||
state.inpaintMask.imageCache = null;
|
||||
},
|
||||
} satisfies SliceCaseReducers<CanvasV2State>;
|
||||
|
@ -1,21 +1,10 @@
|
||||
import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit';
|
||||
import { moveOneToEnd, moveOneToStart, moveToEnd, moveToStart } from 'common/util/arrayUtils';
|
||||
import { getPrefixedId } from 'features/controlLayers/konva/util';
|
||||
import type { IRect } from 'konva/lib/types';
|
||||
import { merge } from 'lodash-es';
|
||||
import type { ImageDTO } from 'services/api/types';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
import type {
|
||||
CanvasBrushLineState,
|
||||
CanvasEraserLineState,
|
||||
CanvasLayerState,
|
||||
CanvasRectState,
|
||||
CanvasV2State,
|
||||
EntityRasterizedArg,
|
||||
ImageObjectAddedArg,
|
||||
PositionChangedArg,
|
||||
} from './types';
|
||||
import type { CanvasLayerState, CanvasV2State, ImageObjectAddedArg } from './types';
|
||||
import { imageDTOToImageObject, imageDTOToImageWithDims } from './types';
|
||||
|
||||
export const selectLayer = (state: CanvasV2State, id: string) => state.layers.entities.find((layer) => layer.id === id);
|
||||
@ -52,52 +41,6 @@ export const layersReducers = {
|
||||
state.selectedEntityIdentifier = { type: 'layer', id: data.id };
|
||||
state.layers.imageCache = null;
|
||||
},
|
||||
layerIsEnabledToggled: (state, action: PayloadAction<{ id: string }>) => {
|
||||
const { id } = action.payload;
|
||||
const layer = selectLayer(state, id);
|
||||
if (!layer) {
|
||||
return;
|
||||
}
|
||||
layer.isEnabled = !layer.isEnabled;
|
||||
state.layers.imageCache = null;
|
||||
},
|
||||
layerTranslated: (state, action: PayloadAction<PositionChangedArg>) => {
|
||||
const { id, position } = action.payload;
|
||||
const layer = selectLayer(state, id);
|
||||
if (!layer) {
|
||||
return;
|
||||
}
|
||||
layer.position = position;
|
||||
state.layers.imageCache = null;
|
||||
},
|
||||
layerBboxChanged: (state, action: PayloadAction<{ id: string; bbox: IRect | null }>) => {
|
||||
const { id, bbox } = action.payload;
|
||||
const layer = selectLayer(state, id);
|
||||
if (!layer) {
|
||||
return;
|
||||
}
|
||||
if (bbox === null) {
|
||||
// TODO(psyche): Clear objects when bbox is cleared - right now this doesn't work bc bbox calculation for layers
|
||||
// doesn't work - always returns null
|
||||
// layer.objects = [];
|
||||
}
|
||||
},
|
||||
layerReset: (state, action: PayloadAction<{ id: string }>) => {
|
||||
const { id } = action.payload;
|
||||
const layer = selectLayer(state, id);
|
||||
if (!layer) {
|
||||
return;
|
||||
}
|
||||
layer.isEnabled = true;
|
||||
layer.objects = [];
|
||||
state.layers.imageCache = null;
|
||||
layer.position = { x: 0, y: 0 };
|
||||
},
|
||||
layerDeleted: (state, action: PayloadAction<{ id: string }>) => {
|
||||
const { id } = action.payload;
|
||||
state.layers.entities = state.layers.entities.filter((l) => l.id !== id);
|
||||
state.layers.imageCache = null;
|
||||
},
|
||||
layerAllDeleted: (state) => {
|
||||
state.layers.entities = [];
|
||||
state.layers.imageCache = null;
|
||||
@ -111,72 +54,6 @@ export const layersReducers = {
|
||||
layer.opacity = opacity;
|
||||
state.layers.imageCache = null;
|
||||
},
|
||||
layerMovedForwardOne: (state, action: PayloadAction<{ id: string }>) => {
|
||||
const { id } = action.payload;
|
||||
const layer = selectLayer(state, id);
|
||||
if (!layer) {
|
||||
return;
|
||||
}
|
||||
moveOneToEnd(state.layers.entities, layer);
|
||||
state.layers.imageCache = null;
|
||||
},
|
||||
layerMovedToFront: (state, action: PayloadAction<{ id: string }>) => {
|
||||
const { id } = action.payload;
|
||||
const layer = selectLayer(state, id);
|
||||
if (!layer) {
|
||||
return;
|
||||
}
|
||||
moveToEnd(state.layers.entities, layer);
|
||||
state.layers.imageCache = null;
|
||||
},
|
||||
layerMovedBackwardOne: (state, action: PayloadAction<{ id: string }>) => {
|
||||
const { id } = action.payload;
|
||||
const layer = selectLayer(state, id);
|
||||
if (!layer) {
|
||||
return;
|
||||
}
|
||||
moveOneToStart(state.layers.entities, layer);
|
||||
state.layers.imageCache = null;
|
||||
},
|
||||
layerMovedToBack: (state, action: PayloadAction<{ id: string }>) => {
|
||||
const { id } = action.payload;
|
||||
const layer = selectLayer(state, id);
|
||||
if (!layer) {
|
||||
return;
|
||||
}
|
||||
moveToStart(state.layers.entities, layer);
|
||||
state.layers.imageCache = null;
|
||||
},
|
||||
layerBrushLineAdded: (state, action: PayloadAction<{ id: string; brushLine: CanvasBrushLineState }>) => {
|
||||
const { id, brushLine } = action.payload;
|
||||
const layer = selectLayer(state, id);
|
||||
if (!layer) {
|
||||
return;
|
||||
}
|
||||
|
||||
layer.objects.push(brushLine);
|
||||
state.layers.imageCache = null;
|
||||
},
|
||||
layerEraserLineAdded: (state, action: PayloadAction<{ id: string; eraserLine: CanvasEraserLineState }>) => {
|
||||
const { id, eraserLine } = action.payload;
|
||||
const layer = selectLayer(state, id);
|
||||
if (!layer) {
|
||||
return;
|
||||
}
|
||||
|
||||
layer.objects.push(eraserLine);
|
||||
state.layers.imageCache = null;
|
||||
},
|
||||
layerRectAdded: (state, action: PayloadAction<{ id: string; rect: CanvasRectState }>) => {
|
||||
const { id, rect } = action.payload;
|
||||
const layer = selectLayer(state, id);
|
||||
if (!layer) {
|
||||
return;
|
||||
}
|
||||
|
||||
layer.objects.push(rect);
|
||||
state.layers.imageCache = null;
|
||||
},
|
||||
layerImageAdded: (
|
||||
state,
|
||||
action: PayloadAction<ImageObjectAddedArg & { objectId: string; pos?: { x: number; y: number } }>
|
||||
@ -198,14 +75,4 @@ export const layersReducers = {
|
||||
const { imageDTO } = action.payload;
|
||||
state.layers.imageCache = imageDTO ? imageDTOToImageWithDims(imageDTO) : null;
|
||||
},
|
||||
layerRasterized: (state, action: PayloadAction<EntityRasterizedArg>) => {
|
||||
const { id, imageObject, position } = action.payload;
|
||||
const layer = selectLayer(state, id);
|
||||
if (!layer) {
|
||||
return;
|
||||
}
|
||||
layer.objects = [imageObject];
|
||||
layer.position = position;
|
||||
state.layers.imageCache = null;
|
||||
},
|
||||
} satisfies SliceCaseReducers<CanvasV2State>;
|
||||
|
@ -1,16 +1,6 @@
|
||||
import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit';
|
||||
import { moveOneToEnd, moveOneToStart, moveToEnd, moveToStart } from 'common/util/arrayUtils';
|
||||
import { getPrefixedId } from 'features/controlLayers/konva/util';
|
||||
import type {
|
||||
CanvasBrushLineState,
|
||||
CanvasEraserLineState,
|
||||
CanvasRectState,
|
||||
CanvasV2State,
|
||||
CLIPVisionModelV2,
|
||||
EntityRasterizedArg,
|
||||
IPMethodV2,
|
||||
PositionChangedArg,
|
||||
} from 'features/controlLayers/store/types';
|
||||
import type { CanvasV2State, CLIPVisionModelV2, IPMethodV2 } from 'features/controlLayers/store/types';
|
||||
import { imageDTOToImageObject, imageDTOToImageWithDims } from 'features/controlLayers/store/types';
|
||||
import { zModelIdentifierField } from 'features/nodes/types/common';
|
||||
import type { ParameterAutoNegative } from 'features/parameters/types/parameterSchemas';
|
||||
@ -71,73 +61,14 @@ export const regionsReducers = {
|
||||
},
|
||||
prepare: () => ({ payload: { id: getPrefixedId('regional_guidance') } }),
|
||||
},
|
||||
rgReset: (state, action: PayloadAction<{ id: string }>) => {
|
||||
const { id } = action.payload;
|
||||
const rg = selectRG(state, id);
|
||||
if (!rg) {
|
||||
return;
|
||||
}
|
||||
rg.objects = [];
|
||||
rg.imageCache = null;
|
||||
},
|
||||
rgRecalled: (state, action: PayloadAction<{ data: CanvasRegionalGuidanceState }>) => {
|
||||
const { data } = action.payload;
|
||||
state.regions.entities.push(data);
|
||||
state.selectedEntityIdentifier = { type: 'regional_guidance', id: data.id };
|
||||
},
|
||||
rgIsEnabledToggled: (state, action: PayloadAction<{ id: string }>) => {
|
||||
const { id } = action.payload;
|
||||
const rg = selectRG(state, id);
|
||||
if (rg) {
|
||||
rg.isEnabled = !rg.isEnabled;
|
||||
}
|
||||
},
|
||||
rgMoved: (state, action: PayloadAction<PositionChangedArg>) => {
|
||||
const { id, position } = action.payload;
|
||||
const rg = selectRG(state, id);
|
||||
if (rg) {
|
||||
rg.position = position;
|
||||
}
|
||||
},
|
||||
rgDeleted: (state, action: PayloadAction<{ id: string }>) => {
|
||||
const { id } = action.payload;
|
||||
state.regions.entities = state.regions.entities.filter((ca) => ca.id !== id);
|
||||
},
|
||||
rgAllDeleted: (state) => {
|
||||
state.regions.entities = [];
|
||||
},
|
||||
rgMovedForwardOne: (state, action: PayloadAction<{ id: string }>) => {
|
||||
const { id } = action.payload;
|
||||
const rg = selectRG(state, id);
|
||||
if (!rg) {
|
||||
return;
|
||||
}
|
||||
moveOneToEnd(state.regions.entities, rg);
|
||||
},
|
||||
rgMovedToFront: (state, action: PayloadAction<{ id: string }>) => {
|
||||
const { id } = action.payload;
|
||||
const rg = selectRG(state, id);
|
||||
if (!rg) {
|
||||
return;
|
||||
}
|
||||
moveToEnd(state.regions.entities, rg);
|
||||
},
|
||||
rgMovedBackwardOne: (state, action: PayloadAction<{ id: string }>) => {
|
||||
const { id } = action.payload;
|
||||
const rg = selectRG(state, id);
|
||||
if (!rg) {
|
||||
return;
|
||||
}
|
||||
moveOneToStart(state.regions.entities, rg);
|
||||
},
|
||||
rgMovedToBack: (state, action: PayloadAction<{ id: string }>) => {
|
||||
const { id } = action.payload;
|
||||
const rg = selectRG(state, id);
|
||||
if (!rg) {
|
||||
return;
|
||||
}
|
||||
moveToStart(state.regions.entities, rg);
|
||||
},
|
||||
rgPositivePromptChanged: (state, action: PayloadAction<{ id: string; prompt: string | null }>) => {
|
||||
const { id, prompt } = action.payload;
|
||||
const rg = selectRG(state, id);
|
||||
@ -286,44 +217,4 @@ export const regionsReducers = {
|
||||
}
|
||||
ipa.clipVisionModel = clipVisionModel;
|
||||
},
|
||||
rgBrushLineAdded: (state, action: PayloadAction<{ id: string; brushLine: CanvasBrushLineState }>) => {
|
||||
const { id, brushLine } = action.payload;
|
||||
const rg = selectRG(state, id);
|
||||
if (!rg) {
|
||||
return;
|
||||
}
|
||||
|
||||
rg.objects.push(brushLine);
|
||||
state.layers.imageCache = null;
|
||||
},
|
||||
rgEraserLineAdded: (state, action: PayloadAction<{ id: string; eraserLine: CanvasEraserLineState }>) => {
|
||||
const { id, eraserLine } = action.payload;
|
||||
const rg = selectRG(state, id);
|
||||
if (!rg) {
|
||||
return;
|
||||
}
|
||||
|
||||
rg.objects.push(eraserLine);
|
||||
state.layers.imageCache = null;
|
||||
},
|
||||
rgRectAdded: (state, action: PayloadAction<{ id: string; rect: CanvasRectState }>) => {
|
||||
const { id, rect } = action.payload;
|
||||
const rg = selectRG(state, id);
|
||||
if (!rg) {
|
||||
return;
|
||||
}
|
||||
|
||||
rg.objects.push(rect);
|
||||
state.layers.imageCache = null;
|
||||
},
|
||||
rgRasterized: (state, action: PayloadAction<EntityRasterizedArg>) => {
|
||||
const { id, imageObject, position } = action.payload;
|
||||
const rg = selectRG(state, id);
|
||||
if (!rg) {
|
||||
return;
|
||||
}
|
||||
rg.objects = [imageObject];
|
||||
rg.position = position;
|
||||
rg.imageCache = null;
|
||||
},
|
||||
} satisfies SliceCaseReducers<CanvasV2State>;
|
||||
|
@ -924,11 +924,20 @@ export type PositionChangedArg = { id: string; position: Coordinate };
|
||||
export type ScaleChangedArg = { id: string; scale: Coordinate; position: Coordinate };
|
||||
export type BboxChangedArg = { id: string; bbox: Rect | null };
|
||||
|
||||
export type BrushLineAddedArg = { id: string; brushLine: CanvasBrushLineState };
|
||||
export type EraserLineAddedArg = { id: string; eraserLine: CanvasEraserLineState };
|
||||
export type RectAddedArg = { id: string; rect: CanvasRectState };
|
||||
export type EntityIdentifierPayload = { entityIdentifier: CanvasEntityIdentifier };
|
||||
export type EntityMovedPayload = { entityIdentifier: CanvasEntityIdentifier; position: Coordinate };
|
||||
export type EntityBrushLineAddedPayload = { entityIdentifier: CanvasEntityIdentifier; brushLine: CanvasBrushLineState };
|
||||
export type EntityEraserLineAddedPayload = {
|
||||
entityIdentifier: CanvasEntityIdentifier;
|
||||
eraserLine: CanvasEraserLineState;
|
||||
};
|
||||
export type EntityRectAddedPayload = { entityIdentifier: CanvasEntityIdentifier; rect: CanvasRectState };
|
||||
export type EntityRasterizedPayload = {
|
||||
entityIdentifier: CanvasEntityIdentifier;
|
||||
imageObject: CanvasImageState;
|
||||
position: Coordinate;
|
||||
};
|
||||
export type ImageObjectAddedArg = { id: string; imageDTO: ImageDTO; position?: Coordinate };
|
||||
export type EntityRasterizedArg = { id: string; imageObject: CanvasImageState; position: Coordinate };
|
||||
|
||||
//#region Type guards
|
||||
export const isLine = (obj: CanvasObjectState): obj is CanvasBrushLineState | CanvasEraserLineState => {
|
||||
|
Loading…
Reference in New Issue
Block a user