DCSOlympus/frontend/react/src/ui/panels/unitcontrolmenu.tsx
Dogma c3fb3fa9c1 Updates to control menu
- Updated UI for control menu
2024-04-14 23:20:30 +10:00

397 lines
15 KiB
TypeScript

import React, { useState } from "react";
import { Menu } from "./components/menu";
import { Unit } from "../../unit/unit";
import { OlLabelToggle } from "../components/ollabeltoggle";
import { OlRangeSlider } from "../components/olrangeslider";
import { getApp } from "../../olympusapp";
import { OlButtonGroup, OlButtonGroupItem } from "../components/olbuttongroup";
import { ROEs, emissionsCountermeasures, reactionsToThreat } from "../../constants/constants";
import { OlToggle } from "../components/oltoggle";
import { OlCoalitionToggle } from "../components/olcoalitiontoggle";
import { olButtonsEmissionsAttack, olButtonsEmissionsDefend, olButtonsEmissionsFree, olButtonsEmissionsSilent, olButtonsIntensity1, olButtonsIntensity2, olButtonsIntensity3, olButtonsRoeDesignated, olButtonsRoeFree, olButtonsRoeHold, olButtonsRoeReturn, olButtonsScatter1, olButtonsScatter2, olButtonsScatter3, olButtonsThreatEvade, olButtonsThreatManoeuvre, olButtonsThreatNone, olButtonsThreatPassive } from "../components/olicons";
import { Coalition } from "../../types/types";
import { ftToM, knotsToMs, mToFt, msToKnots } from "../../other/utils";
export function UnitControlMenu() {
var [open, setOpen] = useState(false);
var [selectedUnits, setSelectedUnits] = useState([] as Unit[]);
var [selectedUnitsData, setSelectedUnitsData] = useState({
desiredAltitude: undefined as undefined | number,
desiredAltitudeType: undefined as undefined | string,
desiredSpeed: undefined as undefined | number,
desiredSpeedType: undefined as undefined | string,
ROE: undefined as undefined | string,
reactionToThreat: undefined as undefined | string,
emissionsCountermeasures: undefined as undefined | string,
shotsScatter: undefined as undefined | number,
shotsIntensity: undefined as undefined | number,
operateAs: undefined as undefined | string,
followRoads: undefined as undefined | boolean,
isActiveAWACS: undefined as undefined | boolean,
isActiveTanker: undefined as undefined | boolean,
onOff: undefined as undefined | boolean
});
/* */
const minAltitude = 0;
const maxAltitude = getApp()?.getUnitsManager()?.getSelectedUnitsCategories().every((category) => { return category === 'Helicopter'}) ? 20000 : 60000;
const altitudeStep = getApp()?.getUnitsManager()?.getSelectedUnitsCategories().every((category) => { return category === 'Helicopter'}) ? 100 : 500;
const minSpeed = 0;
const maxSpeed = getApp()?.getUnitsManager()?.getSelectedUnitsCategories().every((category) => { return category === 'Helicopter'}) ? 200 : 800;
const speedStep = getApp()?.getUnitsManager()?.getSelectedUnitsCategories().every((category) => { return category === 'Helicopter'}) ? 5 : 10;;
/* When a unit is selected, open the menu */
document.addEventListener("unitsSelection", (ev: CustomEventInit) => {
setOpen(true);
setSelectedUnits(ev.detail as Unit[]);
updateData();
})
/* When a unit is deselected, refresh the view */
document.addEventListener("unitDeselection", (ev: CustomEventInit) => {
/* TODO add delay to avoid doing it too many times */
updateData();
})
/* When all units are selected clean the view */
document.addEventListener("clearSelection", () => {
setOpen(false);
setSelectedUnits([])
})
/* 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(); },
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(); },
} as { [key in keyof (typeof selectedUnitsData)]: (unit: Unit) => void }
var updatedData = selectedUnitsData;
Object.entries(getters).forEach(([key, getter]) => {
updatedData[key] = getApp()?.getUnitsManager()?.getSelectedUnitsVariable(getter);
});
setSelectedUnitsData(updatedData);
}
/* Count how many units are selected of each type, divided by coalition */
var unitOccurences = {
blue: {},
red: {},
neutral: {}
}
selectedUnits.forEach((unit) => {
if (!(unit.getName() in unitOccurences[unit.getCoalition()]))
unitOccurences[unit.getCoalition()][unit.getName()] = 1;
else
unitOccurences[unit.getCoalition()][unit.getName()]++;
})
const selectedCategories = getApp()?.getUnitsManager()?.getSelectedUnitsCategories() ?? [];
return <Menu
open={open}
title="Unit control menu"
onClose={() => { }}
>
{/* Units list */}
<div className="dark:bg-[#243141] h-fit p-0 flex flex-col gap-0">
<div>
{
<>
{
['blue', 'red', 'neutral'].map((coalition) => {
return Object.keys(unitOccurences[coalition]).map((name) => {
return <div data-coalition={coalition} className="flex justify-between content-center border-l-4 data-[coalition='blue']:border-blue-500 data-[coalition='neutral']:border-gray-500 data-[coalition='red']:border-red-500 p-4">
<span className="dark:text-gray-300 font-semibold my-auto">
{name}
</span>
<span className="dark:text-gray-500 my-auto font-bold">
x{unitOccurences[coalition][name]}
</span>
</div>
})
})
}
</>
}
</div>
</div>
<div className="p-5 flex flex-col gap-5">
{/* Altitude selector */
selectedCategories.every((category) => { return ['Aircraft', 'Helicopter'].includes(category) }) && <div>
<div className="flex flex-row content-center justify-between">
<div className="flex flex-col">
<span className="font-normal dark:text-white">Altitude</span>
<span
data-flash={selectedUnitsData.desiredAltitude === undefined}
className="data-[flash='true']:animate-pulse dark:text-blue-500 font-bold">{selectedUnitsData.desiredAltitude !== undefined ? (selectedUnitsData.desiredAltitude + " FT") : "Different values"}
</span>
</div>
<OlLabelToggle
toggled={selectedUnitsData.desiredAltitudeType === undefined ? undefined : selectedUnitsData.desiredAltitudeType === "AGL"}
leftLabel={"AGL"}
rightLabel={"ASL"}
onClick={() => {
selectedUnits.forEach((unit) => {
unit.setAltitudeType((selectedUnitsData.desiredAltitudeType === "ASL") ? "AGL" : "ASL");
setSelectedUnitsData({
...selectedUnitsData,
desiredAltitudeType: (selectedUnitsData.desiredAltitudeType === "ASL") ? "AGL" : "ASL"
})
})
}} />
</div>
<OlRangeSlider
onChange={(ev) => {
selectedUnits.forEach((unit) => {
unit.setAltitude(ftToM(Number(ev.target.value)));
setSelectedUnitsData({
...selectedUnitsData,
desiredAltitude: Number(ev.target.value)
})
})
}}
value={selectedUnitsData.desiredAltitude}
min={minAltitude}
max={maxAltitude}
step={altitudeStep}
/>
</div>
}
{/* Airspeed selector */}
<div>
<div className="flex flex-row content-center justify-between">
<div className="flex flex-col">
<span className="font-normal dark:text-white">Speed</span>
<span
data-flash={selectedUnitsData.desiredSpeed === undefined}
className="data-[flash='true']:animate-pulse dark:text-blue-500 font-bold">{selectedUnitsData.desiredSpeed !== undefined ? (selectedUnitsData.desiredSpeed + " KTS") : "Different values"}
</span>
</div>
<OlLabelToggle
toggled={selectedUnitsData.desiredSpeedType === undefined ? undefined : selectedUnitsData.desiredSpeedType === "GS"}
leftLabel={"GS"}
rightLabel={"CAS"}
onClick={() => {
selectedUnits.forEach((unit) => {
unit.setSpeedType((selectedUnitsData.desiredSpeedType === "CAS") ? "GS" : "CAS");
setSelectedUnitsData({
...selectedUnitsData,
desiredSpeedType: (selectedUnitsData.desiredSpeedType === "CAS") ? "GS" : "CAS"
})
})
}}
/>
</div>
<OlRangeSlider
onChange={(ev) => {
selectedUnits.forEach((unit) => {
unit.setSpeed(knotsToMs(Number(ev.target.value)));
setSelectedUnitsData({
...selectedUnitsData,
desiredSpeed: Number(ev.target.value)
})
})
}}
value={selectedUnitsData.desiredSpeed}
min={minSpeed}
max={maxSpeed}
step={speedStep}
/>
</div>
<div className="flex flex-col gap-2">
<span className="font-normal dark:text-white">Rules of engagement</span>
<OlButtonGroup>
{
[olButtonsRoeHold, olButtonsRoeReturn, olButtonsRoeDesignated, olButtonsRoeFree].map((icon, idx) => {
return <OlButtonGroupItem
onClick={() => {
selectedUnits.forEach((unit) => {
unit.setROE(ROEs[idx]);
setSelectedUnitsData({
...selectedUnitsData,
ROE: ROEs[idx]
})
})
}}
active={selectedUnitsData.ROE === ROEs[idx]}
icon={icon} />
})
}
</OlButtonGroup>
</div>
{
selectedCategories.every((category) => { return ['Aircraft', 'Helicopter'].includes(category) }) && <> <div className="flex flex-col gap-2">
<span className="font-normal dark:text-white">Threat reaction</span>
<OlButtonGroup>
{
[olButtonsThreatNone, olButtonsThreatPassive, olButtonsThreatManoeuvre, olButtonsThreatEvade].map((icon, idx) => {
return <OlButtonGroupItem
onClick={() => {
selectedUnits.forEach((unit) => {
unit.setReactionToThreat(reactionsToThreat[idx]);
setSelectedUnitsData({
...selectedUnitsData,
reactionToThreat: reactionsToThreat[idx]
})
})
}}
active={selectedUnitsData.reactionToThreat === reactionsToThreat[idx]}
icon={icon} />
})
}
</OlButtonGroup>
</div>
<div className="flex flex-col gap-2">
<span className="font-normal dark:text-white">Radar and ECM</span>
<OlButtonGroup>
{
[olButtonsEmissionsSilent, olButtonsEmissionsDefend, olButtonsEmissionsAttack, olButtonsEmissionsFree].map((icon, idx) => {
return <OlButtonGroupItem
onClick={() => {
selectedUnits.forEach((unit) => {
unit.setEmissionsCountermeasures(emissionsCountermeasures[idx]);
setSelectedUnitsData({
...selectedUnitsData,
emissionsCountermeasures: emissionsCountermeasures[idx]
})
})
}}
active={selectedUnitsData.emissionsCountermeasures === emissionsCountermeasures[idx]}
icon={icon} />
})
}
</OlButtonGroup>
</div>
</>
}
{
getApp()?.getUnitsManager()?.getSelectedUnitsVariable((unit) => { return unit.isTanker() }) &&
<div className="flex content-center justify-between">
<span className="font-normal dark:text-white"> Act as tanker </span>
<OlToggle toggled={selectedUnitsData.isActiveTanker} onClick={() => {
selectedUnits.forEach((unit) => {
unit.setAdvancedOptions(!selectedUnitsData.isActiveTanker, unit.getIsActiveAWACS(), unit.getTACAN(), unit.getRadio(), unit.getGeneralSettings());
setSelectedUnitsData({
...selectedUnitsData,
isActiveTanker: !selectedUnitsData.isActiveTanker
})
})
}} />
</div>
}
{
getApp()?.getUnitsManager()?.getSelectedUnitsVariable((unit) => { return unit.isAWACS() }) &&
<div className="flex content-center justify-between">
<span className="font-normal dark:text-white"> Act as AWACS </span>
<OlToggle toggled={selectedUnitsData.isActiveAWACS} onClick={() => {
selectedUnits.forEach((unit) => {
unit.setAdvancedOptions(unit.getIsActiveTanker(), !selectedUnitsData.isActiveAWACS, unit.getTACAN(), unit.getRadio(), unit.getGeneralSettings());
setSelectedUnitsData({
...selectedUnitsData,
isActiveAWACS: !selectedUnitsData.isActiveAWACS
})
})
}} />
</div>
}
{
selectedCategories.every((category) => { return ['GroundUnit', 'NavyUnit'].includes(category) }) && <> <div className="flex flex-col gap-2">
<span className="font-normal dark:text-white">Shots scatter</span>
<OlButtonGroup>
{
[olButtonsScatter1, olButtonsScatter2, olButtonsScatter3].map((icon, idx) => {
return <OlButtonGroupItem
onClick={() => {
selectedUnits.forEach((unit) => {
unit.setShotsScatter((idx + 1));
setSelectedUnitsData({
...selectedUnitsData,
shotsScatter: (idx + 1)
})
})
}}
active={selectedUnitsData.shotsScatter === (idx + 1)}
icon={icon} />
})
}
</OlButtonGroup>
</div>
<div className="flex flex-col gap-2">
<span className="font-normal dark:text-white">Shots intensity</span>
<OlButtonGroup>
{
[olButtonsIntensity1, olButtonsIntensity2, olButtonsIntensity3].map((icon, idx) => {
return <OlButtonGroupItem
onClick={() => {
selectedUnits.forEach((unit) => {
unit.setShotsIntensity((idx + 1));
setSelectedUnitsData({
...selectedUnitsData,
shotsIntensity: (idx + 1)
})
})
}}
active={selectedUnitsData.shotsIntensity === (idx + 1)}
icon={icon} />
})
}
</OlButtonGroup>
</div>
<div className="flex content-center justify-between">
<span className="font-normal dark:text-white"> Operate as </span>
<OlCoalitionToggle coalition={selectedUnitsData.operateAs as Coalition} onClick={() => {
selectedUnits.forEach((unit) => {
unit.setOperateAs(selectedUnitsData.operateAs === 'blue' ? 'red' : 'blue');
setSelectedUnitsData({
...selectedUnitsData,
operateAs: selectedUnitsData.operateAs === 'blue' ? 'red' : 'blue'
})
})
}} />
</div>
<div className="flex content-center justify-between">
<span className="font-normal dark:text-white"> Follow roads </span>
<OlToggle toggled={selectedUnitsData.followRoads} onClick={() => {
selectedUnits.forEach((unit) => {
unit.setFollowRoads(!selectedUnitsData.followRoads);
setSelectedUnitsData({
...selectedUnitsData,
followRoads: !selectedUnitsData.followRoads
})
})
}} />
</div>
<div className="flex content-center justify-between">
<span className="font-normal dark:text-white"> Unit active </span>
<OlToggle toggled={selectedUnitsData.onOff} onClick={() => {
selectedUnits.forEach((unit) => {
unit.setOnOff(!selectedUnitsData.onOff);
setSelectedUnitsData({
...selectedUnitsData,
onOff: !selectedUnitsData.onOff
})
})
}} />
</div>
</>
}
</div>
</Menu>
}