2022-10-18 15:18:11 +00:00
|
|
|
import react, { useRef, useState } from 'react';
|
2022-10-18 08:10:23 +00:00
|
|
|
import { Point } from './types';
|
2022-10-18 11:45:16 +00:00
|
|
|
import './Handler.css';
|
2022-10-18 12:24:21 +00:00
|
|
|
import { handlersConfig } from './config';
|
2022-10-18 08:10:23 +00:00
|
|
|
|
|
|
|
export interface HandlersProperties {
|
|
|
|
/**Transform the map:
|
|
|
|
* * Shift the map by a number of pixel
|
|
|
|
* * Multiply the zoom by deltaZoom around the zoomCenter
|
|
|
|
* */
|
|
|
|
transformMap: (
|
|
|
|
deltaShift: Point | null,
|
|
|
|
deltaZoom: number | null,
|
|
|
|
zoomCenter: Point | null
|
|
|
|
) => void;
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @param props
|
2022-10-18 11:45:16 +00:00
|
|
|
* @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
|
2022-10-18 15:42:11 +00:00
|
|
|
* * doubleClick to zoom in
|
|
|
|
* * wheel to zoom in and out
|
2022-10-18 18:40:12 +00:00
|
|
|
* * touchStart, touchEnd and touchCancel to track touch state
|
|
|
|
* * touchMove to shift the map (single finger) or shift and zoom (two fingers).
|
2022-10-18 11:45:16 +00:00
|
|
|
*
|
2022-10-18 08:10:23 +00:00
|
|
|
*/
|
|
|
|
export const Handlers: react.FC<HandlersProperties> = (
|
|
|
|
props: HandlersProperties
|
|
|
|
) => {
|
|
|
|
const genericHandler = (event: any) => {
|
|
|
|
// console.log(`Log - Event: ${event.type}`);
|
2022-10-18 09:44:50 +00:00
|
|
|
// if (event.clientX !== undefined) {
|
2022-10-18 08:10:23 +00:00
|
|
|
// console.log(
|
2022-10-18 09:44:50 +00:00
|
|
|
// `Mouse : ${event.clientX}, ${event.clientY}, target: ${event.target}`
|
2022-10-18 08:10:23 +00:00
|
|
|
// );
|
|
|
|
// console.log(
|
|
|
|
// `mouseState: ' ${JSON.stringify(mouseState)} (+${
|
|
|
|
// Date.now() - mouseState.timestamp
|
|
|
|
// }ms) `
|
|
|
|
// );
|
|
|
|
// return;
|
|
|
|
//}
|
|
|
|
};
|
|
|
|
|
2022-10-18 15:42:11 +00:00
|
|
|
/**
|
|
|
|
*
|
|
|
|
* Mouse handlers
|
|
|
|
*
|
|
|
|
*/
|
2022-10-18 08:10:23 +00:00
|
|
|
const initialMouseState = {
|
|
|
|
down: false,
|
|
|
|
starting: { x: -1, y: -1 },
|
2022-10-18 12:24:21 +00:00
|
|
|
timestamp: 0,
|
2022-10-18 08:10:23 +00:00
|
|
|
};
|
2022-10-18 12:24:21 +00:00
|
|
|
|
2022-10-18 15:18:11 +00:00
|
|
|
const mouseState = useRef(initialMouseState);
|
2022-10-18 12:24:21 +00:00
|
|
|
|
2022-10-18 08:10:23 +00:00
|
|
|
const mouseLeaveHandler = (event: any) => {
|
|
|
|
genericHandler(event);
|
2022-10-18 15:18:11 +00:00
|
|
|
mouseState.current = initialMouseState;
|
2022-10-18 08:10:23 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
const mouseDownHandler = (event: any) => {
|
|
|
|
genericHandler(event);
|
2022-10-18 15:18:11 +00:00
|
|
|
mouseState.current = {
|
2022-10-18 08:10:23 +00:00
|
|
|
down: true,
|
2022-10-18 09:44:50 +00:00
|
|
|
starting: { x: event.clientX, y: event.clientY },
|
2022-10-18 12:24:21 +00:00
|
|
|
timestamp: 0,
|
2022-10-18 15:18:11 +00:00
|
|
|
};
|
2022-10-18 08:10:23 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
const mouseUpHandler = (event: any) => {
|
|
|
|
genericHandler(event);
|
2022-10-18 15:18:11 +00:00
|
|
|
mouseState.current = initialMouseState;
|
2022-10-18 08:10:23 +00:00
|
|
|
};
|
|
|
|
|
2022-10-18 12:24:21 +00:00
|
|
|
const mouseMoveHandler = (event: any) => {
|
|
|
|
if (
|
2022-10-18 15:18:11 +00:00
|
|
|
mouseState.current.down &&
|
|
|
|
Date.now() - mouseState.current.timestamp >
|
|
|
|
handlersConfig.mouseMoveThrottleDelay
|
2022-10-18 12:24:21 +00:00
|
|
|
) {
|
|
|
|
genericHandler(event);
|
2022-10-18 15:18:11 +00:00
|
|
|
if (mouseState.current.down) {
|
2022-10-18 12:24:21 +00:00
|
|
|
props.transformMap(
|
|
|
|
{
|
2022-10-18 15:18:11 +00:00
|
|
|
x: event.clientX - mouseState.current.starting.x,
|
|
|
|
y: event.clientY - mouseState.current.starting.y,
|
2022-10-18 12:24:21 +00:00
|
|
|
},
|
|
|
|
null,
|
|
|
|
null
|
|
|
|
);
|
2022-10-18 15:18:11 +00:00
|
|
|
mouseState.current = {
|
2022-10-18 12:24:21 +00:00
|
|
|
down: true,
|
|
|
|
starting: {
|
|
|
|
x: event.clientX,
|
|
|
|
y: event.clientY,
|
|
|
|
},
|
|
|
|
timestamp: Date.now(),
|
2022-10-18 15:18:11 +00:00
|
|
|
};
|
2022-10-18 12:24:21 +00:00
|
|
|
}
|
2022-10-18 09:44:50 +00:00
|
|
|
}
|
2022-10-18 12:24:21 +00:00
|
|
|
};
|
2022-10-18 08:10:23 +00:00
|
|
|
|
2022-10-18 15:42:11 +00:00
|
|
|
/**
|
|
|
|
*
|
|
|
|
* Double click
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
const doubleClickHandler = (event: any) => {
|
|
|
|
genericHandler(event);
|
|
|
|
props.transformMap(null, Math.SQRT2, {
|
|
|
|
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(null, event.deltaY < 0 ? Math.SQRT2 : Math.SQRT1_2, {
|
|
|
|
x: event.clientX,
|
|
|
|
y: event.clientY,
|
|
|
|
});
|
|
|
|
wheelState.current = {
|
|
|
|
timestamp: Date.now(),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-10-18 16:47:40 +00:00
|
|
|
/**
|
|
|
|
*
|
|
|
|
* 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} ++++++++++`);
|
|
|
|
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(),
|
|
|
|
};
|
|
|
|
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(
|
|
|
|
{
|
|
|
|
x: currentCenter.x - previousCenter.x,
|
|
|
|
y: currentCenter.y - previousCenter.y,
|
|
|
|
},
|
|
|
|
factor,
|
|
|
|
{
|
|
|
|
x: currentCenter.x - previousCenter.x,
|
|
|
|
y: currentCenter.y - previousCenter.y,
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
} else if (
|
|
|
|
touchState.current.state === 'pointer' &&
|
|
|
|
Date.now() - touchState.current.timestamp >
|
|
|
|
handlersConfig.singleTouchMoveThrottleDelay
|
|
|
|
) {
|
|
|
|
if (event.touches.length === 1) {
|
|
|
|
genericHandler(event);
|
|
|
|
props.transformMap(
|
|
|
|
{
|
|
|
|
x: event.touches[0].screenX - touchState.current.touches[0].x,
|
|
|
|
y: event.touches[0].screenY - touchState.current.touches[0].y,
|
|
|
|
},
|
|
|
|
null,
|
|
|
|
null
|
|
|
|
);
|
|
|
|
touchState.current = {
|
|
|
|
state: 'pointer',
|
|
|
|
touches: [
|
|
|
|
{
|
|
|
|
x: event.touches[0].screenX,
|
|
|
|
y: event.touches[0].screenY,
|
|
|
|
},
|
|
|
|
],
|
|
|
|
timestamp: Date.now(),
|
|
|
|
distance: -1,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-10-18 08:10:23 +00:00
|
|
|
return (
|
|
|
|
<div
|
|
|
|
className='handler'
|
2022-10-18 09:44:50 +00:00
|
|
|
role='presentation'
|
2022-10-18 08:10:23 +00:00
|
|
|
onMouseDown={mouseDownHandler}
|
|
|
|
onMouseMove={mouseMoveHandler}
|
|
|
|
onMouseUp={mouseUpHandler}
|
|
|
|
onMouseLeave={mouseLeaveHandler}
|
2022-10-18 15:42:11 +00:00
|
|
|
onDoubleClick={doubleClickHandler}
|
|
|
|
onWheel={wheelEventHandler}
|
2022-10-18 16:47:40 +00:00
|
|
|
onTouchEnd={touchEndHandler}
|
|
|
|
onTouchCancel={touchCancelHandler}
|
|
|
|
onTouchStart={touchStartHandler}
|
|
|
|
onTouchMove={touchMoveHandler}
|
2022-10-18 08:10:23 +00:00
|
|
|
/>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
export default Handlers;
|