From a9a0332465f3709efb4ab685066c08f05a4609e0 Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Sun, 17 Mar 2024 15:58:05 +0100 Subject: [PATCH] Fixed camera control on Firefox, added slider to control zoom level, and other improvements --- .../server/public/themes/olympus/theme.css | 1 + frontend/website/src/constants/constants.ts | 1 + frontend/website/src/controls/slider.ts | 2 +- frontend/website/src/map/map.ts | 120 ++++++++---- frontend/website/src/olympusapp.ts | 18 ++ frontend/website/src/other/utils.ts | 179 ++++++++++-------- .../website/src/shortcut/shortcutmanager.ts | 12 +- scripts/lua/backend/OlympusCameraControl.lua | 12 +- 8 files changed, 220 insertions(+), 125 deletions(-) diff --git a/frontend/server/public/themes/olympus/theme.css b/frontend/server/public/themes/olympus/theme.css index 328f8117..f888f8dd 100644 --- a/frontend/server/public/themes/olympus/theme.css +++ b/frontend/server/public/themes/olympus/theme.css @@ -28,6 +28,7 @@ --accent-light-red: #F5B6B6; --background-grey: #3d4651; + --background-dark-grey: #35393d; --background-slate-blue: #363c43; --background-offwhite: #f2f2f3; --background-steel: #202831; diff --git a/frontend/website/src/constants/constants.ts b/frontend/website/src/constants/constants.ts index f8f69b7b..58a99dd5 100644 --- a/frontend/website/src/constants/constants.ts +++ b/frontend/website/src/constants/constants.ts @@ -256,6 +256,7 @@ export const SHOW_UNIT_CONTACTS = "Show selected units contact lines"; export const SHOW_UNIT_PATHS = "Show selected unit paths"; export const SHOW_UNIT_TARGETS = "Show selected unit targets"; export const DCS_LINK_PORT = "DCS Camera link port"; +export const DCS_LINK_RATIO = "DCS Camera zoom"; export enum DataIndexes { startOfData = 0, diff --git a/frontend/website/src/controls/slider.ts b/frontend/website/src/controls/slider.ts index c715165d..f3ab89e2 100644 --- a/frontend/website/src/controls/slider.ts +++ b/frontend/website/src/controls/slider.ts @@ -104,7 +104,7 @@ export class Slider extends Control { /* Update the position of the slider */ var percentValue = parseFloat(this.#slider.value) / parseFloat(this.#slider.max) * 90 + 5; - this.#slider.style.background = `linear-gradient(to right, var(--accent-light-blue) 5%, var(--accent-light-blue) ${percentValue}%, var(--background-grey) ${percentValue}%, var(--background-grey) 100%)` + this.#slider.style.background = `linear-gradient(to right, var(--accent-light-blue) 5%, var(--accent-light-blue) ${percentValue}%, var(--background-dark-grey) ${percentValue}%, var(--background-dark-grey) 100%)` } this.setActive(true); } diff --git a/frontend/website/src/map/map.ts b/frontend/website/src/map/map.ts index 863b2c8d..2caa42ba 100644 --- a/frontend/website/src/map/map.ts +++ b/frontend/website/src/map/map.ts @@ -7,12 +7,12 @@ import { AirbaseContextMenu } from "../contextmenus/airbasecontextmenu"; import { Dropdown } from "../controls/dropdown"; import { Airbase } from "../mission/airbase"; import { Unit } from "../unit/unit"; -import { bearing, createCheckboxOption, createTextInputOption, deg2rad, getGroundElevation, polyContains } from "../other/utils"; +import { bearing, createCheckboxOption, createSliderInputOption, createTextInputOption, deg2rad, getGroundElevation, polyContains } from "../other/utils"; import { DestinationPreviewMarker } from "./markers/destinationpreviewmarker"; import { TemporaryUnitMarker } from "./markers/temporaryunitmarker"; import { ClickableMiniMap } from "./clickableminimap"; import { SVGInjector } from '@tanem/svg-injector' -import { defaultMapLayers, mapBounds, minimapBoundaries, IDLE, COALITIONAREA_DRAW_POLYGON, MOVE_UNIT, SHOW_UNIT_CONTACTS, HIDE_GROUP_MEMBERS, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, SHOW_UNIT_LABELS, SHOW_UNITS_ENGAGEMENT_RINGS, SHOW_UNITS_ACQUISITION_RINGS, HIDE_UNITS_SHORT_RANGE_RINGS, FILL_SELECTED_RING, MAP_MARKER_CONTROLS, DCS_LINK_PORT, DCSMapsZoomLevelsByTheatre } from "../constants/constants"; +import { defaultMapLayers, mapBounds, minimapBoundaries, IDLE, COALITIONAREA_DRAW_POLYGON, MOVE_UNIT, SHOW_UNIT_CONTACTS, HIDE_GROUP_MEMBERS, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, SHOW_UNIT_LABELS, SHOW_UNITS_ENGAGEMENT_RINGS, SHOW_UNITS_ACQUISITION_RINGS, HIDE_UNITS_SHORT_RANGE_RINGS, FILL_SELECTED_RING, MAP_MARKER_CONTROLS, DCS_LINK_PORT, DCSMapsZoomLevelsByTheatre, DCS_LINK_RATIO } from "../constants/constants"; import { CoalitionArea } from "./coalitionarea/coalitionarea"; import { CoalitionAreaContextMenu } from "../contextmenus/coalitionareacontextmenu"; import { DrawingCursor } from "./coalitionarea/drawingcursor"; @@ -103,6 +103,9 @@ export class Map extends L.Map { #visibilityOptions: { [key: string]: boolean | string | number } = {} #hiddenTypes: string[] = []; #layerName: string = ""; + #cameraOptionsXmlHttp: XMLHttpRequest | null = null; + #bradcastPositionXmlHttp: XMLHttpRequest | null = null; + #cameraZoomRatio: number = 1.0; /** * @@ -211,6 +214,14 @@ export class Map extends L.Map { document.addEventListener("mapOptionsChanged", () => { this.getContainer().toggleAttribute("data-hide-labels", !this.getVisibilityOptions()[SHOW_UNIT_LABELS]); this.#cameraControlPort = this.getVisibilityOptions()[DCS_LINK_PORT] as number; + this.#cameraZoomRatio = 50 / (20 + (this.getVisibilityOptions()[DCS_LINK_RATIO] as number)); + + if (this.#slaveDCSCamera) { + this.#broadcastPosition(); + window.setTimeout(() => { + this.#broadcastPosition(); + }, 500); // DCS does not always apply the altitude correctly at the first set when changing map type + } }); document.addEventListener("configLoaded", () => { @@ -254,6 +265,7 @@ export class Map extends L.Map { /* Create the checkboxes to select the advanced visibility options */ this.addVisibilityOption(DCS_LINK_PORT, 3003, { min: 1024, max: 65535 }); + this.addVisibilityOption(DCS_LINK_RATIO, 50, { min: 0, max: 100, slider: true }); this.#mapVisibilityOptionsDropdown.addHorizontalDivider(); @@ -270,12 +282,16 @@ export class Map extends L.Map { addVisibilityOption(option: string, defaultValue: boolean | number | string, options?: { [key: string]: any }) { this.#visibilityOptions[option] = defaultValue; - if (typeof defaultValue === 'boolean') + if (typeof defaultValue === 'boolean') { this.#mapVisibilityOptionsDropdown.addOptionElement(createCheckboxOption(option, option, defaultValue as boolean, (ev: any) => { this.#setVisibilityOption(option, ev); }, options)); - else if (typeof defaultValue === 'number') - this.#mapVisibilityOptionsDropdown.addOptionElement(createTextInputOption(option, option, defaultValue.toString(), 'number', (ev: any) => { this.#setVisibilityOption(option, ev); }, options)); - else + } else if (typeof defaultValue === 'number') { + if (options !== undefined && options?.slider === true) + this.#mapVisibilityOptionsDropdown.addOptionElement(createSliderInputOption(option, option, defaultValue, (ev: any) => { this.#setVisibilityOption(option, ev); }, options)); + else + this.#mapVisibilityOptionsDropdown.addOptionElement(createTextInputOption(option, option, defaultValue.toString(), 'number', (ev: any) => { this.#setVisibilityOption(option, ev); }, options)); + } else { this.#mapVisibilityOptionsDropdown.addOptionElement(createTextInputOption(option, option, defaultValue, 'text', (ev: any) => { this.#setVisibilityOption(option, ev); }, options)); + } } setLayer(layerName: string) { @@ -593,19 +609,43 @@ export class Map extends L.Map { } setSlaveDCSCamera(newSlaveDCSCamera: boolean) { - // if (this.#slaveDCSCameraAvailable || !newSlaveDCSCamera) { // Commented to experiment with usability - this.#slaveDCSCamera = newSlaveDCSCamera; - let button = document.getElementById("camera-link-control"); - button?.classList.toggle("off", !newSlaveDCSCamera); - if (newSlaveDCSCamera) + this.#slaveDCSCamera = newSlaveDCSCamera; + let button = document.getElementById("camera-link-control"); + button?.classList.toggle("off", !newSlaveDCSCamera); + if (this.#slaveDCSCamera) { + this.#broadcastPosition(); + window.setTimeout(() => { this.#broadcastPosition(); - // } + }, 500); // DCS does not always apply the altitude correctly at the first set when changing map type + } } setCameraControlMode(newCameraControlMode: string) { this.#cameraControlMode = newCameraControlMode; - if (this.#slaveDCSCamera) + if (this.#slaveDCSCamera) { this.#broadcastPosition(); + window.setTimeout(() => { + this.#broadcastPosition(); + }, 500); // DCS does not always apply the altitude correctly at the first set when changing map type + } + } + + increaseCameraZoom() { + const slider = document.querySelector(`label[title="${DCS_LINK_RATIO}"] input`); + if (slider instanceof HTMLInputElement) { + slider.value = String(Math.min(Number(slider.max), Number(slider.value) + 10)); + slider.dispatchEvent(new Event('input')); + slider.dispatchEvent(new Event('mouseup')); + } + } + + decreaseCameraZoom() { + const slider = document.querySelector(`label[title="${DCS_LINK_RATIO}"] input`); + if (slider instanceof HTMLInputElement) { + slider.value = String(Math.max(Number(slider.min), Number(slider.value) - 10)); + slider.dispatchEvent(new Event('input')); + slider.dispatchEvent(new Event('mouseup')); + } } /* Event handlers */ @@ -791,21 +831,24 @@ export class Map extends L.Map { } #broadcastPosition() { + if (this.#bradcastPositionXmlHttp?.readyState !== 4 && this.#bradcastPositionXmlHttp !== null) + return + getGroundElevation(this.getCenter(), (response: string) => { var groundElevation: number | null = null; try { groundElevation = parseFloat(response); - var xmlHttp = new XMLHttpRequest(); - xmlHttp.open("PUT", `http://127.0.0.1:${this.#cameraControlPort}`); - xmlHttp.setRequestHeader("Content-Type", "application/json"); + this.#bradcastPositionXmlHttp = new XMLHttpRequest(); + /* Using 127.0.0.1 instead of localhost because the LuaSocket version used in DCS only listens to IPv4. This avoids the lag caused by the + browser if it first tries to send the request on the IPv6 address for localhost */ + this.#bradcastPositionXmlHttp.open("POST", `http://127.0.0.1:${this.#cameraControlPort}`); const C = 40075016.686; let mpp = C * Math.cos(deg2rad(this.getCenter().lat)) / Math.pow(2, this.getZoom() + 8); let d = mpp * 1920; - let alt = d / 2 * 1 / Math.tan(deg2rad(40)); - if (alt > 100000) - alt = 100000; - xmlHttp.send(JSON.stringify({ lat: this.getCenter().lat, lng: this.getCenter().lng, alt: alt + groundElevation, mode: this.#cameraControlMode })); + let alt = d / 2 * 1 / Math.tan(deg2rad(40)) * this.#cameraZoomRatio; + alt = Math.min(alt, 50000); + this.#bradcastPositionXmlHttp.send(JSON.stringify({ lat: this.getCenter().lat, lng: this.getCenter().lng, alt: alt + groundElevation, mode: this.#cameraControlMode })); } catch { console.warn("broadcastPosition: could not retrieve ground elevation") } @@ -1002,23 +1045,30 @@ export class Map extends L.Map { } } + /* Check if the camera control plugin is available. Right now this will only change the color of the button, no changes in functionality */ #checkCameraPort(){ - var xmlHttp = new XMLHttpRequest(); - xmlHttp.open("OPTIONS", `http://127.0.0.1:${this.#cameraControlPort}`); - xmlHttp.onload = (res: any) => { - if (xmlHttp.status == 200) - this.#setSlaveDCSCameraAvailable(true); - else - this.#setSlaveDCSCameraAvailable(false); - }; - xmlHttp.onerror = (res: any) => { + if (this.#cameraOptionsXmlHttp?.readyState !== 4) + this.#cameraOptionsXmlHttp?.abort() + + this.#cameraOptionsXmlHttp = new XMLHttpRequest(); + + /* Using 127.0.0.1 instead of localhost because the LuaSocket version used in DCS only listens to IPv4. This avoids the lag caused by the + browser if it first tries to send the request on the IPv6 address for localhost */ + this.#cameraOptionsXmlHttp.open("OPTIONS", `http://127.0.0.1:${this.#cameraControlPort}`); + this.#cameraOptionsXmlHttp.onload = (res: any) => { + if (this.#cameraOptionsXmlHttp !== null && this.#cameraOptionsXmlHttp.status == 204) + this.#setSlaveDCSCameraAvailable(true); + else this.#setSlaveDCSCameraAvailable(false); - } - xmlHttp.ontimeout = (res: any) => { - this.#setSlaveDCSCameraAvailable(false); - } - xmlHttp.timeout = 500; - xmlHttp.send(""); + }; + this.#cameraOptionsXmlHttp.onerror = (res: any) => { + this.#setSlaveDCSCameraAvailable(false); + } + this.#cameraOptionsXmlHttp.ontimeout = (res: any) => { + this.#setSlaveDCSCameraAvailable(false); + } + this.#cameraOptionsXmlHttp.timeout = 500; + this.#cameraOptionsXmlHttp.send(""); } } diff --git a/frontend/website/src/olympusapp.ts b/frontend/website/src/olympusapp.ts index e2c5dc09..cb728812 100644 --- a/frontend/website/src/olympusapp.ts +++ b/frontend/website/src/olympusapp.ts @@ -368,6 +368,24 @@ export class OlympusApp { "context": "olympus", "ctrlKey": false, "shiftKey": false + }).addKeyboardShortcut("increaseCameraZoom", { + "altKey": true, + "callback": () => { + this.getMap().increaseCameraZoom(); + }, + "code": "Equal", + "context": "olympus", + "ctrlKey": false, + "shiftKey": false + }).addKeyboardShortcut("decreaseCameraZoom", { + "altKey": true, + "callback": () => { + this.getMap().decreaseCameraZoom(); + }, + "code": "Minus", + "context": "olympus", + "ctrlKey": false, + "shiftKey": false }); ["KeyW", "KeyA", "KeyS", "KeyD", "ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"].forEach(code => { diff --git a/frontend/website/src/other/utils.ts b/frontend/website/src/other/utils.ts index c5a4ccea..6908f20f 100644 --- a/frontend/website/src/other/utils.ts +++ b/frontend/website/src/other/utils.ts @@ -9,9 +9,10 @@ import { GROUND_UNIT_AIR_DEFENCE_REGEX, ROEs, emissionsCountermeasures, reaction import { Dropdown } from "../controls/dropdown"; import { navyUnitDatabase } from "../unit/databases/navyunitdatabase"; import { DateAndTime, UnitBlueprint } from "../interfaces"; +import { Slider } from "../controls/slider"; // comment -const usng = require( "usng.js" ); +const usng = require("usng.js"); export function bearing(lat1: number, lon1: number, lat2: number, lon2: number) { const φ1 = deg2rad(lat1); // φ, λ in radians @@ -45,8 +46,8 @@ export function bearingAndDistanceToLatLng(lat: number, lon: number, brng: numbe const R = 6371e3; // metres const φ1 = deg2rad(lat); // φ, λ in radians const λ1 = deg2rad(lon); - const φ2 = Math.asin( Math.sin(φ1)*Math.cos(dist/R) + Math.cos(φ1)*Math.sin(dist/R)*Math.cos(brng) ); - const λ2 = λ1 + Math.atan2(Math.sin(brng)*Math.sin(dist/R)*Math.cos(φ1), Math.cos(dist/R)-Math.sin(φ1)*Math.sin(φ2)); + const φ2 = Math.asin(Math.sin(φ1) * Math.cos(dist / R) + Math.cos(φ1) * Math.sin(dist / R) * Math.cos(brng)); + const λ2 = λ1 + Math.atan2(Math.sin(brng) * Math.sin(dist / R) * Math.cos(φ1), Math.cos(dist / R) - Math.sin(φ1) * Math.sin(φ2)); return new LatLng(rad2deg(φ2), rad2deg(λ2)); } @@ -64,14 +65,14 @@ export function ConvertDDToDMS(D: number, lng: boolean) { return dir + zeroPad(deg, 2) + "°" + zeroPad(min, 2) + "'" + zeroPad(sec, 2) + "." + zeroPad(dec, 2) + "\""; } -export function dataPointMap( container:HTMLElement, data:any) { - Object.keys( data ).forEach( ( key ) => { - const val = "" + data[ key ]; // Ensure a string - container.querySelectorAll( `[data-point="${key}"]`).forEach( el => { +export function dataPointMap(container: HTMLElement, data: any) { + Object.keys(data).forEach((key) => { + const val = "" + data[key]; // Ensure a string + container.querySelectorAll(`[data-point="${key}"]`).forEach(el => { // We could probably have options here - if ( el instanceof HTMLInputElement ) { + if (el instanceof HTMLInputElement) { el.value = val; - } else if ( el instanceof HTMLElement ) { + } else if (el instanceof HTMLElement) { el.innerText = val; } }); @@ -89,19 +90,19 @@ export function rad2deg(rad: number) { } export function generateUUIDv4() { - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); } -export function keyEventWasInInput( event:KeyboardEvent ) { +export function keyEventWasInInput(event: KeyboardEvent) { const target = event.target; - return ( target instanceof HTMLElement && ( [ "INPUT", "TEXTAREA" ].includes( target.nodeName ) ) ); + return (target instanceof HTMLElement && (["INPUT", "TEXTAREA"].includes(target.nodeName))); } export function reciprocalHeading(heading: number): number { - return heading > 180? heading - 180: heading + 180; + return heading > 180 ? heading - 180 : heading + 180; } /** @@ -113,7 +114,7 @@ export function reciprocalHeading(heading: number): number { * * */ export const zeroAppend = function (num: number, places: number, decimal: boolean = false) { - var string = decimal? num.toFixed(2): String(num); + var string = decimal ? num.toFixed(2) : String(num); while (string.length < places) { string = "0" + string; } @@ -181,22 +182,22 @@ export type MGRS = { zoneNumber: string } -export function latLngToMGRS( lat:number, lng:number, precision:number = 4 ): MGRS | false { +export function latLngToMGRS(lat: number, lng: number, precision: number = 4): MGRS | false { - if ( precision < 0 || precision > 6 ) { - console.error( "latLngToMGRS: precision must be a number >= 0 and <= 6. Given precision: " + precision ); + if (precision < 0 || precision > 6) { + console.error("latLngToMGRS: precision must be a number >= 0 and <= 6. Given precision: " + precision); return false; } - - const mgrs = new usng.Converter().LLtoMGRS( lat, lng, precision ); - const match = mgrs.match( new RegExp( `^(\\d{2})([A-Z])([A-Z])([A-Z])(\\d+)$` ) ); - const easting = match[5].substr(0,match[5].length/2); - const northing = match[5].substr(match[5].length/2); - let output:MGRS = { + const mgrs = new usng.Converter().LLtoMGRS(lat, lng, precision); + const match = mgrs.match(new RegExp(`^(\\d{2})([A-Z])([A-Z])([A-Z])(\\d+)$`)); + const easting = match[5].substr(0, match[5].length / 2); + const northing = match[5].substr(match[5].length / 2); + + let output: MGRS = { bandLetter: match[2], columnLetter: match[3], - groups: [ match[1] + match[2], match[3] + match[4], easting, northing ], + groups: [match[1] + match[2], match[3] + match[4], easting, northing], easting: easting, northing: northing, precision: precision, @@ -204,32 +205,32 @@ export function latLngToMGRS( lat:number, lng:number, precision:number = 4 ): MG string: match[0], zoneNumber: match[1] } - + return output; } -export function latLngToUTM( lat:number, lng:number ) { - return new usng.Converter().LLtoUTM( lat, lng ); +export function latLngToUTM(lat: number, lng: number) { + return new usng.Converter().LLtoUTM(lat, lng); } -export function latLngToMercator(lat: number, lng: number): {x: number, y: number} { +export function latLngToMercator(lat: number, lng: number): { x: number, y: number } { var rMajor = 6378137; //Equatorial Radius, WGS84 - var shift = Math.PI * rMajor; - var x = lng * shift / 180; - var y = Math.log(Math.tan((90 + lat) * Math.PI / 360)) / (Math.PI / 180); + var shift = Math.PI * rMajor; + var x = lng * shift / 180; + var y = Math.log(Math.tan((90 + lat) * Math.PI / 360)) / (Math.PI / 180); y = y * shift / 180; - - return {x: x, y: y}; + + return { x: x, y: y }; } export function mercatorToLatLng(x: number, y: number) { var rMajor = 6378137; //Equatorial Radius, WGS84 - var shift = Math.PI * rMajor; - var lng = x / shift * 180.0; - var lat = y / shift * 180.0; + var shift = Math.PI * rMajor; + var lng = x / shift * 180.0; + var lat = y / shift * 180.0; lat = 180 / Math.PI * (2 * Math.atan(Math.exp(lat * Math.PI / 180.0)) - Math.PI / 2.0); - return { lng: lng, lat: lat }; + return { lng: lng, lat: lat }; } export function createDivWithClass(className: string) { @@ -267,21 +268,21 @@ export function nmToFt(nm: number) { } export function polyContains(latlng: LatLng, polygon: Polygon) { - var poly = polygon.toGeoJSON(); + var poly = polygon.toGeoJSON(); return turf.inside(turf.point([latlng.lng, latlng.lat]), poly); } export function randomPointInPoly(polygon: Polygon): LatLng { - var bounds = polygon.getBounds(); - var x_min = bounds.getEast(); - var x_max = bounds.getWest(); - var y_min = bounds.getSouth(); - var y_max = bounds.getNorth(); + var bounds = polygon.getBounds(); + var x_min = bounds.getEast(); + var x_max = bounds.getWest(); + var y_min = bounds.getSouth(); + var y_max = bounds.getNorth(); var lat = y_min + (Math.random() * (y_max - y_min)); var lng = x_min + (Math.random() * (x_max - x_min)); - var poly = polygon.toGeoJSON(); + var poly = polygon.toGeoJSON(); var inside = turf.inside(turf.point([lng, lat]), poly); if (inside) { @@ -296,7 +297,7 @@ export function polygonArea(polygon: Polygon) { return turf.area(poly); } -export function randomUnitBlueprint(unitDatabase: UnitDatabase, options: {type?: string, role?: string, ranges?: string[], eras?: string[], coalition?: string} ) { +export function randomUnitBlueprint(unitDatabase: UnitDatabase, options: { type?: string, role?: string, ranges?: string[], eras?: string[], coalition?: string }) { /* Start from all the unit blueprints in the database */ var unitBlueprints = Object.values(unitDatabase.getBlueprints()); @@ -315,13 +316,13 @@ export function randomUnitBlueprint(unitDatabase: UnitDatabase, options: {type?: /* Keep only the units that have a range included in the requested values */ if (options.ranges) { - unitBlueprints = unitBlueprints.filter((unitBlueprint: UnitBlueprint) => { + unitBlueprints = unitBlueprints.filter((unitBlueprint: UnitBlueprint) => { var rangeType = ""; - var range = unitBlueprint.acquisitionRange ; + var range = unitBlueprint.acquisitionRange; if (range !== undefined) { - if (range >= 0 && range < 10000) + if (range >= 0 && range < 10000) rangeType = "Short range"; - else if (range >= 10000 && range < 100000) + else if (range >= 10000 && range < 100000) rangeType = "Medium range"; else if (range >= 100000 && range < 999999) rangeType = "Long range"; @@ -332,15 +333,15 @@ export function randomUnitBlueprint(unitDatabase: UnitDatabase, options: {type?: /* Keep only the units that have an era included in the requested values */ if (options.eras) { - unitBlueprints = unitBlueprints.filter((unitBlueprint: UnitBlueprint) => { - return unitBlueprint.era? options.eras?.includes(unitBlueprint.era): true; + unitBlueprints = unitBlueprints.filter((unitBlueprint: UnitBlueprint) => { + return unitBlueprint.era ? options.eras?.includes(unitBlueprint.era) : true; }); } /* Keep only the units that have the correct coalition, if selected */ if (options.coalition) { - unitBlueprints = unitBlueprints.filter((unitBlueprint: UnitBlueprint) => { - return (unitBlueprint.coalition && unitBlueprint.coalition !== "")? options.coalition === unitBlueprint.coalition: true; + unitBlueprints = unitBlueprints.filter((unitBlueprint: UnitBlueprint) => { + return (unitBlueprint.coalition && unitBlueprint.coalition !== "") ? options.coalition === unitBlueprint.coalition : true; }); } @@ -353,7 +354,7 @@ export function getMarkerCategoryByName(name: string) { return "aircraft"; else if (helicopterDatabase.getByName(name) != null) return "helicopter"; - else if (groundUnitDatabase.getByName(name) != null){ + else if (groundUnitDatabase.getByName(name) != null) { var type = groundUnitDatabase.getByName(name)?.type ?? ""; if (/\bAAA|SAM\b/.test(type) || /\bmanpad|stinger\b/i.test(type)) return "groundunit-sam"; @@ -362,7 +363,7 @@ export function getMarkerCategoryByName(name: string) { } else if (navyUnitDatabase.getByName(name) != null) return "navyunit"; - else + else return "aircraft"; // TODO add other unit types } @@ -379,7 +380,7 @@ export function getUnitDatabaseByCategory(category: string) { return null; } -export function getCategoryBlueprintIconSVG(category:string, unitName:string) { +export function getCategoryBlueprintIconSVG(category: string, unitName: string) { const path = "/resources/theme/images/buttons/visibility/"; @@ -398,35 +399,35 @@ export function base64ToBytes(base64: string) { } export function enumToState(state: number) { - if (state < states.length) + if (state < states.length) return states[state]; - else + else return states[0]; } export function enumToROE(ROE: number) { - if (ROE < ROEs.length) + if (ROE < ROEs.length) return ROEs[ROE]; - else + else return ROEs[0]; } export function enumToReactionToThreat(reactionToThreat: number) { - if (reactionToThreat < reactionsToThreat.length) + if (reactionToThreat < reactionsToThreat.length) return reactionsToThreat[reactionToThreat]; - else + else return reactionsToThreat[0]; } export function enumToEmissioNCountermeasure(emissionCountermeasure: number) { - if (emissionCountermeasure < emissionsCountermeasures.length) + if (emissionCountermeasure < emissionsCountermeasures.length) return emissionsCountermeasures[emissionCountermeasure]; - else + else return emissionsCountermeasures[0]; } export function enumToCoalition(coalitionID: number) { - switch (coalitionID){ + switch (coalitionID) { case 0: return "neutral"; case 1: return "red"; case 2: return "blue"; @@ -435,7 +436,7 @@ export function enumToCoalition(coalitionID: number) { } export function coalitionToEnum(coalition: string) { - switch (coalition){ + switch (coalition) { case "neutral": return 0; case "red": return 1; case "blue": return 2; @@ -463,7 +464,7 @@ export function convertDateAndTimeToDate(dateAndTime: DateAndTime) { return new Date(year, month, date.Day, time.h, time.m, time.s); } -export function createCheckboxOption(text: string, description: string, checked: boolean = true, callback: CallableFunction = (ev: any) => {}, options?:any) { +export function createCheckboxOption(text: string, description: string, checked: boolean = true, callback: CallableFunction = (ev: any) => { }, options?: any) { options = { "disabled": false, "name": "", @@ -477,11 +478,11 @@ export function createCheckboxOption(text: string, description: string, checked: label.title = description; var input = document.createElement("input"); input.type = "checkbox"; - input.checked = checked; - input.name = options.name; - input.disabled = options.disabled; - input.readOnly = options.readOnly; - input.value = options.value; + input.checked = checked; + input.name = options.name; + input.disabled = options.disabled; + input.readOnly = options.readOnly; + input.value = options.value; var span = document.createElement("span"); span.innerText = text; label.appendChild(input); @@ -504,7 +505,7 @@ export function getCheckboxOptions(dropdown: Dropdown) { return values; } -export function createTextInputOption(text: string, description: string, initialValue: string, type: string, callback: CallableFunction = (ev: any) => {}, options?:any) { +export function createTextInputOption(text: string, description: string, initialValue: string, type: string, callback: CallableFunction = (ev: any) => { }, options?: any) { options = { "disabled": false, "name": "", @@ -516,15 +517,15 @@ export function createTextInputOption(text: string, description: string, initial var label = document.createElement("label"); label.title = description; var input = document.createElement("input"); - input.type = type; - input.name = options.name; - input.disabled = options.disabled; - input.readOnly = options.readOnly; + input.type = type; + input.name = options.name; + input.disabled = options.disabled; + input.readOnly = options.readOnly; if (options.min) input.min = options.min; if (options.max) input.max = options.max; - input.value = initialValue; + input.value = initialValue; input.style.width = "80px"; var span = document.createElement("span"); span.innerText = text; @@ -543,6 +544,28 @@ export function createTextInputOption(text: string, description: string, initial return div as HTMLElement; } +export function createSliderInputOption(text: string, description: string, initialValue: number, callback: CallableFunction = (ev: any) => { }, options?: any) { + var div = document.createElement("div"); + var label = document.createElement("label"); + label.title = description; + var input = new Slider(null, options.min ?? 0, options.max ?? 100, "", (val: number) => { + callback({currentTarget: {value: val}}); + }); + input.setValue(initialValue); + input.getContainer()?.querySelector(".ol-data-grid")?.classList.add("hide"); + var span = document.createElement("span"); + span.innerText = text; + span.style.width = "100%"; + span.style.margin = "auto"; + label.appendChild(span); + label.appendChild(input.getContainer() as HTMLElement); + label.style.display = "flex"; + label.style.alignContent = "center"; + label.style.width = "100%"; + div.appendChild(label); + return div as HTMLElement; +} + export function getGroundElevation(latlng: LatLng, callback: CallableFunction) { /* Get the ground elevation from the server endpoint */ const xhr = new XMLHttpRequest(); diff --git a/frontend/website/src/shortcut/shortcutmanager.ts b/frontend/website/src/shortcut/shortcutmanager.ts index 2e80ea66..8d86f741 100644 --- a/frontend/website/src/shortcut/shortcutmanager.ts +++ b/frontend/website/src/shortcut/shortcutmanager.ts @@ -27,18 +27,18 @@ export class ShortcutManager extends Manager { } - add( name: string, shortcut:any ) { - console.error( "ShortcutManager:add() cannot be used. Use addKeyboardShortcut or addMouseShortcut." ); + add(name: string, shortcut: any) { + console.error("ShortcutManager:add() cannot be used. Use addKeyboardShortcut or addMouseShortcut."); return this; } - addKeyboardShortcut( name:string, shortcutKeyboardOptions:ShortcutKeyboardOptions ) { - super.add( name, new ShortcutKeyboard( shortcutKeyboardOptions ) ); + addKeyboardShortcut(name: string, shortcutKeyboardOptions: ShortcutKeyboardOptions) { + super.add(name, new ShortcutKeyboard(shortcutKeyboardOptions)); return this; } - addMouseShortcut( name:string, shortcutMouseOptions:ShortcutMouseOptions ) { - super.add( name, new ShortcutMouse( shortcutMouseOptions ) ); + addMouseShortcut(name: string, shortcutMouseOptions: ShortcutMouseOptions) { + super.add(name, new ShortcutMouse(shortcutMouseOptions)); return this; } diff --git a/scripts/lua/backend/OlympusCameraControl.lua b/scripts/lua/backend/OlympusCameraControl.lua index 5fa4894c..bf26c189 100644 --- a/scripts/lua/backend/OlympusCameraControl.lua +++ b/scripts/lua/backend/OlympusCameraControl.lua @@ -4,7 +4,7 @@ local _prevLuaExportStop = LuaExportStop local server = nil local port = 3003 -local headers = "Access-Control-Allow-Origin: *\r\nAccess-Control-Allow-Methods: PUT, OPTIONS\r\nAccess-Control-Allow-Headers: *\r\nAccess-Control-Max-Age: 86400\r\nVary: Accept-Encoding, Origin\r\nKeep-Alive: timeout=2, max=100\r\nConnection: Keep-Alive\r\n\r\n" +local headers = "Access-Control-Allow-Private-Network: true\r\nAccess-Control-Allow-Origin: *\r\nAccess-Control-Allow-Methods: *\r\nAccess-Control-Allow-Headers: *\r\nAccess-Control-Max-Age: 86400\r\nVary: Accept-Encoding, Origin\r\n\r\n" function startTCPServer() log.write('OLYMPUSCAMERACONTROL.EXPORT.LUA', log.INFO, 'Starting TCP Server') @@ -30,11 +30,13 @@ function receiveTCP() if client then -- Set the timeout of the connection to 5ms - client:settimeout(0) + client:settimeout(0.005) client:setoption("tcp-nodelay", true) local acc = "" local data = "" + + log.write('OLYMPUSCAMERACONTROL.EXPORT.LUA', log.INFO, 'CONNECTION') -- Start receiving data, accumulate it in acc while data ~= nil do @@ -45,11 +47,11 @@ function receiveTCP() if data == "" then -- Is this an OPTIONS request? if string.find(acc, "OPTIONS") ~= nil then - client:send("HTTP/1.1 200 OK\r\n" .. headers) + client:send("HTTP/1.1 204 OK\r\n" .. headers) client:close() -- Is this a PUT request? - elseif string.find(acc, "PUT") ~= nil then + elseif string.find(acc, "POST") ~= nil then -- Extract the length of the body local contentLength = string.match(acc, "Content%-Length: (%d+)") if contentLength ~= nil then @@ -62,7 +64,7 @@ function receiveTCP() local mode = string.match(body, '"mode":%s*"(%a+)"%s*[},]') if lat ~= nil and lng ~= nil then - client:send("HTTP/1.1 200 OK\r\n" .. headers) + client:send("HTTP/1.1 200 OK\r\n" .. "Content-Type: application/json\r\n" .. headers) local position = {} position["lat"] = tonumber(lat)