Skip to content

Commit

Permalink
feat(ui): rough out undo/redo on canvas UNDO ME?
Browse files Browse the repository at this point in the history
  • Loading branch information
psychedelicious committed Aug 26, 2024
1 parent 217b275 commit 35b483b
Show file tree
Hide file tree
Showing 44 changed files with 84 additions and 81 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export const addStagingListeners = (startAppListening: AppStartListening) => {
const stagingAreaImage = state.canvasSession.stagedImages[index];

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 imageObject = imageDTOToImageObject(imageDTO);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const addDeleteBoardAndImagesFulfilledListener = (startAppListening: AppS
const { nodes, canvasV2 } = getState();

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) {
dispatch(nodeEditorReset());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const deleteNodesImages = (state: RootState, dispatch: AppDispatch, imageDTO: Im
};

// 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 (
// imageObject?.image.image_name === imageDTO.image_name ||
// processedImageObject?.image.image_name === imageDTO.image_name
Expand All @@ -52,15 +52,15 @@ const deleteNodesImages = (state: RootState, dispatch: AppDispatch, imageDTO: Im
// };

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) {
dispatch(ipaImageChanged({ entityIdentifier: getEntityIdentifier(entity), imageDTO: null }));
}
});
};

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;
for (const obj of objects) {
if (obj.type === 'image' && obj.image.image_name === imageDTO.image_name) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export const addImageDroppedListener = (startAppListening: AppStartListening) =>
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> = {
objects: [imageObject],
position: { x, y },
Expand All @@ -103,7 +103,7 @@ export const addImageDroppedListener = (startAppListening: AppStartListening) =>
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> = {
objects: [imageObject],
position: { x, y },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export const addModelSelectedListener = (startAppListening: AppStartListening) =
}

// handle incompatible controlnets
// state.canvasV2.controlAdapters.entities.forEach((ca) => {
// state.canvasV2.present.controlAdapters.entities.forEach((ca) => {
// if (ca.model?.base !== newBaseModel) {
// modelsCleared += 1;
// if (ca.isEnabled) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,11 @@ const handleMainModels: ModelHandler = (models, state, dispatch, log) => {
dispatch(modelChanged({ model: defaultModelInList, previousModel: currentModel }));

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;
}
const { width, height } = calculateNewSize(
state.canvasV2.bbox.aspectRatio.value,
state.canvasV2.present.bbox.aspectRatio.value,
optimalDimension * optimalDimension
);

Expand Down Expand Up @@ -172,7 +172,7 @@ const handleLoRAModels: ModelHandler = (models, state, dispatch, _log) => {

const handleControlAdapterModels: ModelHandler = (models, state, dispatch, _log) => {
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);
if (isModelAvailable) {
return;
Expand All @@ -183,15 +183,15 @@ const handleControlAdapterModels: ModelHandler = (models, state, dispatch, _log)

const handleIPAdapterModels: ModelHandler = (models, state, dispatch, _log) => {
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);
if (isModelAvailable) {
return;
}
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 }) => {
const isModelAvailable = ipaModels.some((m) => m.key === model?.key);
if (isModelAvailable) {
Expand Down
2 changes: 1 addition & 1 deletion invokeai/frontend/web/src/app/store/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ const allReducers = {
[queueSlice.name]: queueSlice.reducer,
[workflowSlice.name]: workflowSlice.reducer,
[hrfSlice.name]: hrfSlice.reducer,
[canvasV2Slice.name]: canvasV2Slice.reducer,
[canvasV2Slice.name]: undoable(canvasV2Slice.reducer),
[workflowSettingsSlice.name]: workflowSettingsSlice.reducer,
[upscaleSlice.name]: upscaleSlice.reducer,
[stylePresetSlice.name]: stylePresetSlice.reducer,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export const ControlLayerBadges = memo(() => {
const entityIdentifier = useEntityIdentifierContext('control_layer');
const { t } = useTranslation();
const withTransparencyEffect = useAppSelector(
(s) => selectEntityOrThrow(s.canvasV2, entityIdentifier).withTransparencyEffect
(s) => selectEntityOrThrow(s.canvasV2.present, entityIdentifier).withTransparencyEffect
);

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) =
});

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);

if (layerIds.length === 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const HeadsUpDisplay = memo(() => {
const isMouseDown = useStore(canvasManager.stateApi.$isMouseDown);
const lastMouseDownPos = useStore(canvasManager.stateApi.$lastMouseDownPos);
const lastAddedPoint = useStore(canvasManager.stateApi.$lastAddedPoint);
const bbox = useAppSelector((s) => s.canvasV2.bbox);
const bbox = useAppSelector((s) => s.canvasV2.present.bbox);

return (
<Flex flexDir="column" bg="blackAlpha.400" borderBottomEndRadius="base" p={2} minW={64} gap={2}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) =
});

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);

if (ipaIds.length === 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { IPAdapterModel } from './IPAdapterModel';
export const IPAdapterSettings = memo(() => {
const dispatch = useAppDispatch();
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(
(beginEndStepPct: [number, number]) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) =
});

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);

if (entityIds.length === 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const InpaintMaskMaskFillColorPicker = memo(() => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
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(
(color: RgbColor) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) =
});

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);

if (layerIds.length === 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { useTranslation } from 'react-i18next';
export const RegionalGuidanceBadges = memo(() => {
const entityIdentifier = useEntityIdentifierContext('regional_guidance');
const { t } = useTranslation();
const autoNegative = useAppSelector((s) => selectEntityOrThrow(s.canvasV2, entityIdentifier).autoNegative);
const autoNegative = useAppSelector((s) => selectEntityOrThrow(s.canvasV2.present, entityIdentifier).autoNegative);

return (
<>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const selectEntityIds = createMemoizedSelector(selectCanvasV2Slice, (canvasV2) =
});

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);

if (rgIds.length === 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export const RegionalGuidanceIPAdapterSettings = memo(({ ipAdapterId, ipAdapterN
dispatch(rgIPAdapterDeleted({ entityIdentifier, ipAdapterId }));
}, [dispatch, entityIdentifier, ipAdapterId]);
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`);
return ipa;
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const RegionalGuidanceMaskFillColorPicker = memo(() => {
const entityIdentifier = useEntityIdentifierContext('regional_guidance');
const { t } = useTranslation();
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(
(color: RgbColor) => {
dispatch(rgFillColorChanged({ entityIdentifier, color }));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const RegionalGuidanceMenuItemsAutoNegative = memo(() => {
const entityIdentifier = useEntityIdentifierContext('regional_guidance');
const { t } = useTranslation();
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(() => {
dispatch(rgAutoNegativeToggled({ entityIdentifier }));
}, [dispatch, entityIdentifier]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { useTranslation } from 'react-i18next';

export const RegionalGuidanceNegativePrompt = memo(() => {
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 textareaRef = useRef<HTMLTextAreaElement>(null);
const { t } = useTranslation();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { useTranslation } from 'react-i18next';

export const RegionalGuidancePositivePrompt = memo(() => {
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 textareaRef = useRef<HTMLTextAreaElement>(null);
const { t } = useTranslation();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ import { RegionalGuidancePositivePrompt } from './RegionalGuidancePositivePrompt
export const RegionalGuidanceSettings = memo(() => {
const entityIdentifier = useEntityIdentifierContext('regional_guidance');
const hasPositivePrompt = useAppSelector(
(s) => selectEntityOrThrow(s.canvasV2, entityIdentifier).positivePrompt !== null
(s) => selectEntityOrThrow(s.canvasV2.present, entityIdentifier).positivePrompt !== null
);
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 (
<CanvasEntitySettingsWrapper>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ export const ToolBrushButton = memo(() => {
const selectBrush = useSelectTool('brush');
const isSelected = useToolIsSelected('brush');
const isDrawingToolAllowed = useAppSelector((s) => {
if (!s.canvasV2.selectedEntityIdentifier?.type) {
if (!s.canvasV2.present.selectedEntityIdentifier?.type) {
return false;
}
return isDrawableEntityType(s.canvasV2.selectedEntityIdentifier.type);
return isDrawableEntityType(s.canvasV2.present.selectedEntityIdentifier.type);
});

const isDisabled = useMemo(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ export const ToolEraserButton = memo(() => {
const selectEraser = useSelectTool('eraser');
const isSelected = useToolIsSelected('eraser');
const isDrawingToolAllowed = useAppSelector((s) => {
if (!s.canvasV2.selectedEntityIdentifier?.type) {
if (!s.canvasV2.present.selectedEntityIdentifier?.type) {
return false;
}
return isDrawableEntityType(s.canvasV2.selectedEntityIdentifier.type);
return isDrawableEntityType(s.canvasV2.present.selectedEntityIdentifier.type);
});
const isDisabled = useMemo(() => {
return isTransforming || isFiltering || isStaging || !isDrawingToolAllowed;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ export const ToolMoveButton = memo(() => {
const isSelected = useToolIsSelected('move');
const isStaging = useAppSelector((s) => s.canvasSession.isStaging);
const isDrawingToolAllowed = useAppSelector((s) => {
if (!s.canvasV2.selectedEntityIdentifier?.type) {
if (!s.canvasV2.present.selectedEntityIdentifier?.type) {
return false;
}
return isDrawableEntityType(s.canvasV2.selectedEntityIdentifier.type);
return isDrawableEntityType(s.canvasV2.present.selectedEntityIdentifier.type);
});
const isDisabled = useMemo(() => {
return isTransforming || isFiltering || isStaging || !isDrawingToolAllowed;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ export const ToolRectButton = memo(() => {
const isTransforming = useIsTransforming();
const isStaging = useAppSelector((s) => s.canvasSession.isStaging);
const isDrawingToolAllowed = useAppSelector((s) => {
if (!s.canvasV2.selectedEntityIdentifier?.type) {
if (!s.canvasV2.present.selectedEntityIdentifier?.type) {
return false;
}
return isDrawableEntityType(s.canvasV2.selectedEntityIdentifier.type);
return isDrawableEntityType(s.canvasV2.present.selectedEntityIdentifier.type);
});

const isDisabled = useMemo(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,25 @@ import { memo, useCallback } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
import { PiArrowClockwiseBold, PiArrowCounterClockwiseBold } from 'react-icons/pi';
import { useDispatch } from 'react-redux';
import { ActionCreators } from 'redux-undo';

export const UndoRedoButtonGroup = memo(() => {
const { t } = useTranslation();
const dispatch = useDispatch();

const mayUndo = useAppSelector(() => false);
const mayUndo = useAppSelector(() => true);
const handleUndo = useCallback(() => {
// TODO(psyche): Implement undo
// dispatch(undo());
}, []);
dispatch(ActionCreators.undo());
}, [dispatch]);
useHotkeys(['meta+z', 'ctrl+z'], handleUndo, { enabled: mayUndo, preventDefault: true }, [mayUndo, handleUndo]);

const mayRedo = useAppSelector(() => false);
const mayRedo = useAppSelector(() => true);
const handleRedo = useCallback(() => {
// TODO(psyche): Implement redo
// dispatch(redo());
}, []);
dispatch(ActionCreators.redo());
}, [dispatch]);
useHotkeys(['meta+shift+z', 'ctrl+shift+z'], handleRedo, { enabled: mayRedo, preventDefault: true }, [
mayRedo,
handleRedo,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,13 @@ const snapCandidates = marks.slice(1, marks.length - 1);
export const CanvasEntityOpacity = memo(() => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const selectedEntityIdentifier = useAppSelector((s) => s.canvasV2.selectedEntityIdentifier);
const selectedEntityIdentifier = useAppSelector((s) => s.canvasV2.present.selectedEntityIdentifier);
const opacity = useAppSelector((s) => {
const selectedEntityIdentifier = s.canvasV2.selectedEntityIdentifier;
const selectedEntityIdentifier = s.canvasV2.present.selectedEntityIdentifier;
if (!selectedEntityIdentifier) {
return null;
}
const selectedEntity = selectEntity(s.canvasV2, selectedEntityIdentifier);
const selectedEntity = selectEntity(s.canvasV2.present, selectedEntityIdentifier);
if (!selectedEntity) {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types'
import { useMemo } from 'react';

export const useEntityIsSelected = (entityIdentifier: CanvasEntityIdentifier) => {
const selectedEntityIdentifier = useAppSelector((s) => s.canvasV2.selectedEntityIdentifier);
const selectedEntityIdentifier = useAppSelector((s) => s.canvasV2.present.selectedEntityIdentifier);
const isSelected = useMemo(() => {
return selectedEntityIdentifier?.id === entityIdentifier.id;
}, [selectedEntityIdentifier, entityIdentifier.id]);
Expand Down
Loading

0 comments on commit 35b483b

Please sign in to comment.