mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
Merge pull request #275 from Pax1601/271-add-unit-hotgroups
271 add unit hotgroups
This commit is contained in:
commit
16c37a3445
@ -856,4 +856,37 @@ body[data-hide-navyunit] #unit-visibility-control-navyunit {
|
||||
50% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
#hotgroup-panel {
|
||||
position: absolute;
|
||||
bottom: 40px;
|
||||
left: 50%;
|
||||
translate: -50%;
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
column-gap: 10px;
|
||||
}
|
||||
|
||||
#hotgroup-panel>div {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
background-color: var(--background-steel);
|
||||
border-radius: var(--border-radius-sm);
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: bold;
|
||||
border: 0px solid transparent;
|
||||
}
|
||||
|
||||
#hotgroup-panel>div:hover {
|
||||
cursor: pointer;
|
||||
border: 2px solid white;
|
||||
}
|
||||
|
||||
.hotgroup-selector>.unit-hotgroup {
|
||||
display: flex;
|
||||
translate: 0% -300%;
|
||||
}
|
||||
@ -3,7 +3,7 @@
|
||||
--unit-centre-x: calc(var(--unit-width) / 2);
|
||||
--unit-centre-y: calc(var(--unit-height) / 2);
|
||||
|
||||
--unit-hotgroup-height: 10px;
|
||||
--unit-hotgroup-height: 15px;
|
||||
--unit-hotgroup-width: var(--unit-hotgroup-height);
|
||||
|
||||
|
||||
@ -23,7 +23,7 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
[data-object|="unit"] .unit-selected-spotlight {
|
||||
.unit-selected-spotlight {
|
||||
background-color: var(--unit-spotlight-fill);
|
||||
border-radius: 50%;
|
||||
display: none;
|
||||
@ -32,8 +32,7 @@
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
|
||||
[data-object|="unit"] .unit-vvi {
|
||||
.unit-vvi {
|
||||
align-self: center;
|
||||
background: var(--secondary-gunmetal-grey);
|
||||
display: flex;
|
||||
@ -46,30 +45,7 @@
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
|
||||
[data-object|="unit"] .unit-hotgroup {
|
||||
align-content: center;
|
||||
background-color: black;
|
||||
border-radius: var(--border-radius-xs);
|
||||
display: none;
|
||||
height: var(--unit-hotgroup-height);
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
transform: rotate(-45deg);
|
||||
translate: 0 -275%;
|
||||
width: var(--unit-hotgroup-width);
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
[data-object|="unit"] .unit-hotgroup-id {
|
||||
background-color: transparent;
|
||||
color: white;
|
||||
font-size: 9px;
|
||||
font-weight: bolder;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
[data-object|="unit"] .unit-marker-border {
|
||||
.unit-marker-border {
|
||||
border-radius: var(--border-radius-sm);
|
||||
display: none;
|
||||
height: calc(var(--unit-aircraft-height) + (var(--unit-label-border-width) * 2));
|
||||
@ -78,6 +54,29 @@
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.unit-hotgroup {
|
||||
align-content: center;
|
||||
background-color: var(--background-steel);
|
||||
border-radius: var(--border-radius-xs);
|
||||
display: none;
|
||||
height: var(--unit-hotgroup-height);
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
transform: rotate(-45deg);
|
||||
translate: 0 -200%;
|
||||
width: var(--unit-hotgroup-width);
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.unit-hotgroup-id {
|
||||
background-color: transparent;
|
||||
color: white;
|
||||
font-size: 9px;
|
||||
font-weight: bolder;
|
||||
transform: rotate(45deg);
|
||||
translate: -1px 1px;
|
||||
}
|
||||
|
||||
|
||||
/******************************
|
||||
Marker
|
||||
@ -92,8 +91,6 @@
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Air */
|
||||
|
||||
[data-object|="unit-aircraft"] .unit-marker {
|
||||
@ -102,7 +99,7 @@
|
||||
width: var(--unit-aircraft-marker-width);
|
||||
}
|
||||
|
||||
[data-object|="unit-aircraft"]:hover .unit-marker {
|
||||
[data-object|="unit-aircraft"][data-is-highlighted] .unit-marker {
|
||||
background-image: var(--unit-aircraft-marker-neutral-hover-url);
|
||||
}
|
||||
|
||||
@ -110,12 +107,11 @@
|
||||
background-image: var(--unit-aircraft-marker-neutral-selected-url);
|
||||
}
|
||||
|
||||
|
||||
[data-object|="unit-aircraft"][data-coalition="blue"] .unit-marker {
|
||||
background-image: var(--unit-aircraft-marker-blue-url);
|
||||
}
|
||||
|
||||
[data-object|="unit-aircraft"][data-coalition="blue"]:hover .unit-marker {
|
||||
[data-object|="unit-aircraft"][data-coalition="blue"][data-is-highlighted] .unit-marker {
|
||||
background-image: var(--unit-aircraft-marker-blue-hover-url);
|
||||
}
|
||||
|
||||
@ -128,7 +124,7 @@
|
||||
background-image: var(--unit-aircraft-marker-red-url);
|
||||
}
|
||||
|
||||
[data-object|="unit-aircraft"][data-coalition="red"]:hover .unit-marker {
|
||||
[data-object|="unit-aircraft"][data-coalition="red"][data-is-highlighted] .unit-marker {
|
||||
background-image: var(--unit-aircraft-marker-red-hover-url);
|
||||
}
|
||||
|
||||
@ -136,9 +132,6 @@
|
||||
background-image: var(--unit-aircraft-marker-red-selected-url);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/* Ground vehicles (not SAMs) */
|
||||
|
||||
[data-object|="unit-groundunit"] .unit-marker {
|
||||
@ -147,7 +140,7 @@
|
||||
width: var(--unit-groundunit-marker-width);
|
||||
}
|
||||
|
||||
[data-object|="unit-groundunit"]:hover .unit-marker {
|
||||
[data-object|="unit-groundunit"][data-is-highlighted] .unit-marker {
|
||||
background-image: var(--unit-groundunit-marker-neutral-hover-url);
|
||||
}
|
||||
|
||||
@ -160,7 +153,7 @@
|
||||
background-image: var(--unit-groundunit-marker-blue-url);
|
||||
}
|
||||
|
||||
[data-object|="unit-groundunit"][data-coalition="blue"]:hover .unit-marker {
|
||||
[data-object|="unit-groundunit"][data-coalition="blue"][data-is-highlighted] .unit-marker {
|
||||
background-image: var(--unit-groundunit-marker-blue-hover-url);
|
||||
}
|
||||
|
||||
@ -173,7 +166,7 @@
|
||||
background-image: var(--unit-groundunit-marker-red-url);
|
||||
}
|
||||
|
||||
[data-object|="unit-groundunit"][data-coalition="red"]:hover .unit-marker {
|
||||
[data-object|="unit-groundunit"][data-coalition="red"][data-is-highlighted] .unit-marker {
|
||||
background-image: var(--unit-groundunit-marker-red-hover-url);
|
||||
}
|
||||
|
||||
@ -195,7 +188,7 @@
|
||||
}
|
||||
|
||||
|
||||
[data-object|="unit-sam"]:hover .unit-marker {
|
||||
[data-object|="unit-sam"][data-is-highlighted] .unit-marker {
|
||||
background-image: var(--unit-sam-marker-neutral-hover-url);
|
||||
}
|
||||
|
||||
@ -208,7 +201,7 @@
|
||||
background-image: var(--unit-sam-marker-blue-url);
|
||||
}
|
||||
|
||||
[data-object|="unit-sam"][data-coalition="blue"]:hover .unit-marker {
|
||||
[data-object|="unit-sam"][data-coalition="blue"][data-is-highlighted] .unit-marker {
|
||||
background-image: var(--unit-sam-marker-blue-hover-url);
|
||||
}
|
||||
|
||||
@ -221,7 +214,7 @@
|
||||
background-image: var(--unit-sam-marker-red-url);
|
||||
}
|
||||
|
||||
[data-object|="unit-sam"][data-coalition="red"]:hover .unit-marker {
|
||||
[data-object|="unit-sam"][data-coalition="red"][data-is-highlighted] .unit-marker {
|
||||
background-image: var(--unit-sam-marker-red-hover-url);
|
||||
}
|
||||
|
||||
@ -243,7 +236,7 @@
|
||||
}
|
||||
|
||||
|
||||
[data-object|="unit-navyunit"]:hover .unit-marker {
|
||||
[data-object|="unit-navyunit"][data-is-highlighted] .unit-marker {
|
||||
background-image: var(--unit-navyunit-marker-neutral-hover-url);
|
||||
}
|
||||
|
||||
@ -256,7 +249,7 @@
|
||||
background-image: var(--unit-navyunit-marker-blue-url);
|
||||
}
|
||||
|
||||
[data-object|="unit-navyunit"][data-coalition="blue"]:hover .unit-marker {
|
||||
[data-object|="unit-navyunit"][data-coalition="blue"][data-is-highlighted] .unit-marker {
|
||||
background-image: var(--unit-navyunit-marker-blue-hover-url);
|
||||
}
|
||||
|
||||
@ -269,7 +262,7 @@
|
||||
background-image: var(--unit-navyunit-marker-red-url);
|
||||
}
|
||||
|
||||
[data-object|="unit-navyunit"][data-coalition="red"]:hover .unit-marker {
|
||||
[data-object|="unit-navyunit"][data-coalition="red"][data-is-highlighted] .unit-marker {
|
||||
background-image: var(--unit-navyunit-marker-red-hover-url);
|
||||
}
|
||||
|
||||
@ -279,7 +272,6 @@
|
||||
|
||||
|
||||
/* Building */
|
||||
|
||||
[data-object|="unit-building"] .unit-marker {
|
||||
background-image: var(--unit-building-marker-neutral-url);
|
||||
height: var(--unit-building-marker-height);
|
||||
@ -296,8 +288,6 @@
|
||||
background-image: var(--unit-building-marker-red-url);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Weapons */
|
||||
|
||||
[data-object|="unit-missile"],
|
||||
@ -439,8 +429,6 @@
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
|
||||
|
||||
[data-object|="unit"]:hover .unit-ammo,
|
||||
[data-object|="unit"]:hover .unit-fuel {
|
||||
display: flex;
|
||||
@ -460,9 +448,6 @@
|
||||
background-color: var(--secondary-gunmetal-grey);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
[data-object|="unit"][data-coalition="blue"][data-is-selected] .unit-short-label {
|
||||
color: var(--secondary-blue-text);
|
||||
}
|
||||
@ -496,8 +481,6 @@
|
||||
background-color: var(--secondary-red-outline);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@keyframes pulse {
|
||||
50% {
|
||||
opacity: 0;
|
||||
@ -508,7 +491,6 @@
|
||||
animation: pulse 1.5s linear infinite;
|
||||
}
|
||||
|
||||
|
||||
[data-object|="unit"] .unit-state {
|
||||
background-repeat: no-repeat;
|
||||
position: absolute;
|
||||
@ -569,20 +551,19 @@
|
||||
}
|
||||
|
||||
|
||||
[data-object|="unit-aircraft"][ data-is-dead] .unit-selected-spotlight,
|
||||
[data-object|="unit-aircraft"][ data-is-dead] .unit-short-label,
|
||||
[data-object|="unit-aircraft"][ data-is-dead] .unit-vvi,
|
||||
[data-object|="unit-aircraft"][ data-is-dead] .unit-hotgroup,
|
||||
[data-object|="unit-aircraft"][ data-is-dead] .unit-hotgroup-id,
|
||||
[data-object|="unit-aircraft"][ data-is-dead] .unit-state,
|
||||
[data-object|="unit-aircraft"][ data-is-dead] .unit-fuel,
|
||||
[data-object|="unit-aircraft"][ data-is-dead] .unit-ammo,
|
||||
[data-object|="unit-aircraft"][ data-is-dead]:hover .unit-fuel,
|
||||
[data-object|="unit-aircraft"][ data-is-dead]:hover .unit-ammo {
|
||||
[data-object|="unit-aircraft"][data-is-dead] .unit-selected-spotlight,
|
||||
[data-object|="unit-aircraft"][data-is-dead] .unit-short-label,
|
||||
[data-object|="unit-aircraft"][data-is-dead] .unit-vvi,
|
||||
[data-object|="unit-aircraft"][data-is-dead] .unit-hotgroup,
|
||||
[data-object|="unit-aircraft"][data-is-dead] .unit-hotgroup-id,
|
||||
[data-object|="unit-aircraft"][data-is-dead] .unit-state,
|
||||
[data-object|="unit-aircraft"][data-is-dead] .unit-fuel,
|
||||
[data-object|="unit-aircraft"][data-is-dead] .unit-ammo,
|
||||
[data-object|="unit-aircraft"][data-is-dead]:hover .unit-fuel,
|
||||
[data-object|="unit-aircraft"][data-is-dead]:hover .unit-ammo {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
|
||||
[data-object|="unit-aircraft"][ data-is-dead] .unit-summary>* {
|
||||
display: none;
|
||||
}
|
||||
|
||||
3
client/src/@types/dom.d.ts
vendored
3
client/src/@types/dom.d.ts
vendored
@ -5,7 +5,8 @@ interface CustomEventMap {
|
||||
"unitsDeselection": CustomEvent<Unit[]>,
|
||||
"clearSelection": CustomEvent<>,
|
||||
"unitCreation": CustomEvent<Unit>,
|
||||
"unitDeletion": CustomEvent<Unit>,
|
||||
"unitDeletion": CustomEvent<Unit>,
|
||||
"unitDeath": CustomEvent<Unit>,
|
||||
"unitUpdated": CustomEvent<Unit>,
|
||||
"unitMoveCommand": CustomEvent<Unit>,
|
||||
"unitAttackCommand": CustomEvent<Unit>,
|
||||
|
||||
@ -9,11 +9,12 @@ import { AIC } from "./aic/aic";
|
||||
import { ATC } from "./atc/atc";
|
||||
import { FeatureSwitches } from "./featureswitches";
|
||||
import { LogPanel } from "./panels/logpanel";
|
||||
import { getAirbases, getBullseye, getConfig, getFreezed, getMission, getUnits, setAddress, setCredentials, setFreezed, startUpdate, toggleDemoEnabled } from "./server/server";
|
||||
import { getConfig, getPaused, setAddress, setCredentials, setPaused, startUpdate, toggleDemoEnabled } from "./server/server";
|
||||
import { UnitDataTable } from "./units/unitdatatable";
|
||||
import { keyEventWasInInput } from "./other/utils";
|
||||
import { Popup } from "./popups/popup";
|
||||
import { Dropdown } from "./controls/dropdown";
|
||||
import { HotgroupPanel } from "./panels/hotgrouppanel";
|
||||
|
||||
var map: Map;
|
||||
|
||||
@ -28,6 +29,7 @@ var connectionStatusPanel: ConnectionStatusPanel;
|
||||
var unitControlPanel: UnitControlPanel;
|
||||
var mouseInfoPanel: MouseInfoPanel;
|
||||
var logPanel: LogPanel;
|
||||
var hotgroupPanel: HotgroupPanel;
|
||||
|
||||
var infoPopup: Popup;
|
||||
|
||||
@ -50,6 +52,7 @@ function setup() {
|
||||
unitControlPanel = new UnitControlPanel("unit-control-panel");
|
||||
connectionStatusPanel = new ConnectionStatusPanel("connection-status-panel");
|
||||
mouseInfoPanel = new MouseInfoPanel("mouse-info-panel");
|
||||
hotgroupPanel = new HotgroupPanel("hotgroup-panel");
|
||||
//logPanel = new LogPanel("log-panel");
|
||||
|
||||
/* Popups */
|
||||
@ -135,7 +138,7 @@ function setupEvents() {
|
||||
unitDataTable.toggle();
|
||||
break
|
||||
case "Space":
|
||||
setFreezed(!getFreezed());
|
||||
setPaused(!getPaused());
|
||||
break;
|
||||
case "KeyW":
|
||||
case "KeyA":
|
||||
@ -147,6 +150,23 @@ function setupEvents() {
|
||||
case "ArrowDown":
|
||||
getMap().handleMapPanning(ev);
|
||||
break;
|
||||
case "Digit1":
|
||||
case "Digit2":
|
||||
case "Digit3":
|
||||
case "Digit4":
|
||||
case "Digit5":
|
||||
case "Digit6":
|
||||
case "Digit7":
|
||||
case "Digit8":
|
||||
case "Digit9":
|
||||
// Using the substring because the key will be invalid when pressing the Shift key
|
||||
if (ev.ctrlKey && ev.shiftKey)
|
||||
getUnitsManager().selectedUnitsAddToHotgroup(parseInt(ev.code.substring(5)));
|
||||
else if (ev.ctrlKey && !ev.shiftKey)
|
||||
getUnitsManager().selectedUnitsSetHotgroup(parseInt(ev.code.substring(5)));
|
||||
else
|
||||
getUnitsManager().selectUnitsByHotgroup(parseInt(ev.code.substring(5)));
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
@ -232,6 +252,10 @@ export function getConnectionStatusPanel() {
|
||||
return connectionStatusPanel;
|
||||
}
|
||||
|
||||
export function getHotgroupPanel() {
|
||||
return hotgroupPanel;
|
||||
}
|
||||
|
||||
export function setActiveCoalition(newActiveCoalition: string) {
|
||||
activeCoalition = newActiveCoalition;
|
||||
document.querySelectorAll('[data-active-coalition]').forEach((element: any) => { element.setAttribute("data-active-coalition", activeCoalition) });
|
||||
|
||||
48
client/src/panels/hotgrouppanel.ts
Normal file
48
client/src/panels/hotgrouppanel.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import { getUnitsManager } from "..";
|
||||
import { Unit } from "../units/unit";
|
||||
import { Panel } from "./panel";
|
||||
|
||||
export class HotgroupPanel extends Panel {
|
||||
constructor(ID: string) {
|
||||
super(ID);
|
||||
document.addEventListener("unitDeath", () => this.refreshHotgroups());
|
||||
}
|
||||
|
||||
refreshHotgroups() {
|
||||
for (let hotgroup = 1; hotgroup <= 9; hotgroup++){
|
||||
this.removeHotgroup(hotgroup);
|
||||
if (getUnitsManager().getUnitsByHotgroup(hotgroup).length > 0)
|
||||
this.addHotgroup(hotgroup);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
addHotgroup(hotgroup: number) {
|
||||
const hotgroupHtml = `<div class="unit-hotgroup">
|
||||
<div class="unit-hotgroup-id">${hotgroup}</div>
|
||||
</div>
|
||||
x${getUnitsManager().getUnitsByHotgroup(hotgroup).length}`
|
||||
var el = document.createElement("div");
|
||||
el.classList.add("hotgroup-selector");
|
||||
el.innerHTML = hotgroupHtml;
|
||||
el.toggleAttribute(`data-hotgroup-${hotgroup}`, true)
|
||||
this.getElement().appendChild(el);
|
||||
|
||||
el.addEventListener("click", () => {
|
||||
getUnitsManager().selectUnitsByHotgroup(hotgroup);
|
||||
});
|
||||
|
||||
el.addEventListener("mouseover", () => {
|
||||
getUnitsManager().getUnitsByHotgroup(hotgroup).forEach((unit: Unit) => unit.setHighlighted(true));
|
||||
});
|
||||
|
||||
el.addEventListener("mouseout", () => {
|
||||
getUnitsManager().getUnitsByHotgroup(hotgroup).forEach((unit: Unit) => unit.setHighlighted(false));
|
||||
});
|
||||
}
|
||||
|
||||
removeHotgroup(hotgroup: number) {
|
||||
const el = this.getElement().querySelector(`[data-hotgroup-${hotgroup}]`) as HTMLElement;
|
||||
if (el) el.remove();
|
||||
}
|
||||
}
|
||||
@ -3,7 +3,7 @@ import { getConnectionStatusPanel, getInfoPopup, getMissionData, getUnitDataTabl
|
||||
import { SpawnOptions } from '../controls/mapcontextmenu';
|
||||
|
||||
var connected: boolean = false;
|
||||
var freezed: boolean = false;
|
||||
var paused: boolean = false;
|
||||
|
||||
var REST_ADDRESS = "http://localhost:30000/olympus";
|
||||
var DEMO_ADDRESS = window.location.href + "demo";
|
||||
@ -246,7 +246,7 @@ export function startUpdate() {
|
||||
export function requestUpdate() {
|
||||
/* Main update rate = 250ms is minimum time, equal to server update time. */
|
||||
getUnits((data: UnitsData) => {
|
||||
if (!getFreezed()) {
|
||||
if (!getPaused()) {
|
||||
getUnitsManager()?.update(data);
|
||||
checkSessionHash(data.sessionHash);
|
||||
}
|
||||
@ -259,7 +259,7 @@ export function requestUpdate() {
|
||||
export function requestRefresh() {
|
||||
/* Main refresh rate = 5000ms. */
|
||||
getUnits((data: UnitsData) => {
|
||||
if (!getFreezed()) {
|
||||
if (!getPaused()) {
|
||||
getUnitsManager()?.update(data);
|
||||
getAirbases((data: AirbasesData) => getMissionData()?.update(data));
|
||||
getBullseye((data: BullseyesData) => getMissionData()?.update(data));
|
||||
@ -300,11 +300,11 @@ export function getConnected() {
|
||||
return connected;
|
||||
}
|
||||
|
||||
export function setFreezed(newFreezed: boolean) {
|
||||
freezed = newFreezed;
|
||||
freezed ? getInfoPopup().setText("Freezed") : getInfoPopup().setText("Unfreezed");
|
||||
export function setPaused(newPaused: boolean) {
|
||||
paused = newPaused;
|
||||
paused ? getInfoPopup().setText("View paused") : getInfoPopup().setText("View unpaused");
|
||||
}
|
||||
|
||||
export function getFreezed() {
|
||||
return freezed;
|
||||
export function getPaused() {
|
||||
return paused;
|
||||
}
|
||||
@ -65,8 +65,8 @@ export class Unit extends Marker {
|
||||
|
||||
#selectable: boolean;
|
||||
#selected: boolean = false;
|
||||
#hovered: boolean = false;
|
||||
#hidden: boolean = false;
|
||||
#highlighted: boolean = false;
|
||||
|
||||
#preventClick: boolean = false;
|
||||
|
||||
@ -77,6 +77,8 @@ export class Unit extends Marker {
|
||||
|
||||
#timer: number = 0;
|
||||
|
||||
#hotgroup: number | null = null;
|
||||
|
||||
static getConstructor(type: string) {
|
||||
if (type === "GroundUnit") return GroundUnit;
|
||||
if (type === "Aircraft") return Aircraft;
|
||||
@ -92,24 +94,24 @@ export class Unit extends Marker {
|
||||
this.ID = ID;
|
||||
|
||||
this.#selectable = true;
|
||||
|
||||
|
||||
this.on('click', (e) => this.#onClick(e));
|
||||
this.on('dblclick', (e) => this.#onDoubleClick(e));
|
||||
this.on('contextmenu', (e) => this.#onContextMenu(e));
|
||||
this.on('mouseover', () => { this.#hovered = true;})
|
||||
this.on('mouseout', () => { this.#hovered = false;})
|
||||
|
||||
this.on('mouseover', () => { this.setHighlighted(true); })
|
||||
this.on('mouseout', () => { this.setHighlighted(false); })
|
||||
|
||||
this.#pathPolyline = new Polyline([], { color: '#2d3e50', weight: 3, opacity: 0.5, smoothFactor: 1 });
|
||||
this.#pathPolyline.addTo(getMap());
|
||||
this.#targetsPolylines = [];
|
||||
|
||||
/* Deselect units if they are hidden */
|
||||
document.addEventListener("toggleCoalitionVisibility", (ev: CustomEventInit) => {
|
||||
window.setTimeout(() => {this.setSelected(this.getSelected() && !this.getHidden())}, 300);
|
||||
window.setTimeout(() => { this.setSelected(this.getSelected() && !this.getHidden()) }, 300);
|
||||
});
|
||||
|
||||
|
||||
document.addEventListener("toggleUnitVisibility", (ev: CustomEventInit) => {
|
||||
window.setTimeout(() => {this.setSelected(this.getSelected() && !this.getHidden())}, 300);
|
||||
window.setTimeout(() => { this.setSelected(this.getSelected() && !this.getHidden()) }, 300);
|
||||
});
|
||||
|
||||
/* Set the unit data */
|
||||
@ -126,15 +128,14 @@ export class Unit extends Marker {
|
||||
}
|
||||
|
||||
getMarkerHTML() {
|
||||
return `<div class="unit" data-object="unit-${this.getMarkerCategory()}" data-coalition="${this.getMissionData().coalition}">
|
||||
return `<div class="unit" data-object="unit-${this.getMarkerCategory()}" data-coalition="${this.getMissionData().coalition}">
|
||||
<div class="unit-selected-spotlight"></div>
|
||||
<div class="unit-marker"></div>
|
||||
<div class="unit-short-label"></div>
|
||||
</div>`
|
||||
}
|
||||
|
||||
getMarkerCategory()
|
||||
{
|
||||
getMarkerCategory() {
|
||||
// Overloaded by child classes
|
||||
return "";
|
||||
}
|
||||
@ -144,58 +145,56 @@ export class Unit extends Marker {
|
||||
setData(data: UpdateData) {
|
||||
/* Check if data has changed comparing new values to old values */
|
||||
const positionChanged = (data.flightData != undefined && data.flightData.latitude != undefined && data.flightData.longitude != undefined && (this.getFlightData().latitude != data.flightData.latitude || this.getFlightData().longitude != data.flightData.longitude));
|
||||
const headingChanged = (data.flightData != undefined && data.flightData.heading != undefined && this.getFlightData().heading != data.flightData.heading);
|
||||
const aliveChanged = (data.baseData != undefined && data.baseData.alive != undefined && this.getBaseData().alive != data.baseData.alive);
|
||||
var updateMarker = (positionChanged || headingChanged || aliveChanged || !getMap().hasLayer(this))
|
||||
|
||||
if (data.baseData != undefined)
|
||||
{
|
||||
const headingChanged = (data.flightData != undefined && data.flightData.heading != undefined && this.getFlightData().heading != data.flightData.heading);
|
||||
const aliveChanged = (data.baseData != undefined && data.baseData.alive != undefined && this.getBaseData().alive != data.baseData.alive);
|
||||
var updateMarker = (positionChanged || headingChanged || aliveChanged || !getMap().hasLayer(this));
|
||||
|
||||
if (data.baseData != undefined) {
|
||||
for (let key in this.#data.baseData)
|
||||
if (key in data.baseData)
|
||||
//@ts-ignore
|
||||
this.#data.baseData[key] = data.baseData[key];
|
||||
}
|
||||
|
||||
if (data.flightData != undefined)
|
||||
{
|
||||
if (data.flightData != undefined) {
|
||||
for (let key in this.#data.flightData)
|
||||
if (key in data.flightData)
|
||||
//@ts-ignore
|
||||
this.#data.flightData[key] = data.flightData[key];
|
||||
}
|
||||
|
||||
if (data.missionData != undefined)
|
||||
{
|
||||
if (data.missionData != undefined) {
|
||||
for (let key in this.#data.missionData)
|
||||
if (key in data.missionData)
|
||||
//@ts-ignore
|
||||
this.#data.missionData[key] = data.missionData[key];
|
||||
}
|
||||
|
||||
if (data.formationData != undefined)
|
||||
{
|
||||
if (data.formationData != undefined) {
|
||||
for (let key in this.#data.formationData)
|
||||
if (key in data.formationData)
|
||||
//@ts-ignore
|
||||
this.#data.formationData[key] = data.formationData[key];
|
||||
}
|
||||
|
||||
if (data.taskData != undefined)
|
||||
{
|
||||
if (data.taskData != undefined) {
|
||||
for (let key in this.#data.taskData)
|
||||
if (key in data.taskData)
|
||||
//@ts-ignore
|
||||
this.#data.taskData[key] = data.taskData[key];
|
||||
}
|
||||
|
||||
if (data.optionsData != undefined)
|
||||
{
|
||||
if (data.optionsData != undefined) {
|
||||
for (let key in this.#data.optionsData)
|
||||
if (key in data.optionsData)
|
||||
//@ts-ignore
|
||||
this.#data.optionsData[key] = data.optionsData[key];
|
||||
}
|
||||
|
||||
/* Fire an event when a unit dies */
|
||||
if (aliveChanged && this.getBaseData().alive == false)
|
||||
document.dispatchEvent(new CustomEvent("unitDeath", { detail: this }));
|
||||
|
||||
/* Dead units can't be selected */
|
||||
this.setSelected(this.getSelected() && this.getBaseData().alive && !this.getHidden())
|
||||
|
||||
@ -265,17 +264,32 @@ export class Unit extends Marker {
|
||||
return this.#selectable;
|
||||
}
|
||||
|
||||
/********************** Visibility *************************/
|
||||
updateVisibility()
|
||||
{
|
||||
this.setHidden( document.body.getAttribute(`data-hide-${this.getMissionData().coalition}`) != null ||
|
||||
document.body.getAttribute(`data-hide-${this.getMarkerCategory()}`) != null ||
|
||||
!this.getBaseData().alive)
|
||||
setHotgroup(hotgroup: number | null) {
|
||||
this.#hotgroup = hotgroup;
|
||||
}
|
||||
|
||||
setHidden(hidden: boolean)
|
||||
{
|
||||
this.#hidden = hidden;
|
||||
getHotgroup() {
|
||||
return this.#hotgroup;
|
||||
}
|
||||
|
||||
setHighlighted(highlighted: boolean) {
|
||||
this.getElement()?.querySelector(`[data-object|="unit"]`)?.toggleAttribute("data-is-highlighted", highlighted);
|
||||
this.#highlighted = highlighted;
|
||||
}
|
||||
|
||||
getHighlighted() {
|
||||
return this.#highlighted;
|
||||
}
|
||||
|
||||
/********************** Visibility *************************/
|
||||
updateVisibility() {
|
||||
this.setHidden(document.body.getAttribute(`data-hide-${this.getMissionData().coalition}`) != null ||
|
||||
document.body.getAttribute(`data-hide-${this.getMarkerCategory()}`) != null ||
|
||||
!this.getBaseData().alive)
|
||||
}
|
||||
|
||||
setHidden(hidden: boolean) {
|
||||
this.#hidden = hidden;
|
||||
|
||||
/* Add the marker if not present */
|
||||
if (!getMap().hasLayer(this) && !this.getHidden()) {
|
||||
@ -285,7 +299,7 @@ export class Unit extends Marker {
|
||||
/* Hide the marker if necessary*/
|
||||
if (getMap().hasLayer(this) && this.getHidden()) {
|
||||
getMap().removeLayer(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getHidden() {
|
||||
@ -319,15 +333,15 @@ export class Unit extends Marker {
|
||||
attackUnit(targetID: number) {
|
||||
/* Units can't attack themselves */
|
||||
if (!this.getMissionData().flags.Human)
|
||||
if (this.ID != targetID)
|
||||
if (this.ID != targetID)
|
||||
attackUnit(this.ID, targetID);
|
||||
}
|
||||
|
||||
followUnit(targetID: number, offset: {"x": number, "y": number, "z": number}) {
|
||||
followUnit(targetID: number, offset: { "x": number, "y": number, "z": number }) {
|
||||
/* Units can't follow themselves */
|
||||
if (!this.getMissionData().flags.Human)
|
||||
if (this.ID != targetID)
|
||||
followUnit(this.ID, targetID, offset);
|
||||
if (this.ID != targetID)
|
||||
followUnit(this.ID, targetID, offset);
|
||||
}
|
||||
|
||||
landAt(latlng: LatLng) {
|
||||
@ -414,26 +428,22 @@ export class Unit extends Marker {
|
||||
}
|
||||
|
||||
#onContextMenu(e: any) {
|
||||
var options: {[key: string]: string} = {};
|
||||
var options: { [key: string]: string } = {};
|
||||
|
||||
options["Center"] = `<div id="center-map">Center map</div>`;
|
||||
options["Center"] = `<div id="center-map">Center map</div>`;
|
||||
|
||||
if (getUnitsManager().getSelectedUnits().length > 0 && !(getUnitsManager().getSelectedUnits().length == 1 && (getUnitsManager().getSelectedUnits().includes(this))))
|
||||
{
|
||||
if (getUnitsManager().getSelectedUnits().length > 0 && !(getUnitsManager().getSelectedUnits().length == 1 && (getUnitsManager().getSelectedUnits().includes(this)))) {
|
||||
options['Attack'] = `<div id="attack">Attack</div>`;
|
||||
if (getUnitsManager().getSelectedUnitsType() === "Aircraft")
|
||||
options['Follow'] = `<div id="follow">Follow</div>`;
|
||||
}
|
||||
else if ((getUnitsManager().getSelectedUnits().length > 0 && (getUnitsManager().getSelectedUnits().includes(this))) || getUnitsManager().getSelectedUnits().length == 0)
|
||||
{
|
||||
if (this.getBaseData().category == "Aircraft")
|
||||
{
|
||||
else if ((getUnitsManager().getSelectedUnits().length > 0 && (getUnitsManager().getSelectedUnits().includes(this))) || getUnitsManager().getSelectedUnits().length == 0) {
|
||||
if (this.getBaseData().category == "Aircraft") {
|
||||
options["Refuel"] = `<div id="refuel">Refuel</div>`; // TODO Add some way of knowing which aircraft can AAR
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(options).length > 0)
|
||||
{
|
||||
if (Object.keys(options).length > 0) {
|
||||
getMap().showUnitContextMenu(e);
|
||||
getMap().getUnitContextMenu().setOptions(options, (option: string) => {
|
||||
getMap().hideUnitContextMenu();
|
||||
@ -454,7 +464,7 @@ export class Unit extends Marker {
|
||||
}
|
||||
|
||||
#showFollowOptions(e: any) {
|
||||
var options: {[key: string]: string} = {};
|
||||
var options: { [key: string]: string } = {};
|
||||
|
||||
options = {
|
||||
'Trail': `<div id="trail">Trail</div>`,
|
||||
@ -474,12 +484,10 @@ export class Unit extends Marker {
|
||||
getMap().showUnitContextMenu(e);
|
||||
}
|
||||
|
||||
#applyFollowOptions(action: string)
|
||||
{
|
||||
if (action === "Custom")
|
||||
{
|
||||
#applyFollowOptions(action: string) {
|
||||
if (action === "Custom") {
|
||||
document.getElementById("custom-formation-dialog")?.classList.remove("hide");
|
||||
getMap().getUnitContextMenu().setCustomFormationCallback((offset: {x: number, y: number, z: number}) => {
|
||||
getMap().getUnitContextMenu().setCustomFormationCallback((offset: { x: number, y: number, z: number }) => {
|
||||
getUnitsManager().selectedUnitsFollowUnit(this.ID, offset);
|
||||
})
|
||||
}
|
||||
@ -492,17 +500,15 @@ export class Unit extends Marker {
|
||||
this.updateVisibility();
|
||||
|
||||
/* Draw the minimap marker */
|
||||
if (this.getBaseData().alive )
|
||||
{
|
||||
if (this.#miniMapMarker == null)
|
||||
{
|
||||
this.#miniMapMarker = new CircleMarker(new LatLng(this.getFlightData().latitude, this.getFlightData().longitude), {radius: 0.5});
|
||||
if (this.getBaseData().alive) {
|
||||
if (this.#miniMapMarker == null) {
|
||||
this.#miniMapMarker = new CircleMarker(new LatLng(this.getFlightData().latitude, this.getFlightData().longitude), { radius: 0.5 });
|
||||
if (this.getMissionData().coalition == "neutral")
|
||||
this.#miniMapMarker.setStyle({color: "#CFD9E8"});
|
||||
this.#miniMapMarker.setStyle({ color: "#CFD9E8" });
|
||||
else if (this.getMissionData().coalition == "red")
|
||||
this.#miniMapMarker.setStyle({color: "#ff5858"});
|
||||
else
|
||||
this.#miniMapMarker.setStyle({color: "#247be2"});
|
||||
this.#miniMapMarker.setStyle({ color: "#ff5858" });
|
||||
else
|
||||
this.#miniMapMarker.setStyle({ color: "#247be2" });
|
||||
this.#miniMapMarker.addTo(getMap().getMiniMapLayerGroup());
|
||||
this.#miniMapMarker.bringToBack();
|
||||
}
|
||||
@ -544,19 +550,18 @@ export class Unit extends Marker {
|
||||
|
||||
/* Set altitude and speed */
|
||||
if (element.querySelector(".unit-altitude"))
|
||||
(<HTMLElement> element.querySelector(".unit-altitude")).innerText = "FL" + String(Math.floor(this.getFlightData().altitude / 0.3048 / 100));
|
||||
(<HTMLElement>element.querySelector(".unit-altitude")).innerText = "FL" + String(Math.floor(this.getFlightData().altitude / 0.3048 / 100));
|
||||
if (element.querySelector(".unit-speed"))
|
||||
(<HTMLElement> element.querySelector(".unit-speed")).innerHTML = String(Math.floor(this.getFlightData().speed * 1.94384 ) );
|
||||
|
||||
(<HTMLElement>element.querySelector(".unit-speed")).innerHTML = String(Math.floor(this.getFlightData().speed * 1.94384));
|
||||
|
||||
/* Rotate elements according to heading */
|
||||
element.querySelectorAll( "[data-rotate-to-heading]" ).forEach( el => {
|
||||
const headingDeg = rad2deg( this.getFlightData().heading );
|
||||
let currentStyle = el.getAttribute( "style" ) || "";
|
||||
el.setAttribute( "style", currentStyle + `transform:rotate(${headingDeg}deg);` );
|
||||
element.querySelectorAll("[data-rotate-to-heading]").forEach(el => {
|
||||
const headingDeg = rad2deg(this.getFlightData().heading);
|
||||
let currentStyle = el.getAttribute("style") || "";
|
||||
el.setAttribute("style", currentStyle + `transform:rotate(${headingDeg}deg);`);
|
||||
});
|
||||
|
||||
/* Turn on ordnance indicators */
|
||||
|
||||
var hasFox1 = element.querySelector(".unit")?.hasAttribute("data-has-fox-1");
|
||||
var hasFox2 = element.querySelector(".unit")?.hasAttribute("data-has-fox-2");
|
||||
var hasFox3 = element.querySelector(".unit")?.hasAttribute("data-has-fox-3");
|
||||
@ -568,30 +573,35 @@ export class Unit extends Marker {
|
||||
var newHasOtherAmmo = false;
|
||||
Object.values(this.getMissionData().ammo).forEach((ammo: any) => {
|
||||
if (ammo.desc.category == 1 && ammo.desc.missileCategory == 1) {
|
||||
if (ammo.desc.guidance == 4 || ammo.desc.guidance == 5) {
|
||||
if (ammo.desc.guidance == 4 || ammo.desc.guidance == 5)
|
||||
newHasFox1 = true;
|
||||
}
|
||||
else if (ammo.desc.guidance == 2) {
|
||||
else if (ammo.desc.guidance == 2)
|
||||
newHasFox2 = true;
|
||||
}
|
||||
else if (ammo.desc.guidance == 3) {
|
||||
else if (ammo.desc.guidance == 3)
|
||||
newHasFox3 = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
else
|
||||
newHasOtherAmmo = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (hasFox1 != newHasFox1) element.querySelector(".unit")?.toggleAttribute("data-has-fox-1", newHasFox1);
|
||||
if (hasFox2 != newHasFox2) element.querySelector(".unit")?.toggleAttribute("data-has-fox-2", newHasFox2);
|
||||
if (hasFox3 != newHasFox3) element.querySelector(".unit")?.toggleAttribute("data-has-fox-3", newHasFox3);
|
||||
if (hasOtherAmmo != newHasOtherAmmo) element.querySelector(".unit")?.toggleAttribute("data-has-other-ammo", newHasOtherAmmo);
|
||||
|
||||
/* Draw the hotgroup element */
|
||||
element.querySelector(".unit")?.toggleAttribute("data-is-in-hotgroup", this.#hotgroup != null);
|
||||
if (this.#hotgroup) {
|
||||
const hotgroupEl = element.querySelector(".unit-hotgroup-id") as HTMLElement;
|
||||
if (hotgroupEl)
|
||||
hotgroupEl.innerText = String(this.#hotgroup);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* Set vertical offset for altitude stacking */
|
||||
var pos = getMap().latLngToLayerPoint(this.getLatLng()).round();
|
||||
this.setZIndexOffset(1000 + Math.floor(this.getFlightData().altitude) - pos.y + (this.#hovered || this.#selected? 5000: 0));
|
||||
this.setZIndexOffset(1000 + Math.floor(this.getFlightData().altitude) - pos.y + (this.#highlighted || this.#selected ? 5000 : 0));
|
||||
}
|
||||
}
|
||||
|
||||
@ -675,8 +685,7 @@ export class Aircraft extends AirUnit {
|
||||
super(ID, data);
|
||||
}
|
||||
|
||||
getMarkerHTML()
|
||||
{
|
||||
getMarkerHTML() {
|
||||
return `<div class="unit" data-object="unit-aircraft" data-coalition="${this.getMissionData().coalition}">
|
||||
<div class="unit-selected-spotlight"></div>
|
||||
<div class="unit-marker-border"></div>
|
||||
@ -704,8 +713,7 @@ export class Aircraft extends AirUnit {
|
||||
</div>`
|
||||
}
|
||||
|
||||
getMarkerCategory()
|
||||
{
|
||||
getMarkerCategory() {
|
||||
return "aircraft";
|
||||
}
|
||||
}
|
||||
@ -715,8 +723,7 @@ export class Helicopter extends AirUnit {
|
||||
super(ID, data);
|
||||
}
|
||||
|
||||
getVisibilityCategory()
|
||||
{
|
||||
getVisibilityCategory() {
|
||||
return "helicopter";
|
||||
}
|
||||
}
|
||||
@ -728,15 +735,17 @@ export class GroundUnit extends Unit {
|
||||
|
||||
getMarkerHTML() {
|
||||
var role = groundUnitsDatabase.getByName(this.getBaseData().name)?.loadouts[0].roles[0];
|
||||
return `<div class="unit" data-object="unit-${this.getMarkerCategory()}" data-coalition="${this.getMissionData().coalition}">
|
||||
return `<div class="unit" data-object="unit-${this.getMarkerCategory()}" data-coalition="${this.getMissionData().coalition}">
|
||||
<div class="unit-selected-spotlight"></div>
|
||||
<div class="unit-marker"></div>
|
||||
<div class="unit-short-label">${role?.substring(0, 1)?.toUpperCase() || ""}</div>
|
||||
<div class="unit-hotgroup">
|
||||
<div class="unit-hotgroup-id"></div>
|
||||
</div>
|
||||
</div>`
|
||||
}
|
||||
|
||||
getMarkerCategory()
|
||||
{
|
||||
getMarkerCategory() {
|
||||
// TODO this is very messy
|
||||
var role = groundUnitsDatabase.getByName(this.getBaseData().name)?.loadouts[0].roles[0];
|
||||
var markerCategory = (role === "SAM") ? "sam" : "groundunit";
|
||||
@ -748,7 +757,7 @@ export class NavyUnit extends Unit {
|
||||
constructor(ID: number, data: UnitData) {
|
||||
super(ID, data);
|
||||
}
|
||||
|
||||
|
||||
getMarkerCategory() {
|
||||
return "navyunit";
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { LatLng, LatLngBounds } from "leaflet";
|
||||
import { getInfoPopup, getMap, getUnitDataTable } from "..";
|
||||
import { getHotgroupPanel, getInfoPopup, getMap, getUnitDataTable } from "..";
|
||||
import { Unit } from "./unit";
|
||||
import { cloneUnit } from "../server/server";
|
||||
import { IDLE, MOVE_UNIT } from "../map/map";
|
||||
@ -45,6 +45,10 @@ export class UnitsManager {
|
||||
return null;
|
||||
}
|
||||
|
||||
getUnitsByHotgroup(hotgroup: number) {
|
||||
return Object.values(this.#units).filter((unit: Unit) => {return unit.getBaseData().alive && unit.getHotgroup() == hotgroup});
|
||||
}
|
||||
|
||||
addUnit(ID: number, data: UnitData) {
|
||||
/* The name of the unit category is exactly the same as the constructor name */
|
||||
var constructor = Unit.getConstructor(data.baseData.category);
|
||||
@ -112,6 +116,11 @@ export class UnitsManager {
|
||||
}
|
||||
}
|
||||
|
||||
selectUnitsByHotgroup(hotgroup: number) {
|
||||
this.deselectAllUnits();
|
||||
this.getUnitsByHotgroup(hotgroup).forEach((unit: Unit) => unit.setSelected(true))
|
||||
}
|
||||
|
||||
getSelectedUnitsType() {
|
||||
if (this.getSelectedUnits().length == 0)
|
||||
return undefined;
|
||||
@ -214,7 +223,6 @@ export class UnitsManager {
|
||||
for (let idx in selectedUnits) {
|
||||
selectedUnits[idx].setSpeed(speed);
|
||||
}
|
||||
|
||||
this.#showActionMessage(selectedUnits, `setting speed to ${speed * 1.94384} kts`);
|
||||
}
|
||||
|
||||
@ -259,7 +267,7 @@ export class UnitsManager {
|
||||
}
|
||||
|
||||
selectedUnitsRefuel() {
|
||||
var selectedUnits = this.getSelectedUnits({excludeHumans: true});
|
||||
var selectedUnits = this.getSelectedUnits({excludeHumans: true});
|
||||
for (let idx in selectedUnits) {
|
||||
selectedUnits[idx].refuel();
|
||||
}
|
||||
@ -309,6 +317,22 @@ export class UnitsManager {
|
||||
this.#showActionMessage(selectedUnits, `following unit ${this.getUnitByID(ID)?.getBaseData().unitName}`);
|
||||
}
|
||||
|
||||
selectedUnitsSetHotgroup(hotgroup: number)
|
||||
{
|
||||
this.getUnitsByHotgroup(hotgroup).forEach((unit: Unit) => unit.setHotgroup(null));
|
||||
this.selectedUnitsAddToHotgroup(hotgroup);
|
||||
}
|
||||
|
||||
selectedUnitsAddToHotgroup(hotgroup: number)
|
||||
{
|
||||
var selectedUnits = this.getSelectedUnits();
|
||||
for (let idx in selectedUnits) {
|
||||
selectedUnits[idx].setHotgroup(hotgroup);
|
||||
}
|
||||
this.#showActionMessage(selectedUnits, `added to hotgroup ${hotgroup}`);
|
||||
getHotgroupPanel().refreshHotgroups();
|
||||
}
|
||||
|
||||
/***********************************************/
|
||||
copyUnits() {
|
||||
this.#copiedUnits = this.getSelectedUnits(); /* Can be applied to humans too */
|
||||
|
||||
3
client/views/hotgrouppanel.ejs
Normal file
3
client/views/hotgrouppanel.ejs
Normal file
@ -0,0 +1,3 @@
|
||||
<div id="hotgroup-panel">
|
||||
<!-- Here is where the various hotgroup buttons will show up -->
|
||||
</div>
|
||||
@ -24,7 +24,6 @@
|
||||
<div id="map-container"></div>
|
||||
<%- include('aic.ejs') %>
|
||||
<%- include('atc.ejs') %>
|
||||
|
||||
<%- include('contextmenus.ejs') %>
|
||||
<%- include('unitcontrolpanel.ejs') %>
|
||||
<%- include('unitinfopanel.ejs') %>
|
||||
@ -34,6 +33,7 @@
|
||||
<%- include('dialogs.ejs') %>
|
||||
<%- include('unitdatatable.ejs') %>
|
||||
<%- include('popups.ejs') %>
|
||||
<%- include('hotgrouppanel.ejs') %>
|
||||
|
||||
<div id="gray-out"></div>
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user