mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
Started adding AWACS panel
This commit is contained in:
parent
897afb2fef
commit
1791eaa37d
@ -12,6 +12,7 @@ import {
|
||||
faPlaneArrival,
|
||||
faRoute,
|
||||
faTrash,
|
||||
faWifi,
|
||||
faXmarksLines,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { Unit } from "../unit/unit";
|
||||
@ -260,7 +261,14 @@ export const mapBounds = {
|
||||
};
|
||||
|
||||
export const defaultMapMirrors = {};
|
||||
export const defaultMapLayers = {};
|
||||
export const defaultMapLayers = {
|
||||
"AWACS": {
|
||||
"urlTemplate": 'https://abcd.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}{r}.png',
|
||||
"minZoom": 1,
|
||||
"maxZoom": 19,
|
||||
"attribution": `© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors © <a href="https://carto.com/attributions">CARTO</a>'`
|
||||
},
|
||||
};
|
||||
|
||||
export enum OlympusState {
|
||||
NOT_INITIALIZED = "Not initialized",
|
||||
@ -272,6 +280,7 @@ export enum OlympusState {
|
||||
SPAWN_CONTEXT = "Spawn context",
|
||||
DRAW = "Draw",
|
||||
JTAC = "JTAC",
|
||||
AWACS = "AWACS",
|
||||
OPTIONS = "Options",
|
||||
AUDIO = "Audio",
|
||||
AIRBASE = "Airbase",
|
||||
@ -340,6 +349,9 @@ export const MAP_OPTIONS_DEFAULTS: MapOptions = {
|
||||
cameraPluginEnabled: false,
|
||||
cameraPluginMode: "map",
|
||||
tabletMode: false,
|
||||
showUnitBullseyes: false,
|
||||
showUnitBRAA: false,
|
||||
AWACSMode: false
|
||||
};
|
||||
|
||||
export const MAP_HIDDEN_TYPES_DEFAULTS = {
|
||||
@ -459,7 +471,6 @@ export namespace ContextActions {
|
||||
getApp().getUnitsManager().stop(units);
|
||||
},
|
||||
{
|
||||
executeImmediately: true,
|
||||
type: ContextActionType.MOVE,
|
||||
code: "Space",
|
||||
}
|
||||
@ -506,7 +517,6 @@ export namespace ContextActions {
|
||||
getApp().getUnitsManager().delete(false);
|
||||
},
|
||||
{
|
||||
executeImmediately: true,
|
||||
type: ContextActionType.DELETE,
|
||||
code: "Delete",
|
||||
ctrlKey: false,
|
||||
@ -526,7 +536,6 @@ export namespace ContextActions {
|
||||
UnitExplosionRequestEvent.dispatch(units);
|
||||
},
|
||||
{
|
||||
executeImmediately: true,
|
||||
type: ContextActionType.DELETE,
|
||||
code: "Delete",
|
||||
ctrlKey: false,
|
||||
@ -544,7 +553,7 @@ export namespace ContextActions {
|
||||
(units: Unit[]) => {
|
||||
getApp().getMap().centerOnUnit(units[0]);
|
||||
},
|
||||
{ executeImmediately: true, type: ContextActionType.OTHER, code: "KeyM", ctrlKey: false, shiftKey: false, altKey: false }
|
||||
{ type: ContextActionType.OTHER, code: "KeyM", ctrlKey: false, shiftKey: false, altKey: false }
|
||||
);
|
||||
|
||||
export const REFUEL = new ContextAction(
|
||||
@ -556,7 +565,7 @@ export namespace ContextActions {
|
||||
(units: Unit[]) => {
|
||||
getApp().getUnitsManager().refuel(units);
|
||||
},
|
||||
{ executeImmediately: true, type: ContextActionType.ADMIN, code: "KeyR", ctrlKey: false, shiftKey: false, altKey: false }
|
||||
{ type: ContextActionType.ADMIN, code: "KeyR", ctrlKey: false, shiftKey: false, altKey: false }
|
||||
);
|
||||
|
||||
export const FOLLOW = new ContextAction(
|
||||
@ -643,7 +652,7 @@ export namespace ContextActions {
|
||||
(units: Unit[], _1, _2) => {
|
||||
getApp().getUnitsManager().createGroup(units);
|
||||
},
|
||||
{ executeImmediately: true, type: ContextActionType.OTHER, code: "KeyG", ctrlKey: false, shiftKey: false, altKey: false }
|
||||
{ type: ContextActionType.OTHER, code: "KeyG", ctrlKey: false, shiftKey: false, altKey: false }
|
||||
);
|
||||
|
||||
export const ATTACK = new ContextAction(
|
||||
@ -687,4 +696,16 @@ export namespace ContextActions {
|
||||
},
|
||||
{ type: ContextActionType.ADMIN, code: "KeyX", ctrlKey: false, shiftKey: false }
|
||||
);
|
||||
|
||||
export const SET_AWACS_REFERENCE = new ContextAction(
|
||||
"set-awacs-reference",
|
||||
"Set AWACS reference",
|
||||
"Set unit as AWACS reference",
|
||||
faWifi,
|
||||
ContextActionTarget.NONE,
|
||||
(units: Unit[], _1, _2) => {
|
||||
getApp().getUnitsManager().setAWACSReference(units[0].ID);
|
||||
},
|
||||
{ type: ContextActionType.ADMIN, code: "KeyU", ctrlKey: false, shiftKey: false, altKey: false }
|
||||
);
|
||||
}
|
||||
|
||||
@ -151,6 +151,19 @@ export class ModalEvent {
|
||||
}
|
||||
|
||||
/************** Map events ***************/
|
||||
export class MouseMovedEvent {
|
||||
static on(callback: (latlng: LatLng, elevation: number) => void) {
|
||||
document.addEventListener(this.name, (ev: CustomEventInit) => {
|
||||
callback(ev.detail.latlng, ev.detail.elevation);
|
||||
});
|
||||
}
|
||||
|
||||
static dispatch(latlng: LatLng, elevation?: number) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail: { latlng, elevation } }));
|
||||
// Logging disabled since periodic
|
||||
}
|
||||
}
|
||||
|
||||
export class HiddenTypesChangedEvent {
|
||||
static on(callback: (hiddenTypes: MapHiddenTypes) => void) {
|
||||
document.addEventListener(this.name, (ev: CustomEventInit) => {
|
||||
@ -346,13 +359,13 @@ export class SpawnContextMenuRequestEvent {
|
||||
}
|
||||
|
||||
export class HotgroupsChangedEvent {
|
||||
static on(callback: (hotgroups: { [key: number]: number }) => void) {
|
||||
static on(callback: (hotgroups: { [key: number]: Unit[] }) => void) {
|
||||
document.addEventListener(this.name, (ev: CustomEventInit) => {
|
||||
callback(ev.detail.hotgroups);
|
||||
});
|
||||
}
|
||||
|
||||
static dispatch(hotgroups: { [key: number]: number }) {
|
||||
static dispatch(hotgroups: { [key: number]: Unit[] }) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail: { hotgroups } }));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
@ -371,15 +384,15 @@ export class StarredSpawnsChangedEvent {
|
||||
}
|
||||
}
|
||||
|
||||
export class MouseMovedEvent {
|
||||
static on(callback: (latlng: LatLng, elevation: number) => void) {
|
||||
export class AWACSReferenceChangedEvent {
|
||||
static on(callback: (unit: Unit | null) => void) {
|
||||
document.addEventListener(this.name, (ev: CustomEventInit) => {
|
||||
callback(ev.detail.latlng, ev.detail.elevation);
|
||||
callback(ev.detail);
|
||||
});
|
||||
}
|
||||
|
||||
static dispatch(latlng: LatLng, elevation?: number) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail: { latlng, elevation } }));
|
||||
static dispatch(unit: Unit | null) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail: unit }));
|
||||
// Logging disabled since periodic
|
||||
}
|
||||
}
|
||||
@ -461,7 +474,7 @@ export class BullseyesDataChanged {
|
||||
});
|
||||
}
|
||||
|
||||
static dispatch(bullseyes: { [name: string]: Bullseye } ) {
|
||||
static dispatch(bullseyes: { [name: string]: Bullseye }) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail: { bullseyes } }));
|
||||
// Logging disabled since periodic
|
||||
}
|
||||
|
||||
@ -130,6 +130,8 @@ export interface ObjectIconOptions {
|
||||
showSummary: boolean;
|
||||
showCallsign: boolean;
|
||||
rotateToHeading: boolean;
|
||||
showBullseyes: boolean;
|
||||
showBRAA: boolean;
|
||||
}
|
||||
|
||||
export interface GeneralSettings {
|
||||
|
||||
@ -207,7 +207,7 @@ export class Map extends L.Map {
|
||||
/* Custom touch events for touchscreen support */
|
||||
L.DomEvent.on(this.getContainer(), "touchstart", this.#onMouseDown, this);
|
||||
L.DomEvent.on(this.getContainer(), "touchend", this.#onMouseUp, this);
|
||||
L.DomEvent.on(this.getContainer(), 'wheel', this.#onMouseWheel, this);
|
||||
L.DomEvent.on(this.getContainer(), "wheel", this.#onMouseWheel, this);
|
||||
|
||||
/* Event listeners */
|
||||
AppStateChangedEvent.on((state, subState) => this.#onStateChanged(state, subState));
|
||||
@ -227,6 +227,8 @@ export class Map extends L.Map {
|
||||
|
||||
MapOptionsChangedEvent.on((options: MapOptions) => {
|
||||
this.getContainer().toggleAttribute("data-hide-labels", !options.showUnitLabels);
|
||||
this.getContainer().toggleAttribute("data-hide-bullseyes", !options.showUnitBullseyes);
|
||||
this.getContainer().toggleAttribute("data-hide-BRAA", !options.showUnitBRAA);
|
||||
this.#cameraControlPort = options.cameraPluginPort;
|
||||
this.#cameraZoomRatio = 50 / (20 + options.cameraPluginRatio);
|
||||
this.#slaveDCSCamera = options.cameraPluginEnabled;
|
||||
@ -400,7 +402,7 @@ export class Map extends L.Map {
|
||||
const contextActionSet = this.getContextActionSet();
|
||||
if (this.getContextAction() === null || contextAction !== this.getContextAction()) {
|
||||
if (getApp().getState() === OlympusState.UNIT_CONTROL && contextActionSet && contextAction.getId() in contextActionSet.getContextActions()) {
|
||||
if (contextAction.getOptions().executeImmediately) contextAction.executeCallback(null, null);
|
||||
if (contextAction.getTarget() === ContextActionTarget.NONE) contextAction.executeCallback(null, null);
|
||||
else this.setContextAction(contextAction);
|
||||
}
|
||||
} else {
|
||||
|
||||
@ -100,6 +100,7 @@
|
||||
font-weight: var(--unit-font-weight);
|
||||
line-height: normal;
|
||||
position: absolute;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
[data-object|="unit-groundunit"] .unit-short-label {
|
||||
@ -398,3 +399,53 @@
|
||||
.ol-temporary-marker {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/*** Unit summary ***/
|
||||
[data-object|="unit"] .unit-tactical {
|
||||
color: white;
|
||||
column-gap: 6px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
justify-content: left;
|
||||
line-height: 12px;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
row-gap: 1px;
|
||||
|
||||
left: 100%;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
[data-hide-bullseyes] [data-object|="unit"] .unit-bullseyes {
|
||||
display: none;
|
||||
}
|
||||
|
||||
[data-hide-BRAA] [data-object|="unit"] .unit-braa {
|
||||
display: none;
|
||||
}
|
||||
|
||||
[data-object|="unit"] .unit-blue-bullseye {
|
||||
text-shadow:
|
||||
-1px -1px 0 #00F,
|
||||
1px -1px 0 #00F,
|
||||
-1px 1px 0 #00F,
|
||||
1px 1px 0 #00F;
|
||||
}
|
||||
|
||||
[data-object|="unit"] .unit-red-bullseye {
|
||||
text-shadow:
|
||||
-1px -1px 0 #F00,
|
||||
1px -1px 0 #F00,
|
||||
-1px 1px 0 #F00,
|
||||
1px 1px 0 #F00;
|
||||
}
|
||||
|
||||
[data-object|="unit"] .unit-braa {
|
||||
text-shadow:
|
||||
-1px -1px 0 #000,
|
||||
1px -1px 0 #000,
|
||||
-1px 1px 0 #000,
|
||||
1px 1px 0 #000;
|
||||
}
|
||||
|
||||
@ -180,4 +180,4 @@
|
||||
|
||||
path.leaflet-interactive:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
@ -393,3 +393,7 @@ export function wait(time) {
|
||||
setTimeout(resolve, time);
|
||||
});
|
||||
}
|
||||
|
||||
export function computeBearingRangeString(latlng1, latlng2) {
|
||||
return `${bearing(latlng1.lat, latlng1.lng, latlng2.lat, latlng2.lng).toFixed()}/${(latlng1.distanceTo(latlng2) / 1852).toFixed(0)}`;
|
||||
}
|
||||
|
||||
@ -26,6 +26,9 @@ export type MapOptions = {
|
||||
cameraPluginEnabled: boolean;
|
||||
cameraPluginMode: string;
|
||||
tabletMode: boolean;
|
||||
showUnitBullseyes: false;
|
||||
showUnitBRAA: false;
|
||||
AWACSMode: false;
|
||||
};
|
||||
|
||||
export type MapHiddenTypes = {
|
||||
|
||||
@ -10,6 +10,7 @@ import {
|
||||
ContextActionChangedEvent,
|
||||
ContextActionSetChangedEvent,
|
||||
MapContextMenuRequestEvent,
|
||||
SelectedUnitsChangedEvent,
|
||||
SelectionClearedEvent,
|
||||
UnitContextMenuRequestEvent,
|
||||
} from "../../events";
|
||||
@ -24,6 +25,7 @@ export function MapContextMenu(props: {}) {
|
||||
const [yPosition, setYPosition] = useState(0);
|
||||
const [latLng, setLatLng] = useState(null as null | LatLng);
|
||||
const [unit, setUnit] = useState(null as null | Unit);
|
||||
const [selectedUnits, setSelectedUnits] = useState([] as Unit[]);
|
||||
|
||||
var contentRef = useRef(null);
|
||||
|
||||
@ -45,6 +47,7 @@ export function MapContextMenu(props: {}) {
|
||||
setXPosition(getApp().getMap().getContainer().offsetLeft + containerPoint.x);
|
||||
setYPosition(getApp().getMap().getContainer().offsetTop + containerPoint.y);
|
||||
});
|
||||
SelectedUnitsChangedEvent.on((selectedUnits) => setSelectedUnits([...selectedUnits]));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
@ -70,7 +73,13 @@ export function MapContextMenu(props: {}) {
|
||||
|
||||
let reorderedActions: ContextAction[] = contextActionSet
|
||||
? Object.values(
|
||||
contextActionSet.getContextActions(appSubState === UnitControlSubState.MAP_CONTEXT_MENU ? ContextActionTarget.POINT : ContextActionTarget.UNIT)
|
||||
contextActionSet.getContextActions(
|
||||
selectedUnits.length === 1 && unit === selectedUnits[0]
|
||||
? ContextActionTarget.NONE
|
||||
: appSubState === UnitControlSubState.MAP_CONTEXT_MENU
|
||||
? ContextActionTarget.POINT
|
||||
: ContextActionTarget.UNIT
|
||||
)
|
||||
).sort((a: ContextAction, b: ContextAction) => (a.getOptions().type < b.getOptions().type ? -1 : 1))
|
||||
: [];
|
||||
|
||||
@ -104,7 +113,7 @@ export function MapContextMenu(props: {}) {
|
||||
${colorString}
|
||||
`}
|
||||
onClick={() => {
|
||||
if (contextActionIt.getOptions().executeImmediately) {
|
||||
if (contextActionIt.getTarget() === ContextActionTarget.NONE) {
|
||||
contextActionIt.executeCallback(null, null);
|
||||
} else {
|
||||
if (appSubState === UnitControlSubState.MAP_CONTEXT_MENU) {
|
||||
@ -113,10 +122,13 @@ export function MapContextMenu(props: {}) {
|
||||
contextActionIt.executeCallback(unit, null);
|
||||
}
|
||||
window.setTimeout(() => {
|
||||
if (getApp().getSubState() === UnitControlSubState.MAP_CONTEXT_MENU || getApp().getSubState() === UnitControlSubState.UNIT_CONTEXT_MENU) {
|
||||
getApp().setState(OlympusState.UNIT_CONTROL)
|
||||
if (
|
||||
getApp().getSubState() === UnitControlSubState.MAP_CONTEXT_MENU ||
|
||||
getApp().getSubState() === UnitControlSubState.UNIT_CONTEXT_MENU
|
||||
) {
|
||||
getApp().setState(OlympusState.UNIT_CONTROL);
|
||||
}
|
||||
}, 200)
|
||||
}, 200);
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
||||
108
frontend/react/src/ui/panels/awacsmenu.tsx
Normal file
108
frontend/react/src/ui/panels/awacsmenu.tsx
Normal file
@ -0,0 +1,108 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Menu } from "./components/menu";
|
||||
import { OlToggle } from "../components/oltoggle";
|
||||
import { MAP_OPTIONS_DEFAULTS } from "../../constants/constants";
|
||||
import { AWACSReferenceChangedEvent as AWACSReferenceUnitChangedEvent, HotgroupsChangedEvent, MapOptionsChangedEvent } from "../../events";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { OlCoalitionToggle } from "../components/olcoalitiontoggle";
|
||||
import { Coalition } from "../../types/types";
|
||||
import { FaQuestionCircle } from "react-icons/fa";
|
||||
import { Unit } from "../../unit/unit";
|
||||
|
||||
export function AWACSMenu(props: { open: boolean; onClose: () => void; children?: JSX.Element | JSX.Element[] }) {
|
||||
const [callsign, setCallsign] = useState("Magic");
|
||||
const [mapOptions, setMapOptions] = useState(MAP_OPTIONS_DEFAULTS);
|
||||
const [coalition, setCoalition] = useState("blue" as Coalition);
|
||||
const [hotgroups, setHotgroups] = useState({} as { [key: number]: Unit[] });
|
||||
const [referenceUnit, setReferenceUnit] = useState(null as Unit | null);
|
||||
|
||||
useEffect(() => {
|
||||
MapOptionsChangedEvent.on((mapOptions) => setMapOptions({ ...mapOptions }));
|
||||
HotgroupsChangedEvent.on((hotgroups) => setHotgroups({ ...hotgroups }));
|
||||
AWACSReferenceUnitChangedEvent.on((unit) => setReferenceUnit(unit));
|
||||
}, []);
|
||||
|
||||
const enemyGroups = Object.values(hotgroups).filter((hotgroup) => {
|
||||
return hotgroup.every((unit) => unit.getCoalition() !== coalition)
|
||||
})
|
||||
|
||||
return (
|
||||
<Menu title={"AWACS Tools"} open={props.open} onClose={props.onClose} showBackButton={false} canBeHidden={true}>
|
||||
<div
|
||||
className={`
|
||||
flex flex-col gap-4 p-4 font-normal text-gray-800
|
||||
dark:text-white
|
||||
`}
|
||||
>
|
||||
<>
|
||||
<div className="flex content-center gap-4">
|
||||
<FaQuestionCircle
|
||||
className={`my-auto min-h-5 min-w-5 text-sm text-gray-500`}
|
||||
/>
|
||||
<div className="flex flex-col gap-1 text-sm text-gray-500">
|
||||
<p>1 Use the coalition toggle to change your coalition as AWACS.</p>
|
||||
<p>2 Set a friendly unit as reference by right clicking on it and selecting "Set AWACS reference".</p>
|
||||
<p>3 Set enemy unit hotgroups to automatically create picture and tactical calls to read on radio for your CAP.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-4">
|
||||
<span className="my-auto min-w-32 text-nowrap">Callsign</span>
|
||||
<input
|
||||
className={`
|
||||
block h-10 w-full border-[2px] bg-gray-50 py-2.5 text-center
|
||||
text-sm text-gray-900
|
||||
dark:border-gray-700 dark:bg-olympus-600 dark:text-white
|
||||
dark:placeholder-gray-400 dark:focus:border-blue-700
|
||||
dark:focus:ring-blue-700
|
||||
focus:border-blue-700 focus:ring-blue-500
|
||||
`}
|
||||
value={callsign}
|
||||
onChange={(ev) => setCallsign(ev.target.value)}
|
||||
></input>
|
||||
<OlCoalitionToggle
|
||||
onClick={() => {
|
||||
coalition === "blue" && setCoalition("neutral");
|
||||
coalition === "neutral" && setCoalition("red");
|
||||
coalition === "red" && setCoalition("blue");
|
||||
}}
|
||||
coalition={coalition}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex gap-2">
|
||||
<OlToggle
|
||||
onClick={() => {
|
||||
getApp().getMap().setOption("showUnitBullseyes", !mapOptions.showUnitBullseyes);
|
||||
}}
|
||||
toggled={mapOptions.showUnitBullseyes}
|
||||
/>{" "}
|
||||
Show units Bullseye position
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<OlToggle
|
||||
onClick={() => {
|
||||
getApp().getMap().setOption("showUnitBRAA", !mapOptions.showUnitBRAA);
|
||||
}}
|
||||
toggled={mapOptions.showUnitBRAA}
|
||||
/>
|
||||
Show units BRAA from reference unit
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
{
|
||||
referenceUnit ? <>
|
||||
{
|
||||
enemyGroups.length == 0 ? <>
|
||||
No enemy or neutral hotgroup
|
||||
</>:<>
|
||||
|
||||
</>
|
||||
}
|
||||
</>:<>No reference unit selected</>
|
||||
}
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
@ -28,9 +28,9 @@ export function CoordinatesPanel(props: {}) {
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
absolute bottom-[20px] right-[310px] flex w-[380px] flex-col
|
||||
items-center justify-between gap-2 rounded-lg bg-gray-200 p-3 text-sm
|
||||
backdrop-blur-lg backdrop-grayscale
|
||||
absolute bottom-[20px] right-[310px] flex min-h-12 w-[380px] flex-col
|
||||
items-center justify-between gap-2 rounded-lg bg-gray-200 px-3 py-3
|
||||
text-sm backdrop-blur-lg backdrop-grayscale
|
||||
dark:bg-olympus-800/90 dark:text-gray-200
|
||||
`}
|
||||
>
|
||||
|
||||
@ -35,7 +35,8 @@ export function Header() {
|
||||
MapOptionsChangedEvent.on((mapOptions) => setMapOptions({ ...mapOptions }));
|
||||
MapSourceChangedEvent.on((source) => setMapSource(source));
|
||||
ConfigLoadedEvent.on((config: OlympusConfig) => {
|
||||
var sources = Object.keys(config.frontend.mapMirrors).concat(Object.keys(config.frontend.mapLayers));
|
||||
|
||||
var sources = Object.keys(config.frontend.mapMirrors).concat(Object.keys(config.frontend.mapLayers)).concat(getApp().getMap().getLayers());
|
||||
setMapSources(sources);
|
||||
});
|
||||
CommandModeOptionsChangedEvent.on((commandModeOptions) => {
|
||||
|
||||
@ -1,15 +1,13 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { AppStateChangedEvent, ContextActionChangedEvent, HotgroupsChangedEvent, InfoPopupEvent } from "../../events";
|
||||
import { AppStateChangedEvent, HotgroupsChangedEvent } from "../../events";
|
||||
import { OlympusState } from "../../constants/constants";
|
||||
import { ContextAction } from "../../unit/contextaction";
|
||||
import { OlStateButton } from "../components/olstatebutton";
|
||||
import { faUserGroup } from "@fortawesome/free-solid-svg-icons";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { Unit } from "../../unit/unit";
|
||||
|
||||
export function HotGroupBar(props: {}) {
|
||||
const [hotgroups, setHotgroups] = useState({} as { [key: number]: number });
|
||||
const [hotgroups, setHotgroups] = useState({} as { [key: number]: Unit[] });
|
||||
const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED);
|
||||
const [menuHidden, setMenuHidden] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
AppStateChangedEvent.on((state, subState) => setAppState(state));
|
||||
@ -18,14 +16,11 @@ export function HotGroupBar(props: {}) {
|
||||
|
||||
return (
|
||||
<div
|
||||
data-menuhidden={menuHidden || appState === OlympusState.IDLE}
|
||||
className={`
|
||||
absolute bottom-16 left-[50%] flex gap-2
|
||||
data-[menuhidden='false']:translate-x-[calc(200px-50%+2rem)]
|
||||
data-[menuhidden='true']:translate-x-[calc(-50%+2rem)]
|
||||
absolute bottom-24 left-[50%] flex translate-x-[calc(-50%+2rem)] gap-2
|
||||
`}
|
||||
>
|
||||
{Object.entries(hotgroups).map(([hotgroup, counter]) => {
|
||||
{Object.entries(hotgroups).map(([hotgroup, units]) => {
|
||||
return (
|
||||
<div className="flex flex-col content-center gap-2">
|
||||
<div
|
||||
@ -37,11 +32,13 @@ export function HotGroupBar(props: {}) {
|
||||
<div className="relative -rotate-45">{hotgroup}</div>
|
||||
</div>
|
||||
|
||||
<OlStateButton checked={false} onClick={() => {getApp().getUnitsManager().selectUnitsByHotgroup(parseInt(hotgroup))}} tooltip="">
|
||||
<OlStateButton checked={false} onClick={() => {getApp().getUnitsManager().selectUnitsByHotgroup(parseInt(hotgroup))}} tooltip="Select units of this hotgroup" className={`
|
||||
min-h-12 min-w-12
|
||||
`}>
|
||||
<span
|
||||
className={`text-sm text-white`}
|
||||
className={`text-white`}
|
||||
>
|
||||
{counter}
|
||||
{units.length}
|
||||
</span>
|
||||
</OlStateButton>
|
||||
</div>
|
||||
|
||||
@ -58,7 +58,7 @@ export function MiniMapPanel(props: {}) {
|
||||
${mapOptions.showMinimap ? `bottom-[188px]` : `bottom-[20px]`}
|
||||
flex w-[288px] items-center justify-between
|
||||
${mapOptions.showMinimap ? `rounded-t-lg` : `rounded-lg`}
|
||||
bg-gray-200 p-3 text-sm backdrop-blur-lg backdrop-grayscale
|
||||
h-12 bg-gray-200 px-3 text-sm backdrop-blur-lg backdrop-grayscale
|
||||
dark:bg-olympus-800/90 dark:text-gray-200
|
||||
`}
|
||||
>
|
||||
|
||||
@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react";
|
||||
import { AudioSinksChangedEvent } from "../../events";
|
||||
import { AudioSink } from "../../audio/audiosink";
|
||||
import { RadioSink } from "../../audio/radiosink";
|
||||
import { FaJetFighter, FaRadio } from "react-icons/fa6";
|
||||
import { FaJetFighter, FaRadio, FaVolumeHigh } from "react-icons/fa6";
|
||||
import { OlStateButton } from "../components/olstatebutton";
|
||||
import { UnitSink } from "../../audio/unitsink";
|
||||
|
||||
@ -19,13 +19,12 @@ export function RadiosSummaryPanel(props: {}) {
|
||||
<div
|
||||
className={`
|
||||
absolute bottom-[20px] right-[700px] flex w-fit flex-col
|
||||
items-center justify-between gap-2 rounded-lg bg-gray-200 p-3
|
||||
text-sm backdrop-blur-lg backdrop-grayscale
|
||||
dark:bg-olympus-800/90 dark:text-gray-200
|
||||
items-center justify-between gap-2 rounded-lg bg-transparent text-sm
|
||||
text-gray-200
|
||||
`}
|
||||
>
|
||||
<div className="flex w-full items-center justify-between gap-2">
|
||||
<FaRadio className="text-xl" />
|
||||
|
||||
{audioSinks.filter((audioSinks) => audioSinks instanceof RadioSink).length > 0 &&
|
||||
audioSinks
|
||||
.filter((audioSinks) => audioSinks instanceof RadioSink)
|
||||
@ -43,15 +42,14 @@ export function RadiosSummaryPanel(props: {}) {
|
||||
}}
|
||||
tooltip="Click to talk, lights up when receiving"
|
||||
buttonColor={radioSink.getReceiving() ? "white" : null}
|
||||
className="min-h-12 min-w-12"
|
||||
>
|
||||
<span className={`font-bold text-gray-200`}>{idx + 1}</span>
|
||||
<span className={`text-gray-200`}><FaRadio className={`
|
||||
-translate-x-2 translate-y-1
|
||||
`} /> <div className="translate-x-2 font-bold">{idx + 1}</div></span>
|
||||
</OlStateButton>
|
||||
);
|
||||
})}
|
||||
|
||||
{audioSinks.filter((audioSinks) => audioSinks instanceof UnitSink).length > 0 && <FaJetFighter className={`
|
||||
text-xl
|
||||
`} />}
|
||||
{audioSinks.filter((audioSinks) => audioSinks instanceof UnitSink).length > 0 &&
|
||||
audioSinks
|
||||
.filter((audioSinks) => audioSinks instanceof UnitSink)
|
||||
@ -68,8 +66,11 @@ export function RadiosSummaryPanel(props: {}) {
|
||||
unitSink.setPtt(false);
|
||||
}}
|
||||
tooltip="Click to talk"
|
||||
className="min-h-12 min-w-12"
|
||||
>
|
||||
<span className={`font-bold text-gray-200`}>{idx + 1}</span>
|
||||
<span className={`text-gray-200`}><FaVolumeHigh className={`
|
||||
-translate-x-2 translate-y-1
|
||||
`} /> <div className="translate-x-2 font-bold">{idx + 1}</div></span>
|
||||
</OlStateButton>
|
||||
);
|
||||
})}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { OlStateButton } from "../components/olstatebutton";
|
||||
import { faGamepad, faPencil, faEllipsisV, faCog, faQuestionCircle, faPlusSquare, faVolumeHigh, faJ, faCrown } from "@fortawesome/free-solid-svg-icons";
|
||||
import { faGamepad, faPencil, faEllipsisV, faCog, faQuestionCircle, faPlusSquare, faVolumeHigh, faJ, faCrown, faA } from "@fortawesome/free-solid-svg-icons";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { NO_SUBSTATE, OlympusState, OlympusSubState, SpawnSubState } from "../../constants/constants";
|
||||
import { AppStateChangedEvent } from "../../events";
|
||||
@ -65,13 +65,21 @@ export function SideBar() {
|
||||
icon={faVolumeHigh}
|
||||
tooltip="Hide/show audio menu"
|
||||
></OlStateButton>
|
||||
<OlStateButton
|
||||
{/*}<OlStateButton
|
||||
onClick={() => {
|
||||
getApp().setState(appState !== OlympusState.JTAC ? OlympusState.JTAC : OlympusState.IDLE);
|
||||
}}
|
||||
checked={appState === OlympusState.JTAC}
|
||||
icon={faJ}
|
||||
tooltip="Hide/show JTAC menu"
|
||||
></OlStateButton>{*/}
|
||||
<OlStateButton
|
||||
onClick={() => {
|
||||
getApp().setState(appState !== OlympusState.AWACS ? OlympusState.AWACS : OlympusState.IDLE);
|
||||
}}
|
||||
checked={appState === OlympusState.AWACS}
|
||||
icon={faA}
|
||||
tooltip="Hide/show AWACS menu"
|
||||
></OlStateButton>
|
||||
<OlStateButton
|
||||
onClick={() => {
|
||||
|
||||
@ -3,9 +3,8 @@ import { ContextActionSet } from "../../unit/contextactionset";
|
||||
import { OlStateButton } from "../components/olstatebutton";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { ContextAction } from "../../unit/contextaction";
|
||||
import { CONTEXT_ACTION_COLORS, MAP_OPTIONS_DEFAULTS } from "../../constants/constants";
|
||||
import { FaInfoCircle } from "react-icons/fa";
|
||||
import { FaChevronDown, FaChevronLeft, FaChevronRight, FaChevronUp } from "react-icons/fa6";
|
||||
import { CONTEXT_ACTION_COLORS, ContextActionTarget, MAP_OPTIONS_DEFAULTS } from "../../constants/constants";
|
||||
import { FaChevronDown,FaChevronUp } from "react-icons/fa6";
|
||||
import { OlympusState } from "../../constants/constants";
|
||||
import { AppStateChangedEvent, ContextActionChangedEvent, ContextActionSetChangedEvent, MapOptionsChangedEvent } from "../../events";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
@ -83,7 +82,7 @@ export function UnitControlBar(props: {}) {
|
||||
tooltip={contextActionIt.getLabel()}
|
||||
buttonColor={CONTEXT_ACTION_COLORS[contextActionIt.getOptions().type ?? 0]}
|
||||
onClick={() => {
|
||||
if (contextActionIt.getOptions().executeImmediately) {
|
||||
if (contextActionIt.getTarget() === ContextActionTarget.NONE) {
|
||||
contextActionIt.executeCallback(null, null);
|
||||
} else {
|
||||
contextActionIt !== contextAction
|
||||
|
||||
@ -12,7 +12,6 @@ import {
|
||||
emissionsCountermeasures,
|
||||
maxAltitudeValues,
|
||||
maxSpeedValues,
|
||||
minAltitudeValues,
|
||||
reactionsToThreat,
|
||||
speedIncrements,
|
||||
} from "../../constants/constants";
|
||||
|
||||
@ -31,6 +31,7 @@ import { HotGroupBar } from "./panels/hotgroupsbar";
|
||||
import { SpawnContextMenu } from "./contextmenus/spawncontextmenu";
|
||||
import { CoordinatesPanel } from "./panels/coordinatespanel";
|
||||
import { RadiosSummaryPanel } from "./panels/radiossummarypanel";
|
||||
import { AWACSMenu } from "./panels/awacsmenu";
|
||||
|
||||
export type OlympusUIState = {
|
||||
mainMenuVisible: boolean;
|
||||
@ -93,7 +94,8 @@ export function UI() {
|
||||
open={appState === OlympusState.UNIT_CONTROL && appSubState === UnitControlSubState.UNIT_EXPLOSION_MENU}
|
||||
onClose={() => getApp().setState(OlympusState.IDLE)}
|
||||
/>
|
||||
<JTACMenu open={appState === OlympusState.JTAC} onClose={() => getApp().setState(OlympusState.IDLE)} />
|
||||
{/*}<JTACMenu open={appState === OlympusState.JTAC} onClose={() => getApp().setState(OlympusState.IDLE)} />{*/}
|
||||
<AWACSMenu open={appState === OlympusState.AWACS} onClose={() => getApp().setState(OlympusState.IDLE)} />
|
||||
|
||||
<MiniMapPanel />
|
||||
<ControlsPanel />
|
||||
|
||||
@ -4,7 +4,6 @@ import { LatLng } from "leaflet";
|
||||
import { ContextActionTarget, ContextActionType } from "../constants/constants";
|
||||
|
||||
export interface ContextActionOptions {
|
||||
executeImmediately?: boolean;
|
||||
type: ContextActionType;
|
||||
code: string | null;
|
||||
shiftKey?: boolean;
|
||||
@ -32,7 +31,6 @@ export class ContextAction {
|
||||
this.#callback = callback;
|
||||
this.#icon = icon;
|
||||
this.#options = {
|
||||
executeImmediately: false,
|
||||
...options,
|
||||
};
|
||||
}
|
||||
|
||||
@ -21,7 +21,7 @@ export class ContextActionSet {
|
||||
}
|
||||
|
||||
getContextActions(targetFilter?: ContextActionTarget) {
|
||||
if (targetFilter) {
|
||||
if (targetFilter !== undefined) {
|
||||
var filteredContextActionSet = new ContextActionSet();
|
||||
Object.keys(this.#contextActions).forEach((key) => {
|
||||
if (this.#contextActions[key].getTarget() === targetFilter) filteredContextActionSet[key] = this.#contextActions[key];
|
||||
|
||||
@ -969,6 +969,27 @@ export abstract class Unit extends CustomMarker {
|
||||
el.appendChild(summary);
|
||||
}
|
||||
|
||||
var tactical = document.createElement("div");
|
||||
tactical.classList.add("unit-tactical");
|
||||
if (iconOptions.showBullseyes) {
|
||||
var bullseyes = document.createElement("div");
|
||||
bullseyes.classList.add("unit-bullseyes");
|
||||
var blueBullseye = document.createElement("div");
|
||||
blueBullseye.classList.add("unit-blue-bullseye");
|
||||
var redBullseye = document.createElement("div");
|
||||
redBullseye.classList.add("unit-red-bullseye");
|
||||
bullseyes.appendChild(blueBullseye);
|
||||
bullseyes.appendChild(redBullseye);
|
||||
tactical.appendChild(bullseyes);
|
||||
}
|
||||
|
||||
if (iconOptions.showBRAA) {
|
||||
var BRAA = document.createElement("div");
|
||||
BRAA.classList.add("unit-braa");
|
||||
tactical.appendChild(BRAA);
|
||||
}
|
||||
el.appendChild(tactical)
|
||||
|
||||
this.getElement()?.appendChild(el);
|
||||
}
|
||||
|
||||
@ -1490,6 +1511,21 @@ export abstract class Unit extends CustomMarker {
|
||||
const hotgroupEl = element.querySelector(".unit-hotgroup-id") as HTMLElement;
|
||||
if (hotgroupEl) hotgroupEl.innerText = String(this.#hotgroup);
|
||||
}
|
||||
|
||||
/* Set bullseyes positions */
|
||||
const bullseyes = getApp().getMissionManager().getBullseyes();
|
||||
const blueBullseye = `${bearing(bullseyes[2].getLatLng().lat, bullseyes[2].getLatLng().lng, this.getLatLng().lat, this.getLatLng().lng).toFixed()}/${(bullseyes[2].getLatLng().distanceTo(this.getLatLng()) / 1852).toFixed(0)}`
|
||||
const redBullseye = `${bearing(bullseyes[1].getLatLng().lat, bullseyes[1].getLatLng().lng, this.getLatLng().lat, this.getLatLng().lng).toFixed()}/${(bullseyes[1].getLatLng().distanceTo(this.getLatLng()) / 1852).toFixed(0)}`
|
||||
if (element.querySelector(".unit-blue-bullseye")) (<HTMLElement>element.querySelector(".unit-blue-bullseye")).innerText = `${blueBullseye}`;
|
||||
if (element.querySelector(".unit-red-bullseye")) (<HTMLElement>element.querySelector(".unit-red-bullseye")).innerText = `${redBullseye}`;
|
||||
|
||||
/* Set BRAA */
|
||||
const reference = getApp().getUnitsManager().getAWACSReference();
|
||||
if (reference && reference !== this) {
|
||||
const BRAA = `${bearing(reference.getLatLng().lat, reference.getLatLng().lng, this.getLatLng().lat, this.getLatLng().lng).toFixed()}/${(reference.getLatLng().distanceTo(this.getLatLng()) / 1852).toFixed(0)}`
|
||||
if (element.querySelector(".unit-braa")) (<HTMLElement>element.querySelector(".unit-braa")).innerText = `${BRAA}`;
|
||||
}
|
||||
else if (element.querySelector(".unit-braa")) (<HTMLElement>element.querySelector(".unit-braa")).innerText = ``;
|
||||
}
|
||||
|
||||
/* Set vertical offset for altitude stacking */
|
||||
@ -1770,7 +1806,9 @@ export abstract class AirUnit extends Unit {
|
||||
showSummary: belongsToCommandedCoalition || this.getDetectionMethods().some((value) => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value)),
|
||||
showCallsign: belongsToCommandedCoalition,
|
||||
rotateToHeading: false,
|
||||
};
|
||||
showBullseyes: true,
|
||||
showBRAA: true,
|
||||
} as ObjectIconOptions;
|
||||
}
|
||||
|
||||
appendContextActions(contextActionSet: ContextActionSet) {
|
||||
@ -1783,6 +1821,7 @@ export abstract class AirUnit extends Unit {
|
||||
/* Context actions that require a target unit */
|
||||
contextActionSet.addContextAction(this, ContextActions.ATTACK);
|
||||
contextActionSet.addContextAction(this, ContextActions.FOLLOW);
|
||||
contextActionSet.addContextAction(this, ContextActions.SET_AWACS_REFERENCE)
|
||||
|
||||
if (this.canTargetPoint()) {
|
||||
/* Context actions that require a target position */
|
||||
@ -1858,7 +1897,9 @@ export class GroundUnit extends Unit {
|
||||
showSummary: false,
|
||||
showCallsign: belongsToCommandedCoalition,
|
||||
rotateToHeading: false,
|
||||
};
|
||||
showBullseyes: false,
|
||||
showBRAA: false,
|
||||
} as ObjectIconOptions;
|
||||
}
|
||||
|
||||
appendContextActions(contextActionSet: ContextActionSet) {
|
||||
@ -1927,7 +1968,9 @@ export class NavyUnit extends Unit {
|
||||
showSummary: false,
|
||||
showCallsign: belongsToCommandedCoalition,
|
||||
rotateToHeading: false,
|
||||
};
|
||||
showBullseyes: false,
|
||||
showBRAA: false,
|
||||
} as ObjectIconOptions;
|
||||
}
|
||||
|
||||
appendContextActions(contextActionSet: ContextActionSet) {
|
||||
|
||||
@ -23,6 +23,7 @@ import { UnitDataFileImport } from "./importexport/unitdatafileimport";
|
||||
import { CoalitionCircle } from "../map/coalitionarea/coalitioncircle";
|
||||
import { ContextActionSet } from "./contextactionset";
|
||||
import {
|
||||
AWACSReferenceChangedEvent,
|
||||
CommandModeOptionsChangedEvent,
|
||||
ContactsUpdatedEvent,
|
||||
HotgroupsChangedEvent,
|
||||
@ -49,6 +50,7 @@ export class UnitsManager {
|
||||
#unitDataImport!: UnitDataFileImport;
|
||||
#unitDatabase: UnitDatabase;
|
||||
#protectionCallback: (units: Unit[]) => void = (units) => {};
|
||||
#AWACSReference: Unit | null = null;
|
||||
|
||||
constructor() {
|
||||
this.#unitDatabase = new UnitDatabase();
|
||||
@ -1125,15 +1127,15 @@ export class UnitsManager {
|
||||
units.forEach((unit: Unit) => unit.setHotgroup(hotgroup));
|
||||
this.#showActionMessage(units, `added to hotgroup ${hotgroup}`);
|
||||
|
||||
let hotgroups: { [key: number]: number } = {};
|
||||
let hotgroups: { [key: number]: Unit[] } = {};
|
||||
for (let ID in this.#units) {
|
||||
const unit = this.#units[ID];
|
||||
if (unit.getAlive() && !unit.getHuman()) {
|
||||
const hotgroup = unit.getHotgroup();
|
||||
if (hotgroup) {
|
||||
if (!(hotgroup in hotgroups)) {
|
||||
hotgroups[hotgroup] = 1;
|
||||
} else hotgroups[hotgroup] += 1;
|
||||
hotgroups[hotgroup] = [unit];
|
||||
} else hotgroups[hotgroup].push(unit);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1544,6 +1546,15 @@ export class UnitsManager {
|
||||
this.#protectionCallback(this.getSelectedUnits());
|
||||
}
|
||||
|
||||
setAWACSReference(ID) {
|
||||
this.#AWACSReference = this.#units[ID] ?? null;
|
||||
AWACSReferenceChangedEvent.dispatch(this.#AWACSReference)
|
||||
}
|
||||
|
||||
getAWACSReference() {
|
||||
return this.#AWACSReference;
|
||||
}
|
||||
|
||||
/***********************************************/
|
||||
#onUnitSelection(unit: Unit) {
|
||||
if (this.getSelectedUnits().length > 0) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user