From 716d05fb3eb5cd266c0e28cc21bcd330455b1f21 Mon Sep 17 00:00:00 2001 From: evlist Date: Sun, 11 Sep 2022 20:01:12 +0200 Subject: [PATCH] Starting again with what has been developed as sandbox/map. --- src/App.tsx | 7 +- src/components/double-touch-handler.tsx | 135 ++++++++++++ src/components/layer.tsx | 26 +++ src/components/map.tsx | 258 ++++++++--------------- src/components/mouse-handler.tsx | 107 ++++++++++ src/components/single-touch-handler.tsx | 103 +++++++++ src/components/tile.tsx | 41 ---- src/components/viewport.tsx | 100 +++++++++ src/declarations/gpx-parser-builder.d.ts | 1 - src/index.tsx | 12 +- src/lib/gpx.ts | 26 --- src/react-app-env.d.ts | 1 + src/service-worker.ts | 80 +++++++ src/serviceWorkerRegistration.ts | 142 +++++++++++++ src/theme/layer.css | 8 + src/theme/map.css | 17 -- src/theme/viewport.css | 11 + 17 files changed, 807 insertions(+), 268 deletions(-) create mode 100644 src/components/double-touch-handler.tsx create mode 100644 src/components/layer.tsx create mode 100644 src/components/mouse-handler.tsx create mode 100644 src/components/single-touch-handler.tsx delete mode 100644 src/components/tile.tsx create mode 100644 src/components/viewport.tsx delete mode 100644 src/declarations/gpx-parser-builder.d.ts delete mode 100644 src/lib/gpx.ts create mode 100644 src/react-app-env.d.ts create mode 100644 src/service-worker.ts create mode 100644 src/serviceWorkerRegistration.ts create mode 100644 src/theme/layer.css delete mode 100644 src/theme/map.css create mode 100644 src/theme/viewport.css diff --git a/src/App.tsx b/src/App.tsx index 91eebdc..b4a59dc 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -19,18 +19,13 @@ import '@ionic/react/css/display.css'; /* Theme variables */ import './theme/variables.css'; -/* Components */ - import Map from './components/map'; -import React from 'react'; setupIonicReact(); const App: React.FC = () => ( - - - + ); diff --git a/src/components/double-touch-handler.tsx b/src/components/double-touch-handler.tsx new file mode 100644 index 0000000..eb3c010 --- /dev/null +++ b/src/components/double-touch-handler.tsx @@ -0,0 +1,135 @@ +import react, { useCallback, useState } from 'react'; + +import _ from 'lodash'; + +import { Transformation } from './viewport'; + +interface DoubleTouchHandlerProps { + applyTransformations: (transformations: Transformation[]) => void; + children: any; +} + +const DoubleTouchHandler: react.FC = ( + props: DoubleTouchHandlerProps +) => { + const initialTouchState = { + state: 'up', + touches: [ + { x: -1, y: -1 }, + { x: -1, y: -1 }, + ], + distance: -1, + initialZoom: 1, + }; + + const [touchState, setTouchState] = useState(initialTouchState); + + console.log('DoubleTouchHandler, touchState: ' + JSON.stringify(touchState)); + + const genericHandler = (event: any) => { + console.log('Log - Event: ' + event.type); + if (event.type.startsWith('touch')) { + if (event.touches.length > 1) { + console.log( + `${event.touches.length} touches, (${event.touches[0].pageX}, ${event.touches[0].pageY}), (${event.touches[1].pageX}, ${event.touches[1].pageY})` + ); + } + + console.log('touchState: ' + JSON.stringify(touchState)); + return; + } + }; + const touchCancelHandler = (event: any) => { + genericHandler(event); + throtteledTouchMoveHandler.cancel(); + setTouchState(initialTouchState); + }; + + const touchStartHandler = (event: any) => { + genericHandler(event); + if (event.touches.length === 2) { + setTouchState({ + state: 'double', + touches: [ + { x: event.touches[0].pageX, y: event.touches[0].pageY }, + { x: event.touches[1].pageX, y: event.touches[1].pageY }, + ], + distance: Math.sqrt( + (event.touches[0].pageX - event.touches[1].pageX) ** 2 + + (event.touches[0].pageY - event.touches[1].pageY) ** 2 + ), + initialZoom: 1, + }); + } + }; + + const touchEndHandler = (event: any) => { + genericHandler(event); + setTouchState(initialTouchState); + throtteledTouchMoveHandler.cancel(); + }; + + const touchMoveHandler = (event: any) => { + if (touchState.state === 'double') { + if (event.touches.length === 2) { + genericHandler(event); + const newDistance = Math.sqrt( + (event.touches[0].pageX - event.touches[1].pageX) ** 2 + + (event.touches[0].pageY - event.touches[1].pageY) ** 2 + ); + const factor = newDistance / touchState.distance; + console.log(`+++++++++ ZOOM Factor is ${factor} ++++++++++`); + setTouchState({ + state: 'double', + touches: [ + { x: event.touches[0].pageX, y: event.touches[0].pageY }, + { x: event.touches[1].pageX, y: event.touches[1].pageY }, + ], + distance: touchState.distance, + initialZoom: 1, + }); + const previousCenter = { + x: (touchState.touches[0].x + touchState.touches[1].x) / 2, + y: (touchState.touches[0].y + touchState.touches[1].y) / 2, + }; + const currentCenter = { + x: (event.touches[0].pageX + event.touches[1].pageX) / 2, + y: (event.touches[0].pageY + event.touches[1].pageY) / 2, + }; + props.applyTransformations([ + { + scale: { + factor: factor, + center: currentCenter, + }, + }, + { + translate: { + x: currentCenter.x - previousCenter.x, + y: currentCenter.y - previousCenter.y, + }, + }, + ]); + } + } + }; + + const throtteledTouchMoveHandler = useCallback( + _.throttle(touchMoveHandler, 100), + [touchState.state] + ); + + return ( +
+ {props.children} +
+ ); +}; + +export default DoubleTouchHandler; diff --git a/src/components/layer.tsx b/src/components/layer.tsx new file mode 100644 index 0000000..37fe355 --- /dev/null +++ b/src/components/layer.tsx @@ -0,0 +1,26 @@ +import react from 'react'; + +import { ViewportState } from './viewport'; + +import '../theme/layer.css'; + +const Layer: react.FC<{ + viewportState: ViewportState; + children?: JSX.Element; +}> = (props: { viewportState: ViewportState; children?: JSX.Element }) => { + const { children: children, ...argProps } = props; + console.log(`--- Rendering layer, props: ${JSON.stringify(argProps)} ---`); + + return ( +
+ {props.children} +
+ ); +}; + +export default Layer; diff --git a/src/components/map.tsx b/src/components/map.tsx index 165ecad..4fede39 100644 --- a/src/components/map.tsx +++ b/src/components/map.tsx @@ -1,179 +1,91 @@ -import _, { round } from 'lodash'; -import react, { useState, useEffect, useMemo, useRef } from 'react'; - -import Tile from './tile'; - -import '../theme/map.css'; - -export const tileSize = 256; - -// cf https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#ECMAScript_(JavaScript/ActionScript,_etc.) - -const lon2tile = (lon: number, zoom: number) => { - const real = ((lon + 180) / 360) * Math.pow(2, zoom); - const floor = Math.floor(real); - return [floor, real - floor]; -}; -const lat2tile = (lat: number, zoom: number) => { - const real = - ((1 - - Math.log( - Math.tan((lat * Math.PI) / 180) + 1 / Math.cos((lat * Math.PI) / 180) - ) / - Math.PI) / - 2) * - Math.pow(2, zoom); - const floor = Math.floor(real); - return [floor, real - floor]; -}; - -function tile2long(x: number, z: number) { - return (x / Math.pow(2, z)) * 360 - 180; -} - -function tile2lat(y: number, z: number) { - var n = Math.PI - (2 * Math.PI * y) / Math.pow(2, z); - return (180 / Math.PI) * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n))); -} - -const Map: react.FC = () => { - console.log('Log - Rendering '); - - const initialCenter: [number, number] = [43.57029965, 3.94242897]; - const initialZoom: number = 17; - const [center, setCenter] = useState(initialCenter); - const [zoom, setZoom] = useState(initialZoom); - - const [dimensions, setDimensions] = useState({ - height: window.innerHeight, - width: window.innerWidth, - }); - - const resizeHandler = () => { - setDimensions({ - height: window.innerHeight, - width: window.innerWidth, - }); - }; - const debouncedResizeHandler = useMemo( - () => _.debounce(resizeHandler, 300), - [dimensions, zoom, center] - ); - - useEffect(() => { - window.addEventListener('resize', debouncedResizeHandler); - }, []); - - const nbTilesY = _.ceil(dimensions.height / tileSize) + 4; - const nbTilesX = _.ceil(dimensions.width / tileSize) + 4; - const [tileCenterY, reminderY] = lat2tile(center[0], zoom); - const [tileCenterX, reminderX] = lon2tile(center[1], zoom); - const firstTileY = tileCenterY - _.round(nbTilesY / 2); - const firstTileX = tileCenterX - _.round(nbTilesX / 2); - const locationY = (tileCenterY + reminderY - firstTileY) * tileSize; - const locationX = (tileCenterX + reminderX - firstTileX) * tileSize; - const targetLocationY = dimensions.height / 2; - const targetLocationX = dimensions.width / 2; - const deltaY = -targetLocationY + locationY; - const deltaX = -targetLocationX + locationX; - - const [delta, setDelta] = useState({ X: deltaX, Y: deltaY }); - - const genericHandler = (event: any) => { - console.log('Log - Event: ' + event.type); - }; - - const initialMouseState = { - down: false, - starting: { x: -1, y: -1 }, - }; - - const mouseState = useRef(initialMouseState); - - const mouseDownHandler = (event: any) => { - event.preventDefault(); - mouseState.current = { - down: true, - starting: { x: event.pageX, y: event.pageY }, - }; - genericHandler(event); - }; - - const mouseUpHandler = (event: any) => { - event.preventDefault(); - console.log('Log - Up, now do something ! '); - const newCenterY = - tileCenterY + - reminderY + - (deltaX + mouseState.current.starting.x - event.pageX); - const newCenterX = - tileCenterX + - reminderX + - (deltaY + mouseState.current.starting.y - event.pageY); - setCenter([tile2lat(newCenterY, zoom), tile2long(newCenterX, zoom)]); - mouseState.current = initialMouseState; - genericHandler(event); - }; - - const mouseMoveHandler = (event: any) => { - if (mouseState.current.down) { - event.preventDefault(); - console.log('Log - Moving...' + event.pageX); - setDelta({ - X: deltaX + mouseState.current.starting.x - event.pageX, - Y: deltaY + mouseState.current.starting.y - event.pageY, - }); - genericHandler(event); - } - }; - - const debouncedMouseMoveHandler = useMemo( - () => _.throttle(mouseMoveHandler, 100), - [] - ); - - const pointerDownHandler = (event: any) => { - event.preventDefault(); - console.log('PointerDown'); - }; - - const pointerUpHandler = (event: any) => { - event.preventDefault(); - console.log('PointerUp'); - }; - - const pointerMoveHandler = (event: any) => { - event.preventDefault(); - console.log('PointerMove'); - }; +import react from 'react'; +import Viewport from './viewport'; +const Map: react.FC<{}> = (props: {}) => { return ( -
- {_.range(nbTilesY).map((iy) => ( -
- {_.range(nbTilesX).map((ix) => ( - - ))} + +
+
+ + + +
- ))} -
+
+ + + + +
+
+ + + + +
+ +
+ ); }; diff --git a/src/components/mouse-handler.tsx b/src/components/mouse-handler.tsx new file mode 100644 index 0000000..b63d2f5 --- /dev/null +++ b/src/components/mouse-handler.tsx @@ -0,0 +1,107 @@ +import react, { useCallback, useState } from 'react'; + +import _ from 'lodash'; + +import { Transformation } from './viewport'; + +interface MouseHandlerProps { + applyTransformations: (transformations: Transformation[]) => void; + children: any; +} + +const MouseHandler: react.FC = ( + props: MouseHandlerProps +) => { + const initialMouseState = { + down: false, + starting: { x: -1, y: -1 }, + }; + + const [mouseState, setMouseState] = useState(initialMouseState); + + console.log('MouseHandler, mouseState: ' + JSON.stringify(mouseState)); + + const genericHandler = (event: any) => { + console.log('Log - Event: ' + event.type); + if (event.pageX !== undefined) { + console.log(`Mouse : ${event.pageX}, ${event.pageY}`); + console.log('mouseState: ' + JSON.stringify(mouseState)); + return; + } + }; + + const mouseLeaveHandler = (event: any) => { + genericHandler(event); + throtteledMouseMoveHandler.cancel(); + setMouseState(initialMouseState); + }; + + const mouseDownHandler = (event: any) => { + event.preventDefault(); + genericHandler(event); + setMouseState({ + down: true, + starting: { x: event.pageX, y: event.pageY }, + }); + }; + + const mouseUpHandler = (event: any) => { + genericHandler(event); + event.preventDefault(); + setMouseState(initialMouseState); + }; + + const mouseMoveHandler = (event: any) => { + event.preventDefault(); + if (mouseState.down) { + genericHandler(event); + props.applyTransformations([ + { + translate: { + x: event.pageX - mouseState.starting.x, + y: event.pageY - mouseState.starting.y, + }, + }, + ]); + setMouseState({ + down: true, + starting: { + x: event.pageX, + y: event.pageY, + }, + }); + } + }; + + const throtteledMouseMoveHandler = useCallback( + _.throttle(mouseMoveHandler, 50), + [mouseState.down] + ); + + const doubleClickHandler = (event: any) => { + genericHandler(event); + props.applyTransformations([ + { + scale: { + factor: 2, + center: { x: event.pageX, y: event.pageY }, + }, + }, + ]); + }; + + return ( +
+ {props.children} +
+ ); +}; + +export default MouseHandler; diff --git a/src/components/single-touch-handler.tsx b/src/components/single-touch-handler.tsx new file mode 100644 index 0000000..f7cc068 --- /dev/null +++ b/src/components/single-touch-handler.tsx @@ -0,0 +1,103 @@ +import react, { useCallback, useState } from 'react'; + +import _ from 'lodash'; + +import { Transformation } from './viewport'; + +interface SingleTouchHandlerProps { + applyTransformations: (transformations: Transformation[]) => void; + children: any; +} + +const SingleTouchHandler: react.FC = ( + props: SingleTouchHandlerProps +) => { + const initialTouchState = { + state: 'up', + touch: { x: -1, y: -1 }, + }; + + const [touchState, setTouchState] = useState(initialTouchState); + + console.log('SingleTouchHandler, touchState: ' + JSON.stringify(touchState)); + + const genericHandler = (event: any) => { + console.log('Log - Event: ' + event.type); + if (event.type.startsWith('touch')) { + if (event.touches.length > 0) { + console.log( + `Touch1 : (${event.touches[0].pageX}, ${event.touches[0].pageY})` + ); + } + console.log('touchState: ' + JSON.stringify(touchState)); + return; + } + }; + + const touchCancelHandler = (event: any) => { + genericHandler(event); + throtteledTouchMoveHandler.cancel(); + setTouchState(initialTouchState); + }; + + const touchStartHandler = (event: any) => { + genericHandler(event); + // event.preventDefault(); + if (event.touches.length === 1) { + setTouchState({ + state: 'pointer', + touch: { x: event.touches[0].pageX, y: event.touches[0].pageY }, + }); + } + }; + + const touchEndHandler = (event: any) => { + genericHandler(event); + // event.preventDefault(); + setTouchState(initialTouchState); + throtteledTouchMoveHandler.cancel(); + }; + + const touchMoveHandler = (event: any) => { + // event.preventDefault(); + if (touchState.state === 'pointer') { + if (event.touches.length === 1) { + genericHandler(event); + props.applyTransformations([ + { + translate: { + x: event.touches[0].pageX - touchState.touch.x, + y: event.touches[0].pageY - touchState.touch.y, + }, + }, + ]); + setTouchState({ + state: 'pointer', + touch: { + x: event.touches[0].pageX, + y: event.touches[0].pageY, + }, + }); + } + } + }; + + const throtteledTouchMoveHandler = useCallback( + _.throttle(touchMoveHandler, 100), + [touchState.state] + ); + + return ( +
+ {props.children} +
+ ); +}; + +export default SingleTouchHandler; diff --git a/src/components/tile.tsx b/src/components/tile.tsx deleted file mode 100644 index 19d7cfc..0000000 --- a/src/components/tile.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import React from 'react'; -import { tileSize } from './map'; - -const tileProvider = (zoom: number, x: number, y: number) => - 'https://tile.openstreetmap.org/' + zoom + '/' + x + '/' + y + '.png'; - -const Tile: React.FC<{ - ix: number; - iy: number; - x: number; - y: number; - delta: any; - zoom: number; -}> = (props: { - ix: number; - iy: number; - x: number; - y: number; - delta: any; - zoom: number; -}) => { - const style = { - width: tileSize + 'px', - height: tileSize + 'px', - transform: - 'translate3d(' + - (props.ix * tileSize - props.delta.X) + - 'px, ' + - (props.iy * tileSize - props.delta.Y) + - 'px, 0px)', - }; - return ( - - ); -}; - -export default Tile; diff --git a/src/components/viewport.tsx b/src/components/viewport.tsx new file mode 100644 index 0000000..456db18 --- /dev/null +++ b/src/components/viewport.tsx @@ -0,0 +1,100 @@ +import react, { useCallback, useState } from 'react'; + +import _, { constant } from 'lodash'; + +import MouseHandler from './mouse-handler'; +import Layer from './layer'; + +import '../theme/viewport.css'; +import SingleTouchHandler from './single-touch-handler'; +import DoubleTouchHandler from './double-touch-handler'; + +export interface Point { + x: number; + y: number; +} + +export interface Translation { + translate: Point; +} + +export interface Scale { + scale: { + center: Point; + factor: number; + }; +} + +export type Transformation = Translation | Scale; + +// const transform1: Transformation = { translate: { x: 0, y: 1 } }; +// const transform2: Transformation = { +// scale: { center: { x: 10, y: 20 }, factor: 2 }, +// }; + +interface ViewportProps { + children: any; +} + +export interface ViewportState { + scale: number; + translation: Point; +} + +const Viewport: react.FC = (props: ViewportProps) => { + //console.log(`--- Rendering viewport, props: ${JSON.stringify(props)} ---`); + + const initialState: ViewportState = { scale: 1, translation: { x: 0, y: 0 } }; + + const [state, setState] = useState(initialState); + + const genericHandler = (event: any) => { + console.log('Log - Event: ' + event.type); + return; + }; + + const applyTransformations = (transformations: Transformation[]) => { + const newState = transformations.reduce( + (previousState: ViewportState, transformation): ViewportState => { + if ('scale' in transformation) { + return { + scale: previousState.scale * transformation.scale.factor, + translation: { + x: + previousState.translation.x + + (previousState.translation.x - transformation.scale.center.x) * + (transformation.scale.factor - 1), + y: + previousState.translation.y + + (previousState.translation.y - transformation.scale.center.y) * + (transformation.scale.factor - 1), + }, + }; + } + return { + scale: previousState.scale, + translation: { + x: previousState.translation.x + transformation.translate.x, + y: previousState.translation.y + transformation.translate.y, + }, + }; + }, + state + ); + setState(newState); + }; + + return ( +
+ + + + {props.children} + + + +
+ ); +}; + +export default Viewport; diff --git a/src/declarations/gpx-parser-builder.d.ts b/src/declarations/gpx-parser-builder.d.ts deleted file mode 100644 index 8e9dbe3..0000000 --- a/src/declarations/gpx-parser-builder.d.ts +++ /dev/null @@ -1 +0,0 @@ -declare module 'gpx-parser-builder' \ No newline at end of file diff --git a/src/index.tsx b/src/index.tsx index 35a930f..dcd0a01 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,9 +1,13 @@ import React from 'react'; - -import App from './App'; - import { createRoot } from 'react-dom/client'; +import App from './App'; const container = document.getElementById('root'); const root = createRoot(container!); -root.render(); +root.render( + + + +); + + diff --git a/src/lib/gpx.ts b/src/lib/gpx.ts deleted file mode 100644 index d380aea..0000000 --- a/src/lib/gpx.ts +++ /dev/null @@ -1,26 +0,0 @@ -import _ from 'lodash'; - -const initialState: any = { - current: { - $: {}, - trk: [ - { - trkseg: [{ trkpt: [] }], - }, - ], - }, -}; - -export const appendTrkpt = (gpx:any , trkpt:any) => { - var updatedGpx = _.cloneDeep(gpx); - updatedGpx.trk[0].trkseg[0].trkpt.push(trkpt); - return updatedGpx; -}; - -export const clearTrkpt = (gpx:any) => { - var updatedGpx = _.cloneDeep(gpx); - updatedGpx.trk[0]=[]; - return updatedGpx; -}; - - diff --git a/src/react-app-env.d.ts b/src/react-app-env.d.ts new file mode 100644 index 0000000..6431bc5 --- /dev/null +++ b/src/react-app-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/src/service-worker.ts b/src/service-worker.ts new file mode 100644 index 0000000..652a8a4 --- /dev/null +++ b/src/service-worker.ts @@ -0,0 +1,80 @@ +/// +/* eslint-disable no-restricted-globals */ + +// This service worker can be customized! +// See https://developers.google.com/web/tools/workbox/modules +// for the list of available Workbox modules, or add any other +// code you'd like. +// You can also remove this file if you'd prefer not to use a +// service worker, and the Workbox build step will be skipped. + +import { clientsClaim } from 'workbox-core'; +import { ExpirationPlugin } from 'workbox-expiration'; +import { precacheAndRoute, createHandlerBoundToURL } from 'workbox-precaching'; +import { registerRoute } from 'workbox-routing'; +import { StaleWhileRevalidate } from 'workbox-strategies'; + +declare const self: ServiceWorkerGlobalScope; + +clientsClaim(); + +// Precache all of the assets generated by your build process. +// Their URLs are injected into the manifest variable below. +// This variable must be present somewhere in your service worker file, +// even if you decide not to use precaching. See https://cra.link/PWA +precacheAndRoute(self.__WB_MANIFEST); + +// Set up App Shell-style routing, so that all navigation requests +// are fulfilled with your index.html shell. Learn more at +// https://developers.google.com/web/fundamentals/architecture/app-shell +const fileExtensionRegexp = new RegExp('/[^/?]+\\.[^/]+$'); +registerRoute( + // Return false to exempt requests from being fulfilled by index.html. + ({ request, url }: { request: Request; url: URL }) => { + // If this isn't a navigation, skip. + if (request.mode !== 'navigate') { + return false; + } + + // If this is a URL that starts with /_, skip. + if (url.pathname.startsWith('/_')) { + return false; + } + + // If this looks like a URL for a resource, because it contains + // a file extension, skip. + if (url.pathname.match(fileExtensionRegexp)) { + return false; + } + + // Return true to signal that we want to use the handler. + return true; + }, + createHandlerBoundToURL(process.env.PUBLIC_URL + '/index.html') +); + +// An example runtime caching route for requests that aren't handled by the +// precache, in this case same-origin .png requests like those from in public/ +registerRoute( + // Add in any other file extensions or routing criteria as needed. + ({ url }) => url.origin === self.location.origin && url.pathname.endsWith('.png'), + // Customize this strategy as needed, e.g., by changing to CacheFirst. + new StaleWhileRevalidate({ + cacheName: 'images', + plugins: [ + // Ensure that once this runtime cache reaches a maximum size the + // least-recently used images are removed. + new ExpirationPlugin({ maxEntries: 50 }), + ], + }) +); + +// This allows the web app to trigger skipWaiting via +// registration.waiting.postMessage({type: 'SKIP_WAITING'}) +self.addEventListener('message', (event) => { + if (event.data && event.data.type === 'SKIP_WAITING') { + self.skipWaiting(); + } +}); + +// Any other custom service worker logic can go here. diff --git a/src/serviceWorkerRegistration.ts b/src/serviceWorkerRegistration.ts new file mode 100644 index 0000000..efbf2ac --- /dev/null +++ b/src/serviceWorkerRegistration.ts @@ -0,0 +1,142 @@ +// This optional code is used to register a service worker. +// register() is not called by default. + +// This lets the app load faster on subsequent visits in production, and gives +// it offline capabilities. However, it also means that developers (and users) +// will only see deployed updates on subsequent visits to a page, after all the +// existing tabs open on the page have been closed, since previously cached +// resources are updated in the background. + +// To learn more about the benefits of this model and instructions on how to +// opt-in, read https://cra.link/PWA + +const isLocalhost = Boolean( + window.location.hostname === 'localhost' || + // [::1] is the IPv6 localhost address. + window.location.hostname === '[::1]' || + // 127.0.0.0/8 are considered localhost for IPv4. + window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/) +); + +type Config = { + onSuccess?: (registration: ServiceWorkerRegistration) => void; + onUpdate?: (registration: ServiceWorkerRegistration) => void; +}; + +export function register(config?: Config) { + if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { + // The URL constructor is available in all browsers that support SW. + const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); + if (publicUrl.origin !== window.location.origin) { + // Our service worker won't work if PUBLIC_URL is on a different origin + // from what our page is served on. This might happen if a CDN is used to + // serve assets; see https://github.com/facebook/create-react-app/issues/2374 + return; + } + + window.addEventListener('load', () => { + const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; + + if (isLocalhost) { + // This is running on localhost. Let's check if a service worker still exists or not. + checkValidServiceWorker(swUrl, config); + + // Add some additional logging to localhost, pointing developers to the + // service worker/PWA documentation. + navigator.serviceWorker.ready.then(() => { + console.log( + 'This web app is being served cache-first by a service ' + + 'worker. To learn more, visit https://cra.link/PWA' + ); + }); + } else { + // Is not localhost. Just register service worker + registerValidSW(swUrl, config); + } + }); + } +} + +function registerValidSW(swUrl: string, config?: Config) { + navigator.serviceWorker + .register(swUrl) + .then((registration) => { + registration.onupdatefound = () => { + const installingWorker = registration.installing; + if (installingWorker == null) { + return; + } + installingWorker.onstatechange = () => { + if (installingWorker.state === 'installed') { + if (navigator.serviceWorker.controller) { + // At this point, the updated precached content has been fetched, + // but the previous service worker will still serve the older + // content until all client tabs are closed. + console.log( + 'New content is available and will be used when all ' + + 'tabs for this page are closed. See https://cra.link/PWA.' + ); + + // Execute callback + if (config && config.onUpdate) { + config.onUpdate(registration); + } + } else { + // At this point, everything has been precached. + // It's the perfect time to display a + // "Content is cached for offline use." message. + console.log('Content is cached for offline use.'); + + // Execute callback + if (config && config.onSuccess) { + config.onSuccess(registration); + } + } + } + }; + }; + }) + .catch((error) => { + console.error('Error during service worker registration:', error); + }); +} + +function checkValidServiceWorker(swUrl: string, config?: Config) { + // Check if the service worker can be found. If it can't reload the page. + fetch(swUrl, { + headers: { 'Service-Worker': 'script' }, + }) + .then((response) => { + // Ensure service worker exists, and that we really are getting a JS file. + const contentType = response.headers.get('content-type'); + if ( + response.status === 404 || + (contentType != null && contentType.indexOf('javascript') === -1) + ) { + // No service worker found. Probably a different app. Reload the page. + navigator.serviceWorker.ready.then((registration) => { + registration.unregister().then(() => { + window.location.reload(); + }); + }); + } else { + // Service worker found. Proceed as normal. + registerValidSW(swUrl, config); + } + }) + .catch(() => { + console.log('No internet connection found. App is running in offline mode.'); + }); +} + +export function unregister() { + if ('serviceWorker' in navigator) { + navigator.serviceWorker.ready + .then((registration) => { + registration.unregister(); + }) + .catch((error) => { + console.error(error.message); + }); + } +} diff --git a/src/theme/layer.css b/src/theme/layer.css new file mode 100644 index 0000000..e3cf5f5 --- /dev/null +++ b/src/theme/layer.css @@ -0,0 +1,8 @@ +.background { + position: fixed; + width: 4032px; + height: 2268px; + z-index: -1; + transform-origin: top left; +} + diff --git a/src/theme/map.css b/src/theme/map.css deleted file mode 100644 index db47bb0..0000000 --- a/src/theme/map.css +++ /dev/null @@ -1,17 +0,0 @@ -.tiles { - position: relative; - width: 100%; - height: 100%; -} - -.tiles p { - z-index: 20; - position: relative; - width: 100%; - height: 100%; -} - -.tiles img { - max-width: none; - position: absolute; -} diff --git a/src/theme/viewport.css b/src/theme/viewport.css new file mode 100644 index 0000000..8b4137e --- /dev/null +++ b/src/theme/viewport.css @@ -0,0 +1,11 @@ +.viewport { + position: fixed; + width: 100%; + height: 100%; +} + +.background img { + width: 4032px; + height: 2268px; + } + \ No newline at end of file