mirror of
https://git.rezel.net/LudoTech/traque.git
synced 2026-02-27 17:20:17 +01:00
Restructuration of the project folders
This commit is contained in:
@@ -1,9 +0,0 @@
|
||||
export function NumberInput({onChange, ...props}) {
|
||||
function customStringToInt(e) {
|
||||
return parseInt(e, 10) || null;
|
||||
}
|
||||
|
||||
return (
|
||||
<input className="w-12 h-10 text-center rounded ring-1 ring-inset ring-black placeholder:text-gray-400" onChange={(e) => onChange(customStringToInt(e.target.value))} {...props} />
|
||||
)
|
||||
}
|
||||
@@ -1,181 +0,0 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { Marker, Tooltip, CircleMarker, Circle, Polygon, useMap } from "react-leaflet";
|
||||
import "leaflet/dist/leaflet.css";
|
||||
import 'leaflet-polylinedecorator';
|
||||
|
||||
export function Node({position, color = 'black', display = true}) {
|
||||
const nodeSize = 5;
|
||||
const fillOpacity = 1;
|
||||
|
||||
return (
|
||||
display && position && <CircleMarker center={position} radius={nodeSize} pathOptions={{ color: color, fillColor: color, fillOpacity: fillOpacity }} />
|
||||
);
|
||||
}
|
||||
|
||||
export function Label({position, label = "", color = "black", display = true}) {
|
||||
const size = 24;
|
||||
|
||||
const labelIcon = L.divIcon({
|
||||
html: `<div style="
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: ${color};
|
||||
font-weight: bold;
|
||||
font-size: ${size}px;
|
||||
">${label}</div>`,
|
||||
className: 'custom-label-icon',
|
||||
iconSize: [size, size],
|
||||
iconAnchor: [size / 2, size / 2]
|
||||
});
|
||||
|
||||
return (
|
||||
display && position && <Marker position={position} icon={labelIcon} />
|
||||
);
|
||||
}
|
||||
|
||||
export function Tag({text = "", display = true}) {
|
||||
const offset = [0.5, -15];
|
||||
const opacity = 1;
|
||||
|
||||
return (
|
||||
display && <Tooltip permanent direction="top" offset={offset} opacity={opacity} className="custom-tooltip">{text}</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
export function CircleZone({circle, color = "black", display = true, children}) {
|
||||
const opacity = '0.1';
|
||||
const border = 3;
|
||||
|
||||
return (
|
||||
display && circle &&
|
||||
<Circle center={circle.center} radius={circle.radius} pathOptions={{ color: color, fillColor: color, fillOpacity: opacity, weight: border }}>
|
||||
{children}
|
||||
</Circle>
|
||||
);
|
||||
}
|
||||
|
||||
export function PolygonZone({polygon, color = "black", display = true, children}) {
|
||||
const opacity = '0.1';
|
||||
const border = 3;
|
||||
|
||||
return (
|
||||
display && polygon && polygon.length >= 3 &&
|
||||
<Polygon positions={polygon} pathOptions={{ color: color, fillColor: color, fillOpacity: opacity, weight: border }}>
|
||||
{children}
|
||||
</Polygon>
|
||||
);
|
||||
}
|
||||
|
||||
export function Position({position, color = "blue", onClick = () => {}, display = true, children}) {
|
||||
|
||||
const positionIcon = new L.Icon({
|
||||
iconUrl: `/icons/marker/${color}.png`,
|
||||
iconSize: [30, 30],
|
||||
iconAnchor: [15, 15],
|
||||
popupAnchor: [0, -15],
|
||||
shadowSize: [30, 30],
|
||||
});
|
||||
|
||||
return (
|
||||
display && position &&
|
||||
<Marker position={position} icon={positionIcon} eventHandlers={{click: onClick}}>
|
||||
{children}
|
||||
</Marker>
|
||||
);
|
||||
}
|
||||
|
||||
export function Arrow({ pos1, pos2, color = 'black', display = true }) {
|
||||
const weight = 5;
|
||||
const arrowSize = 20;
|
||||
const insetPixels = 25;
|
||||
const map = useMap();
|
||||
const [insetPositions, setInsetPositions] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
const updateInsetLine = () => {
|
||||
if (!pos1 || !pos2) {
|
||||
setInsetPositions(null);
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert lat/lng to screen coordinates
|
||||
const point1 = map.latLngToContainerPoint(pos1);
|
||||
const point2 = map.latLngToContainerPoint(pos2);
|
||||
|
||||
// Calculate direction vector
|
||||
const dx = point2.x - point1.x;
|
||||
const dy = point2.y - point1.y;
|
||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
// If the points are too close, do not render
|
||||
if (distance <= 2*insetPixels) {
|
||||
setInsetPositions(null);
|
||||
return;
|
||||
}
|
||||
|
||||
// Normalize direction vector
|
||||
const unitX = dx / distance;
|
||||
const unitY = dy / distance;
|
||||
|
||||
// Calculate new start and end points in screen coordinates
|
||||
const newStartPoint = {
|
||||
x: point1.x + (unitX * insetPixels),
|
||||
y: point1.y + (unitY * insetPixels)
|
||||
};
|
||||
|
||||
const newEndPoint = {
|
||||
x: point2.x - (unitX * insetPixels),
|
||||
y: point2.y - (unitY * insetPixels)
|
||||
};
|
||||
|
||||
// Convert back to lat/lng
|
||||
const newStartLatLng = map.containerPointToLatLng(newStartPoint);
|
||||
const newEndLatLng = map.containerPointToLatLng(newEndPoint);
|
||||
|
||||
setInsetPositions([[newStartLatLng.lat, newStartLatLng.lng], [newEndLatLng.lat, newEndLatLng.lng]]);
|
||||
};
|
||||
|
||||
updateInsetLine();
|
||||
|
||||
// Update when map moves or zooms
|
||||
map.on('zoom move', updateInsetLine);
|
||||
|
||||
return () => map.off('zoom move', updateInsetLine);
|
||||
}, [pos1, pos2]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!display || !insetPositions) return;
|
||||
|
||||
// Create the base polyline
|
||||
const polyline = L.polyline(insetPositions, {
|
||||
color: color,
|
||||
weight: weight
|
||||
}).addTo(map);
|
||||
|
||||
// Create the arrow decorator
|
||||
const decorator = L.polylineDecorator(polyline, {
|
||||
patterns: [{
|
||||
offset: '100%',
|
||||
repeat: 0,
|
||||
symbol: L.Symbol.arrowHead({
|
||||
pixelSize: arrowSize,
|
||||
polygon: false,
|
||||
pathOptions: {
|
||||
stroke: true,
|
||||
weight: weight,
|
||||
color: color
|
||||
}
|
||||
})
|
||||
}]
|
||||
}).addTo(map);
|
||||
|
||||
// Cleanup function
|
||||
return () => {
|
||||
map.removeLayer(polyline);
|
||||
map.removeLayer(decorator);
|
||||
};
|
||||
}, [display, insetPositions])
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd';
|
||||
|
||||
export function List({array, selectedId, onSelected, children}) {
|
||||
const canSelect = selectedId !== undefined
|
||||
const cursor = () => canSelect ? " cursor-pointer" : "";
|
||||
const outline = (id) => canSelect && id === selectedId ? " outline outline-4 outline-black" : "";
|
||||
|
||||
return (
|
||||
<div className='w-full h-full bg-gray-300 overflow-y-scroll'>
|
||||
<ul className="w-full p-1 pb-0">
|
||||
{array?.map((elem, i) => (
|
||||
<li className="w-full" key={elem.id}>
|
||||
<div className={"w-full" + cursor() + outline(elem.id)} onClick={() => canSelect && onSelected(elem.id)}>
|
||||
{children(elem, i)}
|
||||
</div>
|
||||
<div className="w-full h-1"/>
|
||||
</li>
|
||||
)) ?? null}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ReorderList({droppableId, array, setArray, children}) {
|
||||
const [localArray, setLocalArray] = useState(array);
|
||||
|
||||
useEffect(() => {
|
||||
setLocalArray(array);
|
||||
}, [array])
|
||||
|
||||
function reorder(list, startIndex, endIndex) {
|
||||
const result = Array.from(list);
|
||||
const [removed] = result.splice(startIndex, 1);
|
||||
result.splice(endIndex, 0, removed);
|
||||
return result;
|
||||
};
|
||||
|
||||
function onDragEnd(result) {
|
||||
if (!result.destination) return;
|
||||
if (result.destination.index === result.source.index) return;
|
||||
const newArray = reorder(array, result.source.index, result.destination.index);
|
||||
setLocalArray(newArray);
|
||||
setArray(newArray);
|
||||
}
|
||||
|
||||
return (
|
||||
<DragDropContext onDragEnd={onDragEnd} >
|
||||
<Droppable droppableId={droppableId}>
|
||||
{provided => (
|
||||
<div className='w-full h-full bg-gray-300 overflow-y-scroll' ref={provided.innerRef} {...provided.droppableProps}>
|
||||
<ul className="w-full p-1 pb-0">
|
||||
{localArray?.map((elem, i) => (
|
||||
<li className='w-full' key={elem.id}>
|
||||
<Draggable draggableId={elem.id.toString()} index={i}>
|
||||
{provided => (
|
||||
<div className='w-full cursor-grab' {...provided.draggableProps} {...provided.dragHandleProps} ref={provided.innerRef}>
|
||||
{children(elem, i)}
|
||||
<div className="w-full h-1"/>
|
||||
</div>
|
||||
)}
|
||||
</Draggable>
|
||||
</li>
|
||||
)) ?? null}
|
||||
</ul>
|
||||
{provided.placeholder}
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
</DragDropContext>
|
||||
);
|
||||
}
|
||||
@@ -1,164 +0,0 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { MapContainer, TileLayer, useMap } from "react-leaflet";
|
||||
import "leaflet/dist/leaflet.css";
|
||||
import { mapLocations, mapZooms, mapStyles } from "@/util/configurations";
|
||||
|
||||
export function MapPan({center, zoom, animate=false}) {
|
||||
const map = useMap();
|
||||
|
||||
useEffect(() => {
|
||||
if (center && zoom) {
|
||||
map.flyTo(center, zoom, { animate: animate });
|
||||
}
|
||||
}, [center, zoom]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function MapEventListener({ onLeftClick, onRightClick, onMouseMove, onDragStart, onWheel }) {
|
||||
const map = useMap();
|
||||
// TODO use useMapEvents instead of this + detect when zoom
|
||||
|
||||
// Handle the mouse click left
|
||||
useEffect(() => {
|
||||
if (!onLeftClick) return;
|
||||
|
||||
let moved = false;
|
||||
let downButton = null;
|
||||
|
||||
const handleMouseDown = (e) => {
|
||||
moved = false;
|
||||
downButton = e.originalEvent.button;
|
||||
};
|
||||
|
||||
const handleMouseMove = () => {
|
||||
moved = true;
|
||||
};
|
||||
|
||||
const handleMouseUp = (e) => {
|
||||
if (!moved) {
|
||||
if (downButton == 0) {
|
||||
onLeftClick(e);
|
||||
}
|
||||
}
|
||||
downButton = null;
|
||||
};
|
||||
|
||||
map.on('mousedown', handleMouseDown);
|
||||
map.on('mousemove', handleMouseMove);
|
||||
map.on('mouseup', handleMouseUp);
|
||||
|
||||
return () => {
|
||||
map.off('mousedown', handleMouseDown);
|
||||
map.off('mousemove', handleMouseMove);
|
||||
map.off('mouseup', handleMouseUp);
|
||||
};
|
||||
}, [onLeftClick, onRightClick]);
|
||||
|
||||
// Handle the right click
|
||||
useEffect(() => {
|
||||
if (!onRightClick) return;
|
||||
|
||||
const handleMouseDown = (e) => {
|
||||
if (e.originalEvent.button == 2) {
|
||||
onRightClick(e);
|
||||
}
|
||||
};
|
||||
|
||||
map.on('mousedown', handleMouseDown);
|
||||
|
||||
return () => {
|
||||
map.off('mousedown', handleMouseDown);
|
||||
}
|
||||
}, [onRightClick]);
|
||||
|
||||
// Handle the mouse move
|
||||
useEffect(() => {
|
||||
if (!onMouseMove) return;
|
||||
|
||||
map.on('mousemove', onMouseMove);
|
||||
|
||||
return () => {
|
||||
map.off('mousemove', onMouseMove);
|
||||
}
|
||||
}, [onMouseMove]);
|
||||
|
||||
// Handle the drag start
|
||||
useEffect(() => {
|
||||
if (!onDragStart) return;
|
||||
|
||||
map.on('dragstart', onDragStart);
|
||||
|
||||
return () => {
|
||||
map.off('dragstart', onDragStart);
|
||||
}
|
||||
}, [onDragStart]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!onWheel) return;
|
||||
|
||||
const container = map.getContainer();
|
||||
container.addEventListener('wheel', onWheel);
|
||||
|
||||
return () => {
|
||||
container.removeEventListener('wheel', onWheel);
|
||||
}
|
||||
}, [onWheel]);
|
||||
|
||||
// Prevent right click context menu
|
||||
useEffect(() => {
|
||||
const container = map.getContainer();
|
||||
const preventContextMenu = (e) => e.preventDefault();
|
||||
container.addEventListener('contextmenu', preventContextMenu);
|
||||
return () => container.removeEventListener('contextmenu', preventContextMenu);
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function MapResizeWatcher() {
|
||||
const map = useMap();
|
||||
|
||||
useEffect(() => {
|
||||
const observer = new ResizeObserver(() => {
|
||||
map.invalidateSize();
|
||||
});
|
||||
observer.observe(map.getContainer());
|
||||
|
||||
return () => observer.disconnect();
|
||||
}, [map]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function CustomMapContainer({mapStyle, children}) {
|
||||
const [location, setLocation] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!navigator.geolocation) {
|
||||
console.log('Geolocation not supported');
|
||||
return;
|
||||
}
|
||||
|
||||
navigator.geolocation.getCurrentPosition(
|
||||
(pos) => {
|
||||
setLocation([pos.coords.latitude, pos.coords.longitude]);
|
||||
},
|
||||
(err) => console.log("Error :", err),
|
||||
{
|
||||
enableHighAccuracy: true,
|
||||
timeout: 10000,
|
||||
maximumAge: 0
|
||||
}
|
||||
);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<MapContainer className='w-full h-full' center={mapLocations.paris} zoom={mapZooms.low} scrollWheelZoom={true}>
|
||||
<TileLayer url={(mapStyle || mapStyles.default).url} attribution={(mapStyle || mapStyles.default).attribution}/>
|
||||
<MapPan center={location} zoom={mapZooms.high}/>
|
||||
<MapResizeWatcher/>
|
||||
{children}
|
||||
</MapContainer>
|
||||
)
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
export function Section({title, outerClassName, innerClassName, children}) {
|
||||
return (
|
||||
<div className={outerClassName}>
|
||||
<div className='w-full h-full flex flex-col shadow-2xl'>
|
||||
{title &&
|
||||
<div className='w-full p-1 bg-custom-light-blue text-center'>
|
||||
<h2 className="text-l">{title}</h2>
|
||||
</div>
|
||||
}
|
||||
<div className='w-full flex-1 min-h-0 p-3 bg-white'>
|
||||
<div className={`w-full h-full ${innerClassName}`}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user