+
}
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 (
-
- )
+ return (
+
onChange(customStringToInt(e.target.value))} {...props} />
+ )
}
diff --git a/traque-front/components/layer.jsx b/traque-front/components/layer.jsx
index a874341..39a8e01 100644
--- a/traque-front/components/layer.jsx
+++ b/traque-front/components/layer.jsx
@@ -1,52 +1,77 @@
import { useEffect, useState } from "react";
-import { Marker, CircleMarker, Polygon, useMap } from "react-leaflet";
+import { Marker, Tooltip, CircleMarker, Circle, Polygon, useMap } from "react-leaflet";
import "leaflet/dist/leaflet.css";
import 'leaflet-polylinedecorator';
-export function Node({pos, nodeSize = 5, color = 'black'}) {
+export function Node({position, nodeSize = 5, color = 'black', display = true}) {
return (
-
+ display && position &&
);
}
-export function LabeledPolygon({polygon, label, color = 'black', opacity = '0.5', border = 3, iconSize = 24, iconColor = 'white'}) {
- const length = polygon.length;
-
- if (length < 3) return null;
-
- const sum = polygon.reduce(
- (acc, coord) => ({
- lat: acc.lat + coord.lat,
- lng: acc.lng + coord.lng
- }),
- { lat: 0, lng: 0 }
- );
-
- // meanPoint can be out of the polygon
- // Idea : take the mean point of the largest connected subpolygon
- const meanPoint = {lat: sum.lat / length, lng: sum.lng / length}
-
+export function Label({position, label, color, size = 24, display = true}) {
const labelIcon = L.divIcon({
html: `
${label}
`,
+ font-size: ${size}px;
+ ">${label || ""}
`,
className: 'custom-label-icon',
- iconSize: [iconSize, iconSize],
- iconAnchor: [iconSize / 2, iconSize / 2]
+ iconSize: [size, size],
+ iconAnchor: [size / 2, size / 2]
});
- return (<>
-
-
- >);
+ return (
+ display && position &&
+ );
}
-export function Arrow({ pos1, pos2, color = 'black', weight = 5, arrowSize = 20, insetPixels = 25 }) {
+export function Tag({text, display = true}) {
+ return (
+ display &&
{text || ""}
+ );
+}
+
+export function CircleZone({circle, color, opacity = '0.1', border = 3, display = true, children}) {
+ return (
+ display && circle &&
+
+ {children}
+
+ );
+}
+
+export function PolygonZone({polygon, color, opacity = '0.1', border = 3, display = true, children}) {
+ return (
+ display && polygon && polygon.length >= 3 &&
+
+ {children}
+
+ );
+}
+
+export function Position({position, color, onClick, display = true, children}) {
+
+ const positionIcon = new L.Icon({
+ iconUrl: `/icons/marker/${color}.png`,
+ iconSize: [30, 30],
+ iconAnchor: [15, 15],
+ popupAnchor: [0, -15],
+ shadowSize: [30, 30],
+ });
+
+ return (
+ display && position &&
+
+ {children}
+
+ );
+}
+
+export function Arrow({ pos1, pos2, color = 'black', weight = 5, arrowSize = 20, insetPixels = 25, display = true }) {
const map = useMap();
const [insetPositions, setInsetPositions] = useState(null);
@@ -103,7 +128,7 @@ export function Arrow({ pos1, pos2, color = 'black', weight = 5, arrowSize = 20,
}, [pos1, pos2]);
useEffect(() => {
- if (!insetPositions) return;
+ if (!display || !insetPositions) return;
// Create the base polyline
const polyline = L.polyline(insetPositions, {
diff --git a/traque-front/components/list.jsx b/traque-front/components/list.jsx
index 38cf43d..1fa0418 100644
--- a/traque-front/components/list.jsx
+++ b/traque-front/components/list.jsx
@@ -9,14 +9,14 @@ export function List({array, selectedId, onSelected, children}) {
return (
);
@@ -50,7 +50,7 @@ export function ReorderList({droppableId, array, setArray, children}) {
{provided => (
- {localArray.map((elem, i) => (
+ {localArray?.map((elem, i) => (
-
{provided => (
@@ -61,7 +61,7 @@ export function ReorderList({droppableId, array, setArray, children}) {
)}
- ))}
+ )) ?? null}
{provided.placeholder}
diff --git a/traque-front/components/map.jsx b/traque-front/components/map.jsx
index f9724be..2ae445d 100644
--- a/traque-front/components/map.jsx
+++ b/traque-front/components/map.jsx
@@ -17,6 +17,7 @@ export function MapPan({center, zoom, animate=false}) {
export function MapEventListener({ onLeftClick, onRightClick, onMouseMove, onDragStart }) {
const map = useMap();
+ // TODO use useMapEvents instead of this + detect when zoom
// Handle the mouse click left
useEffect(() => {
diff --git a/traque-front/components/section.jsx b/traque-front/components/section.jsx
index 8f94874..32026a5 100644
--- a/traque-front/components/section.jsx
+++ b/traque-front/components/section.jsx
@@ -2,9 +2,11 @@ export function Section({title, outerClassName, innerClassName, children}) {
return (
-
-
{title}
-
+ {title &&
+
+
{title}
+
+ }
{children}
diff --git a/traque-front/context/adminContext.jsx b/traque-front/context/adminContext.jsx
index fd7650b..9ece481 100644
--- a/traque-front/context/adminContext.jsx
+++ b/traque-front/context/adminContext.jsx
@@ -27,11 +27,10 @@ export function AdminProvider({ children }) {
useSocketListener(adminSocket, "game_state", (data) => {
setGameState(data.state);
- setStartDate(data.date)
+ setStartDate(data.date);
});
useSocketListener(adminSocket, "current_zone", (data) => {
- setZoneType(data.type);
setZoneExtremities({begin: data.begin, end: data.end});
setNextZoneDate(data.endDate);
});
@@ -39,6 +38,7 @@ export function AdminProvider({ children }) {
useSocketListener(adminSocket, "settings", (data) => {
setMessages(data.messages);
setZoneSettings(data.zone);
+ setZoneType(data.zone.type);
setSendPositionDelay(data.sendPositionDelay);
setOutOfZoneDelay(data.outOfZoneDelay);
});
diff --git a/traque-front/hook/useAdmin.jsx b/traque-front/hook/useAdmin.jsx
index 6f7d577..b2b29a9 100644
--- a/traque-front/hook/useAdmin.jsx
+++ b/traque-front/hook/useAdmin.jsx
@@ -11,10 +11,6 @@ export default function useAdmin() {
return teams.find(team => team.id === teamId);
}
- function reorderTeams(newOrder) {
- adminSocket.emit("reorder_teams", newOrder);
- }
-
function addTeam(teamName) {
adminSocket.emit("add_team", teamName);
}
@@ -23,6 +19,10 @@ export default function useAdmin() {
adminSocket.emit("remove_team", teamId);
}
+ function reorderTeams(newOrder) {
+ adminSocket.emit("reorder_teams", newOrder);
+ }
+
function captureTeam(teamId) {
adminSocket.emit("capture_team", teamId);
}
diff --git a/traque-front/hook/useCircleDraw.jsx b/traque-front/hook/useCircleDraw.jsx
index b2e57ae..7eb3118 100644
--- a/traque-front/hook/useCircleDraw.jsx
+++ b/traque-front/hook/useCircleDraw.jsx
@@ -2,42 +2,34 @@
import { useEffect, useState } from "react";
export default function useMapCircleDraw(circle, setCircle) {
- const [drawing, setDrawing] = useState(false);
- const [center, setCenter] = useState(circle?.center || null);
- const [radius, setRadius] = useState(circle?.radius || null);
+ const [drawingCircle, setDrawingCircle] = useState(null);
useEffect(() => {
- setDrawing(false);
- setCenter(circle?.center || null);
- setRadius(circle?.radius || null);
- }, [circle])
+ setDrawingCircle(null);
+ }, [circle]);
function handleLeftClick(e) {
- if (!drawing) {
- setCenter(e.latlng);
- setRadius(null);
- setDrawing(true);
+ if (drawingCircle) {
+ setCircle(drawingCircle);
+ setDrawingCircle(null);
} else {
- setDrawing(false);
- setCircle({center, radius});
+ setDrawingCircle({center: e.latlng, radius: 0});
}
}
function handleRightClick(e) {
- if (drawing) {
- setDrawing(false);
- setCenter(circle?.center || null);
- setRadius(circle?.radius || null);
- } else {
+ if (drawingCircle) {
+ setDrawingCircle(null);
+ } else if (e.latlng.distanceTo(circle.center) < circle.radius) {
setCircle(null);
}
}
function handleMouseMove(e) {
- if (drawing) {
- setRadius(e.latlng.distanceTo(center));
+ if (drawingCircle) {
+ setDrawingCircle({center: drawingCircle.center, radius: e.latlng.distanceTo(drawingCircle.center)});
}
}
- return { center, radius, handleLeftClick, handleRightClick, handleMouseMove };
+ return { drawingCircle, handleLeftClick, handleRightClick, handleMouseMove };
}
diff --git a/traque-front/hook/useLocation.jsx b/traque-front/hook/useLocation.jsx
deleted file mode 100644
index acc51c8..0000000
--- a/traque-front/hook/useLocation.jsx
+++ /dev/null
@@ -1,39 +0,0 @@
-"use client";
-import { useEffect, useState } from "react";
-
-export default function useLocation(interval) {
- const [location, setLocation] = useState(null);
-
- useEffect(() => {
- if (!navigator.geolocation) {
- console.log('Geolocation not supported');
- return;
- }
-
- if (interval < 1000 || interval == Infinity) {
- console.log('Localisation interval no supported');
- return;
- }
-
- const watchId = navigator.geolocation.watchPosition(
- (pos) => {
- setLocation({
- lat: pos.coords.latitude,
- lng: pos.coords.longitude,
- accuracy: pos.coords.accuracy,
- timestamp: pos.timestamp
- });
- },
- (err) => console.log("Error :", err),
- {
- enableHighAccuracy: true,
- timeout: 10000,
- maximumAge: interval,
- }
- );
-
- return () => navigator.geolocation.clearWatch(watchId);
- }, []);
-
- return location;
-}
diff --git a/traque-front/hook/useMultipleCircleDraw.jsx b/traque-front/hook/useMultipleCircleDraw.jsx
index 0fe3569..f1c7ee9 100644
--- a/traque-front/hook/useMultipleCircleDraw.jsx
+++ b/traque-front/hook/useMultipleCircleDraw.jsx
@@ -2,32 +2,16 @@
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);
+ return circles.some(circle => latlng.distanceTo(circle.center) < 2 * circle.radius);
}
function getCircleFromLatlng(latlng) {
- return circles.find(circle => getDistanceFromLatLon(latlng, circle.center) < circle.radius);
+ return circles.find(circle => latlng.distanceTo(circle.center) < circle.radius);
}
function handleLeftClick(e) {
- if (isBaddlyPlaced(e.latlng)) return;
- addCircle(e.latlng, radius);
+ if (!isBaddlyPlaced(e.latlng)) addCircle(e.latlng, radius);
}
function handleRightClick(e) {
diff --git a/traque-front/util/configurations.js b/traque-front/util/configurations.js
index 3b45441..a3a8fd3 100644
--- a/traque-front/util/configurations.js
+++ b/traque-front/util/configurations.js
@@ -1,4 +1,4 @@
-import { ZoneTypes } from "./types";
+import { ZoneTypes, Colors } from "./types";
export const mapLocations = {
paris: [48.86, 2.33]
@@ -26,10 +26,11 @@ export const defaultZoneSettings = {
}
export const teamStatus = {
- playing: { label: "En jeu", color: "text-custom-green" },
- captured: { label: "Capturée", color: "text-custom-red" },
- outofzone: { label: "Hors zone", color: "text-custom-orange" },
- ready: { label: "Placée", color: "text-custom-green" },
- notready: { label: "Non placée", color: "text-custom-red" },
- waiting: { label: "En attente", color: "text-custom-grey" },
+ default: { label: "Indisponible", color: Colors.black },
+ playing: { label: "En jeu", color: Colors.green },
+ captured: { label: "Capturée", color: Colors.red },
+ outofzone: { label: "Hors zone", color: Colors.orange },
+ ready: { label: "Placée", color: Colors.green },
+ notready: { label: "Non placée", color: Colors.red },
+ waiting: { label: "En attente", color: Colors.grey },
}
diff --git a/traque-front/util/functions.js b/traque-front/util/functions.js
index dc3a9d3..0f34e14 100644
--- a/traque-front/util/functions.js
+++ b/traque-front/util/functions.js
@@ -2,6 +2,7 @@ import { GameState } from './types';
import { teamStatus } from './configurations';
export function getStatus(team, gamestate) {
+ if (!team) return null;
switch (gamestate) {
case GameState.SETUP:
return teamStatus.waiting;
@@ -11,5 +12,7 @@ export function getStatus(team, gamestate) {
return team.captured ? teamStatus.captured : team.outofzone ? teamStatus.outofzone : teamStatus.playing;
case GameState.FINISHED:
return team.captured ? teamStatus.captured : teamStatus.playing;
+ default:
+ return teamStatus.default;
}
}
diff --git a/traque-front/util/types.js b/traque-front/util/types.js
index e15c0f8..8a8eb4f 100644
--- a/traque-front/util/types.js
+++ b/traque-front/util/types.js
@@ -1,3 +1,11 @@
+export const Colors = {
+ black: "#000000",
+ grey: "#808080",
+ green: "#19e119",
+ red: "#e11919",
+ orange: "#fa6400"
+}
+
export const GameState = {
SETUP: "setup",
PLACEMENT: "placement",