Compare commits
	
		
			423 Commits
		
	
	
		
			topic-core
			...
			condition-
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 44a8135772 | ||
|   | 6731283cf8 | ||
|   | 0cd2799d00 | ||
|   | 316e0f24cf | ||
|   | 05f94edb49 | ||
|   | e22458f09e | ||
|   | cc2df8401b | ||
|   | 43a82ec05f | ||
|   | fe2e29d69b | ||
|   | 5d21a8b6fe | ||
|   | 500ab52476 | ||
|   | b0bb723357 | ||
|   | e339d743ed | ||
|   | 6ab60ab52e | ||
|   | 55e5c49f6e | ||
|   | f090f7ffe7 | ||
|   | 94b5617e63 | ||
|   | 41c79c6032 | ||
|   | 83c648cc26 | ||
|   | 76e7fec1a0 | ||
|   | 09bfd80f69 | ||
|   | 8f05c57d1a | ||
|   | 81caa27cba | ||
|   | 74a7ef2565 | ||
|   | 649575fd2d | ||
|   | b75b7a958a | ||
|   | 625b39d722 | ||
|   | 65f80f4c45 | ||
|   | 02cd3048c8 | ||
|   | 63feaef988 | ||
|   | efca7c8e58 | ||
|   | 8900072239 | ||
|   | a7e57c62f4 | ||
|   | 5fcc4eebe1 | ||
|   | 27a09239e3 | ||
|   | 8d86c914a1 | ||
|   | fab04519c6 | ||
|   | 4675fc8ae6 | ||
|   | 576b843bd5 | ||
|   | 95f855f905 | ||
|   | 5b00246cc0 | ||
|   | 47c388450f | ||
|   | 34a149661c | ||
|   | 4c4b587d9c | ||
|   | b8b838f490 | ||
|   | 8cb29ba4a9 | ||
|   | ece6223b23 | ||
|   | ecabd00b0c | ||
|   | 768df84f10 | ||
|   | f8b3899bb9 | ||
|   | 3b046db4f8 | ||
|   | 97f829da9f | ||
|   | fb1eed1982 | ||
|   | dd9b567025 | ||
|   | fa83b4867c | ||
|   | 47d4fc9103 | ||
|   | dabd0bff29 | ||
|   | 51c70d02d7 | ||
|   | b74733bf3f | ||
|   | 84ae65536b | ||
|   | 71424dcf8d | ||
|   | 2c40396139 | ||
|   | 16a0bf9d6c | ||
|   | 5498ba8e1e | ||
|   | 0f9d7d2832 | ||
|   | 9bd1c51a6e | ||
|   | fd74fb0ec4 | ||
|   | 3626ff9947 | ||
|   | fd568409d3 | ||
|   | 14e3500a88 | ||
|   | 83d08ae369 | ||
|   | 39bf601ee1 | ||
|   | cfafecdd64 | ||
|   | 629ca089cf | ||
|   | 89ae6ef8c7 | ||
|   | 300acd6ec8 | ||
|   | ba780981a5 | ||
|   | 62774678a7 | ||
|   | ac13bc5850 | ||
|   | e526626e09 | ||
|   | 564be6f8ba | ||
|   | 371a7d3a3e | ||
|   | 8539d60562 | ||
|   | d333fd5822 | ||
|   | 364191eddc | ||
|   | 583f4dac85 | ||
|   | 28255dce01 | ||
|   | c9419d3e2d | ||
|   | b386275330 | ||
|   | d2a45e46f1 | ||
|   | 35d1727dbf | ||
|   | 8125a4f321 | ||
|   | 1a409afb03 | ||
|   | e57c18fd69 | ||
|   | 3aec9ec6ff | ||
|   | 0e9bf74332 | ||
|   | 2609a41ee8 | ||
|   | b8dc5acf00 | ||
|   | fbbdf8cff7 | ||
|   | b0edb19239 | ||
|   | 85902b878e | ||
|   | 9d5c7a4015 | ||
|   | fc53e855c4 | ||
|   | 467c57b7c6 | ||
|   | a51c0d5139 | ||
|   | d46310ca7d | ||
|   | 8f87cc78e8 | ||
|   | ee6e0f310e | ||
|   | f328a1078e | ||
|   | b4cf81a0ef | ||
|   | 1b9b7e2345 | ||
|   | 4456633010 | ||
|   | cda97d142a | ||
|   | 858199e396 | ||
|   | f504a335af | ||
|   | 463ec47af6 | ||
|   | ec4d121a98 | ||
|   | fea6d2df96 | ||
|   | 598d2b31e9 | ||
|   | 25e28ab97c | ||
|   | 43056c4068 | ||
|   | 614206b10c | ||
|   | 30a493d038 | ||
|   | 96e433beaa | ||
|   | 0915aaea3b | ||
|   | 80656c1be0 | ||
|   | acd75f86f4 | ||
|   | 486dae54bd | ||
|   | 92ecf3af1d | ||
|   | fd0c19026d | ||
|   | 3109c8d825 | ||
|   | 78cf75323f | ||
|   | b744467f21 | ||
|   | a0b7999ea2 | ||
|   | 2bb2bb6a1b | ||
|   | 11ed7027e7 | ||
|   | 36bcfd5a41 | ||
|   | 70b5c627ca | ||
|   | f4f1d0387b | ||
|   | a1bf4a92e5 | ||
|   | 7d2256d70f | ||
|   | 5814d2a35e | ||
|   | 6ab84c0bc3 | ||
|   | a7fc9b3caa | ||
|   | 67f493f012 | ||
|   | 0686e6d38f | ||
|   | 7fc825949c | ||
|   | 2a9ccdcffd | ||
|   | 6db78af69f | ||
|   | 038489256c | ||
|   | 53b785269b | ||
|   | 007b14b5c9 | ||
|   | 50b331c451 | ||
|   | 44fc62e0ba | ||
|   | 2635f085f0 | ||
|   | 22161fce7f | ||
|   | 386fc75047 | ||
|   | fa6dd84945 | ||
|   | d425bd564c | ||
|   | 93e3065b3e | ||
|   | 0ad2d59924 | ||
|   | f4468a8233 | ||
|   | dc08877bbb | ||
|   | f08caa6135 | ||
|   | ad7d029ce8 | ||
|   | 387912b4d3 | ||
|   | 53e0ed4d4a | ||
|   | 11c205b5c4 | ||
|   | 4ede6351ec | ||
|   | 24bbcb466f | ||
|   | 682601477c | ||
|   | b6b5cfe403 | ||
|   | b6ce9c6ed7 | ||
|   | 6e5e8f0ce8 | ||
|   | 2415d785cc | ||
|   | 2b5d6beb84 | ||
|   | 86316d8940 | ||
|   | 1f2b5ec5c8 | ||
|   | 0a9d23d86f | ||
|   | 379e37c881 | ||
|   | 37ef269dce | ||
|   | 8db6f8f633 | ||
|   | 79557165a3 | ||
|   | ec1d4abde9 | ||
|   | 07c5e2800a | ||
|   | 79811d6662 | ||
|   | 67919ece16 | ||
|   | 7029dcf09e | ||
|   | 38deef6e72 | ||
|   | b6220288ac | ||
|   | fc03b3a79d | ||
|   | 096d6371f1 | ||
|   | e580734c95 | ||
|   | 2690156a9d | ||
|   | 7ac7a40b1b | ||
|   | dc9e572052 | ||
|   | b15ebfd492 | ||
|   | 8baee7a0c9 | ||
|   | dc85063467 | ||
|   | be428b326e | ||
|   | dd0e360709 | ||
|   | 04da88e3b4 | ||
|   | 9bcab02e35 | ||
|   | 1ff4d41b7c | ||
|   | 04a5c8f69f | ||
|   | 8886a94a01 | ||
|   | f25eebdf3f | ||
|   | f9ba46fe85 | ||
|   | 6f6fb859d6 | ||
|   | 1e3389b427 | ||
|   | c977c64139 | ||
|   | e419149378 | ||
|   | a5a3e41d21 | ||
|   | ecef8eaf86 | ||
|   | de03cfbe64 | ||
|   | 03a6de55d6 | ||
|   | 3c5047df5e | ||
|   | 3cc630d4c2 | ||
|   | b3488c54cd | ||
|   | 01b1d66bea | ||
|   | 2e82edb306 | ||
|   | 8f0e773ac1 | ||
|   | 223a0feada | ||
|   | bc9cadaa77 | ||
|   | 0fd0da8331 | ||
|   | f42ec7e2c5 | ||
|   | d6a422fbdb | ||
|   | d98b54bea7 | ||
|   | 0beda1d053 | ||
|   | e912ab8f4e | ||
|   | 5055a18ca1 | ||
|   | fa21911287 | ||
|   | 96746f4042 | ||
|   | b22ad3ded9 | ||
|   | 7e0f475c63 | ||
|   | efb3c2b71e | ||
|   | 862ea6986f | ||
|   | cfa5dcb02e | ||
|   | 23aaada79d | ||
|   | 9e4458db10 | ||
|   | a8da06033c | ||
|   | 0bf3597147 | ||
|   | cfd9730055 | ||
|   | e88ead30dc | ||
|   | 67b24ce846 | ||
|   | 709c3fff65 | ||
|   | ab6e87ae6b | ||
|   | cdb7066bed | ||
|   | 73d0507f1f | ||
|   | 4d263bcf32 | ||
|   | 2d8f61172d | ||
|   | 621c1dc11e | ||
|   | dd136a5ff4 | ||
|   | 8fc785bbd6 | ||
|   | 82be503f4f | ||
|   | 7feb933519 | ||
|   | e806e5a293 | ||
|   | 770951c6da | ||
|   | 7b7c7b528a | ||
|   | a554aa20f8 | ||
|   | ff1ef1f184 | ||
|   | bf1efaf912 | ||
|   | ff2bc41317 | ||
|   | bdaf8aff15 | ||
|   | 1dc4f9f6bb | ||
|   | d6320f5da1 | ||
|   | 276be5e857 | ||
|   | 3101e77ecc | ||
|   | ab6dae16f1 | ||
|   | 36222d79c6 | ||
|   | 08656a6674 | ||
|   | 8034317796 | ||
|   | a59f3a550e | ||
|   | 415b967c0b | ||
|   | 642499d519 | ||
|   | fa0a54eee7 | ||
|   | 82f175f6c7 | ||
|   | 8df549e8d9 | ||
|   | 9fd720777b | ||
|   | 4c68c725b1 | ||
|   | 06a5207c6d | ||
|   | 78b885c508 | ||
|   | a18a3b6099 | ||
|   | 68949e070c | ||
|   | 654333dabe | ||
|   | 81b8a76f1b | ||
|   | 31736fa194 | ||
|   | 33632ef1dc | ||
|   | 94305ed82c | ||
|   | c8abc45e25 | ||
|   | cd25459ac9 | ||
|   | aaf1eb8059 | ||
|   | 1589e4236a | ||
|   | 8ca202d0a9 | ||
|   | d2f7904118 | ||
|   | 2d059fb856 | ||
|   | 8bbd7898bb | ||
|   | ea6f8c9a50 | ||
|   | d152440436 | ||
|   | 1ffe76a525 | ||
|   | a6825f530c | ||
|   | 7cf6dc386f | ||
|   | 5d8252bb07 | ||
|   | e1e1e0fb2f | ||
|   | 4f7345563f | ||
|   | 68a2b9f3a8 | ||
|   | d79402c568 | ||
|   | d0e8f650be | ||
|   | d819c6efe2 | ||
|   | 91c877f234 | ||
|   | 55a674ba7b | ||
|   | 36055b7c04 | ||
|   | 7db4ac8ff6 | ||
|   | fe3cc661d3 | ||
|   | eb7efae1cc | ||
|   | 63f8fb54d4 | ||
|   | 097fa2e655 | ||
|   | 3d0b4d51c2 | ||
|   | 37650487f7 | ||
|   | 6ccc0b4fbf | ||
|   | 79fe95372d | ||
|   | 6adb190d0e | ||
|   | c094e6c6f4 | ||
|   | 8c796b4e57 | ||
|   | c08e9a89ff | ||
|   | 03829af2ad | ||
|   | cc8ba18ccc | ||
|   | 57c671a42e | ||
|   | 1ee6ecf3ae | ||
|   | 5f80b3773b | ||
|   | 8452455050 | ||
|   | e5d8f60cdb | ||
|   | de466000a0 | ||
|   | 49664c011c | ||
|   | cf34d6b127 | ||
|   | e52f6ce099 | ||
|   | 1ecdc4c487 | ||
|   | d38e2c49cb | ||
|   | f8464fa76f | ||
|   | 308ae2cb2e | ||
|   | 88219659fb | ||
|   | c34c2df061 | ||
|   | 99c7bd4c10 | ||
|   | 322cd94be7 | ||
|   | f93d5a6fbf | ||
|   | cd116667be | ||
|   | 2f2de3952d | ||
|   | 45e56798c5 | ||
|   | 0664d480e6 | ||
|   | 5d31806fb7 | ||
|   | 283599ddf5 | ||
|   | 09e3ceefa0 | ||
|   | 87f76ebfe4 | ||
|   | 384f0efcb3 | ||
|   | 55a195b841 | ||
|   | c7946fd7b3 | ||
|   | 5d3ba3199c | ||
|   | f0d10306fc | ||
|   | 161943b5b8 | ||
|   | e545043a26 | ||
|   | 40fb58b5b7 | ||
|   | 1f9d4708b3 | ||
|   | 162809e081 | ||
|   | 482c871ac2 | ||
|   | f0b3311630 | ||
|   | 656d6d6c3f | ||
|   | ea45f0f4aa | ||
|   | 6a25cb0a58 | ||
|   | 4a1901420d | ||
|   | ad64f00608 | ||
|   | 65aea29cb9 | ||
|   | 7981424e9a | ||
|   | 10c4340475 | ||
|   | 0a95db1a51 | ||
|   | ace77dce65 | ||
|   | c1d58bb25f | ||
|   | fbcafe0f62 | ||
|   | 9a9d9222a9 | ||
|   | 221e5b4f6c | ||
|   | 5df74aee68 | ||
|   | 3b195e9c7d | ||
|   | 17838d8040 | ||
|   | 2248c2da08 | ||
|   | 532c0e98db | ||
|   | ef3bae1312 | ||
|   | 98c9cc92b8 | ||
|   | ecd8372efa | ||
|   | 76fc0b01fa | ||
|   | f82ca91a61 | ||
|   | f86b8cce16 | ||
|   | b06c234b59 | ||
|   | 31a7ebd4f1 | ||
|   | c83e44ff1c | ||
|   | 419285c396 | ||
|   | 3f6f893e29 | ||
|   | 14e8c7a401 | ||
|   | 3dee6db5e2 | ||
|   | 98c8e19d93 | ||
|   | a8ba3b3fdb | ||
|   | 1b31a472a5 | ||
|   | e5fe8fd975 | ||
|   | b36d1ca2bc | ||
|   | e9730ced9e | ||
|   | ff2f79b087 | ||
|   | 76e163473a | ||
|   | 6fdc24ab21 | ||
|   | cb93124ee1 | ||
|   | 4bda4080d2 | ||
|   | e710cafb2c | ||
|   | e9643ad07f | ||
|   | ca16892237 | ||
|   | a2b0d350d8 | ||
|   | 534bdbae50 | ||
|   | 831873e7de | ||
|   | 622d246fdd | ||
|   | 4a1ca9f299 | ||
|   | 4e1de2678c | ||
|   | 33a4792531 | ||
|   | 37dd4856a6 | ||
|   | 6a9cf3389d | ||
|   | 2da2395473 | ||
|   | 00ecd27bb3 | ||
|   | 0fa4486dcf | 
							
								
								
									
										69
									
								
								API.md
									
									
									
									
									
								
							
							
						
						| @@ -52,7 +52,6 @@ | ||||
|     - [The URL Status Indicator](#the-url-status-indicator) | ||||
|     - [Creating a Simple Indicator](#creating-a-simple-indicator) | ||||
|     - [Custom Indicators](#custom-indicators) | ||||
|   - [Included Plugins](#included-plugins) | ||||
|  | ||||
| <!-- END doctoc generated TOC please keep comment here to allow auto update --> | ||||
|  | ||||
| @@ -232,7 +231,7 @@ attributes | ||||
|     of this object. This is used for specifying an icon to appear next to each  | ||||
|     object of this type. | ||||
|  | ||||
| The [Open MCT Tutorials](https://github.com/openmct/openmct-tutorial) provide a  | ||||
| The [Open MCT Tutorials](https://github.com/nasa/openmct-tutorial) provide a  | ||||
| step-by-step examples of writing code for Open MCT that includes a [section on  | ||||
| defining a new object type](https://github.com/nasa/openmct-tutorial#step-3---providing-objects). | ||||
|  | ||||
| @@ -424,7 +423,7 @@ attribute      | type   | flags    | notes | ||||
|  | ||||
| ###### Value Hints | ||||
|  | ||||
| Each telemetry value description has an object defining hints.  Keys in this this object represent the hint itself, and the value represents the weight of that hint.  A lower weight means the hint has a higher priority.  For example, multiple values could be hinted for use as the y axis of a plot (raw, engineering), but the highest priority would be the default choice.  Likewise, a table will use hints to determine the default order of columns. | ||||
| Each telemetry value description has an object defining hints.  Keys in this this object represent the hint itself, and the value represents the weight of that hint.  A lower weight means the hint has a higher priority.  For example, multiple values could be hinted for use as the y-axis of a plot (raw, engineering), but the highest priority would be the default choice.  Likewise, a table will use hints to determine the default order of columns. | ||||
|  | ||||
| Known hints: | ||||
|  | ||||
| @@ -506,7 +505,7 @@ example: | ||||
| } | ||||
| ``` | ||||
|  | ||||
| This strategy says "I want the lastest data point in this time range".  A provider which recognizes this request should return only one value-- the latest-- in the requested time range.  Depending on your back-end implementation, performing these queries in bulk can be a large performance increase.  These are generally issued by views that are only capable of displaying a single value and only need to show the latest value. | ||||
| This strategy says "I want the latest data point in this time range".  A provider which recognizes this request should return only one value-- the latest-- in the requested time range.  Depending on your back-end implementation, performing these queries in bulk can be a large performance increase.  These are generally issued by views that are only capable of displaying a single value and only need to show the latest value. | ||||
|  | ||||
| ##### `minmax` request strategy | ||||
|  | ||||
| @@ -601,7 +600,7 @@ evaluator, take a look at `examples/generator/SinewaveLimitProvider.js`. | ||||
|  | ||||
| ### Telemetry Consumer APIs **draft** | ||||
|  | ||||
| The APIs for requesting telemetry from Open MCT -- e.g. for use in custom views -- are currently in draft state and are being revised.  If you'd like to experiement with them before they are finalized, please contact the team via the contact-us link on our website. | ||||
| The APIs for requesting telemetry from Open MCT -- e.g. for use in custom views -- are currently in draft state and are being revised.  If you'd like to experiment with them before they are finalized, please contact the team via the contact-us link on our website. | ||||
|  | ||||
|  | ||||
| ## Time API | ||||
| @@ -989,7 +988,7 @@ A common use case for indicators is to convey the state of some external system | ||||
| persistence backend or HTTP server. So long as this system is accessible via HTTP request,  | ||||
| Open MCT provides a general purpose indicator to show whether the server is available and  | ||||
| returing a 2xx status code. The URL Status Indicator is made available as a default plugin. See | ||||
| [Included Plugins](#included-plugins) below for details on how to install and configure the  | ||||
| the [documentation](./src/plugins/URLIndicatorPlugin) for details on how to install and configure the  | ||||
| URL Status Indicator. | ||||
|  | ||||
| ### Creating a Simple Indicator | ||||
| @@ -1028,7 +1027,7 @@ different colors to indicate status. | ||||
|  | ||||
| ### Custom Indicators | ||||
|  | ||||
| A completely custom indicator can be added by simple providing a DOM element to place alongside other indicators. | ||||
| A completely custom indicator can be added by simply providing a DOM element to place alongside other indicators. | ||||
|  | ||||
| ``` javascript | ||||
|     var domNode = document.createElement('div'); | ||||
| @@ -1041,59 +1040,3 @@ A completely custom indicator can be added by simple providing a DOM element to | ||||
|         element: domNode | ||||
|     }); | ||||
| ``` | ||||
|  | ||||
| ## Included Plugins | ||||
|  | ||||
| Open MCT is packaged along with a few general-purpose plugins: | ||||
|  | ||||
| * `openmct.plugins.Conductor` provides a user interface for working with time | ||||
| within the application. If activated, configuration must be provided. This is  | ||||
| detailed in the section on [Time Conductor Configuration](#time-conductor-configuration). | ||||
| * `openmct.plugins.CouchDB` is an adapter for using CouchDB for persistence | ||||
|   of user-created objects. This is a constructor that takes the URL for the | ||||
|   CouchDB database as a parameter, e.g. | ||||
| ```javascript | ||||
| openmct.install(openmct.plugins.CouchDB('http://localhost:5984/openmct')) | ||||
| ``` | ||||
| * `openmct.plugins.Elasticsearch` is an adapter for using Elasticsearch for | ||||
|   persistence of user-created objects. This is a | ||||
|   constructor that takes the URL for the Elasticsearch instance as a | ||||
|   parameter. eg. | ||||
| ```javascript | ||||
| openmct.install(openmct.plugins.CouchDB('http://localhost:9200')) | ||||
| ``` | ||||
| * `openmct.plugins.Espresso` and `openmct.plugins.Snow` are two different | ||||
|   themes (dark and light) available for Open MCT. Note that at least one | ||||
|   of these themes must be installed for Open MCT to appear correctly. | ||||
| * `openmct.plugins.URLIndicator` adds an indicator which shows the | ||||
| availability of a URL with the following options:  | ||||
|   - `url` : URL to indicate the status of | ||||
|   - `iconClass`: Icon to show in the status bar, defaults to `icon-database`, [list of all icons](https://nasa.github.io/openmct/style-guide/#/browse/styleguide:home?view=items) | ||||
|   - `interval`: Interval between checking the connection, defaults to `10000` | ||||
|   - `label` Name showing up as text in the status bar, defaults to url | ||||
| ```javascript | ||||
| openmct.install(openmct.plugins.URLIndicator({ | ||||
|   url: 'http://localhost:8080', | ||||
|     iconClass: 'check', | ||||
|     interval: 10000, | ||||
|     label: 'Localhost' | ||||
|  }) | ||||
| ); | ||||
| ``` | ||||
| * `openmct.plugins.LocalStorage` provides persistence of user-created | ||||
|   objects in browser-local storage. This is particularly useful in | ||||
|   development environments. | ||||
| * `openmct.plugins.MyItems` adds a top-level folder named "My Items" | ||||
|   when the application is first started, providing a place for a | ||||
|   user to store created items. | ||||
| * `openmct.plugins.UTCTimeSystem` provides a default time system for Open MCT. | ||||
|  | ||||
| Generally, you will want to either install these plugins, or install | ||||
| different plugins that provide persistence and an initial folder | ||||
| hierarchy. | ||||
|  | ||||
| eg. | ||||
| ```javascript | ||||
| openmct.install(openmct.plugins.LocalStorage()); | ||||
| openmct.install(openmct.plugins.MyItems()); | ||||
| ``` | ||||
|   | ||||
							
								
								
									
										7
									
								
								LICENSE.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,7 @@ | ||||
| # Open MCT License | ||||
|  | ||||
| Open MCT, Copyright (c) 2014-2019, United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All rights reserved. | ||||
|  | ||||
| Open MCT is licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.  You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  | ||||
| Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. | ||||
							
								
								
									
										691
									
								
								LICENSES.md
									
									
									
									
									
								
							
							
						
						| @@ -1,691 +0,0 @@ | ||||
| # Open MCT Licenses | ||||
|  | ||||
| Open MCT, Copyright (c) 2014-2017, 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 as follows. | ||||
|  | ||||
| ## Software Components Licenses | ||||
|  | ||||
| This software includes components released under the following licenses: | ||||
|  | ||||
| --- | ||||
|  | ||||
| ### SuperSocket | ||||
|  | ||||
| #### Info | ||||
|  | ||||
| * Link: https://supersocket.codeplex.com/ | ||||
|  | ||||
| * Version: 0.9.0.2 | ||||
|  | ||||
| * Author: Kerry Jiang | ||||
|  | ||||
| * Description: Supports SuperWebSocket | ||||
|  | ||||
| #### License | ||||
|  | ||||
| Copyright 2012 Kerry Jiang (kerry-jiang@hotmail.com) | ||||
|  | ||||
| SuperSocket 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. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ### SuperWebSocket | ||||
|  | ||||
| #### Info | ||||
|  | ||||
| * Link: https://superswebocket.codeplex.com/ | ||||
|  | ||||
| * Version: 0.9.0.2 | ||||
|  | ||||
| * Author: Kerry Jiang | ||||
|  | ||||
| * Description: WebSocket implementation for client-server communication | ||||
|  | ||||
| #### License | ||||
|  | ||||
| Copyright 2010-2013 Kerry Jiang (kerry-jiang@hotmail.com) | ||||
|  | ||||
| SuperWebSocket 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. | ||||
|  | ||||
|  | ||||
| --- | ||||
|  | ||||
| ### log4net | ||||
|  | ||||
| #### Info | ||||
|  | ||||
| * Link: http://logging.apache.org/log4net/ | ||||
|  | ||||
| * Version: 1.2.13 | ||||
|  | ||||
| * Author: Apache Software Foundation | ||||
|  | ||||
| * Description: Logging. | ||||
|  | ||||
| #### License | ||||
|  | ||||
| Copyright © 2004-2015 Apache Software Foundation. | ||||
|  | ||||
| log4net 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. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ### Blanket.js | ||||
|  | ||||
| #### Info | ||||
|  | ||||
| * Link: http://blanketjs.org/ | ||||
|  | ||||
| * Version: 1.1.5 | ||||
|  | ||||
| * Author: Alex Seville | ||||
|  | ||||
| * Description: Code coverage measurement and reporting | ||||
|  | ||||
| #### License | ||||
|  | ||||
| Copyright (c) 2013 Alex Seville | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: | ||||
|  | ||||
| The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | ||||
|  | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ### Jasmine | ||||
|  | ||||
| #### Info | ||||
|  | ||||
| * Link: http://jasmine.github.io/ | ||||
|  | ||||
| * Version: 1.3.1 | ||||
|  | ||||
| * Author: Pivotal Labs | ||||
|  | ||||
| * Description: Unit testing | ||||
|  | ||||
| #### License | ||||
|  | ||||
| Copyright (c) 2008-2011 Pivotal Labs | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: | ||||
|  | ||||
| The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | ||||
|  | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ### RequireJS | ||||
|  | ||||
| #### Info | ||||
|  | ||||
| * Link: http://requirejs.org/ | ||||
|  | ||||
| * Version: 2.1.22 | ||||
|  | ||||
| * Author: The Dojo Foundation | ||||
|  | ||||
| * Description: Script loader | ||||
|  | ||||
| #### License | ||||
|  | ||||
| Copyright (c) 2010-2015, The Dojo Foundation | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: | ||||
|  | ||||
| The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | ||||
|  | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ### requirejs-text | ||||
|  | ||||
| #### Info | ||||
|  | ||||
| * Link: https://github.com/requirejs/text | ||||
|  | ||||
| * Version: 2.0.14 | ||||
|  | ||||
| * Author: The Dojo Foundation | ||||
|  | ||||
| * Description: Text loading plugin for RequireJS | ||||
|  | ||||
| #### License | ||||
|  | ||||
| Copyright (c) 2010-2014, The Dojo Foundation | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| of this software and associated documentation files (the "Software"), to deal | ||||
| in the Software without restriction, including without limitation the rights | ||||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
| copies of the Software, and to permit persons to whom the Software is | ||||
| furnished to do so, subject to the following conditions: | ||||
|  | ||||
| The above copyright notice and this permission notice shall be included in | ||||
| all copies or substantial portions of the Software. | ||||
|  | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||||
| THE SOFTWARE. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ### AngularJS | ||||
|  | ||||
| #### Info | ||||
|  | ||||
| * Link: http://angularjs.org/ | ||||
|  | ||||
| * Version: 1.4.4 | ||||
|  | ||||
| * Author: Google | ||||
|  | ||||
| * Description: Client-side web application framework | ||||
|  | ||||
| #### License | ||||
|  | ||||
| Copyright (c) 2010-2015 Google, Inc. http://angularjs.org | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: | ||||
|  | ||||
| The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | ||||
|  | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ### Angular-Route | ||||
|  | ||||
| #### Info | ||||
|  | ||||
| * Link: http://angularjs.org/ | ||||
|  | ||||
| * Version: 1.4.4 | ||||
|  | ||||
| * Author: Google | ||||
|  | ||||
| * Description: Client-side view routing | ||||
|  | ||||
| #### License | ||||
|  | ||||
| Copyright (c) 2010-2015 Google, Inc. http://angularjs.org | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: | ||||
|  | ||||
| The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | ||||
|  | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ### ES6-Promise | ||||
|  | ||||
| #### Info | ||||
|  | ||||
| * Link: https://github.com/jakearchibald/es6-promise | ||||
|  | ||||
| * Version: 3.0.2 | ||||
|  | ||||
| * Authors: Yehuda Katz, Tom Dale, Stefan Penner and contributors | ||||
|  | ||||
| * Description: Promise polyfill for pre-ECMAScript 6 browsers | ||||
|  | ||||
| #### License | ||||
|  | ||||
| Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: | ||||
|  | ||||
| The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | ||||
|  | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ### screenfull.js | ||||
|  | ||||
| #### Info | ||||
|  | ||||
| * Link: https://github.com/sindresorhus/screenfull.js/ | ||||
|  | ||||
| * Version: 3.0.0 | ||||
|  | ||||
| * Author: Sindre Sorhus | ||||
|  | ||||
| * Description: Wrapper for cross-browser usage of fullscreen API | ||||
|  | ||||
| #### License | ||||
|  | ||||
| Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com) | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: | ||||
|  | ||||
| The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | ||||
|  | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ### Math.uuid.js | ||||
|  | ||||
| #### Info | ||||
|  | ||||
| * Link: https://github.com/broofa/node-uuid | ||||
|  | ||||
| * Version: 1.4.7 | ||||
|  | ||||
| * Author: Robert Kieffer | ||||
|  | ||||
| * Description: Unique identifer generation. | ||||
|  | ||||
| #### License | ||||
|  | ||||
| Copyright (c) 2010-2012 Robert Kieffer | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: | ||||
|  | ||||
| The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | ||||
|  | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ### Normalize.css | ||||
|  | ||||
| #### Info | ||||
|  | ||||
| * Link: https://github.com/necolas/normalize.css | ||||
|  | ||||
| * Version: 1.1.2 | ||||
|  | ||||
| * Authors: Nicolas Gallagher, Jonathan Neal | ||||
|  | ||||
| * Description: Browser style normalization | ||||
|  | ||||
| #### License | ||||
|  | ||||
| Copyright (c) Nicolas Gallagher and Jonathan Neal | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: | ||||
|  | ||||
| The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | ||||
|  | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ### Moment.js | ||||
|  | ||||
| #### Info | ||||
|  | ||||
| * Link: http://momentjs.com | ||||
|  | ||||
| * Version: 2.11.1 | ||||
|  | ||||
| * Authors: Tim Wood, Iskren Chernev, Moment.js contributors | ||||
|  | ||||
| * Description: Time/date parsing/formatting | ||||
|  | ||||
| #### License | ||||
|  | ||||
| Copyright (c) 2011-2014 Tim Wood, Iskren Chernev, Moment.js contributors | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: | ||||
|  | ||||
| The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | ||||
|  | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ### moment-duration-format | ||||
|  | ||||
| #### Info | ||||
|  | ||||
| * Link: https://github.com/jsmreese/moment-duration-format | ||||
|  | ||||
| * Version: 1.3.0 | ||||
|  | ||||
| * Authors: John Madhavan-Reese | ||||
|  | ||||
| * Description: Duration parsing/formatting | ||||
|  | ||||
| #### License | ||||
|  | ||||
| Copyright 2014 John Madhavan-Reese | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy of | ||||
| this software and associated documentation files (the "Software"), to deal in | ||||
| the Software without restriction, including without limitation the rights to | ||||
| use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of | ||||
| the Software, and to permit persons to whom the Software is furnished to do so, | ||||
| subject to the following conditions: | ||||
|  | ||||
| The above copyright notice and this permission notice shall be included in all | ||||
| copies or substantial portions of the Software. | ||||
|  | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS | ||||
| FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR | ||||
| COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER | ||||
| IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | ||||
| CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ### CSV.js | ||||
|  | ||||
| #### Info | ||||
|  | ||||
| * Link: https://github.com/knrz/CSV.js | ||||
|  | ||||
| * Version: 3.6.4 | ||||
|  | ||||
| * Authors: Kash Nouroozi | ||||
|  | ||||
| * Description: Encoder for CSV (comma separated values) export | ||||
|  | ||||
| #### License | ||||
|  | ||||
| Copyright (c) 2014 Kash Nouroozi | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| of this software and associated documentation files (the "Software"), to deal | ||||
| in the Software without restriction, including without limitation the rights | ||||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
| copies of the Software, and to permit persons to whom the Software is | ||||
| furnished to do so, subject to the following conditions: | ||||
|  | ||||
| The above copyright notice and this permission notice shall be included in all | ||||
| copies or substantial portions of the Software. | ||||
|  | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
| SOFTWARE. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ### FileSaver.js | ||||
|  | ||||
| #### Info | ||||
|  | ||||
| * Link: https://github.com/eligrey/FileSaver.js/ | ||||
|  | ||||
| * Version: 0.0.2 | ||||
|  | ||||
| * Authors: Eli Grey | ||||
|  | ||||
| * Description: File download initiator (for file exports) | ||||
|  | ||||
| #### License | ||||
|  | ||||
| Copyright © 2015 Eli Grey. | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: | ||||
|  | ||||
| The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | ||||
|  | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ### Zepto | ||||
|  | ||||
| #### Info | ||||
|  | ||||
| * Link: http://zeptojs.com/ | ||||
|  | ||||
| * Version: 1.1.6 | ||||
|  | ||||
| * Authors: Thomas Fuchs | ||||
|  | ||||
| * Description: DOM manipulation | ||||
|  | ||||
| #### License | ||||
|  | ||||
| Copyright (c) 2010-2016 Thomas Fuchs | ||||
| http://zeptojs.com/ | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining | ||||
| a copy of this software and associated documentation files (the | ||||
| "Software"), to deal in the Software without restriction, including | ||||
| without limitation the rights to use, copy, modify, merge, publish, | ||||
| distribute, sublicense, and/or sell copies of the Software, and to | ||||
| permit persons to whom the Software is furnished to do so, subject to | ||||
| the following conditions: | ||||
|  | ||||
| The above copyright notice and this permission notice shall be | ||||
| included in all copies or substantial portions of the Software. | ||||
|  | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||||
| EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||||
| MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||||
| NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | ||||
| LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | ||||
| OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | ||||
| WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ### Json.NET | ||||
|  | ||||
| #### Info | ||||
|  | ||||
| * Link: http://www.newtonsoft.com/json | ||||
|  | ||||
| * Version: 6.0.8 | ||||
|  | ||||
| * Author: Newtonsoft | ||||
|  | ||||
| * Description: JSON serialization/deserialization | ||||
|  | ||||
| #### License | ||||
|  | ||||
| Copyright (c) 2007 James Newton-King | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: | ||||
|  | ||||
| The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | ||||
|  | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ### Nancy | ||||
|  | ||||
| #### Info | ||||
|  | ||||
| * Link: http://nancyfx.org | ||||
|  | ||||
| * Version: 0.23.2 | ||||
|  | ||||
| * Author: Andreas Håkansson, Steven Robbins and contributors | ||||
|  | ||||
| * Description: Embedded web server | ||||
|  | ||||
| #### License | ||||
|  | ||||
| Copyright © 2010 Andreas Håkansson, Steven Robbins and contributors | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: | ||||
|  | ||||
| The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | ||||
|  | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ### Nancy.Hosting.Self | ||||
|  | ||||
| #### Info | ||||
|  | ||||
| * Link: http://nancyfx.org | ||||
|  | ||||
| * Version: 0.23.2 | ||||
|  | ||||
| * Author: Andreas Håkansson, Steven Robbins and contributors | ||||
|  | ||||
| * Description: Embedded web server | ||||
|  | ||||
| #### License | ||||
|  | ||||
| Copyright © 2010 Andreas Håkansson, Steven Robbins and contributors | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: | ||||
|  | ||||
| The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | ||||
|  | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ### Almond | ||||
|  | ||||
| * Link: https://github.com/requirejs/almond | ||||
|  | ||||
| * Version: 0.3.3 | ||||
|  | ||||
| * Author: jQuery Foundation | ||||
|  | ||||
| * Description: Lightweight RequireJS replacement for builds | ||||
|  | ||||
| #### License | ||||
|  | ||||
| Copyright jQuery Foundation and other contributors, https://jquery.org/ | ||||
|  | ||||
| This software consists of voluntary contributions made by many | ||||
| individuals. For exact contribution history, see the revision history | ||||
| available at https://github.com/requirejs/almond | ||||
|  | ||||
| The following license applies to all parts of this software except as | ||||
| documented below: | ||||
|  | ||||
| ==== | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining | ||||
| a copy of this software and associated documentation files (the | ||||
| "Software"), to deal in the Software without restriction, including | ||||
| without limitation the rights to use, copy, modify, merge, publish, | ||||
| distribute, sublicense, and/or sell copies of the Software, and to | ||||
| permit persons to whom the Software is furnished to do so, subject to | ||||
| the following conditions: | ||||
|  | ||||
| The above copyright notice and this permission notice shall be | ||||
| included in all copies or substantial portions of the Software. | ||||
|  | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||||
| EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||||
| MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||||
| NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | ||||
| LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | ||||
| OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | ||||
| WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||||
|  | ||||
| ==== | ||||
|  | ||||
| Copyright and related rights for sample code are waived via CC0. Sample | ||||
| code is defined as all source code displayed within the prose of the | ||||
| documentation. | ||||
|  | ||||
| CC0: http://creativecommons.org/publicdomain/zero/1.0/ | ||||
|  | ||||
| ==== | ||||
|  | ||||
| Files located in the node_modules directory, and certain utilities used | ||||
| to build or test the software in the test and dist directories, are | ||||
| externally maintained libraries used by this software which have their own | ||||
| licenses; we recommend you read them, as their terms may differ from the | ||||
| terms above. | ||||
|  | ||||
|  | ||||
| ### Lodash | ||||
|  | ||||
| * Link: https://lodash.com | ||||
|  | ||||
| * Version: 3.10.1 | ||||
|  | ||||
| * Author: Dojo Foundation | ||||
|  | ||||
| * Description: Utility functions | ||||
|  | ||||
| #### License | ||||
|  | ||||
| Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/> | ||||
| Based on Underscore.js, copyright 2009-2015 Jeremy Ashkenas, | ||||
| DocumentCloud and Investigative Reporters & Editors <http://underscorejs.org/> | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining | ||||
| a copy of this software and associated documentation files (the | ||||
| "Software"), to deal in the Software without restriction, including | ||||
| without limitation the rights to use, copy, modify, merge, publish, | ||||
| distribute, sublicense, and/or sell copies of the Software, and to | ||||
| permit persons to whom the Software is furnished to do so, subject to | ||||
| the following conditions: | ||||
|  | ||||
| The above copyright notice and this permission notice shall be | ||||
| included in all copies or substantial portions of the Software. | ||||
|  | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||||
| EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||||
| MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||||
| NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | ||||
| LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | ||||
| OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | ||||
| WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||||
|  | ||||
| ### EventEmitter3 | ||||
|  | ||||
| * Link: https://github.com/primus/eventemitter3 | ||||
|  | ||||
| * Version: 1.2.0 | ||||
|  | ||||
| * Author: Arnout Kazemier | ||||
|  | ||||
| * Description: Event-driven programming support | ||||
|  | ||||
| #### License | ||||
|  | ||||
| The MIT License (MIT) | ||||
|  | ||||
| Copyright (c) 2014 Arnout Kazemier | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| of this software and associated documentation files (the "Software"), to deal | ||||
| in the Software without restriction, including without limitation the rights | ||||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
| copies of the Software, and to permit persons to whom the Software is | ||||
| furnished to do so, subject to the following conditions: | ||||
|  | ||||
| The above copyright notice and this permission notice shall be included in all | ||||
| copies or substantial portions of the Software. | ||||
|  | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
| SOFTWARE. | ||||
							
								
								
									
										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 || 'localhost'; | ||||
| options.directory = options.directory || options.D || '.'; | ||||
|  | ||||
| // Show command line options | ||||
|   | ||||
							
								
								
									
										80
									
								
								coverage/HeadlessChrome 0.0.0 (Mac OS X 10.14.6)/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,80 @@ | ||||
| <!doctype html> | ||||
| <html lang="en"> | ||||
| <head> | ||||
|     <title>Code coverage report for All files</title> | ||||
|     <meta charset="utf-8" /> | ||||
|     <link rel="stylesheet" href="prettify.css" /> | ||||
|     <link rel="stylesheet" href="base.css" /> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1"> | ||||
|     <style type='text/css'> | ||||
|         .coverage-summary .sorter { | ||||
|             background-image: url(sort-arrow-sprite.png); | ||||
|         } | ||||
|     </style> | ||||
| </head> | ||||
| <body> | ||||
| <div class='wrapper'> | ||||
|   <div class='pad1'> | ||||
|     <h1> | ||||
|       / | ||||
|     </h1> | ||||
|     <div class='clearfix'> | ||||
|       <div class='fl pad1y space-right2'> | ||||
|         <span class="strong">100% </span> | ||||
|         <span class="quiet">Statements</span> | ||||
|         <span class='fraction'>0/0</span> | ||||
|       </div> | ||||
|       <div class='fl pad1y space-right2'> | ||||
|         <span class="strong">100% </span> | ||||
|         <span class="quiet">Branches</span> | ||||
|         <span class='fraction'>0/0</span> | ||||
|       </div> | ||||
|       <div class='fl pad1y space-right2'> | ||||
|         <span class="strong">100% </span> | ||||
|         <span class="quiet">Functions</span> | ||||
|         <span class='fraction'>0/0</span> | ||||
|       </div> | ||||
|       <div class='fl pad1y space-right2'> | ||||
|         <span class="strong">100% </span> | ||||
|         <span class="quiet">Lines</span> | ||||
|         <span class='fraction'>0/0</span> | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
|   <div class='status-line high'></div> | ||||
| <div class="pad1"> | ||||
| <table class="coverage-summary"> | ||||
| <thead> | ||||
| <tr> | ||||
|    <th data-col="file" data-fmt="html" data-html="true" class="file">File</th> | ||||
|    <th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th> | ||||
|    <th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th> | ||||
|    <th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th> | ||||
|    <th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th> | ||||
|    <th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th> | ||||
|    <th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th> | ||||
|    <th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th> | ||||
|    <th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th> | ||||
|    <th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th> | ||||
| </tr> | ||||
| </thead> | ||||
| <tbody></tbody> | ||||
| </table> | ||||
| </div><div class='push'></div><!-- for sticky footer --> | ||||
| </div><!-- /wrapper --> | ||||
| <div class='footer quiet pad2 space-top1 center small'> | ||||
|   Code coverage | ||||
|   generated by <a href="http://istanbul-js.org/" target="_blank">istanbul</a> at Wed Dec 11 2019 13:15:10 GMT-0800 (Pacific Standard Time) | ||||
| </div> | ||||
| </div> | ||||
| <script src="prettify.js"></script> | ||||
| <script> | ||||
| window.onload = function () { | ||||
|         if (typeof prettyPrint === 'function') { | ||||
|             prettyPrint(); | ||||
|         } | ||||
| }; | ||||
| </script> | ||||
| <script src="sorter.js"></script> | ||||
| </body> | ||||
| </html> | ||||
| After Width: | Height: | Size: 209 B | 
							
								
								
									
										158
									
								
								coverage/HeadlessChrome 0.0.0 (Mac OS X 10.14.6)/sorter.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,158 @@ | ||||
| var addSorting = (function () { | ||||
|     "use strict"; | ||||
|     var cols, | ||||
|         currentSort = { | ||||
|             index: 0, | ||||
|             desc: false | ||||
|         }; | ||||
|  | ||||
|     // returns the summary table element | ||||
|     function getTable() { return document.querySelector('.coverage-summary'); } | ||||
|     // returns the thead element of the summary table | ||||
|     function getTableHeader() { return getTable().querySelector('thead tr'); } | ||||
|     // returns the tbody element of the summary table | ||||
|     function getTableBody() { return getTable().querySelector('tbody'); } | ||||
|     // returns the th element for nth column | ||||
|     function getNthColumn(n) { return getTableHeader().querySelectorAll('th')[n]; } | ||||
|  | ||||
|     // loads all columns | ||||
|     function loadColumns() { | ||||
|         var colNodes = getTableHeader().querySelectorAll('th'), | ||||
|             colNode, | ||||
|             cols = [], | ||||
|             col, | ||||
|             i; | ||||
|  | ||||
|         for (i = 0; i < colNodes.length; i += 1) { | ||||
|             colNode = colNodes[i]; | ||||
|             col = { | ||||
|                 key: colNode.getAttribute('data-col'), | ||||
|                 sortable: !colNode.getAttribute('data-nosort'), | ||||
|                 type: colNode.getAttribute('data-type') || 'string' | ||||
|             }; | ||||
|             cols.push(col); | ||||
|             if (col.sortable) { | ||||
|                 col.defaultDescSort = col.type === 'number'; | ||||
|                 colNode.innerHTML = colNode.innerHTML + '<span class="sorter"></span>'; | ||||
|             } | ||||
|         } | ||||
|         return cols; | ||||
|     } | ||||
|     // attaches a data attribute to every tr element with an object | ||||
|     // of data values keyed by column name | ||||
|     function loadRowData(tableRow) { | ||||
|         var tableCols = tableRow.querySelectorAll('td'), | ||||
|             colNode, | ||||
|             col, | ||||
|             data = {}, | ||||
|             i, | ||||
|             val; | ||||
|         for (i = 0; i < tableCols.length; i += 1) { | ||||
|             colNode = tableCols[i]; | ||||
|             col = cols[i]; | ||||
|             val = colNode.getAttribute('data-value'); | ||||
|             if (col.type === 'number') { | ||||
|                 val = Number(val); | ||||
|             } | ||||
|             data[col.key] = val; | ||||
|         } | ||||
|         return data; | ||||
|     } | ||||
|     // loads all row data | ||||
|     function loadData() { | ||||
|         var rows = getTableBody().querySelectorAll('tr'), | ||||
|             i; | ||||
|  | ||||
|         for (i = 0; i < rows.length; i += 1) { | ||||
|             rows[i].data = loadRowData(rows[i]); | ||||
|         } | ||||
|     } | ||||
|     // sorts the table using the data for the ith column | ||||
|     function sortByIndex(index, desc) { | ||||
|         var key = cols[index].key, | ||||
|             sorter = function (a, b) { | ||||
|                 a = a.data[key]; | ||||
|                 b = b.data[key]; | ||||
|                 return a < b ? -1 : a > b ? 1 : 0; | ||||
|             }, | ||||
|             finalSorter = sorter, | ||||
|             tableBody = document.querySelector('.coverage-summary tbody'), | ||||
|             rowNodes = tableBody.querySelectorAll('tr'), | ||||
|             rows = [], | ||||
|             i; | ||||
|  | ||||
|         if (desc) { | ||||
|             finalSorter = function (a, b) { | ||||
|                 return -1 * sorter(a, b); | ||||
|             }; | ||||
|         } | ||||
|  | ||||
|         for (i = 0; i < rowNodes.length; i += 1) { | ||||
|             rows.push(rowNodes[i]); | ||||
|             tableBody.removeChild(rowNodes[i]); | ||||
|         } | ||||
|  | ||||
|         rows.sort(finalSorter); | ||||
|  | ||||
|         for (i = 0; i < rows.length; i += 1) { | ||||
|             tableBody.appendChild(rows[i]); | ||||
|         } | ||||
|     } | ||||
|     // removes sort indicators for current column being sorted | ||||
|     function removeSortIndicators() { | ||||
|         var col = getNthColumn(currentSort.index), | ||||
|             cls = col.className; | ||||
|  | ||||
|         cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, ''); | ||||
|         col.className = cls; | ||||
|     } | ||||
|     // adds sort indicators for current column being sorted | ||||
|     function addSortIndicators() { | ||||
|         getNthColumn(currentSort.index).className += currentSort.desc ? ' sorted-desc' : ' sorted'; | ||||
|     } | ||||
|     // adds event listeners for all sorter widgets | ||||
|     function enableUI() { | ||||
|         var i, | ||||
|             el, | ||||
|             ithSorter = function ithSorter(i) { | ||||
|                 var col = cols[i]; | ||||
|  | ||||
|                 return function () { | ||||
|                     var desc = col.defaultDescSort; | ||||
|  | ||||
|                     if (currentSort.index === i) { | ||||
|                         desc = !currentSort.desc; | ||||
|                     } | ||||
|                     sortByIndex(i, desc); | ||||
|                     removeSortIndicators(); | ||||
|                     currentSort.index = i; | ||||
|                     currentSort.desc = desc; | ||||
|                     addSortIndicators(); | ||||
|                 }; | ||||
|             }; | ||||
|         for (i =0 ; i < cols.length; i += 1) { | ||||
|             if (cols[i].sortable) { | ||||
|                 // add the click event handler on the th so users | ||||
|                 // dont have to click on those tiny arrows | ||||
|                 el = getNthColumn(i).querySelector('.sorter').parentElement; | ||||
|                 if (el.addEventListener) { | ||||
|                     el.addEventListener('click', ithSorter(i)); | ||||
|                 } else { | ||||
|                     el.attachEvent('onclick', ithSorter(i)); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     // adds sorting functionality to the UI | ||||
|     return function () { | ||||
|         if (!getTable()) { | ||||
|             return; | ||||
|         } | ||||
|         cols = loadColumns(); | ||||
|         loadData(cols); | ||||
|         addSortIndicators(); | ||||
|         enableUI(); | ||||
|     }; | ||||
| })(); | ||||
|  | ||||
| window.addEventListener('load', addSorting); | ||||
| @@ -33,5 +33,5 @@ As we transition to a new API, the following documentation for the old API | ||||
|  * The [Developer's Guide](guide/) goes into more detail about how to use the | ||||
|  platform and the functionality that it provides. | ||||
|  | ||||
|  * The [Tutorials](tutorials/) give examples of extending the platform to add | ||||
|  * The [Tutorials](https://github.com/nasa/openmct-tutorial) give examples of extending the platform to add | ||||
|  functionality, and integrate with data sources. | ||||
|   | ||||
| Before Width: | Height: | Size: 23 KiB | 
| Before Width: | Height: | Size: 39 KiB | 
| Before Width: | Height: | Size: 42 KiB | 
| Before Width: | Height: | Size: 30 KiB | 
| Before Width: | Height: | Size: 34 KiB | 
| Before Width: | Height: | Size: 140 KiB | 
| Before Width: | Height: | Size: 11 KiB | 
| Before Width: | Height: | Size: 16 KiB | 
| Before Width: | Height: | Size: 39 KiB | 
| Before Width: | Height: | Size: 51 KiB | 
| Before Width: | Height: | Size: 25 KiB | 
| Before Width: | Height: | Size: 25 KiB | 
| Before Width: | Height: | Size: 25 KiB | 
| Before Width: | Height: | Size: 31 KiB | 
| Before Width: | Height: | Size: 43 KiB | 
| @@ -38,13 +38,12 @@ | ||||
|         const THIRTY_MINUTES = 30 * 60 * 1000; | ||||
|  | ||||
|         [ | ||||
|             'example/eventGenerator', | ||||
|             'example/styleguide' | ||||
|             'example/eventGenerator' | ||||
|         ].forEach( | ||||
|             openmct.legacyRegistry.enable.bind(openmct.legacyRegistry) | ||||
|         ); | ||||
|  | ||||
|         openmct.install(openmct.plugins.Espresso()); | ||||
|         openmct.install(openmct.plugins.Snow()); | ||||
|         openmct.install(openmct.plugins.MyItems()); | ||||
|         openmct.install(openmct.plugins.LocalStorage()); | ||||
|         openmct.install(openmct.plugins.Generator()); | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "openmct", | ||||
|   "version": "1.0.0-beta", | ||||
|   "version": "1.0.0-snapshot", | ||||
|   "description": "The Open MCT core platform", | ||||
|   "dependencies": {}, | ||||
|   "devDependencies": { | ||||
| @@ -64,6 +64,7 @@ | ||||
|     "request": "^2.69.0", | ||||
|     "split": "^1.0.0", | ||||
|     "style-loader": "^1.0.1", | ||||
|     "uuid": "^3.3.3", | ||||
|     "v8-compile-cache": "^1.1.0", | ||||
|     "vue": "2.5.6", | ||||
|     "vue-loader": "^15.2.6", | ||||
|   | ||||
| @@ -102,14 +102,14 @@ define( | ||||
|          * @returns {Action[]} an array of matching actions | ||||
|          * @memberof platform/core.ActionCapability# | ||||
|          */ | ||||
|         ActionCapability.prototype.perform = function (context) { | ||||
|         ActionCapability.prototype.perform = function (context, flag) { | ||||
|             // Alias to getActions(context)[0].perform, with a | ||||
|             // check for empty arrays. | ||||
|             var actions = this.getActions(context); | ||||
|  | ||||
|             return this.$q.when( | ||||
|                 (actions && actions.length > 0) ? | ||||
|                     actions[0].perform() : | ||||
|                     actions[0].perform(flag) : | ||||
|                     undefined | ||||
|             ); | ||||
|         }; | ||||
|   | ||||
| @@ -91,7 +91,7 @@ define( | ||||
|                 .then(function () { | ||||
|                     return object | ||||
|                         .getCapability('action') | ||||
|                         .perform('remove'); | ||||
|                         .perform('remove', true); | ||||
|                 }); | ||||
|         }; | ||||
|  | ||||
|   | ||||
| @@ -227,10 +227,11 @@ define( | ||||
|                             locationPromise.resolve(); | ||||
|                         }); | ||||
|  | ||||
|                         it("removes object from parent", function () { | ||||
|                         it("removes object from parent without user warning dialog", function () { | ||||
|                             expect(actionCapability.perform) | ||||
|                                 .toHaveBeenCalledWith('remove'); | ||||
|                                 .toHaveBeenCalledWith('remove', true); | ||||
|                         }); | ||||
|  | ||||
|                     }); | ||||
|  | ||||
|                 }); | ||||
| @@ -247,9 +248,9 @@ define( | ||||
|                             .toHaveBeenCalled(); | ||||
|                     }); | ||||
|  | ||||
|                     it("removes object from parent", function () { | ||||
|                     it("removes object from parent without user warning dialog", function () { | ||||
|                         expect(actionCapability.perform) | ||||
|                             .toHaveBeenCalledWith('remove'); | ||||
|                             .toHaveBeenCalledWith('remove', true); | ||||
|                     }); | ||||
|                 }); | ||||
|  | ||||
|   | ||||
| @@ -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/policies/ImageryViewPolicy", | ||||
|     "./src/controllers/ImageryController", | ||||
|     "./src/directives/MCTBackgroundImage", | ||||
|     "./res/templates/imagery.html" | ||||
| ], function ( | ||||
|     ImageryViewPolicy, | ||||
|     ImageryController, | ||||
|     MCTBackgroundImage, | ||||
|     imageryTemplate | ||||
| ) { | ||||
|  | ||||
|     return { | ||||
|         name:"platform/features/imagery", | ||||
|         definition: { | ||||
|             "name": "Plot view for telemetry", | ||||
|             "extensions": { | ||||
|                 "views": [ | ||||
|                     { | ||||
|                         "name": "Imagery", | ||||
|                         "key": "imagery", | ||||
|                         "cssClass": "icon-image", | ||||
|                         "template": imageryTemplate, | ||||
|                         "priority": "preferred", | ||||
|                         "needs": [ | ||||
|                             "telemetry" | ||||
|                         ], | ||||
|                         "editable": false | ||||
|                     } | ||||
|                 ], | ||||
|                 "policies": [ | ||||
|                     { | ||||
|                         "category": "view", | ||||
|                         "implementation": ImageryViewPolicy, | ||||
|                         "depends": [ | ||||
|                             "openmct" | ||||
|                         ] | ||||
|                     } | ||||
|                 ], | ||||
|                 "controllers": [ | ||||
|                     { | ||||
|                         "key": "ImageryController", | ||||
|                         "implementation": ImageryController, | ||||
|                         "depends": [ | ||||
|                             "$scope", | ||||
|                             "$window", | ||||
|                             "$element", | ||||
|                             "openmct" | ||||
|                         ] | ||||
|                     } | ||||
|                 ], | ||||
|                 "directives": [ | ||||
|                     { | ||||
|                         "key": "mctBackgroundImage", | ||||
|                         "implementation": MCTBackgroundImage, | ||||
|                         "depends": [ | ||||
|                             "$document" | ||||
|                         ] | ||||
|                     } | ||||
|                 ] | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
| }); | ||||
| @@ -1,58 +0,0 @@ | ||||
| <div class="t-imagery c-imagery" ng-controller="ImageryController as imagery"> | ||||
|     <mct-split-pane class='abs' anchor="bottom" alias="imagery"> | ||||
|     <div class="split-pane-component has-local-controls l-image-main-wrapper l-flex-col"> | ||||
|         <div class="h-local-controls h-local-controls--overlay-content c-local-controls--show-on-hover l-flex-row c-imagery__lc"> | ||||
|             <span class="holder flex-elem grows c-imagery__lc__sliders"> | ||||
|                 <input class="icon-brightness" type="range" | ||||
|                        min="0" | ||||
|                        max="500" | ||||
|                        ng-model="filters.brightness" /> | ||||
|                 <input class="icon-contrast" type="range" | ||||
|                        min="0" | ||||
|                        max="500" | ||||
|                        ng-model="filters.contrast" /> | ||||
|             </span> | ||||
|             <span class="holder flex-elem t-reset-btn-holder c-imagery__lc__reset-btn"> | ||||
|                 <a class="s-icon-button icon-reset t-btn-reset" | ||||
|                    ng-click="filters = { brightness: 100, contrast: 100 }"></a> | ||||
|             </span> | ||||
|         </div> | ||||
|  | ||||
|         <div class="l-image-main s-image-main flex-elem grows" | ||||
|              ng-class="{ paused: imagery.paused(), stale:false }"> | ||||
|             <div class="image-main" | ||||
|  | ||||
|             mct-background-image="imagery.getImageUrl()" | ||||
|             filters="filters"> | ||||
|             </div> | ||||
|         </div> | ||||
|  | ||||
|         <div class="l-image-main-controlbar flex-elem l-flex-row"> | ||||
|             <div class="l-datetime-w flex-elem grows"> | ||||
|                 <a class="c-button show-thumbs sm hidden icon-thumbs-strip" | ||||
|                     ng-click="showThumbsBubble = (showThumbsBubble) ? false:true"></a> | ||||
|                 <span class="l-time">{{imagery.getTime()}}</span> | ||||
|             </div> | ||||
|             <div class="h-local-controls flex-elem"> | ||||
|                 <a class="c-button icon-pause pause-play" | ||||
|                     ng-click="imagery.paused(!imagery.paused())" | ||||
|                     ng-class="{ 'is-paused': imagery.paused() }"></a> | ||||
|                 <a href="" | ||||
|                     class="s-button l-mag s-mag vsm icon-reset" | ||||
|                     ng-click="clipped = false" | ||||
|                     ng-show="clipped === true" | ||||
|                     title="Not all of image is visible; click to reset."></a> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
|     <mct-splitter></mct-splitter> | ||||
|     <div class="split-pane-component l-image-thumbs-wrapper"> | ||||
|         <div class="l-image-thumb-item" ng-class="{selected: image.selected}" ng-repeat="image in imageHistory track by $index"  | ||||
|             ng-click="imagery.setSelectedImage(image)"  ng-init="imagery.scrollToBottom()"> | ||||
|             <img class="l-thumb"  | ||||
|                 ng-src={{imagery.getImageUrl(image)}}> | ||||
|             <div class="l-time">{{imagery.getTime(image)}}</div> | ||||
|         </div> | ||||
|     </div> | ||||
|     </mct-split-pane> | ||||
| </div> | ||||
| @@ -1,284 +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 views of image telemetry. | ||||
|  * @namespace platform/features/imagery | ||||
|  */ | ||||
|  | ||||
| define( | ||||
|     [ | ||||
|         'zepto', | ||||
|         'lodash' | ||||
|     ], | ||||
|     function ($, _) { | ||||
|  | ||||
|         /** | ||||
|          * Controller for the "Imagery" view of a domain object which | ||||
|          * provides image telemetry. | ||||
|          * @constructor | ||||
|          * @memberof platform/features/imagery | ||||
|          */ | ||||
|  | ||||
|         function ImageryController($scope, $window, element, openmct) { | ||||
|             this.$scope = $scope; | ||||
|             this.$window = $window; | ||||
|             this.openmct = openmct; | ||||
|             this.date = ""; | ||||
|             this.time = ""; | ||||
|             this.zone = ""; | ||||
|             this.imageUrl = ""; | ||||
|             this.requestCount = 0; | ||||
|             this.scrollable = $(".l-image-thumbs-wrapper"); | ||||
|             this.autoScroll = openmct.time.clock() ? true : false; | ||||
|             this.$scope.imageHistory = []; | ||||
|             this.$scope.filters = { | ||||
|                 brightness: 100, | ||||
|                 contrast: 100 | ||||
|             }; | ||||
|  | ||||
|             this.subscribe = this.subscribe.bind(this); | ||||
|             this.stopListening = this.stopListening.bind(this); | ||||
|             this.updateValues = this.updateValues.bind(this); | ||||
|             this.updateHistory = this.updateHistory.bind(this); | ||||
|             this.onBoundsChange = this.onBoundsChange.bind(this); | ||||
|             this.onScroll = this.onScroll.bind(this); | ||||
|             this.setSelectedImage = this.setSelectedImage.bind(this); | ||||
|  | ||||
|             this.subscribe(this.$scope.domainObject); | ||||
|  | ||||
|             this.$scope.$on('$destroy', this.stopListening); | ||||
|             this.openmct.time.on('bounds', this.onBoundsChange); | ||||
|             this.scrollable.on('scroll', this.onScroll); | ||||
|         } | ||||
|  | ||||
|         ImageryController.prototype.subscribe = function (domainObject) { | ||||
|             this.date = ""; | ||||
|             this.imageUrl = ""; | ||||
|             this.openmct.objects.get(domainObject.getId()) | ||||
|                 .then(function (object) { | ||||
|                     this.domainObject = object; | ||||
|                     var metadata = this.openmct | ||||
|                         .telemetry | ||||
|                         .getMetadata(this.domainObject); | ||||
|                     this.timeKey = this.openmct.time.timeSystem().key; | ||||
|                     this.timeFormat = this.openmct | ||||
|                         .telemetry | ||||
|                         .getValueFormatter(metadata.value(this.timeKey)); | ||||
|                     this.imageFormat = this.openmct | ||||
|                         .telemetry | ||||
|                         .getValueFormatter(metadata.valuesForHints(['image'])[0]); | ||||
|                     this.unsubscribe = this.openmct.telemetry | ||||
|                         .subscribe(this.domainObject, function (datum) { | ||||
|                             this.updateHistory(datum); | ||||
|                             this.updateValues(datum); | ||||
|                         }.bind(this)); | ||||
|  | ||||
|                     this.requestHistory(this.openmct.time.bounds()); | ||||
|                 }.bind(this)); | ||||
|         }; | ||||
|  | ||||
|         ImageryController.prototype.requestHistory = function (bounds) { | ||||
|             this.requestCount++; | ||||
|             this.$scope.imageHistory = []; | ||||
|             var requestId = this.requestCount; | ||||
|             this.openmct.telemetry | ||||
|                 .request(this.domainObject, bounds) | ||||
|                 .then(function (values) { | ||||
|                     if (this.requestCount > requestId) { | ||||
|                         return Promise.resolve('Stale request'); | ||||
|                     } | ||||
|  | ||||
|                     values.forEach(function (datum) { | ||||
|                         this.updateHistory(datum); | ||||
|                     }, this); | ||||
|  | ||||
|                     this.updateValues(values[values.length - 1]); | ||||
|                 }.bind(this)); | ||||
|         }; | ||||
|  | ||||
|         ImageryController.prototype.stopListening = function () { | ||||
|             this.openmct.time.off('bounds', this.onBoundsChange); | ||||
|             this.scrollable.off('scroll', this.onScroll); | ||||
|             if (this.unsubscribe) { | ||||
|                 this.unsubscribe(); | ||||
|                 delete this.unsubscribe; | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Responds to bound change event be requesting new | ||||
|          * historical data if the bound change was manual. | ||||
|          * @private | ||||
|          * @param {object} [newBounds] new bounds object | ||||
|          * @param {boolean} [tick] true when change is automatic | ||||
|          */ | ||||
|         ImageryController.prototype.onBoundsChange = function (newBounds, tick) { | ||||
|             if (this.domainObject && !tick) { | ||||
|                 this.requestHistory(newBounds); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Updates displayable values to match those of the most | ||||
|          * recently received datum. | ||||
|          * @param {object} [datum] the datum | ||||
|          * @private | ||||
|          */ | ||||
|         ImageryController.prototype.updateValues = function (datum) { | ||||
|             if (this.isPaused) { | ||||
|                 this.nextDatum = datum; | ||||
|                 return; | ||||
|             } | ||||
|             this.time = this.timeFormat.format(datum); | ||||
|             this.imageUrl = this.imageFormat.format(datum); | ||||
|  | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Appends given imagery datum to running history. | ||||
|          * @private | ||||
|          * @param {object} [datum] target telemetry datum | ||||
|          * @returns {boolean} falsy when a duplicate datum is given | ||||
|          */ | ||||
|         ImageryController.prototype.updateHistory = function (datum) { | ||||
|             if (!this.datumMatchesMostRecent(datum)) { | ||||
|                 var index = _.sortedIndex(this.$scope.imageHistory, datum, this.timeFormat.format.bind(this.timeFormat)); | ||||
|                 this.$scope.imageHistory.splice(index, 0, datum); | ||||
|                 return true; | ||||
|             } else { | ||||
|                 return false; | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Checks to see if the given datum is the same as the most recent in history. | ||||
|          * @private | ||||
|          * @param {object} [datum] target telemetry datum | ||||
|          * @returns {boolean} true if datum is most recent in history, false otherwise | ||||
|          */ | ||||
|         ImageryController.prototype.datumMatchesMostRecent = function (datum) { | ||||
|             if (this.$scope.imageHistory.length !== 0) { | ||||
|                 var datumTime = this.timeFormat.format(datum); | ||||
|                 var datumURL = this.imageFormat.format(datum); | ||||
|                 var lastHistoryTime = this.timeFormat.format(this.$scope.imageHistory.slice(-1)[0]); | ||||
|                 var lastHistoryURL = this.imageFormat.format(this.$scope.imageHistory.slice(-1)[0]); | ||||
|  | ||||
|                 return datumTime === lastHistoryTime && datumURL === lastHistoryURL; | ||||
|             } | ||||
|             return false; | ||||
|         }; | ||||
|  | ||||
|         ImageryController.prototype.onScroll = function (event) { | ||||
|             this.$window.requestAnimationFrame(function () { | ||||
|                 var thumbnailWrapperHeight = this.scrollable[0].offsetHeight; | ||||
|                 var thumbnailWrapperWidth = this.scrollable[0].offsetWidth; | ||||
|                 if (this.scrollable[0].scrollLeft < | ||||
|                     (this.scrollable[0].scrollWidth - this.scrollable[0].clientWidth) - (thumbnailWrapperWidth) || | ||||
|                     this.scrollable[0].scrollTop < | ||||
|                     (this.scrollable[0].scrollHeight - this.scrollable[0].clientHeight) - (thumbnailWrapperHeight)) { | ||||
|                     this.autoScroll = false; | ||||
|                 } else { | ||||
|                     this.autoScroll = true; | ||||
|                 } | ||||
|             }.bind(this)); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          *  Force history imagery div to scroll to bottom. | ||||
|          */ | ||||
|         ImageryController.prototype.scrollToBottom = function () { | ||||
|             if (this.autoScroll) { | ||||
|                 this.scrollable[0].scrollTop = this.scrollable[0].scrollHeight; | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|  | ||||
|         /** | ||||
|          * Get the time portion (hours, minutes, seconds) of the | ||||
|          * timestamp associated with the incoming image telemetry | ||||
|          * if no parameter is given, or of a provided datum. | ||||
|          * @param {object} [datum] target telemetry datum | ||||
|          * @returns {string} the time | ||||
|          */ | ||||
|         ImageryController.prototype.getTime = function (datum) { | ||||
|             return datum ? | ||||
|                 this.timeFormat.format(datum) : | ||||
|                 this.time; | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Get the URL of the most recent image telemetry if no | ||||
|          * parameter is given, or of a provided datum. | ||||
|          * @param {object} [datum] target telemetry datum | ||||
|          * @returns {string} URL for telemetry image | ||||
|          */ | ||||
|         ImageryController.prototype.getImageUrl = function (datum) { | ||||
|             return datum ? | ||||
|                 this.imageFormat.format(datum) : | ||||
|                 this.imageUrl; | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Getter-setter for paused state of the view (true means | ||||
|          * paused, false means not.) | ||||
|          * @param {boolean} [state] the state to set | ||||
|          * @returns {boolean} the current state | ||||
|          */ | ||||
|         ImageryController.prototype.paused = function (state) { | ||||
|             if (arguments.length > 0 && state !== this.isPaused) { | ||||
|                 this.unselectAllImages(); | ||||
|                 this.isPaused = state; | ||||
|                 if (this.nextDatum) { | ||||
|                     this.updateValues(this.nextDatum); | ||||
|                     delete this.nextDatum; | ||||
|                 } else { | ||||
|                     this.updateValues(this.$scope.imageHistory[this.$scope.imageHistory.length - 1]); | ||||
|                 } | ||||
|                 this.autoScroll = true; | ||||
|             } | ||||
|             return this.isPaused; | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Set the selected image on the state for the large imagery div to use. | ||||
|          * @param {object} [image] the image object to get url from. | ||||
|          */ | ||||
|         ImageryController.prototype.setSelectedImage = function (image) { | ||||
|             this.imageUrl = this.getImageUrl(image); | ||||
|             this.time = this.getTime(image); | ||||
|             this.paused(true); | ||||
|             this.unselectAllImages(); | ||||
|             image.selected = true; | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Loop through the history imagery data to set all images to unselected. | ||||
|          */ | ||||
|         ImageryController.prototype.unselectAllImages = function () { | ||||
|             for (var i = 0; i < this.$scope.imageHistory.length; i++) { | ||||
|                 this.$scope.imageHistory[i].selected = false; | ||||
|             } | ||||
|         }; | ||||
|         return ImageryController; | ||||
|     } | ||||
| ); | ||||
| @@ -1,110 +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 () { | ||||
|  | ||||
|         /** | ||||
|          * Defines the `mct-background-image` directive. | ||||
|          * | ||||
|          * Used as an attribute, this will set the `background-image` | ||||
|          * property to the URL given in its value, but only after that | ||||
|          * image has loaded; this avoids "flashing" as images change. | ||||
|          * | ||||
|          * If the value of `mct-background-image`is falsy, no image | ||||
|          * will be displayed (immediately.) | ||||
|          * | ||||
|          * Optionally, a `filters` attribute may be specified as an | ||||
|          * object with `brightness` and/or `contrast` properties, | ||||
|          * whose values are percentages. A value of 100 will make | ||||
|          * no changes to the image's brightness or contrast. | ||||
|          * | ||||
|          * @constructor | ||||
|          * @memberof platform/features/imagery | ||||
|          */ | ||||
|         function MCTBackgroundImage($document) { | ||||
|             function link(scope, element) { | ||||
|                 // General strategy here: | ||||
|                 // - Keep count of how many images have been requested; this | ||||
|                 //   counter will be used as an internal identifier or sorts | ||||
|                 //   for each image that loads. | ||||
|                 // - As the src attribute changes, begin loading those images. | ||||
|                 // - When images do load, update the background-image property | ||||
|                 //   of the element, but only if a more recently | ||||
|                 //   requested image has not already been loaded. | ||||
|                 // The order in which URLs are passed in and the order | ||||
|                 // in which images are actually loaded may be different, so | ||||
|                 // some strategy like this is necessary to ensure that images | ||||
|                 // do not display out-of-order. | ||||
|                 var requested = 0, loaded = 0; | ||||
|  | ||||
|                 function updateFilters(filters) { | ||||
|                     var styleValue = filters ? | ||||
|                         Object.keys(filters).map(function (k) { | ||||
|                             return k + "(" + filters[k] + "%)"; | ||||
|                         }).join(' ') : | ||||
|                         ""; | ||||
|                     element.css('filter', styleValue); | ||||
|                     element.css('webkitFilter', styleValue); | ||||
|                 } | ||||
|  | ||||
|                 function nextImage(url) { | ||||
|                     var myCounter = requested, | ||||
|                         image; | ||||
|  | ||||
|                     function useImage() { | ||||
|                         if (loaded <= myCounter) { | ||||
|                             loaded = myCounter; | ||||
|                             element.css('background-image', "url('" + url + "')"); | ||||
|                         } | ||||
|                     } | ||||
|  | ||||
|                     if (!url) { | ||||
|                         loaded = myCounter; | ||||
|                         element.css('background-image', 'none'); | ||||
|                     } else { | ||||
|                         image = $document[0].createElement('img'); | ||||
|                         image.src = url; | ||||
|                         image.onload = useImage; | ||||
|                     } | ||||
|  | ||||
|                     requested += 1; | ||||
|                 } | ||||
|  | ||||
|                 scope.$watch('mctBackgroundImage', nextImage); | ||||
|                 scope.$watchCollection('filters', updateFilters); | ||||
|             } | ||||
|  | ||||
|             return { | ||||
|                 restrict: "A", | ||||
|                 scope: { | ||||
|                     mctBackgroundImage: "=", | ||||
|                     filters: "=" | ||||
|                 }, | ||||
|                 link: link | ||||
|             }; | ||||
|         } | ||||
|  | ||||
|         return MCTBackgroundImage; | ||||
|     } | ||||
| ); | ||||
|  | ||||
| @@ -1,59 +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/api/objects/object-utils' | ||||
| ], function ( | ||||
|     objectUtils | ||||
| ) { | ||||
|     /** | ||||
|      * Policy preventing the Imagery view from being made available for | ||||
|      * domain objects which do not have associated image telemetry. | ||||
|      * @implements {Policy.<View, DomainObject>} | ||||
|      * @constructor | ||||
|      */ | ||||
|     function ImageryViewPolicy(openmct) { | ||||
|         this.openmct = openmct; | ||||
|     } | ||||
|  | ||||
|     ImageryViewPolicy.prototype.hasImageTelemetry = function (domainObject) { | ||||
|         var newDO = objectUtils.toNewFormat( | ||||
|             domainObject.getModel(), | ||||
|             domainObject.getId() | ||||
|         ); | ||||
|  | ||||
|         var metadata = this.openmct.telemetry.getMetadata(newDO); | ||||
|         var values = metadata.valuesForHints(['image']); | ||||
|         return values.length >= 1; | ||||
|     }; | ||||
|  | ||||
|     ImageryViewPolicy.prototype.allow = function (view, domainObject) { | ||||
|         if (view.key === 'imagery' || view.key === 'historical-imagery') { | ||||
|             return this.hasImageTelemetry(domainObject); | ||||
|         } | ||||
|  | ||||
|         return true; | ||||
|     }; | ||||
|  | ||||
|     return ImageryViewPolicy; | ||||
| }); | ||||
|  | ||||
| @@ -1,271 +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( | ||||
|     [ | ||||
|         "zepto", | ||||
|         "../../src/controllers/ImageryController" | ||||
|     ], | ||||
|     function ($, ImageryController) { | ||||
|  | ||||
|         var MOCK_ELEMENT_TEMPLATE = | ||||
|             '<div class="l-image-thumbs-wrapper"></div>'; | ||||
|  | ||||
|         xdescribe("The Imagery controller", function () { | ||||
|             var $scope, | ||||
|                 openmct, | ||||
|                 oldDomainObject, | ||||
|                 newDomainObject, | ||||
|                 unsubscribe, | ||||
|                 metadata, | ||||
|                 prefix, | ||||
|                 controller, | ||||
|                 requestPromise, | ||||
|                 mockWindow, | ||||
|                 mockElement; | ||||
|  | ||||
|             beforeEach(function () { | ||||
|                 $scope = jasmine.createSpyObj('$scope', ['$on', '$watch']); | ||||
|                 oldDomainObject = jasmine.createSpyObj( | ||||
|                     'domainObject', | ||||
|                     ['getId'] | ||||
|                 ); | ||||
|                 newDomainObject = { name: 'foo' }; | ||||
|                 oldDomainObject.getId.and.returnValue('testID'); | ||||
|                 openmct = { | ||||
|                     objects: jasmine.createSpyObj('objectAPI', [ | ||||
|                         'get' | ||||
|                     ]), | ||||
|                     time: jasmine.createSpyObj('timeAPI', [ | ||||
|                         'timeSystem', | ||||
|                         'clock', | ||||
|                         'on', | ||||
|                         'off', | ||||
|                         'bounds' | ||||
|                     ]), | ||||
|                     telemetry: jasmine.createSpyObj('telemetryAPI', [ | ||||
|                         'subscribe', | ||||
|                         'request', | ||||
|                         'getValueFormatter', | ||||
|                         'getMetadata' | ||||
|                     ]) | ||||
|                 }; | ||||
|                 metadata = jasmine.createSpyObj('metadata', [ | ||||
|                     'value', | ||||
|                     'valuesForHints' | ||||
|                 ]); | ||||
|                 metadata.value.and.returnValue("timestamp"); | ||||
|                 metadata.valuesForHints.and.returnValue(["value"]); | ||||
|  | ||||
|                 prefix = "formatted "; | ||||
|                 unsubscribe = jasmine.createSpy('unsubscribe'); | ||||
|                 openmct.telemetry.subscribe.and.returnValue(unsubscribe); | ||||
|                 openmct.time.timeSystem.and.returnValue({ | ||||
|                     key: 'testKey' | ||||
|                 }); | ||||
|                 $scope.domainObject = oldDomainObject; | ||||
|                 openmct.objects.get.and.returnValue(Promise.resolve(newDomainObject)); | ||||
|                 openmct.telemetry.getMetadata.and.returnValue(metadata); | ||||
|                 openmct.telemetry.getValueFormatter.and.callFake(function (property) { | ||||
|                     var formatter = | ||||
|                         jasmine.createSpyObj("formatter-" + property, ['format']); | ||||
|                     var isTime = (property === "timestamp"); | ||||
|                     formatter.format.and.callFake(function (datum) { | ||||
|                         return (isTime ? prefix : "") + datum[property]; | ||||
|                     }); | ||||
|                     return formatter; | ||||
|                 }); | ||||
|  | ||||
|                 requestPromise = new Promise(function (resolve) { | ||||
|                     setTimeout(function () { | ||||
|                         resolve([{ | ||||
|                             timestamp: 1434600258123, | ||||
|                             value: 'some/url' | ||||
|                         }]); | ||||
|                     }, 10); | ||||
|                 }); | ||||
|  | ||||
|                 openmct.telemetry.request.and.returnValue(requestPromise); | ||||
|                 mockElement = $(MOCK_ELEMENT_TEMPLATE); | ||||
|                 mockWindow = jasmine.createSpyObj('$window', ['requestAnimationFrame']); | ||||
|                 mockWindow.requestAnimationFrame.and.callFake(function (f) { | ||||
|                     return f(); | ||||
|                 }); | ||||
|  | ||||
|                 controller = new ImageryController( | ||||
|                     $scope, | ||||
|                     mockWindow, | ||||
|                     mockElement, | ||||
|                     openmct | ||||
|                 ); | ||||
|             }); | ||||
|  | ||||
|             describe("when loaded", function () { | ||||
|                 var callback, | ||||
|                     boundsListener, | ||||
|                     bounds; | ||||
|  | ||||
|                 beforeEach(function () { | ||||
|                     return requestPromise.then(function () { | ||||
|                         openmct.time.on.calls.all().forEach(function (call) { | ||||
|                             if (call.args[0] === "bounds") { | ||||
|                                 boundsListener = call.args[1]; | ||||
|                             } | ||||
|                         }); | ||||
|                         callback = | ||||
|                             openmct.telemetry.subscribe.calls.mostRecent().args[1]; | ||||
|                     }); | ||||
|                 }); | ||||
|  | ||||
|                 it("requests history", function () { | ||||
|                     expect(openmct.telemetry.request).toHaveBeenCalledWith( | ||||
|                         newDomainObject, bounds | ||||
|                     ); | ||||
|                     expect(controller.getTime()).toEqual(prefix + 1434600258123); | ||||
|                     expect(controller.getImageUrl()).toEqual('some/url'); | ||||
|                 }); | ||||
|  | ||||
|  | ||||
|                 it("exposes the latest telemetry values", function () { | ||||
|                     callback({ | ||||
|                         timestamp: 1434600259456, | ||||
|                         value: "some/other/url" | ||||
|                     }); | ||||
|  | ||||
|                     expect(controller.getTime()).toEqual(prefix + 1434600259456); | ||||
|                     expect(controller.getImageUrl()).toEqual("some/other/url"); | ||||
|                 }); | ||||
|  | ||||
|                 it("allows updates to be paused and unpaused", function () { | ||||
|                     var newTimestamp = 1434600259456, | ||||
|                         newUrl = "some/other/url", | ||||
|                         initialTimestamp = controller.getTime(), | ||||
|                         initialUrl = controller.getImageUrl(); | ||||
|  | ||||
|                     expect(initialTimestamp).not.toBe(prefix + newTimestamp); | ||||
|                     expect(initialUrl).not.toBe(newUrl); | ||||
|                     expect(controller.paused()).toBeFalsy(); | ||||
|  | ||||
|                     controller.paused(true); | ||||
|                     expect(controller.paused()).toBeTruthy(); | ||||
|                     callback({ timestamp: newTimestamp, value: newUrl }); | ||||
|  | ||||
|                     expect(controller.getTime()).toEqual(initialTimestamp); | ||||
|                     expect(controller.getImageUrl()).toEqual(initialUrl); | ||||
|  | ||||
|                     controller.paused(false); | ||||
|                     expect(controller.paused()).toBeFalsy(); | ||||
|                     expect(controller.getTime()).toEqual(prefix + newTimestamp); | ||||
|                     expect(controller.getImageUrl()).toEqual(newUrl); | ||||
|                 }); | ||||
|  | ||||
|                 it("forwards large image view to latest image in history on un-pause", function () { | ||||
|                     $scope.imageHistory = [ | ||||
|                         { utc: 1434600258122, url: 'some/url1', selected: false}, | ||||
|                         { utc: 1434600258123, url: 'some/url2', selected: false} | ||||
|                     ]; | ||||
|                     controller.paused(true); | ||||
|                     controller.paused(false); | ||||
|  | ||||
|                     expect(controller.getImageUrl()).toEqual(controller.getImageUrl($scope.imageHistory[1])); | ||||
|                 }); | ||||
|  | ||||
|                 it("subscribes to telemetry", function () { | ||||
|                     expect(openmct.telemetry.subscribe).toHaveBeenCalledWith( | ||||
|                         newDomainObject, | ||||
|                         jasmine.any(Function) | ||||
|                     ); | ||||
|                 }); | ||||
|  | ||||
|                 it("requests telemetry", function () { | ||||
|                     expect(openmct.telemetry.request).toHaveBeenCalledWith( | ||||
|                         newDomainObject, | ||||
|                         bounds | ||||
|                     ); | ||||
|                 }); | ||||
|  | ||||
|                 it("unsubscribes and unlistens when scope is destroyed", function () { | ||||
|                     expect(unsubscribe).not.toHaveBeenCalled(); | ||||
|  | ||||
|                     $scope.$on.calls.all().forEach(function (call) { | ||||
|                         if (call.args[0] === '$destroy') { | ||||
|                             call.args[1](); | ||||
|                         } | ||||
|                     }); | ||||
|                     expect(unsubscribe).toHaveBeenCalled(); | ||||
|                     expect(openmct.time.off) | ||||
|                         .toHaveBeenCalledWith('bounds', jasmine.any(Function)); | ||||
|                 }); | ||||
|  | ||||
|                 it("listens for bounds event and responds to tick and manual change", function () { | ||||
|                     var mockBounds = {start: 1434600000000, end: 1434600500000}; | ||||
|                     expect(openmct.time.on).toHaveBeenCalled(); | ||||
|                     openmct.telemetry.request.calls.reset(); | ||||
|                     boundsListener(mockBounds, true); | ||||
|                     expect(openmct.telemetry.request).not.toHaveBeenCalled(); | ||||
|                     boundsListener(mockBounds, false); | ||||
|                     expect(openmct.telemetry.request).toHaveBeenCalledWith(newDomainObject, mockBounds); | ||||
|                 }); | ||||
|  | ||||
|                 it ("doesnt append duplicate datum", function () { | ||||
|                     var mockDatum  = {value: 'image/url', timestamp: 1434700000000}; | ||||
|                     var mockDatum2 = {value: 'image/url', timestamp: 1434700000000}; | ||||
|                     var mockDatum3 = {value: 'image/url', url: 'someval', timestamp: 1434700000000}; | ||||
|                     expect(controller.updateHistory(mockDatum)).toBe(true); | ||||
|                     expect(controller.updateHistory(mockDatum)).toBe(false); | ||||
|                     expect(controller.updateHistory(mockDatum)).toBe(false); | ||||
|                     expect(controller.updateHistory(mockDatum2)).toBe(false); | ||||
|                     expect(controller.updateHistory(mockDatum3)).toBe(false); | ||||
|                 }); | ||||
|  | ||||
|                 describe("when user clicks on imagery thumbnail", function () { | ||||
|                     var mockDatum = { utc: 1434600258123, url: 'some/url', selected: false}; | ||||
|  | ||||
|                     it("pauses and adds selected class to imagery thumbnail", function () { | ||||
|                         controller.setSelectedImage(mockDatum); | ||||
|                         expect(controller.paused()).toBeTruthy(); | ||||
|                         expect(mockDatum.selected).toBeTruthy(); | ||||
|                     }); | ||||
|  | ||||
|                     it("unselects previously selected image", function () { | ||||
|                         $scope.imageHistory = [{ utc: 1434600258123, url: 'some/url', selected: true}]; | ||||
|                         controller.unselectAllImages(); | ||||
|                         expect($scope.imageHistory[0].selected).toBeFalsy(); | ||||
|                     }); | ||||
|  | ||||
|                     it("updates larger image url and time", function () { | ||||
|                         controller.setSelectedImage(mockDatum); | ||||
|                         expect(controller.getImageUrl()).toEqual(controller.getImageUrl(mockDatum)); | ||||
|                         expect(controller.getTime()).toEqual(controller.timeFormat.format(mockDatum.utc)); | ||||
|                     }); | ||||
|                 }); | ||||
|  | ||||
|             }); | ||||
|  | ||||
|             it("initially shows an empty string for date/time", function () { | ||||
|                 expect(controller.getTime()).toEqual(""); | ||||
|                 expect(controller.getImageUrl()).toEqual(""); | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| ); | ||||
|  | ||||
| @@ -1,126 +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/MCTBackgroundImage"], | ||||
|     function (MCTBackgroundImage) { | ||||
|  | ||||
|         describe("The mct-background-image directive", function () { | ||||
|             var mockDocument, | ||||
|                 mockScope, | ||||
|                 mockElement, | ||||
|                 testImage, | ||||
|                 directive; | ||||
|  | ||||
|             beforeEach(function () { | ||||
|                 mockDocument = [ | ||||
|                     jasmine.createSpyObj('document', ['createElement']) | ||||
|                 ]; | ||||
|                 mockScope = jasmine.createSpyObj('scope', [ | ||||
|                     '$watch', | ||||
|                     '$watchCollection' | ||||
|                 ]); | ||||
|                 mockElement = jasmine.createSpyObj('element', ['css']); | ||||
|                 testImage = {}; | ||||
|  | ||||
|                 mockDocument[0].createElement.and.returnValue(testImage); | ||||
|  | ||||
|                 directive = new MCTBackgroundImage(mockDocument); | ||||
|             }); | ||||
|  | ||||
|             it("is applicable as an attribute", function () { | ||||
|                 expect(directive.restrict).toEqual("A"); | ||||
|             }); | ||||
|  | ||||
|             it("two-way-binds its own value", function () { | ||||
|                 expect(directive.scope.mctBackgroundImage).toEqual("="); | ||||
|             }); | ||||
|  | ||||
|             describe("once linked", function () { | ||||
|                 beforeEach(function () { | ||||
|                     directive.link(mockScope, mockElement, {}); | ||||
|                 }); | ||||
|  | ||||
|                 it("watches for changes to the URL", function () { | ||||
|                     expect(mockScope.$watch).toHaveBeenCalledWith( | ||||
|                         'mctBackgroundImage', | ||||
|                         jasmine.any(Function) | ||||
|                     ); | ||||
|                 }); | ||||
|  | ||||
|                 it("updates images in-order, even when they load out-of-order", function () { | ||||
|                     var firstOnload; | ||||
|  | ||||
|                     mockScope.$watch.calls.mostRecent().args[1]("some/url/0"); | ||||
|                     firstOnload = testImage.onload; | ||||
|  | ||||
|                     mockScope.$watch.calls.mostRecent().args[1]("some/url/1"); | ||||
|  | ||||
|                     // Resolve in a different order | ||||
|                     testImage.onload(); | ||||
|                     firstOnload(); | ||||
|  | ||||
|                     // Should still have taken the more recent value | ||||
|                     expect(mockElement.css.calls.mostRecent().args).toEqual([ | ||||
|                         "background-image", | ||||
|                         "url('some/url/1')" | ||||
|                     ]); | ||||
|                 }); | ||||
|  | ||||
|                 it("clears the background image when undefined is passed in", function () { | ||||
|                     mockScope.$watch.calls.mostRecent().args[1]("some/url/0"); | ||||
|                     testImage.onload(); | ||||
|                     mockScope.$watch.calls.mostRecent().args[1](undefined); | ||||
|  | ||||
|                     expect(mockElement.css.calls.mostRecent().args).toEqual([ | ||||
|                         "background-image", | ||||
|                         "none" | ||||
|                     ]); | ||||
|                 }); | ||||
|  | ||||
|                 it("updates filters on change", function () { | ||||
|                     var filters = { brightness: 123, contrast: 21 }; | ||||
|                     mockScope.$watchCollection.calls.all().forEach(function (call) { | ||||
|                         if (call.args[0] === 'filters') { | ||||
|                             call.args[1](filters); | ||||
|                         } | ||||
|                     }); | ||||
|                     expect(mockElement.css).toHaveBeenCalledWith( | ||||
|                         'filter', | ||||
|                         'brightness(123%) contrast(21%)' | ||||
|                     ); | ||||
|                 }); | ||||
|  | ||||
|                 it("clears filters when none are present", function () { | ||||
|                     mockScope.$watchCollection.calls.all().forEach(function (call) { | ||||
|                         if (call.args[0] === 'filters') { | ||||
|                             call.args[1](undefined); | ||||
|                         } | ||||
|                     }); | ||||
|                     expect(mockElement.css) | ||||
|                         .toHaveBeenCalledWith('filter', ''); | ||||
|                 }); | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| ); | ||||
|  | ||||
| @@ -1,84 +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/policies/ImageryViewPolicy"], | ||||
|     function (ImageryViewPolicy) { | ||||
|  | ||||
|         describe("Imagery view policy", function () { | ||||
|             var testView, | ||||
|                 openmct, | ||||
|                 mockDomainObject, | ||||
|                 mockTelemetry, | ||||
|                 mockMetadata, | ||||
|                 policy; | ||||
|  | ||||
|             beforeEach(function () { | ||||
|                 testView = { key: "imagery" }; | ||||
|                 mockMetadata = jasmine.createSpyObj('metadata', [ | ||||
|                     "valuesForHints" | ||||
|                 ]); | ||||
|                 mockDomainObject = jasmine.createSpyObj( | ||||
|                     'domainObject', | ||||
|                     ['getId', 'getModel', 'getCapability'] | ||||
|                 ); | ||||
|                 mockTelemetry = jasmine.createSpyObj( | ||||
|                     'telemetry', | ||||
|                     ['getMetadata'] | ||||
|                 ); | ||||
|                 mockDomainObject.getCapability.and.callFake(function (c) { | ||||
|                     return c === 'telemetry' ? mockTelemetry : undefined; | ||||
|                 }); | ||||
|                 mockDomainObject.getId.and.returnValue("some-id"); | ||||
|                 mockDomainObject.getModel.and.returnValue({ name: "foo" }); | ||||
|                 mockTelemetry.getMetadata.and.returnValue(mockMetadata); | ||||
|                 mockMetadata.valuesForHints.and.returnValue(["bar"]); | ||||
|  | ||||
|                 openmct = { telemetry: mockTelemetry }; | ||||
|  | ||||
|                 policy = new ImageryViewPolicy(openmct); | ||||
|             }); | ||||
|  | ||||
|             it("checks for hints indicating image telemetry", function () { | ||||
|                 policy.allow(testView, mockDomainObject); | ||||
|                 expect(mockMetadata.valuesForHints) | ||||
|                     .toHaveBeenCalledWith(["image"]); | ||||
|             }); | ||||
|  | ||||
|             it("allows the imagery view for domain objects with image telemetry", function () { | ||||
|                 expect(policy.allow(testView, mockDomainObject)).toBeTruthy(); | ||||
|             }); | ||||
|  | ||||
|             it("disallows the imagery view for domain objects without image telemetry", function () { | ||||
|                 mockMetadata.valuesForHints.and.returnValue([]); | ||||
|                 expect(policy.allow(testView, mockDomainObject)).toBeFalsy(); | ||||
|             }); | ||||
|  | ||||
|             it("allows other views", function () { | ||||
|                 testView.key = "somethingElse"; | ||||
|                 expect(policy.allow(testView, mockDomainObject)).toBeTruthy(); | ||||
|             }); | ||||
|  | ||||
|         }); | ||||
|     } | ||||
| ); | ||||
|  | ||||
							
								
								
									
										8
									
								
								platform/features/my-items/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,8 @@ | ||||
| # My Items plugin | ||||
| Defines top-level folder named "My Items" to store user-created items. Enabled by default, this can be disabled in a  | ||||
| read-only deployment with no user-editable objects. | ||||
|  | ||||
| ## Installation | ||||
| ```js | ||||
| openmct.install(openmct.plugins.MyItems()); | ||||
| ``` | ||||
							
								
								
									
										14
									
								
								platform/import-export/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,14 @@ | ||||
| # Import / Export Plugin | ||||
| The Import/Export plugin allows objects to be exported as JSON files. This allows for sharing of objects between users  | ||||
| who are not using a shared persistence store. It also allows object trees to be backed up. Additionally, object trees  | ||||
| exported using this tool can then be exposed as read-only static root trees using the  | ||||
| [Static Root Plugin](../../src/plugins/staticRootPlugin/README.md). | ||||
|  | ||||
| Upon installation it will add two new context menu actions to allow import and export of objects. Initiating the Export  | ||||
| action on an object will produce a JSON file that includes the object and all of its composed children. Selecting Import  | ||||
| on an object will allow the user to import a previously exported object tree as a child of the selected object. | ||||
|  | ||||
| ## Installation | ||||
| ```js | ||||
| openmct.install(openmct.plugins.ImportExport()) | ||||
| ``` | ||||
| @@ -133,7 +133,7 @@ define(['lodash'], function (_) { | ||||
|         copyOfChild.location = parentId; | ||||
|         parent.composition[index] = copyOfChild.identifier; | ||||
|         this.tree[newIdString] = copyOfChild; | ||||
|         this.tree[parentId].composition[index] = newIdString; | ||||
|         this.tree[parentId].composition[index] = copyOfChild.identifier; | ||||
|  | ||||
|         return copyOfChild; | ||||
|     }; | ||||
|   | ||||
| @@ -136,6 +136,10 @@ define(['zepto', '../../../../src/api/objects/object-utils.js'], function ($, ob | ||||
|         return tree; | ||||
|     }; | ||||
|  | ||||
|     ImportAsJSONAction.prototype.getKeyString = function (identifier) { | ||||
|         return this.openmct.objects.makeKeyString(identifier); | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Rewrites all instances of a given id in the tree with a newly generated | ||||
|      * replacement to prevent collision. | ||||
|   | ||||
| @@ -47,7 +47,12 @@ define( | ||||
|                 uniqueId = 0; | ||||
|                 newObjects = []; | ||||
|                 openmct = { | ||||
|                     $injector: jasmine.createSpyObj('$injector', ['get']) | ||||
|                     $injector: jasmine.createSpyObj('$injector', ['get']), | ||||
|                     objects: { | ||||
|                         makeKeyString: function (identifier) { | ||||
|                             return identifier.key; | ||||
|                         } | ||||
|                     } | ||||
|                 }; | ||||
|                 mockInstantiate = jasmine.createSpy('instantiate').and.callFake( | ||||
|                     function (model, id) { | ||||
| @@ -153,7 +158,7 @@ define( | ||||
|                             body: JSON.stringify({ | ||||
|                                 "openmct": { | ||||
|                                     "infiniteParent": { | ||||
|                                         "composition": ["infinteChild"], | ||||
|                                         "composition": [{key: "infinteChild", namespace: ""}], | ||||
|                                         "name": "1", | ||||
|                                         "type": "folder", | ||||
|                                         "modified": 1503598129176, | ||||
| @@ -161,7 +166,7 @@ define( | ||||
|                                         "persisted": 1503598129176 | ||||
|                                     }, | ||||
|                                     "infinteChild": { | ||||
|                                         "composition": ["infiniteParent"], | ||||
|                                         "composition": [{key: "infinteParent", namespace: ""}], | ||||
|                                         "name": "2", | ||||
|                                         "type": "folder", | ||||
|                                         "modified": 1503598132428, | ||||
|   | ||||
| @@ -1,2 +1,8 @@ | ||||
| This bundle implements a connection to an external CouchDB persistence  | ||||
| store in Open MCT. | ||||
| # Couch DB Persistence Plugin | ||||
| An adapter for using CouchDB for persistence of user-created objects. The plugin installation function takes the URL  | ||||
| for the CouchDB database as a parameter. | ||||
|  | ||||
| ## Installation | ||||
| ```js | ||||
| openmct.install(openmct.plugins.CouchDB('http://localhost:5984/openmct')) | ||||
| ``` | ||||
| @@ -1,2 +1,8 @@ | ||||
| This bundle implements a connection to an external ElasticSearch persistence  | ||||
| store in Open MCT. | ||||
| # Elasticsearch Persistence Provider | ||||
| An adapter for using Elastic for persistence of user-created objects. The installation function takes the URL for an  | ||||
| Elasticsearch server as a parameter. | ||||
|  | ||||
| ## Installation | ||||
| ```js | ||||
| openmct.install(openmct.plugins.Elasticsearch('http://localhost:9200')) | ||||
| ``` | ||||
							
								
								
									
										9
									
								
								platform/persistence/local/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,9 @@ | ||||
| # Local Storage Plugin | ||||
| Provides persistence of user-created objects in browser Local Storage. Objects persisted in this way will only be  | ||||
| available from the browser and machine on which they were persisted. For shared persistence, consider the  | ||||
| [Elasticsearch](../elastic/) and [CouchDB](../couch/) persistence plugins. | ||||
|  | ||||
| ## Installation | ||||
| ```js | ||||
| openmct.install(openmct.plugins.LocalStorage()); | ||||
| ``` | ||||
| @@ -1,106 +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. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| // Converts all templateUrl references in bundle.js files to | ||||
| // plain template references, loading said templates with the | ||||
| // RequireJS text plugin. | ||||
|  | ||||
| var glob = require('glob'), | ||||
|     fs = require('fs'), | ||||
|     path = require('path'), | ||||
|     _ = require('lodash'); | ||||
|  | ||||
| function toTemplateName(templateUrl) { | ||||
|     var parts = templateUrl.split('/'); | ||||
|     return _.camelCase(parts[parts.length - 1].replace(".html", "")) + | ||||
|             "Template"; | ||||
| } | ||||
|  | ||||
| function getTemplateUrl(sourceLine) { | ||||
|     return _.trim(sourceLine.split(":")[1], "\", "); | ||||
| } | ||||
|  | ||||
| function hasTemplateUrl(sourceLine) { | ||||
|     return sourceLine.indexOf("templateUrl") !== -1; | ||||
| } | ||||
|  | ||||
| function findTemplateURLs(sourceCode) { | ||||
|     return sourceCode.split('\n') | ||||
|         .map(_.trim) | ||||
|         .filter(hasTemplateUrl) | ||||
|         .map(getTemplateUrl); | ||||
| } | ||||
|  | ||||
| function injectRequireArgument(sourceCode, templateUrls) { | ||||
|     var lines = sourceCode.split('\n'), | ||||
|         index; | ||||
|  | ||||
|     templateUrls = _.uniq(templateUrls); | ||||
|  | ||||
|     // Add arguments for source paths... | ||||
|     index = lines.map(_.trim).indexOf("'legacyRegistry'"); | ||||
|     lines = lines.slice(0, index).concat(templateUrls.map(function (url) { | ||||
|         return "    \"text!./res/" + url + "\","; | ||||
|     }).concat(lines.slice(index))); | ||||
|  | ||||
|     /// ...and for arguments | ||||
|     index = lines.map(_.trim).indexOf("legacyRegistry"); | ||||
|     lines = lines.slice(0, index).concat(templateUrls.map(function (url) { | ||||
|         return "    " + toTemplateName(url) + ","; | ||||
|     }).concat(lines.slice(index))); | ||||
|  | ||||
|     return lines.join('\n'); | ||||
| } | ||||
|  | ||||
| function rewriteUrl(sourceLine) { | ||||
|     return [ | ||||
|         sourceLine.substring(0, sourceLine.indexOf(sourceLine.trim())), | ||||
|         "\"template\": " + toTemplateName(getTemplateUrl(sourceLine)), | ||||
|         _.endsWith(sourceLine, ",") ? "," : "" | ||||
|     ].join(''); | ||||
| } | ||||
|  | ||||
| function rewriteLine(sourceLine) { | ||||
|     return hasTemplateUrl(sourceLine) ? | ||||
|         rewriteUrl(sourceLine.replace("templateUrl", "template")) : | ||||
|         sourceLine; | ||||
| } | ||||
|  | ||||
| function rewriteTemplateUrls(sourceCode) { | ||||
|     return sourceCode.split('\n').map(rewriteLine).join('\n'); | ||||
| } | ||||
|  | ||||
| function migrate(file) { | ||||
|     var sourceCode = fs.readFileSync(file, 'utf8'); | ||||
|     fs.writeFileSync(file, rewriteTemplateUrls( | ||||
|         injectRequireArgument(sourceCode, findTemplateURLs(sourceCode)) | ||||
|     ), 'utf8'); | ||||
| } | ||||
|  | ||||
| glob('platform/**/bundle.js', {}, function (err, files) { | ||||
|     if (err) { | ||||
|         console.log(err); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     files.forEach(migrate); | ||||
| }); | ||||
| @@ -1,72 +0,0 @@ | ||||
| // Temporary utility script to rewrite bundle.json | ||||
| // files as bundle.js files. | ||||
|  | ||||
| var glob = require('glob'), | ||||
|     fs = require('fs'), | ||||
|     path = require('path'), | ||||
|     _ = require('lodash'), | ||||
|     template = _.template( | ||||
|         fs.readFileSync(path.resolve(__dirname, 'rebundle-template.txt'), 'utf8') | ||||
|     ); | ||||
|  | ||||
| function indent(str, depth) { | ||||
|     return _.trimLeft(str.split('\n').map(function (line) { | ||||
|         return _.repeat('    ', depth || 1) + line; | ||||
|     }).filter(function (line) { | ||||
|         return line.trim().length > 0; | ||||
|     }).join('\n')); | ||||
| } | ||||
|  | ||||
| function findImpls(bundleContents) { | ||||
|     return _(bundleContents.extensions || {}) | ||||
|         .map() | ||||
|         .flatten() | ||||
|         .pluck('implementation') | ||||
|         .filter() | ||||
|         .uniq() | ||||
|         .value(); | ||||
| } | ||||
|  | ||||
| function toIdentifier(impl) { | ||||
|     var parts = impl.replace(".js", "").split('/'); | ||||
|     return parts[parts.length - 1]; | ||||
| } | ||||
|  | ||||
| function toPath(impl) { | ||||
|     return "\"./src/" + impl.replace(".js", "") + "\""; | ||||
| } | ||||
|  | ||||
| function replaceImpls(bundleText) { | ||||
|     var rx = /"implementation": "([^"]*)"/; | ||||
|     return bundleText.split('\n').map(function (line) { | ||||
|         var m = line.match(rx); | ||||
|         return m !== null ? | ||||
|                 line.replace(rx, '"implementation": ' + toIdentifier(m[1])) : | ||||
|                 line; | ||||
|     }).join('\n'); | ||||
| } | ||||
|  | ||||
| function rebundle(file) { | ||||
|     var plainJson = fs.readFileSync(file, 'utf8'), | ||||
|         bundleContents = JSON.parse(plainJson), | ||||
|         impls = findImpls(bundleContents), | ||||
|         bundleName = file.replace("/bundle.json", ""), | ||||
|         outputFile = file.replace(".json", ".js"), | ||||
|         contents = template({ | ||||
|             bundleName: bundleName, | ||||
|             implPaths: indent(impls.map(toPath).concat([""]).join(",\n")), | ||||
|             implNames: indent(impls.map(toIdentifier).concat([""]).join(",\n")), | ||||
|             bundleContents: indent(replaceImpls(JSON.stringify(bundleContents, null, 4))) | ||||
|         }); | ||||
|     fs.writeFileSync(outputFile, contents, 'utf8'); | ||||
| } | ||||
|  | ||||
| glob('**/bundle.json', {}, function (err, files) { | ||||
|     if (err) { | ||||
|         console.log(err); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     files.forEach(rebundle); | ||||
| }); | ||||
|  | ||||
| @@ -1,5 +1,5 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2018, United States Government | ||||
|  * Open MCT, Copyright (c) 2014-2019, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
| @@ -33,6 +33,7 @@ define([ | ||||
|     './adapter/indicators/legacy-indicators-plugin', | ||||
|     './plugins/buildInfo/plugin', | ||||
|     './ui/registries/ViewRegistry', | ||||
|     './plugins/imagery/plugin', | ||||
|     './ui/registries/InspectorViewRegistry', | ||||
|     './ui/registries/ToolbarRegistry', | ||||
|     './ui/router/ApplicationRouter', | ||||
| @@ -59,6 +60,7 @@ define([ | ||||
|     LegacyIndicatorsPlugin, | ||||
|     buildInfoPlugin, | ||||
|     ViewRegistry, | ||||
|     ImageryPlugin, | ||||
|     InspectorViewRegistry, | ||||
|     ToolbarRegistry, | ||||
|     ApplicationRouter, | ||||
| @@ -257,10 +259,12 @@ define([ | ||||
|         this.install(RemoveActionPlugin.default()); | ||||
|         this.install(this.plugins.FolderView()); | ||||
|         this.install(this.plugins.Tabs()); | ||||
|         this.install(ImageryPlugin.default()); | ||||
|         this.install(this.plugins.FlexibleLayout()); | ||||
|         this.install(this.plugins.GoToOriginalAction()); | ||||
|         this.install(this.plugins.ImportExport()); | ||||
|         this.install(this.plugins.WebPage()); | ||||
|         this.install(this.plugins.Condition()); | ||||
|     } | ||||
|  | ||||
|     MCT.prototype = Object.create(EventEmitter.prototype); | ||||
|   | ||||
							
								
								
									
										7
									
								
								src/adapter/policies/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,7 @@ | ||||
| # Espresso Theme | ||||
| Dark theme for the Open MCT user interface. | ||||
|  | ||||
| ## Installation | ||||
| ```js | ||||
| openmct.install(openmct.plugins.Espresso()); | ||||
| ``` | ||||
| @@ -38,7 +38,6 @@ const DEFAULTS = [ | ||||
|     'platform/exporters', | ||||
|     'platform/telemetry', | ||||
|     'platform/features/clock', | ||||
|     'platform/features/imagery', | ||||
|     'platform/features/hyperlink', | ||||
|     'platform/features/timeline', | ||||
|     'platform/forms', | ||||
| @@ -82,7 +81,6 @@ define([ | ||||
|     '../platform/execution/bundle', | ||||
|     '../platform/exporters/bundle', | ||||
|     '../platform/features/clock/bundle', | ||||
|     '../platform/features/imagery/bundle', | ||||
|     '../platform/features/my-items/bundle', | ||||
|     '../platform/features/hyperlink/bundle', | ||||
|     '../platform/features/static-markup/bundle', | ||||
|   | ||||
							
								
								
									
										21
									
								
								src/plugins/URLIndicatorPlugin/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,21 @@ | ||||
| # URL Indicator | ||||
| Adds an indicator which shows the availability of a URL, with success based on receipt of a 200 HTTP code. Can be used  | ||||
| for monitoring the availability of web services. | ||||
|  | ||||
| ## Installation | ||||
| ```js | ||||
| openmct.install(openmct.plugins.URLIndicator({ | ||||
|   url: 'http://localhost:8080', | ||||
|     iconClass: 'check', | ||||
|     interval: 10000, | ||||
|     label: 'Localhost' | ||||
|  }) | ||||
| ); | ||||
| ``` | ||||
|  | ||||
| ## Options | ||||
| * __url__: URL to indicate the status of | ||||
| * __iconClass__: Icon to show in the status bar, defaults to icon-database. See the [Style Guide](https://nasa.github.io/openmct/style-guide/#/browse/styleguide:home/glyphs?view=styleguide.glyphs) for more icon options. | ||||
| * __interval__: Interval between checking the connection, defaults to 10000 | ||||
| * __label__: Name showing up as text in the status bar, defaults to url | ||||
|  | ||||
| @@ -25,15 +25,6 @@ define([ | ||||
| ], function ( | ||||
|     AutoflowTabularView | ||||
| ) { | ||||
|     /** | ||||
|      * This plugin provides an Autoflow Tabular View for domain objects | ||||
|      * in Open MCT. | ||||
|      * | ||||
|      * @param {Object} options | ||||
|      * @param {String} [options.type] the domain object type for which | ||||
|      *        this view should be available; if omitted, this view will | ||||
|      *        be available for all objects | ||||
|      */ | ||||
|     return function (options) { | ||||
|         return function (openmct) { | ||||
|             var views = (openmct.mainViews || openmct.objectViews); | ||||
|   | ||||
							
								
								
									
										15
									
								
								src/plugins/autoflow/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,15 @@ | ||||
| # Autoflow View | ||||
|  | ||||
| This plugin provides the Autoflow View for domain objects in Open MCT. This view allows users to visualize the latest  | ||||
| values of a collection of telemetry points in a condensed list. | ||||
|  | ||||
| ## Installation | ||||
| ``` js | ||||
|     openmct.install(openmct.plugins.AutoflowView({ | ||||
|         type: "telemetry.fixed" | ||||
|     })); | ||||
| ``` | ||||
|  | ||||
| ## Options | ||||
| * `type`: The object type to add the Autoflow View to. Currently supports a single value. If not provided, will make the  | ||||
| Autoflow view available for all objects (which is probably not what you want). | ||||
							
								
								
									
										239
									
								
								src/plugins/condition/Condition.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,239 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2020, 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'; | ||||
| import uuid from 'uuid'; | ||||
| import TelemetryCriterion from "./criterion/TelemetryCriterion"; | ||||
| import { TRIGGER } from "./utils/constants"; | ||||
| import {computeCondition} from "./utils/evaluator"; | ||||
|  | ||||
| /* | ||||
| * conditionConfiguration = { | ||||
| *   id: uuid, | ||||
| *   trigger: 'any'/'all', | ||||
| *   criteria: [ | ||||
| *       { | ||||
| *           telemetry: '', | ||||
| *           operation: '', | ||||
| *           input: [], | ||||
| *           metadata: '' | ||||
| *       } | ||||
| *   ] | ||||
| * } | ||||
| */ | ||||
| export default class ConditionClass extends EventEmitter { | ||||
|  | ||||
|     /** | ||||
|      * Manages criteria and emits the result of - true or false - based on criteria evaluated. | ||||
|      * @constructor | ||||
|      * @param conditionConfiguration: {id: uuid,trigger: enum, criteria: Array of {id: uuid, operation: enum, input: Array, metaDataKey: string, key: {domainObject.identifier} } | ||||
|      * @param openmct | ||||
|      */ | ||||
|     constructor(conditionConfiguration, openmct) { | ||||
|         super(); | ||||
|  | ||||
|         this.openmct = openmct; | ||||
|         this.id = conditionConfiguration.id; | ||||
|         this.criteria = []; | ||||
|         this.criteriaResults = {}; | ||||
|         this.result = undefined; | ||||
|         if (conditionConfiguration.configuration.criteria) { | ||||
|             this.createCriteria(conditionConfiguration.configuration.criteria); | ||||
|         } | ||||
|         this.trigger = conditionConfiguration.configuration.trigger; | ||||
|     } | ||||
|  | ||||
|     update(conditionConfiguration) { | ||||
|         this.updateTrigger(conditionConfiguration.configuration.trigger); | ||||
|         this.updateCriteria(conditionConfiguration.configuration.criteria); | ||||
|     } | ||||
|  | ||||
|     updateTrigger(trigger) { | ||||
|         if (this.trigger !== trigger) { | ||||
|             this.trigger = trigger; | ||||
|             this.handleConditionUpdated(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     generateCriterion(criterionConfiguration) { | ||||
|         return { | ||||
|             id: uuid(), | ||||
|             telemetry: criterionConfiguration.telemetry || '', | ||||
|             operation: criterionConfiguration.operation || '', | ||||
|             input: criterionConfiguration.input === undefined ? [] : criterionConfiguration.input, | ||||
|             metadata: criterionConfiguration.metadata || '' | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     createCriteria(criterionConfigurations) { | ||||
|         criterionConfigurations.forEach((criterionConfiguration) => { | ||||
|             this.addCriterion(criterionConfiguration); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     updateCriteria(criterionConfigurations) { | ||||
|         this.destroyCriteria(); | ||||
|         this.createCriteria(criterionConfigurations); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      *  adds criterion to the condition. | ||||
|      */ | ||||
|     addCriterion(criterionConfiguration) { | ||||
|         let criterionConfigurationWithId = this.generateCriterion(criterionConfiguration || null); | ||||
|         let criterion = new TelemetryCriterion(criterionConfigurationWithId, this.openmct); | ||||
|         criterion.on('criterionUpdated', (obj) => this.handleCriterionUpdated(obj)); | ||||
|         criterion.on('criterionResultUpdated', (obj) => this.handleCriterionResult(obj)); | ||||
|         if (!this.criteria) { | ||||
|             this.criteria = []; | ||||
|         } | ||||
|         this.criteria.push(criterion); | ||||
|         return criterionConfigurationWithId.id; | ||||
|     } | ||||
|  | ||||
|     findCriterion(id) { | ||||
|         let criterion; | ||||
|  | ||||
|         for (let i=0, ii=this.criteria.length; i < ii; i ++) { | ||||
|             if (this.criteria[i].id === id) { | ||||
|                 criterion = { | ||||
|                     item: this.criteria[i], | ||||
|                     index: i | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return criterion; | ||||
|     } | ||||
|  | ||||
|     updateCriterion(id, criterionConfiguration) { | ||||
|         let found = this.findCriterion(id); | ||||
|         if (found) { | ||||
|             const newCriterionConfiguration = this.generateCriterion(criterionConfiguration); | ||||
|             let newCriterion = new TelemetryCriterion(newCriterionConfiguration, this.openmct); | ||||
|             newCriterion.on('criterionUpdated', (obj) => this.handleCriterionUpdated(obj)); | ||||
|             newCriterion.on('criterionResultUpdated', (obj) => this.handleCriterionResult(obj)); | ||||
|  | ||||
|             let criterion = found.item; | ||||
|             criterion.unsubscribe(); | ||||
|             criterion.off('criterionUpdated', (obj) => this.handleCriterionUpdated(obj)); | ||||
|             criterion.off('criterionResultUpdated', (obj) => this.handleCriterionResult(obj)); | ||||
|             this.criteria.splice(found.index, 1, newCriterion); | ||||
|             if (this.criteriaResults[criterion.id] !== undefined) { | ||||
|                 delete this.criteriaResults[criterion.id]; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     removeCriterion(id) { | ||||
|         if (this.destroyCriterion(id)) { | ||||
|             this.handleConditionUpdated(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     destroyCriterion(id) { | ||||
|         let found = this.findCriterion(id); | ||||
|         if (found) { | ||||
|             let criterion = found.item; | ||||
|             criterion.destroy(); | ||||
|             // TODO this is passing the wrong args | ||||
|             criterion.off('criterionUpdated', (result) => { | ||||
|                 this.handleCriterionUpdated(id, result); | ||||
|             }); | ||||
|             this.criteria.splice(found.index, 1); | ||||
|             if (this.criteriaResults[criterion.id] !== undefined) { | ||||
|                 delete this.criteriaResults[criterion.id]; | ||||
|             } | ||||
|             return true; | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     handleCriterionUpdated(criterion) { | ||||
|         let found = this.findCriterion(criterion.id); | ||||
|         if (found) { | ||||
|             this.criteria[found.index] = criterion.data; | ||||
|             this.subscribe(); | ||||
|             // TODO nothing is listening to this | ||||
|             this.emitEvent('conditionUpdated', { | ||||
|                 trigger: this.trigger, | ||||
|                 criteria: this.criteria | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     handleCriterionResult(eventData) { | ||||
|         const id = eventData.id; | ||||
|  | ||||
|         if (this.findCriterion(id)) { | ||||
|             this.criteriaResults[id] = eventData.data.result; | ||||
|         } | ||||
|  | ||||
|         this.handleConditionUpdated(eventData.data); | ||||
|     } | ||||
|  | ||||
|     subscribe() { | ||||
|         // TODO it looks like on any single criterion update subscriptions fire for all criteria | ||||
|         this.criteria.forEach((criterion) => { | ||||
|             criterion.subscribe(); | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     handleConditionUpdated(datum) { | ||||
|         // trigger an updated event so that consumers can react accordingly | ||||
|         this.evaluate(); | ||||
|         this.emitEvent('conditionResultUpdated', | ||||
|             Object.assign({}, datum, { result: this.result }) | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     getCriteria() { | ||||
|         return this.criteria; | ||||
|     } | ||||
|  | ||||
|     destroyCriteria() { | ||||
|         let success = true; | ||||
|         //looping through the array backwards since destroyCriterion modifies the criteria array | ||||
|         for (let i=this.criteria.length-1; i >= 0; i--) { | ||||
|             success = success && this.destroyCriterion(this.criteria[i].id); | ||||
|         } | ||||
|         return success; | ||||
|     } | ||||
|  | ||||
|     evaluate() { | ||||
|         this.result = computeCondition(this.criteriaResults, this.trigger === TRIGGER.ALL); | ||||
|     } | ||||
|  | ||||
|     emitEvent(eventName, data) { | ||||
|         this.emit(eventName, { | ||||
|             id: this.id, | ||||
|             data: data | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     destroy() { | ||||
|         if (typeof this.stopObservingForChanges === 'function') { | ||||
|             this.stopObservingForChanges(); | ||||
|         } | ||||
|         this.destroyCriteria(); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										208
									
								
								src/plugins/condition/ConditionManager.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,208 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2020, 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 Condition from "./Condition"; | ||||
| import uuid from "uuid"; | ||||
| import EventEmitter from 'EventEmitter'; | ||||
|  | ||||
| export default class ConditionManager extends EventEmitter { | ||||
|     constructor(conditionSetDomainObject, openmct) { | ||||
|         super(); | ||||
|         this.openmct = openmct; | ||||
|         this.conditionSetDomainObject = conditionSetDomainObject; | ||||
|         this.timeAPI = this.openmct.time; | ||||
|         this.latestTimestamp = {}; | ||||
|         this.initialize(); | ||||
|  | ||||
|         this.stopObservingForChanges = this.openmct.objects.observe(this.conditionSetDomainObject, '*', (newDomainObject) => { | ||||
|             this.conditionSetDomainObject = newDomainObject; | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     initialize() { | ||||
|         this.conditionResults = {}; | ||||
|         this.conditionClassCollection = []; | ||||
|         if (this.conditionSetDomainObject.configuration.conditionCollection.length) { | ||||
|             this.conditionSetDomainObject.configuration.conditionCollection.forEach((conditionConfiguration, index) => { | ||||
|                 this.initCondition(conditionConfiguration, index); | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     updateCondition(conditionConfiguration, index) { | ||||
|         let condition = this.conditionClassCollection[index]; | ||||
|         condition.update(conditionConfiguration); | ||||
|         this.conditionSetDomainObject.configuration.conditionCollection[index] = conditionConfiguration; | ||||
|         this.persistConditions(); | ||||
|     } | ||||
|  | ||||
|     initCondition(conditionConfiguration, index) { | ||||
|         let condition = new Condition(conditionConfiguration, this.openmct); | ||||
|         condition.on('conditionResultUpdated', this.handleConditionResult.bind(this)); | ||||
|         if (index !== undefined) { | ||||
|             this.conditionClassCollection.splice(index + 1, 0, condition); | ||||
|         } else { | ||||
|             this.conditionClassCollection.unshift(condition); | ||||
|         } | ||||
|         //There are no criteria for a default condition and hence no subscriptions. | ||||
|         //Hence the conditionResult must be manually triggered for it. | ||||
|         if (conditionConfiguration.isDefault) { | ||||
|             this.handleConditionResult(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     createCondition(conditionConfiguration) { | ||||
|         let conditionObj; | ||||
|         if (conditionConfiguration) { | ||||
|             conditionObj = { | ||||
|                 ...conditionConfiguration, | ||||
|                 id: uuid(), | ||||
|                 configuration: { | ||||
|                     ...conditionConfiguration.configuration, | ||||
|                     name: `Copy of ${conditionConfiguration.configuration.name}` | ||||
|                 } | ||||
|             }; | ||||
|         } else { | ||||
|             conditionObj = { | ||||
|                 id: uuid(), | ||||
|                 configuration: { | ||||
|                     name: 'Unnamed Condition', | ||||
|                     output: 'false', | ||||
|                     trigger: 'all', | ||||
|                     criteria: [{ | ||||
|                         telemetry: '', | ||||
|                         operation: '', | ||||
|                         input: [], | ||||
|                         metadata: '' | ||||
|                     }] | ||||
|                 }, | ||||
|                 summary: '' | ||||
|             }; | ||||
|         } | ||||
|  | ||||
|         return conditionObj; | ||||
|     } | ||||
|  | ||||
|     addCondition() { | ||||
|         this.createAndSaveCondition(); | ||||
|     } | ||||
|  | ||||
|     cloneCondition(conditionConfiguration, index) { | ||||
|         this.createAndSaveCondition(index, conditionConfiguration); | ||||
|     } | ||||
|  | ||||
|     createAndSaveCondition(index, conditionConfiguration) { | ||||
|         let newCondition = this.createCondition(conditionConfiguration); | ||||
|         if (index !== undefined) { | ||||
|             this.conditionSetDomainObject.configuration.conditionCollection.splice(index + 1, 0, newCondition); | ||||
|         } else { | ||||
|             this.conditionSetDomainObject.configuration.conditionCollection.unshift(newCondition); | ||||
|         } | ||||
|         this.initCondition(newCondition, index); | ||||
|         this.persistConditions(); | ||||
|     } | ||||
|  | ||||
|     removeCondition(index) { | ||||
|         let condition = this.conditionClassCollection[index]; | ||||
|         condition.destroyCriteria(); | ||||
|         condition.off('conditionResultUpdated', this.handleConditionResult.bind(this)); | ||||
|         this.conditionClassCollection.splice(index, 1); | ||||
|         this.conditionSetDomainObject.configuration.conditionCollection.splice(index, 1); | ||||
|         if (this.conditionResults[condition.id] !== undefined) { | ||||
|             delete this.conditionResults[condition.id]; | ||||
|         } | ||||
|         this.persistConditions(); | ||||
|         this.handleConditionResult(); | ||||
|     } | ||||
|  | ||||
|     findConditionById(id) { | ||||
|         return this.conditionClassCollection.find(conditionClass => conditionClass.id === id); | ||||
|     } | ||||
|  | ||||
|     //this.$set(this.conditionClassCollection, reorderEvent.newIndex, oldConditions[reorderEvent.oldIndex]); | ||||
|     reorderConditions(reorderPlan) { | ||||
|         let oldConditions = Array.from(this.conditionSetDomainObject.configuration.conditionCollection); | ||||
|         let newCollection = []; | ||||
|         reorderPlan.forEach((reorderEvent) => { | ||||
|             let item = oldConditions[reorderEvent.oldIndex]; | ||||
|             newCollection.push(item); | ||||
|             this.conditionSetDomainObject.configuration.conditionCollection = newCollection; | ||||
|         }); | ||||
|         this.persistConditions(); | ||||
|     } | ||||
|  | ||||
|     handleConditionResult(resultObj) { | ||||
|         const conditionCollection = this.conditionSetDomainObject.configuration.conditionCollection; | ||||
|         let currentCondition = conditionCollection[conditionCollection.length-1]; | ||||
|  | ||||
|         if (resultObj) { | ||||
|             const id = resultObj.id; | ||||
|             if (this.findConditionById(id)) { | ||||
|                 this.conditionResults[id] = resultObj.data.result; | ||||
|             } | ||||
|             this.updateTimestamp(resultObj.data); | ||||
|         } | ||||
|  | ||||
|         for (let i = 0; i < conditionCollection.length - 1; i++) { | ||||
|             if (this.conditionResults[conditionCollection[i].id]) { | ||||
|                 //first condition to be true wins | ||||
|                 currentCondition = conditionCollection[i]; | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         this.emit('conditionSetResultUpdated', | ||||
|             Object.assign( | ||||
|                 { | ||||
|                     output: currentCondition.configuration.output, | ||||
|                     id: this.conditionSetDomainObject.identifier, | ||||
|                     conditionId: currentCondition.id | ||||
|                 }, | ||||
|                 this.latestTimestamp | ||||
|             ) | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|     updateTimestamp(timestamp) { | ||||
|         this.timeAPI.getAllTimeSystems().forEach(timeSystem => { | ||||
|             if (!this.latestTimestamp[timeSystem.key] | ||||
|                 || timestamp[timeSystem.key] > this.latestTimestamp[timeSystem.key] | ||||
|             ) { | ||||
|                 this.latestTimestamp[timeSystem.key] = timestamp[timeSystem.key]; | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     persistConditions() { | ||||
|         this.openmct.objects.mutate(this.conditionSetDomainObject, 'configuration.conditionCollection', this.conditionSetDomainObject.configuration.conditionCollection); | ||||
|     } | ||||
|  | ||||
|     destroy() { | ||||
|         if(this.stopObservingForChanges) { | ||||
|             this.stopObservingForChanges(); | ||||
|         } | ||||
|         this.conditionClassCollection.forEach((condition) => { | ||||
|             condition.off('conditionResultUpdated', this.handleConditionResult); | ||||
|             condition.destroy(); | ||||
|         }) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										99
									
								
								src/plugins/condition/ConditionManagerSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,99 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2020, 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 ConditionManager  from './ConditionManager'; | ||||
|  | ||||
| describe('ConditionManager', () => { | ||||
|  | ||||
|     let conditionMgr; | ||||
|     let mockListener; | ||||
|     let openmct = {}; | ||||
|     let mockCondition = { | ||||
|         isDefault: true, | ||||
|         id: '1234-5678', | ||||
|         configuration: { | ||||
|             criteria: [] | ||||
|         } | ||||
|     }; | ||||
|     let conditionSetDomainObject = { | ||||
|         identifier: { | ||||
|             namespace: "", | ||||
|             key: "600a7372-8d48-4dc4-98b6-548611b1ff7e" | ||||
|         }, | ||||
|         type: "conditionSet", | ||||
|         location: "mine", | ||||
|         configuration: { | ||||
|             conditionCollection: [ | ||||
|                 mockCondition | ||||
|             ] | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     function mockAngularComponents() { | ||||
|         let mockInjector = jasmine.createSpyObj('$injector', ['get']); | ||||
|  | ||||
|         let mockInstantiate = jasmine.createSpy('mockInstantiate'); | ||||
|         mockInstantiate.and.returnValue(mockInstantiate); | ||||
|  | ||||
|         let mockDomainObject = { | ||||
|             useCapability: function () { | ||||
|                 return mockCondition; | ||||
|             } | ||||
|         }; | ||||
|         mockInstantiate.and.callFake(function () { | ||||
|             return mockDomainObject; | ||||
|         }); | ||||
|         mockInjector.get.and.callFake(function (service) { | ||||
|             return { | ||||
|                 'instantiate': mockInstantiate | ||||
|             }[service]; | ||||
|         }); | ||||
|  | ||||
|         openmct.$injector = mockInjector; | ||||
|     } | ||||
|  | ||||
|     beforeAll(function () { | ||||
|  | ||||
|         mockAngularComponents(); | ||||
|  | ||||
|         openmct.objects = jasmine.createSpyObj('objects', ['get', 'makeKeyString', 'observe', 'mutate']); | ||||
|         openmct.objects.get.and.returnValues(new Promise(function (resolve, reject) { | ||||
|             resolve(conditionSetDomainObject); | ||||
|         }), new Promise(function (resolve, reject) { | ||||
|             resolve(mockCondition); | ||||
|         })); | ||||
|         openmct.objects.makeKeyString.and.returnValue(conditionSetDomainObject.identifier.key); | ||||
|         openmct.objects.observe.and.returnValue(function () {}); | ||||
|         openmct.objects.mutate.and.returnValue(function () {}); | ||||
|         conditionMgr = new ConditionManager(conditionSetDomainObject, openmct); | ||||
|         mockListener = jasmine.createSpy('mockListener'); | ||||
|  | ||||
|         conditionMgr.on('conditionSetResultUpdated', mockListener); | ||||
|     }); | ||||
|  | ||||
|     it('creates a conditionCollection with a default condition', function () { | ||||
|         expect(conditionMgr.conditionSetDomainObject.configuration.conditionCollection.length).toEqual(1); | ||||
|         let defaultConditionId = conditionMgr.conditionClassCollection[0].id; | ||||
|         expect(defaultConditionId).toEqual(mockCondition.id); | ||||
|     }); | ||||
|  | ||||
| }); | ||||
| @@ -1,5 +1,5 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2018, United States Government | ||||
|  * Open MCT, Copyright (c) 2014-2020, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
| @@ -19,16 +19,15 @@ | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
| /*global define*/ | ||||
| 
 | ||||
| define([ | ||||
|     <%= implPaths %> | ||||
|     'legacyRegistry' | ||||
| ], function ( | ||||
|     <%= implNames %> | ||||
|     legacyRegistry | ||||
| ) { | ||||
|     "use strict"; | ||||
| export default function ConditionSetCompositionPolicy(openmct) { | ||||
|     return { | ||||
|         allow: function (parent, child) { | ||||
|             if (parent.type === 'conditionSet' && !openmct.telemetry.isTelemetryObject(child)) { | ||||
|                 return false; | ||||
|             } | ||||
| 
 | ||||
|     legacyRegistry.register("<%= bundleName %>", <%= bundleContents %>); | ||||
| }); | ||||
|             return true; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										87
									
								
								src/plugins/condition/ConditionSetCompositionPolicySpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,87 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2020, 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 ConditionSetCompositionPolicy  from './ConditionSetCompositionPolicy'; | ||||
|  | ||||
| describe('ConditionSetCompositionPolicy', () => { | ||||
|  | ||||
|     let policy; | ||||
|     let testTelemetryObject; | ||||
|     let openmct = {}; | ||||
|     let parentDomainObject; | ||||
|     let composition; | ||||
|  | ||||
|     beforeAll(function () { | ||||
|         testTelemetryObject = { | ||||
|             identifier:{ namespace: "", key: "test-object"}, | ||||
|             type: "test-object", | ||||
|             name: "Test Object", | ||||
|             telemetry: { | ||||
|                 values: [{ | ||||
|                     key: "some-key", | ||||
|                     name: "Some attribute", | ||||
|                     hints: { | ||||
|                         domain: 1 | ||||
|                     } | ||||
|                 }, { | ||||
|                     key: "some-other-key", | ||||
|                     name: "Another attribute", | ||||
|                     hints: { | ||||
|                         range: 1 | ||||
|                     } | ||||
|                 }] | ||||
|             } | ||||
|         }; | ||||
|         openmct.objects = jasmine.createSpyObj('objects', ['get', 'makeKeyString']); | ||||
|         openmct.objects.get.and.returnValue(testTelemetryObject); | ||||
|         openmct.objects.makeKeyString.and.returnValue(testTelemetryObject.identifier.key); | ||||
|         openmct.telemetry = jasmine.createSpyObj('telemetry', ['isTelemetryObject']); | ||||
|         policy = new ConditionSetCompositionPolicy(openmct); | ||||
|         parentDomainObject = {}; | ||||
|         composition = {}; | ||||
|     }); | ||||
|  | ||||
|     it('returns true for object types that are not conditionSets', function () { | ||||
|         parentDomainObject.type = 'random'; | ||||
|         openmct.telemetry.isTelemetryObject.and.returnValue(false); | ||||
|         expect(policy.allow(parentDomainObject, {})).toBe(true); | ||||
|     }); | ||||
|  | ||||
|     it('returns false for object types that are not telemetry objects when parent is a conditionSet', function () { | ||||
|         parentDomainObject.type = 'conditionSet'; | ||||
|         openmct.telemetry.isTelemetryObject.and.returnValue(false); | ||||
|         expect(policy.allow(parentDomainObject, {})).toBe(false); | ||||
|     }); | ||||
|  | ||||
|     it('returns true for object types that are telemetry objects when parent is a conditionSet', function () { | ||||
|         parentDomainObject.type = 'conditionSet'; | ||||
|         openmct.telemetry.isTelemetryObject.and.returnValue(true); | ||||
|         expect(policy.allow(parentDomainObject, testTelemetryObject)).toBe(true); | ||||
|     }); | ||||
|  | ||||
|     it('returns true for object types that are telemetry objects when parent is not a conditionSet', function () { | ||||
|         parentDomainObject.type = 'random'; | ||||
|         openmct.telemetry.isTelemetryObject.and.returnValue(true); | ||||
|         expect(policy.allow(parentDomainObject, testTelemetryObject)).toBe(true); | ||||
|     }); | ||||
|  | ||||
| }); | ||||
							
								
								
									
										37
									
								
								src/plugins/condition/ConditionSetMetadataProvider.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,37 @@ | ||||
| export default class ConditionSetMetadataProvider { | ||||
|     constructor(openmct) { | ||||
|         this.openmct = openmct; | ||||
|     } | ||||
|  | ||||
|     supportsMetadata(domainObject) { | ||||
|         return domainObject.type === 'conditionSet'; | ||||
|     } | ||||
|  | ||||
|     getDomains(domainObject) { | ||||
|         return this.openmct.time.getAllTimeSystems().map(function (ts, i) { | ||||
|             return { | ||||
|                 key: ts.key, | ||||
|                 name: ts.name, | ||||
|                 format: ts.timeFormat, | ||||
|                 hints: { | ||||
|                     domain: i | ||||
|                 } | ||||
|             }; | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     getMetadata(domainObject) { | ||||
|         return { | ||||
|             values: this.getDomains().concat([ | ||||
|                 { | ||||
|                     name: 'Output', | ||||
|                     key: 'output', | ||||
|                     format: 'string', | ||||
|                     hints: { | ||||
|                         range: 1 | ||||
|                     } | ||||
|                 } | ||||
|             ]) | ||||
|         }; | ||||
|     } | ||||
| } | ||||
| @@ -1,5 +1,5 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2018, United States Government | ||||
|  * Open MCT, Copyright (c) 2014-2020, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
| @@ -20,30 +20,32 @@ | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
| 
 | ||||
| // Converts all templateUrl references in bundle.js files to
 | ||||
| // plain template references, loading said templates with the
 | ||||
| // RequireJS text plugin.
 | ||||
| import ConditionManager from './ConditionManager' | ||||
| 
 | ||||
| var glob = require('glob'), | ||||
|     fs = require('fs'); | ||||
| 
 | ||||
| function migrate(file) { | ||||
|     var sourceCode = fs.readFileSync(file, 'utf8'), | ||||
|         lines = sourceCode.split('\n') | ||||
|             .filter(function (line) { | ||||
|                 return !(/^\W*['"]use strict['"];\W*$/.test(line)); | ||||
|             }) | ||||
|             .filter(function (line) { | ||||
|                 return line.indexOf("/*global") !== 0; | ||||
|             }); | ||||
|     fs.writeFileSync(file, lines.join('\n')); | ||||
| } | ||||
| 
 | ||||
| glob('@(src|platform)/**/*.js', {}, function (err, files) { | ||||
|     if (err) { | ||||
|         console.log(err); | ||||
|         return; | ||||
| export default class ConditionSetTelemetryProvider { | ||||
|     constructor(openmct) { | ||||
|         this.openmct = openmct; | ||||
|     } | ||||
| 
 | ||||
|     files.forEach(migrate); | ||||
| }); | ||||
|     isTelemetryObject(domainObject) { | ||||
|         return domainObject.type === 'conditionSet'; | ||||
|     } | ||||
| 
 | ||||
|     supportsRequest(domainObject, options) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     supportsSubscribe(domainObject) { | ||||
|         return domainObject.type === 'conditionSet'; | ||||
|     } | ||||
| 
 | ||||
|     subscribe(domainObject, callback) { | ||||
|         let conditionManager = new ConditionManager(domainObject, this.openmct); | ||||
|         conditionManager.on('conditionSetResultUpdated', callback); | ||||
| 
 | ||||
|         return function unsubscribe() { | ||||
|             conditionManager.off('conditionSetResultUpdated'); | ||||
|             conditionManager.destroy(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										84
									
								
								src/plugins/condition/ConditionSetViewProvider.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,84 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2020, 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 ConditionSet from './components/ConditionSet.vue'; | ||||
| import Vue from 'vue'; | ||||
|  | ||||
| const DEFAULT_VIEW_PRIORITY = 100; | ||||
|  | ||||
| export default class ConditionSetViewProvider { | ||||
|     constructor(openmct) { | ||||
|         this.openmct = openmct; | ||||
|         this.name = 'Conditions View'; | ||||
|         this.key = 'conditionSet.view'; | ||||
|         this.cssClass = 'icon-conditional'; // TODO: replace with class for new icon | ||||
|     } | ||||
|  | ||||
|     canView(domainObject) { | ||||
|         return domainObject.type === 'conditionSet'; | ||||
|     } | ||||
|  | ||||
|     canEdit(domainObject) { | ||||
|         return domainObject.type === 'conditionSet'; | ||||
|     } | ||||
|  | ||||
|     view(domainObject, objectPath) { | ||||
|         let component; | ||||
|         const openmct = this.openmct; | ||||
|         return { | ||||
|             show: (container, isEditing) => { | ||||
|                 component = new Vue({ | ||||
|                     el: container, | ||||
|                     components: { | ||||
|                         ConditionSet | ||||
|                     }, | ||||
|                     provide: { | ||||
|                         openmct, | ||||
|                         domainObject, | ||||
|                         objectPath | ||||
|                     }, | ||||
|                     data() { | ||||
|                         return { | ||||
|                             isEditing | ||||
|                         } | ||||
|                     }, | ||||
|                     template: '<condition-set :isEditing="isEditing"></condition-set>' | ||||
|                 }); | ||||
|             }, | ||||
|             onEditModeChange: (isEditing) => { | ||||
|                 component.isEditing = isEditing; | ||||
|             }, | ||||
|             destroy: () => { | ||||
|                 component.$destroy(); | ||||
|                 component = undefined; | ||||
|             } | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     priority(domainObject) { | ||||
|         if (domainObject.type === 'conditionSet') { | ||||
|             return Number.MAX_VALUE; | ||||
|         } else { | ||||
|             return DEFAULT_VIEW_PRIORITY; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										122
									
								
								src/plugins/condition/ConditionSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,122 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2020, 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 Condition from "./Condition"; | ||||
| import {TRIGGER} from "./utils/constants"; | ||||
| import TelemetryCriterion from "./criterion/TelemetryCriterion"; | ||||
|  | ||||
| let openmct = {}, | ||||
|     mockListener, | ||||
|     testConditionDefinition, | ||||
|     testTelemetryObject, | ||||
|     conditionObj; | ||||
|  | ||||
| describe("The condition", function () { | ||||
|  | ||||
|     beforeEach (() => { | ||||
|         mockListener = jasmine.createSpy('listener'); | ||||
|         testTelemetryObject = { | ||||
|             identifier:{ namespace: "", key: "test-object"}, | ||||
|             type: "test-object", | ||||
|             name: "Test Object", | ||||
|             telemetry: { | ||||
|                 values: [{ | ||||
|                     key: "some-key", | ||||
|                     name: "Some attribute", | ||||
|                     hints: { | ||||
|                         domain: 1 | ||||
|                     } | ||||
|                 }, { | ||||
|                     key: "some-other-key", | ||||
|                     name: "Another attribute", | ||||
|                     hints: { | ||||
|                         range: 1 | ||||
|                     } | ||||
|                 }] | ||||
|             } | ||||
|         }; | ||||
|         openmct.objects = jasmine.createSpyObj('objects', ['get', 'makeKeyString']); | ||||
|         openmct.objects.get.and.returnValue(new Promise(function (resolve, reject) { | ||||
|             resolve(testTelemetryObject); | ||||
|         }));        openmct.objects.makeKeyString.and.returnValue(testTelemetryObject.identifier.key); | ||||
|         openmct.telemetry = jasmine.createSpyObj('telemetry', ['isTelemetryObject', 'subscribe', 'getMetadata']); | ||||
|         openmct.telemetry.isTelemetryObject.and.returnValue(true); | ||||
|         openmct.telemetry.subscribe.and.returnValue(function () {}); | ||||
|         openmct.telemetry.getMetadata.and.returnValue(testTelemetryObject.telemetry.values); | ||||
|  | ||||
|         testConditionDefinition = { | ||||
|             id: '123-456', | ||||
|             configuration: { | ||||
|                 trigger: TRIGGER.ANY, | ||||
|                 criteria: [ | ||||
|                     { | ||||
|                         operation: 'equalTo', | ||||
|                         input: false, | ||||
|                         metadata: 'value', | ||||
|                         telemetry: testTelemetryObject.identifier | ||||
|                     } | ||||
|                 ] | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         conditionObj = new Condition( | ||||
|             testConditionDefinition, | ||||
|             openmct | ||||
|         ); | ||||
|  | ||||
|         conditionObj.on('conditionUpdated', mockListener); | ||||
|  | ||||
|     }); | ||||
|  | ||||
|     it("generates criteria with an id", function () { | ||||
|         const testCriterion = testConditionDefinition.configuration.criteria[0]; | ||||
|         let criterion = conditionObj.generateCriterion(testCriterion); | ||||
|         expect(criterion.id).toBeDefined(); | ||||
|         expect(criterion.operation).toEqual(testCriterion.operation); | ||||
|         expect(criterion.input).toEqual(testCriterion.input); | ||||
|         expect(criterion.metadata).toEqual(testCriterion.metadata); | ||||
|         expect(criterion.telemetry).toEqual(testCriterion.telemetry); | ||||
|     }); | ||||
|  | ||||
|     it("initializes with an id", function () { | ||||
|         expect(conditionObj.id).toBeDefined(); | ||||
|     }); | ||||
|  | ||||
|     it("initializes with criteria from the condition definition", function () { | ||||
|         expect(conditionObj.criteria.length).toEqual(1); | ||||
|         let criterion = conditionObj.criteria[0]; | ||||
|         expect(criterion instanceof TelemetryCriterion).toBeTrue(); | ||||
|         expect(criterion.operator).toEqual(testConditionDefinition.configuration.criteria[0].operator); | ||||
|         expect(criterion.input).toEqual(testConditionDefinition.configuration.criteria[0].input); | ||||
|         expect(criterion.metadata).toEqual(testConditionDefinition.configuration.criteria[0].metadata); | ||||
|     }); | ||||
|  | ||||
|     it("initializes with the trigger from the condition definition", function () { | ||||
|         expect(conditionObj.trigger).toEqual(testConditionDefinition.configuration.trigger); | ||||
|     }); | ||||
|  | ||||
|     it("destroys all criteria for a condition", function () { | ||||
|         const result = conditionObj.destroyCriteria(); | ||||
|         expect(result).toBeTrue(); | ||||
|         expect(conditionObj.criteria.length).toEqual(0); | ||||
|     }); | ||||
| }); | ||||
							
								
								
									
										103
									
								
								src/plugins/condition/StyleRuleManager.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,103 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2020, 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 StyleRuleManager extends EventEmitter { | ||||
|     constructor(conditionalStyleConfiguration, openmct) { | ||||
|         super(); | ||||
|         this.openmct = openmct; | ||||
|         if (conditionalStyleConfiguration && conditionalStyleConfiguration.conditionSetIdentifier) { | ||||
|             this.initialize(conditionalStyleConfiguration); | ||||
|             this.subscribeToConditionSet(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     initialize(conditionalStyleConfiguration) { | ||||
|         this.conditionSetIdentifier = conditionalStyleConfiguration.conditionSetIdentifier; | ||||
|         this.updateConditionStylesMap(conditionalStyleConfiguration.styles || []); | ||||
|     } | ||||
|  | ||||
|     subscribeToConditionSet() { | ||||
|         if (this.stopProvidingTelemetry) { | ||||
|             this.stopProvidingTelemetry(); | ||||
|         } | ||||
|         this.openmct.objects.get(this.conditionSetIdentifier).then((conditionSetDomainObject) => { | ||||
|             this.stopProvidingTelemetry = this.openmct.telemetry.subscribe(conditionSetDomainObject, output => this.handleConditionSetResultUpdated(output)); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     updateConditionalStyleConfig(conditionalStyleConfiguration) { | ||||
|         if (!conditionalStyleConfiguration || !conditionalStyleConfiguration.conditionSetIdentifier) { | ||||
|             this.destroy(); | ||||
|         } else { | ||||
|             let isNewConditionSet = !this.conditionSetIdentifier || | ||||
|                                     this.openmct.objects.areIdsEqual(this.conditionSetIdentifier, conditionalStyleConfiguration.conditionSetIdentifier); | ||||
|             this.initialize(conditionalStyleConfiguration); | ||||
|             //Only resubscribe if the conditionSet has changed. | ||||
|             if (isNewConditionSet) { | ||||
|                 this.subscribeToConditionSet(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     updateConditionStylesMap(conditionStyles) { | ||||
|         let conditionStyleMap = {}; | ||||
|         conditionStyles.forEach((conditionStyle) => { | ||||
|             conditionStyleMap[conditionStyle.conditionId] = conditionStyle.style; | ||||
|         }); | ||||
|         this.conditionalStyleMap = conditionStyleMap; | ||||
|     } | ||||
|  | ||||
|     handleConditionSetResultUpdated(resultData) { | ||||
|         let foundStyle = this.conditionalStyleMap[resultData.conditionId]; | ||||
|         if (foundStyle) { | ||||
|             if (foundStyle !== this.currentStyle) { | ||||
|                 this.currentStyle = foundStyle; | ||||
|             } | ||||
|         } else { | ||||
|             if (this.currentStyle !== this.defaultStyle) { | ||||
|                 this.currentStyle = this.defaultStyle; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         this.updateDomainObjectStyle(); | ||||
|     } | ||||
|  | ||||
|     updateDomainObjectStyle() { | ||||
|         this.emit('conditionalStyleUpdated', this.currentStyle) | ||||
|     } | ||||
|  | ||||
|     destroy() { | ||||
|         if (this.currentStyle) { | ||||
|             Object.keys(this.currentStyle).forEach(key => { | ||||
|                 this.currentStyle[key] = 'inherit'; | ||||
|             }); | ||||
|         } | ||||
|         this.updateDomainObjectStyle(); | ||||
|         if (this.stopProvidingTelemetry) { | ||||
|             this.stopProvidingTelemetry(); | ||||
|         } | ||||
|         this.conditionSetIdentifier = undefined; | ||||
|     } | ||||
|  | ||||
| } | ||||
							
								
								
									
										356
									
								
								src/plugins/condition/components/Condition.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,356 @@ | ||||
| /***************************************************************************** | ||||
| * Open MCT, Copyright (c) 2014-2020, 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 v-if="isEditing" | ||||
|      class="c-condition c-condition--edit js-condition-drag-wrapper" | ||||
| > | ||||
|     <!-- Edit view --> | ||||
|     <div class="c-condition__header"> | ||||
|         <span class="c-condition__drag-grippy c-grippy c-grippy--vertical-drag" | ||||
|               title="Drag to reorder conditions" | ||||
|               :class="[{ 'is-enabled': !condition.isDefault }, { 'hide-nice': condition.isDefault }]" | ||||
|               :draggable="!condition.isDefault" | ||||
|               @dragstart="dragStart" | ||||
|               @dragstop="dragStop" | ||||
|               @dragover.stop | ||||
|         ></span> | ||||
|  | ||||
|         <span class="c-condition__disclosure c-disclosure-triangle c-tree__item__view-control is-enabled" | ||||
|               :class="{ 'c-disclosure-triangle--expanded': expanded }" | ||||
|               @click="expanded = !expanded" | ||||
|         ></span> | ||||
|  | ||||
|         <span class="c-condition__name">{{ condition.configuration.name }}</span> | ||||
|         <!-- TODO: description should be derived from criteria --> | ||||
|         <span v-if="condition.isDefault" | ||||
|               class="c-condition__summary" | ||||
|         > | ||||
|             When all else fails | ||||
|         </span> | ||||
|         <span v-else | ||||
|               class="c-condition__summary" | ||||
|         > | ||||
|             <template v-if="!canEvaluateCriteria"> | ||||
|                 Define criteria | ||||
|             </template> | ||||
|             <template v-else> | ||||
|                 When | ||||
|                 <span v-for="(criterion, index) in condition.configuration.criteria" | ||||
|                       :key="index" | ||||
|                 > | ||||
|                     {{ getRule(criterion, index) }} | ||||
|                     <template v-if="!isLastCriterion"> | ||||
|                         {{ getConjunction }} | ||||
|                     </template> | ||||
|                 </span> | ||||
|             </template> | ||||
|         </span> | ||||
|  | ||||
|         <div class="c-condition__buttons"> | ||||
|             <button v-if="!condition.isDefault" | ||||
|                     class="c-click-icon c-condition__duplicate-button icon-duplicate" | ||||
|                     title="Duplicate this condition" | ||||
|                     @click="cloneCondition" | ||||
|             ></button> | ||||
|  | ||||
|             <button v-if="!condition.isDefault" | ||||
|                     class="c-click-icon c-condition__delete-button icon-trash" | ||||
|                     title="Delete this condition" | ||||
|                     @click="removeCondition" | ||||
|             ></button> | ||||
|         </div> | ||||
|     </div> | ||||
|     <div v-if="expanded" | ||||
|          class="c-condition__definition c-cdef" | ||||
|     > | ||||
|         <span class="c-cdef__separator c-row-separator"></span> | ||||
|         <span class="c-cdef__label">Condition Name</span> | ||||
|         <span class="c-cdef__controls"> | ||||
|             <input v-model="condition.configuration.name" | ||||
|                    class="t-condition-input__name" | ||||
|                    type="text" | ||||
|                    @blur="persist" | ||||
|             > | ||||
|         </span> | ||||
|  | ||||
|         <span class="c-cdef__label">Output</span> | ||||
|         <span class="c-cdef__controls"> | ||||
|             <select v-model="selectedOutputSelection" | ||||
|                     @change="setOutputValue" | ||||
|             > | ||||
|                 <option value="">- Select Output -</option> | ||||
|                 <option v-for="option in outputOptions" | ||||
|                         :key="option" | ||||
|                         :value="option" | ||||
|                 > | ||||
|                     {{ initCap(option) }} | ||||
|                 </option> | ||||
|             </select> | ||||
|             <input v-if="selectedOutputSelection === outputOptions[2]" | ||||
|                    v-model="condition.configuration.output" | ||||
|                    class="t-condition-name-input" | ||||
|                    type="text" | ||||
|                    @blur="persist" | ||||
|             > | ||||
|         </span> | ||||
|  | ||||
|         <div v-if="!condition.isDefault" | ||||
|              class="c-cdef__match-and-criteria" | ||||
|         > | ||||
|             <span class="c-cdef__separator c-row-separator"></span> | ||||
|             <span class="c-cdef__label">Match</span> | ||||
|             <span class="c-cdef__controls"> | ||||
|                 <select v-model="condition.configuration.trigger" | ||||
|                         @change="persist" | ||||
|                 > | ||||
|                     <option value="all">when all criteria are met</option> | ||||
|                     <option value="any">when any criteria are met</option> | ||||
|                 </select> | ||||
|             </span> | ||||
|  | ||||
|             <template v-if="telemetry.length"> | ||||
|                 <div v-for="(criterion, index) in condition.configuration.criteria" | ||||
|                      :key="index" | ||||
|                      class="c-cdef__criteria" | ||||
|                 > | ||||
|                     <Criterion :telemetry="telemetry" | ||||
|                                :criterion="criterion" | ||||
|                                :index="index" | ||||
|                                :trigger="condition.configuration.trigger" | ||||
|                                :is-default="condition.configuration.criteria.length === 1" | ||||
|                                @persist="persist" | ||||
|                     /> | ||||
|                     <div class="c-cdef__criteria__buttons"> | ||||
|                         <button class="c-click-icon c-cdef__criteria-duplicate-button icon-duplicate" | ||||
|                                 title="Duplicate this criteria" | ||||
|                                 @click="cloneCriterion(index)" | ||||
|                         ></button> | ||||
|                         <button v-if="!(condition.configuration.criteria.length === 1)" | ||||
|                                 class="c-click-icon c-cdef__criteria-duplicate-button icon-trash" | ||||
|                                 title="Delete this criteria" | ||||
|                                 @click="removeCriterion(index)" | ||||
|                         ></button> | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </template> | ||||
|             <div class="c-cdef__separator c-row-separator"></div> | ||||
|             <div class="c-cdef__controls" | ||||
|                  :disabled="!telemetry.length" | ||||
|             > | ||||
|                 <button | ||||
|                     class="c-cdef__add-criteria-button c-button c-button--labeled icon-plus" | ||||
|                     @click="addCriteria" | ||||
|                 > | ||||
|                     <span class="c-button__label">Add Criteria</span> | ||||
|                 </button> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
| </div> | ||||
| <div v-else | ||||
|      class="c-condition c-condition--browse" | ||||
| > | ||||
|     <!-- Browse view --> | ||||
|     <div class="c-condition__header"> | ||||
|         <span class="c-condition__name"> | ||||
|             {{ condition.configuration.name }} | ||||
|         </span> | ||||
|         <span class="c-condition__output"> | ||||
|             Output: {{ condition.configuration.output }} | ||||
|         </span> | ||||
|     </div> | ||||
|     <div v-if="condition.isDefault" | ||||
|          class="c-condition__summary" | ||||
|     > | ||||
|         When all else fails | ||||
|     </div> | ||||
|     <div v-else | ||||
|          class="c-condition__summary" | ||||
|     > | ||||
|         <template v-if="!canEvaluateCriteria"> | ||||
|             Define criteria | ||||
|         </template> | ||||
|         <template v-else> | ||||
|             When | ||||
|             <span v-for="(criterion, index) in condition.configuration.criteria" | ||||
|                   :key="index" | ||||
|             > | ||||
|                 {{ getRule(criterion, index) }} | ||||
|                 <template v-if="!isLastCriterion"> | ||||
|                     {{ getConjunction }} | ||||
|                 </template> | ||||
|             </span> | ||||
|         </template> | ||||
|     </div> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import Criterion from './Criterion.vue'; | ||||
| import { OPERATIONS } from '../utils/operations'; | ||||
|  | ||||
| export default { | ||||
|     inject: ['openmct'], | ||||
|     components: { | ||||
|         Criterion | ||||
|     }, | ||||
|     props: { | ||||
|         condition: { | ||||
|             type: Object, | ||||
|             required: true | ||||
|         }, | ||||
|         conditionIndex: { | ||||
|             type: Number, | ||||
|             required: true | ||||
|         }, | ||||
|         isEditing: { | ||||
|             type: Boolean, | ||||
|             required: true | ||||
|         }, | ||||
|         telemetry: { | ||||
|             type: Array, | ||||
|             required: true, | ||||
|             default: () => [] | ||||
|         } | ||||
|     }, | ||||
|     data() { | ||||
|         return { | ||||
|             currentCriteria: this.currentCriteria, | ||||
|             expanded: true, | ||||
|             trigger: 'all', | ||||
|             selectedOutputSelection: '', | ||||
|             outputOptions: ['false', 'true', 'string'], | ||||
|             criterionIndex: 0 | ||||
|         }; | ||||
|     }, | ||||
|     computed: { | ||||
|         canEvaluateCriteria: function () { | ||||
|             let criteria = this.condition.configuration.criteria; | ||||
|             let lastCriterion = criteria[criteria.length - 1]; | ||||
|             if (lastCriterion.telemetry && | ||||
|                 lastCriterion.operation && | ||||
|                 (lastCriterion.input.length || | ||||
|                 lastCriterion.operation === 'isDefined' || | ||||
|                 lastCriterion.operation === 'isUndefined')) { | ||||
|                 return true; | ||||
|             } else { | ||||
|                 return false; | ||||
|             } | ||||
|         }, | ||||
|         getConjunction: function () { | ||||
|             return this.condition.configuration.trigger === 'all' ? 'and' : 'or'; | ||||
|         } | ||||
|     }, | ||||
|     destroyed() { | ||||
|         this.destroy(); | ||||
|     }, | ||||
|     mounted() { | ||||
|         this.setOutputSelection(); | ||||
|     }, | ||||
|     methods: { | ||||
|         getRule(criterion, index) { | ||||
|             return `${criterion.telemetry.name} ${criterion.telemetry.fieldName} ${this.findDescription(criterion.operation, criterion.input)}`; | ||||
|         }, | ||||
|         isLastCriterion(index) { | ||||
|             return index === this.condition.configuration.criteria.length - 1; | ||||
|         }, | ||||
|         findDescription(operation, values) { | ||||
|             for (let i=0, ii= OPERATIONS.length; i < ii; i++) { | ||||
|                 if (operation === OPERATIONS[i].name) { | ||||
|                     return OPERATIONS[i].getDescription(values); | ||||
|                 } | ||||
|             } | ||||
|             return null; | ||||
|         }, | ||||
|         setOutputSelection() { | ||||
|             let conditionOutput = this.condition.configuration.output; | ||||
|             if (conditionOutput) { | ||||
|                 if (conditionOutput !== 'false' && conditionOutput !== 'true') { | ||||
|                     this.selectedOutputSelection = 'string'; | ||||
|                 } else { | ||||
|                     this.selectedOutputSelection = conditionOutput; | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         setOutputValue() { | ||||
|             if (this.selectedOutputSelection === 'string') { | ||||
|                 this.condition.configuration.output = ''; | ||||
|             } else { | ||||
|                 this.condition.configuration.output = this.selectedOutputSelection; | ||||
|             } | ||||
|             this.persist(); | ||||
|         }, | ||||
|         addCriteria() { | ||||
|             const criteriaObject = { | ||||
|                 telemetry: '', | ||||
|                 operation: '', | ||||
|                 input: '', | ||||
|                 metadata: '' | ||||
|             }; | ||||
|             this.condition.configuration.criteria.push(criteriaObject); | ||||
|         }, | ||||
|         dragStart(e) { | ||||
|             e.dataTransfer.setData('dragging', e.target); // required for FF to initiate drag | ||||
|             e.dataTransfer.effectAllowed = "copyMove"; | ||||
|             e.dataTransfer.setDragImage(e.target.closest('.js-condition-drag-wrapper'), 0, 0); | ||||
|             this.$emit('setMoveIndex', this.conditionIndex); | ||||
|         }, | ||||
|         dragStop(e) { | ||||
|             e.dataTransfer.clearData(); | ||||
|         }, | ||||
|         destroy() { | ||||
|         }, | ||||
|         removeCondition(ev) { | ||||
|             this.$emit('removeCondition', this.conditionIndex); | ||||
|         }, | ||||
|         cloneCondition(ev) { | ||||
|             this.$emit('cloneCondition', { | ||||
|                 condition: this.condition, | ||||
|                 index: this.conditionIndex | ||||
|             }); | ||||
|         }, | ||||
|         removeCriterion(index) { | ||||
|             this.condition.configuration.criteria.splice(index, 1); | ||||
|             this.persist() | ||||
|         }, | ||||
|         cloneCriterion(index) { | ||||
|             const clonedCriterion = {...this.condition.configuration.criteria[index]}; | ||||
|             this.condition.configuration.criteria.splice(index + 1, 0, clonedCriterion); | ||||
|             this.persist() | ||||
|         }, | ||||
|         hasTelemetry(identifier) { | ||||
|             // TODO: check parent condition.composition.hasTelemetry | ||||
|             return this.currentCriteria && identifier; | ||||
|         }, | ||||
|         persist() { | ||||
|             this.$emit('updateCondition', { | ||||
|                 condition: this.condition, | ||||
|                 index: this.conditionIndex | ||||
|             }); | ||||
|         }, | ||||
|         initCap: function (str) { | ||||
|             return str.charAt(0).toUpperCase() + str.slice(1) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										210
									
								
								src/plugins/condition/components/ConditionCollection.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,210 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2020, 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> | ||||
| <section id="conditionCollection" | ||||
|          class="c-cs__conditions" | ||||
|          :class="{ 'is-expanded': expanded }" | ||||
| > | ||||
|     <div class="c-cs__header c-section__header"> | ||||
|         <span | ||||
|             class="c-disclosure-triangle c-tree__item__view-control is-enabled" | ||||
|             :class="{ 'c-disclosure-triangle--expanded': expanded }" | ||||
|             @click="expanded = !expanded" | ||||
|         ></span> | ||||
|         <div class="c-cs__header-label c-section__label">Conditions</div> | ||||
|     </div> | ||||
|     <div v-if="expanded" | ||||
|          class="c-cs__content" | ||||
|     > | ||||
|         <div v-show="isEditing" | ||||
|              class="hint" | ||||
|              :class="{ 's-status-icon-warning-lo': !telemetryObjs.length }" | ||||
|         > | ||||
|             <template v-if="!telemetryObjs.length">Drag telemetry into this Condition Set to configure Conditions and add criteria.</template> | ||||
|             <template v-else>The first condition to match is the one that is applied. Drag conditions to reorder.</template> | ||||
|         </div> | ||||
|  | ||||
|         <button | ||||
|             v-show="isEditing" | ||||
|             id="addCondition" | ||||
|             class="c-button c-button--major icon-plus labeled" | ||||
|             @click="addCondition" | ||||
|         > | ||||
|             <span class="c-cs-button__label">Add Condition</span> | ||||
|         </button> | ||||
|  | ||||
|         <div class="c-cs__conditions-h"> | ||||
|             <div v-for="(condition, index) in conditionCollection" | ||||
|                  :key="condition.id" | ||||
|                  class="c-condition-h" | ||||
|             > | ||||
|                 <div v-if="isEditing" | ||||
|                      class="c-c__drag-ghost" | ||||
|                      @drop.prevent="dropCondition" | ||||
|                      @dragenter="dragEnter" | ||||
|                      @dragleave="dragLeave" | ||||
|                      @dragover.prevent | ||||
|                 ></div> | ||||
|                 <Condition :condition="condition" | ||||
|                            :condition-index="index" | ||||
|                            :telemetry="telemetryObjs" | ||||
|                            :is-editing="isEditing" | ||||
|                            @updateCondition="updateCondition" | ||||
|                            @removeCondition="removeCondition" | ||||
|                            @cloneCondition="cloneCondition" | ||||
|                            @setMoveIndex="setMoveIndex" | ||||
|                 /> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
| </section> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import Condition from './Condition.vue'; | ||||
| import ConditionManager from '../ConditionManager'; | ||||
|  | ||||
| export default { | ||||
|     inject: ['openmct', 'domainObject'], | ||||
|     components: { | ||||
|         Condition | ||||
|     }, | ||||
|     props: { | ||||
|         isEditing: Boolean | ||||
|     }, | ||||
|     data() { | ||||
|         return { | ||||
|             expanded: true, | ||||
|             conditionCollection: [], | ||||
|             conditionResults: {}, | ||||
|             conditions: [], | ||||
|             telemetryObjs: [], | ||||
|             moveIndex: Number, | ||||
|             isDragging: false | ||||
|         }; | ||||
|     }, | ||||
|     destroyed() { | ||||
|         this.composition.off('add', this.addTelemetryObject); | ||||
|         this.composition.off('remove', this.removeTelemetryObject); | ||||
|         if(this.conditionManager) { | ||||
|             this.conditionManager.destroy(); | ||||
|         } | ||||
|         if (this.stopObservingForChanges) { | ||||
|             this.stopObservingForChanges(); | ||||
|         } | ||||
|     }, | ||||
|     mounted() { | ||||
|         this.composition = this.openmct.composition.get(this.domainObject); | ||||
|         this.composition.on('add', this.addTelemetryObject); | ||||
|         this.composition.on('remove', this.removeTelemetryObject); | ||||
|         this.composition.load(); | ||||
|         this.conditionCollection = this.domainObject.configuration.conditionCollection; | ||||
|         this.observeForChanges(); | ||||
|         this.conditionManager = new ConditionManager(this.domainObject, this.openmct); | ||||
|         this.conditionManager.on('conditionSetResultUpdated', (data) => { | ||||
|             this.$emit('conditionSetResultUpdated', data); | ||||
|         }) | ||||
|     }, | ||||
|     methods: { | ||||
|         observeForChanges() { | ||||
|             this.stopObservingForChanges = this.openmct.objects.observe(this.domainObject, 'configuration.conditionCollection', (newConditionCollection) => { | ||||
|                 this.conditionCollection = newConditionCollection; | ||||
|             }); | ||||
|         }, | ||||
|         setMoveIndex(index) { | ||||
|             this.moveIndex = index; | ||||
|             this.isDragging = true; | ||||
|         }, | ||||
|         dropCondition(e) { | ||||
|             let targetIndex = Array.from(document.querySelectorAll('.c-c__drag-ghost')).indexOf(e.target); | ||||
|             if (targetIndex > this.moveIndex) { targetIndex-- } // for 'downward' move | ||||
|             const oldIndexArr = Object.keys(this.conditionCollection); | ||||
|             const move = function (arr, old_index, new_index) { | ||||
|                 while (old_index < 0) { | ||||
|                     old_index += arr.length; | ||||
|                 } | ||||
|                 while (new_index < 0) { | ||||
|                     new_index += arr.length; | ||||
|                 } | ||||
|                 if (new_index >= arr.length) { | ||||
|                     var k = new_index - arr.length; | ||||
|                     while ((k--) + 1) { | ||||
|                         arr.push(undefined); | ||||
|                     } | ||||
|                 } | ||||
|                 arr.splice(new_index, 0, arr.splice(old_index, 1)[0]); | ||||
|                 return arr; | ||||
|             } | ||||
|             const newIndexArr = move(oldIndexArr, this.moveIndex, targetIndex); | ||||
|             const reorderPlan = []; | ||||
|  | ||||
|             for (let i = 0; i < oldIndexArr.length; i++) { | ||||
|                 reorderPlan.push({oldIndex: Number(newIndexArr[i]), newIndex: i}); | ||||
|             } | ||||
|  | ||||
|             this.reorder(reorderPlan); | ||||
|  | ||||
|             e.target.classList.remove("dragging"); | ||||
|             this.isDragging = false; | ||||
|         }, | ||||
|         dragEnter(e) { | ||||
|             if (!this.isDragging) { return } | ||||
|             let targetIndex = Array.from(document.querySelectorAll('.c-c__drag-ghost')).indexOf(e.target); | ||||
|             if (targetIndex > this.moveIndex) { targetIndex-- } // for 'downward' move | ||||
|             if (this.moveIndex === targetIndex) { return } | ||||
|             e.target.classList.add("dragging"); | ||||
|         }, | ||||
|         dragLeave(e) { | ||||
|             e.target.classList.remove("dragging"); | ||||
|         }, | ||||
|         addTelemetryObject(domainObject) { | ||||
|             this.telemetryObjs.push(domainObject); | ||||
|         }, | ||||
|         removeTelemetryObject(identifier) { | ||||
|             let index = _.findIndex(this.telemetryObjs, (obj) => { | ||||
|                 let objId = this.openmct.objects.makeKeyString(obj.identifier); | ||||
|                 let id = this.openmct.objects.makeKeyString(identifier); | ||||
|                 return objId === id; | ||||
|             }); | ||||
|             if (index > -1) { | ||||
|                 this.telemetryObjs.splice(index, 1); | ||||
|             } | ||||
|         }, | ||||
|         addCondition() { | ||||
|             this.conditionManager.addCondition(); | ||||
|         }, | ||||
|         updateCondition(data) { | ||||
|             this.conditionManager.updateCondition(data.condition, data.index); | ||||
|         }, | ||||
|         removeCondition(index) { | ||||
|             this.conditionManager.removeCondition(index); | ||||
|         }, | ||||
|         reorder(reorderPlan) { | ||||
|             this.conditionManager.reorderConditions(reorderPlan); | ||||
|         }, | ||||
|         cloneCondition(data) { | ||||
|             this.conditionManager.cloneCondition(data.condition, data.index); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										75
									
								
								src/plugins/condition/components/ConditionSet.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,75 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2020, 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-cs"> | ||||
|     <section class="c-cs__current-output c-section"> | ||||
|         <div class="c-cs__header c-section__header"> | ||||
|             <span class="c-cs__header-label c-section__label">Current Output</span> | ||||
|         </div> | ||||
|         <div class="c-cs__content c-cs__current-output-value"> | ||||
|             <template v-if="currentConditionOutput"> | ||||
|                 {{ currentConditionOutput }} | ||||
|             </template> | ||||
|             <template v-else>No output selected</template> | ||||
|         </div> | ||||
|     </section> | ||||
|     <TestData :is-editing="isEditing" /> | ||||
|     <ConditionCollection :is-editing="isEditing" | ||||
|                          @conditionSetResultUpdated="updateCurrentOutput" /> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import TestData from './TestData.vue'; | ||||
| import ConditionCollection from './ConditionCollection.vue'; | ||||
|  | ||||
| export default { | ||||
|     inject: ["openmct", "domainObject"], | ||||
|     components: { | ||||
|         TestData, | ||||
|         ConditionCollection | ||||
|     }, | ||||
|     props: { | ||||
|         isEditing: Boolean | ||||
|     }, | ||||
|     data() { | ||||
|         return { | ||||
|             currentConditionOutput: '' | ||||
|         } | ||||
|     }, | ||||
|     mounted() { | ||||
|         this.conditionSetIdentifier = this.openmct.objects.makeKeyString(this.domainObject.identifier); | ||||
|     }, | ||||
|     beforeDestroy() { | ||||
|         if (this.stopProvidingTelemetry) { | ||||
|             this.stopProvidingTelemetry(); | ||||
|         } | ||||
|     }, | ||||
|     methods: { | ||||
|         updateCurrentOutput(currentConditionResult) { | ||||
|             this.currentConditionOutput = currentConditionResult.output; | ||||
|         } | ||||
|     } | ||||
| }; | ||||
| </script> | ||||
|  | ||||
							
								
								
									
										196
									
								
								src/plugins/condition/components/Criterion.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,196 @@ | ||||
| <template> | ||||
| <div class="u-contents"> | ||||
|     <div class="c-cdef__separator c-row-separator"></div> | ||||
|     <span class="c-cdef__label">{{ setRowLabel }}</span> | ||||
|     <span class="c-cdef__controls"> | ||||
|         <span class="c-cdef__control"> | ||||
|             <select v-model="selectedTelemetry" | ||||
|                     @change="updateMetadataOptions" | ||||
|             > | ||||
|                 <option value="">- Select Telemetry -</option> | ||||
|                 <option v-for="telemetryOption in telemetry" | ||||
|                         :key="telemetryOption.identifier.key" | ||||
|                         :value="openmct.objects.makeKeyString(telemetryOption.identifier)" | ||||
|                 > | ||||
|                     {{ telemetryOption.name }} | ||||
|                 </option> | ||||
|             </select> | ||||
|         </span> | ||||
|         <span v-if="criterion.telemetry" | ||||
|               class="c-cdef__control" | ||||
|         > | ||||
|             <select v-model="criterion.metadata" | ||||
|                     @change="updateOperations" | ||||
|             > | ||||
|                 <option value="">- Select Field -</option> | ||||
|                 <option v-for="option in telemetryMetadataOptions" | ||||
|                         :key="option.key" | ||||
|                         :value="option.key" | ||||
|                 > | ||||
|                     {{ option.name }} | ||||
|                 </option> | ||||
|             </select> | ||||
|         </span> | ||||
|         <span v-if="criterion.telemetry && criterion.metadata" | ||||
|               class="c-cdef__control" | ||||
|         > | ||||
|             <select v-model="criterion.operation" | ||||
|                     @change="updateOperationInputVisibility" | ||||
|             > | ||||
|                 <option value="">- Select Comparison -</option> | ||||
|                 <option v-for="option in filteredOps" | ||||
|                         :key="option.name" | ||||
|                         :value="option.name" | ||||
|                 > | ||||
|                     {{ option.text }} | ||||
|                 </option> | ||||
|             </select> | ||||
|             <span v-for="(item, inputIndex) in inputCount" | ||||
|                   :key="inputIndex" | ||||
|                   class="c-cdef__control__inputs" | ||||
|             > | ||||
|                 <input v-model="criterion.input[inputIndex]" | ||||
|                        class="c-cdef__control__input" | ||||
|                        :type="setInputType" | ||||
|                        @blur="persist" | ||||
|                 > | ||||
|                 <span v-if="inputIndex < inputCount-1">and</span> | ||||
|             </span> | ||||
|         </span> | ||||
|     </span> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import { OPERATIONS } from '../utils/operations'; | ||||
|  | ||||
| export default { | ||||
|     inject: ['openmct'], | ||||
|     props: { | ||||
|         criterion: { | ||||
|             type: Object, | ||||
|             required: true | ||||
|         }, | ||||
|         telemetry: { | ||||
|             type: Array, | ||||
|             required: true, | ||||
|             default: () => [] | ||||
|         }, | ||||
|         index: { | ||||
|             type: Number, | ||||
|             required: true | ||||
|         }, | ||||
|         trigger: { | ||||
|             type: String, | ||||
|             required: true | ||||
|         } | ||||
|     }, | ||||
|     data() { | ||||
|         let selectedTelemetry = ''; | ||||
|         if (this.criterion.telemetry) { | ||||
|             selectedTelemetry = this.openmct.objects.makeKeyString(this.criterion.telemetry) | ||||
|         } | ||||
|         return { | ||||
|             telemetryMetadata: {}, | ||||
|             telemetryMetadataOptions: {}, | ||||
|             operations: OPERATIONS, | ||||
|             inputCount: 0, | ||||
|             rowLabel: '', | ||||
|             operationFormat: '', | ||||
|             selectedTelemetry | ||||
|         } | ||||
|     }, | ||||
|     computed: { | ||||
|         setRowLabel: function () { | ||||
|             let operator = this.trigger === 'all' ? 'and ': 'or '; | ||||
|             return (this.index !== 0 ? operator : '') + 'when'; | ||||
|         }, | ||||
|         filteredOps: function () { | ||||
|             return [...this.operations.filter(op => op.appliesTo.indexOf(this.operationFormat) !== -1)]; | ||||
|         }, | ||||
|         setInputType: function () { | ||||
|             let type = ''; | ||||
|             for (let i = 0; i < this.filteredOps.length; i++) { | ||||
|                 if (this.criterion.operation === this.filteredOps[i].name) { | ||||
|                     if (this.filteredOps[i].appliesTo.length === 1) { | ||||
|                         type = this.filteredOps[i].appliesTo[0]; | ||||
|                     } else { | ||||
|                         type = 'string' | ||||
|                     } | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|             return type; | ||||
|         } | ||||
|     }, | ||||
|     mounted() { | ||||
|         this.updateMetadataOptions(); | ||||
|     }, | ||||
|     methods: { | ||||
|         getOperationFormat() { | ||||
|             this.telemetryMetadata.valueMetadatas.forEach((value, index) => { | ||||
|                 if (value.key === this.criterion.metadata) { | ||||
|                     let valueMetadata = this.telemetryMetadataOptions[index]; | ||||
|                     if (valueMetadata.enumerations !== undefined) { | ||||
|                         this.operationFormat = 'enum'; | ||||
|                     } else if (valueMetadata.hints.hasOwnProperty('range')) { | ||||
|                         this.operationFormat = 'number'; | ||||
|                     } else if (valueMetadata.hints.hasOwnProperty('domain')) { | ||||
|                         this.operationFormat = 'number'; | ||||
|                     } else if (valueMetadata.key === 'name') { | ||||
|                         this.operationFormat = 'string'; | ||||
|                     } else { | ||||
|                         this.operationFormat = 'string'; | ||||
|                     } | ||||
|                 } | ||||
|             }); | ||||
|         }, | ||||
|         updateMetadataOptions(ev) { | ||||
|             if (ev) {this.clearInputs()} | ||||
|             if (this.selectedTelemetry) { | ||||
|                 this.openmct.objects.get(this.selectedTelemetry).then((telemetryObject) => { | ||||
|                     this.criterion.telemetry.name = telemetryObject.name; | ||||
|                     this.telemetryMetadata = this.openmct.telemetry.getMetadata(telemetryObject); | ||||
|                     this.telemetryMetadataOptions = this.telemetryMetadata.values(); | ||||
|                     this.updateOperations(); | ||||
|                     this.updateOperationInputVisibility(); | ||||
|                 }); | ||||
|             } else { | ||||
|                 this.criterion.metadata = ''; | ||||
|             } | ||||
|         }, | ||||
|         updateOperations(ev) { | ||||
|             if (ev) { | ||||
|                 this.criterion.telemetry.fieldName = ev.target.options[ev.target.selectedIndex].text; | ||||
|                 this.clearInputs() | ||||
|             } | ||||
|             this.getOperationFormat(); | ||||
|             this.persist(); | ||||
|         }, | ||||
|         updateOperationInputVisibility(ev) { | ||||
|             if (ev) { | ||||
|                 this.criterion.input = []; | ||||
|                 this.inputCount = 0; | ||||
|             } | ||||
|             for (let i = 0; i < this.filteredOps.length; i++) { | ||||
|                 if (this.criterion.operation === this.filteredOps[i].name) { | ||||
|                     this.inputCount = this.filteredOps[i].inputCount; | ||||
|                     if (!this.inputCount) {this.criterion.input = []} | ||||
|                 } | ||||
|             } | ||||
|             this.persist(); | ||||
|         }, | ||||
|         clearInputs() { | ||||
|             this.criterion.operation = ''; | ||||
|             this.criterion.input = []; | ||||
|             this.inputCount = 0; | ||||
|         }, | ||||
|         updateMetadataSelection() { | ||||
|             this.updateOperationInputVisibility(); | ||||
|         }, | ||||
|         persist() { | ||||
|             this.$emit('persist', this.criterion); | ||||
|         } | ||||
|     } | ||||
| }; | ||||
| </script> | ||||
							
								
								
									
										0
									
								
								src/plugins/condition/components/CurrentOutput.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										92
									
								
								src/plugins/condition/components/TestData.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,92 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2020, 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> | ||||
| <section v-show="isEditing" | ||||
|          id="test-data" | ||||
|          class="c-cs__test-data" | ||||
|          :class="{ 'is-expanded': expanded }" | ||||
| > | ||||
|     <div class="c-cs__header c-section__header"> | ||||
|         <span | ||||
|             class="c-disclosure-triangle c-tree__item__view-control is-enabled" | ||||
|             :class="{ 'c-disclosure-triangle--expanded': expanded }" | ||||
|             @click="expanded = !expanded" | ||||
|         ></span> | ||||
|         <div class="c-cs__header-label c-section__label">Test Data</div> | ||||
|     </div> | ||||
|     <div v-if="expanded" | ||||
|          class="c-cs__content" | ||||
|     > | ||||
|         <label class="c-toggle-switch"> | ||||
|             <input | ||||
|                 type="checkbox" | ||||
|                 :checked="isApplied" | ||||
|                 @change="applyTestData" | ||||
|             > | ||||
|             <span class="c-toggle-switch__slider"></span> | ||||
|             <span class="c-toggle-switch__label">Apply Test Data</span> | ||||
|         </label> | ||||
|         <div class="c-cs-test-h"> | ||||
|             <div v-for="n in 5" | ||||
|                  :key="n" | ||||
|                  class="c-test-datum" | ||||
|             > | ||||
|                 <span class="c-test-datum__label">Set</span> | ||||
|                 <div class="c-test-datum__controls"> | ||||
|                     <select> | ||||
|                         <option>- Select Input -</option> | ||||
|                     </select> | ||||
|                 </div> | ||||
|                 <div class="c-test-datum__buttons"> | ||||
|                     <button class="c-click-icon c-test-data__duplicate-button icon-duplicate" | ||||
|                             title="Duplicate this test data value" | ||||
|                     ></button> | ||||
|                     <button class="c-click-icon c-test-data__delete-button icon-trash" | ||||
|                             title="Delete this test data value" | ||||
|                     ></button> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
| </section> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| export default { | ||||
|     inject: ['openmct'], | ||||
|     props: { | ||||
|         isEditing: Boolean | ||||
|     }, | ||||
|     data() { | ||||
|         return { | ||||
|             expanded: true, | ||||
|             isApplied: true | ||||
|         }; | ||||
|     }, | ||||
|     methods: { | ||||
|         applyTestData(ev) { | ||||
|             this.$emit('change', ev.target.checked); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										89
									
								
								src/plugins/condition/components/condition-set.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,89 @@ | ||||
| .c-cs { | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     height: 100%; | ||||
|     overflow: hidden; | ||||
|  | ||||
|     &__content { | ||||
|         display: flex; | ||||
|         flex-direction: column; | ||||
|         flex: 1 1 auto; | ||||
|         overflow: hidden; | ||||
|  | ||||
|         > * { | ||||
|             flex: 0 0 auto; | ||||
|             overflow: hidden; | ||||
|             + * { | ||||
|                 margin-top: $interiorMarginSm; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         .c-button { | ||||
|             align-self: start; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     .is-editing & { | ||||
|         // Add some space to kick away from blue editing border indication | ||||
|         padding: $interiorMargin; | ||||
|     } | ||||
|  | ||||
|     section { | ||||
|         display: flex; | ||||
|         flex-direction: column; | ||||
|         overflow: hidden; | ||||
|     } | ||||
|  | ||||
|     &__conditions-h { | ||||
|         display: flex; | ||||
|         flex-direction: column; | ||||
|         flex: 1 1 auto; | ||||
|         overflow: auto; | ||||
|         padding-right: $interiorMarginSm; | ||||
|  | ||||
|         > * + * { | ||||
|             margin-top: $interiorMarginSm; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     &__conditions { | ||||
|         > * + * { | ||||
|             margin-top: $interiorMarginSm; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     .hint { | ||||
|         padding: $interiorMarginSm; | ||||
|     } | ||||
|  | ||||
|     /************************** SPECIFIC ITEMS */ | ||||
|     &__current-output-value { | ||||
|         font-size: 1.25em; | ||||
|         padding: $interiorMargin; | ||||
|     } | ||||
| } | ||||
|  | ||||
| /***************************** TEST DATA */ | ||||
| .c-cs-test-h { | ||||
|     flex: 1 1 auto; | ||||
|     overflow: auto; | ||||
|     padding-right: $interiorMarginSm; | ||||
|  | ||||
|     > * + * { | ||||
|         margin-top: $interiorMarginSm; | ||||
|     } | ||||
| } | ||||
|  | ||||
| .c-test-datum { | ||||
|     > * { | ||||
|         flex: 0 0 auto; | ||||
|         + * { | ||||
|             margin-left: $interiorMargin; | ||||
|         } | ||||
|     } | ||||
|     &__controls { | ||||
|         flex: 1 1 auto; | ||||
|     } | ||||
|  | ||||
| } | ||||
|  | ||||
							
								
								
									
										111
									
								
								src/plugins/condition/components/condition.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,111 @@ | ||||
| .c-condition, | ||||
| .c-test-datum { | ||||
|     @include discreteItem(); | ||||
|     display: flex; | ||||
|     padding: $interiorMargin; | ||||
|  | ||||
|     &--edit { | ||||
|         line-height: 160%; // For layout when inputs wrap, like in criteria | ||||
|     } | ||||
| } | ||||
|  | ||||
| .c-condition { | ||||
|     flex-direction: column; | ||||
|  | ||||
|     > * + * { | ||||
|         margin-top: $interiorMarginSm; | ||||
|     } | ||||
|     &--browse { | ||||
|         .c-condition__summary { | ||||
|             border-top: 1px solid $colorInteriorBorder; | ||||
|             padding-top: $interiorMargin; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /***************************** HEADER */ | ||||
|     &__header { | ||||
|         display: flex; | ||||
|         align-items: flex-start; | ||||
|         align-content: stretch; | ||||
|         overflow: hidden; | ||||
|  | ||||
|         > * { | ||||
|             flex: 0 0 auto; | ||||
|             + * { | ||||
|                 margin-left: $interiorMarginSm; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     &__name { | ||||
|         font-weight: bold; | ||||
|         align-self: baseline; // Fixes bold line-height offset problem | ||||
|     } | ||||
|  | ||||
|     &__output, | ||||
|     &__summary { | ||||
|         flex: 1 1 auto; | ||||
|     } | ||||
| } | ||||
|  | ||||
| /***************************** CONDITION DEFINITION, EDITING */ | ||||
| .c-cdef { | ||||
|     display: grid; | ||||
|     grid-row-gap: $interiorMarginSm; | ||||
|     grid-column-gap: $interiorMargin; | ||||
|     grid-auto-columns: min-content 1fr max-content; | ||||
|     align-items: start; | ||||
|     min-width: 150px; | ||||
|     margin-left: 29px; | ||||
|     overflow: hidden; | ||||
|  | ||||
|     &__criteria, | ||||
|     &__match-and-criteria { | ||||
|         display: contents; | ||||
|     } | ||||
|  | ||||
|     &__label { | ||||
|         grid-column: 1; | ||||
|         text-align: right; | ||||
|         white-space: nowrap; | ||||
|     } | ||||
|  | ||||
|     &__separator { | ||||
|         grid-column: 1 / span 3; | ||||
|     } | ||||
|  | ||||
|     &__controls { | ||||
|         display: flex; | ||||
|         flex-wrap: wrap; | ||||
|         align-items: flex-start; | ||||
|         //line-height: 200%; | ||||
|         grid-column: 2; | ||||
|  | ||||
|         > * > * { | ||||
|             margin-right: $interiorMarginSm; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     &__buttons { | ||||
|         grid-column: 3; | ||||
|     } | ||||
| } | ||||
|  | ||||
| .c-c__drag-ghost { | ||||
|     width: 100%; | ||||
|     min-height: $interiorMarginSm; | ||||
|  | ||||
|     //&:before { | ||||
|     //    @include test(); | ||||
|     //    content: ''; | ||||
|     //    display: block; | ||||
|     //    z-index: 2; | ||||
|     //} | ||||
|  | ||||
|     &.dragging { | ||||
|         min-height: 5em; | ||||
|         //border: solid 1px blue; | ||||
|         background-color: lightblue; | ||||
|         border-radius: 2px; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,69 @@ | ||||
| <template> | ||||
| <div> | ||||
|     <div v-if="conditionStyle" | ||||
|          class="holder c-c-button-wrapper align-left" | ||||
|     > | ||||
|         <div>{{ conditionStyle.conditionName }}</div> | ||||
|         <span :style="conditionStyle.style">ABC</span> | ||||
|         <toolbar-color-picker v-if="conditionStyle.style.border" | ||||
|                               :options="borderColorOption" | ||||
|                               @change="updateStyleValue" | ||||
|         /> | ||||
|         <toolbar-color-picker v-if="conditionStyle.style.backgroundColor" | ||||
|                               :options="backgroundColorOption" | ||||
|                               @change="updateStyleValue" | ||||
|         /> | ||||
|     </div> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
|  | ||||
| import ToolbarColorPicker from "@/ui/toolbar/components/toolbar-color-picker.vue"; | ||||
| export default { | ||||
|     components: { | ||||
|         ToolbarColorPicker | ||||
|     }, | ||||
|     inject: [ | ||||
|         'openmct' | ||||
|     ], | ||||
|     props: { | ||||
|         conditionStyle: { | ||||
|             type: Object, | ||||
|             required: true | ||||
|         } | ||||
|     }, | ||||
|     data() { | ||||
|         return { | ||||
|             condition: null | ||||
|         } | ||||
|     }, | ||||
|     computed: { | ||||
|         borderColorOption() { | ||||
|             return { | ||||
|                 icon: 'icon-line-horz', | ||||
|                 title: 'Set border color', | ||||
|                 value: this.conditionStyle.style.border.replace('1px solid ', ''), | ||||
|                 property: 'border' | ||||
|             } | ||||
|         }, | ||||
|         backgroundColorOption() { | ||||
|             return { | ||||
|                 icon: 'icon-paint-bucket', | ||||
|                 title: 'Set background color', | ||||
|                 value: this.conditionStyle.style.backgroundColor, | ||||
|                 property: 'backgroundColor' | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
|     methods: { | ||||
|         updateStyleValue(value, item) { | ||||
|             if (item.property === 'border') { | ||||
|                 value = '1px solid ' + value; | ||||
|             } | ||||
|             this.conditionStyle.style[item.property] = value; | ||||
|             this.$emit('persist', this.conditionStyle) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| </script> | ||||
| @@ -0,0 +1,165 @@ | ||||
| <template> | ||||
| <div> | ||||
|     <div v-if="!conditionalStyles.length" | ||||
|          class="holder c-c-button-wrapper align-left" | ||||
|     > | ||||
|         <button | ||||
|             class="c-c-button c-c-button--minor add-criteria-button" | ||||
|             @click="addConditionSet" | ||||
|         > | ||||
|             <span class="c-c-button__label">Use conditional styling</span> | ||||
|         </button> | ||||
|     </div> | ||||
|     <div v-else> | ||||
|         <div class="holder c-c-button-wrapper align-left"> | ||||
|             <button | ||||
|                 class="c-c-button c-c-button--minor add-criteria-button" | ||||
|                 @click="removeConditionSet" | ||||
|             > | ||||
|                 <span class="c-c-button__label">Remove conditional styling</span> | ||||
|             </button> | ||||
|         </div> | ||||
|         <ul> | ||||
|             <li v-for="conditionStyle in conditionalStyles" | ||||
|                 :key="conditionStyle.conditionId" | ||||
|             > | ||||
|                 <conditional-style :condition-style="conditionStyle" | ||||
|                                    @persist="updateConditionalStyle" | ||||
|                 /> | ||||
|             </li> | ||||
|         </ul> | ||||
|     </div> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
|  | ||||
| import ConditionalStyle from "./ConditionalStyle.vue"; | ||||
| export default { | ||||
|     components: { | ||||
|         ConditionalStyle | ||||
|     }, | ||||
|     inject: [ | ||||
|         'openmct', | ||||
|         'domainObject' | ||||
|     ], | ||||
|     props: { | ||||
|         itemId: { | ||||
|             type: String, | ||||
|             default: '' | ||||
|         }, | ||||
|         initialStyles: { | ||||
|             type: Object, | ||||
|             default() { | ||||
|                 return undefined; | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
|     data() { | ||||
|         return { | ||||
|             conditionalStyles: [] | ||||
|         } | ||||
|     }, | ||||
|     mounted() { | ||||
|         if (this.domainObject.configuration && this.domainObject.configuration.conditionalStyle) { | ||||
|             if (this.itemId) { | ||||
|                 let conditionalStyle = this.domainObject.configuration.conditionalStyle[this.itemId]; | ||||
|                 if (conditionalStyle) { | ||||
|                     this.conditionalStyles = conditionalStyle.styles || []; | ||||
|                 } | ||||
|             } else { | ||||
|                 this.conditionalStyles = this.domainObject.configuration.conditionalStyle.styles || []; | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
|     methods: { | ||||
|         addConditionSet() { | ||||
|             //TODO: this.conditionSetIdentifier will be set by the UI before calling this | ||||
|             this.conditionSetIdentifier = { | ||||
|                 namespace: '', | ||||
|                 key: "81088c8a-4b80-41fe-9d07-fda8b22d6f5f" | ||||
|             }; | ||||
|             this.initializeConditionalStyles(); | ||||
|         }, | ||||
|         removeConditionSet() { | ||||
|             //TODO: Handle the case where domainObject has items with styles but we're trying to remove the styles on the domainObject itself | ||||
|             this.conditionSetIdentifier = ''; | ||||
|             this.conditionalStyles = []; | ||||
|             let domainObjectConditionalStyle =  (this.domainObject.configuration && this.domainObject.configuration.conditionalStyle) || {}; | ||||
|             if (domainObjectConditionalStyle) { | ||||
|                 if (this.itemId) { | ||||
|                     domainObjectConditionalStyle[this.itemId] = undefined; | ||||
|                     delete domainObjectConditionalStyle[this.itemId]; | ||||
|                 } else { | ||||
|                     domainObjectConditionalStyle.conditionSetIdentifier = undefined; | ||||
|                     delete domainObjectConditionalStyle.conditionSetIdentifier; | ||||
|                     domainObjectConditionalStyle.styles = undefined; | ||||
|                     delete domainObjectConditionalStyle.styles; | ||||
|                 } | ||||
|                 if (_.isEmpty(domainObjectConditionalStyle)) { | ||||
|                     domainObjectConditionalStyle = undefined; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             this.persist(domainObjectConditionalStyle); | ||||
|  | ||||
|         }, | ||||
|         initializeConditionalStyles() { | ||||
|             this.openmct.objects.get(this.conditionSetIdentifier).then((conditionSetDomainObject) => { | ||||
|                 conditionSetDomainObject.configuration.conditionCollection.forEach((conditionConfiguration, index) => { | ||||
|                     this.conditionalStyles.push({ | ||||
|                         conditionId: conditionConfiguration.id, | ||||
|                         conditionName: conditionConfiguration.name, | ||||
|                         style: Object.assign({}, this.initialStyles) | ||||
|                     }); | ||||
|                 }); | ||||
|                 let domainObjectConditionalStyle =  (this.domainObject.configuration && this.domainObject.configuration.conditionalStyle) || {}; | ||||
|                 let conditionalStyle = { | ||||
|                     conditionSetIdentifier: this.conditionSetIdentifier, | ||||
|                     styles: this.conditionalStyles | ||||
|                 }; | ||||
|                 if (this.itemId) { | ||||
|                     this.persist({ | ||||
|                         ...domainObjectConditionalStyle, | ||||
|                         [this.itemId]: conditionalStyle | ||||
|                     }); | ||||
|                 } else { | ||||
|                     this.persist({ | ||||
|                         ...domainObjectConditionalStyle, | ||||
|                         ...conditionalStyle | ||||
|                     }); | ||||
|                 } | ||||
|             }); | ||||
|         }, | ||||
|         findStyleByConditionId(id) { | ||||
|             return this.conditionalStyles.find(conditionalStyle => conditionalStyle.conditionId === id); | ||||
|         }, | ||||
|         updateConditionalStyle(conditionStyle) { | ||||
|             let found = this.findStyleByConditionId(conditionStyle.conditionId); | ||||
|             if (found) { | ||||
|                 found.style = conditionStyle.style; | ||||
|                 let domainObjectConditionalStyle =  this.domainObject.configuration.conditionalStyle || {}; | ||||
|  | ||||
|                 if (this.itemId) { | ||||
|                     let itemConditionalStyle = domainObjectConditionalStyle[this.itemId]; | ||||
|                     if (itemConditionalStyle) { | ||||
|                         this.persist({ | ||||
|                             ...domainObjectConditionalStyle, | ||||
|                             [this.itemId]: { | ||||
|                                 ...itemConditionalStyle, | ||||
|                                 styles: this.conditionalStyles | ||||
|                             } | ||||
|                         }); | ||||
|                     } | ||||
|                 } else { | ||||
|                     domainObjectConditionalStyle.styles = this.conditionalStyles; | ||||
|                     this.persist(domainObjectConditionalStyle); | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         persist(conditionalStyle) { | ||||
|             this.openmct.objects.mutate(this.domainObject, 'configuration.conditionalStyle', conditionalStyle); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										140
									
								
								src/plugins/condition/criterion/TelemetryCriterion.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,140 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2020, 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'; | ||||
| import {OPERATIONS} from '../utils/operations'; | ||||
|  | ||||
| export default class TelemetryCriterion extends EventEmitter { | ||||
|  | ||||
|     /** | ||||
|      * Subscribes/Unsubscribes to telemetry and emits the result | ||||
|      * of operations performed on the telemetry data returned and a given input value. | ||||
|      * @constructor | ||||
|      * @param telemetryDomainObjectDefinition {id: uuid, operation: enum, input: Array, metadata: string, key: {domainObject.identifier} } | ||||
|      * @param openmct | ||||
|      */ | ||||
|     constructor(telemetryDomainObjectDefinition, openmct) { | ||||
|         super(); | ||||
|  | ||||
|         this.openmct = openmct; | ||||
|         this.objectAPI = this.openmct.objects; | ||||
|         this.telemetryAPI = this.openmct.telemetry; | ||||
|         this.timeAPI = this.openmct.time; | ||||
|         this.id = telemetryDomainObjectDefinition.id; | ||||
|         this.telemetry = telemetryDomainObjectDefinition.telemetry; | ||||
|         this.operation = telemetryDomainObjectDefinition.operation; | ||||
|         this.input = telemetryDomainObjectDefinition.input; | ||||
|         this.metadata = telemetryDomainObjectDefinition.metadata; | ||||
|         this.subscription = null; | ||||
|         this.telemetryObjectIdAsString = null; | ||||
|         this.objectAPI.get(this.objectAPI.makeKeyString(this.telemetry)).then((obj) => this.initialize(obj)); | ||||
|     } | ||||
|  | ||||
|     initialize(obj) { | ||||
|         this.telemetryObject = obj; | ||||
|         this.telemetryObjectIdAsString = this.objectAPI.makeKeyString(this.telemetryObject.identifier); | ||||
|         this.emitEvent('criterionUpdated', this); | ||||
|     } | ||||
|  | ||||
|     handleSubscription(data) { | ||||
|         const datum = { | ||||
|             result: this.computeResult(data) | ||||
|         }; | ||||
|         if (data) { | ||||
|             // TODO check back to see if we should format times here | ||||
|             this.timeAPI.getAllTimeSystems().forEach(timeSystem => { | ||||
|                 datum[timeSystem.key] = data[timeSystem.key] | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         this.emitEvent('criterionResultUpdated', datum); | ||||
|     } | ||||
|  | ||||
|     findOperation(operation) { | ||||
|         for (let i=0, ii=OPERATIONS.length; i < ii; i++) { | ||||
|             if (operation === OPERATIONS[i].name) { | ||||
|                 return OPERATIONS[i].operation; | ||||
|             } | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     computeResult(data) { | ||||
|         let result = false; | ||||
|         if (data) { | ||||
|             let comparator = this.findOperation(this.operation); | ||||
|             let params = []; | ||||
|             params.push(data[this.metadata]); | ||||
|             if (this.input instanceof Array && this.input.length) { | ||||
|                 this.input.forEach(input => params.push(input)); | ||||
|             } | ||||
|             if (typeof comparator === 'function') { | ||||
|                 result = comparator(params); | ||||
|             } | ||||
|         } | ||||
|         return result; | ||||
|     } | ||||
|  | ||||
|     emitEvent(eventName, data) { | ||||
|         this.emit(eventName, { | ||||
|             id: this.id, | ||||
|             data: data | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     isValid() { | ||||
|         return this.telemetryObject && this.metadata && this.operation; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      *  Subscribes to the telemetry object and returns an unsubscribe function | ||||
|      *  If the telemetry is not valid, returns nothing | ||||
|      */ | ||||
|     subscribe() { | ||||
|         if (this.isValid()) { | ||||
|             this.unsubscribe(); | ||||
|             this.subscription = this.telemetryAPI.subscribe(this.telemetryObject, (datum) => { | ||||
|                 this.handleSubscription(datum); | ||||
|             }); | ||||
|         } else { | ||||
|             this.handleSubscription(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      *  Calls an unsubscribe function returned by subscribe() and deletes any initialized data | ||||
|      */ | ||||
|     unsubscribe() { | ||||
|         //unsubscribe from telemetry source | ||||
|         if (typeof this.subscription === 'function') { | ||||
|             this.subscription(); | ||||
|         } | ||||
|         delete this.subscription; | ||||
|     } | ||||
|  | ||||
|     destroy() { | ||||
|         this.unsubscribe(); | ||||
|         this.emitEvent('criterionRemoved'); | ||||
|         delete this.telemetryObjectIdAsString; | ||||
|         delete this.telemetryObject; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										123
									
								
								src/plugins/condition/criterion/TelemetryCriterionSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,123 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2020, 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 TelemetryCriterion from "./TelemetryCriterion"; | ||||
|  | ||||
| let openmct = {}, | ||||
|     mockListener, | ||||
|     mockListener2, | ||||
|     testCriterionDefinition, | ||||
|     testTelemetryObject, | ||||
|     telemetryCriterion; | ||||
|  | ||||
| describe("The telemetry criterion", function () { | ||||
|  | ||||
|     beforeEach (() => { | ||||
|         testTelemetryObject = { | ||||
|             identifier:{ namespace: "", key: "test-object"}, | ||||
|             type: "test-object", | ||||
|             name: "Test Object", | ||||
|             telemetry: { | ||||
|                 values: [{ | ||||
|                     key: "some-key", | ||||
|                     name: "Some attribute", | ||||
|                     hints: { | ||||
|                         domain: 1 | ||||
|                     } | ||||
|                 }, { | ||||
|                     key: "some-other-key", | ||||
|                     name: "Another attribute", | ||||
|                     hints: { | ||||
|                         range: 1 | ||||
|                     } | ||||
|                 }] | ||||
|             } | ||||
|         }; | ||||
|         openmct.objects = jasmine.createSpyObj('objects', ['get', 'makeKeyString']); | ||||
|         openmct.objects.get.and.returnValue(new Promise(function (resolve, reject) { | ||||
|             resolve(testTelemetryObject); | ||||
|         })); | ||||
|         openmct.objects.makeKeyString.and.returnValue(testTelemetryObject.identifier.key); | ||||
|         openmct.telemetry = jasmine.createSpyObj('telemetry', ['isTelemetryObject', "subscribe", "getMetadata"]); | ||||
|         openmct.telemetry.isTelemetryObject.and.returnValue(true); | ||||
|         openmct.telemetry.subscribe.and.returnValue(function () {}); | ||||
|         openmct.telemetry.getMetadata.and.returnValue(testTelemetryObject.telemetry.values); | ||||
|  | ||||
|         openmct.time = jasmine.createSpyObj('timeAPI', | ||||
|             ['timeSystem', 'bounds', 'getAllTimeSystems'] | ||||
|         ); | ||||
|         openmct.time.timeSystem.and.returnValue({key: 'system'}); | ||||
|         openmct.time.bounds.and.returnValue({start: 0, end: 1}); | ||||
|         openmct.time.getAllTimeSystems.and.returnValue([{key: 'system'}]); | ||||
|  | ||||
|         testCriterionDefinition = { | ||||
|             id: 'test-criterion-id', | ||||
|             telemetry: openmct.objects.makeKeyString(testTelemetryObject.identifier) | ||||
|         }; | ||||
|  | ||||
|         mockListener = jasmine.createSpy('listener'); | ||||
|         mockListener2 = jasmine.createSpy('updatedListener', (data) => { | ||||
|             console.log(data); | ||||
|         }); | ||||
|  | ||||
|         telemetryCriterion = new TelemetryCriterion( | ||||
|             testCriterionDefinition, | ||||
|             openmct | ||||
|         ); | ||||
|  | ||||
|         telemetryCriterion.on('criterionResultUpdated', mockListener); | ||||
|         telemetryCriterion.on('criterionUpdated', mockListener2); | ||||
|  | ||||
|     }); | ||||
|  | ||||
|     it("initializes with a telemetry objectId as string", function () { | ||||
|         telemetryCriterion.initialize(testTelemetryObject); | ||||
|         expect(telemetryCriterion.telemetryObjectIdAsString).toEqual(testTelemetryObject.identifier.key); | ||||
|         expect(mockListener2).toHaveBeenCalled(); | ||||
|     }); | ||||
|  | ||||
|     it("subscribes to telemetry providers", function () { | ||||
|         telemetryCriterion.subscribe(); | ||||
|         expect(telemetryCriterion.subscription).toBeDefined(); | ||||
|     }); | ||||
|  | ||||
|     it("emits update event on new data from telemetry providers", function () { | ||||
|         spyOn(telemetryCriterion, 'emitEvent').and.callThrough(); | ||||
|         telemetryCriterion.handleSubscription({ | ||||
|             key: 'some-key', | ||||
|             source: 'testSource', | ||||
|             testSource: 'Hello' | ||||
|         }); | ||||
|         expect(telemetryCriterion.emitEvent).toHaveBeenCalled(); | ||||
|         expect(mockListener).toHaveBeenCalled(); | ||||
|     }); | ||||
|  | ||||
|     it("un-subscribes from telemetry providers", function () { | ||||
|         telemetryCriterion.subscribe(); | ||||
|         expect(telemetryCriterion.subscription).toBeDefined(); | ||||
|         telemetryCriterion.destroy(); | ||||
|         expect(telemetryCriterion.subscription).toBeUndefined(); | ||||
|         expect(telemetryCriterion.telemetryObjectIdAsString).toBeUndefined(); | ||||
|         expect(telemetryCriterion.telemetryObject).toBeUndefined(); | ||||
|     }); | ||||
|  | ||||
| }); | ||||
							
								
								
									
										63
									
								
								src/plugins/condition/plugin.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,63 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2020, 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 ConditionSetViewProvider from './ConditionSetViewProvider.js'; | ||||
| import ConditionSetCompositionPolicy from "./ConditionSetCompositionPolicy"; | ||||
| import ConditionSetMetadataProvider from './ConditionSetMetadataProvider'; | ||||
| import ConditionSetTelemetryProvider from './ConditionSetTelemetryProvider'; | ||||
| import uuid from "uuid"; | ||||
|  | ||||
| export default function ConditionPlugin() { | ||||
|  | ||||
|     return function install(openmct) { | ||||
|  | ||||
|         openmct.types.addType('conditionSet', { | ||||
|             name: 'Condition Set', | ||||
|             key: 'conditionSet', | ||||
|             description: 'A set of one or more conditions based on user-specified criteria.', | ||||
|             creatable: true, | ||||
|             cssClass: 'icon-conditional',  // TODO: replace with class for new icon | ||||
|             initialize: function (domainObject) { | ||||
|                 domainObject.configuration = { | ||||
|                     conditionCollection: [{ | ||||
|                         isDefault: true, | ||||
|                         id: uuid(), | ||||
|                         configuration: { | ||||
|                             name: 'Default', | ||||
|                             output: 'false', | ||||
|                             trigger: 'all', | ||||
|                             criteria: [] | ||||
|                         }, | ||||
|                         summary: 'Default condition' | ||||
|                     }] | ||||
|                 }; | ||||
|                 domainObject.composition = []; | ||||
|                 domainObject.telemetry = {}; | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         openmct.composition.addPolicy(new ConditionSetCompositionPolicy(openmct).allow); | ||||
|         openmct.telemetry.addProvider(new ConditionSetMetadataProvider(openmct)); | ||||
|         openmct.telemetry.addProvider(new ConditionSetTelemetryProvider(openmct)); | ||||
|         openmct.objectViews.addProvider(new ConditionSetViewProvider(openmct)); | ||||
|  | ||||
|     } | ||||
| } | ||||
							
								
								
									
										94
									
								
								src/plugins/condition/pluginSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,94 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2020, 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 { createOpenMct } from "testTools"; | ||||
| import ConditionPlugin from "./plugin"; | ||||
|  | ||||
| let openmct = createOpenMct(); | ||||
| openmct.install(new ConditionPlugin()); | ||||
|  | ||||
| let conditionSetDefinition; | ||||
| let mockConditionSetDomainObject; | ||||
| let element; | ||||
| let child; | ||||
|  | ||||
| describe('the plugin', function () { | ||||
|  | ||||
|     beforeAll((done) => { | ||||
|  | ||||
|         conditionSetDefinition = openmct.types.get('conditionSet').definition; | ||||
|         const appHolder = document.createElement('div'); | ||||
|         appHolder.style.width = '640px'; | ||||
|         appHolder.style.height = '480px'; | ||||
|  | ||||
|         element = document.createElement('div'); | ||||
|         child = document.createElement('div'); | ||||
|         element.appendChild(child); | ||||
|  | ||||
|         mockConditionSetDomainObject = { | ||||
|             identifier: { | ||||
|                 key: 'testConditionSetKey', | ||||
|                 namespace: '' | ||||
|             }, | ||||
|             type: 'conditionSet' | ||||
|         }; | ||||
|  | ||||
|         conditionSetDefinition.initialize(mockConditionSetDomainObject); | ||||
|  | ||||
|         openmct.on('start', done); | ||||
|         openmct.start(appHolder); | ||||
|     }); | ||||
|  | ||||
|     let mockConditionSetObject = { | ||||
|         name: 'Condition Set', | ||||
|         key: 'conditionSet', | ||||
|         creatable: true | ||||
|     }; | ||||
|  | ||||
|     it('defines a conditionSet object type with the correct key', () => { | ||||
|         expect(conditionSetDefinition.key).toEqual(mockConditionSetObject.key); | ||||
|     }); | ||||
|  | ||||
|     describe('the conditionSet object', () => { | ||||
|  | ||||
|         it('is creatable', () => { | ||||
|             expect(conditionSetDefinition.creatable).toEqual(mockConditionSetObject.creatable); | ||||
|         }); | ||||
|  | ||||
|         it('initializes with an empty composition list', () => { | ||||
|             expect(mockConditionSetDomainObject.composition instanceof Array).toBeTrue(); | ||||
|             expect(mockConditionSetDomainObject.composition.length).toEqual(0); | ||||
|         }); | ||||
|  | ||||
|         it('provides a view', () => { | ||||
|             const testViewObject = { | ||||
|                 id:"test-object", | ||||
|                 type: "conditionSet" | ||||
|             }; | ||||
|  | ||||
|             const applicableViews = openmct.objectViews.get(testViewObject); | ||||
|             let conditionSetView = applicableViews.find((viewProvider) => viewProvider.key === 'conditionSet.view'); | ||||
|             expect(conditionSetView).toBeDefined(); | ||||
|         }); | ||||
|  | ||||
|     }); | ||||
| }); | ||||
							
								
								
									
										4
									
								
								src/plugins/condition/utils/constants.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,4 @@ | ||||
| export const TRIGGER = { | ||||
|     ANY: 'any', | ||||
|     ALL: 'all' | ||||
| }; | ||||
							
								
								
									
										16
									
								
								src/plugins/condition/utils/evaluator.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,16 @@ | ||||
| export const computeCondition = (resultMap, allMustBeTrue) => { | ||||
|     let result = false; | ||||
|     for (let key in resultMap) { | ||||
|         if (resultMap.hasOwnProperty(key)) { | ||||
|             result = resultMap[key]; | ||||
|             if (allMustBeTrue && !result) { | ||||
|                 //If we want all conditions to be true, then even one negative result should break. | ||||
|                 break; | ||||
|             } else if (!allMustBeTrue && result) { | ||||
|                 //If we want at least one condition to be true, then even one positive result should break. | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     return result; | ||||
| }; | ||||
							
								
								
									
										206
									
								
								src/plugins/condition/utils/operations.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,206 @@ | ||||
| export const OPERATIONS = [ | ||||
|     { | ||||
|         name: 'equalTo', | ||||
|         operation: function (input) { | ||||
|             return input[0] === input[1]; | ||||
|         }, | ||||
|         text: 'is equal to', | ||||
|         appliesTo: ['number'], | ||||
|         inputCount: 1, | ||||
|         getDescription: function (values) { | ||||
|             return ' is ' + values[0]; | ||||
|         } | ||||
|     }, | ||||
|     { | ||||
|         name: 'notEqualTo', | ||||
|         operation: function (input) { | ||||
|             return input[0] !== input[1]; | ||||
|         }, | ||||
|         text: 'is not equal to', | ||||
|         appliesTo: ['number'], | ||||
|         inputCount: 1, | ||||
|         getDescription: function (values) { | ||||
|             return ' is not ' + values[0]; | ||||
|         } | ||||
|     }, | ||||
|     { | ||||
|         name: 'greaterThan', | ||||
|         operation: function (input) { | ||||
|             return input[0] > input[1]; | ||||
|         }, | ||||
|         text: 'is greater than', | ||||
|         appliesTo: ['number'], | ||||
|         inputCount: 1, | ||||
|         getDescription: function (values) { | ||||
|             return ' > ' + values[0]; | ||||
|         } | ||||
|     }, | ||||
|     { | ||||
|         name: 'lessThan', | ||||
|         operation: function (input) { | ||||
|             return input[0] < input[1]; | ||||
|         }, | ||||
|         text: 'is less than', | ||||
|         appliesTo: ['number'], | ||||
|         inputCount: 1, | ||||
|         getDescription: function (values) { | ||||
|             return ' < ' + values[0]; | ||||
|         } | ||||
|     }, | ||||
|     { | ||||
|         name: 'greaterThanOrEq', | ||||
|         operation: function (input) { | ||||
|             return input[0] >= input[1]; | ||||
|         }, | ||||
|         text: 'is greater than or equal to', | ||||
|         appliesTo: ['number'], | ||||
|         inputCount: 1, | ||||
|         getDescription: function (values) { | ||||
|             return ' >= ' + values[0]; | ||||
|         } | ||||
|     }, | ||||
|     { | ||||
|         name: 'lessThanOrEq', | ||||
|         operation: function (input) { | ||||
|             return input[0] <= input[1]; | ||||
|         }, | ||||
|         text: 'is less than or equal to', | ||||
|         appliesTo: ['number'], | ||||
|         inputCount: 1, | ||||
|         getDescription: function (values) { | ||||
|             return ' <= ' + values[0]; | ||||
|         } | ||||
|     }, | ||||
|     { | ||||
|         name: 'between', | ||||
|         operation: function (input) { | ||||
|             return input[0] > input[1] && input[0] < input[2]; | ||||
|         }, | ||||
|         text: 'is between', | ||||
|         appliesTo: ['number'], | ||||
|         inputCount: 2, | ||||
|         getDescription: function (values) { | ||||
|             return ' is between ' + values[0] + ' and ' + values[1]; | ||||
|         } | ||||
|     }, | ||||
|     { | ||||
|         name: 'notBetween', | ||||
|         operation: function (input) { | ||||
|             return input[0] < input[1] || input[0] > input[2]; | ||||
|         }, | ||||
|         text: 'is not between', | ||||
|         appliesTo: ['number'], | ||||
|         inputCount: 2, | ||||
|         getDescription: function (values) { | ||||
|             return ' is not between ' + values[0] + ' and ' + values[1]; | ||||
|         } | ||||
|     }, | ||||
|     { | ||||
|         name: 'textContains', | ||||
|         operation: function (input) { | ||||
|             return input[0] && input[1] && input[0].includes(input[1]); | ||||
|         }, | ||||
|         text: 'text contains', | ||||
|         appliesTo: ['string'], | ||||
|         inputCount: 1, | ||||
|         getDescription: function (values) { | ||||
|             return ' contains ' + values[0]; | ||||
|         } | ||||
|     }, | ||||
|     { | ||||
|         name: 'textDoesNotContain', | ||||
|         operation: function (input) { | ||||
|             return input[0] && input[1] && !input[0].includes(input[1]); | ||||
|         }, | ||||
|         text: 'text does not contain', | ||||
|         appliesTo: ['string'], | ||||
|         inputCount: 1, | ||||
|         getDescription: function (values) { | ||||
|             return ' does not contain ' + values[0]; | ||||
|         } | ||||
|     }, | ||||
|     { | ||||
|         name: 'textStartsWith', | ||||
|         operation: function (input) { | ||||
|             return input[0].startsWith(input[1]); | ||||
|         }, | ||||
|         text: 'text starts with', | ||||
|         appliesTo: ['string'], | ||||
|         inputCount: 1, | ||||
|         getDescription: function (values) { | ||||
|             return ' starts with ' + values[0]; | ||||
|         } | ||||
|     }, | ||||
|     { | ||||
|         name: 'textEndsWith', | ||||
|         operation: function (input) { | ||||
|             return input[0].endsWith(input[1]); | ||||
|         }, | ||||
|         text: 'text ends with', | ||||
|         appliesTo: ['string'], | ||||
|         inputCount: 1, | ||||
|         getDescription: function (values) { | ||||
|             return ' ends with ' + values[0]; | ||||
|         } | ||||
|     }, | ||||
|     { | ||||
|         name: 'textIsExactly', | ||||
|         operation: function (input) { | ||||
|             return input[0] === input[1]; | ||||
|         }, | ||||
|         text: 'text is exactly', | ||||
|         appliesTo: ['string'], | ||||
|         inputCount: 1, | ||||
|         getDescription: function (values) { | ||||
|             return ' is exactly ' + values[0]; | ||||
|         } | ||||
|     }, | ||||
|     { | ||||
|         name: 'isUndefined', | ||||
|         operation: function (input) { | ||||
|             return typeof input[0] === 'undefined'; | ||||
|         }, | ||||
|         text: 'is undefined', | ||||
|         appliesTo: ['string', 'number', 'enum'], | ||||
|         inputCount: 0, | ||||
|         getDescription: function () { | ||||
|             return ' is undefined'; | ||||
|         } | ||||
|     }, | ||||
|     { | ||||
|         name: 'isDefined', | ||||
|         operation: function (input) { | ||||
|             return typeof input[0] !== 'undefined'; | ||||
|         }, | ||||
|         text: 'is defined', | ||||
|         appliesTo: ['string', 'number', 'enum'], | ||||
|         inputCount: 0, | ||||
|         getDescription: function () { | ||||
|             return ' is defined'; | ||||
|         } | ||||
|     }, | ||||
|     { | ||||
|         name: 'enumValueIs', | ||||
|         operation: function (input) { | ||||
|             return input[0] === input[1]; | ||||
|         }, | ||||
|         text: 'is', | ||||
|         appliesTo: ['enum'], | ||||
|         inputCount: 1, | ||||
|         getDescription: function (values) { | ||||
|             return ' is ' + values[0]; | ||||
|         } | ||||
|     }, | ||||
|     { | ||||
|         name: 'enumValueIsNot', | ||||
|         operation: function (input) { | ||||
|             return input[0] !== input[1]; | ||||
|         }, | ||||
|         text: 'is not', | ||||
|         appliesTo: ['enum'], | ||||
|         inputCount: 1, | ||||
|         getDescription: function (values) { | ||||
|             return ' is not ' + values[0]; | ||||
|         } | ||||
|     } | ||||
| ]; | ||||
							
								
								
									
										19
									
								
								src/plugins/condition/utils/styleUtils.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,19 @@ | ||||
| export const getStyleProp = (key, defaultValue) => { | ||||
|     let styleProp = undefined; | ||||
|     switch(key) { | ||||
|     case 'fill': styleProp = { | ||||
|         backgroundColor: defaultValue || 'none' | ||||
|     }; | ||||
|         break; | ||||
|     case 'stroke': styleProp = { | ||||
|         border: '1px solid ' + defaultValue || 'none' | ||||
|     }; | ||||
|         break; | ||||
|     case 'color': styleProp = { | ||||
|         color: defaultValue || 'inherit' | ||||
|     }; | ||||
|         break; | ||||
|     } | ||||
|  | ||||
|     return styleProp; | ||||
| }; | ||||
| @@ -36,6 +36,7 @@ | ||||
|  | ||||
| <script> | ||||
| import LayoutFrame from './LayoutFrame.vue' | ||||
| import conditionalStylesMixin from '../mixins/conditionalStyles-mixin'; | ||||
|  | ||||
| export default { | ||||
|     makeDefinition() { | ||||
| @@ -52,6 +53,7 @@ export default { | ||||
|     components: { | ||||
|         LayoutFrame | ||||
|     }, | ||||
|     mixins: [conditionalStylesMixin], | ||||
|     props: { | ||||
|         item: { | ||||
|             type: Object, | ||||
| @@ -71,10 +73,14 @@ export default { | ||||
|     }, | ||||
|     computed: { | ||||
|         style() { | ||||
|             return { | ||||
|                 backgroundColor: this.item.fill, | ||||
|                 border: '1px solid ' + this.item.stroke | ||||
|             }; | ||||
|             if (this.itemStyle) { | ||||
|                 return this.itemStyle; | ||||
|             } else { | ||||
|                 return { | ||||
|                     backgroundColor: this.item.fill, | ||||
|                     border: '1px solid ' + this.item.stroke | ||||
|                 }; | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
|     watch: { | ||||
|   | ||||
| @@ -36,6 +36,7 @@ | ||||
|  | ||||
| <script> | ||||
| import LayoutFrame from './LayoutFrame.vue' | ||||
| import conditionalStylesMixin from "../mixins/conditionalStyles-mixin"; | ||||
|  | ||||
| export default { | ||||
|     makeDefinition(openmct, gridSize, element) { | ||||
| @@ -52,6 +53,7 @@ export default { | ||||
|     components: { | ||||
|         LayoutFrame | ||||
|     }, | ||||
|     mixins: [conditionalStylesMixin], | ||||
|     props: { | ||||
|         item: { | ||||
|             type: Object, | ||||
| @@ -73,7 +75,7 @@ export default { | ||||
|         style() { | ||||
|             return { | ||||
|                 backgroundImage: 'url(' + this.item.url + ')', | ||||
|                 border: '1px solid ' + this.item.stroke | ||||
|                 border: this.itemStyle ? this.itemStyle.border : '1px solid ' + this.item.stroke | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
|   | ||||
| @@ -31,7 +31,7 @@ | ||||
|     > | ||||
|         <line | ||||
|             v-bind="linePosition" | ||||
|             :stroke="item.stroke" | ||||
|             :stroke="stroke" | ||||
|             stroke-width="2" | ||||
|         /> | ||||
|     </svg> | ||||
| @@ -60,6 +60,8 @@ | ||||
|  | ||||
| <script> | ||||
|  | ||||
| import conditionalStylesMixin from "../mixins/conditionalStyles-mixin"; | ||||
|  | ||||
| const START_HANDLE_QUADRANTS = { | ||||
|     1: 'c-frame-edit__handle--sw', | ||||
|     2: 'c-frame-edit__handle--se', | ||||
| @@ -85,6 +87,7 @@ export default { | ||||
|         }; | ||||
|     }, | ||||
|     inject: ['openmct'], | ||||
|     mixins: [conditionalStylesMixin], | ||||
|     props: { | ||||
|         item: { | ||||
|             type: Object, | ||||
| @@ -122,6 +125,13 @@ export default { | ||||
|             } | ||||
|             return {x, y, x2, y2}; | ||||
|         }, | ||||
|         stroke() { | ||||
|             if (this.itemStyle && this.itemStyle.border) { | ||||
|                 return this.itemStyle.border.replace('1px solid ', ''); | ||||
|             } else { | ||||
|                 return this.item.stroke; | ||||
|             } | ||||
|         }, | ||||
|         style() { | ||||
|             let {x, y, x2, y2} = this.position; | ||||
|             let width = Math.max(this.gridSize[0] * Math.abs(x - x2), 1); | ||||
|   | ||||
| @@ -36,6 +36,7 @@ | ||||
|         <div | ||||
|             v-if="showLabel" | ||||
|             class="c-telemetry-view__label" | ||||
|             :style="conditionalStyle" | ||||
|         > | ||||
|             <div class="c-telemetry-view__label-text"> | ||||
|                 {{ domainObject.name }} | ||||
| @@ -47,6 +48,7 @@ | ||||
|             :title="fieldName" | ||||
|             class="c-telemetry-view__value" | ||||
|             :class="[telemetryClass]" | ||||
|             :style="!telemetryClass && conditionalStyle" | ||||
|         > | ||||
|             <div class="c-telemetry-view__value-text"> | ||||
|                 {{ telemetryValue }} | ||||
| @@ -59,6 +61,7 @@ | ||||
| <script> | ||||
| import LayoutFrame from './LayoutFrame.vue' | ||||
| import printj from 'printj' | ||||
| import StyleRuleManager from "../../condition/StyleRuleManager"; | ||||
|  | ||||
| const DEFAULT_TELEMETRY_DIMENSIONS = [10, 5], | ||||
|     DEFAULT_POSITION = [1, 1], | ||||
| @@ -109,7 +112,8 @@ export default { | ||||
|             datum: undefined, | ||||
|             formats: undefined, | ||||
|             domainObject: undefined, | ||||
|             currentObjectPath: undefined | ||||
|             currentObjectPath: undefined, | ||||
|             conditionalStyle: '' | ||||
|         } | ||||
|     }, | ||||
|     computed: { | ||||
| @@ -182,6 +186,16 @@ export default { | ||||
|             this.removeSelectable(); | ||||
|         } | ||||
|  | ||||
|         if (this.unlistenStyles) { | ||||
|             this.unlistenStyles(); | ||||
|         } | ||||
|  | ||||
|         if (this.styleRuleManager) { | ||||
|             this.styleRuleManager.destroy(); | ||||
|             this.styleRuleManager.off('conditionalStyleUpdated', this.updateStyle.bind(this)); | ||||
|             delete this.styleRuleManager; | ||||
|         } | ||||
|  | ||||
|         this.openmct.time.off("bounds", this.refreshData); | ||||
|     }, | ||||
|     methods: { | ||||
| @@ -224,6 +238,7 @@ export default { | ||||
|         }, | ||||
|         setObject(domainObject) { | ||||
|             this.domainObject = domainObject; | ||||
|             this.initConditionalStyles(); | ||||
|             this.keyString = this.openmct.objects.makeKeyString(domainObject.identifier); | ||||
|             this.metadata = this.openmct.telemetry.getMetadata(this.domainObject); | ||||
|             this.limitEvaluator = this.openmct.telemetry.limitEvaluator(this.domainObject); | ||||
| @@ -248,6 +263,21 @@ export default { | ||||
|         }, | ||||
|         showContextMenu(event) { | ||||
|             this.openmct.contextMenu._showContextMenuForObjectPath(this.currentObjectPath, event.x, event.y, CONTEXT_MENU_ACTIONS); | ||||
|         }, | ||||
|         initConditionalStyles() { | ||||
|             this.styleRuleManager = new StyleRuleManager(this.domainObject.configuration.conditionalStyle, this.openmct); | ||||
|             this.styleRuleManager.on('conditionalStyleUpdated', this.updateStyle.bind(this)); | ||||
|  | ||||
|             if (this.unlistenStyles) { | ||||
|                 this.unlistenStyles(); | ||||
|             } | ||||
|             this.unlistenStyles = this.openmct.objects.observe(this.domainObject, 'configuration.conditionalStyle', (newConditionalStyle) => { | ||||
|                 //Updating conditional styles in the inspector view will trigger this so that the changes are reflected immediately | ||||
|                 this.styleRuleManager.updateConditionalStyleConfig(newConditionalStyle); | ||||
|             }); | ||||
|         }, | ||||
|         updateStyle(styleObj) { | ||||
|             this.conditionalStyle = styleObj; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -38,6 +38,7 @@ | ||||
|  | ||||
| <script> | ||||
| import LayoutFrame from './LayoutFrame.vue' | ||||
| import conditionalStylesMixin from "../mixins/conditionalStyles-mixin"; | ||||
|  | ||||
| export default { | ||||
|     makeDefinition(openmct, gridSize, element) { | ||||
| @@ -57,6 +58,7 @@ export default { | ||||
|     components: { | ||||
|         LayoutFrame | ||||
|     }, | ||||
|     mixins: [conditionalStylesMixin], | ||||
|     props: { | ||||
|         item: { | ||||
|             type: Object, | ||||
| @@ -76,12 +78,16 @@ export default { | ||||
|     }, | ||||
|     computed: { | ||||
|         style() { | ||||
|             return { | ||||
|                 backgroundColor: this.item.fill, | ||||
|                 borderColor: this.item.stroke, | ||||
|                 color: this.item.color, | ||||
|                 fontSize: this.item.size | ||||
|             }; | ||||
|             if (this.itemStyle) { | ||||
|                 return this.itemStyle; | ||||
|             } else { | ||||
|                 return { | ||||
|                     backgroundColor: this.item.fill, | ||||
|                     borderColor: this.item.stroke, | ||||
|                     color: this.item.color, | ||||
|                     fontSize: this.item.size | ||||
|                 }; | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
|     watch: { | ||||
|   | ||||
							
								
								
									
										54
									
								
								src/plugins/displayLayout/mixins/conditionalStyles-mixin.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,54 @@ | ||||
| import StyleRuleManager from "@/plugins/condition/StyleRuleManager"; | ||||
|  | ||||
| export default { | ||||
|     inject: ['openmct'], | ||||
|     data() { | ||||
|         return { | ||||
|             itemStyle: this.itemStyle | ||||
|         } | ||||
|     }, | ||||
|     mounted() { | ||||
|         this.domainObject = this.$parent.domainObject; | ||||
|         this.itemId = this.item.id; | ||||
|         this.conditionalStyle = this.getConditionalStyleForItem(this.domainObject.configuration.conditionalStyle); | ||||
|         this.initConditionalStyles(); | ||||
|     }, | ||||
|     destroyed() { | ||||
|         if (this.stopListeningConditionalStyles) { | ||||
|             this.stopListeningConditionalStyles(); | ||||
|         } | ||||
|     }, | ||||
|     methods: { | ||||
|         getConditionalStyleForItem(conditionalStyle) { | ||||
|             if (conditionalStyle) { | ||||
|                 return conditionalStyle[this.itemId]; | ||||
|             } else { | ||||
|                 return undefined; | ||||
|             } | ||||
|         }, | ||||
|         initConditionalStyles() { | ||||
|             if (!this.styleRuleManager) { | ||||
|                 this.styleRuleManager = new StyleRuleManager(this.conditionalStyle, this.openmct); | ||||
|                 this.styleRuleManager.on('conditionalStyleUpdated', this.updateStyle.bind(this)); | ||||
|             } else { | ||||
|                 this.styleRuleManager.updateConditionalStyleConfig(this.conditionalStyle); | ||||
|             } | ||||
|  | ||||
|             if (this.stopListeningConditionalStyles) { | ||||
|                 this.stopListeningConditionalStyles(); | ||||
|             } | ||||
|  | ||||
|             this.stopListeningConditionalStyles = this.openmct.objects.observe(this.domainObject, 'configuration.conditionalStyle', (newConditionalStyle) => { | ||||
|                 //Updating conditional styles in the inspector view will trigger this so that the changes are reflected immediately | ||||
|                 let newItemConditionalStyle = this.getConditionalStyleForItem(newConditionalStyle); | ||||
|                 if (this.conditionalStyle !== newItemConditionalStyle) { | ||||
|                     this.conditionalStyle = newItemConditionalStyle; | ||||
|                     this.styleRuleManager.updateConditionalStyleConfig(this.conditionalStyle); | ||||
|                 } | ||||
|             }); | ||||
|         }, | ||||
|         updateStyle(style) { | ||||
|             this.itemStyle = style; | ||||
|         } | ||||
|     } | ||||
| }; | ||||
| @@ -232,18 +232,16 @@ export default { | ||||
|             this.newFrameLocation = [containerIndex, insertFrameIndex]; | ||||
|         }, | ||||
|         addFrame(domainObject) { | ||||
|             if (this.newFrameLocation.length) { | ||||
|                 let containerIndex = this.newFrameLocation[0], | ||||
|                     frameIndex = this.newFrameLocation[1], | ||||
|                     frame = new Frame(domainObject.identifier), | ||||
|                     container = this.containers[containerIndex]; | ||||
|             let containerIndex = this.newFrameLocation.length ? this.newFrameLocation[0] : 0; | ||||
|             let container = this.containers[containerIndex]; | ||||
|             let frameIndex = this.newFrameLocation.length ? this.newFrameLocation[1] : container.frames.length; | ||||
|             let frame = new Frame(domainObject.identifier); | ||||
|  | ||||
|                 container.frames.splice(frameIndex + 1, 0, frame); | ||||
|                 sizeItems(container.frames, frame); | ||||
|             container.frames.splice(frameIndex + 1, 0, frame); | ||||
|             sizeItems(container.frames, frame); | ||||
|  | ||||
|                 this.newFrameLocation = []; | ||||
|                 this.persist(containerIndex); | ||||
|             } | ||||
|             this.newFrameLocation = []; | ||||
|             this.persist(containerIndex); | ||||
|         }, | ||||
|         deleteFrame(frameId) { | ||||
|             let container = this.containers | ||||
|   | ||||
							
								
								
									
										47
									
								
								src/plugins/imagery/ImageryViewProvider.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,47 @@ | ||||
| import ImageryViewLayout from './components/ImageryViewLayout.vue'; | ||||
| import Vue from 'vue'; | ||||
|  | ||||
| export default function ImageryViewProvider(openmct) { | ||||
|     const type = 'example.imagery'; | ||||
|  | ||||
|     const hasImageTelemetry = function (domainObject) { | ||||
|         const metadata = openmct.telemetry.getMetadata(domainObject); | ||||
|         if (!metadata) { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         return metadata.valuesForHints(['image']).length > 0; | ||||
|     }; | ||||
|  | ||||
|     return { | ||||
|         key: type, | ||||
|         name: 'Imagery Layout', | ||||
|         cssClass: 'icon-image', | ||||
|         canView: function (domainObject) { | ||||
|             return hasImageTelemetry(domainObject); | ||||
|         }, | ||||
|         view: function (domainObject) { | ||||
|             let component; | ||||
|  | ||||
|             return { | ||||
|                 show: function (element) { | ||||
|                     component = new Vue({ | ||||
|                         components: { | ||||
|                             ImageryViewLayout | ||||
|                         }, | ||||
|                         provide: { | ||||
|                             openmct, | ||||
|                             domainObject | ||||
|                         }, | ||||
|                         el: element, | ||||
|                         template: '<imagery-view-layout ref="ImageryLayout"></imagery-view-layout>' | ||||
|                     }); | ||||
|                 }, | ||||
|                 destroy: function () { | ||||
|                     component.$destroy(); | ||||
|                     component = undefined; | ||||
|                 } | ||||
|             }; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										238
									
								
								src/plugins/imagery/components/ImageryViewLayout.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,238 @@ | ||||
| <template> | ||||
| <div class="c-imagery"> | ||||
|     <div class="c-imagery__main-image-wrapper has-local-controls"> | ||||
|         <div class="h-local-controls h-local-controls--overlay-content c-local-controls--show-on-hover l-flex-row c-imagery__lc"> | ||||
|             <span class="holder flex-elem grows c-imagery__lc__sliders"> | ||||
|                 <input v-model="filters.brightness" | ||||
|                        class="icon-brightness" | ||||
|                        type="range" | ||||
|                        min="0" | ||||
|                        max="500" | ||||
|                 > | ||||
|                 <input v-model="filters.contrast" | ||||
|                        class="icon-contrast" | ||||
|                        type="range" | ||||
|                        min="0" | ||||
|                        max="500" | ||||
|                 > | ||||
|             </span> | ||||
|             <span class="holder flex-elem t-reset-btn-holder c-imagery__lc__reset-btn"> | ||||
|                 <a class="s-icon-button icon-reset t-btn-reset" | ||||
|                    @click="filters={brightness: 100, contrast: 100}" | ||||
|                 ></a> | ||||
|             </span> | ||||
|         </div> | ||||
|         <div class="main-image s-image-main c-imagery__main-image" | ||||
|              :class="{'paused unnsynced': paused(),'stale':false }" | ||||
|              :style="{'background-image': `url(${getImageUrl()})`, | ||||
|                       'filter': `brightness(${filters.brightness}%) contrast(${filters.contrast}%)`}" | ||||
|         > | ||||
|         </div> | ||||
|         <div class="c-imagery__control-bar"> | ||||
|             <div class="c-imagery__timestamp">{{ getTime() }}</div> | ||||
|             <div class="h-local-controls flex-elem"> | ||||
|                 <a class="c-button icon-pause pause-play" | ||||
|                    :class="{'is-paused': paused()}" | ||||
|                    @click="paused(!paused())" | ||||
|                 ></a> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
|     <div ref="thumbsWrapper" | ||||
|          class="c-imagery__thumbs-wrapper" | ||||
|          :class="{'is-paused': paused()}" | ||||
|          @scroll="handleScroll" | ||||
|     > | ||||
|         <div v-for="(imageData, index) in imageHistory" | ||||
|              :key="index" | ||||
|              class="c-imagery__thumb c-thumb" | ||||
|              :class="{selected: imageData.selected}" | ||||
|              @click="setSelectedImage(imageData)" | ||||
|         > | ||||
|             <img class="c-thumb__image" | ||||
|                  :src="getImageUrl(imageData)" | ||||
|             > | ||||
|             <div class="c-thumb__timestamp">{{ getTime(imageData) }}</div> | ||||
|         </div> | ||||
|     </div> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import _ from 'lodash'; | ||||
|  | ||||
| export default { | ||||
|     inject: ['openmct', 'domainObject'], | ||||
|     data() { | ||||
|         return { | ||||
|             autoScroll: true, | ||||
|             date: '', | ||||
|             filters : { | ||||
|                 brightness: 100, | ||||
|                 contrast: 100 | ||||
|             }, | ||||
|             image: { | ||||
|                 selected: '' | ||||
|             }, | ||||
|             imageFormat: '', | ||||
|             imageHistory: [], | ||||
|             imageUrl: '', | ||||
|             isPaused: false, | ||||
|             requestCount: 0, | ||||
|             timeFormat: '' | ||||
|         } | ||||
|     }, | ||||
|     mounted() { | ||||
|         this.keystring = this.openmct.objects.makeKeyString(this.domainObject.identifier); | ||||
|         this.subscribe(this.domainObject); | ||||
|     }, | ||||
|     updated() { | ||||
|         this.scrollToRight(); | ||||
|     }, | ||||
|     beforeDestroy() { | ||||
|         this.stopListening(); | ||||
|     }, | ||||
|     methods: { | ||||
|         datumMatchesMostRecent(datum) { | ||||
|             if (this.imageHistory.length === 0) { | ||||
|                 return false; | ||||
|             } | ||||
|  | ||||
|             const datumTime = this.timeFormat.format(datum); | ||||
|             const datumURL = this.imageFormat.format(datum); | ||||
|             const lastHistoryTime = this.timeFormat.format(this.imageHistory.slice(-1)[0]); | ||||
|             const lastHistoryURL = this.imageFormat.format(this.imageHistory.slice(-1)[0]); | ||||
|  | ||||
|             return (datumTime === lastHistoryTime) && (datumURL === lastHistoryURL); | ||||
|         }, | ||||
|         getImageUrl(datum) { | ||||
|             return datum ? | ||||
|                 this.imageFormat.format(datum) : | ||||
|                 this.imageUrl; | ||||
|         }, | ||||
|         getTime(datum) { | ||||
|             return datum ? | ||||
|                 this.timeFormat.format(datum) : | ||||
|                 this.time; | ||||
|         }, | ||||
|         handleScroll() { | ||||
|             const thumbsWrapper = this.$refs.thumbsWrapper | ||||
|             if (!thumbsWrapper) { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             const { scrollLeft, scrollWidth, clientWidth, scrollTop, scrollHeight, clientHeight } = thumbsWrapper; | ||||
|             const disableScroll = (scrollWidth - scrollLeft) > 2 * clientWidth | ||||
|                     || (scrollHeight - scrollTop) > 2 * clientHeight; | ||||
|             this.autoScroll = !disableScroll; | ||||
|         }, | ||||
|         paused(state) { | ||||
|             if (arguments.length > 0 && state !== this.isPaused) { | ||||
|                 this.unselectAllImages(); | ||||
|                 this.isPaused = state; | ||||
|                 if (state === true) { | ||||
|                     // If we are pausing, select the latest image in imageHistory | ||||
|                     this.setSelectedImage(this.imageHistory[this.imageHistory.length - 1]); | ||||
|                 } | ||||
|  | ||||
|                 if (this.nextDatum) { | ||||
|                     this.updateValues(this.nextDatum); | ||||
|                     delete this.nextDatum; | ||||
|                 } else { | ||||
|                     this.updateValues(this.imageHistory[this.imageHistory.length - 1]); | ||||
|                 } | ||||
|  | ||||
|                 this.autoScroll = true; | ||||
|             } | ||||
|  | ||||
|             return this.isPaused; | ||||
|         }, | ||||
|         requestHistory(bounds) { | ||||
|             this.requestCount++; | ||||
|             this.imageHistory = []; | ||||
|             const requestId = this.requestCount; | ||||
|             this.openmct.telemetry | ||||
|                 .request(this.domainObject, bounds) | ||||
|                 .then((values = []) => { | ||||
|                     if (this.requestCount > requestId) { | ||||
|                         return Promise.resolve('Stale request'); | ||||
|                     } | ||||
|  | ||||
|                     values.forEach(this.updateHistory); | ||||
|                     this.updateValues(values[values.length - 1]); | ||||
|                 }); | ||||
|         }, | ||||
|         scrollToRight() { | ||||
|             if (this.isPaused || !this.$refs.thumbsWrapper || !this.autoScroll) { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             const scrollWidth = this.$refs.thumbsWrapper.scrollWidth || 0; | ||||
|             if (!scrollWidth) { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             setTimeout(() => this.$refs.thumbsWrapper.scrollLeft = scrollWidth, 0); | ||||
|         }, | ||||
|         setSelectedImage(image) { | ||||
|             // If we are paused and the current image IS selected, unpause | ||||
|             // Otherwise, set current image and pause | ||||
|             if (this.isPaused && image.selected) { | ||||
|                 this.paused(false); | ||||
|                 this.unselectAllImages(); | ||||
|             } else { | ||||
|                 this.imageUrl = this.getImageUrl(image); | ||||
|                 this.time = this.getTime(image); | ||||
|                 this.paused(true); | ||||
|                 this.unselectAllImages(); | ||||
|                 image.selected = true; | ||||
|             } | ||||
|         }, | ||||
|         stopListening() { | ||||
|             if (this.unsubscribe) { | ||||
|                 this.unsubscribe(); | ||||
|                 delete this.unsubscribe; | ||||
|             } | ||||
|         }, | ||||
|         subscribe(domainObject) { | ||||
|             this.date = '' | ||||
|             this.imageUrl = ''; | ||||
|             this.openmct.objects.get(this.keystring) | ||||
|                 .then((object) => { | ||||
|                     const metadata = this.openmct.telemetry.getMetadata(this.domainObject); | ||||
|                     this.timeKey = this.openmct.time.timeSystem().key; | ||||
|                     this.timeFormat = this.openmct.telemetry.getValueFormatter(metadata.value(this.timeKey)); | ||||
|                     this.imageFormat = this.openmct.telemetry.getValueFormatter(metadata.valuesForHints(['image'])[0]); | ||||
|                     this.unsubscribe = this.openmct.telemetry | ||||
|                         .subscribe(this.domainObject, (datum) => { | ||||
|                             this.updateHistory(datum); | ||||
|                             this.updateValues(datum); | ||||
|                         }); | ||||
|  | ||||
|                     this.requestHistory(this.openmct.time.bounds()); | ||||
|                 }); | ||||
|         }, | ||||
|         unselectAllImages() { | ||||
|             this.imageHistory.forEach(image => image.selected = false); | ||||
|         }, | ||||
|         updateHistory(datum) { | ||||
|             if (this.datumMatchesMostRecent(datum)) { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             const index = _.sortedIndex(this.imageHistory, datum, this.timeFormat.format.bind(this.timeFormat)); | ||||
|             this.imageHistory.splice(index, 0, datum); | ||||
|         }, | ||||
|         updateValues(datum) { | ||||
|             if (this.isPaused) { | ||||
|                 this.nextDatum = datum; | ||||
|  | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             this.time = this.timeFormat.format(datum); | ||||
|             this.imageUrl = this.imageFormat.format(datum); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										161
									
								
								src/plugins/imagery/components/imagery-view-layout.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,161 @@ | ||||
| .c-imagery { | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     overflow: hidden; | ||||
|     height: 100%; | ||||
|  | ||||
|     > * + * { | ||||
|         margin-top: $interiorMargin; | ||||
|     } | ||||
|  | ||||
|     &__main-image-wrapper { | ||||
|         display: flex; | ||||
|         flex-direction: column; | ||||
|         flex: 1 1 auto; | ||||
|     } | ||||
|  | ||||
|     &__main-image { | ||||
|         background-position: center; | ||||
|         background-repeat: no-repeat; | ||||
|         background-size: contain; | ||||
|         height: 100%; | ||||
|  | ||||
|         &.unnsynced{ | ||||
|             @include sUnsynced(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     &__control-bar { | ||||
|         padding: 5px 0 0 0; | ||||
|         display: flex; | ||||
|         align-items: center; | ||||
|     } | ||||
|  | ||||
|     &__timestamp { | ||||
|         flex: 1 1 auto; | ||||
|     } | ||||
|  | ||||
|     &__thumbs-wrapper { | ||||
|         flex: 0 0 auto; | ||||
|         display: flex; | ||||
|         flex-direction: row; | ||||
|         height: 135px; | ||||
|         overflow-x: auto; | ||||
|         overflow-y: hidden; | ||||
|  | ||||
|         &.is-paused { | ||||
|             background: rgba($colorPausedBg, 0.4); | ||||
|         } | ||||
|  | ||||
|         .c-thumb:last-child { | ||||
|             // Hilite the lastest thumb | ||||
|             background: $colorBodyFg; | ||||
|             color: $colorBodyBg; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| /*************************************** THUMBS */ | ||||
| .c-thumb { | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     padding: 4px; | ||||
|     width: $imageThumbsD; | ||||
|  | ||||
|     &:hover { | ||||
|         background: $colorThumbHoverBg; | ||||
|     } | ||||
|  | ||||
|     &.selected { | ||||
|         background: $colorPausedBg !important; | ||||
|         color: $colorPausedFg !important; | ||||
|     } | ||||
|  | ||||
|     &__image { | ||||
|         background-color: rgba($colorBodyFg, 0.2); | ||||
|         width: 100%; | ||||
|     } | ||||
|  | ||||
|     &__timestamp { | ||||
|         flex: 0 0 auto; | ||||
|         padding: 2px 3px; | ||||
|     } | ||||
| } | ||||
|  | ||||
| .l-layout, | ||||
| .c-fl { | ||||
|     .c-imagery__thumbs-wrapper { | ||||
|         //  When Imagery is in a layout, hide the thumbs area | ||||
|         display: none; | ||||
|     } | ||||
| } | ||||
|  | ||||
| .s-image-main { | ||||
|     background-color: $colorPlotBg; | ||||
|     border: 1px solid transparent; | ||||
| } | ||||
|  | ||||
| /*************************************** IMAGERY LOCAL CONTROLS*/ | ||||
| .c-imagery { | ||||
|     .h-local-controls--overlay-content { | ||||
|         position: absolute; | ||||
|         right: $interiorMargin; top: $interiorMargin; | ||||
|         z-index: 2; | ||||
|         background: $colorLocalControlOvrBg; | ||||
|         border-radius: $basicCr; | ||||
|         max-width: 200px; | ||||
|         min-width: 100px; | ||||
|         width: 35%; | ||||
|         align-items: center; | ||||
|         padding: $interiorMargin $interiorMarginLg; | ||||
|  | ||||
|         input[type="range"] { | ||||
|             display: block; | ||||
|             width: 100%; | ||||
|             &:not(:first-child) { | ||||
|                 margin-top: $interiorMarginLg; | ||||
|             } | ||||
|  | ||||
|             &:before { | ||||
|                 margin-right: $interiorMarginSm; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     &__lc { | ||||
|         &__reset-btn { | ||||
|             $bc: $scrollbarTrackColorBg; | ||||
|             &:before, | ||||
|             &:after { | ||||
|                 border-right: 1px solid $bc; | ||||
|                 content:''; | ||||
|                 display: block; | ||||
|                 width: 5px; | ||||
|                 height: 4px; | ||||
|             } | ||||
|  | ||||
|             &:before { | ||||
|                 border-top: 1px solid $bc; | ||||
|                 margin-bottom: 2px; | ||||
|             } | ||||
|  | ||||
|             &:after { | ||||
|                 border-bottom: 1px solid $bc; | ||||
|                 margin-top: 2px; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| /*************************************** BUTTONS */ | ||||
| .c-button.pause-play { | ||||
|     // Pause icon set by default in markup | ||||
|     &.is-paused { | ||||
|         background: $colorPausedBg !important; | ||||
|         color: $colorPausedFg; | ||||
|  | ||||
|         &:before { | ||||
|             content: $glyph-icon-play; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										8
									
								
								src/plugins/imagery/plugin.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,8 @@ | ||||
| import ImageryViewProvider from './ImageryViewProvider'; | ||||
|  | ||||
| export default function () { | ||||
|     return function install(openmct) { | ||||
|         openmct.objectViews.addProvider(new ImageryViewProvider(openmct)); | ||||
|     }; | ||||
| } | ||||
|  | ||||
							
								
								
									
										10
									
								
								src/plugins/plot/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,10 @@ | ||||
| # Plot Plugin | ||||
|  | ||||
| Enables plot visualization of telemetry data. This plugin adds a plot view that is available from the view switcher for  | ||||
| all telemetry objects. Two user createble objects are also added by this plugin, for Overlay and Stacked Plots.  | ||||
| Telemetry objects can be added to Overlay and Stacked Plots via drag and drop. | ||||
|  | ||||
| ## Installation | ||||
| ``` js | ||||
| openmct.install(openmct.plugins.Plot()); | ||||
| ``` | ||||
| @@ -225,7 +225,7 @@ | ||||
|                               left: (100 * (tick.value - min) / interval) + '%' | ||||
|                           }" | ||||
|                           ng-title=":: tick.fullText || tick.text"> | ||||
|                          {{:: tick.text | reverse}} | ||||
|                          {{:: tick.text }} | ||||
|                      </div> | ||||
|                 </mct-ticks> | ||||
|  | ||||
|   | ||||
| @@ -143,7 +143,7 @@ define([ | ||||
|             let strategy; | ||||
|  | ||||
|             if (this.model.interpolate !== 'none') { | ||||
|                 strategy = 'minMax'; | ||||
|                 strategy = 'minmax'; | ||||
|             } | ||||
|  | ||||
|             options = _.extend({}, { size: 1000, strategy, filters: this.filters }, options || {}); | ||||
| @@ -377,13 +377,17 @@ define([ | ||||
|          * @public | ||||
|          */ | ||||
|         updateFiltersAndRefresh: function (updatedFilters) { | ||||
|             this.filters = updatedFilters; | ||||
|             this.reset(); | ||||
|             if (this.unsubscribe) { | ||||
|                 this.unsubscribe(); | ||||
|                 delete this.unsubscribe; | ||||
|             if (this.filters && !_.isEqual(this.filters, updatedFilters)) { | ||||
|                 this.filters = updatedFilters; | ||||
|                 this.reset(); | ||||
|                 if (this.unsubscribe) { | ||||
|                     this.unsubscribe(); | ||||
|                     delete this.unsubscribe; | ||||
|                 } | ||||
|                 this.fetch(); | ||||
|             } else { | ||||
|                 this.filters = updatedFilters; | ||||
|             } | ||||
|             this.fetch(); | ||||
|         } | ||||
|     }); | ||||
|  | ||||
|   | ||||
| @@ -46,7 +46,7 @@ define([ | ||||
|             }, | ||||
|             { | ||||
|                 modelProp: 'range', | ||||
|                 objectPath: 'form.yAxis.range', | ||||
|                 objectPath: 'configuration.yAxis.range', | ||||
|                 coerce: function coerceRange(range) { | ||||
|                     if (!range) { | ||||
|                         return { | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2018, United States Government | ||||
|  * Open MCT, Copyright (c) 2014-2019, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
| @@ -28,6 +28,7 @@ define([ | ||||
|     './autoflow/AutoflowTabularPlugin', | ||||
|     './timeConductor/plugin', | ||||
|     '../../example/imagery/plugin', | ||||
|     './imagery/plugin', | ||||
|     '../../platform/import-export/bundle', | ||||
|     './summaryWidget/plugin', | ||||
|     './URLIndicatorPlugin/URLIndicatorPlugin', | ||||
| @@ -46,6 +47,7 @@ define([ | ||||
|     './goToOriginalAction/plugin', | ||||
|     './clearData/plugin', | ||||
|     './webPage/plugin', | ||||
|     './condition/plugin', | ||||
|     './themes/espresso', | ||||
|     './themes/maelstrom', | ||||
|     './themes/snow' | ||||
| @@ -57,6 +59,7 @@ define([ | ||||
|     AutoflowPlugin, | ||||
|     TimeConductorPlugin, | ||||
|     ExampleImagery, | ||||
|     ImageryPlugin, | ||||
|     ImportExport, | ||||
|     SummaryWidget, | ||||
|     URLIndicatorPlugin, | ||||
| @@ -75,6 +78,7 @@ define([ | ||||
|     GoToOriginalAction, | ||||
|     ClearData, | ||||
|     WebPagePlugin, | ||||
|     ConditionPlugin, | ||||
|     Espresso, | ||||
|     Maelstrom, | ||||
|     Snow | ||||
| @@ -162,6 +166,7 @@ define([ | ||||
|     }; | ||||
|  | ||||
|     plugins.ExampleImagery = ExampleImagery; | ||||
|     plugins.ImageryPlugin = ImageryPlugin; | ||||
|     plugins.Plot = PlotPlugin; | ||||
|     plugins.TelemetryTable = TelemetryTablePlugin; | ||||
|  | ||||
| @@ -182,6 +187,7 @@ define([ | ||||
|     plugins.Espresso = Espresso.default; | ||||
|     plugins.Maelstrom = Maelstrom.default; | ||||
|     plugins.Snow = Snow.default; | ||||
|     plugins.Condition = ConditionPlugin.default; | ||||
|  | ||||
|     return plugins; | ||||
| }); | ||||
|   | ||||
							
								
								
									
										19
									
								
								src/plugins/staticRootPlugin/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,19 @@ | ||||
| # Static Root Plugin | ||||
|  | ||||
| This plugin takes an object tree as JSON and exposes it as a non-editable root level tree. This can be useful if you  | ||||
| have static non-editable content that you wish to expose, such as a standard set of displays that should not be edited  | ||||
| (but which can be copied and then modified if desired). | ||||
|  | ||||
| Any object tree in Open MCT can be exported as JSON after installing the  | ||||
| [Import/Export plugin](../../../platform/import-export/README.md). | ||||
|  | ||||
| ## Installation | ||||
| ``` js | ||||
| openmct.install(openmct.plugins.StaticRootPlugin('mission', 'data/static-objects.json')); | ||||
| ``` | ||||
|  | ||||
| ## Parameters | ||||
| The StaticRootPlugin takes two parameters: | ||||
| 1. __namespace__: This should be a name that uniquely identifies this collection of objects. | ||||
| 2. __path__: The file that the static tree should be exposed from. This will need to be a path that is reachable by a web  | ||||
| browser, ie not a path on the local file system. | ||||
							
								
								
									
										10
									
								
								src/plugins/summaryWidget/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,10 @@ | ||||
| # Summary Widget Plugin | ||||
| Summary widgets can be used to provide visual indication of state based on telemetry data. They allow rules to be  | ||||
| defined that can then be used to change the appearance of the summary widget element based on data. For example, a  | ||||
| summary widget could be defined that is green when a temparature reading is between `0` and `100` centigrade, red when  | ||||
| it's above `100`, and orange when it's below `0`. | ||||
|  | ||||
| ## Installation | ||||
| ```js | ||||
| openmct.install(openmct.plugins.SummaryWidget()); | ||||
| ``` | ||||