From f2ecc1803f3ea294a0c6b7915b61348ed0395b26 Mon Sep 17 00:00:00 2001 From: Marcin Zelent Date: Wed, 16 Nov 2022 15:16:38 +0100 Subject: Remade and extended the app using React --- src/App.tsx | 303 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 303 insertions(+) create mode 100644 src/App.tsx (limited to 'src/App.tsx') diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..8dece4e --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,303 @@ +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 { MediaType, Trip } from './models'; +import { isTrip } from './models/index.guard'; + +import styles from './App.module.css'; +import 'photoswipe/style.css'; +import LoadingScreen from './components/LoadingScreen/LoadingScreen'; + +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(url: string): Promise { + try { + if (url === undefined) { + return; + } + + const res = await fetch(url); + const data: Trip = await res.json(); + + if (!isTrip(data)) { + throw new Error('The requested data file has incorrect structure or types.'); + } + + const trip = { + ...data, + url, + downloaded: true, + }; + + if (data.groups !== undefined) { + + trip.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 trip; + } 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].url); + + 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].url); + + 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].url).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; -- cgit v1.2.3