This commit is contained in:
Sebastien Riviere
2025-09-08 15:08:44 +02:00
parent 75f8b10ecd
commit 7e4d9f910a
37 changed files with 403 additions and 435 deletions

View File

@@ -1,14 +1,13 @@
import { useEffect, useState } from "react";
import { Circle } from "react-leaflet";
import "leaflet/dist/leaflet.css";
import { CustomButton } from "@/components/button";
import { CustomMapContainer, MapEventListener } from "@/components/map";
import { TextInput } from "@/components/input";
import { NumberInput } from "@/components/input";
import useAdmin from "@/hook/useAdmin";
import useMapCircleDraw from "@/hook/useCircleDraw";
import useLocalVariable from "@/hook/useLocalVariable";
import { defaultZoneSettings } from "@/util/configurations";
import { ZoneTypes } from "@/util/types";
import { CircleZone } from "@/components/layer";
const EditMode = {
MIN: 0,
@@ -16,8 +15,24 @@ const EditMode = {
}
function Drawings({ minZone, setMinZone, maxZone, setMaxZone, editMode }) {
const { center: maxCenter, radius: maxRadius, handleLeftClick: maxLeftClick, handleRightClick: maxRightClick, handleMouseMove: maxHover } = useMapCircleDraw(maxZone, setMaxZone);
const { center: minCenter, radius: minRadius, handleLeftClick: minLeftClick, handleRightClick: minRightClick, handleMouseMove: minHover } = useMapCircleDraw(minZone, setMinZone);
const { drawingCircle: drawingMaxCircle, handleLeftClick: maxLeftClick, handleRightClick: maxRightClick, handleMouseMove: maxHover } = useMapCircleDraw(maxZone, setMaxZone);
const { drawingCircle: drawingMinCircle, handleLeftClick: minLeftClick, handleRightClick: minRightClick, handleMouseMove: minHover } = useMapCircleDraw(minZone, setMinZone);
function MaxCircleZone() {
return (
drawingMaxCircle
? <CircleZone circle={drawingMaxCircle} color="blue" />
: <CircleZone circle={maxZone} color="blue" />
);
}
function MinCircleZone() {
return (
drawingMinCircle
? <CircleZone circle={drawingMinCircle} color="red" />
: <CircleZone circle={minZone} color="red" />
);
}
return (<>
<MapEventListener
@@ -25,33 +40,31 @@ function Drawings({ minZone, setMinZone, maxZone, setMaxZone, editMode }) {
onRightClick={editMode == EditMode.MAX ? maxRightClick : minRightClick}
onMouseMove={editMode == EditMode.MAX ? maxHover : minHover}
/>
{minCenter && minRadius && <Circle center={minCenter} radius={minRadius} color="red" fillColor="red" />}
{maxCenter && maxRadius && <Circle center={maxCenter} radius={maxRadius} color="blue" fillColor="blue" />}
<MaxCircleZone/>
<MinCircleZone/>
</>);
}
export default function CircleZoneSelector() {
export default function CircleZoneSelector({ display }) {
const {zoneSettings, outOfZoneDelay, updateSettings} = useAdmin();
const [localZoneSettings, setLocalZoneSettings, applyLocalZoneSettings] = useLocalVariable(zoneSettings, (e) => updateSettings({zone: e}));
const [localOutOfZoneDelay, setLocalOutOfZoneDelay, applyLocalOutOfZoneDelay] = useLocalVariable(outOfZoneDelay, (e) => updateSettings({outOfZoneDelay: e}));
const [editMode, setEditMode] = useState(EditMode.MIN);
const [editMode, setEditMode] = useState(EditMode.MAX);
useEffect(() => {
if (localZoneSettings.type != ZoneTypes.CIRCLE) {
if (!localZoneSettings || localZoneSettings.type != ZoneTypes.CIRCLE) {
setLocalZoneSettings(defaultZoneSettings.circle);
}
}, [localZoneSettings]);
useEffect(() => {
setEditMode(editMode == EditMode.MIN ? EditMode.MAX : EditMode.MIN);
}, [localZoneSettings.min, localZoneSettings.max]);
function setMinZone(minZone) {
setLocalZoneSettings({...localZoneSettings, min: minZone});
setEditMode(EditMode.MAX);
}
function setMaxZone(maxZone) {
setLocalZoneSettings({...localZoneSettings, max: maxZone});
setEditMode(EditMode.MIN);
}
function updateReductionCount(reductionCount) {
@@ -67,44 +80,30 @@ export default function CircleZoneSelector() {
applyLocalOutOfZoneDelay();
}
function customStringToInt(e) {
return parseInt(e, 10) || null;
}
return (
<div className='h-full w-full gap-3 flex flex-row'>
{localZoneSettings.type == ZoneTypes.CIRCLE && <>
<div className={display ? 'w-full h-full gap-3 flex flex-row' : "hidden"}>
{localZoneSettings && localZoneSettings.type == ZoneTypes.CIRCLE && <>
<div className="h-full flex-1">
<CustomMapContainer>
<Drawings minZone={localZoneSettings.min} setMinZone={setMinZone} maxZone={localZoneSettings.max} setMaxZone={setMaxZone} editMode={editMode} />
</CustomMapContainer>
</div>
<div className="h-full w-1/6 flex flex-col gap-3">
<div className="w-full h-15">
{editMode == EditMode.MIN && <CustomButton color="blue" onClick={() => setEditMode(EditMode.MAX)}>Click to edit first zone</CustomButton>}
{editMode == EditMode.MAX && <CustomButton color="red" onClick={() => setEditMode(EditMode.MIN)}>Click to edit last zone</CustomButton>}
</div>
{editMode == EditMode.MIN && <button className="w-full h-16 text-lg text-white rounded bg-blue-600 hover:bg-blue-500" onClick={() => setEditMode(EditMode.MAX)}>Click to edit first zone</button>}
{editMode == EditMode.MAX && <button className="w-full h-16 text-lg text-white rounded bg-red-600 hover:bg-red-500" onClick={() => setEditMode(EditMode.MIN)}>Click to edit last zone</button>}
<div className="w-full flex flex-row gap-2 items-center justify-between">
<p>Reduction number</p>
<div className="w-16 h-10">
<TextInput id="reduction-number" value={localZoneSettings.reductionCount ?? ""} onChange={(e) => updateReductionCount(customStringToInt(e.target.value))} />
</div>
<NumberInput id="reduction-number" value={localZoneSettings.reductionCount ?? ""} onChange={updateReductionCount} />
</div>
<div className="w-full flex flex-row gap-2 items-center justify-between">
<p>Zone duration</p>
<div className="w-16 h-10">
<TextInput id="duration" value={localZoneSettings.duration ?? ""} onChange={(e) => updateDuration(customStringToInt(e.target.value))} />
</div>
<NumberInput id="duration" value={localZoneSettings.duration ?? ""} onChange={updateDuration} />
</div>
<div className="w-full flex flex-row gap-2 items-center justify-between">
<p>Timeout</p>
<div className="w-16 h-10">
<TextInput id="timeout" value={localOutOfZoneDelay ?? ""} onChange={(e) => setLocalOutOfZoneDelay(customStringToInt(e.target.value))} />
</div>
</div>
<div className="w-full h-15">
<CustomButton color="green" onClick={handleSubmit}>Apply</CustomButton>
<NumberInput id="timeout-circle-selector" value={localOutOfZoneDelay ?? ""} onChange={setLocalOutOfZoneDelay} />
</div>
<button className="w-full h-16 text-lg text-white rounded bg-green-600 hover:bg-green-500" onClick={handleSubmit}>Apply</button>
</div>
</>}
</div>

View File

@@ -6,7 +6,7 @@ function MessageInput({title, ...props}) {
return (
<div className="w-full flex flex-row gap-3 items-center">
<p>{title}</p>
<input {...props} type="text" className="w-full h-8 p-2 rounded ring-1 ring-inset ring-gray-400 placeholder:text-gray-600" />
<input className="w-full p-1 rounded ring-1 ring-inset ring-gray-400 placeholder:text-gray-600" {...props} />
</div>
);
}

View File

@@ -1,9 +1,9 @@
import { useEffect, useState } from "react";
import { Circle, Tooltip } from "react-leaflet";
import { List } from "@/components/list";
import { CustomMapContainer, MapEventListener } from "@/components/map";
import useAdmin from '@/hook/useAdmin';
import useMultipleCircleDraw from "@/hook/useMultipleCircleDraw";
import { CircleZone, Tag } from "@/components/layer";
function Drawings({ placementZones, addZone, removeZone, handleRightClick }) {
const { handleLeftClick, handleRightClick: handleRightClickDrawing } = useMultipleCircleDraw(placementZones, addZone, removeZone, 30);
@@ -16,14 +16,14 @@ function Drawings({ placementZones, addZone, removeZone, handleRightClick }) {
return (<>
<MapEventListener onLeftClick={handleLeftClick} onRightClick={modifiedHandleRightClick} />
{ placementZones.map(placementZone =>
<Circle key={placementZone.id} center={placementZone.center} radius={placementZone.radius} color="blue" fillColor="blue">
<Tooltip permanent direction="top" offset={[0.5, -15]} className="custom-tooltip">{placementZone.name}</Tooltip>
</Circle>
<CircleZone key={placementZone.id} circle={placementZone} color="blue">
<Tag text={placementZone.name} />
</CircleZone>
)}
</>);
}
export default function PlacementZoneSelector() {
export default function PlacementZoneSelector({ display }) {
const { teams, getTeam, placementTeam } = useAdmin();
const [selectedTeamId, setSelectedTeamId] = useState(null);
const [placementZones, setPlacementZones] = useState([]);
@@ -46,7 +46,7 @@ export default function PlacementZoneSelector() {
}
return (
<div className='w-full h-full gap-3 flex flex-row'>
<div className={display ? 'w-full h-full gap-3 flex flex-row' : "hidden"}>
<div className="h-full flex-1">
<CustomMapContainer>
<Drawings placementZones={placementZones} addZone={addPlacementZone} removeZone={removePlacementZone} handleRightClick={() => setSelectedTeamId(null)} />
@@ -58,7 +58,7 @@ export default function PlacementZoneSelector() {
</div>
<List array={teams} selectedId={selectedTeamId} onSelected={(id) => setSelectedTeamId(selectedTeamId != id ? id : null)}>
{ (team) =>
<div className="w-full flex flex-row items-center justify-between gap-2 p-2 bg-white">
<div key={team.id} className="w-full flex flex-row items-center justify-between gap-2 p-2 bg-white">
<p className='text-xl font-bold'>{team.name}</p>
</div>
}

View File

@@ -17,23 +17,19 @@ function ZoneTypeButton({title, onClick, isSelected}) {
);
}
export default function PlayingZoneSelector() {
export default function PlayingZoneSelector({ display }) {
const [zoneType, setZoneType] = useState(ZoneTypes.POLYGON);
return (
<div className="w-full h-full flex flex-col gap-3">
<div className={display ? 'w-full h-full gap-3 flex flex-col' : "hidden"}>
<div className="w-full flex flex-row gap-3 items-center">
<p className="text-l">Type de zone :</p>
<ZoneTypeButton title="Cercles" onClick={() => setZoneType(ZoneTypes.CIRCLE)} isSelected={zoneType == ZoneTypes.CIRCLE} />
<ZoneTypeButton title="Polygones" onClick={() => setZoneType(ZoneTypes.POLYGON)} isSelected={zoneType == ZoneTypes.POLYGON} />
</div>
<div className="w-full flex-1">
{zoneType == ZoneTypes.CIRCLE &&
<CircleZoneSelector/>
}
{zoneType == ZoneTypes.POLYGON &&
<PolygonZoneSelector/>
}
<CircleZoneSelector display={zoneType == ZoneTypes.CIRCLE} />
<PolygonZoneSelector display={zoneType == ZoneTypes.POLYGON} />
</div>
</div>
);

View File

@@ -1,11 +1,10 @@
import { useEffect, useState } from "react";
import { Polyline } from "react-leaflet";
import "leaflet/dist/leaflet.css";
import { CustomButton } from "@/components/button";
import { ReorderList } from "@/components/list";
import { CustomMapContainer, MapEventListener } from "@/components/map";
import { TextInput } from "@/components/input";
import { Node, LabeledPolygon } from "@/components/layer";
import { NumberInput } from "@/components/input";
import { Node, PolygonZone, Label } from "@/components/layer";
import useAdmin from "@/hook/useAdmin";
import useMapPolygonDraw from "@/hook/usePolygonDraw";
import useLocalVariable from "@/hook/useLocalVariable";
@@ -22,25 +21,45 @@ function Drawings({ localZoneSettings, addZone, removeZone }) {
}
}, [localZoneSettings]);
function getLabelPosition(polygon) {
const sum = polygon.reduce(
(acc, coord) => ({
lat: acc.lat + coord.lat,
lng: acc.lng + coord.lng
}),
{ lat: 0, lng: 0 }
);
// The calculated mean point can be out of the polygon
// Idea : take the mean point of the largest convex subpolygon
return {lat: sum.lat / polygon.length, lng: sum.lng / polygon.length};
}
return (<>
<MapEventListener onLeftClick={handleLeftClick} onRightClick={handleRightClick} onMouseMove={handleMouseMove} />
{localZoneSettings.polygons.map((zone, i) => <LabeledPolygon key={i} polygon={zone.polygon} label={zone.id} />)}
{localZoneSettings.polygons.map(zone =>
<PolygonZone key={zone.id} polygon={zone.polygon} color="black" opacity='0.5' >
<Label position={getLabelPosition(zone.polygon)} label={zone.id} color="white" />
</PolygonZone>
)}
{ currentPolygon.length > 0 && <>
<Node pos={currentPolygon[0]} color={"red"} />
<Polyline positions={currentPolygon} pathOptions={{ color: "red", weight: 3 }} />
<Node position={currentPolygon[0]} color="red" />
</>}
{highlightNodes.map((node, i) => <Node key={i} pos={node} color={"black"} />)}
{highlightNodes.map((node, i) =>
<Node key={i} position={node} color="black" />
)}
</>);
}
export default function PolygonZoneSelector() {
export default function PolygonZoneSelector({ display }) {
const defaultDuration = 10;
const {zoneSettings, outOfZoneDelay, updateSettings} = useAdmin();
const [localZoneSettings, setLocalZoneSettings, applyLocalZoneSettings] = useLocalVariable(zoneSettings, (e) => updateSettings({zone: e}));
const [localOutOfZoneDelay, setLocalOutOfZoneDelay, applyLocalOutOfZoneDelay] = useLocalVariable(outOfZoneDelay, (e) => updateSettings({outOfZoneDelay: e}));
useEffect(() => {
if (localZoneSettings.type != ZoneTypes.POLYGON) {
if (!localZoneSettings || localZoneSettings.type != ZoneTypes.POLYGON) {
setLocalZoneSettings(defaultZoneSettings.polygon);
}
}, [localZoneSettings]);
@@ -76,14 +95,10 @@ export default function PolygonZoneSelector() {
applyLocalZoneSettings();
applyLocalOutOfZoneDelay();
}
function customStringToInt(e) {
return parseInt(e, 10) || null;
}
return (
<div className='h-full w-full gap-3 flex flex-row'>
{localZoneSettings.type == ZoneTypes.POLYGON && <>
<div className={display ? 'w-full h-full gap-3 flex flex-row' : "hidden"}>
{localZoneSettings && localZoneSettings.type == ZoneTypes.POLYGON && <>
<div className="h-full flex-1">
<CustomMapContainer>
<Drawings localZoneSettings={localZoneSettings} addZone={addZone} removeZone={removeZone} />
@@ -95,23 +110,17 @@ export default function PolygonZoneSelector() {
</div>
<ReorderList droppableId="zones-order" array={localZoneSettings.polygons} setArray={setLocalZoneSettingsPolygons}>
{ (zone) =>
<div className="w-full p-2 bg-white flex flex-row gap-2 items-center justify-between">
<div key={zone.id} className="w-full p-2 bg-white flex flex-row gap-2 items-center justify-between">
<p>Zone {zone.id}</p>
<div className="w-16 h-10">
<TextInput value={zone.duration || ""} onChange={(e) => updateDuration(zone.id, customStringToInt(e.target.value))}/>
</div>
<NumberInput id={zone.id} value={zone.duration || ""} onChange={value => updateDuration(zone.id, value)}/>
</div>
}
</ReorderList>
<div className="w-full flex flex-row gap-2 items-center justify-between">
<p>Timeout</p>
<div className="w-16 h-10">
<TextInput id="timeout" value={localOutOfZoneDelay ?? ""} onChange={(e) => setLocalOutOfZoneDelay(customStringToInt(e.target.value))}/>
</div>
</div>
<div className="w-full h-15">
<CustomButton color="green" onClick={handleSubmit}>Apply</CustomButton>
<NumberInput id="timeout-polygon-selector" value={localOutOfZoneDelay ?? ""} onChange={setLocalOutOfZoneDelay}/>
</div>
<button className="w-full h-16 text-lg text-white rounded bg-green-600 hover:bg-green-500" onClick={handleSubmit}>Apply</button>
</div>
</>}
</div>

View File

@@ -2,7 +2,7 @@ import { useState } from "react";
import { ReorderList } from '@/components/list';
import useAdmin from '@/hook/useAdmin';
import useLocalVariable from "@/hook/useLocalVariable";
import { TextInput } from "@/components/input";
import { NumberInput } from "@/components/input";
import { Section } from "@/components/section";
function TeamManagerItem({ team }) {
@@ -52,9 +52,7 @@ export default function TeamManager() {
</div>
<div className="w-full flex flex-row gap-2 items-center justify-between">
<p>Interval between position updates</p>
<div className="w-16 h-10">
<TextInput id="position-update" value={localSendPositionDelay ?? ""} onChange={(e) => setLocalSendPositionDelay(parseInt(e.target.value, 10))} onBlur={applyLocalSendPositionDelay} />
</div>
<NumberInput id="position-update" value={localSendPositionDelay ?? ""} onChange={setLocalSendPositionDelay} onBlur={applyLocalSendPositionDelay} />
</div>
</Section>
);

View File

@@ -37,14 +37,14 @@ function TabButton({title, onClick, isSelected}) {
);
}
export default function ConfigurationPage() {
export default function ParametersPage() {
const { useProtect } = useAdminConnexion();
const [currentTab, setCurrentTab] = useState(Tabs.PLACEMENT_ZONES);
useProtect();
return (
<div className='h-full bg-gray-200 p-3 flex flex-row gap-3'>
<div className='w-full h-full p-3 flex flex-row gap-3'>
<div className="h-full w-2/6 gap-3 flex flex-col">
<ParametersTitle/>
<Messages/>
@@ -56,12 +56,8 @@ export default function ConfigurationPage() {
<TabButton title="Zones de jeu" onClick={() => setCurrentTab(Tabs.PLAYING_ZONES)} isSelected={currentTab == Tabs.PLAYING_ZONES}/>
</div>
<div className="w-full flex-1 p-3 bg-white">
{ currentTab == Tabs.PLAYING_ZONES &&
<PlayingZoneSelector />
}
{ currentTab == Tabs.PLACEMENT_ZONES &&
<PlacementZoneSelector />
}
<PlacementZoneSelector display={currentTab == Tabs.PLACEMENT_ZONES} />
<PlayingZoneSelector display={currentTab == Tabs.PLAYING_ZONES} />
</div>
</div>
</div>