Compare commits
	
		
			2 Commits
		
	
	
		
			api-tutori
			...
			omct-elect
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | a11ab24a0c | ||
|   | 9913a514b7 | 
| @@ -14,8 +14,7 @@ | ||||
|     "nonew": true, | ||||
|     "predef": [ | ||||
|         "define", | ||||
|         "Promise", | ||||
|         "WeakMap" | ||||
|         "Promise" | ||||
|     ], | ||||
|     "shadow": "outer", | ||||
|     "strict": "implied", | ||||
|   | ||||
							
								
								
									
										64
									
								
								API.md
									
									
									
									
									
								
							
							
						
						
									
										64
									
								
								API.md
									
									
									
									
									
								
							| @@ -129,10 +129,8 @@ provider. | ||||
|  | ||||
| The "composition" of a domain object is the list of objects it contains, | ||||
| as shown (for example) in the tree for browsing. Open MCT provides a | ||||
| [default solution](#default-composition-provider) for composition, but there | ||||
| may be cases where you want to provide the composition of a certain object | ||||
| (or type of object) dynamically. | ||||
|  | ||||
| default solution for composition, but there may be cases where you want | ||||
| to provide the composition of a certain object (or type of object) dynamically. | ||||
| For instance, you may want to populate a hierarchy under a custom root-level | ||||
| object based on the contents of a telemetry dictionary. | ||||
| To do this, you can add a new CompositionProvider: | ||||
| @@ -148,29 +146,6 @@ openmct.composition.addProvider({ | ||||
| }); | ||||
| ``` | ||||
|  | ||||
| #### Default Composition Provider | ||||
|  | ||||
| The default composition provider applies to any domain object with | ||||
| a `composition` property. The value of `composition` should be an | ||||
| array of identifiers, e.g.: | ||||
|  | ||||
| ```js | ||||
| var domainObject = { | ||||
|     name: "My Object", | ||||
|     type: 'folder', | ||||
|     composition: [ | ||||
|         { | ||||
|             key: '412229c3-922c-444b-8624-736d85516247', | ||||
|             namespace: 'foo' | ||||
|         }, | ||||
|         { | ||||
|             key: 'd6e0ce02-5b85-4e55-8006-a8a505b64c75', | ||||
|             namespace: 'foo' | ||||
|         } | ||||
|     ] | ||||
| }; | ||||
| ``` | ||||
|  | ||||
| ### Adding Telemetry Providers | ||||
|  | ||||
| When connecting to a new telemetry source, you will want to register a new | ||||
| @@ -290,41 +265,6 @@ openmct.install(myPlugin); | ||||
|  | ||||
| The plugin will be invoked to configure Open MCT before it is started. | ||||
|  | ||||
| ### Included Plugins | ||||
|  | ||||
| Open MCT is packaged along with a few general-purpose plugins: | ||||
|  | ||||
| * `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. | ||||
|   `openmct.install(new 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, e.g. | ||||
|   `openmct.install(new openmct.plugins.CouchDB('http://localhost:9200'))`. | ||||
|   Domain objects will be indexed at `/mct/domain_object`. | ||||
| * `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.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 support for using the time | ||||
|   conductor with UTC time. | ||||
|  | ||||
| Generally, you will want to either install these plugins, or install | ||||
| different plugins that provide persistence and an initial folder | ||||
| hierarchy. Installation is as described [above](#installing-plugins): | ||||
|  | ||||
| ``` | ||||
| openmct.install(openmct.plugins.localStorage); | ||||
| openmct.install(openmct.plugins.myItems); | ||||
| ``` | ||||
|  | ||||
| ### Writing Plugins | ||||
|  | ||||
| Plugins configure Open MCT, and should utilize the | ||||
|   | ||||
| @@ -1338,6 +1338,55 @@ are supported: | ||||
|  | ||||
| Open MCT defines several Angular directives that are intended for use both  | ||||
| internally within the platform, and by plugins.  | ||||
|  | ||||
| ## Before Unload  | ||||
|  | ||||
| The `mct-before-unload` directive is used to listen for (and prompt for user  | ||||
| confirmation) of navigation changes in the browser. This includes reloading,  | ||||
| following links out of Open MCT, or changing routes. It is used to hook into  | ||||
| both `onbeforeunload` event handling as well as route changes from within  | ||||
| Angular. | ||||
|  | ||||
| This directive is useable as an attribute. Its value should be an Angular  | ||||
| expression. When an action that would trigger an unload and/or route change  | ||||
| occurs, this Angular expression is evaluated. Its result should be a message to  | ||||
| display to the user to confirm their navigation change; if this expression  | ||||
| evaluates to a falsy value, no message will be displayed.  | ||||
|   | ||||
| ## Chart  | ||||
|  | ||||
| The `mct-chart` directive is used to support drawing of simple charts. It is  | ||||
| present to support the Plot view, and its functionality is limited to the  | ||||
| functionality that is relevant for that view. | ||||
|  | ||||
| This directive is used at the element level and takes one attribute, `draw`  | ||||
| which is an Angular expression which will should evaluate to a drawing object.  | ||||
| This drawing object should contain the following properties: | ||||
|  | ||||
| * `dimensions`: The size, in logical coordinates, of the chart area. A  | ||||
| two-element array or numbers.  | ||||
| * `origin`: The position, in logical coordinates, of the lower-left corner of  | ||||
| the chart area. A two-element array or numbers.  | ||||
| * `lines`: An array of lines (e.g. as a plot line) to draw, where each line is  | ||||
| expressed as an object containing:  | ||||
|     * `buffer`: A Float32Array containing points in the line, in logical  | ||||
|     coordinates, in sequential x,y pairs.  | ||||
|     * `color`: The color of the line, as a four-element RGBA array, where  | ||||
|     each element is a number in the range of 0.0-1.0.  | ||||
|     * `points`: The number of points in the line.  | ||||
| * `boxes`: An array of rectangles to draw in the chart area. Each is an object  | ||||
| containing:  | ||||
|     * `start`: The first corner of the rectangle, as a two-element array of  | ||||
|     numbers, in logical coordinates.  | ||||
|     * `end`: The opposite corner of the rectangle, as a two-element array of  | ||||
|     numbers, in logical coordinates. color : The color of the line, as a  | ||||
|     four-element RGBA array, where each element is a number in the range of  | ||||
|     0.0-1.0.  | ||||
|  | ||||
| While `mct-chart` is intended to support plots specifically, it does perform  | ||||
| some useful management of canvas objects (e.g. choosing between WebGL and Canvas  | ||||
| 2D APIs for drawing based on browser support) so its usage is recommended when  | ||||
| its supported drawing primitives are sufficient for other charting tasks.  | ||||
|   | ||||
| ## Container  | ||||
|  | ||||
|   | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										86
									
								
								electron/bundle.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								electron/bundle.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,86 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2016, 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/NewWindowAction", | ||||
|     "./src/ElectronInitializer", | ||||
|     'openmct' | ||||
| ], function ( | ||||
|     NewWindowAction, | ||||
|     ElectronInitializer, | ||||
|     openmct | ||||
| ) { | ||||
|     function SyncedNewWindowAction () { | ||||
|         this.synced = true; | ||||
|         NewWindowAction.apply(this, arguments) | ||||
|     } | ||||
|  | ||||
|     SyncedNewWindowAction.prototype = Object.create(NewWindowAction.prototype); | ||||
|  | ||||
|     openmct.legacyRegistry.register("electron", { | ||||
|         "extensions": { | ||||
|             "routes": [{ | ||||
|                 "when": "/window/:ids*", | ||||
|                 "templateUrl": "templates/browse-window.html", | ||||
|                 "reloadOnSearch": false | ||||
|             }], | ||||
|             "runs": [{ | ||||
|                 "implementation": ElectronInitializer, | ||||
|                 "depends": ["timeConductor", "$location"] | ||||
|             }], | ||||
|             "actions": [ | ||||
|                 { | ||||
|                     "key": "newWindowAction", | ||||
|                     "name": "Open in Synchronized Window", | ||||
|                     "implementation": SyncedNewWindowAction, | ||||
|                     "description": "Open in a new window which follows this time conductor", | ||||
|                     "depends": [ | ||||
|                         "urlService" | ||||
|                     ], | ||||
|                     "category": [ | ||||
|                         "view-control", | ||||
|                         "contextual" | ||||
|                     ], | ||||
|                     "group": "windowing", | ||||
|                     "cssclass": "icon-new-window", | ||||
|                     "priority": "preferred" | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "newWindowUnsynched", | ||||
|                     "name": "Open in Non-synchronized Window", | ||||
|                     "implementation": NewWindowAction, | ||||
|                     "description": "Open in a new window with its own time conductor", | ||||
|                     "depends": [ | ||||
|                         "urlService" | ||||
|                     ], | ||||
|                     "category": [ | ||||
|                         "view-control", | ||||
|                         "contextual" | ||||
|                     ], | ||||
|                     "group": "windowing", | ||||
|                     "cssclass": "icon-new-window", | ||||
|                     "priority": "preferred" | ||||
|                 } | ||||
|             ] | ||||
|         } | ||||
|     }); | ||||
| }); | ||||
							
								
								
									
										45
									
								
								electron/res/templates/browse-window.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								electron/res/templates/browse-window.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | ||||
| <!-- | ||||
|  Open MCT, Copyright (c) 2014-2016, 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. | ||||
| --> | ||||
| <style> | ||||
|     .primary-pane { | ||||
|         width: 100%; | ||||
|         height: 100%; | ||||
|     } | ||||
| </style> | ||||
| <div class="abs holder-all" ng-controller="BrowseController"> | ||||
|     <div class="abs holder holder-main browse-area s-browse-area browse-wrapper"> | ||||
|         <div class='abs contents'> | ||||
|             <div class='split-pane-component items pane primary-pane right'> | ||||
|                 <div class='holder holder-object-and-inspector abs' id='content-area' | ||||
|                      class="pane-inspect-hidden"> | ||||
|                     <div class='t-object pane primary-pane left'> | ||||
|                         <mct-representation mct-object="navigatedObject" | ||||
|                                             key="'view-object'" | ||||
|                                             class="abs holder holder-object"> | ||||
|                         </mct-representation> | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
| </div> | ||||
|  | ||||
							
								
								
									
										75
									
								
								electron/src/ElectronInitializer.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								electron/src/ElectronInitializer.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,75 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2016, 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. | ||||
|  *****************************************************************************/ | ||||
| var electron = nodeRequire('electron'); | ||||
| define( | ||||
|     [], | ||||
|     function () { | ||||
|         /** | ||||
|          * The new tab action allows a domain object to be opened | ||||
|          * into a new browser tab. | ||||
|          * @memberof platform/commonUI/browse | ||||
|          * @constructor | ||||
|          * @implements {Action} | ||||
|          */ | ||||
|         function ElectronInitializer(conductor, $location) { | ||||
|             var childWindow = $location.search().child === 'true'; | ||||
|  | ||||
|             // If child window, listen for conductor state updates from main thread. | ||||
|             if (childWindow) { | ||||
|                 electron.ipcRenderer.on('conductor-bounds', function (event, bounds) { | ||||
|                     var cBounds = conductor.bounds(); | ||||
|                     var synced = $location.search().synced === 'true'; | ||||
|  | ||||
|                     if (synced) { | ||||
|                         if (synced && | ||||
|                             (bounds.start !== cBounds.start || bounds.end !== cBounds.end)) { | ||||
|                             conductor.bounds(bounds); | ||||
|                         } | ||||
|                     } | ||||
|                 }); | ||||
|  | ||||
|                 electron.ipcRenderer.on('conductor-follow', function (event, follow) { | ||||
|                     var synced = $location.search().synced === 'true'; | ||||
|                     follow = !!follow; | ||||
|  | ||||
|                     if (synced && follow !== conductor.follow()) { | ||||
|                         conductor.follow(follow); | ||||
|                     } | ||||
|                 }); | ||||
|             } | ||||
|  | ||||
|             // If parent window, listen to bounds changes and propogate them back to the main thread. From there they | ||||
|             // will be disseminated to child windows. | ||||
|             if (!childWindow) { | ||||
|                 conductor.on('bounds', function (bounds) { | ||||
|                     electron.ipcRenderer.send('conductor-bounds', bounds); | ||||
|                 }); | ||||
|  | ||||
|                 conductor.on('follow', function (follow) { | ||||
|                     electron.ipcRenderer.send('conductor-follow', follow); | ||||
|                 }); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return ElectronInitializer; | ||||
|     } | ||||
| ); | ||||
							
								
								
									
										68
									
								
								electron/src/NewWindowAction.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								electron/src/NewWindowAction.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,68 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2016, 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. | ||||
|  *****************************************************************************/ | ||||
| var electron = nodeRequire('electron'); | ||||
| /** | ||||
|  * Module defining NewWindowAction (Originally NewWindowAction). Created by vwoeltje on 11/18/14. | ||||
|  */ | ||||
| define( | ||||
|     [], | ||||
|     function () { | ||||
|         /** | ||||
|          * The new tab action allows a domain object to be opened | ||||
|          * into a new browser tab. | ||||
|          * @memberof platform/commonUI/browse | ||||
|          * @constructor | ||||
|          * @implements {Action} | ||||
|          */ | ||||
|         function NewWindowAction(urlService, context) { | ||||
|             context = context || {}; | ||||
|             var windae = undefined; | ||||
|             this.urlService = urlService; | ||||
|             this.open = function (url) { | ||||
|                 windae = new electron.remote.BrowserWindow({width: 800, height: 600}); | ||||
|                 windae.on('close', function () { | ||||
|                     windae = null; | ||||
|                 }); | ||||
|                 var url = [ | ||||
|                         ['file:/', __dirname, url].join('/'), | ||||
|                         'synced=' + !!(this.synced), | ||||
|                         'child=' + true | ||||
|                     ].join('&'); | ||||
|  | ||||
|                 windae.loadURL(url); | ||||
|             }; | ||||
|             this.open.bind(this); | ||||
|  | ||||
|             // Choose the object to be opened into a new tab | ||||
|             this.domainObject = context.selectedObject || context.domainObject; | ||||
|         } | ||||
|  | ||||
|         NewWindowAction.prototype.perform = function () { | ||||
|             this.open( | ||||
|                 this.urlService.urlForNewTab("window", this.domainObject), | ||||
|                 "_blank" | ||||
|             ); | ||||
|         }; | ||||
|  | ||||
|         return NewWindowAction; | ||||
|     } | ||||
| ); | ||||
| @@ -49,7 +49,7 @@ define([ | ||||
|                 { | ||||
|                     "key": "eventGenerator", | ||||
|                     "name": "Event Message Generator", | ||||
|                     "cssClass": "icon-folder-new", | ||||
|                     "cssclass": "icon-folder-new", | ||||
|                     "description": "For development use. Creates sample event message data that mimics a live data stream.", | ||||
|                     "priority": 10, | ||||
|                     "features": "creation", | ||||
|   | ||||
| @@ -36,7 +36,7 @@ define([ | ||||
|                     "name": "Export Telemetry as CSV", | ||||
|                     "implementation": ExportTelemetryAsCSVAction, | ||||
|                     "category": "contextual", | ||||
|                     "cssClass": "icon-download", | ||||
|                     "cssclass": "icon-download", | ||||
|                     "depends": [ "exportService" ] | ||||
|                 } | ||||
|             ] | ||||
|   | ||||
							
								
								
									
										144
									
								
								example/generator/bundle.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								example/generator/bundle.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,144 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2016, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
| /*global define*/ | ||||
|  | ||||
| define([ | ||||
|     "./src/SinewaveTelemetryProvider", | ||||
|     "./src/SinewaveLimitCapability", | ||||
|     "./src/SinewaveDeltaFormat", | ||||
|     'legacyRegistry' | ||||
| ], function ( | ||||
|     SinewaveTelemetryProvider, | ||||
|     SinewaveLimitCapability, | ||||
|     SinewaveDeltaFormat, | ||||
|     legacyRegistry | ||||
| ) { | ||||
|     "use strict"; | ||||
|  | ||||
|     legacyRegistry.register("example/generator", { | ||||
|         "name": "Sine Wave Generator", | ||||
|         "description": "For development use. Generates example streaming telemetry data using a simple sine wave algorithm.", | ||||
|         "extensions": { | ||||
|             "components": [ | ||||
|                 { | ||||
|                     "implementation": SinewaveTelemetryProvider, | ||||
|                     "type": "provider", | ||||
|                     "provides": "telemetryService", | ||||
|                     "depends": [ | ||||
|                         "$q", | ||||
|                         "$timeout" | ||||
|                     ] | ||||
|                 } | ||||
|             ], | ||||
|             "capabilities": [ | ||||
|                 { | ||||
|                     "key": "limit", | ||||
|                     "implementation": SinewaveLimitCapability | ||||
|                 } | ||||
|             ], | ||||
|             "formats": [ | ||||
|                 { | ||||
|                     "key": "example.delta", | ||||
|                     "implementation": SinewaveDeltaFormat | ||||
|                 } | ||||
|             ], | ||||
|             "constants": [ | ||||
|                 { | ||||
|                     "key": "TIME_CONDUCTOR_DOMAINS", | ||||
|                     "value": [ | ||||
|                         { | ||||
|                             "key": "time", | ||||
|                             "name": "Time" | ||||
|                         }, | ||||
|                         { | ||||
|                             "key": "yesterday", | ||||
|                             "name": "Yesterday" | ||||
|                         }, | ||||
|                         { | ||||
|                             "key": "delta", | ||||
|                             "name": "Delta", | ||||
|                             "format": "example.delta" | ||||
|                         } | ||||
|                     ], | ||||
|                     "priority": -1 | ||||
|                 } | ||||
|             ], | ||||
|             "types": [ | ||||
|                 { | ||||
|                     "key": "generator", | ||||
|                     "name": "Sine Wave Generator", | ||||
|                     "cssclass": "icon-telemetry", | ||||
|                     "description": "For development use. Generates example streaming telemetry data using a simple sine wave algorithm.", | ||||
|                     "priority": 10, | ||||
|                     "features": "creation", | ||||
|                     "model": { | ||||
|                         "telemetry": { | ||||
|                             "period": 10 | ||||
|                         } | ||||
|                     }, | ||||
|                     "telemetry": { | ||||
|                         "source": "generator", | ||||
|                         "domains": [ | ||||
|                             { | ||||
|                                 "key": "time", | ||||
|                                 "name": "Time" | ||||
|                             }, | ||||
|                             { | ||||
|                                 "key": "yesterday", | ||||
|                                 "name": "Yesterday" | ||||
|                             }, | ||||
|                             { | ||||
|                                 "key": "delta", | ||||
|                                 "name": "Delta", | ||||
|                                 "format": "example.delta" | ||||
|                             } | ||||
|                         ], | ||||
|                         "ranges": [ | ||||
|                             { | ||||
|                                 "key": "sin", | ||||
|                                 "name": "Sine" | ||||
|                             }, | ||||
|                             { | ||||
|                                 "key": "cos", | ||||
|                                 "name": "Cosine" | ||||
|                             } | ||||
|                         ] | ||||
|                     }, | ||||
|                     "properties": [ | ||||
|                         { | ||||
|                             "name": "Period", | ||||
|                             "control": "textfield", | ||||
|                             "cssclass": "l-input-sm l-numeric", | ||||
|                             "key": "period", | ||||
|                             "required": true, | ||||
|                             "property": [ | ||||
|                                 "telemetry", | ||||
|                                 "period" | ||||
|                             ], | ||||
|                             "pattern": "^\\d*(\\.\\d*)?$" | ||||
|                         } | ||||
|                     ] | ||||
|                 } | ||||
|             ] | ||||
|         } | ||||
|     }); | ||||
| }); | ||||
							
								
								
									
										119
									
								
								example/generator/src/SinewaveTelemetryProvider.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								example/generator/src/SinewaveTelemetryProvider.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,119 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2016, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
| /*global define,Promise*/ | ||||
|  | ||||
| /** | ||||
|  * Module defining SinewaveTelemetryProvider. Created by vwoeltje on 11/12/14. | ||||
|  */ | ||||
| define( | ||||
|     ["./SinewaveTelemetrySeries"], | ||||
|     function (SinewaveTelemetrySeries) { | ||||
|         "use strict"; | ||||
|  | ||||
|         /** | ||||
|          * | ||||
|          * @constructor | ||||
|          */ | ||||
|         function SinewaveTelemetryProvider($q, $timeout) { | ||||
|             var subscriptions = [], | ||||
|                 generating = false; | ||||
|  | ||||
|             // | ||||
|             function matchesSource(request) { | ||||
|                 return request.source === "generator"; | ||||
|             } | ||||
|  | ||||
|             // Used internally; this will be repacked by doPackage | ||||
|             function generateData(request) { | ||||
|                 return { | ||||
|                     key: request.key, | ||||
|                     telemetry: new SinewaveTelemetrySeries(request) | ||||
|                 }; | ||||
|             } | ||||
|  | ||||
|             // | ||||
|             function doPackage(results) { | ||||
|                 var packaged = {}; | ||||
|                 results.forEach(function (result) { | ||||
|                     packaged[result.key] = result.telemetry; | ||||
|                 }); | ||||
|                 // Format as expected (sources -> keys -> telemetry) | ||||
|                 return { generator: packaged }; | ||||
|             } | ||||
|  | ||||
|             function requestTelemetry(requests) { | ||||
|                 return $timeout(function () { | ||||
|                     return doPackage(requests.filter(matchesSource).map(generateData)); | ||||
|                 }, 0); | ||||
|             } | ||||
|  | ||||
|             function handleSubscriptions() { | ||||
|                 subscriptions.forEach(function (subscription) { | ||||
|                     var requests = subscription.requests; | ||||
|                     subscription.callback(doPackage( | ||||
|                         requests.filter(matchesSource).map(generateData) | ||||
|                     )); | ||||
|                 }); | ||||
|             } | ||||
|  | ||||
|             function startGenerating() { | ||||
|                 generating = true; | ||||
|                 $timeout(function () { | ||||
|                     handleSubscriptions(); | ||||
|                     if (generating && subscriptions.length > 0) { | ||||
|                         startGenerating(); | ||||
|                     } else { | ||||
|                         generating = false; | ||||
|                     } | ||||
|                 }, 1000); | ||||
|             } | ||||
|  | ||||
|             function subscribe(callback, requests) { | ||||
|                 var subscription = { | ||||
|                     callback: callback, | ||||
|                     requests: requests | ||||
|                 }; | ||||
|  | ||||
|                 function unsubscribe() { | ||||
|                     subscriptions = subscriptions.filter(function (s) { | ||||
|                         return s !== subscription; | ||||
|                     }); | ||||
|                 } | ||||
|  | ||||
|                 subscriptions.push(subscription); | ||||
|  | ||||
|                 if (!generating) { | ||||
|                     startGenerating(); | ||||
|                 } | ||||
|  | ||||
|                 return unsubscribe; | ||||
|             } | ||||
|  | ||||
|             return { | ||||
|                 requestTelemetry: requestTelemetry, | ||||
|                 subscribe: subscribe | ||||
|             }; | ||||
|         } | ||||
|  | ||||
|         return SinewaveTelemetryProvider; | ||||
|     } | ||||
| ); | ||||
							
								
								
									
										78
									
								
								example/generator/src/SinewaveTelemetrySeries.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								example/generator/src/SinewaveTelemetrySeries.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,78 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2016, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
|  * Administration. All rights reserved. | ||||
|  * | ||||
|  * Open MCT is licensed under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * http://www.apache.org/licenses/LICENSE-2.0. | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
|  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
|  * License for the specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  * | ||||
|  * Open MCT includes source code licensed under additional open source | ||||
|  * licenses. See the Open Source Licenses file (LICENSES.md) included with | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
| /*global define,Promise*/ | ||||
|  | ||||
| /** | ||||
|  * Module defining SinewaveTelemetry. Created by vwoeltje on 11/12/14. | ||||
|  */ | ||||
| define( | ||||
|     ['./SinewaveConstants'], | ||||
|     function (SinewaveConstants) { | ||||
|         "use strict"; | ||||
|  | ||||
|         var ONE_DAY = 60 * 60 * 24, | ||||
|             firstObservedTime = Math.floor(SinewaveConstants.START_TIME / 1000); | ||||
|  | ||||
|         /** | ||||
|          * | ||||
|          * @constructor | ||||
|          */ | ||||
|         function SinewaveTelemetrySeries(request) { | ||||
|             var timeOffset = (request.domain === 'yesterday') ? ONE_DAY : 0, | ||||
|                 latestTime = Math.floor(Date.now() / 1000) - timeOffset, | ||||
|                 firstTime = firstObservedTime - timeOffset, | ||||
|                 endTime = (request.end !== undefined) ? | ||||
|                         Math.floor(request.end / 1000) : latestTime, | ||||
|                 count = Math.min(endTime, latestTime) - firstTime, | ||||
|                 period = +request.period || 30, | ||||
|                 generatorData = {}, | ||||
|                 requestStart = (request.start === undefined) ? firstTime : | ||||
|                         Math.max(Math.floor(request.start / 1000), firstTime), | ||||
|                 offset = requestStart - firstTime; | ||||
|  | ||||
|             if (request.size !== undefined) { | ||||
|                 offset = Math.max(offset, count - request.size); | ||||
|             } | ||||
|  | ||||
|             generatorData.getPointCount = function () { | ||||
|                 return count - offset; | ||||
|             }; | ||||
|  | ||||
|             generatorData.getDomainValue = function (i, domain) { | ||||
|                 // delta uses the same numeric values as the default domain, | ||||
|                 // so it's not checked for here, just formatted for display | ||||
|                 // differently. | ||||
|                 return (i + offset) * 1000 + firstTime * 1000 - | ||||
|                     (domain === 'yesterday' ? (ONE_DAY * 1000) : 0); | ||||
|             }; | ||||
|  | ||||
|             generatorData.getRangeValue = function (i, range) { | ||||
|                 range = range || "sin"; | ||||
|                 return Math[range]((i + offset) * Math.PI * 2 / period); | ||||
|             }; | ||||
|  | ||||
|             return generatorData; | ||||
|         } | ||||
|  | ||||
|         return SinewaveTelemetrySeries; | ||||
|     } | ||||
| ); | ||||
| @@ -49,7 +49,7 @@ define([ | ||||
|                 { | ||||
|                     "key": "imagery", | ||||
|                     "name": "Example Imagery", | ||||
|                     "cssClass": "icon-image", | ||||
|                     "cssclass": "icon-image", | ||||
|                     "features": "creation", | ||||
|                     "description": "For development use. Creates example imagery data that mimics a live imagery stream.", | ||||
|                     "priority": 10, | ||||
|   | ||||
| @@ -20,7 +20,7 @@ | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define(['../../../platform/features/conductor/core/src/timeSystems/LocalClock'], function (LocalClock) { | ||||
| define(['../../../platform/features/conductor-v2/conductor/src/timeSystems/LocalClock'], function (LocalClock) { | ||||
|     /** | ||||
|      * @implements TickSource | ||||
|      * @constructor | ||||
| @@ -31,7 +31,7 @@ define(['../../../platform/features/conductor/core/src/timeSystems/LocalClock'], | ||||
|         this.metadata = { | ||||
|             key: 'test-lad', | ||||
|             mode: 'lad', | ||||
|             cssClass: 'icon-clock', | ||||
|             cssclass: 'icon-clock', | ||||
|             label: 'Latest Available Data', | ||||
|             name: 'Latest available data', | ||||
|             description: 'Monitor real-time streaming data as it comes in. The Time Conductor and displays will automatically advance themselves based on a UTC clock.' | ||||
|   | ||||
| @@ -21,8 +21,8 @@ | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define([ | ||||
|     '../../../platform/features/conductor/core/src/timeSystems/TimeSystem', | ||||
|     '../../../platform/features/conductor/core/src/timeSystems/LocalClock', | ||||
|     '../../../platform/features/conductor-v2/conductor/src/timeSystems/TimeSystem', | ||||
|     '../../../platform/features/conductor-v2/conductor/src/timeSystems/LocalClock', | ||||
|     './LADTickSource' | ||||
| ], function (TimeSystem, LocalClock, LADTickSource) { | ||||
|     var THIRTY_MINUTES = 30 * 60 * 1000, | ||||
|   | ||||
| @@ -37,79 +37,74 @@ define([ | ||||
|     legacyRegistry.register("example/msl", { | ||||
|         "name" : "Mars Science Laboratory Data Adapter", | ||||
|         "extensions" : { | ||||
|             "types": [ | ||||
|                 { | ||||
|                     "name":"Mars Science Laboratory", | ||||
|                     "key": "msl.curiosity", | ||||
|                     "cssClass": "icon-object" | ||||
|                 }, | ||||
|                 { | ||||
|                     "name": "Instrument", | ||||
|                     "key": "msl.instrument", | ||||
|                     "cssClass": "icon-object", | ||||
|                     "model": {"composition": []} | ||||
|                 }, | ||||
|                 { | ||||
|                     "name": "Measurement", | ||||
|                     "key": "msl.measurement", | ||||
|                     "cssClass": "icon-telemetry", | ||||
|                     "model": {"telemetry": {}}, | ||||
|                     "telemetry": { | ||||
|                         "source": "rems.source", | ||||
|                         "domains": [ | ||||
|                             { | ||||
|                                 "name": "Time", | ||||
|                                 "key": "utc", | ||||
|                                 "format": "utc" | ||||
|                             } | ||||
|                         ] | ||||
|                     } | ||||
|         "types": [ | ||||
|             { | ||||
|                 "name":"Mars Science Laboratory", | ||||
|                 "key": "msl.curiosity", | ||||
|                 "cssclass": "icon-object" | ||||
|             }, | ||||
|             { | ||||
|                 "name": "Instrument", | ||||
|                 "key": "msl.instrument", | ||||
|                 "cssclass": "icon-object", | ||||
|                 "model": {"composition": []} | ||||
|             }, | ||||
|             { | ||||
|                 "name": "Measurement", | ||||
|                 "key": "msl.measurement", | ||||
|                 "cssclass": "icon-telemetry", | ||||
|                 "model": {"telemetry": {}}, | ||||
|                 "telemetry": { | ||||
|                     "source": "rems.source", | ||||
|                     "domains": [ | ||||
|                         { | ||||
|                             "name": "Time", | ||||
|                             "key": "timestamp", | ||||
|                             "format": "utc" | ||||
|                         } | ||||
|                     ] | ||||
|                 } | ||||
|             ], | ||||
|             "constants": [ | ||||
|                 { | ||||
|                     "key": "REMS_WS_URL", | ||||
|                     "value": "/proxyUrl?url=http://cab.inta-csic.es/rems/wp-content/plugins/marsweather-widget/api.php" | ||||
|             } | ||||
|         ], | ||||
|         "constants": [ | ||||
|             { | ||||
|                 "key": "REMS_WS_URL", | ||||
|                 "value": "/proxyUrl?url=http://cab.inta-csic.es/rems/wp-content/plugins/marsweather-widget/api.php" | ||||
|             } | ||||
|         ], | ||||
|         "roots": [ | ||||
|             { | ||||
|                 "id": "msl:curiosity", | ||||
|                 "priority" : "preferred", | ||||
|                 "model": { | ||||
|                     "type": "msl.curiosity", | ||||
|                     "name": "Mars Science Laboratory", | ||||
|                     "composition": ["msl_tlm:rems"] | ||||
|                 } | ||||
|             ], | ||||
|             "roots": [ | ||||
|                 { | ||||
|                     "id": "msl:curiosity" | ||||
|                 } | ||||
|             ], | ||||
|             "models": [ | ||||
|                 { | ||||
|                     "id": "msl:curiosity", | ||||
|                     "priority": "preferred", | ||||
|                     "model": { | ||||
|                         "type": "msl.curiosity", | ||||
|                         "name": "Mars Science Laboratory", | ||||
|                         "composition": ["msl_tlm:rems"] | ||||
|                     } | ||||
|                 } | ||||
|             ], | ||||
|             "services": [ | ||||
|                 { | ||||
|                     "key":"rems.adapter", | ||||
|                     "implementation": RemsTelemetryServerAdapter, | ||||
|                     "depends": ["$q", "$http", "$log", "REMS_WS_URL"] | ||||
|                 } | ||||
|             ], | ||||
|             "components": [ | ||||
|                 { | ||||
|                     "provides": "modelService", | ||||
|                     "type": "provider", | ||||
|                     "implementation": RemsTelemetryModelProvider, | ||||
|                     "depends": ["rems.adapter"] | ||||
|                 }, | ||||
|                 { | ||||
|                     "provides": "telemetryService", | ||||
|                     "type": "provider", | ||||
|                     "implementation": RemsTelemetryProvider, | ||||
|                     "depends": ["rems.adapter", "$q"] | ||||
|                 } | ||||
|             ] | ||||
|         } | ||||
|             } | ||||
|         ], | ||||
|         "services": [ | ||||
|             { | ||||
|                 "key":"rems.adapter", | ||||
|                 "implementation": RemsTelemetryServerAdapter, | ||||
|                 "depends": ["$q", "$http", "$log", "REMS_WS_URL"] | ||||
|             } | ||||
|         ], | ||||
|         "components": [ | ||||
|             { | ||||
|                 "provides": "modelService", | ||||
|                 "type": "provider", | ||||
|                 "implementation": RemsTelemetryModelProvider, | ||||
|                 "depends": ["rems.adapter"] | ||||
|             }, | ||||
|             { | ||||
|                 "provides": "telemetryService", | ||||
|                 "type": "provider", | ||||
|                 "implementation": RemsTelemetryProvider, | ||||
|                 "depends": ["rems.adapter", "$q"] | ||||
|             } | ||||
|         ] | ||||
|     } | ||||
|     }); | ||||
| }); | ||||
|  | ||||
|   | ||||
| @@ -81,7 +81,7 @@ define([ | ||||
|                 { | ||||
|                     "key": "plot", | ||||
|                     "name": "Example Telemetry Plot", | ||||
|                     "cssClass": "icon-telemetry-panel", | ||||
|                     "cssclass": "icon-telemetry-panel", | ||||
|                     "description": "For development use. A plot for displaying telemetry.", | ||||
|                     "priority": 10, | ||||
|                     "delegates": [ | ||||
| @@ -129,7 +129,7 @@ define([ | ||||
|                         { | ||||
|                             "name": "Period", | ||||
|                             "control": "textfield", | ||||
|                             "cssClass": "l-input-sm l-numeric", | ||||
|                             "cssclass": "l-input-sm l-numeric", | ||||
|                             "key": "period", | ||||
|                             "required": true, | ||||
|                             "property": [ | ||||
|   | ||||
| @@ -63,7 +63,7 @@ define( | ||||
|                  * Get the CSS class that defines the icon | ||||
|                  * to display in this indicator. This will appear | ||||
|                  * as a dataflow icon. | ||||
|                  * @returns {string} the cssClass of the dataflow icon | ||||
|                  * @returns {string} the cssclass of the dataflow icon | ||||
|                  */ | ||||
|                 getCssClass: function () { | ||||
|                     return "icon-connectivity"; | ||||
|   | ||||
| @@ -33,11 +33,6 @@ define([ | ||||
|     legacyRegistry.register("example/scratchpad", { | ||||
|         "extensions": { | ||||
|             "roots": [ | ||||
|                 { | ||||
|                     "id": "scratch:root" | ||||
|                 } | ||||
|             ], | ||||
|             "models": [ | ||||
|                 { | ||||
|                     "id": "scratch:root", | ||||
|                     "model": { | ||||
|   | ||||
| @@ -35,11 +35,6 @@ define([ | ||||
|         "description": "Example illustrating the addition of a static top-level hierarchy", | ||||
|         "extensions": { | ||||
|             "roots": [ | ||||
|                 { | ||||
|                     "id": "exampleTaxonomy" | ||||
|                 } | ||||
|             ], | ||||
|             "models": [ | ||||
|                 { | ||||
|                     "id": "exampleTaxonomy", | ||||
|                     "model": { | ||||
|   | ||||
							
								
								
									
										14
									
								
								gulpfile.js
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								gulpfile.js
									
									
									
									
									
								
							| @@ -36,7 +36,7 @@ var gulp = require('gulp'), | ||||
|         reports: 'dist/reports', | ||||
|         scss: ['./platform/**/*.scss', './example/**/*.scss'], | ||||
|         assets: [ | ||||
|             './{example,platform}/**/*.{css,css.map,png,svg,ico,woff,eot,ttf}' | ||||
|             './{example,platform}/**/*.{css,css.map,png,jpg,svg,ico,woff,eot,ttf}' | ||||
|         ], | ||||
|         scripts: [ 'openmct.js', 'platform/**/*.js', 'src/**/*.js' ], | ||||
|         specs: [ 'platform/**/*Spec.js', 'src/**/*Spec.js' ], | ||||
| @@ -69,11 +69,6 @@ var gulp = require('gulp'), | ||||
|         } | ||||
|     }; | ||||
|  | ||||
| if (process.env.NODE_ENV === 'development') { | ||||
|     options.requirejsOptimize.optimize = 'none'; | ||||
| } | ||||
|  | ||||
|  | ||||
| gulp.task('scripts', function () { | ||||
|     var requirejsOptimize = require('gulp-requirejs-optimize'); | ||||
|     var replace = require('gulp-replace-task'); | ||||
| @@ -131,17 +126,10 @@ gulp.task('lint', function () { | ||||
|  | ||||
| gulp.task('checkstyle', function () { | ||||
|     var jscs = require('gulp-jscs'); | ||||
|     var mkdirp = require('mkdirp'); | ||||
|     var reportName = 'jscs-html-report.html'; | ||||
|     var reportPath = path.resolve(paths.reports, 'checkstyle', reportName); | ||||
|     var moveReport = fs.rename.bind(fs, reportName, reportPath, _.noop); | ||||
|  | ||||
|     mkdirp.sync(path.resolve(paths.reports, 'checkstyle')); | ||||
|  | ||||
|     return gulp.src(paths.scripts) | ||||
|         .pipe(jscs()) | ||||
|         .pipe(jscs.reporter()) | ||||
|         .pipe(jscs.reporter('jscs-html-reporter')).on('finish', moveReport) | ||||
|         .pipe(jscs.reporter('fail')); | ||||
| }); | ||||
|  | ||||
|   | ||||
							
								
								
									
										19
									
								
								index.html
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								index.html
									
									
									
									
									
								
							| @@ -28,25 +28,16 @@ | ||||
|     <script src="bower_components/requirejs/require.js"> | ||||
|     </script> | ||||
|     <script> | ||||
|         require(['openmct'], function (openmct, generatorPlugin) { | ||||
|         require(['openmct'], function (openmct) { | ||||
|             [ | ||||
|                 'example/imagery', | ||||
|                 'example/eventGenerator' | ||||
|                 'example/eventGenerator', | ||||
|                 'example/generator', | ||||
|                 'platform/features/my-items', | ||||
|                 'platform/persistence/local' | ||||
|             ].forEach( | ||||
|                 openmct.legacyRegistry.enable.bind(openmct.legacyRegistry) | ||||
|             ); | ||||
|             openmct.install(openmct.plugins.myItems); | ||||
|             openmct.install(openmct.plugins.localStorage); | ||||
|             openmct.install(openmct.plugins.espresso); | ||||
|  | ||||
|             openmct.install(openmct.plugins.Generator()); | ||||
|             openmct.install(openmct.plugins.UTCTimeSystem()); | ||||
|             openmct.install(openmct.plugins.Conductor({ | ||||
|                 defaultTimeSystem: 'utc', | ||||
|                 defaultTimespan: 30 * 60 * 1000, | ||||
|                 showConductor: true | ||||
|             })); | ||||
|  | ||||
|             openmct.start(); | ||||
|         }); | ||||
|     </script> | ||||
|   | ||||
| @@ -76,7 +76,7 @@ module.exports = function(config) { | ||||
|         // Specify browsers to run tests in. | ||||
|         // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher | ||||
|         browsers: [ | ||||
|             'Chrome' | ||||
|             'PhantomJS' | ||||
|         ], | ||||
|  | ||||
|         // Code coverage reporting. | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "openmct", | ||||
|   "version": "0.12.1-SNAPSHOT", | ||||
|   "version": "0.12.0-SNAPSHOT", | ||||
|   "description": "The Open MCT core platform", | ||||
|   "dependencies": { | ||||
|     "express": "^4.13.1", | ||||
| @@ -21,16 +21,16 @@ | ||||
|     "gulp-sass": "^2.2.0", | ||||
|     "gulp-sourcemaps": "^1.6.0", | ||||
|     "jasmine-core": "^2.3.0", | ||||
|     "jscs-html-reporter": "^0.1.0", | ||||
|     "jsdoc": "^3.3.2", | ||||
|     "jshint": "^2.7.0", | ||||
|     "karma": "^0.13.3", | ||||
|     "karma-chrome-launcher": "^0.1.12", | ||||
|     "karma-chrome-launcher": "^0.1.8", | ||||
|     "karma-cli": "0.0.4", | ||||
|     "karma-coverage": "^0.5.3", | ||||
|     "karma-html-reporter": "^0.2.7", | ||||
|     "karma-jasmine": "^0.1.5", | ||||
|     "karma-junit-reporter": "^0.3.8", | ||||
|     "karma-phantomjs-launcher": "^1.0.0", | ||||
|     "karma-requirejs": "^0.2.2", | ||||
|     "lodash": "^3.10.1", | ||||
|     "markdown-toc": "^0.11.7", | ||||
| @@ -39,6 +39,7 @@ | ||||
|     "mkdirp": "^0.5.1", | ||||
|     "moment": "^2.11.1", | ||||
|     "node-bourbon": "^4.2.3", | ||||
|     "phantomjs-prebuilt": "2.1.11 || >2.1.12 <3.0.0", | ||||
|     "requirejs": "2.1.x", | ||||
|     "split": "^1.0.0" | ||||
|   }, | ||||
|   | ||||
| @@ -41,6 +41,7 @@ define([ | ||||
|     "text!./res/templates/items/items.html", | ||||
|     "text!./res/templates/browse/object-properties.html", | ||||
|     "text!./res/templates/browse/inspector-region.html", | ||||
|     "text!./res/templates/view-object.html", | ||||
|     'legacyRegistry' | ||||
| ], function ( | ||||
|     BrowseController, | ||||
| @@ -63,6 +64,7 @@ define([ | ||||
|     itemsTemplate, | ||||
|     objectPropertiesTemplate, | ||||
|     inspectorRegionTemplate, | ||||
|     viewObjectTemplate, | ||||
|     legacyRegistry | ||||
| ) { | ||||
|  | ||||
| @@ -70,13 +72,14 @@ define([ | ||||
|         "extensions": { | ||||
|             "routes": [ | ||||
|                 { | ||||
|                     "when": "/browse/:ids*?", | ||||
|                     "when": "/browse/:ids*", | ||||
|                     "template": browseTemplate, | ||||
|                     "reloadOnSearch": false | ||||
|                 }, | ||||
|                 { | ||||
|                     "when": "", | ||||
|                     "redirectTo": "/browse/" | ||||
|                     "template": browseTemplate, | ||||
|                     "reloadOnSearch": false | ||||
|                 } | ||||
|             ], | ||||
|             "constants": [ | ||||
| @@ -139,6 +142,10 @@ define([ | ||||
|                 } | ||||
|             ], | ||||
|             "representations": [ | ||||
|                 { | ||||
|                     "key": "view-object", | ||||
|                     "template": viewObjectTemplate | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "browse-object", | ||||
|                     "template": browseObjectTemplate, | ||||
| @@ -198,10 +205,7 @@ define([ | ||||
|             "services": [ | ||||
|                 { | ||||
|                     "key": "navigationService", | ||||
|                     "implementation": NavigationService, | ||||
|                     "depends": [ | ||||
|                         "$window" | ||||
|                     ] | ||||
|                     "implementation": NavigationService | ||||
|                 } | ||||
|             ], | ||||
|             "actions": [ | ||||
| @@ -209,10 +213,13 @@ define([ | ||||
|                     "key": "navigate", | ||||
|                     "implementation": NavigateAction, | ||||
|                     "depends": [ | ||||
|                         "navigationService" | ||||
|                         "navigationService", | ||||
|                         "$q", | ||||
|                         "policyService", | ||||
|                         "$window" | ||||
|                     ] | ||||
|                 }, | ||||
|                 { | ||||
| /*                { | ||||
|                     "key": "window", | ||||
|                     "name": "Open In New Tab", | ||||
|                     "implementation": NewTabAction, | ||||
| @@ -226,9 +233,9 @@ define([ | ||||
|                         "$window" | ||||
|                     ], | ||||
|                     "group": "windowing", | ||||
|                     "cssClass": "icon-new-window", | ||||
|                     "cssclass": "icon-new-window", | ||||
|                     "priority": "preferred" | ||||
|                 }, | ||||
|                 },*/ | ||||
|                 { | ||||
|                     "key": "fullscreen", | ||||
|                     "implementation": FullscreenAction, | ||||
| @@ -241,7 +248,7 @@ define([ | ||||
|                 { | ||||
|                     "key": "items", | ||||
|                     "name": "Items", | ||||
|                     "cssClass": "icon-thumbs-strip", | ||||
|                     "cssclass": "icon-thumbs-strip", | ||||
|                     "description": "Grid of available items", | ||||
|                     "template": itemsTemplate, | ||||
|                     "uses": [ | ||||
|   | ||||
| @@ -59,5 +59,7 @@ | ||||
|             </mct-representation> | ||||
|         </div> | ||||
|     </div> | ||||
|     <div ng-class="{'hide': synced}"> | ||||
|     <mct-include key="'conductor'" class="abs holder flex-elem flex-fixed l-flex-row l-time-conductor-holder"></mct-include> | ||||
|     </div> | ||||
| </div> | ||||
|   | ||||
| @@ -63,7 +63,7 @@ | ||||
|                     <mct-split-pane class='l-object-and-inspector contents abs' anchor='right'> | ||||
|                         <div class='split-pane-component t-object pane primary-pane left'> | ||||
|                             <mct-representation mct-object="navigatedObject" | ||||
|                                                 key="navigatedObject.getCapability('status').get('editing') ? 'edit-object' : 'browse-object'" | ||||
|                                                 key="'view-object'" | ||||
|                                                 class="abs holder holder-object"> | ||||
|                             </mct-representation> | ||||
|                             <a class="mini-tab-icon anchor-right mobile-hide toggle-pane toggle-inspect flush-right" | ||||
|   | ||||
							
								
								
									
										33
									
								
								platform/commonUI/browse/res/templates/view-object.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								platform/commonUI/browse/res/templates/view-object.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| <!-- | ||||
|  Open MCT, Copyright (c) 2014-2016, 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. | ||||
| --> | ||||
| <!-- | ||||
|  A representation that allows the 'View' region of an object view to change | ||||
|  dynamically (eg. between browse and edit modes). Values correspond to a | ||||
|  representation key, and currently defaults to 'browse-object'. | ||||
|  | ||||
|  In the case of edit, the EditRepresenter will change this to editable | ||||
|  representation of the object as needed. | ||||
|  --> | ||||
| <mct-representation mct-object="domainObject" | ||||
|                     key="viewObjectTemplate || 'browse-object'" | ||||
|                     class="abs holder"> | ||||
| </mct-representation> | ||||
| @@ -28,6 +28,8 @@ define( | ||||
|     [], | ||||
|     function () { | ||||
|  | ||||
|         var ROOT_ID = "ROOT"; | ||||
|  | ||||
|         /** | ||||
|          * The BrowseController is used to populate the initial scope in Browse | ||||
|          * mode. It loads the root object from the objectService and makes it | ||||
| @@ -47,26 +49,74 @@ define( | ||||
|             urlService, | ||||
|             defaultPath | ||||
|         ) { | ||||
|             var initialPath = ($route.current.params.ids || defaultPath).split("/"); | ||||
|             var currentIds; | ||||
|             var path = [ROOT_ID].concat( | ||||
|                 ($route.current.params.ids || defaultPath).split("/") | ||||
|             ); | ||||
|  | ||||
|             $scope.treeModel = { | ||||
|                 selectedObject: undefined, | ||||
|                 onSelection: function (object) { | ||||
|                     navigationService.setNavigation(object, true); | ||||
|                 }, | ||||
|                 allowSelection: function (object) { | ||||
|                     return navigationService.shouldNavigate(); | ||||
|                 } | ||||
|             }; | ||||
|             function updateRoute(domainObject) { | ||||
|                 var priorRoute = $route.current, | ||||
|                     // Act as if params HADN'T changed to avoid page reload | ||||
|                     unlisten; | ||||
|  | ||||
|                 unlisten = $scope.$on('$locationChangeSuccess', function () { | ||||
|                     // Checks path to make sure /browse/ is at front | ||||
|                     // if so, change $route.current | ||||
|                     if ($location.path().indexOf("/browse/") === 0) { | ||||
|                         $route.current = priorRoute; | ||||
|                     } | ||||
|                     unlisten(); | ||||
|                 }); | ||||
|                 // urlService.urlForLocation used to adjust current | ||||
|                 // path to new, addressed, path based on | ||||
|                 // domainObject | ||||
|                 $location.path(urlService.urlForLocation("browse", domainObject)); | ||||
|  | ||||
|             function idsForObject(domainObject) { | ||||
|                 return urlService | ||||
|                     .urlForLocation("", domainObject) | ||||
|                     .replace('/', ''); | ||||
|             } | ||||
|  | ||||
|             // Find an object in an array of objects. | ||||
|             function setScopeObjects(domainObject, navigationAllowed) { | ||||
|                 if (navigationAllowed) { | ||||
|                     $scope.navigatedObject = domainObject; | ||||
|                     $scope.treeModel.selectedObject = domainObject; | ||||
|                     updateRoute(domainObject); | ||||
|                 } else { | ||||
|                     //If navigation was unsuccessful (ie. blocked), reset | ||||
|                     // the selected object in the tree to the currently | ||||
|                     // navigated object | ||||
|                     $scope.treeModel.selectedObject = $scope.navigatedObject ; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             // Callback for updating the in-scope reference to the object | ||||
|             // that is currently navigated-to. | ||||
|             function setNavigation(domainObject) { | ||||
|                 if (domainObject === $scope.navigatedObject) { | ||||
|                     //do nothing; | ||||
|                     return; | ||||
|                 } | ||||
|                 if (domainObject) { | ||||
|                     domainObject.getCapability("action").perform("navigate").then(setScopeObjects.bind(undefined, domainObject)); | ||||
|                 } else { | ||||
|                     setScopeObjects(domainObject, true); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             function navigateTo(domainObject) { | ||||
|  | ||||
|                 // Check if an object has been navigated-to already... | ||||
|                 // If not, or if an ID path has been explicitly set in the URL, | ||||
|                 // navigate to the URL-specified object. | ||||
|                 if (!navigationService.getNavigation() || $route.current.params.ids) { | ||||
|                     // If not, pick a default as the last | ||||
|                     // root-level component (usually "mine") | ||||
|                     navigationService.setNavigation(domainObject); | ||||
|                     $scope.navigatedObject = domainObject; | ||||
|                 } else { | ||||
|                     // Otherwise, just expose the currently navigated object. | ||||
|                     $scope.navigatedObject = navigationService.getNavigation(); | ||||
|                     updateRoute($scope.navigatedObject); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             function findObject(domainObjects, id) { | ||||
|                 var i; | ||||
|                 for (i = 0; i < domainObjects.length; i += 1) { | ||||
| @@ -76,87 +126,63 @@ define( | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             // helper, fetch a single object from the object service. | ||||
|             function getObject(id) { | ||||
|                 return objectService.getObjects([id]) | ||||
|                     .then(function (results) { | ||||
|                         return results[id]; | ||||
|                     }); | ||||
|             } | ||||
|  | ||||
|             // recursively locate and return an object inside of a container | ||||
|             // via a path.  If at any point in the recursion it fails to find | ||||
|             // the next object, it will return the parent. | ||||
|             function findViaComposition(containerObject, path) { | ||||
|                 var nextId = path.shift(); | ||||
|                 if (!nextId) { | ||||
|                     return containerObject; | ||||
|                 } | ||||
|                 return containerObject.useCapability('composition') | ||||
|                     .then(function (composees) { | ||||
|                         var nextObject = findObject(composees, nextId); | ||||
|                         if (!nextObject) { | ||||
|                             return containerObject; | ||||
|             // Navigate to the domain object identified by path[index], | ||||
|             // which we expect to find in the composition of the passed | ||||
|             // domain object. | ||||
|             function doNavigate(domainObject, index) { | ||||
|                 var composition = domainObject.useCapability("composition"); | ||||
|                 if (composition) { | ||||
|                     composition.then(function (c) { | ||||
|                         var nextObject = findObject(c, path[index]); | ||||
|                         if (nextObject) { | ||||
|                             if (index + 1 >= path.length) { | ||||
|                                 navigateTo(nextObject); | ||||
|                             } else { | ||||
|                                 doNavigate(nextObject, index + 1); | ||||
|                             } | ||||
|                         } else if (index === 1 && c.length > 0) { | ||||
|                             // Roots are in a top-level container that we don't | ||||
|                             // want to be selected, so if we couldn't find an | ||||
|                             // object at the path we wanted, at least select | ||||
|                             // one of its children. | ||||
|                             navigateTo(c[c.length - 1]); | ||||
|                         } else { | ||||
|                             // Couldn't find the next element of the path | ||||
|                             // so navigate to the last path object we did find | ||||
|                             navigateTo(domainObject); | ||||
|                         } | ||||
|                         if (!nextObject.hasCapability('composition')) { | ||||
|                             return nextObject; | ||||
|                         } | ||||
|                         return findViaComposition(nextObject, path); | ||||
|                     }); | ||||
|             } | ||||
|  | ||||
|             function navigateToObject(desiredObject) { | ||||
|                 $scope.navigatedObject = desiredObject; | ||||
|                 $scope.treeModel.selectedObject = desiredObject; | ||||
|                 currentIds = idsForObject(desiredObject); | ||||
|                 $route.current.pathParams.ids = currentIds; | ||||
|                 $location.path('/browse/' + currentIds); | ||||
|             } | ||||
|  | ||||
|             function navigateToPath(path) { | ||||
|                 return getObject('ROOT') | ||||
|                     .then(function (root) { | ||||
|                         return findViaComposition(root, path); | ||||
|                     }) | ||||
|                     .then(function (object) { | ||||
|                         navigationService.setNavigation(object); | ||||
|                     }); | ||||
|             } | ||||
|  | ||||
|             getObject('ROOT') | ||||
|                 .then(function (root) { | ||||
|                     $scope.domainObject = root; | ||||
|                     navigateToPath(initialPath); | ||||
|                 }); | ||||
|  | ||||
|             // Handle navigation events from view service.  Only navigates | ||||
|             // if path has changed. | ||||
|             function navigateDirectlyToModel(domainObject) { | ||||
|                 var newIds = idsForObject(domainObject); | ||||
|                 if (currentIds !== newIds) { | ||||
|                     currentIds = newIds; | ||||
|                     navigateToObject(domainObject); | ||||
|                 } else { | ||||
|                     // Similar to above case; this object has no composition, | ||||
|                     // so navigate to it instead of subsequent path elements. | ||||
|                     navigateTo(domainObject); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             // Load the root object, put it in the scope. | ||||
|             // Also, load its immediate children, and (possibly) | ||||
|             // navigate to one of them, so that navigation state has | ||||
|             // a useful initial value. | ||||
|             objectService.getObjects([path[0]]).then(function (objects) { | ||||
|                 $scope.domainObject = objects[path[0]]; | ||||
|                 doNavigate($scope.domainObject, 1); | ||||
|             }); | ||||
|  | ||||
|             // Provide a model for the tree to modify | ||||
|             $scope.treeModel = { | ||||
|                 selectedObject: navigationService.getNavigation() | ||||
|             }; | ||||
|  | ||||
|             // Listen for changes in navigation state. | ||||
|             navigationService.addListener(navigateDirectlyToModel); | ||||
|             navigationService.addListener(setNavigation); | ||||
|  | ||||
|             // Listen for route changes which are caused by browser events | ||||
|             // (e.g. bookmarks to pages in OpenMCT) and prevent them.  Instead, | ||||
|             // navigate to the path ourselves, which results in it being | ||||
|             // properly set. | ||||
|             $scope.$on('$routeChangeStart', function (event, route) { | ||||
|                 if (route.$$route === $route.current.$$route && | ||||
|                     route.pathParams.ids !== $route.current.pathParams.ids) { | ||||
|                     event.preventDefault(); | ||||
|                     navigateToPath(route.pathParams.ids.split('/')); | ||||
|                 } | ||||
|             }); | ||||
|             // Also listen for changes which come from the tree. Changes in | ||||
|             // the tree will trigger a change in browse navigation state. | ||||
|             $scope.$watch("treeModel.selectedObject", setNavigation); | ||||
|  | ||||
|             // Clean up when the scope is destroyed | ||||
|             $scope.$on("$destroy", function () { | ||||
|                 navigationService.removeListener(navigateDirectlyToModel); | ||||
|                 navigationService.removeListener(setNavigation); | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -51,21 +51,30 @@ define( | ||||
|             } | ||||
|  | ||||
|             function updateQueryParam(viewKey) { | ||||
|                 if (viewKey && $location.search().view !== viewKey) { | ||||
|                 var unlisten, | ||||
|                     priorRoute = $route.current; | ||||
|  | ||||
|                 if (viewKey) { | ||||
|                     $location.search('view', viewKey); | ||||
|                     unlisten = $scope.$on('$locationChangeSuccess', function () { | ||||
|                         // Checks path to make sure /browse/ is at front | ||||
|                         // if so, change $route.current | ||||
|                         if ($location.path().indexOf("/browse/") === 0) { | ||||
|                             $route.current = priorRoute; | ||||
|                         } | ||||
|                         unlisten(); | ||||
|                     }); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             $scope.$watch('domainObject', setViewForDomainObject); | ||||
|             $scope.$watch('representation.selected.key', updateQueryParam); | ||||
|             $scope.$on('$locationChangeSuccess', function () { | ||||
|                 setViewForDomainObject($scope.domainObject); | ||||
|             }); | ||||
|  | ||||
|             $scope.doAction = function (action) { | ||||
|                 return $scope[action] && $scope[action](); | ||||
|             }; | ||||
|  | ||||
|             $scope.synced = $location.search().synced === 'true'; | ||||
|         } | ||||
|  | ||||
|         return BrowseObjectController; | ||||
|   | ||||
| @@ -64,11 +64,11 @@ define( | ||||
|                 attachStatusListener(domainObject); | ||||
|             } | ||||
|  | ||||
|             navigationService.addListener(attachStatusListener); | ||||
|             var navigationListener = navigationService.addListener(attachStatusListener); | ||||
|  | ||||
|             $scope.$on("$destroy", function () { | ||||
|                 statusListener(); | ||||
|                 navigationService.removeListener(attachStatusListener); | ||||
|                 navigationListener(); | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -33,9 +33,12 @@ define( | ||||
|          * @constructor | ||||
|          * @implements {Action} | ||||
|          */ | ||||
|         function NavigateAction(navigationService, context) { | ||||
|         function NavigateAction(navigationService, $q, policyService, $window, context) { | ||||
|             this.domainObject = context.domainObject; | ||||
|             this.$q = $q; | ||||
|             this.navigationService = navigationService; | ||||
|             this.policyService = policyService; | ||||
|             this.$window = $window; | ||||
|         } | ||||
|  | ||||
|         /** | ||||
| @@ -44,12 +47,36 @@ define( | ||||
|          *          navigation has been updated | ||||
|          */ | ||||
|         NavigateAction.prototype.perform = function () { | ||||
|             if (this.navigationService.shouldNavigate()) { | ||||
|                 this.navigationService.setNavigation(this.domainObject, true); | ||||
|                 return Promise.resolve({}); | ||||
|             var self = this, | ||||
|                 navigateTo = this.domainObject, | ||||
|                 currentObject = self.navigationService.getNavigation(); | ||||
|  | ||||
|             function allow() { | ||||
|                 var navigationAllowed = true; | ||||
|                 self.policyService.allow("navigation", currentObject, navigateTo, function (message) { | ||||
|                     navigationAllowed = self.$window.confirm(message + "\r\n\r\n" + | ||||
|                         " Are you sure you want to continue?"); | ||||
|                 }); | ||||
|                 return navigationAllowed; | ||||
|             } | ||||
|  | ||||
|             function cancelIfEditing() { | ||||
|                 var editing = currentObject.hasCapability('editor') && | ||||
|                     currentObject.getCapability('editor').isEditContextRoot(); | ||||
|  | ||||
|                 return self.$q.when(editing && currentObject.getCapability("editor").finish()); | ||||
|             } | ||||
|  | ||||
|             function navigate() { | ||||
|                 return self.navigationService.setNavigation(navigateTo); | ||||
|             } | ||||
|  | ||||
|             if (allow()) { | ||||
|                 return cancelIfEditing().then(navigate); | ||||
|             } else { | ||||
|                 return this.$q.when(false); | ||||
|             } | ||||
|  | ||||
|             return Promise.reject('Navigation Prevented by User'); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|   | ||||
| @@ -30,23 +30,16 @@ define( | ||||
|         /** | ||||
|          * The navigation service maintains the application's current | ||||
|          * navigation state, and allows listening for changes thereto. | ||||
|          * | ||||
|          * @memberof platform/commonUI/browse | ||||
|          * @constructor | ||||
|          */ | ||||
|         function NavigationService($window) { | ||||
|         function NavigationService() { | ||||
|             this.navigated = undefined; | ||||
|             this.callbacks = []; | ||||
|             this.checks = []; | ||||
|             this.$window = $window; | ||||
|  | ||||
|             this.oldUnload = $window.onbeforeunload; | ||||
|             $window.onbeforeunload = this.onBeforeUnload.bind(this); | ||||
|         } | ||||
|  | ||||
|         /** | ||||
|          * Get the current navigation state. | ||||
|          * | ||||
|          * @returns {DomainObject} the object that is navigated-to | ||||
|          */ | ||||
|         NavigationService.prototype.getNavigation = function () { | ||||
| @@ -54,33 +47,16 @@ define( | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Navigate to a specified object.  If navigation checks exist and | ||||
|          * return reasons to prevent navigation, it will prompt the user before | ||||
|          * continuing. Trying to navigate to the currently navigated object will | ||||
|          * do nothing. | ||||
|          * | ||||
|          * If a truthy value is passed for `force`, it will skip navigation | ||||
|          * and will not prevent navigation to an already selected object. | ||||
|          * | ||||
|          * Set the current navigation state. This will invoke listeners. | ||||
|          * @param {DomainObject} domainObject the domain object to navigate to | ||||
|          * @param {Boolean} force if true, force navigation to occur. | ||||
|          * @returns {Boolean} true if navigation occured, otherwise false. | ||||
|          */ | ||||
|         NavigationService.prototype.setNavigation = function (domainObject, force) { | ||||
|             if (force) { | ||||
|                 this.doNavigation(domainObject); | ||||
|                 return true; | ||||
|         NavigationService.prototype.setNavigation = function (value) { | ||||
|             if (this.navigated !== value) { | ||||
|                 this.navigated = value; | ||||
|                 this.callbacks.forEach(function (callback) { | ||||
|                     callback(value); | ||||
|                 }); | ||||
|             } | ||||
|             if (this.navigated === domainObject) { | ||||
|                 return true; | ||||
|             } | ||||
|  | ||||
|             var doNotNavigate = this.shouldWarnBeforeNavigate(); | ||||
|             if (doNotNavigate && !this.$window.confirm(doNotNavigate)) { | ||||
|                 return false; | ||||
|             } | ||||
|  | ||||
|             this.doNavigation(domainObject); | ||||
|             return true; | ||||
|         }; | ||||
|  | ||||
| @@ -88,7 +64,6 @@ define( | ||||
|          * Listen for changes in navigation. The passed callback will | ||||
|          * be invoked with the new domain object of navigation when | ||||
|          * this changes. | ||||
|          * | ||||
|          * @param {function} callback the callback to invoke when | ||||
|          *        navigation state changes | ||||
|          */ | ||||
| @@ -98,7 +73,6 @@ define( | ||||
|  | ||||
|         /** | ||||
|          * Stop listening for changes in navigation state. | ||||
|          * | ||||
|          * @param {function} callback the callback which should | ||||
|          *        no longer be invoked when navigation state | ||||
|          *        changes | ||||
| @@ -109,89 +83,6 @@ define( | ||||
|             }); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Check if navigation should proceed.  May prompt a user for input | ||||
|          * if any checkFns return messages.  Returns true if the user wishes to | ||||
|          * navigate, otherwise false.  If using this prior to calling | ||||
|          * `setNavigation`, you should call `setNavigation` with `force=true` | ||||
|          * to prevent duplicate dialogs being displayed to the user. | ||||
|          * | ||||
|          * @returns {Boolean} true if the user wishes to navigate, otherwise false. | ||||
|          */ | ||||
|         NavigationService.prototype.shouldNavigate = function () { | ||||
|             var doNotNavigate = this.shouldWarnBeforeNavigate(); | ||||
|             return !doNotNavigate || this.$window.confirm(doNotNavigate); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Register a check function to be called before any navigation occurs. | ||||
|          * Check functions should return a human readable "message" if | ||||
|          * there are any reasons to prevent navigation.  Otherwise, they should | ||||
|          * return falsy.  Returns a function which can be called to remove the | ||||
|          * check function. | ||||
|          * | ||||
|          * @param {Function} checkFn a function to call before navigation occurs. | ||||
|          * @returns {Function} removeCheck call to remove check | ||||
|          */ | ||||
|         NavigationService.prototype.checkBeforeNavigation = function (checkFn) { | ||||
|             this.checks.push(checkFn); | ||||
|             return function removeCheck() { | ||||
|                 this.checks = this.checks.filter(function (fn) { | ||||
|                     return checkFn !== fn; | ||||
|                 }); | ||||
|             }.bind(this); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Private method to actually perform navigation. | ||||
|          * | ||||
|          * @private | ||||
|          */ | ||||
|         NavigationService.prototype.doNavigation = function (value) { | ||||
|             this.navigated = value; | ||||
|             this.callbacks.forEach(function (callback) { | ||||
|                 callback(value); | ||||
|             }); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Returns either a false value, or a string that should be displayed | ||||
|          * to the user before navigation is allowed. | ||||
|          * | ||||
|          * @private | ||||
|          */ | ||||
|         NavigationService.prototype.shouldWarnBeforeNavigate = function () { | ||||
|             var reasons = []; | ||||
|  | ||||
|             this.checks.forEach(function (checkFn) { | ||||
|                 var reason = checkFn(); | ||||
|                 if (reason) { | ||||
|                     reasons.push(reason); | ||||
|                 } | ||||
|             }); | ||||
|  | ||||
|             if (reasons.length) { | ||||
|                 return reasons.join('\n'); | ||||
|             } | ||||
|             return false; | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Listener for window on before unload event-- will warn before | ||||
|          * navigation is allowed. | ||||
|          * | ||||
|          * @private | ||||
|          */ | ||||
|         NavigationService.prototype.onBeforeUnload = function () { | ||||
|             var shouldWarnBeforeNavigate = this.shouldWarnBeforeNavigate(); | ||||
|             if (shouldWarnBeforeNavigate) { | ||||
|                 return shouldWarnBeforeNavigate; | ||||
|             } | ||||
|             if (this.oldUnload) { | ||||
|                 return this.oldUnload.apply(undefined, [].slice.apply(arguments)); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         return NavigationService; | ||||
|     } | ||||
| ); | ||||
|   | ||||
| @@ -43,24 +43,24 @@ define([], function () { | ||||
|             return context.getParent(); | ||||
|         } | ||||
|  | ||||
|         function preventOrphanNavigation(domainObject) { | ||||
|         function isOrphan(domainObject) { | ||||
|             var parent = getParent(domainObject), | ||||
|                 composition = parent.getModel().composition, | ||||
|                 id = domainObject.getId(); | ||||
|             return !composition || (composition.indexOf(id) === -1); | ||||
|         } | ||||
|  | ||||
|         function navigateToParent(domainObject) { | ||||
|             var parent = getParent(domainObject); | ||||
|             parent.useCapability('composition') | ||||
|                 .then(function (composees) { | ||||
|                     var isOrphan = composees.every(function (c) { | ||||
|                         return c.getId() !== domainObject.getId() | ||||
|                     }); | ||||
|                     if (isOrphan) { | ||||
|                         parent.getCapability('action').perform('navigate'); | ||||
|                     } | ||||
|                 }); | ||||
|             return parent.getCapability('action').perform('navigate'); | ||||
|         } | ||||
|  | ||||
|         function checkNavigation() { | ||||
|             var navigatedObject = navigationService.getNavigation(); | ||||
|             if (navigatedObject.hasCapability('context')) { | ||||
|             if (navigatedObject.hasCapability('context') && | ||||
|                 isOrphan(navigatedObject)) { | ||||
|                 if (!navigatedObject.getCapability('editor').isEditContextRoot()) { | ||||
|                     preventOrphanNavigation(navigatedObject); | ||||
|                     navigateToParent(navigatedObject); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|   | ||||
| @@ -46,12 +46,12 @@ define( | ||||
|         }; | ||||
|  | ||||
|         FullscreenAction.prototype.getMetadata = function () { | ||||
|             // We override getMetadata, because the icon cssClass and | ||||
|             // We override getMetadata, because the icon cssclass and | ||||
|             // description need to be determined at run-time | ||||
|             // based on whether or not we are currently | ||||
|             // full screen. | ||||
|             var metadata = Object.create(FullscreenAction); | ||||
|             metadata.cssClass = screenfull.isFullscreen ? "icon-fullscreen-expand" : "icon-fullscreen-collapse"; | ||||
|             metadata.cssclass = screenfull.isFullscreen ? "icon-fullscreen-expand" : "icon-fullscreen-collapse"; | ||||
|             metadata.description = screenfull.isFullscreen ? | ||||
|                 EXIT_FULLSCREEN : ENTER_FULLSCREEN; | ||||
|             metadata.group = "windowing"; | ||||
|   | ||||
| @@ -24,14 +24,8 @@ | ||||
|  * MCTRepresentationSpec. Created by vwoeltje on 11/6/14. | ||||
|  */ | ||||
| define( | ||||
|     [ | ||||
|         "../src/BrowseController", | ||||
|         "../src/navigation/NavigationService" | ||||
|     ], | ||||
|     function ( | ||||
|         BrowseController, | ||||
|         NavigationService | ||||
|     ) { | ||||
|     ["../src/BrowseController"], | ||||
|     function (BrowseController) { | ||||
|  | ||||
|         describe("The browse controller", function () { | ||||
|             var mockScope, | ||||
| @@ -41,17 +35,18 @@ define( | ||||
|                 mockNavigationService, | ||||
|                 mockRootObject, | ||||
|                 mockUrlService, | ||||
|                 mockDefaultRootObject, | ||||
|                 mockOtherDomainObject, | ||||
|                 mockDomainObject, | ||||
|                 mockNextObject, | ||||
|                 testDefaultRoot, | ||||
|                 mockActionCapability, | ||||
|                 controller; | ||||
|  | ||||
|             function waitsForNavigation() { | ||||
|                 var calls = mockNavigationService.setNavigation.calls.length; | ||||
|                 waitsFor(function () { | ||||
|                     return mockNavigationService.setNavigation.calls.length > calls; | ||||
|                 }); | ||||
|             function mockPromise(value) { | ||||
|                 return { | ||||
|                     then: function (callback) { | ||||
|                         return mockPromise(callback(value)); | ||||
|                     } | ||||
|                 }; | ||||
|             } | ||||
|  | ||||
|             function instantiateController() { | ||||
| @@ -73,114 +68,85 @@ define( | ||||
|                     "$scope", | ||||
|                     ["$on", "$watch"] | ||||
|                 ); | ||||
|                 mockRoute = { current: { params: {}, pathParams: {} } }; | ||||
|                 mockUrlService = jasmine.createSpyObj( | ||||
|                     "urlService", | ||||
|                     ["urlForLocation"] | ||||
|                 ); | ||||
|                 mockUrlService.urlForLocation.andCallFake(function (mode, object) { | ||||
|                     if (object === mockDefaultRootObject) { | ||||
|                         return [mode, testDefaultRoot].join('/'); | ||||
|                     } | ||||
|                     if (object === mockOtherDomainObject) { | ||||
|                         return [mode, 'other'].join('/'); | ||||
|                     } | ||||
|                     if (object === mockNextObject) { | ||||
|                         return [mode, testDefaultRoot, 'next'].join('/'); | ||||
|                     } | ||||
|                     throw new Error('Tried to get url for unexpected object'); | ||||
|                 }); | ||||
|                 mockRoute = { current: { params: {} } }; | ||||
|                 mockLocation = jasmine.createSpyObj( | ||||
|                     "$location", | ||||
|                     ["path"] | ||||
|                 ); | ||||
|                 mockUrlService = jasmine.createSpyObj( | ||||
|                     "urlService", | ||||
|                     ["urlForLocation"] | ||||
|                 ); | ||||
|                 mockObjectService = jasmine.createSpyObj( | ||||
|                     "objectService", | ||||
|                     ["getObjects"] | ||||
|                 ); | ||||
|                 mockNavigationService = new NavigationService({}); | ||||
|                 [ | ||||
|                     "getNavigation", | ||||
|                     "setNavigation", | ||||
|                     "addListener", | ||||
|                     "removeListener" | ||||
|                 ].forEach(function (method) { | ||||
|                     spyOn(mockNavigationService, method) | ||||
|                         .andCallThrough(); | ||||
|                 }); | ||||
|                 mockNavigationService = jasmine.createSpyObj( | ||||
|                     "navigationService", | ||||
|                     [ | ||||
|                         "getNavigation", | ||||
|                         "setNavigation", | ||||
|                         "addListener", | ||||
|                         "removeListener" | ||||
|                     ] | ||||
|                 ); | ||||
|                 mockRootObject = jasmine.createSpyObj( | ||||
|                     "rootObjectContainer", | ||||
|                     ["getId", "getCapability", "getModel", "useCapability", "hasCapability"] | ||||
|                     "domainObject", | ||||
|                     ["getId", "getCapability", "getModel", "useCapability"] | ||||
|                 ); | ||||
|                 mockDefaultRootObject = jasmine.createSpyObj( | ||||
|                     "defaultRootObject", | ||||
|                     ["getId", "getCapability", "getModel", "useCapability", "hasCapability"] | ||||
|                 ); | ||||
|                 mockOtherDomainObject = jasmine.createSpyObj( | ||||
|                     "otherDomainObject", | ||||
|                     ["getId", "getCapability", "getModel", "useCapability", "hasCapability"] | ||||
|                 mockDomainObject = jasmine.createSpyObj( | ||||
|                     "domainObject", | ||||
|                     ["getId", "getCapability", "getModel", "useCapability"] | ||||
|                 ); | ||||
|                 mockNextObject = jasmine.createSpyObj( | ||||
|                     "nestedDomainObject", | ||||
|                     ["getId", "getCapability", "getModel", "useCapability", "hasCapability"] | ||||
|                     "nextObject", | ||||
|                     ["getId", "getCapability", "getModel", "useCapability"] | ||||
|                 ); | ||||
|                 mockObjectService.getObjects.andReturn(Promise.resolve({ | ||||
|  | ||||
|                 mockObjectService.getObjects.andReturn(mockPromise({ | ||||
|                     ROOT: mockRootObject | ||||
|                 })); | ||||
|                 mockRootObject.useCapability.andReturn(Promise.resolve([ | ||||
|                     mockOtherDomainObject, | ||||
|                     mockDefaultRootObject | ||||
|                 mockRootObject.useCapability.andReturn(mockPromise([ | ||||
|                     mockDomainObject | ||||
|                 ])); | ||||
|                 mockRootObject.hasCapability.andReturn(true); | ||||
|                 mockDefaultRootObject.useCapability.andReturn(Promise.resolve([ | ||||
|                 mockDomainObject.useCapability.andReturn(mockPromise([ | ||||
|                     mockNextObject | ||||
|                 ])); | ||||
|                 mockDefaultRootObject.hasCapability.andReturn(true); | ||||
|                 mockOtherDomainObject.hasCapability.andReturn(false); | ||||
|                 mockNextObject.useCapability.andReturn(undefined); | ||||
|                 mockNextObject.hasCapability.andReturn(false); | ||||
|                 mockNextObject.getId.andReturn("next"); | ||||
|                 mockDefaultRootObject.getId.andReturn(testDefaultRoot); | ||||
|                 mockDomainObject.getId.andReturn(testDefaultRoot); | ||||
|  | ||||
|                 mockActionCapability = jasmine.createSpyObj('actionCapability', ['perform']); | ||||
|  | ||||
|                 instantiateController(); | ||||
|                 waitsForNavigation(); | ||||
|             }); | ||||
|  | ||||
|             it("uses composition to set the navigated object, if there is none", function () { | ||||
|                 instantiateController(); | ||||
|                 waitsForNavigation(); | ||||
|                 runs(function () { | ||||
|                     expect(mockNavigationService.setNavigation) | ||||
|                         .toHaveBeenCalledWith(mockDefaultRootObject); | ||||
|                 }); | ||||
|                 expect(mockNavigationService.setNavigation) | ||||
|                     .toHaveBeenCalledWith(mockDomainObject); | ||||
|             }); | ||||
|  | ||||
|             it("navigates to a root-level object, even when default path is not found", function () { | ||||
|                 mockDefaultRootObject.getId | ||||
|                 mockDomainObject.getId | ||||
|                     .andReturn("something-other-than-the-" + testDefaultRoot); | ||||
|                 instantiateController(); | ||||
|  | ||||
|                 waitsForNavigation(); | ||||
|                 runs(function () { | ||||
|                     expect(mockNavigationService.setNavigation) | ||||
|                         .toHaveBeenCalledWith(mockDefaultRootObject); | ||||
|                 }); | ||||
|  | ||||
|                 expect(mockNavigationService.setNavigation) | ||||
|                     .toHaveBeenCalledWith(mockDomainObject); | ||||
|             }); | ||||
|             // | ||||
|  | ||||
|             it("does not try to override navigation", function () { | ||||
|                 mockNavigationService.getNavigation.andReturn(mockDefaultRootObject); | ||||
|                 mockNavigationService.getNavigation.andReturn(mockDomainObject); | ||||
|                 instantiateController(); | ||||
|                 waitsForNavigation(); | ||||
|                 expect(mockScope.navigatedObject).toBe(mockDefaultRootObject); | ||||
|                 expect(mockScope.navigatedObject).toBe(mockDomainObject); | ||||
|             }); | ||||
|             // | ||||
|  | ||||
|             it("updates scope when navigated object changes", function () { | ||||
|                 // Should have registered a listener - call it | ||||
|                 mockNavigationService.addListener.mostRecentCall.args[0]( | ||||
|                     mockOtherDomainObject | ||||
|                     mockDomainObject | ||||
|                 ); | ||||
|                 expect(mockScope.navigatedObject).toEqual(mockOtherDomainObject); | ||||
|                 expect(mockScope.navigatedObject).toEqual(mockDomainObject); | ||||
|             }); | ||||
|  | ||||
|  | ||||
| @@ -200,12 +166,9 @@ define( | ||||
|             it("uses route parameters to choose initially-navigated object", function () { | ||||
|                 mockRoute.current.params.ids = testDefaultRoot + "/next"; | ||||
|                 instantiateController(); | ||||
|                 waitsForNavigation(); | ||||
|                 runs(function () { | ||||
|                     expect(mockScope.navigatedObject).toBe(mockNextObject); | ||||
|                     expect(mockNavigationService.setNavigation) | ||||
|                         .toHaveBeenCalledWith(mockNextObject); | ||||
|                 }); | ||||
|                 expect(mockScope.navigatedObject).toBe(mockNextObject); | ||||
|                 expect(mockNavigationService.setNavigation) | ||||
|                     .toHaveBeenCalledWith(mockNextObject); | ||||
|             }); | ||||
|  | ||||
|             it("handles invalid IDs by going as far as possible", function () { | ||||
| @@ -214,13 +177,9 @@ define( | ||||
|                 // it hits an invalid ID. | ||||
|                 mockRoute.current.params.ids = testDefaultRoot + "/junk"; | ||||
|                 instantiateController(); | ||||
|                 waitsForNavigation(); | ||||
|                 runs(function () { | ||||
|                     expect(mockScope.navigatedObject).toBe(mockDefaultRootObject); | ||||
|                     expect(mockNavigationService.setNavigation) | ||||
|                         .toHaveBeenCalledWith(mockDefaultRootObject); | ||||
|  | ||||
|                 }); | ||||
|                 expect(mockScope.navigatedObject).toBe(mockDomainObject); | ||||
|                 expect(mockNavigationService.setNavigation) | ||||
|                     .toHaveBeenCalledWith(mockDomainObject); | ||||
|             }); | ||||
|  | ||||
|             it("handles compositionless objects by going as far as possible", function () { | ||||
| @@ -229,33 +188,84 @@ define( | ||||
|                 // should stop at it since remaining IDs cannot be loaded. | ||||
|                 mockRoute.current.params.ids = testDefaultRoot + "/next/junk"; | ||||
|                 instantiateController(); | ||||
|                 waitsForNavigation(); | ||||
|                 runs(function () { | ||||
|                     expect(mockScope.navigatedObject).toBe(mockNextObject); | ||||
|                     expect(mockNavigationService.setNavigation) | ||||
|                         .toHaveBeenCalledWith(mockNextObject); | ||||
|                 }); | ||||
|                 expect(mockScope.navigatedObject).toBe(mockNextObject); | ||||
|                 expect(mockNavigationService.setNavigation) | ||||
|                     .toHaveBeenCalledWith(mockNextObject); | ||||
|             }); | ||||
|  | ||||
|             it("updates the displayed route to reflect current navigation", function () { | ||||
|                 // In order to trigger a route update and not a route change, | ||||
|                 // the current route must be updated before location.path is | ||||
|                 // called. | ||||
|                 expect(mockRoute.current.pathParams.ids) | ||||
|                     .not | ||||
|                     .toBe(testDefaultRoot + '/next'); | ||||
|                 mockLocation.path.andCallFake(function () { | ||||
|                     expect(mockRoute.current.pathParams.ids) | ||||
|                         .toBe(testDefaultRoot + '/next'); | ||||
|                 var mockContext = jasmine.createSpyObj('context', ['getPath']), | ||||
|                     mockUnlisten = jasmine.createSpy('unlisten'), | ||||
|                     mockMode = "browse"; | ||||
|  | ||||
|                 mockContext.getPath.andReturn( | ||||
|                     [mockRootObject, mockDomainObject, mockNextObject] | ||||
|                 ); | ||||
|  | ||||
|                 //Return true from navigate action | ||||
|                 mockActionCapability.perform.andReturn(mockPromise(true)); | ||||
|  | ||||
|                 mockNextObject.getCapability.andCallFake(function (c) { | ||||
|                     return (c === 'context' && mockContext) || | ||||
|                         (c === 'action' && mockActionCapability); | ||||
|                 }); | ||||
|                 mockScope.$on.andReturn(mockUnlisten); | ||||
|                 // Provide a navigation change | ||||
|                 mockNavigationService.addListener.mostRecentCall.args[0]( | ||||
|                     mockNextObject | ||||
|                 ); | ||||
|  | ||||
|                 // Allows the path index to be checked | ||||
|                 // prior to setting $route.current | ||||
|                 mockLocation.path.andReturn("/browse/"); | ||||
|  | ||||
|                 mockNavigationService.setNavigation.andReturn(true); | ||||
|                 mockActionCapability.perform.andReturn(mockPromise(true)); | ||||
|  | ||||
|                 // Exercise the Angular workaround | ||||
|                 mockNavigationService.addListener.mostRecentCall.args[0](); | ||||
|                 mockScope.$on.mostRecentCall.args[1](); | ||||
|                 expect(mockUnlisten).toHaveBeenCalled(); | ||||
|  | ||||
|                 // location.path to be called with the urlService's | ||||
|                 // urlFor function with the next domainObject and mode | ||||
|                 expect(mockLocation.path).toHaveBeenCalledWith( | ||||
|                     '/browse/' + testDefaultRoot + '/next' | ||||
|                     mockUrlService.urlForLocation(mockMode, mockNextObject) | ||||
|                 ); | ||||
|             }); | ||||
|  | ||||
|             it("after successful navigation event sets the selected tree " + | ||||
|                 "object", function () { | ||||
|                 mockScope.navigatedObject = mockDomainObject; | ||||
|                 mockNavigationService.setNavigation.andReturn(true); | ||||
|  | ||||
|                 mockActionCapability.perform.andReturn(mockPromise(true)); | ||||
|                 mockNextObject.getCapability.andReturn(mockActionCapability); | ||||
|  | ||||
|                 //Simulate a change in selected tree object | ||||
|                 mockScope.treeModel = {selectedObject: mockDomainObject}; | ||||
|                 mockScope.$watch.mostRecentCall.args[1](mockNextObject); | ||||
|  | ||||
|                 expect(mockScope.treeModel.selectedObject).toBe(mockNextObject); | ||||
|                 expect(mockScope.treeModel.selectedObject).not.toBe(mockDomainObject); | ||||
|             }); | ||||
|  | ||||
|             it("after failed navigation event resets the selected tree" + | ||||
|                 " object", function () { | ||||
|                 mockScope.navigatedObject = mockDomainObject; | ||||
|  | ||||
|                 //Return false from navigation action | ||||
|                 mockActionCapability.perform.andReturn(mockPromise(false)); | ||||
|                 mockNextObject.getCapability.andReturn(mockActionCapability); | ||||
|  | ||||
|                 //Simulate a change in selected tree object | ||||
|                 mockScope.treeModel = {selectedObject: mockDomainObject}; | ||||
|                 mockScope.$watch.mostRecentCall.args[1](mockNextObject); | ||||
|  | ||||
|                 expect(mockScope.treeModel.selectedObject).not.toBe(mockNextObject); | ||||
|                 expect(mockScope.treeModel.selectedObject).toBe(mockDomainObject); | ||||
|             }); | ||||
|  | ||||
|         }); | ||||
|     } | ||||
| ); | ||||
|   | ||||
| @@ -29,6 +29,7 @@ define( | ||||
|             var mockScope, | ||||
|                 mockLocation, | ||||
|                 mockRoute, | ||||
|                 mockUnlisten, | ||||
|                 controller; | ||||
|  | ||||
|             // Utility function; look for a $watch on scope and fire it | ||||
| @@ -50,7 +51,9 @@ define( | ||||
|                     "$location", | ||||
|                     ["path", "search"] | ||||
|                 ); | ||||
|                 mockLocation.search.andReturn({}); | ||||
|                 mockUnlisten = jasmine.createSpy("unlisten"); | ||||
|  | ||||
|                 mockScope.$on.andReturn(mockUnlisten); | ||||
|  | ||||
|                 controller = new BrowseObjectController( | ||||
|                     mockScope, | ||||
| @@ -66,6 +69,10 @@ define( | ||||
|                 // Allows the path index to be checked | ||||
|                 // prior to setting $route.current | ||||
|                 mockLocation.path.andReturn("/browse/"); | ||||
|  | ||||
|                 // Exercise the Angular workaround | ||||
|                 mockScope.$on.mostRecentCall.args[1](); | ||||
|                 expect(mockUnlisten).toHaveBeenCalled(); | ||||
|             }); | ||||
|  | ||||
|             it("sets the active view from query parameters", function () { | ||||
|   | ||||
| @@ -23,74 +23,145 @@ | ||||
| /** | ||||
|  * MCTRepresentationSpec. Created by vwoeltje on 11/6/14. | ||||
|  */ | ||||
| define([ | ||||
|     "../../src/navigation/NavigateAction" | ||||
| ], function ( | ||||
|     NavigateAction | ||||
| ) { | ||||
| define( | ||||
|     ["../../src/navigation/NavigateAction"], | ||||
|     function (NavigateAction) { | ||||
|  | ||||
|     describe("The navigate action", function () { | ||||
|         var mockNavigationService, | ||||
|             mockDomainObject, | ||||
|             action; | ||||
|         describe("The navigate action", function () { | ||||
|             var mockNavigationService, | ||||
|                 mockQ, | ||||
|                 mockDomainObject, | ||||
|                 mockPolicyService, | ||||
|                 mockNavigatedObject, | ||||
|                 mockWindow, | ||||
|                 capabilities, | ||||
|                 action; | ||||
|  | ||||
|             function mockPromise(value) { | ||||
|                 return { | ||||
|                     then: function (callback) { | ||||
|                         return mockPromise(callback(value)); | ||||
|                     } | ||||
|                 }; | ||||
|             } | ||||
|  | ||||
|         function waitForCall() { | ||||
|             var called = false; | ||||
|             waitsFor(function () { | ||||
|                 return called; | ||||
|             beforeEach(function () { | ||||
|                 capabilities = {}; | ||||
|  | ||||
|                 mockQ = { when: mockPromise }; | ||||
|                 mockNavigatedObject = jasmine.createSpyObj( | ||||
|                     "domainObject", | ||||
|                     [ | ||||
|                         "getId", | ||||
|                         "getModel", | ||||
|                         "hasCapability", | ||||
|                         "getCapability" | ||||
|                     ] | ||||
|                 ); | ||||
|  | ||||
|                 capabilities.editor = jasmine.createSpyObj("editorCapability", [ | ||||
|                     "isEditContextRoot", | ||||
|                     "finish" | ||||
|                 ]); | ||||
|  | ||||
|                 mockNavigatedObject.getCapability.andCallFake(function (capability) { | ||||
|                     return capabilities[capability]; | ||||
|                 }); | ||||
|                 mockNavigatedObject.hasCapability.andReturn(false); | ||||
|  | ||||
|                 mockNavigationService = jasmine.createSpyObj( | ||||
|                     "navigationService", | ||||
|                     [ | ||||
|                         "setNavigation", | ||||
|                         "getNavigation" | ||||
|                     ] | ||||
|                 ); | ||||
|                 mockNavigationService.getNavigation.andReturn(mockNavigatedObject); | ||||
|  | ||||
|                 mockDomainObject = jasmine.createSpyObj( | ||||
|                     "domainObject", | ||||
|                     [ | ||||
|                         "getId", | ||||
|                         "getModel" | ||||
|                     ] | ||||
|                 ); | ||||
|  | ||||
|                 mockPolicyService = jasmine.createSpyObj("policyService", | ||||
|                     [ | ||||
|                         "allow" | ||||
|                     ]); | ||||
|                 mockWindow = jasmine.createSpyObj("$window", | ||||
|                     [ | ||||
|                         "confirm" | ||||
|                     ]); | ||||
|  | ||||
|                 action = new NavigateAction( | ||||
|                     mockNavigationService, | ||||
|                     mockQ, | ||||
|                     mockPolicyService, | ||||
|                     mockWindow, | ||||
|                     { domainObject: mockDomainObject } | ||||
|                 ); | ||||
|             }); | ||||
|             return function () { | ||||
|                 called = true; | ||||
|             }; | ||||
|         } | ||||
|  | ||||
|         beforeEach(function () { | ||||
|             mockNavigationService = jasmine.createSpyObj( | ||||
|                 "navigationService", | ||||
|                 [ | ||||
|                     "shouldNavigate", | ||||
|                     "setNavigation" | ||||
|                 ] | ||||
|             ); | ||||
|  | ||||
|             mockDomainObject = {}; | ||||
|  | ||||
|             action = new NavigateAction( | ||||
|                 mockNavigationService, | ||||
|                 { domainObject: mockDomainObject } | ||||
|             ); | ||||
|         }); | ||||
|  | ||||
|         it("sets navigation if it is allowed", function () { | ||||
|             mockNavigationService.shouldNavigate.andReturn(true); | ||||
|             action.perform() | ||||
|                 .then(waitForCall()); | ||||
|             runs(function () { | ||||
|                 expect(mockNavigationService.setNavigation) | ||||
|                     .toHaveBeenCalledWith(mockDomainObject, true); | ||||
|             it("invokes the policy service to determine if navigation" + | ||||
|                 " allowed", function () { | ||||
|                 action.perform(); | ||||
|                 expect(mockPolicyService.allow) | ||||
|                     .toHaveBeenCalledWith("navigation", jasmine.any(Object), jasmine.any(Object), jasmine.any(Function)); | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         it("does not set navigation if it is not allowed", function () { | ||||
|             mockNavigationService.shouldNavigate.andReturn(false); | ||||
|             var onSuccess = jasmine.createSpy('onSuccess'); | ||||
|             action.perform() | ||||
|                 .then(onSuccess, waitForCall()); | ||||
|             runs(function () { | ||||
|                 expect(onSuccess).not.toHaveBeenCalled(); | ||||
|                 expect(mockNavigationService.setNavigation) | ||||
|                     .not | ||||
|                     .toHaveBeenCalledWith(mockDomainObject); | ||||
|             it("prompts user if policy rejection", function () { | ||||
|                 action.perform(); | ||||
|                 expect(mockPolicyService.allow).toHaveBeenCalled(); | ||||
|                 mockPolicyService.allow.mostRecentCall.args[3](); | ||||
|                 expect(mockWindow.confirm).toHaveBeenCalled(); | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         it("is only applicable when a domain object is in context", function () { | ||||
|             expect(NavigateAction.appliesTo({})).toBeFalsy(); | ||||
|             expect(NavigateAction.appliesTo({ | ||||
|                 domainObject: mockDomainObject | ||||
|             })).toBeTruthy(); | ||||
|         }); | ||||
|             describe("shows a prompt", function () { | ||||
|                 beforeEach(function () { | ||||
|                     // Ensure the allow callback is called synchronously | ||||
|                     mockPolicyService.allow.andCallFake(function () { | ||||
|                         return arguments[3](); | ||||
|                     }); | ||||
|                 }); | ||||
|                 it("does not navigate on prompt rejection", function () { | ||||
|                     mockWindow.confirm.andReturn(false); | ||||
|                     action.perform(); | ||||
|                     expect(mockNavigationService.setNavigation) | ||||
|                         .not.toHaveBeenCalled(); | ||||
|                 }); | ||||
|  | ||||
|     }); | ||||
| }); | ||||
|                 it("does navigate on prompt acceptance", function () { | ||||
|                     mockWindow.confirm.andReturn(true); | ||||
|                     action.perform(); | ||||
|                     expect(mockNavigationService.setNavigation) | ||||
|                         .toHaveBeenCalled(); | ||||
|                 }); | ||||
|             }); | ||||
|  | ||||
|             describe("in edit mode", function () { | ||||
|                 beforeEach(function () { | ||||
|                     mockNavigatedObject.hasCapability.andCallFake(function (capability) { | ||||
|                         return capability === "editor"; | ||||
|                     }); | ||||
|                     capabilities.editor.isEditContextRoot.andReturn(true); | ||||
|                 }); | ||||
|  | ||||
|                 it("finishes editing if in edit mode", function () { | ||||
|                     action.perform(); | ||||
|                     expect(capabilities.editor.finish) | ||||
|                         .toHaveBeenCalled(); | ||||
|                 }); | ||||
|             }); | ||||
|  | ||||
|             it("is only applicable when a domain object is in context", function () { | ||||
|                 expect(NavigateAction.appliesTo({})).toBeFalsy(); | ||||
|                 expect(NavigateAction.appliesTo({ | ||||
|                     domainObject: mockDomainObject | ||||
|                 })).toBeTruthy(); | ||||
|             }); | ||||
|  | ||||
|         }); | ||||
|     } | ||||
| ); | ||||
|   | ||||
| @@ -28,12 +28,10 @@ define( | ||||
|     function (NavigationService) { | ||||
|  | ||||
|         describe("The navigation service", function () { | ||||
|             var $window, | ||||
|                 navigationService; | ||||
|             var navigationService; | ||||
|  | ||||
|             beforeEach(function () { | ||||
|                 $window = jasmine.createSpyObj('$window', ['confirm']); | ||||
|                 navigationService = new NavigationService($window); | ||||
|                 navigationService = new NavigationService(); | ||||
|             }); | ||||
|  | ||||
|             it("stores navigation state", function () { | ||||
|   | ||||
| @@ -51,7 +51,7 @@ define( | ||||
|             }); | ||||
|  | ||||
|             it("provides displayable metadata", function () { | ||||
|                 expect(action.getMetadata().cssClass).toBeDefined(); | ||||
|                 expect(action.getMetadata().cssclass).toBeDefined(); | ||||
|             }); | ||||
|  | ||||
|         }); | ||||
|   | ||||
| @@ -53,8 +53,7 @@ define([ | ||||
|                     "depends": [ | ||||
|                         "overlayService", | ||||
|                         "$q", | ||||
|                         "$log", | ||||
|                         "$document" | ||||
|                         "$log" | ||||
|                     ] | ||||
|                 }, | ||||
|                 { | ||||
|   | ||||
| @@ -35,15 +35,11 @@ define( | ||||
|          * @memberof platform/commonUI/dialog | ||||
|          * @constructor | ||||
|          */ | ||||
|         function DialogService(overlayService, $q, $log, $document) { | ||||
|         function DialogService(overlayService, $q, $log) { | ||||
|             this.overlayService = overlayService; | ||||
|             this.$q = $q; | ||||
|             this.$log = $log; | ||||
|             this.activeOverlay = undefined; | ||||
|  | ||||
|             this.findBody = function () { | ||||
|                 return $document.find('body'); | ||||
|             }; | ||||
|         } | ||||
|  | ||||
|         /** | ||||
| @@ -64,8 +60,7 @@ define( | ||||
|             // input is asynchronous. | ||||
|             var deferred = this.$q.defer(), | ||||
|                 self = this, | ||||
|                 overlay, | ||||
|                 handleEscKeydown; | ||||
|                 overlay; | ||||
|  | ||||
|             // Confirm function; this will be passed in to the | ||||
|             // overlay-dialog template and associated with a | ||||
| @@ -81,22 +76,13 @@ define( | ||||
|             // Cancel or X button click | ||||
|             function cancel() { | ||||
|                 deferred.reject(); | ||||
|                 self.findBody().off('keydown', handleEscKeydown); | ||||
|                 self.dismissOverlay(overlay); | ||||
|             } | ||||
|  | ||||
|             handleEscKeydown = function (event) { | ||||
|                 if (event.keyCode === 27) { | ||||
|                     cancel(); | ||||
|                 } | ||||
|             }; | ||||
|  | ||||
|             // Add confirm/cancel callbacks | ||||
|             model.confirm = confirm; | ||||
|             model.cancel = cancel; | ||||
|  | ||||
|             this.findBody().on('keydown', handleEscKeydown); | ||||
|  | ||||
|             if (this.canShowDialog(model)) { | ||||
|                 // Add the overlay using the OverlayService, which | ||||
|                 // will handle actual insertion into the DOM | ||||
|   | ||||
| @@ -33,8 +33,6 @@ define( | ||||
|                 mockLog, | ||||
|                 mockOverlay, | ||||
|                 mockDeferred, | ||||
|                 mockDocument, | ||||
|                 mockBody, | ||||
|                 dialogService; | ||||
|  | ||||
|             beforeEach(function () { | ||||
| @@ -58,13 +56,6 @@ define( | ||||
|                     "deferred", | ||||
|                     ["resolve", "reject"] | ||||
|                 ); | ||||
|                 mockDocument = jasmine.createSpyObj( | ||||
|                   "$document", | ||||
|                   ["find"] | ||||
|                 ); | ||||
|                 mockBody = jasmine.createSpyObj('body', ['on', 'off']); | ||||
|                 mockDocument.find.andReturn(mockBody); | ||||
|  | ||||
|                 mockDeferred.promise = "mock promise"; | ||||
|  | ||||
|                 mockQ.defer.andReturn(mockDeferred); | ||||
| @@ -73,8 +64,7 @@ define( | ||||
|                 dialogService = new DialogService( | ||||
|                     mockOverlayService, | ||||
|                     mockQ, | ||||
|                     mockLog, | ||||
|                     mockDocument | ||||
|                     mockLog | ||||
|                 ); | ||||
|             }); | ||||
|  | ||||
| @@ -140,36 +130,6 @@ define( | ||||
|                 ); | ||||
|             }); | ||||
|  | ||||
|             it("adds a keydown event listener to the body", function () { | ||||
|                 dialogService.getUserInput({}, {}); | ||||
|                 expect(mockDocument.find).toHaveBeenCalledWith("body"); | ||||
|                 expect(mockBody.on).toHaveBeenCalledWith("keydown", jasmine.any(Function)); | ||||
|             }); | ||||
|  | ||||
|             it("destroys the event listener when the dialog is cancelled", function () { | ||||
|                 dialogService.getUserInput({}, {}); | ||||
|                 mockOverlayService.createOverlay.mostRecentCall.args[1].cancel(); | ||||
|                 expect(mockBody.off).toHaveBeenCalledWith("keydown", jasmine.any(Function)); | ||||
|             }); | ||||
|  | ||||
|             it("cancels the dialog when an escape keydown event is triggered", function () { | ||||
|                 dialogService.getUserInput({}, {}); | ||||
|                 mockBody.on.mostRecentCall.args[1]({ | ||||
|                     keyCode: 27 | ||||
|                 }); | ||||
|                 expect(mockDeferred.reject).toHaveBeenCalled(); | ||||
|                 expect(mockDeferred.resolve).not.toHaveBeenCalled(); | ||||
|             }); | ||||
|  | ||||
|             it("ignores non escape keydown events", function () { | ||||
|                 dialogService.getUserInput({}, {}); | ||||
|                 mockBody.on.mostRecentCall.args[1]({ | ||||
|                     keyCode: 13 | ||||
|                 }); | ||||
|                 expect(mockDeferred.reject).not.toHaveBeenCalled(); | ||||
|                 expect(mockDeferred.resolve).not.toHaveBeenCalled(); | ||||
|             }); | ||||
|  | ||||
|             describe("the blocking message dialog", function () { | ||||
|                 var dialogModel = {}; | ||||
|                 var dialogHandle; | ||||
|   | ||||
| @@ -2,6 +2,25 @@ Contains sources and resources associated with Edit mode. | ||||
|  | ||||
| # Extensions | ||||
|  | ||||
| ## Directives | ||||
|  | ||||
| This bundle introduces the `mct-before-unload` directive, primarily for | ||||
| internal use (to prompt the user to confirm navigation away from unsaved | ||||
| changes in Edit mode.) | ||||
|  | ||||
| The `mct-before-unload` directive is used as an attribute whose value is | ||||
| an Angular expression that is evaluated when navigation changes (either | ||||
| via browser-level changes, such as the refresh button, or changes to | ||||
| the Angular route, which happens when hitting the back button in Edit | ||||
| mode.) The result of this evaluation, when truthy, is shown in a browser | ||||
| dialog to allow the user to confirm navigation. When falsy, no prompt is | ||||
| shown, allowing these dialogs to be shown conditionally. (For instance, in | ||||
| Edit mode, prompts are only shown if user-initiated changes have | ||||
| occurred.) | ||||
|  | ||||
| This directive may be attached to any element; its behavior will be enforced | ||||
| so long as that element remains within the DOM. | ||||
|  | ||||
| # Toolbars | ||||
|  | ||||
| Views may specify the contents of a toolbar through a `toolbar` | ||||
|   | ||||
| @@ -25,6 +25,7 @@ define([ | ||||
|     "./src/controllers/EditPanesController", | ||||
|     "./src/controllers/ElementsController", | ||||
|     "./src/controllers/EditObjectController", | ||||
|     "./src/directives/MCTBeforeUnload", | ||||
|     "./src/actions/EditAndComposeAction", | ||||
|     "./src/actions/EditAction", | ||||
|     "./src/actions/PropertiesAction", | ||||
| @@ -36,6 +37,7 @@ define([ | ||||
|     "./src/policies/EditActionPolicy", | ||||
|     "./src/policies/EditableLinkPolicy", | ||||
|     "./src/policies/EditableMovePolicy", | ||||
|     "./src/policies/EditNavigationPolicy", | ||||
|     "./src/policies/EditContextualActionPolicy", | ||||
|     "./src/representers/EditRepresenter", | ||||
|     "./src/representers/EditToolbarRepresenter", | ||||
| @@ -63,6 +65,7 @@ define([ | ||||
|     EditPanesController, | ||||
|     ElementsController, | ||||
|     EditObjectController, | ||||
|     MCTBeforeUnload, | ||||
|     EditAndComposeAction, | ||||
|     EditAction, | ||||
|     PropertiesAction, | ||||
| @@ -74,6 +77,7 @@ define([ | ||||
|     EditActionPolicy, | ||||
|     EditableLinkPolicy, | ||||
|     EditableMovePolicy, | ||||
|     EditNavigationPolicy, | ||||
|     EditContextualActionPolicy, | ||||
|     EditRepresenter, | ||||
|     EditToolbarRepresenter, | ||||
| @@ -128,7 +132,7 @@ define([ | ||||
|                     "depends": [ | ||||
|                         "$scope", | ||||
|                         "$location", | ||||
|                         "navigationService" | ||||
|                         "policyService" | ||||
|                     ] | ||||
|                 }, | ||||
|                 { | ||||
| @@ -148,6 +152,15 @@ define([ | ||||
|                     ] | ||||
|                 } | ||||
|             ], | ||||
|             "directives": [ | ||||
|                 { | ||||
|                     "key": "mctBeforeUnload", | ||||
|                     "implementation": MCTBeforeUnload, | ||||
|                     "depends": [ | ||||
|                         "$window" | ||||
|                     ] | ||||
|                 } | ||||
|             ], | ||||
|             "actions": [ | ||||
|                 { | ||||
|                     "key": "compose", | ||||
| @@ -163,7 +176,7 @@ define([ | ||||
|                     ], | ||||
|                     "description": "Edit", | ||||
|                     "category": "view-control", | ||||
|                     "cssClass": "major icon-pencil" | ||||
|                     "cssclass": "major icon-pencil" | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "properties", | ||||
| @@ -172,7 +185,7 @@ define([ | ||||
|                         "view-control" | ||||
|                     ], | ||||
|                     "implementation": PropertiesAction, | ||||
|                     "cssClass": "major icon-pencil", | ||||
|                     "cssclass": "major icon-pencil", | ||||
|                     "name": "Edit Properties...", | ||||
|                     "description": "Edit properties of this object.", | ||||
|                     "depends": [ | ||||
| @@ -183,7 +196,7 @@ define([ | ||||
|                     "key": "remove", | ||||
|                     "category": "contextual", | ||||
|                     "implementation": RemoveAction, | ||||
|                     "cssClass": "icon-trash", | ||||
|                     "cssclass": "icon-trash", | ||||
|                     "name": "Remove", | ||||
|                     "description": "Remove this object from its containing object.", | ||||
|                     "depends": [ | ||||
| @@ -195,11 +208,10 @@ define([ | ||||
|                     "category": "save", | ||||
|                     "implementation": SaveAndStopEditingAction, | ||||
|                     "name": "Save and Finish Editing", | ||||
|                     "cssClass": "icon-save labeled", | ||||
|                     "cssclass": "icon-save labeled", | ||||
|                     "description": "Save changes made to these objects.", | ||||
|                     "depends": [ | ||||
|                         "dialogService", | ||||
|                         "notificationService" | ||||
|                         "dialogService" | ||||
|                     ] | ||||
|                 }, | ||||
|                 { | ||||
| @@ -207,11 +219,10 @@ define([ | ||||
|                     "category": "save", | ||||
|                     "implementation": SaveAction, | ||||
|                     "name": "Save and Continue Editing", | ||||
|                     "cssClass": "icon-save labeled", | ||||
|                     "cssclass": "icon-save labeled", | ||||
|                     "description": "Save changes made to these objects.", | ||||
|                     "depends": [ | ||||
|                         "dialogService", | ||||
|                         "notificationService" | ||||
|                         "dialogService" | ||||
|                     ] | ||||
|                 }, | ||||
|                 { | ||||
| @@ -219,14 +230,13 @@ define([ | ||||
|                     "category": "save", | ||||
|                     "implementation": SaveAsAction, | ||||
|                     "name": "Save As...", | ||||
|                     "cssClass": "icon-save labeled", | ||||
|                     "cssclass": "icon-save labeled", | ||||
|                     "description": "Save changes made to these objects.", | ||||
|                     "depends": [ | ||||
|                         "$injector", | ||||
|                         "policyService", | ||||
|                         "dialogService", | ||||
|                         "copyService", | ||||
|                         "notificationService" | ||||
|                         "copyService" | ||||
|                     ], | ||||
|                     "priority": "mandatory" | ||||
|                 }, | ||||
| @@ -237,7 +247,7 @@ define([ | ||||
|                     // Because we use the name as label for edit buttons and mct-control buttons need | ||||
|                     // the label to be set to undefined in order to not apply the labeled CSS rule. | ||||
|                     "name": undefined, | ||||
|                     "cssClass": "icon-x no-label", | ||||
|                     "cssclass": "icon-x no-label", | ||||
|                     "description": "Discard changes made to these objects.", | ||||
|                     "depends": [] | ||||
|                 } | ||||
| @@ -260,6 +270,11 @@ define([ | ||||
|                     "category": "action", | ||||
|                     "implementation": EditableLinkPolicy | ||||
|                 }, | ||||
|                 { | ||||
|                     "category": "navigation", | ||||
|                     "message": "Continuing will cause the loss of any unsaved changes.", | ||||
|                     "implementation": EditNavigationPolicy | ||||
|                 }, | ||||
|                 { | ||||
|                     "implementation": CreationPolicy, | ||||
|                     "category": "creation" | ||||
| @@ -374,6 +389,7 @@ define([ | ||||
|                 { | ||||
|                     "implementation": EditRepresenter, | ||||
|                     "depends": [ | ||||
|                         "$q", | ||||
|                         "$log" | ||||
|                     ] | ||||
|                 }, | ||||
|   | ||||
| @@ -25,14 +25,14 @@ | ||||
|             <li ng-repeat="createAction in createActions" ng-click="createAction.perform()"> | ||||
|                 <a ng-mouseover="representation.activeMetadata = createAction.getMetadata()" | ||||
|                    ng-mouseleave="representation.activeMetadata = undefined" | ||||
|                    class="menu-item-a {{ createAction.getMetadata().cssClass }}"> | ||||
|                    class="menu-item-a {{ createAction.getMetadata().cssclass }}"> | ||||
|                     {{createAction.getMetadata().name}} | ||||
|                 </a> | ||||
|             </li> | ||||
|         </ul> | ||||
|     </div> | ||||
|     <div class="pane right menu-item-description"> | ||||
|         <div class="desc-area icon {{ representation.activeMetadata.cssClass }}"></div> | ||||
|         <div class="desc-area icon {{ representation.activeMetadata.cssclass }}"></div> | ||||
|         <div class="desc-area title"> | ||||
|             {{representation.activeMetadata.name}} | ||||
|         </div> | ||||
|   | ||||
| @@ -26,7 +26,7 @@ | ||||
|                      structure="{ | ||||
|                         text: saveActions[0].getMetadata().name, | ||||
|                         click: actionPerformer(saveActions[0]), | ||||
|                         cssClass: 'major ' + saveActions[0].getMetadata().cssClass | ||||
|                         cssclass: 'major ' + saveActions[0].getMetadata().cssclass | ||||
|                      }"> | ||||
|         </mct-control> | ||||
|     </span> | ||||
| @@ -36,7 +36,7 @@ | ||||
|                      structure="{ | ||||
|                         options: saveActionsAsMenuOptions, | ||||
|                         click: saveActionMenuClickHandler, | ||||
|                         cssClass: 'btn-bar right icon-save no-label major' | ||||
|                         cssclass: 'btn-bar right icon-save no-label major' | ||||
|                      }"> | ||||
|         </mct-control> | ||||
|     </span> | ||||
| @@ -46,7 +46,7 @@ | ||||
|                      structure="{ | ||||
|                         text: currentAction.getMetadata().name, | ||||
|                         click: actionPerformer(currentAction), | ||||
|                         cssClass: currentAction.getMetadata().cssClass | ||||
|                         cssclass: currentAction.getMetadata().cssclass | ||||
|                      }"> | ||||
|         </mct-control> | ||||
|     </span> | ||||
|   | ||||
| @@ -20,7 +20,8 @@ | ||||
|  at runtime from the About dialog for additional information. | ||||
| --> | ||||
| <div class="abs l-flex-col" ng-controller="EditObjectController as EditObjectController"> | ||||
|     <div class="holder flex-elem l-flex-row object-browse-bar "> | ||||
|     <div mct-before-unload="EditObjectController.getUnloadWarning()" | ||||
|          class="holder flex-elem l-flex-row object-browse-bar "> | ||||
|         <div class="items-select left flex-elem l-flex-row grows"> | ||||
|             <mct-representation key="'back-arrow'" | ||||
|                                 mct-object="domainObject" | ||||
|   | ||||
| @@ -56,13 +56,13 @@ define( | ||||
|                     //navigate back to parent because nothing to show. | ||||
|                     return domainObject.getCapability("location").getOriginal().then(function (original) { | ||||
|                         parent = original.getCapability("context").getParent(); | ||||
|                         return parent.getCapability("action").perform("navigate"); | ||||
|                         parent.getCapability("action").perform("navigate"); | ||||
|                     }); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             function cancel() { | ||||
|                 return domainObject.getCapability("editor").finish(); | ||||
|             function cancel(allowed) { | ||||
|                 return allowed && domainObject.getCapability("editor").finish(); | ||||
|             } | ||||
|  | ||||
|             //Do navigation first in order to trigger unsaved changes dialog | ||||
|   | ||||
| @@ -33,12 +33,10 @@ define( | ||||
|          */ | ||||
|         function SaveAction( | ||||
|             dialogService, | ||||
|             notificationService, | ||||
|             context | ||||
|         ) { | ||||
|             this.domainObject = (context || {}).domainObject; | ||||
|             this.dialogService = dialogService; | ||||
|             this.notificationService = notificationService; | ||||
|         } | ||||
|  | ||||
|         /** | ||||
| @@ -49,8 +47,7 @@ define( | ||||
|          * @memberof platform/commonUI/edit.SaveAction# | ||||
|          */ | ||||
|         SaveAction.prototype.perform = function () { | ||||
|             var self = this, | ||||
|                 domainObject = this.domainObject, | ||||
|             var domainObject = this.domainObject, | ||||
|                 dialog = new SaveInProgressDialog(this.dialogService); | ||||
|  | ||||
|             // Invoke any save behavior introduced by the editor capability; | ||||
| @@ -61,21 +58,15 @@ define( | ||||
|                 return domainObject.getCapability("editor").save(); | ||||
|             } | ||||
|  | ||||
|             function onSuccess() { | ||||
|             function hideBlockingDialog() { | ||||
|                 dialog.hide(); | ||||
|                 self.notificationService.info("Save Succeeded"); | ||||
|             } | ||||
|  | ||||
|             function onFailure() { | ||||
|                 dialog.hide(); | ||||
|                 self.notificationService.error("Save Failed"); | ||||
|             } | ||||
|  | ||||
|             dialog.show(); | ||||
|  | ||||
|             return doSave() | ||||
|                 .then(onSuccess) | ||||
|                 .catch(onFailure); | ||||
|                 .then(hideBlockingDialog) | ||||
|                 .catch(hideBlockingDialog); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|   | ||||
| @@ -33,13 +33,11 @@ define( | ||||
|          */ | ||||
|         function SaveAndStopEditingAction( | ||||
|             dialogService, | ||||
|             notificationService, | ||||
|             context | ||||
|         ) { | ||||
|             this.context = context; | ||||
|             this.domainObject = (context || {}).domainObject; | ||||
|             this.dialogService = dialogService; | ||||
|             this.notificationService = notificationService; | ||||
|         } | ||||
|  | ||||
|         /** | ||||
| @@ -51,7 +49,7 @@ define( | ||||
|          */ | ||||
|         SaveAndStopEditingAction.prototype.perform = function () { | ||||
|             var domainObject = this.domainObject, | ||||
|                 saveAction = new SaveAction(this.dialogService, this.notificationService, this.context); | ||||
|                 saveAction = new SaveAction(this.dialogService, this.context); | ||||
|  | ||||
|             function closeEditor() { | ||||
|                 return domainObject.getCapability("editor").finish(); | ||||
|   | ||||
| @@ -43,7 +43,6 @@ define([ | ||||
|             policyService, | ||||
|             dialogService, | ||||
|             copyService, | ||||
|             notificationService, | ||||
|             context | ||||
|         ) { | ||||
|             this.domainObject = (context || {}).domainObject; | ||||
| @@ -53,7 +52,6 @@ define([ | ||||
|             this.policyService = policyService; | ||||
|             this.dialogService = dialogService; | ||||
|             this.copyService = copyService; | ||||
|             this.notificationService = notificationService; | ||||
|         } | ||||
|  | ||||
|         /** | ||||
| @@ -119,10 +117,8 @@ define([ | ||||
|  | ||||
|                 return self.dialogService | ||||
|                     .getUserInput(wizard.getFormStructure(true), | ||||
|                         wizard.getInitialFormValue()) | ||||
|                     .then(wizard.populateObjectFromInput.bind(wizard), function (failureReason) { | ||||
|                         return Promise.reject("user canceled"); | ||||
|                     }); | ||||
|                         wizard.getInitialFormValue() | ||||
|                     ).then(wizard.populateObjectFromInput.bind(wizard)); | ||||
|             } | ||||
|  | ||||
|             function showBlockingDialog(object) { | ||||
| @@ -180,16 +176,8 @@ define([ | ||||
|                     }); | ||||
|             } | ||||
|  | ||||
|             function onSuccess(object) { | ||||
|                 self.notificationService.info("Save Succeeded"); | ||||
|                 return object; | ||||
|             } | ||||
|  | ||||
|             function onFailure(reason) { | ||||
|             function onFailure() { | ||||
|                 hideBlockingDialog(); | ||||
|                 if (reason !== "user canceled") { | ||||
|                     self.notificationService.error("Save Failed"); | ||||
|                 } | ||||
|                 return false; | ||||
|             } | ||||
|  | ||||
| @@ -202,7 +190,6 @@ define([ | ||||
|                 .then(saveAfterClone) | ||||
|                 .then(finishEditing) | ||||
|                 .then(hideBlockingDialog) | ||||
|                 .then(onSuccess) | ||||
|                 .catch(onFailure); | ||||
|         }; | ||||
|  | ||||
|   | ||||
| @@ -48,10 +48,9 @@ define( | ||||
|          * Decorate PersistenceCapability to queue persistence calls when a | ||||
|          * transaction is in progress. | ||||
|          */ | ||||
|         TransactionCapabilityDecorator.prototype.getCapabilities = function () { | ||||
|         TransactionCapabilityDecorator.prototype.getCapabilities = function (model) { | ||||
|             var self = this, | ||||
|                 capabilities = this.capabilityService.getCapabilities | ||||
|                     .apply(this.capabilityService, arguments), | ||||
|                 capabilities = this.capabilityService.getCapabilities(model), | ||||
|                 persistenceCapability = capabilities.persistence; | ||||
|  | ||||
|             capabilities.persistence = function (domainObject) { | ||||
|   | ||||
| @@ -41,7 +41,7 @@ define( | ||||
|                 return { | ||||
|                     key: action, | ||||
|                     name: action.getMetadata().name, | ||||
|                     cssClass: action.getMetadata().cssClass | ||||
|                     cssclass: action.getMetadata().cssclass | ||||
|                 }; | ||||
|             } | ||||
|  | ||||
|   | ||||
| @@ -28,49 +28,18 @@ define( | ||||
|     [], | ||||
|     function () { | ||||
|  | ||||
|         function isDirty(domainObject) { | ||||
|             var navigatedObject = domainObject, | ||||
|                 editorCapability = navigatedObject && | ||||
|                     navigatedObject.getCapability("editor"); | ||||
|  | ||||
|             return editorCapability && | ||||
|                 editorCapability.isEditContextRoot() && | ||||
|                 editorCapability.dirty(); | ||||
|         } | ||||
|  | ||||
|         function cancelEditing(domainObject) { | ||||
|             var navigatedObject = domainObject, | ||||
|                 editorCapability = navigatedObject && | ||||
|                     navigatedObject.getCapability("editor"); | ||||
|  | ||||
|             return editorCapability && | ||||
|                 editorCapability.finish(); | ||||
|         } | ||||
|  | ||||
|         /** | ||||
|          * Controller which is responsible for populating the scope for | ||||
|          * Edit mode | ||||
|          * @memberof platform/commonUI/edit | ||||
|          * @constructor | ||||
|          */ | ||||
|         function EditObjectController($scope, $location, navigationService) { | ||||
|         function EditObjectController($scope, $location, policyService) { | ||||
|             this.scope = $scope; | ||||
|             var domainObject = $scope.domainObject; | ||||
|             this.policyService = policyService; | ||||
|  | ||||
|             var removeCheck = navigationService | ||||
|                 .checkBeforeNavigation(function () { | ||||
|                     if (isDirty(domainObject)) { | ||||
|                         return "Continuing will cause the loss of any unsaved changes."; | ||||
|                     } | ||||
|                     return false; | ||||
|                 }); | ||||
|  | ||||
|             $scope.$on('$destroy', function () { | ||||
|                 removeCheck(); | ||||
|                 cancelEditing(domainObject); | ||||
|             }); | ||||
|  | ||||
|             function setViewForDomainObject() { | ||||
|             var navigatedObject; | ||||
|             function setViewForDomainObject(domainObject) { | ||||
|  | ||||
|                 var locationViewKey = $location.search().view; | ||||
|  | ||||
| @@ -85,15 +54,34 @@ define( | ||||
|                     ((domainObject && domainObject.useCapability('view')) || []) | ||||
|                         .forEach(selectViewIfMatching); | ||||
|                 } | ||||
|                 navigatedObject = domainObject; | ||||
|             } | ||||
|  | ||||
|             setViewForDomainObject(); | ||||
|             $scope.$watch('domainObject', setViewForDomainObject); | ||||
|  | ||||
|             $scope.doAction = function (action) { | ||||
|                 return $scope[action] && $scope[action](); | ||||
|             }; | ||||
|         } | ||||
|  | ||||
|         /** | ||||
|          * Get the warning to show if the user attempts to navigate | ||||
|          * away from Edit mode while unsaved changes are present. | ||||
|          * @returns {string} the warning to show, or undefined if | ||||
|          *          there are no unsaved changes | ||||
|          */ | ||||
|         EditObjectController.prototype.getUnloadWarning = function () { | ||||
|             var navigatedObject = this.scope.domainObject, | ||||
|                 policyMessage; | ||||
|  | ||||
|             this.policyService.allow("navigation", navigatedObject, undefined, function (message) { | ||||
|                 policyMessage = message; | ||||
|             }); | ||||
|  | ||||
|             return policyMessage; | ||||
|  | ||||
|         }; | ||||
|  | ||||
|         return EditObjectController; | ||||
|     } | ||||
| ); | ||||
|   | ||||
| @@ -51,7 +51,7 @@ define( | ||||
|         function AddAction(type, parent, context, $q, dialogService, policyService) { | ||||
|             this.metadata = { | ||||
|                 key: 'add', | ||||
|                 cssClass: type.getCssClass(), | ||||
|                 cssclass: type.getCssClass(), | ||||
|                 name: type.getName(), | ||||
|                 type: type.getKey(), | ||||
|                 description: type.getDescription(), | ||||
|   | ||||
| @@ -66,7 +66,9 @@ define( | ||||
|             } | ||||
|  | ||||
|             // Introduce one create action per type | ||||
|             ['timeline', 'activity'].map(function (type) { | ||||
|             return this.typeService.listTypes().filter(function (type) { | ||||
|                 return self.policyService.allow("creation", type) && self.policyService.allow("composition", destination.getCapability('type'), type); | ||||
|             }).map(function (type) { | ||||
|                 return new AddAction( | ||||
|                     type, | ||||
|                     destination, | ||||
|   | ||||
| @@ -47,7 +47,7 @@ define( | ||||
|         function CreateAction(type, parent, context) { | ||||
|             this.metadata = { | ||||
|                 key: 'create', | ||||
|                 cssClass: type.getCssClass(), | ||||
|                 cssclass: type.getCssClass(), | ||||
|                 name: type.getName(), | ||||
|                 type: type.getKey(), | ||||
|                 description: type.getDescription(), | ||||
|   | ||||
| @@ -56,7 +56,7 @@ define( | ||||
|          */ | ||||
|         CreateWizard.prototype.getFormStructure = function (includeLocation) { | ||||
|             var sections = [], | ||||
|                 domainObject = this.domainObject, | ||||
|                 type = this.type, | ||||
|                 policyService = this.policyService; | ||||
|  | ||||
|             function validateLocation(locatingObject) { | ||||
| @@ -65,7 +65,7 @@ define( | ||||
|                 return locatingType && policyService.allow( | ||||
|                     "composition", | ||||
|                     locatingType, | ||||
|                     domainObject | ||||
|                     type | ||||
|                 ); | ||||
|             } | ||||
|  | ||||
| @@ -91,7 +91,7 @@ define( | ||||
|             if (includeLocation) { | ||||
|                 sections.push({ | ||||
|                     name: 'Location', | ||||
|                     cssClass: "grows", | ||||
|                     cssclass: "grows", | ||||
|                     rows: [{ | ||||
|                         name: "Save In", | ||||
|                         control: "locator", | ||||
| @@ -118,7 +118,7 @@ define( | ||||
|                 formModel = this.createModel(formValue); | ||||
|  | ||||
|             formModel.location = parent.getId(); | ||||
|             this.domainObject.useCapability("mutation", function (model) { | ||||
|             this.domainObject.useCapability("mutation", function () { | ||||
|                 return formModel; | ||||
|             }); | ||||
|             return this.domainObject; | ||||
|   | ||||
							
								
								
									
										104
									
								
								platform/commonUI/edit/src/directives/MCTBeforeUnload.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								platform/commonUI/edit/src/directives/MCTBeforeUnload.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,104 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2016, 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-before-unload` directive. The expression bound | ||||
|          * to this attribute will be evaluated during page navigation events | ||||
|          * and, if it returns a truthy value, will be used to populate a | ||||
|          * prompt to the user to confirm this navigation. | ||||
|          * @memberof platform/commonUI/edit | ||||
|          * @constructor | ||||
|          * @param $window the window | ||||
|          */ | ||||
|         function MCTBeforeUnload($window) { | ||||
|             var unloads = [], | ||||
|                 oldBeforeUnload = $window.onbeforeunload; | ||||
|  | ||||
|             // Run all unload functions, returning the first returns truthily. | ||||
|             function checkUnloads() { | ||||
|                 var result; | ||||
|                 unloads.forEach(function (unload) { | ||||
|                     result = result || unload(); | ||||
|                 }); | ||||
|                 return result; | ||||
|             } | ||||
|  | ||||
|             // Link function for an mct-before-unload directive usage | ||||
|             function link(scope, element, attrs) { | ||||
|                 // Invoke the | ||||
|                 function unload() { | ||||
|                     return scope.$eval(attrs.mctBeforeUnload); | ||||
|                 } | ||||
|  | ||||
|                 // Stop using this unload expression | ||||
|                 function removeUnload() { | ||||
|                     unloads = unloads.filter(function (callback) { | ||||
|                         return callback !== unload; | ||||
|                     }); | ||||
|                     if (unloads.length === 0) { | ||||
|                         $window.onbeforeunload = oldBeforeUnload; | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 // Show a dialog before allowing a location change | ||||
|                 function checkLocationChange(event) { | ||||
|                     // Get an unload message (if any) | ||||
|                     var warning = unload(); | ||||
|                     // Prompt the user if there's an unload message | ||||
|                     if (warning && !$window.confirm(warning)) { | ||||
|                         // ...and prevent the route change if it was confirmed | ||||
|                         event.preventDefault(); | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 // If this is the first active instance of this directive, | ||||
|                 // register as the window's beforeunload handler | ||||
|                 if (unloads.length === 0) { | ||||
|                     $window.onbeforeunload = checkUnloads; | ||||
|                 } | ||||
|  | ||||
|                 // Include this instance of the directive's unload function | ||||
|                 unloads.push(unload); | ||||
|  | ||||
|                 // Remove it when the scope is destroyed | ||||
|                 scope.$on("$destroy", removeUnload); | ||||
|  | ||||
|                 // Also handle route changes | ||||
|                 scope.$on("$locationChangeStart", checkLocationChange); | ||||
|             } | ||||
|  | ||||
|             return { | ||||
|                 // Applicable as an attribute | ||||
|                 restrict: "A", | ||||
|                 // Link with the provided function | ||||
|                 link: link | ||||
|             }; | ||||
|         } | ||||
|  | ||||
|         return MCTBeforeUnload; | ||||
|  | ||||
|     } | ||||
| ); | ||||
							
								
								
									
										64
									
								
								platform/commonUI/edit/src/policies/EditNavigationPolicy.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								platform/commonUI/edit/src/policies/EditNavigationPolicy.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2016, 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 () { | ||||
|  | ||||
|         /** | ||||
|          * Policy controlling whether navigation events should proceed | ||||
|          * when object is being edited. | ||||
|          * @memberof platform/commonUI/edit | ||||
|          * @constructor | ||||
|          * @implements {Policy.<Action, ActionContext>} | ||||
|          */ | ||||
|         function EditNavigationPolicy(policyService) { | ||||
|             this.policyService = policyService; | ||||
|         } | ||||
|  | ||||
|         /** | ||||
|          * @private | ||||
|          */ | ||||
|         EditNavigationPolicy.prototype.isDirty = function (domainObject) { | ||||
|             var navigatedObject = domainObject, | ||||
|                 editorCapability = navigatedObject && | ||||
|                     navigatedObject.getCapability("editor"); | ||||
|  | ||||
|             return editorCapability && | ||||
|                 editorCapability.isEditContextRoot() && | ||||
|                 editorCapability.dirty(); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Allow navigation if an object is not dirty, or if the user elects | ||||
|          * to proceed anyway. | ||||
|          * @param currentNavigation | ||||
|          * @returns {boolean|*} true if the object model is clean; or if | ||||
|          * it's dirty and the user wishes to proceed anyway. | ||||
|          */ | ||||
|         EditNavigationPolicy.prototype.allow = function (currentNavigation) { | ||||
|             return !this.isDirty(currentNavigation); | ||||
|         }; | ||||
|  | ||||
|         return EditNavigationPolicy; | ||||
|     } | ||||
| ); | ||||
| @@ -43,55 +43,98 @@ define( | ||||
|          * @implements {Representer} | ||||
|          * @constructor | ||||
|          */ | ||||
|         function EditRepresenter($log, $scope) { | ||||
|             this.$log = $log; | ||||
|             this.$scope = $scope; | ||||
|         function EditRepresenter($q, $log, scope) { | ||||
|             var self = this; | ||||
|  | ||||
|             this.scope = scope; | ||||
|             this.listenHandle = undefined; | ||||
|  | ||||
|             // Mutate and persist a new version of a domain object's model. | ||||
|             function doMutate(model) { | ||||
|                 var domainObject = self.domainObject; | ||||
|  | ||||
|                 // First, mutate; then, persist. | ||||
|                 return $q.when(domainObject.useCapability("mutation", function () { | ||||
|                     return model; | ||||
|                 })); | ||||
|             } | ||||
|  | ||||
|             // Handle changes to model and/or view configuration | ||||
|             function commit(message) { | ||||
|                 // Look up from scope; these will have been populated by | ||||
|                 // mct-representation. | ||||
|                 var model = scope.model, | ||||
|                     configuration = scope.configuration, | ||||
|                     domainObject = self.domainObject; | ||||
|  | ||||
|                 // Log the commit message | ||||
|                 $log.debug([ | ||||
|                     "Committing ", | ||||
|                     domainObject && domainObject.getModel().name, | ||||
|                     "(" + (domainObject && domainObject.getId()) + "):", | ||||
|                     message | ||||
|                 ].join(" ")); | ||||
|  | ||||
|                 // Update the configuration stored in the model, and persist. | ||||
|                 if (domainObject) { | ||||
|                     // Configurations for specific views are stored by | ||||
|                     // key in the "configuration" field of the model. | ||||
|                     if (self.key && configuration) { | ||||
|                         model.configuration = model.configuration || {}; | ||||
|                         model.configuration[self.key] = configuration; | ||||
|                     } | ||||
|                     doMutate(model); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             // Place the "commit" method in the scope | ||||
|             scope.commit = commit; | ||||
|  | ||||
|             // Clean up when the scope is destroyed | ||||
|             scope.$on("$destroy", function () { | ||||
|                 self.destroy(); | ||||
|             }); | ||||
|  | ||||
|             this.$scope.commit = this.commit.bind(this); | ||||
|         } | ||||
|  | ||||
|         /** | ||||
|          * Commit any changes made to the in-scope model to the domain object. | ||||
|          * Also commits any changes made to $scope.configuration to the proper | ||||
|          * configuration value for the current representation. | ||||
|          * | ||||
|          * @param {String} message a message to log with the commit message. | ||||
|          */ | ||||
|         EditRepresenter.prototype.commit = function (message) { | ||||
|             var model = this.$scope.model, | ||||
|                 configuration = this.$scope.configuration, | ||||
|                 domainObject = this.domainObject; | ||||
|  | ||||
|             this.$log.debug([ | ||||
|                 "Committing ", | ||||
|                 domainObject && domainObject.getModel().name, | ||||
|                 "(" + (domainObject && domainObject.getId()) + "):", | ||||
|                 message | ||||
|             ].join(" ")); | ||||
|  | ||||
|             if (this.domainObject) { | ||||
|                 if (this.key && configuration) { | ||||
|                     model.configuration = model.configuration || {}; | ||||
|                     model.configuration[this.key] = configuration; | ||||
|                 } | ||||
|                 domainObject.useCapability('mutation', function () { | ||||
|                     return model; | ||||
|                 }); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         // Handle a specific representation of a specific domain object | ||||
|         EditRepresenter.prototype.represent = function (representation, representedObject) { | ||||
|         EditRepresenter.prototype.represent = function represent(representation, representedObject) { | ||||
|             var scope = this.scope; | ||||
|  | ||||
|             // Track the key, to know which view configuration to save to. | ||||
|             this.key = (representation || {}).key; | ||||
|             // Track the represented object | ||||
|             this.domainObject = representedObject; | ||||
|             if (representation) { | ||||
|                 this.key = representation.key; | ||||
|             } else { | ||||
|                 delete this.key; | ||||
|  | ||||
|             // Ensure existing watches are released | ||||
|             this.destroy(); | ||||
|  | ||||
|             function setEditing() { | ||||
|                 scope.viewObjectTemplate = 'edit-object'; | ||||
|             } | ||||
|  | ||||
|             /** | ||||
|              * Listen for changes in object state. If the object becomes | ||||
|              * editable then change the view and inspector regions | ||||
|              * object representation accordingly | ||||
|              */ | ||||
|             this.listenHandle = this.domainObject.getCapability('status').listen(function (statuses) { | ||||
|                 if (statuses.indexOf('editing') !== -1) { | ||||
|                     setEditing(); | ||||
|                 } else { | ||||
|                     delete scope.viewObjectTemplate; | ||||
|                 } | ||||
|             }); | ||||
|  | ||||
|             if (representedObject.hasCapability('editor') && representedObject.getCapability('editor').isEditContextRoot()) { | ||||
|                 setEditing(); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         // Respond to the destruction of the current representation. | ||||
|         EditRepresenter.prototype.destroy = function () {}; | ||||
|         EditRepresenter.prototype.destroy = function destroy() { | ||||
|             return this.listenHandle && this.listenHandle(); | ||||
|         }; | ||||
|  | ||||
|         return EditRepresenter; | ||||
|     } | ||||
|   | ||||
| @@ -19,7 +19,6 @@ | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
| /*global describe,it,expect,beforeEach,jasmine,waitsFor,runs*/ | ||||
|  | ||||
| define( | ||||
|     ["../../src/actions/SaveAction"], | ||||
| @@ -29,8 +28,7 @@ define( | ||||
|             var mockDomainObject, | ||||
|                 mockEditorCapability, | ||||
|                 actionContext, | ||||
|                 mockDialogService, | ||||
|                 mockNotificationService, | ||||
|                 dialogService, | ||||
|                 mockActionCapability, | ||||
|                 capabilities = {}, | ||||
|                 action; | ||||
| @@ -70,17 +68,11 @@ define( | ||||
|                 actionContext = { | ||||
|                     domainObject: mockDomainObject | ||||
|                 }; | ||||
|  | ||||
|                 mockDialogService = jasmine.createSpyObj( | ||||
|                 dialogService = jasmine.createSpyObj( | ||||
|                     "dialogService", | ||||
|                     ["showBlockingMessage"] | ||||
|                 ); | ||||
|  | ||||
|                 mockNotificationService = jasmine.createSpyObj( | ||||
|                     "notificationService", | ||||
|                     ["info", "error"] | ||||
|                 ); | ||||
|  | ||||
|                 mockDomainObject.hasCapability.andReturn(true); | ||||
|                 mockDomainObject.getCapability.andCallFake(function (capability) { | ||||
|                     return capabilities[capability]; | ||||
| @@ -89,7 +81,7 @@ define( | ||||
|                 mockEditorCapability.save.andReturn(mockPromise(true)); | ||||
|                 mockEditorCapability.isEditContextRoot.andReturn(true); | ||||
|  | ||||
|                 action = new SaveAction(mockDialogService, mockNotificationService, actionContext); | ||||
|                 action = new SaveAction(dialogService, actionContext); | ||||
|             }); | ||||
|  | ||||
|             it("only applies to domain object with an editor capability", function () { | ||||
| @@ -113,54 +105,30 @@ define( | ||||
|                     expect(mockEditorCapability.save).toHaveBeenCalled(); | ||||
|                 }); | ||||
|  | ||||
|             describe("in order to keep the user in the loop", function () { | ||||
|             describe("a blocking dialog", function () { | ||||
|                 var mockDialogHandle; | ||||
|  | ||||
|                 beforeEach(function () { | ||||
|                     mockDialogHandle = jasmine.createSpyObj("dialogHandle", ["dismiss"]); | ||||
|                     mockDialogService.showBlockingMessage.andReturn(mockDialogHandle); | ||||
|                     dialogService.showBlockingMessage.andReturn(mockDialogHandle); | ||||
|                 }); | ||||
|  | ||||
|  | ||||
|                 it("shows a dialog while saving", function () { | ||||
|                     mockEditorCapability.save.andReturn(new Promise(function () { | ||||
|                     })); | ||||
|                     action.perform(); | ||||
|                     expect(mockDialogService.showBlockingMessage).toHaveBeenCalled(); | ||||
|                     expect(dialogService.showBlockingMessage).toHaveBeenCalled(); | ||||
|                     expect(mockDialogHandle.dismiss).not.toHaveBeenCalled(); | ||||
|                 }); | ||||
|  | ||||
|                 it("hides the dialog when saving is complete", function () { | ||||
|                 it("hides a dialog when saving is complete", function () { | ||||
|                     action.perform(); | ||||
|                     expect(mockDialogService.showBlockingMessage).toHaveBeenCalled(); | ||||
|                     expect(dialogService.showBlockingMessage).toHaveBeenCalled(); | ||||
|                     expect(mockDialogHandle.dismiss).toHaveBeenCalled(); | ||||
|                 }); | ||||
|  | ||||
|                 it("notifies if saving succeeded", function () { | ||||
|                     var mockCallback = jasmine.createSpy("callback"); | ||||
|                     mockEditorCapability.save.andReturn(Promise.resolve("success")); | ||||
|                     action.perform().then(mockCallback); | ||||
|                     waitsFor(function () { | ||||
|                         return mockCallback.calls.length > 0; | ||||
|                     }); | ||||
|                     runs(function () { | ||||
|                         expect(mockNotificationService.info).toHaveBeenCalled(); | ||||
|                         expect(mockNotificationService.error).not.toHaveBeenCalled(); | ||||
|                     }); | ||||
|                 }); | ||||
|  | ||||
|                 it("notifies if saving failed", function () { | ||||
|                     var mockCallback = jasmine.createSpy("callback"); | ||||
|                     mockEditorCapability.save.andReturn(Promise.reject("some failure reason")); | ||||
|                     action.perform().then(mockCallback); | ||||
|                     waitsFor(function () { | ||||
|                         return mockCallback.calls.length > 0; | ||||
|                     }); | ||||
|                     runs(function () { | ||||
|                         expect(mockNotificationService.error).toHaveBeenCalled(); | ||||
|                         expect(mockNotificationService.info).not.toHaveBeenCalled(); | ||||
|                     }); | ||||
|                 }); | ||||
|             }); | ||||
|  | ||||
|         }); | ||||
|     } | ||||
| ); | ||||
|   | ||||
| @@ -19,7 +19,6 @@ | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
| /*global describe,it,expect,beforeEach,jasmine*/ | ||||
|  | ||||
| define( | ||||
|     ["../../src/actions/SaveAndStopEditingAction"], | ||||
| @@ -36,7 +35,6 @@ define( | ||||
|                 mockEditorCapability, | ||||
|                 actionContext, | ||||
|                 dialogService, | ||||
|                 notificationService, | ||||
|                 mockActionCapability, | ||||
|                 capabilities = {}, | ||||
|                 action; | ||||
| @@ -81,11 +79,6 @@ define( | ||||
|                     ["showBlockingMessage"] | ||||
|                 ); | ||||
|  | ||||
|                 notificationService = jasmine.createSpyObj( | ||||
|                     "notificationService", | ||||
|                     ["info", "error"] | ||||
|                 ); | ||||
|  | ||||
|                 mockDomainObject.hasCapability.andReturn(true); | ||||
|                 mockDomainObject.getCapability.andCallFake(function (capability) { | ||||
|                     return capabilities[capability]; | ||||
| @@ -94,7 +87,7 @@ define( | ||||
|                 mockEditorCapability.save.andReturn(mockPromise(true)); | ||||
|                 mockEditorCapability.isEditContextRoot.andReturn(true); | ||||
|  | ||||
|                 action = new SaveAndStopEditingAction(dialogService, notificationService, actionContext); | ||||
|                 action = new SaveAndStopEditingAction(dialogService, actionContext); | ||||
|             }); | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -27,13 +27,11 @@ define( | ||||
|  | ||||
|         describe("The Save As action", function () { | ||||
|             var mockDomainObject, | ||||
|                 mockClonedObject, | ||||
|                 mockEditorCapability, | ||||
|                 mockActionCapability, | ||||
|                 mockObjectService, | ||||
|                 mockDialogService, | ||||
|                 mockCopyService, | ||||
|                 mockNotificationService, | ||||
|                 mockParent, | ||||
|                 actionContext, | ||||
|                 capabilities = {}, | ||||
| @@ -59,8 +57,7 @@ define( | ||||
|                     [ | ||||
|                         "getCapability", | ||||
|                         "hasCapability", | ||||
|                         "getModel", | ||||
|                         "getId" | ||||
|                         "getModel" | ||||
|                     ] | ||||
|                 ); | ||||
|                 mockDomainObject.hasCapability.andReturn(true); | ||||
| @@ -68,15 +65,6 @@ define( | ||||
|                     return capabilities[capability]; | ||||
|                 }); | ||||
|                 mockDomainObject.getModel.andReturn({location: 'a', persisted: undefined}); | ||||
|                 mockDomainObject.getId.andReturn(0); | ||||
|  | ||||
|                 mockClonedObject = jasmine.createSpyObj( | ||||
|                     "clonedObject", | ||||
|                     [ | ||||
|                         "getId" | ||||
|                     ] | ||||
|                 ); | ||||
|                 mockClonedObject.getId.andReturn(1); | ||||
|  | ||||
|                 mockParent = jasmine.createSpyObj( | ||||
|                     "parentObject", | ||||
| @@ -123,27 +111,12 @@ define( | ||||
|                         "perform" | ||||
|                     ] | ||||
|                 ); | ||||
|                 mockCopyService.perform.andReturn(mockPromise(mockClonedObject)); | ||||
|  | ||||
|                 mockNotificationService = jasmine.createSpyObj( | ||||
|                     "notificationService", | ||||
|                     [ | ||||
|                         "info", | ||||
|                         "error" | ||||
|                     ] | ||||
|                 ); | ||||
|  | ||||
|                 actionContext = { | ||||
|                     domainObject: mockDomainObject | ||||
|                 }; | ||||
|  | ||||
|                 action = new SaveAsAction( | ||||
|                     undefined, | ||||
|                     undefined, | ||||
|                     mockDialogService, | ||||
|                     mockCopyService, | ||||
|                     mockNotificationService, | ||||
|                     actionContext); | ||||
|                 action = new SaveAsAction(undefined, undefined, mockDialogService, mockCopyService, actionContext); | ||||
|  | ||||
|                 spyOn(action, "getObjectService"); | ||||
|                 action.getObjectService.andReturn(mockObjectService); | ||||
| @@ -213,7 +186,7 @@ define( | ||||
|                 expect(mockDialogService.getUserInput).toHaveBeenCalled(); | ||||
|             }); | ||||
|  | ||||
|             describe("in order to keep the user in the loop", function () { | ||||
|             describe("a blocking dialog", function () { | ||||
|                 var mockDialogHandle; | ||||
|  | ||||
|                 beforeEach(function () { | ||||
| @@ -221,14 +194,14 @@ define( | ||||
|                     mockDialogService.showBlockingMessage.andReturn(mockDialogHandle); | ||||
|                 }); | ||||
|  | ||||
|                 it("shows a blocking dialog indicating that saving is in progress", function () { | ||||
|                 it("indicates that a save is taking place", function () { | ||||
|                     mockEditorCapability.save.andReturn(new Promise(function () {})); | ||||
|                     action.perform(); | ||||
|                     expect(mockDialogService.showBlockingMessage).toHaveBeenCalled(); | ||||
|                     expect(mockDialogHandle.dismiss).not.toHaveBeenCalled(); | ||||
|                 }); | ||||
|  | ||||
|                 it("hides the blocking dialog after saving finishes", function () { | ||||
|                 it("is hidden after saving", function () { | ||||
|                     var mockCallback = jasmine.createSpy(); | ||||
|                     action.perform().then(mockCallback); | ||||
|                     expect(mockDialogService.showBlockingMessage).toHaveBeenCalled(); | ||||
| @@ -239,31 +212,6 @@ define( | ||||
|                         expect(mockDialogHandle.dismiss).toHaveBeenCalled(); | ||||
|                     }); | ||||
|                 }); | ||||
|  | ||||
|                 it("notifies if saving succeeded", function () { | ||||
|                     var mockCallback = jasmine.createSpy(); | ||||
|                     action.perform().then(mockCallback); | ||||
|                     waitsFor(function () { | ||||
|                         return mockCallback.calls.length > 0; | ||||
|                     }); | ||||
|                     runs(function () { | ||||
|                         expect(mockNotificationService.info).toHaveBeenCalled(); | ||||
|                         expect(mockNotificationService.error).not.toHaveBeenCalled(); | ||||
|                     }); | ||||
|                 }); | ||||
|  | ||||
|                 it("notifies if saving failed", function () { | ||||
|                     mockCopyService.perform.andReturn(Promise.reject("some failure reason")); | ||||
|                     var mockCallback = jasmine.createSpy(); | ||||
|                     action.perform().then(mockCallback); | ||||
|                     waitsFor(function () { | ||||
|                         return mockCallback.calls.length > 0; | ||||
|                     }); | ||||
|                     runs(function () { | ||||
|                         expect(mockNotificationService.error).toHaveBeenCalled(); | ||||
|                         expect(mockNotificationService.info).not.toHaveBeenCalled(); | ||||
|                     }); | ||||
|                 }); | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
|   | ||||
| @@ -28,7 +28,7 @@ define( | ||||
|         describe("The Edit Action controller", function () { | ||||
|             var mockSaveActionMetadata = { | ||||
|                 name: "mocked-save-action", | ||||
|                 cssClass: "mocked-save-action-css" | ||||
|                 cssclass: "mocked-save-action-css" | ||||
|             }; | ||||
|  | ||||
|             function fakeGetActions(actionContext) { | ||||
| @@ -86,7 +86,7 @@ define( | ||||
|                 expect(menuOptions[1].key).toEqual(mockScope.saveActions[1]); | ||||
|                 menuOptions.forEach(function (option) { | ||||
|                     expect(option.name).toEqual(mockSaveActionMetadata.name); | ||||
|                     expect(option.cssClass).toEqual(mockSaveActionMetadata.cssClass); | ||||
|                     expect(option.cssclass).toEqual(mockSaveActionMetadata.cssclass); | ||||
|                 }); | ||||
|             }); | ||||
|  | ||||
|   | ||||
| @@ -24,19 +24,32 @@ define( | ||||
|     ["../../src/controllers/EditObjectController"], | ||||
|     function (EditObjectController) { | ||||
| 
 | ||||
|         describe("The Edit Object controller", function () { | ||||
|         describe("The Edit mode controller", function () { | ||||
|             var mockScope, | ||||
|                 mockObject, | ||||
|                 testViews, | ||||
|                 mockEditorCapability, | ||||
|                 mockType, | ||||
|                 mockLocation, | ||||
|                 mockNavigationService, | ||||
|                 removeCheck, | ||||
|                 mockStatusCapability, | ||||
|                 mockCapabilities, | ||||
|                 mockPolicyService, | ||||
|                 controller; | ||||
| 
 | ||||
|             // Utility function; look for a $watch on scope and fire it
 | ||||
|             function fireWatch(expr, value) { | ||||
|                 mockScope.$watch.calls.forEach(function (call) { | ||||
|                     if (call.args[0] === expr) { | ||||
|                         call.args[1](value); | ||||
|                     } | ||||
|                 }); | ||||
|             } | ||||
| 
 | ||||
|             beforeEach(function () { | ||||
|                 mockPolicyService = jasmine.createSpyObj( | ||||
|                     "policyService", | ||||
|                     [ | ||||
|                         "allow" | ||||
|                     ] | ||||
|                 ); | ||||
|                 mockScope = jasmine.createSpyObj( | ||||
|                     "$scope", | ||||
|                     ["$on", "$watch"] | ||||
| @@ -45,16 +58,16 @@ define( | ||||
|                     "domainObject", | ||||
|                     ["getId", "getModel", "getCapability", "hasCapability", "useCapability"] | ||||
|                 ); | ||||
|                 mockEditorCapability = jasmine.createSpyObj( | ||||
|                     "mockEditorCapability", | ||||
|                     ["isEditContextRoot", "dirty", "finish"] | ||||
|                 mockType = jasmine.createSpyObj( | ||||
|                     "type", | ||||
|                     ["hasFeature"] | ||||
|                 ); | ||||
|                 mockStatusCapability = jasmine.createSpyObj('statusCapability', | ||||
|                     ["get"] | ||||
|                 ); | ||||
| 
 | ||||
|                 mockCapabilities = { | ||||
|                     "editor" : mockEditorCapability, | ||||
|                     "type" : mockType, | ||||
|                     "status": mockStatusCapability | ||||
|                 }; | ||||
| 
 | ||||
| @@ -62,70 +75,52 @@ define( | ||||
|                     ["search"] | ||||
|                 ); | ||||
|                 mockLocation.search.andReturn({"view": "fixed"}); | ||||
|                 mockNavigationService = jasmine.createSpyObj('navigationService', | ||||
|                     ["checkBeforeNavigation"] | ||||
|                 ); | ||||
| 
 | ||||
|                 removeCheck = jasmine.createSpy('removeCheck'); | ||||
|                 mockNavigationService.checkBeforeNavigation.andReturn(removeCheck); | ||||
| 
 | ||||
|                 mockObject.getId.andReturn("test"); | ||||
|                 mockObject.getModel.andReturn({ name: "Test object" }); | ||||
|                 mockObject.getCapability.andCallFake(function (key) { | ||||
|                     return mockCapabilities[key]; | ||||
|                 }); | ||||
| 
 | ||||
|                 testViews = [ | ||||
|                     { key: 'abc' }, | ||||
|                     { key: 'def', someKey: 'some value' }, | ||||
|                     { key: 'xyz' } | ||||
|                 ]; | ||||
| 
 | ||||
|                 mockObject.useCapability.andCallFake(function (c) { | ||||
|                     return (c === 'view') && testViews; | ||||
|                 }); | ||||
|                 mockLocation.search.andReturn({ view: 'def' }); | ||||
|                 mockType.hasFeature.andReturn(true); | ||||
| 
 | ||||
|                 mockScope.domainObject = mockObject; | ||||
| 
 | ||||
|                 controller = new EditObjectController( | ||||
|                     mockScope, | ||||
|                     mockLocation, | ||||
|                     mockNavigationService | ||||
|                     mockPolicyService | ||||
|                 ); | ||||
|             }); | ||||
| 
 | ||||
|             it("adds a check before navigation", function () { | ||||
|                 expect(mockNavigationService.checkBeforeNavigation) | ||||
|                     .toHaveBeenCalledWith(jasmine.any(Function)); | ||||
|             it("exposes a warning message for unload", function () { | ||||
|                 var errorMessage = "Unsaved changes"; | ||||
| 
 | ||||
|                 var checkFn = mockNavigationService.checkBeforeNavigation.mostRecentCall.args[0]; | ||||
|                 // Normally, should be undefined
 | ||||
|                 expect(controller.getUnloadWarning()).toBeUndefined(); | ||||
| 
 | ||||
|                 mockEditorCapability.isEditContextRoot.andReturn(false); | ||||
|                 mockEditorCapability.dirty.andReturn(false); | ||||
| 
 | ||||
|                 expect(checkFn()).toBe(false); | ||||
| 
 | ||||
|                 mockEditorCapability.isEditContextRoot.andReturn(true); | ||||
|                 expect(checkFn()).toBe(false); | ||||
| 
 | ||||
|                 mockEditorCapability.dirty.andReturn(true); | ||||
|                 expect(checkFn()) | ||||
|                     .toBe("Continuing will cause the loss of any unsaved changes."); | ||||
|                 // Override the policy service to prevent navigation
 | ||||
|                 mockPolicyService.allow.andCallFake(function (category, object, context, callback) { | ||||
|                     callback(errorMessage); | ||||
|                 }); | ||||
| 
 | ||||
|                 // Should have some warning message here now
 | ||||
|                 expect(controller.getUnloadWarning()).toEqual(errorMessage); | ||||
|             }); | ||||
| 
 | ||||
|             it("cleans up on destroy", function () { | ||||
|                 expect(mockScope.$on) | ||||
|                     .toHaveBeenCalledWith("$destroy", jasmine.any(Function)); | ||||
| 
 | ||||
|                 mockScope.$on.mostRecentCall.args[1](); | ||||
| 
 | ||||
|                 expect(mockEditorCapability.finish).toHaveBeenCalled(); | ||||
|                 expect(removeCheck).toHaveBeenCalled(); | ||||
|             }); | ||||
| 
 | ||||
|             it("sets the active view from query parameters", function () { | ||||
|                 var testViews = [ | ||||
|                         { key: 'abc' }, | ||||
|                         { key: 'def', someKey: 'some value' }, | ||||
|                         { key: 'xyz' } | ||||
|                     ]; | ||||
| 
 | ||||
|                 mockObject.useCapability.andCallFake(function (c) { | ||||
|                     return (c === 'view') && testViews; | ||||
|                 }); | ||||
|                 mockLocation.search.andReturn({ view: 'def' }); | ||||
| 
 | ||||
|                 fireWatch('domainObject', mockObject); | ||||
|                 expect(mockScope.representation.selected) | ||||
|                     .toEqual(testViews[1]); | ||||
|             }); | ||||
| @@ -138,7 +138,7 @@ define( | ||||
|  | ||||
|                 expect(metadata.name).toEqual("Test"); | ||||
|                 expect(metadata.description).toEqual("a test type"); | ||||
|                 expect(metadata.cssClass).toEqual("icon-telemetry"); | ||||
|                 expect(metadata.cssclass).toEqual("icon-telemetry"); | ||||
|             }); | ||||
|  | ||||
|             describe("the perform function", function () { | ||||
|   | ||||
							
								
								
									
										114
									
								
								platform/commonUI/edit/test/directives/MCTBeforeUnloadSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								platform/commonUI/edit/test/directives/MCTBeforeUnloadSpec.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,114 @@ | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2016, 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/MCTBeforeUnload"], | ||||
|     function (MCTBeforeUnload) { | ||||
|  | ||||
|         describe("The mct-before-unload directive", function () { | ||||
|             var mockWindow, | ||||
|                 mockScope, | ||||
|                 testAttrs, | ||||
|                 mockEvent, | ||||
|                 directive; | ||||
|  | ||||
|             function fireListener(eventType, value) { | ||||
|                 mockScope.$on.calls.forEach(function (call) { | ||||
|                     if (call.args[0] === eventType) { | ||||
|                         call.args[1](value); | ||||
|                     } | ||||
|                 }); | ||||
|             } | ||||
|  | ||||
|             beforeEach(function () { | ||||
|                 mockWindow = jasmine.createSpyObj("$window", ['confirm']); | ||||
|                 mockScope = jasmine.createSpyObj("$scope", ['$eval', '$on']); | ||||
|                 testAttrs = { mctBeforeUnload: "someExpression" }; | ||||
|                 mockEvent = jasmine.createSpyObj("event", ["preventDefault"]); | ||||
|                 directive = new MCTBeforeUnload(mockWindow); | ||||
|                 directive.link(mockScope, {}, testAttrs); | ||||
|             }); | ||||
|  | ||||
|             it("can be used only as an attribute", function () { | ||||
|                 expect(directive.restrict).toEqual('A'); | ||||
|             }); | ||||
|  | ||||
|             it("listens for beforeunload", function () { | ||||
|                 expect(mockWindow.onbeforeunload).toEqual(jasmine.any(Function)); | ||||
|             }); | ||||
|  | ||||
|             it("listens for route changes", function () { | ||||
|                 expect(mockScope.$on).toHaveBeenCalledWith( | ||||
|                     "$locationChangeStart", | ||||
|                     jasmine.any(Function) | ||||
|                 ); | ||||
|             }); | ||||
|  | ||||
|             it("listens for its scope's destroy event", function () { | ||||
|                 expect(mockScope.$on).toHaveBeenCalledWith( | ||||
|                     "$destroy", | ||||
|                     jasmine.any(Function) | ||||
|                 ); | ||||
|             }); | ||||
|  | ||||
|             it("uses result of evaluated expression as a warning", function () { | ||||
|                 mockScope.$eval.andReturn(undefined); | ||||
|                 expect(mockWindow.onbeforeunload(mockEvent)).toBeUndefined(); | ||||
|                 mockScope.$eval.andReturn("some message"); | ||||
|                 expect(mockWindow.onbeforeunload(mockEvent)).toEqual("some message"); | ||||
|                 // Verify that the right expression was evaluated | ||||
|                 expect(mockScope.$eval).toHaveBeenCalledWith(testAttrs.mctBeforeUnload); | ||||
|             }); | ||||
|  | ||||
|             it("confirms route changes", function () { | ||||
|                 // First, try with no unsaved changes; | ||||
|                 // should not confirm or preventDefault | ||||
|                 mockScope.$eval.andReturn(undefined); | ||||
|                 fireListener("$locationChangeStart", mockEvent); | ||||
|                 expect(mockWindow.confirm).not.toHaveBeenCalled(); | ||||
|                 expect(mockEvent.preventDefault).not.toHaveBeenCalled(); | ||||
|  | ||||
|                 // Next, try with unsaved changes that the user confirms; | ||||
|                 // should prompt, but not preventDefault | ||||
|                 mockScope.$eval.andReturn("some message"); | ||||
|                 mockWindow.confirm.andReturn(true); | ||||
|                 fireListener("$locationChangeStart", mockEvent); | ||||
|                 expect(mockWindow.confirm).toHaveBeenCalledWith("some message"); | ||||
|                 expect(mockEvent.preventDefault).not.toHaveBeenCalled(); | ||||
|  | ||||
|                 // Finally, act as if the user said no to this dialog; | ||||
|                 // this should preventDefault on the location change. | ||||
|                 mockWindow.confirm.andReturn(false); | ||||
|                 fireListener("$locationChangeStart", mockEvent); | ||||
|                 expect(mockWindow.confirm).toHaveBeenCalledWith("some message"); | ||||
|                 expect(mockEvent.preventDefault).toHaveBeenCalled(); | ||||
|             }); | ||||
|  | ||||
|             it("cleans up listeners when destroyed", function () { | ||||
|                 fireListener("$destroy", mockEvent); | ||||
|                 expect(mockWindow.onbeforeunload).toBeUndefined(); | ||||
|             }); | ||||
|  | ||||
|  | ||||
|         }); | ||||
|     } | ||||
| ); | ||||
| @@ -20,70 +20,103 @@ | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define([ | ||||
|     '../../src/representers/EditRepresenter' | ||||
| ], function ( | ||||
|     EditRepresenter | ||||
| ) { | ||||
|     describe('EditRepresenter', function () { | ||||
|         var $log, | ||||
|             $scope, | ||||
|             representer; | ||||
| define( | ||||
|     ["../../src/representers/EditRepresenter"], | ||||
|     function (EditRepresenter) { | ||||
|  | ||||
|         describe("The Edit mode representer", function () { | ||||
|             var mockQ, | ||||
|                 mockLog, | ||||
|                 mockScope, | ||||
|                 testRepresentation, | ||||
|                 mockDomainObject, | ||||
|                 mockStatusCapability, | ||||
|                 mockEditorCapability, | ||||
|                 mockCapabilities, | ||||
|                 representer; | ||||
|  | ||||
|         beforeEach(function () { | ||||
|             $log = jasmine.createSpyObj('$log', ['debug']); | ||||
|             $scope = {}; | ||||
|             representer = new EditRepresenter($log, $scope); | ||||
|         }); | ||||
|  | ||||
|         it('injects a commit function in scope', function () { | ||||
|             expect($scope.commit).toEqual(jasmine.any(Function)); | ||||
|         }); | ||||
|  | ||||
|         describe('representation', function () { | ||||
|             var domainObject, | ||||
|                 representation; | ||||
|             function mockPromise(value) { | ||||
|                 return { | ||||
|                     then: function (callback) { | ||||
|                         return mockPromise(callback(value)); | ||||
|                     } | ||||
|                 }; | ||||
|             } | ||||
|  | ||||
|             beforeEach(function () { | ||||
|                 domainObject = jasmine.createSpyObj('domainObject', [ | ||||
|                     'getId', | ||||
|                     'getModel', | ||||
|                     'useCapability' | ||||
|                 mockQ = { when: mockPromise }; | ||||
|                 mockLog = jasmine.createSpyObj("$log", ["info", "debug"]); | ||||
|                 mockScope = jasmine.createSpyObj("$scope", ["$watch", "$on"]); | ||||
|                 testRepresentation = { key: "test" }; | ||||
|                 mockDomainObject = jasmine.createSpyObj("domainObject", [ | ||||
|                     "getId", | ||||
|                     "getModel", | ||||
|                     "getCapability", | ||||
|                     "useCapability", | ||||
|                     "hasCapability" | ||||
|                 ]); | ||||
|                 mockStatusCapability = | ||||
|                     jasmine.createSpyObj("statusCapability", ["listen"]); | ||||
|                 mockEditorCapability = | ||||
|                     jasmine.createSpyObj("editorCapability", ["isEditContextRoot"]); | ||||
|  | ||||
|                 domainObject.getId.andReturn('anId'); | ||||
|                 domainObject.getModel.andReturn({name: 'anObject'}); | ||||
|  | ||||
|                 representation = { | ||||
|                     key: 'someRepresentation' | ||||
|                 mockCapabilities = { | ||||
|                     'status': mockStatusCapability, | ||||
|                     'editor': mockEditorCapability | ||||
|                 }; | ||||
|                 $scope.model = {name: 'anotherName'}; | ||||
|                 $scope.configuration = {some: 'config'}; | ||||
|                 representer.represent(representation, domainObject); | ||||
|  | ||||
|                 mockDomainObject.getModel.andReturn({}); | ||||
|                 mockDomainObject.hasCapability.andReturn(true); | ||||
|                 mockDomainObject.useCapability.andReturn(true); | ||||
|                 mockDomainObject.getCapability.andCallFake(function (capability) { | ||||
|                     return mockCapabilities[capability]; | ||||
|                 }); | ||||
|  | ||||
|                 representer = new EditRepresenter(mockQ, mockLog, mockScope); | ||||
|                 representer.represent(testRepresentation, mockDomainObject); | ||||
|             }); | ||||
|  | ||||
|             it('logs a message when commiting', function () { | ||||
|                 $scope.commit('Test Message'); | ||||
|                 expect($log.debug) | ||||
|                     .toHaveBeenCalledWith('Committing  anObject (anId): Test Message'); | ||||
|             it("provides a commit method in scope", function () { | ||||
|                 expect(mockScope.commit).toEqual(jasmine.any(Function)); | ||||
|             }); | ||||
|  | ||||
|             it('mutates the object when committing', function () { | ||||
|                 $scope.commit('Test Message'); | ||||
|  | ||||
|                 expect(domainObject.useCapability) | ||||
|                     .toHaveBeenCalledWith('mutation', jasmine.any(Function)); | ||||
|  | ||||
|                 var mutateValue = domainObject.useCapability.calls[0].args[1](); | ||||
|  | ||||
|                 expect(mutateValue.configuration.someRepresentation) | ||||
|                     .toEqual({some: 'config'}); | ||||
|                 expect(mutateValue.name).toEqual('anotherName'); | ||||
|             it("Sets edit view template on edit mode", function () { | ||||
|                 mockStatusCapability.listen.mostRecentCall.args[0](['editing']); | ||||
|                 mockEditorCapability.isEditContextRoot.andReturn(true); | ||||
|                 expect(mockScope.viewObjectTemplate).toEqual('edit-object'); | ||||
|             }); | ||||
|  | ||||
|             it("Cleans up listeners on scope destroy", function () { | ||||
|                 representer.listenHandle = jasmine.createSpy('listen'); | ||||
|                 mockScope.$on.mostRecentCall.args[1](); | ||||
|                 expect(representer.listenHandle).toHaveBeenCalled(); | ||||
|             }); | ||||
|  | ||||
|             it("mutates upon observed changes", function () { | ||||
|                 mockScope.model = { someKey: "some value" }; | ||||
|                 mockScope.configuration = { someConfiguration: "something" }; | ||||
|  | ||||
|                 mockScope.commit("Some message"); | ||||
|  | ||||
|                 // Should have mutated the object... | ||||
|                 expect(mockDomainObject.useCapability).toHaveBeenCalledWith( | ||||
|                     "mutation", | ||||
|                     jasmine.any(Function) | ||||
|                 ); | ||||
|  | ||||
|                 // Finally, check that the provided mutation function | ||||
|                 // includes both model and configuration | ||||
|                 expect( | ||||
|                     mockDomainObject.useCapability.mostRecentCall.args[1]() | ||||
|                 ).toEqual({ | ||||
|                     someKey: "some value", | ||||
|                     configuration: { | ||||
|                         test: { someConfiguration: "something" } | ||||
|                     } | ||||
|                 }); | ||||
|             }); | ||||
|  | ||||
|  | ||||
|         }); | ||||
|  | ||||
|  | ||||
|     }); | ||||
| }); | ||||
|     } | ||||
| ); | ||||
|   | ||||
| @@ -95,45 +95,6 @@ define([ | ||||
|         })[0][0]; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns a description of the current range of the time conductor's | ||||
|      * bounds. | ||||
|      * @param timeRange | ||||
|      * @returns {*} | ||||
|      */ | ||||
|     UTCTimeFormat.prototype.timeUnits = function (timeRange) { | ||||
|         var momentified = moment.duration(timeRange); | ||||
|  | ||||
|         return [ | ||||
|             ["Decades", function (r) { | ||||
|                 return r.years() > 15; | ||||
|             }], | ||||
|             ["Years", function (r) { | ||||
|                 return r.years() > 1; | ||||
|             }], | ||||
|             ["Months", function (r) { | ||||
|                 return r.years() === 1 || r.months() > 1; | ||||
|             }], | ||||
|             ["Days", function (r) { | ||||
|                 return r.months() === 1 || r.days() > 1; | ||||
|             }], | ||||
|             ["Hours", function (r) { | ||||
|                 return r.days() === 1 || r.hours() > 1; | ||||
|             }], | ||||
|             ["Minutes", function (r) { | ||||
|                 return r.hours() === 1 || r.minutes() > 1; | ||||
|             }], | ||||
|             ["Seconds", function (r) { | ||||
|                 return r.minutes() === 1 || r.seconds() > 1; | ||||
|             }], | ||||
|             ["Milliseconds", function (r) { | ||||
|                 return true; | ||||
|             }] | ||||
|         ].filter(function (row) { | ||||
|             return row[1](momentified); | ||||
|         })[0][0]; | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * | ||||
|      * @param value | ||||
|   | ||||
| @@ -58,26 +58,5 @@ define([ | ||||
|             expect(format.format(APRIL, scale)).toBe("April"); | ||||
|             expect(format.format(TWENTY_SIXTEEN, scale)).toBe("2016"); | ||||
|         }); | ||||
|  | ||||
|         it("Returns appropriate time units for a given time span", function () { | ||||
|             var ONE_DAY = 1000 * 60 * 60 * 24; | ||||
|             var FIVE_DAYS = 5 * ONE_DAY; | ||||
|             var FIVE_MONTHS = 60 * ONE_DAY; | ||||
|  | ||||
|             var ONE_YEAR = 365 * ONE_DAY; | ||||
|             var SEVEN_YEARS = 7 * ONE_YEAR; | ||||
|             var TWO_DECADES = 20 * ONE_YEAR; | ||||
|  | ||||
|             //A span of one day should show a zoom label of "Hours" | ||||
|             expect(format.timeUnits(ONE_DAY)).toEqual("Hours"); | ||||
|             //Multiple days should display "Days" | ||||
|             expect(format.timeUnits(FIVE_DAYS)).toEqual("Days"); | ||||
|             expect(format.timeUnits(FIVE_MONTHS)).toEqual("Days"); | ||||
|             //A span of one year should show a zoom level of "Months". | ||||
|             // Multiple years will show "Years" | ||||
|             expect(format.timeUnits(ONE_YEAR)).toEqual("Months"); | ||||
|             expect(format.timeUnits(SEVEN_YEARS)).toEqual("Years"); | ||||
|             expect(format.timeUnits(TWO_DECADES)).toEqual("Decades"); | ||||
|         }); | ||||
|     }); | ||||
| }); | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| { | ||||
|   "metadata": { | ||||
|     "name": "openmct-symbols-16px", | ||||
|     "lastOpened": 1481575258437, | ||||
|     "created": 1481575255265 | ||||
|     "lastOpened": 1469724858940, | ||||
|     "created": 1469724856623 | ||||
|   }, | ||||
|   "iconSets": [ | ||||
|     { | ||||
| @@ -535,50 +535,18 @@ | ||||
|         { | ||||
|           "order": 21, | ||||
|           "prevSize": 24, | ||||
|           "name": "icon-resync", | ||||
|           "name": "icon-x-in-circle", | ||||
|           "id": 16, | ||||
|           "code": 921654, | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 121, | ||||
|           "id": 103, | ||||
|           "name": "icon-x-in-circle", | ||||
|           "prevSize": 24, | ||||
|           "code": 921656, | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 118, | ||||
|           "id": 102, | ||||
|           "name": "icon-brightness", | ||||
|           "prevSize": 24, | ||||
|           "code": 921657, | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 119, | ||||
|           "id": 104, | ||||
|           "name": "icon-contrast", | ||||
|           "prevSize": 24, | ||||
|           "code": 921664, | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 120, | ||||
|           "id": 105, | ||||
|           "name": "icon-reset", | ||||
|           "prevSize": 24, | ||||
|           "code": 921655, | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 37, | ||||
|           "prevSize": 24, | ||||
|           "name": "icon-activity", | ||||
|           "id": 32, | ||||
|           "code": 921856, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 36, | ||||
| @@ -586,7 +554,7 @@ | ||||
|           "name": "icon-activity-mode", | ||||
|           "id": 31, | ||||
|           "code": 921857, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 52, | ||||
| @@ -594,7 +562,7 @@ | ||||
|           "name": "icon-autoflow-tabular", | ||||
|           "id": 47, | ||||
|           "code": 921858, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 55, | ||||
| @@ -602,7 +570,7 @@ | ||||
|           "name": "icon-clock", | ||||
|           "id": 50, | ||||
|           "code": 921859, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 58, | ||||
| @@ -610,7 +578,7 @@ | ||||
|           "name": "icon-database", | ||||
|           "id": 53, | ||||
|           "code": 921860, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 57, | ||||
| @@ -618,7 +586,7 @@ | ||||
|           "name": "icon-database-query", | ||||
|           "id": 52, | ||||
|           "code": 921861, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 17, | ||||
| @@ -626,7 +594,7 @@ | ||||
|           "name": "icon-dataset", | ||||
|           "id": 12, | ||||
|           "code": 921862, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 22, | ||||
| @@ -634,7 +602,7 @@ | ||||
|           "name": "icon-datatable", | ||||
|           "id": 17, | ||||
|           "code": 921863, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 59, | ||||
| @@ -642,7 +610,7 @@ | ||||
|           "name": "icon-dictionary", | ||||
|           "id": 54, | ||||
|           "code": 921864, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 62, | ||||
| @@ -650,7 +618,7 @@ | ||||
|           "name": "icon-folder", | ||||
|           "id": 57, | ||||
|           "code": 921865, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 66, | ||||
| @@ -658,7 +626,7 @@ | ||||
|           "name": "icon-image", | ||||
|           "id": 61, | ||||
|           "code": 921872, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 68, | ||||
| @@ -666,7 +634,7 @@ | ||||
|           "name": "icon-layout", | ||||
|           "id": 63, | ||||
|           "code": 921873, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 77, | ||||
| @@ -674,7 +642,7 @@ | ||||
|           "name": "icon-object", | ||||
|           "id": 72, | ||||
|           "code": 921874, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 78, | ||||
| @@ -682,7 +650,7 @@ | ||||
|           "name": "icon-object-unknown", | ||||
|           "id": 73, | ||||
|           "code": 921875, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 79, | ||||
| @@ -690,7 +658,7 @@ | ||||
|           "name": "icon-packet", | ||||
|           "id": 74, | ||||
|           "code": 921876, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 80, | ||||
| @@ -698,7 +666,7 @@ | ||||
|           "name": "icon-page", | ||||
|           "id": 75, | ||||
|           "code": 921877, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 114, | ||||
| @@ -706,7 +674,7 @@ | ||||
|           "name": "icon-plot-overlay", | ||||
|           "prevSize": 24, | ||||
|           "code": 921878, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 113, | ||||
| @@ -714,7 +682,7 @@ | ||||
|           "name": "icon-plot-stacked", | ||||
|           "prevSize": 24, | ||||
|           "code": 921879, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 10, | ||||
| @@ -722,7 +690,7 @@ | ||||
|           "name": "icon-session", | ||||
|           "id": 5, | ||||
|           "code": 921880, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 24, | ||||
| @@ -730,7 +698,7 @@ | ||||
|           "name": "icon-tabular", | ||||
|           "id": 19, | ||||
|           "code": 921881, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 7, | ||||
| @@ -738,7 +706,7 @@ | ||||
|           "name": "icon-tabular-lad", | ||||
|           "id": 2, | ||||
|           "code": 921888, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 6, | ||||
| @@ -746,7 +714,7 @@ | ||||
|           "name": "icon-tabular-lad-set", | ||||
|           "id": 1, | ||||
|           "code": 921889, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 8, | ||||
| @@ -754,7 +722,7 @@ | ||||
|           "name": "icon-tabular-realtime", | ||||
|           "id": 3, | ||||
|           "code": 921890, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 23, | ||||
| @@ -762,7 +730,7 @@ | ||||
|           "name": "icon-tabular-scrolling", | ||||
|           "id": 18, | ||||
|           "code": 921891, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 112, | ||||
| @@ -770,7 +738,7 @@ | ||||
|           "name": "icon-telemetry", | ||||
|           "id": 86, | ||||
|           "code": 921892, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 90, | ||||
| @@ -778,7 +746,7 @@ | ||||
|           "name": "icon-telemetry-panel", | ||||
|           "id": 85, | ||||
|           "code": 921893, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 93, | ||||
| @@ -786,7 +754,7 @@ | ||||
|           "name": "icon-timeline", | ||||
|           "id": 88, | ||||
|           "code": 921894, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 116, | ||||
| @@ -794,7 +762,7 @@ | ||||
|           "name": "icon-timer-v1.5", | ||||
|           "prevSize": 24, | ||||
|           "code": 921895, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 11, | ||||
| @@ -802,7 +770,7 @@ | ||||
|           "name": "icon-topic", | ||||
|           "id": 6, | ||||
|           "code": 921896, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         }, | ||||
|         { | ||||
|           "order": 115, | ||||
| @@ -810,13 +778,13 @@ | ||||
|           "name": "icon-box-with-dashed-lines", | ||||
|           "id": 29, | ||||
|           "code": 921897, | ||||
|           "tempChar": "" | ||||
|           "tempChar": "" | ||||
|         } | ||||
|       ], | ||||
|       "metadata": { | ||||
|         "name": "openmct-symbols-16px", | ||||
|         "importSize": { | ||||
|           "width": 512, | ||||
|           "width": 448, | ||||
|           "height": 512 | ||||
|         }, | ||||
|         "designer": "Charles Hacskaylo", | ||||
| @@ -2005,7 +1973,7 @@ | ||||
|         }, | ||||
|         { | ||||
|           "paths": [ | ||||
|             "M960 432v-432l-164.8 164.8c-79.8-65.2-178.8-100.8-283.2-100.8-119.6 0-232.2 46.6-316.8 131.2s-131.2 197.2-131.2 316.8 46.6 232.2 131.2 316.8c84.6 84.6 197.2 131.2 316.8 131.2s232.2-46.6 316.8-131.2c69.4-69.4 113.2-157.4 126.6-252.8h-130c-29.8 145.8-159 256-313.6 256-176.4 0-320-143.6-320-320s143.8-320 320.2-320c72 0 138.4 23.8 192 64l-176 176h432z" | ||||
|             "M1012.8 414.2v-391.6l-127.6 127.4c-96.6-96.8-225.2-150-362-150s-265.2 53.2-362 150c-96.8 96.8-150 225.2-150 362s53.2 265.4 150 362c96.8 96.8 225.2 150 362 150s265.4-53.2 362-150l-136.6-136.6c-124.2 124.2-326.4 124.2-450.8 0-124.2-124.2-124.2-326.4 0-450.8 124.2-124.2 326.4-124.2 450.8 0l-127.4 127.4h391.6z" | ||||
|           ], | ||||
|           "grid": 16, | ||||
|           "tags": [ | ||||
| @@ -2013,19 +1981,9 @@ | ||||
|           ], | ||||
|           "defaultCode": 114, | ||||
|           "id": 27, | ||||
|           "attrs": [ | ||||
|             { | ||||
|               "fill": "rgb(0, 161, 75)" | ||||
|             } | ||||
|           ], | ||||
|           "isMulticolor": false, | ||||
|           "isMulticolor2": false, | ||||
|           "attrs": [], | ||||
|           "colorPermutations": { | ||||
|             "1161751207457516161751": [ | ||||
|               { | ||||
|                 "f": 1 | ||||
|               } | ||||
|             ] | ||||
|             "1161751207457516161751": [] | ||||
|           } | ||||
|         }, | ||||
|         { | ||||
| @@ -2152,37 +2110,6 @@ | ||||
|             "1161751207457516161751": [] | ||||
|           } | ||||
|         }, | ||||
|         { | ||||
|           "id": 105, | ||||
|           "paths": [ | ||||
|             "M795.2 164.8c-79.8-65.2-178.8-100.8-283.2-100.8-119.6 0-232.2 46.6-316.8 131.2-69.4 69.4-113.2 157.4-126.6 252.8h130c29.6-145.8 158.8-256 313.4-256 72 0 138.4 23.8 192 64l-176 176h432v-432l-164.8 164.8z", | ||||
|             "M512 832c-72 0-138.4-23.8-192-64l176-176h-432v432l164.8-164.8c79.8 65.2 178.8 100.8 283.2 100.8 119.6 0 232.2-46.6 316.8-131.2 69.4-69.4 113.2-157.4 126.6-252.8h-130c-29.6 145.8-158.8 256-313.4 256z" | ||||
|           ], | ||||
|           "attrs": [ | ||||
|             { | ||||
|               "fill": "rgb(0, 161, 75)" | ||||
|             }, | ||||
|             { | ||||
|               "fill": "rgb(0, 161, 75)" | ||||
|             } | ||||
|           ], | ||||
|           "isMulticolor": false, | ||||
|           "isMulticolor2": false, | ||||
|           "grid": 16, | ||||
|           "tags": [ | ||||
|             "icon-resync" | ||||
|           ], | ||||
|           "colorPermutations": { | ||||
|             "1161751207457516161751": [ | ||||
|               { | ||||
|                 "f": 1 | ||||
|               }, | ||||
|               { | ||||
|                 "f": 1 | ||||
|               } | ||||
|             ] | ||||
|           } | ||||
|         }, | ||||
|         { | ||||
|           "paths": [ | ||||
|             "M512 0c-282.8 0-512 229.2-512 512s229.2 512 512 512 512-229.2 512-512-229.2-512-512-512zM832 704l-128 128-192-192-192 192-128-128 192-192-192-192 128-128 192 192 192-192 128 128-192 192 192 192z" | ||||
| @@ -2198,134 +2125,6 @@ | ||||
|             "1161751207457516161751": [] | ||||
|           } | ||||
|         }, | ||||
|         { | ||||
|           "id": 103, | ||||
|           "paths": [ | ||||
|             "M253.414 318.061l-155.172-116.384c-50.233 66.209-85.127 146.713-97.91 234.39l191.586 30.216c8.145-56.552 29.998-106.879 62.068-149.006z", | ||||
|             "M191.98 557.717l-191.919 27.434c13.115 90.459 48.009 170.963 99.174 238.453l154.18-117.665c-31.476-41.347-53.309-91.675-61.231-146.504z", | ||||
|             "M466.283 191.98l-27.434-191.919c-90.459 13.115-170.963 48.009-238.453 99.174l117.665 154.18c41.347-31.476 91.675-53.309 146.504-61.231z", | ||||
|             "M822.323 98.242c-66.209-50.233-146.713-85.127-234.39-97.91l-30.216 191.586c56.552 8.145 106.879 29.998 149.006 62.068z", | ||||
|             "M832.020 466.283l191.919-27.434c-13.115-90.459-48.009-170.963-99.174-238.453l-154.18 117.665c31.476 41.347 53.309 91.675 61.231 146.504z", | ||||
|             "M201.677 925.758c66.209 50.233 146.713 85.127 234.39 97.91l30.216-191.586c-56.552-8.145-106.879-29.998-149.006-62.068z", | ||||
|             "M770.586 705.939l155.131 116.343c50.233-66.209 85.127-146.713 97.91-234.39l-191.586-30.216c-8.125 56.564-29.966 106.906-62.028 149.049z", | ||||
|             "M557.717 832.020l27.434 191.919c90.459-13.115 170.963-48.009 238.453-99.174l-117.665-154.18c-41.347 31.476-91.675 53.309-146.504 61.231z", | ||||
|             "M770.586 512c0 142.813-115.773 258.586-258.586 258.586s-258.586-115.773-258.586-258.586c0-142.813 115.773-258.586 258.586-258.586s258.586 115.773 258.586 258.586z" | ||||
|           ], | ||||
|           "attrs": [ | ||||
|             { | ||||
|               "fill": "rgb(0, 161, 75)" | ||||
|             }, | ||||
|             { | ||||
|               "fill": "rgb(0, 161, 75)" | ||||
|             }, | ||||
|             { | ||||
|               "fill": "rgb(0, 161, 75)" | ||||
|             }, | ||||
|             { | ||||
|               "fill": "rgb(0, 161, 75)" | ||||
|             }, | ||||
|             { | ||||
|               "fill": "rgb(0, 161, 75)" | ||||
|             }, | ||||
|             { | ||||
|               "fill": "rgb(0, 161, 75)" | ||||
|             }, | ||||
|             { | ||||
|               "fill": "rgb(0, 161, 75)" | ||||
|             }, | ||||
|             { | ||||
|               "fill": "rgb(0, 161, 75)" | ||||
|             }, | ||||
|             { | ||||
|               "fill": "rgb(0, 161, 75)" | ||||
|             } | ||||
|           ], | ||||
|           "isMulticolor": false, | ||||
|           "isMulticolor2": false, | ||||
|           "grid": 16, | ||||
|           "tags": [ | ||||
|             "icon-brightness" | ||||
|           ], | ||||
|           "colorPermutations": { | ||||
|             "1161751207457516161751": [ | ||||
|               { | ||||
|                 "f": 1 | ||||
|               }, | ||||
|               { | ||||
|                 "f": 1 | ||||
|               }, | ||||
|               { | ||||
|                 "f": 1 | ||||
|               }, | ||||
|               { | ||||
|                 "f": 1 | ||||
|               }, | ||||
|               { | ||||
|                 "f": 1 | ||||
|               }, | ||||
|               { | ||||
|                 "f": 1 | ||||
|               }, | ||||
|               { | ||||
|                 "f": 1 | ||||
|               }, | ||||
|               { | ||||
|                 "f": 1 | ||||
|               }, | ||||
|               { | ||||
|                 "f": 1 | ||||
|               } | ||||
|             ] | ||||
|           } | ||||
|         }, | ||||
|         { | ||||
|           "id": 102, | ||||
|           "paths": [ | ||||
|             "M512 0c-282.78 0-512 229.24-512 512s229.22 512 512 512 512-229.24 512-512-229.22-512-512-512zM783.52 783.52c-69.111 69.481-164.785 112.481-270.502 112.481-0.358 0-0.716-0-1.074-0.001l0.055-768c212.070 0.010 383.982 171.929 383.982 384 0 106.034-42.977 202.031-112.462 271.52z" | ||||
|           ], | ||||
|           "attrs": [ | ||||
|             { | ||||
|               "fill": "rgb(0, 161, 75)" | ||||
|             } | ||||
|           ], | ||||
|           "isMulticolor": false, | ||||
|           "isMulticolor2": false, | ||||
|           "grid": 16, | ||||
|           "tags": [ | ||||
|             "icon-contrast" | ||||
|           ], | ||||
|           "colorPermutations": { | ||||
|             "1161751207457516161751": [ | ||||
|               { | ||||
|                 "f": 1 | ||||
|               } | ||||
|             ] | ||||
|           } | ||||
|         }, | ||||
|         { | ||||
|           "id": 104, | ||||
|           "paths": [ | ||||
|             "M460.8 460.8l-187.8-187.8c57.2-42.8 128-68.2 204.8-68.2 188.2 0 341.6 153.2 341.6 341.4s-153.2 341.2-341.4 341.2c-165 0-302.8-117.6-334.6-273h-138.4c14.2 101.8 61 195.6 135 269.6 90.2 90.2 210.4 140 338 140s247.6-49.8 338-140 140-210.4 140-338-49.8-247.6-140-338-210.4-140-338-140c-111.4 0-217 38-302 107.6l-176-175.6v460.8h460.8z" | ||||
|           ], | ||||
|           "attrs": [ | ||||
|             { | ||||
|               "fill": "rgb(0, 161, 75)" | ||||
|             } | ||||
|           ], | ||||
|           "isMulticolor": false, | ||||
|           "isMulticolor2": false, | ||||
|           "grid": 16, | ||||
|           "tags": [ | ||||
|             "icon-reset" | ||||
|           ], | ||||
|           "colorPermutations": { | ||||
|             "1161751207457516161751": [ | ||||
|               { | ||||
|                 "f": 1 | ||||
|               } | ||||
|             ] | ||||
|           } | ||||
|         }, | ||||
|         { | ||||
|           "paths": [ | ||||
|             "M576 64h-256l320 320h-290.256c-44.264-76.516-126.99-128-221.744-128h-128v512h128c94.754 0 177.48-51.484 221.744-128h290.256l-320 320h256l448-448-448-448z" | ||||
|   | ||||
										
											Binary file not shown.
										
									
								
							| @@ -65,18 +65,14 @@ | ||||
| <glyph unicode="󡀦" glyph-name="icon-plot-resource" d="M255.884 256c0.040 0.034 0.082 0.074 0.116 0.116v127.884c0 70.58 57.42 128 128 128h255.884c0.040 0.034 0.082 0.074 0.116 0.116v127.884c0 70.58 57.42 128 128 128h143.658c-93.832 117.038-237.98 192-399.658 192-282.77 0-512-229.23-512-512 0-67.904 13.25-132.704 37.256-192h218.628zM768.116 640c-0.040-0.034-0.082-0.074-0.116-0.116v-127.884c0-70.58-57.42-128-128-128h-255.884c-0.040-0.034-0.082-0.074-0.116-0.116v-127.884c0-70.58-57.42-128-128-128h-143.658c93.832-117.038 237.98-192 399.658-192 282.77 0 512 229.23 512 512 0 67.904-13.25 132.704-37.256 192h-218.628z" /> | ||||
| <glyph unicode="󡀧" glyph-name="icon-pointer-left" horiz-adv-x="512" d="M510-64l-256 512 256 512h-256l-256-512 256-512z" /> | ||||
| <glyph unicode="󡀨" glyph-name="icon-pointer-right" horiz-adv-x="512" d="M-2 960l256-512-256-512h256l256 512-256 512z" /> | ||||
| <glyph unicode="󡀩" glyph-name="icon-refresh" d="M960 528v432l-164.8-164.8c-79.8 65.2-178.8 100.8-283.2 100.8-119.6 0-232.2-46.6-316.8-131.2s-131.2-197.2-131.2-316.8 46.6-232.2 131.2-316.8c84.6-84.6 197.2-131.2 316.8-131.2s232.2 46.6 316.8 131.2c69.4 69.4 113.2 157.4 126.6 252.8h-130c-29.8-145.8-159-256-313.6-256-176.4 0-320 143.6-320 320s143.8 320 320.2 320c72 0 138.4-23.8 192-64l-176-176h432z" /> | ||||
| <glyph unicode="󡀩" glyph-name="icon-refresh" d="M1012.8 545.8v391.6l-127.6-127.4c-96.6 96.8-225.2 150-362 150s-265.2-53.2-362-150c-96.8-96.8-150-225.2-150-362s53.2-265.4 150-362c96.8-96.8 225.2-150 362-150s265.4 53.2 362 150l-136.6 136.6c-124.2-124.2-326.4-124.2-450.8 0-124.2 124.2-124.2 326.4 0 450.8 124.2 124.2 326.4 124.2 450.8 0l-127.4-127.4h391.6z" /> | ||||
| <glyph unicode="󡀰" glyph-name="icon-save" d="M192.2 384c-0.2 0-0.2 0 0 0l-0.2-448h640v447.8c0 0 0 0-0.2 0.2h-639.6zM978.8 749.2l-165.4 165.4c-25 25-74.2 45.4-109.4 45.4h-576c-70.4 0-128-57.6-128-128v-768c0-70.4 57.6-128 128-128v448c0 35.2 28.8 64 64 64h640c35.2 0 64-28.8 64-64v-448c70.4 0 128 57.6 128 128v576c0 35.2-20.4 84.4-45.2 109.2zM704 704c0-35.2-28.8-64-64-64h-448c-35.2 0-64 28.8-64 64v192h320v-192h128v192h128v-192z" /> | ||||
| <glyph unicode="󡀱" glyph-name="icon-sine" d="M1022.294 448c-1.746 7.196-3.476 14.452-5.186 21.786-20.036 85.992-53.302 208.976-98 306.538-22.42 48.938-45.298 86.556-69.946 115.006-48.454 55.93-98.176 67.67-131.356 67.67s-82.902-11.74-131.356-67.672c-24.648-28.45-47.528-66.068-69.948-115.006-44.696-97.558-77.962-220.544-98-306.538-21.646-92.898-46.444-175.138-71.71-237.836-16.308-40.46-30.222-66.358-40.6-82.604-10.378 16.246-24.292 42.142-40.6 82.604-23.272 57.75-46.144 132.088-66.524 216.052h-197.362c1.746-7.196 3.476-14.452 5.186-21.786 20.036-85.992 53.302-208.976 98-306.538 22.42-48.938 45.298-86.556 69.946-115.006 48.454-55.932 98.176-67.672 131.356-67.672s82.902 11.74 131.356 67.672c24.648 28.45 47.528 66.068 69.948 115.006 44.696 97.558 77.962 220.544 98 306.538 21.646 92.898 46.444 175.138 71.71 237.836 16.308 40.46 30.222 66.358 40.6 82.604 10.378-16.246 24.292-42.142 40.6-82.604 23.274-57.748 46.146-132.086 66.526-216.050h197.36z" /> | ||||
| <glyph unicode="󡀲" glyph-name="icon-T" d="M0 960v-256h128v64h256v-704h-192v-128h640v128h-192v704h256v-64h128v256z" /> | ||||
| <glyph unicode="󡀳" glyph-name="icon-thumbs-strip" d="M448 578c0-35.2-28.8-64-64-64h-320c-35.2 0-64 28.8-64 64v320c0 35.2 28.8 64 64 64h320c35.2 0 64-28.8 64-64v-320zM1024 578c0-35.2-28.8-64-64-64h-320c-35.2 0-64 28.8-64 64v320c0 35.2 28.8 64 64 64h320c35.2 0 64-28.8 64-64v-320zM448 2c0-35.2-28.8-64-64-64h-320c-35.2 0-64 28.8-64 64v320c0 35.2 28.8 64 64 64h320c35.2 0 64-28.8 64-64v-320zM1024 2c0-35.2-28.8-64-64-64h-320c-35.2 0-64 28.8-64 64v320c0 35.2 28.8 64 64 64h320c35.2 0 64-28.8 64-64v-320z" /> | ||||
| <glyph unicode="󡀴" glyph-name="icon-two-parts-both" d="M896 960h-768c-70.4 0-128-57.6-128-128v-768c0-70.4 57.6-128 128-128h768c70.4 0 128 57.6 128 128v768c0 70.4-57.6 128-128 128zM128 832h320v-768h-320v768zM896 64h-320v768h320v-768z" /> | ||||
| <glyph unicode="󡀵" glyph-name="icon-two-parts-one-only" d="M896 960h-768c-70.4 0-128-57.6-128-128v-768c0-70.4 57.6-128 128-128h768c70.4 0 128 57.6 128 128v768c0 70.4-57.6 128-128 128zM896 64h-320v768h320v-768z" /> | ||||
| <glyph unicode="󡀶" glyph-name="icon-resync" d="M795.2 795.2c-79.8 65.2-178.8 100.8-283.2 100.8-119.6 0-232.2-46.6-316.8-131.2-69.4-69.4-113.2-157.4-126.6-252.8h130c29.6 145.8 158.8 256 313.4 256 72 0 138.4-23.8 192-64l-176-176h432v432l-164.8-164.8zM512 128c-72 0-138.4 23.8-192 64l176 176h-432v-432l164.8 164.8c79.8-65.2 178.8-100.8 283.2-100.8 119.6 0 232.2 46.6 316.8 131.2 69.4 69.4 113.2 157.4 126.6 252.8h-130c-29.6-145.8-158.8-256-313.4-256z" /> | ||||
| <glyph unicode="󡀷" glyph-name="icon-reset" d="M460.8 499.2l-187.8 187.8c57.2 42.8 128 68.2 204.8 68.2 188.2 0 341.6-153.2 341.6-341.4s-153.2-341.2-341.4-341.2c-165 0-302.8 117.6-334.6 273h-138.4c14.2-101.8 61-195.6 135-269.6 90.2-90.2 210.4-140 338-140s247.6 49.8 338 140 140 210.4 140 338-49.8 247.6-140 338-210.4 140-338 140c-111.4 0-217-38-302-107.6l-176 175.6v-460.8h460.8z" /> | ||||
| <glyph unicode="󡀸" glyph-name="icon-x-in-circle" d="M512 960c-282.8 0-512-229.2-512-512s229.2-512 512-512 512 229.2 512 512-229.2 512-512 512zM832 256l-128-128-192 192-192-192-128 128 192 192-192 192 128 128 192-192 192 192 128-128-192-192 192-192z" /> | ||||
| <glyph unicode="󡀹" glyph-name="icon-brightness" d="M253.414 641.939l-155.172 116.384c-50.233-66.209-85.127-146.713-97.91-234.39l191.586-30.216c8.145 56.552 29.998 106.879 62.068 149.006zM191.98 402.283l-191.919-27.434c13.115-90.459 48.009-170.963 99.174-238.453l154.18 117.665c-31.476 41.347-53.309 91.675-61.231 146.504zM466.283 768.020l-27.434 191.919c-90.459-13.115-170.963-48.009-238.453-99.174l117.665-154.18c41.347 31.476 91.675 53.309 146.504 61.231zM822.323 861.758c-66.209 50.233-146.713 85.127-234.39 97.91l-30.216-191.586c56.552-8.145 106.879-29.998 149.006-62.068zM832.020 493.717l191.919 27.434c-13.115 90.459-48.009 170.963-99.174 238.453l-154.18-117.665c31.476-41.347 53.309-91.675 61.231-146.504zM201.677 34.242c66.209-50.233 146.713-85.127 234.39-97.91l30.216 191.586c-56.552 8.145-106.879 29.998-149.006 62.068zM770.586 254.061l155.131-116.343c50.233 66.209 85.127 146.713 97.91 234.39l-191.586 30.216c-8.125-56.564-29.966-106.906-62.028-149.049zM557.717 127.98l27.434-191.919c90.459 13.115 170.963 48.009 238.453 99.174l-117.665 154.18c-41.347-31.476-91.675-53.309-146.504-61.231zM770.586 448c0-142.813-115.773-258.586-258.586-258.586s-258.586 115.773-258.586 258.586c0 142.813 115.773 258.586 258.586 258.586s258.586-115.773 258.586-258.586z" /> | ||||
| <glyph unicode="󡁀" glyph-name="icon-contrast" d="M512 960c-282.78 0-512-229.24-512-512s229.22-512 512-512 512 229.24 512 512-229.22 512-512 512zM783.52 176.48c-69.111-69.481-164.785-112.481-270.502-112.481-0.358 0-0.716 0-1.074 0.001l0.055 768c212.070-0.010 383.982-171.929 383.982-384 0-106.034-42.977-202.031-112.462-271.52z" /> | ||||
| <glyph unicode="󡀶" glyph-name="icon-x-in-circle" d="M512 960c-282.8 0-512-229.2-512-512s229.2-512 512-512 512 229.2 512 512-229.2 512-512 512zM832 256l-128-128-192 192-192-192-128 128 192 192-192 192 128 128 192-192 192 192 128-128-192-192 192-192z" /> | ||||
| <glyph unicode="󡄀" glyph-name="icon-activity" d="M576 896h-256l320-320h-290.256c-44.264 76.516-126.99 128-221.744 128h-128v-512h128c94.754 0 177.48 51.484 221.744 128h290.256l-320-320h256l448 448-448 448z" /> | ||||
| <glyph unicode="󡄁" glyph-name="icon-activity-mode" d="M512 960c-214.866 0-398.786-132.372-474.744-320h90.744c56.86 0 107.938-24.724 143.094-64h240.906l-192 192h256l320-320-320-320h-256l192 192h-240.906c-35.156-39.276-86.234-64-143.094-64h-90.744c75.958-187.628 259.878-320 474.744-320 282.77 0 512 229.23 512 512s-229.23 512-512 512z" /> | ||||
| <glyph unicode="󡄂" glyph-name="icon-autoflow-tabular" d="M192 960c-105.6 0-192-86.4-192-192v-640c0-105.6 86.4-192 192-192h64v1024h-64zM384 960h256v-1024h-256v1024zM832 960h-64v-704h256v512c0 105.6-86.4 192-192 192z" /> | ||||
|   | ||||
| Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 37 KiB | 
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							| @@ -78,7 +78,7 @@ $treeContextTriggerW: 20px; | ||||
| /*************** Tabular */ | ||||
| $tabularHeaderH: 22px; | ||||
| $tabularTdPadLR: $itemPadLR; | ||||
| $tabularTdPadTB: 2px; | ||||
| $tabularTdPadTB: 3px; | ||||
| /*************** Imagery */ | ||||
| $imageMainControlBarH: 25px; | ||||
| $imageThumbsD: 120px; | ||||
| @@ -99,7 +99,7 @@ $plotXBarH: 32px; | ||||
| $plotLegendH: 20px; | ||||
| $plotSwatchD: 8px; | ||||
| // 1: Top, 2: right, 3: bottom, 4: left | ||||
| $plotDisplayArea: ($plotLegendH + $interiorMargin, 0, $plotXBarH, $plotYBarW); | ||||
| $plotDisplayArea: ($plotLegendH + $interiorMargin, 0, $plotXBarH + $interiorMargin, $plotYBarW); | ||||
| /* min plot height is based on user testing to find minimum useful height */ | ||||
| $plotMinH: 95px; | ||||
| /*************** Bubbles */ | ||||
|   | ||||
| @@ -21,18 +21,18 @@ | ||||
|  *****************************************************************************/ | ||||
| /************************** FONTS */ | ||||
| @font-face { | ||||
|     /* | ||||
| 	/* | ||||
|     * Use https://icomoon.io/app with icomoon-project-openmct-symbols-16px.json | ||||
|     * to generate font files | ||||
|     */ | ||||
|     font-family: 'symbolsfont'; | ||||
|     src: url($dirCommonRes + 'fonts/symbols/openmct-symbols-16px.eot'); | ||||
|     src: url($dirCommonRes + 'fonts/symbols/openmct-symbols-16px.eot?#iefix') format('embedded-opentype'), | ||||
|     url($dirCommonRes + 'fonts/symbols/openmct-symbols-16px.woff') format('woff'), | ||||
|     url($dirCommonRes + 'fonts/symbols/openmct-symbols-16px.ttf') format('truetype'), | ||||
|     url($dirCommonRes + 'fonts/symbols/openmct-symbols-16px.svg') format('svg'); | ||||
|     font-weight: normal; | ||||
|     font-style: normal; | ||||
| 	font-family: 'symbolsfont'; | ||||
| 	src: url($dirCommonRes + 'fonts/symbols/openmct-symbols-16px.eot'); | ||||
| 	src: url($dirCommonRes + 'fonts/symbols/openmct-symbols-16px.eot?#iefix') format('embedded-opentype'), | ||||
| 	url($dirCommonRes + 'fonts/symbols/openmct-symbols-16px.woff') format('woff'), | ||||
| 	url($dirCommonRes + 'fonts/symbols/openmct-symbols-16px.ttf') format('truetype'), | ||||
| 	url($dirCommonRes + 'fonts/symbols/openmct-symbols-16px.svg') format('svg'); | ||||
| 	font-weight: normal; | ||||
| 	font-style: normal; | ||||
| } | ||||
|  | ||||
| @font-face { | ||||
| @@ -52,53 +52,53 @@ | ||||
|  | ||||
| /************************** HTML ENTITIES */ | ||||
| a { | ||||
|     color: $colorA; | ||||
|     cursor: pointer; | ||||
|     text-decoration: none; | ||||
|     &:hover { | ||||
|         color: $colorAHov; | ||||
|     } | ||||
| 	color: $colorA; | ||||
| 	cursor: pointer; | ||||
| 	text-decoration: none; | ||||
| 	&:hover { | ||||
| 		color: $colorAHov; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| body, html { | ||||
|     -webkit-font-smoothing: subpixel-antialiased; | ||||
|     background-color: $colorBodyBg; | ||||
|     color: $colorBodyFg; | ||||
|     font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; | ||||
|     font-size: 100%; | ||||
|     font-weight: 200; | ||||
|     height: 100%; | ||||
|     width: 100%; | ||||
| 	-webkit-font-smoothing: subpixel-antialiased; | ||||
| 	background-color: $colorBodyBg; | ||||
| 	color: $colorBodyFg; | ||||
| 	font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; | ||||
| 	font-size: 100%; | ||||
| 	font-weight: 200; | ||||
| 	height: 100%; | ||||
| 	width: 100%; | ||||
| } | ||||
|  | ||||
| em { | ||||
|     font-style: normal; | ||||
| 	font-style: normal; | ||||
| } | ||||
|  | ||||
| input, textarea { | ||||
|     font-family: Helvetica, Arial, sans-serif; | ||||
| 	font-family: Helvetica, Arial, sans-serif; | ||||
| } | ||||
|  | ||||
| input[type="text"], | ||||
| input[type="search"] { | ||||
|     vertical-align: baseline; | ||||
|     padding: $inputTextP; | ||||
| 	vertical-align: baseline; | ||||
| 	padding: $inputTextP; | ||||
| } | ||||
|  | ||||
| h1, h2, h3 { | ||||
|     margin: 0; | ||||
| 	margin: 0; | ||||
| } | ||||
|  | ||||
| h1 { | ||||
|     font-size: 1.7em; | ||||
|     font-weight: normal !important; | ||||
|     line-height: 120%; | ||||
|     margin-bottom: 20px; | ||||
|     margin-top: 0; | ||||
| 	font-size: 1.7em; | ||||
| 	font-weight: normal !important; | ||||
| 	line-height: 120%; | ||||
| 	margin-bottom: 20px; | ||||
| 	margin-top: 0; | ||||
| } | ||||
|  | ||||
| p { | ||||
|     margin-bottom: $interiorMarginLg; | ||||
| 	margin-bottom: $interiorMarginLg; | ||||
| } | ||||
|  | ||||
| ol, ul { | ||||
| @@ -108,30 +108,30 @@ ol, ul { | ||||
| } | ||||
|  | ||||
| mct-container { | ||||
|     display: block; | ||||
| 	display: block; | ||||
| } | ||||
|  | ||||
| .abs { | ||||
|     position: absolute; | ||||
|     top: 0; | ||||
|     right: 0; | ||||
|     bottom: 0; | ||||
|     left: 0; | ||||
|     height: auto; | ||||
|     width: auto; | ||||
| 	position: absolute; | ||||
| 	top: 0; | ||||
| 	right: 0; | ||||
| 	bottom: 0; | ||||
| 	left: 0; | ||||
| 	height: auto; | ||||
| 	width: auto; | ||||
| } | ||||
|  | ||||
| .code { | ||||
|     font-family: "Lucida Console", monospace; | ||||
|     font-size: 0.7em; | ||||
|     line-height: 150%; | ||||
|     white-space: pre; | ||||
| 	font-family: "Lucida Console", monospace; | ||||
| 	font-size: 0.7em; | ||||
| 	line-height: 150%; | ||||
| 	white-space: pre; | ||||
| } | ||||
|  | ||||
| .codehilite { | ||||
|     @extend .code; | ||||
|     background-color: rgba($colorBodyFg, 0.1); | ||||
|     padding: 1em; | ||||
| 	@extend .code; | ||||
| 	background-color: rgba($colorBodyFg, 0.1); | ||||
| 	padding: 1em; | ||||
| } | ||||
|  | ||||
| .disabled, | ||||
| @@ -141,41 +141,12 @@ a.disabled { | ||||
|     cursor: default !important; | ||||
| } | ||||
|  | ||||
| .s-status-missing { | ||||
|     // Labels. Expects .s-status-missing to be applied to mct-representation that contains | ||||
|     // .t-object-label | ||||
|     .t-object-label .t-item-icon:before { | ||||
|         content: $glyph-icon-object-unknown; | ||||
|     } | ||||
|  | ||||
|     // Item, grid item. Expects .s-status-missing to be applied to mct-representation that contains .item.grid-item | ||||
|     .item .t-item-icon-glyph:before { | ||||
|         content: $glyph-icon-object-unknown; | ||||
|     } | ||||
|  | ||||
|     // Object header. Expects .s-status-missing to be applied to mct-representation.object-header | ||||
|     &.object-header { | ||||
|         .type-icon:before { | ||||
|             content: $glyph-icon-object-unknown; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Tree item. Expects .s-status-missing to be applied to .tree-item, | ||||
|     // and mct-representation.search-item | ||||
|     &.tree-item, | ||||
|     &.search-item { | ||||
|         > .rep-object-label .t-item-icon:before { | ||||
|             content: $glyph-icon-object-unknown; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| .align-right { | ||||
|     text-align: right; | ||||
| 	text-align: right; | ||||
| } | ||||
|  | ||||
| .centered { | ||||
|     text-align: center; | ||||
| 	text-align: center; | ||||
| } | ||||
|  | ||||
| .ellipsis { | ||||
| @@ -184,42 +155,24 @@ a.disabled { | ||||
|  | ||||
| .scrolling, | ||||
| .scroll { | ||||
|     overflow: auto; | ||||
| 	overflow: auto; | ||||
| } | ||||
|  | ||||
| .vscroll { | ||||
|     overflow-y: auto; | ||||
| } | ||||
|  | ||||
| .slidable { | ||||
|     cursor: move; // Fallback | ||||
|     cursor: grab; | ||||
|     cursor: -moz-grab; | ||||
|     cursor: -webkit-grab; | ||||
|     &.horz { | ||||
|         cursor: col-resize; | ||||
|     } | ||||
|     &.vert { | ||||
|         cursor: row-resize; | ||||
|     } | ||||
| 	overflow-y: auto; | ||||
| } | ||||
|  | ||||
| .no-margin { | ||||
|     margin: 0; | ||||
| 	margin: 0; | ||||
| } | ||||
|  | ||||
| .ds { | ||||
|     box-shadow: rgba(#000, 0.7) 0 4px 10px 2px; | ||||
| 	box-shadow: rgba(#000, 0.7) 0 4px 10px 2px; | ||||
| } | ||||
|  | ||||
| .hide, | ||||
| .hidden { | ||||
|     display: none !important; | ||||
| } | ||||
|  | ||||
| .hide-nice { | ||||
|     opacity: 0; | ||||
|     pointer-events: none; | ||||
| 	display: none !important; | ||||
| } | ||||
|  | ||||
| .off { | ||||
| @@ -233,7 +186,7 @@ a.disabled { | ||||
| } | ||||
|  | ||||
| .sep { | ||||
|     color: rgba(#fff, 0.2); | ||||
| 	color: rgba(#fff, 0.2); | ||||
| } | ||||
|  | ||||
| .test-stripes { | ||||
|   | ||||
| @@ -40,6 +40,7 @@ $glyph-icon-brackets: '\e929'; | ||||
| $glyph-icon-arrows-out: '\e1000'; | ||||
| $glyph-icon-arrows-right-left: '\e1001'; | ||||
| $glyph-icon-arrows-up-down: '\e1002'; | ||||
| $glyph-icon-box-with-dashed-lines: ''; | ||||
| $glyph-icon-bullet: '\e1004'; | ||||
| $glyph-icon-calendar: '\e1005'; | ||||
| $glyph-icon-chain-links: '\e1006'; | ||||
| @@ -72,11 +73,7 @@ $glyph-icon-T: '\e1032'; | ||||
| $glyph-icon-thumbs-strip: '\e1033'; | ||||
| $glyph-icon-two-parts-both: '\e1034'; | ||||
| $glyph-icon-two-parts-one-only: '\e1035'; | ||||
| $glyph-icon-resync: '\e1036'; | ||||
| $glyph-icon-x-in-circle: '\e1038'; | ||||
| $glyph-icon-brightness: '\e1039'; | ||||
| $glyph-icon-contrast: '\e1040'; | ||||
| $glyph-icon-reset: '\e1037'; | ||||
| $glyph-icon-x-in-circle: '\e1036'; | ||||
| $glyph-icon-activity: '\e1100'; | ||||
| $glyph-icon-activity-mode: '\e1101'; | ||||
| $glyph-icon-autoflow-tabular: '\e1102'; | ||||
| @@ -176,10 +173,6 @@ $glyph-icon-box-with-dashed-lines: '\e1129'; | ||||
| .icon-two-parts-both {  @include glyph($glyph-icon-two-parts-both); } | ||||
| .icon-two-parts-one-only {  @include glyph($glyph-icon-two-parts-one-only); } | ||||
| .icon-x-in-circle {  @include glyph($glyph-icon-x-in-circle); } | ||||
| .icon-brightness {  @include glyph($glyph-icon-brightness); } | ||||
| .icon-contrast {  @include glyph($glyph-icon-contrast); } | ||||
| .icon-reset {  @include glyph($glyph-icon-reset); } | ||||
| .icon-resync {  @include glyph($glyph-icon-resync); } | ||||
| .icon-activity {  @include glyph($glyph-icon-activity); } | ||||
| .icon-activity-mode {  @include glyph($glyph-icon-activity-mode); } | ||||
| .icon-autoflow-tabular {  @include glyph($glyph-icon-autoflow-tabular); } | ||||
| @@ -216,4 +209,4 @@ $glyph-icon-box-with-dashed-lines: '\e1129'; | ||||
| .icon-eye-open-12px {  @include glyph($glyph-icon-eye-open,'symbolsfont-12px'); } | ||||
| .icon-collapse-pane-left-12px {  @include glyph($glyph-icon-collapse-pane-left,'symbolsfont-12px'); } | ||||
| .icon-collapse-pane-right-12px {  @include glyph($glyph-icon-collapse-pane-right,'symbolsfont-12px'); } | ||||
| .icon-folder-12px {  @include glyph($glyph-icon-folder,'symbolsfont-12px'); } | ||||
| .icon-folder-12px {  @include glyph($glyph-icon-folder,'symbolsfont-12px'); } | ||||
| @@ -20,9 +20,9 @@ | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
| @import "effects"; | ||||
| @import "global"; | ||||
| @import "glyphs"; | ||||
| @import "animations"; | ||||
| @import "global"; | ||||
| @import "archetypes"; | ||||
| @import "about"; | ||||
| @import "text"; | ||||
|   | ||||
| @@ -363,13 +363,14 @@ | ||||
|     } | ||||
| } | ||||
|  | ||||
| @mixin cursorGrab() { | ||||
|     cursor: grab; | ||||
|     cursor: -webkit-grab; | ||||
|     &:active { | ||||
|         cursor: grabbing; | ||||
|         cursor: -webkit-grabbing; | ||||
|     } | ||||
| @mixin webkitProp($name, $val) { | ||||
| 	#{$name}: #{$val}; | ||||
| 	-webkit-#{$name}: #{$val}; | ||||
| } | ||||
|  | ||||
| @mixin webkitVal($name, $val) { | ||||
| 	#{$name}: #{$val}; | ||||
| 	#{$name}: -webkit-#{$val}; | ||||
| } | ||||
|  | ||||
| @mixin verticalCenter { | ||||
| @@ -391,14 +392,6 @@ | ||||
|     white-space: nowrap; | ||||
| } | ||||
|  | ||||
| @mixin reverseEllipsis() { | ||||
|     direction: rtl; | ||||
|     unicode-bidi:bidi-override; | ||||
|     overflow: hidden; | ||||
|     white-space: nowrap; | ||||
|     text-overflow: ellipsis; | ||||
| } | ||||
|  | ||||
| @mixin scrollH($showBar: auto) { | ||||
|     overflow-x: $showBar; | ||||
|     overflow-y: hidden; | ||||
|   | ||||
| @@ -122,21 +122,13 @@ | ||||
|     // Default position is upper right | ||||
|     $p: $interiorMargin; | ||||
|     position: absolute; | ||||
|     top: $p; right: $p; bottom: auto; | ||||
|     text-align: right; | ||||
|     top: $p; | ||||
|     right: $p; | ||||
|     z-index: 5; | ||||
| } | ||||
|  | ||||
| .s-local-controls { | ||||
|     @include trans-prop-nice(opacity); | ||||
|     font-size: 0.7rem; | ||||
|     &.s-wrapper-transluc { | ||||
|         // Semi-opaque wrapper to visually distinguish a control | ||||
|         // from the background | ||||
|         background: rgba($colorTransLucBg, 0.7); | ||||
|         box-sizing: border-box; | ||||
|         border-radius: $controlCr; | ||||
|     } | ||||
| } | ||||
|  | ||||
| /******************************************************** CUSTOM CHECKBOXES */ | ||||
| @@ -467,7 +459,7 @@ input[type="search"] { | ||||
|     width: $h; | ||||
|     height: $h; | ||||
|     margin-top: 1 + floor($h/2) * -1; | ||||
|     @include btnSubtle(pullForward($colorBtnBg, 20%)); | ||||
|     @include btnSubtle(pullForward($colorBtnBg, 10%)); | ||||
|     border-radius: 50% !important; | ||||
| } | ||||
|  | ||||
| @@ -661,4 +653,3 @@ body.desktop { | ||||
|         background: $scrollbarTrackColorBg; | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| .l-image-main-wrapper, | ||||
| .l-image-thumbs-wrapper, | ||||
| .image-main { | ||||
| .l-image-thumbs-wrapper { | ||||
| 	@include absPosDefault(0, false); | ||||
| } | ||||
|  | ||||
| @@ -33,7 +32,7 @@ | ||||
|  | ||||
| /*************************************** MAIN IMAGE */ | ||||
|  | ||||
| .image-main, | ||||
| .l-image-main, | ||||
| .l-image-thumb-item .l-thumb { | ||||
| 	background-size: contain; | ||||
| 	background-position: center; | ||||
| @@ -135,54 +134,6 @@ | ||||
| 	} | ||||
| } | ||||
|  | ||||
| /*************************************** LOCAL CONTROLS */ | ||||
| .l-local-controls { | ||||
|     max-width: 200px; | ||||
|     width: 35%; | ||||
|     input[type="range"] { | ||||
|         display: block; | ||||
|         width: 100%; | ||||
|         &:not(:first-child) { | ||||
|             margin-top: $interiorMarginLg; | ||||
|         } | ||||
|  | ||||
|         &:before { | ||||
|             margin-right: $interiorMarginSm; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     .t-reset-btn-holder { | ||||
|         $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; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     &.s-wrapper-transluc { | ||||
|         left: auto; | ||||
|         padding: $interiorMargin $interiorMarginLg; | ||||
|     } | ||||
|  | ||||
|     &.l-flex-row { | ||||
|         align-items: center; | ||||
|     } | ||||
| } | ||||
|  | ||||
| /*************************************** WHEN IN FRAME */ | ||||
| .frame .t-imagery { | ||||
| 	.l-image-main-wrapper { | ||||
|   | ||||
| @@ -51,9 +51,13 @@ table { | ||||
|  | ||||
| 	tbody, .tbody { | ||||
| 		display: table-row-group; | ||||
| 		tr, .tr { | ||||
| 			&:hover { | ||||
| 				background: rgba($colorTabBodyFg, 0.1); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	tr, .tr { | ||||
|         border-top: 1px solid $colorTabBorder; | ||||
| 		display: table-row; | ||||
| 		&:first-child .td { | ||||
| 			border-top: none; | ||||
| @@ -67,12 +71,11 @@ table { | ||||
| 		} | ||||
| 		th, .th, td, .td { | ||||
| 			display: table-cell; | ||||
|             font-size: 0.7rem; | ||||
| 		} | ||||
| 		th, .th { | ||||
| 			border-left: 1px solid $colorTabHeaderBorder; | ||||
| 			color: $colorTabHeaderFg; | ||||
| 			padding: $tabularTdPadTB $tabularTdPadLR; | ||||
| 			padding: $tabularTdPadLR $tabularTdPadLR; | ||||
| 			white-space: nowrap; | ||||
| 			vertical-align: middle; // This is crucial to hiding f**king 4px height injected by browser by default | ||||
| 			&:first-child { | ||||
| @@ -96,6 +99,7 @@ table { | ||||
| 			} | ||||
| 		} | ||||
| 		td, .td { | ||||
| 			border-bottom: 1px solid $colorTabBorder; | ||||
| 			min-width: 20px; | ||||
| 			color: $colorTelemFresh; | ||||
| 			padding: $tabularTdPadTB $tabularTdPadLR; | ||||
|   | ||||
| @@ -55,7 +55,9 @@ | ||||
|         height: 70%; | ||||
|         width: 50%; | ||||
|         min-height: 300px; | ||||
|         max-height: 800px; | ||||
|         min-width: 600px; | ||||
|         max-width: 1000px; | ||||
|         z-index: 101; | ||||
|         > .contents { | ||||
|             $m: $overlayMargin; | ||||
| @@ -139,4 +141,4 @@ | ||||
|     $h: 225px; | ||||
|     min-height: $h; | ||||
|     height: $h; | ||||
| } | ||||
| } | ||||
| @@ -46,42 +46,18 @@ | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     .gl-plot-wrapper-display-area-and-x-axis { | ||||
|         // Holds the plot area and the X-axis only | ||||
|         position: absolute; | ||||
|         top: nth($plotDisplayArea, 1); | ||||
|         right: nth($plotDisplayArea, 2); | ||||
|         bottom: 0; | ||||
|         left: nth($plotDisplayArea, 4); | ||||
|  | ||||
|         .gl-plot-display-area { | ||||
|             //@include test(yellow); | ||||
|             @if $colorPlotBg != none { | ||||
|                 background-color: $colorPlotBg; | ||||
|             } | ||||
|             position: absolute; | ||||
|             top: 0; | ||||
|             right: 0; | ||||
|             bottom: nth($plotDisplayArea, 3); | ||||
|             left: 0; | ||||
|             cursor: crosshair; | ||||
|             border: 1px solid $colorPlotAreaBorder; | ||||
|         } | ||||
|  | ||||
|         .gl-plot-axis-area.gl-plot-x { | ||||
|             //@include test(green); | ||||
|             top: auto; | ||||
|             right: 0; | ||||
|             bottom: 0; | ||||
|             left: 0; | ||||
|             height: $plotXBarH; | ||||
|             width: auto; | ||||
|             overflow: hidden; | ||||
|         } | ||||
|     } | ||||
|  | ||||
| 	.gl-plot-axis-area { | ||||
|         //@include test(); | ||||
| 		position: absolute; | ||||
| 		&.gl-plot-x { | ||||
| 			top: auto; | ||||
| 			right: 0; | ||||
| 			bottom: $interiorMargin; | ||||
| 			left: $plotYBarW; | ||||
| 			height: $plotXBarH; | ||||
| 			width: auto; | ||||
| 			overflow: hidden; | ||||
| 		} | ||||
| 		&.gl-plot-y { | ||||
| 			top: $plotLegendH + $interiorMargin; | ||||
| 			right: auto; | ||||
| @@ -108,6 +84,19 @@ | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	.gl-plot-display-area { | ||||
| 		@if $colorPlotBg != none { | ||||
| 			background-color: $colorPlotBg; | ||||
| 		} | ||||
| 		position: absolute; | ||||
| 		top: nth($plotDisplayArea, 1); | ||||
| 		right: nth($plotDisplayArea, 2); | ||||
| 		bottom: nth($plotDisplayArea, 3); | ||||
| 		left: nth($plotDisplayArea, 4); | ||||
| 		cursor: crosshair; | ||||
| 		border: 1px solid $colorPlotAreaBorder; | ||||
| 	} | ||||
|  | ||||
| 	.gl-plot-label, | ||||
| 	.l-plot-label { | ||||
| 		color: $colorPlotLabelFg; | ||||
| @@ -276,9 +265,13 @@ | ||||
|  | ||||
| .gl-plot-tick, | ||||
| .tick-label { | ||||
|     @include reverseEllipsis(); | ||||
|     direction: rtl; | ||||
|     unicode-bidi:bidi-override; | ||||
| 	font-size: 0.7rem; | ||||
| 	position: absolute; | ||||
| 	overflow: hidden; | ||||
| 	white-space: nowrap; | ||||
| 	text-overflow: ellipsis; | ||||
| 	&.gl-plot-x-tick-label, | ||||
| 	&.tick-label-x { | ||||
| 		right: auto; | ||||
|   | ||||
| @@ -19,7 +19,7 @@ | ||||
|  this source code distribution or the Licensing information page available | ||||
|  at runtime from the About dialog for additional information. | ||||
| --> | ||||
| <a class="s-button key-{{parameters.action.getMetadata().key}} {{parameters.action.getMetadata().cssClass}}" | ||||
| <a class="s-button key-{{parameters.action.getMetadata().key}} {{parameters.action.getMetadata().cssclass}}" | ||||
|    ng-class="{ labeled: parameters.labeled }" | ||||
|    title="{{parameters.action.getMetadata().description}}" | ||||
|    ng-click="parameters.action.perform()"> | ||||
|   | ||||
| @@ -20,7 +20,7 @@ | ||||
|  at runtime from the About dialog for additional information. | ||||
| --> | ||||
| <span ng-controller="ViewSwitcherController"> | ||||
|     <div class="view-switcher menu-element s-menu-button {{ngModel.selected.cssClass}}" | ||||
|     <div class="view-switcher menu-element s-menu-button {{ngModel.selected.cssclass}}" | ||||
| 	    ng-if="view.length > 1" | ||||
| 	    ng-controller="ClickAwayController as toggle"> | ||||
|  | ||||
| @@ -33,7 +33,7 @@ | ||||
|             <ul> | ||||
|                 <li ng-repeat="option in view" | ||||
|                     ng-click="ngModel.selected = option; toggle.setState(false)" | ||||
|                     class="{{option.cssClass}}"> | ||||
|                     class="{{option.cssclass}}"> | ||||
|                     {{option.name}} | ||||
|                 </li> | ||||
|             </ul> | ||||
|   | ||||
| @@ -25,7 +25,7 @@ | ||||
|             <li ng-repeat="menuAction in menuActions" | ||||
|                 ng-click="menuAction.perform()" | ||||
|                 title="{{menuAction.getMetadata().description}}" | ||||
|                 class="{{menuAction.getMetadata().cssClass}}"> | ||||
|                 class="{{menuAction.getMetadata().cssclass}}"> | ||||
|                 {{menuAction.getMetadata().name}} | ||||
|             </li> | ||||
|         </ul> | ||||
|   | ||||
| @@ -19,9 +19,6 @@ | ||||
|  this source code distribution or the Licensing information page available | ||||
|  at runtime from the About dialog for additional information. | ||||
| --> | ||||
| <mct-tree root-object="domainObject" | ||||
|     selected-object="ngModel.selectedObject" | ||||
|     on-selection="ngModel.onSelection" | ||||
|     allow-selection="ngModel.allowSelection"> | ||||
| <mct-tree mct-object="domainObject" mct-model="ngModel.selectedObject"> | ||||
| </mct-tree> | ||||
|  | ||||
|   | ||||
| @@ -26,54 +26,25 @@ define([ | ||||
| ], function (angular, TreeView) { | ||||
|     function MCTTree(gestureService) { | ||||
|         function link(scope, element) { | ||||
|             if (!scope.allowSelection) { | ||||
|                 scope.allowSelection = function () { | ||||
|                     return true; | ||||
|                 }; | ||||
|             } | ||||
|             if (!scope.onSelection) { | ||||
|                 scope.onSelection = function () {}; | ||||
|             } | ||||
|             var currentSelection = scope.selectedObject; | ||||
|             var treeView = new TreeView(gestureService); | ||||
|  | ||||
|             function setSelection(domainObject, event) { | ||||
|                 if (currentSelection === domainObject) { | ||||
|                     return; | ||||
|                 } | ||||
|                 if (!scope.allowSelection(domainObject)) { | ||||
|                     treeView.value(currentSelection); | ||||
|                     return; | ||||
|                 } | ||||
|                 currentSelection = domainObject; | ||||
|                 scope.onSelection(domainObject); | ||||
|                 scope.selectedObject = domainObject; | ||||
|                 if (event && event instanceof MouseEvent) { | ||||
|                     scope.$apply(); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             var unobserve = treeView.observe(setSelection); | ||||
|             var treeView = new TreeView(gestureService), | ||||
|                 unobserve = treeView.observe(function (domainObject) { | ||||
|                     if (scope.mctModel !== domainObject) { | ||||
|                         scope.mctModel = domainObject; | ||||
|                         scope.$apply(); | ||||
|                     } | ||||
|                 }); | ||||
|  | ||||
|             element.append(angular.element(treeView.elements())); | ||||
|  | ||||
|             scope.$watch('selectedObject', function (object) { | ||||
|                 currentSelection = object; | ||||
|                 treeView.value(object); | ||||
|             }); | ||||
|             scope.$watch('rootObject', treeView.model.bind(treeView)); | ||||
|             scope.$watch('mctModel', treeView.value.bind(treeView)); | ||||
|             scope.$watch('mctObject', treeView.model.bind(treeView)); | ||||
|             scope.$on('$destroy', unobserve); | ||||
|         } | ||||
|  | ||||
|         return { | ||||
|             restrict: "E", | ||||
|             link: link, | ||||
|             scope: { | ||||
|                 rootObject: "=", | ||||
|                 selectedObject: "=", | ||||
|                 onSelection: "=?", | ||||
|                 allowSelection: "=?" | ||||
|             } | ||||
|             scope: { mctObject: "=", mctModel: "=" } | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -49,8 +49,8 @@ define([ | ||||
|  | ||||
|         this.labelView = new TreeLabelView(gestureService); | ||||
|  | ||||
|         $(this.labelView.elements()).on('click', function (event) { | ||||
|             selectFn(this.activeObject, event); | ||||
|         $(this.labelView.elements()).on('click', function () { | ||||
|             selectFn(this.activeObject); | ||||
|         }.bind(this)); | ||||
|  | ||||
|         this.li.append($(nodeTemplate)); | ||||
|   | ||||
| @@ -109,11 +109,11 @@ define([ | ||||
|         }.bind(this)); | ||||
|     }; | ||||
|  | ||||
|     TreeView.prototype.value = function (domainObject, event) { | ||||
|     TreeView.prototype.value = function (domainObject) { | ||||
|         this.selectedObject = domainObject; | ||||
|         this.updateNodeViewSelection(); | ||||
|         this.callbacks.forEach(function (callback) { | ||||
|             callback(domainObject, event); | ||||
|             callback(domainObject); | ||||
|         }); | ||||
|     }; | ||||
|  | ||||
|   | ||||
| @@ -19,12 +19,10 @@ | ||||
|  * this source code distribution or the Licensing information page available | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
| /* global console*/ | ||||
|  | ||||
| define([ | ||||
|     '../../src/directives/MCTTree', | ||||
|     '../../src/ui/TreeView' | ||||
| ], function (MCTTree, TreeView) { | ||||
|     '../../src/directives/MCTTree' | ||||
| ], function (MCTTree) { | ||||
|     describe("The mct-tree directive", function () { | ||||
|         var mockParse, | ||||
|             mockGestureService, | ||||
| @@ -52,7 +50,6 @@ define([ | ||||
|             mockExpr = jasmine.createSpy('expr'); | ||||
|             mockExpr.assign = jasmine.createSpy('assign'); | ||||
|             mockParse.andReturn(mockExpr); | ||||
|             spyOn(TreeView.prototype, 'observe').andCallThrough(); | ||||
|  | ||||
|             mctTree = new MCTTree(mockParse, mockGestureService); | ||||
|         }); | ||||
| @@ -61,13 +58,8 @@ define([ | ||||
|             expect(mctTree.restrict).toEqual("E"); | ||||
|         }); | ||||
|  | ||||
|         it("two-way binds", function () { | ||||
|             expect(mctTree.scope).toEqual({ | ||||
|                 rootObject: "=", | ||||
|                 selectedObject: "=", | ||||
|                 allowSelection: "=?", | ||||
|                 onSelection: "=?" | ||||
|             }); | ||||
|         it("two-way binds to mctObject and mctModel", function () { | ||||
|             expect(mctTree.scope).toEqual({ mctObject: "=", mctModel: "=" }); | ||||
|         }); | ||||
|  | ||||
|         describe("link", function () { | ||||
| @@ -89,16 +81,16 @@ define([ | ||||
|                 expect(mockElement.append).toHaveBeenCalled(); | ||||
|             }); | ||||
|  | ||||
|             it("watches for selected-object expression in the parent", function () { | ||||
|             it("watches for mct-model's expression in the parent", function () { | ||||
|                 expect(mockScope.$watch).toHaveBeenCalledWith( | ||||
|                     "selectedObject", | ||||
|                     "mctModel", | ||||
|                     jasmine.any(Function) | ||||
|                 ); | ||||
|             }); | ||||
|  | ||||
|             it("watches for changes to root-object", function () { | ||||
|             it("watches for changes to mct-object", function () { | ||||
|                 expect(mockScope.$watch).toHaveBeenCalledWith( | ||||
|                     "rootObject", | ||||
|                     "mctObject", | ||||
|                     jasmine.any(Function) | ||||
|                 ); | ||||
|             }); | ||||
| @@ -110,10 +102,6 @@ define([ | ||||
|                 ); | ||||
|             }); | ||||
|  | ||||
|             it("watches for changes in tree view", function () { | ||||
|  | ||||
|             }); | ||||
|  | ||||
|             // https://github.com/nasa/openmct/issues/1114 | ||||
|             it("does not trigger $apply during $watches", function () { | ||||
|                 mockScope.mctObject = makeMockDomainObject('root'); | ||||
| @@ -123,18 +111,14 @@ define([ | ||||
|                 }); | ||||
|                 expect(mockScope.$apply).not.toHaveBeenCalled(); | ||||
|             }); | ||||
|             it("does trigger $apply from tree manipulation", function () { | ||||
|                 if (/PhantomJS/g.test(window.navigator.userAgent)) { | ||||
|                     console.log('Unable to run test in PhantomJS due to lack of support for event constructors'); | ||||
|                     return; | ||||
|                 } | ||||
|             it("does trigger $apply from other value changes", function () { | ||||
|                 // White-boxy; we know this is the setter for the tree's value | ||||
|                 var treeValueFn = TreeView.prototype.observe.calls[0].args[0]; | ||||
|                 var treeValueFn = mockScope.$watch.calls[0].args[1]; | ||||
|  | ||||
|                 mockScope.mctObject = makeMockDomainObject('root'); | ||||
|                 mockScope.mctMode = makeMockDomainObject('selection'); | ||||
|  | ||||
|                 treeValueFn(makeMockDomainObject('other'), new MouseEvent("click")); | ||||
|                 treeValueFn(makeMockDomainObject('other')); | ||||
|  | ||||
|                 expect(mockScope.$apply).toHaveBeenCalled(); | ||||
|             }); | ||||
|   | ||||
| @@ -289,9 +289,8 @@ define([ | ||||
|             }); | ||||
|  | ||||
|             it("notifies listeners when value is changed", function () { | ||||
|                 treeView.value(mockDomainObject, {some: event}); | ||||
|                 expect(mockCallback) | ||||
|                     .toHaveBeenCalledWith(mockDomainObject, {some: event}); | ||||
|                 treeView.value(mockDomainObject); | ||||
|                 expect(mockCallback).toHaveBeenCalledWith(mockDomainObject); | ||||
|             }); | ||||
|  | ||||
|             it("does not notify listeners when deactivated", function () { | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user