import React, { useCallback, useState, useMemo, MouseEvent } from "react"; import { FormattedNumber, useIntl } from "react-intl"; import cloneDeep from "lodash-es/cloneDeep"; import { useHistory } from "react-router-dom"; import Mousetrap from "mousetrap"; import * as GQL from "src/core/generated-graphql"; import { queryFindImages, useFindImages } from "src/core/StashService"; import { makeItemList, IItemListOperation, PersistanceLevel, showWhenSelected, } from "../List/ItemList"; import { useLightbox } from "src/hooks/Lightbox/hooks"; import { ListFilterModel } from "src/models/list-filter/filter"; import { DisplayMode } from "src/models/list-filter/types"; import { ImageCard } from "./ImageCard"; import { EditImagesDialog } from "./EditImagesDialog"; import { DeleteImagesDialog } from "./DeleteImagesDialog"; import "flexbin/flexbin.css"; import { ExportDialog } from "../Shared/ExportDialog"; import { objectTitle } from "src/core/files"; import TextUtils from "src/utils/text"; interface IImageWallProps { images: GQL.SlimImageDataFragment[]; onChangePage: (page: number) => void; currentPage: number; pageCount: number; handleImageOpen: (index: number) => void; } const ImageWall: React.FC = ({ images, handleImageOpen }) => { const thumbs = images.map((image, index) => (
handleImageOpen(index)} onKeyPress={() => handleImageOpen(index)} > {objectTitle(image)}
)); return (
{thumbs}
); }; interface IImageListImages { images: GQL.SlimImageDataFragment[]; filter: ListFilterModel; selectedIds: Set; onChangePage: (page: number) => void; pageCount: number; onSelectChange: (id: string, selected: boolean, shiftKey: boolean) => void; slideshowRunning: boolean; setSlideshowRunning: (running: boolean) => void; } const ImageListImages: React.FC = ({ images, filter, selectedIds, onChangePage, pageCount, onSelectChange, slideshowRunning, setSlideshowRunning, }) => { const handleLightBoxPage = useCallback( (direction: number) => { if (direction === -1) { if (filter.currentPage === 1) { onChangePage(pageCount); } else { onChangePage(filter.currentPage - 1); } } else if (direction === 1) { if (filter.currentPage === pageCount) { // return to the first page onChangePage(1); } else { onChangePage(filter.currentPage + 1); } } }, [onChangePage, filter.currentPage, pageCount] ); const handleClose = useCallback(() => { setSlideshowRunning(false); }, [setSlideshowRunning]); const lightboxState = useMemo(() => { return { images, showNavigation: false, pageCallback: pageCount > 1 ? handleLightBoxPage : undefined, pageHeader: `Page ${filter.currentPage} / ${pageCount}`, slideshowEnabled: slideshowRunning, onClose: handleClose, }; }, [ images, pageCount, filter.currentPage, slideshowRunning, handleClose, handleLightBoxPage, ]); const showLightbox = useLightbox(lightboxState); const handleImageOpen = useCallback( (index) => { setSlideshowRunning(true); showLightbox(index, true); }, [showLightbox, setSlideshowRunning] ); function onPreview(index: number, ev: MouseEvent) { handleImageOpen(index); ev.preventDefault(); } function renderImageCard( index: number, image: GQL.SlimImageDataFragment, zoomIndex: number ) { return ( 0} selected={selectedIds.has(image.id)} onSelectedChanged={(selected: boolean, shiftKey: boolean) => onSelectChange(image.id, selected, shiftKey) } onPreview={ selectedIds.size < 1 ? (ev) => onPreview(index, ev) : undefined } /> ); } if (filter.displayMode === DisplayMode.Grid) { return (
{images.map((image, index) => renderImageCard(index, image, filter.zoomIndex) )}
); } if (filter.displayMode === DisplayMode.Wall) { return ( ); } // should not happen return <>; }; const ImageItemList = makeItemList({ filterMode: GQL.FilterMode.Images, useResult: useFindImages, getItems(result: GQL.FindImagesQueryResult) { return result?.data?.findImages?.images ?? []; }, getCount(result: GQL.FindImagesQueryResult) { return result?.data?.findImages?.count ?? 0; }, renderMetadataByline(result: GQL.FindImagesQueryResult) { const megapixels = result?.data?.findImages?.megapixels; const size = result?.data?.findImages?.filesize; const filesize = size ? TextUtils.fileSize(size) : undefined; if (!megapixels && !size) { return; } const separator = megapixels && size ? " - " : ""; return (  ( {megapixels ? ( Megapixels ) : undefined} {separator} {size && filesize ? ( {` ${TextUtils.formatFileSizeUnit(filesize.unit)}`} ) : undefined} ) ); }, }); interface IImageList { filterHook?: (filter: ListFilterModel) => ListFilterModel; persistState?: PersistanceLevel; persistanceKey?: string; alterQuery?: boolean; extraOperations?: IItemListOperation[]; } export const ImageList: React.FC = ({ filterHook, persistState, persistanceKey, alterQuery, extraOperations, }) => { const intl = useIntl(); const history = useHistory(); const [isExportDialogOpen, setIsExportDialogOpen] = useState(false); const [isExportAll, setIsExportAll] = useState(false); const [slideshowRunning, setSlideshowRunning] = useState(false); const otherOperations = [ ...(extraOperations ?? []), { text: intl.formatMessage({ id: "actions.view_random" }), onClick: viewRandom, }, { text: intl.formatMessage({ id: "actions.export" }), onClick: onExport, isDisplayed: showWhenSelected, }, { text: intl.formatMessage({ id: "actions.export_all" }), onClick: onExportAll, }, ]; function addKeybinds( result: GQL.FindImagesQueryResult, filter: ListFilterModel ) { Mousetrap.bind("p r", () => { viewRandom(result, filter); }); return () => { Mousetrap.unbind("p r"); }; } async function viewRandom( result: GQL.FindImagesQueryResult, filter: ListFilterModel ) { // query for a random image if (result.data?.findImages) { const { count } = result.data.findImages; const index = Math.floor(Math.random() * count); const filterCopy = cloneDeep(filter); filterCopy.itemsPerPage = 1; filterCopy.currentPage = index + 1; const singleResult = await queryFindImages(filterCopy); if (singleResult.data.findImages.images.length === 1) { const { id } = singleResult.data.findImages.images[0]; // navigate to the image player page history.push(`/images/${id}`); } } } async function onExport() { setIsExportAll(false); setIsExportDialogOpen(true); } async function onExportAll() { setIsExportAll(true); setIsExportDialogOpen(true); } function renderContent( result: GQL.FindImagesQueryResult, filter: ListFilterModel, selectedIds: Set, onSelectChange: (id: string, selected: boolean, shiftKey: boolean) => void, onChangePage: (page: number) => void, pageCount: number ) { function maybeRenderImageExportDialog() { if (isExportDialogOpen) { return ( setIsExportDialogOpen(false)} /> ); } } function renderImages() { if (!result.data?.findImages) return; return ( ); } return ( <> {maybeRenderImageExportDialog()} {renderImages()} ); } function renderEditDialog( selectedImages: GQL.SlimImageDataFragment[], onClose: (applied: boolean) => void ) { return ; } function renderDeleteDialog( selectedImages: GQL.SlimImageDataFragment[], onClose: (confirmed: boolean) => void ) { return ; } return ( ); };