diff options
Diffstat (limited to 'components')
-rw-r--r-- | components/Gallery/Gallery.module.css | 63 | ||||
-rw-r--r-- | components/Gallery/Gallery.tsx | 80 | ||||
-rw-r--r-- | components/Map/Map.module.css | 28 | ||||
-rw-r--r-- | components/Map/Map.tsx | 121 | ||||
-rw-r--r-- | components/Sidebar/Sidebar.module.css | 54 | ||||
-rw-r--r-- | components/Sidebar/Sidebar.tsx | 71 |
6 files changed, 0 insertions, 417 deletions
diff --git a/components/Gallery/Gallery.module.css b/components/Gallery/Gallery.module.css deleted file mode 100644 index 01e8037..0000000 --- a/components/Gallery/Gallery.module.css +++ /dev/null @@ -1,63 +0,0 @@ -.galleryContainer { - padding: 20px; - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 99999; - background: rgba(0, 0, 0, 0.8); -} - -.photoContainer { - width: 100%; - height: 100%; - padding: 20px; - position: relative; -} - -.photo { - max-width: 100%; - max-height: 100%; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); -} - -.btn { - position: absolute; - z-index: 99999; - font-size: 2rem; - color: #eee; - background: none; - border: none; - cursor: pointer; - outline: currentcolor none medium; -} - -.btn:hover { - color: #fff; -} - -.arrowLeft { - top: 50%; - left: 0; - transform: translateY(-50%); -} - -.arrowRight { - top: 50%; - right: 0; - transform: translateY(-50%); -} - -.closeBtn { - width: 64px; - height: 64px; - top: 0; - right: 0; - font-size: 4rem; - line-height: 4rem; - font-weight: 300; -} diff --git a/components/Gallery/Gallery.tsx b/components/Gallery/Gallery.tsx deleted file mode 100644 index dfadbc0..0000000 --- a/components/Gallery/Gallery.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import { useEffect, useRef } from 'react'; - -import { Photo } from 'models'; -import useEvent from 'lib/useEvent'; -import styles from './Gallery.module.css'; - -interface Props { - currentPhoto: Photo; - handleClose: () => void; - handlePhotoChange: (direction: boolean) => void; -} - -export default function Gallery({ - currentPhoto, - handleClose, - handlePhotoChange, -}: Props): JSX.Element { - const wrapperRef = useRef(null); - - useEffect(() => { - function handleClickOutside(e: MouseEvent) { - if ( - wrapperRef.current && - !wrapperRef.current.contains(e.target) && - (e.target as Node).nodeName !== 'BUTTON' - ) { - handleClose(); - } - } - - document.addEventListener('mousedown', handleClickOutside); - return () => { - document.removeEventListener('mousedown', handleClickOutside); - }; - }, [wrapperRef]); - - useEvent('keydown', (e: KeyboardEvent) => { - if (e.key === 'ArrowLeft') { - handlePhotoChange(false); - } else if (e.key === 'ArrowRight') { - handlePhotoChange(true); - } else if (e.key === 'Escape') { - handleClose(); - } - }); - - return ( - <div className={styles.galleryContainer} role="region"> - <button - type="button" - className={`${styles.arrowLeft} ${styles.btn}`} - onClick={() => handlePhotoChange(false)} - > - 〈 - </button> - <div className={styles.photoContainer}> - <img - ref={wrapperRef} - src={currentPhoto.src} - alt={currentPhoto.name} - className={styles.photo} - /> - </div> - <button - type="button" - className={`${styles.arrowRight} ${styles.btn}`} - onClick={() => handlePhotoChange(true)} - > - 〉 - </button> - <button - type="button" - className={`${styles.closeBtn} ${styles.btn}`} - onClick={() => handleClose()} - > - × - </button> - </div> - ); -} diff --git a/components/Map/Map.module.css b/components/Map/Map.module.css deleted file mode 100644 index b13f5d2..0000000 --- a/components/Map/Map.module.css +++ /dev/null @@ -1,28 +0,0 @@ -.popup { - width: auto; - min-height: 90%; -} - -.popup .leaflet-popup-content { - margin: 0; -} - -.markerIcon { - border: 2px solid #fff; - border-radius: 2px; - overflow: hidden; -} - -.markerItemCount { - width: 18px; - height: 18px; - display: block; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - border-radius: 10px; - background: #fff; - text-align: center; - font-weight: bold; -} diff --git a/components/Map/Map.tsx b/components/Map/Map.tsx deleted file mode 100644 index 8ecde47..0000000 --- a/components/Map/Map.tsx +++ /dev/null @@ -1,121 +0,0 @@ -import { LatLngBounds, DivIcon, Icon } from 'leaflet'; -import { useEffect, useState } from 'react'; -import ReactDom from 'react-dom/server'; -import { MapContainer, Marker, TileLayer, GeoJSON, ZoomControl } from 'react-leaflet'; -import MarkerClusterGroup from 'react-leaflet-markercluster'; - -import { Trip, Photo } from 'models'; -import { distanceBetween } from 'lib/util'; - -import 'leaflet/dist/leaflet.css'; -import styles from './Map.module.css'; - -interface Props { - trip: Trip; - handleMarkerClick: (photo: string) => void; -} - -export default function Map({ trip, handleMarkerClick }: Props): JSX.Element { - const [map, setMap] = useState(null); - - const bounds = trip - ? new LatLngBounds( - [trip.track.bbox[1], trip.track.bbox[0]], - [trip.track.bbox[3], trip.track.bbox[2]], - ) - : new LatLngBounds([75, -145], [-52, 145]); - - function createMarkers(photos: Photo[]): JSX.Element[] { - // cluster photos that are close to each other - const clusters: Photo[][] = []; - for (let i = 0; i < photos.length; i += 1) { - if (clusters.filter((c) => c.includes(photos[i])).length === 0) { - const cluster = [photos[i]]; - for (let j = 0; j < photos.length; j += 1) { - if (photos[i] !== photos[j]) { - const a = [photos[i].latitude, photos[i].longitude]; - const b = [photos[j].latitude, photos[j].longitude]; - const distance = distanceBetween(a, b); - if (distance < 10) cluster.push(photos[j]); - } - } - clusters.push(cluster); - } - } - - // create React elements based on the clusters - const markers = clusters.map((cluster) => { - let Wrapper = ({ children }) => <>{children}</>; - if (cluster.length > 1) { - Wrapper = ({ children }) => ( - <MarkerClusterGroup - iconCreateFunction={(markerCluster) => - new DivIcon({ - html: ReactDom.renderToString( - <> - <img - style={{ width: 36, height: 36 }} - src={cluster[0].thumbnail} - alt="" - /> - <span className={styles.markerItemCount}>{markerCluster.getChildCount()}</span> - </>, - ), - iconSize: [36, 36], - className: styles.markerIcon, - }) - } - > - {children} - </MarkerClusterGroup> - ); - } - - const children = cluster.map((photo) => ( - <Marker - key={photo.name} - position={[photo.latitude, photo.longitude]} - icon={ - new Icon({ - iconUrl: photo.thumbnail, - iconSize: [36, 36], - className: styles.markerIcon, - }) - } - eventHandlers={{ click: () => handleMarkerClick(photo.name) }} - /> - )); - - return <Wrapper key={cluster[0].name}>{children}</Wrapper>; - }); - - return markers; - } - - useEffect(() => { - if (map) { - map.setView(bounds.getCenter()); - map.fitBounds(bounds); - } - }, [trip]); - - return ( - <MapContainer - center={bounds.getCenter()} - bounds={bounds} - scrollWheelZoom - whenCreated={setMap} - style={{ height: '100%', width: '100%' }} - keyboard - zoomControl={false} - > - <ZoomControl position="bottomright" /> - <TileLayer - attribution='© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors' - url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" - /> - {trip && <GeoJSON key={trip.name} data={trip.track} />} - {trip && createMarkers(trip.photos)} - </MapContainer> - ); -} diff --git a/components/Sidebar/Sidebar.module.css b/components/Sidebar/Sidebar.module.css deleted file mode 100644 index a3c8a1a..0000000 --- a/components/Sidebar/Sidebar.module.css +++ /dev/null @@ -1,54 +0,0 @@ -.aside { - min-width: 272px; - height: 100%; - padding: 5px; - display: flex; - flex-direction: column; - border-right: 1px solid #e5e5e5; - box-shadow: -2px 0 10px #000; - z-index: 9999; - background-color: #fff; -} - -.aside h2 { - padding-left: 1.5rem; -} - -.list { - margin: 0; - padding: 0; - list-style: none; - overflow-y: auto; -} - -.listItem { - margin-bottom: 5px; - padding: 1rem 2rem 1rem 1.5rem; - display: block; - border-radius: 10px; - cursor: pointer; - user-select: none; - outline: currentcolor none medium; -} - -.listItem:hover { - background-color: #f5f5f5; -} - -.listItemActive { - background-color: #e9f3ff !important; -} - -@media only screen and (max-width: 500px) { - .aside { - position: absolute; - top: 0; - bottom: 0; - left: -100vw; - transition: left 0.3s ease-in; - } - - .asideOpen { - left: 0; - } -} diff --git a/components/Sidebar/Sidebar.tsx b/components/Sidebar/Sidebar.tsx deleted file mode 100644 index 6b0473a..0000000 --- a/components/Sidebar/Sidebar.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import { useEffect, useRef } from 'react'; - -import { Trip } from 'models'; -import { secondsToTimeString } from 'lib/util'; - -import styles from './Sidebar.module.css'; - -interface Props { - trips: Trip[]; - currentTrip: Trip; - asideOpen: boolean; - handleClose: () => void; - setCurrentTrip: (trip: Trip) => void; -} - -export default function Sidebar({ - trips, - currentTrip, - asideOpen, - handleClose, - setCurrentTrip, -}: Props): JSX.Element { - const wrapperRef = useRef(null); - - useEffect(() => { - function handleClickOutside(e: MouseEvent) { - if (wrapperRef.current && !wrapperRef.current.contains(e.target)) { - handleClose(); - } - } - - document.addEventListener('mousedown', handleClickOutside); - return () => { - document.removeEventListener('mousedown', handleClickOutside); - }; - }, [wrapperRef]); - - function handleTripChange(trip: Trip): void { - setCurrentTrip(trip); - handleClose(); - } - - return ( - <aside ref={wrapperRef} className={`${styles.aside} ${asideOpen && styles.asideOpen}`}> - <h2>Trips</h2> - <ul className={styles.list}> - {trips.map((t) => ( - <li key={t.name}> - <a - onClick={() => handleTripChange(t)} - onKeyPress={() => handleTripChange(t)} - tabIndex={0} - role="menuitem" - className={`${styles.listItem} ${ - t.name === currentTrip.name && styles.listItemActive - }`} - > - <b>{new Date(t.start).toDateString()}</b> - <br /> - Total distance: {t.distance} km - <br /> - Duration: {secondsToTimeString(t.duration)} - <br /> - Average speed: {t.speed} km/h - </a> - </li> - ))} - </ul> - </aside> - ); -} |