mirror of
https://git.rezel.net/LudoTech/traque.git
synced 2026-02-09 02:10:18 +01:00
Added placement zones
This commit is contained in:
@@ -41,12 +41,11 @@
|
|||||||
- [x] Refaire les flèches de chasse sur la map
|
- [x] Refaire les flèches de chasse sur la map
|
||||||
- [ ] Mettre en évidence le menu paramètre (configuration)
|
- [ ] Mettre en évidence le menu paramètre (configuration)
|
||||||
- [ ] Afficher un feedback quand un paramètre est sauvegardé
|
- [ ] Afficher un feedback quand un paramètre est sauvegardé
|
||||||
- [ ] (IMPORTANT) Pouvoir définir la zone de départ de chaque équipe
|
- [x] Pouvoir définir la zone de départ de chaque équipe
|
||||||
- [ ] (IMPORTANT) Nommer les polygons par des lettres de l'alphabet
|
- [x] Nommer les polygons par des lettres de l'alphabet
|
||||||
- [ ] Faire un menu quand on arrive sur la traque
|
- [ ] Faire un menu quand on arrive sur la traque
|
||||||
- [ ] Pouvoir load des paramètres enregistrés
|
- [ ] Pouvoir load des paramètres enregistrés
|
||||||
- [ ] (IMPORTANT) Améliorer le système de création zone (cercle et polygone)
|
- [ ] Améliorer le système de création zone (cercle et polygone)
|
||||||
- [ ] (IMPORTANT) Améliorer la sélection du système de zone
|
|
||||||
- [ ] Penser l'affichage en fin de traque
|
- [ ] Penser l'affichage en fin de traque
|
||||||
|
|
||||||
### Améliorations du jeu de la traque
|
### Améliorations du jeu de la traque
|
||||||
|
|||||||
@@ -75,9 +75,14 @@ export function initAdminSocketHandler() {
|
|||||||
game.reorderTeams(newOrder);
|
game.reorderTeams(newOrder);
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("capture_team", (teamId, newTeam) => {
|
socket.on("capture_team", (teamId) => {
|
||||||
if (!loggedIn) return;
|
if (!loggedIn) return;
|
||||||
game.captureTeam(teamId, newTeam);
|
game.captureTeam(teamId);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("placement_team", (teamId, placementZone) => {
|
||||||
|
if (!loggedIn) return;
|
||||||
|
game.placementTeam(teamId, placementZone);
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("change_state", (state) => {
|
socket.on("change_state", (state) => {
|
||||||
|
|||||||
@@ -114,6 +114,7 @@ export default {
|
|||||||
team.finishDate = null;
|
team.finishDate = null;
|
||||||
sendUpdatedTeamInformations(team.id);
|
sendUpdatedTeamInformations(team.id);
|
||||||
}
|
}
|
||||||
|
this.updateChasingChain();
|
||||||
secureAdminBroadcast("teams", this.teams);
|
secureAdminBroadcast("teams", this.teams);
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -317,6 +318,19 @@ export default {
|
|||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
placementTeam(teamId, placementZone) {
|
||||||
|
// Test of parameters
|
||||||
|
if (!this.hasTeam(teamId)) return false;
|
||||||
|
// Variables
|
||||||
|
const team = this.getTeam(teamId);
|
||||||
|
// Make the capture
|
||||||
|
team.startingArea = placementZone;
|
||||||
|
// Broadcast new infos
|
||||||
|
secureAdminBroadcast("teams", this.teams);
|
||||||
|
sendUpdatedTeamInformations(team.id);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
reorderTeams(newOrder) {
|
reorderTeams(newOrder) {
|
||||||
// Update teams
|
// Update teams
|
||||||
const teamMap = new Map(this.teams.map(team => [team.id, team]));
|
const teamMap = new Map(this.teams.map(team => [team.id, team]));
|
||||||
|
|||||||
@@ -68,6 +68,11 @@ export default function LiveMap({ selectedTeamId, onSelected, isFocusing, setIsF
|
|||||||
{isFocusing && <MapPan center={getTeam(selectedTeamId)?.currentLocation} zoom={mapZooms.high} animate />}
|
{isFocusing && <MapPan center={getTeam(selectedTeamId)?.currentLocation} zoom={mapZooms.high} animate />}
|
||||||
<MapEventListener onDragStart={() => setIsFocusing(false)}/>
|
<MapEventListener onDragStart={() => setIsFocusing(false)}/>
|
||||||
<Zones/>
|
<Zones/>
|
||||||
|
{teams.map((team) => gameState == GameState.PLACEMENT && team.startingArea &&
|
||||||
|
<Circle key={team.id} center={team.startingArea.center} radius={team.startingArea.radius} color="blue" fillColor="blue">
|
||||||
|
<Tooltip permanent direction="top" offset={[0.5, -15]} className="custom-tooltip">{team.name}</Tooltip>
|
||||||
|
</Circle>
|
||||||
|
)}
|
||||||
{teams.map((team) => team.currentLocation && !team.captured && <>
|
{teams.map((team) => team.currentLocation && !team.captured && <>
|
||||||
<Marker key={team.id} position={team.currentLocation} icon={positionIcon} eventHandlers={{click: () => onSelected(team.id)}}>
|
<Marker key={team.id} position={team.currentLocation} icon={positionIcon} eventHandlers={{click: () => onSelected(team.id)}}>
|
||||||
{showNames && <Tooltip permanent direction="top" offset={[0.5, -15]} className="custom-tooltip">{team.name}</Tooltip>}
|
{showNames && <Tooltip permanent direction="top" offset={[0.5, -15]} className="custom-tooltip">{team.name}</Tooltip>}
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ import { List } from '@/components/list';
|
|||||||
import useAdmin from '@/hook/useAdmin';
|
import useAdmin from '@/hook/useAdmin';
|
||||||
import { getStatus } from '@/util/functions';
|
import { getStatus } from '@/util/functions';
|
||||||
|
|
||||||
function TeamViewerItem({ team, itemSelected, onSelected }) {
|
function TeamViewerItem({ team }) {
|
||||||
const { gameState } = useAdmin();
|
const { gameState } = useAdmin();
|
||||||
const status = getStatus(team, gameState);
|
const status = getStatus(team, gameState);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'w-full flex flex-row gap-3 p-2 bg-white justify-between cursor-pointer ' + (itemSelected ? "outline outline-4 outline-black" : "")} onClick={() => onSelected(team.id)}>
|
<div className={'w-full flex flex-row gap-3 p-2 bg-white justify-between'}>
|
||||||
<div className='flex flex-row items-center gap-3'>
|
<div className='flex flex-row items-center gap-3'>
|
||||||
<div className='flex flex-row gap-1'>
|
<div className='flex flex-row gap-1'>
|
||||||
<img src={`/icons/user/${team.sockets.length > 0 ? "green" : "red"}.png`} className="w-4 h-4" />
|
<img src={`/icons/user/${team.sockets.length > 0 ? "green" : "red"}.png`} className="w-4 h-4" />
|
||||||
@@ -27,9 +27,9 @@ export default function TeamViewer({selectedTeamId, onSelected}) {
|
|||||||
const { teams } = useAdmin();
|
const { teams } = useAdmin();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<List array={teams}>
|
<List array={teams} selectedId={selectedTeamId} onSelected={onSelected} >
|
||||||
{(team) => (
|
{(team) => (
|
||||||
<TeamViewerItem team={team} itemSelected={selectedTeamId === team.id} onSelected={onSelected}/>
|
<TeamViewerItem team={team}/>
|
||||||
)}
|
)}
|
||||||
</List>
|
</List>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,8 +5,10 @@ import { CustomButton } from "@/components/button";
|
|||||||
import { CustomMapContainer, MapEventListener } from "@/components/map";
|
import { CustomMapContainer, MapEventListener } from "@/components/map";
|
||||||
import { TextInput } from "@/components/input";
|
import { TextInput } from "@/components/input";
|
||||||
import useAdmin from "@/hook/useAdmin";
|
import useAdmin from "@/hook/useAdmin";
|
||||||
import useMapCircleDraw from "@/hook/useMapCircleDraw";
|
import useMapCircleDraw from "@/hook/useCircleDraw";
|
||||||
import useLocalVariable from "@/hook/useLocalVariable";
|
import useLocalVariable from "@/hook/useLocalVariable";
|
||||||
|
import { defaultZoneSettings } from "@/util/configurations";
|
||||||
|
import { ZoneTypes } from "@/util/types";
|
||||||
|
|
||||||
const EditMode = {
|
const EditMode = {
|
||||||
MIN: 0,
|
MIN: 0,
|
||||||
@@ -28,17 +30,40 @@ function Drawings({ minZone, setMinZone, maxZone, setMaxZone, editMode }) {
|
|||||||
</>);
|
</>);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function CircleZoneSelector({zoneSettings, modifyZoneSettings, applyZoneSettings}) {
|
export default function CircleZoneSelector() {
|
||||||
const {outOfZoneDelay, updateSettings} = useAdmin();
|
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 [localOutOfZoneDelay, setLocalOutOfZoneDelay, applyLocalOutOfZoneDelay] = useLocalVariable(outOfZoneDelay, (e) => updateSettings({outOfZoneDelay: e}));
|
||||||
const [editMode, setEditMode] = useState(EditMode.MIN);
|
const [editMode, setEditMode] = useState(EditMode.MIN);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setEditMode(editMode == EditMode.MIN ? EditMode.MAX : EditMode.MIN);
|
if (localZoneSettings.type != ZoneTypes.CIRCLE) {
|
||||||
}, [zoneSettings.min, zoneSettings.max]);
|
setLocalZoneSettings(defaultZoneSettings.circle);
|
||||||
|
}
|
||||||
|
}, [localZoneSettings]);
|
||||||
|
|
||||||
function handleSettingsSubmit() {
|
useEffect(() => {
|
||||||
applyZoneSettings();
|
setEditMode(editMode == EditMode.MIN ? EditMode.MAX : EditMode.MIN);
|
||||||
|
}, [localZoneSettings.min, localZoneSettings.max]);
|
||||||
|
|
||||||
|
function setMinZone(minZone) {
|
||||||
|
setLocalZoneSettings({...localZoneSettings, min: minZone});
|
||||||
|
}
|
||||||
|
|
||||||
|
function setMaxZone(maxZone) {
|
||||||
|
setLocalZoneSettings({...localZoneSettings, max: maxZone});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateReductionCount(reductionCount) {
|
||||||
|
setLocalZoneSettings({...localZoneSettings, reductionCount: reductionCount});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateDuration(duration) {
|
||||||
|
setLocalZoneSettings({...localZoneSettings, duration: duration});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSubmit() {
|
||||||
|
applyLocalZoneSettings();
|
||||||
applyLocalOutOfZoneDelay();
|
applyLocalOutOfZoneDelay();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,38 +73,40 @@ export default function CircleZoneSelector({zoneSettings, modifyZoneSettings, ap
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='h-full w-full gap-3 flex flex-row'>
|
<div className='h-full w-full gap-3 flex flex-row'>
|
||||||
<div className="h-full flex-1">
|
{localZoneSettings.type == ZoneTypes.CIRCLE && <>
|
||||||
<CustomMapContainer>
|
<div className="h-full flex-1">
|
||||||
<Drawings minZone={zoneSettings.min} setMinZone={(e) => modifyZoneSettings("min", e)} maxZone={zoneSettings.max} setMaxZone={(e) => modifyZoneSettings("max", e)} editMode={editMode} />
|
<CustomMapContainer>
|
||||||
</CustomMapContainer>
|
<Drawings minZone={localZoneSettings.min} setMinZone={setMinZone} maxZone={localZoneSettings.max} setMaxZone={setMaxZone} editMode={editMode} />
|
||||||
</div>
|
</CustomMapContainer>
|
||||||
<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>
|
</div>
|
||||||
<div className="w-full flex flex-row gap-2 items-center justify-between">
|
<div className="h-full w-1/6 flex flex-col gap-3">
|
||||||
<p>Reduction number</p>
|
<div className="w-full h-15">
|
||||||
<div className="w-16 h-10">
|
{editMode == EditMode.MIN && <CustomButton color="blue" onClick={() => setEditMode(EditMode.MAX)}>Click to edit first zone</CustomButton>}
|
||||||
<TextInput id="reduction-number" value={zoneSettings?.reductionCount ?? ""} onChange={(e) => modifyZoneSettings("reductionCount", customStringToInt(e.target.value))} />
|
{editMode == EditMode.MAX && <CustomButton color="red" onClick={() => setEditMode(EditMode.MIN)}>Click to edit last zone</CustomButton>}
|
||||||
|
</div>
|
||||||
|
<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>
|
||||||
|
</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>
|
||||||
|
</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>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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={zoneSettings?.duration ?? ""} onChange={(e) => modifyZoneSettings("duration", customStringToInt(e.target.value))} />
|
|
||||||
</div>
|
|
||||||
</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={handleSettingsSubmit}>Apply</CustomButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
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";
|
||||||
|
|
||||||
|
function Drawings({ placementZones, addZone, removeZone, handleRightClick }) {
|
||||||
|
const { handleLeftClick, handleRightClick: handleRightClickDrawing } = useMultipleCircleDraw(placementZones, addZone, removeZone, 30);
|
||||||
|
|
||||||
|
function modifiedHandleRightClick(e) {
|
||||||
|
handleRightClickDrawing(e);
|
||||||
|
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>
|
||||||
|
)}
|
||||||
|
</>);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function PlacementZoneSelector() {
|
||||||
|
const { teams, getTeam, placementTeam } = useAdmin();
|
||||||
|
const [selectedTeamId, setSelectedTeamId] = useState(null);
|
||||||
|
const [placementZones, setPlacementZones] = useState([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setPlacementZones(teams.filter(team => team.startingArea).map(team => ({id: team.id, name: team.name, center: team.startingArea.center, radius: team.startingArea.radius})));
|
||||||
|
}, [teams]);
|
||||||
|
|
||||||
|
function addPlacementZone(center, radius) {
|
||||||
|
if (!selectedTeamId) return;
|
||||||
|
const placementZonesFiltered = placementZones.filter(placementZone => placementZone.id !== selectedTeamId);
|
||||||
|
const newZone = {id: selectedTeamId, name: getTeam(selectedTeamId).name, center: center, radius: radius};
|
||||||
|
placementTeam(selectedTeamId, newZone);
|
||||||
|
setPlacementZones([...placementZonesFiltered, newZone]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function removePlacementZone(id) {
|
||||||
|
placementTeam(id, null);
|
||||||
|
setPlacementZones(placementZones.filter((placementZone) => placementZone.id !== id));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='w-full h-full gap-3 flex flex-row'>
|
||||||
|
<div className="h-full flex-1">
|
||||||
|
<CustomMapContainer>
|
||||||
|
<Drawings placementZones={placementZones} addZone={addPlacementZone} removeZone={removePlacementZone} handleRightClick={() => setSelectedTeamId(null)} />
|
||||||
|
</CustomMapContainer>
|
||||||
|
</div>
|
||||||
|
<div className="h-full w-1/6 flex flex-col gap-3">
|
||||||
|
<div className="w-full text-center">
|
||||||
|
<h2 className="text-xl">Teams</h2>
|
||||||
|
</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">
|
||||||
|
<p className='text-xl font-bold'>{team.name}</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</List>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
|
import { ZoneTypes } from "@/util/types";
|
||||||
|
|
||||||
|
// Imported at runtime and not at compile time
|
||||||
|
const CircleZoneSelector = dynamic(() => import('./circleZoneSelector'), { ssr: false });
|
||||||
|
const PolygonZoneSelector = dynamic(() => import('./polygonZoneSelector'), { ssr: false });
|
||||||
|
|
||||||
|
function ZoneTypeButton({title, onClick, isSelected}) {
|
||||||
|
const grayStyle = "bg-gray-300 hover:bg-gray-400";
|
||||||
|
const blueStyle = "bg-custom-light-blue";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`flex justify-center items-center rounded-md cursor-pointer ${isSelected ? blueStyle : grayStyle}`} onClick={onClick}>
|
||||||
|
<p className="text-l font-bold p-2">{title}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function PlayingZoneSelector() {
|
||||||
|
const [zoneType, setZoneType] = useState(ZoneTypes.POLYGON);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-full h-full flex flex-col gap-3">
|
||||||
|
<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/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -7,15 +7,24 @@ import { CustomMapContainer, MapEventListener } from "@/components/map";
|
|||||||
import { TextInput } from "@/components/input";
|
import { TextInput } from "@/components/input";
|
||||||
import { Node, LabeledPolygon } from "@/components/layer";
|
import { Node, LabeledPolygon } from "@/components/layer";
|
||||||
import useAdmin from "@/hook/useAdmin";
|
import useAdmin from "@/hook/useAdmin";
|
||||||
import useMapPolygonDraw from "@/hook/useMapPolygonDraw";
|
import useMapPolygonDraw from "@/hook/usePolygonDraw";
|
||||||
import useLocalVariable from "@/hook/useLocalVariable";
|
import useLocalVariable from "@/hook/useLocalVariable";
|
||||||
|
import { defaultZoneSettings } from "@/util/configurations";
|
||||||
|
import { ZoneTypes } from "@/util/types";
|
||||||
|
|
||||||
function Drawings({ polygons, addPolygon, removePolygon }) {
|
function Drawings({ localZoneSettings, addZone, removeZone }) {
|
||||||
const { currentPolygon, highlightNodes, handleLeftClick, handleRightClick, handleMouseMove } = useMapPolygonDraw(polygons, addPolygon, removePolygon);
|
const [polygons, setPolygons] = useState([]);
|
||||||
|
const { currentPolygon, highlightNodes, handleLeftClick, handleRightClick, handleMouseMove } = useMapPolygonDraw(polygons, addZone, removeZone);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (localZoneSettings.type == ZoneTypes.POLYGON) {
|
||||||
|
setPolygons(localZoneSettings.polygons.map(zone => zone.polygon));
|
||||||
|
}
|
||||||
|
}, [localZoneSettings]);
|
||||||
|
|
||||||
return (<>
|
return (<>
|
||||||
<MapEventListener onLeftClick={handleLeftClick} onRightClick={handleRightClick} onMouseMove={handleMouseMove} />
|
<MapEventListener onLeftClick={handleLeftClick} onRightClick={handleRightClick} onMouseMove={handleMouseMove} />
|
||||||
{polygons.map((polygon, i) => <LabeledPolygon key={i} polygon={polygon} number={i+1} />)}
|
{localZoneSettings.polygons.map((zone, i) => <LabeledPolygon key={i} polygon={zone.polygon} label={zone.id} />)}
|
||||||
{ currentPolygon.length > 0 && <>
|
{ currentPolygon.length > 0 && <>
|
||||||
<Node pos={currentPolygon[0]} color={"red"} />
|
<Node pos={currentPolygon[0]} color={"red"} />
|
||||||
<Polyline positions={currentPolygon} pathOptions={{ color: "red", weight: 3 }} />
|
<Polyline positions={currentPolygon} pathOptions={{ color: "red", weight: 3 }} />
|
||||||
@@ -24,73 +33,87 @@ function Drawings({ polygons, addPolygon, removePolygon }) {
|
|||||||
</>);
|
</>);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function PolygonZoneSelector({zoneSettings, modifyZoneSettings, applyZoneSettings}) {
|
export default function PolygonZoneSelector() {
|
||||||
const defaultDuration = 10;
|
const defaultDuration = 10;
|
||||||
const [polygons, setPolygons] = useState([]);
|
const {zoneSettings, outOfZoneDelay, updateSettings} = useAdmin();
|
||||||
const {outOfZoneDelay, updateSettings} = useAdmin();
|
const [localZoneSettings, setLocalZoneSettings, applyLocalZoneSettings] = useLocalVariable(zoneSettings, (e) => updateSettings({zone: e}));
|
||||||
const [localOutOfZoneDelay, setLocalOutOfZoneDelay, applyLocalOutOfZoneDelay] = useLocalVariable(outOfZoneDelay, (e) => updateSettings({outOfZoneDelay: e}));
|
const [localOutOfZoneDelay, setLocalOutOfZoneDelay, applyLocalOutOfZoneDelay] = useLocalVariable(outOfZoneDelay, (e) => updateSettings({outOfZoneDelay: e}));
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (zoneSettings) {
|
if (localZoneSettings.type != ZoneTypes.POLYGON) {
|
||||||
setPolygons(zoneSettings.polygons.map((zone) => zone.polygon));
|
setLocalZoneSettings(defaultZoneSettings.polygon);
|
||||||
}
|
}
|
||||||
}, [zoneSettings]);
|
}, [localZoneSettings]);
|
||||||
|
|
||||||
function idFromPolygon(polygon) {
|
function getNewPolygonName() {
|
||||||
return (polygon[0].lat + polygon[1].lat + polygon[2].lat).toString() + (polygon[0].lng + polygon[1].lng + polygon[2].lng).toString();
|
const existingIds = new Set(localZoneSettings.polygons.map(zone => zone.id));
|
||||||
|
for (let i = 0; i < 26; i++) {
|
||||||
|
const letter = String.fromCharCode(65 + i);
|
||||||
|
if (!existingIds.has(letter)) {
|
||||||
|
return letter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "XXX";
|
||||||
}
|
}
|
||||||
|
|
||||||
function addPolygon(polygon) {
|
function setLocalZoneSettingsPolygons(polygons) {
|
||||||
const newPolygons = [...zoneSettings.polygons, {id: idFromPolygon(polygon), polygon: polygon, duration: defaultDuration}];
|
setLocalZoneSettings({type: localZoneSettings.type, polygons: polygons});
|
||||||
modifyZoneSettings("polygons", newPolygons);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function removePolygon(i) {
|
function addZone(polygon) {
|
||||||
const newPolygons = zoneSettings.polygons.filter((_, index) => index !== i);
|
setLocalZoneSettingsPolygons([...localZoneSettings.polygons, {id: getNewPolygonName(), polygon: polygon, duration: defaultDuration}]);
|
||||||
modifyZoneSettings("polygons", newPolygons);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateDuration(i, duration) {
|
function removeZone(i) {
|
||||||
const newPolygons = zoneSettings.polygons.map((zone, index) => index === i ? {id: zone.id, polygon: zone.polygon, duration: duration} : zone);
|
setLocalZoneSettingsPolygons(localZoneSettings.polygons.filter((_, index) => index !== i));
|
||||||
modifyZoneSettings("polygons", newPolygons);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSettingsSubmit() {
|
function updateDuration(id, duration) {
|
||||||
applyZoneSettings();
|
setLocalZoneSettingsPolygons(localZoneSettings.polygons.map(zone => zone.id === id ? {...zone, duration: duration} : zone));
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSubmit() {
|
||||||
|
applyLocalZoneSettings();
|
||||||
applyLocalOutOfZoneDelay();
|
applyLocalOutOfZoneDelay();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function customStringToInt(e) {
|
||||||
|
return parseInt(e, 10) || null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='h-full w-full gap-3 flex flex-row'>
|
<div className='h-full w-full gap-3 flex flex-row'>
|
||||||
<div className="h-full flex-1">
|
{localZoneSettings.type == ZoneTypes.POLYGON && <>
|
||||||
<CustomMapContainer>
|
<div className="h-full flex-1">
|
||||||
<Drawings polygons={polygons} addPolygon={addPolygon} removePolygon={removePolygon} />
|
<CustomMapContainer>
|
||||||
</CustomMapContainer>
|
<Drawings localZoneSettings={localZoneSettings} addZone={addZone} removeZone={removeZone} />
|
||||||
</div>
|
</CustomMapContainer>
|
||||||
<div className="h-full w-1/6 flex flex-col gap-3">
|
|
||||||
<div className="w-full text-center">
|
|
||||||
<h2 className="text-xl">Reduction order</h2>
|
|
||||||
</div>
|
</div>
|
||||||
<ReorderList droppableId="zones-order" array={zoneSettings.polygons} setArray={(polygons) => modifyZoneSettings("polygons", polygons)}>
|
<div className="h-full w-1/6 flex flex-col gap-3">
|
||||||
{ (zone, i) =>
|
<div className="w-full text-center">
|
||||||
<div className="w-full p-2 bg-white flex flex-row gap-2 items-center justify-between">
|
<h2 className="text-xl">Reduction order</h2>
|
||||||
<p>Zone {i+1}</p>
|
</div>
|
||||||
<div className="w-16 h-10">
|
<ReorderList droppableId="zones-order" array={localZoneSettings.polygons} setArray={setLocalZoneSettingsPolygons}>
|
||||||
<TextInput value={zone?.duration || ""} onChange={(e) => updateDuration(i, parseInt(e.target.value, 10))}/>
|
{ (zone) =>
|
||||||
|
<div 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>
|
||||||
</div>
|
</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>
|
||||||
</ReorderList>
|
<div className="w-full h-15">
|
||||||
<div className="w-full flex flex-row gap-2 items-center justify-between">
|
<CustomButton color="green" onClick={handleSubmit}>Apply</CustomButton>
|
||||||
<p>Timeout</p>
|
|
||||||
<div className="w-16 h-10">
|
|
||||||
<TextInput id="timeout" value={localOutOfZoneDelay ?? ""} onChange={(e) => setLocalOutOfZoneDelay(parseInt(e.target.value, 10))} />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full h-15">
|
</>}
|
||||||
<CustomButton color="green" onClick={handleSettingsSubmit}>Apply</CustomButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +1,61 @@
|
|||||||
|
import { useState } from "react";
|
||||||
import { ReorderList } from '@/components/list';
|
import { ReorderList } from '@/components/list';
|
||||||
import useAdmin from '@/hook/useAdmin';
|
import useAdmin from '@/hook/useAdmin';
|
||||||
|
import useLocalVariable from "@/hook/useLocalVariable";
|
||||||
|
import { TextInput } from "@/components/input";
|
||||||
|
import { Section } from "@/components/section";
|
||||||
|
|
||||||
function TeamManagerItem({ team }) {
|
function TeamManagerItem({ team }) {
|
||||||
const { captureTeam, removeTeam } = useAdmin();
|
const { captureTeam, removeTeam } = useAdmin();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='w-full p-2 bg-white flex flex-row items-center text-xl gap-3 font-bold'>
|
<div className='w-full flex flex-row items-center justify-between p-2 gap-3 bg-white'>
|
||||||
<div className='flex-1 w-full h-full flex flex-row items-center justify-between'>
|
<p className='text-xl font-bold'>{team.name}</p>
|
||||||
<p>{team.name}</p>
|
<div className='flex flex-row items-center justify-between gap-3'>
|
||||||
<div className='flex flex-row items-center justify-between gap-3'>
|
<p className='text-xl font-bold'>{String(team.id).padStart(6, '0').replace(/(\d{3})(\d{3})/, "$1 $2")}</p>
|
||||||
<p>{String(team.id).padStart(6, '0').replace(/(\d{3})(\d{3})/, "$1 $2")}</p>
|
<img src={`/icons/heart/${team.captured ? "grey" : "pink"}.png`} className="w-8 h-8 cursor-pointer" onClick={() => captureTeam(team.id)} />
|
||||||
<img src={`/icons/heart/${team.captured ? "grey" : "pink"}.png`} className="w-8 h-8" onClick={() => captureTeam(team.id)} />
|
<img src="/icons/trash.png" className="w-8 h-8 cursor-pointer" onClick={() => removeTeam(team.id)} />
|
||||||
<img src="/icons/trash.png" className="w-8 h-8" onClick={() => removeTeam(team.id)} />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function TeamManager() {
|
export default function TeamManager() {
|
||||||
const { teams, reorderTeams } = useAdmin();
|
const { teams, addTeam, reorderTeams, sendPositionDelay, updateSettings } = useAdmin();
|
||||||
|
const [teamName, setTeamName] = useState('');
|
||||||
|
const [localSendPositionDelay, setLocalSendPositionDelay, applyLocalSendPositionDelay] = useLocalVariable(sendPositionDelay, (e) => updateSettings({sendPositionDelay: e}));
|
||||||
|
|
||||||
|
function handleTeamSubmit(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (teamName !== "") {
|
||||||
|
addTeam(teamName);
|
||||||
|
setTeamName("")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReorderList droppableId="team-manager" array={teams} setArray={(teams) => reorderTeams(teams.map(team => team.id))}>
|
<Section title="Équipe" outerClassName="flex-1 min-h-0" innerClassName="flex flex-col items-center gap-3">
|
||||||
{(team) => (
|
<form className='w-full flex flex-row gap-3' onSubmit={handleTeamSubmit}>
|
||||||
<TeamManagerItem team={team}/>
|
<div className='w-full'>
|
||||||
)}
|
<input name="teamName" label='Team name' value={teamName} onChange={(e) => setTeamName(e.target.value)} type="text" className="w-full h-full p-4 ring-1 ring-inset ring-gray-300" />
|
||||||
</ReorderList>
|
</div>
|
||||||
|
<div className='w-1/5'>
|
||||||
|
<button type="submit" className="w-full h-full bg-custom-light-blue hover:bg-blue-500 transition text-3xl font-bold">+</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<div className="w-full flex-1 min-h-0 ">
|
||||||
|
<ReorderList droppableId="team-manager" array={teams} setArray={(teams) => reorderTeams(teams.map(team => team.id))}>
|
||||||
|
{(team) => (
|
||||||
|
<TeamManagerItem team={team}/>
|
||||||
|
)}
|
||||||
|
</ReorderList>
|
||||||
|
</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>
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,87 +2,66 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { TextInput } from "@/components/input";
|
|
||||||
import { Section } from "@/components/section";
|
|
||||||
import { CustomButton } from "@/components/button";
|
|
||||||
import { useAdminConnexion } from "@/context/adminConnexionContext";
|
import { useAdminConnexion } from "@/context/adminConnexionContext";
|
||||||
import useAdmin from '@/hook/useAdmin';
|
|
||||||
import Messages from "./components/messages";
|
import Messages from "./components/messages";
|
||||||
import TeamManager from './components/teamManager';
|
import TeamManager from './components/teamManager';
|
||||||
import useLocalVariable from "@/hook/useLocalVariable";
|
import PlayingZoneSelector from "./components/playingZoneSelector";
|
||||||
import { ZoneTypes } from "@/util/types";
|
|
||||||
import { defaultZoneSettings } from "@/util/configurations";
|
|
||||||
|
|
||||||
// Imported at runtime and not at compile time
|
// Imported at runtime and not at compile time
|
||||||
const CircleZoneSelector = dynamic(() => import('./components/circleZoneSelector'), { ssr: false });
|
const PlacementZoneSelector = dynamic(() => import('./components/placementZoneSelector'), { ssr: false });
|
||||||
const PolygonZoneSelector = dynamic(() => import('./components/polygonZoneSelector'), { ssr: false });
|
|
||||||
|
const Tabs = {
|
||||||
|
PLACEMENT_ZONES: "placement_zones",
|
||||||
|
PLAYING_ZONES: "playing_zones",
|
||||||
|
}
|
||||||
|
|
||||||
|
function ParametersTitle() {
|
||||||
|
return (
|
||||||
|
<div className='w-full bg-custom-light-blue gap-5 p-5 flex flex-row shadow-2xl'>
|
||||||
|
<Link href="/admin">
|
||||||
|
<img src="/icons/backarrow.png" className="w-8 h-8" title="Main page" />
|
||||||
|
</Link>
|
||||||
|
<h2 className="text-3xl font-bold">Paramètres</h2>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function TabButton({title, onClick, isSelected}) {
|
||||||
|
const grayStyle = "bg-gray-300 hover:bg-gray-400";
|
||||||
|
const blueStyle = "bg-custom-light-blue";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`flex-1 h-full flex justify-center items-center cursor-pointer ${isSelected ? blueStyle : grayStyle}`} onClick={onClick}>
|
||||||
|
<p className="text-2xl font-bold">{title}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default function ConfigurationPage() {
|
export default function ConfigurationPage() {
|
||||||
const { useProtect } = useAdminConnexion();
|
const { useProtect } = useAdminConnexion();
|
||||||
const {zoneSettings, sendPositionDelay, updateSettings, addTeam} = useAdmin();
|
const [currentTab, setCurrentTab] = useState(Tabs.PLACEMENT_ZONES);
|
||||||
const [teamName, setTeamName] = useState('');
|
|
||||||
const [localZoneSettings, setLocalZoneSettings, applyLocalZoneSettings] = useLocalVariable(zoneSettings, (e) => updateSettings({zone: e}));
|
|
||||||
const [localSendPositionDelay, setLocalSendPositionDelay, applyLocalSendPositionDelay] = useLocalVariable(sendPositionDelay, (e) => updateSettings({sendPositionDelay: e}));
|
|
||||||
|
|
||||||
useProtect();
|
useProtect();
|
||||||
|
|
||||||
function modifyLocalZoneSettings(key, value) {
|
|
||||||
setLocalZoneSettings(prev => ({...prev, [key]: value}));
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleChangeZoneType() {
|
|
||||||
setLocalZoneSettings(localZoneSettings.type == ZoneTypes.CIRCLE ? defaultZoneSettings.polygon : defaultZoneSettings.circle)
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleTeamSubmit(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
if (teamName !== "") {
|
|
||||||
addTeam(teamName);
|
|
||||||
setTeamName("")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='h-full bg-gray-200 p-3 flex flex-row gap-3'>
|
<div className='h-full bg-gray-200 p-3 flex flex-row gap-3'>
|
||||||
<div className="h-full w-2/6 gap-3 flex flex-col">
|
<div className="h-full w-2/6 gap-3 flex flex-col">
|
||||||
<div className='w-full bg-custom-light-blue gap-5 p-5 flex flex-row shadow-2xl'>
|
<ParametersTitle/>
|
||||||
<Link href="/admin">
|
|
||||||
<img src="/icons/backarrow.png" className="w-8 h-8" title="Main page" />
|
|
||||||
</Link>
|
|
||||||
<h2 className="text-3xl font-bold">Paramètres</h2>
|
|
||||||
</div>
|
|
||||||
<Messages/>
|
<Messages/>
|
||||||
<Section title="Équipe" outerClassName="flex-1 min-h-0" innerClassName="flex flex-col items-center gap-3">
|
<TeamManager/>
|
||||||
<form className='w-full flex flex-row gap-3' onSubmit={handleTeamSubmit}>
|
|
||||||
<div className='w-full'>
|
|
||||||
<input name="teamName" label='Team name' value={teamName} onChange={(e) => setTeamName(e.target.value)} type="text" className="w-full h-full p-4 ring-1 ring-inset ring-gray-300" />
|
|
||||||
</div>
|
|
||||||
<div className='w-1/5'>
|
|
||||||
<button type="submit" className="w-full h-full bg-custom-light-blue hover:bg-blue-500 transition text-3xl font-bold">+</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
<div className="w-full flex-1 min-h-0 ">
|
|
||||||
<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>
|
|
||||||
</div>
|
|
||||||
</Section>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="h-full flex-1 flex flex-col p-3 gap-3 bg-white shadow-2xl">
|
<div className="h-full flex-1 flex flex-col bg-white shadow-2xl">
|
||||||
<div className="w-full h-15">
|
<div className="w-full h-20 flex flex-row bg-gray-300">
|
||||||
{localZoneSettings && <CustomButton color="blue" onClick={handleChangeZoneType}>Change zone type</CustomButton>}
|
<TabButton title="Zones de placement" onClick={() => setCurrentTab(Tabs.PLACEMENT_ZONES)} isSelected={currentTab == Tabs.PLACEMENT_ZONES}/>
|
||||||
|
<TabButton title="Zones de jeu" onClick={() => setCurrentTab(Tabs.PLAYING_ZONES)} isSelected={currentTab == Tabs.PLAYING_ZONES}/>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full flex-1">
|
<div className="w-full flex-1 p-3 bg-white">
|
||||||
{localZoneSettings && localZoneSettings.type == ZoneTypes.CIRCLE &&
|
{ currentTab == Tabs.PLAYING_ZONES &&
|
||||||
<CircleZoneSelector zoneSettings={localZoneSettings} modifyZoneSettings={modifyLocalZoneSettings} applyZoneSettings={applyLocalZoneSettings}/>
|
<PlayingZoneSelector />
|
||||||
}
|
}
|
||||||
{localZoneSettings && localZoneSettings.type == ZoneTypes.POLYGON &&
|
{ currentTab == Tabs.PLACEMENT_ZONES &&
|
||||||
<PolygonZoneSelector zoneSettings={localZoneSettings} modifyZoneSettings={modifyLocalZoneSettings} applyZoneSettings={applyLocalZoneSettings}/>
|
<PlacementZoneSelector />
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export function Node({pos, nodeSize = 5, color = 'black'}) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function LabeledPolygon({polygon, number, color = 'black', opacity = '0.5', border = 3, iconSize = 24, iconColor = 'white'}) {
|
export function LabeledPolygon({polygon, label, color = 'black', opacity = '0.5', border = 3, iconSize = 24, iconColor = 'white'}) {
|
||||||
const length = polygon.length;
|
const length = polygon.length;
|
||||||
|
|
||||||
if (length < 3) return null;
|
if (length < 3) return null;
|
||||||
@@ -26,7 +26,7 @@ export function LabeledPolygon({polygon, number, color = 'black', opacity = '0.5
|
|||||||
// Idea : take the mean point of the largest connected subpolygon
|
// Idea : take the mean point of the largest connected subpolygon
|
||||||
const meanPoint = {lat: sum.lat / length, lng: sum.lng / length}
|
const meanPoint = {lat: sum.lat / length, lng: sum.lng / length}
|
||||||
|
|
||||||
const numberIcon = L.divIcon({
|
const labelIcon = L.divIcon({
|
||||||
html: `<div style="
|
html: `<div style="
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -34,15 +34,15 @@ export function LabeledPolygon({polygon, number, color = 'black', opacity = '0.5
|
|||||||
color: ${iconColor};
|
color: ${iconColor};
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: ${iconSize}px;
|
font-size: ${iconSize}px;
|
||||||
">${number}</div>`,
|
">${label}</div>`,
|
||||||
className: 'custom-number-icon',
|
className: 'custom-label-icon',
|
||||||
iconSize: [iconSize, iconSize],
|
iconSize: [iconSize, iconSize],
|
||||||
iconAnchor: [iconSize / 2, iconSize / 2]
|
iconAnchor: [iconSize / 2, iconSize / 2]
|
||||||
});
|
});
|
||||||
|
|
||||||
return (<>
|
return (<>
|
||||||
<Polygon positions={polygon} pathOptions={{ color: color, fillColor: color, fillOpacity: opacity, weight: border }} />
|
<Polygon positions={polygon} pathOptions={{ color: color, fillColor: color, fillOpacity: opacity, weight: border }} />
|
||||||
<Marker position={meanPoint} icon={numberIcon} />
|
<Marker position={meanPoint} icon={labelIcon} />
|
||||||
</>);
|
</>);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,19 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd';
|
import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd';
|
||||||
|
|
||||||
export function List({array, children}) {
|
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 (
|
return (
|
||||||
<div className='w-full h-full bg-gray-300 overflow-y-scroll'>
|
<div className='w-full h-full bg-gray-300 overflow-y-scroll'>
|
||||||
<ul className="w-full p-1 pb-0">
|
<ul className="w-full p-1 pb-0">
|
||||||
{array.map((elem, i) => (
|
{array.map((elem, i) => (
|
||||||
<li className="w-full" key={elem.id}>
|
<li className="w-full" key={elem.id}>
|
||||||
{children(elem, i)}
|
<div className={"w-full" + cursor() + outline(elem.id)} onClick={() => canSelect && onSelected(elem.id)}>
|
||||||
|
{children(elem, i)}
|
||||||
|
</div>
|
||||||
<div className="w-full h-1"/>
|
<div className="w-full h-1"/>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
@@ -48,7 +54,7 @@ export function ReorderList({droppableId, array, setArray, children}) {
|
|||||||
<li className='w-full' key={elem.id}>
|
<li className='w-full' key={elem.id}>
|
||||||
<Draggable draggableId={elem.id.toString()} index={i}>
|
<Draggable draggableId={elem.id.toString()} index={i}>
|
||||||
{provided => (
|
{provided => (
|
||||||
<div className='w-full' {...provided.draggableProps} {...provided.dragHandleProps} ref={provided.innerRef}>
|
<div className='w-full cursor-grab' {...provided.draggableProps} {...provided.dragHandleProps} ref={provided.innerRef}>
|
||||||
{children(elem, i)}
|
{children(elem, i)}
|
||||||
<div className="w-full h-1"/>
|
<div className="w-full h-1"/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -27,6 +27,10 @@ export default function useAdmin() {
|
|||||||
adminSocket.emit("capture_team", teamId);
|
adminSocket.emit("capture_team", teamId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function placementTeam(teamId, placementZone) {
|
||||||
|
adminSocket.emit("placement_team", teamId, placementZone);
|
||||||
|
}
|
||||||
|
|
||||||
function changeState(state) {
|
function changeState(state) {
|
||||||
adminSocket.emit("change_state", state);
|
adminSocket.emit("change_state", state);
|
||||||
}
|
}
|
||||||
@@ -35,5 +39,5 @@ export default function useAdmin() {
|
|||||||
adminSocket.emit("update_settings", settings);
|
adminSocket.emit("update_settings", settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { ...adminContext, getTeam, reorderTeams, addTeam, removeTeam, captureTeam, changeState, updateSettings };
|
return { ...adminContext, getTeam, reorderTeams, addTeam, removeTeam, captureTeam, placementTeam, changeState, updateSettings };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
export default function useMapCircleDraw(area, setArea) {
|
export default function useMapCircleDraw(circle, setCircle) {
|
||||||
const [drawing, setDrawing] = useState(false);
|
const [drawing, setDrawing] = useState(false);
|
||||||
const [center, setCenter] = useState(area?.center || null);
|
const [center, setCenter] = useState(circle?.center || null);
|
||||||
const [radius, setRadius] = useState(area?.radius || null);
|
const [radius, setRadius] = useState(circle?.radius || null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setDrawing(false);
|
setDrawing(false);
|
||||||
setCenter(area?.center || null);
|
setCenter(circle?.center || null);
|
||||||
setRadius(area?.radius || null);
|
setRadius(circle?.radius || null);
|
||||||
}, [area])
|
}, [circle])
|
||||||
|
|
||||||
function handleLeftClick(e) {
|
function handleLeftClick(e) {
|
||||||
if (!drawing) {
|
if (!drawing) {
|
||||||
@@ -19,17 +19,17 @@ export default function useMapCircleDraw(area, setArea) {
|
|||||||
setDrawing(true);
|
setDrawing(true);
|
||||||
} else {
|
} else {
|
||||||
setDrawing(false);
|
setDrawing(false);
|
||||||
setArea({center, radius});
|
setCircle({center, radius});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleRightClick(e) {
|
function handleRightClick(e) {
|
||||||
if (drawing) {
|
if (drawing) {
|
||||||
setDrawing(false);
|
setDrawing(false);
|
||||||
setCenter(area?.center || null);
|
setCenter(circle?.center || null);
|
||||||
setRadius(area?.radius || null);
|
setRadius(circle?.radius || null);
|
||||||
} else {
|
} else {
|
||||||
setArea(null);
|
setCircle(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
39
traque-front/hook/useMultipleCircleDraw.jsx
Normal file
39
traque-front/hook/useMultipleCircleDraw.jsx
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
export default function useMultipleCircleDraw(circles, addCircle, removeCircle, radius) {
|
||||||
|
|
||||||
|
function getDistanceFromLatLon({ lat: lat1, lng: lon1 }, { lat: lat2, lng: lon2 }) {
|
||||||
|
const degToRad = (deg) => deg * (Math.PI / 180);
|
||||||
|
var R = 6371; // Radius of the earth in km
|
||||||
|
var dLat = degToRad(lat2 - lat1);
|
||||||
|
var dLon = degToRad(lon2 - lon1);
|
||||||
|
var a =
|
||||||
|
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
|
||||||
|
Math.cos(degToRad(lat1)) * Math.cos(degToRad(lat2)) *
|
||||||
|
Math.sin(dLon / 2) * Math.sin(dLon / 2)
|
||||||
|
;
|
||||||
|
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||||||
|
var d = R * c; // Distance in km
|
||||||
|
return d * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isBaddlyPlaced(latlng) {
|
||||||
|
return circles.some(circle => getDistanceFromLatLon(latlng, circle.center) < 2 * circle.radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCircleFromLatlng(latlng) {
|
||||||
|
return circles.find(circle => getDistanceFromLatLon(latlng, circle.center) < circle.radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleLeftClick(e) {
|
||||||
|
if (isBaddlyPlaced(e.latlng)) return;
|
||||||
|
addCircle(e.latlng, radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleRightClick(e) {
|
||||||
|
const circle = getCircleFromLatlng(e.latlng);
|
||||||
|
if (circle) removeCircle(circle.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { handleLeftClick, handleRightClick };
|
||||||
|
}
|
||||||
@@ -94,15 +94,10 @@ export default function useMapPolygonDraw(polygons, addPolygon, removePolygon) {
|
|||||||
}
|
}
|
||||||
return sum > 0;
|
return sum > 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
function getZoneIndex(latlng) {
|
function getPolygonIndex(latlng) {
|
||||||
// Return the index of the polygon where latlng is according to isInPolygon
|
// Return the index of the polygon where latlng is according to isInPolygon
|
||||||
for (let iPolygon = 0; iPolygon < polygons.length; iPolygon++) {
|
return polygons.findIndex(polygon => isInPolygon(latlng, polygon));
|
||||||
if (isInPolygon(latlng, polygons[iPolygon])) {
|
|
||||||
return iPolygon;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getEventLatLng(e) {
|
function getEventLatLng(e) {
|
||||||
@@ -142,7 +137,7 @@ export default function useMapPolygonDraw(polygons, addPolygon, removePolygon) {
|
|||||||
// If it is the first node
|
// If it is the first node
|
||||||
if (!isDrawing()) {
|
if (!isDrawing()) {
|
||||||
// If the point is not in an existing polygon
|
// If the point is not in an existing polygon
|
||||||
if (getZoneIndex(latlng) == -1) {
|
if (getPolygonIndex(latlng) == -1) {
|
||||||
setCurrentPolygon([latlng]);
|
setCurrentPolygon([latlng]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,7 +167,7 @@ export default function useMapPolygonDraw(polygons, addPolygon, removePolygon) {
|
|||||||
// Is the new point making the current polygon intersect with itself ?
|
// Is the new point making the current polygon intersect with itself ?
|
||||||
if (isIntersecting([latlng, currentPolygon[length-1]], currentPolygon, false)) return;
|
if (isIntersecting([latlng, currentPolygon[length-1]], currentPolygon, false)) return;
|
||||||
// Is the new point inside a polygon ?
|
// Is the new point inside a polygon ?
|
||||||
if (getZoneIndex(latlng) != -1) return;
|
if (getPolygonIndex(latlng) != -1) return;
|
||||||
// Is the new point making the current polygon intersect with another polygon ?
|
// Is the new point making the current polygon intersect with another polygon ?
|
||||||
for (const polygon of polygons) {
|
for (const polygon of polygons) {
|
||||||
// Strict intersection
|
// Strict intersection
|
||||||
@@ -200,7 +195,7 @@ export default function useMapPolygonDraw(polygons, addPolygon, removePolygon) {
|
|||||||
setCurrentPolygon([]);
|
setCurrentPolygon([]);
|
||||||
// If not isDrawing, remove the clicked polygon
|
// If not isDrawing, remove the clicked polygon
|
||||||
} else {
|
} else {
|
||||||
const i = getZoneIndex(e.latlng);
|
const i = getPolygonIndex(e.latlng);
|
||||||
if (i != -1) removePolygon(i);
|
if (i != -1) removePolygon(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user