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",
|
"uploads": "Uploads",
|
||||||
"deleteSelection": "Delete Selection",
|
"deleteSelection": "Delete Selection",
|
||||||
"downloadSelection": "Download Selection",
|
"downloadSelection": "Download Selection",
|
||||||
"preparingDownload": "Preparing Download",
|
"bulkDownloadRequested": "Preparing Download",
|
||||||
"preparingDownloadFailed": "Problem Preparing Download",
|
"bulkDownloadRequestedDesc": "Your download request is being prepared. This may take a few moments.",
|
||||||
"bulkDownloadStarting": "Beginning Download",
|
"bulkDownloadRequestFailed": "Problem Preparing Download",
|
||||||
"bulkDownloadFailed": "Problem Preparing Download",
|
"bulkDownloadStarting": "Download Starting",
|
||||||
|
"bulkDownloadFailed": "Download Failed",
|
||||||
"problemDeletingImages": "Problem Deleting Images",
|
"problemDeletingImages": "Problem Deleting Images",
|
||||||
"problemDeletingImagesDesc": "One or more images could not be deleted"
|
"problemDeletingImagesDesc": "One or more images could not be deleted"
|
||||||
},
|
},
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import type { ListenerEffect, TypedAddListener, TypedStartListening, UnknownAction } from '@reduxjs/toolkit';
|
import type { ListenerEffect, TypedAddListener, TypedStartListening, UnknownAction } from '@reduxjs/toolkit';
|
||||||
import { addListener, createListenerMiddleware } 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 { addGalleryImageClickedListener } from 'app/store/middleware/listenerMiddleware/listeners/galleryImageClicked';
|
||||||
import type { AppDispatch, RootState } from 'app/store/store';
|
import type { AppDispatch, RootState } from 'app/store/store';
|
||||||
|
|
||||||
@ -48,8 +49,6 @@ import { addInitialImageSelectedListener } from './listeners/initialImageSelecte
|
|||||||
import { addModelSelectedListener } from './listeners/modelSelected';
|
import { addModelSelectedListener } from './listeners/modelSelected';
|
||||||
import { addModelsLoadedListener } from './listeners/modelsLoaded';
|
import { addModelsLoadedListener } from './listeners/modelsLoaded';
|
||||||
import { addDynamicPromptsListener } from './listeners/promptChanged';
|
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 { addSocketConnectedEventListener as addSocketConnectedListener } from './listeners/socketio/socketConnected';
|
||||||
import { addSocketDisconnectedEventListener as addSocketDisconnectedListener } from './listeners/socketio/socketDisconnected';
|
import { addSocketDisconnectedEventListener as addSocketDisconnectedListener } from './listeners/socketio/socketDisconnected';
|
||||||
import { addGeneratorProgressEventListener as addGeneratorProgressListener } from './listeners/socketio/socketGeneratorProgress';
|
import { addGeneratorProgressEventListener as addGeneratorProgressListener } from './listeners/socketio/socketGeneratorProgress';
|
||||||
@ -139,8 +138,7 @@ addModelLoadEventListener();
|
|||||||
addSessionRetrievalErrorEventListener();
|
addSessionRetrievalErrorEventListener();
|
||||||
addInvocationRetrievalErrorEventListener();
|
addInvocationRetrievalErrorEventListener();
|
||||||
addSocketQueueItemStatusChangedEventListener();
|
addSocketQueueItemStatusChangedEventListener();
|
||||||
addBulkDownloadCompleteEventListener();
|
addBulkDownloadListeners();
|
||||||
addBulkDownloadFailedEventListener();
|
|
||||||
|
|
||||||
// ControlNet
|
// ControlNet
|
||||||
addControlNetImageProcessedListener();
|
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 { autoAddBoardIdChanged, selectGallerySlice } from 'features/gallery/store/gallerySlice';
|
||||||
import type { BoardId } from 'features/gallery/store/types';
|
import type { BoardId } from 'features/gallery/store/types';
|
||||||
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
||||||
import { addToast } from 'features/system/store/systemSlice';
|
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { PiDownloadBold, PiPlusBold } from 'react-icons/pi';
|
import { PiDownloadBold, PiPlusBold } from 'react-icons/pi';
|
||||||
@ -41,35 +40,9 @@ const BoardContextMenu = ({ board, board_id, setBoardToDelete, children }: Props
|
|||||||
dispatch(autoAddBoardIdChanged(board_id));
|
dispatch(autoAddBoardIdChanged(board_id));
|
||||||
}, [board_id, dispatch]);
|
}, [board_id, dispatch]);
|
||||||
|
|
||||||
const handleBulkDownload = useCallback(async () => {
|
const handleBulkDownload = useCallback(() => {
|
||||||
try {
|
bulkDownload({ image_names: [], board_id: board_id });
|
||||||
const response = await bulkDownload({
|
}, [board_id, 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 renderMenuFunc = useCallback(
|
const renderMenuFunc = useCallback(
|
||||||
() => (
|
() => (
|
||||||
|
@ -5,7 +5,6 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
|||||||
import { imagesToChangeSelected, isModalOpenChanged } from 'features/changeBoardModal/store/slice';
|
import { imagesToChangeSelected, isModalOpenChanged } from 'features/changeBoardModal/store/slice';
|
||||||
import { imagesToDeleteSelected } from 'features/deleteImageModal/store/slice';
|
import { imagesToDeleteSelected } from 'features/deleteImageModal/store/slice';
|
||||||
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
||||||
import { addToast } from 'features/system/store/systemSlice';
|
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { PiDownloadSimpleBold, PiFoldersBold, PiStarBold, PiStarFill, PiTrashSimpleBold } from 'react-icons/pi';
|
import { PiDownloadSimpleBold, PiFoldersBold, PiStarBold, PiStarFill, PiTrashSimpleBold } from 'react-icons/pi';
|
||||||
@ -44,34 +43,9 @@ const MultipleSelectionMenuItems = () => {
|
|||||||
unstarImages({ imageDTOs: selection });
|
unstarImages({ imageDTOs: selection });
|
||||||
}, [unstarImages, selection]);
|
}, [unstarImages, selection]);
|
||||||
|
|
||||||
const handleBulkDownload = useCallback(async () => {
|
const handleBulkDownload = useCallback(() => {
|
||||||
try {
|
bulkDownload({ image_names: selection.map((img) => img.image_name) });
|
||||||
const response = await bulkDownload({
|
}, [selection, 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 areAllStarred = useMemo(() => {
|
const areAllStarred = useMemo(() => {
|
||||||
return selection.every((img) => img.starred);
|
return selection.every((img) => img.starred);
|
||||||
|
Loading…
Reference in New Issue
Block a user