Compare commits
	
		
			19 Commits
		
	
	
		
			table-fixe
			...
			dialog-ser
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | be5da7fa72 | ||
|   | 5e3acc4363 | ||
|   | 196f49fca6 | ||
|   | f9cb753fce | ||
|   | 9bc1630cb4 | ||
|   | 9a6090cd02 | ||
|   | f40c9fa6f9 | ||
|   | e7cdb334de | ||
|   | afca6cd2e9 | ||
|   | 3c324cbea0 | ||
|   | 56b9708ab7 | ||
|   | e6e5b6a64a | ||
|   | 58da916d18 | ||
|   | a0327b56aa | ||
|   | 57d60128a2 | ||
|   | 987740c649 | ||
|   | 944505a5f1 | ||
|   | c5187d8509 | ||
|   | c7b73bdc3f | 
							
								
								
									
										2
									
								
								app.js
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								app.js
									
									
									
									
									
								
							| @@ -16,7 +16,7 @@ const request = require('request'); | ||||
|  | ||||
| // Defaults | ||||
| options.port = options.port || options.p || 8080; | ||||
| options.host = options.host || options.h || 'localhost' | ||||
| options.host = options.host || options.h || 'localhost'; | ||||
| options.directory = options.directory || options.D || '.'; | ||||
|  | ||||
| // Show command line options | ||||
|   | ||||
| @@ -75,6 +75,7 @@ | ||||
|         })); | ||||
|         openmct.install(openmct.plugins.SummaryWidget()); | ||||
|         openmct.install(openmct.plugins.Notebook()); | ||||
|         openmct.install(openmct.plugins.FolderView()); | ||||
|         openmct.time.clock('local', {start: -THIRTY_MINUTES, end: 0}); | ||||
|         openmct.time.timeSystem('utc'); | ||||
|         openmct.start(); | ||||
|   | ||||
| @@ -35,12 +35,10 @@ define([ | ||||
|     "./src/windowing/WindowTitler", | ||||
|     "./res/templates/browse.html", | ||||
|     "./res/templates/browse-object.html", | ||||
|     "./res/templates/items/grid-item.html", | ||||
|     "./res/templates/browse/object-header.html", | ||||
|     "./res/templates/browse/object-header-frame.html", | ||||
|     "./res/templates/menu-arrow.html", | ||||
|     "./res/templates/back-arrow.html", | ||||
|     "./res/templates/items/items.html", | ||||
|     "./res/templates/browse/object-properties.html", | ||||
|     "./res/templates/browse/inspector-region.html", | ||||
|     'legacyRegistry' | ||||
| @@ -59,12 +57,10 @@ define([ | ||||
|     WindowTitler, | ||||
|     browseTemplate, | ||||
|     browseObjectTemplate, | ||||
|     gridItemTemplate, | ||||
|     objectHeaderTemplate, | ||||
|     objectHeaderFrameTemplate, | ||||
|     menuArrowTemplate, | ||||
|     backArrowTemplate, | ||||
|     itemsTemplate, | ||||
|     objectPropertiesTemplate, | ||||
|     inspectorRegionTemplate, | ||||
|     legacyRegistry | ||||
| @@ -156,19 +152,6 @@ define([ | ||||
|                         "view" | ||||
|                     ] | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "grid-item", | ||||
|                     "template": gridItemTemplate, | ||||
|                     "uses": [ | ||||
|                         "type", | ||||
|                         "action", | ||||
|                         "location" | ||||
|                     ], | ||||
|                     "gestures": [ | ||||
|                         "info", | ||||
|                         "menu" | ||||
|                     ] | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "object-header", | ||||
|                     "template": objectHeaderTemplate, | ||||
| @@ -251,23 +234,6 @@ define([ | ||||
|                     "priority": "default" | ||||
|                 } | ||||
|             ], | ||||
|             "views": [ | ||||
|                 { | ||||
|                     "key": "items", | ||||
|                     "name": "Grid", | ||||
|                     "cssClass": "icon-thumbs-strip", | ||||
|                     "description": "Grid of available items", | ||||
|                     "template": itemsTemplate, | ||||
|                     "uses": [ | ||||
|                         "composition" | ||||
|                     ], | ||||
|                     "gestures": [ | ||||
|                         "drop" | ||||
|                     ], | ||||
|                     "type": "folder", | ||||
|                     "editable": false | ||||
|                 } | ||||
|             ], | ||||
|             "runs": [ | ||||
|                 { | ||||
|                     "implementation": WindowTitler, | ||||
|   | ||||
| @@ -1,45 +0,0 @@ | ||||
| <!-- | ||||
|  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. | ||||
| --> | ||||
| <!-- For selected, add class 'selected' to outer div --> | ||||
| <div class='item grid-item' ng-click='action.perform("navigate")'> | ||||
|     <div class='contents abs'> | ||||
|         <div class='top-bar bar abs'> | ||||
|             <span class='icon-people' title='Shared'></span> | ||||
|             <mct-representation class="desktop-hide" key="'info-button'" mct-object="domainObject"></mct-representation> | ||||
|         </div> | ||||
|         <div class='item-main abs lg'> | ||||
|             <span class="t-item-icon" ng-class="{ 'l-icon-link':location.isLink() }"> | ||||
|                 <span class="t-item-icon-glyph ng-binding {{type.getCssClass()}}"></span> | ||||
|             </span> | ||||
|             <div class='abs item-open icon-pointer-right'></div> | ||||
|         </div> | ||||
|         <div class='bottom-bar bar abs'> | ||||
|             <div class='title'>{{model.name}}</div> | ||||
|             <div class='details'> | ||||
|                 <span>{{type.getName()}}</span> | ||||
|                 <span ng-show="model.composition !== undefined"> | ||||
|                     - {{model.composition.length}} Item<span ng-show="model.composition.length > 1">s</span> | ||||
|                 </span> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
| </div> | ||||
| @@ -1,27 +0,0 @@ | ||||
| <!-- | ||||
|  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. | ||||
| --> | ||||
| <div class='items-holder grid abs'> | ||||
|     <mct-representation key="'grid-item'" | ||||
|                         ng-repeat="childObject in composition" | ||||
|                         mct-object="childObject"> | ||||
|     </mct-representation> | ||||
| </div> | ||||
| @@ -58,7 +58,7 @@ define([], function () { | ||||
|  | ||||
|         function checkNavigation() { | ||||
|             var navigatedObject = navigationService.getNavigation(); | ||||
|             if (navigatedObject.hasCapability('context')) { | ||||
|             if (navigatedObject && navigatedObject.hasCapability('context')) { | ||||
|                 if (!navigatedObject.getCapability('editor').isEditContextRoot()) { | ||||
|                     preventOrphanNavigation(navigatedObject); | ||||
|                 } | ||||
|   | ||||
| @@ -405,7 +405,8 @@ define([ | ||||
|                     "description": "Provides transactional editing capabilities", | ||||
|                     "implementation": EditorCapability, | ||||
|                     "depends": [ | ||||
|                         "transactionService" | ||||
|                         "transactionService", | ||||
|                         "openmct" | ||||
|                     ] | ||||
|                 } | ||||
|             ], | ||||
|   | ||||
| @@ -36,9 +36,11 @@ define( | ||||
|          */ | ||||
|         function EditorCapability( | ||||
|             transactionService, | ||||
|             openmct, | ||||
|             domainObject | ||||
|         ) { | ||||
|             this.transactionService = transactionService; | ||||
|             this.openmct = openmct; | ||||
|             this.domainObject = domainObject; | ||||
|         } | ||||
|  | ||||
| @@ -48,27 +50,19 @@ define( | ||||
|          * or finish() are called. | ||||
|          */ | ||||
|         EditorCapability.prototype.edit = function () { | ||||
|             this.transactionService.startTransaction(); | ||||
|             console.warn('DEPRECATED: cannot edit via edit capability, use openmct.editor instead.'); | ||||
|             this.openmct.editor.edit(); | ||||
|             this.domainObject.getCapability('status').set('editing', true); | ||||
|         }; | ||||
|  | ||||
|         function isEditContextRoot(domainObject) { | ||||
|             return domainObject.getCapability('status').get('editing'); | ||||
|         } | ||||
|  | ||||
|         function isEditing(domainObject) { | ||||
|             return isEditContextRoot(domainObject) || | ||||
|                 domainObject.hasCapability('context') && | ||||
|                 isEditing(domainObject.getCapability('context').getParent()); | ||||
|         } | ||||
|  | ||||
|         /** | ||||
|          * Determines whether this object, or any of its ancestors are | ||||
|          * currently being edited. | ||||
|          * @returns boolean | ||||
|          */ | ||||
|         EditorCapability.prototype.inEditContext = function () { | ||||
|             return isEditing(this.domainObject); | ||||
|             console.warn('DEPRECATION WARNING: isEditing checks must be done via openmct.editor.'); | ||||
|             return this.openmct.editor.isEditing(); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
| @@ -77,7 +71,8 @@ define( | ||||
|          * @returns {*} | ||||
|          */ | ||||
|         EditorCapability.prototype.isEditContextRoot = function () { | ||||
|             return isEditContextRoot(this.domainObject); | ||||
|             console.warn('DEPRECATION WARNING: isEditing checks must be done via openmct.editor.'); | ||||
|             return this.openmct.editor.isEditing(); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
| @@ -86,10 +81,7 @@ define( | ||||
|          * @returns {*} | ||||
|          */ | ||||
|         EditorCapability.prototype.save = function () { | ||||
|             var transactionService = this.transactionService; | ||||
|             return transactionService.commit().then(function () { | ||||
|                 transactionService.startTransaction(); | ||||
|             }); | ||||
|             console.warn('DEPRECATED: cannot save via edit capability, use openmct.editor instead.'); | ||||
|         }; | ||||
|  | ||||
|         EditorCapability.prototype.invoke = EditorCapability.prototype.edit; | ||||
| @@ -100,16 +92,7 @@ define( | ||||
|          * @returns {*} | ||||
|          */ | ||||
|         EditorCapability.prototype.finish = function () { | ||||
|             var domainObject = this.domainObject; | ||||
|  | ||||
|             if (this.transactionService.isActive()) { | ||||
|                 return this.transactionService.cancel().then(function () { | ||||
|                     domainObject.getCapability("status").set("editing", false); | ||||
|                     return domainObject; | ||||
|                 }); | ||||
|             } else { | ||||
|                 return Promise.resolve(domainObject); | ||||
|             } | ||||
|             console.warn('DEPRECATED: cannot finish via edit capability, use openmct.editor instead.'); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|   | ||||
| @@ -56,7 +56,7 @@ define([ | ||||
|     }; | ||||
|  | ||||
|     DurationFormat.prototype.validate = function (text) { | ||||
|         return moment.utc(text, DATE_FORMATS).isValid(); | ||||
|         return moment.utc(text, DATE_FORMATS, true).isValid(); | ||||
|     }; | ||||
|  | ||||
|     return DurationFormat; | ||||
|   | ||||
| @@ -29,6 +29,7 @@ define([ | ||||
|     var DATE_FORMAT = "YYYY-MM-DD HH:mm:ss.SSS", | ||||
|         DATE_FORMATS = [ | ||||
|             DATE_FORMAT, | ||||
|             DATE_FORMAT + "Z", | ||||
|             "YYYY-MM-DD HH:mm:ss", | ||||
|             "YYYY-MM-DD HH:mm", | ||||
|             "YYYY-MM-DD" | ||||
| @@ -52,70 +53,14 @@ define([ | ||||
|         this.key = "utc"; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns an appropriate time format based on the provided value and | ||||
|      * the threshold required. | ||||
|      * @private | ||||
|      */ | ||||
|     function getScaledFormat(d) { | ||||
|         var momentified = moment.utc(d); | ||||
|         /** | ||||
|          * Uses logic from d3 Time-Scales, v3 of the API. See | ||||
|          * https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Scales.md | ||||
|          * | ||||
|          * Licensed | ||||
|          */ | ||||
|         var format = [ | ||||
|             [".SSS", function (m) { | ||||
|                 return m.milliseconds(); | ||||
|             }], | ||||
|             [":ss", function (m) { | ||||
|                 return m.seconds(); | ||||
|             }], | ||||
|             ["HH:mm", function (m) { | ||||
|                 return m.minutes(); | ||||
|             }], | ||||
|             ["HH", function (m) { | ||||
|                 return m.hours(); | ||||
|             }], | ||||
|             ["ddd DD", function (m) { | ||||
|                 return m.days() && | ||||
|                     m.date() !== 1; | ||||
|             }], | ||||
|             ["MMM DD", function (m) { | ||||
|                 return m.date() !== 1; | ||||
|             }], | ||||
|             ["MMMM", function (m) { | ||||
|                 return m.month(); | ||||
|             }], | ||||
|             ["YYYY", function () { | ||||
|                 return true; | ||||
|             }] | ||||
|         ].filter(function (row) { | ||||
|             return row[1](momentified); | ||||
|         })[0][0]; | ||||
|  | ||||
|         if (format !== undefined) { | ||||
|             return moment.utc(d).format(format); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @param {number} value The value to format. | ||||
|      * @param {number} [minValue] Contextual information for scaled formatting used in linear scales such as conductor | ||||
|      * and plot axes. Specifies the smallest number on the scale. | ||||
|      * @param {number} [maxValue] Contextual information for scaled formatting used in linear scales such as conductor | ||||
|      * and plot axes. Specifies the largest number on the scale | ||||
|      * @param {number} [count] Contextual information for scaled formatting used in linear scales such as conductor | ||||
|      * and plot axes. The number of labels on the scale. | ||||
|      * @returns {string} the formatted date(s). If multiple values were requested, then an array of | ||||
|      * formatted values will be returned. Where a value could not be formatted, `undefined` will be returned at its position | ||||
|      * in the array. | ||||
|      */ | ||||
|     UTCTimeFormat.prototype.format = function (value) { | ||||
|         if (arguments.length > 1) { | ||||
|             return getScaledFormat(value); | ||||
|         } else if (value !== undefined) { | ||||
|         if (value !== undefined) { | ||||
|             return moment.utc(value).format(DATE_FORMAT) + "Z"; | ||||
|         } else { | ||||
|             return value; | ||||
| @@ -130,7 +75,7 @@ define([ | ||||
|     }; | ||||
|  | ||||
|     UTCTimeFormat.prototype.validate = function (text) { | ||||
|         return moment.utc(text, DATE_FORMATS).isValid(); | ||||
|         return moment.utc(text, DATE_FORMATS, true).isValid(); | ||||
|     }; | ||||
|  | ||||
|     return UTCTimeFormat; | ||||
|   | ||||
| @@ -1,62 +0,0 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT Web, Copyright (c) 2014-2015, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT Web is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT Web includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define([ | ||||
|     "../src/UTCTimeFormat", | ||||
|     "moment" | ||||
| ], function ( | ||||
|     UTCTimeFormat, | ||||
|     moment | ||||
| ) { | ||||
|     describe("The UTCTimeFormat class", function () { | ||||
|         var format; | ||||
|         var scale; | ||||
|  | ||||
|         beforeEach(function () { | ||||
|             format = new UTCTimeFormat(); | ||||
|             scale = {min: 0, max: 0}; | ||||
|         }); | ||||
|  | ||||
|         it("Provides an appropriately scaled time format based on the input" + | ||||
|             " time", function () { | ||||
|             var TWO_HUNDRED_MS = 200; | ||||
|             var THREE_SECONDS = 3000; | ||||
|             var FIVE_MINUTES = 5 * 60 * 1000; | ||||
|             var ONE_HOUR_TWENTY_MINS = (1 * 60 * 60 * 1000) + (20 * 60 * 1000); | ||||
|             var TEN_HOURS = (10 * 60 * 60 * 1000); | ||||
|  | ||||
|             var JUNE_THIRD = moment.utc("2016-06-03", "YYYY-MM-DD"); | ||||
|             var APRIL = moment.utc("2016-04", "YYYY-MM"); | ||||
|             var TWENTY_SIXTEEN = moment.utc("2016", "YYYY"); | ||||
|  | ||||
|             expect(format.format(TWO_HUNDRED_MS, scale)).toBe(".200"); | ||||
|             expect(format.format(THREE_SECONDS, scale)).toBe(":03"); | ||||
|             expect(format.format(FIVE_MINUTES, scale)).toBe("00:05"); | ||||
|             expect(format.format(ONE_HOUR_TWENTY_MINS, scale)).toBe("01:20"); | ||||
|             expect(format.format(TEN_HOURS, scale)).toBe("10"); | ||||
|  | ||||
|             expect(format.format(JUNE_THIRD, scale)).toBe("Fri 03"); | ||||
|             expect(format.format(APRIL, scale)).toBe("April"); | ||||
|             expect(format.format(TWENTY_SIXTEEN, scale)).toBe("2016"); | ||||
|         }); | ||||
|     }); | ||||
| }); | ||||
| @@ -1,43 +0,0 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT Web, Copyright (c) 2014-2015, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT Web is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT Web includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define([ | ||||
|     "./src/ConductorRepresenter", | ||||
|     'legacyRegistry' | ||||
| ], function ( | ||||
|     ConductorRepresenter, | ||||
|     legacyRegistry | ||||
| ) { | ||||
|  | ||||
|     legacyRegistry.register("platform/features/conductor/compatibility", { | ||||
|         "extensions": { | ||||
|             "representers": [ | ||||
|                 { | ||||
|                     "implementation": ConductorRepresenter, | ||||
|                     "depends": [ | ||||
|                         "openmct" | ||||
|                     ] | ||||
|                 } | ||||
|             ] | ||||
|         } | ||||
|     }); | ||||
| }); | ||||
| @@ -1,95 +0,0 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT Web, Copyright (c) 2014-2015, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT Web is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT Web includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define( | ||||
|     [], | ||||
|     function () { | ||||
|  | ||||
|         /** | ||||
|          * Representer that provides a compatibility layer between the new | ||||
|          * time conductor and existing representations / views. Listens to | ||||
|          * the v2 time conductor API and generates v1 style events using the | ||||
|          * Angular event bus. This is transitional code code and will be | ||||
|          * removed. | ||||
|          * | ||||
|          * Deprecated immediately as this is temporary code | ||||
|          * | ||||
|          * @deprecated | ||||
|          * @constructor | ||||
|          */ | ||||
|         function ConductorRepresenter( | ||||
|             openmct, | ||||
|             scope, | ||||
|             element | ||||
|         ) { | ||||
|             this.timeAPI = openmct.time; | ||||
|             this.scope = scope; | ||||
|             this.element = element; | ||||
|  | ||||
|             this.boundsListener = this.boundsListener.bind(this); | ||||
|             this.timeSystemListener = this.timeSystemListener.bind(this); | ||||
|             this.followListener = this.followListener.bind(this); | ||||
|         } | ||||
|  | ||||
|         ConductorRepresenter.prototype.boundsListener = function (bounds) { | ||||
|             var timeSystem = this.timeAPI.timeSystem(); | ||||
|             this.scope.$broadcast('telemetry:display:bounds', { | ||||
|                 start: bounds.start, | ||||
|                 end: bounds.end, | ||||
|                 domain: timeSystem.key | ||||
|             }, this.timeAPI.clock() !== undefined); | ||||
|         }; | ||||
|  | ||||
|         ConductorRepresenter.prototype.timeSystemListener = function (timeSystem) { | ||||
|             var bounds = this.timeAPI.bounds(); | ||||
|             this.scope.$broadcast('telemetry:display:bounds', { | ||||
|                 start: bounds.start, | ||||
|                 end: bounds.end, | ||||
|                 domain: timeSystem.key | ||||
|             }, this.timeAPI.clock() !== undefined); | ||||
|         }; | ||||
|  | ||||
|         ConductorRepresenter.prototype.followListener = function () { | ||||
|             this.boundsListener(this.timeAPI.bounds()); | ||||
|         }; | ||||
|  | ||||
|         // Handle a specific representation of a specific domain object | ||||
|         ConductorRepresenter.prototype.represent = function represent(representation) { | ||||
|             if (representation.key === 'browse-object') { | ||||
|                 this.destroy(); | ||||
|  | ||||
|                 this.timeAPI.on("bounds", this.boundsListener); | ||||
|                 this.timeAPI.on("timeSystem", this.timeSystemListener); | ||||
|                 this.timeAPI.on("follow", this.followListener); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         ConductorRepresenter.prototype.destroy = function destroy() { | ||||
|             this.timeAPI.off("bounds", this.boundsListener); | ||||
|             this.timeAPI.off("timeSystem", this.timeSystemListener); | ||||
|             this.timeAPI.off("follow", this.followListener); | ||||
|         }; | ||||
|  | ||||
|         return ConductorRepresenter; | ||||
|     } | ||||
| ); | ||||
|  | ||||
| @@ -1,148 +0,0 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT Web, Copyright (c) 2014-2015, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT Web is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT Web includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define([ | ||||
|     "./src/ui/TimeConductorController", | ||||
|     "./src/ui/ConductorAxisController", | ||||
|     "./src/ui/ConductorTOIController", | ||||
|     "./src/ui/ConductorTOIDirective", | ||||
|     "./src/ui/TimeOfInterestController", | ||||
|     "./src/ui/ConductorAxisDirective", | ||||
|     "./src/ui/NumberFormat", | ||||
|     "./src/ui/StringFormat", | ||||
|     "./res/templates/time-conductor.html", | ||||
|     "./res/templates/mode-selector/mode-selector.html", | ||||
|     "./res/templates/mode-selector/mode-menu.html", | ||||
|     "./res/templates/time-of-interest.html", | ||||
|     "legacyRegistry" | ||||
| ], function ( | ||||
|     TimeConductorController, | ||||
|     ConductorAxisController, | ||||
|     ConductorTOIController, | ||||
|     ConductorTOIDirective, | ||||
|     TimeOfInterestController, | ||||
|     ConductorAxisDirective, | ||||
|     NumberFormat, | ||||
|     StringFormat, | ||||
|     timeConductorTemplate, | ||||
|     modeSelectorTemplate, | ||||
|     modeMenuTemplate, | ||||
|     timeOfInterest, | ||||
|     legacyRegistry | ||||
| ) { | ||||
|  | ||||
|     legacyRegistry.register("platform/features/conductor/core", { | ||||
|         "extensions": { | ||||
|             "controllers": [ | ||||
|                 { | ||||
|                     "key": "TimeConductorController", | ||||
|                     "implementation": TimeConductorController, | ||||
|                     "depends": [ | ||||
|                         "$scope", | ||||
|                         "$window", | ||||
|                         "openmct", | ||||
|                         "formatService", | ||||
|                         "CONDUCTOR_CONFIG" | ||||
|                     ] | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "ConductorTOIController", | ||||
|                     "implementation": ConductorTOIController, | ||||
|                     "depends": [ | ||||
|                         "$scope", | ||||
|                         "openmct", | ||||
|                         "formatService" | ||||
|                     ] | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "TimeOfInterestController", | ||||
|                     "implementation": TimeOfInterestController, | ||||
|                     "depends": [ | ||||
|                         "$scope", | ||||
|                         "openmct", | ||||
|                         "formatService" | ||||
|                     ] | ||||
|                 } | ||||
|             ], | ||||
|             "directives": [ | ||||
|                 { | ||||
|                     "key": "conductorAxis", | ||||
|                     "implementation": ConductorAxisDirective, | ||||
|                     "depends": [ | ||||
|                         "openmct", | ||||
|                         "formatService" | ||||
|                     ] | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "conductorToi", | ||||
|                     "implementation": ConductorTOIDirective | ||||
|                 } | ||||
|             ], | ||||
|             "templates": [ | ||||
|                 { | ||||
|                     "key": "conductor", | ||||
|                     "template": timeConductorTemplate | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "mode-menu", | ||||
|                     "template": modeMenuTemplate | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "mode-selector", | ||||
|                     "template": modeSelectorTemplate | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "time-of-interest", | ||||
|                     "template": timeOfInterest | ||||
|                 } | ||||
|             ], | ||||
|             "representations": [ | ||||
|                 { | ||||
|                     "key": "time-conductor", | ||||
|                     "template": timeConductorTemplate | ||||
|                 } | ||||
|             ], | ||||
|             "licenses": [ | ||||
|                 { | ||||
|                     "name": "D3: Data-Driven Documents", | ||||
|                     "version": "4.1.0", | ||||
|                     "author": "Mike Bostock", | ||||
|                     "description": "D3 (or D3.js) is a JavaScript library for visualizing data using web standards. D3 helps you bring data to life using SVG, Canvas and HTML. D3 combines powerful visualization and interaction techniques with a data-driven approach to DOM manipulation, giving you the full capabilities of modern browsers and the freedom to design the right visual interface for your data.", | ||||
|                     "website": "https://d3js.org/", | ||||
|                     "copyright": "Copyright 2010-2016 Mike Bostock", | ||||
|                     "license": "BSD-3-Clause", | ||||
|                     "link": "https://github.com/d3/d3/blob/master/LICENSE" | ||||
|                 } | ||||
|             ], | ||||
|             "formats": [ | ||||
|                 { | ||||
|                     "key": "number", | ||||
|                     "implementation": NumberFormat | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "string", | ||||
|                     "implementation": StringFormat | ||||
|                 } | ||||
|             ] | ||||
|         } | ||||
|     }); | ||||
| }); | ||||
| @@ -1,46 +0,0 @@ | ||||
| <!-- | ||||
|  Open MCT Web, Copyright (c) 2014-2015, United States Government | ||||
|  as represented by the Administrator of the National Aeronautics and Space | ||||
|  Administration. All rights reserved. | ||||
|  | ||||
|  Open MCT Web is licensed under the Apache License, Version 2.0 (the | ||||
|  "License"); you may not use this file except in compliance with the License. | ||||
|  You may obtain a copy of the License at | ||||
|  http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  | ||||
|  Unless required by applicable law or agreed to in writing, software | ||||
|  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  License for the specific language governing permissions and limitations | ||||
|  under the License. | ||||
|  | ||||
|  Open MCT Web includes source code licensed under additional open source | ||||
|  licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  this source code distribution or the Licensing information page available | ||||
|  at runtime from the About dialog for additional information. | ||||
| --> | ||||
| <div class="w-menu"> | ||||
|     <div class="col menu-items"> | ||||
|         <ul> | ||||
|             <li ng-repeat="metadata in ngModel.options" | ||||
|                 ng-click="ngModel.select(metadata)"> | ||||
|                 <a ng-mouseover="ngModel.activeMetadata = metadata" | ||||
|                    ng-mouseleave="ngModel.activeMetadata = undefined" | ||||
|                    class="menu-item-a {{metadata.cssClass}}"> | ||||
|                     {{metadata.name}} | ||||
|                 </a> | ||||
|             </li> | ||||
|         </ul> | ||||
|     </div> | ||||
|     <div class="col menu-item-description"> | ||||
|         <div class="desc-area ui-symbol icon type-icon {{ngModel.activeMetadata.cssClass}}"></div> | ||||
|         <div class="w-title-desc"> | ||||
|             <div class="desc-area title"> | ||||
|                 {{ngModel.activeMetadata.name}} | ||||
|             </div> | ||||
|             <div class="desc-area description"> | ||||
|                 {{ngModel.activeMetadata.description}} | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
| </div> | ||||
| @@ -1,33 +0,0 @@ | ||||
| <!-- | ||||
|  Open MCT Web, Copyright (c) 2014-2015, United States Government | ||||
|  as represented by the Administrator of the National Aeronautics and Space | ||||
|  Administration. All rights reserved. | ||||
|  | ||||
|  Open MCT Web is licensed under the Apache License, Version 2.0 (the | ||||
|  "License"); you may not use this file except in compliance with the License. | ||||
|  You may obtain a copy of the License at | ||||
|  http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  | ||||
|  Unless required by applicable law or agreed to in writing, software | ||||
|  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  License for the specific language governing permissions and limitations | ||||
|  under the License. | ||||
|  | ||||
|  Open MCT Web includes source code licensed under additional open source | ||||
|  licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  this source code distribution or the Licensing information page available | ||||
|  at runtime from the About dialog for additional information. | ||||
| --> | ||||
| <span ng-controller="ClickAwayController as modeController"> | ||||
|     <div class="s-menu-button" | ||||
|          ng-click="modeController.toggle()"> | ||||
| 		<span class="title-label">{{ngModel.selected.name}}</span> | ||||
|     </div> | ||||
|     <div class="menu super-menu mini l-mode-selector-menu" | ||||
|          ng-show="modeController.isActive()"> | ||||
|         <mct-include key="'mode-menu'" | ||||
|                      ng-model="ngModel"> | ||||
|         </mct-include> | ||||
|     </div> | ||||
| </span> | ||||
| @@ -1,117 +0,0 @@ | ||||
| <!-- Parent holder for time conductor. follow-mode | fixed-mode --> | ||||
| <div ng-controller="TimeConductorController as tcController" | ||||
|     class="holder grows flex-elem l-flex-row l-time-conductor {{tcController.isFixed ? 'fixed-mode' : 'realtime-mode'}} {{timeSystemModel.selected.metadata.key}}-time-system" | ||||
|         ng-class="{'status-panning': tcController.panning}"> | ||||
|     <div class="flex-elem holder time-conductor-icon"> | ||||
|         <div class="hand-little"></div> | ||||
|         <div class="hand-big"></div> | ||||
|     </div> | ||||
|  | ||||
|     <div class="flex-elem holder grows l-flex-col l-time-conductor-inner"> | ||||
|         <!-- Holds inputs and ticks --> | ||||
|         <div class="l-time-conductor-inputs-and-ticks l-row-elem flex-elem no-margin"> | ||||
|             <form class="l-time-conductor-inputs-holder" | ||||
|                   ng-submit="tcController.isFixed ? tcController.setBoundsFromView(boundsModel) : tcController.setOffsetsFromView(boundsModel)"> | ||||
|                 <span class="l-time-range-w start-w"> | ||||
|                     <span class="l-time-conductor-inputs"> | ||||
|                         <span class="l-time-range-input-w start-date"> | ||||
|                             <span class="title"></span> | ||||
|                             <mct-control key="'datetime-field'" | ||||
|                                          structure="{ | ||||
|                                              format: timeSystemModel.format, | ||||
|                                              validate: tcController.validation.validateStart | ||||
|                                          }" | ||||
|                                          ng-model="boundsModel" | ||||
|                                          ng-blur="tcController.setBoundsFromView(boundsModel)" | ||||
|                                          field="'start'" | ||||
|                                          class="time-range-input"> | ||||
|                             </mct-control> | ||||
|                         </span> | ||||
|                             <span class="l-time-range-input-w time-delta start-delta" | ||||
|                               ng-class="{'hide':tcController.isFixed}"> | ||||
|                                 - | ||||
|                             <mct-control key="'datetime-field'" | ||||
|                                          structure="{ | ||||
|                                             format: timeSystemModel.durationFormat, | ||||
|                                             validate: tcController.validation.validateStartOffset | ||||
|                                          }" | ||||
|                                          ng-model="boundsModel" | ||||
|                                          ng-blur="tcController.setOffsetsFromView(boundsModel)" | ||||
|                                          field="'startOffset'" | ||||
|                                          class="s-input-inline hrs-min-input"> | ||||
|                             </mct-control> | ||||
|                         </span> | ||||
|                     </span> | ||||
|                 </span> | ||||
|                 <span class="l-time-range-w end-w"> | ||||
|                     <span class="l-time-conductor-inputs"> | ||||
|                         <span class="l-time-range-input-w end-date" | ||||
|                               ng-controller="ToggleController as t2"> | ||||
|                             <span class="title"></span> | ||||
|                             <mct-control key="'datetime-field'" | ||||
|                                          structure="{ | ||||
|                                              format: timeSystemModel.format, | ||||
|                                              validate: tcController.validation.validateEnd | ||||
|                                          }" | ||||
|                                          ng-model="boundsModel" | ||||
|                                          ng-blur="tcController.setBoundsFromView(boundsModel)" | ||||
|                                          ng-disabled="!tcController.isFixed" | ||||
|                                          field="'end'" | ||||
|                                          class="time-range-input"> | ||||
|                             </mct-control> | ||||
|                         </span> | ||||
|                         <span class="l-time-range-input-w time-delta end-delta" | ||||
|                               ng-class="{'hide': tcController.isFixed}"> | ||||
|                                 + | ||||
|                             <mct-control key="'datetime-field'" | ||||
|                                          structure="{ | ||||
|                                             format: timeSystemModel.durationFormat, | ||||
|                                             validate: tcController.validation.validateEndOffset | ||||
|                                          }" | ||||
|                                          ng-model="boundsModel" | ||||
|                                          ng-blur="tcController.setOffsetsFromView(boundsModel)" | ||||
|                                          field="'endOffset'" | ||||
|                                          class="s-input-inline hrs-min-input"> | ||||
|                             </mct-control> | ||||
|                         </span> | ||||
|                     </span> | ||||
|                 </span> | ||||
|  | ||||
|                 <input type="submit" class="invisible"> | ||||
|             </form> | ||||
|             <conductor-axis class="mobile-hide" view-service="tcController.conductorViewService"></conductor-axis> | ||||
|         </div> | ||||
|  | ||||
|         <!-- Holds time system and session selectors, and zoom control --> | ||||
|         <div class="l-time-conductor-controls l-row-elem l-flex-row flex-elem"> | ||||
|             <mct-include | ||||
|                 key="'mode-selector'" | ||||
|                 ng-model="tcController.menu" | ||||
|                 class="holder flex-elem menus-up mode-selector"> | ||||
|             </mct-include> | ||||
|             <mct-control | ||||
|                     key="'menu-button'" | ||||
|                     class="holder flex-elem menus-up time-system" | ||||
|                     structure="{ | ||||
|                         text: timeSystemModel.selected.name, | ||||
|                         click: tcController.setTimeSystemFromView, | ||||
|                         options: tcController.timeSystemsForClocks[tcController.menu.selected.key] | ||||
|                     }"> | ||||
|             </mct-control> | ||||
|             <!-- Zoom control --> | ||||
|             <div ng-if="tcController.zoom" | ||||
|                  class="l-time-conductor-zoom-w grows flex-elem l-flex-row"> | ||||
|                 {{currentZoom}} | ||||
|                 <span class="time-conductor-zoom-current-range flex-elem flex-fixed holder">{{timeUnits}}</span> | ||||
|                 <input class="time-conductor-zoom flex-elem" type="range" | ||||
|                        ng-model="tcController.currentZoom" | ||||
|                        ng-mouseUp="tcController.onZoomStop(tcController.currentZoom)" | ||||
|                        ng-change="tcController.onZoom(tcController.currentZoom)" | ||||
|                        min="0.01" | ||||
|                        step="0.01" | ||||
|                        max="0.99" /> | ||||
|             </div> | ||||
|         </div> | ||||
|  | ||||
|     </div> | ||||
| </div> | ||||
| @@ -1,12 +0,0 @@ | ||||
| <div class="abs angular-controller" | ||||
|      ng-controller="TimeOfInterestController as toi"> | ||||
|     <div class="l-flex-row l-toi"> | ||||
|         <span class="flex-elem l-flex-row l-toi-buttons"> | ||||
|             <a class="flex-elem t-button-resync icon-button" title="Re-sync Time of Interest" | ||||
|                ng-click="toi.resync()"></a> | ||||
|             <a class="flex-elem t-button-unpin icon-button" title="Unset Time of Interest" | ||||
|                ng-click="toi.dismiss()"></a> | ||||
|         </span> | ||||
|         <span class="flex-elem l-toi-val">{{toi.toiText}}</span> | ||||
|     </div> | ||||
| </div> | ||||
| @@ -1,236 +0,0 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT Web, Copyright (c) 2014-2015, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT Web is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT Web includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define( | ||||
|     [ | ||||
|         "d3-selection", | ||||
|         "d3-scale", | ||||
|         "d3-axis" | ||||
|     ], | ||||
|     function (d3Selection, d3Scale, d3Axis) { | ||||
|         var PADDING = 1; | ||||
|  | ||||
|         /** | ||||
|          * Controller that renders a horizontal time scale spanning the current bounds defined in the time conductor. | ||||
|          * Used by the mct-conductor-axis directive | ||||
|          * @constructor | ||||
|          */ | ||||
|         function ConductorAxisController(openmct, formatService, scope, element) { | ||||
|             // Dependencies | ||||
|             this.formatService = formatService; | ||||
|             this.timeAPI = openmct.time; | ||||
|  | ||||
|             this.scope = scope; | ||||
|  | ||||
|             this.bounds = this.timeAPI.bounds(); | ||||
|  | ||||
|             //Bind all class functions to 'this' | ||||
|             Object.keys(ConductorAxisController.prototype).filter(function (key) { | ||||
|                 return typeof ConductorAxisController.prototype[key] === 'function'; | ||||
|             }).forEach(function (key) { | ||||
|                 this[key] = ConductorAxisController.prototype[key].bind(this); | ||||
|             }.bind(this)); | ||||
|  | ||||
|             this.initialize(element); | ||||
|         } | ||||
|  | ||||
|         /** | ||||
|          * @private | ||||
|          */ | ||||
|         ConductorAxisController.prototype.destroy = function () { | ||||
|             this.timeAPI.off('timeSystem', this.changeTimeSystem); | ||||
|             this.timeAPI.off('bounds', this.changeBounds); | ||||
|             this.viewService.off("zoom", this.onZoom); | ||||
|             this.viewService.off("zoom-stop", this.onZoomStop); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * @private | ||||
|          */ | ||||
|         ConductorAxisController.prototype.initialize = function (element) { | ||||
|             this.target = element[0].firstChild; | ||||
|             var height = this.target.offsetHeight; | ||||
|             var vis = d3Selection.select(this.target) | ||||
|                 .append("svg:svg") | ||||
|                 .attr("width", "100%") | ||||
|                 .attr("height", height); | ||||
|  | ||||
|             this.xAxis = d3Axis.axisTop(); | ||||
|  | ||||
|             // draw x axis with labels. CSS is used to position them. | ||||
|             this.axisElement = vis.append("g"); | ||||
|  | ||||
|             if (this.timeAPI.timeSystem() !== undefined) { | ||||
|                 this.changeTimeSystem(this.timeAPI.timeSystem()); | ||||
|                 this.setScale(); | ||||
|             } | ||||
|  | ||||
|             //Respond to changes in conductor | ||||
|             this.timeAPI.on("timeSystem", this.changeTimeSystem); | ||||
|             this.timeAPI.on("bounds", this.changeBounds); | ||||
|  | ||||
|             this.scope.$on("$destroy", this.destroy); | ||||
|  | ||||
|             this.viewService.on("zoom", this.onZoom); | ||||
|             this.viewService.on("zoom-stop", this.onZoomStop); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * @private | ||||
|          */ | ||||
|         ConductorAxisController.prototype.changeBounds = function (bounds) { | ||||
|             this.bounds = bounds; | ||||
|             if (!this.zooming) { | ||||
|                 this.setScale(); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Set the scale of the axis, based on current conductor bounds. | ||||
|          */ | ||||
|         ConductorAxisController.prototype.setScale = function () { | ||||
|             var width = this.target.offsetWidth; | ||||
|             var timeSystem = this.timeAPI.timeSystem(); | ||||
|             var bounds = this.bounds; | ||||
|  | ||||
|             if (timeSystem.isUTCBased) { | ||||
|                 this.xScale = this.xScale || d3Scale.scaleUtc(); | ||||
|                 this.xScale.domain([new Date(bounds.start), new Date(bounds.end)]); | ||||
|             } else { | ||||
|                 this.xScale = this.xScale || d3Scale.scaleLinear(); | ||||
|                 this.xScale.domain([bounds.start, bounds.end]); | ||||
|             } | ||||
|  | ||||
|             this.xAxis.scale(this.xScale); | ||||
|  | ||||
|             this.xScale.range([PADDING, width - PADDING * 2]); | ||||
|             this.axisElement.call(this.xAxis); | ||||
|  | ||||
|             this.msPerPixel = (bounds.end - bounds.start) / width; | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * When the time system changes, update the scale and formatter used for showing times. | ||||
|          * @param timeSystem | ||||
|          */ | ||||
|         ConductorAxisController.prototype.changeTimeSystem = function (timeSystem) { | ||||
|             var key = timeSystem.timeFormat; | ||||
|             if (key !== undefined) { | ||||
|                 var format = this.formatService.getFormat(key); | ||||
|                 var bounds = this.timeAPI.bounds(); | ||||
|  | ||||
|                 //The D3 scale used depends on the type of time system as d3 | ||||
|                 // supports UTC out of the box. | ||||
|                 if (timeSystem.isUTCBased) { | ||||
|                     this.xScale = d3Scale.scaleUtc(); | ||||
|                 } else { | ||||
|                     this.xScale = d3Scale.scaleLinear(); | ||||
|                 } | ||||
|  | ||||
|                 this.xAxis.scale(this.xScale); | ||||
|  | ||||
|                 //Define a custom format function | ||||
|                 this.xAxis.tickFormat(function (tickValue) { | ||||
|                     // Normalize date representations to numbers | ||||
|                     if (tickValue instanceof Date) { | ||||
|                         tickValue = tickValue.getTime(); | ||||
|                     } | ||||
|                     return format.format(tickValue, { | ||||
|                         min: bounds.start, | ||||
|                         max: bounds.end | ||||
|                     }); | ||||
|                 }); | ||||
|                 this.axisElement.call(this.xAxis); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * The user has stopped panning the time conductor scale element. | ||||
|          * @event panStop | ||||
|          */ | ||||
|         /** | ||||
|          * Called on release of mouse button after dragging the scale left or right. | ||||
|          * @fires platform.features.conductor.ConductorAxisController~panStop | ||||
|          */ | ||||
|         ConductorAxisController.prototype.panStop = function () { | ||||
|             //resync view bounds with time conductor bounds | ||||
|             this.viewService.emit("pan-stop"); | ||||
|             this.timeAPI.bounds(this.bounds); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Rescales the axis when the user zooms. Although zoom ultimately results in a bounds change once the user | ||||
|          * releases the zoom slider, dragging the slider will not immediately change the conductor bounds. It will | ||||
|          * however immediately update the scale and the bounds displayed in the UI. | ||||
|          * @private | ||||
|          * @param {ZoomLevel} | ||||
|          */ | ||||
|         ConductorAxisController.prototype.onZoom = function (zoom) { | ||||
|             this.zooming = true; | ||||
|  | ||||
|             this.bounds = zoom.bounds; | ||||
|             this.setScale(); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * @private | ||||
|          */ | ||||
|         ConductorAxisController.prototype.onZoomStop = function (zoom) { | ||||
|             this.zooming = false; | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * @event platform.features.conductor.ConductorAxisController~pan | ||||
|          * Fired when the time conductor is panned | ||||
|          */ | ||||
|         /** | ||||
|          * Initiate panning via a click + drag gesture on the time conductor | ||||
|          * scale. Panning triggers a "pan" event | ||||
|          * @param {number} delta the offset from the original click event | ||||
|          * @see TimeConductorViewService# | ||||
|          * @fires platform.features.conductor.ConductorAxisController~pan | ||||
|          */ | ||||
|         ConductorAxisController.prototype.pan = function (delta) { | ||||
|             if (this.timeAPI.clock() === undefined) { | ||||
|                 var deltaInMs = delta[0] * this.msPerPixel; | ||||
|                 var bounds = this.timeAPI.bounds(); | ||||
|                 var start = Math.floor((bounds.start - deltaInMs) / 1000) * 1000; | ||||
|                 var end = Math.floor((bounds.end - deltaInMs) / 1000) * 1000; | ||||
|                 this.bounds = { | ||||
|                     start: start, | ||||
|                     end: end | ||||
|                 }; | ||||
|                 this.setScale(); | ||||
|                 this.viewService.emit("pan", this.bounds); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Invoked on element resize. Will rebuild the scale based on the new dimensions of the element. | ||||
|          */ | ||||
|         ConductorAxisController.prototype.resize = function () { | ||||
|             this.setScale(); | ||||
|         }; | ||||
|  | ||||
|         return ConductorAxisController; | ||||
|     } | ||||
| ); | ||||
| @@ -1,169 +0,0 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT Web, Copyright (c) 2014-2015, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT Web is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT Web includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define([ | ||||
|     './ConductorAxisController', | ||||
|     'zepto', | ||||
|     'd3-selection', | ||||
|     'd3-scale' | ||||
| ], function ( | ||||
|     ConductorAxisController, | ||||
|     $, | ||||
|     d3Selection, | ||||
|     d3Scale | ||||
| ) { | ||||
|     describe("The ConductorAxisController", function () { | ||||
|         var controller, | ||||
|             mockConductor, | ||||
|             mockConductorViewService, | ||||
|             mockFormatService, | ||||
|             mockScope, | ||||
|             mockBounds, | ||||
|             element, | ||||
|             mockTimeSystem, | ||||
|             mockFormat; | ||||
|  | ||||
|         function getCallback(target, name) { | ||||
|             return target.calls.all().filter(function (call) { | ||||
|                 return call.args[0] === name; | ||||
|             })[0].args[1]; | ||||
|         } | ||||
|  | ||||
|         beforeEach(function () { | ||||
|             mockScope = jasmine.createSpyObj("scope", [ | ||||
|                 "$on" | ||||
|             ]); | ||||
|  | ||||
|             //Add some HTML elements | ||||
|             mockBounds = { | ||||
|                 start: 100, | ||||
|                 end: 200 | ||||
|             }; | ||||
|             mockConductor = jasmine.createSpyObj("conductor", [ | ||||
|                 "timeSystem", | ||||
|                 "bounds", | ||||
|                 "on", | ||||
|                 "off", | ||||
|                 "clock" | ||||
|             ]); | ||||
|             mockConductor.bounds.and.returnValue(mockBounds); | ||||
|  | ||||
|             mockFormatService = jasmine.createSpyObj("formatService", [ | ||||
|                 "getFormat" | ||||
|             ]); | ||||
|  | ||||
|             mockConductorViewService = jasmine.createSpyObj("conductorViewService", [ | ||||
|                 "on", | ||||
|                 "off", | ||||
|                 "emit" | ||||
|             ]); | ||||
|  | ||||
|             spyOn(d3Scale, 'scaleUtc').and.callThrough(); | ||||
|             spyOn(d3Scale, 'scaleLinear').and.callThrough(); | ||||
|  | ||||
|             element = $('<div style="width: 100px;"><div style="width: 100%;"></div></div>'); | ||||
|             $(document).find('body').append(element); | ||||
|             ConductorAxisController.prototype.viewService = mockConductorViewService; | ||||
|             controller = new ConductorAxisController({time: mockConductor}, mockFormatService, mockScope, element); | ||||
|  | ||||
|             mockTimeSystem = {}; | ||||
|             mockFormat = jasmine.createSpyObj("format", [ | ||||
|                 "format" | ||||
|             ]); | ||||
|  | ||||
|             mockTimeSystem.timeFormat = "mockFormat"; | ||||
|             mockFormatService.getFormat.and.returnValue(mockFormat); | ||||
|             mockConductor.timeSystem.and.returnValue(mockTimeSystem); | ||||
|             mockTimeSystem.isUTCBased = false; | ||||
|         }); | ||||
|  | ||||
|         it("listens for changes to time system and bounds", function () { | ||||
|             expect(mockConductor.on).toHaveBeenCalledWith("timeSystem", controller.changeTimeSystem); | ||||
|             expect(mockConductor.on).toHaveBeenCalledWith("bounds", controller.changeBounds); | ||||
|         }); | ||||
|  | ||||
|         it("on scope destruction, deregisters listeners", function () { | ||||
|             expect(mockScope.$on).toHaveBeenCalledWith("$destroy", controller.destroy); | ||||
|             controller.destroy(); | ||||
|             expect(mockConductor.off).toHaveBeenCalledWith("timeSystem", controller.changeTimeSystem); | ||||
|             expect(mockConductor.off).toHaveBeenCalledWith("bounds", controller.changeBounds); | ||||
|         }); | ||||
|  | ||||
|         describe("when the time system changes", function () { | ||||
|             it("uses a UTC scale for UTC time systems", function () { | ||||
|                 mockTimeSystem.isUTCBased = true; | ||||
|                 controller.changeTimeSystem(mockTimeSystem); | ||||
|  | ||||
|                 expect(d3Scale.scaleUtc).toHaveBeenCalled(); | ||||
|                 expect(d3Scale.scaleLinear).not.toHaveBeenCalled(); | ||||
|             }); | ||||
|  | ||||
|             it("uses a linear scale for non-UTC time systems", function () { | ||||
|                 mockTimeSystem.isUTCBased = false; | ||||
|                 controller.changeTimeSystem(mockTimeSystem); | ||||
|                 expect(d3Scale.scaleLinear).toHaveBeenCalled(); | ||||
|                 expect(d3Scale.scaleUtc).not.toHaveBeenCalled(); | ||||
|             }); | ||||
|  | ||||
|             it("sets axis domain to time conductor bounds", function () { | ||||
|                 mockTimeSystem.isUTCBased = false; | ||||
|                 controller.setScale(); | ||||
|                 expect(controller.xScale.domain()).toEqual([mockBounds.start, mockBounds.end]); | ||||
|             }); | ||||
|  | ||||
|             it("uses the format specified by the time system to format tick" + | ||||
|                 " labels", function () { | ||||
|                 controller.changeTimeSystem(mockTimeSystem); | ||||
|                 expect(mockFormat.format).toHaveBeenCalled(); | ||||
|             }); | ||||
|  | ||||
|             it('responds to zoom events', function () { | ||||
|                 expect(mockConductorViewService.on).toHaveBeenCalledWith("zoom", controller.onZoom); | ||||
|                 var cb = getCallback(mockConductorViewService.on, "zoom"); | ||||
|                 spyOn(controller, 'setScale').and.callThrough(); | ||||
|                 cb({bounds: {start: 0, end: 100}}); | ||||
|                 expect(controller.setScale).toHaveBeenCalled(); | ||||
|             }); | ||||
|  | ||||
|             it('adjusts scale on pan', function () { | ||||
|                 spyOn(controller, 'setScale').and.callThrough(); | ||||
|                 controller.pan(100); | ||||
|                 expect(controller.setScale).toHaveBeenCalled(); | ||||
|             }); | ||||
|  | ||||
|             it('emits event on pan', function () { | ||||
|                 spyOn(controller, 'setScale').and.callThrough(); | ||||
|                 controller.pan(100); | ||||
|                 expect(mockConductorViewService.emit).toHaveBeenCalledWith("pan", jasmine.any(Object)); | ||||
|             }); | ||||
|  | ||||
|             it('cleans up listeners on destruction', function () { | ||||
|                 controller.destroy(); | ||||
|                 expect(mockConductor.off).toHaveBeenCalledWith("bounds", controller.changeBounds); | ||||
|                 expect(mockConductor.off).toHaveBeenCalledWith("timeSystem", controller.changeTimeSystem); | ||||
|  | ||||
|                 expect(mockConductorViewService.off).toHaveBeenCalledWith("zoom", controller.onZoom); | ||||
|             }); | ||||
|  | ||||
|         }); | ||||
|     }); | ||||
| }); | ||||
| @@ -1,56 +0,0 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT Web, Copyright (c) 2014-2015, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT Web is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT Web includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define(['./ConductorAxisController'], function (ConductorAxisController) { | ||||
|     function ConductorAxisDirective() { | ||||
|         /** | ||||
|          * The mct-conductor-axis renders a horizontal axis with regular | ||||
|          * labelled 'ticks'. It requires 'start' and 'end' integer values to | ||||
|          * be specified as attributes. | ||||
|          */ | ||||
|         return { | ||||
|             controller: [ | ||||
|                 'openmct', | ||||
|                 'formatService', | ||||
|                 '$scope', | ||||
|                 '$element', | ||||
|                 ConductorAxisController | ||||
|             ], | ||||
|             controllerAs: 'axis', | ||||
|             scope: { | ||||
|                 viewService: "=" | ||||
|             }, | ||||
|             bindToController: true, | ||||
|  | ||||
|             restrict: 'E', | ||||
|             priority: 1000, | ||||
|  | ||||
|             template: '<div class="l-axis-holder" ' + | ||||
|             '    mct-drag-down="axis.panStart()"' + | ||||
|             '    mct-drag-up="axis.panStop(delta)"' + | ||||
|             '    mct-drag="axis.pan(delta)"' + | ||||
|             '    mct-resize="axis.resize()"></div>' | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     return ConductorAxisDirective; | ||||
| }); | ||||
| @@ -1,123 +0,0 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT Web, Copyright (c) 2014-2015, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT Web is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT Web includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define( | ||||
|     ["zepto"], | ||||
|     function ($) { | ||||
|  | ||||
|         /** | ||||
|          * Controller for the Time of Interest indicator in the conductor itself. Sets the horizontal position of the | ||||
|          * TOI indicator based on the current value of the TOI, and the width of the TOI conductor. | ||||
|          * @memberof platform.features.conductor | ||||
|          */ | ||||
|         function ConductorTOIController($scope, openmct) { | ||||
|             this.timeAPI = openmct.time; | ||||
|  | ||||
|             //Bind all class functions to 'this' | ||||
|             Object.keys(ConductorTOIController.prototype).filter(function (key) { | ||||
|                 return typeof ConductorTOIController.prototype[key] === 'function'; | ||||
|             }).forEach(function (key) { | ||||
|                 this[key] = ConductorTOIController.prototype[key].bind(this); | ||||
|             }.bind(this)); | ||||
|  | ||||
|             this.timeAPI.on('timeOfInterest', this.changeTimeOfInterest); | ||||
|             this.viewService.on('zoom', this.setOffsetFromZoom); | ||||
|             this.viewService.on('pan', this.setOffsetFromBounds); | ||||
|  | ||||
|             var timeOfInterest = this.timeAPI.timeOfInterest(); | ||||
|             if (timeOfInterest) { | ||||
|                 this.changeTimeOfInterest(timeOfInterest); | ||||
|             } | ||||
|  | ||||
|             $scope.$on('$destroy', this.destroy); | ||||
|         } | ||||
|  | ||||
|         /** | ||||
|          * @private | ||||
|          */ | ||||
|         ConductorTOIController.prototype.destroy = function () { | ||||
|             this.timeAPI.off('timeOfInterest', this.changeTimeOfInterest); | ||||
|             this.viewService.off('zoom', this.setOffsetFromZoom); | ||||
|             this.viewService.off('pan', this.setOffsetFromBounds); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Given some bounds, set horizontal position of TOI indicator based | ||||
|          * on current conductor TOI value. Bounds are provided so that | ||||
|          * ephemeral bounds from zoom and pan events can be used as well | ||||
|          * as current conductor bounds, allowing TOI to be updated in | ||||
|          * realtime during scroll and zoom. | ||||
|          * @param {TimeConductorBounds} bounds | ||||
|          */ | ||||
|         ConductorTOIController.prototype.setOffsetFromBounds = function (bounds) { | ||||
|             var toi = this.timeAPI.timeOfInterest(); | ||||
|             if (toi !== undefined) { | ||||
|                 var offset = toi - bounds.start; | ||||
|                 var duration = bounds.end - bounds.start; | ||||
|                 this.left = offset / duration * 100; | ||||
|                 this.pinned = true; | ||||
|             } else { | ||||
|                 this.left = 0; | ||||
|                 this.pinned = false; | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * @private | ||||
|          */ | ||||
|         ConductorTOIController.prototype.setOffsetFromZoom = function (zoom) { | ||||
|             return this.setOffsetFromBounds(zoom.bounds); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Invoked when time of interest changes. Will set the horizontal offset of the TOI indicator. | ||||
|          * @private | ||||
|          */ | ||||
|         ConductorTOIController.prototype.changeTimeOfInterest = function () { | ||||
|             var bounds = this.timeAPI.bounds(); | ||||
|             if (bounds) { | ||||
|                 this.setOffsetFromBounds(bounds); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * On a mouse click event within the TOI element, convert position within element to a time of interest, and | ||||
|          * set the time of interest on the conductor. | ||||
|          * @param e The angular $event object | ||||
|          */ | ||||
|         ConductorTOIController.prototype.setTOIFromPosition = function (e) { | ||||
|             //TOI is set using the alt key modified + primary click | ||||
|             if (e.altKey) { | ||||
|                 var element = $(e.currentTarget); | ||||
|                 var width = element.width(); | ||||
|                 var relativeX = e.pageX - element.offset().left; | ||||
|                 var percX = relativeX / width; | ||||
|                 var bounds = this.timeAPI.bounds(); | ||||
|                 var timeRange = bounds.end - bounds.start; | ||||
|  | ||||
|                 this.timeAPI.timeOfInterest(timeRange * percX + bounds.start); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         return ConductorTOIController; | ||||
|     } | ||||
| ); | ||||
| @@ -1,153 +0,0 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT Web, Copyright (c) 2014-2015, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT Web is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT Web includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define([ | ||||
|     './ConductorTOIController' | ||||
| ], function ( | ||||
|     ConductorTOIController | ||||
| ) { | ||||
|     var mockConductor; | ||||
|     var mockConductorViewService; | ||||
|     var mockScope; | ||||
|     var mockAPI; | ||||
|     var conductorTOIController; | ||||
|  | ||||
|     function getNamedCallback(thing, name) { | ||||
|         return thing.calls.all().filter(function (call) { | ||||
|             return call.args[0] === name; | ||||
|         }).map(function (call) { | ||||
|             return call.args; | ||||
|         })[0][1]; | ||||
|     } | ||||
|  | ||||
|     describe("The ConductorTOIController", function () { | ||||
|         beforeEach(function () { | ||||
|             mockConductor = jasmine.createSpyObj("conductor", [ | ||||
|                 "bounds", | ||||
|                 "timeOfInterest", | ||||
|                 "on", | ||||
|                 "off" | ||||
|             ]); | ||||
|             mockAPI = {time: mockConductor}; | ||||
|  | ||||
|             mockConductorViewService = jasmine.createSpyObj("conductorViewService", [ | ||||
|                 "on", | ||||
|                 "off" | ||||
|             ]); | ||||
|  | ||||
|             mockScope = jasmine.createSpyObj("openMCT", [ | ||||
|                 "$on" | ||||
|             ]); | ||||
|             ConductorTOIController.prototype.viewService = mockConductorViewService; | ||||
|             conductorTOIController = new ConductorTOIController(mockScope, mockAPI); | ||||
|         }); | ||||
|  | ||||
|         it("listens to changes in the time of interest on the conductor", function () { | ||||
|             expect(mockConductor.on).toHaveBeenCalledWith("timeOfInterest", jasmine.any(Function)); | ||||
|         }); | ||||
|  | ||||
|         describe("when responding to changes in the time of interest", function () { | ||||
|             var toiCallback; | ||||
|             beforeEach(function () { | ||||
|                 var bounds = { | ||||
|                     start: 0, | ||||
|                     end: 200 | ||||
|                 }; | ||||
|                 mockConductor.bounds.and.returnValue(bounds); | ||||
|                 toiCallback = getNamedCallback(mockConductor.on, "timeOfInterest"); | ||||
|             }); | ||||
|  | ||||
|             it("calculates the correct horizontal offset based on bounds and current TOI", function () { | ||||
|                 //Expect time of interest position to be 50% of element width | ||||
|                 mockConductor.timeOfInterest.and.returnValue(100); | ||||
|                 toiCallback(); | ||||
|                 expect(conductorTOIController.left).toBe(50); | ||||
|  | ||||
|                 //Expect time of interest position to be 25% of element width | ||||
|                 mockConductor.timeOfInterest.and.returnValue(50); | ||||
|                 toiCallback(); | ||||
|                 expect(conductorTOIController.left).toBe(25); | ||||
|  | ||||
|                 //Expect time of interest position to be 0% of element width | ||||
|                 mockConductor.timeOfInterest.and.returnValue(0); | ||||
|                 toiCallback(); | ||||
|                 expect(conductorTOIController.left).toBe(0); | ||||
|  | ||||
|                 //Expect time of interest position to be 100% of element width | ||||
|                 mockConductor.timeOfInterest.and.returnValue(200); | ||||
|                 toiCallback(); | ||||
|                 expect(conductorTOIController.left).toBe(100); | ||||
|             }); | ||||
|  | ||||
|             it("renders the TOI indicator visible", function () { | ||||
|                 expect(conductorTOIController.pinned).toBeFalsy(); | ||||
|                 mockConductor.timeOfInterest.and.returnValue(100); | ||||
|                 toiCallback(); | ||||
|                 expect(conductorTOIController.pinned).toBe(true); | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         it("responds to zoom events", function () { | ||||
|             var mockZoom = { | ||||
|                 bounds: { | ||||
|                     start: 500, | ||||
|                     end: 1000 | ||||
|                 } | ||||
|             }; | ||||
|             expect(mockConductorViewService.on).toHaveBeenCalledWith("zoom", jasmine.any(Function)); | ||||
|  | ||||
|             // Should correspond to horizontal offset of 50% | ||||
|             mockConductor.timeOfInterest.and.returnValue(750); | ||||
|             var zoomCallback = getNamedCallback(mockConductorViewService.on, "zoom"); | ||||
|             zoomCallback(mockZoom); | ||||
|             expect(conductorTOIController.left).toBe(50); | ||||
|         }); | ||||
|  | ||||
|         it("responds to pan events", function () { | ||||
|             var mockPanBounds = { | ||||
|                 start: 1000, | ||||
|                 end: 3000 | ||||
|             }; | ||||
|  | ||||
|             expect(mockConductorViewService.on).toHaveBeenCalledWith("pan", jasmine.any(Function)); | ||||
|  | ||||
|             // Should correspond to horizontal offset of 25% | ||||
|             mockConductor.timeOfInterest.and.returnValue(1500); | ||||
|             var panCallback = getNamedCallback(mockConductorViewService.on, "pan"); | ||||
|             panCallback(mockPanBounds); | ||||
|             expect(conductorTOIController.left).toBe(25); | ||||
|         }); | ||||
|  | ||||
|  | ||||
|         it("Cleans up all listeners when controller destroyed", function () { | ||||
|             var zoomCB = getNamedCallback(mockConductorViewService.on, "zoom"); | ||||
|             var panCB = getNamedCallback(mockConductorViewService.on, "pan"); | ||||
|             var toiCB = getNamedCallback(mockConductor.on, "timeOfInterest"); | ||||
|  | ||||
|             expect(mockScope.$on).toHaveBeenCalledWith("$destroy", jasmine.any(Function)); | ||||
|             getNamedCallback(mockScope.$on, "$destroy")(); | ||||
|             expect(mockConductorViewService.off).toHaveBeenCalledWith("zoom", zoomCB); | ||||
|             expect(mockConductorViewService.off).toHaveBeenCalledWith("pan", panCB); | ||||
|             expect(mockConductor.off).toHaveBeenCalledWith("timeOfInterest", toiCB); | ||||
|         }); | ||||
|     }); | ||||
| }); | ||||
| @@ -1,63 +0,0 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT Web, Copyright (c) 2014-2015, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT Web is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT Web includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define(['./ConductorTOIController'], function (ConductorTOIController) { | ||||
|     /** | ||||
|      * A directive that encapsulates the TOI specific behavior of the Time Conductor UI. | ||||
|      * @constructor | ||||
|      */ | ||||
|     function ConductorTOIDirective() { | ||||
|         /** | ||||
|          * The mct-conductor-axis renders a horizontal axis with regular | ||||
|          * labelled 'ticks'. It requires 'start' and 'end' integer values to | ||||
|          * be specified as attributes. | ||||
|          */ | ||||
|         return { | ||||
|             controller: [ | ||||
|                 '$scope', | ||||
|                 'openmct', | ||||
|                 ConductorTOIController | ||||
|             ], | ||||
|             controllerAs: 'toi', | ||||
|             scope: { | ||||
|                 viewService: "=" | ||||
|             }, | ||||
|             bindToController: true, | ||||
|  | ||||
|             restrict: 'E', | ||||
|             priority: 1000, | ||||
|  | ||||
|             template: | ||||
|                 '<div class="l-data-visualization-holder l-row-elem flex-elem">' + | ||||
|                 '   <a class="l-page-button s-icon-button icon-pointer-left"></a>' + | ||||
|                 '   <div class="l-data-visualization" ng-click="toi.setTOIFromPosition($event)">' + | ||||
|                 '       <mct-include key="\'time-of-interest\'" class="l-toi-holder show-val" ' + | ||||
|                 '       ng-class="{ pinned: toi.pinned, \'val-to-left\': toi.left > 80 }" ' + | ||||
|                 '       ng-style="{\'left\': toi.left + \'%\'}"></mct-include>' + | ||||
|                 '   </div>' + | ||||
|                 '   <a class="l-page-button align-right s-icon-button icon-pointer-right"></a>' + | ||||
|                 '</div>' | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     return ConductorTOIDirective; | ||||
| }); | ||||
| @@ -1,49 +0,0 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT Web, Copyright (c) 2014-2015, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT Web is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT Web includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define(['./NumberFormat'], function (NumberFormat) { | ||||
|     describe("The NumberFormat class", function () { | ||||
|         var format; | ||||
|         beforeEach(function () { | ||||
|             format = new NumberFormat(); | ||||
|         }); | ||||
|  | ||||
|         it("The format function takes a string and produces a number", function () { | ||||
|             var text = format.format(1); | ||||
|             expect(text).toBe("1"); | ||||
|             expect(typeof text).toBe("string"); | ||||
|         }); | ||||
|  | ||||
|         it("The parse function takes a string and produces a number", function () { | ||||
|             var number = format.parse("1"); | ||||
|             expect(number).toBe(1); | ||||
|             expect(typeof number).toBe("number"); | ||||
|         }); | ||||
|  | ||||
|         it("validates that the input is a number", function () { | ||||
|             expect(format.validate("1")).toBe(true); | ||||
|             expect(format.validate(1)).toBe(true); | ||||
|             expect(format.validate("1.1")).toBe(true); | ||||
|             expect(format.validate("abc")).toBe(false); | ||||
|         }); | ||||
|     }); | ||||
| }); | ||||
| @@ -1,554 +0,0 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT Web, Copyright (c) 2014-2015, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT Web is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT Web includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define( | ||||
|     [ | ||||
|         'moment', | ||||
|         './TimeConductorValidation', | ||||
|         './TimeConductorViewService' | ||||
|     ], | ||||
|     function ( | ||||
|         moment, | ||||
|         TimeConductorValidation, | ||||
|         TimeConductorViewService | ||||
|     ) { | ||||
|  | ||||
|         var timeUnitsMegastructure = [ | ||||
|             ["Decades", function (r) { | ||||
|                 return r.years() > 15; | ||||
|             }], | ||||
|             ["Years", function (r) { | ||||
|                 return r.years() > 1; | ||||
|             }], | ||||
|             ["Months", function (r) { | ||||
|                 return r.years() === 1 || r.months() > 1; | ||||
|             }], | ||||
|             ["Days", function (r) { | ||||
|                 return r.months() === 1 || r.days() > 1; | ||||
|             }], | ||||
|             ["Hours", function (r) { | ||||
|                 return r.days() === 1 || r.hours() > 1; | ||||
|             }], | ||||
|             ["Minutes", function (r) { | ||||
|                 return r.hours() === 1 || r.minutes() > 1; | ||||
|             }], | ||||
|             ["Seconds", function (r) { | ||||
|                 return r.minutes() === 1 || r.seconds() > 1; | ||||
|             }], | ||||
|             ["Milliseconds", function (r) { | ||||
|                 return true; | ||||
|             }] | ||||
|         ]; | ||||
|  | ||||
|         /** | ||||
|          * Controller for the Time Conductor UI element. The Time Conductor | ||||
|          * includes form fields for specifying time bounds and relative time | ||||
|          * offsets for queries, as well as controls for selection mode, | ||||
|          * time systems, and zooming. | ||||
|          * @memberof platform.features.conductor | ||||
|          * @constructor | ||||
|          */ | ||||
|         function TimeConductorController( | ||||
|             $scope, | ||||
|             $window, | ||||
|             openmct, | ||||
|             formatService, | ||||
|             config | ||||
|         ) { | ||||
|  | ||||
|             //Bind functions that are used as callbacks to 'this'. | ||||
|             [ | ||||
|                 "selectMenuOption", | ||||
|                 "onPan", | ||||
|                 "onPanStop", | ||||
|                 "setViewFromBounds", | ||||
|                 "setViewFromClock", | ||||
|                 "setViewFromOffsets", | ||||
|                 "setViewFromTimeSystem", | ||||
|                 "setTimeSystemFromView", | ||||
|                 "destroy" | ||||
|             ].forEach(function (name) { | ||||
|                 this[name] = this[name].bind(this); | ||||
|             }.bind(this)); | ||||
|  | ||||
|             this.$scope = $scope; | ||||
|             this.$window = $window; | ||||
|             this.timeAPI = openmct.time; | ||||
|             this.conductorViewService = new TimeConductorViewService(openmct); | ||||
|             this.validation = new TimeConductorValidation(this.timeAPI); | ||||
|             this.formatService = formatService; | ||||
|             this.config = config; | ||||
|             this.timeSystemsForClocks = {}; | ||||
|             this.$scope.timeSystemModel = {}; | ||||
|             this.$scope.boundsModel = {}; | ||||
|  | ||||
|             this.timeSystems = this.timeAPI.getAllTimeSystems().reduce(function (map, timeSystem) { | ||||
|                 map[timeSystem.key] = timeSystem; | ||||
|                 return map; | ||||
|             }, {}); | ||||
|  | ||||
|             this.isFixed = this.timeAPI.clock() === undefined; | ||||
|  | ||||
|             var options = this.optionsFromConfig(config); | ||||
|             this.menu = { | ||||
|                 selected: undefined, | ||||
|                 options: options, | ||||
|                 select: this.selectMenuOption | ||||
|             }; | ||||
|  | ||||
|             //Set the initial state of the UI from the conductor state | ||||
|             var timeSystem = this.timeAPI.timeSystem(); | ||||
|             if (timeSystem) { | ||||
|                 this.setViewFromTimeSystem(timeSystem); | ||||
|             } | ||||
|  | ||||
|             this.setViewFromClock(this.timeAPI.clock()); | ||||
|  | ||||
|             var offsets = this.timeAPI.clockOffsets(); | ||||
|             if (offsets) { | ||||
|                 this.setViewFromOffsets(offsets); | ||||
|             } | ||||
|  | ||||
|             var bounds = this.timeAPI.bounds(); | ||||
|             if (bounds && bounds.start !== undefined && bounds.end !== undefined) { | ||||
|                 this.setViewFromBounds(bounds); | ||||
|             } | ||||
|  | ||||
|             this.conductorViewService.on('pan', this.onPan); | ||||
|             this.conductorViewService.on('pan-stop', this.onPanStop); | ||||
|  | ||||
|             //Respond to any subsequent conductor changes | ||||
|             this.timeAPI.on('bounds', this.setViewFromBounds); | ||||
|             this.timeAPI.on('timeSystem', this.setViewFromTimeSystem); | ||||
|             this.timeAPI.on('clock', this.setViewFromClock); | ||||
|             this.timeAPI.on('clockOffsets', this.setViewFromOffsets); | ||||
|             this.$scope.$on('$destroy', this.destroy); | ||||
|         } | ||||
|  | ||||
|         /** | ||||
|          * Given a key for a clock, retrieve the clock object. | ||||
|          * @private | ||||
|          * @param key | ||||
|          * @returns {Clock} | ||||
|          */ | ||||
|         TimeConductorController.prototype.getClock = function (key) { | ||||
|             return this.timeAPI.getAllClocks().filter(function (clock) { | ||||
|                 return clock.key === key; | ||||
|             })[0]; | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Activate the selected menu option. Menu options correspond to clocks. | ||||
|          * A distinction is made to avoid confusion between the menu options and | ||||
|          * their metadata, and actual {@link Clock} objects. | ||||
|          * | ||||
|          * @private | ||||
|          * @param newOption | ||||
|          */ | ||||
|         TimeConductorController.prototype.selectMenuOption = function (newOption) { | ||||
|             if (this.menu.selected.key === newOption.key) { | ||||
|                 return; | ||||
|             } | ||||
|             this.menu.selected = newOption; | ||||
|  | ||||
|             var config = this.getConfig(this.timeAPI.timeSystem(), newOption.clock); | ||||
|             if (!config) { | ||||
|                 // Clock does not support this timeSystem, fallback to first | ||||
|                 // option provided for clock. | ||||
|                 config = this.config.menuOptions.filter(function (menuOption) { | ||||
|                     return menuOption.clock === (newOption.clock && newOption.clock.key); | ||||
|                 })[0]; | ||||
|             } | ||||
|  | ||||
|             if (config.clock) { | ||||
|                 this.timeAPI.clock(config.clock, config.clockOffsets); | ||||
|                 this.timeAPI.timeSystem(config.timeSystem); | ||||
|             } else { | ||||
|                 this.timeAPI.stopClock(); | ||||
|                 this.timeAPI.timeSystem(config.timeSystem, config.bounds); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * From the provided configuration, build the available menu options. | ||||
|          * @private | ||||
|          * @param config | ||||
|          * @returns {*[]} | ||||
|          */ | ||||
|         TimeConductorController.prototype.optionsFromConfig = function (config) { | ||||
|             /* | ||||
|              * "Fixed Mode" is always the first available option. | ||||
|              */ | ||||
|             var options = [{ | ||||
|                 key: 'fixed', | ||||
|                 name: 'Fixed Timespan Mode', | ||||
|                 description: 'Query and explore data that falls between two fixed datetimes.', | ||||
|                 cssClass: 'icon-calendar' | ||||
|             }]; | ||||
|             var clocks = {}; | ||||
|             var timeSystemsForClocks = this.timeSystemsForClocks; | ||||
|  | ||||
|             (config.menuOptions || []).forEach(function (menuOption) { | ||||
|                 var clockKey = menuOption.clock || 'fixed'; | ||||
|                 var clock = this.getClock(clockKey); | ||||
|  | ||||
|                 if (clock !== undefined) { | ||||
|                     clocks[clock.key] = clock; | ||||
|                 } | ||||
|  | ||||
|                 var timeSystem = this.timeSystems[menuOption.timeSystem]; | ||||
|                 if (timeSystem !== undefined) { | ||||
|                     timeSystemsForClocks[clockKey] = timeSystemsForClocks[clockKey] || []; | ||||
|                     timeSystemsForClocks[clockKey].push(timeSystem); | ||||
|                 } | ||||
|             }, this); | ||||
|  | ||||
|             /* | ||||
|              * Populate the clocks menu with metadata from the available clocks | ||||
|              */ | ||||
|             Object.values(clocks).forEach(function (clock) { | ||||
|                 options.push({ | ||||
|                     key: clock.key, | ||||
|                     name: clock.name, | ||||
|                     description: "Monitor streaming data in real-time. The Time " + | ||||
|                     "Conductor and displays will automatically advance themselves based on this clock. " + clock.description, | ||||
|                     cssClass: clock.cssClass || 'icon-clock', | ||||
|                     clock: clock | ||||
|                 }); | ||||
|             }.bind(this)); | ||||
|  | ||||
|             return options; | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * When bounds change, set UI values from the new bounds. | ||||
|          * @param {TimeBounds} bounds the bounds | ||||
|          */ | ||||
|         TimeConductorController.prototype.setViewFromBounds = function (bounds) { | ||||
|             if (!this.zooming && !this.panning) { | ||||
|                 this.$scope.boundsModel.start = bounds.start; | ||||
|                 this.$scope.boundsModel.end = bounds.end; | ||||
|  | ||||
|                 if (this.supportsZoom()) { | ||||
|                     var config = this.getConfig(this.timeAPI.timeSystem(), this.timeAPI.clock()); | ||||
|                     this.currentZoom = this.toSliderValue(bounds.end - bounds.start, config.zoomOutLimit, config.zoomInLimit); | ||||
|                     this.toTimeUnits(bounds.end - bounds.start); | ||||
|                 } | ||||
|  | ||||
|                 /* | ||||
|                     Ensure that a digest occurs, capped at the browser's refresh | ||||
|                     rate. | ||||
|                  */ | ||||
|                 if (!this.pendingUpdate) { | ||||
|                     this.pendingUpdate = true; | ||||
|                     this.$window.requestAnimationFrame(function () { | ||||
|                         this.pendingUpdate = false; | ||||
|                         this.$scope.$digest(); | ||||
|                     }.bind(this)); | ||||
|                 } | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Retrieve any configuration defined for the provided time system and | ||||
|          * clock | ||||
|          * @private | ||||
|          * @param timeSystem | ||||
|          * @param clock | ||||
|          * @returns {object} The Time Conductor configuration corresponding to | ||||
|          * the provided combination of time system and clock | ||||
|          */ | ||||
|         TimeConductorController.prototype.getConfig = function (timeSystem, clock) { | ||||
|             var clockKey = clock && clock.key; | ||||
|             var timeSystemKey = timeSystem && timeSystem.key; | ||||
|  | ||||
|             var option = this.config.menuOptions.filter(function (menuOption) { | ||||
|                 return menuOption.timeSystem === timeSystemKey && menuOption.clock === clockKey; | ||||
|             })[0]; | ||||
|             return option; | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * When the clock offsets change, update the values in the UI | ||||
|          * @param {ClockOffsets} offsets | ||||
|          * @private | ||||
|          */ | ||||
|         TimeConductorController.prototype.setViewFromOffsets = function (offsets) { | ||||
|             this.$scope.boundsModel.startOffset = Math.abs(offsets.start); | ||||
|             this.$scope.boundsModel.endOffset = offsets.end; | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * When form values for bounds change, update the bounds in the Time API | ||||
|          * to trigger an application-wide bounds change. | ||||
|          * @param {object} boundsModel | ||||
|          */ | ||||
|         TimeConductorController.prototype.setBoundsFromView = function (boundsModel) { | ||||
|             var bounds = this.timeAPI.bounds(); | ||||
|             if (boundsModel.start !== bounds.start || boundsModel.end !== bounds.end) { | ||||
|                 this.timeAPI.bounds({ | ||||
|                     start: boundsModel.start, | ||||
|                     end: boundsModel.end | ||||
|                 }); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * When form values for bounds change, update the bounds in the Time API | ||||
|          * to trigger an application-wide bounds change. | ||||
|          * @param {object} formModel | ||||
|          */ | ||||
|         TimeConductorController.prototype.setOffsetsFromView = function (boundsModel) { | ||||
|             if (this.validation.validateStartOffset(boundsModel.startOffset) && this.validation.validateEndOffset(boundsModel.endOffset)) { | ||||
|                 var offsets = { | ||||
|                     start: 0 - boundsModel.startOffset, | ||||
|                     end: boundsModel.endOffset | ||||
|                 }; | ||||
|                 var existingOffsets = this.timeAPI.clockOffsets(); | ||||
|  | ||||
|                 if (offsets.start !== existingOffsets.start || offsets.end !== existingOffsets.end) { | ||||
|                     //Sychronize offsets between form and time API | ||||
|                     this.timeAPI.clockOffsets(offsets); | ||||
|                 } | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * @private | ||||
|          * @returns {boolean} | ||||
|          */ | ||||
|         TimeConductorController.prototype.supportsZoom = function () { | ||||
|             var config = this.getConfig(this.timeAPI.timeSystem(), this.timeAPI.clock()); | ||||
|             return config && (config.zoomInLimit !== undefined && config.zoomOutLimit !== undefined); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Update the UI state to reflect a change in clock. Provided conductor | ||||
|          * configuration will be checked for compatibility between the new clock | ||||
|          * and the currently selected time system. If configuration is not available, | ||||
|          * an attempt will be made to default to a time system that is compatible | ||||
|          * with the new clock | ||||
|          * | ||||
|          * @private | ||||
|          * @param {Clock} clock | ||||
|          */ | ||||
|         TimeConductorController.prototype.setViewFromClock = function (clock) { | ||||
|             var newClockKey = clock ? clock.key : 'fixed'; | ||||
|             var timeSystems = this.timeSystemsForClocks[newClockKey]; | ||||
|             var menuOption = this.menu.options.filter(function (option) { | ||||
|                 return option.key === (newClockKey); | ||||
|             })[0]; | ||||
|  | ||||
|             this.menu.selected = menuOption; | ||||
|  | ||||
|             //Try to find currently selected time system in time systems for clock | ||||
|             var selectedTimeSystem = timeSystems.filter(function (timeSystem) { | ||||
|                 return timeSystem.key === this.$scope.timeSystemModel.selected.key; | ||||
|             }.bind(this))[0]; | ||||
|  | ||||
|             var config = this.getConfig(selectedTimeSystem, clock); | ||||
|  | ||||
|             if (selectedTimeSystem === undefined) { | ||||
|                 selectedTimeSystem = timeSystems[0]; | ||||
|                 config = this.getConfig(selectedTimeSystem, clock); | ||||
|  | ||||
|                 if (clock === undefined) { | ||||
|                     this.timeAPI.timeSystem(selectedTimeSystem, config.bounds); | ||||
|                 } else { | ||||
|                     //When time system changes, some start bounds need to be provided | ||||
|                     this.timeAPI.timeSystem(selectedTimeSystem, { | ||||
|                         start: clock.currentValue() + config.clockOffsets.start, | ||||
|                         end: clock.currentValue() + config.clockOffsets.end | ||||
|                     }); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             this.isFixed = clock === undefined; | ||||
|  | ||||
|             if (clock === undefined) { | ||||
|                 this.setViewFromBounds(this.timeAPI.bounds()); | ||||
|             } | ||||
|  | ||||
|             this.zoom = this.supportsZoom(); | ||||
|             this.$scope.timeSystemModel.options = timeSystems; | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Respond to time system selection from UI | ||||
|          * | ||||
|          * Allows time system to be changed by key. This supports selection | ||||
|          * from the menu. Resolves a TimeSystem object and then invokes | ||||
|          * TimeConductorController#setTimeSystem | ||||
|          * @param key | ||||
|          * @see TimeConductorController#setTimeSystem | ||||
|          */ | ||||
|         TimeConductorController.prototype.setTimeSystemFromView = function (key) { | ||||
|             var clock = this.menu.selected.clock; | ||||
|             var timeSystem = this.timeSystems[key]; | ||||
|             var config = this.getConfig(timeSystem, clock); | ||||
|  | ||||
|             this.$scope.timeSystemModel.selected = timeSystem; | ||||
|             this.$scope.timeSystemModel.format = timeSystem.timeFormat; | ||||
|  | ||||
|             if (clock === undefined) { | ||||
|                 this.timeAPI.timeSystem(timeSystem, config.bounds); | ||||
|             } else { | ||||
|                 this.timeAPI.clock(clock, config.clockOffsets); | ||||
|                 this.timeAPI.timeSystem(timeSystem); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Handles time system change from time conductor | ||||
|          * | ||||
|          * Sets the selected time system. Will populate form with the default | ||||
|          * bounds and offsets defined in the selected time system. | ||||
|          * | ||||
|          * @param newTimeSystem | ||||
|          */ | ||||
|         TimeConductorController.prototype.setViewFromTimeSystem = function (timeSystem) { | ||||
|             var oldKey = (this.$scope.timeSystemModel.selected || {}).key; | ||||
|             var timeSystemModel = this.$scope.timeSystemModel; | ||||
|  | ||||
|             if (timeSystem && (timeSystem.key !== oldKey)) { | ||||
|                 var config = this.getConfig(timeSystem, this.timeAPI.clock()); | ||||
|  | ||||
|                 timeSystemModel.selected = timeSystem; | ||||
|                 timeSystemModel.format = timeSystem.timeFormat; | ||||
|                 timeSystemModel.durationFormat = timeSystem.durationFormat; | ||||
|  | ||||
|                 if (this.supportsZoom()) { | ||||
|                     timeSystemModel.minZoom = config.zoomOutLimit; | ||||
|                     timeSystemModel.maxZoom = config.zoomInLimit; | ||||
|                 } | ||||
|             } | ||||
|             this.zoom = this.supportsZoom(); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Takes a time span and calculates a slider increment value, used | ||||
|          * to set the horizontal offset of the slider. | ||||
|          * @private | ||||
|          * @param {number} timeSpan a duration of time, in ms | ||||
|          * @returns {number} a value between 0.01 and 0.99, in increments of .01 | ||||
|          */ | ||||
|         TimeConductorController.prototype.toSliderValue = function (timeSpan, zoomOutLimit, zoomInLimit) { | ||||
|             var perc = timeSpan / (zoomOutLimit - zoomInLimit); | ||||
|             return 1 - Math.pow(perc, 1 / 4); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Given a time span, set a label for the units of time that it, | ||||
|          * roughly, represents. Leverages | ||||
|          * @param {TimeSpan} timeSpan | ||||
|          */ | ||||
|         TimeConductorController.prototype.toTimeUnits = function (timeSpan) { | ||||
|             var timeSystem = this.timeAPI.timeSystem(); | ||||
|             if (timeSystem && timeSystem.isUTCBased) { | ||||
|                 var momentified = moment.duration(timeSpan); | ||||
|  | ||||
|                 this.$scope.timeUnits = timeUnitsMegastructure.filter(function (row) { | ||||
|                     return row[1](momentified); | ||||
|                 })[0][0]; | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Zooming occurs when the user manipulates the zoom slider. | ||||
|          * Zooming updates the scale and bounds fields immediately, but does | ||||
|          * not trigger a bounds change to other views until the mouse button | ||||
|          * is released. | ||||
|          * @param bounds | ||||
|          */ | ||||
|         TimeConductorController.prototype.onZoom = function (sliderValue) { | ||||
|             var config = this.getConfig(this.timeAPI.timeSystem(), this.timeAPI.clock()); | ||||
|             var timeSpan = Math.pow((1 - sliderValue), 4) * (config.zoomOutLimit - config.zoomInLimit); | ||||
|  | ||||
|             var zoom = this.conductorViewService.zoom(timeSpan); | ||||
|             this.zooming = true; | ||||
|  | ||||
|             this.$scope.boundsModel.start = zoom.bounds.start; | ||||
|             this.$scope.boundsModel.end = zoom.bounds.end; | ||||
|             this.toTimeUnits(zoom.bounds.end - zoom.bounds.start); | ||||
|  | ||||
|             if (zoom.offsets) { | ||||
|                 this.setViewFromOffsets(zoom.offsets); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Fired when user has released the zoom slider | ||||
|          * @event platform.features.conductor.TimeConductorController~zoomStop | ||||
|          */ | ||||
|         /** | ||||
|          * Invoked when zoom slider is released by user. Will update the time conductor with the new bounds, triggering | ||||
|          * a global bounds change event. | ||||
|          * @fires platform.features.conductor.TimeConductorController~zoomStop | ||||
|          */ | ||||
|         TimeConductorController.prototype.onZoomStop = function () { | ||||
|             if (this.timeAPI.clock() !== undefined) { | ||||
|                 this.setOffsetsFromView(this.$scope.boundsModel); | ||||
|             } | ||||
|             this.setBoundsFromView(this.$scope.boundsModel); | ||||
|  | ||||
|             this.zooming = false; | ||||
|             this.conductorViewService.emit('zoom-stop'); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Panning occurs when the user grabs the conductor scale and drags | ||||
|          * it left or right to slide the window of time represented by the | ||||
|          * conductor. Panning updates the scale and bounds fields | ||||
|          * immediately, but does not trigger a bounds change to other views | ||||
|          * until the mouse button is released. | ||||
|          * @param {TimeConductorBounds} bounds | ||||
|          */ | ||||
|         TimeConductorController.prototype.onPan = function (bounds) { | ||||
|             this.panning = true; | ||||
|             this.$scope.boundsModel.start = bounds.start; | ||||
|             this.$scope.boundsModel.end = bounds.end; | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Called when the user releases the mouse button after panning. | ||||
|          */ | ||||
|         TimeConductorController.prototype.onPanStop = function () { | ||||
|             this.panning = false; | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * @private | ||||
|          */ | ||||
|         TimeConductorController.prototype.destroy = function () { | ||||
|             this.timeAPI.off('bounds', this.setViewFromBounds); | ||||
|             this.timeAPI.off('timeSystem', this.setViewFromTimeSystem); | ||||
|             this.timeAPI.off('clock', this.setViewFromClock); | ||||
|             this.timeAPI.off('follow', this.setFollow); | ||||
|             this.timeAPI.off('clockOffsets', this.setViewFromOffsets); | ||||
|  | ||||
|             this.conductorViewService.off('pan', this.onPan); | ||||
|             this.conductorViewService.off('pan-stop', this.onPanStop); | ||||
|         }; | ||||
|  | ||||
|         return TimeConductorController; | ||||
|     } | ||||
| ); | ||||
| @@ -1,513 +0,0 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT Web, Copyright (c) 2014-2015, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT Web is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT Web includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define(['./TimeConductorController'], function (TimeConductorController) { | ||||
|     xdescribe("The time conductor controller", function () { | ||||
|         var mockScope; | ||||
|         var mockWindow; | ||||
|         var mockTimeConductor; | ||||
|         var mockConductorViewService; | ||||
|         var mockTimeSystems; | ||||
|         var controller; | ||||
|         var mockFormatService; | ||||
|         var mockFormat; | ||||
|         var mockLocation; | ||||
|  | ||||
|         beforeEach(function () { | ||||
|             mockScope = jasmine.createSpyObj("$scope", [ | ||||
|                 "$watch", | ||||
|                 "$on" | ||||
|             ]); | ||||
|  | ||||
|             mockWindow = jasmine.createSpyObj("$window", ["requestAnimationFrame"]); | ||||
|             mockTimeConductor = jasmine.createSpyObj( | ||||
|                 "TimeConductor", | ||||
|                 [ | ||||
|                     "bounds", | ||||
|                     "timeSystem", | ||||
|                     "on", | ||||
|                     "off" | ||||
|                 ] | ||||
|             ); | ||||
|             mockTimeConductor.bounds.and.returnValue({start: undefined, end: undefined}); | ||||
|  | ||||
|             mockConductorViewService = jasmine.createSpyObj( | ||||
|                 "ConductorViewService", | ||||
|                 [ | ||||
|                     "availableModes", | ||||
|                     "mode", | ||||
|                     "availableTimeSystems", | ||||
|                     "deltas", | ||||
|                     "deltas", | ||||
|                     "on", | ||||
|                     "off" | ||||
|                 ] | ||||
|             ); | ||||
|             mockConductorViewService.availableModes.and.returnValue([]); | ||||
|             mockConductorViewService.availableTimeSystems.and.returnValue([]); | ||||
|  | ||||
|             mockFormatService = jasmine.createSpyObj('formatService', [ | ||||
|                 'getFormat' | ||||
|             ]); | ||||
|             mockFormat = jasmine.createSpyObj('format', [ | ||||
|                 'format' | ||||
|             ]); | ||||
|             mockFormatService.getFormat.and.returnValue(mockFormat); | ||||
|             mockLocation = jasmine.createSpyObj('location', [ | ||||
|                 'search' | ||||
|             ]); | ||||
|             mockLocation.search.and.returnValue({}); | ||||
|  | ||||
|             mockTimeSystems = []; | ||||
|         }); | ||||
|  | ||||
|         function getListener(target, event) { | ||||
|             return target.calls.all().filter(function (call) { | ||||
|                 return call.args[0] === event; | ||||
|             })[0].args[1]; | ||||
|         } | ||||
|  | ||||
|         describe("when time conductor state changes", function () { | ||||
|             var mockDeltaFormat; | ||||
|             var defaultBounds; | ||||
|             var defaultDeltas; | ||||
|             var mockDefaults; | ||||
|             var timeSystem; | ||||
|             var tsListener; | ||||
|  | ||||
|             beforeEach(function () { | ||||
|                 mockFormat = {}; | ||||
|                 mockDeltaFormat = {}; | ||||
|                 defaultBounds = { | ||||
|                     start: 2, | ||||
|                     end: 3 | ||||
|                 }; | ||||
|                 defaultDeltas = { | ||||
|                     start: 10, | ||||
|                     end: 20 | ||||
|                 }; | ||||
|                 mockDefaults = { | ||||
|                     deltas: defaultDeltas, | ||||
|                     bounds: defaultBounds | ||||
|                 }; | ||||
|                 timeSystem = { | ||||
|                     metadata: { | ||||
|                         key: 'mock' | ||||
|                     }, | ||||
|                     formats: function () { | ||||
|                         return [mockFormat]; | ||||
|                     }, | ||||
|                     deltaFormat: function () { | ||||
|                         return mockDeltaFormat; | ||||
|                     }, | ||||
|                     defaults: function () { | ||||
|                         return mockDefaults; | ||||
|                     } | ||||
|                 }; | ||||
|  | ||||
|                 controller = new TimeConductorController( | ||||
|                     mockScope, | ||||
|                     mockWindow, | ||||
|                     mockLocation, | ||||
|                     {conductor: mockTimeConductor}, | ||||
|                     mockConductorViewService, | ||||
|                     mockFormatService, | ||||
|                     'fixed', | ||||
|                     true | ||||
|  | ||||
|                 ); | ||||
|  | ||||
|                 tsListener = getListener(mockTimeConductor.on, "timeSystem"); | ||||
|             }); | ||||
|  | ||||
|             it("listens for changes to conductor state", function () { | ||||
|                 expect(mockTimeConductor.on).toHaveBeenCalledWith("timeSystem", controller.changeTimeSystem); | ||||
|                 expect(mockTimeConductor.on).toHaveBeenCalledWith("bounds", controller.changeBounds); | ||||
|             }); | ||||
|  | ||||
|             it("deregisters conductor listens when scope is destroyed", function () { | ||||
|                 expect(mockScope.$on).toHaveBeenCalledWith("$destroy", controller.destroy); | ||||
|  | ||||
|                 controller.destroy(); | ||||
|                 expect(mockTimeConductor.off).toHaveBeenCalledWith("timeSystem", controller.changeTimeSystem); | ||||
|                 expect(mockTimeConductor.off).toHaveBeenCalledWith("bounds", controller.changeBounds); | ||||
|             }); | ||||
|  | ||||
|             it("when time system changes, sets time system on scope", function () { | ||||
|                 expect(tsListener).toBeDefined(); | ||||
|                 tsListener(timeSystem); | ||||
|  | ||||
|                 expect(mockScope.timeSystemModel).toBeDefined(); | ||||
|                 expect(mockScope.timeSystemModel.selected).toBe(timeSystem); | ||||
|                 expect(mockScope.timeSystemModel.format).toBe(mockFormat); | ||||
|                 expect(mockScope.timeSystemModel.deltaFormat).toBe(mockDeltaFormat); | ||||
|             }); | ||||
|  | ||||
|             it("when time system changes, sets defaults on scope", function () { | ||||
|                 mockDefaults.zoom = { | ||||
|                     min: 100, | ||||
|                     max: 10 | ||||
|                 }; | ||||
|                 mockTimeConductor.timeSystem.and.returnValue(timeSystem); | ||||
|                 tsListener(timeSystem); | ||||
|  | ||||
|                 expect(mockScope.boundsModel.start).toEqual(defaultBounds.start); | ||||
|                 expect(mockScope.boundsModel.end).toEqual(defaultBounds.end); | ||||
|  | ||||
|                 expect(mockScope.boundsModel.startDelta).toEqual(defaultDeltas.start); | ||||
|                 expect(mockScope.boundsModel.endDelta).toEqual(defaultDeltas.end); | ||||
|  | ||||
|                 expect(mockScope.timeSystemModel.minZoom).toBe(mockDefaults.zoom.min); | ||||
|                 expect(mockScope.timeSystemModel.maxZoom).toBe(mockDefaults.zoom.max); | ||||
|             }); | ||||
|  | ||||
|             it("supports zoom if time system defines zoom defaults", function () { | ||||
|  | ||||
|                 mockDefaults.zoom = undefined; | ||||
|  | ||||
|                 tsListener(timeSystem); | ||||
|                 expect(controller.supportsZoom).toBe(false); | ||||
|  | ||||
|                 mockDefaults.zoom = { | ||||
|                     min: 100, | ||||
|                     max: 10 | ||||
|                 }; | ||||
|  | ||||
|                 var anotherTimeSystem = Object.create(timeSystem); | ||||
|                 timeSystem.defaults = function () { | ||||
|                     return mockDefaults; | ||||
|                 }; | ||||
|  | ||||
|                 tsListener(anotherTimeSystem); | ||||
|                 expect(controller.supportsZoom).toBe(true); | ||||
|  | ||||
|             }); | ||||
|  | ||||
|             it("when bounds change, sets the correct zoom slider value", function () { | ||||
|                 var bounds = { | ||||
|                     start: 0, | ||||
|                     end: 50 | ||||
|                 }; | ||||
|                 mockDefaults.zoom = { | ||||
|                     min: 100, | ||||
|                     max: 0 | ||||
|                 }; | ||||
|  | ||||
|                 function exponentializer(rawValue) { | ||||
|                     return 1 - Math.pow(rawValue, 1 / 4); | ||||
|                 } | ||||
|  | ||||
|                 mockTimeConductor.timeSystem.and.returnValue(timeSystem); | ||||
|                 //Set zoom defaults | ||||
|                 tsListener(timeSystem); | ||||
|  | ||||
|                 controller.changeBounds(bounds); | ||||
|                 expect(controller.currentZoom).toEqual(exponentializer(0.5)); | ||||
|  | ||||
|             }); | ||||
|  | ||||
|             it("when bounds change, sets them on scope", function () { | ||||
|                 var bounds = { | ||||
|                     start: 1, | ||||
|                     end: 2 | ||||
|                 }; | ||||
|  | ||||
|                 var boundsListener = getListener(mockTimeConductor.on, "bounds"); | ||||
|                 expect(boundsListener).toBeDefined(); | ||||
|                 boundsListener(bounds); | ||||
|  | ||||
|                 expect(mockScope.boundsModel).toBeDefined(); | ||||
|                 expect(mockScope.boundsModel.start).toEqual(bounds.start); | ||||
|                 expect(mockScope.boundsModel.end).toEqual(bounds.end); | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         describe("when user makes changes from UI", function () { | ||||
|             var mode = "realtime"; | ||||
|             var ts1Metadata; | ||||
|             var ts2Metadata; | ||||
|             var ts3Metadata; | ||||
|  | ||||
|             beforeEach(function () { | ||||
|                 mode = "realtime"; | ||||
|                 ts1Metadata = { | ||||
|                     'key': 'ts1', | ||||
|                     'name': 'Time System One', | ||||
|                     'cssClass': 'cssClassOne' | ||||
|                 }; | ||||
|                 ts2Metadata = { | ||||
|                     'key': 'ts2', | ||||
|                     'name': 'Time System Two', | ||||
|                     'cssClass': 'cssClassTwo' | ||||
|                 }; | ||||
|                 ts3Metadata = { | ||||
|                     'key': 'ts3', | ||||
|                     'name': 'Time System Three', | ||||
|                     'cssClass': 'cssClassThree' | ||||
|                 }; | ||||
|                 mockTimeSystems = [ | ||||
|                     { | ||||
|                         metadata: ts1Metadata | ||||
|                     }, | ||||
|                     { | ||||
|                         metadata: ts2Metadata | ||||
|                     }, | ||||
|                     { | ||||
|                         metadata: ts3Metadata | ||||
|                     } | ||||
|                 ]; | ||||
|  | ||||
|                 //Wrap in mock constructors | ||||
|                 mockConductorViewService.systems = mockTimeSystems; | ||||
|  | ||||
|                 controller = new TimeConductorController( | ||||
|                     mockScope, | ||||
|                     mockWindow, | ||||
|                     mockLocation, | ||||
|                     {conductor: mockTimeConductor}, | ||||
|                     mockConductorViewService, | ||||
|                     mockFormatService, | ||||
|                     "fixed", | ||||
|                     true | ||||
|                 ); | ||||
|             }); | ||||
|  | ||||
|             it("sets the mode on scope", function () { | ||||
|                 mockConductorViewService.availableTimeSystems.and.returnValue(mockTimeSystems); | ||||
|                 controller.setMode(mode); | ||||
|  | ||||
|                 expect(mockScope.modeModel.selectedKey).toEqual(mode); | ||||
|             }); | ||||
|  | ||||
|             it("sets available time systems on scope when mode changes", function () { | ||||
|                 mockConductorViewService.availableTimeSystems.and.returnValue(mockTimeSystems); | ||||
|                 controller.setMode(mode); | ||||
|  | ||||
|                 expect(mockScope.timeSystemModel.options.length).toEqual(3); | ||||
|                 expect(mockScope.timeSystemModel.options[0]).toEqual(ts1Metadata); | ||||
|                 expect(mockScope.timeSystemModel.options[1]).toEqual(ts2Metadata); | ||||
|                 expect(mockScope.timeSystemModel.options[2]).toEqual(ts3Metadata); | ||||
|             }); | ||||
|  | ||||
|             it("sets bounds on the time conductor", function () { | ||||
|                 var formModel = { | ||||
|                     start: 1, | ||||
|                     end: 10 | ||||
|                 }; | ||||
|  | ||||
|                 controller.setBounds(formModel); | ||||
|                 expect(mockTimeConductor.bounds).toHaveBeenCalledWith(formModel); | ||||
|             }); | ||||
|  | ||||
|             it("applies deltas when they change in form", function () { | ||||
|                 var deltas = { | ||||
|                     start: 1000, | ||||
|                     end: 2000 | ||||
|                 }; | ||||
|                 var formModel = { | ||||
|                     startDelta: deltas.start, | ||||
|                     endDelta: deltas.end | ||||
|                 }; | ||||
|  | ||||
|                 controller.setDeltas(formModel); | ||||
|                 expect(mockConductorViewService.deltas).toHaveBeenCalledWith(deltas); | ||||
|             }); | ||||
|  | ||||
|             it("sets the time system on the time conductor", function () { | ||||
|                 var defaultBounds = { | ||||
|                     start: 5, | ||||
|                     end: 6 | ||||
|                 }; | ||||
|                 var timeSystem = { | ||||
|                     metadata: { | ||||
|                         key: 'testTimeSystem' | ||||
|                     }, | ||||
|                     defaults: function () { | ||||
|                         return { | ||||
|                             bounds: defaultBounds | ||||
|                         }; | ||||
|                     } | ||||
|                 }; | ||||
|  | ||||
|                 controller.timeSystems = [timeSystem]; | ||||
|  | ||||
|                 controller.selectTimeSystemByKey('testTimeSystem'); | ||||
|                 expect(mockTimeConductor.timeSystem).toHaveBeenCalledWith(timeSystem, defaultBounds); | ||||
|             }); | ||||
|  | ||||
|             it("updates form bounds during pan events", function () { | ||||
|                 var testBounds = { | ||||
|                     start: 10, | ||||
|                     end: 20 | ||||
|                 }; | ||||
|  | ||||
|                 expect(controller.$scope.boundsModel.start).not.toBe(testBounds.start); | ||||
|                 expect(controller.$scope.boundsModel.end).not.toBe(testBounds.end); | ||||
|  | ||||
|                 expect(controller.conductorViewService.on).toHaveBeenCalledWith("pan", | ||||
|                     controller.onPan); | ||||
|  | ||||
|                 getListener(controller.conductorViewService.on, "pan")(testBounds); | ||||
|  | ||||
|                 expect(controller.$scope.boundsModel.start).toBe(testBounds.start); | ||||
|                 expect(controller.$scope.boundsModel.end).toBe(testBounds.end); | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         describe("when the URL defines conductor state", function () { | ||||
|             var urlBounds; | ||||
|             var urlTimeSystem; | ||||
|             var urlDeltas; | ||||
|  | ||||
|             var mockDeltaFormat; | ||||
|             var defaultBounds; | ||||
|             var defaultDeltas; | ||||
|             var mockDefaults; | ||||
|             var timeSystem; | ||||
|             var otherTimeSystem; | ||||
|             var mockSearchObject; | ||||
|  | ||||
|             beforeEach(function () { | ||||
|  | ||||
|                 mockFormat = {}; | ||||
|                 mockDeltaFormat = {}; | ||||
|                 defaultBounds = { | ||||
|                     start: 2, | ||||
|                     end: 3 | ||||
|                 }; | ||||
|                 defaultDeltas = { | ||||
|                     start: 10, | ||||
|                     end: 20 | ||||
|                 }; | ||||
|                 mockDefaults = { | ||||
|                     deltas: defaultDeltas, | ||||
|                     bounds: defaultBounds | ||||
|                 }; | ||||
|                 timeSystem = { | ||||
|                     metadata: { | ||||
|                         key: 'mockTimeSystem' | ||||
|                     }, | ||||
|                     formats: function () { | ||||
|                         return [mockFormat]; | ||||
|                     }, | ||||
|                     deltaFormat: function () { | ||||
|                         return mockDeltaFormat; | ||||
|                     }, | ||||
|                     defaults: function () { | ||||
|                         return mockDefaults; | ||||
|                     } | ||||
|                 }; | ||||
|                 otherTimeSystem = { | ||||
|                     metadata: { | ||||
|                         key: 'otherTimeSystem' | ||||
|                     }, | ||||
|                     formats: function () { | ||||
|                         return [mockFormat]; | ||||
|                     }, | ||||
|                     deltaFormat: function () { | ||||
|                         return mockDeltaFormat; | ||||
|                     }, | ||||
|                     defaults: function () { | ||||
|                         return mockDefaults; | ||||
|                     } | ||||
|                 }; | ||||
|  | ||||
|                 mockConductorViewService.systems = [timeSystem, otherTimeSystem]; | ||||
|  | ||||
|                 urlBounds = { | ||||
|                     start: 100, | ||||
|                     end: 200 | ||||
|                 }; | ||||
|                 urlTimeSystem = "otherTimeSystem"; | ||||
|                 urlDeltas = { | ||||
|                     start: 300, | ||||
|                     end: 400 | ||||
|                 }; | ||||
|                 mockSearchObject = { | ||||
|                     "tc.startBound": urlBounds.start, | ||||
|                     "tc.endBound": urlBounds.end, | ||||
|                     "tc.startDelta": urlDeltas.start, | ||||
|                     "tc.endDelta": urlDeltas.end, | ||||
|                     "tc.timeSystem": urlTimeSystem | ||||
|                 }; | ||||
|                 mockLocation.search.and.returnValue(mockSearchObject); | ||||
|                 mockTimeConductor.timeSystem.and.returnValue(timeSystem); | ||||
|  | ||||
|                 controller = new TimeConductorController( | ||||
|                     mockScope, | ||||
|                     mockWindow, | ||||
|                     mockLocation, | ||||
|                     {conductor: mockTimeConductor}, | ||||
|                     mockConductorViewService, | ||||
|                     mockFormatService, | ||||
|                     "fixed", | ||||
|                     true | ||||
|                 ); | ||||
|  | ||||
|                 spyOn(controller, "setMode"); | ||||
|                 spyOn(controller, "selectTimeSystemByKey"); | ||||
|             }); | ||||
|  | ||||
|             it("sets conductor state from URL", function () { | ||||
|                 mockSearchObject["tc.mode"] = "fixed"; | ||||
|                 controller.setStateFromSearchParams(mockSearchObject); | ||||
|                 expect(controller.selectTimeSystemByKey).toHaveBeenCalledWith("otherTimeSystem"); | ||||
|                 expect(mockTimeConductor.bounds).toHaveBeenCalledWith(urlBounds); | ||||
|             }); | ||||
|  | ||||
|             it("sets mode from URL", function () { | ||||
|                 mockTimeConductor.bounds.reset(); | ||||
|                 mockSearchObject["tc.mode"] = "realtime"; | ||||
|                 controller.setStateFromSearchParams(mockSearchObject); | ||||
|                 expect(controller.setMode).toHaveBeenCalledWith("realtime"); | ||||
|                 expect(controller.selectTimeSystemByKey).toHaveBeenCalledWith("otherTimeSystem"); | ||||
|                 expect(mockConductorViewService.deltas).toHaveBeenCalledWith(urlDeltas); | ||||
|                 expect(mockTimeConductor.bounds).not.toHaveBeenCalled(); | ||||
|             }); | ||||
|  | ||||
|             describe("when conductor state changes", function () { | ||||
|                 it("updates the URL with the mode", function () { | ||||
|                     controller.setMode("realtime", "fixed"); | ||||
|                     expect(mockLocation.search).toHaveBeenCalledWith("tc.mode", "fixed"); | ||||
|                 }); | ||||
|  | ||||
|                 it("updates the URL with the bounds", function () { | ||||
|                     mockConductorViewService.mode.and.returnValue("fixed"); | ||||
|                     controller.changeBounds({start: 500, end: 600}); | ||||
|                     expect(mockLocation.search).toHaveBeenCalledWith("tc.startBound", 500); | ||||
|                     expect(mockLocation.search).toHaveBeenCalledWith("tc.endBound", 600); | ||||
|                 }); | ||||
|  | ||||
|                 it("updates the URL with the deltas", function () { | ||||
|                     controller.setDeltas({startDelta: 700, endDelta: 800}); | ||||
|                     expect(mockLocation.search).toHaveBeenCalledWith("tc.startDelta", 700); | ||||
|                     expect(mockLocation.search).toHaveBeenCalledWith("tc.endDelta", 800); | ||||
|                 }); | ||||
|  | ||||
|                 it("updates the URL with the time system", function () { | ||||
|                     controller.changeTimeSystem(otherTimeSystem); | ||||
|                     expect(mockLocation.search).toHaveBeenCalledWith("tc.timeSystem", "otherTimeSystem"); | ||||
|                 }); | ||||
|             }); | ||||
|         }); | ||||
|     }); | ||||
| }); | ||||
| @@ -1,69 +0,0 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT Web, Copyright (c) 2014-2015, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT Web is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT Web includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define( | ||||
|     [], | ||||
|     function () { | ||||
|  | ||||
|         /** | ||||
|          * Form validation for the TimeConductorController. | ||||
|          * @param conductor | ||||
|          * @constructor | ||||
|          */ | ||||
|         function TimeConductorValidation(timeAPI) { | ||||
|             var self = this; | ||||
|             this.timeAPI = timeAPI; | ||||
|  | ||||
|             /* | ||||
|              * Bind all class functions to 'this' | ||||
|              */ | ||||
|             Object.keys(TimeConductorValidation.prototype).filter(function (key) { | ||||
|                 return typeof TimeConductorValidation.prototype[key] === 'function'; | ||||
|             }).forEach(function (key) { | ||||
|                 self[key] = self[key].bind(self); | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         /** | ||||
|          * Validation methods below are invoked directly from controls in the TimeConductor form | ||||
|          */ | ||||
|         TimeConductorValidation.prototype.validateStart = function (start) { | ||||
|             var bounds = this.timeAPI.bounds(); | ||||
|             return this.timeAPI.validateBounds({start: start, end: bounds.end}) === true; | ||||
|         }; | ||||
|  | ||||
|         TimeConductorValidation.prototype.validateEnd = function (end) { | ||||
|             var bounds = this.timeAPI.bounds(); | ||||
|             return this.timeAPI.validateBounds({start: bounds.start, end: end}) === true; | ||||
|         }; | ||||
|  | ||||
|         TimeConductorValidation.prototype.validateStartOffset = function (startOffset) { | ||||
|             return !isNaN(startOffset) && startOffset > 0; | ||||
|         }; | ||||
|  | ||||
|         TimeConductorValidation.prototype.validateEndOffset = function (endOffset) { | ||||
|             return !isNaN(endOffset) && endOffset >= 0; | ||||
|         }; | ||||
|  | ||||
|         return TimeConductorValidation; | ||||
|     } | ||||
| ); | ||||
| @@ -1,73 +0,0 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT Web, Copyright (c) 2014-2015, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT Web is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT Web includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define(['./TimeConductorValidation'], function (TimeConductorValidation) { | ||||
|     describe("The Time Conductor Validation class", function () { | ||||
|         var timeConductorValidation, | ||||
|             mockTimeConductor; | ||||
|  | ||||
|         beforeEach(function () { | ||||
|             mockTimeConductor = jasmine.createSpyObj("timeConductor", [ | ||||
|                 "validateBounds", | ||||
|                 "bounds" | ||||
|             ]); | ||||
|             timeConductorValidation = new TimeConductorValidation(mockTimeConductor); | ||||
|         }); | ||||
|  | ||||
|         describe("Validates start and end values using Time Conductor", function () { | ||||
|             beforeEach(function () { | ||||
|                 var mockBounds = { | ||||
|                     start: 10, | ||||
|                     end: 20 | ||||
|                 }; | ||||
|  | ||||
|                 mockTimeConductor.bounds.and.returnValue(mockBounds); | ||||
|  | ||||
|             }); | ||||
|             it("Validates start values using Time Conductor", function () { | ||||
|                 var startValue = 30; | ||||
|                 timeConductorValidation.validateStart(startValue); | ||||
|                 expect(mockTimeConductor.validateBounds).toHaveBeenCalled(); | ||||
|             }); | ||||
|             it("Validates end values using Time Conductor", function () { | ||||
|                 var endValue = 40; | ||||
|                 timeConductorValidation.validateEnd(endValue); | ||||
|                 expect(mockTimeConductor.validateBounds).toHaveBeenCalled(); | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         it("Validates that start Offset is valid number > 0", function () { | ||||
|             expect(timeConductorValidation.validateStartOffset(-1)).toBe(false); | ||||
|             expect(timeConductorValidation.validateStartOffset("abc")).toBe(false); | ||||
|             expect(timeConductorValidation.validateStartOffset("1")).toBe(true); | ||||
|             expect(timeConductorValidation.validateStartOffset(1)).toBe(true); | ||||
|         }); | ||||
|  | ||||
|         it("Validates that end Offset is valid number >= 0", function () { | ||||
|             expect(timeConductorValidation.validateEndOffset(-1)).toBe(false); | ||||
|             expect(timeConductorValidation.validateEndOffset("abc")).toBe(false); | ||||
|             expect(timeConductorValidation.validateEndOffset("1")).toBe(true); | ||||
|             expect(timeConductorValidation.validateEndOffset(0)).toBe(true); | ||||
|             expect(timeConductorValidation.validateEndOffset(1)).toBe(true); | ||||
|         }); | ||||
|     }); | ||||
| }); | ||||
| @@ -1,97 +0,0 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT Web, Copyright (c) 2014-2015, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT Web is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT Web includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define( | ||||
|     [ | ||||
|         'EventEmitter' | ||||
|     ], | ||||
|     function (EventEmitter) { | ||||
|  | ||||
|         /** | ||||
|          * The TimeConductorViewService acts as an event bus between different | ||||
|          * elements of the Time Conductor UI. Zooming and panning occur via this | ||||
|          * service, as they are specific behaviour of the UI, and not general | ||||
|          * functions of the time API. | ||||
|          * | ||||
|          * Synchronization of conductor state between the Time API and the URL | ||||
|          * also occurs from the conductor view service, whose lifecycle persists | ||||
|          * between view changes. | ||||
|          * | ||||
|          * @memberof platform.features.conductor | ||||
|          * @param conductor | ||||
|          * @constructor | ||||
|          */ | ||||
|         function TimeConductorViewService(openmct) { | ||||
|  | ||||
|             EventEmitter.call(this); | ||||
|  | ||||
|             this.timeAPI = openmct.time; | ||||
|         } | ||||
|  | ||||
|         TimeConductorViewService.prototype = Object.create(EventEmitter.prototype); | ||||
|  | ||||
|         /** | ||||
|          * An event to indicate that zooming is taking place | ||||
|          * @event platform.features.conductor.TimeConductorViewService~zoom | ||||
|          * @property {ZoomLevel} zoom the new zoom level. | ||||
|          */ | ||||
|         /** | ||||
|          * Zoom to given time span. Will fire a zoom event with new zoom | ||||
|          * bounds. Zoom bounds emitted in this way are considered ephemeral | ||||
|          * and should be overridden by any time conductor bounds events. Does | ||||
|          * not set bounds globally. | ||||
|          * @param {number} zoom A time duration in ms | ||||
|          * @fires platform.features.conductor.TimeConductorViewService~zoom | ||||
|          * @see module:openmct.TimeConductor#bounds | ||||
|          */ | ||||
|         TimeConductorViewService.prototype.zoom = function (timeSpan) { | ||||
|             var zoom = {}; | ||||
|  | ||||
|             // If a tick source is defined, then the concept of 'now' is | ||||
|             // important. Calculate zoom based on 'now'. | ||||
|             if (this.timeAPI.clock() !== undefined) { | ||||
|                 zoom.offsets = { | ||||
|                     start: -timeSpan, | ||||
|                     end: this.timeAPI.clockOffsets().end | ||||
|                 }; | ||||
|  | ||||
|                 var currentVal = this.timeAPI.clock().currentValue(); | ||||
|  | ||||
|                 zoom.bounds = { | ||||
|                     start: currentVal + zoom.offsets.start, | ||||
|                     end: currentVal + zoom.offsets.end | ||||
|                 }; | ||||
|             } else { | ||||
|                 var bounds = this.timeAPI.bounds(); | ||||
|                 var center = bounds.start + ((bounds.end - bounds.start)) / 2; | ||||
|                 bounds.start = center - timeSpan / 2; | ||||
|                 bounds.end = center + timeSpan / 2; | ||||
|                 zoom.bounds = bounds; | ||||
|             } | ||||
|  | ||||
|             this.emit("zoom", zoom); | ||||
|             return zoom; | ||||
|         }; | ||||
|  | ||||
|         return TimeConductorViewService; | ||||
|     } | ||||
| ); | ||||
| @@ -1,109 +0,0 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT Web, Copyright (c) 2014-2015, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT Web is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT Web includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define( | ||||
|     [], | ||||
|     function () { | ||||
|  | ||||
|         /** | ||||
|          * Controller for the Time of Interest element used in various views to display the TOI. Responsible for setting | ||||
|          * the text label for the current TOI, and for toggling the (un)pinned state which determines whether the TOI | ||||
|          * indicator is visible. | ||||
|          * @constructor | ||||
|          */ | ||||
|         function TimeOfInterestController($scope, openmct, formatService) { | ||||
|             this.timeAPI = openmct.time; | ||||
|             this.formatService = formatService; | ||||
|             this.format = undefined; | ||||
|             this.toiText = undefined; | ||||
|             this.$scope = $scope; | ||||
|  | ||||
|             //Bind all class functions to 'this' | ||||
|             Object.keys(TimeOfInterestController.prototype).filter(function (key) { | ||||
|                 return typeof TimeOfInterestController.prototype[key] === 'function'; | ||||
|             }).forEach(function (key) { | ||||
|                 this[key] = TimeOfInterestController.prototype[key].bind(this); | ||||
|             }.bind(this)); | ||||
|  | ||||
|             this.timeAPI.on('timeOfInterest', this.changeTimeOfInterest); | ||||
|             this.timeAPI.on('timeSystem', this.changeTimeSystem); | ||||
|             if (this.timeAPI.timeSystem() !== undefined) { | ||||
|                 this.changeTimeSystem(this.timeAPI.timeSystem()); | ||||
|                 var toi = this.timeAPI.timeOfInterest(); | ||||
|                 if (toi) { | ||||
|                     this.changeTimeOfInterest(toi); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             $scope.$on('$destroy', this.destroy); | ||||
|         } | ||||
|  | ||||
|         /** | ||||
|          * Called when the time of interest changes on the conductor. Will pin (display) the TOI indicator, and set the | ||||
|          * text using the default formatter of the currently active Time System. | ||||
|          * @private | ||||
|          * @param {integer} toi Current time of interest in ms | ||||
|          */ | ||||
|         TimeOfInterestController.prototype.changeTimeOfInterest = function (toi) { | ||||
|             if (toi !== undefined) { | ||||
|                 this.$scope.pinned = true; | ||||
|                 this.toiText = this.format.format(toi); | ||||
|             } else { | ||||
|                 this.$scope.pinned = false; | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * When time system is changed, update the formatter used to | ||||
|          * display the current TOI label | ||||
|          */ | ||||
|         TimeOfInterestController.prototype.changeTimeSystem = function (timeSystem) { | ||||
|             this.format = this.formatService.getFormat(timeSystem.timeFormat); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * @private | ||||
|          */ | ||||
|         TimeOfInterestController.prototype.destroy = function () { | ||||
|             this.timeAPI.off('timeOfInterest', this.changeTimeOfInterest); | ||||
|             this.timeAPI.off('timeSystem', this.changeTimeSystem); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Will unpin (hide) the TOI indicator. Has the effect of setting the time of interest to `undefined` on the | ||||
|          * Time Conductor | ||||
|          */ | ||||
|         TimeOfInterestController.prototype.dismiss = function () { | ||||
|             this.timeAPI.timeOfInterest(undefined); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Sends out a time of interest event with the effect of resetting | ||||
|          * the TOI displayed in views. | ||||
|          */ | ||||
|         TimeOfInterestController.prototype.resync = function () { | ||||
|             this.timeAPI.timeOfInterest(this.timeAPI.timeOfInterest()); | ||||
|         }; | ||||
|  | ||||
|         return TimeOfInterestController; | ||||
|     } | ||||
| ); | ||||
| @@ -19,10 +19,10 @@ | ||||
|  this source code distribution or the Licensing information page available | ||||
|  at runtime from the About dialog for additional information. | ||||
| --> | ||||
| <a class="l-hyperlink s-hyperlink" ng-controller="HyperlinkController as hyperlink" href="{{domainObject.getModel().url}}" | ||||
| <a class="c-hyperlink u-links" ng-controller="HyperlinkController as hyperlink" href="{{domainObject.getModel().url}}" | ||||
|    ng-attr-target="{{hyperlink.openNewTab() ? '_blank' : undefined}}" | ||||
|    ng-class="{ | ||||
|        's-button': hyperlink.isButton() | ||||
|    }"> | ||||
|     <span class="label">{{domainObject.getModel().displayText}}</span> | ||||
|    'c-hyperlink--button u-fills-container' : hyperlink.isButton(), | ||||
|    'c-hyperlink--link' : !hyperlink.isButton() }"> | ||||
|     <span class="c-hyperlink__label">{{domainObject.getModel().displayText}}</span> | ||||
| </a> | ||||
|   | ||||
| @@ -1,83 +0,0 @@ | ||||
| <!-- | ||||
|  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. | ||||
| --> | ||||
|  | ||||
| <div class="abs l-layout {{ domainObject.getModel().layoutAdvancedCss }}" | ||||
|      ng-controller="LayoutController as controller" | ||||
|      ng-click="controller.bypassSelection($event)"> | ||||
|  | ||||
|     <!-- Background grid --> | ||||
|     <div class="l-grid-holder" | ||||
|          ng-show="!controller.drilledIn" | ||||
|          ng-click="controller.bypassSelection($event)"> | ||||
|         <div class="l-grid l-grid-x" | ||||
|              ng-if="!controller.getGridSize()[0] < 3" | ||||
|              ng-style="{ 'background-size': controller.getGridSize() [0] + 'px 100%' }"></div> | ||||
|         <div class="l-grid l-grid-y" | ||||
|              ng-if="!controller.getGridSize()[1] < 3" | ||||
|              ng-style="{ 'background-size': '100% ' + controller.getGridSize() [1] + 'px' }"></div> | ||||
|     </div> | ||||
|  | ||||
|     <div class="frame t-frame-outer child-frame panel s-selectable s-moveable s-hover-border t-object-type-{{ childObject.getModel().type }}" | ||||
|          data-layout-id="{{childObject.getId() + '-' + $id}}" | ||||
|          ng-class="{ 'no-frame': !controller.hasFrame(childObject), 's-drilled-in': controller.isDrilledIn(childObject) }" | ||||
|          ng-repeat="childObject in composition" | ||||
|          ng-init="controller.selectIfNew(childObject.getId() + '-' + $id, childObject)" | ||||
|          mct-selectable="controller.getContext(childObject)" | ||||
|          ng-dblclick="controller.drill($event, childObject)" | ||||
|          ng-style="controller.getFrameStyle(childObject.getId())"> | ||||
|  | ||||
|         <mct-representation key="'frame'" | ||||
|                             class="t-rep-frame holder contents abs" | ||||
|                             mct-object="childObject"> | ||||
|         </mct-representation> | ||||
|         <!-- Drag handles --> | ||||
|         <span class="abs t-edit-handle-holder" ng-if="controller.selected(childObject) && !controller.isDrilledIn(childObject)"> | ||||
|             <span class="edit-handle edit-move" | ||||
|                   mct-drag-down="controller.startDrag(childObject.getId(), [1,1], [0,0])" | ||||
|                   mct-drag="controller.continueDrag(delta)" | ||||
|                   mct-drag-up="controller.endDrag()"> | ||||
|             </span> | ||||
|  | ||||
|             <span class="edit-corner edit-resize-nw" | ||||
|                   mct-drag-down="controller.startDrag(childObject.getId(), [1,1], [-1,-1])" | ||||
|                   mct-drag="controller.continueDrag(delta)" | ||||
|                   mct-drag-up="controller.endDrag()"> | ||||
|             </span> | ||||
|             <span class="edit-corner edit-resize-ne" | ||||
|                   mct-drag-down="controller.startDrag(childObject.getId(), [0,1], [1,-1])" | ||||
|                   mct-drag="controller.continueDrag(delta)" | ||||
|                   mct-drag-up="controller.endDrag()"> | ||||
|             </span> | ||||
|             <span class="edit-corner edit-resize-sw" | ||||
|                   mct-drag-down="controller.startDrag(childObject.getId(), [1,0], [-1,1])" | ||||
|                   mct-drag="controller.continueDrag(delta)" | ||||
|                   mct-drag-up="controller.endDrag()"> | ||||
|             </span> | ||||
|             <span class="edit-corner edit-resize-se" | ||||
|                   mct-drag-down="controller.startDrag(childObject.getId(), [0,0], [1,1])" | ||||
|                   mct-drag="controller.continueDrag(delta)" | ||||
|                   mct-drag-up="controller.endDrag()"> | ||||
|             </span> | ||||
|         </span> | ||||
|     </div> | ||||
|  | ||||
| </div> | ||||
| @@ -1,524 +0,0 @@ | ||||
| /***************************************************************************** | ||||
|  * 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. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| /** | ||||
|  * This bundle implements object types and associated views for | ||||
|  * display-building. | ||||
|  * @namespace platform/features/layout | ||||
|  */ | ||||
| define( | ||||
|     [ | ||||
|         'zepto', | ||||
|         './LayoutDrag' | ||||
|     ], | ||||
|     function ( | ||||
|         $, | ||||
|         LayoutDrag | ||||
|     ) { | ||||
|  | ||||
|         var DEFAULT_DIMENSIONS = [12, 8], | ||||
|             DEFAULT_GRID_SIZE = [32, 32], | ||||
|             MINIMUM_FRAME_SIZE = [320, 180]; | ||||
|  | ||||
|         var DEFAULT_HIDDEN_FRAME_TYPES = [ | ||||
|             'hyperlink' | ||||
|         ]; | ||||
|  | ||||
|         /** | ||||
|          * The LayoutController is responsible for supporting the | ||||
|          * Layout view. It arranges frames according to saved configuration | ||||
|          * and provides methods for updating these based on mouse | ||||
|          * movement. | ||||
|          * @memberof platform/features/layout | ||||
|          * @constructor | ||||
|          * @param {Scope} $scope the controller's Angular scope | ||||
|          */ | ||||
|         function LayoutController($scope, $element, openmct) { | ||||
|             var self = this, | ||||
|                 callbackCount = 0; | ||||
|  | ||||
|             this.$element = $element; | ||||
|  | ||||
|             // Update grid size when it changed | ||||
|             function updateGridSize(layoutGrid) { | ||||
|                 var oldSize = self.gridSize; | ||||
|  | ||||
|                 self.gridSize = layoutGrid || DEFAULT_GRID_SIZE; | ||||
|  | ||||
|                 // Only update panel positions if this actually changed things | ||||
|                 if (self.gridSize[0] !== oldSize[0] || | ||||
|                         self.gridSize[1] !== oldSize[1]) { | ||||
|                     self.layoutPanels(Object.keys(self.positions)); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             // Position a panel after a drop event | ||||
|             function handleDrop(e, id, position) { | ||||
|                 if (e.defaultPrevented) { | ||||
|                     return; | ||||
|                 } | ||||
|  | ||||
|                 $scope.configuration = $scope.configuration || {}; | ||||
|                 $scope.configuration.panels = $scope.configuration.panels || {}; | ||||
|  | ||||
|                 self.openmct.objects.get(id).then(function (object) { | ||||
|                     $scope.configuration.panels[id] = { | ||||
|                         position: [ | ||||
|                             Math.floor(position.x / self.gridSize[0]), | ||||
|                             Math.floor(position.y / self.gridSize[1]) | ||||
|                         ], | ||||
|                         dimensions: self.defaultDimensions(), | ||||
|                         hasFrame: self.getDefaultFrame(object.type) | ||||
|                     }; | ||||
|  | ||||
|                     // Store the id so that the newly-dropped object | ||||
|                     // gets selected during refresh composition | ||||
|                     self.droppedIdToSelectAfterRefresh = id; | ||||
|  | ||||
|                     self.commit(); | ||||
|  | ||||
|                     // Populate template-facing position for this id | ||||
|                     self.rawPositions[id] = $scope.configuration.panels[id]; | ||||
|                     self.populatePosition(id); | ||||
|                     refreshComposition(); | ||||
|                 }); | ||||
|  | ||||
|                 // Layout may contain embedded views which will | ||||
|                 // listen for drops, so call preventDefault() so | ||||
|                 // that they can recognize that this event is handled. | ||||
|                 e.preventDefault(); | ||||
|             } | ||||
|  | ||||
|             //Will fetch fully contextualized composed objects, and populate | ||||
|             // scope with them. | ||||
|             function refreshComposition() { | ||||
|                 //Keep a track of how many composition callbacks have been made | ||||
|                 var thisCount = ++callbackCount; | ||||
|  | ||||
|                 $scope.domainObject.useCapability('composition').then(function (composition) { | ||||
|                     var ids; | ||||
|  | ||||
|                     //Is this callback for the most recent composition | ||||
|                     // request? If not, discard it. Prevents race condition | ||||
|                     if (thisCount === callbackCount) { | ||||
|                         ids = composition.map(function (object) { | ||||
|                             return object.getId(); | ||||
|                         }) || []; | ||||
|  | ||||
|                         $scope.composition = composition; | ||||
|                         self.layoutPanels(ids); | ||||
|                         self.setFrames(ids); | ||||
|  | ||||
|                         if (self.selectedId && | ||||
|                             self.selectedId !== $scope.domainObject.getId() && | ||||
|                             composition.indexOf(self.selectedId) === -1) { | ||||
|                             // Click triggers selection of layout parent. | ||||
|                             self.$element[0].click(); | ||||
|                         } | ||||
|                     } | ||||
|                 }); | ||||
|             } | ||||
|  | ||||
|             // End drag; we don't want to put $scope into this | ||||
|             // because it triggers "cpws" (copy window or scope) | ||||
|             // errors in Angular. | ||||
|             this.endDragInScope = function () { | ||||
|                 // Write to configuration; this is watched and | ||||
|                 // saved by the EditRepresenter. | ||||
|                 $scope.configuration = | ||||
|                     $scope.configuration || {}; | ||||
|  | ||||
|                 $scope.configuration.panels = | ||||
|                     $scope.configuration.panels || {}; | ||||
|  | ||||
|                 $scope.configuration.panels[self.activeDragId] = | ||||
|                     $scope.configuration.panels[self.activeDragId] || {}; | ||||
|  | ||||
|                 $scope.configuration.panels[self.activeDragId].position = | ||||
|                     self.rawPositions[self.activeDragId].position; | ||||
|                 $scope.configuration.panels[self.activeDragId].dimensions = | ||||
|                     self.rawPositions[self.activeDragId].dimensions; | ||||
|  | ||||
|                 self.commit(); | ||||
|             }; | ||||
|  | ||||
|             // Sets the selectable object in response to the selection change event. | ||||
|             function setSelection(selectable) { | ||||
|                 var selection = selectable[0]; | ||||
|  | ||||
|                 if (!selection) { | ||||
|                     delete self.selectedId; | ||||
|                     return; | ||||
|                 } | ||||
|  | ||||
|                 self.selectedId = selection.context.oldItem.getId(); | ||||
|                 self.drilledIn = undefined; | ||||
|                 self.selectable = selectable; | ||||
|             } | ||||
|  | ||||
|             this.positions = {}; | ||||
|             this.rawPositions = {}; | ||||
|             this.gridSize = DEFAULT_GRID_SIZE; | ||||
|             this.$scope = $scope; | ||||
|             this.drilledIn = undefined; | ||||
|             this.openmct = openmct; | ||||
|  | ||||
|             // Watch for changes to the grid size in the model | ||||
|             $scope.$watch("model.layoutGrid", updateGridSize); | ||||
|  | ||||
|             // Update composed objects on screen, and position panes | ||||
|             $scope.$watchCollection("model.composition", refreshComposition); | ||||
|  | ||||
|             openmct.selection.on('change', setSelection); | ||||
|  | ||||
|             $scope.$on("$destroy", function () { | ||||
|                 openmct.selection.off("change", setSelection); | ||||
|                 self.unlisten(); | ||||
|             }); | ||||
|  | ||||
|             $scope.$on("mctDrop", handleDrop); | ||||
|  | ||||
|             self.unlisten = self.$scope.domainObject.getCapability('mutation').listen(function (model) { | ||||
|                 $scope.configuration = model.configuration.layout; | ||||
|                 $scope.model = model; | ||||
|                 var panels = $scope.configuration.panels; | ||||
|  | ||||
|                 Object.keys(panels).forEach(function (key) { | ||||
|                     if (self.frames && self.frames.hasOwnProperty(key)) { | ||||
|                         self.frames[key] = panels[key].hasFrame; | ||||
|                     } | ||||
|                 }); | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         // Utility function to copy raw positions from configuration, | ||||
|         // without writing directly to configuration (to avoid triggering | ||||
|         // persistence from watchers during drags). | ||||
|         function shallowCopy(obj, keys) { | ||||
|             var copy = {}; | ||||
|             keys.forEach(function (k) { | ||||
|                 copy[k] = obj[k]; | ||||
|             }); | ||||
|             return copy; | ||||
|         } | ||||
|  | ||||
|         /** | ||||
|          * Set the frames value. If a configuration panel has "hasFrame' property, | ||||
|          * use that value, otherwise set a default value. A 'hyperlink' object should | ||||
|          * have no frame by default. | ||||
|          * | ||||
|          * @param {string[]} ids the object ids | ||||
|          * @private | ||||
|          */ | ||||
|         LayoutController.prototype.setFrames = function (ids) { | ||||
|             var panels = shallowCopy(this.$scope.configuration.panels || {}, ids); | ||||
|             this.frames = {}; | ||||
|  | ||||
|             this.$scope.composition.forEach(function (object) { | ||||
|                 var id = object.getId(); | ||||
|                 panels[id] = panels[id] || {}; | ||||
|  | ||||
|                 if (panels[id].hasOwnProperty('hasFrame')) { | ||||
|                     this.frames[id] = panels[id].hasFrame; | ||||
|                 } else { | ||||
|                     this.frames[id] = this.getDefaultFrame(object.getModel().type); | ||||
|                 } | ||||
|             }, this); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Gets the default value for frame. | ||||
|          * | ||||
|          * @param type the domain object type | ||||
|          * @return {boolean} true if the object should have | ||||
|          *         frame by default, false, otherwise | ||||
|          */ | ||||
|         LayoutController.prototype.getDefaultFrame = function (type) { | ||||
|             return DEFAULT_HIDDEN_FRAME_TYPES.indexOf(type) === -1; | ||||
|         }; | ||||
|  | ||||
|         // Convert from { positions: ..., dimensions: ... } to an | ||||
|         // appropriate ng-style argument, to position frames. | ||||
|         LayoutController.prototype.convertPosition = function (raw) { | ||||
|             var gridSize = this.gridSize; | ||||
|             // Multiply position/dimensions by grid size | ||||
|             return { | ||||
|                 left: (gridSize[0] * raw.position[0]) + 'px', | ||||
|                 top: (gridSize[1] * raw.position[1]) + 'px', | ||||
|                 width: (gridSize[0] * raw.dimensions[0]) + 'px', | ||||
|                 height: (gridSize[1] * raw.dimensions[1]) + 'px', | ||||
|                 minWidth: (gridSize[0] * raw.dimensions[0]) + 'px', | ||||
|                 minHeight: (gridSize[1] * raw.dimensions[1]) + 'px' | ||||
|             }; | ||||
|         }; | ||||
|  | ||||
|         // Generate default positions for a new panel | ||||
|         LayoutController.prototype.defaultDimensions = function () { | ||||
|             var gridSize = this.gridSize; | ||||
|             return MINIMUM_FRAME_SIZE.map(function (min, i) { | ||||
|                 return Math.max( | ||||
|                     Math.ceil(min / gridSize[i]), | ||||
|                     DEFAULT_DIMENSIONS[i] | ||||
|                 ); | ||||
|             }); | ||||
|         }; | ||||
|  | ||||
|         // Generate a default position (in its raw format) for a frame. | ||||
|         // Use an index to ensure that default positions are unique. | ||||
|         LayoutController.prototype.defaultPosition = function (index) { | ||||
|             return { | ||||
|                 position: [index, index], | ||||
|                 dimensions: this.defaultDimensions() | ||||
|             }; | ||||
|         }; | ||||
|  | ||||
|         // Store a computed position for a contained frame by its | ||||
|         // domain object id. Called in a forEach loop, so arguments | ||||
|         // are as expected there. | ||||
|         LayoutController.prototype.populatePosition = function (id, index) { | ||||
|             this.rawPositions[id] = | ||||
|                 this.rawPositions[id] || this.defaultPosition(index || 0); | ||||
|             this.positions[id] = | ||||
|                 this.convertPosition(this.rawPositions[id]); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Get a style object for a frame with the specified domain | ||||
|          * object identifier, suitable for use in an `ng-style` | ||||
|          * directive to position a frame as configured for this layout. | ||||
|          * @param {string} id the object identifier | ||||
|          * @returns {Object.<string, string>} an object with | ||||
|          *          appropriate left, width, etc fields for positioning | ||||
|          */ | ||||
|         LayoutController.prototype.getFrameStyle = function (id) { | ||||
|             // Called in a loop, so just look up; the "positions" | ||||
|             // object is kept up to date by a watch. | ||||
|             return this.positions[id]; | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Start a drag gesture to move/resize a frame. | ||||
|          * | ||||
|          * The provided position and dimensions factors will determine | ||||
|          * whether this is a move or a resize, and what type it | ||||
|          * will be. For instance, a position factor of [1, 1] | ||||
|          * will move a frame along with the mouse as the drag | ||||
|          * proceeds, while a dimension factor of [0, 0] will leave | ||||
|          * dimensions unchanged. Combining these in different | ||||
|          * ways results in different handles; a position factor of | ||||
|          * [1, 0] and a dimensions factor of [-1, 0] will implement | ||||
|          * a left-edge resize, as the horizontal position will move | ||||
|          * with the mouse while the horizontal dimensions shrink in | ||||
|          * kind (and vertical properties remain unmodified.) | ||||
|          * | ||||
|          * @param {string} id the identifier of the domain object | ||||
|          *        in the frame being manipulated | ||||
|          * @param {number[]} posFactor the position factor | ||||
|          * @param {number[]} dimFactor the dimensions factor | ||||
|          */ | ||||
|         LayoutController.prototype.startDrag = function (id, posFactor, dimFactor) { | ||||
|             this.activeDragId = id; | ||||
|             this.activeDrag = new LayoutDrag( | ||||
|                 this.rawPositions[id], | ||||
|                 posFactor, | ||||
|                 dimFactor, | ||||
|                 this.gridSize | ||||
|             ); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Continue an active drag gesture. | ||||
|          * @param {number[]} delta the offset, in pixels, | ||||
|          *        of the current pointer position, relative | ||||
|          *        to its position when the drag started | ||||
|          */ | ||||
|         LayoutController.prototype.continueDrag = function (delta) { | ||||
|             if (this.activeDrag) { | ||||
|                 this.rawPositions[this.activeDragId] = | ||||
|                     this.activeDrag.getAdjustedPosition(delta); | ||||
|                 this.populatePosition(this.activeDragId); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Compute panel positions based on the layout's object model. | ||||
|          * Defined as member function to facilitate testing. | ||||
|          * @private | ||||
|          */ | ||||
|         LayoutController.prototype.layoutPanels = function (ids) { | ||||
|             var configuration = this.$scope.configuration || {}, | ||||
|                 self = this; | ||||
|  | ||||
|             // Pull panel positions from configuration | ||||
|             this.rawPositions = | ||||
|                 shallowCopy(configuration.panels || {}, ids); | ||||
|  | ||||
|             // Clear prior computed positions | ||||
|             this.positions = {}; | ||||
|  | ||||
|             // Update width/height that we are tracking | ||||
|             this.gridSize = | ||||
|                 (this.$scope.model || {}).layoutGrid || DEFAULT_GRID_SIZE; | ||||
|  | ||||
|             // Compute positions and add defaults where needed | ||||
|             ids.forEach(function (id, index) { | ||||
|                 self.populatePosition(id, index); | ||||
|             }); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * End the active drag gesture. This will update the | ||||
|          * view configuration. | ||||
|          */ | ||||
|         LayoutController.prototype.endDrag = function () { | ||||
|             this.dragInProgress = true; | ||||
|  | ||||
|             setTimeout(function () { | ||||
|                 this.dragInProgress = false; | ||||
|             }.bind(this), 0); | ||||
|  | ||||
|             this.endDragInScope(); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Checks if the object is currently selected. | ||||
|          * | ||||
|          * @param {string} obj the object to check for selection | ||||
|          * @returns {boolean} true if selected, otherwise false | ||||
|          */ | ||||
|         LayoutController.prototype.selected = function (obj) { | ||||
|             var sobj = this.openmct.selection.get()[0]; | ||||
|             return (sobj && sobj.context.oldItem.getId() === obj.getId()) ? true : false; | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Bypasses selection if drag is in progress. | ||||
|          * | ||||
|          * @param event the angular event object | ||||
|          */ | ||||
|         LayoutController.prototype.bypassSelection = function (event) { | ||||
|             if (this.dragInProgress) { | ||||
|                 if (event) { | ||||
|                     event.stopPropagation(); | ||||
|                 } | ||||
|                 return; | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Checks if the domain object is drilled in. | ||||
|          * | ||||
|          * @param domainObject the domain object | ||||
|          * @return true if the object is drilled in, false otherwise | ||||
|          */ | ||||
|         LayoutController.prototype.isDrilledIn = function (domainObject) { | ||||
|             return this.drilledIn === domainObject.getId(); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Puts the given object in the drilled-in mode. | ||||
|          * | ||||
|          * @param event the angular event object | ||||
|          * @param domainObject the domain object | ||||
|          */ | ||||
|         LayoutController.prototype.drill = function (event, domainObject) { | ||||
|             if (event) { | ||||
|                 event.stopPropagation(); | ||||
|             } | ||||
|  | ||||
|             if (!domainObject.getCapability('editor').inEditContext()) { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             if (!domainObject.hasCapability('composition')) { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             // Disable since fixed position doesn't use the selection API yet | ||||
|             if (domainObject.getModel().type === 'telemetry.fixed') { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             this.drilledIn = domainObject.getId(); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Check if the object has frame. | ||||
|          * | ||||
|          * @param {object} obj the object | ||||
|          * @return {boolean} true if object has frame, otherwise false | ||||
|          */ | ||||
|         LayoutController.prototype.hasFrame = function (obj) { | ||||
|             return this.frames[obj.getId()]; | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Get the size of the grid, in pixels. The returned array | ||||
|          * is in the form `[x, y]`. | ||||
|          * @returns {number[]} the grid size | ||||
|          */ | ||||
|         LayoutController.prototype.getGridSize = function () { | ||||
|             return this.gridSize; | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Gets the selection context. | ||||
|          * | ||||
|          * @param domainObject the domain object | ||||
|          * @returns {object} the context object which includes item and oldItem | ||||
|          */ | ||||
|         LayoutController.prototype.getContext = function (domainObject) { | ||||
|             return { | ||||
|                 item: domainObject.useCapability('adapter'), | ||||
|                 oldItem: domainObject | ||||
|             }; | ||||
|         }; | ||||
|  | ||||
|         LayoutController.prototype.commit = function () { | ||||
|             var model = this.$scope.model; | ||||
|             model.configuration = model.configuration || {}; | ||||
|             model.configuration.layout = this.$scope.configuration; | ||||
|  | ||||
|             this.$scope.domainObject.useCapability('mutation', function () { | ||||
|                 return model; | ||||
|             }); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Selects a newly-dropped object. | ||||
|          * | ||||
|          * @param classSelector the css class selector | ||||
|          * @param domainObject the domain object | ||||
|          */ | ||||
|         LayoutController.prototype.selectIfNew = function (selector, domainObject) { | ||||
|             if (domainObject.getId() === this.droppedIdToSelectAfterRefresh) { | ||||
|                 setTimeout(function () { | ||||
|                     $('[data-layout-id="' + selector + '"]')[0].click(); | ||||
|                     delete this.droppedIdToSelectAfterRefresh; | ||||
|                 }.bind(this), 0); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         return LayoutController; | ||||
|     } | ||||
| ); | ||||
|  | ||||
| @@ -1,479 +0,0 @@ | ||||
| /***************************************************************************** | ||||
|  * 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( | ||||
|     [ | ||||
|         "../src/LayoutController", | ||||
|         "zepto" | ||||
|     ], | ||||
|     function ( | ||||
|         LayoutController, | ||||
|         $ | ||||
|     ) { | ||||
|  | ||||
|         describe("The Layout controller", function () { | ||||
|             var mockScope, | ||||
|                 mockEvent, | ||||
|                 testModel, | ||||
|                 testConfiguration, | ||||
|                 controller, | ||||
|                 mockCompositionCapability, | ||||
|                 mockComposition, | ||||
|                 mockCompositionObjects, | ||||
|                 mockOpenMCT, | ||||
|                 mockSelection, | ||||
|                 mockDomainObjectCapability, | ||||
|                 mockObjects, | ||||
|                 unlistenFunc, | ||||
|                 $element = [], | ||||
|                 selectable = []; | ||||
|  | ||||
|             function mockPromise(value) { | ||||
|                 return { | ||||
|                     then: function (thenFunc) { | ||||
|                         return mockPromise(thenFunc(value)); | ||||
|                     } | ||||
|                 }; | ||||
|             } | ||||
|  | ||||
|             function mockDomainObject(id) { | ||||
|                 return { | ||||
|                     getId: function () { | ||||
|                         return id; | ||||
|                     }, | ||||
|                     useCapability: function () { | ||||
|                         return mockCompositionCapability; | ||||
|                     }, | ||||
|                     getModel: function () { | ||||
|                         if (id === 'b') { | ||||
|                             return { | ||||
|                                 type : 'hyperlink' | ||||
|                             }; | ||||
|                         } else { | ||||
|                             return {}; | ||||
|                         } | ||||
|                     }, | ||||
|                     getCapability: function () { | ||||
|                         return mockDomainObjectCapability; | ||||
|                     }, | ||||
|                     hasCapability: function (param) { | ||||
|                         if (param === 'composition') { | ||||
|                             return id !== 'b'; | ||||
|                         } | ||||
|                     }, | ||||
|                     type: "testType" | ||||
|                 }; | ||||
|             } | ||||
|  | ||||
|             beforeEach(function () { | ||||
|                 mockScope = jasmine.createSpyObj( | ||||
|                     "$scope", | ||||
|                     ["$watch", "$watchCollection", "$on"] | ||||
|                 ); | ||||
|                 mockEvent = jasmine.createSpyObj( | ||||
|                     'event', | ||||
|                     ['preventDefault', 'stopPropagation'] | ||||
|                 ); | ||||
|  | ||||
|                 testModel = {}; | ||||
|  | ||||
|                 mockComposition = ["a", "b", "c"]; | ||||
|                 mockCompositionObjects = mockComposition.map(mockDomainObject); | ||||
|  | ||||
|                 testConfiguration = { | ||||
|                     panels: { | ||||
|                         a: { | ||||
|                             position: [20, 10], | ||||
|                             dimensions: [5, 5] | ||||
|                         } | ||||
|                     } | ||||
|                 }; | ||||
|  | ||||
|                 unlistenFunc = jasmine.createSpy("unlisten"); | ||||
|                 mockDomainObjectCapability = jasmine.createSpyObj('capability', | ||||
|                     ['inEditContext', 'listen'] | ||||
|                 ); | ||||
|                 mockDomainObjectCapability.listen.and.returnValue(unlistenFunc); | ||||
|  | ||||
|                 mockCompositionCapability = mockPromise(mockCompositionObjects); | ||||
|  | ||||
|                 mockScope.domainObject = mockDomainObject("mockDomainObject"); | ||||
|                 mockScope.model = testModel; | ||||
|                 mockScope.configuration = testConfiguration; | ||||
|  | ||||
|                 selectable[0] = { | ||||
|                     context: { | ||||
|                         oldItem: mockScope.domainObject | ||||
|                     } | ||||
|                 }; | ||||
|  | ||||
|                 mockSelection = jasmine.createSpyObj("selection", [ | ||||
|                     'select', | ||||
|                     'on', | ||||
|                     'off', | ||||
|                     'get' | ||||
|                 ]); | ||||
|                 mockSelection.get.and.returnValue(selectable); | ||||
|  | ||||
|                 mockObjects = jasmine.createSpyObj('objects', [ | ||||
|                     'get' | ||||
|                 ]); | ||||
|                 mockObjects.get.and.returnValue(mockPromise(mockDomainObject("mockObject"))); | ||||
|                 mockOpenMCT = { | ||||
|                     selection: mockSelection, | ||||
|                     objects: mockObjects | ||||
|                 }; | ||||
|  | ||||
|                 $element = $('<div></div>'); | ||||
|                 $(document).find('body').append($element); | ||||
|                 spyOn($element[0], 'click'); | ||||
|  | ||||
|                 spyOn(mockScope.domainObject, "useCapability").and.callThrough(); | ||||
|  | ||||
|                 controller = new LayoutController(mockScope, $element, mockOpenMCT); | ||||
|                 spyOn(controller, "layoutPanels").and.callThrough(); | ||||
|                 spyOn(controller, "commit"); | ||||
|  | ||||
|                 jasmine.clock().install(); | ||||
|             }); | ||||
|  | ||||
|             afterEach(function () { | ||||
|                 $element.remove(); | ||||
|                 jasmine.clock().uninstall(); | ||||
|             }); | ||||
|  | ||||
|  | ||||
|             it("listens for selection change events", function () { | ||||
|                 expect(mockOpenMCT.selection.on).toHaveBeenCalledWith( | ||||
|                     'change', | ||||
|                     jasmine.any(Function) | ||||
|                 ); | ||||
|             }); | ||||
|  | ||||
|             it("cleans up on scope destroy", function () { | ||||
|                 expect(mockScope.$on).toHaveBeenCalledWith( | ||||
|                     '$destroy', | ||||
|                     jasmine.any(Function) | ||||
|                 ); | ||||
|  | ||||
|                 mockScope.$on.calls.all()[0].args[1](); | ||||
|  | ||||
|                 expect(mockOpenMCT.selection.off).toHaveBeenCalledWith( | ||||
|                     'change', | ||||
|                     jasmine.any(Function) | ||||
|                 ); | ||||
|             }); | ||||
|  | ||||
|             // Model changes will indicate that panel positions | ||||
|             // may have changed, for instance. | ||||
|             it("watches for changes to composition", function () { | ||||
|                 expect(mockScope.$watchCollection).toHaveBeenCalledWith( | ||||
|                     "model.composition", | ||||
|                     jasmine.any(Function) | ||||
|                 ); | ||||
|             }); | ||||
|  | ||||
|             it("Retrieves updated composition from composition capability", function () { | ||||
|                 mockScope.$watchCollection.calls.mostRecent().args[1](); | ||||
|                 expect(mockScope.domainObject.useCapability).toHaveBeenCalledWith( | ||||
|                     "composition" | ||||
|                 ); | ||||
|                 expect(controller.layoutPanels).toHaveBeenCalledWith( | ||||
|                     mockComposition | ||||
|                 ); | ||||
|             }); | ||||
|  | ||||
|             it("Is robust to concurrent changes to composition", function () { | ||||
|                 var secondMockComposition = ["a", "b", "c", "d"], | ||||
|                     secondMockCompositionObjects = secondMockComposition.map(mockDomainObject), | ||||
|                     firstCompositionCB, | ||||
|                     secondCompositionCB; | ||||
|  | ||||
|                 spyOn(mockCompositionCapability, "then"); | ||||
|                 mockScope.$watchCollection.calls.mostRecent().args[1](); | ||||
|                 mockScope.$watchCollection.calls.mostRecent().args[1](); | ||||
|  | ||||
|                 firstCompositionCB = mockCompositionCapability.then.calls.all()[0].args[0]; | ||||
|                 secondCompositionCB = mockCompositionCapability.then.calls.all()[1].args[0]; | ||||
|  | ||||
|                 //Resolve promises in reverse order | ||||
|                 secondCompositionCB(secondMockCompositionObjects); | ||||
|                 firstCompositionCB(mockCompositionObjects); | ||||
|  | ||||
|                 //Expect the promise call that was initiated most recently to | ||||
|                 // be the one used to populate scope, irrespective of order that | ||||
|                 // it was eventually resolved | ||||
|                 expect(mockScope.composition).toBe(secondMockCompositionObjects); | ||||
|             }); | ||||
|  | ||||
|  | ||||
|             it("provides styles for frames, from configuration", function () { | ||||
|                 mockScope.$watchCollection.calls.mostRecent().args[1](); | ||||
|                 expect(controller.getFrameStyle("a")).toEqual({ | ||||
|                     top: "320px", | ||||
|                     left: "640px", | ||||
|                     width: "160px", | ||||
|                     height: "160px", | ||||
|                     minWidth : '160px', | ||||
|                     minHeight : '160px' | ||||
|                 }); | ||||
|             }); | ||||
|  | ||||
|             it("provides default styles for frames", function () { | ||||
|                 var styleB, styleC; | ||||
|  | ||||
|                 // b and c do not have configured positions | ||||
|                 mockScope.$watchCollection.calls.mostRecent().args[1](); | ||||
|  | ||||
|                 styleB = controller.getFrameStyle("b"); | ||||
|                 styleC = controller.getFrameStyle("c"); | ||||
|  | ||||
|                 // Should have a position, but we don't care what | ||||
|                 expect(styleB.left).toBeDefined(); | ||||
|                 expect(styleB.top).toBeDefined(); | ||||
|                 expect(styleC.left).toBeDefined(); | ||||
|                 expect(styleC.top).toBeDefined(); | ||||
|  | ||||
|                 // Should have ensured some difference in position | ||||
|                 expect(styleB).not.toEqual(styleC); | ||||
|             }); | ||||
|  | ||||
|             it("allows panels to be dragged", function () { | ||||
|                 // Populate scope | ||||
|                 mockScope.$watchCollection.calls.mostRecent().args[1](); | ||||
|  | ||||
|                 // Verify precondition | ||||
|                 expect(testConfiguration.panels.b).not.toBeDefined(); | ||||
|  | ||||
|                 // Do a drag | ||||
|                 controller.startDrag("b", [1, 1], [0, 0]); | ||||
|                 controller.continueDrag([100, 100]); | ||||
|                 controller.endDrag(); | ||||
|  | ||||
|                 // We do not look closely at the details here; | ||||
|                 // that is tested in LayoutDragSpec. Just make sure | ||||
|                 // that a configuration for b has been defined. | ||||
|                 expect(testConfiguration.panels.b).toBeDefined(); | ||||
|             }); | ||||
|  | ||||
|  | ||||
|             it("invokes commit after drag", function () { | ||||
|                 // Populate scope | ||||
|                 mockScope.$watchCollection.calls.mostRecent().args[1](); | ||||
|  | ||||
|                 // Do a drag | ||||
|                 controller.startDrag("b", [1, 1], [0, 0]); | ||||
|                 controller.continueDrag([100, 100]); | ||||
|                 controller.endDrag(); | ||||
|  | ||||
|                 expect(controller.commit).toHaveBeenCalled(); | ||||
|             }); | ||||
|  | ||||
|             it("listens for drop events", function () { | ||||
|                 // Layout should position panels according to | ||||
|                 // where the user dropped them, so it needs to | ||||
|                 // listen for drop events. | ||||
|                 expect(mockScope.$on).toHaveBeenCalledWith( | ||||
|                     'mctDrop', | ||||
|                     jasmine.any(Function) | ||||
|                 ); | ||||
|  | ||||
|                 // Verify precondition | ||||
|                 expect(testConfiguration.panels.d).not.toBeDefined(); | ||||
|  | ||||
|                 // Notify that a drop occurred | ||||
|                 mockScope.$on.calls.mostRecent().args[1]( | ||||
|                     mockEvent, | ||||
|                     'd', | ||||
|                     { x: 300, y: 100 } | ||||
|                 ); | ||||
|                 expect(testConfiguration.panels.d).toBeDefined(); | ||||
|                 expect(mockEvent.preventDefault).toHaveBeenCalled(); | ||||
|                 expect(controller.commit).toHaveBeenCalled(); | ||||
|             }); | ||||
|  | ||||
|             it("ignores drops when default has been prevented", function () { | ||||
|                 // Avoids redundant drop-handling, WTD-1233 | ||||
|                 mockEvent.defaultPrevented = true; | ||||
|  | ||||
|                 // Notify that a drop occurred | ||||
|                 mockScope.$on.calls.mostRecent().args[1]( | ||||
|                     mockEvent, | ||||
|                     'd', | ||||
|                     { x: 300, y: 100 } | ||||
|                 ); | ||||
|                 expect(testConfiguration.panels.d).not.toBeDefined(); | ||||
|             }); | ||||
|  | ||||
|             it("ensures a minimum frame size", function () { | ||||
|                 var styleB; | ||||
|  | ||||
|                 // Start with a very small frame size | ||||
|                 testModel.layoutGrid = [1, 1]; | ||||
|  | ||||
|                 // White-boxy; we know which watch is which | ||||
|                 mockScope.$watch.calls.all()[0].args[1](testModel.layoutGrid); | ||||
|                 mockScope.$watchCollection.calls.all()[0].args[1](testModel.composition); | ||||
|  | ||||
|                 styleB = controller.getFrameStyle("b"); | ||||
|  | ||||
|                 // Resulting size should still be reasonably large pixel-size | ||||
|                 expect(parseInt(styleB.width, 10)).toBeGreaterThan(63); | ||||
|                 expect(parseInt(styleB.width, 10)).toBeGreaterThan(31); | ||||
|             }); | ||||
|  | ||||
|             it("ensures a minimum frame size on drop", function () { | ||||
|                 var style; | ||||
|  | ||||
|                 // Start with a very small frame size | ||||
|                 testModel.layoutGrid = [1, 1]; | ||||
|                 mockScope.$watch.calls.all()[0].args[1](testModel.layoutGrid); | ||||
|  | ||||
|                 // Add a new object to the composition | ||||
|                 mockComposition = ["a", "b", "c", "d"]; | ||||
|                 mockCompositionObjects = mockComposition.map(mockDomainObject); | ||||
|                 mockCompositionCapability = mockPromise(mockCompositionObjects); | ||||
|  | ||||
|                 // Notify that a drop occurred | ||||
|                 mockScope.$on.calls.mostRecent().args[1]( | ||||
|                     mockEvent, | ||||
|                     'd', | ||||
|                     { x: 300, y: 100 } | ||||
|                 ); | ||||
|  | ||||
|                 style = controller.getFrameStyle("d"); | ||||
|  | ||||
|                 // Resulting size should still be reasonably large pixel-size | ||||
|                 expect(parseInt(style.width, 10)).toBeGreaterThan(63); | ||||
|                 expect(parseInt(style.height, 10)).toBeGreaterThan(31); | ||||
|             }); | ||||
|  | ||||
|             it("updates positions of existing objects on a drop", function () { | ||||
|                 var oldStyle; | ||||
|  | ||||
|                 mockScope.$watchCollection.calls.mostRecent().args[1](); | ||||
|  | ||||
|                 oldStyle = controller.getFrameStyle("b"); | ||||
|  | ||||
|                 expect(oldStyle).toBeDefined(); | ||||
|  | ||||
|                 // ...drop event... | ||||
|                 mockScope.$on.calls.mostRecent() | ||||
|                     .args[1](mockEvent, 'b', { x: 300, y: 100 }); | ||||
|  | ||||
|                 expect(controller.getFrameStyle("b")) | ||||
|                     .not.toEqual(oldStyle); | ||||
|             }); | ||||
|  | ||||
|             it("allows objects to be selected", function () { | ||||
|                 mockScope.$watchCollection.calls.mostRecent().args[1](); | ||||
|                 var childObj = mockCompositionObjects[0]; | ||||
|                 selectable[0].context.oldItem = childObj; | ||||
|                 mockOpenMCT.selection.on.calls.mostRecent().args[1](selectable); | ||||
|  | ||||
|                 expect(controller.selected(childObj)).toBe(true); | ||||
|             }); | ||||
|  | ||||
|             it("prevents event bubbling while drag is in progress", function () { | ||||
|                 mockScope.$watchCollection.calls.mostRecent().args[1](); | ||||
|                 var childObj = mockCompositionObjects[0]; | ||||
|  | ||||
|                 // Do a drag | ||||
|                 controller.startDrag(childObj.getId(), [1, 1], [0, 0]); | ||||
|                 controller.continueDrag([100, 100]); | ||||
|                 controller.endDrag(); | ||||
|  | ||||
|                 // Because mouse position could cause the parent object to be selected, this should be ignored. | ||||
|                 controller.bypassSelection(mockEvent); | ||||
|  | ||||
|                 expect(mockEvent.stopPropagation).toHaveBeenCalled(); | ||||
|  | ||||
|                 // Shoud be able to select another object when dragging is done. | ||||
|                 jasmine.clock().tick(0); | ||||
|                 mockEvent.stopPropagation.calls.reset(); | ||||
|                 controller.bypassSelection(mockEvent); | ||||
|  | ||||
|                 expect(mockEvent.stopPropagation).not.toHaveBeenCalled(); | ||||
|             }); | ||||
|  | ||||
|             it("shows frames by default", function () { | ||||
|                 mockScope.$watchCollection.calls.mostRecent().args[1](); | ||||
|  | ||||
|                 expect(controller.hasFrame(mockCompositionObjects[0])).toBe(true); | ||||
|             }); | ||||
|  | ||||
|             it("hyperlinks hide frame by default", function () { | ||||
|                 mockScope.$watchCollection.calls.mostRecent().args[1](); | ||||
|  | ||||
|                 expect(controller.hasFrame(mockCompositionObjects[1])).toBe(false); | ||||
|             }); | ||||
|  | ||||
|             it("selects the parent object when selected object is removed", function () { | ||||
|                 mockScope.$watchCollection.calls.mostRecent().args[1](); | ||||
|                 var childObj = mockCompositionObjects[0]; | ||||
|                 selectable[0].context.oldItem = childObj; | ||||
|                 mockOpenMCT.selection.on.calls.mostRecent().args[1](selectable); | ||||
|  | ||||
|                 var composition = ["b", "c"]; | ||||
|                 mockScope.$watchCollection.calls.mostRecent().args[1](composition); | ||||
|  | ||||
|                 expect($element[0].click).toHaveBeenCalled(); | ||||
|             }); | ||||
|  | ||||
|             it("allows objects to be drilled-in only when editing", function () { | ||||
|                 mockScope.$watchCollection.calls.mostRecent().args[1](); | ||||
|                 var childObj = mockCompositionObjects[0]; | ||||
|                 childObj.getCapability().inEditContext.and.returnValue(false); | ||||
|                 controller.drill(mockEvent, childObj); | ||||
|  | ||||
|                 expect(controller.isDrilledIn(childObj)).toBe(false); | ||||
|             }); | ||||
|  | ||||
|             it("allows objects to be drilled-in only if it has sub objects", function () { | ||||
|                 mockScope.$watchCollection.calls.mostRecent().args[1](); | ||||
|                 var childObj = mockCompositionObjects[1]; | ||||
|                 childObj.getCapability().inEditContext.and.returnValue(true); | ||||
|                 controller.drill(mockEvent, childObj); | ||||
|  | ||||
|                 expect(controller.isDrilledIn(childObj)).toBe(false); | ||||
|             }); | ||||
|  | ||||
|             it("selects a newly-dropped object", function () { | ||||
|                 mockScope.$on.calls.mostRecent().args[1]( | ||||
|                     mockEvent, | ||||
|                     'd', | ||||
|                     { x: 300, y: 100 } | ||||
|                 ); | ||||
|  | ||||
|                 var childObj = mockDomainObject("d"); | ||||
|                 var testElement = $("<div data-layout-id='some-id'></div>"); | ||||
|                 $element.append(testElement); | ||||
|                 spyOn(testElement[0], 'click'); | ||||
|  | ||||
|                 controller.selectIfNew('some-id', childObj); | ||||
|                 jasmine.clock().tick(0); | ||||
|  | ||||
|                 expect(testElement[0].click).toHaveBeenCalled(); | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| ); | ||||
| @@ -1,64 +0,0 @@ | ||||
| /***************************************************************************** | ||||
|  * 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([ | ||||
|     './src/controllers/ListViewController', | ||||
|     './src/directives/MCTGesture', | ||||
|     './res/templates/listview.html', | ||||
|     'legacyRegistry' | ||||
| ], function ( | ||||
|     ListViewController, | ||||
|     MCTGesture, | ||||
|     listViewTemplate, | ||||
|     legacyRegistry | ||||
| ) { | ||||
|     legacyRegistry.register("platform/features/listview", { | ||||
|         "name": "List View Plugin", | ||||
|         "description": "Allows folder contents to be shown in list format", | ||||
|         "extensions": | ||||
|         { | ||||
|             "views": [ | ||||
|                 { | ||||
|                     "key": "list", | ||||
|                     "type": "folder", | ||||
|                     "name": "List", | ||||
|                     "cssClass": "icon-list-view", | ||||
|                     "template": listViewTemplate | ||||
|                 } | ||||
|             ], | ||||
|             "controllers": [ | ||||
|                 { | ||||
|                     "key": "ListViewController", | ||||
|                     "implementation": ListViewController, | ||||
|                     "depends": ["$scope", "formatService"] | ||||
|                 } | ||||
|             ], | ||||
|             "directives": [ | ||||
|                 { | ||||
|                     "key": "mctGesture", | ||||
|                     "implementation" : MCTGesture, | ||||
|                     "depends": ["gestureService"] | ||||
|                 } | ||||
|             ] | ||||
|         } | ||||
|     }); | ||||
| }); | ||||
| @@ -1,88 +0,0 @@ | ||||
| <!-- | ||||
|  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. | ||||
| --> | ||||
| <div ng-controller="ListViewController"> | ||||
|     <table class="list-view"> | ||||
|         <thead> | ||||
|         <tr> | ||||
|             <th class="sortable" | ||||
|                 ng-click="reverseSort = (orderByField === 'title')? !reverseSort:false; orderByField='title';" | ||||
|                 ng-class="{ | ||||
|                  sort: orderByField == 'title', | ||||
|                  asc: !reverseSort, | ||||
|                  desc: reverseSort | ||||
|                 }"> | ||||
|                 Name | ||||
|             </th> | ||||
|  | ||||
|  | ||||
|             <th class="sortable" | ||||
|                 ng-click="reverseSort = (orderByField === 'type')? !reverseSort:false; orderByField='type';" | ||||
|                 ng-class="{ | ||||
|                 sort: orderByField == 'type', | ||||
|                 asc: !reverseSort, | ||||
|                 desc: reverseSort | ||||
|                 }"> | ||||
|                 Type | ||||
|             </th> | ||||
|  | ||||
|             <th class="sortable" | ||||
|                 ng-click="reverseSort = (orderByField === 'persisted')? !reverseSort:true; orderByField='persisted';" | ||||
|                 ng-class="{ | ||||
|                 sort: orderByField == 'persisted', | ||||
|                 asc: !reverseSort, | ||||
|                 desc: reverseSort | ||||
|                 }"> | ||||
|                 Created Date | ||||
|             </th> | ||||
|  | ||||
|             <th class="sortable" | ||||
|                 ng-click="reverseSort = (orderByField === 'modified')? !reverseSort:true; orderByField='modified';" | ||||
|                 ng-class="{ | ||||
|                 sort: orderByField == 'modified', | ||||
|                 asc: !reverseSort, | ||||
|                 desc: reverseSort | ||||
|                 }"> | ||||
|                 Update Date | ||||
|             </th> | ||||
|  | ||||
|         </tr> | ||||
|         </thead> | ||||
|         <tbody> | ||||
|         <tr ng-repeat="child in children|orderBy:orderByField:reverseSort" | ||||
|             mct-gesture="['menu','info']" | ||||
|             mct-object="child.asDomainObject" | ||||
|             ng-click="child.action.perform('navigate')"> | ||||
|             <td> | ||||
|                 <div class="l-flex-row"> | ||||
|                     <span class="flex-elem t-item-icon" ng-class="{ 'l-icon-link': child.location.isLink()}"> | ||||
|                         <span class="t-item-icon-glyph {{child.icon}}"></span> | ||||
|                     </span> | ||||
|                     <span class="t-title-label flex-elem grows">{{child.title}}</span> | ||||
|                 </div> | ||||
|             </td> | ||||
|             <td>{{child.type}}</td> | ||||
|             <td>{{child.persisted}}</td> | ||||
|             <td>{{child.modified}}</td> | ||||
|         </tr> | ||||
|         </tbody> | ||||
|     </table> | ||||
| </div> | ||||
| @@ -1,70 +0,0 @@ | ||||
| /***************************************************************************** | ||||
|  * 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(function () { | ||||
|     function ListViewController($scope, formatService) { | ||||
|         this.$scope = $scope; | ||||
|         $scope.orderByField = 'title'; | ||||
|         $scope.reverseSort = false; | ||||
|  | ||||
|         this.updateView(); | ||||
|         var unlisten = $scope.domainObject.getCapability('mutation') | ||||
|             .listen(this.updateView.bind(this)); | ||||
|  | ||||
|         this.utc = formatService.getFormat('utc'); | ||||
|  | ||||
|         //Trigger digestive cycle with $apply to update list view | ||||
|         setTimeout(function () { | ||||
|             $scope.$apply(); | ||||
|         }); | ||||
|  | ||||
|         $scope.$on('$destroy', function () { | ||||
|             unlisten(); | ||||
|         }); | ||||
|  | ||||
|     } | ||||
|     ListViewController.prototype.updateView = function () { | ||||
|         this.$scope.domainObject.useCapability('composition') | ||||
|             .then(function (children) { | ||||
|                 var formattedChildren = this.formatChildren(children); | ||||
|                 this.$scope.children = formattedChildren; | ||||
|                 this.$scope.data = {children: formattedChildren}; | ||||
|             }.bind(this) | ||||
|             ); | ||||
|     }; | ||||
|     ListViewController.prototype.formatChildren = function (children) { | ||||
|         return children.map(function (child) { | ||||
|             return { | ||||
|                 icon: child.getCapability('type').getCssClass(), | ||||
|                 title: child.getModel().name, | ||||
|                 type: child.getCapability('type').getName(), | ||||
|                 persisted: this.utc.format(child.getModel().persisted), | ||||
|                 modified: this.utc.format(child.getModel().modified), | ||||
|                 asDomainObject: child, | ||||
|                 location: child.getCapability('location'), | ||||
|                 action: child.getCapability('action') | ||||
|             }; | ||||
|         }, this); | ||||
|     }; | ||||
|  | ||||
|     return ListViewController; | ||||
| }); | ||||
| @@ -1,157 +0,0 @@ | ||||
| /***************************************************************************** | ||||
|  * 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( | ||||
|     ["../../src/controllers/ListViewController"], | ||||
|     function (ListViewController) { | ||||
|         describe("The Controller for the ListView", function () { | ||||
|             var scope, | ||||
|                 unlistenFunc, | ||||
|                 domainObject, | ||||
|                 childObject, | ||||
|                 childModel, | ||||
|                 typeCapability, | ||||
|                 mutationCapability, | ||||
|                 formatService, | ||||
|                 compositionPromise, | ||||
|                 controller; | ||||
|  | ||||
|             beforeEach(function () { | ||||
|                 unlistenFunc = jasmine.createSpy("unlisten"); | ||||
|  | ||||
|                 mutationCapability = jasmine.createSpyObj( | ||||
|                     "mutationCapability", | ||||
|                     ["listen"] | ||||
|                 ); | ||||
|                 mutationCapability.listen.and.returnValue(unlistenFunc); | ||||
|  | ||||
|                 formatService = jasmine.createSpyObj( | ||||
|                     "formatService", | ||||
|                     ["getFormat"] | ||||
|                 ); | ||||
|                 formatService.getFormat.and.returnValue(jasmine.createSpyObj( | ||||
|                     'utc', | ||||
|                     ["format"] | ||||
|                 )); | ||||
|                 formatService.getFormat().format.and.callFake(function (v) { | ||||
|                     return "formatted " + v; | ||||
|                 }); | ||||
|  | ||||
|                 typeCapability = jasmine.createSpyObj( | ||||
|                     "typeCapability", | ||||
|                     ["getCssClass", "getName"] | ||||
|                 ); | ||||
|                 typeCapability.getCssClass.and.returnValue("icon-folder"); | ||||
|                 typeCapability.getName.and.returnValue("Folder"); | ||||
|  | ||||
|  | ||||
|                 childModel = jasmine.createSpyObj( | ||||
|                     "childModel", | ||||
|                     ["persisted", "modified", "name"] | ||||
|                 ); | ||||
|                 childModel.persisted = 1496867697303; | ||||
|                 childModel.modified = 1496867697303; | ||||
|                 childModel.name = "Battery Charge Status"; | ||||
|  | ||||
|                 childObject = jasmine.createSpyObj( | ||||
|                     "childObject", | ||||
|                     ["getModel", "getCapability"] | ||||
|                 ); | ||||
|                 childObject.getModel.and.returnValue( | ||||
|                     childModel | ||||
|                 ); | ||||
|  | ||||
|                 childObject.getCapability.and.callFake(function (arg) { | ||||
|                     if (arg === 'location') { | ||||
|                         return ''; | ||||
|                     } else if (arg === 'type') { | ||||
|                         return typeCapability; | ||||
|                     } | ||||
|                 }); | ||||
|                 childObject.location = ''; | ||||
|  | ||||
|                 domainObject = jasmine.createSpyObj( | ||||
|                     "domainObject", | ||||
|                     ["getCapability", "useCapability"] | ||||
|                 ); | ||||
|                 compositionPromise = Promise.resolve([childObject]); | ||||
|                 domainObject.useCapability.and.returnValue(compositionPromise); | ||||
|                 domainObject.getCapability.and.returnValue( | ||||
|                     mutationCapability | ||||
|                 ); | ||||
|  | ||||
|                 scope = jasmine.createSpyObj( | ||||
|                     "$scope", | ||||
|                     ["$on", "$apply"] | ||||
|                 ); | ||||
|                 scope.domainObject = domainObject; | ||||
|  | ||||
|                 controller = new ListViewController(scope, formatService); | ||||
|  | ||||
|                 return compositionPromise; | ||||
|             }); | ||||
|  | ||||
|             it("uses the UTC time format", function () { | ||||
|                 expect(formatService.getFormat).toHaveBeenCalledWith('utc'); | ||||
|             }); | ||||
|  | ||||
|             it("updates the view", function () { | ||||
|                 var child = scope.children[0]; | ||||
|                 var testChild = { | ||||
|                     icon: "icon-folder", | ||||
|                     title: "Battery Charge Status", | ||||
|                     type: "Folder", | ||||
|                     persisted: formatService.getFormat('utc') | ||||
|                         .format(childModel.persisted), | ||||
|                     modified: formatService.getFormat('utc') | ||||
|                         .format(childModel.modified), | ||||
|                     asDomainObject: childObject, | ||||
|                     location: '', | ||||
|                     action: childObject.getCapability('action') | ||||
|                 }; | ||||
|  | ||||
|                 expect(child).toEqual(testChild); | ||||
|             }); | ||||
|             it("updates the scope when mutation occurs", function () { | ||||
|                 var applyPromise = new Promise(function (resolve) { | ||||
|                     scope.$apply.and.callFake(resolve); | ||||
|                 }); | ||||
|  | ||||
|                 domainObject.useCapability.and.returnValue(Promise.resolve([])); | ||||
|                 expect(mutationCapability.listen).toHaveBeenCalledWith(jasmine.any(Function)); | ||||
|                 mutationCapability.listen.calls.mostRecent().args[0](); | ||||
|  | ||||
|                 return applyPromise.then(function () { | ||||
|                     expect(scope.children.length).toEqual(0); | ||||
|                     expect(scope.$apply).toHaveBeenCalled(); | ||||
|                 }); | ||||
|             }); | ||||
|             it("releases listeners on $destroy", function () { | ||||
|                 expect(scope.$on).toHaveBeenCalledWith('$destroy', jasmine.any(Function)); | ||||
|                 scope.$on.calls.mostRecent().args[1](); | ||||
|                 expect(unlistenFunc).toHaveBeenCalled(); | ||||
|             }); | ||||
|  | ||||
|  | ||||
|         }); | ||||
|     } | ||||
| ); | ||||
| @@ -1,86 +0,0 @@ | ||||
| /***************************************************************************** | ||||
|  * 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( | ||||
|     ["../../src/directives/MCTGesture"], | ||||
|     function (MCTGesture) { | ||||
|         describe("The Gesture Listener for the ListView items", function () { | ||||
|             var mctGesture, | ||||
|                 gestureService, | ||||
|                 scope, | ||||
|                 element, | ||||
|                 attrs, | ||||
|                 attachedGesture; | ||||
|             beforeEach(function () { | ||||
|                 attachedGesture = jasmine.createSpyObj( | ||||
|                     "attachedGesture", | ||||
|                     ['destroy'] | ||||
|                 ); | ||||
|                 gestureService = jasmine.createSpyObj( | ||||
|                     "gestureService", | ||||
|                     ["attachGestures"] | ||||
|                 ); | ||||
|                 gestureService.attachGestures.and.returnValue( | ||||
|                     attachedGesture | ||||
|                 ); | ||||
|                 mctGesture = MCTGesture(gestureService); | ||||
|             }); | ||||
|             it("creates a directive Object", function () { | ||||
|                 expect(mctGesture).toBeDefined(); | ||||
|             }); | ||||
|             it("has link function that attaches gesture to gestureService", | ||||
|                 function () { | ||||
|                     attrs = { | ||||
|                         mctGesture: "menu,info" | ||||
|                     }; | ||||
|                     element = jasmine.createSpy("element"); | ||||
|                     scope = jasmine.createSpyObj( | ||||
|                         "$scope", | ||||
|                         ["$on", "$eval"] | ||||
|                     ); | ||||
|                     scope.domainObject = "fake domainObject"; | ||||
|                     mctGesture.link(scope, element, attrs); | ||||
|                     expect(gestureService.attachGestures).toHaveBeenCalled(); | ||||
|                 } | ||||
|             ); | ||||
|             it("release gesture service on $destroy", function () { | ||||
|                 attrs = { | ||||
|                     mctGesture: "menu,info" | ||||
|                 }; | ||||
|                 element = jasmine.createSpy("element"); | ||||
|                 scope = jasmine.createSpyObj( | ||||
|                     "$scope", | ||||
|                     ["$on", "$eval"] | ||||
|                 ); | ||||
|                 scope.domainObject = "fake domainObject"; | ||||
|                 mctGesture.link(scope, element, attrs); | ||||
|                 expect(scope.$on).toHaveBeenCalledWith( | ||||
|                     '$destroy', | ||||
|                     jasmine.any(Function) | ||||
|                 ); | ||||
|                 scope.$on.calls.mostRecent().args[1](); | ||||
|                 expect(attachedGesture.destroy).toHaveBeenCalled(); | ||||
|             }); | ||||
|  | ||||
|         }); | ||||
|     } | ||||
| ); | ||||
							
								
								
									
										16
									
								
								src/MCT.js
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								src/MCT.js
									
									
									
									
									
								
							| @@ -40,6 +40,7 @@ define([ | ||||
|     './styles-new/core.scss', | ||||
|     './styles-new/notebook.scss', | ||||
|     './ui/components/layout/Layout.vue', | ||||
|     './ui/overlayService/overlayService', | ||||
|     'vue' | ||||
| ], function ( | ||||
|     EventEmitter, | ||||
| @@ -61,6 +62,7 @@ define([ | ||||
|     coreStyles, | ||||
|     NotebookStyles, | ||||
|     Layout, | ||||
|     OverlayService, | ||||
|     Vue | ||||
| ) { | ||||
|     /** | ||||
| @@ -223,9 +225,14 @@ define([ | ||||
|  | ||||
|         this.Dialog = api.Dialog; | ||||
|  | ||||
|         this.editor = new api.EditorAPI.default(this); | ||||
|  | ||||
|         this.OverlayService = new OverlayService(); | ||||
|  | ||||
|         this.legacyRegistry = defaultRegistry; | ||||
|         this.install(this.plugins.Plot()); | ||||
|         this.install(this.plugins.TelemetryTable()); | ||||
|         this.install(this.plugins.DisplayLayout()); | ||||
|  | ||||
|         if (typeof BUILD_CONSTANTS !== 'undefined') { | ||||
|             this.install(buildInfoPlugin(BUILD_CONSTANTS)); | ||||
| @@ -310,14 +317,17 @@ define([ | ||||
|                 this.$injector.get('objectService'); | ||||
|  | ||||
|                 var appLayout = new Vue({ | ||||
|                     mixins: [Layout.default], | ||||
|                     components: { | ||||
|                         'Layout': Layout.default | ||||
|                     }, | ||||
|                     provide: { | ||||
|                         openmct: this | ||||
|                     } | ||||
|                     }, | ||||
|                     template: '<Layout ref="layout"></Layout>' | ||||
|                 }); | ||||
|                 domElement.appendChild(appLayout.$mount().$el); | ||||
|  | ||||
|                 this.layout = appLayout; | ||||
|                 this.layout = appLayout.$refs.layout; | ||||
|                 Browse(this); | ||||
|                 this.router.start(); | ||||
|                 this.emit('start'); | ||||
|   | ||||
| @@ -3,6 +3,16 @@ define([ | ||||
| ], function ( | ||||
|  | ||||
| ) { | ||||
|     const DEFAULT_VIEW_PRIORITY = 100; | ||||
|  | ||||
|     const PRIORITY_LEVELS = { | ||||
|         "fallback": Number.NEGATIVE_INFINITY, | ||||
|         "default": -100, | ||||
|         "none": 0, | ||||
|         "optional": DEFAULT_VIEW_PRIORITY, | ||||
|         "preferred": 1000, | ||||
|         "mandatory": Number.POSITIVE_INFINITY | ||||
|     }; | ||||
|  | ||||
|     function LegacyViewProvider(legacyView, openmct, convertToLegacyObject) { | ||||
|         console.warn(`DEPRECATION WARNING: Migrate ${legacyView.key} from ${legacyView.bundle.path} to use the new View APIs.  Legacy view support will be removed soon.`); | ||||
| @@ -84,6 +94,13 @@ define([ | ||||
|                         scope.$destroy(); | ||||
|                     } | ||||
|                 } | ||||
|             }, | ||||
|             priority: function () { | ||||
|                 let priority = legacyView.priority || DEFAULT_VIEW_PRIORITY; | ||||
|                 if (typeof priority === 'string') { | ||||
|                     priority = PRIORITY_LEVELS[priority]; | ||||
|                 } | ||||
|                 return priority; | ||||
|             } | ||||
|         }; | ||||
|     }; | ||||
|   | ||||
							
								
								
									
										95
									
								
								src/adapter/views/TypeInspectorViewProvider.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								src/adapter/views/TypeInspectorViewProvider.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,95 @@ | ||||
| define([ | ||||
|  | ||||
| ], function ( | ||||
|  | ||||
| ) { | ||||
|     const DEFAULT_VIEW_PRIORITY = 100; | ||||
|  | ||||
|     const PRIORITY_LEVELS = { | ||||
|         "fallback": Number.NEGATIVE_INFINITY, | ||||
|         "default": -100, | ||||
|         "none": 0, | ||||
|         "optional": DEFAULT_VIEW_PRIORITY, | ||||
|         "preferred": 1000, | ||||
|         "mandatory": Number.POSITIVE_INFINITY | ||||
|     }; | ||||
|  | ||||
|     function TypeInspectorViewProvider(typeDefinition, openmct, convertToLegacyObject) { | ||||
|         console.warn(`DEPRECATION WARNING: Migrate ${typeDefinition.key} from ${typeDefinition.bundle.path} to use the new Inspector View APIs.  Legacy Inspector view support will be removed soon.`); | ||||
|         let representation = openmct.$injector.get('representations[]') | ||||
|             .filter((r) => r.key === typeDefinition.inspector)[0]; | ||||
|  | ||||
|         return { | ||||
|             key: representation.key, | ||||
|             name: representation.name, | ||||
|             cssClass: representation.cssClass, | ||||
|             description: representation.description, | ||||
|             canView: function (selection) { | ||||
|                 if (!selection[0] || !selection[0].context.item) { | ||||
|                     return false; | ||||
|                 } | ||||
|                 let domainObject = selection[0].context.item; | ||||
|                 return domainObject.type === typeDefinition.key; | ||||
|             }, | ||||
|             view: function (selection) { | ||||
|                 let domainObject = selection[0].context.item; | ||||
|                 let $rootScope = openmct.$injector.get('$rootScope'); | ||||
|                 let templateLinker = openmct.$injector.get('templateLinker'); | ||||
|                 let scope = $rootScope.$new(); | ||||
|                 let legacyObject = convertToLegacyObject(domainObject); | ||||
|                 let isDestroyed = false; | ||||
|                 scope.domainObject = legacyObject; | ||||
|                 scope.model = legacyObject.getModel(); | ||||
|  | ||||
|  | ||||
|                 return { | ||||
|                     show: function (container) { | ||||
|                         // TODO: implement "gestures" support ? | ||||
|                         let uses = representation.uses || []; | ||||
|                         let promises = []; | ||||
|                         let results = uses.map(function (capabilityKey, i) { | ||||
|                             let result = legacyObject.useCapability(capabilityKey); | ||||
|                             if (result.then) { | ||||
|                                 promises.push(result.then(function (r) { | ||||
|                                     results[i] = r; | ||||
|                                 })); | ||||
|                             } | ||||
|                             return result; | ||||
|                         }); | ||||
|  | ||||
|                         function link() { | ||||
|                             if (isDestroyed) { | ||||
|                                 return; | ||||
|                             } | ||||
|                             uses.forEach(function (key, i) { | ||||
|                                 scope[key] = results[i]; | ||||
|                             }); | ||||
|                             templateLinker.link( | ||||
|                                 scope, | ||||
|                                 openmct.$angular.element(container), | ||||
|                                 representation | ||||
|                             ); | ||||
|                             container.style.height = '100%'; | ||||
|                         } | ||||
|  | ||||
|                         if (promises.length) { | ||||
|                             Promise.all(promises) | ||||
|                                 .then(function () { | ||||
|                                     link(); | ||||
|                                     scope.$digest(); | ||||
|                                 }); | ||||
|                         } else { | ||||
|                             link(); | ||||
|                         } | ||||
|                     }, | ||||
|                     destroy: function () { | ||||
|                         scope.$destroy(); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         }; | ||||
|     }; | ||||
|  | ||||
|     return TypeInspectorViewProvider; | ||||
|  | ||||
| }); | ||||
| @@ -1,8 +1,10 @@ | ||||
| define([ | ||||
|     './LegacyViewProvider', | ||||
|     './TypeInspectorViewProvider', | ||||
|     '../../api/objects/object-utils' | ||||
| ], function ( | ||||
|     LegacyViewProvider, | ||||
|     TypeInspectorViewProvider, | ||||
|     objectUtils | ||||
| ) { | ||||
|     function installLegacyViews(openmct, legacyViews, instantiate) { | ||||
| @@ -16,6 +18,13 @@ define([ | ||||
|         legacyViews.forEach(function (legacyView) { | ||||
|             openmct.objectViews.addProvider(new LegacyViewProvider(legacyView, openmct, convertToLegacyObject)); | ||||
|         }); | ||||
|  | ||||
|         let inspectorTypes = openmct.$injector.get('types[]') | ||||
|             .filter((t) => t.hasOwnProperty('inspector')); | ||||
|  | ||||
|         inspectorTypes.forEach(function (typeDefinition) { | ||||
|             openmct.inspectorViews.addProvider(new TypeInspectorViewProvider(typeDefinition, openmct, convertToLegacyObject)); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     return installLegacyViews; | ||||
|   | ||||
							
								
								
									
										83
									
								
								src/api/Editor.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								src/api/Editor.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,83 @@ | ||||
|  | ||||
| /***************************************************************************** | ||||
|  * 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 EventEmitter from 'EventEmitter'; | ||||
|  | ||||
| export default class Editor extends EventEmitter { | ||||
|     constructor(openmct) { | ||||
|         super(); | ||||
|         this.editing = false; | ||||
|         this.openmct = openmct; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Initiate an editing session. This will start a transaction during | ||||
|      * which any persist operations will be deferred until either save() | ||||
|      * or finish() are called. | ||||
|      */ | ||||
|     edit() { | ||||
|         this.editing = true; | ||||
|         this.getTransactionService().startTransaction(); | ||||
|         this.emit('isEditing', true); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @returns true if the application is in edit mode, false otherwise. | ||||
|      */ | ||||
|     isEditing() { | ||||
|         return this.editing; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Save any unsaved changes from this editing session. This will | ||||
|      * end the current transaction. | ||||
|      */ | ||||
|     save() { | ||||
|         return this.getTransactionService().commit().then((result)=>{ | ||||
|             this.editing = false; | ||||
|             this.emit('isEditing', false); | ||||
|             return result | ||||
|         }).catch((error)=>{ | ||||
|             throw error; | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * End the currently active transaction and discard unsaved changes. | ||||
|      */ | ||||
|     cancel() { | ||||
|         this.getTransactionService().cancel(); | ||||
|         this.editing = false; | ||||
|         this.emit('isEditing', false); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @private | ||||
|      */ | ||||
|     getTransactionService() { | ||||
|         if (!this.transactionService) { | ||||
|             this.transactionService = this.openmct.$injector.get('transactionService'); | ||||
|         } | ||||
|         return this.transactionService; | ||||
|     } | ||||
| } | ||||
| @@ -28,7 +28,9 @@ define([ | ||||
|     './ui/Dialog', | ||||
|     './ui/GestureAPI', | ||||
|     './telemetry/TelemetryAPI', | ||||
|     './indicators/IndicatorAPI' | ||||
|     './indicators/IndicatorAPI', | ||||
|     './Editor' | ||||
|  | ||||
| ], function ( | ||||
|     TimeAPI, | ||||
|     ObjectAPI, | ||||
| @@ -37,7 +39,8 @@ define([ | ||||
|     Dialog, | ||||
|     GestureAPI, | ||||
|     TelemetryAPI, | ||||
|     IndicatorAPI | ||||
|     IndicatorAPI, | ||||
|     EditorAPI | ||||
| ) { | ||||
|     return { | ||||
|         TimeAPI: TimeAPI, | ||||
| @@ -47,6 +50,7 @@ define([ | ||||
|         TypeRegistry: TypeRegistry, | ||||
|         GestureAPI: GestureAPI, | ||||
|         TelemetryAPI: TelemetryAPI, | ||||
|         IndicatorAPI: IndicatorAPI | ||||
|         IndicatorAPI: IndicatorAPI, | ||||
|         EditorAPI: EditorAPI | ||||
|     }; | ||||
| }); | ||||
|   | ||||
| @@ -55,11 +55,7 @@ define([ | ||||
|     '../platform/exporters/bundle', | ||||
|     '../platform/features/clock/bundle', | ||||
|     '../platform/features/fixed/bundle', | ||||
|     '../platform/features/conductor/core/bundle', | ||||
|     '../platform/features/conductor/compatibility/bundle', | ||||
|     '../platform/features/imagery/bundle', | ||||
|     '../platform/features/layout/bundle', | ||||
|     '../platform/features/listview/bundle', | ||||
|     '../platform/features/my-items/bundle', | ||||
|     '../platform/features/pages/bundle', | ||||
|     '../platform/features/hyperlink/bundle', | ||||
| @@ -102,8 +98,6 @@ define([ | ||||
|         'platform/features/clock', | ||||
|         'platform/features/fixed', | ||||
|         'platform/features/imagery', | ||||
|         'platform/features/layout', | ||||
|         'platform/features/listview', | ||||
|         'platform/features/pages', | ||||
|         'platform/features/hyperlink', | ||||
|         'platform/features/timeline', | ||||
|   | ||||
							
								
								
									
										331
									
								
								src/plugins/displayLayout/DisplayLayout.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										331
									
								
								src/plugins/displayLayout/DisplayLayout.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,331 @@ | ||||
| /***************************************************************************** | ||||
|  * 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="l-layout" | ||||
|          @dragover="handleDragOver" | ||||
|          @click="bypassSelection" | ||||
|          @drop="handleDrop"> | ||||
|         <div class="l-layout__object"> | ||||
|             <!-- Background grid --> | ||||
|             <div class="l-layout__grid-holder c-grid" | ||||
|                  v-if="!drilledIn"> | ||||
|                 <div class="c-grid__x l-grid l-grid-x" | ||||
|                      v-if="gridSize[0] >= 3" | ||||
|                      :style="[{ backgroundSize: gridSize[0] + 'px 100%' }]"> | ||||
|                 </div> | ||||
|                 <div class="c-grid__y l-grid l-grid-y" | ||||
|                      v-if="gridSize[1] >= 3" | ||||
|                      :style="[{ backgroundSize: '100%' + gridSize[1] + 'px' }]"></div> | ||||
|             </div> | ||||
|             <layout-frame v-for="item in frameItems" | ||||
|                           class="l-layout__frame" | ||||
|                           :key="item.id" | ||||
|                           :item="item" | ||||
|                           :gridSize="gridSize" | ||||
|                           @drilledIn="updateDrilledInState" | ||||
|                           @dragInProgress="updatePosition" | ||||
|                           @endDrag="endDrag"> | ||||
|             </layout-frame> | ||||
|         </div> | ||||
|     </div> | ||||
| </template> | ||||
|  | ||||
| <style lang="scss"> | ||||
|     @import "~styles/sass-base"; | ||||
|  | ||||
|     .l-layout, | ||||
|     .c-grid, | ||||
|     .c-grid__x, | ||||
|     .c-grid__y { | ||||
|         @include abs(); | ||||
|     } | ||||
|  | ||||
|     .l-layout { | ||||
|         display: flex; | ||||
|         flex-direction: column; | ||||
|  | ||||
|         &__grid-holder { | ||||
|             display: none; | ||||
|         } | ||||
|  | ||||
|         &__object { | ||||
|             flex: 1 1 auto; | ||||
|             overflow: auto; | ||||
|         } | ||||
|  | ||||
|         &__frame { | ||||
|             position: absolute; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     .c-grid { | ||||
|         z-index: -1; | ||||
|         pointer-events: none; | ||||
|  | ||||
|         &__x  { @include bgTicks($colorGridLines, 'x'); } | ||||
|         &__y  { @include bgTicks($colorGridLines, 'y'); } | ||||
|     } | ||||
|  | ||||
|     .is-editing { | ||||
|         .l-shell__main-container > .l-layout { | ||||
|             // Target the top-most layout container and color its background | ||||
|             background: rgba($editColor, 0.1); | ||||
|         } | ||||
|  | ||||
|         .c-frame, | ||||
|         .l-layout { | ||||
|             &.s-selected, | ||||
|             &.s-selected-parent { | ||||
|                 [class*="__grid-holder"] { | ||||
|                     display: block; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| </style> | ||||
|  | ||||
|  | ||||
| <script> | ||||
|     import LayoutFrame from './LayoutFrame.vue'; | ||||
|  | ||||
|     const DEFAULT_GRID_SIZE = [32, 32], | ||||
|           DEFAULT_DIMENSIONS = [12, 8], | ||||
|           DEFAULT_POSITION = [0, 0], | ||||
|           MINIMUM_FRAME_SIZE = [320, 180], | ||||
|           DEFAULT_HIDDEN_FRAME_TYPES = [ | ||||
|             'hyperlink' | ||||
|           ]; | ||||
|  | ||||
|     export default { | ||||
|         data() { | ||||
|             return { | ||||
|                 gridSize: [], | ||||
|                 frameItems: [], | ||||
|                 frames: [], | ||||
|                 frameStyles: [], | ||||
|                 rawPositions: {}, | ||||
|                 initSelect: true, | ||||
|                 drilledIn: undefined | ||||
|             } | ||||
|         },           | ||||
|         inject: ['openmct'], | ||||
|         props: ['domainObject'], | ||||
|         components: { | ||||
|             LayoutFrame | ||||
|         }, | ||||
|         created: function () { | ||||
|             this.newDomainObject = this.domainObject; | ||||
|             this.gridSize = this.newDomainObject.layoutGrid ||  DEFAULT_GRID_SIZE; | ||||
|             this.composition = this.openmct.composition.get(this.newDomainObject); | ||||
|             let panels = (((this.newDomainObject.configuration || {}).layout || {}).panels || {}); | ||||
|  | ||||
|             if (this.composition !== undefined) { | ||||
|                 this.composition.load().then((composition) => { | ||||
|                     composition.forEach(function (domainObject) { | ||||
|                         this.readLayoutConfiguration(domainObject, panels); | ||||
|                         this.makeFrameItem(domainObject, false); | ||||
|                     }.bind(this)); | ||||
|                     this.composition.on('add', this.onAddComposition); | ||||
|                     this.composition.on('remove', this.onRemoveComposition); | ||||
|                 }); | ||||
|             } | ||||
|  | ||||
|             this.unlisten = this.openmct.objects.observe(this.newDomainObject, '*', function (obj) { | ||||
|                 this.newDomainObject = JSON.parse(JSON.stringify(obj)); | ||||
|                 this.gridSize = this.newDomainObject.layoutGrid || DEFAULT_GRID_SIZE;; | ||||
|             }.bind(this)); | ||||
|         }, | ||||
|         methods: { | ||||
|             readLayoutConfiguration(domainObject, panels) { | ||||
|                 let id = this.openmct.objects.makeKeyString(domainObject.identifier); | ||||
|                 this.rawPositions[id] = { | ||||
|                     position: panels[id].position || DEFAULT_POSITION, | ||||
|                     dimensions: panels[id].dimensions || this.defaultDimensions() | ||||
|                 }; | ||||
|                 this.frameStyles[id] = this.convertPosition(this.rawPositions[id]); | ||||
|                 this.frames[id] = panels[id].hasOwnProperty('hasFrame') ?  | ||||
|                                 panels[id].hasFrame : | ||||
|                                 this.hasFrameByDefault(domainObject.type); | ||||
|             }, | ||||
|             makeFrameItem(domainObject, initSelect) { | ||||
|                 let id = this.openmct.objects.makeKeyString(domainObject.identifier); | ||||
|                 this.frameItems.push({ | ||||
|                     id: id, | ||||
|                     hasFrame: this.frames[id], | ||||
|                     domainObject, | ||||
|                     style: this.frameStyles[id], | ||||
|                     drilledIn: this.isDrilledIn(id), | ||||
|                     initSelect: initSelect, | ||||
|                     rawPosition: this.rawPositions[id] | ||||
|                 }); | ||||
|             }, | ||||
|             onAddComposition(domainObject) { | ||||
|                 let id = this.openmct.objects.makeKeyString(domainObject.identifier); | ||||
|                 this.rawPositions[id] = { | ||||
|                     position: [ | ||||
|                         Math.floor(this.droppedObjectPosition.x / this.gridSize[0]), | ||||
|                         Math.floor(this.droppedObjectPosition.y / this.gridSize[1]) | ||||
|                     ], | ||||
|                     dimensions: this.defaultDimensions() | ||||
|                 }; | ||||
|                 this.frameStyles[id] = this.convertPosition(this.rawPositions[id]); | ||||
|                 this.frames[id] = this.hasFrameByDefault(domainObject.type); | ||||
|  | ||||
|                 let newPanel = this.rawPositions[id]; | ||||
|                 newPanel.hasFrame = this.frames[id]; | ||||
|                 this.mutate("configuration.layout.panels[" + id + "]", newPanel); | ||||
|                 this.makeFrameItem(domainObject, true); | ||||
|             }, | ||||
|             onRemoveComposition(identifier) { | ||||
|                 // TODO: remove the object from frameItems | ||||
|             }, | ||||
|             defaultDimensions() { | ||||
|                 let gridSize = this.gridSize; | ||||
|                 return MINIMUM_FRAME_SIZE.map(function (min, i) { | ||||
|                     return Math.max( | ||||
|                         Math.ceil(min / gridSize[i]), | ||||
|                         DEFAULT_DIMENSIONS[i] | ||||
|                     ); | ||||
|                 }); | ||||
|             }, | ||||
|             convertPosition(raw) { | ||||
|                 return { | ||||
|                     left: (this.gridSize[0] * raw.position[0]) + 'px', | ||||
|                     top: (this.gridSize[1] * raw.position[1]) + 'px', | ||||
|                     width: (this.gridSize[0] * raw.dimensions[0]) + 'px', | ||||
|                     height: (this.gridSize[1] * raw.dimensions[1]) + 'px', | ||||
|                     minWidth: (this.gridSize[0] * raw.dimensions[0]) + 'px', | ||||
|                     minHeight: (this.gridSize[1] * raw.dimensions[1]) + 'px' | ||||
|                 }; | ||||
|             }, | ||||
|             /** | ||||
|              * Checks if the frame should be hidden or not. | ||||
|              * | ||||
|              * @param type the domain object type | ||||
|              * @return {boolean} true if the object should have | ||||
|              *         frame by default, false, otherwise | ||||
|              */ | ||||
|             hasFrameByDefault(type) { | ||||
|                 return DEFAULT_HIDDEN_FRAME_TYPES.indexOf(type) === -1; | ||||
|             }, | ||||
|             setSelection(selection) { | ||||
|                 if (selection.length === 0) { | ||||
|                     return; | ||||
|                 } | ||||
|  | ||||
|                 this.updateDrilledInState(); | ||||
|             }, | ||||
|             updateDrilledInState(id) { | ||||
|                 this.drilledIn = id; | ||||
|                 this.frameItems.forEach(function (item) { | ||||
|                     item.drilledIn = item.id === id; | ||||
|                 }); | ||||
|             }, | ||||
|             isDrilledIn(id) { | ||||
|                 return this.drilledIn === id; | ||||
|             }, | ||||
|             updatePosition(id, newPosition) { | ||||
|                 let newStyle = this.convertPosition(newPosition); | ||||
|                 this.frameStyles[id] = newStyle; | ||||
|                 this.rawPositions[id] = newPosition; | ||||
|                 this.frameItems.forEach(function (item) { | ||||
|                     if (item.id === id) { | ||||
|                         item.style = newStyle; | ||||
|                         item.rawPosition = newPosition; | ||||
|                     } | ||||
|                 }); | ||||
|             }, | ||||
|             bypassSelection($event) { | ||||
|                 if (this.dragInProgress) { | ||||
|                     if ($event) { | ||||
|                         $event.stopImmediatePropagation(); | ||||
|                     } | ||||
|                     return; | ||||
|                 } | ||||
|             }, | ||||
|             endDrag(id) { | ||||
|                 this.dragInProgress = true; | ||||
|                 setTimeout(function () { | ||||
|                     this.dragInProgress = false; | ||||
|                 }.bind(this), 0); | ||||
|  | ||||
|                 let path = "configuration.layout.panels[" + id + "]"; | ||||
|                 this.mutate(path + ".dimensions", this.rawPositions[id].dimensions); | ||||
|                 this.mutate(path + ".position", this.rawPositions[id].position); | ||||
|             }, | ||||
|             mutate(path, value) { | ||||
|                 this.openmct.objects.mutate(this.newDomainObject, path, value); | ||||
|             }, | ||||
|             handleDrop($event) { | ||||
|                 $event.preventDefault(); | ||||
|  | ||||
|                 let child = JSON.parse($event.dataTransfer.getData('domainObject')); | ||||
|                 let duplicates = []; | ||||
|                 let composition = this.newDomainObject.composition; | ||||
|                 composition.forEach((object) => { | ||||
|                     if (this.openmct.objects.makeKeyString(JSON.parse(JSON.stringify(object))) === | ||||
|                         this.openmct.objects.makeKeyString(child.identifier)) { | ||||
|                         duplicates.push(object); | ||||
|                     } | ||||
|                 }); | ||||
|  | ||||
|                 // Disallow adding a duplicate object to the composition | ||||
|                 if (duplicates.length !== 0) { | ||||
|                     return; | ||||
|                 } | ||||
|  | ||||
|                 let elementRect = this.$el.getBoundingClientRect(); | ||||
|                 this.droppedObjectPosition = { | ||||
|                     x: $event.pageX - elementRect.left, | ||||
|                     y: $event.pageY - elementRect.top | ||||
|                 } | ||||
|                 // TODO: use the composition API to add child once the default composition | ||||
|                 // provider supports it instead of mutating the composition directly. | ||||
|                 // this.composition.add(child). | ||||
|                 composition.push(child.identifier); | ||||
|                 this.mutate('composition', composition); | ||||
|             }, | ||||
|             handleDragOver($event){ | ||||
|                 $event.preventDefault(); | ||||
|             } | ||||
|         }, | ||||
|         mounted() { | ||||
|             this.removeSelectable = this.openmct.selection.selectable( | ||||
|                 this.$el, | ||||
|                 { | ||||
|                     item: this.newDomainObject | ||||
|                 }, | ||||
|                 this.initSelect | ||||
|             ); | ||||
|             this.openmct.selection.on('change', this.setSelection); | ||||
|         }, | ||||
|         destroyed: function () { | ||||
|             this.composition.off('add', this.onAddComposition); | ||||
|             this.composition.off('remove', this.onRemoveComposition); | ||||
|             this.openmct.off('change', this.selection); | ||||
|             this.removeSelectable(); | ||||
|             this.unlisten(); | ||||
|         } | ||||
|     } | ||||
|      | ||||
| </script> | ||||
| @@ -21,24 +21,21 @@ | ||||
|  *****************************************************************************/ | ||||
| 
 | ||||
| define(function () { | ||||
|     function MCTGesture(gestureService) { | ||||
|     function DisplayLayoutType() { | ||||
|         return { | ||||
|             restrict : 'A', | ||||
|             scope: { | ||||
|                 domainObject: '=mctObject' | ||||
|             }, | ||||
|             link : function ($scope, $element, attrs) { | ||||
|                 var activeGestures = gestureService.attachGestures( | ||||
|                     $element, | ||||
|                     $scope.domainObject, | ||||
|                     $scope.$eval(attrs.mctGesture) | ||||
|                 ); | ||||
|                 $scope.$on('$destroy', function () { | ||||
|                     activeGestures.destroy(); | ||||
|                     delete this.activeGestures; | ||||
|                 }); | ||||
|             name: "Display Layout", | ||||
|             creatable: true, | ||||
|             cssClass: 'icon-layout', | ||||
|             initialize(domainObject) { | ||||
|                 domainObject.composition = []; | ||||
|                 domainObject.configuration = { | ||||
|                     layout: { | ||||
|                         panels: {} | ||||
|                     } | ||||
|                 }; | ||||
|             } | ||||
|         }; | ||||
|         } | ||||
|     } | ||||
|     return MCTGesture; | ||||
| }); | ||||
| 
 | ||||
|     return DisplayLayoutType; | ||||
| }); | ||||
							
								
								
									
										115
									
								
								src/plugins/displayLayout/LayoutDrag.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								src/plugins/displayLayout/LayoutDrag.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,115 @@ | ||||
| /***************************************************************************** | ||||
|  * 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( | ||||
|     [], | ||||
|     function () { | ||||
|  | ||||
|         /** | ||||
|          * Handles drag interactions on frames in layouts. This will | ||||
|          * provides new positions/dimensions for frames based on | ||||
|          * relative pixel positions provided; these will take into account | ||||
|          * the grid size (in a snap-to sense) and will enforce some minimums | ||||
|          * on both position and dimensions. | ||||
|          * | ||||
|          * The provided position and dimensions factors will determine | ||||
|          * whether this is a move or a resize, and what type of resize it | ||||
|          * will be. For instance, a position factor of [1, 1] | ||||
|          * will move a frame along with the mouse as the drag | ||||
|          * proceeds, while a dimension factor of [0, 0] will leave | ||||
|          * dimensions unchanged. Combining these in different | ||||
|          * ways results in different handles; a position factor of | ||||
|          * [1, 0] and a dimensions factor of [-1, 0] will implement | ||||
|          * a left-edge resize, as the horizontal position will move | ||||
|          * with the mouse while the horizontal dimensions shrink in | ||||
|          * kind (and vertical properties remain unmodified.) | ||||
|          * | ||||
|          * @param {object} rawPosition the initial position/dimensions | ||||
|          *                 of the frame being interacted with | ||||
|          * @param {number[]} posFactor the position factor | ||||
|          * @param {number[]} dimFactor the dimensions factor | ||||
|          * @param {number[]} the size of each grid element, in pixels | ||||
|          * @constructor | ||||
|          * @memberof platform/features/layout | ||||
|          */ | ||||
|         function LayoutDrag(rawPosition, posFactor, dimFactor, gridSize) { | ||||
|             this.rawPosition = rawPosition; | ||||
|             this.posFactor = posFactor; | ||||
|             this.dimFactor = dimFactor; | ||||
|             this.gridSize = gridSize; | ||||
|         } | ||||
|  | ||||
|         // Convert a delta from pixel coordinates to grid coordinates, | ||||
|         // rounding to whole-number grid coordinates. | ||||
|         function toGridDelta(gridSize, pixelDelta) { | ||||
|             return pixelDelta.map(function (v, i) { | ||||
|                 return Math.round(v / gridSize[i]); | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         // Utility function to perform element-by-element multiplication | ||||
|         function multiply(array, factors) { | ||||
|             return array.map(function (v, i) { | ||||
|                 return v * factors[i]; | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         // Utility function to perform element-by-element addition | ||||
|         function add(array, other) { | ||||
|             return array.map(function (v, i) { | ||||
|                 return v + other[i]; | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         // Utility function to perform element-by-element max-choosing | ||||
|         function max(array, other) { | ||||
|             return array.map(function (v, i) { | ||||
|                 return Math.max(v, other[i]); | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|  | ||||
|         /** | ||||
|          * Get a new position object in grid coordinates, with | ||||
|          * position and dimensions both offset appropriately | ||||
|          * according to the factors supplied in the constructor. | ||||
|          * @param {number[]} pixelDelta the offset from the | ||||
|          *        original position, in pixels | ||||
|          */ | ||||
|         LayoutDrag.prototype.getAdjustedPosition = function (pixelDelta) { | ||||
|             var gridDelta = toGridDelta(this.gridSize, pixelDelta); | ||||
|             return { | ||||
|                 position: max(add( | ||||
|                     this.rawPosition.position, | ||||
|                     multiply(gridDelta, this.posFactor) | ||||
|                 ), [0, 0]), | ||||
|                 dimensions: max(add( | ||||
|                     this.rawPosition.dimensions, | ||||
|                     multiply(gridDelta, this.dimFactor) | ||||
|                 ), [1, 1]) | ||||
|             }; | ||||
|         }; | ||||
|  | ||||
|         return LayoutDrag; | ||||
|  | ||||
|     } | ||||
| ); | ||||
							
								
								
									
										338
									
								
								src/plugins/displayLayout/LayoutFrame.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										338
									
								
								src/plugins/displayLayout/LayoutFrame.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,338 @@ | ||||
| /***************************************************************************** | ||||
|  * 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-frame has-local-controls is-selectable is-moveable" | ||||
|          :style="item.style" | ||||
|          :class="classObject" | ||||
|          @dblclick="drill(item.id, $event)"> | ||||
|         <div class="c-frame__header"> | ||||
|             <div class="c-frame__header__start"> | ||||
|                 <div class="c-frame__name icon-object">{{ item.domainObject.name }}</div> | ||||
|                 <div class="c-frame__context-actions c-disclosure-button"></div> | ||||
|             </div> | ||||
|             <div class="c-frame__header__end"> | ||||
|                 <div class="c-button icon-expand local-controls--hidden"></div> | ||||
|             </div> | ||||
|         </div> | ||||
|         <object-view class="c-frame__object-view" | ||||
|                      :object="item.domainObject"></object-view> | ||||
|  | ||||
|         <!-- Drag handles --> | ||||
|         <div class="c-frame-edit"> | ||||
|             <div class="c-frame-edit__move" | ||||
|                  @mousedown="startDrag([1,1], [0,0], $event)"></div> | ||||
|             <div class="c-frame-edit__handle --nw" | ||||
|                  @mousedown="startDrag([1,1], [-1,-1], $event)"></div> | ||||
|             <div class="c-frame-edit__handle --ne" | ||||
|                  @mousedown="startDrag([0,1], [1,-1], $event)"></div> | ||||
|             <div class="c-frame-edit__handle --sw" | ||||
|                  @mousedown="startDrag([1,0], [-1,1], $event)"></div> | ||||
|             <div class="c-frame-edit__handle --se" | ||||
|                  @mousedown="startDrag([0,0], [1,1], $event)"></div> | ||||
|         </div> | ||||
|     </div> | ||||
| </template> | ||||
|  | ||||
| <style lang="scss"> | ||||
|     @import "~styles/sass-base"; | ||||
|  | ||||
|     /******************************* FRAME */ | ||||
|     .c-frame { | ||||
|         display: flex; | ||||
|         flex-direction: column; | ||||
|         border-width: 1px; | ||||
|         border-color: transparent; | ||||
|  | ||||
|         /*************************** HEADER */ | ||||
|         &__header { | ||||
|             display: flex; | ||||
|             align-items: center; | ||||
|             flex: 0 0 auto; | ||||
|             margin-bottom: $interiorMargin; | ||||
|  | ||||
|             > [class*="__"] { | ||||
|                 display: flex; | ||||
|                 align-items: center; | ||||
|             } | ||||
|  | ||||
|             > * + * { | ||||
|                 margin-left: $interiorMargin; | ||||
|             } | ||||
|  | ||||
|             [class*="__start"] { | ||||
|                 flex: 1 1 auto; | ||||
|                 overflow: hidden; | ||||
|             } | ||||
|  | ||||
|             [class*="__end"] { | ||||
|                 //justify-content: flex-end; | ||||
|                 flex: 0 0 auto; | ||||
|  | ||||
|                 [class*="button"] { | ||||
|                     font-size: 0.7em; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         &__name { | ||||
|             @include ellipsize(); | ||||
|             flex: 0 1 auto; | ||||
|             font-size: 1.2em; | ||||
|  | ||||
|             &:before { | ||||
|                 // Object type icon | ||||
|                 flex: 0 0 auto; | ||||
|                 margin-right: $interiorMarginSm; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         /*************************** OBJECT VIEW */ | ||||
|         &__object-view { | ||||
|             flex: 1 1 auto; | ||||
|             overflow: auto; | ||||
|  | ||||
|             .c-object-view { | ||||
|                 .u-fills-container { | ||||
|                     // Expand component types that fill a container | ||||
|                     @include abs(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         /*************************** NO-FRAME */ | ||||
|         &.no-frame { | ||||
|             > [class*="__header"] { | ||||
|                 display: none; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         &:not(.no-frame) { | ||||
|             background: $colorBodyBg; | ||||
|             border: 1px solid $colorInteriorBorder; | ||||
|             padding: $interiorMargin; | ||||
|         } | ||||
|  | ||||
|         /*************************** SELECTION */ | ||||
|         &.is-selectable { | ||||
|             &:hover { | ||||
|                 box-shadow: $browseShdwSelectableHov; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         &.s-selected, // LEGACY | ||||
|         &.is-selected { | ||||
|             border: $browseBorderSelected; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /*************************** EDITING */ | ||||
|     .is-editing { | ||||
|         .c-frame { | ||||
|             &:not(.is-drilled-in).is-selectable { | ||||
|                 border: $editBorderSelectable; | ||||
|  | ||||
|                 &:hover { | ||||
|                     border: $editBorderSelectableHov; | ||||
|                 } | ||||
|  | ||||
|                 &.s-selected, | ||||
|                 &.is-selected { | ||||
|                     border: $editBorderSelected; | ||||
|  | ||||
|                     > .c-frame-edit { | ||||
|                         display: block; // Show the editing rect and handles | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             &.is-drilled-in { | ||||
|                 border: $editBorderDrilledIn; | ||||
|             } | ||||
|  | ||||
|             .u-links { | ||||
|                 // Applied in markup to objects that provide links. Disable while editing. | ||||
|                 pointer-events: none; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     .c-frame-edit { | ||||
|         // The editing rect and handles | ||||
|         $z: 10; | ||||
|  | ||||
|         @include abs(); | ||||
|         box-shadow: rgba($editColor, 0.5) 0 0 10px; | ||||
|         display: none; | ||||
|  | ||||
|         &__move { | ||||
|             @include abs(); | ||||
|             cursor: move; | ||||
|             z-index: $z; | ||||
|         } | ||||
|  | ||||
|         &__handle { | ||||
|             $d: 8px; | ||||
|             $o: floor($d * -0.5); | ||||
|             background: rgba($editColor, 0.3); | ||||
|             border: 1px solid $editColor; | ||||
|             position: absolute; | ||||
|             width: $d; height: $d; | ||||
|             top: auto; right: auto; bottom: auto; left: auto; | ||||
|             z-index: $z + 1; | ||||
|  | ||||
|             &:before { | ||||
|                 // Extended hit area | ||||
|                 $m: -5px; | ||||
|                 content: ''; | ||||
|                 display: block; | ||||
|                 position: absolute; | ||||
|                 top: $m; right: $m; bottom: $m; left: $m; | ||||
|                 z-index: -1; | ||||
|             } | ||||
|  | ||||
|             &:hover { | ||||
|                 background: $editColor; | ||||
|             } | ||||
|  | ||||
|             &.--nw { | ||||
|                 cursor: nw-resize; | ||||
|                 left: $o; top: $o; | ||||
|             } | ||||
|  | ||||
|             &.--ne { | ||||
|                 cursor: ne-resize; | ||||
|                 right: $o; top: $o; | ||||
|             } | ||||
|  | ||||
|             &.--se { | ||||
|                 cursor: se-resize; | ||||
|                 right: $o; bottom: $o; | ||||
|             } | ||||
|  | ||||
|             &.--sw { | ||||
|                 cursor: sw-resize; | ||||
|                 left: $o; bottom: $o; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
| </style> | ||||
|  | ||||
|  | ||||
| <script> | ||||
|     import ObjectView from '../../ui/components/layout/ObjectView.vue' | ||||
|     import LayoutDrag from './LayoutDrag' | ||||
|  | ||||
|     export default { | ||||
|         inject: ['openmct'], | ||||
|         props: { | ||||
|             item: Object, | ||||
|             gridSize: Array | ||||
|         }, | ||||
|         components: { | ||||
|             ObjectView | ||||
|         }, | ||||
|         computed: { | ||||
|             classObject: function () { | ||||
|                 return { | ||||
|                     'is-drilled-in': this.item.drilledIn, | ||||
|                     'no-frame': !this.item.hasFrame | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         methods: { | ||||
|             drill(id, $event) { | ||||
|                 if ($event) { | ||||
|                     $event.stopPropagation(); | ||||
|                 } | ||||
|  | ||||
|                 if (!this.isBeingEdited(this.item.domainObject)) { | ||||
|                     return; | ||||
|                 } | ||||
|  | ||||
|                 if (this.openmct.composition.get(this.item.domainObject) === undefined) { | ||||
|                     return; | ||||
|                 } | ||||
|  | ||||
|                 // Disable for fixed position. | ||||
|                 if (this.item.domainObject.type === 'telemetry.fixed') { | ||||
|                     return; | ||||
|                 } | ||||
|  | ||||
|                 this.$emit('drilledIn', id); | ||||
|             }, | ||||
|             isBeingEdited(object) { | ||||
|                 // TODO: add logic when inEditContext() is implemented in Vue. | ||||
|                 return true; | ||||
|             }, | ||||
|             updatePosition(event) { | ||||
|                 let currentPosition = [event.pageX, event.pageY]; | ||||
|                 this.initialPosition = this.initialPosition || currentPosition; | ||||
|                 this.delta = currentPosition.map(function (value, index) { | ||||
|                     return value - this.initialPosition[index]; | ||||
|                 }.bind(this)); | ||||
|             }, | ||||
|             startDrag(posFactor, dimFactor, event) { | ||||
|                 document.body.addEventListener('mousemove', this.continueDrag); | ||||
|                 document.body.addEventListener('mouseup', this.endDrag); | ||||
|  | ||||
|                 this.updatePosition(event); | ||||
|                 this.activeDrag = new LayoutDrag( | ||||
|                     this.item.rawPosition, | ||||
|                     posFactor, | ||||
|                     dimFactor, | ||||
|                     this.gridSize | ||||
|                 ); | ||||
|                 event.preventDefault(); | ||||
|             }, | ||||
|             continueDrag(event) { | ||||
|                 event.preventDefault(); | ||||
|                 this.updatePosition(event); | ||||
|  | ||||
|                 if (this.activeDrag) { | ||||
|                     this.$emit('dragInProgress', this.item.id, this.activeDrag.getAdjustedPosition(this.delta)); | ||||
|                 } | ||||
|             }, | ||||
|             endDrag(event) { | ||||
|                 document.body.removeEventListener('mousemove', this.continueDrag); | ||||
|                 document.body.removeEventListener('mouseup', this.endDrag); | ||||
|                 this.continueDrag(event); | ||||
|                 this.$emit('endDrag', this.item.id); | ||||
|                 this.initialPosition = undefined; | ||||
|                 event.preventDefault(); | ||||
|             } | ||||
|         }, | ||||
|         mounted() { | ||||
|             this.removeSelectable = this.openmct.selection.selectable( | ||||
|                 this.$el, | ||||
|                 { | ||||
|                     item: this.item.domainObject | ||||
|                 }, | ||||
|                 this.item.initSelect | ||||
|             ); | ||||
|         }, | ||||
|         destroyed() { | ||||
|             this.removeSelectable(); | ||||
|         } | ||||
|     } | ||||
| </script> | ||||
							
								
								
									
										67
									
								
								src/plugins/displayLayout/plugin.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								src/plugins/displayLayout/plugin.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,67 @@ | ||||
| /***************************************************************************** | ||||
|  * 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 Layout from './DisplayLayout.vue' | ||||
| import Vue from 'vue' | ||||
| import objectUtils from '../../api/objects/object-utils.js' | ||||
| import DisplayLayoutType from './DisplayLayoutType.js' | ||||
|  | ||||
| export default function () { | ||||
|     return function (openmct) { | ||||
|         openmct.objectViews.addProvider({ | ||||
|             key: 'layout.view', | ||||
|             canView: function (domainObject) { | ||||
|                 return domainObject.type === 'layout'; | ||||
|             }, | ||||
|             view: function (domainObject) { | ||||
|                 let component; | ||||
|                 return { | ||||
|                     show(container) { | ||||
|                         component = new Vue({ | ||||
|                             components: { | ||||
|                                 Layout | ||||
|                             }, | ||||
|                             template: '<layout :domain-object="domainObject"></layout>', | ||||
|                             provide: { | ||||
|                                 openmct, | ||||
|                                 objectUtils | ||||
|                             }, | ||||
|                             el: container, | ||||
|                             data () { | ||||
|                                 return { | ||||
|                                     domainObject: domainObject | ||||
|                                 } | ||||
|                             } | ||||
|                         }); | ||||
|                     }, | ||||
|                     destroy() { | ||||
|                         component.$destroy(); | ||||
|                     } | ||||
|                 }; | ||||
|             }, | ||||
|             priority() { | ||||
|                 return 100; | ||||
|             } | ||||
|         }); | ||||
|         openmct.types.addType('layout', DisplayLayoutType()); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										67
									
								
								src/plugins/folderView/FolderGridView.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								src/plugins/folderView/FolderGridView.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,67 @@ | ||||
| /***************************************************************************** | ||||
|  * 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/GridView.vue', | ||||
|     'vue' | ||||
| ], function ( | ||||
|     GridViewComponent, | ||||
|     Vue | ||||
| ) { | ||||
|     function FolderGridView(openmct) { | ||||
|         return { | ||||
|             key: 'grid', | ||||
|             name: 'Grid Vue', | ||||
|             cssClass: 'icon-thumbs-strip', | ||||
|             canView: function (domainObject) { | ||||
|                 return domainObject.type === 'folder'; | ||||
|             }, | ||||
|             view: function (domainObject) { | ||||
|                 let component; | ||||
|  | ||||
|                 return { | ||||
|                     show: function (element) { | ||||
|                         component =  new Vue({ | ||||
|                             components: { | ||||
|                                 gridViewComponent: GridViewComponent.default | ||||
|                             }, | ||||
|                             provide: { | ||||
|                                 openmct, | ||||
|                                 domainObject | ||||
|                             }, | ||||
|                             el: element, | ||||
|                             template: '<grid-view-component></grid-view-component>' | ||||
|                         }); | ||||
|                     }, | ||||
|                     destroy: function (element) { | ||||
|                         component.$destroy(); | ||||
|                         component = undefined; | ||||
|                     } | ||||
|                 }; | ||||
|             }, | ||||
|             priority: function () { | ||||
|                 return 1; | ||||
|             } | ||||
|         }; | ||||
|     } | ||||
|     return FolderGridView; | ||||
| }); | ||||
							
								
								
									
										70
									
								
								src/plugins/folderView/FolderListView.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								src/plugins/folderView/FolderListView.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,70 @@ | ||||
| /***************************************************************************** | ||||
|  * 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/ListView.vue', | ||||
|     'vue', | ||||
|     'moment' | ||||
| ], function ( | ||||
|     ListViewComponent, | ||||
|     Vue, | ||||
|     Moment | ||||
| ) { | ||||
|     function FolderListView(openmct) { | ||||
|         return { | ||||
|             key: 'list-view', | ||||
|             name: 'List Vue', | ||||
|             cssClass: 'icon-list-view', | ||||
|             canView: function (domainObject) { | ||||
|                 return domainObject.type === 'folder'; | ||||
|             }, | ||||
|             view: function (domainObject) { | ||||
|                 let component; | ||||
|  | ||||
|                 return { | ||||
|                     show: function (element) { | ||||
|                         component =  new Vue({ | ||||
|                             components: { | ||||
|                                 listViewComponent: ListViewComponent.default | ||||
|                             }, | ||||
|                             provide: { | ||||
|                                 openmct, | ||||
|                                 domainObject, | ||||
|                                 Moment | ||||
|                             }, | ||||
|                             el: element, | ||||
|                             template: '<list-view-component></list-view-component>' | ||||
|                         }); | ||||
|                     }, | ||||
|                     destroy: function (element) { | ||||
|                         component.$destroy(); | ||||
|                         component = undefined; | ||||
|                     } | ||||
|                 }; | ||||
|             }, | ||||
|             priority: function () { | ||||
|                 return 1; | ||||
|             } | ||||
|         }; | ||||
|     } | ||||
|     return FolderListView; | ||||
| }); | ||||
							
								
								
									
										200
									
								
								src/plugins/folderView/components/GridView.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										200
									
								
								src/plugins/folderView/components/GridView.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,200 @@ | ||||
| <template> | ||||
|     <div class="l-grid-view"> | ||||
|         <div v-for="(item, index) in items" | ||||
|             v-bind:key="index" | ||||
|             class="l-grid-view__item c-grid-item" | ||||
|              :class="{ 'is-alias': item.isAlias === true }" | ||||
|             @click="navigate(item.model.identifier.key)"> | ||||
|             <div class="c-grid-item__type-icon" | ||||
|                  :class="(item.type.cssClass != undefined) ? 'bg-' + item.type.cssClass : 'bg-icon-object-unknown'"> | ||||
|             </div> | ||||
|             <div class="c-grid-item__details"> | ||||
|                 <!-- Name and metadata --> | ||||
|                 <div class="c-grid-item__name" | ||||
|                      :title="item.model.name">{{item.model.name}}</div> | ||||
|                 <div class="c-grid-item__metadata" | ||||
|                      :title="item.type.name"> | ||||
|                     <span>{{item.type.name}}</span> | ||||
|                     <span v-if="item.model.composition !== undefined"> | ||||
|                         - {{item.model.composition.length}} item<span v-if="item.model.composition.length !== 1">s</span> | ||||
|                     </span> | ||||
|                 </div> | ||||
|             </div> | ||||
|             <div class="c-grid-item__controls"> | ||||
|                 <div class="icon-people" title='Shared'></div> | ||||
|                 <button class="c-click-icon icon-info c-info-button" title='More Info'></button> | ||||
|                 <div class="icon-pointer-right c-pointer-icon"></div> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
| </template> | ||||
|  | ||||
| <style lang="scss"> | ||||
|     @import "~styles/sass-base"; | ||||
|  | ||||
|     /******************************* GRID VIEW */ | ||||
|     .l-grid-view { | ||||
|         display: flex; | ||||
|         flex-flow: column nowrap; | ||||
|  | ||||
|         &__item { | ||||
|             flex: 0 0 auto; | ||||
|             + .l-grid-view__item { margin-top: $interiorMargin; } | ||||
|         } | ||||
|  | ||||
|         body.desktop & { | ||||
|             flex-flow: row wrap; | ||||
|             &__item { | ||||
|                 height: $ueBrowseGridItemLg; | ||||
|                 width: $ueBrowseGridItemLg; | ||||
|                 margin: 0 $interiorMargin $interiorMargin 0; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /******************************* GRID ITEMS */ | ||||
|     .c-grid-item { | ||||
|         // Mobile-first | ||||
|         @include button($bg: $colorItemBg, $fg: $colorItemFg); | ||||
|         cursor: pointer; | ||||
|         display: flex; | ||||
|         padding: $interiorMarginLg; | ||||
|  | ||||
|         &__type-icon { | ||||
|             filter: $colorKeyFilter; | ||||
|             flex: 0 0 32px; | ||||
|             font-size: 2em; // Drives the size of the alias indicator when present | ||||
|             margin-right: $interiorMarginLg; | ||||
|         } | ||||
|  | ||||
|         &.is-alias { | ||||
|             // Object is an alias to an original. | ||||
|             [class*='__type-icon'] { | ||||
|                 @include isAlias(); | ||||
|                 color: $colorIconAliasForKeyFilter; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         &__details { | ||||
|             display: flex; | ||||
|             flex-flow: column nowrap; | ||||
|             flex: 1 1 auto; | ||||
|         } | ||||
|  | ||||
|         &__name { | ||||
|             @include ellipsize(); | ||||
|             color: $colorItemFg; | ||||
|             font-size: 1.3em; | ||||
|             font-weight: 400; | ||||
|             margin-bottom: $interiorMarginSm; | ||||
|         } | ||||
|  | ||||
|         &__metadata { | ||||
|             color: $colorItemFgDetails; | ||||
|         } | ||||
|  | ||||
|         &__controls { | ||||
|             color: $colorItemFgDetails; | ||||
|             flex: 0 0 64px; | ||||
|             font-size: 1.2em; | ||||
|             display: flex; | ||||
|             align-items: center; | ||||
|             justify-content: flex-end; | ||||
|  | ||||
|             > * + * { | ||||
|                 margin-left: $interiorMargin; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         body.desktop & { | ||||
|             $transOutMs: 300ms; | ||||
|             flex-flow: column nowrap; | ||||
|             transition: background $transOutMs ease-in-out; | ||||
|  | ||||
|             &:hover { | ||||
|                 background: $colorItemBgHov; | ||||
|                 transition: $transIn; | ||||
|  | ||||
|                 .c-grid-item__type-icon { | ||||
|                     filter: $colorKeyFilterHov; | ||||
|                     transform: scale(1); | ||||
|                     transition: $transInBounce; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             > * { | ||||
|                 margin: 0; // Reset from mobile | ||||
|             } | ||||
|  | ||||
|             &__controls { | ||||
|                 align-items: start; | ||||
|                 flex: 0 0 auto; | ||||
|                 order: 1; | ||||
|                 .c-info-button, | ||||
|                 .c-pointer-icon { display: none; } | ||||
|             } | ||||
|  | ||||
|             &__type-icon { | ||||
|                 flex: 1 1 auto; | ||||
|                 font-size: 6em; // Drives the size of the alias indicator when present | ||||
|                 margin: $interiorMargin 22.5%; | ||||
|                 order: 2; | ||||
|                 transform: scale(0.9); | ||||
|                 transform-origin: center; | ||||
|                 transition: all $transOutMs ease-in-out; | ||||
|             } | ||||
|  | ||||
|             &__details { | ||||
|                 flex: 0 0 auto; | ||||
|                 justify-content: flex-end; | ||||
|                 order: 3; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| </style> | ||||
|  | ||||
| <script> | ||||
| export default { | ||||
|     inject: ['openmct', 'domainObject'], | ||||
|     data() { | ||||
|         var items = [], | ||||
|             unknownObjectType = { | ||||
|                 definition: { | ||||
|                     cssClass: 'icon-object-unknown', | ||||
|                     name: 'Unknown Type' | ||||
|                 } | ||||
|             }; | ||||
|  | ||||
|         var composition = this.openmct.composition.get(this.domainObject); | ||||
|  | ||||
|         if (composition) { | ||||
|  | ||||
|             composition.load().then((array) => { | ||||
|                 if (Array.isArray(array)) { | ||||
|                     array.forEach((model) => { | ||||
|                         var type = this.openmct.types.get(model.type) || unknownObjectType; | ||||
|  | ||||
|                         items.push({ | ||||
|                             model: model, | ||||
|                             type: type.definition, | ||||
|                             isAlias: this.domainObject.identifier.key !== model.location | ||||
|                         }); | ||||
|                     }); | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         return { | ||||
|             items: items | ||||
|         } | ||||
|     }, | ||||
|     methods: { | ||||
|         navigate(identifier) { | ||||
|             let currentLocation = this.openmct.router.currentLocation.path, | ||||
|                 navigateToPath = `${currentLocation}/${identifier}`; | ||||
|              | ||||
|             this.openmct.router.setPath(navigateToPath); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										215
									
								
								src/plugins/folderView/components/ListView.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										215
									
								
								src/plugins/folderView/components/ListView.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,215 @@ | ||||
| <template> | ||||
|     <div class="c-table c-table--sortable c-list-view"> | ||||
|         <table class="c-table__body"> | ||||
|             <thead class="c-table__header"> | ||||
|             <tr> | ||||
|                 <th class="is-sortable" | ||||
|                     v-bind:class="[orderByField == 'name' ? 'is-sorting' : '', sortClass]" | ||||
|                     @click="sortTrigger('name', 'asc')"> | ||||
|                     Name | ||||
|                 </th> | ||||
|                 <th class="is-sortable" | ||||
|                     v-bind:class="[orderByField == 'type' ? 'is-sorting' : '', sortClass]" | ||||
|                     @click="sortTrigger('type', 'asc')"> | ||||
|                     Type | ||||
|                 </th> | ||||
|                 <th class="is-sortable" | ||||
|                     v-bind:class="[orderByField == 'createdDate' ? 'is-sorting' : '', sortClass]" | ||||
|                     @click="sortTrigger('createdDate', 'desc')"> | ||||
|                     Created Date | ||||
|                 </th> | ||||
|                 <th class="is-sortable" | ||||
|                     v-bind:class="[orderByField == 'updatedDate' ? 'is-sorting' : '', sortClass]" | ||||
|                     @click="sortTrigger('updatedDate', 'desc')"> | ||||
|                     Updated Date | ||||
|                 </th> | ||||
|                 <th class="is-sortable" | ||||
|                     v-bind:class="[orderByField == 'items' ? 'is-sorting' : '', sortClass]" | ||||
|                     @click="sortTrigger('items', 'asc')"> | ||||
|                     Items | ||||
|                 </th> | ||||
|             </tr> | ||||
|             </thead> | ||||
|             <tbody> | ||||
|             <tr class="c-list-item" | ||||
|                 v-for="(item,index) in sortedItems" | ||||
|                 v-bind:key="index" | ||||
|                 :class="{ 'is-alias': item.isAlias === true }" | ||||
|                 @click="navigate(item.identifier)"> | ||||
|                 <td class="c-list-item__name"> | ||||
|                     <div class="c-list-item__type-icon" :class="(item.cssClass != undefined) ? item.cssClass : 'icon-object-unknown'"></div> | ||||
|                     {{item.name}} | ||||
|                 </td> | ||||
|                 <td class="c-list-item__type">{{ item.type }}</td> | ||||
|                 <td class="c-list-item__date-created">{{ formatTime(item.createdDate, 'YYYY-MM-DD HH:mm:ss:SSS') }}Z</td> | ||||
|                 <td class="c-list-item__date-updated">{{ formatTime(item.updatedDate, 'YYYY-MM-DD HH:mm:ss:SSS') }}Z</td> | ||||
|                 <td class="c-list-item__items">{{ item.items }}</td> | ||||
|             </tr> | ||||
|             </tbody> | ||||
|         </table> | ||||
|     </div> | ||||
| </template> | ||||
|  | ||||
| <style lang="scss"> | ||||
|     @import "~styles/sass-base"; | ||||
|  | ||||
|     /******************************* LIST VIEW */ | ||||
|     .c-list-view { | ||||
|         overflow-x: auto !important; | ||||
|         overflow-y: auto; | ||||
|  | ||||
|         tbody tr { | ||||
|             background: $colorListItemBg; | ||||
|             transition: $transOut; | ||||
|         } | ||||
|  | ||||
|         body.desktop & { | ||||
|             tbody tr { | ||||
|                 cursor: pointer; | ||||
|  | ||||
|                 &:hover { | ||||
|                     background: $colorListItemBgHov; | ||||
|                     transition: $transIn; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         td { | ||||
|             $p: floor($interiorMargin * 1.5); | ||||
|             font-size: 1.1em; | ||||
|             padding-top: $p; | ||||
|             padding-bottom: $p; | ||||
|  | ||||
|             &:not(.c-list-item__name) { | ||||
|                 color: $colorItemFgDetails; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     .c-list-item { | ||||
|         &__name { | ||||
|             @include ellipsize(); | ||||
|         } | ||||
|  | ||||
|         &__type-icon { | ||||
|             color: $colorKey; | ||||
|             display: inline-block; | ||||
|             width: 1em; | ||||
|             margin-right:$interiorMarginSm; | ||||
|         } | ||||
|  | ||||
|         &.is-alias { | ||||
|             // Object is an alias to an original. | ||||
|             [class*='__type-icon'] { | ||||
|                 &:after { | ||||
|                     color: $colorIconAlias; | ||||
|                     content: $glyph-icon-link; | ||||
|                     font-family: symbolsfont; | ||||
|                     display: block; | ||||
|                     position: absolute; | ||||
|                     text-shadow: rgba(black, 0.5) 0 1px 2px; | ||||
|                     top: auto; left: -1px; bottom: 1px; right: auto; | ||||
|                     transform-origin: bottom left; | ||||
|                     transform: scale(0.65); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /******************************* LIST ITEM */ | ||||
| </style> | ||||
|  | ||||
| <script> | ||||
|  | ||||
| export default { | ||||
|     inject: ['openmct', 'domainObject', 'Moment'], | ||||
|     data() { | ||||
|         var items = [], | ||||
|             unknownObjectType = { | ||||
|                 definition: { | ||||
|                     cssClass: 'icon-object-unknown', | ||||
|                     name: 'Unknown Type' | ||||
|                 } | ||||
|             }, | ||||
|             composition = this.openmct.composition.get(this.domainObject); | ||||
|      | ||||
|         if (composition) { | ||||
|  | ||||
|             composition.load().then((array) => { | ||||
|                 if (Array.isArray(array)) { | ||||
|                     array.forEach(model => { | ||||
|                         var type = this.openmct.types.get(model.type) || unknownObjectType; | ||||
|  | ||||
|                         items.push({ | ||||
|                             name: model.name, | ||||
|                             identifier: model.identifier.key, | ||||
|                             type: type.definition.name, | ||||
|                             isAlias: false, | ||||
|                             cssClass: type.definition.cssClass, | ||||
|                             createdDate: model.persisted, | ||||
|                             updatedDate: model.modified, | ||||
|                             items: model.composition ? model.composition.length : 0, | ||||
|                             isAlias: this.domainObject.identifier.key !== model.location | ||||
|                         }); | ||||
|                     }); | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         return { | ||||
|             items: items, | ||||
|             orderByField: 'name', | ||||
|             sortClass: 'asc', | ||||
|         } | ||||
|     }, | ||||
|     computed: { | ||||
|         sortedItems () { | ||||
|             if (this.sortClass === 'asc') { | ||||
|                 return this.items.sort(this.ascending.bind(this)); | ||||
|             } else if (this.sortClass === 'desc') { | ||||
|                 return this.items.sort(this.descending.bind(this)); | ||||
|             } | ||||
|         }, | ||||
|         formatTime () { | ||||
|             return function (timestamp, format) { | ||||
|                 return this.Moment(timestamp).format(format); | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
|     methods: { | ||||
|         navigate(identifier) { | ||||
|             let currentLocation = this.openmct.router.currentLocation.path, | ||||
|                 navigateToPath = `${currentLocation}/${identifier}`; | ||||
|              | ||||
|             this.openmct.router.setPath(navigateToPath); | ||||
|         }, | ||||
|         sortTrigger(field, sortOrder) { | ||||
|             if (this.orderByField === field) { | ||||
|                 this.sortClass = (this.sortClass === 'asc') ? 'desc' : 'asc'; | ||||
|             } else { | ||||
|                 this.sortClass = sortOrder; | ||||
|             } | ||||
|             this.orderByField = field; | ||||
|         }, | ||||
|         ascending(first, second) { | ||||
|             if (first[this.orderByField] < second[this.orderByField]) { | ||||
|                 return -1; | ||||
|             } else if (first[this.orderByField] > second[this.orderByField]) { | ||||
|                 return 1; | ||||
|             } else { | ||||
|                 return 0; | ||||
|             } | ||||
|         }, | ||||
|         descending(first, second) { | ||||
|             if (first[this.orderByField] > second[this.orderByField]) { | ||||
|                 return -1; | ||||
|             } else if (first[this.orderByField] < second[this.orderByField]) { | ||||
|                 return 1; | ||||
|             } else { | ||||
|                 return 0; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| </script> | ||||
| @@ -20,34 +20,17 @@ | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
| 
 | ||||
| define([], function () { | ||||
| 
 | ||||
|     /** | ||||
|      * Formatter for basic strings. | ||||
|      * | ||||
|      * @implements {Format} | ||||
|      * @constructor | ||||
|      * @memberof platform/commonUI/formats | ||||
|      */ | ||||
|     function StringFormat() { | ||||
|         this.key = 'string'; | ||||
|     } | ||||
| 
 | ||||
|     StringFormat.prototype.format = function (string) { | ||||
|         if (typeof string === 'string') { | ||||
|             return string; | ||||
|         } else { | ||||
|             return '' + string; | ||||
|         } | ||||
| define([ | ||||
|     './FolderGridView', | ||||
|     './FolderListView' | ||||
| ], function ( | ||||
|     FolderGridView, | ||||
|     FolderListView | ||||
| ) { | ||||
|     return function plugin() { | ||||
|         return function install(openmct) { | ||||
|             openmct.objectViews.addProvider(new FolderGridView(openmct)); | ||||
|             openmct.objectViews.addProvider(new FolderListView(openmct)); | ||||
|         }; | ||||
|     }; | ||||
| 
 | ||||
|     StringFormat.prototype.parse = function (string) { | ||||
|         return string; | ||||
|     }; | ||||
| 
 | ||||
|     StringFormat.prototype.validate = function (string) { | ||||
|         return typeof string === 'string'; | ||||
|     }; | ||||
| 
 | ||||
|     return StringFormat; | ||||
| }); | ||||
| @@ -51,64 +51,12 @@ define([ | ||||
|     function LocalTimeFormat() { | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns an appropriate time format based on the provided value and | ||||
|      * the threshold required. | ||||
|      * @private | ||||
|      */ | ||||
|     function getScaledFormat(d) { | ||||
|         var momentified = moment.utc(d); | ||||
|         /** | ||||
|          * Uses logic from d3 Time-Scales, v3 of the API. See | ||||
|          * https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Scales.md | ||||
|          * | ||||
|          * Licensed | ||||
|          */ | ||||
|         return [ | ||||
|             [".SSS", function (m) { | ||||
|                 return m.milliseconds(); | ||||
|             }], | ||||
|             [":ss", function (m) { | ||||
|                 return m.seconds(); | ||||
|             }], | ||||
|             ["hh:mma", function (m) { | ||||
|                 return m.minutes(); | ||||
|             }], | ||||
|             ["hha", function (m) { | ||||
|                 return m.hours(); | ||||
|             }], | ||||
|             ["ddd DD", function (m) { | ||||
|                 return m.days() && | ||||
|                     m.date() !== 1; | ||||
|             }], | ||||
|             ["MMM DD", function (m) { | ||||
|                 return m.date() !== 1; | ||||
|             }], | ||||
|             ["MMMM", function (m) { | ||||
|                 return m.month(); | ||||
|             }], | ||||
|             ["YYYY", function () { | ||||
|                 return true; | ||||
|             }] | ||||
|         ].filter(function (row) { | ||||
|             return row[1](momentified); | ||||
|         })[0][0]; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * | ||||
|      * @param value | ||||
|      * @param {Scale} [scale] Optionally provides context to the | ||||
|      * format request, allowing for scale-appropriate formatting. | ||||
|      * @returns {string} the formatted date | ||||
|      */ | ||||
|     LocalTimeFormat.prototype.format = function (value, scale) { | ||||
|         if (scale !== undefined) { | ||||
|             var scaledFormat = getScaledFormat(value, scale); | ||||
|             if (scaledFormat) { | ||||
|                 return moment.utc(value).format(scaledFormat); | ||||
|             } | ||||
|         } | ||||
|         return moment(value).format(DATE_FORMAT); | ||||
|     }; | ||||
|  | ||||
|   | ||||
| @@ -41,7 +41,7 @@ define([], function () { | ||||
|         this.timeFormat = 'local-format'; | ||||
|         this.durationFormat = 'duration'; | ||||
|  | ||||
|         this.isUTCBased = true; | ||||
|         this.isUTCBased = false; | ||||
|     } | ||||
|  | ||||
|     return LocalTimeSystem; | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| <div class="c-ne__embed"> | ||||
|     <div class="c-ne__embed__snap-thumb" | ||||
|         v-if="embed.snapshot" | ||||
|         v-on:click="openSnapshot"> | ||||
|         v-on:click="openSnapshot(domainObject, entry, embed)"> | ||||
|         <img v-bind:src="embed.snapshot.src"> | ||||
|     </div> | ||||
|     <div class="c-ne__embed__info"> | ||||
|   | ||||
| @@ -28,8 +28,8 @@ | ||||
|     </div> | ||||
|  | ||||
|     <div class="c-ne__local-controls--hidden"> | ||||
|         <a class="c-icon-button icon-trash" | ||||
|         <button class="c-click-icon icon-trash" | ||||
|            title="Delete this entry" | ||||
|            v-on:click="deleteEntry"></a> | ||||
|            v-on:click="deleteEntry"></button> | ||||
|     </div> | ||||
| </li> | ||||
							
								
								
									
										29
									
								
								src/plugins/notebook/res/templates/snapshotTemplate.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/plugins/notebook/res/templates/snapshotTemplate.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| <div class="u-contents"> | ||||
|  | ||||
|     <div class="t-snapshot abs l-view-header"> | ||||
|         <div class="abs object-browse-bar l-flex-row"> | ||||
|             <div class="left flex-elem l-flex-row grows"> | ||||
|                 <div class="object-header flex-elem l-flex-row grows"> | ||||
|                     <div class="type-icon flex-elem embed-icon holder" v-bind:class="embed.cssClass"></div> | ||||
|                     <div class="title-label flex-elem holder flex-can-shrink">{{embed.name}}</div> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
|  | ||||
|     <div class="btn-bar right l-flex-row flex-elem flex-justify-end flex-fixed"> | ||||
|         <div class="flex-elem holder flex-can-shrink s-snapshot-datetime"> | ||||
|             SNAPSHOT {{formatTime(embed.createdOn, 'YYYY-MM-DD HH:mm:ss')}} | ||||
|         </div> | ||||
|         <a class="s-button icon-pencil" title="Annotate" v-on:click="annotateSnapshot"> | ||||
|             <span class="title-label">Annotate</span> | ||||
|         </a> | ||||
|     </div> | ||||
|  | ||||
|     <div class="abs object-holder t-image-holder s-image-holder"> | ||||
|         <div  | ||||
|             class="image-main s-image-main" | ||||
|             v-bind:style="{ backgroundImage: 'url(' + embed.snapshot.src + ')' }"> | ||||
|         </div> | ||||
|     </div> | ||||
| </div> | ||||
| @@ -23,12 +23,16 @@ | ||||
| define([ | ||||
|     'moment', | ||||
|     'zepto', | ||||
|     '../utils/SnapshotOverlay', | ||||
|     '../../res/templates/snapshotTemplate.html', | ||||
|     'vue', | ||||
|     'painterro' | ||||
| ], | ||||
| function ( | ||||
|     Moment, | ||||
|     $, | ||||
|     SnapshotOverlay | ||||
|     SnapshotTemplate, | ||||
|     Vue, | ||||
|     Painterro | ||||
| ) { | ||||
|     function EmbedController (openmct, domainObject) { | ||||
|         this.openmct = openmct; | ||||
| @@ -48,16 +52,127 @@ function ( | ||||
|  | ||||
|     EmbedController.prototype.navigate = function (embedType) { | ||||
|         this.objectService.getObjects([embedType]).then(function (objects) { | ||||
|             this.navigationService.setNavigation(objects[embedType]);    | ||||
|             this.navigationService.setNavigation(objects[embedType]); | ||||
|         }.bind(this)); | ||||
|     }; | ||||
|  | ||||
|     EmbedController.prototype.openSnapshot = function () { | ||||
|         if (!this.snapshotOverlay) { | ||||
|             this.snapShotOverlay = new SnapshotOverlay(this.embed, this.formatTime); | ||||
|         } else { | ||||
|             this.snapShotOverlay = undefined; | ||||
|     EmbedController.prototype.openSnapshot = function (domainObject, entry, embed) { | ||||
|  | ||||
|         function annotateSnapshot(openmct) { | ||||
|             return function () { | ||||
|  | ||||
|                 var save = false, | ||||
|                     painterroInstance = {}, | ||||
|                     annotateOverlay = new Vue({ | ||||
|                         template: '<div id="snap-annotation"></div>' | ||||
|                     }), | ||||
|                     self = this; | ||||
|  | ||||
|                 var options = { | ||||
|                     cssClass: 'l-large-view', | ||||
|                     onDestroy: function () { | ||||
|                         annotateOverlay.$destroy(true); | ||||
|                     }, | ||||
|                     buttons: [ | ||||
|                         { | ||||
|                             label: 'Cancel', | ||||
|                             callback: function () { | ||||
|                                 save = false; | ||||
|                                 painterroInstance.save(); | ||||
|                             } | ||||
|                         }, | ||||
|                         { | ||||
|                             label: 'Save', | ||||
|                             callback: function () { | ||||
|                                 save = true; | ||||
|                                 painterroInstance.save(); | ||||
|                             } | ||||
|                         } | ||||
|                     ] | ||||
|                 }; | ||||
|  | ||||
|                 openmct.OverlayService.show(annotateOverlay.$mount().$el, options); | ||||
|  | ||||
|                 painterroInstance = Painterro({ | ||||
|                     id: 'snap-annotation', | ||||
|                     activeColor: '#ff0000', | ||||
|                     activeColorAlpha: 1.0, | ||||
|                     activeFillColor: '#fff', | ||||
|                     activeFillColorAlpha: 0.0, | ||||
|                     backgroundFillColor: '#000', | ||||
|                     backgroundFillColorAlpha: 0.0, | ||||
|                     defaultFontSize: 16, | ||||
|                     defaultLineWidth: 2, | ||||
|                     defaultTool: 'ellipse', | ||||
|                     hiddenTools: ['save', 'open', 'close', 'eraser', 'pixelize', 'rotate', 'settings', 'resize'], | ||||
|                     translation: { | ||||
|                         name: 'en', | ||||
|                         strings: { | ||||
|                             lineColor: 'Line', | ||||
|                             fillColor: 'Fill', | ||||
|                             lineWidth: 'Size', | ||||
|                             textColor: 'Color', | ||||
|                             fontSize: 'Size', | ||||
|                             fontStyle: 'Style' | ||||
|                         } | ||||
|                     }, | ||||
|                     saveHandler: function (image, done) { | ||||
|                         if (save) { | ||||
|                             var entryPos = self.findInArray(domainObject.entries, entry.id), | ||||
|                                 embedPos = self.findInArray(entry.embeds, embed.id); | ||||
|  | ||||
|                             if (entryPos !== -1 && embedPos !== -1) { | ||||
|                                 var url = image.asBlob(), | ||||
|                                     reader = new window.FileReader(); | ||||
|  | ||||
|                                 reader.readAsDataURL(url); | ||||
|                                 reader.onloadend = function () { | ||||
|                                     var snapshot = reader.result, | ||||
|                                         snapshotObject = { | ||||
|                                             src: snapshot, | ||||
|                                             type: url.type, | ||||
|                                             size: url.size, | ||||
|                                             modified: Date.now() | ||||
|                                         }, | ||||
|                                         dirString = 'entries[' + entryPos + '].embeds[' + embedPos + '].snapshot'; | ||||
|  | ||||
|                                     openmct.objects.mutate(domainObject, dirString, snapshotObject); | ||||
|                                 }; | ||||
|                             } | ||||
|                         } else { | ||||
|                             console.log('You cancelled the annotation!!!'); | ||||
|                         } | ||||
|                         done(true); | ||||
|                     } | ||||
|                 }).show(embed.snapshot.src); | ||||
|             }; | ||||
|         } | ||||
|  | ||||
|         var self = this, | ||||
|             snapshot = new Vue({ | ||||
|                 template: SnapshotTemplate, | ||||
|                 data: function () { | ||||
|                     return { | ||||
|                         embed: self.embed | ||||
|                     }; | ||||
|                 }, | ||||
|                 methods: { | ||||
|                     formatTime: self.formatTime, | ||||
|                     annotateSnapshot: annotateSnapshot(self.openmct), | ||||
|                     findInArray: self.findInArray | ||||
|                 } | ||||
|             }); | ||||
|  | ||||
|         function onDestroyCallback() { | ||||
|             snapshot.$destroy(true); | ||||
|         } | ||||
|         var options = { | ||||
|             onDestroy: onDestroyCallback, | ||||
|             cssClass: 'l-large-view' | ||||
|         }; | ||||
|  | ||||
|  | ||||
|         this.openmct.OverlayService.show(snapshot.$mount().$el, options); | ||||
|     }; | ||||
|  | ||||
|     EmbedController.prototype.formatTime = function (unixTime, timeFormat) { | ||||
| @@ -108,23 +223,20 @@ function ( | ||||
|                 var entryPosition = self.findInArray(self.domainObject.entries, entry.id), | ||||
|                     embedPosition = self.findInArray(entry.embeds, embed.id); | ||||
|  | ||||
|                 var warningDialog = self.dialogService.showBlockingMessage({ | ||||
|                 self.openmct.OverlayService.showBlockingMessage({ | ||||
|                     severity: "error", | ||||
|                     title: "This action will permanently delete this embed. Do you wish to continue?", | ||||
|                     options: [{ | ||||
|                         label: "OK", | ||||
|                     actionText: 'This Action will permanently delete this embed. Do you wish to continue?', | ||||
|                     buttons: [{ | ||||
|                         label: "No", | ||||
|                         callback: function () {} | ||||
|                     }, | ||||
|                     { | ||||
|                         label: "Yes", | ||||
|                         callback: function () { | ||||
|                             entry.embeds.splice(embedPosition, 1); | ||||
|                             var dirString = 'entries[' + entryPosition + '].embeds'; | ||||
|  | ||||
|                             self.openmct.objects.mutate(self.domainObject, dirString, entry.embeds); | ||||
|  | ||||
|                             warningDialog.dismiss(); | ||||
|                         } | ||||
|                     },{ | ||||
|                         label: "Cancel", | ||||
|                         callback: function () { | ||||
|                             warningDialog.dismiss(); | ||||
|                         } | ||||
|                     }] | ||||
|                 }); | ||||
| @@ -190,7 +302,8 @@ function ( | ||||
|             openSnapshot: self.openSnapshot, | ||||
|             formatTime: self.formatTime, | ||||
|             toggleActionMenu: self.toggleActionMenu, | ||||
|             actionToMenuDecorator: self.actionToMenuDecorator | ||||
|             actionToMenuDecorator: self.actionToMenuDecorator, | ||||
|             findInArray: self.findInArray | ||||
|         }; | ||||
|     }; | ||||
|  | ||||
|   | ||||
| @@ -81,21 +81,18 @@ function ( | ||||
|  | ||||
|         if (entryPos !== -1) { | ||||
|  | ||||
|             var errorDialog = this.dialogService.showBlockingMessage({ | ||||
|             this.openmct.OverlayService.showBlockingMessage({ | ||||
|                 severity: "error", | ||||
|                 title: "This action will permanently delete this Notebook entry. Do you wish to continue?", | ||||
|                 options: [{ | ||||
|                     label: "OK", | ||||
|                 actionText: "This action will permanently delete this Notebook entry. Do you wish to continue?", | ||||
|                 buttons: [{ | ||||
|                     label: "No", | ||||
|                     callback: function () {} | ||||
|                 }, | ||||
|                 { | ||||
|                     label: "Yes", | ||||
|                     callback: function () { | ||||
|                         domainObject.entries.splice(entryPos, 1); | ||||
|                         openmct.objects.mutate(domainObject, 'entries', domainObject.entries); | ||||
|  | ||||
|                         errorDialog.dismiss(); | ||||
|                     } | ||||
|                 },{ | ||||
|                     label: "Cancel", | ||||
|                     callback: function () { | ||||
|                         errorDialog.dismiss(); | ||||
|                     } | ||||
|                 }] | ||||
|             }); | ||||
|   | ||||
| @@ -60,6 +60,7 @@ function ( | ||||
|         this.container = container; | ||||
|  | ||||
|         var notebookEmbed = { | ||||
|             inject:['openmct', 'domainObject'], | ||||
|             props:['embed', 'entry'], | ||||
|             template: EmbedTemplate, | ||||
|             data: embedController.exposedData, | ||||
| @@ -80,6 +81,7 @@ function ( | ||||
|  | ||||
|         var notebookVue = Vue.extend({ | ||||
|             template: NotebookTemplate, | ||||
|             provide: {openmct: self.openmct, domainObject: self.domainObject}, | ||||
|             components: { | ||||
|                 'notebook-entry': entryComponent, | ||||
|                 'search': search.default | ||||
|   | ||||
| @@ -34,7 +34,9 @@ define([ | ||||
|     './plot/plugin', | ||||
|     './telemetryTable/plugin', | ||||
|     './staticRootPlugin/plugin', | ||||
|     './notebook/plugin' | ||||
|     './notebook/plugin', | ||||
|     './displayLayout/plugin', | ||||
|     './folderView/plugin' | ||||
| ], function ( | ||||
|     _, | ||||
|     UTCTimeSystem, | ||||
| @@ -49,7 +51,9 @@ define([ | ||||
|     PlotPlugin, | ||||
|     TelemetryTablePlugin, | ||||
|     StaticRootPlugin, | ||||
|     Notebook | ||||
|     Notebook, | ||||
|     DisplayLayoutPlugin, | ||||
|     FolderView | ||||
| ) { | ||||
|     var bundleMap = { | ||||
|         LocalStorage: 'platform/persistence/local', | ||||
| @@ -101,7 +105,7 @@ define([ | ||||
|      */ | ||||
|     plugins.AutoflowView = AutoflowPlugin; | ||||
|  | ||||
|     plugins.Conductor = TimeConductorPlugin; | ||||
|     plugins.Conductor = TimeConductorPlugin.default; | ||||
|  | ||||
|     plugins.CouchDB = function (url) { | ||||
|         return function (openmct) { | ||||
| @@ -159,6 +163,8 @@ define([ | ||||
|     plugins.TelemetryMean = TelemetryMean; | ||||
|     plugins.URLIndicator = URLIndicatorPlugin; | ||||
|     plugins.Notebook = Notebook; | ||||
|     plugins.DisplayLayout = DisplayLayoutPlugin.default; | ||||
|     plugins.FolderView = FolderView; | ||||
|  | ||||
|     return plugins; | ||||
| }); | ||||
|   | ||||
| @@ -264,7 +264,7 @@ define([ | ||||
|         this.applyStyle($('#widget', this.domElement), activeRule.getProperty('style')); | ||||
|         $('#widget', this.domElement).prop('title', activeRule.getProperty('message')); | ||||
|         $('#widgetLabel', this.domElement).html(activeRule.getProperty('label')); | ||||
|         $('#widgetLabel', this.domElement).removeClass().addClass('label widget-label ' + activeRule.getProperty('icon')); | ||||
|         $('#widgetLabel', this.domElement).removeClass().addClass('label widget-label c-summary-widget__label ' + activeRule.getProperty('icon')); | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|   | ||||
| @@ -18,7 +18,7 @@ define([ | ||||
|         this.widget.title = datum.message; | ||||
|         this.label.title = datum.message; | ||||
|         this.label.innerHTML = datum.ruleLabel; | ||||
|         this.label.className = 'label widget-label ' + datum.icon; | ||||
|         this.label.className = 'label widget-label c-summary-widget__label ' + datum.icon; | ||||
|     }; | ||||
|  | ||||
|     SummaryWidgetView.prototype.render = function () { | ||||
|   | ||||
| @@ -8,7 +8,7 @@ define([ | ||||
|     objectUtils | ||||
| ) { | ||||
|  | ||||
|  | ||||
|     const DEFAULT_VIEW_PRIORITY = 100; | ||||
|     /** | ||||
|      * | ||||
|      */ | ||||
| @@ -34,7 +34,11 @@ define([ | ||||
|             }, | ||||
|             editable: true, | ||||
|             priority: function (domainObject) { | ||||
|                 return 1; | ||||
|                 if (domainObject.type === 'summary-widget') { | ||||
|                     return Number.MAX_VALUE; | ||||
|                 } else { | ||||
|                     return DEFAULT_VIEW_PRIORITY; | ||||
|                 } | ||||
|             } | ||||
|         }; | ||||
|     } | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| <div class="w-summary-widget s-status-no-data"> | ||||
|     <a class="t-summary-widget l-summary-widget s-summary-widget labeled"> | ||||
|         <span class="label widget-label">Loading...</span> | ||||
| <div class="w-summary-widget s-status-no-data c-widget-wrapper u-contents"> | ||||
|     <a class="t-summary-widget c-button c-summary-widget u-links u-fills-container"> | ||||
|         <span class="label widget-label c-summary-widget__label">Loading...</span> | ||||
|     </a> | ||||
| </div> | ||||
|   | ||||
| @@ -33,30 +33,6 @@ define([ | ||||
| ) { | ||||
|  | ||||
|     function TableConfigurationViewProvider(openmct) { | ||||
|         let instantiateService; | ||||
|  | ||||
|         function isBeingEdited(object) { | ||||
|             let oldStyleObject = getOldStyleObject(object); | ||||
|  | ||||
|             return oldStyleObject.hasCapability('editor') && | ||||
|                 oldStyleObject.getCapability('editor').inEditContext(); | ||||
|         } | ||||
|  | ||||
|         function getOldStyleObject(object) { | ||||
|             let oldFormatModel = objectUtils.toOldFormat(object); | ||||
|             let oldFormatId = objectUtils.makeKeyString(object.identifier); | ||||
|  | ||||
|             return instantiate(oldFormatModel, oldFormatId); | ||||
|         } | ||||
|  | ||||
|         function instantiate(model, id) { | ||||
|             if (!instantiateService) { | ||||
|                 instantiateService = openmct.$injector.get('instantiate'); | ||||
|             } | ||||
|             return instantiateService(model, id); | ||||
|         } | ||||
|  | ||||
|  | ||||
|         return { | ||||
|             key: 'table-configuration', | ||||
|             name: 'Telemetry Table Configuration', | ||||
| @@ -65,8 +41,7 @@ define([ | ||||
|                     return false; | ||||
|                 } | ||||
|                 let object = selection[0].context.item; | ||||
|                 return object.type === 'table' && | ||||
|                     isBeingEdited(object); | ||||
|                 return object.type === 'table'; | ||||
|             }, | ||||
|             view: function (selection) { | ||||
|                 let component; | ||||
| @@ -86,7 +61,7 @@ define([ | ||||
|                             el: element | ||||
|                         }); | ||||
|                     }, | ||||
|                     destroy: function (element) { | ||||
|                     destroy: function () { | ||||
|                         component.$destroy(); | ||||
|                         component = undefined; | ||||
|                     } | ||||
|   | ||||
| @@ -48,7 +48,12 @@ define(function () { | ||||
|         } | ||||
|  | ||||
|         getFormattedValue(telemetryDatum) { | ||||
|             return this.formatter.format(telemetryDatum); | ||||
|             let formattedValue = this.formatter.format(telemetryDatum); | ||||
|             if (typeof formattedValue !== 'string') { | ||||
|                 return formattedValue.toString(); | ||||
|             } else { | ||||
|                 return formattedValue; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|     }; | ||||
|   | ||||
| @@ -23,10 +23,10 @@ | ||||
| define([ | ||||
|     'lodash', | ||||
|     'EventEmitter', | ||||
|     './TelemetryTableColumn', | ||||
|     './TelemetryTableColumn' | ||||
| ], function (_, EventEmitter, TelemetryTableColumn) { | ||||
|  | ||||
|     class TelemetryTableConfiguration extends EventEmitter{ | ||||
|     class TelemetryTableConfiguration extends EventEmitter { | ||||
|         constructor(domainObject, openmct) { | ||||
|             super(); | ||||
|  | ||||
| @@ -37,6 +37,8 @@ define([ | ||||
|             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. | ||||
|             this.oldConfiguration = JSON.parse(JSON.stringify(this.getConfiguration())); | ||||
|  | ||||
|             this.unlistenFromMutation = openmct.objects.observe(domainObject, '*', this.objectMutated); | ||||
|         } | ||||
| @@ -56,11 +58,11 @@ define([ | ||||
|          * @param {*} object  | ||||
|          */ | ||||
|         objectMutated(object) { | ||||
|             let oldConfiguration = this.domainObject.configuration; | ||||
|  | ||||
|             //Synchronize domain object reference. Duplicate object otherwise change detection becomes impossible. | ||||
|             this.domainObject = JSON.parse(JSON.stringify(object)); | ||||
|             if (!_.eq(object.configuration, oldConfiguration)){ | ||||
|             this.domainObject = object; | ||||
|             if (!_.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); | ||||
|             } | ||||
|         } | ||||
|   | ||||
| @@ -39,7 +39,7 @@ define([], function () { | ||||
|  | ||||
|         getFormattedValue(key) { | ||||
|             let column = this.columns[key]; | ||||
|             return column.getFormattedValue(this.datum[key]); | ||||
|             return column && column.getFormattedValue(this.datum[key]); | ||||
|         } | ||||
|  | ||||
|         getRowLimitClass() { | ||||
|   | ||||
| @@ -1,14 +1,12 @@ | ||||
| <template> | ||||
| <div class="grid-properties"> | ||||
|     <!--form class="form" --> | ||||
|         <ul class="l-inspector-part"> | ||||
|             <h2>Table Columns</h2> | ||||
|             <li class="grid-row" v-for="(title, key) in headers"> | ||||
|                 <div class="grid-cell label" title="Show or Hide Column"><label :for="key + 'ColumnControl'">{{title}}</label></div> | ||||
|                 <div class="grid-cell value"><input type="checkbox" :id="key + 'ColumnControl'" :checked="configuration.hiddenColumns[key] !== true" @change="toggleColumn(key)"></div> | ||||
|             </li> | ||||
|         </ul> | ||||
|     <!--/form --> | ||||
| <div class="c-properties" v-if="isEditing"> | ||||
|     <div class="c-properties__header">Table Columns</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__value"><input type="checkbox" :id="key + 'ColumnControl'" :checked="configuration.hiddenColumns[key] !== true" @change="toggleColumn(key)"></div> | ||||
|         </li> | ||||
|     </ul> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| @@ -21,6 +19,7 @@ export default { | ||||
|     data() { | ||||
|         return { | ||||
|             headers: {}, | ||||
|             isEditing: this.openmct.editor.isEditing(), | ||||
|             configuration: this.tableConfiguration.getConfiguration() | ||||
|         } | ||||
|     }, | ||||
| @@ -41,11 +40,14 @@ export default { | ||||
|         removeObject(objectIdentifier) { | ||||
|             this.tableConfiguration.removeColumnsForObject(objectIdentifier, true); | ||||
|             this.updateHeaders(this.tableConfiguration.getAllHeaders()); | ||||
|         }, | ||||
|         toggleEdit(isEditing) { | ||||
|             this.isEditing = isEditing; | ||||
|         } | ||||
|  | ||||
|     }, | ||||
|     mounted() { | ||||
|         this.unlisteners = []; | ||||
|         this.openmct.editor.on('isEditing', this.toggleEdit); | ||||
|         let compositionCollection = this.openmct.composition.get(this.tableConfiguration.domainObject); | ||||
|  | ||||
|         compositionCollection.load() | ||||
| @@ -62,6 +64,7 @@ export default { | ||||
|     }, | ||||
|     destroyed() { | ||||
|         this.tableConfiguration.destroy(); | ||||
|         this.openmct.editor.off('isEditing', this.toggleEdit); | ||||
|         this.unlisteners.forEach((unlisten) => unlisten()); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -32,7 +32,9 @@ export default { | ||||
|         columnWidths: { | ||||
|             type: Array, | ||||
|             required: false, | ||||
|             default: [], | ||||
|             default() { | ||||
|                 return []; | ||||
|             }, | ||||
|         }, | ||||
|         rowIndex: { | ||||
|             type: Number, | ||||
| @@ -48,10 +50,6 @@ export default { | ||||
|             type: Number, | ||||
|             required: false, | ||||
|             default: 0 | ||||
|         }, | ||||
|         configuration: { | ||||
|             type: Object, | ||||
|             required: true | ||||
|         } | ||||
|     }, | ||||
|     methods: { | ||||
|   | ||||
| @@ -9,7 +9,7 @@ | ||||
|         </a> | ||||
|     </div> | ||||
|     <!-- Headers table --> | ||||
|     <div class="c-table__headers-w js-table__headers-w"> | ||||
|     <div class="c-telemetry-table__headers-w js-table__headers-w"> | ||||
|         <table class="c-table__headers c-telemetry-table__headers" | ||||
|                :style="{ 'max-width': totalWidth + 'px'}"> | ||||
|             <thead> | ||||
| @@ -68,48 +68,22 @@ | ||||
|  | ||||
| <style lang="scss"> | ||||
|     @import "~styles/sass-base"; | ||||
|     @import "~styles/table"; | ||||
|  | ||||
|     .c-table { | ||||
|         // Can be used by any type of table, scrolling, LAD, etc. | ||||
|         $min-w: 50px; | ||||
|  | ||||
|         display: flex; | ||||
|         flex-flow: column nowrap; | ||||
|         justify-content: flex-start; | ||||
|     .c-telemetry-table { | ||||
|         // Table that displays telemetry in a scrolling body area | ||||
|         overflow: hidden; | ||||
|         position: absolute; | ||||
|         top: 0; right: 0; bottom: 0; left: 0; | ||||
|  | ||||
|         &__control-bar, | ||||
|         &__headers-w { | ||||
|             // Don't allow top level elements to grow or shrink | ||||
|             flex: 0 0 auto; | ||||
|         } | ||||
|  | ||||
|         /******************************* ELEMENTS */ | ||||
|         th, td { | ||||
|             display: block; | ||||
|             flex: 1 0 auto; | ||||
|             font-size: 0.7rem; // TEMP LEGACY TODO: refactor this when __main-container font-size is dealt with | ||||
|             white-space: nowrap; | ||||
|             min-width: $min-w; | ||||
|             padding: $tabularTdPadTB $tabularTdPadLR; | ||||
|             vertical-align: middle; // This is crucial to hiding f**king 4px height injected by browser by default | ||||
|         } | ||||
|  | ||||
|         td { | ||||
|             color: $colorTelemFresh; | ||||
|             vertical-align: top; | ||||
|         } | ||||
|  | ||||
|         &__control-bar { | ||||
|             margin-bottom: $interiorMarginSm; | ||||
|         } | ||||
|  | ||||
|         /******************************* WRAPPERS */ | ||||
|         &__headers-w { | ||||
|             // Wraps __headers table | ||||
|             background: $colorTabHeaderBg; | ||||
|             flex: 0 0 auto; | ||||
|             overflow: hidden; | ||||
|         } | ||||
|  | ||||
| @@ -135,65 +109,6 @@ | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         &__body { | ||||
|             // A table | ||||
|             tr { | ||||
|                 &:not(:first-child) { | ||||
|                     border-top: 1px solid $colorTabBorder; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         /******************************* MODIFIERS */ | ||||
|         &--filterable { | ||||
|             // TODO: discuss using the search.vue custom control here | ||||
|  | ||||
|             .l-filter { | ||||
|                 input[type="text"], | ||||
|                 input[type="search"] { | ||||
|                     $p: 20px; | ||||
|                     transition: padding 200ms ease-in-out; | ||||
|                     box-sizing: border-box; | ||||
|                     padding-right: $p; // Fend off from icon | ||||
|                     padding-left: $p; // Fend off from icon | ||||
|                     width: 100%; | ||||
|                 } | ||||
|                 &.active { | ||||
|                     // When user has typed something, hide the icon and collapse left padding | ||||
|                     &:before { | ||||
|                         opacity: 0; | ||||
|                     } | ||||
|                     input[type="text"], | ||||
|                     input[type="search"] { | ||||
|                         padding-left: $interiorMargin; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         &--sortable { | ||||
|             .is-sorting { | ||||
|                 &:after { | ||||
|                     color: $colorIconLink; | ||||
|                     content: $glyph-icon-arrow-tall-up; | ||||
|                     font-family: symbolsfont; | ||||
|                     font-size: 8px; | ||||
|                     display: inline-block; | ||||
|                     margin-left: $interiorMarginSm; | ||||
|                 } | ||||
|                 &.desc:after { | ||||
|                     content: $glyph-icon-arrow-tall-down; | ||||
|                 } | ||||
|             } | ||||
|             .is-sortable { | ||||
|                 cursor: pointer; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     .c-telemetry-table { | ||||
|         // Table that displays telemetry in a scrolling body area | ||||
|  | ||||
|         /******************************* ELEMENTS */ | ||||
|         &__scroll-forcer { | ||||
|             // Force horz scroll when needed; width set via JS | ||||
| @@ -251,10 +166,6 @@ | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     .c-table__control-bar { | ||||
|         margin-bottom: $interiorMarginSm; | ||||
|     } | ||||
|  | ||||
|     /******************************* LEGACY */ | ||||
|     .s-status-taking-snapshot, | ||||
|     .overlay.snapshot { | ||||
|   | ||||
							
								
								
									
										400
									
								
								src/plugins/timeConductor/Conductor.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										400
									
								
								src/plugins/timeConductor/Conductor.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,400 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT Web, Copyright (c) 2014-2018, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT Web is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT Web includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
| <template> | ||||
|     <div class="c-conductor" | ||||
|          :class="[isFixed ? 'is-fixed-mode' : 'is-realtime-mode', panning ? 'status-panning' : '']"> | ||||
|         <form class="u-contents" ref="conductorForm" | ||||
|               @submit="isFixed ? setBoundsFromView($event) : setOffsetsFromView($event)"> | ||||
|  | ||||
|             <ConductorModeIcon class="c-conductor__mode-icon"></ConductorModeIcon> | ||||
|  | ||||
|             <div class="c-conductor__start-input"> | ||||
|                 <!-- Start input and controls --> | ||||
|                 <div class="c-ctrl-wrapper c-conductor-input c-conductor__start__fixed" | ||||
|                      v-if="isFixed"> | ||||
|                     <!-- Fixed input --> | ||||
|                     <div class="c-conductor__start__fixed__label">Start</div> | ||||
|                     <input class="c-input--datetime" | ||||
|                            type="text" autocorrect="off" spellcheck="false" | ||||
|                            ref="startDate" | ||||
|                            v-model="formattedBounds.start" | ||||
|                            @change="validateBounds('start', $event.target); setBoundsFromView()" /> | ||||
|                     <date-picker | ||||
|                             :default-date-time="formattedBounds.start" | ||||
|                             :formatter="timeFormatter" | ||||
|                             @date-selected="startDateSelected"></date-picker> | ||||
|                 </div> | ||||
|  | ||||
|                 <div class="c-ctrl-wrapper c-conductor-input c-conductor__start__delta" | ||||
|                      v-if="!isFixed"> | ||||
|                     <!-- RT input --> | ||||
|                     <div class="c-direction-indicator icon-minus"></div> | ||||
|                     <input class="c-input--hrs-min-sec" | ||||
|                            type="text" autocorrect="off" | ||||
|                            spellcheck="false" | ||||
|                            v-model="offsets.start" | ||||
|                            @change="validateOffsets($event); setOffsetsFromView()"> | ||||
|                 </div> | ||||
|             </div> | ||||
|  | ||||
|             <div class="c-conductor__end-input"> | ||||
|                 <!-- End input and controls --> | ||||
|                 <div class="c-ctrl-wrapper c-conductor-input c-conductor__end__fixed" | ||||
|                      v-if="isFixed"> | ||||
|                     <!-- Fixed input --> | ||||
|                     <div class="c-conductor__end__fixed__label">End</div> | ||||
|                     <input class="c-input--datetime" | ||||
|                            type="text" autocorrect="off" spellcheck="false" | ||||
|                            v-model="formattedBounds.end" | ||||
|                            :disabled="!isFixed" | ||||
|                            ref="endDate" | ||||
|                            @change="validateBounds('end', $event.target); setBoundsFromView()"> | ||||
|                     <date-picker | ||||
|                             class="c-ctrl-wrapper--menus-left" | ||||
|                             :default-date-time="formattedBounds.end" | ||||
|                             :formatter="timeFormatter" | ||||
|                             @date-selected="endDateSelected"></date-picker> | ||||
|                 </div> | ||||
|  | ||||
|                 <div class="c-ctrl-wrapper c-conductor-input c-conductor__end__delta" | ||||
|                      v-if="!isFixed"> | ||||
|                     <!-- RT input --> | ||||
|                     <div class="c-direction-indicator icon-plus"></div> | ||||
|                     <input class="c-input--hrs-min-sec" | ||||
|                            type="text" | ||||
|                            autocorrect="off" | ||||
|                            spellcheck="false" | ||||
|                            v-model="offsets.end" | ||||
|                            @change="validateOffsets($event); setOffsetsFromView()"> | ||||
|                 </div> | ||||
|             </div> | ||||
|  | ||||
|             <conductor-axis | ||||
|                     class="c-conductor__ticks" | ||||
|                     :bounds="rawBounds" | ||||
|                     @panAxis="setViewFromBounds"></conductor-axis> | ||||
|             <div class="c-conductor__controls"> | ||||
|                 <!-- Mode, time system menu buttons and duration slider --> | ||||
|                 <ConductorMode class="c-conductor__mode-select"></ConductorMode> | ||||
|                 <ConductorTimeSystem class="c-conductor__time-system-select"></ConductorTimeSystem> | ||||
|             </div> | ||||
|             <input type="submit" class="invisible"> | ||||
|         </form> | ||||
|     </div> | ||||
| </template> | ||||
|  | ||||
| <style lang="scss"> | ||||
|     @import "~styles/sass-base"; | ||||
|  | ||||
|     /*********************************************** CONDUCTOR LAYOUT */ | ||||
|     .c-conductor { | ||||
|         display: grid; | ||||
|         grid-column-gap: $interiorMargin; | ||||
|         grid-row-gap: $interiorMargin; | ||||
|         align-items: center; | ||||
|  | ||||
|  | ||||
|         grid-template-rows: 1fr 1fr; | ||||
|         grid-template-columns: 20px auto 1fr auto; | ||||
|         grid-template-areas: | ||||
|                 "tc-mode-icon tc-start tc-ticks tc-end" | ||||
|                 "tc-controls tc-controls tc-controls tc-controls"; | ||||
|  | ||||
|         .c-conductor__end-input { | ||||
|             justify-content: flex-end; | ||||
|         } | ||||
|  | ||||
|         body.phone.portrait & { | ||||
|             &.is-fixed-mode { | ||||
|                 grid-row-gap: $interiorMargin; | ||||
|                 grid-template-rows: auto auto auto; | ||||
|                 grid-template-columns: 20px auto; | ||||
|                 grid-template-areas: | ||||
|                         "tc-mode-icon tc-start" | ||||
|                         "tc-mode-icon tc-end" | ||||
|                         "tc-mode-icon tc-controls"; | ||||
|  | ||||
|                 .c-conductor { | ||||
|                     &__mode-icon { | ||||
|                         grid-row: 1; | ||||
|                     } | ||||
|  | ||||
|                     &__ticks, | ||||
|                     &__zoom { | ||||
|                         display: none; | ||||
|                     } | ||||
|  | ||||
|                     &-input [class*='__label'] { | ||||
|                         // Start and end are in separate columns; make the labels line up | ||||
|                         width: 40px; | ||||
|                     } | ||||
|  | ||||
|                     &__end-input { | ||||
|                         justify-content: flex-start; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         &__mode-icon { | ||||
|             grid-area: tc-mode-icon; | ||||
|         } | ||||
|  | ||||
|         &__start-input, | ||||
|         &__end-input { | ||||
|             display: flex; | ||||
|         } | ||||
|  | ||||
|         &__start-input { | ||||
|             grid-area: tc-start; | ||||
|         } | ||||
|  | ||||
|         &__end-input { | ||||
|             grid-area: tc-end; | ||||
|             display: flex; | ||||
|         } | ||||
|  | ||||
|         &__ticks { | ||||
|             grid-area: tc-ticks; | ||||
|         } | ||||
|  | ||||
|         &__controls { | ||||
|             grid-area: tc-controls; | ||||
|             display: flex; | ||||
|             align-items: center; | ||||
|             > * + * { | ||||
|                 margin-left: $interiorMargin; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         [class*='__delta'] { | ||||
|             &:before { | ||||
|                 content: $glyph-icon-clock; | ||||
|                 font-family: symbolsfont; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     .c-conductor-input { | ||||
|         color: $colorInputFg; | ||||
|         display: flex; | ||||
|         align-items: center; | ||||
|         justify-content: flex-start; | ||||
|  | ||||
|         > * + * { | ||||
|             margin-left: $interiorMarginSm; | ||||
|         } | ||||
|  | ||||
|         &:before { | ||||
|             // Realtime-mode clock icon symbol | ||||
|             margin-right: $interiorMarginSm; | ||||
|         } | ||||
|  | ||||
|         .c-direction-indicator { | ||||
|             // Holds realtime-mode + and - symbols | ||||
|             font-size: 0.7em; | ||||
|         } | ||||
|  | ||||
|         input:invalid { | ||||
|             background: rgba($colorFormInvalid, 0.3); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     .is-realtime-mode { | ||||
|         .c-conductor-input { | ||||
|             &:before { | ||||
|                 color: $colorTime; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| </style> | ||||
|  | ||||
| <script> | ||||
| import moment from 'moment'; | ||||
| import ConductorMode from './ConductorMode.vue'; | ||||
| import ConductorTimeSystem from './ConductorTimeSystem.vue'; | ||||
| import DatePicker from './DatePicker.vue'; | ||||
| import ConductorAxis from './ConductorAxis.vue'; | ||||
| import ConductorModeIcon from './ConductorModeIcon.vue'; | ||||
|  | ||||
| const DEFAULT_DURATION_FORMATTER = 'duration'; | ||||
| const SECONDS = 1000; | ||||
| const DAYS = 24 * 60 * 60 * SECONDS; | ||||
| const YEARS = 365 * DAYS; | ||||
|  | ||||
| const RESIZE_POLL_INTERVAL = 200; | ||||
|  | ||||
| export default { | ||||
|     inject: ['openmct', 'configuration'], | ||||
|     components: { | ||||
|         ConductorMode, | ||||
|         ConductorTimeSystem, | ||||
|         DatePicker, | ||||
|         ConductorAxis, | ||||
|         ConductorModeIcon | ||||
|     }, | ||||
|     data() { | ||||
|         let bounds = this.openmct.time.bounds(); | ||||
|         let offsets = this.openmct.time.clockOffsets(); | ||||
|         let timeSystem = this.openmct.time.timeSystem(); | ||||
|         let timeFormatter = this.getFormatter(timeSystem.timeFormat); | ||||
|         let durationFormatter = this.getFormatter(timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER); | ||||
|  | ||||
|         return { | ||||
|             timeFormatter: timeFormatter, | ||||
|             durationFormatter: durationFormatter, | ||||
|             offsets: { | ||||
|                 start: offsets && durationFormatter.format(Math.abs(offsets.start)), | ||||
|                 end: offsets && durationFormatter.format(Math.abs(offsets.end)), | ||||
|             }, | ||||
|             formattedBounds: { | ||||
|                 start: timeFormatter.format(bounds.start), | ||||
|                 end: timeFormatter.format(bounds.end) | ||||
|             }, | ||||
|             rawBounds: { | ||||
|                 start: bounds.start, | ||||
|                 end: bounds.end | ||||
|             }, | ||||
|             isFixed: this.openmct.time.clock() === undefined, | ||||
|             isUTCBased: timeSystem.isUTCBased, | ||||
|             showDatePicker: false | ||||
|         } | ||||
|     }, | ||||
|     methods: { | ||||
|         setTimeSystem(timeSystem) { | ||||
|             this.timeFormatter = this.getFormatter(timeSystem.timeFormat); | ||||
|             this.durationFormatter = this.getFormatter( | ||||
|                 timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER); | ||||
|  | ||||
|             this.isUTCBased = timeSystem.isUTCBased; | ||||
|         }, | ||||
|         setOffsetsFromView($event) { | ||||
|             if (this.$refs.conductorForm.checkValidity()){ | ||||
|                 let startOffset = 0 - this.durationFormatter.parse(this.offsets.start); | ||||
|                 let endOffset = this.durationFormatter.parse(this.offsets.end); | ||||
|  | ||||
|                 this.openmct.time.clockOffsets({ | ||||
|                     start: startOffset,  | ||||
|                     end: endOffset | ||||
|                 }); | ||||
|             } | ||||
|             if ($event) { | ||||
|                 $event.preventDefault(); | ||||
|                 return false; | ||||
|             } | ||||
|         }, | ||||
|         setBoundsFromView($event) { | ||||
|             if (this.$refs.conductorForm.checkValidity()){ | ||||
|                 let start = this.timeFormatter.parse(this.formattedBounds.start); | ||||
|                 let end = this.timeFormatter.parse(this.formattedBounds.end); | ||||
|  | ||||
|                 this.openmct.time.bounds({ | ||||
|                     start: start, | ||||
|                     end: end | ||||
|                 }); | ||||
|             } | ||||
|             if ($event) { | ||||
|                 $event.preventDefault(); | ||||
|                 return false; | ||||
|             } | ||||
|         }, | ||||
|         setViewFromClock(clock) { | ||||
|             this.isFixed = clock === undefined; | ||||
|         }, | ||||
|         setViewFromBounds(bounds) { | ||||
|             this.formattedBounds.start = this.timeFormatter.format(bounds.start); | ||||
|             this.formattedBounds.end = this.timeFormatter.format(bounds.end); | ||||
|             this.rawBounds.start = bounds.start; | ||||
|             this.rawBounds.end = bounds.end; | ||||
|         }, | ||||
|         setViewFromOffsets(offsets) { | ||||
|             this.offsets.start = this.durationFormatter.format(Math.abs(offsets.start)); | ||||
|             this.offsets.end = this.durationFormatter.format(Math.abs(offsets.end)); | ||||
|         }, | ||||
|         validateBounds(startOrEnd, input) { | ||||
|             let validationResult = true; | ||||
|  | ||||
|             if (!this.timeFormatter.validate(input.value)){ | ||||
|                 validationResult = 'Invalid date value'; | ||||
|             } else { | ||||
|                 let boundsValues = { | ||||
|                     start: this.timeFormatter.parse(this.formattedBounds.start), | ||||
|                     end: this.timeFormatter.parse(this.formattedBounds.end) | ||||
|                 }; | ||||
|                 validationResult = this.openmct.time.validateBounds(boundsValues); | ||||
|             } | ||||
|              | ||||
|             if (validationResult !== true){ | ||||
|                 input.setCustomValidity(validationResult); | ||||
|             } else { | ||||
|                 input.setCustomValidity(''); | ||||
|             } | ||||
|         }, | ||||
|         validateOffsets(event) { | ||||
|             let input = event.target; | ||||
|             let validationResult = true; | ||||
|  | ||||
|             if (!this.durationFormatter.validate(input.value)) { | ||||
|                 validationResult = 'Invalid offset value'; | ||||
|             } else { | ||||
|                 let offsetValues = { | ||||
|                     start: 0 - this.durationFormatter.parse(this.offsets.start), | ||||
|                     end: this.durationFormatter.parse(this.offsets.end) | ||||
|                 }; | ||||
|                 validationResult = this.openmct.time.validateOffsets(offsetValues); | ||||
|             } | ||||
|  | ||||
|             if (validationResult !== true){ | ||||
|                 input.setCustomValidity(validationResult); | ||||
|             } else { | ||||
|                 input.setCustomValidity(''); | ||||
|             } | ||||
|  | ||||
|         }, | ||||
|         getFormatter(key) { | ||||
|             return this.openmct.telemetry.getValueFormatter({ | ||||
|                 format: key | ||||
|             }).formatter; | ||||
|         }, | ||||
|         startDateSelected(date){ | ||||
|             this.formattedBounds.start = this.timeFormatter.format(date); | ||||
|             this.validateBounds('start', this.$refs.startDate); | ||||
|             this.setBoundsFromView(); | ||||
|         }, | ||||
|         endDateSelected(date){ | ||||
|             this.formattedBounds.end = this.timeFormatter.format(date); | ||||
|             this.validateBounds('end', this.$refs.endDate); | ||||
|             this.setBoundsFromView(); | ||||
|         }, | ||||
|     }, | ||||
|     mounted() { | ||||
|         this.setTimeSystem(JSON.parse(JSON.stringify(this.openmct.time.timeSystem()))); | ||||
|  | ||||
|         this.openmct.time.on('bounds', this.setViewFromBounds); | ||||
|         this.openmct.time.on('timeSystem', this.setTimeSystem); | ||||
|         this.openmct.time.on('clock', this.setViewFromClock); | ||||
|         this.openmct.time.on('clockOffsets', this.setViewFromOffsets) | ||||
|     } | ||||
| } | ||||
| </script> | ||||
|  | ||||
|  | ||||
							
								
								
									
										261
									
								
								src/plugins/timeConductor/ConductorAxis.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										261
									
								
								src/plugins/timeConductor/ConductorAxis.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,261 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT Web, Copyright (c) 2014-2018, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT Web is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT Web includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
| <template> | ||||
|     <div class="c-conductor-axis" | ||||
|          ref="axisHolder" | ||||
|          @mousedown="dragStart($event)"> | ||||
|     </div> | ||||
| </template> | ||||
|  | ||||
| <style lang="scss"> | ||||
|     @import "~styles/sass-base"; | ||||
|  | ||||
|     .c-conductor-axis { | ||||
|         $h: 18px; | ||||
|         $tickYPos: ($h / 2) + 12px; | ||||
|  | ||||
|         @include userSelectNone(); | ||||
|         @include bgTicks($c: rgba($colorBodyFg, 0.4)); | ||||
|         background-position: 0 50%; | ||||
|         background-size: 5px 2px; | ||||
|         height: $h; | ||||
|  | ||||
|         svg { | ||||
|             text-rendering: geometricPrecision; | ||||
|             width: 100%; | ||||
|             height: 100%; | ||||
|             > g { | ||||
|                 // Overall Tick holder | ||||
|                 transform: translateY($tickYPos); | ||||
|                 path { | ||||
|                     // Domain line | ||||
|                     display: none; | ||||
|                 } | ||||
|  | ||||
|                 g { | ||||
|                     // Each tick. These move on drag. | ||||
|                     line { | ||||
|                         // Line beneath ticks | ||||
|                         display: none; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             text { | ||||
|                 // Tick labels | ||||
|                 fill: $colorBodyFg; | ||||
|                 font-size: 1em; | ||||
|                 paint-order: stroke; | ||||
|                 font-weight: bold; | ||||
|                 stroke: $colorBodyBg; | ||||
|                 stroke-linecap: butt; | ||||
|                 stroke-linejoin: bevel; | ||||
|                 stroke-width: 6px; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         body.desktop .is-fixed-mode & { | ||||
|             @include cursorGrab(); | ||||
|             background-size: 3px 30%; | ||||
|             border-radius: $controlCr; | ||||
|             background-color: $colorBodyBgSubtle; | ||||
|             box-shadow: inset rgba(black, 0.2) 0 1px 1px; | ||||
|  | ||||
|             svg text { | ||||
|                 fill: $colorBodyFg; | ||||
|                 stroke: $colorBodyBgSubtle; | ||||
|             } | ||||
|  | ||||
|             &:hover, | ||||
|             &:active { | ||||
|                 $c: $colorKeySubtle; | ||||
|                 background-color: $c; | ||||
|                 svg text { | ||||
|                     stroke: $c; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         .is-realtime-mode & { | ||||
|             svg text { | ||||
|                 fill: $colorTime; | ||||
|             } | ||||
|  | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
| </style> | ||||
|  | ||||
| <script> | ||||
|  | ||||
| import * as d3Selection from 'd3-selection'; | ||||
| import * as d3Axis from 'd3-axis'; | ||||
| import * as d3Scale from 'd3-scale'; | ||||
| import utcMultiTimeFormat from './utcMultiTimeFormat.js'; | ||||
|  | ||||
| const PADDING = 1; | ||||
| const DEFAULT_DURATION_FORMATTER = 'duration'; | ||||
| const RESIZE_POLL_INTERVAL = 200; | ||||
| const PIXELS_PER_TICK = 100; | ||||
| const PIXELS_PER_TICK_WIDE = 200; | ||||
|  | ||||
| export default { | ||||
|     inject: ['openmct'], | ||||
|     props: { | ||||
|         bounds: Object | ||||
|     }, | ||||
|     methods: { | ||||
|         setScale() { | ||||
|             let timeSystem = this.openmct.time.timeSystem(); | ||||
|             let bounds = this.bounds; | ||||
|  | ||||
|             if (timeSystem.isUTCBased) { | ||||
|                 this.xScale.domain([new Date(bounds.start), new Date(bounds.end)]); | ||||
|             } else { | ||||
|                 this.xScale.domain([bounds.start, bounds.end]); | ||||
|             } | ||||
|  | ||||
|             this.xAxis.scale(this.xScale); | ||||
|  | ||||
|             this.xScale.range([PADDING, this.width - PADDING * 2]); | ||||
|             this.axisElement.call(this.xAxis); | ||||
|  | ||||
|             if (this.width > 1800) { | ||||
|                 this.xAxis.ticks(this.width / PIXELS_PER_TICK_WIDE); | ||||
|             } else { | ||||
|                 this.xAxis.ticks(this.width / PIXELS_PER_TICK); | ||||
|             } | ||||
|  | ||||
|             this.msPerPixel = (bounds.end - bounds.start) / this.width; | ||||
|         }, | ||||
|         setViewFromTimeSystem(timeSystem) { | ||||
|             let format = this.getActiveFormatter(); | ||||
|             let bounds = this.openmct.time.bounds(); | ||||
|  | ||||
|             //The D3 scale used depends on the type of time system as d3 | ||||
|             // supports UTC out of the box. | ||||
|             if (timeSystem.isUTCBased) { | ||||
|                 this.xScale = d3Scale.scaleUtc(); | ||||
|             } else { | ||||
|                 this.xScale = d3Scale.scaleLinear(); | ||||
|             } | ||||
|  | ||||
|             this.xAxis.scale(this.xScale); | ||||
|             this.xAxis.tickFormat(utcMultiTimeFormat); | ||||
|             this.axisElement.call(this.xAxis); | ||||
|         }, | ||||
|         getActiveFormatter() { | ||||
|             let timeSystem = this.openmct.time.timeSystem(); | ||||
|             let isFixed = this.openmct.time.clock() === undefined; | ||||
|  | ||||
|             if (isFixed) { | ||||
|                 return this.getFormatter(timeSystem.timeFormat); | ||||
|             } else { | ||||
|                 return this.getFormatter(timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER); | ||||
|             } | ||||
|         }, | ||||
|         getFormatter(key) { | ||||
|             return this.openmct.telemetry.getValueFormatter({ | ||||
|                 format: key | ||||
|             }).formatter; | ||||
|         }, | ||||
|         dragStart($event){ | ||||
|             let isFixed = this.openmct.time.clock() === undefined; | ||||
|             if (isFixed){ | ||||
|                 this.dragStartX = $event.clientX; | ||||
|  | ||||
|                 document.addEventListener('mousemove', this.drag); | ||||
|                 document.addEventListener('mouseup', this.dragEnd, { | ||||
|                     once: true | ||||
|                 }); | ||||
|             } | ||||
|         }, | ||||
|         drag($event) { | ||||
|             if (!this.dragging){ | ||||
|                 this.dragging = true; | ||||
|                 requestAnimationFrame(()=>{ | ||||
|                     let deltaX = $event.clientX - this.dragStartX; | ||||
|                     let percX = deltaX / this.width; | ||||
|                     let bounds = this.openmct.time.bounds(); | ||||
|                     let deltaTime = bounds.end - bounds.start; | ||||
|                     let newStart = bounds.start - percX * deltaTime; | ||||
|                     this.bounds = { | ||||
|                         start: newStart, | ||||
|                         end: newStart + deltaTime | ||||
|                     }; | ||||
|                     this.$emit('panAxis', this.bounds); | ||||
|                     this.dragging = false; | ||||
|                 }) | ||||
|             } else { | ||||
|                 console.log('Rejected drag due to RAF cap'); | ||||
|             } | ||||
|         }, | ||||
|         dragEnd() { | ||||
|             document.removeEventListener('mousemove', this.drag); | ||||
|             this.openmct.time.bounds({ | ||||
|                 start: this.bounds.start, | ||||
|                 end: this.bounds.end | ||||
|             }); | ||||
|         }, | ||||
|         resize() { | ||||
|             if (this.$refs.axisHolder.clientWidth !== this.width) { | ||||
|                 this.width = this.$refs.axisHolder.clientWidth; | ||||
|                 this.setScale(); | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
|     watch: { | ||||
|         bounds: { | ||||
|             handler(bounds) { | ||||
|                 this.setScale(); | ||||
|             }, | ||||
|             deep: true | ||||
|         } | ||||
|     }, | ||||
|     mounted() { | ||||
|         let axisHolder = this.$refs.axisHolder; | ||||
|         let height = axisHolder.offsetHeight; | ||||
|         let vis = d3Selection.select(axisHolder) | ||||
|             .append("svg:svg") | ||||
|             .attr("width", "100%") | ||||
|             .attr("height", height); | ||||
|  | ||||
|         this.width = this.$refs.axisHolder.clientWidth; | ||||
|         this.xAxis = d3Axis.axisTop(); | ||||
|         this.dragging = false; | ||||
|  | ||||
|         // draw x axis with labels. CSS is used to position them. | ||||
|         this.axisElement = vis.append("g"); | ||||
|  | ||||
|         this.setViewFromTimeSystem(this.openmct.time.timeSystem()); | ||||
|         this.setScale(); | ||||
|  | ||||
|         //Respond to changes in conductor | ||||
|         this.openmct.time.on("timeSystem", this.setViewFromTimeSystem); | ||||
|         setInterval(this.resize, RESIZE_POLL_INTERVAL); | ||||
|     }, | ||||
|     destroyed() { | ||||
|     } | ||||
|  | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										212
									
								
								src/plugins/timeConductor/ConductorMode.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										212
									
								
								src/plugins/timeConductor/ConductorMode.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,212 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT Web, Copyright (c) 2014-2018, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT Web is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT Web includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
| <template> | ||||
|     <div class="c-ctrl-wrapper c-ctrl-wrapper--menus-up"> | ||||
|         <div class="c-button--menu c-mode-button" | ||||
|              @click="toggleMenu($event)"> | ||||
|             <span class="c-button__label">{{selectedMode.name}}</span> | ||||
|         </div> | ||||
|         <div class="c-menu c-super-menu c-conductor__mode-menu" | ||||
|              v-if="showMenu"> | ||||
|             <div class="c-super-menu__menu"> | ||||
|                 <ul> | ||||
|                     <li v-for="mode in modes" | ||||
|                         :key="mode.key" | ||||
|                         @click="setOption(mode)" | ||||
|                         @mouseover="hoveredMode = mode" | ||||
|                         @mouseleave="hoveredMode = {}" | ||||
|                         class="menu-item-a" | ||||
|                         :class="mode.cssClass"> | ||||
|                         {{mode.name}} | ||||
|                     </li> | ||||
|                 </ul> | ||||
|             </div> | ||||
|             <div class="c-super-menu__item-description"> | ||||
|                 <div :class="['l-item-description__icon', 'bg-' + hoveredMode.cssClass]"></div> | ||||
|                 <div class="l-item-description__name">{{hoveredMode.name}}</div> | ||||
|                 <div class="l-item-description__description">{{hoveredMode.description}}</div> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
| </template> | ||||
|  | ||||
| <style lang="scss"> | ||||
|     @import "~styles/sass-base"; | ||||
|  | ||||
|     .c-conductor__mode-menu { | ||||
|         max-height: 80vh; | ||||
|         max-width: 500px; | ||||
|         min-height: 250px; | ||||
|         z-index: 70; | ||||
|  | ||||
|         [class*="__icon"] { | ||||
|             filter: $colorKeyFilter; | ||||
|         } | ||||
|  | ||||
|         [class*="__item-description"] { | ||||
|             min-width: 200px; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     .is-realtime-mode { | ||||
|         .c-mode-button { | ||||
|             background: $colorTimeBg; | ||||
|  | ||||
|             &:hover { | ||||
|                 background: $colorTimeHov; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| </style> | ||||
|  | ||||
| <script> | ||||
| export default { | ||||
|     inject: ['openmct', 'configuration'], | ||||
|     data: function () { | ||||
|         let activeClock = this.openmct.time.clock(); | ||||
|         if (activeClock !== undefined) { | ||||
|             //Create copy of active clock so the time API does not get reactified. | ||||
|             activeClock = Object.create(activeClock); | ||||
|         } | ||||
|         return { | ||||
|             selectedMode: this.getModeOptionForClock(activeClock), | ||||
|             selectedTimeSystem: JSON.parse(JSON.stringify(this.openmct.time.timeSystem())), | ||||
|             modes: [], | ||||
|             hoveredMode: {}, | ||||
|             showMenu: false | ||||
|         }; | ||||
|     }, | ||||
|     methods: { | ||||
|         loadClocksFromConfiguration() { | ||||
|             let clocks = this.configuration.menuOptions | ||||
|                 .map(menuOption => menuOption.clock) | ||||
|                 .filter(isDefinedAndUnique) | ||||
|                 .map(this.getClock); | ||||
|  | ||||
|             /* | ||||
|              * Populate the modes menu with metadata from the available clocks | ||||
|              * "Fixed Mode" is always first, and has no defined clock | ||||
|              */ | ||||
|             this.modes = [undefined] | ||||
|                 .concat(clocks) | ||||
|                 .map(this.getModeOptionForClock); | ||||
|  | ||||
|             function isDefinedAndUnique(key, index, array) { | ||||
|                 return key!== undefined && array.indexOf(key) === index; | ||||
|             } | ||||
|         }, | ||||
|  | ||||
|         getModeOptionForClock(clock) { | ||||
|             if (clock === undefined) { | ||||
|                 return { | ||||
|                     key: 'fixed', | ||||
|                     name: 'Fixed Timespan Mode', | ||||
|                     description: 'Query and explore data that falls between two fixed datetimes.', | ||||
|                     cssClass: 'icon-tabular' | ||||
|                 } | ||||
|             } else { | ||||
|                 return { | ||||
|                     key: clock.key, | ||||
|                     name: clock.name, | ||||
|                     description: "Monitor streaming data in real-time. The Time " + | ||||
|                     "Conductor and displays will automatically advance themselves based on this clock. " + clock.description, | ||||
|                     cssClass: clock.cssClass || 'icon-clock' | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|  | ||||
|         getClock(key) { | ||||
|             return this.openmct.time.getAllClocks().filter(function (clock) { | ||||
|                 return clock.key === key; | ||||
|             })[0]; | ||||
|         }, | ||||
|  | ||||
|         setOption(option) { | ||||
|             let clockKey = option.key; | ||||
|             if (clockKey === 'fixed') { | ||||
|                 clockKey = undefined; | ||||
|             } | ||||
|  | ||||
|             let configuration = this.getMatchingConfig({ | ||||
|                 clock: clockKey,  | ||||
|                 timeSystem: this.openmct.time.timeSystem().key | ||||
|             }); | ||||
|  | ||||
|             if (configuration === undefined) { | ||||
|                 configuration = this.getMatchingConfig({ | ||||
|                     clock: clockKey | ||||
|                 }); | ||||
|                  | ||||
|                 this.openmct.time.timeSystem(configuration.timeSystem, configuration.bounds); | ||||
|             } | ||||
|              | ||||
|             if (clockKey === undefined) { | ||||
|                 this.openmct.time.stopClock(); | ||||
|             } else { | ||||
|                 this.openmct.time.clock(clockKey, configuration.clockOffsets); | ||||
|             } | ||||
|         }, | ||||
|  | ||||
|         getMatchingConfig(options) { | ||||
|             const matchers = { | ||||
|                 clock(config) { | ||||
|                     return options.clock === config.clock | ||||
|                 }, | ||||
|                 timeSystem(config) { | ||||
|                     return options.timeSystem === config.timeSystem | ||||
|                 } | ||||
|             }; | ||||
|  | ||||
|             function configMatches(config) { | ||||
|                 return Object.keys(options).reduce((match, option) => { | ||||
|                     return match && matchers[option](config); | ||||
|                 }, true); | ||||
|             } | ||||
|  | ||||
|             return this.configuration.menuOptions.filter(configMatches)[0]; | ||||
|         }, | ||||
|  | ||||
|         setViewFromClock(clock) { | ||||
|             this.selectedMode = this.getModeOptionForClock(clock); | ||||
|         }, | ||||
|  | ||||
|         toggleMenu(event) { | ||||
|             this.showMenu = !this.showMenu; | ||||
|  | ||||
|             if (this.showMenu) { | ||||
|                 document.addEventListener('click', this.toggleMenu, true); | ||||
|             } else { | ||||
|                 document.removeEventListener('click', this.toggleMenu, true); | ||||
|             } | ||||
|         }, | ||||
|     }, | ||||
|     mounted: function () { | ||||
|         this.loadClocksFromConfiguration(); | ||||
|  | ||||
|         this.openmct.time.on('clock', this.setViewFromClock); | ||||
|     }, | ||||
|     destroyed: function () { | ||||
|         this.openmct.time.off('clock', this.setViewFromClock); | ||||
|     } | ||||
|  | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										139
									
								
								src/plugins/timeConductor/ConductorModeIcon.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								src/plugins/timeConductor/ConductorModeIcon.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,139 @@ | ||||
| /***************************************************************************** | ||||
| * Open MCT Web, Copyright (c) 2014-2018, United States Government | ||||
| * as represented by the Administrator of the National Aeronautics and Space | ||||
| * Administration. All rights reserved. | ||||
| * | ||||
| * Open MCT Web is licensed under the Apache License, Version 2.0 (the | ||||
| * "License"); you may not use this file except in compliance with the License. | ||||
| * You may obtain a copy of the License at | ||||
| * http://www.apache.org/licenses/LICENSE-2.0. | ||||
| * | ||||
| * Unless required by applicable law or agreed to in writing, software | ||||
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
| * License for the specific language governing permissions and limitations | ||||
| * under the License. | ||||
| * | ||||
| * Open MCT Web includes source code licensed under additional open source | ||||
| * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
| * this source code distribution or the Licensing information page available | ||||
| * at runtime from the About dialog for additional information. | ||||
| *****************************************************************************/ | ||||
| <template> | ||||
|     <div class="c-clock-symbol"> | ||||
|         <div class="hand-little"></div> | ||||
|         <div class="hand-big"></div> | ||||
|     </div> | ||||
| </template> | ||||
|  | ||||
| <style lang="scss"> | ||||
|     @import "~styles/sass-base"; | ||||
|  | ||||
|     @keyframes clock-hands { | ||||
|         0% { transform: translate(-50%, -50%) rotate(0deg); } | ||||
|         100% { transform: translate(-50%, -50%) rotate(360deg);  } | ||||
|     } | ||||
|  | ||||
|     @keyframes clock-hands-sticky { | ||||
|         0% { transform: translate(-50%, -50%) rotate(0deg); } | ||||
|         7% { transform: translate(-50%, -50%) rotate(0deg); } | ||||
|         8% { transform: translate(-50%, -50%) rotate(30deg); } | ||||
|         15% { transform: translate(-50%, -50%) rotate(30deg); } | ||||
|         16% { transform: translate(-50%, -50%) rotate(60deg); } | ||||
|         24% { transform: translate(-50%, -50%) rotate(60deg); } | ||||
|         25% { transform: translate(-50%, -50%) rotate(90deg); } | ||||
|         32% { transform: translate(-50%, -50%) rotate(90deg); } | ||||
|         33% { transform: translate(-50%, -50%) rotate(120deg); } | ||||
|         40% { transform: translate(-50%, -50%) rotate(120deg); } | ||||
|         41% { transform: translate(-50%, -50%) rotate(150deg); } | ||||
|         49% { transform: translate(-50%, -50%) rotate(150deg); } | ||||
|         50% { transform: translate(-50%, -50%) rotate(180deg); } | ||||
|         57% { transform: translate(-50%, -50%) rotate(180deg); } | ||||
|         58% { transform: translate(-50%, -50%) rotate(210deg); } | ||||
|         65% { transform: translate(-50%, -50%) rotate(210deg); } | ||||
|         66% { transform: translate(-50%, -50%) rotate(240deg); } | ||||
|         74% { transform: translate(-50%, -50%) rotate(240deg); } | ||||
|         75% { transform: translate(-50%, -50%) rotate(270deg); } | ||||
|         82% { transform: translate(-50%, -50%) rotate(270deg); } | ||||
|         83% { transform: translate(-50%, -50%) rotate(300deg); } | ||||
|         90% { transform: translate(-50%, -50%) rotate(300deg); } | ||||
|         91% { transform: translate(-50%, -50%) rotate(330deg); } | ||||
|         99% { transform: translate(-50%, -50%) rotate(330deg); } | ||||
|         100% { transform: translate(-50%, -50%) rotate(360deg); } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     .c-clock-symbol { | ||||
|         $c: $colorBtnBg; //$colorObjHdrIc; | ||||
|         $d: 18px; | ||||
|         height: $d; | ||||
|         width: $d; | ||||
|         position: relative; | ||||
|  | ||||
|         &:before { | ||||
|             font-family: symbolsfont; | ||||
|             color: $c; | ||||
|             content: $glyph-icon-brackets; | ||||
|             font-size: $d; | ||||
|             line-height: normal; | ||||
|             display: block; | ||||
|             width: 100%; | ||||
|             height: 100%; | ||||
|             z-index: 1; | ||||
|         } | ||||
|  | ||||
|         // Clock hands | ||||
|         div[class*="hand"] { | ||||
|             $handW: 2px; | ||||
|             $handH: $d * 0.4; | ||||
|             animation-iteration-count: infinite; | ||||
|             animation-timing-function: linear; | ||||
|             transform-origin: bottom; | ||||
|             position: absolute; | ||||
|             height: $handW; | ||||
|             width: $handW; | ||||
|             left: 50%; | ||||
|             top: 50%; | ||||
|             z-index: 2; | ||||
|             &:before { | ||||
|                 background: $c; | ||||
|                 content: ''; | ||||
|                 display: block; | ||||
|                 position: absolute; | ||||
|                 width: 100%; | ||||
|                 bottom: -1px; | ||||
|             } | ||||
|             &.hand-little { | ||||
|                 z-index: 2; | ||||
|                 animation-duration: 12s; | ||||
|                 transform: translate(-50%, -50%) rotate(120deg); | ||||
|                 &:before { | ||||
|                     height: ceil($handH * 0.6); | ||||
|                 } | ||||
|             } | ||||
|             &.hand-big { | ||||
|                 z-index: 1; | ||||
|                 animation-duration: 1s; | ||||
|                 transform: translate(-50%, -50%); | ||||
|                 &:before { | ||||
|                     height: $handH; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Modes | ||||
|         .is-realtime-mode &, | ||||
|         .is-lad-mode & { | ||||
|             &:before { | ||||
|                 // Brackets icon | ||||
|                 color: $colorTime; | ||||
|             } | ||||
|             div[class*="hand"] { | ||||
|                 animation-name: clock-hands; | ||||
|                 &:before { | ||||
|                     background: $colorTime; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| </style> | ||||
							
								
								
									
										140
									
								
								src/plugins/timeConductor/ConductorTimeSystem.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								src/plugins/timeConductor/ConductorTimeSystem.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,140 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT Web, Copyright (c) 2014-2018, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT Web is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT Web includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
| <template> | ||||
|     <div class="c-ctrl-wrapper c-ctrl-wrapper--menus-up" | ||||
|          v-if="selectedTimeSystem.name"> | ||||
|         <div class="c-button--menu c-time-system-button" | ||||
|             :class="selectedTimeSystem.cssClass" | ||||
|              @click="toggleMenu($event)"> | ||||
|             <span class="c-button__label">{{selectedTimeSystem.name}}</span> | ||||
|         </div> | ||||
|         <div class="c-menu" v-if="showMenu"> | ||||
|             <ul> | ||||
|                 <li @click="setTimeSystemFromView(timeSystem)" | ||||
|                     v-for="timeSystem in timeSystems" | ||||
|                     :key="timeSystem.key" | ||||
|                     :class="timeSystem.cssClass"> | ||||
|                     {{timeSystem.name}} | ||||
|                 </li> | ||||
|             </ul> | ||||
|         </div> | ||||
|     </div> | ||||
| </template> | ||||
|  | ||||
| <style lang="scss"> | ||||
|     @import "~styles/sass-base"; | ||||
|  | ||||
|     .is-realtime-mode { | ||||
|         .c-time-system-button { | ||||
|             background: $colorTimeBg; | ||||
|  | ||||
|             &:hover { | ||||
|                 background: $colorTimeHov; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| </style> | ||||
|  | ||||
| <script> | ||||
| export default { | ||||
|     inject: ['openmct', 'configuration'], | ||||
|     data: function () { | ||||
|         let activeClock = this.openmct.time.clock(); | ||||
|  | ||||
|         return { | ||||
|             selectedTimeSystem: JSON.parse(JSON.stringify(this.openmct.time.timeSystem())), | ||||
|             timeSystems: this.getValidTimesystemsForClock(activeClock), | ||||
|             showMenu: false | ||||
|         }; | ||||
|     }, | ||||
|     methods: { | ||||
|         getValidTimesystemsForClock(clock) { | ||||
|             return this.configuration.menuOptions | ||||
|                 .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(); | ||||
|                 let configuration = this.getMatchingConfig({ | ||||
|                     clock: activeClock && activeClock.key,  | ||||
|                     timeSystem: timeSystem.key | ||||
|                 }); | ||||
|                 if (activeClock === undefined) { | ||||
|                     this.openmct.time.timeSystem(timeSystem.key, configuration.bounds); | ||||
|                 } else { | ||||
|                     this.openmct.time.timeSystem(timeSystem.key); | ||||
|                     this.openmct.time.clockOffsets(configuration.clockOffsets); | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|  | ||||
|         getMatchingConfig(options) { | ||||
|             const matchers = { | ||||
|                 clock(config) { | ||||
|                     return options.clock === config.clock | ||||
|                 }, | ||||
|                 timeSystem(config) { | ||||
|                     return options.timeSystem === config.timeSystem | ||||
|                 } | ||||
|             }; | ||||
|  | ||||
|             function configMatches(config) { | ||||
|                 return Object.keys(options).reduce((match, option) => { | ||||
|                     return match && matchers[option](config); | ||||
|                 }, true); | ||||
|             } | ||||
|  | ||||
|             return this.configuration.menuOptions.filter(configMatches)[0]; | ||||
|         }, | ||||
|  | ||||
|         toggleMenu(event) { | ||||
|             this.showMenu = !this.showMenu; | ||||
|  | ||||
|             if (this.showMenu) { | ||||
|                 document.addEventListener('click', this.toggleMenu, true); | ||||
|             } else { | ||||
|                 document.removeEventListener('click', this.toggleMenu, true); | ||||
|             } | ||||
|         }, | ||||
|  | ||||
|         setViewFromTimeSystem(timeSystem) { | ||||
|             this.selectedTimeSystem = timeSystem; | ||||
|         }, | ||||
|  | ||||
|         setViewFromClock(clock) { | ||||
|             let activeClock = this.openmct.time.clock(); | ||||
|             this.timeSystems = this.getValidTimesystemsForClock(activeClock); | ||||
|         } | ||||
|     }, | ||||
|     mounted: function () { | ||||
|         this.openmct.time.on('timeSystem', this.setViewFromTimeSystem); | ||||
|         this.openmct.time.on('clock', this.setViewFromClock); | ||||
|     }, | ||||
|     destroyed: function () { | ||||
|         this.openmct.time.off('timeSystem', this.setViewFromTimeSystem); | ||||
|         this.openmct.time.on('clock', this.setViewFromClock); | ||||
|     } | ||||
|  | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										347
									
								
								src/plugins/timeConductor/DatePicker.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										347
									
								
								src/plugins/timeConductor/DatePicker.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,347 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT Web, Copyright (c) 2014-2018, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT Web is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT Web includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
| <template> | ||||
|     <div class="c-ctrl-wrapper c-ctrl-wrapper--menus-up c-datetime-picker__wrapper" ref="calendarHolder"> | ||||
|         <a class="c-click-icon icon-calendar" | ||||
|            @click="togglePicker()"></a> | ||||
|         <div class="c-menu c-menu--mobile-modal c-datetime-picker" | ||||
|              v-if="showPicker"> | ||||
|             <div class="c-datetime-picker__close-button"> | ||||
|                 <button class="c-click-icon icon-x-in-circle" | ||||
|                         @click="togglePicker()"></button> | ||||
|             </div> | ||||
|             <div class="c-datetime-picker__pager c-pager l-month-year-pager"> | ||||
|                 <div class="c-pager__prev c-click-icon icon-arrow-left" | ||||
|                    @click="changeMonth(-1)"></div> | ||||
|                 <div class="c-pager__month-year">{{model.month}} {{model.year}}</div> | ||||
|                 <div class="c-pager__next c-click-icon icon-arrow-right" | ||||
|                    @click="changeMonth(1)"></div> | ||||
|             </div> | ||||
|             <div class="c-datetime-picker__calendar c-calendar"> | ||||
|                 <ul class="c-calendar__row--header l-cal-row"> | ||||
|                     <li v-for="day in ['Su','Mo','Tu','We','Th','Fr','Sa']" | ||||
|                         :key="day">{{day}}</li> | ||||
|                 </ul> | ||||
|                 <ul class="c-calendar__row--body" | ||||
|                     v-for="(row, index) in table" | ||||
|                     :key="index"> | ||||
|                     <li v-for="(cell, index) in row" | ||||
|                         :key="index" | ||||
|                         @click="select(cell)" | ||||
|                         :class="{ 'is-in-month': isInCurrentMonth(cell), selected: isSelected(cell) }"> | ||||
|                         <div class="c-calendar__day--prime">{{cell.day}}</div> | ||||
|                         <div class="c-calendar__day--sub">{{cell.dayOfYear}}</div> | ||||
|                     </li> | ||||
|                 </ul> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
| </template> | ||||
|  | ||||
| <style lang="scss"> | ||||
|     @import "~styles/sass-base"; | ||||
|  | ||||
|     /******************************************************** PICKER */ | ||||
|     .c-datetime-picker { | ||||
|         @include userSelectNone(); | ||||
|         padding: $interiorMarginLg !important; | ||||
|         display: flex !important; // Override .c-menu display: block; | ||||
|         flex-direction: column; | ||||
|         > * + * { | ||||
|             margin-top: $interiorMargin; | ||||
|         } | ||||
|  | ||||
|         &__close-button { | ||||
|             display: none; // Only show when body.phone, see below. | ||||
|         } | ||||
|  | ||||
|         &__pager { | ||||
|             flex: 0 0 auto; | ||||
|         } | ||||
|  | ||||
|         &__calendar { | ||||
|             border-top: 1px solid $colorInteriorBorder; | ||||
|             flex: 1 1 auto; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     .c-pager { | ||||
|         display: grid; | ||||
|         grid-column-gap: $interiorMargin; | ||||
|         grid-template-rows: 1fr; | ||||
|         grid-template-columns: auto 1fr auto; | ||||
|         align-items: center; | ||||
|  | ||||
|         .c-click-icon { | ||||
|             font-size: 0.8em; | ||||
|         } | ||||
|  | ||||
|         &__month-year { | ||||
|             text-align: center; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /******************************************************** CALENDAR */ | ||||
|     .c-calendar { | ||||
|         display: grid; | ||||
|         grid-template-columns: repeat(7, min-content); | ||||
|         grid-template-rows: auto; | ||||
|         grid-gap: 1px; | ||||
|         height: 100%; | ||||
|  | ||||
|         $mutedOpacity: 0.7; | ||||
|  | ||||
|         ul { | ||||
|             display: contents; | ||||
|             &[class*='--header'] { | ||||
|                 pointer-events: none; | ||||
|                 li { | ||||
|                     opacity: $mutedOpacity; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         li { | ||||
|             display: flex; | ||||
|             flex-direction: column; | ||||
|             justify-content: center !important; | ||||
|             padding: $interiorMargin; | ||||
|  | ||||
|             &.is-in-month { | ||||
|                 background: rgba($colorBodyFg, 0.1); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         &__day { | ||||
|             &--sub { | ||||
|                 opacity: $mutedOpacity; | ||||
|                 font-size: 0.8em; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /******************************************************** MOBILE */ | ||||
|     body.phone { | ||||
|         .c-datetime-picker { | ||||
|             &.c-menu { | ||||
|                 @include modalFullScreen(); | ||||
|             } | ||||
|  | ||||
|             &__close-button { | ||||
|                 display: flex; | ||||
|                 justify-content: flex-end; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         .c-calendar { | ||||
|             grid-template-columns: repeat(7, auto); | ||||
|         } | ||||
|     } | ||||
| </style> | ||||
|  | ||||
| <script> | ||||
| import moment from 'moment'; | ||||
|  | ||||
| const TIME_NAMES = { | ||||
|         'hours': "Hour", | ||||
|         'minutes': "Minute", | ||||
|         'seconds': "Second" | ||||
|     }; | ||||
| const MONTHS = moment.months(); | ||||
| const TIME_OPTIONS = (function makeRanges() { | ||||
|     let arr = []; | ||||
|     while (arr.length < 60) { | ||||
|         arr.push(arr.length); | ||||
|     } | ||||
|     return { | ||||
|         hours: arr.slice(0, 24), | ||||
|         minutes: arr, | ||||
|         seconds: arr | ||||
|     }; | ||||
| }()); | ||||
|  | ||||
| export default { | ||||
|     inject: ['openmct'], | ||||
|     props: { | ||||
|         defaultDateTime: String, | ||||
|         formatter: Object | ||||
|     }, | ||||
|     data: function () { | ||||
|         return { | ||||
|             showPicker: false, | ||||
|             picker: { | ||||
|                 year: undefined, | ||||
|                 month: undefined, | ||||
|                 interacted: false | ||||
|             }, | ||||
|             model: { | ||||
|                 year: undefined, | ||||
|                 month: undefined, | ||||
|             }, | ||||
|             table: undefined, | ||||
|             date: undefined, | ||||
|             time: undefined | ||||
|         } | ||||
|     }, | ||||
|     methods: { | ||||
|         generateTable() { | ||||
|             let m = moment.utc({ year: this.picker.year, month: this.picker.month }).day(0), | ||||
|                 table = [], | ||||
|                 row, | ||||
|                 col; | ||||
|  | ||||
|             for (row = 0; row < 6; row += 1) { | ||||
|                 table.push([]); | ||||
|                 for (col = 0; col < 7; col += 1) { | ||||
|                     table[row].push({ | ||||
|                         year: m.year(), | ||||
|                         month: m.month(), | ||||
|                         day: m.date(), | ||||
|                         dayOfYear: m.dayOfYear() | ||||
|                     }); | ||||
|                     m.add(1, 'days'); // Next day! | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             return table; | ||||
|         }, | ||||
|  | ||||
|         updateViewForMonth() { | ||||
|             this.model.month = MONTHS[this.picker.month]; | ||||
|             this.model.year = this.picker.year; | ||||
|             this.table = this.generateTable(); | ||||
|         }, | ||||
|  | ||||
|         updateFromModel(defaultDateTime) { | ||||
|             let m; | ||||
|  | ||||
|             m = moment.utc(defaultDateTime); | ||||
|  | ||||
|             this.date = { | ||||
|                 year: m.year(), | ||||
|                 month: m.month(), | ||||
|                 day: m.date() | ||||
|             }; | ||||
|             this.time = { | ||||
|                 hours: m.hour(), | ||||
|                 minutes: m.minute(), | ||||
|                 seconds: m.second() | ||||
|             }; | ||||
|  | ||||
|             // Zoom to that date in the picker, but | ||||
|             // only if the user hasn't interacted with it yet. | ||||
|             if (!this.picker.interacted) { | ||||
|                 this.picker.year = m.year(); | ||||
|                 this.picker.month = m.month(); | ||||
|                 this.updateViewForMonth(); | ||||
|             } | ||||
|         }, | ||||
|  | ||||
|         updateFromView() { | ||||
|             let m = moment.utc({ | ||||
|                 year: this.date.year, | ||||
|                 month: this.date.month, | ||||
|                 day: this.date.day, | ||||
|                 hour: this.time.hours, | ||||
|                 minute: this.time.minutes, | ||||
|                 second: this.time.seconds | ||||
|             }); | ||||
|             this.$emit('date-selected', m.valueOf()); | ||||
|         }, | ||||
|  | ||||
|         isInCurrentMonth(cell) { | ||||
|             return cell.month === this.picker.month; | ||||
|         }, | ||||
|  | ||||
|         isSelected(cell) { | ||||
|             let date = this.date || {}; | ||||
|             return cell.day === date.day && | ||||
|                 cell.month === date.month && | ||||
|                 cell.year === date.year; | ||||
|         }, | ||||
|  | ||||
|         select(cell) { | ||||
|             this.date = this.date || {}; | ||||
|             this.date.month = cell.month; | ||||
|             this.date.year = cell.year; | ||||
|             this.date.day = cell.day; | ||||
|             this.updateFromView(); | ||||
|             this.showPicker = false; | ||||
|         }, | ||||
|  | ||||
|         dateEquals(d1, d2) { | ||||
|             return d1.year === d2.year && | ||||
|                 d1.month === d2.month && | ||||
|                 d1.day === d2.day; | ||||
|         }, | ||||
|  | ||||
|         changeMonth(delta) { | ||||
|             this.picker.month += delta; | ||||
|             if (this.picker.month > 11) { | ||||
|                 this.picker.month = 0; | ||||
|                 this.picker.year += 1; | ||||
|             } | ||||
|             if (this.picker.month < 0) { | ||||
|                 this.picker.month = 11; | ||||
|                 this.picker.year -= 1; | ||||
|             } | ||||
|             this.picker.interacted = true; | ||||
|             this.updateViewForMonth(); | ||||
|         }, | ||||
|  | ||||
|         nameFor(key) { | ||||
|             return TIME_NAMES[key]; | ||||
|         }, | ||||
|  | ||||
|         optionsFor(key) { | ||||
|             return TIME_OPTIONS[key]; | ||||
|         }, | ||||
|  | ||||
|         hidePicker(event) { | ||||
|             let path = event.composedPath(); | ||||
|             if (path.indexOf(this.$refs.calendarHolder) === -1) { | ||||
|                 this.showPicker = false; | ||||
|             } | ||||
|         }, | ||||
|          | ||||
|         togglePicker() { | ||||
|             this.showPicker = !this.showPicker; | ||||
|  | ||||
|             if (this.showPicker) { | ||||
|                 document.addEventListener('click', this.hidePicker, { | ||||
|                     capture: true | ||||
|                 }); | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
|     mounted: function () { | ||||
|         this.updateFromModel(this.defaultDateTime); | ||||
|         this.updateViewForMonth(); | ||||
|     }, | ||||
|     destroyed: function () { | ||||
|         document.addEventListener('click', this.hidePicker, { | ||||
|             capture: true | ||||
|         }); | ||||
|     } | ||||
|  | ||||
| } | ||||
| </script> | ||||
| @@ -20,110 +20,108 @@ | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define([], function () { | ||||
| import Conductor from './Conductor.vue'; | ||||
| import Vue from 'vue'; | ||||
|  | ||||
|     function isTruthy(a) { | ||||
|         return !!a; | ||||
| function isTruthy(a) { | ||||
|     return !!a; | ||||
| } | ||||
|  | ||||
| function validateMenuOption(menuOption, index) { | ||||
|     if (menuOption.clock && !menuOption.clockOffsets) { | ||||
|         return "clock-based menuOption at index " + index + " is " + | ||||
|             "missing required property 'clockOffsets'."; | ||||
|     } | ||||
|  | ||||
|     function validateMenuOption(menuOption, index) { | ||||
|         if (menuOption.clock && !menuOption.clockOffsets) { | ||||
|             return "clock-based menuOption at index " + index + " is " + | ||||
|                 "missing required property 'clockOffsets'."; | ||||
|         } | ||||
|         if (!menuOption.timeSystem) { | ||||
|             return "menuOption at index " + index + " is missing " + | ||||
|                 "required property 'timeSystem'."; | ||||
|         } | ||||
|         if (!menuOption.bounds && !menuOption.clock) { | ||||
|             return "fixed-bounds menuOption at index " + index + " is " + | ||||
|                 "missing required property 'bounds'"; | ||||
|         } | ||||
|     if (!menuOption.timeSystem) { | ||||
|         return "menuOption at index " + index + " is missing " + | ||||
|             "required property 'timeSystem'."; | ||||
|     } | ||||
|  | ||||
|     function validateConfiguration(config) { | ||||
|         if (config === undefined || | ||||
|             config.menuOptions === undefined || | ||||
|             config.menuOptions.length === 0) { | ||||
|             return "You must specify one or more 'menuOptions'."; | ||||
|         } | ||||
|         if (config.menuOptions.some(validateMenuOption)) { | ||||
|             return config.menuOptions.map(validateMenuOption) | ||||
|                 .filter(isTruthy) | ||||
|                 .join('\n'); | ||||
|         } | ||||
|         return undefined; | ||||
|     if (!menuOption.bounds && !menuOption.clock) { | ||||
|         return "fixed-bounds menuOption at index " + index + " is " + | ||||
|             "missing required property 'bounds'"; | ||||
|     } | ||||
| } | ||||
|  | ||||
|     function validateRuntimeConfiguration(config, openmct) { | ||||
|         var systems = openmct.time.getAllTimeSystems() | ||||
|             .reduce(function (m, ts) { | ||||
|                 m[ts.key] = ts; | ||||
|                 return m; | ||||
|             }, {}); | ||||
|         var clocks = openmct.time.getAllClocks() | ||||
|             .reduce(function (m, c) { | ||||
|                 m[c.key] = c; | ||||
|                 return m; | ||||
|             }, {}); | ||||
|  | ||||
|         return config.menuOptions.map(function (menuOption, index) { | ||||
|             if (menuOption.timeSystem && !systems[menuOption.timeSystem]) { | ||||
|                 return "menuOption at index " + index + " specifies a " + | ||||
|                         "timeSystem that does not exist: " + menuOption.timeSystem; | ||||
|             } | ||||
|             if (menuOption.clock && !clocks[menuOption.clock]) { | ||||
|                 return "menuOption at index " + index + " specifies a " + | ||||
|                         "clock that does not exist: " + menuOption.clock; | ||||
|             } | ||||
|         }) | ||||
| function validateConfiguration(config) { | ||||
|     if (config === undefined || | ||||
|         config.menuOptions === undefined || | ||||
|         config.menuOptions.length === 0) { | ||||
|         return "You must specify one or more 'menuOptions'."; | ||||
|     } | ||||
|     if (config.menuOptions.some(validateMenuOption)) { | ||||
|         return config.menuOptions.map(validateMenuOption) | ||||
|             .filter(isTruthy) | ||||
|             .join('\n'); | ||||
|     } | ||||
|     return undefined; | ||||
| } | ||||
|  | ||||
|     function throwConfigErrorIfExists(error) { | ||||
|         if (error) { | ||||
|             throw new Error("Invalid Time Conductor Configuration: \n" + | ||||
|                 error + '\n' + | ||||
|                 "https://github.com/nasa/openmct/blob/master/API.md#the-time-conductor"); | ||||
| function validateRuntimeConfiguration(config, openmct) { | ||||
|     var systems = openmct.time.getAllTimeSystems() | ||||
|         .reduce(function (m, ts) { | ||||
|             m[ts.key] = ts; | ||||
|             return m; | ||||
|         }, {}); | ||||
|     var clocks = openmct.time.getAllClocks() | ||||
|         .reduce(function (m, c) { | ||||
|             m[c.key] = c; | ||||
|             return m; | ||||
|         }, {}); | ||||
|  | ||||
|     return config.menuOptions.map(function (menuOption, index) { | ||||
|         if (menuOption.timeSystem && !systems[menuOption.timeSystem]) { | ||||
|             return "menuOption at index " + index + " specifies a " + | ||||
|                     "timeSystem that does not exist: " + menuOption.timeSystem; | ||||
|         } | ||||
|         if (menuOption.clock && !clocks[menuOption.clock]) { | ||||
|             return "menuOption at index " + index + " specifies a " + | ||||
|                     "clock that does not exist: " + menuOption.clock; | ||||
|         } | ||||
|     }).filter(isTruthy).join('\n'); | ||||
| } | ||||
|  | ||||
| function throwIfError(configResult) { | ||||
|     if (configResult) { | ||||
|         throw new Error("Invalid Time Conductor Configuration: \n" + | ||||
|             configResult + '\n' + | ||||
|             "https://github.com/nasa/openmct/blob/master/API.md#the-time-conductor"); | ||||
|     } | ||||
| } | ||||
|  | ||||
|     return function (config) { | ||||
| function mountComponent(openmct, configuration) { | ||||
|     openmct.layout.conductorComponent = Object.create({ | ||||
|         components: { | ||||
|             Conductor | ||||
|         }, | ||||
|         template: "<conductor></conductor>", | ||||
|         provide: { | ||||
|             openmct: openmct, | ||||
|             configuration: configuration | ||||
|         } | ||||
|     }); | ||||
| } | ||||
|  | ||||
|         throwConfigErrorIfExists(validateConfiguration(config)); | ||||
| export default function (config){ | ||||
|  | ||||
|         return function (openmct) { | ||||
|     let configResult = validateConfiguration(config); | ||||
|     throwIfError(configResult); | ||||
|  | ||||
|             openmct.legacyExtension('constants', { | ||||
|                 key: 'CONDUCTOR_CONFIG', | ||||
|                 value: config, | ||||
|                 priority: 'mandatory' | ||||
|             }); | ||||
|     return function (openmct) { | ||||
|  | ||||
|             openmct.legacyRegistry.enable('platform/features/conductor/core'); | ||||
|             openmct.legacyRegistry.enable('platform/features/conductor/compatibility'); | ||||
|         openmct.on('start', function () { | ||||
|             configResult = validateRuntimeConfiguration(config, openmct); | ||||
|             throwIfError(configResult); | ||||
|  | ||||
|             openmct.on('start', function () { | ||||
|             var defaults = config.menuOptions[0]; | ||||
|             if (defaults.clock) { | ||||
|                 openmct.time.clock(defaults.clock, defaults.clockOffsets); | ||||
|                 openmct.time.timeSystem(defaults.timeSystem, openmct.time.bounds()); | ||||
|             } else { | ||||
|                 openmct.time.timeSystem(defaults.timeSystem, defaults.bounds); | ||||
|             } | ||||
|  | ||||
|                 throwConfigErrorIfExists(validateRuntimeConfiguration(config, openmct)); | ||||
|             mountComponent(openmct, config); | ||||
|  | ||||
|                 /* | ||||
|                  On app startup, default the conductor if not already set. | ||||
|                  */ | ||||
|                 if (openmct.time.timeSystem() !== undefined) { | ||||
|                     return; | ||||
|                 } | ||||
|  | ||||
|                 var defaults = config.menuOptions[0]; | ||||
|                 if (defaults.clock) { | ||||
|                     openmct.time.clock(defaults.clock, defaults.clockOffsets); | ||||
|                     openmct.time.timeSystem(defaults.timeSystem, openmct.time.bounds()); | ||||
|                 } else { | ||||
|                     openmct.time.timeSystem(defaults.timeSystem, defaults.bounds); | ||||
|                 } | ||||
|  | ||||
|             }); | ||||
|         }; | ||||
|         }); | ||||
|     }; | ||||
| }); | ||||
| }; | ||||
|   | ||||
| @@ -20,35 +20,47 @@ | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
| 
 | ||||
| define([], function () { | ||||
| import moment from 'moment'; | ||||
| 
 | ||||
| export default function multiFormat(date) { | ||||
|     var momentified = moment.utc(date); | ||||
|     /** | ||||
|      * Formatter for basic numbers. Provides basic support for non-UTC | ||||
|      * numbering systems | ||||
|      * Uses logic from d3 Time-Scales, v3 of the API. See | ||||
|      * https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Scales.md
 | ||||
|      * | ||||
|      * @implements {Format} | ||||
|      * @constructor | ||||
|      * @memberof platform/commonUI/formats | ||||
|      * Licensed | ||||
|      */ | ||||
|     function NumberFormat() { | ||||
|         this.key = 'number'; | ||||
|     var format = [ | ||||
|         [".SSS", function (m) { | ||||
|             return m.milliseconds(); | ||||
|         }], | ||||
|         [":ss", function (m) { | ||||
|             return m.seconds(); | ||||
|         }], | ||||
|         ["HH:mm", function (m) { | ||||
|             return m.minutes(); | ||||
|         }], | ||||
|         ["HH:mm", function (m) { | ||||
|             return m.hours(); | ||||
|         }], | ||||
|         ["ddd DD", function (m) { | ||||
|             return m.days() && | ||||
|                 m.date() !== 1; | ||||
|         }], | ||||
|         ["MMM DD", function (m) { | ||||
|             return m.date() !== 1; | ||||
|         }], | ||||
|         ["MMMM", function (m) { | ||||
|             return m.month(); | ||||
|         }], | ||||
|         ["YYYY", function () { | ||||
|             return true; | ||||
|         }] | ||||
|     ].filter(function (row) { | ||||
|         return row[1](momentified); | ||||
|     })[0][0]; | ||||
| 
 | ||||
|     if (format !== undefined) { | ||||
|         return moment.utc(date).format(format); | ||||
|     } | ||||
| 
 | ||||
|     NumberFormat.prototype.format = function (value) { | ||||
|         if (isNaN(value)) { | ||||
|             return ''; | ||||
|         } else { | ||||
|             return '' + value; | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     NumberFormat.prototype.parse = function (text) { | ||||
|         return parseFloat(text); | ||||
|     }; | ||||
| 
 | ||||
|     NumberFormat.prototype.validate = function (text) { | ||||
|         return !isNaN(text); | ||||
|     }; | ||||
| 
 | ||||
|     return NumberFormat; | ||||
| }); | ||||
| } | ||||
| @@ -58,7 +58,7 @@ define(['EventEmitter'], function (EventEmitter) { | ||||
|             this.selected[0].element.classList.remove('s-selected'); | ||||
|         } | ||||
|  | ||||
|         if (this.selected[1]) { | ||||
|         if (this.selected[1] && this.selected[1].element) { | ||||
|             this.selected[1].element.classList.remove('s-selected-parent'); | ||||
|         } | ||||
|  | ||||
| @@ -66,7 +66,7 @@ define(['EventEmitter'], function (EventEmitter) { | ||||
|             selectable[0].element.classList.add('s-selected'); | ||||
|         } | ||||
|  | ||||
|         if (selectable[1]) { | ||||
|         if (selectable[1] && selectable[1].element) { | ||||
|             selectable[1].element.classList.add('s-selected-parent'); | ||||
|         } | ||||
|  | ||||
| @@ -132,7 +132,7 @@ define(['EventEmitter'], function (EventEmitter) { | ||||
|         } | ||||
|  | ||||
|         return function () { | ||||
|             element.removeEventListener('click', capture); | ||||
|             element.removeEventListener('click', capture, true); | ||||
|             element.removeEventListener('click', selectCapture); | ||||
|  | ||||
|             if (unlisten) { | ||||
|   | ||||
| @@ -1,3 +1,25 @@ | ||||
| /***************************************************************************** | ||||
|  * 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 "constants"; | ||||
|  | ||||
| // Functions | ||||
| @@ -21,11 +43,12 @@ $colorStatusBarFg: #999; | ||||
| $colorStatusBarFgHov: #aaa; | ||||
| $colorKey: #0099cc; | ||||
| $colorKeyFilter: brightness(0.9) sepia(1) hue-rotate(145deg) saturate(6); | ||||
| $colorKeyFilterHov: brightness(1) sepia(1) hue-rotate(145deg) saturate(7); | ||||
| $colorKeySelectedBg: $colorKey; | ||||
| $colorKeyFg: #fff; | ||||
| $colorKeyHov: #00c0f6; | ||||
| $colorEditAreaBg: #eafaff; | ||||
| $colorEditAreaFg: #4bb1c7; | ||||
| $colorEditAreaBg: #eafaff; // Deprecated, use $editColor instead | ||||
| $colorEditAreaFg: #4bb1c7; // Deprecated, use $editColor instead | ||||
| $colorInteriorBorder: rgba($colorBodyFg, 0.2); | ||||
| $colorA: #999; | ||||
| $colorAHov: $colorKey; | ||||
| @@ -37,6 +60,16 @@ $smallCr: 2px; | ||||
| $overlayCr: 11px; | ||||
| $shdwTextSubtle: rgba(black, 0.2) 0 1px 2px; | ||||
|  | ||||
| // Variations | ||||
| $colorBodyBgSubtle: pullForward($colorBodyBg, 5%); | ||||
| $colorBodyBgSubtleHov: pushBack($colorKey, 50%); | ||||
| $colorKeySubtle: pushBack($colorKey, 50%); | ||||
| $colorTime: #618cff; | ||||
| $colorTimeBg: $colorTime; | ||||
| $colorTimeFg: $colorBodyBg; | ||||
| $colorTimeHov: pushBack($colorTime, 5%); | ||||
| $colorTimeSubtle: pushBack($colorTime, 20%); | ||||
|  | ||||
| // Buttons and Controls | ||||
| $btnPad: $interiorMargin, $interiorMargin * 1.25; | ||||
| $colorBtnBg: #aaaaaa; | ||||
| @@ -49,6 +82,9 @@ $colorBtnMajorBg: $colorKey; | ||||
| $colorBtnMajorBgHov: $colorKeyHov; | ||||
| $colorBtnMajorFg: $colorKeyFg; | ||||
| $colorBtnMajorFgHov: pushBack($colorBtnMajorFg, $hoverRatioPercent); | ||||
| $colorBtnCautionBg: #f16f6f; | ||||
| $colorBtnCautionBgHov: #f1504e; | ||||
| $colorBtnCautionFg: $colorBtnFg; | ||||
| $colorClickIcon: $colorKey; | ||||
| $colorClickIconHov: $colorKeyHov; | ||||
| $colorToggleIcon: rgba($colorClickIcon, 0.5); | ||||
| @@ -69,7 +105,7 @@ $sliderColorRangeValHovFg: $colorBodyFg; | ||||
| $sliderKnobW: 15px; | ||||
| $sliderKnobR: 2px; | ||||
| $timeControllerToiLineColor: $colorBodyFg; | ||||
| $timeControllerToiLineColorHov: #0052b5; | ||||
| $timeControllerToiLineColorHov: $colorTime; | ||||
| $colorTransLucBg: #666; // Used as a visual blocking element over variable backgrounds, like imagery | ||||
| $createBtnTextTransform: uppercase; | ||||
|  | ||||
| @@ -82,17 +118,25 @@ $colorDiagnostic: #a4b442; | ||||
| $colorCommand: #3693bd; | ||||
| $colorInfo: #2294a2; | ||||
| $colorOk: #33cc33; | ||||
| $colorIconLink: #49dedb; | ||||
| $colorIconAlias: #4af6f3; | ||||
| $colorIconAliasForKeyFilter: #aaa; | ||||
| $colorPausedBg: #ff9900; | ||||
| $colorPausedFg: #fff; | ||||
| $colorCreateBtn: $colorKey; | ||||
| $colorGridLines: rgba(#000, 0.05); | ||||
| $colorInvokeMenu: #fff; | ||||
| $colorObjHdrTxt: $colorBodyFg; | ||||
| $colorObjHdrIc: lighten($colorObjHdrTxt, 30%); | ||||
| $colorTick: rgba(black, 0.2); | ||||
| $colorSelectableSelectedPrimary: $colorKey; | ||||
| $colorSelectableHov: rgba($colorBodyFg, 0.4); | ||||
|  | ||||
| $editColor: #00c7c3; | ||||
| $browseBorderSelectableHov: 1px dotted rgba($colorBodyFg, 0.2); | ||||
| $browseShdwSelectableHov: rgba($colorBodyFg, 0.2) 0 0 3px; | ||||
| $browseBorderSelected: 1px solid rgba($colorBodyFg, 0.6); | ||||
| $editBorderSelectable: 1px dotted rgba($editColor, 1); | ||||
| $editBorderSelectableHov: 1px dashed rgba($editColor, 1); | ||||
| $editBorderSelected: 1px solid $editColor; | ||||
| $editBorderDrilledIn: 1px dashed #ff4d9a; | ||||
| $colorGridLines: rgba($editColor, 0.2); | ||||
|  | ||||
| // Menus | ||||
| $colorMenuBg: pushBack($colorBodyBg, 10%); | ||||
| @@ -107,6 +151,11 @@ $colorCreateMenuLgIcon: $colorKey; | ||||
| $colorCreateMenuText: $colorBodyFg; | ||||
| $menuItemPad: ($interiorMargin, nth($btnPad, 2)); | ||||
|  | ||||
| // Palettes and Swatches | ||||
| $paletteItemBorderOuterColorSelected: black; | ||||
| $paletteItemBorderInnerColorSelected: white; | ||||
| $paletteItemBorderInnerColor: rgba($paletteItemBorderOuterColorSelected, 0.3); | ||||
|  | ||||
| // Form colors | ||||
| $colorCheck: $colorKey; | ||||
| $colorFormRequired: $colorKey; | ||||
| @@ -123,6 +172,9 @@ $colorInputPlaceholder: pushBack($colorBodyFg, 20%); | ||||
| $colorFormText: pushBack($colorBodyFg, 10%); | ||||
| $colorInputIcon: pushBack($colorBodyFg, 25%); | ||||
| $colorFieldHint: pullForward($colorBodyFg, 40%); | ||||
| $shdwInput: inset rgba(black, 0.4) 0 0 1px; | ||||
| $shdwInputHov: inset rgba(black, 0.7) 0 0 1px; | ||||
| $shdwInputFoc: inset rgba(black, 0.7) 0 0 3px; | ||||
|  | ||||
| // Inspector | ||||
| $colorInspectorBg: pullForward($colorBodyBg, 5%); | ||||
| @@ -172,9 +224,9 @@ $colorThumbsBubbleFg: pullForward($colorBodyFg, 10%); | ||||
| $colorThumbsBubbleBg: pullForward($colorBodyBg, 10%); | ||||
|  | ||||
| // Overlay | ||||
| $colorOvrBlocker: rgba(black, 0.7);// | ||||
| $colorOvrBg: $colorBodyBg; | ||||
| $colorOvrFg: $colorBodyFg; | ||||
| $colorOvrBlocker: rgba(black, 0.7); // Used | ||||
| $colorOvrBg: $colorBodyBg; // Used | ||||
| $colorOvrFg: $colorBodyFg; // Used | ||||
| $colorOvrBtnBg: pullForward($colorOvrBg, 40%); | ||||
| $colorOvrBtnFg: #fff; | ||||
| $colorFieldHintOverlay: pullForward($colorOvrBg, 40%); | ||||
| @@ -182,7 +234,9 @@ $durLargeViewExpand: 250ms; | ||||
|  | ||||
| // Items | ||||
| $colorItemBg: #ddd; | ||||
| $colorItemBgHov: pullForward($colorItemBg, $hoverRatioPercent * 0.7); | ||||
| $colorItemBgHov: rgba($colorKey, 0.1); //pushBack($colorItemBg, $hoverRatioPercent * 0.4); | ||||
| $colorListItemBg: transparent; | ||||
| $colorListItemBgHov: rgba($colorKey, 0.1); | ||||
| $colorItemFg: $colorBodyFg; | ||||
| $colorItemFgDetails: pushBack($colorItemFg, 15%); | ||||
| $colorItemIc: $colorKey; | ||||
| @@ -278,12 +332,6 @@ $colorCalCellSelectedBg: $colorItemTreeSelectedBg; | ||||
| $colorCalCellSelectedFg: $colorItemTreeSelectedFg; | ||||
| $colorCalCellInMonthBg: pullForward($colorMenuBg, 5%); | ||||
|  | ||||
| // Palettes | ||||
| $colorPaletteFg: pullForward($colorMenuBg, 30%); | ||||
| $colorPaletteSelected: #333; | ||||
| $shdwPaletteFg: none; | ||||
| $shdwPaletteSelected: inset 0 0 0 1px #fff; | ||||
|  | ||||
| // About Screen | ||||
| $colorAboutLink: #84b3ff; | ||||
|  | ||||
| @@ -292,8 +340,10 @@ $colorLoadingFg: $colorAlt1; | ||||
| $colorLoadingBg: rgba($colorLoadingFg, 0.1); | ||||
|  | ||||
| // Transitions | ||||
| $transIn: all 50ms ease-in; | ||||
| $transOut: all 250ms ease-out; | ||||
| $transIn: all 50ms ease-in-out; | ||||
| $transOut: all 250ms ease-in-out; | ||||
| $transInBounce: all 200ms cubic-bezier(.47,.01,.25,1.5); | ||||
| $transInBounceBig: all 300ms cubic-bezier(.2,1.6,.6,3); | ||||
|  | ||||
| // Discrete items, like Notebook entries, Widget rules | ||||
| @mixin discreteItem() { | ||||
|   | ||||
| @@ -1,3 +1,25 @@ | ||||
| /***************************************************************************** | ||||
|  * 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. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| /************************** PATHS */ | ||||
| // Paths need to be relative to /platform/commonUI/theme/<theme-name>/css/ directory | ||||
| $dirFonts: 'fonts/'; | ||||
| @@ -18,6 +40,9 @@ $inputTextP: $inputTextPTopBtm $inputTextPLeftRight; | ||||
| $menuLineH: 1.5rem; | ||||
| $treeItemIndent: 16px; | ||||
| $treeTypeIconW: 18px; | ||||
| $overlayOuterMarginLg: 5%; | ||||
| $overlayOuterMarginDialog: 20%; | ||||
| $overlayInnerMargin: 25px; | ||||
|  | ||||
| /*************** Items */ | ||||
| $itemPadLR: 5px; | ||||
| @@ -27,6 +52,9 @@ $tabularHeaderH: 22px; | ||||
| $tabularTdPadLR: $itemPadLR; | ||||
| $tabularTdPadTB: 2px; | ||||
|  | ||||
| /************************** MOBILE */ | ||||
| $mobileMenuIconD: 34px; // Used | ||||
| $mobileTreeItemH: 35px; // Used | ||||
|  | ||||
| /************************** VISUAL */ | ||||
| $controlDisabledOpacity: 0.3; | ||||
| @@ -96,7 +124,7 @@ $glyph-icon-pointer-right: '\e1028'; | ||||
| $glyph-icon-refresh: '\e1029'; | ||||
| $glyph-icon-save: '\e1030'; | ||||
| $glyph-icon-sine: '\e1031'; | ||||
| $glyph-icon-T: '\e1032'; | ||||
| $glyph-icon-font: '\e1032'; | ||||
| $glyph-icon-thumbs-strip: '\e1033'; | ||||
| $glyph-icon-two-parts-both: '\e1034'; | ||||
| $glyph-icon-two-parts-one-only: '\e1035'; | ||||
| @@ -113,7 +141,7 @@ $glyph-icon-frame-show: '\e1045'; | ||||
| $glyph-icon-frame-hide: '\e1046'; | ||||
| $glyph-icon-import: '\e1047'; | ||||
| $glyph-icon-export: '\e1048'; | ||||
| $glyph-icon-minimize: '\e1049'; // 12px only | ||||
| $glyph-icon-font-size: '\e1049'; | ||||
| $glyph-icon-activity: '\e1100'; | ||||
| $glyph-icon-activity-mode: '\e1101'; | ||||
| $glyph-icon-autoflow-tabular: '\e1102'; | ||||
| @@ -152,7 +180,7 @@ $glyph-icon-notebook: '\e1131'; | ||||
| $bg-icon-activity: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23666666' d='M288 32H160l160 160H174.872C152.74 153.742 111.377 128 64 128H0v256h64c47.377 0 88.74-25.742 110.872-64H320L160 480h128l224-224L288 32z'/%3e%3c/svg%3e"); | ||||
| $bg-icon-activity-mode: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23666666' d='M256 0C148.6 0 56.6 66.2 18.6 160H64c28.4 0 54 12.4 71.5 32H256l-96-96h128l160 160-160 160H160l96-96H135.5C118 339.6 92.4 352 64 352H18.6c38 93.8 129.9 160 237.4 160 141.4 0 256-114.6 256-256S397.4 0 256 0z'/%3e%3c/svg%3e"); | ||||
| $bg-icon-autoflow-tabular: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23666666' d='M96 0C43.2 0 0 43.2 0 96v320c0 52.8 43.2 96 96 96h32V0H96zM192 0h128v512H192zM416 0h-32v352h128V96c0-52.8-43.2-96-96-96z'/%3e%3c/svg%3e"); | ||||
| $bg-icon-clock: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23666666' d='M256 0C114.62 0 0 114.62 0 256s114.62 256 256 256 256-114.62 256-256S397.38 0 256 0zm135 345a36 36 0 0 1-31.21 18 35.83 35.83 0 0 1-18-4.83l-110.85-64-.14-.08q-.6-.35-1.19-.73l-.43-.28-.93-.64-.63-.45-.63-.48-.85-.67-.32-.27c-.36-.3-.72-.61-1.07-.92a35.76 35.76 0 0 1-6.52-7.9c-.14-.23-.27-.47-.41-.7s-.29-.49-.43-.74a35.75 35.75 0 0 1-3.58-9.59v-.06c-.1-.46-.19-.92-.27-1.38 0-.14-.05-.28-.08-.42-.06-.35-.11-.71-.15-1.07s-.07-.52-.1-.79-.05-.51-.07-.77l-.09-1.12v-.52-1.39V81a36 36 0 0 1 36-36 36 36 0 0 1 36 36v161.22l92.85 53.61A36 36 0 0 1 391 345z' fill='%2300a14b'/%3e%3c/svg%3e"); | ||||
| $bg-icon-clock: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23666666' d='M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256 256-114.6 256-256S397.4 0 256 0zm135 345c-6.4 11.1-18.3 18-31.2 18-6.3 0-12.5-1.7-18-4.8l-110.9-64-.1-.1c-.4-.2-.8-.5-1.2-.7l-.4-.3-.9-.6-.6-.5-.6-.5-.9-.7-.3-.3c-.4-.3-.7-.6-1.1-.9-2.5-2.3-4.7-5-6.5-7.9-.1-.2-.3-.5-.4-.7s-.3-.5-.4-.7c-1.6-3-2.9-6.2-3.6-9.6v-.1c-.1-.5-.2-.9-.3-1.4 0-.1 0-.3-.1-.4-.1-.3-.1-.7-.1-1.1s-.1-.5-.1-.8 0-.5-.1-.8-.1-.8-.1-1.1v-.5-1.4V81c0-19.9 16.1-36 36-36s36 16.1 36 36v161.2l92.9 53.6c17.1 10 22.9 32 13 49.2z'/%3e%3c/svg%3e"); | ||||
| $bg-icon-database: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23666666' d='M256 256C114.615 256 0 213.019 0 160v256c0 53.019 114.615 96 256 96s256-42.981 256-96V160c0 53.019-114.615 96-256 96z'/%3e%3cellipse fill='%23666666' cx='256' cy='96' rx='256' ry='96'/%3e%3c/svg%3e"); | ||||
| $bg-icon-database-query: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23666666' d='M341.76 409.643C316.369 423.871 287.118 432 256 432c-97.047 0-176-78.953-176-176S158.953 80 256 80s176 78.953 176 176c0 31.118-8.129 60.369-22.357 85.76l95.846 95.846C509.747 430.661 512 423.429 512 416V96c0-53.019-114.615-96-256-96S0 42.981 0 96v320c0 53.019 114.615 96 256 96 63.055 0 120.774-8.554 165.388-22.73l-79.628-79.627z'/%3e%3cpath fill='%23666666' d='M176 256c0 44.112 35.888 80 80 80s80-35.888 80-80-35.888-80-80-80-80 35.888-80 80z'/%3e%3c/svg%3e"); | ||||
| $bg-icon-dataset: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23666666' d='M448 96H288l-54.6-54.6-18.7-18.7C202.2 10.2 177.6 0 160 0H32C14.4 0 0 14.4 0 32v192c0-35.2 28.8-64 64-64h384c35.2 0 64 28.8 64 64v-64c0-35.2-28.8-64-64-64zM448 224H64c-35.2 0-64 28.8-64 64v160c0 35.2 28.8 64 64 64h384c35.2 0 64-28.8 64-64V288c0-35.2-28.8-64-64-64zM160 448H96V288h64v160zm128 0h-64V288h64v160zm128 0h-64V288h64v160z'/%3e%3c/svg%3e"); | ||||
|   | ||||
| @@ -1,12 +1,33 @@ | ||||
| /******************************************************** BUTTONS */ | ||||
| %c-control { | ||||
|     @include userSelectNone(); | ||||
| /***************************************************************************** | ||||
|  * 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. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| /******************************************************** PLACEHOLDERS */ | ||||
| @mixin cControl() { | ||||
|     $fs: 1em; | ||||
|     @include userSelectNone(); | ||||
|     display: inline-flex; | ||||
|     align-items: center; | ||||
|     font-size: $fs; | ||||
|     justify-content: start; | ||||
|     cursor: pointer; | ||||
|     justify-content: center; | ||||
|     overflow: hidden; | ||||
|  | ||||
|     &:before, | ||||
|     &:after { | ||||
| @@ -20,17 +41,19 @@ | ||||
|     } | ||||
|  | ||||
|     [class*="__label"] { | ||||
|         @include ellipsize(); | ||||
|         display: block; | ||||
|         line-height: $fs; // Remove effect on top and bottom padding | ||||
|         font-size: $fs; | ||||
|     } | ||||
|  | ||||
| } | ||||
|  | ||||
| %c-button { | ||||
|     @extend %c-control; | ||||
| @mixin cButton() { | ||||
|     @include cControl(); | ||||
|     background: $colorBtnBg; | ||||
|     border-radius: $controlCr; | ||||
|     color: $colorBtnFg; | ||||
|     cursor: pointer; | ||||
|     padding: nth($btnPad, 1) nth($btnPad, 2); | ||||
|  | ||||
|     &:hover { | ||||
| @@ -47,119 +70,124 @@ | ||||
|             color: $colorBtnMajorFgHov; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     &[class*='--caution'] { | ||||
|         background: $colorBtnCautionBg; | ||||
|         color: $colorBtnCautionFg; | ||||
|  | ||||
|         &:hover { | ||||
|             background: $colorBtnCautionBgHov; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| /********* Buttons */ | ||||
| // Optionally can include icon in :before via markup | ||||
| .c-button, | ||||
| button { | ||||
|     @extend %c-button; | ||||
| } | ||||
|  | ||||
| /********* Icon Buttons */ | ||||
| .c-icon-button { | ||||
| @mixin cClickIcon() { | ||||
|     // A clickable element that just includes the icon, no background | ||||
|     // Padding is included to facilitate a bigger hit area | ||||
|     // Make the icon bigger relative to its container | ||||
|     @extend %c-control; | ||||
|     @include cControl(); | ||||
|     $pLR: 3px; | ||||
|     $pTB: 3px; | ||||
|     border-radius: $controlCr; | ||||
|     color: $colorKey; | ||||
|     font-size: $fontBaseSize * 1.2; | ||||
|     cursor: pointer; | ||||
|     padding: $pTB $pLR ; | ||||
|  | ||||
|     &:hover { | ||||
|         background: rgba($colorKey, 0.2); | ||||
|     } | ||||
|  | ||||
|     &:before { | ||||
|         font-size: 1.1em; | ||||
|     &:before, | ||||
|     *:before { | ||||
|         // *:before handles any nested containers that may contain glyph elements | ||||
|         // Needed for c-togglebutton. | ||||
|         font-size: 1.3em; | ||||
|     } | ||||
| } | ||||
|  | ||||
| /********* Button Sets */ | ||||
| .c-button-set { | ||||
|     // Buttons are smashed together with minimal margin | ||||
|     // c-buttons don't have border-radius between buttons, creates a tool-button-strip look | ||||
|     // c-icon-buttons get grouped more closely together | ||||
|     // When one set is adjacent to another, provides a divider between | ||||
| @mixin cCtrlWrapper { | ||||
|     // Provides a wrapper around  buttons and other controls | ||||
|     // Contains control and provides positioning context for contained menu/palette. | ||||
|     // Wraps --menu elements, contains button and menu | ||||
|     overflow: visible; | ||||
|  | ||||
|     display: inline-flex; | ||||
|     .c-menu { | ||||
|         // Default position of contained menu | ||||
|         top: 100%; left: 0; | ||||
|     } | ||||
|  | ||||
|     &[class*='--menus-up'] { | ||||
|         .c-menu { | ||||
|             top: auto; bottom: 100%; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     &[class*='--menus-left'] { | ||||
|         .c-menu { | ||||
|             left: auto; right: 0; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| /********* Buttons */ | ||||
| // Optionally can include icon in :before via markup | ||||
| button { | ||||
|     @include htmlInputReset(); | ||||
| } | ||||
|  | ||||
| .c-button, | ||||
| .c-button--menu { | ||||
|     @include cButton(); | ||||
| } | ||||
|  | ||||
| .c-button--menu { | ||||
|     $m: $interiorMargin; | ||||
|  | ||||
|     &:before, | ||||
|     > * { | ||||
|         // Assume buttons are immediate descendants | ||||
|         flex: 0 0 auto; | ||||
|  | ||||
|         &[class^="c-button"] { | ||||
|             // Only apply the following to buttons that have background, eg. c-button | ||||
|             border-radius: 0; | ||||
|  | ||||
|             + * { | ||||
|                 margin-left: 1px; | ||||
|             } | ||||
|  | ||||
|             &:first-child { | ||||
|                 border-top-left-radius: $controlCr; | ||||
|                 border-bottom-left-radius: $controlCr; | ||||
|             } | ||||
|  | ||||
|             &:last-child { | ||||
|                 border-top-right-radius: $controlCr; | ||||
|                 border-bottom-right-radius: $controlCr; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     + .c-button-set { | ||||
|         $m: $interiorMarginSm; | ||||
|         &:before { | ||||
|             content: ''; | ||||
|             display: block; | ||||
|             width: $m; | ||||
|             border-right: 1px solid $colorInteriorBorder; | ||||
|             margin-right: $m; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| /********* Menu Buttons */ | ||||
| // Always includes :after dropdown arrow | ||||
| // Optionally can include icon in :before | ||||
| // Default menu position is down and to the right | ||||
| // Apply c-menu-button--menus-up and c-menu-button--menus-left to --w wrapper element to modify menu position | ||||
| .c-menu-button { | ||||
|     $m: $interiorMarginSm; | ||||
|     @extend %c-button; | ||||
|  | ||||
|     &:before { | ||||
|         margin-right: $m; | ||||
|     } | ||||
|  | ||||
|     &:after { | ||||
|         content: $glyph-icon-arrow-down; | ||||
|         font-family: symbolsfont; | ||||
|         margin-left: $m; | ||||
|         opacity: 0.5; | ||||
|     } | ||||
| } | ||||
|  | ||||
|     &--w { | ||||
|         // Wraps c-menu-button, contains button and menu | ||||
|         .c-menu { | ||||
|             // Default position | ||||
|             top: 100%; left: 0; | ||||
| /********* Icon Buttons */ | ||||
| .c-click-icon { | ||||
|     @include cClickIcon(); | ||||
|  | ||||
|     &--menu { | ||||
|         &:after { | ||||
|             content: $glyph-icon-arrow-down; | ||||
|             font-family: symbolsfont; | ||||
|             font-size: 0.6em; | ||||
|             margin-left: floor($interiorMarginSm * 0.8); | ||||
|             opacity: 0.5; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     &--swatched { | ||||
|         // Color control, show swatch element | ||||
|         display: flex; | ||||
|         flex-flow: column nowrap; | ||||
|         align-items: center; | ||||
|         justify-content: center; | ||||
|  | ||||
|         > [class*='swatch'] { | ||||
|             box-shadow: inset rgba(black, 0.2) 0 0 1px; | ||||
|             flex: 0 0 auto; | ||||
|             height: 4px; | ||||
|             width: 100%; | ||||
|             margin-top: 1px; | ||||
|         } | ||||
|  | ||||
|         &.c-menu-button--menus-up { | ||||
|             .c-menu { | ||||
|                 top: auto; bottom: 100%; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         &.c-menu-button--menus-left { | ||||
|             .c-menu { | ||||
|                 left: auto; right: 0; | ||||
|             } | ||||
|         &:before { | ||||
|             // Reduce size of icon to make a bit of room | ||||
|             flex: 1 1 auto; | ||||
|             font-size: 1.1em; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -169,7 +197,7 @@ button { | ||||
| // Provides a downward arrow icon that when clicked displays a context menu | ||||
| // Always placed AFTER an element | ||||
| .c-disclosure-button { | ||||
|     @extend .c-icon-button; | ||||
|     @include cClickIcon(); | ||||
|     margin-left: $interiorMarginSm; | ||||
|  | ||||
|     &:before { | ||||
| @@ -208,11 +236,10 @@ button { | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| /******************************************************** FORM ELEMENTS */ | ||||
| /********* Inline inputs */ | ||||
| // A text input or contenteditable element that indicates edit affordance on hover and looks like an input on focus | ||||
| .c-input-inline { | ||||
|     // A text input or contenteditable element that indicates edit affordance on hover and looks like an input on focus | ||||
|     @include input-base(); | ||||
|     border: 1px solid transparent; | ||||
|     display: block !important; | ||||
| @@ -241,6 +268,48 @@ button { | ||||
|     } | ||||
| } | ||||
|  | ||||
| .c-labeled-input { | ||||
|     // An input used in the Toolbar | ||||
|     // Assumes label is before the input | ||||
|     @include cControl(); | ||||
|  | ||||
|     input { | ||||
|         margin-left: $interiorMarginSm; | ||||
|     } | ||||
| } | ||||
|  | ||||
| /******************************************************** HYPERLINKS AND HYPERLINK BUTTONS */ | ||||
| .c-hyperlink { | ||||
|     &--link { | ||||
|         color: $colorKey; | ||||
|     } | ||||
|  | ||||
|     &--button { | ||||
|         @include cButton(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| .c-labeled-input { | ||||
|     // An input used in the Toolbar | ||||
|     // Assumes label is before the input | ||||
|     @include cControl(); | ||||
|  | ||||
|     input { | ||||
|         margin-left: $interiorMarginSm; | ||||
|     } | ||||
| } | ||||
|  | ||||
| /******************************************************** HYPERLINKS AND HYPERLINK BUTTONS */ | ||||
| .c-hyperlink { | ||||
|     &--link { | ||||
|         color: $colorKey; | ||||
|     } | ||||
|  | ||||
|     &--button { | ||||
|         @include cButton(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| /******************************************************** MENUS */ | ||||
| @mixin menuOuter() { | ||||
|     border-radius: $basicCr; | ||||
| @@ -256,8 +325,10 @@ button { | ||||
| @mixin menuInner() { | ||||
|     color: $colorMenuFg; | ||||
|     li { | ||||
|         @extend %c-control; | ||||
|         @include cControl(); | ||||
|         justify-content: start; | ||||
|         color: $colorMenuFg; | ||||
|         cursor: pointer; | ||||
|         display: flex; | ||||
|         padding: nth($menuItemPad, 1) nth($menuItemPad, 2); | ||||
|         transition: $transIn; | ||||
| @@ -278,14 +349,13 @@ button { | ||||
|     } | ||||
| } | ||||
|  | ||||
| @mixin modalMenu() { | ||||
|     // Optional modifier that makes a c-menu more mobile-friendly | ||||
| } | ||||
|  | ||||
| .c-menu { | ||||
|     @include menuOuter(); | ||||
|     @include menuInner(); | ||||
|      li { | ||||
|          &:not(:first-child) { | ||||
|              border-top: 1px solid pullForward($colorMenuBg, 10%); | ||||
|          } | ||||
|      } | ||||
| } | ||||
|  | ||||
| .c-super-menu { | ||||
| @@ -354,3 +424,155 @@ button { | ||||
|     } | ||||
| } | ||||
|  | ||||
| /******************************************************** PALETTES */ | ||||
| .c-palette { | ||||
|     display: flex; | ||||
|     flex-flow: column nowrap; | ||||
|  | ||||
|     &__items { | ||||
|         flex: 1 1 auto; | ||||
|         display: grid; | ||||
|         grid-template-columns: repeat(10, [col] auto ); | ||||
|         grid-gap: 1px; | ||||
|     } | ||||
|  | ||||
|     &__item { | ||||
|         $d: 16px; | ||||
|  | ||||
|         border: 1px solid transparent; | ||||
|         cursor: pointer; | ||||
|         width: 16px; height: 16px; | ||||
|         transition: $transOut; | ||||
|  | ||||
|         &:hover { | ||||
|             transition: $transIn; | ||||
|             $o: 0.7; | ||||
|             border-color: rgba($paletteItemBorderOuterColorSelected, $o); | ||||
|             box-shadow: inset rgba($paletteItemBorderInnerColorSelected, $o) 0 0 0 1px; | ||||
|         } | ||||
|  | ||||
|         &.is-selected { | ||||
|             border-color: $paletteItemBorderOuterColorSelected !important; | ||||
|             border-width: 2px; | ||||
|             box-shadow: inset rgba($paletteItemBorderInnerColorSelected, 1) 0 0 0 1px; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     &__item-none { | ||||
|         flex: 0 0 auto; | ||||
|         display: flex; | ||||
|         align-items: center; | ||||
|         margin-bottom: $interiorMarginSm; | ||||
|  | ||||
|         .c-palette__item { | ||||
|             @include noColor(); | ||||
|             border-color: $paletteItemBorderInnerColor; | ||||
|             margin-right: $interiorMarginSm; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| /******************************************************** TOOLBAR */ | ||||
| .c-ctrl-wrapper { | ||||
|     @include cCtrlWrapper(); | ||||
| } | ||||
|  | ||||
| .c-toolbar, | ||||
| .c-toolbar .c-ctrl-wrapper { | ||||
|     display: flex; | ||||
|     align-items: stretch; | ||||
| } | ||||
|  | ||||
| .c-toolbar { | ||||
|     height: 24px; // Need to standardize the height | ||||
|  | ||||
|     .c-click-icon { | ||||
|         @include cControl(); | ||||
|         $pLR: $interiorMargin - 1; | ||||
|         $pTB: 2px; | ||||
|         color: $colorBodyFg; | ||||
|         padding: $pTB $pLR; | ||||
|  | ||||
|         &--swatched { | ||||
|             padding-bottom: floor($pTB / 2); | ||||
|             width: 2em; // Standardize the width | ||||
|         } | ||||
|  | ||||
|         &[class*='--caution'] { | ||||
|             &:before { | ||||
|                 color: $colorBtnCautionBg; | ||||
|             } | ||||
|  | ||||
|             &:hover { | ||||
|                 background: rgba($colorBtnCautionBgHov, 0.2); | ||||
|                 :before { | ||||
|                     color: $colorBtnCautionBgHov; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     .c-labeled-input { | ||||
|         font-size: 0.9em; | ||||
|         input[type='number'] { | ||||
|             width: 40px; // Number input sucks and must have size set using this method | ||||
|         } | ||||
|  | ||||
|         + .c-labeled-input { | ||||
|             margin-left: $interiorMargin; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| /********* Button Sets */ | ||||
| .c-button-set { | ||||
|     // When one set is adjacent to another, provides a divider between | ||||
|  | ||||
|     display: inline-flex; | ||||
|     flex: 0 0 auto; | ||||
|  | ||||
|     > * { | ||||
|         // Assume buttons are immediate descendants | ||||
|         flex: 0 0 auto; | ||||
|  | ||||
|         + * { | ||||
|             // margin-left: $interiorMarginSm; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     + .c-button-set { | ||||
|         $m: $interiorMargin; | ||||
|         $b: 1px; | ||||
|         &:before { | ||||
|             content: ''; | ||||
|             display: block; | ||||
|             width: $m + $b; // Allow for border | ||||
|             border-right: $b solid $colorInteriorBorder; | ||||
|             margin-right: $m; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     &[class*='--strip'] { | ||||
|         // Buttons are smashed together with minimal margin | ||||
|         // c-buttons don't have border-radius between buttons, creates a tool-button-strip look | ||||
|         // c-click-icons get grouped more closely together | ||||
|         &[class^="c-button"] { | ||||
|             // Only apply the following to buttons that have background, eg. c-button | ||||
|             border-radius: 0; | ||||
|  | ||||
|             + * { | ||||
|                 margin-left: 1px; | ||||
|             } | ||||
|  | ||||
|             &:first-child { | ||||
|                 border-top-left-radius: $controlCr; | ||||
|                 border-bottom-left-radius: $controlCr; | ||||
|             } | ||||
|  | ||||
|             &:last-child { | ||||
|                 border-top-right-radius: $controlCr; | ||||
|                 border-bottom-right-radius: $controlCr; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,3 +1,25 @@ | ||||
| /***************************************************************************** | ||||
|  * 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. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| /************************** FONTS */ | ||||
| @font-face { | ||||
|     /* | ||||
| @@ -27,7 +49,7 @@ | ||||
|     font-style: normal; | ||||
| } | ||||
|  | ||||
| /******************************* RESETS */ | ||||
| /******************************************************** RESETS */ | ||||
| *, | ||||
| :before, | ||||
| :after { | ||||
| @@ -38,7 +60,7 @@ div { | ||||
|     position: relative; | ||||
| } | ||||
|  | ||||
| /******************************* UTILITIES */ | ||||
| /******************************************************** UTILITIES */ | ||||
| .u-contents { | ||||
|     display: contents; | ||||
| } | ||||
| @@ -52,7 +74,7 @@ div { | ||||
|     } | ||||
| } | ||||
|  | ||||
| /******************************* BROWSER ELEMENTS */ | ||||
| /******************************************************** BROWSER ELEMENTS */ | ||||
| body.desktop { | ||||
|     ::-webkit-scrollbar { | ||||
|         box-sizing: border-box; | ||||
| @@ -89,6 +111,13 @@ body.desktop { | ||||
|     } | ||||
| } | ||||
|  | ||||
| input[type=number]::-webkit-inner-spin-button, | ||||
| input[type=number]::-webkit-outer-spin-button { | ||||
|     //margin: -1px -5px inherit -5px !important; | ||||
|     margin-right: -5px !important; | ||||
|     margin-top: -1px !important; | ||||
| } | ||||
|  | ||||
| /************************** HTML ENTITIES */ | ||||
| a { | ||||
|     color: $colorA; | ||||
| @@ -118,22 +147,6 @@ em { | ||||
|     font-style: normal; | ||||
| } | ||||
|  | ||||
| input, textarea { | ||||
|     font-family: inherit; | ||||
|     font-weight: inherit; | ||||
|     letter-spacing: inherit; | ||||
| } | ||||
|  | ||||
| input[type=text], | ||||
| input[type=search], | ||||
| input[type=number] { | ||||
|     @include nice-input(); | ||||
|     padding: $inputTextP; | ||||
|     &.numeric { | ||||
|         text-align: right; | ||||
|     } | ||||
| } | ||||
|  | ||||
| h1, h2, h3 { | ||||
|     letter-spacing: 0.04em; | ||||
|     margin: 0; | ||||
| @@ -169,7 +182,6 @@ li { | ||||
|     padding: 0; | ||||
| } | ||||
|  | ||||
|  | ||||
| /******************************************************** HAS */ | ||||
| // Local Controls: Controls placed in proximity to or overlaid on components and views | ||||
| body.desktop .has-local-controls { | ||||
| @@ -211,7 +223,21 @@ body.desktop .has-local-controls { | ||||
|     //} | ||||
| //} | ||||
|  | ||||
| /************************** LEGACY */ | ||||
| /******************************************************** IS */ | ||||
|  | ||||
| .is-selectable { | ||||
|     &:hover { | ||||
|          | ||||
|     } | ||||
|  | ||||
| } | ||||
|  | ||||
| .is-editing { | ||||
|     .is-selectable { | ||||
|  | ||||
|     } | ||||
| } | ||||
| /******************************************************** LEGACY */ | ||||
|  | ||||
| mct-container { | ||||
|     display: block; | ||||
| @@ -283,21 +309,6 @@ a.disabled { | ||||
|     text-align: center; | ||||
| } | ||||
|  | ||||
| .no-selection { | ||||
|     // aka selection = "None". Used in palettes and their menu buttons. | ||||
|     $c: red; | ||||
|     $s: 48%; | ||||
|     $e: 52%; | ||||
|     background-image: linear-gradient(-45deg, | ||||
|             transparent $s - 5%, | ||||
|             $c $s, | ||||
|             $c $e, | ||||
|             transparent $e + 5% | ||||
|     ); | ||||
|     background-repeat: no-repeat; | ||||
|     background-size: contain; | ||||
| } | ||||
|  | ||||
| .scrolling, | ||||
| .scroll { | ||||
|     overflow: auto; | ||||
| @@ -380,3 +391,8 @@ a.disabled { | ||||
| .t-imagery { | ||||
|     display: contents; | ||||
| } | ||||
|  | ||||
| .t-frame-outer { | ||||
|     min-width: 200px; | ||||
|     min-height: 200px; | ||||
| } | ||||
|   | ||||
| @@ -1,3 +1,25 @@ | ||||
| /***************************************************************************** | ||||
|  * 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. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| @mixin glyphBefore($unicode, $family: 'symbolsfont') { | ||||
|     &:before { | ||||
|         content: $unicode; | ||||
| @@ -91,7 +113,7 @@ | ||||
| .icon-refresh {  @include glyphBefore($glyph-icon-refresh); } | ||||
| .icon-save {  @include glyphBefore($glyph-icon-save); } | ||||
| .icon-sine {  @include glyphBefore($glyph-icon-sine); } | ||||
| .icon-T {  @include glyphBefore($glyph-icon-T); } | ||||
| .icon-font {  @include glyphBefore($glyph-icon-font); } | ||||
| .icon-thumbs-strip {  @include glyphBefore($glyph-icon-thumbs-strip); } | ||||
| .icon-two-parts-both {  @include glyphBefore($glyph-icon-two-parts-both); } | ||||
| .icon-two-parts-one-only {  @include glyphBefore($glyph-icon-two-parts-one-only); } | ||||
| @@ -108,6 +130,7 @@ | ||||
| .icon-frame-hide {  @include glyphBefore($glyph-icon-frame-hide); } | ||||
| .icon-import {  @include glyphBefore($glyph-icon-import); } | ||||
| .icon-export {  @include glyphBefore($glyph-icon-export); } | ||||
| .icon-font-size {  @include glyphBefore($glyph-icon-font-size); } | ||||
| .icon-activity {  @include glyphBefore($glyph-icon-activity); } | ||||
| .icon-activity-mode {  @include glyphBefore($glyph-icon-activity-mode); } | ||||
| .icon-autoflow-tabular {  @include glyphBefore($glyph-icon-autoflow-tabular); } | ||||
|   | ||||
| @@ -20,6 +20,8 @@ | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| // VERSION MANUALLY MIGRATED FROM VUE-TOOLBAR | ||||
|  | ||||
| /************************** VISUALS */ | ||||
| @mixin ancillaryIcon($d, $c) { | ||||
|     // Used for small icons used in combination with larger icons, | ||||
| @@ -31,6 +33,20 @@ | ||||
|     width: $d; | ||||
| } | ||||
|  | ||||
| @mixin isAlias() { | ||||
|     &:after { | ||||
|         color:$colorIconAlias; | ||||
|         content: $glyph-icon-link; | ||||
|         display: block; | ||||
|         font-family: symbolsfont; | ||||
|         position: absolute; | ||||
|         text-shadow: rgba(black, 0.5) 0 1px 4px; | ||||
|         top: auto; left: 0; bottom: 10%; right: auto; | ||||
|         transform-origin: left bottom; | ||||
|         transform: scale(0.5); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @mixin bgDiagonalStripes($c: yellow, $a: 0.1, $d: 40px) { | ||||
|     background-image: linear-gradient(-45deg, | ||||
|         rgba($c, $a) 25%, transparent 25%, | ||||
| @@ -53,6 +69,37 @@ | ||||
|     background-size: $bgsize $bgsize; | ||||
| } | ||||
|  | ||||
| @mixin noColor() { | ||||
|     // A "no fill/stroke" selection option. Used in palettes. | ||||
|     $c: red; | ||||
|     $s: 48%; | ||||
|     $e: 52%; | ||||
|     background-image: linear-gradient(-45deg, | ||||
|         transparent $s - 5%, | ||||
|         $c $s, | ||||
|         $c $e, | ||||
|         transparent $e + 5% | ||||
|     ); | ||||
|     background-repeat: no-repeat; | ||||
|     background-size: contain; | ||||
| } | ||||
|  | ||||
| @mixin bgTicks($c: $colorBodyFg, $repeatDir: 'x') { | ||||
|     $deg: 90deg; | ||||
|     @if ($repeatDir != 'x') { | ||||
|         $deg: 0deg; | ||||
|         $repeatDir: repeat-y; | ||||
|     } @else { | ||||
|         $repeatDir: repeat-x; | ||||
|     } | ||||
|  | ||||
|     background-image: linear-gradient($deg, | ||||
|             $c 1px, transparent 1px, | ||||
|             transparent 100% | ||||
|     ); | ||||
|     background-repeat: $repeatDir; | ||||
| } | ||||
|  | ||||
| @mixin bgVertStripes($c: yellow, $a: 0.1, $d: 40px) { | ||||
|     @include background-image(linear-gradient(-90deg, | ||||
|         rgba($c, $a) 0%, rgba($c, $a) 50%, | ||||
| @@ -63,6 +110,11 @@ | ||||
| } | ||||
|  | ||||
| /************************** LAYOUT */ | ||||
| @mixin abs($m: 0) { | ||||
|     position: absolute; | ||||
|     top: $m; right: $m; bottom: $m; left: $m; | ||||
| } | ||||
|  | ||||
| @mixin gridTwoColumn() { | ||||
|     display: grid; | ||||
|     grid-row-gap: 0; | ||||
| @@ -92,6 +144,13 @@ | ||||
|     grid-column: 1 / 3; | ||||
| } | ||||
|  | ||||
| @mixin modalFullScreen() { | ||||
|     // Optional modifier that makes a c-menu more mobile-friendly | ||||
|     position: fixed; | ||||
|     border-radius: 0; | ||||
|     top: 0; right: 0; bottom: 0; left: 0; | ||||
| } | ||||
|  | ||||
| /************************** TEXT */ | ||||
| @mixin ellipsize() { | ||||
|     overflow: hidden; | ||||
| @@ -105,20 +164,24 @@ | ||||
|     unicode-bidi:bidi-override; | ||||
| } | ||||
|  | ||||
| @mixin test($c: #ffcc00, $a: 0.2) { | ||||
|     background-color: rgba($c, $a) !important; | ||||
| } | ||||
|  | ||||
| /************************** CONTROLS, BUTTONS */ | ||||
| @mixin input-base() { | ||||
| @mixin htmlInputReset() { | ||||
|     appearance: none; | ||||
|     background: none; | ||||
|     background-color: transparent; | ||||
|     border: none; | ||||
|     border-radius: $controlCr; | ||||
|     border-radius: 0; | ||||
|     outline: none; | ||||
|     text-align: inherit; | ||||
|  | ||||
|     &:focus { | ||||
|         outline: 0; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @mixin input-base() { | ||||
|     @include htmlInputReset(); | ||||
|     border-radius: $controlCr; | ||||
|  | ||||
|     &.error { | ||||
|         background-color: $colorFormFieldErrorBg; | ||||
| @@ -126,15 +189,28 @@ | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
| @mixin nice-input($bg: $colorInputBg, $fg: $colorInputFg, $shdw: rgba(black, 0.5) 0 0px 2px) { | ||||
| @mixin nice-input($bg: $colorInputBg, $fg: $colorInputFg, $shdw: rgba(black, 0.5) 0 0 2px) { | ||||
|     @include input-base(); | ||||
|     background: $bg; | ||||
|     color: $fg; | ||||
|     box-shadow: inset $shdw; | ||||
| } | ||||
|  | ||||
| @mixin reactive-input($bg: $colorInputBg, $fg: $colorInputFg) { | ||||
|     @include input-base(); | ||||
|     background: $bg; | ||||
|     box-shadow: $shdwInput; | ||||
|     color: $fg; | ||||
|  | ||||
|     &:hover { | ||||
|         box-shadow: $shdwInputHov; | ||||
|     } | ||||
|  | ||||
|     &:focus { | ||||
|         box-shadow: $shdwInputFoc; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @mixin button($bg: $colorBtnBg, $fg: $colorBtnFg, $radius: $controlCr, $shdw: none) { | ||||
|     background: $bg; | ||||
|     color: $fg; | ||||
| @@ -142,6 +218,63 @@ | ||||
|     box-shadow: $shdw; | ||||
| } | ||||
|  | ||||
| @mixin wrappedInput() { | ||||
|     // An input that is wrapped. Optionally includes a __label or icon element. | ||||
|     // Based on .c-search. | ||||
|     @include nice-input(); | ||||
|     display: flex; | ||||
|     align-items: center; | ||||
|     padding-left: 4px; | ||||
|     padding-right: 4px; | ||||
|  | ||||
|     &:before, | ||||
|     [class*='__label'] { | ||||
|         opacity: 0.5; | ||||
|     } | ||||
|  | ||||
|     &:before { | ||||
|         // Adds an icon. Content defined in class. | ||||
|         direction: rtl; // Aligns glyph to right-hand side of container, for transition | ||||
|         display: block; | ||||
|         font-family: symbolsfont; | ||||
|         flex: 0 0 auto; | ||||
|         overflow: hidden; | ||||
|         padding: 2px 0; // Prevents clipping | ||||
|         transition: width 250ms ease; | ||||
|         width: 1em; | ||||
|     } | ||||
|  | ||||
|     &:hover { | ||||
|         box-shadow: inset rgba(black, 0.8) 0 0px 2px; | ||||
|         &:before { | ||||
|             opacity: 0.9; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     &--major { | ||||
|         padding: 4px; | ||||
|     } | ||||
|  | ||||
|     &__input, | ||||
|     input[type='text'], | ||||
|     input[type='search'], | ||||
|     input[type='number'] { | ||||
|         background: none  !important; | ||||
|         box-shadow: none !important; // !important needed to override default for [input] | ||||
|         flex: 1 1 auto; | ||||
|         padding-left: 2px !important; | ||||
|         padding-right: 2px !important; | ||||
|         min-width: 10px; // Must be set to allow input to collapse below browser min | ||||
|     } | ||||
|  | ||||
|     &.is-active { | ||||
|         &:before { | ||||
|             padding: 2px 0px; | ||||
|             width: 0px; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| /************************** MATH */ | ||||
| @function percentToDecimal($p) { | ||||
|     @return $p / 100%; | ||||
| @@ -161,4 +294,17 @@ | ||||
|  | ||||
| @mixin userSelectNone() { | ||||
|     @include browserPrefix(user-select, none); | ||||
| } | ||||
| } | ||||
|  | ||||
| @mixin cursorGrab() { | ||||
|     cursor: grab; | ||||
|     cursor: -webkit-grab; | ||||
|     &:active { | ||||
|         cursor: grabbing; | ||||
|         cursor: -webkit-grabbing; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @mixin test($c: deeppink, $a: 0.3) { | ||||
|     background-color: rgba($c, $a) !important; | ||||
| } | ||||
|   | ||||
							
								
								
									
										91
									
								
								src/styles-new/_table.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								src/styles-new/_table.scss
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,91 @@ | ||||
| /***************************************************************************** | ||||
|  * 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. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| /******************************************************** TABLE */ | ||||
| .c-table { | ||||
|     // Can be used by any type of table, scrolling, LAD, etc. | ||||
|     $min-w: 50px; | ||||
|  | ||||
|     display: flex; | ||||
|     flex-flow: column nowrap; | ||||
|     justify-content: flex-start; | ||||
|     position: absolute; | ||||
|     top: 0; right: 0; bottom: 0; left: 0; | ||||
|  | ||||
|     &__control-bar, | ||||
|     &__headers-w { | ||||
|         flex: 0 0 auto; | ||||
|     } | ||||
|  | ||||
|     /******************************* ELEMENTS */ | ||||
|     th, td { | ||||
|         white-space: nowrap; | ||||
|         min-width: $min-w; | ||||
|         padding: $tabularTdPadTB $tabularTdPadLR; | ||||
|     } | ||||
|  | ||||
|     td { | ||||
|         color: $colorTelemFresh; | ||||
|         vertical-align: top; | ||||
|     } | ||||
|  | ||||
|     &__control-bar { | ||||
|         margin-bottom: $interiorMarginSm; | ||||
|     } | ||||
|  | ||||
|     [class*="__header"] { | ||||
|         background: $colorTabHeaderBg; | ||||
|  | ||||
|         th { | ||||
|             &:not(:first-child) { | ||||
|                 border-left: 1px solid $colorTabHeaderBorder; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     &__body { | ||||
|         tr { | ||||
|             &:not(:first-child) { | ||||
|                 border-top: 1px solid $colorTabBorder; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     &--sortable { | ||||
|         .is-sorting { | ||||
|             &:after { | ||||
|                 color: $colorIconAlias; | ||||
|                 content: $glyph-icon-arrow-tall-up; | ||||
|                 font-family: symbolsfont; | ||||
|                 font-size: 8px; | ||||
|                 display: inline-block; | ||||
|                 margin-left: $interiorMarginSm; | ||||
|             } | ||||
|             &.desc:after { | ||||
|                 content: $glyph-icon-arrow-tall-down; | ||||
|             } | ||||
|         } | ||||
|         .is-sortable { | ||||
|             cursor: pointer; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,3 +1,25 @@ | ||||
| /***************************************************************************** | ||||
|  * 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 "vendor/normalize.min.css"; | ||||
| @import "sass-base.scss"; | ||||
|  | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
|   "metadata": { | ||||
|     "name": "openmct-symbols-16px", | ||||
|     "lastOpened": 0, | ||||
|     "created": 1529545133464 | ||||
|     "created": 1537817705550 | ||||
|   }, | ||||
|   "iconSets": [ | ||||
|     { | ||||
| @@ -525,7 +525,7 @@ | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 102, | ||||
|           "order": 149, | ||||
|           "prevSize": 24, | ||||
|           "name": "icon-T", | ||||
|           "id": 84, | ||||
| @@ -660,13 +660,21 @@ | ||||
|           "code": 921672, | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 150, | ||||
|           "id": 119, | ||||
|           "name": "icon-font-size-alt1", | ||||
|           "prevSize": 24, | ||||
|           "code": 921673, | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 37, | ||||
|           "prevSize": 24, | ||||
|           "name": "icon-activity", | ||||
|           "id": 32, | ||||
|           "code": 921856, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 36, | ||||
| @@ -674,7 +682,7 @@ | ||||
|           "name": "icon-activity-mode", | ||||
|           "id": 31, | ||||
|           "code": 921857, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 52, | ||||
| @@ -682,7 +690,7 @@ | ||||
|           "name": "icon-autoflow-tabular", | ||||
|           "id": 47, | ||||
|           "code": 921858, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 55, | ||||
| @@ -690,7 +698,7 @@ | ||||
|           "name": "icon-clock", | ||||
|           "id": 50, | ||||
|           "code": 921859, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 58, | ||||
| @@ -698,7 +706,7 @@ | ||||
|           "name": "icon-database", | ||||
|           "id": 53, | ||||
|           "code": 921860, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 57, | ||||
| @@ -706,7 +714,7 @@ | ||||
|           "name": "icon-database-query", | ||||
|           "id": 52, | ||||
|           "code": 921861, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 17, | ||||
| @@ -714,7 +722,7 @@ | ||||
|           "name": "icon-dataset", | ||||
|           "id": 12, | ||||
|           "code": 921862, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 22, | ||||
| @@ -722,7 +730,7 @@ | ||||
|           "name": "icon-datatable", | ||||
|           "id": 17, | ||||
|           "code": 921863, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 59, | ||||
| @@ -730,7 +738,7 @@ | ||||
|           "name": "icon-dictionary", | ||||
|           "id": 54, | ||||
|           "code": 921864, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 62, | ||||
| @@ -738,7 +746,7 @@ | ||||
|           "name": "icon-folder", | ||||
|           "id": 57, | ||||
|           "code": 921865, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 66, | ||||
| @@ -746,7 +754,7 @@ | ||||
|           "name": "icon-image", | ||||
|           "id": 61, | ||||
|           "code": 921872, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 68, | ||||
| @@ -754,7 +762,7 @@ | ||||
|           "name": "icon-layout", | ||||
|           "id": 63, | ||||
|           "code": 921873, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 77, | ||||
| @@ -762,7 +770,7 @@ | ||||
|           "name": "icon-object", | ||||
|           "id": 72, | ||||
|           "code": 921874, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 78, | ||||
| @@ -770,7 +778,7 @@ | ||||
|           "name": "icon-object-unknown", | ||||
|           "id": 73, | ||||
|           "code": 921875, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 79, | ||||
| @@ -778,7 +786,7 @@ | ||||
|           "name": "icon-packet", | ||||
|           "id": 74, | ||||
|           "code": 921876, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 80, | ||||
| @@ -786,7 +794,7 @@ | ||||
|           "name": "icon-page", | ||||
|           "id": 75, | ||||
|           "code": 921877, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 135, | ||||
| @@ -794,7 +802,7 @@ | ||||
|           "name": "icon-plot-overlay", | ||||
|           "prevSize": 24, | ||||
|           "code": 921878, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 113, | ||||
| @@ -802,7 +810,7 @@ | ||||
|           "name": "icon-plot-stacked", | ||||
|           "prevSize": 24, | ||||
|           "code": 921879, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 10, | ||||
| @@ -810,7 +818,7 @@ | ||||
|           "name": "icon-session", | ||||
|           "id": 5, | ||||
|           "code": 921880, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 24, | ||||
| @@ -818,7 +826,7 @@ | ||||
|           "name": "icon-tabular", | ||||
|           "id": 19, | ||||
|           "code": 921881, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 7, | ||||
| @@ -826,7 +834,7 @@ | ||||
|           "name": "icon-tabular-lad", | ||||
|           "id": 2, | ||||
|           "code": 921888, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 6, | ||||
| @@ -834,7 +842,7 @@ | ||||
|           "name": "icon-tabular-lad-set", | ||||
|           "id": 1, | ||||
|           "code": 921889, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 8, | ||||
| @@ -842,7 +850,7 @@ | ||||
|           "name": "icon-tabular-realtime", | ||||
|           "id": 3, | ||||
|           "code": 921890, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 23, | ||||
| @@ -850,7 +858,7 @@ | ||||
|           "name": "icon-tabular-scrolling", | ||||
|           "id": 18, | ||||
|           "code": 921891, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 112, | ||||
| @@ -858,7 +866,7 @@ | ||||
|           "name": "icon-telemetry", | ||||
|           "id": 86, | ||||
|           "code": 921892, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 90, | ||||
| @@ -866,7 +874,7 @@ | ||||
|           "name": "icon-telemetry-panel", | ||||
|           "id": 85, | ||||
|           "code": 921893, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 93, | ||||
| @@ -874,15 +882,15 @@ | ||||
|           "name": "icon-timeline", | ||||
|           "id": 88, | ||||
|           "code": 921894, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 116, | ||||
|           "id": 101, | ||||
|           "name": "icon-timer-v1.5", | ||||
|           "name": "icon-timer-v15", | ||||
|           "prevSize": 24, | ||||
|           "code": 921895, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 11, | ||||
| @@ -890,7 +898,7 @@ | ||||
|           "name": "icon-topic", | ||||
|           "id": 6, | ||||
|           "code": 921896, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 115, | ||||
| @@ -898,7 +906,7 @@ | ||||
|           "name": "icon-box-with-dashed-lines", | ||||
|           "id": 29, | ||||
|           "code": 921897, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 126, | ||||
| @@ -906,7 +914,7 @@ | ||||
|           "name": "icon-summary-widget", | ||||
|           "prevSize": 24, | ||||
|           "code": 921904, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 139, | ||||
| @@ -914,13 +922,13 @@ | ||||
|           "name": "icon-notebook", | ||||
|           "prevSize": 24, | ||||
|           "code": 921905, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         } | ||||
|       ], | ||||
|       "metadata": { | ||||
|         "name": "openmct-symbols-16px", | ||||
|         "importSize": { | ||||
|           "width": 512, | ||||
|           "width": 745, | ||||
|           "height": 512 | ||||
|         }, | ||||
|         "designer": "Charles Hacskaylo", | ||||
| @@ -2360,7 +2368,7 @@ | ||||
|         }, | ||||
|         { | ||||
|           "paths": [ | ||||
|             "M0 0v256h128v-64h256v704h-192v128h640v-128h-192v-704h256v64h128v-256z" | ||||
|             "M800 1024h224l-384-1024h-256l-384 1024h224l84-224h408zM380 608l132-352 132 352z" | ||||
|           ], | ||||
|           "grid": 16, | ||||
|           "tags": [ | ||||
| @@ -2368,9 +2376,15 @@ | ||||
|           ], | ||||
|           "defaultCode": 228, | ||||
|           "id": 84, | ||||
|           "attrs": [], | ||||
|           "attrs": [ | ||||
|             {} | ||||
|           ], | ||||
|           "isMulticolor": false, | ||||
|           "isMulticolor2": false, | ||||
|           "colorPermutations": { | ||||
|             "1161751207457516161751": [] | ||||
|             "1161751207457516161751": [ | ||||
|               {} | ||||
|             ] | ||||
|           } | ||||
|         }, | ||||
|         { | ||||
| @@ -2840,6 +2854,30 @@ | ||||
|             ] | ||||
|           } | ||||
|         }, | ||||
|         { | ||||
|           "id": 119, | ||||
|           "paths": [ | ||||
|             "M1226.4 320h-176l-76.22 203.24 77 205.34 87.22-232.58 90.74 242h-174.44l49.5 132h174.44l57.76 154h154l-264-704z", | ||||
|             "M384 0l-384 1024h224l84-224h408l84 224h224l-384-1024zM380 608l132-352 132 352z" | ||||
|           ], | ||||
|           "attrs": [ | ||||
|             {}, | ||||
|             {} | ||||
|           ], | ||||
|           "width": 1490, | ||||
|           "isMulticolor": false, | ||||
|           "isMulticolor2": false, | ||||
|           "grid": 16, | ||||
|           "tags": [ | ||||
|             "icon-font-size-alt1" | ||||
|           ], | ||||
|           "colorPermutations": { | ||||
|             "1161751207457516161751": [ | ||||
|               {}, | ||||
|               {} | ||||
|             ] | ||||
|           } | ||||
|         }, | ||||
|         { | ||||
|           "paths": [ | ||||
|             "M576 64h-256l320 320h-290.256c-44.264-76.516-126.99-128-221.744-128h-128v512h128c94.754 0 177.48-51.484 221.744-128h290.256l-320 320h256l448-448-448-448z" | ||||
| @@ -3740,7 +3778,9 @@ | ||||
|       "classSelector": ".ui-symbol", | ||||
|       "showMetrics": true, | ||||
|       "showMetadata": true, | ||||
|       "embed": false | ||||
|       "embed": false, | ||||
|       "noie8": true, | ||||
|       "ie7": false | ||||
|     }, | ||||
|     "imagePref": { | ||||
|       "prefix": "icon-", | ||||
|   | ||||
| @@ -71,7 +71,7 @@ | ||||
| <glyph unicode="󡀩" glyph-name="icon-refresh" d="M960 528v432l-164.8-164.8c-79.8 65.2-178.8 100.8-283.2 100.8-119.6 0-232.2-46.6-316.8-131.2s-131.2-197.2-131.2-316.8 46.6-232.2 131.2-316.8c84.6-84.6 197.2-131.2 316.8-131.2s232.2 46.6 316.8 131.2c69.4 69.4 113.2 157.4 126.6 252.8h-130c-29.8-145.8-159-256-313.6-256-176.4 0-320 143.6-320 320s143.8 320 320.2 320c72 0 138.4-23.8 192-64l-176-176h432z" /> | ||||
| <glyph unicode="󡀰" glyph-name="icon-save" d="M192.2 384c-0.2 0-0.2 0 0 0l-0.2-448h640v447.8c0 0 0 0-0.2 0.2h-639.6zM978.8 749.2l-165.4 165.4c-25 25-74.2 45.4-109.4 45.4h-576c-70.4 0-128-57.6-128-128v-768c0-70.4 57.6-128 128-128v448c0 35.2 28.8 64 64 64h640c35.2 0 64-28.8 64-64v-448c70.4 0 128 57.6 128 128v576c0 35.2-20.4 84.4-45.2 109.2zM704 704c0-35.2-28.8-64-64-64h-448c-35.2 0-64 28.8-64 64v192h320v-192h128v192h128v-192z" /> | ||||
| <glyph unicode="󡀱" glyph-name="icon-sine" d="M1022.294 448c-1.746 7.196-3.476 14.452-5.186 21.786-20.036 85.992-53.302 208.976-98 306.538-22.42 48.938-45.298 86.556-69.946 115.006-48.454 55.93-98.176 67.67-131.356 67.67s-82.902-11.74-131.356-67.672c-24.648-28.45-47.528-66.068-69.948-115.006-44.696-97.558-77.962-220.544-98-306.538-21.646-92.898-46.444-175.138-71.71-237.836-16.308-40.46-30.222-66.358-40.6-82.604-10.378 16.246-24.292 42.142-40.6 82.604-23.272 57.75-46.144 132.088-66.524 216.052h-197.362c1.746-7.196 3.476-14.452 5.186-21.786 20.036-85.992 53.302-208.976 98-306.538 22.42-48.938 45.298-86.556 69.946-115.006 48.454-55.932 98.176-67.672 131.356-67.672s82.902 11.74 131.356 67.672c24.648 28.45 47.528 66.068 69.948 115.006 44.696 97.558 77.962 220.544 98 306.538 21.646 92.898 46.444 175.138 71.71 237.836 16.308 40.46 30.222 66.358 40.6 82.604 10.378-16.246 24.292-42.142 40.6-82.604 23.274-57.748 46.146-132.086 66.526-216.050h197.36z" /> | ||||
| <glyph unicode="󡀲" glyph-name="icon-T" d="M0 960v-256h128v64h256v-704h-192v-128h640v128h-192v704h256v-64h128v256z" /> | ||||
| <glyph unicode="󡀲" glyph-name="icon-T" d="M800-64h224l-384 1024h-256l-384-1024h224l84 224h408zM380 352l132 352 132-352z" /> | ||||
| <glyph unicode="󡀳" glyph-name="icon-thumbs-strip" d="M448 578c0-35.2-28.8-64-64-64h-320c-35.2 0-64 28.8-64 64v320c0 35.2 28.8 64 64 64h320c35.2 0 64-28.8 64-64v-320zM1024 578c0-35.2-28.8-64-64-64h-320c-35.2 0-64 28.8-64 64v320c0 35.2 28.8 64 64 64h320c35.2 0 64-28.8 64-64v-320zM448 2c0-35.2-28.8-64-64-64h-320c-35.2 0-64 28.8-64 64v320c0 35.2 28.8 64 64 64h320c35.2 0 64-28.8 64-64v-320zM1024 2c0-35.2-28.8-64-64-64h-320c-35.2 0-64 28.8-64 64v320c0 35.2 28.8 64 64 64h320c35.2 0 64-28.8 64-64v-320z" /> | ||||
| <glyph unicode="󡀴" glyph-name="icon-two-parts-both" d="M896 960h-768c-70.4 0-128-57.6-128-128v-768c0-70.4 57.6-128 128-128h768c70.4 0 128 57.6 128 128v768c0 70.4-57.6 128-128 128zM128 832h320v-768h-320v768zM896 64h-320v768h320v-768z" /> | ||||
| <glyph unicode="󡀵" glyph-name="icon-two-parts-one-only" d="M896 960h-768c-70.4 0-128-57.6-128-128v-768c0-70.4 57.6-128 128-128h768c70.4 0 128 57.6 128 128v768c0 70.4-57.6 128-128 128zM896 64h-320v768h320v-768z" /> | ||||
| @@ -88,6 +88,7 @@ | ||||
| <glyph unicode="󡁆" glyph-name="icon-frame-hide" d="M128 770h420l104 128h-652v-802.4l128 157.4zM896 130h-420l-104-128h652v802.4l-128-157.4zM832 962l-832-1024h192l832 1024zM392 578l104 128h-304v-128z" /> | ||||
| <glyph unicode="󡁇" glyph-name="icon-import" d="M832 767.6v-639.4c0-0.2-0.2-0.2-0.4-0.4h-319.6v-192h320c105.6 0 192 86.4 192 192v640.2c0 105.6-86.4 192-192 192h-320v-192h319.6c0.2 0 0.4-0.2 0.4-0.4zM192 256v-192l384 384-384 384v-192h-192v-384z" /> | ||||
| <glyph unicode="󡁈" glyph-name="icon-export" d="M192 128.34v639.32l0.34 0.34h319.66v192h-320c-105.6 0-192-86.4-192-192v-640c0-105.6 86.4-192 192-192h320v192h-319.66zM1024 448l-384 384v-192h-192v-384h192v-192l384 384z" /> | ||||
| <glyph unicode="󡁉" glyph-name="icon-font-size-alt1" horiz-adv-x="1490" d="M1226.4 640h-176l-76.22-203.24 77-205.34 87.22 232.58 90.74-242h-174.44l49.5-132h174.44l57.76-154h154l-264 704zM384 960l-384-1024h224l84 224h408l84-224h224l-384 1024zM380 352l132 352 132-352z" /> | ||||
| <glyph unicode="󡄀" glyph-name="icon-activity" d="M576 896h-256l320-320h-290.256c-44.264 76.516-126.99 128-221.744 128h-128v-512h128c94.754 0 177.48 51.484 221.744 128h290.256l-320-320h256l448 448-448 448z" /> | ||||
| <glyph unicode="󡄁" glyph-name="icon-activity-mode" d="M512 960c-214.866 0-398.786-132.372-474.744-320h90.744c56.86 0 107.938-24.724 143.094-64h240.906l-192 192h256l320-320-320-320h-256l192 192h-240.906c-35.156-39.276-86.234-64-143.094-64h-90.744c75.958-187.628 259.878-320 474.744-320 282.77 0 512 229.23 512 512s-229.23 512-512 512z" /> | ||||
| <glyph unicode="󡄂" glyph-name="icon-autoflow-tabular" d="M192 960c-105.6 0-192-86.4-192-192v-640c0-105.6 86.4-192 192-192h64v1024h-64zM384 960h256v-1024h-256v1024zM832 960h-64v-704h256v512c0 105.6-86.4 192-192 192z" /> | ||||
| @@ -115,7 +116,7 @@ | ||||
| <glyph unicode="󡄤" glyph-name="icon-telemetry" d="M32 328.34c14.28 5.62 54.44 47.54 92.96 146 42.46 108.38 116.32 237.66 227.040 237.66 52.4 0 101.42-29.16 145.7-86.68 37.34-48.5 64.84-108.92 81.34-151.080 38.52-98.38 78.68-140.3 92.96-146 14.28 5.62 54.44 47.54 92.96 146 37.4 95.5 99.14 207.14 188.94 232.46-90.462 152.598-254.314 253.3-441.686 253.3-0.075 0-0.15 0-0.226 0-282.748 0-511.988-229.24-511.988-512 0-0.032 0-0.070 0-0.108 0-35.719 3.641-70.587 10.572-104.255 8.968-7.457 16.648-13.417 21.428-15.297zM992 567.66c-14.28-5.62-54.44-47.52-92.96-146-42.46-108.38-116.32-237.66-227.040-237.66-52.4 0-101.42 29.16-145.7 86.68-37.34 48.5-64.84 108.92-81.34 151.080-38.52 98.38-78.68 140.3-92.96 146-14.28-5.62-54.44-47.52-92.96-146-37.4-95.5-99.14-207.14-188.94-232.46 90.462-152.598 254.314-253.3 441.686-253.3 0.075 0 0.15 0 0.226 0 282.748 0 511.988 229.24 511.988 512 0 0.032 0 0.070 0 0.108 0 35.719-3.641 70.587-10.572 104.255-8.968 7.457-16.648 13.417-21.428 15.297z" /> | ||||
| <glyph unicode="󡄥" glyph-name="icon-telemetry-panel" d="M169.2 512c14 56.4 33 122 56.6 176.8 15.4 35.8 31.2 63.2 48.2 84 18.4 22.4 49 49.2 91 49.2s72.6-26.8 91-49.2c17-20.6 32.6-48.2 48.2-84 23.6-54.8 42.8-120.4 56.6-176.8h461.2v256c0 105.6-86.4 192-192 192h-640c-105.6 0-192-86.4-192-192v-256h171.2zM718.6 384h-127.2c25-93.4 48.4-144.4 63.6-168.6 15.2 24.2 38.6 75.2 63.6 168.6zM301.4 512h127.2c-25 93.4-48.4 144.4-63.6 168.6-15.2-24.2-38.6-75.2-63.6-168.6zM850.8 384c-14-56.4-33-122-56.6-176.8-15.4-35.8-31.2-63.2-48.2-84-18.4-22.4-49-49.2-91-49.2s-72.6 26.8-91 49.2c-17 20.6-32.6 48.2-48.2 84-23.6 54.8-42.8 120.4-56.6 176.8h-461.2v-256c0-105.6 86.4-192 192-192h640c105.6 0 192 86.4 192 192v256h-171.2z" /> | ||||
| <glyph unicode="󡄦" glyph-name="icon-timeline" d="M256 704h384v-128h-384v128zM384 512h384v-128h-384v128zM320 320h384v-128h-384v128zM832 960h-128v-192h127.6c0.2 0 0.2-0.2 0.4-0.4v-639.4c0-0.2-0.2-0.2-0.4-0.4h-127.6v-192h128c105.6 0 192 86.4 192 192v640.2c0 105.6-86.4 192-192 192zM192 128.4v639.2c0 0.2 0.2 0.2 0.4 0.4h127.6v192h-128c-105.6 0-192-86.4-192-192v-640c0-105.6 86.4-192 192-192h128v192h-127.6c-0.2 0-0.4 0.2-0.4 0.4z" /> | ||||
| <glyph unicode="󡄧" glyph-name="icon-timer-v1.5" horiz-adv-x="896" d="M576 813.4v82.58c0 35.346-28.654 64-64 64h-128c-35.346 0-64-28.654-64-64v-82.58c-185.040-55.080-320-226.48-320-429.42 0-247.42 200.58-448 448-448s448 200.58 448 448c0 202.96-135 374.4-320 429.42zM468 363.98l-263.76-211c-57.105 59.935-92.24 141.251-92.24 230.772 0 0.080 0 0.16 0 0.24 0 185.268 150.72 335.988 336 335.988 6.72 0 13.38-0.22 20-0.62v-355.38z" /> | ||||
| <glyph unicode="󡄧" glyph-name="icon-timer-v15" horiz-adv-x="896" d="M576 813.4v82.58c0 35.346-28.654 64-64 64h-128c-35.346 0-64-28.654-64-64v-82.58c-185.040-55.080-320-226.48-320-429.42 0-247.42 200.58-448 448-448s448 200.58 448 448c0 202.96-135 374.4-320 429.42zM468 363.98l-263.76-211c-57.105 59.935-92.24 141.251-92.24 230.772 0 0.080 0 0.16 0 0.24 0 185.268 150.72 335.988 336 335.988 6.72 0 13.38-0.22 20-0.62v-355.38z" /> | ||||
| <glyph unicode="󡄨" glyph-name="icon-topic" d="M454.36 483.36l86.3 86.3c9.088 8.965 21.577 14.502 35.36 14.502s26.272-5.537 35.366-14.507l86.294-86.294c19.328-19.358 42.832-34.541 69.047-44.082l1.313 171.722-57.64 57.64c-34.407 34.33-81.9 55.558-134.35 55.558s-99.943-21.228-134.354-55.562l-86.296-86.297c-9.088-8.965-21.577-14.502-35.36-14.502s-26.272 5.537-35.366 14.507l-28.674 28.654v-172.14c19.045-7.022 41.040-11.084 63.984-11.084 52.463 0 99.966 21.239 134.379 55.587zM505.64 412.64l-86.3-86.3c-9.088-8.965-21.577-14.502-35.36-14.502s-26.272 5.537-35.366 14.507l-86.294 86.294c-2 2-4.2 4-6.36 6v-197.36c33.664-30.72 78.65-49.537 128.031-49.537 52.44 0 99.923 21.22 134.333 55.541l86.296 86.296c9.088 8.965 21.577 14.502 35.36 14.502s26.272-5.537 35.366-14.507l86.294-86.294c2-2 4.2-4 6.36-6v197.36c-33.664 30.72-78.65 49.537-128.031 49.537-52.44 0-99.923-21.22-134.333-55.541zM832 960h-128v-192h127.66l0.34-0.34v-639.32l-0.34-0.34h-127.66v-192h128c105.6 0 192 86.4 192 192v640c0 105.6-86.4 192-192 192zM320 128h-127.66l-0.34 0.34v639.32l0.34 0.34h127.66v192h-128c-105.6 0-192-86.4-192-192v-640c0-105.6 86.4-192 192-192h128v192z" /> | ||||
| <glyph unicode="󡄩" glyph-name="icon-box-with-dashed-lines" d="M0 576h128v-256h-128v256zM128 831.78l0.22 0.22h191.78v128h-192c-70.606-0.215-127.785-57.394-128-127.979v-192.021h128v191.78zM128 64.22v191.78h-128v-192c0.215-70.606 57.394-127.785 127.979-128h192.021v128h-191.78zM384 960h256v-128h-256v128zM896 64.22l-0.22-0.22h-191.78v-128h192c70.606 0.215 127.785 57.394 128 127.979v192.021h-128v-191.78zM896 960h-192v-128h191.78l0.22-0.22v-191.78h128v192c-0.215 70.606-57.394 127.785-127.979 128zM896 576h128v-256h-128v256zM384 64h256v-128h-256v128zM256 704h512v-512h-512v512z" /> | ||||
| <glyph unicode="󡄰" glyph-name="icon-summary-widget" d="M896 960h-768c-70.4 0-128-57.6-128-128v-768c0-70.4 57.6-128 128-128h768c70.4 0 128 57.6 128 128v768c0 70.4-57.6 128-128 128zM847.8 349.6l-82.6-143.2-189.6 131.6 19.2-230h-165.4l19.2 230-189.6-131.6-82.6 143.2 208.6 98.4-208.8 98.4 82.6 143.2 189.6-131.6-19.2 230h165.4l-19.2-230 189.6 131.6 82.6-143.2-208.6-98.4 208.8-98.4z" /> | ||||
|   | ||||
| Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB | 
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							| @@ -46,7 +46,7 @@ | ||||
| //@import "../styles/search/search"; | ||||
| //@import "../styles/mobile/search/search"; | ||||
| @import "../styles/overlay/overlay"; | ||||
| @import "../styles/tree/tree"; | ||||
| //@import "../styles/tree/tree"; | ||||
| @import "../styles/object-label"; | ||||
| //@import "../styles/mobile/tree"; | ||||
| @import "../styles/user-environ/frame"; | ||||
|   | ||||
| @@ -1,3 +1,25 @@ | ||||
| /***************************************************************************** | ||||
|  * 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. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| /*********************************************** NOTEBOOK */ | ||||
| @import "sass-base.scss"; | ||||
|  | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user