Added keybinding menu and server side profiles

This commit is contained in:
Davide Passoni
2024-11-11 17:07:12 +01:00
parent 62af0f74e7
commit 68980651dc
18 changed files with 959 additions and 805 deletions

View File

@@ -0,0 +1,151 @@
import React, { useEffect, useState } from "react";
import { Modal } from "./components/modal";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faArrowRight } from "@fortawesome/free-solid-svg-icons";
import { getApp } from "../../olympusapp";
import { OlympusState } from "../../constants/constants";
import { Shortcut } from "../../shortcut/shortcut";
import { BindShortcutRequestEvent, ShortcutsChangedEvent } from "../../events";
export function KeybindModal(props: { open: boolean }) {
const [shortcuts, setShortcuts] = useState({} as { [key: string]: Shortcut });
const [shortcut, setShortcut] = useState(null as null | Shortcut);
const [code, setCode] = useState(null as null | string);
const [shiftKey, setShiftKey] = useState(false);
const [ctrlKey, setCtrlKey] = useState(false);
const [altKey, setAltKey] = useState(false);
useEffect(() => {
ShortcutsChangedEvent.on((shortcuts) => setShortcuts({ ...shortcuts }));
BindShortcutRequestEvent.on((shortcut) => setShortcut(shortcut));
document.addEventListener("keydown", (ev) => {
setCode(ev.code);
if (!(ev.code.indexOf("Shift") >= 0 || ev.code.indexOf("Alt") >= 0 || ev.code.indexOf("Control") >= 0)) {
setShiftKey(ev.shiftKey);
setAltKey(ev.altKey);
setCtrlKey(ev.ctrlKey);
}
});
}, []);
let available: null | boolean = code ? true : null;
let inUseShortcut: null | Shortcut = null;
for (let id in shortcuts) {
if (
code === shortcuts[id].getOptions().code &&
shiftKey == shortcuts[id].getOptions().shiftKey &&
altKey == shortcuts[id].getOptions().altKey &&
ctrlKey == shortcuts[id].getOptions().shiftKey
) {
available = false;
inUseShortcut = shortcuts[id];
}
}
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-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">
{ctrlKey ? "Ctrl + " : ""}
{shiftKey ? "Shift + " : ""}
{altKey ? "Alt + " : ""}
{code}
</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>
</div>
</div>
</Modal>
<div className={`fixed left-0 top-0 z-30 h-full w-full bg-[#111111]/95`}></div>
</>
)}
</>
);
}

View File

@@ -4,19 +4,54 @@ import { Card } from "./components/card";
import { ErrorCallout } from "../../ui/components/olcallout";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faArrowRight, faCheckCircle, faExternalLink } from "@fortawesome/free-solid-svg-icons";
import { VERSION } from "../../olympusapp";
import { getApp, VERSION } from "../../olympusapp";
import { sha256 } from "js-sha256";
import { BLUE_COMMANDER, GAME_MASTER, OlympusState, RED_COMMANDER } from "../../constants/constants";
export function LoginModal(props: {
checkingPassword: boolean;
loginError: boolean;
commandMode: string | null;
onLogin: (password: string) => void;
onContinue: (username: string) => void;
onBack: () => void;
}) {
export function LoginModal(props: {}) {
// TODO: add warning if not in secure context and some features are disabled
const [password, setPassword] = useState("");
const [displayName, setDisplayName] = useState("Game Master");
const [profileName, setProfileName] = useState("Game Master");
const [checkingPassword, setCheckingPassword] = useState(false);
const [loginError, setLoginError] = useState(false);
const [commandMode, setCommandMode] = useState(null as null | string);
function checkPassword(password: string) {
setCheckingPassword(true);
var hash = sha256.create();
getApp().getServerManager().setPassword(hash.update(password).hex());
getApp()
.getServerManager()
.getMission(
(response) => {
const commandMode = response.mission.commandModeOptions.commandMode;
try {
[GAME_MASTER, BLUE_COMMANDER, RED_COMMANDER].includes(commandMode) ? setCommandMode(commandMode) : setLoginError(true);
} catch {
setLoginError(true);
}
setCheckingPassword(false);
},
() => {
setLoginError(true);
setCheckingPassword(false);
}
);
}
function connect() {
getApp().getServerManager().setUsername(profileName);
getApp().getServerManager().startUpdate();
getApp().setState(OlympusState.IDLE);
/* Set the profile name */
getApp().setProfile(profileName);
/* If no profile exists already with that name, create it from scratch from the defaults */
if (getApp().getProfile() === null)
getApp().saveProfile();
/* Load the profile */
getApp().loadProfile();
}
return (
<Modal
@@ -62,7 +97,7 @@ export function LoginModal(props: {
max-lg:w-[100%]
`}
>
{!props.checkingPassword ? (
{!checkingPassword ? (
<>
<div className="flex flex-col items-start">
<div
@@ -115,9 +150,9 @@ export function LoginModal(props: {
</div>
</div>
</div>
{!props.loginError ? (
{!loginError ? (
<>
{props.commandMode === null ? (
{commandMode === null ? (
<>
<div className={`flex flex-col items-start gap-2`}>
<label
@@ -148,7 +183,7 @@ export function LoginModal(props: {
<div className="flex">
<button
type="button"
onClick={() => props.onLogin(password)}
onClick={() => checkPassword(password)}
className={`
mb-2 me-2 flex content-center items-center gap-2
rounded-sm bg-blue-700 px-5 py-2.5 text-sm
@@ -172,24 +207,19 @@ export function LoginModal(props: {
</>
) : (
<>
<div
className={`
flex flex-col items-start
gap-2
`}
>
<div className={`flex flex-col items-start gap-2`}>
<label
className={`
text-gray-800 text-md
dark:text-white
`}
>
Set display name
Set profile name
</label>
<input
type="text"
autoComplete="username"
onChange={(ev) => setDisplayName(ev.currentTarget.value)}
onChange={(ev) => setProfileName(ev.currentTarget.value)}
className={`
block w-full max-w-80 rounded-lg border
border-gray-300 bg-gray-50 p-2.5 text-sm
@@ -201,14 +231,17 @@ export function LoginModal(props: {
focus:border-blue-500 focus:ring-blue-500
`}
placeholder="Enter display name"
value={displayName}
value={profileName}
required
/>
</div>
<div className="text-xs text-gray-400">
The profile name you choose determines what keybinds/groups/options get loaded and edited. Be careful!
</div>
<div className="flex">
<button
type="button"
onClick={() => props.onContinue(displayName)}
onClick={() => connect()}
className={`
mb-2 me-2 flex content-center items-center gap-2
rounded-sm bg-blue-700 px-5 py-2.5 text-sm
@@ -249,11 +282,7 @@ export function LoginModal(props: {
title="Server could not be reached"
description="The Olympus Server at this address could not be reached. Check the address is correct, restart the Olympus server or reinstall Olympus. Ensure the ports set are not already used."
></ErrorCallout>
<div
className={`
text-sm font-medium text-gray-200
`}
>
<div className={`text-sm font-medium text-gray-200`}>
Still having issues? See our
<a
href=""

View File

@@ -2,100 +2,110 @@ import React from "react";
import { Modal } from "./components/modal";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faArrowRight } from "@fortawesome/free-solid-svg-icons";
import { Unit } from "../../unit/unit";
import { FaLock } from "react-icons/fa6";
import { getApp } from "../../olympusapp";
import { OlympusState } from "../../constants/constants";
export function ProtectionPrompt(props: {}) {
export function ProtectionPrompt(props: { open: boolean }) {
return (
<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
<>
{props.open && (
<>
<Modal
className={`
text-gray-800 text-md
dark:text-white
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
`}
>
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="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

@@ -35,7 +35,7 @@ export function Header() {
MapOptionsChangedEvent.on((mapOptions) => setMapOptions({ ...mapOptions }));
MapSourceChangedEvent.on((source) => setMapSource(source));
ConfigLoadedEvent.on((config: OlympusConfig) => {
var sources = Object.keys(config.mapMirrors).concat(Object.keys(config.mapLayers));
var sources = Object.keys(config.frontend.mapMirrors).concat(Object.keys(config.frontend.mapLayers));
setMapSources(sources);
});
CommandModeOptionsChangedEvent.on((commandModeOptions) => {
@@ -112,9 +112,7 @@ export function Header() {
</div>
{commandModeOptions.commandMode === BLUE_COMMANDER && (
<div
className={`
flex h-full rounded-md bg-blue-600 px-4 text-white
`}
className={`flex h-full rounded-md bg-blue-600 px-4 text-white`}
>
<span className="my-auto font-bold">BLUE Commander ({commandModeOptions.spawnPoints.blue} points)</span>
</div>

View File

@@ -4,14 +4,28 @@ import { OlCheckbox } from "../components/olcheckbox";
import { OlRangeSlider } from "../components/olrangeslider";
import { OlNumberInput } from "../components/olnumberinput";
import { getApp } from "../../olympusapp";
import { MAP_OPTIONS_DEFAULTS } from "../../constants/constants";
import { MapOptionsChangedEvent } from "../../events";
import { MAP_OPTIONS_DEFAULTS, OlympusState, OptionsSubstate } from "../../constants/constants";
import { BindShortcutRequestEvent, MapOptionsChangedEvent, ShortcutsChangedEvent } from "../../events";
import { OlAccordion } from "../components/olaccordion";
import { Shortcut } from "../../shortcut/shortcut";
import { OlSearchBar } from "../components/olsearchbar";
const enum Accordion {
NONE,
BINDINGS,
MAP_OPTIONS,
CAMERA_PLUGIN,
}
export function OptionsMenu(props: { open: boolean; onClose: () => void; children?: JSX.Element | JSX.Element[] }) {
const [mapOptions, setMapOptions] = useState(MAP_OPTIONS_DEFAULTS);
const [shortcuts, setShortcuts] = useState({} as { [key: string]: Shortcut });
const [openAccordion, setOpenAccordion] = useState(Accordion.NONE);
const [filterString, setFilterString] = useState("");
useEffect(() => {
MapOptionsChangedEvent.on((mapOptions) => setMapOptions({ ...mapOptions }));
ShortcutsChangedEvent.on((shortcuts) => setShortcuts({ ...shortcuts }));
}, []);
return (
@@ -22,226 +36,201 @@ export function OptionsMenu(props: { open: boolean; onClose: () => void; childre
dark:text-white
`}
>
<div
className={`
group flex flex-row rounded-md justify-content cursor-pointer gap-4
p-2
dark:hover:bg-olympus-400
`}
onClick={() => {
getApp().getMap().setOption("showUnitLabels", !mapOptions.showUnitLabels);
}}
<OlAccordion
onClick={() =>
setOpenAccordion(openAccordion === Accordion.NONE ? Accordion.BINDINGS: Accordion.NONE )
}
open={openAccordion === Accordion.BINDINGS}
title="Key bindings"
>
<OlCheckbox checked={mapOptions.showUnitLabels} onChange={() => {}}></OlCheckbox>
<span>Show Unit Labels</span>
<kbd
<OlSearchBar onChange={(value) => setFilterString(value)} text={filterString} />
<div
className={`
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
flex max-h-[450px] flex-col gap-1 overflow-y-scroll no-scrollbar
`}
>
L
</kbd>
</div>
<div
className={`
group flex flex-row rounded-md justify-content cursor-pointer gap-4
p-2
dark:hover:bg-olympus-400
`}
onClick={() => {
getApp().getMap().setOption("showUnitsEngagementRings", !mapOptions.showUnitsEngagementRings);
}}
>
<OlCheckbox checked={mapOptions.showUnitsEngagementRings} onChange={() => {}}></OlCheckbox>
<span>Show Threat Rings</span>
<kbd
className={`
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
`}
>
Q
</kbd>
</div>
<div
className={`
group flex flex-row rounded-md justify-content cursor-pointer gap-4
p-2
dark:hover:bg-olympus-400
`}
onClick={() => {
getApp().getMap().setOption("showUnitsAcquisitionRings", !mapOptions.showUnitsAcquisitionRings);
}}
>
<OlCheckbox checked={mapOptions.showUnitsAcquisitionRings} onChange={() => {}}></OlCheckbox>
<span>Show Detection rings</span>
<kbd
className={`
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
`}
>
E
</kbd>
</div>
<div
className={`
group flex flex-row rounded-md justify-content cursor-pointer gap-4
p-2
dark:hover:bg-olympus-400
`}
onClick={() => {
getApp().getMap().setOption("showUnitTargets", !mapOptions.showUnitTargets);
}}
>
<OlCheckbox checked={mapOptions.showUnitTargets} onChange={() => {}}></OlCheckbox>
<span>Show Detection lines</span>
<kbd
className={`
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
`}
>
F
</kbd>
</div>
<div
className={`
group flex flex-row rounded-md justify-content cursor-pointer gap-4
p-2
dark:hover:bg-olympus-400
`}
onClick={() => {
getApp().getMap().setOption("hideUnitsShortRangeRings", !mapOptions.hideUnitsShortRangeRings);
}}
>
<OlCheckbox checked={mapOptions.hideUnitsShortRangeRings} onChange={() => {}}></OlCheckbox>
<span>Hide Short range Rings</span>
<kbd
className={`
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
`}
>
R
</kbd>
</div>
<div
className={`
group flex flex-row rounded-md justify-content cursor-pointer gap-4
p-2
dark:hover:bg-olympus-400
`}
onClick={() => {
getApp().getMap().setOption("keepRelativePositions", !mapOptions.keepRelativePositions);
}}
>
<OlCheckbox checked={mapOptions.keepRelativePositions} onChange={() => {}}></OlCheckbox>
<span>Keep units relative positions</span>
<kbd
className={`
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
`}
>
P
</kbd>
</div>
<div
className={`
group flex flex-row rounded-md justify-content cursor-pointer gap-4
p-2
dark:hover:bg-olympus-400
`}
onClick={() => {
getApp().getMap().setOption("hideGroupMembers", !mapOptions.hideGroupMembers);
}}
>
<OlCheckbox checked={mapOptions.hideGroupMembers} onChange={() => {}}></OlCheckbox>
<span>Hide Group members</span>
<kbd
className={`
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
`}
>
G
</kbd>
</div>
<div
className={`
group flex flex-row rounded-md justify-content cursor-pointer gap-4
p-2
dark:hover:bg-olympus-400
`}
onClick={() => {
getApp().getMap().setOption("showMinimap", !mapOptions.showMinimap);
}}
>
<OlCheckbox checked={mapOptions.showMinimap} onChange={() => {}}></OlCheckbox>
<span>Show minimap</span>
<kbd
className={`
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
`}
>
?
</kbd>
</div>
{Object.entries(shortcuts)
.filter(([id, shortcut]) => shortcut.getOptions().label.toLowerCase().indexOf(filterString.toLowerCase()) >= 0)
.map(([id, shortcut]) => {
return (
<div
className={`
group relative mr-2 flex cursor-pointer select-none
items-center justify-between rounded-sm px-2 py-2 text-sm
dark:text-gray-300 dark:hover:bg-olympus-500
`}
onClick={() => {
getApp().setState(OlympusState.OPTIONS, OptionsSubstate.KEYBIND);
BindShortcutRequestEvent.dispatch(shortcut);
}}
>
<span>{shortcut.getOptions().label}</span>
<span>
{shortcut.getOptions().altKey ? "Alt + " : ""}
{shortcut.getOptions().ctrlKey ? "Ctrl + " : ""}
{shortcut.getOptions().shiftKey ? "Shift + " : ""}
{shortcut.getOptions().code}
</span>
</div>
);
})}
</div>
</OlAccordion>
<hr className={`
<OlAccordion onClick={() => setOpenAccordion(openAccordion === Accordion.NONE ? Accordion.MAP_OPTIONS: Accordion.NONE )} open={openAccordion === Accordion.MAP_OPTIONS} title="Map options">
<div
className={`
group flex flex-row rounded-md justify-content cursor-pointer
gap-4 p-2
dark:hover:bg-olympus-400
`}
onClick={() => getApp().getMap().setOption("showUnitLabels", !mapOptions.showUnitLabels)}
>
<OlCheckbox checked={mapOptions.showUnitLabels} onChange={() => {}}></OlCheckbox>
<span>Show Unit Labels</span>
</div>
<div
className={`
group flex flex-row rounded-md justify-content cursor-pointer
gap-4 p-2
dark:hover:bg-olympus-400
`}
onClick={() => getApp().getMap().setOption("showUnitsEngagementRings", !mapOptions.showUnitsEngagementRings)}
>
<OlCheckbox checked={mapOptions.showUnitsEngagementRings} onChange={() => {}}></OlCheckbox>
<span>Show Threat Rings</span>
</div>
<div
className={`
group flex flex-row rounded-md justify-content cursor-pointer
gap-4 p-2
dark:hover:bg-olympus-400
`}
onClick={() => getApp().getMap().setOption("showUnitsAcquisitionRings", !mapOptions.showUnitsAcquisitionRings)}
>
<OlCheckbox checked={mapOptions.showUnitsAcquisitionRings} onChange={() => {}}></OlCheckbox>
<span>Show Detection rings</span>
</div>
<div
className={`
group flex flex-row rounded-md justify-content cursor-pointer
gap-4 p-2
dark:hover:bg-olympus-400
`}
onClick={() => getApp().getMap().setOption("showUnitTargets", !mapOptions.showUnitTargets)}
>
<OlCheckbox checked={mapOptions.showUnitTargets} onChange={() => {}}></OlCheckbox>
<span>Show Detection lines</span>
</div>
<div
className={`
group flex flex-row gap-4 rounded-md justify-content
cursor-pointer p-2 text-sm
dark:hover:bg-olympus-400
`}
onClick={() => getApp().getMap().setOption("hideUnitsShortRangeRings", !mapOptions.hideUnitsShortRangeRings)}
>
<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
dark:hover:bg-olympus-400
`}
onClick={() => getApp().getMap().setOption("keepRelativePositions", !mapOptions.keepRelativePositions)}
>
<OlCheckbox checked={mapOptions.keepRelativePositions} onChange={() => {}}></OlCheckbox>
<span>Keep units relative positions</span>
</div>
<div
className={`
group flex flex-row gap-4 rounded-md justify-content
cursor-pointer p-2 text-sm
dark:hover:bg-olympus-400
`}
onClick={() => getApp().getMap().setOption("hideGroupMembers", !mapOptions.hideGroupMembers)}
>
<OlCheckbox checked={mapOptions.hideGroupMembers} onChange={() => {}}></OlCheckbox>
<span>Hide Group members</span>
</div>
<div
className={`
group flex flex-row gap-4 rounded-md justify-content
cursor-pointer p-2 text-sm
dark:hover:bg-olympus-400
`}
onClick={() => getApp().getMap().setOption("showMinimap", !mapOptions.showMinimap)}
>
<OlCheckbox checked={mapOptions.showMinimap} onChange={() => {}}></OlCheckbox>
<span>Show minimap</span>
</div>
</OlAccordion>
<OlAccordion onClick={() => setOpenAccordion(openAccordion === Accordion.NONE ? Accordion.CAMERA_PLUGIN: Accordion.NONE )} open={openAccordion === Accordion.CAMERA_PLUGIN} title="Camera plugin options">
<hr
className={`
m-2 my-1 w-auto border-[1px] bg-gray-700
dark:border-olympus-500
`}></hr>
<div className={`
`}
></hr>
<div
className={`
flex flex-col content-center items-start justify-between gap-2 p-2
`}>
<div className="flex flex-col">
<span className={`
font-normal
dark:text-white
`}>DCS Camera Zoom Scaling</span>
</div>
<OlRangeSlider
onChange={(ev) => {
getApp().getMap().setOption("cameraPluginRatio", parseInt(ev.target.value))
}}
value={mapOptions.cameraPluginRatio}
min={0}
max={100}
step={1}
/>
</div>
<div className={`
flex flex-col content-center items-start justify-between gap-2 p-2
`}>
<span className={`
`}
>
<div className="flex flex-col">
<span
className={`
font-normal
dark:text-white
`}>DCS Camera Port</span>
<div className="flex">
<OlNumberInput
value={mapOptions.cameraPluginPort}
min={0}
max={9999}
onDecrease={() => { getApp().getMap().setOption("cameraPluginPort", mapOptions.cameraPluginPort - 1) }}
onIncrease={() => { getApp().getMap().setOption("cameraPluginPort", mapOptions.cameraPluginPort + 1) }}
onChange={(ev) => { getApp().getMap().setOption("cameraPluginPort", ev.target.value)}}
/>
</div>
</div>
`}
>
DCS Camera Zoom Scaling
</span>
</div>
<OlRangeSlider
onChange={(ev) => getApp().getMap().setOption("cameraPluginRatio", parseInt(ev.target.value))}
value={mapOptions.cameraPluginRatio}
min={0}
max={100}
step={1}
/>
</div>
<div
className={`
flex flex-col content-center items-start justify-between gap-2 p-2
`}
>
<span
className={`
font-normal
dark:text-white
`}
>
DCS Camera Port
</span>
<div className="flex">
<OlNumberInput
value={mapOptions.cameraPluginPort}
min={0}
max={9999}
onDecrease={() =>
getApp()
.getMap()
.setOption("cameraPluginPort", mapOptions.cameraPluginPort - 1)
}
onIncrease={() =>
getApp()
.getMap()
.setOption("cameraPluginPort", mapOptions.cameraPluginPort + 1)
}
onChange={(ev) => getApp().getMap().setOption("cameraPluginPort", ev.target.value)}
/>
</div>
</div>
</OlAccordion>
</div>
</Menu>
);

View File

@@ -9,29 +9,25 @@ import { SideBar } from "./panels/sidebar";
import { OptionsMenu } from "./panels/optionsmenu";
import { MapHiddenTypes, MapOptions } from "../types/types";
import {
BLUE_COMMANDER,
GAME_MASTER,
MAP_OPTIONS_DEFAULTS,
NO_SUBSTATE,
OlympusState,
OlympusSubState,
RED_COMMANDER,
OptionsSubstate,
UnitControlSubState,
} from "../constants/constants";
import { getApp, setupApp } from "../olympusapp";
import { LoginModal } from "./modals/login";
import { sha256 } from "js-sha256";
import { MiniMapPanel } from "./panels/minimappanel";
import { UnitControlBar } from "./panels/unitcontrolbar";
import { DrawingMenu } from "./panels/drawingmenu";
import { ControlsPanel } from "./panels/controlspanel";
import { MapContextMenu } from "./contextmenus/mapcontextmenu";
import { AirbaseMenu } from "./panels/airbasemenu";
import { Airbase } from "../mission/airbase";
import { AudioMenu } from "./panels/audiomenu";
import { FormationMenu } from "./panels/formationmenu";
import { Unit } from "../unit/unit";
import { ProtectionPrompt } from "./modals/protectionprompt";
import { KeybindModal } from "./modals/keybindmodal";
import { UnitExplosionMenu } from "./panels/unitexplosionmenu";
import { JTACMenu } from "./panels/jtacmenu";
import { AppStateChangedEvent, MapOptionsChangedEvent } from "../events";
@@ -54,10 +50,6 @@ export type OlympusUIState = {
export function UI() {
const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED);
const [appSubState, setAppSubState] = useState(NO_SUBSTATE as OlympusSubState);
const [checkingPassword, setCheckingPassword] = useState(false);
const [loginError, setLoginError] = useState(false);
const [commandMode, setCommandMode] = useState(null as null | string);
useEffect(() => {
AppStateChangedEvent.on((state, subState) => {
@@ -66,34 +58,7 @@ export function UI() {
});
}, []);
function checkPassword(password: string) {
setCheckingPassword(true);
var hash = sha256.create();
getApp().getServerManager().setPassword(hash.update(password).hex());
getApp()
.getServerManager()
.getMission(
(response) => {
const commandMode = response.mission.commandModeOptions.commandMode;
try {
[GAME_MASTER, BLUE_COMMANDER, RED_COMMANDER].includes(commandMode) ? setCommandMode(commandMode) : setLoginError(true);
} catch {
setLoginError(true);
}
setCheckingPassword(false);
},
() => {
setLoginError(true);
setCheckingPassword(false);
}
);
}
function connect(username: string) {
getApp().getServerManager().setUsername(username);
getApp().getServerManager().startUpdate();
getApp().setState(OlympusState.IDLE);
}
return (
<div
@@ -111,36 +76,25 @@ export function UI() {
fixed left-0 top-0 z-30 h-full w-full bg-[#111111]/95
`}></div>
<LoginModal
onLogin={(password) => {
checkPassword(password);
}}
onContinue={(username) => {
connect(username);
}}
onBack={() => {
setCommandMode(null);
}}
checkingPassword={checkingPassword}
loginError={loginError}
commandMode={commandMode}
/>
</>
)}
{appState === OlympusState.UNIT_CONTROL && appSubState == UnitControlSubState.PROTECTION && (
<>
<div className={`
fixed left-0 top-0 z-30 h-full w-full bg-[#111111]/95
`}></div>
<ProtectionPrompt />
</>
)}
<ProtectionPrompt 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" />
<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)}/>
<OptionsMenu open={appState === OlympusState.OPTIONS} onClose={() => getApp().setState(OlympusState.IDLE)} />
<UnitControlMenu
open={appState === OlympusState.UNIT_CONTROL && ![UnitControlSubState.FORMATION, UnitControlSubState.UNIT_EXPLOSION_MENU].includes(appSubState as UnitControlSubState)}
open={
appState === OlympusState.UNIT_CONTROL &&
![UnitControlSubState.FORMATION, UnitControlSubState.UNIT_EXPLOSION_MENU].includes(appSubState as UnitControlSubState)
}
onClose={() => getApp().setState(OlympusState.IDLE)}
/>
<FormationMenu
@@ -149,11 +103,14 @@ export function UI() {
/>
<DrawingMenu open={appState === OlympusState.DRAW} onClose={() => getApp().setState(OlympusState.IDLE)} />
<AirbaseMenu open={appState === OlympusState.AIRBASE} onClose={() => getApp().setState(OlympusState.IDLE)}/>
<AirbaseMenu open={appState === OlympusState.AIRBASE} onClose={() => getApp().setState(OlympusState.IDLE)} />
<AudioMenu open={appState === OlympusState.AUDIO} onClose={() => getApp().setState(OlympusState.IDLE)} />
<GameMasterMenu open={appState === OlympusState.GAME_MASTER} onClose={() => getApp().setState(OlympusState.IDLE)} />
<UnitExplosionMenu open={appState === OlympusState.UNIT_CONTROL && appSubState === UnitControlSubState.UNIT_EXPLOSION_MENU} onClose={() => getApp().setState(OlympusState.IDLE)} />
<UnitExplosionMenu
open={appState === OlympusState.UNIT_CONTROL && appSubState === UnitControlSubState.UNIT_EXPLOSION_MENU}
onClose={() => getApp().setState(OlympusState.IDLE)}
/>
<JTACMenu open={appState === OlympusState.JTAC} onClose={() => getApp().setState(OlympusState.IDLE)} />
<MiniMapPanel />