Problems with character movement reactjs threejs - reactjs

I am making a 3D walkable world. Implemented the keyboard movement using wasd and space and mouse movement. I have no problem with the mouse movement, but when pressing the keys on the keyboard it doesnt work. I have seen the example multiple times and it works, but for me it doesnt seem so.
I have a useKeyboardInput.js where I add the event listeners and it registers when i`m pressing the key.
import { useCallback, useEffect, useState } from "react";
export const useKeyboardInput = (keysToListen = []) => {
const getKeys = useCallback(() => {
const lowerCaseArray = [];
const hookReturn = {};
keysToListen.forEach((key) => {
const lowerCaseKey = key.toLowerCase();
lowerCaseArray.push(lowerCaseKey);
hookReturn[lowerCaseKey] = false;
});
return {
lowerCaseArray,
hookReturn,
};
}, [keysToListen]);
const [keysPressed, setPressedKeys] = useState(getKeys().hookReturn);
useEffect(() => {
const handleKeyDown = (e) => {
const lowerKey = e.key.toLowerCase();
if (getKeys().lowerCaseArray.includes(lowerKey)) {
setPressedKeys((keysPressed) => ({ ...keysPressed, [lowerKey]: true }));
}
console.log("Pressed Key is: " + e.key);
};
const handleKeyUp = (e) => {
const lowerKey = e.key.toLowerCase();
if (getKeys().lowerCaseArray.includes(lowerKey)) {
setPressedKeys((keysPressed) => ({
...keysPressed,
[lowerKey]: false,
}));
}
};
document.addEventListener("keydown", handleKeyDown);
document.addEventListener("keyup", handleKeyUp);
return () => {
document.removeEventListener("keydown", handleKeyDown);
document.removeEventListener("keyup", handleKeyUp);
};
}, [keysToListen, getKeys]);
console.log("KeysPressed is: " + JSON.stringify(keysPressed));
return keysPressed;
};
And then i have the Player.js where i am using the hooks for keyboard and mouse.
import { useSphere } from "#react-three/cannon";
import React, { useEffect, useRef, useState } from "react";
import { useFrame, useThree } from "#react-three/fiber";
import { Vector3 } from "three";
import { useKeyboardInput } from "../Hooks/useKeyboardInput";
import { useMouseInput } from "../Hooks/useMouseInput";
import { useVariable } from "../Hooks/useVariable";
import { Bullet } from "./Bullet";
import { Raycaster } from "three";
/** Player movement constants */
const speed = 300;
const bulletSpeed = 30;
const bulletCoolDown = 300;
const jumpSpeed = 5;
const jumpCoolDown = 400;
export const Player = () => {
/** Player collider */
const [sphereRef, api] = useSphere(() => ({
mass: 100,
fixedRotation: true,
position: [0, 1, 0],
args: [0.2],
material: {
friction: 0,
},
}));
/** Bullets */
const [bullets, setBullets] = useState([]);
/** Input hooks */
const pressed = useKeyboardInput(["w", "a", "s", "d", " "]);
const pressedMouse = useMouseInput();
/** Converts the input state to ref so they can be used inside useFrame */
const input = useVariable(pressed);
const mouseInput = useVariable(pressedMouse);
/** Player movement constants */
const { camera, scene } = useThree();
/** Player state */
const state = useRef({
timeToShoot: 0,
timeTojump: 0,
vel: [0, 0, 0],
jumping: false,
});
useEffect(() => {
api.velocity.subscribe((v) => (state.current.vel = v));
}, [api]);
/** Player loop */
useFrame((_, delta) => {
/** Handles movement */
console.log("Input.current is: " + JSON.stringify(input.current));
const { w, s, a, d } = input.current;
const space = input.current[" "];
let velocity = new Vector3(0, 0, 0);
let cameraDirection = new Vector3();
camera.getWorldDirection(cameraDirection);
let forward = new Vector3();
forward.setFromMatrixColumn(camera.matrix, 0);
forward.crossVectors(camera.up, forward);
let right = new Vector3();
right.setFromMatrixColumn(camera.matrix, 0);
let [horizontal, vertical] = [0, 0];
if (w == true) {
vertical += 1;
console.log("Pressed w");
} else if (s == true) {
vertical -= 1;
console.log("Pressed s");
} else if (d == true) {
horizontal += 1;
console.log("Pressed d");
} else if (a == true) {
horizontal -= 1;
console.log("Pressed a");
}
console.log(
"Horizontal is: " + horizontal + " and vertical is: " + vertical
);
if (horizontal !== 0 && vertical !== 0) {
velocity
.add(forward.clone().multiplyScalar(speed * vertical))
.add(right.clone().multiplyScalar(speed * horizontal));
velocity.clampLength(-speed, speed);
} else if (horizontal !== 0) {
velocity.add(right.clone().multiplyScalar(speed * horizontal));
} else if (vertical !== 0) {
velocity.add(forward.clone().multiplyScalar(speed * vertical));
}
console.log("velocity is: " + JSON.stringify(velocity));
/** Updates player velocity */
api.velocity.set(
velocity.x * delta,
state.current.vel[1],
velocity.z * delta
);
/** Updates camera position */
camera.position.set(
sphereRef.current.position.x,
sphereRef.current.position.y + 1,
sphereRef.current.position.z
);
/** Handles jumping */
if (state.current.jumping && state.current.vel[1] < 0) {
/** Ground check */
const raycaster = new Raycaster(
sphereRef.current.position,
new Vector3(0, -1, 0),
0,
0.2
);
const intersects = raycaster.intersectObjects(scene.children);
if (intersects.length !== 0) {
state.current.jumping = false;
}
}
if (space && !state.current.jumping) {
const now = Date.now();
if (now > state.current.timeTojump) {
state.current.timeTojump = now + jumpCoolDown;
state.current.jumping = true;
api.velocity.set(state.current.vel[0], jumpSpeed, state.current.vel[2]);
}
}
/** Handles shooting */
const bulletDirection = cameraDirection.clone().multiplyScalar(bulletSpeed);
const bulletPosition = camera.position
.clone()
.add(cameraDirection.clone().multiplyScalar(2));
if (mouseInput.current.left) {
const now = Date.now();
if (now >= state.current.timeToShoot) {
state.current.timeToShoot = now + bulletCoolDown;
setBullets((bullets) => [
...bullets,
{
id: now,
position: [bulletPosition.x, bulletPosition.y, bulletPosition.z],
forward: [bulletDirection.x, bulletDirection.y, bulletDirection.z],
},
]);
}
}
});
return (
<>
{/** Renders bullets */}
{bullets.map((bullet) => {
return (
<Bullet
key={bullet.id}
velocity={bullet.forward}
position={bullet.position}
/>
);
})}
</>
);
};
It registers the keys that are being pressed but nothing happens.

At first glance, the following snippet seems to be the source of issue.
const getKeys = useCallback(() => {
const lowerCaseArray = [];
const hookReturn = {};
You may have to move those variables outside of the callback.
both handleKeyDown and handleKeyUp calls getKeys() which creates new empty values

Related

react canvas api, getImageData returns only rgba [0,0,0,0]

I am trying to make floodfill function by manipulating the pixel color.
When i scale the image to the canvas size, context.getImageData returns only rgba[0,0,0,0].
/* library */
import { useCallback, useEffect, useRef } from "react";
import { useSelector } from "react-redux";
/* module from local */
import { RootState } from "../../store";
import img1 from "../../test.png";
interface srcProps {
src: string;
}
export function ImageCanvas({ src }: srcProps) {
const canvasRef = useRef<HTMLCanvasElement>(null);
const contextRef = useRef<CanvasRenderingContext2D | null>(null);
const awareness = useSelector((state: RootState) => state.yjs.awareness);
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
canvas.addEventListener(
"touchmove",
(e) => {
e.preventDefault();
},
{ passive: false }
);
}, []);
useEffect(() => {
const img = new Image();
img.src = img1;
img.onload = () => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext("2d");
if (ctx === null) return;
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
contextRef.current = ctx;
};
}, []);
const _valid = useCallback((x: number, y: number) => {
console.log("Enter _valid");
const width = canvasRef.current?.offsetWidth as number;
const height = canvasRef.current?.offsetHeight as number;
console.log(`width: ${width}, height: ${height}`);
if (x > 0 && x < width && y > 0 && y < height) {
const imageData = contextRef.current?.getImageData(x, y, 1, 1);
console.log(imageData?.data);
if (
imageData?.data[0] === 255 &&
imageData?.data[1] === 255 &&
imageData?.data[2] === 255
) {
console.log("_valid => Success");
return true;
} else {
console.log("_valid => valid Failed!!");
return false;
}
}
}, []);
const _isPixel = useCallback((x: number, y: number) => {
console.log("Enter _isPixel");
const imageData = contextRef.current?.getImageData(x, y, 1, 1);
if (imageData) {
return imageData.data.length > 0;
} else {
return false;
}
}, []);
const _setPixel = useCallback((x: number, y: number) => {
console.log("Enter _setPixel");
const imageData = contextRef.current?.getImageData(x, y, 1, 1);
const pixelData = imageData?.data;
let color = awareness.getLocalState().color;
if (color === "black") {
color = "rgba(223,154,36)";
}
const values = color.match(/\d+/g).map(Number);
console.log("This is pixel data => _setPixel");
console.log(pixelData);
if (pixelData) {
pixelData[0] = values[0];
pixelData[1] = values[1];
pixelData[2] = values[2];
pixelData[3] = 255;
}
if (imageData) {
contextRef.current?.putImageData(imageData, x, y);
}
}, []);
const _fill = useCallback((x: number, y: number) => {
const fillStack = [];
fillStack.push([x, y]);
while (fillStack.length > 0) {
const [x, y]: any = fillStack.pop();
if (_valid(x, y)) {
console.log("It is valid");
} else {
console.log("NOT!! Valid");
continue;
}
if (_isPixel(x, y)) {
console.log("This is pixel");
} else {
console.log("NOT!! pixel");
continue;
}
_setPixel(x, y);
fillStack.push([x + 1, y]);
fillStack.push([x - 1, y]);
fillStack.push([x, y + 1]);
fillStack.push([x, y - 1]);
}
}, []);
const handlePointerDown = useCallback((e: React.PointerEvent<any>) => {
_fill(e.nativeEvent.offsetX, e.nativeEvent.offsetY);
}, []);
return (
<canvas
ref={canvasRef}
onPointerDown={handlePointerDown}
style={{ touchAction: "none" }}
className={"w-full"}
/>
);
}
export default ImageCanvas;
What i tried:
runs fill function after the image is loaded
set ctx.imageSmoothingEnabled = false;
test if the canvas size is equal to the image (then the fill function works because i can get the right pixel color)
What i expect:
i want to get the right pixel color, even the image is scale to the canvas size which is full screen size.

Multiple Shape Selection for resize in React-konva

I am working on drawing app project where I am using react.js and react-konva. Now I am trying to add multiple shape selection for resizing feature. I find a codesandbox example but when I am trying to run the code on my browser adding the code in my project, I am not getting expected output that is shown in codesandbox output. Only the last element is resizing, not the previous elements. and also cannot able to add cntrl and shift key button selection feature as well.
The example is - https://codesandbox.io/s/react-konva-multiple-selection-forked-wysowd?file=/src/index.js:6016-6045
I am sharing the code below of App.js:
import "./App.css";
import { Layer, Rect, Stage, Transformer } from "react-konva";
import { useEffect, useRef, useState } from "react";
import Rectangle from "./Rectangle";
import { v4 as uuidv4 } from 'uuid';
const initialRectangle = [
{
x: 10,
y: 10,
width: 100,
height: 100,
fill: "red",
id: uuidv4(),
},
{
x: 150,
y: 150,
width: 100,
height: 100,
fill: "green",
id: uuidv4(),
},
{
x: 300,
y: 300,
width: 100,
height: 100,
fill: "blue",
id: uuidv4(),
}
];
function App() {
const selectionRect = useRef(null);
const [rectangles, setRectangles] = useState(initialRectangle);
const [nodesArray, setNodes] = useState([]);
const trRef = useRef(null);
const [selectedId, selectShape] = useState(null);
const layerRef = useRef(null);
const Konva = window.Konva;
const selection = useRef({
visible: false,
x1: 0,
y1: 0,
x2: 0,
y2: 0,
});
const updateSelection = () => {
const node = selectionRect.current;
node.setAttrs({
visible: selection.current.visible,
x: Math.min(selection.current.x1, selection.current.x2),
y: Math.min(selection.current.y1, selection.current.y2),
width: Math.abs(selection.current.x1 - selection.current.x2),
height: Math.abs(selection.current.y1 - selection.current.y2),
fill: "rgba(0, 161, 255, 0.3)",
});
};
const onMouseDown = (e) => {
const isElement = e.target.findAncestor(".elements-container");
const isTransformer = e.target.findAncestor("Transformer");
if (isElement || isTransformer) {
return;
}
const pos = e.target.getStage().getPointerPosition();
selection.current.x1 = pos.x;
selection.current.y1 = pos.y;
selection.current.x2 = pos.x;
selection.current.y2 = pos.y;
selection.current.visible = true;
updateSelection();
};
const onMouseMove = (e) => {
if (!selection.current.visible) {
return;
}
const pos = e.target.getStage().getPointerPosition();
selection.current.x2 = pos.x;
selection.current.y2 = pos.y;
updateSelection();
};
const onMouseUp = (e) => {
if (!selection.current.visible) {
return;
}
const selBox = selectionRect.current.getClientRect();
const elements = [];
// console.log({layerRef});
layerRef.current.find(".rectangle").forEach((node) => {
const nodeBox = node.getClientRect();
if (Konva.Util.haveIntersection(selBox, nodeBox)) {
elements.push(node);
}
});
trRef.current.nodes(elements);
console.log({trRef})
selection.current.visible = false;
Konva.listenClickTap = false;
updateSelection();
};
const onClickTap = (e) => {
if (selection.current.visible) {
return;
}
let stage = e.target.getStage();
let layer = layerRef.current;
let tr = trRef.current;
if (e.target === stage) {
selectShape(null);
tr.nodes([]);
setNodes([]);
layer.batchDraw();
return;
}
if (!e.target.hasName("rectangle")) {
return;
}
const metaPressed = e.evt.ctrlKey || e.evt.shiftKey;
// console.log({nodesArray});
const isSelected = tr.nodes().indexOf(e.target) >= 0;
// console.log({isSelected})
// console.log(tr.nodes().indexOf(e.target));
// console.log(metaPressed, isSelected);
// if (!metaPressed && !isSelected) {
// selectShape(e.target.id());
// tr.nodes([e.target]);
// setNodes([e.target]);
// } else if (metaPressed && !isSelected) {
// selectShape(e.target.id());
// tr.nodes([...tr.nodes(), e.target]);
// setNodes([...nodesArray, e.target]);
// } else if (metaPressed && isSelected) {
// selectShape(null);
// const nodes = tr.nodes();
// console.log(nodes);
// const index = nodes.indexOf(e.target);
// nodes.splice(index, 1);
// setNodes(nodes);
// tr.nodes(nodes);
// }
if (!metaPressed && !isSelected) {
// if no key pressed and the node is not selected
// select just one
tr.nodes([e.target]);
} else if (metaPressed && isSelected) {
// if we pressed keys and node was selected
// we need to remove it from selection:
const nodes = tr.nodes().slice(); // use slice to have new copy of array
// remove node from array
nodes.splice(nodes.indexOf(e.target), 1);
tr.nodes(nodes);
} else if (metaPressed && !isSelected) {
// add the node into selection
const nodes = tr.nodes().concat([e.target]);
tr.nodes(nodes);
}
trRef.current.getLayer().batchDraw();
};
return (
<Stage
width={window.innerWidth}
height={window.innerHeight}
onMouseDown={onMouseDown}
onMouseMove={onMouseMove}
onMouseUp={onMouseUp}
onClick={onClickTap}
>
<Layer ref={layerRef}>
{rectangles.map((rect, i) => (
<Rectangle
key={i}
shapeProps={rect}
isSelected={rect.id === selectedId}
onSelect={(e) => {
// if (e.current !== undefined) {
// let temp = nodesArray;
// if (!nodesArray.includes(e.current)) temp.push(e.current);
// setNodes(temp);
// trRef.current.nodes(nodesArray);
// trRef.current.getLayer().batchDraw();
// }
selectShape(rect.id);
}}
onChange={(newAttrs) => {
const copyOfRectangles = JSON.parse(JSON.stringify(rectangles));
const currentIndex = rectangles.findIndex(item => item.id === newAttrs.id);
copyOfRectangles[currentIndex] = newAttrs;
setRectangles(copyOfRectangles);
console.log(copyOfRectangles);
}}
/>
))}
<Transformer
ref={trRef}
boundBoxFunc={(oldBox, newBox) => {
if (newBox.width < 5 || newBox.height < 5) {
return oldBox;
}
return newBox;
}}
/>
<Rect fill="blue" ref={selectionRect} />
</Layer>
</Stage>
);
}
export default App;
the below code is Rectangle component -
import React from 'react'
import { Rect } from 'react-konva';
const Rectangle = ({shapeProps, onSelect, onChange}) => {
const shapeRef = React.useRef(null);
return (
<Rect
{...shapeProps}
onClick={() => onSelect(shapeRef)}
// onDragStart={() => onChange(shapeRef)}
onDragEnd={(e) => onChange({
...shapeProps,
x: e.target.x(),
y: e.target.y(),
})}
ref={shapeRef}
onTap={() => onSelect(shapeRef)}
name="rectangle"
draggable
onTransformEnd={(e) => {
const node = shapeRef.current;
const scaleX = node.scaleX();
const scaleY = node.scaleY();
// we will reset it back
node.scaleX(1);
node.scaleY(1);
onChange({
...shapeProps,
x: node.x(),
y: node.y(),
// set minimal value
width: Math.max(5, node.width() * scaleX),
height: node.height() * scaleY
});
}
}
/>
)
}
export default Rectangle
What is the problem I don't understand.

meshBasicObject color change on condition react-fiber-three

** Looking for matched item to different color**
I'm currently trying to get color changes of mesh if condition match else keep it default color. but somehow it keeps same color for all mesh object.
I did try to add simple logic with Boolean to toggle for color but that also doesn't work. any advice will be accepted.
function generateMatrix(
mesh: MutableRefObject<InstancedMesh>,
getItemForGenerate: GetItemForGenerateData,
userDefineditems: string[],
xRotationValue: number,
yRotationValue: number
) {
if (getItemForGenerate?.items) {
const tempObject = new THREE.Object3D();
getItemForGenerate.items.forEach((items) => {
items.entities.forEach((item, index) => {
tempObject.position.set(
item.xMapPosition
item.yMapPosition
item.zMapPosition
);
//change color of the object if userdefined item matches the any of the items provided
if (userDefineditems?.includes(item.itemName)) {
tempObject.scale.set(item.length, item.depth , item.height );
// how to change color here if user defined lcoations matches any item on the Matrix
}
tempObject.scale.set(item.length , item.depth , item.height );
tempObject.updateMatrix();
mesh.current.setMatrixAt(index, tempObject.matrix);
});
});
mesh.current.rotation.x = xRotationValue;
mesh.current.rotation.y = yRotationValue;
mesh.current.instanceMatrix.needsUpdate = true;
return mesh;
}
}
const itemMatrix = (items: itemMatrixProps) => {
let mesh = useRef();
const { xRotationValue, yRotationValue, getItemForGenerate, userDefineditems } = items;
const [positionDetailsData, setPositionDetailsData] = useState<PositionDetails>(null);
useEffect(() => {
generateMatrix(mesh, getItemForGenerate, userDefineditems, xRotationValue, yRotationValue);
}, [getItemForGenerate, userDefineditems, xRotationValue, yRotationValue]);
useEffect(() => {
setPositionDetailsData(getPostion());
}, [getItemForGenerate]);
const getPostion = () => {
let xDivisor = 1;
let yDivisor = 1;
let zDivisor = 1;
const positionDetails: PositionDetails = {
xPosition: 0,
yPosition: 0,
zPosition: 0
};
let meshCount = getItemForGenerate?.count;
({ xDivisor, yDivisor, zDivisor } = generateMaxDivisors(getItemForGenerate, xDivisor, yDivisor, zDivisor));
const = xDivisor > yDivisor ? xDivisor : yDivisor;
const yPosition = yDivisor / (yDivisor + yDivisor * 0.3);
const xPosition = / (xDivisor + yDivisor);
positionDetails.xPosition = xPosition;
positionDetails.yPosition = yPosition;
positionDetails.zPosition = 0;
return positionDetails;
};
return (
<instancedMesh
ref={mesh}
args={[null, null, getItemForGenerate?.count]}
position={[-positionDetailsData?.xPosition, -positionDetailsData?.yPosition, positionDetailsData?.zPosition]}
>
<boxGeometry attach="geometry" args={[0.9, 0.9, 0.9]} />
<meshNormalMaterial />
<meshBasicMaterial color={'#047ABC'} />
</instancedMesh>
);
}
I try with THREE.Color but didn't work.

How to test carousel in RTL?

I want to test carousel in React-testing-library.
I can't check whether state is changed in RTL like enzyme does, so...
What I've done is just give carousel a scrollX and check whether it is changed
fireEvent.scroll(element, { target: { scrollX: 100 } });
// check
element.scrollLeft...
Is it collect or any ideas?
import React, { useRef } from "react";
import "./Carousel.css";
export interface CarouselProps {
children: React.ReactNode;
}
const Carousel = ({ children }: CarouselProps) => {
const carouselRef = useRef<HTMLDivElement>(null);
let isDown = false;
let startX = 0;
let scrollLeft = 0;
const onMouseDownCarousel: React.MouseEventHandler<HTMLDivElement> = (e) => {
if (!carouselRef || !carouselRef.current) {
return;
}
isDown = true;
carouselRef.current.classList.add("active");
startX = e.pageX - carouselRef.current.offsetLeft;
scrollLeft = carouselRef.current.scrollLeft;
};
const onMouseLeaveCarousel: React.MouseEventHandler<HTMLDivElement> = (e) => {
if (!carouselRef || !carouselRef.current) {
return;
}
isDown = false;
carouselRef.current.classList.remove("active");
};
const onMouseUpCarousel: React.MouseEventHandler<HTMLDivElement> = (e) => {
if (!carouselRef || !carouselRef.current) {
return;
}
isDown = false;
carouselRef.current.classList.remove("active");
};
const onMouseMoveCarousel: React.MouseEventHandler<HTMLDivElement> = (e) => {
if (!carouselRef || !carouselRef.current) {
return;
}
if (!isDown) return;
e.preventDefault();
const x = e.pageX - carouselRef.current.offsetLeft;
const walk = (x - startX) * 3;
carouselRef.current.scrollLeft = scrollLeft - walk;
};
return (
<div
className="carousel"
onMouseDown={onMouseDownCarousel}
onMouseLeave={onMouseLeaveCarousel}
onMouseUp={onMouseUpCarousel}
onMouseMove={onMouseMoveCarousel}
ref={carouselRef}
>
{children}
</div>
);
};
export default Carousel;

Problem with selecting an area using leaflet

so the problem is that when i'm trying to select an area using leaflet and click somewhere outside of selection tab it removes focus from the selection and after trying to select it doesn't work as intended meaning that i can't select properly
how do i return focus back to the tab?
appreciate any suggestions, if need to clarify something - tell me about it
here are the screenshots of how it should be and how it actually is
EDIT: here's code
import React, { useEffect, useState } from 'react';
import { useDispatch } from 'react-redux';
import L, { FeatureGroup, LatLngBounds, LeafletEvent, LeafletEventHandlerFn, LeafletMouseEvent, Point, PointTuple, Map } from 'leaflet';
import 'leaflet/dist/leaflet.css';
import 'leaflet-area-select';
import { Box, IconButton } from '#material-ui/core';
import GpsNotFixedRoundedIcon from '#material-ui/icons/GpsNotFixedRounded';
import { useStyles } from './styles';
import { disableMap, drawGrid } from '../../../utils/MapHelpers';
import { NewObs, Observation, Panorama } from '../../../state/reducers/types';
import { clearObs, saveGridSize, saveObsBounds, selectObsArea } from '../../../state/actions/inspector';
import OrientationMap from '../OrientationMap';
export type Grid = { gridCoordX: number[]; gridCoordY: number[]; gridLayer: FeatureGroup };
export let inspMap: Map;
interface AreaSelectEvent extends LeafletEvent {
bounds: LatLngBounds;
}
interface ILeafletWrapper {
handleObsForm: (showObsForm: boolean) => void;
handleIsSelectingObs: (isSelecting: boolean) => void;
handleOrientationMap: () => void;
handleGridSizeSelecting: () => void;
isSelectingObs: boolean;
newObs: NewObs;
observations: Observation[];
showOrientationMap: boolean;
isSelectingGridSize: boolean;
panorama: Panorama;
}
export const LeafletWrapper = (props: ILeafletWrapper) => {
const {
isSelectingObs,
handleObsForm,
handleIsSelectingObs,
observations,
showOrientationMap,
handleOrientationMap,
isSelectingGridSize,
handleGridSizeSelecting,
panorama,
} = props;
const { urlTemplates, originalResolution, originalZoomLevel, gridInfo } = panorama;
const [{ gridCoordX, gridCoordY, gridLayer }, setGrid] = useState<Grid>({
gridCoordX: [0],
gridCoordY: [0],
gridLayer: {},
} as Grid);
const [gridSizeBounds, setGridSizeBounds] = useState<LatLngBounds | undefined>();
const classes = useStyles();
const dispatch = useDispatch();
// initialization of Leaflet
useEffect(() => {
inspMap = new L.Map('map', { zoomControl: false, maxBoundsViscosity: 0.9 });
inspMap.setView(inspMap.unproject([originalResolution.x / 2, originalResolution.y / 2], originalZoomLevel), originalZoomLevel);
// you just need to pass something as url, further work with url is rewritten
const TileLayer = L.tileLayer('url', {
// lock to only zoom level for first version of app
minZoom: originalZoomLevel,
maxZoom: originalZoomLevel,
noWrap: true,
tileSize: 8192,
});
// custom internal method to get tile's url, have to use this method due to the inability of
// generating a templated URL from a predefined URLs (they are all unique)
TileLayer.getTileUrl = ({ z, x, y }: { z: number; x: number; y: number }) => {
return urlTemplates[y + '-' + x];
};
TileLayer.addTo(inspMap);
// disable the need to use a ctrl btn to select area
inspMap.selectArea.setControlKey(false);
// draw grid if there is cached panoramas info
if (gridInfo.x) {
const bounds = L.latLngBounds(
inspMap.unproject([0, 0], originalZoomLevel),
inspMap.unproject([gridInfo.x, gridInfo.y], originalZoomLevel),
);
const gridObj = drawGrid(bounds, inspMap, originalZoomLevel, originalResolution);
setGrid(gridObj);
setGridSizeBounds(bounds);
}
return () => {
inspMap.remove();
dispatch(clearObs());
};
}, []);
// disable interactions with leaflet while selectArea is enabled
useEffect(() => {
disableMap(isSelectingObs, inspMap);
if ((isSelectingObs || isSelectingGridSize) && !inspMap.selectArea.enabled()) {
inspMap.selectArea.enable();
inspMap.getContainer().style.cursor = 'crosshair';
inspMap.on('areaselected', handleObsAreaSelected as LeafletEventHandlerFn);
} else {
inspMap.getContainer().style.cursor = 'grab';
}
}, [isSelectingObs, isSelectingGridSize]);
// reset grid
useEffect(() => {
if (isSelectingGridSize && gridLayer?.remove) {
gridLayer.remove();
setGridSizeBounds(undefined);
}
}, [isSelectingGridSize, gridLayer]);
// draw observations
useEffect(() => {
// eslint-disable-next-line
observations.map((obs: any) => {
const index = obs.id;
if (obs.position.bounds) return;
const bounds = L.latLngBounds(
inspMap.unproject(obs.position.leftTopPoint, originalZoomLevel),
inspMap.unproject(obs.position.rightBottomPoint, originalZoomLevel),
);
const rectangle = L.rectangle(bounds, { color: '#ff9200', weight: 2, fillOpacity: 0 }).addTo(inspMap);
dispatch(saveObsBounds({ bounds: { remove: rectangle.remove.bind(rectangle) }, index }));
});
}, [observations]);
const handleObsAreaSelected = (e: AreaSelectEvent) => {
if (isSelectingObs) {
const leftTopPoint = inspMap.project(e.bounds.getNorthWest(), originalZoomLevel);
leftTopPoint.x = Math.round(leftTopPoint.x);
leftTopPoint.y = Math.round(leftTopPoint.y);
const rightBottomPoint = inspMap.project(e.bounds.getSouthEast(), originalZoomLevel);
rightBottomPoint.x = Math.round(rightBottomPoint.x);
rightBottomPoint.y = Math.round(rightBottomPoint.y);
const bounds = L.rectangle(e.bounds, { color: '#ff9200', weight: 2, fillOpacity: 0 });
bounds.addTo(inspMap);
dispatch(selectObsArea({ leftTopPoint, rightBottomPoint, bounds: { remove: bounds.remove.bind(bounds) } }));
handleObsForm(true);
handleIsSelectingObs(false);
inspMap.selectArea.disable();
inspMap.removeEventListener('areaselected', handleObsAreaSelected as LeafletEventHandlerFn);
}
if (isSelectingGridSize) {
handleGridSizeSelecting();
const gridObj = drawGrid(e.bounds, inspMap, originalZoomLevel, originalResolution);
setGrid(gridObj);
setGridSizeBounds(e.bounds);
inspMap.selectArea.disable();
inspMap.removeEventListener('areaselected', handleObsAreaSelected as LeafletEventHandlerFn);
dispatch(saveGridSize({ x: gridObj.gridCoordX[0], y: gridObj.gridCoordY[0] }));
}
};
const panToCell = ({ latlng }: LeafletMouseEvent) => {
// user click coordinates converted to pixel relative coordinate system for original zoom level
const point: Point = inspMap.project(latlng, originalZoomLevel);
const cellCenter = [];
// find center of cell
for (let i = 0; i < gridCoordX.length; i++) {
if (point.x < gridCoordX[i]) {
cellCenter.push(gridCoordX[0] / 2);
break;
}
if (point.x > gridCoordX[i] && (point.x < gridCoordX[i + 1] || i === gridCoordX.length - 1)) {
cellCenter.push(gridCoordX[i] + gridCoordX[0] / 2);
break;
}
}
for (let i = 0; i < gridCoordY.length; i++) {
if (point.y < gridCoordY[i]) {
cellCenter.push(gridCoordY[0] / 2);
break;
}
if (point.y > gridCoordY[i] && (point.y < gridCoordY[i + 1] || i === gridCoordY.length - 1)) {
cellCenter.push(gridCoordY[i] + gridCoordY[0] / 2);
break;
}
}
// temporary locked zoom level due only one zoom level available
inspMap.flyTo(inspMap.unproject(cellCenter as PointTuple, originalZoomLevel), originalZoomLevel);
};
const panToCenter = () => inspMap.panTo(inspMap.unproject([originalResolution.x / 2, originalResolution.y / 2], originalZoomLevel));
const zoomIn = () => inspMap.setZoom(inspMap.getZoom() + 1);
const zoomOut = () => inspMap.setZoom(inspMap.getZoom() - 1);
return (
<Box className={classes.root} height="100%">
<div id="map" style={{ height: '100%', width: '100%' }} />
<Box>
<IconButton className={classes.goToCenterBtn} aria-label="delete" onClick={panToCenter}>
<GpsNotFixedRoundedIcon color="inherit" />
</IconButton>
</Box>
<OrientationMap
panToCell={panToCell}
observations={observations}
showOrientationMap={showOrientationMap}
handleOrientationMap={handleOrientationMap}
handlePanToCenter={panToCenter}
zoomIn={zoomIn}
zoomOut={zoomOut}
gridSizeBounds={gridSizeBounds}
panorama={panorama}
/>
</Box>
);
};

Resources