Compare commits
	
		
			12 Commits
		
	
	
		
			dom-testin
			...
			v1.1.1
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | d5028eed98 | ||
|   | 480e327c63 | ||
|   | e8b10f0193 | ||
|   | 1d64dc4653 | ||
|   | 08c1053a66 | ||
|   | 7508062aac | ||
|   | 2f58dfbfe7 | ||
|   | 166cb55945 | ||
|   | 83116257fc | ||
|   | 775f1048bc | ||
|   | 71a8b377bb | ||
|   | c43d3fcfc9 | 
							
								
								
									
										40
									
								
								index.html
									
									
									
									
									
								
							
							
						
						
									
										40
									
								
								index.html
									
									
									
									
									
								
							| @@ -34,8 +34,8 @@ | ||||
|     <body> | ||||
|     </body> | ||||
|     <script> | ||||
|         const FIVE_MINUTES = 5 * 60 * 1000; | ||||
|         const THIRTY_MINUTES = 30 * 60 * 1000; | ||||
|         const THIRTY_SECONDS = 30 * 1000; | ||||
|         const THIRTY_MINUTES = THIRTY_SECONDS * 60; | ||||
|  | ||||
|         [ | ||||
|             'example/eventGenerator' | ||||
| @@ -63,7 +63,39 @@ | ||||
|                     bounds: { | ||||
|                         start: Date.now() - THIRTY_MINUTES, | ||||
|                         end: Date.now() | ||||
|                     } | ||||
|                     }, | ||||
|                     // commonly used bounds can be stored in history | ||||
|                     // bounds (start and end) can accept either a milliseconds number | ||||
|                     // or a callback function returning a milliseconds number | ||||
|                     // a function is useful for invoking Date.now() at exact moment of preset selection | ||||
|                     presets: [ | ||||
|                         { | ||||
|                             label: 'Last Day', | ||||
|                             bounds: { | ||||
|                                 start: () => Date.now() - 1000 * 60 * 60 * 24, | ||||
|                                 end: () => Date.now() | ||||
|                             } | ||||
|                         }, | ||||
|                         { | ||||
|                             label: 'Last 2 hours', | ||||
|                             bounds: { | ||||
|                                 start: () => Date.now() - 1000 * 60 * 60 * 2, | ||||
|                                 end: () => Date.now() | ||||
|                             } | ||||
|                         }, | ||||
|                         { | ||||
|                             label: 'Last hour', | ||||
|                             bounds: { | ||||
|                                 start: () => Date.now() - 1000 * 60 * 60, | ||||
|                                 end: () => Date.now() | ||||
|                             } | ||||
|                         } | ||||
|                     ], | ||||
|                     // maximum recent bounds to retain in conductor history | ||||
|                     records: 10, | ||||
|                     // maximum duration between start and end bounds | ||||
|                     // for utc-based time systems this is in milliseconds | ||||
|                     limit: 1000 * 60 * 60 * 24 | ||||
|                 }, | ||||
|                 { | ||||
|                     name: "Realtime", | ||||
| @@ -71,7 +103,7 @@ | ||||
|                     clock: 'local', | ||||
|                     clockOffsets: { | ||||
|                         start: - THIRTY_MINUTES, | ||||
|                         end: FIVE_MINUTES | ||||
|                         end: THIRTY_SECONDS | ||||
|                     } | ||||
|                 } | ||||
|             ] | ||||
|   | ||||
| @@ -53,9 +53,9 @@ | ||||
|     "marked": "^0.3.5", | ||||
|     "mini-css-extract-plugin": "^0.4.1", | ||||
|     "minimist": "^1.1.1", | ||||
|     "moment": "^2.11.1", | ||||
|     "moment": "2.25.3", | ||||
|     "moment-duration-format": "^2.2.2", | ||||
|     "moment-timezone": "^0.5.21", | ||||
|     "moment-timezone": "0.5.28", | ||||
|     "node-bourbon": "^4.2.3", | ||||
|     "node-sass": "^4.9.2", | ||||
|     "painterro": "^0.2.65", | ||||
|   | ||||
| @@ -204,7 +204,7 @@ export default class ConditionClass extends EventEmitter { | ||||
|         let latestTimestamp; | ||||
|         let criteriaResults = {}; | ||||
|         const criteriaRequests = this.criteria | ||||
|             .map(criterion => criterion.requestLAD({telemetryObjects: this.conditionManager.telemetryObjects})); | ||||
|             .map(criterion => criterion.requestLAD(this.conditionManager.telemetryObjects)); | ||||
|  | ||||
|         return Promise.all(criteriaRequests) | ||||
|             .then(results => { | ||||
|   | ||||
| @@ -55,7 +55,7 @@ export default class ConditionManager extends EventEmitter { | ||||
|         this.telemetryObjects[id] = Object.assign({}, endpoint, {telemetryMetaData: this.openmct.telemetry.getMetadata(endpoint).valueMetadatas}); | ||||
|         this.subscriptions[id] = this.openmct.telemetry.subscribe( | ||||
|             endpoint, | ||||
|             this.telemetryReceived.bind(this, id) | ||||
|             this.telemetryReceived.bind(this, endpoint) | ||||
|         ); | ||||
|         this.updateConditionTelemetry(); | ||||
|     } | ||||
| @@ -258,9 +258,13 @@ export default class ConditionManager extends EventEmitter { | ||||
|                             this.openmct.time.timeSystem() | ||||
|                         ); | ||||
|                     }); | ||||
|                     const currentCondition = this.getCurrentConditionLAD(conditionResults); | ||||
|  | ||||
|                     return Object.assign( | ||||
|                     if (!Object.values(latestTimestamp).some(timeSystem => timeSystem)) { | ||||
|                         return []; | ||||
|                     } | ||||
|  | ||||
|                     const currentCondition = this.getCurrentConditionLAD(conditionResults); | ||||
|                     const currentOutput = Object.assign( | ||||
|                         { | ||||
|                             output: currentCondition.configuration.output, | ||||
|                             id: this.conditionSetDomainObject.identifier, | ||||
| @@ -268,11 +272,15 @@ export default class ConditionManager extends EventEmitter { | ||||
|                         }, | ||||
|                         latestTimestamp | ||||
|                     ); | ||||
|  | ||||
|                     return [currentOutput]; | ||||
|                 }); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     isTelemetryUsed(id) { | ||||
|     isTelemetryUsed(endpoint) { | ||||
|         const id = this.openmct.objects.makeKeyString(endpoint.identifier); | ||||
|  | ||||
|         for(const condition of this.conditionClassCollection) { | ||||
|             if (condition.isTelemetryUsed(id)) { | ||||
|                 return true; | ||||
| @@ -282,12 +290,12 @@ export default class ConditionManager extends EventEmitter { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     telemetryReceived(id, datum) { | ||||
|         if (!this.isTelemetryUsed(id)) { | ||||
|     telemetryReceived(endpoint, datum) { | ||||
|         if (!this.isTelemetryUsed(endpoint)) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         const normalizedDatum = this.createNormalizedDatum(datum, id); | ||||
|         const normalizedDatum = this.createNormalizedDatum(datum, endpoint); | ||||
|         const timeSystemKey = this.openmct.time.timeSystem().key; | ||||
|         let timestamp = {}; | ||||
|         timestamp[timeSystemKey] = normalizedDatum[timeSystemKey]; | ||||
| @@ -321,8 +329,11 @@ export default class ConditionManager extends EventEmitter { | ||||
|         return data; | ||||
|     } | ||||
|  | ||||
|     createNormalizedDatum(telemetryDatum, id) { | ||||
|         const normalizedDatum = Object.values(this.telemetryObjects[id].telemetryMetaData).reduce((datum, metadatum) => { | ||||
|     createNormalizedDatum(telemetryDatum, endpoint) { | ||||
|         const id = this.openmct.objects.makeKeyString(endpoint.identifier); | ||||
|         const metadata = this.openmct.telemetry.getMetadata(endpoint).valueMetadatas; | ||||
|  | ||||
|         const normalizedDatum = Object.values(metadata).reduce((datum, metadatum) => { | ||||
|             const testValue = this.getTestData(metadatum); | ||||
|             const formatter = this.openmct.telemetry.getValueFormatter(metadatum); | ||||
|             datum[metadatum.key] = testValue !== undefined ?  formatter.parse(testValue) : formatter.parse(telemetryDatum[metadatum.source]); | ||||
|   | ||||
| @@ -54,13 +54,22 @@ export default class ConditionSetMetadataProvider { | ||||
|         return { | ||||
|             values: this.getDomains().concat([ | ||||
|                 { | ||||
|                     name: 'Output', | ||||
|                     key: 'output', | ||||
|                     format: 'enum', | ||||
|                     key: "state", | ||||
|                     source: "output", | ||||
|                     name: "State", | ||||
|                     format: "enum", | ||||
|                     enumerations: enumerations, | ||||
|                     hints: { | ||||
|                         range: 1 | ||||
|                     } | ||||
|                 }, | ||||
|                 { | ||||
|                     key: "output", | ||||
|                     name: "Value", | ||||
|                     format: "string", | ||||
|                     hints: { | ||||
|                         range: 2 | ||||
|                     } | ||||
|                 } | ||||
|             ]) | ||||
|         }; | ||||
|   | ||||
| @@ -45,7 +45,7 @@ export default class ConditionSetTelemetryProvider { | ||||
|  | ||||
|         return conditionManager.requestLADConditionSetOutput() | ||||
|             .then(latestOutput => { | ||||
|                 return latestOutput ? [latestOutput] : []; | ||||
|                 return latestOutput; | ||||
|             }); | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -22,6 +22,7 @@ | ||||
|  | ||||
| import TelemetryCriterion from './TelemetryCriterion'; | ||||
| import { evaluateResults } from "../utils/evaluator"; | ||||
| import { getLatestTimestamp } from '../utils/time'; | ||||
|  | ||||
| export default class AllTelemetryCriterion extends TelemetryCriterion { | ||||
|  | ||||
| @@ -107,40 +108,53 @@ export default class AllTelemetryCriterion extends TelemetryCriterion { | ||||
|         this.result = evaluateResults(Object.values(this.telemetryDataCache), this.telemetry); | ||||
|     } | ||||
|  | ||||
|     requestLAD(options) { | ||||
|         options = Object.assign({}, | ||||
|             options, | ||||
|             { | ||||
|                 strategy: 'latest', | ||||
|                 size: 1 | ||||
|             } | ||||
|         ); | ||||
|     requestLAD(telemetryObjects) { | ||||
|         const options = { | ||||
|             strategy: 'latest', | ||||
|             size: 1 | ||||
|         }; | ||||
|  | ||||
|         if (!this.isValid()) { | ||||
|             return this.formatData({}, options.telemetryObjects); | ||||
|             return this.formatData({}, telemetryObjects); | ||||
|         } | ||||
|  | ||||
|         let keys = Object.keys(Object.assign({}, options.telemetryObjects)); | ||||
|         let keys = Object.keys(Object.assign({}, telemetryObjects)); | ||||
|         const telemetryRequests = keys | ||||
|             .map(key => this.openmct.telemetry.request( | ||||
|                 options.telemetryObjects[key], | ||||
|                 telemetryObjects[key], | ||||
|                 options | ||||
|             )); | ||||
|  | ||||
|         let telemetryDataCache = {}; | ||||
|         return Promise.all(telemetryRequests) | ||||
|             .then(telemetryRequestsResults => { | ||||
|                 let latestDatum; | ||||
|                 let latestTimestamp; | ||||
|                 const timeSystems = this.openmct.time.getAllTimeSystems(); | ||||
|                 const timeSystem = this.openmct.time.timeSystem(); | ||||
|  | ||||
|                 telemetryRequestsResults.forEach((results, index) => { | ||||
|                     latestDatum = results.length ? results[results.length - 1] : {}; | ||||
|                     if (index < telemetryRequestsResults.length-1) { | ||||
|                         if (latestDatum) { | ||||
|                             this.telemetryDataCache[latestDatum.id] = this.computeResult(latestDatum); | ||||
|                         } | ||||
|                     } | ||||
|                     const latestDatum = results.length ? results[results.length - 1] : {}; | ||||
|                     const datumId = keys[index]; | ||||
|                     const normalizedDatum = this.createNormalizedDatum(latestDatum, telemetryObjects[datumId]); | ||||
|  | ||||
|                     telemetryDataCache[datumId] = this.computeResult(normalizedDatum); | ||||
|  | ||||
|                     latestTimestamp = getLatestTimestamp( | ||||
|                         latestTimestamp, | ||||
|                         normalizedDatum, | ||||
|                         timeSystems, | ||||
|                         timeSystem | ||||
|                     ); | ||||
|                 }); | ||||
|  | ||||
|                 const datum = { | ||||
|                     result: evaluateResults(Object.values(telemetryDataCache), this.telemetry), | ||||
|                     ...latestTimestamp | ||||
|                 }; | ||||
|  | ||||
|                 return { | ||||
|                     id: this.id, | ||||
|                     data: this.formatData(latestDatum, options.telemetryObjects) | ||||
|                     data: datum | ||||
|                 }; | ||||
|             }); | ||||
|     } | ||||
|   | ||||
| @@ -61,6 +61,21 @@ export default class TelemetryCriterion extends EventEmitter { | ||||
|         this.telemetryObject = telemetryObjects[this.telemetryObjectIdAsString]; | ||||
|     } | ||||
|  | ||||
|     createNormalizedDatum(telemetryDatum, endpoint) { | ||||
|         const id = this.openmct.objects.makeKeyString(endpoint.identifier); | ||||
|         const metadata = this.openmct.telemetry.getMetadata(endpoint).valueMetadatas; | ||||
|  | ||||
|         const normalizedDatum = Object.values(metadata).reduce((datum, metadatum) => { | ||||
|             const formatter = this.openmct.telemetry.getValueFormatter(metadatum); | ||||
|             datum[metadatum.key] = formatter.parse(telemetryDatum[metadatum.source]); | ||||
|             return datum; | ||||
|         }, {}); | ||||
|  | ||||
|         normalizedDatum.id = id; | ||||
|  | ||||
|         return normalizedDatum; | ||||
|     } | ||||
|  | ||||
|     formatData(data) { | ||||
|         const datum = { | ||||
|             result: this.computeResult(data) | ||||
| @@ -79,14 +94,11 @@ export default class TelemetryCriterion extends EventEmitter { | ||||
|         this.result = this.computeResult(validatedData); | ||||
|     } | ||||
|  | ||||
|     requestLAD(options) { | ||||
|         options = Object.assign({}, | ||||
|             options, | ||||
|             { | ||||
|                 strategy: 'latest', | ||||
|                 size: 1 | ||||
|             } | ||||
|         ); | ||||
|     requestLAD() { | ||||
|         const options = { | ||||
|             strategy: 'latest', | ||||
|             size: 1 | ||||
|         }; | ||||
|  | ||||
|         if (!this.isValid()) { | ||||
|             return { | ||||
| @@ -100,9 +112,11 @@ export default class TelemetryCriterion extends EventEmitter { | ||||
|             options | ||||
|         ).then(results => { | ||||
|             const latestDatum = results.length ? results[results.length - 1] : {}; | ||||
|             const normalizedDatum = this.createNormalizedDatum(latestDatum, this.telemetryObject); | ||||
|  | ||||
|             return { | ||||
|                 id: this.id, | ||||
|                 data: this.formatData(latestDatum) | ||||
|                 data: this.formatData(normalizedDatum) | ||||
|             }; | ||||
|         }); | ||||
|     } | ||||
|   | ||||
| @@ -41,7 +41,7 @@ define([], function () { | ||||
|         this.timeFormat = 'local-format'; | ||||
|         this.durationFormat = 'duration'; | ||||
|  | ||||
|         this.isUTCBased = false; | ||||
|         this.isUTCBased = true; | ||||
|     } | ||||
|  | ||||
|     return LocalTimeSystem; | ||||
|   | ||||
| @@ -322,7 +322,15 @@ define([ | ||||
|          *                  a point to the end without dupe checking. | ||||
|          */ | ||||
|         add: function (point, appendOnly) { | ||||
|             var insertIndex = this.data.length; | ||||
|             var insertIndex = this.data.length, | ||||
|                 currentYVal = this.getYVal(point), | ||||
|                 lastYVal = this.getYVal(this.data[insertIndex - 1]); | ||||
|  | ||||
|             if (this.isValueInvalid(currentYVal) && this.isValueInvalid(lastYVal)) { | ||||
|                 console.warn('[Plot] Invalid Y Values detected'); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             if (!appendOnly) { | ||||
|                 insertIndex = this.sortedIndex(point); | ||||
|                 if (this.getXVal(this.data[insertIndex]) === this.getXVal(point)) { | ||||
| @@ -332,11 +340,21 @@ define([ | ||||
|                     return; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             this.updateStats(point); | ||||
|             point.mctLimitState = this.evaluate(point); | ||||
|             this.data.splice(insertIndex, 0, point); | ||||
|             this.emit('add', point, insertIndex, this); | ||||
|         }, | ||||
|  | ||||
|         /** | ||||
|          * | ||||
|          * @private | ||||
|          */ | ||||
|         isValueInvalid: function (val) { | ||||
|             return Number.isNaN(val) || val === undefined; | ||||
|         }, | ||||
|  | ||||
|         /** | ||||
|          * Remove a point from the data array and notify listeners. | ||||
|          * @private | ||||
|   | ||||
| @@ -182,21 +182,6 @@ define([ | ||||
|             this.set('format', yFormat.format.bind(yFormat)); | ||||
|             this.set('values', yMetadata.values); | ||||
|             if (!label) { | ||||
|                 var labelUnits = series.map(function (s) { | ||||
|                     return s.metadata.value(s.get('yKey')).units; | ||||
|                 }).reduce(function (a, b) { | ||||
|                     if (a === undefined) { | ||||
|                         return b; | ||||
|                     } | ||||
|                     if (a === b) { | ||||
|                         return a; | ||||
|                     } | ||||
|                     return ''; | ||||
|                 }, undefined); | ||||
|                 if (labelUnits) { | ||||
|                     this.set('label', labelUnits); | ||||
|                     return; | ||||
|                 } | ||||
|                 var labelName = series.map(function (s) { | ||||
|                     return s.metadata.value(s.get('yKey')).name; | ||||
|                 }).reduce(function (a, b) { | ||||
| @@ -208,7 +193,28 @@ define([ | ||||
|                     } | ||||
|                     return ''; | ||||
|                 }, undefined); | ||||
|                 this.set('label', labelName); | ||||
|  | ||||
|                 if (labelName) { | ||||
|                     this.set('label', labelName); | ||||
|                     return; | ||||
|                 } | ||||
|  | ||||
|                 var labelUnits = series.map(function (s) { | ||||
|                     return s.metadata.value(s.get('yKey')).units; | ||||
|                 }).reduce(function (a, b) { | ||||
|                     if (a === undefined) { | ||||
|                         return b; | ||||
|                     } | ||||
|                     if (a === b) { | ||||
|                         return a; | ||||
|                     } | ||||
|                     return ''; | ||||
|                 }, undefined); | ||||
|  | ||||
|                 if (labelUnits) { | ||||
|                     this.set('label', labelUnits); | ||||
|                     return; | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         defaults: function (options) { | ||||
|   | ||||
| @@ -65,10 +65,10 @@ define([ | ||||
|         } | ||||
|         this.$canvas = this.$element.find('canvas'); | ||||
|  | ||||
|         this.listenTo(this.$canvas, 'click', this.onMouseClick, this); | ||||
|         this.listenTo(this.$canvas, 'mousemove', this.trackMousePosition, this); | ||||
|         this.listenTo(this.$canvas, 'mouseleave', this.untrackMousePosition, this); | ||||
|         this.listenTo(this.$canvas, 'mousedown', this.onMouseDown, this); | ||||
|         this.listenTo(this.$canvas, 'wheel', this.wheelZoom, this); | ||||
|  | ||||
|         this.watchForMarquee(); | ||||
|     }; | ||||
| @@ -76,7 +76,6 @@ define([ | ||||
|     MCTPlotController.prototype.initialize = function () { | ||||
|         this.$canvas = this.$element.find('canvas'); | ||||
|  | ||||
|         this.listenTo(this.$canvas, 'click', this.onMouseClick, this); | ||||
|         this.listenTo(this.$canvas, 'mousemove', this.trackMousePosition, this); | ||||
|         this.listenTo(this.$canvas, 'mouseleave', this.untrackMousePosition, this); | ||||
|         this.listenTo(this.$canvas, 'mousedown', this.onMouseDown, this); | ||||
| @@ -208,23 +207,6 @@ define([ | ||||
|         this.highlightValues(point); | ||||
|     }; | ||||
|  | ||||
|     MCTPlotController.prototype.onMouseClick = function ($event) { | ||||
|         const isClick = this.isMouseClick(); | ||||
|         if (this.pan) { | ||||
|             this.endPan($event); | ||||
|         } | ||||
|         if (this.marquee) { | ||||
|             this.endMarquee($event); | ||||
|         } | ||||
|         this.$scope.$apply(); | ||||
|  | ||||
|         if (!this.$scope.highlights.length || !isClick) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         this.$scope.lockHighlightPoint = !this.$scope.lockHighlightPoint; | ||||
|     }; | ||||
|  | ||||
|     MCTPlotController.prototype.highlightValues = function (point) { | ||||
|         this.highlightPoint = point; | ||||
|         this.$scope.$emit('plot:highlight:update', point); | ||||
| @@ -272,11 +254,23 @@ define([ | ||||
|     MCTPlotController.prototype.onMouseUp = function ($event) { | ||||
|         this.stopListening(this.$window, 'mouseup', this.onMouseUp, this); | ||||
|         this.stopListening(this.$window, 'mousemove', this.trackMousePosition, this); | ||||
|  | ||||
|         if (this.isMouseClick()) { | ||||
|             this.$scope.lockHighlightPoint = !this.$scope.lockHighlightPoint; | ||||
|         } | ||||
|  | ||||
|         if (this.allowPan) { | ||||
|             return this.endPan($event); | ||||
|         } | ||||
|  | ||||
|         if (this.allowMarquee) { | ||||
|             return this.endMarquee($event); | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     MCTPlotController.prototype.isMouseClick = function () { | ||||
|         if (!this.marquee) { | ||||
|             return; | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         const { start, end } = this.marquee; | ||||
| @@ -329,7 +323,7 @@ define([ | ||||
|         } else { | ||||
|             // A history entry is created by startMarquee, need to remove | ||||
|             // if marquee zoom doesn't occur. | ||||
|             this.back(); | ||||
|             this.plotHistory.pop(); | ||||
|         } | ||||
|         this.$scope.rectangles = []; | ||||
|         this.marquee = undefined; | ||||
|   | ||||
| @@ -27,7 +27,7 @@ | ||||
|                 {'is-current': isCurrent(tab)}, | ||||
|                 tab.type.definition.cssClass | ||||
|             ]" | ||||
|             @click="showTab(tab)" | ||||
|             @click="showTab(tab, index)" | ||||
|         > | ||||
|             <span class="c-button__label">{{ tab.domainObject.name }}</span> | ||||
|         </button> | ||||
| @@ -48,6 +48,7 @@ | ||||
|             </div> | ||||
|         </div> | ||||
|         <object-view | ||||
|             v-if="internalDomainObject.keep_alive ? currentTab : isCurrent(tab)" | ||||
|             class="c-tabs-view__object" | ||||
|             :object="tab.domainObject" | ||||
|         /> | ||||
| @@ -57,7 +58,6 @@ | ||||
|  | ||||
| <script> | ||||
| import ObjectView from '../../../ui/components/ObjectView.vue'; | ||||
| import _ from 'lodash'; | ||||
|  | ||||
| var unknownObjectType = { | ||||
|     definition: { | ||||
| @@ -73,6 +73,7 @@ export default { | ||||
|     }, | ||||
|     data: function () { | ||||
|         return { | ||||
|             internalDomainObject: this.domainObject, | ||||
|             currentTab: {}, | ||||
|             tabsList: [], | ||||
|             setCurrentTab: true, | ||||
| @@ -85,9 +86,17 @@ export default { | ||||
|             this.composition.on('add', this.addItem); | ||||
|             this.composition.on('remove', this.removeItem); | ||||
|             this.composition.on('reorder', this.onReorder); | ||||
|             this.composition.load(); | ||||
|             this.composition.load().then(() => { | ||||
|                 let currentTabIndex = this.domainObject.currentTabIndex; | ||||
|  | ||||
|                 if (currentTabIndex !== undefined && this.tabsList.length > currentTabIndex) { | ||||
|                     this.currentTab = this.tabsList[currentTabIndex]; | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         this.unsubscribe = this.openmct.objects.observe(this.internalDomainObject, '*', this.updateInternalDomainObject); | ||||
|  | ||||
|         document.addEventListener('dragstart', this.dragstart); | ||||
|         document.addEventListener('dragend', this.dragend); | ||||
|     }, | ||||
| @@ -96,18 +105,25 @@ export default { | ||||
|         this.composition.off('remove', this.removeItem); | ||||
|         this.composition.off('reorder', this.onReorder); | ||||
|  | ||||
|         this.unsubscribe(); | ||||
|  | ||||
|         document.removeEventListener('dragstart', this.dragstart); | ||||
|         document.removeEventListener('dragend', this.dragend); | ||||
|     }, | ||||
|     methods:{ | ||||
|         showTab(tab) { | ||||
|         showTab(tab, index) { | ||||
|             if (index !== undefined) { | ||||
|                 this.storeCurrentTabIndex(index); | ||||
|             } | ||||
|  | ||||
|             this.currentTab = tab; | ||||
|         }, | ||||
|         addItem(domainObject) { | ||||
|             let type = this.openmct.types.get(domainObject.type) || unknownObjectType, | ||||
|                 tabItem = { | ||||
|                     domainObject, | ||||
|                     type: type | ||||
|                     type: type, | ||||
|                     key: this.openmct.objects.makeKeyString(domainObject.identifier) | ||||
|                 }; | ||||
|  | ||||
|             this.tabsList.push(tabItem); | ||||
| @@ -126,7 +142,7 @@ export default { | ||||
|             this.tabsList.splice(pos, 1); | ||||
|  | ||||
|             if (this.isCurrent(tabToBeRemoved)) { | ||||
|                 this.showTab(this.tabsList[this.tabsList.length - 1]); | ||||
|                 this.showTab(this.tabsList[this.tabsList.length - 1], this.tabsList.length - 1); | ||||
|             } | ||||
|         }, | ||||
|         onReorder(reorderPlan) { | ||||
| @@ -138,6 +154,7 @@ export default { | ||||
|         }, | ||||
|         onDrop(e) { | ||||
|             this.setCurrentTab = true; | ||||
|             this.storeCurrentTabIndex(this.tabsList.length); | ||||
|         }, | ||||
|         dragstart(e) { | ||||
|             if (e.dataTransfer.types.includes('openmct/domain-object-path')) { | ||||
| @@ -155,7 +172,13 @@ export default { | ||||
|             this.allowDrop = false; | ||||
|         }, | ||||
|         isCurrent(tab) { | ||||
|             return _.isEqual(this.currentTab, tab) | ||||
|             return this.currentTab.key === tab.key; | ||||
|         }, | ||||
|         updateInternalDomainObject(domainObject) { | ||||
|             this.internalDomainObject = domainObject; | ||||
|         }, | ||||
|         storeCurrentTabIndex(index) { | ||||
|             this.openmct.objects.mutate(this.internalDomainObject, 'currentTabIndex', index); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -36,7 +36,27 @@ define([ | ||||
|                 cssClass: 'icon-tabs-view', | ||||
|                 initialize(domainObject) { | ||||
|                     domainObject.composition = []; | ||||
|                 } | ||||
|                     domainObject.keep_alive = true; | ||||
|                 }, | ||||
|                 form: [ | ||||
|                     { | ||||
|                         "key": "keep_alive", | ||||
|                         "name": "Keep Tabs Alive", | ||||
|                         "control": "select", | ||||
|                         "options": [ | ||||
|                             { | ||||
|                                 'name': 'True', | ||||
|                                 'value': true | ||||
|                             }, | ||||
|                             { | ||||
|                                 'name': 'False', | ||||
|                                 'value': false | ||||
|                             } | ||||
|                         ], | ||||
|                         "required": true, | ||||
|                         "cssClass": "l-input" | ||||
|                     } | ||||
|                 ] | ||||
|             }); | ||||
|         }; | ||||
|     }; | ||||
|   | ||||
| @@ -46,11 +46,23 @@ define( | ||||
|                 filter = filter.trim().toLowerCase(); | ||||
|  | ||||
|                 let rowsToFilter = this.getRowsToFilter(columnKey, filter); | ||||
|  | ||||
|                 if (filter.length === 0) { | ||||
|                     delete this.columnFilters[columnKey]; | ||||
|                 } else { | ||||
|                     this.columnFilters[columnKey] = filter; | ||||
|                 } | ||||
|  | ||||
|                 this.rows = rowsToFilter.filter(this.matchesFilters, this); | ||||
|                 this.emit('filter'); | ||||
|             } | ||||
|  | ||||
|             setColumnRegexFilter(columnKey, filter) { | ||||
|                 filter = filter.trim(); | ||||
|  | ||||
|                 let rowsToFilter = this.masterCollection.getRows(); | ||||
|  | ||||
|                 this.columnFilters[columnKey] = new RegExp(filter); | ||||
|                 this.rows = rowsToFilter.filter(this.matchesFilters, this); | ||||
|                 this.emit('filter'); | ||||
|             } | ||||
| @@ -70,6 +82,10 @@ define( | ||||
|              * @private | ||||
|              */ | ||||
|             isSubsetOfCurrentFilter(columnKey, filter) { | ||||
|                 if (this.columnFilters[columnKey] instanceof RegExp) { | ||||
|                     return false; | ||||
|                 } | ||||
|  | ||||
|                 return this.columnFilters[columnKey] && | ||||
|                     filter.startsWith(this.columnFilters[columnKey]) && | ||||
|                     // startsWith check will otherwise fail when filter cleared | ||||
| @@ -96,7 +112,11 @@ define( | ||||
|                         return false; | ||||
|                     } | ||||
|  | ||||
|                     doesMatchFilters = formattedValue.toLowerCase().indexOf(this.columnFilters[key]) !== -1; | ||||
|                     if (this.columnFilters[key] instanceof RegExp) { | ||||
|                         doesMatchFilters = this.columnFilters[key].test(formattedValue); | ||||
|                     } else { | ||||
|                         doesMatchFilters = formattedValue.toLowerCase().indexOf(this.columnFilters[key]) !== -1; | ||||
|                     } | ||||
|                 }); | ||||
|                 return doesMatchFilters; | ||||
|             } | ||||
|   | ||||
| @@ -186,7 +186,17 @@ | ||||
|                                 class="c-table__search" | ||||
|                                 @input="filterChanged(key)" | ||||
|                                 @clear="clearFilter(key)" | ||||
|                             /> | ||||
|                             > | ||||
|  | ||||
|                                 <button | ||||
|                                     class="c-search__use-regex" | ||||
|                                     :class="{ 'is-active': enableRegexSearch[key] }" | ||||
|                                     title="Click to enable regex: enter a string with slashes, like this: /regex_exp/" | ||||
|                                     @click="toggleRegex(key)" | ||||
|                                 > | ||||
|                                     /R/ | ||||
|                                 </button> | ||||
|                             </search> | ||||
|                         </table-column-header> | ||||
|                     </tr> | ||||
|                 </thead> | ||||
| @@ -336,7 +346,8 @@ export default { | ||||
|             markCounter: 0, | ||||
|             paused: false, | ||||
|             markedRows: [], | ||||
|             isShowingMarkedRowsOnly: false | ||||
|             isShowingMarkedRowsOnly: false, | ||||
|             enableRegexSearch: {} | ||||
|         } | ||||
|     }, | ||||
|     computed: { | ||||
| @@ -543,7 +554,16 @@ export default { | ||||
|             this.headersHolderEl.scrollLeft = this.scrollable.scrollLeft; | ||||
|         }, | ||||
|         filterChanged(columnKey) { | ||||
|             this.table.filteredRows.setColumnFilter(columnKey, this.filters[columnKey]); | ||||
|             if (this.enableRegexSearch[columnKey]) { | ||||
|                 if (this.isCompleteRegex(this.filters[columnKey])) { | ||||
|                     this.table.filteredRows.setColumnRegexFilter(columnKey, this.filters[columnKey].slice(1,-1)); | ||||
|                 } else { | ||||
|                     return; | ||||
|                 } | ||||
|             } else { | ||||
|                 this.table.filteredRows.setColumnFilter(columnKey, this.filters[columnKey]); | ||||
|             } | ||||
|  | ||||
|             this.setHeight(); | ||||
|         }, | ||||
|         clearFilter(columnKey) { | ||||
| @@ -869,6 +889,18 @@ export default { | ||||
|             this.isAutosizeEnabled = true; | ||||
|  | ||||
|             this.$nextTick().then(this.calculateColumnWidths); | ||||
|         }, | ||||
|         toggleRegex(key) { | ||||
|             this.$set(this.filters, key, ''); | ||||
|  | ||||
|             if (this.enableRegexSearch[key] === undefined) { | ||||
|                 this.$set(this.enableRegexSearch, key, true) | ||||
|             } else { | ||||
|                 this.$set(this.enableRegexSearch, key, !this.enableRegexSearch[key]); | ||||
|             } | ||||
|         }, | ||||
|         isCompleteRegex(string) { | ||||
|             return (string.length > 2 && string[0] === '/' && string[string.length - 1] === '/') | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -22,7 +22,12 @@ | ||||
| <template> | ||||
| <div | ||||
|     class="c-conductor" | ||||
|     :class="[isFixed ? 'is-fixed-mode' : 'is-realtime-mode']" | ||||
|     :class="[ | ||||
|         { 'is-zooming': isZooming }, | ||||
|         { 'is-panning': isPanning }, | ||||
|         { 'alt-pressed': altPressed }, | ||||
|         isFixed ? 'is-fixed-mode' : 'is-realtime-mode' | ||||
|     ]" | ||||
| > | ||||
|     <form | ||||
|         ref="conductorForm" | ||||
| @@ -52,7 +57,7 @@ | ||||
|                     type="text" | ||||
|                     autocorrect="off" | ||||
|                     spellcheck="false" | ||||
|                     @change="validateAllBounds(); submitForm()" | ||||
|                     @change="validateAllBounds('startDate'); submitForm()" | ||||
|                 > | ||||
|                 <date-picker | ||||
|                     v-if="isFixed && isUTCBased" | ||||
| @@ -92,7 +97,7 @@ | ||||
|                     autocorrect="off" | ||||
|                     spellcheck="false" | ||||
|                     :disabled="!isFixed" | ||||
|                     @change="validateAllBounds(); submitForm()" | ||||
|                     @change="validateAllBounds('endDate'); submitForm()" | ||||
|                 > | ||||
|                 <date-picker | ||||
|                     v-if="isFixed && isUTCBased" | ||||
| @@ -122,14 +127,25 @@ | ||||
|  | ||||
|             <conductor-axis | ||||
|                 class="c-conductor__ticks" | ||||
|                 :bounds="rawBounds" | ||||
|                 @panAxis="setViewFromBounds" | ||||
|                 :view-bounds="viewBounds" | ||||
|                 :is-fixed="isFixed" | ||||
|                 :alt-pressed="altPressed" | ||||
|                 @endPan="endPan" | ||||
|                 @endZoom="endZoom" | ||||
|                 @panAxis="pan" | ||||
|                 @zoomAxis="zoom" | ||||
|             /> | ||||
|  | ||||
|         </div> | ||||
|         <div class="c-conductor__controls"> | ||||
|             <!-- Mode, time system menu buttons and duration slider --> | ||||
|             <ConductorMode class="c-conductor__mode-select" /> | ||||
|             <ConductorTimeSystem class="c-conductor__time-system-select" /> | ||||
|             <ConductorHistory | ||||
|                 v-if="isFixed" | ||||
|                 class="c-conductor__history-select" | ||||
|                 :bounds="openmct.time.bounds()" | ||||
|                 :time-system="timeSystem" | ||||
|             /> | ||||
|         </div> | ||||
|         <input | ||||
|             type="submit" | ||||
| @@ -145,6 +161,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'; | ||||
|  | ||||
| @@ -155,7 +172,8 @@ export default { | ||||
|         ConductorTimeSystem, | ||||
|         DatePicker, | ||||
|         ConductorAxis, | ||||
|         ConductorModeIcon | ||||
|         ConductorModeIcon, | ||||
|         ConductorHistory | ||||
|     }, | ||||
|     data() { | ||||
|         let bounds = this.openmct.time.bounds(); | ||||
| @@ -165,6 +183,7 @@ export default { | ||||
|         let durationFormatter = this.getFormatter(timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER); | ||||
|  | ||||
|         return { | ||||
|             timeSystem: timeSystem, | ||||
|             timeFormatter: timeFormatter, | ||||
|             durationFormatter: durationFormatter, | ||||
|             offsets: { | ||||
| @@ -175,29 +194,68 @@ export default { | ||||
|                 start: timeFormatter.format(bounds.start), | ||||
|                 end: timeFormatter.format(bounds.end) | ||||
|             }, | ||||
|             rawBounds: { | ||||
|             viewBounds: { | ||||
|                 start: bounds.start, | ||||
|                 end: bounds.end | ||||
|             }, | ||||
|             isFixed: this.openmct.time.clock() === undefined, | ||||
|             isUTCBased: timeSystem.isUTCBased, | ||||
|             showDatePicker: false | ||||
|             showDatePicker: false, | ||||
|             altPressed: false, | ||||
|             isPanning: false, | ||||
|             isZooming: false | ||||
|         } | ||||
|     }, | ||||
|     mounted() { | ||||
|         document.addEventListener('keydown', this.handleKeyDown); | ||||
|         document.addEventListener('keyup', this.handleKeyUp); | ||||
|         this.setTimeSystem(JSON.parse(JSON.stringify(this.openmct.time.timeSystem()))); | ||||
|  | ||||
|         this.openmct.time.on('bounds', this.setViewFromBounds); | ||||
|         this.openmct.time.on('timeSystem', this.setTimeSystem); | ||||
|         this.openmct.time.on('clock', this.setViewFromClock); | ||||
|         this.openmct.time.on('clockOffsets', this.setViewFromOffsets) | ||||
|     }, | ||||
|     beforeDestroy() { | ||||
|         document.removeEventListener('keydown', this.handleKeyDown); | ||||
|         document.removeEventListener('keyup', this.handleKeyUp); | ||||
|     }, | ||||
|     methods: { | ||||
|         handleKeyDown(event) { | ||||
|             if (event.key === 'Alt') { | ||||
|                 this.altPressed = true; | ||||
|             } | ||||
|         }, | ||||
|         handleKeyUp(event) { | ||||
|             if (event.key === 'Alt') { | ||||
|                 this.altPressed = false; | ||||
|             } | ||||
|         }, | ||||
|         pan(bounds) { | ||||
|             this.isPanning = true; | ||||
|             this.setViewFromBounds(bounds); | ||||
|         }, | ||||
|         endPan(bounds) { | ||||
|             this.isPanning = false; | ||||
|             if (bounds) { | ||||
|                 this.openmct.time.bounds(bounds); | ||||
|             } | ||||
|         }, | ||||
|         zoom(bounds) { | ||||
|             this.isZooming = true; | ||||
|             this.formattedBounds.start = this.timeFormatter.format(bounds.start); | ||||
|             this.formattedBounds.end = this.timeFormatter.format(bounds.end); | ||||
|         }, | ||||
|         endZoom(bounds) { | ||||
|             const _bounds = bounds ? bounds : this.openmct.time.bounds(); | ||||
|             this.isZooming = false; | ||||
|  | ||||
|             this.openmct.time.bounds(_bounds); | ||||
|         }, | ||||
|         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) { | ||||
| @@ -237,8 +295,8 @@ export default { | ||||
|         setViewFromBounds(bounds) { | ||||
|             this.formattedBounds.start = this.timeFormatter.format(bounds.start); | ||||
|             this.formattedBounds.end = this.timeFormatter.format(bounds.end); | ||||
|             this.rawBounds.start = bounds.start; | ||||
|             this.rawBounds.end = bounds.end; | ||||
|             this.viewBounds.start = bounds.start; | ||||
|             this.viewBounds.end = bounds.end; | ||||
|         }, | ||||
|         setViewFromOffsets(offsets) { | ||||
|             this.offsets.start = this.durationFormatter.format(Math.abs(offsets.start)); | ||||
| @@ -251,6 +309,15 @@ export default { | ||||
|                 this.setOffsetsFromView(); | ||||
|             } | ||||
|         }, | ||||
|         getBoundsLimit() { | ||||
|             const configuration = this.configuration.menuOptions | ||||
|                 .filter(option => option.timeSystem ===  this.timeSystem.key) | ||||
|                 .find(option => option.limit); | ||||
|  | ||||
|             const limit = configuration ? configuration.limit : undefined; | ||||
|  | ||||
|             return limit; | ||||
|         }, | ||||
|         clearAllValidation() { | ||||
|             if (this.isFixed) { | ||||
|                 [this.$refs.startDate, this.$refs.endDate].forEach(this.clearValidationForInput); | ||||
| @@ -262,36 +329,52 @@ export default { | ||||
|             input.setCustomValidity(''); | ||||
|             input.title = ''; | ||||
|         }, | ||||
|         validateAllBounds() { | ||||
|             return [this.$refs.startDate, this.$refs.endDate].every((input) => { | ||||
|                 let validationResult = true; | ||||
|                 let formattedDate; | ||||
|         validateAllBounds(ref) { | ||||
|             if (!this.areBoundsFormatsValid()) { | ||||
|                 return false; | ||||
|             } | ||||
|  | ||||
|                 if (input === this.$refs.startDate) { | ||||
|                     formattedDate = this.formattedBounds.start; | ||||
|             let validationResult = true; | ||||
|             const currentInput = this.$refs[ref]; | ||||
|  | ||||
|             return [this.$refs.startDate, this.$refs.endDate].every((input) => { | ||||
|                 let boundsValues = { | ||||
|                     start: this.timeFormatter.parse(this.formattedBounds.start), | ||||
|                     end: this.timeFormatter.parse(this.formattedBounds.end) | ||||
|                 }; | ||||
|                 const limit = this.getBoundsLimit(); | ||||
|  | ||||
|                 if ( | ||||
|                     this.timeSystem.isUTCBased | ||||
|                     && limit | ||||
|                     && boundsValues.end - boundsValues.start > limit | ||||
|                 ) { | ||||
|                     if (input === currentInput) { | ||||
|                         validationResult = "Start and end difference exceeds allowable limit"; | ||||
|                     } | ||||
|                 } else { | ||||
|                     formattedDate = this.formattedBounds.end; | ||||
|                     if (input === currentInput) { | ||||
|                         validationResult = this.openmct.time.validateBounds(boundsValues); | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 return this.handleValidationResults(input, validationResult); | ||||
|             }); | ||||
|         }, | ||||
|         areBoundsFormatsValid() { | ||||
|             let validationResult = true; | ||||
|  | ||||
|             return [this.$refs.startDate, this.$refs.endDate].every((input) => { | ||||
|                 const formattedDate = input === this.$refs.startDate | ||||
|                     ? this.formattedBounds.start | ||||
|                     : this.formattedBounds.end | ||||
|                 ; | ||||
|  | ||||
|                 if (!this.timeFormatter.validate(formattedDate)) { | ||||
|                     validationResult = 'Invalid date'; | ||||
|                 } else { | ||||
|                     let boundsValues = { | ||||
|                         start: this.timeFormatter.parse(this.formattedBounds.start), | ||||
|                         end: this.timeFormatter.parse(this.formattedBounds.end) | ||||
|                     }; | ||||
|                     validationResult = this.openmct.time.validateBounds(boundsValues); | ||||
|                 } | ||||
|  | ||||
|                 if (validationResult !== true) { | ||||
|                     input.setCustomValidity(validationResult); | ||||
|                     input.title = validationResult; | ||||
|                     return false; | ||||
|                 } else { | ||||
|                     input.setCustomValidity(''); | ||||
|                     input.title = ''; | ||||
|                     return true; | ||||
|                 } | ||||
|                 return this.handleValidationResults(input, validationResult); | ||||
|             }); | ||||
|         }, | ||||
|         validateAllOffsets(event) { | ||||
| @@ -315,17 +398,20 @@ export default { | ||||
|                     validationResult = this.openmct.time.validateOffsets(offsetValues); | ||||
|                 } | ||||
|  | ||||
|                 if (validationResult !== true) { | ||||
|                     input.setCustomValidity(validationResult); | ||||
|                     input.title = validationResult; | ||||
|                     return false; | ||||
|                 } else { | ||||
|                     input.setCustomValidity(''); | ||||
|                     input.title = ''; | ||||
|                     return true; | ||||
|                 } | ||||
|                 return this.handleValidationResults(input, validationResult); | ||||
|             }); | ||||
|         }, | ||||
|         handleValidationResults(input, validationResult) { | ||||
|             if (validationResult !== true) { | ||||
|                 input.setCustomValidity(validationResult); | ||||
|                 input.title = validationResult; | ||||
|                 return false; | ||||
|             } else { | ||||
|                 input.setCustomValidity(''); | ||||
|                 input.title = ''; | ||||
|                 return true; | ||||
|             } | ||||
|         }, | ||||
|         submitForm() { | ||||
|             // Allow Vue model to catch up to user input. | ||||
|             // Submitting form will cause validation messages to display (but only if triggered by button click) | ||||
| @@ -338,12 +424,12 @@ export default { | ||||
|         }, | ||||
|         startDateSelected(date) { | ||||
|             this.formattedBounds.start = this.timeFormatter.format(date); | ||||
|             this.validateAllBounds(); | ||||
|             this.validateAllBounds('startDate'); | ||||
|             this.submitForm(); | ||||
|         }, | ||||
|         endDateSelected(date) { | ||||
|             this.formattedBounds.end = this.timeFormatter.format(date); | ||||
|             this.validateAllBounds(); | ||||
|             this.validateAllBounds('endDate'); | ||||
|             this.submitForm(); | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -24,7 +24,12 @@ | ||||
|     ref="axisHolder" | ||||
|     class="c-conductor-axis" | ||||
|     @mousedown="dragStart($event)" | ||||
| ></div> | ||||
| > | ||||
|     <div | ||||
|         class="c-conductor-axis__zoom-indicator" | ||||
|         :style="zoomStyle" | ||||
|     ></div> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| @@ -43,52 +48,81 @@ const PIXELS_PER_TICK_WIDE = 200; | ||||
| export default { | ||||
|     inject: ['openmct'], | ||||
|     props: { | ||||
|         bounds: { | ||||
|         viewBounds: { | ||||
|             type: Object, | ||||
|             required: true | ||||
|         }, | ||||
|         isFixed: { | ||||
|             type: Boolean, | ||||
|             required: true | ||||
|         }, | ||||
|         altPressed: { | ||||
|             type: Boolean, | ||||
|             required: true | ||||
|         } | ||||
|     }, | ||||
|     data() { | ||||
|         return { | ||||
|             inPanMode: false, | ||||
|             dragStartX: undefined, | ||||
|             dragX: undefined, | ||||
|             zoomStyle: {} | ||||
|         } | ||||
|     }, | ||||
|     computed: { | ||||
|         inZoomMode() { | ||||
|             return !this.inPanMode; | ||||
|         } | ||||
|     }, | ||||
|     watch: { | ||||
|         bounds: { | ||||
|             handler(bounds) { | ||||
|         viewBounds: { | ||||
|             handler() { | ||||
|                 this.setScale(); | ||||
|             }, | ||||
|             deep: true | ||||
|         } | ||||
|     }, | ||||
|     mounted() { | ||||
|         let axisHolder = this.$refs.axisHolder; | ||||
|         let height = axisHolder.offsetHeight; | ||||
|         let vis = d3Selection.select(axisHolder) | ||||
|             .append("svg:svg") | ||||
|             .attr("width", "100%") | ||||
|             .attr("height", height); | ||||
|         let vis = d3Selection.select(this.$refs.axisHolder).append("svg:svg"); | ||||
|  | ||||
|         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.setAxisDimensions(); | ||||
|         this.setScale(); | ||||
|  | ||||
|         //Respond to changes in conductor | ||||
|         this.openmct.time.on("timeSystem", this.setViewFromTimeSystem); | ||||
|         setInterval(this.resize, RESIZE_POLL_INTERVAL); | ||||
|     }, | ||||
|     destroyed() { | ||||
|     }, | ||||
|     methods: { | ||||
|         setAxisDimensions() { | ||||
|             const axisHolder = this.$refs.axisHolder; | ||||
|             const rect = axisHolder.getBoundingClientRect(); | ||||
|  | ||||
|             this.left = Math.round(rect.left); | ||||
|             this.width = axisHolder.clientWidth; | ||||
|         }, | ||||
|         setScale() { | ||||
|             if (!this.width) { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             let timeSystem = this.openmct.time.timeSystem(); | ||||
|             let bounds = this.bounds; | ||||
|  | ||||
|             if (timeSystem.isUTCBased) { | ||||
|                 this.xScale.domain([new Date(bounds.start), new Date(bounds.end)]); | ||||
|                 this.xScale.domain( | ||||
|                     [new Date(this.viewBounds.start), new Date(this.viewBounds.end)] | ||||
|                 ); | ||||
|             } else { | ||||
|                 this.xScale.domain([bounds.start, bounds.end]); | ||||
|                 this.xScale.domain( | ||||
|                     [this.viewBounds.start, this.viewBounds.end] | ||||
|                 ); | ||||
|             } | ||||
|  | ||||
|             this.xAxis.scale(this.xScale); | ||||
| @@ -102,7 +136,7 @@ export default { | ||||
|                 this.xAxis.ticks(this.width / PIXELS_PER_TICK); | ||||
|             } | ||||
|  | ||||
|             this.msPerPixel = (bounds.end - bounds.start) / this.width; | ||||
|             this.msPerPixel = (this.viewBounds.end - this.viewBounds.start) / this.width; | ||||
|         }, | ||||
|         setViewFromTimeSystem(timeSystem) { | ||||
|             //The D3 scale used depends on the type of time system as d3 | ||||
| @@ -120,9 +154,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); | ||||
| @@ -134,45 +167,131 @@ export default { | ||||
|             }).formatter; | ||||
|         }, | ||||
|         dragStart($event) { | ||||
|             let isFixed = this.openmct.time.clock() === undefined; | ||||
|             if (isFixed) { | ||||
|             if (this.isFixed) { | ||||
|                 this.dragStartX = $event.clientX; | ||||
|  | ||||
|                 if (this.altPressed) { | ||||
|                     this.inPanMode = true; | ||||
|                 } | ||||
|  | ||||
|                 document.addEventListener('mousemove', this.drag); | ||||
|                 document.addEventListener('mouseup', this.dragEnd, { | ||||
|                     once: true | ||||
|                 }); | ||||
|  | ||||
|                 if (this.inZoomMode) { | ||||
|                     this.startZoom(); | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         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 | ||||
|                     }); | ||||
|  | ||||
|                 requestAnimationFrame(() => { | ||||
|                     this.dragX = $event.clientX; | ||||
|                     this.inPanMode ? this.pan() : this.zoom(); | ||||
|                     this.dragging = false; | ||||
|                 }) | ||||
|             } else { | ||||
|                 console.log('Rejected drag due to RAF cap'); | ||||
|                 }); | ||||
|             } | ||||
|         }, | ||||
|         dragEnd() { | ||||
|             this.inPanMode ? this.endPan() : this.endZoom(); | ||||
|  | ||||
|             document.removeEventListener('mousemove', this.drag); | ||||
|             this.openmct.time.bounds({ | ||||
|                 start: this.bounds.start, | ||||
|                 end: this.bounds.end | ||||
|             this.dragStartX = undefined; | ||||
|             this.dragX = undefined; | ||||
|         }, | ||||
|         pan() { | ||||
|             const panBounds = this.getPanBounds(); | ||||
|             this.$emit('panAxis', panBounds); | ||||
|         }, | ||||
|         endPan() { | ||||
|             const panBounds = this.dragStartX && this.dragX && this.dragStartX !== this.dragX | ||||
|                 ? this.getPanBounds() | ||||
|                 : undefined; | ||||
|             this.$emit('endPan', panBounds); | ||||
|             this.inPanMode = false; | ||||
|         }, | ||||
|         getPanBounds() { | ||||
|             const bounds = this.openmct.time.bounds(); | ||||
|             const deltaTime = bounds.end - bounds.start; | ||||
|             const deltaX = this.dragX - this.dragStartX; | ||||
|             const percX = deltaX / this.width; | ||||
|             const panStart = bounds.start - percX * deltaTime; | ||||
|  | ||||
|             return { | ||||
|                 start: panStart, | ||||
|                 end: panStart + deltaTime | ||||
|             }; | ||||
|         }, | ||||
|         startZoom() { | ||||
|             const x = this.scaleToBounds(this.dragStartX); | ||||
|  | ||||
|             this.zoomStyle = { | ||||
|                 left: `${this.dragStartX - this.left}px` | ||||
|             }; | ||||
|  | ||||
|             this.$emit('zoomAxis', { | ||||
|                 start: x, | ||||
|                 end: x | ||||
|             }); | ||||
|         }, | ||||
|         zoom() { | ||||
|             const zoomRange = this.getZoomRange(); | ||||
|  | ||||
|             this.zoomStyle = { | ||||
|                 left: `${zoomRange.start - this.left}px`, | ||||
|                 width: `${zoomRange.end - zoomRange.start}px` | ||||
|             }; | ||||
|  | ||||
|             this.$emit('zoomAxis', { | ||||
|                 start: this.scaleToBounds(zoomRange.start), | ||||
|                 end: this.scaleToBounds(zoomRange.end) | ||||
|             }); | ||||
|         }, | ||||
|         endZoom() { | ||||
|             const zoomRange = this.dragStartX && this.dragX && this.dragStartX !== this.dragX | ||||
|                 ? this.getZoomRange() | ||||
|                 : undefined; | ||||
|  | ||||
|             const zoomBounds = zoomRange | ||||
|                 ? { | ||||
|                     start: this.scaleToBounds(zoomRange.start), | ||||
|                     end: this.scaleToBounds(zoomRange.end) | ||||
|                 } | ||||
|                 : this.openmct.time.bounds(); | ||||
|  | ||||
|             this.zoomStyle = {}; | ||||
|             this.$emit('endZoom', zoomBounds); | ||||
|         }, | ||||
|         getZoomRange() { | ||||
|             const leftBound = this.left; | ||||
|             const rightBound = this.left + this.width; | ||||
|  | ||||
|             const zoomStart = this.dragX < leftBound | ||||
|                 ? leftBound | ||||
|                 : Math.min(this.dragX, this.dragStartX); | ||||
|  | ||||
|             const zoomEnd = this.dragX > rightBound | ||||
|                 ? rightBound | ||||
|                 : Math.max(this.dragX, this.dragStartX); | ||||
|  | ||||
|             return { | ||||
|                 start: zoomStart, | ||||
|                 end: zoomEnd | ||||
|             }; | ||||
|         }, | ||||
|         scaleToBounds(value) { | ||||
|             const bounds = this.openmct.time.bounds(); | ||||
|             const timeDelta = bounds.end - bounds.start; | ||||
|             const valueDelta = value - this.left; | ||||
|             const offset = valueDelta / this.width * timeDelta; | ||||
|             return bounds.start + offset; | ||||
|         }, | ||||
|         resize() { | ||||
|             if (this.$refs.axisHolder.clientWidth !== this.width) { | ||||
|                 this.width = this.$refs.axisHolder.clientWidth; | ||||
|                 this.setAxisDimensions(); | ||||
|                 this.setScale(); | ||||
|             } | ||||
|         } | ||||
|   | ||||
							
								
								
									
										200
									
								
								src/plugins/timeConductor/ConductorHistory.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										200
									
								
								src/plugins/timeConductor/ConductorHistory.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,200 @@ | ||||
| /***************************************************************************** | ||||
|  * 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 v-if="open" | ||||
|          class="c-menu c-conductor__history-menu" | ||||
|     > | ||||
|         <ul v-if="hasHistoryPresets"> | ||||
|             <li | ||||
|                 v-for="preset in presets" | ||||
|                 :key="preset.label" | ||||
|                 class="icon-clock" | ||||
|                 @click="selectPresetBounds(preset.bounds)" | ||||
|             > | ||||
|                 {{ preset.label }} | ||||
|             </li> | ||||
|         </ul> | ||||
|  | ||||
|         <div | ||||
|             v-if="hasHistoryPresets" | ||||
|             class="c-menu__section-separator" | ||||
|         ></div> | ||||
|  | ||||
|         <div class="c-menu__section-hint"> | ||||
|             Past timeframes, ordered by latest first | ||||
|         </div> | ||||
|  | ||||
|         <ul> | ||||
|             <li | ||||
|                 v-for="(timespan, index) in historyForCurrentTimeSystem" | ||||
|                 :key="index" | ||||
|                 class="icon-history" | ||||
|                 @click="selectTimespan(timespan)" | ||||
|             > | ||||
|                 {{ formatTime(timespan.start) }} - {{ formatTime(timespan.end) }} | ||||
|             </li> | ||||
|         </ul> | ||||
|     </div> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import toggleMixin from '../../ui/mixins/toggle-mixin'; | ||||
|  | ||||
| const LOCAL_STORAGE_HISTORY_KEY = 'tcHistory'; | ||||
| const DEFAULT_RECORDS = 10; | ||||
|  | ||||
| export default { | ||||
|     inject: ['openmct', 'configuration'], | ||||
|     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 | ||||
|             presets: [] | ||||
|         } | ||||
|     }, | ||||
|     computed: { | ||||
|         hasHistoryPresets() { | ||||
|             return this.timeSystem.isUTCBased && this.presets.length; | ||||
|         }, | ||||
|         historyForCurrentTimeSystem() { | ||||
|             const history = this.history[this.timeSystem.key]; | ||||
|  | ||||
|             return history; | ||||
|         } | ||||
|     }, | ||||
|     watch: { | ||||
|         bounds: { | ||||
|             handler() { | ||||
|                 this.addTimespan(); | ||||
|             }, | ||||
|             deep: true | ||||
|         }, | ||||
|         timeSystem: { | ||||
|             handler() { | ||||
|                 this.loadConfiguration(); | ||||
|                 this.addTimespan(); | ||||
|             }, | ||||
|             deep: true | ||||
|         }, | ||||
|         history: { | ||||
|             handler() { | ||||
|                 this.persistHistoryToLocalStorage(); | ||||
|             }, | ||||
|             deep: true | ||||
|         } | ||||
|     }, | ||||
|     mounted() { | ||||
|         this.getHistoryFromLocalStorage(); | ||||
|     }, | ||||
|     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 | ||||
|             }; | ||||
|  | ||||
|             const isNotEqual = function (entry) { | ||||
|                 const start = entry.start !== this.start; | ||||
|                 const end = entry.end !== this.end; | ||||
|  | ||||
|                 return start || end; | ||||
|             }; | ||||
|             currentHistory = currentHistory.filter(isNotEqual, timespan); | ||||
|  | ||||
|             while (currentHistory.length >= this.records) { | ||||
|                 currentHistory.pop(); | ||||
|             } | ||||
|  | ||||
|             currentHistory.unshift(timespan); | ||||
|             this.history[key] = currentHistory; | ||||
|         }, | ||||
|         selectTimespan(timespan) { | ||||
|             this.openmct.time.bounds(timespan); | ||||
|         }, | ||||
|         selectPresetBounds(bounds) { | ||||
|             const start = typeof bounds.start === 'function' ? bounds.start() : bounds.start; | ||||
|             const end = typeof bounds.end === 'function' ? bounds.end() : bounds.end; | ||||
|  | ||||
|             this.selectTimespan({ | ||||
|                 start: start, | ||||
|                 end: end | ||||
|             }); | ||||
|         }, | ||||
|         loadConfiguration() { | ||||
|             const configurations = this.configuration.menuOptions | ||||
|                 .filter(option => option.timeSystem ===  this.timeSystem.key); | ||||
|  | ||||
|             this.presets = this.loadPresets(configurations); | ||||
|             this.records = this.loadRecords(configurations); | ||||
|         }, | ||||
|         loadPresets(configurations) { | ||||
|             const configuration = configurations.find(option => option.presets); | ||||
|             const presets = configuration ? configuration.presets : []; | ||||
|  | ||||
|             return presets; | ||||
|         }, | ||||
|         loadRecords(configurations) { | ||||
|             const configuration = configurations.find(option => option.records); | ||||
|             const records = configuration ? configuration.records : DEFAULT_RECORDS; | ||||
|  | ||||
|             return records; | ||||
|         }, | ||||
|         formatTime(time) { | ||||
|             const formatter = this.openmct.telemetry.getValueFormatter({ | ||||
|                 format: this.timeSystem.timeFormat | ||||
|             }).formatter; | ||||
|  | ||||
|             return formatter.format(time); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| </script> | ||||
| @@ -110,7 +110,7 @@ export default { | ||||
|             if (clock === undefined) { | ||||
|                 return { | ||||
|                     key: 'fixed', | ||||
|                     name: 'Fixed Timespan Mode', | ||||
|                     name: 'Fixed Timespan', | ||||
|                     description: 'Query and explore data that falls between two fixed datetimes.', | ||||
|                     cssClass: 'icon-tabular' | ||||
|                 } | ||||
|   | ||||
| @@ -13,7 +13,7 @@ | ||||
|         text-rendering: geometricPrecision; | ||||
|         width: 100%; | ||||
|         height: 100%; | ||||
|         > g { | ||||
|         > g.axis { | ||||
|             // Overall Tick holder | ||||
|             transform: translateY($tickYPos); | ||||
|             path { | ||||
| @@ -44,7 +44,6 @@ | ||||
|     } | ||||
|  | ||||
|     body.desktop .is-fixed-mode & { | ||||
|         @include cursorGrab(); | ||||
|         background-size: 3px 30%; | ||||
|         background-color: $colorBodyBgSubtle; | ||||
|         box-shadow: inset rgba(black, 0.4) 0 1px 1px; | ||||
| @@ -55,17 +54,6 @@ | ||||
|             stroke: $colorBodyBgSubtle; | ||||
|             transition: $transOut; | ||||
|         } | ||||
|  | ||||
|         &:hover, | ||||
|         &:active { | ||||
|             $c: $colorKeySubtle; | ||||
|             background-color: $c; | ||||
|             transition: $transIn; | ||||
|             svg text { | ||||
|                 stroke: $c; | ||||
|                 transition: $transIn; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     .is-realtime-mode & { | ||||
|   | ||||
| @@ -57,6 +57,65 @@ | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     &.is-fixed-mode { | ||||
|         .c-conductor-axis { | ||||
|             &__zoom-indicator { | ||||
|                 border: 1px solid transparent; | ||||
|                 display: none; // Hidden by default | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         &:not(.is-panning), | ||||
|         &:not(.is-zooming) { | ||||
|             .c-conductor-axis { | ||||
|                 &:hover, | ||||
|                 &:active { | ||||
|                     cursor: col-resize; | ||||
|                     filter: $timeConductorAxisHoverFilter; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         &.is-panning, | ||||
|         &.is-zooming { | ||||
|             .c-conductor-input input { | ||||
|                 // Styles for inputs while zooming or panning | ||||
|                 background: rgba($timeConductorActiveBg, 0.4); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         &.alt-pressed { | ||||
|             .c-conductor-axis:hover { | ||||
|                 // When alt is being pressed and user is hovering over the axis, set the cursor | ||||
|                 @include cursorGrab(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         &.is-panning { | ||||
|             .c-conductor-axis { | ||||
|                 @include cursorGrab(); | ||||
|                 background-color: $timeConductorActivePanBg; | ||||
|                 transition: $transIn; | ||||
|  | ||||
|                 svg text { | ||||
|                     stroke: $timeConductorActivePanBg; | ||||
|                     transition: $transIn; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         &.is-zooming { | ||||
|             .c-conductor-axis__zoom-indicator { | ||||
|                 display: block; | ||||
|                 position: absolute; | ||||
|                 background: rgba($timeConductorActiveBg, 0.4); | ||||
|                 border-left-color: $timeConductorActiveBg; | ||||
|                 border-right-color: $timeConductorActiveBg; | ||||
|                 top: 0; bottom: 0; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     &.is-realtime-mode { | ||||
|         .c-conductor__time-bounds { | ||||
|             grid-template-columns: 20px auto 1fr auto auto; | ||||
|   | ||||
| @@ -142,6 +142,9 @@ $colorTimeHov: pullForward($colorTime, 10%); | ||||
| $colorTimeSubtle: pushBack($colorTime, 20%); | ||||
| $colorTOI: $colorBodyFg; // was $timeControllerToiLineColor | ||||
| $colorTOIHov: $colorTime; // was $timeControllerToiLineColorHov | ||||
| $timeConductorAxisHoverFilter: brightness(1.2); | ||||
| $timeConductorActiveBg: $colorKey; | ||||
| $timeConductorActivePanBg: #226074; | ||||
|  | ||||
| /************************************************** BROWSING */ | ||||
| $browseFrameColor: pullForward($colorBodyBg, 10%); | ||||
|   | ||||
| @@ -146,6 +146,9 @@ $colorTimeHov: pullForward($colorTime, 10%); | ||||
| $colorTimeSubtle: pushBack($colorTime, 20%); | ||||
| $colorTOI: $colorBodyFg; // was $timeControllerToiLineColor | ||||
| $colorTOIHov: $colorTime; // was $timeControllerToiLineColorHov | ||||
| $timeConductorAxisHoverFilter: brightness(1.2); | ||||
| $timeConductorActiveBg: $colorKey; | ||||
| $timeConductorActivePanBg: #226074; | ||||
|  | ||||
| /************************************************** BROWSING */ | ||||
| $browseFrameColor: pullForward($colorBodyBg, 10%); | ||||
|   | ||||
| @@ -132,7 +132,7 @@ $colorPausedFg: #fff; | ||||
| // Base variations | ||||
| $colorBodyBgSubtle: pullForward($colorBodyBg, 5%); | ||||
| $colorBodyBgSubtleHov: pushBack($colorKey, 50%); | ||||
| $colorKeySubtle: pushBack($colorKey, 10%); | ||||
| $colorKeySubtle: pushBack($colorKey, 20%); | ||||
|  | ||||
| // Time Colors | ||||
| $colorTime: #618cff; | ||||
| @@ -142,6 +142,9 @@ $colorTimeHov: pushBack($colorTime, 5%); | ||||
| $colorTimeSubtle: pushBack($colorTime, 20%); | ||||
| $colorTOI: $colorBodyFg; // was $timeControllerToiLineColor | ||||
| $colorTOIHov: $colorTime; // was $timeControllerToiLineColorHov | ||||
| $timeConductorAxisHoverFilter: brightness(0.8); | ||||
| $timeConductorActiveBg: $colorKey; | ||||
| $timeConductorActivePanBg: #A0CDE1; | ||||
|  | ||||
| /************************************************** BROWSING */ | ||||
| $browseFrameColor: pullForward($colorBodyBg, 10%); | ||||
|   | ||||
| @@ -462,9 +462,17 @@ select { | ||||
|     text-shadow: $shdwMenuText; | ||||
|     padding: $interiorMarginSm; | ||||
|     box-shadow: $shdwMenu; | ||||
|     display: block; | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     position: absolute; | ||||
|     z-index: 100; | ||||
|  | ||||
|     > * { | ||||
|         flex: 0 0 auto; | ||||
|         //+ * { | ||||
|         //    margin-top: $interiorMarginSm; | ||||
|         //} | ||||
|     } | ||||
| } | ||||
|  | ||||
| @mixin menuInner() { | ||||
| @@ -502,6 +510,23 @@ select { | ||||
| .c-menu { | ||||
|     @include menuOuter(); | ||||
|     @include menuInner(); | ||||
|  | ||||
|     &__section-hint { | ||||
|         $m: $interiorMargin; | ||||
|         margin: $m 0; | ||||
|         padding: $m nth($menuItemPad, 2) 0 nth($menuItemPad, 2); | ||||
|  | ||||
|         opacity: 0.6; | ||||
|         font-size: 0.9em; | ||||
|         font-style: italic; | ||||
|     } | ||||
|  | ||||
|     &__section-separator { | ||||
|         $m: $interiorMargin; | ||||
|         border-top: 1px solid $colorInteriorBorder; | ||||
|         margin: $m 0; | ||||
|         padding: $m nth($menuItemPad, 2) 0 nth($menuItemPad, 2); | ||||
|     } | ||||
| } | ||||
|  | ||||
| .c-super-menu { | ||||
|   | ||||
| @@ -1,6 +1,11 @@ | ||||
| @mixin visibleRegexButton { | ||||
|     opacity: 1; | ||||
|     padding: 1px 3px; | ||||
|     width: 24px; | ||||
| } | ||||
|  | ||||
| .c-search { | ||||
|     @include wrappedInput(); | ||||
|  | ||||
|     padding-top: 2px; | ||||
|     padding-bottom: 2px; | ||||
|  | ||||
| @@ -9,11 +14,46 @@ | ||||
|         content: $glyph-icon-magnify; | ||||
|     } | ||||
|  | ||||
|     &__use-regex { | ||||
|         // Button | ||||
|         $c: $colorBodyFg; | ||||
|         background: rgba($c, 0.2); | ||||
|         border: 1px solid rgba($c, 0.3); | ||||
|         color: $c; | ||||
|         border-radius: $controlCr; | ||||
|         font-weight: bold; | ||||
|         letter-spacing: 1px; | ||||
|         font-size: 0.8em; | ||||
|         margin-left: $interiorMarginSm; | ||||
|         min-width: 0; | ||||
|         opacity: 0; | ||||
|         order: 2; | ||||
|         overflow: hidden; | ||||
|         padding: 1px 0; | ||||
|         transform-origin: left; | ||||
|         transition: $transOut; | ||||
|         width: 0; | ||||
|  | ||||
|         &.is-active { | ||||
|             $c: $colorBtnActiveBg; | ||||
|             @include visibleRegexButton(); | ||||
|             background: rgba($c, 0.3); | ||||
|             border-color: $c; | ||||
|             color: $c; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     &__clear-input { | ||||
|         display: none; | ||||
|         order: 99; | ||||
|         padding: 1px 0; | ||||
|     } | ||||
|  | ||||
|     &.is-active { | ||||
|         .c-search__use-regex { | ||||
|             margin-left: 0; | ||||
|         } | ||||
|  | ||||
|         .c-search__clear-input { | ||||
|             display: block; | ||||
|         } | ||||
| @@ -21,6 +61,15 @@ | ||||
|  | ||||
|     input[type='text'], | ||||
|     input[type='search'] { | ||||
|         margin-left: $interiorMargin; | ||||
|         order: 3; | ||||
|         text-align: left; | ||||
|     } | ||||
|  | ||||
|     &:hover { | ||||
|         .c-search__use-regex { | ||||
|             @include visibleRegexButton(); | ||||
|             transition: $transIn; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -15,6 +15,7 @@ | ||||
|         class="c-search__clear-input icon-x-in-circle" | ||||
|         @click="clearInput" | ||||
|     ></a> | ||||
|     <slot></slot> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
|   | ||||
| @@ -108,7 +108,7 @@ export default { | ||||
|                 let object = selection[0][0].context.item; | ||||
|                 if (object) { | ||||
|                     let type = this.openmct.types.get(object.type); | ||||
|                     this.showStyles = (this.excludeObjectTypes.indexOf(object.type) < 0) && type.definition.creatable; | ||||
|                     this.showStyles = this.isLayoutObject(selection[0], object.type) || this.isCreatableObject(object, type); | ||||
|                 } | ||||
|                 if (!this.currentTabbedView.key || (!this.showStyles && this.currentTabbedView.key === this.tabbedViews[1].key)) | ||||
|                 { | ||||
| @@ -116,6 +116,14 @@ export default { | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         isLayoutObject(selection, objectType) { | ||||
|             //we allow conditionSets to be styled if they're part of a layout | ||||
|             return selection.length > 1 && | ||||
|                 ((objectType === 'conditionSet') || (this.excludeObjectTypes.indexOf(objectType) < 0)); | ||||
|         }, | ||||
|         isCreatableObject(object, type) { | ||||
|             return (this.excludeObjectTypes.indexOf(object.type) < 0) && type.definition.creatable; | ||||
|         }, | ||||
|         updateCurrentTab(view) { | ||||
|             this.currentTabbedView = view; | ||||
|         }, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user