Skip to content

Commit

Permalink
fix(ui): revise image selection after deletion
Browse files Browse the repository at this point in the history
- For single image deletion, select the image in the same slot as the deleted image
- For multiple image deletion, empty selection
- On list images, if no images are currently selected, select the first image
  • Loading branch information
psychedelicious committed Jul 3, 2024
1 parent 5578660 commit 7c01b69
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 81 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type { TypedStartListening } from '@reduxjs/toolkit';
import { createListenerMiddleware } from '@reduxjs/toolkit';
import { addCommitStagingAreaImageListener } from 'app/store/middleware/listenerMiddleware/listeners/addCommitStagingAreaImageListener';
import { addFirstListImagesListener } from 'app/store/middleware/listenerMiddleware/listeners/addFirstListImagesListener.ts';
import { addAnyEnqueuedListener } from 'app/store/middleware/listenerMiddleware/listeners/anyEnqueued';
import { addAppConfigReceivedListener } from 'app/store/middleware/listenerMiddleware/listeners/appConfigReceived';
import { addAppStartedListener } from 'app/store/middleware/listenerMiddleware/listeners/appStarted';
Expand All @@ -26,7 +25,7 @@ import { addGalleryImageClickedListener } from 'app/store/middleware/listenerMid
import { addGalleryOffsetChangedListener } from 'app/store/middleware/listenerMiddleware/listeners/galleryOffsetChanged';
import { addGetOpenAPISchemaListener } from 'app/store/middleware/listenerMiddleware/listeners/getOpenAPISchema';
import { addImageAddedToBoardFulfilledListener } from 'app/store/middleware/listenerMiddleware/listeners/imageAddedToBoard';
import { addRequestedSingleImageDeletionListener } from 'app/store/middleware/listenerMiddleware/listeners/imageDeleted';
import { addImageDeletionListeners } from 'app/store/middleware/listenerMiddleware/listeners/imageDeletionListeners';
import { addImageDroppedListener } from 'app/store/middleware/listenerMiddleware/listeners/imageDropped';
import { addImageRemovedFromBoardFulfilledListener } from 'app/store/middleware/listenerMiddleware/listeners/imageRemovedFromBoard';
import { addImagesStarredListener } from 'app/store/middleware/listenerMiddleware/listeners/imagesStarred';
Expand Down Expand Up @@ -70,7 +69,7 @@ const startAppListening = listenerMiddleware.startListening as AppStartListening
addImageUploadedFulfilledListener(startAppListening);

// Image deleted
addRequestedSingleImageDeletionListener(startAppListening);
addImageDeletionListeners(startAppListening);
addDeleteBoardAndImagesFulfilledListener(startAppListening);
addImageToDeleteSelectedListener(startAppListening);

Expand Down Expand Up @@ -139,7 +138,6 @@ addModelSelectedListener(startAppListening);
addAppStartedListener(startAppListening);
addModelsLoadedListener(startAppListening);
addAppConfigReceivedListener(startAppListening);
addFirstListImagesListener(startAppListening);

// Ad-hoc upscale workflwo
addUpscaleRequestedListener(startAppListening);
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ import { imageSelected } from 'features/gallery/store/gallerySlice';
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
import { isImageFieldInputInstance } from 'features/nodes/types/field';
import { isInvocationNode } from 'features/nodes/types/invocation';
import { forEach } from 'lodash-es';
import { api } from 'services/api';
import { forEach, intersectionBy } from 'lodash-es';
import { imagesApi } from 'services/api/endpoints/images';
import type { ImageDTO } from 'services/api/types';

// Some utils to delete images from different parts of the app
const deleteNodesImages = (state: RootState, dispatch: AppDispatch, imageDTO: ImageDTO) => {
state.nodes.present.nodes.forEach((node) => {
if (!isInvocationNode(node)) {
Expand Down Expand Up @@ -97,10 +97,11 @@ const deleteControlLayerImages = (state: RootState, dispatch: AppDispatch, image
});
};

export const addRequestedSingleImageDeletionListener = (startAppListening: AppStartListening) => {
export const addImageDeletionListeners = (startAppListening: AppStartListening) => {
// Handle single image deletion
startAppListening({
actionCreator: imageDeletionConfirmed,
effect: async (action, { dispatch, getState, condition }) => {
effect: async (action, { dispatch, getState }) => {
const { imageDTOs, imagesUsage } = action.payload;

if (imageDTOs.length !== 1 || imagesUsage.length !== 1) {
Expand All @@ -116,49 +117,46 @@ export const addRequestedSingleImageDeletionListener = (startAppListening: AppSt
return;
}

dispatch(isModalOpenChanged(false));
const state = getState();

// We need to reset the features where the image is in use - none of these work if their image(s) don't exist
if (imageUsage.isCanvasImage) {
dispatch(resetCanvas());
}

imageDTOs.forEach((imageDTO) => {
deleteControlAdapterImages(state, dispatch, imageDTO);
deleteNodesImages(state, dispatch, imageDTO);
deleteControlLayerImages(state, dispatch, imageDTO);
});

// Delete from server
const { requestId } = dispatch(imagesApi.endpoints.deleteImage.initiate(imageDTO));
try {
const state = getState();
await dispatch(imagesApi.endpoints.deleteImage.initiate(imageDTO)).unwrap();

// Wait for successful deletion, then trigger boards to re-fetch
const wasImageDeleted = await condition(
(action) => imagesApi.endpoints.deleteImage.matchFulfilled(action) && action.meta.requestId === requestId,
30000
);
if (state.gallery.selection.some((i) => i.image_name === imageDTO.image_name)) {
// The deleted image was a selected image, we need to select the next image
const newSelection = state.gallery.selection.filter((i) => i.image_name !== imageDTO.image_name);

if (wasImageDeleted) {
dispatch(api.util.invalidateTags([{ type: 'Board', id: imageDTO.board_id ?? 'none' }]));
}
if (newSelection.length > 0) {
return;
}

const lastSelectedImage = state.gallery.selection[state.gallery.selection.length - 1]?.image_name;
// Get the current list of images and select the same index
const baseQueryArgs = selectListImagesQueryArgs(state);
const data = imagesApi.endpoints.listImages.select(baseQueryArgs)(state).data;

if (imageDTO && imageDTO?.image_name === lastSelectedImage) {
const baseQueryArgs = selectListImagesQueryArgs(state);
const { data } = imagesApi.endpoints.listImages.select(baseQueryArgs)(state);
if (data) {
const deletedImageIndex = data.items.findIndex((i) => i.image_name === imageDTO.image_name);
const nextImage = data.items[deletedImageIndex + 1] ?? data.items[0] ?? null;
dispatch(imageSelected(nextImage));
}
}

if (data && data.items) {
const newlySelectedImage = data?.items.find((img) => img.image_name !== imageDTO?.image_name);
dispatch(imageSelected(newlySelectedImage || null));
} else {
dispatch(imageSelected(null));
// We need to reset the features where the image is in use - none of these work if their image(s) don't exist
if (imageUsage.isCanvasImage) {
dispatch(resetCanvas());
}

deleteControlAdapterImages(state, dispatch, imageDTO);
deleteNodesImages(state, dispatch, imageDTO);
deleteControlLayerImages(state, dispatch, imageDTO);
} catch {
// no-op
} finally {
dispatch(isModalOpenChanged(false));
}
},
});

// Handle multiple image deletion
startAppListening({
actionCreator: imageDeletionConfirmed,
effect: async (action, { dispatch, getState }) => {
Expand All @@ -170,20 +168,18 @@ export const addRequestedSingleImageDeletionListener = (startAppListening: AppSt
}

try {
// Delete from server
await dispatch(imagesApi.endpoints.deleteImages.initiate({ imageDTOs })).unwrap();
const state = getState();
const queryArgs = selectListImagesQueryArgs(state);
const { data } = imagesApi.endpoints.listImages.select(queryArgs)(state);
await dispatch(imagesApi.endpoints.deleteImages.initiate({ imageDTOs })).unwrap();

if (data && data.items[0]) {
dispatch(imageSelected(data.items[0]));
} else {
dispatch(imageSelected(null));
if (intersectionBy(state.gallery.selection, imageDTOs, 'image_name').length > 0) {
// Some selected images were deleted, need to select the next image
const queryArgs = selectListImagesQueryArgs(state);
const { data } = imagesApi.endpoints.listImages.select(queryArgs)(state);
if (data) {
dispatch(imageSelected(null));
}
}

dispatch(isModalOpenChanged(false));

// We need to reset the features where the image is in use - none of these work if their image(s) don't exist

if (imagesUsage.some((i) => i.isCanvasImage)) {
Expand All @@ -197,14 +193,20 @@ export const addRequestedSingleImageDeletionListener = (startAppListening: AppSt
});
} catch {
// no-op
} finally {
dispatch(isModalOpenChanged(false));
}
},
});

// When we list images, if no images is selected, select the first one.
startAppListening({
matcher: imagesApi.endpoints.deleteImage.matchPending,
effect: () => {
//
matcher: imagesApi.endpoints.listImages.matchFulfilled,
effect: (action, { dispatch, getState }) => {
const selection = getState().gallery.selection;
if (selection.length === 0) {
dispatch(imageSelected(action.payload.items[0] ?? null));
}
},
});

Expand Down

0 comments on commit 7c01b69

Please sign in to comment.