mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
feat: Implemented import/export page
This commit is contained in:
parent
c9c34f013f
commit
0376d020e7
@ -289,6 +289,7 @@ export enum OlympusState {
|
||||
AUDIO = "Audio",
|
||||
AIRBASE = "Airbase",
|
||||
GAME_MASTER = "Game master",
|
||||
IMPORT_EXPORT = "Import/export"
|
||||
}
|
||||
|
||||
export const NO_SUBSTATE = "No substate";
|
||||
@ -334,6 +335,13 @@ export enum OptionsSubstate {
|
||||
KEYBIND = "Keybind",
|
||||
}
|
||||
|
||||
export enum ImportExportSubstate {
|
||||
NO_SUBSTATE = "No substate",
|
||||
IMPORT = "IMPORT",
|
||||
EXPORT = "EXPORT"
|
||||
}
|
||||
|
||||
|
||||
export type OlympusSubState = DrawSubState | JTACSubState | SpawnSubState | OptionsSubstate | string;
|
||||
|
||||
export const IADSTypes = ["AAA", "SAM Site", "Radar (EWR)"];
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { AudioSink } from "./audio/audiosink";
|
||||
import { AudioSource } from "./audio/audiosource";
|
||||
import { OlympusState, OlympusSubState } from "./constants/constants";
|
||||
import { CommandModeOptions, OlympusConfig, ServerStatus, SessionData, SpawnRequestTable, UnitData } from "./interfaces";
|
||||
import { CommandModeOptions, MissionData, OlympusConfig, ServerStatus, SessionData, SpawnRequestTable, UnitData } from "./interfaces";
|
||||
import { CoalitionCircle } from "./map/coalitionarea/coalitioncircle";
|
||||
import { CoalitionPolygon } from "./map/coalitionarea/coalitionpolygon";
|
||||
import { Airbase } from "./mission/airbase";
|
||||
@ -12,6 +12,7 @@ import { ContextAction } from "./unit/contextaction";
|
||||
import { ContextActionSet } from "./unit/contextactionset";
|
||||
import { Unit } from "./unit/unit";
|
||||
import { LatLng } from "leaflet";
|
||||
import { Weapon } from "./weapon/weapon";
|
||||
|
||||
export class BaseOlympusEvent {
|
||||
static on(callback: () => void, singleShot = false) {
|
||||
@ -348,8 +349,33 @@ export class UnitSelectedEvent extends BaseUnitEvent {}
|
||||
export class UnitDeselectedEvent extends BaseUnitEvent {}
|
||||
export class UnitDeadEvent extends BaseUnitEvent {}
|
||||
export class SelectionClearedEvent extends BaseOlympusEvent {}
|
||||
export class UnitsRefreshed extends BaseOlympusEvent {}
|
||||
export class WeaponsRefreshed extends BaseOlympusEvent {}
|
||||
|
||||
export class UnitsRefreshedEvent {
|
||||
static on(callback: (units: { [ID: number]: Unit }) => void, singleShot = false) {
|
||||
document.addEventListener(this.name, (ev: CustomEventInit) => {
|
||||
callback(ev.detail);
|
||||
}, {once: singleShot});
|
||||
}
|
||||
|
||||
static dispatch(units: { [ID: number]: Unit }) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail: units }));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
|
||||
export class WeaponsRefreshedEvent {
|
||||
static on(callback: (weapons: { [ID: number]: Weapon }) => void, singleShot = false) {
|
||||
document.addEventListener(this.name, (ev: CustomEventInit) => {
|
||||
callback(ev.detail);
|
||||
}, {once: singleShot});
|
||||
}
|
||||
|
||||
static dispatch(weapons: { [ID: number]: Weapon }) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail: weapons }));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class SelectedUnitsChangedEvent {
|
||||
static on(callback: (selectedUnits: Unit[]) => void, singleShot = false) {
|
||||
@ -361,7 +387,6 @@ export class SelectedUnitsChangedEvent {
|
||||
static dispatch(selectedUnits: Unit[]) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail: selectedUnits }));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
console.log(selectedUnits);
|
||||
}
|
||||
}
|
||||
|
||||
@ -578,7 +603,7 @@ export class AudioManagerOutputChangedEvent {
|
||||
}
|
||||
|
||||
/************** Mission data events ***************/
|
||||
export class BullseyesDataChanged {
|
||||
export class BullseyesDataChangedEvent {
|
||||
static on(callback: (bullseyes: { [name: string]: Bullseye }) => void, singleShot = false) {
|
||||
document.addEventListener(this.name, (ev: CustomEventInit) => {
|
||||
callback(ev.detail.bullseyes);
|
||||
@ -590,3 +615,16 @@ export class BullseyesDataChanged {
|
||||
// Logging disabled since periodic
|
||||
}
|
||||
}
|
||||
|
||||
export class MissionDataChangedEvent {
|
||||
static on(callback: (missionData: MissionData) => void, singleShot = false) {
|
||||
document.addEventListener(this.name, (ev: CustomEventInit) => {
|
||||
callback(ev.detail.missionData);
|
||||
}, {once: singleShot});
|
||||
}
|
||||
|
||||
static dispatch(missionData: MissionData) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail: { missionData } }));
|
||||
// Logging disabled since periodic
|
||||
}
|
||||
}
|
||||
|
||||
@ -200,6 +200,7 @@ export interface Offset {
|
||||
|
||||
export interface UnitData {
|
||||
category: string;
|
||||
markerCategory: string;
|
||||
ID: number;
|
||||
alive: boolean;
|
||||
human: boolean;
|
||||
|
||||
@ -6,7 +6,7 @@ import { BLUE_COMMANDER, GAME_MASTER, NONE, RED_COMMANDER } from "../constants/c
|
||||
import { AirbasesData, BullseyesData, CommandModeOptions, DateAndTime, MissionData } from "../interfaces";
|
||||
import { Coalition } from "../types/types";
|
||||
import { Carrier } from "./carrier";
|
||||
import { AirbaseSelectedEvent, AppStateChangedEvent, BullseyesDataChanged, CommandModeOptionsChangedEvent, InfoPopupEvent } from "../events";
|
||||
import { AirbaseSelectedEvent, AppStateChangedEvent, BullseyesDataChangedEvent, CommandModeOptionsChangedEvent, InfoPopupEvent, MissionDataChangedEvent } from "../events";
|
||||
|
||||
/** The MissionManager */
|
||||
export class MissionManager {
|
||||
@ -61,7 +61,7 @@ export class MissionManager {
|
||||
this.#bullseyes[idx].setCoalition(bullseye.coalition);
|
||||
}
|
||||
|
||||
BullseyesDataChanged.dispatch(this.#bullseyes)
|
||||
BullseyesDataChangedEvent.dispatch(this.#bullseyes)
|
||||
}
|
||||
}
|
||||
|
||||
@ -96,6 +96,8 @@ export class MissionManager {
|
||||
*/
|
||||
updateMission(data: MissionData) {
|
||||
if (data.mission) {
|
||||
MissionDataChangedEvent.dispatch(data);
|
||||
|
||||
/* Set the mission theatre */
|
||||
if (data.mission.theatre != this.#theatre) {
|
||||
this.#theatre = data.mission.theatre;
|
||||
|
||||
@ -488,7 +488,7 @@ export class ServerManager {
|
||||
this.PUT(data, callback);
|
||||
}
|
||||
|
||||
setAdvacedOptions(
|
||||
setAdvancedOptions(
|
||||
ID: number,
|
||||
isActiveTanker: boolean,
|
||||
isActiveAWACS: boolean,
|
||||
|
||||
369
frontend/react/src/ui/modals/importexportmodal.tsx
Normal file
369
frontend/react/src/ui/modals/importexportmodal.tsx
Normal file
@ -0,0 +1,369 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Modal } from "./components/modal";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faArrowRight } from "@fortawesome/free-solid-svg-icons";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { ImportExportSubstate, NO_SUBSTATE, OlympusState } from "../../constants/constants";
|
||||
import { AppStateChangedEvent, MissionDataChangedEvent, UnitsRefreshedEvent } from "../../events";
|
||||
import {
|
||||
olButtonsVisibilityDcs,
|
||||
olButtonsVisibilityGroundunit,
|
||||
olButtonsVisibilityGroundunitSam,
|
||||
olButtonsVisibilityNavyunit,
|
||||
olButtonsVisibilityOlympus,
|
||||
} from "../components/olicons";
|
||||
import { OlToggle } from "../components/oltoggle";
|
||||
import { deepCopyTable } from "../../other/utils";
|
||||
import { OlCheckbox } from "../components/olcheckbox";
|
||||
import { Unit } from "../../unit/unit";
|
||||
import { MissionData, UnitData } from "../../interfaces";
|
||||
|
||||
export function ImportExportModal(props: { open: boolean }) {
|
||||
const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED);
|
||||
const [appSubState, setAppSubState] = useState(NO_SUBSTATE);
|
||||
const [units, setUnits] = useState({} as { [ID: number]: Unit });
|
||||
const [missionData, setMissionData] = useState({} as MissionData);
|
||||
const [importData, setImportData] = useState({} as { [key: string]: UnitData[] });
|
||||
|
||||
function resetFilter() {
|
||||
return {
|
||||
control: {
|
||||
dcs: true,
|
||||
olympus: true,
|
||||
},
|
||||
blue: {
|
||||
"groundunit-sam": true,
|
||||
groundunit: true,
|
||||
navyunit: true,
|
||||
},
|
||||
neutral: {
|
||||
"groundunit-sam": true,
|
||||
groundunit: true,
|
||||
navyunit: true,
|
||||
},
|
||||
red: {
|
||||
"groundunit-sam": true,
|
||||
groundunit: true,
|
||||
navyunit: true,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const [selectionFilter, setSelectionFilter] = useState(resetFilter);
|
||||
|
||||
useEffect(() => {
|
||||
AppStateChangedEvent.on((appState, appSubState) => {
|
||||
setAppState(appState);
|
||||
setAppSubState(appSubState);
|
||||
});
|
||||
UnitsRefreshedEvent.on((units) => setUnits(units));
|
||||
MissionDataChangedEvent.on((missionData) => setMissionData(missionData));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setSelectionFilter(resetFilter);
|
||||
|
||||
if (appState === OlympusState.IMPORT_EXPORT && appSubState === ImportExportSubstate.IMPORT) {
|
||||
setImportData({});
|
||||
var input = document.createElement("input");
|
||||
input.type = "file";
|
||||
|
||||
input.onchange = async (e) => {
|
||||
// @ts-ignore TODO
|
||||
var file = e.target?.files[0];
|
||||
var reader = new FileReader();
|
||||
reader.readAsText(file, "UTF-8");
|
||||
reader.onload = (readerEvent) => {
|
||||
// @ts-ignore TODO
|
||||
var content = readerEvent.target.result;
|
||||
if (content) {
|
||||
setImportData(JSON.parse(content as string));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
input.click();
|
||||
}
|
||||
}, [appState, appSubState]);
|
||||
|
||||
const selectableUnits = Object.values(units).filter((unit) => {
|
||||
return (
|
||||
unit.getAlive() &&
|
||||
!unit.getHuman() &&
|
||||
((unit.isControlledByDCS() && selectionFilter.control.dcs) || (unit.isControlledByOlympus() && selectionFilter.control.olympus))
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={props.open}
|
||||
className={`
|
||||
inline-flex h-fit w-[600px] overflow-y-auto scroll-smooth bg-white p-10
|
||||
dark:bg-olympus-800
|
||||
max-md:h-full max-md:max-h-full max-md:w-full max-md:rounded-none
|
||||
max-md:border-none
|
||||
`}
|
||||
>
|
||||
<div className="flex h-full w-full flex-col gap-4">
|
||||
<div className={`flex flex-col items-start gap-2`}>
|
||||
<span
|
||||
className={`
|
||||
text-gray-800 text-md
|
||||
dark:text-white
|
||||
`}
|
||||
>
|
||||
{appSubState === ImportExportSubstate.EXPORT ? "Export to file" : "Import from file"}
|
||||
</span>
|
||||
|
||||
<span className="text-gray-400">
|
||||
{appSubState === ImportExportSubstate.EXPORT ? <>Select what units you want to export to file using the toggles below</> : <></>}
|
||||
</span>
|
||||
|
||||
<div className="flex w-full flex-col gap-2">
|
||||
<div
|
||||
className={`
|
||||
text-bold border-b-2 border-b-white/10 pb-2 text-gray-400
|
||||
`}
|
||||
>
|
||||
Control mode
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col justify-start gap-2">
|
||||
{Object.entries({
|
||||
olympus: ["Olympus controlled", olButtonsVisibilityOlympus],
|
||||
dcs: ["From DCS mission", olButtonsVisibilityDcs],
|
||||
}).map((entry, idx) => {
|
||||
return (
|
||||
<div className="flex justify-between" key={idx}>
|
||||
<span className="font-light text-white">{entry[1][0] as string}</span>
|
||||
<OlToggle
|
||||
key={entry[0]}
|
||||
onClick={() => {
|
||||
selectionFilter["control"][entry[0]] = !selectionFilter["control"][entry[0]];
|
||||
setSelectionFilter(deepCopyTable(selectionFilter));
|
||||
}}
|
||||
toggled={selectionFilter["control"][entry[0]]}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={`
|
||||
text-bold border-b-2 border-b-white/10 pb-2 text-gray-400
|
||||
`}
|
||||
>
|
||||
Types and coalitions
|
||||
</div>
|
||||
|
||||
<table className="mr-16">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td className="pb-4 text-center font-bold text-blue-500">BLUE</td>
|
||||
<td className="pb-4 text-center font-bold text-gray-500">NEUTRAL</td>
|
||||
<td className="pb-4 text-center font-bold text-red-500">RED</td>
|
||||
</tr>
|
||||
{Object.entries({
|
||||
"groundunit-sam": olButtonsVisibilityGroundunitSam,
|
||||
groundunit: olButtonsVisibilityGroundunit,
|
||||
navyunit: olButtonsVisibilityNavyunit,
|
||||
}).map((entry, idx) => {
|
||||
return (
|
||||
<tr key={idx}>
|
||||
<td className="w-16 text-lg text-gray-200">
|
||||
<FontAwesomeIcon icon={entry[1]} />
|
||||
</td>
|
||||
{["blue", "neutral", "red"].map((coalition) => {
|
||||
return (
|
||||
<td className="w-32 text-center" key={coalition}>
|
||||
<OlCheckbox
|
||||
checked={
|
||||
(appSubState === ImportExportSubstate.EXPORT &&
|
||||
selectionFilter[coalition][entry[0]] &&
|
||||
selectableUnits.find((unit) => unit.getMarkerCategory() === entry[0] && unit.getCoalition() === coalition) !== undefined) ||
|
||||
(appSubState === ImportExportSubstate.IMPORT &&
|
||||
selectionFilter[coalition][entry[0]] &&
|
||||
Object.values(importData).find((group) =>
|
||||
group.find((unit) => unit.markerCategory === entry[0] && unit.coalition === coalition)
|
||||
) !== undefined)
|
||||
}
|
||||
disabled={
|
||||
(appSubState === ImportExportSubstate.EXPORT &&
|
||||
selectableUnits.find((unit) => unit.getMarkerCategory() === entry[0] && unit.getCoalition() === coalition) === undefined) ||
|
||||
(appSubState === ImportExportSubstate.IMPORT &&
|
||||
Object.values(importData).find((group) =>
|
||||
group.find((unit) => unit.markerCategory === entry[0] && unit.coalition === coalition)
|
||||
) === undefined)
|
||||
}
|
||||
onChange={() => {
|
||||
selectionFilter[coalition][entry[0]] = !selectionFilter[coalition][entry[0]];
|
||||
setSelectionFilter(deepCopyTable(selectionFilter));
|
||||
}}
|
||||
/>
|
||||
<span className="absolute ml-2 text-white">
|
||||
{appSubState === ImportExportSubstate.EXPORT &&
|
||||
selectableUnits.filter((unit) => unit.getMarkerCategory() === entry[0] && unit.getCoalition() === coalition).length}
|
||||
{appSubState === ImportExportSubstate.IMPORT &&
|
||||
Object.values(importData)
|
||||
.flatMap((unit) => unit)
|
||||
.filter((unit) => unit.markerCategory === entry[0] && unit.coalition === coalition).length}{" "}
|
||||
units{" "}
|
||||
</span>
|
||||
</td>
|
||||
);
|
||||
})}
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
{
|
||||
<tr>
|
||||
<td className="text-gray-200"></td>
|
||||
<td className="text-center">
|
||||
<OlCheckbox
|
||||
checked={Object.values(selectionFilter["blue"]).some((value) => value)}
|
||||
onChange={() => {
|
||||
const newValue = !Object.values(selectionFilter["blue"]).some((value) => value);
|
||||
Object.keys(selectionFilter["blue"]).forEach((key) => {
|
||||
selectionFilter["blue"][key] = newValue;
|
||||
});
|
||||
setSelectionFilter(deepCopyTable(selectionFilter));
|
||||
}}
|
||||
/>
|
||||
</td>
|
||||
<td className="text-center">
|
||||
<OlCheckbox
|
||||
checked={Object.values(selectionFilter["neutral"]).some((value) => value)}
|
||||
onChange={() => {
|
||||
const newValue = !Object.values(selectionFilter["neutral"]).some((value) => value);
|
||||
Object.keys(selectionFilter["neutral"]).forEach((key) => {
|
||||
selectionFilter["neutral"][key] = newValue;
|
||||
});
|
||||
setSelectionFilter(deepCopyTable(selectionFilter));
|
||||
}}
|
||||
/>
|
||||
</td>
|
||||
<td className="text-center">
|
||||
<OlCheckbox
|
||||
checked={Object.values(selectionFilter["red"]).some((value) => value)}
|
||||
onChange={() => {
|
||||
const newValue = !Object.values(selectionFilter["red"]).some((value) => value);
|
||||
Object.keys(selectionFilter["red"]).forEach((key) => {
|
||||
selectionFilter["red"][key] = newValue;
|
||||
});
|
||||
setSelectionFilter(deepCopyTable(selectionFilter));
|
||||
}}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
if (appSubState === ImportExportSubstate.EXPORT) {
|
||||
var unitsToExport: { [key: string]: any } = {};
|
||||
selectableUnits
|
||||
.filter((unit) => selectionFilter[unit.getCoalition()][unit.getMarkerCategory()])
|
||||
.forEach((unit: Unit) => {
|
||||
var data: any = unit.getData();
|
||||
if (unit.getGroupName() in unitsToExport) unitsToExport[unit.getGroupName()].push(data);
|
||||
else unitsToExport[unit.getGroupName()] = [data];
|
||||
});
|
||||
|
||||
const file = new Blob([JSON.stringify(unitsToExport)], {
|
||||
type: "text/plain",
|
||||
});
|
||||
|
||||
const defaultName = "olympus_export_" + missionData.mission.theatre + "_" + missionData.sessionHash + "_" + missionData.time + ".json";
|
||||
|
||||
//@ts-ignore TODO
|
||||
if (window.showSaveFilePicker) {
|
||||
const opts = {
|
||||
types: [
|
||||
{
|
||||
description: "DCS Olympus export file",
|
||||
accept: { "text/plain": [".json"] },
|
||||
},
|
||||
],
|
||||
suggestedName: defaultName,
|
||||
};
|
||||
//@ts-ignore TODO
|
||||
showSaveFilePicker(opts)
|
||||
.then((handle) => handle.createWritable())
|
||||
.then((writeable) => {
|
||||
getApp().setState(OlympusState.IDLE);
|
||||
getApp().addInfoMessage("Exporting data please wait...");
|
||||
writeable
|
||||
.write(file)
|
||||
.then(() => writeable.close())
|
||||
.catch(() => getApp().addInfoMessage("An error occurred while exporting the data"));
|
||||
})
|
||||
.then(() => getApp().addInfoMessage("Data exported correctly"))
|
||||
.catch((err) => getApp().addInfoMessage("An error occurred while exporting the data"));
|
||||
} else {
|
||||
const a = document.createElement("a");
|
||||
const file = new Blob([JSON.stringify(unitsToExport)], {
|
||||
type: "text/plain",
|
||||
});
|
||||
a.href = URL.createObjectURL(file);
|
||||
|
||||
var filename = defaultName;
|
||||
a.download = filename;
|
||||
a.click();
|
||||
}
|
||||
} else {
|
||||
for (const [groupName, groupData] of Object.entries(importData)) {
|
||||
if (groupName === "" || groupData.length === 0 || !groupData.some((unitData: UnitData) => unitData.alive)) continue;
|
||||
|
||||
let { markerCategory, coalition, category } = groupData[0];
|
||||
|
||||
if (selectionFilter[coalition][markerCategory] !== true) continue;
|
||||
|
||||
let unitsToSpawn = groupData.map((unitData: UnitData) => {
|
||||
return { unitType: unitData.name, location: unitData.position, liveryID: "", skill: "High" };
|
||||
});
|
||||
|
||||
getApp().getUnitsManager().spawnUnits(category.toLocaleLowerCase(), unitsToSpawn, coalition, false);
|
||||
}
|
||||
getApp().setState(OlympusState.IDLE);
|
||||
}
|
||||
}}
|
||||
className={`
|
||||
mb-2 me-2 ml-auto flex content-center items-center gap-2
|
||||
rounded-sm bg-blue-700 px-5 py-2.5 text-sm font-medium text-white
|
||||
dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800
|
||||
focus:outline-none focus:ring-4 focus:ring-blue-300
|
||||
hover:bg-blue-800
|
||||
`}
|
||||
>
|
||||
Continue
|
||||
<FontAwesomeIcon icon={faArrowRight} />
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => getApp().setState(OlympusState.IDLE)}
|
||||
className={`
|
||||
mb-2 me-2 flex content-center items-center gap-2 rounded-sm
|
||||
border-[1px] bg-blue-700 px-5 py-2.5 text-sm font-medium
|
||||
text-white
|
||||
dark:border-gray-600 dark:bg-gray-800 dark:text-gray-400
|
||||
dark:hover:bg-gray-700 dark:focus:ring-blue-800
|
||||
focus:outline-none focus:ring-4 focus:ring-blue-300
|
||||
hover:bg-blue-800
|
||||
`}
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@ -4,7 +4,7 @@ import { OlToggle } from "../components/oltoggle";
|
||||
import { MAP_OPTIONS_DEFAULTS } from "../../constants/constants";
|
||||
import {
|
||||
AWACSReferenceChangedEvent as AWACSReferenceUnitChangedEvent,
|
||||
BullseyesDataChanged,
|
||||
BullseyesDataChangedEvent,
|
||||
HotgroupsChangedEvent,
|
||||
MapOptionsChangedEvent,
|
||||
UnitUpdatedEvent,
|
||||
@ -27,13 +27,10 @@ export function AWACSMenu(props: { open: boolean; onClose: () => void; children?
|
||||
MapOptionsChangedEvent.on((mapOptions) => setMapOptions({ ...mapOptions }));
|
||||
HotgroupsChangedEvent.on((hotgroups) => setHotgroups({ ...hotgroups }));
|
||||
AWACSReferenceUnitChangedEvent.on((unit) => setReferenceUnit(unit));
|
||||
BullseyesDataChanged.on((bullseyes) => setBullseyes(bullseyes));
|
||||
BullseyesDataChangedEvent.on((bullseyes) => setBullseyes(bullseyes));
|
||||
UnitUpdatedEvent.on((unit) => setRefreshTime(Date.now()));
|
||||
}, []);
|
||||
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<Menu title={"AWACS Tools"} open={props.open} onClose={props.onClose} showBackButton={false} canBeHidden={true}>
|
||||
<div
|
||||
|
||||
@ -2,8 +2,8 @@ import React, { useEffect, useState } from "react";
|
||||
import { OlLocation } from "../components/ollocation";
|
||||
import { LatLng } from "leaflet";
|
||||
import { FaBullseye, FaChevronDown, FaChevronUp, FaJetFighter, FaMountain } from "react-icons/fa6";
|
||||
import { BullseyesDataChanged, MouseMovedEvent, SelectedUnitsChangedEvent } from "../../events";
|
||||
import { bearing, computeBearingRangeString, mToFt } from "../../other/utils";
|
||||
import { BullseyesDataChangedEvent, MouseMovedEvent, SelectedUnitsChangedEvent } from "../../events";
|
||||
import { computeBearingRangeString, mToFt } from "../../other/utils";
|
||||
import { Bullseye } from "../../mission/bullseye";
|
||||
import { Unit } from "../../unit/unit";
|
||||
|
||||
@ -20,7 +20,7 @@ export function CoordinatesPanel(props: {}) {
|
||||
if (elevation) setElevation(elevation);
|
||||
});
|
||||
|
||||
BullseyesDataChanged.on((bullseyes) => setBullseyes(bullseyes));
|
||||
BullseyesDataChangedEvent.on((bullseyes) => setBullseyes(bullseyes));
|
||||
SelectedUnitsChangedEvent.on((selectedUnits) => setSelectedUnits(selectedUnits));
|
||||
}, []);
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { OlRoundStateButton, OlStateButton, OlLockStateButton } from "../components/olstatebutton";
|
||||
import { faSkull, faCamera, faFlag, faLink, faUnlink, faBars, faVolumeHigh } from "@fortawesome/free-solid-svg-icons";
|
||||
import { faSkull, faCamera, faFlag, faVolumeHigh, faDownload, faUpload } from "@fortawesome/free-solid-svg-icons";
|
||||
import { OlDropdownItem, OlDropdown } from "../components/oldropdown";
|
||||
import { OlLabelToggle } from "../components/ollabeltoggle";
|
||||
import { getApp, IP } from "../../olympusapp";
|
||||
@ -15,11 +15,11 @@ import {
|
||||
olButtonsVisibilityNavyunit,
|
||||
olButtonsVisibilityOlympus,
|
||||
} from "../components/olicons";
|
||||
import { FaChevronLeft, FaChevronRight, FaComputer, FaFloppyDisk, FaTabletScreenButton } from "react-icons/fa6";
|
||||
import { FaChevronLeft, FaChevronRight, FaFloppyDisk } from "react-icons/fa6";
|
||||
import { CommandModeOptionsChangedEvent, ConfigLoadedEvent, HiddenTypesChangedEvent, MapOptionsChangedEvent, MapSourceChangedEvent, SessionDataChangedEvent, SessionDataSavedEvent } from "../../events";
|
||||
import { BLUE_COMMANDER, COMMAND_MODE_OPTIONS_DEFAULTS, MAP_HIDDEN_TYPES_DEFAULTS, MAP_OPTIONS_DEFAULTS } from "../../constants/constants";
|
||||
import { BLUE_COMMANDER, COMMAND_MODE_OPTIONS_DEFAULTS, ImportExportSubstate, MAP_HIDDEN_TYPES_DEFAULTS, MAP_OPTIONS_DEFAULTS, OlympusState } from "../../constants/constants";
|
||||
import { OlympusConfig } from "../../interfaces";
|
||||
import { FaCheck, FaSpinner } from "react-icons/fa";
|
||||
import { FaCheck, FaSave, FaSpinner } from "react-icons/fa";
|
||||
|
||||
export function Header() {
|
||||
const [mapHiddenTypes, setMapHiddenTypes] = useState(MAP_HIDDEN_TYPES_DEFAULTS);
|
||||
@ -93,11 +93,11 @@ export function Header() {
|
||||
>
|
||||
<div
|
||||
className={`
|
||||
mr-auto hidden flex-none flex-row items-center justify-start gap-6
|
||||
mr-auto hidden flex-none flex-row items-center justify-start gap-2
|
||||
lg:flex
|
||||
`}
|
||||
>
|
||||
<div className="flex flex-col items-start">
|
||||
<div className="mr-2 flex flex-col items-start">
|
||||
<div
|
||||
className={`
|
||||
pt-1 text-xs text-gray-800
|
||||
@ -123,6 +123,8 @@ export function Header() {
|
||||
`}/><FaCheck className={`
|
||||
absolute left-[9px] top-[-6px] text-2xl text-olympus-900
|
||||
`}/><FaCheck className={`absolute left-3 top-0 text-green-500`}/></div>}
|
||||
<OlStateButton className="ml-8" icon={faDownload} onClick={() => {getApp().setState(OlympusState.IMPORT_EXPORT, ImportExportSubstate.EXPORT)}} checked={false}/>
|
||||
<OlStateButton icon={faUpload} onClick={() => {getApp().setState(OlympusState.IMPORT_EXPORT, ImportExportSubstate.IMPORT)}} checked={false}/>
|
||||
</div>
|
||||
|
||||
{commandModeOptions.commandMode === BLUE_COMMANDER && (
|
||||
@ -130,16 +132,6 @@ export function Header() {
|
||||
<span className="my-auto font-bold">BLUE Commander ({commandModeOptions.spawnPoints.blue} points)</span>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={`
|
||||
cursor-pointer rounded-full bg-blue-500 px-4 py-2 text-white
|
||||
`}
|
||||
onClick={() => {
|
||||
getApp().getMap().setOption("tabletMode", !mapOptions.tabletMode);
|
||||
}}
|
||||
>
|
||||
{mapOptions.tabletMode ? <FaTabletScreenButton /> : <FaComputer />}
|
||||
</div>
|
||||
<div className={`flex h-fit flex-row items-center justify-start gap-1`}>
|
||||
<OlLockStateButton
|
||||
checked={!mapOptions.protectDCSUnits}
|
||||
|
||||
@ -33,6 +33,7 @@ import { CoordinatesPanel } from "./panels/coordinatespanel";
|
||||
import { RadiosSummaryPanel } from "./panels/radiossummarypanel";
|
||||
import { AWACSMenu } from "./panels/awacsmenu";
|
||||
import { ServerOverlay } from "./serveroverlay";
|
||||
import { ImportExportModal } from "./modals/importexportmodal";
|
||||
|
||||
export type OlympusUIState = {
|
||||
mainMenuVisible: boolean;
|
||||
@ -81,6 +82,7 @@ export function UI() {
|
||||
<LoginModal open={appState === OlympusState.LOGIN} />
|
||||
<ProtectionPromptModal open={appState === OlympusState.UNIT_CONTROL && appSubState == UnitControlSubState.PROTECTION} />
|
||||
<KeybindModal open={appState === OlympusState.OPTIONS && appSubState === OptionsSubstate.KEYBIND} />
|
||||
<ImportExportModal open={appState === OlympusState.IMPORT_EXPORT} />
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
@ -632,6 +632,7 @@ export abstract class Unit extends CustomMarker {
|
||||
getData(): UnitData {
|
||||
return {
|
||||
category: this.getCategory(),
|
||||
markerCategory: this.getMarkerCategory(),
|
||||
ID: this.ID,
|
||||
alive: this.#alive,
|
||||
human: this.#human,
|
||||
@ -1229,7 +1230,7 @@ export abstract class Unit extends CustomMarker {
|
||||
}
|
||||
|
||||
setAdvancedOptions(isActiveTanker: boolean, isActiveAWACS: boolean, TACAN: TACAN, radio: Radio, generalSettings: GeneralSettings) {
|
||||
if (!this.#human) getApp().getServerManager().setAdvacedOptions(this.ID, isActiveTanker, isActiveAWACS, TACAN, radio, generalSettings);
|
||||
if (!this.#human) getApp().getServerManager().setAdvancedOptions(this.ID, isActiveTanker, isActiveAWACS, TACAN, radio, generalSettings);
|
||||
}
|
||||
|
||||
bombPoint(latlng: LatLng) {
|
||||
@ -1549,7 +1550,7 @@ export abstract class Unit extends CustomMarker {
|
||||
|
||||
/* Set BRAA */
|
||||
const reference = getApp().getUnitsManager().getAWACSReference();
|
||||
if (reference && reference !== this) {
|
||||
if (reference && reference !== this && reference.getAlive()) {
|
||||
const BRAA = `${computeBearingRangeString(reference.getPosition(), this.getPosition())}`;
|
||||
if (element.querySelector(".unit-braa")) (<HTMLElement>element.querySelector(".unit-braa")).innerText = `${BRAA}`;
|
||||
} else if (element.querySelector(".unit-braa")) (<HTMLElement>element.querySelector(".unit-braa")).innerText = ``;
|
||||
|
||||
@ -35,7 +35,7 @@ import {
|
||||
UnitDeadEvent,
|
||||
UnitDeselectedEvent,
|
||||
UnitSelectedEvent,
|
||||
UnitsRefreshed,
|
||||
UnitsRefreshedEvent,
|
||||
} from "../events";
|
||||
import { UnitDatabase } from "./databases/unitdatabase";
|
||||
import * as turf from "@turf/turf";
|
||||
@ -80,7 +80,7 @@ export class UnitsManager {
|
||||
});
|
||||
|
||||
SessionDataLoadedEvent.on((sessionData) => {
|
||||
UnitsRefreshed.on(() => {
|
||||
UnitsRefreshedEvent.on((units) => {
|
||||
const localSessionData = deepCopyTable(sessionData);
|
||||
if (localSessionData.hotgroups) {
|
||||
Object.keys(localSessionData.hotgroups).forEach((hotgroup) => {
|
||||
@ -311,7 +311,7 @@ export class UnitsManager {
|
||||
/* Compute the base clusters */
|
||||
this.#clusters = this.computeClusters();
|
||||
|
||||
if (fullUpdate) UnitsRefreshed.dispatch();
|
||||
if (fullUpdate) UnitsRefreshedEvent.dispatch(this.#units);
|
||||
|
||||
return updateTime;
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@ import { DataExtractor } from "../server/dataextractor";
|
||||
import { ObjectIconOptions } from "../interfaces";
|
||||
import { MapOptionsChangedEvent } from "../events";
|
||||
|
||||
export class Weapon extends CustomMarker {
|
||||
export abstract class Weapon extends CustomMarker {
|
||||
ID: number;
|
||||
|
||||
#alive: boolean = false;
|
||||
@ -54,10 +54,9 @@ export class Weapon extends CustomMarker {
|
||||
MapOptionsChangedEvent.on(() => this.#updateMarker());
|
||||
}
|
||||
|
||||
getCategory() {
|
||||
// Overloaded by child classes
|
||||
return "";
|
||||
}
|
||||
abstract getCategory(): string;
|
||||
abstract getMarkerCategory(): string;
|
||||
abstract getIconOptions(): ObjectIconOptions;
|
||||
|
||||
/********************** Unit data *************************/
|
||||
setData(dataExtractor: DataExtractor) {
|
||||
@ -110,28 +109,7 @@ export class Weapon extends CustomMarker {
|
||||
heading: this.#heading,
|
||||
};
|
||||
}
|
||||
|
||||
getMarkerCategory(): string {
|
||||
return "";
|
||||
}
|
||||
|
||||
getIconOptions(): ObjectIconOptions {
|
||||
// Default values, overloaded by child classes if needed
|
||||
return {
|
||||
showState: false,
|
||||
showVvi: false,
|
||||
showHealth: false,
|
||||
showHotgroup: false,
|
||||
showUnitIcon: true,
|
||||
showShortLabel: false,
|
||||
showFuel: false,
|
||||
showAmmo: false,
|
||||
showSummary: true,
|
||||
showCallsign: true,
|
||||
rotateToHeading: false,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
setAlive(newAlive: boolean) {
|
||||
this.#alive = newAlive;
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@ import { Weapon } from "./weapon";
|
||||
import { DataIndexes } from "../constants/constants";
|
||||
import { DataExtractor } from "../server/dataextractor";
|
||||
import { Contact } from "../interfaces";
|
||||
import { CommandModeOptionsChangedEvent, WeaponsRefreshed } from "../events";
|
||||
import { CommandModeOptionsChangedEvent, WeaponsRefreshedEvent } from "../events";
|
||||
|
||||
/** The WeaponsManager handles the creation and update of weapons. Data is strictly updated by the server ONLY. */
|
||||
export class WeaponsManager {
|
||||
@ -83,7 +83,7 @@ export class WeaponsManager {
|
||||
this.#weapons[ID]?.setData(dataExtractor);
|
||||
}
|
||||
|
||||
if (fullUpdate) WeaponsRefreshed.dispatch();
|
||||
if (fullUpdate) WeaponsRefreshedEvent.dispatch(this.#weapons);
|
||||
|
||||
return updateTime;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user