Multiple bugfixes

This commit is contained in:
Davide Passoni
2024-11-22 14:56:26 +01:00
parent 5c3960868a
commit 897afb2fef
25 changed files with 498 additions and 532 deletions

View File

@@ -1,15 +1,28 @@
import React from "react";
import React, { useEffect } from "react";
import { ModalEvent } from "../../../events";
export function Modal(props: { open: boolean; children?: JSX.Element | JSX.Element[]; className?: string }) {
useEffect(() => {
ModalEvent.dispatch(props.open);
}, [props.open]);
export function Modal(props: { grayout?: boolean; children?: JSX.Element | JSX.Element[]; className?: string }) {
return (
<div
className={`
${props.className}
fixed left-[50%] top-[50%] z-40 translate-x-[-50%] translate-y-[-50%]
rounded-xl border-[1px] border-solid border-gray-700 drop-shadow-md
`}
>
{props.children}
</div>
<>
{props.open && (
<>
<div className={`fixed left-0 top-0 z-30 h-full w-full bg-[#111111]/95`}></div>
<div
className={`
${props.className}
fixed left-[50%] top-[50%] z-40 translate-x-[-50%]
translate-y-[-50%] rounded-xl border-[1px] border-solid
border-gray-700 drop-shadow-md
`}
>
{props.children}
</div>
</>
)}
</>
);
}

View File

@@ -6,7 +6,6 @@ import { getApp } from "../../olympusapp";
import { OlympusState } from "../../constants/constants";
import { Shortcut } from "../../shortcut/shortcut";
import { BindShortcutRequestEvent, ShortcutsChangedEvent } from "../../events";
import { OlToggle } from "../components/oltoggle";
export function KeybindModal(props: { open: boolean }) {
const [shortcuts, setShortcuts] = useState({} as { [key: string]: Shortcut });
@@ -23,180 +22,139 @@ export function KeybindModal(props: { open: boolean }) {
document.addEventListener("keydown", (ev) => {
if (ev.code) {
setCode(ev.code);
if (ev.code.indexOf("Control") < 0) setCtrlKey(ev.ctrlKey);
else setCtrlKey(false);
if (ev.code.indexOf("Shift") < 0) setShiftKey(ev.shiftKey);
else setShiftKey(false);
if (ev.code.indexOf("Alt") < 0) setAltKey(ev.altKey);
else setAltKey(false);
}
});
}, []);
useEffect(() => {
setCode(shortcut?.getOptions().code ?? null)
setShiftKey(shortcut?.getOptions().shiftKey)
setAltKey(shortcut?.getOptions().altKey)
setCtrlKey(shortcut?.getOptions().ctrlKey)
}, [shortcut])
setCode(shortcut?.getOptions().code ?? null);
setShiftKey(shortcut?.getOptions().shiftKey);
setAltKey(shortcut?.getOptions().altKey);
setCtrlKey(shortcut?.getOptions().ctrlKey);
}, [shortcut]);
let available: null | boolean = code ? true : null;
let inUseShortcut: null | Shortcut = null;
let inUseShortcuts: Shortcut[] = [];
for (let id in shortcuts) {
if (id !== shortcut?.getId() &&
if (
id !== shortcut?.getId() &&
code === shortcuts[id].getOptions().code &&
((shiftKey === undefined && shortcuts[id].getOptions().shiftKey !== undefined) ||
(shiftKey !== undefined && shortcuts[id].getOptions().shiftKey === undefined) ||
shiftKey === shortcuts[id].getOptions().shiftKey) && (
(altKey === undefined && shortcuts[id].getOptions().altKey !== undefined) ||
shiftKey === shortcuts[id].getOptions().shiftKey) &&
((altKey === undefined && shortcuts[id].getOptions().altKey !== undefined) ||
(altKey !== undefined && shortcuts[id].getOptions().altKey === undefined) ||
altKey === shortcuts[id].getOptions().altKey) && (
(ctrlKey === undefined && shortcuts[id].getOptions().ctrlKey !== undefined) ||
altKey === shortcuts[id].getOptions().altKey) &&
((ctrlKey === undefined && shortcuts[id].getOptions().ctrlKey !== undefined) ||
(ctrlKey !== undefined && shortcuts[id].getOptions().ctrlKey === undefined) ||
ctrlKey === shortcuts[id].getOptions().ctrlKey)
) {
available = false;
inUseShortcut = shortcuts[id];
inUseShortcuts.push(shortcuts[id]);
}
}
return (
<>
{props.open && (
<>
<Modal
<Modal
open={props.open}
className={`
inline-flex h-fit w-[600px] overflow-y-auto scroll-smooth bg-white p-10
dark:bg-olympus-800
max-md:h-full max-md:max-h-full max-md:w-full max-md:rounded-none
max-md:border-none
`}
>
<div className="flex h-full w-full flex-col gap-4">
<div className={`flex flex-col items-start gap-2`}>
<span
className={`
inline-flex h-fit w-[600px] overflow-y-auto scroll-smooth bg-white
p-10
dark:bg-olympus-800
max-md:h-full max-md:max-h-full max-md:w-full max-md:rounded-none
max-md:border-none
text-gray-800 text-md
dark:text-white
`}
>
<div className="flex h-full w-full flex-col gap-4">
<div className={`flex flex-col items-start gap-2`}>
<span
className={`
text-gray-800 text-md
dark:text-white
`}
>
{shortcut?.getOptions().label}
</span>
<span
className={`
text-gray-800 text-md
dark:text-gray-500
`}
>
Press the key you want to bind to this event
</span>
</div>
<div className="w-full text-center text-white">{code}</div>
<div className="flex flex-col gap-2">
<div className="flex gap-2">
<OlToggle
onClick={() => {
if (shiftKey === false) setShiftKey(undefined);
else if (shiftKey === undefined) setShiftKey(true);
else setShiftKey(false);
}}
toggled={shiftKey}
></OlToggle>
<div className="text-white">
{shiftKey === true && "Shift key must be pressed"}
{shiftKey === undefined && "Shift key can be anything"}
{shiftKey === false && "Shift key must NOT be pressed"}
</div>
</div>
<div className="flex gap-2">
<OlToggle
onClick={() => {
if (altKey === false) setAltKey(undefined);
else if (altKey === undefined) setAltKey(true);
else setAltKey(false);
}}
toggled={altKey}
></OlToggle>
<div className="text-white">
{altKey === true && "Alt key must be pressed"}
{altKey === undefined && "Alt key can be anything"}
{altKey === false && "Alt key must NOT be pressed"}
</div>
</div>
<div className="flex gap-2">
<OlToggle
onClick={() => {
if (ctrlKey === false) setCtrlKey(undefined);
else if (ctrlKey === undefined) setCtrlKey(true);
else setCtrlKey(false);
}}
toggled={ctrlKey}
></OlToggle>
<div className="text-white">
{ctrlKey === true && "Ctrl key must be pressed"}
{ctrlKey === undefined && "Ctrl key can be anything"}
{ctrlKey === false && "Ctrl key must NOT be pressed"}
</div>
</div>
</div>
<div className="text-white">
{available === true && <div className="text-green-600">Keybind is free!</div>}
{available === false && (
<div>
Keybind is already in use: <span className={`
font-bold text-red-600
`}>{inUseShortcut?.getOptions().label}</span>
</div>
)}
</div>
<div className="flex justify-end">
{available && shortcut && (
<button
type="button"
onClick={() => {
if (shortcut && code) {
let options = shortcut.getOptions();
options.code = code;
options.altKey = altKey;
options.shiftKey = shiftKey;
options.ctrlKey = ctrlKey;
getApp().getShortcutManager().setShortcutOption(shortcut.getId(), options);
getApp().setState(OlympusState.OPTIONS);
}
}}
className={`
mb-2 me-2 ml-auto flex content-center items-center gap-2
rounded-sm 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
`}
>
Continue
<FontAwesomeIcon icon={faArrowRight} />
</button>
)}
<button
type="button"
onClick={() => getApp().setState(OlympusState.OPTIONS)}
className={`
mb-2 me-2 flex content-center items-center gap-2 rounded-sm
border-[1px] bg-blue-700 px-5 py-2.5 text-sm font-medium
text-white
dark:border-gray-600 dark:bg-gray-800 dark:text-gray-400
dark:hover:bg-gray-700 dark:focus:ring-blue-800
focus:outline-none focus:ring-4 focus:ring-blue-300
hover:bg-blue-800
`}
>
Back
</button>
{shortcut?.getOptions().label}
</span>
<span
className={`
text-gray-800 text-md
dark:text-gray-500
`}
>
Press the key you want to bind to this event
</span>
</div>
<div className="w-full text-center text-white">
{ctrlKey && "Ctrl + "}
{altKey && "Alt + "}
{shiftKey && "Shift + "}
{code}
</div>
<div className="text-white">
{available === true && <div className="text-green-600">Keybind is free!</div>}
{available === false && (
<div className="flex flex-col gap-2">
<div className="flex gap-2">
Keybind is already in use: <div className={`
flex flex-wrap gap-2 font-bold text-orange-600
`}>{inUseShortcuts.map((shortcut) => <span>{shortcut.getOptions().label}</span>)}</div>
</div>
<div className="text-gray-500">A key combination can be assigned to multiple actions, and all bound actions will fire</div>
</div>
</Modal>
<div className={`fixed left-0 top-0 z-30 h-full w-full bg-[#111111]/95`}></div>
</>
)}
</>
)}
</div>
<div className="flex justify-end">
{shortcut && (
<button
type="button"
onClick={() => {
if (shortcut && code) {
let options = shortcut.getOptions();
options.code = code;
options.altKey = altKey;
options.shiftKey = shiftKey;
options.ctrlKey = ctrlKey;
getApp().getShortcutManager().setShortcutOption(shortcut.getId(), options);
getApp().setState(OlympusState.OPTIONS);
}
}}
className={`
mb-2 me-2 ml-auto flex content-center items-center gap-2
rounded-sm 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
`}
>
Continue
<FontAwesomeIcon icon={faArrowRight} />
</button>
)}
<button
type="button"
onClick={() => getApp().setState(OlympusState.OPTIONS)}
className={`
mb-2 me-2 flex content-center items-center gap-2 rounded-sm
border-[1px] bg-blue-700 px-5 py-2.5 text-sm font-medium
text-white
dark:border-gray-600 dark:bg-gray-800 dark:text-gray-400
dark:hover:bg-gray-700 dark:focus:ring-blue-800
focus:outline-none focus:ring-4 focus:ring-blue-300
hover:bg-blue-800
`}
>
Back
</button>
</div>
</div>
</Modal>
);
}

View File

@@ -1,26 +1,25 @@
import React, { useEffect, useState } from "react";
import { Modal } from "./components/modal";
import { Card } from "./components/card";
import { ErrorCallout } from "../../ui/components/olcallout";
import { ErrorCallout } from "../components/olcallout";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faArrowRight, faCheckCircle, faExternalLink } from "@fortawesome/free-solid-svg-icons";
import { getApp, VERSION } from "../../olympusapp";
import { sha256 } from "js-sha256";
import { BLUE_COMMANDER, GAME_MASTER, OlympusState, RED_COMMANDER } from "../../constants/constants";
import { FaTrash, FaXmark } from "react-icons/fa6";
export function LoginModal(props: {}) {
export function LoginModal(props: { open: boolean }) {
// TODO: add warning if not in secure context and some features are disabled
const [password, setPassword] = useState("");
const [profileName, setProfileName] = useState("Game Master");
const [profileName, setProfileName] = useState("");
const [checkingPassword, setCheckingPassword] = useState(false);
const [loginError, setLoginError] = useState(false);
const [commandMode, setCommandMode] = useState(null as null | string);
useEffect(() => {
/* Set the profile name */
getApp().setProfile(profileName);
}, [profileName])
if (profileName !== "") getApp().setProfile(profileName);
}, [profileName]);
function checkPassword(password: string) {
setCheckingPassword(true);
@@ -51,14 +50,14 @@ export function LoginModal(props: {}) {
getApp().setState(OlympusState.IDLE);
/* If no profile exists already with that name, create it from scratch from the defaults */
if (getApp().getProfile() === null)
getApp().saveProfile();
if (getApp().getProfile() === null) getApp().saveProfile();
/* Load the profile */
getApp().loadProfile();
}
return (
<Modal
open={props.open}
className={`
inline-flex h-[75%] max-h-[570px] w-[80%] max-w-[1100px] overflow-y-auto
scroll-smooth bg-white
@@ -239,9 +238,7 @@ export function LoginModal(props: {}) {
required
/>
</div>
<div className="text-xs text-gray-400">
The profile name you choose determines the saved key binds, groups and options you see.
</div>
<div className="text-xs text-gray-400">The profile name you choose determines the saved key binds, groups and options you see.</div>
<div className="flex">
<button
type="button"

View File

@@ -1,111 +0,0 @@
import React from "react";
import { Modal } from "./components/modal";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faArrowRight } from "@fortawesome/free-solid-svg-icons";
import { FaLock } from "react-icons/fa6";
import { getApp } from "../../olympusapp";
import { OlympusState } from "../../constants/constants";
export function ProtectionPrompt(props: { open: boolean }) {
return (
<>
{props.open && (
<>
<Modal
className={`
inline-flex h-fit w-[600px] overflow-y-auto scroll-smooth bg-white
p-10
dark:bg-olympus-800
max-md:h-full max-md:max-h-full max-md:w-full max-md:rounded-none
max-md:border-none
`}
>
<div className="flex h-full w-full flex-col gap-12">
<div className={`flex flex-col items-start gap-2`}>
<span
className={`
text-gray-800 text-md
dark:text-white
`}
>
Your selection contains protected units, are you sure you want to continue?
</span>
<span
className={`
text-gray-800 text-md
dark:text-gray-500
`}
>
Pressing "Continue" will cause all DCS controlled units in the current selection to abort their mission and start following Olympus commands
only.
</span>
<span
className={`
text-gray-800 text-md
dark:text-gray-500
`}
>
If you are trying to delete a human player unit, they will be killed and de-slotted. Be careful!
</span>
<span
className={`
text-gray-800 text-md
dark:text-gray-500
`}
>
To disable this warning, press on the{" "}
<span
className={`
inline-block translate-y-3 rounded-full border-[1px]
border-gray-900 bg-red-500 p-2 text-olympus-900
`}
>
<FaLock />
</span>{" "}
button
</span>
</div>
<div className="flex">
<button
type="button"
onClick={() => {
getApp().getUnitsManager().executeProtectionCallback();
getApp().setState(OlympusState.UNIT_CONTROL);
}}
className={`
mb-2 me-2 ml-auto flex content-center items-center gap-2
rounded-sm 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
`}
>
Continue
<FontAwesomeIcon className={`my-auto`} icon={faArrowRight} />
</button>
<button
type="button"
onClick={() => getApp().setState(OlympusState.UNIT_CONTROL)}
className={`
mb-2 me-2 flex content-center items-center gap-2 rounded-sm
border-[1px] bg-blue-700 px-5 py-2.5 text-sm font-medium
text-white
dark:border-gray-600 dark:bg-gray-800 dark:text-gray-400
dark:hover:bg-gray-700 dark:focus:ring-blue-800
focus:outline-none focus:ring-4 focus:ring-blue-300
hover:bg-blue-800
`}
>
Back
</button>
</div>
</div>
</Modal>
<div className={`fixed left-0 top-0 z-30 h-full w-full bg-[#111111]/95`}></div>
</>
)}
</>
);
}

View File

@@ -0,0 +1,101 @@
import React from "react";
import { Modal } from "./components/modal";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faArrowRight } from "@fortawesome/free-solid-svg-icons";
import { FaLock } from "react-icons/fa6";
import { getApp } from "../../olympusapp";
import { OlympusState } from "../../constants/constants";
export function ProtectionPromptModal(props: { open: boolean }) {
return (
<Modal
open={props.open}
className={`
inline-flex h-fit w-[600px] overflow-y-auto scroll-smooth bg-white p-10
dark:bg-olympus-800
max-md:h-full max-md:max-h-full max-md:w-full max-md:rounded-none
max-md:border-none
`}
>
<div className="flex h-full w-full flex-col gap-12">
<div className={`flex flex-col items-start gap-2`}>
<span
className={`
text-gray-800 text-md
dark:text-white
`}
>
Your selection contains protected units, are you sure you want to continue?
</span>
<span
className={`
text-gray-800 text-md
dark:text-gray-500
`}
>
Pressing "Continue" will cause all DCS controlled units in the current selection to abort their mission and start following Olympus commands only.
</span>
<span
className={`
text-gray-800 text-md
dark:text-gray-500
`}
>
If you are trying to delete a human player unit, they will be killed and de-slotted. Be careful!
</span>
<span
className={`
text-gray-800 text-md
dark:text-gray-500
`}
>
To disable this warning, press on the{" "}
<span
className={`
inline-block translate-y-3 rounded-full border-[1px]
border-gray-900 bg-red-500 p-2 text-olympus-900
`}
>
<FaLock />
</span>{" "}
button
</span>
</div>
<div className="flex">
<button
type="button"
onClick={() => {
getApp().getUnitsManager().executeProtectionCallback();
getApp().setState(OlympusState.UNIT_CONTROL);
}}
className={`
mb-2 me-2 ml-auto flex content-center items-center gap-2
rounded-sm 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
`}
>
Continue
<FontAwesomeIcon className={`my-auto`} icon={faArrowRight} />
</button>
<button
type="button"
onClick={() => getApp().setState(OlympusState.UNIT_CONTROL)}
className={`
mb-2 me-2 flex content-center items-center gap-2 rounded-sm
border-[1px] bg-blue-700 px-5 py-2.5 text-sm font-medium
text-white
dark:border-gray-600 dark:bg-gray-800 dark:text-gray-400
dark:hover:bg-gray-700 dark:focus:ring-blue-800
focus:outline-none focus:ring-4 focus:ring-blue-300
hover:bg-blue-800
`}
>
Back
</button>
</div>
</div>
</Modal>
);
}

View File

@@ -114,7 +114,7 @@ export function AirbaseMenu(props: { open: boolean; onClose: () => void; childre
<div className="flex flex-col gap-2">
{airbase?.getChartData().runways.map((runway, idx) => {
return (
<>
<div key={idx}>
{Object.keys(runway.headings[0]).map((runwayName) => {
return (
<div key={`${idx}-${runwayName}`} className={`
@@ -137,7 +137,7 @@ export function AirbaseMenu(props: { open: boolean; onClose: () => void; childre
</div>
);
})}
</>
</div>
);
})}
</div>

View File

@@ -19,7 +19,7 @@ export function AudioMenu(props: { open: boolean; onClose: () => void; children?
const [audioManagerEnabled, setAudioManagerEnabled] = useState(false);
const [activeSource, setActiveSource] = useState(null as AudioSource | null);
const [count, setCount] = useState(0);
const [shortcuts, setShortcuts] = useState({})
const [shortcuts, setShortcuts] = useState({});
/* Preallocate 128 references for the source and sink panels. If the number of references changes, React will give an error */
const sourceRefs = Array(128)
@@ -251,20 +251,19 @@ export function AudioMenu(props: { open: boolean; onClose: () => void; children?
right: (end as HTMLDivElement).offsetLeft + (end as HTMLDivElement).clientWidth,
};
return (
<>
<div
className={`
absolute rounded-br-md rounded-tr-md border-2 border-l-0
`}
style={{
top: `${(startRect.bottom + startRect.top) / 2}px`,
left: `${startRect.right}px`,
height: `${endRect.top - startRect.top + (endRect.height - startRect.height) / 2}px`,
width: `${(lineCounters[idx] - 1) * lineDistance + 30}px`,
borderColor: lineColors[idx],
}}
></div>
</>
<div
key={idx}
className={`
absolute rounded-br-md rounded-tr-md border-2 border-l-0
`}
style={{
top: `${(startRect.bottom + startRect.top) / 2}px`,
left: `${startRect.right}px`,
height: `${endRect.top - startRect.top + (endRect.height - startRect.height) / 2}px`,
width: `${(lineCounters[idx] - 1) * lineDistance + 30}px`,
borderColor: lineColors[idx],
}}
/>
);
}
})
@@ -281,29 +280,26 @@ export function AudioMenu(props: { open: boolean; onClose: () => void; children?
right: (div as HTMLDivElement).offsetLeft + (div as HTMLDivElement).clientWidth,
};
return (
<>
<div>
<div
data-active={activeSource === sources[idx]}
className={`
absolute translate-y-[-50%] cursor-pointer rounded-full
bg-blue-600 p-1 text-xs text-white
data-[active='true']:bg-white
data-[active='true']:text-blue-600
hover:bg-blue-800
`}
style={{
top: `${(divRect.bottom + divRect.top) / 2}px`,
left: `${divRect.right - 10}px`,
}}
onClick={() => {
activeSource !== sources[idx] ? setActiveSource(sources[idx]) : setActiveSource(null);
}}
>
<FaPlus></FaPlus>
</div>
</div>
</>
<div
key={idx}
data-active={activeSource === sources[idx]}
className={`
absolute translate-y-[-50%] cursor-pointer rounded-full
bg-blue-600 p-1 text-xs text-white
data-[active='true']:bg-white
data-[active='true']:text-blue-600
hover:bg-blue-800
`}
style={{
top: `${(divRect.bottom + divRect.top) / 2}px`,
left: `${divRect.right - 10}px`,
}}
onClick={() => {
activeSource !== sources[idx] ? setActiveSource(sources[idx]) : setActiveSource(null);
}}
>
<FaPlus></FaPlus>
</div>
);
}
})}
@@ -318,28 +314,25 @@ export function AudioMenu(props: { open: boolean; onClose: () => void; children?
right: (div as HTMLDivElement).offsetLeft + (div as HTMLDivElement).clientWidth,
};
return (
<>
<div>
<div
className={`
absolute translate-y-[-50%] cursor-pointer
rounded-full bg-blue-600 p-1 text-xs text-white
hover:bg-blue-800
`}
style={{
top: `${(divRect.bottom + divRect.top) / 2}px`,
left: `${divRect.right - 10}px`,
}}
onClick={() => {
if (activeSource.getConnectedTo().includes(sinks[idx])) activeSource.disconnect(sinks[idx]);
else activeSource.connect(sinks[idx]);
}}
>
{" "}
{activeSource.getConnectedTo().includes(sinks[idx]) ? <FaMinus></FaMinus> : <FaPlus></FaPlus>}
</div>
</div>
</>
<div
key={idx}
className={`
absolute translate-y-[-50%] cursor-pointer rounded-full
bg-blue-600 p-1 text-xs text-white
hover:bg-blue-800
`}
style={{
top: `${(divRect.bottom + divRect.top) / 2}px`,
left: `${divRect.right - 10}px`,
}}
onClick={() => {
if (activeSource.getConnectedTo().includes(sinks[idx])) activeSource.disconnect(sinks[idx]);
else activeSource.connect(sinks[idx]);
}}
>
{" "}
{activeSource.getConnectedTo().includes(sinks[idx]) ? <FaMinus></FaMinus> : <FaPlus></FaPlus>}
</div>
);
}
})}

View File

@@ -46,21 +46,14 @@ export function ControlsPanel(props: {}) {
text: "Select unit",
},
{
actions: [touch ? faHandPointer : "LMB", "Drag"],
actions: ["Shift", "LMB", "Drag"],
text: "Box selection",
},
{
actions: [touch ? faHandPointer : "Wheel", "Drag"],
actions: [touch ? faHandPointer : "LMB", "Drag"],
text: "Move map",
},
];
if (!touch) {
controls.push({
actions: ["Shift", "LMB", "Drag"],
text: "Box selection",
});
}
if (appState === OlympusState.IDLE) {
controls = baseControls;

View File

@@ -38,20 +38,12 @@ export function GameMasterMenu(props: { open: boolean; onClose: () => void; chil
GAME MASTER
</div>
)}
{commandModeOptions.commandMode === BLUE_COMMANDER && (
<div
className={`w-full rounded-md bg-blue-600 p-2 text-center font-bold`}
>
BLUE COMMANDER
</div>
)}
{commandModeOptions.commandMode === RED_COMMANDER && (
<div
className={`w-full rounded-md bg-red-700 p-2 text-center font-bold`}
>
RED COMMANDER
</div>
)}
{commandModeOptions.commandMode === BLUE_COMMANDER && <div className={`
w-full rounded-md bg-blue-600 p-2 text-center font-bold
`}>BLUE COMMANDER</div>}
{commandModeOptions.commandMode === RED_COMMANDER && <div className={`
w-full rounded-md bg-red-700 p-2 text-center font-bold
`}>RED COMMANDER</div>}
{serverStatus.elapsedTime > currentSetupTime && (
<div
className={`
@@ -125,6 +117,7 @@ export function GameMasterMenu(props: { open: boolean; onClose: () => void; chil
.map((era) => {
return (
<div
key={era}
className={`
group flex flex-row rounded-md justify-content
cursor-pointer gap-4 p-2
@@ -274,9 +267,12 @@ export function GameMasterMenu(props: { open: boolean; onClose: () => void; chil
group flex flex-row rounded-md justify-content gap-4 px-4 py-2
`}
>
<span className="mr-auto">Elapsed time (seconds)</span> <span className={`
w-32 text-center
`}>{serverStatus.elapsedTime?.toFixed()}</span>
<span className="mr-auto">Elapsed time (seconds)</span>{" "}
<span
className={`w-32 text-center`}
>
{serverStatus.elapsedTime?.toFixed()}
</span>
</div>
{commandModeOptions.commandMode === GAME_MASTER && (
<button

View File

@@ -211,7 +211,7 @@ export function Header() {
</div>
<OlLabelToggle
toggled={mapOptions.cameraPluginMode === "live"}
toggled={mapOptions.cameraPluginMode === "map"}
leftLabel={"Live"}
rightLabel={"Map"}
onClick={() => {

View File

@@ -15,12 +15,11 @@ export function InfoBar(props: {}) {
}, []);
return (
<div
className={`absolute left-[50%] top-16`}
>
<div className={`absolute left-[50%] top-16`}>
{messages.slice(Math.max(0, messages.length - 4), Math.max(0, messages.length)).map((message, idx) => {
return (
<div
key={idx}
className={`
absolute w-fit translate-x-[-50%] gap-2 text-nowrap rounded-full
bg-olympus-800/90 px-4 py-2 text-center text-sm text-white

View File

@@ -38,7 +38,7 @@ export function OptionsMenu(props: { open: boolean; onClose: () => void; childre
`}
>
<OlAccordion
onClick={() => setOpenAccordion(openAccordion === Accordion.NONE ? Accordion.BINDINGS : Accordion.NONE)}
onClick={() => setOpenAccordion(openAccordion !== Accordion.BINDINGS ? Accordion.BINDINGS : Accordion.NONE)}
open={openAccordion === Accordion.BINDINGS}
title="Key bindings"
>
@@ -53,9 +53,10 @@ export function OptionsMenu(props: { open: boolean; onClose: () => void; childre
.map(([id, shortcut]) => {
return (
<div
key={id}
className={`
group relative mr-2 flex cursor-pointer select-none
items-center justify-between rounded-sm px-2 py-2 text-sm
items-center justify-between rounded-sm px-2 py-2
dark:text-gray-300 dark:hover:bg-olympus-500
`}
onClick={() => {
@@ -65,39 +66,9 @@ export function OptionsMenu(props: { open: boolean; onClose: () => void; childre
>
<span>{shortcut.getOptions().label}</span>
<span className="flex gap-1">
{shortcut.getOptions().altKey ? (
<div className="flex gap-1">
<div className={`text-green-500`}>Alt</div> +{" "}
</div>
) : shortcut.getOptions().altKey === false ? (
<div className={`flex gap-1`}>
<div className={`text-red-500`}>Alt</div> +{" "}
</div>
) : (
""
)}
{shortcut.getOptions().ctrlKey ? (
<div className="flex gap-1">
<div className={`text-green-500`}>Shift</div> +{" "}
</div>
) : shortcut.getOptions().ctrlKey === false ? (
<div className={`flex gap-1`}>
<div className={`text-red-500`}>Shift</div> +{" "}
</div>
) : (
""
)}
{shortcut.getOptions().shiftKey ? (
<div className="flex gap-1">
<div className={`text-green-500`}>Ctrl</div> +{" "}
</div>
) : shortcut.getOptions().shiftKey === false ? (
<div className={`flex gap-1`}>
<div className={`text-red-500`}>Ctrl</div> +{" "}
</div>
) : (
""
)}
{shortcut.getOptions().ctrlKey && "Ctrl + "}
{shortcut.getOptions().altKey && "Alt + "}
{shortcut.getOptions().shiftKey && "Shift + "}
{shortcut.getOptions().code}
</span>
</div>
@@ -107,7 +78,7 @@ export function OptionsMenu(props: { open: boolean; onClose: () => void; childre
</OlAccordion>
<OlAccordion
onClick={() => setOpenAccordion(openAccordion === Accordion.NONE ? Accordion.MAP_OPTIONS : Accordion.NONE)}
onClick={() => setOpenAccordion(openAccordion !== Accordion.MAP_OPTIONS ? Accordion.MAP_OPTIONS : Accordion.NONE)}
open={openAccordion === Accordion.MAP_OPTIONS}
title="Map options"
>
@@ -158,7 +129,7 @@ export function OptionsMenu(props: { open: boolean; onClose: () => void; childre
<div
className={`
group flex flex-row gap-4 rounded-md justify-content
cursor-pointer p-2 text-sm
cursor-pointer p-2
dark:hover:bg-olympus-400
`}
onClick={() => getApp().getMap().setOption("hideUnitsShortRangeRings", !mapOptions.hideUnitsShortRangeRings)}
@@ -166,11 +137,11 @@ export function OptionsMenu(props: { open: boolean; onClose: () => void; childre
<OlCheckbox checked={mapOptions.hideUnitsShortRangeRings} onChange={() => {}}></OlCheckbox>
<span>Hide Short range Rings</span>
</div>
<div
className={`
group flex flex-row gap-4 rounded-md justify-content
cursor-pointer p-2 text-sm
cursor-pointer p-2
dark:hover:bg-olympus-400
`}
onClick={() => getApp().getMap().setOption("hideGroupMembers", !mapOptions.hideGroupMembers)}
@@ -181,7 +152,7 @@ export function OptionsMenu(props: { open: boolean; onClose: () => void; childre
<div
className={`
group flex flex-row gap-4 rounded-md justify-content
cursor-pointer p-2 text-sm
cursor-pointer p-2
dark:hover:bg-olympus-400
`}
onClick={() => getApp().getMap().setOption("showMinimap", !mapOptions.showMinimap)}
@@ -192,7 +163,7 @@ export function OptionsMenu(props: { open: boolean; onClose: () => void; childre
</OlAccordion>
<OlAccordion
onClick={() => setOpenAccordion(openAccordion === Accordion.NONE ? Accordion.CAMERA_PLUGIN : Accordion.NONE)}
onClick={() => setOpenAccordion(openAccordion !== Accordion.CAMERA_PLUGIN ? Accordion.CAMERA_PLUGIN : Accordion.NONE)}
open={openAccordion === Accordion.CAMERA_PLUGIN}
title="Camera plugin options"
>

View File

@@ -32,6 +32,7 @@ export function RadiosSummaryPanel(props: {}) {
.map((radioSink, idx) => {
return (
<OlStateButton
key={idx}
checked={radioSink.getPtt()}
onClick={() => {}}
onMouseDown={() => {
@@ -41,28 +42,23 @@ export function RadiosSummaryPanel(props: {}) {
radioSink.setPtt(false);
}}
tooltip="Click to talk, lights up when receiving"
buttonColor={
radioSink.getReceiving()
? "white"
: null
}
buttonColor={radioSink.getReceiving() ? "white" : null}
>
<span className={`font-bold text-gray-200`}>{idx + 1}</span>
</OlStateButton>
);
})}
{audioSinks.filter((audioSinks) => audioSinks instanceof UnitSink).length > 0 && (
<FaJetFighter
className={`text-xl`}
/>
)}
{audioSinks.filter((audioSinks) => audioSinks instanceof UnitSink).length > 0 && <FaJetFighter className={`
text-xl
`} />}
{audioSinks.filter((audioSinks) => audioSinks instanceof UnitSink).length > 0 &&
audioSinks
.filter((audioSinks) => audioSinks instanceof UnitSink)
.map((unitSink, idx) => {
return (
<OlStateButton
key={idx}
checked={unitSink.getPtt()}
onClick={() => {}}
onMouseDown={() => {

View File

@@ -17,6 +17,7 @@ import { faStar } from "@fortawesome/free-solid-svg-icons";
import { OlStringInput } from "../components/olstringinput";
import { countryCodes } from "../data/codes";
import { OlAccordion } from "../components/olaccordion";
import { AppStateChangedEvent } from "../../events";
export function UnitSpawnMenu(props: {
starredSpawns: { [key: string]: SpawnRequestTable };
@@ -32,6 +33,7 @@ export function UnitSpawnMenu(props: {
const altitudeStep = altitudeIncrements[props.blueprint.category];
/* State initialization */
const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED);
const [spawnCoalition, setSpawnCoalition] = useState("blue" as Coalition);
const [spawnNumber, setSpawnNumber] = useState(1);
const [spawnRole, setSpawnRole] = useState("");
@@ -46,9 +48,14 @@ export function UnitSpawnMenu(props: {
const [key, setKey] = useState("");
const [spawnRequestTable, setSpawnRequestTable] = useState(null as null | SpawnRequestTable);
/* When the menu is opened show the unit preview on the map as a cursor */
useEffect(() => {
if (!props.airbase && spawnRequestTable) {
setAppState(getApp()?.getState())
AppStateChangedEvent.on((state, subState) => setAppState(state));
}, []);
/* When the menu is opened show the unit preview on the map as a cursor */
const setSpawnRequestTableCallback = useCallback(() => {
if (!props.airbase && spawnRequestTable && appState === OlympusState.SPAWN) {
/* Refresh the unique key identified */
const newKey = hash(JSON.stringify(spawnRequestTable));
setKey(newKey);
@@ -56,7 +63,9 @@ export function UnitSpawnMenu(props: {
getApp()?.getMap()?.setSpawnRequestTable(spawnRequestTable);
getApp().setState(OlympusState.SPAWN, SpawnSubState.SPAWN_UNIT);
}
}, [spawnRequestTable]);
}, [spawnRequestTable, appState]);
useEffect(setSpawnRequestTableCallback, [spawnRequestTable]);
/* Callback and effect to update the quick access name of the starredSpawn */
const updateStarredSpawnQuickAccessNameS = useCallback(() => {
@@ -231,6 +240,7 @@ export function UnitSpawnMenu(props: {
{roles.map((role) => {
return (
<OlDropdownItem
key={role}
onClick={() => {
setSpawnRole(role);
setSpawnLoadout("");
@@ -256,6 +266,7 @@ export function UnitSpawnMenu(props: {
{loadouts.map((loadout) => {
return (
<OlDropdownItem
key={loadout.name}
onClick={() => {
setSpawnLoadout(loadout.name);
}}
@@ -285,10 +296,9 @@ export function UnitSpawnMenu(props: {
>
Livery
</span>
<OlDropdown
label={props.blueprint.liveries ? (props.blueprint.liveries[spawnLiveryID]?.name ?? "Default") : "No livery"}
className={`w-64`}
>
<OlDropdown label={props.blueprint.liveries ? (props.blueprint.liveries[spawnLiveryID]?.name ?? "Default") : "No livery"} className={`
w-64
`}>
{props.blueprint.liveries &&
Object.keys(props.blueprint.liveries)
.sort((ida, idb) => {
@@ -303,6 +313,7 @@ export function UnitSpawnMenu(props: {
});
return (
<OlDropdownItem
key={id}
onClick={() => {
setSpawnLiveryID(id);
}}
@@ -315,9 +326,10 @@ export function UnitSpawnMenu(props: {
`}
>
{props.blueprint.liveries && props.blueprint.liveries[id].countries.length == 1 && (
<img src={`images/countries/${country?.flagCode.toLowerCase()}.svg`} className={`
h-6
`} />
<img
src={`images/countries/${country?.flagCode.toLowerCase()}.svg`}
className={`h-6`}
/>
)}
<div className="my-auto truncate">
@@ -348,6 +360,7 @@ export function UnitSpawnMenu(props: {
{["Average", "Good", "High", "Excellent"].map((skill) => {
return (
<OlDropdownItem
key={skill}
onClick={() => {
setSpawnSkill(skill);
}}
@@ -383,7 +396,7 @@ export function UnitSpawnMenu(props: {
>
{spawnLoadout.items.map((item) => {
return (
<div className="flex content-center gap-2">
<div className="flex content-center gap-2" key={item.name}>
<div
className={`
my-auto w-6 min-w-6 rounded-full py-0.5 text-center
@@ -421,7 +434,13 @@ export function UnitSpawnMenu(props: {
if (spawnRequestTable)
getApp()
.getUnitsManager()
.spawnUnits(spawnRequestTable.category, Array(spawnRequestTable.amount).fill(spawnRequestTable.unit), spawnRequestTable.coalition, false, props.airbase?.getName());
.spawnUnits(
spawnRequestTable.category,
Array(spawnRequestTable.amount).fill(spawnRequestTable.unit),
spawnRequestTable.coalition,
false,
props.airbase?.getName()
);
}}
>
Spawn

View File

@@ -10,7 +10,7 @@ import { OptionsMenu } from "./panels/optionsmenu";
import { MapHiddenTypes, MapOptions } from "../types/types";
import { NO_SUBSTATE, OlympusState, OlympusSubState, OptionsSubstate, SpawnSubState, UnitControlSubState } from "../constants/constants";
import { getApp, setupApp } from "../olympusapp";
import { LoginModal } from "./modals/login";
import { LoginModal } from "./modals/loginmodal";
import { MiniMapPanel } from "./panels/minimappanel";
import { UnitControlBar } from "./panels/unitcontrolbar";
@@ -20,7 +20,7 @@ import { MapContextMenu } from "./contextmenus/mapcontextmenu";
import { AirbaseMenu } from "./panels/airbasemenu";
import { AudioMenu } from "./panels/audiomenu";
import { FormationMenu } from "./panels/formationmenu";
import { ProtectionPrompt } from "./modals/protectionprompt";
import { ProtectionPromptModal } from "./modals/protectionpromptmodal";
import { KeybindModal } from "./modals/keybindmodal";
import { UnitExplosionMenu } from "./panels/unitexplosionmenu";
import { JTACMenu } from "./panels/jtacmenu";
@@ -65,16 +65,8 @@ export function UI() {
>
<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 />
</>
)}
<ProtectionPrompt open={appState === OlympusState.UNIT_CONTROL && appSubState == UnitControlSubState.PROTECTION} />
<LoginModal open={appState === OlympusState.LOGIN} />
<ProtectionPromptModal open={appState === OlympusState.UNIT_CONTROL && appSubState == UnitControlSubState.PROTECTION} />
<KeybindModal open={appState === OlympusState.OPTIONS && appSubState === OptionsSubstate.KEYBIND} />
<div id="map-container" className="z-0 h-full w-screen" />