Readded camera plugin, hotgroups, fixed smoke spawning

This commit is contained in:
Pax1601
2024-11-10 12:27:01 +01:00
parent 95e18f6503
commit 62af0f74e7
15 changed files with 368 additions and 234 deletions

View File

@@ -319,7 +319,7 @@ export const IADSDensities: { [key: string]: number } = {
"Radar (EWR)": 0.05,
};
export const MAP_OPTIONS_DEFAULTS = {
export const MAP_OPTIONS_DEFAULTS: MapOptions = {
hideGroupMembers: true,
hideUnitsShortRangeRings: true,
showUnitContacts: true,
@@ -332,7 +332,11 @@ export const MAP_OPTIONS_DEFAULTS = {
showMinimap: false,
protectDCSUnits: true,
keepRelativePositions: true,
} as MapOptions;
cameraPluginPort: 3003,
cameraPluginRatio: 1,
cameraPluginEnabled: false,
cameraPluginMode: 'map'
};
export const MAP_HIDDEN_TYPES_DEFAULTS = {
human: false,

View File

@@ -95,6 +95,19 @@ export class InfoPopupEvent {
}
}
export class HideMenuEvent {
static on(callback: (hidden: boolean) => void) {
document.addEventListener(this.name, (ev: CustomEventInit) => {
callback(ev.detail.hidden);
});
}
static dispatch(hidden: boolean) {
document.dispatchEvent(new CustomEvent(this.name, {detail: {hidden}}));
console.log(`Event ${this.name} dispatched`);
}
}
/************** Map events ***************/
export class HiddenTypesChangedEvent {
static on(callback: (hiddenTypes: MapHiddenTypes) => void) {
@@ -277,6 +290,19 @@ export class UnitContextMenuRequestEvent {
}
}
export class HotgroupsChangedEvent {
static on(callback: (hotgroups: {[key: number]: number}) => void) {
document.addEventListener(this.name, (ev: CustomEventInit) => {
callback(ev.detail.hotgroups);
});
}
static dispatch(hotgroups: {[key: number]: number}) {
document.dispatchEvent(new CustomEvent(this.name, {detail: {hotgroups}}));
console.log(`Event ${this.name} dispatched`);
}
}
/************** Command mode events ***************/
export class CommandModeOptionsChangedEvent {
static on(callback: (options: CommandModeOptions) => void) {

View File

@@ -218,10 +218,12 @@ export class Map extends L.Map {
if (unit.getSelected()) this.#moveDestinationPreviewMarkers();
});
MapOptionsChangedEvent.on((options) => {
MapOptionsChangedEvent.on((options: MapOptions) => {
this.getContainer().toggleAttribute("data-hide-labels", !options.showUnitLabels);
//this.#cameraControlPort = options[DCS_LINK_PORT] as number;
//this.#cameraZoomRatio = 50 / (20 + (options[DCS_LINK_RATIO] as number));
this.#cameraControlPort = options.cameraPluginPort;
this.#cameraZoomRatio = 50 / (20 + options.cameraPluginRatio);
this.#slaveDCSCamera = options.cameraPluginEnabled;
this.#cameraControlMode = options.cameraPluginMode;
if (this.#slaveDCSCamera) {
this.#broadcastPosition();
@@ -379,6 +381,13 @@ export class Map extends L.Map {
setEffectRequestTable(effectRequestTable: EffectRequestTable) {
this.#effectRequestTable = effectRequestTable;
if (getApp().getState() === OlympusState.SPAWN && getApp().getSubState() === SpawnSubState.SPAWN_EFFECT) {
this.#currentEffectMarker?.removeFrom(this);
this.#currentEffectMarker = null;
if (this.#effectRequestTable?.type === "smoke")
this.#currentEffectMarker = new SmokeMarker(new L.LatLng(0, 0), this.#effectRequestTable.smokeColor ?? "white");
this.#currentEffectMarker?.addTo(this);
}
}
setContextActionSet(contextActionSet: ContextActionSet | null) {
@@ -784,28 +793,6 @@ export class Map extends L.Map {
return this.#previousZoom;
}
setSlaveDCSCamera(newSlaveDCSCamera: boolean) {
this.#slaveDCSCamera = newSlaveDCSCamera;
let button = document.getElementById("camera-link-control");
button?.classList.toggle("off", !newSlaveDCSCamera);
if (this.#slaveDCSCamera) {
this.#broadcastPosition();
window.setTimeout(() => {
this.#broadcastPosition();
}, 500); // DCS does not always apply the altitude correctly at the first set when changing map type
}
}
setCameraControlMode(newCameraControlMode: string) {
this.#cameraControlMode = newCameraControlMode;
if (this.#slaveDCSCamera) {
this.#broadcastPosition();
window.setTimeout(() => {
this.#broadcastPosition();
}, 500); // DCS does not always apply the altitude correctly at the first set when changing map type
}
}
increaseCameraZoom() {
//const slider = document.querySelector(`label[title="${DCS_LINK_RATIO}"] input`);
//if (slider instanceof HTMLInputElement) {

View File

@@ -50,7 +50,7 @@ export class OlympusApp {
constructor() {
SelectedUnitsChangedEvent.on((selectedUnits) => {
if (selectedUnits.length > 0) this.setState(OlympusState.UNIT_CONTROL);
else this.getState() === OlympusState.UNIT_CONTROL && this.setState(OlympusState.IDLE)
else this.getState() === OlympusState.UNIT_CONTROL && this.setState(OlympusState.IDLE);
});
}
@@ -144,11 +144,13 @@ export class OlympusApp {
}
setState(state: OlympusState, subState: OlympusSubState = NO_SUBSTATE) {
this.#state = state;
this.#subState = subState;
if (state !== this.#state || subState !== this.#subState) {
this.#state = state;
this.#subState = subState;
console.log(`App state set to ${state}, substate ${subState}`);
AppStateChangedEvent.dispatch(state, subState);
console.log(`App state set to ${state}, substate ${subState}`);
AppStateChangedEvent.dispatch(state, subState);
}
}
getState() {
@@ -165,6 +167,6 @@ export class OlympusApp {
setTimeout(() => {
this.#infoMessages.shift();
InfoPopupEvent.dispatch(this.#infoMessages);
}, 5000)
}, 5000);
}
}

View File

@@ -22,6 +22,10 @@ export type MapOptions = {
showMinimap: boolean;
protectDCSUnits: boolean;
keepRelativePositions: boolean;
cameraPluginPort: number;
cameraPluginRatio: number;
cameraPluginEnabled: boolean;
cameraPluginMode: string;
};
export type MapHiddenTypes = {

View File

@@ -8,7 +8,7 @@ export function OlStateButton(props: {
className?: string;
buttonColor?: string | null;
checked: boolean;
icon: IconProp;
icon?: IconProp;
tooltip: string;
onClick: () => void;
children?: JSX.Element | JSX.Element[];
@@ -20,14 +20,16 @@ export function OlStateButton(props: {
(props.className ?? "") +
`
h-[40px] w-[40px] flex-none rounded-md text-lg font-medium
dark:bg-olympus-600 dark:text-gray-300 dark:hover:bg-olympus-300
dark:bg-olympus-600 dark:text-gray-300
`;
let textColor = "white";
if (props.checked && props.buttonColor == "white") {
textColor = "#243141"
if ((props.checked || hover) && props.buttonColor == "white") {
textColor = "#243141";
}
const opacity = (hover && !props.checked) ? "AA" : "FF";
return (
<>
<button
@@ -41,7 +43,7 @@ export function OlStateButton(props: {
className={className}
style={{
border: props.buttonColor ? "2px solid " + props.buttonColor : "0px solid transparent",
background: props.checked ? (props.buttonColor? props.buttonColor: "#3b82f6"): "#243141",
background: props.checked || hover ? (props.buttonColor ? props.buttonColor : "#3b82f6" + opacity) : "#243141" + opacity,
}}
onMouseEnter={() => {
setHover(true);
@@ -51,7 +53,7 @@ export function OlStateButton(props: {
}}
>
<div className="m-auto flex w-fit content-center justify-center gap-2">
<FontAwesomeIcon icon={props.icon} className="m-auto" style={{color: textColor}} />
{props.icon && <FontAwesomeIcon icon={props.icon} className="m-auto" style={{ color: textColor }} />}
{props.children}
</div>
</button>

View File

@@ -1,7 +1,8 @@
import { faArrowLeft, faClose } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import { FaChevronDown, FaChevronUp } from "react-icons/fa";
import { HideMenuEvent } from "../../../events";
export function Menu(props: {
title: string;
@@ -15,7 +16,11 @@ export function Menu(props: {
const [hide, setHide] = useState(true);
if (!props.open && hide) setHide(false);
useEffect(() => {
HideMenuEvent.dispatch(hide)
}, [hide])
return (
<div
data-open={props.open}

View File

@@ -62,7 +62,7 @@ export function EffectSpawnMenu(props: { effect: string }) {
setSmokeColor(optionSmokeColor);
}}
tooltip=""
borderColor={optionSmokeColor}
buttonColor={optionSmokeColor}
/>
);
})}

View File

@@ -61,177 +61,187 @@ export function Header() {
}
return (
<nav
<nav
className={`
z-10 flex w-full gap-4 border-gray-200 bg-gray-300 px-3 drop-shadow-md
align-center
dark:border-gray-800 dark:bg-olympus-900
`}
>
<img src="images/icon.png" className={`my-auto h-10 w-10 rounded-md p-0`}></img>
{!scrolledLeft && (
<FaChevronLeft
className={`
z-10 flex w-full gap-4 border-gray-200 bg-gray-300 px-3
drop-shadow-md align-center
dark:border-gray-800 dark:bg-olympus-900
absolute left-14 h-full w-6 rounded-lg px-2 py-3.5 text-gray-200
dark:bg-olympus-900
`}
/>
)}
<div
className={`
my-2 flex w-full items-center gap-3 overflow-x-scroll no-scrollbar
`}
onScroll={(ev) => onScroll(ev.target)}
ref={scrollRef}
>
<div
className={`
mr-auto hidden flex-none flex-row items-center justify-start gap-6
lg:flex
`}
>
<img
src="images/icon.png"
className={`my-auto h-10 w-10 rounded-md p-0`}
></img>
{!scrolledLeft && (
<FaChevronLeft
<div className="flex flex-col items-start">
<div
className={`
absolute left-14 h-full w-6 rounded-lg px-2 py-3.5 text-gray-200
dark:bg-olympus-900
pt-1 text-xs text-gray-800
dark:text-gray-400
`}
/>
)}
>
Connected to
</div>
<div
className={`
flex items-center justify-center gap-2 text-sm font-extrabold
text-gray-800
dark:text-gray-200
`}
>
{IP}
</div>
</div>
</div>
{commandModeOptions.commandMode === BLUE_COMMANDER && (
<div
className={`
my-2 flex w-full items-center gap-3 overflow-x-scroll no-scrollbar
`}
onScroll={(ev) => onScroll(ev.target)}
ref={scrollRef}
>
<div
className={`
mr-auto hidden flex-none flex-row items-center justify-start
gap-6
lg:flex
`}
>
<div className="flex flex-col items-start">
<div
className={`
pt-1 text-xs text-gray-800
dark:text-gray-400
`}
>
Connected to
</div>
<div
className={`
flex items-center justify-center gap-2 text-sm
font-extrabold text-gray-800
dark:text-gray-200
`}
>
{IP}
</div>
</div>
</div>
{commandModeOptions.commandMode === BLUE_COMMANDER && <div 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>}
<div
className={`flex h-fit flex-row items-center justify-start gap-1`}
>
<OlLockStateButton
checked={!mapOptions.protectDCSUnits}
onClick={() => {
getApp().getMap().setOption("protectDCSUnits", !mapOptions.protectDCSUnits);
}}
tooltip="Lock/unlock protected units (from scripted mission)"
/>
<OlRoundStateButton
checked={audioEnabled}
onClick={() => {
audioEnabled ? getApp().getAudioManager().stop() : getApp().getAudioManager().start();
setAudioEnabled(!audioEnabled);
}}
tooltip="Enable/disable audio and radio backend"
icon={faVolumeHigh}
/>
</div>
<div className={`h-8 w-0 border-l-[2px] border-gray-700`}></div>
<div
className={`flex h-fit flex-row items-center justify-start gap-1`}
>
{Object.entries({
human: olButtonsVisibilityHuman,
olympus: olButtonsVisibilityOlympus,
dcs: olButtonsVisibilityDcs,
}).map((entry) => {
return (
<OlRoundStateButton
key={entry[0]}
onClick={() => {
getApp().getMap().setHiddenType(entry[0], !mapHiddenTypes[entry[0]]);
}}
checked={!mapHiddenTypes[entry[0]]}
icon={entry[1]}
tooltip={"Hide/show " + entry[0] + " units"}
/>
);
})}
</div>
<div className={`h-8 w-0 border-l-[2px] border-gray-700`}></div>
<div
className={`flex h-fit flex-row items-center justify-start gap-1`}
>
<OlRoundStateButton
onClick={() => getApp().getMap().setHiddenType("blue", !mapHiddenTypes["blue"])}
checked={!mapHiddenTypes["blue"]}
icon={faFlag}
className={"!text-blue-500"}
tooltip={"Hide/show blue units"}
/>
<OlRoundStateButton
onClick={() => getApp().getMap().setHiddenType("red", !mapHiddenTypes["red"])}
checked={!mapHiddenTypes["red"]}
icon={faFlag}
className={"!text-red-500"}
tooltip={"Hide/show red units"}
/>
<OlRoundStateButton
onClick={() => getApp().getMap().setHiddenType("neutral", !mapHiddenTypes["neutral"])}
checked={!mapHiddenTypes["neutral"]}
icon={faFlag}
className={"!text-gray-500"}
tooltip={"Hide/show neutral units"}
/>
</div>
<div className={`h-8 w-0 border-l-[2px] border-gray-700`}></div>
<div
className={`flex h-fit flex-row items-center justify-start gap-1`}
>
{Object.entries({
aircraft: olButtonsVisibilityAircraft,
helicopter: olButtonsVisibilityHelicopter,
"groundunit-sam": olButtonsVisibilityGroundunitSam,
groundunit: olButtonsVisibilityGroundunit,
navyunit: olButtonsVisibilityNavyunit,
airbase: olButtonsVisibilityAirbase,
dead: faSkull,
}).map((entry) => {
return (
<OlRoundStateButton
key={entry[0]}
onClick={() => {
getApp().getMap().setHiddenType(entry[0], !mapHiddenTypes[entry[0]]);
}}
checked={!mapHiddenTypes[entry[0]]}
icon={entry[1]}
tooltip={"Hide/show " + entry[0] + " units"}
/>
);
})}
</div>
<OlLabelToggle toggled={false} leftLabel={"Live"} rightLabel={"Map"} onClick={() => {}}></OlLabelToggle>
<OlStateButton checked={false} icon={faCamera} onClick={() => {}} tooltip="Activate/deactivate camera plugin" />
<OlDropdown label={mapSource} className="w-60">
{mapSources.map((source) => {
return (
<OlDropdownItem key={source} onClick={() => getApp().getMap().setLayerName(source)}>
<div className="truncate">{source}</div>
</OlDropdownItem>
);
})}
</OlDropdown>
`}
>
<span className="my-auto font-bold">BLUE Commander ({commandModeOptions.spawnPoints.blue} points)</span>
</div>
{!scrolledRight && (
<FaChevronRight
className={`
absolute right-0 h-full w-6 rounded-lg px-2 py-3.5 text-gray-200
dark:bg-olympus-900
`}
/>
)}
</nav>
)}
<div className={`flex h-fit flex-row items-center justify-start gap-1`}>
<OlLockStateButton
checked={!mapOptions.protectDCSUnits}
onClick={() => {
getApp().getMap().setOption("protectDCSUnits", !mapOptions.protectDCSUnits);
}}
tooltip="Lock/unlock protected units (from scripted mission)"
/>
<OlRoundStateButton
checked={audioEnabled}
onClick={() => {
audioEnabled ? getApp().getAudioManager().stop() : getApp().getAudioManager().start();
setAudioEnabled(!audioEnabled);
}}
tooltip="Enable/disable audio and radio backend"
icon={faVolumeHigh}
/>
</div>
<div className={`h-8 w-0 border-l-[2px] border-gray-700`}></div>
<div className={`flex h-fit flex-row items-center justify-start gap-1`}>
{Object.entries({
human: olButtonsVisibilityHuman,
olympus: olButtonsVisibilityOlympus,
dcs: olButtonsVisibilityDcs,
}).map((entry) => {
return (
<OlRoundStateButton
key={entry[0]}
onClick={() => {
getApp().getMap().setHiddenType(entry[0], !mapHiddenTypes[entry[0]]);
}}
checked={!mapHiddenTypes[entry[0]]}
icon={entry[1]}
tooltip={"Hide/show " + entry[0] + " units"}
/>
);
})}
</div>
<div className={`h-8 w-0 border-l-[2px] border-gray-700`}></div>
<div className={`flex h-fit flex-row items-center justify-start gap-1`}>
<OlRoundStateButton
onClick={() => getApp().getMap().setHiddenType("blue", !mapHiddenTypes["blue"])}
checked={!mapHiddenTypes["blue"]}
icon={faFlag}
className={"!text-blue-500"}
tooltip={"Hide/show blue units"}
/>
<OlRoundStateButton
onClick={() => getApp().getMap().setHiddenType("red", !mapHiddenTypes["red"])}
checked={!mapHiddenTypes["red"]}
icon={faFlag}
className={"!text-red-500"}
tooltip={"Hide/show red units"}
/>
<OlRoundStateButton
onClick={() => getApp().getMap().setHiddenType("neutral", !mapHiddenTypes["neutral"])}
checked={!mapHiddenTypes["neutral"]}
icon={faFlag}
className={"!text-gray-500"}
tooltip={"Hide/show neutral units"}
/>
</div>
<div className={`h-8 w-0 border-l-[2px] border-gray-700`}></div>
<div className={`flex h-fit flex-row items-center justify-start gap-1`}>
{Object.entries({
aircraft: olButtonsVisibilityAircraft,
helicopter: olButtonsVisibilityHelicopter,
"groundunit-sam": olButtonsVisibilityGroundunitSam,
groundunit: olButtonsVisibilityGroundunit,
navyunit: olButtonsVisibilityNavyunit,
airbase: olButtonsVisibilityAirbase,
dead: faSkull,
}).map((entry) => {
return (
<OlRoundStateButton
key={entry[0]}
onClick={() => {
getApp().getMap().setHiddenType(entry[0], !mapHiddenTypes[entry[0]]);
}}
checked={!mapHiddenTypes[entry[0]]}
icon={entry[1]}
tooltip={"Hide/show " + entry[0] + " units"}
/>
);
})}
</div>
<OlLabelToggle
toggled={mapOptions.cameraPluginMode === "live"}
leftLabel={"Live"}
rightLabel={"Map"}
onClick={() => {
getApp()
.getMap()
.setOption("cameraPluginMode", mapOptions.cameraPluginMode === "live" ? "map" : "live");
}}
></OlLabelToggle>
<OlStateButton
checked={mapOptions.cameraPluginEnabled}
icon={faCamera}
onClick={() => {
getApp().getMap().setOption("cameraPluginEnabled", !mapOptions.cameraPluginEnabled);
}}
tooltip="Activate/deactivate camera plugin"
/>
<OlDropdown label={mapSource} className="w-60">
{mapSources.map((source) => {
return (
<OlDropdownItem key={source} onClick={() => getApp().getMap().setLayerName(source)}>
<div className="truncate">{source}</div>
</OlDropdownItem>
);
})}
</OlDropdown>
</div>
{!scrolledRight && (
<FaChevronRight
className={`
absolute right-0 h-full w-6 rounded-lg px-2 py-3.5 text-gray-200
dark:bg-olympus-900
`}
/>
)}
</nav>
);
}

View File

@@ -0,0 +1,53 @@
import React, { useEffect, useState } from "react";
import { AppStateChangedEvent, ContextActionChangedEvent, HideMenuEvent, HotgroupsChangedEvent, InfoPopupEvent } from "../../events";
import { OlympusState } from "../../constants/constants";
import { ContextAction } from "../../unit/contextaction";
import { OlStateButton } from "../components/olstatebutton";
import { faUserGroup } from "@fortawesome/free-solid-svg-icons";
import { getApp } from "../../olympusapp";
export function HotGroupBar(props: {}) {
const [hotgroups, setHotgroups] = useState({} as { [key: number]: number });
const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED);
const [menuHidden, setMenuHidden] = useState(false);
useEffect(() => {
AppStateChangedEvent.on((state, subState) => setAppState(state));
HideMenuEvent.on((hidden) => setMenuHidden(hidden));
HotgroupsChangedEvent.on((hotgroups) => setHotgroups({ ...hotgroups }));
}, []);
return (
<div
data-menuhidden={menuHidden || appState === OlympusState.IDLE}
className={`
absolute bottom-16 left-[50%] flex gap-2
data-[menuhidden='false']:translate-x-[calc(200px-50%+2rem)]
data-[menuhidden='true']:translate-x-[calc(-50%+2rem)]
`}
>
{Object.entries(hotgroups).map(([hotgroup, counter]) => {
return (
<div className="flex flex-col content-center gap-2">
<div
className={`
mx-auto aspect-square rotate-45 rounded-sm bg-olympus-900
text-center text-xs font-bold text-gray-200
`}
>
<div className="relative -rotate-45">{hotgroup}</div>
</div>
<OlStateButton checked={false} onClick={() => {getApp().getUnitsManager().selectUnitsByHotgroup(parseInt(hotgroup))}} tooltip="">
<span
className={`text-sm text-white`}
>
{counter}
</span>
</OlStateButton>
</div>
);
})}
</div>
);
}

View File

@@ -1,5 +1,5 @@
import React, { useEffect, useState } from "react";
import { AppStateChangedEvent, ContextActionChangedEvent, InfoPopupEvent } from "../../events";
import { AppStateChangedEvent, ContextActionChangedEvent, HideMenuEvent, InfoPopupEvent } from "../../events";
import { OlympusState } from "../../constants/constants";
import { ContextAction } from "../../unit/contextaction";
@@ -7,17 +7,19 @@ export function InfoBar(props: {}) {
const [messages, setMessages] = useState([] as string[]);
const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED);
const [contextAction, setContextAction] = useState(null as ContextAction | null);
const [menuHidden, setMenuHidden] = useState(false);
useEffect(() => {
InfoPopupEvent.on((messages) => setMessages([...messages]));
AppStateChangedEvent.on((state, subState) => setAppState(state));
ContextActionChangedEvent.on((contextAction) => setContextAction(contextAction));
HideMenuEvent.on((hidden) => setMenuHidden(hidden));
}, []);
let topString = "";
if (appState === OlympusState.UNIT_CONTROL) {
if (contextAction === null) {
topString = "top-32";
topString = "top-36";
} else {
topString = "top-48";
}
@@ -27,19 +29,21 @@ export function InfoBar(props: {}) {
return (
<div
data-menuhidden={menuHidden || appState === OlympusState.IDLE}
className={`
absolute left-[50%]
data-[menuhidden='false']:translate-x-[calc(200px-50%+2rem)]
data-[menuhidden='true']:translate-x-[calc(-50%+2rem)]
${topString}
flex w-[400px] max-w-[80%] translate-x-[calc(-50%+2rem)]
`}
>
{messages.map((message, idx) => {
{messages.slice(Math.max(0, messages.length - 4), Math.max(0, messages.length)).map((message, idx) => {
return (
<div
className={`
absolute left-0 w-full gap-2 rounded-md bg-olympus-800/90 px-4
py-2 text-center text-sm text-white backdrop-blur-lg
backdrop-grayscale
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
shadow-md backdrop-blur-lg backdrop-grayscale
`}
style={{ top: `${idx * 20}px` }}
>

View File

@@ -199,35 +199,49 @@ export function OptionsMenu(props: { open: boolean; onClose: () => void; childre
</kbd>
</div>
{/*
<hr className="w-auto m-2 my-1 bg-gray-700 border-[1px] dark:border-olympus-500"></hr>
<div className="flex flex-col content-center items-start justify-between p-2 gap-2">
<hr className={`
m-2 my-1 w-auto border-[1px] bg-gray-700
dark:border-olympus-500
`}></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>
<span className="dark:text-blue-500 font-bold"> x5
</span>
<span className={`
font-normal
dark:text-white
`}>DCS Camera Zoom Scaling</span>
</div>
<OlRangeSlider
onChange={() => { }}
value={5}
min={1}
max={10}
step={2}
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 p-2 gap-2">
<span className="font-normal dark:text-white">DCS Camera Port</span>
<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={3004}
value={mapOptions.cameraPluginPort}
min={0}
max={9999}
onDecrease={() => { }}
onIncrease={() => { }}
onChange={(ev) => { }}
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> */}
</div>
</div>
</Menu>
);

View File

@@ -7,7 +7,7 @@ import { CONTEXT_ACTION_COLORS } from "../../constants/constants";
import { FaInfoCircle } from "react-icons/fa";
import { FaChevronLeft, FaChevronRight } from "react-icons/fa6";
import { OlympusState } from "../../constants/constants";
import { AppStateChangedEvent, ContextActionChangedEvent, ContextActionSetChangedEvent } from "../../events";
import { AppStateChangedEvent, ContextActionChangedEvent, ContextActionSetChangedEvent, HideMenuEvent } from "../../events";
export function UnitControlBar(props: {}) {
const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED);
@@ -15,6 +15,7 @@ export function UnitControlBar(props: {}) {
const [contextAction, setContextAction] = useState(null as ContextAction | null);
const [scrolledLeft, setScrolledLeft] = useState(true);
const [scrolledRight, setScrolledRight] = useState(false);
const [menuHidden, setMenuHidden] = useState(false);
/* Initialize the "scroll" position of the element */
var scrollRef = useRef(null);
@@ -26,6 +27,7 @@ export function UnitControlBar(props: {}) {
AppStateChangedEvent.on((state, subState) => setAppState(state));
ContextActionSetChangedEvent.on((contextActionSet) => setcontextActionSet(contextActionSet));
ContextActionChangedEvent.on((contextAction) => setContextAction(contextAction));
HideMenuEvent.on((hidden) => setMenuHidden(hidden));
}, []);
function onScroll(el) {
@@ -48,10 +50,13 @@ export function UnitControlBar(props: {}) {
{appState === OlympusState.UNIT_CONTROL && contextActionSet && Object.keys(contextActionSet.getContextActions()).length > 0 && (
<>
<div
data-menuhidden={menuHidden}
className={`
absolute left-[50%] top-16 flex max-w-[80%]
translate-x-[calc(-50%+2rem)] gap-2 rounded-md bg-gray-200
absolute left-[50%] top-16 flex max-w-[80%] gap-2 rounded-md
bg-gray-200
dark:bg-olympus-900
data-[menuhidden='false']:translate-x-[calc(200px-50%+2rem)]
data-[menuhidden='true']:translate-x-[calc(-50%+2rem)]
`}
>
{!scrolledLeft && (

View File

@@ -37,6 +37,7 @@ import { JTACMenu } from "./panels/jtacmenu";
import { AppStateChangedEvent, MapOptionsChangedEvent } from "../events";
import { GameMasterMenu } from "./panels/gamemastermenu";
import { InfoBar } from "./panels/infobar";
import { HotGroupBar } from "./panels/hotgroupsbar";
export type OlympusUIState = {
mainMenuVisible: boolean;
@@ -161,6 +162,7 @@ export function UI() {
<MapContextMenu />
<SideBar />
<InfoBar />
<HotGroupBar />
</div>
</div>
);

View File

@@ -29,6 +29,7 @@ import { ContextActionSet } from "./contextactionset";
import {
CommandModeOptionsChangedEvent,
ContactsUpdatedEvent,
HotgroupsChangedEvent,
InfoPopupEvent,
SelectedUnitsChangedEvent,
SelectionClearedEvent,
@@ -1057,7 +1058,22 @@ export class UnitsManager {
if (units === null) units = this.getSelectedUnits();
units.forEach((unit: Unit) => unit.setHotgroup(hotgroup));
this.#showActionMessage(units, `added to hotgroup ${hotgroup}`);
//(getApp().getPanelsManager().get("hotgroup") as HotgroupPanel).refreshHotgroups();
let hotgroups: {[key: number]: number} = {};
for (let ID in this.#units) {
const unit = this.#units[ID]
if (unit.getAlive() && !unit.getHuman()) {
const hotgroup = unit.getHotgroup()
if (hotgroup) {
if (!(hotgroup in hotgroups)) {
hotgroups[hotgroup] = 1;
}
else
hotgroups[hotgroup] += 1;
}
}
}
HotgroupsChangedEvent.dispatch(hotgroups)
}
/** Delete the selected units