@@ -4,20 +4,20 @@ import * as Utils from './utils.js';
4
4
import imgDownload from './img/download-white.png' ;
5
5
import imgNewtab from './img/newtab-white.png' ;
6
6
7
- /* ——————————————————————————————————————————————————————————————————————————————————————————————————————————————
7
+ /* ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————
8
8
* • Layout
9
9
* - Header with toggle for image/video modes, navigation buttons, and media detail
10
10
* - Full-window image/video view
11
11
*
12
12
* • TODO:
13
- * (•) Show a loading spinner during loading of each image/video
14
13
* (•) Add a thumbnail row along the bottom (pre-generate thumbnails for each picture -- using AWS Lambda fns?).
15
14
* (•) Add (pop-out?) list view for images/videos in bucket.
16
15
* (•) Add sorting options/controls.
17
16
* (•) Add an info icon to show tooltip in mouseover (keyboard shortcuts, etc).
18
- * (?) Set cookie to remember last image/video position.
17
+ * (•) Show a loading spinner during loading of each image/video
18
+ * (?) Create a "demo" version of the app using copies of all AWS components and a demo bucket with stock images.
19
19
*
20
- * ——————————————————————————————————————————————————————————————————————————————————————————————————————————————
20
+ * ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————
21
21
*/
22
22
23
23
//┣━━━━━━━━━━━━━━━━┓
@@ -62,62 +62,110 @@ export default function Gallery(props) {
62
62
// Initialize defaults for each media type
63
63
useEffect ( ( ) => {
64
64
if ( imagesList . length > 0 && videosList . length > 0 ) { // make sure file list has been loaded
65
- // Default to 'images' mode showing the first image, unless a fragment is provided in the URL (format: 'images-0' or 'videos-0')
65
+ // Check cookies and URL fragment for starting position. Otherwise default to 'images' mode and show the first image.
66
+
67
+ // These variables will remain 0 unless a 'last position' cookie is successfully loaded below.
68
+ let startingIndexImages = 0 ;
69
+ let startingIndexVideos = 0 ;
70
+
71
+ // First, check user's cookies for saved position info for this gallery.
72
+ const lastPositionCookieName = Utils . COOKIE_NAME_PREFIX + "last-" + props . galleryBucketParams [ 'id' ] ; // [TODO: Generalize <|1|>]
73
+ const lastPositionCookie = Utils . readCookieAsJSON ( lastPositionCookieName ) ;
74
+
75
+ try {
76
+ if ( lastPositionCookie ) {
77
+
78
+ if ( lastPositionCookie . last_mode == 'images' || lastPositionCookie . last_mode == 'videos' )
79
+ setMode ( lastPositionCookie . last_mode ) ;
80
+ else throw new Error ( "Invalid mode in last position cookie." ) ;
81
+
82
+ const lastIndexImage = parseInt ( lastPositionCookie . last_image_index ) ;
83
+ const lastIndexVideo = parseInt ( lastPositionCookie . last_video_index ) ;
84
+
85
+ if ( lastIndexImage >= 0 && lastIndexImage < imagesList . length && lastIndexVideo >= 0 && lastIndexVideo < videosList . length )
86
+ setCurrentIndex ( { image : lastIndexImage , video : lastIndexVideo } ) ;
87
+ else throw new Error ( "Image or video index out of bounds in last position cookie." ) ;
88
+
89
+ // Update the URL fragment to match the loaded position (unless it's the start of a gallery). [TODO: Generalize <|2|>]
90
+ // If a fragment is already provided in the URL, skip adding one. The index set above will be overwritten for the mode set in the fragment.
91
+ if ( ! window . location . hash . includes ( '#' ) || window . location . hash . length <= 1 ) {
92
+ const lastIndexForMode = lastPositionCookie . last_mode == 'images' ? lastIndexImage : lastIndexVideo ;
93
+ if ( lastIndexForMode > 0 ) {
94
+ let fragment = ( lastPositionCookie . last_mode == 'images' ? 'image' : 'video' ) + ( lastIndexForMode + 1 ) ;
95
+ window . location . hash = "#" + fragment ;
96
+ }
97
+ }
98
+
99
+ // If we update the starting indices from a cookie, set them here for the next step.
100
+ startingIndexImages = lastIndexImage ;
101
+ startingIndexVideos = lastIndexVideo ;
102
+ }
103
+ }
104
+ catch ( err ) {
105
+ console . log ( "[ERROR] " + err ) ;
106
+ // Clear the existing (malformed) cookie for this gallery.
107
+ Utils . deleteCookie ( lastPositionCookieName ) ;
108
+ }
109
+
110
+ // If a fragment is provided in the URL (as 'image123' or 'video123'), switch the current index (and possibly mode) to that position.
111
+ // A pre-existing fragment will override the last position cookie. But, if no fragment is provided, one will be set by the last position cookie.
66
112
if ( window . location . hash . includes ( '#' ) && window . location . hash . length > 1 ) {
67
113
try {
68
- let h_mode = window . location . hash . split ( '#' ) [ 1 ] . split ( '-' ) [ 0 ] ;
69
- let h_index = window . location . hash . split ( '#' ) [ 1 ] . split ( '-' ) [ 1 ] ;
70
- h_index = parseInt ( h_index ) - 1 ; // the fragment index is 1-indexed, so shift to get the array index
71
-
72
- // Check for valid mode and index, fail if invalid
73
- if ( h_mode !== 'images' && h_mode !== 'videos' ) throw new Error ( "Invalid mode in URL fragment." ) ;
74
- if ( Number . isNaN ( Number . parseInt ( h_index ) ) ) throw new Error ( "Invalid index in URL fragment." ) ;
75
- if ( h_mode === 'images' && ( h_index < 0 || h_index >= imagesList . length ) ) throw new Error ( "Out-of-bounds index in URL fragment." ) ;
76
- if ( h_mode === 'videos' && ( h_index < 0 || h_index >= videosList . length ) ) throw new Error ( "Out-of-bounds index in URL fragment." ) ;
77
-
78
- // For a valid mode and index, set the defaults accordingly
79
- let h_media = ( h_mode === 'images' ? imagesList [ h_index ] : videosList [ h_index ] ) ;
114
+ let frag_mode = window . location . hash . split ( '#' ) [ 1 ] . match ( / ( i m a g e | v i d e o ) ( \d + ) / ) [ 1 ] + "s" ;
115
+ let frag_index = window . location . hash . split ( '#' ) [ 1 ] . match ( / ( i m a g e | v i d e o ) ( \d + ) / ) [ 2 ] ;
116
+ frag_index = parseInt ( frag_index ) - 1 ; // the fragment index is 1-indexed, so shift to get the array index
117
+
118
+ // Check for valid mode and index, fail if invalid.
119
+ if ( frag_mode !== 'images' && frag_mode !== 'videos' ) throw new Error ( "Invalid mode in URL fragment." ) ;
120
+ if ( isNaN ( parseInt ( frag_index ) ) ) throw new Error ( "Invalid index in URL fragment." ) ;
121
+ if ( frag_mode === 'images' && ( frag_index < 0 || frag_index >= imagesList . length ) ) throw new Error ( "Out-of-bounds index in URL fragment." ) ;
122
+ if ( frag_mode === 'videos' && ( frag_index < 0 || frag_index >= videosList . length ) ) throw new Error ( "Out-of-bounds index in URL fragment." ) ;
123
+
124
+ // For a valid mode and index, set the defaults accordingly.
125
+ // The mode not given in the fragment will default to the last position cookie, or to 0 if none exists.
126
+ let frag_media = ( frag_mode === 'images' ? imagesList [ frag_index ] : videosList [ frag_index ] ) ;
80
127
setCurrentMedia ( {
81
- src : h_media . src ,
82
- filename : h_media . filename ,
83
- datestamp : h_media . datestamp ,
84
- timestamp : h_media . timestamp ,
85
- credit : h_media . credit
128
+ src : frag_media . src ,
129
+ filename : frag_media . filename ,
130
+ datestamp : frag_media . datestamp ,
131
+ timestamp : frag_media . timestamp ,
132
+ credit : frag_media . credit
86
133
} ) ;
87
- if ( h_mode === 'images' ) {
134
+ if ( frag_mode === 'images' ) {
88
135
setMode ( 'images' ) ;
89
- setCurrentIndex ( { image : h_index , video : 0 } ) ;
136
+ setCurrentIndex ( { image : frag_index , video : startingIndexVideos } ) ;
90
137
}
91
- if ( h_mode === 'videos' ) {
138
+ if ( frag_mode === 'videos' ) {
92
139
setMode ( 'videos' ) ;
93
- setCurrentIndex ( { image : 0 , video : h_index } ) ;
140
+ setCurrentIndex ( { image : startingIndexImages , video : frag_index } ) ;
94
141
}
95
142
}
96
143
catch ( err ) {
97
144
console . log ( "[ERROR] " + err ) ;
98
- // Fallback to defaults if URL fragment is malformed // DRY
145
+ // Fallback to defaults (or last position cookie) if URL fragment is malformed // DRY
99
146
setCurrentMedia ( {
100
- src : imagesList [ 0 ] . src ,
101
- filename : imagesList [ 0 ] . filename ,
102
- datestamp : imagesList [ 0 ] . datestamp ,
103
- timestamp : imagesList [ 0 ] . timestamp ,
104
- credit : imagesList [ 0 ] . credit
147
+ src : imagesList [ startingIndexImages ] . src ,
148
+ filename : imagesList [ startingIndexImages ] . filename ,
149
+ datestamp : imagesList [ startingIndexImages ] . datestamp ,
150
+ timestamp : imagesList [ startingIndexImages ] . timestamp ,
151
+ credit : imagesList [ startingIndexImages ] . credit
105
152
} ) ;
106
- setCurrentIndex ( { image : 0 , video : 0 } ) ;
153
+ setCurrentIndex ( { image : startingIndexImages , video : startingIndexVideos } ) ;
107
154
// Clear the URL fragment (leaves the #)
108
155
window . location . hash = '' ;
109
156
}
110
157
}
158
+
159
+ // Defaults (if no URL fragment is provided) // DRY
111
160
else {
112
- // Defaults (if no URL fragment is provided) // DRY
113
161
setCurrentMedia ( {
114
- src : imagesList [ 0 ] . src ,
115
- filename : imagesList [ 0 ] . filename ,
116
- datestamp : imagesList [ 0 ] . datestamp ,
117
- timestamp : imagesList [ 0 ] . timestamp ,
118
- credit : imagesList [ 0 ] . credit
162
+ src : imagesList [ startingIndexImages ] . src ,
163
+ filename : imagesList [ startingIndexImages ] . filename ,
164
+ datestamp : imagesList [ startingIndexImages ] . datestamp ,
165
+ timestamp : imagesList [ startingIndexImages ] . timestamp ,
166
+ credit : imagesList [ startingIndexImages ] . credit
119
167
} ) ;
120
- setCurrentIndex ( { image : 0 , video : 0 } ) ;
168
+ setCurrentIndex ( { image : startingIndexImages , video : startingIndexVideos } ) ;
121
169
}
122
170
}
123
171
} , [ imagesList , videosList ] ) ; // Only run when 'imagesList' or 'videoList' changes
@@ -264,6 +312,7 @@ export default function Gallery(props) {
264
312
setCurrentIndex = { setCurrentIndex }
265
313
mediaDetails = { mediaDetails }
266
314
accessKey = { props . accessKey }
315
+ galleryBucketParams = { props . galleryBucketParams }
267
316
/>
268
317
269
318
< Display mode = { mode } currentMedia = { currentMedia } mediaDetails = { mediaDetails } accessKey = { props . accessKey } />
@@ -312,6 +361,7 @@ function Header(props) {
312
361
videosListLength = { props . videosListLength }
313
362
currentIndex = { props . currentIndex }
314
363
setCurrentIndex = { props . setCurrentIndex }
364
+ galleryBucketParams = { props . galleryBucketParams }
315
365
/>
316
366
317
367
< Details mode = { props . mode } currentMedia = { props . currentMedia } mediaDetails = { props . mediaDetails } />
@@ -332,7 +382,7 @@ function Navigation(props) {
332
382
const [ navButtonCount , setNavButtonCount ] = useState ( 5 ) ;
333
383
334
384
// One-shot effects to set up adjustments to window length based on viewport width.
335
- // [Ref: https://blog.bitsrc.io/using-react-hooks-to-recognize-respond-to-current-viewport-size-c385009005c0]
385
+ // [https://blog.bitsrc.io/using-react-hooks-to-recognize-respond-to-current-viewport-size-c385009005c0]
336
386
useEffect ( ( ) => { // set initial 'navButtonCount'
337
387
if ( window . innerWidth < 480 )
338
388
setNavButtonCount ( 1 ) ;
@@ -354,13 +404,24 @@ function Navigation(props) {
354
404
return ( ) => { window . removeEventListener ( 'resize' , onResize ) ; } // clean up
355
405
} , [ ] ) ;
356
406
357
- // Change URL fragment (#) on navigation.
358
- // TODO: Change this to use the `useNavigation` hook from react-router-dom instead? [https://reactrouter.com/en/main/hooks/use-navigate]
407
+ // Effects to trigger whenever the `currentIndex` or `mode` is changed, indicating a user navigation.
359
408
useEffect ( ( ) => {
409
+ // Change URL fragment (#) to match the new mode/index on navigation. [TODO: Generalize <|2|>]
410
+ // TODO: Change this to use the `useNavigation` hook from react-router-dom instead? [https://reactrouter.com/en/main/hooks/use-navigate]
360
411
if ( props . currentIndex . image >= 0 && props . currentIndex . video >= 0 ) {
361
- let h_index = ( props . mode === 'images' ? props . currentIndex . image : props . currentIndex . video ) ;
362
- window . location . hash = "#" + props . mode + "-" + ( Number . parseInt ( h_index ) + 1 ) ;
412
+ let frag_index = ( props . mode === 'images' ? props . currentIndex . image : props . currentIndex . video ) ;
413
+ let fragment = ( props . mode == 'images' ? 'image' : 'video' ) + ( parseInt ( frag_index ) + 1 ) ;
414
+ window . location . hash = "#" + fragment ;
363
415
}
416
+
417
+ // Store the new mode/index to remember the user's last position in the current gallery. [TODO: Generalize <|1|>]
418
+ const bucketCookieValue = {
419
+ "bucket_name" : props . galleryBucketParams [ 'bucketName' ] ,
420
+ "last_mode" : props . mode ,
421
+ "last_image_index" : props . currentIndex . image ,
422
+ "last_video_index" : props . currentIndex . video
423
+ } ;
424
+ Utils . setCookieAsJSON ( Utils . COOKIE_NAME_PREFIX + "last-" + props . galleryBucketParams [ 'id' ] , bucketCookieValue , 30 ) ; // set cookie for 30 days
364
425
} , [ props . currentIndex , props . mode ] ) ;
365
426
366
427
0 commit comments