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) return ImagesUpdatedFromListResult(updated_image_names=updated_image_names)
except Exception: except Exception:
raise HTTPException(status_code=500, detail="Failed to unstar images") 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...", "searchBoard": "Search Boards...",
"selectBoard": "Select a Board", "selectBoard": "Select a Board",
"topMessage": "This board contains images used in the following features:", "topMessage": "This board contains images used in the following features:",
"uncategorized": "Uncategorized" "uncategorized": "Uncategorized",
"downloadBoard": "Download Board"
}, },
"common": { "common": {
"accept": "Accept", "accept": "Accept",
@ -323,7 +324,10 @@
"showUploads": "Show Uploads", "showUploads": "Show Uploads",
"singleColumnLayout": "Single Column Layout", "singleColumnLayout": "Single Column Layout",
"unableToLoad": "Unable to load Gallery", "unableToLoad": "Unable to load Gallery",
"uploads": "Uploads" "uploads": "Uploads",
"downloadSelection": "Download Selection",
"preparingDownload": "Preparing Download",
"preparingDownloadFailed": "Problem Preparing Download"
}, },
"hotkeys": { "hotkeys": {
"acceptStagingImage": { "acceptStagingImage": {

View File

@ -22,7 +22,8 @@ export type AppFeature =
| 'pauseQueue' | 'pauseQueue'
| 'resumeQueue' | 'resumeQueue'
| 'prependQueue' | 'prependQueue'
| 'invocationCache'; | 'invocationCache'
| 'bulkDownload';
/** /**
* A disable-able Stable Diffusion feature * 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 { BoardId } from 'features/gallery/store/types';
import { MouseEvent, memo, useCallback, useMemo } from 'react'; import { MouseEvent, memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; 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 { useBoardName } from 'services/api/hooks/useBoardName';
import { BoardDTO } from 'services/api/types'; import { BoardDTO } from 'services/api/types';
import { menuListMotionProps } from 'theme/components/menu'; import { menuListMotionProps } from 'theme/components/menu';
import GalleryBoardContextMenuItems from './GalleryBoardContextMenuItems'; import GalleryBoardContextMenuItems from './GalleryBoardContextMenuItems';
import NoBoardContextMenuItems from './NoBoardContextMenuItems'; 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 = { type Props = {
board?: BoardDTO; board?: BoardDTO;
@ -31,6 +34,7 @@ const BoardContextMenu = ({
setBoardToDelete, setBoardToDelete,
children, children,
}: Props) => { }: Props) => {
const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const selector = useMemo( const selector = useMemo(
@ -49,17 +53,43 @@ const BoardContextMenu = ({
const { isAutoAdd, autoAssignBoardOnClick } = useAppSelector(selector); const { isAutoAdd, autoAssignBoardOnClick } = useAppSelector(selector);
const boardName = useBoardName(board_id); const boardName = useBoardName(board_id);
const isBulkDownloadEnabled =
useFeatureStatus('bulkDownload').isFeatureEnabled;
const [bulkDownload] = useBulkDownloadImagesMutation();
const handleSetAutoAdd = useCallback(() => { const handleSetAutoAdd = useCallback(() => {
dispatch(autoAddBoardIdChanged(board_id)); dispatch(autoAddBoardIdChanged(board_id));
}, [board_id, dispatch]); }, [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>) => { const skipEvent = useCallback((e: MouseEvent<HTMLDivElement>) => {
e.preventDefault(); e.preventDefault();
}, []); }, []);
const { t } = useTranslation();
return ( return (
<IAIContextMenu<HTMLDivElement> <IAIContextMenu<HTMLDivElement>
menuProps={{ size: 'sm', isLazy: true }} menuProps={{ size: 'sm', isLazy: true }}
@ -81,6 +111,14 @@ const BoardContextMenu = ({
> >
{t('boards.menuItemAutoAdd')} {t('boards.menuItemAutoAdd')}
</MenuItem> </MenuItem>
{isBulkDownloadEnabled && (
<MenuItem
icon={<FaDownload />}
onClickCapture={handleBulkDownload}
>
{t('boards.downloadBoard')}
</MenuItem>
)}
{!board && <NoBoardContextMenuItems />} {!board && <NoBoardContextMenuItems />}
{board && ( {board && (
<GalleryBoardContextMenuItems <GalleryBoardContextMenuItems

View File

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

View File

@ -7,7 +7,7 @@ export const initialConfigState: AppConfig = {
shouldUpdateImagesOnConnect: false, shouldUpdateImagesOnConnect: false,
shouldFetchMetadataFromApi: false, shouldFetchMetadataFromApi: false,
disabledTabs: [], disabledTabs: [],
disabledFeatures: ['lightbox', 'faceRestore', 'batches'], disabledFeatures: ['lightbox', 'faceRestore', 'batches', 'bulkDownload'],
disabledSDFeatures: [ disabledSDFeatures: [
'variation', 'variation',
'symmetry', '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, useStarImagesMutation,
useUnstarImagesMutation, useUnstarImagesMutation,
useGetImageMetadataFromFileQuery, useGetImageMetadataFromFileQuery,
useBulkDownloadImagesMutation,
} = imagesApi; } = imagesApi;

File diff suppressed because one or more lines are too long