Restructuration of the project folders

This commit is contained in:
Sebastien Riviere
2026-02-13 16:06:50 +01:00
parent 5f16500634
commit c1f1688794
188 changed files with 265 additions and 301 deletions

View File

@@ -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} />
)
}

View File

@@ -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;
}

View File

@@ -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>
);
}

View File

@@ -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>
)
}

View File

@@ -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>
);
}