diff --git a/frontend/react/src/events.ts b/frontend/react/src/events.ts index bc29b7ea..87e01fb4 100644 --- a/frontend/react/src/events.ts +++ b/frontend/react/src/events.ts @@ -229,6 +229,19 @@ export class CoalitionAreaSelectedEvent { } } +export class CoalitionAreasChangedEvent { + static on(callback: (coalitionAreas: (CoalitionCircle | CoalitionPolygon)[]) => void) { + document.addEventListener(this.name, (ev: CustomEventInit) => { + callback(ev.detail.coalitionAreas); + }); + } + + static dispatch(coalitionAreas: (CoalitionCircle | CoalitionPolygon)[]) { + document.dispatchEvent(new CustomEvent(this.name, { detail: { coalitionAreas } })); + console.log(`Event ${this.name} dispatched`); + } +} + export class AirbaseSelectedEvent { static on(callback: (airbase: Airbase | null) => void) { document.addEventListener(this.name, (ev: CustomEventInit) => { diff --git a/frontend/react/src/map/coalitionarea/coalitionareamanager.ts b/frontend/react/src/map/coalitionarea/coalitionareamanager.ts new file mode 100644 index 00000000..186d44b2 --- /dev/null +++ b/frontend/react/src/map/coalitionarea/coalitionareamanager.ts @@ -0,0 +1,121 @@ +import { LatLng, LeafletMouseEvent } from "leaflet"; +import { DrawSubState, OlympusState } from "../../constants/constants"; +import { AppStateChangedEvent, CoalitionAreasChangedEvent, CoalitionAreaSelectedEvent } from "../../events"; +import { getApp } from "../../olympusapp"; +import { areaContains } from "../../other/utils"; +import { CoalitionCircle } from "./coalitioncircle"; +import { CoalitionPolygon } from "./coalitionpolygon"; + +export class CoalitionAreasManager { + /* Coalition areas drawing */ + #areas: (CoalitionPolygon | CoalitionCircle)[] = []; + #selectedArea: CoalitionCircle | CoalitionPolygon | null = null; + + constructor() { + AppStateChangedEvent.on((state, subState) => { + /* State changes can't happen inside a AppStateChanged handler. Use a timeout */ + window.setTimeout(() => { + this.getSelectedArea()?.setCreating(false); + + if (state !== OlympusState.DRAW || (state === OlympusState.DRAW && subState === DrawSubState.NO_SUBSTATE)) { + this.setSelectedArea(null); + } else { + /* If we are editing but no area is selected, revert to no substate */ + if (subState === DrawSubState.EDIT && this.getSelectedArea() === null) getApp().setState(OlympusState.DRAW); + else { + /* Handle creation of new area */ + let newArea: CoalitionCircle | CoalitionPolygon | null = null; + if (subState == DrawSubState.DRAW_POLYGON) newArea = new CoalitionPolygon([]); + else if (subState === DrawSubState.DRAW_CIRCLE) newArea = new CoalitionCircle(new LatLng(0, 0), { radius: 1000 }); + + if (newArea) { + this.setSelectedArea(newArea); + this.#areas.push(newArea); + CoalitionAreasChangedEvent.dispatch(this.#areas); + } + } + } + }, 200); + }); + } + + setSelectedArea(area: CoalitionCircle | CoalitionPolygon | null) { + this.#selectedArea?.setSelected(this.#selectedArea === area); + area?.setSelected(true); + this.#selectedArea = area; + CoalitionAreaSelectedEvent.dispatch(area); + } + + deleteCoalitionArea(area: CoalitionPolygon | CoalitionCircle) { + if (!this.#areas.includes(area)) return; + + if (area === this.getSelectedArea()) this.setSelectedArea(null); + + this.#areas.splice(this.#areas.indexOf(area), 1); + if (getApp().getMap().hasLayer(area)) getApp().getMap().removeLayer(area); + CoalitionAreasChangedEvent.dispatch(this.#areas); + } + + moveAreaUp(area: CoalitionPolygon | CoalitionCircle) { + if (!this.#areas.includes(area)) return; + + const idx = this.#areas.indexOf(area); + + if (idx === 0) return; + + this.#areas.forEach((coalitionArea) => getApp().getMap().removeLayer(coalitionArea)); + this.#areas.splice(this.#areas.indexOf(area), 1); + this.#areas = [...this.#areas.slice(0, idx - 1), area, ...this.#areas.slice(idx - 1)]; + this.#areas.forEach((coalitionArea) => getApp().getMap().addLayer(coalitionArea)); + CoalitionAreasChangedEvent.dispatch(this.#areas); + } + + moveCoalitionAreaDown(area: CoalitionPolygon | CoalitionCircle) { + if (!this.#areas.includes(area)) return; + + const idx = this.#areas.indexOf(area); + + if (idx === this.#areas.length - 1) return; + + this.#areas.forEach((coalitionArea) => getApp().getMap().removeLayer(coalitionArea)); + this.#areas.splice(this.#areas.indexOf(area), 1); + this.#areas = [...this.#areas.slice(0, idx + 1), area, ...this.#areas.slice(idx + 1)]; + this.#areas.forEach((coalitionArea) => getApp().getMap().addLayer(coalitionArea)); + CoalitionAreasChangedEvent.dispatch(this.#areas); + } + + getSelectedArea() { + return this.#areas.find((coalitionArea: CoalitionPolygon | CoalitionCircle) => coalitionArea.getSelected()) ?? null; + } + + onLeftShortClick(e: LeafletMouseEvent) { + const selectedArea = this.getSelectedArea(); + if (getApp().getSubState() === DrawSubState.DRAW_POLYGON) { + if (selectedArea && selectedArea instanceof CoalitionPolygon) selectedArea.addTemporaryLatLng(e.latlng); + } else if (getApp().getSubState() === DrawSubState.DRAW_CIRCLE) { + if (selectedArea && selectedArea instanceof CoalitionCircle) { + if (selectedArea.getLatLng().lat == 0 && selectedArea.getLatLng().lng == 0) selectedArea.setLatLng(e.latlng); + getApp().setState(OlympusState.DRAW, DrawSubState.EDIT); + } + } else { + let wasAreaSelected = this.getSelectedArea() !== null; + + this.setSelectedArea(null); + for (let idx = 0; idx < this.#areas.length; idx++) { + if (areaContains(e.latlng, this.#areas[idx])) { + this.setSelectedArea(this.#areas[idx]); + getApp().setState(OlympusState.DRAW, DrawSubState.EDIT); + break; + } + } + + if (this.getSelectedArea() === null) getApp().setState(wasAreaSelected ? OlympusState.DRAW : OlympusState.IDLE); + } + } + + onDoubleClick(e: LeafletMouseEvent) { + if (getApp().getSubState() === DrawSubState.DRAW_CIRCLE || getApp().getSubState() === DrawSubState.DRAW_POLYGON) + getApp().setState(OlympusState.DRAW, DrawSubState.EDIT); + else getApp().setState(OlympusState.DRAW); + } +} diff --git a/frontend/react/src/map/coalitionarea/coalitionareamiddlehandle.ts b/frontend/react/src/map/coalitionarea/coalitionareamiddlehandle.ts index 76d861fb..9cd4a650 100644 --- a/frontend/react/src/map/coalitionarea/coalitionareamiddlehandle.ts +++ b/frontend/react/src/map/coalitionarea/coalitionareamiddlehandle.ts @@ -1,9 +1,20 @@ -import { DivIcon, LatLng } from "leaflet"; +import { DivIcon, DomEvent, LatLng } from "leaflet"; import { CustomMarker } from "../markers/custommarker"; export class CoalitionAreaMiddleHandle extends CustomMarker { constructor(latlng: LatLng) { super(latlng, { interactive: true, draggable: false }); + + this.on("mousedown", (e: any) => { + DomEvent.stop(e); + DomEvent.preventDefault(e); + e.originalEvent.stopImmediatePropagation(); + }); + this.on("mouseup", (e: any) => { + DomEvent.stop(e); + DomEvent.preventDefault(e); + e.originalEvent.stopImmediatePropagation(); + }); } createIcon() { diff --git a/frontend/react/src/map/coalitionarea/coalitioncircle.ts b/frontend/react/src/map/coalitionarea/coalitioncircle.ts index d5675376..85b643f4 100644 --- a/frontend/react/src/map/coalitionarea/coalitioncircle.ts +++ b/frontend/react/src/map/coalitionarea/coalitioncircle.ts @@ -11,7 +11,7 @@ let totalCircles = 0; export class CoalitionCircle extends Circle { #coalition: Coalition = "blue"; #selected: boolean = true; - #editing: boolean = true; + #creating: boolean = true; #radiusHandle: CoalitionAreaHandle; #labelText: string; #label: Marker; @@ -39,9 +39,7 @@ export class CoalitionCircle extends Circle { this.#drawLabel(); }); - this.on("remove", () => { - this.#label.removeFrom(this._map); - }); + getApp().getMap().addLayer(this); } setCoalition(coalition: Coalition) { @@ -70,13 +68,16 @@ export class CoalitionCircle extends Circle { return this.#selected; } - setEditing(editing: boolean) { - this.#editing = editing; + setCreating(creating: boolean) { + this.#creating = creating; this.#setRadiusHandle(); + + /* Remove areas with less than 2 vertexes */ + if (this.getLatLng().lat === 0 && this.getLatLng().lng === 0) getApp().getCoalitionAreasManager().deleteCoalitionArea(this); } - getEditing() { - return this.#editing; + getCreating() { + return this.#creating; } setOpacity(opacity: number) { @@ -92,9 +93,16 @@ export class CoalitionCircle extends Circle { this.#drawLabel(); } + onAdd(map: Map): this { + super.onAdd(map); + this.#drawLabel(); + return this; + } + onRemove(map: Map): this { super.onRemove(map); - this.#radiusHandle.removeFrom(getApp().getMap()); + this.#label?.removeFrom(map); + this.#radiusHandle.removeFrom(map); return this; } @@ -130,9 +138,8 @@ export class CoalitionCircle extends Circle { } #drawLabel() { - if (this.#label) { - this.#label.removeFrom(this._map); - } + this.#label?.removeFrom(this._map); + this.#label = new Marker(this.getLatLng(), { icon: new DivIcon({ className: "label", diff --git a/frontend/react/src/map/coalitionarea/coalitionpolygon.ts b/frontend/react/src/map/coalitionarea/coalitionpolygon.ts index c24d5a47..e36afb01 100644 --- a/frontend/react/src/map/coalitionarea/coalitionpolygon.ts +++ b/frontend/react/src/map/coalitionarea/coalitionpolygon.ts @@ -12,7 +12,7 @@ let totalPolygons = 0; export class CoalitionPolygon extends Polygon { #coalition: Coalition = "blue"; #selected: boolean = true; - #editing: boolean = true; + #creating: boolean = true; #handles: CoalitionAreaHandle[] = []; #middleHandles: CoalitionAreaMiddleHandle[] = []; #activeIndex: number = 0; @@ -43,9 +43,7 @@ export class CoalitionPolygon extends Polygon { this.#drawLabel(); }); - this.on("remove", () => { - this.#label?.removeFrom(this._map); - }); + getApp().getMap().addLayer(this); } setCoalition(coalition: Coalition) { @@ -63,12 +61,12 @@ export class CoalitionPolygon extends Polygon { this.#setHandles(); this.#drawLabel(); this.setOpacity(selected ? 1 : 0.5); - if (!this.getSelected() && this.getEditing()) { + if (!this.getSelected() && this.getCreating()) { /* Remove the vertex we were working on */ var latlngs = this.getLatLngs()[0] as LatLng[]; latlngs.splice(this.#activeIndex, 1); this.setLatLngs(latlngs); - this.setEditing(false); + this.setCreating(false); } if (selected) CoalitionAreaSelectedEvent.dispatch(this); @@ -81,17 +79,17 @@ export class CoalitionPolygon extends Polygon { return this.#selected; } - setEditing(editing: boolean) { - this.#editing = editing; + setCreating(creating: boolean) { + this.#creating = creating; this.#setHandles(); var latlngs = this.getLatLngs()[0] as LatLng[]; /* Remove areas with less than 2 vertexes */ - if (latlngs.length <= 2) getApp().getMap().deleteCoalitionArea(this); + if (latlngs.length <= 2) getApp().getCoalitionAreasManager().deleteCoalitionArea(this); } - getEditing() { - return this.#editing; + getCreating() { + return this.#creating; } addTemporaryLatLng(latlng: LatLng) { @@ -115,9 +113,16 @@ export class CoalitionPolygon extends Polygon { this.#drawLabel(); } + onAdd(map: Map): this { + super.onAdd(map); + this.#drawLabel(); + return this; + } + onRemove(map: Map): this { super.onRemove(map); - this.#handles.concat(this.#middleHandles).forEach((handle: CoalitionAreaHandle | CoalitionAreaMiddleHandle) => handle.removeFrom(getApp().getMap())); + this.#label?.removeFrom(map); + this.#handles.concat(this.#middleHandles).forEach((handle: CoalitionAreaHandle | CoalitionAreaMiddleHandle) => handle.removeFrom(map)); return this; } @@ -176,7 +181,6 @@ export class CoalitionPolygon extends Polygon { const middleHandle = new CoalitionAreaMiddleHandle(middleLatLng); middleHandle.addTo(getApp().getMap()); middleHandle.on("click", (e: any) => { - getApp().getMap().preventClicks(); this.#activeIndex = idx - 1; this.addTemporaryLatLng(middleLatLng); }); diff --git a/frontend/react/src/map/map.ts b/frontend/react/src/map/map.ts index 3fca7f6f..0a02d15a 100644 --- a/frontend/react/src/map/map.ts +++ b/frontend/react/src/map/map.ts @@ -17,7 +17,6 @@ import { OlympusSubState, NO_SUBSTATE, SpawnSubState, - DrawSubState, JTACSubState, UnitControlSubState, ContextActionTarget, @@ -26,7 +25,6 @@ import { SHORT_PRESS_MILLISECONDS, DEBOUNCE_MILLISECONDS, } from "../constants/constants"; -import { CoalitionPolygon } from "./coalitionarea/coalitionpolygon"; import { MapHiddenTypes, MapOptions } from "../types/types"; import { EffectRequestTable, OlympusConfig, SpawnRequestTable } from "../interfaces"; import { ContextAction } from "../unit/contextaction"; @@ -36,7 +34,6 @@ import "./markers/stylesheets/airbase.css"; import "./markers/stylesheets/bullseye.css"; import "./markers/stylesheets/units.css"; import "./stylesheets/map.css"; -import { CoalitionCircle } from "./coalitionarea/coalitioncircle"; import { initDraggablePath } from "./coalitionarea/draggablepath"; import { ExplosionMarker } from "./markers/explosionmarker"; @@ -44,7 +41,6 @@ import { TextMarker } from "./markers/textmarker"; import { TargetMarker } from "./markers/targetmarker"; import { AppStateChangedEvent, - CoalitionAreaSelectedEvent, ConfigLoadedEvent, ContextActionChangedEvent, ContextActionSetChangedEvent, @@ -128,9 +124,6 @@ export class Map extends L.Map { #bradcastPositionXmlHttp: XMLHttpRequest | null = null; #cameraZoomRatio: number = 1.0; - /* Coalition areas drawing */ - #coalitionAreas: (CoalitionPolygon | CoalitionCircle)[] = []; - /* Units movement */ #destinationPreviewMarkers: { [key: number]: TemporaryUnitMarker | TargetMarker } = {}; #destinationRotation: number = 0; @@ -300,7 +293,7 @@ export class Map extends L.Map { window.addEventListener("blur", () => { this.setSelectionEnabled(false); this.setPasteEnabled(false); - }) + }); /* Pan interval */ this.#panInterval = window.setInterval(() => { @@ -604,27 +597,6 @@ export class Map extends L.Map { ContextActionChangedEvent.dispatch(this.#contextAction); } - deselectAllCoalitionAreas() { - if (this.getSelectedCoalitionArea() !== null) { - CoalitionAreaSelectedEvent.dispatch(null); - this.#coalitionAreas.forEach((coalitionArea: CoalitionPolygon | CoalitionCircle) => coalitionArea.setSelected(false)); - } - } - - deleteCoalitionArea(coalitionArea: CoalitionPolygon | CoalitionCircle) { - if (this.#coalitionAreas.includes(coalitionArea)) this.#coalitionAreas.splice(this.#coalitionAreas.indexOf(coalitionArea), 1); - - if (this.hasLayer(coalitionArea)) this.removeLayer(coalitionArea); - } - - getSelectedCoalitionArea() { - const coalitionArea = this.#coalitionAreas.find((coalitionArea: CoalitionPolygon | CoalitionCircle) => { - return coalitionArea.getSelected(); - }); - - return coalitionArea ?? null; - } - setHiddenType(key: string, value: boolean) { this.#hiddenTypes[key] = value; HiddenTypesChangedEvent.dispatch(this.#hiddenTypes); @@ -765,7 +737,7 @@ export class Map extends L.Map { setSelectionEnabled(selectionEnabled: boolean) { this.#selectionEnabled = selectionEnabled; - SelectionEnabledChangedEvent.dispatch(selectionEnabled) + SelectionEnabledChangedEvent.dispatch(selectionEnabled); } getSelectionEnabled() { @@ -774,7 +746,7 @@ export class Map extends L.Map { setPasteEnabled(pasteEnabled: boolean) { this.#pasteEnabled = pasteEnabled; - PasteEnabledChangedEvent.dispatch(pasteEnabled) + PasteEnabledChangedEvent.dispatch(pasteEnabled); } getPasteEnabled() { @@ -822,18 +794,16 @@ export class Map extends L.Map { /* Event handlers */ #onStateChanged(state: OlympusState, subState: OlympusSubState) { /* Operations to perform when leaving a state */ - this.getSelectedCoalitionArea()?.setEditing(false); this.#currentSpawnMarker?.removeFrom(this); this.#currentSpawnMarker = null; this.#currentEffectMarker?.removeFrom(this); this.#currentEffectMarker = null; - + if (state !== OlympusState.UNIT_CONTROL) { getApp().getUnitsManager().deselectAllUnits(); this.setContextAction(null); this.setContextActionSet(null); } - if (state !== OlympusState.DRAW || (state === OlympusState.DRAW && subState !== DrawSubState.EDIT)) this.deselectAllCoalitionAreas(); this.getContainer().classList.remove(`explosion-cursor`); ["white", "blue", "red", "green", "orange"].forEach((color) => this.getContainer().classList.remove(`smoke-${color}-cursor`)); @@ -862,17 +832,7 @@ export class Map extends L.Map { } else if (state === OlympusState.UNIT_CONTROL) { console.log(`Context action:`); console.log(this.#contextAction); - } else if (state === OlympusState.DRAW) { - if (subState == DrawSubState.DRAW_POLYGON) { - this.#coalitionAreas.push(new CoalitionPolygon([])); - this.#coalitionAreas[this.#coalitionAreas.length - 1].addTo(this); - this.#coalitionAreas[this.#coalitionAreas.length - 1].setSelected(true); - } else if (subState === DrawSubState.DRAW_CIRCLE) { - this.#coalitionAreas.push(new CoalitionCircle(new L.LatLng(0, 0), { radius: 1000 })); - this.#coalitionAreas[this.#coalitionAreas.length - 1].addTo(this); - this.#coalitionAreas[this.#coalitionAreas.length - 1].setSelected(true); - } - } + } } #onDragStart(e: any) { @@ -935,7 +895,7 @@ export class Map extends L.Map { console.log(`Left short click at ${e.latlng}`); if (this.#pasteEnabled) { - getApp().getUnitsManager().paste(e.latlng) + getApp().getUnitsManager().paste(e.latlng); } /* Execute the short click action */ @@ -982,27 +942,7 @@ export class Map extends L.Map { } } } else if (getApp().getState() === OlympusState.DRAW) { - if (getApp().getSubState() === DrawSubState.DRAW_POLYGON) { - const selectedArea = this.getSelectedCoalitionArea(); - if (selectedArea && selectedArea instanceof CoalitionPolygon) { - selectedArea.addTemporaryLatLng(e.latlng); - } - } else if (getApp().getSubState() === DrawSubState.DRAW_CIRCLE) { - const selectedArea = this.getSelectedCoalitionArea(); - if (selectedArea && selectedArea instanceof CoalitionCircle) { - if (selectedArea.getLatLng().lat == 0 && selectedArea.getLatLng().lng == 0) selectedArea.setLatLng(e.latlng); - getApp().setState(OlympusState.DRAW, DrawSubState.EDIT); - } - } else if (getApp().getSubState() == DrawSubState.NO_SUBSTATE) { - this.deselectAllCoalitionAreas(); - for (let idx = 0; idx < this.#coalitionAreas.length; idx++) { - if (areaContains(e.latlng, this.#coalitionAreas[idx])) { - this.#coalitionAreas[idx].setSelected(true); - getApp().setState(OlympusState.DRAW, DrawSubState.EDIT); - break; - } - } - } + getApp().getCoalitionAreasManager().onLeftShortClick(e); } else if (getApp().getState() === OlympusState.JTAC) { // TODO less redundant way to do this if (getApp().getSubState() === JTACSubState.SELECT_TARGET) { @@ -1097,8 +1037,12 @@ export class Map extends L.Map { this.setPasteEnabled(false); - if (getApp().getSubState() === NO_SUBSTATE) getApp().setState(OlympusState.IDLE); - else getApp().setState(getApp().getState()); + if (getApp().getState() === OlympusState.DRAW) { + getApp().getCoalitionAreasManager().onDoubleClick(e); + } else { + if (getApp().getSubState() === NO_SUBSTATE) getApp().setState(OlympusState.IDLE); + else getApp().setState(getApp().getState()); + } } #onMouseMove(e: any) { diff --git a/frontend/react/src/olympusapp.ts b/frontend/react/src/olympusapp.ts index 31635f14..536855f5 100644 --- a/frontend/react/src/olympusapp.ts +++ b/frontend/react/src/olympusapp.ts @@ -26,6 +26,7 @@ import { OlympusConfig } from "./interfaces"; import { SessionDataManager } from "./sessiondata"; import { ControllerManager } from "./controllers/controllermanager"; import { AWACSController } from "./controllers/awacs"; +import { CoalitionAreasManager } from "./map/coalitionarea/coalitionareamanager"; export var VERSION = "{{OLYMPUS_VERSION_NUMBER}}"; export var IP = window.location.toString(); @@ -50,6 +51,7 @@ export class OlympusApp { #audioManager: AudioManager; #sessionDataManager: SessionDataManager; #controllerManager: ControllerManager; + #coalitionAreasManager: CoalitionAreasManager; //#pluginsManager: // TODO constructor() { @@ -98,6 +100,10 @@ export class OlympusApp { return this.#controllerManager; } + getCoalitionAreasManager() { + return this.#coalitionAreasManager; + } + /* TODO getPluginsManager() { return null // this.#pluginsManager as PluginsManager; @@ -117,6 +123,7 @@ export class OlympusApp { this.#weaponsManager = new WeaponsManager(); this.#audioManager = new AudioManager(); this.#controllerManager = new ControllerManager(); + this.#coalitionAreasManager = new CoalitionAreasManager(); /* Check if we are running the latest version */ const request = new Request("https://raw.githubusercontent.com/Pax1601/DCSOlympus/main/version.json"); diff --git a/frontend/react/src/ui/panels/controlspanel.tsx b/frontend/react/src/ui/panels/controlspanel.tsx index efa44d8a..411bd77a 100644 --- a/frontend/react/src/ui/panels/controlspanel.tsx +++ b/frontend/react/src/ui/panels/controlspanel.tsx @@ -1,9 +1,8 @@ import React, { useCallback, useEffect, useState } from "react"; import { faFighterJet, faHandPointer, faJetFighter, faMap, IconDefinition } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { MAP_OPTIONS_DEFAULTS, NO_SUBSTATE, OlympusState, OlympusSubState, SpawnSubState } from "../../constants/constants"; +import { DrawSubState, MAP_OPTIONS_DEFAULTS, NO_SUBSTATE, OlympusState, OlympusSubState, SpawnSubState } from "../../constants/constants"; import { AppStateChangedEvent, ContextActionSetChangedEvent, MapOptionsChangedEvent, ShortcutsChangedEvent } from "../../events"; -import { ContextAction } from "../../unit/contextaction"; import { ContextActionSet } from "../../unit/contextactionset"; import { MapToolBar } from "./maptoolbar"; @@ -23,6 +22,8 @@ export function ControlsPanel(props: {}) { const [shortcuts, setShortcuts] = useState({}); const [contextActionSet, setContextActionSet] = useState(null as null | ContextActionSet); + // TODO change constant references of "Shift with actual keybind" + useEffect(() => { AppStateChangedEvent.on((state, subState) => { setAppState(state); @@ -100,7 +101,7 @@ export function ControlsPanel(props: {}) { } else if (appState === OlympusState.SPAWN) { controls = [ { - actions: [touch ? faHandPointer : "LMB", 2], + actions: [touch ? faHandPointer : "LMB"], text: appSubState === SpawnSubState.NO_SUBSTATE ? "Close spawn menu" : "Return to spawn menu", }, { @@ -123,6 +124,64 @@ export function ControlsPanel(props: {}) { text: "Spawn effect", }); } + } else if (appState === OlympusState.DRAW) { + controls = [ + { + actions: [touch ? faHandPointer : "LMB", "Drag"], + text: "Move map location", + }, + ]; + + if (appSubState === DrawSubState.NO_SUBSTATE) { + controls.unshift({ + actions: [touch ? faHandPointer : "LMB"], + text: "Close draw menu", + }); + controls.unshift({ + actions: [touch ? faHandPointer : "LMB"], + text: "Select drawing", + }); + } + + if (appSubState === DrawSubState.DRAW_CIRCLE) { + controls.unshift({ + actions: [touch ? faHandPointer : "LMB"], + text: "Add new circle", + }); + controls.unshift({ + actions: [touch ? faHandPointer : "LMB", "Drag"], + text: "Drag circle", + }); + } + + if (appSubState === DrawSubState.DRAW_POLYGON) { + controls.unshift({ + actions: [touch ? faHandPointer : "LMB"], + text: "Add point to polygon", + }); + controls.unshift({ + actions: [touch ? faHandPointer : "LMB", "Drag"], + text: "Drag polygon", + }); + } + + if (appSubState === DrawSubState.EDIT) { + controls.unshift({ + actions: [touch ? faHandPointer : "LMB"], + text: "Add/drag point", + }); + controls.unshift({ + actions: [touch ? faHandPointer : "LMB", "Drag"], + text: "Drag drawing", + }); + } + + if (appSubState !== DrawSubState.NO_SUBSTATE) { + controls.push({ + actions: [touch ? faHandPointer : "LMB"], + text: "Deselect drawing", + }); + } } else { controls = baseControls; controls.push({ @@ -167,14 +226,9 @@ export function ControlsPanel(props: {}) { return (
- {typeof action === "string" || typeof action === "number" ? ( - action - ) : ( - - )} + {typeof action === "string" || typeof action === "number" ? action : }
{idx < control.actions.length - 1 && typeof control.actions[idx + 1] === "string" &&
+
} {idx < control.actions.length - 1 && typeof control.actions[idx + 1] === "number" &&
x
} diff --git a/frontend/react/src/ui/panels/drawingmenu.tsx b/frontend/react/src/ui/panels/drawingmenu.tsx index 52f9e92b..cf7c8a1f 100644 --- a/frontend/react/src/ui/panels/drawingmenu.tsx +++ b/frontend/react/src/ui/panels/drawingmenu.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from "react"; import { Menu } from "./components/menu"; -import { FaTrash } from "react-icons/fa"; +import { FaArrowDown, FaArrowUp, FaTrash } from "react-icons/fa"; import { getApp } from "../../olympusapp"; import { OlStateButton } from "../components/olstatebutton"; import { faDrawPolygon } from "@fortawesome/free-solid-svg-icons"; @@ -13,14 +13,14 @@ import { Coalition } from "../../types/types"; import { OlRangeSlider } from "../components/olrangeslider"; import { CoalitionCircle } from "../../map/coalitionarea/coalitioncircle"; import { DrawSubState, ERAS_ORDER, IADSTypes, NO_SUBSTATE, OlympusState, OlympusSubState } from "../../constants/constants"; -import { AppStateChangedEvent, CoalitionAreaSelectedEvent } from "../../events"; -import { UnitBlueprint } from "../../interfaces"; +import { AppStateChangedEvent, CoalitionAreasChangedEvent, CoalitionAreaSelectedEvent } from "../../events"; +import { FaXmark } from "react-icons/fa6"; export function DrawingMenu(props: { open: boolean; onClose: () => void }) { const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED); const [appSubState, setAppSubState] = useState(NO_SUBSTATE as OlympusSubState); const [activeCoalitionArea, setActiveCoalitionArea] = useState(null as null | CoalitionPolygon | CoalitionCircle); - const [areaCoalition, setAreaCoalition] = useState("blue" as Coalition); + const [coalitionAreas, setCoalitionAreas] = useState([] as (CoalitionPolygon | CoalitionCircle)[]); const [IADSDensity, setIADSDensity] = useState(50); const [IADSDistribution, setIADSDistribution] = useState(50); const [forceCoalitionAppropriateUnits, setForceCoalitionApproriateUnits] = useState(false); @@ -38,27 +38,15 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) { /* Get all the unique types and eras for groundunits */ let types = IADSTypes; - let eras = getApp()?.getUnitsManager().getDatabase().getEras().sort((era1, era2) => ERAS_ORDER[era1] > ERAS_ORDER[era2] ? 1: -1 ); - - useEffect(() => { - if (getApp()) { - // TODO - ///* If we are not in polygon drawing mode, force the draw polygon button off */ - //if (drawingPolygon && getApp().getState() !== COALITIONAREA_DRAW_POLYGON) setDrawingPolygon(false); - // - ///* If we are not in circle drawing mode, force the draw circle button off */ - //if (drawingCircle && getApp().getState() !== COALITIONAREA_DRAW_CIRCLE) setDrawingCircle(false); - // - ///* If we are not in any drawing mode, force the map in edit mode */ - //if (props.open && !drawingPolygon && !drawingCircle) getApp().getMap().setState(COALITIONAREA_EDIT); - // - ///* Align the state of the coalition toggle to the coalition of the area */ - //if (activeCoalitionArea && activeCoalitionArea?.getCoalition() !== areaCoalition) setAreaCoalition(activeCoalitionArea?.getCoalition()); - } - }); + let eras = getApp() + ?.getUnitsManager() + .getDatabase() + .getEras() + .sort((era1, era2) => (ERAS_ORDER[era1] > ERAS_ORDER[era2] ? 1 : -1)); useEffect(() => { CoalitionAreaSelectedEvent.on((coalitionArea) => setActiveCoalitionArea(coalitionArea)); + CoalitionAreasChangedEvent.on((coalitionAreas) => setCoalitionAreas([...coalitionAreas])); }, []); return ( @@ -67,43 +55,87 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) { title="Draw" onClose={props.onClose} canBeHidden={true} - showBackButton={activeCoalitionArea !== null} + showBackButton={appSubState !== DrawSubState.NO_SUBSTATE} onBack={() => { + getApp().getCoalitionAreasManager().setSelectedArea(null); getApp().setState(OlympusState.DRAW, DrawSubState.NO_SUBSTATE); }} > <> - {appState === OlympusState.DRAW && appSubState !== DrawSubState.EDIT && ( -
- { - if (appSubState === DrawSubState.DRAW_POLYGON) getApp().setState(OlympusState.DRAW, DrawSubState.EDIT); - else getApp().setState(OlympusState.DRAW, DrawSubState.DRAW_POLYGON); - }} - > -
Add polygon
-
- { - if (appSubState === DrawSubState.DRAW_CIRCLE) getApp().setState(OlympusState.DRAW); - else getApp().setState(OlympusState.DRAW, DrawSubState.DRAW_CIRCLE); - }} - > -
Add circle
-
+ {appState === OlympusState.DRAW && appSubState === DrawSubState.NO_SUBSTATE && ( +
+
+ {coalitionAreas.map((coalitionArea) => { + return ( +
{ + coalitionArea.setSelected(true); + getApp().setState(OlympusState.DRAW, DrawSubState.EDIT); + }} + > +
{coalitionArea.getLabelText()}
+ { + ev.stopPropagation(); + getApp().getCoalitionAreasManager().moveAreaUp(coalitionArea); + }} + className={` + my-auto ml-auto rounded-md p-2 text-3xl + hover:bg-white/10 + `} + /> + { + ev.stopPropagation(); + getApp().getCoalitionAreasManager().moveCoalitionAreaDown(coalitionArea); + }} + className={` + my-auto rounded-md p-2 text-3xl + hover:bg-white/10 + `} + /> + { + ev.stopPropagation(); + getApp().getCoalitionAreasManager().deleteCoalitionArea(coalitionArea); + }} + className={` + my-auto rounded-md p-2 text-3xl + hover:bg-red-500/50 + `} + /> +
+ ); + })} +
+ +
+ getApp().setState(OlympusState.DRAW, DrawSubState.DRAW_POLYGON)} + > +
Add polygon
+
+ getApp().setState(OlympusState.DRAW, DrawSubState.DRAW_CIRCLE)}> +
Add circle
+
+
)}
- {activeCoalitionArea !== null && appSubState === DrawSubState.EDIT && ( + {activeCoalitionArea !== null && (
void }) {
Area label
{ - getApp().getMap().deleteCoalitionArea(activeCoalitionArea); + getApp().getCoalitionAreasManager().deleteCoalitionArea(activeCoalitionArea); + getApp().setState(OlympusState.DRAW); setActiveCoalitionArea(null); }} > @@ -144,13 +180,12 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) { >
Coalition:
{ let newCoalition = ""; - if (areaCoalition === "blue") newCoalition = "neutral"; - else if (areaCoalition === "neutral") newCoalition = "red"; - else if (areaCoalition === "red") newCoalition = "blue"; - setAreaCoalition(newCoalition as Coalition); + if (activeCoalitionArea.getCoalition() === "blue") newCoalition = "neutral"; + else if (activeCoalitionArea.getCoalition() === "neutral") newCoalition = "red"; + else if (activeCoalitionArea.getCoalition() === "red") newCoalition = "blue"; activeCoalitionArea.setCoalition(newCoalition as Coalition); }} > @@ -161,9 +196,11 @@ export function DrawingMenu(props: { open: boolean; onClose: () => void }) { bg-olympus-600 p-5 `} > -
Automatic IADS generation
+
+ Automatic IADS generation +
{types.map((type, idx) => { if (!(type in typesSelection)) {