Compare commits
	
		
			33 Commits
		
	
	
		
			local-time
			...
			tree-pagin
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 9015028f81 | ||
|   | 3dc6dac12d | ||
|   | aafe524454 | ||
|   | e84ade1752 | ||
|   | 3b094e43e3 | ||
|   | e6a7b4ed6c | ||
|   | 97230bb21f | ||
|   | 768d99d928 | ||
|   | c760190a29 | ||
|   | 7fe4a77c43 | ||
|   | 8578d78c51 | ||
|   | 362e565a09 | ||
|   | 9517c1f2cd | ||
|   | 262d35804d | ||
|   | e0587bf0e7 | ||
|   | f1494fd285 | ||
|   | 884aec8ea0 | ||
|   | 216f447578 | ||
|   | 38d78990da | ||
|   | e0d2c85740 | ||
|   | 29ab9bdf02 | ||
|   | c38d810658 | ||
|   | f5c48b7bf6 | ||
|   | d0e08f1d9a | ||
|   | 72ea7b80fd | ||
|   | 0b433ccb50 | ||
|   | e2ecd106f6 | ||
|   | 7debe89f8b | ||
|   | a396f50acd | ||
|   | 35d0c02bc5 | ||
|   | abd7506b45 | ||
|   | 526b4aa07e | ||
|   | b5e23963d4 | 
| @@ -99,10 +99,10 @@ define([ | ||||
|  | ||||
|     GeneratorMetadataProvider.prototype.getMetadata = function (domainObject) { | ||||
|         return _.extend( | ||||
|                 {}, | ||||
|                 domainObject.telemetry, | ||||
|                 METADATA_BY_TYPE[domainObject.type] | ||||
|             ); | ||||
|             {}, | ||||
|             domainObject.telemetry, | ||||
|             METADATA_BY_TYPE[domainObject.type] | ||||
|         ); | ||||
|     }; | ||||
|  | ||||
|     return GeneratorMetadataProvider; | ||||
|   | ||||
| @@ -1,9 +1,9 @@ | ||||
| <span class="h-indicator" ng-controller="DialogLaunchController"> | ||||
|     <!-- DO NOT ADD SPACES BETWEEN THE SPANS - IT ADDS WHITE SPACE!! --> | ||||
|     <div class="ls-indicator icon-box-with-arrow s-status-available"><span class="label"> | ||||
|         <a ng-click="launchProgress(true)">Known</a> | ||||
|         <a ng-click="launchProgress(false)">Unknown</a> | ||||
|         <a ng-click="launchError()">Error</a> | ||||
|         <a ng-click="launchInfo()">Info</a> | ||||
|     <div class="c-indicator c-indicator--clickable icon-box-with-arrow s-status-available"><span class="label c-indicator__label"> | ||||
|         <button ng-click="launchProgress(true)">Known</button> | ||||
|         <button ng-click="launchProgress(false)">Unknown</button> | ||||
|         <button ng-click="launchError()">Error</button> | ||||
|         <button ng-click="launchInfo()">Info</button> | ||||
|     </span></div> | ||||
| </span> | ||||
|   | ||||
| @@ -1,9 +1,9 @@ | ||||
| <span class="h-indicator" ng-controller="NotificationLaunchController"> | ||||
|     <!-- DO NOT ADD SPACES BETWEEN THE SPANS - IT ADDS WHITE SPACE!! --> | ||||
|     <div class="ls-indicator icon-bell s-status-available"><span class="label"> | ||||
|         <a ng-click="newInfo()">Success</a> | ||||
|         <a ng-click="newError()">Error</a> | ||||
|         <a ng-click="newAlert()">Alert</a> | ||||
|         <a ng-click="newProgress()">Progress</a> | ||||
|     <div class="c-indicator c-indicator--clickable icon-bell s-status-available"><span class="label c-indicator__label"> | ||||
|         <button ng-click="newInfo()">Success</button> | ||||
|         <button ng-click="newError()">Error</button> | ||||
|         <button ng-click="newAlert()">Alert</button> | ||||
|         <button ng-click="newProgress()">Progress</button> | ||||
|     </span></div> | ||||
| </span> | ||||
|   | ||||
| @@ -50,7 +50,6 @@ | ||||
|         openmct.install(openmct.plugins.Generator()); | ||||
|         openmct.install(openmct.plugins.ExampleImagery()); | ||||
|         openmct.install(openmct.plugins.UTCTimeSystem()); | ||||
|         openmct.install(openmct.plugins.ImportExport()); | ||||
|         openmct.install(openmct.plugins.AutoflowView({ | ||||
|             type: "telemetry.panel" | ||||
|         })); | ||||
| @@ -80,13 +79,9 @@ | ||||
|         })); | ||||
|         openmct.install(openmct.plugins.SummaryWidget()); | ||||
|         openmct.install(openmct.plugins.Notebook()); | ||||
|         openmct.install(openmct.plugins.FolderView()); | ||||
|         openmct.install(openmct.plugins.Tabs()); | ||||
|         openmct.install(openmct.plugins.FlexibleLayout()); | ||||
|         openmct.install(openmct.plugins.LADTable()); | ||||
|         openmct.install(openmct.plugins.Filters(['table', 'telemetry.plot.overlay'])); | ||||
|         openmct.install(openmct.plugins.ObjectMigration()); | ||||
|         openmct.install(openmct.plugins.GoToOriginalAction()); | ||||
|         openmct.install(openmct.plugins.ClearData(['table', 'telemetry.plot.overlay', 'telemetry.plot.stacked'])); | ||||
|         openmct.start(); | ||||
|     </script> | ||||
| </html> | ||||
|   | ||||
| @@ -1,9 +1,10 @@ | ||||
| { | ||||
|   "name": "openmct", | ||||
|   "version": "0.14.0-SNAPSHOT", | ||||
|   "version": "1.0.0-beta", | ||||
|   "description": "The Open MCT core platform", | ||||
|   "dependencies": {}, | ||||
|   "devDependencies": { | ||||
|     "acorn": "6.2.0", | ||||
|     "angular": "1.4.14", | ||||
|     "angular-route": "1.4.14", | ||||
|     "babel-eslint": "8.2.6", | ||||
| @@ -55,7 +56,7 @@ | ||||
|     "node-bourbon": "^4.2.3", | ||||
|     "node-sass": "^4.9.2", | ||||
|     "painterro": "^0.2.65", | ||||
|     "printj": "^1.1.0", | ||||
|     "printj": "^1.2.1", | ||||
|     "raw-loader": "^0.5.1", | ||||
|     "request": "^2.69.0", | ||||
|     "split": "^1.0.0", | ||||
|   | ||||
| @@ -20,8 +20,8 @@ | ||||
|  at runtime from the About dialog for additional information. | ||||
| --> | ||||
| <!-- DO NOT ADD SPACES BETWEEN THE SPANS - IT ADDS WHITE SPACE!! --> | ||||
| <div class="ls-indicator {{ngModel.getCssClass()}}" | ||||
| <div class="c-indicator {{ngModel.getCssClass()}}" | ||||
| 	 title="{{ngModel.getDescription()}}" | ||||
| 	 ng-show="ngModel.getText().length > 0"> | ||||
| 	<span class="label">{{ngModel.getText()}}</span> | ||||
| 	<span class="label c-indicator__label">{{ngModel.getText()}}</span> | ||||
| </div> | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| <!-- DO NOT ADD SPACES BETWEEN THE SPANS - IT ADDS WHITE SPACE!! --> | ||||
| <div ng-show="notifications.length > 0" class="ls-indicator s-status-{{highest.severity}} icon-bell" | ||||
| <div ng-show="notifications.length > 0" class="c-indicator c-indicator--clickable s-status-{{highest.severity}} icon-bell" | ||||
|       ng-controller="NotificationIndicatorController"> | ||||
|     <span class="label"> | ||||
|         <a ng-click="showNotificationsList()"> | ||||
|            {{notifications.length}} Notification<span ng-show="notifications.length > 1">s</span></a> | ||||
|     </span><span class="count">{{notifications.length}}</span> | ||||
|     <span class="label c-indicator__label"> | ||||
|         <button ng-click="showNotificationsList()"> | ||||
|            {{notifications.length}} Notification<span ng-show="notifications.length > 1">s</span></button> | ||||
|     </span><span class="c-indicator__count">{{notifications.length}}</span> | ||||
| </div> | ||||
|   | ||||
| @@ -49,7 +49,7 @@ define( | ||||
|         }; | ||||
|  | ||||
|         ClockIndicator.prototype.getCssClass = function () { | ||||
|             return "t-indicator-clock icon-clock no-collapse float-right"; | ||||
|             return "t-indicator-clock icon-clock no-minify c-indicator--not-clickable"; | ||||
|         }; | ||||
|  | ||||
|         ClockIndicator.prototype.getText = function () { | ||||
|   | ||||
| @@ -64,12 +64,30 @@ define(['zepto'], function ($) { | ||||
|         var tree = this.generateNewIdentifiers(objTree); | ||||
|         var rootId = tree.rootId; | ||||
|         var rootObj = this.instantiate(tree.openmct[rootId], rootId); | ||||
|         var newStyleParent = parent.useCapability('adapter'); | ||||
|         var newStyleRootObj = rootObj.useCapability('adapter'); | ||||
|  | ||||
|         // Instantiate all objects in tree with their newly genereated ids, | ||||
|         // adding each to its rightful parent's composition | ||||
|         rootObj.getCapability("location").setPrimaryLocation(parent.getId()); | ||||
|         this.deepInstantiate(rootObj, tree.openmct, []); | ||||
|         parent.getCapability("composition").add(rootObj); | ||||
|         if (this.openmct.composition.checkPolicy(newStyleParent, newStyleRootObj)) { | ||||
|             // Instantiate all objects in tree with their newly generated ids, | ||||
|             // adding each to its rightful parent's composition | ||||
|             rootObj.getCapability("location").setPrimaryLocation(parent.getId()); | ||||
|             this.deepInstantiate(rootObj, tree.openmct, []); | ||||
|             parent.getCapability("composition").add(rootObj); | ||||
|         } else { | ||||
|             var dialog = this.openmct.overlays.dialog({ | ||||
|                 iconClass: 'alert', | ||||
|                 message: "We're sorry, but you cannot import that object type into this object.", | ||||
|                 buttons: [ | ||||
|                     { | ||||
|                         label: "Ok", | ||||
|                         emphasis: true, | ||||
|                         callback: function () { | ||||
|                             dialog.dismiss(); | ||||
|                         } | ||||
|                     } | ||||
|                 ] | ||||
|             }); | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     ImportAsJSONAction.prototype.deepInstantiate = function (parent, tree, seen) { | ||||
| @@ -80,15 +98,17 @@ define(['zepto'], function ($) { | ||||
|             var newObj; | ||||
|  | ||||
|             seen.push(parent.getId()); | ||||
|             parentModel.composition.forEach(function (childId, index) { | ||||
|                 if (!tree[childId] || seen.includes(childId)) { | ||||
|  | ||||
|             parentModel.composition.forEach(function (childId) { | ||||
|                 let keystring = this.openmct.objects.makeKeyString(childId); | ||||
|  | ||||
|                 if (!tree[keystring] || seen.includes(keystring)) { | ||||
|                     return; | ||||
|                 } | ||||
|  | ||||
|                 newObj = this.instantiate(tree[childId], childId); | ||||
|                 parent.getCapability("composition").add(newObj); | ||||
|                 newObj = this.instantiate(tree[keystring], keystring); | ||||
|                 newObj.getCapability("location") | ||||
|                     .setPrimaryLocation(tree[childId].location); | ||||
|                     .setPrimaryLocation(tree[keystring].location); | ||||
|                 this.deepInstantiate(newObj, tree, seen); | ||||
|             }, this); | ||||
|         } | ||||
|   | ||||
| @@ -100,7 +100,7 @@ define( | ||||
|         } | ||||
|  | ||||
|         CouchIndicator.prototype.getCssClass = function () { | ||||
|             return "icon-database " + this.state.statusClass; | ||||
|             return "c-indicator--clickable icon-database " + this.state.statusClass; | ||||
|         }; | ||||
|  | ||||
|         CouchIndicator.prototype.getGlyphClass = function () { | ||||
|   | ||||
| @@ -84,7 +84,7 @@ define( | ||||
|         } | ||||
|  | ||||
|         ElasticIndicator.prototype.getCssClass = function () { | ||||
|             return "icon-database"; | ||||
|             return "c-indicator--clickable icon-database"; | ||||
|         }; | ||||
|         ElasticIndicator.prototype.getGlyphClass = function () { | ||||
|             return this.state.glyphClass; | ||||
|   | ||||
| @@ -41,7 +41,7 @@ define( | ||||
|         } | ||||
|  | ||||
|         LocalStorageIndicator.prototype.getCssClass = function () { | ||||
|             return "icon-database s-status-caution"; | ||||
|             return "c-indicator--clickable icon-database s-status-caution"; | ||||
|         }; | ||||
|         LocalStorageIndicator.prototype.getGlyphClass = function () { | ||||
|             return 'caution'; | ||||
|   | ||||
| @@ -29,6 +29,7 @@ define([ | ||||
|     "./res/templates/search.html", | ||||
|     "./res/templates/search-menu.html", | ||||
|     "raw-loader!./src/services/GenericSearchWorker.js", | ||||
|     "raw-loader!./src/services/BareBonesSearchWorker.js", | ||||
|     'legacyRegistry' | ||||
| ], function ( | ||||
|     SearchController, | ||||
| @@ -39,6 +40,7 @@ define([ | ||||
|     searchTemplate, | ||||
|     searchMenuTemplate, | ||||
|     searchWorkerText, | ||||
|     BareBonesSearchWorkerText, | ||||
|     legacyRegistry | ||||
| ) { | ||||
|  | ||||
| @@ -115,6 +117,10 @@ define([ | ||||
|                 } | ||||
|             ], | ||||
|             "workers": [ | ||||
|                 { | ||||
|                     "key": "bareBonesSearchWorker", | ||||
|                     "scriptText": BareBonesSearchWorkerText | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "genericSearchWorker", | ||||
|                     "scriptText": searchWorkerText | ||||
|   | ||||
							
								
								
									
										80
									
								
								platform/search/src/services/BareBonesSearchWorker.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								platform/search/src/services/BareBonesSearchWorker.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,80 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2018, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT 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 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. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| /*global self*/ | ||||
|  | ||||
| /** | ||||
|  * Module defining GenericSearchWorker. Created by shale on 07/21/2015. | ||||
|  */ | ||||
| (function () { | ||||
|  | ||||
|     // An array of objects composed of domain object IDs and models | ||||
|     // {id: domainObject's ID, model: domainObject's model} | ||||
|     var indexedItems = []; | ||||
|  | ||||
|     function indexItem(id, model) { | ||||
|         indexedItems.push({ | ||||
|             id: id, | ||||
|             name: model.name.toLowerCase() | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Gets search results from the indexedItems based on provided search | ||||
|      *   input. Returns matching results from indexedItems | ||||
|      * | ||||
|      * @param data An object which contains: | ||||
|      *           * input: The original string which we are searching with | ||||
|      *           * maxResults: The maximum number of search results desired | ||||
|      *           * queryId: an id identifying this query, will be returned. | ||||
|      */ | ||||
|     function search(data) { | ||||
|         // This results dictionary will have domain object ID keys which | ||||
|         // point to the value the domain object's score. | ||||
|         var results, | ||||
|             input = data.input.trim().toLowerCase(), | ||||
|             message = { | ||||
|                 request: 'search', | ||||
|                 results: {}, | ||||
|                 total: 0, | ||||
|                 queryId: data.queryId | ||||
|             }; | ||||
|  | ||||
|         results = indexedItems.filter((indexedItem) => { | ||||
|             return indexedItem.name.includes(input); | ||||
|         }); | ||||
|  | ||||
|         message.total = results.length; | ||||
|         message.results = results | ||||
|             .slice(0, data.maxResults); | ||||
|  | ||||
|         return message; | ||||
|     } | ||||
|  | ||||
|     self.onmessage = function (event) { | ||||
|         if (event.data.request === 'index') { | ||||
|             indexItem(event.data.id, event.data.model); | ||||
|         } else if (event.data.request === 'search') { | ||||
|             self.postMessage(search(event.data)); | ||||
|         } | ||||
|     }; | ||||
| }()); | ||||
| @@ -58,6 +58,8 @@ define([ | ||||
|  | ||||
|         this.pendingQueries = {}; | ||||
|  | ||||
|         this.useBareBones = true; | ||||
|  | ||||
|         this.worker = this.startWorker(workerService); | ||||
|         this.indexOnMutation(topic); | ||||
|  | ||||
| @@ -101,8 +103,14 @@ define([ | ||||
|      * @returns worker the created search worker. | ||||
|      */ | ||||
|     GenericSearchProvider.prototype.startWorker = function (workerService) { | ||||
|         var worker = workerService.run('genericSearchWorker'), | ||||
|             provider = this; | ||||
|         var provider = this, | ||||
|             worker; | ||||
|  | ||||
|         if (this.useBareBones) { | ||||
|             worker = workerService.run('bareBonesSearchWorker'); | ||||
|         } else { | ||||
|             worker = workerService.run('genericSearchWorker'); | ||||
|         } | ||||
|  | ||||
|         worker.addEventListener('message', function (messageEvent) { | ||||
|             provider.onWorkerMessage(messageEvent); | ||||
| @@ -242,18 +250,34 @@ define([ | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         var pendingQuery = this.pendingQueries[event.data.queryId], | ||||
|         var pendingQuery, | ||||
|             modelResults; | ||||
|  | ||||
|         if (this.useBareBones) { | ||||
|             pendingQuery = this.pendingQueries[event.data.queryId]; | ||||
|             modelResults = { | ||||
|                 total: event.data.total | ||||
|             }; | ||||
|  | ||||
|         modelResults.hits = event.data.results.map(function (hit) { | ||||
|             return { | ||||
|                 id: hit.item.id, | ||||
|                 model: hit.item.model, | ||||
|                 score: hit.matchCount | ||||
|             modelResults.hits = event.data.results.map(function (hit) { | ||||
|                 return { | ||||
|                     id: hit.id | ||||
|                 }; | ||||
|             }); | ||||
|         } else { | ||||
|             pendingQuery = this.pendingQueries[event.data.queryId]; | ||||
|             modelResults = { | ||||
|                 total: event.data.total | ||||
|             }; | ||||
|         }); | ||||
|  | ||||
|             modelResults.hits = event.data.results.map(function (hit) { | ||||
|                 return { | ||||
|                     id: hit.item.id, | ||||
|                     model: hit.item.model, | ||||
|                     score: hit.matchCount | ||||
|                 }; | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         pendingQuery.resolve(modelResults); | ||||
|         delete this.pendingQueries[event.data.queryId]; | ||||
|   | ||||
| @@ -144,6 +144,8 @@ | ||||
|         message.results = results | ||||
|             .slice(0, data.maxResults); | ||||
|  | ||||
|         console.log(message); | ||||
|  | ||||
|         return message; | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -246,12 +246,21 @@ define([ | ||||
|         this.branding = BrandingAPI.default; | ||||
|  | ||||
|         this.legacyRegistry = defaultRegistry; | ||||
|  | ||||
|         // Plugin's that are installed by default | ||||
|  | ||||
|         this.install(this.plugins.Plot()); | ||||
|         this.install(this.plugins.TelemetryTable()); | ||||
|         this.install(PreviewPlugin.default()); | ||||
|         this.install(LegacyIndicatorsPlugin()); | ||||
|         this.install(LicensesPlugin.default()); | ||||
|         this.install(RemoveActionPlugin.default()); | ||||
|         this.install(this.plugins.ImportExport()); | ||||
|         this.install(this.plugins.FolderView()); | ||||
|         this.install(this.plugins.Tabs()); | ||||
|         this.install(this.plugins.FlexibleLayout()); | ||||
|         this.install(this.plugins.LADTable()); | ||||
|         this.install(this.plugins.GoToOriginalAction()); | ||||
|  | ||||
|         if (typeof BUILD_CONSTANTS !== 'undefined') { | ||||
|             this.install(buildInfoPlugin(BUILD_CONSTANTS)); | ||||
|   | ||||
| @@ -26,6 +26,7 @@ const OUTSIDE_EDIT_PATH_BLACKLIST = ["copy", "follow", "properties", "move", "li | ||||
| export default class LegacyContextMenuAction { | ||||
|     constructor(openmct, LegacyAction) { | ||||
|         this.openmct = openmct; | ||||
|         this.key = LegacyAction.definition.key; | ||||
|         this.name = LegacyAction.definition.name; | ||||
|         this.description = LegacyAction.definition.description; | ||||
|         this.cssClass = LegacyAction.definition.cssClass; | ||||
|   | ||||
| @@ -36,7 +36,7 @@ define([ | ||||
|     './runs/RegisterLegacyTypes', | ||||
|     './services/LegacyObjectAPIInterceptor', | ||||
|     './views/installLegacyViews', | ||||
|     './policies/legacyCompositionPolicyAdapter', | ||||
|     './policies/LegacyCompositionPolicyAdapter', | ||||
|     './actions/LegacyActionAdapter' | ||||
| ], function ( | ||||
|     legacyRegistry, | ||||
|   | ||||
| @@ -108,6 +108,9 @@ define([ | ||||
|                             link(); | ||||
|                         } | ||||
|                     }, | ||||
|                     onClearData() { | ||||
|                         scope.$broadcast('clearData'); | ||||
|                     }, | ||||
|                     destroy: function () { | ||||
|                         element.off(); | ||||
|                         element.remove(); | ||||
|   | ||||
| @@ -25,7 +25,7 @@ define([ | ||||
|             cssClass: representation.cssClass, | ||||
|             description: representation.description, | ||||
|             canView: function (selection) { | ||||
|                 if (selection.length === 0 || selection[0].length === 0) { | ||||
|                 if (selection.length !== 1 || selection[0].length === 0) { | ||||
|                     return false; | ||||
|                 } | ||||
|  | ||||
|   | ||||
| @@ -22,8 +22,20 @@ define([ | ||||
|             publicAPI = {}; | ||||
|             publicAPI.objects = jasmine.createSpyObj('ObjectAPI', [ | ||||
|                 'get', | ||||
|                 'mutate' | ||||
|                 'mutate', | ||||
|                 'observe', | ||||
|                 'areIdsEqual' | ||||
|             ]); | ||||
|  | ||||
|             publicAPI.objects.areIdsEqual.and.callFake(function (id1, id2) { | ||||
|                 return id1.namespace === id2.namespace && id1.key === id2.key; | ||||
|             }); | ||||
|  | ||||
|             publicAPI.composition = jasmine.createSpyObj('CompositionAPI', [ | ||||
|                 'checkPolicy' | ||||
|             ]); | ||||
|             publicAPI.composition.checkPolicy.and.returnValue(true); | ||||
|  | ||||
|             publicAPI.objects.eventEmitter = jasmine.createSpyObj('eventemitter', [ | ||||
|                 'on' | ||||
|             ]); | ||||
| @@ -91,7 +103,7 @@ define([ | ||||
|                 beforeEach(function () { | ||||
|                     listener = jasmine.createSpy('reorderListener'); | ||||
|                     composition.on('reorder', listener); | ||||
|                      | ||||
|  | ||||
|                     return composition.load(); | ||||
|                 }); | ||||
|                 it('', function () { | ||||
| @@ -119,49 +131,16 @@ define([ | ||||
|                     expect(newComposition[2].key).toEqual('a'); | ||||
|                 }) | ||||
|             }); | ||||
|  | ||||
|             // TODO: Implement add/removal in new default provider. | ||||
|             xit('synchronizes changes between instances', function () { | ||||
|                 var otherComposition = compositionAPI.get(domainObject); | ||||
|                 var addListener = jasmine.createSpy('addListener'); | ||||
|                 var removeListener = jasmine.createSpy('removeListener'); | ||||
|                 var otherAddListener = jasmine.createSpy('otherAddListener'); | ||||
|                 var otherRemoveListener = jasmine.createSpy('otherRemoveListener'); | ||||
|             it('supports adding an object to composition', function () { | ||||
|                 let addListener = jasmine.createSpy('addListener'); | ||||
|                 let mockChildObject = { | ||||
|                     identifier: {key: 'mock-key', namespace: ''} | ||||
|                 }; | ||||
|                 composition.on('add', addListener); | ||||
|                 composition.on('remove', removeListener); | ||||
|                 otherComposition.on('add', otherAddListener); | ||||
|                 otherComposition.on('remove', otherRemoveListener); | ||||
|                 composition.add(mockChildObject); | ||||
|  | ||||
|                 return Promise.all([composition.load(), otherComposition.load()]) | ||||
|                     .then(function () { | ||||
|                         expect(addListener).toHaveBeenCalled(); | ||||
|                         expect(otherAddListener).toHaveBeenCalled(); | ||||
|                         expect(removeListener).not.toHaveBeenCalled(); | ||||
|                         expect(otherRemoveListener).not.toHaveBeenCalled(); | ||||
|  | ||||
|                         var object = addListener.calls.mostRecent().args[0]; | ||||
|                         composition.remove(object); | ||||
|                         expect(removeListener).toHaveBeenCalled(); | ||||
|                         expect(otherRemoveListener).toHaveBeenCalled(); | ||||
|  | ||||
|                         addListener.reset(); | ||||
|                         otherAddListener.reset(); | ||||
|                         composition.add(object); | ||||
|                         expect(addListener).toHaveBeenCalled(); | ||||
|                         expect(otherAddListener).toHaveBeenCalled(); | ||||
|  | ||||
|                         removeListener.reset(); | ||||
|                         otherRemoveListener.reset(); | ||||
|                         otherComposition.remove(object); | ||||
|                         expect(removeListener).toHaveBeenCalled(); | ||||
|                         expect(otherRemoveListener).toHaveBeenCalled(); | ||||
|  | ||||
|                         addListener.reset(); | ||||
|                         otherAddListener.reset(); | ||||
|                         otherComposition.add(object); | ||||
|                         expect(addListener).toHaveBeenCalled(); | ||||
|                         expect(otherAddListener).toHaveBeenCalled(); | ||||
|                     }); | ||||
|                 expect(domainObject.composition.length).toBe(4); | ||||
|                 expect(domainObject.composition[3]).toEqual(mockChildObject.identifier); | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
| @@ -184,7 +163,9 @@ define([ | ||||
|                                 key: 'thing' | ||||
|                             } | ||||
|                         ]); | ||||
|                     } | ||||
|                     }, | ||||
|                     add: jasmine.createSpy('add'), | ||||
|                     remove: jasmine.createSpy('remove') | ||||
|                 }; | ||||
|                 domainObject = { | ||||
|                     identifier: { | ||||
| @@ -214,6 +195,25 @@ define([ | ||||
|                     }); | ||||
|                 }); | ||||
|             }); | ||||
|             describe('Calling add or remove', function () { | ||||
|                 let mockChildObject; | ||||
|  | ||||
|                 beforeEach(function () { | ||||
|                     mockChildObject = { | ||||
|                         identifier: {key: 'mock-key', namespace: ''} | ||||
|                     }; | ||||
|                     composition.add(mockChildObject); | ||||
|                 }); | ||||
|  | ||||
|                 it('calls add on the provider', function () { | ||||
|                     expect(customProvider.add).toHaveBeenCalledWith(domainObject, mockChildObject.identifier); | ||||
|                 }); | ||||
|  | ||||
|                 it('calls remove on the provider', function () { | ||||
|                     composition.remove(mockChildObject); | ||||
|                     expect(customProvider.remove).toHaveBeenCalledWith(domainObject, mockChildObject.identifier); | ||||
|                 }); | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         describe('dynamic custom composition', function () { | ||||
|   | ||||
| @@ -75,9 +75,7 @@ define([ | ||||
|             throw new Error('Event not supported by composition: ' + event); | ||||
|         } | ||||
|         if (!this.mutationListener) { | ||||
|             this.mutationListener = this.publicAPI.objects.observe(this.domainObject, '*', (newDomainObject) => { | ||||
|                 this.domainObject = newDomainObject; | ||||
|             }) | ||||
|             this._synchronize(); | ||||
|         } | ||||
|         if (this.provider.on && this.provider.off) { | ||||
|             if (event === 'add') { | ||||
| @@ -134,10 +132,8 @@ define([ | ||||
|  | ||||
|         this.listeners[event].splice(index, 1); | ||||
|         if (this.listeners[event].length === 0) { | ||||
|             if (this.mutationListener) { | ||||
|                 this.mutationListener(); | ||||
|                 delete this.mutationListener; | ||||
|             } | ||||
|             this._destroy(); | ||||
|  | ||||
|             // Remove provider listener if this is the last callback to | ||||
|             // be removed. | ||||
|             if (this.provider.off && this.provider.on) { | ||||
| @@ -181,6 +177,9 @@ define([ | ||||
|      */ | ||||
|     CompositionCollection.prototype.add = function (child, skipMutate) { | ||||
|         if (!skipMutate) { | ||||
|             if (!this.publicAPI.composition.checkPolicy(this.domainObject, child)) { | ||||
|                 throw `Object of type ${child.type} cannot be added to object of type ${this.domainObject.type}`; | ||||
|             } | ||||
|             this.provider.add(this.domainObject, child.identifier); | ||||
|         } else { | ||||
|             this.emit('add', child); | ||||
| @@ -272,6 +271,19 @@ define([ | ||||
|         this.remove(child, true); | ||||
|     }; | ||||
|  | ||||
|     CompositionCollection.prototype._synchronize = function () { | ||||
|         this.mutationListener = this.publicAPI.objects.observe(this.domainObject, '*', (newDomainObject) => { | ||||
|             this.domainObject = JSON.parse(JSON.stringify(newDomainObject)); | ||||
|         }); | ||||
|     }; | ||||
|  | ||||
|     CompositionCollection.prototype._destroy = function () { | ||||
|         if (this.mutationListener) { | ||||
|             this.mutationListener(); | ||||
|             delete this.mutationListener; | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Emit events. | ||||
|      * @private | ||||
|   | ||||
| @@ -48,24 +48,11 @@ define([ | ||||
|         this.listeningTo = {}; | ||||
|         this.onMutation = this.onMutation.bind(this); | ||||
|  | ||||
|         this.cannotContainDuplicates = this.cannotContainDuplicates.bind(this); | ||||
|         this.cannotContainItself = this.cannotContainItself.bind(this); | ||||
|  | ||||
|         compositionAPI.addPolicy(this.cannotContainDuplicates); | ||||
|         compositionAPI.addPolicy(this.cannotContainItself); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @private | ||||
|      */ | ||||
|     DefaultCompositionProvider.prototype.cannotContainDuplicates = function (parent, child) { | ||||
|         return this.appliesTo(parent) && | ||||
|             parent.composition.findIndex((composeeId) => { | ||||
|                 return composeeId.namespace === child.identifier.namespace && | ||||
|                     composeeId.key === child.identifier.key; | ||||
|             }) === -1; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @private | ||||
|      */ | ||||
| @@ -199,9 +186,18 @@ define([ | ||||
|      * @memberof module:openmct.CompositionProvider# | ||||
|      * @method add | ||||
|      */ | ||||
|     DefaultCompositionProvider.prototype.add = function (domainObject, child) { | ||||
|         throw new Error('Default Provider does not implement adding.'); | ||||
|         // TODO: this needs to be synchronized via mutation | ||||
|     DefaultCompositionProvider.prototype.add = function (parent, childId) { | ||||
|         if (!this.includes(parent, childId)) { | ||||
|             parent.composition.push(childId); | ||||
|             this.publicAPI.objects.mutate(parent, 'composition', parent.composition); | ||||
|         } | ||||
|     }; | ||||
|     /** | ||||
|      * @private | ||||
|      */ | ||||
|     DefaultCompositionProvider.prototype.includes = function (parent, childId) { | ||||
|         return parent.composition.findIndex(composee => | ||||
|             this.publicAPI.objects.areIdsEqual(composee, childId)) !== -1; | ||||
|     }; | ||||
|  | ||||
|     DefaultCompositionProvider.prototype.reorder = function (domainObject, oldIndex, newIndex) { | ||||
|   | ||||
| @@ -49,6 +49,9 @@ class ContextMenuAPI { | ||||
|      *           a single sentence or short paragraph) of this kind of view | ||||
|      * @property {string} cssClass the CSS class to apply to labels for this | ||||
|      *           view (to add icons, for instance) | ||||
|      * @property {string} key unique key to identify the context menu action | ||||
|      *           (used in custom context menu eg table rows, to identify which actions to include) | ||||
|      * @property {boolean} hideInDefaultMenu optional flag to hide action from showing in the default context menu (tree item) | ||||
|      */ | ||||
|     /** | ||||
|      * @method appliesTo | ||||
| @@ -72,12 +75,21 @@ class ContextMenuAPI { | ||||
|     /** | ||||
|      * @private | ||||
|      */ | ||||
|     _showContextMenuForObjectPath(objectPath, x, y) { | ||||
|     _showContextMenuForObjectPath(objectPath, x, y, actionsToBeIncluded) { | ||||
|  | ||||
|         let applicableActions = this._allActions.filter((action) => { | ||||
|             if (action.appliesTo === undefined) { | ||||
|                 return true; | ||||
|  | ||||
|             if (actionsToBeIncluded) { | ||||
|                 if (action.appliesTo === undefined && actionsToBeIncluded.includes(action.key)) { | ||||
|                     return true; | ||||
|                 } | ||||
|                 return action.appliesTo(objectPath, actionsToBeIncluded) && actionsToBeIncluded.includes(action.key); | ||||
|             } else { | ||||
|                 if (action.appliesTo === undefined) { | ||||
|                     return true; | ||||
|                 } | ||||
|                 return action.appliesTo(objectPath) && !action.hideInDefaultMenu; | ||||
|             } | ||||
|             return action.appliesTo(objectPath); | ||||
|         }); | ||||
|  | ||||
|         if (this._activeContextMenu) { | ||||
|   | ||||
| @@ -28,7 +28,7 @@ define(['zepto', './res/indicator-template.html'], | ||||
|             this.openmct = openmct; | ||||
|             this.element = $(indicatorTemplate)[0]; | ||||
|  | ||||
|             this.textElement = this.element.querySelector('.indicator-text'); | ||||
|             this.textElement = this.element.querySelector('.js-indicator-text'); | ||||
|  | ||||
|             //Set defaults | ||||
|             this.text('New Indicator'); | ||||
|   | ||||
| @@ -1,3 +1,3 @@ | ||||
| <div class="ls-indicator" title=""> | ||||
|     <span class="label indicator-text"></span> | ||||
| <div class="c-indicator c-indicator--clickable c-indicator--simple" title=""> | ||||
|     <span class="label js-indicator-text c-indicator__label"></span> | ||||
| </div> | ||||
|   | ||||
| @@ -93,7 +93,7 @@ | ||||
|         &.message-severity-error:before { | ||||
|             @include legacyMessage(); | ||||
|             content: $glyph-icon-alert-triangle; | ||||
|             color: $colorWarningLo; | ||||
|             color: $colorWarningHi; | ||||
|         } | ||||
|  | ||||
|         // Messages in a list | ||||
|   | ||||
| @@ -69,6 +69,7 @@ | ||||
|             flex: 1 1 auto; | ||||
|             display: flex; | ||||
|             flex-direction: column; | ||||
|             overflow: hidden; | ||||
|         } | ||||
|  | ||||
|         &__top-bar { | ||||
|   | ||||
| @@ -38,7 +38,7 @@ define([ | ||||
|             canEdit: function (domainObject) { | ||||
|                 return domainObject.type === 'LadTableSet'; | ||||
|             }, | ||||
|             view: function (domainObject) { | ||||
|             view: function (domainObject, isEditing, objectPath) { | ||||
|                 let component; | ||||
|  | ||||
|                 return { | ||||
| @@ -49,7 +49,8 @@ define([ | ||||
|                             }, | ||||
|                             provide: { | ||||
|                                 openmct, | ||||
|                                 domainObject | ||||
|                                 domainObject, | ||||
|                                 objectPath | ||||
|                             }, | ||||
|                             el: element, | ||||
|                             template: '<lad-table-set></lad-table-set>' | ||||
|   | ||||
| @@ -21,7 +21,7 @@ | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define([ | ||||
|     './components/LadTable.vue', | ||||
|     './components/LADTable.vue', | ||||
|     'vue' | ||||
| ], function ( | ||||
|     LadTableComponent, | ||||
| @@ -38,7 +38,7 @@ define([ | ||||
|             canEdit: function (domainObject) { | ||||
|                 return domainObject.type === 'LadTable'; | ||||
|             }, | ||||
|             view: function (domainObject) { | ||||
|             view: function (domainObject, isEditing, objectPath) { | ||||
|                 let component; | ||||
|  | ||||
|                 return { | ||||
| @@ -49,7 +49,8 @@ define([ | ||||
|                             }, | ||||
|                             provide: { | ||||
|                                 openmct, | ||||
|                                 domainObject | ||||
|                                 domainObject, | ||||
|                                 objectPath | ||||
|                             }, | ||||
|                             el: element, | ||||
|                             template: '<lad-table-component></lad-table-component>' | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
|  | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2018, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
| @@ -21,7 +22,7 @@ | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| <template> | ||||
|     <tr> | ||||
|     <tr @contextmenu.prevent="showContextMenu"> | ||||
|         <td>{{name}}</td> | ||||
|         <td>{{timestamp}}</td> | ||||
|         <td :class="valueClass"> | ||||
| @@ -35,15 +36,25 @@ | ||||
| </style> | ||||
|  | ||||
| <script> | ||||
|  | ||||
| const CONTEXT_MENU_ACTIONS = [ | ||||
|     'viewHistoricalData', | ||||
|     'remove' | ||||
| ]; | ||||
|  | ||||
| export default { | ||||
|     inject: ['openmct'], | ||||
|     inject: ['openmct', 'objectPath'], | ||||
|     props: ['domainObject'], | ||||
|     data() { | ||||
|         let currentObjectPath = this.objectPath.slice(); | ||||
|         currentObjectPath.unshift(this.domainObject); | ||||
|  | ||||
|         return { | ||||
|             name: this.domainObject.name, | ||||
|             timestamp: '---', | ||||
|             value: '---', | ||||
|             valueClass: '' | ||||
|             valueClass: '', | ||||
|             currentObjectPath | ||||
|         } | ||||
|     }, | ||||
|     methods: { | ||||
| @@ -73,11 +84,15 @@ export default { | ||||
|                 .request(this.domainObject, {strategy: 'latest'}) | ||||
|                 .then((array) => this.updateValues(array[array.length - 1])); | ||||
|  | ||||
|         }, | ||||
|         showContextMenu(event) { | ||||
|            this.openmct.contextMenu._showContextMenuForObjectPath(this.currentObjectPath, event.x, event.y, CONTEXT_MENU_ACTIONS); | ||||
|         } | ||||
|     }, | ||||
|     mounted() { | ||||
|         this.metadata = this.openmct.telemetry.getMetadata(this.domainObject); | ||||
|         this.formats = this.openmct.telemetry.getFormatMap(this.metadata); | ||||
|         this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier); | ||||
|  | ||||
|         this.limitEvaluator = openmct | ||||
|             .telemetry | ||||
|   | ||||
| @@ -41,10 +41,10 @@ | ||||
|  | ||||
| <script> | ||||
| import lodash from 'lodash'; | ||||
| import LadRow from './LadRow.vue'; | ||||
| import LadRow from './LADRow.vue'; | ||||
|  | ||||
| export default { | ||||
|     inject: ['openmct', 'domainObject'], | ||||
|     inject: ['openmct', 'domainObject', 'objectPath'], | ||||
|     components: { | ||||
|         LadRow | ||||
|     }, | ||||
|   | ||||
| @@ -52,7 +52,7 @@ | ||||
|  | ||||
| <script> | ||||
|     import lodash from 'lodash'; | ||||
|     import LadRow from './LadRow.vue'; | ||||
|     import LadRow from './LADRow.vue'; | ||||
|  | ||||
|     export default { | ||||
|     inject: ['openmct', 'domainObject'], | ||||
|   | ||||
							
								
								
									
										39
									
								
								src/plugins/clearData/clearDataAction.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/plugins/clearData/clearDataAction.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2018, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT 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 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. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| export default class ClearDataAction { | ||||
|     constructor(openmct, appliesToObjects) { | ||||
|         this.name = 'Clear Data'; | ||||
|         this.description = 'Clears current data for object, unsubscribes and resubscribes to data'; | ||||
|  | ||||
|         this._openmct = openmct; | ||||
|         this._appliesToObjects = appliesToObjects; | ||||
|     } | ||||
|     invoke(objectPath) { | ||||
|         this._openmct.objectViews.emit('clearData', objectPath[0]); | ||||
|     } | ||||
|     appliesTo(objectPath) { | ||||
|         let contextualDomainObject = objectPath[0]; | ||||
|  | ||||
|         return this._appliesToObjects.filter(type => contextualDomainObject.type === type).length; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										18
									
								
								src/plugins/clearData/components/globalClearIndicator.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/plugins/clearData/components/globalClearIndicator.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| <template> | ||||
|     <div class="c-indicator c-indicator--clickable icon-session"> | ||||
|         <span class="label c-indicator__label"> | ||||
|             <button @click="globalClearEmit">Clear All Data</button> | ||||
|         </span> | ||||
|     </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| export default { | ||||
|     inject: ['openmct'], | ||||
|     methods: { | ||||
|         globalClearEmit() { | ||||
|             this.openmct.objectViews.emit('clearData'); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										54
									
								
								src/plugins/clearData/plugin.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								src/plugins/clearData/plugin.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2019, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT 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 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. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define([ | ||||
|     './components/globalClearIndicator.vue', | ||||
|     './clearDataAction', | ||||
|     'vue' | ||||
| ], function ( | ||||
|     GlobaClearIndicator, | ||||
|     ClearDataAction, | ||||
|     Vue | ||||
| ) { | ||||
|     return function plugin(appliesToObjects) { | ||||
|         appliesToObjects = appliesToObjects || []; | ||||
|  | ||||
|         return function install(openmct) { | ||||
|             let component = new Vue ({ | ||||
|                     provide: { | ||||
|                         openmct | ||||
|                     }, | ||||
|                     components: { | ||||
|                         GlobalClearIndicator: GlobaClearIndicator.default | ||||
|                     }, | ||||
|                     template: '<GlobalClearIndicator></GlobalClearIndicator>' | ||||
|                 }), | ||||
|                 indicator = { | ||||
|                     element: component.$mount().$el | ||||
|                 }; | ||||
|  | ||||
|             openmct.indicators.add(indicator); | ||||
|  | ||||
|             openmct.contextMenu.registerAction(new ClearDataAction.default(openmct, appliesToObjects)); | ||||
|         }; | ||||
|     }; | ||||
| }); | ||||
							
								
								
									
										62
									
								
								src/plugins/clearData/test/clearDataActionSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								src/plugins/clearData/test/clearDataActionSpec.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,62 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2018, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT 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 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. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| import ClearDataActionPlugin  from '../plugin.js'; | ||||
| import ClearDataAction from '../clearDataAction.js'; | ||||
|  | ||||
| describe('When the Clear Data Plugin is installed,', function () { | ||||
|     var mockObjectViews = jasmine.createSpyObj('objectViews', ['emit']), | ||||
|         mockIndicatorProvider = jasmine.createSpyObj('indicators', ['add']), | ||||
|         mockContextMenuProvider = jasmine.createSpyObj('contextMenu', ['registerAction']), | ||||
|         openmct = { | ||||
|             objectViews: mockObjectViews, | ||||
|             indicators: mockIndicatorProvider, | ||||
|             contextMenu: mockContextMenuProvider, | ||||
|             install: function (plugin) { | ||||
|                 plugin(this); | ||||
|             } | ||||
|         }, | ||||
|         mockObjectPath = [ | ||||
|             {name: 'mockObject1'}, | ||||
|             {name: 'mockObject2'} | ||||
|         ]; | ||||
|  | ||||
|     it('Global Clear Indicator is installed', function () { | ||||
|         openmct.install(ClearDataActionPlugin([])); | ||||
|  | ||||
|         expect(mockIndicatorProvider.add).toHaveBeenCalled(); | ||||
|     }); | ||||
|  | ||||
|     it('Clear Data context menu action is installed', function () { | ||||
|         openmct.install(ClearDataActionPlugin([])); | ||||
|  | ||||
|         expect(mockContextMenuProvider.registerAction).toHaveBeenCalled(); | ||||
|     }); | ||||
|  | ||||
|     it('clear data action emits a clearData event when invoked', function () { | ||||
|         let action = new ClearDataAction(openmct); | ||||
|  | ||||
|         action.invoke(mockObjectPath); | ||||
|  | ||||
|         expect(mockObjectViews.emit).toHaveBeenCalledWith('clearData', mockObjectPath[0]); | ||||
|     }); | ||||
| }); | ||||
							
								
								
									
										78
									
								
								src/plugins/displayLayout/AlphanumericFormatViewProvider.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								src/plugins/displayLayout/AlphanumericFormatViewProvider.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,78 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2018, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT 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 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. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define([ | ||||
|     './components/AlphanumericFormatView.vue', | ||||
|     'vue' | ||||
| ], function (AlphanumericFormatView, Vue) { | ||||
|  | ||||
|     function AlphanumericFormatViewProvider(openmct, options) { | ||||
|         function isTelemetryObject(selectionPath) { | ||||
|             let selectedObject = selectionPath[0].context.item; | ||||
|             let parentObject = selectionPath[1].context.item; | ||||
|             return parentObject &&  | ||||
|                 parentObject.type === 'layout' && | ||||
|                 selectedObject && | ||||
|                 openmct.telemetry.isTelemetryObject(selectedObject) && | ||||
|                 !options.showAsView.includes(selectedObject.type) | ||||
|         } | ||||
|  | ||||
|         return { | ||||
|             key: 'alphanumeric-format', | ||||
|             name: 'Alphanumeric Format', | ||||
|             canView: function (selection) { | ||||
|                 if (selection.length === 0 || selection[0].length === 1) { | ||||
|                     return false; | ||||
|                 } | ||||
|  | ||||
|                 return selection.every(isTelemetryObject); | ||||
|             }, | ||||
|             view: function (domainObject, isEditing, objectPath) { | ||||
|                 let component; | ||||
|                 return { | ||||
|                     show: function (element) { | ||||
|                         component = new Vue({ | ||||
|                             provide: { | ||||
|                                 openmct, | ||||
|                                 objectPath | ||||
|                             }, | ||||
|                             components: { | ||||
|                                 AlphanumericFormatView: AlphanumericFormatView.default | ||||
|                             }, | ||||
|                             template: '<alphanumeric-format-view></alphanumeric-format-view>', | ||||
|                             el: element | ||||
|                         }); | ||||
|                     }, | ||||
|                     destroy: function () { | ||||
|                         component.$destroy(); | ||||
|                         component = undefined; | ||||
|                     } | ||||
|                 } | ||||
|             }, | ||||
|             priority: function () { | ||||
|                 return 1; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return AlphanumericFormatViewProvider; | ||||
| }); | ||||
| @@ -0,0 +1,90 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2018, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT 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 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-properties" v-if="isEditing"> | ||||
|         <div class="c-properties__header">Alphanumeric Format</div> | ||||
|         <ul class="c-properties__section"> | ||||
|             <li class="c-properties__row"> | ||||
|                 <div class="c-properties__label" title="Printf formatting for the selected telemetry"> | ||||
|                     <label for="telemetryPrintfFormat">Format</label> | ||||
|                 </div> | ||||
|                 <div class="c-properties__value"> | ||||
|                     <input id="telemetryPrintfFormat" | ||||
|                         type="text" | ||||
|                         @change="formatTelemetry" | ||||
|                         :value="telemetryFormat" | ||||
|                         :placeholder="nonMixedFormat ? '' : 'Mixed'" | ||||
|                     > | ||||
|                 </div> | ||||
|             </li> | ||||
|         </ul> | ||||
|     </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
|     export default { | ||||
|         inject: ['openmct'], | ||||
|         data() { | ||||
|             let selectionPath = this.openmct.selection.get()[0]; | ||||
|             return { | ||||
|                 isEditing: this.openmct.editor.isEditing(), | ||||
|                 telemetryFormat: undefined, | ||||
|                 nonMixedFormat: false | ||||
|             } | ||||
|         }, | ||||
|         methods: { | ||||
|             toggleEdit(isEditing) { | ||||
|                 this.isEditing = isEditing; | ||||
|             }, | ||||
|             formatTelemetry(event) { | ||||
|                 let newFormat = event.currentTarget.value; | ||||
|                 this.openmct.selection.get().forEach(selectionPath => { | ||||
|                     selectionPath[0].context.updateTelemetryFormat(newFormat);     | ||||
|                 }); | ||||
|                 this.telemetryFormat = newFormat; | ||||
|             }, | ||||
|             handleSelection(selection) { | ||||
|                 if (selection.length === 0 || selection[0].length < 2) { | ||||
|                     return; | ||||
|                 } | ||||
|  | ||||
|                 let format = selection[0][0].context.layoutItem.format; | ||||
|                 this.nonMixedFormat = selection.every(selectionPath => { | ||||
|                     return selectionPath[0].context.layoutItem.format === format; | ||||
|                 }); | ||||
|  | ||||
|                 this.telemetryFormat = this.nonMixedFormat ? format : ''; | ||||
|             } | ||||
|         }, | ||||
|         mounted() { | ||||
|             this.openmct.editor.on('isEditing', this.toggleEdit); | ||||
|             this.openmct.selection.on('change', this.handleSelection); | ||||
|             this.handleSelection(this.openmct.selection.get()); | ||||
|         }, | ||||
|         destroyed() { | ||||
|             this.openmct.editor.off('isEditing', this.toggleEdit); | ||||
|             this.openmct.selection.off('change', this.handleSelection); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| </script> | ||||
| @@ -48,7 +48,8 @@ | ||||
|                    :multiSelect="selectedLayoutItems.length > 1" | ||||
|                    @move="move" | ||||
|                    @endMove="endMove" | ||||
|                    @endLineResize='endLineResize'> | ||||
|                    @endLineResize='endLineResize' | ||||
|                    @formatChanged='updateTelemetryFormat'> | ||||
|         </component> | ||||
|         <edit-marquee v-if='showMarquee' | ||||
|                       :gridSize="gridSize" | ||||
| @@ -201,7 +202,7 @@ | ||||
|                 return selectionPath && selectionPath.length > 1 && !singleSelectedLine; | ||||
|             } | ||||
|         }, | ||||
|         inject: ['openmct', 'options'], | ||||
|         inject: ['openmct', 'options', 'objectPath'], | ||||
|         props: ['domainObject'], | ||||
|         components: components, | ||||
|         methods: { | ||||
| @@ -557,6 +558,11 @@ | ||||
|                     this.layoutItems.splice(itemIndex, 1); | ||||
|                     this.layoutItems.splice(newIndex, 0, items[itemIndex]); | ||||
|                 } | ||||
|             }, | ||||
|             updateTelemetryFormat(item, format) { | ||||
|                 let index = _.findIndex(this.layoutItems, item); | ||||
|                 item.format = format; | ||||
|                 this.mutate(`configuration.items[${index}]`, item); | ||||
|             } | ||||
|         }, | ||||
|         mounted() { | ||||
|   | ||||
| @@ -27,7 +27,7 @@ | ||||
|                   @endMove="() => $emit('endMove')"> | ||||
|         <object-frame v-if="domainObject" | ||||
|                       :domain-object="domainObject" | ||||
|                       :object-path="objectPath" | ||||
|                       :object-path="currentObjectPath" | ||||
|                       :has-frame="item.hasFrame" | ||||
|                       :show-edit-view="false" | ||||
|                       ref="objectFrame"> | ||||
| @@ -71,7 +71,7 @@ | ||||
|                 hasFrame: hasFrameByDefault(domainObject.type) | ||||
|             }; | ||||
|         }, | ||||
|         inject: ['openmct'], | ||||
|         inject: ['openmct', 'objectPath'], | ||||
|         props: { | ||||
|             item: Object, | ||||
|             gridSize: Array, | ||||
| @@ -81,7 +81,7 @@ | ||||
|         data() { | ||||
|             return { | ||||
|                 domainObject: undefined, | ||||
|                 objectPath: [] | ||||
|                 currentObjectPath: [] | ||||
|             } | ||||
|         }, | ||||
|         components: { | ||||
| @@ -100,7 +100,7 @@ | ||||
|         methods: { | ||||
|             setObject(domainObject) { | ||||
|                 this.domainObject = domainObject; | ||||
|                 this.objectPath = [this.domainObject].concat(this.openmct.router.path); | ||||
|                 this.currentObjectPath = [this.domainObject].concat(this.objectPath.slice()); | ||||
|                 this.$nextTick(function () { | ||||
|                     let childContext = this.$refs.objectFrame.getSelectionContext(); | ||||
|                     childContext.item = domainObject; | ||||
|   | ||||
| @@ -27,7 +27,8 @@ | ||||
|                    @endMove="() => $emit('endMove')"> | ||||
|         <div class="c-telemetry-view" | ||||
|              :style="styleObject" | ||||
|              v-if="domainObject"> | ||||
|              v-if="domainObject" | ||||
|              @contextmenu.prevent="showContextMenu"> | ||||
|             <div v-if="showLabel" | ||||
|                   class="c-telemetry-view__label"> | ||||
|                 <div class="c-telemetry-view__label-text">{{ domainObject.name }}</div> | ||||
| @@ -79,9 +80,11 @@ | ||||
|  | ||||
|  <script> | ||||
|     import LayoutFrame from './LayoutFrame.vue' | ||||
|     import printj from 'printj' | ||||
|  | ||||
|     const DEFAULT_TELEMETRY_DIMENSIONS = [10, 5], | ||||
|           DEFAULT_POSITION = [1, 1]; | ||||
|           DEFAULT_POSITION = [1, 1], | ||||
|           CONTEXT_MENU_ACTIONS = ['viewHistoricalData']; | ||||
|  | ||||
|     export default { | ||||
|         makeDefinition(openmct, gridSize, domainObject, position) { | ||||
| @@ -102,7 +105,7 @@ | ||||
|                 size: "13px" | ||||
|             }; | ||||
|         }, | ||||
|         inject: ['openmct'], | ||||
|         inject: ['openmct', 'objectPath'], | ||||
|         props: { | ||||
|             item: Object, | ||||
|             gridSize: Array, | ||||
| @@ -143,6 +146,10 @@ | ||||
|                     return; | ||||
|                 } | ||||
|  | ||||
|                 if (this.item.format) { | ||||
|                     return printj.sprintf(this.item.format, this.datum[this.valueMetadata.key]); | ||||
|                 } | ||||
|  | ||||
|                 return this.valueFormatter && this.valueFormatter.format(this.datum); | ||||
|             }, | ||||
|             telemetryClass() { | ||||
| @@ -158,7 +165,8 @@ | ||||
|             return { | ||||
|                 datum: undefined, | ||||
|                 formats: undefined, | ||||
|                 domainObject: undefined | ||||
|                 domainObject: undefined, | ||||
|                 currentObjectPath: undefined | ||||
|             } | ||||
|         }, | ||||
|         watch: { | ||||
| @@ -168,6 +176,9 @@ | ||||
|                 } | ||||
|  | ||||
|                 this.context.index = newIndex; | ||||
|             }, | ||||
|             item(newItem) { | ||||
|                 this.context.layoutItem = newItem; | ||||
|             } | ||||
|         }, | ||||
|         methods: { | ||||
| @@ -210,19 +221,30 @@ | ||||
|             }, | ||||
|             setObject(domainObject) { | ||||
|                 this.domainObject = domainObject; | ||||
|                 this.keyString = this.openmct.objects.makeKeyString(domainObject.identifier); | ||||
|                 this.metadata = this.openmct.telemetry.getMetadata(this.domainObject); | ||||
|                 this.limitEvaluator = this.openmct.telemetry.limitEvaluator(this.domainObject); | ||||
|                 this.formats = this.openmct.telemetry.getFormatMap(this.metadata); | ||||
|                 this.requestHistoricalData(); | ||||
|                 this.subscribeToObject(); | ||||
|  | ||||
|                 this.currentObjectPath = this.objectPath.slice(); | ||||
|                 this.currentObjectPath.unshift(this.domainObject); | ||||
|  | ||||
|                 this.context = { | ||||
|                     item: domainObject, | ||||
|                     layoutItem: this.item, | ||||
|                     index: this.index | ||||
|                     index: this.index, | ||||
|                     updateTelemetryFormat: this.updateTelemetryFormat | ||||
|                 }; | ||||
|                 this.removeSelectable = this.openmct.selection.selectable( | ||||
|                     this.$el, this.context, this.initSelect); | ||||
|             }, | ||||
|             updateTelemetryFormat(format) { | ||||
|                 this.$emit('formatChanged', this.item, format); | ||||
|             }, | ||||
|             showContextMenu(event) { | ||||
|                 this.openmct.contextMenu._showContextMenuForObjectPath(this.currentObjectPath, event.x, event.y, CONTEXT_MENU_ACTIONS); | ||||
|             } | ||||
|         }, | ||||
|         mounted() { | ||||
|   | ||||
| @@ -25,6 +25,8 @@ import Vue from 'vue' | ||||
| import objectUtils from '../../api/objects/object-utils.js' | ||||
| import DisplayLayoutType from './DisplayLayoutType.js' | ||||
| import DisplayLayoutToolbar from './DisplayLayoutToolbar.js' | ||||
| import AlphaNumericFormatViewProvider from './AlphanumericFormatViewProvider.js' | ||||
|  | ||||
| export default function DisplayLayoutPlugin(options) { | ||||
|     return function (openmct) { | ||||
|         openmct.objectViews.addProvider({ | ||||
| @@ -35,7 +37,7 @@ export default function DisplayLayoutPlugin(options) { | ||||
|             canEdit: function (domainObject) { | ||||
|                 return domainObject.type === 'layout'; | ||||
|             }, | ||||
|             view: function (domainObject) { | ||||
|             view: function (domainObject, isEditing, objectPath) { | ||||
|                 let component; | ||||
|                 return { | ||||
|                     show(container) { | ||||
| @@ -47,13 +49,14 @@ export default function DisplayLayoutPlugin(options) { | ||||
|                             provide: { | ||||
|                                 openmct, | ||||
|                                 objectUtils, | ||||
|                                 options | ||||
|                                 options, | ||||
|                                 objectPath | ||||
|                             }, | ||||
|                             el: container, | ||||
|                             data () { | ||||
|                                 return { | ||||
|                                     domainObject: domainObject | ||||
|                                 } | ||||
|                                 }; | ||||
|                             } | ||||
|                         }); | ||||
|                     }, | ||||
| @@ -76,7 +79,8 @@ export default function DisplayLayoutPlugin(options) { | ||||
|             } | ||||
|         }); | ||||
|         openmct.types.addType('layout', DisplayLayoutType()); | ||||
|         openmct.toolbars.addProvider(new DisplayLayoutToolbar(openmct)); | ||||
|         openmct.toolbars.addProvider(new DisplayLayoutToolbar(openmct, options)); | ||||
|         openmct.inspectorViews.addProvider(new AlphaNumericFormatViewProvider(openmct, options)); | ||||
|         openmct.composition.addPolicy((parent, child) => { | ||||
|             if (parent.type === 'layout' && child.type === 'folder') { | ||||
|                 return false; | ||||
|   | ||||
| @@ -1,19 +1,20 @@ | ||||
| <template> | ||||
|     <div class="u-contents c-filter-settings"> | ||||
|         <li class="grid-row c-filter-settings__setting" | ||||
|     <div class="c-properties__section c-filter-settings"> | ||||
|         <li class="c-properties__row c-filter-settings__setting" | ||||
|             v-for="(filter, index) in filterField.filters" | ||||
|             :key="index"> | ||||
|             <div class="grid-cell label"> | ||||
|             <div class="c-properties__label label" | ||||
|                  :disabled="useGlobal"> | ||||
|                 {{ filterField.name }} = | ||||
|             </div> | ||||
|             <div class="grid-cell value"> | ||||
|             <div class="c-properties__value value"> | ||||
|                 <!-- EDITING --> | ||||
|                 <!-- String input, editing --> | ||||
|                 <template v-if="!filter.possibleValues && isEditing"> | ||||
|                     <input class="c-input--flex" | ||||
|                            type="text" | ||||
|                            placeholder="Enter Value" | ||||
|                            :id="`${filter}filterControl`" | ||||
|                            :disabled="useGlobal" | ||||
|                            :value="persistedValue(filter)" | ||||
|                            @change="updateFilterValue($event, filter)"> | ||||
|                 </template> | ||||
| @@ -21,15 +22,16 @@ | ||||
|                 <!-- Checkbox list, editing --> | ||||
|                 <template v-if="filter.possibleValues && isEditing"> | ||||
|                     <div class="c-checkbox-list__row" | ||||
|                          v-for="value in filter.possibleValues" | ||||
|                          :key="value"> | ||||
|                          v-for="option in filter.possibleValues" | ||||
|                          :key="option.value"> | ||||
|                         <input class="c-checkbox-list__input" | ||||
|                                type="checkbox" | ||||
|                                :id="`${value}filterControl`" | ||||
|                                @change="onUserSelect($event, filter.comparator, value)" | ||||
|                                :checked="isChecked(filter.comparator, value)"> | ||||
|                                :id="`${option.value}filterControl`" | ||||
|                                :disabled="useGlobal" | ||||
|                                @change="updateFilterValue($event, filter.comparator, option.value)" | ||||
|                                :checked="isChecked(filter.comparator, option.value)"> | ||||
|                         <span class="c-checkbox-list__value"> | ||||
|                             {{ value }} | ||||
|                             {{ option.label }} | ||||
|                         </span> | ||||
|                     </div> | ||||
|                 </template> | ||||
| @@ -42,9 +44,8 @@ | ||||
|  | ||||
|                 <!-- Checkbox list, NOT editing --> | ||||
|                 <template v-if="filter.possibleValues && !isEditing"> | ||||
|                     <span  | ||||
|                         v-if="persistedFilters[filter.comparator]"> | ||||
|                         {{persistedFilters[filter.comparator].join(', ')}} | ||||
|                     <span v-if="persistedFilters[filter.comparator]"> | ||||
|                         {{ getFilterLabels(filter) }} | ||||
|                     </span> | ||||
|                 </template> | ||||
|             </div> | ||||
| @@ -52,26 +53,14 @@ | ||||
|     </div> | ||||
| </template> | ||||
|  | ||||
| <style lang="scss"> | ||||
|     @import "~styles/sass-base"; | ||||
|  | ||||
|     .c-filter-settings { | ||||
|         &__setting { | ||||
|             .grid-cell.label { | ||||
|                 white-space: nowrap; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| </style> | ||||
|  | ||||
|  | ||||
| <script> | ||||
| export default { | ||||
|     inject: [ | ||||
|         'openmct' | ||||
|     ], | ||||
|     props: { | ||||
|         filterField: Object,  | ||||
|         filterField: Object, | ||||
|         useGlobal: Boolean, | ||||
|         persistedFilters: { | ||||
|             type: Object, | ||||
|             default: () => { | ||||
| @@ -81,7 +70,6 @@ export default { | ||||
|     }, | ||||
|     data() { | ||||
|         return { | ||||
|             expanded: false, | ||||
|             isEditing: this.openmct.editor.isEditing() | ||||
|         } | ||||
|     }, | ||||
| @@ -89,9 +77,6 @@ export default { | ||||
|         toggleIsEditing(isEditing) { | ||||
|             this.isEditing = isEditing; | ||||
|         }, | ||||
|         onUserSelect(event, comparator, value){ | ||||
|             this.$emit('onUserSelect', this.filterField.key, comparator, value, event.target.checked); | ||||
|         }, | ||||
|         isChecked(comparator, value) { | ||||
|             if (this.persistedFilters[comparator] && this.persistedFilters[comparator].includes(value)) { | ||||
|                 return true; | ||||
| @@ -102,8 +87,25 @@ export default { | ||||
|         persistedValue(comparator) { | ||||
|             return this.persistedFilters && this.persistedFilters[comparator]; | ||||
|         }, | ||||
|         updateFilterValue(event, comparator) { | ||||
|             this.$emit('onTextEnter', this.filterField.key, comparator, event.target.value); | ||||
|         updateFilterValue(event, comparator, value) { | ||||
|             if (value !== undefined) { | ||||
|                 this.$emit('filterSelected', this.filterField.key, comparator, value, event.target.checked); | ||||
|             } else { | ||||
|                 this.$emit('filterTextValueChanged', this.filterField.key, comparator, event.target.value); | ||||
|             } | ||||
|         }, | ||||
|         getFilterLabels(filter) { | ||||
|             return this.persistedFilters[filter.comparator].reduce((accum, filterValue) => { | ||||
|                 accum.push(filter.possibleValues.reduce((label, possibleValue) => { | ||||
|                     if (filterValue === possibleValue.value) { | ||||
|                         label = possibleValue.label; | ||||
|                     } | ||||
|  | ||||
|                     return label; | ||||
|                 }, '')); | ||||
|  | ||||
|                 return accum; | ||||
|             }, []).join(', '); | ||||
|         } | ||||
|     }, | ||||
|     mounted() { | ||||
|   | ||||
| @@ -1,10 +1,12 @@ | ||||
| <template> | ||||
|     <li> | ||||
|     <li class="c-tree__item-h"> | ||||
|         <div class="c-tree__item menus-to-left" | ||||
|              @click="toggleExpanded"> | ||||
|             <div class="c-filter-tree-item__filter-indicator" | ||||
|                 :class="{'icon-filter': hasActiveFilters }"></div> | ||||
|             <span class="c-disclosure-triangle is-enabled flex-elem" | ||||
|               :class="{'c-disclosure-triangle--expanded': expanded}"></span> | ||||
|             <div class="c-tree__item__label"> | ||||
|             <div class="c-tree__item__label c-object-label"> | ||||
|                 <div class="c-object-label"> | ||||
|                     <div class="c-object-label__type-icon" | ||||
|                          :class="objectCssClass"> | ||||
| @@ -13,30 +15,47 @@ | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|         <ul class="grid-properties" v-if="expanded"> | ||||
|             <filter-field | ||||
|                     v-for="field in filterObject.valuesWithFilters" | ||||
|                     :key="field.key" | ||||
|                     :filterField="field" | ||||
|                     :persistedFilters="persistedFilters[field.key]" | ||||
|                     @onUserSelect="collectUserSelects" | ||||
|                     @onTextEnter="updateTextFilter"> | ||||
|             </filter-field> | ||||
|         </ul> | ||||
|  | ||||
|         <div v-if="expanded"> | ||||
|             <ul class="c-properties"> | ||||
|                 <div class="c-properties__label span-all" | ||||
|                      v-if="!isEditing && persistedFilters.useGlobal"> | ||||
|                     Uses global filter | ||||
|                 </div> | ||||
|  | ||||
|                 <div class="c-properties__label span-all" | ||||
|                      v-if="isEditing"> | ||||
|                     <toggle-switch | ||||
|                             :id="keyString" | ||||
|                             @change="useGlobalFilter" | ||||
|                             :checked="persistedFilters.useGlobal"> | ||||
|                     </toggle-switch> | ||||
|                     Use global filter | ||||
|                 </div> | ||||
|                 <filter-field | ||||
|                         v-if="(!persistedFilters.useGlobal && !isEditing) || isEditing" | ||||
|                         v-for="metadatum in filterObject.metadataWithFilters" | ||||
|                         :key="metadatum.key" | ||||
|                         :filterField="metadatum" | ||||
|                         :useGlobal="persistedFilters.useGlobal" | ||||
|                         :persistedFilters="updatedFilters[metadatum.key]" | ||||
|                         @filterSelected="updateFiltersWithSelectedValue" | ||||
|                         @filterTextValueChanged="updateFiltersWithTextValue"> | ||||
|                 </filter-field> | ||||
|             </ul> | ||||
|         </div> | ||||
|     </li> | ||||
| </template> | ||||
|  | ||||
| <style lang="scss"> | ||||
|  | ||||
| </style> | ||||
|  | ||||
| <script> | ||||
| import FilterField from './FilterField.vue'; | ||||
| import ToggleSwitch from '../../../ui/components/ToggleSwitch.vue'; | ||||
|  | ||||
| export default { | ||||
|     inject: ['openmct'], | ||||
|     components: { | ||||
|         FilterField | ||||
|         FilterField, | ||||
|         ToggleSwitch | ||||
|     }, | ||||
|     props: { | ||||
|         filterObject: Object,  | ||||
| @@ -51,44 +70,74 @@ export default { | ||||
|         return { | ||||
|             expanded: false, | ||||
|             objectCssClass: undefined, | ||||
|             updatedFilters: this.persistedFilters | ||||
|             updatedFilters: JSON.parse(JSON.stringify(this.persistedFilters)), | ||||
|             isEditing: this.openmct.editor.isEditing() | ||||
|         } | ||||
|     }, | ||||
|     watch: { | ||||
|         persistedFilters: { | ||||
|             handler: function checkFilters(newpersistedFilters) { | ||||
|                 this.updatedFilters = JSON.parse(JSON.stringify(newpersistedFilters)); | ||||
|             }, | ||||
|             deep: true | ||||
|         } | ||||
|     }, | ||||
|     computed: { | ||||
|         hasActiveFilters() { | ||||
|             // Should be true when the user has entered any filter values. | ||||
|             return Object.values(this.persistedFilters).some(comparator => { | ||||
|                 return (typeof(comparator) === 'object' && !_.isEmpty(comparator)); | ||||
|             }); | ||||
|         } | ||||
|     }, | ||||
|     methods: { | ||||
|         toggleExpanded() { | ||||
|             this.expanded = !this.expanded; | ||||
|         }, | ||||
|         collectUserSelects(key, comparator, valueName, value) { | ||||
|         updateFiltersWithSelectedValue(key, comparator, valueName, value) { | ||||
|             let filterValue = this.updatedFilters[key]; | ||||
|  | ||||
|             if (filterValue && filterValue[comparator]) { | ||||
|                 if (value === false) { | ||||
|                     filterValue[comparator] = filterValue[comparator].filter(v => v !== valueName); | ||||
|                 } else { | ||||
|             if (filterValue[comparator]) { | ||||
|                 if (value === true) { | ||||
|                     filterValue[comparator].push(valueName); | ||||
|                 } else { | ||||
|                     if (filterValue[comparator].length === 1) { | ||||
|                         this.$set(this.updatedFilters, key, {}); | ||||
|                     } else { | ||||
|                         filterValue[comparator] = filterValue[comparator].filter(v => v !== valueName); | ||||
|                     } | ||||
|                 } | ||||
|             } else { | ||||
|                 if (!this.updatedFilters[key]) { | ||||
|                     this.$set(this.updatedFilters, key, {}); | ||||
|                 } | ||||
|                 this.$set(this.updatedFilters[key], comparator, [value ? valueName : undefined]); | ||||
|                 this.$set(this.updatedFilters[key], comparator, [valueName]); | ||||
|             } | ||||
|  | ||||
|             this.$emit('updateFilters', this.keyString, this.updatedFilters); | ||||
|         }, | ||||
|         updateTextFilter(key, comparator, value) { | ||||
|             if (!this.updatedFilters[key]) { | ||||
|         updateFiltersWithTextValue(key, comparator, value) { | ||||
|             if (value.trim() === '') { | ||||
|                 this.$set(this.updatedFilters, key, {}); | ||||
|                 this.$set(this.updatedFilters[key], comparator, ''); | ||||
|             } else { | ||||
|                 this.$set(this.updatedFilters[key], comparator, value); | ||||
|             } | ||||
|             this.updatedFilters[key][comparator] = value; | ||||
|  | ||||
|             this.$emit('updateFilters', this.keyString, this.updatedFilters); | ||||
|         } | ||||
|         }, | ||||
|         useGlobalFilter(checked) { | ||||
|             this.updatedFilters.useGlobal = checked; | ||||
|             this.$emit('updateFilters', this.keyString, this.updatedFilters, checked); | ||||
|         }, | ||||
|         toggleIsEditing(isEditing) { | ||||
|             this.isEditing = isEditing; | ||||
|         }, | ||||
|     }, | ||||
|     mounted() { | ||||
|         let type = this.openmct.types.get(this.filterObject.domainObject.type) || {}; | ||||
|         this.keyString = this.openmct.objects.makeKeyString(this.filterObject.domainObject.identifier); | ||||
|         this.objectCssClass = type.definition.cssClass; | ||||
|         this.openmct.editor.on('isEditing', this.toggleIsEditing); | ||||
|     }, | ||||
|     beforeDestroy() { | ||||
|         this.openmct.editor.off('isEditing', this.toggleIsEditing); | ||||
|     } | ||||
| } | ||||
| </script> | ||||
|   | ||||
| @@ -1,6 +1,14 @@ | ||||
| <template> | ||||
|     <ul class="tree c-tree c-properties__section" v-if="Object.keys(children).length"> | ||||
|         <h2 class="c-properties__header">Filters</h2> | ||||
|     <ul class="c-tree c-filter-tree" v-if="Object.keys(children).length"> | ||||
|         <h2>Data Filters</h2> | ||||
|         <div class="c-filter-indication" | ||||
|             v-if="hasActiveFilters">{{ label }} | ||||
|         </div> | ||||
|         <global-filters | ||||
|             :globalFilters="globalFilters" | ||||
|             :globalMetadata="globalMetadata" | ||||
|             @persistGlobalFilters="persistGlobalFilters"> | ||||
|         </global-filters> | ||||
|         <filter-object  | ||||
|             v-for="(child, key) in children" | ||||
|             :key="key" | ||||
| @@ -12,76 +20,230 @@ | ||||
| </template> | ||||
|  | ||||
| <style lang="scss"> | ||||
|     @import "~styles/sass-base"; | ||||
|  | ||||
|     .c-inspector { | ||||
|         .c-filter-indication { | ||||
|             border-radius: $smallCr; | ||||
|             font-size: inherit; | ||||
|             padding: $interiorMarginSm $interiorMargin; | ||||
|             text-transform: inherit; | ||||
|         } | ||||
|         .c-filter-tree { | ||||
|             // Filters UI uses a tree-based structure | ||||
|             .c-properties { | ||||
|                 // Add extra margin to account for filter-indicator | ||||
|                 margin-left: 38px; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| </style> | ||||
|  | ||||
| <script> | ||||
| import FilterObject from './FilterObject.vue'; | ||||
|     import FilterObject from './FilterObject.vue'; | ||||
|     import GlobalFilters from './GlobalFilters.vue' | ||||
|  | ||||
| export default { | ||||
|     components: { | ||||
|         FilterObject | ||||
|     }, | ||||
|     inject: [ | ||||
|         'openmct' | ||||
|     ], | ||||
|     data() { | ||||
|         let providedObject = this.openmct.selection.get()[0][0].context.item; | ||||
|         let persistedFilters = {}; | ||||
|     const FILTER_VIEW_TITLE = 'Filters applied'; | ||||
|     const FILTER_VIEW_TITLE_MIXED = 'Mixed filters applied'; | ||||
|     const USE_GLOBAL = 'useGlobal'; | ||||
|  | ||||
|         if (providedObject.configuration && providedObject.configuration.filters) { | ||||
|             persistedFilters = providedObject.configuration.filters; | ||||
|         } | ||||
|     export default { | ||||
|         components: { | ||||
|             FilterObject, | ||||
|             GlobalFilters | ||||
|         }, | ||||
|         inject: [ | ||||
|             'openmct' | ||||
|         ], | ||||
|         data() { | ||||
|             let providedObject = this.openmct.selection.get()[0][0].context.item; | ||||
|             let configuration = providedObject.configuration; | ||||
|  | ||||
|         return { | ||||
|             providedObject, | ||||
|             persistedFilters, | ||||
|             children: {} | ||||
|         } | ||||
|     }, | ||||
|     methods: { | ||||
|         addChildren(child) { | ||||
|             let keyString = this.openmct.objects.makeKeyString(child.identifier), | ||||
|                 metadata = this.openmct.telemetry.getMetadata(child), | ||||
|                 valuesWithFilters = metadata.valueMetadatas.filter((value) => value.filters), | ||||
|                 childObject = { | ||||
|                     name: child.name, | ||||
|                     domainObject: child, | ||||
|                     valuesWithFilters | ||||
|                 }; | ||||
|  | ||||
|             if (childObject.valuesWithFilters.length) { | ||||
|                 this.$set(this.children, keyString, childObject); | ||||
|             } else { | ||||
|                 return; | ||||
|             return { | ||||
|                 persistedFilters: (configuration && configuration.filters) || {}, | ||||
|                 globalFilters: (configuration && configuration.globalFilters) || {}, | ||||
|                 globalMetadata: {}, | ||||
|                 providedObject, | ||||
|                 children: {} | ||||
|             } | ||||
|         }, | ||||
|         removeChildren(identifier) { | ||||
|             let keyString = this.openmct.objects.makeKeyString(identifier); | ||||
|             this.$delete(this.children, keyString); | ||||
|             this.persistFilters(keyString); | ||||
|         computed: { | ||||
|             hasActiveFilters() { | ||||
|                 // Should be true when the user has entered any filter values. | ||||
|                 return Object.values(this.persistedFilters).some(filters => { | ||||
|                     return Object.values(filters).some(comparator => { | ||||
|                         return (typeof(comparator) === 'object' && !_.isEmpty(comparator)); | ||||
|                     }); | ||||
|                 }); | ||||
|             }, | ||||
|             hasMixedFilters() { | ||||
|                 // Should be true when filter values are mixed. | ||||
|                 let filtersToCompare = _.omit(this.persistedFilters[Object.keys(this.persistedFilters)[0]], [USE_GLOBAL]); | ||||
|                 return Object.values(this.persistedFilters).some(filters => { | ||||
|                     return !_.isEqual(filtersToCompare, _.omit(filters, [USE_GLOBAL])); | ||||
|                 }); | ||||
|             }, | ||||
|             label() { | ||||
|                 if (this.hasActiveFilters) { | ||||
|                     if (this.hasMixedFilters) { | ||||
|                         return FILTER_VIEW_TITLE_MIXED; | ||||
|                     } else { | ||||
|                         return FILTER_VIEW_TITLE; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         persistFilters(keyString, userSelects) { | ||||
|             this.persistedFilters[keyString] = userSelects; | ||||
|             this.openmct.objects.mutate(this.providedObject, 'configuration.filters', this.persistedFilters); | ||||
|         methods: { | ||||
|             addChildren(domainObject) { | ||||
|                 let keyString = this.openmct.objects.makeKeyString(domainObject.identifier); | ||||
|                 let metadata = this.openmct.telemetry.getMetadata(domainObject); | ||||
|                 let metadataWithFilters = metadata.valueMetadatas.filter(value => value.filters); | ||||
|                 let hasFiltersWithKeyString = this.persistedFilters[keyString] !== undefined; | ||||
|                 let mutateFilters = false; | ||||
|                 let childObject = { | ||||
|                     name: domainObject.name, | ||||
|                     domainObject: domainObject, | ||||
|                     metadataWithFilters | ||||
|                 }; | ||||
|  | ||||
|                 if (metadataWithFilters.length) { | ||||
|                     this.$set(this.children, keyString, childObject); | ||||
|  | ||||
|                     metadataWithFilters.forEach(metadatum => { | ||||
|                         if (!this.globalFilters[metadatum.key]) { | ||||
|                             this.$set(this.globalFilters, metadatum.key, {}); | ||||
|                         } | ||||
|  | ||||
|                         if (!this.globalMetadata[metadatum.key]) { | ||||
|                             this.$set(this.globalMetadata, metadatum.key, metadatum); | ||||
|                         } | ||||
|  | ||||
|                         if (!hasFiltersWithKeyString) { | ||||
|                             if (!this.persistedFilters[keyString]) { | ||||
|                                 this.$set(this.persistedFilters, keyString, {}); | ||||
|                                 this.$set(this.persistedFilters[keyString], 'useGlobal', true); | ||||
|                                 mutateFilters = true; | ||||
|                             } | ||||
|  | ||||
|                             this.$set(this.persistedFilters[keyString], metadatum.key, this.globalFilters[metadatum.key]); | ||||
|                         } | ||||
|                     }); | ||||
|                 } | ||||
|  | ||||
|                 if (mutateFilters) { | ||||
|                     this.mutateConfigurationFilters(); | ||||
|                 } | ||||
|             }, | ||||
|             removeChildren(identifier) { | ||||
|                 let keyString = this.openmct.objects.makeKeyString(identifier); | ||||
|                 let globalFiltersToRemove = this.getGlobalFiltersToRemove(keyString); | ||||
|  | ||||
|                 if (globalFiltersToRemove.length > 0) { | ||||
|                     globalFiltersToRemove.forEach(key => { | ||||
|                         this.$delete(this.globalFilters, key); | ||||
|                         this.$delete(this.globalMetadata, key); | ||||
|                     }); | ||||
|                     this.mutateConfigurationGlobalFilters(); | ||||
|                 } | ||||
|  | ||||
|                 this.$delete(this.children, keyString); | ||||
|                 this.$delete(this.persistedFilters, keyString); | ||||
|                 this.mutateConfigurationFilters(); | ||||
|             }, | ||||
|             getGlobalFiltersToRemove(keyString) { | ||||
|                 let filtersToRemove = new Set(); | ||||
|  | ||||
|                 this.children[keyString].metadataWithFilters.forEach(metadatum => { | ||||
|                     let keepFilter = false | ||||
|                     Object.keys(this.children).forEach(childKeyString => { | ||||
|                         if (childKeyString !== keyString) { | ||||
|                             let filterMatched = this.children[childKeyString].metadataWithFilters.some(childMetadatum => childMetadatum.key === metadatum.key); | ||||
|  | ||||
|                             if (filterMatched) { | ||||
|                                 keepFilter = true; | ||||
|                                 return; | ||||
|                             } | ||||
|                         } | ||||
|                     }); | ||||
|  | ||||
|                     if (!keepFilter) { | ||||
|                         filtersToRemove.add(metadatum.key); | ||||
|                     } | ||||
|                 }); | ||||
|  | ||||
|                 return Array.from(filtersToRemove); | ||||
|             }, | ||||
|             persistFilters(keyString, updatedFilters, useGlobalValues) { | ||||
|                 this.persistedFilters[keyString] = updatedFilters; | ||||
|  | ||||
|                 if (useGlobalValues) { | ||||
|                     Object.keys(this.persistedFilters[keyString]).forEach(key => { | ||||
|                         if (typeof(this.persistedFilters[keyString][key]) === 'object') { | ||||
|                             this.persistedFilters[keyString][key]  = this.globalFilters[key]; | ||||
|                         } | ||||
|                     }); | ||||
|                 } | ||||
|              | ||||
|                 this.mutateConfigurationFilters(); | ||||
|             }, | ||||
|             updatePersistedFilters(filters) { | ||||
|                 this.persistedFilters = filters; | ||||
|             }, | ||||
|             persistGlobalFilters(key, filters) { | ||||
|                 this.globalFilters[key] = filters[key]; | ||||
|                 this.mutateConfigurationGlobalFilters(); | ||||
|                 let mutateFilters = false; | ||||
|  | ||||
|                 Object.keys(this.children).forEach(keyString => { | ||||
|                     if (this.persistedFilters[keyString].useGlobal !== false && this.containsField(keyString, key)) { | ||||
|                         if (!this.persistedFilters[keyString][key]) { | ||||
|                             this.$set(this.persistedFilters[keyString], key, {}); | ||||
|                         } | ||||
|  | ||||
|                         this.$set(this.persistedFilters[keyString], key, filters[key]); | ||||
|                         mutateFilters = true; | ||||
|                     } | ||||
|                 }); | ||||
|  | ||||
|                 if (mutateFilters) { | ||||
|                     this.mutateConfigurationFilters(); | ||||
|                 } | ||||
|             }, | ||||
|             updateGlobalFilters(filters) { | ||||
|                 this.globalFilters = filters; | ||||
|             }, | ||||
|             containsField(keyString, field) { | ||||
|                 let hasField = false; | ||||
|                 this.children[keyString].metadataWithFilters.forEach(metadatum => { | ||||
|                     if (metadatum.key === field) { | ||||
|                         hasField = true; | ||||
|                         return; | ||||
|                     } | ||||
|                 }); | ||||
|                 return hasField; | ||||
|             }, | ||||
|             mutateConfigurationFilters() { | ||||
|                 this.openmct.objects.mutate(this.providedObject, 'configuration.filters', this.persistedFilters); | ||||
|             }, | ||||
|             mutateConfigurationGlobalFilters() { | ||||
|                 this.openmct.objects.mutate(this.providedObject, 'configuration.globalFilters', this.globalFilters); | ||||
|             } | ||||
|         }, | ||||
|         updatePersistedFilters(filters) { | ||||
|             this.persistedFilters = filters; | ||||
|         mounted(){ | ||||
|             this.composition = this.openmct.composition.get(this.providedObject); | ||||
|             this.composition.on('add', this.addChildren); | ||||
|             this.composition.on('remove', this.removeChildren); | ||||
|             this.composition.load(); | ||||
|             this.unobserve = this.openmct.objects.observe(this.providedObject, 'configuration.filters', this.updatePersistedFilters); | ||||
|             this.unobserveGlobalFilters = this.openmct.objects.observe(this.providedObject, 'configuration.globalFilters', this.updateGlobalFilters); | ||||
|             this.unobserveAllMutation = this.openmct.objects.observe(this.providedObject, '*', (mutatedObject) => this.providedObject = mutatedObject); | ||||
|         }, | ||||
|         beforeDestroy() { | ||||
|             this.composition.off('add', this.addChildren); | ||||
|             this.composition.off('remove', this.removeChildren); | ||||
|             this.unobserve(); | ||||
|             this.unobserveGlobalFilters(); | ||||
|             this.unobserveAllMutation(); | ||||
|         } | ||||
|     }, | ||||
|     mounted(){ | ||||
|         this.composition = this.openmct.composition.get(this.providedObject); | ||||
|         this.composition.on('add', this.addChildren); | ||||
|         this.composition.on('remove', this.removeChildren); | ||||
|         this.composition.load(); | ||||
|         this.unobserve = this.openmct.objects.observe(this.providedObject, 'configuration.filters', this.updatePersistedFilters); | ||||
|         this.unobserveAllMutation = this.openmct.objects.observe(this.providedObject, '*', (mutatedObject) => this.providedObject = mutatedObject); | ||||
|     }, | ||||
|     beforeDestroy() { | ||||
|         this.composition.off('add', this.addChildren); | ||||
|         this.composition.off('remove', this.removeChildren); | ||||
|         this.unobserve(); | ||||
|         this.unobserveAllMutation(); | ||||
|     } | ||||
| } | ||||
| </script> | ||||
|   | ||||
							
								
								
									
										134
									
								
								src/plugins/filters/components/GlobalFilters.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								src/plugins/filters/components/GlobalFilters.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,134 @@ | ||||
| <template> | ||||
|     <li class="c-tree__item-h"> | ||||
|         <div class="c-tree__item menus-to-left" | ||||
|             @click="toggleExpanded"> | ||||
|             <div class="c-filter-tree-item__filter-indicator" | ||||
|                 :class="{'icon-filter': hasActiveGlobalFilters }"></div> | ||||
|             <span class="c-disclosure-triangle is-enabled flex-elem" | ||||
|                 :class="{'c-disclosure-triangle--expanded': expanded}"></span> | ||||
|             <div class="c-tree__item__label c-object-label"> | ||||
|                 <div class="c-object-label"> | ||||
|                     <div class="c-object-label__type-icon icon-gear"></div> | ||||
|                     <div class="c-object-label__name flex-elem grows">Global Filtering</div> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|         <ul class="c-properties" v-if="expanded"> | ||||
|             <filter-field | ||||
|                 v-for="metadatum in globalMetadata" | ||||
|                 :key="metadatum.key" | ||||
|                 :filterField="metadatum" | ||||
|                 :persistedFilters="updatedFilters[metadatum.key]" | ||||
|                 @filterSelected="updateFiltersWithSelectedValue" | ||||
|                 @filterTextValueChanged="updateFiltersWithTextValue"> | ||||
|             </filter-field> | ||||
|         </ul> | ||||
|     </li> | ||||
| </template> | ||||
|  | ||||
| <style lang="scss"> | ||||
|     @import "~styles/sass-base"; | ||||
|     .c-filter-indication { | ||||
|         // Appears as a block element beneath tables | ||||
|         @include userSelectNone(); | ||||
|         background: $colorFilterBg; | ||||
|         color: $colorFilterFg; | ||||
|         display: flex; | ||||
|         align-items: center; | ||||
|         font-size: 0.9em; | ||||
|         margin-top: $interiorMarginSm; | ||||
|         padding: 2px; | ||||
|         text-transform: uppercase; | ||||
|  | ||||
|         &:before { | ||||
|             font-family: symbolsfont-12px; | ||||
|             content: $glyph-icon-filter; | ||||
|             display: block; | ||||
|             font-size: 12px; | ||||
|             margin-right: $interiorMarginSm; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     .c-filter-tree-item { | ||||
|         &__filter-indicator { | ||||
|             color: $colorFilter; | ||||
|             width: 1.2em; // Set width explicitly for layout reasons: will either have class icon-filter, or none. | ||||
|         } | ||||
|     } | ||||
| </style> | ||||
|  | ||||
| <script> | ||||
|     import FilterField from './FilterField.vue'; | ||||
|  | ||||
|     export default { | ||||
|         inject: ['openmct'], | ||||
|         components: { | ||||
|             FilterField | ||||
|         }, | ||||
|         props: { | ||||
|             globalMetadata: Object, | ||||
|             globalFilters: { | ||||
|                 type: Object, | ||||
|                 default: () => { | ||||
|                     return {}; | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         data() { | ||||
|             return { | ||||
|                 expanded: false, | ||||
|                 updatedFilters: JSON.parse(JSON.stringify(this.globalFilters)) | ||||
|             } | ||||
|         }, | ||||
|         computed: { | ||||
|             hasActiveGlobalFilters() { | ||||
|                 return Object.values(this.globalFilters).some(field => { | ||||
|                     return Object.values(field).some(comparator => { | ||||
|                         return (comparator && (comparator !== '' || comparator.length > 0)); | ||||
|                     }); | ||||
|                 }); | ||||
|             } | ||||
|         }, | ||||
|         watch: { | ||||
|             globalFilters: { | ||||
|                 handler: function checkFilters(newGlobalFilters) { | ||||
|                     this.updatedFilters = JSON.parse(JSON.stringify(newGlobalFilters)); | ||||
|                 }, | ||||
|                 deep: true | ||||
|             } | ||||
|         }, | ||||
|         methods: { | ||||
|             toggleExpanded() { | ||||
|                 this.expanded = !this.expanded; | ||||
|             }, | ||||
|             updateFiltersWithSelectedValue(key, comparator, valueName, value) { | ||||
|                 let filterValue = this.updatedFilters[key]; | ||||
|  | ||||
|                 if (filterValue[comparator]) { | ||||
|                     if (value === true) { | ||||
|                         filterValue[comparator].push(valueName); | ||||
|                     } else { | ||||
|                         if (filterValue[comparator].length === 1) { | ||||
|                             this.$set(this.updatedFilters, key, {}); | ||||
|                         } else { | ||||
|                             filterValue[comparator] = filterValue[comparator].filter(v => v !== valueName); | ||||
|                         } | ||||
|                     } | ||||
|                 } else { | ||||
|                     this.$set(this.updatedFilters[key], comparator, [valueName]); | ||||
|                 } | ||||
|  | ||||
|                 this.$emit('persistGlobalFilters', key, this.updatedFilters); | ||||
|             }, | ||||
|             updateFiltersWithTextValue(key, comparator, value) { | ||||
|                 if (value.trim() === '') { | ||||
|                     this.$set(this.updatedFilters, key, {}); | ||||
|                 } else { | ||||
|                     this.$set(this.updatedFilters[key], comparator, value); | ||||
|                 } | ||||
|  | ||||
|                 this.$emit('persistGlobalFilters', key, this.updatedFilters); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| </script> | ||||
| @@ -21,7 +21,7 @@ | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define([ | ||||
|     './filtersInspectorViewProvider' | ||||
|     './FiltersInspectorViewProvider' | ||||
| ], function ( | ||||
|     FiltersInspectorViewProvider | ||||
| ) { | ||||
|   | ||||
| @@ -23,6 +23,7 @@ | ||||
| export default class GoToOriginalAction { | ||||
|     constructor(openmct) { | ||||
|         this.name = 'Go To Original'; | ||||
|         this.key = 'goToOriginal'; | ||||
|         this.description = 'Go to the original unlinked instance of this object'; | ||||
|  | ||||
|         this._openmct = openmct; | ||||
|   | ||||
| @@ -115,10 +115,22 @@ | ||||
|                  width: (tickWidth + 30) + 'px' | ||||
|              }"> | ||||
|  | ||||
|             <div class="gl-plot-label gl-plot-y-label"> | ||||
|             <div class="gl-plot-label gl-plot-y-label" ng-if="!yKeyOptions"> | ||||
|                 {{ yAxis.get('label') }} | ||||
|             </div> | ||||
|  | ||||
|             <div class="gl-plot-label gl-plot-y-label" ng-if="yKeyOptions.length > 1 && series.length === 1"> | ||||
|                 <select  class="gl-plot-y-label__select" | ||||
|                          ng-model="yAxisLabel" ng-change="plot.toggleYAxisLabel(yAxisLabel, yKeyOptions, series[0])"> | ||||
|                     <option ng-repeat="option in yKeyOptions" | ||||
|                             value="{{option.name}}" | ||||
|                             ng-selected="option.name === yAxisLabel"> | ||||
|                         {{option.name}} | ||||
|                     </option> | ||||
|                 </select> | ||||
|             </div> | ||||
|              | ||||
|  | ||||
|             <mct-ticks axis="yAxis"> | ||||
|                 <div ng-repeat="tick in ticks track by tick.text" | ||||
|                      class="gl-plot-tick gl-plot-y-tick-label" | ||||
|   | ||||
| @@ -115,11 +115,13 @@ define([ | ||||
|  | ||||
|     Collection.prototype.remove = function (model) { | ||||
|         var index = this.indexOf(model); | ||||
|  | ||||
|         if (index === -1) { | ||||
|             throw new Error('model not found in collection.'); | ||||
|         } | ||||
|         this.models.splice(index, 1); | ||||
|  | ||||
|         this.emit('remove', model, index); | ||||
|         this.models.splice(index, 1); | ||||
|     }; | ||||
|  | ||||
|     Collection.prototype.destroy = function (model) { | ||||
|   | ||||
| @@ -377,6 +377,19 @@ define([ | ||||
|                 delete this.unsubscribe; | ||||
|             } | ||||
|             this.fetch(); | ||||
|         }, | ||||
|  | ||||
|         /** | ||||
|          * Clears the plot series, unsubscribes and resubscribes | ||||
|          * @public | ||||
|          */ | ||||
|         refresh: function () { | ||||
|             this.reset(); | ||||
|             if (this.unsubscribe) { | ||||
|                 this.unsubscribe(); | ||||
|                 delete this.unsubscribe; | ||||
|             } | ||||
|             this.fetch(); | ||||
|         } | ||||
|     }); | ||||
|  | ||||
|   | ||||
| @@ -100,19 +100,33 @@ define([ | ||||
|         removeTelemetryObject: function (identifier) { | ||||
|             var plotObject = this.plot.get('domainObject'); | ||||
|             if (plotObject.type === 'telemetry.plot.overlay') { | ||||
|                 var index = _.findIndex(plotObject.configuration.series, function (s) { | ||||
|  | ||||
|                 var persistedIndex = _.findIndex(plotObject.configuration.series, function (s) { | ||||
|                     return _.isEqual(identifier, s.identifier); | ||||
|                 }); | ||||
|                 this.remove(this.at(index)); | ||||
|                 // Because this is triggered by a composition change, we have | ||||
|                 // to defer mutation of our plot object, otherwise we might | ||||
|                 // mutate an outdated version of the plotObject. | ||||
|                 setTimeout(function () { | ||||
|                     var newPlotObject = this.plot.get('domainObject'); | ||||
|                     var cSeries = newPlotObject.configuration.series.slice(); | ||||
|                     cSeries.splice(index, 1); | ||||
|                     this.openmct.objects.mutate(newPlotObject, 'configuration.series', cSeries); | ||||
|                 }.bind(this)); | ||||
|  | ||||
|                 var configIndex = _.findIndex(this.models, function (m) { | ||||
|                     return _.isEqual(m.domainObject.identifier, identifier); | ||||
|                 }); | ||||
|  | ||||
|                 /* | ||||
|                     when cancelling out of edit mode, the config store and domain object are out of sync | ||||
|                     thus it is necesarry to check both and remove the models that are no longer in composition | ||||
|                 */ | ||||
|                 if (persistedIndex === -1) { | ||||
|                     this.remove(this.at(configIndex)); | ||||
|                 } else { | ||||
|                     this.remove(this.at(persistedIndex)); | ||||
|                     // Because this is triggered by a composition change, we have | ||||
|                     // to defer mutation of our plot object, otherwise we might | ||||
|                     // mutate an outdated version of the plotObject. | ||||
|                     setTimeout(function () { | ||||
|                         var newPlotObject = this.plot.get('domainObject'); | ||||
|                         var cSeries = newPlotObject.configuration.series.slice(); | ||||
|                         cSeries.splice(persistedIndex, 1); | ||||
|                         this.openmct.objects.mutate(newPlotObject, 'configuration.series', cSeries); | ||||
|                     }.bind(this)); | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         onSeriesAdd: function (series) { | ||||
|   | ||||
| @@ -25,23 +25,11 @@ define([ | ||||
|  | ||||
|     function ConfigStore() { | ||||
|         this.store = {}; | ||||
|         this.tracking = {}; | ||||
|     } | ||||
|  | ||||
|     ConfigStore.prototype.track = function (id) { | ||||
|         if (!this.tracking[id]) { | ||||
|             this.tracking[id] = 0; | ||||
|         } | ||||
|         this.tracking[id] += 1; | ||||
|     }; | ||||
|  | ||||
|     ConfigStore.prototype.untrack = function (id) { | ||||
|         this.tracking[id] -= 1; | ||||
|         if (this.tracking[id] <= 0) { | ||||
|             delete this.tracking[id]; | ||||
|             this.store[id].destroy(); | ||||
|             delete this.store[id]; | ||||
|         } | ||||
|     ConfigStore.prototype.deleteStore = function (id) { | ||||
|         this.store[id].destroy(); | ||||
|         delete this.store[id]; | ||||
|     }; | ||||
|  | ||||
|     ConfigStore.prototype.add = function (id, config) { | ||||
|   | ||||
| @@ -49,7 +49,6 @@ define([ | ||||
|     }; | ||||
|  | ||||
|     PlotOptionsController.prototype.destroy = function () { | ||||
|         configStore.untrack(this.configId); | ||||
|         this.stopListening(); | ||||
|         this.unlisten(); | ||||
|     }; | ||||
| @@ -60,7 +59,7 @@ define([ | ||||
|             this.$timeout(this.setUpScope.bind(this)); | ||||
|             return; | ||||
|         } | ||||
|         configStore.track(this.configId); | ||||
|  | ||||
|         this.config = this.$scope.config = config; | ||||
|         this.$scope.plotSeries = []; | ||||
|  | ||||
|   | ||||
| @@ -93,6 +93,8 @@ define([ | ||||
|         this.$scope.series = this.config.series.models; | ||||
|         this.$scope.legend = this.config.legend; | ||||
|  | ||||
|         this.$scope.yAxisLabel = this.config.yAxis.get('label'); | ||||
|  | ||||
|         this.cursorGuideVertical = this.$element[0].querySelector('.js-cursor-guide--v'); | ||||
|         this.cursorGuideHorizontal = this.$element[0].querySelector('.js-cursor-guide--h'); | ||||
|         this.cursorGuide = false; | ||||
| @@ -103,9 +105,35 @@ define([ | ||||
|         this.listenTo(this.$scope, 'plot:tickWidth', this.onTickWidthChange, this); | ||||
|         this.listenTo(this.$scope, 'plot:highlight:set', this.onPlotHighlightSet, this); | ||||
|         this.listenTo(this.$scope, 'plot:reinitializeCanvas', this.initCanvas, this); | ||||
|  | ||||
|         this.listenTo(this.config.xAxis, 'change:displayRange', this.onXAxisChange, this); | ||||
|         this.listenTo(this.config.yAxis, 'change:displayRange', this.onYAxisChange, this); | ||||
|  | ||||
|         this.setUpYAxisOptions(); | ||||
|     }; | ||||
|  | ||||
|     MCTPlotController.prototype.setUpYAxisOptions = function () { | ||||
|         if (this.$scope.series.length === 1) { | ||||
|             let metadata = this.$scope.series[0].metadata; | ||||
|  | ||||
|             this.$scope.yKeyOptions = metadata | ||||
|                 .valuesForHints(['range']) | ||||
|                 .map(function (o) { | ||||
|                     return { | ||||
|                         name: o.name, | ||||
|                         key: o.key | ||||
|                     }; | ||||
|                 }); | ||||
|  | ||||
|             //  set yAxisLabel if none is set yet | ||||
|             if (this.$scope.yAxisLabel === 'none') { | ||||
|                 let yKey = this.$scope.series[0].model.yKey, | ||||
|                     yKeyModel = this.$scope.yKeyOptions.filter(o => o.key === yKey)[0]; | ||||
|  | ||||
|                 this.$scope.yAxisLabel = yKeyModel.name; | ||||
|             } | ||||
|         } else { | ||||
|             this.$scope.yKeyOptions = undefined; | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     MCTPlotController.prototype.onXAxisChange = function (displayBounds) { | ||||
| @@ -282,11 +310,19 @@ define([ | ||||
|     }; | ||||
|  | ||||
|     MCTPlotController.prototype.zoom = function (zoomDirection, zoomFactor) { | ||||
|         var currentXaxis = this.$scope.xAxis.get('displayRange'), | ||||
|             currentYaxis = this.$scope.yAxis.get('displayRange'); | ||||
|  | ||||
|         // when there is no plot data, the ranges can be undefined | ||||
|         // in which case we should not perform zoom | ||||
|         if (!currentXaxis || !currentYaxis) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         this.freeze(); | ||||
|         this.trackHistory(); | ||||
|         var currentXaxis = this.$scope.xAxis.get('displayRange'), | ||||
|             currentYaxis = this.$scope.yAxis.get('displayRange'), | ||||
|             xAxisDist= (currentXaxis.max - currentXaxis.min) * zoomFactor, | ||||
|  | ||||
|         var xAxisDist= (currentXaxis.max - currentXaxis.min) * zoomFactor, | ||||
|             yAxisDist = (currentYaxis.max - currentYaxis.min) * zoomFactor; | ||||
|  | ||||
|         if (zoomDirection === 'in') { | ||||
| @@ -322,12 +358,19 @@ define([ | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         let xDisplayRange = this.$scope.xAxis.get('displayRange'), | ||||
|             yDisplayRange = this.$scope.yAxis.get('displayRange'); | ||||
|  | ||||
|         // when there is no plot data, the ranges can be undefined | ||||
|         // in which case we should not perform zoom | ||||
|         if (!xDisplayRange || !yDisplayRange) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         this.freeze(); | ||||
|         window.clearTimeout(this.stillZooming); | ||||
|  | ||||
|         let xDisplayRange = this.$scope.xAxis.get('displayRange'), | ||||
|             yDisplayRange = this.$scope.yAxis.get('displayRange'), | ||||
|             xAxisDist = (xDisplayRange.max - xDisplayRange.min), | ||||
|         let xAxisDist = (xDisplayRange.max - xDisplayRange.min), | ||||
|             yAxisDist = (yDisplayRange.max - yDisplayRange.min), | ||||
|             xDistMouseToMax = xDisplayRange.max - this.positionOverPlot.x, | ||||
|             xDistMouseToMin = this.positionOverPlot.x - xDisplayRange.min, | ||||
| @@ -478,5 +521,13 @@ define([ | ||||
|         this.cursorGuide = !this.cursorGuide; | ||||
|     }; | ||||
|  | ||||
|     MCTPlotController.prototype.toggleYAxisLabel = function (label, options, series) { | ||||
|         let yAxisObject = options.filter(o => o.name === label)[0]; | ||||
|  | ||||
|         if (yAxisObject) { | ||||
|             series.emit('change:yKey', yAxisObject.key); | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     return MCTPlotController; | ||||
| }); | ||||
|   | ||||
| @@ -63,8 +63,11 @@ define([ | ||||
|  | ||||
|         $scope.pending = 0; | ||||
|  | ||||
|         this.clearData = this.clearData.bind(this); | ||||
|  | ||||
|         this.listenTo($scope, 'user:viewport:change:end', this.onUserViewportChangeEnd, this); | ||||
|         this.listenTo($scope, '$destroy', this.destroy, this); | ||||
|         this.listenTo($scope, 'clearData', this.clearData); | ||||
|  | ||||
|         this.config = this.getConfig(this.$scope.domainObject); | ||||
|         this.listenTo(this.config.series, 'add', this.addSeries, this); | ||||
| @@ -74,6 +77,7 @@ define([ | ||||
|         this.followTimeConductor(); | ||||
|  | ||||
|         this.newStyleDomainObject = $scope.domainObject.useCapability('adapter'); | ||||
|         this.keyString = this.openmct.objects.makeKeyString(this.newStyleDomainObject.identifier); | ||||
|  | ||||
|         this.filterObserver = this.openmct.objects.observe( | ||||
|             this.newStyleDomainObject, | ||||
| @@ -148,7 +152,6 @@ define([ | ||||
|             }); | ||||
|             configStore.add(configId, config); | ||||
|         } | ||||
|         configStore.track(configId); | ||||
|         return config; | ||||
|     }; | ||||
|  | ||||
| @@ -157,7 +160,8 @@ define([ | ||||
|     }; | ||||
|  | ||||
|     PlotController.prototype.destroy = function () { | ||||
|         configStore.untrack(this.config.id); | ||||
|         configStore.deleteStore(this.config.id); | ||||
|  | ||||
|         this.stopListening(); | ||||
|         if (this.checkForSize) { | ||||
|             clearInterval(this.checkForSize); | ||||
| @@ -263,6 +267,12 @@ define([ | ||||
|         }); | ||||
|     }; | ||||
|  | ||||
|     PlotController.prototype.clearData = function () { | ||||
|         this.config.series.forEach(function (series) { | ||||
|             series.refresh(); | ||||
|         }); | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Export view as JPG. | ||||
|      */ | ||||
|   | ||||
| @@ -23,6 +23,7 @@ | ||||
| define([ | ||||
|     'lodash', | ||||
|     './utcTimeSystem/plugin', | ||||
|     './localTimeSystem/plugin', | ||||
|     '../../example/generator/plugin', | ||||
|     './autoflow/AutoflowTabularPlugin', | ||||
|     './timeConductor/plugin', | ||||
| @@ -42,10 +43,12 @@ define([ | ||||
|     './LADTable/plugin', | ||||
|     './filters/plugin', | ||||
|     './objectMigration/plugin', | ||||
|     './goToOriginalAction/plugin' | ||||
|     './goToOriginalAction/plugin', | ||||
|     './clearData/plugin' | ||||
| ], function ( | ||||
|     _, | ||||
|     UTCTimeSystem, | ||||
|     LocalTimeSystem, | ||||
|     GeneratorPlugin, | ||||
|     AutoflowPlugin, | ||||
|     TimeConductorPlugin, | ||||
| @@ -65,7 +68,8 @@ define([ | ||||
|     LADTable, | ||||
|     Filters, | ||||
|     ObjectMigration, | ||||
|     GoToOriginalAction | ||||
|     GoToOriginalAction, | ||||
|     ClearData | ||||
| ) { | ||||
|     var bundleMap = { | ||||
|         LocalStorage: 'platform/persistence/local', | ||||
| @@ -81,6 +85,7 @@ define([ | ||||
|     }); | ||||
|  | ||||
|     plugins.UTCTimeSystem = UTCTimeSystem; | ||||
|     plugins.LocalTimeSystem = LocalTimeSystem; | ||||
|  | ||||
|     plugins.ImportExport = ImportExport; | ||||
|  | ||||
| @@ -163,6 +168,7 @@ define([ | ||||
|     plugins.Filters = Filters; | ||||
|     plugins.ObjectMigration = ObjectMigration.default; | ||||
|     plugins.GoToOriginalAction = GoToOriginalAction.default; | ||||
|     plugins.ClearData = ClearData; | ||||
|  | ||||
|     return plugins; | ||||
| }); | ||||
|   | ||||
| @@ -23,6 +23,7 @@ | ||||
| export default class RemoveAction { | ||||
|     constructor(openmct) { | ||||
|         this.name = 'Remove'; | ||||
|         this.key = 'remove'; | ||||
|         this.description = 'Remove this object from its containing object.'; | ||||
|         this.cssClass = "icon-trash"; | ||||
|  | ||||
|   | ||||
| @@ -70,16 +70,14 @@ define([ | ||||
|          */ | ||||
|         function onValueInput(event) { | ||||
|             var elem = event.target, | ||||
|                 value = (isNaN(elem.valueAsNumber) ? elem.value : elem.valueAsNumber), | ||||
|                 value = isNaN(Number(elem.value)) ? elem.value : Number(elem.value), | ||||
|                 inputIndex = self.valueInputs.indexOf(elem); | ||||
|  | ||||
|             if (elem.tagName.toUpperCase() === 'INPUT') { | ||||
|                 self.eventEmitter.emit('change', { | ||||
|                     value: value, | ||||
|                     property: 'values[' + inputIndex + ']', | ||||
|                     index: self.index | ||||
|                 }); | ||||
|             } | ||||
|             self.eventEmitter.emit('change', { | ||||
|                 value: value, | ||||
|                 property: 'values[' + inputIndex + ']', | ||||
|                 index: self.index | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         this.listenTo(this.deleteButton, 'click', this.remove, this); | ||||
| @@ -108,8 +106,7 @@ define([ | ||||
|         Object.values(this.selects).forEach(function (select) { | ||||
|             $('.t-configuration', self.domElement).append(select.getDOM()); | ||||
|         }); | ||||
|  | ||||
|         this.listenTo($(this.domElement), 'input', onValueInput); | ||||
|         this.listenTo($('.t-value-inputs', this.domElement), 'input', onValueInput); | ||||
|     } | ||||
|  | ||||
|     Condition.prototype.getDOM = function (container) { | ||||
| @@ -167,7 +164,9 @@ define([ | ||||
|  | ||||
|     /** | ||||
|      * When an operation is selected, create the appropriate value inputs | ||||
|      * and add them to the view | ||||
|      * and add them to the view. If an operation is of type enum, create | ||||
|      * a drop-down menu instead. | ||||
|      * | ||||
|      * @param {string} operation The key of currently selected operation | ||||
|      */ | ||||
|     Condition.prototype.generateValueInputs = function (operation) { | ||||
| @@ -176,25 +175,49 @@ define([ | ||||
|             inputCount, | ||||
|             inputType, | ||||
|             newInput, | ||||
|             index = 0; | ||||
|             index = 0, | ||||
|             emitChange = false; | ||||
|  | ||||
|         inputArea.html(''); | ||||
|         this.valueInputs = []; | ||||
|         this.config.values = []; | ||||
|  | ||||
|         if (evaluator.getInputCount(operation)) { | ||||
|             inputCount = evaluator.getInputCount(operation); | ||||
|             inputType = evaluator.getInputType(operation); | ||||
|  | ||||
|             while (index < inputCount) { | ||||
|                 if (!this.config.values[index]) { | ||||
|                     this.config.values[index] = (inputType === 'number' ? 0 : ''); | ||||
|                 if (inputType === 'select') { | ||||
|                     newInput = $('<select>' + this.generateSelectOptions() + '</select>'); | ||||
|                     emitChange = true; | ||||
|                 } else { | ||||
|                     this.config.values[index] = inputType === 'number' ? 0 : ''; | ||||
|                     newInput = $('<input type = "' + inputType + '" value = "' + this.config.values[index] + '"> </input>'); | ||||
|                 } | ||||
|                 newInput = $('<input type = "' + inputType + '" value = "' + this.config.values[index] + '"> </input>'); | ||||
|  | ||||
|                 this.valueInputs.push(newInput.get(0)); | ||||
|                 inputArea.append(newInput); | ||||
|                 index += 1; | ||||
|             } | ||||
|  | ||||
|             if (emitChange) { | ||||
|                 this.eventEmitter.emit('change', { | ||||
|                     value: Number(newInput[0].options[0].value), | ||||
|                     property: 'values[0]', | ||||
|                     index: this.index | ||||
|                 }); | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     Condition.prototype.generateSelectOptions = function () { | ||||
|         let telemetryMetadata = this.conditionManager.getTelemetryMetadata(this.config.object); | ||||
|         let options = ''; | ||||
|         telemetryMetadata[this.config.key].enumerations.forEach(enumeration => { | ||||
|             options += '<option value="' + enumeration.value + '">'+ enumeration.string + '</option>'; | ||||
|         }); | ||||
|         return options; | ||||
|     }; | ||||
|  | ||||
|     return Condition; | ||||
| }); | ||||
|   | ||||
| @@ -24,7 +24,8 @@ define([], function () { | ||||
|          */ | ||||
|         this.inputTypes = { | ||||
|             number: 'number', | ||||
|             string: 'text' | ||||
|             string: 'text', | ||||
|             enum: 'select' | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
| @@ -34,7 +35,8 @@ define([], function () { | ||||
|          */ | ||||
|         this.inputValidators = { | ||||
|             number: this.validateNumberInput, | ||||
|             string: this.validateStringInput | ||||
|             string: this.validateStringInput, | ||||
|             enum: this.validateNumberInput | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
| @@ -201,7 +203,7 @@ define([], function () { | ||||
|                     return typeof input[0] === 'undefined'; | ||||
|                 }, | ||||
|                 text: 'is undefined', | ||||
|                 appliesTo: ['string', 'number'], | ||||
|                 appliesTo: ['string', 'number', 'enum'], | ||||
|                 inputCount: 0, | ||||
|                 getDescription: function () { | ||||
|                     return ' is undefined'; | ||||
| @@ -212,11 +214,33 @@ define([], function () { | ||||
|                     return typeof input[0] !== 'undefined'; | ||||
|                 }, | ||||
|                 text: 'is defined', | ||||
|                 appliesTo: ['string', 'number'], | ||||
|                 appliesTo: ['string', 'number', 'enum'], | ||||
|                 inputCount: 0, | ||||
|                 getDescription: function () { | ||||
|                     return ' is defined'; | ||||
|                 } | ||||
|             }, | ||||
|             enumValueIs: { | ||||
|                 operation: function (input) { | ||||
|                     return input[0] === input[1]; | ||||
|                 }, | ||||
|                 text: 'is', | ||||
|                 appliesTo: ['enum'], | ||||
|                 inputCount: 1, | ||||
|                 getDescription: function (values) { | ||||
|                     return ' == ' + values[0]; | ||||
|                 } | ||||
|             }, | ||||
|             enumValueIsNot: { | ||||
|                 operation: function (input) { | ||||
|                     return input[0] !== input[1]; | ||||
|                 }, | ||||
|                 text: 'is not', | ||||
|                 appliesTo: ['enum'], | ||||
|                 inputCount: 1, | ||||
|                 getDescription: function (values) { | ||||
|                     return ' != ' + values[0]; | ||||
|                 } | ||||
|             } | ||||
|         }; | ||||
|     } | ||||
| @@ -310,13 +334,16 @@ define([], function () { | ||||
|             validator; | ||||
|  | ||||
|         if (cache[object] && typeof cache[object][key] !== 'undefined') { | ||||
|             telemetryValue = [cache[object][key]]; | ||||
|             let value = cache[object][key]; | ||||
|             telemetryValue = [isNaN(Number(value)) ? value : Number(value)]; | ||||
|         } | ||||
|  | ||||
|         op = this.operations[operation] && this.operations[operation].operation; | ||||
|         input = telemetryValue && telemetryValue.concat(values); | ||||
|         validator = op && this.inputValidators[this.operations[operation].appliesTo[0]]; | ||||
|  | ||||
|         if (op && input && validator) { | ||||
|             if (this.operations[operation].appliesTo.length === 2) { | ||||
|             if (this.operations[operation].appliesTo.length > 1) { | ||||
|                 return (this.validateNumberInput(input) || this.validateStringInput(input)) && op(input); | ||||
|             } else { | ||||
|                 return validator(input) && op(input); | ||||
| @@ -372,7 +399,7 @@ define([], function () { | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Returns true only of the given operation applies to a given type | ||||
|      * Returns true only if the given operation applies to a given type | ||||
|      * @param {string} key The key of the operation | ||||
|      * @param {string} type The value type to query | ||||
|      * @returns {boolean} True if the condition applies, false otherwise | ||||
|   | ||||
| @@ -130,7 +130,9 @@ define ([ | ||||
|         this.telemetryTypesById[objectId] = {}; | ||||
|         Object.values(this.telemetryMetadataById[objectId]).forEach(function (valueMetadata) { | ||||
|             var type; | ||||
|             if (valueMetadata.hints.hasOwnProperty('range')) { | ||||
|             if (valueMetadata.enumerations !== undefined) { | ||||
|                 type = 'enum'; | ||||
|             } else if (valueMetadata.hints.hasOwnProperty('range')) { | ||||
|                 type = 'number'; | ||||
|             } else if (valueMetadata.hints.hasOwnProperty('domain')) { | ||||
|                 type = 'number'; | ||||
| @@ -163,11 +165,18 @@ define ([ | ||||
|      * @param {datum} datum The new data from the telemetry source | ||||
|      * @private | ||||
|      */ | ||||
|     ConditionManager.prototype.handleSubscriptionCallback = function (objId, datum) { | ||||
|         this.subscriptionCache[objId] = datum; | ||||
|     ConditionManager.prototype.handleSubscriptionCallback = function (objId, telemetryDatum) { | ||||
|         this.subscriptionCache[objId] = this.createNormalizedDatum(objId, telemetryDatum); | ||||
|         this.eventEmitter.emit('receiveTelemetry'); | ||||
|     }; | ||||
|  | ||||
|     ConditionManager.prototype.createNormalizedDatum = function (objId, telemetryDatum) { | ||||
|         return Object.values(this.telemetryMetadataById[objId]).reduce((normalizedDatum, metadatum) => { | ||||
|             normalizedDatum[metadatum.key] = telemetryDatum[metadatum.source]; | ||||
|             return normalizedDatum; | ||||
|         }, {}); | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Event handler for an add event in this Summary Widget's composition. | ||||
|      * Sets up subscription handlers and parses its property types. | ||||
| @@ -236,6 +245,7 @@ define ([ | ||||
|                 id.namespace === identifier.namespace; | ||||
|         }); | ||||
|         delete this.compositionObjs[objectId]; | ||||
|         delete this.subscriptionCache[objectId]; | ||||
|         this.subscriptions[objectId](); //unsubscribe from telemetry source | ||||
|         delete this.subscriptions[objectId]; | ||||
|         this.eventEmitter.emit('remove', identifier); | ||||
|   | ||||
| @@ -110,9 +110,11 @@ define([ | ||||
|  | ||||
|         type = self.manager.getTelemetryPropertyType(self.config.object, key); | ||||
|  | ||||
|         self.operationKeys = operations.filter(function (operation) { | ||||
|             return self.evaluator.operationAppliesTo(operation, type); | ||||
|         }); | ||||
|         if (type !== undefined) { | ||||
|             self.operationKeys = operations.filter(function (operation) { | ||||
|                 return self.evaluator.operationAppliesTo(operation, type); | ||||
|             }); | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     OperationSelect.prototype.destroy = function () { | ||||
|   | ||||
| @@ -38,7 +38,7 @@ define([ | ||||
|         return this.openmct.time.getAllTimeSystems().map(function (ts, i) { | ||||
|             return { | ||||
|                 key: ts.key, | ||||
|                 name: 'UTC', | ||||
|                 name: ts.name, | ||||
|                 format: ts.timeFormat, | ||||
|                 hints: { | ||||
|                     domain: i | ||||
| @@ -64,7 +64,7 @@ define([ | ||||
|             // Generally safe assumption is that we have one domain per timeSystem. | ||||
|             values: this.getDomains().concat([ | ||||
|                 { | ||||
|                     name: 'state', | ||||
|                     name: 'State', | ||||
|                     key: 'state', | ||||
|                     source: 'ruleIndex', | ||||
|                     format: 'enum', | ||||
|   | ||||
| @@ -174,7 +174,7 @@ define([ | ||||
|                 return typeof input[0] === 'undefined'; | ||||
|             }, | ||||
|             text: 'is undefined', | ||||
|             appliesTo: ['string', 'number'], | ||||
|             appliesTo: ['string', 'number', 'enum'], | ||||
|             inputCount: 0, | ||||
|             getDescription: function () { | ||||
|                 return ' is undefined'; | ||||
| @@ -185,11 +185,33 @@ define([ | ||||
|                 return typeof input[0] !== 'undefined'; | ||||
|             }, | ||||
|             text: 'is defined', | ||||
|             appliesTo: ['string', 'number'], | ||||
|             appliesTo: ['string', 'number', 'enum'], | ||||
|             inputCount: 0, | ||||
|             getDescription: function () { | ||||
|                 return ' is defined'; | ||||
|             } | ||||
|         }, | ||||
|         enumValueIs: { | ||||
|             operation: function (input) { | ||||
|                 return input[0] === input[1]; | ||||
|             }, | ||||
|             text: 'is', | ||||
|             appliesTo: ['enum'], | ||||
|             inputCount: 1, | ||||
|             getDescription: function (values) { | ||||
|                 return ' == ' + values[0]; | ||||
|             } | ||||
|         }, | ||||
|         enumValueIsNot: { | ||||
|             operation: function (input) { | ||||
|                 return input[0] !== input[1]; | ||||
|             }, | ||||
|             text: 'is not', | ||||
|             appliesTo: ['enum'], | ||||
|             inputCount: 1, | ||||
|             getDescription: function (values) { | ||||
|                 return ' != ' + values[0]; | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|   | ||||
| @@ -37,7 +37,7 @@ define([ | ||||
|             key: 'table-configuration', | ||||
|             name: 'Telemetry Table Configuration', | ||||
|             canView: function (selection) { | ||||
|                 if (selection.length === 0 || selection[0].length === 0) { | ||||
|                 if (selection.length !== 1 || selection[0].length === 0) { | ||||
|                     return false; | ||||
|                 } | ||||
|                 let object = selection[0][0].context.item; | ||||
|   | ||||
| @@ -26,6 +26,7 @@ define([ | ||||
|     './collections/BoundedTableRowCollection', | ||||
|     './collections/FilteredTableRowCollection', | ||||
|     './TelemetryTableRow', | ||||
|     './TelemetryTableColumn', | ||||
|     './TelemetryTableConfiguration' | ||||
| ], function ( | ||||
|     EventEmitter, | ||||
| @@ -33,6 +34,7 @@ define([ | ||||
|     BoundedTableRowCollection, | ||||
|     FilteredTableRowCollection, | ||||
|     TelemetryTableRow, | ||||
|     TelemetryTableColumn, | ||||
|     TelemetryTableConfiguration | ||||
| ) { | ||||
|     class TelemetryTable extends EventEmitter { | ||||
| @@ -47,6 +49,8 @@ define([ | ||||
|             this.telemetryObjects = []; | ||||
|             this.outstandingRequests = 0; | ||||
|             this.configuration = new TelemetryTableConfiguration(domainObject, openmct); | ||||
|             this.paused = false; | ||||
|             this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier); | ||||
|  | ||||
|             this.addTelemetryObject = this.addTelemetryObject.bind(this); | ||||
|             this.removeTelemetryObject = this.removeTelemetryObject.bind(this); | ||||
| @@ -94,8 +98,6 @@ define([ | ||||
|                 this.tableComposition.load().then((composition) => { | ||||
|  | ||||
|                     composition = composition.filter(this.isTelemetryObject); | ||||
|  | ||||
|                     this.configuration.addColumnsForAllObjects(composition); | ||||
|                     composition.forEach(this.addTelemetryObject); | ||||
|  | ||||
|                     this.tableComposition.on('add', this.addTelemetryObject); | ||||
| @@ -105,7 +107,7 @@ define([ | ||||
|         } | ||||
|  | ||||
|         addTelemetryObject(telemetryObject) { | ||||
|             this.configuration.addColumnsForObject(telemetryObject, true); | ||||
|             this.addColumnsForObject(telemetryObject, true); | ||||
|             this.requestDataFor(telemetryObject); | ||||
|             this.subscribeTo(telemetryObject); | ||||
|             this.telemetryObjects.push(telemetryObject); | ||||
| @@ -144,14 +146,17 @@ define([ | ||||
|                     let keyString = this.openmct.objects.makeKeyString(telemetryObject.identifier); | ||||
|                     let columnMap = this.getColumnMapForObject(keyString); | ||||
|                     let limitEvaluator = this.openmct.telemetry.limitEvaluator(telemetryObject); | ||||
|  | ||||
|                     let telemetryRows = telemetryData.map(datum => new TelemetryTableRow(datum, columnMap, keyString, limitEvaluator)); | ||||
|                     this.boundedRows.add(telemetryRows); | ||||
|                     this.processHistoricalData(telemetryData, columnMap, keyString, limitEvaluator); | ||||
|                 }).finally(() => { | ||||
|                     this.decrementOutstandingRequests(); | ||||
|                 }); | ||||
|         } | ||||
|  | ||||
|         processHistoricalData(telemetryData, columnMap, keyString, limitEvaluator) { | ||||
|             let telemetryRows = telemetryData.map(datum => new TelemetryTableRow(datum, columnMap, keyString, limitEvaluator)); | ||||
|             this.boundedRows.add(telemetryRows); | ||||
|         } | ||||
|  | ||||
|         /** | ||||
|          * @private | ||||
|          */ | ||||
| @@ -191,6 +196,19 @@ define([ | ||||
|             }, {}); | ||||
|         } | ||||
|  | ||||
|         addColumnsForObject(telemetryObject) { | ||||
|             let metadataValues = this.openmct.telemetry.getMetadata(telemetryObject).values(); | ||||
|  | ||||
|             metadataValues.forEach(metadatum => { | ||||
|                 let column = this.createColumn(metadatum); | ||||
|                 this.configuration.addSingleColumnForObject(telemetryObject, column); | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         createColumn(metadatum) { | ||||
|             return new TelemetryTableColumn(this.openmct, metadatum); | ||||
|         } | ||||
|  | ||||
|         subscribeTo(telemetryObject) { | ||||
|             let subscribeOptions = this.buildOptionsFromConfiguration(telemetryObject); | ||||
|             let keyString = this.openmct.objects.makeKeyString(telemetryObject.identifier); | ||||
| @@ -202,10 +220,17 @@ define([ | ||||
|                 if (!this.telemetryObjects.includes(telemetryObject)) { | ||||
|                     return; | ||||
|                 } | ||||
|                 this.boundedRows.add(new TelemetryTableRow(datum, columnMap, keyString, limitEvaluator)); | ||||
|  | ||||
|                 if (!this.paused) { | ||||
|                     this.processRealtimeDatum(datum, columnMap, keyString, limitEvaluator); | ||||
|                 } | ||||
|             }, subscribeOptions); | ||||
|         } | ||||
|  | ||||
|         processRealtimeDatum(datum, columnMap, keyString, limitEvaluator) { | ||||
|             this.boundedRows.add(new TelemetryTableRow(datum, columnMap, keyString, limitEvaluator)); | ||||
|         } | ||||
|  | ||||
|         isTelemetryObject(domainObject) { | ||||
|             return domainObject.hasOwnProperty('telemetry'); | ||||
|         } | ||||
| @@ -234,12 +259,24 @@ define([ | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         pause() { | ||||
|             this.paused = true; | ||||
|             this.boundedRows.unsubscribeFromBounds(); | ||||
|         } | ||||
|  | ||||
|         unpause() { | ||||
|             this.paused = false; | ||||
|             this.boundedRows.subscribeToBounds(); | ||||
|             this.refreshData(); | ||||
|         } | ||||
|  | ||||
|         destroy() { | ||||
|             this.boundedRows.destroy(); | ||||
|             this.filteredRows.destroy(); | ||||
|             Object.keys(this.subscriptions).forEach(this.unsubscribe, this); | ||||
|             this.openmct.time.off('bounds', this.refreshData); | ||||
|             this.openmct.time.off('timeSystem', this.refreshData); | ||||
|  | ||||
|             if (this.filterObserver) { | ||||
|                 this.filterObserver(); | ||||
|             } | ||||
|   | ||||
| @@ -21,10 +21,11 @@ | ||||
|  *****************************************************************************/ | ||||
| define(function () { | ||||
|     class TelemetryTableColumn { | ||||
|         constructor (openmct, metadatum) { | ||||
|         constructor (openmct, metadatum, options = {selectable: false}) { | ||||
|             this.metadatum = metadatum; | ||||
|             this.formatter = openmct.telemetry.getValueFormatter(metadatum); | ||||
|             this.titleValue = this.metadatum.name; | ||||
|             this.selectable = options.selectable; | ||||
|         } | ||||
|  | ||||
|         getKey() { | ||||
| @@ -55,8 +56,7 @@ define(function () { | ||||
|                 return formattedValue; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|     }; | ||||
|     } | ||||
|  | ||||
|     return TelemetryTableColumn; | ||||
| }); | ||||
|   | ||||
| @@ -22,9 +22,8 @@ | ||||
|  | ||||
| define([ | ||||
|     'lodash', | ||||
|     'EventEmitter', | ||||
|     './TelemetryTableColumn' | ||||
| ], function (_, EventEmitter, TelemetryTableColumn) { | ||||
|     'EventEmitter' | ||||
| ], function (_, EventEmitter) { | ||||
|  | ||||
|     class TelemetryTableConfiguration extends EventEmitter { | ||||
|         constructor(domainObject, openmct) { | ||||
| @@ -34,7 +33,6 @@ define([ | ||||
|             this.openmct = openmct; | ||||
|             this.columns = {}; | ||||
|  | ||||
|             this.addColumnsForObject = this.addColumnsForObject.bind(this); | ||||
|             this.removeColumnsForObject = this.removeColumnsForObject.bind(this); | ||||
|             this.objectMutated = this.objectMutated.bind(this); | ||||
|             //Make copy of configuration, otherwise change detection is impossible if shared instance is being modified. | ||||
| @@ -48,6 +46,7 @@ define([ | ||||
|             configuration.hiddenColumns = configuration.hiddenColumns || {}; | ||||
|             configuration.columnWidths = configuration.columnWidths || {}; | ||||
|             configuration.columnOrder = configuration.columnOrder || []; | ||||
|             configuration.cellFormat = configuration.cellFormat || {}; | ||||
|             configuration.autosize = configuration.autosize === undefined ? true : configuration.autosize; | ||||
|  | ||||
|             return configuration; | ||||
| @@ -65,26 +64,18 @@ define([ | ||||
|             //Synchronize domain object reference. Duplicate object otherwise change detection becomes impossible. | ||||
|             this.domainObject = object; | ||||
|             //Was it the configuration that changed? | ||||
|             if (!_.eq(object.configuration, this.oldConfiguration)) { | ||||
|             if (object.configuration !== undefined && !_.eq(object.configuration, this.oldConfiguration)) { | ||||
|                 //Make copy of configuration, otherwise change detection is impossible if shared instance is being modified. | ||||
|                 this.oldConfiguration = JSON.parse(JSON.stringify(this.getConfiguration())); | ||||
|                 this.emit('change', object.configuration); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         addColumnsForAllObjects(objects) { | ||||
|             objects.forEach(object => this.addColumnsForObject(object, false)); | ||||
|         } | ||||
|  | ||||
|         addColumnsForObject(telemetryObject) { | ||||
|             let metadataValues = this.openmct.telemetry.getMetadata(telemetryObject).values(); | ||||
|         addSingleColumnForObject(telemetryObject, column, position) { | ||||
|             let objectKeyString = this.openmct.objects.makeKeyString(telemetryObject.identifier); | ||||
|             this.columns[objectKeyString] = []; | ||||
|  | ||||
|             metadataValues.forEach(metadatum => { | ||||
|                 let column = new TelemetryTableColumn(this.openmct, metadatum); | ||||
|                 this.columns[objectKeyString].push(column); | ||||
|             }); | ||||
|             this.columns[objectKeyString] = this.columns[objectKeyString] || []; | ||||
|             position = position || this.columns[objectKeyString].length; | ||||
|             this.columns[objectKeyString].splice(position, 0, column); | ||||
|         } | ||||
|  | ||||
|         removeColumnsForObject(objectIdentifier) { | ||||
|   | ||||
| @@ -29,7 +29,7 @@ define([], function () { | ||||
|             this.limitEvaluator = limitEvaluator; | ||||
|             this.objectKeyString = objectKeyString; | ||||
|         } | ||||
|          | ||||
|  | ||||
|         getFormattedDatum(headers) { | ||||
|             return Object.keys(headers).reduce((formattedDatum, columnKey) => { | ||||
|                 formattedDatum[columnKey] = this.getFormattedValue(columnKey); | ||||
| @@ -42,12 +42,19 @@ define([], function () { | ||||
|             return column && column.getFormattedValue(this.datum[key]); | ||||
|         } | ||||
|  | ||||
|         getRowLimitClass() { | ||||
|             if (!this.rowLimitClass) { | ||||
|         getCellComponentName(key) { | ||||
|             let column = this.columns[key]; | ||||
|             return column && | ||||
|                 column.getCellComponentName && | ||||
|                 column.getCellComponentName(); | ||||
|         } | ||||
|  | ||||
|         getRowClass() { | ||||
|             if (!this.rowClass) { | ||||
|                 let limitEvaluation = this.limitEvaluator.evaluate(this.datum); | ||||
|                 this.rowLimitClass = limitEvaluation && limitEvaluation.cssClass; | ||||
|                 this.rowClass = limitEvaluation && limitEvaluation.cssClass; | ||||
|             } | ||||
|             return this.rowLimitClass; | ||||
|             return this.rowClass; | ||||
|         } | ||||
|  | ||||
|         getCellLimitClasses() { | ||||
| @@ -55,12 +62,16 @@ define([], function () { | ||||
|                 this.cellLimitClasses = Object.values(this.columns).reduce((alarmStateMap, column) => { | ||||
|                     let limitEvaluation = this.limitEvaluator.evaluate(this.datum, column.getMetadatum()); | ||||
|                     alarmStateMap[column.getKey()] = limitEvaluation && limitEvaluation.cssClass; | ||||
|                      | ||||
|  | ||||
|                     return alarmStateMap; | ||||
|                 }, {}); | ||||
|             } | ||||
|             return this.cellLimitClasses; | ||||
|         } | ||||
|  | ||||
|         getContextMenuActions() { | ||||
|             return []; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -78,4 +89,4 @@ define([], function () { | ||||
|     } | ||||
|  | ||||
|     return TelemetryTableRow; | ||||
| }); | ||||
| }); | ||||
|   | ||||
| @@ -22,12 +22,10 @@ | ||||
|  | ||||
| define([ | ||||
|     './components/table.vue', | ||||
|     '../../exporters/CSVExporter', | ||||
|     './TelemetryTable', | ||||
|     'vue' | ||||
| ], function ( | ||||
|     TableComponent, | ||||
|     CSVExporter, | ||||
|     TelemetryTable, | ||||
|     Vue | ||||
| ) { | ||||
| @@ -50,8 +48,7 @@ define([ | ||||
|             canEdit(domainObject) { | ||||
|                 return domainObject.type === 'table'; | ||||
|             }, | ||||
|             view(domainObject) { | ||||
|                 let csvExporter = new CSVExporter.default(); | ||||
|             view(domainObject, isEditing, objectPath) { | ||||
|                 let table = new TelemetryTable(domainObject, openmct); | ||||
|                 let component; | ||||
|                 return { | ||||
| @@ -67,16 +64,19 @@ define([ | ||||
|                             }, | ||||
|                             provide: { | ||||
|                                 openmct, | ||||
|                                 csvExporter, | ||||
|                                 table | ||||
|                                 table, | ||||
|                                 objectPath | ||||
|                             }, | ||||
|                             el: element, | ||||
|                             template: '<table-component :isEditing="isEditing"></table-component>' | ||||
|                             template: '<table-component :isEditing="isEditing" :enableMarking="true"></table-component>' | ||||
|                         }); | ||||
|                     }, | ||||
|                     onEditModeChange(isEditing) { | ||||
|                         component.isEditing = isEditing; | ||||
|                     }, | ||||
|                     onClearData() { | ||||
|                         table.refreshData(); | ||||
|                     }, | ||||
|                     destroy: function (element) { | ||||
|                         component.$destroy(); | ||||
|                         component = undefined; | ||||
|   | ||||
| @@ -31,9 +31,9 @@ define( | ||||
|     ) { | ||||
|  | ||||
|         class BoundedTableRowCollection extends SortedTableRowCollection { | ||||
|             constructor (openmct) { | ||||
|             constructor(openmct) { | ||||
|                 super(); | ||||
|                  | ||||
|  | ||||
|                 this.futureBuffer = new SortedTableRowCollection(); | ||||
|                 this.openmct = openmct; | ||||
|  | ||||
| @@ -43,15 +43,17 @@ define( | ||||
|                 this.sortByTimeSystem(openmct.time.timeSystem()); | ||||
|  | ||||
|                 this.lastBounds = openmct.time.bounds(); | ||||
|                 openmct.time.on('bounds', this.bounds); | ||||
|  | ||||
|                 this.subscribeToBounds(); | ||||
|             } | ||||
|  | ||||
|             addOne (item) { | ||||
|             addOne(item) { | ||||
|                 let parsedValue = this.getValueForSortColumn(item); | ||||
|                 // Insert into either in-bounds array, or the future buffer. | ||||
|                 // Data in the future buffer will be re-evaluated for possible  | ||||
|                 // Data in the future buffer will be re-evaluated for possible | ||||
|                 // insertion on next bounds change | ||||
|                 let beforeStartOfBounds = this.parseTime(item.datum[this.sortOptions.key]) < this.lastBounds.start; | ||||
|                 let afterEndOfBounds = this.parseTime(item.datum[this.sortOptions.key]) > this.lastBounds.end; | ||||
|                 let beforeStartOfBounds = parsedValue < this.lastBounds.start; | ||||
|                 let afterEndOfBounds = parsedValue > this.lastBounds.end; | ||||
|  | ||||
|                 if (!afterEndOfBounds && !beforeStartOfBounds) { | ||||
|                     return super.addOne(item); | ||||
| @@ -86,13 +88,13 @@ define( | ||||
|              * @fires TelemetryCollection#discarded | ||||
|              * @param bounds | ||||
|              */ | ||||
|             bounds (bounds) { | ||||
|             bounds(bounds) { | ||||
|                 let startChanged = this.lastBounds.start !== bounds.start; | ||||
|                 let endChanged = this.lastBounds.end !== bounds.end; | ||||
|                  | ||||
|  | ||||
|                 let startIndex = 0; | ||||
|                 let endIndex = 0; | ||||
|                  | ||||
|  | ||||
|                 let discarded = []; | ||||
|                 let added = []; | ||||
|                 let testValue = { | ||||
| @@ -135,9 +137,21 @@ define( | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             destroy() { | ||||
|             getValueForSortColumn(row) { | ||||
|                 return this.parseTime(row.datum[this.sortOptions.key]); | ||||
|             } | ||||
|  | ||||
|             unsubscribeFromBounds() { | ||||
|                 this.openmct.time.off('bounds', this.bounds); | ||||
|             } | ||||
|  | ||||
|             subscribeToBounds() { | ||||
|                 this.openmct.time.on('bounds', this.bounds); | ||||
|             } | ||||
|  | ||||
|             destroy() { | ||||
|                 this.unsubscribeFromBounds(); | ||||
|             } | ||||
|         } | ||||
|     return BoundedTableRowCollection; | ||||
| }); | ||||
|         return BoundedTableRowCollection; | ||||
|     }); | ||||
|   | ||||
| @@ -60,7 +60,7 @@ define( | ||||
|                     if (rowsAdded.length > 0) { | ||||
|                         this.emit('add', rowsAdded); | ||||
|                     } | ||||
|                     this.dupeCheck = true;     | ||||
|                     this.dupeCheck = true; | ||||
|                 } else { | ||||
|                     let wasAdded = this.addOne(rows); | ||||
|                     if (wasAdded) { | ||||
| @@ -115,11 +115,10 @@ define( | ||||
|                 if (this.rows.length === 0) { | ||||
|                     return 0; | ||||
|                 } | ||||
|                  | ||||
|                 const sortOptionsKey = this.sortOptions.key; | ||||
|                 const testRowValue = testRow.datum[sortOptionsKey]; | ||||
|                 const firstValue = this.rows[0].datum[sortOptionsKey]; | ||||
|                 const lastValue = this.rows[this.rows.length - 1].datum[sortOptionsKey]; | ||||
|  | ||||
|                 const testRowValue = this.getValueForSortColumn(testRow); | ||||
|                 const firstValue = this.getValueForSortColumn(this.rows[0]); | ||||
|                 const lastValue = this.getValueForSortColumn(this.rows[this.rows.length - 1]); | ||||
|  | ||||
|                 lodashFunction = lodashFunction || _.sortedIndex; | ||||
|  | ||||
| @@ -133,7 +132,7 @@ define( | ||||
|                         return 0; | ||||
|                     } else { | ||||
|                         return lodashFunction(rows, testRow, (thisRow) => { | ||||
|                             return thisRow.datum[sortOptionsKey]; | ||||
|                             return this.getValueForSortColumn(thisRow); | ||||
|                         }); | ||||
|                     } | ||||
|                 } else { | ||||
| @@ -147,7 +146,7 @@ define( | ||||
|                     } else { | ||||
|                         // Use a custom comparison function to support descending sort. | ||||
|                         return lodashFunction(rows, testRow, (thisRow) => { | ||||
|                             const thisRowValue = thisRow.datum[sortOptionsKey]; | ||||
|                             const thisRowValue = this.getValueForSortColumn(thisRow); | ||||
|                             if (testRowValue === thisRowValue) { | ||||
|                                 return EQUAL; | ||||
|                             } else if (testRowValue < thisRowValue) { | ||||
| @@ -206,7 +205,7 @@ define( | ||||
|                     this.emit('sort'); | ||||
|                 } | ||||
|                 // Return duplicate to avoid direct modification of underlying object | ||||
|                 return Object.assign({}, this.sortOptions);  | ||||
|                 return Object.assign({}, this.sortOptions); | ||||
|             } | ||||
|  | ||||
|             removeAllRowsForObject(objectKeyString) { | ||||
| @@ -218,25 +217,32 @@ define( | ||||
|                     } | ||||
|                     return true; | ||||
|                 }); | ||||
|  | ||||
|                 this.emit('remove', removed); | ||||
|             } | ||||
|  | ||||
|             getValueForSortColumn(row) { | ||||
|                 return row.datum[this.sortOptions.key]; | ||||
|             } | ||||
|  | ||||
|             remove(removedRows) { | ||||
|                 this.rows = this.rows.filter(row => { | ||||
|                     return removedRows.indexOf(row) === -1; | ||||
|                 }); | ||||
|  | ||||
|                 this.emit('remove', removedRows); | ||||
|             } | ||||
|  | ||||
|             getRows () { | ||||
|             getRows() { | ||||
|                 return this.rows; | ||||
|             } | ||||
|  | ||||
|             clear() { | ||||
|                 let removedRows = this.rows; | ||||
|                 this.rows = []; | ||||
|  | ||||
|                 this.emit('remove', removedRows); | ||||
|             } | ||||
|         } | ||||
|     return SortedTableRowCollection; | ||||
| }); | ||||
|         return SortedTableRowCollection; | ||||
|     }); | ||||
|   | ||||
| @@ -0,0 +1,165 @@ | ||||
| <template> | ||||
|     <div v-if="filterNames.length > 0" | ||||
|         :title=title | ||||
|         class="c-filter-indication" | ||||
|         :class="{ 'c-filter-indication--mixed': hasMixedFilters }"> | ||||
|         <span class="c-filter-indication__mixed">{{ label }}</span> | ||||
|         <span v-for="(name, index) in filterNames" | ||||
|               class="c-filter-indication__label"> | ||||
|             {{ name }} | ||||
|         </span> | ||||
|     </div> | ||||
| </template> | ||||
|  | ||||
| <style lang="scss"> | ||||
|     @import "~styles/sass-base"; | ||||
|     .c-filter-indication { | ||||
|         @include userSelectNone(); | ||||
|         background: $colorFilterBg; | ||||
|         color: $colorFilterFg; | ||||
|         display: flex; | ||||
|         align-items: center; | ||||
|         font-size: 0.9em; | ||||
|         margin-top: $interiorMarginSm; | ||||
|         padding: 2px; | ||||
|         text-transform: uppercase; | ||||
|  | ||||
|         &:before { | ||||
|             font-family: symbolsfont-12px; | ||||
|             content: $glyph-icon-filter; | ||||
|             display: block; | ||||
|             font-size: 12px; | ||||
|             margin-right: $interiorMarginSm; | ||||
|         } | ||||
|  | ||||
|         &__mixed { | ||||
|             margin-right: $interiorMarginSm; | ||||
|         } | ||||
|  | ||||
|         &--mixed { | ||||
|             .c-filter-indication__mixed { | ||||
|                 font-style: italic; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         &__label { | ||||
|             + .c-filter-indication__label { | ||||
|                 &:before { | ||||
|                     content: ','; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| </style> | ||||
|  | ||||
| <script> | ||||
|     const FILTER_INDICATOR_LABEL = 'Filters:'; | ||||
|     const FILTER_INDICATOR_LABEL_MIXED = 'Mixed Filters:'; | ||||
|     const FILTER_INDICATOR_TITLE = 'Data filters are being applied to this view.'; | ||||
|     const FILTER_INDICATOR_TITLE_MIXED = 'A mix of data filter values are being applied to this view.'; | ||||
|     const USE_GLOBAL = 'useGlobal'; | ||||
|  | ||||
|     export default { | ||||
|         inject: ['openmct', 'table'], | ||||
|         data() { | ||||
|             return { | ||||
|                 filterNames: [], | ||||
|                 filteredTelemetry: {} | ||||
|             }            | ||||
|         }, | ||||
|         computed: { | ||||
|             hasMixedFilters() { | ||||
|                 let filtersToCompare = _.omit(this.filteredTelemetry[Object.keys(this.filteredTelemetry)[0]], [USE_GLOBAL]); | ||||
|                 return Object.values(this.filteredTelemetry).some(filters => { | ||||
|                     return !_.isEqual(filtersToCompare, _.omit(filters, [USE_GLOBAL])); | ||||
|                 }); | ||||
|             }, | ||||
|             label() { | ||||
|                 if (this.hasMixedFilters) { | ||||
|                     return FILTER_INDICATOR_LABEL_MIXED; | ||||
|                 } else { | ||||
|                     return FILTER_INDICATOR_LABEL; | ||||
|                 } | ||||
|             }, | ||||
|             title() { | ||||
|                 if (this.hasMixedFilters) { | ||||
|                     return FILTER_INDICATOR_TITLE_MIXED; | ||||
|                 } else { | ||||
|                     return FILTER_INDICATOR_TITLE; | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         methods: { | ||||
|             setFilterNames() { | ||||
|                 let names = []; | ||||
|                 let composition = this.openmct.composition.get(this.table.configuration.domainObject); | ||||
|  | ||||
|                 composition && composition.load().then((domainObjects) => { | ||||
|                     domainObjects.forEach(telemetryObject => { | ||||
|                         let keyString= this.openmct.objects.makeKeyString(telemetryObject.identifier); | ||||
|                         let metadataValues = this.openmct.telemetry.getMetadata(telemetryObject).values(); | ||||
|                         let filters = this.filteredTelemetry[keyString]; | ||||
|  | ||||
|                         if (filters !== undefined) { | ||||
|                             names.push(this.getFilterNamesFromMetadata(filters, metadataValues)); | ||||
|                         } | ||||
|                     }); | ||||
|  | ||||
|                     names = _.flatten(names); | ||||
|                     this.filterNames = names.length === 0 ? names : Array.from(new Set(names)); | ||||
|                 }); | ||||
|             }, | ||||
|             getFilterNamesFromMetadata(filters, metadataValues) { | ||||
|                 let filterNames = []; | ||||
|                 filters = _.omit(filters, [USE_GLOBAL]); | ||||
|  | ||||
|                 Object.keys(filters).forEach(key => { | ||||
|                     if (!_.isEmpty(filters[key])) { | ||||
|                         metadataValues.forEach(metadatum => { | ||||
|                             if (key === metadatum.key) { | ||||
|                                 if (typeof metadatum.filters[0] === "object") { | ||||
|                                     filterNames.push(this.getFilterLabels(filters[key], metadatum)); | ||||
|                                 } else { | ||||
|                                     filterNames.push(metadatum.name); | ||||
|                                 } | ||||
|                             } | ||||
|                         }); | ||||
|                     } | ||||
|                 }); | ||||
|  | ||||
|                 return _.flatten(filterNames); | ||||
|             }, | ||||
|             getFilterLabels(filterObject, metadatum, ) { | ||||
|                 let filterLabels = []; | ||||
|                 Object.values(filterObject).forEach(comparator => { | ||||
|                     comparator.forEach(filterValue => { | ||||
|                         metadatum.filters[0].possibleValues.forEach(option => { | ||||
|                             if (option.value === filterValue) { | ||||
|                                 filterLabels.push(option.label); | ||||
|                             } | ||||
|                         }); | ||||
|                     }); | ||||
|                 }); | ||||
|  | ||||
|                 return filterLabels; | ||||
|             }, | ||||
|             handleConfigurationChanges(configuration) { | ||||
|                 if (!_.eq(this.filteredTelemetry, configuration.filters)) { | ||||
|                     this.updateFilters(configuration.filters || {}); | ||||
|                 } | ||||
|             }, | ||||
|             updateFilters(filters) { | ||||
|                 this.filteredTelemetry = JSON.parse(JSON.stringify(filters)); | ||||
|                 this.setFilterNames(); | ||||
|             } | ||||
|         }, | ||||
|         mounted() { | ||||
|             let filters = this.table.configuration.getConfiguration().filters || {}; | ||||
|             this.table.configuration.on('change', this.handleConfigurationChanges); | ||||
|             this.updateFilters(filters); | ||||
|         }, | ||||
|         destroyed() { | ||||
|             this.table.configuration.off('change', this.handleConfigurationChanges); | ||||
|         } | ||||
|     } | ||||
| </script> | ||||
							
								
								
									
										71
									
								
								src/plugins/telemetryTable/components/table-cell.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								src/plugins/telemetryTable/components/table-cell.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,71 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2018, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT 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 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> | ||||
|     <td @click="selectCell($event.currentTarget, columnKey)" :title="formattedValue">{{formattedValue}}</td> | ||||
| </template> | ||||
| <script> | ||||
| export default { | ||||
|     inject: ['openmct'], | ||||
|     props: { | ||||
|         row: { | ||||
|             type: Object, | ||||
|             required: true | ||||
|         }, | ||||
|         columnKey: { | ||||
|             type: String, | ||||
|             require: true | ||||
|         }, | ||||
|         objectPath: { | ||||
|             type: Array, | ||||
|             require: false | ||||
|         } | ||||
|     }, | ||||
|     methods: { | ||||
|         selectCell(element, columnKey) { | ||||
|             if (this.isSelectable) { | ||||
|                 this.openmct.selection.select([{ | ||||
|                     element: element, | ||||
|                     context: { | ||||
|                         type: 'table-cell', | ||||
|                         row: this.row.objectKeyString, | ||||
|                         column: columnKey | ||||
|                     } | ||||
|                 },{ | ||||
|                     element: this.openmct.layout.$refs.browseObject.$el, | ||||
|                     context: { | ||||
|                         item: this.objectPath[0] | ||||
|                     } | ||||
|                 }], false); | ||||
|                 event.stopPropagation(); | ||||
|             } | ||||
|         }, | ||||
|     }, | ||||
|     computed: { | ||||
|         formattedValue() { | ||||
|             return this.row.getFormattedValue(this.columnKey); | ||||
|         }, | ||||
|         isSelectable() { | ||||
|             return this.row.columns[this.columnKey].selectable; | ||||
|         } | ||||
|     } | ||||
| }; | ||||
| </script> | ||||
| @@ -4,14 +4,14 @@ | ||||
|         <div class="c-properties__header">Table Column Size</div> | ||||
|         <ul class="c-properties__section"> | ||||
|             <li class="c-properties__row"> | ||||
|                 <div class="c-properties__label" title="Show or Hide Column"><label for="AutoSizeControl">Auto-size</label></div> | ||||
|                 <div class="c-properties__label" title="Auto-size table"><label for="AutoSizeControl">Auto-size</label></div> | ||||
|                 <div class="c-properties__value"><input type="checkbox" id="AutoSizeControl" :checked="configuration.autosize !== false" @change="toggleAutosize()"></div>             | ||||
|             </li> | ||||
|         </ul> | ||||
|         <div class="c-properties__header">Table Column Visibility</div> | ||||
|         <ul class="c-properties__section"> | ||||
|             <li class="c-properties__row" v-for="(title, key) in headers"> | ||||
|                 <div class="c-properties__label" title="Show or Hide Column"><label :for="key + 'ColumnControl'">{{title}}</label></div> | ||||
|                 <div class="c-properties__label" title="Show or hide column"><label :for="key + 'ColumnControl'">{{title}}</label></div> | ||||
|                 <div class="c-properties__value"><input type="checkbox" :id="key + 'ColumnControl'" :checked="configuration.hiddenColumns[key] !== true" @change="toggleColumn(key)"></div> | ||||
|             </li> | ||||
|         </ul> | ||||
| @@ -23,6 +23,8 @@ | ||||
| </style> | ||||
|  | ||||
| <script> | ||||
| import TelemetryTableColumn from '../TelemetryTableColumn'; | ||||
|  | ||||
| export default { | ||||
|     inject: ['tableConfiguration', 'openmct'], | ||||
|     data() { | ||||
| @@ -43,7 +45,7 @@ export default { | ||||
|             this.tableConfiguration.updateConfiguration(this.configuration); | ||||
|         }, | ||||
|         addObject(domainObject) { | ||||
|             this.tableConfiguration.addColumnsForObject(domainObject, true); | ||||
|                 this.addColumnsForObject(domainObject, true); | ||||
|             this.updateHeaders(this.tableConfiguration.getAllHeaders()); | ||||
|         }, | ||||
|         removeObject(objectIdentifier) { | ||||
| @@ -56,6 +58,17 @@ export default { | ||||
|         toggleAutosize() { | ||||
|             this.configuration.autosize = !this.configuration.autosize; | ||||
|             this.tableConfiguration.updateConfiguration(this.configuration); | ||||
|         }, | ||||
|         addColumnsForAllObjects(objects) { | ||||
|             objects.forEach(object => this.addColumnsForObject(object, false)); | ||||
|         }, | ||||
|         addColumnsForObject(telemetryObject) { | ||||
|             let metadataValues = this.openmct.telemetry.getMetadata(telemetryObject).values(); | ||||
|  | ||||
|             metadataValues.forEach(metadatum => { | ||||
|                 let column = new TelemetryTableColumn(this.openmct, metadatum); | ||||
|                 this.tableConfiguration.addSingleColumnForObject(telemetryObject, column); | ||||
|             }); | ||||
|         } | ||||
|     }, | ||||
|     mounted() { | ||||
| @@ -65,7 +78,7 @@ export default { | ||||
|  | ||||
|         compositionCollection.load() | ||||
|             .then((composition) => { | ||||
|                 this.tableConfiguration.addColumnsForAllObjects(composition); | ||||
|                 this.addColumnsForAllObjects(composition); | ||||
|                 this.updateHeaders(this.tableConfiguration.getAllHeaders()); | ||||
|  | ||||
|                 compositionCollection.on('add', this.addObject); | ||||
|   | ||||
| @@ -20,26 +20,55 @@ | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
| <template> | ||||
| <tr :style="{ top: rowTop }" :class="rowLimitClass"> | ||||
|     <td v-for="(title, key) in headers"  | ||||
| <tr :style="{ top: rowTop }" | ||||
|     class="noselect" | ||||
|     :class="[ | ||||
|         rowClass, | ||||
|         {'is-selected': marked} | ||||
|     ]" | ||||
|     v-on="listeners"> | ||||
|     <component v-for="(title, key) in headers"  | ||||
|         :key="key" | ||||
|         :is="componentList[key]" | ||||
|         :columnKey="key" | ||||
|         :style="columnWidths[key] === undefined ? {} : { width: columnWidths[key] + 'px', 'max-width': columnWidths[key] + 'px'}" | ||||
|         :title="formattedRow[key]" | ||||
|         :class="cellLimitClasses[key]">{{formattedRow[key]}}</td> | ||||
|         :class="[cellLimitClasses[key], selectableColumns[key] ? 'is-selectable' : '']" | ||||
|         :objectPath="objectPath" | ||||
|         :row="row"> | ||||
|     </component> | ||||
| </tr> | ||||
| </template> | ||||
|  | ||||
| <style> | ||||
|     .noselect { | ||||
|     -webkit-touch-callout: none; /* iOS Safari */ | ||||
|         -webkit-user-select: none; /* Safari */ | ||||
|         -khtml-user-select: none; /* Konqueror HTML */ | ||||
|         -moz-user-select: none; /* Firefox */ | ||||
|             -ms-user-select: none; /* Internet Explorer/Edge */ | ||||
|                 user-select: none; /* Non-prefixed version, currently | ||||
|                                     supported by Chrome and Opera */ | ||||
|     } | ||||
| </style> | ||||
|  | ||||
| <script> | ||||
| import TableCell from './table-cell.vue'; | ||||
|  | ||||
| export default { | ||||
|     inject: ['openmct', 'objectPath'], | ||||
|     data: function () { | ||||
|         return { | ||||
|             rowTop: (this.rowOffset + this.rowIndex) * this.rowHeight + 'px', | ||||
|             formattedRow: this.row.getFormattedDatum(this.headers), | ||||
|             rowLimitClass: this.row.getRowLimitClass(), | ||||
|             cellLimitClasses: this.row.getCellLimitClasses() | ||||
|             rowClass: this.row.getRowClass(), | ||||
|             cellLimitClasses: this.row.getCellLimitClasses(), | ||||
|             componentList: Object.keys(this.headers).reduce((components, header) => { | ||||
|                 components[header] = this.row.getCellComponentName(header) || 'table-cell'; | ||||
|                 return components | ||||
|             }, {}), | ||||
|             selectableColumns : Object.keys(this.row.columns).reduce((selectable, columnKeys) => { | ||||
|                 selectable[columnKeys] = this.row.columns[columnKeys].selectable; | ||||
|                 return selectable; | ||||
|             }, {}) | ||||
|         } | ||||
|     }, | ||||
|     props: { | ||||
| @@ -55,6 +84,10 @@ export default { | ||||
|             type: Object, | ||||
|             required: true | ||||
|         }, | ||||
|         objectPath: { | ||||
|             type: Array, | ||||
|             required: false | ||||
|         }, | ||||
|         rowIndex: { | ||||
|             type: Number, | ||||
|             required: false, | ||||
| @@ -69,6 +102,11 @@ export default { | ||||
|             type: Number, | ||||
|             required: false, | ||||
|             default: 0 | ||||
|         }, | ||||
|         marked: { | ||||
|             type: Boolean, | ||||
|             required: false, | ||||
|             default: false | ||||
|         } | ||||
|     }, | ||||
|     methods: { | ||||
| @@ -76,9 +114,54 @@ export default { | ||||
|             this.rowTop = (rowOffset + this.rowIndex) * this.rowHeight + 'px'; | ||||
|         }, | ||||
|         formatRow: function (row) { | ||||
|             this.formattedRow = row.getFormattedDatum(this.headers); | ||||
|             this.rowLimitClass = row.getRowLimitClass(); | ||||
|             this.rowClass = row.getRowClass(); | ||||
|             this.cellLimitClasses = row.getCellLimitClasses(); | ||||
|         }, | ||||
|         markRow: function (event) { | ||||
|             let keyCtrlModifier = false; | ||||
|  | ||||
|             if (event.ctrlKey || event.metaKey) { | ||||
|                 keyCtrlModifier = true; | ||||
|             } | ||||
|  | ||||
|             if (event.shiftKey) { | ||||
|                 this.$emit('markMultipleConcurrent', this.rowIndex); | ||||
|             } else { | ||||
|                 if (this.marked) { | ||||
|                     this.$emit('unmark', this.rowIndex, keyCtrlModifier); | ||||
|                 } else { | ||||
|                     this.$emit('mark', this.rowIndex, keyCtrlModifier); | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         selectCell(element, columnKey) { | ||||
|             if (this.selectableColumns[columnKey]) { | ||||
|                  //TODO: This is a hack. Cannot get parent this way. | ||||
|                 this.openmct.selection.select([{ | ||||
|                     element: element, | ||||
|                     context: { | ||||
|                         type: 'table-cell', | ||||
|                         row: this.row.objectKeyString, | ||||
|                         column: columnKey | ||||
|                     } | ||||
|                 },{ | ||||
|                     element: this.openmct.layout.$refs.browseObject.$el, | ||||
|                     context: { | ||||
|                         item: this.openmct.router.path[0] | ||||
|                     } | ||||
|                 }], false); | ||||
|                 event.stopPropagation(); | ||||
|             } | ||||
|         }, | ||||
|          showContextMenu: function (event) { | ||||
|             event.preventDefault(); | ||||
|  | ||||
|             this.openmct.objects.get(this.row.objectKeyString).then((domainObject) => { | ||||
|                 let contextualObjectPath = this.objectPath.slice(); | ||||
|                 contextualObjectPath.unshift(domainObject); | ||||
|  | ||||
|                 this.openmct.contextMenu._showContextMenuForObjectPath(contextualObjectPath, event.x, event.y, this.row.getContextMenuActions()); | ||||
|             }); | ||||
|         } | ||||
|     }, | ||||
|     // TODO: use computed properties | ||||
| @@ -88,6 +171,22 @@ export default { | ||||
|             handler: 'formatRow', | ||||
|             deep: false | ||||
|         } | ||||
|     }, | ||||
|     components: { | ||||
|         TableCell | ||||
|     }, | ||||
|     computed: { | ||||
|         listeners() { | ||||
|             let listenersObject = { | ||||
|                 click: this.markRow | ||||
|             } | ||||
|  | ||||
|             if (this.row.getContextMenuActions().length) { | ||||
|                 listenersObject.contextmenu = this.showContextMenu; | ||||
|             } | ||||
|              | ||||
|             return listenersObject; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| </script> | ||||
|   | ||||
| @@ -20,92 +20,135 @@ | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
| <template> | ||||
| <div class="c-table c-telemetry-table c-table--filterable c-table--sortable has-control-bar" | ||||
|      :class="{'loading': loading}"> | ||||
|     <div class="c-table__control-bar c-control-bar"> | ||||
| <div class="c-table-wrapper"> | ||||
|     <div class="c-table-control-bar c-control-bar"> | ||||
|         <button class="c-button icon-download labeled" | ||||
|            v-on:click="exportAsCSV()" | ||||
|            title="Export This View's Data"> | ||||
|             <span class="c-button__label">Export As CSV</span> | ||||
|                 v-if="allowExport" | ||||
|                 v-on:click="exportAllDataAsCSV()" | ||||
|                 title="Export This View's Data"> | ||||
|             <span class="c-button__label">Export Table Data</span> | ||||
|         </button> | ||||
|         <button class="c-button icon-download labeled" | ||||
|                 v-if="allowExport" | ||||
|                 v-show="markedRows.length" | ||||
|                 v-on:click="exportMarkedDataAsCSV()" | ||||
|                 title="Export Marked Rows As CSV"> | ||||
|             <span class="c-button__label">Export Marked Rows</span> | ||||
|         </button> | ||||
|         <button class="c-button icon-x labeled" | ||||
|                 v-show="markedRows.length" | ||||
|                 v-on:click="unmarkAllRows()" | ||||
|                 title="Unmark All Rows"> | ||||
|             <span class="c-button__label">Unmark All Rows</span> | ||||
|         </button> | ||||
|         <div v-if="enableMarking" | ||||
|             class="c-separator"> | ||||
|         </div> | ||||
|         <button v-if="enableMarking" | ||||
|                 class="c-button icon-pause pause-play labeled" | ||||
|                 :class=" paused ? 'icon-play is-paused' : 'icon-pause'" | ||||
|                 v-on:click="togglePauseByButton()" | ||||
|                 :title="paused ? 'Continue Data Flow' : 'Pause Data Flow'"> | ||||
|                 <span class="c-button__label"> | ||||
|                     {{paused ? 'Play' : 'Pause'}} | ||||
|                 </span> | ||||
|         </button> | ||||
|         <slot name="buttons"></slot> | ||||
|     </div> | ||||
|     <div v-if="isDropTargetActive" class="c-telemetry-table__drop-target" :style="dropTargetStyle"></div> | ||||
|     <!-- Headers table --> | ||||
|     <div class="c-telemetry-table__headers-w js-table__headers-w" ref="headersTable" :style="{ 'max-width': widthWithScroll}"> | ||||
|         <table class="c-table__headers c-telemetry-table__headers"> | ||||
|             <thead> | ||||
|                 <tr class="c-telemetry-table__headers__labels"> | ||||
|                     <table-column-header | ||||
|                         v-for="(title, key, headerIndex) in headers" | ||||
|                         :key="key" | ||||
|                         :headerKey="key" | ||||
|                         :headerIndex="headerIndex" | ||||
|                         @sort="sortBy(key)" | ||||
|                         @resizeColumn="resizeColumn" | ||||
|                         @dropTargetOffsetChanged="setDropTargetOffset" | ||||
|                         @dropTargetActive="dropTargetActive" | ||||
|                         @reorderColumn="reorderColumn" | ||||
|                         @resizeColumnEnd="updateConfiguredColumnWidths" | ||||
|                         :columnWidth="columnWidths[key]" | ||||
|                         :sortOptions="sortOptions" | ||||
|                         :isEditing="isEditing" | ||||
|                     ><span class="c-telemetry-table__headers__label">{{title}}</span> | ||||
|                     </table-column-header> | ||||
|                 </tr> | ||||
|                 <tr class="c-telemetry-table__headers__filter"> | ||||
|                     <table-column-header | ||||
|                         v-for="(title, key, headerIndex) in headers" | ||||
|                         :key="key" | ||||
|                         :headerKey="key" | ||||
|                         :headerIndex="headerIndex" | ||||
|                         @resizeColumn="resizeColumn" | ||||
|                         @dropTargetOffsetChanged="setDropTargetOffset" | ||||
|                         @dropTargetActive="dropTargetActive" | ||||
|                         @reorderColumn="reorderColumn" | ||||
|                         @resizeColumnEnd="updateConfiguredColumnWidths" | ||||
|                         :columnWidth="columnWidths[key]" | ||||
|                         :isEditing="isEditing" | ||||
|                         > | ||||
|                         <search class="c-table__search" | ||||
|                             v-model="filters[key]" | ||||
|                             v-on:input="filterChanged(key)" | ||||
|                             v-on:clear="clearFilter(key)" /> | ||||
|                     </table-column-header> | ||||
|                 </tr> | ||||
|             </thead> | ||||
|  | ||||
|     <div class="c-table c-telemetry-table c-table--filterable c-table--sortable has-control-bar" | ||||
|          :class="{ | ||||
|          'loading': loading, | ||||
|          'paused' : paused | ||||
|         }"> | ||||
|  | ||||
|         <div :style="{ 'max-width': widthWithScroll, 'min-width': '150px'}"><slot></slot></div> | ||||
|  | ||||
|         <div v-if="isDropTargetActive" class="c-telemetry-table__drop-target" :style="dropTargetStyle"></div> | ||||
|         <!-- Headers table --> | ||||
|         <div class="c-telemetry-table__headers-w js-table__headers-w" ref="headersTable" :style="{ 'max-width': widthWithScroll}"> | ||||
|             <table class="c-table__headers c-telemetry-table__headers"> | ||||
|                 <thead> | ||||
|                     <tr class="c-telemetry-table__headers__labels"> | ||||
|                         <table-column-header | ||||
|                             v-for="(title, key, headerIndex) in headers" | ||||
|                             :key="key" | ||||
|                             :headerKey="key" | ||||
|                             :headerIndex="headerIndex" | ||||
|                             @sort="allowSorting && sortBy(key)" | ||||
|                             @resizeColumn="resizeColumn" | ||||
|                             @dropTargetOffsetChanged="setDropTargetOffset" | ||||
|                             @dropTargetActive="dropTargetActive" | ||||
|                             @reorderColumn="reorderColumn" | ||||
|                             @resizeColumnEnd="updateConfiguredColumnWidths" | ||||
|                             :columnWidth="columnWidths[key]" | ||||
|                             :sortOptions="sortOptions" | ||||
|                             :isEditing="isEditing" | ||||
|                         ><span class="c-telemetry-table__headers__label">{{title}}</span> | ||||
|                         </table-column-header> | ||||
|                     </tr> | ||||
|                     <tr class="c-telemetry-table__headers__filter"> | ||||
|                         <table-column-header | ||||
|                             v-for="(title, key, headerIndex) in headers" | ||||
|                             :key="key" | ||||
|                             :headerKey="key" | ||||
|                             :headerIndex="headerIndex" | ||||
|                             @resizeColumn="resizeColumn" | ||||
|                             @dropTargetOffsetChanged="setDropTargetOffset" | ||||
|                             @dropTargetActive="dropTargetActive" | ||||
|                             @reorderColumn="reorderColumn" | ||||
|                             @resizeColumnEnd="updateConfiguredColumnWidths" | ||||
|                             :columnWidth="columnWidths[key]" | ||||
|                             :isEditing="isEditing" | ||||
|                             > | ||||
|                             <search class="c-table__search" | ||||
|                                 v-model="filters[key]" | ||||
|                                 v-on:input="filterChanged(key)" | ||||
|                                 v-on:clear="clearFilter(key)" /> | ||||
|                         </table-column-header> | ||||
|                     </tr> | ||||
|                 </thead> | ||||
|             </table> | ||||
|         </div> | ||||
|         <!-- Content table --> | ||||
|         <div class="c-table__body-w c-telemetry-table__body-w js-telemetry-table__body-w" @scroll="scroll" :style="{ 'max-width': widthWithScroll}"> | ||||
|             <div class="c-telemetry-table__scroll-forcer" :style="{ width: totalWidth + 'px' }"></div> | ||||
|             <table class="c-table__body c-telemetry-table__body js-telemetry-table__content" | ||||
|                    :style="{ height: totalHeight + 'px'}"> | ||||
|                 <tbody> | ||||
|                     <telemetry-table-row v-for="(row, rowIndex) in visibleRows" | ||||
|                         :headers="headers" | ||||
|                         :columnWidths="columnWidths" | ||||
|                         :rowIndex="rowIndex" | ||||
|                         :objectPath="objectPath" | ||||
|                         :rowOffset="rowOffset" | ||||
|                         :rowHeight="rowHeight" | ||||
|                         :row="row" | ||||
|                         :marked="row.marked" | ||||
|                         @mark="markRow" | ||||
|                         @unmark="unmarkRow" | ||||
|                         @markMultipleConcurrent="markMultipleConcurrentRows"> | ||||
|                     </telemetry-table-row> | ||||
|                 </tbody> | ||||
|             </table> | ||||
|         </div> | ||||
|         <!-- Sizing table --> | ||||
|         <table class="c-telemetry-table__sizing js-telemetry-table__sizing" :style="sizingTableWidth"> | ||||
|             <tr> | ||||
|                 <template v-for="(title, key) in headers"> | ||||
|                 <th :key="key" :style="{ width: configuredColumnWidths[key] + 'px', 'max-width': configuredColumnWidths[key] + 'px'}">{{title}}</th> | ||||
|                 </template> | ||||
|             </tr> | ||||
|             <telemetry-table-row v-for="(sizingRowData, objectKeyString) in sizingRows" | ||||
|                 :key="objectKeyString" | ||||
|                 :headers="headers" | ||||
|                 :columnWidths="configuredColumnWidths" | ||||
|                 :row="sizingRowData"> | ||||
|             </telemetry-table-row> | ||||
|         </table> | ||||
|         <telemetry-filter-indicator></telemetry-filter-indicator> | ||||
|     </div> | ||||
|     <!-- Content table --> | ||||
|     <div class="c-table__body-w c-telemetry-table__body-w js-telemetry-table__body-w" @scroll="scroll" :style="{ 'max-width': widthWithScroll}"> | ||||
|         <div class="c-telemetry-table__scroll-forcer" :style="{ width: totalWidth + 'px' }"></div> | ||||
|         <table class="c-table__body c-telemetry-table__body js-telemetry-table__content" | ||||
|                :style="{ height: totalHeight + 'px'}"> | ||||
|             <tbody> | ||||
|                 <telemetry-table-row v-for="(row, rowIndex) in visibleRows" | ||||
|                     :headers="headers" | ||||
|                     :columnWidths="columnWidths" | ||||
|                     :rowIndex="rowIndex" | ||||
|                     :rowOffset="rowOffset" | ||||
|                     :rowHeight="rowHeight" | ||||
|                     :row="row"> | ||||
|                 </telemetry-table-row> | ||||
|             </tbody> | ||||
|         </table> | ||||
|     </div> | ||||
|     <!-- Sizing table --> | ||||
|     <table class="c-telemetry-table__sizing js-telemetry-table__sizing" :style="sizingTableWidth"> | ||||
|         <tr> | ||||
|             <template v-for="(title, key) in headers"> | ||||
|             <th :key="key" :style="{ width: configuredColumnWidths[key] + 'px', 'max-width': configuredColumnWidths[key] + 'px'}">{{title}}</th> | ||||
|             </template> | ||||
|         </tr> | ||||
|         <telemetry-table-row v-for="(sizingRowData, objectKeyString) in sizingRows" | ||||
|             :headers="headers" | ||||
|             :columnWidths="configuredColumnWidths" | ||||
|             :row="sizingRowData"> | ||||
|         </telemetry-table-row> | ||||
|     </table> | ||||
| </div> | ||||
| </div><!-- closes c-table-wrapper --> | ||||
| </template> | ||||
|  | ||||
| <style lang="scss"> | ||||
| @@ -131,7 +174,7 @@ | ||||
|             display: block; | ||||
|             flex: 1 0 auto; | ||||
|             width: 100px; | ||||
|             vertical-align: middle; // This is crucial to hiding f**king 4px height injected by browser by default | ||||
|             vertical-align: middle; // This is crucial to hiding 4px height injected by browser by default | ||||
|         } | ||||
|  | ||||
|         td { | ||||
| @@ -216,6 +259,10 @@ | ||||
|                 align-items: stretch; | ||||
|                 position: absolute; | ||||
|                 height: 18px; // Needed when a row has empty values in its cells | ||||
|  | ||||
|                 &.is-selected { | ||||
|                     background-color: $colorSelectedBg; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             td { | ||||
| @@ -266,6 +313,10 @@ | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     .paused { | ||||
|         border: 1px solid #ff9900; | ||||
|     } | ||||
|  | ||||
|     /******************************* LEGACY */ | ||||
|     .s-status-taking-snapshot, | ||||
|     .overlay.snapshot { | ||||
| @@ -279,6 +330,8 @@ | ||||
| import TelemetryTableRow from './table-row.vue'; | ||||
| import search from '../../../ui/components/search.vue'; | ||||
| import TableColumnHeader from './table-column-header.vue'; | ||||
| import TelemetryFilterIndicator from './TelemetryFilterIndicator.vue'; | ||||
| import CSVExporter from '../../../exporters/CSVExporter.js'; | ||||
| import _ from 'lodash'; | ||||
|  | ||||
| const VISIBLE_ROW_COUNT = 100; | ||||
| @@ -293,13 +346,30 @@ export default { | ||||
|     components: { | ||||
|         TelemetryTableRow, | ||||
|         TableColumnHeader, | ||||
|         search | ||||
|         search, | ||||
|         TelemetryFilterIndicator | ||||
|     }, | ||||
|     inject: ['table', 'openmct', 'csvExporter'], | ||||
|     inject: ['table', 'openmct', 'objectPath'], | ||||
|     props: { | ||||
|         isEditing: { | ||||
|             type: Boolean, | ||||
|             default: false | ||||
|         }, | ||||
|         allowExport: { | ||||
|             type: Boolean, | ||||
|             default: true | ||||
|         }, | ||||
|         allowFiltering: { | ||||
|             'type': Boolean, | ||||
|             'default': true | ||||
|         }, | ||||
|         allowSorting: { | ||||
|             'type': Boolean, | ||||
|             'default': true | ||||
|         }, | ||||
|         enableMarking: { | ||||
|             type: Boolean, | ||||
|             default: false | ||||
|         } | ||||
|     }, | ||||
|     data() { | ||||
| @@ -328,7 +398,10 @@ export default { | ||||
|             dropOffsetLeft: undefined, | ||||
|             isDropTargetActive: false, | ||||
|             isAutosizeEnabled: configuration.autosize, | ||||
|             scrollW: 0 | ||||
|             scrollW: 0, | ||||
|             markCounter: 0, | ||||
|             paused: false, | ||||
|             markedRows: [] | ||||
|         } | ||||
|     }, | ||||
|     computed: { | ||||
| @@ -472,10 +545,12 @@ export default { | ||||
|         }, | ||||
|         filterChanged(columnKey) { | ||||
|             this.table.filteredRows.setColumnFilter(columnKey, this.filters[columnKey]); | ||||
|             this.setHeight(); | ||||
|         }, | ||||
|         clearFilter(columnKey) { | ||||
|             this.filters[columnKey] = ''; | ||||
|             this.table.filteredRows.setColumnFilter(columnKey, ''); | ||||
|             this.setHeight(); | ||||
|         }, | ||||
|         rowsAdded (rows) { | ||||
|             this.setHeight(); | ||||
| @@ -512,15 +587,27 @@ export default { | ||||
|             // which causes subsequent scroll to use an out of date height. | ||||
|             this.contentTable.style.height = this.totalHeight + 'px';  | ||||
|         }, | ||||
|         exportAsCSV() { | ||||
|         exportAsCSV(data) { | ||||
|             const headerKeys = Object.keys(this.headers); | ||||
|             const justTheData = this.table.filteredRows.getRows() | ||||
|                 .map(row => row.getFormattedDatum(this.headers)); | ||||
|             this.csvExporter.export(justTheData, { | ||||
|  | ||||
|             this.csvExporter.export(data, { | ||||
|                 filename: this.table.domainObject.name + '.csv', | ||||
|                 headers: headerKeys | ||||
|             }); | ||||
|         }, | ||||
|         exportAllDataAsCSV() { | ||||
|             const justTheData = this.table.filteredRows.getRows() | ||||
|                 .map(row => row.getFormattedDatum(this.headers)); | ||||
|  | ||||
|             this.exportAsCSV(justTheData); | ||||
|         }, | ||||
|         exportMarkedDataAsCSV() { | ||||
|             const data = this.table.filteredRows.getRows() | ||||
|                 .filter(row => row.marked === true) | ||||
|                 .map(row => row.getFormattedDatum(this.headers)); | ||||
|  | ||||
|             this.exportAsCSV(data); | ||||
|         }, | ||||
|         outstandingRequests(loading) { | ||||
|             this.loading = loading; | ||||
|         }, | ||||
| @@ -609,12 +696,114 @@ export default { | ||||
|                 scrollTop = this.scrollable.scrollTop; | ||||
|             }, RESIZE_POLL_INTERVAL); | ||||
|         }, | ||||
|         clearRowsAndRerender() { | ||||
|             this.visibleRows = []; | ||||
|             this.$nextTick().then(this.updateVisibleRows); | ||||
|         }, | ||||
|         pause(pausedByButton) { | ||||
|             if (pausedByButton) { | ||||
|                 this.pausedByButton = true; | ||||
|             } | ||||
|             this.paused = true; | ||||
|             this.table.pause(); | ||||
|         }, | ||||
|         unpause(unpausedByButton) { | ||||
|             if (unpausedByButton) { | ||||
|                 this.paused = false; | ||||
|                 this.table.unpause(); | ||||
|                 this.markedRows = []; | ||||
|                 this.pausedByButton = false; | ||||
|             } else { | ||||
|                 if (!this.pausedByButton) { | ||||
|                     this.paused = false; | ||||
|                     this.table.unpause(); | ||||
|                     this.markedRows = []; | ||||
|                 } | ||||
|             } | ||||
|              | ||||
|         }, | ||||
|         togglePauseByButton() { | ||||
|             if (this.paused) { | ||||
|                 this.unpause(true); | ||||
|             } else { | ||||
|                 this.pause(true); | ||||
|             } | ||||
|         }, | ||||
|         undoMarkedRows(unpause) { | ||||
|             this.markedRows.forEach(r => r.marked = false); | ||||
|             this.markedRows = []; | ||||
|         }, | ||||
|         unmarkRow(rowIndex) { | ||||
|             this.undoMarkedRows(); | ||||
|             this.unpause(); | ||||
|         }, | ||||
|         markRow(rowIndex, keyModifier) { | ||||
|             if (!this.enableMarking) { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             let insertMethod = 'unshift'; | ||||
|  | ||||
|             if (this.markedRows.length && !keyModifier) { | ||||
|                 this.undoMarkedRows(); | ||||
|                 insertMethod = 'push'; | ||||
|             } | ||||
|  | ||||
|             let markedRow = this.visibleRows[rowIndex]; | ||||
|  | ||||
|             this.$set(markedRow, 'marked', true); | ||||
|             this.pause(); | ||||
|  | ||||
|             this.markedRows[insertMethod](markedRow); | ||||
|         }, | ||||
|         unmarkAllRows(skipUnpause) { | ||||
|             this.markedRows.forEach(row => row.marked = false); | ||||
|             this.markedRows = []; | ||||
|             this.unpause(); | ||||
|         }, | ||||
|         markMultipleConcurrentRows(rowIndex) { | ||||
|             if (!this.enableMarking) { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             if (!this.markedRows.length) { | ||||
|                 this.markRow(rowIndex); | ||||
|             } else { | ||||
|                 if (this.markedRows.length > 1) { | ||||
|                     this.markedRows.forEach((r,i) => { | ||||
|                         if (i !== 0) { | ||||
|                             r.marked = false; | ||||
|                         } | ||||
|                     }); | ||||
|                     this.markedRows.splice(1); | ||||
|                 } | ||||
|                 let lastRowToBeMarked = this.visibleRows[rowIndex]; | ||||
|  | ||||
|                 let allRows = this.table.filteredRows.getRows(), | ||||
|                     firstRowIndex = allRows.indexOf(this.markedRows[0]), | ||||
|                     lastRowIndex = allRows.indexOf(lastRowToBeMarked); | ||||
|  | ||||
|                 //supports backward selection | ||||
|                 if (lastRowIndex < firstRowIndex) { | ||||
|                     let temp = lastRowIndex; | ||||
|                  | ||||
|                     lastRowIndex = firstRowIndex; | ||||
|                     firstRowIndex = temp - 1; | ||||
|                 } | ||||
|  | ||||
|                 for (var i = firstRowIndex + 1; i <= lastRowIndex; i++) { | ||||
|                     let row = allRows[i]; | ||||
|                     row.marked = true; | ||||
|                     this.markedRows.push(row); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
|     created() { | ||||
|         this.filterChanged = _.debounce(this.filterChanged, 500); | ||||
|     }, | ||||
|     mounted() { | ||||
|         this.csvExporter = new CSVExporter(); | ||||
|         this.rowsAdded = _.throttle(this.rowsAdded, 200); | ||||
|         this.rowsRemoved = _.throttle(this.rowsRemoved, 200); | ||||
|         this.scroll = _.throttle(this.scroll, 100); | ||||
| @@ -622,6 +811,7 @@ export default { | ||||
|         this.table.on('object-added', this.addObject); | ||||
|         this.table.on('object-removed', this.removeObject); | ||||
|         this.table.on('outstanding-requests', this.outstandingRequests); | ||||
|         this.table.on('refresh', this.clearRowsAndRerender); | ||||
|  | ||||
|         this.table.filteredRows.on('add', this.rowsAdded); | ||||
|         this.table.filteredRows.on('remove', this.rowsRemoved); | ||||
| @@ -647,6 +837,7 @@ export default { | ||||
|         this.table.off('object-added', this.addObject); | ||||
|         this.table.off('object-removed', this.removeObject); | ||||
|         this.table.off('outstanding-requests', this.outstandingRequests); | ||||
|         this.table.off('refresh', this.clearRowsAndRerender); | ||||
|  | ||||
|         this.table.filteredRows.off('add', this.rowsAdded); | ||||
|         this.table.filteredRows.off('remove', this.rowsRemoved); | ||||
|   | ||||
| @@ -1,6 +0,0 @@ | ||||
| <tr :style="{ top: rowTop }" :class="rowLimitClass"> | ||||
|     <td v-for="(title, key, headerIndex) in headers"  | ||||
|         :style="{ width: columnWidths[headerIndex], 'max-width': columnWidths[headerIndex]}"  | ||||
|         :title="formattedRow[key]" | ||||
|         :class="cellLimitClasses[key]">{{formattedRow[key]}}</td> | ||||
| </tr> | ||||
| @@ -167,6 +167,7 @@ export default { | ||||
|             this.xAxis.scale(this.xScale); | ||||
|             this.xAxis.tickFormat(utcMultiTimeFormat); | ||||
|             this.axisElement.call(this.xAxis); | ||||
|             this.setScale(); | ||||
|         }, | ||||
|         getActiveFormatter() { | ||||
|             let timeSystem = this.openmct.time.timeSystem(); | ||||
|   | ||||
| @@ -60,7 +60,6 @@ export default { | ||||
|                 .filter(menuOption => menuOption.clock === (clock && clock.key)) | ||||
|                 .map(menuOption => JSON.parse(JSON.stringify(this.openmct.time.timeSystems.get(menuOption.timeSystem)))); | ||||
|         }, | ||||
|  | ||||
|         setTimeSystemFromView(timeSystem) { | ||||
|             if (timeSystem.key !== this.selectedTimeSystem.key) { | ||||
|                 let activeClock = this.openmct.time.clock(); | ||||
| @@ -69,7 +68,15 @@ export default { | ||||
|                     timeSystem: timeSystem.key | ||||
|                 }); | ||||
|                 if (activeClock === undefined) { | ||||
|                     this.openmct.time.timeSystem(timeSystem.key, configuration.bounds); | ||||
|                     let bounds; | ||||
|  | ||||
|                     if (this.selectedTimeSystem.isUTCBased && timeSystem.isUTCBased) { | ||||
|                         bounds = this.openmct.time.bounds(); | ||||
|                     } else { | ||||
|                         bounds = configuration.bounds; | ||||
|                     } | ||||
|  | ||||
|                     this.openmct.time.timeSystem(timeSystem.key, bounds); | ||||
|                 } else { | ||||
|                     this.openmct.time.timeSystem(timeSystem.key); | ||||
|                     this.openmct.time.clockOffsets(configuration.clockOffsets); | ||||
|   | ||||
| @@ -70,9 +70,6 @@ $colorBodyFgEm: #fff; | ||||
| $colorGenBg: #222; | ||||
| $colorHeadBg: #262626; | ||||
| $colorHeadFg: $colorBodyFg; | ||||
| $colorStatusBarBg: $colorHeadBg; | ||||
| $colorStatusBarFg: $colorBodyFg; | ||||
| $colorStatusBarFgHov: #aaa; | ||||
| $colorKey: #0099cc; | ||||
| $colorKeyFg: #fff; | ||||
| $colorKeyHov: #26d8ff; | ||||
| @@ -101,10 +98,12 @@ $colorStatusAlertFilter: invert(78%) sepia(26%) saturate(1160%) hue-rotate(324de | ||||
| $colorStatusError: #da0004; | ||||
| $colorStatusErrorFilter: invert(10%) sepia(96%) saturate(4360%) hue-rotate(351deg) brightness(111%) contrast(115%); | ||||
| $colorStatusBtnBg: #666; // Where is this used? | ||||
| $colorStatusPartialBg: #3f5e8b; | ||||
| $colorStatusCompleteBg: #457638; | ||||
| $colorAlert: #ff3c00; | ||||
| $colorAlertFg: #fff; | ||||
| $colorWarningHi: #990000; | ||||
| $colorWarningHiFg: #FF9594; | ||||
| $colorWarningHi: #ff0000; | ||||
| $colorWarningHiFg: #ffdad0; | ||||
| $colorWarningLo: #ff9900; | ||||
| $colorWarningLoFg: #523400; | ||||
| $colorDiagnostic: #a4b442; | ||||
| @@ -115,6 +114,9 @@ $colorInfo: #2294a2; | ||||
| $colorInfoFg: #fff; | ||||
| $colorOk: #33cc33; | ||||
| $colorOkFg: #fff; | ||||
| $colorFilterBg: #44449c; | ||||
| $colorFilterFg: #8984e9; | ||||
| $colorFilter: $colorFilterFg; // Standalone against $colorBodyBg | ||||
|  | ||||
| // States | ||||
| $colorPausedBg: #ff9900; | ||||
| @@ -210,6 +212,10 @@ $btnStdH: 24px; | ||||
| $colorCursorGuide: rgba(white, 0.6); | ||||
| $shdwCursorGuide: rgba(black, 0.4) 0 0 2px; | ||||
| $colorLocalControlOvrBg: rgba($colorBodyBg, 0.8); | ||||
| $colorSelectBg: $colorBtnBg; // This must be a solid color, not a gradient, due to usage of SVG bg in selects | ||||
| $colorSelectFg: $colorBtnFg; | ||||
| $colorSelectArw: lighten($colorBtnBg, 20%); | ||||
| $shdwSelect: rgba(black, 0.5) 0 0.5px 3px; | ||||
|  | ||||
| // Menus | ||||
| $colorMenuBg: pullForward($colorBodyBg, 15%); | ||||
| @@ -277,6 +283,11 @@ $colorIndicatorAvailable: $colorKey; | ||||
| $colorIndicatorDisabled: #555555; | ||||
| $colorIndicatorOn: $colorOk; | ||||
| $colorIndicatorOff: #777777; | ||||
| $colorIndicatorBgHov: rgba($colorHeadFg, 0.1); | ||||
| $colorIndicatorMenuBg: $colorHeadBg; | ||||
| $colorIndicatorMenuBgShdw: rgba(white, 0.6) 0 0 6px; | ||||
| $colorIndicatorMenuFg: $colorHeadFg; | ||||
| $colorIndicatorMenuFgHov: pullForward($colorHeadFg, 10%); | ||||
|  | ||||
| // Staleness | ||||
| $colorTelemFresh: pullForward($colorBodyFg, 20%); | ||||
| @@ -419,7 +430,3 @@ $createBtnTextTransform: uppercase; | ||||
|     background: linear-gradient(pullForward($c, 5%), $c); | ||||
|     box-shadow: rgba(black, 0.5) 0 0.5px 2px; | ||||
| } | ||||
|  | ||||
| @mixin themedSelect($bg: $colorBtnBg, $fg: $colorBtnFg) { | ||||
|     @include cSelect(linear-gradient(lighten($bg, 5%), $bg), $fg, lighten($bg, 20%), rgba(black, 0.5) 0 0.5px 3px); | ||||
| } | ||||
|   | ||||
| @@ -74,9 +74,6 @@ $colorBodyFgEm: #fff; | ||||
| $colorGenBg: #222; | ||||
| $colorHeadBg: #262626; | ||||
| $colorHeadFg: $colorBodyFg; | ||||
| $colorStatusBarBg: $colorHeadBg; | ||||
| $colorStatusBarFg: $colorBodyFg; | ||||
| $colorStatusBarFgHov: #aaa; | ||||
| $colorKey: #0099cc; | ||||
| $colorKeyFg: #fff; | ||||
| $colorKeyHov: #26d8ff; | ||||
| @@ -105,10 +102,12 @@ $colorStatusAlertFilter: invert(78%) sepia(26%) saturate(1160%) hue-rotate(324de | ||||
| $colorStatusError: #da0004; | ||||
| $colorStatusErrorFilter: invert(10%) sepia(96%) saturate(4360%) hue-rotate(351deg) brightness(111%) contrast(115%); | ||||
| $colorStatusBtnBg: #666; // Where is this used? | ||||
| $colorStatusPartialBg: #3f5e8b; | ||||
| $colorStatusCompleteBg: #457638; | ||||
| $colorAlert: #ff3c00; | ||||
| $colorAlertFg: #fff; | ||||
| $colorWarningHi: #990000; | ||||
| $colorWarningHiFg: #FF9594; | ||||
| $colorWarningHi: #ff0000; | ||||
| $colorWarningHiFg: #ffdad0; | ||||
| $colorWarningLo: #ff9900; | ||||
| $colorWarningLoFg: #523400; | ||||
| $colorDiagnostic: #a4b442; | ||||
| @@ -119,6 +118,9 @@ $colorInfo: #2294a2; | ||||
| $colorInfoFg: #fff; | ||||
| $colorOk: #33cc33; | ||||
| $colorOkFg: #fff; | ||||
| $colorFilterBg: #44449c; | ||||
| $colorFilterFg: #8984e9; | ||||
| $colorFilter: $colorFilterFg; // Standalone against $colorBodyBg | ||||
|  | ||||
| // States | ||||
| $colorPausedBg: #ff9900; | ||||
| @@ -214,6 +216,10 @@ $btnStdH: 24px; | ||||
| $colorCursorGuide: rgba(white, 0.6); | ||||
| $shdwCursorGuide: rgba(black, 0.4) 0 0 2px; | ||||
| $colorLocalControlOvrBg: rgba($colorBodyBg, 0.8); | ||||
| $colorSelectBg: $colorBtnBg; // This must be a solid color, not a gradient, due to usage of SVG bg in selects | ||||
| $colorSelectFg: $colorBtnFg; | ||||
| $colorSelectArw: lighten($colorBtnBg, 20%); | ||||
| $shdwSelect: rgba(black, 0.5) 0 0.5px 3px; | ||||
|  | ||||
| // Menus | ||||
| $colorMenuBg: pullForward($colorBodyBg, 15%); | ||||
| @@ -281,6 +287,11 @@ $colorIndicatorAvailable: $colorKey; | ||||
| $colorIndicatorDisabled: #555555; | ||||
| $colorIndicatorOn: $colorOk; | ||||
| $colorIndicatorOff: #777777; | ||||
| $colorIndicatorBgHov: rgba($colorHeadFg, 0.1); | ||||
| $colorIndicatorMenuBg: $colorHeadBg; | ||||
| $colorIndicatorMenuBgShdw: rgba(white, 0.6) 0 0 6px; | ||||
| $colorIndicatorMenuFg: $colorHeadFg; | ||||
| $colorIndicatorMenuFgHov: pullForward($colorHeadFg, 10%); | ||||
|  | ||||
| // Staleness | ||||
| $colorTelemFresh: pullForward($colorBodyFg, 20%); | ||||
| @@ -424,10 +435,6 @@ $createBtnTextTransform: uppercase; | ||||
|     box-shadow: rgba(black, 0.5) 0 0.5px 2px; | ||||
| } | ||||
|  | ||||
| @mixin themedSelect($bg: $colorBtnBg, $fg: $colorBtnFg) { | ||||
|     @include cSelect(linear-gradient(lighten($bg, 5%), $bg), $fg, lighten($bg, 20%), rgba(black, 0.5) 0 0.5px 3px); | ||||
| } | ||||
|  | ||||
| /**************************************************** OVERRIDES */ | ||||
| .c-frame { | ||||
|     &:not(.no-frame) { | ||||
|   | ||||
| @@ -70,9 +70,6 @@ $colorBodyFgEm: #333; | ||||
| $colorGenBg: #fff; | ||||
| $colorHeadBg: #eee; | ||||
| $colorHeadFg: $colorBodyFg; | ||||
| $colorStatusBarBg: #000; | ||||
| $colorStatusBarFg: #999; | ||||
| $colorStatusBarFgHov: #aaa; | ||||
| $colorKey: #0099cc; | ||||
| $colorKeyFg: #fff; | ||||
| $colorKeyHov: #00c0f6; | ||||
| @@ -101,6 +98,8 @@ $colorStatusAlertFilter: invert(89%) sepia(26%) saturate(5035%) hue-rotate(316de | ||||
| $colorStatusError: #da0004; | ||||
| $colorStatusErrorFilter: invert(8%) sepia(96%) saturate(4511%) hue-rotate(352deg) brightness(136%) contrast(114%); | ||||
| $colorStatusBtnBg: #666; // Where is this used? | ||||
| $colorStatusPartialBg: #c9d6ff; | ||||
| $colorStatusCompleteBg: #a4e4b4; | ||||
| $colorAlert: #ff3c00; | ||||
| $colorAlertFg: #fff; | ||||
| $colorWarningHi: #990000; | ||||
| @@ -115,6 +114,9 @@ $colorInfo: #2294a2; | ||||
| $colorInfoFg: #fff; | ||||
| $colorOk: #33cc33; | ||||
| $colorOkFg: #fff; | ||||
| $colorFilterBg: #a29fe2; | ||||
| $colorFilterFg: #fff; | ||||
| $colorFilter: $colorFilterBg; // Standalone against $colorBodyBg | ||||
|  | ||||
| // States | ||||
| $colorPausedBg: #ff9900; | ||||
| @@ -210,6 +212,10 @@ $btnStdH: 24px; | ||||
| $colorCursorGuide: rgba(black, 0.6); | ||||
| $shdwCursorGuide: rgba(white, 0.4) 0 0 2px; | ||||
| $colorLocalControlOvrBg: rgba($colorBodyFg, 0.8); | ||||
| $colorSelectBg: $colorBtnBg; // This must be a solid color, not a gradient, due to usage of SVG bg in selects | ||||
| $colorSelectFg: $colorBtnFg; | ||||
| $colorSelectArw: lighten($colorBtnBg, 20%); | ||||
| $shdwSelect: none; | ||||
|  | ||||
| // Menus | ||||
| $colorMenuBg: pushBack($colorBodyBg, 10%); | ||||
| @@ -277,6 +283,11 @@ $colorIndicatorAvailable: $colorKey; | ||||
| $colorIndicatorDisabled: #444; | ||||
| $colorIndicatorOn: $colorOk; | ||||
| $colorIndicatorOff: #666; | ||||
| $colorIndicatorBgHov: rgba($colorHeadFg, 0.1); | ||||
| $colorIndicatorMenuBg: white; | ||||
| $colorIndicatorMenuBgShdw: rgba(black, 0.6) 0 0 6px; | ||||
| $colorIndicatorMenuFg: $colorHeadFg; | ||||
| $colorIndicatorMenuFgHov: pullForward($colorHeadFg, 10%); | ||||
|  | ||||
| // Staleness | ||||
| $colorTelemFresh: pullForward($colorBodyFg, 20%); | ||||
| @@ -418,7 +429,3 @@ $createBtnTextTransform: uppercase; | ||||
| @mixin themedButton($c: $colorBtnBg) { | ||||
|     background: $c; | ||||
| } | ||||
|  | ||||
| @mixin themedSelect($bg: $colorBtnBg, $fg: $colorBtnFg) { | ||||
|     @include cSelect($bg, $fg, lighten($bg, 20%), none); | ||||
| } | ||||
|   | ||||
| @@ -93,7 +93,7 @@ $mobileMenuIconD: 24px; // Used | ||||
| $mobileTreeItemH: 35px; // Used | ||||
|  | ||||
| /************************** VISUAL */ | ||||
| $controlDisabledOpacity: 0.3; | ||||
| $controlDisabledOpacity: 0.5; | ||||
|  | ||||
| /************************** UI ELEMENTS */ | ||||
| /*************** Progress Bar */ | ||||
| @@ -141,6 +141,8 @@ $glyph-icon-grid: '\e922'; | ||||
| $glyph-icon-grippy-ew: '\e923'; | ||||
| $glyph-icon-columns: '\e924'; | ||||
| $glyph-icon-rows: '\e925'; | ||||
| $glyph-icon-filter: '\e926'; | ||||
| $glyph-icon-filter-outline: '\e927'; | ||||
| $glyph-icon-arrows-right-left: '\ea00'; | ||||
| $glyph-icon-arrows-up-down: '\ea01'; | ||||
| $glyph-icon-bullet: '\ea02'; | ||||
|   | ||||
| @@ -49,6 +49,21 @@ button { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     &[class*='__collapse-button'] { | ||||
|         box-shadow: none; | ||||
|         background: $splitterBtnColorBg; | ||||
|         color: $splitterBtnColorFg; | ||||
|         border-radius: $smallCr; | ||||
|         font-size: 6px; | ||||
|         line-height: 90%; | ||||
|         padding: 3px 15px; | ||||
|  | ||||
|         @include hover() { | ||||
|             background: $colorBtnBgHov; | ||||
|             color: $colorBtnFgHov; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     &.is-active { | ||||
|         background: $colorBtnActiveBg; | ||||
|         color: $colorBtnActiveFg; | ||||
| @@ -60,23 +75,23 @@ button { | ||||
|     } | ||||
| } | ||||
|  | ||||
| /********* Icon Buttons */ | ||||
| /********* Icon Buttons and Links */ | ||||
| .c-click-icon { | ||||
|     @include cClickIcon(); | ||||
| } | ||||
|  | ||||
| .c-click-link { | ||||
|     // A clickable element, typically inline, with an icon and label | ||||
|     @include cControl(); | ||||
|     cursor: pointer; | ||||
| } | ||||
|  | ||||
| .c-icon-button, | ||||
| .c-click-swatch { | ||||
|     @include cClickIconButton(); | ||||
|  | ||||
|     &--menu { | ||||
|         &:after { | ||||
|             content: $glyph-icon-arrow-down; | ||||
|             font-family: symbolsfont; | ||||
|             font-size: 0.7em; | ||||
|             margin-left: floor($interiorMarginSm * 0.8); | ||||
|             opacity: 0.5; | ||||
|         } | ||||
|         @include hasMenu(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -126,7 +141,7 @@ button { | ||||
|  | ||||
| /******************************************************** DISCLOSURE CONTROLS */ | ||||
| /********* Disclosure Button */ | ||||
| // Provides a downward arrow icon that when clicked displays a context menu | ||||
| // Provides a downward arrow icon that when clicked displays additional options and/or info. | ||||
| // Always placed AFTER an element | ||||
| .c-disclosure-button { | ||||
|     @include cClickIcon(); | ||||
| @@ -264,7 +279,10 @@ input[type=number]::-webkit-outer-spin-button { | ||||
| // SELECTS | ||||
| select { | ||||
|     @include appearanceNone(); | ||||
|     @include themedSelect(); | ||||
|     background-color: $colorSelectBg; | ||||
|     background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10'%3e%3cpath fill='%23#{svgColorFromHex($colorSelectArw)}' d='M5 5l5-5H0z'/%3e%3c/svg%3e"); | ||||
|     color: $colorSelectFg; | ||||
|     box-shadow: $shdwSelect; | ||||
|     background-repeat: no-repeat, no-repeat; | ||||
|     background-position: right .4em top 80%, 0 0; | ||||
|     border: none; | ||||
| @@ -585,15 +603,15 @@ select { | ||||
|     margin-right: $m; | ||||
| } | ||||
|  | ||||
| .c-separator { | ||||
|     @include cToolbarSeparator(); | ||||
| } | ||||
|  | ||||
| .c-toolbar { | ||||
|     > * + * { | ||||
|         margin-left: 2px; | ||||
|     } | ||||
|  | ||||
|     &__button { | ||||
|  | ||||
|     } | ||||
|  | ||||
|     &__separator { | ||||
|         @include cToolbarSeparator(); | ||||
|     } | ||||
|   | ||||
| @@ -202,6 +202,11 @@ body.desktop .has-local-controls { | ||||
|     } | ||||
| } | ||||
|  | ||||
| ::placeholder { | ||||
|     opacity: 0.5; | ||||
|     font-style: italic; | ||||
| } | ||||
|  | ||||
| /******************************************************** STATES */ | ||||
| @mixin  spinner($b: 5px, $c: $colorKey) { | ||||
|     animation-name: rotation-centered; | ||||
|   | ||||
| @@ -30,10 +30,10 @@ | ||||
| } | ||||
|  | ||||
| @font-face { | ||||
|     // Use https://icomoon.io/app with icomoon-project-openmct-symbols-12px.json to generate font files | ||||
|     // Use https://icomoon.io/app with icomoon-project-Open-MCT-Symbols-12px.json to generate font files | ||||
|     font-family: 'symbolsfont-12px'; | ||||
|     src: url('./fonts/openmct-symbols-12px.woff') format('woff'), | ||||
|     url('./fonts/openmct-symbols-12px.ttf') format('truetype'); | ||||
|     src: url('./fonts/Open-MCT-Symbols-12px.woff') format('woff'), | ||||
|     url('./fonts/Open-MCT-Symbols-12px.ttf') format('truetype'); | ||||
|     font-weight: normal; | ||||
|     font-style: normal; | ||||
| } | ||||
| @@ -77,6 +77,8 @@ | ||||
| .icon-grippy-ew {  @include glyphBefore($glyph-icon-grippy-ew); } | ||||
| .icon-columns {  @include glyphBefore($glyph-icon-columns); } | ||||
| .icon-rows {  @include glyphBefore($glyph-icon-rows); } | ||||
| .icon-filter {  @include glyphBefore($glyph-icon-filter); } | ||||
| .icon-filter-outline {  @include glyphBefore($glyph-icon-filter-outline); } | ||||
| .icon-arrows-right-left {  @include glyphBefore($glyph-icon-arrows-right-left); } | ||||
| .icon-arrows-up-down {  @include glyphBefore($glyph-icon-arrows-up-down); } | ||||
| .icon-bullet {  @include glyphBefore($glyph-icon-bullet); } | ||||
| @@ -164,6 +166,8 @@ | ||||
|  | ||||
| /************************** 12 PX CLASSES */ | ||||
| // TODO: sync with 16px redo as of 10/25/18 | ||||
| .icon-filter-12px {  @include glyphBefore($glyph-icon-filter,'symbolsfont-12px'); } | ||||
| .icon-filter-outline-12px {  @include glyphBefore($glyph-icon-filter-outline,'symbolsfont-12px'); } | ||||
| .icon-crosshair-12px {  @include glyphBefore($glyph-icon-crosshair,'symbolsfont-12px'); } | ||||
| .icon-folder-12px {  @include glyphBefore($glyph-icon-folder,'symbolsfont-12px'); } | ||||
| .icon-list-view-12px {  @include glyphBefore($glyph-icon-list-view,'symbolsfont-12px'); } | ||||
|   | ||||
| @@ -161,8 +161,7 @@ mct-plot { | ||||
|             height: auto; | ||||
|         } | ||||
|  | ||||
|         &.gl-plot-y-label, | ||||
|         &.l-plot-y-label { | ||||
|         &.gl-plot-y-label { | ||||
|             $x: -50%; | ||||
|             $r: -90deg; | ||||
|             transform-origin: 50% 0; | ||||
| @@ -172,6 +171,12 @@ mct-plot { | ||||
|             left: 0; | ||||
|             top: 50%; | ||||
|             white-space: nowrap; | ||||
|  | ||||
|             select { | ||||
|                 background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10'%3e%3cpath fill='%23#{svgColorFromHex($colorSelectArw)}' d='M0 5l5 5V0L0 5z'/%3e%3c/svg%3e"); | ||||
|                 background-position: left .4em top 50%, 0 0; | ||||
|                 padding: 1px $interiorMargin 1px 20px; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -782,126 +782,6 @@ mct-indicators mct-include { | ||||
|     display: contents; | ||||
| } | ||||
|  | ||||
| .ls-indicator { | ||||
|     $bg: rgba(white, 0.2) !important; | ||||
|     $hbg: $colorStatusBarBg; | ||||
|     $hshdw: rgba(white, 0.4) 0 0 3px; | ||||
|     $br: $controlCr; | ||||
|     $hoverYOffset: -35px; | ||||
|     background: transparent !important; | ||||
|     border-radius: $br; | ||||
|     display: inline-block; | ||||
|     position: relative; | ||||
|     padding: 1px $interiorMarginSm; // Use padding instead of margin to keep hover chatter to a minimum | ||||
|     text-transform: uppercase; | ||||
|  | ||||
|     &:before { | ||||
|         display: inline-block; | ||||
|     } | ||||
|  | ||||
|     .label { | ||||
|         // Hover bubbles that appear when hovering on an Indicator | ||||
|         display: inline-block; | ||||
|  | ||||
|         a, | ||||
|         button, | ||||
|         s-button, | ||||
|         .c-button { | ||||
|             // Make <a> in label look like buttons | ||||
|             transition: $transIn; | ||||
|             background: transparent; | ||||
|             border: 1px solid rgba($colorStatusBarFg, 0.5); | ||||
|             border-radius: $br; | ||||
|             box-sizing: border-box; | ||||
|             color: inherit; | ||||
|             font-size: inherit; | ||||
|             height: auto; | ||||
|             line-height: normal; | ||||
|             padding: 0 2px; | ||||
|             &:hover { | ||||
|                 background: $bg; | ||||
|                 color: #fff; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         [class*='icon-'] { | ||||
|             // If any elements within label include the class 'icon-*' then deal with their :before's | ||||
|             &:before { | ||||
|                 font-size: 0.8em; | ||||
|                 margin-right: $interiorMarginSm; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     &.no-collapse { | ||||
|         display: flex; | ||||
|         flex-flow: row nowrap; | ||||
|         align-items: center; | ||||
|  | ||||
|         > *, | ||||
|         &:before { | ||||
|             flex: 1 1 auto; | ||||
|         } | ||||
|  | ||||
|         &:before { | ||||
|             margin-right: $interiorMarginSm; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     &:not(.no-collapse) { | ||||
|         &:before { | ||||
|             margin-right: 0 !important; | ||||
|         } | ||||
|  | ||||
|         .label { | ||||
|             transition: all 250ms ease-in 100ms; | ||||
|             background: $hbg; | ||||
|             border-radius: $br; | ||||
|             font-size: .6rem; | ||||
|             left: 0; | ||||
|             bottom: 140%; | ||||
|             opacity: 0; | ||||
|             padding: $interiorMarginSm $interiorMargin; | ||||
|             position: absolute; | ||||
|             transform-origin: 10px 100%; | ||||
|             transform: scale(0.0); | ||||
|             white-space: nowrap; | ||||
|             z-index: 50; | ||||
|  | ||||
|             &:before { | ||||
|                 // Infobubble-style arrow element | ||||
|                 content: ''; | ||||
|                 display: block; | ||||
|                 position: absolute; | ||||
|                 top: 100%; | ||||
|                 @include triangle('down', $size: 4px, $ratio: 1, $color: $hbg); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         @include hover() { | ||||
|             background: $bg; | ||||
|  | ||||
|             .label { | ||||
|                 opacity: 1; | ||||
|                 transform: scale(1.0); | ||||
|                 transition: all 100ms ease-out 0s; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     &.float-right { | ||||
|         float: right; | ||||
|     } | ||||
| } | ||||
|  | ||||
| /* Mobile */ | ||||
| // Hide the clock indicator when we're phone portrait | ||||
| body.phone.portrait { | ||||
|     .ls-indicator.t-indicator-clock { | ||||
|         display: none; | ||||
|     } | ||||
| } | ||||
|  | ||||
| /************************************************* DATETIME UI */ | ||||
| @mixin complexFieldHolder($myW) { | ||||
|     width: $myW + $interiorMargin; | ||||
|   | ||||
| @@ -420,20 +420,9 @@ | ||||
|     } | ||||
| } | ||||
|  | ||||
| @mixin cClickIconButton() { | ||||
|     // A clickable element that just includes the icon | ||||
|     // Background is displayed on hover | ||||
|     // Padding is included to facilitate a bigger hit area | ||||
|     // Make the icon bigger relative to its container | ||||
| @mixin cClickIconButtonLayout() { | ||||
|     $pLR: 4px; | ||||
|     $pTB: 4px; | ||||
|  | ||||
|     @include cControl(); | ||||
|     background: none; | ||||
|     box-shadow: none; | ||||
|     cursor: pointer; | ||||
|     transition: $transOut; | ||||
|     border-radius: $controlCr; | ||||
|     padding: $pTB $pLR; | ||||
|  | ||||
|     &:before, | ||||
| @@ -442,6 +431,20 @@ | ||||
|         // Needed for c-togglebutton. | ||||
|         font-size: 1.25em; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @mixin cClickIconButton() { | ||||
|     // A clickable element that just includes the icon | ||||
|     // Background is displayed on hover | ||||
|     // Padding is included to facilitate a bigger hit area | ||||
|     // Make the icon bigger relative to its container | ||||
|     @include cControl(); | ||||
|     @include cClickIconButtonLayout(); | ||||
|     background: none; | ||||
|     box-shadow: none; | ||||
|     cursor: pointer; | ||||
|     transition: $transOut; | ||||
|     border-radius: $controlCr; | ||||
|  | ||||
|     @include hover() { | ||||
|         transition: $transIn; | ||||
| @@ -478,6 +481,16 @@ | ||||
|     } | ||||
| } | ||||
|  | ||||
| @mixin hasMenu() { | ||||
|     &:after { | ||||
|         content: $glyph-icon-arrow-down; | ||||
|         font-family: symbolsfont; | ||||
|         font-size: 0.7em; | ||||
|         margin-left: floor($interiorMarginSm * 0.8); | ||||
|         opacity: 0.5; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @mixin cSelect($bg, $fg, $arwClr, $shdw) { | ||||
|     $svgArwClr: str-slice(inspect($arwClr), 2, str-length(inspect($arwClr))); // Remove initial # in color value | ||||
|     background: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10'%3e%3cpath fill='%23#{$svgArwClr}' d='M5 5l5-5H0z'/%3e%3c/svg%3e"), $bg; | ||||
| @@ -572,6 +585,11 @@ | ||||
|     } | ||||
| } | ||||
|  | ||||
| @function svgColorFromHex($hexColor) { | ||||
|     // Remove initial # in color value | ||||
|     @return str-slice(inspect($hexColor), 2, str-length(inspect($hexColor))); | ||||
| } | ||||
|  | ||||
| @mixin test($c: deeppink, $a: 0.3) { | ||||
|     background: rgba($c, $a) !important; | ||||
|     background-color: rgba($c, $a) !important; | ||||
|   | ||||
| @@ -61,7 +61,7 @@ | ||||
| } | ||||
|  | ||||
| @mixin indicatorStatusColors($c) { | ||||
|     &:before, .count { | ||||
|     &:before, .c-indicator__count { | ||||
|         color: $c; | ||||
|     } | ||||
| } | ||||
| @@ -127,7 +127,7 @@ tr { | ||||
| .s-status-icon-ok:before { content: $glyph-icon-check; } | ||||
|  | ||||
| /*************************************************** INDICATOR COLORING */ | ||||
| .ls-indicator { | ||||
| .c-indicator { | ||||
|     &.s-status-info { | ||||
|         @include indicatorStatusColors($colorInfo); | ||||
|     } | ||||
| @@ -159,3 +159,16 @@ tr { | ||||
|         @include indicatorStatusColors($colorStatusError); | ||||
|     } | ||||
| } | ||||
|  | ||||
| .s-status { | ||||
|     &--partial { | ||||
|         // Partially completed things, such as a file downloading or process that's running | ||||
|         background-color: $colorStatusPartialBg; | ||||
|     } | ||||
|  | ||||
|     &--complete { | ||||
|         // Completed things, such as a file downloaded or process that's finished | ||||
|         background-color: $colorStatusCompleteBg; | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -76,23 +76,43 @@ div.c-table { | ||||
|     height: 100%; | ||||
| } | ||||
|  | ||||
| .c-table-wrapper { | ||||
|     // Wraps .c-control-bar and .c-table | ||||
|     @include abs(); | ||||
|     overflow: hidden; | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|  | ||||
|     > .c-table { | ||||
|         height: auto; | ||||
|         flex: 1 1 auto; | ||||
|     } | ||||
|  | ||||
|     > * + * { | ||||
|         margin-top: $interiorMarginSm; | ||||
|     } | ||||
| } | ||||
|  | ||||
| .c-table-control-bar { | ||||
|     display: flex; | ||||
|     flex: 0 0 auto; | ||||
|  | ||||
|     > * + * { | ||||
|         margin-left: $interiorMarginSm; | ||||
|     } | ||||
| } | ||||
|  | ||||
| .c-table { | ||||
|     // Can be used by any type of table, scrolling, LAD, etc. | ||||
|     $min-w: 50px; | ||||
|  | ||||
|     width: 100%; | ||||
|  | ||||
|     &__control-bar, | ||||
|     &__headers-w { | ||||
|         flex: 0 0 auto; | ||||
|     } | ||||
|  | ||||
|     /******************************* ELEMENTS */ | ||||
|  | ||||
|     &__control-bar { | ||||
|         margin-bottom: $interiorMarginSm; | ||||
|     } | ||||
|  | ||||
|     thead tr, | ||||
|     &.c-table__headers { | ||||
|         background: $colorTabHeaderBg; | ||||
|   | ||||
| @@ -1,19 +1,35 @@ | ||||
| { | ||||
|   "metadata": { | ||||
|     "name": "openmct-symbols-12px", | ||||
|     "name": "Open MCT Symbols 12px", | ||||
|     "lastOpened": 0, | ||||
|     "created": 1527031065005 | ||||
|     "created": 1561483556329 | ||||
|   }, | ||||
|   "iconSets": [ | ||||
|     { | ||||
|       "selection": [ | ||||
|         { | ||||
|           "order": 12, | ||||
|           "id": 10, | ||||
|           "name": "icon12-filter", | ||||
|           "prevSize": 12, | ||||
|           "code": 59686, | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 14, | ||||
|           "id": 11, | ||||
|           "name": "icon12-filter-outline", | ||||
|           "prevSize": 12, | ||||
|           "code": 59687, | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 9, | ||||
|           "id": 6, | ||||
|           "name": "icon12-crosshair", | ||||
|           "prevSize": 12, | ||||
|           "code": 59696, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 11, | ||||
| @@ -21,7 +37,7 @@ | ||||
|           "name": "icon12-grippy", | ||||
|           "prevSize": 12, | ||||
|           "code": 59697, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 10, | ||||
| @@ -29,7 +45,7 @@ | ||||
|           "name": "icon12-list-view", | ||||
|           "prevSize": 12, | ||||
|           "code": 921666, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 6, | ||||
| @@ -37,14 +53,14 @@ | ||||
|           "prevSize": 12, | ||||
|           "code": 921865, | ||||
|           "name": "icon12-folder", | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         } | ||||
|       ], | ||||
|       "id": 0, | ||||
|       "metadata": { | ||||
|         "name": "openmct-symbols-12px", | ||||
|         "name": "Open MCT Symbols 12px", | ||||
|         "importSize": { | ||||
|           "width": 279, | ||||
|           "width": 384, | ||||
|           "height": 384 | ||||
|         }, | ||||
|         "designer": "Charles Hacskaylo" | ||||
| @@ -52,6 +68,28 @@ | ||||
|       "height": 1024, | ||||
|       "prevSize": 12, | ||||
|       "icons": [ | ||||
|         { | ||||
|           "id": 10, | ||||
|           "paths": [ | ||||
|             "M853.333 0h-682.667c-94.135 0.302-170.364 76.532-170.667 170.638l-0 0.029v682.667c0.302 94.135 76.532 170.364 170.638 170.667l0.029 0h256v-341.333l-341.333-341.333h853.333l-341.333 341.333 1.067 341.333h254.933c94.135-0.302 170.364-76.532 170.667-170.638l0-0.029v-682.667c-0.302-94.135-76.532-170.364-170.638-170.667l-0.029-0z" | ||||
|           ], | ||||
|           "attrs": [], | ||||
|           "grid": 0, | ||||
|           "tags": [ | ||||
|             "icon12-filter" | ||||
|           ] | ||||
|         }, | ||||
|         { | ||||
|           "id": 11, | ||||
|           "paths": [ | ||||
|             "M853.333 0h-682.667c-94.135 0.302-170.364 76.532-170.667 170.638l-0 0.029v682.667c0.302 94.135 76.532 170.364 170.638 170.667l0.029 0h682.667c94.135-0.302 170.364-76.532 170.667-170.638l0-0.029v-682.667c-0.302-94.135-76.532-170.364-170.638-170.667l-0.029-0zM170.933 853.333h-0.267v-512l256 256v256zM853.067 853.333h-255.2l-0.533-256 256-256v511.733zM853.333 341.333h-682.667v-170.4h682.667z" | ||||
|           ], | ||||
|           "attrs": [], | ||||
|           "grid": 0, | ||||
|           "tags": [ | ||||
|             "icon12-filter-outline" | ||||
|           ] | ||||
|         }, | ||||
|         { | ||||
|           "id": 6, | ||||
|           "paths": [ | ||||
| @@ -60,26 +98,11 @@ | ||||
|             "M597.333 768h-170.667v256h170.667v-256z", | ||||
|             "M256 426.667h-256v170.667h256v-170.667z" | ||||
|           ], | ||||
|           "attrs": [ | ||||
|             {}, | ||||
|             {}, | ||||
|             {}, | ||||
|             {} | ||||
|           ], | ||||
|           "isMulticolor": false, | ||||
|           "isMulticolor2": false, | ||||
|           "attrs": [], | ||||
|           "grid": 0, | ||||
|           "tags": [ | ||||
|             "icon12-crosshair" | ||||
|           ], | ||||
|           "colorPermutations": { | ||||
|             "1161751": [ | ||||
|               {}, | ||||
|               {}, | ||||
|               {}, | ||||
|               {} | ||||
|             ] | ||||
|           } | ||||
|           ] | ||||
|         }, | ||||
|         { | ||||
|           "id": 8, | ||||
| @@ -95,39 +118,12 @@ | ||||
|             "M744.773 511.867c0 51.458-41.715 93.173-93.173 93.173s-93.173-41.715-93.173-93.173c0-51.458 41.715-93.173 93.173-93.173s93.173 41.715 93.173 93.173z", | ||||
|             "M744.773 791.36c0 51.458-41.715 93.173-93.173 93.173s-93.173-41.715-93.173-93.173c0-51.458 41.715-93.173 93.173-93.173s93.173 41.715 93.173 93.173z" | ||||
|           ], | ||||
|           "attrs": [ | ||||
|             {}, | ||||
|             {}, | ||||
|             {}, | ||||
|             {}, | ||||
|             {}, | ||||
|             {}, | ||||
|             {}, | ||||
|             {}, | ||||
|             {}, | ||||
|             {} | ||||
|           ], | ||||
|           "attrs": [], | ||||
|           "width": 745, | ||||
|           "isMulticolor": false, | ||||
|           "isMulticolor2": false, | ||||
|           "grid": 0, | ||||
|           "tags": [ | ||||
|             "icon12-grippy" | ||||
|           ], | ||||
|           "colorPermutations": { | ||||
|             "1161751": [ | ||||
|               {}, | ||||
|               {}, | ||||
|               {}, | ||||
|               {}, | ||||
|               {}, | ||||
|               {}, | ||||
|               {}, | ||||
|               {}, | ||||
|               {}, | ||||
|               {} | ||||
|             ] | ||||
|           } | ||||
|           ] | ||||
|         }, | ||||
|         { | ||||
|           "id": 7, | ||||
| @@ -136,24 +132,11 @@ | ||||
|             "M0 426.667h1024v170.667h-1024v-170.667z", | ||||
|             "M0 853.333h1024v170.667h-1024v-170.667z" | ||||
|           ], | ||||
|           "attrs": [ | ||||
|             {}, | ||||
|             {}, | ||||
|             {} | ||||
|           ], | ||||
|           "isMulticolor": false, | ||||
|           "isMulticolor2": false, | ||||
|           "attrs": [], | ||||
|           "grid": 0, | ||||
|           "tags": [ | ||||
|             "icon12-list-view" | ||||
|           ], | ||||
|           "colorPermutations": { | ||||
|             "1161751": [ | ||||
|               {}, | ||||
|               {}, | ||||
|               {} | ||||
|             ] | ||||
|           } | ||||
|           ] | ||||
|         }, | ||||
|         { | ||||
|           "id": 3, | ||||
| @@ -162,40 +145,14 @@ | ||||
|             "M85.333 426.667h853.333c47.128 0 85.333 38.205 85.333 85.333v426.667c0 47.128-38.205 85.333-85.333 85.333h-853.333c-47.128 0-85.333-38.205-85.333-85.333v-426.667c0-47.128 38.205-85.333 85.333-85.333z" | ||||
|           ], | ||||
|           "attrs": [], | ||||
|           "isMulticolor": false, | ||||
|           "grid": 0, | ||||
|           "tags": [ | ||||
|             "icon12-folder" | ||||
|           ], | ||||
|           "colorPermutations": { | ||||
|             "1161751": [ | ||||
|               { | ||||
|                 "f": 0 | ||||
|               }, | ||||
|               { | ||||
|                 "f": 0 | ||||
|               } | ||||
|             ] | ||||
|           } | ||||
|           ] | ||||
|         } | ||||
|       ], | ||||
|       "invisible": false, | ||||
|       "colorThemes": [ | ||||
|         [ | ||||
|           [ | ||||
|             0, | ||||
|             0, | ||||
|             0, | ||||
|             1 | ||||
|           ], | ||||
|           [ | ||||
|             0, | ||||
|             161, | ||||
|             75, | ||||
|             1 | ||||
|           ] | ||||
|         ] | ||||
|       ], | ||||
|       "colorThemes": [], | ||||
|       "colorThemeIdx": 0 | ||||
|     } | ||||
|   ], | ||||
| @@ -206,9 +163,9 @@ | ||||
|     "showQuickUse2": true, | ||||
|     "showSVGs": true, | ||||
|     "fontPref": { | ||||
|       "prefix": "icon-", | ||||
|       "prefix": "openmct-symbols-", | ||||
|       "metadata": { | ||||
|         "fontFamily": "openmct-symbols-12px", | ||||
|         "fontFamily": "Open-MCT-Symbols-12px", | ||||
|         "majorVersion": 1, | ||||
|         "minorVersion": 0 | ||||
|       }, | ||||
| @@ -217,7 +174,12 @@ | ||||
|         "baseline": 6.25, | ||||
|         "whitespace": 50 | ||||
|       }, | ||||
|       "embed": false | ||||
|       "embed": false, | ||||
|       "noie8": true, | ||||
|       "ie7": false, | ||||
|       "showMetadata": false, | ||||
|       "includeMetadata": false, | ||||
|       "showMetrics": true | ||||
|     }, | ||||
|     "imagePref": { | ||||
|       "prefix": "icon-", | ||||
| @@ -1,8 +1,8 @@ | ||||
| { | ||||
|   "metadata": { | ||||
|     "name": "Open MCT Symbols", | ||||
|     "name": "Open MCT Symbols 16px", | ||||
|     "lastOpened": 0, | ||||
|     "created": 1541830438012 | ||||
|     "created": 1561482854927 | ||||
|   }, | ||||
|   "iconSets": [ | ||||
|     { | ||||
| @@ -317,13 +317,29 @@ | ||||
|           "code": 59685, | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 161, | ||||
|           "id": 142, | ||||
|           "name": "icon-filter", | ||||
|           "prevSize": 32, | ||||
|           "code": 59686, | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 162, | ||||
|           "id": 141, | ||||
|           "name": "icon-filter-outline", | ||||
|           "prevSize": 32, | ||||
|           "code": 59687, | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 27, | ||||
|           "id": 105, | ||||
|           "name": "icon-arrows-right-left", | ||||
|           "prevSize": 32, | ||||
|           "code": 59904, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 26, | ||||
| @@ -331,7 +347,7 @@ | ||||
|           "name": "icon-arrows-up-down", | ||||
|           "prevSize": 32, | ||||
|           "code": 59905, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 68, | ||||
| @@ -339,7 +355,7 @@ | ||||
|           "name": "icon-bullet", | ||||
|           "prevSize": 32, | ||||
|           "code": 59906, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 150, | ||||
| @@ -347,7 +363,7 @@ | ||||
|           "prevSize": 32, | ||||
|           "code": 59907, | ||||
|           "name": "icon-calendar", | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 45, | ||||
| @@ -355,7 +371,7 @@ | ||||
|           "name": "icon-chain-links", | ||||
|           "prevSize": 32, | ||||
|           "code": 59908, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 73, | ||||
| @@ -363,7 +379,7 @@ | ||||
|           "name": "icon-download", | ||||
|           "prevSize": 32, | ||||
|           "code": 59909, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 39, | ||||
| @@ -371,7 +387,7 @@ | ||||
|           "name": "icon-duplicate", | ||||
|           "prevSize": 32, | ||||
|           "code": 59910, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 50, | ||||
| @@ -379,7 +395,7 @@ | ||||
|           "name": "icon-folder-new", | ||||
|           "prevSize": 32, | ||||
|           "code": 59911, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 138, | ||||
| @@ -387,7 +403,7 @@ | ||||
|           "name": "icon-fullscreen-collapse", | ||||
|           "prevSize": 32, | ||||
|           "code": 59912, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 139, | ||||
| @@ -395,7 +411,7 @@ | ||||
|           "name": "icon-fullscreen-expand", | ||||
|           "prevSize": 32, | ||||
|           "code": 59913, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 122, | ||||
| @@ -403,7 +419,7 @@ | ||||
|           "name": "icon-layers", | ||||
|           "prevSize": 32, | ||||
|           "code": 59914, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 151, | ||||
| @@ -411,7 +427,7 @@ | ||||
|           "name": "icon-line-horz", | ||||
|           "prevSize": 32, | ||||
|           "code": 59915, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 100, | ||||
| @@ -419,7 +435,7 @@ | ||||
|           "name": "icon-magnify", | ||||
|           "prevSize": 32, | ||||
|           "code": 59916, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 99, | ||||
| @@ -427,7 +443,7 @@ | ||||
|           "name": "icon-magnify-in", | ||||
|           "prevSize": 32, | ||||
|           "code": 59917, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 101, | ||||
| @@ -435,7 +451,7 @@ | ||||
|           "name": "icon-magnify-out-v2", | ||||
|           "prevSize": 32, | ||||
|           "code": 59918, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 103, | ||||
| @@ -443,7 +459,7 @@ | ||||
|           "name": "icon-menu", | ||||
|           "prevSize": 32, | ||||
|           "code": 59919, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 124, | ||||
| @@ -451,7 +467,7 @@ | ||||
|           "name": "icon-move", | ||||
|           "prevSize": 32, | ||||
|           "code": 59920, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 7, | ||||
| @@ -459,7 +475,7 @@ | ||||
|           "name": "icon-new-window", | ||||
|           "prevSize": 32, | ||||
|           "code": 59921, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 63, | ||||
| @@ -467,7 +483,7 @@ | ||||
|           "name": "icon-paint-bucket-v2", | ||||
|           "prevSize": 32, | ||||
|           "code": 59922, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 15, | ||||
| @@ -475,7 +491,7 @@ | ||||
|           "name": "icon-pencil", | ||||
|           "prevSize": 32, | ||||
|           "code": 59923, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 54, | ||||
| @@ -483,7 +499,7 @@ | ||||
|           "name": "icon-pencil-edit-in-place", | ||||
|           "prevSize": 32, | ||||
|           "code": 59924, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 40, | ||||
| @@ -491,7 +507,7 @@ | ||||
|           "name": "icon-play", | ||||
|           "prevSize": 32, | ||||
|           "code": 59925, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 125, | ||||
| @@ -499,7 +515,7 @@ | ||||
|           "name": "icon-pause", | ||||
|           "prevSize": 32, | ||||
|           "code": 59926, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 119, | ||||
| @@ -507,7 +523,7 @@ | ||||
|           "name": "icon-plot-resource", | ||||
|           "prevSize": 32, | ||||
|           "code": 59927, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 48, | ||||
| @@ -515,7 +531,7 @@ | ||||
|           "name": "icon-pointer-left", | ||||
|           "prevSize": 32, | ||||
|           "code": 59928, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 47, | ||||
| @@ -523,7 +539,7 @@ | ||||
|           "name": "icon-pointer-right", | ||||
|           "prevSize": 32, | ||||
|           "code": 59929, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 85, | ||||
| @@ -531,7 +547,7 @@ | ||||
|           "name": "icon-refresh", | ||||
|           "prevSize": 32, | ||||
|           "code": 59930, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 55, | ||||
| @@ -539,7 +555,7 @@ | ||||
|           "name": "icon-save", | ||||
|           "prevSize": 32, | ||||
|           "code": 59931, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 56, | ||||
| @@ -547,7 +563,7 @@ | ||||
|           "name": "icon-save-as", | ||||
|           "prevSize": 32, | ||||
|           "code": 59932, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 58, | ||||
| @@ -555,7 +571,7 @@ | ||||
|           "name": "icon-sine", | ||||
|           "prevSize": 32, | ||||
|           "code": 59933, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 113, | ||||
| @@ -563,7 +579,7 @@ | ||||
|           "name": "icon-font", | ||||
|           "prevSize": 32, | ||||
|           "code": 59934, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 41, | ||||
| @@ -571,7 +587,7 @@ | ||||
|           "name": "icon-thumbs-strip", | ||||
|           "prevSize": 32, | ||||
|           "code": 59935, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 146, | ||||
| @@ -579,7 +595,7 @@ | ||||
|           "name": "icon-two-parts-both", | ||||
|           "prevSize": 32, | ||||
|           "code": 59936, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 145, | ||||
| @@ -587,7 +603,7 @@ | ||||
|           "name": "icon-two-parts-one-only", | ||||
|           "prevSize": 32, | ||||
|           "code": 59937, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 82, | ||||
| @@ -595,7 +611,7 @@ | ||||
|           "name": "icon-resync", | ||||
|           "prevSize": 32, | ||||
|           "code": 59938, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 86, | ||||
| @@ -603,7 +619,7 @@ | ||||
|           "name": "icon-reset", | ||||
|           "prevSize": 32, | ||||
|           "code": 59939, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 61, | ||||
| @@ -611,7 +627,7 @@ | ||||
|           "name": "icon-x-in-circle", | ||||
|           "prevSize": 32, | ||||
|           "code": 59940, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 84, | ||||
| @@ -619,7 +635,7 @@ | ||||
|           "name": "icon-brightness", | ||||
|           "prevSize": 32, | ||||
|           "code": 59941, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 83, | ||||
| @@ -627,7 +643,7 @@ | ||||
|           "name": "icon-contrast", | ||||
|           "prevSize": 32, | ||||
|           "code": 59942, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 87, | ||||
| @@ -635,7 +651,7 @@ | ||||
|           "name": "icon-expand", | ||||
|           "prevSize": 32, | ||||
|           "code": 59943, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 89, | ||||
| @@ -643,7 +659,7 @@ | ||||
|           "name": "icon-list-view", | ||||
|           "prevSize": 32, | ||||
|           "code": 59944, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 133, | ||||
| @@ -651,7 +667,7 @@ | ||||
|           "name": "icon-grid-snap-to", | ||||
|           "prevSize": 32, | ||||
|           "code": 59945, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 132, | ||||
| @@ -659,7 +675,7 @@ | ||||
|           "name": "icon-grid-snap-no", | ||||
|           "prevSize": 32, | ||||
|           "code": 59946, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 94, | ||||
| @@ -667,7 +683,7 @@ | ||||
|           "name": "icon-frame-show", | ||||
|           "prevSize": 32, | ||||
|           "code": 59947, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 95, | ||||
| @@ -675,7 +691,7 @@ | ||||
|           "name": "icon-frame-hide", | ||||
|           "prevSize": 32, | ||||
|           "code": 59948, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 97, | ||||
| @@ -683,7 +699,7 @@ | ||||
|           "name": "icon-import", | ||||
|           "prevSize": 32, | ||||
|           "code": 59949, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 96, | ||||
| @@ -691,7 +707,7 @@ | ||||
|           "name": "icon-export", | ||||
|           "prevSize": 32, | ||||
|           "code": 59950, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 114, | ||||
| @@ -699,7 +715,7 @@ | ||||
|           "name": "icon-font-size", | ||||
|           "prevSize": 32, | ||||
|           "code": 59951, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 144, | ||||
| @@ -707,7 +723,7 @@ | ||||
|           "name": "icon-activity", | ||||
|           "prevSize": 32, | ||||
|           "code": 60160, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 104, | ||||
| @@ -715,7 +731,7 @@ | ||||
|           "name": "icon-activity-mode", | ||||
|           "prevSize": 32, | ||||
|           "code": 60161, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 137, | ||||
| @@ -723,7 +739,7 @@ | ||||
|           "name": "icon-autoflow-tabular", | ||||
|           "prevSize": 32, | ||||
|           "code": 60162, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 115, | ||||
| @@ -731,7 +747,7 @@ | ||||
|           "name": "icon-clock", | ||||
|           "prevSize": 32, | ||||
|           "code": 60163, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 2, | ||||
| @@ -739,7 +755,7 @@ | ||||
|           "name": "icon-database", | ||||
|           "prevSize": 32, | ||||
|           "code": 60164, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 3, | ||||
| @@ -747,7 +763,7 @@ | ||||
|           "name": "icon-database-query", | ||||
|           "prevSize": 32, | ||||
|           "code": 60165, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 67, | ||||
| @@ -755,7 +771,7 @@ | ||||
|           "name": "icon-dataset", | ||||
|           "prevSize": 32, | ||||
|           "code": 60166, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 59, | ||||
| @@ -763,7 +779,7 @@ | ||||
|           "name": "icon-datatable", | ||||
|           "prevSize": 32, | ||||
|           "code": 60167, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 136, | ||||
| @@ -771,7 +787,7 @@ | ||||
|           "name": "icon-dictionary", | ||||
|           "prevSize": 32, | ||||
|           "code": 60168, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 51, | ||||
| @@ -779,7 +795,7 @@ | ||||
|           "name": "icon-folder", | ||||
|           "prevSize": 32, | ||||
|           "code": 60169, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 147, | ||||
| @@ -787,7 +803,7 @@ | ||||
|           "name": "icon-image", | ||||
|           "prevSize": 32, | ||||
|           "code": 60170, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 4, | ||||
| @@ -795,7 +811,7 @@ | ||||
|           "name": "icon-layout", | ||||
|           "prevSize": 32, | ||||
|           "code": 60171, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 24, | ||||
| @@ -803,7 +819,7 @@ | ||||
|           "name": "icon-object", | ||||
|           "prevSize": 32, | ||||
|           "code": 60172, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 52, | ||||
| @@ -811,7 +827,7 @@ | ||||
|           "name": "icon-object-unknown", | ||||
|           "prevSize": 32, | ||||
|           "code": 60173, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 105, | ||||
| @@ -819,7 +835,7 @@ | ||||
|           "name": "icon-packet", | ||||
|           "prevSize": 32, | ||||
|           "code": 60174, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 126, | ||||
| @@ -827,7 +843,7 @@ | ||||
|           "name": "icon-page", | ||||
|           "prevSize": 32, | ||||
|           "code": 60175, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 130, | ||||
| @@ -835,7 +851,7 @@ | ||||
|           "name": "icon-plot-overlay", | ||||
|           "prevSize": 32, | ||||
|           "code": 60176, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 80, | ||||
| @@ -843,7 +859,7 @@ | ||||
|           "name": "icon-plot-stacked", | ||||
|           "prevSize": 32, | ||||
|           "code": 60177, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 134, | ||||
| @@ -851,7 +867,7 @@ | ||||
|           "name": "icon-session", | ||||
|           "prevSize": 32, | ||||
|           "code": 60178, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 109, | ||||
| @@ -859,7 +875,7 @@ | ||||
|           "name": "icon-tabular", | ||||
|           "prevSize": 32, | ||||
|           "code": 60179, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 107, | ||||
| @@ -867,7 +883,7 @@ | ||||
|           "name": "icon-tabular-lad", | ||||
|           "prevSize": 32, | ||||
|           "code": 60180, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 106, | ||||
| @@ -875,7 +891,7 @@ | ||||
|           "name": "icon-tabular-lad-set", | ||||
|           "prevSize": 32, | ||||
|           "code": 60181, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 70, | ||||
| @@ -883,7 +899,7 @@ | ||||
|           "name": "icon-tabular-realtime", | ||||
|           "prevSize": 32, | ||||
|           "code": 60182, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 60, | ||||
| @@ -891,7 +907,7 @@ | ||||
|           "name": "icon-tabular-scrolling", | ||||
|           "prevSize": 32, | ||||
|           "code": 60183, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 131, | ||||
| @@ -899,7 +915,7 @@ | ||||
|           "name": "icon-telemetry", | ||||
|           "prevSize": 32, | ||||
|           "code": 60184, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 108, | ||||
| @@ -907,7 +923,7 @@ | ||||
|           "name": "icon-timeline", | ||||
|           "prevSize": 32, | ||||
|           "code": 60185, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 81, | ||||
| @@ -915,7 +931,7 @@ | ||||
|           "name": "icon-timer", | ||||
|           "prevSize": 32, | ||||
|           "code": 60186, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 69, | ||||
| @@ -923,7 +939,7 @@ | ||||
|           "name": "icon-topic", | ||||
|           "prevSize": 32, | ||||
|           "code": 60187, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 79, | ||||
| @@ -931,7 +947,7 @@ | ||||
|           "name": "icon-box-with-dashed-lines-v2", | ||||
|           "prevSize": 32, | ||||
|           "code": 60188, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 90, | ||||
| @@ -939,7 +955,7 @@ | ||||
|           "name": "icon-summary-widget", | ||||
|           "prevSize": 32, | ||||
|           "code": 60189, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 92, | ||||
| @@ -947,7 +963,7 @@ | ||||
|           "name": "icon-notebook", | ||||
|           "prevSize": 32, | ||||
|           "code": 60190, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 118, | ||||
| @@ -955,7 +971,7 @@ | ||||
|           "name": "icon-tabs-view", | ||||
|           "prevSize": 32, | ||||
|           "code": 60191, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 117, | ||||
| @@ -963,7 +979,7 @@ | ||||
|           "name": "icon-flexible-layout", | ||||
|           "prevSize": 32, | ||||
|           "code": 60192, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 152, | ||||
| @@ -971,7 +987,7 @@ | ||||
|           "name": "icon-generator-sine", | ||||
|           "prevSize": 32, | ||||
|           "code": 60193, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 153, | ||||
| @@ -979,7 +995,7 @@ | ||||
|           "name": "icon-generator-event", | ||||
|           "prevSize": 32, | ||||
|           "code": 60194, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 160, | ||||
| @@ -987,7 +1003,7 @@ | ||||
|           "name": "icon-gauge", | ||||
|           "prevSize": 32, | ||||
|           "code": 60195, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         } | ||||
|       ], | ||||
|       "id": 0, | ||||
| @@ -1602,6 +1618,46 @@ | ||||
|             ] | ||||
|           } | ||||
|         }, | ||||
|         { | ||||
|           "id": 142, | ||||
|           "paths": [ | ||||
|             "M896 0h-768c-70.601 0.227-127.773 57.399-128 127.978l-0 0.022v768c0.227 70.601 57.399 127.773 127.978 128l0.022 0h256v-512l-192-192h640l-192 192v512h256c70.601-0.227 127.773-57.399 128-127.978l0-0.022v-768c-0.227-70.601-57.399-127.773-127.978-128l-0.022-0z" | ||||
|           ], | ||||
|           "attrs": [ | ||||
|             {} | ||||
|           ], | ||||
|           "isMulticolor": false, | ||||
|           "isMulticolor2": false, | ||||
|           "grid": 1, | ||||
|           "tags": [ | ||||
|             "icon-filter" | ||||
|           ], | ||||
|           "colorPermutations": { | ||||
|             "11841841841": [ | ||||
|               {} | ||||
|             ] | ||||
|           } | ||||
|         }, | ||||
|         { | ||||
|           "id": 141, | ||||
|           "paths": [ | ||||
|             "M896 0h-768c-70.601 0.227-127.773 57.399-128 127.978l-0 0.022v768c0.227 70.601 57.399 127.773 127.978 128l0.022 0h768c70.601-0.227 127.773-57.399 128-127.978l0-0.022v-768c-0.227-70.601-57.399-127.773-127.978-128l-0.022-0zM896 895.8h-256v-383.8l192-192h-640l192 192v384h-256v-767.8h768z" | ||||
|           ], | ||||
|           "attrs": [ | ||||
|             {} | ||||
|           ], | ||||
|           "isMulticolor": false, | ||||
|           "isMulticolor2": false, | ||||
|           "grid": 1, | ||||
|           "tags": [ | ||||
|             "icon-filter-outline" | ||||
|           ], | ||||
|           "colorPermutations": { | ||||
|             "11841841841": [ | ||||
|               {} | ||||
|             ] | ||||
|           } | ||||
|         }, | ||||
|         { | ||||
|           "id": 105, | ||||
|           "paths": [ | ||||
							
								
								
									
										
											BIN
										
									
								
								src/styles-new/fonts/Open-MCT-Symbols-12px.ttf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/styles-new/fonts/Open-MCT-Symbols-12px.ttf
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								src/styles-new/fonts/Open-MCT-Symbols-12px.woff
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/styles-new/fonts/Open-MCT-Symbols-12px.woff
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user