mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
Added spawn context menu and controls rework
This commit is contained in:
parent
430d8db15d
commit
38f6788fa8
@ -42,7 +42,7 @@ export class AudioManager {
|
||||
config.audio.WSPort ? this.setPort(config.audio.WSPort) : this.setEndpoint(config.audio.WSEndpoint);
|
||||
});
|
||||
|
||||
let PTTKeys = ["KeyZ", "KeyX", "KeyC", "KeyV", "KeyB", "KeyN", "KeyM", "KeyK", "KeyL"];
|
||||
let PTTKeys = ["KeyZ", "KeyX", "KeyC", "KeyV", "KeyB", "KeyN", "KeyM", "KeyComma", "KeyDot"];
|
||||
PTTKeys.forEach((key, idx) => {
|
||||
getApp()
|
||||
.getShortcutManager()
|
||||
@ -50,7 +50,8 @@ export class AudioManager {
|
||||
label: `PTT ${idx} active`,
|
||||
keyDownCallback: () => this.getSinks()[idx]?.setPtt(true),
|
||||
keyUpCallback: () => this.getSinks()[idx]?.setPtt(false),
|
||||
code: key
|
||||
code: key,
|
||||
shiftKey: true
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -265,7 +265,7 @@ export enum OlympusState {
|
||||
MAIN_MENU = "Main menu",
|
||||
UNIT_CONTROL = "Unit control",
|
||||
SPAWN = "Spawn",
|
||||
STARRED_SPAWN = "Starred spawn",
|
||||
SPAWN_CONTEXT = "Spawn context",
|
||||
DRAW = "Draw",
|
||||
JTAC = "JTAC",
|
||||
OPTIONS = "Options",
|
||||
@ -336,6 +336,7 @@ export const MAP_OPTIONS_DEFAULTS: MapOptions = {
|
||||
cameraPluginRatio: 1,
|
||||
cameraPluginEnabled: false,
|
||||
cameraPluginMode: "map",
|
||||
tabletMode: false
|
||||
};
|
||||
|
||||
export const MAP_HIDDEN_TYPES_DEFAULTS = {
|
||||
@ -457,7 +458,7 @@ export namespace ContextActions {
|
||||
{
|
||||
executeImmediately: true,
|
||||
type: ContextActionType.MOVE,
|
||||
hotkey: "KeyZ",
|
||||
code: "Space",
|
||||
}
|
||||
);
|
||||
|
||||
@ -474,7 +475,7 @@ export namespace ContextActions {
|
||||
.getUnitsManager()
|
||||
.addDestination(targetPosition, getApp().getMap().getOptions().keepRelativePositions, getApp().getMap().getDestinationRotation(), units);
|
||||
},
|
||||
{ type: ContextActionType.MOVE, hotkey: "KeyX" }
|
||||
{ type: ContextActionType.MOVE, code: null }
|
||||
);
|
||||
|
||||
export const PATH = new ContextAction(
|
||||
@ -489,7 +490,7 @@ export namespace ContextActions {
|
||||
.getUnitsManager()
|
||||
.addDestination(targetPosition, getApp().getMap().getOptions().keepRelativePositions, getApp().getMap().getDestinationRotation(), units);
|
||||
},
|
||||
{ type: ContextActionType.MOVE, hotkey: "KeyC" }
|
||||
{ type: ContextActionType.MOVE, code: "ControlLeft" }
|
||||
);
|
||||
|
||||
export const DELETE = new ContextAction(
|
||||
@ -504,6 +505,7 @@ export namespace ContextActions {
|
||||
{
|
||||
executeImmediately: true,
|
||||
type: ContextActionType.DELETE,
|
||||
code: "Delete"
|
||||
}
|
||||
);
|
||||
|
||||
@ -520,6 +522,8 @@ export namespace ContextActions {
|
||||
{
|
||||
executeImmediately: true,
|
||||
type: ContextActionType.DELETE,
|
||||
code: "Delete",
|
||||
ctrlKey: true
|
||||
}
|
||||
);
|
||||
|
||||
@ -532,7 +536,7 @@ export namespace ContextActions {
|
||||
(units: Unit[]) => {
|
||||
getApp().getMap().centerOnUnit(units[0]);
|
||||
},
|
||||
{ executeImmediately: true, type: ContextActionType.OTHER }
|
||||
{ executeImmediately: true, type: ContextActionType.OTHER, code: "KeyM", altKey: true }
|
||||
);
|
||||
|
||||
export const REFUEL = new ContextAction(
|
||||
@ -544,7 +548,7 @@ export namespace ContextActions {
|
||||
(units: Unit[]) => {
|
||||
getApp().getUnitsManager().refuel(units);
|
||||
},
|
||||
{ executeImmediately: true, type: ContextActionType.ADMIN }
|
||||
{ executeImmediately: true, type: ContextActionType.ADMIN, code: "KeyV" }
|
||||
);
|
||||
|
||||
export const FOLLOW = new ContextAction(
|
||||
@ -562,7 +566,7 @@ export namespace ContextActions {
|
||||
);
|
||||
}
|
||||
},
|
||||
{ type: ContextActionType.ADMIN }
|
||||
{ type: ContextActionType.ADMIN, code: "KeyF" }
|
||||
);
|
||||
|
||||
export const BOMB = new ContextAction(
|
||||
@ -577,7 +581,7 @@ export namespace ContextActions {
|
||||
.getUnitsManager()
|
||||
.bombPoint(targetPosition, getApp().getMap().getOptions().keepRelativePositions, getApp().getMap().getDestinationRotation(), units);
|
||||
},
|
||||
{ type: ContextActionType.ENGAGE }
|
||||
{ type: ContextActionType.ENGAGE, code: "KeyB" }
|
||||
);
|
||||
|
||||
export const CARPET_BOMB = new ContextAction(
|
||||
@ -592,7 +596,7 @@ export namespace ContextActions {
|
||||
.getUnitsManager()
|
||||
.carpetBomb(targetPosition, getApp().getMap().getOptions().keepRelativePositions, getApp().getMap().getDestinationRotation(), units);
|
||||
},
|
||||
{ type: ContextActionType.ENGAGE }
|
||||
{ type: ContextActionType.ENGAGE, code: "KeyB", altKey: true }
|
||||
);
|
||||
|
||||
export const LAND = new ContextAction(
|
||||
@ -604,7 +608,7 @@ export namespace ContextActions {
|
||||
(units: Unit[], _, targetPosition: LatLng | null) => {
|
||||
if (targetPosition) getApp().getUnitsManager().landAt(targetPosition, units);
|
||||
},
|
||||
{ type: ContextActionType.ADMIN }
|
||||
{ type: ContextActionType.ADMIN, code: "KeyL" }
|
||||
);
|
||||
|
||||
export const LAND_AT_POINT = new ContextAction(
|
||||
@ -619,7 +623,7 @@ export namespace ContextActions {
|
||||
.getUnitsManager()
|
||||
.landAtPoint(targetPosition, getApp().getMap().getOptions().keepRelativePositions, getApp().getMap().getDestinationRotation(), units);
|
||||
},
|
||||
{ type: ContextActionType.ADMIN }
|
||||
{ type: ContextActionType.ADMIN, code: "KeyL", altKey: true }
|
||||
);
|
||||
|
||||
export const GROUP = new ContextAction(
|
||||
@ -631,7 +635,7 @@ export namespace ContextActions {
|
||||
(units: Unit[], _1, _2) => {
|
||||
getApp().getUnitsManager().createGroup(units);
|
||||
},
|
||||
{ executeImmediately: true, type: ContextActionType.OTHER }
|
||||
{ executeImmediately: true, type: ContextActionType.OTHER, code: "KeyG" }
|
||||
);
|
||||
|
||||
export const ATTACK = new ContextAction(
|
||||
@ -643,7 +647,7 @@ export namespace ContextActions {
|
||||
(units: Unit[], targetUnit: Unit | null, _) => {
|
||||
if (targetUnit) getApp().getUnitsManager().attackUnit(targetUnit.ID, units);
|
||||
},
|
||||
{ type: ContextActionType.ENGAGE }
|
||||
{ type: ContextActionType.ENGAGE, code: "KeyZ" }
|
||||
);
|
||||
|
||||
export const FIRE_AT_AREA = new ContextAction(
|
||||
@ -658,7 +662,7 @@ export namespace ContextActions {
|
||||
.getUnitsManager()
|
||||
.fireAtArea(targetPosition, getApp().getMap().getOptions().keepRelativePositions, getApp().getMap().getDestinationRotation(), units);
|
||||
},
|
||||
{ type: ContextActionType.ENGAGE }
|
||||
{ type: ContextActionType.ENGAGE, code: "KeyZ", altKey: true }
|
||||
);
|
||||
|
||||
export const SIMULATE_FIRE_FIGHT = new ContextAction(
|
||||
@ -673,6 +677,6 @@ export namespace ContextActions {
|
||||
.getUnitsManager()
|
||||
.simulateFireFight(targetPosition, getApp().getMap().getOptions().keepRelativePositions, getApp().getMap().getDestinationRotation(), units);
|
||||
},
|
||||
{ type: ContextActionType.ADMIN }
|
||||
{ type: ContextActionType.ADMIN, code: "KeyX" }
|
||||
);
|
||||
}
|
||||
|
||||
@ -98,19 +98,6 @@ export class InfoPopupEvent {
|
||||
}
|
||||
}
|
||||
|
||||
export class HideMenuEvent {
|
||||
static on(callback: (hidden: boolean) => void) {
|
||||
document.addEventListener(this.name, (ev: CustomEventInit) => {
|
||||
callback(ev.detail.hidden);
|
||||
});
|
||||
}
|
||||
|
||||
static dispatch(hidden: boolean) {
|
||||
document.dispatchEvent(new CustomEvent(this.name, { detail: { hidden } }));
|
||||
console.log(`Event ${this.name} dispatched`);
|
||||
}
|
||||
}
|
||||
|
||||
export class ShortcutsChangedEvent {
|
||||
static on(callback: (shortcuts: { [key: string]: Shortcut }) => void) {
|
||||
document.addEventListener(this.name, (ev: CustomEventInit) => {
|
||||
@ -332,7 +319,7 @@ export class UnitContextMenuRequestEvent {
|
||||
}
|
||||
}
|
||||
|
||||
export class StarredSpawnContextMenuRequestEvent {
|
||||
export class SpawnContextMenuRequestEvent {
|
||||
static on(callback: (latlng: L.LatLng) => void) {
|
||||
document.addEventListener(this.name, (ev: CustomEventInit) => {
|
||||
callback(ev.detail.latlng);
|
||||
|
||||
@ -53,7 +53,7 @@ import {
|
||||
MapSourceChangedEvent,
|
||||
MouseMovedEvent,
|
||||
SelectionClearedEvent,
|
||||
StarredSpawnContextMenuRequestEvent,
|
||||
SpawnContextMenuRequestEvent,
|
||||
StarredSpawnsChangedEvent,
|
||||
UnitDeselectedEvent,
|
||||
UnitSelectedEvent,
|
||||
@ -79,12 +79,6 @@ export class Map extends L.Map {
|
||||
#mapLayers: any = defaultMapLayers;
|
||||
#mapMirrors: any = defaultMapMirrors;
|
||||
|
||||
/* Inputs timers */
|
||||
#mouseCooldownTimer: number = 0;
|
||||
#shortPressTimer: number = 0;
|
||||
#longPressTimer: number = 0;
|
||||
#selecting: boolean = false;
|
||||
|
||||
/* Camera keyboard panning control */
|
||||
defaultPanDelta: number = 100;
|
||||
#panInterval: number | null = null;
|
||||
@ -103,13 +97,13 @@ export class Map extends L.Map {
|
||||
#miniMapPolyline: L.Polyline;
|
||||
|
||||
/* Other state controls */
|
||||
#isMouseOnCooldown: boolean = false;
|
||||
#isZooming: boolean = false;
|
||||
#isDragging: boolean = false;
|
||||
#isMouseDown: boolean = false;
|
||||
#lastMousePosition: L.Point = new L.Point(0, 0);
|
||||
#lastMouseCoordinates: L.LatLng = new L.LatLng(0, 0);
|
||||
#previousZoom: number = 0;
|
||||
#selecting: boolean = false;
|
||||
|
||||
/* Camera control plugin */
|
||||
#slaveDCSCamera: boolean = false;
|
||||
@ -189,10 +183,10 @@ export class Map extends L.Map {
|
||||
this.on("selectionstart", (e: any) => this.#onSelectionStart(e));
|
||||
this.on("selectionend", (e: any) => this.#onSelectionEnd(e));
|
||||
|
||||
this.on("dblclick", (e: any) => this.#onDoubleClick(e));
|
||||
this.on("mouseup", (e: any) => this.#onMouseUp(e));
|
||||
this.on("mousedown", (e: any) => this.#onMouseDown(e));
|
||||
this.on("contextmenu", (e: any) => e.originalEvent.preventDefault());
|
||||
this.on("click", (e: any) => this.#onLeftClick(e));
|
||||
this.on("contextmenu", (e: any) => this.#onRightClick(e));
|
||||
|
||||
this.on("mousemove", (e: any) => this.#onMouseMove(e));
|
||||
|
||||
@ -301,67 +295,78 @@ export class Map extends L.Map {
|
||||
label: "Hide/show labels",
|
||||
keyUpCallback: () => this.setOption("showUnitLabels", !this.getOptions().showUnitLabels),
|
||||
code: "KeyL",
|
||||
shiftKey: true
|
||||
})
|
||||
.addShortcut("toggleAcquisitionRings", {
|
||||
label: "Hide/show acquisition rings",
|
||||
keyUpCallback: () => this.setOption("showUnitsAcquisitionRings", !this.getOptions().showUnitsAcquisitionRings),
|
||||
code: "KeyE",
|
||||
shiftKey: true
|
||||
})
|
||||
.addShortcut("toggleEngagementRings", {
|
||||
label: "Hide/show engagement rings",
|
||||
keyUpCallback: () => this.setOption("showUnitsEngagementRings", !this.getOptions().showUnitsEngagementRings),
|
||||
code: "KeyQ",
|
||||
shiftKey: true
|
||||
})
|
||||
.addShortcut("toggleHideShortEngagementRings", {
|
||||
label: "Hide/show short range rings",
|
||||
keyUpCallback: () => this.setOption("hideUnitsShortRangeRings", !this.getOptions().hideUnitsShortRangeRings),
|
||||
code: "KeyR",
|
||||
shiftKey: true
|
||||
})
|
||||
.addShortcut("toggleDetectionLines", {
|
||||
label: "Hide/show detection lines",
|
||||
keyUpCallback: () => this.setOption("showUnitTargets", !this.getOptions().showUnitTargets),
|
||||
code: "KeyF",
|
||||
shiftKey: true
|
||||
})
|
||||
.addShortcut("toggleGroupMembers", {
|
||||
label: "Hide/show group members",
|
||||
keyUpCallback: () => this.setOption("hideGroupMembers", !this.getOptions().hideGroupMembers),
|
||||
code: "KeyG",
|
||||
shiftKey: true
|
||||
})
|
||||
.addShortcut("toggleRelativePositions", {
|
||||
label: "Toggle group movement mode",
|
||||
keyUpCallback: () => this.setOption("keepRelativePositions", !this.getOptions().keepRelativePositions),
|
||||
code: "KeyP",
|
||||
shiftKey: true
|
||||
})
|
||||
.addShortcut("increaseCameraZoom", {
|
||||
label: "Increase camera zoom",
|
||||
altKey: true,
|
||||
keyUpCallback: () => this.increaseCameraZoom(),
|
||||
code: "Equal",
|
||||
shiftKey: true
|
||||
})
|
||||
.addShortcut("decreaseCameraZoom", {
|
||||
label: "Decrease camera zoom",
|
||||
altKey: true,
|
||||
keyUpCallback: () => this.decreaseCameraZoom(),
|
||||
code: "Minus",
|
||||
shiftKey: true
|
||||
});
|
||||
|
||||
for (let contextActionName in ContextActions) {
|
||||
if (ContextActions[contextActionName].getOptions().hotkey) {
|
||||
const contextAction = ContextActions[contextActionName] as ContextAction;
|
||||
if (contextAction.getOptions().code) {
|
||||
getApp()
|
||||
.getShortcutManager()
|
||||
.addShortcut(`${contextActionName}Hotkey`, {
|
||||
label: ContextActions[contextActionName].getLabel(),
|
||||
code: ContextActions[contextActionName].getOptions().hotkey,
|
||||
shiftKey: true,
|
||||
keyUpCallback: () => {
|
||||
label: contextAction.getLabel(),
|
||||
code: contextAction.getOptions().code as string,
|
||||
shiftKey: contextAction.getOptions().shiftKey,
|
||||
altKey: contextAction.getOptions().altKey,
|
||||
ctrlKey: contextAction.getOptions().ctrlKey,
|
||||
keyDownCallback: () => {
|
||||
const contextActionSet = this.getContextActionSet();
|
||||
if (
|
||||
getApp().getState() === OlympusState.UNIT_CONTROL &&
|
||||
contextActionSet &&
|
||||
ContextActions[contextActionName].getId() in contextActionSet.getContextActions()
|
||||
) {
|
||||
if (ContextActions[contextActionName].getOptions().executeImmediately) ContextActions[contextActionName].executeCallback();
|
||||
else this.setContextAction(ContextActions[contextActionName]);
|
||||
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) {
|
||||
this.setContextAction(null);
|
||||
}
|
||||
},
|
||||
});
|
||||
@ -517,212 +522,6 @@ export class Map extends L.Map {
|
||||
ContextActionChangedEvent.dispatch(this.#contextAction);
|
||||
}
|
||||
|
||||
getCurrentControls() {
|
||||
//const touch = matchMedia("(hover: none)").matches;
|
||||
//if (getApp().getState() === IDLE) {
|
||||
// return [
|
||||
// {
|
||||
// actions: [touch ? faHandPointer : "LMB"],
|
||||
// target: faJetFighter,
|
||||
// text: "Select unit",
|
||||
// },
|
||||
// touch
|
||||
// ? {
|
||||
// actions: [faHandPointer, "Drag"],
|
||||
// target: faMap,
|
||||
// text: "Box selection",
|
||||
// }
|
||||
// : {
|
||||
// actions: ["Shift", "LMB", "Drag"],
|
||||
// target: faMap,
|
||||
// text: "Box selection",
|
||||
// },
|
||||
// {
|
||||
// actions: [touch ? faHandPointer : "LMB", "Drag"],
|
||||
// target: faMap,
|
||||
// text: "Move map location",
|
||||
// },
|
||||
// ];
|
||||
//} else if (getApp().getState() === OlympusState.SPAWN_UNIT) {
|
||||
// return [
|
||||
// {
|
||||
// actions: [touch ? faHandPointer : "LMB"],
|
||||
// target: faMap,
|
||||
// text: "Spawn unit",
|
||||
// },
|
||||
// {
|
||||
// actions: [touch ? faHandPointer : "LMB", 2],
|
||||
// target: faMap,
|
||||
// text: "Exit spawn mode",
|
||||
// },
|
||||
// {
|
||||
// actions: [touch ? faHandPointer : "LMB", "Drag"],
|
||||
// target: faMap,
|
||||
// text: "Move map location",
|
||||
// },
|
||||
// ];
|
||||
//} else if (getApp().getState() === SPAWN_EFFECT) {
|
||||
// return [
|
||||
// {
|
||||
// actions: [touch ? faHandPointer : "LMB"],
|
||||
// target: faMap,
|
||||
// text: "Spawn effect",
|
||||
// },
|
||||
// {
|
||||
// actions: [touch ? faHandPointer : "LMB", 2],
|
||||
// target: faMap,
|
||||
// text: "Exit spawn mode",
|
||||
// },
|
||||
// {
|
||||
// actions: [touch ? faHandPointer : "LMB", "Drag"],
|
||||
// target: faMap,
|
||||
// text: "Move map location",
|
||||
// },
|
||||
// ];
|
||||
//} else if (getApp().getState() === CONTEXT_ACTION) {
|
||||
// let controls = [
|
||||
// {
|
||||
// actions: [touch ? faHandPointer : "LMB"],
|
||||
// target: faMap,
|
||||
// text: "Deselect units",
|
||||
// },
|
||||
// {
|
||||
// actions: [touch ? faHandPointer : "LMB", "Drag"],
|
||||
// target: faMap,
|
||||
// text: "Move map location",
|
||||
// },
|
||||
// ];
|
||||
//
|
||||
// if (this.#contextAction) {
|
||||
// controls.push({
|
||||
// actions: [touch ? faHandPointer : "LMB"],
|
||||
// target: this.#contextAction.getTarget() === "unit" ? faJetFighter : faMap,
|
||||
// text: this.#contextAction?.getLabel() ?? "",
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// if (!touch && this.#defaultContextAction) {
|
||||
// controls.push({
|
||||
// actions: ["RMB"],
|
||||
// target: faMap,
|
||||
// text: this.#defaultContextAction?.getLabel() ?? "",
|
||||
// });
|
||||
// controls.push({
|
||||
// actions: ["RMB", "hold"],
|
||||
// target: faMap,
|
||||
// text: "Open context menu",
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// return controls;
|
||||
//} else if (getApp().getState() === COALITIONAREA_EDIT) {
|
||||
// return [
|
||||
// {
|
||||
// actions: [touch ? faHandPointer : "LMB"],
|
||||
// target: faDrawPolygon,
|
||||
// text: "Select shape",
|
||||
// },
|
||||
// {
|
||||
// actions: [touch ? faHandPointer : "LMB", 2],
|
||||
// target: faMap,
|
||||
// text: "Exit drawing mode",
|
||||
// },
|
||||
// {
|
||||
// actions: [touch ? faHandPointer : "LMB", "Drag"],
|
||||
// target: faMap,
|
||||
// text: "Move map location",
|
||||
// },
|
||||
// ];
|
||||
//} else if (getApp().getState() === COALITIONAREA_DRAW_POLYGON) {
|
||||
// return [
|
||||
// {
|
||||
// actions: [touch ? faHandPointer : "LMB"],
|
||||
// target: faMap,
|
||||
// text: "Add vertex to polygon",
|
||||
// },
|
||||
// {
|
||||
// actions: [touch ? faHandPointer : "LMB", 2],
|
||||
// target: faMap,
|
||||
// text: "Finalize polygon",
|
||||
// },
|
||||
// {
|
||||
// actions: [touch ? faHandPointer : "LMB", "Drag"],
|
||||
// target: faMap,
|
||||
// text: "Move map location",
|
||||
// },
|
||||
// ];
|
||||
//} else if (getApp().getState() === COALITIONAREA_DRAW_CIRCLE) {
|
||||
// return [
|
||||
// {
|
||||
// actions: [touch ? faHandPointer : "LMB"],
|
||||
// target: faMap,
|
||||
// text: "Add circle",
|
||||
// },
|
||||
// {
|
||||
// actions: [touch ? faHandPointer : "LMB", "Drag"],
|
||||
// target: faMap,
|
||||
// text: "Move map location",
|
||||
// },
|
||||
// ];
|
||||
//} else if (getApp().getState() === SELECT_JTAC_TARGET) {
|
||||
// return [
|
||||
// {
|
||||
// actions: [touch ? faHandPointer : "LMB"],
|
||||
// target: faMap,
|
||||
// text: "Set unit/location as target",
|
||||
// },
|
||||
// {
|
||||
// actions: [touch ? faHandPointer : "LMB", 2],
|
||||
// target: faMap,
|
||||
// text: "Exit selection mode",
|
||||
// },
|
||||
// {
|
||||
// actions: [touch ? faHandPointer : "LMB", "Drag"],
|
||||
// target: faMap,
|
||||
// text: "Move map location",
|
||||
// },
|
||||
// ];
|
||||
//} else if (getApp().getState() === SELECT_JTAC_ECHO) {
|
||||
// return [
|
||||
// {
|
||||
// actions: [touch ? faHandPointer : "LMB"],
|
||||
// target: faMap,
|
||||
// text: "Set location as ECHO point",
|
||||
// },
|
||||
// {
|
||||
// actions: [touch ? faHandPointer : "LMB", 2],
|
||||
// target: faMap,
|
||||
// text: "Exit selection mode",
|
||||
// },
|
||||
// {
|
||||
// actions: [touch ? faHandPointer : "LMB", "Drag"],
|
||||
// target: faMap,
|
||||
// text: "Move map location",
|
||||
// },
|
||||
// ];
|
||||
//} else if (getApp().getState() === SELECT_JTAC_IP) {
|
||||
// return [
|
||||
// {
|
||||
// actions: [touch ? faHandPointer : "LMB"],
|
||||
// target: faMap,
|
||||
// text: "Set location as IP point",
|
||||
// },
|
||||
// {
|
||||
// actions: [touch ? faHandPointer : "LMB", 2],
|
||||
// target: faMap,
|
||||
// text: "Exit selection mode",
|
||||
// },
|
||||
// {
|
||||
// actions: [touch ? faHandPointer : "LMB", "Drag"],
|
||||
// target: faMap,
|
||||
// text: "Move map location",
|
||||
// },
|
||||
// ];
|
||||
//} else {
|
||||
// return [];
|
||||
//}
|
||||
}
|
||||
|
||||
deselectAllCoalitionAreas() {
|
||||
if (this.getSelectedCoalitionArea() !== null) {
|
||||
CoalitionAreaSelectedEvent.dispatch(null);
|
||||
@ -905,12 +704,6 @@ export class Map extends L.Map {
|
||||
this.#contextActionSet?.getDefaultContextAction()?.executeCallback(targetUnit, targetPosition, originalEvent);
|
||||
}
|
||||
|
||||
preventClicks() {
|
||||
console.log("Preventing clicks on map");
|
||||
window.clearTimeout(this.#shortPressTimer);
|
||||
window.clearTimeout(this.#longPressTimer);
|
||||
}
|
||||
|
||||
/* Event handlers */
|
||||
#onStateChanged(state: OlympusState, subState: OlympusSubState) {
|
||||
/* Operations to perform when leaving a state */
|
||||
@ -979,71 +772,26 @@ export class Map extends L.Map {
|
||||
|
||||
#onMouseUp(e: any) {
|
||||
this.#isMouseDown = false;
|
||||
window.clearTimeout(this.#longPressTimer);
|
||||
|
||||
this.scrollWheelZoom.enable();
|
||||
this.dragging.enable();
|
||||
|
||||
this.#isRotatingDestination = false;
|
||||
this.#isMouseOnCooldown = true;
|
||||
this.#mouseCooldownTimer = window.setTimeout(() => {
|
||||
this.#isMouseOnCooldown = false;
|
||||
}, 200);
|
||||
}
|
||||
|
||||
#onMouseDown(e: any) {
|
||||
this.#isMouseDown = true;
|
||||
|
||||
if (this.#isMouseOnCooldown) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.#contextAction?.getTarget() === ContextActionTarget.POINT && e.originalEvent?.button === 2) this.#isRotatingDestination = true;
|
||||
this.scrollWheelZoom.disable();
|
||||
|
||||
this.#shortPressTimer = window.setTimeout(() => {
|
||||
/* If the mouse is no longer being pressed, execute the short press action */
|
||||
if (!this.#isMouseDown) this.#onShortPress(e);
|
||||
}, 200);
|
||||
|
||||
this.#longPressTimer = window.setTimeout(() => {
|
||||
/* If the mouse is still being pressed, execute the long press action */
|
||||
if (this.#isMouseDown && !this.#isDragging && !this.#isZooming) this.#onLongPress(e);
|
||||
}, 350);
|
||||
}
|
||||
|
||||
#onDoubleClick(e: any) {
|
||||
console.log(`Double click at ${e.latlng}`);
|
||||
|
||||
window.clearTimeout(this.#shortPressTimer);
|
||||
window.clearTimeout(this.#longPressTimer);
|
||||
|
||||
if (getApp().getState() === OlympusState.IDLE) {
|
||||
StarredSpawnContextMenuRequestEvent.dispatch(e.latlng);
|
||||
getApp().setState(OlympusState.STARRED_SPAWN);
|
||||
} else {
|
||||
if (getApp().getSubState() !== NO_SUBSTATE) {
|
||||
getApp().setState(getApp().getState(), NO_SUBSTATE);
|
||||
} else {
|
||||
getApp().setState(OlympusState.IDLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#onShortPress(e: any) {
|
||||
let pressLocation: L.LatLng;
|
||||
if (e.type === "touchstart") pressLocation = this.containerPointToLatLng(this.mouseEventToContainerPoint(e.touches[0]));
|
||||
else pressLocation = new L.LatLng(e.latlng.lat, e.latlng.lng);
|
||||
|
||||
console.log(`Short press at ${pressLocation}`);
|
||||
#onLeftClick(e: L.LeafletMouseEvent) {
|
||||
console.log(`Left click at ${e.latlng}`);
|
||||
|
||||
/* 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 (e.originalEvent?.button != 2 && this.#spawnRequestTable !== null) {
|
||||
this.#spawnRequestTable.unit.location = pressLocation;
|
||||
if (this.#spawnRequestTable !== null) {
|
||||
this.#spawnRequestTable.unit.location = e.latlng;
|
||||
getApp()
|
||||
.getUnitsManager()
|
||||
.spawnUnits(
|
||||
@ -1054,24 +802,23 @@ export class Map extends L.Map {
|
||||
undefined,
|
||||
undefined,
|
||||
(hash) => {
|
||||
this.addTemporaryMarker(pressLocation, this.#spawnRequestTable?.unit.unitType ?? "unknown", this.#spawnRequestTable?.coalition ?? "blue", hash);
|
||||
this.addTemporaryMarker(e.latlng, this.#spawnRequestTable?.unit.unitType ?? "unknown", this.#spawnRequestTable?.coalition ?? "blue", hash);
|
||||
}
|
||||
);
|
||||
}
|
||||
} else if (getApp().getSubState() === SpawnSubState.SPAWN_EFFECT) {
|
||||
if (e.originalEvent?.button != 2 && this.#effectRequestTable !== null) {
|
||||
if (this.#effectRequestTable !== null) {
|
||||
if (this.#effectRequestTable.type === "explosion") {
|
||||
if (this.#effectRequestTable.explosionType === "High explosive") getApp().getServerManager().spawnExplosion(50, "normal", pressLocation);
|
||||
else if (this.#effectRequestTable.explosionType === "Napalm") getApp().getServerManager().spawnExplosion(50, "napalm", pressLocation);
|
||||
else if (this.#effectRequestTable.explosionType === "White phosphorous")
|
||||
getApp().getServerManager().spawnExplosion(50, "phosphorous", pressLocation);
|
||||
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(pressLocation);
|
||||
this.addExplosionMarker(e.latlng);
|
||||
} else if (this.#effectRequestTable.type === "smoke") {
|
||||
getApp()
|
||||
.getServerManager()
|
||||
.spawnSmoke(this.#effectRequestTable.smokeColor ?? "white", pressLocation);
|
||||
this.addSmokeMarker(pressLocation, this.#effectRequestTable.smokeColor ?? "white");
|
||||
.spawnSmoke(this.#effectRequestTable.smokeColor ?? "white", e.latlng);
|
||||
this.addSmokeMarker(e.latlng, this.#effectRequestTable.smokeColor ?? "white");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1079,18 +826,18 @@ export class Map extends L.Map {
|
||||
if (getApp().getSubState() === DrawSubState.DRAW_POLYGON) {
|
||||
const selectedArea = this.getSelectedCoalitionArea();
|
||||
if (selectedArea && selectedArea instanceof CoalitionPolygon) {
|
||||
selectedArea.addTemporaryLatLng(pressLocation);
|
||||
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(pressLocation);
|
||||
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(pressLocation, this.#coalitionAreas[idx])) {
|
||||
if (areaContains(e.latlng, this.#coalitionAreas[idx])) {
|
||||
this.#coalitionAreas[idx].setSelected(true);
|
||||
getApp().setState(OlympusState.DRAW, DrawSubState.EDIT);
|
||||
break;
|
||||
@ -1098,17 +845,13 @@ export class Map extends L.Map {
|
||||
}
|
||||
}
|
||||
} else if (getApp().getState() === OlympusState.UNIT_CONTROL) {
|
||||
if (e.type === "touchstart" || e.originalEvent?.buttons === 1) {
|
||||
if (this.#contextAction !== null) this.executeContextAction(null, pressLocation, e.originalEvent);
|
||||
else getApp().setState(OlympusState.IDLE);
|
||||
} else if (e.originalEvent?.buttons === 2) {
|
||||
this.executeDefaultContextAction(null, pressLocation, e.originalEvent);
|
||||
}
|
||||
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(pressLocation, "BP", "rgb(37 99 235)", { interactive: true, draggable: true });
|
||||
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;
|
||||
@ -1120,10 +863,10 @@ export class Map extends L.Map {
|
||||
this.#targetPoint.on("click", (event) => {
|
||||
getApp().setState(OlympusState.JTAC);
|
||||
});
|
||||
} else this.#targetPoint.setLatLng(pressLocation);
|
||||
} else this.#targetPoint.setLatLng(e.latlng);
|
||||
} else if (getApp().getSubState() === JTACSubState.SELECT_ECHO_POINT) {
|
||||
if (!this.#ECHOPoint) {
|
||||
this.#ECHOPoint = new TextMarker(pressLocation, "BP", "rgb(37 99 235)", { interactive: true, draggable: true });
|
||||
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;
|
||||
@ -1135,10 +878,10 @@ export class Map extends L.Map {
|
||||
this.#ECHOPoint.on("click", (event) => {
|
||||
getApp().setState(OlympusState.JTAC);
|
||||
});
|
||||
} else this.#ECHOPoint.setLatLng(pressLocation);
|
||||
} else this.#ECHOPoint.setLatLng(e.latlng);
|
||||
} else if (getApp().getSubState() === JTACSubState.SELECT_IP) {
|
||||
if (!this.#IPPoint) {
|
||||
this.#IPPoint = new TextMarker(pressLocation, "BP", "rgb(37 99 235)", { interactive: true, draggable: true });
|
||||
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;
|
||||
@ -1150,7 +893,7 @@ export class Map extends L.Map {
|
||||
this.#IPPoint.on("click", (event) => {
|
||||
getApp().setState(OlympusState.JTAC);
|
||||
});
|
||||
} else this.#IPPoint.setLatLng(pressLocation);
|
||||
} else this.#IPPoint.setLatLng(e.latlng);
|
||||
}
|
||||
getApp().setState(OlympusState.JTAC);
|
||||
this.#drawIPToTargetLine();
|
||||
@ -1158,40 +901,26 @@ export class Map extends L.Map {
|
||||
}
|
||||
}
|
||||
|
||||
#onLongPress(e: any) {
|
||||
let pressLocation: L.LatLng;
|
||||
if (e.type === "touchstart") pressLocation = this.containerPointToLatLng(this.mouseEventToContainerPoint(e.touches[0]));
|
||||
else pressLocation = new L.LatLng(e.latlng.lat, e.latlng.lng);
|
||||
#onRightClick(e: L.LeafletMouseEvent) {
|
||||
e.originalEvent.preventDefault();
|
||||
|
||||
console.log(`Long press at ${pressLocation}`);
|
||||
console.log(`Right click at ${e.latlng}`);
|
||||
|
||||
if (!this.#isDragging && !this.#isZooming) {
|
||||
this.deselectAllCoalitionAreas();
|
||||
if (getApp().getState() === OlympusState.IDLE) {
|
||||
if (e.type === "touchstart") document.dispatchEvent(new CustomEvent("forceboxselect", { detail: e }));
|
||||
else document.dispatchEvent(new CustomEvent("forceboxselect", { detail: e.originalEvent }));
|
||||
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 (e.originalEvent?.button === 2) {
|
||||
if (!this.getContextAction()) {
|
||||
getApp().setState(OlympusState.UNIT_CONTROL, UnitControlSubState.MAP_CONTEXT_MENU);
|
||||
MapContextMenuRequestEvent.dispatch(pressLocation);
|
||||
}
|
||||
} else {
|
||||
if (this.#contextAction?.getTarget() === ContextActionTarget.POINT) {
|
||||
this.dragging.disable();
|
||||
this.#isRotatingDestination = true;
|
||||
} else {
|
||||
if (e.type === "touchstart") document.dispatchEvent(new CustomEvent("forceboxselect", { detail: e }));
|
||||
else document.dispatchEvent(new CustomEvent("forceboxselect", { detail: e.originalEvent }));
|
||||
}
|
||||
if (!this.getContextAction()) {
|
||||
getApp().setState(OlympusState.UNIT_CONTROL, UnitControlSubState.MAP_CONTEXT_MENU);
|
||||
MapContextMenuRequestEvent.dispatch(e.latlng);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#onMouseMove(e: any) {
|
||||
window.clearTimeout(this.#longPressTimer);
|
||||
|
||||
if (!this.#isRotatingDestination) {
|
||||
this.#lastMousePosition.x = e.originalEvent.x;
|
||||
this.#lastMousePosition.y = e.originalEvent.y;
|
||||
@ -1200,8 +929,8 @@ export class Map extends L.Map {
|
||||
MouseMovedEvent.dispatch(e.latlng);
|
||||
getGroundElevation(e.latlng, (elevation) => {
|
||||
MouseMovedEvent.dispatch(e.latlng, elevation);
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
if (this.#currentSpawnMarker) this.#currentSpawnMarker.setLatLng(e.latlng);
|
||||
if (this.#currentEffectMarker) this.#currentEffectMarker.setLatLng(e.latlng);
|
||||
} else {
|
||||
|
||||
@ -8,6 +8,7 @@ export class SmokeMarker extends CustomMarker {
|
||||
|
||||
constructor(latlng: LatLngExpression, color: string, options?: MarkerOptions) {
|
||||
super(latlng, options);
|
||||
this.options.interactive = false;
|
||||
this.setZIndexOffset(9999);
|
||||
this.#color = color;
|
||||
window.setTimeout(() => {
|
||||
|
||||
@ -4,6 +4,7 @@ import { CustomMarker } from "./custommarker";
|
||||
export class TargetMarker extends CustomMarker {
|
||||
constructor(latlng: LatLngExpression, options?: MarkerOptions) {
|
||||
super(latlng, options);
|
||||
this.options.interactive = false;
|
||||
this.setZIndexOffset(9999);
|
||||
}
|
||||
|
||||
|
||||
@ -176,4 +176,8 @@
|
||||
|
||||
.ol-explosion-icon {
|
||||
fill: red;
|
||||
}
|
||||
|
||||
path.leaflet-interactive:focus {
|
||||
outline: none;
|
||||
}
|
||||
@ -4,7 +4,7 @@ import { SVGInjector } from "@tanem/svg-injector";
|
||||
import { AirbaseChartData, AirbaseOptions } from "../interfaces";
|
||||
import { getApp } from "../olympusapp";
|
||||
import { OlympusState } from "../constants/constants";
|
||||
import { AirbaseSelectedEvent } from "../events";
|
||||
import { AirbaseSelectedEvent, AppStateChangedEvent } from "../events";
|
||||
|
||||
// TODO add ability to select the marker
|
||||
export class Airbase extends CustomMarker {
|
||||
@ -27,6 +27,12 @@ export class Airbase extends CustomMarker {
|
||||
this.#name = options.name;
|
||||
this.#img = document.createElement("img");
|
||||
|
||||
AppStateChangedEvent.on((state, subState) => {
|
||||
const element = this.getElement();
|
||||
if (element)
|
||||
element.style.pointerEvents = (state === OlympusState.IDLE || state === OlympusState.AIRBASE)? "all": "none";
|
||||
})
|
||||
|
||||
AirbaseSelectedEvent.on((airbase) => {
|
||||
this.#selected = airbase == this;
|
||||
if (this.getElement()?.querySelector(".airbase-icon"))
|
||||
|
||||
@ -1,10 +1,16 @@
|
||||
import { DivIcon } from "leaflet";
|
||||
import { DivIcon, LatLngExpression, MarkerOptions } from "leaflet";
|
||||
import { CustomMarker } from "../map/markers/custommarker";
|
||||
import { SVGInjector } from "@tanem/svg-injector";
|
||||
|
||||
export class Bullseye extends CustomMarker {
|
||||
#coalition: string = "";
|
||||
|
||||
constructor(latlng: LatLngExpression, options?: MarkerOptions) {
|
||||
super(latlng, options);
|
||||
this.options.interactive = false;
|
||||
this.setZIndexOffset(9999);
|
||||
}
|
||||
|
||||
createIcon() {
|
||||
var icon = new DivIcon({
|
||||
className: "leaflet-bullseye-marker",
|
||||
|
||||
@ -93,11 +93,11 @@ export class OlympusApp {
|
||||
*/
|
||||
|
||||
getExpressAddress() {
|
||||
return `${window.location.href.split("?")[0].replace("vite/", "").replace("vite", "")}express`
|
||||
return `${window.location.href.split("?")[0].replace("vite/", "").replace("vite", "")}express`;
|
||||
}
|
||||
|
||||
getBackendAddress() {
|
||||
return `${window.location.href.split("?")[0].replace("vite/", "").replace("vite", "")}olympus`
|
||||
return `${window.location.href.split("?")[0].replace("vite/", "").replace("vite", "")}olympus`;
|
||||
}
|
||||
|
||||
start() {
|
||||
@ -150,6 +150,17 @@ export class OlympusApp {
|
||||
ConfigLoadedEvent.dispatch(this.#config as OlympusConfig);
|
||||
this.setState(OlympusState.LOGIN);
|
||||
});
|
||||
|
||||
this.#shortcutManager?.addShortcut("idle", {
|
||||
label: "Deselect all",
|
||||
keyUpCallback: (ev: KeyboardEvent) => {
|
||||
this.setState(OlympusState.IDLE);
|
||||
},
|
||||
code: "Escape",
|
||||
});
|
||||
|
||||
this.#shortcutManager.checkShortcuts();
|
||||
|
||||
}
|
||||
|
||||
getConfig() {
|
||||
|
||||
@ -87,29 +87,34 @@ export const zeroPad = function (num: number, places: number) {
|
||||
return string;
|
||||
};
|
||||
|
||||
export function latLngToMGRS(lat: number, lng: number, precision: number = 4): MGRS | false {
|
||||
export function latLngToMGRS(lat: number, lng: number, precision: number = 4): MGRS | undefined {
|
||||
if (precision < 0 || precision > 6) {
|
||||
console.error("latLngToMGRS: precision must be a number >= 0 and <= 6. Given precision: " + precision);
|
||||
return false;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const mgrs = new Converter({}).LLtoMGRS(lat, lng, precision);
|
||||
const match = mgrs.match(new RegExp(`^(\\d{2})([A-Z])([A-Z])([A-Z])(\\d+)$`));
|
||||
const easting = match[5].substr(0, match[5].length / 2);
|
||||
const northing = match[5].substr(match[5].length / 2);
|
||||
if (match) {
|
||||
const easting = match[5].substr(0, match[5].length / 2);
|
||||
const northing = match[5].substr(match[5].length / 2);
|
||||
|
||||
let output: MGRS = {
|
||||
bandLetter: match[2],
|
||||
columnLetter: match[3],
|
||||
groups: [match[1] + match[2], match[3] + match[4], easting, northing],
|
||||
easting: easting,
|
||||
northing: northing,
|
||||
precision: precision,
|
||||
rowLetter: match[4],
|
||||
string: match[0],
|
||||
zoneNumber: match[1],
|
||||
};
|
||||
let output: MGRS = {
|
||||
bandLetter: match[2],
|
||||
columnLetter: match[3],
|
||||
groups: [match[1] + match[2], match[3] + match[4], easting, northing],
|
||||
easting: easting,
|
||||
northing: northing,
|
||||
precision: precision,
|
||||
rowLetter: match[4],
|
||||
string: match[0],
|
||||
zoneNumber: match[1],
|
||||
};
|
||||
|
||||
return output;
|
||||
return output;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function latLngToUTM(lat: number, lng: number) {
|
||||
@ -317,19 +322,20 @@ export function makeID(length) {
|
||||
}
|
||||
|
||||
export function hash(str, seed = 0) {
|
||||
let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed;
|
||||
for(let i = 0, ch; i < str.length; i++) {
|
||||
ch = str.charCodeAt(i);
|
||||
h1 = Math.imul(h1 ^ ch, 2654435761);
|
||||
h2 = Math.imul(h2 ^ ch, 1597334677);
|
||||
let h1 = 0xdeadbeef ^ seed,
|
||||
h2 = 0x41c6ce57 ^ seed;
|
||||
for (let i = 0, ch; i < str.length; i++) {
|
||||
ch = str.charCodeAt(i);
|
||||
h1 = Math.imul(h1 ^ ch, 2654435761);
|
||||
h2 = Math.imul(h2 ^ ch, 1597334677);
|
||||
}
|
||||
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507);
|
||||
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507);
|
||||
h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);
|
||||
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507);
|
||||
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507);
|
||||
h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909);
|
||||
|
||||
return `${4294967296 * (2097151 & h2) + (h1 >>> 0)}`;
|
||||
};
|
||||
}
|
||||
|
||||
export function byteArrayToInteger(array) {
|
||||
let res = 0;
|
||||
|
||||
@ -42,7 +42,7 @@ export class ServerManager {
|
||||
keyUpCallback: () => {
|
||||
this.setPaused(!this.getPaused());
|
||||
},
|
||||
code: "Space"
|
||||
code: "Enter"
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -1,22 +1,26 @@
|
||||
import { ShortcutChangedEvent, ShortcutsChangedEvent } from "../events";
|
||||
import { AppStateChangedEvent, ShortcutChangedEvent, ShortcutsChangedEvent } from "../events";
|
||||
import { ShortcutOptions } from "../interfaces";
|
||||
import { keyEventWasInInput } from "../other/utils";
|
||||
|
||||
export class Shortcut {
|
||||
#id: string;
|
||||
#options: ShortcutOptions;
|
||||
#keydown: boolean = false;
|
||||
|
||||
constructor(id, options: ShortcutOptions) {
|
||||
this.#id = id;
|
||||
this.#options = options;
|
||||
|
||||
AppStateChangedEvent.on(() => this.#keydown = false)
|
||||
|
||||
/* Key up event is mandatory */
|
||||
document.addEventListener("keyup", (ev: any) => {
|
||||
this.#keydown = false;
|
||||
if (keyEventWasInInput(ev) || options.code !== ev.code) return;
|
||||
if (
|
||||
(typeof options.altKey !== "boolean" || (typeof options.altKey === "boolean" && ev.altKey === options.altKey)) &&
|
||||
(typeof options.ctrlKey !== "boolean" || (typeof options.ctrlKey === "boolean" && ev.ctrlKey === options.ctrlKey)) &&
|
||||
(typeof options.shiftKey !== "boolean" || (typeof options.shiftKey === "boolean" && ev.shiftKey === options.shiftKey))
|
||||
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)
|
||||
)
|
||||
options.keyUpCallback(ev);
|
||||
});
|
||||
@ -24,11 +28,12 @@ export class Shortcut {
|
||||
/* Key down event is optional */
|
||||
if (options.keyDownCallback) {
|
||||
document.addEventListener("keydown", (ev: any) => {
|
||||
if (keyEventWasInInput(ev) || options.code !== ev.code) return;
|
||||
if (this.#keydown || keyEventWasInInput(ev) || options.code !== ev.code) return;
|
||||
this.#keydown = true;
|
||||
if (
|
||||
(typeof options.altKey !== "boolean" || (typeof options.altKey === "boolean" && ev.altKey === options.altKey)) &&
|
||||
(typeof options.ctrlKey !== "boolean" || (typeof options.ctrlKey === "boolean" && ev.ctrlKey === options.ctrlKey)) &&
|
||||
(typeof options.shiftKey !== "boolean" || (typeof options.shiftKey === "boolean" && ev.shiftKey === options.shiftKey))
|
||||
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);
|
||||
});
|
||||
|
||||
@ -39,4 +39,24 @@ export class ShortcutManager {
|
||||
this.#shortcuts[id].setOptions(shortcutOptions);
|
||||
ShortcutsChangedEvent.dispatch(this.#shortcuts);
|
||||
}
|
||||
|
||||
checkShortcuts() {
|
||||
for (let id in this.#shortcuts) {
|
||||
const shortcut = this.#shortcuts[id];
|
||||
for (let otherid in this.#shortcuts) {
|
||||
if (id != otherid) {
|
||||
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)
|
||||
) {
|
||||
console.error("Duplicate shortcut: " + shortcut.getOptions().label + " and " + otherShortcut.getOptions().label)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -26,6 +26,7 @@ export type MapOptions = {
|
||||
cameraPluginRatio: number;
|
||||
cameraPluginEnabled: boolean;
|
||||
cameraPluginMode: string;
|
||||
tabletMode: boolean;
|
||||
};
|
||||
|
||||
export type MapHiddenTypes = {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
import { Coalition } from "../../types/types";
|
||||
|
||||
export function OlCoalitionToggle(props: { coalition: Coalition | undefined; onClick: () => void }) {
|
||||
export function OlCoalitionToggle(props: { coalition: Coalition | undefined; onClick: () => void; showLabel?: boolean }) {
|
||||
return (
|
||||
<div className="inline-flex cursor-pointer items-center" onClick={props.onClick}>
|
||||
<button className="peer sr-only" />
|
||||
@ -26,15 +26,17 @@ export function OlCoalitionToggle(props: { coalition: Coalition | undefined; onC
|
||||
rtl:data-[coalition='red']:after:-translate-x-full
|
||||
`}
|
||||
></div>
|
||||
<span
|
||||
className={`
|
||||
ms-3 overflow-hidden text-ellipsis text-nowrap text-gray-900
|
||||
dark:text-white
|
||||
data-[flash='true']:after:animate-pulse
|
||||
`}
|
||||
>
|
||||
{props.coalition ? `${props.coalition[0].toLocaleUpperCase() + props.coalition.substring(1)}` : "Diff. values"}
|
||||
</span>
|
||||
{props.showLabel && (
|
||||
<span
|
||||
className={`
|
||||
ms-3 overflow-hidden text-ellipsis text-nowrap text-gray-900
|
||||
dark:text-white
|
||||
data-[flash='true']:after:animate-pulse
|
||||
`}
|
||||
>
|
||||
{props.coalition ? `${props.coalition[0].toLocaleUpperCase() + props.coalition.substring(1)}` : "Diff. values"}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -113,7 +113,6 @@ export function MapContextMenu(props: {}) {
|
||||
contextActionIt.executeCallback(unit, null);
|
||||
}
|
||||
}
|
||||
getApp().setState(OlympusState.UNIT_CONTROL);
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon className="my-auto" icon={contextActionIt.getIcon()} />
|
||||
|
||||
606
frontend/react/src/ui/contextmenus/spawncontextmenu.tsx
Normal file
606
frontend/react/src/ui/contextmenus/spawncontextmenu.tsx
Normal file
@ -0,0 +1,606 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { BLUE_COMMANDER, COMMAND_MODE_OPTIONS_DEFAULTS, GAME_MASTER, NO_SUBSTATE, OlympusState, OlympusSubState } from "../../constants/constants";
|
||||
import { LatLng } from "leaflet";
|
||||
import {
|
||||
AppStateChangedEvent,
|
||||
CommandModeOptionsChangedEvent,
|
||||
SpawnContextMenuRequestEvent,
|
||||
StarredSpawnsChangedEvent,
|
||||
UnitDatabaseLoadedEvent,
|
||||
} from "../../events";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { SpawnRequestTable, UnitBlueprint } from "../../interfaces";
|
||||
import { faArrowLeft, faEllipsisVertical, faExplosion, faListDots, faSearch, faSmog, faStar } from "@fortawesome/free-solid-svg-icons";
|
||||
import { EffectSpawnMenu } from "../panels/effectspawnmenu";
|
||||
import { UnitSpawnMenu } from "../panels/unitspawnmenu";
|
||||
import { OlEffectListEntry } from "../components/oleffectlistentry";
|
||||
import {
|
||||
olButtonsVisibilityAircraft,
|
||||
olButtonsVisibilityGroundunit,
|
||||
olButtonsVisibilityGroundunitSam,
|
||||
olButtonsVisibilityHelicopter,
|
||||
olButtonsVisibilityNavyunit,
|
||||
} from "../components/olicons";
|
||||
import { OlUnitListEntry } from "../components/olunitlistentry";
|
||||
import { OlSearchBar } from "../components/olsearchbar";
|
||||
import { OlStateButton } from "../components/olstatebutton";
|
||||
import { OlDropdownItem } from "../components/oldropdown";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { OlCoalitionToggle } from "../components/olcoalitiontoggle";
|
||||
import { Coalition } from "../../types/types";
|
||||
import { CompactUnitSpawnMenu } from "../panels/compactunitspawnmenu";
|
||||
import { CompactEffectSpawnMenu } from "../panels/compacteffectspawnmenu";
|
||||
|
||||
enum CategoryGroup {
|
||||
NONE,
|
||||
AIRCRAFT,
|
||||
HELICOPTER,
|
||||
AIR_DEFENCE,
|
||||
GROUND_UNIT,
|
||||
NAVY_UNIT,
|
||||
EFFECT,
|
||||
SEARCH,
|
||||
STARRED,
|
||||
}
|
||||
|
||||
export function SpawnContextMenu(props: {}) {
|
||||
const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED);
|
||||
const [appSubState, setAppSubState] = useState(NO_SUBSTATE as OlympusSubState);
|
||||
const [xPosition, setXPosition] = useState(0);
|
||||
const [yPosition, setYPosition] = useState(0);
|
||||
const [latlng, setLatLng] = useState(null as null | LatLng);
|
||||
const [starredSpawns, setStarredSpawns] = useState({} as { [key: string]: SpawnRequestTable });
|
||||
const [openAccordion, setOpenAccordion] = useState(CategoryGroup.NONE);
|
||||
const [blueprint, setBlueprint] = useState(null as null | UnitBlueprint);
|
||||
const [effect, setEffect] = useState(null as null | string);
|
||||
const [filterString, setFilterString] = useState("");
|
||||
const [selectedRole, setSelectedRole] = useState(null as null | string);
|
||||
const [selectedType, setSelectedType] = useState(null as null | string);
|
||||
const [blueprints, setBlueprints] = useState([] as UnitBlueprint[]);
|
||||
const [roles, setRoles] = useState({ aircraft: [] as string[], helicopter: [] as string[] });
|
||||
const [types, setTypes] = useState({ groundunit: [] as string[], navyunit: [] as string[] });
|
||||
const [commandModeOptions, setCommandModeOptions] = useState(COMMAND_MODE_OPTIONS_DEFAULTS);
|
||||
const [showCost, setShowCost] = useState(false);
|
||||
const [spawnCoalition, setSpawnCoalition] = useState("blue" as Coalition);
|
||||
const [showMore, setShowMore] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedRole) setBlueprints(getApp()?.getUnitsManager().getDatabase().getByRole(selectedRole));
|
||||
else if (selectedType) setBlueprints(getApp()?.getUnitsManager().getDatabase().getByType(selectedType));
|
||||
else setBlueprints(getApp()?.getUnitsManager().getDatabase().getBlueprints());
|
||||
}, [selectedRole, selectedType, openAccordion]);
|
||||
|
||||
useEffect(() => {
|
||||
UnitDatabaseLoadedEvent.on(() => {
|
||||
setRoles({
|
||||
aircraft: getApp()
|
||||
?.getUnitsManager()
|
||||
.getDatabase()
|
||||
.getRoles((unit) => unit.category === "aircraft"),
|
||||
helicopter: getApp()
|
||||
?.getUnitsManager()
|
||||
.getDatabase()
|
||||
.getRoles((unit) => unit.category === "helicopter"),
|
||||
});
|
||||
|
||||
setTypes({
|
||||
groundunit: getApp()
|
||||
?.getUnitsManager()
|
||||
.getDatabase()
|
||||
.getTypes((unit) => unit.category === "groundunit"),
|
||||
navyunit: getApp()
|
||||
?.getUnitsManager()
|
||||
.getDatabase()
|
||||
.getTypes((unit) => unit.category === "navyunit"),
|
||||
});
|
||||
});
|
||||
|
||||
CommandModeOptionsChangedEvent.on((commandModeOptions) => {
|
||||
setCommandModeOptions(commandModeOptions);
|
||||
setShowCost(!(commandModeOptions.commandMode == GAME_MASTER || !commandModeOptions.restrictSpawns));
|
||||
setOpenAccordion(CategoryGroup.NONE);
|
||||
});
|
||||
|
||||
StarredSpawnsChangedEvent.on((starredSpawns) => setStarredSpawns({ ...starredSpawns }));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setBlueprint(null);
|
||||
setEffect(null);
|
||||
setSelectedType(null);
|
||||
setSelectedRole(null);
|
||||
}, [openAccordion]);
|
||||
|
||||
/* Filter the blueprints according to the label */
|
||||
const filteredBlueprints: UnitBlueprint[] = [];
|
||||
if (blueprints && filterString !== "") {
|
||||
blueprints.forEach((blueprint) => {
|
||||
if (blueprint.enabled && (filterString === "" || blueprint.label.toLowerCase().includes(filterString.toLowerCase()))) filteredBlueprints.push(blueprint);
|
||||
});
|
||||
}
|
||||
|
||||
var contentRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
AppStateChangedEvent.on((state, subState) => {
|
||||
setAppState(state);
|
||||
setAppSubState(subState);
|
||||
});
|
||||
StarredSpawnsChangedEvent.on((starredSpawns) => setStarredSpawns({ ...starredSpawns }));
|
||||
SpawnContextMenuRequestEvent.on((latlng) => {
|
||||
setLatLng(latlng);
|
||||
const containerPoint = getApp().getMap().latLngToContainerPoint(latlng);
|
||||
setXPosition(getApp().getMap().getContainer().offsetLeft + containerPoint.x);
|
||||
setYPosition(getApp().getMap().getContainer().offsetTop + containerPoint.y);
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (contentRef.current) {
|
||||
const content = contentRef.current as HTMLDivElement;
|
||||
|
||||
content.style.left = `${xPosition}px`;
|
||||
content.style.top = `${yPosition}px`;
|
||||
|
||||
let newXPosition = xPosition;
|
||||
let newYposition = yPosition;
|
||||
|
||||
let [cxr, cyb] = [content.getBoundingClientRect().x + content.clientWidth, content.getBoundingClientRect().y + content.clientHeight];
|
||||
|
||||
/* Try and move the content so it is inside the screen */
|
||||
if (cxr > window.innerWidth) newXPosition -= cxr - window.innerWidth;
|
||||
if (cyb > window.innerHeight) newYposition -= cyb - window.innerHeight;
|
||||
|
||||
content.style.left = `${newXPosition}px`;
|
||||
content.style.top = `${newYposition}px`;
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{appState === OlympusState.SPAWN_CONTEXT && (
|
||||
<>
|
||||
<div
|
||||
ref={contentRef}
|
||||
className={`
|
||||
absolute flex w-[395px] flex-wrap gap-2 rounded-md bg-olympus-800
|
||||
`}
|
||||
>
|
||||
<div className="flex w-full flex-col gap-4 px-6 py-3">
|
||||
<div className="flex flex-wrap justify-between gap-2">
|
||||
<OlCoalitionToggle
|
||||
coalition={spawnCoalition}
|
||||
onClick={() => {
|
||||
spawnCoalition === "blue" && setSpawnCoalition("neutral");
|
||||
spawnCoalition === "neutral" && setSpawnCoalition("red");
|
||||
spawnCoalition === "red" && setSpawnCoalition("blue");
|
||||
}}
|
||||
/>
|
||||
<OlStateButton
|
||||
checked={openAccordion === CategoryGroup.AIRCRAFT}
|
||||
onClick={() => (openAccordion !== CategoryGroup.AIRCRAFT ? setOpenAccordion(CategoryGroup.AIRCRAFT) : setOpenAccordion(CategoryGroup.NONE))}
|
||||
icon={olButtonsVisibilityAircraft}
|
||||
tooltip="Show aircraft units"
|
||||
buttonColor={spawnCoalition === "blue" ? "#2563eb" : spawnCoalition === "neutral" ? "#9ca3af" : "#ef4444"}
|
||||
/>
|
||||
<OlStateButton
|
||||
checked={openAccordion === CategoryGroup.HELICOPTER}
|
||||
onClick={() =>
|
||||
openAccordion !== CategoryGroup.HELICOPTER ? setOpenAccordion(CategoryGroup.HELICOPTER) : setOpenAccordion(CategoryGroup.NONE)
|
||||
}
|
||||
icon={olButtonsVisibilityHelicopter}
|
||||
tooltip="Show helicopter units"
|
||||
buttonColor={spawnCoalition === "blue" ? "#2563eb" : spawnCoalition === "neutral" ? "#9ca3af" : "#ef4444"}
|
||||
/>
|
||||
<OlStateButton
|
||||
checked={openAccordion === CategoryGroup.AIR_DEFENCE}
|
||||
onClick={() =>
|
||||
openAccordion !== CategoryGroup.AIR_DEFENCE ? setOpenAccordion(CategoryGroup.AIR_DEFENCE) : setOpenAccordion(CategoryGroup.NONE)
|
||||
}
|
||||
icon={olButtonsVisibilityGroundunitSam}
|
||||
tooltip="Show air defence units"
|
||||
buttonColor={spawnCoalition === "blue" ? "#2563eb" : spawnCoalition === "neutral" ? "#9ca3af" : "#ef4444"}
|
||||
/>
|
||||
<OlStateButton
|
||||
checked={openAccordion === CategoryGroup.GROUND_UNIT}
|
||||
onClick={() =>
|
||||
openAccordion !== CategoryGroup.GROUND_UNIT ? setOpenAccordion(CategoryGroup.GROUND_UNIT) : setOpenAccordion(CategoryGroup.NONE)
|
||||
}
|
||||
icon={olButtonsVisibilityGroundunit}
|
||||
tooltip="Show ground units"
|
||||
buttonColor={spawnCoalition === "blue" ? "#2563eb" : spawnCoalition === "neutral" ? "#9ca3af" : "#ef4444"}
|
||||
/>
|
||||
<OlStateButton
|
||||
checked={openAccordion === CategoryGroup.NAVY_UNIT}
|
||||
onClick={() => (openAccordion !== CategoryGroup.NAVY_UNIT ? setOpenAccordion(CategoryGroup.NAVY_UNIT) : setOpenAccordion(CategoryGroup.NONE))}
|
||||
icon={olButtonsVisibilityNavyunit}
|
||||
tooltip="Show navy units"
|
||||
buttonColor={spawnCoalition === "blue" ? "#2563eb" : spawnCoalition === "neutral" ? "#9ca3af" : "#ef4444"}
|
||||
/>
|
||||
<OlStateButton checked={showMore} onClick={() => setShowMore(!showMore)} icon={faEllipsisVertical} tooltip="Show more options" />
|
||||
{showMore && (
|
||||
<>
|
||||
<OlStateButton
|
||||
checked={openAccordion === CategoryGroup.EFFECT}
|
||||
onClick={() => (openAccordion !== CategoryGroup.EFFECT ? setOpenAccordion(CategoryGroup.EFFECT) : setOpenAccordion(CategoryGroup.NONE))}
|
||||
icon={faExplosion}
|
||||
tooltip="Show effects"
|
||||
className="ml-auto"
|
||||
/>
|
||||
<OlStateButton
|
||||
checked={openAccordion === CategoryGroup.SEARCH}
|
||||
onClick={() => (openAccordion !== CategoryGroup.SEARCH ? setOpenAccordion(CategoryGroup.SEARCH) : setOpenAccordion(CategoryGroup.NONE))}
|
||||
icon={faSearch}
|
||||
tooltip="Search unit"
|
||||
/>
|
||||
<OlStateButton
|
||||
checked={openAccordion === CategoryGroup.STARRED}
|
||||
onClick={() => (openAccordion !== CategoryGroup.STARRED ? setOpenAccordion(CategoryGroup.STARRED) : setOpenAccordion(CategoryGroup.NONE))}
|
||||
icon={faStar}
|
||||
tooltip="Show starred spanws"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{blueprint === null && effect === null && openAccordion !== CategoryGroup.NONE && (
|
||||
<div className="mb-3 flex flex-col gap-4">
|
||||
<>
|
||||
<>
|
||||
{openAccordion === CategoryGroup.AIRCRAFT && (
|
||||
<>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{roles.aircraft.sort().map((role) => {
|
||||
return (
|
||||
<div
|
||||
key={role}
|
||||
data-selected={selectedRole === role}
|
||||
className={`
|
||||
cursor-pointer rounded-full bg-olympus-900
|
||||
px-2 py-0.5 text-xs font-bold
|
||||
text-olympus-50
|
||||
data-[selected='true']:bg-blue-500
|
||||
data-[selected='true']:text-gray-200
|
||||
`}
|
||||
onClick={() => {
|
||||
selectedRole === role ? setSelectedRole(null) : setSelectedRole(role);
|
||||
}}
|
||||
>
|
||||
{role}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
flex max-h-[350px] flex-col gap-1
|
||||
overflow-y-scroll no-scrollbar
|
||||
`}
|
||||
>
|
||||
{blueprints
|
||||
?.sort((a, b) => (a.label > b.label ? 1 : -1))
|
||||
.filter((blueprint) => blueprint.category === "aircraft")
|
||||
.map((blueprint) => {
|
||||
return (
|
||||
<OlUnitListEntry
|
||||
key={blueprint.name}
|
||||
icon={olButtonsVisibilityAircraft}
|
||||
blueprint={blueprint}
|
||||
onClick={() => setBlueprint(blueprint)}
|
||||
showCost={showCost}
|
||||
cost={getApp().getUnitsManager().getDatabase().getSpawnPointsByName(blueprint.name)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{openAccordion === CategoryGroup.HELICOPTER && (
|
||||
<>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{roles.helicopter.sort().map((role) => {
|
||||
return (
|
||||
<div
|
||||
key={role}
|
||||
data-selected={selectedRole === role}
|
||||
className={`
|
||||
cursor-pointer rounded-full bg-olympus-900
|
||||
px-2 py-0.5 text-xs font-bold
|
||||
text-olympus-50
|
||||
data-[selected='true']:bg-blue-500
|
||||
data-[selected='true']:text-gray-200
|
||||
`}
|
||||
onClick={() => {
|
||||
selectedRole === role ? setSelectedRole(null) : setSelectedRole(role);
|
||||
}}
|
||||
>
|
||||
{role}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
flex max-h-[350px] flex-col gap-1
|
||||
overflow-y-scroll no-scrollbar
|
||||
`}
|
||||
>
|
||||
{blueprints
|
||||
?.sort((a, b) => (a.label > b.label ? 1 : -1))
|
||||
.filter((blueprint) => blueprint.category === "helicopter")
|
||||
.map((blueprint) => {
|
||||
return (
|
||||
<OlUnitListEntry
|
||||
key={blueprint.name}
|
||||
icon={olButtonsVisibilityHelicopter}
|
||||
blueprint={blueprint}
|
||||
onClick={() => setBlueprint(blueprint)}
|
||||
showCost={showCost}
|
||||
cost={getApp().getUnitsManager().getDatabase().getSpawnPointsByName(blueprint.name)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{openAccordion === CategoryGroup.AIR_DEFENCE && (
|
||||
<>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{types.groundunit
|
||||
.sort()
|
||||
?.filter((type) => type === "SAM Site" || type === "AAA")
|
||||
.map((type) => {
|
||||
return (
|
||||
<div
|
||||
key={type}
|
||||
data-selected={selectedType === type}
|
||||
className={`
|
||||
cursor-pointer rounded-full bg-olympus-900
|
||||
px-2 py-0.5 text-xs font-bold
|
||||
text-olympus-50
|
||||
data-[selected='true']:bg-blue-500
|
||||
data-[selected='true']:text-gray-200
|
||||
`}
|
||||
onClick={() => {
|
||||
selectedType === type ? setSelectedType(null) : setSelectedType(type);
|
||||
}}
|
||||
>
|
||||
{type}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
flex max-h-[350px] flex-col gap-1
|
||||
overflow-y-scroll no-scrollbar
|
||||
`}
|
||||
>
|
||||
{blueprints
|
||||
?.sort((a, b) => (a.label > b.label ? 1 : -1))
|
||||
.filter((blueprint) => blueprint.category === "groundunit" && (blueprint.type === "SAM Site" || blueprint.type === "AAA"))
|
||||
.map((blueprint) => {
|
||||
return (
|
||||
<OlUnitListEntry
|
||||
key={blueprint.name}
|
||||
icon={olButtonsVisibilityGroundunit}
|
||||
blueprint={blueprint}
|
||||
onClick={() => setBlueprint(blueprint)}
|
||||
showCost={showCost}
|
||||
cost={getApp().getUnitsManager().getDatabase().getSpawnPointsByName(blueprint.name)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{openAccordion === CategoryGroup.GROUND_UNIT && (
|
||||
<>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{types.groundunit
|
||||
.sort()
|
||||
?.filter((type) => type !== "SAM Site" && type !== "AAA")
|
||||
.map((type) => {
|
||||
return (
|
||||
<div
|
||||
key={type}
|
||||
data-selected={selectedType === type}
|
||||
className={`
|
||||
cursor-pointer rounded-full bg-olympus-900
|
||||
px-2 py-0.5 text-xs font-bold
|
||||
text-olympus-50
|
||||
data-[selected='true']:bg-blue-500
|
||||
data-[selected='true']:text-gray-200
|
||||
`}
|
||||
onClick={() => {
|
||||
selectedType === type ? setSelectedType(null) : setSelectedType(type);
|
||||
}}
|
||||
>
|
||||
{type}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
flex max-h-[350px] flex-col gap-1
|
||||
overflow-y-scroll no-scrollbar
|
||||
`}
|
||||
>
|
||||
{blueprints
|
||||
?.sort((a, b) => (a.label > b.label ? 1 : -1))
|
||||
.filter((blueprint) => blueprint.category === "groundunit" && blueprint.type !== "SAM Site" && blueprint.type !== "AAA")
|
||||
.map((blueprint) => {
|
||||
return (
|
||||
<OlUnitListEntry
|
||||
key={blueprint.name}
|
||||
icon={olButtonsVisibilityGroundunit}
|
||||
blueprint={blueprint}
|
||||
onClick={() => setBlueprint(blueprint)}
|
||||
showCost={showCost}
|
||||
cost={getApp().getUnitsManager().getDatabase().getSpawnPointsByName(blueprint.name)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{openAccordion === CategoryGroup.NAVY_UNIT && (
|
||||
<>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{types.navyunit.sort().map((type) => {
|
||||
return (
|
||||
<div
|
||||
key={type}
|
||||
data-selected={selectedType === type}
|
||||
className={`
|
||||
cursor-pointer rounded-full bg-olympus-900
|
||||
px-2 py-0.5 text-xs font-bold
|
||||
text-olympus-50
|
||||
data-[selected='true']:bg-blue-500
|
||||
data-[selected='true']:text-gray-200
|
||||
`}
|
||||
onClick={() => {
|
||||
selectedType === type ? setSelectedType(null) : setSelectedType(type);
|
||||
}}
|
||||
>
|
||||
{type}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
flex max-h-[350px] flex-col gap-1
|
||||
overflow-y-scroll no-scrollbar
|
||||
`}
|
||||
>
|
||||
{blueprints
|
||||
?.sort((a, b) => (a.label > b.label ? 1 : -1))
|
||||
.filter((blueprint) => blueprint.category === "navyunit")
|
||||
.map((blueprint) => {
|
||||
return (
|
||||
<OlUnitListEntry
|
||||
key={blueprint.name}
|
||||
icon={olButtonsVisibilityNavyunit}
|
||||
blueprint={blueprint}
|
||||
onClick={() => setBlueprint(blueprint)}
|
||||
showCost={showCost}
|
||||
cost={getApp().getUnitsManager().getDatabase().getSpawnPointsByName(blueprint.name)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{openAccordion === CategoryGroup.EFFECT && (
|
||||
<>
|
||||
<div
|
||||
className={`
|
||||
flex max-h-[350px] flex-col gap-1
|
||||
overflow-y-scroll no-scrollbar
|
||||
`}
|
||||
>
|
||||
<OlEffectListEntry
|
||||
key={"explosion"}
|
||||
icon={faExplosion}
|
||||
label={"Explosion"}
|
||||
onClick={() => {
|
||||
setEffect("explosion");
|
||||
}}
|
||||
/>
|
||||
<OlEffectListEntry
|
||||
key={"smoke"}
|
||||
icon={faSmog}
|
||||
label={"Smoke"}
|
||||
onClick={() => {
|
||||
setEffect("smoke");
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{openAccordion === CategoryGroup.SEARCH && (
|
||||
<div className="flex flex-col gap-2">
|
||||
<OlSearchBar onChange={(value) => setFilterString(value)} text={filterString} />
|
||||
<div
|
||||
className={`
|
||||
flex max-h-[350px] flex-col gap-1
|
||||
overflow-y-scroll no-scrollbar
|
||||
`}
|
||||
>
|
||||
{filteredBlueprints.length > 0 ? (
|
||||
filteredBlueprints.map((blueprint) => {
|
||||
return (
|
||||
<OlUnitListEntry
|
||||
key={blueprint.name}
|
||||
icon={olButtonsVisibilityNavyunit}
|
||||
blueprint={blueprint}
|
||||
onClick={() => setBlueprint(blueprint)}
|
||||
showCost={showCost}
|
||||
cost={getApp().getUnitsManager().getDatabase().getSpawnPointsByName(blueprint.name)}
|
||||
/>
|
||||
);
|
||||
})
|
||||
) : filterString === "" ? (
|
||||
<span className={`text-gray-200`}>Type to search</span>
|
||||
) : (
|
||||
<span className={`text-gray-200`}>No results</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{openAccordion === CategoryGroup.STARRED && (
|
||||
<div className="flex flex-col gap-2">
|
||||
{Object.values(starredSpawns).length > 0 ? (
|
||||
Object.values(starredSpawns).map((spawnRequestTable) => {
|
||||
return (
|
||||
<OlDropdownItem
|
||||
className={`
|
||||
flex w-full content-center gap-2 text-sm
|
||||
text-white
|
||||
`}
|
||||
onClick={() => {
|
||||
if (latlng) {
|
||||
spawnRequestTable.unit.location = latlng;
|
||||
getApp()
|
||||
.getUnitsManager()
|
||||
.spawnUnits(spawnRequestTable.category, Array(spawnRequestTable.amount).fill(spawnRequestTable.unit), spawnRequestTable.coalition, false);
|
||||
getApp().setState(OlympusState.IDLE);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
data-coalition={spawnRequestTable.coalition}
|
||||
className={`
|
||||
my-auto
|
||||
data-[coalition='blue']:text-blue-500
|
||||
data-[coalition='neutral']:text-gay-500
|
||||
data-[coalition='red']:text-red-500
|
||||
`}
|
||||
icon={faStar}
|
||||
/>
|
||||
<div>
|
||||
{getApp().getUnitsManager().getDatabase().getByName(spawnRequestTable.unit.unitType)?.label} (
|
||||
{spawnRequestTable.quickAccessName})
|
||||
</div>
|
||||
</OlDropdownItem>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<div className="p-2 text-sm text-white">No starred spawns, use the spawn menu to create a quick access spawn</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
</>
|
||||
</div>
|
||||
)}
|
||||
{!(blueprint === null) && <CompactUnitSpawnMenu blueprint={blueprint} starredSpawns={starredSpawns} latlng={latlng} coalition={spawnCoalition} onBack={() => setBlueprint(null)}/>}
|
||||
{!(effect === null) && latlng && <CompactEffectSpawnMenu effect={effect} latlng={latlng} onBack={() => setEffect(null)} />}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -1,108 +0,0 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { NO_SUBSTATE, OlympusState, OlympusSubState } from "../../constants/constants";
|
||||
import { OlDropdownItem } from "../components/oldropdown";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { LatLng } from "leaflet";
|
||||
import { AppStateChangedEvent, StarredSpawnContextMenuRequestEvent, StarredSpawnsChangedEvent } from "../../events";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { SpawnRequestTable } from "../../interfaces";
|
||||
import { faStar } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
export function StarredSpawnContextMenu(props: {}) {
|
||||
const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED);
|
||||
const [appSubState, setAppSubState] = useState(NO_SUBSTATE as OlympusSubState);
|
||||
const [xPosition, setXPosition] = useState(0);
|
||||
const [yPosition, setYPosition] = useState(0);
|
||||
const [latlng, setLatLng] = useState(null as null | LatLng);
|
||||
const [starredSpawns, setStarredSpawns] = useState({} as { [key: string]: SpawnRequestTable });
|
||||
|
||||
var contentRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
AppStateChangedEvent.on((state, subState) => {
|
||||
setAppState(state);
|
||||
setAppSubState(subState);
|
||||
});
|
||||
StarredSpawnsChangedEvent.on((starredSpawns) => setStarredSpawns({ ...starredSpawns }));
|
||||
StarredSpawnContextMenuRequestEvent.on((latlng) => {
|
||||
setLatLng(latlng);
|
||||
const containerPoint = getApp().getMap().latLngToContainerPoint(latlng);
|
||||
setXPosition(getApp().getMap().getContainer().offsetLeft + containerPoint.x);
|
||||
setYPosition(getApp().getMap().getContainer().offsetTop + containerPoint.y);
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (contentRef.current) {
|
||||
const content = contentRef.current as HTMLDivElement;
|
||||
|
||||
content.style.left = `${xPosition}px`;
|
||||
content.style.top = `${yPosition}px`;
|
||||
|
||||
let newXPosition = xPosition;
|
||||
let newYposition = yPosition;
|
||||
|
||||
let [cxr, cyb] = [content.getBoundingClientRect().x + content.clientWidth, content.getBoundingClientRect().y + content.clientHeight];
|
||||
|
||||
/* Try and move the content so it is inside the screen */
|
||||
if (cxr > window.innerWidth) newXPosition -= cxr - window.innerWidth;
|
||||
if (cyb > window.innerHeight) newYposition -= cyb - window.innerHeight;
|
||||
|
||||
content.style.left = `${newXPosition}px`;
|
||||
content.style.top = `${newYposition}px`;
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{appState === OlympusState.STARRED_SPAWN && (
|
||||
<>
|
||||
<div
|
||||
ref={contentRef}
|
||||
className={`
|
||||
absolute flex min-w-80 max-w-80 gap-2 rounded-md bg-olympus-600
|
||||
`}
|
||||
>
|
||||
<div
|
||||
className={`
|
||||
flex w-full flex-col gap-2 overflow-y-auto no-scrollbar p-2
|
||||
`}
|
||||
>
|
||||
{Object.values(starredSpawns).length > 0? Object.values(starredSpawns).map((spawnRequestTable) => {
|
||||
return (
|
||||
<OlDropdownItem
|
||||
className={`
|
||||
flex w-full content-center gap-2 text-sm text-white
|
||||
`}
|
||||
onClick={() => {
|
||||
if (latlng) {
|
||||
spawnRequestTable.unit.location = latlng;
|
||||
getApp().getUnitsManager().spawnUnits(spawnRequestTable.category, [spawnRequestTable.unit], spawnRequestTable.coalition, false);
|
||||
getApp().setState(OlympusState.IDLE)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
data-coalition={spawnRequestTable.coalition}
|
||||
className={`
|
||||
my-auto
|
||||
data-[coalition='blue']:text-blue-500
|
||||
data-[coalition='neutral']:text-gay-500
|
||||
data-[coalition='red']:text-red-500
|
||||
`}
|
||||
icon={faStar}
|
||||
/>
|
||||
<div>
|
||||
{getApp().getUnitsManager().getDatabase().getByName(spawnRequestTable.unit.unitType)?.label} ({spawnRequestTable.quickAccessName})
|
||||
</div>
|
||||
</OlDropdownItem>
|
||||
);
|
||||
}):
|
||||
<div className="p-2 text-sm text-white">No starred spawns, use the spawn menu to create a quick access spawn</div>}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -272,7 +272,6 @@ export function AirbaseMenu(props: { open: boolean; onClose: () => void; childre
|
||||
<UnitSpawnMenu
|
||||
blueprint={blueprint}
|
||||
starredSpawns={starredSpawns}
|
||||
spawnAtLocation={false}
|
||||
airbase={airbase}
|
||||
coalition={(airbase?.getCoalition() ?? "blue") as Coalition}
|
||||
/>
|
||||
|
||||
101
frontend/react/src/ui/panels/compacteffectspawnmenu.tsx
Normal file
101
frontend/react/src/ui/panels/compacteffectspawnmenu.tsx
Normal file
@ -0,0 +1,101 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { OlDropdown, OlDropdownItem } from "../components/oldropdown";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { OlympusState, SpawnSubState } from "../../constants/constants";
|
||||
import { OlStateButton } from "../components/olstatebutton";
|
||||
import { faArrowLeft, faSmog } from "@fortawesome/free-solid-svg-icons";
|
||||
import { LatLng } from "leaflet";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
export function CompactEffectSpawnMenu(props: { effect: string; latlng: LatLng; onBack: () => void }) {
|
||||
const [explosionType, setExplosionType] = useState("High explosive");
|
||||
const [smokeColor, setSmokeColor] = useState("white");
|
||||
|
||||
return (
|
||||
<div className="flex h-full flex-col gap-4">
|
||||
{props.effect === "explosion" && (
|
||||
<>
|
||||
<div className="flex">
|
||||
<FontAwesomeIcon
|
||||
onClick={props.onBack}
|
||||
icon={faArrowLeft}
|
||||
className={`
|
||||
my-auto mr-1 h-4 cursor-pointer rounded-md p-2
|
||||
dark:text-gray-500 dark:hover:bg-gray-700 dark:hover:text-white
|
||||
`}
|
||||
/>
|
||||
<span className="my-auto text-white">Explosion type</span>
|
||||
</div>
|
||||
<OlDropdown label={explosionType} className="w-full">
|
||||
{["High explosive", "Napalm", "White phosphorous"].map((optionExplosionType) => {
|
||||
return (
|
||||
<OlDropdownItem
|
||||
key={optionExplosionType}
|
||||
onClick={() => {
|
||||
setExplosionType(optionExplosionType);
|
||||
}}
|
||||
>
|
||||
{optionExplosionType}
|
||||
</OlDropdownItem>
|
||||
);
|
||||
})}
|
||||
</OlDropdown>
|
||||
</>
|
||||
)}
|
||||
{props.effect === "smoke" && (
|
||||
<>
|
||||
<div className="flex">
|
||||
<FontAwesomeIcon
|
||||
onClick={props.onBack}
|
||||
icon={faArrowLeft}
|
||||
className={`
|
||||
my-auto mr-1 h-4 cursor-pointer rounded-md p-2
|
||||
dark:text-gray-500 dark:hover:bg-gray-700 dark:hover:text-white
|
||||
`}
|
||||
/>
|
||||
<span className="my-auto text-white">Smoke color</span>
|
||||
</div>
|
||||
<div className="flex w-full gap-2">
|
||||
{["white", "blue", "red", "green", "orange"].map((optionSmokeColor) => {
|
||||
return (
|
||||
<OlStateButton
|
||||
checked={smokeColor === optionSmokeColor}
|
||||
icon={faSmog}
|
||||
onClick={() => {
|
||||
setSmokeColor(optionSmokeColor);
|
||||
}}
|
||||
tooltip=""
|
||||
buttonColor={optionSmokeColor}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
className={`
|
||||
m-2 rounded-lg bg-blue-600 px-5 py-2.5 text-sm font-medium text-white
|
||||
focus:outline-none focus:ring-4
|
||||
`}
|
||||
onClick={() => {
|
||||
if (props.effect === "explosion") {
|
||||
if (explosionType === "High explosive") getApp().getServerManager().spawnExplosion(50, "normal", props.latlng);
|
||||
else if (explosionType === "Napalm") getApp().getServerManager().spawnExplosion(50, "napalm", props.latlng);
|
||||
else if (explosionType === "White phosphorous") getApp().getServerManager().spawnExplosion(50, "phosphorous", props.latlng);
|
||||
getApp().getMap().addExplosionMarker(props.latlng);
|
||||
} else if (props.effect === "smoke") {
|
||||
getApp().getServerManager().spawnSmoke(smokeColor, props.latlng);
|
||||
getApp()
|
||||
.getMap()
|
||||
.addSmokeMarker(props.latlng, smokeColor ?? "white");
|
||||
}
|
||||
|
||||
getApp().setState(OlympusState.IDLE);
|
||||
}}
|
||||
>
|
||||
Spawn
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
445
frontend/react/src/ui/panels/compactunitspawnmenu.tsx
Normal file
445
frontend/react/src/ui/panels/compactunitspawnmenu.tsx
Normal file
@ -0,0 +1,445 @@
|
||||
import React, { useState, useEffect, useCallback } from "react";
|
||||
import { OlUnitSummary } from "../components/olunitsummary";
|
||||
import { OlCoalitionToggle } from "../components/olcoalitiontoggle";
|
||||
import { OlNumberInput } from "../components/olnumberinput";
|
||||
import { OlLabelToggle } from "../components/ollabeltoggle";
|
||||
import { OlRangeSlider } from "../components/olrangeslider";
|
||||
import { OlDropdownItem, OlDropdown } from "../components/oldropdown";
|
||||
import { LoadoutBlueprint, SpawnRequestTable, UnitBlueprint } from "../../interfaces";
|
||||
import { OlStateButton } from "../components/olstatebutton";
|
||||
import { Coalition } from "../../types/types";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { ftToM, hash } from "../../other/utils";
|
||||
import { LatLng } from "leaflet";
|
||||
import { Airbase } from "../../mission/airbase";
|
||||
import { altitudeIncrements, groupUnitCount, maxAltitudeValues, minAltitudeValues, OlympusState, SpawnSubState } from "../../constants/constants";
|
||||
import { faArrowLeft, faStar } from "@fortawesome/free-solid-svg-icons";
|
||||
import { OlStringInput } from "../components/olstringinput";
|
||||
import { countryCodes } from "../data/codes";
|
||||
import { OlAccordion } from "../components/olaccordion";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
export function CompactUnitSpawnMenu(props: {
|
||||
starredSpawns: { [key: string]: SpawnRequestTable };
|
||||
blueprint: UnitBlueprint;
|
||||
onBack: () => void;
|
||||
latlng?: LatLng | null;
|
||||
airbase?: Airbase | null;
|
||||
coalition?: Coalition;
|
||||
}) {
|
||||
/* Compute the min and max values depending on the unit type */
|
||||
const minNumber = 1;
|
||||
const maxNumber = groupUnitCount[props.blueprint.category];
|
||||
const minAltitude = minAltitudeValues[props.blueprint.category];
|
||||
const maxAltitude = maxAltitudeValues[props.blueprint.category];
|
||||
const altitudeStep = altitudeIncrements[props.blueprint.category];
|
||||
|
||||
/* State initialization */
|
||||
const [spawnCoalition, setSpawnCoalition] = useState("blue" as Coalition);
|
||||
const [spawnNumber, setSpawnNumber] = useState(1);
|
||||
const [spawnRole, setSpawnRole] = useState("");
|
||||
const [spawnLoadoutName, setSpawnLoadout] = useState("");
|
||||
const [spawnAltitude, setSpawnAltitude] = useState((maxAltitude - minAltitude) / 2);
|
||||
const [spawnAltitudeType, setSpawnAltitudeType] = useState(false);
|
||||
const [spawnLiveryID, setSpawnLiveryID] = useState("");
|
||||
const [spawnSkill, setSpawnSkill] = useState("High");
|
||||
|
||||
const [showAdvancedOptions, setShowAdvancedOptions] = useState(false);
|
||||
const [showLoadout, setShowLoadout] = useState(false);
|
||||
const [showUnitSummary, setShowUnitSummary] = useState(false);
|
||||
|
||||
const [quickAccessName, setQuickAccessName] = useState("No name");
|
||||
const [key, setKey] = useState("");
|
||||
const [spawnRequestTable, setSpawnRequestTable] = useState(null as null | SpawnRequestTable);
|
||||
|
||||
/* When the menu is opened show the unit preview on the map as a cursor */
|
||||
useEffect(() => {
|
||||
if (!props.airbase && !props.latlng && spawnRequestTable) {
|
||||
/* Refresh the unique key identified */
|
||||
const newKey = hash(JSON.stringify(spawnRequestTable));
|
||||
setKey(newKey);
|
||||
|
||||
getApp()?.getMap()?.setSpawnRequestTable(spawnRequestTable);
|
||||
getApp().setState(OlympusState.SPAWN, SpawnSubState.SPAWN_UNIT);
|
||||
}
|
||||
}, [spawnRequestTable]);
|
||||
|
||||
/* Callback and effect to update the quick access name of the starredSpawn */
|
||||
const updateStarredSpawnQuickAccessNameS = useCallback(() => {
|
||||
if (key in props.starredSpawns) props.starredSpawns[key].quickAccessName = quickAccessName;
|
||||
}, [props.starredSpawns, key, quickAccessName]);
|
||||
useEffect(updateStarredSpawnQuickAccessNameS, [quickAccessName]);
|
||||
|
||||
/* Callback and effect to update the quick access name in the input field */
|
||||
const updateQuickAccessName = useCallback(() => {
|
||||
/* If the spawn is starred, set the quick access name */
|
||||
if (key in props.starredSpawns && props.starredSpawns[key].quickAccessName) setQuickAccessName(props.starredSpawns[key].quickAccessName);
|
||||
else setQuickAccessName("No name");
|
||||
}, [props.starredSpawns, key]);
|
||||
useEffect(updateQuickAccessName, [key]);
|
||||
|
||||
/* Callback and effect to update the spawn request table */
|
||||
const updateSpawnRequestTable = useCallback(() => {
|
||||
if (props.blueprint !== null) {
|
||||
setSpawnRequestTable({
|
||||
category: props.blueprint.category,
|
||||
unit: {
|
||||
unitType: props.blueprint.name,
|
||||
location: props.latlng ?? new LatLng(0, 0), // This will be filled when the user clicks on the map to spawn the unit
|
||||
skill: spawnSkill,
|
||||
liveryID: spawnLiveryID,
|
||||
altitude: ftToM(spawnAltitude),
|
||||
loadout: props.blueprint.loadouts?.find((loadout) => loadout.name === spawnLoadoutName)?.code ?? "",
|
||||
},
|
||||
amount: spawnNumber,
|
||||
coalition: spawnCoalition,
|
||||
});
|
||||
}
|
||||
}, [props.blueprint, spawnAltitude, spawnLoadoutName, spawnCoalition, spawnNumber, spawnLiveryID, spawnSkill]);
|
||||
useEffect(updateSpawnRequestTable, [props.blueprint, spawnAltitude, spawnLoadoutName, spawnCoalition, spawnNumber, spawnLiveryID, spawnSkill]);
|
||||
|
||||
/* Effect to update the coalition if it is force externally */
|
||||
useEffect(() => {
|
||||
if (props.coalition) setSpawnCoalition(props.coalition);
|
||||
}, [props.coalition]);
|
||||
|
||||
/* Get a list of all the roles */
|
||||
const roles: string[] = [];
|
||||
(props.blueprint as UnitBlueprint).loadouts?.forEach((loadout) => {
|
||||
loadout.roles.forEach((role) => {
|
||||
!roles.includes(role) && roles.push(role);
|
||||
});
|
||||
});
|
||||
|
||||
/* Initialize the role */
|
||||
spawnRole === "" && roles.length > 0 && setSpawnRole(roles[0]);
|
||||
|
||||
/* Get a list of all the loadouts */
|
||||
const loadouts: LoadoutBlueprint[] = [];
|
||||
(props.blueprint as UnitBlueprint).loadouts?.forEach((loadout) => {
|
||||
loadout.roles.includes(spawnRole) && loadouts.push(loadout);
|
||||
});
|
||||
|
||||
/* Initialize the loadout */
|
||||
spawnLoadoutName === "" && loadouts.length > 0 && setSpawnLoadout(loadouts[0].name);
|
||||
const spawnLoadout = props.blueprint.loadouts?.find((loadout) => {
|
||||
return loadout.name === spawnLoadoutName;
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<div className="flex h-fit flex-col gap-3">
|
||||
<div className="flex">
|
||||
<FontAwesomeIcon
|
||||
onClick={props.onBack}
|
||||
icon={faArrowLeft}
|
||||
className={`
|
||||
my-auto mr-1 h-4 cursor-pointer rounded-md p-2
|
||||
dark:text-gray-500 dark:hover:bg-gray-700 dark:hover:text-white
|
||||
`}
|
||||
/>
|
||||
<h5 className="my-auto text-gray-200">{props.blueprint.label}</h5>
|
||||
<OlNumberInput
|
||||
className={"ml-auto"}
|
||||
value={spawnNumber}
|
||||
min={minNumber}
|
||||
max={maxNumber}
|
||||
onDecrease={() => {
|
||||
setSpawnNumber(Math.max(minNumber, spawnNumber - 1));
|
||||
}}
|
||||
onIncrease={() => {
|
||||
setSpawnNumber(Math.min(maxNumber, spawnNumber + 1));
|
||||
}}
|
||||
onChange={(ev) => {
|
||||
!isNaN(Number(ev.target.value)) && setSpawnNumber(Math.max(minNumber, Math.min(maxNumber, Number(ev.target.value))));
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
inline-flex w-full flex-row content-center justify-between gap-2
|
||||
`}
|
||||
>
|
||||
<div className="my-auto text-sm text-white">Quick access: </div>
|
||||
<OlStringInput
|
||||
onChange={(e) => {
|
||||
setQuickAccessName(e.target.value);
|
||||
}}
|
||||
value={quickAccessName}
|
||||
/>
|
||||
<OlStateButton
|
||||
onClick={() => {
|
||||
if (spawnRequestTable)
|
||||
key in props.starredSpawns
|
||||
? getApp().getMap().removeStarredSpawnRequestTable(key)
|
||||
: getApp().getMap().addStarredSpawnRequestTable(key, spawnRequestTable);
|
||||
}}
|
||||
tooltip="Save this spawn for quick access"
|
||||
checked={key in props.starredSpawns}
|
||||
icon={faStar}
|
||||
></OlStateButton>
|
||||
</div>
|
||||
{["aircraft", "helicopter"].includes(props.blueprint.category) && (
|
||||
<>
|
||||
{!props.airbase && (
|
||||
<div>
|
||||
<div
|
||||
className={`
|
||||
flex flex-row content-center items-center justify-between
|
||||
`}
|
||||
>
|
||||
<div className="flex flex-col">
|
||||
<span
|
||||
className={`
|
||||
font-normal
|
||||
dark:text-white
|
||||
`}
|
||||
>
|
||||
Altitude
|
||||
</span>
|
||||
<span
|
||||
className={`
|
||||
font-bold
|
||||
dark:text-blue-500
|
||||
`}
|
||||
>{`${Intl.NumberFormat("en-US").format(spawnAltitude)} FT`}</span>
|
||||
</div>
|
||||
<OlLabelToggle toggled={spawnAltitudeType} leftLabel={"AGL"} rightLabel={"ASL"} onClick={() => setSpawnAltitudeType(!spawnAltitudeType)} />
|
||||
</div>
|
||||
<OlRangeSlider
|
||||
onChange={(ev) => setSpawnAltitude(Number(ev.target.value))}
|
||||
value={spawnAltitude}
|
||||
min={minAltitude}
|
||||
max={maxAltitude}
|
||||
step={altitudeStep}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex content-center justify-between gap-2">
|
||||
<span
|
||||
className={`
|
||||
my-auto font-normal
|
||||
dark:text-white
|
||||
`}
|
||||
>
|
||||
Role
|
||||
</span>
|
||||
<OlDropdown label={spawnRole} className="w-64">
|
||||
{roles.map((role) => {
|
||||
return (
|
||||
<OlDropdownItem
|
||||
onClick={() => {
|
||||
setSpawnRole(role);
|
||||
setSpawnLoadout("");
|
||||
}}
|
||||
className={`w-full`}
|
||||
>
|
||||
{role}
|
||||
</OlDropdownItem>
|
||||
);
|
||||
})}
|
||||
</OlDropdown>
|
||||
</div>
|
||||
<div className="flex content-center justify-between gap-2">
|
||||
<span
|
||||
className={`
|
||||
my-auto font-normal
|
||||
dark:text-white
|
||||
`}
|
||||
>
|
||||
Weapons
|
||||
</span>
|
||||
<OlDropdown label={spawnLoadoutName} className={`w-64`}>
|
||||
{loadouts.map((loadout) => {
|
||||
return (
|
||||
<OlDropdownItem
|
||||
onClick={() => {
|
||||
setSpawnLoadout(loadout.name);
|
||||
}}
|
||||
className={`w-full`}
|
||||
>
|
||||
<span
|
||||
className={`
|
||||
w-full overflow-hidden text-ellipsis text-nowrap
|
||||
text-left w-max-full
|
||||
`}
|
||||
>
|
||||
{loadout.name}
|
||||
</span>
|
||||
</OlDropdownItem>
|
||||
);
|
||||
})}
|
||||
</OlDropdown>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<OlAccordion
|
||||
onClick={() => {
|
||||
setShowAdvancedOptions(!showAdvancedOptions);
|
||||
}}
|
||||
open={showAdvancedOptions}
|
||||
title="Advanced options"
|
||||
>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex content-center justify-between gap-2">
|
||||
<span
|
||||
className={`
|
||||
my-auto font-normal
|
||||
dark:text-white
|
||||
`}
|
||||
>
|
||||
Livery
|
||||
</span>
|
||||
<OlDropdown
|
||||
label={props.blueprint.liveries ? (props.blueprint.liveries[spawnLiveryID]?.name ?? "Default") : "No livery"}
|
||||
className={`w-64`}
|
||||
>
|
||||
{props.blueprint.liveries &&
|
||||
Object.keys(props.blueprint.liveries)
|
||||
.sort((ida, idb) => {
|
||||
if (props.blueprint.liveries) {
|
||||
if (props.blueprint.liveries[ida].countries.length > 1) return 1;
|
||||
return props.blueprint.liveries[ida].countries[0] > props.blueprint.liveries[idb].countries[0] ? 1 : -1;
|
||||
} else return -1;
|
||||
})
|
||||
.map((id) => {
|
||||
let country = Object.values(countryCodes).find((countryCode) => {
|
||||
if (props.blueprint.liveries && countryCode.liveryCodes?.includes(props.blueprint.liveries[id].countries[0])) return true;
|
||||
});
|
||||
return (
|
||||
<OlDropdownItem
|
||||
onClick={() => {
|
||||
setSpawnLiveryID(id);
|
||||
}}
|
||||
className={`w-full`}
|
||||
>
|
||||
<span
|
||||
className={`
|
||||
w-full content-center overflow-hidden
|
||||
text-ellipsis text-nowrap text-left w-max-full
|
||||
flex gap-2
|
||||
`}
|
||||
>
|
||||
{props.blueprint.liveries && props.blueprint.liveries[id].countries.length == 1 && (
|
||||
<img src={`images/countries/${country?.flagCode.toLowerCase()}.svg`} className={`
|
||||
h-6
|
||||
`} />
|
||||
)}
|
||||
|
||||
<div className="my-auto truncate">
|
||||
<span
|
||||
className={`
|
||||
w-full overflow-hidden text-left w-max-full
|
||||
`}
|
||||
>
|
||||
{props.blueprint.liveries ? props.blueprint.liveries[id].name : ""}
|
||||
</span>
|
||||
</div>
|
||||
</span>
|
||||
</OlDropdownItem>
|
||||
);
|
||||
})}
|
||||
</OlDropdown>
|
||||
</div>
|
||||
<div className="flex content-center justify-between gap-2">
|
||||
<span
|
||||
className={`
|
||||
my-auto font-normal
|
||||
dark:text-white
|
||||
`}
|
||||
>
|
||||
Skill
|
||||
</span>
|
||||
<OlDropdown label={spawnSkill} className={`w-64`}>
|
||||
{["Average", "Good", "High", "Excellent"].map((skill) => {
|
||||
return (
|
||||
<OlDropdownItem
|
||||
onClick={() => {
|
||||
setSpawnSkill(skill);
|
||||
}}
|
||||
className={`w-full`}
|
||||
>
|
||||
<span
|
||||
className={`
|
||||
w-full content-center overflow-hidden text-ellipsis
|
||||
text-nowrap text-left w-max-full flex gap-2
|
||||
`}
|
||||
>
|
||||
<div className="my-auto">{skill}</div>
|
||||
</span>
|
||||
</OlDropdownItem>
|
||||
);
|
||||
})}
|
||||
</OlDropdown>
|
||||
</div>
|
||||
</div>
|
||||
</OlAccordion>
|
||||
</div>
|
||||
<OlAccordion
|
||||
onClick={() => {
|
||||
setShowUnitSummary(!showUnitSummary);
|
||||
}}
|
||||
open={showUnitSummary}
|
||||
title="Unit summary"
|
||||
>
|
||||
<OlUnitSummary blueprint={props.blueprint} coalition={spawnCoalition} />
|
||||
</OlAccordion>
|
||||
{spawnLoadout && spawnLoadout.items.length > 0 && (
|
||||
<OlAccordion
|
||||
onClick={() => {
|
||||
setShowLoadout(!showLoadout);
|
||||
}}
|
||||
open={showLoadout}
|
||||
title="Loadout"
|
||||
>
|
||||
{spawnLoadout.items.map((item) => {
|
||||
return (
|
||||
<div className="flex content-center gap-2">
|
||||
<div
|
||||
className={`
|
||||
my-auto w-6 min-w-6 rounded-full py-0.5 text-center text-sm
|
||||
font-bold text-gray-500
|
||||
dark:bg-[#17212D]
|
||||
`}
|
||||
>
|
||||
{item.quantity}
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
my-auto overflow-hidden text-ellipsis text-nowrap text-sm
|
||||
dark:text-gray-300
|
||||
`}
|
||||
>
|
||||
{item.name}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</OlAccordion>
|
||||
)}
|
||||
{(props.latlng || props.airbase) && (
|
||||
<button
|
||||
type="button"
|
||||
data-coalition={props.coalition ?? "blue"}
|
||||
className={`
|
||||
m-2 rounded-lg px-5 py-2.5 text-sm font-medium text-white
|
||||
data-[coalition='blue']:bg-blue-600
|
||||
data-[coalition='neutral']:bg-gray-400
|
||||
data-[coalition='red']:bg-red-500
|
||||
focus:outline-none focus:ring-4
|
||||
`}
|
||||
onClick={() => {
|
||||
if (spawnRequestTable)
|
||||
getApp()
|
||||
.getUnitsManager()
|
||||
.spawnUnits(spawnRequestTable.category, Array(spawnRequestTable.amount).fill(spawnRequestTable.unit), spawnRequestTable.coalition, false, props.airbase?.getName() ?? undefined);
|
||||
|
||||
getApp().setState(OlympusState.IDLE)
|
||||
}}
|
||||
>
|
||||
Spawn
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -2,7 +2,6 @@ import { faArrowLeft, faClose } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { FaChevronDown, FaChevronUp } from "react-icons/fa";
|
||||
import { HideMenuEvent } from "../../../events";
|
||||
|
||||
export function Menu(props: {
|
||||
title: string;
|
||||
@ -16,11 +15,7 @@ export function Menu(props: {
|
||||
const [hide, setHide] = useState(true);
|
||||
|
||||
if (!props.open && hide) setHide(false);
|
||||
|
||||
useEffect(() => {
|
||||
HideMenuEvent.dispatch(hide)
|
||||
}, [hide])
|
||||
|
||||
|
||||
return (
|
||||
<div
|
||||
data-open={props.open}
|
||||
|
||||
@ -1,15 +1,17 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { faHandPointer, faJetFighter, faMap, IconDefinition } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { MAP_OPTIONS_DEFAULTS, NO_SUBSTATE, OlympusState, OlympusSubState, SpawnSubState } from "../../constants/constants";
|
||||
import { ContextActionTarget, MAP_OPTIONS_DEFAULTS, NO_SUBSTATE, OlympusState, OlympusSubState, SpawnSubState } from "../../constants/constants";
|
||||
import { AppStateChangedEvent, MapOptionsChangedEvent } from "../../events";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { ContextAction } from "../../unit/contextaction";
|
||||
|
||||
export function ControlsPanel(props: {}) {
|
||||
const [controls, setControls] = useState(
|
||||
null as
|
||||
| {
|
||||
actions: (string | number | IconDefinition)[];
|
||||
target: IconDefinition;
|
||||
target: IconDefinition | null;
|
||||
text: string;
|
||||
}[]
|
||||
| null
|
||||
@ -26,11 +28,11 @@ export function ControlsPanel(props: {}) {
|
||||
MapOptionsChangedEvent.on((mapOptions) => setMapOptions({ ...mapOptions }));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const callback = useCallback(() => {
|
||||
const touch = matchMedia("(hover: none)").matches;
|
||||
let controls: {
|
||||
actions: (string | number | IconDefinition)[];
|
||||
target: IconDefinition;
|
||||
target: IconDefinition | null;
|
||||
text: string;
|
||||
}[] = [];
|
||||
|
||||
@ -42,7 +44,7 @@ export function ControlsPanel(props: {}) {
|
||||
text: "Select unit",
|
||||
},
|
||||
{
|
||||
actions: [touch ? faHandPointer : "LMB", 2],
|
||||
actions: touch ? [faHandPointer, "Hold"] : ["RMB"],
|
||||
target: faMap,
|
||||
text: "Quick spawn menu",
|
||||
},
|
||||
@ -57,6 +59,58 @@ export function ControlsPanel(props: {}) {
|
||||
text: "Move map location",
|
||||
},
|
||||
];
|
||||
} else if (appState === OlympusState.SPAWN_CONTEXT) {
|
||||
controls = [
|
||||
{
|
||||
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() ?? {})
|
||||
.sort((a: ContextAction, b: ContextAction) => (a.getLabel() > b.getLabel() ? 1 : -1))
|
||||
.filter((contextAction: ContextAction) => contextAction.getOptions().code)
|
||||
.map((contextAction: ContextAction) => {
|
||||
let actions: (string | IconDefinition)[] = [];
|
||||
contextAction.getOptions().shiftKey && actions.push("Shift");
|
||||
contextAction.getOptions().altKey && actions.push("Alt");
|
||||
contextAction.getOptions().ctrlKey && actions.push("Ctrl");
|
||||
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")
|
||||
);
|
||||
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(),
|
||||
};
|
||||
});
|
||||
}
|
||||
} else if (appState === OlympusState.SPAWN) {
|
||||
controls = [
|
||||
{
|
||||
@ -91,7 +145,9 @@ export function ControlsPanel(props: {}) {
|
||||
}
|
||||
|
||||
setControls(controls);
|
||||
}, [appState, appSubState]);
|
||||
}, [appState, appSubState, mapOptions]);
|
||||
|
||||
useEffect(callback, [appState, appSubState, mapOptions]);
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -122,9 +178,14 @@ 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>}
|
||||
|
||||
@ -15,7 +15,7 @@ import {
|
||||
olButtonsVisibilityNavyunit,
|
||||
olButtonsVisibilityOlympus,
|
||||
} from "../components/olicons";
|
||||
import { FaChevronLeft, FaChevronRight } from "react-icons/fa6";
|
||||
import { FaChevronLeft, FaChevronRight, FaComputer, FaTabletScreenButton } from "react-icons/fa6";
|
||||
import { CommandModeOptionsChangedEvent, ConfigLoadedEvent, HiddenTypesChangedEvent, MapOptionsChangedEvent, MapSourceChangedEvent } from "../../events";
|
||||
import { BLUE_COMMANDER, COMMAND_MODE_OPTIONS_DEFAULTS, MAP_HIDDEN_TYPES_DEFAULTS, MAP_OPTIONS_DEFAULTS } from "../../constants/constants";
|
||||
import { OlympusConfig } from "../../interfaces";
|
||||
@ -111,12 +111,18 @@ export function Header() {
|
||||
</div>
|
||||
</div>
|
||||
{commandModeOptions.commandMode === BLUE_COMMANDER && (
|
||||
<div
|
||||
className={`flex h-full rounded-md bg-blue-600 px-4 text-white`}
|
||||
>
|
||||
<div className={`flex h-full rounded-md bg-blue-600 px-4 text-white`}>
|
||||
<span className="my-auto font-bold">BLUE Commander ({commandModeOptions.spawnPoints.blue} points)</span>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className="cursor-pointer rounded-full bg-blue-500 px-4 py-2 text-white"
|
||||
onClick={() => {
|
||||
getApp().getMap().setOption("tabletMode", !mapOptions.tabletMode);
|
||||
}}
|
||||
>
|
||||
{mapOptions.tabletMode ? <FaTabletScreenButton /> : <FaComputer />}
|
||||
</div>
|
||||
<div className={`flex h-fit flex-row items-center justify-start gap-1`}>
|
||||
<OlLockStateButton
|
||||
checked={!mapOptions.protectDCSUnits}
|
||||
@ -213,7 +219,7 @@ export function Header() {
|
||||
.getMap()
|
||||
.setOption("cameraPluginMode", mapOptions.cameraPluginMode === "live" ? "map" : "live");
|
||||
}}
|
||||
></OlLabelToggle>
|
||||
/>
|
||||
<OlStateButton
|
||||
checked={mapOptions.cameraPluginEnabled}
|
||||
icon={faCamera}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { AppStateChangedEvent, ContextActionChangedEvent, HideMenuEvent, HotgroupsChangedEvent, InfoPopupEvent } from "../../events";
|
||||
import { AppStateChangedEvent, ContextActionChangedEvent, HotgroupsChangedEvent, InfoPopupEvent } from "../../events";
|
||||
import { OlympusState } from "../../constants/constants";
|
||||
import { ContextAction } from "../../unit/contextaction";
|
||||
import { OlStateButton } from "../components/olstatebutton";
|
||||
@ -13,7 +13,6 @@ export function HotGroupBar(props: {}) {
|
||||
|
||||
useEffect(() => {
|
||||
AppStateChangedEvent.on((state, subState) => setAppState(state));
|
||||
HideMenuEvent.on((hidden) => setMenuHidden(hidden));
|
||||
HotgroupsChangedEvent.on((hotgroups) => setHotgroups({ ...hotgroups }));
|
||||
}, []);
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { AppStateChangedEvent, ContextActionChangedEvent, HideMenuEvent, InfoPopupEvent } from "../../events";
|
||||
import { AppStateChangedEvent, ContextActionChangedEvent, InfoPopupEvent } from "../../events";
|
||||
import { OlympusState } from "../../constants/constants";
|
||||
import { ContextAction } from "../../unit/contextaction";
|
||||
|
||||
@ -7,35 +7,16 @@ export function InfoBar(props: {}) {
|
||||
const [messages, setMessages] = useState([] as string[]);
|
||||
const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED);
|
||||
const [contextAction, setContextAction] = useState(null as ContextAction | null);
|
||||
const [menuHidden, setMenuHidden] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
InfoPopupEvent.on((messages) => setMessages([...messages]));
|
||||
AppStateChangedEvent.on((state, subState) => setAppState(state));
|
||||
ContextActionChangedEvent.on((contextAction) => setContextAction(contextAction));
|
||||
HideMenuEvent.on((hidden) => setMenuHidden(hidden));
|
||||
}, []);
|
||||
|
||||
let topString = "";
|
||||
if (appState === OlympusState.UNIT_CONTROL) {
|
||||
if (contextAction === null) {
|
||||
topString = "top-36";
|
||||
} else {
|
||||
topString = "top-48";
|
||||
}
|
||||
} else {
|
||||
topString = "top-16";
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
data-menuhidden={menuHidden || appState === OlympusState.IDLE}
|
||||
className={`
|
||||
absolute left-[50%]
|
||||
data-[menuhidden='false']:translate-x-[calc(200px-50%+2rem)]
|
||||
data-[menuhidden='true']:translate-x-[calc(-50%+2rem)]
|
||||
${topString}
|
||||
`}
|
||||
className={`absolute left-[50%] top-16`}
|
||||
>
|
||||
{messages.slice(Math.max(0, messages.length - 4), Math.max(0, messages.length)).map((message, idx) => {
|
||||
return (
|
||||
|
||||
@ -436,7 +436,6 @@ export function SpawnMenu(props: { open: boolean; onClose: () => void; children?
|
||||
{!(blueprint === null) && (
|
||||
<UnitSpawnMenu
|
||||
blueprint={blueprint}
|
||||
spawnAtLocation={true}
|
||||
starredSpawns={starredSpawns}
|
||||
coalition={commandModeOptions.commandMode !== GAME_MASTER ? (commandModeOptions.commandMode === BLUE_COMMANDER ? "blue" : "red") : undefined}
|
||||
/>
|
||||
|
||||
@ -3,19 +3,20 @@ import { ContextActionSet } from "../../unit/contextactionset";
|
||||
import { OlStateButton } from "../components/olstatebutton";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { ContextAction } from "../../unit/contextaction";
|
||||
import { CONTEXT_ACTION_COLORS } from "../../constants/constants";
|
||||
import { CONTEXT_ACTION_COLORS, MAP_OPTIONS_DEFAULTS } from "../../constants/constants";
|
||||
import { FaInfoCircle } from "react-icons/fa";
|
||||
import { FaChevronLeft, FaChevronRight } from "react-icons/fa6";
|
||||
import { FaChevronDown, FaChevronLeft, FaChevronRight, FaChevronUp } from "react-icons/fa6";
|
||||
import { OlympusState } from "../../constants/constants";
|
||||
import { AppStateChangedEvent, ContextActionChangedEvent, ContextActionSetChangedEvent, HideMenuEvent } from "../../events";
|
||||
import { AppStateChangedEvent, ContextActionChangedEvent, ContextActionSetChangedEvent, MapOptionsChangedEvent } from "../../events";
|
||||
|
||||
export function UnitControlBar(props: {}) {
|
||||
const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED);
|
||||
const [contextActionSet, setcontextActionSet] = useState(null as ContextActionSet | null);
|
||||
const [contextAction, setContextAction] = useState(null as ContextAction | null);
|
||||
const [scrolledLeft, setScrolledLeft] = useState(true);
|
||||
const [scrolledRight, setScrolledRight] = useState(false);
|
||||
const [scrolledTop, setScrolledTop] = useState(true);
|
||||
const [scrolledBottom, setScrolledBottom] = useState(false);
|
||||
const [menuHidden, setMenuHidden] = useState(false);
|
||||
const [mapOptions, setMapOptions] = useState(MAP_OPTIONS_DEFAULTS);
|
||||
|
||||
/* Initialize the "scroll" position of the element */
|
||||
var scrollRef = useRef(null);
|
||||
@ -27,18 +28,18 @@ export function UnitControlBar(props: {}) {
|
||||
AppStateChangedEvent.on((state, subState) => setAppState(state));
|
||||
ContextActionSetChangedEvent.on((contextActionSet) => setcontextActionSet(contextActionSet));
|
||||
ContextActionChangedEvent.on((contextAction) => setContextAction(contextAction));
|
||||
HideMenuEvent.on((hidden) => setMenuHidden(hidden));
|
||||
MapOptionsChangedEvent.on((mapOptions) => setMapOptions({ ...mapOptions }));
|
||||
}, []);
|
||||
|
||||
function onScroll(el) {
|
||||
const sl = el.scrollLeft;
|
||||
const sr = el.scrollWidth - el.scrollLeft - el.clientWidth;
|
||||
const sl = el.scrollTop;
|
||||
const sr = el.scrollHeight - el.scrollTop - el.clientHeight;
|
||||
|
||||
sl < 1 && !scrolledLeft && setScrolledLeft(true);
|
||||
sl > 1 && scrolledLeft && setScrolledLeft(false);
|
||||
sl < 1 && !scrolledTop && setScrolledTop(true);
|
||||
sl > 1 && scrolledTop && setScrolledTop(false);
|
||||
|
||||
sr < 1 && !scrolledRight && setScrolledRight(true);
|
||||
sr > 1 && scrolledRight && setScrolledRight(false);
|
||||
sr < 1 && !scrolledBottom && setScrolledBottom(true);
|
||||
sr > 1 && scrolledBottom && setScrolledBottom(false);
|
||||
}
|
||||
|
||||
let reorderedActions: ContextAction[] = contextActionSet
|
||||
@ -49,70 +50,68 @@ export function UnitControlBar(props: {}) {
|
||||
<>
|
||||
{appState === OlympusState.UNIT_CONTROL && contextActionSet && Object.keys(contextActionSet.getContextActions()).length > 0 && (
|
||||
<>
|
||||
<div
|
||||
data-menuhidden={menuHidden}
|
||||
className={`
|
||||
absolute left-[50%] top-16 flex max-w-[80%] gap-2 rounded-md
|
||||
bg-gray-200
|
||||
dark:bg-olympus-900
|
||||
data-[menuhidden='false']:translate-x-[calc(200px-50%+2rem)]
|
||||
data-[menuhidden='true']:translate-x-[calc(-50%+2rem)]
|
||||
`}
|
||||
>
|
||||
{!scrolledLeft && (
|
||||
<FaChevronLeft
|
||||
{mapOptions.tabletMode && (
|
||||
<>
|
||||
<div
|
||||
data-menuhidden={menuHidden}
|
||||
className={`
|
||||
absolute left-0 h-full w-6 rounded-lg px-2 py-3.5
|
||||
text-gray-200
|
||||
absolute right-2 top-16 flex max-h-[80%] gap-2 rounded-md
|
||||
bg-gray-200
|
||||
dark:bg-olympus-900
|
||||
`}
|
||||
/>
|
||||
)}
|
||||
<div className="flex gap-2 overflow-x-auto no-scrollbar p-2" onScroll={(ev) => onScroll(ev.target)} ref={scrollRef}>
|
||||
{reorderedActions.map((contextActionIt: ContextAction) => {
|
||||
return (
|
||||
<div className="flex flex-col gap-1">
|
||||
<OlStateButton
|
||||
key={contextActionIt.getId()}
|
||||
checked={contextActionIt === contextAction}
|
||||
icon={contextActionIt.getIcon()}
|
||||
tooltip={contextActionIt.getLabel()}
|
||||
buttonColor={CONTEXT_ACTION_COLORS[contextActionIt.getOptions().type ?? 0]}
|
||||
onClick={() => {
|
||||
if (contextActionIt.getOptions().executeImmediately) {
|
||||
contextActionIt.executeCallback(null, null);
|
||||
} else {
|
||||
contextActionIt !== contextAction ? getApp().getMap().setContextAction(contextActionIt) : getApp().getMap().setContextAction(null);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
className={`
|
||||
rounded-sm bg-gray-400 text-center text-xs font-bold
|
||||
text-olympus-800
|
||||
`}
|
||||
>
|
||||
{(contextActionIt.getOptions().hotkey ?? "").replace("Key", "")}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{!scrolledRight && (
|
||||
<FaChevronRight
|
||||
className={`
|
||||
absolute right-0 h-full w-6 rounded-lg px-2 py-3.5
|
||||
text-gray-200
|
||||
dark:bg-olympus-900
|
||||
`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{/*}
|
||||
>
|
||||
{!scrolledTop && (
|
||||
<FaChevronUp
|
||||
className={`
|
||||
absolute top-0 h-6 w-full rounded-lg px-2 py-3.5
|
||||
text-gray-200
|
||||
dark:bg-olympus-900
|
||||
`}
|
||||
/>
|
||||
)}
|
||||
<div className={`
|
||||
flex flex-col gap-2 overflow-y-auto no-scrollbar p-2
|
||||
`} onScroll={(ev) => onScroll(ev.target)} ref={scrollRef}>
|
||||
{reorderedActions.map((contextActionIt: ContextAction) => {
|
||||
return (
|
||||
<div className="flex flex-col gap-1">
|
||||
<OlStateButton
|
||||
key={contextActionIt.getId()}
|
||||
checked={contextActionIt === contextAction}
|
||||
icon={contextActionIt.getIcon()}
|
||||
tooltip={contextActionIt.getLabel()}
|
||||
buttonColor={CONTEXT_ACTION_COLORS[contextActionIt.getOptions().type ?? 0]}
|
||||
onClick={() => {
|
||||
if (contextActionIt.getOptions().executeImmediately) {
|
||||
contextActionIt.executeCallback(null, null);
|
||||
} else {
|
||||
contextActionIt !== contextAction
|
||||
? getApp().getMap().setContextAction(contextActionIt)
|
||||
: getApp().getMap().setContextAction(null);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{!scrolledBottom && (
|
||||
<FaChevronDown
|
||||
className={`
|
||||
absolute bottom-0 h-6 w-full rounded-lg px-2 py-3.5
|
||||
text-gray-200
|
||||
dark:bg-olympus-900
|
||||
`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{contextAction && (
|
||||
<div
|
||||
className={`
|
||||
absolute left-[50%] top-32 flex min-w-[300px]
|
||||
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
|
||||
dark:bg-olympus-800
|
||||
@ -135,7 +134,6 @@ export function UnitControlBar(props: {}) {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{*/}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
|
||||
@ -21,7 +21,6 @@ import { OlAccordion } from "../components/olaccordion";
|
||||
export function UnitSpawnMenu(props: {
|
||||
starredSpawns: { [key: string]: SpawnRequestTable };
|
||||
blueprint: UnitBlueprint;
|
||||
spawnAtLocation: boolean;
|
||||
airbase?: Airbase | null;
|
||||
coalition?: Coalition;
|
||||
}) {
|
||||
@ -49,7 +48,7 @@ export function UnitSpawnMenu(props: {
|
||||
|
||||
/* When the menu is opened show the unit preview on the map as a cursor */
|
||||
useEffect(() => {
|
||||
if (props.spawnAtLocation && spawnRequestTable) {
|
||||
if (!props.airbase && spawnRequestTable) {
|
||||
/* Refresh the unique key identified */
|
||||
const newKey = hash(JSON.stringify(spawnRequestTable));
|
||||
setKey(newKey);
|
||||
@ -67,7 +66,7 @@ export function UnitSpawnMenu(props: {
|
||||
|
||||
/* Callback and effect to update the quick access name in the input field */
|
||||
const updateQuickAccessName = useCallback(() => {
|
||||
if (props.spawnAtLocation) {
|
||||
if (!props.airbase) {
|
||||
/* If the spawn is starred, set the quick access name */
|
||||
if (key in props.starredSpawns && props.starredSpawns[key].quickAccessName) setQuickAccessName(props.starredSpawns[key].quickAccessName);
|
||||
else setQuickAccessName("No name");
|
||||
@ -408,7 +407,7 @@ export function UnitSpawnMenu(props: {
|
||||
</OlAccordion>
|
||||
</div>
|
||||
)}
|
||||
{!props.spawnAtLocation && (
|
||||
{props.airbase && (
|
||||
<button
|
||||
type="button"
|
||||
className={`
|
||||
@ -422,7 +421,7 @@ export function UnitSpawnMenu(props: {
|
||||
if (spawnRequestTable)
|
||||
getApp()
|
||||
.getUnitsManager()
|
||||
.spawnUnits(spawnRequestTable.category, [spawnRequestTable.unit], spawnRequestTable.coalition, false, props.airbase?.getName());
|
||||
.spawnUnits(spawnRequestTable.category, Array(spawnRequestTable.amount).fill(spawnRequestTable.unit), spawnRequestTable.coalition, false, props.airbase?.getName());
|
||||
}}
|
||||
>
|
||||
Spawn
|
||||
|
||||
@ -28,7 +28,7 @@ import { AppStateChangedEvent, MapOptionsChangedEvent } from "../events";
|
||||
import { GameMasterMenu } from "./panels/gamemastermenu";
|
||||
import { InfoBar } from "./panels/infobar";
|
||||
import { HotGroupBar } from "./panels/hotgroupsbar";
|
||||
import { StarredSpawnContextMenu } from "./contextmenus/starredspawncontextmenu";
|
||||
import { SpawnContextMenu } from "./contextmenus/SpawnContextmenu";
|
||||
import { CoordinatesPanel } from "./panels/coordinatespanel";
|
||||
|
||||
export type OlympusUIState = {
|
||||
@ -112,7 +112,7 @@ export function UI() {
|
||||
<HotGroupBar />
|
||||
|
||||
<MapContextMenu />
|
||||
<StarredSpawnContextMenu />
|
||||
<SpawnContextMenu />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -6,7 +6,10 @@ import { ContextActionTarget, ContextActionType } from "../constants/constants";
|
||||
export interface ContextActionOptions {
|
||||
executeImmediately?: boolean;
|
||||
type: ContextActionType;
|
||||
hotkey?: string;
|
||||
code: string | null;
|
||||
shiftKey?: boolean;
|
||||
altKey?: boolean;
|
||||
ctrlKey?: boolean;
|
||||
}
|
||||
|
||||
export type ContextActionCallback = (units: Unit[], targetUnit: Unit | null, targetPosition: LatLng | null, originalEvent?: MouseEvent) => void;
|
||||
|
||||
@ -159,7 +159,6 @@ export abstract class Unit extends CustomMarker {
|
||||
/* Inputs timers */
|
||||
#mouseCooldownTimer: number = 0;
|
||||
#shortPressTimer: number = 0;
|
||||
#longPressTimer: number = 0;
|
||||
#isMouseOnCooldown: boolean = false;
|
||||
#isMouseDown: boolean = false;
|
||||
|
||||
@ -345,10 +344,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("mouseover", () => {
|
||||
if (this.belongsToCommandedCoalition() && (getApp().getState() === OlympusState.IDLE || getApp().getState() === OlympusState.UNIT_CONTROL))
|
||||
this.setHighlighted(true);
|
||||
if (this.belongsToCommandedCoalition()) this.setHighlighted(true);
|
||||
});
|
||||
this.on("mouseout", () => this.setHighlighted(false));
|
||||
getApp()
|
||||
@ -1281,96 +1281,83 @@ export abstract class Unit extends CustomMarker {
|
||||
}
|
||||
|
||||
/***********************************************/
|
||||
#onMouseUp(e: any) {
|
||||
if (getApp().getState() === OlympusState.IDLE || getApp().getState() === OlympusState.UNIT_CONTROL) {
|
||||
this.#isMouseDown = false;
|
||||
|
||||
if (getApp().getMap().isSelecting()) return;
|
||||
|
||||
DomEvent.stop(e);
|
||||
DomEvent.preventDefault(e);
|
||||
e.originalEvent.stopImmediatePropagation();
|
||||
|
||||
e.originalEvent.stopPropagation();
|
||||
|
||||
window.clearTimeout(this.#longPressTimer);
|
||||
|
||||
this.#isMouseOnCooldown = true;
|
||||
this.#mouseCooldownTimer = window.setTimeout(() => {
|
||||
this.#isMouseOnCooldown = false;
|
||||
}, 200);
|
||||
}
|
||||
}
|
||||
|
||||
#onMouseDown(e: any) {
|
||||
if (getApp().getState() === OlympusState.IDLE || getApp().getState() === OlympusState.UNIT_CONTROL) {
|
||||
this.#isMouseDown = true;
|
||||
if (e.originalEvent.button === 2) return;
|
||||
|
||||
DomEvent.stop(e);
|
||||
DomEvent.preventDefault(e);
|
||||
e.originalEvent.stopImmediatePropagation();
|
||||
this.#isMouseDown = true;
|
||||
|
||||
if (this.#isMouseOnCooldown) {
|
||||
return;
|
||||
}
|
||||
DomEvent.stop(e);
|
||||
DomEvent.preventDefault(e);
|
||||
e.originalEvent.stopImmediatePropagation();
|
||||
|
||||
this.#shortPressTimer = window.setTimeout(() => {
|
||||
/* If the mouse is no longer being pressed, execute the short press action */
|
||||
if (!this.#isMouseDown) this.#onShortPress(e);
|
||||
}, 200);
|
||||
if (this.#isMouseOnCooldown) return;
|
||||
|
||||
this.#longPressTimer = window.setTimeout(() => {
|
||||
/* If the mouse is still being pressed, execute the long press action */
|
||||
if (this.#isMouseDown) this.#onLongPress(e);
|
||||
}, 350);
|
||||
}
|
||||
this.#shortPressTimer = window.setTimeout(() => {
|
||||
/* If the mouse is no longer being pressed, execute the short press action */
|
||||
if (!this.#isMouseDown) this.#onLeftClick(e);
|
||||
}, 200);
|
||||
}
|
||||
|
||||
#onShortPress(e: LeafletMouseEvent) {
|
||||
console.log(`Short press on ${this.getUnitName()}`);
|
||||
#onMouseUp(e: any) {
|
||||
if (e.originalEvent.button === 2) return;
|
||||
|
||||
if (getApp().getState() === OlympusState.IDLE) {
|
||||
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() && getApp().getMap().getContextAction()?.getTarget() === ContextActionTarget.UNIT) {
|
||||
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());
|
||||
}
|
||||
} else if (getApp().getState() === OlympusState.JTAC && getApp().getSubState() === JTACSubState.SELECT_TARGET) {
|
||||
// TODO document.dispatchEvent(new CustomEvent("selectJTACTarget", { detail: { unit: this } }));
|
||||
getApp().setState(OlympusState.IDLE);
|
||||
}
|
||||
}
|
||||
|
||||
#onLongPress(e: any) {
|
||||
console.log(`Long press on ${this.getUnitName()}`);
|
||||
#onRightClick(e: any) {
|
||||
console.log(`Right click on ${this.getUnitName()}`);
|
||||
|
||||
if (getApp().getState() === OlympusState.UNIT_CONTROL && !getApp().getMap().getContextAction()) {
|
||||
DomEvent.stop(e);
|
||||
DomEvent.preventDefault(e);
|
||||
e.originalEvent.stopImmediatePropagation();
|
||||
|
||||
if (getApp().getState() === OlympusState.UNIT_CONTROL && e.originalEvent.button === 2 && !getApp().getMap().getContextAction()) {
|
||||
getApp().setState(OlympusState.UNIT_CONTROL, UnitControlSubState.UNIT_CONTEXT_MENU);
|
||||
UnitContextMenuRequestEvent.dispatch(this);
|
||||
}
|
||||
}
|
||||
|
||||
#onDoubleClick(e: any) {
|
||||
DomEvent.stop(e);
|
||||
DomEvent.preventDefault(e);
|
||||
|
||||
console.log(`Double click on ${this.getUnitName()}`);
|
||||
|
||||
window.clearTimeout(this.#shortPressTimer);
|
||||
|
||||
/* Select all matching units in the viewport */
|
||||
if (getApp().getState() === OlympusState.IDLE || getApp().getState() === OlympusState.UNIT_CONTROL) {
|
||||
DomEvent.stop(e);
|
||||
DomEvent.preventDefault(e);
|
||||
|
||||
console.log(`Double click on ${this.getUnitName()}`);
|
||||
|
||||
window.clearTimeout(this.#shortPressTimer);
|
||||
window.clearTimeout(this.#longPressTimer);
|
||||
|
||||
/* 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -68,18 +68,6 @@ export class UnitsManager {
|
||||
|
||||
getApp()
|
||||
.getShortcutManager()
|
||||
.addShortcut("deselectAll", {
|
||||
label: "Deselect all units",
|
||||
keyUpCallback: (ev: KeyboardEvent) => {
|
||||
getApp().getUnitsManager().deselectAllUnits();
|
||||
},
|
||||
code: "Escape",
|
||||
})
|
||||
.addShortcut("delete", {
|
||||
label: "Delete selected units",
|
||||
keyUpCallback: () => this.delete(),
|
||||
code: "Delete",
|
||||
})
|
||||
.addShortcut("selectAll", {
|
||||
label: "Select all units",
|
||||
keyUpCallback: () => {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user