From ba846acc0c0b4c09ae35a94c1083ef3926d63ef4 Mon Sep 17 00:00:00 2001 From: Quentin Roussel Date: Thu, 6 Jun 2024 23:55:36 +0200 Subject: [PATCH] MVP systeme de zone quadrillage --- traque-front/app/admin/page.js | 2 +- traque-front/components/admin/mapPicker.jsx | 118 ------------ .../components/admin/mapZoneSelector.jsx | 28 +-- traque-front/components/admin/maps.jsx | 180 ++++++++++++++++++ traque-front/components/admin/teamEdit.jsx | 2 +- .../components/admin/zoneSelector.jsx | 9 +- traque-front/components/team/actionDrawer.jsx | 1 - traque-front/components/team/map.jsx | 35 ++-- .../components/team/placementOverlay.jsx | 1 - traque-front/context/adminContext.jsx | 9 +- traque-front/context/teamContext.jsx | 4 +- traque-front/hook/mapDrawing.jsx | 38 ++-- traque-front/hook/useAdmin.jsx | 10 +- 13 files changed, 242 insertions(+), 195 deletions(-) delete mode 100644 traque-front/components/admin/mapPicker.jsx create mode 100644 traque-front/components/admin/maps.jsx diff --git a/traque-front/app/admin/page.js b/traque-front/app/admin/page.js index af4a7d7..1159612 100644 --- a/traque-front/app/admin/page.js +++ b/traque-front/app/admin/page.js @@ -11,7 +11,7 @@ import dynamic from "next/dynamic"; const ZoneSelector = dynamic(() => import('@/components/admin/zoneSelector').then((mod) => mod.ZoneSelector), { ssr: false }); -const LiveMap = dynamic(() => import('@/components/admin/mapPicker').then((mod) => mod.LiveMap), { +const LiveMap = dynamic(() => import('@/components/admin/maps').then((mod) => mod.ZoneEditor), { ssr: false }); export default function AdminPage() { diff --git a/traque-front/components/admin/mapPicker.jsx b/traque-front/components/admin/mapPicker.jsx deleted file mode 100644 index bcb9bb4..0000000 --- a/traque-front/components/admin/mapPicker.jsx +++ /dev/null @@ -1,118 +0,0 @@ -"use client"; -import { useLocation } from "@/hook/useLocation"; -import { useEffect, useState } from "react"; -import "leaflet/dist/leaflet.css"; -import L from "leaflet"; -import { Circle, LayersControl, MapContainer, Marker, Popup, TileLayer, useMap} from "react-leaflet"; -import { useMapCircleDraw } from "@/hook/mapDrawing"; -import useAdmin from "@/hook/useAdmin"; -import { MapGridZoneSelector } from "./mapZoneSelector.jsx"; - - -function MapPan(props) { - const map = useMap(); - const [initialized, setInitialized] = useState(false); - - useEffect(() => { - if (!initialized && props.center) { - map.flyTo(props.center, props.zoom, { animate: false }); - setInitialized(true) - } - }, [props.center]); - return null; -} - -function MapEventListener({ onClick, onMouseMove }) { - const map = useMap(); - useEffect(() => { - map.on('click', onClick); - return () => { - map.off('click', onClick); - } - }, [onClick]); - useEffect(() => { - map.on('mousemove', onMouseMove); - return () => { - map.off('mousemove', onMouseMove); - } - }); - return null; -} - -const DEFAULT_ZOOM = 13; -export function CircularAreaPicker({ area, setArea, markerPosition, ...props }) { - const location = useLocation(Infinity); - const { handleClick, handleMouseMove, center, radius } = useMapCircleDraw(area, setArea); - return ( - - - {center && radius && } - {markerPosition && } - - - ) -} - -//https://stackoverflow.com/questions/71231865/show-fixed-100-m-x-100-m-grid-on-lowest-zoom-level - -export function ZonePicker({ ...props }) { - const location = useLocation(Infinity); - const [coloredTiles, setColoredTiles] = useState([]); - - useEffect(() => { - console.log(coloredTiles) - }, [coloredTiles]); - return ( - - - - - - - - - - ) -} - -export function LiveMap() { - const location = useLocation(Infinity); - const { zone, zoneExtremities, teams, getTeamName } = useAdmin(); - return ( - - - - {zone && } - {zoneExtremities && } - {zoneExtremities && } - {teams.map((team) => team.currentLocation && !team.captured && - - {team.name} -

Chasing : {getTeamName(team.chasing)}

-

Chased by : {getTeamName(team.chased)}

-
-
)} -
- ) -} \ No newline at end of file diff --git a/traque-front/components/admin/mapZoneSelector.jsx b/traque-front/components/admin/mapZoneSelector.jsx index ea3f57a..cf16fde 100644 --- a/traque-front/components/admin/mapZoneSelector.jsx +++ b/traque-front/components/admin/mapZoneSelector.jsx @@ -1,29 +1,15 @@ -import { useEffect, useState } from 'react'; -import { useLeafletContext } from '@react-leaflet/core'; import { useMapGrid } from '@/hook/mapDrawing'; -import { latLngToTileNumber } from '../util/map'; +import { TileNumber, latLngToTileNumber } from '../util/map'; +import { useMapEvent } from 'react-leaflet'; -export function MapGridZoneSelector({ onSelectedTile, tileSize }) { - const [coloredTiles, setColoredTiles] = useState([]); - const { map } = useLeafletContext(); - - useEffect(() => { - map.on('click', (e) => { +export function MapGridZoneSelector({ tilesColor, onClickTile, tileSize }) { + useMapEvent('click', (e) => { const fractionalTileNumber = latLngToTileNumber(e.latlng, tileSize); const tileNumber = new TileNumber(Math.floor(fractionalTileNumber.x), Math.floor(fractionalTileNumber.y)); - if (coloredTiles.some(t => t.equals(tileNumber))) { - setColoredTiles(coloredTiles.filter(t => !t.equals(tileNumber))); - } else { - setColoredTiles([...coloredTiles, tileNumber]); - } - }); + onClickTile(tileNumber); }); - useEffect(() => { - onSelectedTile(coloredTiles); - }, [coloredTiles]); - - useMapGrid(coloredTiles, tileSize); + useMapGrid(tilesColor, tileSize); return null; -} \ No newline at end of file +} diff --git a/traque-front/components/admin/maps.jsx b/traque-front/components/admin/maps.jsx new file mode 100644 index 0000000..7135b19 --- /dev/null +++ b/traque-front/components/admin/maps.jsx @@ -0,0 +1,180 @@ +"use client"; +import { useLocation } from "@/hook/useLocation"; +import { use, useEffect, useState } from "react"; +import "leaflet/dist/leaflet.css"; +import L from "leaflet"; +import { Circle, LayerGroup, LayersControl, MapContainer, Marker, Popup, TileLayer, useMap } from "react-leaflet"; +import { useMapCircleDraw, useTilesColor } from "@/hook/mapDrawing"; +import useAdmin from "@/hook/useAdmin"; +import { MapGridZoneSelector } from "./mapZoneSelector.jsx"; +import { useAdminContext } from "@/context/adminContext.jsx"; +import { GreenButton } from "../util/button.jsx"; +import TextInput from "../util/textInput.jsx"; + + +function MapPan(props) { + const map = useMap(); + const [initialized, setInitialized] = useState(false); + + useEffect(() => { + if (!initialized && props.center) { + map.flyTo(props.center, props.zoom, { animate: false }); + setInitialized(true) + } + }, [props.center]); + return null; +} + +function MapEventListener({ onClick, onMouseMove }) { + const map = useMap(); + useEffect(() => { + map.on('click', onClick); + return () => { + map.off('click', onClick); + } + }, [onClick]); + useEffect(() => { + map.on('mousemove', onMouseMove); + return () => { + map.off('mousemove', onMouseMove); + } + }); + return null; +} + +const DEFAULT_ZOOM = 13; +export function CircularAreaPicker({ area, setArea, markerPosition, ...props }) { + const location = useLocation(Infinity); + const { handleClick, handleMouseMove, center, radius } = useMapCircleDraw(area, setArea); + return ( + + + {center && radius && } + {markerPosition && } + + + ) +} + +//https://stackoverflow.com/questions/71231865/show-fixed-100-m-x-100-m-grid-on-lowest-zoom-level + +export function ZoneInitializer({ ...props }) { + const location = useLocation(Infinity); + const { zone } = useAdminContext(); + const { initZone } = useAdmin(); + + const tilesColor = useTilesColor(zone); + + const handleClickTile = (tile) => { + if (zone) { + if (zone.some(t => t.x === tile.x && t.y === tile.y)) { + initZone(zone.filter(t => t.x !== tile.x || t.y !== tile.y)); + } else { + initZone([...zone, tile]); + } + } + } + + return ( + + + + + + + + + + + + ) +} + +export function ZoneEditor() { + const location = useLocation(Infinity); + const { zone, teams, getTeamName, removeZone } = useAdmin(); + const [zonesToDelete, setZonesToDelete] = useState([]); + const [tilesColor, setTilesColor] = useState([]); + const [timeBeforeDeletion, setTimeBeforeDeletion] = useState(0); + + function handleClickTile(tile) { + if (!zone.some(t => t.x === tile.x && t.y === tile.y)) return; + console.log(tile, "click", zonesToDelete); + if (!zonesToDelete.some(t => t.x === tile.x && t.y === tile.y)) { + setZonesToDelete([...zonesToDelete, tile]); + console.log("delete", tile); + } else { + setZonesToDelete(zonesToDelete.filter(t => t.x !== tile.x || t.y !== tile.y)); + } + } + + useEffect(() => { + console.log(zone); + setTilesColor([ + ...zonesToDelete.map(t => ({ x: t.x, y: t.y, color: 'rgba(255, 0, 0, 0.5)' })), + ...zone + .filter(t => !zonesToDelete.some(t2 => t.x == t2.x && t.y == t2.y)) + .map(t => { + if (t.removeDate == null) { + return { x: t.x, y: t.y, color: 'rgba(0, 0, 255, 0.5)' } + } else { + return { x: t.x, y: t.y, color: 'rgba(255, 255, 0, 0.5)' } + } + }), + ]); + }, [zone, zonesToDelete]); + + const handleSubmit = (e) => { + e.preventDefault(); + removeZone(zonesToDelete, timeBeforeDeletion); + setZonesToDelete([]); + setTimeBeforeDeletion(0); + } + + + return ( +
+ + + + {teams.map((team) => team.currentLocation && !team.captured && + + {team.name} +

Chasing : {getTeamName(team.chasing)}

+

Chased by : {getTeamName(team.chased)}

+
+
)} + + + + + + + +
+ setTimeBeforeDeletion(Number(e.target.value))}> + Delete selected zones +
+ ) +} \ No newline at end of file diff --git a/traque-front/components/admin/teamEdit.jsx b/traque-front/components/admin/teamEdit.jsx index e8f845a..bb8ea53 100644 --- a/traque-front/components/admin/teamEdit.jsx +++ b/traque-front/components/admin/teamEdit.jsx @@ -4,7 +4,7 @@ import BlueButton, { RedButton } from '../util/button'; import useAdmin from '@/hook/useAdmin'; import dynamic from 'next/dynamic'; -const CircularAreaPicker = dynamic(() => import('./mapPicker').then((mod) => mod.CircularAreaPicker), { +const CircularAreaPicker = dynamic(() => import('./maps').then((mod) => mod.CircularAreaPicker), { ssr: false }); diff --git a/traque-front/components/admin/zoneSelector.jsx b/traque-front/components/admin/zoneSelector.jsx index 1624892..32c7d89 100644 --- a/traque-front/components/admin/zoneSelector.jsx +++ b/traque-front/components/admin/zoneSelector.jsx @@ -1,8 +1,4 @@ -import { useEffect, useState } from "react"; -import BlueButton, { GreenButton, RedButton } from "../util/button"; -import { EditMode, ZonePicker } from "./mapPicker"; -import TextInput from "../util/textInput"; -import useAdmin from "@/hook/useAdmin"; +import { ZoneInitializer } from "./maps"; export function ZoneSelector() { @@ -10,8 +6,7 @@ export function ZoneSelector() { return

Edit zones

- +
- Apply
} \ No newline at end of file diff --git a/traque-front/components/team/actionDrawer.jsx b/traque-front/components/team/actionDrawer.jsx index 0828c00..6354139 100644 --- a/traque-front/components/team/actionDrawer.jsx +++ b/traque-front/components/team/actionDrawer.jsx @@ -16,7 +16,6 @@ export default function ActionDrawer() { useEffect(() => { const interval = setInterval(() => { - console.log(locationSendDeadline) const timeLeft = locationSendDeadline - Date.now(); setTimeLeftBeforePenalty(Math.floor(timeLeft / 1000 / 60)); }, 1000); diff --git a/traque-front/components/team/map.jsx b/traque-front/components/team/map.jsx index e5b505e..b6a11a8 100644 --- a/traque-front/components/team/map.jsx +++ b/traque-front/components/team/map.jsx @@ -1,11 +1,13 @@ 'use client'; import React, { useEffect, useState } from 'react' -import { Circle, MapContainer, Marker, Popup, TileLayer, useMap } from 'react-leaflet' +import { Circle, LayerGroup, LayersControl, MapContainer, Marker, Popup, TileLayer, useMap } from 'react-leaflet' import 'leaflet-defaulticon-compatibility/dist/leaflet-defaulticon-compatibility.css' import "leaflet-defaulticon-compatibility"; import "leaflet/dist/leaflet.css"; import useGame from '@/hook/useGame'; import { useTeamContext } from '@/context/teamContext'; +import { useTilesColor } from '@/hook/mapDrawing'; +import { MapGridZoneSelector } from '../admin/mapZoneSelector'; const DEFAULT_ZOOM = 17; @@ -25,27 +27,11 @@ function MapPan(props) { return null; } -function LiveZone() { - const { zone } = useTeamContext(); - console.log('Zone', zone); - return zone && -} - -function ZoneExtremities() { - const { zoneExtremities } = useTeamContext(); - console.log('Zone extremities', zoneExtremities); - return zoneExtremities?.begin && zoneExtremities?.end && <> - {/* */} - - - -} - export function LiveMap({ ...props }) { + const {zone} = useTeamContext(); + const tilesColor = useTilesColor(zone); const { currentPosition, enemyPosition } = useGame(); - useEffect(() => { - console.log('Current position', currentPosition); - }, [currentPosition]); + return ( } - - + + + + {}} tileSize={16}/> + + + ) } diff --git a/traque-front/components/team/placementOverlay.jsx b/traque-front/components/team/placementOverlay.jsx index 63e6f68..c4bb5a6 100644 --- a/traque-front/components/team/placementOverlay.jsx +++ b/traque-front/components/team/placementOverlay.jsx @@ -1,6 +1,5 @@ import { useTeamConnexion } from "@/context/teamConnexionContext"; import useGame from "@/hook/useGame" -import Image from "next/image"; export default function PlacementOverlay() { const { name, ready } = useGame(); diff --git a/traque-front/context/adminContext.jsx b/traque-front/context/adminContext.jsx index 0c94189..d7f6511 100644 --- a/traque-front/context/adminContext.jsx +++ b/traque-front/context/adminContext.jsx @@ -4,15 +4,15 @@ import { useSocket } from "./socketContext"; import { useSocketListener } from "@/hook/useSocketListener"; import { useAdminConnexion } from "./adminConnexionContext"; import { GameState } from "@/util/gameState"; +import { TileNumber } from "@/components/util/map"; const adminContext = createContext(); function AdminProvider({ children }) { const [teams, setTeams] = useState([]); - const [zoneSettings, setZoneSettings] = useState(null) + const [zone, setZone] = useState([]) const [penaltySettings, setPenaltySettings] = useState(null); const [gameSettings, setGameSettings] = useState(null); - const [zone, setZone] = useState(null); const [zoneExtremities, setZoneExtremities] = useState(null); const { adminSocket } = useSocket(); const { loggedIn } = useAdminConnexion(); @@ -26,13 +26,12 @@ function AdminProvider({ children }) { //Bind listeners to update the team list and the game status on socket message useSocketListener(adminSocket, "teams", setTeams); - useSocketListener(adminSocket, "zone_settings", setZoneSettings); useSocketListener(adminSocket, "game_settings", setGameSettings); useSocketListener(adminSocket, "penalty_settings", setPenaltySettings); - useSocketListener(adminSocket, "zone", setZone); + useSocketListener(adminSocket, "zone", (zone) => setZone(zone.map(t => new TileNumber(t.x, t.y)))); useSocketListener(adminSocket, "new_zone", setZoneExtremities); - const value = useMemo(() => ({ zone, zoneExtremities, teams, zoneSettings, penaltySettings, gameSettings, gameState }), [zoneSettings, teams, gameState, zone, zoneExtremities, penaltySettings, gameSettings]); + const value = useMemo(() => ({ zone, zoneExtremities, teams, penaltySettings, gameSettings, gameState }), [teams, gameState, zone, zoneExtremities, penaltySettings, gameSettings]); return ( {children} diff --git a/traque-front/context/teamContext.jsx b/traque-front/context/teamContext.jsx index 5d66e9f..e22c13f 100644 --- a/traque-front/context/teamContext.jsx +++ b/traque-front/context/teamContext.jsx @@ -13,7 +13,6 @@ function TeamProvider({children}) { const [gameState, setGameState] = useState(GameState.SETUP); const [gameSettings, setGameSettings] = useState(null); const [zone, setZone] = useState(null); - const [zoneExtremities, setZoneExtremities] = useState(null); const measuredLocation = useLocation(5000); const {teamSocket} = useSocket(); const {loggedIn} = useTeamConnexion(); @@ -27,7 +26,6 @@ function TeamProvider({children}) { useSocketListener(teamSocket, "game_state", setGameState); useSocketListener(teamSocket, "zone", setZone); - useSocketListener(teamSocket, "new_zone", setZoneExtremities); useSocketListener(teamSocket, "game_settings", setGameSettings); @@ -40,7 +38,7 @@ function TeamProvider({children}) { } }, [loggedIn, measuredLocation]); - const value = useMemo(() => ({teamInfos, gameState, zone, zoneExtremities, gameSettings}), [gameSettings, teamInfos, gameState, zone, zoneExtremities]); + const value = useMemo(() => ({teamInfos, gameState, zone, gameSettings}), [gameSettings, teamInfos, gameState, zone]); return ( {children} diff --git a/traque-front/hook/mapDrawing.jsx b/traque-front/hook/mapDrawing.jsx index 0794849..17dbd97 100644 --- a/traque-front/hook/mapDrawing.jsx +++ b/traque-front/hook/mapDrawing.jsx @@ -38,7 +38,7 @@ export function useMapCircleDraw(area, setArea) { } } -export function useMapGrid(coloredTiles, tileSize) { +export function useMapGrid(tilesColor, tileSize) { const { layerContainer, map } = useLeafletContext(); const [grid, setGrid] = useState(null); @@ -79,15 +79,12 @@ export function useMapGrid(coloredTiles, tileSize) { const yMinPixel = Math.round(size.y * (y - minTileY) / (maxTileY - minTileY)); const yMaxPixel = Math.round(size.y * (y + 1 - minTileY) / (maxTileY - minTileY)); - // fill the rectangle with a color - // if(coloredTiles.some(t => t.equals(tile))) { - // console.log(coloredTiles, "filling tile " + tile.x + " " + tile.y) - // } else { - // console.log(coloredTiles, "not filling tile " + tile.x + " " + tile.y) - // } - ctx.fillStyle = this.coloredTiles && this.coloredTiles.some(t => t.equals(tile)) - ? 'rgba(0, 0, 255, 0.3)' - : 'rgba(255, 255, 255, 0)'; + if (this.tilesColor?.some(t => t.x == tile.x && t.y == tile.y)) { + ctx.fillStyle = this.tilesColor.find(t => t.x == tile.x && t.y == tile.y).color; + } + else { + ctx.fillStyle = 'rgba(255, 255, 255, 0)'; + } ctx.fillRect(xMinPixel, yMinPixel, xMaxPixel - xMinPixel, yMaxPixel - yMinPixel); } } @@ -105,8 +102,25 @@ export function useMapGrid(coloredTiles, tileSize) { useEffect(() => { if (grid) { - grid.coloredTiles = coloredTiles; + grid.tilesColor = tilesColor; grid.redraw(); } - }, [coloredTiles]); + }, [tilesColor,grid]); +} + +export function useTilesColor(zone) { + const [tilesColor, setTilesColor] = useState([]); + useEffect(() => { + if (zone) { + setTilesColor(zone.map(t => { + if(t.removeDate == null) { + return { x: t.x, y: t.y, color: 'rgba(0, 0, 255, 0.3'} + }else { + return { x: t.x, y: t.y, color: 'rgba(255, 255, 0, 0.3'} + } + + })); + } + }, [zone]); + return tilesColor; } \ No newline at end of file diff --git a/traque-front/hook/useAdmin.jsx b/traque-front/hook/useAdmin.jsx index 3441641..f031e1d 100644 --- a/traque-front/hook/useAdmin.jsx +++ b/traque-front/hook/useAdmin.jsx @@ -39,8 +39,12 @@ export default function useAdmin(){ adminSocket.emit("change_state", state); } - function changeZoneSettings(zone) { - adminSocket.emit("set_zone_settings", zone); + function initZone(zone) { + adminSocket.emit("set_zone", zone); + } + + function removeZone(zone, time) { + adminSocket.emit("remove_zone", zone, time); } function changePenaltySettings(penalties) { @@ -50,6 +54,6 @@ export default function useAdmin(){ function changeGameSettings(settings) { adminSocket.emit("set_game_settings", settings); } - return {...adminContext,changeGameSettings, changeZoneSettings, changePenaltySettings, pollTeams, getTeam, getTeamName, reorderTeams, addTeam, removeTeam, changeState, updateTeam }; + return {...adminContext,changeGameSettings, removeZone, initZone, changePenaltySettings, pollTeams, getTeam, getTeamName, reorderTeams, addTeam, removeTeam, changeState, updateTeam }; } \ No newline at end of file