mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
Major controls rework
This commit is contained in:
parent
73c8ce2fe7
commit
402a1457aa
@ -51,7 +51,9 @@ export class AudioManager {
|
||||
keyDownCallback: () => this.getSinks()[idx]?.setPtt(true),
|
||||
keyUpCallback: () => this.getSinks()[idx]?.setPtt(false),
|
||||
code: key,
|
||||
shiftKey: true
|
||||
shiftKey: true,
|
||||
ctrlKey: false,
|
||||
altKey: false
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -25,6 +25,10 @@ import {
|
||||
} from "../ui/components/olicons";
|
||||
import { FormationCreationRequestEvent, UnitExplosionRequestEvent } from "../events";
|
||||
|
||||
export const SELECT_TOLERANCE_PX = 5;
|
||||
export const SHORT_PRESS_MILLISECONDS = 200;
|
||||
export const DEBOUNCE_MILLISECONDS = 200;
|
||||
|
||||
export const UNITS_URI = "units";
|
||||
export const WEAPONS_URI = "weapons";
|
||||
export const LOGS_URI = "logs";
|
||||
@ -73,8 +77,8 @@ export enum ERAS_ORDER {
|
||||
"Early Cold War",
|
||||
"Mid Cold War",
|
||||
"Late Cold War",
|
||||
"Modern"
|
||||
};
|
||||
"Modern",
|
||||
}
|
||||
|
||||
export const ROEDescriptions: string[] = [
|
||||
"Free (Attack anyone)",
|
||||
@ -331,12 +335,11 @@ export const MAP_OPTIONS_DEFAULTS: MapOptions = {
|
||||
fillSelectedRing: false,
|
||||
showMinimap: false,
|
||||
protectDCSUnits: true,
|
||||
keepRelativePositions: true,
|
||||
cameraPluginPort: 3003,
|
||||
cameraPluginRatio: 1,
|
||||
cameraPluginEnabled: false,
|
||||
cameraPluginMode: "map",
|
||||
tabletMode: false
|
||||
tabletMode: false,
|
||||
};
|
||||
|
||||
export const MAP_HIDDEN_TYPES_DEFAULTS = {
|
||||
@ -473,7 +476,7 @@ export namespace ContextActions {
|
||||
if (targetPosition)
|
||||
getApp()
|
||||
.getUnitsManager()
|
||||
.addDestination(targetPosition, getApp().getMap().getOptions().keepRelativePositions, getApp().getMap().getDestinationRotation(), units);
|
||||
.addDestination(targetPosition, getApp().getMap().getKeepRelativePositions(), getApp().getMap().getDestinationRotation(), units);
|
||||
},
|
||||
{ type: ContextActionType.MOVE, code: null }
|
||||
);
|
||||
@ -488,9 +491,9 @@ export namespace ContextActions {
|
||||
if (targetPosition)
|
||||
getApp()
|
||||
.getUnitsManager()
|
||||
.addDestination(targetPosition, getApp().getMap().getOptions().keepRelativePositions, getApp().getMap().getDestinationRotation(), units);
|
||||
.addDestination(targetPosition, getApp().getMap().getKeepRelativePositions(), getApp().getMap().getDestinationRotation(), units);
|
||||
},
|
||||
{ type: ContextActionType.MOVE, code: "ControlLeft" }
|
||||
{ type: ContextActionType.MOVE, code: "ControlLeft", shiftKey: false }
|
||||
);
|
||||
|
||||
export const DELETE = new ContextAction(
|
||||
@ -505,7 +508,10 @@ export namespace ContextActions {
|
||||
{
|
||||
executeImmediately: true,
|
||||
type: ContextActionType.DELETE,
|
||||
code: "Delete"
|
||||
code: "Delete",
|
||||
ctrlKey: false,
|
||||
shiftKey: false,
|
||||
altKey: false,
|
||||
}
|
||||
);
|
||||
|
||||
@ -523,7 +529,9 @@ export namespace ContextActions {
|
||||
executeImmediately: true,
|
||||
type: ContextActionType.DELETE,
|
||||
code: "Delete",
|
||||
ctrlKey: true
|
||||
ctrlKey: true,
|
||||
shiftKey: false,
|
||||
altKey: false,
|
||||
}
|
||||
);
|
||||
|
||||
@ -536,7 +544,7 @@ export namespace ContextActions {
|
||||
(units: Unit[]) => {
|
||||
getApp().getMap().centerOnUnit(units[0]);
|
||||
},
|
||||
{ executeImmediately: true, type: ContextActionType.OTHER, code: "KeyM", altKey: true }
|
||||
{ executeImmediately: true, type: ContextActionType.OTHER, code: "KeyM", ctrlKey: false, shiftKey: false, altKey: false }
|
||||
);
|
||||
|
||||
export const REFUEL = new ContextAction(
|
||||
@ -548,7 +556,7 @@ export namespace ContextActions {
|
||||
(units: Unit[]) => {
|
||||
getApp().getUnitsManager().refuel(units);
|
||||
},
|
||||
{ executeImmediately: true, type: ContextActionType.ADMIN, code: "KeyV" }
|
||||
{ executeImmediately: true, type: ContextActionType.ADMIN, code: "KeyR", ctrlKey: false, shiftKey: false, altKey: false }
|
||||
);
|
||||
|
||||
export const FOLLOW = new ContextAction(
|
||||
@ -566,7 +574,7 @@ export namespace ContextActions {
|
||||
);
|
||||
}
|
||||
},
|
||||
{ type: ContextActionType.ADMIN, code: "KeyF" }
|
||||
{ type: ContextActionType.ADMIN, code: "KeyF", ctrlKey: false, shiftKey: false, altKey: false }
|
||||
);
|
||||
|
||||
export const BOMB = new ContextAction(
|
||||
@ -579,9 +587,9 @@ export namespace ContextActions {
|
||||
if (targetPosition)
|
||||
getApp()
|
||||
.getUnitsManager()
|
||||
.bombPoint(targetPosition, getApp().getMap().getOptions().keepRelativePositions, getApp().getMap().getDestinationRotation(), units);
|
||||
.bombPoint(targetPosition, getApp().getMap().getKeepRelativePositions(), getApp().getMap().getDestinationRotation(), units);
|
||||
},
|
||||
{ type: ContextActionType.ENGAGE, code: "KeyB" }
|
||||
{ type: ContextActionType.ENGAGE, code: "KeyB", ctrlKey: false, shiftKey: false }
|
||||
);
|
||||
|
||||
export const CARPET_BOMB = new ContextAction(
|
||||
@ -594,9 +602,9 @@ export namespace ContextActions {
|
||||
if (targetPosition)
|
||||
getApp()
|
||||
.getUnitsManager()
|
||||
.carpetBomb(targetPosition, getApp().getMap().getOptions().keepRelativePositions, getApp().getMap().getDestinationRotation(), units);
|
||||
.carpetBomb(targetPosition, getApp().getMap().getKeepRelativePositions(), getApp().getMap().getDestinationRotation(), units);
|
||||
},
|
||||
{ type: ContextActionType.ENGAGE, code: "KeyB", altKey: true }
|
||||
{ type: ContextActionType.ENGAGE, code: "KeyC", ctrlKey: false, shiftKey: false }
|
||||
);
|
||||
|
||||
export const LAND = new ContextAction(
|
||||
@ -608,7 +616,7 @@ export namespace ContextActions {
|
||||
(units: Unit[], _, targetPosition: LatLng | null) => {
|
||||
if (targetPosition) getApp().getUnitsManager().landAt(targetPosition, units);
|
||||
},
|
||||
{ type: ContextActionType.ADMIN, code: "KeyL" }
|
||||
{ type: ContextActionType.ADMIN, code: "KeyL", ctrlKey: false, shiftKey: false }
|
||||
);
|
||||
|
||||
export const LAND_AT_POINT = new ContextAction(
|
||||
@ -621,9 +629,9 @@ export namespace ContextActions {
|
||||
if (targetPosition)
|
||||
getApp()
|
||||
.getUnitsManager()
|
||||
.landAtPoint(targetPosition, getApp().getMap().getOptions().keepRelativePositions, getApp().getMap().getDestinationRotation(), units);
|
||||
.landAtPoint(targetPosition, getApp().getMap().getKeepRelativePositions(), getApp().getMap().getDestinationRotation(), units);
|
||||
},
|
||||
{ type: ContextActionType.ADMIN, code: "KeyL", altKey: true }
|
||||
{ type: ContextActionType.ADMIN, code: "KeyK", ctrlKey: false, shiftKey: false }
|
||||
);
|
||||
|
||||
export const GROUP = new ContextAction(
|
||||
@ -635,7 +643,7 @@ export namespace ContextActions {
|
||||
(units: Unit[], _1, _2) => {
|
||||
getApp().getUnitsManager().createGroup(units);
|
||||
},
|
||||
{ executeImmediately: true, type: ContextActionType.OTHER, code: "KeyG" }
|
||||
{ executeImmediately: true, type: ContextActionType.OTHER, code: "KeyG", ctrlKey: false, shiftKey: false, altKey: false }
|
||||
);
|
||||
|
||||
export const ATTACK = new ContextAction(
|
||||
@ -647,7 +655,7 @@ export namespace ContextActions {
|
||||
(units: Unit[], targetUnit: Unit | null, _) => {
|
||||
if (targetUnit) getApp().getUnitsManager().attackUnit(targetUnit.ID, units);
|
||||
},
|
||||
{ type: ContextActionType.ENGAGE, code: "KeyZ" }
|
||||
{ type: ContextActionType.ENGAGE, code: "KeyZ", ctrlKey: false, shiftKey: false, altKey: false }
|
||||
);
|
||||
|
||||
export const FIRE_AT_AREA = new ContextAction(
|
||||
@ -660,9 +668,9 @@ export namespace ContextActions {
|
||||
if (targetPosition)
|
||||
getApp()
|
||||
.getUnitsManager()
|
||||
.fireAtArea(targetPosition, getApp().getMap().getOptions().keepRelativePositions, getApp().getMap().getDestinationRotation(), units);
|
||||
.fireAtArea(targetPosition, getApp().getMap().getKeepRelativePositions(), getApp().getMap().getDestinationRotation(), units);
|
||||
},
|
||||
{ type: ContextActionType.ENGAGE, code: "KeyZ", altKey: true }
|
||||
{ type: ContextActionType.ENGAGE, code: "KeyV", ctrlKey: false, shiftKey: false }
|
||||
);
|
||||
|
||||
export const SIMULATE_FIRE_FIGHT = new ContextAction(
|
||||
@ -675,8 +683,8 @@ export namespace ContextActions {
|
||||
if (targetPosition)
|
||||
getApp()
|
||||
.getUnitsManager()
|
||||
.simulateFireFight(targetPosition, getApp().getMap().getOptions().keepRelativePositions, getApp().getMap().getDestinationRotation(), units);
|
||||
.simulateFireFight(targetPosition, getApp().getMap().getKeepRelativePositions(), getApp().getMap().getDestinationRotation(), units);
|
||||
},
|
||||
{ type: ContextActionType.ADMIN, code: "KeyX" }
|
||||
{ type: ContextActionType.ADMIN, code: "KeyX", ctrlKey: false, shiftKey: false }
|
||||
);
|
||||
}
|
||||
|
||||
@ -389,7 +389,7 @@ export class CommandModeOptionsChangedEvent {
|
||||
export class AudioSourcesChangedEvent {
|
||||
static on(callback: (audioSources: AudioSource[]) => void) {
|
||||
document.addEventListener(this.name, (ev: CustomEventInit) => {
|
||||
callback(ev.detail);
|
||||
callback(ev.detail.audioSources);
|
||||
});
|
||||
}
|
||||
|
||||
@ -403,7 +403,7 @@ export class AudioSourcesChangedEvent {
|
||||
export class AudioSinksChangedEvent {
|
||||
static on(callback: (audioSinks: AudioSink[]) => void) {
|
||||
document.addEventListener(this.name, (ev: CustomEventInit) => {
|
||||
callback(ev.detail);
|
||||
callback(ev.detail.audioSinks);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ import { DomUtil } from "leaflet";
|
||||
import { DomEvent } from "leaflet";
|
||||
import { LatLngBounds } from "leaflet";
|
||||
import { Bounds } from "leaflet";
|
||||
import { SELECT_TOLERANCE_PX } from "../constants/constants";
|
||||
|
||||
export var BoxSelect = Handler.extend({
|
||||
initialize: function (map) {
|
||||
@ -11,24 +12,15 @@ 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("forceboxselect", (e) => {
|
||||
this._forceBoxSelect = true;
|
||||
const originalEvent = (e as CustomEvent).detail;
|
||||
this._onMouseDown(originalEvent);
|
||||
});
|
||||
},
|
||||
|
||||
addHooks: function () {
|
||||
DomEvent.on(this._container, "mousedown", this._onMouseDown, this);
|
||||
DomEvent.on(this._container, "forceboxselect", this._onMouseDown, this);
|
||||
},
|
||||
|
||||
removeHooks: function () {
|
||||
DomEvent.off(this._container, "mousedown", this._onMouseDown, this);
|
||||
DomEvent.off(this._container, "forceboxselect", this._onMouseDown, this);
|
||||
},
|
||||
|
||||
moved: function () {
|
||||
@ -53,8 +45,7 @@ export var BoxSelect = Handler.extend({
|
||||
},
|
||||
|
||||
_onMouseDown: function (e: any) {
|
||||
if ((e.which == 1 && e.button == 0 && (e.shiftKey || this._forceBoxSelect)) || (e.type === "touchstart" && this._forceBoxSelect)) {
|
||||
this._map.fire("selectionstart");
|
||||
if (e.which == 1 && e.button == 0) {
|
||||
// Clear the deferred resetState if it hasn't executed yet, otherwise it
|
||||
// will interrupt the interaction and orphan a box element in the container.
|
||||
this._clearDeferredResetState();
|
||||
@ -87,15 +78,23 @@ export var BoxSelect = Handler.extend({
|
||||
},
|
||||
|
||||
_onMouseMove: function (e: any) {
|
||||
if (e.type === "touchmove") this._point = this._map.mouseEventToContainerPoint(e.touches[0]);
|
||||
else this._point = this._map.mouseEventToContainerPoint(e);
|
||||
|
||||
if (
|
||||
Math.abs(this._startPoint.x - this._point.x) < SELECT_TOLERANCE_PX &&
|
||||
Math.abs(this._startPoint.y - this._point.y) < SELECT_TOLERANCE_PX &&
|
||||
!this._moved
|
||||
)
|
||||
return;
|
||||
|
||||
if (!this._moved) {
|
||||
this._map.fire("selectionstart");
|
||||
this._moved = true;
|
||||
this._box = DomUtil.create("div", "leaflet-zoom-box", this._container);
|
||||
DomUtil.addClass(this._container, "leaflet-crosshair");
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
@ -114,7 +113,6 @@ export var BoxSelect = Handler.extend({
|
||||
DomUtil.enableTextSelection();
|
||||
DomUtil.enableImageDrag();
|
||||
this._map.dragging.enable();
|
||||
this._forceBoxSelect = false;
|
||||
|
||||
DomEvent.off(
|
||||
//@ts-ignore
|
||||
|
||||
@ -23,6 +23,8 @@ import {
|
||||
ContextActionTarget,
|
||||
ContextActionType,
|
||||
ContextActions,
|
||||
SHORT_PRESS_MILLISECONDS,
|
||||
DEBOUNCE_MILLISECONDS,
|
||||
} from "../constants/constants";
|
||||
import { CoalitionPolygon } from "./coalitionarea/coalitionpolygon";
|
||||
import { MapHiddenTypes, MapOptions } from "../types/types";
|
||||
@ -99,11 +101,19 @@ export class Map extends L.Map {
|
||||
/* Other state controls */
|
||||
#isZooming: boolean = false;
|
||||
#isDragging: boolean = false;
|
||||
#isMouseDown: boolean = false;
|
||||
#isSelecting: boolean = false;
|
||||
|
||||
#debounceTimeout: number | null = null;
|
||||
#isLeftMouseDown: boolean = false;
|
||||
#isRightMouseDown: boolean = false;
|
||||
#leftMouseDownEpoch: number = 0;
|
||||
#rightMouseDownEpoch: number = 0;
|
||||
#leftMouseDownTimeout: number = 0;
|
||||
#rightMouseDownTimeout: number = 0;
|
||||
#lastMousePosition: L.Point = new L.Point(0, 0);
|
||||
#lastMouseCoordinates: L.LatLng = new L.LatLng(0, 0);
|
||||
#previousZoom: number = 0;
|
||||
#selecting: boolean = false;
|
||||
#keepRelativePositions: boolean = false;
|
||||
|
||||
/* Camera control plugin */
|
||||
#slaveDCSCamera: boolean = false;
|
||||
@ -122,6 +132,7 @@ export class Map extends L.Map {
|
||||
#destinationPreviewMarkers: { [key: number]: TemporaryUnitMarker | TargetMarker } = {};
|
||||
#destinationRotation: number = 0;
|
||||
#isRotatingDestination: boolean = false;
|
||||
#destionationWasRotated: boolean = false;
|
||||
|
||||
/* Unit context actions */
|
||||
#contextActionSet: null | ContextActionSet = null;
|
||||
@ -185,8 +196,9 @@ export class Map extends L.Map {
|
||||
|
||||
this.on("mouseup", (e: any) => this.#onMouseUp(e));
|
||||
this.on("mousedown", (e: any) => this.#onMouseDown(e));
|
||||
this.on("click", (e: any) => this.#onLeftClick(e));
|
||||
this.on("contextmenu", (e: any) => this.#onRightClick(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());
|
||||
|
||||
this.on("mousemove", (e: any) => this.#onMouseMove(e));
|
||||
|
||||
@ -195,6 +207,7 @@ export class Map extends L.Map {
|
||||
/* Custom touch events for touchscreen support */
|
||||
L.DomEvent.on(this.getContainer(), "touchstart", this.#onMouseDown, this);
|
||||
L.DomEvent.on(this.getContainer(), "touchend", this.#onMouseUp, this);
|
||||
L.DomEvent.on(this.getContainer(), 'wheel', this.#onMouseWheel, this);
|
||||
|
||||
/* Event listeners */
|
||||
AppStateChangedEvent.on((state, subState) => this.#onStateChanged(state, subState));
|
||||
@ -295,55 +308,73 @@ export class Map extends L.Map {
|
||||
label: "Hide/show labels",
|
||||
keyUpCallback: () => this.setOption("showUnitLabels", !this.getOptions().showUnitLabels),
|
||||
code: "KeyL",
|
||||
shiftKey: true
|
||||
shiftKey: true,
|
||||
altKey: false,
|
||||
ctrlKey: false,
|
||||
})
|
||||
.addShortcut("toggleAcquisitionRings", {
|
||||
label: "Hide/show acquisition rings",
|
||||
keyUpCallback: () => this.setOption("showUnitsAcquisitionRings", !this.getOptions().showUnitsAcquisitionRings),
|
||||
code: "KeyE",
|
||||
shiftKey: true
|
||||
shiftKey: true,
|
||||
altKey: false,
|
||||
ctrlKey: false,
|
||||
})
|
||||
.addShortcut("toggleEngagementRings", {
|
||||
label: "Hide/show engagement rings",
|
||||
keyUpCallback: () => this.setOption("showUnitsEngagementRings", !this.getOptions().showUnitsEngagementRings),
|
||||
code: "KeyQ",
|
||||
shiftKey: true
|
||||
shiftKey: true,
|
||||
altKey: false,
|
||||
ctrlKey: false,
|
||||
})
|
||||
.addShortcut("toggleHideShortEngagementRings", {
|
||||
label: "Hide/show short range rings",
|
||||
keyUpCallback: () => this.setOption("hideUnitsShortRangeRings", !this.getOptions().hideUnitsShortRangeRings),
|
||||
code: "KeyR",
|
||||
shiftKey: true
|
||||
shiftKey: true,
|
||||
altKey: false,
|
||||
ctrlKey: false,
|
||||
})
|
||||
.addShortcut("toggleDetectionLines", {
|
||||
label: "Hide/show detection lines",
|
||||
keyUpCallback: () => this.setOption("showUnitTargets", !this.getOptions().showUnitTargets),
|
||||
code: "KeyF",
|
||||
shiftKey: true
|
||||
shiftKey: true,
|
||||
altKey: false,
|
||||
ctrlKey: false,
|
||||
})
|
||||
.addShortcut("toggleGroupMembers", {
|
||||
label: "Hide/show group members",
|
||||
keyUpCallback: () => this.setOption("hideGroupMembers", !this.getOptions().hideGroupMembers),
|
||||
code: "KeyG",
|
||||
shiftKey: true
|
||||
shiftKey: true,
|
||||
altKey: false,
|
||||
ctrlKey: false,
|
||||
})
|
||||
.addShortcut("toggleRelativePositions", {
|
||||
label: "Toggle group movement mode",
|
||||
keyUpCallback: () => this.setOption("keepRelativePositions", !this.getOptions().keepRelativePositions),
|
||||
code: "KeyP",
|
||||
shiftKey: true
|
||||
keyUpCallback: () => this.setKeepRelativePositions(false),
|
||||
keyDownCallback: () => this.setKeepRelativePositions(true),
|
||||
code: "AltLeft",
|
||||
shiftKey: false,
|
||||
ctrlKey: false,
|
||||
})
|
||||
.addShortcut("increaseCameraZoom", {
|
||||
label: "Increase camera zoom",
|
||||
keyUpCallback: () => this.increaseCameraZoom(),
|
||||
code: "Equal",
|
||||
shiftKey: true
|
||||
shiftKey: true,
|
||||
altKey: false,
|
||||
ctrlKey: false,
|
||||
})
|
||||
.addShortcut("decreaseCameraZoom", {
|
||||
label: "Decrease camera zoom",
|
||||
keyUpCallback: () => this.decreaseCameraZoom(),
|
||||
code: "Minus",
|
||||
shiftKey: true
|
||||
shiftKey: true,
|
||||
altKey: false,
|
||||
ctrlKey: false,
|
||||
});
|
||||
|
||||
for (let contextActionName in ContextActions) {
|
||||
@ -357,15 +388,14 @@ export class Map extends L.Map {
|
||||
shiftKey: contextAction.getOptions().shiftKey,
|
||||
altKey: contextAction.getOptions().altKey,
|
||||
ctrlKey: contextAction.getOptions().ctrlKey,
|
||||
keyDownCallback: () => {
|
||||
const contextActionSet = this.getContextActionSet();
|
||||
if (getApp().getState() === OlympusState.UNIT_CONTROL && contextActionSet && contextAction.getId() in contextActionSet.getContextActions()) {
|
||||
if (contextAction.getOptions().executeImmediately) contextAction.executeCallback(null, null);
|
||||
else this.setContextAction(contextAction);
|
||||
}
|
||||
},
|
||||
keyUpCallback: () => {
|
||||
if (getApp().getState() === OlympusState.UNIT_CONTROL && !contextAction.getOptions().executeImmediately) {
|
||||
const contextActionSet = this.getContextActionSet();
|
||||
if (this.getContextAction() === null || contextAction !== this.getContextAction()) {
|
||||
if (getApp().getState() === OlympusState.UNIT_CONTROL && contextActionSet && contextAction.getId() in contextActionSet.getContextActions()) {
|
||||
if (contextAction.getOptions().executeImmediately) contextAction.executeCallback(null, null);
|
||||
else this.setContextAction(contextAction);
|
||||
}
|
||||
} else {
|
||||
this.setContextAction(null);
|
||||
}
|
||||
},
|
||||
@ -381,30 +411,44 @@ export class Map extends L.Map {
|
||||
keyUpCallback: (ev: KeyboardEvent) => (this.#panUp = false),
|
||||
keyDownCallback: (ev: KeyboardEvent) => (this.#panUp = true),
|
||||
code: "KeyW",
|
||||
shiftKey: false,
|
||||
altKey: false,
|
||||
ctrlKey: false,
|
||||
})
|
||||
.addShortcut(`panDown`, {
|
||||
label: "Pan map down",
|
||||
keyUpCallback: (ev: KeyboardEvent) => (this.#panDown = false),
|
||||
keyDownCallback: (ev: KeyboardEvent) => (this.#panDown = true),
|
||||
code: "KeyS",
|
||||
shiftKey: false,
|
||||
altKey: false,
|
||||
ctrlKey: false,
|
||||
})
|
||||
.addShortcut(`panLeft`, {
|
||||
label: "Pan map left",
|
||||
keyUpCallback: (ev: KeyboardEvent) => (this.#panLeft = false),
|
||||
keyDownCallback: (ev: KeyboardEvent) => (this.#panLeft = true),
|
||||
code: "KeyA",
|
||||
shiftKey: false,
|
||||
altKey: false,
|
||||
ctrlKey: false,
|
||||
})
|
||||
.addShortcut(`panRight`, {
|
||||
label: "Pan map right",
|
||||
keyUpCallback: (ev: KeyboardEvent) => (this.#panRight = false),
|
||||
keyDownCallback: (ev: KeyboardEvent) => (this.#panRight = true),
|
||||
code: "KeyD",
|
||||
shiftKey: false,
|
||||
altKey: false,
|
||||
ctrlKey: false,
|
||||
})
|
||||
.addShortcut(`panFast`, {
|
||||
label: "Pan map fast",
|
||||
keyUpCallback: (ev: KeyboardEvent) => (this.#panFast = false),
|
||||
keyDownCallback: (ev: KeyboardEvent) => (this.#panFast = true),
|
||||
code: "ShiftLeft",
|
||||
altKey: false,
|
||||
ctrlKey: false,
|
||||
});
|
||||
|
||||
/* Periodically check if the camera control endpoint is available */
|
||||
@ -580,7 +624,7 @@ export class Map extends L.Map {
|
||||
}
|
||||
|
||||
isSelecting() {
|
||||
return this.#selecting;
|
||||
return this.#isSelecting;
|
||||
}
|
||||
|
||||
setTheatre(theatre: string) {
|
||||
@ -670,6 +714,17 @@ export class Map extends L.Map {
|
||||
return this.#previousZoom;
|
||||
}
|
||||
|
||||
setKeepRelativePositions(keepRelativePositions: boolean) {
|
||||
this.#keepRelativePositions = keepRelativePositions;
|
||||
this.#updateDestinationPreviewMarkers();
|
||||
if (keepRelativePositions) this.scrollWheelZoom.disable();
|
||||
else this.scrollWheelZoom.enable();
|
||||
}
|
||||
|
||||
getKeepRelativePositions() {
|
||||
return this.#keepRelativePositions;
|
||||
}
|
||||
|
||||
increaseCameraZoom() {
|
||||
//const slider = document.querySelector(`label[title="${DCS_LINK_RATIO}"] input`);
|
||||
//if (slider instanceof HTMLInputElement) {
|
||||
@ -762,166 +817,217 @@ export class Map extends L.Map {
|
||||
}
|
||||
|
||||
#onSelectionStart(e: any) {
|
||||
this.#selecting = true;
|
||||
this.#isSelecting = true;
|
||||
}
|
||||
|
||||
#onSelectionEnd(e: any) {
|
||||
getApp().getUnitsManager().selectFromBounds(e.selectionBounds);
|
||||
this.#selecting = false;
|
||||
|
||||
/* Delay the event so that any other event in the queue still sees the map in selection mode */
|
||||
window.setTimeout(() => {
|
||||
this.#isSelecting = false;
|
||||
}, 300);
|
||||
}
|
||||
|
||||
#onMouseUp(e: any) {
|
||||
this.#isMouseDown = false;
|
||||
this.#isRotatingDestination = false;
|
||||
if (e.originalEvent?.button === 0) {
|
||||
if (Date.now() - this.#leftMouseDownEpoch < SHORT_PRESS_MILLISECONDS) this.#onLeftShortClick(e);
|
||||
this.#isLeftMouseDown = false;
|
||||
} else if (e.originalEvent?.button === 2) {
|
||||
if (Date.now() - this.#rightMouseDownEpoch < SHORT_PRESS_MILLISECONDS) this.#onRightShortClick(e);
|
||||
this.#isRightMouseDown = false;
|
||||
}
|
||||
}
|
||||
|
||||
#onMouseDown(e: any) {
|
||||
this.#isMouseDown = true;
|
||||
if (this.#contextAction?.getTarget() === ContextActionTarget.POINT && e.originalEvent?.button === 2) this.#isRotatingDestination = true;
|
||||
if (e.originalEvent?.button === 0) {
|
||||
this.#isLeftMouseDown = true;
|
||||
this.#leftMouseDownEpoch = Date.now();
|
||||
} else if (e.originalEvent?.button === 2) {
|
||||
this.#isRightMouseDown = true;
|
||||
this.#rightMouseDownEpoch = Date.now();
|
||||
this.#rightMouseDownTimeout = window.setTimeout(() => {
|
||||
this.#onRightLongClick(e);
|
||||
}, SHORT_PRESS_MILLISECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
#onLeftClick(e: L.LeafletMouseEvent) {
|
||||
console.log(`Left click at ${e.latlng}`);
|
||||
#onMouseWheel(e: any) {
|
||||
if (this.#keepRelativePositions) {
|
||||
this.#destinationRotation += e.deltaY / 20;
|
||||
this.#moveDestinationPreviewMarkers();
|
||||
}
|
||||
}
|
||||
|
||||
/* Execute the short click action */
|
||||
if (getApp().getState() === OlympusState.IDLE) {
|
||||
/* Do nothing */
|
||||
} else if (getApp().getState() === OlympusState.SPAWN_CONTEXT) {
|
||||
getApp().setState(OlympusState.IDLE);
|
||||
} else if (getApp().getState() === OlympusState.SPAWN) {
|
||||
if (getApp().getSubState() === SpawnSubState.SPAWN_UNIT) {
|
||||
if (this.#spawnRequestTable !== null) {
|
||||
this.#spawnRequestTable.unit.location = e.latlng;
|
||||
getApp()
|
||||
.getUnitsManager()
|
||||
.spawnUnits(
|
||||
this.#spawnRequestTable.category,
|
||||
Array(this.#spawnRequestTable.amount).fill(this.#spawnRequestTable.unit),
|
||||
this.#spawnRequestTable.coalition,
|
||||
false,
|
||||
undefined,
|
||||
undefined,
|
||||
(hash) => {
|
||||
this.addTemporaryMarker(e.latlng, this.#spawnRequestTable?.unit.unitType ?? "unknown", this.#spawnRequestTable?.coalition ?? "blue", hash);
|
||||
#onLeftShortClick(e: L.LeafletMouseEvent) {
|
||||
if (this.#debounceTimeout) window.clearTimeout(this.#debounceTimeout);
|
||||
|
||||
if (Date.now() - this.#leftMouseDownEpoch < SHORT_PRESS_MILLISECONDS) {
|
||||
this.#debounceTimeout = window.setTimeout(() => {
|
||||
if (!this.#isSelecting) {
|
||||
console.log(`Left short click at ${e.latlng}`);
|
||||
|
||||
/* Execute the short click action */
|
||||
if (getApp().getState() === OlympusState.IDLE) {
|
||||
/* Do nothing */
|
||||
} else if (getApp().getState() === OlympusState.SPAWN) {
|
||||
if (getApp().getSubState() === SpawnSubState.SPAWN_UNIT) {
|
||||
if (this.#spawnRequestTable !== null) {
|
||||
this.#spawnRequestTable.unit.location = e.latlng;
|
||||
getApp()
|
||||
.getUnitsManager()
|
||||
.spawnUnits(
|
||||
this.#spawnRequestTable.category,
|
||||
Array(this.#spawnRequestTable.amount).fill(this.#spawnRequestTable.unit),
|
||||
this.#spawnRequestTable.coalition,
|
||||
false,
|
||||
undefined,
|
||||
undefined,
|
||||
(hash) => {
|
||||
this.addTemporaryMarker(
|
||||
e.latlng,
|
||||
this.#spawnRequestTable?.unit.unitType ?? "unknown",
|
||||
this.#spawnRequestTable?.coalition ?? "blue",
|
||||
hash
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
} else if (getApp().getSubState() === SpawnSubState.SPAWN_EFFECT) {
|
||||
if (this.#effectRequestTable !== null) {
|
||||
if (this.#effectRequestTable.type === "explosion") {
|
||||
if (this.#effectRequestTable.explosionType === "High explosive") getApp().getServerManager().spawnExplosion(50, "normal", e.latlng);
|
||||
else if (this.#effectRequestTable.explosionType === "Napalm") getApp().getServerManager().spawnExplosion(50, "napalm", e.latlng);
|
||||
else if (this.#effectRequestTable.explosionType === "White phosphorous") getApp().getServerManager().spawnExplosion(50, "phosphorous", e.latlng);
|
||||
} else if (getApp().getSubState() === SpawnSubState.SPAWN_EFFECT) {
|
||||
if (this.#effectRequestTable !== null) {
|
||||
if (this.#effectRequestTable.type === "explosion") {
|
||||
if (this.#effectRequestTable.explosionType === "High explosive") getApp().getServerManager().spawnExplosion(50, "normal", e.latlng);
|
||||
else if (this.#effectRequestTable.explosionType === "Napalm") getApp().getServerManager().spawnExplosion(50, "napalm", e.latlng);
|
||||
else if (this.#effectRequestTable.explosionType === "White phosphorous")
|
||||
getApp().getServerManager().spawnExplosion(50, "phosphorous", e.latlng);
|
||||
|
||||
this.addExplosionMarker(e.latlng);
|
||||
} else if (this.#effectRequestTable.type === "smoke") {
|
||||
getApp()
|
||||
.getServerManager()
|
||||
.spawnSmoke(this.#effectRequestTable.smokeColor ?? "white", e.latlng);
|
||||
this.addSmokeMarker(e.latlng, this.#effectRequestTable.smokeColor ?? "white");
|
||||
this.addExplosionMarker(e.latlng);
|
||||
} else if (this.#effectRequestTable.type === "smoke") {
|
||||
getApp()
|
||||
.getServerManager()
|
||||
.spawnSmoke(this.#effectRequestTable.smokeColor ?? "white", e.latlng);
|
||||
this.addSmokeMarker(e.latlng, this.#effectRequestTable.smokeColor ?? "white");
|
||||
}
|
||||
}
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (getApp().getState() === OlympusState.JTAC) {
|
||||
// TODO less redundant way to do this
|
||||
if (getApp().getSubState() === JTACSubState.SELECT_TARGET) {
|
||||
if (!this.#targetPoint) {
|
||||
this.#targetPoint = new TextMarker(e.latlng, "BP", "rgb(37 99 235)", { interactive: true, draggable: true });
|
||||
this.#targetPoint.addTo(this);
|
||||
this.#targetPoint.on("dragstart", (event) => {
|
||||
event.target.options["freeze"] = true;
|
||||
});
|
||||
this.#targetPoint.on("dragend", (event) => {
|
||||
getApp().setState(OlympusState.JTAC);
|
||||
event.target.options["freeze"] = false;
|
||||
});
|
||||
this.#targetPoint.on("click", (event) => {
|
||||
getApp().setState(OlympusState.JTAC);
|
||||
});
|
||||
} else this.#targetPoint.setLatLng(e.latlng);
|
||||
} else if (getApp().getSubState() === JTACSubState.SELECT_ECHO_POINT) {
|
||||
if (!this.#ECHOPoint) {
|
||||
this.#ECHOPoint = new TextMarker(e.latlng, "BP", "rgb(37 99 235)", { interactive: true, draggable: true });
|
||||
this.#ECHOPoint.addTo(this);
|
||||
this.#ECHOPoint.on("dragstart", (event) => {
|
||||
event.target.options["freeze"] = true;
|
||||
});
|
||||
this.#ECHOPoint.on("dragend", (event) => {
|
||||
getApp().setState(OlympusState.JTAC);
|
||||
event.target.options["freeze"] = false;
|
||||
});
|
||||
this.#ECHOPoint.on("click", (event) => {
|
||||
getApp().setState(OlympusState.JTAC);
|
||||
});
|
||||
} else this.#ECHOPoint.setLatLng(e.latlng);
|
||||
} else if (getApp().getSubState() === JTACSubState.SELECT_IP) {
|
||||
if (!this.#IPPoint) {
|
||||
this.#IPPoint = new TextMarker(e.latlng, "BP", "rgb(37 99 235)", { interactive: true, draggable: true });
|
||||
this.#IPPoint.addTo(this);
|
||||
this.#IPPoint.on("dragstart", (event) => {
|
||||
event.target.options["freeze"] = true;
|
||||
});
|
||||
this.#IPPoint.on("dragend", (event) => {
|
||||
getApp().setState(OlympusState.JTAC);
|
||||
event.target.options["freeze"] = false;
|
||||
});
|
||||
this.#IPPoint.on("click", (event) => {
|
||||
getApp().setState(OlympusState.JTAC);
|
||||
});
|
||||
} else this.#IPPoint.setLatLng(e.latlng);
|
||||
}
|
||||
getApp().setState(OlympusState.JTAC);
|
||||
this.#drawIPToTargetLine();
|
||||
} else {
|
||||
if (getApp().getSubState() === NO_SUBSTATE) getApp().setState(OlympusState.IDLE);
|
||||
else getApp().setState(OlympusState.UNIT_CONTROL);
|
||||
}
|
||||
}
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}, DEBOUNCE_MILLISECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
#onRightShortClick(e: L.LeafletMouseEvent) {
|
||||
console.log(`Right short click at ${e.latlng}`);
|
||||
|
||||
window.clearTimeout(this.#rightMouseDownTimeout);
|
||||
|
||||
if (getApp().getState() === OlympusState.IDLE || getApp().getState() === OlympusState.SPAWN_CONTEXT) {
|
||||
SpawnContextMenuRequestEvent.dispatch(e.latlng);
|
||||
getApp().setState(OlympusState.SPAWN_CONTEXT);
|
||||
} else if (getApp().getState() === OlympusState.UNIT_CONTROL) {
|
||||
if (this.#contextAction !== null) this.executeContextAction(null, e.latlng, e.originalEvent);
|
||||
else getApp().setState(OlympusState.IDLE);
|
||||
} else if (getApp().getState() === OlympusState.JTAC) {
|
||||
// TODO less redundant way to do this
|
||||
if (getApp().getSubState() === JTACSubState.SELECT_TARGET) {
|
||||
if (!this.#targetPoint) {
|
||||
this.#targetPoint = new TextMarker(e.latlng, "BP", "rgb(37 99 235)", { interactive: true, draggable: true });
|
||||
this.#targetPoint.addTo(this);
|
||||
this.#targetPoint.on("dragstart", (event) => {
|
||||
event.target.options["freeze"] = true;
|
||||
});
|
||||
this.#targetPoint.on("dragend", (event) => {
|
||||
getApp().setState(OlympusState.JTAC);
|
||||
event.target.options["freeze"] = false;
|
||||
});
|
||||
this.#targetPoint.on("click", (event) => {
|
||||
getApp().setState(OlympusState.JTAC);
|
||||
});
|
||||
} else this.#targetPoint.setLatLng(e.latlng);
|
||||
} else if (getApp().getSubState() === JTACSubState.SELECT_ECHO_POINT) {
|
||||
if (!this.#ECHOPoint) {
|
||||
this.#ECHOPoint = new TextMarker(e.latlng, "BP", "rgb(37 99 235)", { interactive: true, draggable: true });
|
||||
this.#ECHOPoint.addTo(this);
|
||||
this.#ECHOPoint.on("dragstart", (event) => {
|
||||
event.target.options["freeze"] = true;
|
||||
});
|
||||
this.#ECHOPoint.on("dragend", (event) => {
|
||||
getApp().setState(OlympusState.JTAC);
|
||||
event.target.options["freeze"] = false;
|
||||
});
|
||||
this.#ECHOPoint.on("click", (event) => {
|
||||
getApp().setState(OlympusState.JTAC);
|
||||
});
|
||||
} else this.#ECHOPoint.setLatLng(e.latlng);
|
||||
} else if (getApp().getSubState() === JTACSubState.SELECT_IP) {
|
||||
if (!this.#IPPoint) {
|
||||
this.#IPPoint = new TextMarker(e.latlng, "BP", "rgb(37 99 235)", { interactive: true, draggable: true });
|
||||
this.#IPPoint.addTo(this);
|
||||
this.#IPPoint.on("dragstart", (event) => {
|
||||
event.target.options["freeze"] = true;
|
||||
});
|
||||
this.#IPPoint.on("dragend", (event) => {
|
||||
getApp().setState(OlympusState.JTAC);
|
||||
event.target.options["freeze"] = false;
|
||||
});
|
||||
this.#IPPoint.on("click", (event) => {
|
||||
getApp().setState(OlympusState.JTAC);
|
||||
});
|
||||
} else this.#IPPoint.setLatLng(e.latlng);
|
||||
}
|
||||
getApp().setState(OlympusState.JTAC);
|
||||
this.#drawIPToTargetLine();
|
||||
} else {
|
||||
else this.executeDefaultContextAction(null, e.latlng, e.originalEvent);
|
||||
}
|
||||
}
|
||||
|
||||
#onRightClick(e: L.LeafletMouseEvent) {
|
||||
e.originalEvent.preventDefault();
|
||||
#onRightLongClick(e: L.LeafletMouseEvent) {
|
||||
console.log(`Right long click at ${e.latlng}`);
|
||||
|
||||
console.log(`Right click at ${e.latlng}`);
|
||||
|
||||
if (!this.#isDragging && !this.#isZooming) {
|
||||
this.deselectAllCoalitionAreas();
|
||||
if (getApp().getState() === OlympusState.IDLE || getApp().getState() === OlympusState.SPAWN_CONTEXT) {
|
||||
SpawnContextMenuRequestEvent.dispatch(e.latlng);
|
||||
getApp().setState(OlympusState.SPAWN_CONTEXT);
|
||||
} else if (getApp().getState() === OlympusState.UNIT_CONTROL) {
|
||||
if (!this.getContextAction()) {
|
||||
getApp().setState(OlympusState.UNIT_CONTROL, UnitControlSubState.MAP_CONTEXT_MENU);
|
||||
MapContextMenuRequestEvent.dispatch(e.latlng);
|
||||
}
|
||||
if (getApp().getState() === OlympusState.UNIT_CONTROL) {
|
||||
if (!this.getContextAction()) {
|
||||
getApp().setState(OlympusState.UNIT_CONTROL, UnitControlSubState.MAP_CONTEXT_MENU);
|
||||
MapContextMenuRequestEvent.dispatch(e.latlng);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#onDoubleClick(e: L.LeafletMouseEvent) {
|
||||
console.log(`Double click at ${e.latlng}`);
|
||||
|
||||
if (this.#debounceTimeout) window.clearTimeout(this.#debounceTimeout);
|
||||
|
||||
if (getApp().getSubState() === NO_SUBSTATE) getApp().setState(OlympusState.IDLE);
|
||||
else getApp().setState(getApp().getState());
|
||||
}
|
||||
|
||||
#onMouseMove(e: any) {
|
||||
if (!this.#isRotatingDestination) {
|
||||
this.#destionationWasRotated = false;
|
||||
this.#lastMousePosition.x = e.originalEvent.x;
|
||||
this.#lastMousePosition.y = e.originalEvent.y;
|
||||
this.#lastMouseCoordinates = e.latlng;
|
||||
@ -934,6 +1040,7 @@ export class Map extends L.Map {
|
||||
if (this.#currentSpawnMarker) this.#currentSpawnMarker.setLatLng(e.latlng);
|
||||
if (this.#currentEffectMarker) this.#currentEffectMarker.setLatLng(e.latlng);
|
||||
} else {
|
||||
this.#destionationWasRotated = true;
|
||||
this.#destinationRotation -= e.originalEvent.movementX;
|
||||
}
|
||||
|
||||
@ -1047,18 +1154,20 @@ export class Map extends L.Map {
|
||||
delete this.#destinationPreviewMarkers[ID];
|
||||
});
|
||||
|
||||
selectedUnits.forEach((unit) => {
|
||||
if (this.#contextAction?.getOptions().type === ContextActionType.MOVE) {
|
||||
this.#destinationPreviewMarkers[unit.ID] = new TemporaryUnitMarker(new L.LatLng(0, 0), unit.getName(), unit.getCoalition());
|
||||
} else if (this.#contextAction?.getTarget() === ContextActionTarget.POINT) {
|
||||
this.#destinationPreviewMarkers[unit.ID] = new TargetMarker(new L.LatLng(0, 0));
|
||||
}
|
||||
this.#destinationPreviewMarkers[unit.ID]?.addTo(this);
|
||||
});
|
||||
if (this.#keepRelativePositions) {
|
||||
selectedUnits.forEach((unit) => {
|
||||
if (this.#contextAction?.getOptions().type === ContextActionType.MOVE || this.#contextAction === null) {
|
||||
this.#destinationPreviewMarkers[unit.ID] = new TemporaryUnitMarker(new L.LatLng(0, 0), unit.getName(), unit.getCoalition());
|
||||
} else if (this.#contextAction?.getTarget() === ContextActionTarget.POINT) {
|
||||
this.#destinationPreviewMarkers[unit.ID] = new TargetMarker(new L.LatLng(0, 0));
|
||||
}
|
||||
this.#destinationPreviewMarkers[unit.ID]?.addTo(this);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#moveDestinationPreviewMarkers() {
|
||||
if (this.#options.keepRelativePositions) {
|
||||
if (this.#keepRelativePositions) {
|
||||
Object.entries(getApp().getUnitsManager().computeGroupDestination(this.#lastMouseCoordinates, this.#destinationRotation)).forEach(([ID, latlng]) => {
|
||||
this.#destinationPreviewMarkers[ID]?.setLatLng(latlng);
|
||||
});
|
||||
|
||||
@ -160,7 +160,6 @@ export class OlympusApp {
|
||||
});
|
||||
|
||||
this.#shortcutManager.checkShortcuts();
|
||||
|
||||
}
|
||||
|
||||
getConfig() {
|
||||
@ -189,13 +188,55 @@ export class OlympusApp {
|
||||
console.log(`Profile ${this.#profileName} saved correctly`);
|
||||
} else {
|
||||
this.addInfoMessage("Error saving profile");
|
||||
throw new Error("Error saving profile file");
|
||||
throw new Error("Error saving profile");
|
||||
}
|
||||
}) // Parse the response as JSON
|
||||
.catch((error) => console.error(error)); // Handle errors
|
||||
}
|
||||
}
|
||||
|
||||
resetProfile() {
|
||||
if (this.#profileName !== null) {
|
||||
const requestOptions = {
|
||||
method: "PUT", // Specify the request method
|
||||
headers: { "Content-Type": "application/json" }, // Specify the content type
|
||||
body: "", // Send the data in JSON format
|
||||
};
|
||||
|
||||
fetch(this.getExpressAddress() + `/resources/profile/reset/${this.#profileName}`, requestOptions)
|
||||
.then((response) => {
|
||||
if (response.status === 200) {
|
||||
console.log(`Profile ${this.#profileName} reset correctly`);
|
||||
location.reload()
|
||||
} else {
|
||||
this.addInfoMessage("Error resetting profile");
|
||||
throw new Error("Error resetting profile");
|
||||
}
|
||||
}) // Parse the response as JSON
|
||||
.catch((error) => console.error(error)); // Handle errors
|
||||
}
|
||||
}
|
||||
|
||||
resetAllProfiles() {
|
||||
const requestOptions = {
|
||||
method: "PUT", // Specify the request method
|
||||
headers: { "Content-Type": "application/json" }, // Specify the content type
|
||||
body: "", // Send the data in JSON format
|
||||
};
|
||||
|
||||
fetch(this.getExpressAddress() + `/resources/profile/resetall`, requestOptions)
|
||||
.then((response) => {
|
||||
if (response.status === 200) {
|
||||
console.log(`All profiles reset correctly`);
|
||||
location.reload()
|
||||
} else {
|
||||
this.addInfoMessage("Error resetting profiles");
|
||||
throw new Error("Error resetting profiles");
|
||||
}
|
||||
}) // Parse the response as JSON
|
||||
.catch((error) => console.error(error)); // Handle errors
|
||||
}
|
||||
|
||||
getProfile() {
|
||||
if (this.#profileName && this.#config?.profiles && this.#config?.profiles[this.#profileName])
|
||||
return this.#config?.profiles[this.#profileName] as ProfileOptions;
|
||||
@ -208,9 +249,9 @@ export class OlympusApp {
|
||||
this.#map?.setOptions(profile.mapOptions);
|
||||
this.#shortcutManager?.setShortcutsOptions(profile.shortcuts);
|
||||
this.addInfoMessage("Profile loaded correctly");
|
||||
console.log(`Profile ${this.#profileName} saved correctly`);
|
||||
console.log(`Profile ${this.#profileName} loaded correctly`);
|
||||
} else {
|
||||
this.addInfoMessage("Error loading profile");
|
||||
this.addInfoMessage("Profile not found, creating new profile");
|
||||
console.log(`Error loading profile`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,33 +11,31 @@ export class Shortcut {
|
||||
this.#id = id;
|
||||
this.#options = options;
|
||||
|
||||
AppStateChangedEvent.on(() => this.#keydown = false)
|
||||
|
||||
/* Key up event is mandatory */
|
||||
AppStateChangedEvent.on(() => (this.#keydown = false));
|
||||
|
||||
/* On keyup, it is enough to check the code only, not the entire combination */
|
||||
document.addEventListener("keyup", (ev: any) => {
|
||||
this.#keydown = false;
|
||||
if (keyEventWasInInput(ev) || options.code !== ev.code) return;
|
||||
if (
|
||||
ev.altKey === (options.altKey ?? ev.code.indexOf("Alt") >= 0) &&
|
||||
ev.ctrlKey === (options.ctrlKey ?? ev.code.indexOf("Ctrl") >= 0) &&
|
||||
ev.shiftKey === (options.shiftKey ?? ev.code.indexOf("Shift") >= 0)
|
||||
)
|
||||
if (this.#keydown && options.code === ev.code) {
|
||||
ev.preventDefault();
|
||||
options.keyUpCallback(ev);
|
||||
this.#keydown = false;
|
||||
}
|
||||
});
|
||||
|
||||
/* Key down event is optional */
|
||||
if (options.keyDownCallback) {
|
||||
document.addEventListener("keydown", (ev: any) => {
|
||||
if (this.#keydown || keyEventWasInInput(ev) || options.code !== ev.code) return;
|
||||
/* On keydown, check exactly if the requested key combination is being pressed */
|
||||
document.addEventListener("keydown", (ev: any) => {
|
||||
if (
|
||||
!(this.#keydown || keyEventWasInInput(ev) || options.code !== ev.code) &&
|
||||
(options.altKey === undefined || ev.altKey === (options.altKey ?? ev.code.indexOf("Alt") >= 0)) &&
|
||||
(options.ctrlKey === undefined || ev.ctrlKey === (options.ctrlKey ?? ev.code.indexOf("Control") >= 0)) &&
|
||||
(options.shiftKey === undefined || ev.shiftKey === (options.shiftKey ?? ev.code.indexOf("Shift") >= 0))
|
||||
) {
|
||||
ev.preventDefault();
|
||||
this.#keydown = true;
|
||||
if (
|
||||
ev.altKey === (options.altKey ?? ev.code.indexOf("Alt") >= 0) &&
|
||||
ev.ctrlKey === (options.ctrlKey ?? ev.code.indexOf("Control") >= 0) &&
|
||||
ev.shiftKey === (options.shiftKey ?? ev.code.indexOf("Shift") >= 0)
|
||||
)
|
||||
if (options.keyDownCallback) options.keyDownCallback(ev);
|
||||
});
|
||||
}
|
||||
|
||||
if (options.keyDownCallback) options.keyDownCallback(ev); /* Key down event is optional */
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getOptions() {
|
||||
@ -51,4 +49,19 @@ export class Shortcut {
|
||||
getId() {
|
||||
return this.#id;
|
||||
}
|
||||
|
||||
toActions() {
|
||||
let actions: string[] = [];
|
||||
if (this.getOptions().shiftKey) actions.push("Shift");
|
||||
if (this.getOptions().altKey) actions.push("Alt");
|
||||
if (this.getOptions().ctrlKey) actions.push("Ctrl")
|
||||
actions.push(this.getOptions().code.replace("Key", "")
|
||||
.replace("ControlLeft", "Left Ctrl")
|
||||
.replace("AltLeft", "Left Alt")
|
||||
.replace("ShiftLeft", "Left Shift")
|
||||
.replace("ControlRight", "Right Ctrl")
|
||||
.replace("AltRight", "Right Alt")
|
||||
.replace("ShiftRight", "Right Shift"))
|
||||
return actions
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,14 +5,7 @@ import { Shortcut } from "./shortcut";
|
||||
export class ShortcutManager {
|
||||
#shortcuts: { [key: string]: Shortcut } = {};
|
||||
|
||||
constructor() {
|
||||
// Stop ctrl+digits from sending the browser to another tab
|
||||
document.addEventListener("keydown", (ev: KeyboardEvent) => {
|
||||
if (ev.code.indexOf("Digit") >= 0 && ev.ctrlKey === true && ev.altKey === false && ev.shiftKey === false) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
});
|
||||
}
|
||||
constructor() {}
|
||||
|
||||
addShortcut(id: string, shortcutOptions: ShortcutOptions) {
|
||||
this.#shortcuts[id] = new Shortcut(id, shortcutOptions);
|
||||
@ -20,6 +13,14 @@ export class ShortcutManager {
|
||||
return this;
|
||||
}
|
||||
|
||||
getShortcut(id) {
|
||||
return this.#shortcuts[id];
|
||||
}
|
||||
|
||||
getShortcuts() {
|
||||
return this.#shortcuts;
|
||||
}
|
||||
|
||||
getShortcutsOptions() {
|
||||
let shortcutsOptions = {};
|
||||
for (let id in this.#shortcuts) {
|
||||
@ -48,11 +49,18 @@ export class ShortcutManager {
|
||||
const otherShortcut = this.#shortcuts[otherid];
|
||||
if (shortcut.getOptions().code === otherShortcut.getOptions().code) {
|
||||
if (
|
||||
(shortcut.getOptions().altKey ?? false) === (otherShortcut.getOptions().altKey ?? false) &&
|
||||
(shortcut.getOptions().ctrlKey ?? false) === (otherShortcut.getOptions().ctrlKey ?? false) &&
|
||||
(shortcut.getOptions().shiftKey ?? false) === (otherShortcut.getOptions().shiftKey ?? false)
|
||||
shortcut.getOptions().code === otherShortcut.getOptions().code &&
|
||||
((shortcut.getOptions().shiftKey === undefined && otherShortcut.getOptions().shiftKey !== undefined) ||
|
||||
(shortcut.getOptions().shiftKey !== undefined && otherShortcut.getOptions().shiftKey === undefined) ||
|
||||
shortcut.getOptions().shiftKey === otherShortcut.getOptions().shiftKey) &&
|
||||
((shortcut.getOptions().altKey === undefined && otherShortcut.getOptions().altKey !== undefined) ||
|
||||
(shortcut.getOptions().altKey !== undefined && otherShortcut.getOptions().altKey === undefined) ||
|
||||
shortcut.getOptions().altKey === otherShortcut.getOptions().altKey) &&
|
||||
((shortcut.getOptions().ctrlKey === undefined && otherShortcut.getOptions().ctrlKey !== undefined) ||
|
||||
(shortcut.getOptions().ctrlKey !== undefined && otherShortcut.getOptions().ctrlKey === undefined) ||
|
||||
shortcut.getOptions().ctrlKey === otherShortcut.getOptions().ctrlKey)
|
||||
) {
|
||||
console.error("Duplicate shortcut: " + shortcut.getOptions().label + " and " + otherShortcut.getOptions().label)
|
||||
console.error("Duplicate shortcut: " + shortcut.getOptions().label + " and " + otherShortcut.getOptions().label);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,7 +21,6 @@ export type MapOptions = {
|
||||
fillSelectedRing: boolean;
|
||||
showMinimap: boolean;
|
||||
protectDCSUnits: boolean;
|
||||
keepRelativePositions: boolean;
|
||||
cameraPluginPort: number;
|
||||
cameraPluginRatio: number;
|
||||
cameraPluginEnabled: boolean;
|
||||
|
||||
@ -11,6 +11,8 @@ export function OlStateButton(props: {
|
||||
icon?: IconProp;
|
||||
tooltip: string;
|
||||
onClick: () => void;
|
||||
onMouseUp?: () => void;
|
||||
onMouseDown?: () => void;
|
||||
children?: JSX.Element | JSX.Element[];
|
||||
}) {
|
||||
const [hover, setHover] = useState(false);
|
||||
@ -36,8 +38,10 @@ export function OlStateButton(props: {
|
||||
ref={buttonRef}
|
||||
onClick={() => {
|
||||
props.onClick();
|
||||
setHover(false);
|
||||
props.onClick ?? setHover(false);
|
||||
}}
|
||||
onMouseUp={props.onMouseUp ?? (() => {})}
|
||||
onMouseDown={props.onMouseDown ?? (() => {})}
|
||||
data-checked={props.checked}
|
||||
type="button"
|
||||
className={className}
|
||||
|
||||
@ -5,8 +5,7 @@ export function OlToggle(props: { toggled: boolean | undefined; onClick: () => v
|
||||
<div className="inline-flex cursor-pointer items-center" onClick={props.onClick}>
|
||||
<button className="peer sr-only" />
|
||||
<div
|
||||
data-flash={props.toggled === undefined}
|
||||
data-toggled={props.toggled ?? false}
|
||||
data-toggled={props.toggled === true? 'true': props.toggled === undefined? 'undefined': 'false'}
|
||||
className={`
|
||||
peer relative h-7 w-14 rounded-full bg-gray-200
|
||||
after:absolute after:start-[4px] after:top-0.5 after:h-6 after:w-6
|
||||
@ -14,10 +13,11 @@ export function OlToggle(props: { toggled: boolean | undefined; onClick: () => v
|
||||
after:transition-all after:content-['']
|
||||
dark:border-gray-600 dark:peer-focus:ring-blue-800
|
||||
dark:data-[toggled='true']:bg-blue-500
|
||||
data-[flash='true']:after:animate-pulse
|
||||
data-[toggled='false']:bg-gray-500
|
||||
data-[toggled='true']:after:translate-x-full
|
||||
data-[toggled='true']:after:border-white
|
||||
data-[toggled='undefined']:bg-gray-800
|
||||
data-[toggled='undefined']:after:translate-x-[50%]
|
||||
peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-blue-300
|
||||
rtl:data-[toggled='true']:after:-translate-x-full
|
||||
`}
|
||||
|
||||
@ -112,6 +112,11 @@ export function MapContextMenu(props: {}) {
|
||||
} else if (unit !== null) {
|
||||
contextActionIt.executeCallback(unit, null);
|
||||
}
|
||||
window.setTimeout(() => {
|
||||
if (getApp().getSubState() === UnitControlSubState.MAP_CONTEXT_MENU || getApp().getSubState() === UnitControlSubState.UNIT_CONTEXT_MENU) {
|
||||
getApp().setState(OlympusState.UNIT_CONTROL)
|
||||
}
|
||||
}, 200)
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
||||
@ -6,37 +6,48 @@ import { getApp } from "../../olympusapp";
|
||||
import { OlympusState } from "../../constants/constants";
|
||||
import { Shortcut } from "../../shortcut/shortcut";
|
||||
import { BindShortcutRequestEvent, ShortcutsChangedEvent } from "../../events";
|
||||
import { OlToggle } from "../components/oltoggle";
|
||||
|
||||
export function KeybindModal(props: { open: boolean }) {
|
||||
const [shortcuts, setShortcuts] = useState({} as { [key: string]: Shortcut });
|
||||
const [shortcut, setShortcut] = useState(null as null | Shortcut);
|
||||
const [code, setCode] = useState(null as null | string);
|
||||
const [shiftKey, setShiftKey] = useState(false);
|
||||
const [ctrlKey, setCtrlKey] = useState(false);
|
||||
const [altKey, setAltKey] = useState(false);
|
||||
const [shiftKey, setShiftKey] = useState(false as boolean | undefined);
|
||||
const [ctrlKey, setCtrlKey] = useState(false as boolean | undefined);
|
||||
const [altKey, setAltKey] = useState(false as boolean | undefined);
|
||||
|
||||
useEffect(() => {
|
||||
ShortcutsChangedEvent.on((shortcuts) => setShortcuts({ ...shortcuts }));
|
||||
BindShortcutRequestEvent.on((shortcut) => setShortcut(shortcut));
|
||||
|
||||
document.addEventListener("keydown", (ev) => {
|
||||
setCode(ev.code);
|
||||
if (!(ev.code.indexOf("Shift") >= 0 || ev.code.indexOf("Alt") >= 0 || ev.code.indexOf("Control") >= 0)) {
|
||||
setShiftKey(ev.shiftKey);
|
||||
setAltKey(ev.altKey);
|
||||
setCtrlKey(ev.ctrlKey);
|
||||
if (ev.code) {
|
||||
setCode(ev.code);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setCode(shortcut?.getOptions().code ?? null)
|
||||
setShiftKey(shortcut?.getOptions().shiftKey)
|
||||
setAltKey(shortcut?.getOptions().altKey)
|
||||
setCtrlKey(shortcut?.getOptions().ctrlKey)
|
||||
}, [shortcut])
|
||||
|
||||
let available: null | boolean = code ? true : null;
|
||||
let inUseShortcut: null | Shortcut = null;
|
||||
for (let id in shortcuts) {
|
||||
if (
|
||||
if (id !== shortcut?.getId() &&
|
||||
code === shortcuts[id].getOptions().code &&
|
||||
shiftKey === (shortcuts[id].getOptions().shiftKey ?? false) &&
|
||||
altKey === (shortcuts[id].getOptions().altKey ?? false) &&
|
||||
ctrlKey === (shortcuts[id].getOptions().shiftKey ?? false)
|
||||
((shiftKey === undefined && shortcuts[id].getOptions().shiftKey !== undefined) ||
|
||||
(shiftKey !== undefined && shortcuts[id].getOptions().shiftKey === undefined) ||
|
||||
shiftKey === shortcuts[id].getOptions().shiftKey) && (
|
||||
(altKey === undefined && shortcuts[id].getOptions().altKey !== undefined) ||
|
||||
(altKey !== undefined && shortcuts[id].getOptions().altKey === undefined) ||
|
||||
altKey === shortcuts[id].getOptions().altKey) && (
|
||||
(ctrlKey === undefined && shortcuts[id].getOptions().ctrlKey !== undefined) ||
|
||||
(ctrlKey !== undefined && shortcuts[id].getOptions().ctrlKey === undefined) ||
|
||||
ctrlKey === shortcuts[id].getOptions().ctrlKey)
|
||||
) {
|
||||
available = false;
|
||||
inUseShortcut = shortcuts[id];
|
||||
@ -75,26 +86,66 @@ export function KeybindModal(props: { open: boolean }) {
|
||||
Press the key you want to bind to this event
|
||||
</span>
|
||||
</div>
|
||||
<div className="w-full text-center text-white">
|
||||
{ctrlKey ? "Ctrl + " : ""}
|
||||
{shiftKey ? "Shift + " : ""}
|
||||
{altKey ? "Alt + " : ""}
|
||||
|
||||
{code}
|
||||
<div className="w-full text-center text-white">{code}</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex gap-2">
|
||||
<OlToggle
|
||||
onClick={() => {
|
||||
if (shiftKey === false) setShiftKey(undefined);
|
||||
else if (shiftKey === undefined) setShiftKey(true);
|
||||
else setShiftKey(false);
|
||||
}}
|
||||
toggled={shiftKey}
|
||||
></OlToggle>
|
||||
<div className="text-white">
|
||||
{shiftKey === true && "Shift key must be pressed"}
|
||||
{shiftKey === undefined && "Shift key can be anything"}
|
||||
{shiftKey === false && "Shift key must NOT be pressed"}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<OlToggle
|
||||
onClick={() => {
|
||||
if (altKey === false) setAltKey(undefined);
|
||||
else if (altKey === undefined) setAltKey(true);
|
||||
else setAltKey(false);
|
||||
}}
|
||||
toggled={altKey}
|
||||
></OlToggle>
|
||||
<div className="text-white">
|
||||
{altKey === true && "Alt key must be pressed"}
|
||||
{altKey === undefined && "Alt key can be anything"}
|
||||
{altKey === false && "Alt key must NOT be pressed"}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<OlToggle
|
||||
onClick={() => {
|
||||
if (ctrlKey === false) setCtrlKey(undefined);
|
||||
else if (ctrlKey === undefined) setCtrlKey(true);
|
||||
else setCtrlKey(false);
|
||||
}}
|
||||
toggled={ctrlKey}
|
||||
></OlToggle>
|
||||
<div className="text-white">
|
||||
{ctrlKey === true && "Ctrl key must be pressed"}
|
||||
{ctrlKey === undefined && "Ctrl key can be anything"}
|
||||
{ctrlKey === false && "Ctrl key must NOT be pressed"}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div className="text-white">
|
||||
{available === true && <div className="text-green-600">Keybind is free!</div>}
|
||||
{available === false && (
|
||||
<div>
|
||||
Keybind is already in use:{" "}
|
||||
<span
|
||||
className={`font-bold text-red-600`}
|
||||
>
|
||||
{inUseShortcut?.getOptions().label}
|
||||
</span>
|
||||
Keybind is already in use: <span className={`
|
||||
font-bold text-red-600
|
||||
`}>{inUseShortcut?.getOptions().label}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end">
|
||||
{available && shortcut && (
|
||||
<button
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Modal } from "./components/modal";
|
||||
import { Card } from "./components/card";
|
||||
import { ErrorCallout } from "../../ui/components/olcallout";
|
||||
@ -7,6 +7,7 @@ import { faArrowRight, faCheckCircle, faExternalLink } from "@fortawesome/free-s
|
||||
import { getApp, VERSION } from "../../olympusapp";
|
||||
import { sha256 } from "js-sha256";
|
||||
import { BLUE_COMMANDER, GAME_MASTER, OlympusState, RED_COMMANDER } from "../../constants/constants";
|
||||
import { FaTrash, FaXmark } from "react-icons/fa6";
|
||||
|
||||
export function LoginModal(props: {}) {
|
||||
// TODO: add warning if not in secure context and some features are disabled
|
||||
@ -16,6 +17,11 @@ export function LoginModal(props: {}) {
|
||||
const [loginError, setLoginError] = useState(false);
|
||||
const [commandMode, setCommandMode] = useState(null as null | string);
|
||||
|
||||
useEffect(() => {
|
||||
/* Set the profile name */
|
||||
getApp().setProfile(profileName);
|
||||
}, [profileName])
|
||||
|
||||
function checkPassword(password: string) {
|
||||
setCheckingPassword(true);
|
||||
var hash = sha256.create();
|
||||
@ -44,8 +50,6 @@ export function LoginModal(props: {}) {
|
||||
getApp().getServerManager().startUpdate();
|
||||
getApp().setState(OlympusState.IDLE);
|
||||
|
||||
/* Set the profile name */
|
||||
getApp().setProfile(profileName);
|
||||
/* If no profile exists already with that name, create it from scratch from the defaults */
|
||||
if (getApp().getProfile() === null)
|
||||
getApp().saveProfile();
|
||||
@ -56,7 +60,7 @@ export function LoginModal(props: {}) {
|
||||
return (
|
||||
<Modal
|
||||
className={`
|
||||
inline-flex h-[75%] max-h-[530px] w-[80%] max-w-[1100px] overflow-y-auto
|
||||
inline-flex h-[75%] max-h-[570px] w-[80%] max-w-[1100px] overflow-y-auto
|
||||
scroll-smooth bg-white
|
||||
dark:bg-olympus-800
|
||||
max-md:h-full max-md:max-h-full max-md:w-full max-md:rounded-none
|
||||
@ -236,7 +240,7 @@ export function LoginModal(props: {}) {
|
||||
/>
|
||||
</div>
|
||||
<div className="text-xs text-gray-400">
|
||||
The profile name you choose determines what keybinds/groups/options get loaded and edited. Be careful!
|
||||
The profile name you choose determines the saved key binds, groups and options you see.
|
||||
</div>
|
||||
<div className="flex">
|
||||
<button
|
||||
@ -279,8 +283,8 @@ export function LoginModal(props: {}) {
|
||||
) : (
|
||||
<>
|
||||
<ErrorCallout
|
||||
title="Server could not be reached"
|
||||
description="The Olympus Server at this address could not be reached. Check the address is correct, restart the Olympus server or reinstall Olympus. Ensure the ports set are not already used."
|
||||
title="Server could not be reached or password is incorrect"
|
||||
description="The Olympus Server at this address could not be reached or the password is incorrect. Check your password. If correct, check the address is correct, restart the Olympus server or reinstall Olympus. Ensure the ports set are not already used."
|
||||
></ErrorCallout>
|
||||
<div className={`text-sm font-medium text-gray-200`}>
|
||||
Still having issues? See our
|
||||
|
||||
@ -11,9 +11,7 @@ import { UnitSinkPanel } from "./components/unitsinkpanel";
|
||||
import { UnitSink } from "../../audio/unitsink";
|
||||
import { FaMinus, FaVolumeHigh } from "react-icons/fa6";
|
||||
import { getRandomColor } from "../../other/utils";
|
||||
import { AudioManagerStateChangedEvent, AudioSinksChangedEvent, AudioSourcesChangedEvent } from "../../events";
|
||||
|
||||
let shortcutKeys = ["Z", "X", "C", "V", "B", "N", "M", "K", "L"];
|
||||
import { AudioManagerStateChangedEvent, AudioSinksChangedEvent, AudioSourcesChangedEvent, ShortcutsChangedEvent } from "../../events";
|
||||
|
||||
export function AudioMenu(props: { open: boolean; onClose: () => void; children?: JSX.Element | JSX.Element[] }) {
|
||||
const [sinks, setSinks] = useState([] as AudioSink[]);
|
||||
@ -21,6 +19,7 @@ export function AudioMenu(props: { open: boolean; onClose: () => void; children?
|
||||
const [audioManagerEnabled, setAudioManagerEnabled] = useState(false);
|
||||
const [activeSource, setActiveSource] = useState(null as AudioSource | null);
|
||||
const [count, setCount] = useState(0);
|
||||
const [shortcuts, setShortcuts] = useState({})
|
||||
|
||||
/* Preallocate 128 references for the source and sink panels. If the number of references changes, React will give an error */
|
||||
const sourceRefs = Array(128)
|
||||
@ -60,6 +59,8 @@ export function AudioMenu(props: { open: boolean; onClose: () => void; children?
|
||||
AudioManagerStateChangedEvent.on(() => {
|
||||
setAudioManagerEnabled(getApp().getAudioManager().isRunning());
|
||||
});
|
||||
|
||||
ShortcutsChangedEvent.on((shortcuts) => setShortcuts(shortcuts));
|
||||
}, []);
|
||||
|
||||
/* When the sinks or sources change, use the count state to force a rerender to update the connection lines */
|
||||
@ -180,7 +181,7 @@ export function AudioMenu(props: { open: boolean; onClose: () => void; children?
|
||||
if (sink instanceof RadioSink)
|
||||
return (
|
||||
<RadioSinkPanel
|
||||
shortcutKey={shortcutKeys[idx]}
|
||||
shortcutKeys={shortcuts[`PTT${idx}Active`].toActions()}
|
||||
key={sink.getName()}
|
||||
radio={sink}
|
||||
onExpanded={() => {
|
||||
@ -218,7 +219,7 @@ export function AudioMenu(props: { open: boolean; onClose: () => void; children?
|
||||
if (sink instanceof UnitSink)
|
||||
return (
|
||||
<UnitSinkPanel
|
||||
shortcutKey={shortcutKeys[idx]}
|
||||
shortcutKeys={shortcuts[`PTT${idx}Active`].toActions()}
|
||||
key={sink.getName()}
|
||||
sink={sink}
|
||||
ref={sinkRefs[idx]}
|
||||
|
||||
@ -7,7 +7,7 @@ import { faEarListen, faMicrophoneLines } from "@fortawesome/free-solid-svg-icon
|
||||
import { RadioSink } from "../../../audio/radiosink";
|
||||
import { getApp } from "../../../olympusapp";
|
||||
|
||||
export const RadioSinkPanel = forwardRef((props: { radio: RadioSink; shortcutKey: string; onExpanded: () => void }, ref: ForwardedRef<HTMLDivElement>) => {
|
||||
export const RadioSinkPanel = forwardRef((props: { radio: RadioSink; shortcutKeys: string[]; onExpanded: () => void }, ref: ForwardedRef<HTMLDivElement>) => {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
@ -39,22 +39,20 @@ export const RadioSinkPanel = forwardRef((props: { radio: RadioSink; shortcutKey
|
||||
data-expanded={expanded}
|
||||
/>
|
||||
</div>
|
||||
{props.shortcutKey && (
|
||||
{props.shortcutKeys && (
|
||||
<>
|
||||
<kbd
|
||||
className={`
|
||||
my-auto ml-auto rounded-lg border border-gray-200 bg-gray-100
|
||||
px-2 py-1.5 text-xs font-semibold text-gray-800
|
||||
my-auto ml-auto text-nowrap rounded-lg border border-gray-200
|
||||
bg-gray-100 px-2 py-1.5 text-xs font-semibold text-gray-800
|
||||
dark:border-gray-500 dark:bg-gray-600 dark:text-gray-100
|
||||
`}
|
||||
>
|
||||
{props.shortcutKey}
|
||||
{props.shortcutKeys.flatMap((key, idx, array) => [key, idx < array.length - 1 ? " + " : ""])}
|
||||
</kbd>
|
||||
</>
|
||||
)}
|
||||
<span className="my-auto w-full">
|
||||
{props.radio.getName()} {!expanded && `: ${props.radio.getFrequency() / 1e6} MHz ${props.radio.getModulation() ? "FM" : "AM"}`} {}{" "}
|
||||
</span>
|
||||
<span className="my-auto w-full">{props.radio.getName()}</span>
|
||||
<div
|
||||
className={`
|
||||
mb-auto ml-auto aspect-square cursor-pointer rounded-md p-2
|
||||
@ -89,8 +87,12 @@ export const RadioSinkPanel = forwardRef((props: { radio: RadioSink; shortcutKey
|
||||
className="ml-auto"
|
||||
checked={props.radio.getPtt()}
|
||||
icon={faMicrophoneLines}
|
||||
onClick={() => {
|
||||
props.radio.setPtt(!props.radio.getPtt());
|
||||
onClick={() => {}}
|
||||
onMouseDown={() => {
|
||||
props.radio.setPtt(true);
|
||||
}}
|
||||
onMouseUp={() => {
|
||||
props.radio.setPtt(false);
|
||||
}}
|
||||
tooltip="Talk on frequency"
|
||||
></OlStateButton>
|
||||
|
||||
@ -6,7 +6,7 @@ import { OlStateButton } from "../../components/olstatebutton";
|
||||
import { faMicrophoneLines } from "@fortawesome/free-solid-svg-icons";
|
||||
import { OlRangeSlider } from "../../components/olrangeslider";
|
||||
|
||||
export const UnitSinkPanel = forwardRef((props: { sink: UnitSink; shortcutKey: string; onExpanded: () => void }, ref: ForwardedRef<HTMLDivElement>) => {
|
||||
export const UnitSinkPanel = forwardRef((props: { sink: UnitSink; shortcutKeys: string[]; onExpanded: () => void }, ref: ForwardedRef<HTMLDivElement>) => {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
@ -36,20 +36,21 @@ export const UnitSinkPanel = forwardRef((props: { sink: UnitSink; shortcutKey: s
|
||||
data-expanded={expanded}
|
||||
/>
|
||||
</div>
|
||||
{props.shortcutKey && (<>
|
||||
<kbd
|
||||
className={`
|
||||
my-auto ml-auto rounded-lg border border-gray-200 bg-gray-100 px-2
|
||||
py-1.5 text-xs font-semibold text-gray-800
|
||||
dark:border-gray-500 dark:bg-gray-600 dark:text-gray-100
|
||||
`}
|
||||
>
|
||||
{props.shortcutKey}
|
||||
</kbd>
|
||||
{props.shortcutKeys && (
|
||||
<>
|
||||
<kbd
|
||||
className={`
|
||||
my-auto ml-auto text-nowrap rounded-lg border border-gray-200
|
||||
bg-gray-100 px-2 py-1.5 text-xs font-semibold text-gray-800
|
||||
dark:border-gray-500 dark:bg-gray-600 dark:text-gray-100
|
||||
`}
|
||||
>
|
||||
{props.shortcutKeys.flatMap((key, idx, array) => [key, idx < array.length - 1 ? " + " : ""])}
|
||||
</kbd>
|
||||
</>
|
||||
)}
|
||||
<div className="flex w-full overflow-hidden">
|
||||
<span className="my-auto truncate"> {props.sink.getName()}</span>
|
||||
<span className="my-auto truncate"> {props.sink.getName()}</span>
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
@ -79,8 +80,12 @@ export const UnitSinkPanel = forwardRef((props: { sink: UnitSink; shortcutKey: s
|
||||
<OlStateButton
|
||||
checked={props.sink.getPtt()}
|
||||
icon={faMicrophoneLines}
|
||||
onClick={() => {
|
||||
props.sink.setPtt(!props.sink.getPtt());
|
||||
onClick={() => {}}
|
||||
onMouseDown={() => {
|
||||
props.sink.setPtt(true);
|
||||
}}
|
||||
onMouseUp={() => {
|
||||
props.sink.setPtt(false);
|
||||
}}
|
||||
tooltip="Talk on frequency"
|
||||
></OlStateButton>
|
||||
|
||||
@ -1,17 +1,17 @@
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { faHandPointer, faJetFighter, faMap, IconDefinition } from "@fortawesome/free-solid-svg-icons";
|
||||
import { faFighterJet, faHandPointer, faJetFighter, faMap, IconDefinition } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { ContextActionTarget, MAP_OPTIONS_DEFAULTS, NO_SUBSTATE, OlympusState, OlympusSubState, SpawnSubState } from "../../constants/constants";
|
||||
import { AppStateChangedEvent, MapOptionsChangedEvent } from "../../events";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { 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";
|
||||
|
||||
export function ControlsPanel(props: {}) {
|
||||
const [controls, setControls] = useState(
|
||||
null as
|
||||
| {
|
||||
actions: (string | number | IconDefinition)[];
|
||||
target: IconDefinition | null;
|
||||
target?: IconDefinition;
|
||||
text: string;
|
||||
}[]
|
||||
| null
|
||||
@ -19,6 +19,8 @@ export function ControlsPanel(props: {}) {
|
||||
const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED);
|
||||
const [appSubState, setAppSubState] = useState(NO_SUBSTATE as OlympusSubState);
|
||||
const [mapOptions, setMapOptions] = useState(MAP_OPTIONS_DEFAULTS);
|
||||
const [shortcuts, setShortcuts] = useState({})
|
||||
const [contextActionSet, setContextActionSet] = useState(null as null | ContextActionSet)
|
||||
|
||||
useEffect(() => {
|
||||
AppStateChangedEvent.on((state, subState) => {
|
||||
@ -26,65 +28,61 @@ export function ControlsPanel(props: {}) {
|
||||
setAppSubState(subState);
|
||||
});
|
||||
MapOptionsChangedEvent.on((mapOptions) => setMapOptions({ ...mapOptions }));
|
||||
ShortcutsChangedEvent.on((shortcuts) => setShortcuts(shortcuts));
|
||||
ContextActionSetChangedEvent.on((contextActionSet) => setContextActionSet(contextActionSet));
|
||||
}, []);
|
||||
|
||||
const callback = useCallback(() => {
|
||||
const touch = matchMedia("(hover: none)").matches;
|
||||
let controls: {
|
||||
actions: (string | number | IconDefinition)[];
|
||||
target: IconDefinition | null;
|
||||
target?: IconDefinition;
|
||||
text: string;
|
||||
}[] = [];
|
||||
|
||||
const baseControls = [
|
||||
{
|
||||
actions: [touch ? faHandPointer : "LMB"],
|
||||
text: "Select unit",
|
||||
},
|
||||
{
|
||||
actions: [touch ? faHandPointer : "LMB", "Drag"],
|
||||
text: "Box selection",
|
||||
},
|
||||
{
|
||||
actions: [touch ? faHandPointer : "Wheel", "Drag"],
|
||||
text: "Move map",
|
||||
},
|
||||
];
|
||||
if (!touch) {
|
||||
controls.push({
|
||||
actions: ["Shift", "LMB", "Drag"],
|
||||
|
||||
text: "Box selection",
|
||||
});
|
||||
}
|
||||
|
||||
if (appState === OlympusState.IDLE) {
|
||||
controls = [
|
||||
{
|
||||
actions: [touch ? faHandPointer : "LMB"],
|
||||
target: faJetFighter,
|
||||
text: "Select unit",
|
||||
},
|
||||
{
|
||||
actions: touch ? [faHandPointer, "Hold"] : ["RMB"],
|
||||
target: faMap,
|
||||
text: "Quick spawn menu",
|
||||
},
|
||||
{
|
||||
actions: touch ? [faHandPointer, "Drag"] : ["Shift", "LMB", "Drag"],
|
||||
target: faMap,
|
||||
text: "Box selection",
|
||||
},
|
||||
{
|
||||
actions: [touch ? faHandPointer : "LMB", "Drag"],
|
||||
target: faMap,
|
||||
text: "Move map location",
|
||||
},
|
||||
];
|
||||
controls = baseControls;
|
||||
controls.push({
|
||||
actions: touch ? [faHandPointer, "Hold"] : ["RMB"],
|
||||
text: "Quick spawn menu",
|
||||
});
|
||||
} else if (appState === OlympusState.SPAWN_CONTEXT) {
|
||||
controls = [
|
||||
controls = baseControls;
|
||||
controls.push(
|
||||
{
|
||||
actions: [touch ? faHandPointer : "LMB"],
|
||||
target: faJetFighter,
|
||||
text: "Close context menu",
|
||||
},
|
||||
{
|
||||
actions: touch ? [faHandPointer, "Hold"] : ["RMB"],
|
||||
target: faMap,
|
||||
text: "Move context menu",
|
||||
},
|
||||
{
|
||||
actions: touch ? [faHandPointer, "Drag"] : ["Shift", "LMB", "Drag"],
|
||||
target: faMap,
|
||||
text: "Box selection",
|
||||
},
|
||||
{
|
||||
actions: [touch ? faHandPointer : "LMB", "Drag"],
|
||||
target: faMap,
|
||||
text: "Move map location",
|
||||
},
|
||||
];
|
||||
}
|
||||
);
|
||||
} else if (appState === OlympusState.UNIT_CONTROL) {
|
||||
if (!mapOptions.tabletMode) {
|
||||
controls = Object.values(getApp().getMap().getContextActionSet()?.getContextActions() ?? {})
|
||||
controls = Object.values(contextActionSet?.getContextActions() ?? {})
|
||||
.sort((a: ContextAction, b: ContextAction) => (a.getLabel() > b.getLabel() ? 1 : -1))
|
||||
.filter((contextAction: ContextAction) => contextAction.getOptions().code)
|
||||
.map((contextAction: ContextAction) => {
|
||||
@ -95,53 +93,73 @@ export function ControlsPanel(props: {}) {
|
||||
actions.push(
|
||||
(contextAction.getOptions().code as string)
|
||||
.replace("Key", "")
|
||||
.replace("ControlLeft", "Ctrl LH")
|
||||
.replace("AltLeft", "Alt LH")
|
||||
.replace("ShiftLeft", "Shift LH")
|
||||
.replace("ControlRight", "Ctrl RH")
|
||||
.replace("AltRight", "Alt RH")
|
||||
.replace("ShiftRight", "Shift RH")
|
||||
.replace("ControlLeft", "Left Ctrl")
|
||||
.replace("AltLeft", "Left Alt")
|
||||
.replace("ShiftLeft", "Left Shift")
|
||||
.replace("ControlRight", "Right Ctrl")
|
||||
.replace("AltRight", "Right Alt")
|
||||
.replace("ShiftRight", "Right Shift")
|
||||
);
|
||||
contextAction.getTarget() !== ContextActionTarget.NONE && actions.push(touch ? faHandPointer : "LMB");
|
||||
return {
|
||||
actions: actions,
|
||||
target:
|
||||
contextAction.getTarget() === ContextActionTarget.NONE ? null : contextAction.getTarget() === ContextActionTarget.POINT ? faMap : faJetFighter,
|
||||
text: contextAction.getLabel(),
|
||||
};
|
||||
});
|
||||
controls.unshift({
|
||||
actions: ["RMB"],
|
||||
text: "Move",
|
||||
});
|
||||
controls.push({
|
||||
actions: ["RMB", "Hold"],
|
||||
target: faMap,
|
||||
text: "Show point actions",
|
||||
});
|
||||
controls.push({
|
||||
actions: ["RMB", "Hold"],
|
||||
target: faFighterJet,
|
||||
text: "Show unit actions",
|
||||
});
|
||||
controls.push({
|
||||
actions: shortcuts["toggleRelativePositions"]?.toActions(),
|
||||
text: "Activate group movement",
|
||||
});
|
||||
controls.push({
|
||||
actions: [...shortcuts["toggleRelativePositions"]?.toActions(), "Wheel"],
|
||||
text: "Rotate formation",
|
||||
});
|
||||
}
|
||||
} else if (appState === OlympusState.SPAWN) {
|
||||
controls = [
|
||||
{
|
||||
actions: [touch ? faHandPointer : "LMB", 2],
|
||||
target: faMap,
|
||||
text: appSubState === SpawnSubState.NO_SUBSTATE ? "Close spawn menu" : "Return to spawn menu",
|
||||
},
|
||||
{
|
||||
actions: touch ? [faHandPointer, "Drag"] : ["Shift", "LMB", "Drag"],
|
||||
target: faMap,
|
||||
text: "Box selection",
|
||||
},
|
||||
{
|
||||
actions: [touch ? faHandPointer : "LMB", "Drag"],
|
||||
target: faMap,
|
||||
text: "Move map location",
|
||||
},
|
||||
];
|
||||
if (appSubState === SpawnSubState.SPAWN_UNIT) {
|
||||
controls.unshift({
|
||||
actions: [touch ? faHandPointer : "LMB"],
|
||||
target: faMap,
|
||||
text: "Spawn unit",
|
||||
});
|
||||
} else if (appSubState === SpawnSubState.SPAWN_EFFECT) {
|
||||
controls.unshift({
|
||||
actions: [touch ? faHandPointer : "LMB"],
|
||||
target: faMap,
|
||||
text: "Spawn effect",
|
||||
});
|
||||
}
|
||||
} else {
|
||||
controls = baseControls;
|
||||
controls.push({
|
||||
actions: ["LMB"],
|
||||
text: "Return to idle state"
|
||||
})
|
||||
}
|
||||
|
||||
setControls(controls);
|
||||
@ -178,20 +196,23 @@ export function ControlsPanel(props: {}) {
|
||||
return (
|
||||
<div key={idx} className="flex gap-1">
|
||||
<div>
|
||||
{typeof action === "string" || typeof action === "number" ? (
|
||||
action
|
||||
) : (
|
||||
<FontAwesomeIcon
|
||||
icon={action}
|
||||
className={`my-auto ml-auto`}
|
||||
/>
|
||||
)}
|
||||
{typeof action === "string" || typeof action === "number" ? action : <FontAwesomeIcon icon={action} className={`
|
||||
my-auto ml-auto
|
||||
`} />}
|
||||
</div>
|
||||
{idx < control.actions.length - 1 && typeof control.actions[idx + 1] === "string" && <div>+</div>}
|
||||
{idx < control.actions.length - 1 && typeof control.actions[idx + 1] === "number" && <div>x</div>}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{control.target && (
|
||||
<>
|
||||
<div>+</div>
|
||||
<div>
|
||||
<FontAwesomeIcon icon={control.target} />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -9,6 +9,7 @@ import { BindShortcutRequestEvent, MapOptionsChangedEvent, ShortcutsChangedEvent
|
||||
import { OlAccordion } from "../components/olaccordion";
|
||||
import { Shortcut } from "../../shortcut/shortcut";
|
||||
import { OlSearchBar } from "../components/olsearchbar";
|
||||
import { FaTrash, FaXmark } from "react-icons/fa6";
|
||||
|
||||
const enum Accordion {
|
||||
NONE,
|
||||
@ -32,14 +33,12 @@ export function OptionsMenu(props: { open: boolean; onClose: () => void; childre
|
||||
<Menu title="User preferences" open={props.open} showBackButton={false} onClose={props.onClose}>
|
||||
<div
|
||||
className={`
|
||||
flex flex-col gap-2 p-5 font-normal text-gray-800
|
||||
flex h-full flex-col justify-end gap-2 p-5 font-normal text-gray-800
|
||||
dark:text-white
|
||||
`}
|
||||
>
|
||||
<OlAccordion
|
||||
onClick={() =>
|
||||
setOpenAccordion(openAccordion === Accordion.NONE ? Accordion.BINDINGS: Accordion.NONE )
|
||||
}
|
||||
onClick={() => setOpenAccordion(openAccordion === Accordion.NONE ? Accordion.BINDINGS : Accordion.NONE)}
|
||||
open={openAccordion === Accordion.BINDINGS}
|
||||
title="Key bindings"
|
||||
>
|
||||
@ -65,10 +64,40 @@ export function OptionsMenu(props: { open: boolean; onClose: () => void; childre
|
||||
}}
|
||||
>
|
||||
<span>{shortcut.getOptions().label}</span>
|
||||
<span>
|
||||
{shortcut.getOptions().altKey ? "Alt + " : ""}
|
||||
{shortcut.getOptions().ctrlKey ? "Ctrl + " : ""}
|
||||
{shortcut.getOptions().shiftKey ? "Shift + " : ""}
|
||||
<span className="flex gap-1">
|
||||
{shortcut.getOptions().altKey ? (
|
||||
<div className="flex gap-1">
|
||||
<div className={`text-green-500`}>Alt</div> +{" "}
|
||||
</div>
|
||||
) : shortcut.getOptions().altKey === false ? (
|
||||
<div className={`flex gap-1`}>
|
||||
<div className={`text-red-500`}>Alt</div> +{" "}
|
||||
</div>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
{shortcut.getOptions().ctrlKey ? (
|
||||
<div className="flex gap-1">
|
||||
<div className={`text-green-500`}>Shift</div> +{" "}
|
||||
</div>
|
||||
) : shortcut.getOptions().ctrlKey === false ? (
|
||||
<div className={`flex gap-1`}>
|
||||
<div className={`text-red-500`}>Shift</div> +{" "}
|
||||
</div>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
{shortcut.getOptions().shiftKey ? (
|
||||
<div className="flex gap-1">
|
||||
<div className={`text-green-500`}>Ctrl</div> +{" "}
|
||||
</div>
|
||||
) : shortcut.getOptions().shiftKey === false ? (
|
||||
<div className={`flex gap-1`}>
|
||||
<div className={`text-red-500`}>Ctrl</div> +{" "}
|
||||
</div>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
{shortcut.getOptions().code}
|
||||
</span>
|
||||
</div>
|
||||
@ -77,7 +106,11 @@ export function OptionsMenu(props: { open: boolean; onClose: () => void; childre
|
||||
</div>
|
||||
</OlAccordion>
|
||||
|
||||
<OlAccordion onClick={() => setOpenAccordion(openAccordion === Accordion.NONE ? Accordion.MAP_OPTIONS: Accordion.NONE )} open={openAccordion === Accordion.MAP_OPTIONS} title="Map options">
|
||||
<OlAccordion
|
||||
onClick={() => setOpenAccordion(openAccordion === Accordion.NONE ? Accordion.MAP_OPTIONS : Accordion.NONE)}
|
||||
open={openAccordion === Accordion.MAP_OPTIONS}
|
||||
title="Map options"
|
||||
>
|
||||
<div
|
||||
className={`
|
||||
group flex flex-row rounded-md justify-content cursor-pointer
|
||||
@ -133,17 +166,7 @@ export function OptionsMenu(props: { open: boolean; onClose: () => void; childre
|
||||
<OlCheckbox checked={mapOptions.hideUnitsShortRangeRings} onChange={() => {}}></OlCheckbox>
|
||||
<span>Hide Short range Rings</span>
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
group flex flex-row gap-4 rounded-md justify-content
|
||||
cursor-pointer p-2 text-sm
|
||||
dark:hover:bg-olympus-400
|
||||
`}
|
||||
onClick={() => getApp().getMap().setOption("keepRelativePositions", !mapOptions.keepRelativePositions)}
|
||||
>
|
||||
<OlCheckbox checked={mapOptions.keepRelativePositions} onChange={() => {}}></OlCheckbox>
|
||||
<span>Keep units relative positions</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={`
|
||||
group flex flex-row gap-4 rounded-md justify-content
|
||||
@ -168,7 +191,11 @@ export function OptionsMenu(props: { open: boolean; onClose: () => void; childre
|
||||
</div>
|
||||
</OlAccordion>
|
||||
|
||||
<OlAccordion onClick={() => setOpenAccordion(openAccordion === Accordion.NONE ? Accordion.CAMERA_PLUGIN: Accordion.NONE )} open={openAccordion === Accordion.CAMERA_PLUGIN} title="Camera plugin options">
|
||||
<OlAccordion
|
||||
onClick={() => setOpenAccordion(openAccordion === Accordion.NONE ? Accordion.CAMERA_PLUGIN : Accordion.NONE)}
|
||||
open={openAccordion === Accordion.CAMERA_PLUGIN}
|
||||
title="Camera plugin options"
|
||||
>
|
||||
<hr
|
||||
className={`
|
||||
m-2 my-1 w-auto border-[1px] bg-gray-700
|
||||
@ -231,6 +258,41 @@ export function OptionsMenu(props: { open: boolean; onClose: () => void; childre
|
||||
</div>
|
||||
</div>
|
||||
</OlAccordion>
|
||||
|
||||
<div className="mt-auto flex">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => getApp().resetProfile()}
|
||||
className={`
|
||||
mb-2 me-2 flex content-center items-center gap-2 rounded-sm
|
||||
border-[1px] bg-blue-700 px-5 py-2.5 text-sm font-medium
|
||||
text-white
|
||||
dark:border-red-600 dark:bg-gray-800 dark:text-gray-400
|
||||
dark:hover:bg-gray-700 dark:focus:ring-blue-800
|
||||
focus:outline-none focus:ring-4 focus:ring-blue-300
|
||||
hover:bg-blue-800
|
||||
`}
|
||||
>
|
||||
Reset profile
|
||||
<FaXmark />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => getApp().resetAllProfiles()}
|
||||
className={`
|
||||
mb-2 me-2 flex content-center items-center gap-2 rounded-sm
|
||||
border-[1px] bg-blue-700 px-5 py-2.5 text-sm font-medium
|
||||
text-white
|
||||
dark:border-red-600 dark:bg-red-800 dark:text-gray-400
|
||||
dark:hover:bg-red-700 dark:focus:ring-blue-800
|
||||
focus:outline-none focus:ring-4 focus:ring-blue-300
|
||||
hover:bg-red-800
|
||||
`}
|
||||
>
|
||||
Reset all profiles
|
||||
<FaTrash />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Menu>
|
||||
);
|
||||
|
||||
74
frontend/react/src/ui/panels/radiossummarypanel.tsx
Normal file
74
frontend/react/src/ui/panels/radiossummarypanel.tsx
Normal file
@ -0,0 +1,74 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { AudioSinksChangedEvent } from "../../events";
|
||||
import { AudioSink } from "../../audio/audiosink";
|
||||
import { RadioSink } from "../../audio/radiosink";
|
||||
import { FaJetFighter, FaRadio } from "react-icons/fa6";
|
||||
import { OlStateButton } from "../components/olstatebutton";
|
||||
import { UnitSink } from "../../audio/unitsink";
|
||||
|
||||
export function RadiosSummaryPanel(props: {}) {
|
||||
const [audioSinks, setAudioSinks] = useState([] as AudioSink[]);
|
||||
|
||||
useEffect(() => {
|
||||
AudioSinksChangedEvent.on((audioSinks) => setAudioSinks(audioSinks));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
{audioSinks.length > 0 && (
|
||||
<div
|
||||
className={`
|
||||
absolute bottom-[20px] right-[700px] flex w-fit flex-col
|
||||
items-center justify-between gap-2 rounded-lg bg-gray-200 p-3
|
||||
text-sm backdrop-blur-lg backdrop-grayscale
|
||||
dark:bg-olympus-800/90 dark:text-gray-200
|
||||
`}
|
||||
>
|
||||
<div className="flex w-full items-center justify-between gap-2">
|
||||
<FaRadio className="text-xl" />
|
||||
{audioSinks
|
||||
.filter((audioSinks) => audioSinks instanceof RadioSink)
|
||||
.map((radioSink, idx) => {
|
||||
return (
|
||||
<OlStateButton
|
||||
checked={radioSink.getReceiving()}
|
||||
onClick={() => {}}
|
||||
onMouseDown={() => {
|
||||
radioSink.setPtt(true);
|
||||
}}
|
||||
onMouseUp={() => {
|
||||
radioSink.setPtt(false);
|
||||
}}
|
||||
tooltip="Click to talk, lights up when receiving"
|
||||
>
|
||||
<span className={`font-bold text-gray-200`}>{idx + 1}</span>
|
||||
</OlStateButton>
|
||||
);
|
||||
})}
|
||||
|
||||
<FaJetFighter className="text-xl" />
|
||||
{audioSinks
|
||||
.filter((audioSinks) => audioSinks instanceof UnitSink)
|
||||
.map((radioSink, idx) => {
|
||||
return (
|
||||
<OlStateButton
|
||||
checked={false}
|
||||
onClick={() => {}}
|
||||
onMouseDown={() => {
|
||||
radioSink.setPtt(true);
|
||||
}}
|
||||
onMouseUp={() => {
|
||||
radioSink.setPtt(false);
|
||||
}}
|
||||
tooltip="Click to talk"
|
||||
>
|
||||
<span className={`font-bold text-gray-200`}>{idx + 1}</span>
|
||||
</OlStateButton>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -8,6 +8,7 @@ import { FaInfoCircle } from "react-icons/fa";
|
||||
import { FaChevronDown, FaChevronLeft, FaChevronRight, FaChevronUp } from "react-icons/fa6";
|
||||
import { OlympusState } from "../../constants/constants";
|
||||
import { AppStateChangedEvent, ContextActionChangedEvent, ContextActionSetChangedEvent, MapOptionsChangedEvent } from "../../events";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
export function UnitControlBar(props: {}) {
|
||||
const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED);
|
||||
@ -111,24 +112,20 @@ export function UnitControlBar(props: {}) {
|
||||
{contextAction && (
|
||||
<div
|
||||
className={`
|
||||
absolute left-[50%] top-16 flex min-w-[300px]
|
||||
translate-x-[calc(-50%+2rem)] items-center gap-2 rounded-md
|
||||
bg-gray-200 p-4
|
||||
absolute left-[50%] top-16 flex translate-x-[calc(-50%+2rem)]
|
||||
items-center gap-2 rounded-md bg-gray-200 p-4
|
||||
dark:bg-olympus-800
|
||||
`}
|
||||
>
|
||||
<FaInfoCircle
|
||||
<FontAwesomeIcon
|
||||
icon={contextAction.getIcon()}
|
||||
className={`
|
||||
mr-2 hidden min-w-8 text-sm text-blue-500
|
||||
mr-2 hidden text-xl text-blue-500
|
||||
md:block
|
||||
`}
|
||||
/>
|
||||
<div
|
||||
className={`
|
||||
px-2
|
||||
dark:text-gray-400
|
||||
md:border-l-[1px] md:px-5
|
||||
`}
|
||||
className={`text-gray-200`}
|
||||
>
|
||||
{contextAction.getDescription()}
|
||||
</div>
|
||||
|
||||
@ -24,12 +24,13 @@ import { ProtectionPrompt } from "./modals/protectionprompt";
|
||||
import { KeybindModal } from "./modals/keybindmodal";
|
||||
import { UnitExplosionMenu } from "./panels/unitexplosionmenu";
|
||||
import { JTACMenu } from "./panels/jtacmenu";
|
||||
import { AppStateChangedEvent, MapOptionsChangedEvent } from "../events";
|
||||
import { AppStateChangedEvent } from "../events";
|
||||
import { GameMasterMenu } from "./panels/gamemastermenu";
|
||||
import { InfoBar } from "./panels/infobar";
|
||||
import { HotGroupBar } from "./panels/hotgroupsbar";
|
||||
import { SpawnContextMenu } from "./contextmenus/spawncontextmenu";
|
||||
import { CoordinatesPanel } from "./panels/coordinatespanel";
|
||||
import { RadiosSummaryPanel } from "./panels/radiossummarypanel";
|
||||
|
||||
export type OlympusUIState = {
|
||||
mainMenuVisible: boolean;
|
||||
@ -105,6 +106,7 @@ export function UI() {
|
||||
<MiniMapPanel />
|
||||
<ControlsPanel />
|
||||
<CoordinatesPanel />
|
||||
<RadiosSummaryPanel />
|
||||
|
||||
<UnitControlBar />
|
||||
<SideBar />
|
||||
|
||||
@ -40,6 +40,7 @@ import {
|
||||
UnitControlSubState,
|
||||
ContextActions,
|
||||
ContextActionTarget,
|
||||
SHORT_PRESS_MILLISECONDS,
|
||||
} from "../constants/constants";
|
||||
import { DataExtractor } from "../server/dataextractor";
|
||||
import { Weapon } from "../weapon/weapon";
|
||||
@ -157,10 +158,13 @@ export abstract class Unit extends CustomMarker {
|
||||
#detectionMethods: number[] = [];
|
||||
|
||||
/* Inputs timers */
|
||||
#mouseCooldownTimer: number = 0;
|
||||
#shortPressTimer: number = 0;
|
||||
#isMouseOnCooldown: boolean = false;
|
||||
#isMouseDown: boolean = false;
|
||||
#debounceTimeout: number | null = null;
|
||||
#isLeftMouseDown: boolean = false;
|
||||
#isRightMouseDown: boolean = false;
|
||||
#leftMouseDownEpoch: number = 0;
|
||||
#rightMouseDownEpoch: number = 0;
|
||||
#leftMouseDownTimeout: number = 0;
|
||||
#rightMouseDownTimeout: number = 0;
|
||||
|
||||
/* Getters for backend driven data */
|
||||
getAlive() {
|
||||
@ -342,10 +346,11 @@ export abstract class Unit extends CustomMarker {
|
||||
});
|
||||
|
||||
/* Leaflet events listeners */
|
||||
this.on("mousedown", (e) => this.#onMouseDown(e));
|
||||
this.on("mouseup", (e) => this.#onMouseUp(e));
|
||||
this.on("contextmenu", (e) => this.#onRightClick(e));
|
||||
this.on("dblclick", (e) => this.#onDoubleClick(e));
|
||||
this.on("mouseup", (e: any) => this.#onMouseUp(e));
|
||||
this.on("mousedown", (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());
|
||||
|
||||
this.on("mouseover", () => {
|
||||
if (this.belongsToCommandedCoalition()) this.setHighlighted(true);
|
||||
@ -1281,58 +1286,72 @@ export abstract class Unit extends CustomMarker {
|
||||
}
|
||||
|
||||
/***********************************************/
|
||||
#onMouseDown(e: any) {
|
||||
if (e.originalEvent.button === 2) return;
|
||||
|
||||
this.#isMouseDown = true;
|
||||
|
||||
DomEvent.stop(e);
|
||||
DomEvent.preventDefault(e);
|
||||
e.originalEvent.stopImmediatePropagation();
|
||||
|
||||
if (this.#isMouseOnCooldown) return;
|
||||
|
||||
this.#shortPressTimer = window.setTimeout(() => {
|
||||
/* If the mouse is no longer being pressed, execute the short press action */
|
||||
if (!this.#isMouseDown) this.#onLeftClick(e);
|
||||
}, 200);
|
||||
}
|
||||
|
||||
#onMouseUp(e: any) {
|
||||
if (e.originalEvent.button === 2) return;
|
||||
if (e.originalEvent?.button === 0) {
|
||||
DomEvent.stop(e);
|
||||
DomEvent.preventDefault(e);
|
||||
e.originalEvent.stopImmediatePropagation();
|
||||
|
||||
this.#isMouseDown = false;
|
||||
|
||||
if (getApp().getMap().isSelecting()) return;
|
||||
|
||||
DomEvent.stop(e);
|
||||
DomEvent.preventDefault(e);
|
||||
e.originalEvent.stopImmediatePropagation();
|
||||
|
||||
this.#isMouseOnCooldown = true;
|
||||
this.#mouseCooldownTimer = window.setTimeout(() => {
|
||||
this.#isMouseOnCooldown = false;
|
||||
}, 200);
|
||||
}
|
||||
|
||||
#onLeftClick(e: any) {
|
||||
console.log(`Left click on ${this.getUnitName()}`);
|
||||
|
||||
if (getApp().getMap().getContextAction() === null) {
|
||||
if (!e.originalEvent.ctrlKey) getApp().getUnitsManager().deselectAllUnits();
|
||||
this.setSelected(!this.getSelected());
|
||||
} else if (getApp().getState() === OlympusState.UNIT_CONTROL) {
|
||||
if (getApp().getMap().getContextAction()?.getTarget() === ContextActionTarget.UNIT) {
|
||||
getApp().getMap().executeContextAction(this, null, e.originalEvent);
|
||||
} else {
|
||||
if (!e.originalEvent.ctrlKey) getApp().getUnitsManager().deselectAllUnits();
|
||||
this.setSelected(!this.getSelected());
|
||||
if (Date.now() - this.#leftMouseDownEpoch < SHORT_PRESS_MILLISECONDS) this.#onLeftShortClick(e);
|
||||
this.#isLeftMouseDown = false;
|
||||
} else if (e.originalEvent?.button === 2) {
|
||||
if (getApp().getState() === OlympusState.UNIT_CONTROL && getApp().getMap().getContextAction()?.getTarget() !== ContextActionTarget.POINT) {
|
||||
DomEvent.stop(e);
|
||||
DomEvent.preventDefault(e);
|
||||
e.originalEvent.stopImmediatePropagation();
|
||||
}
|
||||
|
||||
if (Date.now() - this.#rightMouseDownEpoch < SHORT_PRESS_MILLISECONDS) this.#onRightShortClick(e);
|
||||
this.#isRightMouseDown = false;
|
||||
}
|
||||
}
|
||||
|
||||
#onRightClick(e: any) {
|
||||
console.log(`Right click on ${this.getUnitName()}`);
|
||||
#onMouseDown(e: any) {
|
||||
if (e.originalEvent?.button === 0) {
|
||||
DomEvent.stop(e);
|
||||
DomEvent.preventDefault(e);
|
||||
e.originalEvent.stopImmediatePropagation();
|
||||
|
||||
this.#isLeftMouseDown = true;
|
||||
this.#leftMouseDownEpoch = Date.now();
|
||||
} else if (e.originalEvent?.button === 2) {
|
||||
if (getApp().getState() === OlympusState.UNIT_CONTROL && getApp().getMap().getContextAction()?.getTarget() !== ContextActionTarget.POINT) {
|
||||
DomEvent.stop(e);
|
||||
DomEvent.preventDefault(e);
|
||||
e.originalEvent.stopImmediatePropagation();
|
||||
}
|
||||
|
||||
this.#isRightMouseDown = true;
|
||||
this.#rightMouseDownEpoch = Date.now();
|
||||
this.#rightMouseDownTimeout = window.setTimeout(() => {
|
||||
this.#onRightLongClick(e);
|
||||
}, SHORT_PRESS_MILLISECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
#onLeftShortClick(e: any) {
|
||||
DomEvent.stop(e);
|
||||
DomEvent.preventDefault(e);
|
||||
e.originalEvent.stopImmediatePropagation();
|
||||
|
||||
if (this.#debounceTimeout) window.clearTimeout(this.#debounceTimeout);
|
||||
this.#debounceTimeout = window.setTimeout(() => {
|
||||
console.log(`Left short click on ${this.getUnitName()}`);
|
||||
|
||||
if (!e.originalEvent.ctrlKey) getApp().getUnitsManager().deselectAllUnits();
|
||||
this.setSelected(!this.getSelected());
|
||||
}, SHORT_PRESS_MILLISECONDS);
|
||||
}
|
||||
|
||||
#onRightShortClick(e: any) {
|
||||
console.log(`Right short click on ${this.getUnitName()}`);
|
||||
|
||||
if (getApp().getState() === OlympusState.UNIT_CONTROL && getApp().getMap().getContextAction()?.getTarget() === ContextActionTarget.UNIT)
|
||||
getApp().getMap().executeContextAction(this, null, e.originalEvent);
|
||||
}
|
||||
|
||||
#onRightLongClick(e: any) {
|
||||
console.log(`Right long click on ${this.getUnitName()}`);
|
||||
|
||||
if (getApp().getState() === OlympusState.UNIT_CONTROL && !getApp().getMap().getContextAction()) {
|
||||
DomEvent.stop(e);
|
||||
@ -1347,18 +1366,17 @@ export abstract class Unit extends CustomMarker {
|
||||
#onDoubleClick(e: any) {
|
||||
DomEvent.stop(e);
|
||||
DomEvent.preventDefault(e);
|
||||
e.originalEvent.stopImmediatePropagation();
|
||||
|
||||
console.log(`Double click on ${this.getUnitName()}`);
|
||||
|
||||
window.clearTimeout(this.#shortPressTimer);
|
||||
if (this.#debounceTimeout) window.clearTimeout(this.#debounceTimeout);
|
||||
|
||||
/* Select all matching units in the viewport */
|
||||
if (getApp().getState() === OlympusState.IDLE || getApp().getState() === OlympusState.UNIT_CONTROL) {
|
||||
const unitsManager = getApp().getUnitsManager();
|
||||
Object.values(unitsManager.getUnits()).forEach((unit: Unit) => {
|
||||
if (unit.getAlive() === true && unit.getName() === this.getName() && unit.isInViewport()) unitsManager.selectUnit(unit.ID, false);
|
||||
});
|
||||
}
|
||||
const unitsManager = getApp().getUnitsManager();
|
||||
Object.values(unitsManager.getUnits()).forEach((unit: Unit) => {
|
||||
if (unit.getAlive() === true && unit.getName() === this.getName() && unit.isInViewport()) unitsManager.selectUnit(unit.ID, false);
|
||||
});
|
||||
}
|
||||
|
||||
#updateMarker() {
|
||||
|
||||
@ -79,36 +79,66 @@ export class UnitsManager {
|
||||
},
|
||||
code: "KeyA",
|
||||
ctrlKey: true,
|
||||
shiftKey: false,
|
||||
altKey: false
|
||||
})
|
||||
.addShortcut("copyUnits", {
|
||||
label: "Copy units",
|
||||
keyUpCallback: () => this.copy(),
|
||||
code: "KeyC",
|
||||
ctrlKey: true,
|
||||
shiftKey: false,
|
||||
altKey: false
|
||||
})
|
||||
.addShortcut("pasteUnits", {
|
||||
label: "Paste units",
|
||||
keyUpCallback: () => this.paste(),
|
||||
code: "KeyV",
|
||||
ctrlKey: true,
|
||||
shiftKey: false,
|
||||
altKey: false
|
||||
});
|
||||
|
||||
const digits = ["Digit1", "Digit2", "Digit3", "Digit4", "Digit5", "Digit6", "Digit7", "Digit8", "Digit9"];
|
||||
digits.forEach((code, idx) => {
|
||||
getApp()
|
||||
.getShortcutManager()
|
||||
.addShortcut(`hotgroup${idx}`, {
|
||||
label: `Hotgroup ${idx} management`,
|
||||
.addShortcut(`hotgroup${idx + 1}only`, {
|
||||
label: `Hotgroup ${idx + 1} (Select only)`,
|
||||
keyUpCallback: (ev: KeyboardEvent) => {
|
||||
if (ev.ctrlKey && ev.shiftKey) this.selectUnitsByHotgroup(parseInt(ev.code.substring(5)), false);
|
||||
// "Select hotgroup X in addition to any units already selected"
|
||||
else if (ev.ctrlKey && !ev.shiftKey) this.setHotgroup(parseInt(ev.code.substring(5)));
|
||||
// "These selected units are hotgroup X (forget any previous membership)"
|
||||
else if (!ev.ctrlKey && ev.shiftKey) this.addToHotgroup(parseInt(ev.code.substring(5)));
|
||||
// "Add (append) these units to hotgroup X (in addition to any existing members)"
|
||||
else this.selectUnitsByHotgroup(parseInt(ev.code.substring(5))); // "Select hotgroup X, deselect any units not in it."
|
||||
this.selectUnitsByHotgroup(parseInt(ev.code.substring(5)));
|
||||
},
|
||||
code: code,
|
||||
shiftKey: false,
|
||||
altKey: false,
|
||||
ctrlKey: false
|
||||
}).addShortcut(`hotgroup${idx + 1}add`, {
|
||||
label: `Hotgroup ${idx + 1} (Add to)`,
|
||||
keyUpCallback: (ev: KeyboardEvent) => {
|
||||
this.addToHotgroup(parseInt(ev.code.substring(5)));
|
||||
},
|
||||
code: code,
|
||||
shiftKey: true,
|
||||
altKey: false,
|
||||
ctrlKey: false
|
||||
}).addShortcut(`hotgroup${idx + 1}set`, {
|
||||
label: `Hotgroup ${idx + 1} (Set)`,
|
||||
keyUpCallback: (ev: KeyboardEvent) => {
|
||||
this.setHotgroup(parseInt(ev.code.substring(5)));
|
||||
},
|
||||
code: code,
|
||||
ctrlKey: true,
|
||||
altKey: false,
|
||||
shiftKey: false
|
||||
}).addShortcut(`hotgroup${idx + 1}also`, {
|
||||
label: `Hotgroup ${idx + 1} (Select also)`,
|
||||
keyUpCallback: (ev: KeyboardEvent) => {
|
||||
this.selectUnitsByHotgroup(parseInt(ev.code.substring(5)), false);
|
||||
},
|
||||
code: code,
|
||||
ctrlKey: true,
|
||||
shiftKey: true,
|
||||
altKey: false
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -1,33 +1,74 @@
|
||||
import express = require('express');
|
||||
import fs = require('fs');
|
||||
import express = require("express");
|
||||
import fs = require("fs");
|
||||
const router = express.Router();
|
||||
|
||||
module.exports = function (configLocation) {
|
||||
router.get('/config', function (req, res, next) {
|
||||
if (fs.existsSync(configLocation)) {
|
||||
let rawdata = fs.readFileSync(configLocation, "utf-8");
|
||||
const config = JSON.parse(rawdata);
|
||||
res.send(JSON.stringify({frontend:{...config.frontend}, audio:{...(config.audio ?? {})}, profiles: {...(config.profiles ?? {})} }));
|
||||
res.end()
|
||||
} else {
|
||||
res.sendStatus(404);
|
||||
}
|
||||
});
|
||||
router.get("/config", function (req, res, next) {
|
||||
if (fs.existsSync(configLocation)) {
|
||||
let rawdata = fs.readFileSync(configLocation, "utf-8");
|
||||
const config = JSON.parse(rawdata);
|
||||
res.send(
|
||||
JSON.stringify({
|
||||
frontend: { ...config.frontend },
|
||||
audio: { ...(config.audio ?? {}) },
|
||||
profiles: { ...(config.profiles ?? {}) },
|
||||
})
|
||||
);
|
||||
res.end();
|
||||
} else {
|
||||
res.sendStatus(404);
|
||||
}
|
||||
});
|
||||
|
||||
router.put('/profile/:profileName', function (req, res, next) {
|
||||
if (fs.existsSync(configLocation)) {
|
||||
let rawdata = fs.readFileSync(configLocation, "utf-8");
|
||||
const config = JSON.parse(rawdata);
|
||||
if (config.profiles === undefined)
|
||||
config.profiles = {}
|
||||
config.profiles[req.params.profileName] = req.body;
|
||||
fs.writeFileSync(configLocation, JSON.stringify(config, null, 2), "utf-8");
|
||||
res.end()
|
||||
} else {
|
||||
res.sendStatus(404);
|
||||
}
|
||||
});
|
||||
router.put("/profile/:profileName", function (req, res, next) {
|
||||
if (fs.existsSync(configLocation)) {
|
||||
let rawdata = fs.readFileSync(configLocation, "utf-8");
|
||||
const config = JSON.parse(rawdata);
|
||||
if (config.profiles === undefined) config.profiles = {};
|
||||
config.profiles[req.params.profileName] = req.body;
|
||||
fs.writeFileSync(
|
||||
configLocation,
|
||||
JSON.stringify(config, null, 2),
|
||||
"utf-8"
|
||||
);
|
||||
res.end();
|
||||
} else {
|
||||
res.sendStatus(404);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
return router;
|
||||
}
|
||||
router.put("/profile/reset/:profileName", function (req, res, next) {
|
||||
if (fs.existsSync(configLocation)) {
|
||||
let rawdata = fs.readFileSync(configLocation, "utf-8");
|
||||
const config = JSON.parse(rawdata);
|
||||
if (config.profiles[req.params.profileName])
|
||||
delete config.profiles[req.params.profileName];
|
||||
fs.writeFileSync(
|
||||
configLocation,
|
||||
JSON.stringify(config, null, 2),
|
||||
"utf-8"
|
||||
);
|
||||
res.end();
|
||||
} else {
|
||||
res.sendStatus(404);
|
||||
}
|
||||
});
|
||||
|
||||
router.put("/profile/resetall", function (req, res, next) {
|
||||
if (fs.existsSync(configLocation)) {
|
||||
let rawdata = fs.readFileSync(configLocation, "utf-8");
|
||||
const config = JSON.parse(rawdata);
|
||||
config.profiles = {};
|
||||
fs.writeFileSync(
|
||||
configLocation,
|
||||
JSON.stringify(config, null, 2),
|
||||
"utf-8"
|
||||
);
|
||||
res.end();
|
||||
} else {
|
||||
res.sendStatus(404);
|
||||
}
|
||||
});
|
||||
|
||||
return router;
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user