mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
feat: implemented map toolbar
This commit is contained in:
@@ -9,7 +9,8 @@ export function OlStateButton(props: {
|
||||
buttonColor?: string | null;
|
||||
checked: boolean;
|
||||
icon?: IconProp;
|
||||
tooltip: string;
|
||||
tooltip?: string | JSX.Element | JSX.Element[];
|
||||
tooltipPosition?: string;
|
||||
onClick: () => void;
|
||||
onMouseUp?: () => void;
|
||||
onMouseDown?: () => void;
|
||||
@@ -21,7 +22,8 @@ export function OlStateButton(props: {
|
||||
const className =
|
||||
(props.className ?? "") +
|
||||
`
|
||||
h-[40px] w-[40px] flex-none rounded-md text-lg font-medium
|
||||
pointer-events-auto h-[40px] w-[40px] flex-none rounded-md text-lg
|
||||
font-medium
|
||||
dark:bg-olympus-600 dark:text-gray-300
|
||||
`;
|
||||
|
||||
@@ -57,12 +59,12 @@ export function OlStateButton(props: {
|
||||
setHover(false);
|
||||
}}
|
||||
>
|
||||
<div className="m-auto flex w-fit content-center justify-center gap-2">
|
||||
<div className={`m-auto flex w-fit content-center justify-center gap-2`}>
|
||||
{props.icon && <FontAwesomeIcon icon={props.icon} className="m-auto" style={{ color: textColor }} />}
|
||||
{props.children}
|
||||
</div>
|
||||
</button>
|
||||
{hover && <OlTooltip buttonRef={buttonRef} content={props.tooltip} />}
|
||||
{hover && props.tooltip && <OlTooltip buttonRef={buttonRef} content={props.tooltip} position={props.tooltipPosition}/>}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
|
||||
export function OlTooltip(props: { content: string; buttonRef: React.MutableRefObject<null> }) {
|
||||
export function OlTooltip(props: { content: string | JSX.Element | JSX.Element[]; buttonRef: React.MutableRefObject<null>; position?: string }) {
|
||||
var contentRef = useRef(null);
|
||||
|
||||
function setPosition(content: HTMLDivElement, button: HTMLButtonElement) {
|
||||
@@ -13,18 +13,18 @@ export function OlTooltip(props: { content: string; buttonRef: React.MutableRefO
|
||||
let [cxl, cyt, cxr, cyb, cw, ch] = [
|
||||
content.getBoundingClientRect().x,
|
||||
content.getBoundingClientRect().y,
|
||||
content.getBoundingClientRect().x + content.clientWidth,
|
||||
content.getBoundingClientRect().y + content.clientHeight,
|
||||
content.clientWidth,
|
||||
content.clientHeight,
|
||||
content.getBoundingClientRect().x + content.offsetWidth,
|
||||
content.getBoundingClientRect().y + content.offsetHeight,
|
||||
content.offsetWidth,
|
||||
content.offsetHeight,
|
||||
];
|
||||
let [bxl, byt, bxr, byb, bbw, bh] = [
|
||||
button.getBoundingClientRect().x,
|
||||
button.getBoundingClientRect().y,
|
||||
button.getBoundingClientRect().x + button.clientWidth,
|
||||
button.getBoundingClientRect().y + button.clientHeight,
|
||||
button.clientWidth,
|
||||
button.clientHeight,
|
||||
button.getBoundingClientRect().x + button.offsetWidth,
|
||||
button.getBoundingClientRect().y + button.offsetHeight,
|
||||
button.offsetWidth,
|
||||
button.offsetHeight,
|
||||
];
|
||||
|
||||
/* Limit the maximum height */
|
||||
@@ -37,19 +37,29 @@ export function OlTooltip(props: { content: string; buttonRef: React.MutableRefO
|
||||
var cxc = (cxl + cxr) / 2;
|
||||
var bxc = (bxl + bxr) / 2;
|
||||
|
||||
/* Compute the x and y offsets needed to align the button and element horizontally, and to put the content below the button */
|
||||
var offsetX = bxc - cxc;
|
||||
var offsetY = byb - cyt + 8;
|
||||
/* Compute the x and y offsets needed to align the button and element horizontally, and to put the content depending on the requested position */
|
||||
var offsetX = 0;
|
||||
var offsetY = 0;
|
||||
|
||||
if (props.position === undefined || props.position === "below") {
|
||||
offsetX = bxc - cxc;
|
||||
offsetY = byb - cyt + 8;
|
||||
} else if (props.position === "side") {
|
||||
offsetX = bxr + 8;
|
||||
offsetY = byt - cyt + (bh - ch) / 2;
|
||||
}
|
||||
|
||||
/* Compute the new position of the left and right margins of the content */
|
||||
cxl += offsetX;
|
||||
cxr += offsetX;
|
||||
cyb += offsetY;
|
||||
let ncxl = cxl + offsetX;
|
||||
let ncxr = cxr + offsetX;
|
||||
let ncyb = cyb + offsetY;
|
||||
|
||||
/* Try and move the content so it is inside the screen */
|
||||
if (cxl < 0) offsetX -= cxl;
|
||||
if (cxr > window.innerWidth) offsetX -= cxr - window.innerWidth;
|
||||
if (cyb > window.innerHeight) offsetY -= bh + ch + 16;
|
||||
if (ncxl < 0) offsetX -= cxl;
|
||||
if (ncxr > window.innerWidth) {
|
||||
offsetX = bxl - cxl - cw - 12;
|
||||
}
|
||||
if (ncyb > window.innerHeight) offsetY -= bh + ch + 16;
|
||||
|
||||
/* Apply the offset */
|
||||
content.style.left = `${offsetX}px`;
|
||||
|
||||
@@ -5,6 +5,7 @@ import { MAP_OPTIONS_DEFAULTS, NO_SUBSTATE, OlympusState, OlympusSubState, Spawn
|
||||
import { AppStateChangedEvent, ContextActionSetChangedEvent, MapOptionsChangedEvent, ShortcutsChangedEvent } from "../../events";
|
||||
import { ContextAction } from "../../unit/contextaction";
|
||||
import { ContextActionSet } from "../../unit/contextactionset";
|
||||
import { MapToolBar } from "./maptoolbar";
|
||||
|
||||
export function ControlsPanel(props: {}) {
|
||||
const [controls, setControls] = useState(
|
||||
@@ -19,8 +20,8 @@ export function ControlsPanel(props: {}) {
|
||||
const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED);
|
||||
const [appSubState, setAppSubState] = useState(NO_SUBSTATE as OlympusSubState);
|
||||
const [mapOptions, setMapOptions] = useState(MAP_OPTIONS_DEFAULTS);
|
||||
const [shortcuts, setShortcuts] = useState({})
|
||||
const [contextActionSet, setContextActionSet] = useState(null as null | ContextActionSet)
|
||||
const [shortcuts, setShortcuts] = useState({});
|
||||
const [contextActionSet, setContextActionSet] = useState(null as null | ContextActionSet);
|
||||
|
||||
useEffect(() => {
|
||||
AppStateChangedEvent.on((state, subState) => {
|
||||
@@ -74,53 +75,28 @@ export function ControlsPanel(props: {}) {
|
||||
}
|
||||
);
|
||||
} else if (appState === OlympusState.UNIT_CONTROL) {
|
||||
if (!mapOptions.tabletMode) {
|
||||
controls = Object.values(contextActionSet?.getContextActions() ?? {})
|
||||
.sort((a: ContextAction, b: ContextAction) => (a.getLabel() > b.getLabel() ? 1 : -1))
|
||||
.filter((contextAction: ContextAction) => contextAction.getOptions().code)
|
||||
.map((contextAction: ContextAction) => {
|
||||
let actions: (string | IconDefinition)[] = [];
|
||||
contextAction.getOptions().shiftKey && actions.push("Shift");
|
||||
contextAction.getOptions().altKey && actions.push("Alt");
|
||||
contextAction.getOptions().ctrlKey && actions.push("Ctrl");
|
||||
actions.push(
|
||||
(contextAction.getOptions().code as string)
|
||||
.replace("Key", "")
|
||||
.replace("ControlLeft", "Left Ctrl")
|
||||
.replace("AltLeft", "Left Alt")
|
||||
.replace("ShiftLeft", "Left Shift")
|
||||
.replace("ControlRight", "Right Ctrl")
|
||||
.replace("AltRight", "Right Alt")
|
||||
.replace("ShiftRight", "Right Shift")
|
||||
);
|
||||
return {
|
||||
actions: actions,
|
||||
text: contextAction.getLabel(),
|
||||
};
|
||||
});
|
||||
controls.unshift({
|
||||
actions: ["RMB"],
|
||||
text: "Move",
|
||||
});
|
||||
controls.push({
|
||||
actions: ["RMB", "Hold"],
|
||||
target: faMap,
|
||||
text: "Show point actions",
|
||||
});
|
||||
controls.push({
|
||||
actions: ["RMB", "Hold"],
|
||||
target: faFighterJet,
|
||||
text: "Show unit actions",
|
||||
});
|
||||
controls.push({
|
||||
actions: shortcuts["toggleRelativePositions"]?.toActions(),
|
||||
text: "Activate group movement",
|
||||
});
|
||||
controls.push({
|
||||
actions: [...shortcuts["toggleRelativePositions"]?.toActions(), "Wheel"],
|
||||
text: "Rotate formation",
|
||||
});
|
||||
}
|
||||
controls.unshift({
|
||||
actions: ["RMB"],
|
||||
text: "Move",
|
||||
});
|
||||
controls.push({
|
||||
actions: ["RMB", "Hold"],
|
||||
target: faMap,
|
||||
text: "Show point actions",
|
||||
});
|
||||
controls.push({
|
||||
actions: ["RMB", "Hold"],
|
||||
target: faFighterJet,
|
||||
text: "Show unit actions",
|
||||
});
|
||||
controls.push({
|
||||
actions: shortcuts["toggleRelativePositions"]?.toActions(),
|
||||
text: "Activate group movement",
|
||||
});
|
||||
controls.push({
|
||||
actions: [...shortcuts["toggleRelativePositions"]?.toActions(), "Wheel"],
|
||||
text: "Rotate formation",
|
||||
});
|
||||
} else if (appState === OlympusState.SPAWN) {
|
||||
controls = [
|
||||
{
|
||||
@@ -151,8 +127,8 @@ export function ControlsPanel(props: {}) {
|
||||
controls = baseControls;
|
||||
controls.push({
|
||||
actions: ["LMB"],
|
||||
text: "Return to idle state"
|
||||
})
|
||||
text: "Return to idle state",
|
||||
});
|
||||
}
|
||||
|
||||
setControls(controls);
|
||||
@@ -163,11 +139,13 @@ export function ControlsPanel(props: {}) {
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
absolute right-[0px]
|
||||
absolute right-[0px] top-16
|
||||
${mapOptions.showMinimap ? `bottom-[233px]` : `bottom-[65px]`}
|
||||
flex w-[310px] flex-col items-center justify-between gap-1 p-3 text-sm
|
||||
pointer-events-none flex w-[310px] flex-col items-center justify-between
|
||||
gap-1 p-3 text-sm
|
||||
`}
|
||||
>
|
||||
<MapToolBar />
|
||||
{controls?.map((control) => {
|
||||
return (
|
||||
<div
|
||||
@@ -189,9 +167,14 @@ export function ControlsPanel(props: {}) {
|
||||
return (
|
||||
<div key={idx} className="flex gap-1">
|
||||
<div>
|
||||
{typeof action === "string" || typeof action === "number" ? action : <FontAwesomeIcon icon={action} className={`
|
||||
my-auto ml-auto
|
||||
`} />}
|
||||
{typeof action === "string" || typeof action === "number" ? (
|
||||
action
|
||||
) : (
|
||||
<FontAwesomeIcon
|
||||
icon={action}
|
||||
className={`my-auto ml-auto`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{idx < control.actions.length - 1 && typeof control.actions[idx + 1] === "string" && <div>+</div>}
|
||||
{idx < control.actions.length - 1 && typeof control.actions[idx + 1] === "number" && <div>x</div>}
|
||||
|
||||
275
frontend/react/src/ui/panels/maptoolbar.tsx
Normal file
275
frontend/react/src/ui/panels/maptoolbar.tsx
Normal file
@@ -0,0 +1,275 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { ContextActionSet } from "../../unit/contextactionset";
|
||||
import { OlStateButton } from "../components/olstatebutton";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { ContextAction, ContextActionOptions } from "../../unit/contextaction";
|
||||
import { CONTEXT_ACTION_COLORS, ContextActionTarget, MAP_OPTIONS_DEFAULTS } from "../../constants/constants";
|
||||
import { FaChevronDown, FaChevronUp } from "react-icons/fa6";
|
||||
import { OlympusState } from "../../constants/constants";
|
||||
import {
|
||||
AppStateChangedEvent,
|
||||
ContextActionChangedEvent,
|
||||
ContextActionSetChangedEvent,
|
||||
CopiedUnitsEvents,
|
||||
MapOptionsChangedEvent,
|
||||
PasteEnabledChangedEvent,
|
||||
SelectedUnitsChangedEvent,
|
||||
SelectionClearedEvent,
|
||||
SelectionEnabledChangedEvent,
|
||||
ShortcutsChangedEvent,
|
||||
} from "../../events";
|
||||
import { faCopy, faObjectGroup, faPaste } from "@fortawesome/free-solid-svg-icons";
|
||||
import { Shortcut } from "../../shortcut/shortcut";
|
||||
import { ShortcutOptions, UnitData } from "../../interfaces";
|
||||
import { Unit } from "../../unit/unit";
|
||||
|
||||
export function MapToolBar(props: {}) {
|
||||
const [appState, setAppState] = useState(OlympusState.IDLE);
|
||||
const [contextActionSet, setcontextActionSet] = useState(null as ContextActionSet | null);
|
||||
const [contextAction, setContextAction] = useState(null as ContextAction | null);
|
||||
const [scrolledTop, setScrolledTop] = useState(true);
|
||||
const [scrolledBottom, setScrolledBottom] = useState(false);
|
||||
const [mapOptions, setMapOptions] = useState(MAP_OPTIONS_DEFAULTS);
|
||||
const [selectionEnabled, setSelectionEnabled] = useState(false);
|
||||
const [pasteEnabled, setPasteEnabled] = useState(false);
|
||||
const [controller, setController] = useState(new AbortController());
|
||||
const [shortcuts, setShortcuts] = useState(
|
||||
{} as {
|
||||
[key: string]: Shortcut;
|
||||
}
|
||||
);
|
||||
const [selectedUnits, setSelectedUnits] = useState([] as Unit[]);
|
||||
const [copiedUnitsData, setCopiedUnitsData] = useState([] as UnitData[]);
|
||||
|
||||
/* Initialize the "scroll" position of the element */
|
||||
var scrollRef = useRef(null);
|
||||
useEffect(() => {
|
||||
if (scrollRef.current) onScroll(scrollRef.current);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
AppStateChangedEvent.on((state, subState) => setAppState(state));
|
||||
ContextActionSetChangedEvent.on((contextActionSet) => setcontextActionSet(contextActionSet));
|
||||
ContextActionChangedEvent.on((contextAction) => setContextAction(contextAction));
|
||||
MapOptionsChangedEvent.on((mapOptions) => setMapOptions(mapOptions));
|
||||
SelectionEnabledChangedEvent.on((selectionEnabled) => setSelectionEnabled(selectionEnabled));
|
||||
PasteEnabledChangedEvent.on((pasteEnabled) => setPasteEnabled(pasteEnabled));
|
||||
ShortcutsChangedEvent.on((shortcuts) => setShortcuts({ ...shortcuts }));
|
||||
SelectedUnitsChangedEvent.on((selectedUnits) => setSelectedUnits(selectedUnits));
|
||||
SelectionClearedEvent.on(() => setSelectedUnits([]));
|
||||
CopiedUnitsEvents.on((unitsData) => setCopiedUnitsData(unitsData));
|
||||
}, []);
|
||||
|
||||
function onScroll(el) {
|
||||
const sl = el.scrollTop;
|
||||
const sr = el.scrollHeight - el.scrollTop - el.clientHeight;
|
||||
|
||||
sl < 1 && !scrolledTop && setScrolledTop(true);
|
||||
sl > 1 && scrolledTop && setScrolledTop(false);
|
||||
|
||||
sr < 1 && !scrolledBottom && setScrolledBottom(true);
|
||||
sr > 1 && scrolledBottom && setScrolledBottom(false);
|
||||
}
|
||||
|
||||
function shortcutCombination(options: ShortcutOptions | ContextActionOptions) {
|
||||
if (options === undefined) return <></>;
|
||||
return (
|
||||
<>
|
||||
{options.ctrlKey && (
|
||||
<kbd
|
||||
className={`
|
||||
my-auto ml-auto text-nowrap rounded-lg border border-gray-200
|
||||
bg-gray-100 px-2 py-1 text-xs font-semibold text-gray-800
|
||||
dark:border-gray-500 dark:bg-gray-600 dark:text-gray-100
|
||||
`}
|
||||
>
|
||||
Ctrl
|
||||
</kbd>
|
||||
)}
|
||||
{options.altKey && (
|
||||
<kbd
|
||||
className={`
|
||||
my-auto ml-auto text-nowrap rounded-lg border border-gray-200
|
||||
bg-gray-100 px-2 py-1 text-xs font-semibold text-gray-800
|
||||
dark:border-gray-500 dark:bg-gray-600 dark:text-gray-100
|
||||
`}
|
||||
>
|
||||
Alt
|
||||
</kbd>
|
||||
)}
|
||||
{options.shiftKey && (
|
||||
<kbd
|
||||
className={`
|
||||
my-auto ml-auto text-nowrap rounded-lg border border-gray-200
|
||||
bg-gray-100 px-2 py-1 text-xs font-semibold text-gray-800
|
||||
dark:border-gray-500 dark:bg-gray-600 dark:text-gray-100
|
||||
`}
|
||||
>
|
||||
Shift
|
||||
</kbd>
|
||||
)}
|
||||
|
||||
{options.code && (
|
||||
<kbd
|
||||
className={`
|
||||
my-auto ml-auto text-nowrap rounded-lg border border-gray-200
|
||||
bg-gray-100 px-2 py-1 text-xs font-semibold text-gray-800
|
||||
dark:border-gray-500 dark:bg-gray-600 dark:text-gray-100
|
||||
`}
|
||||
>
|
||||
{options.code?.replace("Key", "")}
|
||||
</kbd>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
let reorderedActions: ContextAction[] = contextActionSet
|
||||
? Object.values(contextActionSet.getContextActions()).sort((a: ContextAction, b: ContextAction) => (a.getOptions().type < b.getOptions().type ? -1 : 1))
|
||||
: [];
|
||||
|
||||
return (
|
||||
<>
|
||||
<>
|
||||
<div
|
||||
className={`
|
||||
relative top-0 mb-auto ml-auto flex max-h-[calc(100%-200px)] gap-2
|
||||
rounded-md bg-olympus-900
|
||||
`}
|
||||
>
|
||||
{!scrolledTop && (
|
||||
<FaChevronUp
|
||||
className={`
|
||||
absolute top-0 h-6 w-full rounded-lg px-3.5 py-1 text-gray-200
|
||||
dark:bg-olympus-900
|
||||
`}
|
||||
/>
|
||||
)}
|
||||
<div className={`flex flex-col gap-2 overflow-y-auto no-scrollbar p-2`} onScroll={(ev) => onScroll(ev.target)} ref={scrollRef}>
|
||||
<>
|
||||
<div className="flex flex-col gap-1">
|
||||
<OlStateButton
|
||||
key={"select"}
|
||||
checked={selectionEnabled}
|
||||
icon={faObjectGroup}
|
||||
tooltip={
|
||||
<div className="flex content-center gap-2">
|
||||
{shortcutCombination(shortcuts["toggleSelectionEnabled"]?.getOptions())}
|
||||
<div className="my-auto">Box selection</div>
|
||||
</div>
|
||||
}
|
||||
tooltipPosition="side"
|
||||
onClick={() => {
|
||||
getApp().getMap().setSelectionEnabled(!selectionEnabled);
|
||||
if (!selectionEnabled) {
|
||||
getApp()
|
||||
.getMap()
|
||||
.getContainer()
|
||||
.addEventListener(
|
||||
"mouseup",
|
||||
() => {
|
||||
getApp().getMap().setSelectionEnabled(false);
|
||||
},
|
||||
{ once: true, signal: controller.signal }
|
||||
);
|
||||
} else {
|
||||
controller.abort();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{selectedUnits.length > 0 && (
|
||||
<div className="flex flex-col gap-1">
|
||||
<OlStateButton
|
||||
key={"copy"}
|
||||
checked={false}
|
||||
icon={faCopy}
|
||||
tooltip={
|
||||
<div className="flex content-center gap-2">
|
||||
{shortcutCombination(shortcuts["copyUnits"]?.getOptions())}
|
||||
<div className="my-auto">Copy selected units</div>
|
||||
</div>
|
||||
}
|
||||
tooltipPosition="side"
|
||||
onClick={() => {
|
||||
getApp().getUnitsManager().copy(selectedUnits);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{copiedUnitsData.length > 0 && (
|
||||
<div className="flex flex-col gap-1">
|
||||
<OlStateButton key={"paste"} checked={pasteEnabled} icon={faPaste} tooltip={
|
||||
<div className="flex content-center gap-2">
|
||||
{shortcutCombination(shortcuts["pasteUnits"]?.getOptions())}
|
||||
<div className="my-auto">Paste copied units</div>
|
||||
</div>
|
||||
} tooltipPosition="side" onClick={() => {
|
||||
getApp().getMap().setPasteEnabled(!pasteEnabled)
|
||||
}} />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
|
||||
{reorderedActions.map((contextActionIt: ContextAction) => {
|
||||
return (
|
||||
<div className="flex flex-col gap-1">
|
||||
<OlStateButton
|
||||
key={contextActionIt.getId()}
|
||||
checked={contextActionIt === contextAction}
|
||||
icon={contextActionIt.getIcon()}
|
||||
tooltip={
|
||||
<div className="flex content-center gap-2">
|
||||
{shortcutCombination(contextActionIt.getOptions())}
|
||||
<div className="my-auto">{contextActionIt.getLabel()}</div>
|
||||
</div>
|
||||
}
|
||||
tooltipPosition="side"
|
||||
buttonColor={CONTEXT_ACTION_COLORS[contextActionIt.getOptions().type ?? 0]}
|
||||
onClick={() => {
|
||||
if (contextActionIt.getTarget() === ContextActionTarget.NONE) {
|
||||
contextActionIt.executeCallback(null, null);
|
||||
} else {
|
||||
contextActionIt !== contextAction ? getApp().getMap().setContextAction(contextActionIt) : getApp().getMap().setContextAction(null);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{!scrolledBottom && (
|
||||
<FaChevronDown
|
||||
className={`
|
||||
absolute bottom-0 h-6 w-full rounded-lg px-3.5 py-1
|
||||
text-gray-200
|
||||
dark:bg-olympus-900
|
||||
`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
|
||||
{/*}
|
||||
{contextAction && (
|
||||
<div
|
||||
className={`
|
||||
absolute left-[50%] top-16 flex translate-x-[calc(-50%+2rem)]
|
||||
items-center gap-2 rounded-md bg-gray-200 p-4
|
||||
dark:bg-olympus-800
|
||||
`}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={contextAction.getIcon()}
|
||||
className={`
|
||||
mr-2 hidden text-xl text-blue-500
|
||||
md:block
|
||||
`}
|
||||
/>
|
||||
<div className={`text-gray-200`}>{contextAction.getDescription()}</div>
|
||||
</div>
|
||||
)}
|
||||
{*/}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -20,7 +20,7 @@ export function MiniMapPanel(props: {}) {
|
||||
useEffect(() => {
|
||||
let miniMap = document.querySelector(".leaflet-control-minimap");
|
||||
if (miniMap) {
|
||||
miniMap.classList.add("rounded-t-lg");
|
||||
miniMap.classList.add("rounded-b-lg");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ export function SideBar() {
|
||||
checked={appState === OlympusState.MAIN_MENU}
|
||||
icon={faEllipsisV}
|
||||
tooltip="Hide/show main menu"
|
||||
tooltipPosition="side"
|
||||
></OlStateButton>
|
||||
<OlStateButton
|
||||
onClick={() => {
|
||||
@@ -40,6 +41,7 @@ export function SideBar() {
|
||||
checked={appState === OlympusState.SPAWN}
|
||||
icon={faPlusSquare}
|
||||
tooltip="Hide/show unit spawn menu"
|
||||
tooltipPosition="side"
|
||||
></OlStateButton>
|
||||
<OlStateButton
|
||||
onClick={() => {
|
||||
@@ -48,6 +50,7 @@ export function SideBar() {
|
||||
checked={appState === OlympusState.UNIT_CONTROL}
|
||||
icon={faGamepad}
|
||||
tooltip="Hide/show selection tool and unit control menu"
|
||||
tooltipPosition="side"
|
||||
></OlStateButton>
|
||||
<OlStateButton
|
||||
onClick={() => {
|
||||
@@ -56,6 +59,7 @@ export function SideBar() {
|
||||
checked={appState === OlympusState.DRAW}
|
||||
icon={faPencil}
|
||||
tooltip="Hide/show drawing menu"
|
||||
tooltipPosition="side"
|
||||
></OlStateButton>
|
||||
<OlStateButton
|
||||
onClick={() => {
|
||||
@@ -64,6 +68,7 @@ export function SideBar() {
|
||||
checked={appState === OlympusState.AUDIO}
|
||||
icon={faVolumeHigh}
|
||||
tooltip="Hide/show audio menu"
|
||||
tooltipPosition="side"
|
||||
></OlStateButton>
|
||||
{/*}<OlStateButton
|
||||
onClick={() => {
|
||||
@@ -80,6 +85,7 @@ export function SideBar() {
|
||||
checked={appState === OlympusState.AWACS}
|
||||
icon={faA}
|
||||
tooltip="Hide/show AWACS menu"
|
||||
tooltipPosition="side"
|
||||
></OlStateButton>
|
||||
<OlStateButton
|
||||
onClick={() => {
|
||||
@@ -88,6 +94,7 @@ export function SideBar() {
|
||||
checked={appState === OlympusState.GAME_MASTER}
|
||||
icon={faCrown}
|
||||
tooltip="Hide/show Game Master menu"
|
||||
tooltipPosition="side"
|
||||
></OlStateButton>
|
||||
</div>
|
||||
</div>
|
||||
@@ -98,6 +105,7 @@ export function SideBar() {
|
||||
checked={false}
|
||||
icon={faQuestionCircle}
|
||||
tooltip="Open user guide on separate window"
|
||||
tooltipPosition="side"
|
||||
></OlStateButton>
|
||||
<OlStateButton
|
||||
onClick={() => {
|
||||
@@ -106,6 +114,7 @@ export function SideBar() {
|
||||
checked={appState === OlympusState.OPTIONS}
|
||||
icon={faCog}
|
||||
tooltip="Hide/show settings menu"
|
||||
tooltipPosition="side"
|
||||
></OlStateButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,137 +0,0 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { ContextActionSet } from "../../unit/contextactionset";
|
||||
import { OlStateButton } from "../components/olstatebutton";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { ContextAction } from "../../unit/contextaction";
|
||||
import { CONTEXT_ACTION_COLORS, ContextActionTarget, MAP_OPTIONS_DEFAULTS } from "../../constants/constants";
|
||||
import { FaChevronDown,FaChevronUp } from "react-icons/fa6";
|
||||
import { OlympusState } from "../../constants/constants";
|
||||
import { AppStateChangedEvent, ContextActionChangedEvent, ContextActionSetChangedEvent, MapOptionsChangedEvent } from "../../events";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
export function UnitControlBar(props: {}) {
|
||||
const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED);
|
||||
const [contextActionSet, setcontextActionSet] = useState(null as ContextActionSet | null);
|
||||
const [contextAction, setContextAction] = useState(null as ContextAction | null);
|
||||
const [scrolledTop, setScrolledTop] = useState(true);
|
||||
const [scrolledBottom, setScrolledBottom] = useState(false);
|
||||
const [menuHidden, setMenuHidden] = useState(false);
|
||||
const [mapOptions, setMapOptions] = useState(MAP_OPTIONS_DEFAULTS);
|
||||
|
||||
/* Initialize the "scroll" position of the element */
|
||||
var scrollRef = useRef(null);
|
||||
useEffect(() => {
|
||||
if (scrollRef.current) onScroll(scrollRef.current);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
AppStateChangedEvent.on((state, subState) => setAppState(state));
|
||||
ContextActionSetChangedEvent.on((contextActionSet) => setcontextActionSet(contextActionSet));
|
||||
ContextActionChangedEvent.on((contextAction) => setContextAction(contextAction));
|
||||
MapOptionsChangedEvent.on((mapOptions) => setMapOptions({ ...mapOptions }));
|
||||
}, []);
|
||||
|
||||
function onScroll(el) {
|
||||
const sl = el.scrollTop;
|
||||
const sr = el.scrollHeight - el.scrollTop - el.clientHeight;
|
||||
|
||||
sl < 1 && !scrolledTop && setScrolledTop(true);
|
||||
sl > 1 && scrolledTop && setScrolledTop(false);
|
||||
|
||||
sr < 1 && !scrolledBottom && setScrolledBottom(true);
|
||||
sr > 1 && scrolledBottom && setScrolledBottom(false);
|
||||
}
|
||||
|
||||
let reorderedActions: ContextAction[] = contextActionSet
|
||||
? Object.values(contextActionSet.getContextActions()).sort((a: ContextAction, b: ContextAction) => (a.getOptions().type < b.getOptions().type ? -1 : 1))
|
||||
: [];
|
||||
|
||||
return (
|
||||
<>
|
||||
{appState === OlympusState.UNIT_CONTROL && contextActionSet && Object.keys(contextActionSet.getContextActions()).length > 0 && (
|
||||
<>
|
||||
{mapOptions.tabletMode && (
|
||||
<>
|
||||
<div
|
||||
data-menuhidden={menuHidden}
|
||||
className={`
|
||||
absolute right-2 top-16 flex max-h-[80%] gap-2 rounded-md
|
||||
bg-gray-200
|
||||
dark:bg-olympus-900
|
||||
`}
|
||||
>
|
||||
{!scrolledTop && (
|
||||
<FaChevronUp
|
||||
className={`
|
||||
absolute top-0 h-6 w-full rounded-lg px-2 py-3.5
|
||||
text-gray-200
|
||||
dark:bg-olympus-900
|
||||
`}
|
||||
/>
|
||||
)}
|
||||
<div className={`
|
||||
flex flex-col gap-2 overflow-y-auto no-scrollbar p-2
|
||||
`} onScroll={(ev) => onScroll(ev.target)} ref={scrollRef}>
|
||||
{reorderedActions.map((contextActionIt: ContextAction) => {
|
||||
return (
|
||||
<div className="flex flex-col gap-1">
|
||||
<OlStateButton
|
||||
key={contextActionIt.getId()}
|
||||
checked={contextActionIt === contextAction}
|
||||
icon={contextActionIt.getIcon()}
|
||||
tooltip={contextActionIt.getLabel()}
|
||||
buttonColor={CONTEXT_ACTION_COLORS[contextActionIt.getOptions().type ?? 0]}
|
||||
onClick={() => {
|
||||
if (contextActionIt.getTarget() === ContextActionTarget.NONE) {
|
||||
contextActionIt.executeCallback(null, null);
|
||||
} else {
|
||||
contextActionIt !== contextAction
|
||||
? getApp().getMap().setContextAction(contextActionIt)
|
||||
: getApp().getMap().setContextAction(null);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{!scrolledBottom && (
|
||||
<FaChevronDown
|
||||
className={`
|
||||
absolute bottom-0 h-6 w-full rounded-lg px-2 py-3.5
|
||||
text-gray-200
|
||||
dark:bg-olympus-900
|
||||
`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{contextAction && (
|
||||
<div
|
||||
className={`
|
||||
absolute left-[50%] top-16 flex translate-x-[calc(-50%+2rem)]
|
||||
items-center gap-2 rounded-md bg-gray-200 p-4
|
||||
dark:bg-olympus-800
|
||||
`}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={contextAction.getIcon()}
|
||||
className={`
|
||||
mr-2 hidden text-xl text-blue-500
|
||||
md:block
|
||||
`}
|
||||
/>
|
||||
<div
|
||||
className={`text-gray-200`}
|
||||
>
|
||||
{contextAction.getDescription()}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -13,7 +13,7 @@ import { getApp, setupApp } from "../olympusapp";
|
||||
import { LoginModal } from "./modals/loginmodal";
|
||||
|
||||
import { MiniMapPanel } from "./panels/minimappanel";
|
||||
import { UnitControlBar } from "./panels/unitcontrolbar";
|
||||
import { MapToolBar } from "./panels/maptoolbar";
|
||||
import { DrawingMenu } from "./panels/drawingmenu";
|
||||
import { ControlsPanel } from "./panels/controlspanel";
|
||||
import { MapContextMenu } from "./contextmenus/mapcontextmenu";
|
||||
@@ -105,7 +105,6 @@ export function UI() {
|
||||
<CoordinatesPanel />
|
||||
<RadiosSummaryPanel />
|
||||
|
||||
<UnitControlBar />
|
||||
<SideBar />
|
||||
<InfoBar />
|
||||
<HotGroupBar />
|
||||
|
||||
Reference in New Issue
Block a user