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` -
`; + const sideMenuHtmlWrapped = this.generateSideMenuHtml_(); this.addSideMenu(sideMenuHtmlWrapped); } else { @@ -270,7 +277,7 @@ export class KeepTrackPlugin { if (this.sideMenuSettingsHtml) { const sideMenuHtmlWrapped = keepTrackApi.html` - `; 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_ =