mirror of
https://git.rezel.net/LudoTech/traque.git
synced 2026-02-09 02:10:18 +01:00
Map resize correction + player focus + cleaning
This commit is contained in:
@@ -33,11 +33,11 @@
|
|||||||
- [x] Ajouter timer du rétrécissement des zones.
|
- [x] Ajouter timer du rétrécissement des zones.
|
||||||
- [x] Pouvoir changer les paramètres du jeu pendant une partie.
|
- [x] Pouvoir changer les paramètres du jeu pendant une partie.
|
||||||
- [x] Implémenter les wireframes
|
- [x] Implémenter les wireframes
|
||||||
- [ ] Ajouter une région par défaut si pas de position
|
- [x] Ajouter une région par défaut si pas de position
|
||||||
- [ ] Pouvoir faire pause dans la partie
|
- [ ] Pouvoir faire pause dans la partie
|
||||||
- [ ] Voir les traces et évènements des teams
|
- [ ] Voir les traces et évènements des teams
|
||||||
- [ ] Voir l'incertitude de position des teams
|
- [ ] Voir l'incertitude de position des teams
|
||||||
- [ ] Focus une team cliquée
|
- [x] Focus une team cliquée
|
||||||
- [ ] Refaire les flèches de chasse sur la map
|
- [ ] 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é
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Marker, Tooltip, Polyline, Polygon, Circle } from "react-leaflet";
|
import { Marker, Tooltip, Polyline, Polygon, Circle } from "react-leaflet";
|
||||||
import "leaflet/dist/leaflet.css";
|
import "leaflet/dist/leaflet.css";
|
||||||
import { CustomMapContainer } from "@/components/map";
|
import { CustomMapContainer, MapEventListener, MapPan } from "@/components/map";
|
||||||
import useAdmin from "@/hook/useAdmin";
|
import useAdmin from "@/hook/useAdmin";
|
||||||
import { GameState } from "@/util/gameState";
|
import { GameState, ZoneTypes } from "@/util/types";
|
||||||
|
import { mapZooms } from "@/util/configurations";
|
||||||
|
|
||||||
const positionIcon = new L.Icon({
|
const positionIcon = new L.Icon({
|
||||||
iconUrl: '/icons/marker/blue.png',
|
iconUrl: '/icons/marker/blue.png',
|
||||||
@@ -13,12 +14,7 @@ const positionIcon = new L.Icon({
|
|||||||
shadowSize: [30, 30],
|
shadowSize: [30, 30],
|
||||||
});
|
});
|
||||||
|
|
||||||
const zoneTypes = {
|
export default function LiveMap({ selectedTeamId, isFocusing, setIsFocusing, mapStyle, showZones, showNames, showArrows}) {
|
||||||
circle: "circle",
|
|
||||||
polygon: "polygon"
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function LiveMap({mapStyle, showZones, showNames, showArrows}) {
|
|
||||||
const { zoneType, zoneExtremities, teams, nextZoneDate, getTeam, gameState } = useAdmin();
|
const { zoneType, zoneExtremities, teams, nextZoneDate, getTeam, gameState } = useAdmin();
|
||||||
const [timeLeftNextZone, setTimeLeftNextZone] = useState(null);
|
const [timeLeftNextZone, setTimeLeftNextZone] = useState(null);
|
||||||
|
|
||||||
@@ -58,14 +54,14 @@ export default function LiveMap({mapStyle, showZones, showNames, showArrows}) {
|
|||||||
if (!(showZones && gameState == GameState.PLAYING && zoneType)) return null;
|
if (!(showZones && gameState == GameState.PLAYING && zoneType)) return null;
|
||||||
|
|
||||||
switch (zoneType) {
|
switch (zoneType) {
|
||||||
case zoneTypes.circle:
|
case ZoneTypes.CIRCLE:
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{ zoneExtremities.begin && <Circle center={zoneExtremities.begin.center} radius={zoneExtremities.begin.radius} color="red" fillColor="red" />}
|
{ zoneExtremities.begin && <Circle center={zoneExtremities.begin.center} radius={zoneExtremities.begin.radius} color="red" fillColor="red" />}
|
||||||
{ zoneExtremities.end && <Circle center={zoneExtremities.end.center} radius={zoneExtremities.end.radius} color="green" fillColor="green" />}
|
{ zoneExtremities.end && <Circle center={zoneExtremities.end.center} radius={zoneExtremities.end.radius} color="green" fillColor="green" />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
case zoneTypes.polygon:
|
case ZoneTypes.POLYGON:
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{ zoneExtremities.begin && <Polygon positions={zoneExtremities.begin.points} pathOptions={{ color: 'red', fillColor: 'red', fillOpacity: '0.1', weight: 3 }} />}
|
{ zoneExtremities.begin && <Polygon positions={zoneExtremities.begin.points} pathOptions={{ color: 'red', fillColor: 'red', fillOpacity: '0.1', weight: 3 }} />}
|
||||||
@@ -81,14 +77,16 @@ export default function LiveMap({mapStyle, showZones, showNames, showArrows}) {
|
|||||||
<div className='h-full w-full flex flex-col'>
|
<div className='h-full w-full flex flex-col'>
|
||||||
{gameState == GameState.PLAYING && <p>{`Next zone in : ${formatTime(timeLeftNextZone)}`}</p>}
|
{gameState == GameState.PLAYING && <p>{`Next zone in : ${formatTime(timeLeftNextZone)}`}</p>}
|
||||||
<CustomMapContainer mapStyle={mapStyle}>
|
<CustomMapContainer mapStyle={mapStyle}>
|
||||||
|
{isFocusing && <MapPan center={getTeam(selectedTeamId)?.currentLocation} zoom={mapZooms.high} animate />}
|
||||||
|
<MapEventListener onDragStart={() => setIsFocusing(false)}/>
|
||||||
<Zones/>
|
<Zones/>
|
||||||
{teams.map((team) => team.currentLocation && !team.captured &&
|
{teams.map((team) => team.currentLocation && !team.captured &&
|
||||||
<div>
|
<>
|
||||||
<Marker key={team.id} position={team.currentLocation} icon={positionIcon}>
|
<Marker key={team.id} position={team.currentLocation} icon={positionIcon}>
|
||||||
{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>}
|
||||||
</Marker>
|
</Marker>
|
||||||
{showArrows && <Arrow key={team.id} pos1={team.currentLocation} pos2={getTeam(team.chased).currentLocation}/>}
|
{showArrows && <Arrow key={team.id} pos1={team.currentLocation} pos2={getTeam(team.chased).currentLocation}/>}
|
||||||
</div>
|
</>
|
||||||
)}
|
)}
|
||||||
</CustomMapContainer>
|
</CustomMapContainer>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { env } from 'next-runtime-env';
|
import { env } from 'next-runtime-env';
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import useAdmin from "@/hook/useAdmin";
|
import useAdmin from "@/hook/useAdmin";
|
||||||
import { GameState } from '@/util/gameState';
|
import { getStatus } from '@/util/functions';
|
||||||
|
|
||||||
function DotLine({ label, value }) {
|
function DotLine({ label, value }) {
|
||||||
return (
|
return (
|
||||||
@@ -27,28 +27,6 @@ function IconValue({ color, icon, value }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const TEAM_STATUS = {
|
|
||||||
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" },
|
|
||||||
};
|
|
||||||
|
|
||||||
function getStatus(team, gamestate) {
|
|
||||||
switch (gamestate) {
|
|
||||||
case GameState.SETUP:
|
|
||||||
return TEAM_STATUS.waiting;
|
|
||||||
case GameState.PLACEMENT:
|
|
||||||
return team.ready ? TEAM_STATUS.ready : TEAM_STATUS.notready;
|
|
||||||
case GameState.PLAYING:
|
|
||||||
return team.captured ? TEAM_STATUS.captured : team.outofzone ? TEAM_STATUS.outofzone : TEAM_STATUS.playing;
|
|
||||||
case GameState.FINISHED:
|
|
||||||
return team.captured ? TEAM_STATUS.captured : TEAM_STATUS.playing;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function TeamSidePanel({ selectedTeamId, onClose }) {
|
export default function TeamSidePanel({ selectedTeamId, onClose }) {
|
||||||
const { getTeam, startDate, gameState } = useAdmin();
|
const { getTeam, startDate, gameState } = useAdmin();
|
||||||
const [imgSrc, setImgSrc] = useState("");
|
const [imgSrc, setImgSrc] = useState("");
|
||||||
|
|||||||
@@ -1,28 +1,6 @@
|
|||||||
import { List } from '@/components/list';
|
import { List } from '@/components/list';
|
||||||
import useAdmin from '@/hook/useAdmin';
|
import useAdmin from '@/hook/useAdmin';
|
||||||
import { GameState } from '@/util/gameState';
|
import { getStatus } from '@/util/functions';
|
||||||
|
|
||||||
const TEAM_STATUS = {
|
|
||||||
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" },
|
|
||||||
};
|
|
||||||
|
|
||||||
function getStatus(team, gamestate) {
|
|
||||||
switch (gamestate) {
|
|
||||||
case GameState.SETUP:
|
|
||||||
return TEAM_STATUS.waiting;
|
|
||||||
case GameState.PLACEMENT:
|
|
||||||
return team.ready ? TEAM_STATUS.ready : TEAM_STATUS.notready;
|
|
||||||
case GameState.PLAYING:
|
|
||||||
return team.captured ? TEAM_STATUS.captured : team.outofzone ? TEAM_STATUS.outofzone : TEAM_STATUS.playing;
|
|
||||||
case GameState.FINISHED:
|
|
||||||
return team.captured ? TEAM_STATUS.captured : TEAM_STATUS.playing;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function TeamViewerItem({ team, itemSelected, onSelected }) {
|
function TeamViewerItem({ team, itemSelected, onSelected }) {
|
||||||
const { gameState } = useAdmin();
|
const { gameState } = useAdmin();
|
||||||
|
|||||||
@@ -4,16 +4,18 @@ import { TextInput } from "@/components/input";
|
|||||||
|
|
||||||
export default function LoginForm({ onSubmit, title, placeholder, buttonText}) {
|
export default function LoginForm({ onSubmit, title, placeholder, buttonText}) {
|
||||||
const [value, setValue] = useState("");
|
const [value, setValue] = useState("");
|
||||||
|
|
||||||
function handleSubmit(e) {
|
function handleSubmit(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setValue("");
|
setValue("");
|
||||||
onSubmit(value);
|
onSubmit(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className="bg-white shadow-md max-w mx-auto p-5 mx-10 flex flex-col space-y-4" onSubmit={handleSubmit}>
|
<form className="bg-white shadow-md max-w mx-auto p-5 mx-10 flex flex-col space-y-4" onSubmit={handleSubmit}>
|
||||||
<h1 className="text-2xl font-bold text-center text-gray-700">{title}</h1>
|
<h1 className="text-2xl font-bold text-center text-gray-700">{title}</h1>
|
||||||
<TextInput inputMode="numeric" placeholder={placeholder} value={value} onChange={(e) => setValue(e.target.value)} name="team-id"/>
|
<TextInput inputMode="numeric" placeholder={placeholder} value={value} onChange={(e) => setValue(e.target.value)} name="team-id"/>
|
||||||
<BlueButton type="submit">{buttonText}</BlueButton>
|
<BlueButton type="submit">{buttonText}</BlueButton>
|
||||||
</form>
|
</form>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import React, { 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 { Section } from "@/components/section";
|
import { Section } from "@/components/section";
|
||||||
import { useAdminConnexion } from "@/context/adminConnexionContext";
|
import { useAdminConnexion } from "@/context/adminConnexionContext";
|
||||||
import useAdmin from "@/hook/useAdmin";
|
import useAdmin from "@/hook/useAdmin";
|
||||||
import { GameState } from "@/util/gameState";
|
import { GameState } from "@/util/types";
|
||||||
|
import { mapStyles } from '@/util/configurations';
|
||||||
import TeamSidePanel from "./components/teamSidePanel";
|
import TeamSidePanel from "./components/teamSidePanel";
|
||||||
import TeamViewer from './components/teamViewer';
|
import TeamViewer from './components/teamViewer';
|
||||||
import { MapButton, ControlButton } from './components/buttons';
|
import { MapButton, ControlButton } from './components/buttons';
|
||||||
@@ -13,33 +14,25 @@ import { MapButton, ControlButton } from './components/buttons';
|
|||||||
// Imported at runtime and not at compile time
|
// Imported at runtime and not at compile time
|
||||||
const LiveMap = dynamic(() => import('./components/liveMap'), { ssr: false });
|
const LiveMap = dynamic(() => import('./components/liveMap'), { ssr: false });
|
||||||
|
|
||||||
const mapStyles = {
|
|
||||||
default: {
|
|
||||||
url: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
|
|
||||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
|
|
||||||
},
|
|
||||||
satellite: {
|
|
||||||
url: "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}",
|
|
||||||
attribution: 'Tiles © Esri'
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function AdminPage() {
|
export default function AdminPage() {
|
||||||
const { useProtect } = useAdminConnexion();
|
const { useProtect } = useAdminConnexion();
|
||||||
const [selectedTeamId, setSelectedTeamId] = useState(null);
|
const [selectedTeamId, setSelectedTeamId] = useState(null);
|
||||||
const { changeState } = useAdmin();
|
const { changeState, getTeam } = useAdmin();
|
||||||
const [mapStyle, setMapStyle] = useState(mapStyles.default);
|
const [mapStyle, setMapStyle] = useState(mapStyles.default);
|
||||||
const [showZones, setShowZones] = useState(true);
|
const [showZones, setShowZones] = useState(true);
|
||||||
const [showNames, setShowNames] = useState(true);
|
const [showNames, setShowNames] = useState(true);
|
||||||
const [showArrows, setShowArrows] = useState(false);
|
const [showArrows, setShowArrows] = useState(false);
|
||||||
|
const [isFocusing, setIsFocusing] = useState(true);
|
||||||
|
|
||||||
useProtect();
|
useProtect();
|
||||||
|
|
||||||
function onSelected(id) {
|
function onSelected(id) {
|
||||||
if (selectedTeamId === id) {
|
if (selectedTeamId == id && (!getTeam(id)?.currentLocation || isFocusing)) {
|
||||||
setSelectedTeamId(null);
|
setSelectedTeamId(null);
|
||||||
|
setIsFocusing(false);
|
||||||
} else {
|
} else {
|
||||||
setSelectedTeamId(id);
|
setSelectedTeamId(id);
|
||||||
|
setIsFocusing(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,7 +75,15 @@ export default function AdminPage() {
|
|||||||
<div className='grow flex-1 flex flex-col bg-white p-3 gap-3 shadow-2xl'>
|
<div className='grow flex-1 flex flex-col bg-white p-3 gap-3 shadow-2xl'>
|
||||||
<div className="flex-1 flex flex-row gap-3">
|
<div className="flex-1 flex flex-row gap-3">
|
||||||
<div className="flex-1 h-full">
|
<div className="flex-1 h-full">
|
||||||
<LiveMap mapStyle={mapStyle} showZones={showZones} showNames={showNames} showArrows={showArrows}/>
|
<LiveMap
|
||||||
|
selectedTeamId={selectedTeamId}
|
||||||
|
isFocusing={isFocusing}
|
||||||
|
setIsFocusing={setIsFocusing}
|
||||||
|
mapStyle={mapStyle}
|
||||||
|
showZones={showZones}
|
||||||
|
showNames={showNames}
|
||||||
|
showArrows={showArrows}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{selectedTeamId &&
|
{selectedTeamId &&
|
||||||
<div className="w-3/12 h-full">
|
<div className="w-3/12 h-full">
|
||||||
|
|||||||
@@ -10,18 +10,12 @@ 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 useLocalVariable from "@/hook/useLocalVariable";
|
||||||
|
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 PolygonZoneSelector = dynamic(() => import('./components/polygonZoneSelector'), { ssr: false });
|
|
||||||
const CircleZoneSelector = dynamic(() => import('./components/circleZoneSelector'), { ssr: false });
|
const CircleZoneSelector = dynamic(() => import('./components/circleZoneSelector'), { ssr: false });
|
||||||
|
const PolygonZoneSelector = dynamic(() => import('./components/polygonZoneSelector'), { ssr: false });
|
||||||
const zoneTypes = {
|
|
||||||
circle: "circle",
|
|
||||||
polygon: "polygon"
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultCircleSettings = {type: zoneTypes.circle, min: null, max: null, reductionCount: 4, duration: 10}
|
|
||||||
const defaultPolygonSettings = {type: zoneTypes.polygon, polygons: []}
|
|
||||||
|
|
||||||
export default function ConfigurationPage() {
|
export default function ConfigurationPage() {
|
||||||
const { useProtect } = useAdminConnexion();
|
const { useProtect } = useAdminConnexion();
|
||||||
@@ -34,10 +28,10 @@ export default function ConfigurationPage() {
|
|||||||
|
|
||||||
function modifyLocalZoneSettings(key, value) {
|
function modifyLocalZoneSettings(key, value) {
|
||||||
setLocalZoneSettings(prev => ({...prev, [key]: value}));
|
setLocalZoneSettings(prev => ({...prev, [key]: value}));
|
||||||
};
|
}
|
||||||
|
|
||||||
function handleChangeZoneType() {
|
function handleChangeZoneType() {
|
||||||
setLocalZoneSettings(localZoneSettings.type == zoneTypes.circle ? defaultPolygonSettings : defaultCircleSettings)
|
setLocalZoneSettings(localZoneSettings.type == ZoneTypes.CIRCLE ? defaultZoneSettings.polygon : defaultZoneSettings.circle)
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleTeamSubmit(e) {
|
function handleTeamSubmit(e) {
|
||||||
@@ -83,10 +77,10 @@ export default function ConfigurationPage() {
|
|||||||
{localZoneSettings && <BlueButton onClick={handleChangeZoneType}>Change zone type</BlueButton>}
|
{localZoneSettings && <BlueButton onClick={handleChangeZoneType}>Change zone type</BlueButton>}
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full flex-1">
|
<div className="w-full flex-1">
|
||||||
{localZoneSettings && localZoneSettings.type == zoneTypes.circle &&
|
{localZoneSettings && localZoneSettings.type == ZoneTypes.CIRCLE &&
|
||||||
<CircleZoneSelector zoneSettings={localZoneSettings} modifyZoneSettings={modifyLocalZoneSettings} applyZoneSettings={applyLocalZoneSettings}/>
|
<CircleZoneSelector zoneSettings={localZoneSettings} modifyZoneSettings={modifyLocalZoneSettings} applyZoneSettings={applyLocalZoneSettings}/>
|
||||||
}
|
}
|
||||||
{localZoneSettings && localZoneSettings.type == zoneTypes.polygon &&
|
{localZoneSettings && localZoneSettings.type == ZoneTypes.POLYGON &&
|
||||||
<PolygonZoneSelector zoneSettings={localZoneSettings} modifyZoneSettings={modifyLocalZoneSettings} applyZoneSettings={applyLocalZoneSettings}/>
|
<PolygonZoneSelector zoneSettings={localZoneSettings} modifyZoneSettings={modifyLocalZoneSettings} applyZoneSettings={applyLocalZoneSettings}/>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,33 +1,21 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { MapContainer, TileLayer, useMap } from "react-leaflet";
|
import { MapContainer, TileLayer, useMap } from "react-leaflet";
|
||||||
import "leaflet/dist/leaflet.css";
|
import "leaflet/dist/leaflet.css";
|
||||||
|
import { mapLocations, mapZooms, mapStyles } from "@/util/configurations";
|
||||||
|
|
||||||
const DEFAULT_ZOOM = 14;
|
export function MapPan({center, zoom, animate=false}) {
|
||||||
|
|
||||||
const mapStyles = {
|
|
||||||
default: {
|
|
||||||
url: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
|
|
||||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
|
|
||||||
},
|
|
||||||
satellite: {
|
|
||||||
url: "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}",
|
|
||||||
attribution: 'Tiles © Esri'
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
export function MapPan({center, zoom}) {
|
|
||||||
const map = useMap();
|
const map = useMap();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (center, zoom) {
|
if (center && zoom) {
|
||||||
map.flyTo(center, zoom, { animate: false });
|
map.flyTo(center, zoom, { animate: animate });
|
||||||
}
|
}
|
||||||
}, [center, zoom]);
|
}, [center, zoom]);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MapEventListener({ onLeftClick, onRightClick, onMouseMove }) {
|
export function MapEventListener({ onLeftClick, onRightClick, onMouseMove, onDragStart }) {
|
||||||
const map = useMap();
|
const map = useMap();
|
||||||
|
|
||||||
// Handle the mouse click left
|
// Handle the mouse click left
|
||||||
@@ -93,6 +81,17 @@ export function MapEventListener({ onLeftClick, onRightClick, onMouseMove }) {
|
|||||||
map.off('mousemove', onMouseMove);
|
map.off('mousemove', onMouseMove);
|
||||||
}
|
}
|
||||||
}, [onMouseMove]);
|
}, [onMouseMove]);
|
||||||
|
|
||||||
|
// Handle the drag start
|
||||||
|
useEffect(() => {
|
||||||
|
if (!onDragStart) return;
|
||||||
|
|
||||||
|
map.on('dragstart', onDragStart);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
map.off('dragstart', onDragStart);
|
||||||
|
}
|
||||||
|
}, [onDragStart]);
|
||||||
|
|
||||||
// Prevent right click context menu
|
// Prevent right click context menu
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -105,9 +104,23 @@ export function MapEventListener({ onLeftClick, onRightClick, onMouseMove }) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function MapResizeWatcher() {
|
||||||
|
const map = useMap();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const observer = new ResizeObserver(() => {
|
||||||
|
map.invalidateSize();
|
||||||
|
});
|
||||||
|
observer.observe(map.getContainer());
|
||||||
|
|
||||||
|
return () => observer.disconnect();
|
||||||
|
}, [map]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
export function CustomMapContainer({mapStyle, children}) {
|
export function CustomMapContainer({mapStyle, children}) {
|
||||||
const [location, setLocation] = useState(null);
|
const [location, setLocation] = useState(null);
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!navigator.geolocation) {
|
if (!navigator.geolocation) {
|
||||||
@@ -118,7 +131,6 @@ export function CustomMapContainer({mapStyle, children}) {
|
|||||||
navigator.geolocation.getCurrentPosition(
|
navigator.geolocation.getCurrentPosition(
|
||||||
(pos) => {
|
(pos) => {
|
||||||
setLocation([pos.coords.latitude, pos.coords.longitude]);
|
setLocation([pos.coords.latitude, pos.coords.longitude]);
|
||||||
setLoading(false);
|
|
||||||
},
|
},
|
||||||
(err) => console.log("Error :", err),
|
(err) => console.log("Error :", err),
|
||||||
{
|
{
|
||||||
@@ -129,13 +141,11 @@ export function CustomMapContainer({mapStyle, children}) {
|
|||||||
);
|
);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
return <div className="w-full h-full"/>
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MapContainer className='w-full h-full' center={location} zoom={DEFAULT_ZOOM} scrollWheelZoom={true}>
|
<MapContainer className='w-full h-full' center={mapLocations.paris} zoom={mapZooms.low} scrollWheelZoom={true}>
|
||||||
<TileLayer url={(mapStyle || mapStyles.default).url} attribution={(mapStyle || mapStyles.default).attribution}/>
|
<TileLayer url={(mapStyle || mapStyles.default).url} attribution={(mapStyle || mapStyles.default).attribution}/>
|
||||||
|
<MapPan center={location} zoom={mapZooms.high}/>
|
||||||
|
<MapResizeWatcher/>
|
||||||
{children}
|
{children}
|
||||||
</MapContainer>
|
</MapContainer>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { createContext, useContext, useMemo, useState } from "react";
|
import { createContext, useContext, useMemo, useState } from "react";
|
||||||
import { useSocket } from "./socketContext";
|
import { useSocket } from "./socketContext";
|
||||||
import useSocketListener from "@/hook/useSocketListener";
|
import useSocketListener from "@/hook/useSocketListener";
|
||||||
import { GameState } from "@/util/gameState";
|
import { GameState } from "@/util/types";
|
||||||
|
|
||||||
const adminContext = createContext();
|
const adminContext = createContext();
|
||||||
|
|
||||||
|
|||||||
35
traque-front/util/configurations.js
Normal file
35
traque-front/util/configurations.js
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { ZoneTypes } from "./types";
|
||||||
|
|
||||||
|
export const mapLocations = {
|
||||||
|
paris: [48.86, 2.33]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const mapZooms = {
|
||||||
|
low: 4,
|
||||||
|
high: 15,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const mapStyles = {
|
||||||
|
default: {
|
||||||
|
url: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
|
||||||
|
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
|
||||||
|
},
|
||||||
|
satellite: {
|
||||||
|
url: "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}",
|
||||||
|
attribution: 'Tiles © Esri'
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const defaultZoneSettings = {
|
||||||
|
circle: {type: ZoneTypes.CIRCLE, min: null, max: null, reductionCount: 4, duration: 10},
|
||||||
|
polygon: {type: ZoneTypes.POLYGON, polygons: []}
|
||||||
|
}
|
||||||
|
|
||||||
|
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" },
|
||||||
|
}
|
||||||
15
traque-front/util/functions.js
Normal file
15
traque-front/util/functions.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { GameState } from './types';
|
||||||
|
import { teamStatus } from './configurations';
|
||||||
|
|
||||||
|
export function getStatus(team, gamestate) {
|
||||||
|
switch (gamestate) {
|
||||||
|
case GameState.SETUP:
|
||||||
|
return teamStatus.waiting;
|
||||||
|
case GameState.PLACEMENT:
|
||||||
|
return team.ready ? teamStatus.ready : teamStatus.notready;
|
||||||
|
case GameState.PLAYING:
|
||||||
|
return team.captured ? teamStatus.captured : team.outofzone ? teamStatus.outofzone : teamStatus.playing;
|
||||||
|
case GameState.FINISHED:
|
||||||
|
return team.captured ? teamStatus.captured : teamStatus.playing;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,4 +3,9 @@ export const GameState = {
|
|||||||
PLACEMENT: "placement",
|
PLACEMENT: "placement",
|
||||||
PLAYING: "playing",
|
PLAYING: "playing",
|
||||||
FINISHED: "finished"
|
FINISHED: "finished"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const ZoneTypes = {
|
||||||
|
CIRCLE: "circle",
|
||||||
|
POLYGON: "polygon"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user