mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
Multiple bugfixes
This commit is contained in:
parent
5c3960868a
commit
897afb2fef
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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) => {
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { ShortcutChangedEvent, ShortcutsChangedEvent } from "../events";
|
||||
import { ShortcutsChangedEvent } from "../events";
|
||||
import { ShortcutOptions } from "../interfaces";
|
||||
import { Shortcut } from "./shortcut";
|
||||
|
||||
|
||||
@ -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 (
|
||||
<div
|
||||
className={`
|
||||
${props.className}
|
||||
fixed left-[50%] top-[50%] z-40 translate-x-[-50%] translate-y-[-50%]
|
||||
rounded-xl border-[1px] border-solid border-gray-700 drop-shadow-md
|
||||
`}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
<>
|
||||
{props.open && (
|
||||
<>
|
||||
<div className={`fixed left-0 top-0 z-30 h-full w-full bg-[#111111]/95`}></div>
|
||||
<div
|
||||
className={`
|
||||
${props.className}
|
||||
fixed left-[50%] top-[50%] z-40 translate-x-[-50%]
|
||||
translate-y-[-50%] rounded-xl border-[1px] border-solid
|
||||
border-gray-700 drop-shadow-md
|
||||
`}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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 && (
|
||||
<>
|
||||
<Modal
|
||||
<Modal
|
||||
open={props.open}
|
||||
className={`
|
||||
inline-flex h-fit w-[600px] overflow-y-auto scroll-smooth bg-white p-10
|
||||
dark:bg-olympus-800
|
||||
max-md:h-full max-md:max-h-full max-md:w-full max-md:rounded-none
|
||||
max-md:border-none
|
||||
`}
|
||||
>
|
||||
<div className="flex h-full w-full flex-col gap-4">
|
||||
<div className={`flex flex-col items-start gap-2`}>
|
||||
<span
|
||||
className={`
|
||||
inline-flex h-fit w-[600px] overflow-y-auto scroll-smooth bg-white
|
||||
p-10
|
||||
dark:bg-olympus-800
|
||||
max-md:h-full max-md:max-h-full max-md:w-full max-md:rounded-none
|
||||
max-md:border-none
|
||||
text-gray-800 text-md
|
||||
dark:text-white
|
||||
`}
|
||||
>
|
||||
<div className="flex h-full w-full flex-col gap-4">
|
||||
<div className={`flex flex-col items-start gap-2`}>
|
||||
<span
|
||||
className={`
|
||||
text-gray-800 text-md
|
||||
dark:text-white
|
||||
`}
|
||||
>
|
||||
{shortcut?.getOptions().label}
|
||||
</span>
|
||||
<span
|
||||
className={`
|
||||
text-gray-800 text-md
|
||||
dark:text-gray-500
|
||||
`}
|
||||
>
|
||||
Press the key you want to bind to this event
|
||||
</span>
|
||||
</div>
|
||||
<div className="w-full text-center text-white">{code}</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex gap-2">
|
||||
<OlToggle
|
||||
onClick={() => {
|
||||
if (shiftKey === false) setShiftKey(undefined);
|
||||
else if (shiftKey === undefined) setShiftKey(true);
|
||||
else setShiftKey(false);
|
||||
}}
|
||||
toggled={shiftKey}
|
||||
></OlToggle>
|
||||
<div className="text-white">
|
||||
{shiftKey === true && "Shift key must be pressed"}
|
||||
{shiftKey === undefined && "Shift key can be anything"}
|
||||
{shiftKey === false && "Shift key must NOT be pressed"}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<OlToggle
|
||||
onClick={() => {
|
||||
if (altKey === false) setAltKey(undefined);
|
||||
else if (altKey === undefined) setAltKey(true);
|
||||
else setAltKey(false);
|
||||
}}
|
||||
toggled={altKey}
|
||||
></OlToggle>
|
||||
<div className="text-white">
|
||||
{altKey === true && "Alt key must be pressed"}
|
||||
{altKey === undefined && "Alt key can be anything"}
|
||||
{altKey === false && "Alt key must NOT be pressed"}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<OlToggle
|
||||
onClick={() => {
|
||||
if (ctrlKey === false) setCtrlKey(undefined);
|
||||
else if (ctrlKey === undefined) setCtrlKey(true);
|
||||
else setCtrlKey(false);
|
||||
}}
|
||||
toggled={ctrlKey}
|
||||
></OlToggle>
|
||||
<div className="text-white">
|
||||
{ctrlKey === true && "Ctrl key must be pressed"}
|
||||
{ctrlKey === undefined && "Ctrl key can be anything"}
|
||||
{ctrlKey === false && "Ctrl key must NOT be pressed"}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div className="text-white">
|
||||
{available === true && <div className="text-green-600">Keybind is free!</div>}
|
||||
{available === false && (
|
||||
<div>
|
||||
Keybind is already in use: <span className={`
|
||||
font-bold text-red-600
|
||||
`}>{inUseShortcut?.getOptions().label}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end">
|
||||
{available && shortcut && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
if (shortcut && code) {
|
||||
let options = shortcut.getOptions();
|
||||
options.code = code;
|
||||
options.altKey = altKey;
|
||||
options.shiftKey = shiftKey;
|
||||
options.ctrlKey = ctrlKey;
|
||||
getApp().getShortcutManager().setShortcutOption(shortcut.getId(), options);
|
||||
|
||||
getApp().setState(OlympusState.OPTIONS);
|
||||
}
|
||||
}}
|
||||
className={`
|
||||
mb-2 me-2 ml-auto flex content-center items-center gap-2
|
||||
rounded-sm bg-blue-700 px-5 py-2.5 text-sm font-medium
|
||||
text-white
|
||||
dark:bg-blue-600 dark:hover:bg-blue-700
|
||||
dark:focus:ring-blue-800
|
||||
focus:outline-none focus:ring-4 focus:ring-blue-300
|
||||
hover:bg-blue-800
|
||||
`}
|
||||
>
|
||||
Continue
|
||||
<FontAwesomeIcon icon={faArrowRight} />
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => getApp().setState(OlympusState.OPTIONS)}
|
||||
className={`
|
||||
mb-2 me-2 flex content-center items-center gap-2 rounded-sm
|
||||
border-[1px] bg-blue-700 px-5 py-2.5 text-sm font-medium
|
||||
text-white
|
||||
dark:border-gray-600 dark:bg-gray-800 dark:text-gray-400
|
||||
dark:hover:bg-gray-700 dark:focus:ring-blue-800
|
||||
focus:outline-none focus:ring-4 focus:ring-blue-300
|
||||
hover:bg-blue-800
|
||||
`}
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
{shortcut?.getOptions().label}
|
||||
</span>
|
||||
<span
|
||||
className={`
|
||||
text-gray-800 text-md
|
||||
dark:text-gray-500
|
||||
`}
|
||||
>
|
||||
Press the key you want to bind to this event
|
||||
</span>
|
||||
</div>
|
||||
<div className="w-full text-center text-white">
|
||||
{ctrlKey && "Ctrl + "}
|
||||
{altKey && "Alt + "}
|
||||
{shiftKey && "Shift + "}
|
||||
{code}
|
||||
</div>
|
||||
<div className="text-white">
|
||||
{available === true && <div className="text-green-600">Keybind is free!</div>}
|
||||
{available === false && (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex gap-2">
|
||||
Keybind is already in use: <div className={`
|
||||
flex flex-wrap gap-2 font-bold text-orange-600
|
||||
`}>{inUseShortcuts.map((shortcut) => <span>{shortcut.getOptions().label}</span>)}</div>
|
||||
</div>
|
||||
<div className="text-gray-500">A key combination can be assigned to multiple actions, and all bound actions will fire</div>
|
||||
</div>
|
||||
</Modal>
|
||||
<div className={`fixed left-0 top-0 z-30 h-full w-full bg-[#111111]/95`}></div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end">
|
||||
{shortcut && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
if (shortcut && code) {
|
||||
let options = shortcut.getOptions();
|
||||
options.code = code;
|
||||
options.altKey = altKey;
|
||||
options.shiftKey = shiftKey;
|
||||
options.ctrlKey = ctrlKey;
|
||||
getApp().getShortcutManager().setShortcutOption(shortcut.getId(), options);
|
||||
|
||||
getApp().setState(OlympusState.OPTIONS);
|
||||
}
|
||||
}}
|
||||
className={`
|
||||
mb-2 me-2 ml-auto flex content-center items-center gap-2
|
||||
rounded-sm bg-blue-700 px-5 py-2.5 text-sm font-medium
|
||||
text-white
|
||||
dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800
|
||||
focus:outline-none focus:ring-4 focus:ring-blue-300
|
||||
hover:bg-blue-800
|
||||
`}
|
||||
>
|
||||
Continue
|
||||
<FontAwesomeIcon icon={faArrowRight} />
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => getApp().setState(OlympusState.OPTIONS)}
|
||||
className={`
|
||||
mb-2 me-2 flex content-center items-center gap-2 rounded-sm
|
||||
border-[1px] bg-blue-700 px-5 py-2.5 text-sm font-medium
|
||||
text-white
|
||||
dark:border-gray-600 dark:bg-gray-800 dark:text-gray-400
|
||||
dark:hover:bg-gray-700 dark:focus:ring-blue-800
|
||||
focus:outline-none focus:ring-4 focus:ring-blue-300
|
||||
hover:bg-blue-800
|
||||
`}
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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 (
|
||||
<Modal
|
||||
open={props.open}
|
||||
className={`
|
||||
inline-flex h-[75%] max-h-[570px] w-[80%] max-w-[1100px] overflow-y-auto
|
||||
scroll-smooth bg-white
|
||||
@ -239,9 +238,7 @@ export function LoginModal(props: {}) {
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="text-xs text-gray-400">
|
||||
The profile name you choose determines the saved key binds, groups and options you see.
|
||||
</div>
|
||||
<div className="text-xs text-gray-400">The profile name you choose determines the saved key binds, groups and options you see.</div>
|
||||
<div className="flex">
|
||||
<button
|
||||
type="button"
|
||||
@ -1,111 +0,0 @@
|
||||
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 ProtectionPrompt(props: { open: boolean }) {
|
||||
return (
|
||||
<>
|
||||
{props.open && (
|
||||
<>
|
||||
<Modal
|
||||
className={`
|
||||
inline-flex h-fit w-[600px] overflow-y-auto scroll-smooth bg-white
|
||||
p-10
|
||||
dark:bg-olympus-800
|
||||
max-md:h-full max-md:max-h-full max-md:w-full max-md:rounded-none
|
||||
max-md:border-none
|
||||
`}
|
||||
>
|
||||
<div className="flex h-full w-full flex-col gap-12">
|
||||
<div className={`flex flex-col items-start gap-2`}>
|
||||
<span
|
||||
className={`
|
||||
text-gray-800 text-md
|
||||
dark:text-white
|
||||
`}
|
||||
>
|
||||
Your selection contains protected units, are you sure you want to continue?
|
||||
</span>
|
||||
<span
|
||||
className={`
|
||||
text-gray-800 text-md
|
||||
dark:text-gray-500
|
||||
`}
|
||||
>
|
||||
Pressing "Continue" will cause all DCS controlled units in the current selection to abort their mission and start following Olympus commands
|
||||
only.
|
||||
</span>
|
||||
<span
|
||||
className={`
|
||||
text-gray-800 text-md
|
||||
dark:text-gray-500
|
||||
`}
|
||||
>
|
||||
If you are trying to delete a human player unit, they will be killed and de-slotted. Be careful!
|
||||
</span>
|
||||
<span
|
||||
className={`
|
||||
text-gray-800 text-md
|
||||
dark:text-gray-500
|
||||
`}
|
||||
>
|
||||
To disable this warning, press on the{" "}
|
||||
<span
|
||||
className={`
|
||||
inline-block translate-y-3 rounded-full border-[1px]
|
||||
border-gray-900 bg-red-500 p-2 text-olympus-900
|
||||
`}
|
||||
>
|
||||
<FaLock />
|
||||
</span>{" "}
|
||||
button
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
getApp().getUnitsManager().executeProtectionCallback();
|
||||
getApp().setState(OlympusState.UNIT_CONTROL);
|
||||
}}
|
||||
className={`
|
||||
mb-2 me-2 ml-auto flex content-center items-center gap-2
|
||||
rounded-sm bg-blue-700 px-5 py-2.5 text-sm font-medium
|
||||
text-white
|
||||
dark:bg-blue-600 dark:hover:bg-blue-700
|
||||
dark:focus:ring-blue-800
|
||||
focus:outline-none focus:ring-4 focus:ring-blue-300
|
||||
hover:bg-blue-800
|
||||
`}
|
||||
>
|
||||
Continue
|
||||
<FontAwesomeIcon className={`my-auto`} icon={faArrowRight} />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => getApp().setState(OlympusState.UNIT_CONTROL)}
|
||||
className={`
|
||||
mb-2 me-2 flex content-center items-center gap-2 rounded-sm
|
||||
border-[1px] bg-blue-700 px-5 py-2.5 text-sm font-medium
|
||||
text-white
|
||||
dark:border-gray-600 dark:bg-gray-800 dark:text-gray-400
|
||||
dark:hover:bg-gray-700 dark:focus:ring-blue-800
|
||||
focus:outline-none focus:ring-4 focus:ring-blue-300
|
||||
hover:bg-blue-800
|
||||
`}
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
<div className={`fixed left-0 top-0 z-30 h-full w-full bg-[#111111]/95`}></div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
101
frontend/react/src/ui/modals/protectionpromptmodal.tsx
Normal file
101
frontend/react/src/ui/modals/protectionpromptmodal.tsx
Normal file
@ -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 (
|
||||
<Modal
|
||||
open={props.open}
|
||||
className={`
|
||||
inline-flex h-fit w-[600px] overflow-y-auto scroll-smooth bg-white p-10
|
||||
dark:bg-olympus-800
|
||||
max-md:h-full max-md:max-h-full max-md:w-full max-md:rounded-none
|
||||
max-md:border-none
|
||||
`}
|
||||
>
|
||||
<div className="flex h-full w-full flex-col gap-12">
|
||||
<div className={`flex flex-col items-start gap-2`}>
|
||||
<span
|
||||
className={`
|
||||
text-gray-800 text-md
|
||||
dark:text-white
|
||||
`}
|
||||
>
|
||||
Your selection contains protected units, are you sure you want to continue?
|
||||
</span>
|
||||
<span
|
||||
className={`
|
||||
text-gray-800 text-md
|
||||
dark:text-gray-500
|
||||
`}
|
||||
>
|
||||
Pressing "Continue" will cause all DCS controlled units in the current selection to abort their mission and start following Olympus commands only.
|
||||
</span>
|
||||
<span
|
||||
className={`
|
||||
text-gray-800 text-md
|
||||
dark:text-gray-500
|
||||
`}
|
||||
>
|
||||
If you are trying to delete a human player unit, they will be killed and de-slotted. Be careful!
|
||||
</span>
|
||||
<span
|
||||
className={`
|
||||
text-gray-800 text-md
|
||||
dark:text-gray-500
|
||||
`}
|
||||
>
|
||||
To disable this warning, press on the{" "}
|
||||
<span
|
||||
className={`
|
||||
inline-block translate-y-3 rounded-full border-[1px]
|
||||
border-gray-900 bg-red-500 p-2 text-olympus-900
|
||||
`}
|
||||
>
|
||||
<FaLock />
|
||||
</span>{" "}
|
||||
button
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
getApp().getUnitsManager().executeProtectionCallback();
|
||||
getApp().setState(OlympusState.UNIT_CONTROL);
|
||||
}}
|
||||
className={`
|
||||
mb-2 me-2 ml-auto flex content-center items-center gap-2
|
||||
rounded-sm bg-blue-700 px-5 py-2.5 text-sm font-medium text-white
|
||||
dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800
|
||||
focus:outline-none focus:ring-4 focus:ring-blue-300
|
||||
hover:bg-blue-800
|
||||
`}
|
||||
>
|
||||
Continue
|
||||
<FontAwesomeIcon className={`my-auto`} icon={faArrowRight} />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => getApp().setState(OlympusState.UNIT_CONTROL)}
|
||||
className={`
|
||||
mb-2 me-2 flex content-center items-center gap-2 rounded-sm
|
||||
border-[1px] bg-blue-700 px-5 py-2.5 text-sm font-medium
|
||||
text-white
|
||||
dark:border-gray-600 dark:bg-gray-800 dark:text-gray-400
|
||||
dark:hover:bg-gray-700 dark:focus:ring-blue-800
|
||||
focus:outline-none focus:ring-4 focus:ring-blue-300
|
||||
hover:bg-blue-800
|
||||
`}
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@ -114,7 +114,7 @@ export function AirbaseMenu(props: { open: boolean; onClose: () => void; childre
|
||||
<div className="flex flex-col gap-2">
|
||||
{airbase?.getChartData().runways.map((runway, idx) => {
|
||||
return (
|
||||
<>
|
||||
<div key={idx}>
|
||||
{Object.keys(runway.headings[0]).map((runwayName) => {
|
||||
return (
|
||||
<div key={`${idx}-${runwayName}`} className={`
|
||||
@ -137,7 +137,7 @@ export function AirbaseMenu(props: { open: boolean; onClose: () => void; childre
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
@ -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 (
|
||||
<>
|
||||
<div
|
||||
className={`
|
||||
absolute rounded-br-md rounded-tr-md border-2 border-l-0
|
||||
`}
|
||||
style={{
|
||||
top: `${(startRect.bottom + startRect.top) / 2}px`,
|
||||
left: `${startRect.right}px`,
|
||||
height: `${endRect.top - startRect.top + (endRect.height - startRect.height) / 2}px`,
|
||||
width: `${(lineCounters[idx] - 1) * lineDistance + 30}px`,
|
||||
borderColor: lineColors[idx],
|
||||
}}
|
||||
></div>
|
||||
</>
|
||||
<div
|
||||
key={idx}
|
||||
className={`
|
||||
absolute rounded-br-md rounded-tr-md border-2 border-l-0
|
||||
`}
|
||||
style={{
|
||||
top: `${(startRect.bottom + startRect.top) / 2}px`,
|
||||
left: `${startRect.right}px`,
|
||||
height: `${endRect.top - startRect.top + (endRect.height - startRect.height) / 2}px`,
|
||||
width: `${(lineCounters[idx] - 1) * lineDistance + 30}px`,
|
||||
borderColor: lineColors[idx],
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
})
|
||||
@ -281,29 +280,26 @@ export function AudioMenu(props: { open: boolean; onClose: () => void; children?
|
||||
right: (div as HTMLDivElement).offsetLeft + (div as HTMLDivElement).clientWidth,
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<div
|
||||
data-active={activeSource === sources[idx]}
|
||||
className={`
|
||||
absolute translate-y-[-50%] cursor-pointer rounded-full
|
||||
bg-blue-600 p-1 text-xs text-white
|
||||
data-[active='true']:bg-white
|
||||
data-[active='true']:text-blue-600
|
||||
hover:bg-blue-800
|
||||
`}
|
||||
style={{
|
||||
top: `${(divRect.bottom + divRect.top) / 2}px`,
|
||||
left: `${divRect.right - 10}px`,
|
||||
}}
|
||||
onClick={() => {
|
||||
activeSource !== sources[idx] ? setActiveSource(sources[idx]) : setActiveSource(null);
|
||||
}}
|
||||
>
|
||||
<FaPlus></FaPlus>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
<div
|
||||
key={idx}
|
||||
data-active={activeSource === sources[idx]}
|
||||
className={`
|
||||
absolute translate-y-[-50%] cursor-pointer rounded-full
|
||||
bg-blue-600 p-1 text-xs text-white
|
||||
data-[active='true']:bg-white
|
||||
data-[active='true']:text-blue-600
|
||||
hover:bg-blue-800
|
||||
`}
|
||||
style={{
|
||||
top: `${(divRect.bottom + divRect.top) / 2}px`,
|
||||
left: `${divRect.right - 10}px`,
|
||||
}}
|
||||
onClick={() => {
|
||||
activeSource !== sources[idx] ? setActiveSource(sources[idx]) : setActiveSource(null);
|
||||
}}
|
||||
>
|
||||
<FaPlus></FaPlus>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
})}
|
||||
@ -318,28 +314,25 @@ export function AudioMenu(props: { open: boolean; onClose: () => void; children?
|
||||
right: (div as HTMLDivElement).offsetLeft + (div as HTMLDivElement).clientWidth,
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<div
|
||||
className={`
|
||||
absolute translate-y-[-50%] cursor-pointer
|
||||
rounded-full bg-blue-600 p-1 text-xs text-white
|
||||
hover:bg-blue-800
|
||||
`}
|
||||
style={{
|
||||
top: `${(divRect.bottom + divRect.top) / 2}px`,
|
||||
left: `${divRect.right - 10}px`,
|
||||
}}
|
||||
onClick={() => {
|
||||
if (activeSource.getConnectedTo().includes(sinks[idx])) activeSource.disconnect(sinks[idx]);
|
||||
else activeSource.connect(sinks[idx]);
|
||||
}}
|
||||
>
|
||||
{" "}
|
||||
{activeSource.getConnectedTo().includes(sinks[idx]) ? <FaMinus></FaMinus> : <FaPlus></FaPlus>}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
<div
|
||||
key={idx}
|
||||
className={`
|
||||
absolute translate-y-[-50%] cursor-pointer rounded-full
|
||||
bg-blue-600 p-1 text-xs text-white
|
||||
hover:bg-blue-800
|
||||
`}
|
||||
style={{
|
||||
top: `${(divRect.bottom + divRect.top) / 2}px`,
|
||||
left: `${divRect.right - 10}px`,
|
||||
}}
|
||||
onClick={() => {
|
||||
if (activeSource.getConnectedTo().includes(sinks[idx])) activeSource.disconnect(sinks[idx]);
|
||||
else activeSource.connect(sinks[idx]);
|
||||
}}
|
||||
>
|
||||
{" "}
|
||||
{activeSource.getConnectedTo().includes(sinks[idx]) ? <FaMinus></FaMinus> : <FaPlus></FaPlus>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
})}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -38,20 +38,12 @@ export function GameMasterMenu(props: { open: boolean; onClose: () => void; chil
|
||||
GAME MASTER
|
||||
</div>
|
||||
)}
|
||||
{commandModeOptions.commandMode === BLUE_COMMANDER && (
|
||||
<div
|
||||
className={`w-full rounded-md bg-blue-600 p-2 text-center font-bold`}
|
||||
>
|
||||
BLUE COMMANDER
|
||||
</div>
|
||||
)}
|
||||
{commandModeOptions.commandMode === RED_COMMANDER && (
|
||||
<div
|
||||
className={`w-full rounded-md bg-red-700 p-2 text-center font-bold`}
|
||||
>
|
||||
RED COMMANDER
|
||||
</div>
|
||||
)}
|
||||
{commandModeOptions.commandMode === BLUE_COMMANDER && <div className={`
|
||||
w-full rounded-md bg-blue-600 p-2 text-center font-bold
|
||||
`}>BLUE COMMANDER</div>}
|
||||
{commandModeOptions.commandMode === RED_COMMANDER && <div className={`
|
||||
w-full rounded-md bg-red-700 p-2 text-center font-bold
|
||||
`}>RED COMMANDER</div>}
|
||||
{serverStatus.elapsedTime > currentSetupTime && (
|
||||
<div
|
||||
className={`
|
||||
@ -125,6 +117,7 @@ export function GameMasterMenu(props: { open: boolean; onClose: () => void; chil
|
||||
.map((era) => {
|
||||
return (
|
||||
<div
|
||||
key={era}
|
||||
className={`
|
||||
group flex flex-row rounded-md justify-content
|
||||
cursor-pointer gap-4 p-2
|
||||
@ -274,9 +267,12 @@ export function GameMasterMenu(props: { open: boolean; onClose: () => void; chil
|
||||
group flex flex-row rounded-md justify-content gap-4 px-4 py-2
|
||||
`}
|
||||
>
|
||||
<span className="mr-auto">Elapsed time (seconds)</span> <span className={`
|
||||
w-32 text-center
|
||||
`}>{serverStatus.elapsedTime?.toFixed()}</span>
|
||||
<span className="mr-auto">Elapsed time (seconds)</span>{" "}
|
||||
<span
|
||||
className={`w-32 text-center`}
|
||||
>
|
||||
{serverStatus.elapsedTime?.toFixed()}
|
||||
</span>
|
||||
</div>
|
||||
{commandModeOptions.commandMode === GAME_MASTER && (
|
||||
<button
|
||||
|
||||
@ -211,7 +211,7 @@ export function Header() {
|
||||
</div>
|
||||
|
||||
<OlLabelToggle
|
||||
toggled={mapOptions.cameraPluginMode === "live"}
|
||||
toggled={mapOptions.cameraPluginMode === "map"}
|
||||
leftLabel={"Live"}
|
||||
rightLabel={"Map"}
|
||||
onClick={() => {
|
||||
|
||||
@ -15,12 +15,11 @@ export function InfoBar(props: {}) {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`absolute left-[50%] top-16`}
|
||||
>
|
||||
<div className={`absolute left-[50%] top-16`}>
|
||||
{messages.slice(Math.max(0, messages.length - 4), Math.max(0, messages.length)).map((message, idx) => {
|
||||
return (
|
||||
<div
|
||||
key={idx}
|
||||
className={`
|
||||
absolute w-fit translate-x-[-50%] gap-2 text-nowrap rounded-full
|
||||
bg-olympus-800/90 px-4 py-2 text-center text-sm text-white
|
||||
|
||||
@ -38,7 +38,7 @@ export function OptionsMenu(props: { open: boolean; onClose: () => void; childre
|
||||
`}
|
||||
>
|
||||
<OlAccordion
|
||||
onClick={() => setOpenAccordion(openAccordion === Accordion.NONE ? Accordion.BINDINGS : Accordion.NONE)}
|
||||
onClick={() => setOpenAccordion(openAccordion !== Accordion.BINDINGS ? Accordion.BINDINGS : Accordion.NONE)}
|
||||
open={openAccordion === Accordion.BINDINGS}
|
||||
title="Key bindings"
|
||||
>
|
||||
@ -53,9 +53,10 @@ export function OptionsMenu(props: { open: boolean; onClose: () => void; childre
|
||||
.map(([id, shortcut]) => {
|
||||
return (
|
||||
<div
|
||||
key={id}
|
||||
className={`
|
||||
group relative mr-2 flex cursor-pointer select-none
|
||||
items-center justify-between rounded-sm px-2 py-2 text-sm
|
||||
items-center justify-between rounded-sm px-2 py-2
|
||||
dark:text-gray-300 dark:hover:bg-olympus-500
|
||||
`}
|
||||
onClick={() => {
|
||||
@ -65,39 +66,9 @@ export function OptionsMenu(props: { open: boolean; onClose: () => void; childre
|
||||
>
|
||||
<span>{shortcut.getOptions().label}</span>
|
||||
<span className="flex gap-1">
|
||||
{shortcut.getOptions().altKey ? (
|
||||
<div className="flex gap-1">
|
||||
<div className={`text-green-500`}>Alt</div> +{" "}
|
||||
</div>
|
||||
) : shortcut.getOptions().altKey === false ? (
|
||||
<div className={`flex gap-1`}>
|
||||
<div className={`text-red-500`}>Alt</div> +{" "}
|
||||
</div>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
{shortcut.getOptions().ctrlKey ? (
|
||||
<div className="flex gap-1">
|
||||
<div className={`text-green-500`}>Shift</div> +{" "}
|
||||
</div>
|
||||
) : shortcut.getOptions().ctrlKey === false ? (
|
||||
<div className={`flex gap-1`}>
|
||||
<div className={`text-red-500`}>Shift</div> +{" "}
|
||||
</div>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
{shortcut.getOptions().shiftKey ? (
|
||||
<div className="flex gap-1">
|
||||
<div className={`text-green-500`}>Ctrl</div> +{" "}
|
||||
</div>
|
||||
) : shortcut.getOptions().shiftKey === false ? (
|
||||
<div className={`flex gap-1`}>
|
||||
<div className={`text-red-500`}>Ctrl</div> +{" "}
|
||||
</div>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
{shortcut.getOptions().ctrlKey && "Ctrl + "}
|
||||
{shortcut.getOptions().altKey && "Alt + "}
|
||||
{shortcut.getOptions().shiftKey && "Shift + "}
|
||||
{shortcut.getOptions().code}
|
||||
</span>
|
||||
</div>
|
||||
@ -107,7 +78,7 @@ export function OptionsMenu(props: { open: boolean; onClose: () => void; childre
|
||||
</OlAccordion>
|
||||
|
||||
<OlAccordion
|
||||
onClick={() => setOpenAccordion(openAccordion === Accordion.NONE ? Accordion.MAP_OPTIONS : Accordion.NONE)}
|
||||
onClick={() => setOpenAccordion(openAccordion !== Accordion.MAP_OPTIONS ? Accordion.MAP_OPTIONS : Accordion.NONE)}
|
||||
open={openAccordion === Accordion.MAP_OPTIONS}
|
||||
title="Map options"
|
||||
>
|
||||
@ -158,7 +129,7 @@ export function OptionsMenu(props: { open: boolean; onClose: () => void; childre
|
||||
<div
|
||||
className={`
|
||||
group flex flex-row gap-4 rounded-md justify-content
|
||||
cursor-pointer p-2 text-sm
|
||||
cursor-pointer p-2
|
||||
dark:hover:bg-olympus-400
|
||||
`}
|
||||
onClick={() => getApp().getMap().setOption("hideUnitsShortRangeRings", !mapOptions.hideUnitsShortRangeRings)}
|
||||
@ -166,11 +137,11 @@ export function OptionsMenu(props: { open: boolean; onClose: () => void; childre
|
||||
<OlCheckbox checked={mapOptions.hideUnitsShortRangeRings} onChange={() => {}}></OlCheckbox>
|
||||
<span>Hide Short range Rings</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div
|
||||
className={`
|
||||
group flex flex-row gap-4 rounded-md justify-content
|
||||
cursor-pointer p-2 text-sm
|
||||
cursor-pointer p-2
|
||||
dark:hover:bg-olympus-400
|
||||
`}
|
||||
onClick={() => getApp().getMap().setOption("hideGroupMembers", !mapOptions.hideGroupMembers)}
|
||||
@ -181,7 +152,7 @@ export function OptionsMenu(props: { open: boolean; onClose: () => void; childre
|
||||
<div
|
||||
className={`
|
||||
group flex flex-row gap-4 rounded-md justify-content
|
||||
cursor-pointer p-2 text-sm
|
||||
cursor-pointer p-2
|
||||
dark:hover:bg-olympus-400
|
||||
`}
|
||||
onClick={() => getApp().getMap().setOption("showMinimap", !mapOptions.showMinimap)}
|
||||
@ -192,7 +163,7 @@ export function OptionsMenu(props: { open: boolean; onClose: () => void; childre
|
||||
</OlAccordion>
|
||||
|
||||
<OlAccordion
|
||||
onClick={() => setOpenAccordion(openAccordion === Accordion.NONE ? Accordion.CAMERA_PLUGIN : Accordion.NONE)}
|
||||
onClick={() => setOpenAccordion(openAccordion !== Accordion.CAMERA_PLUGIN ? Accordion.CAMERA_PLUGIN : Accordion.NONE)}
|
||||
open={openAccordion === Accordion.CAMERA_PLUGIN}
|
||||
title="Camera plugin options"
|
||||
>
|
||||
|
||||
@ -32,6 +32,7 @@ export function RadiosSummaryPanel(props: {}) {
|
||||
.map((radioSink, idx) => {
|
||||
return (
|
||||
<OlStateButton
|
||||
key={idx}
|
||||
checked={radioSink.getPtt()}
|
||||
onClick={() => {}}
|
||||
onMouseDown={() => {
|
||||
@ -41,28 +42,23 @@ export function RadiosSummaryPanel(props: {}) {
|
||||
radioSink.setPtt(false);
|
||||
}}
|
||||
tooltip="Click to talk, lights up when receiving"
|
||||
buttonColor={
|
||||
radioSink.getReceiving()
|
||||
? "white"
|
||||
: null
|
||||
}
|
||||
buttonColor={radioSink.getReceiving() ? "white" : null}
|
||||
>
|
||||
<span className={`font-bold text-gray-200`}>{idx + 1}</span>
|
||||
</OlStateButton>
|
||||
);
|
||||
})}
|
||||
|
||||
{audioSinks.filter((audioSinks) => audioSinks instanceof UnitSink).length > 0 && (
|
||||
<FaJetFighter
|
||||
className={`text-xl`}
|
||||
/>
|
||||
)}
|
||||
{audioSinks.filter((audioSinks) => audioSinks instanceof UnitSink).length > 0 && <FaJetFighter className={`
|
||||
text-xl
|
||||
`} />}
|
||||
{audioSinks.filter((audioSinks) => audioSinks instanceof UnitSink).length > 0 &&
|
||||
audioSinks
|
||||
.filter((audioSinks) => audioSinks instanceof UnitSink)
|
||||
.map((unitSink, idx) => {
|
||||
return (
|
||||
<OlStateButton
|
||||
key={idx}
|
||||
checked={unitSink.getPtt()}
|
||||
onClick={() => {}}
|
||||
onMouseDown={() => {
|
||||
|
||||
@ -17,6 +17,7 @@ import { faStar } from "@fortawesome/free-solid-svg-icons";
|
||||
import { OlStringInput } from "../components/olstringinput";
|
||||
import { countryCodes } from "../data/codes";
|
||||
import { OlAccordion } from "../components/olaccordion";
|
||||
import { AppStateChangedEvent } from "../../events";
|
||||
|
||||
export function UnitSpawnMenu(props: {
|
||||
starredSpawns: { [key: string]: SpawnRequestTable };
|
||||
@ -32,6 +33,7 @@ export function UnitSpawnMenu(props: {
|
||||
const altitudeStep = altitudeIncrements[props.blueprint.category];
|
||||
|
||||
/* State initialization */
|
||||
const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED);
|
||||
const [spawnCoalition, setSpawnCoalition] = useState("blue" as Coalition);
|
||||
const [spawnNumber, setSpawnNumber] = useState(1);
|
||||
const [spawnRole, setSpawnRole] = useState("");
|
||||
@ -46,9 +48,14 @@ export function UnitSpawnMenu(props: {
|
||||
const [key, setKey] = useState("");
|
||||
const [spawnRequestTable, setSpawnRequestTable] = useState(null as null | SpawnRequestTable);
|
||||
|
||||
/* When the menu is opened show the unit preview on the map as a cursor */
|
||||
useEffect(() => {
|
||||
if (!props.airbase && spawnRequestTable) {
|
||||
setAppState(getApp()?.getState())
|
||||
AppStateChangedEvent.on((state, subState) => setAppState(state));
|
||||
}, []);
|
||||
|
||||
/* When the menu is opened show the unit preview on the map as a cursor */
|
||||
const setSpawnRequestTableCallback = useCallback(() => {
|
||||
if (!props.airbase && spawnRequestTable && appState === OlympusState.SPAWN) {
|
||||
/* Refresh the unique key identified */
|
||||
const newKey = hash(JSON.stringify(spawnRequestTable));
|
||||
setKey(newKey);
|
||||
@ -56,7 +63,9 @@ export function UnitSpawnMenu(props: {
|
||||
getApp()?.getMap()?.setSpawnRequestTable(spawnRequestTable);
|
||||
getApp().setState(OlympusState.SPAWN, SpawnSubState.SPAWN_UNIT);
|
||||
}
|
||||
}, [spawnRequestTable]);
|
||||
}, [spawnRequestTable, appState]);
|
||||
|
||||
useEffect(setSpawnRequestTableCallback, [spawnRequestTable]);
|
||||
|
||||
/* Callback and effect to update the quick access name of the starredSpawn */
|
||||
const updateStarredSpawnQuickAccessNameS = useCallback(() => {
|
||||
@ -231,6 +240,7 @@ export function UnitSpawnMenu(props: {
|
||||
{roles.map((role) => {
|
||||
return (
|
||||
<OlDropdownItem
|
||||
key={role}
|
||||
onClick={() => {
|
||||
setSpawnRole(role);
|
||||
setSpawnLoadout("");
|
||||
@ -256,6 +266,7 @@ export function UnitSpawnMenu(props: {
|
||||
{loadouts.map((loadout) => {
|
||||
return (
|
||||
<OlDropdownItem
|
||||
key={loadout.name}
|
||||
onClick={() => {
|
||||
setSpawnLoadout(loadout.name);
|
||||
}}
|
||||
@ -285,10 +296,9 @@ export function UnitSpawnMenu(props: {
|
||||
>
|
||||
Livery
|
||||
</span>
|
||||
<OlDropdown
|
||||
label={props.blueprint.liveries ? (props.blueprint.liveries[spawnLiveryID]?.name ?? "Default") : "No livery"}
|
||||
className={`w-64`}
|
||||
>
|
||||
<OlDropdown label={props.blueprint.liveries ? (props.blueprint.liveries[spawnLiveryID]?.name ?? "Default") : "No livery"} className={`
|
||||
w-64
|
||||
`}>
|
||||
{props.blueprint.liveries &&
|
||||
Object.keys(props.blueprint.liveries)
|
||||
.sort((ida, idb) => {
|
||||
@ -303,6 +313,7 @@ export function UnitSpawnMenu(props: {
|
||||
});
|
||||
return (
|
||||
<OlDropdownItem
|
||||
key={id}
|
||||
onClick={() => {
|
||||
setSpawnLiveryID(id);
|
||||
}}
|
||||
@ -315,9 +326,10 @@ export function UnitSpawnMenu(props: {
|
||||
`}
|
||||
>
|
||||
{props.blueprint.liveries && props.blueprint.liveries[id].countries.length == 1 && (
|
||||
<img src={`images/countries/${country?.flagCode.toLowerCase()}.svg`} className={`
|
||||
h-6
|
||||
`} />
|
||||
<img
|
||||
src={`images/countries/${country?.flagCode.toLowerCase()}.svg`}
|
||||
className={`h-6`}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="my-auto truncate">
|
||||
@ -348,6 +360,7 @@ export function UnitSpawnMenu(props: {
|
||||
{["Average", "Good", "High", "Excellent"].map((skill) => {
|
||||
return (
|
||||
<OlDropdownItem
|
||||
key={skill}
|
||||
onClick={() => {
|
||||
setSpawnSkill(skill);
|
||||
}}
|
||||
@ -383,7 +396,7 @@ export function UnitSpawnMenu(props: {
|
||||
>
|
||||
{spawnLoadout.items.map((item) => {
|
||||
return (
|
||||
<div className="flex content-center gap-2">
|
||||
<div className="flex content-center gap-2" key={item.name}>
|
||||
<div
|
||||
className={`
|
||||
my-auto w-6 min-w-6 rounded-full py-0.5 text-center
|
||||
@ -421,7 +434,13 @@ export function UnitSpawnMenu(props: {
|
||||
if (spawnRequestTable)
|
||||
getApp()
|
||||
.getUnitsManager()
|
||||
.spawnUnits(spawnRequestTable.category, Array(spawnRequestTable.amount).fill(spawnRequestTable.unit), spawnRequestTable.coalition, false, props.airbase?.getName());
|
||||
.spawnUnits(
|
||||
spawnRequestTable.category,
|
||||
Array(spawnRequestTable.amount).fill(spawnRequestTable.unit),
|
||||
spawnRequestTable.coalition,
|
||||
false,
|
||||
props.airbase?.getName()
|
||||
);
|
||||
}}
|
||||
>
|
||||
Spawn
|
||||
|
||||
@ -10,7 +10,7 @@ import { OptionsMenu } from "./panels/optionsmenu";
|
||||
import { MapHiddenTypes, MapOptions } from "../types/types";
|
||||
import { NO_SUBSTATE, OlympusState, OlympusSubState, OptionsSubstate, SpawnSubState, UnitControlSubState } from "../constants/constants";
|
||||
import { getApp, setupApp } from "../olympusapp";
|
||||
import { LoginModal } from "./modals/login";
|
||||
import { LoginModal } from "./modals/loginmodal";
|
||||
|
||||
import { MiniMapPanel } from "./panels/minimappanel";
|
||||
import { UnitControlBar } from "./panels/unitcontrolbar";
|
||||
@ -20,7 +20,7 @@ import { MapContextMenu } from "./contextmenus/mapcontextmenu";
|
||||
import { AirbaseMenu } from "./panels/airbasemenu";
|
||||
import { AudioMenu } from "./panels/audiomenu";
|
||||
import { FormationMenu } from "./panels/formationmenu";
|
||||
import { ProtectionPrompt } from "./modals/protectionprompt";
|
||||
import { ProtectionPromptModal } from "./modals/protectionpromptmodal";
|
||||
import { KeybindModal } from "./modals/keybindmodal";
|
||||
import { UnitExplosionMenu } from "./panels/unitexplosionmenu";
|
||||
import { JTACMenu } from "./panels/jtacmenu";
|
||||
@ -65,16 +65,8 @@ export function UI() {
|
||||
>
|
||||
<Header />
|
||||
<div className="flex h-full w-full flex-row-reverse">
|
||||
{appState === OlympusState.LOGIN && (
|
||||
<>
|
||||
<div className={`
|
||||
fixed left-0 top-0 z-30 h-full w-full bg-[#111111]/95
|
||||
`}></div>
|
||||
<LoginModal />
|
||||
</>
|
||||
)}
|
||||
|
||||
<ProtectionPrompt open={appState === OlympusState.UNIT_CONTROL && appSubState == UnitControlSubState.PROTECTION} />
|
||||
<LoginModal open={appState === OlympusState.LOGIN} />
|
||||
<ProtectionPromptModal open={appState === OlympusState.UNIT_CONTROL && appSubState == UnitControlSubState.PROTECTION} />
|
||||
<KeybindModal open={appState === OlympusState.OPTIONS && appSubState === OptionsSubstate.KEYBIND} />
|
||||
|
||||
<div id="map-container" className="z-0 h-full w-screen" />
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user