Refactored app state

This commit is contained in:
Davide Passoni
2024-10-21 19:02:33 +02:00
parent 4946807d88
commit 14c0a2f1e8
27 changed files with 1070 additions and 1056 deletions

View File

@@ -2,8 +2,7 @@ import React, { useEffect, useRef, useState } from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faArrowCircleDown } from "@fortawesome/free-solid-svg-icons";
export function OlAccordion(props: { title: string; children?: JSX.Element | JSX.Element[]; showArrows?: boolean; className?: string }) {
const [open, setOpen] = useState(false);
export function OlAccordion(props: { title: string; open: boolean, onClick: () => void; children?: JSX.Element | JSX.Element[]; showArrows?: boolean; className?: string }) {
const [scrolledUp, setScrolledUp] = useState(true);
const [scrolledDown, setScrolledDown] = useState(false);
@@ -29,7 +28,7 @@ export function OlAccordion(props: { title: string; children?: JSX.Element | JSX
<h3>
<button
type="button"
onClick={() => setOpen(!open)}
onClick={() => props.onClick()}
className={`
${props.className ?? ""}
flex w-full items-center justify-between gap-3 border-gray-200 py-2
@@ -56,7 +55,7 @@ export function OlAccordion(props: { title: string; children?: JSX.Element | JSX
</svg>
</button>
</h3>
<div className={open ? "" : "hidden"}>
<div className={props.open ? "" : "hidden"}>
{props.showArrows && (
<div className="rotate-180">
{!scrolledUp && (

View File

@@ -1,10 +1,9 @@
import React, { useEffect, useRef, useState } from "react";
import { Unit } from "../../unit/unit";
import { ContextActionSet } from "../../unit/contextactionset";
import { OlStateButton } from "../components/olstatebutton";
import { getApp } from "../../olympusapp";
import { ContextAction } from "../../unit/contextaction";
import { CONTEXT_ACTION, CONTEXT_ACTION_COLORS } from "../../constants/constants";
import { CONTEXT_ACTION_COLORS } from "../../constants/constants";
import { OlDropdownItem } from "../components/oldropdown";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { LatLng } from "leaflet";

View File

@@ -3,7 +3,6 @@ import { Menu } from "./components/menu";
import { Coalition } from "../../types/types";
import { Airbase } from "../../mission/airbase";
import { FaArrowLeft, FaCompass } from "react-icons/fa6";
import { getUnitsByLabel } from "../../other/utils";
import { UnitBlueprint } from "../../interfaces";
import { OlSearchBar } from "../components/olsearchbar";
import { OlAccordion } from "../components/olaccordion";
@@ -15,7 +14,7 @@ export function AirbaseMenu(props: { open: boolean; onClose: () => void; airbase
const [blueprint, setBlueprint] = useState(null as null | UnitBlueprint);
const [filterString, setFilterString] = useState("");
const [filteredAircraft, filteredHelicopters, _1, _2, _3] = getUnitsByLabel(filterString);
const [filteredAircraft, filteredHelicopters] = [{}, {}] // TODOgetUnitsByLabel(filterString);
return (
<Menu title={props.airbase?.getName() ?? "No airbase selected"} open={props.open} onClose={props.onClose} showBackButton={false} canBeHidden={true}>
@@ -47,7 +46,9 @@ export function AirbaseMenu(props: { open: boolean; onClose: () => void; airbase
<span className="text-gray-400">Elevation</span>
<span>{props.airbase?.getChartData().elevation !== "" ? props.airbase?.getChartData().elevation : "N/A"}ft</span>
</div>
{
// TODO
}
<OlAccordion title={`Runways`} className="!p-0 !text-gray-400">
<div className="flex flex-col gap-2">
{props.airbase?.getChartData().runways.map((runway, idx) => {

View File

@@ -19,7 +19,7 @@ export function ControlsPanel(props: {}) {
});
useEffect(() => {
document.addEventListener("mapStateChanged", (ev) => {
document.addEventListener("appStateChanged", (ev) => {
setControls(getApp().getMap().getCurrentControls());
});
}, []);

View File

@@ -1,8 +1,7 @@
import React, { useEffect, useState } from "react";
import { Menu } from "./components/menu";
import { FaQuestionCircle, FaRegCircle, FaTrash } from "react-icons/fa";
import { FaQuestionCircle, FaTrash } from "react-icons/fa";
import { getApp } from "../../olympusapp";
import { COALITIONAREA_DRAW_CIRCLE, COALITIONAREA_DRAW_POLYGON, COALITIONAREA_EDIT, IDLE } from "../../constants/constants";
import { OlStateButton } from "../components/olstatebutton";
import { faDrawPolygon } from "@fortawesome/free-solid-svg-icons";
import { faCircle } from "@fortawesome/free-regular-svg-icons";
@@ -13,10 +12,11 @@ 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";
export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
const [drawingPolygon, setDrawingPolygon] = useState(false);
const [drawingCircle, setDrawingCircle] = useState(false);
const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED);
const [appSubState, setAppSubState] = useState(NO_SUBSTATE);
const [activeCoalitionArea, setActiveCoalitionArea] = useState(null as null | CoalitionPolygon | CoalitionCircle);
const [areaCoalition, setAreaCoalition] = useState("blue" as Coalition);
const [IADSDensity, setIADSDensity] = useState(50);
@@ -27,37 +27,32 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
const [erasSelection, setErasSelection] = useState({});
const [rangesSelection, setRangesSelection] = useState({});
const [showPolygonTip, setShowPolygonTip] = useState(true);
const [showCircleTip, setShowCircleTip] = useState(true);
useEffect(() => {
if (getApp()) {
/* If we are not in polygon drawing mode, force the draw polygon button off */
if (drawingPolygon && getApp().getMap().getState() !== COALITIONAREA_DRAW_POLYGON) setDrawingPolygon(false);
/* If we are not in circle drawing mode, force the draw circle button off */
if (drawingCircle && getApp().getMap().getState() !== COALITIONAREA_DRAW_CIRCLE) setDrawingCircle(false);
/* If we are not in any drawing mode, force the map in edit mode */
if (props.open && !drawingPolygon && !drawingCircle) getApp().getMap().setState(COALITIONAREA_EDIT);
/* Align the state of the coalition toggle to the coalition of the area */
if (activeCoalitionArea && activeCoalitionArea?.getCoalition() !== areaCoalition) setAreaCoalition(activeCoalitionArea?.getCoalition());
// TODO
///* If we are not in polygon drawing mode, force the draw polygon button off */
//if (drawingPolygon && getApp().getState() !== COALITIONAREA_DRAW_POLYGON) setDrawingPolygon(false);
//
///* If we are not in circle drawing mode, force the draw circle button off */
//if (drawingCircle && getApp().getState() !== COALITIONAREA_DRAW_CIRCLE) setDrawingCircle(false);
//
///* If we are not in any drawing mode, force the map in edit mode */
//if (props.open && !drawingPolygon && !drawingCircle) getApp().getMap().setState(COALITIONAREA_EDIT);
//
///* Align the state of the coalition toggle to the coalition of the area */
//if (activeCoalitionArea && activeCoalitionArea?.getCoalition() !== areaCoalition) setAreaCoalition(activeCoalitionArea?.getCoalition());
}
});
useEffect(() => {
document.addEventListener("mapStateChanged", (event: any) => {
if (drawingPolygon && getApp().getMap().getState() !== COALITIONAREA_DRAW_POLYGON) setDrawingPolygon(false);
if (getApp().getMap().getState() == COALITIONAREA_EDIT) {
setActiveCoalitionArea(getApp().getMap().getSelectedCoalitionArea() ?? null);
}
});
document.addEventListener("coalitionAreaSelected", (event: any) => {
setActiveCoalitionArea(event.detail);
});
document.addEventListener("appStateChanged", (ev: CustomEventInit) => {
setAppState(ev.detail.state);
setAppSubState(ev.detail.subState);
});
}, []);
return (
@@ -68,71 +63,20 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
canBeHidden={true}
showBackButton={activeCoalitionArea !== null}
onBack={() => {
setActiveCoalitionArea(null);
getApp().getMap().deselectAllCoalitionAreas();
getApp().setState(OlympusState.DRAW, DrawSubState.NO_SUBSTATE)
}}
>
<>
{activeCoalitionArea === null && !drawingPolygon && !drawingCircle && (
<>
<div className="p-4 text-sm text-gray-400">
The draw tool allows you to quickly draw areas on the map and use these areas to spawn units and activate triggers.
</div>
<div className="mx-6 flex rounded-lg bg-olympus-400 p-4 text-sm">
<div className="my-auto">
<FaQuestionCircle className="my-auto ml-2 mr-6 text-gray-400" />
</div>
<div className="flex flex-col gap-1">
<div className="text-gray-100">Use the polygon or circle tool to draw areas on the map.</div>
<div className="text-gray-400">After drawing a shape, select it to see the options for spawning units. Click on a shape to select it.</div>
</div>
</div>
</>
)}
</>
<>
{activeCoalitionArea === null && drawingPolygon && (
<div className="mx-6 flex rounded-lg bg-olympus-400 p-4 text-sm">
<div>
<FaQuestionCircle className="my-4 ml-2 mr-6 text-gray-400" />
</div>
<div className="flex flex-col gap-1">
<div className="text-gray-100">Click on the map to add vertices to the polygon.</div>
<div className="text-gray-400">
When you are done, double click on the map to finalize the polygon. Vertices can be dragged or added to adjust the shape.
</div>
</div>
</div>
)}
</>
<>
{activeCoalitionArea === null && drawingCircle && (
<div className="mx-6 flex rounded-lg bg-olympus-400 p-4 text-sm">
<div>
<FaQuestionCircle className="my-4 ml-2 mr-6 text-gray-400" />
</div>
<div className="flex flex-col gap-1">
<div className="text-gray-100">Click on the map to add a new circle.</div>
<div className="text-gray-400">You can drag the circle to move it and you can use the handle to set the radius.</div>
</div>
</div>
)}
</>
<>
{activeCoalitionArea === null && (
{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={drawingPolygon}
checked={appSubState === DrawSubState.DRAW_POLYGON}
onClick={() => {
if (drawingPolygon) getApp().getMap().setState(COALITIONAREA_EDIT);
else getApp().getMap().setState(COALITIONAREA_DRAW_POLYGON);
setDrawingPolygon(!drawingPolygon);
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>
@@ -141,11 +85,10 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
className="!w-full"
icon={faCircle}
tooltip={"Add a new circle"}
checked={drawingCircle}
checked={appSubState === DrawSubState.DRAW_CIRCLE}
onClick={() => {
if (drawingCircle) getApp().getMap().setState(COALITIONAREA_EDIT);
else getApp().getMap().setState(COALITIONAREA_DRAW_CIRCLE);
setDrawingCircle(!drawingCircle);
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>
@@ -154,7 +97,7 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
)}
</>
<div>
{activeCoalitionArea !== null && (
{activeCoalitionArea !== null && appSubState === DrawSubState.EDIT && (
<div className={`flex flex-col gap-4 py-4`}>
<div
className={`

View File

@@ -1,24 +1,24 @@
import React, { useEffect, useState } from "react";
import { OlDropdown, OlDropdownItem } from "../components/oldropdown";
import { getApp } from "../../olympusapp";
import { IDLE, SPAWN_EFFECT } from "../../constants/constants";
export function EffectSpawnMenu(props: { effect: string }) {
const [explosionType, setExplosionType] = useState("High explosive");
/* When the menu is opened show the unit preview on the map as a cursor */
useEffect(() => {
if (props.effect !== null) {
getApp()
?.getMap()
?.setState(SPAWN_EFFECT, {
effectRequestTable: {
type: props.effect,
}
});
} else {
if (getApp().getMap().getState() === SPAWN_EFFECT) getApp().getMap().setState(IDLE);
}
// TODO
//if (props.effect !== null) {
// getApp()
// ?.getMap()
// ?.setState(SPAWN_EFFECT, {
// effectRequestTable: {
// type: props.effect,
// }
// });
//} else {
// if (getApp().getState() === SPAWN_EFFECT) getApp().setState(OlympusState.IDLE);
//}
});

View File

@@ -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 { EventsConsumer } from "../../eventscontext";
import { StateConsumer } from "../../statecontext";
import { OlDropdownItem, OlDropdown } from "../components/oldropdown";
import { OlLabelToggle } from "../components/ollabeltoggle";
@@ -45,192 +44,192 @@ export function Header() {
return (
<StateConsumer>
{(appState) => (
<EventsConsumer>
{() => (
<nav
<nav
className={`
z-10 flex w-full gap-4 border-gray-200 bg-gray-300 px-3
drop-shadow-md align-center
dark:border-gray-800 dark:bg-olympus-900
`}
>
<img
src="images/icon.png"
className={`my-auto h-10 w-10 rounded-md p-0`}
></img>
{!scrolledLeft && (
<FaChevronLeft
className={`
z-10 flex w-full gap-4 border-gray-200 bg-gray-300 px-3
drop-shadow-md align-center
dark:border-gray-800 dark:bg-olympus-900
absolute left-14 h-full w-6 rounded-lg px-2 py-3.5 text-gray-200
dark:bg-olympus-900
`}
/>
)}
<div
className={`
my-2 flex w-full items-center gap-3 overflow-x-scroll no-scrollbar
`}
onScroll={(ev) => onScroll(ev.target)}
ref={scrollRef}
>
<div
className={`
mr-auto hidden flex-none flex-row items-center justify-start
gap-6
lg:flex
`}
>
<img src="images/icon.png" className={`
my-auto h-10 w-10 rounded-md p-0
`}></img>
{!scrolledLeft && (
<FaChevronLeft
className={`
absolute left-14 h-full w-6 rounded-lg px-2 py-3.5
text-gray-200
dark:bg-olympus-900
`}
/>
)}
<div
className={`
my-2 flex w-full items-center gap-3 overflow-x-scroll
no-scrollbar
`}
onScroll={(ev) => onScroll(ev.target)}
ref={scrollRef}
>
<div className="flex flex-col items-start">
<div
className={`
mr-auto hidden flex-none flex-row items-center justify-start
gap-6
lg:flex
pt-1 text-xs text-gray-800
dark:text-gray-400
`}
>
<div className="flex flex-col items-start">
<div
className={`
pt-1 text-xs text-gray-800
dark:text-gray-400
`}
>
Connected to
</div>
<div
className={`
flex items-center justify-center gap-2 text-sm
font-extrabold text-gray-800
dark:text-gray-200
`}
>
{IP}
<FontAwesomeIcon
icon={connectedToServer ? faLink : faUnlink}
data-connected={connectedToServer}
className={`
py-auto text-green-400
dark:text-red-500
data-[connected='true']:dark:text-green-400
`}
/>
</div>
</div>
Connected to
</div>
<div
className={`
flex h-fit flex-row items-center justify-start gap-1
flex items-center justify-center gap-2 text-sm
font-extrabold text-gray-800
dark:text-gray-200
`}
>
<OlLockStateButton checked={!appState.mapOptions.protectDCSUnits} onClick={() => {getApp().getMap().setOption("protectDCSUnits", !appState.mapOptions.protectDCSUnits)}} tooltip="Lock/unlock protected units (from scripted mission)" />
<OlRoundStateButton
checked={audioEnabled}
onClick={() => {
audioEnabled ? getApp().getAudioManager().stop() : getApp().getAudioManager().start();
setAudioEnabled(!audioEnabled);
}}
tooltip="Enable/disable audio and radio backend"
icon={faVolumeHigh}
{IP}
<FontAwesomeIcon
icon={connectedToServer ? faLink : faUnlink}
data-connected={connectedToServer}
className={`
py-auto text-green-400
dark:text-red-500
data-[connected='true']:dark:text-green-400
`}
/>
</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
`}
>
{Object.entries({
human: olButtonsVisibilityHuman,
olympus: olButtonsVisibilityOlympus,
dcs: olButtonsVisibilityDcs,
}).map((entry) => {
return (
<OlRoundStateButton
key={entry[0]}
onClick={() => {
getApp().getMap().setHiddenType(entry[0], !appState.mapHiddenTypes[entry[0]]);
}}
checked={!appState.mapHiddenTypes[entry[0]]}
icon={entry[1]}
tooltip={"Hide/show " + entry[0] + " units"}
/>
);
})}
</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
`}
>
<OlRoundStateButton
onClick={() => getApp().getMap().setHiddenType("blue", !appState.mapHiddenTypes["blue"])}
checked={!appState.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"]}
icon={faFlag}
className={"!text-red-500"}
tooltip={"Hide/show red units"}
/>
<OlRoundStateButton
onClick={() => getApp().getMap().setHiddenType("neutral", !appState.mapHiddenTypes["neutral"])}
checked={!appState.mapHiddenTypes["neutral"]}
icon={faFlag}
className={"!text-gray-500"}
tooltip={"Hide/show neutral units"}
/>
</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
`}
>
{Object.entries({
aircraft: olButtonsVisibilityAircraft,
helicopter: olButtonsVisibilityHelicopter,
"groundunit-sam": olButtonsVisibilityGroundunitSam,
groundunit: olButtonsVisibilityGroundunit,
navyunit: olButtonsVisibilityNavyunit,
airbase: olButtonsVisibilityAirbase,
dead: faSkull,
}).map((entry) => {
return (
<OlRoundStateButton
key={entry[0]}
onClick={() => {
getApp().getMap().setHiddenType(entry[0], !appState.mapHiddenTypes[entry[0]]);
}}
checked={!appState.mapHiddenTypes[entry[0]]}
icon={entry[1]}
tooltip={"Hide/show " + entry[0] + " units"}
/>
);
})}
</div>
<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) => {
return (
<OlDropdownItem key={source} onClick={() => getApp().getMap().setLayerName(source)}>
<div className="truncate">{source}</div>
</OlDropdownItem>
);
})}
</OlDropdown>
</div>
{!scrolledRight && (
<FaChevronRight
className={`
absolute right-0 h-full w-6 rounded-lg px-2 py-3.5
text-gray-200
dark:bg-olympus-900
</div>
<div
className={`
flex h-fit flex-row items-center justify-start gap-1
`}
/>
)}
</nav>
>
<OlLockStateButton
checked={!appState.mapOptions.protectDCSUnits}
onClick={() => {
getApp().getMap().setOption("protectDCSUnits", !appState.mapOptions.protectDCSUnits);
}}
tooltip="Lock/unlock protected units (from scripted mission)"
/>
<OlRoundStateButton
checked={audioEnabled}
onClick={() => {
audioEnabled ? getApp().getAudioManager().stop() : getApp().getAudioManager().start();
setAudioEnabled(!audioEnabled);
}}
tooltip="Enable/disable audio and radio backend"
icon={faVolumeHigh}
/>
</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
`}
>
{Object.entries({
human: olButtonsVisibilityHuman,
olympus: olButtonsVisibilityOlympus,
dcs: olButtonsVisibilityDcs,
}).map((entry) => {
return (
<OlRoundStateButton
key={entry[0]}
onClick={() => {
getApp().getMap().setHiddenType(entry[0], !appState.mapHiddenTypes[entry[0]]);
}}
checked={!appState.mapHiddenTypes[entry[0]]}
icon={entry[1]}
tooltip={"Hide/show " + entry[0] + " units"}
/>
);
})}
</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
`}
>
<OlRoundStateButton
onClick={() => getApp().getMap().setHiddenType("blue", !appState.mapHiddenTypes["blue"])}
checked={!appState.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"]}
icon={faFlag}
className={"!text-red-500"}
tooltip={"Hide/show red units"}
/>
<OlRoundStateButton
onClick={() => getApp().getMap().setHiddenType("neutral", !appState.mapHiddenTypes["neutral"])}
checked={!appState.mapHiddenTypes["neutral"]}
icon={faFlag}
className={"!text-gray-500"}
tooltip={"Hide/show neutral units"}
/>
</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
`}
>
{Object.entries({
aircraft: olButtonsVisibilityAircraft,
helicopter: olButtonsVisibilityHelicopter,
"groundunit-sam": olButtonsVisibilityGroundunitSam,
groundunit: olButtonsVisibilityGroundunit,
navyunit: olButtonsVisibilityNavyunit,
airbase: olButtonsVisibilityAirbase,
dead: faSkull,
}).map((entry) => {
return (
<OlRoundStateButton
key={entry[0]}
onClick={() => {
getApp().getMap().setHiddenType(entry[0], !appState.mapHiddenTypes[entry[0]]);
}}
checked={!appState.mapHiddenTypes[entry[0]]}
icon={entry[1]}
tooltip={"Hide/show " + entry[0] + " units"}
/>
);
})}
</div>
<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) => {
return (
<OlDropdownItem key={source} onClick={() => getApp().getMap().setLayerName(source)}>
<div className="truncate">{source}</div>
</OlDropdownItem>
);
})}
</OlDropdown>
</div>
{!scrolledRight && (
<FaChevronRight
className={`
absolute right-0 h-full w-6 rounded-lg px-2 py-3.5 text-gray-200
dark:bg-olympus-900
`}
/>
)}
</EventsConsumer>
</nav>
)}
</StateConsumer>
);

View File

@@ -1,7 +1,6 @@
import React, { useEffect, useState } from "react";
import { Menu } from "./components/menu";
import { getApp } from "../../olympusapp";
import { IDLE, SELECT_JTAC_ECHO, SELECT_JTAC_IP, SELECT_JTAC_TARGET } from "../../constants/constants";
import { LatLng } from "leaflet";
import { Unit } from "../../unit/unit";
import { OlDropdown, OlDropdownItem } from "../components/oldropdown";
@@ -10,6 +9,7 @@ import { ConvertDDToDMS, latLngToMGRS, mToFt, zeroAppend } from "../../other/uti
import { FaMousePointer } from "react-icons/fa";
import { OlLocation } from "../components/ollocation";
import { FaBullseye } from "react-icons/fa6";
import { JTACSubState, OlympusState } from "../../constants/constants";
export function JTACMenu(props: { open: boolean; onClose: () => void; children?: JSX.Element | JSX.Element[] }) {
const [referenceSystem, setReferenceSystem] = useState("LatLngDec");
@@ -17,7 +17,6 @@ export function JTACMenu(props: { open: boolean; onClose: () => void; children?:
const [targetUnit, setTargetUnit] = useState(null as null | Unit);
const [IP, setIP] = useState(null as null | LatLng);
const [ECHO, setECHO] = useState(null as null | LatLng);
const [mapState, setMapState] = useState(IDLE);
const [callsign, setCallsign] = useState("Eyeball");
const [humanUnits, setHumanUnits] = useState([] as Unit[]);
const [attacker, setAttacker] = useState(null as null | Unit);
@@ -40,9 +39,8 @@ export function JTACMenu(props: { open: boolean; onClose: () => void; children?:
setIP(ev.detail);
});
document.addEventListener("mapStateChanged", (ev: CustomEventInit) => {
setMapState(ev.detail);
if (ev.detail === SELECT_JTAC_TARGET) {
document.addEventListener("appStateChanged", (ev: CustomEventInit) => {
if (ev.detail.subState === JTACSubState.SELECT_TARGET) {
setTargetLocation(null);
setTargetUnit(null);
}
@@ -124,7 +122,7 @@ export function JTACMenu(props: { open: boolean; onClose: () => void; children?:
<button
type="button"
onClick={() => {
getApp().getMap().setState(SELECT_JTAC_ECHO);
getApp().setState(OlympusState.JTAC, JTACSubState.SELECT_ECHO_POINT);
}}
className={`
rounded-r-md bg-blue-700 px-3 py-2.5 text-md font-medium
@@ -161,7 +159,7 @@ export function JTACMenu(props: { open: boolean; onClose: () => void; children?:
<button
type="button"
onClick={() => {
getApp().getMap().setState(SELECT_JTAC_IP);
getApp().setState(OlympusState.JTAC, JTACSubState.SELECT_IP);
}}
className={`
rounded-r-lg bg-blue-700 px-3 py-2.5 text-md font-medium
@@ -198,7 +196,7 @@ export function JTACMenu(props: { open: boolean; onClose: () => void; children?:
<button
type="button"
onClick={() => {
getApp().getMap().setState(SELECT_JTAC_TARGET);
getApp().setState(OlympusState.JTAC, JTACSubState.SELECT_TARGET);
}}
className={`
rounded-r-lg bg-blue-700 px-3 py-2.5 text-md font-medium

View File

@@ -1,94 +1,98 @@
import React from "react";
import { OlStateButton } from "../components/olstatebutton";
import { faGamepad, faRuler, faPencil, faEllipsisV, faCog, faQuestionCircle, faPlusSquare, faMagnifyingGlass, faRadio, faVolumeHigh, faJ } from "@fortawesome/free-solid-svg-icons";
import { EventsConsumer } from "../../eventscontext";
import {
faGamepad,
faRuler,
faPencil,
faEllipsisV,
faCog,
faQuestionCircle,
faPlusSquare,
faMagnifyingGlass,
faVolumeHigh,
faJ,
} from "@fortawesome/free-solid-svg-icons";
import { StateConsumer } from "../../statecontext";
import { CONTEXT_ACTION, IDLE } from "../../constants/constants";
import { getApp } from "../../olympusapp";
import { OlympusState } from "../../constants/constants";
export function SideBar() {
return (
<StateConsumer>
{(appState) => (
<EventsConsumer>
{(events) => (
<nav
className={`
z-20 flex h-full flex-col bg-gray-300
dark:bg-olympus-900
`}
>
<div
className={`
<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={`
>
<div
className={`
flex flex-col items-center justify-center gap-2.5
`}
>
<OlStateButton
onClick={events.toggleMainMenuVisible}
checked={appState.mainMenuVisible}
icon={faEllipsisV}
tooltip="Hide/show main menu"
></OlStateButton>
<OlStateButton
onClick={events.toggleSpawnMenuVisible}
checked={appState.spawnMenuVisible}
icon={faPlusSquare}
tooltip="Hide/show unit spawn menu"
></OlStateButton>
<OlStateButton
onClick={events.toggleUnitControlMenuVisible}
checked={appState.unitControlMenuVisible}
icon={appState.mapState !== CONTEXT_ACTION? faMagnifyingGlass: faGamepad}
tooltip="Hide/show selection tool and unit control menu"
></OlStateButton>
<OlStateButton onClick={events.toggleMeasureMenuVisible} checked={appState.measureMenuVisible} icon={faRuler} tooltip="NOT IMPLEMENTED"></OlStateButton>
<OlStateButton
onClick={events.toggleDrawingMenuVisible}
checked={appState.drawingMenuVisible}
icon={faPencil}
tooltip="Hide/show drawing menu"
></OlStateButton>
<OlStateButton
onClick={events.toggleAudioMenuVisible}
checked={appState.audioMenuVisible}
icon={faVolumeHigh}
tooltip="Hide/show audio menu"
></OlStateButton>
<OlStateButton
onClick={events.toggleJTACMenuVisible}
checked={appState.JTACMenuVisible}
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={`
>
<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={events.toggleOptionsMenuVisible}
checked={appState.optionsMenuVisible}
icon={faCog}
tooltip="Hide/show settings menu"
></OlStateButton>
</div>
</div>
</nav>
)}
</EventsConsumer>
>
<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>
);

View File

@@ -13,29 +13,68 @@ import {
olButtonsVisibilityHelicopter,
olButtonsVisibilityNavyunit,
} from "../components/olicons";
import { IDLE, SPAWN_EFFECT, SPAWN_UNIT } from "../../constants/constants";
import { getUnitsByLabel } from "../../other/utils";
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";
enum Accordion {
NONE,
AIRCRAFT,
HELICOPTER,
SAM,
AAA,
GROUND_UNIT,
NAVY_UNIT,
EFFECT,
}
export function SpawnMenu(props: { open: boolean; onClose: () => void; children?: JSX.Element | JSX.Element[] }) {
const [openAccordion, setOpenAccordion] = useState(Accordion.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 [filteredAircraft, filteredHelicopters, filteredAirDefense, filteredGroundUnits, filteredNavyUnits] = getUnitsByLabel(filterString);
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 (!props.open && getApp()) {
if (getApp().getMap().getState() === SPAWN_UNIT) getApp().getMap().setState(IDLE);
else if (getApp().getMap().getState() === SPAWN_EFFECT) getApp().getMap().setState(IDLE);
if (!props.open) {
if (blueprint !== null) setBlueprint(null);
if (effect !== null) setEffect(null);
if (filterString !== "") setFilterString("");
if (openAccordion !== Accordion.NONE) setOpenAccordion(Accordion.NONE);
}
});
useEffect(() => {
document.addEventListener("appStateChanged", (ev: CustomEventInit) => {
if (ev.detail.subState === NO_SUBSTATE) {
setBlueprint(null);
setEffect(null);
}
});
}, []);
return (
<Menu
{...props}
@@ -43,7 +82,7 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
showBackButton={blueprint !== null || effect !== null}
canBeHidden={true}
onBack={() => {
getApp().getMap().setState(IDLE);
getApp().setState(OlympusState.SPAWN);
setBlueprint(null);
setEffect(null);
}}
@@ -52,10 +91,43 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
{blueprint === null && effect === null && (
<div className="p-5">
<OlSearchBar onChange={(value) => setFilterString(value)} text={filterString} />
<OlAccordion title={`Aircraft`}>
<OlAccordion
title={`Aircraft`}
open={openAccordion == Accordion.AIRCRAFT}
onClick={() => {
setOpenAccordion(openAccordion === Accordion.AIRCRAFT ? Accordion.NONE : Accordion.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>
);
})}
</div>
<div
className={`
flex max-h-80 flex-col gap-1 overflow-y-scroll no-scrollbar
flex max-h-[450px] flex-col gap-1 overflow-y-scroll
no-scrollbar
`}
>
{Object.entries(filteredAircraft).map((entry) => {
@@ -63,10 +135,43 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
})}
</div>
</OlAccordion>
<OlAccordion title={`Helicopters`}>
<OlAccordion
title={`Helicopters`}
open={openAccordion == Accordion.HELICOPTER}
onClick={() => {
setOpenAccordion(openAccordion === Accordion.HELICOPTER ? Accordion.NONE : Accordion.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>
);
})}
</div>
<div
className={`
flex max-h-80 flex-col gap-1 overflow-y-scroll no-scrollbar
flex max-h-[450px] flex-col gap-1 overflow-y-scroll
no-scrollbar
`}
>
{Object.entries(filteredHelicopters).map((entry) => {
@@ -74,21 +179,86 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
})}
</div>
</OlAccordion>
<OlAccordion title={`SAM & AAA`}>
<OlAccordion
title={`Surfact to Air Missiles (SAM sites)`}
open={openAccordion == Accordion.SAM}
onClick={() => {
setOpenAccordion(openAccordion === Accordion.SAM ? Accordion.NONE : Accordion.SAM);
setSelectedRole(null);
setSelectedType(null);
}}
>
<div
className={`
flex max-h-80 flex-col gap-1 overflow-y-scroll no-scrollbar
flex max-h-[450px] flex-col gap-1 overflow-y-scroll
no-scrollbar
`}
>
{Object.entries(filteredAirDefense).map((entry) => {
{Object.entries(filteredSAMs).map((entry) => {
return <OlUnitListEntry key={entry[0]} icon={olButtonsVisibilityGroundunitSam} blueprint={entry[1]} onClick={() => setBlueprint(entry[1])} />;
})}
</div>
</OlAccordion>
<OlAccordion title={`Ground Units`}>
<OlAccordion
title={`Anti Aircraft Artillery (AAA)`}
open={openAccordion == Accordion.AAA}
onClick={() => {
setOpenAccordion(openAccordion === Accordion.AAA ? Accordion.NONE : Accordion.AAA);
setSelectedRole(null);
setSelectedType(null);
}}
>
<div
className={`
flex max-h-80 flex-col gap-1 overflow-y-scroll no-scrollbar
flex max-h-[450px] flex-col gap-1 overflow-y-scroll
no-scrollbar
`}
>
{Object.entries(filteredAAA).map((entry) => {
return <OlUnitListEntry key={entry[0]} icon={olButtonsVisibilityGroundunitSam} blueprint={entry[1]} onClick={() => setBlueprint(entry[1])} />;
})}
</div>
</OlAccordion>
<OlAccordion
title={`Ground Units`}
open={openAccordion == Accordion.GROUND_UNIT}
onClick={() => {
setOpenAccordion(openAccordion === Accordion.GROUND_UNIT ? Accordion.NONE : Accordion.GROUND_UNIT);
setSelectedRole(null);
setSelectedType(null);
}}
>
<div className="mb-2 flex flex-wrap gap-1">
{groundUnitDatabase
.getTypes()
.sort()
.filter((type) => {
return type !== "AAA" && type !== "SAM Site";
})
.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={`
flex max-h-[450px] flex-col gap-1 overflow-y-scroll
no-scrollbar
`}
>
{Object.entries(filteredGroundUnits).map((entry) => {
@@ -96,10 +266,43 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
})}
</div>
</OlAccordion>
<OlAccordion title={`Ships and submarines`}>
<OlAccordion
title={`Ships and submarines`}
open={openAccordion == Accordion.NAVY_UNIT}
onClick={() => {
setOpenAccordion(openAccordion === Accordion.NAVY_UNIT ? Accordion.NONE : Accordion.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>
);
})}
</div>
<div
className={`
flex max-h-80 flex-col gap-1 overflow-y-scroll no-scrollbar
flex max-h-[450px] flex-col gap-1 overflow-y-scroll
no-scrollbar
`}
>
{Object.entries(filteredNavyUnits).map((entry) => {
@@ -107,10 +310,19 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
})}
</div>
</OlAccordion>
<OlAccordion title="Effects (smokes, explosions etc)">
<OlAccordion
title="Effects (smokes, explosions etc)"
open={openAccordion == Accordion.EFFECT}
onClick={() => {
setOpenAccordion(openAccordion === Accordion.EFFECT ? Accordion.NONE : Accordion.EFFECT);
setSelectedRole(null);
setSelectedType(null);
}}
>
<div
className={`
flex max-h-80 flex-col gap-1 overflow-y-scroll no-scrollbar
flex max-h-[450px] flex-col gap-1 overflow-y-scroll
no-scrollbar
`}
>
<OlEffectListEntry

View File

@@ -38,7 +38,7 @@ import {
olButtonsVisibilityOlympus,
} from "../components/olicons";
import { Coalition } from "../../types/types";
import { ftToM, getUnitsByLabel, knotsToMs, mToFt, msToKnots } from "../../other/utils";
import { ftToM, knotsToMs, mToFt, msToKnots } from "../../other/utils";
import { FaCog, FaGasPump, FaSignal, FaTag } from "react-icons/fa";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { OlSearchBar } from "../components/olsearchbar";
@@ -236,7 +236,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
const selectedCategories = getApp()?.getUnitsManager()?.getSelectedUnitsCategories() ?? [];
const [filteredAircraft, filteredHelicopters, filteredAirDefense, filteredGroundUnits, filteredNavyUnits] = getUnitsByLabel(filterString);
const [filteredAircraft, filteredHelicopters, filteredAirDefense, filteredGroundUnits, filteredNavyUnits] = [{}, {}, {}, {}, {}] // TODOgetUnitsByLabel(filterString);
const mergedFilteredUnits = Object.assign({}, filteredAircraft, filteredHelicopters, filteredAirDefense, filteredGroundUnits, filteredNavyUnits) as {
[key: string]: UnitBlueprint;

View File

@@ -4,9 +4,10 @@ import { ContextActionSet } from "../../unit/contextactionset";
import { OlStateButton } from "../components/olstatebutton";
import { getApp } from "../../olympusapp";
import { ContextAction } from "../../unit/contextaction";
import { CONTEXT_ACTION, CONTEXT_ACTION_COLORS } from "../../constants/constants";
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";
export function UnitMouseControlBar(props: {}) {
const [open, setOpen] = useState(false);
@@ -41,8 +42,8 @@ export function UnitMouseControlBar(props: {}) {
});
/* Deselect the context action when exiting state */
document.addEventListener("mapStateChanged", (ev) => {
setOpen((ev as CustomEvent).detail === CONTEXT_ACTION);
document.addEventListener("appStateChanged", (ev) => {
setOpen((ev as CustomEvent).detail.state === OlympusState.UNIT_CONTROL);
});
}, []);
@@ -123,16 +124,12 @@ export function UnitMouseControlBar(props: {}) {
} else {
if (activeContextAction !== contextAction) {
setActiveContextAction(contextAction);
getApp().getMap().setState(CONTEXT_ACTION, {
contextAction: contextAction,
defaultContextAction: contextActionsSet.getDefaultContextAction(),
});
getApp().getMap().setContextAction(contextAction);
getApp().getMap().setDefaultContextAction(contextActionsSet.getDefaultContextAction());
} else {
setActiveContextAction(null);
getApp().getMap().setState(CONTEXT_ACTION, {
contextAction: null,
defaultContextAction: contextActionsSet.getDefaultContextAction(),
});
getApp().getMap().setContextAction(null);
getApp().getMap().setDefaultContextAction(null);
}
}
}}

View File

@@ -8,10 +8,10 @@ import { OlDropdownItem, OlDropdown } from "../components/oldropdown";
import { LoadoutBlueprint, UnitBlueprint } from "../../interfaces";
import { Coalition } from "../../types/types";
import { getApp } from "../../olympusapp";
import { IDLE, SPAWN_UNIT } from "../../constants/constants";
import { ftToM, getUnitCategoryByBlueprint } from "../../other/utils";
import { LatLng } from "leaflet";
import { Airbase } from "../../mission/airbase";
import { OlympusState, SpawnSubState } from "../../constants/constants";
export function UnitSpawnMenu(props: { blueprint: UnitBlueprint; spawnAtLocation: boolean; airbase?: Airbase | null; coalition?: Coalition }) {
/* Compute the min and max values depending on the unit type */
@@ -38,25 +38,24 @@ export function UnitSpawnMenu(props: { blueprint: UnitBlueprint; spawnAtLocation
if (props.blueprint !== null) {
getApp()
?.getMap()
?.setState(SPAWN_UNIT, {
spawnRequestTable: {
category: getUnitCategoryByBlueprint(props.blueprint),
unit: {
unitType: props.blueprint.name,
location: new LatLng(0, 0), // This will be filled when the user clicks on the map to spawn the unit
skill: "High",
liveryID: "",
altitude: ftToM(spawnAltitude),
loadout:
props.blueprint.loadouts?.find((loadout) => {
return loadout.name === spawnLoadoutName;
})?.code ?? "",
},
coalition: spawnCoalition,
?.setSpawnRequestTable({
category: getUnitCategoryByBlueprint(props.blueprint),
unit: {
unitType: props.blueprint.name,
location: new LatLng(0, 0), // This will be filled when the user clicks on the map to spawn the unit
skill: "High",
liveryID: "",
altitude: ftToM(spawnAltitude),
loadout:
props.blueprint.loadouts?.find((loadout) => {
return loadout.name === spawnLoadoutName;
})?.code ?? "",
},
coalition: spawnCoalition,
});
getApp().setState(OlympusState.SPAWN, SpawnSubState.SPAWN_UNIT)
} else {
if (getApp().getMap().getState() === SPAWN_UNIT) getApp().getMap().setState(IDLE);
if (getApp().getState() === OlympusState.SPAWN) getApp().setState(OlympusState.IDLE);
}
}
});

View File

@@ -1,7 +1,6 @@
import React, { useEffect, useState } from "react";
import "./ui.css";
import { EventsProvider } from "../eventscontext";
import { StateProvider } from "../statecontext";
import { Header } from "./panels/header";
@@ -11,7 +10,7 @@ import { MainMenu } from "./panels/mainmenu";
import { SideBar } from "./panels/sidebar";
import { OptionsMenu } from "./panels/optionsmenu";
import { MapHiddenTypes, MapOptions } from "../types/types";
import { BLUE_COMMANDER, CONTEXT_ACTION, GAME_MASTER, IDLE, MAP_HIDDEN_TYPES_DEFAULTS, MAP_OPTIONS_DEFAULTS, RED_COMMANDER } from "../constants/constants";
import { BLUE_COMMANDER, GAME_MASTER, MAP_HIDDEN_TYPES_DEFAULTS, MAP_OPTIONS_DEFAULTS, NO_SUBSTATE, OlympusEvent, OlympusState, OlympusSubState, RED_COMMANDER, UnitControlSubState } from "../constants/constants";
import { getApp, setupApp } from "../olympusapp";
import { LoginModal } from "./modals/login";
import { sha256 } from "js-sha256";
@@ -42,35 +41,34 @@ export type OlympusUIState = {
};
export function UI() {
const [loginModalVisible, setLoginModalVisible] = useState(true);
const [mainMenuVisible, setMainMenuVisible] = useState(false);
const [spawnMenuVisible, setSpawnMenuVisible] = useState(false);
const [unitControlMenuVisible, setUnitControlMenuVisible] = useState(false);
const [measureMenuVisible, setMeasureMenuVisible] = useState(false);
const [drawingMenuVisible, setDrawingMenuVisible] = useState(false);
const [audioMenuVisible, setAudioMenuVisible] = useState(false);
const [optionsMenuVisible, setOptionsMenuVisible] = useState(false);
const [airbaseMenuVisible, setAirbaseMenuVisible] = useState(false);
const [formationMenuVisible, setFormationMenuVisible] = useState(false);
const [unitExplosionMenuVisible, setUnitExplosionMenuVisible] = useState(false);
const [JTACMenuVisible, setJTACMenuVisible] = useState(false);
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 [checkingPassword, setCheckingPassword] = useState(false);
const [loginError, setLoginError] = useState(false);
const [commandMode, setCommandMode] = useState(null as null | string);
const [mapSources, setMapSources] = useState([] as string[]);
const [activeMapSource, setActiveMapSource] = useState("");
const [mapState, setMapState] = useState(IDLE);
const [airbase, setAirbase] = useState(null as null | Airbase);
const [formationLeader, setFormationLeader] = useState(null as null | Unit);
const [formationWingmen, setFormationWingmen] = useState(null as null | Unit[]);
const [protectionPromptVisible, setProtectionPromptVisible] = useState(false);
const [protectionCallback, setProtectionCallback] = useState(null as any);
const [protectionUnits, setProtectionUnits] = useState([] as Unit[]);
const [unitExplosionUnits, setUnitExplosionUnits] = useState([] as Unit[]);
useEffect(() => {
getApp()?.registerEventCallback(OlympusEvent.STATE_CHANGED, (state, subState) => {
setAppState(state);
setAppSubState(subState);
})
document.addEventListener("hiddenTypesChanged", (ev) => {
setMapHiddenTypes({ ...getApp().getMap().getHiddenTypes() });
});
@@ -79,15 +77,6 @@ export function UI() {
setMapOptions({ ...getApp().getMap().getOptions() });
});
document.addEventListener("mapStateChanged", (ev: CustomEventInit) => {
if (ev.detail === IDLE || ev.detail === CONTEXT_ACTION || mapState !== IDLE) {
hideAllMenus();
}
if (ev.detail === CONTEXT_ACTION && window.innerWidth > 1000) setUnitControlMenuVisible(true);
setMapState(ev.detail);
});
document.addEventListener("mapSourceChanged", (ev) => {
var source = (ev as CustomEvent).detail;
setActiveMapSource(source);
@@ -99,20 +88,7 @@ export function UI() {
setMapSources(sources);
setActiveMapSource(sources[0]);
});
document.addEventListener("airbaseClick", (ev) => {
hideAllMenus();
getApp().getMap().setState(IDLE);
setAirbase((ev as CustomEvent).detail);
setAirbaseMenuVisible(true);
});
document.addEventListener("showFormationMenu", (ev) => {
setFormationMenuVisible(true);
setFormationLeader((ev as CustomEvent).detail.leader);
setFormationWingmen((ev as CustomEvent).detail.wingmen);
});
document.addEventListener("showProtectionPrompt", (ev: CustomEventInit) => {
setProtectionPromptVisible(true);
setProtectionCallback(() => {
@@ -120,27 +96,8 @@ export function UI() {
});
setProtectionUnits(ev.detail.units);
});
document.addEventListener("showUnitExplosionMenu", (ev) => {
setUnitExplosionMenuVisible(true);
setUnitExplosionUnits((ev as CustomEvent).detail.units);
});
}, []);
function hideAllMenus() {
setMainMenuVisible(false);
setSpawnMenuVisible(false);
setUnitControlMenuVisible(false);
setMeasureMenuVisible(false);
setDrawingMenuVisible(false);
setOptionsMenuVisible(false);
setAirbaseMenuVisible(false);
setAudioMenuVisible(false);
setFormationMenuVisible(false);
setUnitExplosionMenuVisible(false);
setJTACMenuVisible(false);
}
function checkPassword(password: string) {
setCheckingPassword(true);
var hash = sha256.create();
@@ -167,7 +124,7 @@ export function UI() {
function connect(username: string) {
getApp().getServerManager().setUsername(username);
getApp().getServerManager().startUpdate();
setLoginModalVisible(false);
getApp().setState(OlympusState.IDLE);
}
return (
@@ -180,74 +137,18 @@ export function UI() {
>
<StateProvider
value={{
mainMenuVisible: mainMenuVisible,
spawnMenuVisible: spawnMenuVisible,
unitControlMenuVisible: unitControlMenuVisible,
measureMenuVisible: measureMenuVisible,
drawingMenuVisible: drawingMenuVisible,
optionsMenuVisible: optionsMenuVisible,
airbaseMenuVisible: airbaseMenuVisible,
audioMenuVisible: audioMenuVisible,
JTACMenuVisible: JTACMenuVisible,
mapOptions: mapOptions,
mapHiddenTypes: mapHiddenTypes,
mapSources: mapSources,
activeMapSource: activeMapSource,
mapState: mapState,
appState,
appSubState,
mapOptions,
mapHiddenTypes,
mapSources,
activeMapSource,
}}
>
<EventsProvider
value={{
setMainMenuVisible: setMainMenuVisible,
setSpawnMenuVisible: setSpawnMenuVisible,
setUnitControlMenuVisible: setUnitControlMenuVisible,
setDrawingMenuVisible: setDrawingMenuVisible,
setMeasureMenuVisible: setMeasureMenuVisible,
setOptionsMenuVisible: setOptionsMenuVisible,
setAirbaseMenuVisible: setAirbaseMenuVisible,
setJTACMenuVisible: setJTACMenuVisible,
setAudioMenuVisible: setAudioMenuVisible,
toggleMainMenuVisible: () => {
hideAllMenus();
setMainMenuVisible(!mainMenuVisible);
},
toggleSpawnMenuVisible: () => {
hideAllMenus();
setSpawnMenuVisible(!spawnMenuVisible);
},
toggleUnitControlMenuVisible: () => {
hideAllMenus();
setUnitControlMenuVisible(!unitControlMenuVisible);
},
toggleMeasureMenuVisible: () => {
hideAllMenus();
setMeasureMenuVisible(!measureMenuVisible);
},
toggleDrawingMenuVisible: () => {
hideAllMenus();
setDrawingMenuVisible(!drawingMenuVisible);
},
toggleOptionsMenuVisible: () => {
hideAllMenus();
setOptionsMenuVisible(!optionsMenuVisible);
},
toggleAirbaseMenuVisible: () => {
hideAllMenus();
setAirbaseMenuVisible(!airbaseMenuVisible);
},
toggleAudioMenuVisible: () => {
hideAllMenus();
setAudioMenuVisible(!audioMenuVisible);
},
toggleJTACMenuVisible: () => {
hideAllMenus();
setJTACMenuVisible(!JTACMenuVisible);
},
}}
>
<Header />
<div className="flex h-full w-full flex-row-reverse">
{loginModalVisible && (
{appState === OlympusState.LOGIN && (
<>
<div
className={`
@@ -290,16 +191,19 @@ export function UI() {
</>
)}
<div id="map-container" className="z-0 h-full w-screen" />
<MainMenu open={mainMenuVisible} onClose={() => setMainMenuVisible(false)} />
<SpawnMenu open={spawnMenuVisible} onClose={() => setSpawnMenuVisible(false)} />
<OptionsMenu open={optionsMenuVisible} onClose={() => setOptionsMenuVisible(false)} options={mapOptions} />
<UnitControlMenu open={unitControlMenuVisible} onClose={() => setUnitControlMenuVisible(false)} />
<DrawingMenu open={drawingMenuVisible} onClose={() => setDrawingMenuVisible(false)} />
<AirbaseMenu open={airbaseMenuVisible} onClose={() => setAirbaseMenuVisible(false)} airbase={airbase} />
<AudioMenu open={audioMenuVisible} onClose={() => setAudioMenuVisible(false)} />
<FormationMenu open={formationMenuVisible} leader={formationLeader} wingmen={formationWingmen} onClose={() => setFormationMenuVisible(false)} />
<UnitExplosionMenu open={unitExplosionMenuVisible} units={unitExplosionUnits} onClose={() => setUnitExplosionMenuVisible(false)} />
<JTACMenu open={JTACMenuVisible} onClose={() => setJTACMenuVisible(false)} />
<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} />
<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)} />
{/* 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 />
@@ -307,7 +211,6 @@ export function UI() {
<MapContextMenu />
<SideBar />
</div>
</EventsProvider>
</StateProvider>
</div>
);