mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
ATC Ground board PoC.
This commit is contained in:
parent
14147168f9
commit
6f64bd1622
@ -0,0 +1,114 @@
|
||||
|
||||
|
||||
.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;
|
||||
padding:4px;
|
||||
}
|
||||
|
||||
.ol-strip-board-strip[data-flight-status="checkedin"] {
|
||||
background-color: #ffffff2A;
|
||||
}
|
||||
|
||||
.ol-strip-board-strip[data-flight-status="readytotaxi"] {
|
||||
background-color: #ffff002A;
|
||||
}
|
||||
|
||||
.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 > * {
|
||||
padding: 4px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
width:70px;
|
||||
}
|
||||
|
||||
.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),
|
||||
.ol-strip-board-headers :nth-child(2),
|
||||
.ol-strip-board-strip :nth-child(1),
|
||||
.ol-strip-board-strip :nth-child(2) {
|
||||
width:120px;
|
||||
}
|
||||
|
||||
.ol-strip-board-strip :nth-child(4) {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
|
||||
.ol-strip-board-add-flight > * {
|
||||
border:none;
|
||||
outline: none;
|
||||
padding:4px 8px;
|
||||
}
|
||||
|
||||
.ol-strip-board-add-flight button {
|
||||
background-color: darkgreen;
|
||||
border-bottom-right-radius: var( --border-radius-sm );
|
||||
border-bottom-left-radius: 0;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: var( --border-radius-sm );
|
||||
}
|
||||
|
||||
.ol-strip-board-add-flight input {
|
||||
border-bottom-left-radius: var( --border-radius-sm );
|
||||
border-top-left-radius: var( --border-radius-sm );
|
||||
}
|
||||
@ -124,8 +124,8 @@ dl.ol-data-grid dd {
|
||||
font-size:16px;
|
||||
font-weight: var( --font-weight-bolder );
|
||||
position: absolute;
|
||||
right: 25px;
|
||||
top: 25px;
|
||||
right: 20px;
|
||||
top: 10px;
|
||||
}
|
||||
|
||||
.ol-dialog-close::before {
|
||||
@ -137,6 +137,10 @@ dl.ol-data-grid dd {
|
||||
padding-bottom:10px;
|
||||
}
|
||||
|
||||
.ol-dialog-content {
|
||||
margin:4px 0;
|
||||
}
|
||||
|
||||
.ol-dialog-footer {
|
||||
border-top:1px solid var( --background-grey );
|
||||
padding-top:15px;
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
@import url("layout.css");
|
||||
@import url("airbase.css");
|
||||
@import url("atc.css");
|
||||
@import url("connectionstatuspanel.css");
|
||||
@import url("contextmenus.css");
|
||||
@import url("mouseinfopanel.css");
|
||||
@ -123,14 +124,17 @@ form > div {
|
||||
align-items: center;
|
||||
background-color: var(--background-grey);
|
||||
border-radius: var(--border-radius-sm);
|
||||
padding: 1em;
|
||||
padding: 1em 30px 1em 20px;
|
||||
width: 100%;
|
||||
padding-left: 20px;
|
||||
padding-right: 30px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.ol-select.narrow:not(.ol-select-image)>.ol-select-value {
|
||||
opacity: .9;
|
||||
padding:6px 30px 6px 15px;
|
||||
}
|
||||
|
||||
.ol-select:not(.ol-select-image)>.ol-select-value svg {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
@ -26,18 +26,53 @@ function uuidv4() {
|
||||
|
||||
|
||||
function Flight( name ) {
|
||||
this.id = uuidv4();
|
||||
this.name = name;
|
||||
this.status = "unknown";
|
||||
this.id = uuidv4();
|
||||
this.name = name;
|
||||
this.status = "unknown";
|
||||
this.takeoffTime = -1;
|
||||
}
|
||||
|
||||
Flight.prototype.getData = function() {
|
||||
return {
|
||||
"id": this.id,
|
||||
"name": this.name
|
||||
"id" : this.id,
|
||||
"name" : this.name,
|
||||
"status" : this.status,
|
||||
"takeoffTime" : this.takeoffTime
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Flight.prototype.setStatus = function( status ) {
|
||||
|
||||
if ( [ "unknown", "checkedin", "readytotaxi", "clearedtotaxi", "halted", "terminated" ].indexOf( status ) < 0 ) {
|
||||
return "Invalid status";
|
||||
}
|
||||
|
||||
this.status = status;
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
|
||||
Flight.prototype.setTakeoffTime = function( takeoffTime ) {
|
||||
|
||||
if ( takeoffTime === "" || takeoffTime === -1 ) {
|
||||
this.takeoffTime = -1;
|
||||
}
|
||||
|
||||
if ( isNaN( takeoffTime ) ) {
|
||||
return "Invalid takeoff time"
|
||||
}
|
||||
|
||||
this.takeoffTime = parseInt( takeoffTime );
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
function ATCDataHandler( data ) {
|
||||
this.data = data;
|
||||
}
|
||||
@ -86,6 +121,40 @@ app.get( "/flight", ( req, res ) => {
|
||||
});
|
||||
|
||||
|
||||
app.patch( "/flight/:flightId", ( req, res ) => {
|
||||
|
||||
const flightId = req.params.flightId;
|
||||
const flight = dataHandler.getFlight( flightId );
|
||||
|
||||
if ( !flight ) {
|
||||
res.status( 400 ).send( `Unrecognised flight ID (given: "${req.params.flightId}")` );
|
||||
}
|
||||
|
||||
if ( req.body.status ) {
|
||||
|
||||
const statusChangeSuccess = flight.setStatus( req.body.status );
|
||||
|
||||
if ( statusChangeSuccess !== true ) {
|
||||
res.status( 400 ).send( statusChangeSuccess );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ( req.body.hasOwnProperty( "takeoffTime" ) ) {
|
||||
|
||||
const takeoffChangeSuccess = flight.setTakeoffTime( req.body.takeoffTime );
|
||||
|
||||
if ( takeoffChangeSuccess !== true ) {
|
||||
res.status( 400 ).send( takeoffChangeSuccess );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
res.json( flight.getData() );
|
||||
|
||||
});
|
||||
|
||||
|
||||
app.post( "/flight", ( req, res ) => {
|
||||
|
||||
if ( !req.body.name ) {
|
||||
|
||||
@ -2,9 +2,10 @@ import { ATCBoard } from "./atcboard";
|
||||
import { ATCBoardFlight } from "./board/flight";
|
||||
|
||||
export interface FlightInterface {
|
||||
id : string;
|
||||
name : string;
|
||||
status : "unknown";
|
||||
id : string;
|
||||
name : string;
|
||||
status : "unknown";
|
||||
takeoffTime : number;
|
||||
}
|
||||
|
||||
|
||||
@ -73,6 +74,7 @@ export class ATC {
|
||||
#boards:ATCBoard[] = [];
|
||||
#dataHandler:ATCDataHandler;
|
||||
|
||||
#initDate:Date = new Date();
|
||||
|
||||
constructor() {
|
||||
|
||||
@ -97,9 +99,24 @@ export class ATC {
|
||||
}
|
||||
|
||||
|
||||
getMissionElapsedSeconds() : number {
|
||||
return new Date().getTime() - this.#initDate.getTime();
|
||||
}
|
||||
|
||||
|
||||
getMissionStartDateTime() : Date {
|
||||
return new Date( 1990, 3, 1, 18, 0, 0 );
|
||||
}
|
||||
|
||||
|
||||
getMissionDateTime() : Date {
|
||||
return new Date( this.getMissionStartDateTime().getTime() + this.getMissionElapsedSeconds() );
|
||||
}
|
||||
|
||||
|
||||
lookForBoards() {
|
||||
|
||||
document.querySelectorAll( ".ol-atc-board" ).forEach( board => {
|
||||
document.querySelectorAll( ".ol-strip-board" ).forEach( board => {
|
||||
|
||||
if ( board instanceof HTMLElement ) {
|
||||
this.addBoard( new ATCBoardFlight( this, board ) );
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { zeroAppend } from "../other/utils";
|
||||
import { ATC } from "./atc";
|
||||
|
||||
export interface ATCTemplateInterface {
|
||||
@ -11,6 +12,7 @@ export abstract class ATCBoard {
|
||||
|
||||
// Elements
|
||||
#boardElement:HTMLElement;
|
||||
#clockElement:HTMLElement;
|
||||
#stripBoardElement:HTMLElement;
|
||||
|
||||
// Update timing
|
||||
@ -22,7 +24,34 @@ export abstract class ATCBoard {
|
||||
|
||||
this.#atc = atc;
|
||||
this.#boardElement = boardElement;
|
||||
this.#stripBoardElement = <HTMLElement>this.getBoardElement().querySelector( ".atc-strip-board" );
|
||||
this.#stripBoardElement = <HTMLElement>this.getBoardElement().querySelector( ".ol-strip-board-strips" );
|
||||
this.#clockElement = <HTMLElement>this.getBoardElement().querySelector( ".ol-strip-board-clock" );
|
||||
|
||||
|
||||
setInterval( () => {
|
||||
this.updateClock();
|
||||
}, 1000 );
|
||||
|
||||
}
|
||||
|
||||
|
||||
calculateTimeToGo( fromTimestamp:number, toTimestamp:number ) {
|
||||
|
||||
let timestamp = ( toTimestamp - fromTimestamp ) / 1000;
|
||||
|
||||
const hours = zeroAppend( Math.floor( timestamp / 3600 ), 2 );
|
||||
const rMinutes = timestamp % 3600;
|
||||
|
||||
const minutes = zeroAppend( Math.floor( rMinutes / 60 ), 2 );
|
||||
const seconds = zeroAppend( Math.floor( rMinutes % 60 ), 2 );
|
||||
|
||||
return {
|
||||
"hours": hours,
|
||||
"minutes": minutes,
|
||||
"seconds": seconds,
|
||||
"time": `${hours}:${minutes}:${seconds}`,
|
||||
"totalSeconds": timestamp
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -76,5 +105,27 @@ export abstract class ATCBoard {
|
||||
|
||||
}
|
||||
|
||||
|
||||
timestampToLocaleTime( timestamp:number ) {
|
||||
|
||||
return ( timestamp === -1 ) ? "-" : new Date( timestamp ).toLocaleTimeString();
|
||||
|
||||
}
|
||||
|
||||
|
||||
timeToGo( timestamp:number ) {
|
||||
|
||||
return ( timestamp === -1 ) ? "-" : this.calculateTimeToGo( this.getATC().getMissionDateTime().getTime(), timestamp ).time;
|
||||
|
||||
}
|
||||
|
||||
|
||||
updateClock() {
|
||||
|
||||
const now = this.#atc.getMissionDateTime();
|
||||
this.#clockElement.innerText = now.toLocaleTimeString();
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -1,15 +1,15 @@
|
||||
import { getMissionData } from "../..";
|
||||
import { Dropdown } from "../../controls/dropdown";
|
||||
import { ATC } from "../atc";
|
||||
import { ATCBoard } from "../atcboard";
|
||||
|
||||
|
||||
export class ATCBoardFlight extends ATCBoard {
|
||||
|
||||
constructor( atc:ATC, element:HTMLElement ) {
|
||||
|
||||
super( atc, element );
|
||||
|
||||
console.log( getMissionData() );
|
||||
|
||||
document.addEventListener( "deleteFlightStrip", ( ev:CustomEventInit ) => {
|
||||
|
||||
if ( ev.detail.id ) {
|
||||
@ -26,6 +26,46 @@ export class ATCBoardFlight extends ATCBoard {
|
||||
|
||||
});
|
||||
|
||||
|
||||
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();
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -34,6 +74,7 @@ export class ATCBoardFlight extends ATCBoard {
|
||||
const flights = Object.values( this.getATC().getDataHandler().getFlights() );
|
||||
const stripBoard = this.getStripBoardElement();
|
||||
|
||||
const missionTime = this.getATC().getMissionDateTime().getTime();
|
||||
|
||||
for( const strip of stripBoard.children ) {
|
||||
strip.toggleAttribute( "data-updating", true );
|
||||
@ -46,9 +87,18 @@ export class ATCBoardFlight extends ATCBoard {
|
||||
|
||||
if ( !strip ) {
|
||||
|
||||
const template = `<div data-flight-id="${flight.id}">
|
||||
const template = `<div class="ol-strip-board-strip" data-flight-id="${flight.id}" data-flight-status="${flight.status}">
|
||||
<div data-point="name">${flight.name}</div>
|
||||
<div data-point="status">${flight.status}</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 data-on-click="deleteFlightStrip" data-on-click-params='{"id":"${flight.id}"}'>Delete</button>
|
||||
</div>`;
|
||||
|
||||
@ -56,6 +106,115 @@ export class ATCBoardFlight extends ATCBoard {
|
||||
|
||||
strip = <HTMLElement>stripBoard.lastElementChild;
|
||||
|
||||
strip.querySelectorAll( ".ol-select" ).forEach( select => {
|
||||
|
||||
switch( select.getAttribute( "data-point" ) ) {
|
||||
|
||||
case "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.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
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
} else {
|
||||
|
||||
// TODO: change status dropdown if status is different
|
||||
strip.setAttribute( "data-flight-status", flight.status );
|
||||
|
||||
strip.querySelectorAll( `input[name="takeoffTime"]:not(:focus)` ).forEach( el => {
|
||||
if ( el instanceof HTMLInputElement ) {
|
||||
el.value = this.timestampToLocaleTime( flight.takeoffTime );
|
||||
el.dataset.previousValue = el.value;
|
||||
}
|
||||
});
|
||||
|
||||
strip.querySelectorAll( `[data-point="timeToGo"]` ).forEach( el => {
|
||||
|
||||
if ( flight.takeoffTime > 0 && this.calculateTimeToGo( missionTime, flight.takeoffTime ).totalSeconds <= 120 ) {
|
||||
strip?.setAttribute( "data-time-warning", "level-1" );
|
||||
}
|
||||
|
||||
if ( el instanceof HTMLElement ) {
|
||||
el.innerText = this.timeToGo( flight.takeoffTime );
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
strip.toggleAttribute( "data-updating", false );
|
||||
@ -68,4 +227,22 @@ export class ATCBoardFlight extends ATCBoard {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
#updateTakeoffTime = function( flightId:string, time:number ) {
|
||||
|
||||
fetch( '/api/atc/flight/' + flightId, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Accept': '*/*',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
"body": JSON.stringify({
|
||||
"takeoffTime": time
|
||||
})
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -13,8 +13,29 @@ export class Dropdown {
|
||||
this.#value = <HTMLElement>this.#element.querySelector(".ol-select-value");
|
||||
this.#defaultValue = this.#value.innerText;
|
||||
this.#callback = callback;
|
||||
if (options != null)
|
||||
if (options != null) {
|
||||
this.setOptions(options);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Do open/close toggle
|
||||
this.#element.addEventListener("click", ev => {
|
||||
|
||||
if ( ev.target instanceof HTMLElement && ev.target.nodeName !== "A" ) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
ev.stopPropagation();
|
||||
this.#element.classList.toggle("is-open");
|
||||
|
||||
});
|
||||
|
||||
// Autoclose on mouseleave
|
||||
this.#element.addEventListener("mouseleave", ev => {
|
||||
this.#element.classList.remove("is-open");
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
setOptions(optionsList: string[])
|
||||
@ -27,7 +48,7 @@ export class Dropdown {
|
||||
div.appendChild(button);
|
||||
button.addEventListener("click", (e: MouseEvent) => {
|
||||
this.#value.innerText = option;
|
||||
this.#callback(option);
|
||||
this.#callback( option, e );
|
||||
});
|
||||
return div;
|
||||
}));
|
||||
|
||||
@ -124,6 +124,7 @@ function checkSessionHash(newSessionHash: string) {
|
||||
function setupEvents() {
|
||||
/* Generic clicks */
|
||||
document.addEventListener("click", (ev) => {
|
||||
|
||||
if (ev instanceof PointerEvent && ev.target instanceof HTMLElement) {
|
||||
const target = ev.target;
|
||||
if (target.classList.contains("olympus-dialog-close")) {
|
||||
@ -194,26 +195,6 @@ function setupEvents() {
|
||||
})
|
||||
});
|
||||
|
||||
/** Olympus UI ***/
|
||||
document.querySelectorAll(".ol-select").forEach(select => {
|
||||
|
||||
// Do open/close toggle
|
||||
select.addEventListener("click", ev => {
|
||||
|
||||
if ( ev.target instanceof HTMLElement && ev.target.nodeName !== "A" ) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
ev.stopPropagation();
|
||||
select.classList.toggle("is-open");
|
||||
});
|
||||
|
||||
// Autoclose on mouseleave
|
||||
select.addEventListener("mouseleave", ev => {
|
||||
select.classList.remove("is-open");
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
export function getMap() {
|
||||
|
||||
@ -1,5 +1,26 @@
|
||||
<div class="ol-panel ol-dialog ol-atc-board" data-feature-switch="atc">
|
||||
<div class="ol-panel ol-dialog ol-strip-board" data-feature-switch="atc">
|
||||
|
||||
<div class="ol-dialog-close" data-on-click="closeDialog"></div>
|
||||
|
||||
<div class="atc-strip-board"></div>
|
||||
<div class="ol-dialog-header">
|
||||
<div class="ol-strip-board-clock"></div>
|
||||
</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>
|
||||
Loading…
x
Reference in New Issue
Block a user