From 5e352166a6cb4faf7095733ba2948a6f3b45ddca Mon Sep 17 00:00:00 2001 From: Theodore Kruczek Date: Sun, 4 Aug 2024 03:13:12 -0400 Subject: [PATCH] feat: :sparkles: add timeline feature --- public/settings/settingsOverride.js | 1 + src/plugins/plugins.ts | 220 +++++---------- src/plugins/reports/reports.ts | 25 ++ src/plugins/timeline/timeline.css | 12 + src/plugins/timeline/timeline.ts | 414 ++++++++++++++++++++++++++++ src/settings/version.js | 2 +- src/settings/versionDate.js | 2 +- 7 files changed, 525 insertions(+), 151 deletions(-) create mode 100644 src/plugins/timeline/timeline.css create mode 100644 src/plugins/timeline/timeline.ts diff --git a/public/settings/settingsOverride.js b/public/settings/settingsOverride.js index 36933ad21..55344408f 100644 --- a/public/settings/settingsOverride.js +++ b/public/settings/settingsOverride.js @@ -71,6 +71,7 @@ const settingsOverride = { videoDirector: false, reports: true, polarPlot: true, + timeline: true, }, /* * searchLimit: 150, diff --git a/src/plugins/plugins.ts b/src/plugins/plugins.ts index 5fc5aa298..0ad98d6e6 100644 --- a/src/plugins/plugins.ts +++ b/src/plugins/plugins.ts @@ -59,6 +59,7 @@ import { ShortTermFences } from './short-term-fences/short-term-fences'; import { SocialMedia } from './social/social'; import { StereoMap } from './stereo-map/stereo-map'; import { timeMachinePlugin } from './time-machine/time-machine'; +import { Timeline } from './timeline/timeline'; import { videoDirectorPlugin } from './video-director/video-director'; import { WatchlistPlugin } from './watchlist/watchlist'; import { WatchlistOverlay } from './watchlist/watchlist-overlay'; @@ -116,139 +117,83 @@ export type KeepTrackPlugins = { watchlist?: boolean; reports?: boolean; polarPlot?: boolean; + timeline?: boolean; }; // Register all core modules -export const loadPlugins = async (keepTrackApi: KeepTrackApi, plugins: KeepTrackPlugins): Promise => { +export const loadPlugins = (keepTrackApi: KeepTrackApi, plugins: KeepTrackPlugins): void => { plugins ??= {}; try { - loadCorePlugins_(plugins); + const pluginList = [ + { init: () => new DebugMenuPlugin().init(), enabled: plugins.debug }, + { init: () => new SelectSatManager().init(), enabled: true }, + { init: () => new TopMenu().init(), enabled: plugins.topMenu }, + { init: () => new SatInfoBox().init(), enabled: plugins.satInfoboxCore }, + { init: () => new DateTimeManager().init(), enabled: plugins.datetime }, + { init: () => new SocialMedia().init(), enabled: plugins.social }, + { init: () => new ClassificationBar().init(), enabled: plugins.classificationBar }, + { init: () => new SensorListPlugin().init(), enabled: plugins.sensor }, + { init: () => new SensorInfoPlugin().init(), enabled: plugins.sensor }, + { init: () => new CustomSensorPlugin().init(), enabled: plugins.sensor }, + { init: () => new LookAnglesPlugin().init(), enabled: plugins.sensor }, + { init: () => new MultiSiteLookAnglesPlugin().init(), enabled: plugins.sensor }, + { init: () => new Timeline().init(), enabled: plugins.timeline }, + { init: () => new WatchlistPlugin().init(), enabled: plugins.watchlist }, + { init: () => new WatchlistOverlay().init(), enabled: plugins.watchlist }, + { init: () => new ReportsPlugin().init(), enabled: plugins.reports }, + { init: () => new PolarPlotPlugin().init(), enabled: plugins.polarPlot }, + { init: () => new NextLaunchesPlugin().init(), enabled: plugins.nextLaunch }, + { init: () => new FindSatPlugin().init(), enabled: plugins.findSat }, + { init: () => new ShortTermFences().init(), enabled: plugins.shortTermFences }, + { init: () => new OrbitReferences().init(), enabled: plugins.orbitReferences }, + { init: () => new Collissions().init(), enabled: plugins.collisions }, + { init: () => new Breakup().init(), enabled: plugins.breakup }, + { init: () => new DebrisScreening().init(), enabled: plugins.debrisScreening }, + { init: () => new EditSat().init(), enabled: plugins.editSat }, + { init: () => new NewLaunch().init(), enabled: plugins.newLaunch }, + { init: () => missile.init(), enabled: plugins.missile }, + { init: () => new StereoMap().init(), enabled: plugins.stereoMap }, + { init: () => new SensorFov().init(), enabled: plugins.sensorFov }, + { init: () => new SensorSurvFence().init(), enabled: plugins.sensorSurv }, + { init: () => new SatelliteViewPlugin().init(), enabled: plugins.satelliteView }, + { init: () => new SatelliteFov().init(), enabled: plugins.satelliteFov }, + { init: () => new Planetarium().init(), enabled: plugins.planetarium }, + { init: () => new Astronomy().init(), enabled: plugins.astronomy }, + { init: () => new NightToggle().init(), enabled: plugins.nightToggle }, + { init: () => dopsPlugin.init(), enabled: plugins.dops }, + { init: () => satConstellationsPlugin.init(), enabled: plugins.constellations }, + { init: () => countriesMenuPlugin.init(), enabled: plugins.countries }, + { init: () => colorMenuPlugin.init(), enabled: plugins.colorsMenu }, + { init: () => screenshotPlugin.init(), enabled: plugins.screenshot }, + { init: () => launchCalendarPlugin.init(), enabled: plugins.launchCalendar }, + { init: () => timeMachinePlugin.init(), enabled: plugins.timeMachine }, + { init: () => satellitePhotosPlugin.init(), enabled: plugins.photoManager }, + { init: () => screenRecorderPlugin.init(), enabled: plugins.screenRecorder }, + { init: () => analysisMenuPlugin.init(), enabled: plugins.analysis }, + /* + * { plugin: eciPlotsPlugin, enabled: plugins.plotAnalysis }, + * { plugin: ecfPlotsPlugin, enabled: plugins.plotAnalysis }, + * { plugin: ricPlotPlugin, enabled: plugins.plotAnalysis }, + * { plugin: time2LonPlotsPlugin, enabled: plugins.plotAnalysis }, + * { plugin: inc2AltPlotPlugin, enabled: plugins.plotAnalysis }, + * { plugin: inc2LonPlotPlugin, enabled: plugins.plotAnalysis }, + * { plugin: aboutMenuPlugin, enabled: plugins.aboutManager }, + */ + { init: () => settingsMenuPlugin.init(), enabled: plugins.settingsMenu }, + { init: () => soundManagerPlugin.init(), enabled: plugins.soundManager }, + { init: () => gamepadPluginInstance.init(), enabled: plugins.gamepad }, + { init: () => videoDirectorPlugin.init(), enabled: plugins.videoDirector }, + ]; - if (plugins.classificationBar) { - new ClassificationBar().init(); + for (const { init, enabled } of pluginList) { + if (enabled) { + init(); + } } - if (plugins.sensor) { - new SensorListPlugin().init(); - new SensorInfoPlugin().init(); - new CustomSensorPlugin().init(); - new LookAnglesPlugin().init(); - new MultiSiteLookAnglesPlugin().init(); - } - if (plugins.watchlist) { - new WatchlistPlugin().init(); - new WatchlistOverlay().init(); - } - if (plugins.reports) { - new ReportsPlugin().init(); - } - if (plugins.polarPlot) { - new PolarPlotPlugin().init(); - } - if (plugins.nextLaunch) { - new NextLaunchesPlugin().init(); - } - if (plugins.findSat) { - new FindSatPlugin().init(); - } - if (plugins.shortTermFences) { - new ShortTermFences().init(); - } - if (plugins.orbitReferences) { - new OrbitReferences().init(); - } - if (plugins.collisions) { - new Collissions().init(); - } - if (plugins.breakup) { - new Breakup().init(); - } - if (plugins.debrisScreening) { - new DebrisScreening().init(); - } - if (plugins.editSat) { - new EditSat().init(); - } - if (plugins.newLaunch) { - new NewLaunch().init(); - } - if (plugins.missile) { - missile.init(); - } - if (plugins.stereoMap) { - new StereoMap().init(); - } - if (plugins.sensorFov) { - new SensorFov().init(); - } - if (plugins.sensorSurv) { - new SensorSurvFence().init(); - } - if (plugins.satelliteView) { - new SatelliteViewPlugin().init(); - } - if (plugins.satelliteFov) { - new SatelliteFov().init(); - } - if (plugins.planetarium) { - new Planetarium().init(); - } - // TODO: Fix astronomy plugin - if (plugins.astronomy) { - new Astronomy().init(); - } - if (plugins.nightToggle) { - new NightToggle().init(); - } - if (plugins.dops) { - dopsPlugin.init(); - } - if (plugins.constellations) { - satConstellationsPlugin.init(); - } - if (plugins.countries) { - countriesMenuPlugin.init(); - } - if (plugins.colorsMenu) { - colorMenuPlugin.init(); - } - if (plugins.screenshot) { - screenshotPlugin.init(); - } - if (plugins.launchCalendar) { - launchCalendarPlugin.init(); - } - if (plugins.timeMachine) { - timeMachinePlugin.init(); - } - if (plugins.photoManager) { - satellitePhotosPlugin.init(); - } - if (plugins.screenRecorder) { - screenRecorderPlugin.init(); - } - if (plugins.analysis) { - analysisMenuPlugin.init(); - } - /* - * if (plugins.plotAnalysis) eciPlotsPlugin.init(); - * if (plugins.plotAnalysis) ecfPlotsPlugin.init(); - * if (plugins.plotAnalysis) ricPlotPlugin.init(); - * if (plugins.plotAnalysis) time2LonPlotsPlugin.init(); - * if (plugins.plotAnalysis) inc2AltPlotPlugin.init(); - * if (plugins.plotAnalysis) inc2LonPlotPlugin.init(); - * if (plugins.aboutManager) aboutMenuPlugin.init(); - */ - if (plugins.settingsMenu) { - settingsMenuPlugin.init(); - } - if (plugins.soundManager) { - soundManagerPlugin.init(); - } - if (plugins.gamepad) { - gamepadPluginInstance.init(); - } - if (plugins.videoDirector) { - videoDirectorPlugin.init(); + + if (!plugins.topMenu) { + // Set --nav-bar-height of :root to 0px if topMenu is not enabled and ensure it overrides any other value + document.documentElement.style.setProperty('--nav-bar-height', '0px'); } keepTrackApi.register({ @@ -263,29 +208,6 @@ export const loadPlugins = async (keepTrackApi: KeepTrackApi, plugins: KeepTrack } }; -const loadCorePlugins_ = (plugins: KeepTrackPlugins) => { - if (plugins.debug) { - new DebugMenuPlugin().init(); - } - new SelectSatManager().init(); - - if (plugins.topMenu) { - new TopMenu().init(); - } else { - // Set --nav-bar-height of :root to 0px if topMenu is not enabled and ensure it overrides any other value - document.documentElement.style.setProperty('--nav-bar-height', '0px'); - } - if (plugins.satInfoboxCore) { - new SatInfoBox().init(); - } - if (plugins.datetime) { - new DateTimeManager().init(); - } - if (plugins.social) { - new SocialMedia().init(); - } -}; - export const uiManagerFinal = (plugins: any): void => { const bicDom = getEl('bottom-icons-container'); diff --git a/src/plugins/reports/reports.ts b/src/plugins/reports/reports.ts index b5ef8cda5..23058c53a 100644 --- a/src/plugins/reports/reports.ts +++ b/src/plugins/reports/reports.ts @@ -1,3 +1,28 @@ +/** + * ///////////////////////////////////////////////////////////////////////////// + * + * reports.ts is a plugin for generating quick reports in text format of various + * satellite and sensor data + * + * http://keeptrack.space + * + * @Copyright (C) 2016-2024 Theodore Kruczek + * @Copyright (C) 2020-2024 Heather Kruczek + * + * KeepTrack is free software: you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later version. + * + * KeepTrack is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along with + * KeepTrack. If not, see . + * + * ///////////////////////////////////////////////////////////////////////////// + */ + import { KeepTrackApiEvents } from '@app/interfaces'; import { keepTrackApi } from '@app/keepTrackApi'; import { getEl } from '@app/lib/get-el'; diff --git a/src/plugins/timeline/timeline.css b/src/plugins/timeline/timeline.css new file mode 100644 index 000000000..1a291d3c9 --- /dev/null +++ b/src/plugins/timeline/timeline.css @@ -0,0 +1,12 @@ +#timeline-menu { + display: none; + position: absolute; + width: 800px; + left: 0; + margin: 0; + border-top: 3px solid var(--color-primary); + border-left: 0; + border-right: 0; + border-bottom: 0; + z-index: 10; +} diff --git a/src/plugins/timeline/timeline.ts b/src/plugins/timeline/timeline.ts new file mode 100644 index 000000000..aa5fec042 --- /dev/null +++ b/src/plugins/timeline/timeline.ts @@ -0,0 +1,414 @@ +import { KeepTrackApiEvents } from '@app/interfaces'; +import { keepTrackApi } from '@app/keepTrackApi'; +import { getEl } from '@app/lib/get-el'; +import { errorManagerInstance } from '@app/singletons/errorManager'; +import mapPng from '@public/img/icons/map.png'; + +import { sensors } from '@app/catalogs/sensors'; +import { SatMath } from '@app/static/sat-math'; +import { BaseObject, Degrees, DetailedSatellite, DetailedSensor, Kilometers, MILLISECONDS_PER_SECOND, SatelliteRecord } from 'ootk'; +import { KeepTrackPlugin } from '../KeepTrackPlugin'; +import { SelectSatManager } from '../select-sat-manager/select-sat-manager'; +import { MultiSiteLookAnglesPlugin } from '../sensor/multi-site-look-angles-plugin'; +import { SensorManager } from '../sensor/sensorManager'; + +interface Pass { + start: Date; + end: Date; +} + +interface SensorPasses { + sensor: DetailedSensor; + passes: Pass[]; +} + +export class Timeline extends KeepTrackPlugin { + static readonly PLUGIN_NAME = 'Timeline'; + dependencies = [SelectSatManager.PLUGIN_NAME]; + private selectSatManager_: SelectSatManager; + private canvas_: HTMLCanvasElement; + private ctx_: CanvasRenderingContext2D; + private canvasStatic_: HTMLCanvasElement; + private ctxStatic_: CanvasRenderingContext2D; + multiSitePlugin_: MultiSiteLookAnglesPlugin; + drawEvents_: { [key: string]: (mouseX: number, mouseY: number) => boolean } = {}; + + constructor() { + super(Timeline.PLUGIN_NAME); + this.selectSatManager_ = keepTrackApi.getPlugin(SelectSatManager); + this.multiSitePlugin_ = keepTrackApi.getPlugin(MultiSiteLookAnglesPlugin); + } + + isRequireSatelliteSelected = true; + isIconDisabled = true; + isIconDisabledOnLoad = true; + + bottomIconElementName = 'menu-timeline'; + bottomIconImg = mapPng; + bottomIconLabel = 'Timeline'; + bottomIconCallback: () => void = () => { + if (!this.isMenuButtonActive) { + return; + } + this.resizeCanvas_(); + this.updateTimeline(); + }; + + helpTitle = 'Timeline Menu'; + helpBody = keepTrackApi.html`The timeline menu displays a chart of satellite passes across multiple sensors.`; + + sideMenuElementName = 'timeline-menu'; + sideMenuElementHtml = keepTrackApi.html` +
+ + +
`; + + addHtml(): void { + super.addHtml(); + import('./timeline.css'); + + keepTrackApi.register({ + event: KeepTrackApiEvents.uiManagerFinal, + cbName: this.PLUGIN_NAME, + cb: () => { + this.canvas_ = getEl('timeline-canvas'); + this.canvasStatic_ = getEl('timeline-canvas-static'); + this.ctx_ = this.canvas_.getContext('2d'); + this.ctxStatic_ = this.canvasStatic_.getContext('2d'); + }, + }); + } + + addJs(): void { + super.addJs(); + + keepTrackApi.register({ + event: KeepTrackApiEvents.selectSatData, + cbName: this.PLUGIN_NAME, + cb: (sat: BaseObject) => { + if (sat) { + this.updateTimeline(); + this.canvas_.style.display = 'block'; + } + }, + }); + } + + updateTimeline(): void { + try { + if (this.selectSatManager_.selectedSat === -1) { + return; + } + if (!this.isMenuButtonActive) { + return; + } + + const satellite = this.selectSatManager_.getSelectedSat() as DetailedSatellite; + const sensors_ = [sensors.BLEAFB, sensors.CODSFS, sensors.MITMIL, sensors.CAVSFS, sensors.CLRSFS, sensors.COBRADANE, sensors.RAFFYL, sensors.PITSB]; + const sensorPasses = this.calculatePasses(satellite, sensors_); + + this.drawTimeline(sensorPasses); + } catch (e) { + errorManagerInstance.info(e); + } + } + + private calculatePasses(satellite: DetailedSatellite, sensors: DetailedSensor[]): SensorPasses[] { + const sensorPasses: SensorPasses[] = []; + + for (const sensor of sensors) { + const sensorPass: SensorPasses = { + sensor, + passes: [], + }; + + // Skip if satellite is above the max range of the sensor + if (sensor.maxRng < satellite.perigee && (!sensor.maxRng2 || sensor.maxRng2 < satellite.perigee)) { + continue; + } + + SensorManager.updateSensorUiStyling([sensor]); + let offset = 0; + + const secondsIn24Hours = 24 * 60 * 60; + let isInView = false; + let isEnterView = false; + let isExitView = false; + let startTime = null; + + + for (let i = 0; i < secondsIn24Hours; i += 30) { + // 5second Looks + offset = i * 1000; // Offset in seconds (msec * 1000) + const now = keepTrackApi.getTimeManager().getOffsetTimeObj(offset); + const multiSitePass = Timeline.propagateMultiSite(now, satellite.satrec, sensor); + + // Check if in FOV + if (multiSitePass.time && !isInView) { + startTime = new Date(multiSitePass.time); + isInView = true; + isEnterView = true; + } + + if (!multiSitePass.time && isInView) { + isExitView = true; + isInView = false; + // Jump 3/4th to the next orbit + i += satellite.period * 60 * 0.75; + } + + if ((isEnterView && isExitView) || (isEnterView && i === secondsIn24Hours - 30)) { + sensorPass.passes.push({ + start: startTime, + end: now, + }); + isEnterView = false; + isExitView = false; + } + } + + sensorPasses.push(sensorPass); + } + + return sensorPasses; + } + + static propagateMultiSite(now: Date, satrec: SatelliteRecord, sensor: DetailedSensor) { + // Setup Realtime and Offset Time + const aer = SatMath.getRae(now, satrec, sensor); + + if (SatMath.checkIsInView(sensor, aer)) { + return { + time: now, + el: aer.el, + az: aer.az, + rng: aer.rng, + objName: null, + }; + } + + return { + time: null, + el: 0, + az: 0, + rng: 0, + objName: null, + }; + + } + + private drawTimeline(sensorPasses: SensorPasses[]): void { + // Clone the canvas element to remove all event listeners + const oldCanvas = this.canvas_; + const newCanvas = oldCanvas.cloneNode(true) as HTMLCanvasElement; + + oldCanvas.parentNode.replaceChild(newCanvas, oldCanvas); + this.canvas_ = newCanvas; + this.ctx_ = this.canvas_.getContext('2d'); + + // Clear the events list + this.drawEvents_ = {}; + + const leftOffset = this.canvas_.width * 0.1; + const topOffset = this.canvas_.height * 0.05; + const width = this.canvas_.width * 0.8; + const height = this.canvas_.height * 0.8; + const timeManager = keepTrackApi.getTimeManager(); + const startTime = timeManager.simulationTimeObj.getTime(); + const endTime = startTime + 24 * 60 * 60 * 1000; // 24 hours from now + + // clear canvas + this.ctx_.reset(); + + this.ctx_.fillStyle = 'rgb(31, 51, 71)'; + this.ctx_.fillRect(leftOffset, topOffset, width, height - 15); + + const yStep = height / (sensorPasses.length + 1); + const xScale = (width) / (endTime - startTime); + + // Draw time axis + this.ctx_.strokeStyle = 'rgb(255, 255, 255)'; + this.ctx_.lineWidth = 5; // Increase line width to make it thicker + this.ctx_.beginPath(); + this.ctx_.moveTo(leftOffset, topOffset + height - 20); + this.ctx_.lineTo(leftOffset + width, topOffset + height - 20); + this.ctx_.stroke(); + + // Draw hour markers + for (let i = 0; i < 25; i++) { + const x = leftOffset + ((i * 60 * 60 * 1000) * xScale); + + this.ctx_.lineWidth = 5; // Increase line width to make it thicker + this.ctx_.beginPath(); + this.ctx_.moveTo(x, topOffset + height - 25); + this.ctx_.lineTo(x, topOffset + height - 15); + this.ctx_.strokeStyle = 'rgb(255, 255, 255)'; + this.ctx_.stroke(); + + // Extend a thin line to the top of the canvas + this.ctx_.lineWidth = 1; + this.ctx_.beginPath(); + this.ctx_.moveTo(x, topOffset + height - 15); + this.ctx_.lineTo(x, topOffset); + this.ctx_.stroke(); + + let hour = timeManager.simulationTimeObj.getUTCHours(); + + if (hour + i > 23) { + hour = hour + i - 24; + } else { + hour += i; + } + + this.ctx_.font = '12px Consolas'; + this.ctx_.fillStyle = 'rgb(255, 255, 255)'; + this.ctx_.fillText(`${hour}h`, x - 10, topOffset + height - 5); + } + + // Draw passes for each sensor + sensorPasses.forEach((sensorPass, index) => { + const y = topOffset + (index + 1) * yStep; + + // Draw sensor name + this.ctx_.fillStyle = 'rgb(255, 255, 255)'; + this.ctx_.font = '12px Consolas'; + this.ctx_.fillText(sensorPass.sensor.shortName, leftOffset - 30, y + 5); + + // Draw passes + sensorPass.passes.forEach((pass) => { + const passStart = pass.start.getTime(); + const passEnd = pass.end.getTime(); + const x1 = leftOffset + (passStart - startTime) * xScale; + const x2 = leftOffset + (passEnd - startTime) * xScale; + + const passLength = (passEnd - passStart) / MILLISECONDS_PER_SECOND; + + if (passLength < 120) { + this.ctx_.fillStyle = 'rgb(255, 42, 4)'; + } else if (passLength < 240) { + this.ctx_.fillStyle = 'rgb(252, 232, 58)'; + } else { + this.ctx_.fillStyle = 'rgb(86, 240, 0)'; + } + + this.ctx_.fillRect(x1, y - 10, x2 - x1, 20); + + + const drawEvent = (mouseX: number, mouseY: number): boolean => { + if (mouseX >= x1 - 10 && mouseX <= x2 + 10 && mouseY >= y - 10 && mouseY <= y + 10) { + const startTime = new Date(passStart).toISOString().slice(11, 19); + const endTime = new Date(passEnd).toISOString().slice(11, 19); + + // Calculate width of box based on text + const text = `${sensorPass.sensor.shortName}: ${startTime} - ${endTime}`; + + this.ctx_.font = '12px Consolas'; + + const boxWidth = this.ctx_.measureText(text).width; + + // Draw tooltip box (first box is bigger to make a white border) + this.ctx_.fillStyle = 'rgb(255, 255, 255)'; + this.ctx_.fillRect(mouseX - boxWidth / 2 - 6, mouseY - 30, boxWidth + 12, 24); + // Draw tooltip box (second box is smaller to create a border effect) + this.ctx_.fillStyle = 'rgb(31, 51, 71)'; + this.ctx_.fillRect(mouseX - boxWidth / 2 - 3, mouseY - 27, boxWidth + 6, 18); + + // Draw tooltip text + this.ctx_.fillStyle = 'rgb(255, 255, 255)'; + this.ctx_.fillText(text, mouseX - boxWidth / 2, mouseY - 15); + + // Make mouse cursor a pointer + this.canvas_.style.cursor = 'pointer'; + + return true; + } + + return false; + }; + + this.drawEvents_[`${index}-${passStart}-${passEnd}`] = drawEvent; + + // Create an onclick event for each pass + this.canvas_.addEventListener('click', (event) => { + const rect = this.canvas_.getBoundingClientRect(); + const mouseX = event.clientX - rect.left; + const mouseY = event.clientY - rect.top; + + // If the mouse is over a pass change the sensor + if (drawEvent(mouseX, mouseY)) { + const timeManagerInstance = keepTrackApi.getTimeManager(); + + keepTrackApi.getSensorManager().setSensor(sensorPass.sensor); + + timeManagerInstance.changeStaticOffset(new Date(passStart).getTime() - timeManagerInstance.realTime); + timeManagerInstance.calculateSimulationTime(); + keepTrackApi.runEvent(KeepTrackApiEvents.updateDateTime, new Date(timeManagerInstance.dynamicOffsetEpoch + timeManagerInstance.staticOffset)); + + const currentSatId = this.selectSatManager_.selectedSat; + + this.selectSatManager_.selectSat(null); + this.selectSatManager_.selectSat(currentSatId); + } + }); + }); + }); + + // Add one mousemove event + this.canvas_.addEventListener('mousemove', (event) => { + this.handleOnMouseMove_(event); + }); + + // Save initial state as staticCtx_ so we can redraw the static elements without clearing the canvas + this.ctxStatic_ = this.canvasStatic_.getContext('2d'); + this.ctxStatic_.drawImage(this.canvas_, 0, 0); + } + + private handleOnMouseMove_(event: MouseEvent): void { + // clear canvas + this.ctx_.reset(); + + // Draw static elements + this.ctx_.drawImage(this.canvasStatic_, 0, 0); + + const rect = this.canvas_.getBoundingClientRect(); + const mouseX = event.clientX - rect.left; + const mouseY = event.clientY - rect.top; + + let isHoveringOverPass = false; + + // eslint-disable-next-line guard-for-in + for (const key in this.drawEvents_) { + const success = this.drawEvents_[key](mouseX, mouseY); + + isHoveringOverPass = isHoveringOverPass || success; + } + + if (!isHoveringOverPass) { + this.canvas_.style.cursor = 'default'; + } + } + + private resizeCanvas_(isForceWidescreen?: boolean): void { + isForceWidescreen ??= false; + const timelineMenuDOM = getEl('timeline-menu'); + + if (isForceWidescreen || window.innerWidth > window.innerHeight) { + timelineMenuDOM.style.width = `${window.innerWidth}px`; + + this.canvas_.width = window.innerWidth; + this.canvas_.height = window.innerHeight; + } else { + settingsManager.mapWidth = settingsManager.mapHeight * 2; + timelineMenuDOM.style.width = `${settingsManager.mapWidth}px`; + + this.canvas_.width = window.innerWidth; + this.canvas_.style.width = `${window.innerWidth}px`; + this.canvas_.height = window.innerHeight - 100; + this.canvas_.style.height = `${window.innerHeight - 100}px`; + } + + this.canvasStatic_.width = this.canvas_.width; + this.canvasStatic_.height = this.canvas_.height; + } +} diff --git a/src/settings/version.js b/src/settings/version.js index e513730dd..c54b071c7 100644 --- a/src/settings/version.js +++ b/src/settings/version.js @@ -1,4 +1,4 @@ // THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -export const VERSION = '9.0.3'; +export const VERSION = '9.1.0'; diff --git a/src/settings/versionDate.js b/src/settings/versionDate.js index 43eb2f633..0b9904f9f 100644 --- a/src/settings/versionDate.js +++ b/src/settings/versionDate.js @@ -1,2 +1,2 @@ // THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -export const VERSION_DATE = 'August 3, 2024'; +export const VERSION_DATE = 'August 4, 2024';