Compare commits
	
		
			29 Commits
		
	
	
		
			recursive-
			...
			feature/ti
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | a6ac54383d | ||
|   | 490f25add8 | ||
|   | 694255db6b | ||
|   | 6910ae0a2b | ||
|   | b3bf7f2db1 | ||
|   | 348ba9085b | ||
|   | 9d6a5e2e17 | ||
|   | 5cb0e2e885 | ||
|   | ee277f3547 | ||
|   | bf77d240c7 | ||
|   | 684a3d2807 | ||
|   | 27e01ef13f | ||
|   | 4a2b1640e9 | ||
|   | 38cb92b203 | ||
|   | 0792ae0ae4 | ||
|   | d462f5d763 | ||
|   | cb8e97dc1c | ||
|   | aed3c19edd | ||
|   | a19c5cfd52 | ||
|   | 03e5241041 | ||
|   | 29ed251685 | ||
|   | 60433a12b8 | ||
|   | 69b8dd6c37 | ||
|   | e6c78c1826 | ||
|   | a1657817dc | ||
|   | 29538e6e78 | ||
|   | 68e9152d6a | ||
|   | 02b2b47411 | ||
|   | 70624c2c5c | 
							
								
								
									
										20
									
								
								index.html
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								index.html
									
									
									
									
									
								
							| @@ -48,6 +48,7 @@ | ||||
|         openmct.install(openmct.plugins.Generator()); | ||||
|         openmct.install(openmct.plugins.ExampleImagery()); | ||||
|         openmct.install(openmct.plugins.UTCTimeSystem()); | ||||
|         openmct.install(openmct.plugins.LocalTimeSystem()); | ||||
|         openmct.install(openmct.plugins.AutoflowView({ | ||||
|             type: "telemetry.panel" | ||||
|         })); | ||||
| @@ -72,7 +73,24 @@ | ||||
|                         start: - THIRTY_MINUTES, | ||||
|                         end: FIVE_MINUTES | ||||
|                     } | ||||
|                 } | ||||
|                 }, | ||||
|                 { | ||||
|                     name: "Fixed", | ||||
|                     timeSystem: 'local', | ||||
|                     bounds: { | ||||
|                         start: Date.now() - THIRTY_MINUTES, | ||||
|                         end: Date.now() | ||||
|                     } | ||||
|                 }, | ||||
|                 { | ||||
|                     name: "Realtime", | ||||
|                     timeSystem: 'local', | ||||
|                     clock: 'local', | ||||
|                     clockOffsets: { | ||||
|                         start: - THIRTY_MINUTES, | ||||
|                         end: FIVE_MINUTES | ||||
|                     } | ||||
|                 }, | ||||
|             ] | ||||
|         })); | ||||
|         openmct.install(openmct.plugins.SummaryWidget()); | ||||
|   | ||||
| @@ -14,6 +14,7 @@ | ||||
|     "css-loader": "^1.0.0", | ||||
|     "d3-array": "1.2.x", | ||||
|     "d3-axis": "1.0.x", | ||||
|     "d3-brush": "^1.1.3", | ||||
|     "d3-collection": "1.0.x", | ||||
|     "d3-color": "1.0.x", | ||||
|     "d3-format": "1.2.x", | ||||
|   | ||||
| @@ -41,7 +41,7 @@ define([], function () { | ||||
|         this.timeFormat = 'local-format'; | ||||
|         this.durationFormat = 'duration'; | ||||
|  | ||||
|         this.isUTCBased = false; | ||||
|         this.isUTCBased = true; | ||||
|     } | ||||
|  | ||||
|     return LocalTimeSystem; | ||||
|   | ||||
| @@ -88,15 +88,24 @@ | ||||
|                 </div> | ||||
|  | ||||
|                 <conductor-axis | ||||
|                         class="c-conductor__ticks" | ||||
|                         :bounds="rawBounds" | ||||
|                         @panAxis="setViewFromBounds"></conductor-axis> | ||||
|                     class="c-conductor__ticks" | ||||
|                     :bounds="rawBounds" | ||||
|                     :isFixed="isFixed" | ||||
|                     @panAxis="setViewFromBounds" | ||||
|                     @zoomAxis="setViewFromBounds" | ||||
|                 ></conductor-axis> | ||||
|  | ||||
|             </div> | ||||
|             <div class="c-conductor__controls"> | ||||
|                 <!-- Mode, time system menu buttons and duration slider --> | ||||
|                 <ConductorMode class="c-conductor__mode-select"></ConductorMode> | ||||
|                 <ConductorTimeSystem class="c-conductor__time-system-select"></ConductorTimeSystem> | ||||
|                 <ConductorHistory | ||||
|                     v-if="isFixed" | ||||
|                     class="c-conductor__history-select" | ||||
|                     :bounds="bounds" | ||||
|                     :time-system="timeSystem" | ||||
|                     @select-timespan="setViewFromBounds" | ||||
|                 ></ConductorHistory> | ||||
|             </div> | ||||
|             <input type="submit" class="invisible"> | ||||
|         </form> | ||||
| @@ -294,6 +303,7 @@ import ConductorTimeSystem from './ConductorTimeSystem.vue'; | ||||
| import DatePicker from './DatePicker.vue'; | ||||
| import ConductorAxis from './ConductorAxis.vue'; | ||||
| import ConductorModeIcon from './ConductorModeIcon.vue'; | ||||
| import ConductorHistory from './ConductorHistory.vue' | ||||
|  | ||||
| const DEFAULT_DURATION_FORMATTER = 'duration'; | ||||
| const SECONDS = 1000; | ||||
| @@ -309,7 +319,8 @@ export default { | ||||
|         ConductorTimeSystem, | ||||
|         DatePicker, | ||||
|         ConductorAxis, | ||||
|         ConductorModeIcon | ||||
|         ConductorModeIcon, | ||||
|         ConductorHistory | ||||
|     }, | ||||
|     data() { | ||||
|         let bounds = this.openmct.time.bounds(); | ||||
| @@ -319,6 +330,7 @@ export default { | ||||
|         let durationFormatter = this.getFormatter(timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER); | ||||
|  | ||||
|         return { | ||||
|             timeSystem: timeSystem, | ||||
|             timeFormatter: timeFormatter, | ||||
|             durationFormatter: durationFormatter, | ||||
|             offsets: { | ||||
| @@ -329,6 +341,10 @@ export default { | ||||
|                 start: timeFormatter.format(bounds.start), | ||||
|                 end: timeFormatter.format(bounds.end) | ||||
|             }, | ||||
|             bounds: { | ||||
|                 start: bounds.start, | ||||
|                 end: bounds.end | ||||
|             }, | ||||
|             rawBounds: { | ||||
|                 start: bounds.start, | ||||
|                 end: bounds.end | ||||
| @@ -340,10 +356,10 @@ export default { | ||||
|     }, | ||||
|     methods: { | ||||
|         setTimeSystem(timeSystem) { | ||||
|             this.timeSystem = timeSystem | ||||
|             this.timeFormatter = this.getFormatter(timeSystem.timeFormat); | ||||
|             this.durationFormatter = this.getFormatter( | ||||
|                 timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER); | ||||
|  | ||||
|             this.isUTCBased = timeSystem.isUTCBased; | ||||
|         }, | ||||
|         setOffsetsFromView($event) { | ||||
| @@ -492,11 +508,14 @@ export default { | ||||
|             this.validateAllBounds(); | ||||
|             this.submitForm(); | ||||
|         }, | ||||
|         setNewBounds(bounds) { | ||||
|             this.bounds.start = bounds.start; | ||||
|             this.bounds.end = bounds.end; | ||||
|         } | ||||
|     }, | ||||
|     mounted() { | ||||
|         this.setTimeSystem(JSON.parse(JSON.stringify(this.openmct.time.timeSystem()))); | ||||
|  | ||||
|         this.openmct.time.on('bounds', this.setViewFromBounds); | ||||
|         this.openmct.time.on('bounds', this.setNewBounds); | ||||
|         this.openmct.time.on('timeSystem', this.setTimeSystem); | ||||
|         this.openmct.time.on('clock', this.setViewFromClock); | ||||
|         this.openmct.time.on('clockOffsets', this.setViewFromOffsets) | ||||
|   | ||||
| @@ -22,7 +22,8 @@ | ||||
| <template> | ||||
|     <div class="c-conductor-axis" | ||||
|          ref="axisHolder" | ||||
|          @mousedown="dragStart($event)"> | ||||
|          @mousedown="dragStart($event)" | ||||
|     > | ||||
|     </div> | ||||
| </template> | ||||
|  | ||||
| @@ -44,7 +45,7 @@ | ||||
|             text-rendering: geometricPrecision; | ||||
|             width: 100%; | ||||
|             height: 100%; | ||||
|             > g { | ||||
|             > g.axis { | ||||
|                 // Overall Tick holder | ||||
|                 transform: translateY($tickYPos); | ||||
|                 path { | ||||
| @@ -97,6 +98,7 @@ | ||||
|                     transition: $transIn; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|         } | ||||
|  | ||||
|         .is-realtime-mode & { | ||||
| @@ -115,6 +117,7 @@ | ||||
| import * as d3Selection from 'd3-selection'; | ||||
| import * as d3Axis from 'd3-axis'; | ||||
| import * as d3Scale from 'd3-scale'; | ||||
| import * as d3Brush from 'd3-brush'; | ||||
| import utcMultiTimeFormat from './utcMultiTimeFormat.js'; | ||||
|  | ||||
| const PADDING = 1; | ||||
| @@ -126,7 +129,13 @@ const PIXELS_PER_TICK_WIDE = 200; | ||||
| export default { | ||||
|     inject: ['openmct'], | ||||
|     props: { | ||||
|         bounds: Object | ||||
|         bounds: Object, | ||||
|         isFixed: Boolean | ||||
|     }, | ||||
|     data() { | ||||
|         return { | ||||
|             altPressed: false | ||||
|         } | ||||
|     }, | ||||
|     methods: { | ||||
|         setScale() { | ||||
| @@ -171,9 +180,8 @@ export default { | ||||
|         }, | ||||
|         getActiveFormatter() { | ||||
|             let timeSystem = this.openmct.time.timeSystem(); | ||||
|             let isFixed = this.openmct.time.clock() === undefined; | ||||
|  | ||||
|             if (isFixed) { | ||||
|             if (this.isFixed) { | ||||
|                 return this.getFormatter(timeSystem.timeFormat); | ||||
|             } else { | ||||
|                 return this.getFormatter(timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER); | ||||
| @@ -185,8 +193,7 @@ export default { | ||||
|             }).formatter; | ||||
|         }, | ||||
|         dragStart($event){ | ||||
|             let isFixed = this.openmct.time.clock() === undefined; | ||||
|             if (isFixed){ | ||||
|             if (this.isFixed){ | ||||
|                 this.dragStartX = $event.clientX; | ||||
|  | ||||
|                 document.addEventListener('mousemove', this.drag); | ||||
| @@ -198,18 +205,20 @@ export default { | ||||
|         drag($event) { | ||||
|             if (!this.dragging){ | ||||
|                 this.dragging = true; | ||||
|                 requestAnimationFrame(()=>{ | ||||
|                     let deltaX = $event.clientX - this.dragStartX; | ||||
|                     let percX = deltaX / this.width; | ||||
|                     let bounds = this.openmct.time.bounds(); | ||||
|                     let deltaTime = bounds.end - bounds.start; | ||||
|                     let newStart = bounds.start - percX * deltaTime; | ||||
|                     this.$emit('panAxis',{ | ||||
|                         start: newStart, | ||||
|                         end: newStart + deltaTime | ||||
|                     }); | ||||
|                     this.dragging = false; | ||||
|                 }) | ||||
|                 if (this.altPressed) { | ||||
|                     requestAnimationFrame(()=>{ | ||||
|                         let deltaX = $event.clientX - this.dragStartX; | ||||
|                         let percX = deltaX / this.width; | ||||
|                         let bounds = this.openmct.time.bounds(); | ||||
|                         let deltaTime = bounds.end - bounds.start; | ||||
|                         let newStart = bounds.start - percX * deltaTime; | ||||
|                         this.$emit('panAxis',{ | ||||
|                             start: newStart, | ||||
|                             end: newStart + deltaTime | ||||
|                         }); | ||||
|                     }) | ||||
|                 } | ||||
|                 this.dragging = false; | ||||
|             } else { | ||||
|                 console.log('Rejected drag due to RAF cap'); | ||||
|             } | ||||
| @@ -225,6 +234,55 @@ export default { | ||||
|             if (this.$refs.axisHolder.clientWidth !== this.width) { | ||||
|                 this.width = this.$refs.axisHolder.clientWidth; | ||||
|                 this.setScale(); | ||||
|                 this.destroyBrush(); | ||||
|                 this.initBrush(); | ||||
|                 this.createBrush(); | ||||
|             } | ||||
|         }, | ||||
|         initBrush() { | ||||
|             // brush extent y coordinate starts at -1 to prevent underlying layer cursor to show | ||||
|             this.brush = d3Brush.brushX() | ||||
|                 .extent([[0, -1], [this.width, this.height]]) | ||||
|                 .on("end", this.brushEnd); | ||||
|         }, | ||||
|         createBrush() { | ||||
|             if (!this.isFixed) { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             this.svg = this.svg || d3Selection.select('svg'); | ||||
|             if (!this.svg) { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             this.svg.append("g") | ||||
|                 .attr("class", "brush") | ||||
|                 .call(this.brush); | ||||
|         }, | ||||
|         brushEnd () { | ||||
|             const selection = d3Selection.event.selection; | ||||
|             if (!d3Selection.event.sourceEvent || !selection) { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             // SMELL | ||||
|             const [x0, x1] = selection.map(d => this.xScale.invert(d).getTime()); | ||||
|             this.openmct.time.bounds({ | ||||
|                 start: x0, | ||||
|                 end: x1 | ||||
|             }); | ||||
|             this.$emit('zoomAxis', { | ||||
|                 start: x0, | ||||
|                 end: x1 | ||||
|             }); | ||||
|  | ||||
|             // clear brush | ||||
|             d3Selection.select('g.brush').call(this.brush.move, null); | ||||
|         }, | ||||
|         destroyBrush() { | ||||
|             const brush = d3Selection.select('g.brush') | ||||
|             if (brush){ | ||||
|                 brush.remove(); | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
| @@ -234,22 +292,40 @@ export default { | ||||
|                 this.setScale(); | ||||
|             }, | ||||
|             deep: true | ||||
|         }, | ||||
|         isFixed: function(value) { | ||||
|             value ? this.createBrush() : this.destroyBrush(); | ||||
|         } | ||||
|     }, | ||||
|     created() { | ||||
|         document.addEventListener('keydown', (e) => { | ||||
|             if (e.key === 'Alt') { | ||||
|                 this.altPressed = true; | ||||
|                 this.destroyBrush(); | ||||
|             } | ||||
|         }); | ||||
|         document.addEventListener('keyup', (e) => { | ||||
|             if (e.key === 'Alt') { | ||||
|                 this.altPressed = false; | ||||
|                 this.createBrush(); | ||||
|             } | ||||
|         }); | ||||
|     }, | ||||
|     mounted() { | ||||
|         let axisHolder = this.$refs.axisHolder; | ||||
|         let height = axisHolder.offsetHeight; | ||||
|         this.height = axisHolder.offsetHeight; | ||||
|         let vis = d3Selection.select(axisHolder) | ||||
|             .append("svg:svg") | ||||
|             .attr("width", "100%") | ||||
|             .attr("height", height); | ||||
|             .attr("height", this.height); | ||||
|  | ||||
|         this.width = this.$refs.axisHolder.clientWidth; | ||||
|         this.xAxis = d3Axis.axisTop(); | ||||
|         this.dragging = false; | ||||
|  | ||||
|         // draw x axis with labels. CSS is used to position them. | ||||
|         this.axisElement = vis.append("g"); | ||||
|         this.axisElement = vis.append("g") | ||||
|             .attr("class", "axis"); | ||||
|  | ||||
|         this.setViewFromTimeSystem(this.openmct.time.timeSystem()); | ||||
|         this.setScale(); | ||||
| @@ -257,6 +333,9 @@ export default { | ||||
|         //Respond to changes in conductor | ||||
|         this.openmct.time.on("timeSystem", this.setViewFromTimeSystem); | ||||
|         setInterval(this.resize, RESIZE_POLL_INTERVAL); | ||||
|  | ||||
|         this.initBrush(); | ||||
|         this.createBrush(); | ||||
|     }, | ||||
|     destroyed() { | ||||
|     } | ||||
|   | ||||
							
								
								
									
										161
									
								
								src/plugins/timeConductor/ConductorHistory.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										161
									
								
								src/plugins/timeConductor/ConductorHistory.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,161 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT Web, Copyright (c) 2014-2018, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT Web is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT Web includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
| <template> | ||||
|     <div class="c-ctrl-wrapper c-ctrl-wrapper--menus-up"> | ||||
|         <button class="c-button--menu c-history-button icon-history" | ||||
|              @click.prevent="toggle"> | ||||
|             <span class="c-button__label">History</span> | ||||
|         </button> | ||||
|         <div class="c-menu" v-if="open"> | ||||
|             <ul | ||||
|                 v-if="isUTCBased" | ||||
|             > | ||||
|                 <li @click="selectHours(24)">Last 24 hours</li> | ||||
|                 <li @click="selectHours(2)">Last 2 hours</li> | ||||
|                 <li @click="selectHours(1)">Last hour</li> | ||||
|             </ul> | ||||
|             <ul> | ||||
|                 <li | ||||
|                     v-for="(timespan, index) in historyForCurrentTimeSystem" | ||||
|                     :key="index" | ||||
|                     @click="selectTimespan(timespan)" | ||||
|                 > | ||||
|                     {{ formatTime(timespan.start) }} - {{ formatTime(timespan.end) }} | ||||
|                 </li> | ||||
|             </ul> | ||||
|         </div>     | ||||
|     </div> | ||||
| </template> | ||||
|  | ||||
| <style lang="scss"> | ||||
|     @import "~styles/sass-base"; | ||||
| </style> | ||||
|  | ||||
| <script> | ||||
| import toggleMixin from '../../ui/mixins/toggle-mixin'; | ||||
| import utcMultiTimeFormat from './utcMultiTimeFormat.js'; | ||||
| import _ from 'lodash' | ||||
|  | ||||
| const LOCAL_STORAGE_HISTORY_MAX_RECORDS = 20; | ||||
| const LOCAL_STORAGE_HISTORY_KEY = 'tcHistory'; | ||||
|  | ||||
| export default { | ||||
|     inject: ['openmct'], | ||||
|     mixins: [toggleMixin], | ||||
|     props: { | ||||
|         bounds: { | ||||
|             type: Object, | ||||
|             required: true | ||||
|         }, | ||||
|         timeSystem: { | ||||
|             type: Object, | ||||
|             required: true | ||||
|         } | ||||
|     }, | ||||
|     data() { | ||||
|         return { | ||||
|             history: {} // contains arrays of timespans {start, end}, array key is time system key | ||||
|         } | ||||
|     }, | ||||
|     computed: { | ||||
|         isUTCBased() { | ||||
|             return this.timeSystem.isUTCBased; | ||||
|         }, | ||||
|         historyForCurrentTimeSystem() { | ||||
|             return this.history[this.timeSystem.key] | ||||
|         } | ||||
|     }, | ||||
|     methods: { | ||||
|         getHistoryFromLocalStorage() { | ||||
|             if (localStorage.getItem(LOCAL_STORAGE_HISTORY_KEY)) { | ||||
|                 this.history = JSON.parse(localStorage.getItem(LOCAL_STORAGE_HISTORY_KEY)) | ||||
|             } else { | ||||
|                 this.history = {}; | ||||
|                 this.persistHistoryToLocalStorage(); | ||||
|             } | ||||
|         }, | ||||
|         persistHistoryToLocalStorage() { | ||||
|             localStorage.setItem(LOCAL_STORAGE_HISTORY_KEY, JSON.stringify(this.history)); | ||||
|         }, | ||||
|         addTimespan() { | ||||
|             const key = this.timeSystem.key; | ||||
|             let [...currentHistory] = this.history[key] || []; | ||||
|             const timespan = { | ||||
|                 start: this.bounds.start, | ||||
|                 end: this.bounds.end, | ||||
|             }; | ||||
|  | ||||
|             // when choosing an existing entry, remove it and add it back as latest entry | ||||
|             currentHistory = currentHistory.filter((entry) => { | ||||
|                 return !_.isEqual(timespan, entry); | ||||
|             }); | ||||
|  | ||||
|             if (currentHistory.length >= LOCAL_STORAGE_HISTORY_MAX_RECORDS) { | ||||
|                 currentHistory.shift(); | ||||
|             } | ||||
|  | ||||
|             currentHistory.push(timespan); | ||||
|             this.history[key] = currentHistory; | ||||
|         }, | ||||
|         selectTimespan(timespan) { | ||||
|             this.$emit('select-timespan', timespan); | ||||
|         }, | ||||
|         selectHours(hours) { | ||||
|             const now = Date.now(); | ||||
|             this.selectTimespan({ | ||||
|                 start: now - hours * 60 * 60 * 1000, | ||||
|                 end: now | ||||
|             }); | ||||
|         }, | ||||
|         formatTime(time) { | ||||
|             const formatter = this.openmct.telemetry.getValueFormatter({ | ||||
|                 format: this.timeSystem.timeFormat | ||||
|             }).formatter; | ||||
|  | ||||
|             return formatter.format(time); | ||||
|         } | ||||
|     }, | ||||
|     watch: { | ||||
|         bounds: { | ||||
|             handler() { | ||||
|                 this.addTimespan(); | ||||
|             }, | ||||
|             deep: true | ||||
|         }, | ||||
|         timeSystem: { | ||||
|             handler() { | ||||
|                 this.addTimespan(); | ||||
|             }, | ||||
|             deep: true | ||||
|         }, | ||||
|         history: { | ||||
|             handler() { | ||||
|                 this.persistHistoryToLocalStorage(); | ||||
|             }, | ||||
|             deep: true | ||||
|         } | ||||
|     }, | ||||
|     mounted() { | ||||
|         this.getHistoryFromLocalStorage(); | ||||
|     } | ||||
| } | ||||
| </script> | ||||
| @@ -432,6 +432,11 @@ select { | ||||
|              content: ''; // Add this element so that menu items without an icon still indent properly | ||||
|         } | ||||
|     } | ||||
|     ul { | ||||
|         &:not(:last-child) { | ||||
|             border-bottom: 1px solid $colorInteriorBorder; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| .c-menu { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user