UNPKG

aerofly-missions

Version:

The Aerofly Missionsgerät converts simulator flight plan files for Aerofly FS 4, Microsoft Flight Simulator, X-Plane, GeoFS, and Garmin / Infinite Flight flight plan files. It also imports SimBrief flight plans.

754 lines (753 loc) 37.2 kB
import { Mission } from "../Aerofly/Mission.js"; import { GeoJson } from "../Export/GeoJson.js"; import { LonLatArea } from "../World/LonLat.js"; import { MissionCheckpoint } from "../Aerofly/MissionCheckpoint.js"; import { Outputtable } from "../Export/Outputtable.js"; import { ComponentsAirports, ComponentsCheckpoints, ComponentsWeather } from "./Components.js"; import { ComponentsDownloadButtons } from "./ComponentsDownloadButtons.js"; import { ComponentSimBrief } from "./ComponentSimbrief.js"; import { SimBrief } from "../Import/SimBrief.js"; import { StatEvent } from "./StatEvent.js"; import { ComponentUploadField } from "./ComponentUploadField.js"; import { CheckWx } from "../Import/CheckWx.js"; export class App { constructor() { this.elements = { wind_speed: document.getElementById("wind_speed"), wind_gusts: document.getElementById("wind_gusts"), wind_direction: document.getElementById("wind_direction"), visibility_sm: document.getElementById("visibility_sm"), visibility: document.getElementById("visibility"), upload: document.querySelector("missionsgeraet-upload-field"), turn_time: document.getElementById("turn_time"), turn_radius: document.getElementById("turn_radius"), turbulence_strength: document.getElementById("turbulence_strength"), expertMode: document.getElementById("expertMode"), title: document.getElementById("title"), time: document.getElementById("time"), thermal_strength: document.getElementById("thermal_strength"), outputWeather: document.getElementById("output-weather"), outputCheckpoints: document.getElementById("output-checkpoints"), outputAirports: document.getElementById("output-airports"), origin_dir: document.getElementById("origin_dir"), no_guides: document.getElementById("no_guides"), metarApiKey: document.getElementById("metar-api-key"), metar: document.getElementById("metar"), makeMetarDest: document.getElementById("make-metar-dest"), makeMetarDept: document.getElementById("make-metar-dept"), main: document.querySelector("main"), magneticDeclination: document.getElementById("magnetic_declination"), flight_setting: document.getElementById("flight_setting"), downloadButtons: document.getElementById("download-buttons"), description: document.getElementById("description"), date: document.getElementById("date"), cruise_speed: document.getElementById("cruise_speed"), cruise_altitude_ft: document.getElementById("cruise_altitude_ft"), cloud_cover_code: document.getElementById("cloud_cover_code"), cloud_cover: document.getElementById("cloud_cover"), cloud_base_feet: document.getElementById("cloud_base_feet"), cloud3_cover_code: document.getElementById("cloud3_cover_code"), cloud3_cover: document.getElementById("cloud3_cover"), cloud3_base_feet: document.getElementById("cloud3_base_feet"), cloud2_cover_code: document.getElementById("cloud2_cover_code"), cloud2_cover: document.getElementById("cloud2_cover"), cloud2_base_feet: document.getElementById("cloud2_base_feet"), callsign: document.getElementById("callsign"), aircraft_name: document.getElementById("aircraft_name"), simBrief: document.querySelector("missionsgeraet-simbrief"), simBriefUseDestinationWeather: document.getElementById("simBriefUseDestinationWeather"), }; this.useIcao = true; this.metarApiKey = ""; this.simBriefUseDestinationWeather = false; this.mission = new Mission("", ""); customElements.define("missionsgeraet-upload-field", ComponentUploadField); this.elements.upload.mission = this.mission; this.elements.upload.addEventListener("file-uploaded", (event) => { var _a, _b; if (((_a = event.detail) === null || _a === void 0 ? void 0 : _a.filename) === undefined || ((_b = event.detail) === null || _b === void 0 ? void 0 : _b.fileEnding) === undefined) { return; } document.body.dispatchEvent(StatEvent.createEvent("Import", "Upload " + event.detail.fileEnding + " file")); this.useIcao = this.mission.origin_country !== "US"; this.mission.magnetic_declination = undefined; this.syncToForm(); this.showFlightplan(App.SHOW_ALL | App.SHOW_MAP_CENTER); }); customElements.define("missionsgeraet-simbrief", ComponentSimBrief); this.elements.simBrief.addEventListener("simbrief-payload-fetched", (event) => { if (!event.detail) { return; } const simBrief = new SimBrief(); simBrief.convertMission(event.detail, this.mission, this.simBriefUseDestinationWeather); this.syncToForm(); this.showFlightplan(App.SHOW_ALL | App.SHOW_MAP_CENTER); }); customElements.define("missionsgeraet-buttons", ComponentsDownloadButtons); this.elements.downloadButtons.mission = this.mission; this.elements.downloadButtons.draw(); customElements.define("missionsgeraet-weather", ComponentsWeather); this.elements.outputWeather.mission = this.mission; this.elements.outputWeather.draw(); customElements.define("missionsgeraet-airports", ComponentsAirports); this.elements.outputAirports.mission = this.mission; this.elements.outputAirports.draw(); customElements.define("missionsgeraet-checkpoints", ComponentsCheckpoints); this.elements.outputCheckpoints.mission = this.mission; this.elements.outputCheckpoints.draw(); this.restore(); this.geoJson = new GeoJson(); document.body.addEventListener("input", this); document.body.addEventListener("click", this); this.showFlightplan(); this.syncToForm(); } handleEvent(e) { switch (e.type) { case "click": const target = e.target.closest("[data-handler]"); if (!target) { return; } const handler = target.getAttribute("data-handler"); switch (handler) { case "add-separation": this.handleEventClickAddSeparation(target); break; case "fetch-metar": this.handleEventClickFetchMetar(target); break; case "modal-close": this.handleEventClickModalClose(target); break; case "modal-open": this.handleEventClickModalOpen(target); break; case "random-weather": this.handleEventClickRandomWeather(); break; case "reset": this.handleEventClickReset(target); break; case "reverse-flightplan": this.handleEventClickReverseFlightplan(); break; case "waypoint-edit": this.handleEventClickWaypointEdit(target); break; } break; case "input": this.handleEventInput(e.target); break; } } handleEventClickAddSeparation(target) { const type = target.getAttribute("data-type"); this.mission.calculateCheckpoints(type); this.showFlightplan(App.SHOW_CHECKPOINTS); } handleEventClickWaypointEdit(target) { var _a; const type = target.getAttribute("data-type"); const waypointId = Number(target.closest("dialog").getAttribute("data-cp-id")); switch (type) { case "delete": this.mission.checkpoints.splice(waypointId, 1); break; case "add-before": this.mission.addCheckpointBefore(waypointId, 3, 1000); break; case "add-after": this.mission.addCheckpointAfter(waypointId, 3, 1000); break; case "make-finish": const currentWaypoint = this.mission.checkpoints[waypointId]; this.mission.finish = currentWaypoint === this.mission.finish ? null : (_a = this.mission.checkpoints[waypointId]) !== null && _a !== void 0 ? _a : null; break; case "toggle-flyover": this.mission.checkpoints[waypointId].flyOver = !this.mission.checkpoints[waypointId].flyOver; break; } this.handleEventClickModalClose(target); this.mission.calculateCheckpoints(); this.showFlightplan(App.SHOW_AIRPORTS | App.SHOW_CHECKPOINTS | App.SHOW_MAP); } async handleEventClickFetchMetar(target) { const icao = target.id === "make-metar-dept" ? this.mission.origin_icao : this.mission.destination_icao; const checkWxApi = new CheckWx(this.metarApiKey); try { const result = await checkWxApi.fetch(icao); checkWxApi.addToMission(result, this.mission); this.syncToForm(); this.showFlightplan(App.SHOW_WEATHER | App.SHOW_CHECKPOINTS); document.body.dispatchEvent(StatEvent.createEvent("Weather", "Fetched METAR via API")); } catch (e) { this.showError("Error fetching METAR: " + e); return; } } handleEventClickModalClose(target) { target.closest("dialog").close(); } handleEventClickModalOpen(target) { const tgt = target.getAttribute("data-modal"); if (tgt) { document.getElementById(tgt).showModal(); } } handleEventClickRandomWeather() { this.makeWeather(); this.syncToForm(); this.showFlightplan(App.SHOW_WEATHER | App.SHOW_CHECKPOINTS); } handleEventClickReset(target) { let show = 0; switch (target.id) { case "reset-description": this.mission.title = ""; this.mission.description = ""; this.mission.setAutoTitleDescription(); break; case "reset-aircraft": this.mission.aircraft_name = "c172"; this.mission.cruise_altitude = 0; this.mission.flight_setting = Mission.FLIGHT_SETTING_TAXI; show = App.SHOW_AIRPORTS | App.SHOW_CHECKPOINTS; break; case "reset-time": this.mission.conditions.time.dateTime = new Date(); this.mission.conditions.time.dateTime.setUTCSeconds(0); this.mission.conditions.time.dateTime.setUTCMilliseconds(0); show = App.SHOW_AIRPORTS | App.SHOW_CHECKPOINTS; break; case "reset-weather": this.mission.conditions.wind_direction = 0; this.mission.conditions.wind_gusts = 0; this.mission.conditions.wind_speed = 0; this.mission.conditions.turbulence_strength = 0; this.mission.conditions.thermal_strength = 0; this.mission.conditions.visibility_percent = 1; this.mission.conditions.clouds = []; show = App.SHOW_WEATHER | App.SHOW_CHECKPOINTS; break; case "reset-flightplan": this.mission.checkpoints = []; this.mission.magnetic_declination = undefined; show = App.SHOW_WEATHER | App.SHOW_AIRPORTS | App.SHOW_CHECKPOINTS; break; } this.syncToForm(); this.showFlightplan(show); } handleEventClickReverseFlightplan() { this.mission.reverseWaypoints(); this.syncToForm(); this.showFlightplan(App.SHOW_ALL); } handleEventClickToggleExpertMode(target) { const isChecked = target.checked; this.elements.main.classList.toggle(App.CLASS_SIMPLE_MODE, !isChecked); localStorage.setItem(App.CLASS_SIMPLE_MODE, !isChecked ? "1" : "0"); } handleEventInput(target) { let show = 0; switch (target.id) { case "aircraft_name": this.mission.aircraft_name = target.value; show |= App.SHOW_MAP | App.SHOW_AIRPORTS | App.SHOW_CHECKPOINTS; this.syncToForm(); break; case "callsign": this.mission.callsign = target.value; break; case "cloud_base_feet": this.mission.conditions.cloud.height_feet = target.valueAsNumber; show |= App.SHOW_WEATHER; this.syncToOutput(); break; case "cloud2_base_feet": this.mission.conditions.cloud2.height_feet = target.valueAsNumber; show |= App.SHOW_WEATHER; this.syncToOutput(); break; case "cloud3_base_feet": this.mission.conditions.cloud3.height_feet = target.valueAsNumber; show |= App.SHOW_WEATHER; this.syncToOutput(); break; case "cloud_cover": this.mission.conditions.cloud.cover = target.valueAsNumber / 100; show |= App.SHOW_WEATHER; this.syncToOutput(); break; case "cloud2_cover": this.mission.conditions.cloud2.cover = target.valueAsNumber / 100; show |= App.SHOW_WEATHER; this.syncToOutput(); break; case "cloud3_cover": this.mission.conditions.cloud3.cover = target.valueAsNumber / 100; show |= App.SHOW_WEATHER; this.syncToOutput(); break; case "cruise_altitude_ft": this.mission.cruise_altitude_ft = target.valueAsNumber; show |= App.SHOW_CHECKPOINTS; this.mission.syncCruiseAltitude(); break; case "cruise_speed": this.mission.cruise_speed = target.valueAsNumber; this.mission.syncCruiseSpeed(); this.syncToOutput(); show |= App.SHOW_MAP | App.SHOW_AIRPORTS | App.SHOW_CHECKPOINTS; break; case "date": if (target.valueAsDate) { this.mission.conditions.time.dateTime.setUTCFullYear(target.valueAsDate.getUTCFullYear()); this.mission.conditions.time.dateTime.setUTCMonth(target.valueAsDate.getUTCMonth()); this.mission.conditions.time.dateTime.setUTCDate(target.valueAsDate.getUTCDate()); } show |= App.SHOW_AIRPORTS | App.SHOW_WEATHER; break; case "description": this.mission.description = target.value; break; case "magnetic_declination": this.mission.magnetic_declination = target.value ? target.valueAsNumber : undefined; show |= App.SHOW_CHECKPOINTS; break; case "metar-api-key": this.metarApiKey = target.value; this.syncToForm(); break; case "origin_dir": this.mission.origin_dir = target.valueAsNumber; break; case "thermal_strength": this.mission.conditions.thermal_strength = target.valueAsNumber / 100; show |= App.SHOW_WEATHER; break; case "time": if (target.valueAsDate) { this.mission.conditions.time.dateTime.setUTCHours(target.valueAsDate.getUTCHours()); this.mission.conditions.time.dateTime.setUTCMinutes(target.valueAsDate.getUTCMinutes()); show |= App.SHOW_AIRPORTS | App.SHOW_WEATHER; } break; case "title": this.mission.title = target.value; break; case "turbulence_strength": this.mission.conditions.turbulence_strength = target.valueAsNumber / 100; show |= App.SHOW_WEATHER; break; case "turn_time": this.mission.turn_time = target.valueAsNumber; this.syncToOutput(); show |= App.SHOW_MAP; break; case "visibility": this.mission.conditions.visibility = target.valueAsNumber; show |= App.SHOW_WEATHER; this.syncToOutput(); break; case "wind_direction": this.mission.conditions.wind_direction = target.valueAsNumber; show |= App.SHOW_ALL; break; case "wind_gusts": this.mission.conditions.wind_gusts = target.valueAsNumber; show |= App.SHOW_WEATHER; break; case "wind_speed": this.mission.conditions.wind_speed = target.valueAsNumber; show |= App.SHOW_ALL; break; case "no_guides": this.mission.no_guides = target.checked; break; case "expertMode": this.handleEventClickToggleExpertMode(target); break; case "simBriefUseDestinationWeather": this.simBriefUseDestinationWeather = target.checked; break; default: const prop = target.getAttribute("data-cp-prop"); const id = (target.closest("[data-cp-id]") || target).getAttribute("data-cp-id"); if (prop && id) { const index = Number(id); switch (prop) { case "name": this.mission.checkpoints[index].name = target.value; if (index === 1) { this.mission.checkpoints[index].type = target.value.match(/^\d\d[LRCSGHUW]?$/) ? MissionCheckpoint.TYPE_DEPARTURE_RUNWAY : MissionCheckpoint.TYPE_WAYPOINT; } else if (index === this.mission.checkpoints.length - 2) { this.mission.checkpoints[index].type = target.value.match(/^\d\d[LRCSGHUW]?$/) ? MissionCheckpoint.TYPE_DESTINATION_RUNWAY : MissionCheckpoint.TYPE_WAYPOINT; } show |= App.SHOW_MAP; break; case "altitude_ft": this.mission.checkpoints[index].lon_lat.altitude_ft = target.valueAsNumber; break; case "lat": this.mission.checkpoints[index].lon_lat.lat = target.valueAsNumber; show |= App.SHOW_MAP; break; case "lon": this.mission.checkpoints[index].lon_lat.lon = target.valueAsNumber; show |= App.SHOW_MAP; break; case "speed": this.mission.checkpoints[index].speed = target.valueAsNumber; this.mission.calculateCheckpoints(); const tr = target.closest("tr"); if (tr) { tr.querySelector(".heading").innerText = Outputtable.padThree(this.mission.checkpoints[index].heading); tr.querySelector(".time_enroute").innerText = Outputtable.convertHoursToMinutesString(this.mission.checkpoints[index].time_enroute); } const table = target.closest("table"); if (table) { table.querySelector("tfoot .time_enroute").innerText = Outputtable.convertHoursToMinutesString(this.mission.time_enroute); } show |= App.SHOW_AIRPORTS | App.SHOW_MAP; break; case "frequency_mhz": this.mission.checkpoints[index].frequency_mhz = target.valueAsNumber; break; } } break; } if (target.id !== "upload") { this.mission.calculateCheckpoints(); } this.showFlightplan(show); } showFlightplan(show = App.SHOW_WEATHER | App.SHOW_AIRPORTS | App.SHOW_CHECKPOINTS) { if (App.SHOW_WEATHER & show) { this.elements.outputWeather.draw(); } if (App.SHOW_AIRPORTS & show) { this.elements.outputAirports.draw(); } if (App.SHOW_CHECKPOINTS & show) { this.elements.outputCheckpoints.draw(); } if (App.SHOW_MAP & show || App.SHOW_MAP_CENTER & show) { this.drawMap((App.SHOW_MAP_CENTER & show) !== 0); } this.elements.downloadButtons.draw(); this.store(); } addMapbox(mapboxMap) { this.mapboxMap = mapboxMap; if (this.mission.origin_lon_lat) { this.mapboxMap.setZoom(5); this.mapboxMap.setCenter([this.mission.origin_lon_lat.lon, this.mission.origin_lon_lat.lat]); } this.mapboxMap.on("load", () => { if (this.mapboxMap === undefined) { return; } this.mapboxMap.addSource("mapbox-dem", { type: "raster-dem", url: "mapbox://mapbox.mapbox-terrain-dem-v1", tileSize: 512, maxzoom: 14, }); this.mapboxMap.setTerrain({ source: "mapbox-dem" }); this.geoJson.fromMission(this.mission); this.mapboxMap.addSource("waypoints", { type: "geojson", data: this.geoJson, }); this.mapboxMap.addLayer({ id: "waypoints-line", type: "line", source: "waypoints", layout: { "line-join": "round", "line-cap": "round", }, paint: { "line-color": "#FF1493", "line-width": 2, "line-dasharray": ["match", ["get", "type"], "Taxi", ["literal", [1, 2]], ["literal", [10, 0]]], }, //filter: ['==', '$type', 'Polygon'] }); this.mapboxMap.addLayer({ id: "waypoints", type: "symbol", source: "waypoints", layout: { "icon-image": ["get", "marker-symbol"], "text-field": ["get", "title"], // 'icon-rotate': ['get', 'direction'], "text-offset": [0, 0.5], "text-anchor": "top", "text-size": 12, "symbol-sort-key": ["get", "symbol-sort-key"], }, paint: { "icon-color": "#FF1493", }, //filter: ['==', '$type', 'Point'] }); this.drawMap(true); // ----------------------------------------------------------------------- let currentFeature = null; const source = this.mapboxMap.getSource("waypoints"); const onDown = (e) => { if (this.mapboxMap === undefined) { return; } e.preventDefault(); const features = this.mapboxMap.queryRenderedFeatures(e.point, { layers: ["waypoints"], }); currentFeature = features[0]; }; const onMove = (e) => { if (this.mapboxMap === undefined) { return; } const coords = e.lngLat; if (!currentFeature || !source || source.type !== "geojson") { return; } const featureId = currentFeature.id; this.geoJson.features[featureId].geometry.coordinates = [coords.lng, coords.lat]; if (featureId === 0) { this.mission.origin_lon_lat.lon = coords.lng; this.mission.origin_lon_lat.lat = coords.lat; } else if (featureId === this.mission.checkpoints.length + 1) { this.mission.destination_lon_lat.lon = coords.lng; this.mission.destination_lon_lat.lat = coords.lat; } else { this.mission.checkpoints[featureId - 1].lon_lat.lon = coords.lng; this.mission.checkpoints[featureId - 1].lon_lat.lat = coords.lat; } source.setData(this.geoJson); }; const onUp = () => { if (this.mapboxMap === undefined) { return; } this.mapboxMap.off("mousemove", onMove); currentFeature = null; this.mission.calculateCheckpoints(); this.showFlightplan(App.SHOW_AIRPORTS | App.SHOW_CHECKPOINTS | App.SHOW_MAP); }; const onClick = () => { if (!currentFeature) { return; } const modal = document.getElementById("edit-waypoint-modal"); const currentCheckpointIndex = Number(currentFeature.id) - 1; if (currentCheckpointIndex < 0 || currentCheckpointIndex >= this.mission.checkpoints.length) { return; } const currentCheckpoint = this.mission.checkpoints[currentCheckpointIndex]; modal.setAttribute("data-cp-id", String(currentCheckpointIndex)); modal.querySelector('[data-type="delete"]').disabled = currentFeature.id === 1 || currentFeature.id === this.mission.checkpoints.length - 1; modal.querySelector('[data-type="add-before"]').disabled = currentFeature.id === 1; modal.querySelector('[data-type="add-after"]').disabled = currentFeature.id === this.mission.checkpoints.length; document.getElementById("wp-lon").value = currentCheckpoint.lon_lat.lon.toFixed(5); document.getElementById("wp-lat").value = currentCheckpoint.lon_lat.lat.toFixed(5); document.getElementById("wp-name").value = currentCheckpoint.name; modal.addEventListener("close", () => { this.showFlightplan(App.SHOW_ALL); }, { once: true }); modal.showModal(); }; // ----------------------------------------------------------------------- // @see https://docs.mapbox.com/mapbox-gl-js/example/drag-a-point/ this.mapboxMap.on("mouseenter", "waypoints", (e) => { if (this.mapboxMap === undefined) { return; } this.mapboxMap.getCanvasContainer().style.cursor = e.originalEvent.shiftKey ? "move" : "pointer"; }); this.mapboxMap.on("mouseleave", "waypoints", () => { if (this.mapboxMap === undefined) { return; } this.mapboxMap.getCanvasContainer().style.cursor = ""; }); this.mapboxMap.on("mousedown", "waypoints", (e) => { if (this.mapboxMap === undefined) { return; } onDown(e); if (e.originalEvent.shiftKey) { this.mapboxMap.getCanvasContainer().style.cursor = "grab"; this.mapboxMap.on("mousemove", onMove); this.mapboxMap.once("mouseup", onUp); } else { onClick(); } }); this.mapboxMap.on("touchstart", "waypoints", () => { onClick(); }); }); } drawMap(resetCenter = false) { if (this.mapboxMap === undefined || this.mission.checkpoints.length === 0) { return; } if (resetCenter) { const lonLatArea = new LonLatArea(this.mission.origin_lon_lat); this.mission.checkpoints.forEach((c) => { lonLatArea.push(c.lon_lat); }); const center = lonLatArea.center; this.mapboxMap.flyTo({ center: [center.lon, center.lat], zoom: lonLatArea.getZoomLevel(16 / 9, 0.75, true), }); } const source = this.mapboxMap.getSource("waypoints"); if (source && source.type === "geojson") { const geoJsonData = this.geoJson.fromMission(this.mission); source.setData(geoJsonData); } } makeWeather() { const lastHeading = this.mission.checkpoints.length ? this.mission.checkpoints[this.mission.checkpoints.length - 1].direction : Math.floor(Math.random() * 360); this.mission.conditions.cloud.cover = Math.random(); this.mission.conditions.cloud.height_feet = this.mission.conditions.cloud.cover ? 1000 + Math.floor(Math.random() * 91) * 100 : 0; this.mission.conditions.cloud2.cover = this.mission.conditions.cloud.cover ? Math.random() : 0; this.mission.conditions.cloud2.height_feet = this.mission.conditions.cloud2.cover ? 1000 + this.mission.conditions.cloud.height_feet + Math.floor(Math.random() * 41) * 100 : 0; this.mission.conditions.cloud3.cover = this.mission.conditions.cloud2.cover ? Math.random() : 0; this.mission.conditions.cloud3.height_feet = this.mission.conditions.cloud3.cover ? 1000 + this.mission.conditions.cloud2.height_feet + Math.floor(Math.random() * 41) * 100 : 0; this.mission.conditions.thermal_strength = Math.random() * 0.5; this.mission.conditions.turbulence_strength = Math.random() * 0.5; this.mission.conditions.visibility = 5000 + Math.floor(Math.random() * 16) * 1000; this.mission.conditions.wind_direction = (360 + lastHeading - 30 + Math.floor(Math.random() * 61)) % 360; this.mission.conditions.wind_speed = Math.floor(Math.random() * 20); this.mission.conditions.wind_gusts = this.mission.conditions.wind_speed * (1 + this.mission.conditions.turbulence_strength); } syncToForm() { this.elements.aircraft_name.value = this.mission.aircraft_name; this.elements.callsign.value = this.mission.callsign; this.elements.cloud_base_feet.value = this.mission.conditions.cloud.height_feet.toFixed(); this.elements.cloud_cover.value = (this.mission.conditions.cloud.cover * 100).toFixed(); this.elements.cloud2_base_feet.value = this.mission.conditions.cloud2.height_feet.toFixed(); this.elements.cloud2_cover.value = (this.mission.conditions.cloud2.cover * 100).toFixed(); this.elements.cloud3_base_feet.value = this.mission.conditions.cloud3.height_feet.toFixed(); this.elements.cloud3_cover.value = (this.mission.conditions.cloud3.cover * 100).toFixed(); this.elements.cruise_altitude_ft.value = this.mission.cruise_altitude_ft.toFixed(); this.elements.cruise_speed.value = this.mission.cruise_speed.toFixed(); this.elements.flight_setting.value = this.mission.flight_setting; this.elements.date.valueAsDate = this.mission.conditions.time.dateTime; this.elements.description.value = this.mission.description; this.elements.metarApiKey.value = this.metarApiKey; this.elements.origin_dir.value = this.mission.origin_dir.toFixed(); this.elements.origin_dir.valueAsNumber = Math.round(this.mission.origin_dir); this.elements.thermal_strength.value = (this.mission.conditions.thermal_strength * 100).toFixed(); this.elements.time.valueAsDate = this.mission.conditions.time.dateTime; this.elements.title.value = this.mission.title; this.elements.turbulence_strength.value = (this.mission.conditions.turbulence_strength * 100).toFixed(); this.elements.turn_time.valueAsNumber = this.mission.turn_time; this.elements.visibility.value = this.mission.conditions.visibility.toFixed(); this.elements.wind_direction.value = this.mission.conditions.wind_direction.toFixed(); this.elements.wind_gusts.value = this.mission.conditions.wind_gusts.toFixed(); this.elements.wind_speed.value = this.mission.conditions.wind_speed.toFixed(); this.elements.no_guides.checked = this.mission.no_guides; this.elements.simBriefUseDestinationWeather.checked = this.simBriefUseDestinationWeather; if (this.mission.magnetic_declination !== undefined) { this.elements.magneticDeclination.valueAsNumber = this.mission.magnetic_declination; } else { this.elements.magneticDeclination.value = ""; } this.syncToOutput(); } store() { localStorage.setItem(this.constructor.name, JSON.stringify(this)); } restore() { const classSimpleMode = localStorage.getItem(App.CLASS_SIMPLE_MODE) || "1"; this.elements.main.classList.toggle(App.CLASS_SIMPLE_MODE, classSimpleMode === "1"); this.elements.expertMode.checked = classSimpleMode !== "1"; const appState = localStorage.getItem(this.constructor.name); if (appState) { this.hydrate(JSON.parse(appState)); } } syncToOutput() { this.elements.visibility_sm.value = this.mission.conditions.visibility_sm.toFixed(); this.elements.cloud_cover_code.value = this.mission.conditions.cloud.cover_code; this.elements.cloud2_cover_code.value = this.mission.conditions.cloud2.cover_code; this.elements.cloud3_cover_code.value = this.mission.conditions.cloud3.cover_code; if (this.mission.origin_icao && this.mission.destination_icao) { this.elements.makeMetarDept.innerText = "Fetch weather for " + this.mission.origin_icao; this.elements.makeMetarDest.innerText = "Fetch weather for " + this.mission.destination_icao; this.elements.makeMetarDept.disabled = this.metarApiKey.length < 4; this.elements.makeMetarDest.disabled = this.metarApiKey.length < 4; this.elements.metar.setAttribute("href", "https://metar-taf.com/" + this.mission.destination_icao); this.elements.metar.innerText = "check the weather for " + this.mission.destination_icao; } else { this.elements.makeMetarDept.disabled = true; this.elements.makeMetarDest.disabled = true; } this.elements.turn_radius.value = ((this.mission.cruise_speed * (this.mission.turn_time / 60)) / (2 * Math.PI)).toFixed(1); } showError(message) { alert(message); } toJSON() { return { metarApiKey: this.metarApiKey, simBriefUsername: this.elements.simBrief.username, simBriefUseDestinationWeather: this.simBriefUseDestinationWeather, mission: this.mission, }; } hydrate(json) { this.metarApiKey = json.metarApiKey || this.metarApiKey; this.elements.simBrief.username = json.simBriefUsername || this.elements.simBrief.username; this.simBriefUseDestinationWeather = json.simBriefUseDestinationWeather === true; this.elements.simBriefUseDestinationWeather.checked = this.simBriefUseDestinationWeather; if (json.mission) { this.mission.hydrate(json.mission); } } } App.CLASS_SIMPLE_MODE = "is-simple-mode"; App.SHOW_WEATHER = 2 ** 0; App.SHOW_AIRPORTS = 2 ** 1; App.SHOW_CHECKPOINTS = 2 ** 2; App.SHOW_MAP = 2 ** 3; App.SHOW_MAP_CENTER = 2 ** 4; App.SHOW_ALL = App.SHOW_WEATHER | App.SHOW_AIRPORTS | App.SHOW_CHECKPOINTS | App.SHOW_MAP;