Skip to content

Commit

Permalink
Replace import of HashRouter, Route and Routes with BrowserRouter and…
Browse files Browse the repository at this point in the history
… useRoutes in, create RoutesComponent in, add initialPhotoAlbumSlug as prop to TravelPhotos in the routes array of RoutesComponent in, refactor from HashRouter to BrowserRouter approach and update annotations to App.js, import useNavigate in, add initialPhotoAlbumSlug to, change setPhotoAlbum to setPhotoAlbumSlug in, create handlePopState in and update annotations to TravelPhotos.js, remove ReactDOM import from, remove react-photo-view/dist/react-photo-view.css import from, add useNavigate reference to arguments to, update annotations to and add navigate('/travel-photos') to the afterClose prop of PhotoProvider in and modify onClick to handle window.history.pushState in the parseAndRenderPhotoData procedure in _utils.js, add react-photo-view/dist/react-photo-view.css to index.js, remove window.location.reload from SingleTrack.js, remove window.location.reload from the onClick that navigates to the travel-photos path in SocialMenu.js, remove window.location.reload from Track.js, update language in InfoSheet.js, and update annotations to Home.js
  • Loading branch information
dvho committed Jul 27, 2024
1 parent d14f880 commit e9093ac
Show file tree
Hide file tree
Showing 10 changed files with 478 additions and 533 deletions.
872 changes: 405 additions & 467 deletions package-lock.json

Large diffs are not rendered by default.

50 changes: 24 additions & 26 deletions src/App.js
Original file line number Diff line number Diff line change
@@ -1,38 +1,36 @@
import { HashRouter, Route, Routes } from 'react-router-dom'
import { BrowserRouter, useRoutes } from 'react-router-dom' // Changed from HashRouter to BrowserRouter

import Home from './Home'
import { SingleTrack, TravelPhotos } from './components'


import config from './_config'
import { photoData } from './assets/data'

const RoutesComponent = () => { //This component is designed to handle routing for the entire app...
const routes = [ //...by constructing an array of routes from the config object, the photoData array and then adding two static routes, one for <TravelPhotos> when no photo album is open and all the photo albums are showing, and one for <Home> to handle the root route and to catch all remaining cases...
...config.tracks.map(i => ({
path: `/${i.slug}`,
element: <SingleTrack track={i} />
})),
...photoData.map(i => ({
path: `/travel-photos/${i.slug}`,
element: <TravelPhotos initialPhotoAlbumSlug={i.slug} />
})),
{ path: '/travel-photos', element: <TravelPhotos initialPhotoAlbumSlug={''} /> },
{ path: '*', element: <Home /> }
]

const element = useRoutes(routes) //...then by using the useRoutes hook from react-router-dom to render the appropriate component based on the current route
return element
}

const App = () => {

let hashes, index, path, track, element

hashes = config.tracks.map(i => `#/${i.slug}`) //Make hashes from each of the slugs in config.tracks[n].slug
hashes.push('#/journeys') //...and push #/journeys as the final one.

index = hashes.indexOf(window.location.hash) //...Because the hashes array begins with making hashes from the tracks in numerical order before #/journeys is pushed to the hashes array your indexOf(window.location.hash) will be the index...

if (config.tracks.filter(i => `#/${i.slug}` === window.location.hash).length === 1) {
path = window.location.hash.replace('#', '')
track = config.tracks[index] //...that you can pass here in order to get the track...
element = (() => <SingleTrack track={track}/>)() //...to pass here.
} else if (window.location.hash === '#/journeys') {
path = window.location.hash.replace('#', '')
element = (() => <TravelPhotos/>)()
} else {
path = window.location.hash.replace('#', '')
element = (() => <Home/>)()
}

return (
<HashRouter>
<Routes>
{ /* <Route path='*' element={<Home />} /> */ }
<Route path={path} element={element}/>
</Routes>
</HashRouter>
<BrowserRouter>
<RoutesComponent />
</BrowserRouter>
)
}

Expand Down
4 changes: 2 additions & 2 deletions src/Home.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,8 @@ const Home = () => {

const handleEvents = e => {

if (e.type === 'resize' && !navigator.userAgent.includes('iPhone')) { //Unfortunately I have to prevent this from working on iOS Safari because as of iOS 14 the new bottom address bar, which now dynamically appears on touchstart, also always fires the resize event
setTimeout(() => window.location.reload(), 10) //setTimeout needed for Mozilla (not Chrome) per Morteza Ziyae on 2015, 01-27th in https://stackoverflow.com/questions/18967532/window-location-reload-not-working-for-firefox-and-chrome
if (e.type === 'resize' && !navigator.userAgent.includes('iPhone')) { //Unfortunately I have to prevent this from working on iOS Safari because as of iOS 14 the new bottom address bar, which now dynamically appears on touchstart, also always fires the resize event...
setTimeout(() => window.location.reload(), 10) //...calling window.location.reload() is required so that resizing the window reloads the page to recalculate the canvasWidth and canvasHeight from within config.constants, and the setTimeout is needed for Mozilla (not Chrome) per Morteza Ziyae on 2015, 01-27th in https://stackoverflow.com/questions/18967532/window-location-reload-not-working-for-firefox-and-chrome
}

if (e.type === 'pointermove' || e.type === 'click' || e.type === 'handmove') { //Get the X and Y positions on pointermove and on click. Note: e.pageX and e.pageY have to be used instead of e.clientX and e.clientY because the latter two are properties of the MouseEvent only, and the former of both MouseEvent and TouchEvent. This caused an hours long headache that was eventually solved.
Expand Down
41 changes: 22 additions & 19 deletions src/_utils.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import * as Hands from '@mediapipe/hands'
import * as drawingUtils from '@mediapipe/drawing_utils'
import ReactDOM, { render } from 'react-dom'
import { findDOMNode } from 'react-dom'
import emailjs from '@emailjs/browser' //https://github.com/emailjs-com/emailjs-sdk https://www.emailjs.com/docs/sdk/send/
import { PhotoProvider, PhotoView } from 'react-photo-view' //https://react-photo-view.vercel.app/en-US/docs/getting-started
//import 'react-photo-view/dist/react-photo-view.css' //This is the default css file that comes with react-photo-view but I've commented it out because I'm using my own css file for the photo viewer

import config from './_config'
import { photoData } from './assets/data'
Expand All @@ -16,29 +15,29 @@ const { dummyImage, eyePosition, eyePosition: { faceEmpty, faceEyePosition1L1, f

const utils = {
//TODO: https://www.npmjs.com/package/onhold
onHold: (() => { //The function returned by this onHold IIFE is actually the one being called, hence, calling utils.onHold(fn, latency, event) works even though onHold appears to take no parameters. It's structure as an IIFE's is to set up the closure scope for holdTimer outside of the returned function, ensuring that it's private and persistent across multiple calls to utils.onHold, so the three arguments it takes are: A function to call on hold, a latency to determine hold duration and an event to check its type...
onHold: (() => { //The function returned by this onHold IIFE is actually the one being called, hence, calling utils.onHold(fn, latency, e) works even though onHold appears to take no parameters. It's structure as an IIFE's is to set up the closure scope for holdTimer outside of the returned function, ensuring that it's private and persistent across multiple calls to utils.onHold, so the three arguments it takes are: A function to call on hold, a latency to determine hold duration and an event to check its type...
let holdTimer = null //...declare holdTimer inside the closure scope of onHold so that it persists across multiple calls to utils.onHold and initialize it to null...

const onEvents = ['mouseenter', 'keydown', 'mousedown', 'touchstart', 'pointerdown', 'dragstart'] //...define the events that trigger the onHold function...
const offEvents = ['mouseleave', 'keyup', 'mouseup', 'touchend', 'pointerup', 'dragend'] //...and the events that stop it...

return (fn, latency, event) => { //...return the actual onHold function...
//...(and because the default is onClick don't call event.preventDefault() here)...
return (fn, latency, e) => { //...return the actual onHold function...
//...(and because the default is onClick don't call e.preventDefault() here)...
const upListener = () => { //...in which a listener is defined for the mouseleave, keyup, mouseup and touchend events...
clearTimeout(holdTimer) //...where if a the mouse leaves, or a key, the mouse button or a touch is released it clears the holdTimer...
offEvents.forEach(i =>
event.target.removeEventListener(i, upListener)) //...and removes the listeners for mouseleave, keyup, mouseup and touchend...
e.target.removeEventListener(i, upListener)) //...and removes the listeners for mouseleave, keyup, mouseup and touchend...
}

if (onEvents.includes(event.type)) { //...and if the event is a mouseenter, keydown, mousedown or touchstart...
if (onEvents.includes(e.type)) { //...and if the event is a mouseenter, keydown, mousedown or touchstart...
offEvents.forEach(i =>
event.target.addEventListener(i, upListener)) //...add the mouseleave, keyup, mouseup and touchend listeners to the target of the event...
e.target.addEventListener(i, upListener)) //...add the mouseleave, keyup, mouseup and touchend listeners to the target of the event...

holdTimer = setTimeout(() => { //...set holdTimer to a setTimeout which calls the specified function after the specified latency...
fn() // ...wherein the function is called...
upListener() //...and the upListener used to clean up...
}, latency)
} else if (offEvents.includes(event.type)) { //...otherwise if the event is a mouseleave, keyup, mouseup or touchend...
} else if (offEvents.includes(e.type)) { //...otherwise if the event is a mouseleave, keyup, mouseup or touchend...
upListener() //...directly call upListener to clean up
}
}
Expand Down Expand Up @@ -347,7 +346,7 @@ const utils = {
},

handleHand: (coords, ref, fn, rateLimit) => { //utils.handleHand takes four arguments, coords, ref, fn and rateLimit and calls fn at the specified rateLimit on the referenced element when the coords (from the tip of the pointing index finger) are within its boundaries. It's called from all components that are tracking the hand for this purpose, namely SocialMenu and AudioPlayer...
const boundingClientRect = ReactDOM.findDOMNode(ref).getBoundingClientRect() //...get the four boundaries of the referenced element...
const boundingClientRect = findDOMNode(ref).getBoundingClientRect() //...get the four boundaries of the referenced element...

let boundingClientRectFloats = utils.getBoundingClientRectFloats(boundingClientRect) //...and pass them to utils.getBoundingClientRectFloats to convert them to four floats...

Expand Down Expand Up @@ -673,19 +672,19 @@ const utils = {
}
},

parseAndRenderPhotoData: (photoAlbum, setPhotoAlbum) => { //utils.parseAndRenderPhotoData takes two arguments, photoAlbum and setPhotoAlbum, which are the state and setState functions from TravelPhotos wherein it's called. If a photo album is open it returns a react-photo-view PhotoProvider component (carousel) with a PhotoView child for each of the photos in the specified album, otherwise it returns a page of divs displaying photo album titles and cover photos...
parseAndRenderPhotoData: (photoAlbumSlug, setPhotoAlbumSlug, navigate) => { //utils.parseAndRenderPhotoData takes three arguments, photoAlbumSlug and setPhotoAlbumSlug, which are the state and setState functions from TravelPhotos wherein it's called, and navigate, which is a reference to react-router-dom's navigation hook. If a photo album is open it returns a react-photo-view PhotoProvider component (carousel) with a PhotoView child for each of the photos in the specified album, otherwise it returns a page of divs displaying photo album titles and cover photos...

if (photoAlbum.isPhotoProviderOpen) { //...if an album is open...
const fullArray = photoData[photoAlbum.value].fullString.split(' ') //...create an array of urls of the hi res photos for that album...
if (photoAlbumSlug !== '') { //...if photoAlbumSlug is not an empty string, meaning a photo album is open...
const fullArray = photoData.find(i => i.slug === photoAlbumSlug).fullString.split(' ') //...create an array of urls of the hi res photos for that album...

return ( //...and return a PhotoProvider component with PhotoView children for each of the photos in the album...
<PhotoProvider
speed={() => 0} //...where speed is set to 0 so the carousel is quick...
loop={3} //...loop is 3 so the carousel looks ahead 3 images to download them...
pullClosable={false} //...pullClosable is false so the carousel can't be closed by pulling down because it tends to happen inadvertently when a user is swiping on mobile...
//maskOpacity={0.9} //...(this is a relic of my old implementation which had the issues caused by react-photo-view's rigidity as described in annotations to utils.simulateClickOrTouch)...
onVisibleChange={() => setPhotoAlbum({...photoAlbum, isPhotoProviderOpen: true})} //...onVisibleChange, called when PhotoProvider is visible (should more appropriately be called afterOpen), sets photoAlbum's isPhotoProviderOpen prop to true...
afterClose={() => setPhotoAlbum({...photoAlbum, isPhotoProviderOpen: false})} //...afterClose, called when PhotoProvider is closed, sets photoAlbum's isPhotoProviderOpen prop to false...
//onVisibleChange={() => {}} //...I might use this prop for something in the future...
afterClose={() => {setPhotoAlbumSlug(''); navigate('/travel-photos')}} //...afterClose, called when PhotoProvider is closed, sets photoAlbumSlug back to an empty string and uses react-router-dom's navigation hook to make the url path '/travel-photos'...
loadingElement={
<div className='loading-screen'>
<div className='cube-container'>
Expand Down Expand Up @@ -715,14 +714,18 @@ const utils = {
</PhotoProvider>
)
} else { //...otherwise if no album is open...
return ( //...return a page of lazy loading divs displaying photo album titles and their associated cover photos from photoData which when clicked set photoAlbum's value prop to the index of the desired photo album and it's isPhotoProviderOpen prop to true
return ( //...return a page of lazy loading divs displaying photo album titles and their associated cover photos from photoData, which when clicked each set photoAlbumSlug to the slug of the associated photo album...
<div className='card-page'>
{
photoData.map((i, index) => {
photoData.map(i => {
return (
<div key={index} className='album-container'>
<div key={i.slug} className='album-container'>
<h2 className='album-name'>{i.albumName.substring(16)}</h2>
<img src={i.albumCover} className='album-cover' onClick={() => setPhotoAlbum({value: index, isPhotoProviderOpen: true})} alt='' loading='lazy'/>
<img
src={i.albumCover}
className='album-cover'
onClick={() => { setPhotoAlbumSlug(i.slug); window.history.pushState(i.slug, '', `/travel-photos/${i.slug}`)}} //...using window.history.pushState, which takes three parameters, a state, a title and a path (the second, i.e. the title, which is ignored by most browsers) to push the slug to the URL path
alt='' loading='lazy' />
</div>
)
})
Expand Down
6 changes: 3 additions & 3 deletions src/components/InfoSheet.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion src/components/SingleTrack.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ const SingleTrack = ({ track }) => {
const lyrics = document.getElementById('lyrics')
title.innerHTML = `"${track.title}"`
lyrics.innerHTML = lyrics.innerHTML + track.lyrics
return () => window.location.reload() //If you don't call this on when the useEffect unmounts the home page won't reload when you navigate back
}, [])

return (
Expand Down
2 changes: 1 addition & 1 deletion src/components/SocialMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const SocialMenu = ({ dispatch, coords, isHandPointing, menuPosition, cloudsOn,
<div className={classNames.socialMenuContainer} style={styles.socialMenuContainer}>

<div className={classNames.socialIconsColumn1}>
<i className='plane social-icon fa fa-plane-departure' onClick={() => { navigate('journeys'); window.location.reload()}} /> { /* If you don't call window.location.reload here too the path will change in your browser's address bar but the app won't navigate */ }
<i className='plane social-icon fa fa-plane-departure' onClick={() => navigate('travel-photos')} />
<i className='cloud social-icon fa-solid fa-cloud' style={styles.cloudIcon} onClick={() => dispatch({type: 'toggleClouds'})} ref={cloudsOnRef} />
<i className='cloud-haze fi fi-cloudy-gusts' style={styles.cloudHazeIcon} onClick={() => dispatch({type: 'toggleCloudHaze'})} ref={cloudHazeOnRef} />
<i className='hand social-icon fa-solid fa-hand-sparkles' style={styles.handControllerIcon} onClick={() => dispatch({type: 'toggleHandController'})} />
Expand Down
1 change: 0 additions & 1 deletion src/components/Track.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ const Track = ({ dispatch, track, currentTrack, trackNumber, leftColumn }) => {
const onHoldWrapper = e => {
onHold(() => {
navigate(slug)
window.location.reload() //If you don't call this here the path will change in your browser's address bar but the app won't navigate, and it must be called from a setTimeout otherwise the navigation will not have completed first
}, 1000, e)
}

Expand Down
Loading

0 comments on commit e9093ac

Please sign in to comment.