diff --git a/client/package-lock.json b/client/package-lock.json index 4e0499d0..5cf0baf1 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -21,9 +21,11 @@ }, "devDependencies": { "@types/gtag.js": "^0.0.12", + "@types/sortablejs": "^1.15.0", "browserify": "^17.0.0", "concurrently": "^7.6.0", "nodemon": "^2.0.20", + "sortablejs": "^1.15.0", "tsify": "^5.0.4", "typescript": "^4.9.4", "watchify": "^4.0.0" @@ -48,6 +50,12 @@ "@types/geojson": "*" } }, + "node_modules/@types/sortablejs": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@types/sortablejs/-/sortablejs-1.15.0.tgz", + "integrity": "sha512-qrhtM7M41EhH4tZQTNw2/RJkxllBx3reiJpTbgWCM2Dx0U1sZ6LwKp9lfNln9uqE26ZMKUaPEYaD4rzvOWYtZw==", + "dev": true + }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -2621,6 +2629,12 @@ "semver": "bin/semver.js" } }, + "node_modules/sortablejs": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.0.tgz", + "integrity": "sha512-bv9qgVMjUMf89wAvM6AxVvS/4MX3sPeN0+agqShejLU5z5GX4C75ow1O2e5k4L6XItUyAK3gH6AxSbXrOM5e8w==", + "dev": true + }, "node_modules/source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -3248,6 +3262,12 @@ "@types/geojson": "*" } }, + "@types/sortablejs": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@types/sortablejs/-/sortablejs-1.15.0.tgz", + "integrity": "sha512-qrhtM7M41EhH4tZQTNw2/RJkxllBx3reiJpTbgWCM2Dx0U1sZ6LwKp9lfNln9uqE26ZMKUaPEYaD4rzvOWYtZw==", + "dev": true + }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -5329,6 +5349,12 @@ } } }, + "sortablejs": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.0.tgz", + "integrity": "sha512-bv9qgVMjUMf89wAvM6AxVvS/4MX3sPeN0+agqShejLU5z5GX4C75ow1O2e5k4L6XItUyAK3gH6AxSbXrOM5e8w==", + "dev": true + }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", diff --git a/client/package.json b/client/package.json index 80775352..f86ac849 100644 --- a/client/package.json +++ b/client/package.json @@ -23,9 +23,11 @@ }, "devDependencies": { "@types/gtag.js": "^0.0.12", + "@types/sortablejs": "^1.15.0", "browserify": "^17.0.0", "concurrently": "^7.6.0", "nodemon": "^2.0.20", + "sortablejs": "^1.15.0", "tsify": "^5.0.4", "typescript": "^4.9.4", "watchify": "^4.0.0" diff --git a/client/public/images/buttons/atc.svg b/client/public/images/buttons/atc.svg new file mode 100644 index 00000000..6bc33c3b --- /dev/null +++ b/client/public/images/buttons/atc.svg @@ -0,0 +1,27 @@ + + + + + + + + diff --git a/client/public/images/buttons/reorder.svg b/client/public/images/buttons/reorder.svg new file mode 100644 index 00000000..4ae9b04e --- /dev/null +++ b/client/public/images/buttons/reorder.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + diff --git a/client/public/stylesheets/atc.css b/client/public/stylesheets/atc.css new file mode 100644 index 00000000..61f79d75 --- /dev/null +++ b/client/public/stylesheets/atc.css @@ -0,0 +1,211 @@ +/*** Control panel ***/ + +#atc-control-panel { + align-self: flex-end; + background: white; + border-radius: 10px; + display:flex; + margin: 0 0 50px 100px; + padding:5px; + position: absolute; + z-index: 9999; +} + +.atc-tool { + align-self: center; + border-radius: 10px; + display:none; + justify-self: center; + padding: 10px; + position: absolute; + z-index: 9999; +} + + +.atc-enabled .atc-tool { + display:flex; +} + + + + +#atc-flight-list { + flex-direction: column; +} + +#atc-flight-list table { + color:white; +} + +#atc-flight-list table td { + padding:0 10px; + text-align: center; +} + +#atc-flight-list table td:first-of-type { + text-align: left; +} + +#atc-flight-list table tr[data-status='checkedIn'] td { + background-color:goldenrod; +} + +#atc-flight-list table tr[data-status='readyToTaxi'] td { + background-color:darkgreen; +} + +#atc-flight-list table button { + background-color: #666; + border:1px solid white; + color:white; + font-weight: bold; + margin:2px 0; +} + + +.atc-strip-board { + align-self: center; + display:flex; + justify-self: center; + position: absolute; + z-index: 9999 ; +} + +.atc-strip-board-header { + display:none; +} + +.atc-strip-board-strips { + display:flex; + flex-direction: column; +} + +.atc-strip-board-strip { + display:flex; + flex-direction: row; +} + + +/* +.atc-strip-board-header { + background:black; + color:white; + display:none; + justify-content: right; +} + + +.atc-strip-board { + display:flex; + flex-direction: column; + row-gap: 5px; +} + +.atc-strip-board-strips { + display:flex; + flex-direction: column; + padding:10px; + row-gap: 5px; +} + +.atc-strip-board-strips > div { + align-items: center; + color:white; + column-gap: 2px; + display: flex; + flex-direction: row; + padding: 5px; +} + +.atc-strip-board-header > div, .atc-strip-board-strips > div > div { + text-align: center; + width: 75px; +} + +.atc-strip-board-header > .name { + width:150px; +} + +.atc-strip-board-header > div, .atc-strip-board-strips > div > div { + text-align: center; + width: 75px; +} + +.atc-strip-board-strips > div > .name { + text-align: left; + width:150px; +} + +.atc-strip-board-strips > div { + align-items: center; + column-gap: 5px; + display: flex; + flex-direction: row; + font-size:12px; + font-weight: 600; + padding: 5px; + row-gap: 5px; +} + + +/* + +.atc-strip-board-header, .atc-strip-board-strips > div { + align-items: center; + background:#FFF3; + color:white; + column-gap: 5px; + display: flex; + flex-direction: row; + font-size:12px; + font-weight: 600; + padding: 5px; + row-gap: 5px; +} + +.atc-strip-board-header { + background:black; + color:white; + display:none; + justify-content: right; +} + +.atc-strip-board-strips > div { + border-bottom:1px solid black; +} + +.atc-strip-board-header > div, .atc-strip-board-strips > div > div { + text-align: center; + width: 75px; +} + +.atc-strip-board-header > .name { + width:150px; +} + +.atc-strip-board-strips > div > .handle { + background: black; + border-radius: 50%; + cursor:grab; + height:10px; + width:10px; +} + +.atc-strip-board-strips > div > .name { + text-align: left; + width:150px; +} + +.atc-strip-board-strips > div > .warning { + background:red; + color: white; + font-weight: bold; +} + +.atc-strip-board-strips > div > .link-warning { + border: 1px solid red; + color: red; + font-weight: bold; + +} + */ diff --git a/client/public/stylesheets/olympus.css b/client/public/stylesheets/olympus.css new file mode 100644 index 00000000..e300f89f --- /dev/null +++ b/client/public/stylesheets/olympus.css @@ -0,0 +1,106 @@ +/* Variables definitions */ +:root { + --active-coalition-color: var(--blue-coalition-color); + --background-color-dark: #202831; + --background-color-light: #AAA; + --blue-coalition-color: #247be2; + --highlight-color: #FFF5; + --neutral-coalition-color: whitesmoke; + --neutral-coalition-text: #202831; + --red-coalition-color: #f32121; + --text-color: white; + --title-color: #d3e9ff; +} + + +* { + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + + +html { + font-family: 'Open Sans', sans-serif; +} + + + +.ol-panel { + background-color: var(--background-color-dark); + border-radius: 15px; + box-shadow: 0px 2px 5px #000A; + color:white; + font-size: 12px; + height:fit-content; + padding:10px; + width:fit-content; +} + + +.ol-panel-list { + display: flex; + flex-direction: column; + height: fit-content; + row-gap: 5px; + text-align: center; + width: fit-content; + border-radius: 5px; +} + +.ol-panel-list .list-item { + border-radius: 10px; + display:flex; + justify-content: space-between; + padding: 6px 10px; +} + +.ol-panel-list.sortable > .sortable-item { + align-items: center; + column-gap: 5px; + display:flex; + flex-direction: row; +} + +.ol-panel-list.sortable > .sortable-item > .handle { + cursor:grab; + filter:invert(100); +} + +.ol-panel-list.sortable > .sortable-item > .handle img { + max-width: 16px; +} + + +.ol-panel-info { + display:flex; + flex-direction: row; + justify-content: space-evenly; +} + +.ol-panel-info > .panel-section { + border-right: 1px solid #555; + padding:10px; +} + +.ol-panel-info > .panel-section:last-of-type { + border-right-width: 0; +} + + +.highlight-primary { + background-color: var(--highlight-color); +} + +.highlight-bluefor { + background-color: var(--blue-coalition-color); +} + +.highlight-redfor { + background-color: var(--red-coalition-color); +} + +.highlight-neutral { + background-color: var(--neutral-coalition-color); + color: var(--neutral-coalition-text) +} \ No newline at end of file diff --git a/client/public/stylesheets/style.css b/client/public/stylesheets/style.css index c71b28cc..3a3a32c2 100644 --- a/client/public/stylesheets/style.css +++ b/client/public/stylesheets/style.css @@ -16,6 +16,7 @@ @import url("mouseinfopanel.css"); @import url( "aic.css" ); +@import url( "atc.css" ); @import url("layout.css"); @@ -36,7 +37,7 @@ * { -moz-box-sizing: border-box; -webkit-box-sizing: border-box; - box-sizing: border-box; + box-sizing: border-box; } html { diff --git a/client/public/stylesheets/uikit.css b/client/public/stylesheets/uikit.css new file mode 100644 index 00000000..9e9ed469 --- /dev/null +++ b/client/public/stylesheets/uikit.css @@ -0,0 +1,54 @@ +body { + background-color:#eaeaea; +} + +#content-wrapper { + row-gap: 5px; + display: flex; + flex-direction: column; + flex-wrap: wrap; + height:100%; + width:100%; +} + +section { + column-gap: 20px; + display:flex; + flex-direction: row; +} + +.section-header { + font-size:125%; + font-weight: bold; + margin-bottom: 1vh; +} + +.content { + background:white; + border-radius: 10px; + height:fit-content; + margin-bottom:4vh; + padding:20px; + width:fit-content; +} + +.content-header { + color:#666; + letter-spacing:1px; + margin-bottom: 1vh; +} + +.content-body { + column-gap: 20px; + display:flex; + flex-direction: row; +} + +.caption { + margin:2vh 0 1vh 0; +} + + +#paragraph { + max-width: 750px; +} \ No newline at end of file diff --git a/client/public/uikit.html b/client/public/uikit.html new file mode 100644 index 00000000..c8620198 --- /dev/null +++ b/client/public/uikit.html @@ -0,0 +1,181 @@ + + + + Olympus UI Kit + + + + + +
+ +

Olympus UI Kit

+ +
Typeography
+
+ +
+ +
Headings
+
+ +
+

h1 | open sans | 32px

+

h2 | open sans | 24px

+

h3 | open sans | 18.72px

+

h4 | open sans | 16px

+
h5 | open sans | 13.28px
+
h6 | open sans | 10.72px
+
+ +
+ +
+ +
+ +
Paragraph
+
+ +
+
Plain
+

Nullam iaculis nisi sed mi tincidunt pretium blandit tempus urna. Vestibulum non ex vitae massa tristique auctor. Praesent orci justo, porttitor pellentesque convallis non, commodo at augue.

+
+ +
+
In a panel
+
+

Donec nibh est, fringilla sed pharetra eu, varius vel sem. Aliquam ac libero leo. Sed consectetur enim aliquam dui pellentesque luctus. Pellentesque vel iaculis quam.

+
+
+ +
+ +
+ +
+ +
.ol-panel
+
+ + +
+ +
Plain panel
+ +
+ +
+ +
+ Disconnected +
+ +
+ +
+ +
+ +
+ +
Panel list
+ +
+ +
+ +
Basic list
+ +
+
+
List item 1
+
List item 2
+
List item 3
+
+
+ +
+ +
+ +
List with .highlight-primary
+ +
+
+
List item with highlight-primary
+
List item with highlight-bluefor
+
List item with highlight-redfor
+
List item with highlight-neutral
+
+
+ +
+ +
+ +
Sortable list
+ +
+
+
+
+
List item 1
+
+
+
+
List item 2
+
+
+
+
List item 3
+
+
+ +
+ +
+ + + +
+ +
+ +
+ +
.ol-panel > .ol-panel-info
+ +
+ +
+ +
+
+
+ Info panel number 1 +
+
+ Info panel number 2 +
+
+ Info panel number 3 +
+
+
+ +
+ + +
+ +
+ +
+ + + +
+ + + \ No newline at end of file diff --git a/client/src/FeatureSwitches.ts b/client/src/FeatureSwitches.ts new file mode 100644 index 00000000..6cf723d0 --- /dev/null +++ b/client/src/FeatureSwitches.ts @@ -0,0 +1,77 @@ +export interface FeatureSwitchInterface { + "enabled": boolean, + "label": string, + "name": string, + "options"?: object, + "removeArtifactsIfDisabled"?: boolean +} + + +class FeatureSwitch { + enabled; + label; + name; + removeArtifactsIfDisabled = true; + + constructor( config:FeatureSwitchInterface ) { + + this.enabled = config.enabled; + this.label = config.label; + this.name = config.name; + + } + + + isEnabled() { + return this.enabled; + } + +} + +export class FeatureSwitches { + + #featureSwitches:FeatureSwitch[] = [ + + new FeatureSwitch({ + "enabled": false, + "label": "AIC", + "name": "aic" + }), + + new FeatureSwitch({ + "enabled": false, + "label": "ATC", + "name": "atc", + "options": { + "key": "value" + } + }) + + ]; + + + constructor() { + + this.#removeArtifacts(); + + } + + + getSwitch( switchName:string ) { + + return this.#featureSwitches.find( featureSwitch => featureSwitch.name === switchName ); + + } + + + #removeArtifacts() { + + for ( const featureSwitch of this.#featureSwitches ) { + if ( !featureSwitch.isEnabled() && featureSwitch.removeArtifactsIfDisabled !== false ) { + document.querySelectorAll( "[data-feature-switch='" + featureSwitch.name + "']" ).forEach( el => el.remove() ); + } + } + + } + +} \ No newline at end of file diff --git a/client/src/ToggleableFeature.ts b/client/src/ToggleableFeature.ts new file mode 100644 index 00000000..5873fb6e --- /dev/null +++ b/client/src/ToggleableFeature.ts @@ -0,0 +1,35 @@ +export abstract class ToggleableFeature { + + #status:boolean = false; + + + constructor( defaultStatus:boolean ) { + + this.#status = defaultStatus; + + this.onStatusUpdate(); + + } + + + getStatus() : boolean { + return this.#status; + } + + + protected onStatusUpdate() {} + + + toggleStatus( force?:boolean ) : void { + + if ( force ) { + this.#status = force; + } else { + this.#status = !this.#status; + } + + this.onStatusUpdate(); + + } + +} \ No newline at end of file diff --git a/client/src/aic/aic.ts b/client/src/aic/aic.ts index a9d65322..a28aaa9d 100644 --- a/client/src/aic/aic.ts +++ b/client/src/aic/aic.ts @@ -1,12 +1,11 @@ +import { ToggleableFeature } from "../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 { - - #status:boolean = true; +export class AIC extends ToggleableFeature { #formations = [ @@ -18,8 +17,10 @@ export class AIC { constructor() { + + super( false ); - this.#onStatusUpdate(); + this.onStatusUpdate(); // This feels kind of dirty let $aicFormationList = document.getElementById( "aic-formation-list" ); @@ -80,30 +81,10 @@ export class AIC { } - getStatus() { - return this.#status; - } - - - #onStatusUpdate() { - - const status:boolean = this.getStatus(); + onStatusUpdate() { // Update the DOM - document.body.classList.toggle( "aic-enabled", status ); - - } - - - toggleStatus(force?:boolean) { - - if ( force ) { - this.#status = force; - } else { - this.#status = !this.#status; - } - - this.#onStatusUpdate(); + document.body.classList.toggle( "aic-enabled", this.getStatus() ); } diff --git a/client/src/atc/ATC.ts b/client/src/atc/ATC.ts new file mode 100644 index 00000000..c409b02e --- /dev/null +++ b/client/src/atc/ATC.ts @@ -0,0 +1,87 @@ +import { ToggleableFeature } from "../ToggleableFeature"; +import Sortable from 'sortablejs'; +import { ATCFLightList } from "./FlightList"; + +export class ATC extends ToggleableFeature { + + constructor() { + + super( true ); + + //this.#generateFlightList(); + + let $list = document.getElementById( "atc-strip-board-arrivals" ); + + if ( $list instanceof HTMLElement ) { + Sortable.create( $list, { + "handle": ".handle" + }); + } + + } + + + #generateFlightList() { + + const flightList = new ATCFLightList(); + const flights:any = flightList.getFlights( true ); + + const $tbody = document.getElementById( "atc-flight-list-table-body" ); + + if ( $tbody instanceof HTMLElement ) { + + if ( flights.length > 0 ) { + + let flight:any = {}; + + let $button, i; + + for ( [ i, flight ] of flights.entries() ) { + + const $row = document.createElement( "tr" ); + $row.dataset.status = flight.status + + let $td = document.createElement( "td" ); + $td.innerText = flight.name; + $row.appendChild( $td ); + + $td = document.createElement( "td" ); + $td.innerText = flight.takeOffTime; + $row.appendChild( $td ); + + $td = document.createElement( "td" ); + $td.innerText = "00:0" + ( 5 + i ); + $row.appendChild( $td ); + + $td = document.createElement( "td" ); + $td.innerText = flight.status; + $row.appendChild( $td ); + + + $td = document.createElement( "td" ); + $button = document.createElement( "button" ); + $button.innerText = "..."; + + $td.appendChild( $button ); + + $row.appendChild( $td ); + + + $tbody.appendChild( $row ); + + } + + } + + } + + } + + + protected onStatusUpdate(): void { + + document.body.classList.toggle( "atc-enabled", this.getStatus() ); + + } + +} \ No newline at end of file diff --git a/client/src/atc/ATCAPI.ts b/client/src/atc/ATCAPI.ts deleted file mode 100644 index 22aaa3eb..00000000 --- a/client/src/atc/ATCAPI.ts +++ /dev/null @@ -1,14 +0,0 @@ - -export interface ATCAPIInterface { - get: CallableFunction -} - -export abstract class ATCAPI { - - - - constructor() { - - } - -} \ No newline at end of file diff --git a/client/src/atc/ATCMockAPI.ts b/client/src/atc/ATCMockAPI.ts new file mode 100644 index 00000000..9720e2d0 --- /dev/null +++ b/client/src/atc/ATCMockAPI.ts @@ -0,0 +1,7 @@ +export abstract class ATCMockAPI { + + constructor() {} + + generateMockData() {} + +} \ No newline at end of file diff --git a/client/src/atc/ATCMockAPI/Flights.ts b/client/src/atc/ATCMockAPI/Flights.ts new file mode 100644 index 00000000..b710b016 --- /dev/null +++ b/client/src/atc/ATCMockAPI/Flights.ts @@ -0,0 +1,40 @@ +import { ATCMockAPI } from "../ATCMockAPI"; + +export class ATCMockAPI_Flights extends ATCMockAPI { + + + generateMockData() { + + let data = []; + const statuses = [ "unknown", "checkedIn", "readyToTaxi" ] + + for ( const [ i, flightName ] of [ "Shark", "Whale", "Dolphin" ].entries() ) { + + data.push({ + "name": flightName, + "status": statuses[ i ], + "takeOffTime": "18:0" + i + }); + + } + + localStorage.setItem( "flightList", JSON.stringify( data ) ); + + } + + + get( generateMockDataIfEmpty?:boolean ) : object { + + generateMockDataIfEmpty = generateMockDataIfEmpty || false; + + let data = localStorage.getItem( "flightList" ) || "[]"; + + if ( data === "[]" && generateMockDataIfEmpty ) { + this.generateMockData(); + } + + return JSON.parse( data ); + + } + +} \ No newline at end of file diff --git a/client/src/atc/FlightList.ts b/client/src/atc/FlightList.ts new file mode 100644 index 00000000..a24a55e2 --- /dev/null +++ b/client/src/atc/FlightList.ts @@ -0,0 +1,18 @@ +import { ATCMockAPI_Flights } from "./ATCMockAPI/Flights"; + +export class ATCFLightList { + + + constructor() { + + + + } + + + getFlights( generateMockDataIfEmpty?:boolean ) { + let api = new ATCMockAPI_Flights(); + return api.get( generateMockDataIfEmpty ); + } + +} \ No newline at end of file diff --git a/client/src/index.ts b/client/src/index.ts index a04de9e5..de4c3ae4 100644 --- a/client/src/index.ts +++ b/client/src/index.ts @@ -14,6 +14,8 @@ import { Slider } from "./controls/slider"; import { AIC } from "./aic/AIC"; import { VisibilityControlPanel } from "./panels/visibilitycontrolpanel"; +import { ATC } from "./atc/ATC"; +import { FeatureSwitches } from "./FeatureSwitches"; /* TODO: should this be a class? */ var map: Map; @@ -41,13 +43,22 @@ var aic: AIC; var aicToggleButton: Button; var aicHelpButton: Button; + +var atc: ATC; +var atcToggleButton: Button; + var altitudeSlider: Slider; var airspeedSlider: Slider; var connected: boolean; var activeCoalition: string; +var featureSwitches; + function setup() { + + featureSwitches = new FeatureSwitches(); + /* Initialize */ map = new Map('map-container'); unitsManager = new UnitsManager(); @@ -63,9 +74,6 @@ function setup() { mouseInfoPanel = new MouseInfoPanel("mouse-info-panel"); visibilityControlPanel = new VisibilityControlPanel("visibility-control-panel"); - scenarioDropdown = new Dropdown("scenario-dropdown", ["Caucasus", "Syria", "Marianas", "Nevada", "South Atlantic", "The channel"], () => { }); - mapSourceDropdown = new Dropdown("map-source-dropdown", map.getLayers(), (option: string) => map.setLayer(option)); - missionData = new MissionData(); /* Unit control buttons */ @@ -79,15 +87,20 @@ function setup() { airspeedSlider = new Slider("airspeed-slider", 0, 100, "kts", (value: number) => getUnitsManager().selectedUnitsSetSpeed(value / 1.94384)); /* AIC */ - aic = new AIC(); - aicToggleButton = new Button( "toggle-aic-button", ["images/buttons/radar.svg"], () => { - aic.toggleStatus(); - }); + let aicFeatureSwitch = featureSwitches.getSwitch( "aic" ); - aicHelpButton = new Button( "aic-help-button", [ "images/buttons/question-mark.svg" ], () => { - aic.toggleHelp(); - }); + if ( aicFeatureSwitch?.isEnabled() ) { + aic = new AIC(); + + aicToggleButton = new Button( "toggle-aic-button", ["images/buttons/radar.svg"], () => { + aic.toggleStatus(); + }); + + aicHelpButton = new Button( "aic-help-button", [ "images/buttons/question-mark.svg" ], () => { + aic.toggleHelp(); + }); + } /* Generic clicks */ @@ -104,6 +117,22 @@ function setup() { }); + + /*** ATC ***/ + + let atcFeatureSwitch = featureSwitches.getSwitch( "atc" ); + + if ( atcFeatureSwitch?.isEnabled() ) { + + atc = new ATC(); + + atcToggleButton = new Button( "atc-toggle-button", [ "images/buttons/atc.svg" ], () => { + atc.toggleStatus(); + } ); + + } + + /* Default values */ activeCoalition = "blue"; connected = false; diff --git a/client/views/aiccontrolpanel.ejs b/client/views/aiccontrolpanel.ejs index 5a52381d..a1486db4 100644 --- a/client/views/aiccontrolpanel.ejs +++ b/client/views/aiccontrolpanel.ejs @@ -1,9 +1,9 @@ -
+
-
+
×
AIC Help
diff --git a/client/views/aicformationpanel.ejs b/client/views/aicformationpanel.ejs index 81f4c39e..1f248c2c 100644 --- a/client/views/aicformationpanel.ejs +++ b/client/views/aicformationpanel.ejs @@ -1,4 +1,4 @@ -
+

My callsign

@@ -8,7 +8,7 @@
-
+

Control

diff --git a/client/views/atc.ejs b/client/views/atc.ejs new file mode 100644 index 00000000..87f727e3 --- /dev/null +++ b/client/views/atc.ejs @@ -0,0 +1,105 @@ +
+
+
+ + +
+ + + + + + + + + + + +
FlightT/OTTGStatus
+
+ + +
+ +
+
Name
+
BR
+
t. Alt
+
Alt
+
t. Spd
+
Speed
+
RWY
+
Line
+
+ + +
+
+
+
+
Shark 3
+
250 / 28
+
-
+
10000
+
-
+
421
+
-
+
-
+
+
+
+
+
+
Shark 2
+
250 / 24
+
6000
+
6000
+
-
+
400
+
-
+
-
+
+
+
+
+
+
Shark 1
+ +
5000
+
5100
+
-
+
367
+
-
+
-
+
+
+
+
+
+
Dolphin 1
+
250 / 4
+ +
4100
+
-
+
511
+
25L
+
2nd
+
+
+
+
+
+
Whale 1
+
070 / 2
+
1500
+
1650
+ +
312
+
25L
+
1st
+
+
+
+ + +
\ No newline at end of file diff --git a/client/views/index.ejs b/client/views/index.ejs index 96bb259f..0b89363c 100644 --- a/client/views/index.ejs +++ b/client/views/index.ejs @@ -31,9 +31,12 @@ <%- include('visibilitycontrolpanel.ejs') %> <%- include('connectionstatuspanel.ejs') %> <%- include('mouseinfopanel.ejs') %> + <%- include('aiccontrolpanel.ejs') %> <%- include('aicformationpanel.ejs') %> + <%- include( 'atc.ejs' ) %> + diff --git a/package-lock.json b/package-lock.json index 8a4f50b6..54a219d1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,8 @@ "": { "devDependencies": { "chai": "^4.3.7", - "mocha": "^10.2.0" + "mocha": "^10.2.0", + "sortablejs": "^1.15.0" } }, "node_modules/ansi-colors": { @@ -827,6 +828,12 @@ "randombytes": "^2.1.0" } }, + "node_modules/sortablejs": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.0.tgz", + "integrity": "sha512-bv9qgVMjUMf89wAvM6AxVvS/4MX3sPeN0+agqShejLU5z5GX4C75ow1O2e5k4L6XItUyAK3gH6AxSbXrOM5e8w==", + "dev": true + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", diff --git a/package.json b/package.json index bd57792c..0ba95f08 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "devDependencies": { "chai": "^4.3.7", - "mocha": "^10.2.0" + "mocha": "^10.2.0", + "sortablejs": "^1.15.0" } }