1
1
import "yet-another-react-lightbox/styles.css" ;
2
2
import { listAlbumAssets } from "@/handlers/api/album.handler" ;
3
3
import { useConfig } from "@/contexts/ConfigContext" ;
4
- import { IAlbum } from "@/types/album" ;
4
+ import { IAlbum , IAlbumPerson } from "@/types/album" ;
5
5
import { IAsset } from "@/types/asset" ;
6
- import React , { useEffect , useMemo , useRef , useState } from "react" ;
6
+ import React , { useEffect , useMemo , useRef , useState , MouseEvent } from "react" ;
7
7
import { Hourglass } from "lucide-react" ;
8
8
import { useRouter } from "next/router" ;
9
9
import { Button } from "@/components/ui/button" ;
10
- import AssetGrid from "@/components/shared/AssetGrid" ;
11
- import PhotoSelectionContext , {
12
- IPhotoSelectionContext ,
13
- } from "@/contexts/PhotoSelectionContext" ;
10
+ import { Gallery } from "react-grid-gallery" ;
11
+ import Lightbox from "yet-another-react-lightbox" ;
12
+ import Captions from "yet-another-react-lightbox/plugins/captions" ;
13
+ import LazyGridImage from "@/components/ui/lazy-grid-image" ;
14
+ import { usePhotoSelectionContext } from "@/contexts/PhotoSelectionContext" ;
15
+ import { PERSON_THUBNAIL_PATH } from "@/config/routes" ;
16
+ import Image from "next/image" ;
14
17
15
18
interface AlbumImagesProps {
16
19
album : IAlbum ;
20
+ selectedPerson : IAlbumPerson | null ;
17
21
}
18
22
19
23
interface IAssetFilter {
20
24
faceId ?: string ;
21
25
page : number ;
22
26
}
23
- export default function AlbumImages ( { album } : AlbumImagesProps ) {
27
+
28
+ export default function AlbumImages ( { album, selectedPerson } : AlbumImagesProps ) {
24
29
const { exImmichUrl } = useConfig ( ) ;
25
30
const router = useRouter ( ) ;
26
31
const { faceId } = router . query as { faceId : string } ;
27
- const [ assets , setAssets ] = useState < IAsset [ ] > ( [ ] ) ;
32
+
33
+ // Use context from parent instead of managing own
34
+ const { selectedIds, updateContext, assets } = usePhotoSelectionContext ( ) ;
35
+
28
36
const [ loading , setLoading ] = useState ( false ) ;
29
37
const [ errorMessage , setErrorMessage ] = useState < string | null > ( null ) ;
30
38
const [ filters , setFilters ] = useState < IAssetFilter > ( {
31
39
faceId,
32
40
page : 1 ,
33
41
} ) ;
34
42
const [ hasMore , setHasMore ] = useState ( true ) ;
35
-
36
- // Initialize context state
37
- const [ contextState , setContextState ] = useState < IPhotoSelectionContext > ( {
38
- selectedIds : [ ] ,
39
- assets : [ ] ,
40
- config : { } ,
41
- updateContext : ( newConfig : Partial < IPhotoSelectionContext > ) => {
42
- setContextState ( ( prevState ) => ( {
43
- ...prevState ,
44
- ...newConfig ,
45
- // No deep merge needed for config here as it's simple
46
- config : newConfig . config
47
- ? { ...prevState . config , ...newConfig . config }
48
- : prevState . config ,
49
- } ) ) ;
50
- } ,
51
- } ) ;
43
+ const [ index , setIndex ] = useState ( - 1 ) ;
44
+ const [ lastSelectedIndex , setLastSelectedIndex ] = useState ( - 1 ) ;
52
45
53
46
const fetchAssets = async ( ) => {
54
47
setLoading ( true ) ;
55
48
return listAlbumAssets ( album . id , filters )
56
49
. then ( ( newAssets ) => {
57
50
if ( filters . page === 1 ) {
58
- setAssets ( newAssets ) ;
51
+ updateContext ( { assets : newAssets } ) ;
59
52
} else {
60
- setAssets ( ( prevAssets ) => [ ...prevAssets , ...newAssets ] ) ;
53
+ updateContext ( { assets : [ ...assets , ...newAssets ] } ) ;
61
54
}
62
55
setHasMore ( newAssets . length === 100 ) ;
63
56
} )
64
57
. catch ( setErrorMessage )
65
58
. finally ( ( ) => setLoading ( false ) ) ;
66
59
} ;
67
60
61
+ const images = useMemo ( ( ) => {
62
+ return assets . map ( ( p ) => ( {
63
+ ...p ,
64
+ src : p . url as string ,
65
+ original : p . previewUrl as string ,
66
+ width : p . exifImageWidth as number ,
67
+ height : p . exifImageHeight as number ,
68
+ isSelected : selectedIds . includes ( p . id ) ,
69
+ orientation : 1 ,
70
+ tags : [
71
+ {
72
+ title : "Immich Link" ,
73
+ value : (
74
+ < a href = { exImmichUrl + "/photos/" + p . id } target = "_blank" rel = "noopener noreferrer" >
75
+ Open in Immich
76
+ </ a >
77
+ ) ,
78
+ } ,
79
+ ] ,
80
+ } ) ) ;
81
+ } , [ assets , selectedIds , exImmichUrl ] ) ;
82
+
83
+ const slides = useMemo (
84
+ ( ) =>
85
+ images . map ( ( { original, width, height } ) => ( {
86
+ src : original ,
87
+ width,
88
+ height,
89
+ } ) ) ,
90
+ [ images ]
91
+ ) ;
92
+
93
+ const handleClick = ( idx : number , asset : IAsset , event : MouseEvent < HTMLElement > ) => {
94
+ if ( selectedIds . length > 0 ) {
95
+ handleSelect ( idx , asset , event ) ;
96
+ } else {
97
+ setIndex ( idx ) ;
98
+ }
99
+ } ;
100
+
101
+ const handleSelect = ( _idx : number , asset : IAsset , event : MouseEvent < HTMLElement > ) => {
102
+ event . stopPropagation ( ) ;
103
+ const isPresent = selectedIds . includes ( asset . id ) ;
104
+ if ( isPresent ) {
105
+ updateContext ( {
106
+ selectedIds : selectedIds . filter ( ( id ) => id !== asset . id ) ,
107
+ } ) ;
108
+ } else {
109
+ const clickedIndex = images . findIndex ( ( image ) => {
110
+ return image . id === asset . id ;
111
+ } ) ;
112
+ if ( event . shiftKey && lastSelectedIndex !== - 1 ) {
113
+ const startIndex = Math . min ( clickedIndex , lastSelectedIndex ) ;
114
+ const endIndex = Math . max ( clickedIndex , lastSelectedIndex ) ;
115
+ if ( startIndex >= 0 && endIndex < images . length ) {
116
+ const newSelectedIds = images . slice ( startIndex , endIndex + 1 ) . map ( ( image ) => image . id ) ;
117
+ const allSelectedIds = [ ...selectedIds , ...newSelectedIds ] ;
118
+ const uniqueSelectedIds = [ ...new Set ( allSelectedIds ) ] ;
119
+ updateContext ( { selectedIds : uniqueSelectedIds } ) ;
120
+ } else {
121
+ console . warn ( "Shift-select index out of bounds" ) ;
122
+ updateContext ( { selectedIds : [ ...selectedIds , asset . id ] } ) ;
123
+ }
124
+ } else {
125
+ updateContext ( { selectedIds : [ ...selectedIds , asset . id ] } ) ;
126
+ }
127
+ setLastSelectedIndex ( clickedIndex ) ;
128
+ }
129
+ } ;
130
+
68
131
useEffect ( ( ) => {
69
132
if ( album . id ) {
70
133
fetchAssets ( ) ;
@@ -76,7 +139,7 @@ export default function AlbumImages({ album }: AlbumImagesProps) {
76
139
faceId,
77
140
page : 1 ,
78
141
} ) ;
79
- setAssets ( [ ] ) ;
142
+ updateContext ( { assets : [ ] , selectedIds : [ ] } ) ;
80
143
setHasMore ( true ) ;
81
144
} , [ faceId ] ) ;
82
145
@@ -97,11 +160,30 @@ export default function AlbumImages({ album }: AlbumImagesProps) {
97
160
}
98
161
99
162
return (
100
- < PhotoSelectionContext . Provider
101
- value = { { ...contextState , updateContext : contextState . updateContext } }
102
- >
103
- < div className = "w-full p-2" >
104
- < AssetGrid assets = { assets } />
163
+ < >
164
+
165
+ < Lightbox
166
+ slides = { slides }
167
+ plugins = { [ Captions ] }
168
+ open = { index >= 0 }
169
+ index = { index }
170
+ close = { ( ) => setIndex ( - 1 ) }
171
+ />
172
+ < div className = "w-full p-2 overflow-y-auto max-h-[calc(100vh-60px)]" >
173
+ < Gallery
174
+ images = { images }
175
+ onClick = { handleClick }
176
+ enableImageSelection = { true }
177
+ onSelect = { handleSelect }
178
+ thumbnailImageComponent = { LazyGridImage }
179
+ tagStyle = { {
180
+ color : "white" ,
181
+ fontSize : "12px" ,
182
+ backgroundColor : "rgba(0, 0, 0)" ,
183
+ padding : "2px" ,
184
+ borderRadius : "5px" ,
185
+ } }
186
+ />
105
187
{ hasMore && (
106
188
< Button
107
189
variant = "outline"
@@ -118,6 +200,6 @@ export default function AlbumImages({ album }: AlbumImagesProps) {
118
200
</ Button >
119
201
) }
120
202
</ div >
121
- </ PhotoSelectionContext . Provider >
203
+ </ >
122
204
) ;
123
205
}
0 commit comments