mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
Completed creation of areas
This commit is contained in:
@@ -4,6 +4,10 @@ import { CustomMarker } from "../markers/custommarker";
|
||||
export class CoalitionAreaHandle extends CustomMarker {
|
||||
constructor(latlng: LatLng) {
|
||||
super(latlng, { interactive: true, draggable: true });
|
||||
|
||||
this.on("add", (e) => {
|
||||
this.getElement()?.addEventListener("touchstart", (e) => e.stopPropagation());
|
||||
})
|
||||
}
|
||||
|
||||
createIcon() {
|
||||
|
||||
@@ -49,6 +49,10 @@ export class CoalitionCircle extends Circle {
|
||||
this.#setRadiusHandle();
|
||||
this.#drawLabel();
|
||||
});
|
||||
|
||||
this.on("remove", () => {
|
||||
this.#label.removeFrom(this._map);
|
||||
});
|
||||
}
|
||||
|
||||
setCoalition(coalition: Coalition) {
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
Polygon,
|
||||
PolylineOptions,
|
||||
DivIcon,
|
||||
Marker,
|
||||
Marker
|
||||
} from "leaflet";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { CoalitionAreaHandle } from "./coalitionareahandle";
|
||||
@@ -57,6 +57,10 @@ export class CoalitionPolygon extends Polygon {
|
||||
this.#setMiddleHandles();
|
||||
this.#drawLabel();
|
||||
});
|
||||
|
||||
this.on("remove", () => {
|
||||
this.#label.removeFrom(this._map);
|
||||
});
|
||||
}
|
||||
|
||||
setCoalition(coalition: Coalition) {
|
||||
|
||||
179
frontend/react/src/map/coalitionarea/draggablepath.ts
Normal file
179
frontend/react/src/map/coalitionarea/draggablepath.ts
Normal file
@@ -0,0 +1,179 @@
|
||||
import * as L from "leaflet";
|
||||
|
||||
export const initDraggablePath = () => {
|
||||
//@ts-ignore
|
||||
L.PathDraggable = L.Draggable.extend({
|
||||
initialize: function (path) {
|
||||
this._path = path;
|
||||
|
||||
this._canvas = path._map.getRenderer(path) instanceof L.Canvas;
|
||||
|
||||
var element = this._canvas
|
||||
? this._path._map.getRenderer(this._path)._container
|
||||
: this._path._path;
|
||||
|
||||
//@ts-ignore
|
||||
L.Draggable.prototype.initialize.call(this, element, element, true);
|
||||
},
|
||||
|
||||
_updatePosition: function () {
|
||||
var e = { originalEvent: this._lastEvent };
|
||||
|
||||
this.fire("drag", e);
|
||||
},
|
||||
|
||||
_onDown: function (e) {
|
||||
var first = e.touches ? e.touches[0] : e;
|
||||
|
||||
this._startPoint = new L.Point(first.clientX, first.clientY);
|
||||
|
||||
if (
|
||||
this._canvas &&
|
||||
!this._path._containsPoint(
|
||||
this._path._map.mouseEventToLayerPoint(first)
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
L.Draggable.prototype._onDown.call(this, e);
|
||||
e.stopPropagation();
|
||||
},
|
||||
});
|
||||
|
||||
//@ts-ignore
|
||||
L.Handler.PathDrag = L.Handler.extend({
|
||||
initialize: function (path) {
|
||||
this._path = path;
|
||||
},
|
||||
|
||||
getEvents: function () {
|
||||
return {
|
||||
dragstart: this._onDragStart,
|
||||
|
||||
drag: this._onDrag,
|
||||
|
||||
dragend: this._onDragEnd,
|
||||
};
|
||||
},
|
||||
|
||||
addHooks: function () {
|
||||
if (!this._draggable) {
|
||||
//@ts-ignore
|
||||
this._draggable = new L.PathDraggable(this._path);
|
||||
}
|
||||
|
||||
this._draggable.on(this.getEvents(), this).enable();
|
||||
|
||||
L.DomUtil.addClass(this._draggable._element, "leaflet-path-draggable");
|
||||
},
|
||||
|
||||
removeHooks: function () {
|
||||
this._draggable.off(this.getEvents(), this).disable();
|
||||
|
||||
L.DomUtil.removeClass(this._draggable._element, "leaflet-path-draggable");
|
||||
},
|
||||
|
||||
moved: function () {
|
||||
return this._draggable && this._draggable._moved;
|
||||
},
|
||||
|
||||
_onDragStart: function () {
|
||||
this._startPoint = this._draggable._startPoint;
|
||||
|
||||
this._path.closePopup().fire("movestart").fire("dragstart");
|
||||
},
|
||||
|
||||
_onDrag: function (e) {
|
||||
var path = this._path,
|
||||
event =
|
||||
e.originalEvent.touches && e.originalEvent.touches.length === 1
|
||||
? e.originalEvent.touches[0]
|
||||
: e.originalEvent,
|
||||
newPoint = L.point(event.clientX, event.clientY),
|
||||
latlng = path._map.layerPointToLatLng(newPoint);
|
||||
|
||||
this._offset = newPoint.subtract(this._startPoint);
|
||||
|
||||
this._startPoint = newPoint;
|
||||
|
||||
this._path.eachLatLng(this.updateLatLng, this);
|
||||
|
||||
path.redraw();
|
||||
|
||||
e.latlng = latlng;
|
||||
|
||||
e.offset = this._offset;
|
||||
|
||||
path.fire("drag", e);
|
||||
|
||||
e.latlng = this._path.getCenter
|
||||
? this._path.getCenter()
|
||||
: this._path.getLatLng();
|
||||
|
||||
path.fire("move", e);
|
||||
},
|
||||
|
||||
_onDragEnd: function (e) {
|
||||
if (this._path._bounds) this.resetBounds();
|
||||
|
||||
this._path.fire("moveend").fire("dragend", e);
|
||||
},
|
||||
|
||||
latLngToLayerPoint: function (latlng) {
|
||||
// Same as map.latLngToLayerPoint, but without the round().
|
||||
|
||||
var projectedPoint = this._path._map.project(L.latLng(latlng));
|
||||
|
||||
return projectedPoint._subtract(this._path._map.getPixelOrigin());
|
||||
},
|
||||
|
||||
updateLatLng: function (latlng) {
|
||||
var oldPoint = this.latLngToLayerPoint(latlng);
|
||||
|
||||
oldPoint._add(this._offset);
|
||||
|
||||
var newLatLng = this._path._map.layerPointToLatLng(oldPoint);
|
||||
|
||||
latlng.lat = newLatLng.lat;
|
||||
|
||||
latlng.lng = newLatLng.lng;
|
||||
},
|
||||
|
||||
resetBounds: function () {
|
||||
//@ts-ignore
|
||||
this._path._bounds = new L.LatLngBounds();
|
||||
|
||||
this._path.eachLatLng(function (latlng) {
|
||||
this._bounds.extend(latlng);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
L.Path.include({
|
||||
eachLatLng: function (callback, context) {
|
||||
context = context || this;
|
||||
|
||||
var loop = function (latlngs) {
|
||||
for (var i = 0; i < latlngs.length; i++) {
|
||||
if (L.Util.isArray(latlngs[i])) loop(latlngs[i]);
|
||||
else callback.call(context, latlngs[i]);
|
||||
}
|
||||
};
|
||||
|
||||
loop(this.getLatLngs ? this.getLatLngs() : [this.getLatLng()]);
|
||||
},
|
||||
});
|
||||
|
||||
L.Path.addInitHook(function () {
|
||||
//@ts-ignore
|
||||
this.dragging = new L.Handler.PathDrag(this);
|
||||
|
||||
if (this.options.draggable) {
|
||||
this.once("add", function () {
|
||||
this.dragging.enable();
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -3,13 +3,7 @@ import { getApp } from "../olympusapp";
|
||||
import { BoxSelect } from "./boxselect";
|
||||
import { Airbase } from "../mission/airbase";
|
||||
import { Unit } from "../unit/unit";
|
||||
import {
|
||||
areaContains,
|
||||
circleContains,
|
||||
deg2rad,
|
||||
getGroundElevation,
|
||||
polyContains,
|
||||
} from "../other/utils";
|
||||
import { areaContains, deg2rad, getFunctionArguments, getGroundElevation } from "../other/utils";
|
||||
import { TemporaryUnitMarker } from "./markers/temporaryunitmarker";
|
||||
import { ClickableMiniMap } from "./clickableminimap";
|
||||
import {
|
||||
@@ -38,12 +32,14 @@ import "./markers/stylesheets/units.css";
|
||||
import "./map.css";
|
||||
import { CoalitionCircle } from "./coalitionarea/coalitioncircle";
|
||||
|
||||
import "leaflet-path-drag";
|
||||
import { faLeftLong } from "@fortawesome/free-solid-svg-icons";
|
||||
import { initDraggablePath } from "./coalitionarea/draggablepath";
|
||||
import { faDrawPolygon, faJetFighter, faMap } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
/* Register the handler for the box selection */
|
||||
L.Map.addInitHook("addHandler", "boxSelect", BoxSelect);
|
||||
|
||||
initDraggablePath();
|
||||
|
||||
export class Map extends L.Map {
|
||||
/* Options */
|
||||
#options: MapOptions = MAP_OPTIONS_DEFAULTS;
|
||||
@@ -413,63 +409,83 @@ export class Map extends L.Map {
|
||||
return [
|
||||
{
|
||||
actions: ["Tap"],
|
||||
target: faJetFighter,
|
||||
text: "Select unit",
|
||||
},
|
||||
{
|
||||
actions: ["Shift", "Drag"],
|
||||
target: faMap,
|
||||
text: "Box selection",
|
||||
},
|
||||
{
|
||||
actions: ["Press", "Drag"],
|
||||
target: faMap,
|
||||
text: "Box selection",
|
||||
},
|
||||
{
|
||||
actions: ["Drag"],
|
||||
target: faMap,
|
||||
text: "Move map location",
|
||||
},
|
||||
{
|
||||
actions: ["Long tap", "Drag"],
|
||||
text: "Box selection",
|
||||
},
|
||||
];
|
||||
} else if (this.#state === SPAWN_UNIT) {
|
||||
return [
|
||||
{
|
||||
actions: ["Tap"],
|
||||
target: faMap,
|
||||
text: "Spawn unit",
|
||||
},
|
||||
{
|
||||
actions: ["Double tap"],
|
||||
target: faMap,
|
||||
text: "Exit spawn mode",
|
||||
},
|
||||
{
|
||||
actions: ["Drag"],
|
||||
target: faMap,
|
||||
text: "Move map location",
|
||||
},
|
||||
];
|
||||
} else if (this.#state === CONTEXT_ACTION) {
|
||||
return [
|
||||
{
|
||||
actions: ["Tap"],
|
||||
text: this.#contextAction?.getLabel() ?? "",
|
||||
},
|
||||
let controls = [
|
||||
{
|
||||
actions: ["Double tap"],
|
||||
text: "Exit action mode",
|
||||
target: faMap,
|
||||
text: "Deselect units",
|
||||
},
|
||||
{
|
||||
actions: ["Drag"],
|
||||
target: faMap,
|
||||
text: "Move map location",
|
||||
},
|
||||
];
|
||||
|
||||
if (this.#contextAction) {
|
||||
/* TODO: I don't like this approach, it relies on the arguments names of the callback. We should find a better method */
|
||||
const args = getFunctionArguments(this.#contextAction.getCallback());
|
||||
controls.push({
|
||||
actions: ["Tap"],
|
||||
target: args.includes("targetUnit")? faJetFighter: faMap,
|
||||
text: this.#contextAction?.getLabel() ?? "",
|
||||
});
|
||||
}
|
||||
|
||||
return controls;
|
||||
} else if (this.#state === COALITIONAREA_EDIT) {
|
||||
return [
|
||||
{
|
||||
actions: ["Tap"],
|
||||
target: faDrawPolygon,
|
||||
text: "Select shape",
|
||||
},
|
||||
{
|
||||
actions: ["Double tap"],
|
||||
target: faMap,
|
||||
text: "Exit drawing mode",
|
||||
},
|
||||
{
|
||||
actions: ["Drag"],
|
||||
target: faMap,
|
||||
text: "Move map location",
|
||||
},
|
||||
];
|
||||
@@ -477,14 +493,17 @@ export class Map extends L.Map {
|
||||
return [
|
||||
{
|
||||
actions: ["Tap"],
|
||||
target: faMap,
|
||||
text: "Add vertex to polygon",
|
||||
},
|
||||
{
|
||||
actions: ["Double tap"],
|
||||
target: faMap,
|
||||
text: "Finalize polygon",
|
||||
},
|
||||
{
|
||||
actions: ["Drag"],
|
||||
target: faMap,
|
||||
text: "Move map location",
|
||||
},
|
||||
];
|
||||
@@ -492,10 +511,12 @@ export class Map extends L.Map {
|
||||
return [
|
||||
{
|
||||
actions: ["Tap"],
|
||||
target: faMap,
|
||||
text: "Add circle",
|
||||
},
|
||||
{
|
||||
actions: ["Drag"],
|
||||
target: faMap,
|
||||
text: "Move map location",
|
||||
},
|
||||
];
|
||||
@@ -801,7 +822,8 @@ export class Map extends L.Map {
|
||||
|
||||
this.#longPressTimer = window.setTimeout(() => {
|
||||
/* If the mouse is still being pressed, execute the long press action */
|
||||
if (this.#isMouseDown) this.#onLongPress(e);
|
||||
if (this.#isMouseDown && !this.#isDragging && !this.#isZooming)
|
||||
this.#onLongPress(e);
|
||||
}, 500);
|
||||
}
|
||||
|
||||
@@ -822,15 +844,20 @@ export class Map extends L.Map {
|
||||
}
|
||||
|
||||
#onShortPress(e: any) {
|
||||
console.log(`Short press at ${e.latlng}`);
|
||||
let touchLocation: L.LatLng;
|
||||
if (e.type === "touchstart")
|
||||
touchLocation = this.containerPointToLatLng(
|
||||
this.mouseEventToContainerPoint(e.touches[0])
|
||||
);
|
||||
else touchLocation = new L.LatLng(e.latlng.lat, e.latlng.lng);
|
||||
|
||||
const location = new L.LatLng(e.latlng.lat, e.latlng.lng);
|
||||
console.log(`Short press at ${touchLocation}`);
|
||||
|
||||
/* Execute the short click action */
|
||||
if (this.#state === IDLE) {
|
||||
} else if (this.#state === SPAWN_UNIT) {
|
||||
if (this.#spawnRequestTable !== null) {
|
||||
this.#spawnRequestTable.unit.location = location;
|
||||
this.#spawnRequestTable.unit.location = touchLocation;
|
||||
getApp()
|
||||
.getUnitsManager()
|
||||
.spawnUnits(
|
||||
@@ -842,7 +869,7 @@ export class Map extends L.Map {
|
||||
undefined,
|
||||
(hash) => {
|
||||
this.addTemporaryMarker(
|
||||
location,
|
||||
touchLocation,
|
||||
this.#spawnRequestTable?.unit.unitType ?? "unknown",
|
||||
this.#spawnRequestTable?.coalition ?? "blue",
|
||||
hash
|
||||
@@ -853,7 +880,7 @@ export class Map extends L.Map {
|
||||
} else if (this.#state === COALITIONAREA_DRAW_POLYGON) {
|
||||
const selectedArea = this.getSelectedCoalitionArea();
|
||||
if (selectedArea && selectedArea instanceof CoalitionPolygon) {
|
||||
selectedArea.addTemporaryLatLng(location);
|
||||
selectedArea.addTemporaryLatLng(touchLocation);
|
||||
}
|
||||
} else if (this.#state === COALITIONAREA_DRAW_CIRCLE) {
|
||||
const selectedArea = this.getSelectedCoalitionArea();
|
||||
@@ -862,13 +889,13 @@ export class Map extends L.Map {
|
||||
selectedArea.getLatLng().lat == 0 &&
|
||||
selectedArea.getLatLng().lng == 0
|
||||
)
|
||||
selectedArea.setLatLng(location);
|
||||
selectedArea.setLatLng(touchLocation);
|
||||
this.setState(COALITIONAREA_EDIT);
|
||||
}
|
||||
} else if (this.#state == COALITIONAREA_EDIT) {
|
||||
this.deselectAllCoalitionAreas();
|
||||
for (let idx = 0; idx < this.#coalitionAreas.length; idx++) {
|
||||
if (areaContains(e.latlng, this.#coalitionAreas[idx])) {
|
||||
if (areaContains(touchLocation, this.#coalitionAreas[idx])) {
|
||||
this.#coalitionAreas[idx].setSelected(true);
|
||||
document.dispatchEvent(
|
||||
new CustomEvent("coalitionAreaSelected", {
|
||||
@@ -879,18 +906,24 @@ export class Map extends L.Map {
|
||||
}
|
||||
}
|
||||
} else if (this.#state === CONTEXT_ACTION) {
|
||||
this.executeContextAction(null, e.latlng);
|
||||
this.executeContextAction(null, touchLocation);
|
||||
} else {
|
||||
}
|
||||
}
|
||||
|
||||
#onLongPress(e: any) {
|
||||
console.log(`Long press at ${e.latlng}`);
|
||||
let touchLocation: L.LatLng;
|
||||
if (e.type === "touchstart")
|
||||
touchLocation = this.containerPointToLatLng(
|
||||
this.mouseEventToContainerPoint(e.touches[0])
|
||||
);
|
||||
else touchLocation = new L.LatLng(e.latlng.lat, e.latlng.lng);
|
||||
|
||||
this.deselectAllCoalitionAreas();
|
||||
console.log(`Long press at ${touchLocation}`);
|
||||
|
||||
if (!this.#isDragging && !this.#isZooming) {
|
||||
if (this.#state == IDLE) {
|
||||
this.deselectAllCoalitionAreas();
|
||||
if (this.#state === IDLE) {
|
||||
if (e.type === "touchstart")
|
||||
document.dispatchEvent(
|
||||
new CustomEvent("mapForceBoxSelect", { detail: e })
|
||||
@@ -908,7 +941,7 @@ export class Map extends L.Map {
|
||||
|
||||
this.#lastMousePosition.x = e.originalEvent.x;
|
||||
this.#lastMousePosition.y = e.originalEvent.y;
|
||||
this.#lastMouseCoordinates = this.mouseEventToLatLng(e.originalEvent);
|
||||
this.#lastMouseCoordinates = e.latlng;
|
||||
}
|
||||
|
||||
#onMapMove(e: any) {
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { Circle, LatLng, Polygon } from "leaflet";
|
||||
import * as turf from "@turf/turf";
|
||||
import { UnitDatabase } from "../unit/databases/unitdatabase";
|
||||
import {
|
||||
aircraftDatabase,
|
||||
} from "../unit/databases/aircraftdatabase";
|
||||
import { aircraftDatabase } from "../unit/databases/aircraftdatabase";
|
||||
import { helicopterDatabase } from "../unit/databases/helicopterdatabase";
|
||||
import { groundUnitDatabase } from "../unit/databases/groundunitdatabase";
|
||||
import {
|
||||
@@ -317,33 +315,52 @@ export function nmToFt(nm: number) {
|
||||
}
|
||||
|
||||
export function areaContains(latlng: LatLng, area: Polygon | Circle) {
|
||||
if (area instanceof Polygon)
|
||||
return polyContains(latlng, area);
|
||||
else
|
||||
return circleContains(latlng, area);
|
||||
if (area instanceof Polygon) return polyContains(latlng, area);
|
||||
else return circleContains(latlng, area);
|
||||
}
|
||||
|
||||
export function polyContains(latlng: LatLng, polygon: Polygon) {
|
||||
let coordinates = [(polygon.getLatLngs()[0] as LatLng[]).map((latlng) => {return [latlng.lng, latlng.lat]} )];
|
||||
coordinates[0].push([polygon.getLatLngs()[0][0].lng, polygon.getLatLngs()[0][0].lat])
|
||||
let coordinates = [
|
||||
(polygon.getLatLngs()[0] as LatLng[]).map((latlng) => {
|
||||
return [latlng.lng, latlng.lat];
|
||||
}),
|
||||
];
|
||||
coordinates[0].push([
|
||||
polygon.getLatLngs()[0][0].lng,
|
||||
polygon.getLatLngs()[0][0].lat,
|
||||
]);
|
||||
const poly = turf.polygon(coordinates);
|
||||
return turf.inside(turf.point([latlng.lng, latlng.lat]), poly);
|
||||
}
|
||||
|
||||
export function circleContains(latlng: LatLng, circle: Circle) {
|
||||
const poly = turf.circle(turf.point([circle.getLatLng().lng, circle.getLatLng().lat]), circle.getRadius() / 1000, 100, 'kilometers');
|
||||
const poly = turf.circle(
|
||||
turf.point([circle.getLatLng().lng, circle.getLatLng().lat]),
|
||||
circle.getRadius() / 1000,
|
||||
100,
|
||||
"kilometers"
|
||||
);
|
||||
return turf.inside(turf.point([latlng.lng, latlng.lat]), poly);
|
||||
}
|
||||
|
||||
export function polyCenter(polygon: Polygon) {
|
||||
let coordinates = [(polygon.getLatLngs()[0] as LatLng[]).map((latlng) => {return [latlng.lng, latlng.lat]} )];
|
||||
coordinates[0].push([polygon.getLatLngs()[0][0].lng, polygon.getLatLngs()[0][0].lat])
|
||||
let coordinates = [
|
||||
(polygon.getLatLngs()[0] as LatLng[]).map((latlng) => {
|
||||
return [latlng.lng, latlng.lat];
|
||||
}),
|
||||
];
|
||||
coordinates[0].push([
|
||||
polygon.getLatLngs()[0][0].lng,
|
||||
polygon.getLatLngs()[0][0].lat,
|
||||
]);
|
||||
const poly = turf.polygon(coordinates);
|
||||
const center = turf.center(featureCollection([poly]));
|
||||
return new LatLng(center.geometry.coordinates[1], center.geometry.coordinates[0]);
|
||||
return new LatLng(
|
||||
center.geometry.coordinates[1],
|
||||
center.geometry.coordinates[0]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export function randomPointInPoly(polygon: Polygon): LatLng {
|
||||
var bounds = polygon.getBounds();
|
||||
var x_min = bounds.getEast();
|
||||
@@ -354,8 +371,15 @@ export function randomPointInPoly(polygon: Polygon): LatLng {
|
||||
var lat = y_min + Math.random() * (y_max - y_min);
|
||||
var lng = x_min + Math.random() * (x_max - x_min);
|
||||
|
||||
let coordinates = [(polygon.getLatLngs()[0] as LatLng[]).map((latlng) => {return [latlng.lng, latlng.lat]} )];
|
||||
coordinates[0].push([polygon.getLatLngs()[0][0].lng, polygon.getLatLngs()[0][0].lat])
|
||||
let coordinates = [
|
||||
(polygon.getLatLngs()[0] as LatLng[]).map((latlng) => {
|
||||
return [latlng.lng, latlng.lat];
|
||||
}),
|
||||
];
|
||||
coordinates[0].push([
|
||||
polygon.getLatLngs()[0][0].lng,
|
||||
polygon.getLatLngs()[0][0].lat,
|
||||
]);
|
||||
const poly = turf.polygon(coordinates);
|
||||
var inside = turf.inside(turf.point([lng, lat]), poly);
|
||||
|
||||
@@ -565,3 +589,15 @@ export function getWikipediaEntry(search: string, callback: CallableFunction) {
|
||||
};
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
export function getFunctionArguments(func) {
|
||||
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/gm;
|
||||
var ARGUMENT_NAMES = /([^\s,]+)/g;
|
||||
|
||||
var fnStr = func.toString().replace(STRIP_COMMENTS, "");
|
||||
var result = fnStr
|
||||
.slice(fnStr.indexOf("(") + 1, fnStr.indexOf(")"))
|
||||
.match(ARGUMENT_NAMES);
|
||||
if (result === null) result = [];
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
import React, { useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { IconDefinition } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
export function ControlsPanel(props: {}) {
|
||||
const [controls, setControls] = useState(
|
||||
[] as { actions: string[]; text: string }[]
|
||||
[] as { actions: string[]; target: IconDefinition, text: string }[]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (getApp() && controls.length === 0) {
|
||||
setControls(getApp().getMap().getCurrentControls());
|
||||
}
|
||||
})
|
||||
|
||||
document.addEventListener("mapStateChanged", (ev) => {
|
||||
setControls(getApp().getMap().getCurrentControls());
|
||||
});
|
||||
@@ -11,7 +20,7 @@ export function ControlsPanel(props: {}) {
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
absolute bottom-[20px] right-[10px] w-[288px] z-ui-0 flex flex-col
|
||||
absolute bottom-[20px] right-[10px] w-80 z-ui-0 flex flex-col
|
||||
items-center justify-between gap-1 p-3 text-sm
|
||||
`}
|
||||
>
|
||||
@@ -19,29 +28,32 @@ export function ControlsPanel(props: {}) {
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
flex w-full justify-between gap-2 rounded-full px-2 py-1
|
||||
flex w-full justify-between gap-2 rounded-full py-1 pl-4 pr-2
|
||||
backdrop-blur-lg
|
||||
dark:bg-olympus-800/90 dark:text-gray-200
|
||||
`}
|
||||
>
|
||||
<div className="my-auto">{control.text}</div>
|
||||
<div className="my-auto overflow-hidden text-nowrap">{control.text}</div>
|
||||
<FontAwesomeIcon icon={control.target} className="my-auto ml-auto"/>
|
||||
<div className="flex gap-1">
|
||||
{control.actions.map((action, idx) => {
|
||||
return (
|
||||
<>
|
||||
{<div>+</div>}
|
||||
<div
|
||||
className={`
|
||||
rounded-full bg-olympus-500 px-1 py-0.5 text-sm
|
||||
rounded-full bg-olympus-500 px-2 py-0.5 text-sm
|
||||
font-bold text-white
|
||||
`}
|
||||
>
|
||||
{action}
|
||||
</div>
|
||||
{idx !== control.actions.length - 1 && <div>+</div>}
|
||||
|
||||
</>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Menu } from "./components/menu";
|
||||
import { FaQuestionCircle, FaRegCircle } from "react-icons/fa";
|
||||
import { FaQuestionCircle, FaRegCircle, FaTrash } from "react-icons/fa";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import {
|
||||
COALITIONAREA_DRAW_CIRCLE,
|
||||
@@ -62,6 +62,19 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
|
||||
setAreaCoalition(activeCoalitionArea?.getCoalition());
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!props.open) {
|
||||
if (
|
||||
[
|
||||
COALITIONAREA_EDIT,
|
||||
COALITIONAREA_DRAW_CIRCLE,
|
||||
COALITIONAREA_DRAW_POLYGON,
|
||||
].includes(getApp()?.getMap()?.getState())
|
||||
)
|
||||
getApp().getMap().setState(IDLE);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("mapStateChanged", (event: any) => {
|
||||
if (
|
||||
drawingPolygon &&
|
||||
@@ -147,7 +160,8 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
|
||||
Click on the map to add a new circle.
|
||||
</div>
|
||||
<div className="text-gray-400">
|
||||
You can drag the circle to move it and you can use the handle to set the radius.
|
||||
You can drag the circle to move it and you can use the handle to
|
||||
set the radius.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -197,11 +211,21 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) {
|
||||
text-gray-200
|
||||
`}
|
||||
>
|
||||
<div className="my-auto text-md">Area label </div>
|
||||
<div className="my-auto flex justify-between text-md">
|
||||
Area label
|
||||
<div className="rounded-md bg-red-800 p-2" onClick={() => {
|
||||
getApp().getMap().deleteCoalitionArea(activeCoalitionArea);
|
||||
setActiveCoalitionArea(null);
|
||||
}}>
|
||||
<FaTrash
|
||||
className={`text-gray-50`}
|
||||
></FaTrash>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
className={`
|
||||
block max-w-80 flex-grow rounded-lg border border-gray-300
|
||||
block w-full flex-grow rounded-lg border border-gray-300
|
||||
bg-gray-50 p-2.5 text-sm text-gray-900
|
||||
dark:border-gray-600 dark:bg-gray-700 dark:text-white
|
||||
dark:placeholder-gray-400 dark:focus:border-blue-500
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from "react";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
OlRoundStateButton,
|
||||
OlStateButton,
|
||||
@@ -35,10 +35,18 @@ export function Header() {
|
||||
const [scrolledLeft, setScrolledLeft] = useState(true);
|
||||
const [scrolledRight, setScrolledRight] = useState(false);
|
||||
|
||||
function onScroll(ev) {
|
||||
const sl = ev.target.scrollLeft;
|
||||
/* Initialize the "scroll" position of the element */
|
||||
var scrollRef = useRef(null);
|
||||
useEffect(() => {
|
||||
if (scrollRef.current) {
|
||||
onScroll(scrollRef.current);
|
||||
}
|
||||
});
|
||||
|
||||
function onScroll(el) {
|
||||
const sl = el.scrollLeft;
|
||||
const sr =
|
||||
ev.target.scrollWidth - ev.target.scrollLeft - ev.target.clientWidth;
|
||||
el.scrollWidth - el.scrollLeft - el.clientWidth;
|
||||
|
||||
sl < 1 && !scrolledLeft && setScrolledLeft(true);
|
||||
sl > 1 && scrolledLeft && setScrolledLeft(false);
|
||||
@@ -77,7 +85,8 @@ export function Header() {
|
||||
my-2 flex w-full items-center gap-3 overflow-x-scroll
|
||||
no-scrollbar
|
||||
`}
|
||||
onScroll={(ev) => onScroll(ev)}
|
||||
onScroll={(ev) => onScroll(ev.target)}
|
||||
ref={scrollRef}
|
||||
>
|
||||
<div
|
||||
className={`
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from "react";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { Unit } from "../../unit/unit";
|
||||
import { ContextActionSet } from "../../unit/contextactionset";
|
||||
import { OlStateButton } from "../components/olstatebutton";
|
||||
@@ -20,6 +20,14 @@ export function UnitMouseControlBar(props: {}) {
|
||||
const [scrolledLeft, setScrolledLeft] = useState(true);
|
||||
const [scrolledRight, setScrolledRight] = useState(false);
|
||||
|
||||
/* Initialize the "scroll" position of the element */
|
||||
var scrollRef = useRef(null);
|
||||
useEffect(() => {
|
||||
if (scrollRef.current) {
|
||||
onScroll(scrollRef.current);
|
||||
}
|
||||
});
|
||||
|
||||
/* When a unit is selected, open the menu */
|
||||
document.addEventListener("unitsSelection", (ev: CustomEventInit) => {
|
||||
setOpen(true);
|
||||
@@ -61,10 +69,10 @@ export function UnitMouseControlBar(props: {}) {
|
||||
setActiveContextAction(null);
|
||||
}
|
||||
|
||||
function onScroll(ev) {
|
||||
const sl = ev.target.scrollLeft;
|
||||
function onScroll(el) {
|
||||
const sl = el.scrollLeft;
|
||||
const sr =
|
||||
ev.target.scrollWidth - ev.target.scrollLeft - ev.target.clientWidth;
|
||||
el.scrollWidth - el.scrollLeft - el.clientWidth;
|
||||
|
||||
sl < 1 && !scrolledLeft && setScrolledLeft(true);
|
||||
sl > 1 && scrolledLeft && setScrolledLeft(false);
|
||||
@@ -96,7 +104,8 @@ export function UnitMouseControlBar(props: {}) {
|
||||
)}
|
||||
<div
|
||||
className="flex gap-2 overflow-x-auto no-scrollbar p-2"
|
||||
onScroll={(ev) => onScroll(ev)}
|
||||
onScroll={(ev) => onScroll(ev.target)}
|
||||
ref={scrollRef}
|
||||
>
|
||||
{Object.values(contextActionsSet.getContextActions()).map(
|
||||
(contextAction) => {
|
||||
|
||||
@@ -892,7 +892,7 @@ export abstract class Unit extends CustomMarker {
|
||||
contextActionSet.addContextAction(
|
||||
this,
|
||||
"move",
|
||||
"Move",
|
||||
"Set destination",
|
||||
"Click on the map to move the units there",
|
||||
faLocationDot,
|
||||
(units: Unit[], _, targetPosition) => {
|
||||
@@ -905,7 +905,7 @@ export abstract class Unit extends CustomMarker {
|
||||
contextActionSet.addContextAction(
|
||||
this,
|
||||
"path",
|
||||
"Path",
|
||||
"Append destination",
|
||||
"Click on the map to add a destination to the path",
|
||||
faRoute,
|
||||
(units: Unit[], _, targetPosition) => {
|
||||
@@ -2190,7 +2190,7 @@ export abstract class AirUnit extends Unit {
|
||||
contextActionSet.addContextAction(
|
||||
this,
|
||||
"refuel",
|
||||
"Refuel",
|
||||
"Refuel at tanker",
|
||||
"Refuel units at the nearest AAR Tanker. If no tanker is available the unit will RTB",
|
||||
olStatesRefuel,
|
||||
(units: Unit[]) => {
|
||||
@@ -2220,7 +2220,7 @@ export abstract class AirUnit extends Unit {
|
||||
(
|
||||
units: Unit[],
|
||||
targetUnit: Unit | null,
|
||||
targetPosition: LatLng | null
|
||||
_
|
||||
) => {
|
||||
if (targetUnit)
|
||||
getApp().getUnitsManager().attackUnit(targetUnit.ID, units);
|
||||
@@ -2235,7 +2235,7 @@ export abstract class AirUnit extends Unit {
|
||||
(
|
||||
units: Unit[],
|
||||
targetUnit: Unit | null,
|
||||
targetPosition: LatLng | null
|
||||
_
|
||||
) => {
|
||||
if (targetUnit) targetUnit.showFollowOptions(units);
|
||||
}
|
||||
@@ -2245,12 +2245,12 @@ export abstract class AirUnit extends Unit {
|
||||
contextActionSet.addContextAction(
|
||||
this,
|
||||
"bomb",
|
||||
"Precision bombing",
|
||||
"Precision bomb location",
|
||||
"Click on a point to execute a precision bombing attack",
|
||||
faLocationCrosshairs,
|
||||
(
|
||||
units: Unit[],
|
||||
targetUnit: Unit | null,
|
||||
_,
|
||||
targetPosition: LatLng | null
|
||||
) => {
|
||||
if (targetPosition)
|
||||
@@ -2260,12 +2260,12 @@ export abstract class AirUnit extends Unit {
|
||||
contextActionSet.addContextAction(
|
||||
this,
|
||||
"carpet-bomb",
|
||||
"Carpet bombing",
|
||||
"Carpet bomb location",
|
||||
"Click on a point to execute a carpet bombing attack",
|
||||
faXmarksLines,
|
||||
(
|
||||
units: Unit[],
|
||||
targetUnit: Unit | null,
|
||||
_,
|
||||
targetPosition: LatLng | null
|
||||
) => {
|
||||
if (targetPosition)
|
||||
@@ -2311,12 +2311,12 @@ export class Helicopter extends AirUnit {
|
||||
contextActionSet.addContextAction(
|
||||
this,
|
||||
"land-at-point",
|
||||
"Land here",
|
||||
"Land at location",
|
||||
"Click on a point to land there",
|
||||
olIconsLandAtPoint,
|
||||
(
|
||||
units: Unit[],
|
||||
targetUnit: Unit | null,
|
||||
_,
|
||||
targetPosition: LatLng | null
|
||||
) => {
|
||||
if (targetPosition)
|
||||
@@ -2372,8 +2372,8 @@ export class GroundUnit extends Unit {
|
||||
faPeopleGroup,
|
||||
(
|
||||
units: Unit[],
|
||||
targetUnit: Unit | null,
|
||||
targetPosition: LatLng | null
|
||||
_1,
|
||||
_2
|
||||
) => {
|
||||
getApp().getUnitsManager().createGroup(units);
|
||||
},
|
||||
@@ -2426,7 +2426,7 @@ export class GroundUnit extends Unit {
|
||||
(
|
||||
units: Unit[],
|
||||
targetUnit: Unit | null,
|
||||
targetPosition: LatLng | null
|
||||
_
|
||||
) => {
|
||||
if (targetUnit)
|
||||
getApp().getUnitsManager().attackUnit(targetUnit.ID, units);
|
||||
@@ -2443,7 +2443,7 @@ export class GroundUnit extends Unit {
|
||||
faLocationCrosshairs,
|
||||
(
|
||||
units: Unit[],
|
||||
targetUnit: Unit | null,
|
||||
_,
|
||||
targetPosition: LatLng | null
|
||||
) => {
|
||||
if (targetPosition)
|
||||
@@ -2458,7 +2458,7 @@ export class GroundUnit extends Unit {
|
||||
olButtonsContextSimulateFireFight,
|
||||
(
|
||||
units: Unit[],
|
||||
targetUnit: Unit | null,
|
||||
_,
|
||||
targetPosition: LatLng | null
|
||||
) => {
|
||||
if (targetPosition)
|
||||
@@ -2577,8 +2577,8 @@ export class NavyUnit extends Unit {
|
||||
faQuestionCircle,
|
||||
(
|
||||
units: Unit[],
|
||||
targetUnit: Unit | null,
|
||||
targetPosition: LatLng | null
|
||||
_1,
|
||||
_2
|
||||
) => {
|
||||
getApp().getUnitsManager().createGroup(units);
|
||||
},
|
||||
@@ -2606,7 +2606,7 @@ export class NavyUnit extends Unit {
|
||||
(
|
||||
units: Unit[],
|
||||
targetUnit: Unit | null,
|
||||
targetPosition: LatLng | null
|
||||
_
|
||||
) => {
|
||||
if (targetUnit)
|
||||
getApp().getUnitsManager().attackUnit(targetUnit.ID, units);
|
||||
@@ -2622,7 +2622,7 @@ export class NavyUnit extends Unit {
|
||||
faQuestionCircle,
|
||||
(
|
||||
units: Unit[],
|
||||
targetUnit: Unit | null,
|
||||
_,
|
||||
targetPosition: LatLng | null
|
||||
) => {
|
||||
if (targetPosition)
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
import { CoalitionPolygon } from "../map/coalitionarea/coalitionpolygon";
|
||||
import { groundUnitDatabase } from "./databases/groundunitdatabase";
|
||||
import {
|
||||
CONTEXT_ACTION,
|
||||
DELETE_CYCLE_TIME,
|
||||
DELETE_SLOW_THRESHOLD,
|
||||
DataIndexes,
|
||||
@@ -1953,6 +1954,7 @@ export class UnitsManager {
|
||||
|
||||
#onUnitSelection(unit: Unit) {
|
||||
if (this.getSelectedUnits().length > 0) {
|
||||
getApp().getMap().setState(CONTEXT_ACTION);
|
||||
/* Disable the firing of the selection event for a certain amount of time. This avoids firing many events if many units are selected */
|
||||
if (!this.#selectionEventDisabled) {
|
||||
window.setTimeout(() => {
|
||||
|
||||
Reference in New Issue
Block a user