feat(ui): rough out undo/redo on canvas UNDO ME?

This commit is contained in:
psychedelicious 2024-08-26 22:45:11 +10:00
parent 217b2759c3
commit 35b483b83a
44 changed files with 84 additions and 81 deletions

View File

@ -58,7 +58,7 @@ export const addStagingListeners = (startAppListening: AppStartListening) => {
const stagingAreaImage = state.canvasSession.stagedImages[index]; const stagingAreaImage = state.canvasSession.stagedImages[index];
assert(stagingAreaImage, 'No staged image found to accept'); assert(stagingAreaImage, 'No staged image found to accept');
const { x, y } = state.canvasV2.bbox.rect; const { x, y } = state.canvasV2.present.bbox.rect;
const { imageDTO, offsetX, offsetY } = stagingAreaImage; const { imageDTO, offsetX, offsetY } = stagingAreaImage;
const imageObject = imageDTOToImageObject(imageDTO); const imageObject = imageDTOToImageObject(imageDTO);

View File

@ -16,7 +16,7 @@ export const addDeleteBoardAndImagesFulfilledListener = (startAppListening: AppS
const { nodes, canvasV2 } = getState(); const { nodes, canvasV2 } = getState();
deleted_images.forEach((image_name) => { deleted_images.forEach((image_name) => {
const imageUsage = getImageUsage(nodes.present, canvasV2, image_name); const imageUsage = getImageUsage(nodes.present, canvasV2.present, image_name);
if (imageUsage.isNodesImage && !wasNodeEditorReset) { if (imageUsage.isNodesImage && !wasNodeEditorReset) {
dispatch(nodeEditorReset()); dispatch(nodeEditorReset());

View File

@ -40,7 +40,7 @@ const deleteNodesImages = (state: RootState, dispatch: AppDispatch, imageDTO: Im
}; };
// const deleteControlAdapterImages = (state: RootState, dispatch: AppDispatch, imageDTO: ImageDTO) => { // const deleteControlAdapterImages = (state: RootState, dispatch: AppDispatch, imageDTO: ImageDTO) => {
// state.canvasV2.controlAdapters.entities.forEach(({ id, imageObject, processedImageObject }) => { // state.canvasV2.present.controlAdapters.entities.forEach(({ id, imageObject, processedImageObject }) => {
// if ( // if (
// imageObject?.image.image_name === imageDTO.image_name || // imageObject?.image.image_name === imageDTO.image_name ||
// processedImageObject?.image.image_name === imageDTO.image_name // processedImageObject?.image.image_name === imageDTO.image_name
@ -52,7 +52,7 @@ const deleteNodesImages = (state: RootState, dispatch: AppDispatch, imageDTO: Im
// }; // };
const deleteIPAdapterImages = (state: RootState, dispatch: AppDispatch, imageDTO: ImageDTO) => { const deleteIPAdapterImages = (state: RootState, dispatch: AppDispatch, imageDTO: ImageDTO) => {
state.canvasV2.ipAdapters.entities.forEach((entity) => { state.canvasV2.present.ipAdapters.entities.forEach((entity) => {
if (entity.ipAdapter.image?.image_name === imageDTO.image_name) { if (entity.ipAdapter.image?.image_name === imageDTO.image_name) {
dispatch(ipaImageChanged({ entityIdentifier: getEntityIdentifier(entity), imageDTO: null })); dispatch(ipaImageChanged({ entityIdentifier: getEntityIdentifier(entity), imageDTO: null }));
} }
@ -60,7 +60,7 @@ const deleteIPAdapterImages = (state: RootState, dispatch: AppDispatch, imageDTO
}; };
const deleteLayerImages = (state: RootState, dispatch: AppDispatch, imageDTO: ImageDTO) => { const deleteLayerImages = (state: RootState, dispatch: AppDispatch, imageDTO: ImageDTO) => {
state.canvasV2.rasterLayers.entities.forEach(({ id, objects }) => { state.canvasV2.present.rasterLayers.entities.forEach(({ id, objects }) => {
let shouldDelete = false; let shouldDelete = false;
for (const obj of objects) { for (const obj of objects) {
if (obj.type === 'image' && obj.image.image_name === imageDTO.image_name) { if (obj.type === 'image' && obj.image.image_name === imageDTO.image_name) {

View File

@ -85,7 +85,7 @@ export const addImageDroppedListener = (startAppListening: AppStartListening) =>
activeData.payload.imageDTO activeData.payload.imageDTO
) { ) {
const imageObject = imageDTOToImageObject(activeData.payload.imageDTO); const imageObject = imageDTOToImageObject(activeData.payload.imageDTO);
const { x, y } = getState().canvasV2.bbox.rect; const { x, y } = getState().canvasV2.present.bbox.rect;
const overrides: Partial<CanvasRasterLayerState> = { const overrides: Partial<CanvasRasterLayerState> = {
objects: [imageObject], objects: [imageObject],
position: { x, y }, position: { x, y },
@ -103,7 +103,7 @@ export const addImageDroppedListener = (startAppListening: AppStartListening) =>
activeData.payload.imageDTO activeData.payload.imageDTO
) { ) {
const imageObject = imageDTOToImageObject(activeData.payload.imageDTO); const imageObject = imageDTOToImageObject(activeData.payload.imageDTO);
const { x, y } = getState().canvasV2.bbox.rect; const { x, y } = getState().canvasV2.present.bbox.rect;
const overrides: Partial<CanvasControlLayerState> = { const overrides: Partial<CanvasControlLayerState> = {
objects: [imageObject], objects: [imageObject],
position: { x, y }, position: { x, y },

View File

@ -46,7 +46,7 @@ export const addModelSelectedListener = (startAppListening: AppStartListening) =
} }
// handle incompatible controlnets // handle incompatible controlnets
// state.canvasV2.controlAdapters.entities.forEach((ca) => { // state.canvasV2.present.controlAdapters.entities.forEach((ca) => {
// if (ca.model?.base !== newBaseModel) { // if (ca.model?.base !== newBaseModel) {
// modelsCleared += 1; // modelsCleared += 1;
// if (ca.isEnabled) { // if (ca.isEnabled) {

View File

@ -83,11 +83,11 @@ const handleMainModels: ModelHandler = (models, state, dispatch, log) => {
dispatch(modelChanged({ model: defaultModelInList, previousModel: currentModel })); dispatch(modelChanged({ model: defaultModelInList, previousModel: currentModel }));
const optimalDimension = getOptimalDimension(defaultModelInList); const optimalDimension = getOptimalDimension(defaultModelInList);
if (getIsSizeOptimal(state.canvasV2.bbox.rect.width, state.canvasV2.bbox.rect.height, optimalDimension)) { if (getIsSizeOptimal(state.canvasV2.present.bbox.rect.width, state.canvasV2.present.bbox.rect.height, optimalDimension)) {
return; return;
} }
const { width, height } = calculateNewSize( const { width, height } = calculateNewSize(
state.canvasV2.bbox.aspectRatio.value, state.canvasV2.present.bbox.aspectRatio.value,
optimalDimension * optimalDimension optimalDimension * optimalDimension
); );
@ -172,7 +172,7 @@ const handleLoRAModels: ModelHandler = (models, state, dispatch, _log) => {
const handleControlAdapterModels: ModelHandler = (models, state, dispatch, _log) => { const handleControlAdapterModels: ModelHandler = (models, state, dispatch, _log) => {
const caModels = models.filter(isControlNetOrT2IAdapterModelConfig); const caModels = models.filter(isControlNetOrT2IAdapterModelConfig);
state.canvasV2.controlLayers.entities.forEach((entity) => { state.canvasV2.present.controlLayers.entities.forEach((entity) => {
const isModelAvailable = caModels.some((m) => m.key === entity.controlAdapter.model?.key); const isModelAvailable = caModels.some((m) => m.key === entity.controlAdapter.model?.key);
if (isModelAvailable) { if (isModelAvailable) {
return; return;
@ -183,7 +183,7 @@ const handleControlAdapterModels: ModelHandler = (models, state, dispatch, _log)
const handleIPAdapterModels: ModelHandler = (models, state, dispatch, _log) => { const handleIPAdapterModels: ModelHandler = (models, state, dispatch, _log) => {
const ipaModels = models.filter(isIPAdapterModelConfig); const ipaModels = models.filter(isIPAdapterModelConfig);
state.canvasV2.ipAdapters.entities.forEach((entity) => { state.canvasV2.present.ipAdapters.entities.forEach((entity) => {
const isModelAvailable = ipaModels.some((m) => m.key === entity.ipAdapter.model?.key); const isModelAvailable = ipaModels.some((m) => m.key === entity.ipAdapter.model?.key);
if (isModelAvailable) { if (isModelAvailable) {
return; return;
@ -191,7 +191,7 @@ const handleIPAdapterModels: ModelHandler = (models, state, dispatch, _log) => {
dispatch(ipaModelChanged({ entityIdentifier: getEntityIdentifier(entity), modelConfig: null })); dispatch(ipaModelChanged({ entityIdentifier: getEntityIdentifier(entity), modelConfig: null }));
}); });
state.canvasV2.regions.entities.forEach((entity) => { state.canvasV2.present.regions.entities.forEach((entity) => {
entity.ipAdapters.forEach(({ id: ipAdapterId, model }) => { entity.ipAdapters.forEach(({ id: ipAdapterId, model }) => {
const isModelAvailable = ipaModels.some((m) => m.key === model?.key); const isModelAvailable = ipaModels.some((m) => m.key === model?.key);
if (isModelAvailable) { if (isModelAvailable) {

View File

@ -58,7 +58,7 @@ const allReducers = {
[queueSlice.name]: queueSlice.reducer, [queueSlice.name]: queueSlice.reducer,
[workflowSlice.name]: workflowSlice.reducer, [workflowSlice.name]: workflowSlice.reducer,
[hrfSlice.name]: hrfSlice.reducer, [hrfSlice.name]: hrfSlice.reducer,
[canvasV2Slice.name]: canvasV2Slice.reducer, [canvasV2Slice.name]: undoable(canvasV2Slice.reducer),
[workflowSettingsSlice.name]: workflowSettingsSlice.reducer, [workflowSettingsSlice.name]: workflowSettingsSlice.reducer,
[upscaleSlice.name]: upscaleSlice.reducer, [upscaleSlice.name]: upscaleSlice.reducer,
[stylePresetSlice.name]: stylePresetSlice.reducer, [stylePresetSlice.name]: stylePresetSlice.reducer,

View File

@ -9,7 +9,7 @@ export const ControlLayerBadges = memo(() => {
const entityIdentifier = useEntityIdentifierContext('control_layer'); const entityIdentifier = useEntityIdentifierContext('control_layer');
const { t } = useTranslation(); const { t } = useTranslation();
const withTransparencyEffect = useAppSelector( const withTransparencyEffect = useAppSelector(
(s) => selectEntityOrThrow(s.canvasV2, entityIdentifier).withTransparencyEffect (s) => selectEntityOrThrow(s.canvasV2.present, entityIdentifier).withTransparencyEffect
); );
return ( return (

View File

@ -11,7 +11,7 @@ const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) =
}); });
export const ControlLayerEntityList = memo(() => { export const ControlLayerEntityList = memo(() => {
const isSelected = useAppSelector((s) => Boolean(s.canvasV2.selectedEntityIdentifier?.type === 'control_layer')); const isSelected = useAppSelector((s) => Boolean(s.canvasV2.present.selectedEntityIdentifier?.type === 'control_layer'));
const layerIds = useAppSelector(selectEntityIds); const layerIds = useAppSelector(selectEntityIds);
if (layerIds.length === 0) { if (layerIds.length === 0) {

View File

@ -13,7 +13,7 @@ export const HeadsUpDisplay = memo(() => {
const isMouseDown = useStore(canvasManager.stateApi.$isMouseDown); const isMouseDown = useStore(canvasManager.stateApi.$isMouseDown);
const lastMouseDownPos = useStore(canvasManager.stateApi.$lastMouseDownPos); const lastMouseDownPos = useStore(canvasManager.stateApi.$lastMouseDownPos);
const lastAddedPoint = useStore(canvasManager.stateApi.$lastAddedPoint); const lastAddedPoint = useStore(canvasManager.stateApi.$lastAddedPoint);
const bbox = useAppSelector((s) => s.canvasV2.bbox); const bbox = useAppSelector((s) => s.canvasV2.present.bbox);
return ( return (
<Flex flexDir="column" bg="blackAlpha.400" borderBottomEndRadius="base" p={2} minW={64} gap={2}> <Flex flexDir="column" bg="blackAlpha.400" borderBottomEndRadius="base" p={2} minW={64} gap={2}>

View File

@ -12,7 +12,7 @@ const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) =
}); });
export const IPAdapterList = memo(() => { export const IPAdapterList = memo(() => {
const isSelected = useAppSelector((s) => Boolean(s.canvasV2.selectedEntityIdentifier?.type === 'ip_adapter')); const isSelected = useAppSelector((s) => Boolean(s.canvasV2.present.selectedEntityIdentifier?.type === 'ip_adapter'));
const ipaIds = useAppSelector(selectEntityIds); const ipaIds = useAppSelector(selectEntityIds);
if (ipaIds.length === 0) { if (ipaIds.length === 0) {

View File

@ -25,7 +25,7 @@ import { IPAdapterModel } from './IPAdapterModel';
export const IPAdapterSettings = memo(() => { export const IPAdapterSettings = memo(() => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const entityIdentifier = useEntityIdentifierContext('ip_adapter'); const entityIdentifier = useEntityIdentifierContext('ip_adapter');
const ipAdapter = useAppSelector((s) => selectEntityOrThrow(s.canvasV2, entityIdentifier).ipAdapter); const ipAdapter = useAppSelector((s) => selectEntityOrThrow(s.canvasV2.present, entityIdentifier).ipAdapter);
const onChangeBeginEndStepPct = useCallback( const onChangeBeginEndStepPct = useCallback(
(beginEndStepPct: [number, number]) => { (beginEndStepPct: [number, number]) => {

View File

@ -11,7 +11,7 @@ const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) =
}); });
export const InpaintMaskList = memo(() => { export const InpaintMaskList = memo(() => {
const isSelected = useAppSelector((s) => Boolean(s.canvasV2.selectedEntityIdentifier?.type === 'inpaint_mask')); const isSelected = useAppSelector((s) => Boolean(s.canvasV2.present.selectedEntityIdentifier?.type === 'inpaint_mask'));
const entityIds = useAppSelector(selectEntityIds); const entityIds = useAppSelector(selectEntityIds);
if (entityIds.length === 0) { if (entityIds.length === 0) {

View File

@ -14,7 +14,7 @@ export const InpaintMaskMaskFillColorPicker = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const entityIdentifier = useEntityIdentifierContext('inpaint_mask'); const entityIdentifier = useEntityIdentifierContext('inpaint_mask');
const fill = useAppSelector((s) => selectEntityOrThrow(s.canvasV2, entityIdentifier).fill); const fill = useAppSelector((s) => selectEntityOrThrow(s.canvasV2.present, entityIdentifier).fill);
const onChangeFillColor = useCallback( const onChangeFillColor = useCallback(
(color: RgbColor) => { (color: RgbColor) => {

View File

@ -11,7 +11,7 @@ const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) =
}); });
export const RasterLayerEntityList = memo(() => { export const RasterLayerEntityList = memo(() => {
const isSelected = useAppSelector((s) => Boolean(s.canvasV2.selectedEntityIdentifier?.type === 'raster_layer')); const isSelected = useAppSelector((s) => Boolean(s.canvasV2.present.selectedEntityIdentifier?.type === 'raster_layer'));
const layerIds = useAppSelector(selectEntityIds); const layerIds = useAppSelector(selectEntityIds);
if (layerIds.length === 0) { if (layerIds.length === 0) {

View File

@ -8,7 +8,7 @@ import { useTranslation } from 'react-i18next';
export const RegionalGuidanceBadges = memo(() => { export const RegionalGuidanceBadges = memo(() => {
const entityIdentifier = useEntityIdentifierContext('regional_guidance'); const entityIdentifier = useEntityIdentifierContext('regional_guidance');
const { t } = useTranslation(); const { t } = useTranslation();
const autoNegative = useAppSelector((s) => selectEntityOrThrow(s.canvasV2, entityIdentifier).autoNegative); const autoNegative = useAppSelector((s) => selectEntityOrThrow(s.canvasV2.present, entityIdentifier).autoNegative);
return ( return (
<> <>

View File

@ -11,7 +11,7 @@ const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) =
}); });
export const RegionalGuidanceEntityList = memo(() => { export const RegionalGuidanceEntityList = memo(() => {
const isSelected = useAppSelector((s) => Boolean(s.canvasV2.selectedEntityIdentifier?.type === 'regional_guidance')); const isSelected = useAppSelector((s) => Boolean(s.canvasV2.present.selectedEntityIdentifier?.type === 'regional_guidance'));
const rgIds = useAppSelector(selectEntityIds); const rgIds = useAppSelector(selectEntityIds);
if (rgIds.length === 0) { if (rgIds.length === 0) {

View File

@ -35,7 +35,7 @@ export const RegionalGuidanceIPAdapterSettings = memo(({ ipAdapterId, ipAdapterN
dispatch(rgIPAdapterDeleted({ entityIdentifier, ipAdapterId })); dispatch(rgIPAdapterDeleted({ entityIdentifier, ipAdapterId }));
}, [dispatch, entityIdentifier, ipAdapterId]); }, [dispatch, entityIdentifier, ipAdapterId]);
const ipAdapter = useAppSelector((s) => { const ipAdapter = useAppSelector((s) => {
const ipa = selectRegionalGuidanceIPAdapter(s.canvasV2, entityIdentifier, ipAdapterId); const ipa = selectRegionalGuidanceIPAdapter(s.canvasV2.present, entityIdentifier, ipAdapterId);
assert(ipa, `Regional GuidanceIP Adapter with id ${ipAdapterId} not found`); assert(ipa, `Regional GuidanceIP Adapter with id ${ipAdapterId} not found`);
return ipa; return ipa;
}); });

View File

@ -14,7 +14,7 @@ export const RegionalGuidanceMaskFillColorPicker = memo(() => {
const entityIdentifier = useEntityIdentifierContext('regional_guidance'); const entityIdentifier = useEntityIdentifierContext('regional_guidance');
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const fill = useAppSelector((s) => selectEntityOrThrow(s.canvasV2, entityIdentifier).fill); const fill = useAppSelector((s) => selectEntityOrThrow(s.canvasV2.present, entityIdentifier).fill);
const onChangeFillColor = useCallback( const onChangeFillColor = useCallback(
(color: RgbColor) => { (color: RgbColor) => {
dispatch(rgFillColorChanged({ entityIdentifier, color })); dispatch(rgFillColorChanged({ entityIdentifier, color }));

View File

@ -11,7 +11,7 @@ export const RegionalGuidanceMenuItemsAutoNegative = memo(() => {
const entityIdentifier = useEntityIdentifierContext('regional_guidance'); const entityIdentifier = useEntityIdentifierContext('regional_guidance');
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const autoNegative = useAppSelector((s) => selectEntityOrThrow(s.canvasV2, entityIdentifier).autoNegative); const autoNegative = useAppSelector((s) => selectEntityOrThrow(s.canvasV2.present, entityIdentifier).autoNegative);
const onClick = useCallback(() => { const onClick = useCallback(() => {
dispatch(rgAutoNegativeToggled({ entityIdentifier })); dispatch(rgAutoNegativeToggled({ entityIdentifier }));
}, [dispatch, entityIdentifier]); }, [dispatch, entityIdentifier]);

View File

@ -13,7 +13,7 @@ import { useTranslation } from 'react-i18next';
export const RegionalGuidanceNegativePrompt = memo(() => { export const RegionalGuidanceNegativePrompt = memo(() => {
const entityIdentifier = useEntityIdentifierContext('regional_guidance'); const entityIdentifier = useEntityIdentifierContext('regional_guidance');
const prompt = useAppSelector((s) => selectEntityOrThrow(s.canvasV2, entityIdentifier).negativePrompt ?? ''); const prompt = useAppSelector((s) => selectEntityOrThrow(s.canvasV2.present, entityIdentifier).negativePrompt ?? '');
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const textareaRef = useRef<HTMLTextAreaElement>(null); const textareaRef = useRef<HTMLTextAreaElement>(null);
const { t } = useTranslation(); const { t } = useTranslation();

View File

@ -13,7 +13,7 @@ import { useTranslation } from 'react-i18next';
export const RegionalGuidancePositivePrompt = memo(() => { export const RegionalGuidancePositivePrompt = memo(() => {
const entityIdentifier = useEntityIdentifierContext('regional_guidance'); const entityIdentifier = useEntityIdentifierContext('regional_guidance');
const prompt = useAppSelector((s) => selectEntityOrThrow(s.canvasV2, entityIdentifier).positivePrompt ?? ''); const prompt = useAppSelector((s) => selectEntityOrThrow(s.canvasV2.present, entityIdentifier).positivePrompt ?? '');
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const textareaRef = useRef<HTMLTextAreaElement>(null); const textareaRef = useRef<HTMLTextAreaElement>(null);
const { t } = useTranslation(); const { t } = useTranslation();

View File

@ -13,12 +13,12 @@ import { RegionalGuidancePositivePrompt } from './RegionalGuidancePositivePrompt
export const RegionalGuidanceSettings = memo(() => { export const RegionalGuidanceSettings = memo(() => {
const entityIdentifier = useEntityIdentifierContext('regional_guidance'); const entityIdentifier = useEntityIdentifierContext('regional_guidance');
const hasPositivePrompt = useAppSelector( const hasPositivePrompt = useAppSelector(
(s) => selectEntityOrThrow(s.canvasV2, entityIdentifier).positivePrompt !== null (s) => selectEntityOrThrow(s.canvasV2.present, entityIdentifier).positivePrompt !== null
); );
const hasNegativePrompt = useAppSelector( const hasNegativePrompt = useAppSelector(
(s) => selectEntityOrThrow(s.canvasV2, entityIdentifier).negativePrompt !== null (s) => selectEntityOrThrow(s.canvasV2.present, entityIdentifier).negativePrompt !== null
); );
const hasIPAdapters = useAppSelector((s) => selectEntityOrThrow(s.canvasV2, entityIdentifier).ipAdapters.length > 0); const hasIPAdapters = useAppSelector((s) => selectEntityOrThrow(s.canvasV2.present, entityIdentifier).ipAdapters.length > 0);
return ( return (
<CanvasEntitySettingsWrapper> <CanvasEntitySettingsWrapper>

View File

@ -17,10 +17,10 @@ export const ToolBrushButton = memo(() => {
const selectBrush = useSelectTool('brush'); const selectBrush = useSelectTool('brush');
const isSelected = useToolIsSelected('brush'); const isSelected = useToolIsSelected('brush');
const isDrawingToolAllowed = useAppSelector((s) => { const isDrawingToolAllowed = useAppSelector((s) => {
if (!s.canvasV2.selectedEntityIdentifier?.type) { if (!s.canvasV2.present.selectedEntityIdentifier?.type) {
return false; return false;
} }
return isDrawableEntityType(s.canvasV2.selectedEntityIdentifier.type); return isDrawableEntityType(s.canvasV2.present.selectedEntityIdentifier.type);
}); });
const isDisabled = useMemo(() => { const isDisabled = useMemo(() => {

View File

@ -17,10 +17,10 @@ export const ToolEraserButton = memo(() => {
const selectEraser = useSelectTool('eraser'); const selectEraser = useSelectTool('eraser');
const isSelected = useToolIsSelected('eraser'); const isSelected = useToolIsSelected('eraser');
const isDrawingToolAllowed = useAppSelector((s) => { const isDrawingToolAllowed = useAppSelector((s) => {
if (!s.canvasV2.selectedEntityIdentifier?.type) { if (!s.canvasV2.present.selectedEntityIdentifier?.type) {
return false; return false;
} }
return isDrawableEntityType(s.canvasV2.selectedEntityIdentifier.type); return isDrawableEntityType(s.canvasV2.present.selectedEntityIdentifier.type);
}); });
const isDisabled = useMemo(() => { const isDisabled = useMemo(() => {
return isTransforming || isFiltering || isStaging || !isDrawingToolAllowed; return isTransforming || isFiltering || isStaging || !isDrawingToolAllowed;

View File

@ -17,10 +17,10 @@ export const ToolMoveButton = memo(() => {
const isSelected = useToolIsSelected('move'); const isSelected = useToolIsSelected('move');
const isStaging = useAppSelector((s) => s.canvasSession.isStaging); const isStaging = useAppSelector((s) => s.canvasSession.isStaging);
const isDrawingToolAllowed = useAppSelector((s) => { const isDrawingToolAllowed = useAppSelector((s) => {
if (!s.canvasV2.selectedEntityIdentifier?.type) { if (!s.canvasV2.present.selectedEntityIdentifier?.type) {
return false; return false;
} }
return isDrawableEntityType(s.canvasV2.selectedEntityIdentifier.type); return isDrawableEntityType(s.canvasV2.present.selectedEntityIdentifier.type);
}); });
const isDisabled = useMemo(() => { const isDisabled = useMemo(() => {
return isTransforming || isFiltering || isStaging || !isDrawingToolAllowed; return isTransforming || isFiltering || isStaging || !isDrawingToolAllowed;

View File

@ -17,10 +17,10 @@ export const ToolRectButton = memo(() => {
const isTransforming = useIsTransforming(); const isTransforming = useIsTransforming();
const isStaging = useAppSelector((s) => s.canvasSession.isStaging); const isStaging = useAppSelector((s) => s.canvasSession.isStaging);
const isDrawingToolAllowed = useAppSelector((s) => { const isDrawingToolAllowed = useAppSelector((s) => {
if (!s.canvasV2.selectedEntityIdentifier?.type) { if (!s.canvasV2.present.selectedEntityIdentifier?.type) {
return false; return false;
} }
return isDrawableEntityType(s.canvasV2.selectedEntityIdentifier.type); return isDrawableEntityType(s.canvasV2.present.selectedEntityIdentifier.type);
}); });
const isDisabled = useMemo(() => { const isDisabled = useMemo(() => {

View File

@ -5,22 +5,25 @@ import { memo, useCallback } from 'react';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { PiArrowClockwiseBold, PiArrowCounterClockwiseBold } from 'react-icons/pi'; import { PiArrowClockwiseBold, PiArrowCounterClockwiseBold } from 'react-icons/pi';
import { useDispatch } from 'react-redux';
import { ActionCreators } from 'redux-undo';
export const UndoRedoButtonGroup = memo(() => { export const UndoRedoButtonGroup = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useDispatch();
const mayUndo = useAppSelector(() => false); const mayUndo = useAppSelector(() => true);
const handleUndo = useCallback(() => { const handleUndo = useCallback(() => {
// TODO(psyche): Implement undo // TODO(psyche): Implement undo
// dispatch(undo()); dispatch(ActionCreators.undo());
}, []); }, [dispatch]);
useHotkeys(['meta+z', 'ctrl+z'], handleUndo, { enabled: mayUndo, preventDefault: true }, [mayUndo, handleUndo]); useHotkeys(['meta+z', 'ctrl+z'], handleUndo, { enabled: mayUndo, preventDefault: true }, [mayUndo, handleUndo]);
const mayRedo = useAppSelector(() => false); const mayRedo = useAppSelector(() => true);
const handleRedo = useCallback(() => { const handleRedo = useCallback(() => {
// TODO(psyche): Implement redo // TODO(psyche): Implement redo
// dispatch(redo()); dispatch(ActionCreators.redo());
}, []); }, [dispatch]);
useHotkeys(['meta+shift+z', 'ctrl+shift+z'], handleRedo, { enabled: mayRedo, preventDefault: true }, [ useHotkeys(['meta+shift+z', 'ctrl+shift+z'], handleRedo, { enabled: mayRedo, preventDefault: true }, [
mayRedo, mayRedo,
handleRedo, handleRedo,

View File

@ -59,13 +59,13 @@ const snapCandidates = marks.slice(1, marks.length - 1);
export const CanvasEntityOpacity = memo(() => { export const CanvasEntityOpacity = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const selectedEntityIdentifier = useAppSelector((s) => s.canvasV2.selectedEntityIdentifier); const selectedEntityIdentifier = useAppSelector((s) => s.canvasV2.present.selectedEntityIdentifier);
const opacity = useAppSelector((s) => { const opacity = useAppSelector((s) => {
const selectedEntityIdentifier = s.canvasV2.selectedEntityIdentifier; const selectedEntityIdentifier = s.canvasV2.present.selectedEntityIdentifier;
if (!selectedEntityIdentifier) { if (!selectedEntityIdentifier) {
return null; return null;
} }
const selectedEntity = selectEntity(s.canvasV2, selectedEntityIdentifier); const selectedEntity = selectEntity(s.canvasV2.present, selectedEntityIdentifier);
if (!selectedEntity) { if (!selectedEntity) {
return null; return null;
} }

View File

@ -3,7 +3,7 @@ import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types'
import { useMemo } from 'react'; import { useMemo } from 'react';
export const useEntityIsSelected = (entityIdentifier: CanvasEntityIdentifier) => { export const useEntityIsSelected = (entityIdentifier: CanvasEntityIdentifier) => {
const selectedEntityIdentifier = useAppSelector((s) => s.canvasV2.selectedEntityIdentifier); const selectedEntityIdentifier = useAppSelector((s) => s.canvasV2.present.selectedEntityIdentifier);
const isSelected = useMemo(() => { const isSelected = useMemo(() => {
return selectedEntityIdentifier?.id === entityIdentifier.id; return selectedEntityIdentifier?.id === entityIdentifier.id;
}, [selectedEntityIdentifier, entityIdentifier.id]); }, [selectedEntityIdentifier, entityIdentifier.id]);

View File

@ -100,7 +100,7 @@ export class CanvasStateApiModule extends CanvasModuleBase {
// Reminder - use arrow functions to avoid binding issues // Reminder - use arrow functions to avoid binding issues
getCanvasState = () => { getCanvasState = () => {
return this.store.getState().canvasV2; return this.store.getState().canvasV2.present;
}; };
resetEntity = (arg: EntityIdentifierPayload) => { resetEntity = (arg: EntityIdentifierPayload) => {
this.store.dispatch(entityReset(arg)); this.store.dispatch(entityReset(arg));

View File

@ -16,7 +16,7 @@ import { assert } from 'tsafe';
/** /**
* Selects the canvasV2 slice from the root state * Selects the canvasV2 slice from the root state
*/ */
export const selectCanvasV2Slice = (state: RootState) => state.canvasV2; export const selectCanvasV2Slice = (state: RootState) => state.canvasV2.present;
/** /**
* Selects the total canvas entity count: * Selects the total canvas entity count:

View File

@ -22,7 +22,7 @@ export const addInpaint = async (
denoise.denoising_start = denoising_start; denoise.denoising_start = denoising_start;
const { params, canvasV2, canvasSession } = state; const { params, canvasV2, canvasSession } = state;
const { bbox } = canvasV2; const { bbox } = canvasV2.present;
const { mode } = canvasSession; const { mode } = canvasSession;
const initialImage = await manager.compositor.getCompositeRasterLayerImageDTO(bbox.rect); const initialImage = await manager.compositor.getCompositeRasterLayerImageDTO(bbox.rect);

View File

@ -23,7 +23,7 @@ export const addOutpaint = async (
denoise.denoising_start = denoising_start; denoise.denoising_start = denoising_start;
const { params, canvasV2, canvasSession } = state; const { params, canvasV2, canvasSession } = state;
const { bbox } = canvasV2; const { bbox } = canvasV2.present;
const { mode } = canvasSession; const { mode } = canvasSession;
const initialImage = await manager.compositor.getCompositeRasterLayerImageDTO(bbox.rect); const initialImage = await manager.compositor.getCompositeRasterLayerImageDTO(bbox.rect);

View File

@ -32,7 +32,7 @@ export const buildSD1Graph = async (
log.debug({ generationMode }, 'Building SD1/SD2 graph'); log.debug({ generationMode }, 'Building SD1/SD2 graph');
const { canvasV2, params, canvasSettings, canvasSession } = state; const { canvasV2, params, canvasSettings, canvasSession } = state;
const { bbox } = canvasV2; const { bbox } = canvasV2.present;
const { const {
model, model,
@ -208,9 +208,9 @@ export const buildSD1Graph = async (
}); });
const controlNetResult = await addControlNets( const controlNetResult = await addControlNets(
manager, manager,
state.canvasV2.controlLayers.entities, state.canvasV2.present.controlLayers.entities,
g, g,
state.canvasV2.bbox.rect, state.canvasV2.present.bbox.rect,
controlNetCollector, controlNetCollector,
modelConfig.base modelConfig.base
); );
@ -226,9 +226,9 @@ export const buildSD1Graph = async (
}); });
const t2iAdapterResult = await addT2IAdapters( const t2iAdapterResult = await addT2IAdapters(
manager, manager,
state.canvasV2.controlLayers.entities, state.canvasV2.present.controlLayers.entities,
g, g,
state.canvasV2.bbox.rect, state.canvasV2.present.bbox.rect,
t2iAdapterCollector, t2iAdapterCollector,
modelConfig.base modelConfig.base
); );
@ -242,13 +242,13 @@ export const buildSD1Graph = async (
type: 'collect', type: 'collect',
id: getPrefixedId('ip_adapter_collector'), id: getPrefixedId('ip_adapter_collector'),
}); });
const ipAdapterResult = addIPAdapters(state.canvasV2.ipAdapters.entities, g, ipAdapterCollector, modelConfig.base); const ipAdapterResult = addIPAdapters(state.canvasV2.present.ipAdapters.entities, g, ipAdapterCollector, modelConfig.base);
const regionsResult = await addRegions( const regionsResult = await addRegions(
manager, manager,
state.canvasV2.regions.entities, state.canvasV2.present.regions.entities,
g, g,
state.canvasV2.bbox.rect, state.canvasV2.present.bbox.rect,
modelConfig.base, modelConfig.base,
denoise, denoise,
posCond, posCond,

View File

@ -32,7 +32,7 @@ export const buildSDXLGraph = async (
log.debug({ generationMode }, 'Building SDXL graph'); log.debug({ generationMode }, 'Building SDXL graph');
const { params, canvasV2, canvasSettings, canvasSession } = state; const { params, canvasV2, canvasSettings, canvasSession } = state;
const { bbox } = canvasV2; const { bbox } = canvasV2.present;
const { const {
model, model,
@ -211,9 +211,9 @@ export const buildSDXLGraph = async (
}); });
const controlNetResult = await addControlNets( const controlNetResult = await addControlNets(
manager, manager,
state.canvasV2.controlLayers.entities, state.canvasV2.present.controlLayers.entities,
g, g,
state.canvasV2.bbox.rect, state.canvasV2.present.bbox.rect,
controlNetCollector, controlNetCollector,
modelConfig.base modelConfig.base
); );
@ -229,9 +229,9 @@ export const buildSDXLGraph = async (
}); });
const t2iAdapterResult = await addT2IAdapters( const t2iAdapterResult = await addT2IAdapters(
manager, manager,
state.canvasV2.controlLayers.entities, state.canvasV2.present.controlLayers.entities,
g, g,
state.canvasV2.bbox.rect, state.canvasV2.present.bbox.rect,
t2iAdapterCollector, t2iAdapterCollector,
modelConfig.base modelConfig.base
); );
@ -245,13 +245,13 @@ export const buildSDXLGraph = async (
type: 'collect', type: 'collect',
id: getPrefixedId('ip_adapter_collector'), id: getPrefixedId('ip_adapter_collector'),
}); });
const ipAdapterResult = addIPAdapters(state.canvasV2.ipAdapters.entities, g, ipAdapterCollector, modelConfig.base); const ipAdapterResult = addIPAdapters(state.canvasV2.present.ipAdapters.entities, g, ipAdapterCollector, modelConfig.base);
const regionsResult = await addRegions( const regionsResult = await addRegions(
manager, manager,
state.canvasV2.regions.entities, state.canvasV2.present.regions.entities,
g, g,
state.canvasV2.bbox.rect, state.canvasV2.present.bbox.rect,
modelConfig.base, modelConfig.base,
denoise, denoise,
posCond, posCond,

View File

@ -10,7 +10,7 @@ import { useTranslation } from 'react-i18next';
const ParamScaleBeforeProcessing = () => { const ParamScaleBeforeProcessing = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { t } = useTranslation(); const { t } = useTranslation();
const scaleMethod = useAppSelector((s) => s.canvasV2.bbox.scaleMethod); const scaleMethod = useAppSelector((s) => s.canvasV2.present.bbox.scaleMethod);
const OPTIONS: ComboboxOption[] = useMemo( const OPTIONS: ComboboxOption[] = useMemo(
() => [ () => [

View File

@ -9,8 +9,8 @@ const ParamScaledHeight = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const optimalDimension = useAppSelector(selectOptimalDimension); const optimalDimension = useAppSelector(selectOptimalDimension);
const isManual = useAppSelector((s) => s.canvasV2.bbox.scaleMethod === 'manual'); const isManual = useAppSelector((s) => s.canvasV2.present.bbox.scaleMethod === 'manual');
const height = useAppSelector((s) => s.canvasV2.bbox.scaledSize.height); const height = useAppSelector((s) => s.canvasV2.present.bbox.scaledSize.height);
const sliderMin = useAppSelector((s) => s.config.sd.scaledBoundingBoxHeight.sliderMin); const sliderMin = useAppSelector((s) => s.config.sd.scaledBoundingBoxHeight.sliderMin);
const sliderMax = useAppSelector((s) => s.config.sd.scaledBoundingBoxHeight.sliderMax); const sliderMax = useAppSelector((s) => s.config.sd.scaledBoundingBoxHeight.sliderMax);
const numberInputMin = useAppSelector((s) => s.config.sd.scaledBoundingBoxHeight.numberInputMin); const numberInputMin = useAppSelector((s) => s.config.sd.scaledBoundingBoxHeight.numberInputMin);

View File

@ -9,8 +9,8 @@ const ParamScaledWidth = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const optimalDimension = useAppSelector(selectOptimalDimension); const optimalDimension = useAppSelector(selectOptimalDimension);
const isManual = useAppSelector((s) => s.canvasV2.bbox.scaleMethod === 'manual'); const isManual = useAppSelector((s) => s.canvasV2.present.bbox.scaleMethod === 'manual');
const width = useAppSelector((s) => s.canvasV2.bbox.scaledSize.width); const width = useAppSelector((s) => s.canvasV2.present.bbox.scaledSize.width);
const sliderMin = useAppSelector((s) => s.config.sd.scaledBoundingBoxWidth.sliderMin); const sliderMin = useAppSelector((s) => s.config.sd.scaledBoundingBoxWidth.sliderMin);
const sliderMax = useAppSelector((s) => s.config.sd.scaledBoundingBoxWidth.sliderMax); const sliderMax = useAppSelector((s) => s.config.sd.scaledBoundingBoxWidth.sliderMax);
const numberInputMin = useAppSelector((s) => s.config.sd.scaledBoundingBoxWidth.numberInputMin); const numberInputMin = useAppSelector((s) => s.config.sd.scaledBoundingBoxWidth.numberInputMin);

View File

@ -10,7 +10,7 @@ export const ParamHeight = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const optimalDimension = useAppSelector(selectOptimalDimension); const optimalDimension = useAppSelector(selectOptimalDimension);
const height = useAppSelector((s) => s.canvasV2.bbox.rect.height); const height = useAppSelector((s) => s.canvasV2.present.bbox.rect.height);
const sliderMin = useAppSelector((s) => s.config.sd.height.sliderMin); const sliderMin = useAppSelector((s) => s.config.sd.height.sliderMin);
const sliderMax = useAppSelector((s) => s.config.sd.height.sliderMax); const sliderMax = useAppSelector((s) => s.config.sd.height.sliderMax);
const numberInputMin = useAppSelector((s) => s.config.sd.height.numberInputMin); const numberInputMin = useAppSelector((s) => s.config.sd.height.numberInputMin);

View File

@ -9,7 +9,7 @@ import { useTranslation } from 'react-i18next';
export const ParamWidth = memo(() => { export const ParamWidth = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const width = useAppSelector((s) => s.canvasV2.bbox.rect.width); const width = useAppSelector((s) => s.canvasV2.present.bbox.rect.width);
const optimalDimension = useAppSelector(selectOptimalDimension); const optimalDimension = useAppSelector(selectOptimalDimension);
const sliderMin = useAppSelector((s) => s.config.sd.width.sliderMin); const sliderMin = useAppSelector((s) => s.config.sd.width.sliderMin);
const sliderMax = useAppSelector((s) => s.config.sd.width.sliderMax); const sliderMax = useAppSelector((s) => s.config.sd.width.sliderMax);

View File

@ -12,7 +12,7 @@ import { useTranslation } from 'react-i18next';
export const AspectRatioSelect = memo(() => { export const AspectRatioSelect = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const id = useAppSelector((s) => s.canvasV2.bbox.aspectRatio.id); const id = useAppSelector((s) => s.canvasV2.present.bbox.aspectRatio.id);
const onChange = useCallback( const onChange = useCallback(
(v: SingleValue<ComboboxOption>) => { (v: SingleValue<ComboboxOption>) => {

View File

@ -8,7 +8,7 @@ import { PiLockSimpleFill, PiLockSimpleOpenBold } from 'react-icons/pi';
export const LockAspectRatioButton = memo(() => { export const LockAspectRatioButton = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const isLocked = useAppSelector((s) => s.canvasV2.bbox.aspectRatio.isLocked); const isLocked = useAppSelector((s) => s.canvasV2.present.bbox.aspectRatio.isLocked);
const onClick = useCallback(() => { const onClick = useCallback(() => {
dispatch(bboxAspectRatioLockToggled()); dispatch(bboxAspectRatioLockToggled());
}, [dispatch]); }, [dispatch]);

View File

@ -10,8 +10,8 @@ import { RiSparklingFill } from 'react-icons/ri';
export const SetOptimalSizeButton = memo(() => { export const SetOptimalSizeButton = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const width = useAppSelector((s) => s.canvasV2.bbox.rect.width); const width = useAppSelector((s) => s.canvasV2.present.bbox.rect.width);
const height = useAppSelector((s) => s.canvasV2.bbox.rect.height); const height = useAppSelector((s) => s.canvasV2.present.bbox.rect.height);
const optimalDimension = useAppSelector(selectOptimalDimension); const optimalDimension = useAppSelector(selectOptimalDimension);
const isSizeTooSmall = useMemo( const isSizeTooSmall = useMemo(
() => getIsSizeTooSmall(width, height, optimalDimension), () => getIsSizeTooSmall(width, height, optimalDimension),