From fbc22676c580ec34fe03ad20850f25e03bc43d93 Mon Sep 17 00:00:00 2001 From: Davide Passoni Date: Fri, 5 Jul 2024 17:26:53 +0200 Subject: [PATCH] Added box selection for mobiles and ability to drag markers, units can be moved only via context action --- frontend/react/src/constants/constants.ts | 1 - frontend/react/src/dom.d.ts | 2 + frontend/react/src/interfaces.ts | 4 +- frontend/react/src/map/boxselect.ts | 38 +++++-- frontend/react/src/map/map.ts | 98 +--------------- frontend/react/src/statecontext.tsx | 1 + .../src/ui/components/olcoalitiontoggle.tsx | 6 +- .../react/src/ui/components/oldropdown.tsx | 28 +++-- .../react/src/ui/components/ollabeltoggle.tsx | 4 +- .../react/src/ui/components/olnumberinput.tsx | 2 +- .../react/src/ui/panels/components/menu.tsx | 30 +++-- frontend/react/src/ui/panels/header.tsx | 35 ++++++ frontend/react/src/ui/panels/minimappanel.tsx | 6 +- frontend/react/src/ui/panels/sidebar.tsx | 14 ++- frontend/react/src/ui/panels/spawnmenu.tsx | 20 +++- .../src/ui/panels/unitmousecontrolbar.tsx | 105 ++++++++++++------ .../react/src/ui/panels/unitspawnmenu.tsx | 15 +-- frontend/react/src/ui/ui.tsx | 20 +++- frontend/react/src/unit/unit.ts | 71 +++++++++--- frontend/react/src/unit/unitsmanager.ts | 4 +- frontend/server/demo.js | 4 +- 21 files changed, 303 insertions(+), 205 deletions(-) diff --git a/frontend/react/src/constants/constants.ts b/frontend/react/src/constants/constants.ts index 70de3819..486fdc30 100644 --- a/frontend/react/src/constants/constants.ts +++ b/frontend/react/src/constants/constants.ts @@ -257,7 +257,6 @@ export const defaultMapLayers = {}; /* Map constants */ export const IDLE = "Idle"; -export const MOVE_UNIT = "Move unit"; export const SPAWN_UNIT = "Spawn unit"; export const CONTEXT_ACTION = "Context action"; export const COALITIONAREA_DRAW_POLYGON = "Draw Coalition Area"; diff --git a/frontend/react/src/dom.d.ts b/frontend/react/src/dom.d.ts index 6e403c42..146d437f 100644 --- a/frontend/react/src/dom.d.ts +++ b/frontend/react/src/dom.d.ts @@ -14,11 +14,13 @@ interface CustomEventMap { mapOptionChanged: CustomEvent; mapSourceChanged: CustomEvent; mapOptionsChanged: CustomEvent; // TODO not very clear, why the two options? + mapSelectionEnd: CustomEvent; configLoaded: CustomEvent; commandModeOptionsChanged: CustomEvent; contactsUpdated: CustomEvent; activeCoalitionChanged: CustomEvent; serverStatusUpdated: CustomEvent; + mapForceBoxSelect: CustomEvent } declare global { diff --git a/frontend/react/src/interfaces.ts b/frontend/react/src/interfaces.ts index 45f6fffc..b1a3be44 100644 --- a/frontend/react/src/interfaces.ts +++ b/frontend/react/src/interfaces.ts @@ -75,8 +75,8 @@ export interface UnitSpawnTable { location: LatLng; skill: string; liveryID: string; - altitude: number; - loadout: string; + altitude?: number; + loadout?: string; } export interface ObjectIconOptions { diff --git a/frontend/react/src/map/boxselect.ts b/frontend/react/src/map/boxselect.ts index 6d94b8ba..cc54d58a 100644 --- a/frontend/react/src/map/boxselect.ts +++ b/frontend/react/src/map/boxselect.ts @@ -1,4 +1,3 @@ -import { Map } from "leaflet"; import { Handler } from "leaflet"; import { Util } from "leaflet"; import { DomUtil } from "leaflet"; @@ -12,15 +11,22 @@ export var BoxSelect = Handler.extend({ this._container = map.getContainer(); this._pane = map.getPanes().overlayPane; this._resetStateTimeout = 0; + this._forceBoxSelect = false; map.on("unload", this._destroy, this); + + document.addEventListener("mapForceBoxSelect", () => { + this._forceBoxSelect = true; + }); }, 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, "touchstart", this._onMouseDown, this); }, moved: function () { @@ -45,7 +51,10 @@ export var BoxSelect = Handler.extend({ }, _onMouseDown: function (e: any) { - if (e.which == 1 && e.button == 0 && e.shiftKey) { + if ( + (e.which == 1 && e.button == 0 && (e.shiftKey || this._forceBoxSelect)) || + (e.type === "touchstart" && this._forceBoxSelect) + ) { this._map.fire("selectionstart"); // Clear the deferred resetState if it hasn't executed yet, otherwise it // will interrupt the interaction and orphan a box element in the container. @@ -54,14 +63,21 @@ export var BoxSelect = Handler.extend({ DomUtil.disableTextSelection(); DomUtil.disableImageDrag(); + this._map.dragging.disable(); - this._startPoint = this._map.mouseEventToContainerPoint(e); + if (e.type === "touchstart") + this._startPoint = this._map.mouseEventToContainerPoint(e.touches[0]); + else + this._startPoint = this._map.mouseEventToContainerPoint(e); - //@ts-ignore DomEvent.on( + //@ts-ignore document, { contextmenu: DomEvent.stop, + touchmove: this._onMouseMove, + touchend: this._onMouseUp, + touchstart: this._onKeyDown, mousemove: this._onMouseMove, mouseup: this._onMouseUp, keydown: this._onKeyDown, @@ -83,7 +99,10 @@ export var BoxSelect = Handler.extend({ this._map.fire("boxzoomstart"); } - this._point = this._map.mouseEventToContainerPoint(e); + if (e.type === "touchmove") + this._point = this._map.mouseEventToContainerPoint(e.touches[0]); + else + this._point = this._map.mouseEventToContainerPoint(e); var bounds = new Bounds(this._point, this._startPoint), size = bounds.getSize(); @@ -102,12 +121,16 @@ export var BoxSelect = Handler.extend({ DomUtil.enableTextSelection(); DomUtil.enableImageDrag(); + this._map.dragging.enable(); - //@ts-ignore DomEvent.off( + //@ts-ignore document, { contextmenu: DomEvent.stop, + touchmove: this._onMouseMove, + touchend: this._onMouseUp, + touchstart: this._onKeyDown, mousemove: this._onMouseMove, mouseup: this._onMouseUp, keydown: this._onKeyDown, @@ -117,7 +140,7 @@ export var BoxSelect = Handler.extend({ }, _onMouseUp: function (e: any) { - if (e.which !== 1 && e.button !== 0) { + if (e.which !== 1 && e.button !== 0 && e.type !== "touchend") { return; } @@ -134,6 +157,7 @@ export var BoxSelect = Handler.extend({ this._map.containerPointToLatLng(this._point) ); + this._forceBoxSelect = false; this._map.fire("selectionend", { selectionBounds: bounds }); }, diff --git a/frontend/react/src/map/map.ts b/frontend/react/src/map/map.ts index ead5d712..3a730862 100644 --- a/frontend/react/src/map/map.ts +++ b/frontend/react/src/map/map.ts @@ -18,7 +18,6 @@ import { minimapBoundaries, IDLE, COALITIONAREA_DRAW_POLYGON, - MOVE_UNIT, defaultMapMirrors, SPAWN_UNIT, CONTEXT_ACTION, @@ -26,7 +25,6 @@ import { MAP_HIDDEN_TYPES_DEFAULTS, } from "../constants/constants"; import { CoalitionArea } from "./coalitionarea/coalitionarea"; -import { DrawingCursor } from "./coalitionarea/drawingcursor"; import { TouchBoxSelect } from "./touchboxselect"; import { DestinationPreviewHandle } from "./markers/destinationpreviewHandle"; @@ -40,16 +38,7 @@ import { MapHiddenTypes, MapOptions } from "../types/types"; import { SpawnRequestTable } from "../interfaces"; import { ContextAction } from "../unit/contextaction"; -// Touch screen support temporarily disabled -var hasTouchScreen = false; -//if ("maxTouchPoints" in navigator) -// hasTouchScreen = navigator.maxTouchPoints > 0; - -if (hasTouchScreen) - L.Map.addInitHook("addHandler", "boxSelect", TouchBoxSelect); -else L.Map.addInitHook("addHandler", "boxSelect", BoxSelect); - -//L.Map.addInitHook("addHandler", "gestureHandling", GestureHandling); +L.Map.addInitHook("addHandler", "boxSelect", BoxSelect); export class Map extends L.Map { /* Options */ @@ -95,9 +84,6 @@ export class Map extends L.Map { #coalitionAreas: CoalitionArea[] = []; - #drawingCursor: DrawingCursor = new DrawingCursor(); - #spawnCursor: TemporaryUnitMarker | null = null; - #mapLayers: any = defaultMapLayers; #mapMirrors: any = defaultMapMirrors; #layerName: string = ""; @@ -127,8 +113,7 @@ export class Map extends L.Map { maxBoundsViscosity: 1.0, minZoom: 7, keyboard: true, - keyboardPanDelta: 0, - gestureHandling: hasTouchScreen, + keyboardPanDelta: 0 }); this.setView([37.23, -115.8], 10); @@ -373,12 +358,6 @@ export class Map extends L.Map { getApp().getUnitsManager().deselectAllUnits(); } else if (this.#state === SPAWN_UNIT) { this.#spawnRequestTable = options?.spawnRequestTable ?? null; - this.#spawnCursor?.removeFrom(this); - this.#spawnCursor = new TemporaryUnitMarker( - new L.LatLng(0, 0), - this.#spawnRequestTable?.unit.unitType ?? "unknown", - this.#spawnRequestTable?.coalition ?? "blue" - ); } else if (this.#state === CONTEXT_ACTION) { this.#contextAction = options?.contextAction ?? null; } else if (this.#state === COALITIONAREA_DRAW_POLYGON) { @@ -386,8 +365,6 @@ export class Map extends L.Map { this.#coalitionAreas[this.#coalitionAreas.length - 1].addTo(this); } - this.#updateCursor(); - document.dispatchEvent( new CustomEvent("mapStateChanged", { detail: this.#state }) ); @@ -535,7 +512,6 @@ export class Map extends L.Map { this.options.scrollWheelZoom = undefined; this.#centerUnit = null; } - this.#updateCursor(); } getCenteredOnUnit() { @@ -762,9 +738,6 @@ export class Map extends L.Map { this.hideAllContextMenus(); if (this.#state === IDLE) { this.deselectAllCoalitionAreas(); - } else if (this.#state === MOVE_UNIT) { - getApp().getUnitsManager().clearDestinations(); - getApp().getUnitsManager().addDestination(e.latlng, false, 0); } else if (this.#state === SPAWN_UNIT) { if (this.#spawnRequestTable !== null) { const location = e.latlng; @@ -820,7 +793,6 @@ export class Map extends L.Map { #onSelectionStart(e: any) { this.#selecting = true; - this.#updateCursor(); } #onSelectionEnd(e: any) { @@ -831,7 +803,7 @@ export class Map extends L.Map { this.#preventLeftClick = false; }, 200); getApp().getUnitsManager().selectFromBounds(e.selectionBounds); - this.#updateCursor(); + document.dispatchEvent(new CustomEvent("mapSelectionEnd")); } #onMouseDown(e: any) {} @@ -841,15 +813,10 @@ export class Map extends L.Map { this.#lastMousePosition.y = e.originalEvent.y; this.#lastMouseCoordinates = this.mouseEventToLatLng(e.originalEvent); - this.#updateCursor(); - - if (this.#state === SPAWN_UNIT) { - this.#updateSpawnCursor(); - } else if ( + if ( this.#state === COALITIONAREA_DRAW_POLYGON && e.latlng !== undefined ) { - this.#drawingCursor.setLatLng(e.latlng); /* Update the polygon being drawn with the current position of the mouse cursor */ this.getSelectedCoalitionArea()?.moveActiveVertex(e.latlng); } @@ -857,12 +824,10 @@ export class Map extends L.Map { #onKeyDown(e: any) { this.#shiftKey = e.originalEvent.shiftKey; - this.#updateCursor(); } #onKeyUp(e: any) { this.#shiftKey = e.originalEvent.shiftKey; - this.#updateCursor(); } #onZoomStart(e: any) { @@ -927,7 +892,6 @@ export class Map extends L.Map { unit.getPosition().lng ); this.setView(unitPosition, this.getZoom(), { animate: false }); - this.#updateCursor(); } #getMinimapBoundaries() { @@ -939,60 +903,6 @@ export class Map extends L.Map { this.getSelectedCoalitionArea()?.setSelected(false); } - /* Cursors */ - #updateCursor() { - /* If we are performing an area selection, show the default cursor */ - if (this.#selecting) { - /* Hide all non default cursors */ - this.#hideDrawingCursor(); - this.#hideSpawnCursor(); - - this.#showDefaultCursor(); - } else { - /* Hide all the unnecessary cursors depending on the active state */ - if (this.#state !== IDLE) this.#hideDefaultCursor(); - if (this.#state !== SPAWN_UNIT) this.#hideSpawnCursor(); - if (this.#state !== COALITIONAREA_DRAW_POLYGON) this.#hideDrawingCursor(); - - /* Show the active cursor depending on the active state */ - if (this.#state === IDLE) this.#showDefaultCursor(); - else if (this.#state === SPAWN_UNIT) this.#showSpawnCursor(); - else if (this.#state === COALITIONAREA_DRAW_POLYGON) - this.#showDrawingCursor(); - } - } - - #showDefaultCursor() { - document.getElementById(this.#ID)?.classList.remove("hidden-cursor"); - } - - #hideDefaultCursor() { - document.getElementById(this.#ID)?.classList.add("hidden-cursor"); - } - - #showDrawingCursor() { - this.#hideDefaultCursor(); - if (!this.hasLayer(this.#drawingCursor)) this.#drawingCursor.addTo(this); - } - - #hideDrawingCursor() { - this.#drawingCursor.setLatLng(new L.LatLng(0, 0)); - if (this.hasLayer(this.#drawingCursor)) - this.#drawingCursor.removeFrom(this); - } - - #showSpawnCursor() { - this.#spawnCursor?.addTo(this); - } - - #updateSpawnCursor() { - this.#spawnCursor?.setLatLng(this.getMouseCoordinates()); - } - - #hideSpawnCursor() { - this.#spawnCursor?.removeFrom(this); - } - #setSlaveDCSCameraAvailable(newSlaveDCSCameraAvailable: boolean) { this.#slaveDCSCameraAvailable = newSlaveDCSCameraAvailable; let linkButton = document.getElementById("camera-link-control"); diff --git a/frontend/react/src/statecontext.tsx b/frontend/react/src/statecontext.tsx index b6297aad..aeb39261 100644 --- a/frontend/react/src/statecontext.tsx +++ b/frontend/react/src/statecontext.tsx @@ -15,6 +15,7 @@ export const StateContext = createContext({ mapOptions: MAP_OPTIONS_DEFAULTS, mapSources: [] as string[], activeMapSource: "", + mapBoxSelection: false }); export const StateProvider = StateContext.Provider; diff --git a/frontend/react/src/ui/components/olcoalitiontoggle.tsx b/frontend/react/src/ui/components/olcoalitiontoggle.tsx index 4cc38404..ff5d1477 100644 --- a/frontend/react/src/ui/components/olcoalitiontoggle.tsx +++ b/frontend/react/src/ui/components/olcoalitiontoggle.tsx @@ -34,14 +34,14 @@ export function OlCoalitionToggle(props: { > {props.coalition - ? `Coalition (${props.coalition[0].toLocaleUpperCase() + props.coalition.substring(1)})` - : "Different values"} + ? `${props.coalition[0].toLocaleUpperCase() + props.coalition.substring(1)}` + : "Diff. values"} ); diff --git a/frontend/react/src/ui/components/oldropdown.tsx b/frontend/react/src/ui/components/oldropdown.tsx index 66a0d300..defd78ba 100644 --- a/frontend/react/src/ui/components/oldropdown.tsx +++ b/frontend/react/src/ui/components/oldropdown.tsx @@ -19,7 +19,21 @@ export function OlDropdown(props: { content.style.top = "0px"; content.style.height = ""; - /* Get the position and size of the button and the content elements */ + /* Get the position and size of the button */ + var [bxl, byt, bxr, byb, bw, bh] = [ + button.getBoundingClientRect().x, + button.getBoundingClientRect().y, + button.getBoundingClientRect().x + button.clientWidth, + button.getBoundingClientRect().y + button.clientHeight, + button.clientWidth, + button.clientHeight, + ]; + + /* Set the minimum and maximum width to be equal to the button width */ + content.style.minWidth = `${bw}px`; + content.style.maxWidth = `${bw}px`; + + /* Get the position and size of the content element */ var [cxl, cyt, cxr, cyb, cw, ch] = [ content.getBoundingClientRect().x, content.getBoundingClientRect().y, @@ -28,14 +42,6 @@ export function OlDropdown(props: { content.clientWidth, content.clientHeight, ]; - var [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, - ]; /* Limit the maximum height */ if (ch > 400) { @@ -134,8 +140,8 @@ export function OlDropdown(props: { ref={contentRef} data-open={open} className={` - absolute z-ui-4 divide-y divide-gray-100 overflow-y-scroll rounded-lg - bg-white p-2 shadow + absolute z-ui-4 divide-y divide-gray-100 overflow-y-scroll + no-scrollbar rounded-lg bg-white p-2 shadow dark:bg-gray-700 data-[open='false']:hidden `} diff --git a/frontend/react/src/ui/components/ollabeltoggle.tsx b/frontend/react/src/ui/components/ollabeltoggle.tsx index 2c6a5c08..84a4c254 100644 --- a/frontend/react/src/ui/components/ollabeltoggle.tsx +++ b/frontend/react/src/ui/components/ollabeltoggle.tsx @@ -31,7 +31,7 @@ export function OlLabelToggle(props: { ) => void; }) { return ( -
+