From 3e06324c1ce3099cfa93aa17fe5611c590f0c6c0 Mon Sep 17 00:00:00 2001 From: Cannon Lock Date: Mon, 27 Jan 2025 14:51:23 -0600 Subject: [PATCH] Add Cache and Server Cards to Map - Add more data onClick to the map server components --- web_ui/frontend/app/director/map/page.tsx | 34 +++--- .../frontend/components/InformationSpan.tsx | 14 +-- web_ui/frontend/components/Map/PopOutCard.tsx | 65 ++++++++++ web_ui/frontend/components/Map/ServerCard.tsx | 43 +++++++ web_ui/frontend/components/Map/ServerMap.tsx | 114 ++++++++++++------ web_ui/frontend/components/Map/index.ts | 2 + 6 files changed, 211 insertions(+), 61 deletions(-) create mode 100644 web_ui/frontend/components/Map/PopOutCard.tsx create mode 100644 web_ui/frontend/components/Map/ServerCard.tsx diff --git a/web_ui/frontend/app/director/map/page.tsx b/web_ui/frontend/app/director/map/page.tsx index 3f7db037f..a6129b6b2 100644 --- a/web_ui/frontend/app/director/map/page.tsx +++ b/web_ui/frontend/app/director/map/page.tsx @@ -22,26 +22,24 @@ import { Box } from '@mui/material'; import useSWR from 'swr'; import { Server } from '@/index'; import { ServerMap } from '@/components/Map'; +import { useContext } from 'react'; +import { AlertDispatchContext } from '@/components/AlertProvider'; +import { ServerGeneral } from '@/types'; +import { alertOnError } from '@/helpers/util'; +import { getDirectorServers } from '@/helpers/get'; export default function Page() { - const { data } = useSWR('getServers', getServers); + const dispatch = useContext(AlertDispatchContext); - return ( - - - + const { data } = useSWR( + 'getDirectorServers', + async () => + await alertOnError( + getDirectorServers, + 'Failed to fetch servers', + dispatch + ) ); -} - -const getServers = async (): Promise => { - const url = new URL('/api/v1.0/director_ui/servers', window.location.origin); - let response = await fetch(url); - if (response.ok) { - const responseData: Server[] = await response.json(); - responseData.sort((a, b) => a.name.localeCompare(b.name)); - return responseData; - } - - throw new Error('Failed to fetch servers'); -}; + return ; +} diff --git a/web_ui/frontend/components/InformationSpan.tsx b/web_ui/frontend/components/InformationSpan.tsx index 46ddd89b4..9dce8bb2a 100644 --- a/web_ui/frontend/components/InformationSpan.tsx +++ b/web_ui/frontend/components/InformationSpan.tsx @@ -47,15 +47,13 @@ export const InformationSpan = ({ diff --git a/web_ui/frontend/components/Map/PopOutCard.tsx b/web_ui/frontend/components/Map/PopOutCard.tsx new file mode 100644 index 000000000..80a080dfb --- /dev/null +++ b/web_ui/frontend/components/Map/PopOutCard.tsx @@ -0,0 +1,65 @@ +import React, { FC, ReactNode, useEffect } from 'react'; +import { Box, Grow, IconButton, Typography } from '@mui/material'; +import { Close } from '@mui/icons-material'; + +const PopOutCard = ({ + title, + children, + active, + onClose, +}: { + title?: string; + children: ReactNode; + active: boolean; + onClose: () => void; +}) => { + useEffect(() => { + if (active) { + const handler = (event: KeyboardEvent) => { + if (event.key == 'Escape') { + onClose(); + } + }; + document.addEventListener('keydown', handler); + return () => { + document.removeEventListener('keydown', handler); + }; + } + }, []); + + return ( + + + + {title} + + + {children} + + + ); +}; + +const CloseButton = ({ onClose }: { onClose: () => void }) => { + return ( + + + + ); +}; + +export default PopOutCard; diff --git a/web_ui/frontend/components/Map/ServerCard.tsx b/web_ui/frontend/components/Map/ServerCard.tsx new file mode 100644 index 000000000..5a1b412f6 --- /dev/null +++ b/web_ui/frontend/components/Map/ServerCard.tsx @@ -0,0 +1,43 @@ +import { Box, Grid, Typography } from '@mui/material'; +import { InformationSpan } from '@/components'; +import { ServerCapabilitiesTable } from '@/components/ServerCapabilitiesTable'; +import React from 'react'; +import { ServerDetailed, ServerGeneral } from '@/types'; + +const ServerCard = ({ + server, +}: { + server?: ServerGeneral | ServerDetailed; +}) => { + // If there is no server, return null + if (!server) { + return null; + } + + return ( + + + + + + + + + + + {server.type == 'Origin' && ( + + + + )} + + ); +}; + +export default ServerCard; diff --git a/web_ui/frontend/components/Map/ServerMap.tsx b/web_ui/frontend/components/Map/ServerMap.tsx index c21946de7..f5305738f 100644 --- a/web_ui/frontend/components/Map/ServerMap.tsx +++ b/web_ui/frontend/components/Map/ServerMap.tsx @@ -1,67 +1,111 @@ -import React, { useState } from 'react'; +import React, { useContext, useMemo, useState } from 'react'; import { AttributionControl, + LngLat, LngLatLike, Marker, Popup, } from 'react-map-gl/maplibre'; -import { FmdGood } from '@mui/icons-material'; -import { ClickAwayListener } from '@mui/base'; +import { TripOrigin, Storage } from '@mui/icons-material'; -import { DefaultMap } from './'; +import { DefaultMap, PopOutCard, ServerCard } from '@/components/Map'; import { Server } from '@/index'; import 'maplibre-gl/dist/maplibre-gl.css'; -import { Box, Typography } from '@mui/material'; +import { alertOnError } from '@/helpers/util'; +import { getDirectorServer } from '@/helpers/api'; +import { AlertDispatchContext } from '@/components/AlertProvider'; +import { ServerDetailed, ServerGeneral } from '@/types'; +import { Box } from '@mui/material'; interface ServerMapProps { - servers?: Server[]; + servers?: ServerGeneral[]; } export const ServerMap = ({ servers }: ServerMapProps) => { + const dispatch = useContext(AlertDispatchContext); + + const [activeServer, setActiveServer] = useState< + ServerGeneral | ServerDetailed | undefined + >(undefined); + + const _setActiveServer = (server: ServerGeneral | undefined) => { + setActiveServer(server); + + if (server?.type == 'Origin') { + alertOnError( + async () => { + const response = await getDirectorServer(server.name); + setActiveServer(await response.json()); + }, + 'Failed to fetch server details', + dispatch + ); + } + }; + + const serverMarkers = useMemo(() => { + return servers?.map((server) => { + return ( + { + _setActiveServer(x); + }} + key={server.name} + /> + ); + }); + }, [servers]); + return ( - - {servers && - servers.map((server) => { - return ; - })} - + <> + + _setActiveServer(undefined)} + > + + + + {serverMarkers} + + + ); }; -const ServerMarker = ({ server }: { server: Server }) => { - const [showPopup, setShowPopup] = useState(false); - +const ServerMarker = ({ + server, + onClick, +}: { + server: ServerGeneral; + onClick: (server: ServerGeneral) => void; +}) => { return ( <> { - setShowPopup(true); + onClick(server); }} style={{ cursor: 'pointer' }} > - + {server.type == 'Origin' ? : } - {showPopup && ( - setShowPopup(false)}> - setShowPopup(false)} - offset={[0, -24] as [number, number]} - maxWidth={'auto'} - > - - {server.name} - - - - )} ); }; + +/** + * Jitter the coordinates of a server to prevent markers from overlapping + * @param n The latitude or longitude to jitter + * @param distance The ~ # of meters to jitter the coordinates by + */ +const jitter = (n: number, distance: number = 1000) => { + return n + (Math.random() - 0.5) * (0.000009 * distance); +}; diff --git a/web_ui/frontend/components/Map/index.ts b/web_ui/frontend/components/Map/index.ts index cbf6ff08b..096d61426 100644 --- a/web_ui/frontend/components/Map/index.ts +++ b/web_ui/frontend/components/Map/index.ts @@ -1,3 +1,5 @@ export * from './SinglePointMap'; export * from './DefaultMap'; export * from './ServerMap'; +export { default as PopOutCard } from './PopOutCard'; +export { default as ServerCard } from './ServerCard';