mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
More refactoring of events
This commit is contained in:
parent
14c0a2f1e8
commit
7f5873b5b8
@ -11,6 +11,7 @@ import { AudioSink } from "./audiosink";
|
||||
import { Unit } from "../unit/unit";
|
||||
import { UnitSink } from "./unitsink";
|
||||
import { AudioPacket, MessageType } from "./audiopacket";
|
||||
import { AudioManagerStateChangedEvent, AudioSinksChangedEvent, AudioSourcesChangedEvent, ConfigLoadedEvent, SRSClientsChangedEvent } from "../events";
|
||||
|
||||
export class AudioManager {
|
||||
#audioContext: AudioContext;
|
||||
@ -35,7 +36,7 @@ export class AudioManager {
|
||||
#SRSClientUnitIDs: number[] = [];
|
||||
|
||||
constructor() {
|
||||
document.addEventListener("configLoaded", () => {
|
||||
ConfigLoadedEvent.on(() => {
|
||||
let config = getApp().getConfig();
|
||||
if (config["WSPort"]) {
|
||||
this.setPort(config["WSPort"]);
|
||||
@ -95,7 +96,7 @@ export class AudioManager {
|
||||
});
|
||||
} else {
|
||||
this.#SRSClientUnitIDs = JSON.parse(new TextDecoder().decode(packetUint8Array.slice(1))).unitIDs;
|
||||
document.dispatchEvent(new CustomEvent("SRSClientsUpdated"));
|
||||
SRSClientsChangedEvent.dispatch();
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -108,13 +109,13 @@ export class AudioManager {
|
||||
if (sink instanceof RadioSink) microphoneSource.connect(sink);
|
||||
});
|
||||
this.#sources.push(microphoneSource);
|
||||
document.dispatchEvent(new CustomEvent("audioSourcesUpdated"));
|
||||
AudioSourcesChangedEvent.dispatch(getApp().getAudioManager().getSources());
|
||||
|
||||
/* Add two default radios */
|
||||
this.addRadio();
|
||||
this.addRadio();
|
||||
});
|
||||
document.dispatchEvent(new CustomEvent("audioManagerStateChanged"));
|
||||
AudioManagerStateChangedEvent.dispatch(this.#running);
|
||||
}
|
||||
|
||||
stop() {
|
||||
@ -128,9 +129,9 @@ export class AudioManager {
|
||||
this.#sources = [];
|
||||
this.#sinks = [];
|
||||
|
||||
document.dispatchEvent(new CustomEvent("audioSourcesUpdated"));
|
||||
document.dispatchEvent(new CustomEvent("audioSinksUpdated"));
|
||||
document.dispatchEvent(new CustomEvent("audioManagerStateChanged"));
|
||||
AudioSourcesChangedEvent.dispatch(this.#sources);
|
||||
AudioSinksChangedEvent.dispatch(this.#sinks);
|
||||
AudioManagerStateChangedEvent.dispatch(this.#running);
|
||||
}
|
||||
|
||||
setAddress(address) {
|
||||
@ -153,7 +154,7 @@ export class AudioManager {
|
||||
}
|
||||
const newSource = new FileSource(file);
|
||||
this.#sources.push(newSource);
|
||||
document.dispatchEvent(new CustomEvent("audioSourcesUpdated"));
|
||||
AudioSourcesChangedEvent.dispatch(getApp().getAudioManager().getSources());
|
||||
}
|
||||
|
||||
getSources() {
|
||||
@ -168,7 +169,7 @@ export class AudioManager {
|
||||
}
|
||||
source.disconnect();
|
||||
this.#sources = this.#sources.filter((v) => v != source);
|
||||
document.dispatchEvent(new CustomEvent("audioSourcesUpdated"));
|
||||
AudioSourcesChangedEvent.dispatch(this.#sources);
|
||||
}
|
||||
|
||||
addUnitSink(unit: Unit) {
|
||||
@ -178,7 +179,7 @@ export class AudioManager {
|
||||
return;
|
||||
}
|
||||
this.#sinks.push(new UnitSink(unit));
|
||||
document.dispatchEvent(new CustomEvent("audioSinksUpdated"));
|
||||
AudioSinksChangedEvent.dispatch(this.#sinks);
|
||||
}
|
||||
|
||||
addRadio() {
|
||||
@ -191,7 +192,7 @@ export class AudioManager {
|
||||
this.#sinks.push(newRadio);
|
||||
newRadio.setName(`Radio ${this.#sinks.length}`);
|
||||
this.#sources[0].connect(newRadio);
|
||||
document.dispatchEvent(new CustomEvent("audioSinksUpdated"));
|
||||
AudioSinksChangedEvent.dispatch(this.#sinks);
|
||||
}
|
||||
|
||||
getSinks() {
|
||||
@ -210,7 +211,7 @@ export class AudioManager {
|
||||
this.#sinks.forEach((sink) => {
|
||||
if (sink instanceof RadioSink) sink.setName(`Radio ${idx++}`);
|
||||
});
|
||||
document.dispatchEvent(new CustomEvent("audioSinksUpdated"));
|
||||
AudioSinksChangedEvent.dispatch(getApp().getAudioManager().getSinks());
|
||||
this.#sources.forEach((source) => {
|
||||
if (source.getConnectedTo().includes(sink))
|
||||
source.disconnect(sink)
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { AudioSinksChangedEvent } from "../events";
|
||||
import { getApp } from "../olympusapp";
|
||||
|
||||
/* Base audio sink class */
|
||||
@ -19,7 +20,7 @@ export abstract class AudioSink {
|
||||
|
||||
disconnect() {
|
||||
this.getInputNode().disconnect();
|
||||
document.dispatchEvent(new CustomEvent("audioSinksUpdated"));
|
||||
AudioSinksChangedEvent.dispatch(getApp().getAudioManager().getSinks());
|
||||
}
|
||||
|
||||
getInputNode() {
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { AudioSourcesChangedEvent } from "../events";
|
||||
import { getApp } from "../olympusapp";
|
||||
import { AudioSink } from "./audiosink";
|
||||
import { WebAudioPeakMeter } from "web-audio-peak-meter";
|
||||
@ -21,7 +22,7 @@ export abstract class AudioSource {
|
||||
if (!this.#connectedTo.includes(sink)) {
|
||||
this.getOutputNode().connect(sink.getInputNode());
|
||||
this.#connectedTo.push(sink);
|
||||
document.dispatchEvent(new CustomEvent("audioSourcesUpdated"));
|
||||
AudioSourcesChangedEvent.dispatch(getApp().getAudioManager().getSources());
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,7 +34,7 @@ export abstract class AudioSource {
|
||||
this.getOutputNode().disconnect();
|
||||
}
|
||||
|
||||
document.dispatchEvent(new CustomEvent("audioSourcesUpdated"));
|
||||
AudioSourcesChangedEvent.dispatch(getApp().getAudioManager().getSources());
|
||||
}
|
||||
|
||||
setName(name) {
|
||||
@ -51,7 +52,7 @@ export abstract class AudioSource {
|
||||
setVolume(volume) {
|
||||
this.#volume = volume;
|
||||
this.#gainNode.gain.exponentialRampToValueAtTime(volume, getApp().getAudioManager().getAudioContext().currentTime + 0.02);
|
||||
document.dispatchEvent(new CustomEvent("audioSourcesUpdated"));
|
||||
AudioSourcesChangedEvent.dispatch(getApp().getAudioManager().getSources());
|
||||
}
|
||||
|
||||
getVolume() {
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { AudioSource } from "./audiosource";
|
||||
import { getApp } from "../olympusapp";
|
||||
import { AudioSourcesChangedEvent } from "../events";
|
||||
|
||||
export class FileSource extends AudioSource {
|
||||
#file: File;
|
||||
@ -50,7 +51,7 @@ export class FileSource extends AudioSource {
|
||||
const now = Date.now() / 1000;
|
||||
this.#lastUpdateTime = now;
|
||||
|
||||
document.dispatchEvent(new CustomEvent("audioSourcesUpdated"));
|
||||
AudioSourcesChangedEvent.dispatch(getApp().getAudioManager().getSources());
|
||||
|
||||
this.#updateInterval = setInterval(() => {
|
||||
/* Update the current position value every second */
|
||||
@ -63,7 +64,7 @@ export class FileSource extends AudioSource {
|
||||
if (!this.#looping) this.pause();
|
||||
}
|
||||
|
||||
document.dispatchEvent(new CustomEvent("audioSourcesUpdated"));
|
||||
AudioSourcesChangedEvent.dispatch(getApp().getAudioManager().getSources());
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
@ -77,7 +78,7 @@ export class FileSource extends AudioSource {
|
||||
this.#currentPosition += now - this.#lastUpdateTime;
|
||||
clearInterval(this.#updateInterval);
|
||||
|
||||
document.dispatchEvent(new CustomEvent("audioSourcesUpdated"));
|
||||
AudioSourcesChangedEvent.dispatch(getApp().getAudioManager().getSources());
|
||||
}
|
||||
|
||||
getPlaying() {
|
||||
@ -110,7 +111,7 @@ export class FileSource extends AudioSource {
|
||||
setLooping(looping) {
|
||||
this.#looping = looping;
|
||||
if (this.#source) this.#source.loop = looping;
|
||||
document.dispatchEvent(new CustomEvent("audioSourcesUpdated"));
|
||||
AudioSourcesChangedEvent.dispatch(getApp().getAudioManager().getSources());
|
||||
}
|
||||
|
||||
getLooping() {
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { AudioSourcesChangedEvent } from "../events";
|
||||
import { getApp } from "../olympusapp";
|
||||
import { AudioSource } from "./audiosource";
|
||||
|
||||
@ -20,6 +21,6 @@ export class MicrophoneSource extends AudioSource {
|
||||
}
|
||||
|
||||
play() {
|
||||
document.dispatchEvent(new CustomEvent("audioSourcesUpdated"));
|
||||
AudioSourcesChangedEvent.dispatch(getApp().getAudioManager().getSources());
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { AudioSink } from "./audiosink";
|
||||
import { AudioPacket } from "./audiopacket";
|
||||
import { getApp } from "../olympusapp";
|
||||
import { AudioSinksChangedEvent } from "../events";
|
||||
|
||||
let packetID = 0;
|
||||
|
||||
@ -54,7 +55,7 @@ export class RadioSink extends AudioSink {
|
||||
|
||||
setFrequency(frequency) {
|
||||
this.#frequency = frequency;
|
||||
document.dispatchEvent(new CustomEvent("audioSinksUpdated"));
|
||||
AudioSinksChangedEvent.dispatch(getApp().getAudioManager().getSinks());
|
||||
}
|
||||
|
||||
getFrequency() {
|
||||
@ -63,7 +64,7 @@ export class RadioSink extends AudioSink {
|
||||
|
||||
setModulation(modulation) {
|
||||
this.#modulation = modulation;
|
||||
document.dispatchEvent(new CustomEvent("audioSinksUpdated"));
|
||||
AudioSinksChangedEvent.dispatch(getApp().getAudioManager().getSinks());
|
||||
}
|
||||
|
||||
getModulation() {
|
||||
@ -72,7 +73,7 @@ export class RadioSink extends AudioSink {
|
||||
|
||||
setPtt(ptt) {
|
||||
this.#ptt = ptt;
|
||||
document.dispatchEvent(new CustomEvent("audioSinksUpdated"));
|
||||
AudioSinksChangedEvent.dispatch(getApp().getAudioManager().getSinks());
|
||||
}
|
||||
|
||||
getPtt() {
|
||||
@ -81,7 +82,7 @@ export class RadioSink extends AudioSink {
|
||||
|
||||
setTuned(tuned) {
|
||||
this.#tuned = tuned;
|
||||
document.dispatchEvent(new CustomEvent("audioSinksUpdated"));
|
||||
AudioSinksChangedEvent.dispatch(getApp().getAudioManager().getSinks());
|
||||
}
|
||||
|
||||
getTuned() {
|
||||
@ -90,7 +91,7 @@ export class RadioSink extends AudioSink {
|
||||
|
||||
setVolume(volume) {
|
||||
this.#volume = volume;
|
||||
document.dispatchEvent(new CustomEvent("audioSinksUpdated"));
|
||||
AudioSinksChangedEvent.dispatch(getApp().getAudioManager().getSinks());
|
||||
}
|
||||
|
||||
getVolume() {
|
||||
|
||||
@ -2,6 +2,7 @@ import { AudioSink } from "./audiosink";
|
||||
import { getApp } from "../olympusapp";
|
||||
import { Unit } from "../unit/unit";
|
||||
import { AudioUnitPipeline } from "./audiounitpipeline";
|
||||
import { AudioSinksChangedEvent, SRSClientsChangedEvent } from "../events";
|
||||
|
||||
/* Unit sink to implement a "loudspeaker" external sound. Useful for stuff like 5MC calls, air sirens,
|
||||
scramble calls and so on. Ideally, one may want to move this code to the backend*/
|
||||
@ -17,7 +18,7 @@ export class UnitSink extends AudioSink {
|
||||
this.#unit = unit;
|
||||
this.setName(`${unit.getUnitName()} - ${unit.getName()}`);
|
||||
|
||||
document.addEventListener("SRSClientsUpdated", () => {
|
||||
SRSClientsChangedEvent.on(() => {
|
||||
this.#updatePipelines();
|
||||
});
|
||||
|
||||
@ -53,7 +54,7 @@ export class UnitSink extends AudioSink {
|
||||
Object.values(this.#unitPipelines).forEach((pipeline) => {
|
||||
pipeline.setPtt(ptt);
|
||||
})
|
||||
document.dispatchEvent(new CustomEvent("audioSinksUpdated"));
|
||||
AudioSinksChangedEvent.dispatch(getApp().getAudioManager().getSinks());
|
||||
}
|
||||
|
||||
getPtt() {
|
||||
@ -65,7 +66,7 @@ export class UnitSink extends AudioSink {
|
||||
Object.values(this.#unitPipelines).forEach((pipeline) => {
|
||||
pipeline.setMaxDistance(maxDistance);
|
||||
})
|
||||
document.dispatchEvent(new CustomEvent("audioSinksUpdated"));
|
||||
AudioSinksChangedEvent.dispatch(getApp().getAudioManager().getSinks());
|
||||
}
|
||||
|
||||
getMaxDistance() {
|
||||
|
||||
280
frontend/react/src/events.ts
Normal file
280
frontend/react/src/events.ts
Normal file
@ -0,0 +1,280 @@
|
||||
import { AudioSink } from "./audio/audiosink";
|
||||
import { AudioSource } from "./audio/audiosource";
|
||||
import { OlympusState, OlympusSubState } from "./constants/constants";
|
||||
import { ServerStatus } from "./interfaces";
|
||||
import { CoalitionCircle } from "./map/coalitionarea/coalitioncircle";
|
||||
import { CoalitionPolygon } from "./map/coalitionarea/coalitionpolygon";
|
||||
import { Airbase } from "./mission/airbase";
|
||||
import { MapHiddenTypes, MapOptions } from "./types/types";
|
||||
import { ContextAction } from "./unit/contextaction";
|
||||
import { ContextActionSet } from "./unit/contextactionset";
|
||||
import { Unit } from "./unit/unit";
|
||||
|
||||
export class BaseOlympusEvent {
|
||||
static on(callback: () => void) {
|
||||
document.addEventListener(this.name, (ev: CustomEventInit) => {
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
static dispatch() {
|
||||
document.dispatchEvent(new CustomEvent(this.name));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
|
||||
export class BaseUnitEvent {
|
||||
static on(callback: (unit: Unit) => void) {
|
||||
document.addEventListener(this.name, (ev: CustomEventInit) => {
|
||||
callback(ev.detail.unit);
|
||||
});
|
||||
}
|
||||
|
||||
static dispatch(unit: Unit) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail: { unit } }));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
console.log(unit)
|
||||
}
|
||||
}
|
||||
|
||||
/************** App events ***************/
|
||||
export class AppStateChangedEvent {
|
||||
static on(callback: (state: OlympusState, subState: OlympusSubState) => void) {
|
||||
document.addEventListener(this.name, (ev: CustomEventInit) => {
|
||||
callback(ev.detail.state, ev.detail.subState);
|
||||
});
|
||||
}
|
||||
|
||||
static dispatch(state: OlympusState, subState: OlympusSubState) {
|
||||
const detail = { state, subState };
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail }));
|
||||
console.log(`Event ${this.name} dispatched with detail:`);
|
||||
console.log(detail);
|
||||
}
|
||||
}
|
||||
|
||||
export class ConfigLoadedEvent {
|
||||
/* TODO add config */
|
||||
static on(callback: () => void) {
|
||||
document.addEventListener(this.name, (ev: CustomEventInit) => {
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
static dispatch() {
|
||||
document.dispatchEvent(new CustomEvent(this.name));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
|
||||
export class ServerStatusUpdatedEvent {
|
||||
static on(callback: (serverStatus: ServerStatus) => void) {
|
||||
document.addEventListener(this.name, (ev: CustomEventInit) => {
|
||||
callback(ev.detail.serverStatus);
|
||||
});
|
||||
}
|
||||
|
||||
static dispatch(serverStatus: ServerStatus) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail: { serverStatus } }));
|
||||
// Logging disabled since periodic
|
||||
}
|
||||
}
|
||||
|
||||
/************** Map events ***************/
|
||||
export class HiddenTypesChangedEvent {
|
||||
static on(callback: (hiddenTypes: MapHiddenTypes) => void) {
|
||||
document.addEventListener(this.name, (ev: CustomEventInit) => {
|
||||
callback(ev.detail.hiddenTypes);
|
||||
});
|
||||
}
|
||||
|
||||
static dispatch(hiddenTypes: MapHiddenTypes) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, {detail: {hiddenTypes}}));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
|
||||
export class MapOptionsChangedEvent {
|
||||
static on(callback: (mapOptions: MapOptions) => void) {
|
||||
document.addEventListener(this.name, (ev: CustomEventInit) => {
|
||||
callback(ev.detail.mapOptions);
|
||||
});
|
||||
}
|
||||
|
||||
static dispatch(mapOptions: MapOptions) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, {detail: {mapOptions}}));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
|
||||
export class MapSourceChangedEvent {
|
||||
static on(callback: (source: string) => void) {
|
||||
document.addEventListener(this.name, (ev: CustomEventInit) => {
|
||||
callback(ev.detail.source);
|
||||
});
|
||||
}
|
||||
|
||||
static dispatch(source: string) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, {detail: {source}}));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
|
||||
export class CoalitionAreaSelectedEvent {
|
||||
static on(callback: (coalitionArea: CoalitionCircle | CoalitionPolygon | null) => void) {
|
||||
document.addEventListener(this.name, (ev: CustomEventInit) => {
|
||||
callback(ev.detail.coalitionArea);
|
||||
});
|
||||
}
|
||||
|
||||
static dispatch(coalitionArea: CoalitionCircle | CoalitionPolygon | null) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail: { coalitionArea } }));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
|
||||
export class AirbaseSelectedEvent {
|
||||
static on(callback: (airbase: Airbase) => void) {
|
||||
document.addEventListener(this.name, (ev: CustomEventInit) => {
|
||||
callback(ev.detail.airbase);
|
||||
});
|
||||
}
|
||||
|
||||
static dispatch(airbase: Airbase) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail: { airbase } }));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
|
||||
export class ContactsUpdatedEvent {
|
||||
static on(callback: () => void) {
|
||||
document.addEventListener(this.name, (ev: CustomEventInit) => {
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
static dispatch() {
|
||||
document.dispatchEvent(new CustomEvent(this.name));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
|
||||
export class ContextActionSetChangedEvent {
|
||||
static on(callback: (contextActionSet: ContextActionSet) => void) {
|
||||
document.addEventListener(this.name, (ev: CustomEventInit) => {
|
||||
callback(ev.detail.contextActionSet);
|
||||
});
|
||||
}
|
||||
|
||||
static dispatch(contextActionSet: ContextActionSet) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, {detail: {contextActionSet}}));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
|
||||
export class ContextActionChangedEvent {
|
||||
static on(callback: (contextAction: ContextAction) => void) {
|
||||
document.addEventListener(this.name, (ev: CustomEventInit) => {
|
||||
callback(ev.detail.contextActionSet);
|
||||
});
|
||||
}
|
||||
|
||||
static dispatch(contextAction: ContextAction) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, {detail: {contextAction}}));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
|
||||
export class UnitUpdatedEvent extends BaseUnitEvent {};
|
||||
export class UnitSelectedEvent extends BaseUnitEvent {};
|
||||
export class UnitDeselectedEvent extends BaseUnitEvent {};
|
||||
export class UnitDeadEvent extends BaseUnitEvent {};
|
||||
export class SelectionClearedEvent extends BaseOlympusEvent {};
|
||||
|
||||
export class SelectedUnitsChangedEvent {
|
||||
static on(callback: (selectedUnits: Unit[]) => void) {
|
||||
document.addEventListener(this.name, (ev: CustomEventInit) => {
|
||||
callback(ev.detail);
|
||||
});
|
||||
}
|
||||
|
||||
static dispatch(selectedUnits: Unit[]) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, {detail: selectedUnits}));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
console.log(selectedUnits)
|
||||
}
|
||||
}
|
||||
|
||||
/************** Command mode events ***************/
|
||||
export class CommandModeOptionsChangedEvent {
|
||||
/* TODO: add command mode options */
|
||||
static on(callback: () => void) {
|
||||
document.addEventListener(this.name, (ev: CustomEventInit) => {
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
static dispatch() {
|
||||
document.dispatchEvent(new CustomEvent(this.name));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
|
||||
/************** Audio backend events ***************/
|
||||
/* TODO: split into two events for signgle source changed */
|
||||
export class AudioSourcesChangedEvent {
|
||||
/* TODO add audio sources */
|
||||
static on(callback: (audioSources: AudioSource[]) => void) {
|
||||
document.addEventListener(this.name, (ev: CustomEventInit) => {
|
||||
callback(ev.detail);
|
||||
});
|
||||
}
|
||||
|
||||
static dispatch(audioSources: AudioSource[]) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, {detail: {audioSources}}));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
console.log(audioSources)
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO: split into two events for signgle sink changed */
|
||||
export class AudioSinksChangedEvent {
|
||||
static on(callback: (audioSinks: AudioSink[]) => void) {
|
||||
document.addEventListener(this.name, (ev: CustomEventInit) => {
|
||||
callback(ev.detail);
|
||||
});
|
||||
}
|
||||
|
||||
static dispatch(audioSinks: AudioSink[]) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, {detail: {audioSinks}}));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
console.log(audioSinks)
|
||||
}
|
||||
}
|
||||
|
||||
export class SRSClientsChangedEvent {
|
||||
/* TODO add clients */
|
||||
static on(callback: () => void) {
|
||||
document.addEventListener(this.name, (ev: CustomEventInit) => {
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
static dispatch() {
|
||||
document.dispatchEvent(new CustomEvent(this.name));
|
||||
// Logging disabled since periodic
|
||||
}
|
||||
}
|
||||
|
||||
export class AudioManagerStateChangedEvent {
|
||||
static on(callback: (state: boolean) => void) {
|
||||
document.addEventListener(this.name, (ev: CustomEventInit) => {
|
||||
callback(ev.detail.state);
|
||||
});
|
||||
}
|
||||
|
||||
static dispatch(state: boolean) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, {detail: {state}}));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
@ -4,6 +4,7 @@ import { CoalitionAreaHandle } from "./coalitionareahandle";
|
||||
import { BLUE_COMMANDER, RED_COMMANDER } from "../../constants/constants";
|
||||
import { Coalition } from "../../types/types";
|
||||
import * as turf from "@turf/turf";
|
||||
import { CoalitionAreaSelectedEvent } from "../../events";
|
||||
|
||||
let totalAreas = 0;
|
||||
|
||||
@ -59,13 +60,7 @@ export class CoalitionCircle extends Circle {
|
||||
this.#drawLabel();
|
||||
this.setOpacity(selected ? 1 : 0.5);
|
||||
|
||||
if (selected) {
|
||||
document.dispatchEvent(
|
||||
new CustomEvent("coalitionAreaSelected", {
|
||||
detail: this,
|
||||
})
|
||||
);
|
||||
}
|
||||
if (selected) CoalitionAreaSelectedEvent.dispatch(this);
|
||||
|
||||
//@ts-ignore draggable option added by leaflet-path-drag
|
||||
selected ? this.dragging.enable() : this.dragging.disable();
|
||||
|
||||
@ -5,6 +5,7 @@ import { CoalitionAreaMiddleHandle } from "./coalitionareamiddlehandle";
|
||||
import { BLUE_COMMANDER, RED_COMMANDER } from "../../constants/constants";
|
||||
import { Coalition } from "../../types/types";
|
||||
import { polyCenter } from "../../other/utils";
|
||||
import { CoalitionAreaSelectedEvent } from "../../events";
|
||||
|
||||
let totalAreas = 0;
|
||||
|
||||
@ -70,13 +71,7 @@ export class CoalitionPolygon extends Polygon {
|
||||
this.setEditing(false);
|
||||
}
|
||||
|
||||
if (selected) {
|
||||
document.dispatchEvent(
|
||||
new CustomEvent("coalitionAreaSelected", {
|
||||
detail: this,
|
||||
})
|
||||
);
|
||||
}
|
||||
if (selected) CoalitionAreaSelectedEvent.dispatch(this);
|
||||
|
||||
//@ts-ignore draggable option added by leaflet-path-drag
|
||||
selected ? this.dragging.enable() : this.dragging.disable();
|
||||
|
||||
@ -37,6 +37,8 @@ import { faDrawPolygon, faHandPointer, faJetFighter, faMap } from "@fortawesome/
|
||||
import { ExplosionMarker } from "./markers/explosionmarker";
|
||||
import { TextMarker } from "./markers/textmarker";
|
||||
import { TargetMarker } from "./markers/targetmarker";
|
||||
import { AppStateChangedEvent, CoalitionAreaSelectedEvent, ConfigLoadedEvent, HiddenTypesChangedEvent, MapOptionsChangedEvent, MapSourceChangedEvent } from "../events";
|
||||
import { ContextActionSet } from "../unit/contextactionset";
|
||||
|
||||
/* Register the handler for the box selection */
|
||||
L.Map.addInitHook("addHandler", "boxSelect", BoxSelect);
|
||||
@ -105,8 +107,8 @@ export class Map extends L.Map {
|
||||
#coalitionAreas: (CoalitionPolygon | CoalitionCircle)[] = [];
|
||||
|
||||
/* Unit context actions */
|
||||
#contextActionSet: null | ContextActionSet = null;
|
||||
#contextAction: null | ContextAction = null;
|
||||
#defaultContextAction: null | ContextAction = null;
|
||||
|
||||
/* Unit spawning */
|
||||
#spawnRequestTable: SpawnRequestTable | null = null;
|
||||
@ -181,11 +183,9 @@ export class Map extends L.Map {
|
||||
L.DomEvent.on(this.getContainer(), "touchend", this.#onMouseUp, this);
|
||||
|
||||
/* Event listeners */
|
||||
document.addEventListener("appStateChanged", (ev: CustomEventInit) => {
|
||||
this.#onStateChanged(ev.detail.state, ev.detail.subState);
|
||||
});
|
||||
AppStateChangedEvent.on((state, subState) => this.#onStateChanged(state, subState));
|
||||
|
||||
document.addEventListener("hiddenTypesChanged", (ev: CustomEventInit) => {
|
||||
HiddenTypesChangedEvent.on((hiddenTypes) => {
|
||||
Object.values(getApp().getUnitsManager().getUnits()).forEach((unit: Unit) => unit.updateVisibility());
|
||||
Object.values(getApp().getMissionManager().getAirbases()).forEach((airbase: Airbase) => {
|
||||
if (this.getHiddenTypes().airbase) airbase.removeFrom(this);
|
||||
@ -198,10 +198,10 @@ export class Map extends L.Map {
|
||||
// this.#panToUnit(this.#centerUnit);
|
||||
//});
|
||||
|
||||
document.addEventListener("mapOptionsChanged", () => {
|
||||
this.getContainer().toggleAttribute("data-hide-labels", !this.getOptions().showUnitLabels);
|
||||
//this.#cameraControlPort = this.getOptions()[DCS_LINK_PORT] as number;
|
||||
//this.#cameraZoomRatio = 50 / (20 + (this.getOptions()[DCS_LINK_RATIO] as number));
|
||||
MapOptionsChangedEvent.on((options) => {
|
||||
this.getContainer().toggleAttribute("data-hide-labels", !options.showUnitLabels);
|
||||
//this.#cameraControlPort = options[DCS_LINK_PORT] as number;
|
||||
//this.#cameraZoomRatio = 50 / (20 + (options[DCS_LINK_RATIO] as number));
|
||||
|
||||
if (this.#slaveDCSCamera) {
|
||||
this.#broadcastPosition();
|
||||
@ -213,7 +213,7 @@ export class Map extends L.Map {
|
||||
this.updateMinimap();
|
||||
});
|
||||
|
||||
document.addEventListener("configLoaded", () => {
|
||||
ConfigLoadedEvent.on(() => {
|
||||
let config = getApp().getConfig();
|
||||
let layerSet = false;
|
||||
|
||||
@ -248,73 +248,6 @@ export class Map extends L.Map {
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("toggleCameraLinkStatus", () => {
|
||||
this.setSlaveDCSCamera(!this.#slaveDCSCamera);
|
||||
});
|
||||
|
||||
document.addEventListener("slewCameraToPosition", () => {
|
||||
this.#broadcastPosition();
|
||||
});
|
||||
|
||||
document.addEventListener("selectJTACECHO", (ev: CustomEventInit) => {
|
||||
if (!this.#ECHOPoint) {
|
||||
this.#ECHOPoint = new TextMarker(ev.detail, "BP", "rgb(37 99 235)", { interactive: true, draggable: true });
|
||||
this.#ECHOPoint.addTo(this);
|
||||
this.#ECHOPoint.on("dragstart", (event) => {
|
||||
event.target.options["freeze"] = true;
|
||||
});
|
||||
this.#ECHOPoint.on("dragend", (event) => {
|
||||
document.dispatchEvent(new CustomEvent("selectJTACECHO", { detail: this.#ECHOPoint?.getLatLng() }));
|
||||
event.target.options["freeze"] = false;
|
||||
});
|
||||
this.#ECHOPoint.on("click", (event) => {
|
||||
getApp().setState(OlympusState.JTAC)
|
||||
})
|
||||
} else this.#ECHOPoint.setLatLng(ev.detail);
|
||||
});
|
||||
|
||||
document.addEventListener("selectJTACIP", (ev: CustomEventInit) => {
|
||||
if (!this.#IPPoint) {
|
||||
this.#IPPoint = new TextMarker(ev.detail, "IP", "rgb(168 85 247)", { interactive: true, draggable: true });
|
||||
this.#IPPoint.addTo(this);
|
||||
this.#IPPoint.on("dragstart", (event) => {
|
||||
event.target.options["freeze"] = true;
|
||||
});
|
||||
this.#IPPoint.on("dragend", (event) => {
|
||||
document.dispatchEvent(new CustomEvent("selectJTACIP", { detail: this.#IPPoint?.getLatLng() }));
|
||||
event.target.options["freeze"] = false;
|
||||
});
|
||||
this.#IPPoint.on("click", (event) => {
|
||||
getApp().setState(OlympusState.JTAC)
|
||||
})
|
||||
} else this.#IPPoint.setLatLng(ev.detail);
|
||||
|
||||
this.#drawIPToTargetLine();
|
||||
});
|
||||
|
||||
document.addEventListener("selectJTACTarget", (ev: CustomEventInit) => {
|
||||
if (ev.detail.location) {
|
||||
if (!this.#targetPoint) {
|
||||
this.#targetPoint = new TargetMarker(ev.detail.location, { interactive: true, draggable: true });
|
||||
this.#targetPoint.addTo(this);
|
||||
this.#targetPoint.on("dragstart", (event) => {
|
||||
event.target.options["freeze"] = true;
|
||||
});
|
||||
this.#targetPoint.on("dragend", (event) => {
|
||||
document.dispatchEvent(new CustomEvent("selectJTACTarget", { detail: { location: this.#targetPoint?.getLatLng() } }));
|
||||
event.target.options["freeze"] = false;
|
||||
});
|
||||
this.#targetPoint.on("click", (event) => {
|
||||
getApp().setState(OlympusState.JTAC)
|
||||
})
|
||||
} else this.#targetPoint.setLatLng(ev.detail.location);
|
||||
} else {
|
||||
this.#targetPoint?.removeFrom(this);
|
||||
this.#targetPoint = null;
|
||||
}
|
||||
this.#drawIPToTargetLine();
|
||||
});
|
||||
|
||||
/* Pan interval */
|
||||
this.#panInterval = window.setInterval(() => {
|
||||
if (this.#panUp || this.#panDown || this.#panRight || this.#panLeft)
|
||||
@ -395,7 +328,7 @@ export class Map extends L.Map {
|
||||
}
|
||||
this.#layerName = layerName;
|
||||
|
||||
document.dispatchEvent(new CustomEvent("mapSourceChanged", { detail: layerName }));
|
||||
MapSourceChangedEvent.dispatch(layerName);
|
||||
}
|
||||
|
||||
getLayerName() {
|
||||
@ -414,12 +347,12 @@ export class Map extends L.Map {
|
||||
this.#effectRequestTable = effectRequestTable;
|
||||
}
|
||||
|
||||
setContextAction(contextAction: ContextAction | null) {
|
||||
this.#contextAction = contextAction;
|
||||
setContextActionSet(contextActionSet: ContextActionSet | null) {
|
||||
this.#contextActionSet = contextActionSet;
|
||||
}
|
||||
|
||||
setDefaultContextAction(defaultContextAction: ContextAction | null) {
|
||||
this.#defaultContextAction = defaultContextAction;
|
||||
setContextAction(contextAction: ContextAction | null) {
|
||||
this.#contextAction = contextAction;
|
||||
}
|
||||
|
||||
#onStateChanged(state: OlympusState, subState: OlympusSubState) {
|
||||
@ -459,13 +392,11 @@ export class Map extends L.Map {
|
||||
} else if (state === OlympusState.UNIT_CONTROL) {
|
||||
console.log(`Context action:`);
|
||||
console.log(this.#contextAction);
|
||||
console.log(`Default context action callback:`);
|
||||
console.log(this.#defaultContextAction);
|
||||
} else if (state === OlympusState.DRAW) {
|
||||
if (subState == DrawSubState.DRAW_POLYGON) {
|
||||
this.#coalitionAreas.push(new CoalitionPolygon([]));
|
||||
this.#coalitionAreas[this.#coalitionAreas.length - 1].addTo(this);
|
||||
this.#coalitionAreas[this.#coalitionAreas.length - 1].setSelected(true);
|
||||
this.#coalitionAreas[this.#coalitionAreas.length - 1].setSelected(true);
|
||||
} else if (subState === DrawSubState.DRAW_CIRCLE) {
|
||||
this.#coalitionAreas.push(new CoalitionCircle(new L.LatLng(0, 0), { radius: 1000 }));
|
||||
this.#coalitionAreas[this.#coalitionAreas.length - 1].addTo(this);
|
||||
@ -683,12 +614,7 @@ export class Map extends L.Map {
|
||||
}
|
||||
|
||||
deselectAllCoalitionAreas() {
|
||||
document.dispatchEvent(
|
||||
new CustomEvent("coalitionAreaSelected", {
|
||||
detail: null,
|
||||
})
|
||||
);
|
||||
|
||||
CoalitionAreaSelectedEvent.dispatch(null);
|
||||
this.#coalitionAreas.forEach((coalitionArea: CoalitionPolygon | CoalitionCircle) => coalitionArea.setSelected(false));
|
||||
}
|
||||
|
||||
@ -700,7 +626,7 @@ export class Map extends L.Map {
|
||||
|
||||
setHiddenType(key: string, value: boolean) {
|
||||
this.#hiddenTypes[key] = value;
|
||||
document.dispatchEvent(new CustomEvent("hiddenTypesChanged"));
|
||||
HiddenTypesChangedEvent.dispatch(this.#hiddenTypes);
|
||||
}
|
||||
|
||||
getHiddenTypes() {
|
||||
@ -847,7 +773,7 @@ export class Map extends L.Map {
|
||||
|
||||
setOption(key, value) {
|
||||
this.#options[key] = value;
|
||||
document.dispatchEvent(new CustomEvent("mapOptionsChanged"));
|
||||
MapOptionsChangedEvent.dispatch(this.#options);
|
||||
}
|
||||
|
||||
getOptions() {
|
||||
@ -906,16 +832,16 @@ export class Map extends L.Map {
|
||||
this.#contextAction?.executeCallback(targetUnit, targetPosition);
|
||||
}
|
||||
|
||||
getContextActionSet() {
|
||||
return this.#contextActionSet;
|
||||
}
|
||||
|
||||
getContextAction() {
|
||||
return this.#contextAction;
|
||||
}
|
||||
|
||||
executeDefaultContextAction(targetUnit: Unit | null, targetPosition: L.LatLng | null) {
|
||||
if (this.#defaultContextAction) this.#defaultContextAction.executeCallback(targetUnit, targetPosition);
|
||||
}
|
||||
|
||||
getDefaultContextAction() {
|
||||
return this.#defaultContextAction;
|
||||
this.#contextActionSet?.getDefaultContextAction()?.executeCallback(targetUnit, targetPosition);
|
||||
}
|
||||
|
||||
preventClicks() {
|
||||
@ -939,7 +865,6 @@ export class Map extends L.Map {
|
||||
|
||||
#onSelectionEnd(e: any) {
|
||||
getApp().getUnitsManager().selectFromBounds(e.selectionBounds);
|
||||
document.dispatchEvent(new CustomEvent("mapSelectionEnd"));
|
||||
this.#selecting = false;
|
||||
}
|
||||
|
||||
@ -1037,7 +962,7 @@ export class Map extends L.Map {
|
||||
for (let idx = 0; idx < this.#coalitionAreas.length; idx++) {
|
||||
if (areaContains(pressLocation, this.#coalitionAreas[idx])) {
|
||||
this.#coalitionAreas[idx].setSelected(true);
|
||||
getApp().setState(OlympusState.DRAW, DrawSubState.EDIT)
|
||||
getApp().setState(OlympusState.DRAW, DrawSubState.EDIT);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -1047,17 +972,57 @@ export class Map extends L.Map {
|
||||
if (this.#contextAction !== null) this.executeContextAction(null, pressLocation);
|
||||
else getApp().setState(OlympusState.IDLE);
|
||||
} else if (e.originalEvent.buttons === 2) {
|
||||
if (this.#defaultContextAction !== null) this.executeDefaultContextAction(null, pressLocation);
|
||||
this.executeDefaultContextAction(null, pressLocation);
|
||||
}
|
||||
} else if (getApp().getState() === OlympusState.JTAC) {
|
||||
if (getApp().getSubState() === JTACSubState.SELECT_TARGET) {
|
||||
document.dispatchEvent(new CustomEvent("selectJTACTarget", { detail: { location: pressLocation } }));
|
||||
if (!this.#targetPoint) {
|
||||
this.#targetPoint = new TextMarker(pressLocation, "BP", "rgb(37 99 235)", { interactive: true, draggable: true });
|
||||
this.#targetPoint.addTo(this);
|
||||
this.#targetPoint.on("dragstart", (event) => {
|
||||
event.target.options["freeze"] = true;
|
||||
});
|
||||
this.#targetPoint.on("dragend", (event) => {
|
||||
getApp().setState(OlympusState.JTAC);
|
||||
event.target.options["freeze"] = false;
|
||||
});
|
||||
this.#targetPoint.on("click", (event) => {
|
||||
getApp().setState(OlympusState.JTAC);
|
||||
});
|
||||
} else this.#targetPoint.setLatLng(pressLocation);
|
||||
} else if (getApp().getSubState() === JTACSubState.SELECT_ECHO_POINT) {
|
||||
document.dispatchEvent(new CustomEvent("selectJTACECHO", { detail: pressLocation }));
|
||||
if (!this.#ECHOPoint) {
|
||||
this.#ECHOPoint = new TextMarker(pressLocation, "BP", "rgb(37 99 235)", { interactive: true, draggable: true });
|
||||
this.#ECHOPoint.addTo(this);
|
||||
this.#ECHOPoint.on("dragstart", (event) => {
|
||||
event.target.options["freeze"] = true;
|
||||
});
|
||||
this.#ECHOPoint.on("dragend", (event) => {
|
||||
getApp().setState(OlympusState.JTAC);
|
||||
event.target.options["freeze"] = false;
|
||||
});
|
||||
this.#ECHOPoint.on("click", (event) => {
|
||||
getApp().setState(OlympusState.JTAC);
|
||||
});
|
||||
} else this.#ECHOPoint.setLatLng(pressLocation);
|
||||
} else if (getApp().getSubState() === JTACSubState.SELECT_IP) {
|
||||
document.dispatchEvent(new CustomEvent("selectJTACIP", { detail: pressLocation }));
|
||||
if (!this.#IPPoint) {
|
||||
this.#IPPoint = new TextMarker(pressLocation, "BP", "rgb(37 99 235)", { interactive: true, draggable: true });
|
||||
this.#IPPoint.addTo(this);
|
||||
this.#IPPoint.on("dragstart", (event) => {
|
||||
event.target.options["freeze"] = true;
|
||||
});
|
||||
this.#IPPoint.on("dragend", (event) => {
|
||||
getApp().setState(OlympusState.JTAC);
|
||||
event.target.options["freeze"] = false;
|
||||
});
|
||||
this.#IPPoint.on("click", (event) => {
|
||||
getApp().setState(OlympusState.JTAC);
|
||||
});
|
||||
} else this.#IPPoint.setLatLng(pressLocation);
|
||||
}
|
||||
getApp().setState(OlympusState.JTAC);
|
||||
this.#drawIPToTargetLine();
|
||||
} else {
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ import { SVGInjector } from "@tanem/svg-injector";
|
||||
import { AirbaseChartData, AirbaseOptions } from "../interfaces";
|
||||
import { getApp } from "../olympusapp";
|
||||
import { OlympusState } from "../constants/constants";
|
||||
import { AirbaseSelectedEvent } from "../events";
|
||||
|
||||
export class Airbase extends CustomMarker {
|
||||
#name: string = "";
|
||||
@ -27,7 +28,7 @@ export class Airbase extends CustomMarker {
|
||||
this.addEventListener("click", (ev) => {
|
||||
if (getApp().getState() === OlympusState.IDLE) {
|
||||
getApp().setState(OlympusState.AIRBASE)
|
||||
// TODO: document.dispatchEvent(new CustomEvent("airbaseClick", { detail: ev.target }));
|
||||
AirbaseSelectedEvent.dispatch(this)
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -48,12 +49,6 @@ export class Airbase extends CustomMarker {
|
||||
this.#img.onload = () => SVGInjector(this.#img);
|
||||
el.appendChild(this.#img);
|
||||
this.getElement()?.appendChild(el);
|
||||
el.addEventListener("mouseover", (ev) => {
|
||||
document.dispatchEvent(new CustomEvent("airbasemouseover", { detail: this }));
|
||||
});
|
||||
el.addEventListener("mouseout", (ev) => {
|
||||
document.dispatchEvent(new CustomEvent("airbasemouseout", { detail: this }));
|
||||
});
|
||||
el.dataset.coalition = this.#coalition;
|
||||
}
|
||||
|
||||
|
||||
@ -18,12 +18,6 @@ export class Carrier extends Airbase {
|
||||
this.getImg().style.width = `0px`; // Make the image immediately small to avoid giant carriers
|
||||
el.appendChild(this.getImg());
|
||||
this.getElement()?.appendChild(el);
|
||||
el.addEventListener("mouseover", (ev) => {
|
||||
document.dispatchEvent(new CustomEvent("airbasemouseover", { detail: this }));
|
||||
});
|
||||
el.addEventListener("mouseout", (ev) => {
|
||||
document.dispatchEvent(new CustomEvent("airbasemouseout", { detail: this }));
|
||||
});
|
||||
el.dataset.coalition = this.getCoalition();
|
||||
}
|
||||
|
||||
|
||||
@ -14,6 +14,7 @@ import { AirbasesData, BullseyesData, CommandModeOptions, DateAndTime, MissionDa
|
||||
import { Coalition } from "../types/types";
|
||||
import { Carrier } from "./carrier";
|
||||
import { NavyUnit } from "../unit/unit";
|
||||
import { CommandModeOptionsChangedEvent } from "../events";
|
||||
|
||||
/** The MissionManager */
|
||||
export class MissionManager {
|
||||
@ -42,17 +43,7 @@ export class MissionManager {
|
||||
//#commandModeErasDropdown: Dropdown;
|
||||
#coalitions: { red: string[]; blue: string[] } = { red: [], blue: [] };
|
||||
|
||||
constructor() {
|
||||
document.addEventListener("applycommandModeOptions", () => this.#applycommandModeOptions());
|
||||
document.addEventListener("showCommandModeDialog", () => this.showCommandModeDialog());
|
||||
document.addEventListener("toggleSpawnRestrictions", (ev: CustomEventInit) => {
|
||||
this.#toggleSpawnRestrictions(ev.detail._element.checked);
|
||||
});
|
||||
|
||||
/* command-mode settings dialog */
|
||||
//this.#commandModeDialog = document.querySelector("#command-mode-settings-dialog") as HTMLElement;
|
||||
//this.#commandModeErasDropdown = new Dropdown("command-mode-era-options", () => {});
|
||||
}
|
||||
constructor() {}
|
||||
|
||||
/** Update location of bullseyes
|
||||
*
|
||||
@ -303,13 +294,7 @@ export class MissionManager {
|
||||
this.refreshSpawnPoints();
|
||||
|
||||
if (commandModeOptionsChanged) {
|
||||
document.dispatchEvent(new CustomEvent("commandModeOptionsChanged", { detail: this }));
|
||||
document.getElementById("command-mode-toolbar")?.classList.remove("hide");
|
||||
const el = document.getElementById("command-mode");
|
||||
if (el) {
|
||||
el.dataset.mode = commandModeOptions.commandMode;
|
||||
el.textContent = commandModeOptions.commandMode.toUpperCase();
|
||||
}
|
||||
CommandModeOptionsChangedEvent.dispatch();
|
||||
}
|
||||
|
||||
document
|
||||
|
||||
@ -27,12 +27,12 @@ import { groundUnitDatabase } from "./unit/databases/groundunitdatabase";
|
||||
import { navyUnitDatabase } from "./unit/databases/navyunitdatabase";
|
||||
import { Coalition, Context } from "./types/types";
|
||||
import { Unit } from "./unit/unit";
|
||||
import { AppStateChangedEvent, ConfigLoadedEvent, SelectedUnitsChangedEvent } from "./events";
|
||||
|
||||
export var VERSION = "{{OLYMPUS_VERSION_NUMBER}}";
|
||||
export var IP = window.location.toString();
|
||||
export var connectedToServer = true; // TODO Temporary
|
||||
|
||||
|
||||
export class OlympusApp {
|
||||
/* Global data */
|
||||
#latestVersion: string | undefined = undefined;
|
||||
@ -42,7 +42,7 @@ export class OlympusApp {
|
||||
|
||||
#events = {
|
||||
[OlympusEvent.STATE_CHANGED]: [] as ((state: OlympusState, subState: OlympusSubState) => void)[],
|
||||
[OlympusEvent.UNITS_SELECTED]: [] as ((units: Unit[]) => void)[]
|
||||
[OlympusEvent.UNITS_SELECTED]: [] as ((units: Unit[]) => void)[],
|
||||
};
|
||||
|
||||
/* Main leaflet map, extended by custom methods */
|
||||
@ -60,7 +60,12 @@ export class OlympusApp {
|
||||
/* Current context */
|
||||
#context: Context = DEFAULT_CONTEXT;
|
||||
|
||||
constructor() {}
|
||||
constructor() {
|
||||
SelectedUnitsChangedEvent.on((selectedUnits) => {
|
||||
if (selectedUnits.length > 0) this.setState(OlympusState.UNIT_CONTROL);
|
||||
else this.getState() === OlympusState.UNIT_CONTROL && this.setState(OlympusState.IDLE)
|
||||
});
|
||||
}
|
||||
|
||||
getCurrentContext() {
|
||||
return this.#context;
|
||||
@ -178,10 +183,9 @@ export class OlympusApp {
|
||||
})
|
||||
.then((res) => {
|
||||
this.#config = res;
|
||||
document.dispatchEvent(new CustomEvent("configLoaded"));
|
||||
ConfigLoadedEvent.dispatch(); // TODO actually dispatch the config
|
||||
this.setState(OlympusState.LOGIN);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
getConfig() {
|
||||
@ -192,8 +196,8 @@ export class OlympusApp {
|
||||
this.#state = state;
|
||||
this.#subState = subState;
|
||||
|
||||
console.log(`App state set to ${state}, substate ${subState}`)
|
||||
this.dispatchEvent(OlympusEvent.STATE_CHANGED, state, subState)
|
||||
console.log(`App state set to ${state}, substate ${subState}`);
|
||||
AppStateChangedEvent.dispatch(state, subState);
|
||||
}
|
||||
|
||||
getState() {
|
||||
@ -203,15 +207,4 @@ export class OlympusApp {
|
||||
getSubState() {
|
||||
return this.#subState;
|
||||
}
|
||||
|
||||
registerEventCallback(event: OlympusEvent, callback: any) {
|
||||
this.#events[event].push(callback)
|
||||
}
|
||||
|
||||
dispatchEvent(event: OlympusEvent, ...args) {
|
||||
console.log(`Dispatching event ${event}. Arguments: ${args}`)
|
||||
this.#events[event].forEach((event) => {
|
||||
event(args);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,6 +14,7 @@ import {
|
||||
reactionsToThreat,
|
||||
} from "../constants/constants";
|
||||
import { AirbasesData, BullseyesData, GeneralSettings, MissionData, Radio, ServerRequestOptions, ServerStatus, TACAN } from "../interfaces";
|
||||
import { ServerStatusUpdatedEvent } from "../events";
|
||||
|
||||
export class ServerManager {
|
||||
#connected: boolean = false;
|
||||
@ -142,7 +143,7 @@ export class ServerManager {
|
||||
|
||||
setAddress(address: string) {
|
||||
this.#REST_ADDRESS = `${address.replace("vite/", "").replace("vite", "")}olympus`;
|
||||
|
||||
|
||||
console.log(`Setting REST address to ${this.#REST_ADDRESS}`);
|
||||
}
|
||||
|
||||
@ -610,18 +611,14 @@ export class ServerManager {
|
||||
this.#serverIsPaused = elapsedMissionTime === this.#previousMissionElapsedTime;
|
||||
this.#previousMissionElapsedTime = elapsedMissionTime;
|
||||
|
||||
document.dispatchEvent(
|
||||
new CustomEvent("serverStatusUpdated", {
|
||||
detail: {
|
||||
frameRate: getApp().getMissionManager().getFrameRate(),
|
||||
load: getApp().getMissionManager().getLoad(),
|
||||
elapsedTime: getApp().getMissionManager().getDateAndTime().elapsedTime,
|
||||
missionTime: getApp().getMissionManager().getDateAndTime().time,
|
||||
connected: this.getConnected(),
|
||||
paused: this.getPaused(),
|
||||
} as ServerStatus,
|
||||
})
|
||||
);
|
||||
ServerStatusUpdatedEvent.dispatch({
|
||||
frameRate: getApp().getMissionManager().getFrameRate(),
|
||||
load: getApp().getMissionManager().getLoad(),
|
||||
elapsedTime: getApp().getMissionManager().getDateAndTime().elapsedTime,
|
||||
missionTime: getApp().getMissionManager().getDateAndTime().time,
|
||||
connected: this.getConnected(),
|
||||
paused: this.getPaused(),
|
||||
} as ServerStatus);
|
||||
}, 1000)
|
||||
);
|
||||
|
||||
|
||||
@ -1,5 +1,11 @@
|
||||
import { createContext } from "react";
|
||||
import { MAP_HIDDEN_TYPES_DEFAULTS, MAP_OPTIONS_DEFAULTS, NO_SUBSTATE, OlympusState, OlympusSubState } from "./constants/constants";
|
||||
import { Unit } from "./unit/unit";
|
||||
import { AudioSource } from "./audio/audiosource";
|
||||
import { AudioSink } from "./audio/audiosink";
|
||||
import { ServerStatus } from "./interfaces";
|
||||
import { ContextActionSet } from "./unit/contextactionset";
|
||||
import { ContextAction } from "./unit/contextaction";
|
||||
|
||||
export const StateContext = createContext({
|
||||
appState: OlympusState.NOT_INITIALIZED as OlympusState,
|
||||
@ -7,7 +13,14 @@ export const StateContext = createContext({
|
||||
mapHiddenTypes: MAP_HIDDEN_TYPES_DEFAULTS,
|
||||
mapOptions: MAP_OPTIONS_DEFAULTS,
|
||||
mapSources: [] as string[],
|
||||
activeMapSource: ""
|
||||
activeMapSource: "",
|
||||
selectedUnits: [] as Unit[],
|
||||
audioSources: [] as AudioSource[],
|
||||
audioSinks: [] as AudioSink[],
|
||||
audioManagerState: false,
|
||||
serverStatus: {} as ServerStatus,
|
||||
contextActionSet: null as ContextActionSet | null,
|
||||
contextAction: null as ContextAction | null
|
||||
});
|
||||
|
||||
export const StateProvider = StateContext.Provider;
|
||||
|
||||
@ -7,6 +7,7 @@ import { CONTEXT_ACTION_COLORS } from "../../constants/constants";
|
||||
import { OlDropdownItem } from "../components/oldropdown";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { LatLng } from "leaflet";
|
||||
import { SelectionClearedEvent } from "../../events";
|
||||
|
||||
export function MapContextMenu(props: {}) {
|
||||
const [open, setOpen] = useState(false);
|
||||
@ -71,7 +72,7 @@ export function MapContextMenu(props: {}) {
|
||||
setOpen(false);
|
||||
});
|
||||
|
||||
document.addEventListener("clearSelection", () => {
|
||||
SelectionClearedEvent.on(() => {
|
||||
setOpen(false);
|
||||
});
|
||||
}, []);
|
||||
|
||||
@ -11,6 +11,7 @@ import { UnitSinkPanel } from "./components/unitsinkpanel";
|
||||
import { UnitSink } from "../../audio/unitsink";
|
||||
import { FaMinus, FaVolumeHigh } from "react-icons/fa6";
|
||||
import { getRandomColor } from "../../other/utils";
|
||||
import { AudioManagerStateChangedEvent, AudioSinksChangedEvent, AudioSourcesChangedEvent } from "../../events";
|
||||
|
||||
let shortcutKeys = ["Z", "X", "C", "V", "B", "N", "M", "K", "L"];
|
||||
|
||||
@ -36,7 +37,7 @@ export function AudioMenu(props: { open: boolean; onClose: () => void; children?
|
||||
|
||||
useEffect(() => {
|
||||
/* Force a rerender */
|
||||
document.addEventListener("audioSinksUpdated", () => {
|
||||
AudioSinksChangedEvent.on(() => {
|
||||
setSinks(
|
||||
getApp()
|
||||
?.getAudioManager()
|
||||
@ -47,7 +48,7 @@ export function AudioMenu(props: { open: boolean; onClose: () => void; children?
|
||||
});
|
||||
|
||||
/* Force a rerender */
|
||||
document.addEventListener("audioSourcesUpdated", () => {
|
||||
AudioSourcesChangedEvent.on(() => {
|
||||
setSources(
|
||||
getApp()
|
||||
?.getAudioManager()
|
||||
@ -56,7 +57,7 @@ export function AudioMenu(props: { open: boolean; onClose: () => void; children?
|
||||
);
|
||||
});
|
||||
|
||||
document.addEventListener("audioManagerStateChanged", () => {
|
||||
AudioManagerStateChangedEvent.on(() => {
|
||||
setAudioManagerEnabled(getApp().getAudioManager().isRunning());
|
||||
});
|
||||
}, []);
|
||||
|
||||
@ -2,6 +2,7 @@ import React, { useEffect, useState } from "react";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { IconDefinition } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { AppStateChangedEvent } from "../../events";
|
||||
|
||||
export function ControlsPanel(props: {}) {
|
||||
const [controls, setControls] = useState(
|
||||
@ -19,7 +20,7 @@ export function ControlsPanel(props: {}) {
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener("appStateChanged", (ev) => {
|
||||
AppStateChangedEvent.on(() => {
|
||||
setControls(getApp().getMap().getCurrentControls());
|
||||
});
|
||||
}, []);
|
||||
|
||||
@ -13,10 +13,10 @@ import { Coalition } from "../../types/types";
|
||||
import { OlRangeSlider } from "../components/olrangeslider";
|
||||
import { CoalitionCircle } from "../../map/coalitionarea/coalitioncircle";
|
||||
import { DrawSubState, NO_SUBSTATE, OlympusState } from "../../constants/constants";
|
||||
import { StateConsumer } from "../../statecontext";
|
||||
import { CoalitionAreaSelectedEvent } from "../../events";
|
||||
|
||||
export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
|
||||
const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED);
|
||||
const [appSubState, setAppSubState] = useState(NO_SUBSTATE);
|
||||
const [activeCoalitionArea, setActiveCoalitionArea] = useState(null as null | CoalitionPolygon | CoalitionCircle);
|
||||
const [areaCoalition, setAreaCoalition] = useState("blue" as Coalition);
|
||||
const [IADSDensity, setIADSDensity] = useState(50);
|
||||
@ -32,276 +32,275 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
|
||||
// TODO
|
||||
///* If we are not in polygon drawing mode, force the draw polygon button off */
|
||||
//if (drawingPolygon && getApp().getState() !== COALITIONAREA_DRAW_POLYGON) setDrawingPolygon(false);
|
||||
//
|
||||
//
|
||||
///* If we are not in circle drawing mode, force the draw circle button off */
|
||||
//if (drawingCircle && getApp().getState() !== COALITIONAREA_DRAW_CIRCLE) setDrawingCircle(false);
|
||||
//
|
||||
//
|
||||
///* If we are not in any drawing mode, force the map in edit mode */
|
||||
//if (props.open && !drawingPolygon && !drawingCircle) getApp().getMap().setState(COALITIONAREA_EDIT);
|
||||
//
|
||||
//
|
||||
///* Align the state of the coalition toggle to the coalition of the area */
|
||||
//if (activeCoalitionArea && activeCoalitionArea?.getCoalition() !== areaCoalition) setAreaCoalition(activeCoalitionArea?.getCoalition());
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener("coalitionAreaSelected", (event: any) => {
|
||||
setActiveCoalitionArea(event.detail);
|
||||
});
|
||||
|
||||
document.addEventListener("appStateChanged", (ev: CustomEventInit) => {
|
||||
setAppState(ev.detail.state);
|
||||
setAppSubState(ev.detail.subState);
|
||||
});
|
||||
CoalitionAreaSelectedEvent.on((coalitionArea) => setActiveCoalitionArea(coalitionArea));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Menu
|
||||
open={props.open}
|
||||
title="Draw"
|
||||
onClose={props.onClose}
|
||||
canBeHidden={true}
|
||||
showBackButton={activeCoalitionArea !== null}
|
||||
onBack={() => {
|
||||
getApp().setState(OlympusState.DRAW, DrawSubState.NO_SUBSTATE)
|
||||
}}
|
||||
>
|
||||
<>
|
||||
{appState === OlympusState.DRAW && appSubState !== DrawSubState.EDIT && (
|
||||
<div className="flex flex-col gap-2 p-6 text-sm text-gray-400">
|
||||
<OlStateButton
|
||||
className="!w-full"
|
||||
icon={faDrawPolygon}
|
||||
tooltip={"Add a new polygon"}
|
||||
checked={appSubState === DrawSubState.DRAW_POLYGON}
|
||||
onClick={() => {
|
||||
if (appSubState === DrawSubState.DRAW_POLYGON) getApp().setState(OlympusState.DRAW, DrawSubState.EDIT);
|
||||
else getApp().setState(OlympusState.DRAW, DrawSubState.DRAW_POLYGON);
|
||||
}}
|
||||
>
|
||||
<div className="text-sm">Add polygon</div>
|
||||
</OlStateButton>
|
||||
<OlStateButton
|
||||
className="!w-full"
|
||||
icon={faCircle}
|
||||
tooltip={"Add a new circle"}
|
||||
checked={appSubState === DrawSubState.DRAW_CIRCLE}
|
||||
onClick={() => {
|
||||
if (appSubState === DrawSubState.DRAW_CIRCLE) getApp().setState(OlympusState.DRAW, DrawSubState.EDIT);
|
||||
else getApp().setState(OlympusState.DRAW, DrawSubState.DRAW_CIRCLE);
|
||||
}}
|
||||
>
|
||||
<div className="text-sm">Add circle</div>
|
||||
</OlStateButton>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
<div>
|
||||
{activeCoalitionArea !== null && appSubState === DrawSubState.EDIT && (
|
||||
<div className={`flex flex-col gap-4 py-4`}>
|
||||
<div
|
||||
className={`
|
||||
flex flex-col content-center justify-start gap-2 px-6
|
||||
text-gray-200
|
||||
`}
|
||||
>
|
||||
<div className="my-auto flex justify-between text-md">
|
||||
Area label
|
||||
<div
|
||||
className="rounded-md bg-red-800 p-2"
|
||||
<StateConsumer>
|
||||
{(appState) => (
|
||||
<Menu
|
||||
open={props.open}
|
||||
title="Draw"
|
||||
onClose={props.onClose}
|
||||
canBeHidden={true}
|
||||
showBackButton={activeCoalitionArea !== null}
|
||||
onBack={() => {
|
||||
getApp().setState(OlympusState.DRAW, DrawSubState.NO_SUBSTATE);
|
||||
}}
|
||||
>
|
||||
<>
|
||||
{appState.appState === OlympusState.DRAW && appState.appSubState !== DrawSubState.EDIT && (
|
||||
<div className="flex flex-col gap-2 p-6 text-sm text-gray-400">
|
||||
<OlStateButton
|
||||
className="!w-full"
|
||||
icon={faDrawPolygon}
|
||||
tooltip={"Add a new polygon"}
|
||||
checked={appState.appSubState === DrawSubState.DRAW_POLYGON}
|
||||
onClick={() => {
|
||||
getApp().getMap().deleteCoalitionArea(activeCoalitionArea);
|
||||
setActiveCoalitionArea(null);
|
||||
if (appState.appSubState === DrawSubState.DRAW_POLYGON) getApp().setState(OlympusState.DRAW, DrawSubState.EDIT);
|
||||
else getApp().setState(OlympusState.DRAW, DrawSubState.DRAW_POLYGON);
|
||||
}}
|
||||
>
|
||||
<FaTrash className={`text-gray-50`}></FaTrash>
|
||||
</div>
|
||||
<div className="text-sm">Add polygon</div>
|
||||
</OlStateButton>
|
||||
<OlStateButton
|
||||
className="!w-full"
|
||||
icon={faCircle}
|
||||
tooltip={"Add a new circle"}
|
||||
checked={appState.appSubState === DrawSubState.DRAW_CIRCLE}
|
||||
onClick={() => {
|
||||
if (appState.appSubState === DrawSubState.DRAW_CIRCLE) getApp().setState(OlympusState.DRAW, DrawSubState.EDIT);
|
||||
else getApp().setState(OlympusState.DRAW, DrawSubState.DRAW_CIRCLE);
|
||||
}}
|
||||
>
|
||||
<div className="text-sm">Add circle</div>
|
||||
</OlStateButton>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
className={`
|
||||
block w-full flex-grow rounded-lg border border-gray-300
|
||||
bg-gray-50 p-2.5 text-sm text-gray-900
|
||||
dark:border-gray-600 dark:bg-gray-700 dark:text-white
|
||||
dark:placeholder-gray-400 dark:focus:border-blue-500
|
||||
dark:focus:ring-blue-500
|
||||
focus:border-blue-500 focus:ring-blue-500
|
||||
`}
|
||||
placeholder={activeCoalitionArea.getLabelText()}
|
||||
onInput={(ev) => activeCoalitionArea.setLabelText(ev.currentTarget.value)}
|
||||
></input>
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
flex content-center justify-start gap-4 px-6 text-gray-200
|
||||
`}
|
||||
>
|
||||
<div className="my-auto text-md">Coalition: </div>
|
||||
<OlCoalitionToggle
|
||||
coalition={areaCoalition}
|
||||
onClick={() => {
|
||||
let newCoalition = "";
|
||||
if (areaCoalition === "blue") newCoalition = "neutral";
|
||||
else if (areaCoalition === "neutral") newCoalition = "red";
|
||||
else if (areaCoalition === "red") newCoalition = "blue";
|
||||
setAreaCoalition(newCoalition as Coalition);
|
||||
activeCoalitionArea.setCoalition(newCoalition as Coalition);
|
||||
}}
|
||||
></OlCoalitionToggle>
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
flex flex-col gap-3 border-l-4 border-l-olympus-100
|
||||
bg-olympus-600 p-5
|
||||
`}
|
||||
>
|
||||
<div className="border-b-2 border-b-olympus-100 pb-4 text-gray-300">Automatic IADS generation</div>
|
||||
<OlDropdown className="" label="Units types">
|
||||
{getApp()
|
||||
.getGroundUnitDatabase()
|
||||
.getTypes()
|
||||
.map((type, idx) => {
|
||||
if (!(type in typesSelection)) {
|
||||
typesSelection[type] = true;
|
||||
setTypesSelection(JSON.parse(JSON.stringify(typesSelection)));
|
||||
}
|
||||
|
||||
return (
|
||||
<OlDropdownItem key={idx} className={`flex gap-4`}>
|
||||
<OlCheckbox
|
||||
checked={typesSelection[type]}
|
||||
onChange={(ev) => {
|
||||
typesSelection[type] = ev.currentTarget.checked;
|
||||
setTypesSelection(JSON.parse(JSON.stringify(typesSelection)));
|
||||
}}
|
||||
/>
|
||||
<div>{type}</div>
|
||||
</OlDropdownItem>
|
||||
);
|
||||
})}
|
||||
</OlDropdown>
|
||||
<OlDropdown className="" label="Units eras">
|
||||
{getApp()
|
||||
.getGroundUnitDatabase()
|
||||
.getEras()
|
||||
.map((era) => {
|
||||
if (!(era in erasSelection)) {
|
||||
erasSelection[era] = true;
|
||||
setErasSelection(JSON.parse(JSON.stringify(erasSelection)));
|
||||
}
|
||||
|
||||
return (
|
||||
<OlDropdownItem className={`flex gap-4`}>
|
||||
<OlCheckbox
|
||||
checked={erasSelection[era]}
|
||||
onChange={(ev) => {
|
||||
erasSelection[era] = ev.currentTarget.checked;
|
||||
setErasSelection(JSON.parse(JSON.stringify(erasSelection)));
|
||||
}}
|
||||
/>
|
||||
<div>{era}</div>
|
||||
</OlDropdownItem>
|
||||
);
|
||||
})}
|
||||
</OlDropdown>
|
||||
<OlDropdown className="" label="Units ranges">
|
||||
{["Short range", "Medium range", "Long range"].map((range) => {
|
||||
if (!(range in rangesSelection)) {
|
||||
rangesSelection[range] = true;
|
||||
setRangesSelection(JSON.parse(JSON.stringify(rangesSelection)));
|
||||
}
|
||||
|
||||
return (
|
||||
<OlDropdownItem className={`flex gap-4`}>
|
||||
<OlCheckbox
|
||||
checked={rangesSelection[range]}
|
||||
onChange={(ev) => {
|
||||
rangesSelection[range] = ev.currentTarget.checked;
|
||||
setErasSelection(JSON.parse(JSON.stringify(rangesSelection)));
|
||||
}}
|
||||
/>
|
||||
<div>{range}</div>
|
||||
</OlDropdownItem>
|
||||
);
|
||||
})}
|
||||
</OlDropdown>
|
||||
<div>
|
||||
<div className="flex justify-between">
|
||||
<div className="text-gray-100">IADS Density</div>
|
||||
<div
|
||||
className={`
|
||||
font-bold
|
||||
dark:text-blue-500
|
||||
`}
|
||||
>
|
||||
{IADSDensity}%
|
||||
)}
|
||||
</>
|
||||
<div>
|
||||
{activeCoalitionArea !== null && appState.appSubState === DrawSubState.EDIT && (
|
||||
<div className={`flex flex-col gap-4 py-4`}>
|
||||
<div
|
||||
className={`
|
||||
flex flex-col content-center justify-start gap-2 px-6
|
||||
text-gray-200
|
||||
`}
|
||||
>
|
||||
<div className="my-auto flex justify-between text-md">
|
||||
Area label
|
||||
<div
|
||||
className="rounded-md bg-red-800 p-2"
|
||||
onClick={() => {
|
||||
getApp().getMap().deleteCoalitionArea(activeCoalitionArea);
|
||||
setActiveCoalitionArea(null);
|
||||
}}
|
||||
>
|
||||
<FaTrash className={`text-gray-50`}></FaTrash>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<OlRangeSlider
|
||||
value={IADSDensity}
|
||||
onChange={(ev) => {
|
||||
setIADSDensity(Number(ev.currentTarget.value));
|
||||
}}
|
||||
></OlRangeSlider>
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex justify-between">
|
||||
<div className="text-gray-100">IADS Distribution</div>
|
||||
<div
|
||||
<input
|
||||
type="text"
|
||||
className={`
|
||||
font-bold
|
||||
dark:text-blue-500
|
||||
block w-full flex-grow rounded-lg border border-gray-300
|
||||
bg-gray-50 p-2.5 text-sm text-gray-900
|
||||
dark:border-gray-600 dark:bg-gray-700 dark:text-white
|
||||
dark:placeholder-gray-400 dark:focus:border-blue-500
|
||||
dark:focus:ring-blue-500
|
||||
focus:border-blue-500 focus:ring-blue-500
|
||||
`}
|
||||
>
|
||||
{IADSDistribution}%
|
||||
</div>
|
||||
placeholder={activeCoalitionArea.getLabelText()}
|
||||
onInput={(ev) => activeCoalitionArea.setLabelText(ev.currentTarget.value)}
|
||||
></input>
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
flex content-center justify-start gap-4 px-6 text-gray-200
|
||||
`}
|
||||
>
|
||||
<div className="my-auto text-md">Coalition: </div>
|
||||
<OlCoalitionToggle
|
||||
coalition={areaCoalition}
|
||||
onClick={() => {
|
||||
let newCoalition = "";
|
||||
if (areaCoalition === "blue") newCoalition = "neutral";
|
||||
else if (areaCoalition === "neutral") newCoalition = "red";
|
||||
else if (areaCoalition === "red") newCoalition = "blue";
|
||||
setAreaCoalition(newCoalition as Coalition);
|
||||
activeCoalitionArea.setCoalition(newCoalition as Coalition);
|
||||
}}
|
||||
></OlCoalitionToggle>
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
flex flex-col gap-3 border-l-4 border-l-olympus-100
|
||||
bg-olympus-600 p-5
|
||||
`}
|
||||
>
|
||||
<div className={`
|
||||
border-b-2 border-b-olympus-100 pb-4 text-gray-300
|
||||
`}>Automatic IADS generation</div>
|
||||
<OlDropdown className="" label="Units types">
|
||||
{getApp()
|
||||
.getGroundUnitDatabase()
|
||||
.getTypes()
|
||||
.map((type, idx) => {
|
||||
if (!(type in typesSelection)) {
|
||||
typesSelection[type] = true;
|
||||
setTypesSelection(JSON.parse(JSON.stringify(typesSelection)));
|
||||
}
|
||||
|
||||
return (
|
||||
<OlDropdownItem key={idx} className={`flex gap-4`}>
|
||||
<OlCheckbox
|
||||
checked={typesSelection[type]}
|
||||
onChange={(ev) => {
|
||||
typesSelection[type] = ev.currentTarget.checked;
|
||||
setTypesSelection(JSON.parse(JSON.stringify(typesSelection)));
|
||||
}}
|
||||
/>
|
||||
<div>{type}</div>
|
||||
</OlDropdownItem>
|
||||
);
|
||||
})}
|
||||
</OlDropdown>
|
||||
<OlDropdown className="" label="Units eras">
|
||||
{getApp()
|
||||
.getGroundUnitDatabase()
|
||||
.getEras()
|
||||
.map((era) => {
|
||||
if (!(era in erasSelection)) {
|
||||
erasSelection[era] = true;
|
||||
setErasSelection(JSON.parse(JSON.stringify(erasSelection)));
|
||||
}
|
||||
|
||||
return (
|
||||
<OlDropdownItem className={`flex gap-4`}>
|
||||
<OlCheckbox
|
||||
checked={erasSelection[era]}
|
||||
onChange={(ev) => {
|
||||
erasSelection[era] = ev.currentTarget.checked;
|
||||
setErasSelection(JSON.parse(JSON.stringify(erasSelection)));
|
||||
}}
|
||||
/>
|
||||
<div>{era}</div>
|
||||
</OlDropdownItem>
|
||||
);
|
||||
})}
|
||||
</OlDropdown>
|
||||
<OlDropdown className="" label="Units ranges">
|
||||
{["Short range", "Medium range", "Long range"].map((range) => {
|
||||
if (!(range in rangesSelection)) {
|
||||
rangesSelection[range] = true;
|
||||
setRangesSelection(JSON.parse(JSON.stringify(rangesSelection)));
|
||||
}
|
||||
|
||||
return (
|
||||
<OlDropdownItem className={`flex gap-4`}>
|
||||
<OlCheckbox
|
||||
checked={rangesSelection[range]}
|
||||
onChange={(ev) => {
|
||||
rangesSelection[range] = ev.currentTarget.checked;
|
||||
setErasSelection(JSON.parse(JSON.stringify(rangesSelection)));
|
||||
}}
|
||||
/>
|
||||
<div>{range}</div>
|
||||
</OlDropdownItem>
|
||||
);
|
||||
})}
|
||||
</OlDropdown>
|
||||
<div>
|
||||
<div className="flex justify-between">
|
||||
<div className="text-gray-100">IADS Density</div>
|
||||
<div
|
||||
className={`
|
||||
font-bold
|
||||
dark:text-blue-500
|
||||
`}
|
||||
>
|
||||
{IADSDensity}%
|
||||
</div>
|
||||
</div>
|
||||
<OlRangeSlider
|
||||
value={IADSDensity}
|
||||
onChange={(ev) => {
|
||||
setIADSDensity(Number(ev.currentTarget.value));
|
||||
}}
|
||||
></OlRangeSlider>
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex justify-between">
|
||||
<div className="text-gray-100">IADS Distribution</div>
|
||||
<div
|
||||
className={`
|
||||
font-bold
|
||||
dark:text-blue-500
|
||||
`}
|
||||
>
|
||||
{IADSDistribution}%
|
||||
</div>
|
||||
</div>
|
||||
<OlRangeSlider
|
||||
value={IADSDistribution}
|
||||
onChange={(ev) => {
|
||||
setIADSDistribution(Number(ev.target.value));
|
||||
}}
|
||||
></OlRangeSlider>
|
||||
</div>
|
||||
<div className="flex content-center gap-4 text-gray-200">
|
||||
<OlCheckbox
|
||||
checked={forceCoalitionAppropriateUnits}
|
||||
onChange={() => {
|
||||
setForceCoalitionApproriateUnits(!forceCoalitionAppropriateUnits);
|
||||
}}
|
||||
/>
|
||||
Force coalition appropriate units
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className={`
|
||||
mb-2 me-2 rounded-lg 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
|
||||
`}
|
||||
onClick={() =>
|
||||
getApp()
|
||||
.getUnitsManager()
|
||||
.createIADS(
|
||||
activeCoalitionArea,
|
||||
typesSelection,
|
||||
erasSelection,
|
||||
rangesSelection,
|
||||
IADSDensity,
|
||||
IADSDistribution,
|
||||
forceCoalitionAppropriateUnits
|
||||
)
|
||||
}
|
||||
>
|
||||
Generate IADS
|
||||
</button>
|
||||
</div>
|
||||
<OlRangeSlider
|
||||
value={IADSDistribution}
|
||||
onChange={(ev) => {
|
||||
setIADSDistribution(Number(ev.target.value));
|
||||
}}
|
||||
></OlRangeSlider>
|
||||
</div>
|
||||
<div className="flex content-center gap-4 text-gray-200">
|
||||
<OlCheckbox
|
||||
checked={forceCoalitionAppropriateUnits}
|
||||
onChange={() => {
|
||||
setForceCoalitionApproriateUnits(!forceCoalitionAppropriateUnits);
|
||||
}}
|
||||
/>
|
||||
Force coalition appropriate units
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className={`
|
||||
mb-2 me-2 rounded-lg 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
|
||||
`}
|
||||
onClick={() =>
|
||||
getApp()
|
||||
.getUnitsManager()
|
||||
.createIADS(
|
||||
activeCoalitionArea,
|
||||
typesSelection,
|
||||
erasSelection,
|
||||
rangesSelection,
|
||||
IADSDensity,
|
||||
IADSDistribution,
|
||||
forceCoalitionAppropriateUnits
|
||||
)
|
||||
}
|
||||
>
|
||||
Generate IADS
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Menu>
|
||||
</Menu>
|
||||
)}
|
||||
</StateConsumer>
|
||||
);
|
||||
}
|
||||
|
||||
@ -10,6 +10,7 @@ import { FaMousePointer } from "react-icons/fa";
|
||||
import { OlLocation } from "../components/ollocation";
|
||||
import { FaBullseye } from "react-icons/fa6";
|
||||
import { JTACSubState, OlympusState } from "../../constants/constants";
|
||||
import { AppStateChangedEvent } from "../../events";
|
||||
|
||||
export function JTACMenu(props: { open: boolean; onClose: () => void; children?: JSX.Element | JSX.Element[] }) {
|
||||
const [referenceSystem, setReferenceSystem] = useState("LatLngDec");
|
||||
@ -23,24 +24,8 @@ export function JTACMenu(props: { open: boolean; onClose: () => void; children?:
|
||||
const [type, setType] = useState("Type 1");
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener("selectJTACTarget", (ev: CustomEventInit) => {
|
||||
setTargetLocation(null);
|
||||
setTargetUnit(null);
|
||||
|
||||
if (ev.detail.location) setTargetLocation(ev.detail.location);
|
||||
if (ev.detail.unit) setTargetUnit(ev.detail.unit);
|
||||
});
|
||||
|
||||
document.addEventListener("selectJTACECHO", (ev: CustomEventInit) => {
|
||||
setECHO(ev.detail);
|
||||
});
|
||||
|
||||
document.addEventListener("selectJTACIP", (ev: CustomEventInit) => {
|
||||
setIP(ev.detail);
|
||||
});
|
||||
|
||||
document.addEventListener("appStateChanged", (ev: CustomEventInit) => {
|
||||
if (ev.detail.subState === JTACSubState.SELECT_TARGET) {
|
||||
AppStateChangedEvent.on((state, subState) => {
|
||||
if (subState === JTACSubState.SELECT_TARGET) {
|
||||
setTargetLocation(null);
|
||||
setTargetUnit(null);
|
||||
}
|
||||
|
||||
@ -1,34 +1,14 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import React, { useState, useEffect, useContext } from "react";
|
||||
import { zeroAppend } from "../../other/utils";
|
||||
import { DateAndTime } from "../../interfaces";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { FaChevronDown, FaChevronUp } from "react-icons/fa6";
|
||||
import { StateContext } from "../../statecontext";
|
||||
|
||||
export function MiniMapPanel(props: {}) {
|
||||
const [frameRate, setFrameRate] = useState(0);
|
||||
const [load, setLoad] = useState(0);
|
||||
const [elapsedTime, setElapsedTime] = useState(0);
|
||||
const [missionTime, setMissionTime] = useState({
|
||||
h: 0,
|
||||
m: 0,
|
||||
s: 0,
|
||||
} as DateAndTime["time"]);
|
||||
const [connected, setConnected] = useState(false);
|
||||
const [paused, setPaused] = useState(false);
|
||||
const [showMissionTime, setShowMissionTime] = useState(false);
|
||||
const [showMinimap, setShowMinimap] = useState(false);
|
||||
const appState = useContext(StateContext)
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener("serverStatusUpdated", (ev) => {
|
||||
const detail = (ev as CustomEvent).detail;
|
||||
setFrameRate(detail.frameRate);
|
||||
setLoad(detail.load);
|
||||
setElapsedTime(detail.elapsedTime);
|
||||
setMissionTime(detail.missionTime);
|
||||
setConnected(detail.connected);
|
||||
setPaused(detail.paused);
|
||||
});
|
||||
}, []);
|
||||
const [showMissionTime, setShowMissionTime] = useState(false);
|
||||
|
||||
// A bit of a hack to set the rounded borders to the minimap
|
||||
useEffect(() => {
|
||||
@ -38,55 +18,51 @@ export function MiniMapPanel(props: {}) {
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("mapOptionsChanged", (event) => {
|
||||
setShowMinimap(getApp().getMap().getOptions().showMinimap);
|
||||
});
|
||||
|
||||
// Compute the time string depending on mission or elapsed time
|
||||
let hours = 0;
|
||||
let minutes = 0;
|
||||
let seconds = 0;
|
||||
|
||||
if (showMissionTime) {
|
||||
hours = missionTime.h;
|
||||
minutes = missionTime.m;
|
||||
seconds = missionTime.s;
|
||||
hours = appState.serverStatus.missionTime.h;
|
||||
minutes = appState.serverStatus.missionTime.m;
|
||||
seconds = appState.serverStatus.missionTime.s;
|
||||
} else {
|
||||
hours = Math.floor(elapsedTime / 3600);
|
||||
minutes = Math.floor(elapsedTime / 60) % 60;
|
||||
seconds = Math.round(elapsedTime) % 60;
|
||||
hours = Math.floor(appState.serverStatus.elapsedTime / 3600);
|
||||
minutes = Math.floor(appState.serverStatus.elapsedTime / 60) % 60;
|
||||
seconds = Math.round(appState.serverStatus.elapsedTime) % 60;
|
||||
}
|
||||
|
||||
let timeString = `${zeroAppend(hours, 2)}:${zeroAppend(minutes, 2)}:${zeroAppend(seconds, 2)}`;
|
||||
|
||||
// Choose frame rate string color
|
||||
let frameRateColor = "#8BFF63";
|
||||
if (frameRate < 30) frameRateColor = "#F05252";
|
||||
else if (frameRate >= 30 && frameRate < 60) frameRateColor = "#FF9900";
|
||||
if (appState.serverStatus.frameRate < 30) frameRateColor = "#F05252";
|
||||
else if (appState.serverStatus.frameRate >= 30 && appState.serverStatus.frameRate < 60) frameRateColor = "#FF9900";
|
||||
|
||||
// Choose load string color
|
||||
let loadColor = "#8BFF63";
|
||||
if (load > 1000) loadColor = "#F05252";
|
||||
else if (load >= 100 && load < 1000) loadColor = "#FF9900";
|
||||
if (appState.serverStatus.load > 1000) loadColor = "#F05252";
|
||||
else if (appState.serverStatus.load >= 100 && appState.serverStatus.load < 1000) loadColor = "#FF9900";
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={() => setShowMissionTime(!showMissionTime)}
|
||||
className={`
|
||||
absolute right-[10px]
|
||||
${showMinimap ? `top-[232px]` : `top-[70px]`}
|
||||
${appState.mapOptions.showMinimap ? `top-[232px]` : `top-[70px]`}
|
||||
flex w-[288px] items-center justify-between
|
||||
${showMinimap ? `rounded-b-lg` : `rounded-lg`}
|
||||
${appState.mapOptions.showMinimap ? `rounded-b-lg` : `rounded-lg`}
|
||||
bg-gray-200 p-3 text-sm backdrop-blur-lg backdrop-grayscale
|
||||
dark:bg-olympus-800/90 dark:text-gray-200
|
||||
`}
|
||||
>
|
||||
{!connected ? (
|
||||
{!appState.serverStatus.connected ? (
|
||||
<div className={`flex animate-pulse items-center gap-2 font-semibold`}>
|
||||
<div className={`relative h-4 w-4 rounded-full bg-[#F05252]`}></div>
|
||||
Server disconnected
|
||||
</div>
|
||||
) : paused ? (
|
||||
) : appState.serverStatus.paused ? (
|
||||
<div className={`flex animate-pulse items-center gap-2 font-semibold`}>
|
||||
<div className={`relative h-4 w-4 rounded-full bg-[#FF9900]`}></div>
|
||||
Server paused
|
||||
@ -96,13 +72,13 @@ export function MiniMapPanel(props: {}) {
|
||||
<div className="flex gap-2 font-semibold">
|
||||
FPS:
|
||||
<span style={{ color: frameRateColor }} className={`font-semibold`}>
|
||||
{frameRate}
|
||||
{appState.serverStatus.frameRate}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex gap-2 font-semibold">
|
||||
Load:
|
||||
<span style={{ color: loadColor }} className={`font-semibold`}>
|
||||
{load}
|
||||
{appState.serverStatus.load}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex gap-2 font-semibold">
|
||||
@ -111,7 +87,7 @@ export function MiniMapPanel(props: {}) {
|
||||
<div className={`relative h-4 w-4 rounded-full bg-[#8BFF63]`}></div>
|
||||
</>
|
||||
)}
|
||||
{showMinimap ? (
|
||||
{appState.mapOptions.showMinimap ? (
|
||||
<FaChevronUp
|
||||
onClick={() => {
|
||||
getApp().getMap().setOption("showMinimap", false);
|
||||
|
||||
@ -22,6 +22,7 @@ import { navyUnitDatabase } from "../../unit/databases/navyunitdatabase";
|
||||
import { filterBlueprintsByLabel } from "../../other/utils";
|
||||
import { helicopterDatabase } from "../../unit/databases/helicopterdatabase";
|
||||
import { groundUnitDatabase } from "../../unit/databases/groundunitdatabase";
|
||||
import { AppStateChangedEvent } from "../../events";
|
||||
|
||||
enum Accordion {
|
||||
NONE,
|
||||
@ -67,8 +68,8 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener("appStateChanged", (ev: CustomEventInit) => {
|
||||
if (ev.detail.subState === NO_SUBSTATE) {
|
||||
AppStateChangedEvent.on((state, subState) => {
|
||||
if (subState === NO_SUBSTATE) {
|
||||
setBlueprint(null);
|
||||
setEffect(null);
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { MutableRefObject, useEffect, useRef, useState } from "react";
|
||||
import React, { MutableRefObject, useContext, useEffect, useRef, useState } from "react";
|
||||
import { Menu } from "./components/menu";
|
||||
import { Unit } from "../../unit/unit";
|
||||
import { OlLabelToggle } from "../components/ollabeltoggle";
|
||||
@ -50,9 +50,11 @@ import { Radio, TACAN } from "../../interfaces";
|
||||
import { OlStringInput } from "../components/olstringinput";
|
||||
import { OlFrequencyInput } from "../components/olfrequencyinput";
|
||||
import { UnitSink } from "../../audio/unitsink";
|
||||
import { StateContext } from "../../statecontext";
|
||||
|
||||
export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
const [selectedUnits, setSelectedUnits] = useState([] as Unit[]);
|
||||
const appState = useContext(StateContext);
|
||||
|
||||
const [selectedUnitsData, setSelectedUnitsData] = useState({
|
||||
desiredAltitude: undefined as undefined | number,
|
||||
desiredAltitudeType: undefined as undefined | string,
|
||||
@ -105,7 +107,6 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
const [filterString, setFilterString] = useState("");
|
||||
const [showAdvancedSettings, setShowAdvancedSettings] = useState(false);
|
||||
const [activeAdvancedSettings, setActiveAdvancedSettings] = useState(null as null | { radio: Radio; TACAN: TACAN });
|
||||
const [audioManagerEnabled, setAudioManagerEnabled] = useState(false);
|
||||
|
||||
var searchBarRef = useRef(null);
|
||||
|
||||
@ -115,83 +116,26 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
if (!props.open && filterString !== "") setFilterString("");
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
/* When a unit is selected, update the data */
|
||||
document.addEventListener("unitsSelection", (ev: CustomEventInit) => {
|
||||
setSelectedUnits(ev.detail as Unit[]);
|
||||
updateData();
|
||||
});
|
||||
|
||||
/* When a unit is deselected, refresh the view */
|
||||
document.addEventListener("unitDeselection", (ev: CustomEventInit) => {
|
||||
window.setTimeout(() => updateData(), 200);
|
||||
});
|
||||
|
||||
/* When all units are deselected clean the view */
|
||||
document.addEventListener("clearSelection", () => {
|
||||
setSelectedUnits([]);
|
||||
});
|
||||
|
||||
document.addEventListener("audioManagerStateChanged", () => {
|
||||
setAudioManagerEnabled(getApp().getAudioManager().isRunning());
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setShowAdvancedSettings(false);
|
||||
}, [selectedUnits])
|
||||
|
||||
/* Update the current values of the shown data */
|
||||
function updateData() {
|
||||
const getters = {
|
||||
desiredAltitude: (unit: Unit) => {
|
||||
return Math.round(mToFt(unit.getDesiredAltitude()));
|
||||
},
|
||||
desiredAltitudeType: (unit: Unit) => {
|
||||
return unit.getDesiredAltitudeType();
|
||||
},
|
||||
desiredSpeed: (unit: Unit) => {
|
||||
return Math.round(msToKnots(unit.getDesiredSpeed()));
|
||||
},
|
||||
desiredSpeedType: (unit: Unit) => {
|
||||
return unit.getDesiredSpeedType();
|
||||
},
|
||||
ROE: (unit: Unit) => {
|
||||
return unit.getROE();
|
||||
},
|
||||
reactionToThreat: (unit: Unit) => {
|
||||
return unit.getReactionToThreat();
|
||||
},
|
||||
emissionsCountermeasures: (unit: Unit) => {
|
||||
return unit.getEmissionsCountermeasures();
|
||||
},
|
||||
scenicAAA: (unit: Unit) => {
|
||||
return unit.getState() === "scenic-aaa";
|
||||
},
|
||||
missOnPurpose: (unit: Unit) => {
|
||||
return unit.getState() === "miss-on-purpose";
|
||||
},
|
||||
shotsScatter: (unit: Unit) => {
|
||||
return unit.getShotsScatter();
|
||||
},
|
||||
shotsIntensity: (unit: Unit) => {
|
||||
return unit.getShotsIntensity();
|
||||
},
|
||||
operateAs: (unit: Unit) => {
|
||||
return unit.getOperateAs();
|
||||
},
|
||||
followRoads: (unit: Unit) => {
|
||||
return unit.getFollowRoads();
|
||||
},
|
||||
isActiveAWACS: (unit: Unit) => {
|
||||
return unit.getIsActiveAWACS();
|
||||
},
|
||||
isActiveTanker: (unit: Unit) => {
|
||||
return unit.getIsActiveTanker();
|
||||
},
|
||||
onOff: (unit: Unit) => {
|
||||
return unit.getOnOff();
|
||||
},
|
||||
desiredAltitude: (unit: Unit) => Math.round(mToFt(unit.getDesiredAltitude())),
|
||||
desiredAltitudeType: (unit: Unit) => unit.getDesiredAltitudeType(),
|
||||
desiredSpeed: (unit: Unit) => Math.round(msToKnots(unit.getDesiredSpeed())),
|
||||
desiredSpeedType: (unit: Unit) => unit.getDesiredSpeedType(),
|
||||
ROE: (unit: Unit) => unit.getROE(),
|
||||
reactionToThreat: (unit: Unit) => unit.getReactionToThreat(),
|
||||
emissionsCountermeasures: (unit: Unit) => unit.getEmissionsCountermeasures(),
|
||||
scenicAAA: (unit: Unit) => unit.getState() === "scenic-aaa",
|
||||
missOnPurpose: (unit: Unit) => unit.getState() === "miss-on-purpose",
|
||||
shotsScatter: (unit: Unit) => unit.getShotsScatter(),
|
||||
shotsIntensity: (unit: Unit) => unit.getShotsIntensity(),
|
||||
operateAs: (unit: Unit) => unit.getOperateAs(),
|
||||
followRoads: (unit: Unit) => unit.getFollowRoads(),
|
||||
isActiveAWACS: (unit: Unit) => unit.getIsActiveAWACS(),
|
||||
isActiveTanker: (unit: Unit) => unit.getIsActiveTanker(),
|
||||
onOff: (unit: Unit) => unit.getOnOff(),
|
||||
isAudioSink: (unit: Unit) => {
|
||||
return (
|
||||
getApp()
|
||||
@ -215,7 +159,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
updatedData[key] = getApp()?.getUnitsManager()?.getSelectedUnitsVariable(getter);
|
||||
});
|
||||
setSelectedUnitsData(updatedData);
|
||||
}
|
||||
}, [appState.selectedUnits]);
|
||||
|
||||
/* Count how many units are selected of each type, divided by coalition */
|
||||
var unitOccurences: {
|
||||
@ -228,7 +172,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
neutral: {},
|
||||
};
|
||||
|
||||
selectedUnits.forEach((unit) => {
|
||||
appState.selectedUnits.forEach((unit) => {
|
||||
if (!(unit.getName() in unitOccurences[unit.getCoalition()]))
|
||||
unitOccurences[unit.getCoalition()][unit.getName()] = { occurences: 1, label: unit.getBlueprint()?.label };
|
||||
else unitOccurences[unit.getCoalition()][unit.getName()].occurences++;
|
||||
@ -236,7 +180,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
|
||||
const selectedCategories = getApp()?.getUnitsManager()?.getSelectedUnitsCategories() ?? [];
|
||||
|
||||
const [filteredAircraft, filteredHelicopters, filteredAirDefense, filteredGroundUnits, filteredNavyUnits] = [{}, {}, {}, {}, {}] // TODOgetUnitsByLabel(filterString);
|
||||
const [filteredAircraft, filteredHelicopters, filteredAirDefense, filteredGroundUnits, filteredNavyUnits] = [{}, {}, {}, {}, {}]; // TODOgetUnitsByLabel(filterString);
|
||||
|
||||
const mergedFilteredUnits = Object.assign({}, filteredAircraft, filteredHelicopters, filteredAirDefense, filteredGroundUnits, filteredNavyUnits) as {
|
||||
[key: string]: UnitBlueprint;
|
||||
@ -276,13 +220,13 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
return (
|
||||
<Menu
|
||||
open={props.open}
|
||||
title={selectedUnits.length > 0 ? `Units selected (x${selectedUnits.length})` : `No units selected`}
|
||||
title={appState.selectedUnits.length > 0 ? `Units selected (x${appState.selectedUnits.length})` : `No units selected`}
|
||||
onClose={props.onClose}
|
||||
canBeHidden={true}
|
||||
>
|
||||
<>
|
||||
{/* ============== Selection tool START ============== */}
|
||||
{selectedUnits.length == 0 && (
|
||||
{appState.selectedUnits.length == 0 && (
|
||||
<div className="flex flex-col gap-4 p-4">
|
||||
<div className="text-lg text-bold text-gray-200">Selection tool</div>
|
||||
<div className="text-sm text-gray-400">
|
||||
@ -485,7 +429,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
{/* */}
|
||||
<>
|
||||
{/* ============== Unit control menu START ============== */}
|
||||
{selectedUnits.length > 0 && (
|
||||
{appState.selectedUnits.length > 0 && (
|
||||
<>
|
||||
{/* ============== Units list START ============== */}
|
||||
<div
|
||||
@ -578,7 +522,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
leftLabel={"AGL"}
|
||||
rightLabel={"ASL"}
|
||||
onClick={() => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
appState.selectedUnits.forEach((unit) => {
|
||||
unit.setAltitudeType(selectedUnitsData.desiredAltitudeType === "ASL" ? "AGL" : "ASL");
|
||||
setSelectedUnitsData({
|
||||
...selectedUnitsData,
|
||||
@ -590,7 +534,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
</div>
|
||||
<OlRangeSlider
|
||||
onChange={(ev) => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
appState.selectedUnits.forEach((unit) => {
|
||||
unit.setAltitude(ftToM(Number(ev.target.value)));
|
||||
setSelectedUnitsData({
|
||||
...selectedUnitsData,
|
||||
@ -640,7 +584,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
leftLabel={"GS"}
|
||||
rightLabel={"CAS"}
|
||||
onClick={() => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
appState.selectedUnits.forEach((unit) => {
|
||||
unit.setSpeedType(selectedUnitsData.desiredSpeedType === "CAS" ? "GS" : "CAS");
|
||||
setSelectedUnitsData({
|
||||
...selectedUnitsData,
|
||||
@ -653,7 +597,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
</div>
|
||||
<OlRangeSlider
|
||||
onChange={(ev) => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
appState.selectedUnits.forEach((unit) => {
|
||||
unit.setSpeed(knotsToMs(Number(ev.target.value)));
|
||||
setSelectedUnitsData({
|
||||
...selectedUnitsData,
|
||||
@ -669,38 +613,39 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
</div>
|
||||
{/* ============== Airspeed selector END ============== */}
|
||||
{/* ============== Rules of Engagement START ============== */}
|
||||
{!(selectedUnits.length === 1 && selectedUnits[0].isTanker()) && !(selectedUnits.length === 1 && selectedUnits[0].isAWACS()) && (
|
||||
<div className="flex flex-col gap-2">
|
||||
<span
|
||||
className={`
|
||||
my-auto font-normal
|
||||
dark:text-white
|
||||
`}
|
||||
>
|
||||
Rules of engagement
|
||||
</span>
|
||||
<OlButtonGroup>
|
||||
{[olButtonsRoeHold, olButtonsRoeReturn, olButtonsRoeDesignated, olButtonsRoeFree].map((icon, idx) => {
|
||||
return (
|
||||
<OlButtonGroupItem
|
||||
key={idx}
|
||||
onClick={() => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
unit.setROE(ROEs[idx]);
|
||||
setSelectedUnitsData({
|
||||
...selectedUnitsData,
|
||||
ROE: ROEs[idx],
|
||||
{!(appState.selectedUnits.length === 1 && appState.selectedUnits[0].isTanker()) &&
|
||||
!(appState.selectedUnits.length === 1 && appState.selectedUnits[0].isAWACS()) && (
|
||||
<div className="flex flex-col gap-2">
|
||||
<span
|
||||
className={`
|
||||
my-auto font-normal
|
||||
dark:text-white
|
||||
`}
|
||||
>
|
||||
Rules of engagement
|
||||
</span>
|
||||
<OlButtonGroup>
|
||||
{[olButtonsRoeHold, olButtonsRoeReturn, olButtonsRoeDesignated, olButtonsRoeFree].map((icon, idx) => {
|
||||
return (
|
||||
<OlButtonGroupItem
|
||||
key={idx}
|
||||
onClick={() => {
|
||||
appState.selectedUnits.forEach((unit) => {
|
||||
unit.setROE(ROEs[idx]);
|
||||
setSelectedUnitsData({
|
||||
...selectedUnitsData,
|
||||
ROE: ROEs[idx],
|
||||
});
|
||||
});
|
||||
});
|
||||
}}
|
||||
active={selectedUnitsData.ROE === ROEs[idx]}
|
||||
icon={icon}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</OlButtonGroup>
|
||||
</div>
|
||||
)}
|
||||
}}
|
||||
active={selectedUnitsData.ROE === ROEs[idx]}
|
||||
icon={icon}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</OlButtonGroup>
|
||||
</div>
|
||||
)}
|
||||
{/* ============== Rules of Engagement END ============== */}
|
||||
|
||||
{selectedCategories.every((category) => {
|
||||
@ -723,7 +668,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
<OlButtonGroupItem
|
||||
key={idx}
|
||||
onClick={() => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
appState.selectedUnits.forEach((unit) => {
|
||||
unit.setReactionToThreat(reactionsToThreat[idx]);
|
||||
setSelectedUnitsData({
|
||||
...selectedUnitsData,
|
||||
@ -755,7 +700,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
<OlButtonGroupItem
|
||||
key={idx}
|
||||
onClick={() => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
appState.selectedUnits.forEach((unit) => {
|
||||
unit.setEmissionsCountermeasures(emissionsCountermeasures[idx]);
|
||||
setSelectedUnitsData({
|
||||
...selectedUnitsData,
|
||||
@ -791,7 +736,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
<OlToggle
|
||||
toggled={selectedUnitsData.isActiveTanker}
|
||||
onClick={() => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
appState.selectedUnits.forEach((unit) => {
|
||||
unit.setAdvancedOptions(
|
||||
!selectedUnitsData.isActiveTanker,
|
||||
unit.getIsActiveAWACS(),
|
||||
@ -825,7 +770,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
<OlToggle
|
||||
toggled={selectedUnitsData.isActiveAWACS}
|
||||
onClick={() => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
appState.selectedUnits.forEach((unit) => {
|
||||
unit.setAdvancedOptions(
|
||||
unit.getIsActiveTanker(),
|
||||
!selectedUnitsData.isActiveAWACS,
|
||||
@ -844,7 +789,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
)}
|
||||
{/* ============== Tanker and AWACS available button END ============== */}
|
||||
{/* ============== Advanced settings buttons START ============== */}
|
||||
{selectedUnits.length === 1 && (selectedUnits[0].isTanker() || selectedUnits[0].isAWACS()) && (
|
||||
{appState.selectedUnits.length === 1 && (appState.selectedUnits[0].isTanker() || appState.selectedUnits[0].isAWACS()) && (
|
||||
<div className="flex content-center justify-between">
|
||||
<button
|
||||
className={`
|
||||
@ -855,13 +800,13 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
`}
|
||||
onClick={() => {
|
||||
setActiveAdvancedSettings({
|
||||
radio: JSON.parse(JSON.stringify(selectedUnits[0].getRadio())),
|
||||
TACAN: JSON.parse(JSON.stringify(selectedUnits[0].getTACAN())),
|
||||
radio: JSON.parse(JSON.stringify(appState.selectedUnits[0].getRadio())),
|
||||
TACAN: JSON.parse(JSON.stringify(appState.selectedUnits[0].getTACAN())),
|
||||
});
|
||||
setShowAdvancedSettings(true);
|
||||
}}
|
||||
>
|
||||
<FaCog className="my-auto" /> {selectedUnits[0].isTanker() ? "Configure tanker settings" : "Configure AWACS settings"}
|
||||
<FaCog className="my-auto" /> {appState.selectedUnits[0].isTanker() ? "Configure tanker settings" : "Configure AWACS settings"}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
@ -889,7 +834,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
<OlToggle
|
||||
toggled={selectedUnitsData.scenicAAA}
|
||||
onClick={() => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
appState.selectedUnits.forEach((unit) => {
|
||||
selectedUnitsData.scenicAAA ? unit.changeSpeed("stop") : unit.scenicAAA();
|
||||
setSelectedUnitsData({
|
||||
...selectedUnitsData,
|
||||
@ -914,7 +859,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
<OlToggle
|
||||
toggled={selectedUnitsData.missOnPurpose}
|
||||
onClick={() => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
appState.selectedUnits.forEach((unit) => {
|
||||
selectedUnitsData.missOnPurpose ? unit.changeSpeed("stop") : unit.missOnPurpose();
|
||||
setSelectedUnitsData({
|
||||
...selectedUnitsData,
|
||||
@ -943,7 +888,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
<OlButtonGroupItem
|
||||
key={idx}
|
||||
onClick={() => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
appState.selectedUnits.forEach((unit) => {
|
||||
unit.setShotsScatter(idx + 1);
|
||||
setSelectedUnitsData({
|
||||
...selectedUnitsData,
|
||||
@ -975,7 +920,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
<OlButtonGroupItem
|
||||
key={idx}
|
||||
onClick={() => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
appState.selectedUnits.forEach((unit) => {
|
||||
unit.setShotsIntensity(idx + 1);
|
||||
setSelectedUnitsData({
|
||||
...selectedUnitsData,
|
||||
@ -1005,7 +950,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
<OlCoalitionToggle
|
||||
coalition={selectedUnitsData.operateAs as Coalition}
|
||||
onClick={() => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
appState.selectedUnits.forEach((unit) => {
|
||||
unit.setOperateAs(selectedUnitsData.operateAs === "blue" ? "red" : "blue");
|
||||
setSelectedUnitsData({
|
||||
...selectedUnitsData,
|
||||
@ -1030,7 +975,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
<OlToggle
|
||||
toggled={selectedUnitsData.followRoads}
|
||||
onClick={() => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
appState.selectedUnits.forEach((unit) => {
|
||||
unit.setFollowRoads(!selectedUnitsData.followRoads);
|
||||
setSelectedUnitsData({
|
||||
...selectedUnitsData,
|
||||
@ -1054,7 +999,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
<OlToggle
|
||||
toggled={selectedUnitsData.onOff}
|
||||
onClick={() => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
appState.selectedUnits.forEach((unit) => {
|
||||
unit.setOnOff(!selectedUnitsData.onOff);
|
||||
setSelectedUnitsData({
|
||||
...selectedUnitsData,
|
||||
@ -1077,11 +1022,11 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
>
|
||||
Loudspeakers
|
||||
</span>
|
||||
{audioManagerEnabled ? (
|
||||
{appState.audioManagerState ? (
|
||||
<OlToggle
|
||||
toggled={selectedUnitsData.isAudioSink}
|
||||
onClick={() => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
appState.selectedUnits.forEach((unit) => {
|
||||
if (!selectedUnitsData.isAudioSink) {
|
||||
getApp()?.getAudioManager().addUnitSink(unit);
|
||||
setSelectedUnitsData({
|
||||
@ -1115,7 +1060,8 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
`}
|
||||
>
|
||||
<FaVolumeHigh />
|
||||
</span>{" "}first
|
||||
</span>{" "}
|
||||
first
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@ -1131,14 +1077,14 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
<div className="flex content-center gap-2">
|
||||
<OlDropdown
|
||||
label={
|
||||
selectedUnits[0].isAWACS()
|
||||
appState.selectedUnits[0].isAWACS()
|
||||
? ["Overlord", "Magic", "Wizard", "Focus", "Darkstar"][activeAdvancedSettings ? activeAdvancedSettings.radio.callsign - 1 : 0]
|
||||
: ["Texaco", "Arco", "Shell"][activeAdvancedSettings ? activeAdvancedSettings.radio.callsign - 1 : 0]
|
||||
}
|
||||
className="my-auto w-full"
|
||||
>
|
||||
<>
|
||||
{selectedUnits[0].isAWACS() && (
|
||||
{appState.selectedUnits[0].isAWACS() && (
|
||||
<>
|
||||
{["Overlord", "Magic", "Wizard", "Focus", "Darkstar"].map((name, idx) => {
|
||||
return (
|
||||
@ -1157,7 +1103,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
)}
|
||||
</>
|
||||
<>
|
||||
{selectedUnits[0].isTanker() && (
|
||||
{appState.selectedUnits[0].isTanker() && (
|
||||
<>
|
||||
{["Texaco", "Arco", "Shell"].map((name, idx) => {
|
||||
return (
|
||||
@ -1220,9 +1166,10 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
value={activeAdvancedSettings ? activeAdvancedSettings.TACAN.channel : 1}
|
||||
></OlNumberInput>
|
||||
|
||||
<OlDropdown label={activeAdvancedSettings ? activeAdvancedSettings.TACAN.XY : "X"} className={`
|
||||
my-auto w-20
|
||||
`}>
|
||||
<OlDropdown
|
||||
label={activeAdvancedSettings ? activeAdvancedSettings.TACAN.XY : "X"}
|
||||
className={`my-auto w-20`}
|
||||
>
|
||||
<OlDropdownItem
|
||||
key={"X"}
|
||||
onClick={() => {
|
||||
@ -1291,12 +1238,12 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
`}
|
||||
onClick={() => {
|
||||
if (activeAdvancedSettings)
|
||||
selectedUnits[0].setAdvancedOptions(
|
||||
selectedUnits[0].getIsActiveTanker(),
|
||||
selectedUnits[0].getIsActiveAWACS(),
|
||||
appState.selectedUnits[0].setAdvancedOptions(
|
||||
appState.selectedUnits[0].getIsActiveTanker(),
|
||||
appState.selectedUnits[0].getIsActiveAWACS(),
|
||||
activeAdvancedSettings.TACAN,
|
||||
activeAdvancedSettings.radio,
|
||||
selectedUnits[0].getGeneralSettings()
|
||||
appState.selectedUnits[0].getGeneralSettings()
|
||||
);
|
||||
setActiveAdvancedSettings(null);
|
||||
setShowAdvancedSettings(false);
|
||||
@ -1329,7 +1276,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
{/* ============== Unit basic options END ============== */}
|
||||
<>
|
||||
{/* ============== Fuel/payload/radio section START ============== */}
|
||||
{selectedUnits.length === 1 && (
|
||||
{appState.selectedUnits.length === 1 && (
|
||||
<div
|
||||
className={`
|
||||
flex flex-col gap-4 border-l-4 border-l-olympus-100
|
||||
@ -1340,21 +1287,27 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
<div
|
||||
className={`
|
||||
flex content-center gap-2 rounded-full
|
||||
${selectedUnits[0].getFuel() > 40 && `bg-green-700`}
|
||||
${selectedUnits[0].getFuel() > 10 && selectedUnits[0].getFuel() <= 40 && `
|
||||
bg-yellow-700
|
||||
${appState.selectedUnits[0].getFuel() > 40 && `
|
||||
bg-green-700
|
||||
`}
|
||||
${
|
||||
appState.selectedUnits[0].getFuel() > 10 &&
|
||||
appState.selectedUnits[0].getFuel() <= 40 &&
|
||||
`bg-yellow-700`
|
||||
}
|
||||
${appState.selectedUnits[0].getFuel() <= 10 && `
|
||||
bg-red-700
|
||||
`}
|
||||
${selectedUnits[0].getFuel() <= 10 && `bg-red-700`}
|
||||
px-2 py-1 text-sm font-bold text-white
|
||||
`}
|
||||
>
|
||||
<FaGasPump className="my-auto" />
|
||||
{selectedUnits[0].getFuel()}%
|
||||
{appState.selectedUnits[0].getFuel()}%
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
{selectedUnits[0].isControlledByOlympus() && (selectedUnits[0].isTanker() || selectedUnits[0].isAWACS()) && (
|
||||
{appState.selectedUnits[0].isControlledByOlympus() && (appState.selectedUnits[0].isTanker() || appState.selectedUnits[0].isAWACS()) && (
|
||||
<>
|
||||
{/* ============== Radio section START ============== */}
|
||||
<div className="flex content-center justify-between">
|
||||
@ -1376,7 +1329,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
dark:text-gray-300
|
||||
`}
|
||||
>
|
||||
{`${selectedUnits[0].isTanker() ? ["Texaco", "Arco", "Shell"][selectedUnits[0].getRadio().callsign - 1] : ["Overlord", "Magic", "Wizard", "Focus", "Darkstar"][selectedUnits[0].getRadio().callsign - 1]}-${selectedUnits[0].getRadio().callsignNumber}`}
|
||||
{`${appState.selectedUnits[0].isTanker() ? ["Texaco", "Arco", "Shell"][appState.selectedUnits[0].getRadio().callsign - 1] : ["Overlord", "Magic", "Wizard", "Focus", "Darkstar"][appState.selectedUnits[0].getRadio().callsign - 1]}-${appState.selectedUnits[0].getRadio().callsignNumber}`}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1399,7 +1352,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
dark:text-gray-300
|
||||
`}
|
||||
>
|
||||
{`${(selectedUnits[0].getRadio().frequency / 1000000).toFixed(3)} MHz`}
|
||||
{`${(appState.selectedUnits[0].getRadio().frequency / 1000000).toFixed(3)} MHz`}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1423,8 +1376,8 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
dark:text-gray-300
|
||||
`}
|
||||
>
|
||||
{selectedUnits[0].getTACAN().isOn
|
||||
? `${selectedUnits[0].getTACAN().channel}${selectedUnits[0].getTACAN().XY} ${selectedUnits[0].getTACAN().callsign}`
|
||||
{appState.selectedUnits[0].getTACAN().isOn
|
||||
? `${appState.selectedUnits[0].getTACAN().channel}${appState.selectedUnits[0].getTACAN().XY} ${appState.selectedUnits[0].getTACAN().callsign}`
|
||||
: "TACAN OFF"}
|
||||
</div>
|
||||
</div>
|
||||
@ -1433,9 +1386,9 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
</>
|
||||
)}
|
||||
{/* ============== Payload section START ============== */}
|
||||
{!selectedUnits[0].isTanker() &&
|
||||
!selectedUnits[0].isAWACS() &&
|
||||
selectedUnits[0].getAmmo().map((ammo, idx) => {
|
||||
{!appState.selectedUnits[0].isTanker() &&
|
||||
!appState.selectedUnits[0].isAWACS() &&
|
||||
appState.selectedUnits[0].getAmmo().map((ammo, idx) => {
|
||||
return (
|
||||
<div className="flex content-center gap-2" key={idx}>
|
||||
<div
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import React, { useContext, useEffect, useRef, useState } from "react";
|
||||
import { Unit } from "../../unit/unit";
|
||||
import { ContextActionSet } from "../../unit/contextactionset";
|
||||
import { OlStateButton } from "../components/olstatebutton";
|
||||
@ -8,11 +8,13 @@ import { CONTEXT_ACTION_COLORS } from "../../constants/constants";
|
||||
import { FaInfoCircle } from "react-icons/fa";
|
||||
import { FaChevronLeft, FaChevronRight } from "react-icons/fa6";
|
||||
import { OlympusState } from "../../constants/constants";
|
||||
import { AppStateChangedEvent } from "../../events";
|
||||
import { StateContext } from "../../statecontext";
|
||||
|
||||
export function UnitMouseControlBar(props: {}) {
|
||||
const appState = useContext(StateContext);
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
const [contextActionsSet, setContextActionsSet] = useState(new ContextActionSet());
|
||||
const [activeContextAction, setActiveContextAction] = useState(null as null | ContextAction);
|
||||
const [scrolledLeft, setScrolledLeft] = useState(true);
|
||||
const [scrolledRight, setScrolledRight] = useState(false);
|
||||
|
||||
@ -23,45 +25,11 @@ export function UnitMouseControlBar(props: {}) {
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
/* When a unit is selected, open the menu */
|
||||
document.addEventListener("unitsSelection", (ev: CustomEventInit) => {
|
||||
setOpen(true);
|
||||
updateData();
|
||||
setActiveContextAction(null);
|
||||
});
|
||||
|
||||
/* When a unit is deselected, refresh the view */
|
||||
document.addEventListener("unitDeselection", (ev: CustomEventInit) => {
|
||||
window.setTimeout(() => updateData(), 200);
|
||||
});
|
||||
|
||||
/* When all units are deselected clean the view */
|
||||
document.addEventListener("clearSelection", () => {
|
||||
setOpen(false);
|
||||
updateData();
|
||||
});
|
||||
|
||||
/* Deselect the context action when exiting state */
|
||||
document.addEventListener("appStateChanged", (ev) => {
|
||||
setOpen((ev as CustomEvent).detail.state === OlympusState.UNIT_CONTROL);
|
||||
AppStateChangedEvent.on((state, subState) => {
|
||||
setOpen(state === OlympusState.UNIT_CONTROL);
|
||||
});
|
||||
}, []);
|
||||
|
||||
/* Update the current values of the shown data */
|
||||
function updateData() {
|
||||
var newContextActionSet = new ContextActionSet();
|
||||
|
||||
getApp()
|
||||
.getUnitsManager()
|
||||
.getSelectedUnits()
|
||||
.forEach((unit: Unit) => {
|
||||
unit.appendContextActions(newContextActionSet);
|
||||
});
|
||||
|
||||
setContextActionsSet(newContextActionSet);
|
||||
return newContextActionSet;
|
||||
}
|
||||
|
||||
function onScroll(el) {
|
||||
const sl = el.scrollLeft;
|
||||
const sr = el.scrollWidth - el.scrollLeft - el.clientWidth;
|
||||
@ -75,15 +43,17 @@ export function UnitMouseControlBar(props: {}) {
|
||||
|
||||
let reorderedActions: ContextAction[] = [];
|
||||
CONTEXT_ACTION_COLORS.forEach((color) => {
|
||||
Object.values(contextActionsSet.getContextActions()).forEach((contextAction: ContextAction) => {
|
||||
if (color === null && contextAction.getOptions().buttonColor === undefined) reorderedActions.push(contextAction);
|
||||
else if (color === contextAction.getOptions().buttonColor) reorderedActions.push(contextAction);
|
||||
});
|
||||
if (appState.contextActionSet) {
|
||||
Object.values(appState.contextActionSet.getContextActions()).forEach((contextAction: ContextAction) => {
|
||||
if (color === null && contextAction.getOptions().buttonColor === undefined) reorderedActions.push(contextAction);
|
||||
else if (color === contextAction.getOptions().buttonColor) reorderedActions.push(contextAction);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{open && Object.keys(contextActionsSet.getContextActions()).length > 0 && (
|
||||
{open && appState.contextActionSet && Object.keys(appState.contextActionSet.getContextActions()).length > 0 && (
|
||||
<>
|
||||
<div
|
||||
className={`
|
||||
@ -106,7 +76,7 @@ export function UnitMouseControlBar(props: {}) {
|
||||
return (
|
||||
<OlStateButton
|
||||
key={contextAction.getId()}
|
||||
checked={contextAction === activeContextAction}
|
||||
checked={contextAction === appState.contextAction}
|
||||
icon={contextAction.getIcon()}
|
||||
tooltip={contextAction.getLabel()}
|
||||
className={
|
||||
@ -119,18 +89,9 @@ export function UnitMouseControlBar(props: {}) {
|
||||
}
|
||||
onClick={() => {
|
||||
if (contextAction.getOptions().executeImmediately) {
|
||||
setActiveContextAction(null);
|
||||
contextAction.executeCallback(null, null);
|
||||
} else {
|
||||
if (activeContextAction !== contextAction) {
|
||||
setActiveContextAction(contextAction);
|
||||
getApp().getMap().setContextAction(contextAction);
|
||||
getApp().getMap().setDefaultContextAction(contextActionsSet.getDefaultContextAction());
|
||||
} else {
|
||||
setActiveContextAction(null);
|
||||
getApp().getMap().setContextAction(null);
|
||||
getApp().getMap().setDefaultContextAction(null);
|
||||
}
|
||||
appState.contextAction !== contextAction ? getApp().getMap().setContextAction(contextAction) : getApp().getMap().setContextAction(null);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
@ -147,7 +108,7 @@ export function UnitMouseControlBar(props: {}) {
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{activeContextAction && (
|
||||
{appState.contextAction && (
|
||||
<div
|
||||
className={`
|
||||
absolute left-[50%] top-32 flex min-w-[300px]
|
||||
@ -169,7 +130,7 @@ export function UnitMouseControlBar(props: {}) {
|
||||
md:border-l-[1px] md:px-5
|
||||
`}
|
||||
>
|
||||
{activeContextAction.getDescription()}
|
||||
{appState.contextAction.getDescription()}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -10,7 +10,18 @@ import { MainMenu } from "./panels/mainmenu";
|
||||
import { SideBar } from "./panels/sidebar";
|
||||
import { OptionsMenu } from "./panels/optionsmenu";
|
||||
import { MapHiddenTypes, MapOptions } from "../types/types";
|
||||
import { BLUE_COMMANDER, GAME_MASTER, MAP_HIDDEN_TYPES_DEFAULTS, MAP_OPTIONS_DEFAULTS, NO_SUBSTATE, OlympusEvent, OlympusState, OlympusSubState, RED_COMMANDER, UnitControlSubState } from "../constants/constants";
|
||||
import {
|
||||
BLUE_COMMANDER,
|
||||
GAME_MASTER,
|
||||
MAP_HIDDEN_TYPES_DEFAULTS,
|
||||
MAP_OPTIONS_DEFAULTS,
|
||||
NO_SUBSTATE,
|
||||
OlympusEvent,
|
||||
OlympusState,
|
||||
OlympusSubState,
|
||||
RED_COMMANDER,
|
||||
UnitControlSubState,
|
||||
} from "../constants/constants";
|
||||
import { getApp, setupApp } from "../olympusapp";
|
||||
import { LoginModal } from "./modals/login";
|
||||
import { sha256 } from "js-sha256";
|
||||
@ -27,6 +38,26 @@ import { Unit } from "../unit/unit";
|
||||
import { ProtectionPrompt } from "./modals/protectionprompt";
|
||||
import { UnitExplosionMenu } from "./panels/unitexplosionmenu";
|
||||
import { JTACMenu } from "./panels/jtacmenu";
|
||||
import {
|
||||
AppStateChangedEvent,
|
||||
AudioManagerStateChangedEvent,
|
||||
AudioSinksChangedEvent,
|
||||
AudioSourcesChangedEvent,
|
||||
ConfigLoadedEvent,
|
||||
ContextActionChangedEvent,
|
||||
ContextActionSetChangedEvent,
|
||||
HiddenTypesChangedEvent,
|
||||
MapOptionsChangedEvent,
|
||||
MapSourceChangedEvent,
|
||||
SelectedUnitsChangedEvent,
|
||||
ServerStatusUpdatedEvent,
|
||||
UnitSelectedEvent,
|
||||
} from "../events";
|
||||
import { ServerStatus } from "../interfaces";
|
||||
import { AudioSource } from "../audio/audiosource";
|
||||
import { AudioSink } from "../audio/audiosink";
|
||||
import { ContextAction } from "../unit/contextaction";
|
||||
import { ContextActionSet } from "../unit/contextactionset";
|
||||
|
||||
export type OlympusUIState = {
|
||||
mainMenuVisible: boolean;
|
||||
@ -43,16 +74,22 @@ export type OlympusUIState = {
|
||||
export function UI() {
|
||||
const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED);
|
||||
const [appSubState, setAppSubState] = useState(NO_SUBSTATE as OlympusSubState);
|
||||
|
||||
const [mapHiddenTypes, setMapHiddenTypes] = useState(MAP_HIDDEN_TYPES_DEFAULTS);
|
||||
const [mapOptions, setMapOptions] = useState(MAP_OPTIONS_DEFAULTS);
|
||||
const [mapSources, setMapSources] = useState([] as string[]);
|
||||
const [activeMapSource, setActiveMapSource] = useState("");
|
||||
const [selectedUnits, setSelectedUnits] = useState([] as Unit[]);
|
||||
const [audioSources, setAudioSources] = useState([] as AudioSource[]);
|
||||
const [audioSinks, setAudioSinks] = useState([] as AudioSink[]);
|
||||
const [audioManagerState, setAudioManagerState] = useState(false);
|
||||
const [serverStatus, setServerStatus] = useState({} as ServerStatus);
|
||||
const [contextActionSet, setContextActionsSet] = useState(null as ContextActionSet | null);
|
||||
const [contextAction, setContextActions] = useState(null as ContextAction | null);
|
||||
|
||||
const [checkingPassword, setCheckingPassword] = useState(false);
|
||||
const [loginError, setLoginError] = useState(false);
|
||||
const [commandMode, setCommandMode] = useState(null as null | string);
|
||||
|
||||
|
||||
const [airbase, setAirbase] = useState(null as null | Airbase);
|
||||
|
||||
const [formationLeader, setFormationLeader] = useState(null as null | Unit);
|
||||
@ -64,31 +101,27 @@ export function UI() {
|
||||
const [unitExplosionUnits, setUnitExplosionUnits] = useState([] as Unit[]);
|
||||
|
||||
useEffect(() => {
|
||||
getApp()?.registerEventCallback(OlympusEvent.STATE_CHANGED, (state, subState) => {
|
||||
AppStateChangedEvent.on((state, subState) => {
|
||||
setAppState(state);
|
||||
setAppSubState(subState);
|
||||
})
|
||||
|
||||
document.addEventListener("hiddenTypesChanged", (ev) => {
|
||||
setMapHiddenTypes({ ...getApp().getMap().getHiddenTypes() });
|
||||
});
|
||||
|
||||
document.addEventListener("mapOptionsChanged", (ev) => {
|
||||
setMapOptions({ ...getApp().getMap().getOptions() });
|
||||
});
|
||||
|
||||
document.addEventListener("mapSourceChanged", (ev) => {
|
||||
var source = (ev as CustomEvent).detail;
|
||||
setActiveMapSource(source);
|
||||
});
|
||||
|
||||
document.addEventListener("configLoaded", (ev) => {
|
||||
ConfigLoadedEvent.on(() => {
|
||||
let config = getApp().getConfig();
|
||||
var sources = Object.keys(config.mapMirrors).concat(Object.keys(config.mapLayers));
|
||||
setMapSources(sources);
|
||||
setActiveMapSource(sources[0]);
|
||||
});
|
||||
|
||||
HiddenTypesChangedEvent.on((hiddenTypes) => setMapHiddenTypes({ ...hiddenTypes }));
|
||||
MapOptionsChangedEvent.on((mapOptions) => setMapOptions({ ...mapOptions }));
|
||||
MapSourceChangedEvent.on((source) => setActiveMapSource(source));
|
||||
SelectedUnitsChangedEvent.on((units) => setSelectedUnits(units));
|
||||
AudioSourcesChangedEvent.on((sources) => setAudioSources(sources));
|
||||
AudioSinksChangedEvent.on((sinks) => setAudioSinks(sinks));
|
||||
AudioManagerStateChangedEvent.on((state) => setAudioManagerState(state));
|
||||
ServerStatusUpdatedEvent.on((status) => setServerStatus(status));
|
||||
ContextActionSetChangedEvent.on((contextActionSet) => setContextActionsSet(contextActionSet));
|
||||
ContextActionChangedEvent.on((contextAction) => setContextActions(contextAction));
|
||||
|
||||
document.addEventListener("showProtectionPrompt", (ev: CustomEventInit) => {
|
||||
setProtectionPromptVisible(true);
|
||||
setProtectionCallback(() => {
|
||||
@ -143,74 +176,88 @@ export function UI() {
|
||||
mapHiddenTypes,
|
||||
mapSources,
|
||||
activeMapSource,
|
||||
selectedUnits,
|
||||
audioSources,
|
||||
audioSinks,
|
||||
audioManagerState,
|
||||
serverStatus,
|
||||
contextActionSet,
|
||||
contextAction,
|
||||
}}
|
||||
>
|
||||
|
||||
<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
|
||||
onLogin={(password) => {
|
||||
checkPassword(password);
|
||||
}}
|
||||
onContinue={(username) => {
|
||||
connect(username);
|
||||
}}
|
||||
onBack={() => {
|
||||
setCommandMode(null);
|
||||
}}
|
||||
checkingPassword={checkingPassword}
|
||||
loginError={loginError}
|
||||
commandMode={commandMode}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{protectionPromptVisible && (
|
||||
<>
|
||||
<div
|
||||
className={`
|
||||
fixed left-0 top-0 z-30 h-full w-full bg-[#111111]/95
|
||||
`}
|
||||
></div>
|
||||
<ProtectionPrompt
|
||||
onContinue={(units) => {
|
||||
protectionCallback(units);
|
||||
setProtectionPromptVisible(false);
|
||||
}}
|
||||
onBack={() => {
|
||||
setProtectionPromptVisible(false);
|
||||
}}
|
||||
units={protectionUnits}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<div id="map-container" className="z-0 h-full w-screen" />
|
||||
<MainMenu open={appState === OlympusState.MAIN_MENU} onClose={() => getApp().setState(OlympusState.IDLE)} />
|
||||
<SpawnMenu open={appState === OlympusState.SPAWN} onClose={() => getApp().setState(OlympusState.IDLE)} />
|
||||
<OptionsMenu open={appState === OlympusState.OPTIONS} onClose={() =>getApp().setState(OlympusState.IDLE)} options={mapOptions} />
|
||||
<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
|
||||
onLogin={(password) => {
|
||||
checkPassword(password);
|
||||
}}
|
||||
onContinue={(username) => {
|
||||
connect(username);
|
||||
}}
|
||||
onBack={() => {
|
||||
setCommandMode(null);
|
||||
}}
|
||||
checkingPassword={checkingPassword}
|
||||
loginError={loginError}
|
||||
commandMode={commandMode}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{protectionPromptVisible && (
|
||||
<>
|
||||
<div
|
||||
className={`
|
||||
fixed left-0 top-0 z-30 h-full w-full bg-[#111111]/95
|
||||
`}
|
||||
></div>
|
||||
<ProtectionPrompt
|
||||
onContinue={(units) => {
|
||||
protectionCallback(units);
|
||||
setProtectionPromptVisible(false);
|
||||
}}
|
||||
onBack={() => {
|
||||
setProtectionPromptVisible(false);
|
||||
}}
|
||||
units={protectionUnits}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<div id="map-container" className="z-0 h-full w-screen" />
|
||||
<MainMenu open={appState === OlympusState.MAIN_MENU} onClose={() => getApp().setState(OlympusState.IDLE)} />
|
||||
<SpawnMenu open={appState === OlympusState.SPAWN} onClose={() => getApp().setState(OlympusState.IDLE)} />
|
||||
<OptionsMenu open={appState === OlympusState.OPTIONS} onClose={() => getApp().setState(OlympusState.IDLE)} options={mapOptions} />
|
||||
|
||||
<UnitControlMenu open={appState === OlympusState.UNIT_CONTROL && appSubState !== UnitControlSubState.FORMATION} onClose={() => getApp().setState(OlympusState.IDLE)} />
|
||||
<FormationMenu open={appState === OlympusState.UNIT_CONTROL && appSubState === UnitControlSubState.FORMATION} leader={formationLeader} wingmen={formationWingmen} onClose={() => getApp().setState(OlympusState.IDLE)} />
|
||||
|
||||
<DrawingMenu open={appState === OlympusState.DRAW} onClose={() => getApp().setState(OlympusState.IDLE)} />
|
||||
<AirbaseMenu open={appState === OlympusState.AIRBASE} onClose={() =>getApp().setState(OlympusState.IDLE)} airbase={airbase} />
|
||||
<AudioMenu open={appState === OlympusState.AUDIO} onClose={() => getApp().setState(OlympusState.IDLE)} />
|
||||
|
||||
{/* TODO} <UnitExplosionMenu open={appState === OlympusState.MAIN_MENU} units={unitExplosionUnits} onClose={() => getApp().setState(OlympusState.IDLE)} /> {*/}
|
||||
<JTACMenu open={appState === OlympusState.JTAC} onClose={() => getApp().setState(OlympusState.IDLE)} />
|
||||
<UnitControlMenu
|
||||
open={appState === OlympusState.UNIT_CONTROL && appSubState !== UnitControlSubState.FORMATION}
|
||||
onClose={() => getApp().setState(OlympusState.IDLE)}
|
||||
/>
|
||||
<FormationMenu
|
||||
open={appState === OlympusState.UNIT_CONTROL && appSubState === UnitControlSubState.FORMATION}
|
||||
leader={formationLeader}
|
||||
wingmen={formationWingmen}
|
||||
onClose={() => getApp().setState(OlympusState.IDLE)}
|
||||
/>
|
||||
|
||||
<MiniMapPanel />
|
||||
<ControlsPanel />
|
||||
<UnitMouseControlBar />
|
||||
<MapContextMenu />
|
||||
<SideBar />
|
||||
</div>
|
||||
<DrawingMenu open={appState === OlympusState.DRAW} onClose={() => getApp().setState(OlympusState.IDLE)} />
|
||||
<AirbaseMenu open={appState === OlympusState.AIRBASE} onClose={() => getApp().setState(OlympusState.IDLE)} airbase={airbase} />
|
||||
<AudioMenu open={appState === OlympusState.AUDIO} onClose={() => getApp().setState(OlympusState.IDLE)} />
|
||||
|
||||
{/* TODO} <UnitExplosionMenu open={appState === OlympusState.MAIN_MENU} units={unitExplosionUnits} onClose={() => getApp().setState(OlympusState.IDLE)} /> {*/}
|
||||
<JTACMenu open={appState === OlympusState.JTAC} onClose={() => getApp().setState(OlympusState.IDLE)} />
|
||||
|
||||
<MiniMapPanel />
|
||||
<ControlsPanel />
|
||||
<UnitMouseControlBar />
|
||||
<MapContextMenu />
|
||||
<SideBar />
|
||||
</div>
|
||||
</StateProvider>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { UnitDeadEvent } from "../events";
|
||||
import { Unit } from "./unit";
|
||||
|
||||
export class Group {
|
||||
@ -7,8 +8,8 @@ export class Group {
|
||||
constructor(name: string) {
|
||||
this.#name = name;
|
||||
|
||||
document.addEventListener("unitDeath", (e: any) => {
|
||||
if (this.#members.includes(e.detail)) this.getLeader()?.onGroupChanged(e.detail);
|
||||
UnitDeadEvent.on((unit) => {
|
||||
if (this.#members.includes(unit)) this.getLeader()?.onGroupChanged(unit);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -71,6 +71,7 @@ import {
|
||||
faXmarksLines,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { Carrier } from "../mission/carrier";
|
||||
import { ContactsUpdatedEvent, HiddenTypesChangedEvent, MapOptionsChangedEvent, UnitDeadEvent, UnitDeselectedEvent, UnitSelectedEvent } from "../events";
|
||||
|
||||
var pathIcon = new Icon({
|
||||
iconUrl: "/vite/images/markers/marker-icon.png",
|
||||
@ -356,29 +357,20 @@ export abstract class Unit extends CustomMarker {
|
||||
this.on("mouseup", (e) => this.#onMouseUp(e));
|
||||
this.on("dblclick", (e) => this.#onDoubleClick(e));
|
||||
this.on("mouseover", () => {
|
||||
if (this.belongsToCommandedCoalition()) {
|
||||
if (this.belongsToCommandedCoalition())
|
||||
this.setHighlighted(true);
|
||||
document.dispatchEvent(new CustomEvent("unitMouseover", { detail: this }));
|
||||
}
|
||||
});
|
||||
this.on("mouseout", () => {
|
||||
this.setHighlighted(false);
|
||||
document.dispatchEvent(new CustomEvent("unitMouseout", { detail: this }));
|
||||
});
|
||||
getApp()
|
||||
.getMap()
|
||||
.on("zoomend", (e: any) => {
|
||||
this.#onZoom(e);
|
||||
});
|
||||
this.on("mouseout", () => this.setHighlighted(false));
|
||||
getApp().getMap().on("zoomend", (e: any) => this.#onZoom(e));
|
||||
|
||||
/* Deselect units if they are hidden */
|
||||
document.addEventListener("hiddenTypesChanged", (ev: CustomEventInit) => {
|
||||
HiddenTypesChangedEvent.on((hiddenTypes) => {
|
||||
this.#updateMarker();
|
||||
this.setSelected(this.getSelected() && !this.getHidden());
|
||||
});
|
||||
|
||||
/* Update the marker when the options change */
|
||||
document.addEventListener("mapOptionChanged", (ev: CustomEventInit) => {
|
||||
MapOptionsChangedEvent.on(() => {
|
||||
this.#updateMarker();
|
||||
|
||||
/* Circles don't like to be updated when the map is zooming */
|
||||
@ -559,7 +551,7 @@ export abstract class Unit extends CustomMarker {
|
||||
break;
|
||||
case DataIndexes.contacts:
|
||||
this.#contacts = dataExtractor.extractContacts();
|
||||
document.dispatchEvent(new CustomEvent("contactsUpdated", { detail: this }));
|
||||
ContactsUpdatedEvent.dispatch();
|
||||
break;
|
||||
case DataIndexes.activePath:
|
||||
this.#activePath = dataExtractor.extractActivePath();
|
||||
@ -599,9 +591,6 @@ export abstract class Unit extends CustomMarker {
|
||||
this.setSelected(true);
|
||||
}
|
||||
}
|
||||
|
||||
/* If the unit is selected or if the view is centered on this unit, sent the update signal so that other elements like the UnitControlPanel can be updated. */
|
||||
if (this.getSelected() || getApp().getMap().getCenteredOnUnit() === this) document.dispatchEvent(new CustomEvent("unitUpdated", { detail: this }));
|
||||
}
|
||||
|
||||
/** Get unit data collated into an object
|
||||
@ -672,7 +661,7 @@ export abstract class Unit extends CustomMarker {
|
||||
* @param newAlive (boolean) true = alive, false = dead
|
||||
*/
|
||||
setAlive(newAlive: boolean) {
|
||||
if (newAlive != this.#alive) document.dispatchEvent(new CustomEvent("unitDeath", { detail: this }));
|
||||
if (newAlive != this.#alive) UnitDeadEvent.dispatch(this)
|
||||
this.#alive = newAlive;
|
||||
}
|
||||
|
||||
@ -709,11 +698,7 @@ export abstract class Unit extends CustomMarker {
|
||||
this.getElement()?.querySelector(`.unit`)?.toggleAttribute("data-is-selected", selected);
|
||||
|
||||
/* Trigger events after all (de-)selecting has been done */
|
||||
if (selected) {
|
||||
document.dispatchEvent(new CustomEvent("unitSelection", { detail: this }));
|
||||
} else {
|
||||
document.dispatchEvent(new CustomEvent("unitDeselection", { detail: this }));
|
||||
}
|
||||
selected? UnitSelectedEvent.dispatch(this): UnitDeselectedEvent.dispatch(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -35,6 +35,14 @@ import { UnitDataFileExport } from "./importexport/unitdatafileexport";
|
||||
import { UnitDataFileImport } from "./importexport/unitdatafileimport";
|
||||
import { CoalitionCircle } from "../map/coalitionarea/coalitioncircle";
|
||||
import { ContextActionSet } from "./contextactionset";
|
||||
import {
|
||||
CommandModeOptionsChangedEvent,
|
||||
ContactsUpdatedEvent,
|
||||
SelectedUnitsChangedEvent,
|
||||
SelectionClearedEvent,
|
||||
UnitDeselectedEvent,
|
||||
UnitSelectedEvent,
|
||||
} from "../events";
|
||||
|
||||
/** The UnitsManager handles the creation, update, and control of units. Data is strictly updated by the server ONLY. This means that any interaction from the user will always and only
|
||||
* result in a command to the server, executed by means of a REST PUT request. Any subsequent change in data will be reflected only when the new data is sent back by the server. This strategy allows
|
||||
@ -55,17 +63,18 @@ export class UnitsManager {
|
||||
this.#copiedUnits = [];
|
||||
this.#units = {};
|
||||
|
||||
document.addEventListener("commandModeOptionsChanged", () => {
|
||||
CommandModeOptionsChangedEvent.on(() => {
|
||||
Object.values(this.#units).forEach((unit: Unit) => unit.updateVisibility());
|
||||
});
|
||||
document.addEventListener("contactsUpdated", (e) => {
|
||||
ContactsUpdatedEvent.on(() => {
|
||||
this.#requestDetectionUpdate = true;
|
||||
});
|
||||
UnitSelectedEvent.on((unit) => this.#onUnitDeselection(unit));
|
||||
UnitDeselectedEvent.on((unit) => this.#onUnitSelection(unit));
|
||||
|
||||
document.addEventListener("copy", () => this.copy());
|
||||
document.addEventListener("keyup", (event) => this.#onKeyUp(event));
|
||||
document.addEventListener("paste", () => this.paste());
|
||||
document.addEventListener("unitDeselection", (e) => this.#onUnitDeselection((e as CustomEvent).detail));
|
||||
document.addEventListener("unitSelection", (e) => this.#onUnitSelection((e as CustomEvent).detail));
|
||||
|
||||
//this.#slowDeleteDialog = new Dialog("slow-delete-dialog");
|
||||
}
|
||||
@ -1416,18 +1425,6 @@ export class UnitsManager {
|
||||
if (spawnPoints <= getApp().getMissionManager().getAvailableSpawnPoints()) {
|
||||
getApp().getMissionManager().setSpentSpawnPoints(spawnPoints);
|
||||
spawnFunction();
|
||||
document.dispatchEvent(
|
||||
new CustomEvent("unitSpawned", {
|
||||
detail: {
|
||||
airbase: airbase,
|
||||
category: category,
|
||||
coalition: coalition,
|
||||
country: country,
|
||||
immediate: immediate,
|
||||
unitSpawnTable: units,
|
||||
},
|
||||
})
|
||||
);
|
||||
return true;
|
||||
} else {
|
||||
//(getApp().getPopupsManager().get("infoPopup") as Popup).setText("Not enough spawn points available!");
|
||||
@ -1453,16 +1450,13 @@ export class UnitsManager {
|
||||
/* Disable the firing of the selection event for a certain amount of time. This avoids firing many events if many units are selected */
|
||||
if (!this.#selectionEventDisabled) {
|
||||
window.setTimeout(() => {
|
||||
document.dispatchEvent(
|
||||
new CustomEvent("unitsSelection", {
|
||||
detail: this.getSelectedUnits(),
|
||||
})
|
||||
);
|
||||
SelectedUnitsChangedEvent.dispatch(this.getSelectedUnits());
|
||||
|
||||
let newContextActionSet = new ContextActionSet();
|
||||
this.getSelectedUnits().forEach((unit) => unit.appendContextActions(newContextActionSet));
|
||||
|
||||
getApp().getMap().setContextAction(null);
|
||||
getApp().getMap().setDefaultContextAction(newContextActionSet.getDefaultContextAction());
|
||||
getApp().getMap().setContextActionSet(newContextActionSet);
|
||||
getApp().setState(OlympusState.UNIT_CONTROL);
|
||||
|
||||
this.#selectionEventDisabled = false;
|
||||
@ -1472,24 +1466,19 @@ export class UnitsManager {
|
||||
}
|
||||
} else {
|
||||
getApp().setState(OlympusState.IDLE);
|
||||
document.dispatchEvent(new CustomEvent("clearSelection"));
|
||||
SelectionClearedEvent.dispatch();
|
||||
}
|
||||
}
|
||||
|
||||
#onUnitDeselection(unit: Unit) {
|
||||
if (this.getSelectedUnits().length == 0) {
|
||||
if (getApp().getState() === OlympusState.UNIT_CONTROL)
|
||||
getApp().setState(OlympusState.IDLE);
|
||||
document.dispatchEvent(new CustomEvent("clearSelection"));
|
||||
if (getApp().getState() === OlympusState.UNIT_CONTROL) getApp().setState(OlympusState.IDLE);
|
||||
SelectionClearedEvent.dispatch();
|
||||
} else {
|
||||
/* Disable the firing of the selection event for a certain amount of time. This avoids firing many events if many units are selected */
|
||||
if (!this.#deselectionEventDisabled) {
|
||||
window.setTimeout(() => {
|
||||
document.dispatchEvent(
|
||||
new CustomEvent("unitsDeselection", {
|
||||
detail: this.getSelectedUnits(),
|
||||
})
|
||||
);
|
||||
SelectedUnitsChangedEvent.dispatch(this.getSelectedUnits());
|
||||
this.#deselectionEventDisabled = false;
|
||||
}, 100);
|
||||
this.#deselectionEventDisabled = true;
|
||||
|
||||
@ -6,6 +6,7 @@ import { SVGInjector } from "@tanem/svg-injector";
|
||||
import { DLINK, DataIndexes, GAME_MASTER, IRST, OPTIC, RADAR, VISUAL } from "../constants/constants";
|
||||
import { DataExtractor } from "../server/dataextractor";
|
||||
import { ObjectIconOptions } from "../interfaces";
|
||||
import { MapOptionsChangedEvent } from "../events";
|
||||
|
||||
export class Weapon extends CustomMarker {
|
||||
ID: number;
|
||||
@ -50,9 +51,7 @@ export class Weapon extends CustomMarker {
|
||||
this.ID = ID;
|
||||
|
||||
/* Update the marker when the options change */
|
||||
document.addEventListener("mapOptionsChanged", (ev: CustomEventInit) => {
|
||||
this.#updateMarker();
|
||||
});
|
||||
MapOptionsChangedEvent.on(() => this.#updateMarker());
|
||||
}
|
||||
|
||||
getCategory() {
|
||||
|
||||
@ -3,6 +3,7 @@ import { Weapon } from "./weapon";
|
||||
import { DataIndexes } from "../constants/constants";
|
||||
import { DataExtractor } from "../server/dataextractor";
|
||||
import { Contact } from "../interfaces";
|
||||
import { CommandModeOptionsChangedEvent } from "../events";
|
||||
|
||||
/** The WeaponsManager handles the creation and update of weapons. Data is strictly updated by the server ONLY. */
|
||||
export class WeaponsManager {
|
||||
@ -11,7 +12,7 @@ export class WeaponsManager {
|
||||
constructor() {
|
||||
this.#weapons = {};
|
||||
|
||||
document.addEventListener("commandModeOptionsChanged", () => {
|
||||
CommandModeOptionsChangedEvent.on(() => {
|
||||
Object.values(this.#weapons).forEach((weapon: Weapon) => weapon.updateVisibility());
|
||||
});
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user