Compare commits
	
		
			9 Commits
		
	
	
		
			timesystem
			...
			indicators
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 7e3ade81e3 | ||
|   | 05ed6597e8 | ||
|   | 638651a0ad | ||
|   | dbae627ad9 | ||
|   | a6e93f7ad0 | ||
|   | 0dc75b2ace | ||
|   | 96e11c4f1b | ||
|   | 6cc7f23786 | ||
|   | ce11d48d25 | 
| @@ -21,6 +21,5 @@ | ||||
|     "shadow": "outer", | ||||
|     "strict": "implied", | ||||
|     "undef": true, | ||||
|     "unused": "vars", | ||||
|     "latedef": "nofunc" | ||||
|     "unused": "vars" | ||||
| } | ||||
|   | ||||
							
								
								
									
										171
									
								
								API.md
									
									
									
									
									
								
							
							
						
						
									
										171
									
								
								API.md
									
									
									
									
									
								
							| @@ -1,57 +1,3 @@ | ||||
| <!-- START doctoc generated TOC please keep comment here to allow auto update --> | ||||
| <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> | ||||
| **Table of Contents**  | ||||
|  | ||||
| - [Building Applications With Open MCT](#building-applications-with-open-mct) | ||||
|   - [Scope and purpose of this document](#scope-and-purpose-of-this-document) | ||||
|   - [Building From Source](#building-from-source) | ||||
|   - [Starting an Open MCT application](#starting-an-open-mct-application) | ||||
|   - [Plugins](#plugins) | ||||
|     - [Defining and Installing a New Plugin](#defining-and-installing-a-new-plugin) | ||||
|   - [Domain Objects and Identifiers](#domain-objects-and-identifiers) | ||||
|     - [Object Attributes](#object-attributes) | ||||
|     - [Domain Object Types](#domain-object-types) | ||||
|   - [Root Objects](#root-objects) | ||||
|   - [Object Providers](#object-providers) | ||||
|   - [Composition Providers](#composition-providers) | ||||
|     - [Adding Composition Providers](#adding-composition-providers) | ||||
|     - [Default Composition Provider](#default-composition-provider) | ||||
|   - [Telemetry API](#telemetry-api) | ||||
|     - [Integrating Telemetry Sources](#integrating-telemetry-sources) | ||||
|       - [Telemetry Metadata](#telemetry-metadata) | ||||
|         - [Values](#values) | ||||
|           - [Value Hints](#value-hints) | ||||
|         - [The Time Conductor and Telemetry](#the-time-conductor-and-telemetry) | ||||
|       - [Telemetry Providers](#telemetry-providers) | ||||
|       - [Telemetry Requests and Responses.](#telemetry-requests-and-responses) | ||||
|       - [Request Strategies **draft**](#request-strategies-draft) | ||||
|         - [`latest` request strategy](#latest-request-strategy) | ||||
|         - [`minmax` request strategy](#minmax-request-strategy) | ||||
|       - [Telemetry Formats **draft**](#telemetry-formats-draft) | ||||
|         - [Registering Formats](#registering-formats) | ||||
|       - [Telemetry Data](#telemetry-data) | ||||
|         - [Telemetry Datums](#telemetry-datums) | ||||
|       - [Limit Evaluators **draft**](#limit-evaluators-draft) | ||||
|     - [Telemetry Consumer APIs **draft**](#telemetry-consumer-apis-draft) | ||||
|   - [Time API](#time-api) | ||||
|     - [Time Systems and Bounds](#time-systems-and-bounds) | ||||
|       - [Defining and Registering Time Systems](#defining-and-registering-time-systems) | ||||
|       - [Getting and Setting the Active Time System](#getting-and-setting-the-active-time-system) | ||||
|     - [Time Bounds](#time-bounds) | ||||
|     - [Clocks](#clocks) | ||||
|       - [Defining and registering clocks](#defining-and-registering-clocks) | ||||
|       - [Getting and setting active clock](#getting-and-setting-active-clock) | ||||
|       - [Stopping an active clock](#stopping-an-active-clock) | ||||
|       - [Clock Offsets](#clock-offsets) | ||||
|     - [Time Events](#time-events) | ||||
|       - [List of Time Events](#list-of-time-events) | ||||
|     - [The Time Conductor](#the-time-conductor) | ||||
|       - [Time Conductor Configuration](#time-conductor-configuration) | ||||
|       - [Example conductor configuration](#example-conductor-configuration) | ||||
|   - [Included Plugins](#included-plugins) | ||||
|  | ||||
| <!-- END doctoc generated TOC please keep comment here to allow auto update --> | ||||
|  | ||||
| # Building Applications With Open MCT | ||||
|  | ||||
| ## Scope and purpose of this document | ||||
| @@ -208,7 +154,7 @@ registry. | ||||
|  | ||||
| eg. | ||||
| ```javascript | ||||
| openmct.types.addType('example.my-type', { | ||||
| openmct.types.addType('my-type', { | ||||
|     name: "My Type", | ||||
|     description: "This is a type that I added!", | ||||
|     creatable: true | ||||
| @@ -216,9 +162,8 @@ openmct.types.addType('example.my-type', { | ||||
| ``` | ||||
|  | ||||
| The `addType` function accepts two arguments: | ||||
| * A `string` key identifying the type. This key is used when specifying a type | ||||
| for an object.  We recommend prefixing your types with a namespace to avoid | ||||
| conflicts with other plugins. | ||||
| * A `string` key identifying the type. This key is used when specifying a type  | ||||
| for an object. | ||||
| * An object type specification. An object type definition supports the following  | ||||
| attributes       | ||||
|     * `name`: a `string` naming this object type | ||||
| @@ -249,7 +194,7 @@ To do so, use the `addRoot` method of the object API. | ||||
| eg. | ||||
| ```javascript | ||||
| openmct.objects.addRoot({ | ||||
|         namespace: "example.namespace", | ||||
|         namespace: "my-namespace", | ||||
|         key: "my-key" | ||||
|     }); | ||||
| ``` | ||||
| @@ -290,12 +235,13 @@ It is expected that the `get` function will return a | ||||
| [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)  | ||||
| that resolves with the object being requested. | ||||
|  | ||||
| In future, object providers will support other methods to enable other operations with persistence stores, such as creating, updating, and deleting objects. | ||||
| In future, object providers will support other methods to enable other operations  | ||||
| with persistence stores, such as creating, updating, and deleting objects. | ||||
|  | ||||
| ## Composition Providers | ||||
|  | ||||
| The _composition_ of a domain object is the list of objects it contains, as | ||||
| shown (for example) in the tree for browsing. Open MCT provides a | ||||
| The _composition_ of a domain object is the list of objects it contains, as shown  | ||||
| (for example) in the tree for browsing. Open MCT provides a | ||||
| [default solution](#default-composition-provider) for composition, but there | ||||
| may be cases where you want to provide the composition of a certain object | ||||
| (or type of object) dynamically. | ||||
| @@ -309,7 +255,7 @@ Composition Provider: | ||||
| ```javascript | ||||
| openmct.composition.addProvider({ | ||||
|     appliesTo: function (domainObject) { | ||||
|         return domainObject.type === 'example.my-type'; | ||||
|         return domainObject.type === 'my-type'; | ||||
|     }, | ||||
|     load: function (domainObject) { | ||||
|         return Promise.resolve(myDomainObjects); | ||||
| @@ -327,9 +273,8 @@ These identifiers will be used to fetch Domain Objects from an [Object Provider] | ||||
|  | ||||
| ### Default Composition Provider | ||||
|  | ||||
| The default composition provider applies to any domain object with a  | ||||
| `composition` property. The value of `composition` should be an array of  | ||||
| identifiers, e.g.: | ||||
| The default composition provider applies to any domain object with a `composition`  | ||||
| property. The value of `composition` should be an array of identifiers, e.g.: | ||||
|  | ||||
| ```javascript | ||||
| var domainObject = { | ||||
| @@ -350,17 +295,13 @@ var domainObject = { | ||||
|  | ||||
| ## Telemetry API | ||||
|  | ||||
| The Open MCT telemetry API provides two main sets of interfaces-- one for | ||||
| integrating telemetry data into Open MCT, and another for developing Open MCT | ||||
| visualization plugins utilizing the telemetry API.   | ||||
| The Open MCT telemetry API provides two main sets of interfaces-- one for integrating telemetry data into Open MCT, and another for developing Open MCT visualization plugins utilizing telemetry API.   | ||||
|  | ||||
| The APIs for visualization plugins are still a work in progress and docs may | ||||
| change at any time.  However, the APIs for integrating telemetry metadata into | ||||
| Open MCT are stable and documentation is included below. | ||||
| The APIs for visualization plugins are still a work in progress and docs may change at any time.  However, the APIs for integrating telemetry metadata into Open MCT are stable and documentation is included below. | ||||
|  | ||||
| ### Integrating Telemetry Sources | ||||
|  | ||||
| There are two main tasks for integrating telemetry sources-- describing telemetry objects with relevant metadata, and then providing telemetry data for those objects.  You'll use an [Object Provider](#object-providers) to provide objects with the necessary [Telemetry Metadata](#telemetry-metadata), and then register a [Telemetry Provider](#telemetry-providers) to retrieve telemetry data for those objects.  Alternatively, you can register a telemetry metadata provider to provide the necessary telemetry metadata. | ||||
| There are two main tasks for integrating telemetry sources-- describing telemetry objects with relevant metadata, and then providing telemetry data for those objects.  You'll use an [Object Provider](#object-providers) to provide objects with the necessary [Telemetry Metadata](#telemetry-metadata), and then register a [Telemetry Provider](#telemetry-providers) to retrieve telemetry data for those objects. | ||||
|  | ||||
| For a step-by-step guide to building a telemetry adapter, please see the  | ||||
| [Open MCT Tutorials](https://github.com/nasa/openmct-tutorial). | ||||
| @@ -414,7 +355,7 @@ attribute      | type   | flags    | notes | ||||
| ---            |  ---   |  ---     | --- | ||||
| `key`          | string | required | unique identifier for this field.   | ||||
| `hints`        | object | required | Hints allow views to intelligently select relevant attributes for display, and are required for most views to function.  See section on "Value Hints" below. | ||||
| `name`         | string | optional | a human readable label for this field.  If omitted, defaults to `key`. | ||||
| `name`         | string | optional | a human readible label for this field.  If omitted, defaults to `key`. | ||||
| `source`       | string | optional | identifies the property of a datum where this value is stored.  If omitted, defaults to `key`. | ||||
| `format`       | string | optional | a specific format identifier, mapping to a formatter.  If omitted, uses a default formatter.  For enumerations, use `enum`.  For timestamps, use `utc` if you are using utc dates, otherwise use a key mapping to your custom date format.   | ||||
| `units`        | string | optional | the units of this value, e.g. `km`, `seconds`, `parsecs` | ||||
| @@ -442,18 +383,14 @@ In order for the time conductor to work, there will always be an active "time sy | ||||
|  | ||||
| #### Telemetry Providers | ||||
|  | ||||
| Telemetry providers are responsible for providing historical and real-time telemetry data for telemetry objects.  Each telemetry provider determines which objects it can provide telemetry for, and then must implement methods to provide telemetry for those objects. | ||||
| Telemetry providers are responsible for providing historical and real time telemetry data for telemetry objects.  Each telemetry provider determines which objects it can provide telemetry for, and then must implement methods to provide telemetry for those objects. | ||||
|  | ||||
| A telemetry provider is a javascript object with up to four methods: | ||||
|  | ||||
| * `supportsSubscribe(domainObject, callback, options)` optional.  Must be implemented to provide realtime telemetry.  Should return `true` if the provider supports subscriptions for the given domain object (and request options). | ||||
| * `subscribe(domainObject, callback, options)` required if `supportsSubscribe` is implemented.  Establish a subscription for realtime data for the given domain object.  Should invoke `callback` with a single telemetry datum every time data is received.  Must return an unsubscribe function.  Multiple views can subscribe to the same telemetry object, so it should always return a new unsubscribe function. | ||||
| * `supportsRequest(domainObject, options)` optional.  Must be implemented to provide historical telemetry.  Should return `true` if the provider supports historical requests for the given domain object. | ||||
| * `request(domainObject, options)` required if `supportsRequest` is implemented.  Must return a promise for an array of telemetry datums that fulfills the request.  The `options` argument will include a `start`, `end`, and `domain` attribute representing the query bounds.  See [Telemetry Requests and Responses](#telemetry-requests-and-responses) for more info on how to respond to requests. | ||||
| * `supportsMetadata(domainObject)` optional.  Implement and return `true` for objects that you want to provide dynamic metadata for. | ||||
| * `getMetadata(domainObject)` required if `supportsMetadata` is implemented.  Must return a valid telemetry metadata definition that includes at least one valueMetadata definition. | ||||
| * `supportsLimits(domainObject)` optional.  Implement and return `true` for domain objects that you want to provide a limit evaluator for. | ||||
| * `getLimitEvaluator(domainObject)` required if `supportsLimits` is implemented.  Must return a valid LimitEvaluator for a given domain object. | ||||
| * `request(domainObject, options)` required if `supportsRequest` is implemented.  Must return a promise for an array of telemetry datums that fulfills the request.  The `options` argument will include a `start`, `end`, and `domain` attribute representing the query bounds.  For more request properties, see Request Properties below. | ||||
|  | ||||
| Telemetry providers are registered by calling `openmct.telemetry.addProvider(provider)`, e.g. | ||||
|  | ||||
| @@ -461,15 +398,14 @@ Telemetry providers are registered by calling `openmct.telemetry.addProvider(pro | ||||
| openmct.telemetry.addProvider({ | ||||
|     supportsRequest: function (domainObject, options) { /*...*/ }, | ||||
|     request: function (domainObject, options) { /*...*/ }, | ||||
|     supportsSubscribe: function (domainObject, callback, options) { /*...*/ }, | ||||
|     subscribe: function (domainObject, callback, options) { /*...*/ } | ||||
| }) | ||||
| ``` | ||||
|  | ||||
| Note: it is not required to implement all of the methods on every provider.  Depending on the complexity of your implementation, it may be helpful to instantiate and register your realtime, historical, and metadata providers separately. | ||||
|  | ||||
| #### Telemetry Requests and Responses. | ||||
| #### Telemetry Requests | ||||
|  | ||||
| Telemetry requests support time bounded queries. A call to a _Telemetry Provider_'s `request` function will include an `options` argument. These are simply javascript objects with attributes for the request parameters. An example of a telemetry request object with a start and end time is included below: | ||||
|  | ||||
| ```javascript | ||||
| { | ||||
|     start: 1487981997240, | ||||
| @@ -478,53 +414,7 @@ Telemetry requests support time bounded queries. A call to a _Telemetry Provider | ||||
| } | ||||
| ``` | ||||
|  | ||||
| In this case, the `domain` is the currently selected time-system, and the start and end dates are valid dates in that time system. | ||||
|  | ||||
| A telemetry provider's `request` method should return a promise for an array of telemetry datums.  These datums must be sorted by `domain` in ascending order. | ||||
|  | ||||
| #### Request Strategies **draft** | ||||
|  | ||||
| To improve performance views may request a certain strategy for data reduction.  These are intended to improve visualization performance by reducing the amount of data needed to be sent to the client.  These strategies will be indicated by additional parameters in the request options.  You may choose to handle them or ignore them.   | ||||
|  | ||||
| Note: these strategies are currently being tested in core plugins and may change based on developer feedback. | ||||
|  | ||||
| ##### `latest` request strategy | ||||
|  | ||||
| This request is a "depth based" strategy.  When a view is only capable of | ||||
| displaying a single value (or perhaps the last ten values), then it can | ||||
| use the `latest` request strategy with a `size` parameter that specifies | ||||
| the number of results it desires.  The `size` parameter is a hint; views | ||||
| must not assume the response will have the exact number of results requested. | ||||
|  | ||||
| example: | ||||
| ```javascript | ||||
| { | ||||
|     start: 1487981997240, | ||||
|     end: 1487982897240, | ||||
|     domain: 'utc', | ||||
|     strategy: 'latest', | ||||
|     size: 1 | ||||
| } | ||||
| ``` | ||||
|  | ||||
| This strategy says "I want the lastest data point in this time range".  A provider which recognizes this request should return only one value-- the latest-- in the requested time range.  Depending on your back-end implementation, performing these queries in bulk can be a large performance increase.  These are generally issued by views that are only capable of displaying a single value and only need to show the latest value. | ||||
|  | ||||
| ##### `minmax` request strategy | ||||
|  | ||||
| example: | ||||
| ```javascript | ||||
| { | ||||
|     start: 1487981997240, | ||||
|     end: 1487982897240, | ||||
|     domain: 'utc', | ||||
|     strategy: 'minmax', | ||||
|     size: 720 | ||||
| } | ||||
| ``` | ||||
|  | ||||
| MinMax queries are issued by plots, and may be issued by other types as well.  The aim is to reduce the amount of data returned but still faithfully represent the full extent of the data.  In order to do this, the view calculates the maximum data resolution it can display (i.e. the number of horizontal pixels in a plot) and sends that as the `size`.  The response should include at least one minimum and one maximum value per point of resolution. | ||||
|  | ||||
| #### Telemetry Formats **draft** | ||||
| #### Telemetry Formats | ||||
|  | ||||
| Telemetry format objects define how to interpret and display telemetry data.  | ||||
| They have a simple structure: | ||||
| @@ -594,17 +484,6 @@ The key-value pairs of this object are described by the telemetry metadata of | ||||
| a domain object, as discussed in the [Telemetry Metadata](#telemetry-metadata) | ||||
| section. | ||||
|  | ||||
| #### Limit Evaluators **draft** | ||||
|  | ||||
| Limit evaluators allow a telemetry integrator to define how limits should be  | ||||
| applied to telemetry from a given domain object.  For an example of a limit  | ||||
| evaluator, take a look at `examples/generator/SinewaveLimitProvider.js`. | ||||
|  | ||||
| ### Telemetry Consumer APIs **draft** | ||||
|  | ||||
| The APIs for requesting telemetry from Open MCT -- e.g. for use in custom views -- are currently in draft state and are being revised.  If you'd like to experiement with them before they are finalized, please contact the team via the contact-us link on our website. | ||||
|  | ||||
|  | ||||
| ## Time API | ||||
|  | ||||
| Open MCT provides API for managing the temporal state of the application. | ||||
| @@ -712,7 +591,7 @@ openmct.time.bounds({start: now - ONE_HOUR, now); | ||||
| To respond to bounds change events, listen for the [`'bounds'`](#time-events) | ||||
| event. | ||||
|  | ||||
| ### Clocks | ||||
| ## Clocks | ||||
|  | ||||
| The Time API can be set to follow a clock source which will cause the bounds | ||||
| to be updated automatically whenever the clock source "ticks". A clock is simply | ||||
| @@ -731,7 +610,7 @@ be defined to tick on some remote timing source. | ||||
| The values provided by clocks are simple `number`s, which are interpreted in the | ||||
| context of the active [Time System](#defining-and-registering-time-systems). | ||||
|  | ||||
| #### Defining and registering clocks | ||||
| ### Defining and registering clocks | ||||
|  | ||||
| A clock is an object that defines certain required metadata and functions: | ||||
|  | ||||
| @@ -845,7 +724,7 @@ __Note:__ Setting the clock offsets will trigger an immediate bounds change, as | ||||
| new bounds will be calculated based on the `currentValue()` of the active clock.  | ||||
| Clock offsets are only relevant when a clock source is active. | ||||
|  | ||||
| ### Time Events | ||||
| ## Time Events | ||||
|  | ||||
| The Time API is a standard event emitter; you can register callbacks for events using the `on` method and remove callbacks for events with the `off` method. | ||||
|  | ||||
| @@ -887,7 +766,7 @@ The events emitted by the Time API are: | ||||
|   * `clockOffsets`: The new [clock offsets](#clock-offsets). | ||||
|  | ||||
|  | ||||
| ### The Time Conductor | ||||
| ## The Time Conductor | ||||
|  | ||||
| The Time Conductor provides a user interface for managing time bounds in Open  | ||||
| MCT. It allows a user to select from configured time systems and clocks, and to set bounds and clock offsets. | ||||
|   | ||||
| @@ -88,7 +88,7 @@ and [`gulp`](http://gulpjs.com/). | ||||
|  | ||||
| To build Open MCT for deployment: | ||||
|  | ||||
| `npm run prepare` | ||||
| `npm run prepublish` | ||||
|  | ||||
| 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 | ||||
|   | ||||
| @@ -17,7 +17,7 @@ | ||||
|     "screenfull": "^3.0.0", | ||||
|     "node-uuid": "^1.4.7", | ||||
|     "comma-separated-values": "^3.6.4", | ||||
|     "file-saver": "1.3.3", | ||||
|     "file-saver": "^1.3.3", | ||||
|     "zepto": "^1.1.6", | ||||
|     "eventemitter3": "^1.2.0", | ||||
|     "lodash": "3.10.1", | ||||
|   | ||||
| @@ -1,11 +1,3 @@ | ||||
| machine: | ||||
|   node: | ||||
|     version: 8.11.0 | ||||
|  | ||||
| dependencies: | ||||
|   pre: | ||||
|     - npm install -g npm@latest | ||||
|  | ||||
| deployment: | ||||
|   production: | ||||
|     branch: master | ||||
|   | ||||
| @@ -2283,7 +2283,7 @@ To install build dependencies (only needs to be run once): | ||||
|  | ||||
| To build: | ||||
|  | ||||
| `npm run prepare` | ||||
| `npm run prepublish` | ||||
|  | ||||
| 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 | ||||
|   | ||||
| @@ -1,121 +0,0 @@ | ||||
| # 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) | ||||
| @@ -1,108 +0,0 @@ | ||||
| define([ | ||||
|     'lodash' | ||||
| ], function ( | ||||
|     _ | ||||
| ) { | ||||
|  | ||||
|     var METADATA_BY_TYPE = { | ||||
|         'generator': { | ||||
|             values: [ | ||||
|                 { | ||||
|                     key: "name", | ||||
|                     name: "Name" | ||||
|                 }, | ||||
|                 { | ||||
|                     key: "utc", | ||||
|                     name: "Time", | ||||
|                     format: "utc", | ||||
|                     hints: { | ||||
|                         domain: 1 | ||||
|                     } | ||||
|                 }, | ||||
|                 { | ||||
|                     key: "yesterday", | ||||
|                     name: "Yesterday", | ||||
|                     format: "utc", | ||||
|                     hints: { | ||||
|                         domain: 2 | ||||
|                     } | ||||
|                 }, | ||||
|                 { | ||||
|                     key: "sin", | ||||
|                     name: "Sine", | ||||
|                     hints: { | ||||
|                         range: 1 | ||||
|                     } | ||||
|                 }, | ||||
|                 { | ||||
|                     key: "cos", | ||||
|                     name: "Cosine", | ||||
|                     hints: { | ||||
|                         range: 2 | ||||
|                     } | ||||
|                 } | ||||
|             ] | ||||
|         }, | ||||
|         'example.state-generator': { | ||||
|             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 | ||||
|                     } | ||||
|                 } | ||||
|             ] | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function GeneratorMetadataProvider() { | ||||
|  | ||||
|     } | ||||
|  | ||||
|     GeneratorMetadataProvider.prototype.supportsMetadata = function (domainObject) { | ||||
|         return METADATA_BY_TYPE.hasOwnProperty(domainObject.type); | ||||
|     }; | ||||
|  | ||||
|     GeneratorMetadataProvider.prototype.getMetadata = function (domainObject) { | ||||
|         return _.extend( | ||||
|                 {}, | ||||
|                 domainObject.telemetry, | ||||
|                 METADATA_BY_TYPE[domainObject.type] | ||||
|             ); | ||||
|     }; | ||||
|  | ||||
|     return GeneratorMetadataProvider; | ||||
|  | ||||
| }); | ||||
| @@ -30,8 +30,7 @@ define([ | ||||
|         amplitude: 1, | ||||
|         period: 10, | ||||
|         offset: 0, | ||||
|         dataRateInHz: 1, | ||||
|         phase: 0 | ||||
|         dataRateInHz: 1 | ||||
|     }; | ||||
|  | ||||
|     function GeneratorProvider() { | ||||
| @@ -51,12 +50,9 @@ define([ | ||||
|             'amplitude', | ||||
|             'period', | ||||
|             'offset', | ||||
|             'dataRateInHz', | ||||
|             'phase', | ||||
|             'dataRateInHz' | ||||
|         ]; | ||||
|  | ||||
|         request = request || {}; | ||||
|  | ||||
|         var workerRequest = {}; | ||||
|  | ||||
|         props.forEach(function (prop) { | ||||
| @@ -66,12 +62,12 @@ define([ | ||||
|             if (request && request.hasOwnProperty(prop)) { | ||||
|                 workerRequest[prop] = request[prop]; | ||||
|             } | ||||
|             if (!workerRequest.hasOwnProperty(prop)) { | ||||
|             if (!workerRequest[prop]) { | ||||
|                 workerRequest[prop] = REQUEST_DEFAULTS[prop]; | ||||
|             } | ||||
|             workerRequest[prop] = Number(workerRequest[prop]); | ||||
|         }); | ||||
|         workerRequest.name = domainObject.name; | ||||
|  | ||||
|         return workerRequest; | ||||
|     }; | ||||
|  | ||||
|   | ||||
							
								
								
									
										87
									
								
								example/generator/SinewaveLimitCapability.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								example/generator/SinewaveLimitCapability.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,87 @@ | ||||
| /***************************************************************************** | ||||
|  * 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( | ||||
|     [], | ||||
|     function () { | ||||
|         "use strict"; | ||||
|  | ||||
|         var RED = 0.9, | ||||
|             YELLOW = 0.5, | ||||
|             LIMITS = { | ||||
|                 rh: { | ||||
|                     cssClass: "s-limit-upr s-limit-red", | ||||
|                     low: RED, | ||||
|                     high: Number.POSITIVE_INFINITY, | ||||
|                     name: "Red High" | ||||
|                 }, | ||||
|                 rl: { | ||||
|                     cssClass: "s-limit-lwr s-limit-red", | ||||
|                     high: -RED, | ||||
|                     low: Number.NEGATIVE_INFINITY, | ||||
|                     name: "Red Low" | ||||
|                 }, | ||||
|                 yh: { | ||||
|                     cssClass: "s-limit-upr s-limit-yellow", | ||||
|                     low: YELLOW, | ||||
|                     high: RED, | ||||
|                     name: "Yellow High" | ||||
|                 }, | ||||
|                 yl: { | ||||
|                     cssClass: "s-limit-lwr s-limit-yellow", | ||||
|                     low: -RED, | ||||
|                     high: -YELLOW, | ||||
|                     name: "Yellow Low" | ||||
|                 } | ||||
|             }; | ||||
|  | ||||
|         function SinewaveLimitCapability(domainObject) { | ||||
|             return { | ||||
|                 limits: function (range) { | ||||
|                     return LIMITS; | ||||
|                 }, | ||||
|                 evaluate: function (datum, range) { | ||||
|                     range = range || 'sin'; | ||||
|                     if (datum[range] > RED) { | ||||
|                         return LIMITS.rh; | ||||
|                     } | ||||
|                     if (datum[range] < -RED) { | ||||
|                         return LIMITS.rl; | ||||
|                     } | ||||
|                     if (datum[range] > YELLOW) { | ||||
|                         return LIMITS.yh; | ||||
|                     } | ||||
|                     if (datum[range] < -YELLOW) { | ||||
|                         return LIMITS.yl; | ||||
|                     } | ||||
|                 } | ||||
|             }; | ||||
|         } | ||||
|  | ||||
|         SinewaveLimitCapability.appliesTo = function (model) { | ||||
|             return model.type === 'generator'; | ||||
|         }; | ||||
|  | ||||
|         return SinewaveLimitCapability; | ||||
|     } | ||||
| ); | ||||
| @@ -1,88 +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([ | ||||
|  | ||||
| ], function ( | ||||
|  | ||||
| ) { | ||||
|  | ||||
|     var RED = 0.9, | ||||
|         YELLOW = 0.5, | ||||
|         LIMITS = { | ||||
|             rh: { | ||||
|                 cssClass: "s-limit-upr s-limit-red", | ||||
|                 low: RED, | ||||
|                 high: Number.POSITIVE_INFINITY, | ||||
|                 name: "Red High" | ||||
|             }, | ||||
|             rl: { | ||||
|                 cssClass: "s-limit-lwr s-limit-red", | ||||
|                 high: -RED, | ||||
|                 low: Number.NEGATIVE_INFINITY, | ||||
|                 name: "Red Low" | ||||
|             }, | ||||
|             yh: { | ||||
|                 cssClass: "s-limit-upr s-limit-yellow", | ||||
|                 low: YELLOW, | ||||
|                 high: RED, | ||||
|                 name: "Yellow High" | ||||
|             }, | ||||
|             yl: { | ||||
|                 cssClass: "s-limit-lwr s-limit-yellow", | ||||
|                 low: -RED, | ||||
|                 high: -YELLOW, | ||||
|                 name: "Yellow Low" | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|     function SinewaveLimitProvider() { | ||||
|  | ||||
|     } | ||||
|  | ||||
|     SinewaveLimitProvider.prototype.supportsLimits = function (domainObject) { | ||||
|         return domainObject.type === 'generator'; | ||||
|     }; | ||||
|  | ||||
|     SinewaveLimitProvider.prototype.getLimitEvaluator = function (domainObject) { | ||||
|         return { | ||||
|             evaluate: function (datum, valueMetadata) { | ||||
|                 var range = valueMetadata ? valueMetadata.key : 'sin' | ||||
|                 if (datum[range] > RED) { | ||||
|                     return LIMITS.rh; | ||||
|                 } | ||||
|                 if (datum[range] < -RED) { | ||||
|                     return LIMITS.rl; | ||||
|                 } | ||||
|                 if (datum[range] > YELLOW) { | ||||
|                     return LIMITS.yh; | ||||
|                 } | ||||
|                 if (datum[range] < -YELLOW) { | ||||
|                     return LIMITS.yl; | ||||
|                 } | ||||
|             } | ||||
|         }; | ||||
|     }; | ||||
|  | ||||
|     return SinewaveLimitProvider; | ||||
| }); | ||||
| @@ -1,82 +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. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| 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(); | ||||
|             var datum = pointForTimestamp(now, duration, domainObject.name); | ||||
|             datum.value += ""; | ||||
|             callback(datum); | ||||
|         }, 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; | ||||
|  | ||||
| }); | ||||
| @@ -62,11 +62,10 @@ | ||||
|                 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, data.phase), | ||||
|                         cos: cos(nextStep, data.period, data.amplitude, data.offset, data.phase) | ||||
|                         sin: sin(nextStep, data.period, data.amplitude, data.offset), | ||||
|                         cos: cos(nextStep, data.period, data.amplitude, data.offset) | ||||
|                     } | ||||
|                 }); | ||||
|                 nextStep += step; | ||||
| @@ -83,22 +82,21 @@ | ||||
|     } | ||||
|  | ||||
|     function onRequest(message) { | ||||
|         var request = message.data; | ||||
|         if (request.end == undefined) { | ||||
|             request.end = Date.now(); | ||||
|         var data = message.data; | ||||
|         if (data.end == undefined) { | ||||
|             data.end = Date.now(); | ||||
|         } | ||||
|         if (request.start == undefined){ | ||||
|             request.start = request.end - FIFTEEN_MINUTES; | ||||
|         if (data.start == undefined){ | ||||
|             data.start = data.end - FIFTEEN_MINUTES; | ||||
|         } | ||||
|  | ||||
|         var now = Date.now(); | ||||
|         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 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 step = 1000 / dataRateInHz; | ||||
|         var nextStep = start - (start % step) + step; | ||||
| @@ -107,11 +105,10 @@ | ||||
|  | ||||
|         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, phase), | ||||
|                 cos: cos(nextStep, period, amplitude, offset, phase) | ||||
|                 sin: sin(nextStep, period, amplitude, offset), | ||||
|                 cos: cos(nextStep, period, amplitude, offset) | ||||
|             }); | ||||
|         } | ||||
|         self.postMessage({ | ||||
| @@ -120,14 +117,14 @@ | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     function cos(timestamp, period, amplitude, offset, phase) { | ||||
|     function cos(timestamp, period, amplitude, offset) { | ||||
|         return amplitude * | ||||
|             Math.cos(phase + (timestamp / period / 1000 * Math.PI * 2)) + offset; | ||||
|             Math.cos(timestamp / period / 1000 * Math.PI * 2) + offset; | ||||
|     } | ||||
|  | ||||
|     function sin(timestamp, period, amplitude, offset, phase) { | ||||
|     function sin(timestamp, period, amplitude, offset) { | ||||
|         return amplitude * | ||||
|             Math.sin(phase + (timestamp / period / 1000 * Math.PI * 2)) + offset; | ||||
|             Math.sin(timestamp / period / 1000 * Math.PI * 2) + offset; | ||||
|     } | ||||
|  | ||||
|     function sendError(error, message) { | ||||
|   | ||||
| @@ -23,46 +23,29 @@ | ||||
|  | ||||
| define([ | ||||
|     "./GeneratorProvider", | ||||
|     "./SinewaveLimitProvider", | ||||
|     "./StateGeneratorProvider", | ||||
|     "./GeneratorMetadataProvider" | ||||
|     "./SinewaveLimitCapability" | ||||
| ], function ( | ||||
|     GeneratorProvider, | ||||
|     SinewaveLimitProvider, | ||||
|     StateGeneratorProvider, | ||||
|     GeneratorMetadataProvider | ||||
|     SinewaveLimitCapability | ||||
| ) { | ||||
|  | ||||
|     return function(openmct){ | ||||
|  | ||||
|         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 | ||||
|                 } | ||||
|     var legacyExtensions = { | ||||
|         "capabilities": [ | ||||
|             { | ||||
|                 "key": "limit", | ||||
|                 "implementation": SinewaveLimitCapability | ||||
|             } | ||||
|         ] | ||||
|     }; | ||||
|  | ||||
|     return function(openmct){ | ||||
|         //Register legacy extensions for things not yet supported by the new API | ||||
|         Object.keys(legacyExtensions).forEach(function (type){ | ||||
|             var extensionsOfType = legacyExtensions[type]; | ||||
|             extensionsOfType.forEach(function (extension) { | ||||
|                 openmct.legacyExtension(type, extension) | ||||
|             }) | ||||
|         }); | ||||
|  | ||||
|         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.", | ||||
| @@ -71,58 +54,51 @@ define([ | ||||
|             form: [ | ||||
|                 { | ||||
|                     name: "Period", | ||||
|                     control: "numberfield", | ||||
|                     control: "textfield", | ||||
|                     cssClass: "l-input-sm l-numeric", | ||||
|                     key: "period", | ||||
|                     required: true, | ||||
|                     property: [ | ||||
|                         "telemetry", | ||||
|                         "period" | ||||
|                     ] | ||||
|                     ], | ||||
|                     pattern: "^\\d*(\\.\\d*)?$" | ||||
|                 }, | ||||
|                 { | ||||
|                     name: "Amplitude", | ||||
|                     control: "numberfield", | ||||
|                     control: "textfield", | ||||
|                     cssClass: "l-input-sm l-numeric", | ||||
|                     key: "amplitude", | ||||
|                     required: true, | ||||
|                     property: [ | ||||
|                         "telemetry", | ||||
|                         "amplitude" | ||||
|                     ] | ||||
|                     ], | ||||
|                     pattern: "^\\d*(\\.\\d*)?$" | ||||
|                 }, | ||||
|                 { | ||||
|                     name: "Offset", | ||||
|                     control: "numberfield", | ||||
|                     control: "textfield", | ||||
|                     cssClass: "l-input-sm l-numeric", | ||||
|                     key: "offset", | ||||
|                     required: true, | ||||
|                     property: [ | ||||
|                         "telemetry", | ||||
|                         "offset" | ||||
|                     ] | ||||
|                     ], | ||||
|                     pattern: "^\\d*(\\.\\d*)?$" | ||||
|                 }, | ||||
|                 { | ||||
|                     name: "Data Rate (hz)", | ||||
|                     control: "numberfield", | ||||
|                     control: "textfield", | ||||
|                     cssClass: "l-input-sm l-numeric", | ||||
|                     key: "dataRateInHz", | ||||
|                     required: true, | ||||
|                     property: [ | ||||
|                         "telemetry", | ||||
|                         "dataRateInHz" | ||||
|                     ] | ||||
|                 }, | ||||
|                 { | ||||
|                     name: "Phase (radians)", | ||||
|                     control: "numberfield", | ||||
|                     cssClass: "l-input-sm l-numeric", | ||||
|                     key: "phase", | ||||
|                     required: true, | ||||
|                     property: [ | ||||
|                         "telemetry", | ||||
|                         "phase" | ||||
|                     ] | ||||
|                     ], | ||||
|                     pattern: "^\\d*(\\.\\d*)?$" | ||||
|                 } | ||||
|             ], | ||||
|             initialize: function (object) { | ||||
| @@ -131,14 +107,42 @@ define([ | ||||
|                     amplitude: 1, | ||||
|                     offset: 0, | ||||
|                     dataRateInHz: 1, | ||||
|                     phase: 0 | ||||
|                     values: [ | ||||
|                         { | ||||
|                             key: "utc", | ||||
|                             name: "Time", | ||||
|                             format: "utc", | ||||
|                             hints: { | ||||
|                                 domain: 1 | ||||
|                             } | ||||
|                         }, | ||||
|                         { | ||||
|                             key: "yesterday", | ||||
|                             name: "Yesterday", | ||||
|                             format: "utc", | ||||
|                             hints: { | ||||
|                                 domain: 2 | ||||
|                             } | ||||
|                         }, | ||||
|                         { | ||||
|                             key: "sin", | ||||
|                             name: "Sine", | ||||
|                             hints: { | ||||
|                                 range: 1 | ||||
|                             } | ||||
|                         }, | ||||
|                         { | ||||
|                             key: "cos", | ||||
|                             name: "Cosine", | ||||
|                             hints: { | ||||
|                                 range: 2 | ||||
|                             } | ||||
|                         } | ||||
|                     ] | ||||
|                 }; | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         openmct.telemetry.addProvider(new GeneratorProvider()); | ||||
|         openmct.telemetry.addProvider(new GeneratorMetadataProvider()); | ||||
|         openmct.telemetry.addProvider(new SinewaveLimitProvider()); | ||||
|     }; | ||||
|  | ||||
| }); | ||||
|   | ||||
| @@ -38,7 +38,8 @@ define([ | ||||
|                     "provides": "identityService", | ||||
|                     "type": "provider", | ||||
|                     "depends": [ | ||||
|                         "dialogService" | ||||
|                         "dialogService", | ||||
|                         "$q" | ||||
|                     ] | ||||
|                 } | ||||
|             ] | ||||
|   | ||||
| @@ -55,21 +55,37 @@ define( | ||||
|          * @implements {IdentityService} | ||||
|          * @memberof platform/identity | ||||
|          */ | ||||
|         function ExampleIdentityProvider(dialogService) { | ||||
|             // Handle rejected dialog messages by treating the | ||||
|             // current user as undefined. | ||||
|             function echo(v) { return v; } | ||||
|             function giveUndefined() { return undefined; } | ||||
|         function ExampleIdentityProvider(dialogService, $q) { | ||||
|             this.dialogService = dialogService; | ||||
|             this.$q = $q; | ||||
|  | ||||
|             this.userPromise = | ||||
|                 dialogService.getUserInput(DIALOG_STRUCTURE, DEFAULT_IDENTITY) | ||||
|                     .then(echo, giveUndefined); | ||||
|             this.returnUser = this.returnUser.bind(this); | ||||
|             this.returnUndefined = this.returnUndefined.bind(this); | ||||
|         } | ||||
|  | ||||
|         ExampleIdentityProvider.prototype.getUser = function () { | ||||
|             return this.userPromise; | ||||
|             if (this.user) { | ||||
|                 return this.$q.when(this.user); | ||||
|             } else { | ||||
|                 return this.dialogService.getUserInput(DIALOG_STRUCTURE, DEFAULT_IDENTITY) | ||||
|                     .then(this.returnUser, this.returnUndefined); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * @private | ||||
|          */ | ||||
|         ExampleIdentityProvider.prototype.returnUser = function (user) { | ||||
|             return this.user = user; | ||||
|         } | ||||
|  | ||||
|         /** | ||||
|          * @private | ||||
|          */ | ||||
|         ExampleIdentityProvider.prototype.returnUndefined = function () { | ||||
|             return undefined; | ||||
|         } | ||||
|  | ||||
|         return ExampleIdentityProvider; | ||||
|     } | ||||
| ); | ||||
|   | ||||
| @@ -48,9 +48,8 @@ define([ | ||||
|                 "https://www.hq.nasa.gov/alsj/a16/AS16-117-18748.jpg" | ||||
|             ]; | ||||
|  | ||||
|         function pointForTimestamp(timestamp, name) { | ||||
|         function pointForTimestamp(timestamp) { | ||||
|             return { | ||||
|                 name: name, | ||||
|                 utc: Math.floor(timestamp / 5000) * 5000, | ||||
|                 url: IMAGE_SAMPLES[Math.floor(timestamp / 5000) % IMAGE_SAMPLES.length] | ||||
|             }; | ||||
| @@ -62,7 +61,7 @@ define([ | ||||
|             }, | ||||
|             subscribe: function (domainObject, callback) { | ||||
|                 var interval = setInterval(function () { | ||||
|                     callback(pointForTimestamp(Date.now(), domainObject.name)); | ||||
|                     callback(pointForTimestamp(Date.now())); | ||||
|                 }, 5000); | ||||
|  | ||||
|                 return function (interval) { | ||||
| @@ -80,8 +79,8 @@ define([ | ||||
|                 var start = options.start; | ||||
|                 var end = options.end; | ||||
|                 var data = []; | ||||
|                 while (start <= end && data.length < 5000) { | ||||
|                     data.push(pointForTimestamp(start, domainObject.name)); | ||||
|                 while (start < end && data.length < 5000) { | ||||
|                     data.push(pointForTimestamp(start)); | ||||
|                     start += 5000; | ||||
|                 } | ||||
|                 return Promise.resolve(data); | ||||
| @@ -94,7 +93,7 @@ define([ | ||||
|                     options.strategy === 'latest'; | ||||
|             }, | ||||
|             request: function (domainObject, options) { | ||||
|                 return Promise.resolve([pointForTimestamp(Date.now(), domainObject.name)]); | ||||
|                 return Promise.resolve([pointForTimestamp(Date.now())]); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
| @@ -110,10 +109,6 @@ define([ | ||||
|                 initialize: function (object) { | ||||
|                     object.telemetry = { | ||||
|                         values: [ | ||||
|                             { | ||||
|                                 name: 'Name', | ||||
|                                 key: 'name' | ||||
|                             }, | ||||
|                             { | ||||
|                                 name: 'Time', | ||||
|                                 key: 'utc', | ||||
|   | ||||
							
								
								
									
										146
									
								
								example/plotOptions/bundle.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								example/plotOptions/bundle.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,146 @@ | ||||
| /***************************************************************************** | ||||
|  * 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,7 +58,11 @@ | ||||
|         position: relative; | ||||
|     } | ||||
|  | ||||
|     .w-mct-example > div { margin-bottom: $interiorMarginLg; } | ||||
|     .w-mct-example { | ||||
|         div { | ||||
|             margin-bottom: $interiorMarginLg; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     code, | ||||
|     pre { | ||||
| @@ -101,11 +105,11 @@ | ||||
|         padding-top: 1em; | ||||
|  | ||||
|         .cols { | ||||
|             display: flex; | ||||
|             flex-direction: row; | ||||
|             @include display(flex); | ||||
|             @include flex-direction(row); | ||||
|  | ||||
|             .col { | ||||
|                 flex: 1 1 auto; | ||||
|                 @include flex(1 1 auto); | ||||
|                 &:not(:last-child) { | ||||
|                     $v: $interiorMargin * 4; | ||||
|                     border-right: 1px solid $colorInteriorBorder; | ||||
| @@ -199,7 +203,7 @@ | ||||
|                 border-radius: 15%; | ||||
|                 position: absolute; | ||||
|                 left: 50%; | ||||
|                 transform: translateX(-50%); | ||||
|                 @include transform(translateX(-50%)); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -54,7 +54,7 @@ define( | ||||
|                     return "icon-object-unknown"; | ||||
|                 }, | ||||
|                 getText: function () { | ||||
|                     return latest; | ||||
|                     return "" + latest; | ||||
|                 }, | ||||
|                 getDescription: function () { | ||||
|                     return ""; | ||||
|   | ||||
| @@ -22,8 +22,6 @@ | ||||
|  | ||||
| /*global require,__dirname*/ | ||||
|  | ||||
| require("v8-compile-cache"); | ||||
|  | ||||
| var gulp = require('gulp'), | ||||
|     sourcemaps = require('gulp-sourcemaps'), | ||||
|     path = require('path'), | ||||
| @@ -179,4 +177,4 @@ gulp.task('install', [ 'assets', 'scripts' ]); | ||||
|  | ||||
| gulp.task('verify', [ 'lint', 'test', 'checkstyle' ]); | ||||
|  | ||||
| gulp.task('build', [ 'verify', 'install' ]); | ||||
| gulp.task('build', [ 'verify', 'install' ]); | ||||
|   | ||||
| @@ -46,7 +46,7 @@ | ||||
|             openmct.install(openmct.plugins.AutoflowView({ | ||||
|                 type: "telemetry.panel" | ||||
|             })); | ||||
|             /*openmct.install(openmct.plugins.Conductor({ | ||||
|             openmct.install(openmct.plugins.Conductor({ | ||||
|                 menuOptions: [ | ||||
|                     { | ||||
|                         name: "Fixed", | ||||
| @@ -66,7 +66,7 @@ | ||||
|                         } | ||||
|                     } | ||||
|                 ] | ||||
|             }));*/ | ||||
|             })); | ||||
|             openmct.install(openmct.plugins.SummaryWidget()); | ||||
|             openmct.time.clock('local', {start: -THIRTY_MINUTES, end: 0}); | ||||
|             openmct.time.timeSystem('utc'); | ||||
|   | ||||
| @@ -37,13 +37,14 @@ module.exports = function(config) { | ||||
|             {pattern: 'bower_components/**/*.js', included: false}, | ||||
|             {pattern: 'node_modules/d3-*/**/*.js', included: false}, | ||||
|             {pattern: 'node_modules/vue/**/*.js', included: false}, | ||||
|             {pattern: 'src/**/*', included: false}, | ||||
|             {pattern: 'src/**/*.js', 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,8 +89,7 @@ module.exports = function(config) { | ||||
|                 "dist/reports/coverage", | ||||
|             check: { | ||||
|                 global: { | ||||
|                     lines: 80, | ||||
|                     excludes: ['src/plugins/plot/**/*.js'] | ||||
|                     lines: 80 | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|   | ||||
| @@ -40,7 +40,7 @@ requirejs.config({ | ||||
|         "vue": "node_modules/vue/dist/vue.min", | ||||
|         "zepto": "bower_components/zepto/zepto.min", | ||||
|         "lodash": "bower_components/lodash/lodash", | ||||
|         "d3-selection": "node_modules/d3-selection/dist/d3-selection.min", | ||||
|         "d3-selection": "node_modules/d3-selection/build/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", | ||||
| @@ -101,7 +101,6 @@ define([ | ||||
|     var openmct = new MCT(); | ||||
|  | ||||
|     openmct.legacyRegistry = defaultRegistry; | ||||
|     openmct.install(openmct.plugins.Plot()); | ||||
|  | ||||
|     if (typeof BUILD_CONSTANTS !== 'undefined') { | ||||
|         openmct.install(buildInfo(BUILD_CONSTANTS)); | ||||
|   | ||||
							
								
								
									
										29
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										29
									
								
								package.json
									
									
									
									
									
								
							| @@ -1,18 +1,18 @@ | ||||
| { | ||||
|   "name": "openmct", | ||||
|   "version": "0.14.0-SNAPSHOT", | ||||
|   "version": "0.12.1-SNAPSHOT", | ||||
|   "description": "The Open MCT core platform", | ||||
|   "dependencies": { | ||||
|     "d3-array": "1.2.x", | ||||
|     "d3-axis": "1.0.x", | ||||
|     "d3-collection": "1.0.x", | ||||
|     "d3-color": "1.0.x", | ||||
|     "d3-format": "1.2.x", | ||||
|     "d3-interpolate": "1.1.x", | ||||
|     "d3-scale": "1.0.x", | ||||
|     "d3-selection": "1.3.x", | ||||
|     "d3-time": "1.0.x", | ||||
|     "d3-time-format": "2.1.x", | ||||
|     "d3-array": "^1.0.2", | ||||
|     "d3-axis": "^1.0.4", | ||||
|     "d3-collection": "^1.0.2", | ||||
|     "d3-color": "^1.0.2", | ||||
|     "d3-format": "^1.0.2", | ||||
|     "d3-interpolate": "^1.1.3", | ||||
|     "d3-scale": "^1.0.4", | ||||
|     "d3-selection": "^1.0.3", | ||||
|     "d3-time": "^1.0.4", | ||||
|     "d3-time-format": "^2.0.3", | ||||
|     "express": "^4.13.1", | ||||
|     "minimist": "^1.1.1", | ||||
|     "request": "^2.69.0", | ||||
| @@ -28,7 +28,7 @@ | ||||
|     "gulp-jshint-html-reporter": "^0.1.3", | ||||
|     "gulp-rename": "^1.2.2", | ||||
|     "gulp-requirejs-optimize": "^0.3.1", | ||||
|     "gulp-sass": "^3.1.0", | ||||
|     "gulp-sass": "^2.2.0", | ||||
|     "gulp-sourcemaps": "^1.6.0", | ||||
|     "jasmine-core": "^2.3.0", | ||||
|     "jscs-html-reporter": "^0.1.0", | ||||
| @@ -50,8 +50,7 @@ | ||||
|     "moment": "^2.11.1", | ||||
|     "node-bourbon": "^4.2.3", | ||||
|     "requirejs": "2.1.x", | ||||
|     "split": "^1.0.0", | ||||
|     "v8-compile-cache": "^1.1.0" | ||||
|     "split": "^1.0.0" | ||||
|   }, | ||||
|   "scripts": { | ||||
|     "start": "node app.js", | ||||
| @@ -61,7 +60,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", | ||||
|     "prepare": "node ./node_modules/bower/bin/bower install && node ./node_modules/gulp/bin/gulp.js install" | ||||
|     "prepublish": "node ./node_modules/bower/bin/bower install && node ./node_modules/gulp/bin/gulp.js install" | ||||
|   }, | ||||
|   "repository": { | ||||
|     "type": "git", | ||||
|   | ||||
| @@ -38,7 +38,6 @@ | ||||
|                   ng-class="{ last:($index + 1) === contextualParents.length }"> | ||||
|                 <mct-representation key="'label'" | ||||
|                                     mct-object="parent" | ||||
|                                     ng-click="parent.getCapability('action').perform('navigate')" | ||||
|                                     class="location-item"> | ||||
|                 </mct-representation> | ||||
|             </span> | ||||
| @@ -50,7 +49,6 @@ | ||||
|                   ng-class="{ last:($index + 1) === primaryParents.length }"> | ||||
|                 <mct-representation key="'label'" | ||||
|                                     mct-object="parent" | ||||
|                                     ng-click="parent.getCapability('action').perform('navigate')" | ||||
|                                     class="location-item"> | ||||
|                 </mct-representation> | ||||
|             </span> | ||||
|   | ||||
| @@ -28,6 +28,16 @@ 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 && | ||||
| @@ -49,7 +59,10 @@ define( | ||||
|  | ||||
|             var removeCheck = navigationService | ||||
|                 .checkBeforeNavigation(function () { | ||||
|                     return "Continuing will cause the loss of any unsaved changes."; | ||||
|                     if (isDirty(domainObject)) { | ||||
|                         return "Continuing will cause the loss of any unsaved changes."; | ||||
|                     } | ||||
|                     return false; | ||||
|                 }); | ||||
|  | ||||
|             $scope.$on('$destroy', function () { | ||||
|   | ||||
| @@ -52,22 +52,8 @@ 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)); | ||||
|                 } | ||||
|                 self.scope.selection = selection; | ||||
|                 self.refreshComposition(selection); | ||||
|             } | ||||
|  | ||||
|             $scope.filterBy = filterBy; | ||||
| @@ -84,19 +70,19 @@ define( | ||||
|         /** | ||||
|          * Gets the composition for the selected object and populates the scope with it. | ||||
|          * | ||||
|          * @param domainObject the selected object | ||||
|          * @param selection the selection object | ||||
|          * @private | ||||
|          */ | ||||
|         ElementsController.prototype.refreshComposition = function (domainObject) { | ||||
|             var refreshTracker = {}; | ||||
|             this.currentRefresh = refreshTracker; | ||||
|         ElementsController.prototype.refreshComposition = function (selection) { | ||||
|             if (!selection[0]) { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             var selectedObjectComposition = selection[0].context.oldItem.useCapability('composition'); | ||||
|  | ||||
|             var selectedObjectComposition = domainObject && domainObject.useCapability('composition'); | ||||
|             if (selectedObjectComposition) { | ||||
|                 selectedObjectComposition.then(function (composition) { | ||||
|                     if (this.currentRefresh === refreshTracker) { | ||||
|                         this.scope.composition = composition; | ||||
|                     } | ||||
|                     this.scope.composition = composition; | ||||
|                 }.bind(this)); | ||||
|             } else { | ||||
|                 this.scope.composition = []; | ||||
|   | ||||
| @@ -88,6 +88,12 @@ define( | ||||
|                     commit("Changes from toolbar."); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             // Avoid attaching scope to this; | ||||
|             // http://errors.angularjs.org/1.2.26/ng/cpws | ||||
|             this.setSelection = function (s) { | ||||
|                 scope.selection = s; | ||||
|             }; | ||||
|             this.clearExposedToolbar = function () { | ||||
|                 // Clear exposed toolbar state (if any) | ||||
|                 if (attrs.toolbar) { | ||||
| @@ -104,7 +110,6 @@ define( | ||||
|             this.toolbar = undefined; | ||||
|             this.toolbarObject = {}; | ||||
|             this.openmct = openmct; | ||||
|             this.scope = scope; | ||||
|  | ||||
|             // If this representation exposes a toolbar, set up watches | ||||
|             // to synchronize with it. | ||||
| @@ -125,23 +130,26 @@ define( | ||||
|         // Represent a domain object using this definition | ||||
|         EditToolbarRepresenter.prototype.represent = function (representation) { | ||||
|             // Get the newest toolbar definition from the view | ||||
|             var definition = (representation || {}).toolbar || {}; | ||||
|             var definition = (representation || {}).toolbar || {}, | ||||
|                 self = this; | ||||
|  | ||||
|             // If we have been asked to expose toolbar state... | ||||
|             if (this.attrs.toolbar) { | ||||
|                 // Initialize toolbar object | ||||
|                 this.toolbar = new EditToolbar(definition, this.commit); | ||||
|                 // Ensure toolbar state is exposed | ||||
|                 this.exposeToolbar(); | ||||
|             // Initialize toolbar (expose object to parent scope) | ||||
|             function initialize(def) { | ||||
|                 // If we have been asked to expose toolbar state... | ||||
|                 if (self.attrs.toolbar) { | ||||
|                     // Initialize toolbar object | ||||
|                     self.toolbar = new EditToolbar(def, self.commit); | ||||
|                     // Ensure toolbar state is exposed | ||||
|                     self.exposeToolbar(); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             // Add toolbar selection to scope. | ||||
|             this.scope.selection = new EditToolbarSelection( | ||||
|                 this.scope, | ||||
|                 this.openmct | ||||
|             ); | ||||
|             // Initialize toolbar to current selection | ||||
|             this.updateSelection(this.scope.selection.all()); | ||||
|             // Expose the toolbar object to the parent scope | ||||
|             initialize(definition); | ||||
|             // Create a selection scope | ||||
|             this.setSelection(new EditToolbarSelection(this.openmct)); | ||||
|             // Initialize toolbar to an empty selection | ||||
|             this.updateSelection([]); | ||||
|         }; | ||||
|  | ||||
|         // Destroy; remove toolbar object from parent scope | ||||
|   | ||||
| @@ -38,37 +38,18 @@ define( | ||||
|          * @memberof platform/commonUI/edit | ||||
|          * @constructor | ||||
|          */ | ||||
|         function EditToolbarSelection($scope, openmct) { | ||||
|         function EditToolbarSelection(openmct) { | ||||
|             this.selection = [{}]; | ||||
|             this.selecting = false; | ||||
|             this.selectedObj = undefined; | ||||
|             this.openmct = openmct; | ||||
|             var self = this; | ||||
|  | ||||
|             function setSelection(selection) { | ||||
|                 var selected = selection[0]; | ||||
|  | ||||
|                 if (selected && selected.context.toolbar) { | ||||
|                     self.select(selected.context.toolbar); | ||||
|             openmct.selection.on('change', function (selection) { | ||||
|                 if (selection[0] && selection[0].context.toolbar) { | ||||
|                     this.select(selection[0].context.toolbar); | ||||
|                 } else { | ||||
|                     self.deselect(); | ||||
|                     this.deselect(); | ||||
|                 } | ||||
|  | ||||
|                 if (selected && selected.context.viewProxy) { | ||||
|                     self.proxy(selected.context.viewProxy); | ||||
|                 } | ||||
|  | ||||
|                 setTimeout(function () { | ||||
|                     $scope.$apply(); | ||||
|                 }); | ||||
|             } | ||||
|  | ||||
|             $scope.$on("$destroy", function () { | ||||
|                 self.openmct.selection.off('change', setSelection); | ||||
|             }); | ||||
|  | ||||
|             this.openmct.selection.on('change', setSelection); | ||||
|             setSelection(this.openmct.selection.get()); | ||||
|             }.bind(this)); | ||||
|         } | ||||
|  | ||||
|         /** | ||||
|   | ||||
| @@ -104,10 +104,10 @@ define( | ||||
|                 mockEditorCapability.isEditContextRoot.andReturn(false); | ||||
|                 mockEditorCapability.dirty.andReturn(false); | ||||
|  | ||||
|                 expect(checkFn()).toBe("Continuing will cause the loss of any unsaved changes."); | ||||
|                 expect(checkFn()).toBe(false); | ||||
|  | ||||
|                 mockEditorCapability.isEditContextRoot.andReturn(true); | ||||
|                 expect(checkFn()).toBe("Continuing will cause the loss of any unsaved changes."); | ||||
|                 expect(checkFn()).toBe(false); | ||||
|  | ||||
|                 mockEditorCapability.dirty.andReturn(true); | ||||
|                 expect(checkFn()) | ||||
|   | ||||
| @@ -29,48 +29,9 @@ define( | ||||
|             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 () { | ||||
|                 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', | ||||
| @@ -82,14 +43,6 @@ define( | ||||
|                     selection: mockSelection | ||||
|                 }; | ||||
|  | ||||
|                 selectable[0] = { | ||||
|                     context: { | ||||
|                         oldItem: mockDomainObject | ||||
|                     } | ||||
|                 }; | ||||
|  | ||||
|                 spyOn(ElementsController.prototype, 'refreshComposition').andCallThrough(); | ||||
|  | ||||
|                 controller = new ElementsController(mockScope, mockOpenMCT); | ||||
|             }); | ||||
|  | ||||
| @@ -122,63 +75,6 @@ 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); | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| ); | ||||
|   | ||||
| @@ -36,7 +36,7 @@ define( | ||||
|             beforeEach(function () { | ||||
|                 mockScope = jasmine.createSpyObj( | ||||
|                     '$scope', | ||||
|                     ['$on', '$watch', '$watchCollection', "commit", "$apply"] | ||||
|                     ['$on', '$watch', '$watchCollection', "commit"] | ||||
|                 ); | ||||
|                 mockElement = {}; | ||||
|                 testAttrs = { toolbar: 'testToolbar' }; | ||||
|   | ||||
| @@ -30,8 +30,7 @@ define( | ||||
|                 otherElement, | ||||
|                 selection, | ||||
|                 mockSelection, | ||||
|                 mockOpenMCT, | ||||
|                 mockScope; | ||||
|                 mockOpenMCT; | ||||
|  | ||||
|             beforeEach(function () { | ||||
|                 testProxy = { someKey: "some value" }; | ||||
| @@ -47,12 +46,7 @@ define( | ||||
|                 mockOpenMCT = { | ||||
|                     selection: mockSelection | ||||
|                 }; | ||||
|                 mockScope = jasmine.createSpyObj('$scope', [ | ||||
|                     '$on', | ||||
|                     '$apply' | ||||
|                 ]); | ||||
|  | ||||
|                 selection = new EditToolbarSelection(mockScope, mockOpenMCT); | ||||
|                 selection = new EditToolbarSelection(mockOpenMCT); | ||||
|                 selection.proxy(testProxy); | ||||
|             }); | ||||
|  | ||||
| @@ -109,20 +103,6 @@ define( | ||||
|                 expect(selection.all()).toEqual([testProxy]); | ||||
|             }); | ||||
|  | ||||
|             it("cleans up selection 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) | ||||
|                 ); | ||||
|             }); | ||||
|  | ||||
|         }); | ||||
|     } | ||||
| ); | ||||
|   | ||||
| @@ -34,7 +34,6 @@ define([ | ||||
|     "./src/controllers/ContextMenuController", | ||||
|     "./src/controllers/ClickAwayController", | ||||
|     "./src/controllers/ViewSwitcherController", | ||||
|     "./src/controllers/BottomBarController", | ||||
|     "./src/controllers/GetterSetterController", | ||||
|     "./src/controllers/SelectorController", | ||||
|     "./src/controllers/ObjectInspectorController", | ||||
| @@ -49,6 +48,7 @@ define([ | ||||
|     "./src/directives/MCTSplitPane", | ||||
|     "./src/directives/MCTSplitter", | ||||
|     "./src/directives/MCTTree", | ||||
|     "./src/directives/MCTIndicators", | ||||
|     "./src/filters/ReverseFilter", | ||||
|     "text!./res/templates/bottombar.html", | ||||
|     "text!./res/templates/controls/action-button.html", | ||||
| @@ -84,7 +84,6 @@ define([ | ||||
|     ContextMenuController, | ||||
|     ClickAwayController, | ||||
|     ViewSwitcherController, | ||||
|     BottomBarController, | ||||
|     GetterSetterController, | ||||
|     SelectorController, | ||||
|     ObjectInspectorController, | ||||
| @@ -99,6 +98,7 @@ define([ | ||||
|     MCTSplitPane, | ||||
|     MCTSplitter, | ||||
|     MCTTree, | ||||
|     MCTIndicators, | ||||
|     ReverseFilter, | ||||
|     bottombarTemplate, | ||||
|     actionButtonTemplate, | ||||
| @@ -275,13 +275,6 @@ define([ | ||||
|                         "$timeout" | ||||
|                     ] | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "BottomBarController", | ||||
|                     "implementation": BottomBarController, | ||||
|                     "depends": [ | ||||
|                         "indicators[]" | ||||
|                     ] | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "GetterSetterController", | ||||
|                     "implementation": GetterSetterController, | ||||
| @@ -395,6 +388,11 @@ define([ | ||||
|                     "key": "mctTree", | ||||
|                     "implementation": MCTTree, | ||||
|                     "depends": ['gestureService'] | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "mctIndicators", | ||||
|                     "implementation": MCTIndicators, | ||||
|                     "depends": ['openmct'] | ||||
|                 } | ||||
|             ], | ||||
|             "constants": [ | ||||
|   | ||||
| @@ -1,91 +1,91 @@ | ||||
| @include keyframes(rotation) { | ||||
|     100% { transform: rotate(360deg); } | ||||
|     100% { @include transform(rotate(360deg)); } | ||||
| } | ||||
|  | ||||
| @include keyframes(rotation-centered) { | ||||
|     0%   { transform: translate(-50%, -50%) rotate(0deg); } | ||||
|     100% { transform: translate(-50%, -50%) rotate(360deg); } | ||||
|     0%   { @include transform(translate(-50%, -50%) rotate(0deg)); } | ||||
|     100% { @include transform(translate(-50%, -50%) rotate(360deg)); } | ||||
| } | ||||
|  | ||||
| @include keyframes(clock-hands) { | ||||
|     0% { transform: translate(-50%, -50%) rotate(0deg); } | ||||
|     100% { transform: translate(-50%, -50%) rotate(360deg);  } | ||||
|     0% { @include transform(translate(-50%, -50%) rotate(0deg)); } | ||||
|     100% { @include transform(translate(-50%, -50%) rotate(360deg));  } | ||||
| } | ||||
|  | ||||
| @include keyframes(clock-hands-sticky) { | ||||
|     0% { | ||||
|         transform: translate(-50%, -50%) rotate(0deg); | ||||
|         @include transform(translate(-50%, -50%) rotate(0deg)); | ||||
|     } | ||||
|     7% { | ||||
|         transform: translate(-50%, -50%) rotate(0deg); | ||||
|         @include transform(translate(-50%, -50%) rotate(0deg)); | ||||
|     } | ||||
|     8% { | ||||
|         transform: translate(-50%, -50%) rotate(30deg); | ||||
|         @include transform(translate(-50%, -50%) rotate(30deg)); | ||||
|     } | ||||
|     15% { | ||||
|         transform: translate(-50%, -50%) rotate(30deg); | ||||
|         @include transform(translate(-50%, -50%) rotate(30deg)); | ||||
|     } | ||||
|     16% { | ||||
|         transform: translate(-50%, -50%) rotate(60deg); | ||||
|         @include transform(translate(-50%, -50%) rotate(60deg)); | ||||
|     } | ||||
|     24% { | ||||
|         transform: translate(-50%, -50%) rotate(60deg); | ||||
|         @include transform(translate(-50%, -50%) rotate(60deg)); | ||||
|     } | ||||
|     25% { | ||||
|         transform: translate(-50%, -50%) rotate(90deg); | ||||
|         @include transform(translate(-50%, -50%) rotate(90deg)); | ||||
|     } | ||||
|     32% { | ||||
|         transform: translate(-50%, -50%) rotate(90deg); | ||||
|         @include transform(translate(-50%, -50%) rotate(90deg)); | ||||
|     } | ||||
|     33% { | ||||
|         transform: translate(-50%, -50%) rotate(120deg); | ||||
|         @include transform(translate(-50%, -50%) rotate(120deg)); | ||||
|     } | ||||
|     40% { | ||||
|         transform: translate(-50%, -50%) rotate(120deg); | ||||
|         @include transform(translate(-50%, -50%) rotate(120deg)); | ||||
|     } | ||||
|     41% { | ||||
|         transform: translate(-50%, -50%) rotate(150deg); | ||||
|         @include transform(translate(-50%, -50%) rotate(150deg)); | ||||
|     } | ||||
|     49% { | ||||
|         transform: translate(-50%, -50%) rotate(150deg); | ||||
|         @include transform(translate(-50%, -50%) rotate(150deg)); | ||||
|     } | ||||
|     50% { | ||||
|         transform: translate(-50%, -50%) rotate(180deg); | ||||
|         @include transform(translate(-50%, -50%) rotate(180deg)); | ||||
|     } | ||||
|     57% { | ||||
|         transform: translate(-50%, -50%) rotate(180deg); | ||||
|         @include transform(translate(-50%, -50%) rotate(180deg)); | ||||
|     } | ||||
|     58% { | ||||
|         transform: translate(-50%, -50%) rotate(210deg); | ||||
|         @include transform(translate(-50%, -50%) rotate(210deg)); | ||||
|     } | ||||
|     65% { | ||||
|         transform: translate(-50%, -50%) rotate(210deg); | ||||
|         @include transform(translate(-50%, -50%) rotate(210deg)); | ||||
|     } | ||||
|     66% { | ||||
|         transform: translate(-50%, -50%) rotate(240deg); | ||||
|         @include transform(translate(-50%, -50%) rotate(240deg)); | ||||
|     } | ||||
|     74% { | ||||
|         transform: translate(-50%, -50%) rotate(240deg); | ||||
|         @include transform(translate(-50%, -50%) rotate(240deg)); | ||||
|     } | ||||
|     75% { | ||||
|         transform: translate(-50%, -50%) rotate(270deg); | ||||
|         @include transform(translate(-50%, -50%) rotate(270deg)); | ||||
|     } | ||||
|     82% { | ||||
|         transform: translate(-50%, -50%) rotate(270deg); | ||||
|         @include transform(translate(-50%, -50%) rotate(270deg)); | ||||
|     } | ||||
|     83% { | ||||
|         transform: translate(-50%, -50%) rotate(300deg); | ||||
|         @include transform(translate(-50%, -50%) rotate(300deg)); | ||||
|     } | ||||
|     90% { | ||||
|         transform: translate(-50%, -50%) rotate(300deg); | ||||
|         @include transform(translate(-50%, -50%) rotate(300deg)); | ||||
|     } | ||||
|     91% { | ||||
|         transform: translate(-50%, -50%) rotate(330deg); | ||||
|         @include transform(translate(-50%, -50%) rotate(330deg)); | ||||
|     } | ||||
|     99% { | ||||
|         transform: translate(-50%, -50%) rotate(330deg); | ||||
|         @include transform(translate(-50%, -50%) rotate(330deg)); | ||||
|     } | ||||
|     100% { | ||||
|         transform: translate(-50%, -50%) rotate(360deg); | ||||
|         @include transform(translate(-50%, -50%) rotate(360deg)); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,11 +0,0 @@ | ||||
| // At the last, hide .l-splash-holder and show .holder-all | ||||
| .l-splash-holder.fadeout { | ||||
|     @include trans-prop-nice($props: opacity, $dur: 1000ms); | ||||
|     opacity: 0; | ||||
|     pointer-events: none; | ||||
| } | ||||
|  | ||||
| .user-environ .holder-all { | ||||
|     opacity: 1; | ||||
|     pointer-events: inherit; | ||||
| } | ||||
| @@ -94,19 +94,19 @@ | ||||
| /********************************************* FLEX STYLES */ | ||||
| .l-flex-row, | ||||
| .l-flex-col { | ||||
|     display: flex; | ||||
|     flex-wrap: nowrap; | ||||
|     @include display(flex); | ||||
|     @include flex-wrap(nowrap); | ||||
|     .flex-elem { | ||||
|         min-height: 0; // Needed to allow element to shrink within parent | ||||
|         position: relative; | ||||
|         &:not(.grows) { | ||||
|             flex: 0 0 auto; | ||||
|             @include flex(0 0 auto); | ||||
|             &.flex-can-shrink { | ||||
|                 flex: 0 1 auto; | ||||
|                 @include flex(0 1 auto); | ||||
|             } | ||||
|         } | ||||
|         &.grows { | ||||
|             flex: 1 1 auto; | ||||
|             @include flex(1 1 auto); | ||||
|         } | ||||
|         &.contents-align-right { | ||||
|             text-align: right; | ||||
| @@ -114,25 +114,25 @@ | ||||
|     } | ||||
|     .flex-container { | ||||
|         // Apply to wrapping elements, mct-includes, etc. | ||||
|         display: flex; | ||||
|         flex-wrap: nowrap; | ||||
|         flex: 1 1 auto; | ||||
|         @include display(flex); | ||||
|         @include flex-wrap(nowrap); | ||||
|         @include flex(1 1 auto); | ||||
|         min-height:0; | ||||
|     } | ||||
| } | ||||
|  | ||||
| .l-flex-row { | ||||
|     flex-direction: row; | ||||
|     &.flex-elem { flex: 1 1 auto; } | ||||
|     @include flex-direction(row); | ||||
|     &.flex-elem { @include flex(1 1 auto); } | ||||
|     > .flex-elem { | ||||
|         min-width: 0; | ||||
|         &.holder:not(:last-child) { margin-right: $interiorMargin; } | ||||
|     } | ||||
|     .flex-container { flex-direction: row; } | ||||
|     .flex-container { @include flex-direction(row); } | ||||
| } | ||||
|  | ||||
| .l-flex-col { | ||||
|     flex-direction: column; | ||||
|     @include flex-direction(column); | ||||
|     > .flex-elem { | ||||
|         min-height: 0; | ||||
|         &.holder:not(:last-child) { margin-bottom: $interiorMarginLg; } | ||||
| @@ -142,15 +142,15 @@ | ||||
|         flex-direction: column; | ||||
|         //overflow: hidden !important; | ||||
|     } | ||||
|     .flex-container { flex-direction: column; } | ||||
|     .flex-container { @include flex-direction(column); } | ||||
| } | ||||
|  | ||||
| .flex-fixed { | ||||
|     flex: 0 0 auto; | ||||
|     @include flex(0 0 auto); | ||||
| } | ||||
|  | ||||
| .flex-justify-end { | ||||
|     justify-content: flex-end; | ||||
|     @include justify-content(flex-end); | ||||
| } | ||||
|  | ||||
| /********************************************* POPUPS */ | ||||
|   | ||||
| @@ -99,7 +99,7 @@ $plotXBarH: 32px; | ||||
| $plotLegendH: 20px; | ||||
| $plotSwatchD: 8px; | ||||
| // 1: Top, 2: right, 3: bottom, 4: left | ||||
| $plotDisplayArea: (0, 0, $plotXBarH, $plotYBarW); | ||||
| $plotDisplayArea: ($plotLegendH + $interiorMargin, 0, $plotXBarH, $plotYBarW); | ||||
| /* min plot height is based on user testing to find minimum useful height */ | ||||
| $plotMinH: 95px; | ||||
| /*************** Bubbles */ | ||||
|   | ||||
| @@ -40,7 +40,7 @@ | ||||
|     * Use https://icomoon.io/app with icomoon-project-openmct-symbols-12px.json | ||||
|     * to generate font files | ||||
|     */ | ||||
|     font-family: 'symbolsfont-12px'; | ||||
|     font-family: 'symbolsfont 12px'; | ||||
|     src: url($dirCommonRes + 'fonts/symbols/openmct-symbols-12px.eot'); | ||||
|     src: url($dirCommonRes + 'fonts/symbols/openmct-symbols-12px.eot?#iefix') format('embedded-opentype'), | ||||
|     url($dirCommonRes + 'fonts/symbols/openmct-symbols-12px.woff') format('woff'), | ||||
| @@ -248,12 +248,6 @@ a.disabled { | ||||
|     color: rgba(#fff, 0.2); | ||||
| } | ||||
|  | ||||
| .comma-list span { | ||||
|     &:not(:first-child) { | ||||
|         &:before { content: ', '; } | ||||
|     } | ||||
| } | ||||
|  | ||||
| .test-stripes { | ||||
|     @include bgDiagonalStripes(); | ||||
| } | ||||
|   | ||||
| @@ -44,12 +44,6 @@ | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| .t-alert-unsynced { | ||||
|     @extend .icon-alert-triangle; | ||||
|     color: $colorPausedBg; | ||||
| } | ||||
|  | ||||
| .bar .ui-symbol { | ||||
| 	display: inline-block; | ||||
| } | ||||
| @@ -83,9 +77,22 @@ | ||||
|             height: auto; width: auto; | ||||
|             position: absolute; | ||||
|             left: 0; top: 0; right: 0; bottom: 20%; | ||||
|             transform-origin: bottom left; | ||||
|             transform: scale(0.3); | ||||
|             @include transform-origin(bottom left); | ||||
|             @include transform(scale(0.3)); | ||||
|             z-index: 2; | ||||
|         } | ||||
|  | ||||
| /*        .t-item-icon-glyph { | ||||
|             &:after { | ||||
|                 color: $colorIconLink; | ||||
|                 content: '\e921'; //$glyph-icon-link; | ||||
|                 height: auto; width: auto; | ||||
|                 position: absolute; | ||||
|                 left: 0; top: 0; right: 0; bottom: 20%; | ||||
|                 @include transform-origin(bottom left); | ||||
|                 @include transform(scale(0.3)); | ||||
|                 z-index: 2; | ||||
|             } | ||||
|         }*/ | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -78,7 +78,7 @@ | ||||
|                 } | ||||
|             } | ||||
|             .form-row { | ||||
|                 align-items: center; | ||||
|                 @include align-items(center); | ||||
|                 border: none !important; | ||||
|                 margin-bottom: 0 !important; | ||||
|                 padding: $interiorMarginSm 0; | ||||
|   | ||||
| @@ -70,17 +70,14 @@ | ||||
| @import "fixed-position"; | ||||
| @import "lists/tabular"; | ||||
| @import "plots/plots-main"; | ||||
| @import "plots/legend"; | ||||
| @import "iframe"; | ||||
| @import "views"; | ||||
| @import "items/item"; | ||||
| @import "mobile/item"; | ||||
|  | ||||
|  | ||||
| /********************************* TO BE MOVED */ | ||||
| @import "autoflow"; | ||||
| @import "features/imagery"; | ||||
| @import "features/time-display"; | ||||
| @import "widgets"; | ||||
|  | ||||
| /********************************* APP STARTUP */ | ||||
| @import "app-start"; | ||||
|   | ||||
| @@ -382,7 +382,7 @@ | ||||
|     /* This doesn't work on an element inside an element with absolute positioning that has height: auto */ | ||||
|     //position: relative; | ||||
|     top: 50%; | ||||
|     transform: translateY(-50%); | ||||
|     @include transform(translateY(-50%)); | ||||
| } | ||||
|  | ||||
| @mixin verticalCenterBlock($holderH, $itemH) { | ||||
|   | ||||
| @@ -22,8 +22,8 @@ | ||||
|  | ||||
| // mct-representation surrounding an object-label key="'label'" | ||||
| .rep-object-label { | ||||
|     flex-direction: row; | ||||
|     flex: 1 1 auto; | ||||
|     @include flex-direction(row); | ||||
|     @include flex(1 1 auto); | ||||
|     height: inherit; | ||||
|     line-height: inherit; | ||||
|     min-width: 0; | ||||
|   | ||||
| @@ -5,7 +5,6 @@ | ||||
| } | ||||
|  | ||||
| .l-view-section { | ||||
|     //@include test(orange, 0.1); | ||||
| 	@include absPosDefault(0); | ||||
| 	h2 { | ||||
| 		color: #fff; | ||||
|   | ||||
| @@ -55,7 +55,7 @@ | ||||
|  | ||||
| .widget-rule-header { | ||||
|     @extend .l-flex-row; | ||||
|     align-items: center; | ||||
|     @include align-items(center); | ||||
|     margin-bottom: $interiorMargin; | ||||
|     > .flex-elem { | ||||
|         &:not(:first-child) { | ||||
| @@ -103,7 +103,7 @@ | ||||
| .l-compact-form label { | ||||
|     $ruleLabelW: 40%; | ||||
|     $ruleLabelMaxW: 150px; | ||||
|     display: flex; | ||||
|     @include display(flex); | ||||
|     max-width: $ruleLabelMaxW; | ||||
|     width: $ruleLabelW; | ||||
| } | ||||
| @@ -177,8 +177,8 @@ | ||||
|         ul { | ||||
|             &:last-child { margin: 0; } | ||||
|             li { | ||||
|                 align-items: flex-start; | ||||
|                 flex-wrap: nowrap; | ||||
|                 @include align-items(flex-start); | ||||
|                 @include flex-wrap(nowrap); | ||||
|                 line-height: 230%; // Provide enough space when controls wrap | ||||
|                 padding: 2px 0; | ||||
|                 &:not(.widget-rule-header) { | ||||
| @@ -233,7 +233,7 @@ | ||||
|  | ||||
| .l-widget-thumb-wrapper { | ||||
|     @extend .l-flex-row; | ||||
|     align-items: center; | ||||
|     @include align-items(center); | ||||
|     > span { display: block; } | ||||
|     .grippy-holder, | ||||
|     .view-control { | ||||
| @@ -243,18 +243,18 @@ | ||||
|     } | ||||
|  | ||||
|     .widget-thumb { | ||||
|         flex: 1 1 auto; | ||||
|         @include flex(1 1 auto); | ||||
|         width: 100%; | ||||
|     } | ||||
| } | ||||
|  | ||||
| .rule-title { | ||||
|     flex: 0 1 auto; | ||||
|     @include flex(0 1 auto); | ||||
|     color: pullForward($colorBodyFg, 50%); | ||||
| } | ||||
|  | ||||
| .rule-description { | ||||
|     flex: 1 1 auto; | ||||
|     @include flex(1 1 auto); | ||||
|     @include ellipsize(); | ||||
|     color: pushBack($colorBodyFg, 20%); | ||||
| } | ||||
|   | ||||
| @@ -150,26 +150,6 @@ | ||||
|     } | ||||
| } | ||||
|  | ||||
| /******************************************************** VIEW CONTROLS */ | ||||
| // Expand/collapse > and v arrows, used in tree and plot legend | ||||
| // Moved this over from a tree-only context 5/18/17 | ||||
|  | ||||
| .view-control { | ||||
|     @extend .ui-symbol; | ||||
|     cursor: pointer; | ||||
|     height: 1em; width: 1em; | ||||
|     line-height: inherit; | ||||
|     &:before { | ||||
|         position: absolute; | ||||
|         @include trans-prop-nice(transform, 100ms); | ||||
|         content: $glyph-icon-arrow-right; | ||||
|         @include transform-origin(center); | ||||
|     } | ||||
|     &.expanded:before { | ||||
|         @include transform(rotate(90deg)); | ||||
|     } | ||||
| } | ||||
|  | ||||
| /******************************************************** CUSTOM CHECKBOXES */ | ||||
| label.checkbox.custom, | ||||
| label.radio.custom { | ||||
| @@ -356,7 +336,7 @@ input[type="text"].s-input-inline, | ||||
|         } | ||||
|     } | ||||
|     &:before { | ||||
|         transform: translateY(-50%); | ||||
|         @include transform(translateY(-50%)); | ||||
|         color: rgba($colorInvokeMenu, percentToDecimal($contrastInvokeMenuPercent)); | ||||
|         display: block; | ||||
|         pointer-events: none; | ||||
| @@ -434,7 +414,7 @@ input[type="text"].s-input-inline, | ||||
|  | ||||
|     .context-available { | ||||
|         font-size: 0.7em; | ||||
|         flex: 0 0 1; | ||||
|         @include flex(0 0 1); | ||||
|     } | ||||
|  | ||||
|     .t-object-alert { | ||||
| @@ -675,14 +655,14 @@ input[type="range"] { | ||||
| .l-calendar { | ||||
|     $colorMuted: pushBack($colorMenuFg, 30%); | ||||
|     ul.l-cal-row { | ||||
|         display: flex; | ||||
|         flex-flow: row nowrap; | ||||
|         @include display(flex); | ||||
|         @include flex-flow(row nowrap); | ||||
|         margin-top: 1px; | ||||
|         &:first-child { | ||||
|             margin-top: 0; | ||||
|         } | ||||
|         li { | ||||
|             flex: 1 0; | ||||
|             @include flex(1 0); | ||||
|             margin-left: 1px; | ||||
|             padding: $interiorMargin; | ||||
|             text-align: center; | ||||
| @@ -763,10 +743,10 @@ textarea { | ||||
|     &:before { | ||||
|         position: absolute; | ||||
|         @include trans-prop-nice(transform, 100ms); | ||||
|         transform-origin: center; | ||||
|         @include transform-origin(center); | ||||
|     } | ||||
|     &.expanded:before { | ||||
|         transform: rotate(90deg); | ||||
|         @include transform(rotate(90deg)); | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -77,14 +77,6 @@ | ||||
|     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); | ||||
|   | ||||
| @@ -157,16 +157,16 @@ | ||||
| 	$lh: $ueFooterH - ($m*2) - 1; | ||||
| 	box-sizing: border-box; | ||||
| 	@include ellipsize(); | ||||
| 	display: flex; | ||||
| 	flex-direction: row; | ||||
| 	align-items: center; | ||||
| 	@include display(flex); | ||||
| 	@include flex-direction(row); | ||||
| 	@include align-items(center); | ||||
| 	position: absolute; | ||||
| 	top: $m; right: auto; bottom: $m; left: 50%; | ||||
| 	height: auto; width: auto; | ||||
| 	line-height: $lh; | ||||
| 	max-width: 300px; | ||||
| 	padding: 0 $interiorMargin 0 $interiorMargin; | ||||
| 	transform: translateX(-50%); | ||||
| 	@include transform(translateX(-50%)); | ||||
|  | ||||
| 	&.minimized { | ||||
|         @include transition-property(left, opacity); | ||||
| @@ -185,7 +185,7 @@ | ||||
|     } | ||||
|  | ||||
| 	.banner-elem { | ||||
| 		flex: 0 1 auto; | ||||
| 		@include flex(0 1 auto); | ||||
| 		margin-left: $interiorMargin; | ||||
| 	} | ||||
| 	a { | ||||
| @@ -250,14 +250,14 @@ | ||||
| // Archetypal message | ||||
| .l-message { | ||||
|     $iconW: 32px; | ||||
|     display: flex; | ||||
|     flex-direction: row; | ||||
|     align-items: stretch; | ||||
|     @include display(flex); | ||||
|     @include flex-direction(row); | ||||
|     @include align-items(stretch); | ||||
|     padding: $interiorMarginLg; | ||||
|  | ||||
|     &:before { | ||||
|         // Icon | ||||
|         flex: 0 1 auto; | ||||
|         @include flex(0 1 auto); | ||||
|         @include txtShdw($shdwStatusIc); | ||||
|         @extend .icon-bell; | ||||
|         color: $colorStatusDefault; | ||||
| @@ -283,9 +283,9 @@ | ||||
|  | ||||
|  | ||||
| .w-message-contents { | ||||
|     flex: 1 1 auto; | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     @include flex(1 1 auto); | ||||
|     @include display(flex); | ||||
|     @include flex-direction(column); | ||||
|  | ||||
|     > div, | ||||
|     > span { | ||||
| @@ -294,7 +294,7 @@ | ||||
|     } | ||||
|  | ||||
|     .message-body { | ||||
|         flex: 1 1 100%; | ||||
|         @include flex(1 1 100%); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -331,8 +331,8 @@ | ||||
| // In a list | ||||
| .t-message-list { | ||||
|     @include absPosDefault(); | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     @include display(flex); | ||||
|     @include flex-direction(column); | ||||
|  | ||||
|     > div, | ||||
|     > span { | ||||
| @@ -340,7 +340,7 @@ | ||||
|     } | ||||
|  | ||||
|     .w-messages { | ||||
|         flex: 1 1 100%; | ||||
|         @include flex(1 1 100%); | ||||
|         overflow-y: auto; | ||||
|         padding-right: $interiorMargin; | ||||
|     } | ||||
| @@ -360,7 +360,7 @@ | ||||
| @include phonePortrait { | ||||
|     .t-message-single .l-message, | ||||
|     .t-message-single.l-message { | ||||
|         flex-direction: column; | ||||
|         @include flex-direction(column); | ||||
|         &:before { | ||||
|             margin-right: 0; | ||||
|             margin-bottom: $interiorMarginLg; | ||||
| @@ -398,6 +398,10 @@ body.desktop .t-message-list { | ||||
|     .object-header { | ||||
|         .t-object-alert { | ||||
|             display: inline; | ||||
|             &.t-alert-unsynced { | ||||
|                 @extend .icon-alert-triangle; | ||||
|                 color: $colorPausedBg; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -20,70 +20,53 @@ | ||||
|  * 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 { | ||||
|     $d: 16px; | ||||
|     $m: 1; | ||||
|     $colorsPerRow: 10; | ||||
|     display: flex; | ||||
|     flex-wrap: wrap; | ||||
|     line-height: $d; | ||||
|     width: ($d * $colorsPerRow) + ($m * $colorsPerRow); | ||||
| 	.l-palette-row { | ||||
| 		@include clearfix; | ||||
| 		line-height: $d; | ||||
| 		width: ($d * $colorsPerRow) + ($m * $colorsPerRow); | ||||
|  | ||||
|     &.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-option-row { | ||||
|             margin-bottom: $interiorMargin; | ||||
|             .s-palette-item { | ||||
|                 border-color: $colorPaletteFg; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| 		.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; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -15,7 +15,7 @@ | ||||
|         margin-bottom: $interiorMargin; | ||||
| 	} | ||||
| 	.l-image-main-controlbar { | ||||
|         &.l-flex-row { align-items: center; } | ||||
|         &.l-flex-row { @include align-items(center); } | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -34,10 +34,13 @@ | ||||
| 	} | ||||
| 	 | ||||
| 	.btns-add-remove { | ||||
| //		background: rgba(#ff0000, 0.3);; | ||||
| 		margin-top: 150px; | ||||
| 		.s-button { | ||||
| 			display: block; | ||||
| 			//font-size: 1.5em; | ||||
| 			margin-bottom: $interiorMargin; | ||||
| 			//padding: 10px; | ||||
| 			text-align: center; | ||||
| 		} | ||||
| 	} | ||||
|   | ||||
| @@ -45,7 +45,7 @@ | ||||
|         &.grows { | ||||
|             .l-section-body, | ||||
|             .form-row { | ||||
|                 flex: 1 1 auto; | ||||
|                 @include flex(1 1 auto); | ||||
|                 .wrapper { | ||||
|                     height: 100%; | ||||
|                 } | ||||
| @@ -87,7 +87,7 @@ | ||||
| 		.controls { | ||||
|             order: 2; | ||||
|             position: relative; | ||||
|             flex: 1 1 auto; | ||||
|             @include flex(1 1 auto); | ||||
|  | ||||
| 			.l-composite-control { | ||||
| 				&.l-checkbox { | ||||
| @@ -124,16 +124,16 @@ | ||||
|     >.label, | ||||
|     >.controls { | ||||
|         line-height: inherit; | ||||
|         min-height: inherit; | ||||
|         min-height: inherit;; | ||||
|     } | ||||
|     >.label { | ||||
|         flex: 1 1 auto; | ||||
|         @include flex(1 1 auto); | ||||
|         min-width: 0; | ||||
|         width: auto; | ||||
|         order: 2; | ||||
|     } | ||||
|     >.controls { | ||||
|         flex: 0 0 auto; | ||||
|         @include flex(0 0 auto); | ||||
|         margin-right: $interiorMargin; | ||||
|         order: 1; | ||||
|     } | ||||
| @@ -141,7 +141,7 @@ | ||||
|  | ||||
| .l-controls-under.l-flex-row { | ||||
|     // Change to use column layout | ||||
|     flex-direction: column; | ||||
|     @include flex-direction(column); | ||||
|     .flex-elem { | ||||
|         margin-bottom: $interiorMarginLg; | ||||
|     } | ||||
| @@ -190,19 +190,19 @@ | ||||
|     ul { | ||||
|         margin-bottom: $interiorMarginLg; | ||||
|         li { | ||||
|             display: flex; | ||||
|             flex-wrap: wrap; | ||||
|             align-items: center; | ||||
|             @include display(flex); | ||||
|             @include flex-wrap(wrap); | ||||
|             @include align-items(center); | ||||
|             label, | ||||
|             .control { | ||||
|                 display: flex; | ||||
|                 @include display(flex); | ||||
|             } | ||||
|             label { | ||||
|                 line-height: inherit; | ||||
|                 width: $labelW; | ||||
|             } | ||||
|             .controls { | ||||
|                 flex-grow: 1; | ||||
|                 @include flex-grow(1); | ||||
|                 margin-left: $interiorMargin; | ||||
|                 input[type="text"], | ||||
|                 input[type="search"], | ||||
| @@ -232,14 +232,14 @@ | ||||
|  | ||||
|             &.controls-first { | ||||
|                 .control { | ||||
|                     flex-grow: 0; | ||||
|                     @include flex-grow(0); | ||||
|                     margin-right: $interiorMargin; | ||||
|                     min-width: 0; | ||||
|                     order: 1; | ||||
|                     width: auto; | ||||
|                 } | ||||
|                 label { | ||||
|                     flex-grow: 1; | ||||
|                     @include flex-grow(1); | ||||
|                     order: 2; | ||||
|                     width: auto; | ||||
|                 } | ||||
|   | ||||
| @@ -101,7 +101,7 @@ | ||||
|         line-height: inherit; | ||||
|         position: absolute; | ||||
|         top: 50%; | ||||
|         transform: translateY(-50%); | ||||
|         @include transform(translateY(-50%)); | ||||
|         z-index: 1; | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -20,7 +20,7 @@ | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
| @mixin  spinner($b: 5px, $c: $colorKey) { | ||||
|     transform-origin: center; | ||||
|     @include transform-origin(center); | ||||
|     @include animation-name(rotation-centered); | ||||
|     @include animation-duration(0.5s); | ||||
|     @include animation-iteration-count(infinite); | ||||
|   | ||||
| @@ -85,14 +85,14 @@ | ||||
| 	            z-index: 1; | ||||
|                 .item-type, | ||||
|                 .t-item-icon { | ||||
|                     transform: translateX(-50%) translateY(-55%); | ||||
|                     @include transform(translateX(-50%) translateY(-55%)); | ||||
|                     position: absolute; | ||||
|                     top: 50%; left: 50%; | ||||
|                     font-size: $iconD * 0.95; | ||||
|                     &.l-icon-link { | ||||
|                         .t-item-icon-glyph { | ||||
|                             &:after { | ||||
|                                 transform: scale(0.25); | ||||
|                                 @include transform(scale(0.25)); | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|   | ||||
| @@ -138,7 +138,7 @@ body.phone.portrait { | ||||
|         } | ||||
|         .pane.right.items { | ||||
|             left: 0 !important; | ||||
|             transform: translateX($proporMenuOnly); | ||||
|             @include transform(translateX($proporMenuOnly)); | ||||
|             .holder-object-and-inspector { | ||||
|                 opacity: 0; | ||||
|             } | ||||
|   | ||||
| @@ -40,10 +40,10 @@ body.touch { | ||||
|                 &:before { | ||||
|                     content: $glyph-icon-arrow-down; | ||||
|                     left: 50%; | ||||
|                     transform: translateX(-50%) rotate(-90deg); | ||||
|                     @include transform(translateX(-50%) rotate(-90deg)); | ||||
|                 } | ||||
|                 &.expanded:before { | ||||
|                     transform: translateX(-50%) rotate(0deg); | ||||
|                     @include transform(translateX(-50%) rotate(0deg)); | ||||
|                 } | ||||
|             } | ||||
| 		} | ||||
|   | ||||
| @@ -89,7 +89,7 @@ | ||||
|         > .abs.outer-holder { | ||||
|             @include desktopandtablet { | ||||
|                 $max: 1280px; | ||||
|                 transform: translate(-50%, -50%); | ||||
|                 @include transform(translateX(-50%) translateY(-50%)); | ||||
|                 border-radius: $overlayCr; | ||||
|                 top: 50%; right: auto; bottom: auto; left: 50%; | ||||
|                 width: 70%; height: 70%; | ||||
| @@ -101,7 +101,7 @@ | ||||
|  | ||||
|                 .editor .form .form-row.l-flex-row  { | ||||
|                     // Display elements in a columnar view | ||||
|                     flex-direction: column; | ||||
|                     @include flex-direction(column); | ||||
|                     > .flex-elem { | ||||
|                         &:not(:first-child) { | ||||
|                             margin-top: $interiorMargin; | ||||
|   | ||||
| @@ -1,208 +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. | ||||
|  *****************************************************************************/ | ||||
| .gl-plot { | ||||
|     .gl-plot-legend { | ||||
|         min-height: $plotLegendH; | ||||
|  | ||||
|         .view-control { | ||||
|             font-size: 1em; | ||||
|             margin-right: $interiorMarginSm; | ||||
|         } | ||||
|  | ||||
|         table { | ||||
|             table-layout: fixed; | ||||
|             tr { | ||||
|                 display: table-row; | ||||
|             } | ||||
|             th, | ||||
|             td { | ||||
|                 @include ellipsize(); // Note: this won't work if table-layout uses anything other than fixed. | ||||
|                 display: table-cell; | ||||
|                 padding: 1px 3px; // Tighter than standard tabular padding | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         &.hover-on-plot { | ||||
|             // User is hovering over the plot to get a value at a point | ||||
|             .hover-value-enabled { | ||||
|                 background-color: $legendHoverValueBg; | ||||
|                 border-radius: $smallCr; | ||||
|                 padding: 0 $interiorMarginSm; | ||||
|                 &:before { | ||||
|                     opacity: 0.5; | ||||
|                 } | ||||
|                 &.cursor-hover, | ||||
|                 .value-to-display-nearestTimestamp, | ||||
|                 .value-to-display-nearestValue | ||||
|                 { | ||||
|                     @extend .icon-crosshair-12px; | ||||
|                     &:before { | ||||
|                         font-size: 9px; | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 &.value-to-display-min:before { | ||||
|                     content: 'MIN '; | ||||
|                 } | ||||
|                 &.value-to-display-max:before { | ||||
|                     content: 'MAX '; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     &.plot-legend-collapsed .plot-wrapper-expanded-legend { display: none; } | ||||
|     &.plot-legend-expanded .plot-wrapper-collapsed-legend { display: none; } | ||||
|  | ||||
|     /***************** GENERAL STYLES, ALL STATES */ | ||||
|     .plot-legend-item { | ||||
|         // General styles for legend items, both expanded and collapsed legend states | ||||
|         .plot-series-color-swatch { | ||||
|             border-radius: $smallCr; | ||||
|             border: 1px solid $colorBodyBg; | ||||
|             display: inline-block; | ||||
|             height: $plotSwatchD; | ||||
|             width: $plotSwatchD; | ||||
|         } | ||||
|         .plot-series-name { | ||||
|             display: inline; | ||||
|         } | ||||
|  | ||||
|         .plot-series-value { | ||||
|             @include ellipsize(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /***************** GENERAL STYLES, COLLAPSED */ | ||||
|     &.plot-legend-collapsed { | ||||
|         // .plot-legend-item is a span of spans. | ||||
|         &.plot-legend-top .gl-plot-legend { margin-bottom: $interiorMargin; } | ||||
|         &.plot-legend-bottom .gl-plot-legend { margin-top: $interiorMargin; } | ||||
|         &.plot-legend-right .gl-plot-legend { margin-left: $interiorMargin; } | ||||
|         &.plot-legend-left .gl-plot-legend { margin-right: $interiorMargin; } | ||||
|  | ||||
|         .plot-legend-item { | ||||
|             display: flex; | ||||
|             align-items: center; | ||||
|             &:not(:first-child) { | ||||
|                 margin-left: $interiorMarginLg; | ||||
|             } | ||||
|             .plot-series-swatch-and-name, | ||||
|             .plot-series-value { | ||||
|                 @include ellipsize(); | ||||
|                 flex: 1 1 auto; | ||||
|             } | ||||
|  | ||||
|             .plot-series-swatch-and-name { | ||||
|                 margin-right: $interiorMarginSm; | ||||
|             } | ||||
|  | ||||
|             .plot-series-value { | ||||
|                 text-align: left; | ||||
|                 width: 170px; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /***************** GENERAL STYLES, EXPANDED */ | ||||
|     &.plot-legend-expanded { | ||||
|         .gl-plot-legend { | ||||
|             max-height: 70%; | ||||
|         } | ||||
|  | ||||
|         .plot-wrapper-expanded-legend { | ||||
|             overflow-y: auto; | ||||
|         } | ||||
|  | ||||
|         &.plot-legend-top .gl-plot-legend { | ||||
|             margin-bottom: $interiorMargin; | ||||
|         } | ||||
|         &.plot-legend-bottom .gl-plot-legend { | ||||
|             margin-top: $interiorMargin; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /***************** TOP OR BOTTOM */ | ||||
|     &.plot-legend-top, | ||||
|     &.plot-legend-bottom { | ||||
|         // General styles when legend is on the top or bottom | ||||
|         @extend .l-flex-col; | ||||
|         &.plot-legend-collapsed { | ||||
|             // COLLAPSED ON TOP OR BOTTOM | ||||
|             .plot-wrapper-collapsed-legend { | ||||
|                 display: flex; | ||||
|                 flex: 1 1 auto; | ||||
|                 overflow: hidden; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /***************** EITHER SIDE */ | ||||
|     &.plot-legend-left, | ||||
|     &.plot-legend-right { | ||||
|         @extend .l-flex-row; | ||||
|         // If the legend is expanded, use flex-col instead so that the legend gets the width it needs. | ||||
|         &.plot-legend-expanded { | ||||
|             // EXPANDED, ON EITHER SIDE | ||||
|             @extend .l-flex-col; | ||||
|         } | ||||
|  | ||||
|         &.plot-legend-collapsed { | ||||
|             // COLLAPSED, ON EITHER SIDE | ||||
|             .gl-plot-legend { | ||||
|                 max-height: inherit; | ||||
|                 width: 25%; | ||||
|             } | ||||
|             .plot-wrapper-collapsed-legend { | ||||
|                 display: flex; | ||||
|                 flex-flow: column nowrap; | ||||
|                 min-width: 0; | ||||
|                 flex: 1 1 auto; | ||||
|                 overflow-y: auto; | ||||
|             } | ||||
|             .plot-legend-item { | ||||
|                 margin-bottom: 1px; | ||||
|                 margin-left: 0; | ||||
|                 flex-wrap: wrap; | ||||
|                 .plot-series-swatch-and-name { | ||||
|                     flex: 0 1 auto; | ||||
|                     min-width: 20%; | ||||
|                 } | ||||
|                 .plot-series-value { | ||||
|                     flex: 0 1 auto; | ||||
|                     width: auto; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /***************** ON BOTTOM OR RIGHT */ | ||||
|     &.plot-legend-right:not(.plot-legend-expanded), | ||||
|     &.plot-legend-bottom { | ||||
|         .gl-plot-legend { | ||||
|             order: 2; | ||||
|         } | ||||
|         .plot-wrapper-axis-and-display-area { | ||||
|             order: 1; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -20,64 +20,18 @@ | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
| .abs.holder-plot { | ||||
|     right: $interiorMargin; // Fend off the scrollbar when less than min-height; | ||||
|     .t-object-alert.t-alert-unsynced { | ||||
|         display: none; | ||||
|     } | ||||
|     // Fend off the scrollbar when less than min-height; | ||||
|     right: $interiorMargin; | ||||
| } | ||||
|  | ||||
| /********************************************* STACKED PLOT LAYOUT */ | ||||
| .t-plot-stacked { | ||||
|     .l-view-section { | ||||
|         //  Make this a flex container | ||||
|         display: flex; | ||||
|         flex-flow: column nowrap; | ||||
|         .gl-plot.child-frame { | ||||
|             mct-plot {  | ||||
|                 display: flex;  | ||||
|                 flex: 1 1 auto; | ||||
|                 height: 100%; | ||||
|                 position: relative; | ||||
|             } | ||||
|             flex: 1 1 auto; | ||||
|             &:not(:first-child) { | ||||
|                 margin-top: $interiorMargin; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     .s-status-timeconductor-unsynced .holder-plot { | ||||
|         .t-object-alert.t-alert-unsynced { | ||||
|             display: block; | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
| .gl-plot { | ||||
| 	color: $colorPlotFg; | ||||
|     display: flex; | ||||
| 	font-size: 0.7rem; | ||||
| 	position: relative; | ||||
| 	width: 100%; | ||||
| 	height: 100%; | ||||
|     min-height: $plotMinH; | ||||
|  | ||||
|     /********************************************* AXIS AND DISPLAY AREA */ | ||||
|     .plot-wrapper-axis-and-display-area { | ||||
|         margin-top: $interiorMargin; // Keep the top tick label from getting clipped | ||||
|         position: relative; | ||||
|         flex: 1 1 auto; | ||||
|         .t-object-alert { | ||||
|             position: absolute; | ||||
|             display: block; | ||||
|             font-size: 1.5em; | ||||
|             top: $interiorMarginSm; left: $interiorMarginSm; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     .gl-plot-wrapper-display-area-and-x-axis { | ||||
|         // Holds the plot area and the X-axis only | ||||
|         position: absolute; | ||||
| @@ -95,6 +49,7 @@ | ||||
|         } | ||||
|  | ||||
|         .gl-plot-axis-area.gl-plot-x { | ||||
|             //@include test(green); | ||||
|             top: auto; | ||||
|             right: 0; | ||||
|             bottom: 0; | ||||
| @@ -108,7 +63,7 @@ | ||||
| 	.gl-plot-axis-area { | ||||
| 		position: absolute; | ||||
| 		&.gl-plot-y { | ||||
| 			top: nth($plotDisplayArea, 1); | ||||
| 			top: $plotLegendH + $interiorMargin; | ||||
| 			right: auto; | ||||
| 			bottom: nth($plotDisplayArea, 3); | ||||
| 			left: 0; | ||||
| @@ -152,8 +107,8 @@ | ||||
| 		&.l-plot-y-label { | ||||
| 			$x: -50%; | ||||
| 			$r: -90deg; | ||||
| 			transform-origin: 50% 0; | ||||
| 			transform: translateX($x) rotate($r); | ||||
| 			@include transform-origin(50% 0); | ||||
| 			@include transform(translateX($x) rotate($r)); | ||||
| 			display: inline-block; | ||||
| 			margin-left: $interiorMargin; // Kick off the left edge | ||||
| 			left: 0; | ||||
| @@ -172,13 +127,13 @@ | ||||
| 	} | ||||
|  | ||||
|     .gl-plot-x-options { | ||||
|         transform: translateX(-50%); | ||||
|         @include transform(translateX(-50%)); | ||||
|         bottom: 0; | ||||
|         left: 50%; | ||||
|     } | ||||
|  | ||||
|     .gl-plot-y-options { | ||||
|         transform: translateY(-50%); | ||||
|         @include transform(translateY(-50%)); | ||||
|         min-width: 150px; // Need this due to enclosure of .select | ||||
|         top: 50%; | ||||
|         left: $plotYLabelW + $interiorMargin * 2; | ||||
| @@ -203,6 +158,17 @@ | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	.gl-plot-legend { | ||||
| 		position: absolute; | ||||
| 		top: 0; | ||||
| 		right: 0; | ||||
| 		bottom: auto; | ||||
| 		left: 0; | ||||
| 		height: $plotLegendH; | ||||
| 		overflow-x: hidden; | ||||
| 		overflow-y: auto; | ||||
| 	} | ||||
|  | ||||
| 	/****************************** Limits and Out-of-Bounds data */ | ||||
|  | ||||
| 	.l-limit-bar, | ||||
| @@ -269,6 +235,39 @@ | ||||
|     border: 1px solid $colorPlotAreaBorder; | ||||
| } | ||||
|  | ||||
| .gl-plot-legend, | ||||
| .legend { | ||||
| 	.plot-legend-item, | ||||
| 	.legend-item { | ||||
| 		display: inline-block; | ||||
| 		margin-right: $interiorMarginLg; | ||||
| 		margin-bottom: $interiorMarginSm; | ||||
| 		span { | ||||
| 			vertical-align: middle; | ||||
| 		} | ||||
| 		.plot-color-swatch, | ||||
| 		.color-swatch { | ||||
| 			border-radius: 2px; | ||||
| 			display: inline-block; | ||||
| 			height: $plotSwatchD; | ||||
| 			width: $plotSwatchD; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| .gl-plot-legend { | ||||
| 	.plot-legend-item { | ||||
| 		border-radius: $smallCr; | ||||
| 		line-height: 1.5em; | ||||
| 		padding: 0px $itemPadLR; | ||||
| 		.plot-color-swatch { | ||||
| 			border: 1px solid $colorBodyBg; | ||||
| 			height: $plotSwatchD + 1; | ||||
| 			width: $plotSwatchD + 1; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| .tick { | ||||
| 	position: absolute; | ||||
| 	border: 0 $colorPlotHash solid; | ||||
|   | ||||
| @@ -152,7 +152,7 @@ | ||||
|             opacity: 1; | ||||
|         } | ||||
| 		.load-more-button { | ||||
|             transform: translateX(-50%); | ||||
|             @include transform(translateX(-50%)); | ||||
|             display: inline-block; | ||||
| 			margin-top: $interiorMargin; | ||||
|             padding: 0 $interiorMarginLg; | ||||
|   | ||||
| @@ -32,7 +32,11 @@ body, html { | ||||
| } | ||||
|  | ||||
| .l-splash-holder { | ||||
|     // Main outer holder for splash. | ||||
|     // Main outer holder. | ||||
|     @include transition-property(opacity); | ||||
|     @include transition-duration(500ms); | ||||
|     @include transition-timing-function(ease-in-out); | ||||
|     @include transition-delay(1s); | ||||
|     position: absolute; | ||||
|     top: 0; | ||||
|     right: 0; | ||||
| @@ -40,18 +44,16 @@ body, html { | ||||
|     left: 0; | ||||
|     z-index: 10000; | ||||
|     opacity: 1; | ||||
|     &.fadeout { | ||||
|         opacity: 0; | ||||
|         pointer-events: none; | ||||
|     } | ||||
|     .l-splash { | ||||
|         // The splash element. | ||||
|         @include splashElem(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| .user-environ .holder-all { | ||||
|     // Gets shown again by main CSS, once loaded | ||||
|     opacity: 0; | ||||
|     pointer-events: none; | ||||
| } | ||||
|  | ||||
| @media only screen and (max-device-width: 767px) { | ||||
|     .l-splash-holder .l-splash { | ||||
|         @include splashElem(0); | ||||
|   | ||||
| @@ -23,7 +23,7 @@ | ||||
| ul.tree { | ||||
|     @include menuUlReset(); | ||||
|     @include user-select(none); | ||||
|     > li { | ||||
|     li { | ||||
|         display: block; | ||||
|         position: relative; | ||||
|     } | ||||
| @@ -53,10 +53,12 @@ ul.tree { | ||||
|     .view-control { | ||||
|         color: $colorItemTreeVC; | ||||
|         margin-right: $interiorMargin; | ||||
|         height: 100%; | ||||
|         line-height: inherit; | ||||
|         width: $treeVCW; | ||||
|         &:before { display: block; } | ||||
|         &.no-children { | ||||
|             &:before { display: none; } | ||||
|         &:before { display: none; } | ||||
|         &.has-children { | ||||
|             &:before { display: block; } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -23,6 +23,7 @@ | ||||
|     $ohH: $btnFrameH; | ||||
|     $bc: $colorInteriorBorder; | ||||
|     &.child-frame.panel { | ||||
|         border: 1px solid transparent; | ||||
|         z-index: 0; // Needed to prevent child-frame controls from showing through when another child-frame is above | ||||
|         &:not(.no-frame) { | ||||
|             background: $colorBodyBg; | ||||
| @@ -90,7 +91,7 @@ | ||||
|  | ||||
|     &.no-frame { | ||||
|         background: transparent !important; | ||||
|         border-color: transparent; | ||||
|         border: none; | ||||
|         .object-browse-bar .right { | ||||
|             $m: 0; | ||||
|             background: rgba(black, 0.3); | ||||
| @@ -142,7 +143,7 @@ | ||||
|         .l-hyperlink.s-button { | ||||
|             .label { | ||||
|                 @include ellipsize(); | ||||
|                 transform: translateY(-50%); | ||||
|                 @include transform(translateY(-50%)); | ||||
|                 padding: 0 $interiorMargin; | ||||
|                 position: absolute; | ||||
|                 min-width: 0; | ||||
|   | ||||
| @@ -159,20 +159,20 @@ body.desktop .pane .mini-tab-icon.toggle-pane { | ||||
|  | ||||
|     &.toggle-tree.anchor-left { | ||||
|         left: 0; | ||||
|         transform: translateX(-1 * $paneExpandedOffset); | ||||
|         @include transform(translateX(-1 * $paneExpandedOffset)); | ||||
|         &.collapsed { | ||||
|             transform: translateX(-1 * $ueCollapsedPaneEdgeM); | ||||
|             @include transform(translateX(-1 * $ueCollapsedPaneEdgeM)); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     &.toggle-inspect.anchor-right { | ||||
|         right: 0; | ||||
|         transform: translateX($paneExpandedOffset); | ||||
|         @include transform(translateX($paneExpandedOffset)); | ||||
|         &.flush-right { | ||||
|             transform: translateX(($uePaneMiniTabW + ceil($splitterD / 2))); | ||||
|             @include transform(translateX(($uePaneMiniTabW + ceil($splitterD / 2)))); | ||||
|         } | ||||
|         &.collapsed { | ||||
|             transform: translateX($ueCollapsedPaneEdgeM); | ||||
|             @include transform(translateX($ueCollapsedPaneEdgeM)); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -20,7 +20,6 @@ | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
| .s-hover-border { | ||||
|     border: 1px solid transparent; | ||||
|     &:hover { | ||||
|         border-color: rgba($colorSelectableSelectedPrimary, 0.5) !important; | ||||
|     } | ||||
|   | ||||
| @@ -19,14 +19,9 @@ | ||||
|  this source code distribution or the Licensing information page available | ||||
|  at runtime from the About dialog for additional information. | ||||
| --> | ||||
| <div class='abs bottom-bar ue-bottom-bar mobile-disable-select' ng-controller="BottomBarController as bar"> | ||||
| <div class='abs bottom-bar ue-bottom-bar mobile-disable-select'> | ||||
|     <div id='status' class='status-holder'> | ||||
|         <mct-include ng-repeat="indicator in bar.getIndicators()" | ||||
|                      ng-model="indicator.ngModel" | ||||
|                      key="indicator.template" | ||||
|                      class="status-block-holder" | ||||
|                      ng-class='indicator.ngModel.getGlyphClass()'> | ||||
|         </mct-include> | ||||
|         <mct-indicators></mct-indicators> | ||||
|     </div> | ||||
|     <mct-include key="'message-banner'"></mct-include> | ||||
|     <mct-include key="'about-logo'"></mct-include> | ||||
|   | ||||
| @@ -21,13 +21,11 @@ | ||||
| --> | ||||
| <!-- DO NOT ADD SPACES BETWEEN THE SPANS - IT ADDS WHITE SPACE!! --> | ||||
| <div class='status block' | ||||
| 	 title="{{ngModel.getDescription()}}" | ||||
| 	 ng-click='ngModel.configure()' | ||||
| 	 ng-show="ngModel.getText().length > 0"> | ||||
| 	<span class="status-indicator {{ngModel.getCssClass()}}"></span><span class="label" | ||||
| 		  ng-class='ngModel.getTextClass()'> | ||||
| 		{{ngModel.getText()}} | ||||
|         <a class="s-button icon-gear" ng-if="ngModel.configure"></a> | ||||
| 	 title="{{ngModel.description()}}" | ||||
| 	 ng-show="ngModel.text().length > 0"> | ||||
| 	<span class="status-indicator {{ngModel.cssClass()}}"></span><span class="label" | ||||
| 		  ng-class='ngModel.textClass()'> | ||||
| 		{{ngModel.text()}} | ||||
| 	</span><span class="count"> | ||||
| 	</span> | ||||
| </div> | ||||
|   | ||||
| @@ -20,26 +20,24 @@ | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
| 
 | ||||
| define([ | ||||
| 
 | ||||
| ], function ( | ||||
| 
 | ||||
| ) { | ||||
| 
 | ||||
|     /** | ||||
|      * Policy determining which views can apply to summary widget.  Disables | ||||
|      * any view other than normal summary widget view. | ||||
|      */ | ||||
|     function SummaryWidgetViewPolicy() { | ||||
|     } | ||||
| 
 | ||||
|     SummaryWidgetViewPolicy.prototype.allow = function (view, domainObject) { | ||||
|         if (domainObject.getModel().type === 'summary-widget') { | ||||
|             return view.key === 'summary-widget-viewer'; | ||||
| define( | ||||
|     [], | ||||
|     function () { | ||||
|         function MCTIndicators(openmct) { | ||||
|             return { | ||||
|                 restrict: "E", | ||||
|                 link: function link(scope, element, attrs) { | ||||
|                     openmct.indicators.displayFunctions().then(function (displayFunctions){ | ||||
|                         displayFunctions.forEach(function (displayFunction){ | ||||
|                             var displayElement = displayFunction(); | ||||
|                             element.append(displayElement); | ||||
|                         }); | ||||
|                     }) | ||||
|                 } | ||||
|             }; | ||||
|         } | ||||
|         return true; | ||||
| 
 | ||||
|     }; | ||||
|         return MCTIndicators; | ||||
| 
 | ||||
|     return SummaryWidgetViewPolicy; | ||||
| }); | ||||
|     } | ||||
| ); | ||||
| @@ -73,17 +73,10 @@ define( | ||||
|          * @returns {string} URL for the domain object | ||||
|          */ | ||||
|         UrlService.prototype.urlForNewTab = function (mode, domainObject) { | ||||
|             var search = this.$location.search(), | ||||
|                 arr = []; | ||||
|             for (var key in search) { | ||||
|                 if (search.hasOwnProperty(key)) { | ||||
|                     arr.push(key + '=' + search[key]); | ||||
|                 } | ||||
|             } | ||||
|             var searchPath = "?" + arr.join('&'), | ||||
|             var viewPath = "?view=" + this.$location.search().view, | ||||
|                 newTabPath = | ||||
|                     "index.html#" + this.urlForLocation(mode, domainObject) + | ||||
|                             searchPath; | ||||
|                             viewPath; | ||||
|             return newTabPath; | ||||
|         }; | ||||
|  | ||||
|   | ||||
| @@ -40,18 +40,10 @@ define([ | ||||
|         return type.getCssClass(); | ||||
|     } | ||||
|  | ||||
|     function removePreviousIconClass(el) { | ||||
|         $(el).removeClass(function (index, className) { | ||||
|             return (className.match (/\bicon-\S+/g) || []).join(' '); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     TreeLabelView.prototype.updateView = function (domainObject) { | ||||
|         var titleEl = this.el.find('.t-title-label'), | ||||
|             iconEl = this.el.find('.t-item-icon'); | ||||
|  | ||||
|         removePreviousIconClass(iconEl); | ||||
|  | ||||
|         titleEl.text(domainObject ? domainObject.getModel().name : ""); | ||||
|         iconEl.addClass(domainObject ? getClass(domainObject) : ""); | ||||
|  | ||||
|   | ||||
| @@ -83,9 +83,9 @@ define([ | ||||
|         this.activeObject = domainObject; | ||||
|  | ||||
|         if (domainObject && domainObject.hasCapability('composition')) { | ||||
|             $(this.toggleView.elements()).removeClass('no-children'); | ||||
|             $(this.toggleView.elements()).addClass('has-children'); | ||||
|         } else { | ||||
|             $(this.toggleView.elements()).addClass('no-children'); | ||||
|             $(this.toggleView.elements()).removeClass('has-children'); | ||||
|         } | ||||
|  | ||||
|         if (domainObject && domainObject.hasCapability('status')) { | ||||
|   | ||||
| @@ -1,76 +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. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define( | ||||
|     ["../../src/controllers/BottomBarController"], | ||||
|     function (BottomBarController) { | ||||
|  | ||||
|         describe("The bottom bar controller", function () { | ||||
|             var testIndicators, | ||||
|                 testIndicatorA, | ||||
|                 testIndicatorB, | ||||
|                 testIndicatorC, | ||||
|                 mockIndicator, | ||||
|                 controller; | ||||
|  | ||||
|             beforeEach(function () { | ||||
|                 mockIndicator = jasmine.createSpyObj( | ||||
|                     "indicator", | ||||
|                     ["getGlyph", "getCssClass", "getText"] | ||||
|                 ); | ||||
|  | ||||
|                 testIndicatorA = {}; | ||||
|                 testIndicatorB = function () { | ||||
|                     return mockIndicator; | ||||
|                 }; | ||||
|                 testIndicatorC = { template: "someTemplate" }; | ||||
|  | ||||
|                 testIndicators = [ | ||||
|                     testIndicatorA, | ||||
|                     testIndicatorB, | ||||
|                     testIndicatorC | ||||
|                 ]; | ||||
|  | ||||
|                 controller = new BottomBarController(testIndicators); | ||||
|             }); | ||||
|  | ||||
|             it("exposes one indicator description per extension", function () { | ||||
|                 expect(controller.getIndicators().length) | ||||
|                     .toEqual(testIndicators.length); | ||||
|             }); | ||||
|  | ||||
|             it("uses template field provided, or its own default", function () { | ||||
|                 // "indicator" is the default; | ||||
|                 // only testIndicatorC overrides this. | ||||
|                 var indicators = controller.getIndicators(); | ||||
|                 expect(indicators[0].template).toEqual("indicator"); | ||||
|                 expect(indicators[1].template).toEqual("indicator"); | ||||
|                 expect(indicators[2].template).toEqual("someTemplate"); | ||||
|             }); | ||||
|  | ||||
|             it("instantiates indicators given as constructors", function () { | ||||
|                 // testIndicatorB constructs to mockIndicator | ||||
|                 expect(controller.getIndicators()[1].ngModel).toBe(mockIndicator); | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| ); | ||||
| @@ -65,10 +65,6 @@ 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()) { | ||||
|   | ||||
| @@ -50,11 +50,7 @@ define( | ||||
|                         view.show(container); | ||||
|                     } else { | ||||
|                         self.providerView = false; | ||||
|                         var selectedItem = selection[0].context.oldItem; | ||||
|  | ||||
|                         if (selectedItem) { | ||||
|                             $scope.inspectorKey = selectedItem.getCapability("type").typeDef.inspector; | ||||
|                         } | ||||
|                         $scope.inspectorKey = selection[0].context.oldItem.getCapability("type").typeDef.inspector; | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|   | ||||
| @@ -181,8 +181,6 @@ $colorPlotHash: $colorTick; | ||||
| $stylePlotHash: dashed; | ||||
| $colorPlotAreaBorder: $colorInteriorBorder; | ||||
| $colorPlotLabelFg: pushBack($colorPlotFg, 20%); | ||||
| $legendCollapsedNameMaxW: 50%; | ||||
| $legendHoverValueBg: rgba($colorBodyFg, 0.1); | ||||
|  | ||||
| // Tree | ||||
| $colorItemTreeHoverBg: pullForward($colorBodyBg, $hoverRatioPercent); | ||||
|   | ||||
| @@ -181,8 +181,6 @@ $colorPlotHash: $colorTick; | ||||
| $stylePlotHash: dashed; | ||||
| $colorPlotAreaBorder: $colorInteriorBorder; | ||||
| $colorPlotLabelFg: pushBack($colorPlotFg, 20%); | ||||
| $legendCollapsedNameMaxW: 50%; | ||||
| $legendHoverValueBg: rgba($colorBodyFg, 0.2); | ||||
|  | ||||
| // Tree | ||||
| $colorItemTreeHoverBg: pullForward($colorBodyBg, $hoverRatioPercent); | ||||
|   | ||||
| @@ -55,7 +55,6 @@ define([ | ||||
|     timerTemplate, | ||||
|     legacyRegistry | ||||
| ) { | ||||
|  | ||||
|     legacyRegistry.register("platform/features/clock", { | ||||
|         "name": "Clocks/Timers", | ||||
|         "descriptions": "Domain objects for displaying current & relative times.", | ||||
| @@ -86,11 +85,6 @@ define([ | ||||
|                         "CLOCK_INDICATOR_FORMAT" | ||||
|                     ], | ||||
|                     "priority": "preferred" | ||||
|                 }, | ||||
|                 { | ||||
|                     "implementation": FollowIndicator, | ||||
|                     "depends": ["timerService"], | ||||
|                     "priority": "fallback" | ||||
|                 } | ||||
|             ], | ||||
|             "services": [ | ||||
| @@ -305,6 +299,10 @@ define([ | ||||
|                     } | ||||
|                 } | ||||
|             ], | ||||
|             "runs": [{ | ||||
|                 "implementation": FollowIndicator, | ||||
|                 "depends": ["openmct", "timerService"] | ||||
|             }], | ||||
|             "licenses": [ | ||||
|                 { | ||||
|                     "name": "moment-duration-format", | ||||
|   | ||||
| @@ -20,38 +20,29 @@ | ||||
|  * at runtime from the About dialog for additional information. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define( | ||||
|     ['moment'], | ||||
|     function (moment) { | ||||
|         var NO_TIMER = "No timer being followed"; | ||||
| define([],function () { | ||||
|  | ||||
|         /** | ||||
|          * Indicator that displays the active timer, as well as its | ||||
|          * current state. | ||||
|          * @implements {Indicator} | ||||
|          * @memberof platform/features/clock | ||||
|          */ | ||||
|         function FollowIndicator(timerService) { | ||||
|             this.timerService = timerService; | ||||
|     function setIndicatorStatus(indicator, timer) { | ||||
|         if (timer !== undefined) { | ||||
|             indicator.cssClass('icon-timer s-status-ok'); | ||||
|             indicator.text('Following timer ' + timer.name); | ||||
|         } else { | ||||
|             indicator.cssClass('icon-timer'); | ||||
|             indicator.text('No timer being followed.'); | ||||
|         } | ||||
|  | ||||
|         FollowIndicator.prototype.getGlyphClass = function () { | ||||
|             return ""; | ||||
|         }; | ||||
|  | ||||
|         FollowIndicator.prototype.getCssClass = function () { | ||||
|             return (this.timerService.getTimer()) ? "icon-timer s-status-ok" : "icon-timer"; | ||||
|         }; | ||||
|  | ||||
|         FollowIndicator.prototype.getText = function () { | ||||
|             var timer = this.timerService.getTimer(); | ||||
|             return timer ? ('Following timer ' + timer.name) : NO_TIMER; | ||||
|         }; | ||||
|  | ||||
|         FollowIndicator.prototype.getDescription = function () { | ||||
|             return ""; | ||||
|         }; | ||||
|  | ||||
|         return FollowIndicator; | ||||
|     } | ||||
| ); | ||||
|  | ||||
|     /** | ||||
|      * Indicator that displays the active timer, as well as its | ||||
|      * current state. | ||||
|      * @memberof platform/features/clock | ||||
|      */ | ||||
|     return function installFollowIndicator(openmct, timerService) { | ||||
|         var indicator = openmct.indicators.create(); | ||||
|         var timer = timerService.getTimer(); | ||||
|         var setIndicatorStatusFromTimer = setIndicatorStatus.bind(this, indicator); | ||||
|          | ||||
|         setIndicatorStatusFromTimer(timer); | ||||
|         timerService.on('change', setIndicatorStatusFromTimer); | ||||
|     }; | ||||
| }); | ||||
|   | ||||
| @@ -44,7 +44,7 @@ define(['EventEmitter'], function (EventEmitter) { | ||||
|      */ | ||||
|     TimerService.prototype.setTimer = function (timer) { | ||||
|         this.timer = timer; | ||||
|         this.emit('change'); | ||||
|         this.emit('change', timer); | ||||
|  | ||||
|         if (this.stopObserving) { | ||||
|             this.stopObserving(); | ||||
|   | ||||
| @@ -42,15 +42,18 @@ define(["../../src/indicators/FollowIndicator"], function (FollowIndicator) { | ||||
|         }); | ||||
|  | ||||
|         describe("when a timer is set", function () { | ||||
|             var testObject; | ||||
|             var testModel; | ||||
|             var mockDomainObject; | ||||
|  | ||||
|             beforeEach(function () { | ||||
|                 testObject = { name: "some timer!" }; | ||||
|                 mockTimerService.getTimer.andReturn(testObject); | ||||
|                 testModel = { name: "some timer!" }; | ||||
|                 mockDomainObject = jasmine.createSpyObj('timer', ['getModel']); | ||||
|                 mockDomainObject.getModel.andReturn(testModel); | ||||
|                 mockTimerService.getTimer.andReturn(mockDomainObject); | ||||
|             }); | ||||
|  | ||||
|             it("displays the timer's name", function () { | ||||
|                 expect(indicator.getText().indexOf(testObject.name)) | ||||
|                 expect(indicator.getText().indexOf(testModel.name)) | ||||
|                     .not.toEqual(-1); | ||||
|             }); | ||||
|         }); | ||||
|   | ||||
| @@ -55,7 +55,7 @@ | ||||
|     div[class*="hand"] { | ||||
|         $handW: 2px; | ||||
|         $handH: $d * 0.4; | ||||
|         transform: translate(-50%, -50%); | ||||
|         @include transform(translate(-50%, -50%)); | ||||
|         @include animation-iteration-count(infinite); | ||||
|         @include animation-timing-function(linear); | ||||
|         position: absolute; | ||||
| @@ -258,7 +258,7 @@ | ||||
|         align-items: center; | ||||
|         margin-top: $interiorMargin; | ||||
|         .l-time-conductor-zoom-w { | ||||
|             justify-content: flex-end; | ||||
|             @include justify-content(flex-end); | ||||
|             .time-conductor-zoom { | ||||
|                 height: $r3H; | ||||
|                 min-width: 100px; | ||||
| @@ -327,7 +327,7 @@ | ||||
|         $i: $glyph-icon-calendar; | ||||
|         .time-conductor-icon div[class*="hand"] { | ||||
|             &.hand-little { | ||||
|                 transform: rotate(120deg); | ||||
|                 @include transform(rotate(120deg)); | ||||
|             } | ||||
|         } | ||||
|         .mode-selector .s-menu-button:before { | ||||
|   | ||||
| @@ -29,7 +29,7 @@ mct-include.l-toi-holder, | ||||
|  | ||||
| mct-include.l-toi-holder { | ||||
|     $blockerFadeW: $toiBlockerFadeW; | ||||
|     transform: translateX(-50%); | ||||
|     @include transform(translateX(-50%)); | ||||
|     color: $toiColorBg; | ||||
|     position: absolute; | ||||
|     top: 0; | ||||
| @@ -66,7 +66,7 @@ mct-include.l-toi-holder { | ||||
|         box-sizing: content-box; | ||||
|         height: $toiH; | ||||
|         left: $toiPad * -2; | ||||
|         transform: translateY(-50%); top: 50%; | ||||
|         @include transform(translateY(-50%)); top: 50%; | ||||
|         padding: $toiPad; | ||||
|         z-index: 1; | ||||
|  | ||||
| @@ -207,7 +207,7 @@ table { | ||||
|                             border-radius: 20%; | ||||
|                             height: auto; | ||||
|                             padding: $toiPad; | ||||
|                             transform: translate(-50%, -50%); | ||||
|                             @include transform(translate(-50%, -50%)); | ||||
|                             left: 50%; right: auto; top: 0; | ||||
|                             .l-toi-buttons { | ||||
|                                 padding: 1px; | ||||
| @@ -233,7 +233,7 @@ table { | ||||
|             z-index: 3; | ||||
|  | ||||
|             .l-toi { | ||||
|                 transform: translateY(100%); | ||||
|                 @include transform(translateY(100%)); | ||||
|  | ||||
|             } | ||||
|  | ||||
| @@ -257,13 +257,13 @@ table { | ||||
|         } | ||||
|  | ||||
|         &:before { | ||||
|             transform: translate(-50%, $linesVOffset * -1); | ||||
|             @include transform(translate(-50%, $linesVOffset * -1)); | ||||
|             top: 0px; | ||||
|             bottom: auto; | ||||
|         } | ||||
|  | ||||
|         &:after { | ||||
|             transform: translate(-50%, $linesVOffset); | ||||
|             @include transform(translate(-50%, $linesVOffset)); | ||||
|             top: auto; | ||||
|             bottom: 0px; | ||||
|         } | ||||
|   | ||||
| @@ -92,7 +92,7 @@ define( | ||||
|                             this.updateHistory(datum); | ||||
|                             this.updateValues(datum); | ||||
|                         }.bind(this)); | ||||
|  | ||||
|                     this.requestLad(false); | ||||
|                     this.requestHistory(this.openmct.time.bounds()); | ||||
|                 }.bind(this)); | ||||
|         }; | ||||
| @@ -107,15 +107,36 @@ define( | ||||
|                         if (this.requestCount > requestId) { | ||||
|                             return Promise.resolve('Stale request'); | ||||
|                         } | ||||
|  | ||||
|                         values.forEach(function (datum) { | ||||
|                             this.updateHistory(datum); | ||||
|                         }, this); | ||||
|  | ||||
|                         this.updateValues(values[values.length - 1]); | ||||
|                         this.requestLad(true); | ||||
|                     }.bind(this)); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Makes a request for the most recent datum in the | ||||
|          * telelmetry store. Optional addToHistory argument | ||||
|          * determines whether the requested telemetry should | ||||
|          * be added to history or only used to update the current | ||||
|          * image url and timestamp. | ||||
|          * @private | ||||
|          * @param {boolean} [addToHistory] if true, adds to history | ||||
|          */ | ||||
|         ImageryController.prototype.requestLad = function (addToHistory) { | ||||
|             this.openmct.telemetry | ||||
|                 .request(this.domainObject, { | ||||
|                     strategy: 'latest', | ||||
|                     size: 1 | ||||
|                 }) | ||||
|                 .then(function (values) { | ||||
|                     this.updateValues(values[0]); | ||||
|                     if (addToHistory !== false) { | ||||
|                         this.updateHistory(values[0]); | ||||
|                     } | ||||
|                 }.bind(this)); | ||||
|         }; | ||||
|  | ||||
|         ImageryController.prototype.stopListening = function () { | ||||
|             this.openmct.time.off('bounds', this.onBoundsChange); | ||||
|             this.scrollable.off('scroll', this.onScroll); | ||||
|   | ||||
| @@ -59,8 +59,7 @@ define( | ||||
|                         'timeSystem', | ||||
|                         'clock', | ||||
|                         'on', | ||||
|                         'off', | ||||
|                         'bounds' | ||||
|                         'off' | ||||
|                     ]), | ||||
|                     telemetry: jasmine.createSpyObj('telemetryAPI', [ | ||||
|                         'subscribe', | ||||
| @@ -119,8 +118,7 @@ define( | ||||
|  | ||||
|             describe("when loaded", function () { | ||||
|                 var callback, | ||||
|                     boundsListener, | ||||
|                     bounds; | ||||
|                     boundsListener; | ||||
|  | ||||
|                 beforeEach(function () { | ||||
|                     waitsFor(function () { | ||||
| @@ -139,9 +137,13 @@ define( | ||||
|                     }); | ||||
|                 }); | ||||
|  | ||||
|                 it("requests history", function () { | ||||
|                 it("uses LAD telemetry", function () { | ||||
|                     expect(openmct.telemetry.request).toHaveBeenCalledWith( | ||||
|                         newDomainObject, bounds | ||||
|                         newDomainObject, | ||||
|                         { | ||||
|                             strategy: 'latest', | ||||
|                             size: 1 | ||||
|                         } | ||||
|                     ); | ||||
|                     expect(controller.getTime()).toEqual(prefix + 1434600258123); | ||||
|                     expect(controller.getImageUrl()).toEqual('some/url'); | ||||
| @@ -202,7 +204,7 @@ define( | ||||
|                 it("requests telemetry", function () { | ||||
|                     expect(openmct.telemetry.request).toHaveBeenCalledWith( | ||||
|                         newDomainObject, | ||||
|                         bounds | ||||
|                         jasmine.any(Object) | ||||
|                     ); | ||||
|                 }); | ||||
|  | ||||
|   | ||||
| @@ -272,8 +272,7 @@ define([ | ||||
|                         "$scope", | ||||
|                         "$q", | ||||
|                         "dialogService", | ||||
|                         "openmct", | ||||
|                         "$element" | ||||
|                         "openmct" | ||||
|                     ] | ||||
|                 } | ||||
|             ], | ||||
| @@ -337,6 +336,46 @@ define([ | ||||
|                             "conversion": "number[]" | ||||
|                         } | ||||
|                     ] | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "telemetry.panel", | ||||
|                     "name": "Telemetry Panel", | ||||
|                     "cssClass": "icon-telemetry-panel", | ||||
|                     "description": "A panel for collecting telemetry elements.", | ||||
|                     "priority": 899, | ||||
|                     "delegates": [ | ||||
|                         "telemetry" | ||||
|                     ], | ||||
|                     "features": "creation", | ||||
|                     "contains": [ | ||||
|                         { | ||||
|                             "has": "telemetry" | ||||
|                         } | ||||
|                     ], | ||||
|                     "model": { | ||||
|                         "composition": [] | ||||
|                     }, | ||||
|                     "properties": [ | ||||
|                         { | ||||
|                             "name": "Layout Grid", | ||||
|                             "control": "composite", | ||||
|                             "items": [ | ||||
|                                 { | ||||
|                                     "name": "Horizontal grid (px)", | ||||
|                                     "control": "textfield", | ||||
|                                     "cssClass": "l-input-sm l-numeric" | ||||
|                                 }, | ||||
|                                 { | ||||
|                                     "name": "Vertical grid (px)", | ||||
|                                     "control": "textfield", | ||||
|                                     "cssClass": "l-input-sm l-numeric" | ||||
|                                 } | ||||
|                             ], | ||||
|                             "pattern": "^(\\d*[1-9]\\d*)?$", | ||||
|                             "property": "layoutGrid", | ||||
|                             "conversion": "number[]" | ||||
|                         } | ||||
|                     ] | ||||
|                 } | ||||
|             ] | ||||
|         } | ||||
|   | ||||
| @@ -23,7 +23,7 @@ | ||||
|      ng-controller="FixedController as controller"> | ||||
|  | ||||
|     <!-- Background grid --> | ||||
|     <div class="l-grid-holder" ng-click="controller.bypassSelection($event)"> | ||||
|     <div class="l-grid-holder" ng-click="controller.clearSelection()"> | ||||
|         <div class="l-grid l-grid-x" | ||||
|              ng-if="!controller.getGridSize()[0] < 3" | ||||
|              ng-style="{ 'background-size': controller.getGridSize() [0] + 'px 100%' }"></div> | ||||
| @@ -35,28 +35,35 @@ | ||||
|     <!-- 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" | ||||
|          mct-selectable="controller.getContext(element)" | ||||
|          mct-init-select="controller.shouldSelect(element)"> | ||||
|          ng-click="controller.select(element, $event)"> | ||||
|         <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.isElementSelected()"> | ||||
|     <span class="s-selected s-moveable" ng-if="controller.selected()"> | ||||
|         <div class="l-fixed-position-item t-edit-handle-holder" | ||||
|              mct-drag-down="controller.moveHandle().startDrag()" | ||||
|              mct-drag-down="controller.moveHandle().startDrag(controller.selected())" | ||||
|              mct-drag="controller.moveHandle().continueDrag(delta)" | ||||
|              mct-drag-up="controller.endDrag()" | ||||
|              ng-style="controller.getSelectedElementStyle()"> | ||||
|              mct-drag-up="controller.moveHandle().endDrag()" | ||||
|              ng-style="controller.selected().style" | ||||
|              ng-click="$event.stopPropagation()"> | ||||
|         </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="controller.endDrag(handle)"> | ||||
|              mct-drag-up="handle.endDrag()" | ||||
|              ng-click="$event.stopPropagation()"> | ||||
|         </div> | ||||
|     </span> | ||||
|  | ||||
| </div> | ||||
|   | ||||
| @@ -36,8 +36,7 @@ | ||||
|              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 t-object-type-{{ childObject.getModel().type }}" | ||||
|          data-layout-id="{{childObject.getId() + '-' + $id}}" | ||||
|     <div class="abs frame t-frame-outer child-frame panel s-selectable s-moveable s-hover-border {{childObject.getId() + '-' + $id}} t-object-type-{{ childObject.getModel().type }}" | ||||
|          ng-class="{ 'no-frame': !controller.hasFrame(childObject), 's-drilled-in': controller.isDrilledIn(childObject) }" | ||||
|          ng-repeat="childObject in composition" | ||||
|          ng-init="controller.selectIfNew(childObject.getId() + '-' + $id, childObject)" | ||||
|   | ||||
| @@ -47,7 +47,7 @@ define( | ||||
|          * @constructor | ||||
|          * @param {Scope} $scope the controller's Angular scope | ||||
|          */ | ||||
|         function FixedController($scope, $q, dialogService, openmct, $element) { | ||||
|         function FixedController($scope, $q, dialogService, openmct) { | ||||
|             this.names = {}; // Cache names by ID | ||||
|             this.values = {}; // Cache values by ID | ||||
|             this.elementProxiesById = {}; | ||||
| @@ -55,11 +55,9 @@ 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; | ||||
|             [ | ||||
| @@ -89,8 +87,9 @@ define( | ||||
|  | ||||
|             // Update the style for a selected element | ||||
|             function updateSelectionStyle() { | ||||
|                 if (self.selectedElementProxy) { | ||||
|                     self.selectedElementProxy.style = convertPosition(self.selectedElementProxy); | ||||
|                 var element = self.selection && self.selection.get(); | ||||
|                 if (element) { | ||||
|                     element.style = convertPosition(element); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
| @@ -137,19 +136,25 @@ define( | ||||
|  | ||||
|             // Decorate elements in the current configuration | ||||
|             function refreshElements() { | ||||
|                 var elements = (($scope.configuration || {}).elements || []); | ||||
|                 // 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); | ||||
|                 } | ||||
|  | ||||
|                 // Create the new proxies... | ||||
|                 self.elementProxies = elements.map(makeProxyElement); | ||||
|  | ||||
|                 // 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; | ||||
|                 // Clear old selection, and restore if appropriate | ||||
|                 if (self.selection) { | ||||
|                     self.selection.deselect(); | ||||
|                     if (index > -1) { | ||||
|                         self.select(self.elementProxies[index]); | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
| @@ -219,12 +224,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."); | ||||
| @@ -258,36 +263,21 @@ 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; | ||||
|             this.updateSelectionStyle = updateSelectionStyle; | ||||
|  | ||||
|             // 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)); | ||||
|  | ||||
|             // Detect changes to grid size | ||||
|             $scope.$watch("model.layoutGrid", updateElementPositions); | ||||
| @@ -308,15 +298,10 @@ 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)); | ||||
|  | ||||
|             setSelection(this.openmct.selection.get()); | ||||
|         } | ||||
|  | ||||
|         /** | ||||
| @@ -375,10 +360,10 @@ define( | ||||
|          */ | ||||
|         FixedController.prototype.updateView = function (telemetryObject, datum) { | ||||
|             var metadata = this.openmct.telemetry.getMetadata(telemetryObject); | ||||
|             var valueMetadata = this.chooseValueMetadataToDisplay(metadata); | ||||
|             var formattedTelemetryValue = this.getFormattedTelemetryValueForKey(valueMetadata, datum); | ||||
|             var telemetryKeyToDisplay = this.chooseTelemetryKeyToDisplay(metadata); | ||||
|             var formattedTelemetryValue = this.getFormattedTelemetryValueForKey(telemetryKeyToDisplay, datum, metadata); | ||||
|             var limitEvaluator = this.openmct.telemetry.limitEvaluator(telemetryObject); | ||||
|             var alarm = limitEvaluator && limitEvaluator.evaluate(datum, valueMetadata); | ||||
|             var alarm = limitEvaluator && limitEvaluator.evaluate(datum, telemetryKeyToDisplay); | ||||
|  | ||||
|             this.setDisplayedValue( | ||||
|                 telemetryObject, | ||||
| @@ -391,28 +376,29 @@ define( | ||||
|         /** | ||||
|          * @private | ||||
|          */ | ||||
|         FixedController.prototype.getFormattedTelemetryValueForKey = function (valueMetadata, datum) { | ||||
|         FixedController.prototype.getFormattedTelemetryValueForKey = function (telemetryKeyToDisplay, datum, metadata) { | ||||
|             var valueMetadata = metadata.value(telemetryKeyToDisplay); | ||||
|             var formatter = this.openmct.telemetry.getValueFormatter(valueMetadata); | ||||
|  | ||||
|             return formatter.format(datum); | ||||
|             return formatter.format(datum[valueMetadata.key]); | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * @private | ||||
|          */ | ||||
|         FixedController.prototype.chooseValueMetadataToDisplay = function (metadata) { | ||||
|         FixedController.prototype.chooseTelemetryKeyToDisplay = function (metadata) { | ||||
|             // If there is a range value, show that preferentially | ||||
|             var valueMetadata = metadata.valuesForHints(['range'])[0]; | ||||
|             var telemetryKeyToDisplay = metadata.valuesForHints(['range'])[0]; | ||||
|  | ||||
|             // If no range is defined, default to the highest priority non time-domain data. | ||||
|             if (valueMetadata === undefined) { | ||||
|             if (telemetryKeyToDisplay === undefined) { | ||||
|                 var valuesOrderedByPriority = metadata.values(); | ||||
|                 valueMetadata = valuesOrderedByPriority.filter(function (values) { | ||||
|                     return !(values.hints.domain); | ||||
|                 telemetryKeyToDisplay = valuesOrderedByPriority.filter(function (valueMetadata) { | ||||
|                     return !(valueMetadata.hints.domain); | ||||
|                 })[0]; | ||||
|             } | ||||
|  | ||||
|             return valueMetadata; | ||||
|             return telemetryKeyToDisplay.source; | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
| @@ -466,7 +452,7 @@ define( | ||||
|  | ||||
|             function filterForTelemetryObjects(objects) { | ||||
|                 return objects.filter(function (object) { | ||||
|                     return self.openmct.telemetry.isTelemetryObject(object); | ||||
|                     return self.openmct.telemetry.canProvideTelemetry(object); | ||||
|                 }); | ||||
|             } | ||||
|  | ||||
| @@ -506,56 +492,42 @@ define( | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Checks if the element should be selected or not. | ||||
|          * | ||||
|          * @param elementProxy the element to check | ||||
|          * @returns {boolean} true if the element should be selected. | ||||
|          * Check if the element is currently selected, or (if no | ||||
|          * argument is supplied) get the currently selected element. | ||||
|          * @returns {boolean} true if selected | ||||
|          */ | ||||
|         FixedController.prototype.shouldSelect = function (elementProxy) { | ||||
|             if (elementProxy.element === this.elementToSelectAfterRefresh) { | ||||
|                 delete this.elementToSelectAfterRefresh; | ||||
|                 return true; | ||||
|             } else { | ||||
|                 return false; | ||||
|         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, event) { | ||||
|             if (event) { | ||||
|                 event.stopPropagation(); | ||||
|             } | ||||
|  | ||||
|             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); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         /** | ||||
|          * Checks if an element is currently selected. | ||||
|          * | ||||
|          * @returns {boolean} true if an element is selected. | ||||
|          * Clear the current user selection. | ||||
|          */ | ||||
|         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; | ||||
|         FixedController.prototype.clearSelection = function () { | ||||
|             if (this.selection) { | ||||
|                 this.selection.deselect(); | ||||
|                 this.resizeHandles = []; | ||||
|                 this.mvHandle = undefined; | ||||
|             } | ||||
|         }; | ||||
|  | ||||
| @@ -576,38 +548,6 @@ 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 () { | ||||
|         FixedDragHandle.prototype.startDrag = function startDrag() { | ||||
|             // Cache initial x/y positions | ||||
|             this.dragging = { | ||||
|                 x: this.elementHandle.x(), | ||||
|   | ||||
| @@ -515,7 +515,7 @@ define( | ||||
|         LayoutController.prototype.selectIfNew = function (selector, domainObject) { | ||||
|             if (domainObject.getId() === this.droppedIdToSelectAfterRefresh) { | ||||
|                 setTimeout(function () { | ||||
|                     $('[data-layout-id="' + selector + '"]')[0].click(); | ||||
|                     $('.' + selector)[0].click(); | ||||
|                     delete this.droppedIdToSelectAfterRefresh; | ||||
|                 }.bind(this), 0); | ||||
|             } | ||||
|   | ||||
| @@ -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 {Array} elements the full array of elements | ||||
|          * @param {number[]} gridSize the current layout grid size in [x,y] from | ||||
|          * @param {Array} elements the full array of elements | ||||
|          */ | ||||
|         function ElementProxy(element, index, elements, gridSize) { | ||||
|             /** | ||||
|   | ||||
| @@ -21,14 +21,8 @@ | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define( | ||||
|     [ | ||||
|         "../src/FixedController", | ||||
|         "zepto" | ||||
|     ], | ||||
|     function ( | ||||
|         FixedController, | ||||
|         $ | ||||
|     ) { | ||||
|     ["../src/FixedController"], | ||||
|     function (FixedController) { | ||||
|  | ||||
|         describe("The Fixed Position controller", function () { | ||||
|             var mockScope, | ||||
| @@ -52,9 +46,6 @@ define( | ||||
|                 mockMetadata, | ||||
|                 mockTimeSystem, | ||||
|                 mockLimitEvaluator, | ||||
|                 mockSelection, | ||||
|                 $element = [], | ||||
|                 selectable = [], | ||||
|                 controller; | ||||
|  | ||||
|             // Utility function; find a watch for a given expression | ||||
| @@ -106,8 +97,8 @@ define( | ||||
|                     'telemetryFormatter', | ||||
|                     ['format'] | ||||
|                 ); | ||||
|                 mockFormatter.format.andCallFake(function (valueMetadata) { | ||||
|                     return "Formatted " + valueMetadata.value; | ||||
|                 mockFormatter.format.andCallFake(function (value) { | ||||
|                     return "Formatted " + value; | ||||
|                 }); | ||||
|  | ||||
|                 mockDomainObject = jasmine.createSpyObj( | ||||
| @@ -150,13 +141,13 @@ define( | ||||
|                     [ | ||||
|                         'subscribe', | ||||
|                         'request', | ||||
|                         'isTelemetryObject', | ||||
|                         'canProvideTelemetry', | ||||
|                         'getMetadata', | ||||
|                         'limitEvaluator', | ||||
|                         'getValueFormatter' | ||||
|                     ] | ||||
|                 ); | ||||
|                 mockTelemetryAPI.isTelemetryObject.andReturn(true); | ||||
|                 mockTelemetryAPI.canProvideTelemetry.andReturn(true); | ||||
|                 mockTelemetryAPI.request.andReturn(Promise.resolve([])); | ||||
|  | ||||
|                 testGrid = [123, 456]; | ||||
| @@ -189,30 +180,17 @@ define( | ||||
|  | ||||
|                 mockScope.model = testModel; | ||||
|                 mockScope.configuration = testConfiguration; | ||||
|  | ||||
|                 selectable[0] = { | ||||
|                     context: { | ||||
|                         oldItem: mockDomainObject | ||||
|                     } | ||||
|                 }; | ||||
|                 mockSelection = jasmine.createSpyObj("selection", [ | ||||
|                     'select', | ||||
|                     'on', | ||||
|                     'off', | ||||
|                     'get' | ||||
|                 ]); | ||||
|                 mockSelection.get.andReturn([]); | ||||
|                 mockScope.selection = jasmine.createSpyObj( | ||||
|                     'selection', | ||||
|                     ['select', 'get', 'selected', 'deselect', 'proxy'] | ||||
|                 ); | ||||
|  | ||||
|                 mockOpenMCT = { | ||||
|                     time: mockConductor, | ||||
|                     telemetry: mockTelemetryAPI, | ||||
|                     composition: mockCompositionAPI, | ||||
|                     selection: mockSelection | ||||
|                     composition: mockCompositionAPI | ||||
|                 }; | ||||
|  | ||||
|                 $element = $('<div></div>'); | ||||
|                 spyOn($element[0], 'click'); | ||||
|  | ||||
|                 mockMetadata = jasmine.createSpyObj('mockMetadata', [ | ||||
|                     'valuesForHints', | ||||
|                     'value', | ||||
| @@ -248,11 +226,11 @@ define( | ||||
|                     mockScope, | ||||
|                     mockQ, | ||||
|                     mockDialogService, | ||||
|                     mockOpenMCT, | ||||
|                     $element | ||||
|                     mockOpenMCT | ||||
|                 ); | ||||
|  | ||||
|                 findWatch("model.layoutGrid")(testModel.layoutGrid); | ||||
|                 findWatch("selection")(mockScope.selection); | ||||
|             }); | ||||
|  | ||||
|             it("subscribes when a domain object is available", function () { | ||||
| @@ -328,41 +306,41 @@ define( | ||||
|             }); | ||||
|  | ||||
|             it("allows elements to be selected", function () { | ||||
|                 testModel.modified = 1; | ||||
|                 findWatch("model.modified")(testModel.modified); | ||||
|  | ||||
|                 selectable[0].context.elementProxy = controller.getElements()[1]; | ||||
|                 mockOpenMCT.selection.on.mostRecentCall.args[1](selectable); | ||||
|  | ||||
|                 expect(controller.isElementSelected()).toBe(true); | ||||
|             }); | ||||
|  | ||||
|             it("allows selection retrieval", function () { | ||||
|                 var elements; | ||||
|  | ||||
|                 testModel.modified = 1; | ||||
|                 findWatch("model.modified")(testModel.modified); | ||||
|  | ||||
|                 elements = controller.getElements(); | ||||
|                 selectable[0].context.elementProxy = elements[1]; | ||||
|                 mockOpenMCT.selection.on.mostRecentCall.args[1](selectable); | ||||
|  | ||||
|                 expect(controller.getSelectedElement()).toEqual(elements[1]); | ||||
|                 controller.select(elements[1]); | ||||
|                 expect(mockScope.selection.select) | ||||
|                     .toHaveBeenCalledWith(elements[1]); | ||||
|             }); | ||||
|  | ||||
|             it("selects the parent view when selected element is removed", function () { | ||||
|             it("allows selection retrieval", function () { | ||||
|                 // selected with no arguments should give the current | ||||
|                 // selection | ||||
|                 var elements; | ||||
|  | ||||
|                 testModel.modified = 1; | ||||
|                 findWatch("model.modified")(testModel.modified); | ||||
|  | ||||
|                 var elements = controller.getElements(); | ||||
|                 selectable[0].context.elementProxy = elements[1]; | ||||
|                 mockOpenMCT.selection.on.mostRecentCall.args[1](selectable); | ||||
|                 elements = controller.getElements(); | ||||
|                 controller.select(elements[1]); | ||||
|                 mockScope.selection.get.andReturn(elements[1]); | ||||
|                 expect(controller.selected()).toEqual(elements[1]); | ||||
|             }); | ||||
|  | ||||
|                 elements[1].remove(); | ||||
|                 testModel.modified = 2; | ||||
|             it("allows selections to be cleared", function () { | ||||
|                 var elements; | ||||
|  | ||||
|                 testModel.modified = 1; | ||||
|                 findWatch("model.modified")(testModel.modified); | ||||
|  | ||||
|                 expect($element[0].click).toHaveBeenCalled(); | ||||
|                 elements = controller.getElements(); | ||||
|                 controller.select(elements[1]); | ||||
|                 controller.clearSelection(); | ||||
|                 expect(controller.selected(elements[1])).toBeFalsy(); | ||||
|             }); | ||||
|  | ||||
|             it("retains selections during refresh", function () { | ||||
| @@ -374,21 +352,23 @@ define( | ||||
|                 findWatch("model.modified")(testModel.modified); | ||||
|  | ||||
|                 elements = controller.getElements(); | ||||
|                 selectable[0].context.elementProxy = elements[1]; | ||||
|                 mockOpenMCT.selection.on.mostRecentCall.args[1](selectable); | ||||
|                 controller.select(elements[1]); | ||||
|  | ||||
|                 expect(controller.getSelectedElement()).toEqual(elements[1]); | ||||
|                 // Verify precondition | ||||
|                 expect(mockScope.selection.select.calls.length).toEqual(1); | ||||
|  | ||||
|                 // Mimic selection behavior | ||||
|                 mockScope.selection.get.andReturn(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(controller.shouldSelect(elements[1])).toBe(true); | ||||
|                 expect(mockScope.selection.select.calls.length).toEqual(2); | ||||
|             }); | ||||
|  | ||||
|             it("Displays received values for telemetry elements", function () { | ||||
| @@ -525,25 +505,21 @@ define( | ||||
|             }); | ||||
|  | ||||
|             it("exposes a view-level selection proxy", function () { | ||||
|                 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(); | ||||
|                 expect(mockScope.selection.proxy).toHaveBeenCalledWith( | ||||
|                     jasmine.any(Object) | ||||
|                 ); | ||||
|             }); | ||||
|  | ||||
|             it("exposes drag handles", function () { | ||||
|                 var handles; | ||||
|  | ||||
|                 // Select something so that drag handles are expected | ||||
|                 testModel.modified = 1; | ||||
|                 findWatch("model.modified")(testModel.modified); | ||||
|  | ||||
|                 selectable[0].context.elementProxy = controller.getElements()[1]; | ||||
|                 mockOpenMCT.selection.on.mostRecentCall.args[1](selectable); | ||||
|                 controller.select(controller.getElements()[1]); | ||||
|  | ||||
|                 // Should have a non-empty array of handles | ||||
|                 handles = controller.handles(); | ||||
|  | ||||
|                 expect(handles).toEqual(jasmine.any(Array)); | ||||
|                 expect(handles.length).not.toEqual(0); | ||||
|  | ||||
| @@ -556,14 +532,15 @@ 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); | ||||
|  | ||||
|                 selectable[0].context.elementProxy = controller.getElements()[1]; | ||||
|                 mockOpenMCT.selection.on.mostRecentCall.args[1](selectable); | ||||
|                 controller.select(controller.getElements()[1]); | ||||
|  | ||||
|                 // Should have a move handle | ||||
|                 var handle = controller.moveHandle(); | ||||
|                 handle = controller.moveHandle(); | ||||
|  | ||||
|                 // And it should have start/continue/end drag methods | ||||
|                 expect(handle.startDrag).toEqual(jasmine.any(Function)); | ||||
| @@ -574,40 +551,26 @@ 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); | ||||
|  | ||||
|                 selectable[0].context.elementProxy = controller.getElements()[1]; | ||||
|                 mockOpenMCT.selection.on.mostRecentCall.args[1](selectable); | ||||
|                 controller.select(controller.getElements()[1]); | ||||
|                 mockScope.selection.get.andReturn(controller.getElements()[1]); | ||||
|  | ||||
|                 // Get style | ||||
|                 oldStyle = controller.getSelectedElementStyle(); | ||||
|                 oldStyle = controller.selected().style; | ||||
|  | ||||
|                 // Start a drag gesture | ||||
|                 controller.moveHandle().startDrag(); | ||||
|  | ||||
|                 // Haven't moved yet; style shouldn't have updated yet | ||||
|                 expect(controller.getSelectedElementStyle()).toEqual(oldStyle); | ||||
|                 expect(controller.selected().style).toEqual(oldStyle); | ||||
|  | ||||
|                 // Drag a little | ||||
|                 controller.moveHandle().continueDrag([1000, 100]); | ||||
|  | ||||
|                 // Style should have been updated | ||||
|                 expect(controller.getSelectedElementStyle()).not.toEqual(oldStyle); | ||||
|             }); | ||||
|  | ||||
|             it("cleans up selection 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) | ||||
|                 ); | ||||
|                 expect(controller.selected().style).not.toEqual(oldStyle); | ||||
|             }); | ||||
|  | ||||
|             describe("on display bounds changes", function () { | ||||
| @@ -697,7 +660,7 @@ define( | ||||
|                             source: 'range' | ||||
|                         } | ||||
|                     ]); | ||||
|                     var key = controller.chooseValueMetadataToDisplay(mockMetadata).source; | ||||
|                     var key = controller.chooseTelemetryKeyToDisplay(mockMetadata); | ||||
|                     expect(key).toEqual('range'); | ||||
|                 }); | ||||
|  | ||||
| @@ -719,7 +682,7 @@ define( | ||||
|                             } | ||||
|                         } | ||||
|                     ]); | ||||
|                     var key = controller.chooseValueMetadataToDisplay(mockMetadata).source; | ||||
|                     var key = controller.chooseTelemetryKeyToDisplay(mockMetadata); | ||||
|                     expect(key).toEqual('image'); | ||||
|                 }); | ||||
|  | ||||
| @@ -739,14 +702,6 @@ 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) | ||||
|                     ); | ||||
|                 }); | ||||
|  | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
|   | ||||
| @@ -475,11 +475,11 @@ define( | ||||
|                 ); | ||||
|  | ||||
|                 var childObj = mockDomainObject("d"); | ||||
|                 var testElement = $("<div data-layout-id='some-id'></div>"); | ||||
|                 var testElement = $("<div class='some-class'></div>"); | ||||
|                 $element.append(testElement); | ||||
|                 spyOn(testElement[0], 'click'); | ||||
|  | ||||
|                 controller.selectIfNew('some-id', childObj); | ||||
|                 controller.selectIfNew('some-class', childObj); | ||||
|                 jasmine.Clock.tick(0); | ||||
|  | ||||
|                 expect(testElement[0].click).toHaveBeenCalled(); | ||||
|   | ||||
							
								
								
									
										37
									
								
								platform/features/plot/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								platform/features/plot/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| # Plot README | ||||
|  | ||||
| ## Chart  | ||||
|  | ||||
| The `mct-chart` directive is used to support drawing of simple charts. It is  | ||||
| present to support the Plot view, and its functionality is limited to the  | ||||
| functionality that is relevant for that view. | ||||
|  | ||||
| This directive is used at the element level and takes one attribute, `draw`  | ||||
| which is an Angular expression which will should evaluate to a drawing object.  | ||||
| This drawing object should contain the following properties: | ||||
|  | ||||
| * `dimensions`: The size, in logical coordinates, of the chart area. A  | ||||
| two-element array or numbers.  | ||||
| * `origin`: The position, in logical coordinates, of the lower-left corner of  | ||||
| the chart area. A two-element array or numbers.  | ||||
| * `lines`: An array of lines (e.g. as a plot line) to draw, where each line is  | ||||
| expressed as an object containing:  | ||||
|     * `buffer`: A Float32Array containing points in the line, in logical  | ||||
|     coordinates, in sequential x,y pairs.  | ||||
|     * `color`: The color of the line, as a four-element RGBA array, where  | ||||
|     each element is a number in the range of 0.0-1.0.  | ||||
|     * `points`: The number of points in the line.  | ||||
| * `boxes`: An array of rectangles to draw in the chart area. Each is an object  | ||||
| containing:  | ||||
|     * `start`: The first corner of the rectangle, as a two-element array of  | ||||
|     numbers, in logical coordinates.  | ||||
|     * `end`: The opposite corner of the rectangle, as a two-element array of  | ||||
|     numbers, in logical coordinates. color : The color of the line, as a  | ||||
|     four-element RGBA array, where each element is a number in the range of  | ||||
|     0.0-1.0.  | ||||
|  | ||||
| While `mct-chart` is intended to support plots specifically, it does perform  | ||||
| some useful management of canvas objects (e.g. choosing between WebGL and Canvas  | ||||
| 2D APIs for drawing based on browser support) so its usage is recommended when  | ||||
| its supported drawing primitives are sufficient for other charting tasks.  | ||||
|   | ||||
							
								
								
									
										157
									
								
								platform/features/plot/bundle.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								platform/features/plot/bundle.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,157 @@ | ||||
| /***************************************************************************** | ||||
|  * 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([ | ||||
|     "./src/MCTChart", | ||||
|     "./src/PlotController", | ||||
|     "./src/policies/PlotViewPolicy", | ||||
|     "./src/PlotOptionsController", | ||||
|     "./src/services/ExportImageService", | ||||
|     "text!./res/templates/plot.html", | ||||
|     "text!./res/templates/plot-options-browse.html", | ||||
|     'legacyRegistry' | ||||
| ], function ( | ||||
|     MCTChart, | ||||
|     PlotController, | ||||
|     PlotViewPolicy, | ||||
|     PlotOptionsController, | ||||
|     exportImageService, | ||||
|     plotTemplate, | ||||
|     plotOptionsBrowseTemplate, | ||||
|     legacyRegistry | ||||
| ) { | ||||
|  | ||||
|     legacyRegistry.register("platform/features/plot", { | ||||
|         "name": "Plot view for telemetry", | ||||
|         "extensions": { | ||||
|             "views": [ | ||||
|                 { | ||||
|                     "name": "Plot", | ||||
|                     "key": "plot", | ||||
|                     "cssClass": "icon-sine", | ||||
|                     "template": plotTemplate, | ||||
|                     "needs": [ | ||||
|                         "telemetry" | ||||
|                     ], | ||||
|                     "priority": "preferred", | ||||
|                     "delegation": true | ||||
|                 } | ||||
|             ], | ||||
|             "directives": [ | ||||
|                 { | ||||
|                     "key": "mctChart", | ||||
|                     "implementation": MCTChart, | ||||
|                     "depends": [ | ||||
|                         "$interval", | ||||
|                         "$log" | ||||
|                     ] | ||||
|                 } | ||||
|             ], | ||||
|             "controllers": [ | ||||
|                 { | ||||
|                     "key": "PlotController", | ||||
|                     "implementation": PlotController, | ||||
|                     "depends": [ | ||||
|                         "$scope", | ||||
|                         "$element", | ||||
|                         "exportImageService", | ||||
|                         "telemetryFormatter", | ||||
|                         "telemetryHandler", | ||||
|                         "throttle", | ||||
|                         "PLOT_FIXED_DURATION", | ||||
|                         "openmct" | ||||
|                     ] | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "PlotOptionsController", | ||||
|                     "implementation": PlotOptionsController, | ||||
|                     "depends": [ | ||||
|                         "$scope" | ||||
|                     ] | ||||
|                 } | ||||
|             ], | ||||
|             "services": [ | ||||
|                 { | ||||
|                     "key": "exportImageService", | ||||
|                     "implementation": exportImageService, | ||||
|                     "depends": [ | ||||
|                         "$q", | ||||
|                         "$timeout", | ||||
|                         "$log", | ||||
|                         "EXPORT_IMAGE_TIMEOUT" | ||||
|                     ] | ||||
|  | ||||
|                 } | ||||
|             ], | ||||
|             "constants": [ | ||||
|                 { | ||||
|                     "key": "PLOT_FIXED_DURATION", | ||||
|                     "value": 900000, | ||||
|                     "priority": "fallback", | ||||
|                     "comment": "Fifteen minutes." | ||||
|                 }, | ||||
|                 { | ||||
|                     "key": "EXPORT_IMAGE_TIMEOUT", | ||||
|                     "value": 500, | ||||
|                     "priority": "fallback" | ||||
|                 } | ||||
|             ], | ||||
|             "policies": [ | ||||
|                 { | ||||
|                     "category": "view", | ||||
|                     "implementation": PlotViewPolicy, | ||||
|                     "depends": [ | ||||
|                         "openmct" | ||||
|                     ] | ||||
|                 } | ||||
|             ], | ||||
|             "representations": [ | ||||
|                 { | ||||
|                     "key": "plot-options-browse", | ||||
|                     "template": plotOptionsBrowseTemplate | ||||
|                 } | ||||
|             ], | ||||
|             "licenses": [ | ||||
|                 { | ||||
|                     "name": "FileSaver.js", | ||||
|                     "version": "0.0.2", | ||||
|                     "author": "Eli Grey", | ||||
|                     "description": "File download initiator (for file exports)", | ||||
|                     "website": "https://github.com/eligrey/FileSaver.js/", | ||||
|                     "copyright": "Copyright © 2015 Eli Grey.", | ||||
|                     "license": "license-mit", | ||||
|                     "link": "https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md" | ||||
|                 }, | ||||
|                 { | ||||
|                     "name": "html2canvas", | ||||
|                     "version": "0.4.1", | ||||
|                     "author": "Niklas von Hertzen", | ||||
|                     "description": "JavaScript HTML renderer", | ||||
|                     "website": "https://github.com/niklasvh/html2canvas", | ||||
|                     "copyright": "Copyright © 2012 Niklas von Hertzen.", | ||||
|                     "license": "license-mit", | ||||
|                     "link": "https://github.com/niklasvh/html2canvas/blob/master/LICENSE" | ||||
|                 } | ||||
|             ] | ||||
|         } | ||||
|     }); | ||||
| }); | ||||
| @@ -0,0 +1,70 @@ | ||||
| <!-- | ||||
|  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. | ||||
| --> | ||||
| <div ng-controller="PlotOptionsController" class="flex-elem grows l-inspector-part"> | ||||
|     <em class="t-inspector-part-header" title="Display properties for this object">Plot Options</em> | ||||
|     <mct-form | ||||
|             ng-model="configuration.plot.xAxis" | ||||
|             structure="xAxisForm" | ||||
|             name="xAxisFormState" | ||||
|             class="flex-elem l-flex-row no-margin"> | ||||
|     </mct-form> | ||||
|     <mct-form | ||||
|             ng-model="configuration.plot.yAxis" | ||||
|             structure="yAxisForm" | ||||
|             name="yAxisFormState" | ||||
|             class="flex-elem l-flex-row no-margin"> | ||||
|     </mct-form> | ||||
|     <div class="form"> | ||||
|         <div class="section-header ng-binding ng-scope"> | ||||
|             Plot Series | ||||
|         </div> | ||||
|         <ul class="first flex-elem grows vscroll"> | ||||
|             <ul class="tree"> | ||||
|                 <li ng-repeat="child in children"> | ||||
|                     <span ng-controller="ToggleController as toggle"> | ||||
|                         <span ng-controller="TreeNodeController as treeNode"> | ||||
|                             <span class="tree-item menus-to-left"> | ||||
|                                 <span | ||||
|                                     class='ui-symbol view-control flex-elem has-children' | ||||
|                                     ng-class="{ expanded: toggle.isActive() }" | ||||
|                                     ng-click="toggle.toggle(); treeNode.trackExpansion()"> | ||||
|                                 </span> | ||||
|                                 <mct-representation | ||||
|                                     class="rep-object-label" | ||||
|                                     key="'label'" | ||||
|                                     mct-object="child"> | ||||
|                                 </mct-representation> | ||||
|                             </span> | ||||
|                         </span> | ||||
|                         <mct-form | ||||
|                             ng-class="{hidden: !toggle.isActive()}" | ||||
|                             ng-model="configuration.plot.series[$index]" | ||||
|                             structure="plotSeriesForm" | ||||
|                             name="plotOptionsState" | ||||
|                             class="flex-elem l-flex-row"> | ||||
|                         </mct-form> | ||||
|                     </span> | ||||
|                 </li> | ||||
|             </ul> | ||||
|         </ul> | ||||
|     </div> | ||||
| </div> | ||||
							
								
								
									
										165
									
								
								platform/features/plot/res/templates/plot.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										165
									
								
								platform/features/plot/res/templates/plot.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,165 @@ | ||||
| <!-- | ||||
|  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. | ||||
| --> | ||||
| <span ng-controller="PlotController as plot" | ||||
|       class="abs holder holder-plot has-control-bar"> | ||||
|     <div class="l-control-bar" ng-show="!plot.hideExportButtons"> | ||||
|          <span class="l-btn-set"> | ||||
|             <a class="s-button t-export labeled icon-download" | ||||
|                ng-click="plot.exportPNG()" | ||||
|                title="Export This View's Data as PNG"> | ||||
|                 PNG | ||||
|             </a> | ||||
|             <a class="s-button t-export labeled" | ||||
|                ng-click="plot.exportJPG()" | ||||
|                title="Export This View's Data as JPG"> | ||||
|                 JPG | ||||
|             </a> | ||||
|         </span> | ||||
|     </div> | ||||
|     <div class="l-view-section"> | ||||
|         <div class="gl-plot" | ||||
|              ng-style="{ height: 100 / plot.getSubPlots().length + '%'}" | ||||
|              ng-repeat="subplot in plot.getSubPlots()"> | ||||
|             <div class="gl-plot-legend"> | ||||
|                 <span class='plot-legend-item' | ||||
|                         ng-repeat="telemetryObject in subplot.getTelemetryObjects()" | ||||
|                         ng-class="plot.getLegendClass(telemetryObject)"> | ||||
|                     <span class='plot-color-swatch' | ||||
|                           ng-style="{ 'background-color': plot.getColor($index) }"> | ||||
|                     </span> | ||||
|                     <span class='title-label'>{{telemetryObject.getModel().name}}</span> | ||||
|                 </span> | ||||
|             </div> | ||||
|  | ||||
|             <div class="gl-plot-axis-area gl-plot-y"> | ||||
|                 <div class="gl-plot-label gl-plot-y-label"> | ||||
|                     {{axes[1].active.name}} | ||||
|                 </div> | ||||
|                 <div ng-repeat="tick in subplot.getRangeTicks()" | ||||
|                      class="gl-plot-tick gl-plot-y-tick-label" | ||||
|                      ng-style="{ bottom: (100 * $index / (subplot.getRangeTicks().length - 1)) + '%' }"> | ||||
|                     {{tick.label | reverse}} | ||||
|                 </div> | ||||
|                 <div class="gl-plot-y-options gl-plot-local-controls" | ||||
|                      ng-if="axes[1].options.length > 1"> | ||||
|                     <div class='form-control shell select'> | ||||
|                         <select class="form-control input shell" | ||||
|                                 ng-model="axes[1].active" | ||||
|                                 ng-options="option.name for option in axes[1].options"> | ||||
|                         </select> | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </div> | ||||
|  | ||||
|             <div class="gl-plot-wrapper-display-area-and-x-axis"> | ||||
|                 <mct-include key="'time-of-interest'" | ||||
|                              class="l-toi-holder show-val" | ||||
|                              ng-if="toiPerc" | ||||
|                              ng-class="{ 'pinned': toiPinned, 'val-to-left': toiPerc > 80 }" | ||||
|                              ng-style="{'left': toiPerc + '%'}"></mct-include> | ||||
|  | ||||
|                 <div class="gl-plot-coords" | ||||
|                      ng-if="subplot.isHovering() && subplot.getHoverCoordinates()"> | ||||
|                     {{subplot.getHoverCoordinates()}} | ||||
|                 </div> | ||||
|  | ||||
|                 <div class="gl-plot-display-area" | ||||
|                      ng-mouseenter="subplot.isHovering(true);" | ||||
|                      ng-mouseleave="subplot.isHovering(false)" | ||||
|                      ng-class="{ loading: plot.isRequestPending() }"> | ||||
|  | ||||
|                     <!-- Out-of-bounds data indicators --> | ||||
|                     <!-- ng-show is temporarily hard-coded in next element --> | ||||
|                     <div ng-show="false" class="l-oob-data l-oob-data-up"></div> | ||||
|                     <div ng-show="false" class="l-oob-data l-oob-data-dwn"></div> | ||||
|                     <div class="gl-plot-hash hash-v" | ||||
|                          ng-repeat="tick in subplot.getDomainTicks()" | ||||
|                          ng-style="{ left: (100 * $index / (subplot.getDomainTicks().length - 1)) + '%', height: '100%' }" | ||||
|                          ng-show="$index > 0 && $index < (subplot.getDomainTicks().length - 1)"> | ||||
|                     </div> | ||||
|                     <div class="gl-plot-hash hash-h" | ||||
|                          ng-repeat="tick in subplot.getRangeTicks()" | ||||
|                          ng-style="{ bottom: (100 * $index / (subplot.getRangeTicks().length - 1)) + '%', width: '100%' }" | ||||
|                          ng-show="$index > 0 && $index < (subplot.getRangeTicks().length - 1)"> | ||||
|                     </div> | ||||
|                     <mct-chart draw="subplot.getDrawingObject()" | ||||
|                                ng-if="subplot.getTelemetryObjects().length > 0" | ||||
|                                ng-mousemove="subplot.hover($event)" | ||||
|                                mct-drag="subplot.continueDrag($event)" | ||||
|                                mct-drag-down="subplot.startDrag($event)" | ||||
|                                mct-drag-up="subplot.endDrag($event); plot.update()"> | ||||
|                     </mct-chart> | ||||
|                     <!-- TODO: Move into correct position; make part of group; infer from set of actions --> | ||||
|                     <div class="l-local-controls gl-plot-local-controls t-plot-display-controls" | ||||
|                          ng-if="$first"> | ||||
|                         <a class="s-button icon-arrow-left" | ||||
|                            ng-click="plot.stepBackPanZoom()" | ||||
|                            ng-show="plot.isZoomed()" | ||||
|                            title="Restore previous pan/zoom"> | ||||
|                         </a> | ||||
|                         <a class="s-button icon-arrows-out" | ||||
|                            ng-click="plot.unzoom()" | ||||
|                            ng-show="plot.isZoomed()" | ||||
|                            title="Reset pan/zoom"> | ||||
|                         </a> | ||||
|                         <div class="menu-element s-menu-button menus-to-left {{plot.getMode().cssClass}}" | ||||
|                              ng-if="plot.getModeOptions().length > 1" | ||||
|                              ng-controller="ClickAwayController as toggle"> | ||||
|                             <span class="l-click-area" ng-click="toggle.toggle()"></span> | ||||
|                             <span>{{plot.getMode().name}}</span> | ||||
|                             <div class="menu" ng-show="toggle.isActive()"> | ||||
|                                 <ul> | ||||
|                                     <li ng-repeat="option in plot.getModeOptions()" | ||||
|                                         ng-click="plot.setMode(option); toggle.setState(false)" | ||||
|                                         class="{{option.cssClass}}"> | ||||
|                                         {{option.name}} | ||||
|                                     </li> | ||||
|                                 </ul> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 </div> | ||||
|                 <div ng-if="$last" class="gl-plot-axis-area gl-plot-x"> | ||||
|                     <div ng-repeat="tick in subplot.getDomainTicks()" | ||||
|                          class="gl-plot-tick gl-plot-x-tick-label" | ||||
|                          ng-show="$index > 0 && $index < (subplot.getDomainTicks().length - 1)" | ||||
|                          ng-style="{ left: (100 * $index / (subplot.getDomainTicks().length - 1)) + '%' }"> | ||||
|                         {{tick.label | reverse}} | ||||
|                     </div> | ||||
|                     <div class="gl-plot-label gl-plot-x-label"> | ||||
|                         {{axes[0].active.name}} | ||||
|                     </div> | ||||
|                     <div class="gl-plot-x-options gl-plot-local-controls" | ||||
|                          ng-if="axes[0].options.length > 1"> | ||||
|                         <div class='form-control shell select'> | ||||
|                             <select class="form-control input shell" | ||||
|                                     ng-model="axes[0].active" | ||||
|                                     ng-options="option.name for option in axes[0].options"> | ||||
|                             </select> | ||||
|                         </div> | ||||
|                     </div> | ||||
|  | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
| </span> | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user