mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
Merge pull request #199 from Pax1601/feature-atc-tower
Feature atc tower
This commit is contained in:
commit
6ce9b2ddea
@ -42,7 +42,7 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ol-strip-board-headers > *, .ol-strip-board-strip > * {
|
.ol-strip-board-headers > *, .ol-strip-board-strip > [data-point] {
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
@ -67,17 +67,70 @@
|
|||||||
border:1px solid #cc0000;
|
border:1px solid #cc0000;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ol-strip-board-headers :nth-child(1),
|
.ol-strip-board-headers :nth-child(1) {
|
||||||
.ol-strip-board-headers :nth-child(2),
|
width:12px;
|
||||||
.ol-strip-board-strip :nth-child(1),
|
|
||||||
.ol-strip-board-strip :nth-child(2) {
|
|
||||||
width:120px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ol-strip-board-strip :nth-child(4) {
|
.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;
|
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"] {
|
.ol-strip-board-strip > [data-point="name"] {
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
overflow:hidden;
|
overflow:hidden;
|
||||||
@ -91,6 +144,7 @@
|
|||||||
.ol-strip-board-add-flight {
|
.ol-strip-board-add-flight {
|
||||||
display:flex;
|
display:flex;
|
||||||
flex-flow: row nowrap;
|
flex-flow: row nowrap;
|
||||||
|
position:relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -100,15 +154,42 @@
|
|||||||
padding:4px 8px;
|
padding:4px 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ol-strip-board-add-flight button {
|
.add-flight-by-click img {
|
||||||
background-color: darkgreen;
|
filter:invert();
|
||||||
border-bottom-right-radius: var( --border-radius-sm );
|
height: 12px;
|
||||||
border-bottom-left-radius: 0;
|
|
||||||
border-top-left-radius: 0;
|
|
||||||
border-top-right-radius: var( --border-radius-sm );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ol-strip-board-add-flight input {
|
.ol-strip-board-add-flight input {
|
||||||
border-bottom-left-radius: var( --border-radius-sm );
|
border-radius: var( --border-radius-sm );
|
||||||
border-top-left-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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
}
|
}
|
||||||
@ -153,7 +153,7 @@ form > div {
|
|||||||
|
|
||||||
.ol-select.narrow:not(.ol-select-image)>.ol-select-value {
|
.ol-select.narrow:not(.ol-select-image)>.ol-select-value {
|
||||||
opacity: .9;
|
opacity: .9;
|
||||||
padding:6px 30px 6px 15px;
|
padding:4px 30px 4px 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ol-select:not(.ol-select-image)>.ol-select-value svg {
|
.ol-select:not(.ol-select-image)>.ol-select-value svg {
|
||||||
@ -537,6 +537,15 @@ nav.ol-panel> :last-child {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.ol-sortable .handle {
|
||||||
|
background-image: url( "/images/icons/grip-lines-solid.svg" );
|
||||||
|
cursor:ns-resize;
|
||||||
|
filter:invert();
|
||||||
|
height:12px;
|
||||||
|
width:12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#unit-selection {
|
#unit-selection {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@ -25,23 +25,66 @@ function uuidv4() {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
function Flight( name ) {
|
function Flight( name, boardId, unitId ) {
|
||||||
this.id = uuidv4();
|
this.assignedAltitude = 0;
|
||||||
this.name = name;
|
this.assignedSpeed = 0;
|
||||||
this.status = "unknown";
|
this.id = uuidv4();
|
||||||
this.takeoffTime = -1;
|
this.boardId = boardId;
|
||||||
|
this.name = name;
|
||||||
|
this.status = "unknown";
|
||||||
|
this.takeoffTime = -1;
|
||||||
|
this.unitId = parseInt( unitId );
|
||||||
}
|
}
|
||||||
|
|
||||||
Flight.prototype.getData = function() {
|
Flight.prototype.getData = function() {
|
||||||
return {
|
return {
|
||||||
"id" : this.id,
|
"assignedAltitude" : this.assignedAltitude,
|
||||||
"name" : this.name,
|
"assignedSpeed" : this.assignedSpeed,
|
||||||
"status" : this.status,
|
"id" : this.id,
|
||||||
"takeoffTime" : this.takeoffTime
|
"boardId" : this.boardId,
|
||||||
|
"name" : this.name,
|
||||||
|
"status" : this.status,
|
||||||
|
"takeoffTime" : this.takeoffTime,
|
||||||
|
"unitId" : this.unitId
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Flight.prototype.setAssignedAltitude = function( assignedAltitude ) {
|
||||||
|
|
||||||
|
if ( isNaN( assignedAltitude ) ) {
|
||||||
|
return "Altitude must be a number"
|
||||||
|
}
|
||||||
|
|
||||||
|
this.assignedAltitude = parseInt( assignedAltitude );
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Flight.prototype.setAssignedSpeed = function( assignedSpeed ) {
|
||||||
|
|
||||||
|
if ( isNaN( assignedSpeed ) ) {
|
||||||
|
return "Speed must be a number"
|
||||||
|
}
|
||||||
|
|
||||||
|
this.assignedSpeed = parseInt( assignedSpeed );
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Flight.prototype.setOrder = function( order ) {
|
||||||
|
|
||||||
|
this.order = order;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Flight.prototype.setStatus = function( status ) {
|
Flight.prototype.setStatus = function( status ) {
|
||||||
|
|
||||||
if ( [ "unknown", "checkedin", "readytotaxi", "clearedtotaxi", "halted", "terminated" ].indexOf( status ) < 0 ) {
|
if ( [ "unknown", "checkedin", "readytotaxi", "clearedtotaxi", "halted", "terminated" ].indexOf( status ) < 0 ) {
|
||||||
@ -116,7 +159,20 @@ const dataHandler = new ATCDataHandler( {
|
|||||||
|
|
||||||
app.get( "/flight", ( req, res ) => {
|
app.get( "/flight", ( req, res ) => {
|
||||||
|
|
||||||
res.json( dataHandler.getFlights() );
|
let flights = Object.values( dataHandler.getFlights() );
|
||||||
|
|
||||||
|
if ( flights && req.query.boardId ) {
|
||||||
|
|
||||||
|
flights = flights.reduce( ( acc, flight ) => {
|
||||||
|
if ( flight.boardId === req.query.boardId ) {
|
||||||
|
acc[ flight.id ] = flight;
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, {} );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json( flights );
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -130,6 +186,26 @@ app.patch( "/flight/:flightId", ( req, res ) => {
|
|||||||
res.status( 400 ).send( `Unrecognised flight ID (given: "${req.params.flightId}")` );
|
res.status( 400 ).send( `Unrecognised flight ID (given: "${req.params.flightId}")` );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( req.body.hasOwnProperty( "assignedAltitude" ) ) {
|
||||||
|
|
||||||
|
const altitudeChangeSuccess = flight.setAssignedAltitude( req.body.assignedAltitude );
|
||||||
|
|
||||||
|
if ( altitudeChangeSuccess !== true ) {
|
||||||
|
res.status( 400 ).send( altitudeChangeSuccess );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( req.body.hasOwnProperty( "assignedSpeed" ) ) {
|
||||||
|
|
||||||
|
const speedChangeSuccess = flight.setAssignedSpeed( req.body.assignedSpeed );
|
||||||
|
|
||||||
|
if ( speedChangeSuccess !== true ) {
|
||||||
|
res.status( 400 ).send( speedChangeSuccess );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
if ( req.body.status ) {
|
if ( req.body.status ) {
|
||||||
|
|
||||||
const statusChangeSuccess = flight.setStatus( req.body.status );
|
const statusChangeSuccess = flight.setStatus( req.body.status );
|
||||||
@ -155,13 +231,42 @@ app.patch( "/flight/:flightId", ( req, res ) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
app.post( "/flight/order", ( req, res ) => {
|
||||||
|
|
||||||
|
if ( !req.body.boardId ) {
|
||||||
|
res.status( 400 ).send( "Invalid/missing boardId" );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !req.body.order || !Array.isArray( req.body.order ) ) {
|
||||||
|
res.status( 400 ).send( "Invalid/missing boardId" );
|
||||||
|
}
|
||||||
|
|
||||||
|
req.body.order.forEach( ( flightId, i ) => {
|
||||||
|
|
||||||
|
dataHandler.getFlight( flightId ).setOrder( i );
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
res.send( "" );
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
app.post( "/flight", ( req, res ) => {
|
app.post( "/flight", ( req, res ) => {
|
||||||
|
|
||||||
|
if ( !req.body.boardId ) {
|
||||||
|
res.status( 400 ).send( "Invalid/missing boardId" );
|
||||||
|
}
|
||||||
|
|
||||||
if ( !req.body.name ) {
|
if ( !req.body.name ) {
|
||||||
res.status( 400 ).send( "Invalid/missing flight name" );
|
res.status( 400 ).send( "Invalid/missing flight name" );
|
||||||
}
|
}
|
||||||
|
|
||||||
const flight = new Flight( req.body.name );
|
if ( !req.body.unitId || isNaN( req.body.unitId ) ) {
|
||||||
|
res.status( 400 ).send( "Invalid/missing unitId" );
|
||||||
|
}
|
||||||
|
|
||||||
|
const flight = new Flight( req.body.name, req.body.boardId, req.body.unitId );
|
||||||
|
|
||||||
dataHandler.addFlight( flight );
|
dataHandler.addFlight( flight );
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,17 @@
|
|||||||
import { ATCBoard } from "./atcboard";
|
import { ATCBoard } from "./atcboard";
|
||||||
import { ATCBoardFlight } from "./board/flight";
|
import { ATCBoardGround } from "./board/ground";
|
||||||
|
import { ATCBoardTower } from "./board/tower";
|
||||||
|
|
||||||
export interface FlightInterface {
|
export interface FlightInterface {
|
||||||
id : string;
|
assignedSpeed: any;
|
||||||
name : string;
|
assignedAltitude : any;
|
||||||
status : "unknown";
|
id : string;
|
||||||
takeoffTime : number;
|
boardId : string;
|
||||||
|
name : string;
|
||||||
|
order : number;
|
||||||
|
status : "unknown";
|
||||||
|
takeoffTime : number;
|
||||||
|
unitId : number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -15,7 +21,7 @@ class ATCDataHandler {
|
|||||||
#flights:{[key:string]: FlightInterface} = {};
|
#flights:{[key:string]: FlightInterface} = {};
|
||||||
|
|
||||||
#updateInterval:number|undefined = undefined;
|
#updateInterval:number|undefined = undefined;
|
||||||
#updateIntervalDelay:number = 1000;
|
#updateIntervalDelay:number = 2500; // Wait between unit update requests
|
||||||
|
|
||||||
|
|
||||||
constructor( atc:ATC ) {
|
constructor( atc:ATC ) {
|
||||||
@ -25,29 +31,43 @@ class ATCDataHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
getFlights() {
|
getFlights( boardId:string ) {
|
||||||
return this.#flights;
|
|
||||||
|
return Object.values( this.#flights ).reduce( ( acc:{[key:string]: FlightInterface}, flight ) => {
|
||||||
|
|
||||||
|
if ( flight.boardId === boardId ) {
|
||||||
|
acc[ flight.id ] = flight;
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, {} );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
startUpdates() {
|
startUpdates() {
|
||||||
|
|
||||||
this.#updateInterval = setInterval( () => {
|
this.#updateInterval = setInterval( () => {
|
||||||
|
|
||||||
fetch( '/api/atc/flight', {
|
const aBoardIsVisible = this.#atc.getBoards().some( board => board.boardIsVisible() );
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
if ( aBoardIsVisible ) {
|
||||||
'Accept': '*/*',
|
|
||||||
'Content-Type': 'application/json'
|
fetch( '/api/atc/flight', {
|
||||||
}
|
method: 'GET',
|
||||||
})
|
headers: {
|
||||||
.then( response => response.json() )
|
'Accept': '*/*',
|
||||||
.then( data => {
|
'Content-Type': 'application/json'
|
||||||
this.setFlights( data );
|
}
|
||||||
});
|
})
|
||||||
|
.then( response => response.json() )
|
||||||
|
.then( data => {
|
||||||
|
this.setFlights( data );
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}, this.#updateIntervalDelay );
|
}, this.#updateIntervalDelay );
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -94,6 +114,11 @@ export class ATC {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
getBoards() {
|
||||||
|
return this.#boards;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
getDataHandler() {
|
getDataHandler() {
|
||||||
return this.#dataHandler;
|
return this.#dataHandler;
|
||||||
}
|
}
|
||||||
@ -119,7 +144,22 @@ export class ATC {
|
|||||||
document.querySelectorAll( ".ol-strip-board" ).forEach( board => {
|
document.querySelectorAll( ".ol-strip-board" ).forEach( board => {
|
||||||
|
|
||||||
if ( board instanceof HTMLElement ) {
|
if ( board instanceof HTMLElement ) {
|
||||||
this.addBoard( new ATCBoardFlight( this, board ) );
|
|
||||||
|
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 );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,39 +1,93 @@
|
|||||||
import { Draggable } from "leaflet";
|
|
||||||
import { Dropdown } from "../controls/dropdown";
|
import { Dropdown } from "../controls/dropdown";
|
||||||
import { zeroAppend } from "../other/utils";
|
import { zeroAppend } from "../other/utils";
|
||||||
import { ATC } from "./atc";
|
import { ATC } from "./atc";
|
||||||
|
import { Unit } from "../units/unit";
|
||||||
|
import { getUnitsManager } from "..";
|
||||||
|
import Sortable from "sortablejs";
|
||||||
|
import { FlightInterface } from "./atc";
|
||||||
|
|
||||||
export interface StripBoardStripInterface {
|
export interface StripBoardStripInterface {
|
||||||
"id": string,
|
"id": string,
|
||||||
"element": HTMLElement,
|
"element": HTMLElement,
|
||||||
"dropdowns": {[key:string]: Dropdown}
|
"dropdowns": {[key:string]: Dropdown},
|
||||||
|
"isDeleted"?: boolean,
|
||||||
|
"unitId": number
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class ATCBoard {
|
export abstract class ATCBoard {
|
||||||
|
|
||||||
#atc:ATC;
|
#atc:ATC;
|
||||||
|
#boardId:string = "";
|
||||||
#templates: {[key:string]: string} = {};
|
#templates: {[key:string]: string} = {};
|
||||||
|
|
||||||
|
|
||||||
// Elements
|
// Elements
|
||||||
#boardElement:HTMLElement;
|
#boardElement:HTMLElement;
|
||||||
#clockElement:HTMLElement;
|
#clockElement:HTMLElement;
|
||||||
#stripBoardElement:HTMLElement;
|
#stripBoardElement:HTMLElement;
|
||||||
|
|
||||||
// Content
|
// Content
|
||||||
|
#isAddFlightByClickEnabled:boolean = false;
|
||||||
#strips:{[key:string]: StripBoardStripInterface} = {};
|
#strips:{[key:string]: StripBoardStripInterface} = {};
|
||||||
|
#unitIdsBeingMonitored:number[] = [];
|
||||||
|
|
||||||
// Update timing
|
// Update timing
|
||||||
#updateInterval:number|undefined = undefined;
|
#updateInterval:number|undefined = undefined;
|
||||||
#updateIntervalDelay:number = 1000;
|
#updateIntervalDelay:number = 1000;
|
||||||
|
|
||||||
|
|
||||||
constructor( atc:ATC, boardElement:HTMLElement ) {
|
constructor( atc:ATC, boardElement:HTMLElement, options?:{[key:string]: any} ) {
|
||||||
|
|
||||||
|
options = options || {};
|
||||||
|
|
||||||
this.#atc = atc;
|
this.#atc = atc;
|
||||||
this.#boardElement = boardElement;
|
this.#boardElement = boardElement;
|
||||||
this.#stripBoardElement = <HTMLElement>this.getBoardElement().querySelector( ".ol-strip-board-strips" );
|
this.#stripBoardElement = <HTMLElement>this.getBoardElement().querySelector( ".ol-strip-board-strips" );
|
||||||
this.#clockElement = <HTMLElement>this.getBoardElement().querySelector( ".ol-strip-board-clock" );
|
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
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
setInterval( () => {
|
||||||
|
this.updateClock();
|
||||||
|
}, 1000 );
|
||||||
|
|
||||||
|
|
||||||
if ( this.#boardElement.classList.contains( "ol-draggable" ) ) {
|
if ( this.#boardElement.classList.contains( "ol-draggable" ) ) {
|
||||||
|
|
||||||
let options:any = {};
|
let options:any = {};
|
||||||
@ -47,15 +101,72 @@ export abstract class ATCBoard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
setInterval( () => {
|
this.#setupAddFlight();
|
||||||
this.updateClock();
|
|
||||||
}, 1000 );
|
// this.#_setupDemoData();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
addFlight( unit:Unit ) {
|
||||||
|
|
||||||
|
const baseData = unit.getBaseData();
|
||||||
|
|
||||||
|
const unitCanBeAdded = () => {
|
||||||
|
|
||||||
|
if ( baseData.category !== "Aircraft" ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( baseData.AI === 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 ) {
|
addStrip( strip:StripBoardStripInterface ) {
|
||||||
|
|
||||||
this.#strips[ strip.id ] = strip;
|
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" ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -88,16 +199,40 @@ export abstract class ATCBoard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
deleteStrip( id:string ) {
|
deleteStrip( flightId:string ) {
|
||||||
|
|
||||||
|
if ( this.#strips.hasOwnProperty( flightId ) ) {
|
||||||
|
|
||||||
|
this.#strips[ flightId ].element.remove();
|
||||||
|
this.#strips[ flightId ].isDeleted = true;
|
||||||
|
|
||||||
|
setTimeout( () => {
|
||||||
|
delete this.#strips[ flightId ];
|
||||||
|
}, 10000 );
|
||||||
|
|
||||||
if ( this.#strips.hasOwnProperty( id ) ) {
|
|
||||||
this.#strips[ id ].element.remove();
|
|
||||||
delete this.#strips[ id ];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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() {
|
getATC() {
|
||||||
return this.#atc;
|
return this.#atc;
|
||||||
}
|
}
|
||||||
@ -108,6 +243,11 @@ export abstract class ATCBoard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
getBoardId(): string {
|
||||||
|
return this.getBoardElement().id;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
getStripBoardElement() {
|
getStripBoardElement() {
|
||||||
return this.#stripBoardElement;
|
return this.#stripBoardElement;
|
||||||
}
|
}
|
||||||
@ -130,8 +270,10 @@ export abstract class ATCBoard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected update() {
|
getUnitIdsBeingMonitored() {
|
||||||
console.warn( "No custom update method defined." );
|
|
||||||
|
return this.#unitIdsBeingMonitored;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -140,8 +282,132 @@ export abstract class ATCBoard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#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 = 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.getBaseData();
|
||||||
|
|
||||||
|
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.getBaseData();
|
||||||
|
|
||||||
|
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() {
|
startUpdates() {
|
||||||
|
|
||||||
|
if ( !this.boardIsVisible() ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.#updateInterval = setInterval( () => {
|
this.#updateInterval = setInterval( () => {
|
||||||
|
|
||||||
this.update();
|
this.update();
|
||||||
@ -173,6 +439,10 @@ export abstract class ATCBoard {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected update() {
|
||||||
|
console.warn( "No custom update method defined." );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
updateClock() {
|
updateClock() {
|
||||||
|
|
||||||
@ -181,5 +451,64 @@ export abstract class ATCBoard {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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,77 +1,20 @@
|
|||||||
import { getMissionData } from "../..";
|
|
||||||
import { Dropdown } from "../../controls/dropdown";
|
import { Dropdown } from "../../controls/dropdown";
|
||||||
import { ATC } from "../atc";
|
import { ATC } from "../atc";
|
||||||
import { ATCBoard, StripBoardStripInterface } from "../atcboard";
|
import { ATCBoard } from "../atcboard";
|
||||||
|
|
||||||
|
|
||||||
export class ATCBoardFlight extends ATCBoard {
|
export class ATCBoardGround extends ATCBoard {
|
||||||
|
|
||||||
constructor( atc:ATC, element:HTMLElement ) {
|
constructor( atc:ATC, element:HTMLElement ) {
|
||||||
|
|
||||||
super( atc, element );
|
super( atc, element );
|
||||||
|
|
||||||
document.addEventListener( "deleteFlightStrip", ( ev:CustomEventInit ) => {
|
|
||||||
|
|
||||||
if ( ev.detail.id ) {
|
|
||||||
|
|
||||||
fetch( '/api/atc/flight/' + ev.detail.id, {
|
|
||||||
method: 'DELETE',
|
|
||||||
headers: {
|
|
||||||
'Accept': '*/*',
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
this.getBoardElement().querySelectorAll( "form.ol-strip-board-add-flight" ).forEach( form => {
|
|
||||||
|
|
||||||
if ( form instanceof HTMLFormElement ) {
|
|
||||||
|
|
||||||
form.addEventListener( "submit", ev => {
|
|
||||||
|
|
||||||
ev.preventDefault();
|
|
||||||
|
|
||||||
|
|
||||||
if ( ev.target instanceof HTMLFormElement ) {
|
|
||||||
|
|
||||||
const elements = ev.target.elements;
|
|
||||||
const flightName = <HTMLInputElement>elements[0];
|
|
||||||
|
|
||||||
if ( flightName.value === "" ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
fetch( '/api/atc/flight/', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Accept': '*/*',
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
"body": JSON.stringify({
|
|
||||||
"name": flightName.value
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
form.reset();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
update() {
|
update() {
|
||||||
|
|
||||||
const flights = Object.values( this.getATC().getDataHandler().getFlights() );
|
const flights = this.sortFlights( Object.values( this.getATC().getDataHandler().getFlights( this.getBoardId() ) ) );
|
||||||
const stripBoard = this.getStripBoardElement();
|
const stripBoard = this.getStripBoardElement();
|
||||||
|
|
||||||
const missionTime = this.getATC().getMissionDateTime().getTime();
|
const missionTime = this.getATC().getMissionDateTime().getTime();
|
||||||
@ -88,6 +31,7 @@ export class ATCBoardFlight extends ATCBoard {
|
|||||||
if ( !strip ) {
|
if ( !strip ) {
|
||||||
|
|
||||||
const template = `<div class="ol-strip-board-strip" data-flight-id="${flight.id}" data-flight-status="${flight.status}">
|
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 data-point="name">${flight.name}</div>
|
||||||
|
|
||||||
<div id="flight-status-${flight.id}" class="ol-select narrow" data-point="status">
|
<div id="flight-status-${flight.id}" class="ol-select narrow" data-point="status">
|
||||||
@ -99,7 +43,7 @@ export class ATCBoardFlight extends ATCBoard {
|
|||||||
|
|
||||||
<div data-point="timeToGo">${this.timeToGo( flight.takeoffTime )}</div>
|
<div data-point="timeToGo">${this.timeToGo( flight.takeoffTime )}</div>
|
||||||
|
|
||||||
<button data-on-click="deleteFlightStrip" data-on-click-params='{"id":"${flight.id}"}'>Delete</button>
|
<button class="deleteFlight">×</button>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
stripBoard.insertAdjacentHTML( "beforeend", template );
|
stripBoard.insertAdjacentHTML( "beforeend", template );
|
||||||
@ -108,7 +52,8 @@ export class ATCBoardFlight extends ATCBoard {
|
|||||||
strip = {
|
strip = {
|
||||||
"id": flight.id,
|
"id": flight.id,
|
||||||
"element": <HTMLElement>stripBoard.lastElementChild,
|
"element": <HTMLElement>stripBoard.lastElementChild,
|
||||||
"dropdowns": {}
|
"dropdowns": {},
|
||||||
|
"unitId": -1
|
||||||
};
|
};
|
||||||
|
|
||||||
strip.element.querySelectorAll( ".ol-select" ).forEach( select => {
|
strip.element.querySelectorAll( ".ol-select" ).forEach( select => {
|
||||||
@ -227,6 +172,7 @@ export class ATCBoardFlight extends ATCBoard {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
stripBoard.querySelectorAll( `[data-updating]` ).forEach( strip => {
|
stripBoard.querySelectorAll( `[data-updating]` ).forEach( strip => {
|
||||||
this.deleteStrip( strip.getAttribute( "data-flight-id" ) || "" );
|
this.deleteStrip( strip.getAttribute( "data-flight-id" ) || "" );
|
||||||
});
|
});
|
||||||
@ -69,6 +69,14 @@ export function distance(lat1: number, lon1: number, lat2: number, lon2: number)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function generateUUIDv4() {
|
||||||
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||||
|
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
|
||||||
|
return v.toString(16);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export function keyEventWasInInput( event:KeyboardEvent ) {
|
export function keyEventWasInInput( event:KeyboardEvent ) {
|
||||||
|
|
||||||
const target = event.target;
|
const target = event.target;
|
||||||
|
|||||||
@ -23,6 +23,24 @@ export class UnitsManager {
|
|||||||
document.addEventListener('deleteSelectedUnits', () => this.selectedUnitsDelete() )
|
document.addEventListener('deleteSelectedUnits', () => this.selectedUnitsDelete() )
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getSelectableAircraft() {
|
||||||
|
|
||||||
|
const units = this.getUnits();
|
||||||
|
|
||||||
|
return Object.keys( units ).reduce( ( acc:{[key:number]: Unit}, unitId:any ) => {
|
||||||
|
|
||||||
|
const baseData = units[ unitId ].getBaseData();
|
||||||
|
|
||||||
|
if ( baseData.category === "Aircraft" && baseData.alive === true ) {
|
||||||
|
acc[ unitId ] = units[ unitId ];
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
getUnits() {
|
getUnits() {
|
||||||
return this.#units;
|
return this.#units;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,26 +1,11 @@
|
|||||||
<div id="strip-board-ground" class="ol-panel ol-dialog ol-strip-board ol-draggable" data-feature-switch="atc">
|
<%- include('atc/board.ejs', {
|
||||||
|
"boardId": "strip-board-tower",
|
||||||
|
"boardType": "tower",
|
||||||
|
"headers": [ "Flight", "a. Alt", "alt", "a. Speed", "Speed" ]
|
||||||
|
}) %>
|
||||||
|
|
||||||
<div class="ol-dialog-close" data-on-click="closeDialog"></div>
|
<%- include('atc/board.ejs', {
|
||||||
|
"boardId": "strip-board-ground",
|
||||||
<div class="ol-dialog-header">
|
"boardType": "ground",
|
||||||
<div class="ol-strip-board-clock"></div>
|
"headers": [ "Flight", "Status", "T/O Time", "TTG" ]
|
||||||
</div>
|
}) %>
|
||||||
|
|
||||||
<div class="ol-dialog-content">
|
|
||||||
<div class="ol-strip-board-headers">
|
|
||||||
<div>Flight</div>
|
|
||||||
<div>Status</div>
|
|
||||||
<div>T/O Time</div>
|
|
||||||
<div>TTG</div>
|
|
||||||
</div>
|
|
||||||
<div class="ol-strip-board-strips"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="ol-dialog-footer">
|
|
||||||
<form class="ol-strip-board-add-flight">
|
|
||||||
<input type="text" name="flightName" placeholder="Add a flight" />
|
|
||||||
<button type="submit">Add</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
@ -56,7 +56,7 @@
|
|||||||
<div class="ol-group-header">ATC</div>
|
<div class="ol-group-header">ATC</div>
|
||||||
<div class="ol-group">
|
<div class="ol-group">
|
||||||
<button data-on-click="toggleElements" data-on-click-params='{"selector": "#strip-board-ground"}'>GND</button>
|
<button data-on-click="toggleElements" data-on-click-params='{"selector": "#strip-board-ground"}'>GND</button>
|
||||||
<button>TWR</button>
|
<button data-on-click="toggleElements" data-on-click-params='{"selector": "#strip-board-tower"}'>TWR</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
Loading…
x
Reference in New Issue
Block a user