@@ -4,7 +4,7 @@ import { logger } from 'app/logging/logger';
4
4
import { useAppSelector , useAppStore } from 'app/store/storeHooks' ;
5
5
import { getFocusedRegion , useIsRegionFocused } from 'common/hooks/focus' ;
6
6
import { useRangeBasedImageFetching } from 'features/gallery/hooks/useRangeBasedImageFetching' ;
7
- import type { selectGetImageNamesQueryArgs } from 'features/gallery/store/gallerySelectors' ;
7
+ import type { selectGetImageNamesQueryArgs , selectGetVideoIdsQueryArgs } from 'features/gallery/store/gallerySelectors' ;
8
8
import {
9
9
selectGalleryImageMinimumWidth ,
10
10
selectGalleryView ,
@@ -28,23 +28,30 @@ import type {
28
28
} from 'react-virtuoso' ;
29
29
import { VirtuosoGrid } from 'react-virtuoso' ;
30
30
import { imagesApi , useImageDTO , useStarImagesMutation , useUnstarImagesMutation } from 'services/api/endpoints/images' ;
31
- import { videosApi } from 'services/api/endpoints/videos' ;
31
+ import { useStarVideosMutation , useUnstarVideosMutation , useVideoDTO , videosApi } from 'services/api/endpoints/videos' ;
32
32
import { useDebounce } from 'use-debounce' ;
33
33
34
34
import { GalleryImage , GalleryImagePlaceholder } from './ImageGrid/GalleryImage' ;
35
35
import { GallerySelectionCountTag } from './ImageGrid/GallerySelectionCountTag' ;
36
- import { useGalleryImageNames } from './use-gallery-image-names' ;
37
- import { useGalleryVideoIds } from './use-gallery-video-ids' ;
38
36
import { GalleryVideo } from './ImageGrid/GalleryVideo' ;
37
+ import { useGalleryImageNames , useGalleryVideoIds } from './use-gallery-image-names' ;
39
38
40
39
const log = logger ( 'gallery' ) ;
41
40
42
41
type ListImageNamesQueryArgs = ReturnType < typeof selectGetImageNamesQueryArgs > ;
42
+ type ListVideoIdsQueryArgs = ReturnType < typeof selectGetVideoIdsQueryArgs > ;
43
43
44
- type GridContext = {
45
- queryArgs : ListImageNamesQueryArgs ;
46
- imageNames : string [ ] ;
47
- } ;
44
+ type GridContext =
45
+ | {
46
+ queryArgs : ListImageNamesQueryArgs ;
47
+ galleryView : 'images' | 'assets' ;
48
+ itemIds : string [ ] ;
49
+ }
50
+ | {
51
+ queryArgs : ListVideoIdsQueryArgs ;
52
+ galleryView : 'videos' ;
53
+ itemIds : string [ ] ;
54
+ } ;
48
55
49
56
const ImageAtPosition = memo ( ( { imageName } : { index : number ; imageName : string } ) => {
50
57
/*
@@ -96,8 +103,8 @@ const VideoAtPosition = memo(({ itemId }: { index: number; itemId: string }) =>
96
103
} ) ;
97
104
VideoAtPosition . displayName = 'VideoAtPosition' ;
98
105
99
- const computeItemKey : GridComputeItemKey < string , GridContext > = ( index , imageName , { queryArgs } ) => {
100
- return `${ JSON . stringify ( queryArgs ) } -${ imageName ?? index } ` ;
106
+ const computeItemKey : GridComputeItemKey < string , GridContext > = ( index , id , { queryArgs } ) => {
107
+ return `${ JSON . stringify ( queryArgs ) } -${ id ?? index } ` ;
101
108
} ;
102
109
103
110
/**
@@ -106,7 +113,7 @@ const computeItemKey: GridComputeItemKey<string, GridContext> = (index, imageNam
106
113
* TODO(psyche): We only need to do this when the gallery width changes, or when the galleryImageMinimumWidth value
107
114
* changes. Cache this calculation.
108
115
*/
109
- const getImagesPerRow = ( rootEl : HTMLDivElement ) : number => {
116
+ const getItemsPerRow = ( rootEl : HTMLDivElement ) : number => {
110
117
// Start from root and find virtuoso grid elements
111
118
const gridElement = rootEl . querySelector ( '.virtuoso-grid-list' ) ;
112
119
@@ -140,20 +147,20 @@ const getImagesPerRow = (rootEl: HTMLDivElement): number => {
140
147
*
141
148
* Instead, we use a more robust approach that iteratively calculates how many images fit in the row.
142
149
*/
143
- let imagesPerRow = 0 ;
150
+ let itemsPerRow = 0 ;
144
151
let spaceUsed = 0 ;
145
152
146
153
// Floating point precision can cause imagesPerRow to be 1 too small. Adding 1px to the container size fixes
147
154
// this, without the possibility of accidentally adding an extra column.
148
155
while ( spaceUsed + itemRect . width <= containerRect . width + 1 ) {
149
- imagesPerRow ++ ; // Increment the number of images
156
+ itemsPerRow ++ ; // Increment the number of images
150
157
spaceUsed += itemRect . width ; // Add image size to the used space
151
158
if ( spaceUsed + gap <= containerRect . width ) {
152
159
spaceUsed += gap ; // Add gap size to the used space after each image except after the last image
153
160
}
154
161
}
155
162
156
- return Math . max ( 1 , imagesPerRow ) ;
163
+ return Math . max ( 1 , itemsPerRow ) ;
157
164
} ;
158
165
159
166
/**
@@ -180,9 +187,7 @@ const scrollIntoView = (
180
187
return ;
181
188
}
182
189
183
- const targetItem = rootEl . querySelector (
184
- `.virtuoso-grid-item:has([data-item-id="${ targetItemId } "])`
185
- ) as HTMLElement ;
190
+ const targetItem = rootEl . querySelector ( `.virtuoso-grid-item:has([data-item-id="${ targetItemId } "])` ) as HTMLElement ;
186
191
187
192
if ( ! targetItem ) {
188
193
if ( targetIndex > range . endIndex ) {
@@ -268,19 +273,19 @@ const scrollIntoView = (
268
273
* If the image name is not found, return 0.
269
274
* If no image name is provided, return 0.
270
275
*/
271
- const getImageIndex = ( imageName : string | undefined | null , imageNames : string [ ] ) => {
272
- if ( ! imageName || imageNames . length === 0 ) {
276
+ const getItemIndex = ( targetItemId : string | undefined | null , itemIds : string [ ] ) => {
277
+ if ( ! targetItemId || itemIds . length === 0 ) {
273
278
return 0 ;
274
279
}
275
- const index = imageNames . findIndex ( ( n ) => n === imageName ) ;
280
+ const index = itemIds . findIndex ( ( n ) => n === targetItemId ) ;
276
281
return index >= 0 ? index : 0 ;
277
282
} ;
278
283
279
284
/**
280
285
* Handles keyboard navigation for the gallery.
281
286
*/
282
287
const useKeyboardNavigation = (
283
- imageNames : string [ ] ,
288
+ itemIds : string [ ] ,
284
289
virtuosoRef : React . RefObject < VirtuosoGridHandle > ,
285
290
rootRef : React . RefObject < HTMLDivElement >
286
291
) => {
@@ -308,27 +313,28 @@ const useKeyboardNavigation = (
308
313
return ;
309
314
}
310
315
311
- if ( imageNames . length === 0 ) {
316
+ if ( itemIds . length === 0 ) {
312
317
return ;
313
318
}
314
319
315
- const imagesPerRow = getImagesPerRow ( rootEl ) ;
320
+ const itemsPerRow = getItemsPerRow ( rootEl ) ;
316
321
317
- if ( imagesPerRow === 0 ) {
322
+ if ( itemsPerRow === 0 ) {
318
323
// This can happen if the grid is not yet rendered or has no items
319
324
return ;
320
325
}
321
326
322
327
event . preventDefault ( ) ;
323
328
324
329
const state = getState ( ) ;
325
- const imageName = event . altKey
326
- ? // When the user holds alt, we are changing the image to compare - if no image to compare is currently selected,
327
- // we start from the last selected image
328
- ( selectImageToCompare ( state ) ?? selectLastSelectedImage ( state ) )
329
- : selectLastSelectedImage ( state ) ;
330
+ const imageName =
331
+ event . altKey && selectGalleryView ( state ) !== 'videos'
332
+ ? // When the user holds alt, we are changing the image to compare - if no image to compare is currently selected,
333
+ // we start from the last selected image
334
+ ( selectImageToCompare ( state ) ?? selectLastSelectedImage ( state ) )
335
+ : selectLastSelectedImage ( state ) ;
330
336
331
- const currentIndex = getImageIndex ( imageName , imageNames ) ;
337
+ const currentIndex = getItemIndex ( imageName , itemIds ) ;
332
338
333
339
let newIndex = currentIndex ;
334
340
@@ -342,7 +348,7 @@ const useKeyboardNavigation = (
342
348
}
343
349
break ;
344
350
case 'ArrowRight' :
345
- if ( currentIndex < imageNames . length - 1 ) {
351
+ if ( currentIndex < itemIds . length - 1 ) {
346
352
newIndex = currentIndex + 1 ;
347
353
// } else {
348
354
// // Wrap to first image
@@ -351,34 +357,34 @@ const useKeyboardNavigation = (
351
357
break ;
352
358
case 'ArrowUp' :
353
359
// If on first row, stay on current image
354
- if ( currentIndex < imagesPerRow ) {
360
+ if ( currentIndex < itemsPerRow ) {
355
361
newIndex = currentIndex ;
356
362
} else {
357
- newIndex = Math . max ( 0 , currentIndex - imagesPerRow ) ;
363
+ newIndex = Math . max ( 0 , currentIndex - itemsPerRow ) ;
358
364
}
359
365
break ;
360
366
case 'ArrowDown' :
361
367
// If no images below, stay on current image
362
- if ( currentIndex >= imageNames . length - imagesPerRow ) {
368
+ if ( currentIndex >= itemIds . length - itemsPerRow ) {
363
369
newIndex = currentIndex ;
364
370
} else {
365
- newIndex = Math . min ( imageNames . length - 1 , currentIndex + imagesPerRow ) ;
371
+ newIndex = Math . min ( itemIds . length - 1 , currentIndex + itemsPerRow ) ;
366
372
}
367
373
break ;
368
374
}
369
375
370
- if ( newIndex !== currentIndex && newIndex >= 0 && newIndex < imageNames . length ) {
371
- const newImageName = imageNames [ newIndex ] ;
376
+ if ( newIndex !== currentIndex && newIndex >= 0 && newIndex < itemIds . length ) {
377
+ const newImageName = itemIds [ newIndex ] ;
372
378
if ( newImageName ) {
373
- if ( event . altKey ) {
379
+ if ( selectGalleryView ( state ) !== 'videos' && event . altKey ) {
374
380
dispatch ( imageToCompareChanged ( newImageName ) ) ;
375
381
} else {
376
382
dispatch ( selectionChanged ( [ newImageName ] ) ) ;
377
383
}
378
384
}
379
385
}
380
386
} ,
381
- [ rootRef , virtuosoRef , imageNames , getState , dispatch ]
387
+ [ rootRef , virtuosoRef , itemIds , getState , dispatch ]
382
388
) ;
383
389
384
390
useRegisteredHotkeys ( {
@@ -451,28 +457,28 @@ const useKeyboardNavigation = (
451
457
* This is useful for keyboard navigation and ensuring the user can see their selection.
452
458
* It only tracks the last selected image, not the image to compare.
453
459
*/
454
- const useKeepSelectedImageInView = (
455
- imageNames : string [ ] ,
460
+ const useKeepSelectedItemInView = (
461
+ itemIds : string [ ] ,
456
462
virtuosoRef : React . RefObject < VirtuosoGridHandle > ,
457
463
rootRef : React . RefObject < HTMLDivElement > ,
458
464
rangeRef : MutableRefObject < ListRange >
459
465
) => {
460
466
const selection = useAppSelector ( selectSelection ) ;
461
467
462
468
useEffect ( ( ) => {
463
- const targetImageName = selection . at ( - 1 ) ;
469
+ const targetItemId = selection . at ( - 1 ) ;
464
470
const virtuosoGridHandle = virtuosoRef . current ;
465
471
const rootEl = rootRef . current ;
466
472
const range = rangeRef . current ;
467
473
468
- if ( ! virtuosoGridHandle || ! rootEl || ! targetImageName || ! imageNames || imageNames . length === 0 ) {
474
+ if ( ! virtuosoGridHandle || ! rootEl || ! targetItemId || ! itemIds || itemIds . length === 0 ) {
469
475
return ;
470
476
}
471
477
472
478
setTimeout ( ( ) => {
473
- scrollIntoView ( targetImageName , imageNames , rootEl , virtuosoGridHandle , range ) ;
479
+ scrollIntoView ( targetItemId , itemIds , rootEl , virtuosoGridHandle , range ) ;
474
480
} , 0 ) ;
475
- } , [ imageNames , rangeRef , rootRef , virtuosoRef , selection ] ) ;
481
+ } , [ itemIds , rangeRef , rootRef , virtuosoRef , selection ] ) ;
476
482
} ;
477
483
478
484
/**
@@ -523,30 +529,45 @@ const useScrollableGallery = (rootRef: RefObject<HTMLDivElement>) => {
523
529
const useStarImageHotkey = ( ) => {
524
530
const lastSelectedImage = useAppSelector ( selectLastSelectedImage ) ;
525
531
const selectionCount = useAppSelector ( selectSelectionCount ) ;
532
+ const galleryView = useAppSelector ( selectGalleryView ) ;
526
533
const isGalleryFocused = useIsRegionFocused ( 'gallery' ) ;
527
- const imageDTO = useImageDTO ( lastSelectedImage ) ;
534
+ const imageDTO = useImageDTO ( galleryView !== 'videos' ? lastSelectedImage : null ) ;
535
+ const videoDTO = useVideoDTO ( galleryView === 'videos' ? lastSelectedImage : null ) ;
528
536
const [ starImages ] = useStarImagesMutation ( ) ;
529
537
const [ unstarImages ] = useUnstarImagesMutation ( ) ;
530
538
539
+ const [ starVideos ] = useStarVideosMutation ( ) ;
540
+ const [ unstarVideos ] = useUnstarVideosMutation ( ) ;
541
+
531
542
const handleStarHotkey = useCallback ( ( ) => {
532
- if ( ! imageDTO ) {
533
- return ;
534
- }
535
543
if ( ! isGalleryFocused ) {
536
544
return ;
537
545
}
538
- if ( imageDTO . starred ) {
539
- unstarImages ( { image_names : [ imageDTO . image_name ] } ) ;
540
- } else {
541
- starImages ( { image_names : [ imageDTO . image_name ] } ) ;
546
+ if ( galleryView === 'videos' && videoDTO ) {
547
+ if ( videoDTO . starred ) {
548
+ unstarVideos ( { video_ids : [ videoDTO . video_id ] } ) ;
549
+ } else {
550
+ starVideos ( { video_ids : [ videoDTO . video_id ] } ) ;
551
+ }
552
+ } else if ( galleryView !== 'videos' && imageDTO ) {
553
+ if ( imageDTO . starred ) {
554
+ unstarImages ( { image_names : [ imageDTO . image_name ] } ) ;
555
+ } else {
556
+ starImages ( { image_names : [ imageDTO . image_name ] } ) ;
557
+ }
542
558
}
543
559
} , [ imageDTO , isGalleryFocused , starImages , unstarImages ] ) ;
544
560
545
561
useRegisteredHotkeys ( {
546
562
id : 'starImage' ,
547
563
category : 'gallery' ,
548
564
callback : handleStarHotkey ,
549
- options : { enabled : ! ! imageDTO && selectionCount === 1 && isGalleryFocused } ,
565
+ options : {
566
+ enabled :
567
+ ( ( galleryView === 'videos' && ! ! videoDTO ) || ( galleryView !== 'videos' && ! ! imageDTO ) ) &&
568
+ selectionCount === 1 &&
569
+ isGalleryFocused ,
570
+ } ,
550
571
dependencies : [ imageDTO , selectionCount , isGalleryFocused , handleStarHotkey ] ,
551
572
} ) ;
552
573
} ;
@@ -558,18 +579,22 @@ export const NewGallery = memo(() => {
558
579
const galleryView = useAppSelector ( selectGalleryView ) ;
559
580
560
581
// Get the ordered list of image names - this is our primary data source for virtualization
561
- const { queryArgs , imageNames , isLoading } = useGalleryImageNames ( ) ;
562
- const { queryArgs : videoQueryArgs , videoIds , isLoading : isLoadingVideos } = useGalleryVideoIds ( ) ;
582
+ const galleryImageNamesQuery = useGalleryImageNames ( ) ;
583
+ const galleryVideoIdsQuery = useGalleryVideoIds ( ) ;
563
584
564
585
// Use range-based fetching for bulk loading image DTOs into cache based on the visible range
565
586
const { onRangeChanged } = useRangeBasedImageFetching ( {
566
- imageNames,
567
- enabled : ! isLoading ,
587
+ imageNames : galleryImageNamesQuery . imageNames ,
588
+ enabled : ! galleryImageNamesQuery . isLoading ,
568
589
} ) ;
569
590
591
+ const itemIds = galleryView === 'videos' ? galleryVideoIdsQuery . video_ids : galleryImageNamesQuery . imageNames ;
592
+ const queryArgs = galleryView === 'videos' ? galleryVideoIdsQuery . queryArgs : galleryImageNamesQuery . queryArgs ;
593
+ const isLoading = galleryView === 'videos' ? galleryVideoIdsQuery . isLoading : galleryImageNamesQuery . isLoading ;
570
594
useStarImageHotkey ( ) ;
571
- useKeepSelectedImageInView ( imageNames , virtuosoRef , rootRef , rangeRef ) ;
572
- useKeyboardNavigation ( imageNames , virtuosoRef , rootRef ) ;
595
+
596
+ useKeepSelectedItemInView ( itemIds , virtuosoRef , rootRef , rangeRef ) ;
597
+ useKeyboardNavigation ( itemIds , virtuosoRef , rootRef ) ;
573
598
const scrollerRef = useScrollableGallery ( rootRef ) ;
574
599
575
600
/*
@@ -584,7 +609,7 @@ export const NewGallery = memo(() => {
584
609
[ onRangeChanged ]
585
610
) ;
586
611
587
- const context = useMemo < GridContext > ( ( ) => ( { imageNames , queryArgs , videoIds , videoQueryArgs } ) , [ imageNames , queryArgs , videoIds , videoQueryArgs ] ) ;
612
+ const context = useMemo < GridContext > ( ( ) => ( { itemIds , galleryView , queryArgs } ) , [ itemIds , queryArgs , galleryView ] ) ;
588
613
589
614
if ( isLoading ) {
590
615
return (
@@ -595,7 +620,7 @@ export const NewGallery = memo(() => {
595
620
) ;
596
621
}
597
622
598
- if ( imageNames . length === 0 ) {
623
+ if ( itemIds . length === 0 ) {
599
624
return (
600
625
< Flex w = "full" h = "full" alignItems = "center" justifyContent = "center" >
601
626
< Text color = "base.300" > No images found</ Text >
@@ -609,7 +634,7 @@ export const NewGallery = memo(() => {
609
634
< VirtuosoGrid < string , GridContext >
610
635
ref = { virtuosoRef }
611
636
context = { context }
612
- data = { galleryView === 'images' ? imageNames : videoIds }
637
+ data = { itemIds }
613
638
increaseViewportBy = { 4096 }
614
639
itemContent = { itemContent }
615
640
computeItemKey = { computeItemKey }
@@ -652,8 +677,12 @@ const ListComponent: GridComponents<GridContext>['List'] = forwardRef(({ context
652
677
} ) ;
653
678
ListComponent . displayName = 'ListComponent' ;
654
679
655
- const itemContent : GridItemContent < string , GridContext > = ( index , imageName ) => {
656
- return < ImageAtPosition index = { index } imageName = { imageName } /> ;
680
+ const itemContent : GridItemContent < string , GridContext > = ( index , itemId , { galleryView } ) => {
681
+ if ( galleryView === 'videos' ) {
682
+ return < VideoAtPosition index = { index } itemId = { itemId } /> ;
683
+ } else {
684
+ return < ImageAtPosition index = { index } imageName = { itemId } /> ;
685
+ }
657
686
} ;
658
687
659
688
const ItemComponent : GridComponents < GridContext > [ 'Item' ] = forwardRef ( ( { context : _ , ...rest } , ref ) => (
0 commit comments