Maryhipp/dummy bulk download (#4852)

* UI for bulk downloading boards or groups of images

* placeholder route for bulk downloads that does nothing

* lint

---------

Co-authored-by: Mary Hipp <maryhipp@Marys-MacBook-Air.local>
This commit is contained in:
Mary Hipp Rogers 2023-10-11 19:27:22 -04:00 committed by GitHub
parent 40f9e49b5e
commit 69937d68d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 194 additions and 36 deletions

View File

@ -322,3 +322,20 @@ async def unstar_images_in_list(
return ImagesUpdatedFromListResult(updated_image_names=updated_image_names)
except Exception:
raise HTTPException(status_code=500, detail="Failed to unstar images")
class ImagesDownloaded(BaseModel):
response: Optional[str] = Field(
description="If defined, the message to display to the user when images begin downloading"
)
@images_router.post("/download", operation_id="download_images_from_list", response_model=ImagesDownloaded)
async def download_images_from_list(
image_names: list[str] = Body(description="The list of names of images to download", embed=True),
board_id: Optional[str] = Body(
default=None, description="The board from which image should be downloaded from", embed=True
),
) -> ImagesDownloaded:
# return ImagesDownloaded(response="Your images are downloading")
raise HTTPException(status_code=501, detail="Endpoint is not yet implemented")

View File

@ -38,7 +38,8 @@
"searchBoard": "Search Boards...",
"selectBoard": "Select a Board",
"topMessage": "This board contains images used in the following features:",
"uncategorized": "Uncategorized"
"uncategorized": "Uncategorized",
"downloadBoard": "Download Board"
},
"common": {
"accept": "Accept",
@ -323,7 +324,10 @@
"showUploads": "Show Uploads",
"singleColumnLayout": "Single Column Layout",
"unableToLoad": "Unable to load Gallery",
"uploads": "Uploads"
"uploads": "Uploads",
"downloadSelection": "Download Selection",
"preparingDownload": "Preparing Download",
"preparingDownloadFailed": "Problem Preparing Download"
},
"hotkeys": {
"acceptStagingImage": {

View File

@ -22,7 +22,8 @@ export type AppFeature =
| 'pauseQueue'
| 'resumeQueue'
| 'prependQueue'
| 'invocationCache';
| 'invocationCache'
| 'bulkDownload';
/**
* A disable-able Stable Diffusion feature

View File

@ -11,12 +11,15 @@ import { autoAddBoardIdChanged } from 'features/gallery/store/gallerySlice';
import { BoardId } from 'features/gallery/store/types';
import { MouseEvent, memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { FaPlus } from 'react-icons/fa';
import { FaDownload, FaPlus } from 'react-icons/fa';
import { useBoardName } from 'services/api/hooks/useBoardName';
import { BoardDTO } from 'services/api/types';
import { menuListMotionProps } from 'theme/components/menu';
import GalleryBoardContextMenuItems from './GalleryBoardContextMenuItems';
import NoBoardContextMenuItems from './NoBoardContextMenuItems';
import { useFeatureStatus } from '../../../system/hooks/useFeatureStatus';
import { useBulkDownloadImagesMutation } from '../../../../services/api/endpoints/images';
import { addToast } from '../../../system/store/systemSlice';
type Props = {
board?: BoardDTO;
@ -31,6 +34,7 @@ const BoardContextMenu = ({
setBoardToDelete,
children,
}: Props) => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const selector = useMemo(
@ -49,17 +53,43 @@ const BoardContextMenu = ({
const { isAutoAdd, autoAssignBoardOnClick } = useAppSelector(selector);
const boardName = useBoardName(board_id);
const isBulkDownloadEnabled =
useFeatureStatus('bulkDownload').isFeatureEnabled;
const [bulkDownload] = useBulkDownloadImagesMutation();
const handleSetAutoAdd = useCallback(() => {
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 } : {}),
})
);
} catch {
dispatch(
addToast({
title: t('gallery.preparingDownloadFailed'),
status: 'error',
})
);
}
}, [t, board_id, bulkDownload, dispatch]);
const skipEvent = useCallback((e: MouseEvent<HTMLDivElement>) => {
e.preventDefault();
}, []);
const { t } = useTranslation();
return (
<IAIContextMenu<HTMLDivElement>
menuProps={{ size: 'sm', isLazy: true }}
@ -81,6 +111,14 @@ const BoardContextMenu = ({
>
{t('boards.menuItemAutoAdd')}
</MenuItem>
{isBulkDownloadEnabled && (
<MenuItem
icon={<FaDownload />}
onClickCapture={handleBulkDownload}
>
{t('boards.downloadBoard')}
</MenuItem>
)}
{!board && <NoBoardContextMenuItems />}
{board && (
<GalleryBoardContextMenuItems

View File

@ -8,20 +8,29 @@ import {
} from 'features/changeBoardModal/store/slice';
import { imagesToDeleteSelected } from 'features/deleteImageModal/store/slice';
import { memo, useCallback, useMemo } from 'react';
import { FaFolder, FaTrash } from 'react-icons/fa';
import { FaDownload, FaFolder, FaTrash } from 'react-icons/fa';
import { MdStar, MdStarBorder } from 'react-icons/md';
import {
useBulkDownloadImagesMutation,
useStarImagesMutation,
useUnstarImagesMutation,
} from '../../../../services/api/endpoints/images';
import { useFeatureStatus } from '../../../system/hooks/useFeatureStatus';
import { addToast } from '../../../system/store/systemSlice';
import { useTranslation } from 'react-i18next';
const MultipleSelectionMenuItems = () => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const selection = useAppSelector((state) => state.gallery.selection);
const customStarUi = useStore($customStarUI);
const isBulkDownloadEnabled =
useFeatureStatus('bulkDownload').isFeatureEnabled;
const [starImages] = useStarImagesMutation();
const [unstarImages] = useUnstarImagesMutation();
const [bulkDownload] = useBulkDownloadImagesMutation();
const handleChangeBoard = useCallback(() => {
dispatch(imagesToChangeSelected(selection));
@ -40,6 +49,29 @@ 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 } : {}),
})
);
} catch {
dispatch(
addToast({
title: t('gallery.preparingDownloadFailed'),
status: 'error',
})
);
}
}, [t, selection, bulkDownload, dispatch]);
const areAllStarred = useMemo(() => {
return selection.every((img) => img.starred);
}, [selection]);
@ -66,6 +98,11 @@ const MultipleSelectionMenuItems = () => {
{customStarUi ? customStarUi.on.text : `Star All`}
</MenuItem>
)}
{isBulkDownloadEnabled && (
<MenuItem icon={<FaDownload />} onClickCapture={handleBulkDownload}>
{t('gallery.downloadSelection')}
</MenuItem>
)}
<MenuItem icon={<FaFolder />} onClickCapture={handleChangeBoard}>
Change Board
</MenuItem>

View File

@ -7,7 +7,7 @@ export const initialConfigState: AppConfig = {
shouldUpdateImagesOnConnect: false,
shouldFetchMetadataFromApi: false,
disabledTabs: [],
disabledFeatures: ['lightbox', 'faceRestore', 'batches'],
disabledFeatures: ['lightbox', 'faceRestore', 'batches', 'bulkDownload'],
disabledSDFeatures: [
'variation',
'symmetry',

View File

@ -1599,6 +1599,19 @@ export const imagesApi = api.injectEndpoints({
}
},
}),
bulkDownloadImages: build.mutation<
components['schemas']['ImagesDownloaded'],
components['schemas']['Body_download_images_from_list']
>({
query: ({ image_names, board_id }) => ({
url: `images/download`,
method: 'POST',
body: {
image_names,
board_id,
},
}),
}),
}),
});
@ -1623,4 +1636,5 @@ export const {
useStarImagesMutation,
useUnstarImagesMutation,
useGetImageMetadataFromFileQuery,
useBulkDownloadImagesMutation,
} = imagesApi;

File diff suppressed because one or more lines are too long