mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
More work on audio system, started adding carrier icon
This commit is contained in:
@@ -4,11 +4,14 @@ import { getApp } from "../../olympusapp";
|
||||
import { FaQuestionCircle } from "react-icons/fa";
|
||||
import { AudioSourcePanel } from "./components/sourcepanel";
|
||||
import { AudioSource } from "../../audio/audiosource";
|
||||
import { FaVolumeHigh } from "react-icons/fa6";
|
||||
import { FaVolumeHigh, FaX } from "react-icons/fa6";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faClose } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
export function AudioMenu(props: { open: boolean; onClose: () => void; children?: JSX.Element | JSX.Element[] }) {
|
||||
const [sources, setSources] = useState([] as AudioSource[]);
|
||||
const [audioManagerEnabled, setAudioManagerEnabled] = useState(false);
|
||||
const [showTip, setShowTip] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
/* Force a rerender */
|
||||
@@ -29,40 +32,56 @@ export function AudioMenu(props: { open: boolean; onClose: () => void; children?
|
||||
return (
|
||||
<Menu title="Audio sources" open={props.open} showBackButton={false} onClose={props.onClose}>
|
||||
<div className="p-4 text-sm text-gray-400">The audio source panel allows you to add and manage audio sources.</div>
|
||||
<div className="mx-6 flex rounded-lg bg-olympus-400 p-4 text-sm">
|
||||
{audioManagerEnabled && (
|
||||
<>
|
||||
<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">Use the controls to apply effects and start/stop the playback of an audio source.</div>
|
||||
<div className="text-gray-400">Sources can be connected to your radios, or attached to a unit to be played on loudspeakers.</div>
|
||||
</div>
|
||||
</>
|
||||
<>
|
||||
{showTip && (
|
||||
<div className="mx-6 flex rounded-lg bg-olympus-400 p-4 text-sm">
|
||||
{audioManagerEnabled ? (
|
||||
<>
|
||||
<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 controls to apply effects and start/stop the playback of an audio source.</div>
|
||||
<div className="text-gray-400">Sources can be connected to your radios, or attached to a unit to be played on loudspeakers.</div>
|
||||
</div>
|
||||
<div>
|
||||
<FontAwesomeIcon
|
||||
onClick={() => setShowTip(false)}
|
||||
icon={faClose}
|
||||
className={`
|
||||
ml-2 flex cursor-pointer items-center justify-center
|
||||
rounded-md p-2 text-lg
|
||||
dark:text-gray-500 dark:hover:bg-gray-700
|
||||
dark:hover:text-white
|
||||
hover:bg-gray-200
|
||||
`}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<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">
|
||||
To enable the audio menu, first start the audio backend with the{" "}
|
||||
<span
|
||||
className={`
|
||||
mx-1 mt-[-7px] inline-block translate-y-2 rounded-full
|
||||
border-[1px] border-white p-1
|
||||
`}
|
||||
>
|
||||
<FaVolumeHigh />
|
||||
</span>{" "}
|
||||
button on the navigation header.
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{!audioManagerEnabled && (
|
||||
<>
|
||||
<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">
|
||||
To enable the audio menu, first start the audio backend with the{" "}
|
||||
<span
|
||||
className={`
|
||||
mx-1 mt-[-7px] inline-block translate-y-2 rounded-full
|
||||
border-[1px] border-white p-1
|
||||
`}
|
||||
>
|
||||
<FaVolumeHigh />
|
||||
</span>{" "}
|
||||
button on the navigation header.
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
|
||||
<div
|
||||
className={`
|
||||
@@ -71,8 +90,8 @@ export function AudioMenu(props: { open: boolean; onClose: () => void; children?
|
||||
`}
|
||||
>
|
||||
<>
|
||||
{sources.map((source) => {
|
||||
return <AudioSourcePanel source={source} />;
|
||||
{sources.map((source, idx) => {
|
||||
return <AudioSourcePanel key={idx} source={source} />;
|
||||
})}
|
||||
</>
|
||||
{audioManagerEnabled && (
|
||||
|
||||
@@ -30,9 +30,10 @@ export function Menu(props: {
|
||||
<div
|
||||
data-hide={hide}
|
||||
className={`
|
||||
pointer-events-auto h-[calc(100vh-58px-2rem)] overflow-y-auto
|
||||
overflow-x-hidden no-scrollbar backdrop-blur-lg backdrop-grayscale
|
||||
transition-transform
|
||||
pointer-events-auto
|
||||
h-[calc(100vh-58px${props.canBeHidden ? "-2rem" : ""})]
|
||||
overflow-y-auto overflow-x-hidden no-scrollbar backdrop-blur-lg
|
||||
backdrop-grayscale transition-transform
|
||||
dark:bg-olympus-800/90
|
||||
data-[hide='true']:translate-y-[calc(100vh-58px)]
|
||||
`}
|
||||
@@ -81,9 +82,7 @@ export function Menu(props: {
|
||||
<FaChevronUp className="mx-auto my-auto text-gray-400" />
|
||||
) : (
|
||||
<FaChevronDown
|
||||
className={`
|
||||
mx-auto my-auto text-gray-400
|
||||
`}
|
||||
className={`mx-auto my-auto text-gray-400`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -7,7 +7,7 @@ import { faEarListen, faMicrophoneLines } from "@fortawesome/free-solid-svg-icon
|
||||
import { RadioSink } from "../../../audio/radiosink";
|
||||
import { getApp } from "../../../olympusapp";
|
||||
|
||||
export function RadioPanel(props: { radio: RadioSink }) {
|
||||
export function RadioPanel(props: { radio: RadioSink; shortcutKey: string }) {
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
@@ -17,14 +17,19 @@ export function RadioPanel(props: { radio: RadioSink }) {
|
||||
>
|
||||
<div className="flex content-center justify-between">
|
||||
<span className="my-auto">{props.radio.getName()}</span>
|
||||
<div className="cursor-pointer rounded-md bg-red-800 p-2" onClick={() => {getApp().getAudioManager().removeSink(props.radio);}}>
|
||||
<div
|
||||
className="cursor-pointer rounded-md bg-red-800 p-2"
|
||||
onClick={() => {
|
||||
getApp().getAudioManager().removeSink(props.radio);
|
||||
}}
|
||||
>
|
||||
<FaTrash className={`text-gray-50`}></FaTrash>
|
||||
</div>
|
||||
</div>
|
||||
<OlFrequencyInput
|
||||
value={props.radio.getFrequency()}
|
||||
onChange={(value) => {
|
||||
props.radio.setFrequency(value)
|
||||
props.radio.setFrequency(value);
|
||||
}}
|
||||
/>
|
||||
<div className="flex flex-row gap-2">
|
||||
@@ -37,8 +42,17 @@ export function RadioPanel(props: { radio: RadioSink }) {
|
||||
}}
|
||||
></OlLabelToggle>
|
||||
|
||||
<kbd
|
||||
className={`
|
||||
my-auto ml-auto rounded-lg border border-gray-200 bg-gray-100 px-2
|
||||
py-1.5 text-xs font-semibold text-gray-800
|
||||
dark:border-gray-500 dark:bg-gray-600 dark:text-gray-100
|
||||
`}
|
||||
>
|
||||
{props.shortcutKey}
|
||||
</kbd>
|
||||
|
||||
<OlStateButton
|
||||
className="ml-auto"
|
||||
checked={props.radio.getPtt()}
|
||||
icon={faMicrophoneLines}
|
||||
onClick={() => {
|
||||
|
||||
@@ -54,7 +54,7 @@ export function AudioSourcePanel(props: { source: AudioSource }) {
|
||||
checked={false}
|
||||
icon={props.source.getPlaying() ? faPause : faPlay}
|
||||
onClick={() => {
|
||||
if (props.source instanceof FileSource) props.source.getPlaying() ? props.source.stop() : props.source.play();
|
||||
if (props.source instanceof FileSource) props.source.getPlaying() ? props.source.pause() : props.source.play();
|
||||
}}
|
||||
tooltip="Play file"
|
||||
></OlStateButton>
|
||||
@@ -106,9 +106,10 @@ export function AudioSourcePanel(props: { source: AudioSource }) {
|
||||
|
||||
<span className="text-sm">Connected to:</span>
|
||||
<div className="flex flex-col gap-1">
|
||||
{props.source.getConnectedTo().map((sink) => {
|
||||
{props.source.getConnectedTo().map((sink, idx) => {
|
||||
return (
|
||||
<div
|
||||
key={idx}
|
||||
className={`
|
||||
flex justify-start gap-2 rounded-full bg-olympus-400 px-4 py-1
|
||||
text-sm
|
||||
@@ -123,9 +124,10 @@ export function AudioSourcePanel(props: { source: AudioSource }) {
|
||||
</div>
|
||||
{availabileSinks.length > 0 && (
|
||||
<OlDropdown label="Connect to:">
|
||||
{availabileSinks.map((sink) => {
|
||||
{availabileSinks.map((sink, idx) => {
|
||||
return (
|
||||
<OlDropdownItem
|
||||
key={idx}
|
||||
onClick={() => {
|
||||
props.source.connect(sink);
|
||||
}}
|
||||
|
||||
@@ -33,6 +33,7 @@ export function ControlsPanel(props: {}) {
|
||||
{controls.map((control) => {
|
||||
return (
|
||||
<div
|
||||
key={control.text}
|
||||
className={`
|
||||
flex w-full justify-between gap-2 rounded-full py-1 pl-4 pr-1
|
||||
backdrop-blur-lg
|
||||
@@ -48,15 +49,15 @@ export function ControlsPanel(props: {}) {
|
||||
>
|
||||
{control.actions.map((action, idx) => {
|
||||
return (
|
||||
<>
|
||||
<div className={``}>
|
||||
<div key={idx} className="flex gap-1">
|
||||
<div>
|
||||
{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>}
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
@@ -27,22 +27,27 @@ 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 we are not in polygon drawing mode, force the draw polygon button off */
|
||||
if (drawingPolygon && getApp().getMap().getState() !== COALITIONAREA_DRAW_POLYGON) setDrawingPolygon(false);
|
||||
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 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);
|
||||
/* 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());
|
||||
/* Align the state of the coalition toggle to the coalition of the area */
|
||||
if (activeCoalitionArea && activeCoalitionArea?.getCoalition() !== areaCoalition) setAreaCoalition(activeCoalitionArea?.getCoalition());
|
||||
|
||||
if (!props.open) {
|
||||
if ([COALITIONAREA_EDIT, COALITIONAREA_DRAW_CIRCLE, COALITIONAREA_DRAW_POLYGON].includes(getApp()?.getMap()?.getState()))
|
||||
getApp().getMap().setState(IDLE);
|
||||
if (!props.open) {
|
||||
if ([COALITIONAREA_EDIT, COALITIONAREA_DRAW_CIRCLE, COALITIONAREA_DRAW_POLYGON].includes(getApp().getMap().getState()))
|
||||
getApp().getMap().setState(IDLE);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -79,8 +84,8 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
|
||||
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>
|
||||
<FaQuestionCircle className="my-4 ml-2 mr-6 text-gray-400" />
|
||||
<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>
|
||||
@@ -217,14 +222,14 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
|
||||
{getApp()
|
||||
.getGroundUnitDatabase()
|
||||
.getTypes()
|
||||
.map((type) => {
|
||||
.map((type, idx) => {
|
||||
if (!(type in typesSelection)) {
|
||||
typesSelection[type] = true;
|
||||
setTypesSelection(JSON.parse(JSON.stringify(typesSelection)));
|
||||
}
|
||||
|
||||
return (
|
||||
<OlDropdownItem className={`flex gap-4`}>
|
||||
<OlDropdownItem key={idx} className={`flex gap-4`}>
|
||||
<OlCheckbox
|
||||
checked={typesSelection[type]}
|
||||
onChange={(ev) => {
|
||||
|
||||
@@ -5,10 +5,15 @@ import { RadioPanel } from "./components/radiopanel";
|
||||
import { FaQuestionCircle } from "react-icons/fa";
|
||||
import { RadioSink } from "../../audio/radiosink";
|
||||
import { FaVolumeHigh } from "react-icons/fa6";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faClose } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
let shortcutKeys = ["Z", "X", "C", "V", "B", "N", "M", "K", "L"];
|
||||
|
||||
export function RadioMenu(props: { open: boolean; onClose: () => void; children?: JSX.Element | JSX.Element[] }) {
|
||||
const [radios, setRadios] = useState([] as RadioSink[]);
|
||||
const [audioManagerEnabled, setAudioManagerEnabled] = useState(false);
|
||||
const [showTip, setShowTip] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
/* Force a rerender */
|
||||
@@ -30,40 +35,56 @@ export function RadioMenu(props: { open: boolean; onClose: () => void; children?
|
||||
return (
|
||||
<Menu title="Radio" open={props.open} showBackButton={false} onClose={props.onClose}>
|
||||
<div className="p-4 text-sm text-gray-400">The radio menu allows you to talk on radio to the players online using SRS.</div>
|
||||
<div className="mx-6 flex rounded-lg bg-olympus-400 p-4 text-sm">
|
||||
{audioManagerEnabled && (
|
||||
<>
|
||||
<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">Use the radio controls to tune to a frequency, then click on the PTT button to talk. </div>
|
||||
<div className="text-gray-400">You can add up to 10 radios. Use the audio effects menu to play audio tracks or to add background noises.</div>
|
||||
</div>
|
||||
</>
|
||||
<>
|
||||
{showTip && (
|
||||
<div className="mx-6 flex rounded-lg bg-olympus-400 p-4 text-sm">
|
||||
{audioManagerEnabled ? (
|
||||
<>
|
||||
<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 radio controls to tune to a frequency, then click on the PTT button to talk. </div>
|
||||
<div className="text-gray-400">You can add up to 10 radios. Use the audio effects menu to play audio tracks or to add background noises.</div>
|
||||
</div>
|
||||
<div>
|
||||
<FontAwesomeIcon
|
||||
onClick={() => setShowTip(false)}
|
||||
icon={faClose}
|
||||
className={`
|
||||
ml-2 flex cursor-pointer items-center justify-center
|
||||
rounded-md p-2 text-lg
|
||||
dark:text-gray-500 dark:hover:bg-gray-700
|
||||
dark:hover:text-white
|
||||
hover:bg-gray-200
|
||||
`}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<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">
|
||||
To enable the radio menu, first start the audio backend with the{" "}
|
||||
<span
|
||||
className={`
|
||||
mx-1 mt-[-7px] inline-block translate-y-2 rounded-full
|
||||
border-[1px] border-white p-1
|
||||
`}
|
||||
>
|
||||
<FaVolumeHigh />
|
||||
</span>{" "}
|
||||
button on the navigation header.
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{!audioManagerEnabled && (
|
||||
<>
|
||||
<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">
|
||||
To enable the radio menu, first start the audio backend with the{" "}
|
||||
<span
|
||||
className={`
|
||||
mx-1 mt-[-7px] inline-block translate-y-2 rounded-full
|
||||
border-[1px] border-white p-1
|
||||
`}
|
||||
>
|
||||
<FaVolumeHigh />
|
||||
</span>{" "}
|
||||
button on the navigation header.
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
|
||||
<div
|
||||
className={`
|
||||
@@ -71,8 +92,8 @@ export function RadioMenu(props: { open: boolean; onClose: () => void; children?
|
||||
dark:text-white
|
||||
`}
|
||||
>
|
||||
{radios.map((radio) => {
|
||||
return <RadioPanel radio={radio}></RadioPanel>;
|
||||
{radios.map((radio, idx) => {
|
||||
return <RadioPanel shortcutKey={shortcutKeys[idx]} key={radio.getName()} radio={radio}></RadioPanel>;
|
||||
})}
|
||||
{audioManagerEnabled && radios.length < 10 && (
|
||||
<button
|
||||
|
||||
@@ -23,8 +23,8 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
|
||||
const [filteredAircraft, filteredHelicopters, filteredAirDefense, filteredGroundUnits, filteredNavyUnits] = getUnitsByLabel(filterString);
|
||||
|
||||
useEffect(() => {
|
||||
if (!props.open) {
|
||||
if (getApp()?.getMap()?.getState() === SPAWN_UNIT) getApp().getMap().setState(IDLE);
|
||||
if (!props.open && getApp()) {
|
||||
if (getApp().getMap().getState() === SPAWN_UNIT) getApp().getMap().setState(IDLE);
|
||||
if (blueprint !== null) setBlueprint(null);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -38,7 +38,7 @@ import {
|
||||
olButtonsVisibilityOlympus,
|
||||
} from "../components/olicons";
|
||||
import { Coalition } from "../../types/types";
|
||||
import { ftToM, getUnitDatabaseByCategory, getUnitsByLabel, knotsToMs, mToFt, msToKnots } from "../../other/utils";
|
||||
import { ftToM, getUnitsByLabel, 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";
|
||||
@@ -272,9 +272,9 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
human: ["Human", olButtonsVisibilityHuman],
|
||||
olympus: ["Olympus controlled", olButtonsVisibilityOlympus],
|
||||
dcs: ["From DCS mission", olButtonsVisibilityDcs],
|
||||
}).map((entry) => {
|
||||
}).map((entry, idx) => {
|
||||
return (
|
||||
<div className="flex justify-between">
|
||||
<div className="flex justify-between" key={idx}>
|
||||
<span className="font-light text-white">{entry[1][0] as string}</span>
|
||||
<OlToggle
|
||||
key={entry[0]}
|
||||
@@ -297,81 +297,83 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
Types and coalitions
|
||||
</div>
|
||||
<table>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td className="pb-4 text-center font-bold text-blue-500">BLUE</td>
|
||||
<td className="pb-4 text-center font-bold text-gray-500">NEUTRAL</td>
|
||||
<td className="pb-4 text-center font-bold text-red-500">RED</td>
|
||||
</tr>
|
||||
{selectionBlueprint === null &&
|
||||
Object.entries({
|
||||
aircraft: olButtonsVisibilityAircraft,
|
||||
helicopter: olButtonsVisibilityHelicopter,
|
||||
"groundunit-sam": olButtonsVisibilityGroundunitSam,
|
||||
groundunit: olButtonsVisibilityGroundunit,
|
||||
navyunit: olButtonsVisibilityNavyunit,
|
||||
}).map((entry) => {
|
||||
return (
|
||||
<tr>
|
||||
<td className="text-lg text-gray-200">
|
||||
<FontAwesomeIcon icon={entry[1]} />
|
||||
</td>
|
||||
{["blue", "neutral", "red"].map((coalition) => {
|
||||
return (
|
||||
<td className="text-center">
|
||||
<OlCheckbox
|
||||
checked={selectionFilter[coalition][entry[0]]}
|
||||
disabled={selectionBlueprint !== null}
|
||||
onChange={() => {
|
||||
selectionFilter[coalition][entry[0]] = !selectionFilter[coalition][entry[0]];
|
||||
setSelectionFilter(JSON.parse(JSON.stringify(selectionFilter)));
|
||||
}}
|
||||
/>
|
||||
</td>
|
||||
);
|
||||
})}
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
<tr>
|
||||
<td className="text-gray-200"></td>
|
||||
<td className="text-center">
|
||||
<OlCheckbox
|
||||
checked={Object.values(selectionFilter["blue"]).some((value) => value)}
|
||||
onChange={() => {
|
||||
const newValue = !Object.values(selectionFilter["blue"]).some((value) => value);
|
||||
Object.keys(selectionFilter["blue"]).forEach((key) => {
|
||||
selectionFilter["blue"][key] = newValue;
|
||||
});
|
||||
setSelectionFilter(JSON.parse(JSON.stringify(selectionFilter)));
|
||||
}}
|
||||
/>
|
||||
</td>
|
||||
<td className="text-center">
|
||||
<OlCheckbox
|
||||
checked={Object.values(selectionFilter["neutral"]).some((value) => value)}
|
||||
onChange={() => {
|
||||
const newValue = !Object.values(selectionFilter["neutral"]).some((value) => value);
|
||||
Object.keys(selectionFilter["neutral"]).forEach((key) => {
|
||||
selectionFilter["neutral"][key] = newValue;
|
||||
});
|
||||
setSelectionFilter(JSON.parse(JSON.stringify(selectionFilter)));
|
||||
}}
|
||||
/>
|
||||
</td>
|
||||
<td className="text-center">
|
||||
<OlCheckbox
|
||||
checked={Object.values(selectionFilter["red"]).some((value) => value)}
|
||||
onChange={() => {
|
||||
const newValue = !Object.values(selectionFilter["red"]).some((value) => value);
|
||||
Object.keys(selectionFilter["red"]).forEach((key) => {
|
||||
selectionFilter["red"][key] = newValue;
|
||||
});
|
||||
setSelectionFilter(JSON.parse(JSON.stringify(selectionFilter)));
|
||||
}}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td className="pb-4 text-center font-bold text-blue-500">BLUE</td>
|
||||
<td className="pb-4 text-center font-bold text-gray-500">NEUTRAL</td>
|
||||
<td className="pb-4 text-center font-bold text-red-500">RED</td>
|
||||
</tr>
|
||||
{selectionBlueprint === null &&
|
||||
Object.entries({
|
||||
aircraft: olButtonsVisibilityAircraft,
|
||||
helicopter: olButtonsVisibilityHelicopter,
|
||||
"groundunit-sam": olButtonsVisibilityGroundunitSam,
|
||||
groundunit: olButtonsVisibilityGroundunit,
|
||||
navyunit: olButtonsVisibilityNavyunit,
|
||||
}).map((entry, idx) => {
|
||||
return (
|
||||
<tr key={idx}>
|
||||
<td className="text-lg text-gray-200">
|
||||
<FontAwesomeIcon icon={entry[1]} />
|
||||
</td>
|
||||
{["blue", "neutral", "red"].map((coalition) => {
|
||||
return (
|
||||
<td className="text-center" key={coalition}>
|
||||
<OlCheckbox
|
||||
checked={selectionFilter[coalition][entry[0]]}
|
||||
disabled={selectionBlueprint !== null}
|
||||
onChange={() => {
|
||||
selectionFilter[coalition][entry[0]] = !selectionFilter[coalition][entry[0]];
|
||||
setSelectionFilter(JSON.parse(JSON.stringify(selectionFilter)));
|
||||
}}
|
||||
/>
|
||||
</td>
|
||||
);
|
||||
})}
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
<tr>
|
||||
<td className="text-gray-200"></td>
|
||||
<td className="text-center">
|
||||
<OlCheckbox
|
||||
checked={Object.values(selectionFilter["blue"]).some((value) => value)}
|
||||
onChange={() => {
|
||||
const newValue = !Object.values(selectionFilter["blue"]).some((value) => value);
|
||||
Object.keys(selectionFilter["blue"]).forEach((key) => {
|
||||
selectionFilter["blue"][key] = newValue;
|
||||
});
|
||||
setSelectionFilter(JSON.parse(JSON.stringify(selectionFilter)));
|
||||
}}
|
||||
/>
|
||||
</td>
|
||||
<td className="text-center">
|
||||
<OlCheckbox
|
||||
checked={Object.values(selectionFilter["neutral"]).some((value) => value)}
|
||||
onChange={() => {
|
||||
const newValue = !Object.values(selectionFilter["neutral"]).some((value) => value);
|
||||
Object.keys(selectionFilter["neutral"]).forEach((key) => {
|
||||
selectionFilter["neutral"][key] = newValue;
|
||||
});
|
||||
setSelectionFilter(JSON.parse(JSON.stringify(selectionFilter)));
|
||||
}}
|
||||
/>
|
||||
</td>
|
||||
<td className="text-center">
|
||||
<OlCheckbox
|
||||
checked={Object.values(selectionFilter["red"]).some((value) => value)}
|
||||
onChange={() => {
|
||||
const newValue = !Object.values(selectionFilter["red"]).some((value) => value);
|
||||
Object.keys(selectionFilter["red"]).forEach((key) => {
|
||||
selectionFilter["red"][key] = newValue;
|
||||
});
|
||||
setSelectionFilter(JSON.parse(JSON.stringify(selectionFilter)));
|
||||
}}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div>
|
||||
<div ref={searchBarRef}>
|
||||
@@ -466,9 +468,10 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
{
|
||||
<>
|
||||
{["blue", "red", "neutral"].map((coalition) => {
|
||||
return Object.keys(unitOccurences[coalition]).map((name) => {
|
||||
return Object.keys(unitOccurences[coalition]).map((name, idx) => {
|
||||
return (
|
||||
<div
|
||||
key={idx}
|
||||
data-coalition={coalition}
|
||||
className={`
|
||||
flex content-center justify-between border-l-4
|
||||
@@ -648,6 +651,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
{[olButtonsRoeHold, olButtonsRoeReturn, olButtonsRoeDesignated, olButtonsRoeFree].map((icon, idx) => {
|
||||
return (
|
||||
<OlButtonGroupItem
|
||||
key={idx}
|
||||
onClick={() => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
unit.setROE(ROEs[idx]);
|
||||
@@ -685,6 +689,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
{[olButtonsThreatNone, olButtonsThreatPassive, olButtonsThreatManoeuvre, olButtonsThreatEvade].map((icon, idx) => {
|
||||
return (
|
||||
<OlButtonGroupItem
|
||||
key={idx}
|
||||
onClick={() => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
unit.setReactionToThreat(reactionsToThreat[idx]);
|
||||
@@ -716,6 +721,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
{[olButtonsEmissionsSilent, olButtonsEmissionsDefend, olButtonsEmissionsAttack, olButtonsEmissionsFree].map((icon, idx) => {
|
||||
return (
|
||||
<OlButtonGroupItem
|
||||
key={idx}
|
||||
onClick={() => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
unit.setEmissionsCountermeasures(emissionsCountermeasures[idx]);
|
||||
@@ -847,6 +853,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
{[olButtonsScatter1, olButtonsScatter2, olButtonsScatter3].map((icon, idx) => {
|
||||
return (
|
||||
<OlButtonGroupItem
|
||||
key={idx}
|
||||
onClick={() => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
unit.setShotsScatter(idx + 1);
|
||||
@@ -878,6 +885,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
{[olButtonsIntensity1, olButtonsIntensity2, olButtonsIntensity3].map((icon, idx) => {
|
||||
return (
|
||||
<OlButtonGroupItem
|
||||
key={idx}
|
||||
onClick={() => {
|
||||
selectedUnits.forEach((unit) => {
|
||||
unit.setShotsIntensity(idx + 1);
|
||||
@@ -992,6 +1000,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
{["Overlord", "Magic", "Wizard", "Focus", "Darkstar"].map((name, idx) => {
|
||||
return (
|
||||
<OlDropdownItem
|
||||
key={idx}
|
||||
onClick={() => {
|
||||
if (activeAdvancedSettings) activeAdvancedSettings.radio.callsign = idx + 1;
|
||||
setActiveAdvancedSettings(JSON.parse(JSON.stringify(activeAdvancedSettings)));
|
||||
@@ -1010,6 +1019,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
{["Texaco", "Arco", "Shell"].map((name, idx) => {
|
||||
return (
|
||||
<OlDropdownItem
|
||||
key={idx}
|
||||
onClick={() => {
|
||||
if (activeAdvancedSettings) activeAdvancedSettings.radio.callsign = idx + 1;
|
||||
setActiveAdvancedSettings(JSON.parse(JSON.stringify(activeAdvancedSettings)));
|
||||
@@ -1071,6 +1081,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
my-auto w-20
|
||||
`}>
|
||||
<OlDropdownItem
|
||||
key={"X"}
|
||||
onClick={() => {
|
||||
if (activeAdvancedSettings) activeAdvancedSettings.TACAN.XY = "X";
|
||||
setActiveAdvancedSettings(JSON.parse(JSON.stringify(activeAdvancedSettings)));
|
||||
@@ -1079,6 +1090,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
X
|
||||
</OlDropdownItem>
|
||||
<OlDropdownItem
|
||||
key={"Y"}
|
||||
onClick={() => {
|
||||
if (activeAdvancedSettings) activeAdvancedSettings.TACAN.XY = "Y";
|
||||
setActiveAdvancedSettings(JSON.parse(JSON.stringify(activeAdvancedSettings)));
|
||||
@@ -1113,12 +1125,15 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
||||
|
||||
<div className="text-sm text-gray-200">Radio frequency</div>
|
||||
<div className="flex content-center gap-2">
|
||||
<OlFrequencyInput value={activeAdvancedSettings? activeAdvancedSettings.radio.frequency: 251000000} onChange={(value) => {
|
||||
if (activeAdvancedSettings) {
|
||||
activeAdvancedSettings.radio.frequency = value;
|
||||
setActiveAdvancedSettings(JSON.parse(JSON.stringify(activeAdvancedSettings)));
|
||||
}
|
||||
}}/>
|
||||
<OlFrequencyInput
|
||||
value={activeAdvancedSettings ? activeAdvancedSettings.radio.frequency : 251000000}
|
||||
onChange={(value) => {
|
||||
if (activeAdvancedSettings) {
|
||||
activeAdvancedSettings.radio.frequency = value;
|
||||
setActiveAdvancedSettings(JSON.parse(JSON.stringify(activeAdvancedSettings)));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex pt-8">
|
||||
|
||||
@@ -18,8 +18,7 @@ export function UnitMouseControlBar(props: {}) {
|
||||
/* Initialize the "scroll" position of the element */
|
||||
var scrollRef = useRef(null);
|
||||
useEffect(() => {
|
||||
if (scrollRef.current)
|
||||
onScroll(scrollRef.current);
|
||||
if (scrollRef.current) onScroll(scrollRef.current);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
@@ -94,9 +93,10 @@ export function UnitMouseControlBar(props: {}) {
|
||||
/>
|
||||
)}
|
||||
<div className="flex gap-2 overflow-x-auto no-scrollbar p-2" onScroll={(ev) => onScroll(ev.target)} ref={scrollRef}>
|
||||
{Object.values(contextActionsSet.getContextActions()).map((contextAction) => {
|
||||
{Object.values(contextActionsSet.getContextActions()).map((contextAction: ContextAction) => {
|
||||
return (
|
||||
<OlStateButton
|
||||
key={contextAction.getId()}
|
||||
checked={contextAction === activeContextAction}
|
||||
icon={contextAction.getIcon()}
|
||||
tooltip={contextAction.getLabel()}
|
||||
@@ -109,13 +109,13 @@ export function UnitMouseControlBar(props: {}) {
|
||||
setActiveContextAction(contextAction);
|
||||
getApp().getMap().setState(CONTEXT_ACTION, {
|
||||
contextAction: contextAction,
|
||||
defaultContextAction: contextActionsSet.getDefaultContextAction()
|
||||
defaultContextAction: contextActionsSet.getDefaultContextAction(),
|
||||
});
|
||||
} else {
|
||||
setActiveContextAction(null);
|
||||
getApp().getMap().setState(CONTEXT_ACTION, {
|
||||
contextAction: null,
|
||||
defaultContextAction: contextActionsSet.getDefaultContextAction()
|
||||
defaultContextAction: contextActionsSet.getDefaultContextAction(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ export function UnitSpawnMenu(props: { blueprint: UnitBlueprint; spawnAtLocation
|
||||
},
|
||||
});
|
||||
} else {
|
||||
if (getApp()?.getMap()?.getState() === SPAWN_UNIT) getApp().getMap().setState(IDLE);
|
||||
if (getApp().getMap().getState() === SPAWN_UNIT) getApp().getMap().setState(IDLE);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user