DCSOlympus/frontend/react/src/ui/panels/unitspawnmenu.tsx
2024-08-29 15:31:16 +02:00

290 lines
9.5 KiB
TypeScript

import React, { useState, useEffect } from "react";
import { OlUnitSummary } from "../components/olunitsummary";
import { OlCoalitionToggle } from "../components/olcoalitiontoggle";
import { OlNumberInput } from "../components/olnumberinput";
import { OlLabelToggle } from "../components/ollabeltoggle";
import { OlRangeSlider } from "../components/olrangeslider";
import { OlDropdownItem, OlDropdown } from "../components/oldropdown";
import { LoadoutBlueprint, UnitBlueprint } from "../../interfaces";
import { Coalition } from "../../types/types";
import { getApp } from "../../olympusapp";
import { IDLE, SPAWN_UNIT } from "../../constants/constants";
import { ftToM, getUnitCategoryByBlueprint } from "../../other/utils";
import { LatLng } from "leaflet";
import { Airbase } from "../../mission/airbase";
export function UnitSpawnMenu(props: { blueprint: UnitBlueprint; spawnAtLocation: boolean; airbase?: Airbase | null; coalition?: Coalition }) {
/* Compute the min and max values depending on the unit type */
const minNumber = 1;
const maxNumber = 4;
const minAltitude = 0;
const maxAltitude = 30000;
const altitudeStep = 500;
/* State initialization */
const [spawnCoalition, setSpawnCoalition] = useState("blue" as Coalition);
const [spawnNumber, setSpawnNumber] = useState(1);
const [spawnRole, setSpawnRole] = useState("");
const [spawnLoadoutName, setSpawnLoadout] = useState("");
const [spawnAltitude, setSpawnAltitude] = useState((maxAltitude - minAltitude) / 2);
const [spawnAltitudeType, setSpawnAltitudeType] = useState(false);
/* When the menu is opened show the unit preview on the map as a cursor */
useEffect(() => {
if (props.coalition && props.coalition !== spawnCoalition) {
setSpawnCoalition(props.coalition);
}
if (props.spawnAtLocation) {
if (props.blueprint !== null) {
getApp()
?.getMap()
?.setState(SPAWN_UNIT, {
spawnRequestTable: {
category: getUnitCategoryByBlueprint(props.blueprint),
unit: {
unitType: props.blueprint.name,
location: new LatLng(0, 0), // This will be filled when the user clicks on the map to spawn the unit
skill: "High",
liveryID: "",
altitude: ftToM(spawnAltitude),
loadout:
props.blueprint.loadouts?.find((loadout) => {
return loadout.name === spawnLoadoutName;
})?.code ?? "",
},
coalition: spawnCoalition,
},
});
} else {
if (getApp()?.getMap()?.getState() === SPAWN_UNIT) getApp().getMap().setState(IDLE);
}
}
});
function spawnAtAirbase() {
getApp()
.getUnitsManager()
.spawnUnits(
getUnitCategoryByBlueprint(props.blueprint),
[
{
unitType: props.blueprint.name,
location: new LatLng(0, 0), // Not relevant spawning at airbase
skill: "High",
liveryID: "",
altitude: 0,
loadout:
props.blueprint.loadouts?.find((loadout) => {
return loadout.name === spawnLoadoutName;
})?.code ?? "",
},
],
props.coalition,
false,
props.airbase?.getName()
);
}
/* Get a list of all the roles */
const roles: string[] = [];
(props.blueprint as UnitBlueprint).loadouts?.forEach((loadout) => {
loadout.roles.forEach((role) => {
!roles.includes(role) && roles.push(role);
});
});
/* Initialize the role */
spawnRole === "" && roles.length > 0 && setSpawnRole(roles[0]);
/* Get a list of all the loadouts */
const loadouts: LoadoutBlueprint[] = [];
(props.blueprint as UnitBlueprint).loadouts?.forEach((loadout) => {
loadout.roles.includes(spawnRole) && loadouts.push(loadout);
});
/* Initialize the loadout */
spawnLoadoutName === "" && loadouts.length > 0 && setSpawnLoadout(loadouts[0].name);
const spawnLoadout = props.blueprint.loadouts?.find((loadout) => {
return loadout.name === spawnLoadoutName;
});
return (
<div className="flex flex-col">
<OlUnitSummary blueprint={props.blueprint} coalition={spawnCoalition} />
<div className="flex h-fit flex-col gap-5 px-5 pb-8 pt-6">
<div
className={`
inline-flex w-full flex-row content-center justify-between
`}
>
{!props.coalition && (
<OlCoalitionToggle
coalition={spawnCoalition}
onClick={() => {
spawnCoalition === "blue" && setSpawnCoalition("neutral");
spawnCoalition === "neutral" && setSpawnCoalition("red");
spawnCoalition === "red" && setSpawnCoalition("blue");
}}
/>
)}
<OlNumberInput
value={spawnNumber}
min={minNumber}
max={maxNumber}
onDecrease={() => {
setSpawnNumber(Math.max(minNumber, spawnNumber - 1));
}}
onIncrease={() => {
setSpawnNumber(Math.min(maxNumber, spawnNumber + 1));
}}
onChange={(ev) => {
!isNaN(Number(ev.target.value)) && setSpawnNumber(Math.max(minNumber, Math.min(maxNumber, Number(ev.target.value))));
}}
/>
</div>
<div>
<div
className={`
flex flex-row content-center items-center justify-between
`}
>
<div className="flex flex-col">
<span
className={`
font-normal
dark:text-white
`}
>
Altitude
</span>
<span
className={`
font-bold
dark:text-blue-500
`}
>{`${Intl.NumberFormat("en-US").format(spawnAltitude)} FT`}</span>
</div>
<OlLabelToggle toggled={spawnAltitudeType} leftLabel={"AGL"} rightLabel={"ASL"} onClick={() => setSpawnAltitudeType(!spawnAltitudeType)} />
</div>
<OlRangeSlider
onChange={(ev) => setSpawnAltitude(Number(ev.target.value))}
value={spawnAltitude}
min={minAltitude}
max={maxAltitude}
step={altitudeStep}
/>
</div>
<div>
<div className="flex flex-row content-center justify-between">
<span
className={`
h-8 font-normal
dark:text-white
`}
>
Role
</span>
</div>
<OlDropdown label={spawnRole} className="w-full">
{roles.map((role) => {
return (
<OlDropdownItem
onClick={() => {
setSpawnRole(role);
setSpawnLoadout("");
}}
className={`w-full`}
>
{role}
</OlDropdownItem>
);
})}
</OlDropdown>
</div>
<div>
<div className="flex flex-row content-center justify-between">
<span
className={`
h-8 font-normal
dark:text-white
`}
>
Weapons
</span>
</div>
<OlDropdown label={spawnLoadoutName} className={`w-full w-max-full`}>
{loadouts.map((loadout) => {
return (
<OlDropdownItem
onClick={() => {
setSpawnLoadout(loadout.name);
}}
className={`w-full`}
>
<span
className={`
w-full overflow-hidden text-ellipsis text-nowrap text-left
w-max-full
`}
>
{loadout.name}
</span>
</OlDropdownItem>
);
})}
</OlDropdown>
</div>
</div>
{spawnLoadout && spawnLoadout.items.length > 0 && (
<div
className={`
flex h-fit flex-col gap-1 p-4
dark:bg-olympus-200/30
`}
>
{spawnLoadout.items.map((item) => {
return (
<div className="flex content-center gap-2">
<div
className={`
my-auto w-6 min-w-6 rounded-full py-0.5 text-center text-sm
font-bold text-gray-500
dark:bg-[#17212D]
`}
>
{item.quantity}
</div>
<div
className={`
my-auto overflow-hidden text-ellipsis text-nowrap text-sm
dark:text-gray-300
`}
>
{item.name}
</div>
</div>
);
})}
</div>
)}
{!props.spawnAtLocation && (
<button
type="button"
className={`
m-2 rounded-lg bg-blue-700 px-5 py-2.5 text-sm font-medium
text-white
dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800
focus:outline-none focus:ring-4 focus:ring-blue-300
hover:bg-blue-800
`}
onClick={() => {
spawnAtAirbase();
}}
>
Spawn
</button>
)}
</div>
);
}