diff --git a/client/app.js b/client/app.js index 210711e8..c94b8cb2 100644 --- a/client/app.js +++ b/client/app.js @@ -27,8 +27,7 @@ const DemoDataGenerator = require('./demo.js'); var demoDataGenerator = new DemoDataGenerator(10); -app.get('/demo/units/refresh', (req, res) => demoDataGenerator.unitsRefresh(req, res)); -app.get('/demo/units/update', (req, res) => demoDataGenerator.unitsUpdate(req, res)); +app.get('/demo/units', (req, res) => demoDataGenerator.units(req, res)); app.get('/demo/logs', (req, res) => demoDataGenerator.logs(req, res)); app.get('/demo/bullseyes', (req, res) => demoDataGenerator.bullseyes(req, res)); app.get('/demo/airbases', (req, res) => demoDataGenerator.airbases(req, res)); diff --git a/client/demo.js b/client/demo.js index f35bd7bc..0860c08a 100644 --- a/client/demo.js +++ b/client/demo.js @@ -1,21 +1,23 @@ const DEMO_UNIT_DATA = { ["1"]:{ - AI: true, - name: "F-5E", - unitName: "Olympus 1-1", - groupName: "Group 1", - alive: true, - category: "Aircraft", + baseData: { + AI: true, + name: "F-5E", + unitName: "Olympus 1-1", + groupName: "Group 1", + alive: true, + category: "Aircraft", + }, flightData: { - latitude: 37.2, + latitude: 37.20, longitude: -115.8, altitude: 2000, heading: 0.5, speed: 300 }, missionData: { - fuel: 0.5, + fuel: 50, flags: {human: false}, ammo: [], targets: [], @@ -42,12 +44,14 @@ const DEMO_UNIT_DATA = { } }, ["2"]:{ - AI: true, - name: "F-5E", - unitName: "Olympus 1-2", - groupName: "Group 1", - alive: true, - category: "Aircraft", + baseData: { + AI: true, + name: "F-5E", + unitName: "Olympus 1-2", + groupName: "Group 1", + alive: true, + category: "Aircraft", + }, flightData: { latitude: 37.2, longitude: -115.75, @@ -83,12 +87,14 @@ const DEMO_UNIT_DATA = { } }, ["3"]:{ - AI: true, - name: "2S6 Tunguska", - unitName: "Olympus 1-3", - groupName: "Group 1", - alive: true, - category: "GroundUnit", + baseData: { + AI: true, + name: "2S6 Tunguska", + unitName: "Olympus 1-3", + groupName: "Group 1", + alive: true, + category: "GroundUnit", + }, flightData: { latitude: 37.175, longitude: -115.8, @@ -124,12 +130,14 @@ const DEMO_UNIT_DATA = { } }, ["4"]:{ - AI: true, - name: "2S6 Tunguska", - unitName: "Olympus 1-4", - groupName: "Group 1", - alive: true, - category: "GroundUnit", + baseData: { + AI: true, + name: "2S6 Tunguska", + unitName: "Olympus 1-4", + groupName: "Group 1", + alive: true, + category: "GroundUnit", + }, flightData: { latitude: 37.175, longitude: -115.75, @@ -165,12 +173,14 @@ const DEMO_UNIT_DATA = { } }, ["5"]:{ - AI: true, - name: "M-60", - unitName: "Olympus 1-3", - groupName: "Group 1", - alive: true, - category: "GroundUnit", + baseData: { + AI: true, + name: "M-60", + unitName: "Olympus 1-3", + groupName: "Group 1", + alive: true, + category: "GroundUnit", + }, flightData: { latitude: 37.15, longitude: -115.8, @@ -206,12 +216,14 @@ const DEMO_UNIT_DATA = { } }, ["6"]:{ - AI: true, - name: "M-60", - unitName: "Olympus 1-4", - groupName: "Group 1", - alive: true, - category: "GroundUnit", + baseData: { + AI: true, + name: "M-60", + unitName: "Olympus 1-4", + groupName: "Group 1", + alive: true, + category: "GroundUnit", + }, flightData: { latitude: 37.15, longitude: -115.75, @@ -245,7 +257,265 @@ const DEMO_UNIT_DATA = { ROE: "None", reactionToThreat: "None", } - } + }, + ["7"]:{ + baseData: { + AI: true, + name: "CVN-75", + unitName: "Olympus 1-7", + groupName: "Group 1", + alive: true, + category: "NavyUnit", + }, + flightData: { + latitude: 37.125, + longitude: -115.8, + altitude: 2000, + heading: 0.5, + speed: 300 + }, + missionData: { + fuel: 0.5, + flags: {human: false}, + ammo: [], + targets: [], + hasTask: true, + coalition: "blue" + }, + formationData: { + formation: "Echelon", + isLeader: false, + isWingman: false, + leaderID: null, + wingmen: [], + wingmenIDs: [] + }, + taskData: { + currentTask: "Example task", + activePath: undefined, + targetSpeed: 400, + targetAltitude: 3000 + }, + optionsData: { + ROE: "None", + reactionToThreat: "None", + } + }, + ["8"]:{ + baseData: { + AI: true, + name: "CVN-75", + unitName: "Olympus 1-8", + groupName: "Group 1", + alive: true, + category: "NavyUnit", + }, + flightData: { + latitude: 37.125, + longitude: -115.75, + altitude: 2000, + heading: 0.5, + speed: 300 + }, + missionData: { + fuel: 0.5, + flags: {human: false}, + ammo: [], + targets: [], + hasTask: true, + coalition: "red" + }, + formationData: { + formation: "Echelon", + isLeader: false, + isWingman: false, + leaderID: null, + wingmen: [], + wingmenIDs: [] + }, + taskData: { + currentTask: "Example task", + activePath: undefined, + targetSpeed: 400, + targetAltitude: 3000 + }, + optionsData: { + ROE: "None", + reactionToThreat: "None", + } + }, + ["9"]:{ + baseData: { + AI: true, + name: "CVN-75", + unitName: "Olympus 1-9", + groupName: "Group 1", + alive: false, + category: "Aircraft", + }, + flightData: { + latitude: 37.10, + longitude: -115.75, + altitude: 2000, + heading: 0.5, + speed: 300 + }, + missionData: { + fuel: 0.5, + flags: {human: false}, + ammo: [], + targets: [], + hasTask: true, + coalition: "red" + }, + formationData: { + formation: "Echelon", + isLeader: false, + isWingman: false, + leaderID: null, + wingmen: [], + wingmenIDs: [] + }, + taskData: { + currentTask: "Example task", + activePath: undefined, + targetSpeed: 400, + targetAltitude: 3000 + }, + optionsData: { + ROE: "None", + reactionToThreat: "None", + } + }, + ["10"]:{ + baseData: { + AI: true, + name: "CVN-75", + unitName: "Olympus 1-10", + groupName: "Group 1", + alive: false, + category: "Aircraft", + }, + flightData: { + latitude: 37.10, + longitude: -115.8, + altitude: 2000, + heading: 0.5, + speed: 300 + }, + missionData: { + fuel: 0.5, + flags: {human: false}, + ammo: [], + targets: [], + hasTask: true, + coalition: "blue" + }, + formationData: { + formation: "Echelon", + isLeader: false, + isWingman: false, + leaderID: null, + wingmen: [], + wingmenIDs: [] + }, + taskData: { + currentTask: "Example task", + activePath: undefined, + targetSpeed: 400, + targetAltitude: 3000 + }, + optionsData: { + ROE: "None", + reactionToThreat: "None", + } + }, + ["11"]:{ + baseData: { + AI: true, + name: "CVN-75", + unitName: "Olympus 1-11", + groupName: "Group 1", + alive: false, + category: "Missile", + }, + flightData: { + latitude: 37.075, + longitude: -115.8, + altitude: 2000, + heading: 0.5, + speed: 300 + }, + missionData: { + fuel: 0.5, + flags: {human: false}, + ammo: [], + targets: [], + hasTask: true, + coalition: "blue" + }, + formationData: { + formation: "Echelon", + isLeader: false, + isWingman: false, + leaderID: null, + wingmen: [], + wingmenIDs: [] + }, + taskData: { + currentTask: "Example task", + activePath: undefined, + targetSpeed: 400, + targetAltitude: 3000 + }, + optionsData: { + ROE: "None", + reactionToThreat: "None", + } + }, + ["12"]:{ + baseData: { + AI: true, + name: "CVN-75", + unitName: "Olympus 1-12", + groupName: "Group 1", + alive: false, + category: "Missile", + }, + flightData: { + latitude: 37.075, + longitude: -115.75, + altitude: 2000, + heading: 0.5, + speed: 300 + }, + missionData: { + fuel: 0.5, + flags: {human: false}, + ammo: [], + targets: [], + hasTask: true, + coalition: "red" + }, + formationData: { + formation: "Echelon", + isLeader: false, + isWingman: false, + leaderID: null, + wingmen: [], + wingmenIDs: [] + }, + taskData: { + currentTask: "Example task", + activePath: undefined, + targetSpeed: 400, + targetAltitude: 3000 + }, + optionsData: { + ROE: "None", + reactionToThreat: "None", + } + } } class DemoDataGenerator { @@ -253,18 +523,8 @@ class DemoDataGenerator { { this.demoUnits = this.generateRandomUnitsDemoData(unitsNumber); } - - unitsRefresh(req, res) { - var ret = this.demoUnits; - res.send(JSON.stringify(ret)); - } - unitsUpdate(req, res){ - //Object.keys(this.demoUnits.units).forEach((ID) => { - // this.demoUnits.units[ID].flightData.heading += 0.05; - // this.demoUnits.units[ID].flightData.latitude += 0.001 * Math.cos(this.demoUnits.units[ID].flightData.heading); - // this.demoUnits.units[ID].flightData.longitude += 0.001 * Math.sin(this.demoUnits.units[ID].flightData.heading); - //}); + units(req, res){ var ret = this.demoUnits; res.send(JSON.stringify(ret)); }; diff --git a/client/src/index.ts b/client/src/index.ts index e1ec24af..46893fe3 100644 --- a/client/src/index.ts +++ b/client/src/index.ts @@ -10,14 +10,14 @@ import { AIC } from "./aic/aic"; import { ATC } from "./atc/ATC"; import { FeatureSwitches } from "./FeatureSwitches"; import { LogPanel } from "./panels/logpanel"; -import { getAirbases, getBulllseye as getBulllseyes, getUnits } from "./server/server"; +import { getAirbases, getBulllseye as getBulllseyes, getUnits, toggleDemoEnabled } from "./server/server"; var map: Map; var contextMenu: ContextMenu; var unitsManager: UnitsManager; var missionHandler: MissionHandler; - + var aic: AIC; var atc: ATC; @@ -38,144 +38,48 @@ function setup() { featureSwitches = new FeatureSwitches(); - /* Initialize */ + /* Initialize base functionalitites*/ map = new Map('map-container'); unitsManager = new UnitsManager(); missionHandler = new MissionHandler(); + /* Context menus */ contextMenu = new ContextMenu("contextmenu"); - + + /* Panels */ unitInfoPanel = new UnitInfoPanel("unit-info-panel"); unitControlPanel = new UnitControlPanel("unit-control-panel"); connectionStatusPanel = new ConnectionStatusPanel("connection-status-panel"); mouseInfoPanel = new MouseInfoPanel("mouse-info-panel"); //logPanel = new LogPanel("log-panel"); - missionHandler = new MissionHandler(); - /* AIC */ - let aicFeatureSwitch = featureSwitches.getSwitch( "aic" ); - if ( aicFeatureSwitch?.isEnabled() ) { + let aicFeatureSwitch = featureSwitches.getSwitch("aic"); + if (aicFeatureSwitch?.isEnabled()) { aic = new AIC(); // TODO: add back buttons } - /* 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" ) ) { - target.closest( "div.olympus-dialog" )?.classList.add( "hide" ); - } - - const triggerElement = target.closest( "[data-on-click]" ); - - if ( triggerElement instanceof HTMLElement ) { - const eventName:string = triggerElement.dataset.onClick || ""; - let params = JSON.parse( triggerElement.dataset.onClickParams || "{}" ); - params._element = triggerElement; - - if ( eventName ) { - document.dispatchEvent( new CustomEvent( eventName, { - detail: params - } ) ); - } - - } - - } - - }); - - - /* Keyup events */ - document.addEventListener( "keyup", ev => { - - switch( ev.code ) { - case "KeyL": - document.body.toggleAttribute( "data-hide-labels" ); - break; - } - - }); - - - /* - const unitName = document.getElementById( "unit-name" ); - - if ( unitName instanceof HTMLInputElement ) { - - unitName.addEventListener( "change", ev => { - unitName.setAttribute( "disabled", "true" ); - unitName.setAttribute( "readonly", "true" ); - - // Do something with this: - console.log( unitName.value ); - }); - - - document.addEventListener( "editUnitName", ev => { - - unitName.removeAttribute( "disabled" ); - unitName.removeAttribute( "readonly" ); - unitName.focus(); - - }); - - } - //*/ - - - - document.addEventListener( "toggleCoalitionVisibility", ( ev:CustomEventInit ) => { - ev.detail._element.classList.toggle( "off" ); - document.body.toggleAttribute( "data-hide-" + ev.detail.coalition ); - }); - - document.addEventListener( "toggleUnitVisibility", ( ev:CustomEventInit ) => { - document.body.toggleAttribute( "data-hide-" + ev.detail.unitType ); - }); - - - /** Olympus UI ***/ - document.querySelectorAll( ".ol-select" ).forEach( select => { - - // Do open/close toggle - select.addEventListener( "click", ev => { - ev.preventDefault(); - ev.stopPropagation(); - select.classList.toggle( "is-open" ); - }); - - // Autoclose on mouseleave - select.addEventListener( "mouseleave", ev => { - select.classList.remove( "is-open" ); - }); - - }); - - /* ATC */ - let atcFeatureSwitch = featureSwitches.getSwitch( "atc" ); - if ( atcFeatureSwitch?.isEnabled() ) { + let atcFeatureSwitch = featureSwitches.getSwitch("atc"); + if (atcFeatureSwitch?.isEnabled()) { atc = new ATC(); // TODO: add back buttons } + /* Setup event handlers */ + setupEvents(); + /* On the first connection, force request of full data */ getAirbases((data: AirbasesData) => getMissionData()?.update(data)); getBulllseyes((data: BullseyesData) => getMissionData()?.update(data)); getUnits((data: UnitsData) => getUnitsManager()?.update(data), true /* Does a full refresh */); - + /* Start periodically requesting updates */ startPeriodicUpdate(); } -function startPeriodicUpdate() -{ +function startPeriodicUpdate() { requestUpdate(); requestRefresh(); } @@ -201,10 +105,8 @@ function requestRefresh() { setTimeout(() => requestRefresh(), 5000); } -function checkSessionHash(newSessionHash: string) -{ - if (sessionHash != null) - { +function checkSessionHash(newSessionHash: string) { + if (sessionHash != null) { if (newSessionHash != sessionHash) location.reload(); } @@ -212,6 +114,88 @@ function checkSessionHash(newSessionHash: string) sessionHash = newSessionHash; } +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")) { + target.closest("div.olympus-dialog")?.classList.add("hide"); + } + + const triggerElement = target.closest("[data-on-click]"); + if (triggerElement instanceof HTMLElement) { + const eventName: string = triggerElement.dataset.onClick || ""; + let params = JSON.parse(triggerElement.dataset.onClickParams || "{}"); + params._element = triggerElement; + + if (eventName) { + document.dispatchEvent(new CustomEvent(eventName, { + detail: params + })); + } + } + } + }); + + /* Keyup events */ + document.addEventListener("keyup", ev => { + switch (ev.code) { + case "KeyL": + document.body.toggleAttribute("data-hide-labels"); + break; + case "KeyD": + toggleDemoEnabled(); + } + }); + + /* + const unitName = document.getElementById( "unit-name" ); + if ( unitName instanceof HTMLInputElement ) { + unitName.addEventListener( "change", ev => { + unitName.setAttribute( "disabled", "true" ); + unitName.setAttribute( "readonly", "true" ); + + // Do something with this: + console.log( unitName.value ); + }); + + document.addEventListener( "editUnitName", ev => { + unitName.removeAttribute( "disabled" ); + unitName.removeAttribute( "readonly" ); + unitName.focus(); + }); + } + //*/ + + document.addEventListener("toggleCoalitionVisibility", (ev: CustomEventInit) => { + ev.detail._element.classList.toggle("off"); + document.body.toggleAttribute("data-hide-" + ev.detail.coalition); + }); + + document.addEventListener("toggleUnitVisibility", (ev: CustomEventInit) => { + document.body.toggleAttribute("data-hide-" + ev.detail.unitType); + }); + + + /** Olympus UI ***/ + document.querySelectorAll(".ol-select").forEach(select => { + + // Do open/close toggle + select.addEventListener("click", ev => { + ev.preventDefault(); + ev.stopPropagation(); + select.classList.toggle("is-open"); + }); + + // Autoclose on mouseleave + select.addEventListener("mouseleave", ev => { + select.classList.remove("is-open"); + }); + + }); +} + export function getMap() { return map; } @@ -250,7 +234,7 @@ export function getConnectionStatusPanel() { export function setActiveCoalition(newActiveCoalition: string) { activeCoalition = newActiveCoalition; - document.querySelectorAll('[data-active-coalition]').forEach((element: any) => {element.setAttribute("data-active-coalition", activeCoalition)}); + document.querySelectorAll('[data-active-coalition]').forEach((element: any) => { element.setAttribute("data-active-coalition", activeCoalition) }); } export function getActiveCoalition() { diff --git a/client/src/server/server.ts b/client/src/server/server.ts index 411e6506..2d7f83b0 100644 --- a/client/src/server/server.ts +++ b/client/src/server/server.ts @@ -4,16 +4,23 @@ import { SpawnOptions } from '../controls/contextmenu'; /* Edit here to change server address */ const REST_ADDRESS = "http://localhost:30000/olympus"; +const DEMO_ADDRESS = "http://localhost:3000/demo"; const UNITS_URI = "units"; const LOGS_URI = "logs"; const AIRBASES_URI = "airbases"; const BULLSEYE_URI = "bullseyes"; var lastUpdateTime = 0; +var demoEnabled = false; + +export function toggleDemoEnabled() +{ + demoEnabled = !demoEnabled; +} export function GET(callback: CallableFunction, uri: string){ var xmlHttp = new XMLHttpRequest(); - xmlHttp.open("GET", `${REST_ADDRESS}/${uri}`, true); + xmlHttp.open("GET", `${demoEnabled? DEMO_ADDRESS: REST_ADDRESS}/${uri}`, true); xmlHttp.onload = function (e) { var data = JSON.parse(xmlHttp.responseText); callback(data); @@ -29,7 +36,7 @@ export function GET(callback: CallableFunction, uri: string){ export function POST(request: object, callback: CallableFunction){ var xhr = new XMLHttpRequest(); - xhr.open("PUT", REST_ADDRESS); + xhr.open("PUT", demoEnabled? DEMO_ADDRESS: REST_ADDRESS); xhr.setRequestHeader("Content-Type", "application/json"); xhr.onreadystatechange = () => { callback(); diff --git a/client/src/units/unit.ts b/client/src/units/unit.ts index 0ce5bd90..bfe59e14 100644 --- a/client/src/units/unit.ts +++ b/client/src/units/unit.ts @@ -370,6 +370,8 @@ export class Unit extends Marker { element.querySelector(".unit-fuel-level")?.setAttribute("style", `width: ${this.getMissionData().fuel}%`); element.querySelector(".unit")?.toggleAttribute("data-has-low-fuel", this.getMissionData().fuel < 20); + element.querySelector(".unit")?.toggleAttribute("data-is-dead", !this.getBaseData().alive); + var unitHeadingDiv = element.querySelector(".unit-heading"); if (unitHeadingDiv != null) unitHeadingDiv.innerHTML = String(Math.floor(rad2deg(this.getFlightData().heading))); @@ -518,7 +520,13 @@ export class GroundUnit extends Unit { export class NavyUnit extends Unit { constructor(ID: number, data: UnitData) { - super(ID, data, ""); + super(ID, data, ` +