mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): revise bulk download listeners
- Use a single listener for all of the to keep them in one spot - Use the bulk download item name as a toast id so we can update the existing toasts - Update handling to work with other environments - Move all bulk download handling from components to listener
This commit is contained in:
parent
ffef5c65bb
commit
85dae6ad1e
@ -424,10 +424,11 @@
|
||||
"uploads": "Uploads",
|
||||
"deleteSelection": "Delete Selection",
|
||||
"downloadSelection": "Download Selection",
|
||||
"preparingDownload": "Preparing Download",
|
||||
"preparingDownloadFailed": "Problem Preparing Download",
|
||||
"bulkDownloadStarting": "Beginning Download",
|
||||
"bulkDownloadFailed": "Problem Preparing Download",
|
||||
"bulkDownloadRequested": "Preparing Download",
|
||||
"bulkDownloadRequestedDesc": "Your download request is being prepared. This may take a few moments.",
|
||||
"bulkDownloadRequestFailed": "Problem Preparing Download",
|
||||
"bulkDownloadStarting": "Download Starting",
|
||||
"bulkDownloadFailed": "Download Failed",
|
||||
"problemDeletingImages": "Problem Deleting Images",
|
||||
"problemDeletingImagesDesc": "One or more images could not be deleted"
|
||||
},
|
||||
|
@ -1,5 +1,6 @@
|
||||
import type { ListenerEffect, TypedAddListener, TypedStartListening, UnknownAction } from '@reduxjs/toolkit';
|
||||
import { addListener, createListenerMiddleware } from '@reduxjs/toolkit';
|
||||
import { addBulkDownloadListeners } from 'app/store/middleware/listenerMiddleware/listeners/bulkDownload';
|
||||
import { addGalleryImageClickedListener } from 'app/store/middleware/listenerMiddleware/listeners/galleryImageClicked';
|
||||
import type { AppDispatch, RootState } from 'app/store/store';
|
||||
|
||||
@ -48,8 +49,6 @@ import { addInitialImageSelectedListener } from './listeners/initialImageSelecte
|
||||
import { addModelSelectedListener } from './listeners/modelSelected';
|
||||
import { addModelsLoadedListener } from './listeners/modelsLoaded';
|
||||
import { addDynamicPromptsListener } from './listeners/promptChanged';
|
||||
import { addBulkDownloadCompleteEventListener } from './listeners/socketio/socketBulkDownloadComplete';
|
||||
import { addBulkDownloadFailedEventListener } from './listeners/socketio/socketBulkDownloadFailed';
|
||||
import { addSocketConnectedEventListener as addSocketConnectedListener } from './listeners/socketio/socketConnected';
|
||||
import { addSocketDisconnectedEventListener as addSocketDisconnectedListener } from './listeners/socketio/socketDisconnected';
|
||||
import { addGeneratorProgressEventListener as addGeneratorProgressListener } from './listeners/socketio/socketGeneratorProgress';
|
||||
@ -139,8 +138,7 @@ addModelLoadEventListener();
|
||||
addSessionRetrievalErrorEventListener();
|
||||
addInvocationRetrievalErrorEventListener();
|
||||
addSocketQueueItemStatusChangedEventListener();
|
||||
addBulkDownloadCompleteEventListener();
|
||||
addBulkDownloadFailedEventListener();
|
||||
addBulkDownloadListeners();
|
||||
|
||||
// ControlNet
|
||||
addControlNetImageProcessedListener();
|
||||
|
@ -0,0 +1,118 @@
|
||||
import type { UseToastOptions } from '@invoke-ai/ui-library';
|
||||
import { createStandaloneToast, theme, TOAST_OPTIONS } from '@invoke-ai/ui-library';
|
||||
import { logger } from 'app/logging/logger';
|
||||
import { startAppListening } from 'app/store/middleware/listenerMiddleware';
|
||||
import { t } from 'i18next';
|
||||
import { imagesApi } from 'services/api/endpoints/images';
|
||||
import {
|
||||
socketBulkDownloadCompleted,
|
||||
socketBulkDownloadFailed,
|
||||
socketBulkDownloadStarted,
|
||||
} from 'services/events/actions';
|
||||
|
||||
const log = logger('images');
|
||||
|
||||
const { toast } = createStandaloneToast({
|
||||
theme: theme,
|
||||
defaultOptions: TOAST_OPTIONS.defaultOptions,
|
||||
});
|
||||
|
||||
export const addBulkDownloadListeners = () => {
|
||||
startAppListening({
|
||||
matcher: imagesApi.endpoints.bulkDownloadImages.matchFulfilled,
|
||||
effect: async (action) => {
|
||||
log.debug(action.payload, 'Bulk download requested');
|
||||
|
||||
// If we have an item name, we are processing the bulk download locally and should use it as the toast id to
|
||||
// prevent multiple toasts for the same item.
|
||||
toast({
|
||||
id: action.payload.bulk_download_item_name ?? undefined,
|
||||
title: t('gallery.bulkDownloadRequested'),
|
||||
status: 'success',
|
||||
// Show the response message if it exists, otherwise show the default message
|
||||
description: action.payload.response || t('gallery.bulkDownloadRequestedDesc'),
|
||||
duration: null,
|
||||
isClosable: true,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
startAppListening({
|
||||
matcher: imagesApi.endpoints.bulkDownloadImages.matchRejected,
|
||||
effect: async () => {
|
||||
log.debug('Bulk download request failed');
|
||||
|
||||
// There isn't any toast to update if we get this event.
|
||||
toast({
|
||||
title: t('gallery.bulkDownloadRequestFailed'),
|
||||
status: 'success',
|
||||
isClosable: true,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
startAppListening({
|
||||
actionCreator: socketBulkDownloadStarted,
|
||||
effect: async (action) => {
|
||||
// This should always happen immediately after the bulk download request, so we don't need to show a toast here.
|
||||
log.debug(action.payload.data, 'Bulk download preparation started');
|
||||
},
|
||||
});
|
||||
|
||||
startAppListening({
|
||||
actionCreator: socketBulkDownloadCompleted,
|
||||
effect: async (action) => {
|
||||
log.debug(action.payload.data, 'Bulk download preparation completed');
|
||||
|
||||
const { bulk_download_item_name } = action.payload.data;
|
||||
|
||||
// TODO(psyche): This URL may break in in some environments (e.g. Nvidia workbench) but we need to test it first
|
||||
const url = `/api/v1/images/download/${bulk_download_item_name}`;
|
||||
const a = document.createElement('a');
|
||||
a.style.display = 'none';
|
||||
a.href = url;
|
||||
a.download = bulk_download_item_name;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
|
||||
const toastOptions: UseToastOptions = {
|
||||
id: bulk_download_item_name,
|
||||
title: t('gallery.bulkDownloadStarting'),
|
||||
status: 'success',
|
||||
description: bulk_download_item_name,
|
||||
duration: 5000,
|
||||
isClosable: true,
|
||||
};
|
||||
|
||||
if (toast.isActive(bulk_download_item_name)) {
|
||||
toast.update(bulk_download_item_name, toastOptions);
|
||||
} else {
|
||||
toast(toastOptions);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
startAppListening({
|
||||
actionCreator: socketBulkDownloadFailed,
|
||||
effect: async (action) => {
|
||||
log.debug(action.payload.data, 'Bulk download preparation failed');
|
||||
|
||||
const { bulk_download_item_name } = action.payload.data;
|
||||
|
||||
const toastOptions: UseToastOptions = {
|
||||
id: bulk_download_item_name,
|
||||
title: t('gallery.bulkDownloadFailed'),
|
||||
status: 'error',
|
||||
description: action.payload.data.error,
|
||||
duration: null,
|
||||
isClosable: true,
|
||||
};
|
||||
|
||||
if (toast.isActive(bulk_download_item_name)) {
|
||||
toast.update(bulk_download_item_name, toastOptions);
|
||||
} else {
|
||||
toast(toastOptions);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
@ -1,41 +0,0 @@
|
||||
import { logger } from 'app/logging/logger';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { t } from 'i18next';
|
||||
import { socketBulkDownloadCompleted } from 'services/events/actions';
|
||||
|
||||
import { startAppListening } from '../..';
|
||||
|
||||
const log = logger('socketio');
|
||||
|
||||
export const addBulkDownloadCompleteEventListener = () => {
|
||||
startAppListening({
|
||||
actionCreator: socketBulkDownloadCompleted,
|
||||
effect: async (action, { dispatch }) => {
|
||||
log.debug(action.payload, 'Bulk download complete');
|
||||
|
||||
const bulk_download_item_name = action.payload.data.bulk_download_item_name;
|
||||
|
||||
const url = `/api/v1/images/download/${bulk_download_item_name}`;
|
||||
const a = document.createElement('a');
|
||||
a.style.display = 'none';
|
||||
a.href = url;
|
||||
a.download = bulk_download_item_name;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
|
||||
dispatch(
|
||||
addToast({
|
||||
title: t('gallery.bulkDownloadStarting'),
|
||||
status: 'success',
|
||||
...(action.payload
|
||||
? {
|
||||
description: bulk_download_item_name,
|
||||
duration: null,
|
||||
isClosable: true,
|
||||
}
|
||||
: {}),
|
||||
})
|
||||
);
|
||||
},
|
||||
});
|
||||
};
|
@ -1,32 +0,0 @@
|
||||
import { logger } from 'app/logging/logger';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { t } from 'i18next';
|
||||
import { socketBulkDownloadFailed } from 'services/events/actions';
|
||||
|
||||
import { startAppListening } from '../..';
|
||||
|
||||
const log = logger('socketio');
|
||||
|
||||
export const addBulkDownloadFailedEventListener = () => {
|
||||
startAppListening({
|
||||
actionCreator: socketBulkDownloadFailed,
|
||||
effect: async (action, { dispatch }) => {
|
||||
log.debug(action.payload, 'Bulk download error');
|
||||
|
||||
|
||||
dispatch(
|
||||
addToast({
|
||||
title: t('gallery.bulkDownloadFailed'),
|
||||
status: 'error',
|
||||
...(action.payload
|
||||
? {
|
||||
description: action.payload.data.error,
|
||||
duration: null,
|
||||
isClosable: true,
|
||||
}
|
||||
: {}),
|
||||
})
|
||||
);
|
||||
},
|
||||
});
|
||||
};
|
@ -5,7 +5,6 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { autoAddBoardIdChanged, selectGallerySlice } from 'features/gallery/store/gallerySlice';
|
||||
import type { BoardId } from 'features/gallery/store/types';
|
||||
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiDownloadBold, PiPlusBold } from 'react-icons/pi';
|
||||
@ -41,35 +40,9 @@ const BoardContextMenu = ({ board, board_id, setBoardToDelete, children }: Props
|
||||
dispatch(autoAddBoardIdChanged(board_id));
|
||||
}, [board_id, dispatch]);
|
||||
|
||||
const handleBulkDownload = useCallback(async () => {
|
||||
try {
|
||||
const response = await bulkDownload({
|
||||
image_names: [],
|
||||
board_id: board_id,
|
||||
}).unwrap();
|
||||
|
||||
dispatch(
|
||||
addToast({
|
||||
title: t('gallery.preparingDownload'),
|
||||
status: 'success',
|
||||
...(response.response
|
||||
? {
|
||||
description: response.response,
|
||||
duration: null,
|
||||
isClosable: true,
|
||||
}
|
||||
: {}),
|
||||
})
|
||||
);
|
||||
} catch {
|
||||
dispatch(
|
||||
addToast({
|
||||
title: t('gallery.preparingDownloadFailed'),
|
||||
status: 'error',
|
||||
})
|
||||
);
|
||||
}
|
||||
}, [t, board_id, bulkDownload, dispatch]);
|
||||
const handleBulkDownload = useCallback(() => {
|
||||
bulkDownload({ image_names: [], board_id: board_id });
|
||||
}, [board_id, bulkDownload]);
|
||||
|
||||
const renderMenuFunc = useCallback(
|
||||
() => (
|
||||
|
@ -5,7 +5,6 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { imagesToChangeSelected, isModalOpenChanged } from 'features/changeBoardModal/store/slice';
|
||||
import { imagesToDeleteSelected } from 'features/deleteImageModal/store/slice';
|
||||
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiDownloadSimpleBold, PiFoldersBold, PiStarBold, PiStarFill, PiTrashSimpleBold } from 'react-icons/pi';
|
||||
@ -44,34 +43,9 @@ const MultipleSelectionMenuItems = () => {
|
||||
unstarImages({ imageDTOs: selection });
|
||||
}, [unstarImages, selection]);
|
||||
|
||||
const handleBulkDownload = useCallback(async () => {
|
||||
try {
|
||||
const response = await bulkDownload({
|
||||
image_names: selection.map((img) => img.image_name),
|
||||
}).unwrap();
|
||||
|
||||
dispatch(
|
||||
addToast({
|
||||
title: t('gallery.preparingDownload'),
|
||||
status: 'success',
|
||||
...(response.response
|
||||
? {
|
||||
description: response.response,
|
||||
duration: null,
|
||||
isClosable: true,
|
||||
}
|
||||
: {}),
|
||||
})
|
||||
);
|
||||
} catch {
|
||||
dispatch(
|
||||
addToast({
|
||||
title: t('gallery.preparingDownloadFailed'),
|
||||
status: 'error',
|
||||
})
|
||||
);
|
||||
}
|
||||
}, [t, selection, bulkDownload, dispatch]);
|
||||
const handleBulkDownload = useCallback(() => {
|
||||
bulkDownload({ image_names: selection.map((img) => img.image_name) });
|
||||
}, [selection, bulkDownload]);
|
||||
|
||||
const areAllStarred = useMemo(() => {
|
||||
return selection.every((img) => img.starred);
|
||||
|
Loading…
Reference in New Issue
Block a user