Spliting handlers into different components

This commit is contained in:
Eric van der Vlist 2022-09-09 19:09:26 +02:00
parent 828eac45ff
commit bfb8b622cc
5 changed files with 331 additions and 201 deletions

View File

@ -12,7 +12,7 @@ const Layer: react.FC<{
children?: JSX.Element; children?: JSX.Element;
}) => { }) => {
const {children: children, ...argProps} = props; const {children: children, ...argProps} = props;
console.log(`--- Rendering background, props: ${JSON.stringify(argProps)} ---`); console.log(`--- Rendering layer, props: ${JSON.stringify(argProps)} ---`);
return ( return (
<div <div

View File

@ -0,0 +1,91 @@
import react, { useCallback, useState } from 'react';
import _ from 'lodash';
import { Point } from './viewport';
interface MouseShiftHandlerProps {
updateShift: (shiftDelta: Point) => void;
children: any;
}
const MouseShiftHandler: react.FC<MouseShiftHandlerProps> = (
props: MouseShiftHandlerProps
) => {
const initialMouseState = {
down: false,
starting: { x: -1, y: -1 },
};
const [mouseState, setMouseState] = useState(initialMouseState);
console.log('MouseShiftHandler, mouseState: ' + JSON.stringify(mouseState));
const genericHandler = (event: any) => {
console.log('Log - Event: ' + event.type);
if (event.type.startsWith('mouse')) {
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) => {
genericHandler(event);
event.preventDefault();
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.updateShift({
x: mouseState.starting.x - event.pageX,
y: mouseState.starting.y - event.pageY,
});
setMouseState({
down: true,
starting: {
x: event.pageX,
y: event.pageY,
},
});
}
};
const throtteledMouseMoveHandler = useCallback(
_.throttle(mouseMoveHandler, 50),
[mouseState.down]
);
return (
<div
className='viewport'
onMouseDown={mouseDownHandler}
onMouseMove={throtteledMouseMoveHandler}
onMouseUp={mouseUpHandler}
onMouseLeave={mouseLeaveHandler}
>
{props.children}
</div>
);
};
export default MouseShiftHandler;

View File

@ -0,0 +1,99 @@
import react, { useCallback, useState } from 'react';
import _ from 'lodash';
import { Point } from './viewport';
interface TouchShiftHandlerProps {
updateShift: (shiftDelta: Point) => void;
children: any;
}
const TouchShiftHandler: react.FC<TouchShiftHandlerProps> = (
props: TouchShiftHandlerProps
) => {
const initialTouchState = {
state: 'up',
touch: { x: -1, y: -1 },
};
const [touchState, setTouchState] = useState(initialTouchState);
console.log('TouchShiftHandler, 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.updateShift({
x: touchState.touch.x - event.touches[0].pageX,
y: touchState.touch.y - event.touches[0].pageY,
});
setTouchState({
state: 'pointer',
touch: {
x: event.touches[0].pageX,
y: event.touches[0].pageY,
},
});
}
}
};
const throtteledTouchMoveHandler = useCallback(
_.throttle(touchMoveHandler, 100),
[touchState.state]
);
return (
<div
className='viewport'
onTouchStart={touchStartHandler}
onTouchMove={throtteledTouchMoveHandler}
onTouchEnd={touchEndHandler}
onTouchCancel={touchCancelHandler}
>
{props.children}
</div>
);
};
export default TouchShiftHandler;

View File

@ -0,0 +1,114 @@
import react, { useCallback, useState } from 'react';
import _ from 'lodash';
import { Point } from './viewport';
interface TouchZoomHandlerProps {
updateZoom: (zoomFactor: number) => void;
children: any;
}
const TouchZoomHandler: react.FC<TouchZoomHandlerProps> = (
props: TouchZoomHandlerProps
) => {
const initialTouchState = {
state: 'up',
touches: [
{ x: -1, y: -1 },
{ x: -1, y: -1 },
],
distance: -1,
initialZoom: 1,
};
const [touchState, setTouchState] = useState(initialTouchState);
console.log('TouchZoomHandler, 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,
});
props.updateZoom(factor);
}
}
};
const throtteledTouchMoveHandler = useCallback(
_.throttle(touchMoveHandler, 100),
[touchState.state]
);
return (
<div
className='viewport'
onTouchStart={touchStartHandler}
onTouchMove={throtteledTouchMoveHandler}
onTouchEnd={touchEndHandler}
onTouchCancel={touchCancelHandler}
>
{props.children}
</div>
);
};
export default TouchZoomHandler;

View File

@ -2,228 +2,54 @@ import react, { useCallback, useState } from 'react';
import _ from 'lodash'; import _ from 'lodash';
import MouseShiftHandler from './mouse-shift-handler';
import Layer from './layer'; import Layer from './layer';
import '../theme/viewport.css'; import '../theme/viewport.css';
import TouchShiftHandler from './touch-shift-handler';
import TouchZoomHandler from './touch-zoom-handler';
export interface Point {
x: number;
y: number;
}
const Viewport: react.FC<{}> = (props: {}) => { const Viewport: react.FC<{}> = (props: {}) => {
console.log(`--- Rendering viewport, props: ${JSON.stringify(props)} ---`); console.log(`--- Rendering viewport, props: ${JSON.stringify(props)} ---`);
const [shift, setShift] = useState({ x: 1000, y: 1000 }); const initialShitf: Point = { x: 1000, y: 1000 };
const [shift, setShift] = useState(initialShitf);
const [zoom, setZoom] = useState(1); const [zoom, setZoom] = useState(1);
const initialMouseState = {
down: false,
starting: { x: -1, y: -1 },
};
const initialTouchState = {
state: 'up',
touches: [
{ x: -1, y: -1 },
{ x: -1, y: -1 },
],
distance: -1,
initialZoom: 1,
};
const [mouseState, setMouseState] = useState(initialMouseState);
const [touchState, setTouchState] = useState(initialTouchState);
console.log('viewport, mouseState: ' + JSON.stringify(mouseState));
console.log('viewport, touchState: ' + JSON.stringify(touchState));
const genericHandler = (event: any) => { const genericHandler = (event: any) => {
console.log('Log - Event: ' + event.type); console.log('Log - Event: ' + event.type);
if (event.type.startsWith('mouse')) {
console.log(`Mouse : ${event.pageX}, ${event.pageY}`);
console.log('mouseState: ' + JSON.stringify(mouseState));
return; return;
}
if (event.type.startsWith('touch')) {
if (event.touches.length > 0) {
console.log(
`Touch : ${event.touches.length} touches, ${
event.touches[0].pageX
}, ${event.touches[0].pageY}, ${
event.touches.length > 1 ? event.touches[1].pageX : '-'
}, ${event.touches.length > 1 ? event.touches[1].pageY : '-'}`
);
}
console.log('touchState: ' + JSON.stringify(touchState));
return;
}
}; };
// TODO: implement resize event // TODO: implement resize event
// TODO: check boundaries // TODO: check boundaries
const mouseLeaveHandler = (event: any) => { const updateShift = (shiftDelta: Point) => {
genericHandler(event); setShift({ x: shift.x + shiftDelta.x, y: shift.y + shiftDelta.y });
event.preventDefault();
throtteledMouseMoveHandler.cancel();
throtteledTouchMoveHandler.cancel();
setMouseState(initialMouseState);
setTouchState(initialTouchState);
}; };
const mouseDownHandler = (event: any) => { const updateZoom = (zoomFactor: number) => {
genericHandler(event); setZoom(zoom * zoomFactor);
event.preventDefault();
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);
// setShift((shift) => ({
// x: shift.x + (mouseState.starting.x - event.pageX),
// y: shift.y + (mouseState.starting.y - event.pageY),
// }));
// setMouseState((mouseState) => ({
// down: true,
// starting: {
// x: event.pageX,
// y: event.pageY,
// },
// }));
setShift({
x: shift.x + (mouseState.starting.x - event.pageX),
y: shift.y + (mouseState.starting.y - event.pageY),
});
setMouseState({
down: true,
starting: {
x: event.pageX,
y: event.pageY,
},
});
}
};
const throtteledMouseMoveHandler = useCallback(
_.throttle(mouseMoveHandler, 50),
[mouseState.down]
);
// Touch
const touchStartHandler = (event: any) => {
genericHandler(event);
// event.preventDefault();
if (event.touches.length === 1) {
setTouchState({
state: 'pointer',
touches: [{ x: event.touches[0].pageX, y: event.touches[0].pageY }],
distance: -1,
initialZoom: -1,
});
return;
}
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: zoom,
});
}
};
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);
setShift({
x: shift.x + (touchState.touches[0].x - event.touches[0].pageX),
y: shift.y + (touchState.touches[0].y - event.touches[0].pageY),
});
setTouchState({
state: 'pointer',
touches: [
{
x: event.touches[0].pageX,
y: event.touches[0].pageY,
},
],
distance: 0,
initialZoom: -1,
});
}
return;
}
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: zoom,
});
setZoom(zoom * factor);
}
return;
}
};
const throtteledTouchMoveHandler = useCallback(
_.throttle(touchMoveHandler, 100),
[touchState.state]
);
return ( return (
<div <div className='viewport'>
className='viewport' <MouseShiftHandler updateShift={updateShift}>
onMouseDown={mouseDownHandler} <TouchShiftHandler updateShift={updateShift}>
onMouseMove={throtteledMouseMoveHandler} <TouchZoomHandler updateZoom={updateZoom}>
onMouseUp={mouseUpHandler}
onMouseLeave={mouseLeaveHandler}
onTouchStart={touchStartHandler}
onTouchMove={throtteledTouchMoveHandler}
onTouchEnd={touchEndHandler}
onTouchCancel={mouseLeaveHandler}
>
<Layer shift={shift} zoom={zoom}> <Layer shift={shift} zoom={zoom}>
<img src='/assets/background.jpg' alt='' /> <img src='/assets/background.jpg' alt='' />
</Layer> </Layer>
</TouchZoomHandler>
</TouchShiftHandler>
</MouseShiftHandler>
</div> </div>
); );
}; };