import React, { useEffect, useState } from 'react'; import { SlideData } from 'photoswipe'; import PhotoSwipeLightbox from 'photoswipe/lightbox'; import Sidebar from './components/Sidebar/Sidebar'; import Map from './components/Map/Map'; import { createLightbox } from './components/Lightbox/Lightbox'; import LoadingScreen from './components/LoadingScreen/LoadingScreen'; import { MediaType, Trip } from './models'; import { isTrip } from './models/index.guard'; import { getObjectProperty } from './lib/util'; import styles from './App.module.css'; import 'photoswipe/style.css'; function App(): JSX.Element { const [allTrips, setAllTrips] = useState([]); const [currentTripIndex, setCurrentTripIndex] = useState(0); const [currentGroupIndex, setCurrentGroupIndex] = useState(1); const [asideOpen, setAsideOpen] = useState(false); const [lightbox, setLightbox] = useState(); const [openLightbox, setOpenLightbox] = useState(false); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(); const currentTrip = allTrips[currentTripIndex]; const currentGroup = currentTrip?.groups?.[currentGroupIndex]; async function getAllTrips(): Promise { try { const url = process.env.REACT_APP_DATA_URL ?? '/data/index.json'; const res = await fetch(url); const data = await res.json(); if (!Array.isArray(data) || data.length === 0) { return []; } data.forEach((obj) => { if (!isTrip(obj)) { throw new Error('The requested data file has incorrect structure or types.'); } }); return data; } catch (err) { setError(`An error occurred while retrieving data: "${err as string}"`); console.error(err); return []; } } async function getTrip(trip: Trip): Promise { try { if (trip.url === undefined) { return; } const res = await fetch(trip.url); const data: Trip = await res.json(); if (!isTrip(data)) { throw new Error('The requested data file has incorrect structure or types.'); } const fullTrip = { ...data, ...trip, downloaded: true, }; if (data.groups !== undefined) { const sortProperty = fullTrip.sortProperty ?? 'name'; const sortOrder = fullTrip.sortOrder ?? false; data.groups.sort((a, b) => { let aVal = getObjectProperty(a, sortProperty); let bVal = getObjectProperty(b, sortProperty); aVal = aVal === undefined ? (typeof bVal === "string" ? "" : 0) : aVal; bVal = bVal === undefined ? (typeof aVal === "string" ? "" : 0) : bVal; if (sortOrder) { return aVal > bVal ? -1 : (aVal === bVal ? 0 : 1); } return aVal > bVal ? 1 : (aVal === bVal ? 0 : -1); }); fullTrip.groups = [ { id: 'all', name: 'Show all', media: data.groups?.flatMap((g) => g.media), geoData: data.groups?.flatMap((g) => (g.geoData !== undefined ? g.geoData : [])), }, ...data.groups, ]; } return fullTrip; } catch (err) { setError(`An error occurred while retrieving data: "${err as string}"`); console.error(err); } } function updateAllTrips(trips: Trip[], trip: Trip): void { const updatedTrips = trips.map((t) => { if (t.id === trip.id) { t = trip; } return t; }); setAllTrips(updatedTrips); } function updateUrlHash(trip: Trip): void { const groupId = trip.groups?.[1] !== undefined ? `/${trip.groups[1].id}` : ''; window.location.hash = `${trip.id}${groupId}`; } async function getFirstTrip(trips: Trip[]): Promise { const trip = await getTrip(trips[0]); if (trip !== undefined) { updateAllTrips(trips, trip); updateUrlHash(trip); } } async function getTripFromUrlHash(trips: Trip[]): Promise { if (window.location.hash.length === 0) { return await getFirstTrip(trips); } const hash = window.location.hash.endsWith('/') ? window.location.hash.slice(0, window.location.hash.length - 1) : window.location.hash; const [tripId, groupId] = hash.substring(1).split('/'); const tripIndex = trips.findIndex((t) => t.id === tripId); if (tripIndex < 0) { return await getFirstTrip(trips); } const trip = await getTrip(trips[tripIndex]); if (trip === undefined) { return await getFirstTrip(trips); } updateAllTrips(trips, trip); setCurrentTripIndex(tripIndex); const groupIndex = trip.groups?.findIndex((g) => g.id === groupId); if (groupIndex !== undefined && groupIndex > -1) { setCurrentGroupIndex(groupIndex); } else { updateUrlHash(trip); } } useEffect(() => { void getAllTrips().then(async (trips) => { setAllTrips(trips); await getTripFromUrlHash(trips); setIsLoading(false); }); }, []); useEffect(() => { if (currentTrip !== undefined && currentGroup !== undefined) { window.location.hash = `${currentTrip.id}/${currentGroup.id}`; } }, [currentTripIndex, currentGroupIndex]); useEffect(() => { if (lightbox !== undefined) { lightbox.destroy(); } if (currentGroup?.media === undefined || currentGroup?.media.length === 0) { return; } const dataSource = currentGroup?.media.map((mediaItem) => { const slideData: SlideData = { width: mediaItem.width, height: mediaItem.height, alt: mediaItem.caption, }; if (mediaItem.type === MediaType.Photo) { slideData.type = 'image'; slideData.src = mediaItem.src; } else if (mediaItem.type === MediaType.Video) { slideData.type = 'video'; slideData.videoSrc = mediaItem.src; } return slideData; }); const lb = createLightbox(dataSource); lb.on('beforeOpen', () => setIsLoading(false)); lb.init(); if (openLightbox) { lb.loadAndOpen(0); setOpenLightbox(false); } setLightbox(lb); }, [currentGroup]); function handleMarkerClick(mediaName: string): void { if (currentGroup === undefined) { return; } const index = currentGroup.media.findIndex((p) => p.name === mediaName); if (index < 0) { lightbox?.loadAndOpen(0); } else { lightbox?.loadAndOpen(index); } } function handleGroupChange(groupIndex: number, openGallery?: boolean): void { setCurrentGroupIndex(groupIndex); if (openGallery ?? false) { setOpenLightbox(true); } } function handleTripChange(tripIndex: number): void { if (allTrips[tripIndex] === undefined) { return; } setIsLoading(true); if (!allTrips[tripIndex].downloaded) { void getTrip(allTrips[tripIndex]).then((trip) => { if (trip === undefined) { return; } updateAllTrips(allTrips, trip); setCurrentTripIndex(tripIndex); setCurrentGroupIndex(1); setIsLoading(false); }); } else { setCurrentTripIndex(tripIndex); setCurrentGroupIndex(1); setIsLoading(false); } } if (error !== undefined) { return <>{error}; } if (currentTrip === undefined || currentTrip.groups === undefined) { return ; } return (
setAsideOpen(false)} handleGroupChange={handleGroupChange} handleTripChange={handleTripChange} />
{isLoading && }
); } export default App;