Compare commits
	
		
			105 Commits
		
	
	
		
			demo-2017.
			...
			eagle-spri
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					eca3c57bd8 | ||
| 
						 | 
					7753703034 | ||
| 
						 | 
					f9060a485d | ||
| 
						 | 
					39a7f43cd0 | ||
| 
						 | 
					5103207a70 | ||
| 
						 | 
					8c2cc90f04 | ||
| 
						 | 
					ce431848b3 | ||
| 
						 | 
					5726fe6313 | ||
| 
						 | 
					6145843e86 | ||
| 
						 | 
					0225cbab6a | ||
| 
						 | 
					e477beb587 | ||
| 
						 | 
					ee5d59024a | ||
| 
						 | 
					5288dadafb | ||
| 
						 | 
					7f3cc09cbc | ||
| 
						 | 
					94fa70abb1 | ||
| 
						 | 
					12574a1333 | ||
| 
						 | 
					dc91a94f0e | ||
| 
						 | 
					0243aa6584 | ||
| 
						 | 
					e5d869f01e | ||
| 
						 | 
					d4e3e6689c | ||
| 
						 | 
					0363d0e8ad | ||
| 
						 | 
					3669e776a9 | ||
| 
						 | 
					5d3adc6a7f | ||
| 
						 | 
					c1b2db848a | ||
| 
						 | 
					5d19294c11 | ||
| 
						 | 
					8c72729a2a | ||
| 
						 | 
					9b8d5f3f9c | ||
| 
						 | 
					129ab1791b | ||
| 
						 | 
					d03f323a9b | ||
| 
						 | 
					54a453e5a0 | ||
| 
						 | 
					14894cf197 | ||
| 
						 | 
					0c6786198a | ||
| 
						 | 
					6d077b775d | ||
| 
						 | 
					144437a06e | ||
| 
						 | 
					557cd91b21 | ||
| 
						 | 
					39d3e92094 | ||
| 
						 | 
					7529a86d01 | ||
| 
						 | 
					d34e36831c | ||
| 
						 | 
					aa8fa9168a | ||
| 
						 | 
					3f1b7e0a87 | ||
| 
						 | 
					5ec3b98d1c | ||
| 
						 | 
					1ad5094b72 | ||
| 
						 | 
					b54ee2257e | ||
| 
						 | 
					fcef4274e5 | ||
| 
						 | 
					744a5340d3 | ||
| 
						 | 
					d140051054 | ||
| 
						 | 
					8da74f2665 | ||
| 
						 | 
					2390278b97 | ||
| 
						 | 
					8a66731271 | ||
| 
						 | 
					0a9ea48355 | ||
| 
						 | 
					01d93306f3 | ||
| 
						 | 
					0588f9190a | ||
| 
						 | 
					1378b57567 | ||
| 
						 | 
					9e12886c66 | ||
| 
						 | 
					2d352ac574 | ||
| 
						 | 
					284dec4903 | ||
| 
						 | 
					5a0656c700 | ||
| 
						 | 
					425655bae0 | ||
| 
						 | 
					50b4d5cb28 | ||
| 
						 | 
					bc62d7d5ae | ||
| 
						 | 
					c0dcf4495e | ||
| 
						 | 
					a51b9bc63f | ||
| 
						 | 
					ff003c3dab | ||
| 
						 | 
					de7c4d2ce3 | ||
| 
						 | 
					4b07930305 | ||
| 
						 | 
					5a49ac16b1 | ||
| 
						 | 
					91b150c064 | ||
| 
						 | 
					9506d309b0 | ||
| 
						 | 
					c9bd60f50e | ||
| 
						 | 
					cf15ff5c07 | ||
| 
						 | 
					6bbdfcdfbe | ||
| 
						 | 
					06e93ff520 | ||
| 
						 | 
					550e7a15e6 | ||
| 
						 | 
					71c54cd541 | ||
| 
						 | 
					e81b8e53dc | ||
| 
						 | 
					84e6928f54 | ||
| 
						 | 
					ba688fe62c | ||
| 
						 | 
					c533e10352 | ||
| 
						 | 
					04f47b3db6 | ||
| 
						 | 
					717fa5edf4 | ||
| 
						 | 
					14f5f048fb | ||
| 
						 | 
					72929500d3 | ||
| 
						 | 
					471adde923 | ||
| 
						 | 
					6c5d5f3d00 | ||
| 
						 | 
					2262fef29b | ||
| 
						 | 
					bda30f1475 | ||
| 
						 | 
					bfec434369 | ||
| 
						 | 
					14df350994 | ||
| 
						 | 
					80582f5e8d | ||
| 
						 | 
					7442768ced | ||
| 
						 | 
					77c7bdfdec | ||
| 
						 | 
					07d9769966 | ||
| 
						 | 
					385b6177b2 | ||
| 
						 | 
					7f68d26433 | ||
| 
						 | 
					d7b44f8d09 | ||
| 
						 | 
					c4cd36e15b | ||
| 
						 | 
					618a6e7e8d | ||
| 
						 | 
					63a8c91f71 | ||
| 
						 | 
					e59020fec7 | ||
| 
						 | 
					a2e424203a | ||
| 
						 | 
					16853644cb | ||
| 
						 | 
					2272766c57 | ||
| 
						 | 
					1d9cdea2d4 | ||
| 
						 | 
					715219c44d | ||
| 
						 | 
					00dc2875bf | 
@@ -21,5 +21,6 @@
 | 
			
		||||
    "shadow": "outer",
 | 
			
		||||
    "strict": "implied",
 | 
			
		||||
    "undef": true,
 | 
			
		||||
    "unused": "vars"
 | 
			
		||||
    "unused": "vars",
 | 
			
		||||
    "latedef": "nofunc"
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										15
									
								
								API.md
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								API.md
									
									
									
									
									
								
							@@ -879,6 +879,21 @@ openmct.install(openmct.plugins.CouchDB('http://localhost:9200'))
 | 
			
		||||
* `openmct.plugins.Espresso` and `openmct.plugins.Snow` are two different
 | 
			
		||||
  themes (dark and light) available for Open MCT. Note that at least one
 | 
			
		||||
  of these themes must be installed for Open MCT to appear correctly.
 | 
			
		||||
* `openmct.plugins.URLIndicatorPlugin` adds an indicator which shows the
 | 
			
		||||
availability of a URL with the following options: 
 | 
			
		||||
  - `url` : URL to indicate the status of
 | 
			
		||||
  - `cssClass`: Icon to show in the status bar, defaults to `icon-database`, [list of all icons](https://nasa.github.io/openmct/style-guide/#/browse/styleguide:home?view=items)
 | 
			
		||||
  - `interval`: Interval between checking the connection, defaults to `10000`
 | 
			
		||||
  - `label` Name showing up as text in the status bar, defaults to url
 | 
			
		||||
```javascript
 | 
			
		||||
openmct.install(openmct.plugins.URLIndicatorPlugin({
 | 
			
		||||
  url: 'http://google.com',
 | 
			
		||||
  cssClass: 'check',
 | 
			
		||||
  interval: 10000,
 | 
			
		||||
  label: 'Google'
 | 
			
		||||
 })
 | 
			
		||||
);
 | 
			
		||||
```
 | 
			
		||||
* `openmct.plugins.LocalStorage` provides persistence of user-created
 | 
			
		||||
  objects in browser-local storage. This is particularly useful in
 | 
			
		||||
  development environments.
 | 
			
		||||
 
 | 
			
		||||
@@ -88,7 +88,7 @@ and [`gulp`](http://gulpjs.com/).
 | 
			
		||||
 | 
			
		||||
To build Open MCT for deployment:
 | 
			
		||||
 | 
			
		||||
`npm run prepublish`
 | 
			
		||||
`npm run prepare`
 | 
			
		||||
 | 
			
		||||
This will compile and minify JavaScript sources, as well as copy over assets.
 | 
			
		||||
The contents of the `dist` folder will contain a runnable Open MCT
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,7 @@
 | 
			
		||||
    "node-uuid": "^1.4.7",
 | 
			
		||||
    "comma-separated-values": "^3.6.4",
 | 
			
		||||
    "file-saver": "^1.3.3",
 | 
			
		||||
    "zepto": "1.2.0",
 | 
			
		||||
    "zepto": "^1.1.6",
 | 
			
		||||
    "eventemitter3": "^1.2.0",
 | 
			
		||||
    "lodash": "3.10.1",
 | 
			
		||||
    "almond": "~0.3.2",
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										16
									
								
								circle.yml
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								circle.yml
									
									
									
									
									
								
							@@ -1,15 +1,17 @@
 | 
			
		||||
machine:
 | 
			
		||||
  node:
 | 
			
		||||
    version: 4.7.0
 | 
			
		||||
 | 
			
		||||
dependencies:
 | 
			
		||||
  pre:
 | 
			
		||||
    - npm install -g npm@latest
 | 
			
		||||
 | 
			
		||||
deployment:
 | 
			
		||||
  production:
 | 
			
		||||
    branch: master
 | 
			
		||||
    commands:
 | 
			
		||||
        - npm install canvas nomnoml
 | 
			
		||||
        - ./build-docs.sh
 | 
			
		||||
        - git fetch --unshallow
 | 
			
		||||
        - git push git@heroku.com:openmctweb-demo.git $CIRCLE_SHA1:refs/heads/master
 | 
			
		||||
  openmct-demo:
 | 
			
		||||
    branch: live_demo
 | 
			
		||||
    heroku:
 | 
			
		||||
      appname: openmct-demo
 | 
			
		||||
  openmctweb-staging-deux:
 | 
			
		||||
    branch: mobile
 | 
			
		||||
    heroku:
 | 
			
		||||
@@ -22,4 +24,4 @@ test:
 | 
			
		||||
general:
 | 
			
		||||
  branches:
 | 
			
		||||
    ignore:
 | 
			
		||||
      - gh-pages
 | 
			
		||||
      - gh-pages
 | 
			
		||||
@@ -2283,7 +2283,7 @@ To install build dependencies (only needs to be run once):
 | 
			
		||||
 | 
			
		||||
To build:
 | 
			
		||||
 | 
			
		||||
`npm run prepublish`
 | 
			
		||||
`npm run prepare`
 | 
			
		||||
 | 
			
		||||
This will compile and minify JavaScript sources, as well as copy over assets.
 | 
			
		||||
The contents of the `dist` folder will contain a runnable Open MCT
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										121
									
								
								docs/src/guide/security.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								docs/src/guide/security.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,121 @@
 | 
			
		||||
# Security Guide
 | 
			
		||||
 | 
			
		||||
Open MCT is a rich client with plugin support that executes as a single page
 | 
			
		||||
web application in a browser environment. Security concerns and
 | 
			
		||||
vulnerabilities associated with the web as a platform should be considered
 | 
			
		||||
before deploying Open MCT (or any other web application) for mission or
 | 
			
		||||
production usage.
 | 
			
		||||
 | 
			
		||||
This document describes several important points to consider when developing
 | 
			
		||||
for or deploying Open MCT securely. Other resources such as
 | 
			
		||||
[Open Web Application Security Project (OWASP)](https://www.owasp.org)
 | 
			
		||||
provide a deeper and more general overview of security for web applications.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## Security Model
 | 
			
		||||
 | 
			
		||||
Open MCT has been architected assuming the following deployment pattern:
 | 
			
		||||
 | 
			
		||||
* A tagged, tested Open MCT version will be used.
 | 
			
		||||
* Externally authored plugins will be installed.
 | 
			
		||||
* A server will provide persistent storage, telemetry, and other shared data.
 | 
			
		||||
* Authorization, authentication, and auditing will be handled by a server.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## Security Procedures
 | 
			
		||||
 | 
			
		||||
The Open MCT team secures our code base using a combination of code review,
 | 
			
		||||
dependency review, and periodic security reviews. Static analysis performed 
 | 
			
		||||
during automated verification additionally safeguards against common 
 | 
			
		||||
coding errors which may result in vulnerabilities.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Code Review
 | 
			
		||||
 | 
			
		||||
All contributions are reviewed by internal team members. External
 | 
			
		||||
contributors receive increased scrutiny for security and quality,
 | 
			
		||||
and must sign a licensing agreement.
 | 
			
		||||
 | 
			
		||||
### Dependency Review
 | 
			
		||||
 | 
			
		||||
Before integrating third-party dependencies, they are reviewed for security
 | 
			
		||||
and quality, with consideration given to authors and users of these
 | 
			
		||||
dependencies, as well as review of open source code.
 | 
			
		||||
 | 
			
		||||
### Periodic Security Reviews
 | 
			
		||||
 | 
			
		||||
Open MCT's code, design, and architecture are periodically reviewed
 | 
			
		||||
(approximately annually) for common security issues, such as the
 | 
			
		||||
[OWASP Top Ten](https://www.owasp.org/index.php/Category:OWASP_Top_Ten_Project).
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## Security Concerns
 | 
			
		||||
 | 
			
		||||
Certain security concerns deserve special attention when deploying Open MCT,
 | 
			
		||||
or when authoring plugins.
 | 
			
		||||
 | 
			
		||||
### Identity Spoofing
 | 
			
		||||
 | 
			
		||||
Open MCT issues calls to web services with the privileges of a logged in user.
 | 
			
		||||
Compromised sources (either for Open MCT itself or a plugin) could
 | 
			
		||||
therefore allow malicious code to execute with those privileges.
 | 
			
		||||
 | 
			
		||||
To avoid this:
 | 
			
		||||
 | 
			
		||||
* Serve Open MCT and other scripts over SSL (https rather than http)
 | 
			
		||||
  to prevent man-in-the-middle attacks.
 | 
			
		||||
* Exercise precautions such as security reviews for any plugins or
 | 
			
		||||
  applications built for or with Open MCT to reject malicious changes.
 | 
			
		||||
 | 
			
		||||
### Information Disclosure
 | 
			
		||||
 | 
			
		||||
If Open MCT is used to handle or display sensitive data, any components
 | 
			
		||||
(such as adapter plugins) must take care to avoid leaking or disclosing
 | 
			
		||||
this information. For example, avoid sending sensitive data to third-party
 | 
			
		||||
servers or insecure APIs.
 | 
			
		||||
 | 
			
		||||
### Data Tampering
 | 
			
		||||
 | 
			
		||||
The web application architecture leaves open the possibility that direct
 | 
			
		||||
calls will be made to back-end services, circumventing Open MCT entirely.
 | 
			
		||||
As such, Open MCT assumes that server components will perform any necessary
 | 
			
		||||
data validation during calls issues to the server.
 | 
			
		||||
 | 
			
		||||
Additionally, plugins which serialize and write data to the server must
 | 
			
		||||
escape that data to avoid database injection attacks, and similar.
 | 
			
		||||
 | 
			
		||||
### Repudiation
 | 
			
		||||
 | 
			
		||||
Open MCT assumes that servers log any relevant interactions and associates
 | 
			
		||||
these with a user identity; the specific user actions taken within the
 | 
			
		||||
application are assumed not to be of concern for auditing.
 | 
			
		||||
 | 
			
		||||
In the absence of server-side logging, users may disclaim (maliciously,
 | 
			
		||||
mistakenly, or otherwise) actions taken within the system without any
 | 
			
		||||
way to prove otherwise.
 | 
			
		||||
 | 
			
		||||
If keeping client-level interactions is important, this will need to be
 | 
			
		||||
implemented via a plugin.
 | 
			
		||||
 | 
			
		||||
### Denial-of-service
 | 
			
		||||
 | 
			
		||||
Open MCT assumes that server-side components will be insulated against
 | 
			
		||||
denial-of-service attacks. Services should only permit resource-intensive
 | 
			
		||||
tasks to be initiated by known or trusted users.
 | 
			
		||||
 | 
			
		||||
### Elevation of Privilege
 | 
			
		||||
 | 
			
		||||
Corollary to the assumption that servers guide against identity spoofing,
 | 
			
		||||
Open MCT assumes that services do not allow a user to act with
 | 
			
		||||
inappropriately escalated privileges. Open MCT cannot protect against
 | 
			
		||||
such escalation; in the clearest case, a malicious actor could interact
 | 
			
		||||
with web services directly to exploit such a vulnerability.
 | 
			
		||||
 | 
			
		||||
## Additional Reading
 | 
			
		||||
 | 
			
		||||
The following resources have been used as a basis for identifying potential
 | 
			
		||||
security threats to Open MCT deployments in preparation of this document:
 | 
			
		||||
 | 
			
		||||
* [STRIDE model](https://www.owasp.org/index.php/Threat_Risk_Modeling#STRIDE)
 | 
			
		||||
* [Attack Surface Analysis Cheat Sheet](https://www.owasp.org/index.php/Attack_Surface_Analysis_Cheat_Sheet)
 | 
			
		||||
* [XSS Prevention Cheat Sheet](https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet)
 | 
			
		||||
@@ -30,7 +30,8 @@ define([
 | 
			
		||||
        amplitude: 1,
 | 
			
		||||
        period: 10,
 | 
			
		||||
        offset: 0,
 | 
			
		||||
        dataRateInHz: 1
 | 
			
		||||
        dataRateInHz: 1,
 | 
			
		||||
        phase: 0
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    function GeneratorProvider() {
 | 
			
		||||
@@ -50,7 +51,8 @@ define([
 | 
			
		||||
            'amplitude',
 | 
			
		||||
            'period',
 | 
			
		||||
            'offset',
 | 
			
		||||
            'dataRateInHz'
 | 
			
		||||
            'dataRateInHz',
 | 
			
		||||
            'phase',
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        request = request || {};
 | 
			
		||||
@@ -69,7 +71,7 @@ define([
 | 
			
		||||
            }
 | 
			
		||||
            workerRequest[prop] = Number(workerRequest[prop]);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        workerRequest.name = domainObject.name;
 | 
			
		||||
        return workerRequest;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										80
									
								
								example/generator/StateGeneratorProvider.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								example/generator/StateGeneratorProvider.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,80 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * Open MCT, Copyright (c) 2014-2017, United States Government
 | 
			
		||||
 * as represented by the Administrator of the National Aeronautics and Space
 | 
			
		||||
 * Administration. All rights reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Open MCT is licensed under the Apache License, Version 2.0 (the
 | 
			
		||||
 * "License"); you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 * http://www.apache.org/licenses/LICENSE-2.0.
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
 * License for the specific language governing permissions and limitations
 | 
			
		||||
 * under the License.
 | 
			
		||||
 *
 | 
			
		||||
 * Open MCT includes source code licensed under additional open source
 | 
			
		||||
 * licenses. See the Open Source Licenses file (LICENSES.md) included with
 | 
			
		||||
 * this source code distribution or the Licensing information page available
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
define([
 | 
			
		||||
 | 
			
		||||
], function (
 | 
			
		||||
 | 
			
		||||
) {
 | 
			
		||||
 | 
			
		||||
    function StateGeneratorProvider() {
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function pointForTimestamp(timestamp, duration, name) {
 | 
			
		||||
        return {
 | 
			
		||||
            name: name,
 | 
			
		||||
            utc: Math.floor(timestamp / duration) * duration,
 | 
			
		||||
            value: Math.floor(timestamp / duration) % 2
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    StateGeneratorProvider.prototype.supportsSubscribe = function (domainObject) {
 | 
			
		||||
        return domainObject.type === 'example.state-generator';
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    StateGeneratorProvider.prototype.subscribe = function (domainObject, callback) {
 | 
			
		||||
        var duration = domainObject.telemetry.duration * 1000;
 | 
			
		||||
 | 
			
		||||
        var interval = setInterval(function () {
 | 
			
		||||
            var now = Date.now();
 | 
			
		||||
            callback(pointForTimestamp(now, duration, domainObject.name));
 | 
			
		||||
        }, duration);
 | 
			
		||||
 | 
			
		||||
        return function () {
 | 
			
		||||
            clearInterval(interval);
 | 
			
		||||
        };
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    StateGeneratorProvider.prototype.supportsRequest = function (domainObject, options) {
 | 
			
		||||
        return domainObject.type === 'example.state-generator';
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    StateGeneratorProvider.prototype.request = function (domainObject, options) {
 | 
			
		||||
        var start = options.start;
 | 
			
		||||
        var end = options.end;
 | 
			
		||||
        var duration = domainObject.telemetry.duration * 1000;
 | 
			
		||||
        if (options.strategy === 'latest' || options.size === 1) {
 | 
			
		||||
            start = end;
 | 
			
		||||
        }
 | 
			
		||||
        var data = [];
 | 
			
		||||
        while (start <= end && data.length < 5000) {
 | 
			
		||||
            data.push(pointForTimestamp(start, duration, domainObject.name));
 | 
			
		||||
            start += 5000;
 | 
			
		||||
        }
 | 
			
		||||
        return Promise.resolve(data);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return StateGeneratorProvider;
 | 
			
		||||
 | 
			
		||||
});
 | 
			
		||||
@@ -44,9 +44,7 @@ define([
 | 
			
		||||
        message = message.data;
 | 
			
		||||
        var callback = this.callbacks[message.id];
 | 
			
		||||
        if (callback) {
 | 
			
		||||
            if (callback(message)) {
 | 
			
		||||
                delete this.callbacks[message.id];
 | 
			
		||||
            }
 | 
			
		||||
            callback(message);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
@@ -72,6 +70,7 @@ define([
 | 
			
		||||
            deferred.resolve = resolve;
 | 
			
		||||
            deferred.reject = reject;
 | 
			
		||||
        });
 | 
			
		||||
        var messageId;
 | 
			
		||||
 | 
			
		||||
        function callback(message) {
 | 
			
		||||
            if (message.error) {
 | 
			
		||||
@@ -79,33 +78,27 @@ define([
 | 
			
		||||
            } else {
 | 
			
		||||
                deferred.resolve(message.data);
 | 
			
		||||
            }
 | 
			
		||||
            return true;
 | 
			
		||||
            delete this.callbacks[messageId];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.dispatch('request', request, callback);
 | 
			
		||||
        messageId = this.dispatch('request', request, callback.bind(this));
 | 
			
		||||
 | 
			
		||||
        return promise;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    WorkerInterface.prototype.subscribe = function (request, cb) {
 | 
			
		||||
        var isCancelled = false;
 | 
			
		||||
 | 
			
		||||
        var callback = function (message) {
 | 
			
		||||
            if (isCancelled) {
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
        function callback(message) {
 | 
			
		||||
            cb(message.data);
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        var messageId = this.dispatch('subscribe', request, callback)
 | 
			
		||||
        var messageId = this.dispatch('subscribe', request, callback);
 | 
			
		||||
 | 
			
		||||
        return function () {
 | 
			
		||||
            isCancelled = true;
 | 
			
		||||
            this.dispatch('unsubscribe', {
 | 
			
		||||
                id: messageId
 | 
			
		||||
            });
 | 
			
		||||
            delete this.callbacks[messageId];
 | 
			
		||||
        }.bind(this);
 | 
			
		||||
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -62,10 +62,11 @@
 | 
			
		||||
                self.postMessage({
 | 
			
		||||
                    id: message.id,
 | 
			
		||||
                    data: {
 | 
			
		||||
                        name: data.name,
 | 
			
		||||
                        utc: nextStep,
 | 
			
		||||
                        yesterday: nextStep - 60*60*24*1000,
 | 
			
		||||
                        sin: sin(nextStep, data.period, data.amplitude, data.offset),
 | 
			
		||||
                        cos: cos(nextStep, data.period, data.amplitude, data.offset)
 | 
			
		||||
                        sin: sin(nextStep, data.period, data.amplitude, data.offset, data.phase),
 | 
			
		||||
                        cos: cos(nextStep, data.period, data.amplitude, data.offset, data.phase)
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
                nextStep += step;
 | 
			
		||||
@@ -82,21 +83,22 @@
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function onRequest(message) {
 | 
			
		||||
        var data = message.data;
 | 
			
		||||
        if (data.end == undefined) {
 | 
			
		||||
            data.end = Date.now();
 | 
			
		||||
        var request = message.data;
 | 
			
		||||
        if (request.end == undefined) {
 | 
			
		||||
            request.end = Date.now();
 | 
			
		||||
        }
 | 
			
		||||
        if (data.start == undefined){
 | 
			
		||||
            data.start = data.end - FIFTEEN_MINUTES;
 | 
			
		||||
        if (request.start == undefined){
 | 
			
		||||
            request.start = request.end - FIFTEEN_MINUTES;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var now = Date.now();
 | 
			
		||||
        var start = data.start;
 | 
			
		||||
        var end = data.end > now ? now : data.end;
 | 
			
		||||
        var amplitude = data.amplitude;
 | 
			
		||||
        var period = data.period;
 | 
			
		||||
        var offset = data.offset;
 | 
			
		||||
        var dataRateInHz = data.dataRateInHz;
 | 
			
		||||
        var start = request.start;
 | 
			
		||||
        var end = request.end > now ? now : request.end;
 | 
			
		||||
        var amplitude = request.amplitude;
 | 
			
		||||
        var period = request.period;
 | 
			
		||||
        var offset = request.offset;
 | 
			
		||||
        var dataRateInHz = request.dataRateInHz;
 | 
			
		||||
        var phase = request.phase;
 | 
			
		||||
 | 
			
		||||
        var step = 1000 / dataRateInHz;
 | 
			
		||||
        var nextStep = start - (start % step) + step;
 | 
			
		||||
@@ -105,10 +107,11 @@
 | 
			
		||||
 | 
			
		||||
        for (; nextStep < end && data.length < 5000; nextStep += step) {
 | 
			
		||||
            data.push({
 | 
			
		||||
                name: request.name,
 | 
			
		||||
                utc: nextStep,
 | 
			
		||||
                yesterday: nextStep - 60*60*24*1000,
 | 
			
		||||
                sin: sin(nextStep, period, amplitude, offset),
 | 
			
		||||
                cos: cos(nextStep, period, amplitude, offset)
 | 
			
		||||
                sin: sin(nextStep, period, amplitude, offset, phase),
 | 
			
		||||
                cos: cos(nextStep, period, amplitude, offset, phase)
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        self.postMessage({
 | 
			
		||||
@@ -117,14 +120,14 @@
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function cos(timestamp, period, amplitude, offset) {
 | 
			
		||||
    function cos(timestamp, period, amplitude, offset, phase) {
 | 
			
		||||
        return amplitude *
 | 
			
		||||
            Math.cos(timestamp / period / 1000 * Math.PI * 2) + offset;
 | 
			
		||||
            Math.cos(phase + (timestamp / period / 1000 * Math.PI * 2)) + offset;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function sin(timestamp, period, amplitude, offset) {
 | 
			
		||||
    function sin(timestamp, period, amplitude, offset, phase) {
 | 
			
		||||
        return amplitude *
 | 
			
		||||
            Math.sin(timestamp / period / 1000 * Math.PI * 2) + offset;
 | 
			
		||||
            Math.sin(phase + (timestamp / period / 1000 * Math.PI * 2)) + offset;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function sendError(error, message) {
 | 
			
		||||
 
 | 
			
		||||
@@ -23,10 +23,12 @@
 | 
			
		||||
 | 
			
		||||
define([
 | 
			
		||||
    "./GeneratorProvider",
 | 
			
		||||
    "./SinewaveLimitCapability"
 | 
			
		||||
    "./SinewaveLimitCapability",
 | 
			
		||||
    "./StateGeneratorProvider"
 | 
			
		||||
], function (
 | 
			
		||||
    GeneratorProvider,
 | 
			
		||||
    SinewaveLimitCapability
 | 
			
		||||
    SinewaveLimitCapability,
 | 
			
		||||
    StateGeneratorProvider
 | 
			
		||||
) {
 | 
			
		||||
 | 
			
		||||
    var legacyExtensions = {
 | 
			
		||||
@@ -46,6 +48,75 @@ define([
 | 
			
		||||
                openmct.legacyExtension(type, extension)
 | 
			
		||||
            })
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        openmct.types.addType("example.state-generator", {
 | 
			
		||||
            name: "State Generator",
 | 
			
		||||
            description: "For development use.  Generates test enumerated telemetry by cycling through a given set of states",
 | 
			
		||||
            cssClass: "icon-telemetry",
 | 
			
		||||
            creatable: true,
 | 
			
		||||
            form: [
 | 
			
		||||
                {
 | 
			
		||||
                    name: "State Duration (seconds)",
 | 
			
		||||
                    control: "textfield",
 | 
			
		||||
                    cssClass: "l-input-sm l-numeric",
 | 
			
		||||
                    key: "duration",
 | 
			
		||||
                    required: true,
 | 
			
		||||
                    property: [
 | 
			
		||||
                        "telemetry",
 | 
			
		||||
                        "duration"
 | 
			
		||||
                    ],
 | 
			
		||||
                    pattern: "^\\d*(\\.\\d*)?$"
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            initialize: function (object) {
 | 
			
		||||
                object.telemetry = {
 | 
			
		||||
                    duration: 5,
 | 
			
		||||
                    values: [
 | 
			
		||||
                        {
 | 
			
		||||
                            key: "name",
 | 
			
		||||
                            name: "Name"
 | 
			
		||||
                        },
 | 
			
		||||
                        {
 | 
			
		||||
                            key: "utc",
 | 
			
		||||
                            name: "Time",
 | 
			
		||||
                            format: "utc",
 | 
			
		||||
                            hints: {
 | 
			
		||||
                                domain: 1
 | 
			
		||||
                            }
 | 
			
		||||
                        },
 | 
			
		||||
                        {
 | 
			
		||||
                            key: "state",
 | 
			
		||||
                            source: "value",
 | 
			
		||||
                            name: "State",
 | 
			
		||||
                            format: "enum",
 | 
			
		||||
                            enumerations: [
 | 
			
		||||
                                {
 | 
			
		||||
                                    value: 0,
 | 
			
		||||
                                    string: "OFF"
 | 
			
		||||
                                },
 | 
			
		||||
                                {
 | 
			
		||||
                                    value: 1,
 | 
			
		||||
                                    string: "ON"
 | 
			
		||||
                                }
 | 
			
		||||
                            ],
 | 
			
		||||
                            hints: {
 | 
			
		||||
                                range: 1
 | 
			
		||||
                            }
 | 
			
		||||
                        },
 | 
			
		||||
                        {
 | 
			
		||||
                            key: "value",
 | 
			
		||||
                            name: "Value",
 | 
			
		||||
                            hints: {
 | 
			
		||||
                                range: 2
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    ]
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        openmct.telemetry.addProvider(new StateGeneratorProvider());
 | 
			
		||||
 | 
			
		||||
        openmct.types.addType("generator", {
 | 
			
		||||
            name: "Sine Wave Generator",
 | 
			
		||||
            description: "For development use. Generates example streaming telemetry data using a simple sine wave algorithm.",
 | 
			
		||||
@@ -99,6 +170,18 @@ define([
 | 
			
		||||
                        "dataRateInHz"
 | 
			
		||||
                    ],
 | 
			
		||||
                    pattern: "^\\d*(\\.\\d*)?$"
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    name: "Phase (radians)",
 | 
			
		||||
                    control: "textfield",
 | 
			
		||||
                    cssClass: "l-input-sm l-numeric",
 | 
			
		||||
                    key: "phase",
 | 
			
		||||
                    required: true,
 | 
			
		||||
                    property: [
 | 
			
		||||
                        "telemetry",
 | 
			
		||||
                        "phase"
 | 
			
		||||
                    ],
 | 
			
		||||
                    pattern: "^\\d*(\\.\\d*)?$"
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            initialize: function (object) {
 | 
			
		||||
@@ -107,7 +190,12 @@ define([
 | 
			
		||||
                    amplitude: 1,
 | 
			
		||||
                    offset: 0,
 | 
			
		||||
                    dataRateInHz: 1,
 | 
			
		||||
                    phase: 0,
 | 
			
		||||
                    values: [
 | 
			
		||||
                        {
 | 
			
		||||
                            key: "name",
 | 
			
		||||
                            name: "Name"
 | 
			
		||||
                        },
 | 
			
		||||
                        {
 | 
			
		||||
                            key: "utc",
 | 
			
		||||
                            name: "Time",
 | 
			
		||||
@@ -142,6 +230,7 @@ define([
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        openmct.telemetry.addProvider(new GeneratorProvider());
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -48,8 +48,9 @@ define([
 | 
			
		||||
                "https://www.hq.nasa.gov/alsj/a16/AS16-117-18748.jpg"
 | 
			
		||||
            ];
 | 
			
		||||
 | 
			
		||||
        function pointForTimestamp(timestamp) {
 | 
			
		||||
        function pointForTimestamp(timestamp, name) {
 | 
			
		||||
            return {
 | 
			
		||||
                name: name,
 | 
			
		||||
                utc: Math.floor(timestamp / 5000) * 5000,
 | 
			
		||||
                url: IMAGE_SAMPLES[Math.floor(timestamp / 5000) % IMAGE_SAMPLES.length]
 | 
			
		||||
            };
 | 
			
		||||
@@ -61,7 +62,7 @@ define([
 | 
			
		||||
            },
 | 
			
		||||
            subscribe: function (domainObject, callback) {
 | 
			
		||||
                var interval = setInterval(function () {
 | 
			
		||||
                    callback(pointForTimestamp(Date.now()));
 | 
			
		||||
                    callback(pointForTimestamp(Date.now(), domainObject.name));
 | 
			
		||||
                }, 5000);
 | 
			
		||||
 | 
			
		||||
                return function (interval) {
 | 
			
		||||
@@ -79,8 +80,8 @@ define([
 | 
			
		||||
                var start = options.start;
 | 
			
		||||
                var end = options.end;
 | 
			
		||||
                var data = [];
 | 
			
		||||
                while (start < end && data.length < 5000) {
 | 
			
		||||
                    data.push(pointForTimestamp(start));
 | 
			
		||||
                while (start <= end && data.length < 5000) {
 | 
			
		||||
                    data.push(pointForTimestamp(start, domainObject.name));
 | 
			
		||||
                    start += 5000;
 | 
			
		||||
                }
 | 
			
		||||
                return Promise.resolve(data);
 | 
			
		||||
@@ -93,7 +94,7 @@ define([
 | 
			
		||||
                    options.strategy === 'latest';
 | 
			
		||||
            },
 | 
			
		||||
            request: function (domainObject, options) {
 | 
			
		||||
                return Promise.resolve([pointForTimestamp(Date.now())]);
 | 
			
		||||
                return Promise.resolve([pointForTimestamp(Date.now(), domainObject.name)]);
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
@@ -109,6 +110,10 @@ define([
 | 
			
		||||
                initialize: function (object) {
 | 
			
		||||
                    object.telemetry = {
 | 
			
		||||
                        values: [
 | 
			
		||||
                            {
 | 
			
		||||
                                name: 'Name',
 | 
			
		||||
                                key: 'name'
 | 
			
		||||
                            },
 | 
			
		||||
                            {
 | 
			
		||||
                                name: 'Time',
 | 
			
		||||
                                key: 'utc',
 | 
			
		||||
 
 | 
			
		||||
@@ -1,146 +0,0 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * Open MCT, Copyright (c) 2014-2017, United States Government
 | 
			
		||||
 * as represented by the Administrator of the National Aeronautics and Space
 | 
			
		||||
 * Administration. All rights reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Open MCT is licensed under the Apache License, Version 2.0 (the
 | 
			
		||||
 * "License"); you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 * http://www.apache.org/licenses/LICENSE-2.0.
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
 * License for the specific language governing permissions and limitations
 | 
			
		||||
 * under the License.
 | 
			
		||||
 *
 | 
			
		||||
 * Open MCT includes source code licensed under additional open source
 | 
			
		||||
 * licenses. 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([
 | 
			
		||||
    'legacyRegistry',
 | 
			
		||||
    '../../platform/commonUI/browse/src/InspectorRegion',
 | 
			
		||||
    '../../platform/commonUI/regions/src/Region'
 | 
			
		||||
], function (
 | 
			
		||||
    legacyRegistry,
 | 
			
		||||
    InspectorRegion,
 | 
			
		||||
    Region
 | 
			
		||||
) {
 | 
			
		||||
    "use strict";
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Add a 'plot options' region part to the inspector region for the
 | 
			
		||||
     * Telemetry Plot type only. {@link InspectorRegion} is a default region
 | 
			
		||||
     * implementation that is added automatically to all types. In order to
 | 
			
		||||
     * customize what appears in the inspector region, you can start from a
 | 
			
		||||
     * blank slate by using Region, or customize the default inspector
 | 
			
		||||
     * region by using {@link InspectorRegion}.
 | 
			
		||||
     */
 | 
			
		||||
    var plotInspector = new InspectorRegion(),
 | 
			
		||||
     /**
 | 
			
		||||
      * Two region parts are defined here. One that appears only in browse
 | 
			
		||||
      * mode, and one that appears only in edit mode. For not they both point
 | 
			
		||||
      * to the same representation, but a different key could be used here to
 | 
			
		||||
      * include a customized representation for edit mode.
 | 
			
		||||
      */
 | 
			
		||||
        plotOptionsBrowseRegion = new Region({
 | 
			
		||||
            name: "plot-options",
 | 
			
		||||
            title: "Plot Options",
 | 
			
		||||
            modes: ['browse'],
 | 
			
		||||
            content: {
 | 
			
		||||
                key: "plot-options-browse"
 | 
			
		||||
            }
 | 
			
		||||
        }),
 | 
			
		||||
        plotOptionsEditRegion = new Region({
 | 
			
		||||
            name: "plot-options",
 | 
			
		||||
            title: "Plot Options",
 | 
			
		||||
            modes: ['edit'],
 | 
			
		||||
            content: {
 | 
			
		||||
                key: "plot-options-browse"
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Both parts are added, and policies of type 'region' will determine
 | 
			
		||||
     * which is shown based on domain object state. A default policy is
 | 
			
		||||
     * provided which will check the 'modes' attribute of the region part
 | 
			
		||||
     * definition.
 | 
			
		||||
     */
 | 
			
		||||
    plotInspector.addRegion(plotOptionsBrowseRegion);
 | 
			
		||||
    plotInspector.addRegion(plotOptionsEditRegion);
 | 
			
		||||
 | 
			
		||||
    legacyRegistry.register("example/plotType", {
 | 
			
		||||
        "name": "Plot Type",
 | 
			
		||||
        "description": "Example illustrating registration of a new object type",
 | 
			
		||||
        "extensions": {
 | 
			
		||||
            "types": [
 | 
			
		||||
                {
 | 
			
		||||
                    "key": "plot",
 | 
			
		||||
                    "name": "Example Telemetry Plot",
 | 
			
		||||
                    "cssClass": "icon-telemetry-panel",
 | 
			
		||||
                    "description": "For development use. A plot for displaying telemetry.",
 | 
			
		||||
                    "priority": 10,
 | 
			
		||||
                    "delegates": [
 | 
			
		||||
                        "telemetry"
 | 
			
		||||
                    ],
 | 
			
		||||
                    "features": "creation",
 | 
			
		||||
                    "contains": [
 | 
			
		||||
                        {
 | 
			
		||||
                            "has": "telemetry"
 | 
			
		||||
                        }
 | 
			
		||||
                    ],
 | 
			
		||||
                    "model": {
 | 
			
		||||
                        "composition": []
 | 
			
		||||
                    },
 | 
			
		||||
                    "inspector": plotInspector,
 | 
			
		||||
                    "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*)?$"
 | 
			
		||||
                        }
 | 
			
		||||
                    ]
 | 
			
		||||
                }
 | 
			
		||||
            ]
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
@@ -58,11 +58,7 @@
 | 
			
		||||
        position: relative;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .w-mct-example {
 | 
			
		||||
        div {
 | 
			
		||||
            margin-bottom: $interiorMarginLg;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    .w-mct-example > div { margin-bottom: $interiorMarginLg; }
 | 
			
		||||
 | 
			
		||||
    code,
 | 
			
		||||
    pre {
 | 
			
		||||
 
 | 
			
		||||
@@ -22,6 +22,8 @@
 | 
			
		||||
 | 
			
		||||
/*global require,__dirname*/
 | 
			
		||||
 | 
			
		||||
require("v8-compile-cache");
 | 
			
		||||
 | 
			
		||||
var gulp = require('gulp'),
 | 
			
		||||
    sourcemaps = require('gulp-sourcemaps'),
 | 
			
		||||
    path = require('path'),
 | 
			
		||||
@@ -177,4 +179,4 @@ gulp.task('install', [ 'assets', 'scripts' ]);
 | 
			
		||||
 | 
			
		||||
gulp.task('verify', [ 'lint', 'test', 'checkstyle' ]);
 | 
			
		||||
 | 
			
		||||
gulp.task('build', [ 'verify', 'install' ]);
 | 
			
		||||
gulp.task('build', [ 'verify', 'install' ]);
 | 
			
		||||
@@ -43,7 +43,9 @@
 | 
			
		||||
            openmct.install(openmct.plugins.ExampleImagery());
 | 
			
		||||
            openmct.install(openmct.plugins.UTCTimeSystem());
 | 
			
		||||
            openmct.install(openmct.plugins.ImportExport());
 | 
			
		||||
            openmct.install(openmct.plugins.TelemetryMean());
 | 
			
		||||
            openmct.install(openmct.plugins.AutoflowView({
 | 
			
		||||
                type: "telemetry.panel"
 | 
			
		||||
            }));
 | 
			
		||||
            openmct.install(openmct.plugins.Conductor({
 | 
			
		||||
                menuOptions: [
 | 
			
		||||
                    {
 | 
			
		||||
 
 | 
			
		||||
@@ -36,14 +36,14 @@ module.exports = function(config) {
 | 
			
		||||
        files: [
 | 
			
		||||
            {pattern: 'bower_components/**/*.js', included: false},
 | 
			
		||||
            {pattern: 'node_modules/d3-*/**/*.js', included: false},
 | 
			
		||||
            {pattern: 'src/**/*.js', included: false},
 | 
			
		||||
            {pattern: 'node_modules/vue/**/*.js', included: false},
 | 
			
		||||
            {pattern: 'src/**/*', included: false},
 | 
			
		||||
            {pattern: 'example/**/*.html', included: false},
 | 
			
		||||
            {pattern: 'example/**/*.js', included: false},
 | 
			
		||||
            {pattern: 'example/**/*.json', included: false},
 | 
			
		||||
            {pattern: 'platform/**/*.js', included: false},
 | 
			
		||||
            {pattern: 'warp/**/*.js', included: false},
 | 
			
		||||
            {pattern: 'platform/**/*.html', included: false},
 | 
			
		||||
            {pattern: 'src/**/*.html', included: false},
 | 
			
		||||
            'test-main.js'
 | 
			
		||||
        ],
 | 
			
		||||
 | 
			
		||||
@@ -88,7 +88,8 @@ module.exports = function(config) {
 | 
			
		||||
                "dist/reports/coverage",
 | 
			
		||||
            check: {
 | 
			
		||||
                global: {
 | 
			
		||||
                    lines: 80
 | 
			
		||||
                    lines: 80,
 | 
			
		||||
                    excludes: ['src/plugins/plot/**/*.js']
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
 
 | 
			
		||||
@@ -37,9 +37,10 @@ requirejs.config({
 | 
			
		||||
        "screenfull": "bower_components/screenfull/dist/screenfull.min",
 | 
			
		||||
        "text": "bower_components/text/text",
 | 
			
		||||
        "uuid": "bower_components/node-uuid/uuid",
 | 
			
		||||
        "vue": "node_modules/vue/dist/vue.min",
 | 
			
		||||
        "zepto": "bower_components/zepto/zepto.min",
 | 
			
		||||
        "lodash": "bower_components/lodash/lodash",
 | 
			
		||||
        "d3-selection": "node_modules/d3-selection/build/d3-selection.min",
 | 
			
		||||
        "d3-selection": "node_modules/d3-selection/dist/d3-selection.min",
 | 
			
		||||
        "d3-scale": "node_modules/d3-scale/build/d3-scale.min",
 | 
			
		||||
        "d3-axis": "node_modules/d3-axis/build/d3-axis.min",
 | 
			
		||||
        "d3-array": "node_modules/d3-array/build/d3-array.min",
 | 
			
		||||
@@ -66,6 +67,9 @@ requirejs.config({
 | 
			
		||||
        "moment-duration-format": {
 | 
			
		||||
            "deps": ["moment"]
 | 
			
		||||
        },
 | 
			
		||||
        "saveAs": {
 | 
			
		||||
            "exports": "saveAs"
 | 
			
		||||
        },
 | 
			
		||||
        "screenfull": {
 | 
			
		||||
            "exports": "screenfull"
 | 
			
		||||
        },
 | 
			
		||||
@@ -97,6 +101,7 @@ define([
 | 
			
		||||
    var openmct = new MCT();
 | 
			
		||||
 | 
			
		||||
    openmct.legacyRegistry = defaultRegistry;
 | 
			
		||||
    openmct.install(openmct.plugins.Plot());
 | 
			
		||||
 | 
			
		||||
    if (typeof BUILD_CONSTANTS !== 'undefined') {
 | 
			
		||||
        openmct.install(buildInfo(BUILD_CONSTANTS));
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										12
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								package.json
									
									
									
									
									
								
							@@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "openmct",
 | 
			
		||||
  "version": "0.12.1-SNAPSHOT",
 | 
			
		||||
  "version": "0.13.3-SNAPSHOT",
 | 
			
		||||
  "description": "The Open MCT core platform",
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "d3-array": "^1.0.2",
 | 
			
		||||
@@ -15,7 +15,8 @@
 | 
			
		||||
    "d3-time-format": "^2.0.3",
 | 
			
		||||
    "express": "^4.13.1",
 | 
			
		||||
    "minimist": "^1.1.1",
 | 
			
		||||
    "request": "^2.69.0"
 | 
			
		||||
    "request": "^2.69.0",
 | 
			
		||||
    "vue": "^2.5.6"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "bower": "^1.7.7",
 | 
			
		||||
@@ -27,7 +28,7 @@
 | 
			
		||||
    "gulp-jshint-html-reporter": "^0.1.3",
 | 
			
		||||
    "gulp-rename": "^1.2.2",
 | 
			
		||||
    "gulp-requirejs-optimize": "^0.3.1",
 | 
			
		||||
    "gulp-sass": "^2.2.0",
 | 
			
		||||
    "gulp-sass": "^3.1.0",
 | 
			
		||||
    "gulp-sourcemaps": "^1.6.0",
 | 
			
		||||
    "jasmine-core": "^2.3.0",
 | 
			
		||||
    "jscs-html-reporter": "^0.1.0",
 | 
			
		||||
@@ -49,7 +50,8 @@
 | 
			
		||||
    "moment": "^2.11.1",
 | 
			
		||||
    "node-bourbon": "^4.2.3",
 | 
			
		||||
    "requirejs": "2.1.x",
 | 
			
		||||
    "split": "^1.0.0"
 | 
			
		||||
    "split": "^1.0.0",
 | 
			
		||||
    "v8-compile-cache": "^1.1.0"
 | 
			
		||||
  },
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "start": "node app.js",
 | 
			
		||||
@@ -59,7 +61,7 @@
 | 
			
		||||
    "jsdoc": "jsdoc -c jsdoc.json -R API.md -r -d dist/docs/api",
 | 
			
		||||
    "otherdoc": "node docs/gendocs.js --in docs/src --out dist/docs --suppress-toc 'docs/src/index.md|docs/src/process/index.md'",
 | 
			
		||||
    "docs": "npm run jsdoc ; npm run otherdoc",
 | 
			
		||||
    "prepublish": "node ./node_modules/bower/bin/bower install && node ./node_modules/gulp/bin/gulp.js install"
 | 
			
		||||
    "prepare": "node ./node_modules/bower/bin/bower install && node ./node_modules/gulp/bin/gulp.js install"
 | 
			
		||||
  },
 | 
			
		||||
  "repository": {
 | 
			
		||||
    "type": "git",
 | 
			
		||||
 
 | 
			
		||||
@@ -57,7 +57,12 @@
 | 
			
		||||
            </div>
 | 
			
		||||
            <mct-representation key="representation.selected.key"
 | 
			
		||||
                                mct-object="representation.selected.key && domainObject"
 | 
			
		||||
                                class="abs flex-elem grows object-holder-main scroll">
 | 
			
		||||
                                class="abs flex-elem grows object-holder-main scroll"
 | 
			
		||||
                                mct-selectable="{
 | 
			
		||||
                                    item: domainObject.useCapability('adapter'),
 | 
			
		||||
                                    oldItem: domainObject
 | 
			
		||||
                                }"
 | 
			
		||||
                                mct-init-select>
 | 
			
		||||
            </mct-representation>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -19,12 +19,21 @@
 | 
			
		||||
 this source code distribution or the Licensing information page available
 | 
			
		||||
 at runtime from the About dialog for additional information.
 | 
			
		||||
-->
 | 
			
		||||
<div ng-controller="InspectorController">
 | 
			
		||||
    <div ng-repeat="region in regions">
 | 
			
		||||
<div ng-controller="InspectorController as controller">
 | 
			
		||||
        <mct-representation
 | 
			
		||||
                key="region.content.key"
 | 
			
		||||
                mct-object="domainObject"
 | 
			
		||||
                key="'object-properties'"
 | 
			
		||||
                mct-object="controller.selectedItem()"
 | 
			
		||||
                ng-model="ngModel">
 | 
			
		||||
        </mct-representation>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
        <div ng-if="!controller.hasProviderView()">
 | 
			
		||||
            <mct-representation
 | 
			
		||||
                    key="inspectorKey"
 | 
			
		||||
                    mct-object="controller.selectedItem()"
 | 
			
		||||
                    ng-model="ngModel">
 | 
			
		||||
            </mct-representation>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div class='inspector-provider-view'>
 | 
			
		||||
        </div>
 | 
			
		||||
</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -38,8 +38,7 @@
 | 
			
		||||
                  ng-class="{ last:($index + 1) === contextualParents.length }">
 | 
			
		||||
                <mct-representation key="'label'"
 | 
			
		||||
                                    mct-object="parent"
 | 
			
		||||
                                    ng-model="ngModel"
 | 
			
		||||
                                    ng-click="ngModel.selectedObject = parent"
 | 
			
		||||
                                    ng-click="parent.getCapability('action').perform('navigate')"
 | 
			
		||||
                                    class="location-item">
 | 
			
		||||
                </mct-representation>
 | 
			
		||||
            </span>
 | 
			
		||||
@@ -51,8 +50,7 @@
 | 
			
		||||
                  ng-class="{ last:($index + 1) === primaryParents.length }">
 | 
			
		||||
                <mct-representation key="'label'"
 | 
			
		||||
                                    mct-object="parent"
 | 
			
		||||
                                    ng-model="ngModel"
 | 
			
		||||
                                    ng-click="ngModel.selectedObject = parent"
 | 
			
		||||
                                    ng-click="parent.getCapability('action').perform('navigate')"
 | 
			
		||||
                                    class="location-item">
 | 
			
		||||
                </mct-representation>
 | 
			
		||||
            </span>
 | 
			
		||||
 
 | 
			
		||||
@@ -42,23 +42,37 @@ define(
 | 
			
		||||
         * @param event the mouse event
 | 
			
		||||
         */
 | 
			
		||||
        ObjectHeaderController.prototype.updateName = function (event) {
 | 
			
		||||
            if (event && (event.type === 'blur' || event.which === 13)) {
 | 
			
		||||
                var name = event.currentTarget.innerHTML;
 | 
			
		||||
            if (!event || !event.currentTarget) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
                if (name.length === 0) {
 | 
			
		||||
                    name = "Unnamed " + this.domainObject.getCapability("type").typeDef.name;
 | 
			
		||||
                    event.currentTarget.innerHTML = name;
 | 
			
		||||
                }
 | 
			
		||||
            if (event.type === 'blur') {
 | 
			
		||||
                this.updateModel(event);
 | 
			
		||||
            } else if (event.which === 13) {
 | 
			
		||||
                this.updateModel(event);
 | 
			
		||||
                event.currentTarget.blur();
 | 
			
		||||
                window.getSelection().removeAllRanges();
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
                if (name !== this.$scope.domainObject.model.name) {
 | 
			
		||||
                    this.domainObject.getCapability('mutation').mutate(function (model) {
 | 
			
		||||
                        model.name = name;
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
        /**
 | 
			
		||||
         * Updates the model.
 | 
			
		||||
         *
 | 
			
		||||
         * @param event the mouse event
 | 
			
		||||
         * @param private
 | 
			
		||||
         */
 | 
			
		||||
        ObjectHeaderController.prototype.updateModel = function (event) {
 | 
			
		||||
            var name = event.currentTarget.textContent.replace(/\n/g, ' ');
 | 
			
		||||
 | 
			
		||||
                if (event.which === 13) {
 | 
			
		||||
                    event.currentTarget.blur();
 | 
			
		||||
                }
 | 
			
		||||
            if (name.length === 0) {
 | 
			
		||||
                name = "Unnamed " + this.domainObject.getCapability("type").typeDef.name;
 | 
			
		||||
                event.currentTarget.textContent = name;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (name !== this.domainObject.getModel().name) {
 | 
			
		||||
                this.domainObject.getCapability('mutation').mutate(function (model) {
 | 
			
		||||
                    model.name = name;
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -32,6 +32,7 @@ define(
 | 
			
		||||
                mockTypeCapability,
 | 
			
		||||
                mockEvent,
 | 
			
		||||
                mockCurrentTarget,
 | 
			
		||||
                model,
 | 
			
		||||
                controller;
 | 
			
		||||
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
@@ -47,8 +48,11 @@ define(
 | 
			
		||||
                    type: mockTypeCapability
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                mockDomainObject = jasmine.createSpyObj("domainObject", ["getCapability", "model"]);
 | 
			
		||||
                mockDomainObject.model = {name: "Test name"};
 | 
			
		||||
                model = {
 | 
			
		||||
                    name: "Test name"
 | 
			
		||||
                };
 | 
			
		||||
                mockDomainObject = jasmine.createSpyObj("domainObject", ["getCapability", "getModel"]);
 | 
			
		||||
                mockDomainObject.getModel.andReturn(model);
 | 
			
		||||
                mockDomainObject.getCapability.andCallFake(function (key) {
 | 
			
		||||
                    return mockCapabilities[key];
 | 
			
		||||
                });
 | 
			
		||||
@@ -57,7 +61,7 @@ define(
 | 
			
		||||
                    domainObject: mockDomainObject
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                mockCurrentTarget = jasmine.createSpyObj("currentTarget", ["blur", "innerHTML"]);
 | 
			
		||||
                mockCurrentTarget = jasmine.createSpyObj("currentTarget", ["blur", "textContent"]);
 | 
			
		||||
                mockCurrentTarget.blur.andReturn(mockCurrentTarget);
 | 
			
		||||
 | 
			
		||||
                mockEvent = {
 | 
			
		||||
@@ -71,7 +75,7 @@ define(
 | 
			
		||||
 | 
			
		||||
            it("updates the model with new name on blur", function () {
 | 
			
		||||
                mockEvent.type = "blur";
 | 
			
		||||
                mockCurrentTarget.innerHTML = "New name";
 | 
			
		||||
                mockCurrentTarget.textContent = "New name";
 | 
			
		||||
                controller.updateName(mockEvent);
 | 
			
		||||
 | 
			
		||||
                expect(mockMutationCapability.mutate).toHaveBeenCalled();
 | 
			
		||||
@@ -79,23 +83,23 @@ define(
 | 
			
		||||
 | 
			
		||||
            it("updates the model with a default for blank names", function () {
 | 
			
		||||
                mockEvent.type = "blur";
 | 
			
		||||
                mockCurrentTarget.innerHTML = "";
 | 
			
		||||
                mockCurrentTarget.textContent = "";
 | 
			
		||||
                controller.updateName(mockEvent);
 | 
			
		||||
 | 
			
		||||
                expect(mockCurrentTarget.innerHTML.length).not.toEqual(0);
 | 
			
		||||
                expect(mockCurrentTarget.textContent.length).not.toEqual(0);
 | 
			
		||||
                expect(mockMutationCapability.mutate).toHaveBeenCalled();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("does not update the model if the same name", function () {
 | 
			
		||||
                mockEvent.type = "blur";
 | 
			
		||||
                mockCurrentTarget.innerHTML = mockDomainObject.model.name;
 | 
			
		||||
                mockCurrentTarget.textContent = mockDomainObject.getModel().name;
 | 
			
		||||
                controller.updateName(mockEvent);
 | 
			
		||||
 | 
			
		||||
                expect(mockMutationCapability.mutate).not.toHaveBeenCalled();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("updates the model on enter keypress event only", function () {
 | 
			
		||||
                mockCurrentTarget.innerHTML = "New name";
 | 
			
		||||
                mockCurrentTarget.textContent = "New name";
 | 
			
		||||
                controller.updateName(mockEvent);
 | 
			
		||||
 | 
			
		||||
                expect(mockMutationCapability.mutate).not.toHaveBeenCalled();
 | 
			
		||||
@@ -105,12 +109,13 @@ define(
 | 
			
		||||
 | 
			
		||||
                expect(mockMutationCapability.mutate).toHaveBeenCalledWith(jasmine.any(Function));
 | 
			
		||||
 | 
			
		||||
                mockMutationCapability.mutate.mostRecentCall.args[0](mockDomainObject.model);
 | 
			
		||||
                mockMutationCapability.mutate.mostRecentCall.args[0](model);
 | 
			
		||||
 | 
			
		||||
                expect(mockDomainObject.model.name).toBe("New name");
 | 
			
		||||
                expect(mockDomainObject.getModel().name).toBe("New name");
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("blurs the field on enter key press", function () {
 | 
			
		||||
                mockCurrentTarget.textContent = "New name";
 | 
			
		||||
                mockEvent.which = 13;
 | 
			
		||||
                controller.updateName(mockEvent);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -121,7 +121,8 @@ define([
 | 
			
		||||
                    "key": "ElementsController",
 | 
			
		||||
                    "implementation": ElementsController,
 | 
			
		||||
                    "depends": [
 | 
			
		||||
                        "$scope"
 | 
			
		||||
                        "$scope",
 | 
			
		||||
                        "openmct"
 | 
			
		||||
                    ]
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
@@ -299,9 +300,6 @@ define([
 | 
			
		||||
                {
 | 
			
		||||
                    "key": "edit-elements",
 | 
			
		||||
                    "template": elementsTemplate,
 | 
			
		||||
                    "uses": [
 | 
			
		||||
                        "composition"
 | 
			
		||||
                    ],
 | 
			
		||||
                    "gestures": [
 | 
			
		||||
                        "drop"
 | 
			
		||||
                    ]
 | 
			
		||||
@@ -385,7 +383,10 @@ define([
 | 
			
		||||
                    ]
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "implementation": EditToolbarRepresenter
 | 
			
		||||
                    "implementation": EditToolbarRepresenter,
 | 
			
		||||
                    "depends": [
 | 
			
		||||
                        "openmct"
 | 
			
		||||
                    ]
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            "constants": [
 | 
			
		||||
 
 | 
			
		||||
@@ -61,7 +61,12 @@
 | 
			
		||||
            <mct-representation key="representation.selected.key"
 | 
			
		||||
                                mct-object="representation.selected.key && domainObject"
 | 
			
		||||
                                class="abs flex-elem grows object-holder-main scroll"
 | 
			
		||||
                                toolbar="toolbar">
 | 
			
		||||
                                toolbar="toolbar"
 | 
			
		||||
                                mct-selectable="{
 | 
			
		||||
                                    item: domainObject.useCapability('adapter'),
 | 
			
		||||
                                    oldItem: domainObject
 | 
			
		||||
                                }"
 | 
			
		||||
                                mct-init-select>
 | 
			
		||||
            </mct-representation>
 | 
			
		||||
        </div><!--/ l-object-wrapper-inner -->
 | 
			
		||||
    </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,7 @@
 | 
			
		||||
                 ng-model="filterBy">
 | 
			
		||||
    </mct-include>
 | 
			
		||||
    <div class="flex-elem grows vscroll">
 | 
			
		||||
        <ul class="tree">
 | 
			
		||||
        <ul class="tree" ng-if="composition.length > 0">
 | 
			
		||||
            <li ng-repeat="containedObject in composition | filter:searchElements">
 | 
			
		||||
                <span class="tree-item">
 | 
			
		||||
                    <mct-representation
 | 
			
		||||
@@ -36,5 +36,6 @@
 | 
			
		||||
                </span>
 | 
			
		||||
            </li>
 | 
			
		||||
        </ul>
 | 
			
		||||
        <div ng-if="composition.length === 0">No contained elements</div>    
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -101,10 +101,15 @@ define(
 | 
			
		||||
         */
 | 
			
		||||
        EditorCapability.prototype.finish = function () {
 | 
			
		||||
            var domainObject = this.domainObject;
 | 
			
		||||
            return this.transactionService.cancel().then(function () {
 | 
			
		||||
                domainObject.getCapability("status").set("editing", false);
 | 
			
		||||
                return domainObject;
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            if (this.transactionService.isActive()) {
 | 
			
		||||
                return this.transactionService.cancel().then(function () {
 | 
			
		||||
                    domainObject.getCapability("status").set("editing", false);
 | 
			
		||||
                    return domainObject;
 | 
			
		||||
                });
 | 
			
		||||
            } else {
 | 
			
		||||
                return Promise.resolve(domainObject);
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
 
 | 
			
		||||
@@ -28,16 +28,6 @@ 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 &&
 | 
			
		||||
@@ -59,10 +49,7 @@ define(
 | 
			
		||||
 | 
			
		||||
            var removeCheck = navigationService
 | 
			
		||||
                .checkBeforeNavigation(function () {
 | 
			
		||||
                    if (isDirty(domainObject)) {
 | 
			
		||||
                        return "Continuing will cause the loss of any unsaved changes.";
 | 
			
		||||
                    }
 | 
			
		||||
                    return false;
 | 
			
		||||
                    return "Continuing will cause the loss of any unsaved changes.";
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            $scope.$on('$destroy', function () {
 | 
			
		||||
 
 | 
			
		||||
@@ -29,7 +29,11 @@ define(
 | 
			
		||||
         *
 | 
			
		||||
         * @constructor
 | 
			
		||||
         */
 | 
			
		||||
        function ElementsController($scope) {
 | 
			
		||||
        function ElementsController($scope, openmct) {
 | 
			
		||||
            this.scope = $scope;
 | 
			
		||||
            this.scope.composition = [];
 | 
			
		||||
            var self = this;
 | 
			
		||||
 | 
			
		||||
            function filterBy(text) {
 | 
			
		||||
                if (typeof text === 'undefined') {
 | 
			
		||||
                    return $scope.searchText;
 | 
			
		||||
@@ -47,10 +51,58 @@ define(
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function setSelection(selection) {
 | 
			
		||||
                if (!selection[0]) {
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (self.mutationListener) {
 | 
			
		||||
                    self.mutationListener();
 | 
			
		||||
                    delete self.mutationListener;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var domainObject = selection[0].context.oldItem;
 | 
			
		||||
                self.refreshComposition(domainObject);
 | 
			
		||||
 | 
			
		||||
                if (domainObject) {
 | 
			
		||||
                    self.mutationListener = domainObject.getCapability('mutation')
 | 
			
		||||
                        .listen(self.refreshComposition.bind(self, domainObject));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $scope.filterBy = filterBy;
 | 
			
		||||
            $scope.searchElements = searchElements;
 | 
			
		||||
 | 
			
		||||
            openmct.selection.on('change', setSelection);
 | 
			
		||||
            setSelection(openmct.selection.get());
 | 
			
		||||
 | 
			
		||||
            $scope.$on("$destroy", function () {
 | 
			
		||||
                openmct.selection.off("change", setSelection);
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Gets the composition for the selected object and populates the scope with it.
 | 
			
		||||
         *
 | 
			
		||||
         * @param domainObject the selected object
 | 
			
		||||
         * @private
 | 
			
		||||
         */
 | 
			
		||||
        ElementsController.prototype.refreshComposition = function (domainObject) {
 | 
			
		||||
            var refreshTracker = {};
 | 
			
		||||
            this.currentRefresh = refreshTracker;
 | 
			
		||||
 | 
			
		||||
            var selectedObjectComposition = domainObject && domainObject.useCapability('composition');
 | 
			
		||||
            if (selectedObjectComposition) {
 | 
			
		||||
                selectedObjectComposition.then(function (composition) {
 | 
			
		||||
                    if (this.currentRefresh === refreshTracker) {
 | 
			
		||||
                        this.scope.composition = composition;
 | 
			
		||||
                    }
 | 
			
		||||
                }.bind(this));
 | 
			
		||||
            } else {
 | 
			
		||||
                this.scope.composition = [];
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return ElementsController;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 
 | 
			
		||||
@@ -38,7 +38,7 @@ define(
 | 
			
		||||
         * @constructor
 | 
			
		||||
         * @implements {Representer}
 | 
			
		||||
         */
 | 
			
		||||
        function EditToolbarRepresenter(scope, element, attrs) {
 | 
			
		||||
        function EditToolbarRepresenter(openmct, scope, element, attrs) {
 | 
			
		||||
            var self = this;
 | 
			
		||||
 | 
			
		||||
            // Mark changes as ready to persist
 | 
			
		||||
@@ -109,6 +109,7 @@ define(
 | 
			
		||||
            this.updateSelection = updateSelection;
 | 
			
		||||
            this.toolbar = undefined;
 | 
			
		||||
            this.toolbarObject = {};
 | 
			
		||||
            this.openmct = openmct;
 | 
			
		||||
 | 
			
		||||
            // If this representation exposes a toolbar, set up watches
 | 
			
		||||
            // to synchronize with it.
 | 
			
		||||
@@ -146,7 +147,7 @@ define(
 | 
			
		||||
            // Expose the toolbar object to the parent scope
 | 
			
		||||
            initialize(definition);
 | 
			
		||||
            // Create a selection scope
 | 
			
		||||
            this.setSelection(new EditToolbarSelection());
 | 
			
		||||
            this.setSelection(new EditToolbarSelection(this.openmct));
 | 
			
		||||
            // Initialize toolbar to an empty selection
 | 
			
		||||
            this.updateSelection([]);
 | 
			
		||||
        };
 | 
			
		||||
 
 | 
			
		||||
@@ -38,10 +38,24 @@ define(
 | 
			
		||||
         * @memberof platform/commonUI/edit
 | 
			
		||||
         * @constructor
 | 
			
		||||
         */
 | 
			
		||||
        function EditToolbarSelection() {
 | 
			
		||||
        function EditToolbarSelection(openmct) {
 | 
			
		||||
            this.selection = [{}];
 | 
			
		||||
            this.selecting = false;
 | 
			
		||||
            this.selectedObj = undefined;
 | 
			
		||||
 | 
			
		||||
            openmct.selection.on('change', function (selection) {
 | 
			
		||||
                var selected = selection[0];
 | 
			
		||||
 | 
			
		||||
                if (selected && selected.context.toolbar) {
 | 
			
		||||
                    this.select(selected.context.toolbar);
 | 
			
		||||
                } else {
 | 
			
		||||
                    this.deselect();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (selected && selected.context.viewProxy) {
 | 
			
		||||
                    this.proxy(selected.context.viewProxy);
 | 
			
		||||
                }
 | 
			
		||||
            }.bind(this));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
 
 | 
			
		||||
@@ -62,6 +62,7 @@ define(
 | 
			
		||||
                );
 | 
			
		||||
                mockTransactionService.commit.andReturn(fastPromise());
 | 
			
		||||
                mockTransactionService.cancel.andReturn(fastPromise());
 | 
			
		||||
                mockTransactionService.isActive = jasmine.createSpy('isActive');
 | 
			
		||||
 | 
			
		||||
                mockStatusCapability = jasmine.createSpyObj(
 | 
			
		||||
                    "statusCapability",
 | 
			
		||||
@@ -141,6 +142,7 @@ define(
 | 
			
		||||
 | 
			
		||||
            describe("finish", function () {
 | 
			
		||||
                beforeEach(function () {
 | 
			
		||||
                    mockTransactionService.isActive.andReturn(true);
 | 
			
		||||
                    capability.edit();
 | 
			
		||||
                    capability.finish();
 | 
			
		||||
                });
 | 
			
		||||
@@ -152,6 +154,23 @@ define(
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            describe("finish", function () {
 | 
			
		||||
                beforeEach(function () {
 | 
			
		||||
                    mockTransactionService.isActive.andReturn(false);
 | 
			
		||||
                    capability.edit();
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("does not cancel transaction when transaction is not active", function () {
 | 
			
		||||
                    capability.finish();
 | 
			
		||||
                    expect(mockTransactionService.cancel).not.toHaveBeenCalled();
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("returns a promise", function () {
 | 
			
		||||
                    expect(capability.finish() instanceof Promise).toBe(true);
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            describe("dirty", function () {
 | 
			
		||||
                var model = {};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -104,10 +104,10 @@ define(
 | 
			
		||||
                mockEditorCapability.isEditContextRoot.andReturn(false);
 | 
			
		||||
                mockEditorCapability.dirty.andReturn(false);
 | 
			
		||||
 | 
			
		||||
                expect(checkFn()).toBe(false);
 | 
			
		||||
                expect(checkFn()).toBe("Continuing will cause the loss of any unsaved changes.");
 | 
			
		||||
 | 
			
		||||
                mockEditorCapability.isEditContextRoot.andReturn(true);
 | 
			
		||||
                expect(checkFn()).toBe(false);
 | 
			
		||||
                expect(checkFn()).toBe("Continuing will cause the loss of any unsaved changes.");
 | 
			
		||||
 | 
			
		||||
                mockEditorCapability.dirty.andReturn(true);
 | 
			
		||||
                expect(checkFn())
 | 
			
		||||
 
 | 
			
		||||
@@ -27,11 +27,70 @@ define(
 | 
			
		||||
 | 
			
		||||
        describe("The Elements Pane controller", function () {
 | 
			
		||||
            var mockScope,
 | 
			
		||||
                mockOpenMCT,
 | 
			
		||||
                mockSelection,
 | 
			
		||||
                mockDomainObject,
 | 
			
		||||
                mockMutationCapability,
 | 
			
		||||
                mockCompositionCapability,
 | 
			
		||||
                mockCompositionObjects,
 | 
			
		||||
                mockComposition,
 | 
			
		||||
                mockUnlisten,
 | 
			
		||||
                selectable = [],
 | 
			
		||||
                controller;
 | 
			
		||||
 | 
			
		||||
            function mockPromise(value) {
 | 
			
		||||
                return {
 | 
			
		||||
                    then: function (thenFunc) {
 | 
			
		||||
                        return mockPromise(thenFunc(value));
 | 
			
		||||
                    }
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function createDomainObject() {
 | 
			
		||||
                return {
 | 
			
		||||
                    useCapability: function () {
 | 
			
		||||
                        return mockCompositionCapability;
 | 
			
		||||
                    }
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                mockScope = jasmine.createSpy("$scope");
 | 
			
		||||
                controller = new ElementsController(mockScope);
 | 
			
		||||
                mockComposition = ["a", "b"];
 | 
			
		||||
                mockCompositionObjects = mockComposition.map(createDomainObject);
 | 
			
		||||
                mockCompositionCapability = mockPromise(mockCompositionObjects);
 | 
			
		||||
 | 
			
		||||
                mockUnlisten = jasmine.createSpy('unlisten');
 | 
			
		||||
                mockMutationCapability = jasmine.createSpyObj("mutationCapability", [
 | 
			
		||||
                    "listen"
 | 
			
		||||
                ]);
 | 
			
		||||
                mockMutationCapability.listen.andReturn(mockUnlisten);
 | 
			
		||||
                mockDomainObject = jasmine.createSpyObj("domainObject", [
 | 
			
		||||
                    "getCapability",
 | 
			
		||||
                    "useCapability"
 | 
			
		||||
                ]);
 | 
			
		||||
                mockDomainObject.useCapability.andReturn(mockCompositionCapability);
 | 
			
		||||
                mockDomainObject.getCapability.andReturn(mockMutationCapability);
 | 
			
		||||
 | 
			
		||||
                mockScope = jasmine.createSpyObj("$scope", ['$on']);
 | 
			
		||||
                mockSelection = jasmine.createSpyObj("selection", [
 | 
			
		||||
                    'on',
 | 
			
		||||
                    'off',
 | 
			
		||||
                    'get'
 | 
			
		||||
                ]);
 | 
			
		||||
                mockSelection.get.andReturn([]);
 | 
			
		||||
                mockOpenMCT = {
 | 
			
		||||
                    selection: mockSelection
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                selectable[0] = {
 | 
			
		||||
                    context: {
 | 
			
		||||
                        oldItem: mockDomainObject
 | 
			
		||||
                    }
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                spyOn(ElementsController.prototype, 'refreshComposition').andCallThrough();
 | 
			
		||||
 | 
			
		||||
                controller = new ElementsController(mockScope, mockOpenMCT);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            function getModel(model) {
 | 
			
		||||
@@ -63,6 +122,63 @@ define(
 | 
			
		||||
                expect(objects.filter(mockScope.searchElements).length).toBe(4);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("refreshes composition on selection", function () {
 | 
			
		||||
                mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
 | 
			
		||||
 | 
			
		||||
                expect(ElementsController.prototype.refreshComposition).toHaveBeenCalledWith(mockDomainObject);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("listens on mutation and refreshes composition", function () {
 | 
			
		||||
                mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
 | 
			
		||||
 | 
			
		||||
                expect(mockDomainObject.getCapability).toHaveBeenCalledWith('mutation');
 | 
			
		||||
                expect(mockMutationCapability.listen).toHaveBeenCalled();
 | 
			
		||||
                expect(ElementsController.prototype.refreshComposition.calls.length).toBe(1);
 | 
			
		||||
 | 
			
		||||
                mockMutationCapability.listen.mostRecentCall.args[0](mockDomainObject);
 | 
			
		||||
 | 
			
		||||
                expect(ElementsController.prototype.refreshComposition.calls.length).toBe(2);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("cleans up mutation listener when selection changes", function () {
 | 
			
		||||
                mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
 | 
			
		||||
 | 
			
		||||
                expect(mockMutationCapability.listen).toHaveBeenCalled();
 | 
			
		||||
 | 
			
		||||
                mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
 | 
			
		||||
 | 
			
		||||
                expect(mockUnlisten).toHaveBeenCalled();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("does not listen on mutation for element proxy selectable", function () {
 | 
			
		||||
                selectable[0] = {
 | 
			
		||||
                    context: {
 | 
			
		||||
                        elementProxy: {}
 | 
			
		||||
                    }
 | 
			
		||||
                };
 | 
			
		||||
                mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
 | 
			
		||||
 | 
			
		||||
                expect(mockDomainObject.getCapability).not.toHaveBeenCalledWith('mutation');
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("checks concurrent changes to composition", function () {
 | 
			
		||||
                var secondMockComposition = ["a", "b", "c"],
 | 
			
		||||
                    secondMockCompositionObjects = secondMockComposition.map(createDomainObject),
 | 
			
		||||
                    firstCompositionCallback,
 | 
			
		||||
                    secondCompositionCallback;
 | 
			
		||||
 | 
			
		||||
                spyOn(mockCompositionCapability, "then").andCallThrough();
 | 
			
		||||
 | 
			
		||||
                controller.refreshComposition(mockDomainObject);
 | 
			
		||||
                controller.refreshComposition(mockDomainObject);
 | 
			
		||||
 | 
			
		||||
                firstCompositionCallback = mockCompositionCapability.then.calls[0].args[0];
 | 
			
		||||
                secondCompositionCallback = mockCompositionCapability.then.calls[1].args[0];
 | 
			
		||||
                secondCompositionCallback(secondMockCompositionObjects);
 | 
			
		||||
                firstCompositionCallback(mockCompositionObjects);
 | 
			
		||||
 | 
			
		||||
                expect(mockScope.composition).toBe(secondMockCompositionObjects);
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 
 | 
			
		||||
@@ -29,7 +29,9 @@ define(
 | 
			
		||||
                mockElement,
 | 
			
		||||
                testAttrs,
 | 
			
		||||
                mockUnwatch,
 | 
			
		||||
                representer;
 | 
			
		||||
                representer,
 | 
			
		||||
                mockOpenMCT,
 | 
			
		||||
                mockSelection;
 | 
			
		||||
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                mockScope = jasmine.createSpyObj(
 | 
			
		||||
@@ -46,7 +48,18 @@ define(
 | 
			
		||||
 | 
			
		||||
                mockScope.$parent.$watchCollection.andReturn(mockUnwatch);
 | 
			
		||||
 | 
			
		||||
                mockSelection = jasmine.createSpyObj("selection", [
 | 
			
		||||
                    'on',
 | 
			
		||||
                    'off',
 | 
			
		||||
                    'get'
 | 
			
		||||
                ]);
 | 
			
		||||
                mockSelection.get.andReturn([]);
 | 
			
		||||
                mockOpenMCT = {
 | 
			
		||||
                    selection: mockSelection
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                representer = new EditToolbarRepresenter(
 | 
			
		||||
                    mockOpenMCT,
 | 
			
		||||
                    mockScope,
 | 
			
		||||
                    mockElement,
 | 
			
		||||
                    testAttrs
 | 
			
		||||
 
 | 
			
		||||
@@ -28,13 +28,25 @@ define(
 | 
			
		||||
            var testProxy,
 | 
			
		||||
                testElement,
 | 
			
		||||
                otherElement,
 | 
			
		||||
                selection;
 | 
			
		||||
                selection,
 | 
			
		||||
                mockSelection,
 | 
			
		||||
                mockOpenMCT;
 | 
			
		||||
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                testProxy = { someKey: "some value" };
 | 
			
		||||
                testElement = { someOtherKey: "some other value" };
 | 
			
		||||
                otherElement = { yetAnotherKey: 42 };
 | 
			
		||||
                selection = new EditToolbarSelection();
 | 
			
		||||
                mockSelection = jasmine.createSpyObj("selection", [
 | 
			
		||||
                    // 'select',
 | 
			
		||||
                    'on',
 | 
			
		||||
                    'off',
 | 
			
		||||
                    'get'
 | 
			
		||||
                ]);
 | 
			
		||||
                mockSelection.get.andReturn([]);
 | 
			
		||||
                mockOpenMCT = {
 | 
			
		||||
                    selection: mockSelection
 | 
			
		||||
                };
 | 
			
		||||
                selection = new EditToolbarSelection(mockOpenMCT);
 | 
			
		||||
                selection.proxy(testProxy);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -41,6 +41,7 @@ define([
 | 
			
		||||
    "./src/controllers/BannerController",
 | 
			
		||||
    "./src/directives/MCTContainer",
 | 
			
		||||
    "./src/directives/MCTDrag",
 | 
			
		||||
    "./src/directives/MCTSelectable",
 | 
			
		||||
    "./src/directives/MCTClickElsewhere",
 | 
			
		||||
    "./src/directives/MCTResize",
 | 
			
		||||
    "./src/directives/MCTPopup",
 | 
			
		||||
@@ -90,6 +91,7 @@ define([
 | 
			
		||||
    BannerController,
 | 
			
		||||
    MCTContainer,
 | 
			
		||||
    MCTDrag,
 | 
			
		||||
    MCTSelectable,
 | 
			
		||||
    MCTClickElsewhere,
 | 
			
		||||
    MCTResize,
 | 
			
		||||
    MCTPopup,
 | 
			
		||||
@@ -328,6 +330,13 @@ define([
 | 
			
		||||
                        "$document"
 | 
			
		||||
                    ]
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "key": "mctSelectable",
 | 
			
		||||
                    "implementation": MCTSelectable,
 | 
			
		||||
                    "depends": [
 | 
			
		||||
                        "openmct"
 | 
			
		||||
                    ]
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "key": "mctClickElsewhere",
 | 
			
		||||
                    "implementation": MCTClickElsewhere,
 | 
			
		||||
 
 | 
			
		||||
@@ -25,6 +25,7 @@
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	.l-fixed-position-item {
 | 
			
		||||
        border-width: 1px;
 | 
			
		||||
		position: absolute;
 | 
			
		||||
		&.s-not-selected {
 | 
			
		||||
			opacity: 0.8;
 | 
			
		||||
 
 | 
			
		||||
@@ -290,6 +290,11 @@
 | 
			
		||||
            opacity: 1;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    .l-rule-action-buttons-wrapper {
 | 
			
		||||
          .t-delete {
 | 
			
		||||
            margin-left: 10px;
 | 
			
		||||
          }
 | 
			
		||||
    }
 | 
			
		||||
    .t-condition {
 | 
			
		||||
        &:hover {
 | 
			
		||||
            .l-condition-action-buttons-wrapper {
 | 
			
		||||
 
 | 
			
		||||
@@ -77,6 +77,14 @@
 | 
			
		||||
    position: relative;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.s-menu {
 | 
			
		||||
    border-radius: $basicCr;
 | 
			
		||||
    @include containerSubtle($colorMenuBg, $colorMenuFg);
 | 
			
		||||
    @include boxShdw($shdwMenu);
 | 
			
		||||
    @include txtShdw($shdwMenuText);
 | 
			
		||||
    padding: $interiorMarginSm 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.menu {
 | 
			
		||||
    border-radius: $basicCr;
 | 
			
		||||
    @include containerSubtle($colorMenuBg, $colorMenuFg);
 | 
			
		||||
 
 | 
			
		||||
@@ -20,53 +20,70 @@
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
.l-palette {
 | 
			
		||||
	$d: 16px;
 | 
			
		||||
	$colorsPerRow: 10;
 | 
			
		||||
	$m: 1;
 | 
			
		||||
 | 
			
		||||
	box-sizing: border-box;
 | 
			
		||||
	padding: $interiorMargin !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	.l-palette-row {
 | 
			
		||||
		@include clearfix;
 | 
			
		||||
		line-height: $d;
 | 
			
		||||
		width: ($d * $colorsPerRow) + ($m * $colorsPerRow);
 | 
			
		||||
.l-palette-row {
 | 
			
		||||
    $d: 16px;
 | 
			
		||||
    $m: 1;
 | 
			
		||||
    $colorsPerRow: 10;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-wrap: wrap;
 | 
			
		||||
    line-height: $d;
 | 
			
		||||
    width: ($d * $colorsPerRow) + ($m * $colorsPerRow);
 | 
			
		||||
 | 
			
		||||
        &.l-option-row {
 | 
			
		||||
            margin-bottom: $interiorMargin;
 | 
			
		||||
            .s-palette-item {
 | 
			
		||||
                border-color: $colorPaletteFg;
 | 
			
		||||
    &.l-option-row {
 | 
			
		||||
        margin-bottom: $interiorMargin;
 | 
			
		||||
        .s-palette-item {
 | 
			
		||||
            border-color: $colorPaletteFg;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .l-palette-item {
 | 
			
		||||
        box-sizing: border-box;
 | 
			
		||||
        display: block;
 | 
			
		||||
        height: $d; width: $d;
 | 
			
		||||
        min-width: $d;
 | 
			
		||||
        line-height: $d * 0.9;
 | 
			
		||||
        margin: 0 ($m * 1px) ($m * 1px) 0;
 | 
			
		||||
        position: relative;
 | 
			
		||||
        text-align: center;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.s-palette-item {
 | 
			
		||||
    border: 1px solid transparent;
 | 
			
		||||
    color: $colorPaletteFg;
 | 
			
		||||
    text-shadow: $shdwPaletteFg;
 | 
			
		||||
    @include trans-prop-nice-fade(0.25s);
 | 
			
		||||
    &:hover {
 | 
			
		||||
        @include trans-prop-nice-fade(0);
 | 
			
		||||
        border-color: $colorPaletteSelected !important;
 | 
			
		||||
    }
 | 
			
		||||
    &.selected {
 | 
			
		||||
        border-color: $colorPaletteSelected;
 | 
			
		||||
        box-shadow: $shdwPaletteSelected; //Needed to see selection rect on light colored swatches
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.l-palette-item-label {
 | 
			
		||||
    margin-left: $interiorMargin;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.l-inline-palette {
 | 
			
		||||
    .l-palette-row {
 | 
			
		||||
        width: 100%;
 | 
			
		||||
        .l-palette-item {
 | 
			
		||||
            //@include display(flex);
 | 
			
		||||
            @include flex(1 0 auto);
 | 
			
		||||
            margin: 1px;
 | 
			
		||||
            min-width: auto;
 | 
			
		||||
            width: auto;
 | 
			
		||||
            &:before {
 | 
			
		||||
                content: '';
 | 
			
		||||
                padding-top: 75%;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
		.l-palette-item {
 | 
			
		||||
			box-sizing: border-box;
 | 
			
		||||
			display: block;
 | 
			
		||||
			float: left;
 | 
			
		||||
			height: $d; width: $d;
 | 
			
		||||
			line-height: $d * 0.9;
 | 
			
		||||
			margin: 0 ($m * 1px) ($m * 1px) 0;
 | 
			
		||||
            position: relative;
 | 
			
		||||
			text-align: center;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		.s-palette-item {
 | 
			
		||||
            border: 1px solid transparent;
 | 
			
		||||
            color: $colorPaletteFg;
 | 
			
		||||
            text-shadow: $shdwPaletteFg;
 | 
			
		||||
            @include trans-prop-nice-fade(0.25s);
 | 
			
		||||
			&:hover {
 | 
			
		||||
				@include trans-prop-nice-fade(0);
 | 
			
		||||
				border-color: $colorPaletteSelected !important;
 | 
			
		||||
			}
 | 
			
		||||
            &.selected {
 | 
			
		||||
                border-color: $colorPaletteSelected;
 | 
			
		||||
                box-shadow: $shdwPaletteSelected; //Needed to see selection rect on light colored swatches
 | 
			
		||||
            }
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		.l-palette-item-label {
 | 
			
		||||
			margin-left: $interiorMargin;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -80,23 +80,32 @@
 | 
			
		||||
 | 
			
		||||
    // Editing Grids
 | 
			
		||||
    .l-grid-holder {
 | 
			
		||||
        display: block;
 | 
			
		||||
        .l-grid {
 | 
			
		||||
            &.l-grid-x { @include bgTicks($colorGridLines, 'x'); }
 | 
			
		||||
            &.l-grid-y { @include bgTicks($colorGridLines, 'y'); }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Prevent nested frames from showing their grids
 | 
			
		||||
    .t-frame-outer .l-grid-holder { display: none !important; }
 | 
			
		||||
 | 
			
		||||
    // Prevent nested elements from showing s-hover-border
 | 
			
		||||
    .t-frame-outer .s-hover-border {
 | 
			
		||||
        border: none !important;
 | 
			
		||||
    // Display grid when selected or selection parent.
 | 
			
		||||
    .s-selected .l-grid-holder,
 | 
			
		||||
    .s-selected-parent .l-grid-holder {
 | 
			
		||||
        display: block;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Prevent nested frames from being selectable until we have proper sub-object editing
 | 
			
		||||
    .t-frame-outer .t-frame-outer {
 | 
			
		||||
        pointer-events: none;
 | 
			
		||||
    // Display in nested frames...
 | 
			
		||||
    .t-frame-outer {
 | 
			
		||||
        // ...when drilled in or selection parent...
 | 
			
		||||
        &.s-drilled-in, &.s-selected-parent {
 | 
			
		||||
            .l-grid-holder {
 | 
			
		||||
                display: block;
 | 
			
		||||
            }
 | 
			
		||||
            .t-frame-outer:not(.s-drilled-in) .l-grid-holder {
 | 
			
		||||
                display: none;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        // ...but hide otherwise.
 | 
			
		||||
        .l-grid-holder {
 | 
			
		||||
            display: none;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -58,6 +58,7 @@
 | 
			
		||||
 | 
			
		||||
.gl-plot {
 | 
			
		||||
	color: $colorPlotFg;
 | 
			
		||||
    display: flex;
 | 
			
		||||
	font-size: 0.7rem;
 | 
			
		||||
	position: relative;
 | 
			
		||||
	width: 100%;
 | 
			
		||||
@@ -277,14 +278,10 @@
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.s-tick-label {
 | 
			
		||||
    font-size: 0.7rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.gl-plot-tick,
 | 
			
		||||
.tick-label {
 | 
			
		||||
    @extend .s-tick-label;
 | 
			
		||||
    @include reverseEllipsis();
 | 
			
		||||
	font-size: 0.7rem;
 | 
			
		||||
	position: absolute;
 | 
			
		||||
	&.gl-plot-x-tick-label,
 | 
			
		||||
	&.tick-label-x {
 | 
			
		||||
@@ -324,76 +321,3 @@
 | 
			
		||||
		right: 0; left: 0;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/********************************************* HEATMAPS */
 | 
			
		||||
.l-heatmap {
 | 
			
		||||
    right: $interiorMargin; // Fend off from the scrollbar
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.h-heatmap-legend {
 | 
			
		||||
    flex: 1 1 1%; // Pertains to the width of legend area
 | 
			
		||||
    height: 50%;
 | 
			
		||||
    min-height: 100px !important;
 | 
			
		||||
    canvas.heatmap-legend {
 | 
			
		||||
        width: 10px;
 | 
			
		||||
    }
 | 
			
		||||
    .h-heatmap-legend-ticks {
 | 
			
		||||
       justify-content: space-between;
 | 
			
		||||
       margin-left: $interiorMarginSm;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.h-heatmap-grid-outer {
 | 
			
		||||
    $ticksYW: 40px;
 | 
			
		||||
    flex: 1 1 100% !important;
 | 
			
		||||
    margin-left: $ticksYW + $interiorMarginLg;
 | 
			
		||||
 | 
			
		||||
    .h-heatmap-grid-and-ticks {
 | 
			
		||||
        position: relative;
 | 
			
		||||
        height: 0;
 | 
			
		||||
        width: 100%;
 | 
			
		||||
        padding-bottom: 100%;
 | 
			
		||||
        .heatmap-grid {
 | 
			
		||||
            width: 100%;
 | 
			
		||||
            height:100%;
 | 
			
		||||
            position: absolute;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .s-tick-label {
 | 
			
		||||
            flex: 1;
 | 
			
		||||
            min-width: 0;
 | 
			
		||||
            overflow: hidden;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .l-heatmap-ticks-x,
 | 
			
		||||
        .l-heatmap-ticks-y {
 | 
			
		||||
            justify-content: stretch;
 | 
			
		||||
            position: absolute;
 | 
			
		||||
            top: 0; right: 0; bottom: 0; left: 0;
 | 
			
		||||
            z-index: 2;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .l-heatmap-ticks-x {
 | 
			
		||||
            @extend .l-flex-row;
 | 
			
		||||
            bottom: auto;
 | 
			
		||||
            top: 100%;
 | 
			
		||||
            transform: translateY($interiorMargin);
 | 
			
		||||
            .s-tick-label {
 | 
			
		||||
                text-align: center;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .l-heatmap-ticks-y {
 | 
			
		||||
            @extend .l-flex-col;
 | 
			
		||||
            left: auto;
 | 
			
		||||
            right: 100%;
 | 
			
		||||
            text-align: right;
 | 
			
		||||
            transform: translateX($interiorMargin * -1);
 | 
			
		||||
            .s-tick-label {
 | 
			
		||||
                display: flex;
 | 
			
		||||
                align-items: center;
 | 
			
		||||
                text-align: right;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,7 @@
 | 
			
		||||
ul.tree {
 | 
			
		||||
    @include menuUlReset();
 | 
			
		||||
    @include user-select(none);
 | 
			
		||||
    li {
 | 
			
		||||
    > li {
 | 
			
		||||
        display: block;
 | 
			
		||||
        position: relative;
 | 
			
		||||
    }
 | 
			
		||||
@@ -53,8 +53,6 @@ ul.tree {
 | 
			
		||||
    .view-control {
 | 
			
		||||
        color: $colorItemTreeVC;
 | 
			
		||||
        margin-right: $interiorMargin;
 | 
			
		||||
        height: 100%;
 | 
			
		||||
        line-height: inherit;
 | 
			
		||||
        width: $treeVCW;
 | 
			
		||||
        &:before { display: block; }
 | 
			
		||||
        &.no-children {
 | 
			
		||||
 
 | 
			
		||||
@@ -26,12 +26,10 @@
 | 
			
		||||
        z-index: 0; // Needed to prevent child-frame controls from showing through when another child-frame is above
 | 
			
		||||
        &:not(.no-frame) {
 | 
			
		||||
            background: $colorBodyBg;
 | 
			
		||||
            border: 1px solid $bc;
 | 
			
		||||
            &:hover {
 | 
			
		||||
                border-color: lighten($bc, 10%);
 | 
			
		||||
            }
 | 
			
		||||
            border-color: $bc;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .object-browse-bar {
 | 
			
		||||
        font-size: 0.75em;
 | 
			
		||||
        height: $ohH;
 | 
			
		||||
@@ -45,9 +43,7 @@
 | 
			
		||||
    &.t-object-type-timer,
 | 
			
		||||
    &.t-object-type-clock,
 | 
			
		||||
    &.t-object-type-hyperlink,
 | 
			
		||||
    &.t-object-type-summary-widget,
 | 
			
		||||
    &.no-frame .t-object-type-fixed-display,
 | 
			
		||||
    &.no-frame .t-object-type-layout {
 | 
			
		||||
    &.t-object-type-summary-widget {
 | 
			
		||||
        // Hide the right side buttons for objects where they don't make sense
 | 
			
		||||
        // Note that this will hide the view Switcher button if applied
 | 
			
		||||
        // to an object that has it.
 | 
			
		||||
@@ -94,9 +90,9 @@
 | 
			
		||||
 | 
			
		||||
    &.no-frame {
 | 
			
		||||
        background: transparent !important;
 | 
			
		||||
        border: none !important;
 | 
			
		||||
        border-color: transparent;
 | 
			
		||||
        .object-browse-bar .right {
 | 
			
		||||
            $m: 0; // $interiorMarginSm;
 | 
			
		||||
            $m: 0;
 | 
			
		||||
            background: rgba(black, 0.3);
 | 
			
		||||
            border-radius: $basicCr;
 | 
			
		||||
            padding: $interiorMarginSm;
 | 
			
		||||
@@ -117,6 +113,7 @@
 | 
			
		||||
                    display: none;
 | 
			
		||||
                }
 | 
			
		||||
                > .object-holder.abs {
 | 
			
		||||
                    overflow: hidden;
 | 
			
		||||
                    top: 0 !important;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
@@ -20,35 +20,52 @@
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
.s-hover-border {
 | 
			
		||||
    border: none;
 | 
			
		||||
    border: 1px solid transparent;
 | 
			
		||||
    &:hover {
 | 
			
		||||
        border-color: rgba($colorSelectableSelectedPrimary, 0.5) !important;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.s-status-editing {
 | 
			
		||||
    // Limit to editing mode until we have sub-object selection
 | 
			
		||||
    // Limit to editing mode
 | 
			
		||||
    $o: 0.5;
 | 
			
		||||
    $oHover: 0.8;
 | 
			
		||||
    $bc: $colorSelectableSelectedPrimary;
 | 
			
		||||
    .s-hover-border {
 | 
			
		||||
        // Show a border by default so user can see object bounds and empty objects
 | 
			
		||||
        border: 1px dotted rgba($colorSelectableSelectedPrimary, 0.3) !important;
 | 
			
		||||
        border-color: rgba($bc, $o) !important;
 | 
			
		||||
        border-style: dotted !important;
 | 
			
		||||
 | 
			
		||||
        &:hover {
 | 
			
		||||
            border-color: rgba($colorSelectableSelectedPrimary, 0.7) !important;
 | 
			
		||||
            border-color: rgba($bc, $oHover) !important;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        &.t-object-type-layout {
 | 
			
		||||
            border-style: dashed !important;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .s-selected > .s-hover-border,
 | 
			
		||||
    .s-selected.s-hover-border {
 | 
			
		||||
        // Styles for a selected object. Also used by legacy Fixed Position/Panel objects.
 | 
			
		||||
        border-color: $colorSelectableSelectedPrimary !important;
 | 
			
		||||
        @include boxShdwLarge();
 | 
			
		||||
        // Show edit-corners if you got 'em
 | 
			
		||||
        .edit-corner {
 | 
			
		||||
            display: block;
 | 
			
		||||
            &:hover {
 | 
			
		||||
                background-color: rgba($colorKey, 1);
 | 
			
		||||
    .s-selected {
 | 
			
		||||
        &.s-moveable {
 | 
			
		||||
            &:not(.s-drilled-in) {
 | 
			
		||||
                cursor: move;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    .s-selected > .s-moveable,
 | 
			
		||||
    .s-selected.s-moveable {
 | 
			
		||||
        cursor: move;
 | 
			
		||||
.s-selected > .s-hover-border,
 | 
			
		||||
.s-selected.s-hover-border {
 | 
			
		||||
    // Styles for a selected object. Also used by legacy Fixed Position/Panel objects.
 | 
			
		||||
    border-color: $colorSelectableSelectedPrimary !important;
 | 
			
		||||
    @include boxShdwLarge();
 | 
			
		||||
    // Show edit-corners if you got 'em
 | 
			
		||||
    .edit-corner {
 | 
			
		||||
        display: block;
 | 
			
		||||
        &:hover {
 | 
			
		||||
            background-color: rgba($colorKey, 1);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -20,7 +20,7 @@
 | 
			
		||||
 at runtime from the About dialog for additional information.
 | 
			
		||||
-->
 | 
			
		||||
<span ng-controller="DateTimeFieldController">
 | 
			
		||||
    <input type="text"
 | 
			
		||||
    <input type="text" autocorrect="off" spellcheck="false"
 | 
			
		||||
           ng-model="textValue"
 | 
			
		||||
           ng-blur="restoreTextValue(); ngBlur()"
 | 
			
		||||
           ng-mouseup="ngMouseup()"
 | 
			
		||||
 
 | 
			
		||||
@@ -40,7 +40,7 @@ define(
 | 
			
		||||
 | 
			
		||||
            // Gets an array of the contextual parents/ancestors of the selected object
 | 
			
		||||
            function getContextualPath() {
 | 
			
		||||
                var currentObj = $scope.ngModel.selectedObject,
 | 
			
		||||
                var currentObj = $scope.domainObject,
 | 
			
		||||
                    currentParent,
 | 
			
		||||
                    parents = [];
 | 
			
		||||
 | 
			
		||||
@@ -68,7 +68,7 @@ define(
 | 
			
		||||
 | 
			
		||||
                // If this the the initial call of this recursive function
 | 
			
		||||
                if (!current) {
 | 
			
		||||
                    current = $scope.ngModel.selectedObject;
 | 
			
		||||
                    current = $scope.domainObject;
 | 
			
		||||
                    $scope.primaryParents = [];
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
@@ -87,16 +87,16 @@ define(
 | 
			
		||||
 | 
			
		||||
            // Gets the metadata for the selected object
 | 
			
		||||
            function getMetadata() {
 | 
			
		||||
                $scope.metadata = $scope.ngModel.selectedObject &&
 | 
			
		||||
                    $scope.ngModel.selectedObject.hasCapability('metadata') &&
 | 
			
		||||
                    $scope.ngModel.selectedObject.useCapability('metadata');
 | 
			
		||||
                $scope.metadata = $scope.domainObject &&
 | 
			
		||||
                    $scope.domainObject.hasCapability('metadata') &&
 | 
			
		||||
                    $scope.domainObject.useCapability('metadata');
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Set scope variables when the selected object changes
 | 
			
		||||
            $scope.$watch('ngModel.selectedObject', function () {
 | 
			
		||||
                $scope.isLink = $scope.ngModel.selectedObject &&
 | 
			
		||||
                    $scope.ngModel.selectedObject.hasCapability('location') &&
 | 
			
		||||
                    $scope.ngModel.selectedObject.getCapability('location').isLink();
 | 
			
		||||
            $scope.$watch('domainObject', function () {
 | 
			
		||||
                $scope.isLink = $scope.domainObject &&
 | 
			
		||||
                    $scope.domainObject.hasCapability('location') &&
 | 
			
		||||
                    $scope.domainObject.getCapability('location').isLink();
 | 
			
		||||
 | 
			
		||||
                if ($scope.isLink) {
 | 
			
		||||
                    getPrimaryPath();
 | 
			
		||||
@@ -109,7 +109,7 @@ define(
 | 
			
		||||
                getMetadata();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            var mutation = $scope.ngModel.selectedObject.getCapability('mutation');
 | 
			
		||||
            var mutation = $scope.domainObject.getCapability('mutation');
 | 
			
		||||
            var unlisten = mutation.listen(getMetadata);
 | 
			
		||||
            $scope.$on('$destroy', unlisten);
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										60
									
								
								platform/commonUI/general/src/directives/MCTSelectable.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								platform/commonUI/general/src/directives/MCTSelectable.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,60 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * Open MCT, Copyright (c) 2014-2017, United States Government
 | 
			
		||||
 * as represented by the Administrator of the National Aeronautics and Space
 | 
			
		||||
 * Administration. All rights reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Open MCT is licensed under the Apache License, Version 2.0 (the
 | 
			
		||||
 * "License"); you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 * http://www.apache.org/licenses/LICENSE-2.0.
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
 * License for the specific language governing permissions and limitations
 | 
			
		||||
 * under the License.
 | 
			
		||||
 *
 | 
			
		||||
 * Open MCT includes source code licensed under additional open source
 | 
			
		||||
 * licenses. 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 () {
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * The mct-selectable directive allows selection functionality
 | 
			
		||||
         * (click) to be attached to specific elements.
 | 
			
		||||
         *
 | 
			
		||||
         * @memberof platform/commonUI/general
 | 
			
		||||
         * @constructor
 | 
			
		||||
         */
 | 
			
		||||
        function MCTSelectable(openmct) {
 | 
			
		||||
 | 
			
		||||
            // Link; install event handlers.
 | 
			
		||||
            function link(scope, element, attrs) {
 | 
			
		||||
                var removeSelectable = openmct.selection.selectable(
 | 
			
		||||
                    element[0],
 | 
			
		||||
                    scope.$eval(attrs.mctSelectable),
 | 
			
		||||
                    attrs.hasOwnProperty('mctInitSelect') && scope.$eval(attrs.mctInitSelect) !== false
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                scope.$on("$destroy", function () {
 | 
			
		||||
                    removeSelectable();
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return {
 | 
			
		||||
                // mct-selectable only makes sense as an attribute
 | 
			
		||||
                restrict: "A",
 | 
			
		||||
                // Link function, to install event handlers
 | 
			
		||||
                link: link
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return MCTSelectable;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
@@ -41,16 +41,6 @@ define(
 | 
			
		||||
                    "$scope",
 | 
			
		||||
                    ["$watch", "$on"]
 | 
			
		||||
                );
 | 
			
		||||
                mockScope.ngModel = {};
 | 
			
		||||
                mockScope.ngModel.selectedObject = {
 | 
			
		||||
                    getCapability: function () {
 | 
			
		||||
                        return {
 | 
			
		||||
                            listen: function () {
 | 
			
		||||
                                return true;
 | 
			
		||||
                            }
 | 
			
		||||
                        };
 | 
			
		||||
                    }
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                mockObjectService = jasmine.createSpyObj(
 | 
			
		||||
                    "objectService",
 | 
			
		||||
@@ -77,22 +67,27 @@ define(
 | 
			
		||||
                    "location capability",
 | 
			
		||||
                    ["isLink"]
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                mockDomainObject.getCapability.andCallFake(function (param) {
 | 
			
		||||
                    if (param === 'location') {
 | 
			
		||||
                        return mockLocationCapability;
 | 
			
		||||
                    } else if (param === 'context') {
 | 
			
		||||
                        return mockContextCapability;
 | 
			
		||||
                    } else if (param === 'mutation') {
 | 
			
		||||
                        return {
 | 
			
		||||
                            listen: function () {
 | 
			
		||||
                                return true;
 | 
			
		||||
                            }
 | 
			
		||||
                        };
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                mockScope.domainObject = mockDomainObject;
 | 
			
		||||
                controller = new ObjectInspectorController(mockScope, mockObjectService);
 | 
			
		||||
 | 
			
		||||
                // Change the selected object to trigger the watch call
 | 
			
		||||
                mockScope.ngModel.selectedObject = mockDomainObject;
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("watches for changes to the selected object", function () {
 | 
			
		||||
                expect(mockScope.$watch).toHaveBeenCalledWith('ngModel.selectedObject', jasmine.any(Function));
 | 
			
		||||
                expect(mockScope.$watch).toHaveBeenCalledWith('domainObject', jasmine.any(Function));
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("looks for contextual parent objects", function () {
 | 
			
		||||
 
 | 
			
		||||
@@ -65,6 +65,10 @@ define(
 | 
			
		||||
            options = Object.create(OPTIONS);
 | 
			
		||||
            options.marginX = -bubbleSpaceLR;
 | 
			
		||||
 | 
			
		||||
            // prevent bubble from appearing right under pointer,
 | 
			
		||||
            // which causes hover callback to be called multiple times
 | 
			
		||||
            options.offsetX = 1;
 | 
			
		||||
 | 
			
		||||
            // On a phone, bubble takes up more screen real estate,
 | 
			
		||||
            // so position it differently (toward the bottom)
 | 
			
		||||
            if (this.agentService.isPhone()) {
 | 
			
		||||
 
 | 
			
		||||
@@ -38,7 +38,8 @@ define([
 | 
			
		||||
                    "implementation": InspectorController,
 | 
			
		||||
                    "depends": [
 | 
			
		||||
                        "$scope",
 | 
			
		||||
                        "policyService"
 | 
			
		||||
                        "openmct",
 | 
			
		||||
                        "$document"
 | 
			
		||||
                    ]
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
 
 | 
			
		||||
@@ -21,44 +21,73 @@
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
define(
 | 
			
		||||
    ['../../browse/src/InspectorRegion'],
 | 
			
		||||
    function (InspectorRegion) {
 | 
			
		||||
    [],
 | 
			
		||||
    function () {
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * The InspectorController adds region data for a domain object's type
 | 
			
		||||
         * to the scope.
 | 
			
		||||
         * The InspectorController listens for the selection changes and adds the selection
 | 
			
		||||
         * object to the scope.
 | 
			
		||||
         *
 | 
			
		||||
         * @constructor
 | 
			
		||||
         */
 | 
			
		||||
        function InspectorController($scope, policyService) {
 | 
			
		||||
            var domainObject = $scope.domainObject,
 | 
			
		||||
                typeCapability = domainObject.getCapability('type'),
 | 
			
		||||
                statusListener;
 | 
			
		||||
        function InspectorController($scope, openmct, $document) {
 | 
			
		||||
            var self = this;
 | 
			
		||||
            self.$scope = $scope;
 | 
			
		||||
 | 
			
		||||
            /**
 | 
			
		||||
             * Filters region parts to only those allowed by region policies
 | 
			
		||||
             * @param regions
 | 
			
		||||
             * @returns {{}}
 | 
			
		||||
             * Callback handler for the selection change event.
 | 
			
		||||
             * Adds the selection object to the scope. If the selected item has an inspector view,
 | 
			
		||||
             * it puts the key in the scope. If provider view exists, it shows the view.
 | 
			
		||||
             */
 | 
			
		||||
            function filterRegions(inspector) {
 | 
			
		||||
                //Dupe so we're not modifying the type definition.
 | 
			
		||||
                return inspector.regions && inspector.regions.filter(function (region) {
 | 
			
		||||
                    return policyService.allow('region', region, domainObject);
 | 
			
		||||
                });
 | 
			
		||||
            function setSelection(selection) {
 | 
			
		||||
                if (selection[0]) {
 | 
			
		||||
                    var view = openmct.inspectorViews.get(selection);
 | 
			
		||||
                    var container = $document[0].querySelectorAll('.inspector-provider-view')[0];
 | 
			
		||||
                    container.innerHTML = "";
 | 
			
		||||
 | 
			
		||||
                    if (view) {
 | 
			
		||||
                        self.providerView = true;
 | 
			
		||||
                        view.show(container);
 | 
			
		||||
                    } else {
 | 
			
		||||
                        self.providerView = false;
 | 
			
		||||
                        var selectedItem = selection[0].context.oldItem;
 | 
			
		||||
 | 
			
		||||
                        if (selectedItem) {
 | 
			
		||||
                            $scope.inspectorKey = selectedItem.getCapability("type").typeDef.inspector;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                self.$scope.selection = selection;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function setRegions() {
 | 
			
		||||
                $scope.regions = filterRegions(typeCapability.getDefinition().inspector || new InspectorRegion());
 | 
			
		||||
            }
 | 
			
		||||
            openmct.selection.on("change", setSelection);
 | 
			
		||||
 | 
			
		||||
            setSelection(openmct.selection.get());
 | 
			
		||||
 | 
			
		||||
            statusListener = domainObject.getCapability("status").listen(setRegions);
 | 
			
		||||
            $scope.$on("$destroy", function () {
 | 
			
		||||
                statusListener();
 | 
			
		||||
                openmct.selection.off("change", setSelection);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            setRegions();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Gets the selected item.
 | 
			
		||||
         *
 | 
			
		||||
         * @returns a domain object
 | 
			
		||||
         */
 | 
			
		||||
        InspectorController.prototype.selectedItem = function () {
 | 
			
		||||
            return this.$scope.selection[0].context.oldItem;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Checks if a provider view exists.
 | 
			
		||||
         *
 | 
			
		||||
         * @returns 'true' if provider view exists, 'false' otherwise
 | 
			
		||||
         */
 | 
			
		||||
        InspectorController.prototype.hasProviderView = function () {
 | 
			
		||||
            return this.providerView;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return InspectorController;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 
 | 
			
		||||
@@ -27,82 +27,93 @@ define(
 | 
			
		||||
        describe("The inspector controller ", function () {
 | 
			
		||||
            var mockScope,
 | 
			
		||||
                mockDomainObject,
 | 
			
		||||
                mockTypeCapability,
 | 
			
		||||
                mockTypeDefinition,
 | 
			
		||||
                mockPolicyService,
 | 
			
		||||
                mockStatusCapability,
 | 
			
		||||
                capabilities = {},
 | 
			
		||||
                controller;
 | 
			
		||||
                mockOpenMCT,
 | 
			
		||||
                mockSelection,
 | 
			
		||||
                mockInspectorViews,
 | 
			
		||||
                mockTypeDef,
 | 
			
		||||
                controller,
 | 
			
		||||
                container,
 | 
			
		||||
                $document = [],
 | 
			
		||||
                selectable = [];
 | 
			
		||||
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                mockTypeDefinition = {
 | 
			
		||||
                    inspector:
 | 
			
		||||
                        {
 | 
			
		||||
                            'regions': [
 | 
			
		||||
                                {'name': 'Part One'},
 | 
			
		||||
                                {'name': 'Part Two'}
 | 
			
		||||
                            ]
 | 
			
		||||
                        }
 | 
			
		||||
                mockTypeDef = {
 | 
			
		||||
                    typeDef: {
 | 
			
		||||
                        inspector: "some-key"
 | 
			
		||||
                    }
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                mockTypeCapability = jasmine.createSpyObj('typeCapability', [
 | 
			
		||||
                    'getDefinition'
 | 
			
		||||
                ]);
 | 
			
		||||
                mockTypeCapability.getDefinition.andReturn(mockTypeDefinition);
 | 
			
		||||
                capabilities.type = mockTypeCapability;
 | 
			
		||||
 | 
			
		||||
                mockStatusCapability = jasmine.createSpyObj('statusCapability', [
 | 
			
		||||
                    'listen'
 | 
			
		||||
                ]);
 | 
			
		||||
                capabilities.status = mockStatusCapability;
 | 
			
		||||
 | 
			
		||||
                mockDomainObject = jasmine.createSpyObj('domainObject', [
 | 
			
		||||
                    'getCapability'
 | 
			
		||||
                ]);
 | 
			
		||||
                mockDomainObject.getCapability.andCallFake(function (name) {
 | 
			
		||||
                    return capabilities[name];
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                mockPolicyService = jasmine.createSpyObj('policyService', [
 | 
			
		||||
                   'allow'
 | 
			
		||||
                ]);
 | 
			
		||||
                mockDomainObject.getCapability.andReturn(mockTypeDef);
 | 
			
		||||
 | 
			
		||||
                mockScope = jasmine.createSpyObj('$scope',
 | 
			
		||||
                    ['$on']
 | 
			
		||||
                    ['$on', 'selection']
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                mockScope.domainObject = mockDomainObject;
 | 
			
		||||
                selectable[0] = {
 | 
			
		||||
                    context: {
 | 
			
		||||
                        oldItem: mockDomainObject
 | 
			
		||||
                    }
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                mockSelection = jasmine.createSpyObj("selection", [
 | 
			
		||||
                    'on',
 | 
			
		||||
                    'off',
 | 
			
		||||
                    'get'
 | 
			
		||||
                ]);
 | 
			
		||||
                mockSelection.get.andReturn(selectable);
 | 
			
		||||
 | 
			
		||||
                mockInspectorViews = jasmine.createSpyObj('inspectorViews', ['get']);
 | 
			
		||||
                mockOpenMCT = {
 | 
			
		||||
                    selection: mockSelection,
 | 
			
		||||
                    inspectorViews: mockInspectorViews
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                container = jasmine.createSpy('container', ['innerHTML']);
 | 
			
		||||
                $document[0] = jasmine.createSpyObj("$document", ['querySelectorAll']);
 | 
			
		||||
                $document[0].querySelectorAll.andReturn([container]);
 | 
			
		||||
 | 
			
		||||
                controller = new InspectorController(mockScope, mockOpenMCT, $document);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("filters out regions disallowed by region policy", function () {
 | 
			
		||||
                mockPolicyService.allow.andReturn(false);
 | 
			
		||||
                controller = new InspectorController(mockScope, mockPolicyService);
 | 
			
		||||
                expect(mockScope.regions.length).toBe(0);
 | 
			
		||||
            it("listens for selection change event", function () {
 | 
			
		||||
                expect(mockOpenMCT.selection.on).toHaveBeenCalledWith(
 | 
			
		||||
                    'change',
 | 
			
		||||
                    jasmine.any(Function)
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                expect(controller.selectedItem()).toEqual(mockDomainObject);
 | 
			
		||||
 | 
			
		||||
                var mockItem = jasmine.createSpyObj('domainObject', [
 | 
			
		||||
                    'getCapability'
 | 
			
		||||
                ]);
 | 
			
		||||
                mockItem.getCapability.andReturn(mockTypeDef);
 | 
			
		||||
                selectable[0].context.oldItem = mockItem;
 | 
			
		||||
 | 
			
		||||
                mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
 | 
			
		||||
 | 
			
		||||
                expect(controller.selectedItem()).toEqual(mockItem);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("does not filter out regions allowed by region policy", function () {
 | 
			
		||||
                mockPolicyService.allow.andReturn(true);
 | 
			
		||||
                controller = new InspectorController(mockScope, mockPolicyService);
 | 
			
		||||
                expect(mockScope.regions.length).toBe(2);
 | 
			
		||||
            it("cleans up on scope destroy", function () {
 | 
			
		||||
                expect(mockScope.$on).toHaveBeenCalledWith(
 | 
			
		||||
                    '$destroy',
 | 
			
		||||
                    jasmine.any(Function)
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                mockScope.$on.calls[0].args[1]();
 | 
			
		||||
 | 
			
		||||
                expect(mockOpenMCT.selection.off).toHaveBeenCalledWith(
 | 
			
		||||
                    'change',
 | 
			
		||||
                    jasmine.any(Function)
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("Responds to status changes", function () {
 | 
			
		||||
                mockPolicyService.allow.andReturn(true);
 | 
			
		||||
                controller = new InspectorController(mockScope, mockPolicyService);
 | 
			
		||||
                expect(mockScope.regions.length).toBe(2);
 | 
			
		||||
                expect(mockStatusCapability.listen).toHaveBeenCalled();
 | 
			
		||||
                mockPolicyService.allow.andReturn(false);
 | 
			
		||||
                mockStatusCapability.listen.mostRecentCall.args[0]();
 | 
			
		||||
                expect(mockScope.regions.length).toBe(0);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("Unregisters status listener", function () {
 | 
			
		||||
                var mockListener = jasmine.createSpy('listener');
 | 
			
		||||
                mockStatusCapability.listen.andReturn(mockListener);
 | 
			
		||||
                controller = new InspectorController(mockScope, mockPolicyService);
 | 
			
		||||
                expect(mockScope.$on).toHaveBeenCalledWith("$destroy", jasmine.any(Function));
 | 
			
		||||
                mockScope.$on.mostRecentCall.args[1]();
 | 
			
		||||
                expect(mockListener).toHaveBeenCalled();
 | 
			
		||||
            it("adds selection object to scope", function () {
 | 
			
		||||
                expect(mockScope.selection).toEqual(selectable);
 | 
			
		||||
                expect(controller.selectedItem()).toEqual(mockDomainObject);
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,54 +0,0 @@
 | 
			
		||||
define([
 | 
			
		||||
    'text!./res/templates/autoflow-tabular.html',
 | 
			
		||||
    './src/AutoflowTabularController',
 | 
			
		||||
    './src/MCTAutoflowTable'
 | 
			
		||||
], function (
 | 
			
		||||
    autoflowTabularTemplate,
 | 
			
		||||
    AutoflowTabularController,
 | 
			
		||||
    MCTAutoflowTable
 | 
			
		||||
) {
 | 
			
		||||
    return function (options) {
 | 
			
		||||
        return function (openmct) {
 | 
			
		||||
            openmct.legacyRegistry.register("platform/features/autoflow", {
 | 
			
		||||
                "name": "WARP Telemetry Adapter",
 | 
			
		||||
                "description": "Retrieves telemetry from the WARP Server and provides related types and views.",
 | 
			
		||||
                "resources": "res",
 | 
			
		||||
                "extensions": {
 | 
			
		||||
                    "views": [
 | 
			
		||||
                        {
 | 
			
		||||
                            "key": "autoflow",
 | 
			
		||||
                            "name": "Autoflow Tabular",
 | 
			
		||||
                            "cssClass": "icon-packet",
 | 
			
		||||
                            "description": "A tabular view of packet contents.",
 | 
			
		||||
                            "template": autoflowTabularTemplate,
 | 
			
		||||
                            "type": options && options.type,
 | 
			
		||||
                            "needs": [
 | 
			
		||||
                                "telemetry"
 | 
			
		||||
                            ],
 | 
			
		||||
                            "delegation": true
 | 
			
		||||
                        }
 | 
			
		||||
                    ],
 | 
			
		||||
                    "controllers": [
 | 
			
		||||
                        {
 | 
			
		||||
                            "key": "AutoflowTabularController",
 | 
			
		||||
                            "implementation": AutoflowTabularController,
 | 
			
		||||
                            "depends": [
 | 
			
		||||
                                "$scope",
 | 
			
		||||
                                "$timeout",
 | 
			
		||||
                                "telemetrySubscriber"
 | 
			
		||||
                            ]
 | 
			
		||||
                        }
 | 
			
		||||
                    ],
 | 
			
		||||
                    "directives": [
 | 
			
		||||
                        {
 | 
			
		||||
                            "key": "mctAutoflowTable",
 | 
			
		||||
                            "implementation": MCTAutoflowTable
 | 
			
		||||
                        }
 | 
			
		||||
                    ]
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            openmct.legacyRegistry.enable("platform/features/autoflow");
 | 
			
		||||
        };
 | 
			
		||||
    };
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@@ -1,26 +0,0 @@
 | 
			
		||||
<div class="items-holder abs contents autoflow obj-value-format"
 | 
			
		||||
     ng-controller="AutoflowTabularController as autoflow">
 | 
			
		||||
    <div class="abs l-flex-row holder t-autoflow-header l-autoflow-header">
 | 
			
		||||
        <mct-include key="'input-filter'"
 | 
			
		||||
                     ng-model="autoflow.filter"
 | 
			
		||||
                     class="flex-elem">
 | 
			
		||||
        </mct-include>
 | 
			
		||||
        <div class="flex-elem grows t-last-update" title="Last Update">{{autoflow.updated()}}</div>
 | 
			
		||||
        <a title="Change column width"
 | 
			
		||||
            class="s-button flex-elem icon-arrows-right-left change-column-width"
 | 
			
		||||
            ng-click="autoflow.increaseColumnWidth()"></a>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="abs t-autoflow-items l-autoflow-items"
 | 
			
		||||
         mct-resize="autoflow.setBounds(bounds)"
 | 
			
		||||
         mct-resize-interval="50">
 | 
			
		||||
        <mct-autoflow-table values="autoflow.rangeValues()"
 | 
			
		||||
                            objects="autoflow.getTelemetryObjects()"
 | 
			
		||||
                            rows="autoflow.getRows()"
 | 
			
		||||
                            classes="autoflow.classes()"
 | 
			
		||||
                            updated="autoflow.updated()"
 | 
			
		||||
                            column-width="autoflow.columnWidth()"
 | 
			
		||||
                            counter="autoflow.counter()"
 | 
			
		||||
                            >
 | 
			
		||||
        </mct-autoflow-table>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
@@ -1,169 +0,0 @@
 | 
			
		||||
/*global angular*/
 | 
			
		||||
define(
 | 
			
		||||
    [],
 | 
			
		||||
    function () {
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * The link step for the `mct-autoflow-table` directive;
 | 
			
		||||
         * watches scope and updates the DOM appropriately.
 | 
			
		||||
         * See documentation in `MCTAutoflowTable.js` for the rationale
 | 
			
		||||
         * for including this directive, as well as for an explanation
 | 
			
		||||
         * of which values are placed in scope.
 | 
			
		||||
         *
 | 
			
		||||
         * @constructor
 | 
			
		||||
         * @param {Scope} scope the scope for this usage of the directive
 | 
			
		||||
         * @param element the jqLite-wrapped element which used this directive
 | 
			
		||||
         */
 | 
			
		||||
        function AutoflowTableLinker(scope, element) {
 | 
			
		||||
            var objects, // Domain objects at last structure refresh
 | 
			
		||||
                rows, // Number of rows from last structure refresh
 | 
			
		||||
                priorClasses = {},
 | 
			
		||||
                valueSpans = {}; // Span elements to put data values in
 | 
			
		||||
 | 
			
		||||
            // Create a new name-value pair in the specified column
 | 
			
		||||
            function createListItem(domainObject, ul) {
 | 
			
		||||
                // Create a new li, and spans to go in it.
 | 
			
		||||
                var li = angular.element('<li>'),
 | 
			
		||||
                    titleSpan = angular.element('<span>'),
 | 
			
		||||
                    valueSpan = angular.element('<span>');
 | 
			
		||||
 | 
			
		||||
                // Place spans in the li, and li into the column.
 | 
			
		||||
                // valueSpan must precede titleSpan in the DOM due to new CSS float approach
 | 
			
		||||
                li.append(valueSpan).append(titleSpan);
 | 
			
		||||
                ul.append(li);
 | 
			
		||||
 | 
			
		||||
                // Style appropriately
 | 
			
		||||
                li.addClass('l-autoflow-row');
 | 
			
		||||
                titleSpan.addClass('l-autoflow-item l');
 | 
			
		||||
                valueSpan.addClass('l-autoflow-item r l-obj-val-format');
 | 
			
		||||
 | 
			
		||||
                // Set text/tooltip for the name-value row
 | 
			
		||||
                titleSpan.text(domainObject.getModel().name);
 | 
			
		||||
                titleSpan.attr("title", domainObject.getModel().name);
 | 
			
		||||
 | 
			
		||||
                // Keep a reference to the span which will hold the
 | 
			
		||||
                // data value, to populate in the next refreshValues call
 | 
			
		||||
                valueSpans[domainObject.getId()] = valueSpan;
 | 
			
		||||
 | 
			
		||||
                return li;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Create a new column of name-value pairs in this table.
 | 
			
		||||
            function createColumn(el) {
 | 
			
		||||
                // Create a ul
 | 
			
		||||
                var ul = angular.element('<ul>');
 | 
			
		||||
 | 
			
		||||
                // Add it into the mct-autoflow-table
 | 
			
		||||
                el.append(ul);
 | 
			
		||||
 | 
			
		||||
                // Style appropriately
 | 
			
		||||
                ul.addClass('l-autoflow-col');
 | 
			
		||||
 | 
			
		||||
                // Get the current col width and apply at time of column creation
 | 
			
		||||
                // Important to do this here, as new columns could be created after
 | 
			
		||||
                // the user has changed the width.
 | 
			
		||||
                ul.css('width', scope.columnWidth + 'px');
 | 
			
		||||
 | 
			
		||||
                // Return it, so some li elements can be added
 | 
			
		||||
                return ul;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Change the width of the columns when user clicks the resize button.
 | 
			
		||||
            function resizeColumn() {
 | 
			
		||||
                element.find('ul').css('width', scope.columnWidth + 'px');
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Rebuild the DOM associated with this table.
 | 
			
		||||
            function rebuild(domainObjects, rowCount) {
 | 
			
		||||
                var activeColumn;
 | 
			
		||||
 | 
			
		||||
                // Empty out our cached span elements
 | 
			
		||||
                valueSpans = {};
 | 
			
		||||
 | 
			
		||||
                // Start with an empty DOM beneath this directive
 | 
			
		||||
                element.html("");
 | 
			
		||||
 | 
			
		||||
                // Add DOM elements for each domain object being displayed
 | 
			
		||||
                // in this table.
 | 
			
		||||
                domainObjects.forEach(function (object, index) {
 | 
			
		||||
                    // Start a new column if we'd run out of room
 | 
			
		||||
                    if (index % rowCount === 0) {
 | 
			
		||||
                        activeColumn = createColumn(element);
 | 
			
		||||
                    }
 | 
			
		||||
                    // Add the DOM elements for that object to whichever
 | 
			
		||||
                    // column (a `ul` element) is current.
 | 
			
		||||
                    createListItem(object, activeColumn);
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Update spans with values, as made available via the
 | 
			
		||||
            // `values` attribute of this directive.
 | 
			
		||||
            function refreshValues() {
 | 
			
		||||
                // Get the available values
 | 
			
		||||
                var values = scope.values || {},
 | 
			
		||||
                    classes = scope.classes || {};
 | 
			
		||||
 | 
			
		||||
                // Populate all spans with those values (or clear
 | 
			
		||||
                // those spans if no value is available)
 | 
			
		||||
                (objects || []).forEach(function (object) {
 | 
			
		||||
                    var id = object.getId(),
 | 
			
		||||
                        span = valueSpans[id],
 | 
			
		||||
                        value;
 | 
			
		||||
 | 
			
		||||
                    if (span) {
 | 
			
		||||
                        // Look up the value...
 | 
			
		||||
                        value = values[id];
 | 
			
		||||
                        // ...and convert to empty string if it's undefined
 | 
			
		||||
                        value = value === undefined ? "" : value;
 | 
			
		||||
                        span.attr("data-value", value);
 | 
			
		||||
 | 
			
		||||
                        // Update the span
 | 
			
		||||
                        span.text(value);
 | 
			
		||||
                        span.attr("title", value);
 | 
			
		||||
                        span.removeClass(priorClasses[id]);
 | 
			
		||||
                        span.addClass(classes[id]);
 | 
			
		||||
                        priorClasses[id] = classes[id];
 | 
			
		||||
                    }
 | 
			
		||||
                    // Also need stale/alert/ok class
 | 
			
		||||
                    // on span
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Refresh the DOM for this table, if necessary
 | 
			
		||||
            function refreshStructure() {
 | 
			
		||||
                // Only rebuild if number of rows or set of objects
 | 
			
		||||
                // has changed; otherwise, our structure is still valid.
 | 
			
		||||
                if (scope.objects !== objects ||
 | 
			
		||||
                        scope.rows !== rows) {
 | 
			
		||||
 | 
			
		||||
                    // Track those values to support future refresh checks
 | 
			
		||||
                    objects = scope.objects;
 | 
			
		||||
                    rows = scope.rows;
 | 
			
		||||
 | 
			
		||||
                    // Rebuild the DOM
 | 
			
		||||
                    rebuild(objects || [], rows || 1);
 | 
			
		||||
 | 
			
		||||
                    // Refresh all data values shown
 | 
			
		||||
                    refreshValues();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Changing the domain objects in use or the number
 | 
			
		||||
            // of rows should trigger a structure change (DOM rebuild)
 | 
			
		||||
            scope.$watch("objects", refreshStructure);
 | 
			
		||||
            scope.$watch("rows", refreshStructure);
 | 
			
		||||
 | 
			
		||||
            // When the current column width has been changed, resize the column
 | 
			
		||||
            scope.$watch('columnWidth', resizeColumn);
 | 
			
		||||
 | 
			
		||||
            // When the last-updated time ticks,
 | 
			
		||||
            scope.$watch("updated", refreshValues);
 | 
			
		||||
 | 
			
		||||
            // Update displayed values when the counter changes.
 | 
			
		||||
            scope.$watch("counter", refreshValues);
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return AutoflowTableLinker;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
@@ -1,324 +0,0 @@
 | 
			
		||||
 | 
			
		||||
define(
 | 
			
		||||
    ['moment'],
 | 
			
		||||
    function (moment) {
 | 
			
		||||
 | 
			
		||||
        var ROW_HEIGHT = 16,
 | 
			
		||||
            SLIDER_HEIGHT = 10,
 | 
			
		||||
            INITIAL_COLUMN_WIDTH = 225,
 | 
			
		||||
            MAX_COLUMN_WIDTH = 525,
 | 
			
		||||
            COLUMN_WIDTH_STEP = 25,
 | 
			
		||||
            DEBOUNCE_INTERVAL = 100,
 | 
			
		||||
            DATE_FORMAT = "YYYY-DDD HH:mm:ss.SSS\\Z",
 | 
			
		||||
            NOT_UPDATED = "No updates",
 | 
			
		||||
            EMPTY_ARRAY = [];
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Responsible for supporting the autoflow tabular view.
 | 
			
		||||
         * Implements the all-over logic which drives that view,
 | 
			
		||||
         * mediating between template-provided areas, the included
 | 
			
		||||
         * `mct-autoflow-table` directive, and the underlying
 | 
			
		||||
         * domain object model.
 | 
			
		||||
         * @constructor
 | 
			
		||||
         */
 | 
			
		||||
        function AutflowTabularController(
 | 
			
		||||
            $scope,
 | 
			
		||||
            $timeout,
 | 
			
		||||
            telemetrySubscriber
 | 
			
		||||
        ) {
 | 
			
		||||
            var filterValue = "",
 | 
			
		||||
                filterValueLowercase = "",
 | 
			
		||||
                subscription,
 | 
			
		||||
                filteredObjects = [],
 | 
			
		||||
                lastUpdated = {},
 | 
			
		||||
                updateText = NOT_UPDATED,
 | 
			
		||||
                rangeValues = {},
 | 
			
		||||
                classes = {},
 | 
			
		||||
                limits = {},
 | 
			
		||||
                updatePending = false,
 | 
			
		||||
                lastBounce = Number.NEGATIVE_INFINITY,
 | 
			
		||||
                columnWidth = INITIAL_COLUMN_WIDTH,
 | 
			
		||||
                rows = 1,
 | 
			
		||||
                counter = 0;
 | 
			
		||||
 | 
			
		||||
            // Trigger an update of the displayed table by incrementing
 | 
			
		||||
            // the counter that it watches.
 | 
			
		||||
            function triggerDisplayUpdate() {
 | 
			
		||||
                counter += 1;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Check whether or not an object's name matches the
 | 
			
		||||
            // user-entered filter value.
 | 
			
		||||
            function filterObject(domainObject) {
 | 
			
		||||
                return (domainObject.getModel().name || "")
 | 
			
		||||
                    .toLowerCase()
 | 
			
		||||
                    .indexOf(filterValueLowercase) !== -1;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Comparator for sorting points back into packet order
 | 
			
		||||
            function compareObject(objectA, objectB) {
 | 
			
		||||
                var indexA = objectA.getModel().index || 0,
 | 
			
		||||
                    indexB = objectB.getModel().index || 0;
 | 
			
		||||
                return indexA - indexB;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Update the list of currently-displayed objects; these
 | 
			
		||||
            // will be the subset of currently subscribed-to objects
 | 
			
		||||
            // which match a user-entered filter.
 | 
			
		||||
            function doUpdateFilteredObjects() {
 | 
			
		||||
                // Generate the list
 | 
			
		||||
                filteredObjects = (
 | 
			
		||||
                    subscription ?
 | 
			
		||||
                            subscription.getTelemetryObjects() :
 | 
			
		||||
                            []
 | 
			
		||||
                ).filter(filterObject).sort(compareObject);
 | 
			
		||||
 | 
			
		||||
                // Clear the pending flag
 | 
			
		||||
                updatePending = false;
 | 
			
		||||
 | 
			
		||||
                // Track when this occurred, so that we can wait
 | 
			
		||||
                // a whole before updating again.
 | 
			
		||||
                lastBounce = Date.now();
 | 
			
		||||
 | 
			
		||||
                triggerDisplayUpdate();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Request an update to the list of current objects; this may
 | 
			
		||||
            // run on a timeout to avoid excessive calls, e.g. while the user
 | 
			
		||||
            // is typing a filter.
 | 
			
		||||
            function updateFilteredObjects() {
 | 
			
		||||
                // Don't do anything if an update is already scheduled
 | 
			
		||||
                if (!updatePending) {
 | 
			
		||||
                    if (Date.now() > lastBounce + DEBOUNCE_INTERVAL) {
 | 
			
		||||
                        // Update immediately if it's been long enough
 | 
			
		||||
                        doUpdateFilteredObjects();
 | 
			
		||||
                    } else {
 | 
			
		||||
                        // Otherwise, update later, and track that we have
 | 
			
		||||
                        // an update pending so that subsequent calls can
 | 
			
		||||
                        // be ignored.
 | 
			
		||||
                        updatePending = true;
 | 
			
		||||
                        $timeout(doUpdateFilteredObjects, DEBOUNCE_INTERVAL);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Track the latest data values for this domain object
 | 
			
		||||
            function recordData(telemetryObject) {
 | 
			
		||||
                // Get latest domain/range values for this object.
 | 
			
		||||
                var id = telemetryObject.getId(),
 | 
			
		||||
                    domainValue = subscription.getDomainValue(telemetryObject),
 | 
			
		||||
                    rangeValue = subscription.getRangeValue(telemetryObject);
 | 
			
		||||
 | 
			
		||||
                // Track the most recent timestamp change observed...
 | 
			
		||||
                if (domainValue !== undefined && domainValue !== lastUpdated[id]) {
 | 
			
		||||
                    lastUpdated[id] = domainValue;
 | 
			
		||||
                    // ... and update the displayable text for that timestamp
 | 
			
		||||
                    updateText = isNaN(domainValue) ? "" :
 | 
			
		||||
                            moment.utc(domainValue).format(DATE_FORMAT);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Store data values into the rangeValues structure, which
 | 
			
		||||
                // will be used to populate the table itself.
 | 
			
		||||
                // Note that we want full precision here.
 | 
			
		||||
                rangeValues[id] = rangeValue;
 | 
			
		||||
 | 
			
		||||
                // Update limit states as well
 | 
			
		||||
                classes[id] = limits[id] && (limits[id].evaluate({
 | 
			
		||||
                    // This relies on external knowledge that the
 | 
			
		||||
                    // range value of a telemetry point is encoded
 | 
			
		||||
                    // in its datum as "value."
 | 
			
		||||
                    value: rangeValue
 | 
			
		||||
                }) || {}).cssClass;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            // Look at telemetry objects from the subscription; this is watched
 | 
			
		||||
            // to detect changes from the subscription.
 | 
			
		||||
            function subscribedTelemetry() {
 | 
			
		||||
                return subscription ?
 | 
			
		||||
                        subscription.getTelemetryObjects() : EMPTY_ARRAY;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Update the data values which will be used to populate the table
 | 
			
		||||
            function updateValues() {
 | 
			
		||||
                subscribedTelemetry().forEach(recordData);
 | 
			
		||||
                triggerDisplayUpdate();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Getter-setter function for user-entered filter text.
 | 
			
		||||
            function filter(value) {
 | 
			
		||||
                // If value was specified, we're a setter
 | 
			
		||||
                if (value !== undefined) {
 | 
			
		||||
                    // Store the new value
 | 
			
		||||
                    filterValue = value;
 | 
			
		||||
                    filterValueLowercase = value.toLowerCase();
 | 
			
		||||
                    // Change which objects appear in the table
 | 
			
		||||
                    updateFilteredObjects();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Always act as a getter
 | 
			
		||||
                return filterValue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Update the bounds (width and height) of this view;
 | 
			
		||||
            // called from the mct-resize directive. Recalculates how
 | 
			
		||||
            // many rows should appear in the contained table.
 | 
			
		||||
            function setBounds(bounds) {
 | 
			
		||||
                var availableSpace = bounds.height - SLIDER_HEIGHT;
 | 
			
		||||
                rows = Math.max(1, Math.floor(availableSpace / ROW_HEIGHT));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Increment the current column width, up to the defined maximum.
 | 
			
		||||
            // When the max is hit, roll back to the default.
 | 
			
		||||
            function increaseColumnWidth() {
 | 
			
		||||
                columnWidth += COLUMN_WIDTH_STEP;
 | 
			
		||||
                // Cycle down to the initial width instead of exceeding max
 | 
			
		||||
                columnWidth = columnWidth > MAX_COLUMN_WIDTH ?
 | 
			
		||||
                        INITIAL_COLUMN_WIDTH : columnWidth;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Get displayable text for last-updated value
 | 
			
		||||
            function updated() {
 | 
			
		||||
                return updateText;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Unsubscribe, if a subscription is active.
 | 
			
		||||
            function releaseSubscription() {
 | 
			
		||||
                if (subscription) {
 | 
			
		||||
                    subscription.unsubscribe();
 | 
			
		||||
                    subscription = undefined;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Update set of telemetry objects managed by this view
 | 
			
		||||
            function updateTelemetryObjects(telemetryObjects) {
 | 
			
		||||
                updateFilteredObjects();
 | 
			
		||||
                limits = {};
 | 
			
		||||
                telemetryObjects.forEach(function (telemetryObject) {
 | 
			
		||||
                    var id = telemetryObject.getId();
 | 
			
		||||
                    limits[id] = telemetryObject.getCapability('limit');
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Create a subscription for the represented domain object.
 | 
			
		||||
            // This will resolve capability delegation as necessary.
 | 
			
		||||
            function makeSubscription(domainObject) {
 | 
			
		||||
                // Unsubscribe, if there is an existing subscription
 | 
			
		||||
                releaseSubscription();
 | 
			
		||||
 | 
			
		||||
                // Clear updated timestamp
 | 
			
		||||
                lastUpdated = {};
 | 
			
		||||
                updateText = NOT_UPDATED;
 | 
			
		||||
 | 
			
		||||
                // Create a new subscription; telemetrySubscriber gets
 | 
			
		||||
                // to do the meaningful work here.
 | 
			
		||||
                subscription = domainObject && telemetrySubscriber.subscribe(
 | 
			
		||||
                    domainObject,
 | 
			
		||||
                    updateValues
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                // Our set of in-view telemetry objects may have changed,
 | 
			
		||||
                // so update the set that is being passed down to the table.
 | 
			
		||||
                updateFilteredObjects();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Watch for changes to the set of objects which have telemetry
 | 
			
		||||
            $scope.$watch(subscribedTelemetry, updateTelemetryObjects);
 | 
			
		||||
 | 
			
		||||
            // Watch for the represented domainObject (this field will
 | 
			
		||||
            // be populated by mct-representation)
 | 
			
		||||
            $scope.$watch("domainObject", makeSubscription);
 | 
			
		||||
 | 
			
		||||
            // Make sure we unsubscribe when this view is destroyed.
 | 
			
		||||
            $scope.$on("$destroy", releaseSubscription);
 | 
			
		||||
 | 
			
		||||
            return {
 | 
			
		||||
                /**
 | 
			
		||||
                 * Get the number of rows which should be shown in this table.
 | 
			
		||||
                 * @return {number} the number of rows to show
 | 
			
		||||
                 */
 | 
			
		||||
                getRows: function () {
 | 
			
		||||
                    return rows;
 | 
			
		||||
                },
 | 
			
		||||
                /**
 | 
			
		||||
                 * Get the objects which should currently be displayed in
 | 
			
		||||
                 * this table. This will be watched, so the return value
 | 
			
		||||
                 * should be stable when this list is unchanging. Only
 | 
			
		||||
                 * objects which match the user-entered filter value should
 | 
			
		||||
                 * be returned here.
 | 
			
		||||
                 * @return {DomainObject[]} the domain objects to include in
 | 
			
		||||
                 *         this table.
 | 
			
		||||
                 */
 | 
			
		||||
                getTelemetryObjects: function () {
 | 
			
		||||
                    return filteredObjects;
 | 
			
		||||
                },
 | 
			
		||||
                /**
 | 
			
		||||
                 * Set the bounds (width/height) of this autoflow tabular view.
 | 
			
		||||
                 * The template must ensure that these bounds are tracked on
 | 
			
		||||
                 * the table area only.
 | 
			
		||||
                 * @param bounds the bounds; and object with `width` and
 | 
			
		||||
                 *        `height` properties, both as numbers, in pixels.
 | 
			
		||||
                 */
 | 
			
		||||
                setBounds: setBounds,
 | 
			
		||||
                /**
 | 
			
		||||
                 * Increments the width of the autoflow column.
 | 
			
		||||
                 * Setting does not yet persist.
 | 
			
		||||
                 */
 | 
			
		||||
                increaseColumnWidth: increaseColumnWidth,
 | 
			
		||||
                /**
 | 
			
		||||
                 * Get-or-set the user-supplied filter value.
 | 
			
		||||
                 * @param {string} [value] the new filter value; omit to use
 | 
			
		||||
                 *        as a getter
 | 
			
		||||
                 * @returns {string} the user-supplied filter value
 | 
			
		||||
                 */
 | 
			
		||||
                filter: filter,
 | 
			
		||||
                /**
 | 
			
		||||
                 * Get all range values for use in this table. These will be
 | 
			
		||||
                 * returned as an object of key-value pairs, where keys are
 | 
			
		||||
                 * domain object IDs, and values are the most recently observed
 | 
			
		||||
                 * data values associated with those objects, formatted for
 | 
			
		||||
                 * display.
 | 
			
		||||
                 * @returns {object.<string,string>} most recent values
 | 
			
		||||
                 */
 | 
			
		||||
                rangeValues: function () {
 | 
			
		||||
                    return rangeValues;
 | 
			
		||||
                },
 | 
			
		||||
                /**
 | 
			
		||||
                 * Get CSS classes to apply to specific rows, representing limit
 | 
			
		||||
                 * states and/or stale states. These are returned as key-value
 | 
			
		||||
                 * pairs where keys are domain object IDs, and values are CSS
 | 
			
		||||
                 * classes to display for domain objects with those IDs.
 | 
			
		||||
                 * @returns {object.<string,string>} CSS classes
 | 
			
		||||
                 */
 | 
			
		||||
                classes: function () {
 | 
			
		||||
                    return classes;
 | 
			
		||||
                },
 | 
			
		||||
                /**
 | 
			
		||||
                 * Get the "last updated" text for this view; this will be
 | 
			
		||||
                 * the most recent timestamp observed for any telemetry-
 | 
			
		||||
                 * providing object, formatted for display.
 | 
			
		||||
                 * @returns {string} the time of the most recent update
 | 
			
		||||
                 */
 | 
			
		||||
                updated: updated,
 | 
			
		||||
                /**
 | 
			
		||||
                 * Get the current column width, in pixels.
 | 
			
		||||
                 * @returns {number} column width
 | 
			
		||||
                 */
 | 
			
		||||
                columnWidth: function () {
 | 
			
		||||
                    return columnWidth;
 | 
			
		||||
                },
 | 
			
		||||
                /**
 | 
			
		||||
                 * Keep a counter and increment this whenever the display
 | 
			
		||||
                 * should be updated; this will be watched by the
 | 
			
		||||
                 * `mct-autoflow-table`.
 | 
			
		||||
                 * @returns {number} a counter value
 | 
			
		||||
                 */
 | 
			
		||||
                counter: function () {
 | 
			
		||||
                    return counter;
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return AutflowTabularController;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
@@ -1,60 +0,0 @@
 | 
			
		||||
 | 
			
		||||
define(
 | 
			
		||||
    ["./AutoflowTableLinker"],
 | 
			
		||||
    function (AutoflowTableLinker) {
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * The `mct-autoflow-table` directive specifically supports
 | 
			
		||||
         * autoflow tabular views; it is not intended for use outside
 | 
			
		||||
         * of that view.
 | 
			
		||||
         *
 | 
			
		||||
         * This directive is responsible for creating the structure
 | 
			
		||||
         * of the table in this view, and for updating its values.
 | 
			
		||||
         * While this is achievable using a regular Angular template,
 | 
			
		||||
         * this is undesirable from the perspective of performance
 | 
			
		||||
         * due to the number of watches that can be involved for large
 | 
			
		||||
         * tables. Instead, this directive will maintain a small number
 | 
			
		||||
         * of watches, rebuilding table structure only when necessary,
 | 
			
		||||
         * and updating displayed values in the more common case of
 | 
			
		||||
         * new data arriving.
 | 
			
		||||
         *
 | 
			
		||||
         * @constructor
 | 
			
		||||
         */
 | 
			
		||||
        function MCTAutoflowTable() {
 | 
			
		||||
            return {
 | 
			
		||||
                // Only applicable at the element level
 | 
			
		||||
                restrict: "E",
 | 
			
		||||
 | 
			
		||||
                // The link function; handles DOM update/manipulation
 | 
			
		||||
                link: AutoflowTableLinker,
 | 
			
		||||
 | 
			
		||||
                // Parameters to pass from attributes into scope
 | 
			
		||||
                scope: {
 | 
			
		||||
                    // Set of domain objects to show in the table
 | 
			
		||||
                    objects: "=",
 | 
			
		||||
 | 
			
		||||
                    // Values for those objects, by ID
 | 
			
		||||
                    values: "=",
 | 
			
		||||
 | 
			
		||||
                    // CSS classes to show for objects, by ID
 | 
			
		||||
                    classes: "=",
 | 
			
		||||
 | 
			
		||||
                    // Number of rows to show before autoflowing
 | 
			
		||||
                    rows: "=",
 | 
			
		||||
 | 
			
		||||
                    // Time of last update; watched to refresh values
 | 
			
		||||
                    updated: "=",
 | 
			
		||||
 | 
			
		||||
                    // Current width of the autoflow column
 | 
			
		||||
                    columnWidth: "=",
 | 
			
		||||
 | 
			
		||||
                    // A counter used to trigger display updates
 | 
			
		||||
                    counter: "="
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return MCTAutoflowTable;
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
@@ -1,178 +0,0 @@
 | 
			
		||||
 | 
			
		||||
define(
 | 
			
		||||
    ["../src/AutoflowTableLinker"],
 | 
			
		||||
    function (AutoflowTableLinker) {
 | 
			
		||||
 | 
			
		||||
        describe("The mct-autoflow-table linker", function () {
 | 
			
		||||
            var cachedAngular,
 | 
			
		||||
                mockAngular,
 | 
			
		||||
                mockScope,
 | 
			
		||||
                mockElement,
 | 
			
		||||
                mockElements,
 | 
			
		||||
                linker;
 | 
			
		||||
 | 
			
		||||
            // Utility function to generate more mock elements
 | 
			
		||||
            function createMockElement(html) {
 | 
			
		||||
                var mockEl = jasmine.createSpyObj(
 | 
			
		||||
                    "element-" + html,
 | 
			
		||||
                    [
 | 
			
		||||
                        "append",
 | 
			
		||||
                        "addClass",
 | 
			
		||||
                        "removeClass",
 | 
			
		||||
                        "text",
 | 
			
		||||
                        "attr",
 | 
			
		||||
                        "html",
 | 
			
		||||
                        "css",
 | 
			
		||||
                        "find"
 | 
			
		||||
                    ]
 | 
			
		||||
                );
 | 
			
		||||
                mockEl.testHtml = html;
 | 
			
		||||
                mockEl.append.andReturn(mockEl);
 | 
			
		||||
                mockElements.push(mockEl);
 | 
			
		||||
                return mockEl;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function createMockDomainObject(id) {
 | 
			
		||||
                var mockDomainObject = jasmine.createSpyObj(
 | 
			
		||||
                    "domainObject-" + id,
 | 
			
		||||
                    ["getId", "getModel"]
 | 
			
		||||
                );
 | 
			
		||||
                mockDomainObject.getId.andReturn(id);
 | 
			
		||||
                mockDomainObject.getModel.andReturn({name: id.toUpperCase()});
 | 
			
		||||
                return mockDomainObject;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function fireWatch(watchExpression, value) {
 | 
			
		||||
                mockScope.$watch.calls.forEach(function (call) {
 | 
			
		||||
                    if (call.args[0] === watchExpression) {
 | 
			
		||||
                        call.args[1](value);
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // AutoflowTableLinker accesses Angular in the global
 | 
			
		||||
            // scope, since it is not injectable; we simulate that
 | 
			
		||||
            // here by adding/removing it to/from the window object.
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                mockElements = [];
 | 
			
		||||
 | 
			
		||||
                mockAngular = jasmine.createSpyObj("angular", ["element"]);
 | 
			
		||||
                mockScope = jasmine.createSpyObj("scope", ["$watch"]);
 | 
			
		||||
                mockElement = createMockElement('<div>');
 | 
			
		||||
 | 
			
		||||
                mockAngular.element.andCallFake(createMockElement);
 | 
			
		||||
 | 
			
		||||
                if (window.angular !== undefined) {
 | 
			
		||||
                    cachedAngular = window.angular;
 | 
			
		||||
                }
 | 
			
		||||
                window.angular = mockAngular;
 | 
			
		||||
 | 
			
		||||
                linker = new AutoflowTableLinker(mockScope, mockElement);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            afterEach(function () {
 | 
			
		||||
                if (cachedAngular !== undefined) {
 | 
			
		||||
                    window.angular = cachedAngular;
 | 
			
		||||
                } else {
 | 
			
		||||
                    delete window.angular;
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("watches for changes in inputs", function () {
 | 
			
		||||
                expect(mockScope.$watch).toHaveBeenCalledWith(
 | 
			
		||||
                    "objects",
 | 
			
		||||
                    jasmine.any(Function)
 | 
			
		||||
                );
 | 
			
		||||
                expect(mockScope.$watch).toHaveBeenCalledWith(
 | 
			
		||||
                    "rows",
 | 
			
		||||
                    jasmine.any(Function)
 | 
			
		||||
                );
 | 
			
		||||
                expect(mockScope.$watch).toHaveBeenCalledWith(
 | 
			
		||||
                    "counter",
 | 
			
		||||
                    jasmine.any(Function)
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("changes structure when domain objects change", function () {
 | 
			
		||||
                // Set up scope
 | 
			
		||||
                mockScope.rows = 4;
 | 
			
		||||
                mockScope.objects = ['a', 'b', 'c', 'd', 'e', 'f']
 | 
			
		||||
                    .map(createMockDomainObject);
 | 
			
		||||
 | 
			
		||||
                // Fire an update to the set of objects
 | 
			
		||||
                fireWatch("objects");
 | 
			
		||||
 | 
			
		||||
                // Should have rebuilt with two columns of
 | 
			
		||||
                // four and two rows each; first, by clearing...
 | 
			
		||||
                expect(mockElement.html).toHaveBeenCalledWith("");
 | 
			
		||||
 | 
			
		||||
                // Should have appended two columns...
 | 
			
		||||
                expect(mockElement.append.calls.length).toEqual(2);
 | 
			
		||||
 | 
			
		||||
                // ...which should have received two and four rows each
 | 
			
		||||
                expect(mockElement.append.calls[0].args[0].append.calls.length)
 | 
			
		||||
                    .toEqual(4);
 | 
			
		||||
                expect(mockElement.append.calls[1].args[0].append.calls.length)
 | 
			
		||||
                    .toEqual(2);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("updates values", function () {
 | 
			
		||||
                var mockSpans;
 | 
			
		||||
 | 
			
		||||
                mockScope.objects = ['a', 'b', 'c', 'd', 'e', 'f']
 | 
			
		||||
                    .map(createMockDomainObject);
 | 
			
		||||
                mockScope.values = { a: 0 };
 | 
			
		||||
 | 
			
		||||
                // Fire an update to the set of values
 | 
			
		||||
                fireWatch("objects");
 | 
			
		||||
                fireWatch("updated");
 | 
			
		||||
 | 
			
		||||
                // Get all created spans
 | 
			
		||||
                mockSpans = mockElements.filter(function (mockElem) {
 | 
			
		||||
                    return mockElem.testHtml === '<span>';
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                // First span should be a, should have gotten this value.
 | 
			
		||||
                // This test detects, in particular, WTD-749
 | 
			
		||||
                expect(mockSpans[0].text).toHaveBeenCalledWith('A');
 | 
			
		||||
                expect(mockSpans[1].text).toHaveBeenCalledWith(0);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("listens for changes in column width", function () {
 | 
			
		||||
                var mockUL = createMockElement("<ul>");
 | 
			
		||||
                mockElement.find.andReturn(mockUL);
 | 
			
		||||
                mockScope.columnWidth = 200;
 | 
			
		||||
                fireWatch("columnWidth", mockScope.columnWidth);
 | 
			
		||||
                expect(mockUL.css).toHaveBeenCalledWith("width", "200px");
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("updates CSS classes", function () {
 | 
			
		||||
                var mockSpans;
 | 
			
		||||
 | 
			
		||||
                mockScope.objects = ['a', 'b', 'c', 'd', 'e', 'f']
 | 
			
		||||
                    .map(createMockDomainObject);
 | 
			
		||||
                mockScope.values = { a: "a value to find" };
 | 
			
		||||
                mockScope.classes = { a: 'class-a' };
 | 
			
		||||
 | 
			
		||||
                // Fire an update to the set of values
 | 
			
		||||
                fireWatch("objects");
 | 
			
		||||
                fireWatch("updated");
 | 
			
		||||
 | 
			
		||||
                // Figure out which span holds the relevant value...
 | 
			
		||||
                mockSpans = mockElements.filter(function (mockElem) {
 | 
			
		||||
                    return mockElem.testHtml === '<span>';
 | 
			
		||||
                }).filter(function (mockSpan) {
 | 
			
		||||
                    var attrCalls = mockSpan.attr.calls;
 | 
			
		||||
                    return attrCalls.some(function (call) {
 | 
			
		||||
                        return call.args[0] === 'title' &&
 | 
			
		||||
                                call.args[1] === mockScope.values.a;
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                // ...and make sure it also has had its class applied
 | 
			
		||||
                expect(mockSpans[0].addClass)
 | 
			
		||||
                    .toHaveBeenCalledWith(mockScope.classes.a);
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
@@ -1,341 +0,0 @@
 | 
			
		||||
 | 
			
		||||
define(
 | 
			
		||||
    ["../src/AutoflowTabularController"],
 | 
			
		||||
    function (AutoflowTabularController) {
 | 
			
		||||
 | 
			
		||||
        describe("The autoflow tabular controller", function () {
 | 
			
		||||
            var mockScope,
 | 
			
		||||
                mockTimeout,
 | 
			
		||||
                mockSubscriber,
 | 
			
		||||
                mockDomainObject,
 | 
			
		||||
                mockSubscription,
 | 
			
		||||
                controller;
 | 
			
		||||
 | 
			
		||||
            // Fire watches that are registered as functions.
 | 
			
		||||
            function fireFnWatches() {
 | 
			
		||||
                mockScope.$watch.calls.forEach(function (call) {
 | 
			
		||||
                    if (typeof call.args[0] === 'function') {
 | 
			
		||||
                        call.args[1](call.args[0]());
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                mockScope = jasmine.createSpyObj(
 | 
			
		||||
                    "$scope",
 | 
			
		||||
                    ["$on", "$watch"]
 | 
			
		||||
                );
 | 
			
		||||
                mockTimeout = jasmine.createSpy("$timeout");
 | 
			
		||||
                mockSubscriber = jasmine.createSpyObj(
 | 
			
		||||
                    "telemetrySubscriber",
 | 
			
		||||
                    ["subscribe"]
 | 
			
		||||
                );
 | 
			
		||||
                mockDomainObject = jasmine.createSpyObj(
 | 
			
		||||
                    "domainObject",
 | 
			
		||||
                    ["getId", "getModel", "getCapability"]
 | 
			
		||||
                );
 | 
			
		||||
                mockSubscription = jasmine.createSpyObj(
 | 
			
		||||
                    "subscription",
 | 
			
		||||
                    [
 | 
			
		||||
                        "unsubscribe",
 | 
			
		||||
                        "getTelemetryObjects",
 | 
			
		||||
                        "getDomainValue",
 | 
			
		||||
                        "getRangeValue"
 | 
			
		||||
                    ]
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                mockSubscriber.subscribe.andReturn(mockSubscription);
 | 
			
		||||
                mockDomainObject.getModel.andReturn({name: "something"});
 | 
			
		||||
 | 
			
		||||
                controller = new AutoflowTabularController(
 | 
			
		||||
                    mockScope,
 | 
			
		||||
                    mockTimeout,
 | 
			
		||||
                    mockSubscriber
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("listens for the represented domain object", function () {
 | 
			
		||||
                expect(mockScope.$watch).toHaveBeenCalledWith(
 | 
			
		||||
                    "domainObject",
 | 
			
		||||
                    jasmine.any(Function)
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("provides a getter-setter function for filtering", function () {
 | 
			
		||||
                expect(controller.filter()).toEqual("");
 | 
			
		||||
                controller.filter("something");
 | 
			
		||||
                expect(controller.filter()).toEqual("something");
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("tracks bounds and adjust number of rows accordingly", function () {
 | 
			
		||||
                // Rows are 15px high, and need room for an 10px slider
 | 
			
		||||
                controller.setBounds({ width: 700, height: 120 });
 | 
			
		||||
                expect(controller.getRows()).toEqual(6); // 110 usable height / 16px
 | 
			
		||||
                controller.setBounds({ width: 700, height: 240 });
 | 
			
		||||
                expect(controller.getRows()).toEqual(14); // 230 usable height / 16px
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("subscribes to a represented object's telemetry", function () {
 | 
			
		||||
                // Set up subscription, scope
 | 
			
		||||
                mockSubscription.getTelemetryObjects
 | 
			
		||||
                    .andReturn([mockDomainObject]);
 | 
			
		||||
                mockScope.domainObject = mockDomainObject;
 | 
			
		||||
 | 
			
		||||
                // Invoke the watcher with represented domain object
 | 
			
		||||
                mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
 | 
			
		||||
 | 
			
		||||
                // Should have subscribed to it
 | 
			
		||||
                expect(mockSubscriber.subscribe).toHaveBeenCalledWith(
 | 
			
		||||
                    mockDomainObject,
 | 
			
		||||
                    jasmine.any(Function)
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                // Should report objects as reported from subscription
 | 
			
		||||
                expect(controller.getTelemetryObjects())
 | 
			
		||||
                    .toEqual([mockDomainObject]);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("releases subscriptions on destroy", function () {
 | 
			
		||||
                // Set up subscription...
 | 
			
		||||
                mockSubscription.getTelemetryObjects
 | 
			
		||||
                    .andReturn([mockDomainObject]);
 | 
			
		||||
                mockScope.domainObject = mockDomainObject;
 | 
			
		||||
                mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
 | 
			
		||||
 | 
			
		||||
                // Verify precondition
 | 
			
		||||
                expect(mockSubscription.unsubscribe).not.toHaveBeenCalled();
 | 
			
		||||
 | 
			
		||||
                // Make sure we're listening for $destroy
 | 
			
		||||
                expect(mockScope.$on).toHaveBeenCalledWith(
 | 
			
		||||
                    "$destroy",
 | 
			
		||||
                    jasmine.any(Function)
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                // Fire a destroy event
 | 
			
		||||
                mockScope.$on.mostRecentCall.args[1]();
 | 
			
		||||
 | 
			
		||||
                // Should have unsubscribed
 | 
			
		||||
                expect(mockSubscription.unsubscribe).toHaveBeenCalled();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("presents latest values and latest update state", function () {
 | 
			
		||||
                // Make sure values are available
 | 
			
		||||
                mockSubscription.getDomainValue.andReturn(402654321123);
 | 
			
		||||
                mockSubscription.getRangeValue.andReturn(789);
 | 
			
		||||
                mockDomainObject.getId.andReturn('testId');
 | 
			
		||||
 | 
			
		||||
                // Set up subscription...
 | 
			
		||||
                mockSubscription.getTelemetryObjects
 | 
			
		||||
                    .andReturn([mockDomainObject]);
 | 
			
		||||
                mockScope.domainObject = mockDomainObject;
 | 
			
		||||
                mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
 | 
			
		||||
 | 
			
		||||
                // Fire subscription callback
 | 
			
		||||
                mockSubscriber.subscribe.mostRecentCall.args[1]();
 | 
			
		||||
 | 
			
		||||
                // ...and exposed the results for template to consume
 | 
			
		||||
                expect(controller.updated()).toEqual("1982-278 08:25:21.123Z");
 | 
			
		||||
                expect(controller.rangeValues().testId).toEqual(789);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("sorts domain objects by index", function () {
 | 
			
		||||
                var testIndexes = { a: 2, b: 1, c: 3, d: 0 },
 | 
			
		||||
                    mockDomainObjects = Object.keys(testIndexes).sort().map(function (id) {
 | 
			
		||||
                        var mockDomainObj = jasmine.createSpyObj(
 | 
			
		||||
                            "domainObject",
 | 
			
		||||
                            ["getId", "getModel"]
 | 
			
		||||
                        );
 | 
			
		||||
 | 
			
		||||
                        mockDomainObj.getId.andReturn(id);
 | 
			
		||||
                        mockDomainObj.getModel.andReturn({ index: testIndexes[id] });
 | 
			
		||||
 | 
			
		||||
                        return mockDomainObj;
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                // Expose those domain objects...
 | 
			
		||||
                mockSubscription.getTelemetryObjects.andReturn(mockDomainObjects);
 | 
			
		||||
                mockScope.domainObject = mockDomainObject;
 | 
			
		||||
                mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
 | 
			
		||||
 | 
			
		||||
                // Fire subscription callback
 | 
			
		||||
                mockSubscriber.subscribe.mostRecentCall.args[1]();
 | 
			
		||||
 | 
			
		||||
                // Controller should expose same objects, but sorted by index from model
 | 
			
		||||
                expect(controller.getTelemetryObjects()).toEqual([
 | 
			
		||||
                    mockDomainObjects[3], // d, index=0
 | 
			
		||||
                    mockDomainObjects[1], // b, index=1
 | 
			
		||||
                    mockDomainObjects[0], // a, index=2
 | 
			
		||||
                    mockDomainObjects[2]  // c, index=3
 | 
			
		||||
                ]);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("uses a timeout to throttle update", function () {
 | 
			
		||||
                // Set up subscription...
 | 
			
		||||
                mockSubscription.getTelemetryObjects
 | 
			
		||||
                    .andReturn([mockDomainObject]);
 | 
			
		||||
                mockScope.domainObject = mockDomainObject;
 | 
			
		||||
 | 
			
		||||
                // Set the object in view; should not need a timeout
 | 
			
		||||
                mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
 | 
			
		||||
                expect(mockTimeout.calls.length).toEqual(0);
 | 
			
		||||
 | 
			
		||||
                // Next call should schedule an update on a timeout
 | 
			
		||||
                mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
 | 
			
		||||
                expect(mockTimeout.calls.length).toEqual(1);
 | 
			
		||||
 | 
			
		||||
                // ...but this last one should not, since existing
 | 
			
		||||
                // timeout will cover it
 | 
			
		||||
                mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
 | 
			
		||||
                expect(mockTimeout.calls.length).toEqual(1);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("allows changing column width", function () {
 | 
			
		||||
                var initialWidth = controller.columnWidth();
 | 
			
		||||
                controller.increaseColumnWidth();
 | 
			
		||||
                expect(controller.columnWidth()).toBeGreaterThan(initialWidth);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            describe("filter", function () {
 | 
			
		||||
                var doFilter,
 | 
			
		||||
                    filteredObjects,
 | 
			
		||||
                    filteredObjectNames;
 | 
			
		||||
 | 
			
		||||
                beforeEach(function () {
 | 
			
		||||
                    var telemetryObjects,
 | 
			
		||||
                        updateFilteredObjects;
 | 
			
		||||
 | 
			
		||||
                    telemetryObjects = [
 | 
			
		||||
                        'DEF123',
 | 
			
		||||
                        'abc789',
 | 
			
		||||
                        '456abc',
 | 
			
		||||
                        '4ab3cdef',
 | 
			
		||||
                        'hjs[12].*(){}^\\'
 | 
			
		||||
                    ].map(function (objectName, index) {
 | 
			
		||||
                        var mockTelemetryObject = jasmine.createSpyObj(
 | 
			
		||||
                            objectName,
 | 
			
		||||
                            ["getId", "getModel"]
 | 
			
		||||
                        );
 | 
			
		||||
 | 
			
		||||
                        mockTelemetryObject.getId.andReturn(objectName);
 | 
			
		||||
                        mockTelemetryObject.getModel.andReturn({
 | 
			
		||||
                            name: objectName,
 | 
			
		||||
                            index: index
 | 
			
		||||
                        });
 | 
			
		||||
 | 
			
		||||
                        return mockTelemetryObject;
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                    mockSubscription
 | 
			
		||||
                        .getTelemetryObjects
 | 
			
		||||
                        .andReturn(telemetryObjects);
 | 
			
		||||
 | 
			
		||||
                    // Trigger domainObject change to create subscription.
 | 
			
		||||
                    mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
 | 
			
		||||
 | 
			
		||||
                    updateFilteredObjects = function () {
 | 
			
		||||
                        filteredObjects = controller.getTelemetryObjects();
 | 
			
		||||
                        filteredObjectNames = filteredObjects.map(function (o) {
 | 
			
		||||
                            return o.getModel().name;
 | 
			
		||||
                        });
 | 
			
		||||
                    };
 | 
			
		||||
 | 
			
		||||
                    doFilter = function (term) {
 | 
			
		||||
                        controller.filter(term);
 | 
			
		||||
                        // Filter is debounced so we have to force it to occur.
 | 
			
		||||
                        mockTimeout.mostRecentCall.args[0]();
 | 
			
		||||
                        updateFilteredObjects();
 | 
			
		||||
                    };
 | 
			
		||||
 | 
			
		||||
                    updateFilteredObjects();
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("initially shows all objects", function () {
 | 
			
		||||
                    expect(filteredObjectNames).toEqual([
 | 
			
		||||
                        'DEF123',
 | 
			
		||||
                        'abc789',
 | 
			
		||||
                        '456abc',
 | 
			
		||||
                        '4ab3cdef',
 | 
			
		||||
                        'hjs[12].*(){}^\\'
 | 
			
		||||
                    ]);
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("by blank string matches all objects", function () {
 | 
			
		||||
                    doFilter('');
 | 
			
		||||
                    expect(filteredObjectNames).toEqual([
 | 
			
		||||
                        'DEF123',
 | 
			
		||||
                        'abc789',
 | 
			
		||||
                        '456abc',
 | 
			
		||||
                        '4ab3cdef',
 | 
			
		||||
                        'hjs[12].*(){}^\\'
 | 
			
		||||
                    ]);
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("exactly matches an object name", function () {
 | 
			
		||||
                    doFilter('4ab3cdef');
 | 
			
		||||
                    expect(filteredObjectNames).toEqual(['4ab3cdef']);
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("partially matches object names", function () {
 | 
			
		||||
                    doFilter('abc');
 | 
			
		||||
                    expect(filteredObjectNames).toEqual([
 | 
			
		||||
                        'abc789',
 | 
			
		||||
                        '456abc'
 | 
			
		||||
                    ]);
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("matches case insensitive names", function () {
 | 
			
		||||
                    doFilter('def');
 | 
			
		||||
                    expect(filteredObjectNames).toEqual([
 | 
			
		||||
                        'DEF123',
 | 
			
		||||
                        '4ab3cdef'
 | 
			
		||||
                    ]);
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("works as expected with special characters", function () {
 | 
			
		||||
                    doFilter('[12]');
 | 
			
		||||
                    expect(filteredObjectNames).toEqual(['hjs[12].*(){}^\\']);
 | 
			
		||||
                    doFilter('.*');
 | 
			
		||||
                    expect(filteredObjectNames).toEqual(['hjs[12].*(){}^\\']);
 | 
			
		||||
                    doFilter('.*()');
 | 
			
		||||
                    expect(filteredObjectNames).toEqual(['hjs[12].*(){}^\\']);
 | 
			
		||||
                    doFilter('.*?');
 | 
			
		||||
                    expect(filteredObjectNames).toEqual([]);
 | 
			
		||||
                    doFilter('.+');
 | 
			
		||||
                    expect(filteredObjectNames).toEqual([]);
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("exposes CSS classes from limits", function () {
 | 
			
		||||
                    var id = mockDomainObject.getId(),
 | 
			
		||||
                        testClass = "some-css-class",
 | 
			
		||||
                        mockLimitCapability =
 | 
			
		||||
                            jasmine.createSpyObj('limit', ['evaluate']);
 | 
			
		||||
 | 
			
		||||
                    mockDomainObject.getCapability.andCallFake(function (key) {
 | 
			
		||||
                        return key === 'limit' && mockLimitCapability;
 | 
			
		||||
                    });
 | 
			
		||||
                    mockLimitCapability.evaluate
 | 
			
		||||
                        .andReturn({ cssClass: testClass });
 | 
			
		||||
 | 
			
		||||
                    mockSubscription.getTelemetryObjects
 | 
			
		||||
                        .andReturn([mockDomainObject]);
 | 
			
		||||
 | 
			
		||||
                    fireFnWatches();
 | 
			
		||||
                    mockSubscriber.subscribe.mostRecentCall.args[1]();
 | 
			
		||||
 | 
			
		||||
                    expect(controller.classes()[id]).toEqual(testClass);
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("exposes a counter that changes with each update", function () {
 | 
			
		||||
                    var i, prior;
 | 
			
		||||
 | 
			
		||||
                    for (i = 0; i < 10; i += 1) {
 | 
			
		||||
                        prior = controller.counter();
 | 
			
		||||
                        expect(controller.counter()).toEqual(prior);
 | 
			
		||||
                        mockSubscriber.subscribe.mostRecentCall.args[1]();
 | 
			
		||||
                        expect(controller.counter()).not.toEqual(prior);
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
@@ -1,39 +0,0 @@
 | 
			
		||||
 | 
			
		||||
define(
 | 
			
		||||
    ["../src/MCTAutoflowTable"],
 | 
			
		||||
    function (MCTAutoflowTable) {
 | 
			
		||||
 | 
			
		||||
        describe("The mct-autoflow-table directive", function () {
 | 
			
		||||
            var mctAutoflowTable;
 | 
			
		||||
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                mctAutoflowTable = new MCTAutoflowTable();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            // Real functionality is contained/tested in the linker,
 | 
			
		||||
            // so just check to make sure we're exposing the directive
 | 
			
		||||
            // appropriately.
 | 
			
		||||
            it("is applicable at the element level", function () {
 | 
			
		||||
                expect(mctAutoflowTable.restrict).toEqual("E");
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("two-ways binds needed scope variables", function () {
 | 
			
		||||
                expect(mctAutoflowTable.scope).toEqual({
 | 
			
		||||
                    objects: "=",
 | 
			
		||||
                    values: "=",
 | 
			
		||||
                    rows: "=",
 | 
			
		||||
                    updated: "=",
 | 
			
		||||
                    classes: "=",
 | 
			
		||||
                    columnWidth: "=",
 | 
			
		||||
                    counter: "="
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("provides a link function", function () {
 | 
			
		||||
                expect(mctAutoflowTable.link).toEqual(jasmine.any(Function));
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
@@ -36,7 +36,9 @@ define(
 | 
			
		||||
         * @param {ActionContext} context the context for this action
 | 
			
		||||
         */
 | 
			
		||||
        function FollowTimerAction(timerService, context) {
 | 
			
		||||
            var domainObject = context.domainObject;
 | 
			
		||||
            var domainObject =
 | 
			
		||||
                context.domainObject &&
 | 
			
		||||
                context.domainObject.useCapability('adapter');
 | 
			
		||||
            this.perform =
 | 
			
		||||
                timerService.setTimer.bind(timerService, domainObject);
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -45,7 +45,7 @@ define(
 | 
			
		||||
 | 
			
		||||
        FollowIndicator.prototype.getText = function () {
 | 
			
		||||
            var timer = this.timerService.getTimer();
 | 
			
		||||
            return (timer) ? 'Following timer ' + timer.getModel().name : NO_TIMER;
 | 
			
		||||
            return timer ? ('Following timer ' + timer.name) : NO_TIMER;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        FollowIndicator.prototype.getDescription = function () {
 | 
			
		||||
 
 | 
			
		||||
@@ -32,6 +32,7 @@ define(['EventEmitter'], function (EventEmitter) {
 | 
			
		||||
    function TimerService(openmct) {
 | 
			
		||||
        EventEmitter.apply(this);
 | 
			
		||||
        this.time = openmct.time;
 | 
			
		||||
        this.objects = openmct.objects;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    TimerService.prototype = Object.create(EventEmitter.prototype);
 | 
			
		||||
@@ -44,6 +45,16 @@ define(['EventEmitter'], function (EventEmitter) {
 | 
			
		||||
    TimerService.prototype.setTimer = function (timer) {
 | 
			
		||||
        this.timer = timer;
 | 
			
		||||
        this.emit('change');
 | 
			
		||||
 | 
			
		||||
        if (this.stopObserving) {
 | 
			
		||||
            this.stopObserving();
 | 
			
		||||
            delete this.stopObserving;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (timer) {
 | 
			
		||||
            this.stopObserving =
 | 
			
		||||
                this.objects.observe(timer, '*', this.setTimer.bind(this));
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -73,16 +84,16 @@ define(['EventEmitter'], function (EventEmitter) {
 | 
			
		||||
        var clock = this.time.clock();
 | 
			
		||||
        var canConvert = this.hasTimer() &&
 | 
			
		||||
            !!clock &&
 | 
			
		||||
            this.timer.getModel().timerState !== 'stopped';
 | 
			
		||||
            this.timer.timerState !== 'stopped';
 | 
			
		||||
 | 
			
		||||
        if (!canConvert) {
 | 
			
		||||
            return undefined;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var now = clock.currentValue();
 | 
			
		||||
        var model = this.timer.getModel();
 | 
			
		||||
        var delta = model.timerState === 'paused' ? now - model.pausedTime : 0;
 | 
			
		||||
        var epoch = model.timestamp;
 | 
			
		||||
        var delta = this.timer.timerState === 'paused' ?
 | 
			
		||||
            now - this.timer.pausedTime : 0;
 | 
			
		||||
        var epoch = this.timer.timestamp;
 | 
			
		||||
 | 
			
		||||
        return timestamp - epoch - delta;
 | 
			
		||||
    };
 | 
			
		||||
 
 | 
			
		||||
@@ -29,12 +29,19 @@ define([
 | 
			
		||||
    describe("The Follow Timer action", function () {
 | 
			
		||||
        var testContext;
 | 
			
		||||
        var testModel;
 | 
			
		||||
        var testAdaptedObject;
 | 
			
		||||
 | 
			
		||||
        beforeEach(function () {
 | 
			
		||||
            testModel = {};
 | 
			
		||||
            testContext = { domainObject: { getModel: function () {
 | 
			
		||||
                return testModel;
 | 
			
		||||
            } } };
 | 
			
		||||
            testContext = { domainObject: jasmine.createSpyObj('domainObject', [
 | 
			
		||||
                'getModel',
 | 
			
		||||
                'useCapability'
 | 
			
		||||
            ]) };
 | 
			
		||||
            testAdaptedObject = { foo: 'bar' };
 | 
			
		||||
            testContext.domainObject.getModel.andReturn(testModel);
 | 
			
		||||
            testContext.domainObject.useCapability.andCallFake(function (c) {
 | 
			
		||||
                return c === 'adapter' && testAdaptedObject;
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("is applicable to timers", function () {
 | 
			
		||||
@@ -72,7 +79,7 @@ define([
 | 
			
		||||
 | 
			
		||||
                it("sets the active timer", function () {
 | 
			
		||||
                    expect(mockTimerService.setTimer)
 | 
			
		||||
                        .toHaveBeenCalledWith(testContext.domainObject);
 | 
			
		||||
                        .toHaveBeenCalledWith(testAdaptedObject);
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 
 | 
			
		||||
@@ -42,18 +42,15 @@ define(["../../src/indicators/FollowIndicator"], function (FollowIndicator) {
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        describe("when a timer is set", function () {
 | 
			
		||||
            var testModel;
 | 
			
		||||
            var mockDomainObject;
 | 
			
		||||
            var testObject;
 | 
			
		||||
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                testModel = { name: "some timer!" };
 | 
			
		||||
                mockDomainObject = jasmine.createSpyObj('timer', ['getModel']);
 | 
			
		||||
                mockDomainObject.getModel.andReturn(testModel);
 | 
			
		||||
                mockTimerService.getTimer.andReturn(mockDomainObject);
 | 
			
		||||
                testObject = { name: "some timer!" };
 | 
			
		||||
                mockTimerService.getTimer.andReturn(testObject);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("displays the timer's name", function () {
 | 
			
		||||
                expect(indicator.getText().indexOf(testModel.name))
 | 
			
		||||
                expect(indicator.getText().indexOf(testObject.name))
 | 
			
		||||
                    .not.toEqual(-1);
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 
 | 
			
		||||
@@ -30,7 +30,10 @@ define([
 | 
			
		||||
 | 
			
		||||
        beforeEach(function () {
 | 
			
		||||
            callback = jasmine.createSpy('callback');
 | 
			
		||||
            mockmct = { time: { clock: jasmine.createSpy('clock') } };
 | 
			
		||||
            mockmct = {
 | 
			
		||||
                time: { clock: jasmine.createSpy('clock') },
 | 
			
		||||
                objects: { observe: jasmine.createSpy('observe') }
 | 
			
		||||
            };
 | 
			
		||||
            timerService = new TimerService(mockmct);
 | 
			
		||||
            timerService.on('change', callback);
 | 
			
		||||
        });
 | 
			
		||||
@@ -58,6 +61,17 @@ define([
 | 
			
		||||
            it("reports the current timer", function () {
 | 
			
		||||
                expect(timerService.getTimer()).toBe(testTimer);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("observes changes to an object", function () {
 | 
			
		||||
                var newTimer = { name: "I am another timer." };
 | 
			
		||||
                expect(mockmct.objects.observe).toHaveBeenCalledWith(
 | 
			
		||||
                    testTimer,
 | 
			
		||||
                    '*',
 | 
			
		||||
                    jasmine.any(Function)
 | 
			
		||||
                );
 | 
			
		||||
                mockmct.objects.observe.mostRecentCall.args[2](newTimer);
 | 
			
		||||
                expect(timerService.getTimer()).toBe(newTimer);
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -255,6 +255,8 @@ define(
 | 
			
		||||
                if (this.nextDatum) {
 | 
			
		||||
                    this.updateValues(this.nextDatum);
 | 
			
		||||
                    delete this.nextDatum;
 | 
			
		||||
                } else {
 | 
			
		||||
                    this.updateValues(this.$scope.imageHistory[this.$scope.imageHistory.length - 1]);
 | 
			
		||||
                }
 | 
			
		||||
                this.autoScroll = true;
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
@@ -183,6 +183,17 @@ define(
 | 
			
		||||
                    expect(controller.getImageUrl()).toEqual(newUrl);
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("forwards large image view to latest image in history on un-pause", function () {
 | 
			
		||||
                    $scope.imageHistory = [
 | 
			
		||||
                        { utc: 1434600258122, url: 'some/url1', selected: false},
 | 
			
		||||
                        { utc: 1434600258123, url: 'some/url2', selected: false}
 | 
			
		||||
                    ];
 | 
			
		||||
                    controller.paused(true);
 | 
			
		||||
                    controller.paused(false);
 | 
			
		||||
 | 
			
		||||
                    expect(controller.getImageUrl()).toEqual(controller.getImageUrl($scope.imageHistory[1]));
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("subscribes to telemetry", function () {
 | 
			
		||||
                    expect(openmct.telemetry.subscribe).toHaveBeenCalledWith(
 | 
			
		||||
                        newDomainObject,
 | 
			
		||||
@@ -227,7 +238,7 @@ define(
 | 
			
		||||
                    expect(controller.updateHistory(mockDatum)).toBe(false);
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                describe("user clicks on imagery thumbnail", function () {
 | 
			
		||||
                describe("when user clicks on imagery thumbnail", function () {
 | 
			
		||||
                    var mockDatum = { utc: 1434600258123, url: 'some/url', selected: false};
 | 
			
		||||
 | 
			
		||||
                    it("pauses and adds selected class to imagery thumbnail", function () {
 | 
			
		||||
@@ -248,6 +259,7 @@ define(
 | 
			
		||||
                        expect(controller.getTime()).toEqual(controller.timeFormat.format(mockDatum.utc));
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("initially shows an empty string for date/time", function () {
 | 
			
		||||
 
 | 
			
		||||
@@ -260,7 +260,9 @@ define([
 | 
			
		||||
                    "key": "LayoutController",
 | 
			
		||||
                    "implementation": LayoutController,
 | 
			
		||||
                    "depends": [
 | 
			
		||||
                        "$scope"
 | 
			
		||||
                        "$scope",
 | 
			
		||||
                        "$element",
 | 
			
		||||
                        "openmct"
 | 
			
		||||
                    ]
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
@@ -270,7 +272,8 @@ define([
 | 
			
		||||
                        "$scope",
 | 
			
		||||
                        "$q",
 | 
			
		||||
                        "dialogService",
 | 
			
		||||
                        "openmct"
 | 
			
		||||
                        "openmct",
 | 
			
		||||
                        "$element"
 | 
			
		||||
                    ]
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,7 @@
 | 
			
		||||
     ng-controller="FixedController as controller">
 | 
			
		||||
 | 
			
		||||
    <!-- Background grid -->
 | 
			
		||||
    <div class="l-grid-holder" ng-click="controller.clearSelection()">
 | 
			
		||||
    <div class="l-grid-holder" ng-click="controller.bypassSelection($event)">
 | 
			
		||||
        <div class="l-grid l-grid-x"
 | 
			
		||||
             ng-if="!controller.getGridSize()[0] < 3"
 | 
			
		||||
             ng-style="{ 'background-size': controller.getGridSize() [0] + 'px 100%' }"></div>
 | 
			
		||||
@@ -35,33 +35,28 @@
 | 
			
		||||
    <!-- Fixed position elements -->
 | 
			
		||||
    <div ng-repeat="element in controller.getElements()"
 | 
			
		||||
         class="l-fixed-position-item s-selectable s-moveable s-hover-border"
 | 
			
		||||
         ng-class="{
 | 
			
		||||
            's-not-selected': controller.selected() && !controller.selected(element),
 | 
			
		||||
            's-selected': controller.selected(element)
 | 
			
		||||
         }"
 | 
			
		||||
         ng-style="element.style"
 | 
			
		||||
         ng-click="controller.select(element)">
 | 
			
		||||
         mct-selectable="controller.getContext(element)"
 | 
			
		||||
         mct-init-select="controller.shouldSelect(element)">
 | 
			
		||||
        <mct-include key="element.template"
 | 
			
		||||
                     parameters="{ gridSize: controller.getGridSize() }"
 | 
			
		||||
                     ng-model="element">
 | 
			
		||||
        </mct-include>
 | 
			
		||||
        </mct-include>       
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- Selection highlight, handles -->
 | 
			
		||||
    <span class="s-selected s-moveable" ng-if="controller.selected()">
 | 
			
		||||
    <span class="s-selected s-moveable" ng-if="controller.isElementSelected()">
 | 
			
		||||
        <div class="l-fixed-position-item t-edit-handle-holder"
 | 
			
		||||
             mct-drag-down="controller.moveHandle().startDrag(controller.selected())"
 | 
			
		||||
             mct-drag-down="controller.moveHandle().startDrag()"
 | 
			
		||||
             mct-drag="controller.moveHandle().continueDrag(delta)"
 | 
			
		||||
             mct-drag-up="controller.moveHandle().endDrag()"
 | 
			
		||||
             ng-style="controller.selected().style">
 | 
			
		||||
             mct-drag-up="controller.endDrag()"
 | 
			
		||||
             ng-style="controller.getSelectedElementStyle()">
 | 
			
		||||
        </div>
 | 
			
		||||
        <div ng-repeat="handle in controller.handles()"
 | 
			
		||||
             class="l-fixed-position-item-handle edit-corner"
 | 
			
		||||
             ng-style="handle.style()"
 | 
			
		||||
             mct-drag-down="handle.startDrag()"
 | 
			
		||||
             mct-drag="handle.continueDrag(delta)"
 | 
			
		||||
             mct-drag-up="handle.endDrag()">
 | 
			
		||||
             mct-drag-up="controller.endDrag(handle)">
 | 
			
		||||
        </div>
 | 
			
		||||
    </span>
 | 
			
		||||
 | 
			
		||||
</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -22,10 +22,12 @@
 | 
			
		||||
 | 
			
		||||
<div class="abs l-layout"
 | 
			
		||||
     ng-controller="LayoutController as controller"
 | 
			
		||||
     ng-click="controller.clearSelection()">
 | 
			
		||||
     ng-click="controller.bypassSelection($event)">
 | 
			
		||||
 | 
			
		||||
    <!-- Background grid -->
 | 
			
		||||
    <div class="l-grid-holder" ng-click="controller.clearSelection()">
 | 
			
		||||
    <div class="l-grid-holder"
 | 
			
		||||
         ng-show="!controller.drilledIn"
 | 
			
		||||
         ng-click="controller.bypassSelection($event)">
 | 
			
		||||
        <div class="l-grid l-grid-x"
 | 
			
		||||
             ng-if="!controller.getGridSize()[0] < 3"
 | 
			
		||||
             ng-style="{ 'background-size': controller.getGridSize() [0] + 'px 100%' }"></div>
 | 
			
		||||
@@ -34,10 +36,13 @@
 | 
			
		||||
             ng-style="{ 'background-size': '100% ' + controller.getGridSize() [1] + 'px' }"></div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class='abs frame t-frame-outer child-frame panel s-selectable s-moveable s-hover-border'
 | 
			
		||||
         ng-class="{ 'no-frame': !controller.hasFrame(childObject), 's-selected':controller.selected(childObject) }"
 | 
			
		||||
    <div class="abs frame t-frame-outer child-frame panel s-selectable s-moveable s-hover-border t-object-type-{{ childObject.getModel().type }}"
 | 
			
		||||
         data-layout-id="{{childObject.getId() + '-' + $id}}"
 | 
			
		||||
         ng-class="{ 'no-frame': !controller.hasFrame(childObject), 's-drilled-in': controller.isDrilledIn(childObject) }"
 | 
			
		||||
         ng-repeat="childObject in composition"
 | 
			
		||||
         ng-click="controller.select($event, childObject.getId())"
 | 
			
		||||
         ng-init="controller.selectIfNew(childObject.getId() + '-' + $id, childObject)"
 | 
			
		||||
         mct-selectable="controller.getContext(childObject, true)"
 | 
			
		||||
         ng-dblclick="controller.drill($event, childObject)"
 | 
			
		||||
         ng-style="controller.getFrameStyle(childObject.getId())">
 | 
			
		||||
 | 
			
		||||
        <mct-representation key="'frame'"
 | 
			
		||||
@@ -45,7 +50,7 @@
 | 
			
		||||
                            mct-object="childObject">
 | 
			
		||||
        </mct-representation>
 | 
			
		||||
        <!-- Drag handles -->
 | 
			
		||||
        <span class="abs t-edit-handle-holder s-hover-border" ng-if="controller.selected(childObject)">
 | 
			
		||||
        <span class="abs t-edit-handle-holder" ng-if="controller.selected(childObject) && !controller.isDrilledIn(childObject)">
 | 
			
		||||
            <span class="edit-handle edit-move"
 | 
			
		||||
                  mct-drag-down="controller.startDrag(childObject.getId(), [1,1], [0,0])"
 | 
			
		||||
                  mct-drag="controller.continueDrag(delta)"
 | 
			
		||||
@@ -73,7 +78,6 @@
 | 
			
		||||
                  mct-drag-up="controller.endDrag()">
 | 
			
		||||
            </span>
 | 
			
		||||
        </span>
 | 
			
		||||
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -47,7 +47,7 @@ define(
 | 
			
		||||
         * @constructor
 | 
			
		||||
         * @param {Scope} $scope the controller's Angular scope
 | 
			
		||||
         */
 | 
			
		||||
        function FixedController($scope, $q, dialogService, openmct) {
 | 
			
		||||
        function FixedController($scope, $q, dialogService, openmct, $element) {
 | 
			
		||||
            this.names = {}; // Cache names by ID
 | 
			
		||||
            this.values = {}; // Cache values by ID
 | 
			
		||||
            this.elementProxiesById = {};
 | 
			
		||||
@@ -55,9 +55,11 @@ define(
 | 
			
		||||
            this.telemetryObjects = [];
 | 
			
		||||
            this.subscriptions = [];
 | 
			
		||||
            this.openmct = openmct;
 | 
			
		||||
            this.$element = $element;
 | 
			
		||||
            this.$scope = $scope;
 | 
			
		||||
 | 
			
		||||
            this.gridSize = $scope.domainObject && $scope.domainObject.getModel().layoutGrid;
 | 
			
		||||
            this.fixedViewSelectable = false;
 | 
			
		||||
 | 
			
		||||
            var self = this;
 | 
			
		||||
            [
 | 
			
		||||
@@ -87,9 +89,8 @@ define(
 | 
			
		||||
 | 
			
		||||
            // Update the style for a selected element
 | 
			
		||||
            function updateSelectionStyle() {
 | 
			
		||||
                var element = self.selection && self.selection.get();
 | 
			
		||||
                if (element) {
 | 
			
		||||
                    element.style = convertPosition(element);
 | 
			
		||||
                if (self.selectedElementProxy) {
 | 
			
		||||
                    self.selectedElementProxy.style = convertPosition(self.selectedElementProxy);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@@ -136,25 +137,19 @@ define(
 | 
			
		||||
 | 
			
		||||
            // Decorate elements in the current configuration
 | 
			
		||||
            function refreshElements() {
 | 
			
		||||
                // Cache selection; we are instantiating new proxies
 | 
			
		||||
                // so we may want to restore this.
 | 
			
		||||
                var selected = self.selection && self.selection.get(),
 | 
			
		||||
                    elements = (($scope.configuration || {}).elements || []),
 | 
			
		||||
                    index = -1; // Start with a 'not-found' value
 | 
			
		||||
 | 
			
		||||
                // Find the selection in the new array
 | 
			
		||||
                if (selected !== undefined) {
 | 
			
		||||
                    index = elements.indexOf(selected.element);
 | 
			
		||||
                }
 | 
			
		||||
                var elements = (($scope.configuration || {}).elements || []);
 | 
			
		||||
 | 
			
		||||
                // Create the new proxies...
 | 
			
		||||
                self.elementProxies = elements.map(makeProxyElement);
 | 
			
		||||
 | 
			
		||||
                // Clear old selection, and restore if appropriate
 | 
			
		||||
                if (self.selection) {
 | 
			
		||||
                    self.selection.deselect();
 | 
			
		||||
                    if (index > -1) {
 | 
			
		||||
                        self.select(self.elementProxies[index]);
 | 
			
		||||
                // If selection is not in array, select parent.
 | 
			
		||||
                // Otherwise, set the element to select after refresh.
 | 
			
		||||
                if (self.selectedElementProxy) {
 | 
			
		||||
                    var index = elements.indexOf(self.selectedElementProxy.element);
 | 
			
		||||
                    if (index === -1) {
 | 
			
		||||
                        self.$element[0].click();
 | 
			
		||||
                    } else if (!self.elementToSelectAfterRefresh) {
 | 
			
		||||
                        self.elementToSelectAfterRefresh = self.elementProxies[index].element;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
@@ -224,12 +219,12 @@ define(
 | 
			
		||||
                    $scope.configuration.elements || [];
 | 
			
		||||
                // Store the position of this element.
 | 
			
		||||
                $scope.configuration.elements.push(element);
 | 
			
		||||
 | 
			
		||||
                self.elementToSelectAfterRefresh = element;
 | 
			
		||||
 | 
			
		||||
                // Refresh displayed elements
 | 
			
		||||
                refreshElements();
 | 
			
		||||
                // Select the newly-added element
 | 
			
		||||
                self.select(
 | 
			
		||||
                    self.elementProxies[self.elementProxies.length - 1]
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                // Mark change as persistable
 | 
			
		||||
                if ($scope.commit) {
 | 
			
		||||
                    $scope.commit("Dropped an element.");
 | 
			
		||||
@@ -263,21 +258,36 @@ define(
 | 
			
		||||
                self.getTelemetry($scope.domainObject);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Sets the selectable object in response to the selection change event.
 | 
			
		||||
            function setSelection(selectable) {
 | 
			
		||||
                var selection = selectable[0];
 | 
			
		||||
 | 
			
		||||
                if (!selection) {
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (selection.context.elementProxy) {
 | 
			
		||||
                    self.selectedElementProxy = selection.context.elementProxy;
 | 
			
		||||
                    self.mvHandle = self.generateDragHandle(self.selectedElementProxy);
 | 
			
		||||
                    self.resizeHandles = self.generateDragHandles(self.selectedElementProxy);
 | 
			
		||||
                } else {
 | 
			
		||||
                    // Make fixed view selectable if it's not already.
 | 
			
		||||
                    if (!self.fixedViewSelectable && selectable.length === 1) {
 | 
			
		||||
                        self.fixedViewSelectable = true;
 | 
			
		||||
                        selection.context.viewProxy = new FixedProxy(addElement, $q, dialogService);
 | 
			
		||||
                        self.openmct.selection.select(selection);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    self.resizeHandles = [];
 | 
			
		||||
                    self.mvHandle = undefined;
 | 
			
		||||
                    self.selectedElementProxy = undefined;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.elementProxies = [];
 | 
			
		||||
            this.generateDragHandle = generateDragHandle;
 | 
			
		||||
            this.generateDragHandles = generateDragHandles;
 | 
			
		||||
 | 
			
		||||
            // Track current selection state
 | 
			
		||||
            $scope.$watch("selection", function (selection) {
 | 
			
		||||
                this.selection = selection;
 | 
			
		||||
 | 
			
		||||
                // Expose the view's selection proxy
 | 
			
		||||
                if (this.selection) {
 | 
			
		||||
                    this.selection.proxy(
 | 
			
		||||
                        new FixedProxy(addElement, $q, dialogService)
 | 
			
		||||
                    );
 | 
			
		||||
                }
 | 
			
		||||
            }.bind(this));
 | 
			
		||||
            this.updateSelectionStyle = updateSelectionStyle;
 | 
			
		||||
 | 
			
		||||
            // Detect changes to grid size
 | 
			
		||||
            $scope.$watch("model.layoutGrid", updateElementPositions);
 | 
			
		||||
@@ -298,10 +308,13 @@ define(
 | 
			
		||||
            $scope.$on("$destroy", function () {
 | 
			
		||||
                self.unsubscribe();
 | 
			
		||||
                self.openmct.time.off("bounds", updateDisplayBounds);
 | 
			
		||||
                self.openmct.selection.off("change", setSelection);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            // Respond to external bounds changes
 | 
			
		||||
            this.openmct.time.on("bounds", updateDisplayBounds);
 | 
			
		||||
            this.openmct.selection.on('change', setSelection);
 | 
			
		||||
            this.$element.on('click', this.bypassSelection.bind(this));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
@@ -360,22 +373,46 @@ define(
 | 
			
		||||
         */
 | 
			
		||||
        FixedController.prototype.updateView = function (telemetryObject, datum) {
 | 
			
		||||
            var metadata = this.openmct.telemetry.getMetadata(telemetryObject);
 | 
			
		||||
            var rangeMetadata = metadata.valuesForHints(['range'])[0];
 | 
			
		||||
            var rangeKey = rangeMetadata.source || rangeMetadata.key;
 | 
			
		||||
            var valueMetadata = metadata.value(rangeKey);
 | 
			
		||||
            var valueMetadata = this.chooseValueMetadataToDisplay(metadata);
 | 
			
		||||
            var formattedTelemetryValue = this.getFormattedTelemetryValueForKey(valueMetadata, datum);
 | 
			
		||||
            var limitEvaluator = this.openmct.telemetry.limitEvaluator(telemetryObject);
 | 
			
		||||
            var formatter = this.openmct.telemetry.getValueFormatter(valueMetadata);
 | 
			
		||||
            var value = datum[valueMetadata.key];
 | 
			
		||||
            var alarm = limitEvaluator && limitEvaluator.evaluate(datum, rangeKey);
 | 
			
		||||
            var alarm = limitEvaluator && limitEvaluator.evaluate(datum, valueMetadata);
 | 
			
		||||
 | 
			
		||||
            this.setDisplayedValue(
 | 
			
		||||
                telemetryObject,
 | 
			
		||||
                formatter.format(value),
 | 
			
		||||
                formattedTelemetryValue,
 | 
			
		||||
                alarm && alarm.cssClass
 | 
			
		||||
            );
 | 
			
		||||
            this.digest();
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * @private
 | 
			
		||||
         */
 | 
			
		||||
        FixedController.prototype.getFormattedTelemetryValueForKey = function (valueMetadata, datum) {
 | 
			
		||||
            var formatter = this.openmct.telemetry.getValueFormatter(valueMetadata);
 | 
			
		||||
 | 
			
		||||
            return formatter.format(datum);
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * @private
 | 
			
		||||
         */
 | 
			
		||||
        FixedController.prototype.chooseValueMetadataToDisplay = function (metadata) {
 | 
			
		||||
            // If there is a range value, show that preferentially
 | 
			
		||||
            var valueMetadata = metadata.valuesForHints(['range'])[0];
 | 
			
		||||
 | 
			
		||||
            // If no range is defined, default to the highest priority non time-domain data.
 | 
			
		||||
            if (valueMetadata === undefined) {
 | 
			
		||||
                var valuesOrderedByPriority = metadata.values();
 | 
			
		||||
                valueMetadata = valuesOrderedByPriority.filter(function (values) {
 | 
			
		||||
                    return !(values.hints.domain);
 | 
			
		||||
                })[0];
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return valueMetadata;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Request the last historical data point for the given domain objects
 | 
			
		||||
         * @param {object[]} objects
 | 
			
		||||
@@ -388,7 +425,9 @@ define(
 | 
			
		||||
            objects.forEach(function (object) {
 | 
			
		||||
                self.openmct.telemetry.request(object, {start: bounds.start, end: bounds.end, size: 1})
 | 
			
		||||
                    .then(function (data) {
 | 
			
		||||
                        self.updateView(object, data[data.length - 1]);
 | 
			
		||||
                        if (data.length > 0) {
 | 
			
		||||
                            self.updateView(object, data[data.length - 1]);
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
            });
 | 
			
		||||
            return objects;
 | 
			
		||||
@@ -465,38 +504,56 @@ define(
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Check if the element is currently selected, or (if no
 | 
			
		||||
         * argument is supplied) get the currently selected element.
 | 
			
		||||
         * @returns {boolean} true if selected
 | 
			
		||||
         * Checks if the element should be selected or not.
 | 
			
		||||
         *
 | 
			
		||||
         * @param elementProxy the element to check
 | 
			
		||||
         * @returns {boolean} true if the element should be selected.
 | 
			
		||||
         */
 | 
			
		||||
        FixedController.prototype.selected = function (element) {
 | 
			
		||||
            var selection = this.selection;
 | 
			
		||||
            return selection && ((arguments.length > 0) ?
 | 
			
		||||
                    selection.selected(element) : selection.get());
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Set the active user selection in this view.
 | 
			
		||||
         * @param element the element to select
 | 
			
		||||
         */
 | 
			
		||||
        FixedController.prototype.select = function select(element) {
 | 
			
		||||
            if (this.selection) {
 | 
			
		||||
                // Update selection...
 | 
			
		||||
                this.selection.select(element);
 | 
			
		||||
                // ...as well as move, resize handles
 | 
			
		||||
                this.mvHandle = this.generateDragHandle(element);
 | 
			
		||||
                this.resizeHandles = this.generateDragHandles(element);
 | 
			
		||||
        FixedController.prototype.shouldSelect = function (elementProxy) {
 | 
			
		||||
            if (elementProxy.element === this.elementToSelectAfterRefresh) {
 | 
			
		||||
                delete this.elementToSelectAfterRefresh;
 | 
			
		||||
                return true;
 | 
			
		||||
            } else {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Clear the current user selection.
 | 
			
		||||
         * Checks if an element is currently selected.
 | 
			
		||||
         *
 | 
			
		||||
         * @returns {boolean} true if an element is selected.
 | 
			
		||||
         */
 | 
			
		||||
        FixedController.prototype.clearSelection = function () {
 | 
			
		||||
            if (this.selection) {
 | 
			
		||||
                this.selection.deselect();
 | 
			
		||||
                this.resizeHandles = [];
 | 
			
		||||
                this.mvHandle = undefined;
 | 
			
		||||
        FixedController.prototype.isElementSelected = function () {
 | 
			
		||||
            return (this.selectedElementProxy) ? true : false;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Gets the style for the selected element.
 | 
			
		||||
         *
 | 
			
		||||
         * @returns {string} element style
 | 
			
		||||
         */
 | 
			
		||||
        FixedController.prototype.getSelectedElementStyle = function () {
 | 
			
		||||
            return (this.selectedElementProxy) ? this.selectedElementProxy.style : undefined;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Gets the selected element.
 | 
			
		||||
         *
 | 
			
		||||
         * @returns the selected element
 | 
			
		||||
         */
 | 
			
		||||
        FixedController.prototype.getSelectedElement = function () {
 | 
			
		||||
            return this.selectedElementProxy;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Prevents the event from bubbling up if drag is in progress.
 | 
			
		||||
         */
 | 
			
		||||
        FixedController.prototype.bypassSelection = function ($event) {
 | 
			
		||||
            if (this.dragInProgress) {
 | 
			
		||||
                if ($event) {
 | 
			
		||||
                    $event.stopPropagation();
 | 
			
		||||
                }
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
@@ -517,6 +574,38 @@ define(
 | 
			
		||||
            return this.mvHandle;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Gets the selection context.
 | 
			
		||||
         *
 | 
			
		||||
         * @param elementProxy the element proxy
 | 
			
		||||
         * @returns {object} the context object which includes elementProxy and toolbar
 | 
			
		||||
         */
 | 
			
		||||
        FixedController.prototype.getContext = function (elementProxy) {
 | 
			
		||||
            return {
 | 
			
		||||
                elementProxy: elementProxy,
 | 
			
		||||
                toolbar: elementProxy
 | 
			
		||||
            };
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * End drag.
 | 
			
		||||
         *
 | 
			
		||||
         * @param handle the resize handle
 | 
			
		||||
         */
 | 
			
		||||
        FixedController.prototype.endDrag = function (handle) {
 | 
			
		||||
            this.dragInProgress = true;
 | 
			
		||||
 | 
			
		||||
            setTimeout(function () {
 | 
			
		||||
                this.dragInProgress = false;
 | 
			
		||||
            }.bind(this), 0);
 | 
			
		||||
 | 
			
		||||
            if (handle) {
 | 
			
		||||
                handle.endDrag();
 | 
			
		||||
            } else {
 | 
			
		||||
                this.moveHandle().endDrag();
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return FixedController;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 
 | 
			
		||||
@@ -65,7 +65,7 @@ define(
 | 
			
		||||
         * Start a drag gesture. This should be called when a drag
 | 
			
		||||
         * begins to track initial state.
 | 
			
		||||
         */
 | 
			
		||||
        FixedDragHandle.prototype.startDrag = function startDrag() {
 | 
			
		||||
        FixedDragHandle.prototype.startDrag = function () {
 | 
			
		||||
            // Cache initial x/y positions
 | 
			
		||||
            this.dragging = {
 | 
			
		||||
                x: this.elementHandle.x(),
 | 
			
		||||
 
 | 
			
		||||
@@ -26,8 +26,14 @@
 | 
			
		||||
 * @namespace platform/features/layout
 | 
			
		||||
 */
 | 
			
		||||
define(
 | 
			
		||||
    ['./LayoutDrag'],
 | 
			
		||||
    function (LayoutDrag) {
 | 
			
		||||
    [
 | 
			
		||||
        'zepto',
 | 
			
		||||
        './LayoutDrag'
 | 
			
		||||
    ],
 | 
			
		||||
    function (
 | 
			
		||||
        $,
 | 
			
		||||
        LayoutDrag
 | 
			
		||||
    ) {
 | 
			
		||||
 | 
			
		||||
        var DEFAULT_DIMENSIONS = [12, 8],
 | 
			
		||||
            DEFAULT_GRID_SIZE = [32, 32],
 | 
			
		||||
@@ -46,10 +52,12 @@ define(
 | 
			
		||||
         * @constructor
 | 
			
		||||
         * @param {Scope} $scope the controller's Angular scope
 | 
			
		||||
         */
 | 
			
		||||
        function LayoutController($scope) {
 | 
			
		||||
        function LayoutController($scope, $element, openmct) {
 | 
			
		||||
            var self = this,
 | 
			
		||||
                callbackCount = 0;
 | 
			
		||||
 | 
			
		||||
            this.$element = $element;
 | 
			
		||||
 | 
			
		||||
            // Update grid size when it changed
 | 
			
		||||
            function updateGridSize(layoutGrid) {
 | 
			
		||||
                var oldSize = self.gridSize;
 | 
			
		||||
@@ -119,10 +127,11 @@ define(
 | 
			
		||||
                        self.layoutPanels(ids);
 | 
			
		||||
                        self.setFrames(ids);
 | 
			
		||||
 | 
			
		||||
                        // If there is a newly-dropped object, select it.
 | 
			
		||||
                        if (self.droppedIdToSelectAfterRefresh) {
 | 
			
		||||
                            self.select(null, self.droppedIdToSelectAfterRefresh);
 | 
			
		||||
                            delete self.droppedIdToSelectAfterRefresh;
 | 
			
		||||
                        if (self.selectedId &&
 | 
			
		||||
                            self.selectedId !== $scope.domainObject.getId() &&
 | 
			
		||||
                            composition.indexOf(self.selectedId) === -1) {
 | 
			
		||||
                            // Click triggers selection of layout parent.
 | 
			
		||||
                            self.$element[0].click();
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
@@ -154,22 +163,39 @@ define(
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            // Sets the selectable object in response to the selection change event.
 | 
			
		||||
            function setSelection(selectable) {
 | 
			
		||||
                var selection = selectable[0];
 | 
			
		||||
 | 
			
		||||
                if (!selection) {
 | 
			
		||||
                    delete self.selectedId;
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                self.selectedId = selection.context.oldItem.getId();
 | 
			
		||||
                self.drilledIn = undefined;
 | 
			
		||||
                self.selectable = selectable;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.positions = {};
 | 
			
		||||
            this.rawPositions = {};
 | 
			
		||||
            this.gridSize = DEFAULT_GRID_SIZE;
 | 
			
		||||
            this.$scope = $scope;
 | 
			
		||||
            this.drilledIn = undefined;
 | 
			
		||||
            this.openmct = openmct;
 | 
			
		||||
 | 
			
		||||
            // Watch for changes to the grid size in the model
 | 
			
		||||
            $scope.$watch("model.layoutGrid", updateGridSize);
 | 
			
		||||
 | 
			
		||||
            $scope.$watch("selection", function (selection) {
 | 
			
		||||
                this.selection = selection;
 | 
			
		||||
            }.bind(this));
 | 
			
		||||
 | 
			
		||||
            // Update composed objects on screen, and position panes
 | 
			
		||||
            $scope.$watchCollection("model.composition", refreshComposition);
 | 
			
		||||
 | 
			
		||||
            // Position panes where they are dropped
 | 
			
		||||
            openmct.selection.on('change', setSelection);
 | 
			
		||||
 | 
			
		||||
            $scope.$on("$destroy", function () {
 | 
			
		||||
                openmct.selection.off("change", setSelection);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            $scope.$on("mctDrop", handleDrop);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -351,37 +377,14 @@ define(
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Check if the object is currently selected.
 | 
			
		||||
         * Checks if the object is currently selected.
 | 
			
		||||
         *
 | 
			
		||||
         * @param {string} obj the object to check for selection
 | 
			
		||||
         * @returns {boolean} true if selected, otherwise false
 | 
			
		||||
         */
 | 
			
		||||
        LayoutController.prototype.selected = function (obj) {
 | 
			
		||||
            return !!this.selectedId && this.selectedId === obj.getId();
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Set the active user selection in this view.
 | 
			
		||||
         *
 | 
			
		||||
         * @param event the mouse event
 | 
			
		||||
         * @param {string} id the object id
 | 
			
		||||
         */
 | 
			
		||||
        LayoutController.prototype.select = function (event, id) {
 | 
			
		||||
            if (event) {
 | 
			
		||||
                event.stopPropagation();
 | 
			
		||||
                if (this.selection) {
 | 
			
		||||
                    event.preventDefault();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.selectedId = id;
 | 
			
		||||
 | 
			
		||||
            var selectedObj = {};
 | 
			
		||||
            selectedObj[this.frames[id] ? 'hideFrame' : 'showFrame'] = this.toggleFrame.bind(this, id);
 | 
			
		||||
 | 
			
		||||
            if (this.selection) {
 | 
			
		||||
                this.selection.select(selectedObj);
 | 
			
		||||
            }
 | 
			
		||||
            var sobj = this.openmct.selection.get()[0];
 | 
			
		||||
            return (sobj && sobj.context.oldItem.getId() === obj.getId()) ? true : false;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
@@ -390,7 +393,7 @@ define(
 | 
			
		||||
         * @param {string} id the object id
 | 
			
		||||
         * @private
 | 
			
		||||
         */
 | 
			
		||||
        LayoutController.prototype.toggleFrame = function (id) {
 | 
			
		||||
        LayoutController.prototype.toggleFrame = function (id, domainObject) {
 | 
			
		||||
            var configuration = this.$scope.configuration;
 | 
			
		||||
 | 
			
		||||
            if (!configuration.panels[id]) {
 | 
			
		||||
@@ -398,21 +401,75 @@ define(
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.frames[id] = configuration.panels[id].hasFrame = !this.frames[id];
 | 
			
		||||
            this.select(undefined, id); // reselect so toolbar updates
 | 
			
		||||
 | 
			
		||||
            var selection = this.openmct.selection.get();
 | 
			
		||||
            selection[0].context.toolbar = this.getToolbar(id, domainObject);
 | 
			
		||||
            this.openmct.selection.select(selection);  // reselect so toolbar updates
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Clear the current user selection.
 | 
			
		||||
         * Gets the toolbar object for the given domain object.
 | 
			
		||||
         *
 | 
			
		||||
         * @param id the domain object id
 | 
			
		||||
         * @param domainObject the domain object
 | 
			
		||||
         * @returns {object}
 | 
			
		||||
         * @private
 | 
			
		||||
         */
 | 
			
		||||
        LayoutController.prototype.clearSelection = function () {
 | 
			
		||||
        LayoutController.prototype.getToolbar = function (id, domainObject) {
 | 
			
		||||
            var toolbarObj = {};
 | 
			
		||||
            toolbarObj[this.frames[id] ? 'hideFrame' : 'showFrame'] = this.toggleFrame.bind(this, id, domainObject);
 | 
			
		||||
            return toolbarObj;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Bypasses selection if drag is in progress.
 | 
			
		||||
         *
 | 
			
		||||
         * @param event the angular event object
 | 
			
		||||
         */
 | 
			
		||||
        LayoutController.prototype.bypassSelection = function (event) {
 | 
			
		||||
            if (this.dragInProgress) {
 | 
			
		||||
                if (event) {
 | 
			
		||||
                    event.stopPropagation();
 | 
			
		||||
                }
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Checks if the domain object is drilled in.
 | 
			
		||||
         *
 | 
			
		||||
         * @param domainObject the domain object
 | 
			
		||||
         * @return true if the object is drilled in, false otherwise
 | 
			
		||||
         */
 | 
			
		||||
        LayoutController.prototype.isDrilledIn = function (domainObject) {
 | 
			
		||||
            return this.drilledIn === domainObject.getId();
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Puts the given object in the drilled-in mode.
 | 
			
		||||
         *
 | 
			
		||||
         * @param event the angular event object
 | 
			
		||||
         * @param domainObject the domain object
 | 
			
		||||
         */
 | 
			
		||||
        LayoutController.prototype.drill = function (event, domainObject) {
 | 
			
		||||
            if (event) {
 | 
			
		||||
                event.stopPropagation();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!domainObject.getCapability('editor').inEditContext()) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (this.selection) {
 | 
			
		||||
                this.selection.deselect();
 | 
			
		||||
                delete this.selectedId;
 | 
			
		||||
            if (!domainObject.hasCapability('composition')) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Disable since fixed position doesn't use the selection API yet
 | 
			
		||||
            if (domainObject.getModel().type === 'telemetry.fixed') {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.drilledIn = domainObject.getId();
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
@@ -434,6 +491,36 @@ define(
 | 
			
		||||
            return this.gridSize;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Gets the selection context.
 | 
			
		||||
         *
 | 
			
		||||
         * @param domainObject the domain object
 | 
			
		||||
         * @returns {object} the context object which includes
 | 
			
		||||
         *                  item, oldItem and toolbar
 | 
			
		||||
         */
 | 
			
		||||
        LayoutController.prototype.getContext = function (domainObject, toolbar) {
 | 
			
		||||
            return {
 | 
			
		||||
                item: domainObject.useCapability('adapter'),
 | 
			
		||||
                oldItem: domainObject,
 | 
			
		||||
                toolbar: toolbar ? this.getToolbar(domainObject.getId(), domainObject) : undefined
 | 
			
		||||
            };
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Selects a newly-dropped object.
 | 
			
		||||
         *
 | 
			
		||||
         * @param classSelector the css class selector
 | 
			
		||||
         * @param domainObject the domain object
 | 
			
		||||
         */
 | 
			
		||||
        LayoutController.prototype.selectIfNew = function (selector, domainObject) {
 | 
			
		||||
            if (domainObject.getId() === this.droppedIdToSelectAfterRefresh) {
 | 
			
		||||
                setTimeout(function () {
 | 
			
		||||
                    $('[data-layout-id="' + selector + '"]')[0].click();
 | 
			
		||||
                    delete this.droppedIdToSelectAfterRefresh;
 | 
			
		||||
                }.bind(this), 0);
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return LayoutController;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 
 | 
			
		||||
@@ -55,8 +55,8 @@ define(
 | 
			
		||||
         * @param element the fixed position element, as stored in its
 | 
			
		||||
         *        configuration
 | 
			
		||||
         * @param index the element's index within its array
 | 
			
		||||
         * @param {number[]} gridSize the current layout grid size in [x,y] from
 | 
			
		||||
         * @param {Array} elements the full array of elements
 | 
			
		||||
         * @param {number[]} gridSize the current layout grid size in [x,y] from
 | 
			
		||||
         */
 | 
			
		||||
        function ElementProxy(element, index, elements, gridSize) {
 | 
			
		||||
            /**
 | 
			
		||||
 
 | 
			
		||||
@@ -21,8 +21,14 @@
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
define(
 | 
			
		||||
    ["../src/FixedController"],
 | 
			
		||||
    function (FixedController) {
 | 
			
		||||
    [
 | 
			
		||||
        "../src/FixedController",
 | 
			
		||||
        "zepto"
 | 
			
		||||
    ],
 | 
			
		||||
    function (
 | 
			
		||||
        FixedController,
 | 
			
		||||
        $
 | 
			
		||||
    ) {
 | 
			
		||||
 | 
			
		||||
        describe("The Fixed Position controller", function () {
 | 
			
		||||
            var mockScope,
 | 
			
		||||
@@ -46,6 +52,9 @@ define(
 | 
			
		||||
                mockMetadata,
 | 
			
		||||
                mockTimeSystem,
 | 
			
		||||
                mockLimitEvaluator,
 | 
			
		||||
                mockSelection,
 | 
			
		||||
                $element = [],
 | 
			
		||||
                selectable = [],
 | 
			
		||||
                controller;
 | 
			
		||||
 | 
			
		||||
            // Utility function; find a watch for a given expression
 | 
			
		||||
@@ -97,8 +106,8 @@ define(
 | 
			
		||||
                    'telemetryFormatter',
 | 
			
		||||
                    ['format']
 | 
			
		||||
                );
 | 
			
		||||
                mockFormatter.format.andCallFake(function (value) {
 | 
			
		||||
                    return "Formatted " + value;
 | 
			
		||||
                mockFormatter.format.andCallFake(function (valueMetadata) {
 | 
			
		||||
                    return "Formatted " + valueMetadata.value;
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                mockDomainObject = jasmine.createSpyObj(
 | 
			
		||||
@@ -178,23 +187,36 @@ define(
 | 
			
		||||
                    Promise.resolve(mockChildren)
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                mockScope.model = testModel;
 | 
			
		||||
                mockScope.configuration = testConfiguration;
 | 
			
		||||
                mockScope.selection = jasmine.createSpyObj(
 | 
			
		||||
                    'selection',
 | 
			
		||||
                    ['select', 'get', 'selected', 'deselect', 'proxy']
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                selectable[0] = {
 | 
			
		||||
                    context: {
 | 
			
		||||
                        oldItem: mockDomainObject
 | 
			
		||||
                    }
 | 
			
		||||
                };
 | 
			
		||||
                mockSelection = jasmine.createSpyObj("selection", [
 | 
			
		||||
                    'select',
 | 
			
		||||
                    'on',
 | 
			
		||||
                    'off',
 | 
			
		||||
                    'get'
 | 
			
		||||
                ]);
 | 
			
		||||
                mockSelection.get.andCallThrough();
 | 
			
		||||
 | 
			
		||||
                mockOpenMCT = {
 | 
			
		||||
                    time: mockConductor,
 | 
			
		||||
                    telemetry: mockTelemetryAPI,
 | 
			
		||||
                    composition: mockCompositionAPI
 | 
			
		||||
                    composition: mockCompositionAPI,
 | 
			
		||||
                    selection: mockSelection
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                $element = $('<div></div>');
 | 
			
		||||
                spyOn($element[0], 'click');
 | 
			
		||||
 | 
			
		||||
                mockMetadata = jasmine.createSpyObj('mockMetadata', [
 | 
			
		||||
                    'valuesForHints',
 | 
			
		||||
                    'value'
 | 
			
		||||
                    'value',
 | 
			
		||||
                    'values'
 | 
			
		||||
                ]);
 | 
			
		||||
                mockMetadata.value.andReturn({
 | 
			
		||||
                    key: 'value'
 | 
			
		||||
@@ -226,11 +248,11 @@ define(
 | 
			
		||||
                    mockScope,
 | 
			
		||||
                    mockQ,
 | 
			
		||||
                    mockDialogService,
 | 
			
		||||
                    mockOpenMCT
 | 
			
		||||
                    mockOpenMCT,
 | 
			
		||||
                    $element
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                findWatch("model.layoutGrid")(testModel.layoutGrid);
 | 
			
		||||
                findWatch("selection")(mockScope.selection);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("subscribes when a domain object is available", function () {
 | 
			
		||||
@@ -306,41 +328,41 @@ define(
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("allows elements to be selected", function () {
 | 
			
		||||
                var elements;
 | 
			
		||||
 | 
			
		||||
                testModel.modified = 1;
 | 
			
		||||
                findWatch("model.modified")(testModel.modified);
 | 
			
		||||
 | 
			
		||||
                elements = controller.getElements();
 | 
			
		||||
                controller.select(elements[1]);
 | 
			
		||||
                expect(mockScope.selection.select)
 | 
			
		||||
                    .toHaveBeenCalledWith(elements[1]);
 | 
			
		||||
                selectable[0].context.elementProxy = controller.getElements()[1];
 | 
			
		||||
                mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
 | 
			
		||||
 | 
			
		||||
                expect(controller.isElementSelected()).toBe(true);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("allows selection retrieval", function () {
 | 
			
		||||
                // selected with no arguments should give the current
 | 
			
		||||
                // selection
 | 
			
		||||
                var elements;
 | 
			
		||||
 | 
			
		||||
                testModel.modified = 1;
 | 
			
		||||
                findWatch("model.modified")(testModel.modified);
 | 
			
		||||
 | 
			
		||||
                elements = controller.getElements();
 | 
			
		||||
                controller.select(elements[1]);
 | 
			
		||||
                mockScope.selection.get.andReturn(elements[1]);
 | 
			
		||||
                expect(controller.selected()).toEqual(elements[1]);
 | 
			
		||||
                selectable[0].context.elementProxy = elements[1];
 | 
			
		||||
                mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
 | 
			
		||||
 | 
			
		||||
                expect(controller.getSelectedElement()).toEqual(elements[1]);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("allows selections to be cleared", function () {
 | 
			
		||||
                var elements;
 | 
			
		||||
 | 
			
		||||
            it("selects the parent view when selected element is removed", function () {
 | 
			
		||||
                testModel.modified = 1;
 | 
			
		||||
                findWatch("model.modified")(testModel.modified);
 | 
			
		||||
 | 
			
		||||
                elements = controller.getElements();
 | 
			
		||||
                controller.select(elements[1]);
 | 
			
		||||
                controller.clearSelection();
 | 
			
		||||
                expect(controller.selected(elements[1])).toBeFalsy();
 | 
			
		||||
                var elements = controller.getElements();
 | 
			
		||||
                selectable[0].context.elementProxy = elements[1];
 | 
			
		||||
                mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
 | 
			
		||||
 | 
			
		||||
                elements[1].remove();
 | 
			
		||||
                testModel.modified = 2;
 | 
			
		||||
                findWatch("model.modified")(testModel.modified);
 | 
			
		||||
 | 
			
		||||
                expect($element[0].click).toHaveBeenCalled();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("retains selections during refresh", function () {
 | 
			
		||||
@@ -352,23 +374,21 @@ define(
 | 
			
		||||
                findWatch("model.modified")(testModel.modified);
 | 
			
		||||
 | 
			
		||||
                elements = controller.getElements();
 | 
			
		||||
                controller.select(elements[1]);
 | 
			
		||||
                selectable[0].context.elementProxy = elements[1];
 | 
			
		||||
                mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
 | 
			
		||||
 | 
			
		||||
                // Verify precondition
 | 
			
		||||
                expect(mockScope.selection.select.calls.length).toEqual(1);
 | 
			
		||||
 | 
			
		||||
                // Mimic selection behavior
 | 
			
		||||
                mockScope.selection.get.andReturn(elements[1]);
 | 
			
		||||
                expect(controller.getSelectedElement()).toEqual(elements[1]);
 | 
			
		||||
 | 
			
		||||
                elements[2].remove();
 | 
			
		||||
                testModel.modified = 2;
 | 
			
		||||
                findWatch("model.modified")(testModel.modified);
 | 
			
		||||
 | 
			
		||||
                elements = controller.getElements();
 | 
			
		||||
 | 
			
		||||
                // Verify removal, as test assumes this
 | 
			
		||||
                expect(elements.length).toEqual(2);
 | 
			
		||||
 | 
			
		||||
                expect(mockScope.selection.select.calls.length).toEqual(2);
 | 
			
		||||
                expect(controller.shouldSelect(elements[1])).toBe(true);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("Displays received values for telemetry elements", function () {
 | 
			
		||||
@@ -505,21 +525,25 @@ define(
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("exposes a view-level selection proxy", function () {
 | 
			
		||||
                expect(mockScope.selection.proxy).toHaveBeenCalledWith(
 | 
			
		||||
                    jasmine.any(Object)
 | 
			
		||||
                );
 | 
			
		||||
                mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
 | 
			
		||||
                var selection = mockOpenMCT.selection.select.mostRecentCall.args[0];
 | 
			
		||||
 | 
			
		||||
                expect(mockOpenMCT.selection.select).toHaveBeenCalled();
 | 
			
		||||
                expect(selection.context.viewProxy).toBeDefined();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("exposes drag handles", function () {
 | 
			
		||||
                var handles;
 | 
			
		||||
 | 
			
		||||
                // Select something so that drag handles are expected
 | 
			
		||||
                testModel.modified = 1;
 | 
			
		||||
                findWatch("model.modified")(testModel.modified);
 | 
			
		||||
                controller.select(controller.getElements()[1]);
 | 
			
		||||
 | 
			
		||||
                selectable[0].context.elementProxy = controller.getElements()[1];
 | 
			
		||||
                mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
 | 
			
		||||
 | 
			
		||||
                // Should have a non-empty array of handles
 | 
			
		||||
                handles = controller.handles();
 | 
			
		||||
 | 
			
		||||
                expect(handles).toEqual(jasmine.any(Array));
 | 
			
		||||
                expect(handles.length).not.toEqual(0);
 | 
			
		||||
 | 
			
		||||
@@ -532,15 +556,14 @@ define(
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("exposes a move handle", function () {
 | 
			
		||||
                var handle;
 | 
			
		||||
 | 
			
		||||
                // Select something so that drag handles are expected
 | 
			
		||||
                testModel.modified = 1;
 | 
			
		||||
                findWatch("model.modified")(testModel.modified);
 | 
			
		||||
                controller.select(controller.getElements()[1]);
 | 
			
		||||
 | 
			
		||||
                selectable[0].context.elementProxy = controller.getElements()[1];
 | 
			
		||||
                mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
 | 
			
		||||
 | 
			
		||||
                // Should have a move handle
 | 
			
		||||
                handle = controller.moveHandle();
 | 
			
		||||
                var handle = controller.moveHandle();
 | 
			
		||||
 | 
			
		||||
                // And it should have start/continue/end drag methods
 | 
			
		||||
                expect(handle.startDrag).toEqual(jasmine.any(Function));
 | 
			
		||||
@@ -551,26 +574,40 @@ define(
 | 
			
		||||
            it("updates selection style during drag", function () {
 | 
			
		||||
                var oldStyle;
 | 
			
		||||
 | 
			
		||||
                // Select something so that drag handles are expected
 | 
			
		||||
                testModel.modified = 1;
 | 
			
		||||
                findWatch("model.modified")(testModel.modified);
 | 
			
		||||
                controller.select(controller.getElements()[1]);
 | 
			
		||||
                mockScope.selection.get.andReturn(controller.getElements()[1]);
 | 
			
		||||
 | 
			
		||||
                selectable[0].context.elementProxy = controller.getElements()[1];
 | 
			
		||||
                mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
 | 
			
		||||
 | 
			
		||||
                // Get style
 | 
			
		||||
                oldStyle = controller.selected().style;
 | 
			
		||||
                oldStyle = controller.getSelectedElementStyle();
 | 
			
		||||
 | 
			
		||||
                // Start a drag gesture
 | 
			
		||||
                controller.moveHandle().startDrag();
 | 
			
		||||
 | 
			
		||||
                // Haven't moved yet; style shouldn't have updated yet
 | 
			
		||||
                expect(controller.selected().style).toEqual(oldStyle);
 | 
			
		||||
                expect(controller.getSelectedElementStyle()).toEqual(oldStyle);
 | 
			
		||||
 | 
			
		||||
                // Drag a little
 | 
			
		||||
                controller.moveHandle().continueDrag([1000, 100]);
 | 
			
		||||
 | 
			
		||||
                // Style should have been updated
 | 
			
		||||
                expect(controller.selected().style).not.toEqual(oldStyle);
 | 
			
		||||
                expect(controller.getSelectedElementStyle()).not.toEqual(oldStyle);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("cleans up slection on scope destroy", function () {
 | 
			
		||||
                expect(mockScope.$on).toHaveBeenCalledWith(
 | 
			
		||||
                    '$destroy',
 | 
			
		||||
                    jasmine.any(Function)
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                mockScope.$on.mostRecentCall.args[1]();
 | 
			
		||||
 | 
			
		||||
                expect(mockOpenMCT.selection.off).toHaveBeenCalledWith(
 | 
			
		||||
                    'change',
 | 
			
		||||
                    jasmine.any(Function)
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            describe("on display bounds changes", function () {
 | 
			
		||||
@@ -653,6 +690,39 @@ define(
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("selects an range value to display, if available", function () {
 | 
			
		||||
                    mockMetadata.valuesForHints.andReturn([
 | 
			
		||||
                        {
 | 
			
		||||
                            key: 'range',
 | 
			
		||||
                            source: 'range'
 | 
			
		||||
                        }
 | 
			
		||||
                    ]);
 | 
			
		||||
                    var key = controller.chooseValueMetadataToDisplay(mockMetadata).source;
 | 
			
		||||
                    expect(key).toEqual('range');
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("selects the first non-domain value to display, if no range available", function () {
 | 
			
		||||
                    mockMetadata.valuesForHints.andReturn([]);
 | 
			
		||||
                    mockMetadata.values.andReturn([
 | 
			
		||||
                        {
 | 
			
		||||
                            key: 'domain',
 | 
			
		||||
                            source: 'domain',
 | 
			
		||||
                            hints: {
 | 
			
		||||
                                domain: 1
 | 
			
		||||
                            }
 | 
			
		||||
                        },
 | 
			
		||||
                        {
 | 
			
		||||
                            key: 'image',
 | 
			
		||||
                            source: 'image',
 | 
			
		||||
                            hints: {
 | 
			
		||||
                                image: 1
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    ]);
 | 
			
		||||
                    var key = controller.chooseValueMetadataToDisplay(mockMetadata).source;
 | 
			
		||||
                    expect(key).toEqual('image');
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("reflects limit status", function () {
 | 
			
		||||
                    mockLimitEvaluator.evaluate.andReturn({cssClass: "alarm-a"});
 | 
			
		||||
                    controller.updateView(mockTelemetryObject, [{
 | 
			
		||||
@@ -669,6 +739,14 @@ define(
 | 
			
		||||
                        expect(controller.getElements()[0].cssClass).toEqual("alarm-a");
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("listens for selection change events", function () {
 | 
			
		||||
                    expect(mockOpenMCT.selection.on).toHaveBeenCalledWith(
 | 
			
		||||
                        'change',
 | 
			
		||||
                        jasmine.any(Function)
 | 
			
		||||
                    );
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -21,8 +21,14 @@
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
define(
 | 
			
		||||
    ["../src/LayoutController"],
 | 
			
		||||
    function (LayoutController) {
 | 
			
		||||
    [
 | 
			
		||||
        "../src/LayoutController",
 | 
			
		||||
        "zepto"
 | 
			
		||||
    ],
 | 
			
		||||
    function (
 | 
			
		||||
        LayoutController,
 | 
			
		||||
        $
 | 
			
		||||
    ) {
 | 
			
		||||
 | 
			
		||||
        describe("The Layout controller", function () {
 | 
			
		||||
            var mockScope,
 | 
			
		||||
@@ -32,7 +38,12 @@ define(
 | 
			
		||||
                controller,
 | 
			
		||||
                mockCompositionCapability,
 | 
			
		||||
                mockComposition,
 | 
			
		||||
                mockCompositionObjects;
 | 
			
		||||
                mockCompositionObjects,
 | 
			
		||||
                mockOpenMCT,
 | 
			
		||||
                mockSelection,
 | 
			
		||||
                mockDomainObjectCapability,
 | 
			
		||||
                $element = [],
 | 
			
		||||
                selectable = [];
 | 
			
		||||
 | 
			
		||||
            function mockPromise(value) {
 | 
			
		||||
                return {
 | 
			
		||||
@@ -58,21 +69,18 @@ define(
 | 
			
		||||
                        } else {
 | 
			
		||||
                            return {};
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
                    getCapability: function () {
 | 
			
		||||
                        return mockDomainObjectCapability;
 | 
			
		||||
                    },
 | 
			
		||||
                    hasCapability: function (param) {
 | 
			
		||||
                        if (param === 'composition') {
 | 
			
		||||
                            return id !== 'b';
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Utility function to find a watch for a given expression
 | 
			
		||||
            function findWatch(expr) {
 | 
			
		||||
                var watch;
 | 
			
		||||
                mockScope.$watch.calls.forEach(function (call) {
 | 
			
		||||
                    if (call.args[0] === expr) {
 | 
			
		||||
                        watch = call.args[1];
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
                return watch;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                mockScope = jasmine.createSpyObj(
 | 
			
		||||
                    "$scope",
 | 
			
		||||
@@ -88,7 +96,6 @@ define(
 | 
			
		||||
                mockComposition = ["a", "b", "c"];
 | 
			
		||||
                mockCompositionObjects = mockComposition.map(mockDomainObject);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                testConfiguration = {
 | 
			
		||||
                    panels: {
 | 
			
		||||
                        a: {
 | 
			
		||||
@@ -97,27 +104,70 @@ define(
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                mockDomainObjectCapability = jasmine.createSpyObj('capability',
 | 
			
		||||
                    ['inEditContext']
 | 
			
		||||
                );
 | 
			
		||||
                mockCompositionCapability = mockPromise(mockCompositionObjects);
 | 
			
		||||
 | 
			
		||||
                mockScope.domainObject = mockDomainObject("mockDomainObject");
 | 
			
		||||
                mockScope.model = testModel;
 | 
			
		||||
                mockScope.configuration = testConfiguration;
 | 
			
		||||
                mockScope.selection = jasmine.createSpyObj(
 | 
			
		||||
                    'selection',
 | 
			
		||||
                    ['select', 'get', 'selected', 'deselect']
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                selectable[0] = {
 | 
			
		||||
                    context: {
 | 
			
		||||
                        oldItem: mockScope.domainObject
 | 
			
		||||
                    }
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                mockSelection = jasmine.createSpyObj("selection", [
 | 
			
		||||
                    'select',
 | 
			
		||||
                    'on',
 | 
			
		||||
                    'off',
 | 
			
		||||
                    'get'
 | 
			
		||||
                ]);
 | 
			
		||||
                mockSelection.get.andReturn(selectable);
 | 
			
		||||
                mockOpenMCT = {
 | 
			
		||||
                    selection: mockSelection
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                $element = $('<div></div>');
 | 
			
		||||
                $(document).find('body').append($element);
 | 
			
		||||
                spyOn($element[0], 'click');
 | 
			
		||||
 | 
			
		||||
                spyOn(mockScope.domainObject, "useCapability").andCallThrough();
 | 
			
		||||
 | 
			
		||||
                controller = new LayoutController(mockScope);
 | 
			
		||||
                controller = new LayoutController(mockScope, $element, mockOpenMCT);
 | 
			
		||||
                spyOn(controller, "layoutPanels").andCallThrough();
 | 
			
		||||
 | 
			
		||||
                findWatch("selection")(mockScope.selection);
 | 
			
		||||
 | 
			
		||||
                jasmine.Clock.useMock();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            afterEach(function () {
 | 
			
		||||
                $element.remove();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            it("listens for selection change events", function () {
 | 
			
		||||
                expect(mockOpenMCT.selection.on).toHaveBeenCalledWith(
 | 
			
		||||
                    'change',
 | 
			
		||||
                    jasmine.any(Function)
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("cleans up on scope destroy", function () {
 | 
			
		||||
                expect(mockScope.$on).toHaveBeenCalledWith(
 | 
			
		||||
                    '$destroy',
 | 
			
		||||
                    jasmine.any(Function)
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                mockScope.$on.calls[0].args[1]();
 | 
			
		||||
 | 
			
		||||
                expect(mockOpenMCT.selection.off).toHaveBeenCalledWith(
 | 
			
		||||
                    'change',
 | 
			
		||||
                    jasmine.any(Function)
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            // Model changes will indicate that panel positions
 | 
			
		||||
            // may have changed, for instance.
 | 
			
		||||
            it("watches for changes to composition", function () {
 | 
			
		||||
@@ -320,67 +370,35 @@ define(
 | 
			
		||||
                    .not.toEqual(oldStyle);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("allows panels to be selected", function () {
 | 
			
		||||
            it("allows objects to be selected", function () {
 | 
			
		||||
                mockScope.$watchCollection.mostRecentCall.args[1]();
 | 
			
		||||
                var childObj = mockCompositionObjects[0];
 | 
			
		||||
                selectable[0].context.oldItem = childObj;
 | 
			
		||||
                mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
 | 
			
		||||
 | 
			
		||||
                expect(controller.selected(childObj)).toBe(true);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("prevents event bubbling while drag is in progress", function () {
 | 
			
		||||
                mockScope.$watchCollection.mostRecentCall.args[1]();
 | 
			
		||||
                var childObj = mockCompositionObjects[0];
 | 
			
		||||
 | 
			
		||||
                controller.select(mockEvent, childObj.getId());
 | 
			
		||||
                // Do a drag
 | 
			
		||||
                controller.startDrag(childObj.getId(), [1, 1], [0, 0]);
 | 
			
		||||
                controller.continueDrag([100, 100]);
 | 
			
		||||
                controller.endDrag();
 | 
			
		||||
 | 
			
		||||
                // Because mouse position could cause the parent object to be selected, this should be ignored.
 | 
			
		||||
                controller.bypassSelection(mockEvent);
 | 
			
		||||
 | 
			
		||||
                expect(mockEvent.stopPropagation).toHaveBeenCalled();
 | 
			
		||||
 | 
			
		||||
                expect(controller.selected(childObj)).toBe(true);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("allows selection to be cleared", function () {
 | 
			
		||||
                mockScope.$watchCollection.mostRecentCall.args[1]();
 | 
			
		||||
                var childObj = mockCompositionObjects[0];
 | 
			
		||||
 | 
			
		||||
                controller.select(null, childObj.getId());
 | 
			
		||||
                controller.clearSelection();
 | 
			
		||||
 | 
			
		||||
                expect(controller.selected(childObj)).toBeFalsy();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("prevents clearing selection while drag is in progress", function () {
 | 
			
		||||
                mockScope.$watchCollection.mostRecentCall.args[1]();
 | 
			
		||||
                var childObj = mockCompositionObjects[0];
 | 
			
		||||
                var id = childObj.getId();
 | 
			
		||||
 | 
			
		||||
                controller.select(mockEvent, id);
 | 
			
		||||
 | 
			
		||||
                // Do a drag
 | 
			
		||||
                controller.startDrag(id, [1, 1], [0, 0]);
 | 
			
		||||
                controller.continueDrag([100, 100]);
 | 
			
		||||
                controller.endDrag();
 | 
			
		||||
 | 
			
		||||
                // Because mouse position could cause clearSelection to be called, this should be ignored.
 | 
			
		||||
                controller.clearSelection();
 | 
			
		||||
 | 
			
		||||
                expect(controller.selected(childObj)).toBe(true);
 | 
			
		||||
 | 
			
		||||
                // Shoud be able to clear the selection after dragging is done.
 | 
			
		||||
                // Shoud be able to select another object when dragging is done.
 | 
			
		||||
                jasmine.Clock.tick(0);
 | 
			
		||||
                controller.clearSelection();
 | 
			
		||||
                mockEvent.stopPropagation.reset();
 | 
			
		||||
                controller.bypassSelection(mockEvent);
 | 
			
		||||
 | 
			
		||||
                expect(controller.selected(childObj)).toBe(false);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("clears selection after moving/resizing", function () {
 | 
			
		||||
                mockScope.$watchCollection.mostRecentCall.args[1]();
 | 
			
		||||
                var childObj = mockCompositionObjects[0];
 | 
			
		||||
                var id = childObj.getId();
 | 
			
		||||
 | 
			
		||||
                controller.select(mockEvent, id);
 | 
			
		||||
 | 
			
		||||
                // Do a drag
 | 
			
		||||
                controller.startDrag(id, [1, 1], [0, 0]);
 | 
			
		||||
                controller.continueDrag([100, 100]);
 | 
			
		||||
                controller.endDrag();
 | 
			
		||||
 | 
			
		||||
                jasmine.Clock.tick(0);
 | 
			
		||||
                controller.clearSelection();
 | 
			
		||||
 | 
			
		||||
                expect(controller.selected(childObj)).toBe(false);
 | 
			
		||||
                expect(mockEvent.stopPropagation).not.toHaveBeenCalled();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("shows frames by default", function () {
 | 
			
		||||
@@ -398,32 +416,74 @@ define(
 | 
			
		||||
            it("hides frame when selected object has frame ", function () {
 | 
			
		||||
                mockScope.$watchCollection.mostRecentCall.args[1]();
 | 
			
		||||
                var childObj = mockCompositionObjects[0];
 | 
			
		||||
                controller.select(mockEvent, childObj.getId());
 | 
			
		||||
 | 
			
		||||
                expect(mockScope.selection.select).toHaveBeenCalled();
 | 
			
		||||
 | 
			
		||||
                var selectedObj = mockScope.selection.select.mostRecentCall.args[0];
 | 
			
		||||
                selectable[0].context.oldItem = childObj;
 | 
			
		||||
                mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
 | 
			
		||||
                var toolbarObj = controller.getToolbar(childObj.getId(), childObj);
 | 
			
		||||
 | 
			
		||||
                expect(controller.hasFrame(childObj)).toBe(true);
 | 
			
		||||
                expect(selectedObj.hideFrame).toBeDefined();
 | 
			
		||||
                expect(selectedObj.hideFrame).toEqual(jasmine.any(Function));
 | 
			
		||||
                expect(toolbarObj.hideFrame).toBeDefined();
 | 
			
		||||
                expect(toolbarObj.hideFrame).toEqual(jasmine.any(Function));
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("shows frame when selected object has no frame", function () {
 | 
			
		||||
                mockScope.$watchCollection.mostRecentCall.args[1]();
 | 
			
		||||
 | 
			
		||||
                var childObj = mockCompositionObjects[1];
 | 
			
		||||
                controller.select(mockEvent, childObj.getId());
 | 
			
		||||
 | 
			
		||||
                expect(mockScope.selection.select).toHaveBeenCalled();
 | 
			
		||||
 | 
			
		||||
                var selectedObj = mockScope.selection.select.mostRecentCall.args[0];
 | 
			
		||||
                selectable[0].context.oldItem = childObj;
 | 
			
		||||
                mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
 | 
			
		||||
                var toolbarObj = controller.getToolbar(childObj.getId(), childObj);
 | 
			
		||||
 | 
			
		||||
                expect(controller.hasFrame(childObj)).toBe(false);
 | 
			
		||||
                expect(selectedObj.showFrame).toBeDefined();
 | 
			
		||||
                expect(selectedObj.showFrame).toEqual(jasmine.any(Function));
 | 
			
		||||
                expect(toolbarObj.showFrame).toBeDefined();
 | 
			
		||||
                expect(toolbarObj.showFrame).toEqual(jasmine.any(Function));
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("selects the parent object when selected object is removed", function () {
 | 
			
		||||
                mockScope.$watchCollection.mostRecentCall.args[1]();
 | 
			
		||||
                var childObj = mockCompositionObjects[0];
 | 
			
		||||
                selectable[0].context.oldItem = childObj;
 | 
			
		||||
                mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
 | 
			
		||||
 | 
			
		||||
                var composition = ["b", "c"];
 | 
			
		||||
                mockScope.$watchCollection.mostRecentCall.args[1](composition);
 | 
			
		||||
 | 
			
		||||
                expect($element[0].click).toHaveBeenCalled();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("allows objects to be drilled-in only when editing", function () {
 | 
			
		||||
                mockScope.$watchCollection.mostRecentCall.args[1]();
 | 
			
		||||
                var childObj = mockCompositionObjects[0];
 | 
			
		||||
                childObj.getCapability().inEditContext.andReturn(false);
 | 
			
		||||
                controller.drill(mockEvent, childObj);
 | 
			
		||||
 | 
			
		||||
                expect(controller.isDrilledIn(childObj)).toBe(false);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("allows objects to be drilled-in only if it has sub objects", function () {
 | 
			
		||||
                mockScope.$watchCollection.mostRecentCall.args[1]();
 | 
			
		||||
                var childObj = mockCompositionObjects[1];
 | 
			
		||||
                childObj.getCapability().inEditContext.andReturn(true);
 | 
			
		||||
                controller.drill(mockEvent, childObj);
 | 
			
		||||
 | 
			
		||||
                expect(controller.isDrilledIn(childObj)).toBe(false);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("selects a newly-dropped object", function () {
 | 
			
		||||
                mockScope.$on.mostRecentCall.args[1](
 | 
			
		||||
                    mockEvent,
 | 
			
		||||
                    'd',
 | 
			
		||||
                    { x: 300, y: 100 }
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                var childObj = mockDomainObject("d");
 | 
			
		||||
                var testElement = $("<div data-layout-id='some-id'></div>");
 | 
			
		||||
                $element.append(testElement);
 | 
			
		||||
                spyOn(testElement[0], 'click');
 | 
			
		||||
 | 
			
		||||
                controller.selectIfNew('some-id', childObj);
 | 
			
		||||
                jasmine.Clock.tick(0);
 | 
			
		||||
 | 
			
		||||
                expect(testElement[0].click).toHaveBeenCalled();
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 
 | 
			
		||||
@@ -49,7 +49,7 @@
 | 
			
		||||
                {
 | 
			
		||||
                    "key": "ListViewController",
 | 
			
		||||
                    "implementation": ListViewController,
 | 
			
		||||
                    "depends": ["$scope"]
 | 
			
		||||
                    "depends": ["$scope", "formatService"]
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            "directives": [
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
define(function () {
 | 
			
		||||
    function ListViewController($scope) {
 | 
			
		||||
    function ListViewController($scope, formatService) {
 | 
			
		||||
        this.$scope = $scope;
 | 
			
		||||
        $scope.orderByField = 'title';
 | 
			
		||||
        $scope.reverseSort = false;
 | 
			
		||||
@@ -30,6 +30,8 @@ define(function () {
 | 
			
		||||
        var unlisten = $scope.domainObject.getCapability('mutation')
 | 
			
		||||
            .listen(this.updateView.bind(this));
 | 
			
		||||
 | 
			
		||||
        this.utc = formatService.getFormat('utc');
 | 
			
		||||
 | 
			
		||||
        $scope.$on('$destroy', function () {
 | 
			
		||||
            unlisten();
 | 
			
		||||
        });
 | 
			
		||||
@@ -50,17 +52,13 @@ define(function () {
 | 
			
		||||
                icon: child.getCapability('type').getCssClass(),
 | 
			
		||||
                title: child.getModel().name,
 | 
			
		||||
                type: child.getCapability('type').getName(),
 | 
			
		||||
                persisted: new Date(
 | 
			
		||||
                    child.getModel().persisted
 | 
			
		||||
                ).toUTCString(),
 | 
			
		||||
                modified: new Date(
 | 
			
		||||
                    child.getModel().modified
 | 
			
		||||
                ).toUTCString(),
 | 
			
		||||
                persisted: this.utc.format(child.getModel().persisted),
 | 
			
		||||
                modified: this.utc.format(child.getModel().modified),
 | 
			
		||||
                asDomainObject: child,
 | 
			
		||||
                location: child.getCapability('location'),
 | 
			
		||||
                action: child.getCapability('action')
 | 
			
		||||
            };
 | 
			
		||||
        });
 | 
			
		||||
        }, this);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return ListViewController;
 | 
			
		||||
 
 | 
			
		||||
@@ -31,7 +31,9 @@ define(
 | 
			
		||||
                controller,
 | 
			
		||||
                childModel,
 | 
			
		||||
                typeCapability,
 | 
			
		||||
                mutationCapability;
 | 
			
		||||
                mutationCapability,
 | 
			
		||||
                formatService;
 | 
			
		||||
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                unlistenFunc = jasmine.createSpy("unlisten");
 | 
			
		||||
 | 
			
		||||
@@ -41,6 +43,18 @@ define(
 | 
			
		||||
                );
 | 
			
		||||
                mutationCapability.listen.andReturn(unlistenFunc);
 | 
			
		||||
 | 
			
		||||
                formatService = jasmine.createSpyObj(
 | 
			
		||||
                    "formatService",
 | 
			
		||||
                    ["getFormat"]
 | 
			
		||||
                );
 | 
			
		||||
                formatService.getFormat.andReturn(jasmine.createSpyObj(
 | 
			
		||||
                    'utc',
 | 
			
		||||
                    ["format"]
 | 
			
		||||
                ));
 | 
			
		||||
                formatService.getFormat().format.andCallFake(function (v) {
 | 
			
		||||
                    return "formatted " + v;
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                typeCapability = jasmine.createSpyObj(
 | 
			
		||||
                    "typeCapability",
 | 
			
		||||
                    ["getCssClass", "getName"]
 | 
			
		||||
@@ -94,20 +108,27 @@ define(
 | 
			
		||||
                );
 | 
			
		||||
                scope.domainObject = domainObject;
 | 
			
		||||
 | 
			
		||||
                controller  = new ListViewController(scope);
 | 
			
		||||
                controller  = new ListViewController(scope, formatService);
 | 
			
		||||
 | 
			
		||||
                waitsFor(function () {
 | 
			
		||||
                    return scope.children;
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("uses the UTC time format", function () {
 | 
			
		||||
                expect(formatService.getFormat).toHaveBeenCalledWith('utc');
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("updates the view", function () {
 | 
			
		||||
                expect(scope.children[0]).toEqual(
 | 
			
		||||
                    {
 | 
			
		||||
                        icon: "icon-folder",
 | 
			
		||||
                        title: "Battery Charge Status",
 | 
			
		||||
                        type: "Folder",
 | 
			
		||||
                        persisted: "Wed, 07 Jun 2017 20:34:57 GMT",
 | 
			
		||||
                        modified: "Wed, 07 Jun 2017 20:34:57 GMT",
 | 
			
		||||
                        persisted: formatService.getFormat('utc')
 | 
			
		||||
                            .format(childModel.persisted),
 | 
			
		||||
                        modified: formatService.getFormat('utc')
 | 
			
		||||
                            .format(childModel.modified),
 | 
			
		||||
                        asDomainObject: childObject,
 | 
			
		||||
                        location: ''
 | 
			
		||||
                    }
 | 
			
		||||
 
 | 
			
		||||
@@ -69,7 +69,7 @@ define([
 | 
			
		||||
                    "delegates": [
 | 
			
		||||
                        "telemetry"
 | 
			
		||||
                    ],
 | 
			
		||||
                    "inspector": tableInspector,
 | 
			
		||||
                    "inspector": "table-options-edit",
 | 
			
		||||
                    "contains": [
 | 
			
		||||
                        {
 | 
			
		||||
                            "has": "telemetry"
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,10 @@
 | 
			
		||||
 this source code distribution or the Licensing information page available
 | 
			
		||||
 at runtime from the About dialog for additional information.
 | 
			
		||||
-->
 | 
			
		||||
<div ng-controller="TableOptionsController" class="l-controls-first flex-elem grows l-inspector-part">
 | 
			
		||||
 | 
			
		||||
<div ng-if="domainObject.getCapability('editor').inEditContext()"
 | 
			
		||||
     ng-controller="TableOptionsController"
 | 
			
		||||
     class="l-controls-first flex-elem grows l-inspector-part">
 | 
			
		||||
    <em class="t-inspector-part-header" title="Display properties for this object">Table Options</em>
 | 
			
		||||
    <mct-form
 | 
			
		||||
            ng-model="configuration.table.columns"
 | 
			
		||||
 
 | 
			
		||||
@@ -32,6 +32,7 @@ define(
 | 
			
		||||
         */
 | 
			
		||||
        function TelemetryCollection() {
 | 
			
		||||
            EventEmitter.call(this, arguments);
 | 
			
		||||
            this.dupeCheck = false;
 | 
			
		||||
            this.telemetry = [];
 | 
			
		||||
            this.highBuffer = [];
 | 
			
		||||
            this.sortField = undefined;
 | 
			
		||||
@@ -161,7 +162,7 @@ define(
 | 
			
		||||
                var startIx = _.sortedIndex(array, item, this.sortField);
 | 
			
		||||
                var endIx;
 | 
			
		||||
 | 
			
		||||
                if (startIx !== array.length) {
 | 
			
		||||
                if (this.dupeCheck && startIx !== array.length) {
 | 
			
		||||
                    endIx = _.sortedLastIndex(array, item, this.sortField);
 | 
			
		||||
 | 
			
		||||
                    // Create an array of potential dupes, based on having the
 | 
			
		||||
@@ -189,6 +190,7 @@ define(
 | 
			
		||||
        TelemetryCollection.prototype.add = function (items) {
 | 
			
		||||
            var added = items.filter(this.addOne);
 | 
			
		||||
            this.emit('added', added);
 | 
			
		||||
            this.dupeCheck = true;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
 
 | 
			
		||||
@@ -436,9 +436,31 @@ define(
 | 
			
		||||
         * @param {Object} searchElement Object to find the insertion point for
 | 
			
		||||
         */
 | 
			
		||||
        MCTTableController.prototype.findInsertionPoint = function (searchArray, searchElement) {
 | 
			
		||||
            //First, use a binary search to find the correct insertion point
 | 
			
		||||
            var index = this.binarySearch(searchArray, searchElement, 0, searchArray.length - 1);
 | 
			
		||||
            var testIndex = index;
 | 
			
		||||
            var index;
 | 
			
		||||
            var testIndex;
 | 
			
		||||
            var first = searchArray[0];
 | 
			
		||||
            var last = searchArray[searchArray.length - 1];
 | 
			
		||||
 | 
			
		||||
            if (first) {
 | 
			
		||||
                first = first[this.$scope.sortColumn].text;
 | 
			
		||||
            }
 | 
			
		||||
            if (last) {
 | 
			
		||||
                last = last[this.$scope.sortColumn].text;
 | 
			
		||||
            }
 | 
			
		||||
            // Shortcut check for append/prepend
 | 
			
		||||
            if (first && this.sortComparator(first, searchElement) >= 0) {
 | 
			
		||||
                index = testIndex = 0;
 | 
			
		||||
            } else if (last && this.sortComparator(last, searchElement) <= 0) {
 | 
			
		||||
                index = testIndex = searchArray.length;
 | 
			
		||||
            } else {
 | 
			
		||||
                // use a binary search to find the correct insertion point
 | 
			
		||||
                index = testIndex =  this.binarySearch(
 | 
			
		||||
                    searchArray,
 | 
			
		||||
                    searchElement,
 | 
			
		||||
                    0,
 | 
			
		||||
                    searchArray.length - 1
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            //It's possible that the insertion point is a duplicate of the element to be inserted
 | 
			
		||||
            var isDupe = function () {
 | 
			
		||||
 
 | 
			
		||||
@@ -170,6 +170,9 @@ define(
 | 
			
		||||
         * @param rows
 | 
			
		||||
         */
 | 
			
		||||
        TelemetryTableController.prototype.addRowsToTable = function (rows) {
 | 
			
		||||
            rows.forEach(function (row) {
 | 
			
		||||
                this.$scope.rows.push(row);
 | 
			
		||||
            }, this);
 | 
			
		||||
            this.$scope.$broadcast('add:rows', rows);
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -436,5 +436,28 @@ define(
 | 
			
		||||
                expect(mockScope.$broadcast).toHaveBeenCalledWith("remove:rows", discardedRows);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            describe('when telemetry is added', function () {
 | 
			
		||||
                var testRows;
 | 
			
		||||
                var expectedRows;
 | 
			
		||||
 | 
			
		||||
                beforeEach(function () {
 | 
			
		||||
                    testRows = [{ a: 0 }, { a: 1 }, { a: 2 }];
 | 
			
		||||
                    mockScope.rows = [{ a: -1 }];
 | 
			
		||||
                    expectedRows = mockScope.rows.concat(testRows);
 | 
			
		||||
 | 
			
		||||
                    spyOn(controller.telemetry, "on").andCallThrough();
 | 
			
		||||
                    controller.registerChangeListeners();
 | 
			
		||||
 | 
			
		||||
                    controller.telemetry.on.calls.forEach(function (call) {
 | 
			
		||||
                        if (call.args[0] === 'added') {
 | 
			
		||||
                            call.args[1](testRows);
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("adds it to rows in scope", function () {
 | 
			
		||||
                    expect(mockScope.rows).toEqual(expectedRows);
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 
 | 
			
		||||
@@ -38,6 +38,7 @@ define([
 | 
			
		||||
    "./src/capabilities/CostCapability",
 | 
			
		||||
    "./src/directives/MCTSwimlaneDrop",
 | 
			
		||||
    "./src/directives/MCTSwimlaneDrag",
 | 
			
		||||
    "./src/directives/MCTResourceGraphDrop",
 | 
			
		||||
    "./src/services/ObjectLoader",
 | 
			
		||||
    "./src/chart/MCTTimelineChart",
 | 
			
		||||
    "text!./res/templates/values.html",
 | 
			
		||||
@@ -69,6 +70,7 @@ define([
 | 
			
		||||
    CostCapability,
 | 
			
		||||
    MCTSwimlaneDrop,
 | 
			
		||||
    MCTSwimlaneDrag,
 | 
			
		||||
    MCTResourceGraphDrop,
 | 
			
		||||
    ObjectLoader,
 | 
			
		||||
    MCTTimelineChart,
 | 
			
		||||
    valuesTemplate,
 | 
			
		||||
@@ -577,6 +579,13 @@ define([
 | 
			
		||||
                        "$interval",
 | 
			
		||||
                        "$log"
 | 
			
		||||
                    ]
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "key": "mctResourceGraphDrop",
 | 
			
		||||
                    "implementation": MCTResourceGraphDrop,
 | 
			
		||||
                    "depends": [
 | 
			
		||||
                        "dndService"
 | 
			
		||||
                    ]
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            "services": [
 | 
			
		||||
 
 | 
			
		||||
@@ -38,6 +38,10 @@
 | 
			
		||||
    .l-timeline-pane {
 | 
			
		||||
        @include absPosDefault();
 | 
			
		||||
 | 
			
		||||
		&.drop-over {
 | 
			
		||||
			background-color: lighten($colorEditAreaBg, 5%);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	    .l-width-control {
 | 
			
		||||
		    position: relative;
 | 
			
		||||
	    }
 | 
			
		||||
 
 | 
			
		||||
@@ -20,6 +20,7 @@
 | 
			
		||||
 at runtime from the About dialog for additional information.
 | 
			
		||||
-->
 | 
			
		||||
<div class="s-timeline l-timeline-holder split-layout vertical splitter-sm"
 | 
			
		||||
     ng-click="$event.stopPropagation()"
 | 
			
		||||
     ng-controller="TimelineController as timelineController">
 | 
			
		||||
 | 
			
		||||
<mct-split-pane anchor="left" class="abs" position="pane.x">
 | 
			
		||||
@@ -77,7 +78,8 @@
 | 
			
		||||
        <mct-splitter></mct-splitter>
 | 
			
		||||
 | 
			
		||||
        <!-- BOTTOM PANE RESOURCE LEGEND -->
 | 
			
		||||
        <div class="split-pane-component abs l-timeline-pane t-pane-h l-pane-btm s-timeline-resource-legend l-timeline-resource-legend">
 | 
			
		||||
        <div mct-resource-graph-drop
 | 
			
		||||
             class="split-pane-component abs l-timeline-pane t-pane-h l-pane-btm s-timeline-resource-legend l-timeline-resource-legend">
 | 
			
		||||
            <div class="l-title s-title">{{ngModel.title}}Resource Graph Legend</div>
 | 
			
		||||
            <div class="l-legend-items legend">
 | 
			
		||||
                <mct-include key="'timeline-legend-item'"
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,81 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * Open MCT, Copyright (c) 2009-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(
 | 
			
		||||
    ['./SwimlaneDragConstants'],
 | 
			
		||||
    function (SwimlaneDragConstants) {
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Defines the `mct-resource-graph-drop` directive. When a drop occurs
 | 
			
		||||
         * on an element with this attribute, the swimlane targeted by the drop
 | 
			
		||||
         * will receive the dropped domain object (at which point it can handle
 | 
			
		||||
         * the drop, typically by toggling the swimlane graph.)
 | 
			
		||||
         * @param {DndService} dndService drag-and-drop service
 | 
			
		||||
         */
 | 
			
		||||
        function MCTResourceGraphDrop(dndService) {
 | 
			
		||||
 | 
			
		||||
            function link(scope, element, attrs) {
 | 
			
		||||
                // Handle dragover
 | 
			
		||||
                element.on('dragover', function (e) {
 | 
			
		||||
                    var swimlane = dndService.getData(
 | 
			
		||||
                        SwimlaneDragConstants.TIMELINE_SWIMLANE_DRAG_TYPE
 | 
			
		||||
                    );
 | 
			
		||||
 | 
			
		||||
                    if (typeof swimlane !== "undefined" && !swimlane.graph()) {
 | 
			
		||||
                        element.addClass('drop-over');
 | 
			
		||||
                        scope.$apply();
 | 
			
		||||
                        e.preventDefault();
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
                // Handle drops
 | 
			
		||||
                element.on('drop', function (e) {
 | 
			
		||||
                    var swimlane = dndService.getData(
 | 
			
		||||
                        SwimlaneDragConstants.TIMELINE_SWIMLANE_DRAG_TYPE
 | 
			
		||||
                    );
 | 
			
		||||
 | 
			
		||||
                    element.removeClass('drop-over');
 | 
			
		||||
 | 
			
		||||
                    // Only toggle if the graph isn't already set
 | 
			
		||||
                    if (typeof swimlane !== "undefined" && !swimlane.graph()) {
 | 
			
		||||
                        swimlane.toggleGraph();
 | 
			
		||||
                        e.preventDefault();
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
                // Clear highlights when drag leaves this swimlane
 | 
			
		||||
                element.on('dragleave', function (e) {
 | 
			
		||||
                    element.removeClass('drop-over');
 | 
			
		||||
                    scope.$apply();
 | 
			
		||||
                    e.preventDefault();
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return {
 | 
			
		||||
                // Applies to attributes
 | 
			
		||||
                restrict: "A",
 | 
			
		||||
                // Link using above function
 | 
			
		||||
                link: link
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return MCTResourceGraphDrop;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
@@ -0,0 +1,159 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * Open MCT, Copyright (c) 2009-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/MCTResourceGraphDrop', '../../src/directives/SwimlaneDragConstants'],
 | 
			
		||||
    function (MCTResourceGraphDrop, SwimlaneDragConstants) {
 | 
			
		||||
 | 
			
		||||
        describe("The mct-resource-graph-drop directive", function () {
 | 
			
		||||
            var mockDndService,
 | 
			
		||||
                mockScope,
 | 
			
		||||
                mockElement,
 | 
			
		||||
                testAttrs,
 | 
			
		||||
                mockSwimlane,
 | 
			
		||||
                testEvent,
 | 
			
		||||
                handlers,
 | 
			
		||||
                directive;
 | 
			
		||||
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                handlers = {};
 | 
			
		||||
 | 
			
		||||
                mockDndService = jasmine.createSpyObj(
 | 
			
		||||
                    'dndService',
 | 
			
		||||
                    ['setData', 'getData', 'removeData']
 | 
			
		||||
                );
 | 
			
		||||
                mockScope = jasmine.createSpyObj('$scope', ['$eval', '$apply']);
 | 
			
		||||
                mockElement = jasmine.createSpyObj('element', ['on', 'addClass', 'removeClass']);
 | 
			
		||||
                testAttrs = { mctSwimlaneDrop: "mockSwimlane" };
 | 
			
		||||
                mockSwimlane = jasmine.createSpyObj(
 | 
			
		||||
                    "swimlane",
 | 
			
		||||
                    ['graph', 'toggleGraph']
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                testEvent = {
 | 
			
		||||
                    dataTransfer: { getData: jasmine.createSpy() },
 | 
			
		||||
                    preventDefault: jasmine.createSpy(),
 | 
			
		||||
                    stopPropagation: jasmine.createSpy()
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                testEvent.dataTransfer.getData.andReturn('abc');
 | 
			
		||||
                mockDndService.getData.andCallFake(function (key) {
 | 
			
		||||
                    return key === SwimlaneDragConstants.TIMELINE_SWIMLANE_DRAG_TYPE ?
 | 
			
		||||
                        mockSwimlane : undefined;
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                mockSwimlane.graph.andReturn(false);
 | 
			
		||||
 | 
			
		||||
                directive = new MCTResourceGraphDrop(mockDndService);
 | 
			
		||||
                directive.link(mockScope, mockElement, testAttrs);
 | 
			
		||||
 | 
			
		||||
                mockElement.on.calls.forEach(function (call) {
 | 
			
		||||
                    handlers[call.args[0]] = call.args[1];
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("is available as an attribute", function () {
 | 
			
		||||
                expect(directive.restrict).toEqual("A");
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            [false, true].forEach(function (graphing) {
 | 
			
		||||
                describe("when swimlane graph is " + (graphing ? "" : "not ") + "enabled", function () {
 | 
			
		||||
                    beforeEach(function () {
 | 
			
		||||
                        mockSwimlane.graph.andReturn(graphing);
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                    describe("on dragover", function () {
 | 
			
		||||
                        var prefix = !graphing ? "does" : "does not";
 | 
			
		||||
 | 
			
		||||
                        beforeEach(function ()  {
 | 
			
		||||
                            handlers.dragover(testEvent);
 | 
			
		||||
                        });
 | 
			
		||||
 | 
			
		||||
                        it(prefix + " add a drop-over class", function () {
 | 
			
		||||
                            var expectAddClass = expect(mockElement.addClass);
 | 
			
		||||
                            (!graphing ? expectAddClass : expectAddClass.not)
 | 
			
		||||
                                .toHaveBeenCalledWith('drop-over');
 | 
			
		||||
                        });
 | 
			
		||||
 | 
			
		||||
                        it(prefix + " call $apply on scope", function () {
 | 
			
		||||
                            var expectApply = expect(mockScope.$apply);
 | 
			
		||||
                            (!graphing ? expectApply : expectApply.not)
 | 
			
		||||
                                .toHaveBeenCalled();
 | 
			
		||||
                        });
 | 
			
		||||
 | 
			
		||||
                        it(prefix + " prevent default", function () {
 | 
			
		||||
                            var expectPreventDefault = expect(testEvent.preventDefault);
 | 
			
		||||
                            (!graphing ? expectPreventDefault : expectPreventDefault.not)
 | 
			
		||||
                                .toHaveBeenCalled();
 | 
			
		||||
                        });
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                    describe("on drop", function () {
 | 
			
		||||
                        var prefix = !graphing ? "does" : "does not";
 | 
			
		||||
 | 
			
		||||
                        beforeEach(function ()  {
 | 
			
		||||
                            handlers.drop(testEvent);
 | 
			
		||||
                        });
 | 
			
		||||
 | 
			
		||||
                        it("removes any drop-over class", function () {
 | 
			
		||||
                            expect(mockElement.removeClass)
 | 
			
		||||
                                .toHaveBeenCalledWith('drop-over');
 | 
			
		||||
                        });
 | 
			
		||||
 | 
			
		||||
                        it(prefix + " toggle the swimlane's resource graph", function () {
 | 
			
		||||
                            var expectToggle = expect(mockSwimlane.toggleGraph);
 | 
			
		||||
                            (!graphing ? expectToggle : expectToggle.not)
 | 
			
		||||
                                .toHaveBeenCalled();
 | 
			
		||||
                        });
 | 
			
		||||
 | 
			
		||||
                        it(prefix + " prevent default", function () {
 | 
			
		||||
                            var expectPreventDefault = expect(testEvent.preventDefault);
 | 
			
		||||
                            (!graphing ? expectPreventDefault : expectPreventDefault.not)
 | 
			
		||||
                                .toHaveBeenCalled();
 | 
			
		||||
                        });
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                    describe("on dragleave", function () {
 | 
			
		||||
                        beforeEach(function ()  {
 | 
			
		||||
                            handlers.dragleave(testEvent);
 | 
			
		||||
                        });
 | 
			
		||||
 | 
			
		||||
                        it("removes any drop-over class", function () {
 | 
			
		||||
                            expect(mockElement.removeClass)
 | 
			
		||||
                                .toHaveBeenCalledWith('drop-over');
 | 
			
		||||
                        });
 | 
			
		||||
 | 
			
		||||
                        it("calls $apply on scope", function () {
 | 
			
		||||
                            expect(mockScope.$apply).toHaveBeenCalled();
 | 
			
		||||
                        });
 | 
			
		||||
 | 
			
		||||
                        it("calls preventDefault on events", function () {
 | 
			
		||||
                            expect(testEvent.preventDefault).toHaveBeenCalled();
 | 
			
		||||
                        });
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
@@ -27,7 +27,7 @@
 | 
			
		||||
                         ng-required="ngRequired || compositeCtrl.isNonEmpty(ngModel[field])"
 | 
			
		||||
                         ng-pattern="ngPattern"
 | 
			
		||||
                         options="item.options"
 | 
			
		||||
                         structure="row"
 | 
			
		||||
                         structure="item"
 | 
			
		||||
                         field="$index">
 | 
			
		||||
            </mct-control>
 | 
			
		||||
            <span class="composite-control-label">
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										25
									
								
								src/MCT.js
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								src/MCT.js
									
									
									
									
									
								
							@@ -28,7 +28,8 @@ define([
 | 
			
		||||
    './selection/Selection',
 | 
			
		||||
    './api/objects/object-utils',
 | 
			
		||||
    './plugins/plugins',
 | 
			
		||||
    './ui/ViewRegistry'
 | 
			
		||||
    './ui/ViewRegistry',
 | 
			
		||||
    './ui/InspectorViewRegistry'
 | 
			
		||||
], function (
 | 
			
		||||
    EventEmitter,
 | 
			
		||||
    legacyRegistry,
 | 
			
		||||
@@ -37,7 +38,8 @@ define([
 | 
			
		||||
    Selection,
 | 
			
		||||
    objectUtils,
 | 
			
		||||
    plugins,
 | 
			
		||||
    ViewRegistry
 | 
			
		||||
    ViewRegistry,
 | 
			
		||||
    InspectorViewRegistry
 | 
			
		||||
) {
 | 
			
		||||
    /**
 | 
			
		||||
     * Open MCT is an extensible web application for building mission
 | 
			
		||||
@@ -112,15 +114,13 @@ define([
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Registry for views which should appear in the Inspector area.
 | 
			
		||||
         * These views will be chosen based on selection state, so
 | 
			
		||||
         * providers should be prepared to test arbitrary objects for
 | 
			
		||||
         * viewability.
 | 
			
		||||
         * These views will be chosen based on the selection state.
 | 
			
		||||
         *
 | 
			
		||||
         * @type {module:openmct.ViewRegistry}
 | 
			
		||||
         * @type {module:openmct.InspectorViewRegistry}
 | 
			
		||||
         * @memberof module:openmct.MCT#
 | 
			
		||||
         * @name inspectors
 | 
			
		||||
         * @name inspectorViews
 | 
			
		||||
         */
 | 
			
		||||
        this.inspectors = new ViewRegistry();
 | 
			
		||||
        this.inspectorViews = new InspectorViewRegistry();
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Registry for views which should appear in Edit Properties
 | 
			
		||||
@@ -196,7 +196,6 @@ define([
 | 
			
		||||
 | 
			
		||||
        this.Dialog = api.Dialog;
 | 
			
		||||
 | 
			
		||||
        this.on('navigation', this.selection.clear.bind(this.selection));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    MCT.prototype = Object.create(EventEmitter.prototype);
 | 
			
		||||
@@ -255,22 +254,20 @@ define([
 | 
			
		||||
            this.legacyExtension('types', legacyDefinition);
 | 
			
		||||
        }.bind(this));
 | 
			
		||||
 | 
			
		||||
        this.objectViews.providers.forEach(function (p) {
 | 
			
		||||
        this.objectViews.getAllProviders().forEach(function (p) {
 | 
			
		||||
            this.legacyExtension('views', {
 | 
			
		||||
                key: 'vpid' + p.vpid,
 | 
			
		||||
                vpid: p.vpid,
 | 
			
		||||
                key: p.key,
 | 
			
		||||
                provider: p,
 | 
			
		||||
                name: p.name,
 | 
			
		||||
                cssClass: p.cssClass,
 | 
			
		||||
                description: p.description,
 | 
			
		||||
                editable: p.editable,
 | 
			
		||||
                template: '<mct-view mct-vpid="' + p.vpid + '"/>'
 | 
			
		||||
                template: '<mct-view mct-provider-key="' + p.key + '"/>'
 | 
			
		||||
            });
 | 
			
		||||
        }, this);
 | 
			
		||||
 | 
			
		||||
        legacyRegistry.register('adapter', this.legacyBundle);
 | 
			
		||||
        legacyRegistry.enable('adapter');
 | 
			
		||||
        this.install(this.plugins.Plot());
 | 
			
		||||
        /**
 | 
			
		||||
         * Fired by [MCT]{@link module:openmct.MCT} when the application
 | 
			
		||||
         * is started.
 | 
			
		||||
 
 | 
			
		||||
@@ -43,8 +43,8 @@ define([
 | 
			
		||||
                        priority: i + 100 // arbitrary to allow new views to
 | 
			
		||||
                        // be defaults by returning priority less than 100.
 | 
			
		||||
                    };
 | 
			
		||||
                    if (v.provider) {
 | 
			
		||||
                        vd.priority = v.provider.canView(newDomainObject);
 | 
			
		||||
                    if (v.provider && v.provider.priority) {
 | 
			
		||||
                        vd.priority = v.provider.priority(newDomainObject);
 | 
			
		||||
                    }
 | 
			
		||||
                    return vd;
 | 
			
		||||
                })
 | 
			
		||||
@@ -54,7 +54,6 @@ define([
 | 
			
		||||
            };
 | 
			
		||||
            return capability;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return patchViewCapability;
 | 
			
		||||
 
 | 
			
		||||
@@ -27,12 +27,15 @@ define([
 | 
			
		||||
        return {
 | 
			
		||||
            restrict: 'E',
 | 
			
		||||
            link: function (scope, element, attrs) {
 | 
			
		||||
                var provider = openmct.objectViews.getByVPID(Number(attrs.mctVpid));
 | 
			
		||||
                var provider = openmct.objectViews.getByProviderKey(attrs.mctProviderKey);
 | 
			
		||||
                var view = new provider.view(scope.domainObject.useCapability('adapter'));
 | 
			
		||||
                view.show(element[0]);
 | 
			
		||||
                var domElement = element[0];
 | 
			
		||||
 | 
			
		||||
                view.show(domElement);
 | 
			
		||||
 | 
			
		||||
                if (view.destroy) {
 | 
			
		||||
                    scope.$on('$destroy', function () {
 | 
			
		||||
                        view.destroy(element[0]);
 | 
			
		||||
                        view.destroy(domElement);
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
@@ -29,7 +29,7 @@ define([], function () {
 | 
			
		||||
        view,
 | 
			
		||||
        legacyObject
 | 
			
		||||
    ) {
 | 
			
		||||
        if (view.hasOwnProperty('vpid')) {
 | 
			
		||||
        if (view.hasOwnProperty('provider')) {
 | 
			
		||||
            var domainObject = legacyObject.useCapability('adapter');
 | 
			
		||||
            return view.provider.canView(domainObject);
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user