1- import { Divider , Grid , Paper , Typography } from '@mui/material' ;
2- import { useInfiniteQuery } from '@tanstack/react-query' ;
1+ import CloseIcon from '@mui/icons-material/Close' ;
2+ import DeleteIcon from '@mui/icons-material/Delete' ;
3+ import DownloadIcon from '@mui/icons-material/Download' ;
4+ import {
5+ Button ,
6+ Dialog ,
7+ DialogActions ,
8+ DialogTitle ,
9+ Divider ,
10+ Grid ,
11+ Paper ,
12+ Tooltip ,
13+ Typography ,
14+ } from '@mui/material' ;
15+ import AppBar from '@mui/material/AppBar' ;
16+ import Box from '@mui/material/Box' ;
17+ import IconButton from '@mui/material/IconButton' ;
18+ import Slide from '@mui/material/Slide' ;
19+ import { useTheme } from '@mui/material/styles' ;
20+ import Toolbar from '@mui/material/Toolbar' ;
21+ import {
22+ useInfiniteQuery ,
23+ useMutation ,
24+ useQueryClient ,
25+ } from '@tanstack/react-query' ;
326import React , { useEffect , useState } from 'react' ;
27+ import toast from 'react-hot-toast' ;
428import { useInView } from 'react-intersection-observer' ;
529import { IThumbnail } from 'types/types' ;
630
7- import { fetchImageIds } from '../api/api' ;
31+ import { fetchImageIds , trashObjects } from '../api/api' ;
832import ImageThumbnail from './ImageThumbnail' ;
933import Loading from './Loading' ;
1034import Preview from './Preview' ;
35+ import SelectImageButton from './SelectImageButton' ;
1136
1237export const ImageGallery : React . FC = ( ) => {
1338 const [ previewOpen , setPreviewOpen ] = useState ( false ) ;
1439 const [ currentImage , setCurrentImage ] = useState < number | null > ( null ) ;
40+ const [ selectedImages , setSelectedImages ] = useState < string [ ] > ( [ ] ) ; // <-- add this
1541 const { ref, inView } = useInView ( ) ;
1642
43+ const queryClient = useQueryClient ( ) ;
44+
1745 const { data, fetchNextPage, hasNextPage, isLoading } = useInfiniteQuery ( {
1846 queryKey : [ 'fetchIds' ] ,
1947 queryFn : fetchImageIds ,
2048 initialPageParam : '' ,
2149 getNextPageParam : ( lastPage ) => lastPage . lastId || null ,
2250 } ) ;
2351
52+ const trashObjectMutation = useMutation ( {
53+ mutationFn : trashObjects ,
54+ onSuccess : ( ) => {
55+ toast . success ( 'Object(s) trashed successfully.' ) ;
56+ } ,
57+ onError : ( error ) => {
58+ toast . error ( `Something went wrong: ${ error . message } ` ) ;
59+ } ,
60+ } ) ;
61+
2462 const lastId = data ?. pages . slice ( - 1 ) [ 0 ] . lastId ;
2563 const imageIds = data ?. pages . map ( ( page ) => page . properties ) . flat ( ) ;
2664 const lastImage = data ?. pages . slice ( - 1 ) [ 0 ] . properties ?. slice ( - 1 ) [ 0 ] . id ;
@@ -65,8 +103,125 @@ export const ImageGallery: React.FC = () => {
65103 setCurrentImage ( ( prev ) => ( prev ? prev - 1 : 0 ) ) ;
66104 } ;
67105
106+ const toggleSelectImage = ( id : string ) => {
107+ setSelectedImages ( ( prev ) =>
108+ prev . includes ( id ) ? prev . filter ( ( imgId ) => imgId !== id ) : [ ...prev , id ]
109+ ) ;
110+ } ;
111+
112+ const handleClearSelection = ( ) => setSelectedImages ( [ ] ) ;
113+ const handleDelete = ( ) => {
114+ trashObjectMutation . mutate (
115+ { objectIds : selectedImages } ,
116+ {
117+ onSuccess : ( ) => {
118+ handleClearSelection ( ) ;
119+ queryClient . invalidateQueries ( { queryKey : [ 'fetchIds' ] } ) ; // <-- refresh the gallery
120+ } ,
121+ }
122+ ) ;
123+ } ;
124+ const handleDownload = ( ) => {
125+ // TODO: Implement download logic for selectedImages
126+ } ;
127+
128+ const theme = useTheme ( ) ;
129+
130+ const [ openDeleteDialog , setOpenDeleteDialog ] = useState ( false ) ;
131+
68132 return (
69133 < >
134+ { /* Selection Toolbar */ }
135+ < Slide
136+ direction = "down"
137+ in = { selectedImages . length > 0 }
138+ mountOnEnter
139+ unmountOnExit
140+ >
141+ < AppBar
142+ position = "fixed"
143+ color = "default"
144+ elevation = { 2 }
145+ sx = { {
146+ top : 0 ,
147+ left : 0 ,
148+ right : 0 ,
149+ background : theme . palette . background . paper ,
150+ borderBottom : '1px solid #eee' ,
151+ zIndex : theme . zIndex . drawer + 2 ,
152+ } }
153+ >
154+ < Toolbar >
155+ < IconButton
156+ edge = "start"
157+ color = "inherit"
158+ onClick = { handleClearSelection }
159+ >
160+ < CloseIcon />
161+ </ IconButton >
162+ < Typography sx = { { flexGrow : 1 , fontWeight : 600 } } >
163+ { selectedImages . length } selected
164+ </ Typography >
165+ < Tooltip title = "Download" >
166+ < IconButton color = "inherit" onClick = { handleDownload } >
167+ < DownloadIcon />
168+ </ IconButton >
169+ </ Tooltip >
170+ < Tooltip title = "Delete" >
171+ < IconButton
172+ color = "inherit"
173+ onClick = { ( ) => setOpenDeleteDialog ( true ) }
174+ >
175+ < DeleteIcon />
176+ </ IconButton >
177+ </ Tooltip >
178+ < Dialog
179+ open = { openDeleteDialog }
180+ onClose = { ( ) => setOpenDeleteDialog ( false ) }
181+ aria-labelledby = "delete-dialog-title"
182+ aria-describedby = "delete-dialog-description"
183+ >
184+ < DialogTitle
185+ id = "delete-dialog-title"
186+ sx = { { display : 'flex' , alignItems : 'center' , gap : 1 } }
187+ >
188+ < DeleteIcon color = "error" sx = { { fontSize : 32 } } />
189+ Delete { selectedImages . length } { ' ' }
190+ { selectedImages . length === 1 ? 'item' : 'items' } ?
191+ </ DialogTitle >
192+ < Typography
193+ id = "delete-dialog-description"
194+ sx = { { px : 3 , pb : 1 , color : 'text.secondary' } }
195+ >
196+ This action will move the selected{ ' ' }
197+ { selectedImages . length === 1 ? 'item' : 'items' } to trash. You
198+ can restore them from trash later.
199+ </ Typography >
200+ < DialogActions >
201+ < Button
202+ onClick = { ( ) => setOpenDeleteDialog ( false ) }
203+ variant = "outlined"
204+ >
205+ Cancel
206+ </ Button >
207+ < Button
208+ onClick = { ( ) => {
209+ handleDelete ( ) ;
210+ setOpenDeleteDialog ( false ) ;
211+ } }
212+ color = "error"
213+ variant = "contained"
214+ >
215+ Move to Trash
216+ </ Button >
217+ </ DialogActions >
218+ </ Dialog >
219+ </ Toolbar >
220+ </ AppBar >
221+ </ Slide >
222+
223+ { selectedImages . length > 0 && < Toolbar /> }
224+
70225 < Divider sx = { { mb : 2 } } />
71226 < Typography
72227 color = "text.primary"
@@ -82,9 +237,35 @@ export const ImageGallery: React.FC = () => {
82237 . map ( ( page ) => page . properties )
83238 . flat ( )
84239 . map ( ( thumbnail : IThumbnail , index ) => (
85- < Grid item xs = { 1 } key = { thumbnail . id } >
86- < Paper elevation = { 0 } onClick = { ( ) => openPreview ( index ) } >
87- < ImageThumbnail id = { thumbnail . id } mediaType = { thumbnail . mediaType } />
240+ < Grid
241+ item
242+ xs = { 1 }
243+ key = { thumbnail . id }
244+ sx = { { position : 'relative' } }
245+ >
246+ < Paper elevation = { 0 } >
247+ < SelectImageButton
248+ selected = { selectedImages . includes ( thumbnail . id ) }
249+ onClick = { ( e ) => {
250+ e . stopPropagation ( ) ;
251+ toggleSelectImage ( thumbnail . id ) ;
252+ } }
253+ />
254+ < div onClick = { ( ) => openPreview ( index ) } >
255+ { selectedImages . includes ( thumbnail . id ) ? (
256+ < Box component = "section" sx = { { p : 1.5 } } >
257+ < ImageThumbnail
258+ id = { thumbnail . id }
259+ mediaType = { thumbnail . mediaType }
260+ />
261+ </ Box >
262+ ) : (
263+ < ImageThumbnail
264+ id = { thumbnail . id }
265+ mediaType = { thumbnail . mediaType }
266+ />
267+ ) }
268+ </ div >
88269 </ Paper >
89270 </ Grid >
90271 ) ) }
0 commit comments