diff --git a/frontend/react/src/constants/constants.ts b/frontend/react/src/constants/constants.ts index 2f85a2f1..1fdda607 100644 --- a/frontend/react/src/constants/constants.ts +++ b/frontend/react/src/constants/constants.ts @@ -154,15 +154,9 @@ export const mapBounds = { "SinaiMap": { bounds: new LatLngBounds([34.312222, 28.523333], [25.946944, 36.897778]), zoom: 4 }, } -export const DCSMapsZoomLevelsByTheatre: { [key: string]: { minNativeZoom?: number, maxNativeZoom?: number, minZoom?: number }[] } = { - "Syria": [], - "MarianaIslands": [{ minNativeZoom: 1, maxNativeZoom: 13, }, { minNativeZoom: 14, maxNativeZoom: 18, minZoom: 14 }], - "Nevada": [], - "PersianGulf": [], - "Caucasus": [], - "Falklands": [], - "Normandy": [], - "SinaiMap": [], +export const mapMirrors = { + "DCS Map mirror 1": "https://maps.dcsolympus.com/maps", + "DCS Map mirror 2": "https://refugees.dcsolympus.com/maps" } export const defaultMapLayers = { diff --git a/frontend/react/src/dom.d.ts b/frontend/react/src/dom.d.ts index 22bfc7d3..e3daa2a3 100644 --- a/frontend/react/src/dom.d.ts +++ b/frontend/react/src/dom.d.ts @@ -1,3 +1,4 @@ +import { ServerStatus } from "./interfaces"; import { Unit } from "./unit/unit"; interface CustomEventMap { @@ -6,25 +7,16 @@ interface CustomEventMap { "unitsSelection": CustomEvent, "unitsDeselection": CustomEvent, "clearSelection": CustomEvent, - "unitCreation": CustomEvent, - "unitDeletion": CustomEvent, "unitDeath": CustomEvent, "unitUpdated": CustomEvent, - "unitMoveCommand": CustomEvent, - "unitAttackCommand": CustomEvent, - "unitLandCommand": CustomEvent, - "unitSetAltitudeCommand": CustomEvent, - "unitSetSpeedCommand": CustomEvent, - "unitSetOption": CustomEvent, - "groupCreation": CustomEvent, - "groupDeletion": CustomEvent, "mapStateChanged": CustomEvent, "mapContextMenu": CustomEvent, "mapOptionChanged": CustomEvent, - "mapOptionsChanged": CustomEvent, + "mapOptionsChanged": CustomEvent, // TODO not very clear, why the two options? "commandModeOptionsChanged": CustomEvent, "contactsUpdated": CustomEvent, - "activeCoalitionChanged": CustomEvent + "activeCoalitionChanged": CustomEvent, + "serverStatusUpdated": CustomEvent } declare global { diff --git a/frontend/react/src/interfaces.ts b/frontend/react/src/interfaces.ts index 2ea1e135..f6f33118 100644 --- a/frontend/react/src/interfaces.ts +++ b/frontend/react/src/interfaces.ts @@ -282,4 +282,13 @@ export interface ShortcutMouseOptions extends ShortcutOptions { export interface Manager { add: CallableFunction; +} + +export interface ServerStatus { + frameRate: number, + load: number, + elapsedTime: number, + missionTime: DateAndTime["time"], + connected: boolean, + paused: boolean } \ No newline at end of file diff --git a/frontend/react/src/map/map.ts b/frontend/react/src/map/map.ts index 329a3cf0..7267796c 100644 --- a/frontend/react/src/map/map.ts +++ b/frontend/react/src/map/map.ts @@ -11,8 +11,7 @@ import { bearing, /*createCheckboxOption, createSliderInputOption, createTextInp import { DestinationPreviewMarker } from "./markers/destinationpreviewmarker"; import { TemporaryUnitMarker } from "./markers/temporaryunitmarker"; import { ClickableMiniMap } from "./clickableminimap"; -import { SVGInjector } from '@tanem/svg-injector' -import { defaultMapLayers, mapBounds, minimapBoundaries, IDLE, COALITIONAREA_DRAW_POLYGON, MOVE_UNIT, SHOW_UNIT_CONTACTS, HIDE_GROUP_MEMBERS, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, SHOW_UNIT_LABELS, SHOW_UNITS_ENGAGEMENT_RINGS, SHOW_UNITS_ACQUISITION_RINGS, HIDE_UNITS_SHORT_RANGE_RINGS, FILL_SELECTED_RING, /*MAP_MARKER_CONTROLS,*/ DCS_LINK_PORT, DCSMapsZoomLevelsByTheatre, DCS_LINK_RATIO, MAP_OPTIONS_DEFAULTS, MAP_HIDDEN_TYPES_DEFAULTS, SPAWN_UNIT } from "../constants/constants"; +import { mapMirrors, defaultMapLayers, mapBounds, minimapBoundaries, IDLE, COALITIONAREA_DRAW_POLYGON, MOVE_UNIT, SHOW_UNIT_CONTACTS, HIDE_GROUP_MEMBERS, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, SHOW_UNIT_LABELS, SHOW_UNITS_ENGAGEMENT_RINGS, SHOW_UNITS_ACQUISITION_RINGS, HIDE_UNITS_SHORT_RANGE_RINGS, FILL_SELECTED_RING, /*MAP_MARKER_CONTROLS,*/ DCS_LINK_PORT, DCS_LINK_RATIO, MAP_OPTIONS_DEFAULTS, MAP_HIDDEN_TYPES_DEFAULTS, SPAWN_UNIT } from "../constants/constants"; import { CoalitionArea } from "./coalitionarea/coalitionarea"; //import { CoalitionAreaContextMenu } from "../contextmenus/coalitionareacontextmenu"; import { DrawingCursor } from "./coalitionarea/drawingcursor"; @@ -133,7 +132,7 @@ export class Map extends L.Map { this.#ID = ID; - this.setLayer("DCS Map"); + this.setLayer("DCS Map mirror 2"); /* Minimap */ var minimapLayer = new L.TileLayer(this.#mapLayers[Object.keys(this.#mapLayers)[0]].urlTemplate, { minZoom: 0, maxZoom: 13 }); @@ -268,25 +267,39 @@ export class Map extends L.Map { } else { this.#layer = new L.TileLayer(layerData.urlTemplate, layerData); } - /* DCS core layers are handled here */ - } else if (["DCS Map", "DCS Satellite"].includes(layerName)) { + /* DCS core layers are handled here */ + } else if (["DCS Map mirror 1", "DCS Map mirror 2"].includes(layerName) ) { let layerData = this.#mapLayers["ArcGIS Satellite"]; let layers = [new L.TileLayer(layerData.urlTemplate, layerData)]; - let template = `https://maps.dcsolympus.com/maps/${layerName === "DCS Map" ? "alt" : "sat"}-{theatre}-modern/{z}/{x}/{y}.png`; - layers.push(...DCSMapsZoomLevelsByTheatre[theatre].map((nativeZoomLevels: any) => { - return new L.TileLayer(template.replace("{theatre}", theatre.toLowerCase()), { ...nativeZoomLevels, maxZoom: 20, crossOrigin: "" }); - })); - - this.#layer = new L.LayerGroup(layers); + /* Load the configuration file */ + const mirror = mapMirrors[layerName as keyof typeof mapMirrors]; + const request = new Request(mirror + "/config.json"); + fetch(request).then((response) => { + if (response.status === 200) { + return response.json(); + } else { + return {}; + } + }).then((res: any) => { + if ("alt-" + theatre.toLowerCase() in res) { + let template = `${mirror}/alt-${theatre.toLowerCase()}/{z}/{x}/{y}.png`; + layers.push(...res["alt-" + theatre.toLowerCase()].map((layerConfig: any) => { + return new L.TileLayer(template, {...layerConfig, crossOrigin: ""}); + })); + } + this.#layer = new L.LayerGroup(layers); + this.#layer?.addTo(this); + }).catch(() => { + this.#layer = new L.LayerGroup(layers); + this.#layer?.addTo(this); + }) } - - this.#layer?.addTo(this); this.#layerName = layerName; } getLayers() { - let layers = ["DCS Map", "DCS Satellite"] + let layers = ["DCS Map mirror 1", "DCS Map mirror 2"]; layers.push(...Object.keys(this.#mapLayers)); return layers; } diff --git a/frontend/react/src/mission/missionmanager.ts b/frontend/react/src/mission/missionmanager.ts index 6d6dcc72..8f47970f 100644 --- a/frontend/react/src/mission/missionmanager.ts +++ b/frontend/react/src/mission/missionmanager.ts @@ -18,6 +18,8 @@ export class MissionManager { #airbases: { [name: string]: Airbase } = {}; #theatre: string = ""; #dateAndTime: DateAndTime = {date: {Year: 0, Month: 0, Day: 0}, time: {h: 0, m: 0, s: 0}, startTime: 0, elapsedTime: 0}; + #load: number = 0; + #frameRate: number = 0; #commandModeOptions: CommandModeOptions = {commandMode: NONE, restrictSpawns: false, restrictToCoalition: false, setupTime: Infinity, spawnPoints: {red: Infinity, blue: Infinity}, eras: []}; #remainingSetupTime: number = 0; #spentSpawnPoint: number = 0; @@ -212,6 +214,22 @@ export class MissionManager { this.refreshSpawnPoints(); } + setLoad(load: number) { + this.#load = load; + } + + getLoad() { + return this.#load; + } + + setFrameRate(frameRate: number) { + this.#frameRate = frameRate; + } + + getFrameRate() { + return this.#frameRate; + } + showCommandModeDialog() { //const options = this.getCommandModeOptions() //const { restrictSpawns, restrictToCoalition, setupTime } = options; diff --git a/frontend/react/src/server/servermanager.ts b/frontend/react/src/server/servermanager.ts index f8085c8c..e0a853e5 100644 --- a/frontend/react/src/server/servermanager.ts +++ b/frontend/react/src/server/servermanager.ts @@ -5,8 +5,9 @@ import { AIRBASES_URI, BULLSEYE_URI, COMMANDS_URI, LOGS_URI, MISSION_URI, NONE, //import { LogPanel } from '../panels/logpanel'; //import { Popup } from '../popups/popup'; //import { ConnectionStatusPanel } from '../panels/connectionstatuspanel'; -import { AirbasesData, BullseyesData, GeneralSettings, MissionData, Radio, ServerRequestOptions, TACAN } from '../interfaces'; +import { AirbasesData, BullseyesData, GeneralSettings, MissionData, Radio, ServerRequestOptions, ServerStatus, TACAN } from '../interfaces'; import { zeroAppend } from '../other/utils'; +import { SiTheregister } from 'react-icons/si'; export class ServerManager { #connected: boolean = false; @@ -80,8 +81,10 @@ export class ServerManager { const result = JSON.parse(xmlHttp.responseText); this.#lastUpdateTimes[uri] = callback(result); - //if (result.frameRate !== undefined && result.load !== undefined) - // (getApp().getPanelsManager().get("serverStatus") as ServerStatusPanel).update(result.frameRate, result.load); + if (result.frameRate !== undefined && result.load !== undefined) { + getApp().getMissionManager().setLoad(result.load); + getApp().getMissionManager().setFrameRate(result.frameRate); + } } } else if (xmlHttp.status == 401) { /* Bad credentials */ @@ -489,40 +492,25 @@ export class ServerManager { var time = getApp().getUnitsManager()?.update(buffer); return time; }, true); - - const elapsedMissionTime = getApp().getMissionManager().getDateAndTime().elapsedTime; - this.#serverIsPaused = (elapsedMissionTime === this.#previousMissionElapsedTime); - this.#previousMissionElapsedTime = elapsedMissionTime; - - //const csp = (getApp().getPanelsManager().get("connectionStatus") as ConnectionStatusPanel); - - //if (this.getConnected()) { - // if (this.getServerIsPaused()) { - // csp.showServerPaused(); - // } else { - // csp.showConnected(); - // } - //} else { - // csp.showDisconnected(); - //} - } }, (this.getServerIsPaused() ? 500 : 5000))); // Mission clock and elapsed time this.#intervals.push(window.setInterval(() => { - - if (!this.getConnected() || this.#serverIsPaused) { - return; - } - const elapsedMissionTime = getApp().getMissionManager().getDateAndTime().elapsedTime; - - //const csp = (getApp().getPanelsManager().get("connectionStatus") as ConnectionStatusPanel); - //const mt = getApp().getMissionManager().getDateAndTime().time; - - //csp.setMissionTime([mt.h, mt.m, mt.s].map(n => zeroAppend(n, 2)).join(":")); - //csp.setElapsedTime(new Date(elapsedMissionTime * 1000).toISOString().substring(11, 19)); + this.#serverIsPaused = (elapsedMissionTime === this.#previousMissionElapsedTime); + this.#previousMissionElapsedTime = elapsedMissionTime; + + document.dispatchEvent(new CustomEvent("serverStatusUpdated", { + detail: { + frameRate: getApp().getMissionManager().getFrameRate(), + load: getApp().getMissionManager().getLoad(), + elapsedTime: getApp().getMissionManager().getDateAndTime().elapsedTime, + missionTime: getApp().getMissionManager().getDateAndTime().time, + connected: this.getConnected(), + paused: this.getPaused() + } as ServerStatus + })); }, 1000)); diff --git a/frontend/react/src/ui/panels/minimappanel.tsx b/frontend/react/src/ui/panels/minimappanel.tsx new file mode 100644 index 00000000..f91076f7 --- /dev/null +++ b/frontend/react/src/ui/panels/minimappanel.tsx @@ -0,0 +1,79 @@ +import React, { useState, useEffect } from "react"; +import { zeroAppend } from "../../other/utils"; +import { DateAndTime } from "../../interfaces"; + +export function MiniMapPanel(props: { + +}) { + var [frameRate, setFrameRate] = useState(0); + var [load, setLoad] = useState(0); + var [elapsedTime, setElapsedTime] = useState(0); + var [missionTime, setMissionTime] = useState({ h: 0, m: 0, s: 0 } as DateAndTime["time"]); + var [connected, setConnected] = useState(false); + var [paused, setPaused] = useState(false); + var [showMissionTime, setShowMissionTime] = useState(false); + + document.addEventListener("serverStatusUpdated", (ev) => { + setFrameRate(ev.detail.frameRate); + setLoad(ev.detail.load); + setElapsedTime(ev.detail.elapsedTime); + setMissionTime(ev.detail.missionTime); + setConnected(ev.detail.connected); + setPaused(ev.detail.paused); + }) + + // A bit of a hack to set the rounded borders to the minimap + useEffect(() => { + let miniMap = document.querySelector(".leaflet-control-minimap"); + if (miniMap) { + miniMap.classList.add("rounded-t-lg") + } + }) + + // Compute the time string depending on mission or elapsed time + let hours = 0; + let minutes = 0; + let seconds = 0; + + if (showMissionTime) { + hours = missionTime.h; + minutes = missionTime.m; + seconds = missionTime.s; + } else { + hours = Math.floor(elapsedTime / 3600); + minutes = Math.floor((elapsedTime / 60)) % 60; + seconds = Math.round(elapsedTime) % 60; + } + + let timeString = `${zeroAppend(hours, 2)}:${zeroAppend(minutes, 2)}:${zeroAppend(seconds, 2)}` + + // Choose frame rate string color + let frameRateColor = "#8BFF63"; + if (frameRate < 30) + frameRateColor = "#F05252"; + else if (frameRate >= 30 && frameRate < 60) + frameRateColor = "#FF9900" + + // Choose load string color + let loadColor = "#8BFF63"; + if (load > 1000) + loadColor = "#F05252"; + else if (load >= 100 && load < 1000) + loadColor = "#FF9900" + + return
setShowMissionTime(!showMissionTime)} className="absolute w-[288px] top-[233px] right-[10px] z-ui-0 text-sm dark:text-gray-200 bg-gray-200 dark:bg-olympus-800/90 backdrop-blur-lg backdrop-grayscale flex items-center justify-between p-3 rounded-b-lg"> + { + !connected ? +
Server disconnected
: + paused ? +
Server paused
: + <> +
FPS: {frameRate}
+
Load: {load}
+
{showMissionTime ? "MT" : "ET"}: {timeString}
+
+ + } + +
+} \ No newline at end of file diff --git a/frontend/react/src/ui/ui.tsx b/frontend/react/src/ui/ui.tsx index 9435bdfc..2e3cb5eb 100644 --- a/frontend/react/src/ui/ui.tsx +++ b/frontend/react/src/ui/ui.tsx @@ -15,6 +15,7 @@ import { BLUE_COMMANDER, GAME_MASTER, MAP_HIDDEN_TYPES_DEFAULTS, MAP_OPTIONS_DEF import { getApp, setupApp } from '../olympusapp' import { LoginModal } from './modals/login' import { sha256 } from 'js-sha256' +import { MiniMapPanel } from './panels/minimappanel' export type OlympusState = { mainMenuVisible: boolean, @@ -141,10 +142,12 @@ export function UI() { open={optionsMenuVisible} onClose={() => setOptionsMenuVisible(false)} /> + +
-
+
diff --git a/frontend/website/src/map/map.ts b/frontend/website/src/map/map.ts index 5a841b1d..983109df 100644 --- a/frontend/website/src/map/map.ts +++ b/frontend/website/src/map/map.ts @@ -343,7 +343,7 @@ export class Map extends L.Map { } getLayers() { - let layers = ["DCS Map mirror 1", "DCS Map mirror 2"] + let layers = ["DCS Map mirror 1", "DCS Map mirror 2"]; layers.push(...Object.keys(this.#mapLayers)); return layers; } diff --git a/version.json b/version.json index c29a7836..1ffac4bd 100644 --- a/version.json +++ b/version.json @@ -1,3 +1,3 @@ { - "version": "v1.0.4" + "version": "v2.0.0" }