mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
Minor refactor and bug fixing
This commit is contained in:
parent
89c39c7038
commit
74d5480587
@ -1,194 +0,0 @@
|
||||
/**************************************/
|
||||
|
||||
.olympus-dialog {
|
||||
align-self: center;
|
||||
background:white;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-self: center;
|
||||
padding:10px;
|
||||
position:absolute;
|
||||
width:fit-content;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.olympus-dialog-close {
|
||||
cursor:pointer;
|
||||
position:absolute;
|
||||
right:10px;
|
||||
top:5px;
|
||||
}
|
||||
|
||||
.olympus-dialog-header {
|
||||
font-weight:bold;
|
||||
}
|
||||
|
||||
|
||||
/**************************************/
|
||||
|
||||
|
||||
/***** AIC *****/
|
||||
|
||||
.aic-panel {
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
#aic-control-panel {
|
||||
bottom:30px;
|
||||
position: absolute;
|
||||
left:30px;
|
||||
}
|
||||
|
||||
|
||||
#aic-control-panel .olympus-button img {
|
||||
max-width: 32px;
|
||||
}
|
||||
|
||||
|
||||
#aic-toolbox, #aic-callsign-panel {
|
||||
align-items: flex-start;
|
||||
align-self: center;
|
||||
flex-direction: column;
|
||||
row-gap: 10px;
|
||||
display:none;
|
||||
position:absolute;
|
||||
}
|
||||
|
||||
.aic-panel {
|
||||
background:#eaeaea;
|
||||
border-bottom-right-radius: 10px;
|
||||
border-top-right-radius: 10px;
|
||||
justify-self: left;
|
||||
padding:5px 10px;
|
||||
}
|
||||
|
||||
.aic-enabled #aic-toolbox, .aic-enabled #aic-callsign-panel {
|
||||
display:flex;
|
||||
}
|
||||
|
||||
.aic-enabled #aic-callsign-panel {
|
||||
align-self: auto;
|
||||
top: 100px;
|
||||
}
|
||||
|
||||
.aic-panel h2 {
|
||||
font-size:90%;
|
||||
margin:0;
|
||||
padding:0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#aic-callsign-display {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#aic-formation-list {
|
||||
display:flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#aic-formation-list > div {
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
display:flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
margin-top:10px;
|
||||
position:relative;
|
||||
}
|
||||
|
||||
#aic-formation-list .aic-formation-image img {
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 10px;
|
||||
max-width: 50px;
|
||||
}
|
||||
|
||||
#aic-formation-list .aic-formation-name {
|
||||
font-size:90%;
|
||||
}
|
||||
|
||||
#aic-formation-list .aic-formation-descriptor {
|
||||
background:white;
|
||||
border-radius: 10px;
|
||||
left:100px;
|
||||
padding:5px;
|
||||
position:absolute;
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
#aic-teleprompt {
|
||||
background-color: white;
|
||||
border:2px solid black;
|
||||
border-radius: 10px;
|
||||
bottom: 50px;
|
||||
color: black;
|
||||
display: none;
|
||||
justify-content: center;
|
||||
justify-self: center;
|
||||
padding: 10px;
|
||||
position: absolute;
|
||||
width: fit-content;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.aic-enabled #aic-teleprompt {
|
||||
display:flex;
|
||||
}
|
||||
|
||||
#aic-descriptor {
|
||||
display:flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
#aic-descriptor .aic-descriptor-section {
|
||||
display:flex;
|
||||
flex-direction: column;
|
||||
margin:0 10px;
|
||||
}
|
||||
|
||||
#aic-descriptor .aic-descriptor-section-label {
|
||||
background-color:#eaeaea;
|
||||
border-top-left-radius: 10px;
|
||||
border-top-right-radius: 10px;
|
||||
padding:.25em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#aic-descriptor .aic-descriptor-phrase {
|
||||
border-bottom: 1px solid #ccc;
|
||||
display:flex;
|
||||
flex-direction: row;
|
||||
margin-bottom:5px;
|
||||
padding-bottom:2px;
|
||||
}
|
||||
|
||||
#aic-descriptor .aic-descriptor-phrase:last-of-type {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
#aic-descriptor .aic-descriptor-components .aic-descriptor-component {
|
||||
margin:0 5px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#aic-descriptor .aic-descriptor-component-label {
|
||||
display:none;
|
||||
}
|
||||
|
||||
#aic-descriptor .aic-descriptor-component-value:after {
|
||||
content:",";
|
||||
margin-right:5px;
|
||||
}
|
||||
|
||||
#aic-descriptor .aic-descriptor-component:last-of-type .aic-descriptor-component-value:after {
|
||||
content:"; ";
|
||||
}
|
||||
|
||||
#aic-descriptor .aic-descriptor-section:last-of-type .aic-descriptor-component:last-of-type .aic-descriptor-component-value:after {
|
||||
content:".";
|
||||
}
|
||||
|
||||
|
||||
/**************************************/
|
||||
@ -1,205 +0,0 @@
|
||||
.ol-strip-board .ol-dialog-header {
|
||||
align-items: center;
|
||||
display:flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.ol-strip-board-strips {
|
||||
display:flex;
|
||||
flex-direction: column;
|
||||
row-gap: 4px;
|
||||
}
|
||||
|
||||
.ol-strip-board-strip {
|
||||
align-items: center;
|
||||
border-radius: var( --border-radius-sm );
|
||||
column-gap: 4px;
|
||||
display:flex;
|
||||
flex-flow: row nowrap;
|
||||
row-gap:4px;
|
||||
}
|
||||
|
||||
.ol-strip-board-strip[data-flight-status="checkedin"] {
|
||||
background-color: #ffffff2A;
|
||||
}
|
||||
|
||||
.ol-strip-board-strip[data-flight-status="readytotaxi"] {
|
||||
background-color: #ffff0063;
|
||||
}
|
||||
|
||||
.ol-strip-board-strip[data-flight-status="clearedtotaxi"] {
|
||||
background-color: #00ff0030;
|
||||
}
|
||||
|
||||
.ol-strip-board-strip[data-flight-status="halted"] {
|
||||
background-color: #FF000040;
|
||||
}
|
||||
|
||||
.ol-strip-board-strip[data-flight-status="terminated"] {
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
.ol-strip-board-headers {
|
||||
column-gap: 4px;
|
||||
display:flex;
|
||||
flex-flow:row nowrap;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ol-strip-board-headers > *, .ol-strip-board-strip > [data-point] {
|
||||
padding: 4px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
width:80px;
|
||||
}
|
||||
|
||||
.ol-strip-board-strip input[type="text"] {
|
||||
appearance: none;
|
||||
background-color: transparent;
|
||||
border:1px solid #ffffff30;
|
||||
border-radius: var( --border-radius-sm );
|
||||
color:white;
|
||||
font-size:12px;
|
||||
font-weight:normal;
|
||||
outline:none;
|
||||
padding: 4px 0;
|
||||
text-align: center;
|
||||
width:100%;
|
||||
}
|
||||
|
||||
.ol-strip-board-strip[data-time-warning="level-1"] [data-point="timeToGo"] {
|
||||
border:1px solid #cc0000;
|
||||
}
|
||||
|
||||
.ol-strip-board-headers :nth-child(1) {
|
||||
width:12px;
|
||||
}
|
||||
|
||||
.ol-strip-board-headers :nth-child(2),
|
||||
.ol-strip-board-strip :nth-child(2),
|
||||
[data-board-type="ground"] .ol-strip-board-headers :nth-child(3),
|
||||
[data-board-type="ground"] .ol-strip-board-strip :nth-child(3) {
|
||||
width:130px;
|
||||
}
|
||||
|
||||
[data-board-type="ground"] .ol-strip-board-strip :nth-child(5) {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.ol-strip-board-headers :last-child,
|
||||
.ol-strip-board-strip :last-child {
|
||||
width:20px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[data-board-type="tower"] .ol-strip-board-strip > * {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
[data-board-type="tower"] .ol-strip-board-strip a {
|
||||
color:white;
|
||||
}
|
||||
|
||||
[data-board-type="tower"] .ol-strip-board-strip > :nth-child(2) {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
[data-board-type="tower"] .ol-strip-board-strip :nth-child(3) input,
|
||||
[data-board-type="tower"] .ol-strip-board-strip :nth-child(5) input {
|
||||
width:30px;
|
||||
}
|
||||
|
||||
[data-board-type="tower"] .ol-strip-board-strip :nth-child(3) {
|
||||
font-size:10px;
|
||||
}
|
||||
|
||||
|
||||
[data-altitude-assigned] [data-point="assignedAltitude"] input,
|
||||
[data-speed-assigned] [data-point="assignedSpeed"] input {
|
||||
background-color:#ffffffbb;
|
||||
color: black;
|
||||
font-weight: var( --font-weight-bolder );
|
||||
}
|
||||
|
||||
[data-warning-altitude] [data-point="altitude"],
|
||||
[data-warning-speed] [data-point="speed"] {
|
||||
background:#cc0000;
|
||||
border-radius: var( --border-radius-sm );
|
||||
}
|
||||
|
||||
|
||||
|
||||
.ol-strip-board-strip > [data-point="name"] {
|
||||
text-overflow: ellipsis;
|
||||
overflow:hidden;
|
||||
}
|
||||
|
||||
.ol-strip-board-strip .ol-select-value {
|
||||
opacity: .85;
|
||||
}
|
||||
|
||||
|
||||
.ol-strip-board-add-flight {
|
||||
display:flex;
|
||||
flex-flow: row nowrap;
|
||||
position:relative;
|
||||
}
|
||||
|
||||
|
||||
.ol-strip-board-add-flight > * {
|
||||
border:none;
|
||||
outline: none;
|
||||
padding:4px 8px;
|
||||
}
|
||||
|
||||
.add-flight-by-click img {
|
||||
filter:invert();
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.ol-strip-board-add-flight input {
|
||||
border-radius: var( --border-radius-sm );
|
||||
}
|
||||
|
||||
.ol-strip-board-add-flight .ol-auto-suggest {
|
||||
background:white;
|
||||
border-radius: var(--border-radius-sm );
|
||||
color:black;
|
||||
display:none;
|
||||
flex-direction: column;
|
||||
left:0;
|
||||
margin:0;
|
||||
position:absolute;
|
||||
translate:0 -100%;
|
||||
top:0;
|
||||
}
|
||||
|
||||
.ol-strip-board-add-flight .ol-auto-suggest[data-has-suggestions] {
|
||||
display:flex;
|
||||
row-gap: 4px;
|
||||
}
|
||||
|
||||
.ol-strip-board-add-flight .ol-auto-suggest[data-has-suggestions] a {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
[data-board-type="ground"] {
|
||||
bottom:20px;
|
||||
}
|
||||
|
||||
[data-board-type="tower"] {
|
||||
right:10px;
|
||||
top:10px;
|
||||
}
|
||||
|
||||
[data-board-type="tower"] .ol-auto-suggest {
|
||||
top:30px;
|
||||
translate:0;
|
||||
}
|
||||
@ -1,27 +0,0 @@
|
||||
#unit-list {
|
||||
display:flex;
|
||||
flex-direction: column;
|
||||
font-size:13px;
|
||||
height: 250px;
|
||||
width:fit-content;
|
||||
}
|
||||
|
||||
#unit-list > div {
|
||||
display:flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
#unit-list > div > div {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
width:100px;
|
||||
}
|
||||
|
||||
#unit-list > div:first-of-type {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#unit-list > div > div:nth-of-type( 4 ) {
|
||||
text-align: center;
|
||||
}
|
||||
@ -1,9 +1,6 @@
|
||||
@import url("layout/layout.css");
|
||||
@import url("style/style.css");
|
||||
|
||||
@import url("atc/atc.css");
|
||||
@import url("atc/unitdatatable.css");
|
||||
|
||||
@import url("panels/connectionstatus.css");
|
||||
@import url("panels/serverstatus.css");
|
||||
@import url("panels/mouseinfo.css");
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
|
||||
* {
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
@ -7,7 +6,7 @@
|
||||
|
||||
html * {
|
||||
font-family: 'Open Sans', sans-serif !important;
|
||||
user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
body {
|
||||
@ -117,7 +116,8 @@ form>div {
|
||||
|
||||
.ol-panel {
|
||||
background-color: var(--background-steel);
|
||||
border-radius: var(--border-radius-md);;
|
||||
border-radius: var(--border-radius-md);
|
||||
;
|
||||
box-shadow: 0px 2px 5px #000A;
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
@ -963,16 +963,16 @@ nav.ol-panel> :last-child {
|
||||
|
||||
#command-mode-phase.game-commenced::after {
|
||||
content: "Spawn restrictions are being enforced";
|
||||
font-size: 10px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
#command-mode-phase.no-restrictions::after {
|
||||
content: "No spawn restrictions";
|
||||
font-size: 10px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
#command-mode-toolbar {
|
||||
min-width: fit-content ;
|
||||
min-width: fit-content;
|
||||
}
|
||||
|
||||
#command-mode-toolbar .ol-button {
|
||||
@ -990,17 +990,17 @@ nav.ol-panel> :last-child {
|
||||
}
|
||||
|
||||
#command-mode-settings-dialog>.ol-dialog-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: nowrap;
|
||||
margin-bottom: 10px;
|
||||
margin-top: 10px;
|
||||
row-gap: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: nowrap;
|
||||
margin-bottom: 10px;
|
||||
margin-top: 10px;
|
||||
row-gap: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#command-mode-settings-dialog>.ol-dialog-content .ol-group {
|
||||
justify-content: space-between;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
#command-mode-settings-dialog h4 {
|
||||
@ -1292,11 +1292,11 @@ input[type=number]::-webkit-outer-spin-button {
|
||||
position: relative;
|
||||
transition: background-color 0.2s;
|
||||
height: var(--height);
|
||||
width: var(--width);
|
||||
width: var(--width);
|
||||
}
|
||||
|
||||
.ol-switch-fill::after {
|
||||
aspect-ratio : 1 / 1;
|
||||
aspect-ratio: 1 / 1;
|
||||
background-clip: content-box;
|
||||
background-color: #ffffff;
|
||||
border-radius: 999px;
|
||||
@ -1310,15 +1310,15 @@ input[type=number]::-webkit-outer-spin-button {
|
||||
}
|
||||
|
||||
.ol-switch-fill::before {
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
color: white;
|
||||
display: flex;
|
||||
font-size: 11px;
|
||||
height: 100%;
|
||||
padding: 0px 7px;
|
||||
position: absolute;
|
||||
transition: transform 0.2s;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
color: white;
|
||||
display: flex;
|
||||
font-size: 11px;
|
||||
height: 100%;
|
||||
padding: 0px 7px;
|
||||
position: absolute;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.ol-switch[data-value="false"]>.ol-switch-fill::before {
|
||||
@ -1326,11 +1326,11 @@ input[type=number]::-webkit-outer-spin-button {
|
||||
}
|
||||
|
||||
.ol-switch[data-value="true"]>.ol-switch-fill::after {
|
||||
transform: translateX(calc(var(--width) - var(--height)));
|
||||
transform: translateX(calc(var(--width) - var(--height)));
|
||||
}
|
||||
|
||||
.ol-switch[data-value="undefined"]>.ol-switch-fill::after {
|
||||
transform: translateX(calc((var(--width) - var(--height)) * 0.5));
|
||||
transform: translateX(calc((var(--width) - var(--height)) * 0.5));
|
||||
}
|
||||
|
||||
.ol-contexmenu-panel {
|
||||
@ -1338,15 +1338,15 @@ input[type=number]::-webkit-outer-spin-button {
|
||||
}
|
||||
|
||||
.ol-coalition-switch[data-value="false"]>.ol-switch-fill {
|
||||
background-color: var(--primary-blue);
|
||||
background-color: var(--primary-blue);
|
||||
}
|
||||
|
||||
.ol-coalition-switch[data-value="true"]>.ol-switch-fill {
|
||||
background-color: var(--primary-red);
|
||||
background-color: var(--primary-red);
|
||||
}
|
||||
|
||||
.ol-coalition-switch[data-value="undefined"]>.ol-switch-fill {
|
||||
background-color: var(--primary-neutral);
|
||||
background-color: var(--primary-neutral);
|
||||
}
|
||||
|
||||
.ol-context-menu>ul {
|
||||
@ -1412,4 +1412,4 @@ input[type=number]::-webkit-outer-spin-button {
|
||||
|
||||
.ol-log-entry {
|
||||
border-bottom: 1px solid #FFFFFF44;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,172 +0,0 @@
|
||||
import { ToggleableFeature } from "../features/toggleablefeature";
|
||||
import { AICFormation_Azimuth } from "./aicformation/azimuth";
|
||||
import { AICFormation_Range } from "./aicformation/range";
|
||||
import { AICFormation_Single } from "./aicformation/single";
|
||||
import { AICFormationDescriptorSection } from "./aicformationdescriptorsection";
|
||||
|
||||
|
||||
export class AIC extends ToggleableFeature {
|
||||
|
||||
#formations = [
|
||||
|
||||
new AICFormation_Single(),
|
||||
new AICFormation_Range(),
|
||||
new AICFormation_Azimuth()
|
||||
|
||||
];
|
||||
|
||||
|
||||
constructor() {
|
||||
|
||||
super( false );
|
||||
|
||||
this.onStatusUpdate();
|
||||
|
||||
// This feels kind of dirty
|
||||
let $aicFormationList = document.getElementById( "aic-formation-list" );
|
||||
|
||||
if ( $aicFormationList ) {
|
||||
|
||||
this.getFormations().forEach( formation => {
|
||||
|
||||
// Image
|
||||
let $imageDiv = document.createElement( "div" );
|
||||
$imageDiv.classList.add( "aic-formation-image" );
|
||||
|
||||
let $img = document.createElement( "img" );
|
||||
$img.src = "images/formations/" + formation.icon;
|
||||
|
||||
$imageDiv.appendChild( $img );
|
||||
|
||||
// Name
|
||||
let $nameDiv = document.createElement( "div" );
|
||||
$nameDiv.classList.add( "aic-formation-name" );
|
||||
$nameDiv.innerText = formation.label;
|
||||
|
||||
// Wrapper
|
||||
let $wrapperDiv = document.createElement( "div" );
|
||||
$wrapperDiv.dataset.formationName = formation.name;
|
||||
$wrapperDiv.appendChild( $imageDiv )
|
||||
$wrapperDiv.appendChild( $nameDiv );
|
||||
$wrapperDiv.addEventListener( "click", ( ev ) => {
|
||||
|
||||
const controlTypeInput = document.querySelector( "input[type='radio'][name='control-type']:checked" );
|
||||
|
||||
let controlTypeValue:any = ( controlTypeInput instanceof HTMLInputElement && [ "broadcast", "tactical" ].indexOf( controlTypeInput.value ) > -1 ) ? controlTypeInput.value : "broadcast";
|
||||
|
||||
// TODO: make this not an "any"
|
||||
const output:any = formation.getDescriptor({
|
||||
"aicCallsign" : "Magic",
|
||||
"bullseyeName" : "Bullseye",
|
||||
"control" : controlTypeValue,
|
||||
"numGroups" : formation.numGroups
|
||||
});
|
||||
|
||||
this.updateTeleprompt( output );
|
||||
|
||||
});
|
||||
|
||||
// Add to DOM
|
||||
$aicFormationList?.appendChild( $wrapperDiv );
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
getFormations() {
|
||||
return this.#formations;
|
||||
}
|
||||
|
||||
|
||||
onStatusUpdate() {
|
||||
|
||||
// Update the DOM
|
||||
document.body.classList.toggle( "aic-enabled", this.getStatus() );
|
||||
|
||||
}
|
||||
|
||||
|
||||
toggleHelp() {
|
||||
document.getElementById( "aic-help" )?.classList.toggle( "hide" );
|
||||
}
|
||||
|
||||
//*
|
||||
updateTeleprompt<T extends AICFormationDescriptorSection>( descriptor:T[] ) {
|
||||
|
||||
let $teleprompt = document.getElementById( "aic-teleprompt" );
|
||||
|
||||
if ( $teleprompt instanceof HTMLElement ) {
|
||||
|
||||
// Clean slate
|
||||
while ( $teleprompt.childNodes.length > 0 ) {
|
||||
$teleprompt.childNodes[0].remove();
|
||||
}
|
||||
|
||||
function newDiv() {
|
||||
return document.createElement( "div" );
|
||||
}
|
||||
|
||||
// Wrapper
|
||||
let $descriptor = newDiv();
|
||||
$descriptor.id = "aic-descriptor";
|
||||
|
||||
for ( const section of descriptor ) {
|
||||
|
||||
if ( section.omitSection ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let $section = newDiv();
|
||||
$section.classList.add( "aic-descriptor-section" );
|
||||
|
||||
let $sectionLabel = newDiv();
|
||||
$sectionLabel.classList.add( "aic-descriptor-section-label" );
|
||||
$sectionLabel.innerText = section.label;
|
||||
$section.appendChild( $sectionLabel );
|
||||
|
||||
|
||||
for ( const phrase of section.getPhrases() ) {
|
||||
|
||||
let $phrase = newDiv();
|
||||
$phrase.classList.add( "aic-descriptor-phrase" );
|
||||
|
||||
for ( const component of phrase.getComponents() ) {
|
||||
|
||||
let $component = newDiv();
|
||||
$component.classList.add( "aic-descriptor-component" );
|
||||
|
||||
let $componentLabel = newDiv();
|
||||
$componentLabel.classList.add( "aic-descriptor-component-label" );
|
||||
$componentLabel.innerText = component.label;
|
||||
|
||||
let $componentValue = newDiv();
|
||||
$componentValue.classList.add( "aic-descriptor-component-value" );
|
||||
$componentValue.innerText = component.value;
|
||||
|
||||
$component.appendChild( $componentLabel );
|
||||
$component.appendChild( $componentValue );
|
||||
|
||||
$phrase.appendChild( $component );
|
||||
|
||||
}
|
||||
|
||||
$section.appendChild( $phrase );
|
||||
|
||||
}
|
||||
|
||||
$descriptor.appendChild( $section );
|
||||
|
||||
}
|
||||
|
||||
$teleprompt.appendChild( $descriptor );
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
//*/
|
||||
|
||||
}
|
||||
@ -1,54 +0,0 @@
|
||||
import { AICFormationContextDataInterface, AICFormationDescriptor } from "./aicformationdescriptor";
|
||||
import { AICFormationDescriptorPhrase } from "./aicformationdescriptorphrase";
|
||||
import { AICFormationDescriptorSection } from "./aicformationdescriptorsection";
|
||||
|
||||
export interface AICFormationInterface {
|
||||
"icon" : string,
|
||||
"label" : string,
|
||||
"name" : string,
|
||||
"numGroups" : number,
|
||||
"summary" : string,
|
||||
"unitBreakdown" : string[]
|
||||
}
|
||||
|
||||
|
||||
|
||||
export abstract class AICFormation {
|
||||
|
||||
"icon" = "";
|
||||
"label" = "";
|
||||
"name" = "";
|
||||
"numGroups" = 1;
|
||||
"summary" = "";
|
||||
"unitBreakdown":string[] = []
|
||||
|
||||
|
||||
constructor() {
|
||||
|
||||
this.unitBreakdown = [];
|
||||
}
|
||||
|
||||
|
||||
addToDescriptorPhrase( section: AICFormationDescriptorSection, phrase: AICFormationDescriptorPhrase, contextData: AICFormationContextDataInterface ) {
|
||||
return phrase;
|
||||
}
|
||||
|
||||
|
||||
getDescriptor( contextData: AICFormationContextDataInterface ) {
|
||||
|
||||
return new AICFormationDescriptor().generate( this, contextData );
|
||||
|
||||
}
|
||||
|
||||
|
||||
hasUnitBreakdown() {
|
||||
return this.unitBreakdown.length > 0;
|
||||
}
|
||||
|
||||
|
||||
showFormationNameInDescriptor() {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -1,38 +0,0 @@
|
||||
import { AICFormation, AICFormationInterface } from "../aicformation";
|
||||
import { AICFormationContextDataInterface } from "../aicformationdescriptor";
|
||||
import { AICFormationDescriptorSection } from "../aicformationdescriptorsection";
|
||||
import { AICFormationDescriptorComponent } from "../aicformationdescriptorcomponent";
|
||||
import { AICFormationDescriptorPhrase } from "../aicformationdescriptorphrase";
|
||||
|
||||
export class AICFormation_Azimuth extends AICFormation implements AICFormationInterface {
|
||||
|
||||
"icon" = "azimuth.png";
|
||||
"label" = "Azimuth";
|
||||
"name" = "azimuth";
|
||||
"numGroups" = 2;
|
||||
"summary" = "Two contacts, side-by-side in a line perpedicular to the perspective.";
|
||||
"unitBreakdown" = [ "<compass> group", "<compass> group" ];
|
||||
|
||||
constructor() {
|
||||
|
||||
super();
|
||||
|
||||
}
|
||||
|
||||
addToDescriptorPhrase( section: AICFormationDescriptorSection, phrase: AICFormationDescriptorPhrase, contextData: AICFormationContextDataInterface ) {
|
||||
|
||||
switch ( section.name ) {
|
||||
|
||||
case "formation":
|
||||
|
||||
phrase.addComponent( new AICFormationDescriptorComponent( "<distance>" ) );
|
||||
phrase.addComponent( new AICFormationDescriptorComponent( "track <compass>" ) );
|
||||
|
||||
}
|
||||
|
||||
return phrase;
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,38 +0,0 @@
|
||||
import { AICFormation, AICFormationInterface } from "../aicformation";
|
||||
import { AICFormationContextDataInterface } from "../aicformationdescriptor";
|
||||
import { AICFormationDescriptorSection } from "../aicformationdescriptorsection";
|
||||
import { AICFormationDescriptorComponent } from "../aicformationdescriptorcomponent";
|
||||
import { AICFormationDescriptorPhrase } from "../aicformationdescriptorphrase";
|
||||
|
||||
export class AICFormation_Range extends AICFormation implements AICFormationInterface {
|
||||
|
||||
"icon" = "range.png";
|
||||
"label" = "Range";
|
||||
"name" = "range";
|
||||
"numGroups" = 2;
|
||||
"summary" = "Two contacts, one behind the other";
|
||||
"unitBreakdown" = [ "Lead group", "Trail group" ];
|
||||
|
||||
constructor() {
|
||||
|
||||
super();
|
||||
|
||||
}
|
||||
|
||||
addToDescriptorPhrase( section: AICFormationDescriptorSection, phrase: AICFormationDescriptorPhrase, contextData: AICFormationContextDataInterface ) {
|
||||
|
||||
switch ( section.name ) {
|
||||
|
||||
case "formation":
|
||||
|
||||
phrase.addComponent( new AICFormationDescriptorComponent( "<distance>" ) );
|
||||
phrase.addComponent( new AICFormationDescriptorComponent( "track <compass>" ) );
|
||||
|
||||
|
||||
}
|
||||
|
||||
return phrase;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,24 +0,0 @@
|
||||
import { AICFormation, AICFormationInterface } from "../aicformation";
|
||||
import { AICFormationContextDataInterface, AICFormationDescriptor } from "../aicformationdescriptor";
|
||||
|
||||
export class AICFormation_Single extends AICFormation implements AICFormationInterface {
|
||||
|
||||
"icon" = "single.png";
|
||||
"label" = "Single";
|
||||
"name" = "single";
|
||||
"numGroups" = 1;
|
||||
"summary" = "One contact on its own";
|
||||
"unitBreakdown": string[] = [];
|
||||
|
||||
constructor() {
|
||||
|
||||
super();
|
||||
|
||||
}
|
||||
|
||||
|
||||
showFormationNameInDescriptor() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,55 +0,0 @@
|
||||
import { AICFormation } from "./aicformation";
|
||||
import { AICFormationDescriptorSection } from "./aicformationdescriptorsection";
|
||||
import { AICFormationDescriptorSection_Formation } from "./aicformationdescriptorsection/formation";
|
||||
import { AICFormationDescriptorSection_Unit } from "./aicformationdescriptorsection/unit";
|
||||
import { AICFormationDescriptorSection_NumGroups } from "./aicformationdescriptorsection/numgroups";
|
||||
import { AICFormationDescriptorSection_Who } from "./aicformationdescriptorsection/who";
|
||||
|
||||
|
||||
export interface AICFormationContextDataInterface {
|
||||
"aicCallsign" : string,
|
||||
"bullseyeName" : string,
|
||||
"control" : "broadcast" | "tactical",
|
||||
"numGroups" : number
|
||||
}
|
||||
|
||||
|
||||
export class AICFormationDescriptor {
|
||||
|
||||
#sections:AICFormationDescriptorSection[] = [
|
||||
new AICFormationDescriptorSection_Who(),
|
||||
new AICFormationDescriptorSection_NumGroups(),
|
||||
new AICFormationDescriptorSection_Formation(),
|
||||
new AICFormationDescriptorSection_Unit()
|
||||
]
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
|
||||
addSection( section:AICFormationDescriptorSection ) {
|
||||
this.#sections.push( section );
|
||||
}
|
||||
|
||||
|
||||
getSections() {
|
||||
return this.#sections;
|
||||
}
|
||||
|
||||
|
||||
generate( formation:AICFormation, contextData: AICFormationContextDataInterface ) {
|
||||
|
||||
let output:object[] = [];
|
||||
|
||||
for ( const section of this.#sections ) {
|
||||
output.push(
|
||||
section.generate( formation, contextData )
|
||||
);
|
||||
}
|
||||
|
||||
return output;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -1,18 +0,0 @@
|
||||
interface ComponentInterface {
|
||||
"label" : string;
|
||||
"value" : string;
|
||||
}
|
||||
|
||||
export class AICFormationDescriptorComponent implements ComponentInterface {
|
||||
|
||||
label = "(not set)";
|
||||
value = "(not set)";
|
||||
|
||||
constructor( value:any, label?:string ) {
|
||||
|
||||
this.label = label || "(not set)";
|
||||
this.value = value;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
import { AICFormationDescriptorComponent } from "../aicformationdescriptorcomponent";
|
||||
|
||||
export abstract class AICFormactionDescriptorComponent_Distance extends AICFormationDescriptorComponent {
|
||||
|
||||
constructor( value:string, label?:string ) {
|
||||
super( value, label );
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
import { AICFormactionDescriptorComponent_Distance } from "../distance";
|
||||
|
||||
export class AICFormationDescriptorComponent_Distance_Range extends AICFormactionDescriptorComponent_Distance {
|
||||
|
||||
constructor( value:string, label?:string ) {
|
||||
super( value, label );
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,40 +0,0 @@
|
||||
import { AICFormation } from "./aicformation";
|
||||
import { AICFormationContextDataInterface } from "./aicformationdescriptor";
|
||||
import { AICFormationDescriptorComponent } from "./aicformationdescriptorcomponent";
|
||||
|
||||
export interface AICFormationDescriptorPhraseInterface {
|
||||
"generate" : CallableFunction,
|
||||
"label" : string,
|
||||
"name" : string
|
||||
}
|
||||
|
||||
export class AICFormationDescriptorPhrase {
|
||||
|
||||
#components : AICFormationDescriptorComponent[] = [];
|
||||
label = "";
|
||||
name = "";
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
|
||||
addComponent( component:AICFormationDescriptorComponent ) {
|
||||
this.#components.push( component );
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
getComponents() {
|
||||
return this.#components;
|
||||
}
|
||||
|
||||
|
||||
generate( formation:AICFormation, contextData: AICFormationContextDataInterface ) {
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -1,40 +0,0 @@
|
||||
import { AICFormation } from "./aicformation";
|
||||
import { AICFormationContextDataInterface } from "./aicformationdescriptor";
|
||||
import { AICFormationDescriptorPhrase } from "./aicformationdescriptorphrase";
|
||||
|
||||
export interface AICFormationDescriptorSectionInterface {
|
||||
"generate" : CallableFunction,
|
||||
"label" : string,
|
||||
"name" : string,
|
||||
"omitSection" : boolean
|
||||
}
|
||||
|
||||
export abstract class AICFormationDescriptorSection {
|
||||
|
||||
#phrases : AICFormationDescriptorPhrase[] = [];
|
||||
label = "";
|
||||
name = "";
|
||||
omitSection = false;
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
|
||||
addPhrase( phrase:AICFormationDescriptorPhrase ) {
|
||||
this.#phrases.push( phrase );
|
||||
}
|
||||
|
||||
|
||||
generate( formation:AICFormation, contextData: AICFormationContextDataInterface ) {
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
|
||||
getPhrases() {
|
||||
return this.#phrases;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -1,39 +0,0 @@
|
||||
import { AICFormation } from "../aicformation";
|
||||
import { AICFormationContextDataInterface } from "../aicformationdescriptor";
|
||||
import { AICFormationDescriptorSection } from "../aicformationdescriptorsection";
|
||||
import { AICFormationDescriptorComponent } from "../aicformationdescriptorcomponent";
|
||||
import { AICFormationDescriptorPhrase } from "../aicformationdescriptorphrase";
|
||||
|
||||
export class AICFormationDescriptorSection_Formation extends AICFormationDescriptorSection {
|
||||
|
||||
label = "Formation";
|
||||
name = "formation";
|
||||
|
||||
constructor() {
|
||||
|
||||
super();
|
||||
|
||||
}
|
||||
|
||||
|
||||
generate( formation:AICFormation, contextData: AICFormationContextDataInterface ) {
|
||||
|
||||
if ( !formation.showFormationNameInDescriptor() ) {
|
||||
this.omitSection = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
let phrase = new AICFormationDescriptorPhrase();
|
||||
|
||||
phrase.addComponent( new AICFormationDescriptorComponent( formation.label, "Formation" ) );
|
||||
|
||||
phrase = formation.addToDescriptorPhrase( this, phrase, contextData );
|
||||
|
||||
this.addPhrase( phrase );
|
||||
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,35 +0,0 @@
|
||||
import { AICFormation } from "../aicformation";
|
||||
import { AICFormationContextDataInterface } from "../aicformationdescriptor";
|
||||
import { AICFormationDescriptorSection } from "../aicformationdescriptorsection";
|
||||
import { AICFormationDescriptorComponent } from "../aicformationdescriptorcomponent";
|
||||
import { AICFormationDescriptorPhrase } from "../aicformationdescriptorphrase";
|
||||
|
||||
export class AICFormationDescriptorSection_NumGroups extends AICFormationDescriptorSection {
|
||||
|
||||
label = "Groups";
|
||||
name = "numgroups";
|
||||
|
||||
constructor() {
|
||||
|
||||
super();
|
||||
|
||||
}
|
||||
|
||||
|
||||
generate( formation:AICFormation, contextData: AICFormationContextDataInterface ) {
|
||||
|
||||
let value = "Single group";
|
||||
|
||||
if ( contextData.numGroups > 1 ) {
|
||||
value = contextData.numGroups + " groups";
|
||||
}
|
||||
|
||||
let phrase = new AICFormationDescriptorPhrase();
|
||||
phrase.addComponent( new AICFormationDescriptorComponent( value, "Number of groups" ) );
|
||||
this.addPhrase( phrase );
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,83 +0,0 @@
|
||||
import { AICFormation } from "../aicformation";
|
||||
import { AICFormationContextDataInterface } from "../aicformationdescriptor";
|
||||
import { AICFormationDescriptorSection } from "../aicformationdescriptorsection";
|
||||
import { AICFormationDescriptorComponent } from "../aicformationdescriptorcomponent";
|
||||
import { AICFormationDescriptorPhrase } from "../aicformationdescriptorphrase";
|
||||
|
||||
interface addUnitInformationInterface {
|
||||
omitTrack?: boolean
|
||||
}
|
||||
|
||||
export class AICFormationDescriptorSection_Unit extends AICFormationDescriptorSection {
|
||||
|
||||
label = "Unit";
|
||||
name = "unit";
|
||||
|
||||
constructor() {
|
||||
|
||||
super();
|
||||
|
||||
}
|
||||
|
||||
|
||||
addUnitInformation( formation:AICFormation, contextData: AICFormationContextDataInterface, phrase: AICFormationDescriptorPhrase, options?:addUnitInformationInterface ) {
|
||||
|
||||
options = options || {};
|
||||
|
||||
const originPoint = ( contextData.control === "broadcast" ) ? contextData.bullseyeName : "BRAA";
|
||||
|
||||
phrase.addComponent( new AICFormationDescriptorComponent( originPoint, "Bearing origin point" ) );
|
||||
phrase.addComponent( new AICFormationDescriptorComponent( "<bearing>", "Bearing" ) );
|
||||
phrase.addComponent( new AICFormationDescriptorComponent( "<range>", "Range" ) );
|
||||
phrase.addComponent( new AICFormationDescriptorComponent( "<altitude>", "Altitude" ) );
|
||||
|
||||
if ( contextData.control === "broadcast" ) {
|
||||
if ( !options.hasOwnProperty( "omitTrack" ) || options.omitTrack !== true ) {
|
||||
phrase.addComponent( new AICFormationDescriptorComponent( "track <compass>", "Tracking" ) );
|
||||
}
|
||||
} else {
|
||||
phrase.addComponent( new AICFormationDescriptorComponent( "[hot|flanking [left|right]|beam <compass>|cold]", "Azimuth" ) );
|
||||
}
|
||||
|
||||
return phrase;
|
||||
|
||||
}
|
||||
|
||||
|
||||
generate( formation:AICFormation, contextData: AICFormationContextDataInterface ) {
|
||||
|
||||
if ( formation.hasUnitBreakdown() ) {
|
||||
|
||||
for ( const [ i, unitRef ] of formation.unitBreakdown.entries() ) {
|
||||
|
||||
let phrase = new AICFormationDescriptorPhrase();
|
||||
|
||||
phrase.addComponent( new AICFormationDescriptorComponent( unitRef, "Unit reference" ) );
|
||||
|
||||
if ( i === 0 ) {
|
||||
this.addUnitInformation( formation, contextData, phrase, { "omitTrack": true } );
|
||||
} else {
|
||||
phrase.addComponent( new AICFormationDescriptorComponent( "<altitude>" ) );
|
||||
}
|
||||
|
||||
phrase.addComponent( new AICFormationDescriptorComponent( "hostile" ) );
|
||||
|
||||
this.addPhrase( phrase );
|
||||
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
this.addPhrase(
|
||||
this.addUnitInformation( formation, contextData, new AICFormationDescriptorPhrase() )
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -1,35 +0,0 @@
|
||||
import { AICFormation } from "../aicformation";
|
||||
import { AICFormationContextDataInterface } from "../aicformationdescriptor";
|
||||
import { AICFormationDescriptorSection } from "../aicformationdescriptorsection";
|
||||
import { AICFormationDescriptorComponent } from "../aicformationdescriptorcomponent";
|
||||
import { AICFormationDescriptorPhrase } from "../aicformationdescriptorphrase";
|
||||
|
||||
export class AICFormationDescriptorSection_Who extends AICFormationDescriptorSection {
|
||||
|
||||
label = "Who";
|
||||
name = "who";
|
||||
|
||||
constructor() {
|
||||
|
||||
super();
|
||||
|
||||
}
|
||||
|
||||
|
||||
generate( formation:AICFormation, contextData: AICFormationContextDataInterface ) {
|
||||
|
||||
let phrase = new AICFormationDescriptorPhrase();
|
||||
|
||||
if ( contextData.control === "tactical" ) {
|
||||
phrase.addComponent( new AICFormationDescriptorComponent( "<their callsign>", "Their callsign" ) );
|
||||
}
|
||||
|
||||
phrase.addComponent( new AICFormationDescriptorComponent( contextData.aicCallsign, "Your callsign" ) );
|
||||
|
||||
this.addPhrase( phrase );
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,188 +0,0 @@
|
||||
import { getMissionHandler } from "..";
|
||||
import { convertDateAndTimeToDate } from "../other/utils";
|
||||
import { getConnected } from "../server/server";
|
||||
import { ATCBoard } from "./atcboard";
|
||||
import { ATCBoardGround } from "./board/ground";
|
||||
import { ATCBoardTower } from "./board/tower";
|
||||
|
||||
export interface FlightInterface {
|
||||
assignedSpeed: any;
|
||||
assignedAltitude : any;
|
||||
id : string;
|
||||
boardId : string;
|
||||
name : string;
|
||||
order : number;
|
||||
status : "unknown";
|
||||
takeoffTime : number;
|
||||
unitId : number;
|
||||
}
|
||||
|
||||
|
||||
class ATCDataHandler {
|
||||
|
||||
#atc:ATC;
|
||||
#flights:{[key:string]: FlightInterface} = {};
|
||||
|
||||
#updateInterval:number|undefined = undefined;
|
||||
#updateIntervalDelay:number = 2500; // Wait between unit update requests
|
||||
|
||||
|
||||
constructor( atc:ATC ) {
|
||||
|
||||
this.#atc = atc;
|
||||
|
||||
}
|
||||
|
||||
|
||||
getFlights( boardId:string ) {
|
||||
|
||||
return Object.values( this.#flights ).reduce( ( acc:{[key:string]: FlightInterface}, flight ) => {
|
||||
|
||||
if ( flight.boardId === boardId ) {
|
||||
acc[ flight.id ] = flight;
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {} );
|
||||
}
|
||||
|
||||
|
||||
startUpdates() {
|
||||
|
||||
this.#updateInterval = window.setInterval( () => {
|
||||
|
||||
if ( !getConnected() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const aBoardIsVisible = this.#atc.getBoards().some( board => board.boardIsVisible() );
|
||||
|
||||
if ( aBoardIsVisible ) {
|
||||
|
||||
fetch( '/api/atc/flight', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Accept': '*/*',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
.then( response => response.json() )
|
||||
.then( data => {
|
||||
this.setFlights( data );
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}, this.#updateIntervalDelay );
|
||||
|
||||
}
|
||||
|
||||
|
||||
setFlights( flights:{[key:string]: any} ) {
|
||||
|
||||
this.#flights = flights;
|
||||
|
||||
}
|
||||
|
||||
|
||||
stopUpdates() {
|
||||
|
||||
clearInterval( this.#updateInterval );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
export class ATC {
|
||||
|
||||
#boards:ATCBoard[] = [];
|
||||
#dataHandler:ATCDataHandler;
|
||||
|
||||
#initDate:Date = new Date();
|
||||
|
||||
constructor() {
|
||||
|
||||
this.#dataHandler = new ATCDataHandler( this );
|
||||
|
||||
this.lookForBoards();
|
||||
|
||||
}
|
||||
|
||||
|
||||
addBoard<T extends ATCBoard>( board:T ) {
|
||||
|
||||
board.startUpdates();
|
||||
|
||||
this.#boards.push( board );
|
||||
|
||||
}
|
||||
|
||||
|
||||
getBoards() {
|
||||
return this.#boards;
|
||||
}
|
||||
|
||||
|
||||
getDataHandler() {
|
||||
return this.#dataHandler;
|
||||
}
|
||||
|
||||
|
||||
getMissionElapsedSeconds() : number {
|
||||
return new Date().getTime() - this.#initDate.getTime();
|
||||
}
|
||||
|
||||
|
||||
getMissionStartDateTime() : Date {
|
||||
return new Date( 1990, 3, 1, 18, 0, 0 );
|
||||
}
|
||||
|
||||
|
||||
getMissionDate() : Date {
|
||||
return convertDateAndTimeToDate(getMissionHandler().getDateAndTime());
|
||||
}
|
||||
|
||||
lookForBoards() {
|
||||
|
||||
document.querySelectorAll( ".ol-strip-board" ).forEach( board => {
|
||||
|
||||
if ( board instanceof HTMLElement ) {
|
||||
|
||||
switch ( board.dataset.boardType ) {
|
||||
|
||||
case "ground":
|
||||
this.addBoard( new ATCBoardGround( this, board ) );
|
||||
return;
|
||||
|
||||
case "tower":
|
||||
this.addBoard( new ATCBoardTower( this, board ) );
|
||||
return;
|
||||
|
||||
default:
|
||||
console.warn( "Unknown board type for ATC board, got: " + board.dataset.boardType );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
startUpdates() {
|
||||
|
||||
this.#dataHandler.startUpdates();
|
||||
|
||||
}
|
||||
|
||||
|
||||
stopUpdates() {
|
||||
|
||||
this.#dataHandler.stopUpdates();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,527 +0,0 @@
|
||||
import { Dropdown } from "../controls/dropdown";
|
||||
import { zeroAppend } from "../other/utils";
|
||||
import { ATC } from "./atc";
|
||||
import { Unit } from "../unit/unit";
|
||||
import { getMissionHandler, getUnitsManager } from "..";
|
||||
import Sortable from "sortablejs";
|
||||
import { FlightInterface } from "./atc";
|
||||
import { getConnected } from "../server/server";
|
||||
|
||||
export interface StripBoardStripInterface {
|
||||
"id": string,
|
||||
"element": HTMLElement,
|
||||
"dropdowns": {[key:string]: Dropdown},
|
||||
"isDeleted"?: boolean,
|
||||
"unitId": number
|
||||
}
|
||||
|
||||
export abstract class ATCBoard {
|
||||
|
||||
#atc:ATC;
|
||||
#boardId:string = "";
|
||||
#templates: {[key:string]: string} = {};
|
||||
|
||||
|
||||
// Elements
|
||||
#boardElement:HTMLElement;
|
||||
#clockElement:HTMLElement;
|
||||
#stripBoardElement:HTMLElement;
|
||||
|
||||
// Content
|
||||
#isAddFlightByClickEnabled:boolean = false;
|
||||
#strips:{[key:string]: StripBoardStripInterface} = {};
|
||||
#unitIdsBeingMonitored:number[] = [];
|
||||
|
||||
// Update timing
|
||||
#updateInterval:number|undefined = undefined;
|
||||
#updateIntervalDelay:number = 1000;
|
||||
|
||||
|
||||
constructor( atc:ATC, boardElement:HTMLElement, options?:{[key:string]: any} ) {
|
||||
|
||||
options = options || {};
|
||||
|
||||
this.#atc = atc;
|
||||
this.#boardElement = boardElement;
|
||||
this.#stripBoardElement = <HTMLElement>this.getBoardElement().querySelector( ".ol-strip-board-strips" );
|
||||
this.#clockElement = <HTMLElement>this.getBoardElement().querySelector( ".ol-strip-board-clock" );
|
||||
|
||||
|
||||
new MutationObserver( () => {
|
||||
if ( this.boardIsVisible() ) {
|
||||
this.startUpdates();
|
||||
} else {
|
||||
this.stopUpdates();
|
||||
}
|
||||
}).observe( this.getBoardElement(), {
|
||||
"attributes": true,
|
||||
"childList": false,
|
||||
"subtree": false
|
||||
});
|
||||
|
||||
|
||||
new Sortable( this.getStripBoardElement(), {
|
||||
"handle": ".handle",
|
||||
"onUpdate": ev => {
|
||||
|
||||
const order = [].slice.call( this.getStripBoardElement().children ).map( ( strip:HTMLElement ) => {
|
||||
return strip.dataset.flightId
|
||||
});
|
||||
|
||||
fetch( '/api/atc/flight/order', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': '*/*',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
"body": JSON.stringify({
|
||||
"boardId" : this.getBoardId(),
|
||||
"order" : order
|
||||
})
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
window.setInterval( () => {
|
||||
|
||||
if ( !getConnected() ) {
|
||||
return;
|
||||
}
|
||||
this.updateClock();
|
||||
}, 1000 );
|
||||
|
||||
|
||||
if ( this.#boardElement.classList.contains( "ol-draggable" ) ) {
|
||||
|
||||
let options:any = {};
|
||||
|
||||
let handle = this.#boardElement.querySelector( ".handle" );
|
||||
|
||||
if ( handle instanceof HTMLElement ) {
|
||||
options.handle = handle;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
this.#setupAddFlight();
|
||||
|
||||
// this.#_setupDemoData();
|
||||
|
||||
}
|
||||
|
||||
|
||||
addFlight( unit:Unit ) {
|
||||
|
||||
const baseData = unit.getData();
|
||||
|
||||
const unitCanBeAdded = () => {
|
||||
|
||||
if ( unit.getCategory() !== "Aircraft" ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( baseData.controlled === true ) {
|
||||
// return false;
|
||||
}
|
||||
|
||||
if ( this.#unitIdsBeingMonitored.includes( unit.ID ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( !unitCanBeAdded() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#unitIdsBeingMonitored.push( unit.ID );
|
||||
|
||||
return fetch( '/api/atc/flight/', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': '*/*',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
"body": JSON.stringify({
|
||||
"boardId" : this.getBoardId(),
|
||||
"name" : baseData.unitName,
|
||||
"unitId" : unit.ID
|
||||
})
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
addStrip( strip:StripBoardStripInterface ) {
|
||||
|
||||
this.#strips[ strip.id ] = strip;
|
||||
|
||||
strip.element.querySelectorAll( "button.deleteFlight" ).forEach( btn => {
|
||||
btn.addEventListener( "click", ev => {
|
||||
ev.preventDefault();
|
||||
this.deleteFlight( strip.id );
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
boardIsVisible() {
|
||||
return ( !this.getBoardElement().classList.contains( "hide" ) );
|
||||
}
|
||||
|
||||
|
||||
calculateTimeToGo( fromTimestamp:number, toTimestamp:number ) {
|
||||
|
||||
let timestamp = ( toTimestamp - fromTimestamp ) / 1000;
|
||||
|
||||
const hasElapsed = ( timestamp < 0 ) ? true : false;
|
||||
|
||||
if ( hasElapsed ) {
|
||||
timestamp = -( timestamp );
|
||||
}
|
||||
|
||||
const hours = ( timestamp < 3600 ) ? "00" : zeroAppend( Math.floor( timestamp / 3600 ), 2 );
|
||||
const rMinutes = timestamp % 3600;
|
||||
|
||||
const minutes = ( timestamp < 60 ) ? "00" : zeroAppend( Math.floor( rMinutes / 60 ), 2 );
|
||||
const seconds = zeroAppend( Math.floor( rMinutes % 60 ), 2 );
|
||||
|
||||
return {
|
||||
"elapsedMarker": ( hasElapsed ) ? "+" : "-",
|
||||
"hasElapsed": hasElapsed,
|
||||
"hours": hours,
|
||||
"minutes": minutes,
|
||||
"seconds": seconds,
|
||||
"time": `${hours}:${minutes}:${seconds}`,
|
||||
"totalSeconds": timestamp
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
deleteStrip( flightId:string ) {
|
||||
|
||||
if ( this.#strips.hasOwnProperty( flightId ) ) {
|
||||
|
||||
this.#strips[ flightId ].element.remove();
|
||||
this.#strips[ flightId ].isDeleted = true;
|
||||
|
||||
window.setTimeout( () => {
|
||||
delete this.#strips[ flightId ];
|
||||
}, 10000 );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
deleteFlight( flightId:string ) {
|
||||
|
||||
this.deleteStrip( flightId );
|
||||
|
||||
fetch( '/api/atc/flight/' + flightId, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Accept': '*/*',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
"body": JSON.stringify({
|
||||
"boardId": this.getBoardId()
|
||||
})
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
getATC() {
|
||||
return this.#atc;
|
||||
}
|
||||
|
||||
|
||||
getBoardElement() {
|
||||
return this.#boardElement;
|
||||
}
|
||||
|
||||
|
||||
getBoardId(): string {
|
||||
return this.getBoardElement().id;
|
||||
}
|
||||
|
||||
|
||||
getStripBoardElement() {
|
||||
return this.#stripBoardElement;
|
||||
}
|
||||
|
||||
|
||||
getStrips() {
|
||||
return this.#strips;
|
||||
}
|
||||
|
||||
|
||||
getStrip( id:string ) {
|
||||
return this.#strips[ id ] || false;
|
||||
}
|
||||
|
||||
|
||||
getTemplate( key:string ) {
|
||||
|
||||
return this.#templates[ key ] || false;
|
||||
|
||||
}
|
||||
|
||||
|
||||
getUnitIdsBeingMonitored() {
|
||||
|
||||
return this.#unitIdsBeingMonitored;
|
||||
|
||||
}
|
||||
|
||||
|
||||
setTemplates( templates:{[key:string]: string} ) {
|
||||
this.#templates = templates;
|
||||
}
|
||||
|
||||
|
||||
#setupAddFlight() {
|
||||
|
||||
const toggleIsAddFlightByClickEnabled = () => {
|
||||
this.#isAddFlightByClickEnabled = ( !this.#isAddFlightByClickEnabled );
|
||||
this.getBoardElement().classList.toggle( "add-flight-by-click", this.#isAddFlightByClickEnabled );
|
||||
}
|
||||
|
||||
|
||||
document.addEventListener( "unitSelection", ( ev:CustomEventInit ) => {
|
||||
|
||||
if ( this.#isAddFlightByClickEnabled !== true ) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.addFlight( ev.detail );
|
||||
|
||||
toggleIsAddFlightByClickEnabled();
|
||||
|
||||
});
|
||||
|
||||
|
||||
const form = <HTMLElement>this.getBoardElement().querySelector( "form.ol-strip-board-add-flight" );
|
||||
const suggestions = <HTMLElement>form.querySelector( ".ol-auto-suggest" );
|
||||
const unitName = <HTMLInputElement>form.querySelector( "input[name='unitName']" );
|
||||
|
||||
const toggleSuggestions = ( bool:boolean ) => {
|
||||
suggestions.toggleAttribute( "data-has-suggestions", bool );
|
||||
}
|
||||
|
||||
let searchTimeout:number|null;
|
||||
|
||||
unitName.addEventListener( "keyup", ev => {
|
||||
|
||||
if ( searchTimeout ) {
|
||||
clearTimeout( searchTimeout );
|
||||
}
|
||||
|
||||
const resetSuggestions = () => {
|
||||
suggestions.innerHTML = "";
|
||||
toggleSuggestions( false );
|
||||
}
|
||||
|
||||
resetSuggestions();
|
||||
|
||||
searchTimeout = window.setTimeout( () => {
|
||||
|
||||
const searchString = unitName.value.toLowerCase();
|
||||
|
||||
if ( searchString === "" ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const units = getUnitsManager().getSelectableAircraft();
|
||||
const unitIdsBeingMonitored = this.getUnitIdsBeingMonitored();
|
||||
|
||||
const results = Object.keys( units ).reduce( ( acc:Unit[], unitId:any ) => {
|
||||
|
||||
const unit = units[ unitId ];
|
||||
const baseData = unit.getData();
|
||||
|
||||
if ( !unitIdsBeingMonitored.includes( parseInt( unitId ) ) && baseData.unitName.toLowerCase().indexOf( searchString ) > -1 ) {
|
||||
acc.push( unit );
|
||||
}
|
||||
|
||||
return acc;
|
||||
|
||||
}, [] );
|
||||
|
||||
toggleSuggestions( results.length > 0 );
|
||||
|
||||
results.forEach( unit => {
|
||||
|
||||
const baseData = unit.getData();
|
||||
|
||||
const a = document.createElement( "a" );
|
||||
a.innerText = baseData.unitName;
|
||||
|
||||
a.addEventListener( "click", ev => {
|
||||
this.addFlight( unit );
|
||||
resetSuggestions();
|
||||
unitName.value = "";
|
||||
});
|
||||
|
||||
suggestions.appendChild( a );
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
}, 1000 );
|
||||
|
||||
|
||||
});
|
||||
|
||||
form.querySelectorAll( ".add-flight-by-click" ).forEach( el => {
|
||||
el.addEventListener( "click", ev => {
|
||||
ev.preventDefault();
|
||||
toggleIsAddFlightByClickEnabled();
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
sortFlights( flights:FlightInterface[] ) {
|
||||
|
||||
flights.sort( ( a, b ) => {
|
||||
|
||||
const aVal = a.order;
|
||||
const bVal = b.order;
|
||||
|
||||
return ( aVal > bVal ) ? 1 : -1;
|
||||
|
||||
});
|
||||
|
||||
return flights;
|
||||
|
||||
}
|
||||
|
||||
|
||||
startUpdates() {
|
||||
|
||||
if ( !this.boardIsVisible() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#updateInterval = window.setInterval( () => {
|
||||
|
||||
if ( !getConnected() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.update();
|
||||
|
||||
}, this.#updateIntervalDelay );
|
||||
|
||||
}
|
||||
|
||||
|
||||
stopUpdates() {
|
||||
|
||||
clearInterval( this.#updateInterval );
|
||||
|
||||
}
|
||||
|
||||
|
||||
timestampToLocaleTime( timestamp:number ) {
|
||||
|
||||
return ( timestamp === -1 ) ? "-" : new Date( timestamp ).toLocaleTimeString();
|
||||
|
||||
}
|
||||
|
||||
|
||||
timeToGo( timestamp:number ) {
|
||||
|
||||
const timeData = this.calculateTimeToGo( this.getATC().getMissionDate().getTime(), timestamp );
|
||||
|
||||
return ( timestamp === -1 ) ? "-" : timeData.elapsedMarker + timeData.time;
|
||||
|
||||
}
|
||||
|
||||
protected update() {
|
||||
console.warn( "No custom update method defined." );
|
||||
}
|
||||
|
||||
|
||||
updateClock() {
|
||||
|
||||
const missionTime = this.#atc.getMissionDate().getTime();
|
||||
const timeDiff = new Date().getTime() - getMissionHandler().getDateAndTime().elapsedTime;
|
||||
|
||||
const nowDate = new Date( missionTime + timeDiff );
|
||||
|
||||
this.#clockElement.innerText = nowDate.toLocaleTimeString();
|
||||
|
||||
}
|
||||
|
||||
|
||||
updateFlight( flightId:string, reqBody:object ) {
|
||||
|
||||
return fetch( '/api/atc/flight/' + flightId, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Accept': '*/*',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
"body": JSON.stringify( reqBody )
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
#_setupDemoData() {
|
||||
|
||||
fetch( '/api/atc/flight/', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': '*/*',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
"body": JSON.stringify({
|
||||
"boardId" : this.getBoardId(),
|
||||
"name" : this.getBoardId() + " 1",
|
||||
"unitId" : 1
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
// fetch( '/api/atc/flight/', {
|
||||
// method: 'POST',
|
||||
// headers: {
|
||||
// 'Accept': '*/*',
|
||||
// 'Content-Type': 'application/json'
|
||||
// },
|
||||
// "body": JSON.stringify({
|
||||
// "boardId" : this.getBoardId(),
|
||||
// "name" : this.getBoardId() + " 2",
|
||||
// "unitId" : 2
|
||||
// })
|
||||
// });
|
||||
|
||||
// fetch( '/api/atc/flight/', {
|
||||
// method: 'POST',
|
||||
// headers: {
|
||||
// 'Accept': '*/*',
|
||||
// 'Content-Type': 'application/json'
|
||||
// },
|
||||
// "body": JSON.stringify({
|
||||
// "boardId" : this.getBoardId(),
|
||||
// "name" : this.getBoardId() + " 3",
|
||||
// "unitId" : 9
|
||||
// })
|
||||
// });
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -1,200 +0,0 @@
|
||||
import { Dropdown } from "../../controls/dropdown";
|
||||
import { ATC } from "../atc";
|
||||
import { ATCBoard } from "../atcboard";
|
||||
|
||||
|
||||
export class ATCBoardGround extends ATCBoard {
|
||||
|
||||
constructor( atc:ATC, element:HTMLElement ) {
|
||||
|
||||
super( atc, element );
|
||||
|
||||
}
|
||||
|
||||
|
||||
update() {
|
||||
|
||||
const flights = this.sortFlights( Object.values( this.getATC().getDataHandler().getFlights( this.getBoardId() ) ) );
|
||||
const stripBoard = this.getStripBoardElement();
|
||||
|
||||
const missionTime = this.getATC().getMissionDate().getTime();
|
||||
|
||||
for( const strip of stripBoard.children ) {
|
||||
strip.toggleAttribute( "data-updating", true );
|
||||
}
|
||||
|
||||
|
||||
flights.forEach( flight => {
|
||||
|
||||
let strip = this.getStrip( flight.id );
|
||||
|
||||
if ( !strip ) {
|
||||
|
||||
const template = `<div class="ol-strip-board-strip" data-flight-id="${flight.id}" data-flight-status="${flight.status}">
|
||||
<div class="handle"></div>
|
||||
<div data-point="name">${flight.name}</div>
|
||||
|
||||
<div id="flight-status-${flight.id}" class="ol-select narrow" data-point="status">
|
||||
<div class="ol-select-value">${flight.status}</div>
|
||||
<div class="ol-select-options"></div>
|
||||
</div>
|
||||
|
||||
<div data-point="takeoffTime"><input type="text" name="takeoffTime" value="${this.timestampToLocaleTime( flight.takeoffTime )}" /></div>
|
||||
|
||||
<div data-point="timeToGo">${this.timeToGo( flight.takeoffTime )}</div>
|
||||
|
||||
<button class="deleteFlight">×</button>
|
||||
</div>`;
|
||||
|
||||
stripBoard.insertAdjacentHTML( "beforeend", template );
|
||||
|
||||
|
||||
strip = {
|
||||
"id": flight.id,
|
||||
"element": <HTMLElement>stripBoard.lastElementChild,
|
||||
"dropdowns": {},
|
||||
"unitId": -1
|
||||
};
|
||||
|
||||
strip.element.querySelectorAll( ".ol-select" ).forEach( select => {
|
||||
|
||||
switch( select.getAttribute( "data-point" ) ) {
|
||||
|
||||
case "status":
|
||||
|
||||
strip.dropdowns.status = new Dropdown( select.id, ( value:string, ev:MouseEvent ) => {
|
||||
|
||||
fetch( '/api/atc/flight/' + flight.id, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Accept': '*/*',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
"body": JSON.stringify({
|
||||
"status": value
|
||||
})
|
||||
});
|
||||
|
||||
}, [
|
||||
"unknown", "checkedin", "readytotaxi", "clearedtotaxi", "halted", "terminated"
|
||||
]);
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
strip.element.querySelectorAll( `input[type="text"]` ).forEach( input => {
|
||||
|
||||
if ( input instanceof HTMLInputElement ) {
|
||||
|
||||
input.addEventListener( "blur", ( ev ) => {
|
||||
|
||||
const target = ev.target;
|
||||
|
||||
if ( target instanceof HTMLInputElement ) {
|
||||
|
||||
if ( /^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$/.test( target.value ) ) {
|
||||
target.value += ":00";
|
||||
}
|
||||
|
||||
const value = target.value;
|
||||
|
||||
if ( value === target.dataset.previousValue ) {
|
||||
return;
|
||||
|
||||
} else if ( value === "" ) {
|
||||
|
||||
this.#updateTakeoffTime( flight.id, -1 );
|
||||
|
||||
} else if ( /^([0-1]?[0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]$/.test( value ) ) {
|
||||
|
||||
let [ hours, minutes, seconds ] = value.split( ":" ).map( str => parseInt( str ) );
|
||||
|
||||
const missionStart = this.getATC().getMissionStartDateTime();
|
||||
|
||||
this.#updateTakeoffTime( flight.id, new Date(
|
||||
missionStart.getFullYear(),
|
||||
missionStart.getMonth(),
|
||||
missionStart.getDate(),
|
||||
hours,
|
||||
minutes,
|
||||
seconds
|
||||
).getTime() );
|
||||
|
||||
} else {
|
||||
|
||||
target.value === target.dataset.previousValue
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
this.addStrip( strip );
|
||||
|
||||
} else {
|
||||
|
||||
if ( flight.status !== strip.element.getAttribute( "data-flight-status" ) ) {
|
||||
strip.element.setAttribute( "data-flight-status", flight.status );
|
||||
strip.dropdowns.status.selectText( flight.status );
|
||||
}
|
||||
|
||||
strip.element.querySelectorAll( `input[name="takeoffTime"]:not(:focus)` ).forEach( el => {
|
||||
if ( el instanceof HTMLInputElement ) {
|
||||
el.value = this.timestampToLocaleTime( flight.takeoffTime );
|
||||
el.dataset.previousValue = el.value;
|
||||
}
|
||||
});
|
||||
|
||||
strip.element.querySelectorAll( `[data-point="timeToGo"]` ).forEach( el => {
|
||||
|
||||
if ( flight.takeoffTime > 0 && this.calculateTimeToGo( missionTime, flight.takeoffTime ).totalSeconds <= 120 ) {
|
||||
strip.element.setAttribute( "data-time-warning", "level-1" );
|
||||
}
|
||||
|
||||
if ( el instanceof HTMLElement ) {
|
||||
el.innerText = this.timeToGo( flight.takeoffTime );
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
strip.element.toggleAttribute( "data-updating", false );
|
||||
|
||||
});
|
||||
|
||||
|
||||
stripBoard.querySelectorAll( `[data-updating]` ).forEach( strip => {
|
||||
this.deleteStrip( strip.getAttribute( "data-flight-id" ) || "" );
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
#updateTakeoffTime = function( flightId:string, time:number ) {
|
||||
|
||||
fetch( '/api/atc/flight/' + flightId, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Accept': '*/*',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
"body": JSON.stringify({
|
||||
"takeoffTime": time
|
||||
})
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,194 +0,0 @@
|
||||
import { getUnitsManager } from "../..";
|
||||
import { Dropdown } from "../../controls/dropdown";
|
||||
import { mToFt, msToKnots } from "../../other/utils";
|
||||
import { ATC } from "../atc";
|
||||
import { ATCBoard } from "../atcboard";
|
||||
|
||||
|
||||
export class ATCBoardTower extends ATCBoard {
|
||||
|
||||
constructor( atc:ATC, element:HTMLElement ) {
|
||||
|
||||
super( atc, element );
|
||||
|
||||
}
|
||||
|
||||
|
||||
update() {
|
||||
|
||||
const flights = this.sortFlights( Object.values( this.getATC().getDataHandler().getFlights( this.getBoardId() ) ) );
|
||||
const missionTime = this.getATC().getMissionDate().getTime();
|
||||
const selectableUnits = getUnitsManager().getSelectableAircraft();
|
||||
const stripBoard = this.getStripBoardElement();
|
||||
|
||||
for( const strip of stripBoard.children ) {
|
||||
strip.toggleAttribute( "data-updating", true );
|
||||
}
|
||||
|
||||
|
||||
flights.forEach( flight => {
|
||||
|
||||
let strip = this.getStrip( flight.id );
|
||||
|
||||
if ( strip.isDeleted === true ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const flightData = {
|
||||
latitude: -1,
|
||||
longitude: -1,
|
||||
altitude: -1,
|
||||
heading: -1,
|
||||
speed: -1,
|
||||
...( selectableUnits.hasOwnProperty( flight.unitId ) ? selectableUnits[flight.unitId].getData() : {} )
|
||||
};
|
||||
|
||||
if ( !strip ) {
|
||||
|
||||
const template = `<div class="ol-strip-board-strip" data-flight-id="${flight.id}" data-flight-status="${flight.status}">
|
||||
<div class="handle"></div>
|
||||
<div data-point="name"><a href="#" class="select-unit">${flight.name}</a></div>
|
||||
|
||||
<div data-point="assignedAltitude"><input type="text" name="assignedAltitude" value="${flight.assignedAltitude}" size="2" /> 000</div>
|
||||
<div data-point="altitude">-</div>
|
||||
|
||||
<div data-point="assignedSpeed"><input type="text" name="assignedSpeed" value="${flight.assignedSpeed}" size="3" /></div>
|
||||
<div data-point="speed">-</div>
|
||||
|
||||
<button class="deleteFlight">×</button>
|
||||
</div>`;
|
||||
|
||||
stripBoard.insertAdjacentHTML( "beforeend", template );
|
||||
|
||||
|
||||
strip = {
|
||||
"id": flight.id,
|
||||
"element": <HTMLElement>stripBoard.lastElementChild,
|
||||
"dropdowns": {},
|
||||
"unitId": flight.unitId
|
||||
};
|
||||
|
||||
|
||||
strip.element.querySelectorAll( `input[type="text"]` ).forEach( input => {
|
||||
|
||||
if ( input instanceof HTMLInputElement ) {
|
||||
|
||||
switch ( input.name ) {
|
||||
|
||||
case "assignedAltitude":
|
||||
|
||||
input.addEventListener( "change", ( ev ) => {
|
||||
|
||||
let val = parseInt( input.value.replace( /[^\d]/g, "" ) );
|
||||
|
||||
if ( isNaN( val ) || val < 0 || val > 40 ) {
|
||||
val = 0;
|
||||
}
|
||||
|
||||
this.updateFlight( flight.id, {
|
||||
"assignedAltitude": val
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
break;
|
||||
|
||||
case "assignedSpeed":
|
||||
|
||||
input.addEventListener( "change", ( ev ) => {
|
||||
|
||||
let val = parseInt( input.value.replace( /[^\d]/g, "" ) );
|
||||
|
||||
if ( isNaN( val ) || val < 0 || val > 750 ) {
|
||||
val = 0;
|
||||
}
|
||||
|
||||
this.updateFlight( flight.id, {
|
||||
"assignedSpeed": val
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
strip.element.querySelectorAll( ".select-unit" ).forEach( el => {
|
||||
|
||||
el.addEventListener( "click", ev => {
|
||||
ev.preventDefault();
|
||||
getUnitsManager().selectUnit( flight.unitId );
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
this.addStrip( strip );
|
||||
|
||||
} else {
|
||||
|
||||
//
|
||||
// Altitude
|
||||
//
|
||||
|
||||
let assignedAltitude = <HTMLInputElement>strip.element.querySelector( `input[name="assignedAltitude"]`);
|
||||
|
||||
if ( !assignedAltitude.matches( ":focus" ) && assignedAltitude.value !== flight.assignedAltitude ) {
|
||||
assignedAltitude.value = flight.assignedAltitude;
|
||||
}
|
||||
|
||||
flightData.altitude = Math.floor( mToFt(flightData.altitude) );
|
||||
|
||||
strip.element.querySelectorAll( `[data-point="altitude"]` ).forEach( el => {
|
||||
if ( el instanceof HTMLElement ) {
|
||||
el.innerText = "" + flightData.altitude;
|
||||
}
|
||||
});
|
||||
|
||||
const altitudeDelta = ( flight.assignedAltitude === 0 ) ? 0 : ( flight.assignedAltitude * 1000 ) - flightData.altitude;
|
||||
|
||||
strip.element.toggleAttribute( "data-altitude-assigned", ( flight.assignedAltitude > 0 ) );
|
||||
strip.element.toggleAttribute( "data-warning-altitude", ( altitudeDelta >= 300 || altitudeDelta <= -300 ) );
|
||||
|
||||
|
||||
//
|
||||
// Speed
|
||||
//
|
||||
|
||||
let assignedSpeed = <HTMLInputElement>strip.element.querySelector( `input[name="assignedSpeed"]`);
|
||||
|
||||
if ( !assignedSpeed.matches( ":focus" ) && assignedSpeed.value !== flight.assignedSpeed ) {
|
||||
assignedSpeed.value = flight.assignedSpeed;
|
||||
}
|
||||
|
||||
flightData.speed = Math.floor( msToKnots(flightData.speed) );
|
||||
|
||||
strip.element.querySelectorAll( `[data-point="speed"]` ).forEach( el => {
|
||||
if ( el instanceof HTMLElement ) {
|
||||
el.innerText = "" + flightData.speed;
|
||||
}
|
||||
});
|
||||
|
||||
const speedDelta = ( flight.assignedSpeed === 0 ) ? 0 : flight.assignedSpeed - flightData.speed;
|
||||
|
||||
strip.element.toggleAttribute( "data-speed-assigned", ( flight.assignedSpeed > 0 ) );
|
||||
strip.element.toggleAttribute( "data-warning-speed", ( speedDelta >= 25 || speedDelta <= -25 ) );
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
strip.element.toggleAttribute( "data-updating", false );
|
||||
|
||||
});
|
||||
|
||||
stripBoard.querySelectorAll( `[data-updating]` ).forEach( strip => {
|
||||
this.deleteStrip( strip.getAttribute( "data-flight-id" ) || "" );
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,61 +0,0 @@
|
||||
import { getUnitsManager } from "..";
|
||||
import { Panel } from "../panels/panel";
|
||||
import { Unit } from "../unit/unit";
|
||||
|
||||
export class UnitDataTable extends Panel {
|
||||
/**
|
||||
*
|
||||
* @param ID - the ID of the HTML element which will contain the context menu
|
||||
*/
|
||||
constructor(ID: string){
|
||||
super(ID);
|
||||
this.hide();
|
||||
}
|
||||
|
||||
update() {
|
||||
var units = getUnitsManager().getUnits();
|
||||
|
||||
const unitsArray = Object.values(units).sort((a: Unit, b: Unit) => {
|
||||
const aVal = a.getUnitName()?.toLowerCase();
|
||||
const bVal = b.getUnitName()?.toLowerCase();
|
||||
|
||||
if (aVal > bVal) {
|
||||
return 1;
|
||||
} else if (bVal > aVal) {
|
||||
return -1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function addRow(parentEl: HTMLElement, columns: string[]) {
|
||||
const rowDiv = document.createElement("div");
|
||||
|
||||
for (const item of columns) {
|
||||
|
||||
const div = document.createElement("div");
|
||||
div.innerText = item;
|
||||
rowDiv.appendChild(div);
|
||||
|
||||
}
|
||||
parentEl.appendChild(rowDiv);
|
||||
}
|
||||
|
||||
const el = <HTMLElement> this.getElement().querySelector("#unit-list");
|
||||
|
||||
if (el) {
|
||||
|
||||
el.innerHTML = "";
|
||||
|
||||
addRow(el, ["Callsign", "Name", "Category", "AI/Human"])
|
||||
|
||||
for (const unit of unitsArray) {
|
||||
|
||||
const dataset = [unit.getUnitName(), unit.getName(), unit.getCategory(), (unit.getControlled()) ? "AI" : "Human"];
|
||||
|
||||
addRow(el, dataset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -35,6 +35,9 @@ export class AirbaseSpawnContextMenu extends ContextMenu {
|
||||
this.#aircraftSpawnMenu.getContainer().addEventListener("resize", () => this.clip());
|
||||
this.#helicopterSpawnMenu.getContainer().addEventListener("resize", () => this.clip());
|
||||
|
||||
this.#aircraftSpawnMenu.getContainer().addEventListener("hide", () => this.hide());
|
||||
this.#helicopterSpawnMenu.getContainer().addEventListener("hide", () => this.hide());
|
||||
|
||||
this.hide();
|
||||
}
|
||||
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import { LatLng } from "leaflet";
|
||||
import { getMap, getMissionHandler, getUnitsManager } from "..";
|
||||
import { GAME_MASTER, IADSTypes } from "../constants/constants";
|
||||
import { CoalitionArea } from "../map/coalitionarea";
|
||||
import { CoalitionArea } from "../map/coalitionarea/coalitionarea";
|
||||
import { ContextMenu } from "./contextmenu";
|
||||
import { Dropdown } from "../controls/dropdown";
|
||||
import { Slider } from "../controls/slider";
|
||||
import { Switch } from "../controls/switch";
|
||||
import { groundUnitDatabase } from "../unit/groundunitdatabase";
|
||||
import { groundUnitDatabase } from "../unit/databases/groundunitdatabase";
|
||||
import { createCheckboxOption, getCheckboxOptions } from "../other/utils";
|
||||
|
||||
/** This context menu allows the user to edit or delete a CoalitionArea. Moreover, it allows the user to create a IADS automatically using the CoalitionArea as bounds. */
|
||||
|
||||
@ -4,10 +4,10 @@ import { spawnExplosion, spawnSmoke } from "../server/server";
|
||||
import { ContextMenu } from "./contextmenu";
|
||||
import { Switch } from "../controls/switch";
|
||||
import { GAME_MASTER } from "../constants/constants";
|
||||
import { CoalitionArea } from "../map/coalitionarea";
|
||||
import { CoalitionArea } from "../map/coalitionarea/coalitionarea";
|
||||
import { AircraftSpawnMenu, GroundUnitSpawnMenu, HelicopterSpawnMenu, NavyUnitSpawnMenu } from "../controls/unitspawnmenu";
|
||||
import { Airbase } from "../mission/airbase";
|
||||
import { SmokeMarker } from "../map/smokemarker";
|
||||
import { SmokeMarker } from "../map/markers/smokemarker";
|
||||
|
||||
/** The MapContextMenu is the main contextmenu shown to the user whenever it rightclicks on the map. It is the primary interaction method for the user.
|
||||
* It allows to spawn units, create explosions and smoke, and edit CoalitionAreas.
|
||||
@ -75,6 +75,11 @@ export class MapContextMenu extends ContextMenu {
|
||||
this.#groundUnitSpawnMenu.getContainer().addEventListener("resize", () => this.clip());
|
||||
this.#navyUnitSpawnMenu.getContainer().addEventListener("resize", () => this.clip());
|
||||
|
||||
this.#aircraftSpawnMenu.getContainer().addEventListener("hide", () => this.hide());
|
||||
this.#helicopterSpawnMenu.getContainer().addEventListener("hide", () => this.hide());
|
||||
this.#groundUnitSpawnMenu.getContainer().addEventListener("hide", () => this.hide());
|
||||
this.#navyUnitSpawnMenu.getContainer().addEventListener("hide", () => this.hide());
|
||||
|
||||
this.hide();
|
||||
}
|
||||
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
import { LatLng } from "leaflet";
|
||||
import { Dropdown } from "./dropdown";
|
||||
import { Slider } from "./slider";
|
||||
import { UnitDatabase } from "../unit/unitdatabase";
|
||||
import { UnitDatabase } from "../unit/databases/unitdatabase";
|
||||
import { getActiveCoalition, getMap, getMissionHandler, getUnitsManager } from "..";
|
||||
import { GAME_MASTER } from "../constants/constants";
|
||||
import { UnitSpawnOptions } from "../@types/unitdatabase";
|
||||
import { Airbase } from "../mission/airbase";
|
||||
import { ftToM } from "../other/utils";
|
||||
import { aircraftDatabase } from "../unit/aircraftdatabase";
|
||||
import { helicopterDatabase } from "../unit/helicopterdatabase";
|
||||
import { groundUnitDatabase } from "../unit/groundunitdatabase";
|
||||
import { navyUnitDatabase } from "../unit/navyunitdatabase";
|
||||
import { aircraftDatabase } from "../unit/databases/aircraftdatabase";
|
||||
import { helicopterDatabase } from "../unit/databases/helicopterdatabase";
|
||||
import { groundUnitDatabase } from "../unit/databases/groundunitdatabase";
|
||||
import { navyUnitDatabase } from "../unit/databases/navyunitdatabase";
|
||||
|
||||
export class UnitSpawnMenu {
|
||||
#container: HTMLElement;
|
||||
@ -418,7 +418,7 @@ export class AircraftSpawnMenu extends UnitSpawnMenu {
|
||||
getMap().addTemporaryMarker(spawnOptions.latlng, spawnOptions.name, getActiveCoalition(), res.commandHash);
|
||||
});
|
||||
|
||||
getMap().getMapContextMenu().hide();
|
||||
this.getContainer().dispatchEvent(new Event("hide"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -456,7 +456,7 @@ export class HelicopterSpawnMenu extends UnitSpawnMenu {
|
||||
getMap().addTemporaryMarker(spawnOptions.latlng, spawnOptions.name, getActiveCoalition(), res.commandHash);
|
||||
});
|
||||
|
||||
getMap().getMapContextMenu().hide();
|
||||
this.getContainer().dispatchEvent(new Event("hide"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -493,7 +493,7 @@ export class GroundUnitSpawnMenu extends UnitSpawnMenu {
|
||||
getMap().addTemporaryMarker(spawnOptions.latlng, spawnOptions.name, getActiveCoalition(), res.commandHash);
|
||||
});
|
||||
|
||||
getMap().getMapContextMenu().hide();
|
||||
this.getContainer().dispatchEvent(new Event("hide"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -530,7 +530,7 @@ export class NavyUnitSpawnMenu extends UnitSpawnMenu {
|
||||
getMap().addTemporaryMarker(spawnOptions.latlng, spawnOptions.name, getActiveCoalition(), res.commandHash);
|
||||
});
|
||||
|
||||
getMap().getMapContextMenu().hide();
|
||||
this.getContainer().dispatchEvent(new Event("hide"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -15,6 +15,8 @@ import { BLUE_COMMANDER, GAME_MASTER, RED_COMMANDER } from "./constants/constant
|
||||
import { ServerStatusPanel } from "./panels/serverstatuspanel";
|
||||
import { WeaponsManager } from "./weapon/weaponsmanager";
|
||||
import { ConfigParameters } from "./@types/dom";
|
||||
import { CommandModeToolbar } from "./toolbars/commandmodetoolbar";
|
||||
import { PrimaryToolbar } from "./toolbars/primaryToolbar";
|
||||
|
||||
/* Global data */
|
||||
var activeCoalition: string = "blue";
|
||||
@ -36,10 +38,15 @@ var mouseInfoPanel: MouseInfoPanel;
|
||||
var logPanel: LogPanel;
|
||||
var hotgroupPanel: HotgroupPanel;
|
||||
|
||||
/* UI Toolbars */
|
||||
var primaryToolbar: PrimaryToolbar;
|
||||
var commandModeToolbar: CommandModeToolbar;
|
||||
|
||||
/* Popups */
|
||||
var infoPopup: Popup;
|
||||
|
||||
function setup() {
|
||||
|
||||
/* Initialize base functionalitites */
|
||||
map = new Map('map-container');
|
||||
|
||||
@ -56,6 +63,10 @@ function setup() {
|
||||
hotgroupPanel = new HotgroupPanel("hotgroup-panel");
|
||||
logPanel = new LogPanel("log-panel");
|
||||
|
||||
/* Toolbars */
|
||||
primaryToolbar = new PrimaryToolbar("primary-toolbar");
|
||||
commandModeToolbar = new CommandModeToolbar("command-mode-toolbar");
|
||||
|
||||
/* Popups */
|
||||
infoPopup = new Popup("info-popup");
|
||||
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { DomUtil, LatLng, LatLngExpression, Map, Point, Polygon, PolylineOptions } from "leaflet";
|
||||
import { getMap, getMissionHandler, getUnitsManager } from "..";
|
||||
import { getMap, getMissionHandler, getUnitsManager } from "../..";
|
||||
import { CoalitionAreaHandle } from "./coalitionareahandle";
|
||||
import { CoalitionAreaMiddleHandle } from "./coalitionareamiddlehandle";
|
||||
import { BLUE_COMMANDER, RED_COMMANDER } from "../constants/constants";
|
||||
import { BLUE_COMMANDER, RED_COMMANDER } from "../../constants/constants";
|
||||
|
||||
export class CoalitionArea extends Polygon {
|
||||
#coalition: string = "blue";
|
||||
@ -1,5 +1,5 @@
|
||||
import { DivIcon, LatLng } from "leaflet";
|
||||
import { CustomMarker } from "./custommarker";
|
||||
import { CustomMarker } from "../markers/custommarker";
|
||||
|
||||
export class CoalitionAreaHandle extends CustomMarker {
|
||||
constructor(latlng: LatLng) {
|
||||
@ -1,5 +1,5 @@
|
||||
import { DivIcon, LatLng } from "leaflet";
|
||||
import { CustomMarker } from "./custommarker";
|
||||
import { CustomMarker } from "../markers/custommarker";
|
||||
|
||||
export class CoalitionAreaMiddleHandle extends CustomMarker {
|
||||
constructor(latlng: LatLng) {
|
||||
@ -1,5 +1,5 @@
|
||||
import { DivIcon, LatLng } from "leaflet";
|
||||
import { CustomMarker } from "./custommarker";
|
||||
import { CustomMarker } from "../markers/custommarker";
|
||||
|
||||
export class DrawingCursor extends CustomMarker {
|
||||
constructor() {
|
||||
@ -8,15 +8,15 @@ import { Dropdown } from "../controls/dropdown";
|
||||
import { Airbase } from "../mission/airbase";
|
||||
import { Unit } from "../unit/unit";
|
||||
import { bearing, createCheckboxOption } from "../other/utils";
|
||||
import { DestinationPreviewMarker } from "./destinationpreviewmarker";
|
||||
import { TemporaryUnitMarker } from "./temporaryunitmarker";
|
||||
import { DestinationPreviewMarker } from "./markers/destinationpreviewmarker";
|
||||
import { TemporaryUnitMarker } from "./markers/temporaryunitmarker";
|
||||
import { ClickableMiniMap } from "./clickableminimap";
|
||||
import { SVGInjector } from '@tanem/svg-injector'
|
||||
import { layers as mapLayers, mapBounds, minimapBoundaries, IDLE, COALITIONAREA_DRAW_POLYGON, visibilityControls, visibilityControlsTooltips, MOVE_UNIT, SHOW_CONTACT_LINES, HIDE_GROUP_MEMBERS, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, visibilityControlsTypes, SHOW_UNIT_LABELS } from "../constants/constants";
|
||||
import { TargetMarker } from "./targetmarker";
|
||||
import { CoalitionArea } from "./coalitionarea";
|
||||
import { TargetMarker } from "./markers/targetmarker";
|
||||
import { CoalitionArea } from "./coalitionarea/coalitionarea";
|
||||
import { CoalitionAreaContextMenu } from "../contextmenus/coalitionareacontextmenu";
|
||||
import { DrawingCursor } from "./drawingcursor";
|
||||
import { DrawingCursor } from "./coalitionarea/drawingcursor";
|
||||
import { AirbaseSpawnContextMenu } from "../contextmenus/airbasespawnmenu";
|
||||
|
||||
L.Map.addInitHook('addHandler', 'boxSelect', BoxSelect);
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { DivIcon, LatLngExpression, MarkerOptions } from "leaflet";
|
||||
import { CustomMarker } from "./custommarker";
|
||||
import { SVGInjector } from "@tanem/svg-injector";
|
||||
import { getMap } from "..";
|
||||
import { getMap } from "../..";
|
||||
|
||||
export class SmokeMarker extends CustomMarker {
|
||||
#color: string;
|
||||
@ -1,9 +1,9 @@
|
||||
import { CustomMarker } from "./custommarker";
|
||||
import { DivIcon, LatLng } from "leaflet";
|
||||
import { SVGInjector } from "@tanem/svg-injector";
|
||||
import { getMarkerCategoryByName, getUnitDatabaseByCategory } from "../other/utils";
|
||||
import { isCommandExecuted } from "../server/server";
|
||||
import { getMap } from "..";
|
||||
import { getMarkerCategoryByName, getUnitDatabaseByCategory } from "../../other/utils";
|
||||
import { isCommandExecuted } from "../../server/server";
|
||||
import { getMap } from "../..";
|
||||
|
||||
export class TemporaryUnitMarker extends CustomMarker {
|
||||
#name: string;
|
||||
@ -1,5 +1,5 @@
|
||||
import { DivIcon } from 'leaflet';
|
||||
import { CustomMarker } from '../map/custommarker';
|
||||
import { CustomMarker } from '../map/markers/custommarker';
|
||||
import { SVGInjector } from '@tanem/svg-injector';
|
||||
|
||||
export interface AirbaseOptions {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { DivIcon } from "leaflet";
|
||||
import { CustomMarker } from "../map/custommarker";
|
||||
import { CustomMarker } from "../map/markers/custommarker";
|
||||
import { SVGInjector } from "@tanem/svg-injector";
|
||||
|
||||
export class Bullseye extends CustomMarker {
|
||||
|
||||
@ -5,11 +5,11 @@ import { Bullseye } from "./bullseye";
|
||||
import { BLUE_COMMANDER, GAME_MASTER, NONE, RED_COMMANDER } from "../constants/constants";
|
||||
import { refreshAll, setCommandModeOptions } from "../server/server";
|
||||
import { Dropdown } from "../controls/dropdown";
|
||||
import { groundUnitDatabase } from "../unit/groundunitdatabase";
|
||||
import { groundUnitDatabase } from "../unit/databases/groundunitdatabase";
|
||||
import { createCheckboxOption, getCheckboxOptions } from "../other/utils";
|
||||
import { aircraftDatabase } from "../unit/aircraftdatabase";
|
||||
import { helicopterDatabase } from "../unit/helicopterdatabase";
|
||||
import { navyUnitDatabase } from "../unit/navyunitdatabase";
|
||||
import { aircraftDatabase } from "../unit/databases/aircraftdatabase";
|
||||
import { helicopterDatabase } from "../unit/databases/helicopterdatabase";
|
||||
import { navyUnitDatabase } from "../unit/databases/navyunitdatabase";
|
||||
|
||||
export class MissionManager {
|
||||
#bullseyes: { [name: string]: Bullseye } = {};
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
import { LatLng, Point, Polygon } from "leaflet";
|
||||
import * as turf from "@turf/turf";
|
||||
import { UnitDatabase } from "../unit/unitdatabase";
|
||||
import { aircraftDatabase } from "../unit/aircraftdatabase";
|
||||
import { helicopterDatabase } from "../unit/helicopterdatabase";
|
||||
import { groundUnitDatabase } from "../unit/groundunitdatabase";
|
||||
import { UnitDatabase } from "../unit/databases/unitdatabase";
|
||||
import { aircraftDatabase } from "../unit/databases/aircraftdatabase";
|
||||
import { helicopterDatabase } from "../unit/databases/helicopterdatabase";
|
||||
import { groundUnitDatabase } from "../unit/databases/groundunitdatabase";
|
||||
import { Buffer } from "buffer";
|
||||
import { ROEs, emissionsCountermeasures, reactionsToThreat, states } from "../constants/constants";
|
||||
import { Dropdown } from "../controls/dropdown";
|
||||
import { UnitBlueprint } from "../@types/unitdatabase";
|
||||
import { navyUnitDatabase } from "../unit/navyunitdatabase";
|
||||
import { navyUnitDatabase } from "../unit/databases/navyunitdatabase";
|
||||
|
||||
export function bearing(lat1: number, lon1: number, lat2: number, lon2: number) {
|
||||
const φ1 = deg2rad(lat1); // φ, λ in radians
|
||||
|
||||
@ -19,6 +19,7 @@ export class LogPanel extends Panel {
|
||||
this.#open = !this.#open;
|
||||
this.#queuedMessages = 0;
|
||||
this.#updateHeader();
|
||||
this.#calculateHeight();
|
||||
|
||||
if (this.#scrolledDown)
|
||||
this.#scrollDown();
|
||||
@ -89,6 +90,9 @@ export class LogPanel extends Panel {
|
||||
|
||||
#calculateHeight() {
|
||||
const mouseInfoPanel = getMouseInfoPanel();
|
||||
this.getElement().style.height = `${mouseInfoPanel.getElement().offsetTop - this.getElement().offsetTop - 10}px`;
|
||||
if (this.#open)
|
||||
this.getElement().style.height = `${mouseInfoPanel.getElement().offsetTop - this.getElement().offsetTop - 10}px`;
|
||||
else
|
||||
this.getElement().style.height = "fit-content";
|
||||
}
|
||||
}
|
||||
@ -2,7 +2,7 @@ import { SVGInjector } from "@tanem/svg-injector";
|
||||
import { getUnitsManager } from "..";
|
||||
import { Dropdown } from "../controls/dropdown";
|
||||
import { Slider } from "../controls/slider";
|
||||
import { aircraftDatabase } from "../unit/aircraftdatabase";
|
||||
import { aircraftDatabase } from "../unit/databases/aircraftdatabase";
|
||||
import { Unit } from "../unit/unit";
|
||||
import { Panel } from "./panel";
|
||||
import { Switch } from "../controls/switch";
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Ammo } from "../@types/unit";
|
||||
import { aircraftDatabase } from "../unit/aircraftdatabase";
|
||||
import { aircraftDatabase } from "../unit/databases/aircraftdatabase";
|
||||
import { Unit } from "../unit/unit";
|
||||
import { Panel } from "./panel";
|
||||
|
||||
|
||||
5
client/src/toolbars/commandmodetoolbar.ts
Normal file
5
client/src/toolbars/commandmodetoolbar.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { Toolbar } from "./toolbar";
|
||||
|
||||
export class CommandModeToolbar extends Toolbar {
|
||||
// TODO move here all code about the command mode toolbar
|
||||
}
|
||||
13
client/src/toolbars/primarytoolbar.ts
Normal file
13
client/src/toolbars/primarytoolbar.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { Dropdown } from "../controls/dropdown";
|
||||
import { Toolbar } from "./toolbar";
|
||||
|
||||
export class PrimaryToolbar extends Toolbar {
|
||||
constructor(ID: string) {
|
||||
super(ID);
|
||||
|
||||
// TODO move here all code about primary toolbar
|
||||
|
||||
/* The content of the dropdown is entirely defined in the .ejs file */
|
||||
new Dropdown("app-icon", () => { });
|
||||
}
|
||||
}
|
||||
38
client/src/toolbars/toolbar.ts
Normal file
38
client/src/toolbars/toolbar.ts
Normal file
@ -0,0 +1,38 @@
|
||||
export class Toolbar {
|
||||
#element: HTMLElement
|
||||
#visible: boolean = true;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param ID - the ID of the HTML element which will contain the context menu
|
||||
*/
|
||||
constructor(ID: string){
|
||||
this.#element = document.getElementById(ID) as HTMLElement;
|
||||
}
|
||||
|
||||
show() {
|
||||
this.#element.classList.toggle("hide", false);
|
||||
this.#visible = true;
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.#element.classList.toggle("hide", true);
|
||||
this.#visible = false;
|
||||
}
|
||||
|
||||
toggle() {
|
||||
// Simple way to track if currently visible
|
||||
if (this.#visible)
|
||||
this.hide();
|
||||
else
|
||||
this.show();
|
||||
}
|
||||
|
||||
getElement() {
|
||||
return this.#element;
|
||||
}
|
||||
|
||||
getVisible(){
|
||||
return this.#visible;
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
import { getMissionHandler } from "..";
|
||||
import { GAME_MASTER } from "../constants/constants";
|
||||
import { getMissionHandler } from "../..";
|
||||
import { GAME_MASTER } from "../../constants/constants";
|
||||
import { UnitDatabase } from "./unitdatabase"
|
||||
|
||||
export class AircraftDatabase extends UnitDatabase {
|
||||
@ -1,5 +1,5 @@
|
||||
import { getMissionHandler} from "..";
|
||||
import { GAME_MASTER } from "../constants/constants";
|
||||
import { getMissionHandler } from "../..";
|
||||
import { GAME_MASTER } from "../../constants/constants";
|
||||
import { UnitDatabase } from "./unitdatabase"
|
||||
|
||||
export class GroundUnitDatabase extends UnitDatabase {
|
||||
@ -1,5 +1,5 @@
|
||||
import { getMissionHandler } from "..";
|
||||
import { GAME_MASTER } from "../constants/constants";
|
||||
import { getMissionHandler } from "../..";
|
||||
import { GAME_MASTER } from "../../constants/constants";
|
||||
import { UnitDatabase } from "./unitdatabase"
|
||||
|
||||
export class HelicopterDatabase extends UnitDatabase {
|
||||
@ -1,5 +1,5 @@
|
||||
import { getMissionHandler } from "..";
|
||||
import { GAME_MASTER } from "../constants/constants";
|
||||
import { getMissionHandler } from "../..";
|
||||
import { GAME_MASTER } from "../../constants/constants";
|
||||
import { UnitDatabase } from "./unitdatabase"
|
||||
|
||||
export class NavyUnitDatabase extends UnitDatabase {
|
||||
@ -1,7 +1,7 @@
|
||||
import { LatLng } from "leaflet";
|
||||
import { getMissionHandler, getUnitsManager } from "..";
|
||||
import { GAME_MASTER } from "../constants/constants";
|
||||
import { UnitBlueprint } from "../@types/unitdatabase";
|
||||
import { getMissionHandler, getUnitsManager } from "../..";
|
||||
import { GAME_MASTER } from "../../constants/constants";
|
||||
import { UnitBlueprint } from "../../@types/unitdatabase";
|
||||
|
||||
export class UnitDatabase {
|
||||
blueprints: { [key: string]: UnitBlueprint } = {};
|
||||
@ -2,15 +2,15 @@ import { Marker, LatLng, Polyline, Icon, DivIcon, CircleMarker, Map, Point } fro
|
||||
import { getMap, getMissionHandler, getUnitsManager, getWeaponsManager } from '..';
|
||||
import { enumToCoalition, enumToEmissioNCountermeasure, getMarkerCategoryByName, enumToROE, enumToReactionToThreat, enumToState, getUnitDatabaseByCategory, mToFt, msToKnots, rad2deg, bearing, deg2rad, ftToM } from '../other/utils';
|
||||
import { addDestination, attackUnit, changeAltitude, changeSpeed, createFormation as setLeader, deleteUnit, landAt, setAltitude, setReactionToThreat, setROE, setSpeed, refuel, setAdvacedOptions, followUnit, setEmissionsCountermeasures, setSpeedType, setAltitudeType, setOnOff, setFollowRoads, bombPoint, carpetBomb, bombBuilding, fireAtArea } from '../server/server';
|
||||
import { CustomMarker } from '../map/custommarker';
|
||||
import { CustomMarker } from '../map/markers/custommarker';
|
||||
import { SVGInjector } from '@tanem/svg-injector';
|
||||
import { UnitDatabase } from './unitdatabase';
|
||||
import { TargetMarker } from '../map/targetmarker';
|
||||
import { UnitDatabase } from './databases/unitdatabase';
|
||||
import { TargetMarker } from '../map/markers/targetmarker';
|
||||
import { DLINK, DataIndexes, GAME_MASTER, HIDE_GROUP_MEMBERS, IDLE, IRST, MOVE_UNIT, OPTIC, RADAR, ROEs, RWR, SHOW_CONTACT_LINES, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, VISUAL, emissionsCountermeasures, reactionsToThreat, states } from '../constants/constants';
|
||||
import { Ammo, Contact, GeneralSettings, Offset, Radio, TACAN, ObjectIconOptions, UnitData } from '../@types/unit';
|
||||
import { DataExtractor } from '../server/dataextractor';
|
||||
import { groundUnitDatabase } from './groundunitdatabase';
|
||||
import { navyUnitDatabase } from './navyunitdatabase';
|
||||
import { groundUnitDatabase } from './databases/groundunitdatabase';
|
||||
import { navyUnitDatabase } from './databases/navyunitdatabase';
|
||||
import { Weapon } from '../weapon/weapon';
|
||||
import { LoadoutBlueprint } from '../@types/unitdatabase';
|
||||
|
||||
|
||||
@ -3,16 +3,16 @@ import { getHotgroupPanel, getInfoPopup, getMap, getMissionHandler, getUnitsMana
|
||||
import { Unit } from "./unit";
|
||||
import { cloneUnits, deleteUnit, spawnAircrafts, spawnGroundUnits, spawnHelicopters, spawnNavyUnits } from "../server/server";
|
||||
import { bearingAndDistanceToLatLng, deg2rad, getUnitDatabaseByCategory, keyEventWasInInput, latLngToMercator, mToFt, mercatorToLatLng, msToKnots, polyContains, polygonArea, randomPointInPoly, randomUnitBlueprint } from "../other/utils";
|
||||
import { CoalitionArea } from "../map/coalitionarea";
|
||||
import { groundUnitDatabase } from "./groundunitdatabase";
|
||||
import { CoalitionArea } from "../map/coalitionarea/coalitionarea";
|
||||
import { groundUnitDatabase } from "./databases/groundunitdatabase";
|
||||
import { DataIndexes, GAME_MASTER, IADSDensities, IDLE, MOVE_UNIT } from "../constants/constants";
|
||||
import { DataExtractor } from "../server/dataextractor";
|
||||
import { Contact, UnitData } from "../@types/unit";
|
||||
import { citiesDatabase } from "./citiesDatabase";
|
||||
import { aircraftDatabase } from "./aircraftdatabase";
|
||||
import { helicopterDatabase } from "./helicopterdatabase";
|
||||
import { navyUnitDatabase } from "./navyunitdatabase";
|
||||
import { TemporaryUnitMarker } from "../map/temporaryunitmarker";
|
||||
import { aircraftDatabase } from "./databases/aircraftdatabase";
|
||||
import { helicopterDatabase } from "./databases/helicopterdatabase";
|
||||
import { navyUnitDatabase } from "./databases/navyunitdatabase";
|
||||
import { TemporaryUnitMarker } from "../map/markers/temporaryunitmarker";
|
||||
|
||||
/** The UnitsManager handles the creation, update, and control of units. Data is strictly updated by the server ONLY. This means that any interaction from the user will always and only
|
||||
* result in a command to the server, executed by means of a REST PUT request. Any subsequent change in data will be reflected only when the new data is sent back by the server. This strategy allows
|
||||
@ -595,11 +595,7 @@ export class UnitsManager {
|
||||
var unit = selectedUnits[idx];
|
||||
units.push({ ID: unit.ID, location: unit.getPosition() });
|
||||
}
|
||||
cloneUnits(units, true, 0 /* No spawn points, we delete the original units */, () => {
|
||||
units.forEach((unit: any) => {
|
||||
deleteUnit(unit.ID, false, false);
|
||||
});
|
||||
});
|
||||
cloneUnits(units, true, 0 /* No spawn points, we delete the original units */);
|
||||
}
|
||||
|
||||
/***********************************************/
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { LatLng, DivIcon, Map } from 'leaflet';
|
||||
import { getMap, getMissionHandler, getUnitsManager } from '..';
|
||||
import { enumToCoalition, mToFt, msToKnots, rad2deg } from '../other/utils';
|
||||
import { CustomMarker } from '../map/custommarker';
|
||||
import { CustomMarker } from '../map/markers/custommarker';
|
||||
import { SVGInjector } from '@tanem/svg-injector';
|
||||
import { DLINK, DataIndexes, GAME_MASTER, IRST, OPTIC, RADAR, VISUAL } from '../constants/constants';
|
||||
import { ObjectIconOptions } from '../@types/unit';
|
||||
|
||||
@ -22,11 +22,6 @@
|
||||
<%- include('toolbars/commandmode.ejs') %>
|
||||
</div>
|
||||
|
||||
<!-- AIC/ATC -->
|
||||
<%- include('aic/aic.ejs') %>
|
||||
<%- include('atc/atc.ejs') %>
|
||||
<%- include('atc/unitdatatable.ejs') %>
|
||||
|
||||
<!-- Panels -->
|
||||
<%- include('panels/unitcontrol.ejs') %>
|
||||
<%- include('panels/unitinfo.ejs') %>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user