Started adding AWACS panel

This commit is contained in:
Davide Passoni
2024-11-23 17:18:16 +01:00
parent 897afb2fef
commit 1791eaa37d
23 changed files with 344 additions and 69 deletions

View File

@@ -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);
}
}}
>

View 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>
);
}

View File

@@ -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
`}
>

View File

@@ -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) => {

View File

@@ -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>

View File

@@ -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
`}
>

View File

@@ -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>
);
})}

View File

@@ -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={() => {

View File

@@ -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

View File

@@ -12,7 +12,6 @@ import {
emissionsCountermeasures,
maxAltitudeValues,
maxSpeedValues,
minAltitudeValues,
reactionsToThreat,
speedIncrements,
} from "../../constants/constants";

View File

@@ -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 />