diff --git a/frontend/react/src/constants/constants.ts b/frontend/react/src/constants/constants.ts index fe6fad95..e6b81668 100644 --- a/frontend/react/src/constants/constants.ts +++ b/frontend/react/src/constants/constants.ts @@ -301,7 +301,8 @@ export enum OlympusState { AUDIO = "Audio", AIRBASE = "Airbase", GAME_MASTER = "Game master", - IMPORT_EXPORT = "Import/export" + IMPORT_EXPORT = "Import/export", + WARNING = "Warning modal" } export const NO_SUBSTATE = "No substate"; @@ -353,6 +354,12 @@ export enum ImportExportSubstate { EXPORT = "EXPORT" } +export enum WarningSubstate { + NO_SUBSTATE = "No substate", + NOT_CHROME = "Not chrome", + NOT_SECURE = "Not secure" +} + export type OlympusSubState = DrawSubState | JTACSubState | SpawnSubState | OptionsSubstate | string; @@ -379,9 +386,10 @@ export const MAP_OPTIONS_DEFAULTS: MapOptions = { cameraPluginRatio: 1, cameraPluginEnabled: false, cameraPluginMode: "map", - tabletMode: false, AWACSMode: false, AWACSCoalition: "blue", + hideChromeWarning: false, + hideSecureWarning: false }; export const MAP_HIDDEN_TYPES_DEFAULTS = { diff --git a/frontend/react/src/map/map.ts b/frontend/react/src/map/map.ts index 3de6640a..cd00d896 100644 --- a/frontend/react/src/map/map.ts +++ b/frontend/react/src/map/map.ts @@ -559,6 +559,9 @@ export class Map extends L.Map { setSpawnRequestTable(spawnRequestTable: SpawnRequestTable) { this.#spawnRequestTable = spawnRequestTable; + + this.#currentSpawnMarker?.removeFrom(this); + this.#currentSpawnMarker = this.addTemporaryMarker(spawnRequestTable.unit.location, spawnRequestTable.unit.unitType, spawnRequestTable.coalition, true); } addStarredSpawnRequestTable(key, spawnRequestTable: SpawnRequestTable, quickAccessName: string) { @@ -693,8 +696,8 @@ export class Map extends L.Map { return this.#miniMapLayerGroup; } - addTemporaryMarker(latlng: L.LatLng, name: string, coalition: string, commandHash?: string) { - var marker = new TemporaryUnitMarker(latlng, name, coalition, commandHash); + addTemporaryMarker(latlng: L.LatLng, name: string, coalition: string, headingHandle: boolean, commandHash?: string) { + var marker = new TemporaryUnitMarker(latlng, name, coalition, headingHandle, commandHash); marker.addTo(this); this.#temporaryMarkers.push(marker); return marker; @@ -828,7 +831,8 @@ export class Map extends L.Map { this.#currentSpawnMarker = new TemporaryUnitMarker( new L.LatLng(0, 0), this.#spawnRequestTable?.unit.unitType ?? "", - this.#spawnRequestTable?.coalition ?? "neutral" + this.#spawnRequestTable?.coalition ?? "neutral", + false ); this.#currentSpawnMarker.addTo(this); } else if (subState === SpawnSubState.SPAWN_EFFECT) { @@ -1073,8 +1077,10 @@ export class Map extends L.Map { MouseMovedEvent.dispatch(e.latlng, elevation); }); - if (this.#currentSpawnMarker) this.#currentSpawnMarker.setLatLng(e.latlng); - if (this.#currentEffectMarker) this.#currentEffectMarker.setLatLng(e.latlng); + if (getApp().getState() === OlympusState.SPAWN) { + if (this.#currentSpawnMarker) this.#currentSpawnMarker.setLatLng(e.latlng); + if (this.#currentEffectMarker) this.#currentEffectMarker.setLatLng(e.latlng); + } } else { this.#destionationWasRotated = true; this.#destinationRotation -= e.originalEvent.movementX; @@ -1202,7 +1208,7 @@ export class Map extends L.Map { if (this.#keepRelativePositions) { selectedUnits.forEach((unit) => { if (this.#contextAction?.getOptions().type === ContextActionType.MOVE || this.#contextAction === null) { - this.#destinationPreviewMarkers[unit.ID] = new TemporaryUnitMarker(new L.LatLng(0, 0), unit.getName(), unit.getCoalition()); + this.#destinationPreviewMarkers[unit.ID] = new TemporaryUnitMarker(new L.LatLng(0, 0), unit.getName(), unit.getCoalition(), false); } else if (this.#contextAction?.getTarget() === ContextActionTarget.POINT) { this.#destinationPreviewMarkers[unit.ID] = new TargetMarker(new L.LatLng(0, 0)); } diff --git a/frontend/react/src/map/markers/temporaryunitmarker.ts b/frontend/react/src/map/markers/temporaryunitmarker.ts index 723c35f7..c809aea0 100644 --- a/frontend/react/src/map/markers/temporaryunitmarker.ts +++ b/frontend/react/src/map/markers/temporaryunitmarker.ts @@ -9,12 +9,14 @@ export class TemporaryUnitMarker extends CustomMarker { #coalition: string; #commandHash: string | undefined = undefined; #timer: number = 0; + #headingHandle: boolean; - constructor(latlng: LatLng, name: string, coalition: string, commandHash?: string) { + constructor(latlng: LatLng, name: string, coalition: string, headingHandle: boolean, commandHash?: string) { super(latlng, { interactive: false }); this.#name = name; this.#coalition = coalition; this.#commandHash = commandHash; + this.#headingHandle = headingHandle; if (commandHash !== undefined) this.setCommandHash(commandHash); } diff --git a/frontend/react/src/olympusapp.ts b/frontend/react/src/olympusapp.ts index 536855f5..a6f13647 100644 --- a/frontend/react/src/olympusapp.ts +++ b/frontend/react/src/olympusapp.ts @@ -20,7 +20,7 @@ import { WeaponsManager } from "./weapon/weaponsmanager"; import { ServerManager } from "./server/servermanager"; import { AudioManager } from "./audio/audiomanager"; -import { GAME_MASTER, LoginSubState, NO_SUBSTATE, OlympusState, OlympusSubState } from "./constants/constants"; +import { GAME_MASTER, LoginSubState, NO_SUBSTATE, OlympusState, OlympusSubState, WarningSubstate } from "./constants/constants"; import { AppStateChangedEvent, ConfigLoadedEvent, InfoPopupEvent, MapOptionsChangedEvent, SelectedUnitsChangedEvent, ShortcutsChangedEvent } from "./events"; import { OlympusConfig } from "./interfaces"; import { SessionDataManager } from "./sessiondata"; @@ -38,6 +38,7 @@ export class OlympusApp { #state: OlympusState = OlympusState.NOT_INITIALIZED; #subState: OlympusSubState = NO_SUBSTATE; #infoMessages: string[] = []; + #startupWarningsShown: boolean = false; /* Main leaflet map, extended by custom methods */ #map: Map; @@ -306,6 +307,16 @@ export class OlympusApp { this.#state = state; this.#subState = subState; + if (this.#state === OlympusState.IDLE && !this.#startupWarningsShown) { + window.setTimeout(() => { + const isChrome = navigator.userAgent.indexOf("Chrome") > -1; + if (!isChrome && !this.getMap().getOptions().hideChromeWarning) this.setState(OlympusState.WARNING, WarningSubstate.NOT_CHROME); + if (!isSecureContext && !this.getMap().getOptions().hideSecureWarning) this.setState(OlympusState.WARNING, WarningSubstate.NOT_SECURE); + }, 200); + + this.#startupWarningsShown = true; + } + console.log(`App state set to ${state}, substate ${subState}`); AppStateChangedEvent.dispatch(state, subState); } diff --git a/frontend/react/src/types/types.ts b/frontend/react/src/types/types.ts index 3dad636f..e4fed8f8 100644 --- a/frontend/react/src/types/types.ts +++ b/frontend/react/src/types/types.ts @@ -25,9 +25,10 @@ export type MapOptions = { cameraPluginRatio: number; cameraPluginEnabled: boolean; cameraPluginMode: string; - tabletMode: boolean; AWACSMode: boolean; AWACSCoalition: Coalition; + hideChromeWarning: boolean; + hideSecureWarning: boolean; }; export type MapHiddenTypes = { diff --git a/frontend/react/src/ui/modals/warningmodal.tsx b/frontend/react/src/ui/modals/warningmodal.tsx new file mode 100644 index 00000000..2ee38610 --- /dev/null +++ b/frontend/react/src/ui/modals/warningmodal.tsx @@ -0,0 +1,115 @@ +import React, { useEffect, useState } from "react"; +import { Modal } from "./components/modal"; +import { FaExclamationTriangle } from "react-icons/fa"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faArrowRight } from "@fortawesome/free-solid-svg-icons"; +import { getApp } from "../../olympusapp"; +import { MAP_OPTIONS_DEFAULTS, NO_SUBSTATE, OlympusState, WarningSubstate } from "../../constants/constants"; +import { AppStateChangedEvent, MapOptionsChangedEvent } from "../../events"; +import { OlCheckbox } from "../components/olcheckbox"; + +export function WarningModal(props: { open: boolean }) { + const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED); + const [appSubState, setAppSubState] = useState(NO_SUBSTATE); + const [mapOptions, setMapOptions] = useState(MAP_OPTIONS_DEFAULTS); + + useEffect(() => { + AppStateChangedEvent.on((appState, appSubState) => { + setAppState(appState); + setAppSubState(appSubState); + }); + MapOptionsChangedEvent.on((mapOptions) => setMapOptions({ ...mapOptions })); + }, []); + + let warningText; + if (appState === OlympusState.WARNING) { + switch (appSubState) { + case WarningSubstate.NOT_CHROME: + warningText = ( +
+ Non-Google Chrome Browser Detected. + + It appears you are using a browser other than Google Chrome. + + If you encounter any problems, we strongly suggest you use a Chrome based browser. Many features, especially advanced ones such as audio playback and capture, were developed specifically for Chrome based browsers. +
+ { + getApp().getMap().setOption("hideChromeWarning", !mapOptions.hideChromeWarning); + }} + />{" "} + Don't show this warning again +
+
+ ); + break; + case WarningSubstate.NOT_SECURE: + case WarningSubstate.NOT_CHROME: + warningText = ( +
+ Your connection to DCS Olympus is not secure. + + To protect your personal data some advanced DCS Olympus features like the camera plugin or the audio backend + have been disabled. + + + To solve this issue, DCS Olympus should be served using the https protocol. + + To do so, we suggest using a dedicated server and a reverse proxy with SSL enabled. +
+ { + getApp().getMap().setOption("hideSecureWarning", !mapOptions.hideSecureWarning); + }} + />{" "} + Don't show this warning again +
+
+ ); + break; + default: + break; + } + } + + return ( + +
+
+ +
Warning
+
+
{warningText}
+
+ +
+
+
+ ); +} diff --git a/frontend/react/src/ui/ui.tsx b/frontend/react/src/ui/ui.tsx index 254c104d..8dd125eb 100644 --- a/frontend/react/src/ui/ui.tsx +++ b/frontend/react/src/ui/ui.tsx @@ -8,7 +8,13 @@ import { MainMenu } from "./panels/mainmenu"; import { SideBar } from "./panels/sidebar"; import { OptionsMenu } from "./panels/optionsmenu"; import { MapHiddenTypes, MapOptions } from "../types/types"; -import { NO_SUBSTATE, OlympusState, OlympusSubState, OptionsSubstate, SpawnSubState, UnitControlSubState } from "../constants/constants"; +import { + NO_SUBSTATE, + OlympusState, + OlympusSubState, + OptionsSubstate, + UnitControlSubState +} from "../constants/constants"; import { getApp, setupApp } from "../olympusapp"; import { LoginModal } from "./modals/loginmodal"; @@ -34,6 +40,7 @@ import { RadiosSummaryPanel } from "./panels/radiossummarypanel"; import { AWACSMenu } from "./panels/awacsmenu"; import { ServerOverlay } from "./serveroverlay"; import { ImportExportModal } from "./modals/importexportmodal"; +import { WarningModal } from "./modals/warningmodal"; export type OlympusUIState = { mainMenuVisible: boolean; @@ -83,6 +90,8 @@ export function UI() { + + )}