From 7e4d9f910a837bdea67c79ca98089bf71c6caad1 Mon Sep 17 00:00:00 2001 From: Sebastien Riviere Date: Mon, 8 Sep 2025 15:08:44 +0200 Subject: [PATCH] Cleaning --- doc/TODO.md | 13 +-- traque-app/app/display.js | 20 +++-- traque-app/context/teamContext.jsx | 68 +++++++-------- traque-app/hook/useGame.jsx | 6 +- traque-back/admin_socket.js | 4 +- traque-back/game.js | 46 +++++++--- traque-back/team_socket.js | 12 +-- traque-back/zone_manager.js | 17 ++-- traque-front/app/admin/components/buttons.jsx | 15 ---- traque-front/app/admin/components/liveMap.jsx | 50 ++++------- .../app/admin/components/teamSidePanel.jsx | 34 ++++---- .../app/admin/components/teamViewer.jsx | 7 +- .../app/admin/login/components/loginForm.jsx | 21 ----- traque-front/app/admin/login/page.js | 17 +++- traque-front/app/admin/page.js | 37 ++++++-- .../components/circleZoneSelector.jsx | 71 ++++++++------- .../admin/parameters/components/messages.jsx | 2 +- .../components/placementZoneSelector.jsx | 14 +-- .../components/playingZoneSelector.jsx | 12 +-- .../components/polygonZoneSelector.jsx | 57 +++++++----- .../parameters/components/teamManager.jsx | 6 +- traque-front/app/admin/parameters/page.js | 12 +-- traque-front/app/layout.js | 27 +++--- traque-front/components/button.jsx | 16 ---- traque-front/components/input.jsx | 17 ++-- traque-front/components/layer.jsx | 87 ++++++++++++------- traque-front/components/list.jsx | 8 +- traque-front/components/map.jsx | 1 + traque-front/components/section.jsx | 8 +- traque-front/context/adminContext.jsx | 4 +- traque-front/hook/useAdmin.jsx | 8 +- traque-front/hook/useCircleDraw.jsx | 34 +++----- traque-front/hook/useLocation.jsx | 39 --------- traque-front/hook/useMultipleCircleDraw.jsx | 22 +---- traque-front/util/configurations.js | 15 ++-- traque-front/util/functions.js | 3 + traque-front/util/types.js | 8 ++ 37 files changed, 403 insertions(+), 435 deletions(-) delete mode 100644 traque-front/app/admin/components/buttons.jsx delete mode 100644 traque-front/app/admin/login/components/loginForm.jsx delete mode 100644 traque-front/components/button.jsx delete mode 100644 traque-front/hook/useLocation.jsx diff --git a/doc/TODO.md b/doc/TODO.md index 637fc88..fe1f491 100644 --- a/doc/TODO.md +++ b/doc/TODO.md @@ -34,18 +34,19 @@ - [x] Pouvoir changer les paramètres du jeu pendant une partie. - [x] Implémenter les wireframes - [x] Ajouter une région par défaut si pas de position -- [ ] Pouvoir faire pause dans la partie -- [ ] Voir les traces et évènements des teams -- [ ] Voir l'incertitude de position des teams - [x] Focus une team cliquée - [x] Refaire les flèches de chasse sur la map -- [ ] Mettre en évidence le menu paramètre (configuration) -- [ ] Afficher un feedback quand un paramètre est sauvegardé - [x] Pouvoir définir la zone de départ de chaque équipe - [x] Nommer les polygons par des lettres de l'alphabet +- [ ] Plein écran +- [ ] Pouvoir faire pause dans la partie +- [ ] Mettre en évidence le menu paramètre +- [ ] Afficher un feedback quand un paramètre est sauvegardé +- [ ] Améliorer le système de création zone (cercle et polygone) +- [ ] Voir les traces et évènements des teams +- [ ] Voir l'incertitude de position des teams - [ ] Faire un menu quand on arrive sur la traque - [ ] Pouvoir load des paramètres enregistrés -- [ ] Améliorer le système de création zone (cercle et polygone) - [ ] Penser l'affichage en fin de traque ### Améliorations du jeu de la traque diff --git a/traque-app/app/display.js b/traque-app/app/display.js index d3822d3..814e2a4 100644 --- a/traque-app/app/display.js +++ b/traque-app/app/display.js @@ -32,9 +32,9 @@ export default function Display() { const [bottomContainerHeight, setBottomContainerHeight] = useState(0); const router = useRouter(); const {SERVER_URL} = useSocket(); - const {gameSettings, zoneType, zoneExtremities, nextZoneDate, isShrinking, location, startLocationTracking, stopLocationTracking, gameState} = useTeamContext(); + const {messages, zoneType, zoneExtremities, nextZoneDate, isShrinking, location, startLocationTracking, stopLocationTracking, gameState, startDate} = useTeamContext(); const {loggedIn, logout, loading} = useTeamConnexion(); - const {sendCurrentPosition, capture, enemyLocation, enemyName, startingArea, captureCode, name, ready, captured, lastSentLocation, locationSendDeadline, teamId, outOfZone, outOfZoneDeadline, distance, startDate, finishDate, nCaptures, nSentLocation} = useGame(); + const {sendCurrentPosition, capture, enemyLocation, enemyName, startingArea, captureCode, name, ready, captured, lastSentLocation, locationSendDeadline, teamId, outOfZone, outOfZoneDeadline, distance, finishDate, nCaptures, nSentLocation} = useGame(); const [enemyCaptureCode, setEnemyCaptureCode] = useState(""); const [timeLeftSendLocation] = useTimeDifference(locationSendDeadline, 1000); const [timeLeftNextZone] = useTimeDifference(nextZoneDate, 1000); @@ -225,7 +225,7 @@ export default function Display() { const CapturedMessage = () => { return ( - {gameSettings?.capturedMessage || "Vous avez été éliminé..."} + {messages?.captured || "Vous avez été éliminé..."} ); } @@ -233,26 +233,28 @@ export default function Display() { const EndGameMessage = () => { return ( - {captured && {gameSettings?.loserEndGameMessage || "Vous avez perdu..."}} - {!captured && {gameSettings?.winnerEndGameMessage || "Vous avez gagné !"}} + {captured && {messages?.loser || "Vous avez perdu..."}} + {!captured && {messages?.winner || "Vous avez gagné !"}} ); } const Zones = () => { + const latToLatitude = (pos) => ({latitude: pos.lat, longitude: pos.lng}); + switch (zoneType) { case zoneTypes.circle: return ( - { zoneExtremities.begin && } - { zoneExtremities.end && } + { zoneExtremities.begin && } + { zoneExtremities.end && } ); case zoneTypes.polygon: return ( - { zoneExtremities.begin && } - { zoneExtremities.end && } + { zoneExtremities.begin && latToLatitude(pos))} strokeColor="red" fillColor="rgba(255,0,0,0.1)" strokeWidth={2} /> } + { zoneExtremities.end && latToLatitude(pos))} strokeColor="green" fillColor="rgba(0,255,0,0.1)" strokeWidth={2} /> } ); default: diff --git a/traque-app/context/teamContext.jsx b/traque-app/context/teamContext.jsx index d2709f9..a7e60c9 100644 --- a/traque-app/context/teamContext.jsx +++ b/traque-app/context/teamContext.jsx @@ -1,6 +1,6 @@ import { useLocation } from "../hook/useLocation"; import { useSocketListener } from "../hook/useSocketListener"; -import { createContext, useContext, useMemo, useRef, useState } from "react"; +import { createContext, useContext, useMemo, useState } from "react"; import { useSocket } from "./socketContext"; import { GameState } from "../util/gameState"; import useSendDeviceInfo from "../hook/useSendDeviceInfo"; @@ -14,52 +14,52 @@ const zoneTypes = { } function TeamProvider({children}) { - const { logout } = useTeamConnexion(); + const {teamSocket} = useSocket(); + const [location, getLocationAuthorization, startLocationTracking, stopLocationTracking] = useLocation(5000, 10); + // update_team const [teamInfos, setTeamInfos] = useState({}); + // game_state const [gameState, setGameState] = useState(GameState.SETUP); - const [gameSettings, setGameSettings] = useState(null); - const [zoneType, setZoneType] = useState(null); + const [startDate, setStartDate] = useState(null); + // current_zone const [zoneExtremities, setZoneExtremities] = useState(null); const [nextZoneDate, setNextZoneDate] = useState(null); - const [location, getLocationAuthorization, startLocationTracking, stopLocationTracking] = useLocation(5000, 10); - const {teamSocket} = useSocket(); - const teamInfosRef = useRef(); + // settings + const [messages, setMessages] = useState(null); + const [zoneType, setZoneType] = useState(null); + // logout + const { logout } = useTeamConnexion(); useSendDeviceInfo(); - teamInfosRef.current = teamInfos; + useSocketListener(teamSocket, "update_team", (data) => { + setTeamInfos(teamInfos => ({...teamInfos, ...data})) + }); - function setZone(data) { - setZoneType(data.type); - switch (data.type) { - case zoneTypes.circle: - setZoneExtremities({ - begin: {...data.begin, ...{center : {latitude: data.begin.center.lat, longitude: data.begin.center.lng} }}, - end: {...data.end, ...{center : {latitude: data.end.center.lat, longitude: data.end.center.lng} }} - }); - break; - case zoneTypes.polygon: - setZoneExtremities({ - begin: {...data.begin, ...{points : data.begin.points.map( p => ({latitude: p.lat,longitude: p.lng}) )}}, - end: {...data.end, ...{points : data.end.points.map( p => ({latitude: p.lat,longitude: p.lng}) )}} - }); - break; - default: - setZoneExtremities({begin: data.begin, end: data.end}); - break; - } + useSocketListener(teamSocket, "game_state", (data) => { + setGameState(data.state); + setStartDate(data.date); + }); + + useSocketListener(teamSocket, "settings", (data) => { + setMessages(data.messages); + setZoneType(data.zone.type); + //TODO + //setSendPositionDelay(data.sendPositionDelay); + //setOutOfZoneDelay(data.outOfZoneDelay); + }); + + useSocketListener(teamSocket, "current_zone", (data) => { + setZoneExtremities({begin: data.begin, end: data.end}); setNextZoneDate(data.endDate); - } + }); - useSocketListener(teamSocket, "update_team", (newTeamInfos) => {setTeamInfos({...teamInfosRef.current, ...newTeamInfos})}); - useSocketListener(teamSocket, "game_state", setGameState); - useSocketListener(teamSocket, "zone", setZone); - useSocketListener(teamSocket, "game_settings", setGameSettings); useSocketListener(teamSocket, "logout", logout); + const value = useMemo(() => ( - {teamInfos, gameState, zoneType, zoneExtremities, nextZoneDate, gameSettings, location, getLocationAuthorization, startLocationTracking, stopLocationTracking} - ), [teamInfos, gameState, zoneType, zoneExtremities, nextZoneDate, gameSettings, location]); + {teamInfos, gameState, startDate, zoneType, zoneExtremities, nextZoneDate, messages, location, getLocationAuthorization, startLocationTracking, stopLocationTracking} + ), [teamInfos, gameState, startDate, zoneType, zoneExtremities, nextZoneDate, messages, location]); return ( diff --git a/traque-app/hook/useGame.jsx b/traque-app/hook/useGame.jsx index d681904..ec1f07f 100644 --- a/traque-app/hook/useGame.jsx +++ b/traque-app/hook/useGame.jsx @@ -5,8 +5,8 @@ import { useTeamContext } from "../context/teamContext"; export default function useGame() { const { teamSocket } = useSocket(); const { teamId } = useTeamConnexion(); - const { teamInfos, gameState } = useTeamContext(); - + const { teamInfos } = useTeamContext(); + function sendCurrentPosition() { console.log("Reveal position.") teamSocket.emit("send_position"); @@ -29,5 +29,5 @@ export default function useGame() { }); } - return {...teamInfos, sendCurrentPosition, capture, teamId, gameState}; + return {...teamInfos, sendCurrentPosition, capture, teamId}; } \ No newline at end of file diff --git a/traque-back/admin_socket.js b/traque-back/admin_socket.js index 364a000..7e278dc 100644 --- a/traque-back/admin_socket.js +++ b/traque-back/admin_socket.js @@ -55,9 +55,9 @@ export function initAdminSocketHandler() { socket.emit("current_zone", { begin: zoneManager.getCurrentZone(), end: zoneManager.getNextZone(), - endDate: zoneManager.currentZoneEndDate, + endDate: zoneManager.currentZone.endDate, }); - socket.emit("settings", game.getSettings()); + socket.emit("settings", game.getAdminSettings()); }); socket.on("add_team", (teamName) => { diff --git a/traque-back/game.js b/traque-back/game.js index 266c5b3..ea3e97f 100644 --- a/traque-back/game.js +++ b/traque-back/game.js @@ -60,7 +60,7 @@ export default { }, checkEndGame() { - if (this.teams.filter(team => !team.captured) <= 2) this.setState(GameState.FINISHED); + if (this.teams.filter(team => !team.captured).length <= 2) this.setState(GameState.FINISHED); }, updateChasingChain() { @@ -78,7 +78,7 @@ export default { // Update of lastSentLocation for (const team of this.teams) { team.lastSentLocation = team.currentLocation; - team.locationSendDeadline = dateNow + sendPositionTimeouts.duration * 60 * 1000; + team.locationSendDeadline = dateNow + sendPositionTimeouts.delay * 60 * 1000; sendPositionTimeouts.set(team.id); sendUpdatedTeamInformations(team.id); } @@ -122,7 +122,7 @@ export default { /* ------------------------------- STATE AND SETTINGS FUNCTIONS ------------------------------- */ - getSettings() { + getAdminSettings() { return { messages: this.messages, zone: zoneManager.settings, @@ -131,14 +131,23 @@ export default { }; }, + getPlayerSettings() { + return { + messages: this.messages, + zone: {type: zoneManager.settings.type}, + sendPositionDelay: sendPositionTimeouts.delay, + outOfZoneDelay: outOfZoneTimeouts.delay + }; + }, + changeSettings(newSettings) { if ("messages" in newSettings) this.messages = {...this.messages, ...newSettings.messages}; if ("zone" in newSettings) zoneManager.changeSettings(newSettings.zone); if ("sendPositionDelay" in newSettings) sendPositionTimeouts.setDelay(newSettings.sendPositionDelay); if ("outOfZoneDelay" in newSettings) outOfZoneTimeouts.setDelay(newSettings.outOfZoneDelay); // Broadcast new infos - secureAdminBroadcast("settings", this.getSettings()); - playersBroadcast("game_settings", this.messages); + secureAdminBroadcast("settings", this.getAdminSettings()); + playersBroadcast("settings", this.getPlayerSettings()); }, setState(newState) { @@ -153,7 +162,7 @@ export default { break; case GameState.PLACEMENT: if (this.teams.length < 3) { - secureAdminBroadcast("game_state", {state: this.state, stateDate: this.stateDate}); + secureAdminBroadcast("game_state", {state: this.state, date: this.stateDate}); return false; } trajectory.stop(); @@ -163,7 +172,7 @@ export default { break; case GameState.PLAYING: if (this.teams.length < 3) { - secureAdminBroadcast("game_state", {state: this.state, stateDate: this.stateDate}); + secureAdminBroadcast("game_state", {state: this.state, date: this.stateDate}); return false; } trajectory.start(); @@ -172,7 +181,7 @@ export default { break; case GameState.FINISHED: if (this.state != GameState.PLAYING) { - secureAdminBroadcast("game_state", {state: this.state, stateDate: this.stateDate}); + secureAdminBroadcast("game_state", {state: this.state, date: this.stateDate}); return false; } trajectory.stop(); @@ -187,8 +196,8 @@ export default { this.state = newState; this.stateDate = dateNow; // Broadcast new infos - secureAdminBroadcast("game_state", {state: newState, stateDate: this.stateDate}); - playersBroadcast("game_state", newState); + secureAdminBroadcast("game_state", {state: newState, date: this.stateDate}); + playersBroadcast("game_state", {state: newState, date: this.stateDate}); return true; }, @@ -214,7 +223,7 @@ export default { // Variables const team = this.getTeam(teamId); // Remove the player and its data - if (this.isCapitain(teamId, socketId)) { + if (this.isPlayerCapitain(teamId, socketId)) { team.battery = null; team.phoneModel = null; team.phoneName = null; @@ -297,6 +306,17 @@ export default { return true; }, + updateTeam(teamId, newInfos) { + // Test of parameters + if (!this.hasTeam(teamId)) return false; + // Update + this.teams = this.teams.map(team => team.id == teamId ? {...team, ...newInfos} : team); + // Broadcast new infos + secureAdminBroadcast("teams", this.teams); + sendUpdatedTeamInformations(teamId); + return true; + }, + captureTeam(teamId) { // Test of parameters if (!this.hasTeam(teamId)) return false; @@ -369,7 +389,7 @@ export default { const teamCurrentlyOutOfZone = !zoneManager.isInZone({ lat: location[0], lng: location[1] }) if (teamCurrentlyOutOfZone && !team.outOfZone) { team.outOfZone = true; - team.outOfZoneDeadline = dateNow + outOfZoneTimeouts.duration * 60 * 1000; + team.outOfZoneDeadline = dateNow + outOfZoneTimeouts.delay * 60 * 1000; outOfZoneTimeouts.set(teamId); } else if (!teamCurrentlyOutOfZone && team.outOfZone) { team.outOfZone = false; @@ -395,7 +415,7 @@ export default { team.nSentLocation++; team.lastSentLocation = team.currentLocation; team.enemyLocation = enemyTeam.lastSentLocation; - team.locationSendDeadline = dateNow + sendPositionTimeouts.duration * 60 * 1000; + team.locationSendDeadline = dateNow + sendPositionTimeouts.delay * 60 * 1000; sendPositionTimeouts.set(team.id); // Update enemy enemyTeam.nObserved++; diff --git a/traque-back/team_socket.js b/traque-back/team_socket.js index f9c50a1..add93b7 100644 --- a/traque-back/team_socket.js +++ b/traque-back/team_socket.js @@ -92,14 +92,16 @@ export function initTeamSocket() { return; } sendUpdatedTeamInformations(loginTeamId); - socket.emit("game_state", game.state); - socket.emit("game_settings", game.messages); - socket.emit("zone", { - type: zoneManager.settings.type, + socket.emit("game_state", { + state: game.state, + date: game.stateDate + }); + socket.emit("current_zone", { begin: zoneManager.getCurrentZone(), end: zoneManager.getNextZone(), - endDate: zoneManager.currentZoneEndDate, + endDate: zoneManager.currentZone.endDate, }); + socket.emit("settings", game.getPlayerSettings()); callback({ isLoggedIn : true, message: "Logged in"}); }); diff --git a/traque-back/zone_manager.js b/traque-back/zone_manager.js index 9dafe3d..ad44e14 100644 --- a/traque-back/zone_manager.js +++ b/traque-back/zone_manager.js @@ -31,6 +31,7 @@ const defaultCircleSettings = {type: zoneTypes.circle, min: null, max: null, red function circleZone(center, radius, duration) { return { + type: zoneTypes.circle, center: center, radius: radius, duration: duration, @@ -84,18 +85,19 @@ function circleSettingsToZones(settings) { const defaultPolygonSettings = {type: zoneTypes.polygon, polygons: []} -function polygonZone(points, duration) { +function polygonZone(polygon, duration) { return { - points: points, + type: zoneTypes.polygon, + polygon: polygon, duration: duration, isInZone(location) { const {lat: x, lng: y} = location; let inside = false; - for (let i = 0, j = this.points.length - 1; i < this.points.length; j = i++) { - const {lat: xi, lng: yi} = this.points[i]; - const {lat: xj, lng: yj} = this.points[j]; + for (let i = 0, j = this.polygon.length - 1; i < this.polygon.length; j = i++) { + const {lat: xi, lng: yi} = this.polygon[i]; + const {lat: xj, lng: yj} = this.polygon[j]; const intersects = ((yi > y) !== (yj > y)) && (x < ((xj - xi) * (y - yi)) / (yj - yi) + xi); @@ -156,7 +158,7 @@ function polygonSettingsToZones(settings) { )); } else { zones.push(polygonZone( - mergePolygons(zones[length-1].points, polygon), + mergePolygons(zones[length-1].polygon, polygon), duration )); } @@ -233,12 +235,11 @@ export default { zoneBroadcast() { const zone = { - type: this.settings.type, begin: this.getCurrentZone(), end: this.getNextZone(), endDate:this.currentZone.endDate, }; - playersBroadcast("zone", zone); + playersBroadcast("current_zone", zone); secureAdminBroadcast("current_zone", zone); }, } diff --git a/traque-front/app/admin/components/buttons.jsx b/traque-front/app/admin/components/buttons.jsx deleted file mode 100644 index 82366c4..0000000 --- a/traque-front/app/admin/components/buttons.jsx +++ /dev/null @@ -1,15 +0,0 @@ -export function MapButton({ icon, ...props }) { - return ( - - ); -} - -export function ControlButton({ icon, ...props }) { - return ( - - ); -} diff --git a/traque-front/app/admin/components/liveMap.jsx b/traque-front/app/admin/components/liveMap.jsx index c5245eb..87321dc 100644 --- a/traque-front/app/admin/components/liveMap.jsx +++ b/traque-front/app/admin/components/liveMap.jsx @@ -1,26 +1,14 @@ -import { useEffect, useState } from "react"; -import { Marker, Tooltip, Polygon, Circle } from "react-leaflet"; -import "leaflet/dist/leaflet.css"; -import 'leaflet-polylinedecorator'; -import { Arrow } from "@/components/layer"; +import { Fragment, useEffect, useState } from "react"; +import { Arrow, CircleZone, PolygonZone, Position, Tag } from "@/components/layer"; import { CustomMapContainer, MapEventListener, MapPan } from "@/components/map"; import useAdmin from "@/hook/useAdmin"; import { GameState, ZoneTypes } from "@/util/types"; import { mapZooms } from "@/util/configurations"; -const positionIcon = new L.Icon({ - iconUrl: '/icons/marker/blue.png', - iconSize: [30, 30], - iconAnchor: [15, 15], - popupAnchor: [0, -15], - shadowSize: [30, 30], -}); - -export default function LiveMap({ selectedTeamId, onSelected, isFocusing, setIsFocusing, mapStyle, showZones, showNames, showArrows}) { +export default function LiveMap({ selectedTeamId, onSelected, isFocusing, setIsFocusing, mapStyle, showZones, showNames, showArrows }) { const { zoneType, zoneExtremities, teams, nextZoneDate, getTeam, gameState } = useAdmin(); const [timeLeftNextZone, setTimeLeftNextZone] = useState(null); - // Remaining time before sending position useEffect(() => { if (nextZoneDate) { const updateTime = () => { @@ -36,25 +24,25 @@ export default function LiveMap({ selectedTeamId, onSelected, isFocusing, setIsF function formatTime(time) { // time is in seconds - if (time < 0) return "00:00"; + if (!time || time < 0) return "00:00"; const minutes = Math.floor(time / 60); const seconds = Math.floor(time % 60); return String(minutes).padStart(2,"0") + ":" + String(seconds).padStart(2,"0"); } function Zones() { - if (!(showZones && gameState == GameState.PLAYING && zoneType)) return null; + if (!(showZones && gameState == GameState.PLAYING)) return null; switch (zoneType) { case ZoneTypes.CIRCLE: return (<> - { zoneExtremities.begin && } - { zoneExtremities.end && } + + ); case ZoneTypes.POLYGON: return (<> - { zoneExtremities.begin && } - { zoneExtremities.end && } + + ); default: return null; @@ -68,17 +56,15 @@ export default function LiveMap({ selectedTeamId, onSelected, isFocusing, setIsF {isFocusing && } setIsFocusing(false)}/> - {teams.map((team) => gameState == GameState.PLACEMENT && team.startingArea && - - {team.name} - - )} - {teams.map((team) => team.currentLocation && !team.captured && <> - onSelected(team.id)}}> - {showNames && {team.name}} - - {showArrows && } - )} + {teams.map((team) => team && + + + + + onSelected(team.id)} display={!team.captured}> + + + )} ) diff --git a/traque-front/app/admin/components/teamSidePanel.jsx b/traque-front/app/admin/components/teamSidePanel.jsx index cd71ef6..95ead05 100644 --- a/traque-front/app/admin/components/teamSidePanel.jsx +++ b/traque-front/app/admin/components/teamSidePanel.jsx @@ -2,6 +2,8 @@ import { env } from 'next-runtime-env'; import { useEffect, useState } from "react"; import useAdmin from "@/hook/useAdmin"; import { getStatus } from '@/util/functions'; +import { Colors } from '@/util/types'; +import { teamStatus } from '@/util/configurations'; function DotLine({ label, value }) { return ( @@ -22,7 +24,7 @@ function IconValue({ color, icon, value }) { return (
-

{value}

+

{value}

); } @@ -41,11 +43,10 @@ export default function TeamSidePanel({ selectedTeamId, onClose }) { if (!team) return null; - function formatTime(startDate) { + function formatTime(startDate, endDate) { // startDate in milliseconds - const date = team.captured ? team.finishDate : Date.now(); - if (date == null || startDate == null || startDate < 0) return NO_VALUE + ":" + NO_VALUE; - const seconds = Math.floor((date - startDate) / 1000); + if (endDate == null || startDate == null || startDate < 0) return NO_VALUE + ":" + NO_VALUE; + const seconds = Math.floor((endDate - startDate) / 1000); const m = Math.floor(seconds / 60); const s = Math.floor(seconds % 60); return `${m}:${s.toString().padStart(2, "0")}`; @@ -57,11 +58,10 @@ export default function TeamSidePanel({ selectedTeamId, onClose }) { return `${Math.floor(distance / 100) / 10}km`; } - function formatSpeed(distance, startDate) { + function formatSpeed(distance, startDate, endDate) { // distance in meters | startDate in milliseconds - const date = team.captured ? team.finishDate : Date.now(); - if (distance == null || distance < 0 || date == null || startDate == null || date <= startDate) return NO_VALUE + "km/h"; - const speed = distance / (date - startDate); + if (distance == null || distance < 0 || endDate == null || startDate == null || endDate <= startDate) return NO_VALUE + "km/h"; + const speed = distance / (endDate - startDate); return `${Math.floor(speed * 36000) / 10}km/h`; } @@ -75,31 +75,31 @@ export default function TeamSidePanel({ selectedTeamId, onClose }) { return (
-

{getStatus(team, gameState).label}

+

{getStatus(team, gameState).label}

{team.name ?? NO_VALUE}

Photo de l'équipe
- 0 ? "green" : "red"} icon="user" value={team.sockets.length ?? NO_VALUE} /> + 0 ? "green" : "red"} icon="user" value={team.sockets?.length ?? NO_VALUE} /> = 20 ? "green" : "red"} icon="battery" value={(team.battery ?? NO_VALUE) + "%"} />
- +
- - + +
- - + + diff --git a/traque-front/app/admin/components/teamViewer.jsx b/traque-front/app/admin/components/teamViewer.jsx index 0d59813..d4b561d 100644 --- a/traque-front/app/admin/components/teamViewer.jsx +++ b/traque-front/app/admin/components/teamViewer.jsx @@ -5,6 +5,7 @@ import { getStatus } from '@/util/functions'; function TeamViewerItem({ team }) { const { gameState } = useAdmin(); const status = getStatus(team, gameState); + const NO_VALUE = "XX"; return (
@@ -12,11 +13,11 @@ function TeamViewerItem({ team }) {
0 ? "green" : "red"}.png`} className="w-4 h-4" /> = 20 ? "green" : "red"}.png`} className="w-4 h-4" /> - +
-

{team.name}

+

{team.name ?? NO_VALUE}

-

+

{status.label}

diff --git a/traque-front/app/admin/login/components/loginForm.jsx b/traque-front/app/admin/login/components/loginForm.jsx deleted file mode 100644 index 6642e88..0000000 --- a/traque-front/app/admin/login/components/loginForm.jsx +++ /dev/null @@ -1,21 +0,0 @@ -import { useState } from "react"; -import { BlueButton } from "@/components/button"; -import { TextInput } from "@/components/input"; - -export default function LoginForm({ onSubmit, title, placeholder, buttonText}) { - const [value, setValue] = useState(""); - - function handleSubmit(e) { - e.preventDefault(); - setValue(""); - onSubmit(value); - } - - return ( -
-

{title}

- setValue(e.target.value)} name="team-id"/> - {buttonText} - - ); -} diff --git a/traque-front/app/admin/login/page.js b/traque-front/app/admin/login/page.js index 0b7633c..682ebd9 100644 --- a/traque-front/app/admin/login/page.js +++ b/traque-front/app/admin/login/page.js @@ -1,13 +1,26 @@ "use client"; +import { useState } from "react"; import { useAdminConnexion } from '@/context/adminConnexionContext'; -import LoginForm from './components/loginForm'; export default function AdminLoginPage() { const {login, useProtect} = useAdminConnexion(); + const [value, setValue] = useState(""); useProtect(); + + function handleSubmit(e) { + e.preventDefault(); + setValue(""); + login(value); + } return ( - +
+
+

Admin login

+ setValue(e.target.value)}/> + +
+
); } diff --git a/traque-front/app/admin/page.js b/traque-front/app/admin/page.js index 8bc5c67..80c881a 100644 --- a/traque-front/app/admin/page.js +++ b/traque-front/app/admin/page.js @@ -9,11 +9,35 @@ import { GameState } from "@/util/types"; import { mapStyles } from '@/util/configurations'; import TeamSidePanel from "./components/teamSidePanel"; import TeamViewer from './components/teamViewer'; -import { MapButton, ControlButton } from './components/buttons'; // Imported at runtime and not at compile time const LiveMap = dynamic(() => import('./components/liveMap'), { ssr: false }); +function MainTitle() { + return ( +
+ +

Page principale

+
+ ); +} + +function MapButton({ icon, ...props }) { + return ( + + ); +} + +function ControlButton({ icon, ...props }) { + return ( + + ); +} + export default function AdminPage() { const { useProtect } = useAdminConnexion(); const { changeState, getTeam } = useAdmin(); @@ -53,12 +77,9 @@ export default function AdminPage() { } return ( -
+
-
- -

Page principale

-
+
@@ -72,7 +93,7 @@ export default function AdminPage() {
-
+
} {false && }
-
+
) } diff --git a/traque-front/app/admin/parameters/components/circleZoneSelector.jsx b/traque-front/app/admin/parameters/components/circleZoneSelector.jsx index 6264d42..ef8c9fc 100644 --- a/traque-front/app/admin/parameters/components/circleZoneSelector.jsx +++ b/traque-front/app/admin/parameters/components/circleZoneSelector.jsx @@ -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 + ? + : + ); + } + + function MinCircleZone() { + return ( + drawingMinCircle + ? + : + ); + } return (<> - {minCenter && minRadius && } - {maxCenter && maxRadius && } + + ); } -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 ( -
- {localZoneSettings.type == ZoneTypes.CIRCLE && <> +
+ {localZoneSettings && localZoneSettings.type == ZoneTypes.CIRCLE && <>
-
- {editMode == EditMode.MIN && setEditMode(EditMode.MAX)}>Click to edit first zone} - {editMode == EditMode.MAX && setEditMode(EditMode.MIN)}>Click to edit last zone} -
+ {editMode == EditMode.MIN && } + {editMode == EditMode.MAX && }

Reduction number

-
- updateReductionCount(customStringToInt(e.target.value))} /> -
+

Zone duration

-
- updateDuration(customStringToInt(e.target.value))} /> -
+

Timeout

-
- setLocalOutOfZoneDelay(customStringToInt(e.target.value))} /> -
-
-
- Apply +
+
}
diff --git a/traque-front/app/admin/parameters/components/messages.jsx b/traque-front/app/admin/parameters/components/messages.jsx index f0c350e..e67ca89 100644 --- a/traque-front/app/admin/parameters/components/messages.jsx +++ b/traque-front/app/admin/parameters/components/messages.jsx @@ -6,7 +6,7 @@ function MessageInput({title, ...props}) { return (

{title}

- +
); } diff --git a/traque-front/app/admin/parameters/components/placementZoneSelector.jsx b/traque-front/app/admin/parameters/components/placementZoneSelector.jsx index e8e3a3a..a498d21 100644 --- a/traque-front/app/admin/parameters/components/placementZoneSelector.jsx +++ b/traque-front/app/admin/parameters/components/placementZoneSelector.jsx @@ -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 (<> { placementZones.map(placementZone => - - {placementZone.name} - + + + )} ); } -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 ( -
+
setSelectedTeamId(null)} /> @@ -58,7 +58,7 @@ export default function PlacementZoneSelector() {
setSelectedTeamId(selectedTeamId != id ? id : null)}> { (team) => -
+

{team.name}

} diff --git a/traque-front/app/admin/parameters/components/playingZoneSelector.jsx b/traque-front/app/admin/parameters/components/playingZoneSelector.jsx index 55484a5..e2b18d7 100644 --- a/traque-front/app/admin/parameters/components/playingZoneSelector.jsx +++ b/traque-front/app/admin/parameters/components/playingZoneSelector.jsx @@ -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 ( -
+

Type de zone :

setZoneType(ZoneTypes.CIRCLE)} isSelected={zoneType == ZoneTypes.CIRCLE} /> setZoneType(ZoneTypes.POLYGON)} isSelected={zoneType == ZoneTypes.POLYGON} />
- {zoneType == ZoneTypes.CIRCLE && - - } - {zoneType == ZoneTypes.POLYGON && - - } + +
); diff --git a/traque-front/app/admin/parameters/components/polygonZoneSelector.jsx b/traque-front/app/admin/parameters/components/polygonZoneSelector.jsx index 7ae4bed..e6fa614 100644 --- a/traque-front/app/admin/parameters/components/polygonZoneSelector.jsx +++ b/traque-front/app/admin/parameters/components/polygonZoneSelector.jsx @@ -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 (<> - {localZoneSettings.polygons.map((zone, i) => )} + {localZoneSettings.polygons.map(zone => + + + )} { currentPolygon.length > 0 && <> - + } - {highlightNodes.map((node, i) => )} + {highlightNodes.map((node, i) => + + )} ); } -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 ( -
- {localZoneSettings.type == ZoneTypes.POLYGON && <> +
+ {localZoneSettings && localZoneSettings.type == ZoneTypes.POLYGON && <>
@@ -95,23 +110,17 @@ export default function PolygonZoneSelector() {
{ (zone) => -
+

Zone {zone.id}

-
- updateDuration(zone.id, customStringToInt(e.target.value))}/> -
+ updateDuration(zone.id, value)}/>
}

Timeout

-
- setLocalOutOfZoneDelay(customStringToInt(e.target.value))}/> -
-
-
- Apply +
+
}
diff --git a/traque-front/app/admin/parameters/components/teamManager.jsx b/traque-front/app/admin/parameters/components/teamManager.jsx index 470f330..131f9b8 100644 --- a/traque-front/app/admin/parameters/components/teamManager.jsx +++ b/traque-front/app/admin/parameters/components/teamManager.jsx @@ -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() {

Interval between position updates

-
- setLocalSendPositionDelay(parseInt(e.target.value, 10))} onBlur={applyLocalSendPositionDelay} /> -
+
); diff --git a/traque-front/app/admin/parameters/page.js b/traque-front/app/admin/parameters/page.js index f5dc215..44e856b 100644 --- a/traque-front/app/admin/parameters/page.js +++ b/traque-front/app/admin/parameters/page.js @@ -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 ( -
+
@@ -56,12 +56,8 @@ export default function ConfigurationPage() { setCurrentTab(Tabs.PLAYING_ZONES)} isSelected={currentTab == Tabs.PLAYING_ZONES}/>
- { currentTab == Tabs.PLAYING_ZONES && - - } - { currentTab == Tabs.PLACEMENT_ZONES && - - } + +
diff --git a/traque-front/app/layout.js b/traque-front/app/layout.js index 209bd7e..0435b54 100644 --- a/traque-front/app/layout.js +++ b/traque-front/app/layout.js @@ -1,24 +1,25 @@ import { Inter } from "next/font/google"; import "./globals.css"; -import SocketProvider from "@/context/socketContext"; - import { PublicEnvScript } from 'next-runtime-env'; +import SocketProvider from "@/context/socketContext"; const inter = Inter({ subsets: ["latin"] }); export const metadata = { - title: "La Traque", + title: "La Traque", }; export default function RootLayout({ children }) { - return ( - - - - - - {children} - - - ); + return ( + + + + + + + {children} + + + + ); } diff --git a/traque-front/components/button.jsx b/traque-front/components/button.jsx deleted file mode 100644 index c96c27e..0000000 --- a/traque-front/components/button.jsx +++ /dev/null @@ -1,16 +0,0 @@ -export function CustomButton({ color, children, ...props }) { - const colorClasses = { - blue: 'bg-blue-600 hover:bg-blue-500', - red: 'bg-red-600 hover:bg-red-500', - green: 'bg-green-600 hover:bg-green-500', - yellow: 'bg-yellow-600 hover:bg-yellow-500', - purple: 'bg-purple-600 hover:bg-purple-500', - gray: 'bg-gray-600 hover:bg-gray-500', - }; - - return ( - - ); -} diff --git a/traque-front/components/input.jsx b/traque-front/components/input.jsx index 94aed9d..04b92e7 100644 --- a/traque-front/components/input.jsx +++ b/traque-front/components/input.jsx @@ -1,12 +1,9 @@ -const className = "block w-full h-full p-4 rounded text-center ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600"; +export function NumberInput({onChange, ...props}) { + function customStringToInt(e) { + return parseInt(e, 10) || null; + } -export function TextInput({...props}) { - return ( - - ) -} -export function TextArea({...props}) { - return ( -