mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
Large rework of events and state
This commit is contained in:
@@ -41,7 +41,7 @@ export function OlAccordion(props: { title: string; open: boolean, onClick: () =
|
||||
>
|
||||
<span className="font-normal">{props.title}</span>
|
||||
<svg
|
||||
data-open={open}
|
||||
data-open={props.open}
|
||||
className={`
|
||||
h-3 w-3 shrink-0 -rotate-180 transition-transform
|
||||
dark:text-olympus-50
|
||||
|
||||
@@ -7,7 +7,7 @@ export function ErrorCallout(props: { title?: string; description?: string }) {
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
flex w-fit w-full flex-row gap-2 rounded-md border-[1px] border-red-800
|
||||
flex w-full flex-row gap-2 rounded-md border-[1px] border-red-800
|
||||
bg-gray-300 p-4 text-red-700
|
||||
dark:bg-gray-800 dark:text-red-500
|
||||
`}
|
||||
@@ -19,13 +19,9 @@ export function ErrorCallout(props: { title?: string; description?: string }) {
|
||||
`}
|
||||
>
|
||||
{props.title}
|
||||
<div
|
||||
className={`
|
||||
flex whitespace-pre-line text-xs font-medium text-red-500
|
||||
`}
|
||||
>
|
||||
{props.description}
|
||||
</div>
|
||||
<div className={`
|
||||
flex whitespace-pre-line text-xs font-medium text-red-500
|
||||
`}>{props.description}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -36,7 +32,7 @@ export function InfoCallout(props: { title?: string; description?: string }) {
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
flex w-fit w-full flex-row gap-2 rounded-md border-[1px] border-blue-800
|
||||
flex w-full flex-row gap-2 rounded-md border-[1px] border-blue-800
|
||||
bg-gray-300 p-4 text-blue-400
|
||||
dark:bg-gray-800 dark:text-blue-400
|
||||
`}
|
||||
@@ -48,15 +44,9 @@ export function InfoCallout(props: { title?: string; description?: string }) {
|
||||
`}
|
||||
>
|
||||
{props.title}
|
||||
{props.description && (
|
||||
<div
|
||||
className={`
|
||||
flex whitespace-pre-line text-xs font-medium
|
||||
`}
|
||||
>
|
||||
{props.description}
|
||||
</div>
|
||||
)}
|
||||
{props.description && <div className={`
|
||||
flex whitespace-pre-line text-xs font-medium
|
||||
`}>{props.description}</div>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -67,7 +57,7 @@ export function CommandCallout(props: { coalition?: string }) {
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
flex w-fit w-full flex-row gap-2 rounded-md border-[1px] border-gray-700
|
||||
flex w-full flex-row gap-2 rounded-md border-[1px] border-gray-700
|
||||
bg-gray-300 p-4 text-gray-400
|
||||
dark:bg-gray-800 dark:text-gray-400
|
||||
`}
|
||||
|
||||
@@ -12,7 +12,6 @@ import { SelectionClearedEvent } from "../../events";
|
||||
export function MapContextMenu(props: {}) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [contextActionsSet, setContextActionsSet] = useState(new ContextActionSet());
|
||||
const [activeContextAction, setActiveContextAction] = useState(null as null | ContextAction);
|
||||
const [xPosition, setXPosition] = useState(0);
|
||||
const [yPosition, setYPosition] = useState(0);
|
||||
const [latLng, setLatLng] = useState(null as null | LatLng);
|
||||
@@ -107,9 +106,7 @@ export function MapContextMenu(props: {}) {
|
||||
<>
|
||||
<div
|
||||
ref={contentRef}
|
||||
className={`
|
||||
absolute flex min-w-80 gap-2 rounded-md bg-olympus-600
|
||||
`}
|
||||
className={`absolute flex min-w-80 gap-2 rounded-md bg-olympus-600`}
|
||||
>
|
||||
<div
|
||||
className={`
|
||||
|
||||
@@ -14,6 +14,7 @@ export function LoginModal(props: {
|
||||
onContinue: (username: string) => void;
|
||||
onBack: () => void;
|
||||
}) {
|
||||
// TODO: add warning if not in secure context and some features are disabled
|
||||
const [password, setPassword] = useState("");
|
||||
const [displayName, setDisplayName] = useState("Game Master");
|
||||
|
||||
|
||||
@@ -6,11 +6,13 @@ import { AppStateChangedEvent } from "../../events";
|
||||
|
||||
export function ControlsPanel(props: {}) {
|
||||
const [controls, setControls] = useState(
|
||||
null as {
|
||||
actions: (string | number | IconDefinition)[];
|
||||
target: IconDefinition;
|
||||
text: string;
|
||||
}[] | null
|
||||
null as
|
||||
| {
|
||||
actions: (string | number | IconDefinition)[];
|
||||
target: IconDefinition;
|
||||
text: string;
|
||||
}[]
|
||||
| null
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -20,9 +22,7 @@ export function ControlsPanel(props: {}) {
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
AppStateChangedEvent.on(() => {
|
||||
setControls(getApp().getMap().getCurrentControls());
|
||||
});
|
||||
AppStateChangedEvent.on(() => setControls(getApp().getMap().getCurrentControls()));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
@@ -53,9 +53,14 @@ export function ControlsPanel(props: {}) {
|
||||
return (
|
||||
<div key={idx} className="flex gap-1">
|
||||
<div>
|
||||
{typeof action === "string" || typeof action === "number" ? action : <FontAwesomeIcon icon={action} className={`
|
||||
my-auto ml-auto
|
||||
`} />}
|
||||
{typeof action === "string" || typeof action === "number" ? (
|
||||
action
|
||||
) : (
|
||||
<FontAwesomeIcon
|
||||
icon={action}
|
||||
className={`my-auto ml-auto`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{idx < control.actions.length - 1 && typeof control.actions[idx + 1] === "string" && <div>+</div>}
|
||||
{idx < control.actions.length - 1 && typeof control.actions[idx + 1] === "number" && <div>x</div>}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Menu } from "./components/menu";
|
||||
import { FaQuestionCircle, FaTrash } from "react-icons/fa";
|
||||
import { FaTrash } from "react-icons/fa";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { OlStateButton } from "../components/olstatebutton";
|
||||
import { faDrawPolygon } from "@fortawesome/free-solid-svg-icons";
|
||||
@@ -12,11 +12,13 @@ import { OlCheckbox } from "../components/olcheckbox";
|
||||
import { Coalition } from "../../types/types";
|
||||
import { OlRangeSlider } from "../components/olrangeslider";
|
||||
import { CoalitionCircle } from "../../map/coalitionarea/coalitioncircle";
|
||||
import { DrawSubState, NO_SUBSTATE, OlympusState } from "../../constants/constants";
|
||||
import { StateConsumer } from "../../statecontext";
|
||||
import { CoalitionAreaSelectedEvent } from "../../events";
|
||||
import { DrawSubState, NO_SUBSTATE, OlympusState, OlympusSubState } from "../../constants/constants";
|
||||
import { AppStateChangedEvent, CoalitionAreaSelectedEvent } from "../../events";
|
||||
import { UnitBlueprint } from "../../interfaces";
|
||||
|
||||
export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
|
||||
const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED);
|
||||
const [appSubState, setAppSubState] = useState(NO_SUBSTATE as OlympusSubState);
|
||||
const [activeCoalitionArea, setActiveCoalitionArea] = useState(null as null | CoalitionPolygon | CoalitionCircle);
|
||||
const [areaCoalition, setAreaCoalition] = useState("blue" as Coalition);
|
||||
const [IADSDensity, setIADSDensity] = useState(50);
|
||||
@@ -27,6 +29,29 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
|
||||
const [erasSelection, setErasSelection] = useState({});
|
||||
const [rangesSelection, setRangesSelection] = useState({});
|
||||
|
||||
useEffect(() => {
|
||||
AppStateChangedEvent.on((state, subState) => {
|
||||
setAppState(state);
|
||||
setAppSubState(subState);
|
||||
});
|
||||
}, []);
|
||||
|
||||
/* Get all the unique types and eras for groundunits */
|
||||
/* TODO move in effect */
|
||||
const blueprints = getApp()?.getUnitsManager().getDatabase().getBlueprints();
|
||||
let types: string[] = [];
|
||||
let eras: string[] = [];
|
||||
if (blueprints) {
|
||||
types = blueprints
|
||||
.filter((blueprint) => blueprint.category === "groundunit")
|
||||
.map((blueprint) => blueprint.type)
|
||||
.filter((type) => type !== undefined);
|
||||
eras = blueprints
|
||||
.filter((blueprint) => blueprint.category === "groundunit")
|
||||
.map((blueprint) => blueprint.era)
|
||||
.filter((era) => era !== undefined);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (getApp()) {
|
||||
// TODO
|
||||
@@ -49,258 +74,248 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<StateConsumer>
|
||||
{(appState) => (
|
||||
<Menu
|
||||
open={props.open}
|
||||
title="Draw"
|
||||
onClose={props.onClose}
|
||||
canBeHidden={true}
|
||||
showBackButton={activeCoalitionArea !== null}
|
||||
onBack={() => {
|
||||
getApp().setState(OlympusState.DRAW, DrawSubState.NO_SUBSTATE);
|
||||
}}
|
||||
>
|
||||
<>
|
||||
{appState.appState === OlympusState.DRAW && appState.appSubState !== DrawSubState.EDIT && (
|
||||
<div className="flex flex-col gap-2 p-6 text-sm text-gray-400">
|
||||
<OlStateButton
|
||||
className="!w-full"
|
||||
icon={faDrawPolygon}
|
||||
tooltip={"Add a new polygon"}
|
||||
checked={appState.appSubState === DrawSubState.DRAW_POLYGON}
|
||||
onClick={() => {
|
||||
if (appState.appSubState === DrawSubState.DRAW_POLYGON) getApp().setState(OlympusState.DRAW, DrawSubState.EDIT);
|
||||
else getApp().setState(OlympusState.DRAW, DrawSubState.DRAW_POLYGON);
|
||||
}}
|
||||
>
|
||||
<div className="text-sm">Add polygon</div>
|
||||
</OlStateButton>
|
||||
<OlStateButton
|
||||
className="!w-full"
|
||||
icon={faCircle}
|
||||
tooltip={"Add a new circle"}
|
||||
checked={appState.appSubState === DrawSubState.DRAW_CIRCLE}
|
||||
onClick={() => {
|
||||
if (appState.appSubState === DrawSubState.DRAW_CIRCLE) getApp().setState(OlympusState.DRAW, DrawSubState.EDIT);
|
||||
else getApp().setState(OlympusState.DRAW, DrawSubState.DRAW_CIRCLE);
|
||||
}}
|
||||
>
|
||||
<div className="text-sm">Add circle</div>
|
||||
</OlStateButton>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
<div>
|
||||
{activeCoalitionArea !== null && appState.appSubState === DrawSubState.EDIT && (
|
||||
<div className={`flex flex-col gap-4 py-4`}>
|
||||
<div
|
||||
className={`
|
||||
flex flex-col content-center justify-start gap-2 px-6
|
||||
text-gray-200
|
||||
`}
|
||||
>
|
||||
<div className="my-auto flex justify-between text-md">
|
||||
Area label
|
||||
<div
|
||||
className="rounded-md bg-red-800 p-2"
|
||||
onClick={() => {
|
||||
getApp().getMap().deleteCoalitionArea(activeCoalitionArea);
|
||||
setActiveCoalitionArea(null);
|
||||
}}
|
||||
>
|
||||
<FaTrash className={`text-gray-50`}></FaTrash>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
className={`
|
||||
block w-full flex-grow rounded-lg border border-gray-300
|
||||
bg-gray-50 p-2.5 text-sm text-gray-900
|
||||
dark:border-gray-600 dark:bg-gray-700 dark:text-white
|
||||
dark:placeholder-gray-400 dark:focus:border-blue-500
|
||||
dark:focus:ring-blue-500
|
||||
focus:border-blue-500 focus:ring-blue-500
|
||||
`}
|
||||
placeholder={activeCoalitionArea.getLabelText()}
|
||||
onInput={(ev) => activeCoalitionArea.setLabelText(ev.currentTarget.value)}
|
||||
></input>
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
flex content-center justify-start gap-4 px-6 text-gray-200
|
||||
`}
|
||||
>
|
||||
<div className="my-auto text-md">Coalition: </div>
|
||||
<OlCoalitionToggle
|
||||
coalition={areaCoalition}
|
||||
onClick={() => {
|
||||
let newCoalition = "";
|
||||
if (areaCoalition === "blue") newCoalition = "neutral";
|
||||
else if (areaCoalition === "neutral") newCoalition = "red";
|
||||
else if (areaCoalition === "red") newCoalition = "blue";
|
||||
setAreaCoalition(newCoalition as Coalition);
|
||||
activeCoalitionArea.setCoalition(newCoalition as Coalition);
|
||||
}}
|
||||
></OlCoalitionToggle>
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
flex flex-col gap-3 border-l-4 border-l-olympus-100
|
||||
bg-olympus-600 p-5
|
||||
`}
|
||||
>
|
||||
<div className={`
|
||||
border-b-2 border-b-olympus-100 pb-4 text-gray-300
|
||||
`}>Automatic IADS generation</div>
|
||||
<OlDropdown className="" label="Units types">
|
||||
{getApp()
|
||||
.getGroundUnitDatabase()
|
||||
.getTypes()
|
||||
.map((type, idx) => {
|
||||
if (!(type in typesSelection)) {
|
||||
typesSelection[type] = true;
|
||||
setTypesSelection(JSON.parse(JSON.stringify(typesSelection)));
|
||||
}
|
||||
|
||||
return (
|
||||
<OlDropdownItem key={idx} className={`flex gap-4`}>
|
||||
<OlCheckbox
|
||||
checked={typesSelection[type]}
|
||||
onChange={(ev) => {
|
||||
typesSelection[type] = ev.currentTarget.checked;
|
||||
setTypesSelection(JSON.parse(JSON.stringify(typesSelection)));
|
||||
}}
|
||||
/>
|
||||
<div>{type}</div>
|
||||
</OlDropdownItem>
|
||||
);
|
||||
})}
|
||||
</OlDropdown>
|
||||
<OlDropdown className="" label="Units eras">
|
||||
{getApp()
|
||||
.getGroundUnitDatabase()
|
||||
.getEras()
|
||||
.map((era) => {
|
||||
if (!(era in erasSelection)) {
|
||||
erasSelection[era] = true;
|
||||
setErasSelection(JSON.parse(JSON.stringify(erasSelection)));
|
||||
}
|
||||
|
||||
return (
|
||||
<OlDropdownItem className={`flex gap-4`}>
|
||||
<OlCheckbox
|
||||
checked={erasSelection[era]}
|
||||
onChange={(ev) => {
|
||||
erasSelection[era] = ev.currentTarget.checked;
|
||||
setErasSelection(JSON.parse(JSON.stringify(erasSelection)));
|
||||
}}
|
||||
/>
|
||||
<div>{era}</div>
|
||||
</OlDropdownItem>
|
||||
);
|
||||
})}
|
||||
</OlDropdown>
|
||||
<OlDropdown className="" label="Units ranges">
|
||||
{["Short range", "Medium range", "Long range"].map((range) => {
|
||||
if (!(range in rangesSelection)) {
|
||||
rangesSelection[range] = true;
|
||||
setRangesSelection(JSON.parse(JSON.stringify(rangesSelection)));
|
||||
}
|
||||
|
||||
return (
|
||||
<OlDropdownItem className={`flex gap-4`}>
|
||||
<OlCheckbox
|
||||
checked={rangesSelection[range]}
|
||||
onChange={(ev) => {
|
||||
rangesSelection[range] = ev.currentTarget.checked;
|
||||
setErasSelection(JSON.parse(JSON.stringify(rangesSelection)));
|
||||
}}
|
||||
/>
|
||||
<div>{range}</div>
|
||||
</OlDropdownItem>
|
||||
);
|
||||
})}
|
||||
</OlDropdown>
|
||||
<div>
|
||||
<div className="flex justify-between">
|
||||
<div className="text-gray-100">IADS Density</div>
|
||||
<div
|
||||
className={`
|
||||
font-bold
|
||||
dark:text-blue-500
|
||||
`}
|
||||
>
|
||||
{IADSDensity}%
|
||||
</div>
|
||||
</div>
|
||||
<OlRangeSlider
|
||||
value={IADSDensity}
|
||||
onChange={(ev) => {
|
||||
setIADSDensity(Number(ev.currentTarget.value));
|
||||
}}
|
||||
></OlRangeSlider>
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex justify-between">
|
||||
<div className="text-gray-100">IADS Distribution</div>
|
||||
<div
|
||||
className={`
|
||||
font-bold
|
||||
dark:text-blue-500
|
||||
`}
|
||||
>
|
||||
{IADSDistribution}%
|
||||
</div>
|
||||
</div>
|
||||
<OlRangeSlider
|
||||
value={IADSDistribution}
|
||||
onChange={(ev) => {
|
||||
setIADSDistribution(Number(ev.target.value));
|
||||
}}
|
||||
></OlRangeSlider>
|
||||
</div>
|
||||
<div className="flex content-center gap-4 text-gray-200">
|
||||
<OlCheckbox
|
||||
checked={forceCoalitionAppropriateUnits}
|
||||
onChange={() => {
|
||||
setForceCoalitionApproriateUnits(!forceCoalitionAppropriateUnits);
|
||||
}}
|
||||
/>
|
||||
Force coalition appropriate units
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className={`
|
||||
mb-2 me-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={() =>
|
||||
getApp()
|
||||
.getUnitsManager()
|
||||
.createIADS(
|
||||
activeCoalitionArea,
|
||||
typesSelection,
|
||||
erasSelection,
|
||||
rangesSelection,
|
||||
IADSDensity,
|
||||
IADSDistribution,
|
||||
forceCoalitionAppropriateUnits
|
||||
)
|
||||
}
|
||||
>
|
||||
Generate IADS
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<Menu
|
||||
open={props.open}
|
||||
title="Draw"
|
||||
onClose={props.onClose}
|
||||
canBeHidden={true}
|
||||
showBackButton={activeCoalitionArea !== null}
|
||||
onBack={() => {
|
||||
getApp().setState(OlympusState.DRAW, DrawSubState.NO_SUBSTATE);
|
||||
}}
|
||||
>
|
||||
<>
|
||||
{appState === OlympusState.DRAW && appSubState !== DrawSubState.EDIT && (
|
||||
<div className="flex flex-col gap-2 p-6 text-sm text-gray-400">
|
||||
<OlStateButton
|
||||
className="!w-full"
|
||||
icon={faDrawPolygon}
|
||||
tooltip={"Add a new polygon"}
|
||||
checked={appSubState === DrawSubState.DRAW_POLYGON}
|
||||
onClick={() => {
|
||||
if (appSubState === DrawSubState.DRAW_POLYGON) getApp().setState(OlympusState.DRAW, DrawSubState.EDIT);
|
||||
else getApp().setState(OlympusState.DRAW, DrawSubState.DRAW_POLYGON);
|
||||
}}
|
||||
>
|
||||
<div className="text-sm">Add polygon</div>
|
||||
</OlStateButton>
|
||||
<OlStateButton
|
||||
className="!w-full"
|
||||
icon={faCircle}
|
||||
tooltip={"Add a new circle"}
|
||||
checked={appSubState === DrawSubState.DRAW_CIRCLE}
|
||||
onClick={() => {
|
||||
if (appSubState === DrawSubState.DRAW_CIRCLE) getApp().setState(OlympusState.DRAW, DrawSubState.EDIT);
|
||||
else getApp().setState(OlympusState.DRAW, DrawSubState.DRAW_CIRCLE);
|
||||
}}
|
||||
>
|
||||
<div className="text-sm">Add circle</div>
|
||||
</OlStateButton>
|
||||
</div>
|
||||
</Menu>
|
||||
)}
|
||||
</StateConsumer>
|
||||
)}
|
||||
</>
|
||||
<div>
|
||||
{activeCoalitionArea !== null && appSubState === DrawSubState.EDIT && (
|
||||
<div className={`flex flex-col gap-4 py-4`}>
|
||||
<div
|
||||
className={`
|
||||
flex flex-col content-center justify-start gap-2 px-6
|
||||
text-gray-200
|
||||
`}
|
||||
>
|
||||
<div className="my-auto flex justify-between text-md">
|
||||
Area label
|
||||
<div
|
||||
className="rounded-md bg-red-800 p-2"
|
||||
onClick={() => {
|
||||
getApp().getMap().deleteCoalitionArea(activeCoalitionArea);
|
||||
setActiveCoalitionArea(null);
|
||||
}}
|
||||
>
|
||||
<FaTrash className={`text-gray-50`}></FaTrash>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
className={`
|
||||
block w-full flex-grow rounded-lg border border-gray-300
|
||||
bg-gray-50 p-2.5 text-sm text-gray-900
|
||||
dark:border-gray-600 dark:bg-gray-700 dark:text-white
|
||||
dark:placeholder-gray-400 dark:focus:border-blue-500
|
||||
dark:focus:ring-blue-500
|
||||
focus:border-blue-500 focus:ring-blue-500
|
||||
`}
|
||||
placeholder={activeCoalitionArea.getLabelText()}
|
||||
onInput={(ev) => activeCoalitionArea.setLabelText(ev.currentTarget.value)}
|
||||
></input>
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
flex content-center justify-start gap-4 px-6 text-gray-200
|
||||
`}
|
||||
>
|
||||
<div className="my-auto text-md">Coalition: </div>
|
||||
<OlCoalitionToggle
|
||||
coalition={areaCoalition}
|
||||
onClick={() => {
|
||||
let newCoalition = "";
|
||||
if (areaCoalition === "blue") newCoalition = "neutral";
|
||||
else if (areaCoalition === "neutral") newCoalition = "red";
|
||||
else if (areaCoalition === "red") newCoalition = "blue";
|
||||
setAreaCoalition(newCoalition as Coalition);
|
||||
activeCoalitionArea.setCoalition(newCoalition as Coalition);
|
||||
}}
|
||||
></OlCoalitionToggle>
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
flex flex-col gap-3 border-l-4 border-l-olympus-100
|
||||
bg-olympus-600 p-5
|
||||
`}
|
||||
>
|
||||
<div className={`
|
||||
border-b-2 border-b-olympus-100 pb-4 text-gray-300
|
||||
`}>Automatic IADS generation</div>
|
||||
<OlDropdown className="" label="Units types">
|
||||
{types.map((type, idx) => {
|
||||
if (!(type in typesSelection)) {
|
||||
typesSelection[type] = true;
|
||||
setTypesSelection(JSON.parse(JSON.stringify(typesSelection)));
|
||||
}
|
||||
|
||||
return (
|
||||
<OlDropdownItem key={idx} className={`flex gap-4`}>
|
||||
<OlCheckbox
|
||||
checked={typesSelection[type]}
|
||||
onChange={(ev) => {
|
||||
typesSelection[type] = ev.currentTarget.checked;
|
||||
setTypesSelection(JSON.parse(JSON.stringify(typesSelection)));
|
||||
}}
|
||||
/>
|
||||
<div>{type}</div>
|
||||
</OlDropdownItem>
|
||||
);
|
||||
})}
|
||||
</OlDropdown>
|
||||
<OlDropdown className="" label="Units eras">
|
||||
{eras.map((era) => {
|
||||
if (!(era in erasSelection)) {
|
||||
erasSelection[era] = true;
|
||||
setErasSelection(JSON.parse(JSON.stringify(erasSelection)));
|
||||
}
|
||||
|
||||
return (
|
||||
<OlDropdownItem className={`flex gap-4`}>
|
||||
<OlCheckbox
|
||||
checked={erasSelection[era]}
|
||||
onChange={(ev) => {
|
||||
erasSelection[era] = ev.currentTarget.checked;
|
||||
setErasSelection(JSON.parse(JSON.stringify(erasSelection)));
|
||||
}}
|
||||
/>
|
||||
<div>{era}</div>
|
||||
</OlDropdownItem>
|
||||
);
|
||||
})}
|
||||
</OlDropdown>
|
||||
<OlDropdown className="" label="Units ranges">
|
||||
{["Short range", "Medium range", "Long range"].map((range) => {
|
||||
if (!(range in rangesSelection)) {
|
||||
rangesSelection[range] = true;
|
||||
setRangesSelection(JSON.parse(JSON.stringify(rangesSelection)));
|
||||
}
|
||||
|
||||
return (
|
||||
<OlDropdownItem className={`flex gap-4`}>
|
||||
<OlCheckbox
|
||||
checked={rangesSelection[range]}
|
||||
onChange={(ev) => {
|
||||
rangesSelection[range] = ev.currentTarget.checked;
|
||||
setErasSelection(JSON.parse(JSON.stringify(rangesSelection)));
|
||||
}}
|
||||
/>
|
||||
<div>{range}</div>
|
||||
</OlDropdownItem>
|
||||
);
|
||||
})}
|
||||
</OlDropdown>
|
||||
<div>
|
||||
<div className="flex justify-between">
|
||||
<div className="text-gray-100">IADS Density</div>
|
||||
<div
|
||||
className={`
|
||||
font-bold
|
||||
dark:text-blue-500
|
||||
`}
|
||||
>
|
||||
{IADSDensity}%
|
||||
</div>
|
||||
</div>
|
||||
<OlRangeSlider
|
||||
value={IADSDensity}
|
||||
onChange={(ev) => {
|
||||
setIADSDensity(Number(ev.currentTarget.value));
|
||||
}}
|
||||
></OlRangeSlider>
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex justify-between">
|
||||
<div className="text-gray-100">IADS Distribution</div>
|
||||
<div
|
||||
className={`
|
||||
font-bold
|
||||
dark:text-blue-500
|
||||
`}
|
||||
>
|
||||
{IADSDistribution}%
|
||||
</div>
|
||||
</div>
|
||||
<OlRangeSlider
|
||||
value={IADSDistribution}
|
||||
onChange={(ev) => {
|
||||
setIADSDistribution(Number(ev.target.value));
|
||||
}}
|
||||
></OlRangeSlider>
|
||||
</div>
|
||||
<div className="flex content-center gap-4 text-gray-200">
|
||||
<OlCheckbox
|
||||
checked={forceCoalitionAppropriateUnits}
|
||||
onChange={() => {
|
||||
setForceCoalitionApproriateUnits(!forceCoalitionAppropriateUnits);
|
||||
}}
|
||||
/>
|
||||
Force coalition appropriate units
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className={`
|
||||
mb-2 me-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={() =>
|
||||
getApp()
|
||||
.getUnitsManager()
|
||||
.createIADS(
|
||||
activeCoalitionArea,
|
||||
typesSelection,
|
||||
erasSelection,
|
||||
rangesSelection,
|
||||
IADSDensity,
|
||||
IADSDistribution,
|
||||
forceCoalitionAppropriateUnits
|
||||
)
|
||||
}
|
||||
>
|
||||
Generate IADS
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,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 { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { StateConsumer } from "../../statecontext";
|
||||
import { OlDropdownItem, OlDropdown } from "../components/oldropdown";
|
||||
import { OlLabelToggle } from "../components/ollabeltoggle";
|
||||
import { getApp, IP, connectedToServer } from "../../olympusapp";
|
||||
@@ -18,12 +17,29 @@ import {
|
||||
olButtonsVisibilityOlympus,
|
||||
} from "../components/olicons";
|
||||
import { FaChevronLeft, FaChevronRight } from "react-icons/fa6";
|
||||
import { ConfigLoadedEvent, HiddenTypesChangedEvent, MapOptionsChangedEvent, MapSourceChangedEvent } from "../../events";
|
||||
import { MAP_HIDDEN_TYPES_DEFAULTS, MAP_OPTIONS_DEFAULTS } from "../../constants/constants";
|
||||
import { OlympusConfig } from "../../interfaces";
|
||||
|
||||
export function Header() {
|
||||
const [mapHiddenTypes, setMapHiddenTypes] = useState(MAP_HIDDEN_TYPES_DEFAULTS);
|
||||
const [mapOptions, setMapOptions] = useState(MAP_OPTIONS_DEFAULTS);
|
||||
const [mapSource, setMapSource] = useState("");
|
||||
const [mapSources, setMapSources] = useState([] as string[]);
|
||||
const [scrolledLeft, setScrolledLeft] = useState(true);
|
||||
const [scrolledRight, setScrolledRight] = useState(false);
|
||||
const [audioEnabled, setAudioEnabled] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
HiddenTypesChangedEvent.on((hiddenTypes) => setMapHiddenTypes({ ...hiddenTypes }));
|
||||
MapOptionsChangedEvent.on((mapOptions) => setMapOptions({ ...mapOptions }));
|
||||
MapSourceChangedEvent.on((source) => setMapSource(source));
|
||||
ConfigLoadedEvent.on((config: OlympusConfig) => {
|
||||
var sources = Object.keys(config.mapMirrors).concat(Object.keys(config.mapLayers));
|
||||
setMapSources(sources);
|
||||
});
|
||||
}, []);
|
||||
|
||||
/* Initialize the "scroll" position of the element */
|
||||
var scrollRef = useRef(null);
|
||||
useEffect(() => {
|
||||
@@ -42,8 +58,6 @@ export function Header() {
|
||||
}
|
||||
|
||||
return (
|
||||
<StateConsumer>
|
||||
{(appState) => (
|
||||
<nav
|
||||
className={`
|
||||
z-10 flex w-full gap-4 border-gray-200 bg-gray-300 px-3
|
||||
@@ -107,14 +121,12 @@ export function Header() {
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
flex h-fit flex-row items-center justify-start gap-1
|
||||
`}
|
||||
className={`flex h-fit flex-row items-center justify-start gap-1`}
|
||||
>
|
||||
<OlLockStateButton
|
||||
checked={!appState.mapOptions.protectDCSUnits}
|
||||
checked={!mapOptions.protectDCSUnits}
|
||||
onClick={() => {
|
||||
getApp().getMap().setOption("protectDCSUnits", !appState.mapOptions.protectDCSUnits);
|
||||
getApp().getMap().setOption("protectDCSUnits", !mapOptions.protectDCSUnits);
|
||||
}}
|
||||
tooltip="Lock/unlock protected units (from scripted mission)"
|
||||
/>
|
||||
@@ -130,9 +142,7 @@ export function Header() {
|
||||
</div>
|
||||
<div className={`h-8 w-0 border-l-[2px] border-gray-700`}></div>
|
||||
<div
|
||||
className={`
|
||||
flex h-fit flex-row items-center justify-start gap-1
|
||||
`}
|
||||
className={`flex h-fit flex-row items-center justify-start gap-1`}
|
||||
>
|
||||
{Object.entries({
|
||||
human: olButtonsVisibilityHuman,
|
||||
@@ -143,9 +153,9 @@ export function Header() {
|
||||
<OlRoundStateButton
|
||||
key={entry[0]}
|
||||
onClick={() => {
|
||||
getApp().getMap().setHiddenType(entry[0], !appState.mapHiddenTypes[entry[0]]);
|
||||
getApp().getMap().setHiddenType(entry[0], !mapHiddenTypes[entry[0]]);
|
||||
}}
|
||||
checked={!appState.mapHiddenTypes[entry[0]]}
|
||||
checked={!mapHiddenTypes[entry[0]]}
|
||||
icon={entry[1]}
|
||||
tooltip={"Hide/show " + entry[0] + " units"}
|
||||
/>
|
||||
@@ -154,27 +164,25 @@ export function Header() {
|
||||
</div>
|
||||
<div className={`h-8 w-0 border-l-[2px] border-gray-700`}></div>
|
||||
<div
|
||||
className={`
|
||||
flex h-fit flex-row items-center justify-start gap-1
|
||||
`}
|
||||
className={`flex h-fit flex-row items-center justify-start gap-1`}
|
||||
>
|
||||
<OlRoundStateButton
|
||||
onClick={() => getApp().getMap().setHiddenType("blue", !appState.mapHiddenTypes["blue"])}
|
||||
checked={!appState.mapHiddenTypes["blue"]}
|
||||
onClick={() => getApp().getMap().setHiddenType("blue", !mapHiddenTypes["blue"])}
|
||||
checked={!mapHiddenTypes["blue"]}
|
||||
icon={faFlag}
|
||||
className={"!text-blue-500"}
|
||||
tooltip={"Hide/show blue units"}
|
||||
/>
|
||||
<OlRoundStateButton
|
||||
onClick={() => getApp().getMap().setHiddenType("red", !appState.mapHiddenTypes["red"])}
|
||||
checked={!appState.mapHiddenTypes["red"]}
|
||||
onClick={() => getApp().getMap().setHiddenType("red", !mapHiddenTypes["red"])}
|
||||
checked={!mapHiddenTypes["red"]}
|
||||
icon={faFlag}
|
||||
className={"!text-red-500"}
|
||||
tooltip={"Hide/show red units"}
|
||||
/>
|
||||
<OlRoundStateButton
|
||||
onClick={() => getApp().getMap().setHiddenType("neutral", !appState.mapHiddenTypes["neutral"])}
|
||||
checked={!appState.mapHiddenTypes["neutral"]}
|
||||
onClick={() => getApp().getMap().setHiddenType("neutral", !mapHiddenTypes["neutral"])}
|
||||
checked={!mapHiddenTypes["neutral"]}
|
||||
icon={faFlag}
|
||||
className={"!text-gray-500"}
|
||||
tooltip={"Hide/show neutral units"}
|
||||
@@ -182,9 +190,7 @@ export function Header() {
|
||||
</div>
|
||||
<div className={`h-8 w-0 border-l-[2px] border-gray-700`}></div>
|
||||
<div
|
||||
className={`
|
||||
flex h-fit flex-row items-center justify-start gap-1
|
||||
`}
|
||||
className={`flex h-fit flex-row items-center justify-start gap-1`}
|
||||
>
|
||||
{Object.entries({
|
||||
aircraft: olButtonsVisibilityAircraft,
|
||||
@@ -199,9 +205,9 @@ export function Header() {
|
||||
<OlRoundStateButton
|
||||
key={entry[0]}
|
||||
onClick={() => {
|
||||
getApp().getMap().setHiddenType(entry[0], !appState.mapHiddenTypes[entry[0]]);
|
||||
getApp().getMap().setHiddenType(entry[0], !mapHiddenTypes[entry[0]]);
|
||||
}}
|
||||
checked={!appState.mapHiddenTypes[entry[0]]}
|
||||
checked={!mapHiddenTypes[entry[0]]}
|
||||
icon={entry[1]}
|
||||
tooltip={"Hide/show " + entry[0] + " units"}
|
||||
/>
|
||||
@@ -211,8 +217,8 @@ export function Header() {
|
||||
|
||||
<OlLabelToggle toggled={false} leftLabel={"Live"} rightLabel={"Map"} onClick={() => {}}></OlLabelToggle>
|
||||
<OlStateButton checked={false} icon={faCamera} onClick={() => {}} tooltip="Activate/deactivate camera plugin" />
|
||||
<OlDropdown label={appState.activeMapSource} className="w-60">
|
||||
{appState.mapSources.map((source) => {
|
||||
<OlDropdown label={mapSource} className="w-60">
|
||||
{mapSources.map((source) => {
|
||||
return (
|
||||
<OlDropdownItem key={source} onClick={() => getApp().getMap().setLayerName(source)}>
|
||||
<div className="truncate">{source}</div>
|
||||
@@ -230,7 +236,5 @@ export function Header() {
|
||||
/>
|
||||
)}
|
||||
</nav>
|
||||
)}
|
||||
</StateConsumer>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -85,9 +85,7 @@ export function JTACMenu(props: { open: boolean; onClose: () => void; children?:
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span
|
||||
className={`
|
||||
my-auto h-full min-w-10 text-nowrap p-2 text-center
|
||||
`}
|
||||
className={`my-auto h-full min-w-10 text-nowrap p-2 text-center`}
|
||||
>
|
||||
BP
|
||||
</span>
|
||||
@@ -122,9 +120,7 @@ export function JTACMenu(props: { open: boolean; onClose: () => void; children?:
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span
|
||||
className={`
|
||||
my-auto h-full min-w-10 text-nowrap p-2 text-center
|
||||
`}
|
||||
className={`my-auto h-full min-w-10 text-nowrap p-2 text-center`}
|
||||
>
|
||||
IP
|
||||
</span>
|
||||
@@ -159,9 +155,7 @@ export function JTACMenu(props: { open: boolean; onClose: () => void; children?:
|
||||
</div>
|
||||
<div className="flex">
|
||||
<span
|
||||
className={`
|
||||
my-auto h-full min-w-10 text-nowrap p-3 text-center
|
||||
`}
|
||||
className={`my-auto h-full min-w-10 text-nowrap p-3 text-center`}
|
||||
>
|
||||
<FaBullseye />
|
||||
</span>
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
import React, { useState, useEffect, useContext } from "react";
|
||||
import { zeroAppend } from "../../other/utils";
|
||||
import { DateAndTime } from "../../interfaces";
|
||||
import { DateAndTime, ServerStatus } from "../../interfaces";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { FaChevronDown, FaChevronUp } from "react-icons/fa6";
|
||||
import { StateContext } from "../../statecontext";
|
||||
import { MapOptionsChangedEvent, ServerStatusUpdatedEvent } from "../../events";
|
||||
import { MAP_OPTIONS_DEFAULTS } from "../../constants/constants";
|
||||
|
||||
export function MiniMapPanel(props: {}) {
|
||||
const appState = useContext(StateContext)
|
||||
|
||||
const [serverStatus, setServerStatus] = useState({} as ServerStatus);
|
||||
const [mapOptions, setMapOptions] = useState(MAP_OPTIONS_DEFAULTS);
|
||||
const [showMissionTime, setShowMissionTime] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
ServerStatusUpdatedEvent.on((status) => setServerStatus(status));
|
||||
MapOptionsChangedEvent.on((mapOptions) => setMapOptions({ ...mapOptions }));
|
||||
}, []);
|
||||
|
||||
// A bit of a hack to set the rounded borders to the minimap
|
||||
useEffect(() => {
|
||||
let miniMap = document.querySelector(".leaflet-control-minimap");
|
||||
@@ -24,45 +30,45 @@ export function MiniMapPanel(props: {}) {
|
||||
let seconds = 0;
|
||||
|
||||
if (showMissionTime) {
|
||||
hours = appState.serverStatus.missionTime.h;
|
||||
minutes = appState.serverStatus.missionTime.m;
|
||||
seconds = appState.serverStatus.missionTime.s;
|
||||
hours = serverStatus.missionTime.h;
|
||||
minutes = serverStatus.missionTime.m;
|
||||
seconds = serverStatus.missionTime.s;
|
||||
} else {
|
||||
hours = Math.floor(appState.serverStatus.elapsedTime / 3600);
|
||||
minutes = Math.floor(appState.serverStatus.elapsedTime / 60) % 60;
|
||||
seconds = Math.round(appState.serverStatus.elapsedTime) % 60;
|
||||
hours = Math.floor(serverStatus.elapsedTime / 3600);
|
||||
minutes = Math.floor(serverStatus.elapsedTime / 60) % 60;
|
||||
seconds = Math.round(serverStatus.elapsedTime) % 60;
|
||||
}
|
||||
|
||||
let timeString = `${zeroAppend(hours, 2)}:${zeroAppend(minutes, 2)}:${zeroAppend(seconds, 2)}`;
|
||||
|
||||
// Choose frame rate string color
|
||||
let frameRateColor = "#8BFF63";
|
||||
if (appState.serverStatus.frameRate < 30) frameRateColor = "#F05252";
|
||||
else if (appState.serverStatus.frameRate >= 30 && appState.serverStatus.frameRate < 60) frameRateColor = "#FF9900";
|
||||
if (serverStatus.frameRate < 30) frameRateColor = "#F05252";
|
||||
else if (serverStatus.frameRate >= 30 && serverStatus.frameRate < 60) frameRateColor = "#FF9900";
|
||||
|
||||
// Choose load string color
|
||||
let loadColor = "#8BFF63";
|
||||
if (appState.serverStatus.load > 1000) loadColor = "#F05252";
|
||||
else if (appState.serverStatus.load >= 100 && appState.serverStatus.load < 1000) loadColor = "#FF9900";
|
||||
if (serverStatus.load > 1000) loadColor = "#F05252";
|
||||
else if (serverStatus.load >= 100 && serverStatus.load < 1000) loadColor = "#FF9900";
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={() => setShowMissionTime(!showMissionTime)}
|
||||
className={`
|
||||
absolute right-[10px]
|
||||
${appState.mapOptions.showMinimap ? `top-[232px]` : `top-[70px]`}
|
||||
${mapOptions.showMinimap ? `top-[232px]` : `top-[70px]`}
|
||||
flex w-[288px] items-center justify-between
|
||||
${appState.mapOptions.showMinimap ? `rounded-b-lg` : `rounded-lg`}
|
||||
${mapOptions.showMinimap ? `rounded-b-lg` : `rounded-lg`}
|
||||
bg-gray-200 p-3 text-sm backdrop-blur-lg backdrop-grayscale
|
||||
dark:bg-olympus-800/90 dark:text-gray-200
|
||||
`}
|
||||
>
|
||||
{!appState.serverStatus.connected ? (
|
||||
{!serverStatus.connected ? (
|
||||
<div className={`flex animate-pulse items-center gap-2 font-semibold`}>
|
||||
<div className={`relative h-4 w-4 rounded-full bg-[#F05252]`}></div>
|
||||
Server disconnected
|
||||
</div>
|
||||
) : appState.serverStatus.paused ? (
|
||||
) : serverStatus.paused ? (
|
||||
<div className={`flex animate-pulse items-center gap-2 font-semibold`}>
|
||||
<div className={`relative h-4 w-4 rounded-full bg-[#FF9900]`}></div>
|
||||
Server paused
|
||||
@@ -72,13 +78,13 @@ export function MiniMapPanel(props: {}) {
|
||||
<div className="flex gap-2 font-semibold">
|
||||
FPS:
|
||||
<span style={{ color: frameRateColor }} className={`font-semibold`}>
|
||||
{appState.serverStatus.frameRate}
|
||||
{serverStatus.frameRate}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex gap-2 font-semibold">
|
||||
Load:
|
||||
<span style={{ color: loadColor }} className={`font-semibold`}>
|
||||
{appState.serverStatus.load}
|
||||
{serverStatus.load}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex gap-2 font-semibold">
|
||||
@@ -87,7 +93,7 @@ export function MiniMapPanel(props: {}) {
|
||||
<div className={`relative h-4 w-4 rounded-full bg-[#8BFF63]`}></div>
|
||||
</>
|
||||
)}
|
||||
{appState.mapOptions.showMinimap ? (
|
||||
{mapOptions.showMinimap ? (
|
||||
<FaChevronUp
|
||||
onClick={() => {
|
||||
getApp().getMap().setOption("showMinimap", false);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { OlStateButton } from "../components/olstatebutton";
|
||||
import {
|
||||
faGamepad,
|
||||
@@ -12,88 +12,94 @@ import {
|
||||
faVolumeHigh,
|
||||
faJ,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { StateConsumer } from "../../statecontext";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { OlympusState } from "../../constants/constants";
|
||||
import { NO_SUBSTATE, OlympusState, OlympusSubState } from "../../constants/constants";
|
||||
import { AppStateChangedEvent } from "../../events";
|
||||
|
||||
export function SideBar() {
|
||||
const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED);
|
||||
|
||||
useEffect(() => {
|
||||
AppStateChangedEvent.on((state, subState) => setAppState(state));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<StateConsumer>
|
||||
{(appState) => (
|
||||
<nav
|
||||
className={`
|
||||
z-20 flex h-full flex-col bg-gray-300
|
||||
dark:bg-olympus-900
|
||||
`}
|
||||
>
|
||||
<div
|
||||
className={`
|
||||
w-16 flex-1 flex-wrap items-center justify-center p-4
|
||||
`}
|
||||
>
|
||||
<div
|
||||
className={`
|
||||
flex flex-col items-center justify-center gap-2.5
|
||||
`}
|
||||
>
|
||||
<OlStateButton
|
||||
onClick={() => {getApp().setState(appState.appState !== OlympusState.MAIN_MENU? OlympusState.MAIN_MENU: OlympusState.IDLE)}}
|
||||
checked={appState.appState === OlympusState.MAIN_MENU}
|
||||
icon={faEllipsisV}
|
||||
tooltip="Hide/show main menu"
|
||||
></OlStateButton>
|
||||
<OlStateButton
|
||||
onClick={() => {getApp().setState(appState.appState !== OlympusState.SPAWN? OlympusState.SPAWN: OlympusState.IDLE)}}
|
||||
checked={appState.appState === OlympusState.SPAWN}
|
||||
icon={faPlusSquare}
|
||||
tooltip="Hide/show unit spawn menu"
|
||||
></OlStateButton>
|
||||
<OlStateButton
|
||||
onClick={() => {getApp().setState(appState.appState !== OlympusState.UNIT_CONTROL? OlympusState.UNIT_CONTROL: OlympusState.IDLE)}}
|
||||
checked={appState.appState === OlympusState.UNIT_CONTROL}
|
||||
icon={faGamepad}
|
||||
tooltip="Hide/show selection tool and unit control menu"
|
||||
></OlStateButton>
|
||||
<OlStateButton
|
||||
onClick={() => {getApp().setState(appState.appState !== OlympusState.DRAW? OlympusState.DRAW: OlympusState.IDLE)}}
|
||||
checked={appState.appState === OlympusState.DRAW}
|
||||
icon={faPencil}
|
||||
tooltip="Hide/show drawing menu"
|
||||
></OlStateButton>
|
||||
<OlStateButton
|
||||
onClick={() => {getApp().setState(appState.appState !== OlympusState.AUDIO? OlympusState.AUDIO: OlympusState.IDLE)}}
|
||||
checked={appState.appState === OlympusState.AUDIO}
|
||||
icon={faVolumeHigh}
|
||||
tooltip="Hide/show audio menu"
|
||||
></OlStateButton>
|
||||
<OlStateButton
|
||||
onClick={() => {getApp().setState(appState.appState !== OlympusState.JTAC? OlympusState.JTAC: OlympusState.IDLE)}}
|
||||
checked={appState.appState === OlympusState.JTAC}
|
||||
icon={faJ} tooltip="Hide/show JTAC menu"></OlStateButton>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex w-16 flex-wrap content-end justify-center p-4">
|
||||
<div
|
||||
className={`
|
||||
flex flex-col items-center justify-center gap-2.5
|
||||
`}
|
||||
>
|
||||
<OlStateButton
|
||||
onClick={() => window.open("https://github.com/Pax1601/DCSOlympus/wiki")}
|
||||
checked={false}
|
||||
icon={faQuestionCircle}
|
||||
tooltip="Open user guide on separate window"
|
||||
></OlStateButton>
|
||||
<OlStateButton
|
||||
onClick={() => {getApp().setState(appState.appState !== OlympusState.OPTIONS? OlympusState.OPTIONS: OlympusState.IDLE)}}
|
||||
checked={appState.appState === OlympusState.OPTIONS}
|
||||
icon={faCog}
|
||||
tooltip="Hide/show settings menu"
|
||||
></OlStateButton>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
)}
|
||||
</StateConsumer>
|
||||
<nav
|
||||
className={`
|
||||
z-20 flex h-full flex-col bg-gray-300
|
||||
dark:bg-olympus-900
|
||||
`}
|
||||
>
|
||||
<div className={`w-16 flex-1 flex-wrap items-center justify-center p-4`}>
|
||||
<div className={`flex flex-col items-center justify-center gap-2.5`}>
|
||||
<OlStateButton
|
||||
onClick={() => {
|
||||
getApp().setState(appState !== OlympusState.MAIN_MENU ? OlympusState.MAIN_MENU : OlympusState.IDLE);
|
||||
}}
|
||||
checked={appState === OlympusState.MAIN_MENU}
|
||||
icon={faEllipsisV}
|
||||
tooltip="Hide/show main menu"
|
||||
></OlStateButton>
|
||||
<OlStateButton
|
||||
onClick={() => {
|
||||
getApp().setState(appState !== OlympusState.SPAWN ? OlympusState.SPAWN : OlympusState.IDLE);
|
||||
}}
|
||||
checked={appState === OlympusState.SPAWN}
|
||||
icon={faPlusSquare}
|
||||
tooltip="Hide/show unit spawn menu"
|
||||
></OlStateButton>
|
||||
<OlStateButton
|
||||
onClick={() => {
|
||||
getApp().setState(appState !== OlympusState.UNIT_CONTROL ? OlympusState.UNIT_CONTROL : OlympusState.IDLE);
|
||||
}}
|
||||
checked={appState === OlympusState.UNIT_CONTROL}
|
||||
icon={faGamepad}
|
||||
tooltip="Hide/show selection tool and unit control menu"
|
||||
></OlStateButton>
|
||||
<OlStateButton
|
||||
onClick={() => {
|
||||
getApp().setState(appState !== OlympusState.DRAW ? OlympusState.DRAW : OlympusState.IDLE);
|
||||
}}
|
||||
checked={appState === OlympusState.DRAW}
|
||||
icon={faPencil}
|
||||
tooltip="Hide/show drawing menu"
|
||||
></OlStateButton>
|
||||
<OlStateButton
|
||||
onClick={() => {
|
||||
getApp().setState(appState !== OlympusState.AUDIO ? OlympusState.AUDIO : OlympusState.IDLE);
|
||||
}}
|
||||
checked={appState === OlympusState.AUDIO}
|
||||
icon={faVolumeHigh}
|
||||
tooltip="Hide/show audio menu"
|
||||
></OlStateButton>
|
||||
<OlStateButton
|
||||
onClick={() => {
|
||||
getApp().setState(appState !== OlympusState.JTAC ? OlympusState.JTAC : OlympusState.IDLE);
|
||||
}}
|
||||
checked={appState === OlympusState.JTAC}
|
||||
icon={faJ}
|
||||
tooltip="Hide/show JTAC menu"
|
||||
></OlStateButton>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex w-16 flex-wrap content-end justify-center p-4">
|
||||
<div className={`flex flex-col items-center justify-center gap-2.5`}>
|
||||
<OlStateButton
|
||||
onClick={() => window.open("https://github.com/Pax1601/DCSOlympus/wiki")}
|
||||
checked={false}
|
||||
icon={faQuestionCircle}
|
||||
tooltip="Open user guide on separate window"
|
||||
></OlStateButton>
|
||||
<OlStateButton
|
||||
onClick={() => {
|
||||
getApp().setState(appState !== OlympusState.OPTIONS ? OlympusState.OPTIONS : OlympusState.IDLE);
|
||||
}}
|
||||
checked={appState === OlympusState.OPTIONS}
|
||||
icon={faCog}
|
||||
tooltip="Hide/show settings menu"
|
||||
></OlStateButton>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -16,15 +16,10 @@ import {
|
||||
import { faExplosion, faSmog } from "@fortawesome/free-solid-svg-icons";
|
||||
import { OlEffectListEntry } from "../components/oleffectlistentry";
|
||||
import { EffectSpawnMenu } from "./effectspawnmenu";
|
||||
import { NO_SUBSTATE, OlympusState, SpawnSubState } from "../../constants/constants";
|
||||
import { aircraftDatabase } from "../../unit/databases/aircraftdatabase";
|
||||
import { navyUnitDatabase } from "../../unit/databases/navyunitdatabase";
|
||||
import { filterBlueprintsByLabel } from "../../other/utils";
|
||||
import { helicopterDatabase } from "../../unit/databases/helicopterdatabase";
|
||||
import { groundUnitDatabase } from "../../unit/databases/groundunitdatabase";
|
||||
import { AppStateChangedEvent } from "../../events";
|
||||
import { NO_SUBSTATE, OlympusState } from "../../constants/constants";
|
||||
import { AppStateChangedEvent, UnitDatabaseLoadedEvent } from "../../events";
|
||||
|
||||
enum Accordion {
|
||||
enum CategoryAccordion {
|
||||
NONE,
|
||||
AIRCRAFT,
|
||||
HELICOPTER,
|
||||
@@ -36,34 +31,53 @@ enum Accordion {
|
||||
}
|
||||
|
||||
export function SpawnMenu(props: { open: boolean; onClose: () => void; children?: JSX.Element | JSX.Element[] }) {
|
||||
const [openAccordion, setOpenAccordion] = useState(Accordion.NONE);
|
||||
const [openAccordion, setOpenAccordion] = useState(CategoryAccordion.NONE);
|
||||
const [blueprint, setBlueprint] = useState(null as null | UnitBlueprint);
|
||||
const [effect, setEffect] = useState(null as null | string);
|
||||
const [filterString, setFilterString] = useState("");
|
||||
const [selectedRole, setSelectedRole] = useState(null as null | string);
|
||||
const [selectedType, setSelectedType] = useState(null as null | string);
|
||||
const [blueprints, setBlueprints] = useState([] as UnitBlueprint[]);
|
||||
const [roles, setRoles] = useState({ aircraft: [] as string[], helicopter: [] as string[] });
|
||||
const [types, setTypes] = useState({ groundunit: [] as string[], navyunit: [] as string[] });
|
||||
|
||||
const filteredAircraft = getApp()
|
||||
? filterBlueprintsByLabel(selectedRole ? aircraftDatabase.getByRole(selectedRole) : Object.values(aircraftDatabase.getBlueprints()), filterString)
|
||||
: ({} as { [key: string]: UnitBlueprint });
|
||||
const filteredHelicopters = getApp()
|
||||
? filterBlueprintsByLabel(selectedRole ? helicopterDatabase.getByRole(selectedRole) : Object.values(helicopterDatabase.getBlueprints()), filterString)
|
||||
: ({} as { [key: string]: UnitBlueprint });
|
||||
const filteredSAMs = getApp() ? filterBlueprintsByLabel(groundUnitDatabase.getByType("SAM Site"), filterString) : ({} as { [key: string]: UnitBlueprint });
|
||||
const filteredAAA = getApp() ? filterBlueprintsByLabel(groundUnitDatabase.getByType("AAA"), filterString) : ({} as { [key: string]: UnitBlueprint });
|
||||
const filteredGroundUnits = getApp()
|
||||
? filterBlueprintsByLabel(selectedType ? groundUnitDatabase.getByType(selectedType) : Object.values(groundUnitDatabase.getBlueprints()), filterString)
|
||||
: ({} as { [key: string]: UnitBlueprint });
|
||||
const filteredNavyUnits = getApp()
|
||||
? filterBlueprintsByLabel(selectedType ? navyUnitDatabase.getByType(selectedType) : Object.values(navyUnitDatabase.getBlueprints()), filterString)
|
||||
: ({} as { [key: string]: UnitBlueprint });
|
||||
useEffect(() => {
|
||||
if (selectedRole)
|
||||
setBlueprints(getApp()?.getUnitsManager().getDatabase().getByRole(selectedRole));
|
||||
else if (selectedType)
|
||||
setBlueprints(getApp()?.getUnitsManager().getDatabase().getByType(selectedType));
|
||||
else
|
||||
setBlueprints(getApp()?.getUnitsManager().getDatabase().getBlueprints())
|
||||
}, [selectedRole, selectedType, openAccordion]);
|
||||
|
||||
useEffect(() => {
|
||||
UnitDatabaseLoadedEvent.on(() => {
|
||||
setRoles({
|
||||
aircraft: getApp()?.getUnitsManager().getDatabase().getRoles((unit) => unit.category === "aircraft"),
|
||||
helicopter: getApp()?.getUnitsManager().getDatabase().getRoles((unit) => unit.category === "helicopter"),
|
||||
});
|
||||
|
||||
setTypes({
|
||||
groundunit: getApp()?.getUnitsManager().getDatabase().getTypes((unit) => unit.category === "groundunit"),
|
||||
navyunit: getApp()?.getUnitsManager().getDatabase().getTypes((unit) => unit.category === "navyunit")
|
||||
});
|
||||
});
|
||||
}, []);
|
||||
|
||||
/* Filter the blueprints according to the label */
|
||||
const filteredBlueprints: UnitBlueprint[] = [];
|
||||
if (blueprints) {
|
||||
blueprints.forEach((blueprint) => {
|
||||
if (blueprint.enabled && (filterString === "" || blueprint.label.toLowerCase().includes(filterString.toLowerCase()))) filteredBlueprints.push(blueprint);
|
||||
});
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!props.open) {
|
||||
if (blueprint !== null) setBlueprint(null);
|
||||
if (effect !== null) setEffect(null);
|
||||
if (filterString !== "") setFilterString("");
|
||||
if (openAccordion !== Accordion.NONE) setOpenAccordion(Accordion.NONE);
|
||||
if (openAccordion !== CategoryAccordion.NONE) setOpenAccordion(CategoryAccordion.NONE);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -94,36 +108,33 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
|
||||
<OlSearchBar onChange={(value) => setFilterString(value)} text={filterString} />
|
||||
<OlAccordion
|
||||
title={`Aircraft`}
|
||||
open={openAccordion == Accordion.AIRCRAFT}
|
||||
open={openAccordion == CategoryAccordion.AIRCRAFT}
|
||||
onClick={() => {
|
||||
setOpenAccordion(openAccordion === Accordion.AIRCRAFT ? Accordion.NONE : Accordion.AIRCRAFT);
|
||||
setOpenAccordion(openAccordion === CategoryAccordion.AIRCRAFT ? CategoryAccordion.NONE : CategoryAccordion.AIRCRAFT);
|
||||
setSelectedRole(null);
|
||||
setSelectedType(null);
|
||||
}}
|
||||
>
|
||||
<div className="mb-2 flex flex-wrap gap-1">
|
||||
{aircraftDatabase
|
||||
.getRoles()
|
||||
.sort()
|
||||
.map((role) => {
|
||||
return (
|
||||
<div
|
||||
key={role}
|
||||
data-selected={selectedRole === role}
|
||||
className={`
|
||||
cursor-pointer rounded-full bg-olympus-800 px-2 py-0.5
|
||||
text-xs font-bold text-olympus-50
|
||||
data-[selected='true']:bg-blue-500
|
||||
data-[selected='true']:text-gray-200
|
||||
`}
|
||||
onClick={() => {
|
||||
selectedRole === role ? setSelectedRole(null) : setSelectedRole(role);
|
||||
}}
|
||||
>
|
||||
{role}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{roles.aircraft.sort().map((role) => {
|
||||
return (
|
||||
<div
|
||||
key={role}
|
||||
data-selected={selectedRole === role}
|
||||
className={`
|
||||
cursor-pointer rounded-full bg-olympus-800 px-2 py-0.5
|
||||
text-xs font-bold text-olympus-50
|
||||
data-[selected='true']:bg-blue-500
|
||||
data-[selected='true']:text-gray-200
|
||||
`}
|
||||
onClick={() => {
|
||||
selectedRole === role ? setSelectedRole(null) : setSelectedRole(role);
|
||||
}}
|
||||
>
|
||||
{role}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
@@ -131,43 +142,42 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
|
||||
no-scrollbar
|
||||
`}
|
||||
>
|
||||
{Object.entries(filteredAircraft).map((entry) => {
|
||||
return <OlUnitListEntry key={entry[0]} icon={olButtonsVisibilityAircraft} blueprint={entry[1]} onClick={() => setBlueprint(entry[1])} />;
|
||||
})}
|
||||
{filteredBlueprints
|
||||
.filter((blueprint) => blueprint.category === "aircraft")
|
||||
.map((entry) => {
|
||||
return <OlUnitListEntry key={entry.name} icon={olButtonsVisibilityAircraft} blueprint={entry} onClick={() => setBlueprint(entry)} />;
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
<OlAccordion
|
||||
title={`Helicopters`}
|
||||
open={openAccordion == Accordion.HELICOPTER}
|
||||
open={openAccordion == CategoryAccordion.HELICOPTER}
|
||||
onClick={() => {
|
||||
setOpenAccordion(openAccordion === Accordion.HELICOPTER ? Accordion.NONE : Accordion.HELICOPTER);
|
||||
setOpenAccordion(openAccordion === CategoryAccordion.HELICOPTER ? CategoryAccordion.NONE : CategoryAccordion.HELICOPTER);
|
||||
setSelectedRole(null);
|
||||
setSelectedType(null);
|
||||
}}
|
||||
>
|
||||
<div className="mb-2 flex flex-wrap gap-1">
|
||||
{helicopterDatabase
|
||||
.getRoles()
|
||||
.sort()
|
||||
.map((role) => {
|
||||
return (
|
||||
<div
|
||||
key={role}
|
||||
data-selected={selectedRole === role}
|
||||
className={`
|
||||
cursor-pointer rounded-full bg-olympus-800 px-2 py-0.5
|
||||
text-xs font-bold text-olympus-50
|
||||
data-[selected='true']:bg-blue-500
|
||||
data-[selected='true']:text-gray-200
|
||||
`}
|
||||
onClick={() => {
|
||||
selectedRole === role ? setSelectedRole(null) : setSelectedRole(role);
|
||||
}}
|
||||
>
|
||||
{role}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{roles.helicopter.sort().map((role) => {
|
||||
return (
|
||||
<div
|
||||
key={role}
|
||||
data-selected={selectedRole === role}
|
||||
className={`
|
||||
cursor-pointer rounded-full bg-olympus-800 px-2 py-0.5
|
||||
text-xs font-bold text-olympus-50
|
||||
data-[selected='true']:bg-blue-500
|
||||
data-[selected='true']:text-gray-200
|
||||
`}
|
||||
onClick={() => {
|
||||
selectedRole === role ? setSelectedRole(null) : setSelectedRole(role);
|
||||
}}
|
||||
>
|
||||
{role}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
@@ -175,16 +185,18 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
|
||||
no-scrollbar
|
||||
`}
|
||||
>
|
||||
{Object.entries(filteredHelicopters).map((entry) => {
|
||||
return <OlUnitListEntry key={entry[0]} icon={olButtonsVisibilityHelicopter} blueprint={entry[1]} onClick={() => setBlueprint(entry[1])} />;
|
||||
})}
|
||||
{filteredBlueprints
|
||||
.filter((blueprint) => blueprint.category === "helicopter")
|
||||
.map((entry) => {
|
||||
return <OlUnitListEntry key={entry.name} icon={olButtonsVisibilityHelicopter} blueprint={entry} onClick={() => setBlueprint(entry)} />;
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
<OlAccordion
|
||||
title={`Surfact to Air Missiles (SAM sites)`}
|
||||
open={openAccordion == Accordion.SAM}
|
||||
open={openAccordion == CategoryAccordion.SAM}
|
||||
onClick={() => {
|
||||
setOpenAccordion(openAccordion === Accordion.SAM ? Accordion.NONE : Accordion.SAM);
|
||||
setOpenAccordion(openAccordion === CategoryAccordion.SAM ? CategoryAccordion.NONE : CategoryAccordion.SAM);
|
||||
setSelectedRole(null);
|
||||
setSelectedType(null);
|
||||
}}
|
||||
@@ -195,16 +207,18 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
|
||||
no-scrollbar
|
||||
`}
|
||||
>
|
||||
{Object.entries(filteredSAMs).map((entry) => {
|
||||
return <OlUnitListEntry key={entry[0]} icon={olButtonsVisibilityGroundunitSam} blueprint={entry[1]} onClick={() => setBlueprint(entry[1])} />;
|
||||
})}
|
||||
{filteredBlueprints
|
||||
.filter((blueprint) => blueprint.category === "groundunit")
|
||||
.map((entry) => {
|
||||
return <OlUnitListEntry key={entry.name} icon={olButtonsVisibilityGroundunitSam} blueprint={entry} onClick={() => setBlueprint(entry)} />;
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
<OlAccordion
|
||||
title={`Anti Aircraft Artillery (AAA)`}
|
||||
open={openAccordion == Accordion.AAA}
|
||||
open={openAccordion == CategoryAccordion.AAA}
|
||||
onClick={() => {
|
||||
setOpenAccordion(openAccordion === Accordion.AAA ? Accordion.NONE : Accordion.AAA);
|
||||
setOpenAccordion(openAccordion === CategoryAccordion.AAA ? CategoryAccordion.NONE : CategoryAccordion.AAA);
|
||||
setSelectedRole(null);
|
||||
setSelectedType(null);
|
||||
}}
|
||||
@@ -215,23 +229,24 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
|
||||
no-scrollbar
|
||||
`}
|
||||
>
|
||||
{Object.entries(filteredAAA).map((entry) => {
|
||||
return <OlUnitListEntry key={entry[0]} icon={olButtonsVisibilityGroundunitSam} blueprint={entry[1]} onClick={() => setBlueprint(entry[1])} />;
|
||||
})}
|
||||
{filteredBlueprints
|
||||
.filter((blueprint) => blueprint.category === "groundunit")
|
||||
.map((entry) => {
|
||||
return <OlUnitListEntry key={entry.name} icon={olButtonsVisibilityGroundunitSam} blueprint={entry} onClick={() => setBlueprint(entry)} />;
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
<OlAccordion
|
||||
title={`Ground Units`}
|
||||
open={openAccordion == Accordion.GROUND_UNIT}
|
||||
open={openAccordion == CategoryAccordion.GROUND_UNIT}
|
||||
onClick={() => {
|
||||
setOpenAccordion(openAccordion === Accordion.GROUND_UNIT ? Accordion.NONE : Accordion.GROUND_UNIT);
|
||||
setOpenAccordion(openAccordion === CategoryAccordion.GROUND_UNIT ? CategoryAccordion.NONE : CategoryAccordion.GROUND_UNIT);
|
||||
setSelectedRole(null);
|
||||
setSelectedType(null);
|
||||
}}
|
||||
>
|
||||
<div className="mb-2 flex flex-wrap gap-1">
|
||||
{groundUnitDatabase
|
||||
.getTypes()
|
||||
{types.groundunit
|
||||
.sort()
|
||||
.filter((type) => {
|
||||
return type !== "AAA" && type !== "SAM Site";
|
||||
@@ -262,43 +277,42 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
|
||||
no-scrollbar
|
||||
`}
|
||||
>
|
||||
{Object.entries(filteredGroundUnits).map((entry) => {
|
||||
return <OlUnitListEntry key={entry[0]} icon={olButtonsVisibilityGroundunit} blueprint={entry[1]} onClick={() => setBlueprint(entry[1])} />;
|
||||
})}
|
||||
{filteredBlueprints
|
||||
.filter((blueprint) => blueprint.category === "groundunit")
|
||||
.map((entry) => {
|
||||
return <OlUnitListEntry key={entry.name} icon={olButtonsVisibilityGroundunit} blueprint={entry} onClick={() => setBlueprint(entry)} />;
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
<OlAccordion
|
||||
title={`Ships and submarines`}
|
||||
open={openAccordion == Accordion.NAVY_UNIT}
|
||||
open={openAccordion == CategoryAccordion.NAVY_UNIT}
|
||||
onClick={() => {
|
||||
setOpenAccordion(openAccordion === Accordion.NAVY_UNIT ? Accordion.NONE : Accordion.NAVY_UNIT);
|
||||
setOpenAccordion(openAccordion === CategoryAccordion.NAVY_UNIT ? CategoryAccordion.NONE : CategoryAccordion.NAVY_UNIT);
|
||||
setSelectedRole(null);
|
||||
setSelectedType(null);
|
||||
}}
|
||||
>
|
||||
<div className="mb-2 flex flex-wrap gap-1">
|
||||
{navyUnitDatabase
|
||||
.getTypes()
|
||||
.sort()
|
||||
.map((type) => {
|
||||
return (
|
||||
<div
|
||||
key={type}
|
||||
data-selected={selectedType === type}
|
||||
className={`
|
||||
cursor-pointer rounded-full bg-olympus-800 px-2 py-0.5
|
||||
text-xs font-bold text-olympus-50
|
||||
data-[selected='true']:bg-blue-500
|
||||
data-[selected='true']:text-gray-200
|
||||
`}
|
||||
onClick={() => {
|
||||
selectedType === type ? setSelectedType(null) : setSelectedType(type);
|
||||
}}
|
||||
>
|
||||
{type}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{types.navyunit.sort().map((type) => {
|
||||
return (
|
||||
<div
|
||||
key={type}
|
||||
data-selected={selectedType === type}
|
||||
className={`
|
||||
cursor-pointer rounded-full bg-olympus-800 px-2 py-0.5
|
||||
text-xs font-bold text-olympus-50
|
||||
data-[selected='true']:bg-blue-500
|
||||
data-[selected='true']:text-gray-200
|
||||
`}
|
||||
onClick={() => {
|
||||
selectedType === type ? setSelectedType(null) : setSelectedType(type);
|
||||
}}
|
||||
>
|
||||
{type}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
@@ -306,16 +320,18 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
|
||||
no-scrollbar
|
||||
`}
|
||||
>
|
||||
{Object.entries(filteredNavyUnits).map((entry) => {
|
||||
return <OlUnitListEntry key={entry[0]} icon={olButtonsVisibilityNavyunit} blueprint={entry[1]} onClick={() => setBlueprint(entry[1])} />;
|
||||
})}
|
||||
{filteredBlueprints
|
||||
.filter((blueprint) => blueprint.category === "navyunit")
|
||||
.map((entry) => {
|
||||
return <OlUnitListEntry key={entry.name} icon={olButtonsVisibilityNavyunit} blueprint={entry} onClick={() => setBlueprint(entry)} />;
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
<OlAccordion
|
||||
title="Effects (smokes, explosions etc)"
|
||||
open={openAccordion == Accordion.EFFECT}
|
||||
open={openAccordion == CategoryAccordion.EFFECT}
|
||||
onClick={() => {
|
||||
setOpenAccordion(openAccordion === Accordion.EFFECT ? Accordion.NONE : Accordion.EFFECT);
|
||||
setOpenAccordion(openAccordion === CategoryAccordion.EFFECT ? CategoryAccordion.NONE : CategoryAccordion.EFFECT);
|
||||
setSelectedRole(null);
|
||||
setSelectedType(null);
|
||||
}}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React, { useContext, useEffect, useRef, useState } from "react";
|
||||
import { Unit } from "../../unit/unit";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { ContextActionSet } from "../../unit/contextactionset";
|
||||
import { OlStateButton } from "../components/olstatebutton";
|
||||
import { getApp } from "../../olympusapp";
|
||||
@@ -8,13 +7,12 @@ import { CONTEXT_ACTION_COLORS } from "../../constants/constants";
|
||||
import { FaInfoCircle } from "react-icons/fa";
|
||||
import { FaChevronLeft, FaChevronRight } from "react-icons/fa6";
|
||||
import { OlympusState } from "../../constants/constants";
|
||||
import { AppStateChangedEvent } from "../../events";
|
||||
import { StateContext } from "../../statecontext";
|
||||
import { AppStateChangedEvent, ContextActionChangedEvent, ContextActionSetChangedEvent } from "../../events";
|
||||
|
||||
export function UnitMouseControlBar(props: {}) {
|
||||
const appState = useContext(StateContext);
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
export function UnitControlBar(props: {}) {
|
||||
const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED);
|
||||
const [contextActionSet, setContextActionsSet] = useState(null as ContextActionSet | null);
|
||||
const [contextAction, setContextAction] = useState(null as ContextAction | null);
|
||||
const [scrolledLeft, setScrolledLeft] = useState(true);
|
||||
const [scrolledRight, setScrolledRight] = useState(false);
|
||||
|
||||
@@ -25,9 +23,9 @@ export function UnitMouseControlBar(props: {}) {
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
AppStateChangedEvent.on((state, subState) => {
|
||||
setOpen(state === OlympusState.UNIT_CONTROL);
|
||||
});
|
||||
AppStateChangedEvent.on((state, subState) => setAppState(state));
|
||||
ContextActionSetChangedEvent.on((contextActionSet) => setContextActionsSet(contextActionSet));
|
||||
ContextActionChangedEvent.on((contextAction) => setContextAction(contextAction));
|
||||
}, []);
|
||||
|
||||
function onScroll(el) {
|
||||
@@ -43,8 +41,8 @@ export function UnitMouseControlBar(props: {}) {
|
||||
|
||||
let reorderedActions: ContextAction[] = [];
|
||||
CONTEXT_ACTION_COLORS.forEach((color) => {
|
||||
if (appState.contextActionSet) {
|
||||
Object.values(appState.contextActionSet.getContextActions()).forEach((contextAction: ContextAction) => {
|
||||
if (contextActionSet) {
|
||||
Object.values(contextActionSet.getContextActions()).forEach((contextAction: ContextAction) => {
|
||||
if (color === null && contextAction.getOptions().buttonColor === undefined) reorderedActions.push(contextAction);
|
||||
else if (color === contextAction.getOptions().buttonColor) reorderedActions.push(contextAction);
|
||||
});
|
||||
@@ -53,7 +51,7 @@ export function UnitMouseControlBar(props: {}) {
|
||||
|
||||
return (
|
||||
<>
|
||||
{open && appState.contextActionSet && Object.keys(appState.contextActionSet.getContextActions()).length > 0 && (
|
||||
{appState === OlympusState.UNIT_CONTROL && contextActionSet && Object.keys(contextActionSet.getContextActions()).length > 0 && (
|
||||
<>
|
||||
<div
|
||||
className={`
|
||||
@@ -72,26 +70,26 @@ export function UnitMouseControlBar(props: {}) {
|
||||
/>
|
||||
)}
|
||||
<div className="flex gap-2 overflow-x-auto no-scrollbar p-2" onScroll={(ev) => onScroll(ev.target)} ref={scrollRef}>
|
||||
{reorderedActions.map((contextAction: ContextAction) => {
|
||||
{reorderedActions.map((contextActionIt: ContextAction) => {
|
||||
return (
|
||||
<OlStateButton
|
||||
key={contextAction.getId()}
|
||||
checked={contextAction === appState.contextAction}
|
||||
icon={contextAction.getIcon()}
|
||||
tooltip={contextAction.getLabel()}
|
||||
key={contextActionIt.getId()}
|
||||
checked={contextActionIt === contextAction}
|
||||
icon={contextActionIt.getIcon()}
|
||||
tooltip={contextActionIt.getLabel()}
|
||||
className={
|
||||
contextAction.getOptions().buttonColor
|
||||
contextActionIt.getOptions().buttonColor
|
||||
? `
|
||||
border-2
|
||||
border-${contextAction.getOptions().buttonColor}-500
|
||||
border-${contextActionIt.getOptions().buttonColor}-500
|
||||
`
|
||||
: ""
|
||||
}
|
||||
onClick={() => {
|
||||
if (contextAction.getOptions().executeImmediately) {
|
||||
contextAction.executeCallback(null, null);
|
||||
if (contextActionIt.getOptions().executeImmediately) {
|
||||
contextActionIt.executeCallback(null, null);
|
||||
} else {
|
||||
appState.contextAction !== contextAction ? getApp().getMap().setContextAction(contextAction) : getApp().getMap().setContextAction(null);
|
||||
contextActionIt !== contextAction ? getApp().getMap().setContextAction(contextActionIt) : getApp().getMap().setContextAction(null);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
@@ -108,7 +106,7 @@ export function UnitMouseControlBar(props: {}) {
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{appState.contextAction && (
|
||||
{contextAction && (
|
||||
<div
|
||||
className={`
|
||||
absolute left-[50%] top-32 flex min-w-[300px]
|
||||
@@ -130,7 +128,7 @@ export function UnitMouseControlBar(props: {}) {
|
||||
md:border-l-[1px] md:px-5
|
||||
`}
|
||||
>
|
||||
{appState.contextAction.getDescription()}
|
||||
{contextAction.getDescription()}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { MutableRefObject, useContext, useEffect, useRef, useState } from "react";
|
||||
import React, { MutableRefObject, useEffect, useRef, useState } from "react";
|
||||
import { Menu } from "./components/menu";
|
||||
import { Unit } from "../../unit/unit";
|
||||
import { OlLabelToggle } from "../components/ollabeltoggle";
|
||||
@@ -50,11 +50,11 @@ import { Radio, TACAN } from "../../interfaces";
|
||||
import { OlStringInput } from "../components/olstringinput";
|
||||
import { OlFrequencyInput } from "../components/olfrequencyinput";
|
||||
import { UnitSink } from "../../audio/unitsink";
|
||||
import { StateContext } from "../../statecontext";
|
||||
import { AudioManagerStateChangedEvent, SelectedUnitsChangedEvent, SelectionClearedEvent } from "../../events";
|
||||
|
||||
export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
const appState = useContext(StateContext);
|
||||
|
||||
const [selectedUnits, setSelectedUnits] = useState([] as Unit[]);
|
||||
const [audioManagerState, setAudioManagerState] = useState(false);
|
||||
const [selectedUnitsData, setSelectedUnitsData] = useState({
|
||||
desiredAltitude: undefined as undefined | number,
|
||||
desiredAltitudeType: undefined as undefined | string,
|
||||
@@ -110,6 +110,12 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
|
||||
var searchBarRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
SelectedUnitsChangedEvent.on((units) => setSelectedUnits(units));
|
||||
SelectionClearedEvent.on(() => setSelectedUnits([]));
|
||||
AudioManagerStateChangedEvent.on((state) => setAudioManagerState(state));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!searchBarRefState) setSearchBarRefState(searchBarRef);
|
||||
if (!props.open && selectionBlueprint !== null) setSelectionBlueprint(null);
|
||||
@@ -154,12 +160,12 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
},
|
||||
} as { [key in keyof typeof selectedUnitsData]: (unit: Unit) => void };
|
||||
|
||||
var updatedData = selectedUnitsData;
|
||||
var updatedData = {};
|
||||
Object.entries(getters).forEach(([key, getter]) => {
|
||||
updatedData[key] = getApp()?.getUnitsManager()?.getSelectedUnitsVariable(getter);
|
||||
});
|
||||
setSelectedUnitsData(updatedData);
|
||||
}, [appState.selectedUnits]);
|
||||
setSelectedUnitsData(updatedData as typeof selectedUnitsData);
|
||||
}, [selectedUnits]);
|
||||
|
||||
/* Count how many units are selected of each type, divided by coalition */
|
||||
var unitOccurences: {
|
||||
@@ -172,7 +178,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
neutral: {},
|
||||
};
|
||||
|
||||
appState.selectedUnits.forEach((unit) => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
if (!(unit.getName() in unitOccurences[unit.getCoalition()]))
|
||||
unitOccurences[unit.getCoalition()][unit.getName()] = { occurences: 1, label: unit.getBlueprint()?.label };
|
||||
else unitOccurences[unit.getCoalition()][unit.getName()].occurences++;
|
||||
@@ -196,6 +202,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
return category === "Helicopter";
|
||||
});
|
||||
|
||||
// TODO: use constants
|
||||
const minAltitude = 0;
|
||||
const minSpeed = 0;
|
||||
|
||||
@@ -220,13 +227,13 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
return (
|
||||
<Menu
|
||||
open={props.open}
|
||||
title={appState.selectedUnits.length > 0 ? `Units selected (x${appState.selectedUnits.length})` : `No units selected`}
|
||||
title={selectedUnits.length > 0 ? `Units selected (x${selectedUnits.length})` : `No units selected`}
|
||||
onClose={props.onClose}
|
||||
canBeHidden={true}
|
||||
>
|
||||
<>
|
||||
{/* ============== Selection tool START ============== */}
|
||||
{appState.selectedUnits.length == 0 && (
|
||||
{selectedUnits.length == 0 && (
|
||||
<div className="flex flex-col gap-4 p-4">
|
||||
<div className="text-lg text-bold text-gray-200">Selection tool</div>
|
||||
<div className="text-sm text-gray-400">
|
||||
@@ -429,7 +436,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
{/* */}
|
||||
<>
|
||||
{/* ============== Unit control menu START ============== */}
|
||||
{appState.selectedUnits.length > 0 && (
|
||||
{selectedUnits.length > 0 && (
|
||||
<>
|
||||
{/* ============== Units list START ============== */}
|
||||
<div
|
||||
@@ -522,7 +529,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
leftLabel={"AGL"}
|
||||
rightLabel={"ASL"}
|
||||
onClick={() => {
|
||||
appState.selectedUnits.forEach((unit) => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
unit.setAltitudeType(selectedUnitsData.desiredAltitudeType === "ASL" ? "AGL" : "ASL");
|
||||
setSelectedUnitsData({
|
||||
...selectedUnitsData,
|
||||
@@ -534,7 +541,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
</div>
|
||||
<OlRangeSlider
|
||||
onChange={(ev) => {
|
||||
appState.selectedUnits.forEach((unit) => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
unit.setAltitude(ftToM(Number(ev.target.value)));
|
||||
setSelectedUnitsData({
|
||||
...selectedUnitsData,
|
||||
@@ -584,7 +591,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
leftLabel={"GS"}
|
||||
rightLabel={"CAS"}
|
||||
onClick={() => {
|
||||
appState.selectedUnits.forEach((unit) => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
unit.setSpeedType(selectedUnitsData.desiredSpeedType === "CAS" ? "GS" : "CAS");
|
||||
setSelectedUnitsData({
|
||||
...selectedUnitsData,
|
||||
@@ -597,7 +604,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
</div>
|
||||
<OlRangeSlider
|
||||
onChange={(ev) => {
|
||||
appState.selectedUnits.forEach((unit) => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
unit.setSpeed(knotsToMs(Number(ev.target.value)));
|
||||
setSelectedUnitsData({
|
||||
...selectedUnitsData,
|
||||
@@ -613,8 +620,8 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
</div>
|
||||
{/* ============== Airspeed selector END ============== */}
|
||||
{/* ============== Rules of Engagement START ============== */}
|
||||
{!(appState.selectedUnits.length === 1 && appState.selectedUnits[0].isTanker()) &&
|
||||
!(appState.selectedUnits.length === 1 && appState.selectedUnits[0].isAWACS()) && (
|
||||
{!(selectedUnits.length === 1 && selectedUnits[0].isTanker()) &&
|
||||
!(selectedUnits.length === 1 && selectedUnits[0].isAWACS()) && (
|
||||
<div className="flex flex-col gap-2">
|
||||
<span
|
||||
className={`
|
||||
@@ -630,7 +637,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
<OlButtonGroupItem
|
||||
key={idx}
|
||||
onClick={() => {
|
||||
appState.selectedUnits.forEach((unit) => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
unit.setROE(ROEs[idx]);
|
||||
setSelectedUnitsData({
|
||||
...selectedUnitsData,
|
||||
@@ -668,7 +675,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
<OlButtonGroupItem
|
||||
key={idx}
|
||||
onClick={() => {
|
||||
appState.selectedUnits.forEach((unit) => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
unit.setReactionToThreat(reactionsToThreat[idx]);
|
||||
setSelectedUnitsData({
|
||||
...selectedUnitsData,
|
||||
@@ -700,7 +707,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
<OlButtonGroupItem
|
||||
key={idx}
|
||||
onClick={() => {
|
||||
appState.selectedUnits.forEach((unit) => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
unit.setEmissionsCountermeasures(emissionsCountermeasures[idx]);
|
||||
setSelectedUnitsData({
|
||||
...selectedUnitsData,
|
||||
@@ -736,7 +743,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
<OlToggle
|
||||
toggled={selectedUnitsData.isActiveTanker}
|
||||
onClick={() => {
|
||||
appState.selectedUnits.forEach((unit) => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
unit.setAdvancedOptions(
|
||||
!selectedUnitsData.isActiveTanker,
|
||||
unit.getIsActiveAWACS(),
|
||||
@@ -770,7 +777,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
<OlToggle
|
||||
toggled={selectedUnitsData.isActiveAWACS}
|
||||
onClick={() => {
|
||||
appState.selectedUnits.forEach((unit) => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
unit.setAdvancedOptions(
|
||||
unit.getIsActiveTanker(),
|
||||
!selectedUnitsData.isActiveAWACS,
|
||||
@@ -789,7 +796,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
)}
|
||||
{/* ============== Tanker and AWACS available button END ============== */}
|
||||
{/* ============== Advanced settings buttons START ============== */}
|
||||
{appState.selectedUnits.length === 1 && (appState.selectedUnits[0].isTanker() || appState.selectedUnits[0].isAWACS()) && (
|
||||
{selectedUnits.length === 1 && (selectedUnits[0].isTanker() || selectedUnits[0].isAWACS()) && (
|
||||
<div className="flex content-center justify-between">
|
||||
<button
|
||||
className={`
|
||||
@@ -800,13 +807,13 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
`}
|
||||
onClick={() => {
|
||||
setActiveAdvancedSettings({
|
||||
radio: JSON.parse(JSON.stringify(appState.selectedUnits[0].getRadio())),
|
||||
TACAN: JSON.parse(JSON.stringify(appState.selectedUnits[0].getTACAN())),
|
||||
radio: JSON.parse(JSON.stringify(selectedUnits[0].getRadio())),
|
||||
TACAN: JSON.parse(JSON.stringify(selectedUnits[0].getTACAN())),
|
||||
});
|
||||
setShowAdvancedSettings(true);
|
||||
}}
|
||||
>
|
||||
<FaCog className="my-auto" /> {appState.selectedUnits[0].isTanker() ? "Configure tanker settings" : "Configure AWACS settings"}
|
||||
<FaCog className="my-auto" /> {selectedUnits[0].isTanker() ? "Configure tanker settings" : "Configure AWACS settings"}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
@@ -834,7 +841,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
<OlToggle
|
||||
toggled={selectedUnitsData.scenicAAA}
|
||||
onClick={() => {
|
||||
appState.selectedUnits.forEach((unit) => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
selectedUnitsData.scenicAAA ? unit.changeSpeed("stop") : unit.scenicAAA();
|
||||
setSelectedUnitsData({
|
||||
...selectedUnitsData,
|
||||
@@ -859,7 +866,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
<OlToggle
|
||||
toggled={selectedUnitsData.missOnPurpose}
|
||||
onClick={() => {
|
||||
appState.selectedUnits.forEach((unit) => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
selectedUnitsData.missOnPurpose ? unit.changeSpeed("stop") : unit.missOnPurpose();
|
||||
setSelectedUnitsData({
|
||||
...selectedUnitsData,
|
||||
@@ -888,7 +895,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
<OlButtonGroupItem
|
||||
key={idx}
|
||||
onClick={() => {
|
||||
appState.selectedUnits.forEach((unit) => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
unit.setShotsScatter(idx + 1);
|
||||
setSelectedUnitsData({
|
||||
...selectedUnitsData,
|
||||
@@ -920,7 +927,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
<OlButtonGroupItem
|
||||
key={idx}
|
||||
onClick={() => {
|
||||
appState.selectedUnits.forEach((unit) => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
unit.setShotsIntensity(idx + 1);
|
||||
setSelectedUnitsData({
|
||||
...selectedUnitsData,
|
||||
@@ -950,7 +957,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
<OlCoalitionToggle
|
||||
coalition={selectedUnitsData.operateAs as Coalition}
|
||||
onClick={() => {
|
||||
appState.selectedUnits.forEach((unit) => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
unit.setOperateAs(selectedUnitsData.operateAs === "blue" ? "red" : "blue");
|
||||
setSelectedUnitsData({
|
||||
...selectedUnitsData,
|
||||
@@ -975,7 +982,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
<OlToggle
|
||||
toggled={selectedUnitsData.followRoads}
|
||||
onClick={() => {
|
||||
appState.selectedUnits.forEach((unit) => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
unit.setFollowRoads(!selectedUnitsData.followRoads);
|
||||
setSelectedUnitsData({
|
||||
...selectedUnitsData,
|
||||
@@ -999,7 +1006,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
<OlToggle
|
||||
toggled={selectedUnitsData.onOff}
|
||||
onClick={() => {
|
||||
appState.selectedUnits.forEach((unit) => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
unit.setOnOff(!selectedUnitsData.onOff);
|
||||
setSelectedUnitsData({
|
||||
...selectedUnitsData,
|
||||
@@ -1022,11 +1029,11 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
>
|
||||
Loudspeakers
|
||||
</span>
|
||||
{appState.audioManagerState ? (
|
||||
{audioManagerState ? (
|
||||
<OlToggle
|
||||
toggled={selectedUnitsData.isAudioSink}
|
||||
onClick={() => {
|
||||
appState.selectedUnits.forEach((unit) => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
if (!selectedUnitsData.isAudioSink) {
|
||||
getApp()?.getAudioManager().addUnitSink(unit);
|
||||
setSelectedUnitsData({
|
||||
@@ -1077,14 +1084,14 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
<div className="flex content-center gap-2">
|
||||
<OlDropdown
|
||||
label={
|
||||
appState.selectedUnits[0].isAWACS()
|
||||
selectedUnits[0].isAWACS()
|
||||
? ["Overlord", "Magic", "Wizard", "Focus", "Darkstar"][activeAdvancedSettings ? activeAdvancedSettings.radio.callsign - 1 : 0]
|
||||
: ["Texaco", "Arco", "Shell"][activeAdvancedSettings ? activeAdvancedSettings.radio.callsign - 1 : 0]
|
||||
}
|
||||
className="my-auto w-full"
|
||||
>
|
||||
<>
|
||||
{appState.selectedUnits[0].isAWACS() && (
|
||||
{selectedUnits[0].isAWACS() && (
|
||||
<>
|
||||
{["Overlord", "Magic", "Wizard", "Focus", "Darkstar"].map((name, idx) => {
|
||||
return (
|
||||
@@ -1103,7 +1110,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
)}
|
||||
</>
|
||||
<>
|
||||
{appState.selectedUnits[0].isTanker() && (
|
||||
{selectedUnits[0].isTanker() && (
|
||||
<>
|
||||
{["Texaco", "Arco", "Shell"].map((name, idx) => {
|
||||
return (
|
||||
@@ -1238,12 +1245,12 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
`}
|
||||
onClick={() => {
|
||||
if (activeAdvancedSettings)
|
||||
appState.selectedUnits[0].setAdvancedOptions(
|
||||
appState.selectedUnits[0].getIsActiveTanker(),
|
||||
appState.selectedUnits[0].getIsActiveAWACS(),
|
||||
selectedUnits[0].setAdvancedOptions(
|
||||
selectedUnits[0].getIsActiveTanker(),
|
||||
selectedUnits[0].getIsActiveAWACS(),
|
||||
activeAdvancedSettings.TACAN,
|
||||
activeAdvancedSettings.radio,
|
||||
appState.selectedUnits[0].getGeneralSettings()
|
||||
selectedUnits[0].getGeneralSettings()
|
||||
);
|
||||
setActiveAdvancedSettings(null);
|
||||
setShowAdvancedSettings(false);
|
||||
@@ -1276,7 +1283,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
{/* ============== Unit basic options END ============== */}
|
||||
<>
|
||||
{/* ============== Fuel/payload/radio section START ============== */}
|
||||
{appState.selectedUnits.length === 1 && (
|
||||
{selectedUnits.length === 1 && (
|
||||
<div
|
||||
className={`
|
||||
flex flex-col gap-4 border-l-4 border-l-olympus-100
|
||||
@@ -1287,27 +1294,23 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
<div
|
||||
className={`
|
||||
flex content-center gap-2 rounded-full
|
||||
${appState.selectedUnits[0].getFuel() > 40 && `
|
||||
bg-green-700
|
||||
`}
|
||||
${selectedUnits[0].getFuel() > 40 && `bg-green-700`}
|
||||
${
|
||||
appState.selectedUnits[0].getFuel() > 10 &&
|
||||
appState.selectedUnits[0].getFuel() <= 40 &&
|
||||
selectedUnits[0].getFuel() > 10 &&
|
||||
selectedUnits[0].getFuel() <= 40 &&
|
||||
`bg-yellow-700`
|
||||
}
|
||||
${appState.selectedUnits[0].getFuel() <= 10 && `
|
||||
bg-red-700
|
||||
`}
|
||||
${selectedUnits[0].getFuel() <= 10 && `bg-red-700`}
|
||||
px-2 py-1 text-sm font-bold text-white
|
||||
`}
|
||||
>
|
||||
<FaGasPump className="my-auto" />
|
||||
{appState.selectedUnits[0].getFuel()}%
|
||||
{selectedUnits[0].getFuel()}%
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
{appState.selectedUnits[0].isControlledByOlympus() && (appState.selectedUnits[0].isTanker() || appState.selectedUnits[0].isAWACS()) && (
|
||||
{selectedUnits[0].isControlledByOlympus() && (selectedUnits[0].isTanker() || selectedUnits[0].isAWACS()) && (
|
||||
<>
|
||||
{/* ============== Radio section START ============== */}
|
||||
<div className="flex content-center justify-between">
|
||||
@@ -1329,7 +1332,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
dark:text-gray-300
|
||||
`}
|
||||
>
|
||||
{`${appState.selectedUnits[0].isTanker() ? ["Texaco", "Arco", "Shell"][appState.selectedUnits[0].getRadio().callsign - 1] : ["Overlord", "Magic", "Wizard", "Focus", "Darkstar"][appState.selectedUnits[0].getRadio().callsign - 1]}-${appState.selectedUnits[0].getRadio().callsignNumber}`}
|
||||
{`${selectedUnits[0].isTanker() ? ["Texaco", "Arco", "Shell"][selectedUnits[0].getRadio().callsign - 1] : ["Overlord", "Magic", "Wizard", "Focus", "Darkstar"][selectedUnits[0].getRadio().callsign - 1]}-${selectedUnits[0].getRadio().callsignNumber}`}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1352,7 +1355,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
dark:text-gray-300
|
||||
`}
|
||||
>
|
||||
{`${(appState.selectedUnits[0].getRadio().frequency / 1000000).toFixed(3)} MHz`}
|
||||
{`${(selectedUnits[0].getRadio().frequency / 1000000).toFixed(3)} MHz`}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1376,8 +1379,8 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
dark:text-gray-300
|
||||
`}
|
||||
>
|
||||
{appState.selectedUnits[0].getTACAN().isOn
|
||||
? `${appState.selectedUnits[0].getTACAN().channel}${appState.selectedUnits[0].getTACAN().XY} ${appState.selectedUnits[0].getTACAN().callsign}`
|
||||
{selectedUnits[0].getTACAN().isOn
|
||||
? `${selectedUnits[0].getTACAN().channel}${selectedUnits[0].getTACAN().XY} ${selectedUnits[0].getTACAN().callsign}`
|
||||
: "TACAN OFF"}
|
||||
</div>
|
||||
</div>
|
||||
@@ -1386,9 +1389,9 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
</>
|
||||
)}
|
||||
{/* ============== Payload section START ============== */}
|
||||
{!appState.selectedUnits[0].isTanker() &&
|
||||
!appState.selectedUnits[0].isAWACS() &&
|
||||
appState.selectedUnits[0].getAmmo().map((ammo, idx) => {
|
||||
{!selectedUnits[0].isTanker() &&
|
||||
!selectedUnits[0].isAWACS() &&
|
||||
selectedUnits[0].getAmmo().map((ammo, idx) => {
|
||||
return (
|
||||
<div className="flex content-center gap-2" key={idx}>
|
||||
<div
|
||||
|
||||
@@ -8,7 +8,7 @@ import { OlDropdownItem, OlDropdown } from "../components/oldropdown";
|
||||
import { LoadoutBlueprint, UnitBlueprint } from "../../interfaces";
|
||||
import { Coalition } from "../../types/types";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { ftToM, getUnitCategoryByBlueprint } from "../../other/utils";
|
||||
import { ftToM } from "../../other/utils";
|
||||
import { LatLng } from "leaflet";
|
||||
import { Airbase } from "../../mission/airbase";
|
||||
import { OlympusState, SpawnSubState } from "../../constants/constants";
|
||||
@@ -39,7 +39,7 @@ export function UnitSpawnMenu(props: { blueprint: UnitBlueprint; spawnAtLocation
|
||||
getApp()
|
||||
?.getMap()
|
||||
?.setSpawnRequestTable({
|
||||
category: getUnitCategoryByBlueprint(props.blueprint),
|
||||
category: props.blueprint.category,
|
||||
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
|
||||
@@ -58,13 +58,13 @@ export function UnitSpawnMenu(props: { blueprint: UnitBlueprint; spawnAtLocation
|
||||
if (getApp().getState() === OlympusState.SPAWN) getApp().setState(OlympusState.IDLE);
|
||||
}
|
||||
}
|
||||
});
|
||||
}, [props.blueprint, spawnAltitude, spawnLoadoutName, spawnCoalition]);
|
||||
|
||||
function spawnAtAirbase() {
|
||||
getApp()
|
||||
.getUnitsManager()
|
||||
.spawnUnits(
|
||||
getUnitCategoryByBlueprint(props.blueprint),
|
||||
props.blueprint.category,
|
||||
[
|
||||
{
|
||||
unitType: props.blueprint.name,
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import "./ui.css";
|
||||
|
||||
import { StateProvider } from "../statecontext";
|
||||
|
||||
import { Header } from "./panels/header";
|
||||
import { SpawnMenu } from "./panels/spawnmenu";
|
||||
import { UnitControlMenu } from "./panels/unitcontrolmenu";
|
||||
@@ -13,10 +11,8 @@ import { MapHiddenTypes, MapOptions } from "../types/types";
|
||||
import {
|
||||
BLUE_COMMANDER,
|
||||
GAME_MASTER,
|
||||
MAP_HIDDEN_TYPES_DEFAULTS,
|
||||
MAP_OPTIONS_DEFAULTS,
|
||||
NO_SUBSTATE,
|
||||
OlympusEvent,
|
||||
OlympusState,
|
||||
OlympusSubState,
|
||||
RED_COMMANDER,
|
||||
@@ -26,7 +22,7 @@ import { getApp, setupApp } from "../olympusapp";
|
||||
import { LoginModal } from "./modals/login";
|
||||
import { sha256 } from "js-sha256";
|
||||
import { MiniMapPanel } from "./panels/minimappanel";
|
||||
import { UnitMouseControlBar } from "./panels/unitmousecontrolbar";
|
||||
import { UnitControlBar } from "./panels/unitcontrolbar";
|
||||
import { DrawingMenu } from "./panels/drawingmenu";
|
||||
import { ControlsPanel } from "./panels/controlspanel";
|
||||
import { MapContextMenu } from "./contextmenus/mapcontextmenu";
|
||||
@@ -40,24 +36,8 @@ import { UnitExplosionMenu } from "./panels/unitexplosionmenu";
|
||||
import { JTACMenu } from "./panels/jtacmenu";
|
||||
import {
|
||||
AppStateChangedEvent,
|
||||
AudioManagerStateChangedEvent,
|
||||
AudioSinksChangedEvent,
|
||||
AudioSourcesChangedEvent,
|
||||
ConfigLoadedEvent,
|
||||
ContextActionChangedEvent,
|
||||
ContextActionSetChangedEvent,
|
||||
HiddenTypesChangedEvent,
|
||||
MapOptionsChangedEvent,
|
||||
MapSourceChangedEvent,
|
||||
SelectedUnitsChangedEvent,
|
||||
ServerStatusUpdatedEvent,
|
||||
UnitSelectedEvent,
|
||||
MapOptionsChangedEvent
|
||||
} from "../events";
|
||||
import { ServerStatus } from "../interfaces";
|
||||
import { AudioSource } from "../audio/audiosource";
|
||||
import { AudioSink } from "../audio/audiosink";
|
||||
import { ContextAction } from "../unit/contextaction";
|
||||
import { ContextActionSet } from "../unit/contextactionset";
|
||||
|
||||
export type OlympusUIState = {
|
||||
mainMenuVisible: boolean;
|
||||
@@ -74,17 +54,7 @@ export type OlympusUIState = {
|
||||
export function UI() {
|
||||
const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED);
|
||||
const [appSubState, setAppSubState] = useState(NO_SUBSTATE as OlympusSubState);
|
||||
const [mapHiddenTypes, setMapHiddenTypes] = useState(MAP_HIDDEN_TYPES_DEFAULTS);
|
||||
const [mapOptions, setMapOptions] = useState(MAP_OPTIONS_DEFAULTS);
|
||||
const [mapSources, setMapSources] = useState([] as string[]);
|
||||
const [activeMapSource, setActiveMapSource] = useState("");
|
||||
const [selectedUnits, setSelectedUnits] = useState([] as Unit[]);
|
||||
const [audioSources, setAudioSources] = useState([] as AudioSource[]);
|
||||
const [audioSinks, setAudioSinks] = useState([] as AudioSink[]);
|
||||
const [audioManagerState, setAudioManagerState] = useState(false);
|
||||
const [serverStatus, setServerStatus] = useState({} as ServerStatus);
|
||||
const [contextActionSet, setContextActionsSet] = useState(null as ContextActionSet | null);
|
||||
const [contextAction, setContextActions] = useState(null as ContextAction | null);
|
||||
|
||||
const [checkingPassword, setCheckingPassword] = useState(false);
|
||||
const [loginError, setLoginError] = useState(false);
|
||||
@@ -105,22 +75,7 @@ export function UI() {
|
||||
setAppState(state);
|
||||
setAppSubState(subState);
|
||||
});
|
||||
ConfigLoadedEvent.on(() => {
|
||||
let config = getApp().getConfig();
|
||||
var sources = Object.keys(config.mapMirrors).concat(Object.keys(config.mapLayers));
|
||||
setMapSources(sources);
|
||||
setActiveMapSource(sources[0]);
|
||||
});
|
||||
HiddenTypesChangedEvent.on((hiddenTypes) => setMapHiddenTypes({ ...hiddenTypes }));
|
||||
MapOptionsChangedEvent.on((mapOptions) => setMapOptions({ ...mapOptions }));
|
||||
MapSourceChangedEvent.on((source) => setActiveMapSource(source));
|
||||
SelectedUnitsChangedEvent.on((units) => setSelectedUnits(units));
|
||||
AudioSourcesChangedEvent.on((sources) => setAudioSources(sources));
|
||||
AudioSinksChangedEvent.on((sinks) => setAudioSinks(sinks));
|
||||
AudioManagerStateChangedEvent.on((state) => setAudioManagerState(state));
|
||||
ServerStatusUpdatedEvent.on((status) => setServerStatus(status));
|
||||
ContextActionSetChangedEvent.on((contextActionSet) => setContextActionsSet(contextActionSet));
|
||||
ContextActionChangedEvent.on((contextAction) => setContextActions(contextAction));
|
||||
|
||||
document.addEventListener("showProtectionPrompt", (ev: CustomEventInit) => {
|
||||
setProtectionPromptVisible(true);
|
||||
@@ -168,97 +123,75 @@ export function UI() {
|
||||
`}
|
||||
onLoad={setupApp}
|
||||
>
|
||||
<StateProvider
|
||||
value={{
|
||||
appState,
|
||||
appSubState,
|
||||
mapOptions,
|
||||
mapHiddenTypes,
|
||||
mapSources,
|
||||
activeMapSource,
|
||||
selectedUnits,
|
||||
audioSources,
|
||||
audioSinks,
|
||||
audioManagerState,
|
||||
serverStatus,
|
||||
contextActionSet,
|
||||
contextAction,
|
||||
}}
|
||||
>
|
||||
<Header />
|
||||
<div className="flex h-full w-full flex-row-reverse">
|
||||
{appState === OlympusState.LOGIN && (
|
||||
<>
|
||||
<div
|
||||
className={`
|
||||
fixed left-0 top-0 z-30 h-full w-full bg-[#111111]/95
|
||||
`}
|
||||
></div>
|
||||
<LoginModal
|
||||
onLogin={(password) => {
|
||||
checkPassword(password);
|
||||
}}
|
||||
onContinue={(username) => {
|
||||
connect(username);
|
||||
}}
|
||||
onBack={() => {
|
||||
setCommandMode(null);
|
||||
}}
|
||||
checkingPassword={checkingPassword}
|
||||
loginError={loginError}
|
||||
commandMode={commandMode}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{protectionPromptVisible && (
|
||||
<>
|
||||
<div
|
||||
className={`
|
||||
fixed left-0 top-0 z-30 h-full w-full bg-[#111111]/95
|
||||
`}
|
||||
></div>
|
||||
<ProtectionPrompt
|
||||
onContinue={(units) => {
|
||||
protectionCallback(units);
|
||||
setProtectionPromptVisible(false);
|
||||
}}
|
||||
onBack={() => {
|
||||
setProtectionPromptVisible(false);
|
||||
}}
|
||||
units={protectionUnits}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<div id="map-container" className="z-0 h-full w-screen" />
|
||||
<MainMenu open={appState === OlympusState.MAIN_MENU} onClose={() => getApp().setState(OlympusState.IDLE)} />
|
||||
<SpawnMenu open={appState === OlympusState.SPAWN} onClose={() => getApp().setState(OlympusState.IDLE)} />
|
||||
<OptionsMenu open={appState === OlympusState.OPTIONS} onClose={() => getApp().setState(OlympusState.IDLE)} options={mapOptions} />
|
||||
<Header />
|
||||
<div className="flex h-full w-full flex-row-reverse">
|
||||
{appState === OlympusState.LOGIN && (
|
||||
<>
|
||||
<div
|
||||
className={`fixed left-0 top-0 z-30 h-full w-full bg-[#111111]/95`}
|
||||
></div>
|
||||
<LoginModal
|
||||
onLogin={(password) => {
|
||||
checkPassword(password);
|
||||
}}
|
||||
onContinue={(username) => {
|
||||
connect(username);
|
||||
}}
|
||||
onBack={() => {
|
||||
setCommandMode(null);
|
||||
}}
|
||||
checkingPassword={checkingPassword}
|
||||
loginError={loginError}
|
||||
commandMode={commandMode}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{protectionPromptVisible && (
|
||||
<>
|
||||
<div
|
||||
className={`fixed left-0 top-0 z-30 h-full w-full bg-[#111111]/95`}
|
||||
></div>
|
||||
<ProtectionPrompt
|
||||
onContinue={(units) => {
|
||||
protectionCallback(units);
|
||||
setProtectionPromptVisible(false);
|
||||
}}
|
||||
onBack={() => {
|
||||
setProtectionPromptVisible(false);
|
||||
}}
|
||||
units={protectionUnits}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<div id="map-container" className="z-0 h-full w-screen" />
|
||||
<MainMenu open={appState === OlympusState.MAIN_MENU} onClose={() => getApp().setState(OlympusState.IDLE)} />
|
||||
<SpawnMenu open={appState === OlympusState.SPAWN} onClose={() => getApp().setState(OlympusState.IDLE)} />
|
||||
<OptionsMenu open={appState === OlympusState.OPTIONS} onClose={() => getApp().setState(OlympusState.IDLE)} options={mapOptions} /* TODO remove *//>
|
||||
|
||||
<UnitControlMenu
|
||||
open={appState === OlympusState.UNIT_CONTROL && appSubState !== UnitControlSubState.FORMATION}
|
||||
onClose={() => getApp().setState(OlympusState.IDLE)}
|
||||
/>
|
||||
<FormationMenu
|
||||
open={appState === OlympusState.UNIT_CONTROL && appSubState === UnitControlSubState.FORMATION}
|
||||
leader={formationLeader}
|
||||
wingmen={formationWingmen}
|
||||
onClose={() => getApp().setState(OlympusState.IDLE)}
|
||||
/>
|
||||
<UnitControlMenu
|
||||
open={appState === OlympusState.UNIT_CONTROL && appSubState !== UnitControlSubState.FORMATION}
|
||||
onClose={() => getApp().setState(OlympusState.IDLE)}
|
||||
/>
|
||||
<FormationMenu
|
||||
open={appState === OlympusState.UNIT_CONTROL && appSubState === UnitControlSubState.FORMATION}
|
||||
leader={formationLeader}
|
||||
wingmen={formationWingmen}
|
||||
onClose={() => getApp().setState(OlympusState.IDLE)}
|
||||
/>
|
||||
|
||||
<DrawingMenu open={appState === OlympusState.DRAW} onClose={() => getApp().setState(OlympusState.IDLE)} />
|
||||
<AirbaseMenu open={appState === OlympusState.AIRBASE} onClose={() => getApp().setState(OlympusState.IDLE)} airbase={airbase} />
|
||||
<AudioMenu open={appState === OlympusState.AUDIO} onClose={() => getApp().setState(OlympusState.IDLE)} />
|
||||
<DrawingMenu open={appState === OlympusState.DRAW} onClose={() => getApp().setState(OlympusState.IDLE)} />
|
||||
<AirbaseMenu open={appState === OlympusState.AIRBASE} onClose={() => getApp().setState(OlympusState.IDLE)} airbase={airbase} /* TODO remove */ />
|
||||
<AudioMenu open={appState === OlympusState.AUDIO} onClose={() => getApp().setState(OlympusState.IDLE)} />
|
||||
|
||||
{/* TODO} <UnitExplosionMenu open={appState === OlympusState.MAIN_MENU} units={unitExplosionUnits} onClose={() => getApp().setState(OlympusState.IDLE)} /> {*/}
|
||||
<JTACMenu open={appState === OlympusState.JTAC} onClose={() => getApp().setState(OlympusState.IDLE)} />
|
||||
{/* TODO} <UnitExplosionMenu open={appState === OlympusState.MAIN_MENU} units={unitExplosionUnits} onClose={() => getApp().setState(OlympusState.IDLE)} /> {*/}
|
||||
<JTACMenu open={appState === OlympusState.JTAC} onClose={() => getApp().setState(OlympusState.IDLE)} />
|
||||
|
||||
<MiniMapPanel />
|
||||
<ControlsPanel />
|
||||
<UnitMouseControlBar />
|
||||
<MapContextMenu />
|
||||
<SideBar />
|
||||
</div>
|
||||
</StateProvider>
|
||||
<MiniMapPanel />
|
||||
<ControlsPanel />
|
||||
<UnitControlBar />
|
||||
<MapContextMenu />
|
||||
<SideBar />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user