From d77735581ad2c5b41889b3be79d236c78a6111dd Mon Sep 17 00:00:00 2001 From: PeekabooSteam Date: Sun, 16 Apr 2023 23:34:00 +0100 Subject: [PATCH] Flights now sortable. --- client/public/stylesheets/atc.css | 67 ++++++--- client/public/stylesheets/olympus.css | 11 +- client/routes/api/atc.js | 30 ++++ client/src/atc/atc.ts | 1 + client/src/atc/atcboard.ts | 192 ++++++++++++++++++++++---- client/src/atc/board/ground.ts | 5 +- client/src/units/unitsmanager.ts | 18 +++ 7 files changed, 276 insertions(+), 48 deletions(-) diff --git a/client/public/stylesheets/atc.css b/client/public/stylesheets/atc.css index baab81f5..9258da8c 100644 --- a/client/public/stylesheets/atc.css +++ b/client/public/stylesheets/atc.css @@ -42,7 +42,7 @@ text-align: center; } -.ol-strip-board-headers > *, .ol-strip-board-strip > * { +.ol-strip-board-headers > *, .ol-strip-board-strip > [data-point] { padding: 4px; text-overflow: ellipsis; white-space: nowrap; @@ -67,19 +67,33 @@ border:1px solid #cc0000; } -[data-board-type="ground"] .ol-strip-board-headers :nth-child(1), -[data-board-type="ground"] .ol-strip-board-headers :nth-child(2), -[data-board-type="ground"] .ol-strip-board-strip :nth-child(1), -[data-board-type="ground"] .ol-strip-board-strip :nth-child(2) { - width:120px; +.ol-strip-board-headers :nth-child(1) { + width:12px; } -[data-board-type="ground"] .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; } +.ol-strip-board-headers :nth-child(6), +.ol-strip-board-strip :nth-child(6) { + width:20px; +} + + + + + + [data-board-type="tower"] .ol-strip-board-strip > * { text-align: center; } @@ -88,11 +102,11 @@ text-align: left; } -[data-board-type="tower"] .ol-strip-board-strip :nth-child(2) input { +[data-board-type="tower"] .ol-strip-board-strip :nth-child(3) input { width:25px; } -[data-board-type="tower"] .ol-strip-board-strip :nth-child(2) { +[data-board-type="tower"] .ol-strip-board-strip :nth-child(3) { font-size:10px; } @@ -112,6 +126,7 @@ .ol-strip-board-add-flight { display:flex; flex-flow: row nowrap; + position:relative; } @@ -126,24 +141,36 @@ height: 12px; } -.ol-strip-board-add-flight button[type="submit"] { - 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-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 ); +.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"] { - translate: 0 100%; + bottom:20px; } [data-board-type="tower"] { - translate: 0 -100%; + top:100px; } \ No newline at end of file diff --git a/client/public/stylesheets/olympus.css b/client/public/stylesheets/olympus.css index 2864d9cc..cc006d2e 100644 --- a/client/public/stylesheets/olympus.css +++ b/client/public/stylesheets/olympus.css @@ -149,7 +149,7 @@ form > div { .ol-select.narrow:not(.ol-select-image)>.ol-select-value { opacity: .9; - padding:6px 30px 6px 15px; + padding:4px 30px 4px 15px; } .ol-select:not(.ol-select-image)>.ol-select-value svg { @@ -534,6 +534,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 { display: flex; diff --git a/client/routes/api/atc.js b/client/routes/api/atc.js index f29e88e6..a89df3b0 100644 --- a/client/routes/api/atc.js +++ b/client/routes/api/atc.js @@ -46,6 +46,15 @@ Flight.prototype.getData = function() { } +Flight.prototype.setOrder = function( order ) { + + this.order = order; + + return true; + +} + + Flight.prototype.setStatus = function( status ) { if ( [ "unknown", "checkedin", "readytotaxi", "clearedtotaxi", "halted", "terminated" ].indexOf( status ) < 0 ) { @@ -172,6 +181,27 @@ 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 ) => { if ( !req.body.boardId ) { diff --git a/client/src/atc/atc.ts b/client/src/atc/atc.ts index 158a2f1a..4855eaaf 100644 --- a/client/src/atc/atc.ts +++ b/client/src/atc/atc.ts @@ -6,6 +6,7 @@ export interface FlightInterface { id : string; boardId : string; name : string; + order : number; status : "unknown"; takeoffTime : number; unitId : number; diff --git a/client/src/atc/atcboard.ts b/client/src/atc/atcboard.ts index 79c066a4..52877996 100644 --- a/client/src/atc/atcboard.ts +++ b/client/src/atc/atcboard.ts @@ -2,6 +2,9 @@ import { Dropdown } from "../controls/dropdown"; import { zeroAppend } from "../other/utils"; import { ATC } from "./atc"; import { Unit } from "../units/unit"; +import { getUnitsManager } from ".."; +import Sortable from "sortablejs"; +import { FlightInterface } from "./atc"; export interface StripBoardStripInterface { "id": string, @@ -43,6 +46,30 @@ export abstract class ATCBoard { this.#clockElement = this.getBoardElement().querySelector( ".ol-strip-board-clock" ); + 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 ); @@ -63,6 +90,8 @@ export abstract class ATCBoard { this.#setupAddFlight(); + //this.#_setupDemoData(); + } @@ -177,7 +206,10 @@ export abstract class ATCBoard { headers: { 'Accept': '*/*', 'Content-Type': 'application/json' - } + }, + "body": JSON.stringify({ + "boardId": this.getBoardId() + }) }); } @@ -220,6 +252,13 @@ export abstract class ATCBoard { } + getUnitIdsBeingMonitored() { + + return this.#unitIdsBeingMonitored; + + } + + setTemplates( templates:{[key:string]: string} ) { this.#templates = templates; } @@ -246,39 +285,82 @@ export abstract class ATCBoard { }); - this.getBoardElement().querySelectorAll( "form.ol-strip-board-add-flight" ).forEach( form => { + const form = this.getBoardElement().querySelector( "form.ol-strip-board-add-flight" ); + const suggestions = form.querySelector( ".ol-auto-suggest" ); + const unitName = form.querySelector( "input[name='unitName']" ); - if ( form instanceof HTMLFormElement ) { + const toggleSuggestions = ( bool:boolean ) => { + suggestions.toggleAttribute( "data-has-suggestions", bool ); + } - form.addEventListener( "submit", ev => { + 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 ) => { - ev.preventDefault(); - - - if ( ev.target instanceof HTMLFormElement ) { - - const elements = ev.target.elements; - const flightName = elements[1]; - - if ( flightName.value === "" ) { - return; - } - - // this.addFlight( -1, flightName.value ); - - form.reset(); - + 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 ); + }); - - this.getBoardElement().querySelectorAll( ".add-flight-by-click" ).forEach( el => { - el.addEventListener( "click", () => { + form.querySelectorAll( ".add-flight-by-click" ).forEach( el => { + el.addEventListener( "click", ev => { + ev.preventDefault(); toggleIsAddFlightByClickEnabled(); }); }); @@ -286,6 +368,22 @@ export abstract class ATCBoard { } + sortFlights( flights:FlightInterface[] ) { + + flights.sort( ( a, b ) => { + + const aVal = a.order; + const bVal = b.order; + + return ( aVal > bVal ) ? 1 : -1; + + }); + + return flights; + + } + + startUpdates() { this.#updateInterval = setInterval( () => { @@ -332,5 +430,49 @@ export abstract class ATCBoard { } + + #_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" : 1 + }) + }); + + fetch( '/api/atc/flight/', { + method: 'POST', + headers: { + 'Accept': '*/*', + 'Content-Type': 'application/json' + }, + "body": JSON.stringify({ + "boardId" : this.getBoardId(), + "name" : this.getBoardId() + " 3", + "unitId" : 1 + }) + }); + + } + } \ No newline at end of file diff --git a/client/src/atc/board/ground.ts b/client/src/atc/board/ground.ts index e722e03f..2baf4862 100644 --- a/client/src/atc/board/ground.ts +++ b/client/src/atc/board/ground.ts @@ -14,7 +14,7 @@ export class ATCBoardGround extends ATCBoard { update() { - const flights = Object.values( this.getATC().getDataHandler().getFlights( this.getBoardId() ) ); + const flights = this.sortFlights( Object.values( this.getATC().getDataHandler().getFlights( this.getBoardId() ) ) ); const stripBoard = this.getStripBoardElement(); const missionTime = this.getATC().getMissionDateTime().getTime(); @@ -31,6 +31,7 @@ export class ATCBoardGround extends ATCBoard { if ( !strip ) { const template = `
+
${flight.name}
@@ -42,7 +43,7 @@ export class ATCBoardGround extends ATCBoard {
${this.timeToGo( flight.takeoffTime )}
- +
`; stripBoard.insertAdjacentHTML( "beforeend", template ); diff --git a/client/src/units/unitsmanager.ts b/client/src/units/unitsmanager.ts index b8badd5e..a3319a85 100644 --- a/client/src/units/unitsmanager.ts +++ b/client/src/units/unitsmanager.ts @@ -23,6 +23,24 @@ export class UnitsManager { 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() { return this.#units; }