fix: Small fixes to responsive design

This commit is contained in:
Pax1601 2025-03-18 16:14:39 +01:00
parent 50f3882b3e
commit 3c33d3883e
17 changed files with 211 additions and 116 deletions

View File

@ -17,10 +17,12 @@ export var BoxSelect = Handler.extend({
addHooks: function () {
DomEvent.on(this._container, "mousedown", this._onMouseDown, this);
DomEvent.on(this._container, "touchstart", this._onMouseDown, this);
},
removeHooks: function () {
DomEvent.off(this._container, "mousedown", this._onMouseDown, this);
DomEvent.off(this._container, "touchend", this._onMouseDown, this);
},
moved: function () {
@ -37,7 +39,7 @@ export var BoxSelect = Handler.extend({
},
_onMouseDown: function (e: any) {
if (this._map.getSelectionEnabled() && e.button == 0) {
if (this._map.getSelectionEnabled() && (e.button == 0 || e.type === "touchstart")) {
if (this._moved) this._finish();
DomUtil.disableImageDrag();
@ -64,7 +66,7 @@ export var BoxSelect = Handler.extend({
},
_onMouseUp: function (e: any) {
if (e.button !== 0) return;
if (e.button !== 0 && e.type !== "touchend") return;
window.setTimeout(Util.bind(this._finish, this), 0);
if (!this._moved) return;
var bounds = new LatLngBounds(this._map.containerPointToLatLng(this._startPoint), this._map.containerPointToLatLng(this._point));

View File

@ -209,7 +209,9 @@ export class Map extends L.Map {
this.on("selectionend", (e: any) => this.#onSelectionEnd(e));
this.on("mouseup", (e: any) => this.#onMouseUp(e));
this.on("touchend", (e: any) => this.#onMouseUp(e));
this.on("mousedown", (e: any) => this.#onMouseDown(e));
this.on("touchstart", (e: any) => this.#onMouseDown(e));
this.on("dblclick", (e: any) => this.#onDoubleClick(e));
this.on("click", (e: any) => e.originalEvent.preventDefault());
this.on("contextmenu", (e: any) => e.originalEvent.preventDefault());
@ -810,6 +812,10 @@ export class Map extends L.Map {
setSelectionEnabled(selectionEnabled: boolean) {
this.#selectionEnabled = selectionEnabled;
if (selectionEnabled) this.dragging.disable();
else this.dragging.enable();
SelectionEnabledChangedEvent.dispatch(selectionEnabled);
}
@ -963,6 +969,9 @@ export class Map extends L.Map {
#onSelectionEnd(e: any) {
getApp().getUnitsManager().selectFromBounds(e.selectionBounds);
// Autodisable the selection mode if touchscreen
if ("ontouchstart" in window) this.setSelectionEnabled(false);
/* Delay the event so that any other event in the queue still sees the map in selection mode */
window.setTimeout(() => {
this.#isSelecting = false;
@ -997,7 +1006,7 @@ export class Map extends L.Map {
}
#onMouseDown(e: any) {
if (e.originalEvent.button === 1) {
if (e.originalEvent?.button === 1) {
this.dragging.disable();
} // Disable dragging when right clicking

View File

@ -12,7 +12,14 @@ export function OlLocation(props: { location: LatLng; className?: string; refere
${props.className ?? ""}
my-auto cursor-pointer bg-olympus-400 p-2 text-white
`}
onClick={props.onClick ? props.onClick : () => setReferenceSystem("LatLngDec")}
onClick={
props.onClick
? props.onClick
: (ev) => {
setReferenceSystem("LatLngDec");
ev.stopPropagation();
}
}
>
<span
className={`
@ -32,7 +39,14 @@ export function OlLocation(props: { location: LatLng; className?: string; refere
my-auto flex cursor-pointer justify-between gap-2 bg-olympus-400 p-2
text-white
`}
onClick={props.onClick ? props.onClick : () => setReferenceSystem("LatLngDMS")}
onClick={
props.onClick
? props.onClick
: (ev) => {
setReferenceSystem("LatLngDMS");
ev.stopPropagation();
}
}
>
<div className="flex gap-2">
<span
@ -64,7 +78,14 @@ export function OlLocation(props: { location: LatLng; className?: string; refere
my-auto flex cursor-pointer justify-between gap-2 bg-olympus-400 p-2
text-white
`}
onClick={props.onClick ? props.onClick : () => setReferenceSystem("MGRS")}
onClick={
props.onClick
? props.onClick
: (ev) => {
setReferenceSystem("MGRS");
ev.stopPropagation();
}
}
>
<div className="flex gap-2">
<span

View File

@ -6,6 +6,12 @@ export function OlTooltip(props: {
position?: string;
relativeToParent?: boolean;
}) {
const [isTouchscreen, setIsTouchscreen] = useState(false);
useEffect(() => {
setIsTouchscreen("ontouchstart" in window);
}, []);
var contentRef = useRef(null);
function setPosition(content: HTMLDivElement, button: HTMLButtonElement) {
@ -106,7 +112,7 @@ export function OlTooltip(props: {
});
return (
props.content !== "" && (
props.content !== "" && !isTouchscreen && (
<div
ref={contentRef}
className={`

View File

@ -73,7 +73,7 @@ export function AirbaseMenu(props: { open: boolean; onClose: () => void; childre
}
return (
<Menu title={airbase?.getName() ?? "No airbase selected"} open={props.open} onClose={props.onClose} showBackButton={false} canBeHidden={true}>
<Menu title={airbase?.getName() ?? "No airbase selected"} open={props.open} onClose={props.onClose} showBackButton={false}>
<div
className={`
flex flex-col gap-2 font-normal text-gray-800

View File

@ -32,7 +32,7 @@ export function AWACSMenu(props: { open: boolean; onClose: () => void; children?
}, []);
return (
<Menu title={"AWACS Tools"} open={props.open} onClose={props.onClose} showBackButton={false} canBeHidden={true}>
<Menu title={"AWACS Tools"} open={props.open} onClose={props.onClose} showBackButton={false}>
<div
className={`
flex flex-col gap-4 p-4 font-normal text-gray-800

View File

@ -1,46 +1,62 @@
import { faArrowLeft, faCircleQuestion, faClose } from "@fortawesome/free-solid-svg-icons";
import { faArrowLeft, faCircleQuestion, faClose, faEyeSlash } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import React, { useEffect, useState } from "react";
import { FaChevronDown, FaChevronUp } from "react-icons/fa";
import { FaChevronRight } from "react-icons/fa6";
export function Menu(props: {
title: string;
open: boolean;
onClose: () => void;
canBeHidden?: boolean;
onBack?: () => void;
showBackButton?: boolean;
children?: JSX.Element | JSX.Element[];
wiki?: () => (JSX.Element | JSX.Element[]);
wiki?: () => JSX.Element | JSX.Element[];
}) {
const [hide, setHide] = useState(true);
const [wiki, setWiki] = useState(false);
if (!props.open && hide) setHide(false);
useEffect(() => {
if (window.innerWidth > 640) setHide(false);
}, [props.open]);
return (
<div
data-open={props.open}
data-wiki={wiki}
className={`
pointer-events-none absolute left-16 right-0 top-[58px] z-10
h-[calc(100vh-58px)] bg-transparent transition-all ol-panel-container
pointer-events-none absolute left-16 right-0 top-[58px] z-10 flex
h-[calc(100vh-58px)] transition-all ol-panel-container
data-[open='false']:-translate-x-full
data-[wiki='true']:w-[calc(100%-58px)] data-[wiki='true']:lg:w-[800px]
sm:w-[400px]
`}
tabIndex={-1}
>
{props.open && (
<div className="absolute flex h-full w-[30px]">
<div
className={`
pointer-events-auto my-auto flex h-[80px] w-full cursor-pointer
justify-center rounded-r-lg bg-olympus-800/90 backdrop-blur-lg
backdrop-grayscale
hover:bg-olympus-400/90
`}
onClick={() => setHide(!hide)}
>
<FaChevronRight className={`my-auto text-gray-400`} />
</div>
</div>
)}
<div
data-hide={hide}
data-canbehidden={props.canBeHidden}
className={`
pointer-events-auto h-[calc(100vh-58px)] overflow-y-auto
pointer-events-auto h-[calc(100vh-58px)] w-full overflow-y-auto
overflow-x-hidden backdrop-blur-lg backdrop-grayscale
transition-transform no-scrollbar
dark:bg-olympus-800/90
data-[canbehidden='true']:h-[calc(100vh-58px-2rem)]
data-[hide='true']:translate-y-[calc(100vh-58px)]
data-[hide='true']:-translate-x-full
`}
>
<h5
@ -71,6 +87,16 @@ export function Menu(props: {
hover:bg-gray-200
`}
/>
<FontAwesomeIcon
onClick={() => setHide(true)}
icon={faEyeSlash}
className={`
flex cursor-pointer items-center justify-center rounded-md p-2
text-lg
dark:text-gray-500 dark:hover:bg-gray-700 dark:hover:text-white
hover:bg-gray-200
`}
/>
<FontAwesomeIcon
onClick={props.onClose}
icon={faClose}
@ -82,37 +108,27 @@ export function Menu(props: {
`}
/>
</h5>
<div className="flex h-[calc(100%-3rem)]">
<div data-wiki={wiki} className={`
w-0 overflow-hidden transition-all
data-[wiki='true']:w-[50%]
`}>
{props.wiki ? props.wiki() : <div className={`p-4 text-gray-200`}>Work in progress</div>}
</div>
<div data-wiki={wiki} className={`
w-full
sm:w-[400px]
`}>{props.children}</div>
<div className="flex h-[calc(100%-3rem)] w-full">
<div
data-wiki={wiki}
className={`
w-0 overflow-hidden transition-all
data-[wiki='true']:w-[50%]
`}
>
{props.wiki ? props.wiki() : <div className={`p-4 text-gray-200`}>Work in progress</div>}
</div>
<div
data-wiki={wiki}
className={`
min-w-full
sm:w-[400px]
`}
>
{props.children}
</div>
</div>
</div>
{props.canBeHidden == true && (
<div
className={`
pointer-events-auto flex h-8 cursor-pointer justify-center
bg-olympus-800/90 backdrop-blur-lg backdrop-grayscale
hover:bg-olympus-400/90
`}
onClick={() => setHide(!hide)}
>
{hide ? (
<FaChevronUp className="mx-auto my-auto text-gray-400" />
) : (
<FaChevronDown
className={`mx-auto my-auto text-gray-400`}
/>
)}
</div>
)}
</div>
);
}

View File

@ -5,6 +5,7 @@ import { DrawSubState, MAP_OPTIONS_DEFAULTS, NO_SUBSTATE, OlympusState, OlympusS
import { AppStateChangedEvent, ContextActionSetChangedEvent, MapOptionsChangedEvent, ShortcutsChangedEvent } from "../../events";
import { ContextActionSet } from "../../unit/contextactionset";
import { MapToolBar } from "./maptoolbar";
import { CoordinatesPanel } from "./coordinatespanel";
export function ControlsPanel(props: {}) {
const [controls, setControls] = useState(
@ -200,12 +201,12 @@ export function ControlsPanel(props: {}) {
className={`
absolute right-[0px] top-16
${mapOptions.showMinimap ? `bottom-[233px]` : `bottom-[65px]`}
pointer-events-none flex w-[310px] flex-col items-center justify-between
pointer-events-none flex w-[288px] flex-col items-center justify-between
gap-1 p-3 text-sm
`}
>
<MapToolBar />
{controls?.map((control) => {
{/*controls?.map((control) => {
return (
<div
key={control.text}
@ -246,7 +247,7 @@ export function ControlsPanel(props: {}) {
</div>
</div>
);
})}
})*/}
</div>
);
}

View File

@ -12,6 +12,7 @@ export function CoordinatesPanel(props: {}) {
const [elevation, setElevation] = useState(0);
const [bullseyes, setBullseyes] = useState(null as null | { [name: string]: Bullseye });
const [selectedUnits, setSelectedUnits] = useState([] as Unit[]);
const [open, setOpen] = useState(true);
useEffect(() => {
MouseMovedEvent.on((latlng, elevation) => {
@ -27,18 +28,30 @@ export function CoordinatesPanel(props: {}) {
return (
<div
className={`
absolute bottom-[20px] right-[310px] flex min-h-12 w-[380px] flex-col
items-center justify-between gap-2 rounded-lg bg-gray-200 px-3 py-3
text-sm backdrop-blur-lg backdrop-grayscale
flex w-full flex-col items-center justify-between gap-2 rounded-lg
bg-gray-200 px-3 py-3 text-sm backdrop-blur-lg backdrop-grayscale
dark:bg-olympus-800/90 dark:text-gray-200
`}
onClick={() => setOpen(!open)}
>
{bullseyes && (
<div className="flex w-full items-center justify-start">
<div className="absolute right-[12px] top-[15px]">
{open ? (
<FaChevronDown className="cursor-pointer" />
) : (
<FaChevronUp
className={`cursor-pointer`}
/>
)}
</div>
{open && bullseyes && (
<div
className={`
flex w-full flex-col items-start justify-start gap-2
`}
>
<div
className={`
mr-[11px] flex min-w-64 max-w-64 items-center justify-between
gap-2
flex flex min-w-64 max-w-64 items-start justify-between gap-2
`}
>
{bullseyes[2] && (
@ -84,18 +97,27 @@ export function CoordinatesPanel(props: {}) {
</div>
)}
<div className="flex w-full items-center justify-between">
<div
className={`
flex w-full items-center justify-between pointer-events-all
`}
>
<OlLocation className="!min-w-64 !max-w-64 bg-transparent !p-0" location={latlng} />
<span
className={`
mr-2 rounded-sm bg-white px-1 py-1 text-center font-bold
text-olympus-700
`}
>
<FaMountain />
</span>
<div className="min-w-12">{mToFt(elevation).toFixed()}ft</div>
</div>
{open && (
<div className="flex w-full items-center justify-start">
<span
className={`
mr-2 rounded-sm bg-white px-1 py-1 text-center font-bold
text-olympus-700
`}
>
<FaMountain />
</span>
<div className="min-w-12">{mToFt(elevation).toFixed()}ft</div>
</div>
)}
</div>
);
}

View File

@ -147,7 +147,6 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
open={props.open}
title="Draw"
onClose={props.onClose}
canBeHidden={true}
showBackButton={appSubState !== DrawSubState.NO_SUBSTATE}
onBack={() => {
getApp().getCoalitionAreasManager().setSelectedArea(null);

View File

@ -102,7 +102,8 @@ export function Header() {
return (
<div
className={`
z-10 flex w-full gap-4 border-gray-200 bg-gray-300 px-3 align-center
relative z-10 flex w-full gap-4 border-gray-200 bg-gray-300 px-3
align-center
dark:border-gray-800 dark:bg-olympus-900
`}
>
@ -147,6 +148,7 @@ export function Header() {
{IP}
</div>
</div>
<div className="w-8">
{savingSessionData ? (
<div className="text-white">
<FaSpinner className={`animate-spin text-2xl`} />
@ -162,6 +164,7 @@ export function Header() {
<FaCheck className={`absolute left-3 top-0 text-green-500`} />
</div>
)}
</div>
</div>
{commandModeOptions.commandMode === BLUE_COMMANDER && (

View File

@ -60,7 +60,7 @@ export function JTACMenu(props: { open: boolean; onClose: () => void; children?:
let targetPosition = (targetUnit ? targetUnit.getPosition() : targetLocation) ?? new LatLng(0, 0);
return (
<Menu title={"JTAC Tools"} open={props.open} onClose={props.onClose} showBackButton={false} canBeHidden={true}>
<Menu title={"JTAC Tools"} open={props.open} onClose={props.onClose} showBackButton={false}>
<div
className={`
flex flex-col gap-2 p-4 font-normal text-gray-800

View File

@ -5,6 +5,8 @@ import { getApp } from "../../olympusapp";
import { FaChevronDown, FaChevronUp } from "react-icons/fa6";
import { MapOptionsChangedEvent, ServerStatusUpdatedEvent } from "../../events";
import { colors, MAP_OPTIONS_DEFAULTS } from "../../constants/constants";
import { CoordinatesPanel } from "./coordinatespanel";
import { RadiosSummaryPanel } from "./radiossummarypanel";
export function MiniMapPanel(props: {}) {
const [serverStatus, setServerStatus] = useState({} as ServerStatus);
@ -54,47 +56,65 @@ export function MiniMapPanel(props: {}) {
className={`
absolute right-[10px]
${mapOptions.showMinimap ? `bottom-[188px]` : `bottom-[20px]`}
flex w-[288px] items-center justify-between
${mapOptions.showMinimap ? `rounded-t-lg` : `rounded-lg`}
h-12 bg-gray-200 px-3 text-sm backdrop-blur-lg backdrop-grayscale
dark:bg-olympus-800/90 dark:text-gray-200
flex w-[288px] cursor-pointer flex-col items-center justify-between
gap-2 text-sm backdrop-blur-lg
`}
>
{!serverStatus.connected ? (
<div className={`flex animate-pulse items-center gap-2 font-semibold`}>
<div className={`relative h-4 w-4 rounded-full bg-[#F05252]`}></div>
Server disconnected
</div>
) : serverStatus.paused ? (
<div className={`flex animate-pulse items-center gap-2 font-semibold`}>
<div className={`relative h-4 w-4 rounded-full bg-[#FF9900]`}></div>
Server paused
</div>
) : (
<>
<div className="flex gap-2 font-semibold">
FPS:
<span style={{ color: frameRateColor }} className={`font-semibold`}>
{serverStatus.frameRate}
</span>
<RadiosSummaryPanel />
<CoordinatesPanel />
<div className={`
flex h-12 w-full items-center justify-between gap-2 px-3
backdrop-grayscale
dark:bg-olympus-800/90 dark:text-gray-200
${mapOptions.showMinimap ? `rounded-t-lg` : `rounded-lg`}
`}
onClick={(ev) => {
getApp().getMap().setOption("showMinimap", !mapOptions.showMinimap);
}}>
{!serverStatus.connected ? (
<div className={`flex animate-pulse items-center gap-2 font-semibold`}>
<div className={`relative h-4 w-4 rounded-full bg-[#F05252]`}></div>
Server disconnected
</div>
<div className="flex gap-2 font-semibold">
Load:
<span style={{ color: loadColor }} className={`font-semibold`}>
{serverStatus.load}
</span>
) : serverStatus.paused ? (
<div className={`flex animate-pulse items-center gap-2 font-semibold`}>
<div className={`relative h-4 w-4 rounded-full bg-[#FF9900]`}></div>
Server paused
</div>
<div className="flex cursor-pointer gap-2 font-semibold" onClick={() => setShowMissionTime(!showMissionTime)}>
{showMissionTime ? "MT" : "ET"}: {timeString}
</div>
<div className={`relative h-4 w-4 rounded-full bg-[#8BFF63]`}></div>
</>
)}
{mapOptions.showMinimap ? (
<FaChevronDown className="cursor-pointer" onClick={() => getApp().getMap().setOption("showMinimap", false)} />
) : (
<FaChevronUp className="cursor-pointer" onClick={() => getApp().getMap().setOption("showMinimap", true)} />
)}
) : (
<>
<div className="flex w-16 gap-1 font-semibold">
FPS:
<span style={{ color: frameRateColor }} className={`font-semibold`}>
{serverStatus.frameRate}
</span>
</div>
<div className="flex gap-1 font-semibold">
Load:
<span style={{ color: loadColor }} className={`font-semibold`}>
{serverStatus.load}
</span>
</div>
<div
className="ml-auto flex w-24 cursor-pointer gap-2 font-semibold"
onClick={(ev) => {
setShowMissionTime(!showMissionTime);
ev.stopPropagation();
}}
>
{showMissionTime ? "MT" : "ET"}: {timeString}
</div>
</>
)}
{mapOptions.showMinimap ? (
<FaChevronDown className="cursor-pointer" />
) : (
<FaChevronUp
className={`cursor-pointer`}
/>
)}
</div>
</div>
);
}

View File

@ -19,12 +19,10 @@ export function RadiosSummaryPanel(props: {}) {
{audioSinks.length > 0 && (
<div
className={`
absolute bottom-[20px] right-[700px] flex w-fit flex-col
items-center justify-between gap-2 rounded-lg bg-transparent text-sm
text-gray-200
flex w-full gap-2 rounded-lg text-sm text-gray-200
`}
>
<div className="flex w-full items-center justify-between gap-2">
<div className="flex w-full flex-wrap gap-2">
{audioSinks.filter((audioSinks) => audioSinks instanceof RadioSink).length > 0 &&
audioSinks

View File

@ -113,7 +113,6 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
{...props}
title="Spawn menu"
showBackButton={blueprint !== null || effect !== null}
canBeHidden={true}
onBack={() => {
getApp().setState(OlympusState.SPAWN);
setBlueprint(null);

View File

@ -295,7 +295,6 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
open={props.open}
title={selectedUnits.length > 0 ? `Units selected (x${selectedUnits.length})` : `No units selected`}
onClose={props.onClose}
canBeHidden={true}
wiki={() => {
return (
<div

View File

@ -111,8 +111,8 @@ export function UI() {
<MiniMapPanel />
<ControlsPanel />
<CoordinatesPanel />
<RadiosSummaryPanel />
<SideBar />
<InfoBar />