mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
Merge pull request #195 from Pax1601/193-atc-tower-manually-sort-strips
Flights now sortable.
This commit is contained in:
commit
79f616cf8d
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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 ) {
|
||||
|
||||
@ -6,6 +6,7 @@ export interface FlightInterface {
|
||||
id : string;
|
||||
boardId : string;
|
||||
name : string;
|
||||
order : number;
|
||||
status : "unknown";
|
||||
takeoffTime : number;
|
||||
unitId : number;
|
||||
|
||||
@ -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 = <HTMLElement>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 = <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']" );
|
||||
|
||||
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 = <HTMLInputElement>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
|
||||
})
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -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 = `<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">
|
||||
@ -42,7 +43,7 @@ export class ATCBoardGround extends ATCBoard {
|
||||
|
||||
<div data-point="timeToGo">${this.timeToGo( flight.takeoffTime )}</div>
|
||||
|
||||
<button class="deleteFlight">Delete</button>
|
||||
<button class="deleteFlight">×</button>
|
||||
</div>`;
|
||||
|
||||
stripBoard.insertAdjacentHTML( "beforeend", template );
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user