diff --git a/package-lock.json b/package-lock.json index fdc3462f9..68dcfa6df 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "gremlins.js": "^2.2.0", "jquery": "^3.7.1", "jquery-ui-bundle": "^1.12.1-migrate", + "material-icons": "^1.13.12", "meeusjs": "^1.0.4", "new-github-issue-url": "^1.0.0", "node-fetch": "^3.3.2", @@ -12170,6 +12171,11 @@ "version": "0.1.0", "dev": true }, + "node_modules/material-icons": { + "version": "1.13.12", + "resolved": "https://registry.npmjs.org/material-icons/-/material-icons-1.13.12.tgz", + "integrity": "sha512-/2YoaB79IjUK2B2JB+vIXXYGtBfHb/XG66LvoKVM5ykHW7yfrV5SP6d7KLX6iijY6/G9GqwgtPQ/sbhFnOURVA==" + }, "node_modules/meeusjs": { "version": "1.0.4", "license": "MIT" diff --git a/package.json b/package.json index 2142bb23c..5fb2ee2ea 100644 --- a/package.json +++ b/package.json @@ -147,6 +147,7 @@ "gremlins.js": "^2.2.0", "jquery": "^3.7.1", "jquery-ui-bundle": "^1.12.1-migrate", + "material-icons": "^1.13.12", "meeusjs": "^1.0.4", "new-github-issue-url": "^1.0.0", "node-fetch": "^3.3.2", diff --git a/public/css/fonts.css b/public/css/fonts.css index f09c137ab..741ec462a 100644 --- a/public/css/fonts.css +++ b/public/css/fonts.css @@ -28,49 +28,6 @@ /* Safari, Android, iOS */ url('fonts/droid/droid-sans-v6-latin-regular.svg#DroidSans') format('svg'); /* Legacy iOS */ } -/*Material Icons*/ - -/* fallback */ -@font-face { - font-family: 'Material Icons'; - font-style: normal; - font-weight: 400; - src: url('fonts/material-icons/material-icons.eot'); /* For IE6-8 */ - src: - local('Material Icons'), - local('MaterialIcons-Regular'), - url('fonts/material-icons/material-icons.woff2') format('woff2'), - url('fonts/material-icons/material-icons.woff') format('woff'), - url('fonts/material-icons/material-icons.ttf') format('truetype'); -} - -.material-icons { - font-family: 'Material Icons', sans-serif; - font-weight: normal; - font-style: normal; - font-size: 24px; /* Preferred icon size */ - display: inline-block; - line-height: 1; - text-transform: none; - letter-spacing: normal; - word-wrap: normal; - white-space: nowrap; - direction: ltr; - - /* Support for all WebKit browsers. */ - -webkit-font-smoothing: antialiased; - /* Support for Safari and Chrome. */ - text-rendering: optimizeLegibility; - - /* Support for Firefox. */ - -moz-osx-font-smoothing: grayscale; - - /* Support for IE. */ - font-feature-settings: 'liga'; -} - -/*Material Icons*/ - /*Icomoon Font*/ @font-face { diff --git a/scripts/webpack.mjs b/scripts/webpack.mjs index 30a3fd549..7b03fd6a4 100644 --- a/scripts/webpack.mjs +++ b/scripts/webpack.mjs @@ -22,6 +22,7 @@ export const generateConfig = (env, isWatch) => { * @type {webpack.Configuration[]} */ const webpackConfig = []; + env ??= 'production'; let baseConfig = getBaseConfig(dirName); @@ -71,9 +72,11 @@ export const generateConfig = (env, isWatch) => { // split entry points main, webworkers, and possibly analysis tools const mainConfig = getMainConfig(baseConfig, dirName, 'dist'); + webpackConfig.push(mainConfig); const exampleConfig = getMainConfig(baseConfig, dirName, 'dist', '../../'); + exampleConfig.plugins = [ new webpack.ProvidePlugin({ '$': 'jquery', @@ -84,20 +87,22 @@ export const generateConfig = (env, isWatch) => { beforeCompile: true, }), ]; - const examples = readdirSync(`./public/examples`, { withFileTypes: true }); + const examples = readdirSync('./public/examples', { withFileTypes: true }); + examples.forEach((example) => { if (!example.isDirectory()) { exampleConfig.plugins.push( new HtmlWebpackPlugin({ filename: `../examples/${example.name}`, template: `./public/examples/${example.name}`, - }) + }), ); } }); webpackConfig.push(exampleConfig); const webWorkerConfig = getWebWorkerConfig(baseConfig, dirName, 'dist', ''); + webpackConfig.push(webWorkerConfig); return webpackConfig; @@ -145,7 +150,7 @@ const getBaseConfig = (dirName) => ({ }, { test: /\.css$/iu, - include: [/src/u, /public/u], + include: [/node_modules/u, /src/u, /public/u], use: ['style-loader', 'css-loader'], generator: { filename: './css/[name][ext]', @@ -216,13 +221,14 @@ const getNonEmbedConfig = (baseConfig, env) => { new HtmlWebpackPlugin({ filename: '../index.html', template: './public/index.html', - }) + }), ); baseConfig.module.rules.push({ test: /\.(?:woff|woff2|eot|ttf|otf)$/iu, include: [/src/u], type: 'asset/resource', }); + return baseConfig; }; diff --git a/src/keeptrack.ts b/src/keeptrack.ts index 8792a3834..7769ccf29 100644 --- a/src/keeptrack.ts +++ b/src/keeptrack.ts @@ -35,6 +35,7 @@ import rocket2Jpg from '@public/img/wallpaper/rocket2.jpg'; import rocket3Jpg from '@public/img/wallpaper/rocket3.jpg'; import telescopeJpg from '@public/img/wallpaper/telescope.jpg'; import thuleJpg from '@public/img/wallpaper/thule.jpg'; +import 'material-icons/iconfont/material-icons.css'; import eruda from 'eruda'; import erudaFps from 'eruda-fps'; diff --git a/src/lib/click-and-drag.ts b/src/lib/click-and-drag.ts index bc2c6e49f..ba5bee4dd 100644 --- a/src/lib/click-and-drag.ts +++ b/src/lib/click-and-drag.ts @@ -23,7 +23,7 @@ export const clickAndDragWidth = (el: HTMLElement | null, options: clickDragOpti // create new element on right edge const edgeEl = createElWidth_(el); - addEventsWidth_(edgeEl, el, width, minWidth, maxWidth, options.attachedElement); + addEventsWidth_(edgeEl, el, width, minWidth, maxWidth, options.attachedElement, options.leftOffset); } }; @@ -39,7 +39,7 @@ export const clickAndDragHeight = (el: HTMLElement, maxHeight?: number, callback addEventsHeight_(edgeEl, el, callback, maxHeight); }; -const addEventsWidth_ = (edgeEl: HTMLDivElement, el: HTMLElement, width: number, minWidth: number, maxWidth: number, attachedElement?: HTMLElement) => { +const addEventsWidth_ = (edgeEl: HTMLDivElement, el: HTMLElement, width: number, minWidth: number, maxWidth: number, attachedElement?: HTMLElement, leftOffset?: number) => { let startX: number; let startWidth: number; @@ -72,7 +72,7 @@ const addEventsWidth_ = (edgeEl: HTMLDivElement, el: HTMLElement, width: number, width = width > maxWidth ? maxWidth : width; el.style.width = `${width}px`; - if (attachedElement) { + if (attachedElement && !leftOffset) { attachedElement.style.left = `${el.getBoundingClientRect().right}px`; } }); diff --git a/src/plugins/KeepTrackPlugin.ts b/src/plugins/KeepTrackPlugin.ts index 11567fd12..5a8f7e030 100644 --- a/src/plugins/KeepTrackPlugin.ts +++ b/src/plugins/KeepTrackPlugin.ts @@ -12,12 +12,29 @@ import { SelectSatManager } from './select-sat-manager/select-sat-manager'; import { SoundNames } from './sounds/SoundNames'; export interface clickDragOptions { + leftOffset?: number; isDraggable?: boolean; minWidth?: number; maxWidth?: number; attachedElement?: HTMLElement; } +interface SideMenuSettingsOptions { + /** + * The width of the side menu's settings sub-menu. + */ + width: number; + /** + * Override for the left offset of the side menu's settings sub-menu. + * If this is not set then it will default to the right edge of the side menu. + */ + leftOffset?: number; + /** + * The z-index of the side menu's settings sub-menu. + */ + zIndex: number; +} + /** * Represents a plugin for KeepTrack. */ @@ -84,10 +101,17 @@ export class KeepTrackPlugin { */ sideMenuSettingsHtml: string; + sideMenuSettingsOptions: SideMenuSettingsOptions = { + width: 300, + leftOffset: null, + zIndex: 5, + }; + /** - * The width of the side menu's settings sub-menu. + * The callback to run when the download icon is clicked. + * Download icon is automatically added if this is defined. */ - sideMenuSettingsWidth: number = 300; + downloadIconCb: () => void = null; /** * Whether the side menu settings are open. @@ -227,6 +251,8 @@ export class KeepTrackPlugin { throw new Error(`${this.PLUGIN_NAME} HTML already added.`); } + this.sideMenuSettingsOptions.leftOffset = typeof this.sideMenuSettingsOptions.leftOffset === 'number' ? this.sideMenuSettingsOptions.leftOffset : null; + if (this.bottomIconElementName || this.bottomIconLabel) { if (!this.bottomIconElementName || !this.bottomIconLabel) { throw new Error(`${this.PLUGIN_NAME} bottom icon element name, image, and label must all be defined.`); @@ -239,26 +265,7 @@ export class KeepTrackPlugin { if (this.sideMenuElementName && this.sideMenuElementHtml) { if (this.sideMenuSettingsHtml) { - const settingsBtn = `${this.sideMenuElementName}-settings-btn`; - const menuWidthStr = `${this.sideMenuSettingsWidth.toString()} px !important`; - const sideMenuHtmlWrapped = keepTrackApi.html` -
-
-
-
${this.sideMenuTitle}
- -
-
  • - ${this.sideMenuElementHtml} -
    -
    `; + const sideMenuHtmlWrapped = this.generateSideMenuHtml_(); this.addSideMenu(sideMenuHtmlWrapped); } else { @@ -270,7 +277,7 @@ export class KeepTrackPlugin { if (this.sideMenuSettingsHtml) { const sideMenuHtmlWrapped = keepTrackApi.html` -
    +
    ${this.sideMenuSettingsHtml} @@ -294,6 +301,13 @@ export class KeepTrackPlugin { getEl(`${this.sideMenuElementName}-settings-btn`).style.color = 'var(--statusDarkNormal)'; } }); + + if (this.downloadIconCb) { + getEl(`${this.sideMenuElementName}-download-btn`).addEventListener('click', () => { + keepTrackApi.getSoundManager().play(SoundNames.EXPORT); + this.downloadIconCb(); + }); + } }, }); } @@ -345,6 +359,49 @@ export class KeepTrackPlugin { */ isRmbOnSat: boolean = false; + private generateSideMenuHtml_() { + const menuWidthStr = `${this.sideMenuSettingsOptions.width.toString()} px !important`; + const downloadIconHtml = this.downloadIconCb ? keepTrackApi.html` + + ` : ''; + const settingsIconHtml = keepTrackApi.html` + `; + const spacerDiv = keepTrackApi.html`
    `; + + const sideMenuHtmlWrapped = keepTrackApi.html` +
    +
    +
    + ${spacerDiv} + ${this.downloadIconCb ? spacerDiv : ''} +
    ${this.sideMenuTitle}
    + ${downloadIconHtml} + ${settingsIconHtml} +
    +
  • + ${this.sideMenuElementHtml} +
    +
    `; + + + return sideMenuHtmlWrapped; + } + /** * Adds the JS for the KeepTrackPlugin. * @throws {Error} If the JS has already been added. @@ -649,7 +706,11 @@ export class KeepTrackPlugin { if (settingsMenuElement) { this.isSideMenuSettingsOpen = true; - settingsMenuElement.style.left = `${sideMenuElement.getBoundingClientRect().right}px`; + if (this.sideMenuSettingsOptions.leftOffset !== null) { + settingsMenuElement.style.left = `${this.sideMenuSettingsOptions.leftOffset}px`; + } else { + settingsMenuElement.style.left = `${sideMenuElement.getBoundingClientRect().right}px`; + } slideInRight(settingsMenuElement, 1000); } } @@ -696,6 +757,9 @@ export class KeepTrackPlugin { if (this.sideMenuSettingsHtml) { opts.attachedElement = getEl(`${this.sideMenuElementName}-settings`); } + if (this.sideMenuSettingsOptions.leftOffset) { + opts.leftOffset = this.sideMenuSettingsOptions.leftOffset; + } clickAndDragWidth(getEl(this.sideMenuElementName), opts); }, }); diff --git a/src/plugins/sensor/custom-sensor-plugin.ts b/src/plugins/sensor/custom-sensor-plugin.ts index c4aec1bdc..669183bfb 100644 --- a/src/plugins/sensor/custom-sensor-plugin.ts +++ b/src/plugins/sensor/custom-sensor-plugin.ts @@ -12,7 +12,6 @@ import customPng from '@public/img/icons/custom.png'; import { Degrees, DetailedSensor, Kilometers, SpaceObjectType, ZoomValue } from 'ootk'; import { KeepTrackPlugin, clickDragOptions } from '../KeepTrackPlugin'; import { SoundNames } from '../sounds/SoundNames'; -import { MultiSiteLookAnglesPlugin } from './multi-site-look-angles-plugin'; export class CustomSensorPlugin extends KeepTrackPlugin { bottomIconCallback: () => void = () => { @@ -209,9 +208,8 @@ export class CustomSensorPlugin extends KeepTrackPlugin { isDraggable: true, }; - static PLUGIN_NAME = 'Custom Sensor'; constructor() { - super(MultiSiteLookAnglesPlugin.PLUGIN_NAME); + super(CustomSensorPlugin.name); } helpTitle = 'Custom Sensor Menu'; diff --git a/src/plugins/sensor/look-angles-plugin.ts b/src/plugins/sensor/look-angles-plugin.ts index a41ebcbce..b5aeb449c 100644 --- a/src/plugins/sensor/look-angles-plugin.ts +++ b/src/plugins/sensor/look-angles-plugin.ts @@ -10,7 +10,6 @@ import lookanglesPng from '@public/img/icons/lookangles.png'; import { BaseObject, DetailedSatellite, DetailedSensor } from 'ootk'; import { KeepTrackPlugin, clickDragOptions } from '../KeepTrackPlugin'; import { SelectSatManager } from '../select-sat-manager/select-sat-manager'; -import { SoundNames } from '../sounds/SoundNames'; export class LookAnglesPlugin extends KeepTrackPlugin { dependencies = [SelectSatManager.PLUGIN_NAME]; private selectSatManager_: SelectSatManager; @@ -32,6 +31,7 @@ export class LookAnglesPlugin extends KeepTrackPlugin { * The length of time to calculate look angles for */ private lengthOfLookAngles_ = 2; + /** * The last look angles array */ @@ -52,8 +52,8 @@ export class LookAnglesPlugin extends KeepTrackPlugin { dragOptions: clickDragOptions = { isDraggable: true, - minWidth: 300, - maxWidth: 450, + minWidth: 400, + maxWidth: 600, }; helpTitle = 'Look Angles Menu'; @@ -70,11 +70,6 @@ export class LookAnglesPlugin extends KeepTrackPlugin { sideMenuTitle: string = 'Sensor Look Angles'; sideMenuElementHtml: string = keepTrackApi.html`
    -
    -
    - -
    -
    `; @@ -102,6 +97,15 @@ export class LookAnglesPlugin extends KeepTrackPlugin {
    `; + downloadIconCb = () => { + const sensor = keepTrackApi.getSensorManager().getSensor(); + + saveCsv(this.lastlooksArray_, `${sensor.shortName ?? sensor.objName ?? 'unk'}-${(this.selectSatManager_.getSelectedSat() as DetailedSatellite).sccNum6}-look-angles`); + }; + sideMenuSettingsOptions = { + width: 300, + zIndex: 3, + }; addHtml(): void { super.addHtml(); @@ -119,11 +123,6 @@ export class LookAnglesPlugin extends KeepTrackPlugin { this.refreshSideMenuData_(); }); - getEl('export-look-angles')?.addEventListener('click', () => { - keepTrackApi.getSoundManager().play(SoundNames.EXPORT); - saveCsv(this.lastlooksArray_, 'Look-Angles'); - }); - getEl('settings-riseset').addEventListener('change', this.settingsRisesetChange_.bind(this)); const sat = this.selectSatManager_.getSelectedSat(); diff --git a/src/plugins/sensor/multi-site-look-angles-plugin.ts b/src/plugins/sensor/multi-site-look-angles-plugin.ts index 7b98c010e..343346754 100644 --- a/src/plugins/sensor/multi-site-look-angles-plugin.ts +++ b/src/plugins/sensor/multi-site-look-angles-plugin.ts @@ -14,12 +14,10 @@ import { SelectSatManager } from '../select-sat-manager/select-sat-manager'; import { SoundNames } from '../sounds/SoundNames'; import { SensorManager } from './sensorManager'; export class MultiSiteLookAnglesPlugin extends KeepTrackPlugin { - static readonly PLUGIN_NAME = 'Multi Site Look Angles'; - dependencies = [SelectSatManager.PLUGIN_NAME]; + dependencies = [SelectSatManager.name]; private selectSatManager_: SelectSatManager; - // combine sensorListSsn, sesnorListLeoLabs, and SensorListRus - private sensorList = keepTrackApi.getSensorManager().sensorListSsn.concat( + private sensorList_ = keepTrackApi.getSensorManager().sensorListSsn.concat( keepTrackApi.getSensorManager().sensorListMw, keepTrackApi.getSensorManager().sensorListMda, keepTrackApi.getSensorManager().sensorListLeoLabs, @@ -28,18 +26,26 @@ export class MultiSiteLookAnglesPlugin extends KeepTrackPlugin { keepTrackApi.getSensorManager().sensorListPrc, keepTrackApi.getSensorManager().sensorListOther, ); + isRequireSatelliteSelected = true; + isRequireSensorSelected = false; + + // Settings + private lengthOfLookAngles_ = 1; // Days + private angleCalculationInterval_ = 30; + private disabledSensors_: DetailedSensor[] = this.sensorList_.filter((s) => + !keepTrackApi.getSensorManager().sensorListMw.includes(s), + ); constructor() { - super(MultiSiteLookAnglesPlugin.PLUGIN_NAME); + super(MultiSiteLookAnglesPlugin.name); this.selectSatManager_ = keepTrackApi.getPlugin(SelectSatManager); + // remove duplicates in sensorList - this.sensorList = this.sensorList.filter( + this.sensorList_ = this.sensorList_.filter( (sensor, index, self) => index === self.findIndex((t) => t.uiName === sensor.uiName), ); } - isRequireSatelliteSelected: boolean = true; - isRequireSensorSelected: boolean = false; bottomIconCallback: () => void = () => { const sat = this.selectSatManager_.getSelectedSat(); @@ -50,11 +56,6 @@ export class MultiSiteLookAnglesPlugin extends KeepTrackPlugin { this.refreshSideMenuData(sat as DetailedSatellite); }; - lookanglesLength = 1; // Days - lookanglesInterval = 30; - disabledSensors: DetailedSensor[] = this.sensorList.filter((s) => - !keepTrackApi.getSensorManager().sensorListMw.includes(s), - ); bottomIconElementName = 'multi-site-look-angles-icon'; bottomIconLabel = 'Multi-Site Looks'; @@ -83,11 +84,6 @@ export class MultiSiteLookAnglesPlugin extends KeepTrackPlugin { sideMenuTitle: string = 'Multi-Sensor Look Angles'; sideMenuElementHtml: string = keepTrackApi.html`
    -
    -
    - -
    -
    `; @@ -97,29 +93,26 @@ export class MultiSiteLookAnglesPlugin extends KeepTrackPlugin {
    `; sideMenuSettingsWidth: number = 350; + downloadIconCb = () => { + keepTrackApi.getSoundManager().play(SoundNames.EXPORT); + const exportData = keepTrackApi.getSensorManager().lastMultiSiteArray.map((look) => ({ + time: look.time, + sensor: look.objName, + az: look.az.toFixed(2), + el: look.el.toFixed(2), + rng: look.rng.toFixed(2), + })); + + saveCsv(exportData, `multisite-${(this.selectSatManager_.getSelectedSat() as DetailedSatellite).sccNum6}-look-angles`); + }; + sideMenuSettingsOptions = { + width: 300, + zIndex: 3, + }; addHtml(): void { super.addHtml(); - keepTrackApi.register({ - event: KeepTrackApiEvents.uiManagerFinal, - cbName: this.PLUGIN_NAME, - cb: () => { - getEl('multi-site-look-angles-export')?.addEventListener('click', () => { - keepTrackApi.getSoundManager().play(SoundNames.EXPORT); - const exportData = keepTrackApi.getSensorManager().lastMultiSiteArray.map((look) => ({ - time: look.time, - sensor: look.objName, - az: look.az.toFixed(2), - el: look.el.toFixed(2), - rng: look.rng.toFixed(2), - })); - - saveCsv(exportData, 'multiSiteLooks'); - }); - }, - }); - keepTrackApi.register({ event: KeepTrackApiEvents.selectSatData, cbName: this.PLUGIN_NAME, @@ -176,7 +169,7 @@ export class MultiSiteLookAnglesPlugin extends KeepTrackPlugin { const allSensors: DetailedSensor[] = []; - for (const sensor of this.sensorList) { + for (const sensor of this.sensorList_) { if (!sensor.objName) { continue; } @@ -184,7 +177,7 @@ export class MultiSiteLookAnglesPlugin extends KeepTrackPlugin { const sensorButton = document.createElement('button'); sensorButton.classList.add('btn', 'btn-ui', 'waves-effect', 'waves-light'); - if (this.disabledSensors.includes(sensor)) { + if (this.disabledSensors_.includes(sensor)) { sensorButton.classList.add('btn-red'); } @@ -194,17 +187,17 @@ export class MultiSiteLookAnglesPlugin extends KeepTrackPlugin { sensorButton.addEventListener('click', () => { if (sensorButton.classList.contains('btn-red')) { sensorButton.classList.remove('btn-red'); - this.disabledSensors.splice(this.disabledSensors.indexOf(sensor), 1); + this.disabledSensors_.splice(this.disabledSensors_.indexOf(sensor), 1); keepTrackApi.getSoundManager().play(SoundNames.TOGGLE_ON); } else { sensorButton.classList.add('btn-red'); - this.disabledSensors.push(sensor); + this.disabledSensors_.push(sensor); keepTrackApi.getSoundManager().play(SoundNames.TOGGLE_OFF); } this.getlookanglesMultiSite_( sat, - allSensors.filter((s) => !this.disabledSensors.includes(s)), + allSensors.filter((s) => !this.disabledSensors_.includes(s)), ); }); sensorListDom.appendChild(sensorButton); @@ -213,7 +206,7 @@ export class MultiSiteLookAnglesPlugin extends KeepTrackPlugin { this.getlookanglesMultiSite_( sat, - allSensors.filter((s) => !this.disabledSensors.includes(s)), + allSensors.filter((s) => !this.disabledSensors_.includes(s)), ); }); } @@ -252,7 +245,7 @@ export class MultiSiteLookAnglesPlugin extends KeepTrackPlugin { SensorManager.updateSensorUiStyling([sensor]); let offset = 0; - for (let i = 0; i < this.lookanglesLength * 24 * 60 * 60; i += this.lookanglesInterval) { + for (let i = 0; i < this.lengthOfLookAngles_ * 24 * 60 * 60; i += this.angleCalculationInterval_) { // 5second Looks offset = i * 1000; // Offset in seconds (msec * 1000) const now = timeManagerInstance.getOffsetTimeObj(offset); diff --git a/src/plugins/timeline/timeline.ts b/src/plugins/timeline/timeline.ts index aa5fec042..0e8e1398f 100644 --- a/src/plugins/timeline/timeline.ts +++ b/src/plugins/timeline/timeline.ts @@ -2,14 +2,13 @@ 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 viewTimelinePng from '@public/img/icons/view_timeline.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 { BaseObject, Degrees, DetailedSatellite, DetailedSensor, Hours, Kilometers, MILLISECONDS_PER_SECOND, SatelliteRecord, Seconds } 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 { @@ -23,20 +22,38 @@ interface SensorPasses { } 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 } = {}; + private drawEvents_: { [key: string]: (mouseX: number, mouseY: number) => boolean } = {}; + private allSensorLists_ = keepTrackApi.getSensorManager().sensorListSsn.concat( + keepTrackApi.getSensorManager().sensorListMw, + keepTrackApi.getSensorManager().sensorListMda, + keepTrackApi.getSensorManager().sensorListLeoLabs, + keepTrackApi.getSensorManager().sensorListEsoc, + keepTrackApi.getSensorManager().sensorListRus, + keepTrackApi.getSensorManager().sensorListPrc, + keepTrackApi.getSensorManager().sensorListOther, + ); + disabledSensors_: DetailedSensor[] = this.allSensorLists_.filter((s) => + !keepTrackApi.getSensorManager().sensorListMw.includes(s), + ); + private lengthOfLookAngles_ = 6 as Hours; + private lengthOfBadPass_ = 120 as Seconds; + private lengthOfAvgPass_ = 240 as Seconds; + private angleCalculationInterval_ = 30; constructor() { - super(Timeline.PLUGIN_NAME); + super(Timeline.name); this.selectSatManager_ = keepTrackApi.getPlugin(SelectSatManager); - this.multiSitePlugin_ = keepTrackApi.getPlugin(MultiSiteLookAnglesPlugin); + + // remove duplicates in sensorList + this.allSensorLists_ = this.allSensorLists_.filter( + (sensor, index, self) => index === self.findIndex((t) => t.uiName === sensor.uiName), + ); } isRequireSatelliteSelected = true; @@ -44,7 +61,7 @@ export class Timeline extends KeepTrackPlugin { isIconDisabledOnLoad = true; bottomIconElementName = 'menu-timeline'; - bottomIconImg = mapPng; + bottomIconImg = viewTimelinePng; bottomIconLabel = 'Timeline'; bottomIconCallback: () => void = () => { if (!this.isMenuButtonActive) { @@ -58,11 +75,63 @@ export class Timeline extends KeepTrackPlugin { helpBody = keepTrackApi.html`The timeline menu displays a chart of satellite passes across multiple sensors.`; sideMenuElementName = 'timeline-menu'; + sideMenuTitle: string = 'Timeline'; sideMenuElementHtml = keepTrackApi.html` -
    +
    +
    `; + sideMenuSettingsHtml: string = keepTrackApi.html` +
    + +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +
    +
    +
    `; + sideMenuSettingsOptions = { + width: 350, + leftOffset: 0, + zIndex: 10, + }; + downloadIconCb = () => { + const canvas = document.getElementById('timeline-canvas') as HTMLCanvasElement; + const image = canvas.toDataURL('image/png').replace('image/png', 'image/octet-stream'); + const link = document.createElement('a'); + + link.href = image; + link.download = `sat-${(this.selectSatManager_.getSelectedSat() as DetailedSatellite).sccNum6}-timeline.png`; + link.click(); + }; addHtml(): void { super.addHtml(); @@ -76,8 +145,27 @@ export class Timeline extends KeepTrackPlugin { this.canvasStatic_ = getEl('timeline-canvas-static'); this.ctx_ = this.canvas_.getContext('2d'); this.ctxStatic_ = this.canvasStatic_.getContext('2d'); + + getEl('timeline-setting-total-length').addEventListener('change', () => { + this.lengthOfLookAngles_ = parseFloat((getEl('timeline-setting-total-length')).value) as Hours; + this.ctxStatic_.reset(); + this.updateTimeline(); + }); + + getEl('timeline-setting-bad-length').addEventListener('change', () => { + this.lengthOfBadPass_ = parseFloat((getEl('timeline-setting-bad-length')).value) as Seconds; + this.ctxStatic_.reset(); + this.updateTimeline(); + }); + + getEl('timeline-setting-avg-length').addEventListener('change', () => { + this.lengthOfAvgPass_ = parseFloat((getEl('timeline-setting-avg-length')).value) as Seconds; + this.ctxStatic_.reset(); + this.updateTimeline(); + }); }, }); + } addJs(): void { @@ -106,7 +194,7 @@ export class Timeline extends KeepTrackPlugin { 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_); + const sensorPasses = this.calculatePasses_(satellite, sensors_); this.drawTimeline(sensorPasses); } catch (e) { @@ -114,7 +202,7 @@ export class Timeline extends KeepTrackPlugin { } } - private calculatePasses(satellite: DetailedSatellite, sensors: DetailedSensor[]): SensorPasses[] { + private calculatePasses_(satellite: DetailedSatellite, sensors: DetailedSensor[]): SensorPasses[] { const sensorPasses: SensorPasses[] = []; for (const sensor of sensors) { @@ -131,14 +219,14 @@ export class Timeline extends KeepTrackPlugin { SensorManager.updateSensorUiStyling([sensor]); let offset = 0; - const secondsIn24Hours = 24 * 60 * 60; + const durationInSeconds = this.lengthOfLookAngles_ * 60 * 60; let isInView = false; let isEnterView = false; let isExitView = false; let startTime = null; - for (let i = 0; i < secondsIn24Hours; i += 30) { + for (let i = 0; i < durationInSeconds; i += this.angleCalculationInterval_) { // 5second Looks offset = i * 1000; // Offset in seconds (msec * 1000) const now = keepTrackApi.getTimeManager().getOffsetTimeObj(offset); @@ -158,7 +246,7 @@ export class Timeline extends KeepTrackPlugin { i += satellite.period * 60 * 0.75; } - if ((isEnterView && isExitView) || (isEnterView && i === secondsIn24Hours - 30)) { + if ((isEnterView && isExitView) || (isEnterView && i === durationInSeconds - this.angleCalculationInterval_)) { sensorPass.passes.push({ start: startTime, end: now, @@ -211,12 +299,12 @@ export class Timeline extends KeepTrackPlugin { this.drawEvents_ = {}; const leftOffset = this.canvas_.width * 0.1; - const topOffset = this.canvas_.height * 0.05; + const topOffset = 0; const width = this.canvas_.width * 0.8; - const height = this.canvas_.height * 0.8; + const height = this.canvas_.height * 0.75; const timeManager = keepTrackApi.getTimeManager(); const startTime = timeManager.simulationTimeObj.getTime(); - const endTime = startTime + 24 * 60 * 60 * 1000; // 24 hours from now + const endTime = startTime + this.lengthOfLookAngles_ * 60 * 60 * 1000; // 24 hours from now // clear canvas this.ctx_.reset(); @@ -236,7 +324,7 @@ export class Timeline extends KeepTrackPlugin { this.ctx_.stroke(); // Draw hour markers - for (let i = 0; i < 25; i++) { + for (let i = 0; i <= this.lengthOfLookAngles_; i++) { const x = leftOffset + ((i * 60 * 60 * 1000) * xScale); this.ctx_.lineWidth = 5; // Increase line width to make it thicker @@ -255,15 +343,11 @@ export class Timeline extends KeepTrackPlugin { let hour = timeManager.simulationTimeObj.getUTCHours(); - if (hour + i > 23) { - hour = hour + i - 24; - } else { - hour += i; - } + hour = (hour + i) % 24; - this.ctx_.font = '12px Consolas'; + this.ctx_.font = '14px Consolas'; this.ctx_.fillStyle = 'rgb(255, 255, 255)'; - this.ctx_.fillText(`${hour}h`, x - 10, topOffset + height - 5); + this.ctx_.fillText(`${hour}h`, x - 10, topOffset + height); } // Draw passes for each sensor @@ -272,7 +356,7 @@ export class Timeline extends KeepTrackPlugin { // Draw sensor name this.ctx_.fillStyle = 'rgb(255, 255, 255)'; - this.ctx_.font = '12px Consolas'; + this.ctx_.font = '14px Consolas'; this.ctx_.fillText(sensorPass.sensor.shortName, leftOffset - 30, y + 5); // Draw passes @@ -284,9 +368,9 @@ export class Timeline extends KeepTrackPlugin { const passLength = (passEnd - passStart) / MILLISECONDS_PER_SECOND; - if (passLength < 120) { + if (passLength < this.lengthOfBadPass_) { this.ctx_.fillStyle = 'rgb(255, 42, 4)'; - } else if (passLength < 240) { + } else if (passLength < this.lengthOfAvgPass_) { this.ctx_.fillStyle = 'rgb(252, 232, 58)'; } else { this.ctx_.fillStyle = 'rgb(86, 240, 0)'; @@ -303,7 +387,7 @@ export class Timeline extends KeepTrackPlugin { // Calculate width of box based on text const text = `${sensorPass.sensor.shortName}: ${startTime} - ${endTime}`; - this.ctx_.font = '12px Consolas'; + this.ctx_.font = '14px Consolas'; const boxWidth = this.ctx_.measureText(text).width; diff --git a/src/plugins/top-menu/top-menu.ts b/src/plugins/top-menu/top-menu.ts index 6ec53f278..ea75077ab 100644 --- a/src/plugins/top-menu/top-menu.ts +++ b/src/plugins/top-menu/top-menu.ts @@ -5,9 +5,9 @@ import { adviceManagerInstance } from '@app/singletons/adviceManager'; import fullscreenIconPng from '@public/img/fullscreen-icon.png'; import helpPng from '@public/img/help.png'; import findPng from '@public/img/icons/find.png'; +import layersIconPng from '@public/img/icons/layers-icon.png'; import soundOffPng from '@public/img/icons/sound-off.png'; import soundOnPng from '@public/img/icons/sound-on.png'; -import layersIconPng from '@public/img/layers-icon.png'; import { errorManagerInstance } from '../../singletons/errorManager'; import { KeepTrackPlugin } from '../KeepTrackPlugin';