diff --git a/frontend/react/src/audio/audiomanager.ts b/frontend/react/src/audio/audiomanager.ts index 928172b7..92202606 100644 --- a/frontend/react/src/audio/audiomanager.ts +++ b/frontend/react/src/audio/audiomanager.ts @@ -199,7 +199,7 @@ export class AudioManager { addRadio() { console.log("Adding new radio"); - if (!this.#running) { + if (!this.#running || this.#sources[0] === undefined) { console.log("Audio manager not started, aborting..."); return; } diff --git a/frontend/react/src/audio/radiosink.ts b/frontend/react/src/audio/radiosink.ts index eb0af37f..cf75253d 100644 --- a/frontend/react/src/audio/radiosink.ts +++ b/frontend/react/src/audio/radiosink.ts @@ -13,7 +13,7 @@ export class RadioSink extends AudioSink { #frequency = 251000000; #modulation = 0; #ptt = false; - #tuned = false; + #tuned = true; #volume = 0.5; #receiving = false; #clearReceivingTimeout: number; diff --git a/frontend/react/src/constants/constants.ts b/frontend/react/src/constants/constants.ts index 826387a7..06f6f031 100644 --- a/frontend/react/src/constants/constants.ts +++ b/frontend/react/src/constants/constants.ts @@ -529,8 +529,8 @@ export namespace ContextActions { executeImmediately: true, type: ContextActionType.DELETE, code: "Delete", - ctrlKey: true, - shiftKey: false, + ctrlKey: false, + shiftKey: true, altKey: false, } ); @@ -562,7 +562,7 @@ export namespace ContextActions { export const FOLLOW = new ContextAction( "follow", "Follow unit", - "Click on a unit to follow it in formation", + "Right-click on a unit to follow it in formation", olButtonsContextFollow, ContextActionTarget.UNIT, (units: Unit[], targetUnit: Unit | null, _) => { @@ -580,7 +580,7 @@ export namespace ContextActions { export const BOMB = new ContextAction( "bomb", "Precision bomb location", - "Click on a point to execute a precision bombing attack", + "Right-click on a point to execute a precision bombing attack", faLocationCrosshairs, ContextActionTarget.POINT, (units: Unit[], _, targetPosition: LatLng | null) => { @@ -595,7 +595,7 @@ export namespace ContextActions { export const CARPET_BOMB = new ContextAction( "carpet-bomb", "Carpet bomb location", - "Click on a point to execute a carpet bombing attack", + "Right-click on a point to execute a carpet bombing attack", faXmarksLines, ContextActionTarget.POINT, (units: Unit[], _, targetPosition: LatLng | null) => { @@ -610,7 +610,7 @@ export namespace ContextActions { export const LAND = new ContextAction( "land", "Land", - "Click on a point to land at the nearest airbase", + "Right-click on a point to land at the nearest airbase", faPlaneArrival, ContextActionTarget.POINT, (units: Unit[], _, targetPosition: LatLng | null) => { @@ -622,7 +622,7 @@ export namespace ContextActions { export const LAND_AT_POINT = new ContextAction( "land-at-point", "Land at location", - "Click on a point to land there", + "Right-click on a point to land there", olButtonsContextLandAtPoint, ContextActionTarget.POINT, (units: Unit[], _, targetPosition: LatLng | null) => { @@ -649,7 +649,7 @@ export namespace ContextActions { export const ATTACK = new ContextAction( "attack", "Attack unit", - "Click on a unit to attack it", + "Right-click on a unit to attack it", olButtonsContextAttack, ContextActionTarget.UNIT, (units: Unit[], targetUnit: Unit | null, _) => { @@ -661,7 +661,7 @@ export namespace ContextActions { export const FIRE_AT_AREA = new ContextAction( "fire-at-area", "Fire at area", - "Click on a point to precisely fire at it (if possible)", + "Right-click on a point to precisely fire at it (if possible)", faLocationCrosshairs, ContextActionTarget.POINT, (units: Unit[], _, targetPosition: LatLng | null) => { diff --git a/frontend/react/src/events.ts b/frontend/react/src/events.ts index 9c8b3421..de9d0309 100644 --- a/frontend/react/src/events.ts +++ b/frontend/react/src/events.ts @@ -137,6 +137,19 @@ export class BindShortcutRequestEvent { } } +export class ModalEvent { + static on(callback: (modal: boolean) => void) { + document.addEventListener(this.name, (ev: CustomEventInit) => { + callback(ev.detail.modal); + }); + } + + static dispatch(modal: boolean) { + document.dispatchEvent(new CustomEvent(this.name, { detail: { modal } })); + console.log(`Event ${this.name} dispatched`); + } +} + /************** Map events ***************/ export class HiddenTypesChangedEvent { static on(callback: (hiddenTypes: MapHiddenTypes) => void) { diff --git a/frontend/react/src/map/boxselect.ts b/frontend/react/src/map/boxselect.ts index d525014a..b8cb922e 100644 --- a/frontend/react/src/map/boxselect.ts +++ b/frontend/react/src/map/boxselect.ts @@ -5,9 +5,10 @@ import { DomEvent } from "leaflet"; import { LatLngBounds } from "leaflet"; import { Bounds } from "leaflet"; import { SELECT_TOLERANCE_PX } from "../constants/constants"; +import { Map } from "./map"; export var BoxSelect = Handler.extend({ - initialize: function (map) { + initialize: function (map: Map) { this._map = map; this._container = map.getContainer(); this._pane = map.getPanes().overlayPane; @@ -45,13 +46,12 @@ export var BoxSelect = Handler.extend({ }, _onMouseDown: function (e: any) { - if (e.which == 1 && e.button == 0) { + if (this._map.getEnableSelection() && e.button == 0) { // Clear the deferred resetState if it hasn't executed yet, otherwise it // will interrupt the interaction and orphan a box element in the container. this._clearDeferredResetState(); this._resetState(); - DomUtil.disableTextSelection(); DomUtil.disableImageDrag(); this._map.dragging.disable(); @@ -65,10 +65,8 @@ export var BoxSelect = Handler.extend({ contextmenu: DomEvent.stop, touchmove: this._onMouseMove, touchend: this._onMouseUp, - touchstart: this._onKeyDown, mousemove: this._onMouseMove, - mouseup: this._onMouseUp, - keydown: this._onKeyDown, + mouseup: this._onMouseUp }, this ); @@ -77,6 +75,24 @@ export var BoxSelect = Handler.extend({ } }, + _onMouseUp: function (e: any) { + if (e.button !== 0) { + return; + } + + this._finish(); + + if (!this._moved) { + return; + } + // Postpone to next JS tick so internal click event handling + // still see it as "moved". + window.setTimeout(Util.bind(this._resetState, this), 0); + var bounds = new LatLngBounds(this._map.containerPointToLatLng(this._startPoint), this._map.containerPointToLatLng(this._point)); + + this._map.fire("selectionend", { selectionBounds: bounds }); + }, + _onMouseMove: function (e: any) { if (e.type === "touchmove") this._point = this._map.mouseEventToContainerPoint(e.touches[0]); else this._point = this._map.mouseEventToContainerPoint(e); @@ -110,7 +126,6 @@ export var BoxSelect = Handler.extend({ DomUtil.removeClass(this._container, "leaflet-crosshair"); } - DomUtil.enableTextSelection(); DomUtil.enableImageDrag(); this._map.dragging.enable(); @@ -121,38 +136,10 @@ export var BoxSelect = Handler.extend({ contextmenu: DomEvent.stop, touchmove: this._onMouseMove, touchend: this._onMouseUp, - touchstart: this._onKeyDown, mousemove: this._onMouseMove, mouseup: this._onMouseUp, - keydown: this._onKeyDown, }, this ); }, - - _onMouseUp: function (e: any) { - if (e.which !== 1 && e.button !== 0 && e.type !== "touchend") { - return; - } - - this._finish(); - - if (!this._moved) { - return; - } - // Postpone to next JS tick so internal click event handling - // still see it as "moved". - window.setTimeout(Util.bind(this._resetState, this), 0); - var bounds = new LatLngBounds(this._map.containerPointToLatLng(this._startPoint), this._map.containerPointToLatLng(this._point)); - - this._map.fire("selectionend", { selectionBounds: bounds }); - }, - - _onKeyDown: function (e: any) { - if (e.keyCode === 27) { - this._finish(); - this._clearDeferredResetState(); - this._resetState(); - } - }, }); diff --git a/frontend/react/src/map/map.ts b/frontend/react/src/map/map.ts index dd4cc8a3..b9384789 100644 --- a/frontend/react/src/map/map.ts +++ b/frontend/react/src/map/map.ts @@ -43,7 +43,6 @@ import { ExplosionMarker } from "./markers/explosionmarker"; import { TextMarker } from "./markers/textmarker"; import { TargetMarker } from "./markers/targetmarker"; import { - AirbaseSelectedEvent, AppStateChangedEvent, CoalitionAreaSelectedEvent, ConfigLoadedEvent, @@ -114,6 +113,7 @@ export class Map extends L.Map { #lastMouseCoordinates: L.LatLng = new L.LatLng(0, 0); #previousZoom: number = 0; #keepRelativePositions: boolean = false; + #enableSelection: boolean = false; /* Camera control plugin */ #slaveDCSCamera: boolean = false; @@ -360,6 +360,14 @@ export class Map extends L.Map { shiftKey: false, ctrlKey: false, }) + .addShortcut("toggleEnableSelection", { + label: "Toggle box selection", + keyUpCallback: () => this.setEnableSelection(false), + keyDownCallback: () => this.setEnableSelection(true), + code: "ShiftLeft", + altKey: false, + ctrlKey: false, + }) .addShortcut("increaseCameraZoom", { label: "Increase camera zoom", keyUpCallback: () => this.increaseCameraZoom(), @@ -725,6 +733,14 @@ export class Map extends L.Map { return this.#keepRelativePositions; } + setEnableSelection(enableSelection: boolean) { + this.#enableSelection = enableSelection; + } + + getEnableSelection() { + return this.#enableSelection; + } + increaseCameraZoom() { //const slider = document.querySelector(`label[title="${DCS_LINK_RATIO}"] input`); //if (slider instanceof HTMLInputElement) { @@ -769,7 +785,6 @@ export class Map extends L.Map { this.#currentEffectMarker = null; if (state !== OlympusState.UNIT_CONTROL) getApp().getUnitsManager().deselectAllUnits(); if (state !== OlympusState.DRAW || (state === OlympusState.DRAW && subState !== DrawSubState.EDIT)) this.deselectAllCoalitionAreas(); - AirbaseSelectedEvent.dispatch(null); /* Operations to perform when entering a state */ if (state === OlympusState.IDLE) { diff --git a/frontend/react/src/mission/airbase.ts b/frontend/react/src/mission/airbase.ts index dd3d1ebe..894b1032 100644 --- a/frontend/react/src/mission/airbase.ts +++ b/frontend/react/src/mission/airbase.ts @@ -1,4 +1,4 @@ -import { DivIcon } from "leaflet"; +import { DivIcon, DomEvent } from "leaflet"; import { CustomMarker } from "../map/markers/custommarker"; import { SVGInjector } from "@tanem/svg-injector"; import { AirbaseChartData, AirbaseOptions } from "../interfaces"; @@ -29,20 +29,28 @@ export class Airbase extends CustomMarker { AppStateChangedEvent.on((state, subState) => { const element = this.getElement(); - if (element) - element.style.pointerEvents = (state === OlympusState.IDLE || state === OlympusState.AIRBASE)? "all": "none"; - }) + if (element) element.style.pointerEvents = state === OlympusState.IDLE || state === OlympusState.AIRBASE ? "all" : "none"; + }); AirbaseSelectedEvent.on((airbase) => { - this.#selected = airbase == this; + this.#selected = (airbase === this); if (this.getElement()?.querySelector(".airbase-icon")) (this.getElement()?.querySelector(".airbase-icon") as HTMLElement).dataset.selected = `${this.#selected}`; - }) + }); - this.addEventListener("click", (ev) => { + this.addEventListener("mousedown", (ev) => { if (getApp().getState() === OlympusState.IDLE || getApp().getState() === OlympusState.AIRBASE) { - getApp().setState(OlympusState.AIRBASE) - AirbaseSelectedEvent.dispatch(this) + DomEvent.stop(ev); + ev.originalEvent.stopImmediatePropagation(); + } + }); + + this.addEventListener("mouseup", (ev) => { + if (getApp().getState() === OlympusState.IDLE || getApp().getState() === OlympusState.AIRBASE) { + DomEvent.stop(ev); + ev.originalEvent.stopImmediatePropagation(); + getApp().setState(OlympusState.AIRBASE); + AirbaseSelectedEvent.dispatch(this); } }); } @@ -110,4 +118,8 @@ export class Airbase extends CustomMarker { getImg() { return this.#img; } + + getSelected() { + return this.#selected; + } } diff --git a/frontend/react/src/mission/missionmanager.ts b/frontend/react/src/mission/missionmanager.ts index 4d565abe..82b2b646 100644 --- a/frontend/react/src/mission/missionmanager.ts +++ b/frontend/react/src/mission/missionmanager.ts @@ -6,8 +6,7 @@ import { BLUE_COMMANDER, GAME_MASTER, NONE, RED_COMMANDER } from "../constants/c import { AirbasesData, BullseyesData, CommandModeOptions, DateAndTime, MissionData } from "../interfaces"; import { Coalition } from "../types/types"; import { Carrier } from "./carrier"; -import { NavyUnit } from "../unit/unit"; -import { BullseyesDataChanged, CommandModeOptionsChangedEvent, InfoPopupEvent } from "../events"; +import { AirbaseSelectedEvent, AppStateChangedEvent, BullseyesDataChanged, CommandModeOptionsChangedEvent, InfoPopupEvent } from "../events"; /** The MissionManager */ export class MissionManager { @@ -34,7 +33,12 @@ export class MissionManager { #spentSpawnPoint: number = 0; #coalitions: { red: string[]; blue: string[] } = { red: [], blue: [] }; - constructor() {} + constructor() { + AppStateChangedEvent.on((state, subState) => { + if (this.getSelectedAirbase() !== null) AirbaseSelectedEvent.dispatch(null); + }) + + } /** Update location of bullseyes * @@ -209,6 +213,14 @@ export class MissionManager { return this.#frameRate; } + getSelectedAirbase() { + const airbase = Object.values(this.#airbases).find((airbase: Airbase | Carrier) => { + return airbase.getSelected(); + }); + + return airbase ?? null; + } + #setcommandModeOptions(commandModeOptions: CommandModeOptions) { /* Refresh all the data if we have exited the NONE state */ var requestRefresh = false; diff --git a/frontend/react/src/shortcut/shortcut.ts b/frontend/react/src/shortcut/shortcut.ts index 406b097b..8f915653 100644 --- a/frontend/react/src/shortcut/shortcut.ts +++ b/frontend/react/src/shortcut/shortcut.ts @@ -1,4 +1,5 @@ -import { AppStateChangedEvent, ShortcutChangedEvent, ShortcutsChangedEvent } from "../events"; +import { OlympusState } from "../constants/constants"; +import { AppStateChangedEvent, ModalEvent, ShortcutChangedEvent, ShortcutsChangedEvent } from "../events"; import { ShortcutOptions } from "../interfaces"; import { keyEventWasInInput } from "../other/utils"; @@ -6,34 +7,40 @@ export class Shortcut { #id: string; #options: ShortcutOptions; #keydown: boolean = false; + #modal: boolean = false; constructor(id, options: ShortcutOptions) { this.#id = id; this.#options = options; - AppStateChangedEvent.on(() => (this.#keydown = false)); + AppStateChangedEvent.on((state, subState) => (this.#keydown = false)); + ModalEvent.on((modal) => (this.#modal = modal)) /* On keyup, it is enough to check the code only, not the entire combination */ document.addEventListener("keyup", (ev: any) => { - if (this.#keydown && options.code === ev.code) { + if (this.#modal) return; + if (this.#keydown && this.getOptions().code === ev.code) { + console.log(`Keydown up for shortcut ${this.#id}`) ev.preventDefault(); - options.keyUpCallback(ev); this.#keydown = false; + this.getOptions().keyUpCallback(ev); } }); /* On keydown, check exactly if the requested key combination is being pressed */ document.addEventListener("keydown", (ev: any) => { + if (this.#modal) return; if ( - !(this.#keydown || keyEventWasInInput(ev) || options.code !== ev.code) && - (options.altKey === undefined || ev.altKey === (options.altKey ?? ev.code.indexOf("Alt") >= 0)) && - (options.ctrlKey === undefined || ev.ctrlKey === (options.ctrlKey ?? ev.code.indexOf("Control") >= 0)) && - (options.shiftKey === undefined || ev.shiftKey === (options.shiftKey ?? ev.code.indexOf("Shift") >= 0)) + !(this.#keydown || keyEventWasInInput(ev) || this.getOptions().code !== ev.code) && + (this.getOptions().altKey === undefined || ev.altKey === (this.getOptions().altKey ?? ev.code.indexOf("Alt") >= 0)) && + (this.getOptions().ctrlKey === undefined || ev.ctrlKey === (this.getOptions().ctrlKey ?? ev.code.indexOf("Control") >= 0)) && + (this.getOptions().shiftKey === undefined || ev.shiftKey === (this.getOptions().shiftKey ?? ev.code.indexOf("Shift") >= 0)) ) { + console.log(`Keydown event for shortcut ${this.#id}`) ev.preventDefault(); this.#keydown = true; - - if (options.keyDownCallback) options.keyDownCallback(ev); /* Key down event is optional */ + const keyDownCallback = this.getOptions().keyDownCallback + if (keyDownCallback) keyDownCallback(ev); /* Key down event is optional */ } }); } @@ -43,7 +50,7 @@ export class Shortcut { } setOptions(options: ShortcutOptions) { - this.#options = { ...options }; + this.#options = { ...this.#options, ...options }; } getId() { @@ -54,14 +61,17 @@ export class Shortcut { let actions: string[] = []; if (this.getOptions().shiftKey) actions.push("Shift"); if (this.getOptions().altKey) actions.push("Alt"); - if (this.getOptions().ctrlKey) actions.push("Ctrl") - actions.push(this.getOptions().code.replace("Key", "") - .replace("ControlLeft", "Left Ctrl") - .replace("AltLeft", "Left Alt") - .replace("ShiftLeft", "Left Shift") - .replace("ControlRight", "Right Ctrl") - .replace("AltRight", "Right Alt") - .replace("ShiftRight", "Right Shift")) - return actions + if (this.getOptions().ctrlKey) actions.push("Ctrl"); + actions.push( + this.getOptions() + .code.replace("Key", "") + .replace("ControlLeft", "Left Ctrl") + .replace("AltLeft", "Left Alt") + .replace("ShiftLeft", "Left Shift") + .replace("ControlRight", "Right Ctrl") + .replace("AltRight", "Right Alt") + .replace("ShiftRight", "Right Shift") + ); + return actions; } } diff --git a/frontend/react/src/shortcut/shortcutmanager.ts b/frontend/react/src/shortcut/shortcutmanager.ts index e5ad2af2..3cdb7de6 100644 --- a/frontend/react/src/shortcut/shortcutmanager.ts +++ b/frontend/react/src/shortcut/shortcutmanager.ts @@ -1,4 +1,4 @@ -import { ShortcutChangedEvent, ShortcutsChangedEvent } from "../events"; +import { ShortcutsChangedEvent } from "../events"; import { ShortcutOptions } from "../interfaces"; import { Shortcut } from "./shortcut"; diff --git a/frontend/react/src/ui/modals/components/modal.tsx b/frontend/react/src/ui/modals/components/modal.tsx index 9b0eb9bc..1592944a 100644 --- a/frontend/react/src/ui/modals/components/modal.tsx +++ b/frontend/react/src/ui/modals/components/modal.tsx @@ -1,15 +1,28 @@ -import React from "react"; +import React, { useEffect } from "react"; +import { ModalEvent } from "../../../events"; + +export function Modal(props: { open: boolean; children?: JSX.Element | JSX.Element[]; className?: string }) { + useEffect(() => { + ModalEvent.dispatch(props.open); + }, [props.open]); -export function Modal(props: { grayout?: boolean; children?: JSX.Element | JSX.Element[]; className?: string }) { return ( -
- {props.children} -
+ <> + {props.open && ( + <> +
+
+ {props.children} +
+ + )} + ); } diff --git a/frontend/react/src/ui/modals/keybindmodal.tsx b/frontend/react/src/ui/modals/keybindmodal.tsx index a2e1146e..47a22852 100644 --- a/frontend/react/src/ui/modals/keybindmodal.tsx +++ b/frontend/react/src/ui/modals/keybindmodal.tsx @@ -6,7 +6,6 @@ import { getApp } from "../../olympusapp"; import { OlympusState } from "../../constants/constants"; import { Shortcut } from "../../shortcut/shortcut"; import { BindShortcutRequestEvent, ShortcutsChangedEvent } from "../../events"; -import { OlToggle } from "../components/oltoggle"; export function KeybindModal(props: { open: boolean }) { const [shortcuts, setShortcuts] = useState({} as { [key: string]: Shortcut }); @@ -23,180 +22,139 @@ export function KeybindModal(props: { open: boolean }) { document.addEventListener("keydown", (ev) => { if (ev.code) { setCode(ev.code); + if (ev.code.indexOf("Control") < 0) setCtrlKey(ev.ctrlKey); + else setCtrlKey(false); + if (ev.code.indexOf("Shift") < 0) setShiftKey(ev.shiftKey); + else setShiftKey(false); + if (ev.code.indexOf("Alt") < 0) setAltKey(ev.altKey); + else setAltKey(false); } }); }, []); useEffect(() => { - setCode(shortcut?.getOptions().code ?? null) - setShiftKey(shortcut?.getOptions().shiftKey) - setAltKey(shortcut?.getOptions().altKey) - setCtrlKey(shortcut?.getOptions().ctrlKey) - }, [shortcut]) + setCode(shortcut?.getOptions().code ?? null); + setShiftKey(shortcut?.getOptions().shiftKey); + setAltKey(shortcut?.getOptions().altKey); + setCtrlKey(shortcut?.getOptions().ctrlKey); + }, [shortcut]); let available: null | boolean = code ? true : null; - let inUseShortcut: null | Shortcut = null; + let inUseShortcuts: Shortcut[] = []; for (let id in shortcuts) { - if (id !== shortcut?.getId() && + if ( + id !== shortcut?.getId() && code === shortcuts[id].getOptions().code && ((shiftKey === undefined && shortcuts[id].getOptions().shiftKey !== undefined) || (shiftKey !== undefined && shortcuts[id].getOptions().shiftKey === undefined) || - shiftKey === shortcuts[id].getOptions().shiftKey) && ( - (altKey === undefined && shortcuts[id].getOptions().altKey !== undefined) || + shiftKey === shortcuts[id].getOptions().shiftKey) && + ((altKey === undefined && shortcuts[id].getOptions().altKey !== undefined) || (altKey !== undefined && shortcuts[id].getOptions().altKey === undefined) || - altKey === shortcuts[id].getOptions().altKey) && ( - (ctrlKey === undefined && shortcuts[id].getOptions().ctrlKey !== undefined) || + altKey === shortcuts[id].getOptions().altKey) && + ((ctrlKey === undefined && shortcuts[id].getOptions().ctrlKey !== undefined) || (ctrlKey !== undefined && shortcuts[id].getOptions().ctrlKey === undefined) || ctrlKey === shortcuts[id].getOptions().ctrlKey) ) { available = false; - inUseShortcut = shortcuts[id]; + inUseShortcuts.push(shortcuts[id]); } } return ( - <> - {props.open && ( - <> - +
+
+ -
-
- - {shortcut?.getOptions().label} - - - Press the key you want to bind to this event - -
-
{code}
-
-
- { - if (shiftKey === false) setShiftKey(undefined); - else if (shiftKey === undefined) setShiftKey(true); - else setShiftKey(false); - }} - toggled={shiftKey} - > -
- {shiftKey === true && "Shift key must be pressed"} - {shiftKey === undefined && "Shift key can be anything"} - {shiftKey === false && "Shift key must NOT be pressed"} -
-
-
- { - if (altKey === false) setAltKey(undefined); - else if (altKey === undefined) setAltKey(true); - else setAltKey(false); - }} - toggled={altKey} - > -
- {altKey === true && "Alt key must be pressed"} - {altKey === undefined && "Alt key can be anything"} - {altKey === false && "Alt key must NOT be pressed"} -
-
-
- { - if (ctrlKey === false) setCtrlKey(undefined); - else if (ctrlKey === undefined) setCtrlKey(true); - else setCtrlKey(false); - }} - toggled={ctrlKey} - > -
- {ctrlKey === true && "Ctrl key must be pressed"} - {ctrlKey === undefined && "Ctrl key can be anything"} - {ctrlKey === false && "Ctrl key must NOT be pressed"} -
-
- -
-
- {available === true &&
Keybind is free!
} - {available === false && ( -
- Keybind is already in use: {inUseShortcut?.getOptions().label} -
- )} -
- -
- {available && shortcut && ( - - )} - + {shortcut?.getOptions().label} + + + Press the key you want to bind to this event + +
+
+ {ctrlKey && "Ctrl + "} + {altKey && "Alt + "} + {shiftKey && "Shift + "} + {code} +
+
+ {available === true &&
Keybind is free!
} + {available === false && ( +
+
+ Keybind is already in use:
{inUseShortcuts.map((shortcut) => {shortcut.getOptions().label})}
+
A key combination can be assigned to multiple actions, and all bound actions will fire
- -
- - )} - + )} +
+ +
+ {shortcut && ( + + )} + +
+
+ ); } diff --git a/frontend/react/src/ui/modals/login.tsx b/frontend/react/src/ui/modals/loginmodal.tsx similarity index 96% rename from frontend/react/src/ui/modals/login.tsx rename to frontend/react/src/ui/modals/loginmodal.tsx index 99c0f4a6..c7d0aa8c 100644 --- a/frontend/react/src/ui/modals/login.tsx +++ b/frontend/react/src/ui/modals/loginmodal.tsx @@ -1,26 +1,25 @@ import React, { useEffect, useState } from "react"; import { Modal } from "./components/modal"; import { Card } from "./components/card"; -import { ErrorCallout } from "../../ui/components/olcallout"; +import { ErrorCallout } from "../components/olcallout"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faArrowRight, faCheckCircle, faExternalLink } from "@fortawesome/free-solid-svg-icons"; import { getApp, VERSION } from "../../olympusapp"; import { sha256 } from "js-sha256"; import { BLUE_COMMANDER, GAME_MASTER, OlympusState, RED_COMMANDER } from "../../constants/constants"; -import { FaTrash, FaXmark } from "react-icons/fa6"; -export function LoginModal(props: {}) { +export function LoginModal(props: { open: boolean }) { // TODO: add warning if not in secure context and some features are disabled const [password, setPassword] = useState(""); - const [profileName, setProfileName] = useState("Game Master"); + const [profileName, setProfileName] = useState(""); const [checkingPassword, setCheckingPassword] = useState(false); const [loginError, setLoginError] = useState(false); const [commandMode, setCommandMode] = useState(null as null | string); useEffect(() => { /* Set the profile name */ - getApp().setProfile(profileName); - }, [profileName]) + if (profileName !== "") getApp().setProfile(profileName); + }, [profileName]); function checkPassword(password: string) { setCheckingPassword(true); @@ -51,14 +50,14 @@ export function LoginModal(props: {}) { getApp().setState(OlympusState.IDLE); /* If no profile exists already with that name, create it from scratch from the defaults */ - if (getApp().getProfile() === null) - getApp().saveProfile(); + if (getApp().getProfile() === null) getApp().saveProfile(); /* Load the profile */ getApp().loadProfile(); } return (
-
- The profile name you choose determines the saved key binds, groups and options you see. -
+
The profile name you choose determines the saved key binds, groups and options you see.
- -
-
-
-
- - )} - - ); -} diff --git a/frontend/react/src/ui/modals/protectionpromptmodal.tsx b/frontend/react/src/ui/modals/protectionpromptmodal.tsx new file mode 100644 index 00000000..d8d0d230 --- /dev/null +++ b/frontend/react/src/ui/modals/protectionpromptmodal.tsx @@ -0,0 +1,101 @@ +import React from "react"; +import { Modal } from "./components/modal"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faArrowRight } from "@fortawesome/free-solid-svg-icons"; +import { FaLock } from "react-icons/fa6"; +import { getApp } from "../../olympusapp"; +import { OlympusState } from "../../constants/constants"; + +export function ProtectionPromptModal(props: { open: boolean }) { + return ( + +
+
+ + Your selection contains protected units, are you sure you want to continue? + + + Pressing "Continue" will cause all DCS controlled units in the current selection to abort their mission and start following Olympus commands only. + + + If you are trying to delete a human player unit, they will be killed and de-slotted. Be careful! + + + To disable this warning, press on the{" "} + + + {" "} + button + +
+
+ + +
+
+
+ ); +} diff --git a/frontend/react/src/ui/panels/airbasemenu.tsx b/frontend/react/src/ui/panels/airbasemenu.tsx index 70d93de3..923aa04e 100644 --- a/frontend/react/src/ui/panels/airbasemenu.tsx +++ b/frontend/react/src/ui/panels/airbasemenu.tsx @@ -114,7 +114,7 @@ export function AirbaseMenu(props: { open: boolean; onClose: () => void; childre
{airbase?.getChartData().runways.map((runway, idx) => { return ( - <> +
{Object.keys(runway.headings[0]).map((runwayName) => { return (
void; childre
); })} - +
); })}
diff --git a/frontend/react/src/ui/panels/audiomenu.tsx b/frontend/react/src/ui/panels/audiomenu.tsx index 66a83330..49729a21 100644 --- a/frontend/react/src/ui/panels/audiomenu.tsx +++ b/frontend/react/src/ui/panels/audiomenu.tsx @@ -19,7 +19,7 @@ export function AudioMenu(props: { open: boolean; onClose: () => void; children? const [audioManagerEnabled, setAudioManagerEnabled] = useState(false); const [activeSource, setActiveSource] = useState(null as AudioSource | null); const [count, setCount] = useState(0); - const [shortcuts, setShortcuts] = useState({}) + const [shortcuts, setShortcuts] = useState({}); /* Preallocate 128 references for the source and sink panels. If the number of references changes, React will give an error */ const sourceRefs = Array(128) @@ -251,20 +251,19 @@ export function AudioMenu(props: { open: boolean; onClose: () => void; children? right: (end as HTMLDivElement).offsetLeft + (end as HTMLDivElement).clientWidth, }; return ( - <> -
- +
); } }) @@ -281,29 +280,26 @@ export function AudioMenu(props: { open: boolean; onClose: () => void; children? right: (div as HTMLDivElement).offsetLeft + (div as HTMLDivElement).clientWidth, }; return ( - <> -
-
{ - activeSource !== sources[idx] ? setActiveSource(sources[idx]) : setActiveSource(null); - }} - > - -
-
- +
{ + activeSource !== sources[idx] ? setActiveSource(sources[idx]) : setActiveSource(null); + }} + > + +
); } })} @@ -318,28 +314,25 @@ export function AudioMenu(props: { open: boolean; onClose: () => void; children? right: (div as HTMLDivElement).offsetLeft + (div as HTMLDivElement).clientWidth, }; return ( - <> -
-
{ - if (activeSource.getConnectedTo().includes(sinks[idx])) activeSource.disconnect(sinks[idx]); - else activeSource.connect(sinks[idx]); - }} - > - {" "} - {activeSource.getConnectedTo().includes(sinks[idx]) ? : } -
-
- +
{ + if (activeSource.getConnectedTo().includes(sinks[idx])) activeSource.disconnect(sinks[idx]); + else activeSource.connect(sinks[idx]); + }} + > + {" "} + {activeSource.getConnectedTo().includes(sinks[idx]) ? : } +
); } })} diff --git a/frontend/react/src/ui/panels/controlspanel.tsx b/frontend/react/src/ui/panels/controlspanel.tsx index 9488daeb..fda86a97 100644 --- a/frontend/react/src/ui/panels/controlspanel.tsx +++ b/frontend/react/src/ui/panels/controlspanel.tsx @@ -46,21 +46,14 @@ export function ControlsPanel(props: {}) { text: "Select unit", }, { - actions: [touch ? faHandPointer : "LMB", "Drag"], + actions: ["Shift", "LMB", "Drag"], text: "Box selection", }, { - actions: [touch ? faHandPointer : "Wheel", "Drag"], + actions: [touch ? faHandPointer : "LMB", "Drag"], text: "Move map", }, ]; - if (!touch) { - controls.push({ - actions: ["Shift", "LMB", "Drag"], - - text: "Box selection", - }); - } if (appState === OlympusState.IDLE) { controls = baseControls; diff --git a/frontend/react/src/ui/panels/gamemastermenu.tsx b/frontend/react/src/ui/panels/gamemastermenu.tsx index e14f6bdb..49a22f60 100644 --- a/frontend/react/src/ui/panels/gamemastermenu.tsx +++ b/frontend/react/src/ui/panels/gamemastermenu.tsx @@ -38,20 +38,12 @@ export function GameMasterMenu(props: { open: boolean; onClose: () => void; chil GAME MASTER
)} - {commandModeOptions.commandMode === BLUE_COMMANDER && ( -
- BLUE COMMANDER -
- )} - {commandModeOptions.commandMode === RED_COMMANDER && ( -
- RED COMMANDER -
- )} + {commandModeOptions.commandMode === BLUE_COMMANDER &&
BLUE COMMANDER
} + {commandModeOptions.commandMode === RED_COMMANDER &&
RED COMMANDER
} {serverStatus.elapsedTime > currentSetupTime && (
void; chil .map((era) => { return (
void; chil group flex flex-row rounded-md justify-content gap-4 px-4 py-2 `} > - Elapsed time (seconds) {serverStatus.elapsedTime?.toFixed()} + Elapsed time (seconds){" "} + + {serverStatus.elapsedTime?.toFixed()} +
{commandModeOptions.commandMode === GAME_MASTER && (