Starting again...
This commit is contained in:
parent
1c403ae819
commit
e75b2f670c
|
@ -1,8 +1,8 @@
|
|||
import { CapacitorConfig } from '@capacitor/cli';
|
||||
|
||||
const config: CapacitorConfig = {
|
||||
appId: 'com.dyomedea.dyomedea2',
|
||||
appName: 'dyomedea2',
|
||||
appId: 'io.ionic.starter',
|
||||
appName: 'dyomedeaOl',
|
||||
webDir: 'build',
|
||||
bundledWebRuntime: false
|
||||
};
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "dyomedea",
|
||||
"name": "dyomedeaOl",
|
||||
"integrations": {
|
||||
"capacitor": {}
|
||||
},
|
||||
|
|
89
package.json
89
package.json
|
@ -1,55 +1,45 @@
|
|||
{
|
||||
"name": "dyomedea",
|
||||
"version": "0.0.2",
|
||||
"name": "dyomedeaOl",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@awesome-cordova-plugins/geolocation": "^6.2.0",
|
||||
"@capacitor/android": "^4.5.0",
|
||||
"@capacitor/app": "^4.1.1",
|
||||
"@capacitor/core": "^4.5.0",
|
||||
"@capacitor/haptics": "^4.1.0",
|
||||
"@capacitor/keyboard": "^4.1.0",
|
||||
"@capacitor/status-bar": "^4.1.0",
|
||||
"@ionic/react": "^6.3.7",
|
||||
"@ionic/react-router": "^6.3.7",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/user-event": "^14.4.3",
|
||||
"@types/jest": "^29.2.3",
|
||||
"@types/node": "^18.11.9",
|
||||
"@types/react": "^18.0.25",
|
||||
"@types/react-dom": "^18.0.9",
|
||||
"@types/react-router": "^5.1.19",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"ionicons": "^6.0.4",
|
||||
"isomorphic-xml2js": "^0.1.3",
|
||||
"jotai": "^1.10.0",
|
||||
"jotai-location": "^0.2.0",
|
||||
"lodash": "^4.17.21",
|
||||
"pouchdb": "^7.3.1",
|
||||
"pouchdb-browser": "^7.3.1",
|
||||
"pouchdb-find": "^7.3.1",
|
||||
"@capacitor/app": "4.1.1",
|
||||
"@capacitor/core": "4.5.0",
|
||||
"@capacitor/haptics": "4.1.0",
|
||||
"@capacitor/keyboard": "4.1.0",
|
||||
"@capacitor/status-bar": "4.1.0",
|
||||
"@ionic/react": "^6.0.0",
|
||||
"@ionic/react-router": "^6.0.0",
|
||||
"@testing-library/jest-dom": "^5.11.9",
|
||||
"@testing-library/react": "^13.3.0",
|
||||
"@testing-library/user-event": "^12.6.3",
|
||||
"@types/jest": "^26.0.20",
|
||||
"@types/node": "^12.19.15",
|
||||
"@types/react": "^18.0.17",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@types/react-router": "^5.1.11",
|
||||
"@types/react-router-dom": "^5.1.7",
|
||||
"history": "^4.9.0",
|
||||
"ionicons": "^6.0.3",
|
||||
"react": "^18.2.0",
|
||||
"react-cool-dimensions": "^2.0.7",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-localization": "^1.0.19",
|
||||
"react-router": "^6.4.3",
|
||||
"react-router-dom": "^6.4.3",
|
||||
"react-scripts": "^5.0.1",
|
||||
"typedoc": "^0.23.21",
|
||||
"typescript": "^4.9.3",
|
||||
"web-vitals": "^3.1.0",
|
||||
"workbox-background-sync": "^6.5.4",
|
||||
"workbox-broadcast-update": "^6.5.4",
|
||||
"workbox-cacheable-response": "^6.5.4",
|
||||
"workbox-core": "^6.5.4",
|
||||
"workbox-expiration": "^6.5.4",
|
||||
"workbox-navigation-preload": "^6.5.4",
|
||||
"workbox-precaching": "^6.5.4",
|
||||
"workbox-range-requests": "^6.5.4",
|
||||
"workbox-routing": "^6.5.4",
|
||||
"workbox-strategies": "^6.5.4",
|
||||
"workbox-streams": "^6.5.4"
|
||||
"react-router": "^5.2.0",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-scripts": "^5.0.0",
|
||||
"typescript": "^4.1.3",
|
||||
"web-vitals": "^0.2.4",
|
||||
"workbox-background-sync": "^5.1.4",
|
||||
"workbox-broadcast-update": "^5.1.4",
|
||||
"workbox-cacheable-response": "^5.1.4",
|
||||
"workbox-core": "^5.1.4",
|
||||
"workbox-expiration": "^5.1.4",
|
||||
"workbox-google-analytics": "^5.1.4",
|
||||
"workbox-navigation-preload": "^5.1.4",
|
||||
"workbox-precaching": "^5.1.4",
|
||||
"workbox-range-requests": "^5.1.4",
|
||||
"workbox-routing": "^5.1.4",
|
||||
"workbox-strategies": "^5.1.4",
|
||||
"workbox-streams": "^5.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
|
@ -76,10 +66,7 @@
|
|||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@capacitor/cli": "^4.5.0",
|
||||
"@ionic/cli": "^6.20.4",
|
||||
"@types/lodash": "^4.14.189",
|
||||
"@types/pouchdb": "^6.4.0"
|
||||
"@capacitor/cli": "4.5.0"
|
||||
},
|
||||
"description": "An Ionic project"
|
||||
}
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
<svg width="1000" height="1000" xmlns="http://www.w3.org/2000/svg">
|
||||
<g transform="scale(256) translate(-110000, -110000) ">
|
||||
<path d=" M 110000,110000 L 111000, 111000" pointer-events="none" style="
|
||||
vector-effect: non-scaling-stroke;
|
||||
stroke-width: 2px
|
||||
|
||||
fill: transparent;
|
||||
stroke: black;
|
||||
"/>
|
||||
</g>
|
||||
</svg>
|
||||
<!--
|
||||
vector-effect: non-scaling-stroke;
|
||||
stroke-width: 2
|
||||
-->
|
||||
<!--
|
||||
stroke-width: 0.00781211;
|
||||
-->
|
Before Width: | Height: | Size: 428 B |
|
@ -3,6 +3,6 @@ import { render } from '@testing-library/react';
|
|||
import App from './App';
|
||||
|
||||
test('renders without crashing', () => {
|
||||
// const { baseElement } = render(<App />);
|
||||
// expect(baseElement).toBeDefined();
|
||||
const { baseElement } = render(<App />);
|
||||
expect(baseElement).toBeDefined();
|
||||
});
|
||||
|
|
154
src/App.tsx
154
src/App.tsx
|
@ -1,12 +1,7 @@
|
|||
import {
|
||||
IonApp,
|
||||
IonButtons,
|
||||
IonContent,
|
||||
IonFooter,
|
||||
IonHeader,
|
||||
IonToolbar,
|
||||
setupIonicReact,
|
||||
} from '@ionic/react';
|
||||
import { Redirect, Route } from 'react-router-dom';
|
||||
import { IonApp, IonRouterOutlet, setupIonicReact } from '@ionic/react';
|
||||
import { IonReactRouter } from '@ionic/react-router';
|
||||
import Home from './pages/Home';
|
||||
|
||||
/* Core CSS required for Ionic components to work properly */
|
||||
import '@ionic/react/css/core.css';
|
||||
|
@ -27,136 +22,21 @@ import '@ionic/react/css/display.css';
|
|||
/* Theme variables */
|
||||
import './theme/variables.css';
|
||||
|
||||
import dispatch from './workers/dispatcher-main';
|
||||
|
||||
import LiveMap from './components/map/LiveMap';
|
||||
|
||||
import { atom, useAtom } from 'jotai';
|
||||
import { atomWithHash } from 'jotai-location';
|
||||
import { MapScope } from './components/map/types';
|
||||
import { debounce } from 'lodash';
|
||||
import GetLocation from './components/buttons/GetLocation';
|
||||
import { geoPoint } from './components/map/types';
|
||||
import Back from './components/buttons/Back';
|
||||
import Forward from './components/buttons/Forward';
|
||||
import CurrentLocation from './components/map/CurrentLocation';
|
||||
import GpxImport from './components/dialogs/GpxImport';
|
||||
import Gpxes from './components/map/Gpxes';
|
||||
import MapChooser from './components/dialogs/MapChooser';
|
||||
import Explorer from './components/dialogs/Explorer';
|
||||
// import { initDb } from './db';
|
||||
// import PouchDB from 'pouchdb';
|
||||
// import PouchDBFind from 'pouchdb-find';
|
||||
// PouchDB.plugin(PouchDBFind);
|
||||
|
||||
setupIonicReact();
|
||||
|
||||
// See https://stackoverflow.com/questions/71538643/property-wakelock-does-not-exist-on-type-navigator
|
||||
const requestWakeLock = async () => {
|
||||
const anyNav: any = navigator;
|
||||
if ('wakeLock' in navigator) {
|
||||
try {
|
||||
const wakeLock = await anyNav['wakeLock'].request('screen');
|
||||
} catch (err: any) {
|
||||
// The wake lock request fails - usually system-related, such as low battery.
|
||||
console.log(`Wake lock request failed: ${err.name}, ${err.message}`);
|
||||
}
|
||||
} else {
|
||||
console.log('No wake lock support here...');
|
||||
}
|
||||
};
|
||||
|
||||
const handleVisibilityChange = () => {
|
||||
if (document.hidden) {
|
||||
console.log('Application hidden');
|
||||
} else {
|
||||
console.log('Application visible');
|
||||
requestWakeLock();
|
||||
}
|
||||
};
|
||||
|
||||
// See https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API
|
||||
document.addEventListener('visibilitychange', handleVisibilityChange, false);
|
||||
requestWakeLock();
|
||||
|
||||
const initialScope: MapScope = {
|
||||
center: { lat: -37.8403508, lon: 77.5539501 },
|
||||
zoom: 13,
|
||||
tileProvider: 'osm',
|
||||
};
|
||||
const scopeAtom = atomWithHash('scope', initialScope);
|
||||
|
||||
export const setCenterAtom = atom(null, (get, set, center: geoPoint) => {
|
||||
const previousScope = get(scopeAtom);
|
||||
const newScope: MapScope = {
|
||||
...previousScope,
|
||||
center: center,
|
||||
};
|
||||
set(scopeAtom, newScope);
|
||||
});
|
||||
|
||||
export const tileProviderAtom = atom(
|
||||
(get) => get(scopeAtom).tileProvider,
|
||||
(get, set, tileProvider: string) => {
|
||||
const previousScope = get(scopeAtom);
|
||||
const newScope: MapScope = {
|
||||
...previousScope,
|
||||
tileProvider,
|
||||
};
|
||||
set(scopeAtom, newScope);
|
||||
}
|
||||
const App: React.FC = () => (
|
||||
<IonApp>
|
||||
<IonReactRouter>
|
||||
<IonRouterOutlet>
|
||||
<Route exact path="/home">
|
||||
<Home />
|
||||
</Route>
|
||||
<Route exact path="/">
|
||||
<Redirect to="/home" />
|
||||
</Route>
|
||||
</IonRouterOutlet>
|
||||
</IonReactRouter>
|
||||
</IonApp>
|
||||
);
|
||||
|
||||
// const db = new PouchDB('dyomedea', { auto_compaction: true, revs_limit: 10 });
|
||||
// initDb(db);
|
||||
|
||||
dispatch({ action: 'initDb' });
|
||||
//dispatch({ action: 'getGpxesForViewport', params: { type: 'tech' } });
|
||||
/**
|
||||
*
|
||||
* @returns The root app component
|
||||
*/
|
||||
const App: React.FC = () => {
|
||||
const [scope, setScope] = useAtom(scopeAtom);
|
||||
|
||||
console.log(`App, scope: ${JSON.stringify(scope)}`);
|
||||
return (
|
||||
<IonApp>
|
||||
<IonContent fullscreen={true}>
|
||||
<IonApp>
|
||||
<LiveMap
|
||||
scope={scope}
|
||||
setScope={debounce(setScope, 1000)}
|
||||
numberOfTiledLayers={5}
|
||||
slippyGraphics={[
|
||||
<CurrentLocation key='currentLocation' />,
|
||||
<Gpxes key='gpxes' />,
|
||||
]}
|
||||
/>
|
||||
</IonApp>
|
||||
</IonContent>
|
||||
<Explorer/>
|
||||
<IonHeader className='ion-no-border' translucent={true}>
|
||||
<IonToolbar>
|
||||
<IonButtons slot='start'>
|
||||
<Back />
|
||||
<Forward />
|
||||
</IonButtons>
|
||||
<IonButtons slot='end'>
|
||||
<GpxImport />
|
||||
<MapChooser />
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonFooter className='ion-no-border'>
|
||||
<IonToolbar>
|
||||
<IonButtons>
|
||||
<GetLocation />
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonFooter>
|
||||
</IonApp>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
.container {
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.container strong {
|
||||
font-size: 20px;
|
||||
line-height: 26px;
|
||||
}
|
||||
|
||||
.container p {
|
||||
font-size: 16px;
|
||||
line-height: 22px;
|
||||
color: #8c8c8c;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.container a {
|
||||
text-decoration: none;
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
import './ExploreContainer.css';
|
||||
|
||||
interface ContainerProps { }
|
||||
|
||||
const ExploreContainer: React.FC<ContainerProps> = () => {
|
||||
return (
|
||||
<div className="container">
|
||||
<strong>Ready to create an app?</strong>
|
||||
<p>Start with Ionic <a target="_blank" rel="noopener noreferrer" href="https://ionicframework.com/docs/components">UI Components</a></p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ExploreContainer;
|
|
@ -1,26 +0,0 @@
|
|||
import react from 'react';
|
||||
import { IonButton, IonIcon } from '@ionic/react';
|
||||
import { arrowBackCircleOutline } from 'ionicons/icons';
|
||||
import './BackForward.css';
|
||||
|
||||
export interface BackProperties {}
|
||||
|
||||
export const Back: react.FC<BackProperties> = (props: BackProperties) => {
|
||||
const onClickHandler = (event: any) => {
|
||||
window.history.back();
|
||||
};
|
||||
|
||||
return (
|
||||
<IonButton
|
||||
onClick={onClickHandler}
|
||||
size='large'
|
||||
className='back-forward'
|
||||
shape='round'
|
||||
fill='solid'
|
||||
>
|
||||
<IonIcon slot='icon-only' icon={arrowBackCircleOutline} />
|
||||
</IonButton>
|
||||
);
|
||||
};
|
||||
|
||||
export default Back;
|
|
@ -1,5 +0,0 @@
|
|||
ion-button.back-forward {
|
||||
--opacity: 0.6;
|
||||
--ion-background-color: white;
|
||||
--ionicon-stroke-width: 48px;
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
import react from 'react';
|
||||
import { IonButton, IonIcon } from '@ionic/react';
|
||||
import { arrowForwardCircleOutline } from 'ionicons/icons';
|
||||
import './BackForward.css';
|
||||
|
||||
export interface ForwardProperties {}
|
||||
|
||||
export const Forward: react.FC<ForwardProperties> = (props: ForwardProperties) => {
|
||||
const onClickHandler = (event: any) => {
|
||||
window.history.forward();
|
||||
};
|
||||
|
||||
return (
|
||||
<IonButton
|
||||
onClick={onClickHandler}
|
||||
size='large'
|
||||
className='back-forward'
|
||||
shape='round'
|
||||
fill='solid'
|
||||
>
|
||||
<IonIcon slot='icon-only' icon={arrowForwardCircleOutline} />
|
||||
</IonButton>
|
||||
);
|
||||
};
|
||||
|
||||
export default Forward;
|
|
@ -1,6 +0,0 @@
|
|||
ion-button.get-location {
|
||||
--opacity: 0.6;
|
||||
--ion-background-color: white;
|
||||
margin-left: calc(50% - 20px);
|
||||
}
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
import React from 'react';
|
||||
|
||||
import { Geolocation } from '@awesome-cordova-plugins/geolocation';
|
||||
|
||||
import './GetLocation.css';
|
||||
import { IonButton, IonIcon } from '@ionic/react';
|
||||
import { locateOutline } from 'ionicons/icons';
|
||||
import { useAtom } from 'jotai';
|
||||
import { setCenterAtom } from '../../App';
|
||||
import { locationAtom } from '../map/CurrentLocation';
|
||||
|
||||
const GetLocation: React.FC<{}> = () => {
|
||||
const [, setCenter] = useAtom(setCenterAtom);
|
||||
const [, setLocation] = useAtom(locationAtom);
|
||||
|
||||
const onClickHandler = (event: any) => {
|
||||
console.log('Click handler');
|
||||
Geolocation.getCurrentPosition().then((position) => {
|
||||
console.log(
|
||||
`Click handler, position: ${position.coords.latitude}, ${position.coords.longitude}`
|
||||
);
|
||||
setCenter({
|
||||
lat: position.coords.latitude,
|
||||
lon: position.coords.longitude,
|
||||
});
|
||||
setLocation({
|
||||
lat: position.coords.latitude,
|
||||
lon: position.coords.longitude,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<IonButton
|
||||
onClick={onClickHandler}
|
||||
className='get-location get-position'
|
||||
shape='round'
|
||||
size='small'
|
||||
fill='solid'
|
||||
>
|
||||
<IonIcon slot='icon-only' icon={locateOutline} color='white' />
|
||||
</IonButton>
|
||||
);
|
||||
};
|
||||
|
||||
export default GetLocation;
|
|
@ -1,50 +0,0 @@
|
|||
import {
|
||||
IonModal,
|
||||
IonToolbar,
|
||||
IonTitle,
|
||||
IonButtons,
|
||||
IonButton,
|
||||
IonContent,
|
||||
IonList,
|
||||
} from '@ionic/react';
|
||||
import react, { useRef, useState } from 'react';
|
||||
import { atom, useAtom } from 'jotai';
|
||||
import i18n from '../../i18n';
|
||||
|
||||
import cssDialog from './dialogs.module.css';
|
||||
|
||||
export interface ExplorerProperties {}
|
||||
|
||||
export const isOpenAtom = atom(false);
|
||||
|
||||
export const Explorer: react.FC<ExplorerProperties> = (
|
||||
props: ExplorerProperties
|
||||
) => {
|
||||
const modal = useRef<HTMLIonModalElement>(null);
|
||||
const [isOpen, setIsOpen] = useAtom(isOpenAtom);
|
||||
|
||||
const dismiss = () => {
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<IonModal
|
||||
ref={modal}
|
||||
className={cssDialog.modal}
|
||||
isOpen={isOpen}
|
||||
onDidDismiss={dismiss}
|
||||
>
|
||||
<IonToolbar>
|
||||
<IonTitle>{i18n.explorer.nearBy}</IonTitle>
|
||||
<IonButtons slot='end'>
|
||||
<IonButton onClick={dismiss}>{i18n.common.close}</IonButton>
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
<IonContent>
|
||||
<IonList></IonList>
|
||||
</IonContent>
|
||||
</IonModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default Explorer;
|
|
@ -1,8 +0,0 @@
|
|||
.inputFile {
|
||||
width: 0.1px;
|
||||
height: 0.1px;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
import React from 'react';
|
||||
|
||||
import GPX from '../../lib/gpx-parser-builder/src/gpx';
|
||||
|
||||
import css from './GpxImport.module.css';
|
||||
import { IonIcon, IonItem } from '@ionic/react';
|
||||
import { cloudUpload } from 'ionicons/icons';
|
||||
import { findStartTime } from '../../lib/gpx';
|
||||
import dispatch from '../../workers/dispatcher-main';
|
||||
import { intToGpxId } from '../../lib/ids';
|
||||
|
||||
const GpxImport: React.FC<{}> = () => {
|
||||
const onChangeHandler = (event: any) => {
|
||||
console.log('On change handler');
|
||||
const file: File = event.target.files[0];
|
||||
const fileReader = new FileReader();
|
||||
fileReader.readAsText(file);
|
||||
|
||||
fileReader.addEventListener(
|
||||
'load',
|
||||
() => {
|
||||
// this will then display a text file
|
||||
console.log(fileReader.result);
|
||||
const gpx = GPX.parse(fileReader.result);
|
||||
console.log(`gpx: ${JSON.stringify(gpx)}`);
|
||||
const startTime = new Date(findStartTime(gpx)!);
|
||||
dispatch({
|
||||
action: 'pruneAndSaveImportedGpx',
|
||||
params: {
|
||||
id: { gpx: intToGpxId(startTime.valueOf()) },
|
||||
gpx: gpx,
|
||||
tech: {
|
||||
lastModified: new Date(file.lastModified).toISOString(),
|
||||
importDate: new Date().toISOString(),
|
||||
name: file.name,
|
||||
size: file.size,
|
||||
type: file.type,
|
||||
},
|
||||
},
|
||||
});
|
||||
// pushGpx(db, {
|
||||
// gpx,
|
||||
// metadata: {
|
||||
// lastModified: new Date(file.lastModified).toISOString(),
|
||||
// importDate: new Date().toISOString(),
|
||||
// name: file.name,
|
||||
// size: file.size,
|
||||
// type: file.type,
|
||||
// },
|
||||
// });
|
||||
},
|
||||
false
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<IonItem>
|
||||
<input
|
||||
type='file'
|
||||
id='gpx-import'
|
||||
className={css.inputFile}
|
||||
accept='.gpx'
|
||||
onChange={onChangeHandler}
|
||||
/>
|
||||
<label htmlFor='gpx-import'>
|
||||
<IonIcon slot='icon-only' icon={cloudUpload} title='import' />
|
||||
</label>
|
||||
</IonItem>
|
||||
);
|
||||
};
|
||||
|
||||
export default GpxImport;
|
|
@ -1,78 +0,0 @@
|
|||
import react, { useRef } from 'react';
|
||||
|
||||
import {
|
||||
IonButton,
|
||||
IonButtons,
|
||||
IonContent,
|
||||
IonIcon,
|
||||
IonItem,
|
||||
IonLabel,
|
||||
IonList,
|
||||
IonModal,
|
||||
IonRadio,
|
||||
IonRadioGroup,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
} from '@ionic/react';
|
||||
import { layersOutline } from 'ionicons/icons';
|
||||
import { nonFakeTileProviders } from '../map/tile-providers';
|
||||
import { tileProviderAtom } from '../../App';
|
||||
import i18n from '../../i18n/index';
|
||||
import { useAtom } from 'jotai';
|
||||
|
||||
import cssDialog from './dialogs.module.css';
|
||||
|
||||
export interface MapChooserProperties {}
|
||||
|
||||
export const MapChooser: react.FC<MapChooserProperties> = (
|
||||
props: MapChooserProperties
|
||||
) => {
|
||||
const modal = useRef<HTMLIonModalElement>(null);
|
||||
|
||||
const dismiss = () => {
|
||||
modal.current?.dismiss();
|
||||
};
|
||||
|
||||
const [tileProvider, setTileProvider] = useAtom(tileProviderAtom);
|
||||
|
||||
const changeHandler = (event: any) => {
|
||||
setTileProvider(event.detail.value);
|
||||
dismiss();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<IonButton id='open-TileServerChooser'>
|
||||
<IonIcon slot='icon-only' icon={layersOutline} />
|
||||
</IonButton>
|
||||
<IonModal
|
||||
trigger='open-TileServerChooser'
|
||||
ref={modal}
|
||||
className={cssDialog.modal}
|
||||
>
|
||||
<IonToolbar>
|
||||
<IonTitle>{i18n.mapChooser.chooseYourMap}</IonTitle>
|
||||
<IonButtons slot='end'>
|
||||
<IonButton onClick={() => dismiss()}>{i18n.common.close}</IonButton>
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
<IonContent>
|
||||
<IonList>
|
||||
<IonRadioGroup value={tileProvider} onIonChange={changeHandler}>
|
||||
{Object.keys(nonFakeTileProviders).map((provider) => {
|
||||
return (
|
||||
<IonItem key={provider}>
|
||||
<IonLabel> {nonFakeTileProviders[provider].name}</IonLabel>
|
||||
<IonRadio slot='start' value={provider} />
|
||||
</IonItem>
|
||||
);
|
||||
})}
|
||||
</IonRadioGroup>
|
||||
</IonList>
|
||||
</IonContent>
|
||||
</IonModal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default MapChooser;
|
|
@ -1,15 +0,0 @@
|
|||
.modal {
|
||||
--height: 100%;
|
||||
--border-radius: 16px;
|
||||
--box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1),
|
||||
0 4px 6px -4px rgb(0 0 0 / 0.1);
|
||||
}
|
||||
|
||||
.modal ion-toolbar {
|
||||
--background: rgba(14, 116, 144, 0.7);
|
||||
--color: white;
|
||||
}
|
||||
|
||||
.modal ion-content {
|
||||
background-color: rgba(255, 255, 255, 0.7);
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
import { atom, useAtom } from 'jotai';
|
||||
import react from 'react';
|
||||
import Marker from './Marker';
|
||||
import { geoPoint, Rectangle, TileKeyObject } from './types';
|
||||
|
||||
export interface CurrentLocationProperties {
|
||||
keyObject?: TileKeyObject;
|
||||
zoom?: number;
|
||||
viewPort?: Rectangle;
|
||||
}
|
||||
|
||||
const initialLocation: geoPoint | null = null;
|
||||
export const locationAtom = atom<geoPoint | null>(initialLocation);
|
||||
|
||||
export const CurrentLocation: react.FC<CurrentLocationProperties> = (
|
||||
props: CurrentLocationProperties
|
||||
) => {
|
||||
const [location] = useAtom(locationAtom);
|
||||
console.log(
|
||||
`Rendering CurrentLocation, location:${JSON.stringify(location)}`
|
||||
);
|
||||
|
||||
return location !== null ? (
|
||||
<Marker
|
||||
key='currentLocation'
|
||||
id='currentLocation'
|
||||
coordinates={location}
|
||||
icon={
|
||||
<circle
|
||||
cx={0}
|
||||
cy={0}
|
||||
r={8}
|
||||
fill='blue'
|
||||
opacity='90%'
|
||||
stroke='white'
|
||||
strokeWidth={3}
|
||||
strokeOpacity='100%'
|
||||
/>
|
||||
}
|
||||
keyObject={props.keyObject}
|
||||
zoom={props.zoom}
|
||||
viewPort={props.viewPort}
|
||||
/>
|
||||
) : null;
|
||||
};
|
||||
|
||||
export default CurrentLocation;
|
|
@ -1,47 +0,0 @@
|
|||
import react, { startTransition, useEffect, useState } from 'react';
|
||||
import dispatch from '../../workers/dispatcher-main';
|
||||
import Trk from './Trk';
|
||||
import { TileKeyObject, Rectangle } from './types';
|
||||
import getUri from '../../lib/ids';
|
||||
|
||||
export interface GpxProperties {
|
||||
id: string;
|
||||
keyObject: TileKeyObject;
|
||||
zoom: number;
|
||||
viewPort: Rectangle;
|
||||
}
|
||||
|
||||
export const Gpx: react.FC<GpxProperties> = (props: GpxProperties) => {
|
||||
const [gpx, setGpx] = useState<any>([]);
|
||||
useEffect(() => {
|
||||
const getGpx = async () => {
|
||||
const gpx = await dispatch({
|
||||
action: 'getGpx',
|
||||
params: {
|
||||
id: props.id,
|
||||
},
|
||||
});
|
||||
console.log(`<Gpx>, gpx: ${JSON.stringify(gpx)}`);
|
||||
startTransition(() => setGpx(gpx));
|
||||
};
|
||||
getGpx();
|
||||
}, [props.id]);
|
||||
|
||||
return (
|
||||
<g>
|
||||
{gpx
|
||||
.filter((row: any) => row.doc.type === 'trk')
|
||||
.map((row: any) => (
|
||||
<Trk
|
||||
key={row.id}
|
||||
id={row.id}
|
||||
keyObject={props.keyObject}
|
||||
zoom={props.zoom}
|
||||
viewPort={props.viewPort}
|
||||
/>
|
||||
))}
|
||||
</g>
|
||||
);
|
||||
};
|
||||
|
||||
export default Gpx;
|
|
@ -1,51 +0,0 @@
|
|||
import react, { startTransition, useEffect, useState } from 'react';
|
||||
import dispatch from '../../workers/dispatcher-main';
|
||||
import Gpx from './Gpx';
|
||||
import { TileKeyObject, Rectangle } from './types';
|
||||
|
||||
export interface GpxesProperties {
|
||||
keyObject?: TileKeyObject;
|
||||
zoom?: number;
|
||||
viewPort?: Rectangle;
|
||||
}
|
||||
|
||||
export const Gpxes: react.FC<GpxesProperties> = (props: GpxesProperties) => {
|
||||
const [visibleGpxes, setVisibleGpxes] = useState<any>([]);
|
||||
useEffect(() => {
|
||||
const getVisibleGpxes = async () => {
|
||||
const gpxes = await dispatch({
|
||||
action: 'getGpxesForViewport',
|
||||
params: {
|
||||
viewport: props.viewPort,
|
||||
zoomLevel: props.keyObject?.zoomLevel,
|
||||
},
|
||||
});
|
||||
console.log(`Gpxes, visibles: ${JSON.stringify(gpxes)}`);
|
||||
startTransition(() => setVisibleGpxes(gpxes));
|
||||
};
|
||||
if (props.viewPort !== undefined) {
|
||||
getVisibleGpxes();
|
||||
}
|
||||
}, [
|
||||
props.viewPort?.bottomRight.x,
|
||||
props.viewPort?.bottomRight.y,
|
||||
props.viewPort?.topLeft.x,
|
||||
props.viewPort?.topLeft.y,
|
||||
props.keyObject?.zoomLevel,
|
||||
]);
|
||||
return (
|
||||
<>
|
||||
{visibleGpxes.map((id: string) => (
|
||||
<Gpx
|
||||
key={id}
|
||||
id={id}
|
||||
keyObject={props.keyObject!}
|
||||
zoom={props.zoom!}
|
||||
viewPort={props.viewPort!}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Gpxes;
|
|
@ -1,5 +0,0 @@
|
|||
.handler {
|
||||
position: fixed;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
|
@ -1,243 +0,0 @@
|
|||
import { createEvent, fireEvent, render, screen } from '@testing-library/react';
|
||||
import { Handlers } from './Handlers';
|
||||
import { Transformation } from './LiveMap';
|
||||
import { Point } from './types';
|
||||
describe('The Handlers component ', () => {
|
||||
test('is just an empty div', () => {
|
||||
const transformMap = (t: Transformation) => {};
|
||||
render(<Handlers transformMap={transformMap} />);
|
||||
// screen.debug();
|
||||
const handlers = screen.getByRole('presentation');
|
||||
// screen.debug();
|
||||
expect(handlers).toMatchInlineSnapshot(`
|
||||
<div
|
||||
class="handler"
|
||||
role="presentation"
|
||||
/>
|
||||
`);
|
||||
});
|
||||
/* test('handle mouseDown / mouseMove events', () => {
|
||||
var transformMapParams: any;
|
||||
function transformMap(
|
||||
deltaShift: Point | null,
|
||||
deltaZoom: number | null,
|
||||
zoomCenter: Point | null
|
||||
) {
|
||||
console.log(`transformMap${JSON.stringify(arguments)}`);
|
||||
transformMapParams = arguments;
|
||||
}
|
||||
render(<Handlers transformMap={transformMap} />);
|
||||
const handlers = screen.getByRole('presentation');
|
||||
fireEvent(
|
||||
handlers,
|
||||
createEvent.mouseDown(handlers, {
|
||||
clientX: 10,
|
||||
clientY: 20,
|
||||
})
|
||||
);
|
||||
fireEvent(
|
||||
handlers,
|
||||
createEvent.mouseMove(handlers, {
|
||||
clientX: 20,
|
||||
clientY: 50,
|
||||
})
|
||||
);
|
||||
// screen.debug();
|
||||
expect(transformMapParams).toMatchInlineSnapshot(`
|
||||
Arguments [
|
||||
Object {
|
||||
"x": 10,
|
||||
"y": 30,
|
||||
},
|
||||
null,
|
||||
null,
|
||||
]
|
||||
`);
|
||||
});
|
||||
test('does *not* handle mouseMove events not preceded by a mouseDown', () => {
|
||||
var transformMapParams: any;
|
||||
function transformMap(
|
||||
deltaShift: Point | null,
|
||||
deltaZoom: number | null,
|
||||
zoomCenter: Point | null
|
||||
) {
|
||||
console.log(`transformMap${JSON.stringify(arguments)}`);
|
||||
transformMapParams = arguments;
|
||||
}
|
||||
render(<Handlers transformMap={transformMap} />);
|
||||
const handlers = screen.getByRole('presentation');
|
||||
fireEvent(
|
||||
handlers,
|
||||
createEvent.mouseMove(handlers, {
|
||||
clientX: 20,
|
||||
clientY: 50,
|
||||
})
|
||||
);
|
||||
// screen.debug();
|
||||
expect(transformMapParams).toBeUndefined();
|
||||
});
|
||||
|
||||
test('A mouseUp event disable further mouseMove events', () => {
|
||||
var transformMapParams: any;
|
||||
function transformMap(
|
||||
deltaShift: Point | null,
|
||||
deltaZoom: number | null,
|
||||
zoomCenter: Point | null
|
||||
) {
|
||||
console.log(`transformMap${JSON.stringify(arguments)}`);
|
||||
transformMapParams = arguments;
|
||||
}
|
||||
render(<Handlers transformMap={transformMap} />);
|
||||
const handlers = screen.getByRole('presentation');
|
||||
fireEvent(
|
||||
handlers,
|
||||
createEvent.mouseDown(handlers, {
|
||||
clientX: 10,
|
||||
clientY: 20,
|
||||
})
|
||||
);
|
||||
fireEvent(
|
||||
handlers,
|
||||
createEvent.mouseUp(handlers, {
|
||||
clientX: 20,
|
||||
clientY: 50,
|
||||
})
|
||||
);
|
||||
fireEvent(
|
||||
handlers,
|
||||
createEvent.mouseMove(handlers, {
|
||||
clientX: 20,
|
||||
clientY: 50,
|
||||
})
|
||||
);
|
||||
// screen.debug();
|
||||
expect(transformMapParams).toBeUndefined();
|
||||
});
|
||||
|
||||
test('A mouseLeave event disable further mouseMove events', () => {
|
||||
var transformMapParams: any;
|
||||
function transformMap(
|
||||
deltaShift: Point | null,
|
||||
deltaZoom: number | null,
|
||||
zoomCenter: Point | null
|
||||
) {
|
||||
console.log(`transformMap${JSON.stringify(arguments)}`);
|
||||
transformMapParams = arguments;
|
||||
}
|
||||
render(<Handlers transformMap={transformMap} />);
|
||||
const handlers = screen.getByRole('presentation');
|
||||
fireEvent(handlers, createEvent.mouseOver(handlers));
|
||||
fireEvent(handlers, createEvent.mouseEnter(handlers));
|
||||
fireEvent(
|
||||
handlers,
|
||||
createEvent.mouseDown(handlers, {
|
||||
clientX: 10,
|
||||
clientY: 20,
|
||||
})
|
||||
);
|
||||
fireEvent(handlers, createEvent.mouseOut(handlers));
|
||||
fireEvent(handlers, createEvent.mouseLeave(handlers));
|
||||
fireEvent(
|
||||
handlers,
|
||||
createEvent.mouseMove(handlers, {
|
||||
clientX: 20,
|
||||
clientY: 50,
|
||||
})
|
||||
);
|
||||
// screen.debug();
|
||||
expect(transformMapParams).toBeUndefined();
|
||||
});
|
||||
|
||||
test('throttle mouseMove events', () => {
|
||||
var transformMapParams: any;
|
||||
function transformMap(
|
||||
deltaShift: Point | null,
|
||||
deltaZoom: number | null,
|
||||
zoomCenter: Point | null
|
||||
) {
|
||||
console.log(`transformMap${JSON.stringify(arguments)}`);
|
||||
transformMapParams = arguments;
|
||||
}
|
||||
render(<Handlers transformMap={transformMap} />);
|
||||
const handlers = screen.getByRole('presentation');
|
||||
fireEvent(
|
||||
handlers,
|
||||
createEvent.mouseDown(handlers, {
|
||||
clientX: 10,
|
||||
clientY: 20,
|
||||
})
|
||||
);
|
||||
fireEvent(
|
||||
handlers,
|
||||
createEvent.mouseMove(handlers, {
|
||||
clientX: 20,
|
||||
clientY: 50,
|
||||
})
|
||||
);
|
||||
fireEvent(
|
||||
handlers,
|
||||
createEvent.mouseMove(handlers, {
|
||||
clientX: 30,
|
||||
clientY: 60,
|
||||
})
|
||||
);
|
||||
// screen.debug();
|
||||
expect(transformMapParams).toMatchInlineSnapshot(`
|
||||
Arguments [
|
||||
Object {
|
||||
"x": 10,
|
||||
"y": 30,
|
||||
},
|
||||
null,
|
||||
null,
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
test('throttle mouseMove events', async () => {
|
||||
var transformMapParams: any;
|
||||
function transformMap(
|
||||
deltaShift: Point | null,
|
||||
deltaZoom: number | null,
|
||||
zoomCenter: Point | null
|
||||
) {
|
||||
console.log(`transformMap${JSON.stringify(arguments)}`);
|
||||
transformMapParams = arguments;
|
||||
}
|
||||
render(<Handlers transformMap={transformMap} />);
|
||||
const handlers = screen.getByRole('presentation');
|
||||
fireEvent(
|
||||
handlers,
|
||||
createEvent.mouseDown(handlers, {
|
||||
clientX: 10,
|
||||
clientY: 20,
|
||||
})
|
||||
);
|
||||
fireEvent(
|
||||
handlers,
|
||||
createEvent.mouseMove(handlers, {
|
||||
clientX: 20,
|
||||
clientY: 50,
|
||||
})
|
||||
);
|
||||
await new Promise((r) => setTimeout(r, 500));
|
||||
fireEvent(
|
||||
handlers,
|
||||
createEvent.mouseMove(handlers, {
|
||||
clientX: 5,
|
||||
clientY: 5,
|
||||
})
|
||||
);
|
||||
// screen.debug();
|
||||
expect(transformMapParams).toMatchInlineSnapshot(`
|
||||
Arguments [
|
||||
Object {
|
||||
"x": -15,
|
||||
"y": -45,
|
||||
},
|
||||
null,
|
||||
null,
|
||||
]
|
||||
`);
|
||||
}); */
|
||||
});
|
|
@ -1,327 +0,0 @@
|
|||
import react, { useRef } from 'react';
|
||||
|
||||
import { Point } from './types';
|
||||
import './Handler.css';
|
||||
import { handlersConfig } from './config';
|
||||
import { Transformation } from './LiveMap';
|
||||
import { isOpenAtom } from '../dialogs/Explorer';
|
||||
import cache from '../../lib/cache';
|
||||
import { useAtom } from 'jotai';
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
export interface HandlersProperties {
|
||||
/** The transformation to apply on the `<LiveMap>` parent */
|
||||
transformMap: (t: Transformation) => void;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param props
|
||||
* @returns A div with the following handlers
|
||||
* * mouseLeave, mouseDown and mouseUp to track the mouse state
|
||||
* * mouseMove to shift the map if the mouse is down
|
||||
* * doubleClick to zoom in
|
||||
* * wheel to zoom in and out
|
||||
* * touchStart, touchEnd and touchCancel to track touch state
|
||||
* * touchMove to shift the map (single finger) or shift and zoom (two fingers).
|
||||
*
|
||||
* Communication with the parent `<LiveMap>` is done through the transformMap {@link components/map/LiveMap!Transformation} property.
|
||||
*/
|
||||
export const Handlers: react.FC<HandlersProperties> = (
|
||||
props: HandlersProperties
|
||||
) => {
|
||||
const genericHandler = (event: any) => {
|
||||
// console.log(`Log - Event: ${event.type}`);
|
||||
// if (event.clientX !== undefined) {
|
||||
// console.log(
|
||||
// `Mouse : ${event.clientX}, ${event.clientY}, target: ${event.target}`
|
||||
// );
|
||||
// console.log(
|
||||
// `mouseState: ' ${JSON.stringify(mouseState)} (+${
|
||||
// Date.now() - mouseState.timestamp
|
||||
// }ms) `
|
||||
// );
|
||||
// return;
|
||||
//}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* Mouse handlers
|
||||
*
|
||||
*/
|
||||
const initialMouseState = {
|
||||
down: false,
|
||||
starting: { x: -1, y: -1 },
|
||||
timestamp: 0,
|
||||
};
|
||||
|
||||
const mouseState = useRef(initialMouseState);
|
||||
|
||||
const mouseLeaveHandler = (event: any) => {
|
||||
genericHandler(event);
|
||||
mouseState.current = initialMouseState;
|
||||
};
|
||||
|
||||
const mouseDownHandler = (event: any) => {
|
||||
genericHandler(event);
|
||||
mouseState.current = {
|
||||
down: true,
|
||||
starting: { x: event.clientX, y: event.clientY },
|
||||
timestamp: 0,
|
||||
};
|
||||
};
|
||||
|
||||
const mouseUpHandler = (event: any) => {
|
||||
genericHandler(event);
|
||||
mouseState.current = initialMouseState;
|
||||
};
|
||||
|
||||
const mouseMoveHandler = (event: any) => {
|
||||
if (
|
||||
mouseState.current.down &&
|
||||
Date.now() - mouseState.current.timestamp >
|
||||
handlersConfig.mouseMoveThrottleDelay /* &&
|
||||
(event.clientX - mouseState.current.starting.x) ** 2 +
|
||||
(event.clientY - mouseState.current.starting.y) ** 2 >
|
||||
100 */
|
||||
) {
|
||||
genericHandler(event);
|
||||
if (mouseState.current.down) {
|
||||
props.transformMap({
|
||||
deltaShift: {
|
||||
x: event.clientX - mouseState.current.starting.x,
|
||||
y: event.clientY - mouseState.current.starting.y,
|
||||
},
|
||||
deltaZoom: null,
|
||||
zoomCenter: null,
|
||||
});
|
||||
mouseState.current = {
|
||||
down: true,
|
||||
starting: {
|
||||
x: event.clientX,
|
||||
y: event.clientY,
|
||||
},
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* Double click
|
||||
*
|
||||
*/
|
||||
|
||||
const doubleClickHandler = (event: any) => {
|
||||
genericHandler(event);
|
||||
props.transformMap({
|
||||
deltaShift: null,
|
||||
deltaZoom: Math.SQRT2,
|
||||
zoomCenter: {
|
||||
x: event.clientX,
|
||||
y: event.clientY,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* Wheel handler
|
||||
*
|
||||
*/
|
||||
|
||||
const initialWheelState = {
|
||||
timestamp: 0,
|
||||
};
|
||||
|
||||
const wheelState = useRef(initialWheelState);
|
||||
|
||||
const wheelEventHandler = (event: any) => {
|
||||
genericHandler(event);
|
||||
if (
|
||||
event.deltaMode === WheelEvent.DOM_DELTA_PIXEL &&
|
||||
Date.now() - wheelState.current.timestamp >
|
||||
handlersConfig.wheelThrottleDelay
|
||||
) {
|
||||
props.transformMap({
|
||||
deltaShift: null,
|
||||
deltaZoom: event.deltaY < 0 ? Math.SQRT2 : Math.SQRT1_2,
|
||||
zoomCenter: {
|
||||
x: event.clientX,
|
||||
y: event.clientY,
|
||||
},
|
||||
});
|
||||
wheelState.current = {
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* Touch handlers
|
||||
*
|
||||
*/
|
||||
|
||||
interface TouchState {
|
||||
state: 'up' | 'pointer' | 'double';
|
||||
touches: Point[];
|
||||
distance: number;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
const initialTouchState: TouchState = {
|
||||
state: 'up',
|
||||
touches: [],
|
||||
distance: -1,
|
||||
timestamp: 0,
|
||||
};
|
||||
|
||||
const touchState = useRef<TouchState>(initialTouchState);
|
||||
|
||||
const touchCancelHandler = (event: any) => {
|
||||
genericHandler(event);
|
||||
touchState.current = initialTouchState;
|
||||
};
|
||||
|
||||
const touchEndHandler = touchCancelHandler;
|
||||
|
||||
const touchStartHandler = (event: any) => {
|
||||
genericHandler(event);
|
||||
if (event.touches.length === 2) {
|
||||
touchState.current = {
|
||||
state: 'double',
|
||||
touches: [
|
||||
{ x: event.touches[0].screenX, y: event.touches[0].screenY },
|
||||
{ x: event.touches[1].screenX, y: event.touches[1].screenY },
|
||||
],
|
||||
distance: Math.sqrt(
|
||||
(event.touches[0].screenX - event.touches[1].screenX) ** 2 +
|
||||
(event.touches[0].screenY - event.touches[1].screenY) ** 2
|
||||
),
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
} else if (event.touches.length === 1) {
|
||||
touchState.current = {
|
||||
state: 'pointer',
|
||||
touches: [{ x: event.touches[0].screenX, y: event.touches[0].screenY }],
|
||||
timestamp: Date.now(),
|
||||
distance: -1,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const touchMoveHandler = (event: any) => {
|
||||
if (
|
||||
touchState.current.state === 'double' &&
|
||||
Date.now() - touchState.current.timestamp >
|
||||
handlersConfig.doubleTouchMoveThrottleDelay
|
||||
) {
|
||||
if (event.touches.length === 2) {
|
||||
genericHandler(event);
|
||||
const newDistance = Math.sqrt(
|
||||
(event.touches[0].screenX - event.touches[1].screenX) ** 2 +
|
||||
(event.touches[0].screenY - event.touches[1].screenY) ** 2
|
||||
);
|
||||
const factor = newDistance / touchState.current.distance;
|
||||
// console.log(`+++++++++ ZOOM Factor is ${factor} ++++++++++`);
|
||||
const previousCenter = {
|
||||
x:
|
||||
(touchState.current.touches[0].x +
|
||||
touchState.current.touches[1].x) /
|
||||
2,
|
||||
y:
|
||||
(touchState.current.touches[0].y +
|
||||
touchState.current.touches[1].y) /
|
||||
2,
|
||||
};
|
||||
const currentCenter = {
|
||||
x: (event.touches[0].screenX + event.touches[1].screenX) / 2,
|
||||
y: (event.touches[0].screenY + event.touches[1].screenY) / 2,
|
||||
};
|
||||
props.transformMap({
|
||||
deltaShift: {
|
||||
x: currentCenter.x - previousCenter.x,
|
||||
y: currentCenter.y - previousCenter.y,
|
||||
},
|
||||
deltaZoom: factor,
|
||||
zoomCenter: {
|
||||
x: (currentCenter.x + previousCenter.x) / 2,
|
||||
y: (currentCenter.y + previousCenter.y) / 2,
|
||||
},
|
||||
});
|
||||
touchState.current = {
|
||||
state: 'double',
|
||||
touches: [
|
||||
{ x: event.touches[0].screenX, y: event.touches[0].screenY },
|
||||
{ x: event.touches[1].screenX, y: event.touches[1].screenY },
|
||||
],
|
||||
distance: newDistance,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
}
|
||||
} else if (
|
||||
touchState.current.state === 'pointer' &&
|
||||
Date.now() - touchState.current.timestamp >
|
||||
handlersConfig.singleTouchMoveThrottleDelay
|
||||
) {
|
||||
if (event.touches.length === 1) {
|
||||
genericHandler(event);
|
||||
props.transformMap({
|
||||
deltaShift: {
|
||||
x: event.touches[0].screenX - touchState.current.touches[0].x,
|
||||
y: event.touches[0].screenY - touchState.current.touches[0].y,
|
||||
},
|
||||
deltaZoom: null,
|
||||
zoomCenter: null,
|
||||
});
|
||||
touchState.current = {
|
||||
state: 'pointer',
|
||||
touches: [
|
||||
{
|
||||
x: event.touches[0].screenX,
|
||||
y: event.touches[0].screenY,
|
||||
},
|
||||
],
|
||||
timestamp: Date.now(),
|
||||
distance: -1,
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const [, setIsOpen] = useAtom(isOpenAtom);
|
||||
|
||||
const contextMenuHandler = (event: any) => {
|
||||
console.log(event);
|
||||
event.preventDefault();
|
||||
console.log(cache.map({ cacheId: 'points' }));
|
||||
|
||||
setIsOpen(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className='handler'
|
||||
role='presentation'
|
||||
onMouseDown={mouseDownHandler}
|
||||
onMouseMove={mouseMoveHandler}
|
||||
onMouseUp={mouseUpHandler}
|
||||
onMouseLeave={mouseLeaveHandler}
|
||||
onDoubleClick={doubleClickHandler}
|
||||
onWheel={wheelEventHandler}
|
||||
onTouchEnd={touchEndHandler}
|
||||
onTouchCancel={touchCancelHandler}
|
||||
onTouchStart={touchStartHandler}
|
||||
onTouchMove={touchMoveHandler}
|
||||
onContextMenu={contextMenuHandler}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|