diff --git a/svgmap/src/App.tsx b/svgmap/src/App.tsx index 64f7bc0..4b2be5f 100644 --- a/svgmap/src/App.tsx +++ b/svgmap/src/App.tsx @@ -19,12 +19,14 @@ import '@ionic/react/css/display.css'; /* Theme variables */ import './theme/variables.css'; +import Map from './components/map/Map'; + setupIonicReact(); const App: React.FC = () => ( -

This works!

-
+ + ); export default App; diff --git a/svgmap/src/components/map/Map.css b/svgmap/src/components/map/Map.css new file mode 100644 index 0000000..34812f7 --- /dev/null +++ b/svgmap/src/components/map/Map.css @@ -0,0 +1,3 @@ +.map { + overflow: hidden; +} diff --git a/svgmap/src/components/map/Map.tsx b/svgmap/src/components/map/Map.tsx new file mode 100644 index 0000000..ba68acf --- /dev/null +++ b/svgmap/src/components/map/Map.tsx @@ -0,0 +1,28 @@ +import { IonContent, IonApp } from '@ionic/react'; +import react, { useState } from 'react'; + +import './Map.css'; +import MouseHandler from './MouseHandler'; +import Viewport from './Viewport'; + +interface MapProperties { + height: number; + width: number; +} + +const Map: react.FC = (props: MapProperties) => { + const boardSize = Math.max(props.width, props.height) * 2; + + return ( + +
+ +
+
+ ); +}; + +export default Map; diff --git a/svgmap/src/components/map/MouseHandler.tsx b/svgmap/src/components/map/MouseHandler.tsx new file mode 100644 index 0000000..3b627a7 --- /dev/null +++ b/svgmap/src/components/map/MouseHandler.tsx @@ -0,0 +1,110 @@ +import react, { useState } from 'react'; +import Viewport from './Viewport'; + +interface MouseHandlerProperties { + boardSize: number; + shift: { x: number; y: number }; + zoom: number; +} + +const MouseHandler: react.FC = ( + props: MouseHandlerProperties +) => { + const initialMouseState = { + down: false, + starting: { x: -1, y: -1 }, + timestamp: 0, + }; + + const [mouseState, setMouseState] = useState(initialMouseState); + + const [shift, setShift] = useState(props.shift); + const [zoom, setZoom] = useState(props.zoom); + + console.log('MouseHandler, mouseState: ' + JSON.stringify(mouseState)); + console.log( + 'MouseHandler, shift: ' + JSON.stringify(shift) + ', zoom:' + zoom + ); + + const genericHandler = (event: any) => { + console.log(`Log - Event: ${event.type}`); + if (event.pageX !== undefined) { + console.log( + `Mouse : ${event.pageX}, ${event.pageY}, target: ${event.target}` + ); + console.log( + `mouseState: ' ${JSON.stringify(mouseState)} (+${ + Date.now() - mouseState.timestamp + }ms) ` + ); + return; + } + }; + + const mouseLeaveHandler = (event: any) => { + genericHandler(event); + setMouseState(initialMouseState); + }; + + const mouseDownHandler = (event: any) => { + genericHandler(event); + setMouseState({ + down: true, + starting: { x: event.pageX, y: event.pageY }, + timestamp: Date.now(), + }); + }; + + const mouseUpHandler = (event: any) => { + genericHandler(event); + setMouseState(initialMouseState); + }; + + const mouseMoveHandler = (event: any) => { + if (mouseState.down && Date.now() - mouseState.timestamp > 5) { + genericHandler(event); + console.log( + `dispatch ${JSON.stringify({ + x: event.pageX - mouseState.starting.x, + y: event.pageY - mouseState.starting.y, + })}` + ); + setShift({ + x: shift.x + (event.pageX - mouseState.starting.x), + y: shift.y + (event.pageY - mouseState.starting.y), + }); + setMouseState({ + down: true, + starting: { + x: event.pageX, + y: event.pageY, + }, + timestamp: Date.now(), + }); + } + }; + + const doubleClickHandler = (event: any) => { + genericHandler(event); + const newZoom = zoom * Math.SQRT2; + setShift({ + x: shift.x + (shift.x - event.pageX) * (newZoom / zoom - 1), + y: shift.y + (shift.y - event.pageY) * (newZoom / zoom - 1), + }); + setZoom(newZoom); + }; + + return ( +
+ +
+ ); +}; + +export default MouseHandler; diff --git a/svgmap/src/components/map/Viewport.tsx b/svgmap/src/components/map/Viewport.tsx new file mode 100644 index 0000000..9310aa4 --- /dev/null +++ b/svgmap/src/components/map/Viewport.tsx @@ -0,0 +1,63 @@ +import react from 'react'; + +interface ViewportProperties { + boardSize: number; + shift: { x: number; y: number }; + zoom: number; +} + +/** + * + * Let's call: + * - x and y the SVG coordinates + * - X and Y the screen coordinates (pixels on screen) + * + * X0 = (x + shift.x * zoom) * zoom + * or + * x = X0 * zoom - shift.x + * id for Y + * + * To add a new shift of S screen pixels, we need to apply a zoom of S/zoom + * + * How can we zoom so that X and x stay constant ? + * + * Knowing X0, x0, zoom0, zoom1 and shift.x0, + * + * + * X0 = (x0 + shift.x0 *zoom0) * zoom0 + * X1 = (x1 + shift.x1 *zoom1) * zoom1 + * X0 = X1 (=X) + * x0 = x1 (=x) + * => + * (x + shift.x1*zoom1) * zoom1 = (x + shift.x0 * zoom0) * zoom0 + * => + * (x + shift.x1*zoom1) = (x + shift.x0*zoom0) * zoom0 / zoom1 + * shift.x1 = ((x + shift.x0) * zoom0 / zoom1 - x) / zoom1 + * + * x = 333 + * 282 => -25,5 + * + */ + +/** + * + * viewBox={`${-props.shift.x} ${-props.shift.y} ${ + props.boardSize / props.zoom + } ${props.boardSize / props.zoom}`} + + * + */ + +const Viewport: react.FC = (props: ViewportProperties) => { + return ( + + + + + + ); +}; + +export default Viewport; diff --git a/svgmap/templates/Template.tsx b/svgmap/templates/Template.tsx new file mode 100644 index 0000000..8349dcc --- /dev/null +++ b/svgmap/templates/Template.tsx @@ -0,0 +1,9 @@ +import react from 'react'; + +interface MapProperties {} + +const Map: react.FC = (props: MapProperties) => { + return <>; +}; + +export default Map;