added live map

This commit is contained in:
Quentin Roussel
2024-06-08 10:10:48 +02:00
parent ba846acc0c
commit cd2bba2aed
7 changed files with 113 additions and 34 deletions

View File

@@ -11,6 +11,7 @@ export default function AdminLayout({ children}) {
<ul className='flex' > <ul className='flex' >
<Link href="/admin"><li className="p-5 bg-gray-800 hover:bg-gray-600 transition-all cursor-pointer h-full">Admin</li></Link> <Link href="/admin"><li className="p-5 bg-gray-800 hover:bg-gray-600 transition-all cursor-pointer h-full">Admin</li></Link>
<Link href="/admin/teams"><li className="p-5 bg-gray-800 hover:bg-gray-600 transition-all cursor-pointer h-full">Teams</li></Link> <Link href="/admin/teams"><li className="p-5 bg-gray-800 hover:bg-gray-600 transition-all cursor-pointer h-full">Teams</li></Link>
<Link href="/admin/map"><li className="p-5 bg-gray-800 hover:bg-gray-600 transition-all cursor-pointer h-full">Map</li></Link>
</ul> </ul>
</div> </div>
<div className="h-full overflow-y-scroll"> <div className="h-full overflow-y-scroll">

View File

@@ -0,0 +1,9 @@
"use client";
import dynamic from 'next/dynamic';
const LiveMap = dynamic(() => import('@/components/admin/maps').then((mod) => mod.LiveMap), {
ssr: false
});
export default function LiveMapPage() {
return <LiveMap />
}

View File

@@ -11,7 +11,7 @@ import dynamic from "next/dynamic";
const ZoneSelector = dynamic(() => import('@/components/admin/zoneSelector').then((mod) => mod.ZoneSelector), { const ZoneSelector = dynamic(() => import('@/components/admin/zoneSelector').then((mod) => mod.ZoneSelector), {
ssr: false ssr: false
}); });
const LiveMap = dynamic(() => import('@/components/admin/maps').then((mod) => mod.ZoneEditor), { const ZoneEditor = dynamic(() => import('@/components/admin/maps').then((mod) => mod.ZoneEditor), {
ssr: false ssr: false
}); });
export default function AdminPage() { export default function AdminPage() {
@@ -36,7 +36,7 @@ export default function AdminPage() {
{(gameState == GameState.SETUP || gameState == GameState.PLACEMENT) && <ZoneSelector />} {(gameState == GameState.SETUP || gameState == GameState.PLACEMENT) && <ZoneSelector />}
{(gameState == GameState.SETUP || gameState == GameState.PLACEMENT) && <PenaltySettings />} {(gameState == GameState.SETUP || gameState == GameState.PLACEMENT) && <PenaltySettings />}
{gameState == GameState.PLAYING && <div className='grow flex-1 row-span-2 bg-white p-10 flex shadow-2xl'> {gameState == GameState.PLAYING && <div className='grow flex-1 row-span-2 bg-white p-10 flex shadow-2xl'>
<LiveMap /> <ZoneEditor />
</div>} </div>}
</div> </div>
) )

View File

@@ -107,7 +107,7 @@ export function ZoneEditor() {
const { zone, teams, getTeamName, removeZone } = useAdmin(); const { zone, teams, getTeamName, removeZone } = useAdmin();
const [zonesToDelete, setZonesToDelete] = useState([]); const [zonesToDelete, setZonesToDelete] = useState([]);
const [tilesColor, setTilesColor] = useState([]); const [tilesColor, setTilesColor] = useState([]);
const [timeBeforeDeletion, setTimeBeforeDeletion] = useState(0); const [timeBeforeDeletion, setTimeBeforeDeletion] = useState(null);
function handleClickTile(tile) { function handleClickTile(tile) {
if (!zone.some(t => t.x === tile.x && t.y === tile.y)) return; if (!zone.some(t => t.x === tile.x && t.y === tile.y)) return;
@@ -127,6 +127,7 @@ export function ZoneEditor() {
...zone ...zone
.filter(t => !zonesToDelete.some(t2 => t.x == t2.x && t.y == t2.y)) .filter(t => !zonesToDelete.some(t2 => t.x == t2.x && t.y == t2.y))
.map(t => { .map(t => {
console.log(t)
if (t.removeDate == null) { if (t.removeDate == null) {
return { x: t.x, y: t.y, color: 'rgba(0, 0, 255, 0.5)' } return { x: t.x, y: t.y, color: 'rgba(0, 0, 255, 0.5)' }
} else { } else {
@@ -137,44 +138,107 @@ export function ZoneEditor() {
}, [zone, zonesToDelete]); }, [zone, zonesToDelete]);
const handleSubmit = (e) => { const handleSubmit = (e) => {
if (timeBeforeDeletion == null) {
return;
}
e.preventDefault(); e.preventDefault();
removeZone(zonesToDelete, timeBeforeDeletion); removeZone(zonesToDelete, timeBeforeDeletion);
setZonesToDelete([]); setZonesToDelete([]);
setTimeBeforeDeletion(0); setTimeBeforeDeletion(null);
} }
return ( return (
<div className="flex flex-col w-full"> <div className="flex flex-col w-full">
<MapContainer className='min-h-full w-full ' center={location} zoom={DEFAULT_ZOOM} scrollWheelZoom={true}> <div className="h-full">
<TileLayer <MapContainer className='min-h-full w-full ' center={location} zoom={DEFAULT_ZOOM} scrollWheelZoom={true}>
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' <TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
/> url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
<MapPan center={location} zoom={DEFAULT_ZOOM} /> />
{teams.map((team) => team.currentLocation && !team.captured && <Marker key={team.id} position={team.currentLocation} icon={new L.Icon({ <MapPan center={location} zoom={DEFAULT_ZOOM} />
iconUrl: '/icons/location.png', {teams.map((team) => team.currentLocation && !team.captured && <Marker key={team.id} position={team.currentLocation} icon={new L.Icon({
iconSize: [41, 41], iconUrl: '/icons/location.png',
iconAnchor: [12, 41], iconSize: [41, 41],
popupAnchor: [1, -34], iconAnchor: [12, 41],
shadowSize: [41, 41] popupAnchor: [1, -34],
})}> shadowSize: [41, 41]
<Popup> })}>
<strong className="text-lg">{team.name}</strong> <Popup>
<p className="text-md">Chasing : {getTeamName(team.chasing)}</p> <strong className="text-lg">{team.name}</strong>
<p className="text-md">Chased by : {getTeamName(team.chased)}</p> <p className="text-md">Chasing : {getTeamName(team.chasing)}</p>
</Popup> <p className="text-md">Chased by : {getTeamName(team.chased)}</p>
</Marker>)} </Popup>
<LayersControl> </Marker>)}
<LayersControl.Overlay name="Play area" checked={true}> <LayersControl>
<LayerGroup> <LayersControl.Overlay name="Play area" checked={true}>
<MapGridZoneSelector tilesColor={tilesColor} onClickTile={handleClickTile} tileSize={16} /> <LayerGroup>
</LayerGroup> <MapGridZoneSelector tilesColor={tilesColor} onClickTile={handleClickTile} tileSize={16} />
</LayersControl.Overlay> </LayerGroup>
</LayersControl> </LayersControl.Overlay>
</MapContainer> </LayersControl>
<TextInput placeholder="Time before deletion" value={timeBeforeDeletion} onChange={(e) => setTimeBeforeDeletion(Number(e.target.value))}></TextInput> </MapContainer>
<GreenButton onClick={handleSubmit}>Delete selected zones</GreenButton> </div>
<div className="flex flex-row h-20">
<TextInput className="w-4/5" placeholder="Time before deletion" value={timeBeforeDeletion} onChange={(e) => setTimeBeforeDeletion(Number(e.target.value))}></TextInput>
<GreenButton className="w-1/5" onClick={handleSubmit}>Delete selected zones</GreenButton>
</div>
</div> </div>
) )
}
export function LiveMap({ ...props }) {
const location = useLocation(Infinity);
const { zone, teams, getTeamName } = useAdmin();
const tilesColor = useTilesColor(zone);
return (
<MapContainer {...props} className='min-h-full w-full ' center={[0, 0]} zoom={0} scrollWheelZoom={true}>
<TileLayer
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<MapPan center={location} zoom={DEFAULT_ZOOM} />
<LayersControl>
<LayersControl.Overlay name="Play area" checked={true}>
<LayerGroup>
<MapGridZoneSelector tilesColor={tilesColor} onClickTile={() => { }} tileSize={16} />
</LayerGroup>
</LayersControl.Overlay>
<LayersControl.Overlay name="Players live position" checked={true}>
<LayerGroup>
{teams.map((team) => team.currentLocation && !team.captured && <Marker key={team.id} position={team.currentLocation} icon={new L.Icon({
iconUrl: '/icons/location.png',
iconSize: [41, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
shadowSize: [41, 41]
})}>
<Popup>
<strong className="text-lg">{team.name}</strong>
<p className="text-md">Chasing : {getTeamName(team.chasing)}</p>
<p className="text-md">Chased by : {getTeamName(team.chased)}</p>
</Popup>
</Marker>)}
</LayerGroup>
</LayersControl.Overlay>
<LayersControl.Overlay name="Players last sent position" checked={false}>
<LayerGroup>
{teams.map((team) => team.currentLocation && !team.captured && <Marker key={team.id} position={team.lastSentLocation} icon={new L.Icon({
iconUrl: '/icons/clock.png',
iconSize: [41, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
shadowSize: [41, 41]
})}>
<Popup>
<strong className="text-lg">Last position of {team.name}</strong>
</Popup>
</Marker>)}
</LayerGroup>
</LayersControl.Overlay>
</LayersControl>
</MapContainer>
)
} }

View File

@@ -2,6 +2,7 @@ export class TileNumber {
constructor(x, y) { constructor(x, y) {
this.x = x; this.x = x;
this.y = y; this.y = y;
this.removeDate = null;
} }
equals(other) { equals(other) {
return this.x === other.x && this.y === other.y; return this.x === other.x && this.y === other.y;

View File

@@ -28,7 +28,11 @@ function AdminProvider({ children }) {
useSocketListener(adminSocket, "teams", setTeams); useSocketListener(adminSocket, "teams", setTeams);
useSocketListener(adminSocket, "game_settings", setGameSettings); useSocketListener(adminSocket, "game_settings", setGameSettings);
useSocketListener(adminSocket, "penalty_settings", setPenaltySettings); useSocketListener(adminSocket, "penalty_settings", setPenaltySettings);
useSocketListener(adminSocket, "zone", (zone) => setZone(zone.map(t => new TileNumber(t.x, t.y)))); useSocketListener(adminSocket, "zone", (zone) => setZone(zone.map(t => {
let tile = new TileNumber(t.x, t.y);
tile.removeDate = t.removeDate;
return tile;
})));
useSocketListener(adminSocket, "new_zone", setZoneExtremities); useSocketListener(adminSocket, "new_zone", setZoneExtremities);
const value = useMemo(() => ({ zone, zoneExtremities, teams, penaltySettings, gameSettings, gameState }), [teams, gameState, zone, zoneExtremities, penaltySettings, gameSettings]); const value = useMemo(() => ({ zone, zoneExtremities, teams, penaltySettings, gameSettings, gameState }), [teams, gameState, zone, zoneExtremities, penaltySettings, gameSettings]);

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB