mirror of
https://github.com/nasa/openmct.git
synced 2025-06-25 18:50:11 +00:00
Compare commits
1 Commits
plot-wait-
...
edit-sizin
Author | SHA1 | Date | |
---|---|---|---|
c2db5c3cce |
@ -21,6 +21,5 @@
|
|||||||
"shadow": "outer",
|
"shadow": "outer",
|
||||||
"strict": "implied",
|
"strict": "implied",
|
||||||
"undef": true,
|
"undef": true,
|
||||||
"unused": "vars",
|
"unused": "vars"
|
||||||
"latedef": "nofunc"
|
|
||||||
}
|
}
|
||||||
|
183
API.md
183
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](#telemetry-requests)
|
|
||||||
- [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 Visualization APIs **draft**](#telemetry-visualization-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
|
# Building Applications With Open MCT
|
||||||
|
|
||||||
## Scope and purpose of this document
|
## Scope and purpose of this document
|
||||||
@ -208,7 +154,7 @@ registry.
|
|||||||
|
|
||||||
eg.
|
eg.
|
||||||
```javascript
|
```javascript
|
||||||
openmct.types.addType('example.my-type', {
|
openmct.types.addType('my-type', {
|
||||||
name: "My Type",
|
name: "My Type",
|
||||||
description: "This is a type that I added!",
|
description: "This is a type that I added!",
|
||||||
creatable: true
|
creatable: true
|
||||||
@ -216,9 +162,8 @@ openmct.types.addType('example.my-type', {
|
|||||||
```
|
```
|
||||||
|
|
||||||
The `addType` function accepts two arguments:
|
The `addType` function accepts two arguments:
|
||||||
* A `string` key identifying the type. This key is used when specifying a type
|
* 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
|
for an object.
|
||||||
conflicts with other plugins.
|
|
||||||
* An object type specification. An object type definition supports the following
|
* An object type specification. An object type definition supports the following
|
||||||
attributes
|
attributes
|
||||||
* `name`: a `string` naming this object type
|
* `name`: a `string` naming this object type
|
||||||
@ -249,7 +194,7 @@ To do so, use the `addRoot` method of the object API.
|
|||||||
eg.
|
eg.
|
||||||
```javascript
|
```javascript
|
||||||
openmct.objects.addRoot({
|
openmct.objects.addRoot({
|
||||||
namespace: "example.namespace",
|
namespace: "my-namespace",
|
||||||
key: "my-key"
|
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)
|
[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
|
||||||
that resolves with the object being requested.
|
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
|
## Composition Providers
|
||||||
|
|
||||||
The _composition_ of a domain object is the list of objects it contains, as
|
The _composition_ of a domain object is the list of objects it contains, as shown
|
||||||
shown (for example) in the tree for browsing. Open MCT provides a
|
(for example) in the tree for browsing. Open MCT provides a
|
||||||
[default solution](#default-composition-provider) for composition, but there
|
[default solution](#default-composition-provider) for composition, but there
|
||||||
may be cases where you want to provide the composition of a certain object
|
may be cases where you want to provide the composition of a certain object
|
||||||
(or type of object) dynamically.
|
(or type of object) dynamically.
|
||||||
@ -309,7 +255,7 @@ Composition Provider:
|
|||||||
```javascript
|
```javascript
|
||||||
openmct.composition.addProvider({
|
openmct.composition.addProvider({
|
||||||
appliesTo: function (domainObject) {
|
appliesTo: function (domainObject) {
|
||||||
return domainObject.type === 'example.my-type';
|
return domainObject.type === 'my-type';
|
||||||
},
|
},
|
||||||
load: function (domainObject) {
|
load: function (domainObject) {
|
||||||
return Promise.resolve(myDomainObjects);
|
return Promise.resolve(myDomainObjects);
|
||||||
@ -327,9 +273,8 @@ These identifiers will be used to fetch Domain Objects from an [Object Provider]
|
|||||||
|
|
||||||
### Default Composition Provider
|
### Default Composition Provider
|
||||||
|
|
||||||
The default composition provider applies to any domain object with a
|
The default composition provider applies to any domain object with a `composition`
|
||||||
`composition` property. The value of `composition` should be an array of
|
property. The value of `composition` should be an array of identifiers, e.g.:
|
||||||
identifiers, e.g.:
|
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
var domainObject = {
|
var domainObject = {
|
||||||
@ -350,17 +295,13 @@ var domainObject = {
|
|||||||
|
|
||||||
## Telemetry API
|
## Telemetry API
|
||||||
|
|
||||||
The Open MCT telemetry API provides two main sets of interfaces-- one for
|
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.
|
||||||
integrating telemetry data into Open MCT, and another for developing Open MCT
|
|
||||||
visualization plugins utilizing the telemetry API.
|
|
||||||
|
|
||||||
The APIs for visualization plugins are still a work in progress and docs may
|
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.
|
||||||
change at any time. However, the APIs for integrating telemetry metadata into
|
|
||||||
Open MCT are stable and documentation is included below.
|
|
||||||
|
|
||||||
### Integrating Telemetry Sources
|
### 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
|
For a step-by-step guide to building a telemetry adapter, please see the
|
||||||
[Open MCT Tutorials](https://github.com/nasa/openmct-tutorial).
|
[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.
|
`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.
|
`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`.
|
`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.
|
`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`
|
`units` | string | optional | the units of this value, e.g. `km`, `seconds`, `parsecs`
|
||||||
@ -442,7 +383,7 @@ In order for the time conductor to work, there will always be an active "time sy
|
|||||||
|
|
||||||
#### Telemetry Providers
|
#### 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:
|
A telemetry provider is a javascript object with up to four methods:
|
||||||
|
|
||||||
@ -450,10 +391,6 @@ A telemetry provider is a javascript object with up to four methods:
|
|||||||
* `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.
|
* `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.
|
* `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. For more request properties, see Request Properties below.
|
* `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.
|
||||||
* `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.
|
|
||||||
|
|
||||||
Telemetry providers are registered by calling `openmct.telemetry.addProvider(provider)`, e.g.
|
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({
|
openmct.telemetry.addProvider({
|
||||||
supportsRequest: function (domainObject, options) { /*...*/ },
|
supportsRequest: function (domainObject, options) { /*...*/ },
|
||||||
request: 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
|
#### 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:
|
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
|
```javascript
|
||||||
{
|
{
|
||||||
start: 1487981997240,
|
start: 1487981997240,
|
||||||
@ -478,54 +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.
|
#### Telemetry Formats
|
||||||
|
|
||||||
The response to a telemetry request is 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 format objects define how to interpret and display telemetry data.
|
Telemetry format objects define how to interpret and display telemetry data.
|
||||||
They have a simple structure:
|
They have a simple structure:
|
||||||
@ -595,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)
|
a domain object, as discussed in the [Telemetry Metadata](#telemetry-metadata)
|
||||||
section.
|
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
|
## Time API
|
||||||
|
|
||||||
Open MCT provides API for managing the temporal state of the application.
|
Open MCT provides API for managing the temporal state of the application.
|
||||||
@ -713,7 +591,7 @@ openmct.time.bounds({start: now - ONE_HOUR, now);
|
|||||||
To respond to bounds change events, listen for the [`'bounds'`](#time-events)
|
To respond to bounds change events, listen for the [`'bounds'`](#time-events)
|
||||||
event.
|
event.
|
||||||
|
|
||||||
### Clocks
|
## Clocks
|
||||||
|
|
||||||
The Time API can be set to follow a clock source which will cause the bounds
|
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
|
to be updated automatically whenever the clock source "ticks". A clock is simply
|
||||||
@ -732,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
|
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).
|
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:
|
A clock is an object that defines certain required metadata and functions:
|
||||||
|
|
||||||
@ -846,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.
|
new bounds will be calculated based on the `currentValue()` of the active clock.
|
||||||
Clock offsets are only relevant when a clock source is active.
|
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.
|
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.
|
||||||
|
|
||||||
@ -888,7 +766,7 @@ The events emitted by the Time API are:
|
|||||||
* `clockOffsets`: The new [clock offsets](#clock-offsets).
|
* `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
|
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.
|
MCT. It allows a user to select from configured time systems and clocks, and to set bounds and clock offsets.
|
||||||
@ -1001,21 +879,6 @@ openmct.install(openmct.plugins.CouchDB('http://localhost:9200'))
|
|||||||
* `openmct.plugins.Espresso` and `openmct.plugins.Snow` are two different
|
* `openmct.plugins.Espresso` and `openmct.plugins.Snow` are two different
|
||||||
themes (dark and light) available for Open MCT. Note that at least one
|
themes (dark and light) available for Open MCT. Note that at least one
|
||||||
of these themes must be installed for Open MCT to appear correctly.
|
of these themes must be installed for Open MCT to appear correctly.
|
||||||
* `openmct.plugins.URLIndicatorPlugin` adds an indicator which shows the
|
|
||||||
availability of a URL with the following options:
|
|
||||||
- `url` : URL to indicate the status of
|
|
||||||
- `cssClass`: Icon to show in the status bar, defaults to `icon-database`, [list of all icons](https://nasa.github.io/openmct/style-guide/#/browse/styleguide:home?view=items)
|
|
||||||
- `interval`: Interval between checking the connection, defaults to `10000`
|
|
||||||
- `label` Name showing up as text in the status bar, defaults to url
|
|
||||||
```javascript
|
|
||||||
openmct.install(openmct.plugins.URLIndicatorPlugin({
|
|
||||||
url: 'http://google.com',
|
|
||||||
cssClass: 'check',
|
|
||||||
interval: 10000,
|
|
||||||
label: 'Google'
|
|
||||||
})
|
|
||||||
);
|
|
||||||
```
|
|
||||||
* `openmct.plugins.LocalStorage` provides persistence of user-created
|
* `openmct.plugins.LocalStorage` provides persistence of user-created
|
||||||
objects in browser-local storage. This is particularly useful in
|
objects in browser-local storage. This is particularly useful in
|
||||||
development environments.
|
development environments.
|
||||||
|
@ -88,7 +88,7 @@ and [`gulp`](http://gulpjs.com/).
|
|||||||
|
|
||||||
To build Open MCT for deployment:
|
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.
|
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
|
The contents of the `dist` folder will contain a runnable Open MCT
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
"screenfull": "^3.0.0",
|
"screenfull": "^3.0.0",
|
||||||
"node-uuid": "^1.4.7",
|
"node-uuid": "^1.4.7",
|
||||||
"comma-separated-values": "^3.6.4",
|
"comma-separated-values": "^3.6.4",
|
||||||
"file-saver": "1.3.3",
|
"FileSaver.js": "^0.0.2",
|
||||||
"zepto": "^1.1.6",
|
"zepto": "^1.1.6",
|
||||||
"eventemitter3": "^1.2.0",
|
"eventemitter3": "^1.2.0",
|
||||||
"lodash": "3.10.1",
|
"lodash": "3.10.1",
|
||||||
|
10
circle.yml
10
circle.yml
@ -1,11 +1,3 @@
|
|||||||
machine:
|
|
||||||
node:
|
|
||||||
version: 4.7.0
|
|
||||||
|
|
||||||
dependencies:
|
|
||||||
pre:
|
|
||||||
- npm install -g npm@latest
|
|
||||||
|
|
||||||
deployment:
|
deployment:
|
||||||
production:
|
production:
|
||||||
branch: master
|
branch: master
|
||||||
@ -24,4 +16,4 @@ test:
|
|||||||
general:
|
general:
|
||||||
branches:
|
branches:
|
||||||
ignore:
|
ignore:
|
||||||
- gh-pages
|
- gh-pages
|
||||||
|
@ -2283,7 +2283,7 @@ To install build dependencies (only needs to be run once):
|
|||||||
|
|
||||||
To build:
|
To build:
|
||||||
|
|
||||||
`npm run prepare`
|
`npm run prepublish`
|
||||||
|
|
||||||
This will compile and minify JavaScript sources, as well as copy over assets.
|
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
|
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,
|
amplitude: 1,
|
||||||
period: 10,
|
period: 10,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
dataRateInHz: 1,
|
dataRateInHz: 1
|
||||||
phase: 0
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function GeneratorProvider() {
|
function GeneratorProvider() {
|
||||||
@ -51,27 +50,24 @@ define([
|
|||||||
'amplitude',
|
'amplitude',
|
||||||
'period',
|
'period',
|
||||||
'offset',
|
'offset',
|
||||||
'dataRateInHz',
|
'dataRateInHz'
|
||||||
'phase',
|
|
||||||
];
|
];
|
||||||
|
|
||||||
request = request || {};
|
|
||||||
|
|
||||||
var workerRequest = {};
|
var workerRequest = {};
|
||||||
|
|
||||||
props.forEach(function (prop) {
|
props.forEach(function (prop) {
|
||||||
if (domainObject.telemetry && domainObject.telemetry.hasOwnProperty(prop)) {
|
if (domainObject.telemetry && domainObject.telemetry.hasOwnProperty(prop)) {
|
||||||
workerRequest[prop] = domainObject.telemetry[prop];
|
workerRequest[prop] = domainObject.telemetry[prop];
|
||||||
}
|
}
|
||||||
if (request && request.hasOwnProperty(prop)) {
|
if (request.hasOwnProperty(prop)) {
|
||||||
workerRequest[prop] = request[prop];
|
workerRequest[prop] = request[prop];
|
||||||
}
|
}
|
||||||
if (!workerRequest.hasOwnProperty(prop)) {
|
if (!workerRequest[prop]) {
|
||||||
workerRequest[prop] = REQUEST_DEFAULTS[prop];
|
workerRequest[prop] = REQUEST_DEFAULTS[prop];
|
||||||
}
|
}
|
||||||
workerRequest[prop] = Number(workerRequest[prop]);
|
workerRequest[prop] = Number(workerRequest[prop]);
|
||||||
});
|
});
|
||||||
workerRequest.name = domainObject.name;
|
|
||||||
return workerRequest;
|
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,80 +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();
|
|
||||||
callback(pointForTimestamp(now, duration, domainObject.name));
|
|
||||||
}, duration);
|
|
||||||
|
|
||||||
return function () {
|
|
||||||
clearInterval(interval);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
StateGeneratorProvider.prototype.supportsRequest = function (domainObject, options) {
|
|
||||||
return domainObject.type === 'example.state-generator';
|
|
||||||
};
|
|
||||||
|
|
||||||
StateGeneratorProvider.prototype.request = function (domainObject, options) {
|
|
||||||
var start = options.start;
|
|
||||||
var end = options.end;
|
|
||||||
var duration = domainObject.telemetry.duration * 1000;
|
|
||||||
if (options.strategy === 'latest' || options.size === 1) {
|
|
||||||
start = end;
|
|
||||||
}
|
|
||||||
var data = [];
|
|
||||||
while (start <= end && data.length < 5000) {
|
|
||||||
data.push(pointForTimestamp(start, duration, domainObject.name));
|
|
||||||
start += 5000;
|
|
||||||
}
|
|
||||||
return Promise.resolve(data);
|
|
||||||
};
|
|
||||||
|
|
||||||
return StateGeneratorProvider;
|
|
||||||
|
|
||||||
});
|
|
@ -44,7 +44,9 @@ define([
|
|||||||
message = message.data;
|
message = message.data;
|
||||||
var callback = this.callbacks[message.id];
|
var callback = this.callbacks[message.id];
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback(message);
|
if (callback(message)) {
|
||||||
|
delete this.callbacks[message.id];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -70,7 +72,6 @@ define([
|
|||||||
deferred.resolve = resolve;
|
deferred.resolve = resolve;
|
||||||
deferred.reject = reject;
|
deferred.reject = reject;
|
||||||
});
|
});
|
||||||
var messageId;
|
|
||||||
|
|
||||||
function callback(message) {
|
function callback(message) {
|
||||||
if (message.error) {
|
if (message.error) {
|
||||||
@ -78,27 +79,33 @@ define([
|
|||||||
} else {
|
} else {
|
||||||
deferred.resolve(message.data);
|
deferred.resolve(message.data);
|
||||||
}
|
}
|
||||||
delete this.callbacks[messageId];
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
messageId = this.dispatch('request', request, callback.bind(this));
|
this.dispatch('request', request, callback);
|
||||||
|
|
||||||
return promise;
|
return promise;
|
||||||
};
|
};
|
||||||
|
|
||||||
WorkerInterface.prototype.subscribe = function (request, cb) {
|
WorkerInterface.prototype.subscribe = function (request, cb) {
|
||||||
function callback(message) {
|
var isCancelled = false;
|
||||||
|
|
||||||
|
var callback = function (message) {
|
||||||
|
if (isCancelled) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
cb(message.data);
|
cb(message.data);
|
||||||
};
|
};
|
||||||
|
|
||||||
var messageId = this.dispatch('subscribe', request, callback);
|
var messageId = this.dispatch('subscribe', request, callback)
|
||||||
|
|
||||||
return function () {
|
return function () {
|
||||||
|
isCancelled = true;
|
||||||
this.dispatch('unsubscribe', {
|
this.dispatch('unsubscribe', {
|
||||||
id: messageId
|
id: messageId
|
||||||
});
|
});
|
||||||
delete this.callbacks[messageId];
|
|
||||||
}.bind(this);
|
}.bind(this);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -62,11 +62,10 @@
|
|||||||
self.postMessage({
|
self.postMessage({
|
||||||
id: message.id,
|
id: message.id,
|
||||||
data: {
|
data: {
|
||||||
name: data.name,
|
|
||||||
utc: nextStep,
|
utc: nextStep,
|
||||||
yesterday: nextStep - 60*60*24*1000,
|
yesterday: nextStep - 60*60*24*1000,
|
||||||
sin: sin(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, data.phase)
|
cos: cos(nextStep, data.period, data.amplitude, data.offset)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
nextStep += step;
|
nextStep += step;
|
||||||
@ -83,22 +82,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onRequest(message) {
|
function onRequest(message) {
|
||||||
var request = message.data;
|
var data = message.data;
|
||||||
if (request.end == undefined) {
|
if (data.end == undefined) {
|
||||||
request.end = Date.now();
|
data.end = Date.now();
|
||||||
}
|
}
|
||||||
if (request.start == undefined){
|
if (data.start == undefined){
|
||||||
request.start = request.end - FIFTEEN_MINUTES;
|
data.start = data.end - FIFTEEN_MINUTES;
|
||||||
}
|
}
|
||||||
|
|
||||||
var now = Date.now();
|
var now = Date.now();
|
||||||
var start = request.start;
|
var start = data.start;
|
||||||
var end = request.end > now ? now : request.end;
|
var end = data.end > now ? now : data.end;
|
||||||
var amplitude = request.amplitude;
|
var amplitude = data.amplitude;
|
||||||
var period = request.period;
|
var period = data.period;
|
||||||
var offset = request.offset;
|
var offset = data.offset;
|
||||||
var dataRateInHz = request.dataRateInHz;
|
var dataRateInHz = data.dataRateInHz;
|
||||||
var phase = request.phase;
|
|
||||||
|
|
||||||
var step = 1000 / dataRateInHz;
|
var step = 1000 / dataRateInHz;
|
||||||
var nextStep = start - (start % step) + step;
|
var nextStep = start - (start % step) + step;
|
||||||
@ -107,11 +105,10 @@
|
|||||||
|
|
||||||
for (; nextStep < end && data.length < 5000; nextStep += step) {
|
for (; nextStep < end && data.length < 5000; nextStep += step) {
|
||||||
data.push({
|
data.push({
|
||||||
name: request.name,
|
|
||||||
utc: nextStep,
|
utc: nextStep,
|
||||||
yesterday: nextStep - 60*60*24*1000,
|
yesterday: nextStep - 60*60*24*1000,
|
||||||
sin: sin(nextStep, period, amplitude, offset, phase),
|
sin: sin(nextStep, period, amplitude, offset),
|
||||||
cos: cos(nextStep, period, amplitude, offset, phase)
|
cos: cos(nextStep, period, amplitude, offset)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
self.postMessage({
|
self.postMessage({
|
||||||
@ -120,14 +117,14 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function cos(timestamp, period, amplitude, offset, phase) {
|
function cos(timestamp, period, amplitude, offset) {
|
||||||
return amplitude *
|
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 *
|
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) {
|
function sendError(error, message) {
|
||||||
|
@ -23,46 +23,29 @@
|
|||||||
|
|
||||||
define([
|
define([
|
||||||
"./GeneratorProvider",
|
"./GeneratorProvider",
|
||||||
"./SinewaveLimitProvider",
|
"./SinewaveLimitCapability"
|
||||||
"./StateGeneratorProvider",
|
|
||||||
"./GeneratorMetadataProvider"
|
|
||||||
], function (
|
], function (
|
||||||
GeneratorProvider,
|
GeneratorProvider,
|
||||||
SinewaveLimitProvider,
|
SinewaveLimitCapability
|
||||||
StateGeneratorProvider,
|
|
||||||
GeneratorMetadataProvider
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
return function(openmct){
|
var legacyExtensions = {
|
||||||
|
"capabilities": [
|
||||||
openmct.types.addType("example.state-generator", {
|
{
|
||||||
name: "State Generator",
|
"key": "limit",
|
||||||
description: "For development use. Generates test enumerated telemetry by cycling through a given set of states",
|
"implementation": SinewaveLimitCapability
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
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", {
|
openmct.types.addType("generator", {
|
||||||
name: "Sine Wave Generator",
|
name: "Sine Wave Generator",
|
||||||
description: "For development use. Generates example streaming telemetry data using a simple sine wave algorithm.",
|
description: "For development use. Generates example streaming telemetry data using a simple sine wave algorithm.",
|
||||||
@ -71,58 +54,51 @@ define([
|
|||||||
form: [
|
form: [
|
||||||
{
|
{
|
||||||
name: "Period",
|
name: "Period",
|
||||||
control: "numberfield",
|
control: "textfield",
|
||||||
cssClass: "l-input-sm l-numeric",
|
cssClass: "l-input-sm l-numeric",
|
||||||
key: "period",
|
key: "period",
|
||||||
required: true,
|
required: true,
|
||||||
property: [
|
property: [
|
||||||
"telemetry",
|
"telemetry",
|
||||||
"period"
|
"period"
|
||||||
]
|
],
|
||||||
|
pattern: "^\\d*(\\.\\d*)?$"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Amplitude",
|
name: "Amplitude",
|
||||||
control: "numberfield",
|
control: "textfield",
|
||||||
cssClass: "l-input-sm l-numeric",
|
cssClass: "l-input-sm l-numeric",
|
||||||
key: "amplitude",
|
key: "amplitude",
|
||||||
required: true,
|
required: true,
|
||||||
property: [
|
property: [
|
||||||
"telemetry",
|
"telemetry",
|
||||||
"amplitude"
|
"amplitude"
|
||||||
]
|
],
|
||||||
|
pattern: "^\\d*(\\.\\d*)?$"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Offset",
|
name: "Offset",
|
||||||
control: "numberfield",
|
control: "textfield",
|
||||||
cssClass: "l-input-sm l-numeric",
|
cssClass: "l-input-sm l-numeric",
|
||||||
key: "offset",
|
key: "offset",
|
||||||
required: true,
|
required: true,
|
||||||
property: [
|
property: [
|
||||||
"telemetry",
|
"telemetry",
|
||||||
"offset"
|
"offset"
|
||||||
]
|
],
|
||||||
|
pattern: "^\\d*(\\.\\d*)?$"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Data Rate (hz)",
|
name: "Data Rate (hz)",
|
||||||
control: "numberfield",
|
control: "textfield",
|
||||||
cssClass: "l-input-sm l-numeric",
|
cssClass: "l-input-sm l-numeric",
|
||||||
key: "dataRateInHz",
|
key: "dataRateInHz",
|
||||||
required: true,
|
required: true,
|
||||||
property: [
|
property: [
|
||||||
"telemetry",
|
"telemetry",
|
||||||
"dataRateInHz"
|
"dataRateInHz"
|
||||||
]
|
],
|
||||||
},
|
pattern: "^\\d*(\\.\\d*)?$"
|
||||||
{
|
|
||||||
name: "Phase (radians)",
|
|
||||||
control: "numberfield",
|
|
||||||
cssClass: "l-input-sm l-numeric",
|
|
||||||
key: "phase",
|
|
||||||
required: true,
|
|
||||||
property: [
|
|
||||||
"telemetry",
|
|
||||||
"phase"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
initialize: function (object) {
|
initialize: function (object) {
|
||||||
@ -131,14 +107,42 @@ define([
|
|||||||
amplitude: 1,
|
amplitude: 1,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
dataRateInHz: 1,
|
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 GeneratorProvider());
|
||||||
openmct.telemetry.addProvider(new GeneratorMetadataProvider());
|
|
||||||
openmct.telemetry.addProvider(new SinewaveLimitProvider());
|
|
||||||
};
|
};
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -48,9 +48,8 @@ define([
|
|||||||
"https://www.hq.nasa.gov/alsj/a16/AS16-117-18748.jpg"
|
"https://www.hq.nasa.gov/alsj/a16/AS16-117-18748.jpg"
|
||||||
];
|
];
|
||||||
|
|
||||||
function pointForTimestamp(timestamp, name) {
|
function pointForTimestamp(timestamp) {
|
||||||
return {
|
return {
|
||||||
name: name,
|
|
||||||
utc: Math.floor(timestamp / 5000) * 5000,
|
utc: Math.floor(timestamp / 5000) * 5000,
|
||||||
url: IMAGE_SAMPLES[Math.floor(timestamp / 5000) % IMAGE_SAMPLES.length]
|
url: IMAGE_SAMPLES[Math.floor(timestamp / 5000) % IMAGE_SAMPLES.length]
|
||||||
};
|
};
|
||||||
@ -62,7 +61,7 @@ define([
|
|||||||
},
|
},
|
||||||
subscribe: function (domainObject, callback) {
|
subscribe: function (domainObject, callback) {
|
||||||
var interval = setInterval(function () {
|
var interval = setInterval(function () {
|
||||||
callback(pointForTimestamp(Date.now(), domainObject.name));
|
callback(pointForTimestamp(Date.now()));
|
||||||
}, 5000);
|
}, 5000);
|
||||||
|
|
||||||
return function (interval) {
|
return function (interval) {
|
||||||
@ -80,8 +79,8 @@ define([
|
|||||||
var start = options.start;
|
var start = options.start;
|
||||||
var end = options.end;
|
var end = options.end;
|
||||||
var data = [];
|
var data = [];
|
||||||
while (start <= end && data.length < 5000) {
|
while (start < end && data.length < 5000) {
|
||||||
data.push(pointForTimestamp(start, domainObject.name));
|
data.push(pointForTimestamp(start));
|
||||||
start += 5000;
|
start += 5000;
|
||||||
}
|
}
|
||||||
return Promise.resolve(data);
|
return Promise.resolve(data);
|
||||||
@ -94,7 +93,7 @@ define([
|
|||||||
options.strategy === 'latest';
|
options.strategy === 'latest';
|
||||||
},
|
},
|
||||||
request: function (domainObject, options) {
|
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) {
|
initialize: function (object) {
|
||||||
object.telemetry = {
|
object.telemetry = {
|
||||||
values: [
|
values: [
|
||||||
{
|
|
||||||
name: 'Name',
|
|
||||||
key: 'name'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'Time',
|
name: 'Time',
|
||||||
key: 'utc',
|
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;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.w-mct-example > div { margin-bottom: $interiorMarginLg; }
|
.w-mct-example {
|
||||||
|
div {
|
||||||
|
margin-bottom: $interiorMarginLg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
code,
|
code,
|
||||||
pre {
|
pre {
|
||||||
|
@ -121,7 +121,7 @@
|
|||||||
<h2>Palettes</h2>
|
<h2>Palettes</h2>
|
||||||
<div class="cols cols1-1">
|
<div class="cols cols1-1">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<p>Use a palette to provide color choices. Similar to context menus and dropdowns, palettes should be dismissed when a choice is made within them, or if the user clicks outside one. Selected palette choices should utilize the <code>selected</code> CSS class to visualize indicate that state.</p>
|
<p>Use a palette to provide color choices. Similar to context menus and dropdowns, palettes should be dismissed when a choice is made within them, or if the user clicks outside one.</p>
|
||||||
<p>Note that while this example uses static markup for illustrative purposes, don't do this - use a front-end framework with repeaters to build the color choices.</p>
|
<p>Note that while this example uses static markup for illustrative purposes, don't do this - use a front-end framework with repeaters to build the color choices.</p>
|
||||||
</div>
|
</div>
|
||||||
<mct-example><div style="height: 220px" title="Ignore me, I'm just here to provide space for this example.">
|
<mct-example><div style="height: 220px" title="Ignore me, I'm just here to provide space for this example.">
|
||||||
@ -129,9 +129,9 @@
|
|||||||
<div class="s-button s-menu-button menu-element t-color-palette icon-paint-bucket" ng-controller="ClickAwayController as toggle">
|
<div class="s-button s-menu-button menu-element t-color-palette icon-paint-bucket" ng-controller="ClickAwayController as toggle">
|
||||||
<span class="l-click-area" ng-click="toggle.toggle()"></span>
|
<span class="l-click-area" ng-click="toggle.toggle()"></span>
|
||||||
<span class="color-swatch" style="background: rgb(255, 0, 0);"></span>
|
<span class="color-swatch" style="background: rgb(255, 0, 0);"></span>
|
||||||
<div class="menu l-palette l-color-palette" ng-show="toggle.isActive()">
|
<div class="menu l-color-palette" ng-show="toggle.isActive()">
|
||||||
<div class="l-palette-row l-option-row">
|
<div class="l-palette-row l-option-row">
|
||||||
<div class="l-palette-item s-palette-item no-selection"></div>
|
<div class="l-palette-item s-palette-item " ng-click="ngModel[field] = 'transparent'"></div>
|
||||||
<span class="l-palette-item-label">None</span>
|
<span class="l-palette-item-label">None</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="l-palette-row">
|
<div class="l-palette-row">
|
||||||
@ -147,7 +147,7 @@
|
|||||||
<div class="l-palette-item s-palette-item" style="background: rgb(255, 255, 255);"></div>
|
<div class="l-palette-item s-palette-item" style="background: rgb(255, 255, 255);"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="l-palette-row">
|
<div class="l-palette-row">
|
||||||
<div class="l-palette-item s-palette-item selected" style="background: rgb(255, 0, 0);"></div>
|
<div class="l-palette-item s-palette-item" style="background: rgb(136, 32, 32);"></div>
|
||||||
<div class="l-palette-item s-palette-item" style="background: rgb(224, 64, 64);"></div>
|
<div class="l-palette-item s-palette-item" style="background: rgb(224, 64, 64);"></div>
|
||||||
<div class="l-palette-item s-palette-item" style="background: rgb(240, 160, 72);"></div>
|
<div class="l-palette-item s-palette-item" style="background: rgb(240, 160, 72);"></div>
|
||||||
<div class="l-palette-item s-palette-item" style="background: rgb(255, 248, 96);"></div>
|
<div class="l-palette-item s-palette-item" style="background: rgb(255, 248, 96);"></div>
|
||||||
|
@ -22,8 +22,6 @@
|
|||||||
|
|
||||||
/*global require,__dirname*/
|
/*global require,__dirname*/
|
||||||
|
|
||||||
require("v8-compile-cache");
|
|
||||||
|
|
||||||
var gulp = require('gulp'),
|
var gulp = require('gulp'),
|
||||||
sourcemaps = require('gulp-sourcemaps'),
|
sourcemaps = require('gulp-sourcemaps'),
|
||||||
path = require('path'),
|
path = require('path'),
|
||||||
@ -179,4 +177,4 @@ gulp.task('install', [ 'assets', 'scripts' ]);
|
|||||||
|
|
||||||
gulp.task('verify', [ 'lint', 'test', 'checkstyle' ]);
|
gulp.task('verify', [ 'lint', 'test', 'checkstyle' ]);
|
||||||
|
|
||||||
gulp.task('build', [ 'verify', 'install' ]);
|
gulp.task('build', [ 'verify', 'install' ]);
|
||||||
|
@ -25,7 +25,8 @@
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||||
<title></title>
|
<title></title>
|
||||||
<script src="bower_components/requirejs/require.js"> </script>
|
<script src="bower_components/requirejs/require.js">
|
||||||
|
</script>
|
||||||
<script>
|
<script>
|
||||||
var THIRTY_MINUTES = 30 * 60 * 1000;
|
var THIRTY_MINUTES = 30 * 60 * 1000;
|
||||||
|
|
||||||
@ -43,16 +44,13 @@
|
|||||||
openmct.install(openmct.plugins.ExampleImagery());
|
openmct.install(openmct.plugins.ExampleImagery());
|
||||||
openmct.install(openmct.plugins.UTCTimeSystem());
|
openmct.install(openmct.plugins.UTCTimeSystem());
|
||||||
openmct.install(openmct.plugins.ImportExport());
|
openmct.install(openmct.plugins.ImportExport());
|
||||||
openmct.install(openmct.plugins.AutoflowView({
|
|
||||||
type: "telemetry.panel"
|
|
||||||
}));
|
|
||||||
openmct.install(openmct.plugins.Conductor({
|
openmct.install(openmct.plugins.Conductor({
|
||||||
menuOptions: [
|
menuOptions: [
|
||||||
{
|
{
|
||||||
name: "Fixed",
|
name: "Fixed",
|
||||||
timeSystem: 'utc',
|
timeSystem: 'utc',
|
||||||
bounds: {
|
bounds: {
|
||||||
start: Date.now() - THIRTY_MINUTES,
|
start: Date.now() - 30 * 60 * 1000,
|
||||||
end: Date.now()
|
end: Date.now()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -67,7 +65,6 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}));
|
}));
|
||||||
openmct.install(openmct.plugins.SummaryWidget());
|
|
||||||
openmct.time.clock('local', {start: -THIRTY_MINUTES, end: 0});
|
openmct.time.clock('local', {start: -THIRTY_MINUTES, end: 0});
|
||||||
openmct.time.timeSystem('utc');
|
openmct.time.timeSystem('utc');
|
||||||
openmct.start();
|
openmct.start();
|
||||||
|
@ -36,14 +36,14 @@ module.exports = function(config) {
|
|||||||
files: [
|
files: [
|
||||||
{pattern: 'bower_components/**/*.js', included: false},
|
{pattern: 'bower_components/**/*.js', included: false},
|
||||||
{pattern: 'node_modules/d3-*/**/*.js', included: false},
|
{pattern: 'node_modules/d3-*/**/*.js', included: false},
|
||||||
{pattern: 'node_modules/vue/**/*.js', included: false},
|
{pattern: 'src/**/*.js', included: false},
|
||||||
{pattern: 'src/**/*', included: false},
|
|
||||||
{pattern: 'example/**/*.html', included: false},
|
{pattern: 'example/**/*.html', included: false},
|
||||||
{pattern: 'example/**/*.js', included: false},
|
{pattern: 'example/**/*.js', included: false},
|
||||||
{pattern: 'example/**/*.json', included: false},
|
{pattern: 'example/**/*.json', included: false},
|
||||||
{pattern: 'platform/**/*.js', included: false},
|
{pattern: 'platform/**/*.js', included: false},
|
||||||
{pattern: 'warp/**/*.js', included: false},
|
{pattern: 'warp/**/*.js', included: false},
|
||||||
{pattern: 'platform/**/*.html', included: false},
|
{pattern: 'platform/**/*.html', included: false},
|
||||||
|
{pattern: 'src/**/*.html', included: false},
|
||||||
'test-main.js'
|
'test-main.js'
|
||||||
],
|
],
|
||||||
|
|
||||||
@ -88,8 +88,7 @@ module.exports = function(config) {
|
|||||||
"dist/reports/coverage",
|
"dist/reports/coverage",
|
||||||
check: {
|
check: {
|
||||||
global: {
|
global: {
|
||||||
lines: 80,
|
lines: 80
|
||||||
excludes: ['src/plugins/plot/**/*.js']
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -33,14 +33,13 @@ requirejs.config({
|
|||||||
"moment": "bower_components/moment/moment",
|
"moment": "bower_components/moment/moment",
|
||||||
"moment-duration-format": "bower_components/moment-duration-format/lib/moment-duration-format",
|
"moment-duration-format": "bower_components/moment-duration-format/lib/moment-duration-format",
|
||||||
"moment-timezone": "bower_components/moment-timezone/builds/moment-timezone-with-data",
|
"moment-timezone": "bower_components/moment-timezone/builds/moment-timezone-with-data",
|
||||||
"saveAs": "bower_components/file-saver/FileSaver.min",
|
"saveAs": "bower_components/FileSaver.js/FileSaver.min",
|
||||||
"screenfull": "bower_components/screenfull/dist/screenfull.min",
|
"screenfull": "bower_components/screenfull/dist/screenfull.min",
|
||||||
"text": "bower_components/text/text",
|
"text": "bower_components/text/text",
|
||||||
"uuid": "bower_components/node-uuid/uuid",
|
"uuid": "bower_components/node-uuid/uuid",
|
||||||
"vue": "node_modules/vue/dist/vue.min",
|
|
||||||
"zepto": "bower_components/zepto/zepto.min",
|
"zepto": "bower_components/zepto/zepto.min",
|
||||||
"lodash": "bower_components/lodash/lodash",
|
"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-scale": "node_modules/d3-scale/build/d3-scale.min",
|
||||||
"d3-axis": "node_modules/d3-axis/build/d3-axis.min",
|
"d3-axis": "node_modules/d3-axis/build/d3-axis.min",
|
||||||
"d3-array": "node_modules/d3-array/build/d3-array.min",
|
"d3-array": "node_modules/d3-array/build/d3-array.min",
|
||||||
@ -67,9 +66,6 @@ requirejs.config({
|
|||||||
"moment-duration-format": {
|
"moment-duration-format": {
|
||||||
"deps": ["moment"]
|
"deps": ["moment"]
|
||||||
},
|
},
|
||||||
"saveAs": {
|
|
||||||
"exports": "saveAs"
|
|
||||||
},
|
|
||||||
"screenfull": {
|
"screenfull": {
|
||||||
"exports": "screenfull"
|
"exports": "screenfull"
|
||||||
},
|
},
|
||||||
@ -101,7 +97,6 @@ define([
|
|||||||
var openmct = new MCT();
|
var openmct = new MCT();
|
||||||
|
|
||||||
openmct.legacyRegistry = defaultRegistry;
|
openmct.legacyRegistry = defaultRegistry;
|
||||||
openmct.install(openmct.plugins.Plot());
|
|
||||||
|
|
||||||
if (typeof BUILD_CONSTANTS !== 'undefined') {
|
if (typeof BUILD_CONSTANTS !== 'undefined') {
|
||||||
openmct.install(buildInfo(BUILD_CONSTANTS));
|
openmct.install(buildInfo(BUILD_CONSTANTS));
|
||||||
|
32
package.json
32
package.json
@ -1,22 +1,21 @@
|
|||||||
{
|
{
|
||||||
"name": "openmct",
|
"name": "openmct",
|
||||||
"version": "0.13.3-SNAPSHOT",
|
"version": "0.12.1-SNAPSHOT",
|
||||||
"description": "The Open MCT core platform",
|
"description": "The Open MCT core platform",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"d3-array": "1.2.x",
|
"d3-array": "^1.0.2",
|
||||||
"d3-axis": "1.0.x",
|
"d3-axis": "^1.0.4",
|
||||||
"d3-collection": "1.0.x",
|
"d3-collection": "^1.0.2",
|
||||||
"d3-color": "1.0.x",
|
"d3-color": "^1.0.2",
|
||||||
"d3-format": "1.2.x",
|
"d3-format": "^1.0.2",
|
||||||
"d3-interpolate": "1.1.x",
|
"d3-interpolate": "^1.1.3",
|
||||||
"d3-scale": "1.0.x",
|
"d3-scale": "^1.0.4",
|
||||||
"d3-selection": "1.3.x",
|
"d3-selection": "^1.0.3",
|
||||||
"d3-time": "1.0.x",
|
"d3-time": "^1.0.4",
|
||||||
"d3-time-format": "2.1.x",
|
"d3-time-format": "^2.0.3",
|
||||||
"express": "^4.13.1",
|
"express": "^4.13.1",
|
||||||
"minimist": "^1.1.1",
|
"minimist": "^1.1.1",
|
||||||
"request": "^2.69.0",
|
"request": "^2.69.0"
|
||||||
"vue": "^2.5.6"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"bower": "^1.7.7",
|
"bower": "^1.7.7",
|
||||||
@ -28,7 +27,7 @@
|
|||||||
"gulp-jshint-html-reporter": "^0.1.3",
|
"gulp-jshint-html-reporter": "^0.1.3",
|
||||||
"gulp-rename": "^1.2.2",
|
"gulp-rename": "^1.2.2",
|
||||||
"gulp-requirejs-optimize": "^0.3.1",
|
"gulp-requirejs-optimize": "^0.3.1",
|
||||||
"gulp-sass": "^3.1.0",
|
"gulp-sass": "^2.2.0",
|
||||||
"gulp-sourcemaps": "^1.6.0",
|
"gulp-sourcemaps": "^1.6.0",
|
||||||
"jasmine-core": "^2.3.0",
|
"jasmine-core": "^2.3.0",
|
||||||
"jscs-html-reporter": "^0.1.0",
|
"jscs-html-reporter": "^0.1.0",
|
||||||
@ -50,8 +49,7 @@
|
|||||||
"moment": "^2.11.1",
|
"moment": "^2.11.1",
|
||||||
"node-bourbon": "^4.2.3",
|
"node-bourbon": "^4.2.3",
|
||||||
"requirejs": "2.1.x",
|
"requirejs": "2.1.x",
|
||||||
"split": "^1.0.0",
|
"split": "^1.0.0"
|
||||||
"v8-compile-cache": "^1.1.0"
|
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node app.js",
|
"start": "node app.js",
|
||||||
@ -61,7 +59,7 @@
|
|||||||
"jsdoc": "jsdoc -c jsdoc.json -R API.md -r -d dist/docs/api",
|
"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'",
|
"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",
|
"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": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -57,12 +57,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<mct-representation key="representation.selected.key"
|
<mct-representation key="representation.selected.key"
|
||||||
mct-object="representation.selected.key && domainObject"
|
mct-object="representation.selected.key && domainObject"
|
||||||
class="abs flex-elem grows object-holder-main scroll"
|
class="abs flex-elem grows object-holder-main scroll">
|
||||||
mct-selectable="{
|
|
||||||
item: domainObject.useCapability('adapter'),
|
|
||||||
oldItem: domainObject
|
|
||||||
}"
|
|
||||||
mct-init-select>
|
|
||||||
</mct-representation>
|
</mct-representation>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -19,21 +19,12 @@
|
|||||||
this source code distribution or the Licensing information page available
|
this source code distribution or the Licensing information page available
|
||||||
at runtime from the About dialog for additional information.
|
at runtime from the About dialog for additional information.
|
||||||
-->
|
-->
|
||||||
<div ng-controller="InspectorController as controller">
|
<div ng-controller="InspectorController">
|
||||||
|
<div ng-repeat="region in regions">
|
||||||
<mct-representation
|
<mct-representation
|
||||||
key="'object-properties'"
|
key="region.content.key"
|
||||||
mct-object="controller.selectedItem()"
|
mct-object="domainObject"
|
||||||
ng-model="ngModel">
|
ng-model="ngModel">
|
||||||
</mct-representation>
|
</mct-representation>
|
||||||
|
</div>
|
||||||
<div ng-if="!controller.hasProviderView()">
|
|
||||||
<mct-representation
|
|
||||||
key="inspectorKey"
|
|
||||||
mct-object="controller.selectedItem()"
|
|
||||||
ng-model="ngModel">
|
|
||||||
</mct-representation>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class='inspector-provider-view'>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -38,7 +38,8 @@
|
|||||||
ng-class="{ last:($index + 1) === contextualParents.length }">
|
ng-class="{ last:($index + 1) === contextualParents.length }">
|
||||||
<mct-representation key="'label'"
|
<mct-representation key="'label'"
|
||||||
mct-object="parent"
|
mct-object="parent"
|
||||||
ng-click="parent.getCapability('action').perform('navigate')"
|
ng-model="ngModel"
|
||||||
|
ng-click="ngModel.selectedObject = parent"
|
||||||
class="location-item">
|
class="location-item">
|
||||||
</mct-representation>
|
</mct-representation>
|
||||||
</span>
|
</span>
|
||||||
@ -50,7 +51,8 @@
|
|||||||
ng-class="{ last:($index + 1) === primaryParents.length }">
|
ng-class="{ last:($index + 1) === primaryParents.length }">
|
||||||
<mct-representation key="'label'"
|
<mct-representation key="'label'"
|
||||||
mct-object="parent"
|
mct-object="parent"
|
||||||
ng-click="parent.getCapability('action').perform('navigate')"
|
ng-model="ngModel"
|
||||||
|
ng-click="ngModel.selectedObject = parent"
|
||||||
class="location-item">
|
class="location-item">
|
||||||
</mct-representation>
|
</mct-representation>
|
||||||
</span>
|
</span>
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
at runtime from the About dialog for additional information.
|
at runtime from the About dialog for additional information.
|
||||||
-->
|
-->
|
||||||
<div class="abs top-bar">
|
<div class="abs top-bar">
|
||||||
<div class="dialog-title">{{ngModel.title}}</div>
|
<div class="title">{{ngModel.title}}</div>
|
||||||
<div class="hint">All fields marked <span class="req icon-asterisk"></span> are required.</div>
|
<div class="hint">All fields marked <span class="req icon-asterisk"></span> are required.</div>
|
||||||
</div>
|
</div>
|
||||||
<div class='abs editor'>
|
<div class='abs editor'>
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
<div class="l-message"
|
<div class="l-message"
|
||||||
ng-class="'message-severity-' + ngModel.severity">
|
ng-class="'message-severity-' + ngModel.severity">
|
||||||
<div class="w-message-contents">
|
<div class="ui-symbol type-icon message-type"></div>
|
||||||
|
<div class="message-contents">
|
||||||
<div class="top-bar">
|
<div class="top-bar">
|
||||||
<div class="title">{{ngModel.title}}</div>
|
<div class="title">{{ngModel.title}}</div>
|
||||||
|
<div class="hint" ng-hide="ngModel.hint === undefined">{{ngModel.hint}}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="hint" ng-hide="ngModel.hint === undefined">{{ngModel.hint}}</div>
|
|
||||||
<div class="message-body">
|
<div class="message-body">
|
||||||
<div class="message-action">
|
<div class="message-action">
|
||||||
{{ngModel.actionText}}
|
{{ngModel.actionText}}
|
||||||
@ -24,6 +25,8 @@
|
|||||||
ng-click="ngModel.primaryOption.callback()">
|
ng-click="ngModel.primaryOption.callback()">
|
||||||
{{ngModel.primaryOption.label}}
|
{{ngModel.primaryOption.label}}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
<mct-container key="overlay">
|
<mct-container key="overlay" class="t-message-list">
|
||||||
<div class="t-message-list">
|
<div class="message-contents">
|
||||||
<div class="top-bar">
|
<div class="abs top-bar">
|
||||||
<div class="dialog-title">{{ngModel.dialog.title}}</div>
|
<div class="title">{{ngModel.dialog.title}}</div>
|
||||||
<div class="hint">Displaying {{ngModel.dialog.messages.length}} message<span ng-show="ngModel.dialog.messages.length > 1 ||
|
<div class="hint">Displaying {{ngModel.dialog.messages.length}} message<span ng-show="ngModel.dialog.messages.length > 1 ||
|
||||||
ngModel.dialog.messages.length == 0">s</span>
|
ngModel.dialog.messages.length == 0">s</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-messages">
|
<div class="abs message-body">
|
||||||
<mct-include
|
<mct-include
|
||||||
ng-repeat="msg in ngModel.dialog.messages | orderBy: '-'"
|
ng-repeat="msg in ngModel.dialog.messages | orderBy: '-'"
|
||||||
key="'message'" ng-model="msg.model"></mct-include>
|
key="'message'" ng-model="msg.model"></mct-include>
|
||||||
</div>
|
</div>
|
||||||
<div class="bottom-bar">
|
<div class="abs bottom-bar">
|
||||||
<a ng-repeat="dialogAction in ngModel.dialog.actions"
|
<a ng-repeat="dialogAction in ngModel.dialog.actions"
|
||||||
class="s-button major"
|
class="s-button major"
|
||||||
ng-click="dialogAction.action()">
|
ng-click="dialogAction.action()">
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
-->
|
-->
|
||||||
<mct-container key="overlay">
|
<mct-container key="overlay">
|
||||||
<div class="abs top-bar">
|
<div class="abs top-bar">
|
||||||
<div class="dialog-title">{{ngModel.dialog.title}}</div>
|
<div class="title">{{ngModel.dialog.title}}</div>
|
||||||
<div class="hint">{{ngModel.dialog.hint}}</div>
|
<div class="hint">{{ngModel.dialog.hint}}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class='abs editor'>
|
<div class='abs editor'>
|
||||||
|
@ -121,8 +121,7 @@ define([
|
|||||||
"key": "ElementsController",
|
"key": "ElementsController",
|
||||||
"implementation": ElementsController,
|
"implementation": ElementsController,
|
||||||
"depends": [
|
"depends": [
|
||||||
"$scope",
|
"$scope"
|
||||||
"openmct"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -300,6 +299,9 @@ define([
|
|||||||
{
|
{
|
||||||
"key": "edit-elements",
|
"key": "edit-elements",
|
||||||
"template": elementsTemplate,
|
"template": elementsTemplate,
|
||||||
|
"uses": [
|
||||||
|
"composition"
|
||||||
|
],
|
||||||
"gestures": [
|
"gestures": [
|
||||||
"drop"
|
"drop"
|
||||||
]
|
]
|
||||||
@ -383,10 +385,7 @@ define([
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"implementation": EditToolbarRepresenter,
|
"implementation": EditToolbarRepresenter
|
||||||
"depends": [
|
|
||||||
"openmct"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"constants": [
|
"constants": [
|
||||||
|
@ -61,12 +61,7 @@
|
|||||||
<mct-representation key="representation.selected.key"
|
<mct-representation key="representation.selected.key"
|
||||||
mct-object="representation.selected.key && domainObject"
|
mct-object="representation.selected.key && domainObject"
|
||||||
class="abs flex-elem grows object-holder-main scroll"
|
class="abs flex-elem grows object-holder-main scroll"
|
||||||
toolbar="toolbar"
|
toolbar="toolbar">
|
||||||
mct-selectable="{
|
|
||||||
item: domainObject.useCapability('adapter'),
|
|
||||||
oldItem: domainObject
|
|
||||||
}"
|
|
||||||
mct-init-select>
|
|
||||||
</mct-representation>
|
</mct-representation>
|
||||||
</div><!--/ l-object-wrapper-inner -->
|
</div><!--/ l-object-wrapper-inner -->
|
||||||
</div>
|
</div>
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
ng-model="filterBy">
|
ng-model="filterBy">
|
||||||
</mct-include>
|
</mct-include>
|
||||||
<div class="flex-elem grows vscroll">
|
<div class="flex-elem grows vscroll">
|
||||||
<ul class="tree" ng-if="composition.length > 0">
|
<ul class="tree">
|
||||||
<li ng-repeat="containedObject in composition | filter:searchElements">
|
<li ng-repeat="containedObject in composition | filter:searchElements">
|
||||||
<span class="tree-item">
|
<span class="tree-item">
|
||||||
<mct-representation
|
<mct-representation
|
||||||
@ -36,6 +36,5 @@
|
|||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div ng-if="composition.length === 0">No contained elements</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -101,15 +101,10 @@ define(
|
|||||||
*/
|
*/
|
||||||
EditorCapability.prototype.finish = function () {
|
EditorCapability.prototype.finish = function () {
|
||||||
var domainObject = this.domainObject;
|
var domainObject = this.domainObject;
|
||||||
|
return this.transactionService.cancel().then(function () {
|
||||||
if (this.transactionService.isActive()) {
|
domainObject.getCapability("status").set("editing", false);
|
||||||
return this.transactionService.cancel().then(function () {
|
return domainObject;
|
||||||
domainObject.getCapability("status").set("editing", false);
|
});
|
||||||
return domainObject;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return Promise.resolve(domainObject);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -28,6 +28,16 @@ define(
|
|||||||
[],
|
[],
|
||||||
function () {
|
function () {
|
||||||
|
|
||||||
|
function isDirty(domainObject) {
|
||||||
|
var navigatedObject = domainObject,
|
||||||
|
editorCapability = navigatedObject &&
|
||||||
|
navigatedObject.getCapability("editor");
|
||||||
|
|
||||||
|
return editorCapability &&
|
||||||
|
editorCapability.isEditContextRoot() &&
|
||||||
|
editorCapability.dirty();
|
||||||
|
}
|
||||||
|
|
||||||
function cancelEditing(domainObject) {
|
function cancelEditing(domainObject) {
|
||||||
var navigatedObject = domainObject,
|
var navigatedObject = domainObject,
|
||||||
editorCapability = navigatedObject &&
|
editorCapability = navigatedObject &&
|
||||||
@ -49,7 +59,10 @@ define(
|
|||||||
|
|
||||||
var removeCheck = navigationService
|
var removeCheck = navigationService
|
||||||
.checkBeforeNavigation(function () {
|
.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 () {
|
$scope.$on('$destroy', function () {
|
||||||
|
@ -29,11 +29,7 @@ define(
|
|||||||
*
|
*
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
function ElementsController($scope, openmct) {
|
function ElementsController($scope) {
|
||||||
this.scope = $scope;
|
|
||||||
this.scope.composition = [];
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
function filterBy(text) {
|
function filterBy(text) {
|
||||||
if (typeof text === 'undefined') {
|
if (typeof text === 'undefined') {
|
||||||
return $scope.searchText;
|
return $scope.searchText;
|
||||||
@ -51,58 +47,10 @@ define(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setSelection(selection) {
|
|
||||||
if (!selection[0]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self.mutationListener) {
|
|
||||||
self.mutationListener();
|
|
||||||
delete self.mutationListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
var domainObject = selection[0].context.oldItem;
|
|
||||||
self.refreshComposition(domainObject);
|
|
||||||
|
|
||||||
if (domainObject) {
|
|
||||||
self.mutationListener = domainObject.getCapability('mutation')
|
|
||||||
.listen(self.refreshComposition.bind(self, domainObject));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.filterBy = filterBy;
|
$scope.filterBy = filterBy;
|
||||||
$scope.searchElements = searchElements;
|
$scope.searchElements = searchElements;
|
||||||
|
|
||||||
openmct.selection.on('change', setSelection);
|
|
||||||
setSelection(openmct.selection.get());
|
|
||||||
|
|
||||||
$scope.$on("$destroy", function () {
|
|
||||||
openmct.selection.off("change", setSelection);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the composition for the selected object and populates the scope with it.
|
|
||||||
*
|
|
||||||
* @param domainObject the selected object
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
ElementsController.prototype.refreshComposition = function (domainObject) {
|
|
||||||
var refreshTracker = {};
|
|
||||||
this.currentRefresh = refreshTracker;
|
|
||||||
|
|
||||||
var selectedObjectComposition = domainObject && domainObject.useCapability('composition');
|
|
||||||
if (selectedObjectComposition) {
|
|
||||||
selectedObjectComposition.then(function (composition) {
|
|
||||||
if (this.currentRefresh === refreshTracker) {
|
|
||||||
this.scope.composition = composition;
|
|
||||||
}
|
|
||||||
}.bind(this));
|
|
||||||
} else {
|
|
||||||
this.scope.composition = [];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return ElementsController;
|
return ElementsController;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -38,7 +38,7 @@ define(
|
|||||||
* @constructor
|
* @constructor
|
||||||
* @implements {Representer}
|
* @implements {Representer}
|
||||||
*/
|
*/
|
||||||
function EditToolbarRepresenter(openmct, scope, element, attrs) {
|
function EditToolbarRepresenter(scope, element, attrs) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
// Mark changes as ready to persist
|
// Mark changes as ready to persist
|
||||||
@ -109,7 +109,6 @@ define(
|
|||||||
this.updateSelection = updateSelection;
|
this.updateSelection = updateSelection;
|
||||||
this.toolbar = undefined;
|
this.toolbar = undefined;
|
||||||
this.toolbarObject = {};
|
this.toolbarObject = {};
|
||||||
this.openmct = openmct;
|
|
||||||
|
|
||||||
// If this representation exposes a toolbar, set up watches
|
// If this representation exposes a toolbar, set up watches
|
||||||
// to synchronize with it.
|
// to synchronize with it.
|
||||||
@ -147,7 +146,7 @@ define(
|
|||||||
// Expose the toolbar object to the parent scope
|
// Expose the toolbar object to the parent scope
|
||||||
initialize(definition);
|
initialize(definition);
|
||||||
// Create a selection scope
|
// Create a selection scope
|
||||||
this.setSelection(new EditToolbarSelection(this.openmct));
|
this.setSelection(new EditToolbarSelection());
|
||||||
// Initialize toolbar to an empty selection
|
// Initialize toolbar to an empty selection
|
||||||
this.updateSelection([]);
|
this.updateSelection([]);
|
||||||
};
|
};
|
||||||
|
@ -38,24 +38,10 @@ define(
|
|||||||
* @memberof platform/commonUI/edit
|
* @memberof platform/commonUI/edit
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
function EditToolbarSelection(openmct) {
|
function EditToolbarSelection() {
|
||||||
this.selection = [{}];
|
this.selection = [{}];
|
||||||
this.selecting = false;
|
this.selecting = false;
|
||||||
this.selectedObj = undefined;
|
this.selectedObj = undefined;
|
||||||
|
|
||||||
openmct.selection.on('change', function (selection) {
|
|
||||||
var selected = selection[0];
|
|
||||||
|
|
||||||
if (selected && selected.context.toolbar) {
|
|
||||||
this.select(selected.context.toolbar);
|
|
||||||
} else {
|
|
||||||
this.deselect();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selected && selected.context.viewProxy) {
|
|
||||||
this.proxy(selected.context.viewProxy);
|
|
||||||
}
|
|
||||||
}.bind(this));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -62,7 +62,6 @@ define(
|
|||||||
);
|
);
|
||||||
mockTransactionService.commit.andReturn(fastPromise());
|
mockTransactionService.commit.andReturn(fastPromise());
|
||||||
mockTransactionService.cancel.andReturn(fastPromise());
|
mockTransactionService.cancel.andReturn(fastPromise());
|
||||||
mockTransactionService.isActive = jasmine.createSpy('isActive');
|
|
||||||
|
|
||||||
mockStatusCapability = jasmine.createSpyObj(
|
mockStatusCapability = jasmine.createSpyObj(
|
||||||
"statusCapability",
|
"statusCapability",
|
||||||
@ -142,7 +141,6 @@ define(
|
|||||||
|
|
||||||
describe("finish", function () {
|
describe("finish", function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
mockTransactionService.isActive.andReturn(true);
|
|
||||||
capability.edit();
|
capability.edit();
|
||||||
capability.finish();
|
capability.finish();
|
||||||
});
|
});
|
||||||
@ -154,23 +152,6 @@ define(
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("finish", function () {
|
|
||||||
beforeEach(function () {
|
|
||||||
mockTransactionService.isActive.andReturn(false);
|
|
||||||
capability.edit();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("does not cancel transaction when transaction is not active", function () {
|
|
||||||
capability.finish();
|
|
||||||
expect(mockTransactionService.cancel).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("returns a promise", function () {
|
|
||||||
expect(capability.finish() instanceof Promise).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("dirty", function () {
|
describe("dirty", function () {
|
||||||
var model = {};
|
var model = {};
|
||||||
|
|
||||||
|
@ -104,10 +104,10 @@ define(
|
|||||||
mockEditorCapability.isEditContextRoot.andReturn(false);
|
mockEditorCapability.isEditContextRoot.andReturn(false);
|
||||||
mockEditorCapability.dirty.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);
|
mockEditorCapability.isEditContextRoot.andReturn(true);
|
||||||
expect(checkFn()).toBe("Continuing will cause the loss of any unsaved changes.");
|
expect(checkFn()).toBe(false);
|
||||||
|
|
||||||
mockEditorCapability.dirty.andReturn(true);
|
mockEditorCapability.dirty.andReturn(true);
|
||||||
expect(checkFn())
|
expect(checkFn())
|
||||||
|
@ -27,70 +27,11 @@ define(
|
|||||||
|
|
||||||
describe("The Elements Pane controller", function () {
|
describe("The Elements Pane controller", function () {
|
||||||
var mockScope,
|
var mockScope,
|
||||||
mockOpenMCT,
|
|
||||||
mockSelection,
|
|
||||||
mockDomainObject,
|
|
||||||
mockMutationCapability,
|
|
||||||
mockCompositionCapability,
|
|
||||||
mockCompositionObjects,
|
|
||||||
mockComposition,
|
|
||||||
mockUnlisten,
|
|
||||||
selectable = [],
|
|
||||||
controller;
|
controller;
|
||||||
|
|
||||||
function mockPromise(value) {
|
|
||||||
return {
|
|
||||||
then: function (thenFunc) {
|
|
||||||
return mockPromise(thenFunc(value));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function createDomainObject() {
|
|
||||||
return {
|
|
||||||
useCapability: function () {
|
|
||||||
return mockCompositionCapability;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
mockComposition = ["a", "b"];
|
mockScope = jasmine.createSpy("$scope");
|
||||||
mockCompositionObjects = mockComposition.map(createDomainObject);
|
controller = new ElementsController(mockScope);
|
||||||
mockCompositionCapability = mockPromise(mockCompositionObjects);
|
|
||||||
|
|
||||||
mockUnlisten = jasmine.createSpy('unlisten');
|
|
||||||
mockMutationCapability = jasmine.createSpyObj("mutationCapability", [
|
|
||||||
"listen"
|
|
||||||
]);
|
|
||||||
mockMutationCapability.listen.andReturn(mockUnlisten);
|
|
||||||
mockDomainObject = jasmine.createSpyObj("domainObject", [
|
|
||||||
"getCapability",
|
|
||||||
"useCapability"
|
|
||||||
]);
|
|
||||||
mockDomainObject.useCapability.andReturn(mockCompositionCapability);
|
|
||||||
mockDomainObject.getCapability.andReturn(mockMutationCapability);
|
|
||||||
|
|
||||||
mockScope = jasmine.createSpyObj("$scope", ['$on']);
|
|
||||||
mockSelection = jasmine.createSpyObj("selection", [
|
|
||||||
'on',
|
|
||||||
'off',
|
|
||||||
'get'
|
|
||||||
]);
|
|
||||||
mockSelection.get.andReturn([]);
|
|
||||||
mockOpenMCT = {
|
|
||||||
selection: mockSelection
|
|
||||||
};
|
|
||||||
|
|
||||||
selectable[0] = {
|
|
||||||
context: {
|
|
||||||
oldItem: mockDomainObject
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
spyOn(ElementsController.prototype, 'refreshComposition').andCallThrough();
|
|
||||||
|
|
||||||
controller = new ElementsController(mockScope, mockOpenMCT);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function getModel(model) {
|
function getModel(model) {
|
||||||
@ -122,63 +63,6 @@ define(
|
|||||||
expect(objects.filter(mockScope.searchElements).length).toBe(4);
|
expect(objects.filter(mockScope.searchElements).length).toBe(4);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("refreshes composition on selection", function () {
|
|
||||||
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
|
||||||
|
|
||||||
expect(ElementsController.prototype.refreshComposition).toHaveBeenCalledWith(mockDomainObject);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("listens on mutation and refreshes composition", function () {
|
|
||||||
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
|
||||||
|
|
||||||
expect(mockDomainObject.getCapability).toHaveBeenCalledWith('mutation');
|
|
||||||
expect(mockMutationCapability.listen).toHaveBeenCalled();
|
|
||||||
expect(ElementsController.prototype.refreshComposition.calls.length).toBe(1);
|
|
||||||
|
|
||||||
mockMutationCapability.listen.mostRecentCall.args[0](mockDomainObject);
|
|
||||||
|
|
||||||
expect(ElementsController.prototype.refreshComposition.calls.length).toBe(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("cleans up mutation listener when selection changes", function () {
|
|
||||||
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
|
||||||
|
|
||||||
expect(mockMutationCapability.listen).toHaveBeenCalled();
|
|
||||||
|
|
||||||
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
|
||||||
|
|
||||||
expect(mockUnlisten).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("does not listen on mutation for element proxy selectable", function () {
|
|
||||||
selectable[0] = {
|
|
||||||
context: {
|
|
||||||
elementProxy: {}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
|
||||||
|
|
||||||
expect(mockDomainObject.getCapability).not.toHaveBeenCalledWith('mutation');
|
|
||||||
});
|
|
||||||
|
|
||||||
it("checks concurrent changes to composition", function () {
|
|
||||||
var secondMockComposition = ["a", "b", "c"],
|
|
||||||
secondMockCompositionObjects = secondMockComposition.map(createDomainObject),
|
|
||||||
firstCompositionCallback,
|
|
||||||
secondCompositionCallback;
|
|
||||||
|
|
||||||
spyOn(mockCompositionCapability, "then").andCallThrough();
|
|
||||||
|
|
||||||
controller.refreshComposition(mockDomainObject);
|
|
||||||
controller.refreshComposition(mockDomainObject);
|
|
||||||
|
|
||||||
firstCompositionCallback = mockCompositionCapability.then.calls[0].args[0];
|
|
||||||
secondCompositionCallback = mockCompositionCapability.then.calls[1].args[0];
|
|
||||||
secondCompositionCallback(secondMockCompositionObjects);
|
|
||||||
firstCompositionCallback(mockCompositionObjects);
|
|
||||||
|
|
||||||
expect(mockScope.composition).toBe(secondMockCompositionObjects);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -29,9 +29,7 @@ define(
|
|||||||
mockElement,
|
mockElement,
|
||||||
testAttrs,
|
testAttrs,
|
||||||
mockUnwatch,
|
mockUnwatch,
|
||||||
representer,
|
representer;
|
||||||
mockOpenMCT,
|
|
||||||
mockSelection;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
mockScope = jasmine.createSpyObj(
|
mockScope = jasmine.createSpyObj(
|
||||||
@ -48,18 +46,7 @@ define(
|
|||||||
|
|
||||||
mockScope.$parent.$watchCollection.andReturn(mockUnwatch);
|
mockScope.$parent.$watchCollection.andReturn(mockUnwatch);
|
||||||
|
|
||||||
mockSelection = jasmine.createSpyObj("selection", [
|
|
||||||
'on',
|
|
||||||
'off',
|
|
||||||
'get'
|
|
||||||
]);
|
|
||||||
mockSelection.get.andReturn([]);
|
|
||||||
mockOpenMCT = {
|
|
||||||
selection: mockSelection
|
|
||||||
};
|
|
||||||
|
|
||||||
representer = new EditToolbarRepresenter(
|
representer = new EditToolbarRepresenter(
|
||||||
mockOpenMCT,
|
|
||||||
mockScope,
|
mockScope,
|
||||||
mockElement,
|
mockElement,
|
||||||
testAttrs
|
testAttrs
|
||||||
|
@ -28,25 +28,13 @@ define(
|
|||||||
var testProxy,
|
var testProxy,
|
||||||
testElement,
|
testElement,
|
||||||
otherElement,
|
otherElement,
|
||||||
selection,
|
selection;
|
||||||
mockSelection,
|
|
||||||
mockOpenMCT;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
testProxy = { someKey: "some value" };
|
testProxy = { someKey: "some value" };
|
||||||
testElement = { someOtherKey: "some other value" };
|
testElement = { someOtherKey: "some other value" };
|
||||||
otherElement = { yetAnotherKey: 42 };
|
otherElement = { yetAnotherKey: 42 };
|
||||||
mockSelection = jasmine.createSpyObj("selection", [
|
selection = new EditToolbarSelection();
|
||||||
// 'select',
|
|
||||||
'on',
|
|
||||||
'off',
|
|
||||||
'get'
|
|
||||||
]);
|
|
||||||
mockSelection.get.andReturn([]);
|
|
||||||
mockOpenMCT = {
|
|
||||||
selection: mockSelection
|
|
||||||
};
|
|
||||||
selection = new EditToolbarSelection(mockOpenMCT);
|
|
||||||
selection.proxy(testProxy);
|
selection.proxy(testProxy);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -121,9 +121,6 @@ define([
|
|||||||
};
|
};
|
||||||
|
|
||||||
UTCTimeFormat.prototype.parse = function (text) {
|
UTCTimeFormat.prototype.parse = function (text) {
|
||||||
if (typeof text === 'number') {
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
return moment.utc(text, DATE_FORMATS).valueOf();
|
return moment.utc(text, DATE_FORMATS).valueOf();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -41,7 +41,6 @@ define([
|
|||||||
"./src/controllers/BannerController",
|
"./src/controllers/BannerController",
|
||||||
"./src/directives/MCTContainer",
|
"./src/directives/MCTContainer",
|
||||||
"./src/directives/MCTDrag",
|
"./src/directives/MCTDrag",
|
||||||
"./src/directives/MCTSelectable",
|
|
||||||
"./src/directives/MCTClickElsewhere",
|
"./src/directives/MCTClickElsewhere",
|
||||||
"./src/directives/MCTResize",
|
"./src/directives/MCTResize",
|
||||||
"./src/directives/MCTPopup",
|
"./src/directives/MCTPopup",
|
||||||
@ -91,7 +90,6 @@ define([
|
|||||||
BannerController,
|
BannerController,
|
||||||
MCTContainer,
|
MCTContainer,
|
||||||
MCTDrag,
|
MCTDrag,
|
||||||
MCTSelectable,
|
|
||||||
MCTClickElsewhere,
|
MCTClickElsewhere,
|
||||||
MCTResize,
|
MCTResize,
|
||||||
MCTPopup,
|
MCTPopup,
|
||||||
@ -330,13 +328,6 @@ define([
|
|||||||
"$document"
|
"$document"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"key": "mctSelectable",
|
|
||||||
"implementation": MCTSelectable,
|
|
||||||
"depends": [
|
|
||||||
"openmct"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"key": "mctClickElsewhere",
|
"key": "mctClickElsewhere",
|
||||||
"implementation": MCTClickElsewhere,
|
"implementation": MCTClickElsewhere,
|
||||||
|
@ -137,11 +137,6 @@
|
|||||||
min-height: 0;
|
min-height: 0;
|
||||||
&.holder:not(:last-child) { margin-bottom: $interiorMarginLg; }
|
&.holder:not(:last-child) { margin-bottom: $interiorMarginLg; }
|
||||||
}
|
}
|
||||||
&.l-flex-accordion .flex-accordion-holder {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
//overflow: hidden !important;
|
|
||||||
}
|
|
||||||
.flex-container { @include flex-direction(column); }
|
.flex-container { @include flex-direction(column); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,7 +99,7 @@ $plotXBarH: 32px;
|
|||||||
$plotLegendH: 20px;
|
$plotLegendH: 20px;
|
||||||
$plotSwatchD: 8px;
|
$plotSwatchD: 8px;
|
||||||
// 1: Top, 2: right, 3: bottom, 4: left
|
// 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 */
|
/* min plot height is based on user testing to find minimum useful height */
|
||||||
$plotMinH: 95px;
|
$plotMinH: 95px;
|
||||||
/*************** Bubbles */
|
/*************** Bubbles */
|
||||||
|
@ -25,7 +25,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.l-fixed-position-item {
|
.l-fixed-position-item {
|
||||||
border-width: 1px;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
&.s-not-selected {
|
&.s-not-selected {
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
|
@ -40,7 +40,7 @@
|
|||||||
* Use https://icomoon.io/app with icomoon-project-openmct-symbols-12px.json
|
* Use https://icomoon.io/app with icomoon-project-openmct-symbols-12px.json
|
||||||
* to generate font files
|
* 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');
|
||||||
src: url($dirCommonRes + 'fonts/symbols/openmct-symbols-12px.eot?#iefix') format('embedded-opentype'),
|
src: url($dirCommonRes + 'fonts/symbols/openmct-symbols-12px.eot?#iefix') format('embedded-opentype'),
|
||||||
url($dirCommonRes + 'fonts/symbols/openmct-symbols-12px.woff') format('woff'),
|
url($dirCommonRes + 'fonts/symbols/openmct-symbols-12px.woff') format('woff'),
|
||||||
@ -180,20 +180,6 @@ a.disabled {
|
|||||||
@include ellipsize();
|
@include ellipsize();
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-selection {
|
|
||||||
// aka selection = "None". Used in palettes and their menu buttons.
|
|
||||||
$c: red; $s: 48%; $e: 52%;
|
|
||||||
@include background-image(linear-gradient(-45deg,
|
|
||||||
transparent $s - 5%,
|
|
||||||
$c $s,
|
|
||||||
$c $e,
|
|
||||||
transparent $e + 5%
|
|
||||||
));
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: contain;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.scrolling,
|
.scrolling,
|
||||||
.scroll {
|
.scroll {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
@ -248,12 +234,6 @@ a.disabled {
|
|||||||
color: rgba(#fff, 0.2);
|
color: rgba(#fff, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.comma-list span {
|
|
||||||
&:not(:first-child) {
|
|
||||||
&:before { content: ', '; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.test-stripes {
|
.test-stripes {
|
||||||
@include bgDiagonalStripes();
|
@include bgDiagonalStripes();
|
||||||
}
|
}
|
||||||
|
@ -44,12 +44,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.t-alert-unsynced {
|
|
||||||
@extend .icon-alert-triangle;
|
|
||||||
color: $colorPausedBg;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bar .ui-symbol {
|
.bar .ui-symbol {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
@ -87,5 +81,18 @@
|
|||||||
@include transform(scale(0.3));
|
@include transform(scale(0.3));
|
||||||
z-index: 2;
|
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;
|
||||||
|
}
|
||||||
|
}*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,6 @@
|
|||||||
.l-inspector-part {
|
.l-inspector-part {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding-right: $interiorMargin;
|
padding-right: $interiorMargin;
|
||||||
|
|
||||||
.tree .form {
|
.tree .form {
|
||||||
margin-left: $treeVCW + $interiorMarginLg;
|
margin-left: $treeVCW + $interiorMarginLg;
|
||||||
}
|
}
|
||||||
@ -79,7 +78,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.form-row {
|
.form-row {
|
||||||
// To be replaced with .inspector-config, see below.
|
|
||||||
@include align-items(center);
|
@include align-items(center);
|
||||||
border: none !important;
|
border: none !important;
|
||||||
margin-bottom: 0 !important;
|
margin-bottom: 0 !important;
|
||||||
@ -101,12 +99,15 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ul li {
|
||||||
|
margin-bottom: $interiorMarginLg;
|
||||||
|
}
|
||||||
|
|
||||||
em.t-inspector-part-header {
|
em.t-inspector-part-header {
|
||||||
border-radius: $basicCr;
|
border-radius: $basicCr;
|
||||||
background-color: $colorInspectorSectionHeaderBg;
|
background-color: $colorInspectorSectionHeaderBg;
|
||||||
color: $colorInspectorSectionHeaderFg;
|
color: $colorInspectorSectionHeaderFg;
|
||||||
margin-top: $interiorMarginLg;
|
margin-bottom: $interiorMargin;
|
||||||
//margin-bottom: $interiorMargin;
|
|
||||||
padding: floor($formTBPad * .75) $formLRPad;
|
padding: floor($formTBPad * .75) $formLRPad;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
@ -200,102 +201,3 @@ mct-representation:not(.s-status-editing) .l-inspect {
|
|||||||
pointer-events: inherit;
|
pointer-events: inherit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NEW COMPACT FORM, FOR USE IN INSPECTOR
|
|
||||||
// ul > li > label, control
|
|
||||||
// Make a new UL for each form section
|
|
||||||
// Allow control-first, controls-below
|
|
||||||
|
|
||||||
.l-inspect .tree ul li,
|
|
||||||
.inspector-config ul li {
|
|
||||||
padding: 2px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.inspector-config {
|
|
||||||
$labelW: 40%;
|
|
||||||
$minW: $labelW;
|
|
||||||
ul {
|
|
||||||
margin-bottom: $interiorMarginLg;
|
|
||||||
li {
|
|
||||||
@include display(flex);
|
|
||||||
@include flex-wrap(wrap);
|
|
||||||
@include align-items(center);
|
|
||||||
label,
|
|
||||||
.control {
|
|
||||||
@include display(flex);
|
|
||||||
min-width: $minW;
|
|
||||||
}
|
|
||||||
label {
|
|
||||||
line-height: inherit;
|
|
||||||
padding: $interiorMarginSm 0;
|
|
||||||
width: $labelW;
|
|
||||||
}
|
|
||||||
.control {
|
|
||||||
@include flex-grow(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:not(.section-header) {
|
|
||||||
&:not(.connects-to-previous) {
|
|
||||||
//border-top: 1px solid $colorFormLines;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.connects-to-previous {
|
|
||||||
padding-top: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.section-header {
|
|
||||||
margin-top: $interiorMarginLg;
|
|
||||||
border-top: 1px solid $colorFormLines;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.controls-first {
|
|
||||||
.control {
|
|
||||||
@include flex-grow(0);
|
|
||||||
margin-right: $interiorMargin;
|
|
||||||
min-width: 0;
|
|
||||||
order: 1;
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
label {
|
|
||||||
@include flex-grow(1);
|
|
||||||
order: 2;
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&.controls-under {
|
|
||||||
display: block;
|
|
||||||
.control, label {
|
|
||||||
display: block;
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul li {
|
|
||||||
border-top: none !important;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-error {
|
|
||||||
// Block element that visually flags an error and contains a message
|
|
||||||
background-color: $colorFormFieldErrorBg;
|
|
||||||
color: $colorFormFieldErrorFg;
|
|
||||||
border-radius: $basicCr;
|
|
||||||
display: block;
|
|
||||||
padding: 1px 6px;
|
|
||||||
&:before {
|
|
||||||
content: $glyph-icon-alert-triangle;
|
|
||||||
display: inline;
|
|
||||||
font-family: symbolsfont;
|
|
||||||
margin-right: $interiorMarginSm;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.tree .inspector-config {
|
|
||||||
margin-left: $treeVCW + $interiorMarginLg;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@
|
|||||||
/********************************* CONTROLS */
|
/********************************* CONTROLS */
|
||||||
@import "controls/breadcrumb";
|
@import "controls/breadcrumb";
|
||||||
@import "controls/buttons";
|
@import "controls/buttons";
|
||||||
@import "controls/palette";
|
@import "controls/color-palette";
|
||||||
@import "controls/controls";
|
@import "controls/controls";
|
||||||
@import "controls/lists";
|
@import "controls/lists";
|
||||||
@import "controls/menus";
|
@import "controls/menus";
|
||||||
@ -70,7 +70,6 @@
|
|||||||
@import "fixed-position";
|
@import "fixed-position";
|
||||||
@import "lists/tabular";
|
@import "lists/tabular";
|
||||||
@import "plots/plots-main";
|
@import "plots/plots-main";
|
||||||
@import "plots/legend";
|
|
||||||
@import "iframe";
|
@import "iframe";
|
||||||
@import "views";
|
@import "views";
|
||||||
@import "items/item";
|
@import "items/item";
|
||||||
@ -81,4 +80,3 @@
|
|||||||
@import "autoflow";
|
@import "autoflow";
|
||||||
@import "features/imagery";
|
@import "features/imagery";
|
||||||
@import "features/time-display";
|
@import "features/time-display";
|
||||||
@import "widgets";
|
|
||||||
|
@ -5,7 +5,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.l-view-section {
|
.l-view-section {
|
||||||
//@include test(orange, 0.1);
|
|
||||||
@include absPosDefault(0);
|
@include absPosDefault(0);
|
||||||
h2 {
|
h2 {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
@ -1,306 +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.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
/************************************************************* WIDGET OBJECT */
|
|
||||||
.l-summary-widget {
|
|
||||||
// Widget layout classes here
|
|
||||||
@include ellipsize();
|
|
||||||
display: inline-block;
|
|
||||||
text-align: center;
|
|
||||||
.widget-label:before {
|
|
||||||
// Widget icon
|
|
||||||
font-size: 0.9em;
|
|
||||||
margin-right: $interiorMarginSm;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.s-summary-widget {
|
|
||||||
// Widget style classes here
|
|
||||||
@include boxShdw($shdwBtns);
|
|
||||||
border-radius: $basicCr;
|
|
||||||
border-style: solid;
|
|
||||||
border-width: 1px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
cursor: default;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
padding: $interiorMarginLg $interiorMarginLg * 2;
|
|
||||||
&[href] {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.widget-edit-holder {
|
|
||||||
// Hide edit area when in browse mode
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.widget-rule-header {
|
|
||||||
@extend .l-flex-row;
|
|
||||||
@include align-items(center);
|
|
||||||
margin-bottom: $interiorMargin;
|
|
||||||
> .flex-elem {
|
|
||||||
&:not(:first-child) {
|
|
||||||
margin-left: $interiorMargin;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.widget-rules-wrapper,
|
|
||||||
.widget-rule-content,
|
|
||||||
.w-widget-test-data-content {
|
|
||||||
@include trans-prop-nice($props: (height, min-height, opacity), $dur: 250ms);
|
|
||||||
min-height: 0;
|
|
||||||
height: 0;
|
|
||||||
opacity: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.widget-rules-wrapper {
|
|
||||||
flex: 1 1 auto !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.widget-rule-content.expanded {
|
|
||||||
overflow: visible !important;
|
|
||||||
min-height: 50px;
|
|
||||||
height: auto;
|
|
||||||
opacity: 1;
|
|
||||||
pointer-events: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.w-widget-test-data-content {
|
|
||||||
.l-enable {
|
|
||||||
padding: $interiorMargin 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.w-widget-test-data-items {
|
|
||||||
max-height: 20vh;
|
|
||||||
overflow-y: scroll !important;
|
|
||||||
padding-right: $interiorMargin;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.l-widget-thumb-wrapper,
|
|
||||||
.l-compact-form label {
|
|
||||||
$ruleLabelW: 40%;
|
|
||||||
$ruleLabelMaxW: 150px;
|
|
||||||
@include display(flex);
|
|
||||||
max-width: $ruleLabelMaxW;
|
|
||||||
width: $ruleLabelW;
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-message-widget-no-data {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/********************************************************** EDITING A WIDGET */
|
|
||||||
.s-status-editing > mct-view > .w-summary-widget {
|
|
||||||
// Classes for editor layout while editing a widget
|
|
||||||
// This selector is ugly and brittle, but needed to prevent interface from showing when widget is in a layout
|
|
||||||
// being edited.
|
|
||||||
@include absPosDefault();
|
|
||||||
@extend .l-flex-col;
|
|
||||||
|
|
||||||
> .l-summary-widget {
|
|
||||||
// Main view of the summary widget
|
|
||||||
// Give some airspace and center the widget in the area
|
|
||||||
margin: 30px auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.widget-edit-holder {
|
|
||||||
display: flex; // Overrides `display: none` during Browse mode
|
|
||||||
.flex-accordion-holder {
|
|
||||||
// Needed because otherwise accordion elements "creep" when contents expand and contract
|
|
||||||
display: block !important;
|
|
||||||
}
|
|
||||||
&.expanded-widget-test-data {
|
|
||||||
.w-widget-test-data-content {
|
|
||||||
min-height: 50px;
|
|
||||||
height: auto;
|
|
||||||
opacity: 1;
|
|
||||||
pointer-events: inherit;
|
|
||||||
}
|
|
||||||
&:not(.expanded-widget-rules) {
|
|
||||||
// Test data is expanded and rules are collapsed
|
|
||||||
// Make text data take up all the vertical space
|
|
||||||
.flex-accordion-holder { display: flex; }
|
|
||||||
.widget-test-data {
|
|
||||||
flex-grow: 999999;
|
|
||||||
}
|
|
||||||
.w-widget-test-data-items {
|
|
||||||
max-height: inherit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&.expanded-widget-rules {
|
|
||||||
.widget-rules-wrapper {
|
|
||||||
min-height: 50px;
|
|
||||||
height: auto;
|
|
||||||
opacity: 1;
|
|
||||||
pointer-events: inherit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.s-status-no-data {
|
|
||||||
.widget-edit-holder {
|
|
||||||
opacity: 0.3;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
.t-message-widget-no-data {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.l-compact-form {
|
|
||||||
// Overrides on .l-compact-form
|
|
||||||
ul {
|
|
||||||
&:last-child { margin: 0; }
|
|
||||||
li {
|
|
||||||
@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) {
|
|
||||||
&:not(.connects-to-previous) {
|
|
||||||
border-top: 1px solid $colorFormLines;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&.connects-to-previous {
|
|
||||||
padding: $interiorMargin 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
> label {
|
|
||||||
display: block; // Needed to align text to right
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.s-widget-test-data-item {
|
|
||||||
// Single line of ul li label span, etc.
|
|
||||||
ul {
|
|
||||||
li {
|
|
||||||
border: none !important;
|
|
||||||
> label {
|
|
||||||
display: inline-block;
|
|
||||||
width: auto;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.widget-edit-holder {
|
|
||||||
font-size: 0.8rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.widget-rules-wrapper {
|
|
||||||
// Wrapper area that holds n rules
|
|
||||||
box-sizing: border-box;
|
|
||||||
overflow-y: scroll;
|
|
||||||
padding-right: $interiorMargin;
|
|
||||||
}
|
|
||||||
|
|
||||||
.l-widget-rule,
|
|
||||||
.l-widget-test-data-item {
|
|
||||||
box-sizing: border-box;
|
|
||||||
margin-bottom: $interiorMarginSm;
|
|
||||||
padding: $interiorMargin $interiorMarginLg;
|
|
||||||
}
|
|
||||||
|
|
||||||
.l-widget-thumb-wrapper {
|
|
||||||
@extend .l-flex-row;
|
|
||||||
@include align-items(center);
|
|
||||||
> span { display: block; }
|
|
||||||
.grippy-holder,
|
|
||||||
.view-control {
|
|
||||||
margin-right: $interiorMargin;
|
|
||||||
width: 1em;
|
|
||||||
height: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.widget-thumb {
|
|
||||||
@include flex(1 1 auto);
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.rule-title {
|
|
||||||
@include flex(0 1 auto);
|
|
||||||
color: pullForward($colorBodyFg, 50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.rule-description {
|
|
||||||
@include flex(1 1 auto);
|
|
||||||
@include ellipsize();
|
|
||||||
color: pushBack($colorBodyFg, 20%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.s-widget-rule,
|
|
||||||
.s-widget-test-data-item {
|
|
||||||
background-color: rgba($colorBodyFg, 0.1);
|
|
||||||
border-radius: $basicCr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.widget-thumb {
|
|
||||||
@include ellipsize();
|
|
||||||
@extend .s-summary-widget;
|
|
||||||
@extend .l-summary-widget;
|
|
||||||
padding: $interiorMarginSm $interiorMargin;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hide and show elements in the rule-header on hover
|
|
||||||
.l-widget-rule,
|
|
||||||
.l-widget-test-data-item {
|
|
||||||
.grippy,
|
|
||||||
.l-rule-action-buttons-wrapper,
|
|
||||||
.l-condition-action-buttons-wrapper,
|
|
||||||
.l-widget-test-data-item-action-buttons-wrapper {
|
|
||||||
@include trans-prop-nice($props: opacity, $dur: 500ms);
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
&:hover {
|
|
||||||
.grippy,
|
|
||||||
.l-rule-action-buttons-wrapper,
|
|
||||||
.l-widget-test-data-item-action-buttons-wrapper {
|
|
||||||
@include trans-prop-nice($props: opacity, $dur: 0);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.l-rule-action-buttons-wrapper {
|
|
||||||
.t-delete {
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.t-condition {
|
|
||||||
&:hover {
|
|
||||||
.l-condition-action-buttons-wrapper {
|
|
||||||
@include trans-prop-nice($props: opacity, $dur: 0);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -19,15 +19,60 @@
|
|||||||
* this source code distribution or the Licensing information page available
|
* this source code distribution or the Licensing information page available
|
||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
.l-color-palette {
|
||||||
|
$d: 16px;
|
||||||
|
$colorsPerRow: 10;
|
||||||
|
$m: 1;
|
||||||
|
$colorSelectedColor: #fff;
|
||||||
|
|
||||||
define(['vue'], function (Vue) {
|
box-sizing: border-box;
|
||||||
function VueView(options) {
|
padding: $interiorMargin !important;
|
||||||
var vm = new Vue(options);
|
|
||||||
this.show = function (container) {
|
|
||||||
container.appendChild(vm.$mount().$el);
|
|
||||||
};
|
|
||||||
this.destroy = vm.$destroy.bind(vm);
|
|
||||||
}
|
|
||||||
|
|
||||||
return VueView;
|
.l-palette-row {
|
||||||
});
|
@include clearfix;
|
||||||
|
line-height: $d;
|
||||||
|
width: ($d * $colorsPerRow) + ($m * $colorsPerRow);
|
||||||
|
|
||||||
|
.l-palette-item {
|
||||||
|
box-sizing: border-box;
|
||||||
|
@include txtShdwSubtle(0.8);
|
||||||
|
@include trans-prop-nice-fade(0.25s);
|
||||||
|
border: 1px solid transparent;
|
||||||
|
color: $colorSelectedColor;
|
||||||
|
display: block;
|
||||||
|
float: left;
|
||||||
|
height: $d; width: $d;
|
||||||
|
line-height: $d * 0.9;
|
||||||
|
margin: 0 ($m * 1px) ($m * 1px) 0;
|
||||||
|
text-align: center;
|
||||||
|
&:before {
|
||||||
|
// Check mark for selected items
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.s-palette-item {
|
||||||
|
&:hover {
|
||||||
|
@include trans-prop-nice-fade(0);
|
||||||
|
border-color: $colorSelectedColor !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.l-palette-item-label {
|
||||||
|
margin-left: $interiorMargin;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.l-option-row {
|
||||||
|
margin-bottom: $interiorMargin;
|
||||||
|
.s-palette-item {
|
||||||
|
border-color: $colorBodyFg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -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 */
|
/******************************************************** CUSTOM CHECKBOXES */
|
||||||
label.checkbox.custom,
|
label.checkbox.custom,
|
||||||
label.radio.custom {
|
label.radio.custom {
|
||||||
@ -281,7 +261,7 @@ input[type="number"] {
|
|||||||
input[type="text"].lg { width: 100% !important; }
|
input[type="text"].lg { width: 100% !important; }
|
||||||
.l-input-med input[type="text"],
|
.l-input-med input[type="text"],
|
||||||
input[type="text"].med { width: 200px !important; }
|
input[type="text"].med { width: 200px !important; }
|
||||||
input[type="text"].sm, input[type="number"].sm { width: 50px !important; }
|
input[type="text"].sm { width: 50px !important; }
|
||||||
.l-numeric input[type="text"],
|
.l-numeric input[type="text"],
|
||||||
input[type="text"].numeric { text-align: right; }
|
input[type="text"].numeric { text-align: right; }
|
||||||
|
|
||||||
@ -337,10 +317,14 @@ input[type="text"].s-input-inline,
|
|||||||
.select {
|
.select {
|
||||||
@include btnSubtle($bg: $colorSelectBg);
|
@include btnSubtle($bg: $colorSelectBg);
|
||||||
@extend .icon-arrow-down; // Context arrow
|
@extend .icon-arrow-down; // Context arrow
|
||||||
|
@if $shdwBtns != none {
|
||||||
|
margin: 0 0 2px 0; // Needed to avoid dropshadow from being clipped by parent containers
|
||||||
|
}
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 0 $interiorMargin;
|
padding: 0 $interiorMargin;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
line-height: $formInputH;
|
||||||
select {
|
select {
|
||||||
@include appearance(none);
|
@include appearance(none);
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
@ -356,13 +340,11 @@ input[type="text"].s-input-inline,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
&:before {
|
&:before {
|
||||||
@include transform(translateY(-50%));
|
pointer-events: none;
|
||||||
color: rgba($colorInvokeMenu, percentToDecimal($contrastInvokeMenuPercent));
|
color: rgba($colorInvokeMenu, percentToDecimal($contrastInvokeMenuPercent));
|
||||||
display: block;
|
display: block;
|
||||||
pointer-events: none;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: $interiorMargin;
|
right: $interiorMargin; top: 0;
|
||||||
top: 50%;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -414,7 +396,8 @@ input[type="text"].s-input-inline,
|
|||||||
.l-elem-wrapper {
|
.l-elem-wrapper {
|
||||||
mct-representation {
|
mct-representation {
|
||||||
// Holds the context-available item
|
// Holds the context-available item
|
||||||
// Must have min-width to make flex work properly in Safari
|
// Must have min-width to make flex work properly
|
||||||
|
// in Safari
|
||||||
min-width: 0.7em;
|
min-width: 0.7em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -580,6 +563,7 @@ input[type="text"].s-input-inline,
|
|||||||
height: $h;
|
height: $h;
|
||||||
margin-top: 1 + floor($h/2) * -1;
|
margin-top: 1 + floor($h/2) * -1;
|
||||||
@include btnSubtle(pullForward($colorBtnBg, 10%));
|
@include btnSubtle(pullForward($colorBtnBg, 10%));
|
||||||
|
//border-radius: 50% !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin sliderKnobRound() {
|
@mixin sliderKnobRound() {
|
||||||
@ -594,6 +578,7 @@ input[type="text"].s-input-inline,
|
|||||||
|
|
||||||
input[type="range"] {
|
input[type="range"] {
|
||||||
// HTML5 range inputs
|
// HTML5 range inputs
|
||||||
|
|
||||||
-webkit-appearance: none; /* Hides the slider so that custom slider can be made */
|
-webkit-appearance: none; /* Hides the slider so that custom slider can be made */
|
||||||
background: transparent; /* Otherwise white in Chrome */
|
background: transparent; /* Otherwise white in Chrome */
|
||||||
&:focus {
|
&:focus {
|
||||||
@ -751,30 +736,6 @@ textarea {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.view-switcher,
|
|
||||||
.t-btn-view-large {
|
|
||||||
@include trans-prop-nice-fade($controlFadeMs);
|
|
||||||
}
|
|
||||||
|
|
||||||
.view-control {
|
|
||||||
@extend .icon-arrow-right;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 0.75em;
|
|
||||||
&:before {
|
|
||||||
position: absolute;
|
|
||||||
@include trans-prop-nice(transform, 100ms);
|
|
||||||
@include transform-origin(center);
|
|
||||||
}
|
|
||||||
&.expanded:before {
|
|
||||||
@include transform(rotate(90deg));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.grippy {
|
|
||||||
@extend .icon-grippy;
|
|
||||||
cursor: move;
|
|
||||||
}
|
|
||||||
|
|
||||||
/******************************************************** BROWSER ELEMENTS */
|
/******************************************************** BROWSER ELEMENTS */
|
||||||
body.desktop {
|
body.desktop {
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
|
@ -29,27 +29,23 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
font-size: 16px;
|
font-size: 16px; //120%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-label {
|
.title-label {
|
||||||
margin-left: $interiorMarginSm;
|
margin-left: $interiorMarginSm;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-swatch,
|
|
||||||
.color-swatch {
|
.color-swatch {
|
||||||
// Used in color menu buttons in toolbar
|
// Used in color menu buttons in toolbar
|
||||||
$d: 10px;
|
$d: 10px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
border: 1px solid rgba($colorBtnFg, 0.2);
|
border: 1px solid rgba($colorBtnFg, 0.2);
|
||||||
height: $d; width: $d;
|
height: $d;
|
||||||
line-height: $d;
|
width: $d;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
margin-left: $interiorMarginSm;
|
margin-left: $interiorMarginSm;
|
||||||
margin-top: -2px;
|
margin-top: -2px;
|
||||||
&:not(.no-selection) {
|
|
||||||
border-color: transparent;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&:after {
|
&:after {
|
||||||
@ -77,14 +73,6 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.s-menu {
|
|
||||||
border-radius: $basicCr;
|
|
||||||
@include containerSubtle($colorMenuBg, $colorMenuFg);
|
|
||||||
@include boxShdw($shdwMenu);
|
|
||||||
@include txtShdw($shdwMenuText);
|
|
||||||
padding: $interiorMarginSm 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu {
|
.menu {
|
||||||
border-radius: $basicCr;
|
border-radius: $basicCr;
|
||||||
@include containerSubtle($colorMenuBg, $colorMenuFg);
|
@include containerSubtle($colorMenuBg, $colorMenuFg);
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
* this source code distribution or the Licensing information page available
|
* this source code distribution or the Licensing information page available
|
||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
/******************************************************************* STATUS BLOCK ELEMS */
|
|
||||||
@mixin statusBannerColors($bg, $fg: $colorStatusFg) {
|
@mixin statusBannerColors($bg, $fg: $colorStatusFg) {
|
||||||
$bgPb: 30%;
|
$bgPb: 30%;
|
||||||
$bgPbD: 10%;
|
$bgPbD: 10%;
|
||||||
@ -140,7 +140,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/******************************************************************* MESSAGE BANNERS */
|
/* Styles for messages and message banners */
|
||||||
.message {
|
.message {
|
||||||
&.block {
|
&.block {
|
||||||
border-radius: $basicCr;
|
border-radius: $basicCr;
|
||||||
@ -196,6 +196,7 @@
|
|||||||
padding: 0 $interiorMargin;
|
padding: 0 $interiorMargin;
|
||||||
}
|
}
|
||||||
.close {
|
.close {
|
||||||
|
//@include test(red, 0.7);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 7px;
|
font-size: 7px;
|
||||||
width: 8px;
|
width: 8px;
|
||||||
@ -239,147 +240,132 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/******************************************************************* MESSAGES */
|
@mixin messageBlock($iconW: 32px) {
|
||||||
|
.type-icon.message-type {
|
||||||
/* Contexts:
|
|
||||||
In .t-message-list
|
|
||||||
In .overlay as a singleton
|
|
||||||
Inline in the view area
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Archetypal message
|
|
||||||
.l-message {
|
|
||||||
$iconW: 32px;
|
|
||||||
@include display(flex);
|
|
||||||
@include flex-direction(row);
|
|
||||||
@include align-items(stretch);
|
|
||||||
padding: $interiorMarginLg;
|
|
||||||
|
|
||||||
&:before {
|
|
||||||
// Icon
|
|
||||||
@include flex(0 1 auto);
|
|
||||||
@include txtShdw($shdwStatusIc);
|
@include txtShdw($shdwStatusIc);
|
||||||
@extend .icon-bell;
|
@extend .icon-bell;
|
||||||
color: $colorStatusDefault;
|
color: $colorStatusDefault;
|
||||||
font-size: $iconW;
|
font-size: $iconW;
|
||||||
|
padding: 1px;
|
||||||
width: $iconW + 2;
|
width: $iconW + 2;
|
||||||
margin-right: $interiorMarginLg;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.message-severity-info:before {
|
.message-severity-info .type-icon.message-type {
|
||||||
@extend .icon-info;
|
@extend .icon-info;
|
||||||
color: $colorInfo;
|
color: $colorInfo;
|
||||||
}
|
}
|
||||||
|
.message-severity-alert .type-icon.message-type {
|
||||||
&.message-severity-alert:before {
|
@extend .icon-bell;
|
||||||
color: $colorWarningLo;
|
color: $colorWarningLo;
|
||||||
}
|
}
|
||||||
|
.message-severity-error .type-icon.message-type {
|
||||||
&.message-severity-error:before {
|
|
||||||
@extend .icon-alert-rect;
|
@extend .icon-alert-rect;
|
||||||
color: $colorWarningHi;
|
color: $colorWarningHi;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/* Paths:
|
||||||
|
t-dialog | t-dialog-sm > t-message-single | t-message-list > overlay > holder > contents > l-message >
|
||||||
|
message-type > (icon)
|
||||||
|
message-contents >
|
||||||
|
top-bar >
|
||||||
|
title
|
||||||
|
hint
|
||||||
|
editor >
|
||||||
|
(if displaying list of messages)
|
||||||
|
ul > li > l-message >
|
||||||
|
... same as above
|
||||||
|
bottom-bar
|
||||||
|
*/
|
||||||
|
|
||||||
|
.l-message {
|
||||||
.w-message-contents {
|
|
||||||
@include flex(1 1 auto);
|
|
||||||
@include display(flex);
|
@include display(flex);
|
||||||
@include flex-direction(column);
|
@include flex-direction(row);
|
||||||
|
@include align-items(stretch);
|
||||||
> div,
|
.type-icon.message-type {
|
||||||
> span {
|
@include flex(0 1 auto);
|
||||||
//@include test(red);
|
position: relative;
|
||||||
margin-bottom: $interiorMargin;
|
|
||||||
}
|
}
|
||||||
|
.message-contents {
|
||||||
|
@include flex(1 1 auto);
|
||||||
|
margin-left: $overlayMargin;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
.message-body {
|
.top-bar,
|
||||||
@include flex(1 1 100%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Singleton in an overlay dialog
|
|
||||||
.t-message-single .l-message,
|
|
||||||
.t-message-single.l-message {
|
|
||||||
$iconW: 80px;
|
|
||||||
@include absPosDefault();
|
|
||||||
padding: 0;
|
|
||||||
&:before {
|
|
||||||
font-size: $iconW;
|
|
||||||
width: $iconW + 2;
|
|
||||||
}
|
|
||||||
.title {
|
|
||||||
font-size: 1.2em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Singleton inline in a view
|
|
||||||
.t-message-inline .l-message,
|
|
||||||
.t-message-inline.l-message {
|
|
||||||
border-radius: $controlCr;
|
|
||||||
&.message-severity-info { background-color: rgba($colorInfo, 0.3); }
|
|
||||||
&.message-severity-alert { background-color: rgba($colorWarningLo, 0.3); }
|
|
||||||
&.message-severity-error { background-color: rgba($colorWarningHi, 0.3); }
|
|
||||||
|
|
||||||
.w-message-contents.l-message-body-only {
|
|
||||||
.message-body {
|
.message-body {
|
||||||
margin-top: $interiorMargin;
|
margin-bottom: $interiorMarginLg * 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// In a list
|
|
||||||
.t-message-list {
|
|
||||||
@include absPosDefault();
|
|
||||||
@include display(flex);
|
|
||||||
@include flex-direction(column);
|
|
||||||
|
|
||||||
> div,
|
// Message as singleton
|
||||||
> span {
|
.t-message-single {
|
||||||
margin-bottom: $interiorMargin;
|
@include messageBlock(80px);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.desktop .t-message-single {
|
||||||
|
.l-message,
|
||||||
|
.bottom-bar {
|
||||||
|
@include absPosDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
.w-messages {
|
.bottom-bar {
|
||||||
@include flex(1 1 100%);
|
top: auto;
|
||||||
overflow-y: auto;
|
height: $ovrFooterH;
|
||||||
padding-right: $interiorMargin;
|
|
||||||
}
|
}
|
||||||
// Each message
|
|
||||||
.l-message {
|
|
||||||
border-radius: $controlCr;
|
|
||||||
background: rgba($colorOvrFg, 0.1);
|
|
||||||
margin-bottom: $interiorMargin;
|
|
||||||
.hint,
|
|
||||||
.bottom-bar {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@include phonePortrait {
|
@include phonePortrait {
|
||||||
.t-message-single .l-message,
|
.t-message-single {
|
||||||
.t-message-single.l-message {
|
.l-message {
|
||||||
@include flex-direction(column);
|
@include flex-direction(column);
|
||||||
&:before {
|
.message-contents { margin-left: 0; }
|
||||||
margin-right: 0;
|
}
|
||||||
|
.type-icon.message-type {
|
||||||
margin-bottom: $interiorMarginLg;
|
margin-bottom: $interiorMarginLg;
|
||||||
text-align: center;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bottom-bar {
|
.bottom-bar {
|
||||||
text-align: center;
|
text-align: center !important;
|
||||||
.s-button {
|
}
|
||||||
display: block;
|
}
|
||||||
width: 100%;
|
}
|
||||||
|
|
||||||
|
// Messages in list
|
||||||
|
.t-message-list {
|
||||||
|
@include messageBlock(32px);
|
||||||
|
|
||||||
|
.message-contents {
|
||||||
|
.l-message {
|
||||||
|
border-radius: $controlCr;
|
||||||
|
background: rgba($colorOvrFg, 0.1);
|
||||||
|
margin-bottom: $interiorMargin;
|
||||||
|
padding: $interiorMarginLg;
|
||||||
|
|
||||||
|
.message-contents,
|
||||||
|
.bottom-bar {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-contents {
|
||||||
|
font-size: 0.9em;
|
||||||
|
margin-left: $interiorMarginLg;
|
||||||
|
.message-action { color: pushBack($colorOvrFg, 20%); }
|
||||||
|
.bottom-bar { text-align: left; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-bar,
|
||||||
|
.message-body {
|
||||||
|
margin-bottom: $interiorMarginLg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
body.desktop .t-message-list {
|
body.desktop .t-message-list {
|
||||||
.w-message-contents { padding-right: $interiorMargin; }
|
.message-contents .l-message { margin-right: $interiorMarginLg; }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alert elements in views
|
// Alert elements in views
|
||||||
@ -398,6 +384,10 @@ body.desktop .t-message-list {
|
|||||||
.object-header {
|
.object-header {
|
||||||
.t-object-alert {
|
.t-object-alert {
|
||||||
display: inline;
|
display: inline;
|
||||||
|
&.t-alert-unsynced {
|
||||||
|
@extend .icon-alert-triangle;
|
||||||
|
color: $colorPausedBg;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,89 +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.
|
|
||||||
*****************************************************************************/
|
|
||||||
.l-palette {
|
|
||||||
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-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%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -26,12 +26,45 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
min-height: 2160px;
|
||||||
|
min-width: 3840px;
|
||||||
|
pointer-events: none;
|
||||||
.l-grid {
|
.l-grid {
|
||||||
@extend .abs;
|
@extend .abs;
|
||||||
pointer-events: none;
|
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
&.l-grid-y { background-position: 0 1px; }
|
&.l-grid-y { background-position: 0 1px; }
|
||||||
}
|
}
|
||||||
|
.l-sizing-guide {
|
||||||
|
$c: rgba(#fff, 0.3);
|
||||||
|
$b: 1px solid $c;
|
||||||
|
$d: 50px;
|
||||||
|
$offsetL: ($ueCollapsedPaneEdgeM * 2) + $interiorMarginSm * 2;
|
||||||
|
$offsetT: 165px;
|
||||||
|
border-right: $b;
|
||||||
|
border-bottom: $b;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
height: $d; width: $d;
|
||||||
|
overflow: visible;
|
||||||
|
position: absolute;
|
||||||
|
&:after {
|
||||||
|
color: $c;
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
right: $interiorMargin;
|
||||||
|
bottom: $interiorMargin;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
&.t-size-720p {
|
||||||
|
left: 1280px - $d - $offsetL;
|
||||||
|
top: 720px - $d - $offsetT;
|
||||||
|
&:after { content: '720P [1280 x 720]'; }
|
||||||
|
}
|
||||||
|
&.t-size-1080p {
|
||||||
|
left: 1920px - $d - $offsetL;
|
||||||
|
top: 1080px - $d - $offsetT;
|
||||||
|
&:after { content: '1080P [1920 x 1080]'; }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.s-status-editing {
|
.s-status-editing {
|
||||||
@ -80,32 +113,23 @@
|
|||||||
|
|
||||||
// Editing Grids
|
// Editing Grids
|
||||||
.l-grid-holder {
|
.l-grid-holder {
|
||||||
|
display: block;
|
||||||
.l-grid {
|
.l-grid {
|
||||||
&.l-grid-x { @include bgTicks($colorGridLines, 'x'); }
|
&.l-grid-x { @include bgTicks($colorGridLines, 'x'); }
|
||||||
&.l-grid-y { @include bgTicks($colorGridLines, 'y'); }
|
&.l-grid-y { @include bgTicks($colorGridLines, 'y'); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display grid when selected or selection parent.
|
// Prevent nested frames from showing their grids
|
||||||
.s-selected .l-grid-holder,
|
.t-frame-outer .l-grid-holder { display: none !important; }
|
||||||
.s-selected-parent .l-grid-holder {
|
|
||||||
display: block;
|
// Prevent nested elements from showing s-hover-border
|
||||||
|
.t-frame-outer .s-hover-border {
|
||||||
|
border: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display in nested frames...
|
// Prevent nested frames from being selectable until we have proper sub-object editing
|
||||||
.t-frame-outer {
|
.t-frame-outer .t-frame-outer {
|
||||||
// ...when drilled in or selection parent...
|
pointer-events: none;
|
||||||
&.s-drilled-in, &.s-selected-parent {
|
|
||||||
.l-grid-holder {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
.t-frame-outer:not(.s-drilled-in) .l-grid-holder {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// ...but hide otherwise.
|
|
||||||
.l-grid-holder {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,19 +20,7 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
.section-header {
|
.section-header {
|
||||||
border-radius: $basicCr;
|
|
||||||
background: $colorFormSectionHeader;
|
|
||||||
color: lighten($colorBodyFg, 20%);
|
|
||||||
font-size: inherit;
|
|
||||||
margin: $interiorMargin 0;
|
|
||||||
padding: $formTBPad $formLRPad;
|
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
.view-control {
|
|
||||||
display: inline-block;
|
|
||||||
margin-right: $interiorMargin;
|
|
||||||
width: 1em;
|
|
||||||
height: 1em;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.form {
|
.form {
|
||||||
@ -53,6 +41,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.section-header {
|
||||||
|
border-radius: $basicCr;
|
||||||
|
background: $colorFormSectionHeader;
|
||||||
|
$c: lighten($colorBodyFg, 20%);
|
||||||
|
color: $c;
|
||||||
|
font-size: 0.8em;
|
||||||
|
padding: $formTBPad $formLRPad;
|
||||||
|
}
|
||||||
|
|
||||||
.form-row {
|
.form-row {
|
||||||
$m: $interiorMargin;
|
$m: $interiorMargin;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
@ -60,6 +57,9 @@
|
|||||||
margin-bottom: $interiorMarginLg * 2;
|
margin-bottom: $interiorMarginLg * 2;
|
||||||
padding: $formTBPad 0;
|
padding: $formTBPad 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
//&ng-form {
|
||||||
|
// display: block;
|
||||||
|
//}
|
||||||
|
|
||||||
&.first {
|
&.first {
|
||||||
border-top: none;
|
border-top: none;
|
||||||
@ -171,106 +171,3 @@
|
|||||||
padding: $interiorMargin;
|
padding: $interiorMargin;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**************************************************************************** COMPACT FORM */
|
|
||||||
// ul > li > label, control
|
|
||||||
// Make a new UL for each form section
|
|
||||||
// Allow control-first, controls-below
|
|
||||||
// TO-DO: migrate work in branch ch-plot-styling that users .inspector-config to use classes below instead
|
|
||||||
|
|
||||||
.l-compact-form .tree ul li,
|
|
||||||
.l-compact-form ul li {
|
|
||||||
padding: 2px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.l-compact-form {
|
|
||||||
$labelW: 40%;
|
|
||||||
$minW: $labelW;
|
|
||||||
ul {
|
|
||||||
margin-bottom: $interiorMarginLg;
|
|
||||||
li {
|
|
||||||
@include display(flex);
|
|
||||||
@include flex-wrap(wrap);
|
|
||||||
@include align-items(center);
|
|
||||||
label,
|
|
||||||
.control {
|
|
||||||
@include display(flex);
|
|
||||||
}
|
|
||||||
label {
|
|
||||||
line-height: inherit;
|
|
||||||
width: $labelW;
|
|
||||||
}
|
|
||||||
.controls {
|
|
||||||
@include flex-grow(1);
|
|
||||||
margin-left: $interiorMargin;
|
|
||||||
input[type="text"],
|
|
||||||
input[type="search"],
|
|
||||||
input[type="number"],
|
|
||||||
.select {
|
|
||||||
height: $btnStdH;
|
|
||||||
line-height: $btnStdH;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.e-control {
|
|
||||||
// Individual form controls
|
|
||||||
&:not(:first-child) {
|
|
||||||
margin-left: $interiorMarginSm;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.connects-to-previous {
|
|
||||||
padding-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.section-header {
|
|
||||||
margin-top: $interiorMarginLg;
|
|
||||||
border-top: 1px solid $colorFormLines;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.controls-first {
|
|
||||||
.control {
|
|
||||||
@include flex-grow(0);
|
|
||||||
margin-right: $interiorMargin;
|
|
||||||
min-width: 0;
|
|
||||||
order: 1;
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
label {
|
|
||||||
@include flex-grow(1);
|
|
||||||
order: 2;
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&.controls-under {
|
|
||||||
display: block;
|
|
||||||
.control, label {
|
|
||||||
display: block;
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul li {
|
|
||||||
border-top: none !important;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-error {
|
|
||||||
// Block element that visually flags an error and contains a message
|
|
||||||
background-color: $colorFormFieldErrorBg;
|
|
||||||
color: $colorFormFieldErrorFg;
|
|
||||||
border-radius: $basicCr;
|
|
||||||
display: block;
|
|
||||||
padding: 1px 6px;
|
|
||||||
&:before {
|
|
||||||
content: $glyph-icon-alert-triangle;
|
|
||||||
display: inline;
|
|
||||||
font-family: symbolsfont;
|
|
||||||
margin-right: $interiorMarginSm;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -34,7 +34,18 @@ body.touch {
|
|||||||
line-height: $mobileTreeItemH !important;
|
line-height: $mobileTreeItemH !important;
|
||||||
.view-control {
|
.view-control {
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
width: ceil($mobileTreeItemH * 0.5);
|
margin-right: $interiorMargin;
|
||||||
|
width: ceil($mobileTreeItemH * 0.75);
|
||||||
|
&.has-children {
|
||||||
|
&:before {
|
||||||
|
content: $glyph-icon-arrow-down;
|
||||||
|
left: 50%;
|
||||||
|
@include transform(translateX(-50%) rotate(-90deg));
|
||||||
|
}
|
||||||
|
&.expanded:before {
|
||||||
|
@include transform(translateX(-50%) rotate(0deg));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.t-object-label {
|
.t-object-label {
|
||||||
line-height: inherit;
|
line-height: inherit;
|
||||||
|
@ -79,7 +79,6 @@
|
|||||||
|
|
||||||
// Dialog boxes, size constrained and centered in desktop/tablet
|
// Dialog boxes, size constrained and centered in desktop/tablet
|
||||||
&.l-dialog {
|
&.l-dialog {
|
||||||
font-size: 0.8rem;
|
|
||||||
.s-button {
|
.s-button {
|
||||||
&:not(.major) {
|
&:not(.major) {
|
||||||
@include btnSubtle($bg: $colorOvrBtnBg, $bgHov: pullForward($colorOvrBtnBg, 10%), $fg: $colorOvrBtnFg, $fgHov: $colorOvrBtnFg, $ic: $colorOvrBtnFg, $icHov: $colorOvrBtnFg);
|
@include btnSubtle($bg: $colorOvrBtnBg, $bgHov: pullForward($colorOvrBtnBg, 10%), $fg: $colorOvrBtnFg, $fgHov: $colorOvrBtnFg, $ic: $colorOvrBtnFg, $icHov: $colorOvrBtnFg);
|
||||||
@ -126,9 +125,9 @@
|
|||||||
@include containerSubtle($colorOvrBg, $colorOvrFg);
|
@include containerSubtle($colorOvrBg, $colorOvrFg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dialog-title {
|
.title {
|
||||||
@include ellipsize();
|
@include ellipsize();
|
||||||
font-size: 1.5em;
|
font-size: 1.2em;
|
||||||
line-height: 120%;
|
line-height: 120%;
|
||||||
margin-bottom: $interiorMargin;
|
margin-bottom: $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.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
.abs.holder-plot {
|
.abs.holder-plot {
|
||||||
right: $interiorMargin; // Fend off the scrollbar when less than min-height;
|
// Fend off the scrollbar when less than min-height;
|
||||||
.t-object-alert.t-alert-unsynced {
|
right: $interiorMargin;
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/********************************************* 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 {
|
.gl-plot {
|
||||||
color: $colorPlotFg;
|
color: $colorPlotFg;
|
||||||
display: flex;
|
|
||||||
font-size: 0.7rem;
|
font-size: 0.7rem;
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
min-height: $plotMinH;
|
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 {
|
.gl-plot-wrapper-display-area-and-x-axis {
|
||||||
// Holds the plot area and the X-axis only
|
// Holds the plot area and the X-axis only
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -95,6 +49,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.gl-plot-axis-area.gl-plot-x {
|
.gl-plot-axis-area.gl-plot-x {
|
||||||
|
//@include test(green);
|
||||||
top: auto;
|
top: auto;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
@ -108,7 +63,7 @@
|
|||||||
.gl-plot-axis-area {
|
.gl-plot-axis-area {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
&.gl-plot-y {
|
&.gl-plot-y {
|
||||||
top: nth($plotDisplayArea, 1);
|
top: $plotLegendH + $interiorMargin;
|
||||||
right: auto;
|
right: auto;
|
||||||
bottom: nth($plotDisplayArea, 3);
|
bottom: nth($plotDisplayArea, 3);
|
||||||
left: 0;
|
left: 0;
|
||||||
@ -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 */
|
/****************************** Limits and Out-of-Bounds data */
|
||||||
|
|
||||||
.l-limit-bar,
|
.l-limit-bar,
|
||||||
@ -269,6 +235,39 @@
|
|||||||
border: 1px solid $colorPlotAreaBorder;
|
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 {
|
.tick {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
border: 0 $colorPlotHash solid;
|
border: 0 $colorPlotHash solid;
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
ul.tree {
|
ul.tree {
|
||||||
@include menuUlReset();
|
@include menuUlReset();
|
||||||
@include user-select(none);
|
@include user-select(none);
|
||||||
> li {
|
li {
|
||||||
display: block;
|
display: block;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
@ -52,11 +52,21 @@ ul.tree {
|
|||||||
|
|
||||||
.view-control {
|
.view-control {
|
||||||
color: $colorItemTreeVC;
|
color: $colorItemTreeVC;
|
||||||
|
font-size: 0.75em;
|
||||||
margin-right: $interiorMargin;
|
margin-right: $interiorMargin;
|
||||||
|
height: 100%;
|
||||||
|
line-height: inherit;
|
||||||
width: $treeVCW;
|
width: $treeVCW;
|
||||||
&:before { display: block; }
|
&.has-children {
|
||||||
&.no-children {
|
&:before {
|
||||||
&:before { display: none; }
|
position: absolute;
|
||||||
|
@include trans-prop-nice(transform, 100ms);
|
||||||
|
content: "\e904";
|
||||||
|
@include transform-origin(center);
|
||||||
|
}
|
||||||
|
&.expanded:before {
|
||||||
|
@include transform(rotate(90deg));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,10 +26,12 @@
|
|||||||
z-index: 0; // Needed to prevent child-frame controls from showing through when another child-frame is above
|
z-index: 0; // Needed to prevent child-frame controls from showing through when another child-frame is above
|
||||||
&:not(.no-frame) {
|
&:not(.no-frame) {
|
||||||
background: $colorBodyBg;
|
background: $colorBodyBg;
|
||||||
border-color: $bc;
|
border: 1px solid $bc;
|
||||||
|
&:hover {
|
||||||
|
border-color: lighten($bc, 10%);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.object-browse-bar {
|
.object-browse-bar {
|
||||||
font-size: 0.75em;
|
font-size: 0.75em;
|
||||||
height: $ohH;
|
height: $ohH;
|
||||||
@ -42,8 +44,7 @@
|
|||||||
|
|
||||||
&.t-object-type-timer,
|
&.t-object-type-timer,
|
||||||
&.t-object-type-clock,
|
&.t-object-type-clock,
|
||||||
&.t-object-type-hyperlink,
|
&.t-object-type-hyperlink {
|
||||||
&.t-object-type-summary-widget {
|
|
||||||
// Hide the right side buttons for objects where they don't make sense
|
// Hide the right side buttons for objects where they don't make sense
|
||||||
// Note that this will hide the view Switcher button if applied
|
// Note that this will hide the view Switcher button if applied
|
||||||
// to an object that has it.
|
// to an object that has it.
|
||||||
@ -90,9 +91,9 @@
|
|||||||
|
|
||||||
&.no-frame {
|
&.no-frame {
|
||||||
background: transparent !important;
|
background: transparent !important;
|
||||||
border-color: transparent;
|
border: none !important;
|
||||||
.object-browse-bar .right {
|
.object-browse-bar .right {
|
||||||
$m: 0;
|
$m: 0; // $interiorMarginSm;
|
||||||
background: rgba(black, 0.3);
|
background: rgba(black, 0.3);
|
||||||
border-radius: $basicCr;
|
border-radius: $basicCr;
|
||||||
padding: $interiorMarginSm;
|
padding: $interiorMarginSm;
|
||||||
@ -102,7 +103,7 @@
|
|||||||
}
|
}
|
||||||
&.t-frame-outer > .t-rep-frame {
|
&.t-frame-outer > .t-rep-frame {
|
||||||
&.contents {
|
&.contents {
|
||||||
$m: 0px;
|
$m: 2px;
|
||||||
top: $m;
|
top: $m;
|
||||||
right: $m;
|
right: $m;
|
||||||
bottom: $m;
|
bottom: $m;
|
||||||
@ -113,7 +114,6 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
> .object-holder.abs {
|
> .object-holder.abs {
|
||||||
overflow: hidden;
|
|
||||||
top: 0 !important;
|
top: 0 !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -125,21 +125,14 @@
|
|||||||
pointer-events: none !important;
|
pointer-events: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/********************************************************** OBJECT TYPES */
|
/********************************************************** OBJECT TYPES */
|
||||||
.t-object-type-hyperlink,
|
.t-object-type-hyperlink {
|
||||||
.t-object-type-summary-widget {
|
|
||||||
.object-holder {
|
.object-holder {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.w-summary-widget,
|
|
||||||
.l-summary-widget,
|
|
||||||
.l-hyperlink.s-button {
|
.l-hyperlink.s-button {
|
||||||
// Some object types expand to the full size of the object-holder.
|
// When a hyperlink is a button in a frame, make it expand to fill out to the object-holder
|
||||||
@extend .abs;
|
@extend .abs;
|
||||||
}
|
|
||||||
|
|
||||||
.l-summary-widget,
|
|
||||||
.l-hyperlink.s-button {
|
|
||||||
.label {
|
.label {
|
||||||
@include ellipsize();
|
@include ellipsize();
|
||||||
@include transform(translateY(-50%));
|
@include transform(translateY(-50%));
|
||||||
|
@ -20,52 +20,35 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
.s-hover-border {
|
.s-hover-border {
|
||||||
border: 1px solid transparent;
|
border: 1px dotted transparent;
|
||||||
&:hover {
|
|
||||||
border-color: rgba($colorSelectableSelectedPrimary, 0.5) !important;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.s-status-editing {
|
.s-status-editing {
|
||||||
// Limit to editing mode
|
// Limit to editing mode until we have sub-object selection
|
||||||
$o: 0.5;
|
|
||||||
$oHover: 0.8;
|
|
||||||
$bc: $colorSelectableSelectedPrimary;
|
|
||||||
.s-hover-border {
|
.s-hover-border {
|
||||||
// Show a border by default so user can see object bounds and empty objects
|
// Show a border by default so user can see object bounds and empty objects
|
||||||
border-color: rgba($bc, $o) !important;
|
border: 1px dotted rgba($colorSelectableSelectedPrimary, 0.3) !important;
|
||||||
border-style: dotted !important;
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
border-color: rgba($bc, $oHover) !important;
|
border-color: rgba($colorSelectableSelectedPrimary, 0.7) !important;
|
||||||
}
|
|
||||||
|
|
||||||
&.t-object-type-layout {
|
|
||||||
border-style: dashed !important;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.s-selected {
|
|
||||||
&.s-moveable {
|
.s-selected > .s-hover-border,
|
||||||
&:not(.s-drilled-in) {
|
.s-selected.s-hover-border {
|
||||||
cursor: move;
|
// Styles for a selected object. Also used by legacy Fixed Position/Panel objects.
|
||||||
|
border-color: $colorSelectableSelectedPrimary !important;
|
||||||
|
@include boxShdwLarge();
|
||||||
|
// Show edit-corners if you got 'em
|
||||||
|
.edit-corner {
|
||||||
|
display: block;
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba($colorKey, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.s-selected > .s-hover-border,
|
.s-selected > .s-moveable,
|
||||||
.s-selected.s-hover-border {
|
.s-selected.s-moveable {
|
||||||
// Styles for a selected object. Also used by legacy Fixed Position/Panel objects.
|
cursor: move;
|
||||||
border-color: $colorSelectableSelectedPrimary !important;
|
|
||||||
@include boxShdwLarge();
|
|
||||||
// Show edit-corners if you got 'em
|
|
||||||
.edit-corner {
|
|
||||||
display: block;
|
|
||||||
&:hover {
|
|
||||||
background-color: rgba($colorKey, 1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -20,7 +20,7 @@
|
|||||||
at runtime from the About dialog for additional information.
|
at runtime from the About dialog for additional information.
|
||||||
-->
|
-->
|
||||||
<span ng-controller="DateTimeFieldController">
|
<span ng-controller="DateTimeFieldController">
|
||||||
<input type="text" autocorrect="off" spellcheck="false"
|
<input type="text"
|
||||||
ng-model="textValue"
|
ng-model="textValue"
|
||||||
ng-blur="restoreTextValue(); ngBlur()"
|
ng-blur="restoreTextValue(); ngBlur()"
|
||||||
ng-mouseup="ngMouseup()"
|
ng-mouseup="ngMouseup()"
|
||||||
|
@ -40,7 +40,7 @@ define(
|
|||||||
|
|
||||||
// Gets an array of the contextual parents/ancestors of the selected object
|
// Gets an array of the contextual parents/ancestors of the selected object
|
||||||
function getContextualPath() {
|
function getContextualPath() {
|
||||||
var currentObj = $scope.domainObject,
|
var currentObj = $scope.ngModel.selectedObject,
|
||||||
currentParent,
|
currentParent,
|
||||||
parents = [];
|
parents = [];
|
||||||
|
|
||||||
@ -68,7 +68,7 @@ define(
|
|||||||
|
|
||||||
// If this the the initial call of this recursive function
|
// If this the the initial call of this recursive function
|
||||||
if (!current) {
|
if (!current) {
|
||||||
current = $scope.domainObject;
|
current = $scope.ngModel.selectedObject;
|
||||||
$scope.primaryParents = [];
|
$scope.primaryParents = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,16 +87,16 @@ define(
|
|||||||
|
|
||||||
// Gets the metadata for the selected object
|
// Gets the metadata for the selected object
|
||||||
function getMetadata() {
|
function getMetadata() {
|
||||||
$scope.metadata = $scope.domainObject &&
|
$scope.metadata = $scope.ngModel.selectedObject &&
|
||||||
$scope.domainObject.hasCapability('metadata') &&
|
$scope.ngModel.selectedObject.hasCapability('metadata') &&
|
||||||
$scope.domainObject.useCapability('metadata');
|
$scope.ngModel.selectedObject.useCapability('metadata');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set scope variables when the selected object changes
|
// Set scope variables when the selected object changes
|
||||||
$scope.$watch('domainObject', function () {
|
$scope.$watch('ngModel.selectedObject', function () {
|
||||||
$scope.isLink = $scope.domainObject &&
|
$scope.isLink = $scope.ngModel.selectedObject &&
|
||||||
$scope.domainObject.hasCapability('location') &&
|
$scope.ngModel.selectedObject.hasCapability('location') &&
|
||||||
$scope.domainObject.getCapability('location').isLink();
|
$scope.ngModel.selectedObject.getCapability('location').isLink();
|
||||||
|
|
||||||
if ($scope.isLink) {
|
if ($scope.isLink) {
|
||||||
getPrimaryPath();
|
getPrimaryPath();
|
||||||
@ -109,7 +109,7 @@ define(
|
|||||||
getMetadata();
|
getMetadata();
|
||||||
});
|
});
|
||||||
|
|
||||||
var mutation = $scope.domainObject.getCapability('mutation');
|
var mutation = $scope.ngModel.selectedObject.getCapability('mutation');
|
||||||
var unlisten = mutation.listen(getMetadata);
|
var unlisten = mutation.listen(getMetadata);
|
||||||
$scope.$on('$destroy', unlisten);
|
$scope.$on('$destroy', unlisten);
|
||||||
}
|
}
|
||||||
|
@ -83,9 +83,9 @@ define([
|
|||||||
this.activeObject = domainObject;
|
this.activeObject = domainObject;
|
||||||
|
|
||||||
if (domainObject && domainObject.hasCapability('composition')) {
|
if (domainObject && domainObject.hasCapability('composition')) {
|
||||||
$(this.toggleView.elements()).removeClass('no-children');
|
$(this.toggleView.elements()).addClass('has-children');
|
||||||
} else {
|
} else {
|
||||||
$(this.toggleView.elements()).addClass('no-children');
|
$(this.toggleView.elements()).removeClass('has-children');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (domainObject && domainObject.hasCapability('status')) {
|
if (domainObject && domainObject.hasCapability('status')) {
|
||||||
|
@ -41,6 +41,16 @@ define(
|
|||||||
"$scope",
|
"$scope",
|
||||||
["$watch", "$on"]
|
["$watch", "$on"]
|
||||||
);
|
);
|
||||||
|
mockScope.ngModel = {};
|
||||||
|
mockScope.ngModel.selectedObject = {
|
||||||
|
getCapability: function () {
|
||||||
|
return {
|
||||||
|
listen: function () {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
mockObjectService = jasmine.createSpyObj(
|
mockObjectService = jasmine.createSpyObj(
|
||||||
"objectService",
|
"objectService",
|
||||||
@ -67,27 +77,22 @@ define(
|
|||||||
"location capability",
|
"location capability",
|
||||||
["isLink"]
|
["isLink"]
|
||||||
);
|
);
|
||||||
|
|
||||||
mockDomainObject.getCapability.andCallFake(function (param) {
|
mockDomainObject.getCapability.andCallFake(function (param) {
|
||||||
if (param === 'location') {
|
if (param === 'location') {
|
||||||
return mockLocationCapability;
|
return mockLocationCapability;
|
||||||
} else if (param === 'context') {
|
} else if (param === 'context') {
|
||||||
return mockContextCapability;
|
return mockContextCapability;
|
||||||
} else if (param === 'mutation') {
|
|
||||||
return {
|
|
||||||
listen: function () {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
mockScope.domainObject = mockDomainObject;
|
|
||||||
controller = new ObjectInspectorController(mockScope, mockObjectService);
|
controller = new ObjectInspectorController(mockScope, mockObjectService);
|
||||||
|
|
||||||
|
// Change the selected object to trigger the watch call
|
||||||
|
mockScope.ngModel.selectedObject = mockDomainObject;
|
||||||
});
|
});
|
||||||
|
|
||||||
it("watches for changes to the selected object", function () {
|
it("watches for changes to the selected object", function () {
|
||||||
expect(mockScope.$watch).toHaveBeenCalledWith('domainObject', jasmine.any(Function));
|
expect(mockScope.$watch).toHaveBeenCalledWith('ngModel.selectedObject', jasmine.any(Function));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("looks for contextual parent objects", function () {
|
it("looks for contextual parent objects", function () {
|
||||||
|
@ -65,10 +65,6 @@ define(
|
|||||||
options = Object.create(OPTIONS);
|
options = Object.create(OPTIONS);
|
||||||
options.marginX = -bubbleSpaceLR;
|
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,
|
// On a phone, bubble takes up more screen real estate,
|
||||||
// so position it differently (toward the bottom)
|
// so position it differently (toward the bottom)
|
||||||
if (this.agentService.isPhone()) {
|
if (this.agentService.isPhone()) {
|
||||||
|
@ -38,8 +38,7 @@ define([
|
|||||||
"implementation": InspectorController,
|
"implementation": InspectorController,
|
||||||
"depends": [
|
"depends": [
|
||||||
"$scope",
|
"$scope",
|
||||||
"openmct",
|
"policyService"
|
||||||
"$document"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -21,73 +21,44 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
define(
|
define(
|
||||||
[],
|
['../../browse/src/InspectorRegion'],
|
||||||
function () {
|
function (InspectorRegion) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The InspectorController listens for the selection changes and adds the selection
|
* The InspectorController adds region data for a domain object's type
|
||||||
* object to the scope.
|
* to the scope.
|
||||||
*
|
*
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
function InspectorController($scope, openmct, $document) {
|
function InspectorController($scope, policyService) {
|
||||||
var self = this;
|
var domainObject = $scope.domainObject,
|
||||||
self.$scope = $scope;
|
typeCapability = domainObject.getCapability('type'),
|
||||||
|
statusListener;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback handler for the selection change event.
|
* Filters region parts to only those allowed by region policies
|
||||||
* Adds the selection object to the scope. If the selected item has an inspector view,
|
* @param regions
|
||||||
* it puts the key in the scope. If provider view exists, it shows the view.
|
* @returns {{}}
|
||||||
*/
|
*/
|
||||||
function setSelection(selection) {
|
function filterRegions(inspector) {
|
||||||
if (selection[0]) {
|
//Dupe so we're not modifying the type definition.
|
||||||
var view = openmct.inspectorViews.get(selection);
|
return inspector.regions && inspector.regions.filter(function (region) {
|
||||||
var container = $document[0].querySelectorAll('.inspector-provider-view')[0];
|
return policyService.allow('region', region, domainObject);
|
||||||
container.innerHTML = "";
|
});
|
||||||
|
|
||||||
if (view) {
|
|
||||||
self.providerView = true;
|
|
||||||
view.show(container);
|
|
||||||
} else {
|
|
||||||
self.providerView = false;
|
|
||||||
var selectedItem = selection[0].context.oldItem;
|
|
||||||
|
|
||||||
if (selectedItem) {
|
|
||||||
$scope.inspectorKey = selectedItem.getCapability("type").typeDef.inspector;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.$scope.selection = selection;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
openmct.selection.on("change", setSelection);
|
function setRegions() {
|
||||||
|
$scope.regions = filterRegions(typeCapability.getDefinition().inspector || new InspectorRegion());
|
||||||
setSelection(openmct.selection.get());
|
}
|
||||||
|
|
||||||
|
statusListener = domainObject.getCapability("status").listen(setRegions);
|
||||||
$scope.$on("$destroy", function () {
|
$scope.$on("$destroy", function () {
|
||||||
openmct.selection.off("change", setSelection);
|
statusListener();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
setRegions();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the selected item.
|
|
||||||
*
|
|
||||||
* @returns a domain object
|
|
||||||
*/
|
|
||||||
InspectorController.prototype.selectedItem = function () {
|
|
||||||
return this.$scope.selection[0].context.oldItem;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if a provider view exists.
|
|
||||||
*
|
|
||||||
* @returns 'true' if provider view exists, 'false' otherwise
|
|
||||||
*/
|
|
||||||
InspectorController.prototype.hasProviderView = function () {
|
|
||||||
return this.providerView;
|
|
||||||
};
|
|
||||||
|
|
||||||
return InspectorController;
|
return InspectorController;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -27,93 +27,82 @@ define(
|
|||||||
describe("The inspector controller ", function () {
|
describe("The inspector controller ", function () {
|
||||||
var mockScope,
|
var mockScope,
|
||||||
mockDomainObject,
|
mockDomainObject,
|
||||||
mockOpenMCT,
|
mockTypeCapability,
|
||||||
mockSelection,
|
mockTypeDefinition,
|
||||||
mockInspectorViews,
|
mockPolicyService,
|
||||||
mockTypeDef,
|
mockStatusCapability,
|
||||||
controller,
|
capabilities = {},
|
||||||
container,
|
controller;
|
||||||
$document = [],
|
|
||||||
selectable = [];
|
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
mockTypeDef = {
|
mockTypeDefinition = {
|
||||||
typeDef: {
|
inspector:
|
||||||
inspector: "some-key"
|
{
|
||||||
}
|
'regions': [
|
||||||
|
{'name': 'Part One'},
|
||||||
|
{'name': 'Part Two'}
|
||||||
|
]
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mockTypeCapability = jasmine.createSpyObj('typeCapability', [
|
||||||
|
'getDefinition'
|
||||||
|
]);
|
||||||
|
mockTypeCapability.getDefinition.andReturn(mockTypeDefinition);
|
||||||
|
capabilities.type = mockTypeCapability;
|
||||||
|
|
||||||
|
mockStatusCapability = jasmine.createSpyObj('statusCapability', [
|
||||||
|
'listen'
|
||||||
|
]);
|
||||||
|
capabilities.status = mockStatusCapability;
|
||||||
|
|
||||||
mockDomainObject = jasmine.createSpyObj('domainObject', [
|
mockDomainObject = jasmine.createSpyObj('domainObject', [
|
||||||
'getCapability'
|
'getCapability'
|
||||||
]);
|
]);
|
||||||
mockDomainObject.getCapability.andReturn(mockTypeDef);
|
mockDomainObject.getCapability.andCallFake(function (name) {
|
||||||
|
return capabilities[name];
|
||||||
|
});
|
||||||
|
|
||||||
|
mockPolicyService = jasmine.createSpyObj('policyService', [
|
||||||
|
'allow'
|
||||||
|
]);
|
||||||
|
|
||||||
mockScope = jasmine.createSpyObj('$scope',
|
mockScope = jasmine.createSpyObj('$scope',
|
||||||
['$on', 'selection']
|
['$on']
|
||||||
);
|
);
|
||||||
|
|
||||||
selectable[0] = {
|
mockScope.domainObject = mockDomainObject;
|
||||||
context: {
|
|
||||||
oldItem: mockDomainObject
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
mockSelection = jasmine.createSpyObj("selection", [
|
|
||||||
'on',
|
|
||||||
'off',
|
|
||||||
'get'
|
|
||||||
]);
|
|
||||||
mockSelection.get.andReturn(selectable);
|
|
||||||
|
|
||||||
mockInspectorViews = jasmine.createSpyObj('inspectorViews', ['get']);
|
|
||||||
mockOpenMCT = {
|
|
||||||
selection: mockSelection,
|
|
||||||
inspectorViews: mockInspectorViews
|
|
||||||
};
|
|
||||||
|
|
||||||
container = jasmine.createSpy('container', ['innerHTML']);
|
|
||||||
$document[0] = jasmine.createSpyObj("$document", ['querySelectorAll']);
|
|
||||||
$document[0].querySelectorAll.andReturn([container]);
|
|
||||||
|
|
||||||
controller = new InspectorController(mockScope, mockOpenMCT, $document);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("listens for selection change event", function () {
|
it("filters out regions disallowed by region policy", function () {
|
||||||
expect(mockOpenMCT.selection.on).toHaveBeenCalledWith(
|
mockPolicyService.allow.andReturn(false);
|
||||||
'change',
|
controller = new InspectorController(mockScope, mockPolicyService);
|
||||||
jasmine.any(Function)
|
expect(mockScope.regions.length).toBe(0);
|
||||||
);
|
|
||||||
|
|
||||||
expect(controller.selectedItem()).toEqual(mockDomainObject);
|
|
||||||
|
|
||||||
var mockItem = jasmine.createSpyObj('domainObject', [
|
|
||||||
'getCapability'
|
|
||||||
]);
|
|
||||||
mockItem.getCapability.andReturn(mockTypeDef);
|
|
||||||
selectable[0].context.oldItem = mockItem;
|
|
||||||
|
|
||||||
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
|
||||||
|
|
||||||
expect(controller.selectedItem()).toEqual(mockItem);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("cleans up on scope destroy", function () {
|
it("does not filter out regions allowed by region policy", function () {
|
||||||
expect(mockScope.$on).toHaveBeenCalledWith(
|
mockPolicyService.allow.andReturn(true);
|
||||||
'$destroy',
|
controller = new InspectorController(mockScope, mockPolicyService);
|
||||||
jasmine.any(Function)
|
expect(mockScope.regions.length).toBe(2);
|
||||||
);
|
|
||||||
|
|
||||||
mockScope.$on.calls[0].args[1]();
|
|
||||||
|
|
||||||
expect(mockOpenMCT.selection.off).toHaveBeenCalledWith(
|
|
||||||
'change',
|
|
||||||
jasmine.any(Function)
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("adds selection object to scope", function () {
|
it("Responds to status changes", function () {
|
||||||
expect(mockScope.selection).toEqual(selectable);
|
mockPolicyService.allow.andReturn(true);
|
||||||
expect(controller.selectedItem()).toEqual(mockDomainObject);
|
controller = new InspectorController(mockScope, mockPolicyService);
|
||||||
|
expect(mockScope.regions.length).toBe(2);
|
||||||
|
expect(mockStatusCapability.listen).toHaveBeenCalled();
|
||||||
|
mockPolicyService.allow.andReturn(false);
|
||||||
|
mockStatusCapability.listen.mostRecentCall.args[0]();
|
||||||
|
expect(mockScope.regions.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Unregisters status listener", function () {
|
||||||
|
var mockListener = jasmine.createSpy('listener');
|
||||||
|
mockStatusCapability.listen.andReturn(mockListener);
|
||||||
|
controller = new InspectorController(mockScope, mockPolicyService);
|
||||||
|
expect(mockScope.$on).toHaveBeenCalledWith("$destroy", jasmine.any(Function));
|
||||||
|
mockScope.$on.mostRecentCall.args[1]();
|
||||||
|
expect(mockListener).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -181,8 +181,6 @@ $colorPlotHash: $colorTick;
|
|||||||
$stylePlotHash: dashed;
|
$stylePlotHash: dashed;
|
||||||
$colorPlotAreaBorder: $colorInteriorBorder;
|
$colorPlotAreaBorder: $colorInteriorBorder;
|
||||||
$colorPlotLabelFg: pushBack($colorPlotFg, 20%);
|
$colorPlotLabelFg: pushBack($colorPlotFg, 20%);
|
||||||
$legendCollapsedNameMaxW: 50%;
|
|
||||||
$legendHoverValueBg: rgba($colorBodyFg, 0.1);
|
|
||||||
|
|
||||||
// Tree
|
// Tree
|
||||||
$colorItemTreeHoverBg: pullForward($colorBodyBg, $hoverRatioPercent);
|
$colorItemTreeHoverBg: pullForward($colorBodyBg, $hoverRatioPercent);
|
||||||
@ -245,12 +243,6 @@ $colorCalCellSelectedBg: $colorItemTreeSelectedBg;
|
|||||||
$colorCalCellSelectedFg: $colorItemTreeSelectedFg;
|
$colorCalCellSelectedFg: $colorItemTreeSelectedFg;
|
||||||
$colorCalCellInMonthBg: pushBack($colorMenuBg, 5%);
|
$colorCalCellInMonthBg: pushBack($colorMenuBg, 5%);
|
||||||
|
|
||||||
// Palettes
|
|
||||||
$colorPaletteFg: pullForward($colorMenuBg, 30%);
|
|
||||||
$colorPaletteSelected: #fff;
|
|
||||||
$shdwPaletteFg: black 0 0 2px;
|
|
||||||
$shdwPaletteSelected: inset 0 0 0 1px #000;
|
|
||||||
|
|
||||||
// About Screen
|
// About Screen
|
||||||
$colorAboutLink: #84b3ff;
|
$colorAboutLink: #84b3ff;
|
||||||
|
|
||||||
|
@ -181,8 +181,6 @@ $colorPlotHash: $colorTick;
|
|||||||
$stylePlotHash: dashed;
|
$stylePlotHash: dashed;
|
||||||
$colorPlotAreaBorder: $colorInteriorBorder;
|
$colorPlotAreaBorder: $colorInteriorBorder;
|
||||||
$colorPlotLabelFg: pushBack($colorPlotFg, 20%);
|
$colorPlotLabelFg: pushBack($colorPlotFg, 20%);
|
||||||
$legendCollapsedNameMaxW: 50%;
|
|
||||||
$legendHoverValueBg: rgba($colorBodyFg, 0.2);
|
|
||||||
|
|
||||||
// Tree
|
// Tree
|
||||||
$colorItemTreeHoverBg: pullForward($colorBodyBg, $hoverRatioPercent);
|
$colorItemTreeHoverBg: pullForward($colorBodyBg, $hoverRatioPercent);
|
||||||
@ -245,12 +243,6 @@ $colorCalCellSelectedBg: $colorItemTreeSelectedBg;
|
|||||||
$colorCalCellSelectedFg: $colorItemTreeSelectedFg;
|
$colorCalCellSelectedFg: $colorItemTreeSelectedFg;
|
||||||
$colorCalCellInMonthBg: pullForward($colorMenuBg, 5%);
|
$colorCalCellInMonthBg: pullForward($colorMenuBg, 5%);
|
||||||
|
|
||||||
// Palettes
|
|
||||||
$colorPaletteFg: pullForward($colorMenuBg, 30%);
|
|
||||||
$colorPaletteSelected: #333;
|
|
||||||
$shdwPaletteFg: none;
|
|
||||||
$shdwPaletteSelected: inset 0 0 0 1px #fff;
|
|
||||||
|
|
||||||
// About Screen
|
// About Screen
|
||||||
$colorAboutLink: #84b3ff;
|
$colorAboutLink: #84b3ff;
|
||||||
|
|
||||||
|
54
platform/features/autoflow/plugin.js
Executable file
54
platform/features/autoflow/plugin.js
Executable file
@ -0,0 +1,54 @@
|
|||||||
|
define([
|
||||||
|
'text!./res/templates/autoflow-tabular.html',
|
||||||
|
'./src/AutoflowTabularController',
|
||||||
|
'./src/MCTAutoflowTable'
|
||||||
|
], function (
|
||||||
|
autoflowTabularTemplate,
|
||||||
|
AutoflowTabularController,
|
||||||
|
MCTAutoflowTable
|
||||||
|
) {
|
||||||
|
return function (options) {
|
||||||
|
return function (openmct) {
|
||||||
|
openmct.legacyRegistry.register("platform/features/autoflow", {
|
||||||
|
"name": "WARP Telemetry Adapter",
|
||||||
|
"description": "Retrieves telemetry from the WARP Server and provides related types and views.",
|
||||||
|
"resources": "res",
|
||||||
|
"extensions": {
|
||||||
|
"views": [
|
||||||
|
{
|
||||||
|
"key": "autoflow",
|
||||||
|
"name": "Autoflow Tabular",
|
||||||
|
"cssClass": "icon-packet",
|
||||||
|
"description": "A tabular view of packet contents.",
|
||||||
|
"template": autoflowTabularTemplate,
|
||||||
|
"type": options && options.type,
|
||||||
|
"needs": [
|
||||||
|
"telemetry"
|
||||||
|
],
|
||||||
|
"delegation": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"controllers": [
|
||||||
|
{
|
||||||
|
"key": "AutoflowTabularController",
|
||||||
|
"implementation": AutoflowTabularController,
|
||||||
|
"depends": [
|
||||||
|
"$scope",
|
||||||
|
"$timeout",
|
||||||
|
"telemetrySubscriber"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"directives": [
|
||||||
|
{
|
||||||
|
"key": "mctAutoflowTable",
|
||||||
|
"implementation": MCTAutoflowTable
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
openmct.legacyRegistry.enable("platform/features/autoflow");
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
26
platform/features/autoflow/res/templates/autoflow-tabular.html
Executable file
26
platform/features/autoflow/res/templates/autoflow-tabular.html
Executable file
@ -0,0 +1,26 @@
|
|||||||
|
<div class="items-holder abs contents autoflow obj-value-format"
|
||||||
|
ng-controller="AutoflowTabularController as autoflow">
|
||||||
|
<div class="abs l-flex-row holder t-autoflow-header l-autoflow-header">
|
||||||
|
<mct-include key="'input-filter'"
|
||||||
|
ng-model="autoflow.filter"
|
||||||
|
class="flex-elem">
|
||||||
|
</mct-include>
|
||||||
|
<div class="flex-elem grows t-last-update" title="Last Update">{{autoflow.updated()}}</div>
|
||||||
|
<a title="Change column width"
|
||||||
|
class="s-button flex-elem icon-arrows-right-left change-column-width"
|
||||||
|
ng-click="autoflow.increaseColumnWidth()"></a>
|
||||||
|
</div>
|
||||||
|
<div class="abs t-autoflow-items l-autoflow-items"
|
||||||
|
mct-resize="autoflow.setBounds(bounds)"
|
||||||
|
mct-resize-interval="50">
|
||||||
|
<mct-autoflow-table values="autoflow.rangeValues()"
|
||||||
|
objects="autoflow.getTelemetryObjects()"
|
||||||
|
rows="autoflow.getRows()"
|
||||||
|
classes="autoflow.classes()"
|
||||||
|
updated="autoflow.updated()"
|
||||||
|
column-width="autoflow.columnWidth()"
|
||||||
|
counter="autoflow.counter()"
|
||||||
|
>
|
||||||
|
</mct-autoflow-table>
|
||||||
|
</div>
|
||||||
|
</div>
|
169
platform/features/autoflow/src/AutoflowTableLinker.js
Executable file
169
platform/features/autoflow/src/AutoflowTableLinker.js
Executable file
@ -0,0 +1,169 @@
|
|||||||
|
/*global angular*/
|
||||||
|
define(
|
||||||
|
[],
|
||||||
|
function () {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The link step for the `mct-autoflow-table` directive;
|
||||||
|
* watches scope and updates the DOM appropriately.
|
||||||
|
* See documentation in `MCTAutoflowTable.js` for the rationale
|
||||||
|
* for including this directive, as well as for an explanation
|
||||||
|
* of which values are placed in scope.
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
* @param {Scope} scope the scope for this usage of the directive
|
||||||
|
* @param element the jqLite-wrapped element which used this directive
|
||||||
|
*/
|
||||||
|
function AutoflowTableLinker(scope, element) {
|
||||||
|
var objects, // Domain objects at last structure refresh
|
||||||
|
rows, // Number of rows from last structure refresh
|
||||||
|
priorClasses = {},
|
||||||
|
valueSpans = {}; // Span elements to put data values in
|
||||||
|
|
||||||
|
// Create a new name-value pair in the specified column
|
||||||
|
function createListItem(domainObject, ul) {
|
||||||
|
// Create a new li, and spans to go in it.
|
||||||
|
var li = angular.element('<li>'),
|
||||||
|
titleSpan = angular.element('<span>'),
|
||||||
|
valueSpan = angular.element('<span>');
|
||||||
|
|
||||||
|
// Place spans in the li, and li into the column.
|
||||||
|
// valueSpan must precede titleSpan in the DOM due to new CSS float approach
|
||||||
|
li.append(valueSpan).append(titleSpan);
|
||||||
|
ul.append(li);
|
||||||
|
|
||||||
|
// Style appropriately
|
||||||
|
li.addClass('l-autoflow-row');
|
||||||
|
titleSpan.addClass('l-autoflow-item l');
|
||||||
|
valueSpan.addClass('l-autoflow-item r l-obj-val-format');
|
||||||
|
|
||||||
|
// Set text/tooltip for the name-value row
|
||||||
|
titleSpan.text(domainObject.getModel().name);
|
||||||
|
titleSpan.attr("title", domainObject.getModel().name);
|
||||||
|
|
||||||
|
// Keep a reference to the span which will hold the
|
||||||
|
// data value, to populate in the next refreshValues call
|
||||||
|
valueSpans[domainObject.getId()] = valueSpan;
|
||||||
|
|
||||||
|
return li;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new column of name-value pairs in this table.
|
||||||
|
function createColumn(el) {
|
||||||
|
// Create a ul
|
||||||
|
var ul = angular.element('<ul>');
|
||||||
|
|
||||||
|
// Add it into the mct-autoflow-table
|
||||||
|
el.append(ul);
|
||||||
|
|
||||||
|
// Style appropriately
|
||||||
|
ul.addClass('l-autoflow-col');
|
||||||
|
|
||||||
|
// Get the current col width and apply at time of column creation
|
||||||
|
// Important to do this here, as new columns could be created after
|
||||||
|
// the user has changed the width.
|
||||||
|
ul.css('width', scope.columnWidth + 'px');
|
||||||
|
|
||||||
|
// Return it, so some li elements can be added
|
||||||
|
return ul;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change the width of the columns when user clicks the resize button.
|
||||||
|
function resizeColumn() {
|
||||||
|
element.find('ul').css('width', scope.columnWidth + 'px');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rebuild the DOM associated with this table.
|
||||||
|
function rebuild(domainObjects, rowCount) {
|
||||||
|
var activeColumn;
|
||||||
|
|
||||||
|
// Empty out our cached span elements
|
||||||
|
valueSpans = {};
|
||||||
|
|
||||||
|
// Start with an empty DOM beneath this directive
|
||||||
|
element.html("");
|
||||||
|
|
||||||
|
// Add DOM elements for each domain object being displayed
|
||||||
|
// in this table.
|
||||||
|
domainObjects.forEach(function (object, index) {
|
||||||
|
// Start a new column if we'd run out of room
|
||||||
|
if (index % rowCount === 0) {
|
||||||
|
activeColumn = createColumn(element);
|
||||||
|
}
|
||||||
|
// Add the DOM elements for that object to whichever
|
||||||
|
// column (a `ul` element) is current.
|
||||||
|
createListItem(object, activeColumn);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update spans with values, as made available via the
|
||||||
|
// `values` attribute of this directive.
|
||||||
|
function refreshValues() {
|
||||||
|
// Get the available values
|
||||||
|
var values = scope.values || {},
|
||||||
|
classes = scope.classes || {};
|
||||||
|
|
||||||
|
// Populate all spans with those values (or clear
|
||||||
|
// those spans if no value is available)
|
||||||
|
(objects || []).forEach(function (object) {
|
||||||
|
var id = object.getId(),
|
||||||
|
span = valueSpans[id],
|
||||||
|
value;
|
||||||
|
|
||||||
|
if (span) {
|
||||||
|
// Look up the value...
|
||||||
|
value = values[id];
|
||||||
|
// ...and convert to empty string if it's undefined
|
||||||
|
value = value === undefined ? "" : value;
|
||||||
|
span.attr("data-value", value);
|
||||||
|
|
||||||
|
// Update the span
|
||||||
|
span.text(value);
|
||||||
|
span.attr("title", value);
|
||||||
|
span.removeClass(priorClasses[id]);
|
||||||
|
span.addClass(classes[id]);
|
||||||
|
priorClasses[id] = classes[id];
|
||||||
|
}
|
||||||
|
// Also need stale/alert/ok class
|
||||||
|
// on span
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh the DOM for this table, if necessary
|
||||||
|
function refreshStructure() {
|
||||||
|
// Only rebuild if number of rows or set of objects
|
||||||
|
// has changed; otherwise, our structure is still valid.
|
||||||
|
if (scope.objects !== objects ||
|
||||||
|
scope.rows !== rows) {
|
||||||
|
|
||||||
|
// Track those values to support future refresh checks
|
||||||
|
objects = scope.objects;
|
||||||
|
rows = scope.rows;
|
||||||
|
|
||||||
|
// Rebuild the DOM
|
||||||
|
rebuild(objects || [], rows || 1);
|
||||||
|
|
||||||
|
// Refresh all data values shown
|
||||||
|
refreshValues();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Changing the domain objects in use or the number
|
||||||
|
// of rows should trigger a structure change (DOM rebuild)
|
||||||
|
scope.$watch("objects", refreshStructure);
|
||||||
|
scope.$watch("rows", refreshStructure);
|
||||||
|
|
||||||
|
// When the current column width has been changed, resize the column
|
||||||
|
scope.$watch('columnWidth', resizeColumn);
|
||||||
|
|
||||||
|
// When the last-updated time ticks,
|
||||||
|
scope.$watch("updated", refreshValues);
|
||||||
|
|
||||||
|
// Update displayed values when the counter changes.
|
||||||
|
scope.$watch("counter", refreshValues);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return AutoflowTableLinker;
|
||||||
|
}
|
||||||
|
);
|
324
platform/features/autoflow/src/AutoflowTabularController.js
Executable file
324
platform/features/autoflow/src/AutoflowTabularController.js
Executable file
@ -0,0 +1,324 @@
|
|||||||
|
|
||||||
|
define(
|
||||||
|
['moment'],
|
||||||
|
function (moment) {
|
||||||
|
|
||||||
|
var ROW_HEIGHT = 16,
|
||||||
|
SLIDER_HEIGHT = 10,
|
||||||
|
INITIAL_COLUMN_WIDTH = 225,
|
||||||
|
MAX_COLUMN_WIDTH = 525,
|
||||||
|
COLUMN_WIDTH_STEP = 25,
|
||||||
|
DEBOUNCE_INTERVAL = 100,
|
||||||
|
DATE_FORMAT = "YYYY-DDD HH:mm:ss.SSS\\Z",
|
||||||
|
NOT_UPDATED = "No updates",
|
||||||
|
EMPTY_ARRAY = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Responsible for supporting the autoflow tabular view.
|
||||||
|
* Implements the all-over logic which drives that view,
|
||||||
|
* mediating between template-provided areas, the included
|
||||||
|
* `mct-autoflow-table` directive, and the underlying
|
||||||
|
* domain object model.
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function AutflowTabularController(
|
||||||
|
$scope,
|
||||||
|
$timeout,
|
||||||
|
telemetrySubscriber
|
||||||
|
) {
|
||||||
|
var filterValue = "",
|
||||||
|
filterValueLowercase = "",
|
||||||
|
subscription,
|
||||||
|
filteredObjects = [],
|
||||||
|
lastUpdated = {},
|
||||||
|
updateText = NOT_UPDATED,
|
||||||
|
rangeValues = {},
|
||||||
|
classes = {},
|
||||||
|
limits = {},
|
||||||
|
updatePending = false,
|
||||||
|
lastBounce = Number.NEGATIVE_INFINITY,
|
||||||
|
columnWidth = INITIAL_COLUMN_WIDTH,
|
||||||
|
rows = 1,
|
||||||
|
counter = 0;
|
||||||
|
|
||||||
|
// Trigger an update of the displayed table by incrementing
|
||||||
|
// the counter that it watches.
|
||||||
|
function triggerDisplayUpdate() {
|
||||||
|
counter += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether or not an object's name matches the
|
||||||
|
// user-entered filter value.
|
||||||
|
function filterObject(domainObject) {
|
||||||
|
return (domainObject.getModel().name || "")
|
||||||
|
.toLowerCase()
|
||||||
|
.indexOf(filterValueLowercase) !== -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Comparator for sorting points back into packet order
|
||||||
|
function compareObject(objectA, objectB) {
|
||||||
|
var indexA = objectA.getModel().index || 0,
|
||||||
|
indexB = objectB.getModel().index || 0;
|
||||||
|
return indexA - indexB;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the list of currently-displayed objects; these
|
||||||
|
// will be the subset of currently subscribed-to objects
|
||||||
|
// which match a user-entered filter.
|
||||||
|
function doUpdateFilteredObjects() {
|
||||||
|
// Generate the list
|
||||||
|
filteredObjects = (
|
||||||
|
subscription ?
|
||||||
|
subscription.getTelemetryObjects() :
|
||||||
|
[]
|
||||||
|
).filter(filterObject).sort(compareObject);
|
||||||
|
|
||||||
|
// Clear the pending flag
|
||||||
|
updatePending = false;
|
||||||
|
|
||||||
|
// Track when this occurred, so that we can wait
|
||||||
|
// a whole before updating again.
|
||||||
|
lastBounce = Date.now();
|
||||||
|
|
||||||
|
triggerDisplayUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request an update to the list of current objects; this may
|
||||||
|
// run on a timeout to avoid excessive calls, e.g. while the user
|
||||||
|
// is typing a filter.
|
||||||
|
function updateFilteredObjects() {
|
||||||
|
// Don't do anything if an update is already scheduled
|
||||||
|
if (!updatePending) {
|
||||||
|
if (Date.now() > lastBounce + DEBOUNCE_INTERVAL) {
|
||||||
|
// Update immediately if it's been long enough
|
||||||
|
doUpdateFilteredObjects();
|
||||||
|
} else {
|
||||||
|
// Otherwise, update later, and track that we have
|
||||||
|
// an update pending so that subsequent calls can
|
||||||
|
// be ignored.
|
||||||
|
updatePending = true;
|
||||||
|
$timeout(doUpdateFilteredObjects, DEBOUNCE_INTERVAL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track the latest data values for this domain object
|
||||||
|
function recordData(telemetryObject) {
|
||||||
|
// Get latest domain/range values for this object.
|
||||||
|
var id = telemetryObject.getId(),
|
||||||
|
domainValue = subscription.getDomainValue(telemetryObject),
|
||||||
|
rangeValue = subscription.getRangeValue(telemetryObject);
|
||||||
|
|
||||||
|
// Track the most recent timestamp change observed...
|
||||||
|
if (domainValue !== undefined && domainValue !== lastUpdated[id]) {
|
||||||
|
lastUpdated[id] = domainValue;
|
||||||
|
// ... and update the displayable text for that timestamp
|
||||||
|
updateText = isNaN(domainValue) ? "" :
|
||||||
|
moment.utc(domainValue).format(DATE_FORMAT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store data values into the rangeValues structure, which
|
||||||
|
// will be used to populate the table itself.
|
||||||
|
// Note that we want full precision here.
|
||||||
|
rangeValues[id] = rangeValue;
|
||||||
|
|
||||||
|
// Update limit states as well
|
||||||
|
classes[id] = limits[id] && (limits[id].evaluate({
|
||||||
|
// This relies on external knowledge that the
|
||||||
|
// range value of a telemetry point is encoded
|
||||||
|
// in its datum as "value."
|
||||||
|
value: rangeValue
|
||||||
|
}) || {}).cssClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Look at telemetry objects from the subscription; this is watched
|
||||||
|
// to detect changes from the subscription.
|
||||||
|
function subscribedTelemetry() {
|
||||||
|
return subscription ?
|
||||||
|
subscription.getTelemetryObjects() : EMPTY_ARRAY;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the data values which will be used to populate the table
|
||||||
|
function updateValues() {
|
||||||
|
subscribedTelemetry().forEach(recordData);
|
||||||
|
triggerDisplayUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getter-setter function for user-entered filter text.
|
||||||
|
function filter(value) {
|
||||||
|
// If value was specified, we're a setter
|
||||||
|
if (value !== undefined) {
|
||||||
|
// Store the new value
|
||||||
|
filterValue = value;
|
||||||
|
filterValueLowercase = value.toLowerCase();
|
||||||
|
// Change which objects appear in the table
|
||||||
|
updateFilteredObjects();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always act as a getter
|
||||||
|
return filterValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the bounds (width and height) of this view;
|
||||||
|
// called from the mct-resize directive. Recalculates how
|
||||||
|
// many rows should appear in the contained table.
|
||||||
|
function setBounds(bounds) {
|
||||||
|
var availableSpace = bounds.height - SLIDER_HEIGHT;
|
||||||
|
rows = Math.max(1, Math.floor(availableSpace / ROW_HEIGHT));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increment the current column width, up to the defined maximum.
|
||||||
|
// When the max is hit, roll back to the default.
|
||||||
|
function increaseColumnWidth() {
|
||||||
|
columnWidth += COLUMN_WIDTH_STEP;
|
||||||
|
// Cycle down to the initial width instead of exceeding max
|
||||||
|
columnWidth = columnWidth > MAX_COLUMN_WIDTH ?
|
||||||
|
INITIAL_COLUMN_WIDTH : columnWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get displayable text for last-updated value
|
||||||
|
function updated() {
|
||||||
|
return updateText;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unsubscribe, if a subscription is active.
|
||||||
|
function releaseSubscription() {
|
||||||
|
if (subscription) {
|
||||||
|
subscription.unsubscribe();
|
||||||
|
subscription = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update set of telemetry objects managed by this view
|
||||||
|
function updateTelemetryObjects(telemetryObjects) {
|
||||||
|
updateFilteredObjects();
|
||||||
|
limits = {};
|
||||||
|
telemetryObjects.forEach(function (telemetryObject) {
|
||||||
|
var id = telemetryObject.getId();
|
||||||
|
limits[id] = telemetryObject.getCapability('limit');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a subscription for the represented domain object.
|
||||||
|
// This will resolve capability delegation as necessary.
|
||||||
|
function makeSubscription(domainObject) {
|
||||||
|
// Unsubscribe, if there is an existing subscription
|
||||||
|
releaseSubscription();
|
||||||
|
|
||||||
|
// Clear updated timestamp
|
||||||
|
lastUpdated = {};
|
||||||
|
updateText = NOT_UPDATED;
|
||||||
|
|
||||||
|
// Create a new subscription; telemetrySubscriber gets
|
||||||
|
// to do the meaningful work here.
|
||||||
|
subscription = domainObject && telemetrySubscriber.subscribe(
|
||||||
|
domainObject,
|
||||||
|
updateValues
|
||||||
|
);
|
||||||
|
|
||||||
|
// Our set of in-view telemetry objects may have changed,
|
||||||
|
// so update the set that is being passed down to the table.
|
||||||
|
updateFilteredObjects();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch for changes to the set of objects which have telemetry
|
||||||
|
$scope.$watch(subscribedTelemetry, updateTelemetryObjects);
|
||||||
|
|
||||||
|
// Watch for the represented domainObject (this field will
|
||||||
|
// be populated by mct-representation)
|
||||||
|
$scope.$watch("domainObject", makeSubscription);
|
||||||
|
|
||||||
|
// Make sure we unsubscribe when this view is destroyed.
|
||||||
|
$scope.$on("$destroy", releaseSubscription);
|
||||||
|
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
* Get the number of rows which should be shown in this table.
|
||||||
|
* @return {number} the number of rows to show
|
||||||
|
*/
|
||||||
|
getRows: function () {
|
||||||
|
return rows;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Get the objects which should currently be displayed in
|
||||||
|
* this table. This will be watched, so the return value
|
||||||
|
* should be stable when this list is unchanging. Only
|
||||||
|
* objects which match the user-entered filter value should
|
||||||
|
* be returned here.
|
||||||
|
* @return {DomainObject[]} the domain objects to include in
|
||||||
|
* this table.
|
||||||
|
*/
|
||||||
|
getTelemetryObjects: function () {
|
||||||
|
return filteredObjects;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Set the bounds (width/height) of this autoflow tabular view.
|
||||||
|
* The template must ensure that these bounds are tracked on
|
||||||
|
* the table area only.
|
||||||
|
* @param bounds the bounds; and object with `width` and
|
||||||
|
* `height` properties, both as numbers, in pixels.
|
||||||
|
*/
|
||||||
|
setBounds: setBounds,
|
||||||
|
/**
|
||||||
|
* Increments the width of the autoflow column.
|
||||||
|
* Setting does not yet persist.
|
||||||
|
*/
|
||||||
|
increaseColumnWidth: increaseColumnWidth,
|
||||||
|
/**
|
||||||
|
* Get-or-set the user-supplied filter value.
|
||||||
|
* @param {string} [value] the new filter value; omit to use
|
||||||
|
* as a getter
|
||||||
|
* @returns {string} the user-supplied filter value
|
||||||
|
*/
|
||||||
|
filter: filter,
|
||||||
|
/**
|
||||||
|
* Get all range values for use in this table. These will be
|
||||||
|
* returned as an object of key-value pairs, where keys are
|
||||||
|
* domain object IDs, and values are the most recently observed
|
||||||
|
* data values associated with those objects, formatted for
|
||||||
|
* display.
|
||||||
|
* @returns {object.<string,string>} most recent values
|
||||||
|
*/
|
||||||
|
rangeValues: function () {
|
||||||
|
return rangeValues;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Get CSS classes to apply to specific rows, representing limit
|
||||||
|
* states and/or stale states. These are returned as key-value
|
||||||
|
* pairs where keys are domain object IDs, and values are CSS
|
||||||
|
* classes to display for domain objects with those IDs.
|
||||||
|
* @returns {object.<string,string>} CSS classes
|
||||||
|
*/
|
||||||
|
classes: function () {
|
||||||
|
return classes;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Get the "last updated" text for this view; this will be
|
||||||
|
* the most recent timestamp observed for any telemetry-
|
||||||
|
* providing object, formatted for display.
|
||||||
|
* @returns {string} the time of the most recent update
|
||||||
|
*/
|
||||||
|
updated: updated,
|
||||||
|
/**
|
||||||
|
* Get the current column width, in pixels.
|
||||||
|
* @returns {number} column width
|
||||||
|
*/
|
||||||
|
columnWidth: function () {
|
||||||
|
return columnWidth;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Keep a counter and increment this whenever the display
|
||||||
|
* should be updated; this will be watched by the
|
||||||
|
* `mct-autoflow-table`.
|
||||||
|
* @returns {number} a counter value
|
||||||
|
*/
|
||||||
|
counter: function () {
|
||||||
|
return counter;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return AutflowTabularController;
|
||||||
|
}
|
||||||
|
);
|
60
platform/features/autoflow/src/MCTAutoflowTable.js
Executable file
60
platform/features/autoflow/src/MCTAutoflowTable.js
Executable file
@ -0,0 +1,60 @@
|
|||||||
|
|
||||||
|
define(
|
||||||
|
["./AutoflowTableLinker"],
|
||||||
|
function (AutoflowTableLinker) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `mct-autoflow-table` directive specifically supports
|
||||||
|
* autoflow tabular views; it is not intended for use outside
|
||||||
|
* of that view.
|
||||||
|
*
|
||||||
|
* This directive is responsible for creating the structure
|
||||||
|
* of the table in this view, and for updating its values.
|
||||||
|
* While this is achievable using a regular Angular template,
|
||||||
|
* this is undesirable from the perspective of performance
|
||||||
|
* due to the number of watches that can be involved for large
|
||||||
|
* tables. Instead, this directive will maintain a small number
|
||||||
|
* of watches, rebuilding table structure only when necessary,
|
||||||
|
* and updating displayed values in the more common case of
|
||||||
|
* new data arriving.
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function MCTAutoflowTable() {
|
||||||
|
return {
|
||||||
|
// Only applicable at the element level
|
||||||
|
restrict: "E",
|
||||||
|
|
||||||
|
// The link function; handles DOM update/manipulation
|
||||||
|
link: AutoflowTableLinker,
|
||||||
|
|
||||||
|
// Parameters to pass from attributes into scope
|
||||||
|
scope: {
|
||||||
|
// Set of domain objects to show in the table
|
||||||
|
objects: "=",
|
||||||
|
|
||||||
|
// Values for those objects, by ID
|
||||||
|
values: "=",
|
||||||
|
|
||||||
|
// CSS classes to show for objects, by ID
|
||||||
|
classes: "=",
|
||||||
|
|
||||||
|
// Number of rows to show before autoflowing
|
||||||
|
rows: "=",
|
||||||
|
|
||||||
|
// Time of last update; watched to refresh values
|
||||||
|
updated: "=",
|
||||||
|
|
||||||
|
// Current width of the autoflow column
|
||||||
|
columnWidth: "=",
|
||||||
|
|
||||||
|
// A counter used to trigger display updates
|
||||||
|
counter: "="
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return MCTAutoflowTable;
|
||||||
|
|
||||||
|
}
|
||||||
|
);
|
178
platform/features/autoflow/test/AutoflowTableLinkerSpec.js
Executable file
178
platform/features/autoflow/test/AutoflowTableLinkerSpec.js
Executable file
@ -0,0 +1,178 @@
|
|||||||
|
|
||||||
|
define(
|
||||||
|
["../src/AutoflowTableLinker"],
|
||||||
|
function (AutoflowTableLinker) {
|
||||||
|
|
||||||
|
describe("The mct-autoflow-table linker", function () {
|
||||||
|
var cachedAngular,
|
||||||
|
mockAngular,
|
||||||
|
mockScope,
|
||||||
|
mockElement,
|
||||||
|
mockElements,
|
||||||
|
linker;
|
||||||
|
|
||||||
|
// Utility function to generate more mock elements
|
||||||
|
function createMockElement(html) {
|
||||||
|
var mockEl = jasmine.createSpyObj(
|
||||||
|
"element-" + html,
|
||||||
|
[
|
||||||
|
"append",
|
||||||
|
"addClass",
|
||||||
|
"removeClass",
|
||||||
|
"text",
|
||||||
|
"attr",
|
||||||
|
"html",
|
||||||
|
"css",
|
||||||
|
"find"
|
||||||
|
]
|
||||||
|
);
|
||||||
|
mockEl.testHtml = html;
|
||||||
|
mockEl.append.andReturn(mockEl);
|
||||||
|
mockElements.push(mockEl);
|
||||||
|
return mockEl;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createMockDomainObject(id) {
|
||||||
|
var mockDomainObject = jasmine.createSpyObj(
|
||||||
|
"domainObject-" + id,
|
||||||
|
["getId", "getModel"]
|
||||||
|
);
|
||||||
|
mockDomainObject.getId.andReturn(id);
|
||||||
|
mockDomainObject.getModel.andReturn({name: id.toUpperCase()});
|
||||||
|
return mockDomainObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fireWatch(watchExpression, value) {
|
||||||
|
mockScope.$watch.calls.forEach(function (call) {
|
||||||
|
if (call.args[0] === watchExpression) {
|
||||||
|
call.args[1](value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// AutoflowTableLinker accesses Angular in the global
|
||||||
|
// scope, since it is not injectable; we simulate that
|
||||||
|
// here by adding/removing it to/from the window object.
|
||||||
|
beforeEach(function () {
|
||||||
|
mockElements = [];
|
||||||
|
|
||||||
|
mockAngular = jasmine.createSpyObj("angular", ["element"]);
|
||||||
|
mockScope = jasmine.createSpyObj("scope", ["$watch"]);
|
||||||
|
mockElement = createMockElement('<div>');
|
||||||
|
|
||||||
|
mockAngular.element.andCallFake(createMockElement);
|
||||||
|
|
||||||
|
if (window.angular !== undefined) {
|
||||||
|
cachedAngular = window.angular;
|
||||||
|
}
|
||||||
|
window.angular = mockAngular;
|
||||||
|
|
||||||
|
linker = new AutoflowTableLinker(mockScope, mockElement);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function () {
|
||||||
|
if (cachedAngular !== undefined) {
|
||||||
|
window.angular = cachedAngular;
|
||||||
|
} else {
|
||||||
|
delete window.angular;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("watches for changes in inputs", function () {
|
||||||
|
expect(mockScope.$watch).toHaveBeenCalledWith(
|
||||||
|
"objects",
|
||||||
|
jasmine.any(Function)
|
||||||
|
);
|
||||||
|
expect(mockScope.$watch).toHaveBeenCalledWith(
|
||||||
|
"rows",
|
||||||
|
jasmine.any(Function)
|
||||||
|
);
|
||||||
|
expect(mockScope.$watch).toHaveBeenCalledWith(
|
||||||
|
"counter",
|
||||||
|
jasmine.any(Function)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("changes structure when domain objects change", function () {
|
||||||
|
// Set up scope
|
||||||
|
mockScope.rows = 4;
|
||||||
|
mockScope.objects = ['a', 'b', 'c', 'd', 'e', 'f']
|
||||||
|
.map(createMockDomainObject);
|
||||||
|
|
||||||
|
// Fire an update to the set of objects
|
||||||
|
fireWatch("objects");
|
||||||
|
|
||||||
|
// Should have rebuilt with two columns of
|
||||||
|
// four and two rows each; first, by clearing...
|
||||||
|
expect(mockElement.html).toHaveBeenCalledWith("");
|
||||||
|
|
||||||
|
// Should have appended two columns...
|
||||||
|
expect(mockElement.append.calls.length).toEqual(2);
|
||||||
|
|
||||||
|
// ...which should have received two and four rows each
|
||||||
|
expect(mockElement.append.calls[0].args[0].append.calls.length)
|
||||||
|
.toEqual(4);
|
||||||
|
expect(mockElement.append.calls[1].args[0].append.calls.length)
|
||||||
|
.toEqual(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updates values", function () {
|
||||||
|
var mockSpans;
|
||||||
|
|
||||||
|
mockScope.objects = ['a', 'b', 'c', 'd', 'e', 'f']
|
||||||
|
.map(createMockDomainObject);
|
||||||
|
mockScope.values = { a: 0 };
|
||||||
|
|
||||||
|
// Fire an update to the set of values
|
||||||
|
fireWatch("objects");
|
||||||
|
fireWatch("updated");
|
||||||
|
|
||||||
|
// Get all created spans
|
||||||
|
mockSpans = mockElements.filter(function (mockElem) {
|
||||||
|
return mockElem.testHtml === '<span>';
|
||||||
|
});
|
||||||
|
|
||||||
|
// First span should be a, should have gotten this value.
|
||||||
|
// This test detects, in particular, WTD-749
|
||||||
|
expect(mockSpans[0].text).toHaveBeenCalledWith('A');
|
||||||
|
expect(mockSpans[1].text).toHaveBeenCalledWith(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("listens for changes in column width", function () {
|
||||||
|
var mockUL = createMockElement("<ul>");
|
||||||
|
mockElement.find.andReturn(mockUL);
|
||||||
|
mockScope.columnWidth = 200;
|
||||||
|
fireWatch("columnWidth", mockScope.columnWidth);
|
||||||
|
expect(mockUL.css).toHaveBeenCalledWith("width", "200px");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updates CSS classes", function () {
|
||||||
|
var mockSpans;
|
||||||
|
|
||||||
|
mockScope.objects = ['a', 'b', 'c', 'd', 'e', 'f']
|
||||||
|
.map(createMockDomainObject);
|
||||||
|
mockScope.values = { a: "a value to find" };
|
||||||
|
mockScope.classes = { a: 'class-a' };
|
||||||
|
|
||||||
|
// Fire an update to the set of values
|
||||||
|
fireWatch("objects");
|
||||||
|
fireWatch("updated");
|
||||||
|
|
||||||
|
// Figure out which span holds the relevant value...
|
||||||
|
mockSpans = mockElements.filter(function (mockElem) {
|
||||||
|
return mockElem.testHtml === '<span>';
|
||||||
|
}).filter(function (mockSpan) {
|
||||||
|
var attrCalls = mockSpan.attr.calls;
|
||||||
|
return attrCalls.some(function (call) {
|
||||||
|
return call.args[0] === 'title' &&
|
||||||
|
call.args[1] === mockScope.values.a;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ...and make sure it also has had its class applied
|
||||||
|
expect(mockSpans[0].addClass)
|
||||||
|
.toHaveBeenCalledWith(mockScope.classes.a);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
341
platform/features/autoflow/test/AutoflowTabularControllerSpec.js
Executable file
341
platform/features/autoflow/test/AutoflowTabularControllerSpec.js
Executable file
@ -0,0 +1,341 @@
|
|||||||
|
|
||||||
|
define(
|
||||||
|
["../src/AutoflowTabularController"],
|
||||||
|
function (AutoflowTabularController) {
|
||||||
|
|
||||||
|
describe("The autoflow tabular controller", function () {
|
||||||
|
var mockScope,
|
||||||
|
mockTimeout,
|
||||||
|
mockSubscriber,
|
||||||
|
mockDomainObject,
|
||||||
|
mockSubscription,
|
||||||
|
controller;
|
||||||
|
|
||||||
|
// Fire watches that are registered as functions.
|
||||||
|
function fireFnWatches() {
|
||||||
|
mockScope.$watch.calls.forEach(function (call) {
|
||||||
|
if (typeof call.args[0] === 'function') {
|
||||||
|
call.args[1](call.args[0]());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
mockScope = jasmine.createSpyObj(
|
||||||
|
"$scope",
|
||||||
|
["$on", "$watch"]
|
||||||
|
);
|
||||||
|
mockTimeout = jasmine.createSpy("$timeout");
|
||||||
|
mockSubscriber = jasmine.createSpyObj(
|
||||||
|
"telemetrySubscriber",
|
||||||
|
["subscribe"]
|
||||||
|
);
|
||||||
|
mockDomainObject = jasmine.createSpyObj(
|
||||||
|
"domainObject",
|
||||||
|
["getId", "getModel", "getCapability"]
|
||||||
|
);
|
||||||
|
mockSubscription = jasmine.createSpyObj(
|
||||||
|
"subscription",
|
||||||
|
[
|
||||||
|
"unsubscribe",
|
||||||
|
"getTelemetryObjects",
|
||||||
|
"getDomainValue",
|
||||||
|
"getRangeValue"
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
mockSubscriber.subscribe.andReturn(mockSubscription);
|
||||||
|
mockDomainObject.getModel.andReturn({name: "something"});
|
||||||
|
|
||||||
|
controller = new AutoflowTabularController(
|
||||||
|
mockScope,
|
||||||
|
mockTimeout,
|
||||||
|
mockSubscriber
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("listens for the represented domain object", function () {
|
||||||
|
expect(mockScope.$watch).toHaveBeenCalledWith(
|
||||||
|
"domainObject",
|
||||||
|
jasmine.any(Function)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("provides a getter-setter function for filtering", function () {
|
||||||
|
expect(controller.filter()).toEqual("");
|
||||||
|
controller.filter("something");
|
||||||
|
expect(controller.filter()).toEqual("something");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("tracks bounds and adjust number of rows accordingly", function () {
|
||||||
|
// Rows are 15px high, and need room for an 10px slider
|
||||||
|
controller.setBounds({ width: 700, height: 120 });
|
||||||
|
expect(controller.getRows()).toEqual(6); // 110 usable height / 16px
|
||||||
|
controller.setBounds({ width: 700, height: 240 });
|
||||||
|
expect(controller.getRows()).toEqual(14); // 230 usable height / 16px
|
||||||
|
});
|
||||||
|
|
||||||
|
it("subscribes to a represented object's telemetry", function () {
|
||||||
|
// Set up subscription, scope
|
||||||
|
mockSubscription.getTelemetryObjects
|
||||||
|
.andReturn([mockDomainObject]);
|
||||||
|
mockScope.domainObject = mockDomainObject;
|
||||||
|
|
||||||
|
// Invoke the watcher with represented domain object
|
||||||
|
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||||
|
|
||||||
|
// Should have subscribed to it
|
||||||
|
expect(mockSubscriber.subscribe).toHaveBeenCalledWith(
|
||||||
|
mockDomainObject,
|
||||||
|
jasmine.any(Function)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Should report objects as reported from subscription
|
||||||
|
expect(controller.getTelemetryObjects())
|
||||||
|
.toEqual([mockDomainObject]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("releases subscriptions on destroy", function () {
|
||||||
|
// Set up subscription...
|
||||||
|
mockSubscription.getTelemetryObjects
|
||||||
|
.andReturn([mockDomainObject]);
|
||||||
|
mockScope.domainObject = mockDomainObject;
|
||||||
|
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||||
|
|
||||||
|
// Verify precondition
|
||||||
|
expect(mockSubscription.unsubscribe).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
// Make sure we're listening for $destroy
|
||||||
|
expect(mockScope.$on).toHaveBeenCalledWith(
|
||||||
|
"$destroy",
|
||||||
|
jasmine.any(Function)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Fire a destroy event
|
||||||
|
mockScope.$on.mostRecentCall.args[1]();
|
||||||
|
|
||||||
|
// Should have unsubscribed
|
||||||
|
expect(mockSubscription.unsubscribe).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("presents latest values and latest update state", function () {
|
||||||
|
// Make sure values are available
|
||||||
|
mockSubscription.getDomainValue.andReturn(402654321123);
|
||||||
|
mockSubscription.getRangeValue.andReturn(789);
|
||||||
|
mockDomainObject.getId.andReturn('testId');
|
||||||
|
|
||||||
|
// Set up subscription...
|
||||||
|
mockSubscription.getTelemetryObjects
|
||||||
|
.andReturn([mockDomainObject]);
|
||||||
|
mockScope.domainObject = mockDomainObject;
|
||||||
|
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||||
|
|
||||||
|
// Fire subscription callback
|
||||||
|
mockSubscriber.subscribe.mostRecentCall.args[1]();
|
||||||
|
|
||||||
|
// ...and exposed the results for template to consume
|
||||||
|
expect(controller.updated()).toEqual("1982-278 08:25:21.123Z");
|
||||||
|
expect(controller.rangeValues().testId).toEqual(789);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sorts domain objects by index", function () {
|
||||||
|
var testIndexes = { a: 2, b: 1, c: 3, d: 0 },
|
||||||
|
mockDomainObjects = Object.keys(testIndexes).sort().map(function (id) {
|
||||||
|
var mockDomainObj = jasmine.createSpyObj(
|
||||||
|
"domainObject",
|
||||||
|
["getId", "getModel"]
|
||||||
|
);
|
||||||
|
|
||||||
|
mockDomainObj.getId.andReturn(id);
|
||||||
|
mockDomainObj.getModel.andReturn({ index: testIndexes[id] });
|
||||||
|
|
||||||
|
return mockDomainObj;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Expose those domain objects...
|
||||||
|
mockSubscription.getTelemetryObjects.andReturn(mockDomainObjects);
|
||||||
|
mockScope.domainObject = mockDomainObject;
|
||||||
|
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||||
|
|
||||||
|
// Fire subscription callback
|
||||||
|
mockSubscriber.subscribe.mostRecentCall.args[1]();
|
||||||
|
|
||||||
|
// Controller should expose same objects, but sorted by index from model
|
||||||
|
expect(controller.getTelemetryObjects()).toEqual([
|
||||||
|
mockDomainObjects[3], // d, index=0
|
||||||
|
mockDomainObjects[1], // b, index=1
|
||||||
|
mockDomainObjects[0], // a, index=2
|
||||||
|
mockDomainObjects[2] // c, index=3
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses a timeout to throttle update", function () {
|
||||||
|
// Set up subscription...
|
||||||
|
mockSubscription.getTelemetryObjects
|
||||||
|
.andReturn([mockDomainObject]);
|
||||||
|
mockScope.domainObject = mockDomainObject;
|
||||||
|
|
||||||
|
// Set the object in view; should not need a timeout
|
||||||
|
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||||
|
expect(mockTimeout.calls.length).toEqual(0);
|
||||||
|
|
||||||
|
// Next call should schedule an update on a timeout
|
||||||
|
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||||
|
expect(mockTimeout.calls.length).toEqual(1);
|
||||||
|
|
||||||
|
// ...but this last one should not, since existing
|
||||||
|
// timeout will cover it
|
||||||
|
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||||
|
expect(mockTimeout.calls.length).toEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows changing column width", function () {
|
||||||
|
var initialWidth = controller.columnWidth();
|
||||||
|
controller.increaseColumnWidth();
|
||||||
|
expect(controller.columnWidth()).toBeGreaterThan(initialWidth);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("filter", function () {
|
||||||
|
var doFilter,
|
||||||
|
filteredObjects,
|
||||||
|
filteredObjectNames;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
var telemetryObjects,
|
||||||
|
updateFilteredObjects;
|
||||||
|
|
||||||
|
telemetryObjects = [
|
||||||
|
'DEF123',
|
||||||
|
'abc789',
|
||||||
|
'456abc',
|
||||||
|
'4ab3cdef',
|
||||||
|
'hjs[12].*(){}^\\'
|
||||||
|
].map(function (objectName, index) {
|
||||||
|
var mockTelemetryObject = jasmine.createSpyObj(
|
||||||
|
objectName,
|
||||||
|
["getId", "getModel"]
|
||||||
|
);
|
||||||
|
|
||||||
|
mockTelemetryObject.getId.andReturn(objectName);
|
||||||
|
mockTelemetryObject.getModel.andReturn({
|
||||||
|
name: objectName,
|
||||||
|
index: index
|
||||||
|
});
|
||||||
|
|
||||||
|
return mockTelemetryObject;
|
||||||
|
});
|
||||||
|
|
||||||
|
mockSubscription
|
||||||
|
.getTelemetryObjects
|
||||||
|
.andReturn(telemetryObjects);
|
||||||
|
|
||||||
|
// Trigger domainObject change to create subscription.
|
||||||
|
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||||
|
|
||||||
|
updateFilteredObjects = function () {
|
||||||
|
filteredObjects = controller.getTelemetryObjects();
|
||||||
|
filteredObjectNames = filteredObjects.map(function (o) {
|
||||||
|
return o.getModel().name;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
doFilter = function (term) {
|
||||||
|
controller.filter(term);
|
||||||
|
// Filter is debounced so we have to force it to occur.
|
||||||
|
mockTimeout.mostRecentCall.args[0]();
|
||||||
|
updateFilteredObjects();
|
||||||
|
};
|
||||||
|
|
||||||
|
updateFilteredObjects();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("initially shows all objects", function () {
|
||||||
|
expect(filteredObjectNames).toEqual([
|
||||||
|
'DEF123',
|
||||||
|
'abc789',
|
||||||
|
'456abc',
|
||||||
|
'4ab3cdef',
|
||||||
|
'hjs[12].*(){}^\\'
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("by blank string matches all objects", function () {
|
||||||
|
doFilter('');
|
||||||
|
expect(filteredObjectNames).toEqual([
|
||||||
|
'DEF123',
|
||||||
|
'abc789',
|
||||||
|
'456abc',
|
||||||
|
'4ab3cdef',
|
||||||
|
'hjs[12].*(){}^\\'
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("exactly matches an object name", function () {
|
||||||
|
doFilter('4ab3cdef');
|
||||||
|
expect(filteredObjectNames).toEqual(['4ab3cdef']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("partially matches object names", function () {
|
||||||
|
doFilter('abc');
|
||||||
|
expect(filteredObjectNames).toEqual([
|
||||||
|
'abc789',
|
||||||
|
'456abc'
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("matches case insensitive names", function () {
|
||||||
|
doFilter('def');
|
||||||
|
expect(filteredObjectNames).toEqual([
|
||||||
|
'DEF123',
|
||||||
|
'4ab3cdef'
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("works as expected with special characters", function () {
|
||||||
|
doFilter('[12]');
|
||||||
|
expect(filteredObjectNames).toEqual(['hjs[12].*(){}^\\']);
|
||||||
|
doFilter('.*');
|
||||||
|
expect(filteredObjectNames).toEqual(['hjs[12].*(){}^\\']);
|
||||||
|
doFilter('.*()');
|
||||||
|
expect(filteredObjectNames).toEqual(['hjs[12].*(){}^\\']);
|
||||||
|
doFilter('.*?');
|
||||||
|
expect(filteredObjectNames).toEqual([]);
|
||||||
|
doFilter('.+');
|
||||||
|
expect(filteredObjectNames).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("exposes CSS classes from limits", function () {
|
||||||
|
var id = mockDomainObject.getId(),
|
||||||
|
testClass = "some-css-class",
|
||||||
|
mockLimitCapability =
|
||||||
|
jasmine.createSpyObj('limit', ['evaluate']);
|
||||||
|
|
||||||
|
mockDomainObject.getCapability.andCallFake(function (key) {
|
||||||
|
return key === 'limit' && mockLimitCapability;
|
||||||
|
});
|
||||||
|
mockLimitCapability.evaluate
|
||||||
|
.andReturn({ cssClass: testClass });
|
||||||
|
|
||||||
|
mockSubscription.getTelemetryObjects
|
||||||
|
.andReturn([mockDomainObject]);
|
||||||
|
|
||||||
|
fireFnWatches();
|
||||||
|
mockSubscriber.subscribe.mostRecentCall.args[1]();
|
||||||
|
|
||||||
|
expect(controller.classes()[id]).toEqual(testClass);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("exposes a counter that changes with each update", function () {
|
||||||
|
var i, prior;
|
||||||
|
|
||||||
|
for (i = 0; i < 10; i += 1) {
|
||||||
|
prior = controller.counter();
|
||||||
|
expect(controller.counter()).toEqual(prior);
|
||||||
|
mockSubscriber.subscribe.mostRecentCall.args[1]();
|
||||||
|
expect(controller.counter()).not.toEqual(prior);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
39
platform/features/autoflow/test/MCTAutoflowTableSpec.js
Executable file
39
platform/features/autoflow/test/MCTAutoflowTableSpec.js
Executable file
@ -0,0 +1,39 @@
|
|||||||
|
|
||||||
|
define(
|
||||||
|
["../src/MCTAutoflowTable"],
|
||||||
|
function (MCTAutoflowTable) {
|
||||||
|
|
||||||
|
describe("The mct-autoflow-table directive", function () {
|
||||||
|
var mctAutoflowTable;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
mctAutoflowTable = new MCTAutoflowTable();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Real functionality is contained/tested in the linker,
|
||||||
|
// so just check to make sure we're exposing the directive
|
||||||
|
// appropriately.
|
||||||
|
it("is applicable at the element level", function () {
|
||||||
|
expect(mctAutoflowTable.restrict).toEqual("E");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("two-ways binds needed scope variables", function () {
|
||||||
|
expect(mctAutoflowTable.scope).toEqual({
|
||||||
|
objects: "=",
|
||||||
|
values: "=",
|
||||||
|
rows: "=",
|
||||||
|
updated: "=",
|
||||||
|
classes: "=",
|
||||||
|
columnWidth: "=",
|
||||||
|
counter: "="
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("provides a link function", function () {
|
||||||
|
expect(mctAutoflowTable.link).toEqual(jasmine.any(Function));
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
@ -45,7 +45,7 @@ define(
|
|||||||
|
|
||||||
FollowIndicator.prototype.getText = function () {
|
FollowIndicator.prototype.getText = function () {
|
||||||
var timer = this.timerService.getTimer();
|
var timer = this.timerService.getTimer();
|
||||||
return timer ? ('Following timer ' + timer.name) : NO_TIMER;
|
return (timer) ? 'Following timer ' + timer.getModel().name : NO_TIMER;
|
||||||
};
|
};
|
||||||
|
|
||||||
FollowIndicator.prototype.getDescription = function () {
|
FollowIndicator.prototype.getDescription = function () {
|
||||||
|
@ -42,15 +42,18 @@ define(["../../src/indicators/FollowIndicator"], function (FollowIndicator) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("when a timer is set", function () {
|
describe("when a timer is set", function () {
|
||||||
var testObject;
|
var testModel;
|
||||||
|
var mockDomainObject;
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
testObject = { name: "some timer!" };
|
testModel = { name: "some timer!" };
|
||||||
mockTimerService.getTimer.andReturn(testObject);
|
mockDomainObject = jasmine.createSpyObj('timer', ['getModel']);
|
||||||
|
mockDomainObject.getModel.andReturn(testModel);
|
||||||
|
mockTimerService.getTimer.andReturn(mockDomainObject);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("displays the timer's name", function () {
|
it("displays the timer's name", function () {
|
||||||
expect(indicator.getText().indexOf(testObject.name))
|
expect(indicator.getText().indexOf(testModel.name))
|
||||||
.not.toEqual(-1);
|
.not.toEqual(-1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -255,8 +255,6 @@ define(
|
|||||||
if (this.nextDatum) {
|
if (this.nextDatum) {
|
||||||
this.updateValues(this.nextDatum);
|
this.updateValues(this.nextDatum);
|
||||||
delete this.nextDatum;
|
delete this.nextDatum;
|
||||||
} else {
|
|
||||||
this.updateValues(this.$scope.imageHistory[this.$scope.imageHistory.length - 1]);
|
|
||||||
}
|
}
|
||||||
this.autoScroll = true;
|
this.autoScroll = true;
|
||||||
}
|
}
|
||||||
|
@ -183,17 +183,6 @@ define(
|
|||||||
expect(controller.getImageUrl()).toEqual(newUrl);
|
expect(controller.getImageUrl()).toEqual(newUrl);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("forwards large image view to latest image in history on un-pause", function () {
|
|
||||||
$scope.imageHistory = [
|
|
||||||
{ utc: 1434600258122, url: 'some/url1', selected: false},
|
|
||||||
{ utc: 1434600258123, url: 'some/url2', selected: false}
|
|
||||||
];
|
|
||||||
controller.paused(true);
|
|
||||||
controller.paused(false);
|
|
||||||
|
|
||||||
expect(controller.getImageUrl()).toEqual(controller.getImageUrl($scope.imageHistory[1]));
|
|
||||||
});
|
|
||||||
|
|
||||||
it("subscribes to telemetry", function () {
|
it("subscribes to telemetry", function () {
|
||||||
expect(openmct.telemetry.subscribe).toHaveBeenCalledWith(
|
expect(openmct.telemetry.subscribe).toHaveBeenCalledWith(
|
||||||
newDomainObject,
|
newDomainObject,
|
||||||
@ -238,7 +227,7 @@ define(
|
|||||||
expect(controller.updateHistory(mockDatum)).toBe(false);
|
expect(controller.updateHistory(mockDatum)).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("when user clicks on imagery thumbnail", function () {
|
describe("user clicks on imagery thumbnail", function () {
|
||||||
var mockDatum = { utc: 1434600258123, url: 'some/url', selected: false};
|
var mockDatum = { utc: 1434600258123, url: 'some/url', selected: false};
|
||||||
|
|
||||||
it("pauses and adds selected class to imagery thumbnail", function () {
|
it("pauses and adds selected class to imagery thumbnail", function () {
|
||||||
@ -259,7 +248,6 @@ define(
|
|||||||
expect(controller.getTime()).toEqual(controller.timeFormat.format(mockDatum.utc));
|
expect(controller.getTime()).toEqual(controller.timeFormat.format(mockDatum.utc));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("initially shows an empty string for date/time", function () {
|
it("initially shows an empty string for date/time", function () {
|
||||||
|
@ -260,9 +260,7 @@ define([
|
|||||||
"key": "LayoutController",
|
"key": "LayoutController",
|
||||||
"implementation": LayoutController,
|
"implementation": LayoutController,
|
||||||
"depends": [
|
"depends": [
|
||||||
"$scope",
|
"$scope"
|
||||||
"$element",
|
|
||||||
"openmct"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -272,8 +270,7 @@ define([
|
|||||||
"$scope",
|
"$scope",
|
||||||
"$q",
|
"$q",
|
||||||
"dialogService",
|
"dialogService",
|
||||||
"openmct",
|
"openmct"
|
||||||
"$element"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -337,6 +334,46 @@ define([
|
|||||||
"conversion": "number[]"
|
"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">
|
ng-controller="FixedController as controller">
|
||||||
|
|
||||||
<!-- Background grid -->
|
<!-- 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"
|
<div class="l-grid l-grid-x"
|
||||||
ng-if="!controller.getGridSize()[0] < 3"
|
ng-if="!controller.getGridSize()[0] < 3"
|
||||||
ng-style="{ 'background-size': controller.getGridSize() [0] + 'px 100%' }"></div>
|
ng-style="{ 'background-size': controller.getGridSize() [0] + 'px 100%' }"></div>
|
||||||
@ -35,28 +35,33 @@
|
|||||||
<!-- Fixed position elements -->
|
<!-- Fixed position elements -->
|
||||||
<div ng-repeat="element in controller.getElements()"
|
<div ng-repeat="element in controller.getElements()"
|
||||||
class="l-fixed-position-item s-selectable s-moveable s-hover-border"
|
class="l-fixed-position-item s-selectable s-moveable s-hover-border"
|
||||||
|
ng-class="{
|
||||||
|
's-not-selected': controller.selected() && !controller.selected(element),
|
||||||
|
's-selected': controller.selected(element)
|
||||||
|
}"
|
||||||
ng-style="element.style"
|
ng-style="element.style"
|
||||||
mct-selectable="controller.getContext(element)"
|
ng-click="controller.select(element)">
|
||||||
mct-init-select="controller.shouldSelect(element)">
|
|
||||||
<mct-include key="element.template"
|
<mct-include key="element.template"
|
||||||
parameters="{ gridSize: controller.getGridSize() }"
|
parameters="{ gridSize: controller.getGridSize() }"
|
||||||
ng-model="element">
|
ng-model="element">
|
||||||
</mct-include>
|
</mct-include>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Selection highlight, handles -->
|
<!-- 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"
|
<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="controller.moveHandle().continueDrag(delta)"
|
||||||
mct-drag-up="controller.endDrag()"
|
mct-drag-up="controller.moveHandle().endDrag()"
|
||||||
ng-style="controller.getSelectedElementStyle()">
|
ng-style="controller.selected().style">
|
||||||
</div>
|
</div>
|
||||||
<div ng-repeat="handle in controller.handles()"
|
<div ng-repeat="handle in controller.handles()"
|
||||||
class="l-fixed-position-item-handle edit-corner"
|
class="l-fixed-position-item-handle edit-corner"
|
||||||
ng-style="handle.style()"
|
ng-style="handle.style()"
|
||||||
mct-drag-down="handle.startDrag()"
|
mct-drag-down="handle.startDrag()"
|
||||||
mct-drag="handle.continueDrag(delta)"
|
mct-drag="handle.continueDrag(delta)"
|
||||||
mct-drag-up="controller.endDrag(handle)">
|
mct-drag-up="handle.endDrag()">
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
this source code distribution or the Licensing information page available
|
this source code distribution or the Licensing information page available
|
||||||
at runtime from the About dialog for additional information.
|
at runtime from the About dialog for additional information.
|
||||||
-->
|
-->
|
||||||
<div class="frame frame-template t-frame-inner abs t-object-type-{{ domainObject.getModel().type }}">
|
<div class="frame frame-template t-frame-inner abs t-object-type-{{ representation.selected.key }}">
|
||||||
<div class="abs object-browse-bar l-flex-row">
|
<div class="abs object-browse-bar l-flex-row">
|
||||||
<div class="left flex-elem l-flex-row grows">
|
<div class="left flex-elem l-flex-row grows">
|
||||||
<mct-representation
|
<mct-representation
|
||||||
|
@ -22,27 +22,24 @@
|
|||||||
|
|
||||||
<div class="abs l-layout"
|
<div class="abs l-layout"
|
||||||
ng-controller="LayoutController as controller"
|
ng-controller="LayoutController as controller"
|
||||||
ng-click="controller.bypassSelection($event)">
|
ng-click="controller.clearSelection()">
|
||||||
|
|
||||||
<!-- Background grid -->
|
<!-- Background grid -->
|
||||||
<div class="l-grid-holder"
|
<div class="l-grid-holder" ng-click="controller.clearSelection()">
|
||||||
ng-show="!controller.drilledIn"
|
|
||||||
ng-click="controller.bypassSelection($event)">
|
|
||||||
<div class="l-grid l-grid-x"
|
<div class="l-grid l-grid-x"
|
||||||
ng-if="!controller.getGridSize()[0] < 3"
|
ng-if="!controller.getGridSize()[0] < 3"
|
||||||
ng-style="{ 'background-size': controller.getGridSize() [0] + 'px 100%' }"></div>
|
ng-style="{ 'background-size': controller.getGridSize() [0] + 'px 100%' }"></div>
|
||||||
<div class="l-grid l-grid-y"
|
<div class="l-grid l-grid-y"
|
||||||
ng-if="!controller.getGridSize()[1] < 3"
|
ng-if="!controller.getGridSize()[1] < 3"
|
||||||
ng-style="{ 'background-size': '100% ' + controller.getGridSize() [1] + 'px' }"></div>
|
ng-style="{ 'background-size': '100% ' + controller.getGridSize() [1] + 'px' }"></div>
|
||||||
|
<div class="l-sizing-guide t-size-720p"></div>
|
||||||
|
<div class="l-sizing-guide t-size-1080p"></div>
|
||||||
</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 }}"
|
<div class='abs frame t-frame-outer child-frame panel s-selectable s-moveable s-hover-border'
|
||||||
data-layout-id="{{childObject.getId() + '-' + $id}}"
|
ng-class="{ 'no-frame': !controller.hasFrame(childObject), 's-selected':controller.selected(childObject) }"
|
||||||
ng-class="{ 'no-frame': !controller.hasFrame(childObject), 's-drilled-in': controller.isDrilledIn(childObject) }"
|
|
||||||
ng-repeat="childObject in composition"
|
ng-repeat="childObject in composition"
|
||||||
ng-init="controller.selectIfNew(childObject.getId() + '-' + $id, childObject)"
|
ng-click="controller.select($event, childObject.getId())"
|
||||||
mct-selectable="controller.getContext(childObject, true)"
|
|
||||||
ng-dblclick="controller.drill($event, childObject)"
|
|
||||||
ng-style="controller.getFrameStyle(childObject.getId())">
|
ng-style="controller.getFrameStyle(childObject.getId())">
|
||||||
|
|
||||||
<mct-representation key="'frame'"
|
<mct-representation key="'frame'"
|
||||||
@ -50,7 +47,7 @@
|
|||||||
mct-object="childObject">
|
mct-object="childObject">
|
||||||
</mct-representation>
|
</mct-representation>
|
||||||
<!-- Drag handles -->
|
<!-- Drag handles -->
|
||||||
<span class="abs t-edit-handle-holder" ng-if="controller.selected(childObject) && !controller.isDrilledIn(childObject)">
|
<span class="abs t-edit-handle-holder s-hover-border" ng-if="controller.selected(childObject)">
|
||||||
<span class="edit-handle edit-move"
|
<span class="edit-handle edit-move"
|
||||||
mct-drag-down="controller.startDrag(childObject.getId(), [1,1], [0,0])"
|
mct-drag-down="controller.startDrag(childObject.getId(), [1,1], [0,0])"
|
||||||
mct-drag="controller.continueDrag(delta)"
|
mct-drag="controller.continueDrag(delta)"
|
||||||
@ -78,6 +75,7 @@
|
|||||||
mct-drag-up="controller.endDrag()">
|
mct-drag-up="controller.endDrag()">
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -47,7 +47,7 @@ define(
|
|||||||
* @constructor
|
* @constructor
|
||||||
* @param {Scope} $scope the controller's Angular scope
|
* @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.names = {}; // Cache names by ID
|
||||||
this.values = {}; // Cache values by ID
|
this.values = {}; // Cache values by ID
|
||||||
this.elementProxiesById = {};
|
this.elementProxiesById = {};
|
||||||
@ -55,11 +55,9 @@ define(
|
|||||||
this.telemetryObjects = [];
|
this.telemetryObjects = [];
|
||||||
this.subscriptions = [];
|
this.subscriptions = [];
|
||||||
this.openmct = openmct;
|
this.openmct = openmct;
|
||||||
this.$element = $element;
|
|
||||||
this.$scope = $scope;
|
this.$scope = $scope;
|
||||||
|
|
||||||
this.gridSize = $scope.domainObject && $scope.domainObject.getModel().layoutGrid;
|
this.gridSize = $scope.domainObject && $scope.domainObject.getModel().layoutGrid;
|
||||||
this.fixedViewSelectable = false;
|
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
[
|
[
|
||||||
@ -89,8 +87,9 @@ define(
|
|||||||
|
|
||||||
// Update the style for a selected element
|
// Update the style for a selected element
|
||||||
function updateSelectionStyle() {
|
function updateSelectionStyle() {
|
||||||
if (self.selectedElementProxy) {
|
var element = self.selection && self.selection.get();
|
||||||
self.selectedElementProxy.style = convertPosition(self.selectedElementProxy);
|
if (element) {
|
||||||
|
element.style = convertPosition(element);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,19 +136,25 @@ define(
|
|||||||
|
|
||||||
// Decorate elements in the current configuration
|
// Decorate elements in the current configuration
|
||||||
function refreshElements() {
|
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...
|
// Create the new proxies...
|
||||||
self.elementProxies = elements.map(makeProxyElement);
|
self.elementProxies = elements.map(makeProxyElement);
|
||||||
|
|
||||||
// If selection is not in array, select parent.
|
// Clear old selection, and restore if appropriate
|
||||||
// Otherwise, set the element to select after refresh.
|
if (self.selection) {
|
||||||
if (self.selectedElementProxy) {
|
self.selection.deselect();
|
||||||
var index = elements.indexOf(self.selectedElementProxy.element);
|
if (index > -1) {
|
||||||
if (index === -1) {
|
self.select(self.elementProxies[index]);
|
||||||
self.$element[0].click();
|
|
||||||
} else if (!self.elementToSelectAfterRefresh) {
|
|
||||||
self.elementToSelectAfterRefresh = self.elementProxies[index].element;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -219,12 +224,12 @@ define(
|
|||||||
$scope.configuration.elements || [];
|
$scope.configuration.elements || [];
|
||||||
// Store the position of this element.
|
// Store the position of this element.
|
||||||
$scope.configuration.elements.push(element);
|
$scope.configuration.elements.push(element);
|
||||||
|
|
||||||
self.elementToSelectAfterRefresh = element;
|
|
||||||
|
|
||||||
// Refresh displayed elements
|
// Refresh displayed elements
|
||||||
refreshElements();
|
refreshElements();
|
||||||
|
// Select the newly-added element
|
||||||
|
self.select(
|
||||||
|
self.elementProxies[self.elementProxies.length - 1]
|
||||||
|
);
|
||||||
// Mark change as persistable
|
// Mark change as persistable
|
||||||
if ($scope.commit) {
|
if ($scope.commit) {
|
||||||
$scope.commit("Dropped an element.");
|
$scope.commit("Dropped an element.");
|
||||||
@ -258,36 +263,21 @@ define(
|
|||||||
self.getTelemetry($scope.domainObject);
|
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.elementProxies = [];
|
||||||
this.generateDragHandle = generateDragHandle;
|
this.generateDragHandle = generateDragHandle;
|
||||||
this.generateDragHandles = generateDragHandles;
|
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
|
// Detect changes to grid size
|
||||||
$scope.$watch("model.layoutGrid", updateElementPositions);
|
$scope.$watch("model.layoutGrid", updateElementPositions);
|
||||||
@ -308,13 +298,10 @@ define(
|
|||||||
$scope.$on("$destroy", function () {
|
$scope.$on("$destroy", function () {
|
||||||
self.unsubscribe();
|
self.unsubscribe();
|
||||||
self.openmct.time.off("bounds", updateDisplayBounds);
|
self.openmct.time.off("bounds", updateDisplayBounds);
|
||||||
self.openmct.selection.off("change", setSelection);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Respond to external bounds changes
|
// Respond to external bounds changes
|
||||||
this.openmct.time.on("bounds", updateDisplayBounds);
|
this.openmct.time.on("bounds", updateDisplayBounds);
|
||||||
this.openmct.selection.on('change', setSelection);
|
|
||||||
this.$element.on('click', this.bypassSelection.bind(this));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -373,10 +360,10 @@ define(
|
|||||||
*/
|
*/
|
||||||
FixedController.prototype.updateView = function (telemetryObject, datum) {
|
FixedController.prototype.updateView = function (telemetryObject, datum) {
|
||||||
var metadata = this.openmct.telemetry.getMetadata(telemetryObject);
|
var metadata = this.openmct.telemetry.getMetadata(telemetryObject);
|
||||||
var valueMetadata = this.chooseValueMetadataToDisplay(metadata);
|
var telemetryKeyToDisplay = this.chooseTelemetryKeyToDisplay(metadata);
|
||||||
var formattedTelemetryValue = this.getFormattedTelemetryValueForKey(valueMetadata, datum);
|
var formattedTelemetryValue = this.getFormattedTelemetryValueForKey(telemetryKeyToDisplay, datum, metadata);
|
||||||
var limitEvaluator = this.openmct.telemetry.limitEvaluator(telemetryObject);
|
var limitEvaluator = this.openmct.telemetry.limitEvaluator(telemetryObject);
|
||||||
var alarm = limitEvaluator && limitEvaluator.evaluate(datum, valueMetadata);
|
var alarm = limitEvaluator && limitEvaluator.evaluate(datum, telemetryKeyToDisplay);
|
||||||
|
|
||||||
this.setDisplayedValue(
|
this.setDisplayedValue(
|
||||||
telemetryObject,
|
telemetryObject,
|
||||||
@ -389,28 +376,29 @@ define(
|
|||||||
/**
|
/**
|
||||||
* @private
|
* @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);
|
var formatter = this.openmct.telemetry.getValueFormatter(valueMetadata);
|
||||||
|
|
||||||
return formatter.format(datum);
|
return formatter.format(datum[valueMetadata.key]);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
FixedController.prototype.chooseValueMetadataToDisplay = function (metadata) {
|
FixedController.prototype.chooseTelemetryKeyToDisplay = function (metadata) {
|
||||||
// If there is a range value, show that preferentially
|
// 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 no range is defined, default to the highest priority non time-domain data.
|
||||||
if (valueMetadata === undefined) {
|
if (telemetryKeyToDisplay === undefined) {
|
||||||
var valuesOrderedByPriority = metadata.values();
|
var valuesOrderedByPriority = metadata.values();
|
||||||
valueMetadata = valuesOrderedByPriority.filter(function (values) {
|
telemetryKeyToDisplay = valuesOrderedByPriority.filter(function (valueMetadata) {
|
||||||
return !(values.hints.domain);
|
return !(valueMetadata.hints.domain);
|
||||||
})[0];
|
})[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
return valueMetadata;
|
return telemetryKeyToDisplay.source;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -464,7 +452,7 @@ define(
|
|||||||
|
|
||||||
function filterForTelemetryObjects(objects) {
|
function filterForTelemetryObjects(objects) {
|
||||||
return objects.filter(function (object) {
|
return objects.filter(function (object) {
|
||||||
return self.openmct.telemetry.isTelemetryObject(object);
|
return self.openmct.telemetry.canProvideTelemetry(object);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -504,56 +492,38 @@ define(
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the element should be selected or not.
|
* Check if the element is currently selected, or (if no
|
||||||
*
|
* argument is supplied) get the currently selected element.
|
||||||
* @param elementProxy the element to check
|
* @returns {boolean} true if selected
|
||||||
* @returns {boolean} true if the element should be selected.
|
|
||||||
*/
|
*/
|
||||||
FixedController.prototype.shouldSelect = function (elementProxy) {
|
FixedController.prototype.selected = function (element) {
|
||||||
if (elementProxy.element === this.elementToSelectAfterRefresh) {
|
var selection = this.selection;
|
||||||
delete this.elementToSelectAfterRefresh;
|
return selection && ((arguments.length > 0) ?
|
||||||
return true;
|
selection.selected(element) : selection.get());
|
||||||
} else {
|
};
|
||||||
return false;
|
|
||||||
|
/**
|
||||||
|
* Set the active user selection in this view.
|
||||||
|
* @param element the element to select
|
||||||
|
*/
|
||||||
|
FixedController.prototype.select = function select(element) {
|
||||||
|
if (this.selection) {
|
||||||
|
// Update selection...
|
||||||
|
this.selection.select(element);
|
||||||
|
// ...as well as move, resize handles
|
||||||
|
this.mvHandle = this.generateDragHandle(element);
|
||||||
|
this.resizeHandles = this.generateDragHandles(element);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if an element is currently selected.
|
* Clear the current user selection.
|
||||||
*
|
|
||||||
* @returns {boolean} true if an element is selected.
|
|
||||||
*/
|
*/
|
||||||
FixedController.prototype.isElementSelected = function () {
|
FixedController.prototype.clearSelection = function () {
|
||||||
return (this.selectedElementProxy) ? true : false;
|
if (this.selection) {
|
||||||
};
|
this.selection.deselect();
|
||||||
|
this.resizeHandles = [];
|
||||||
/**
|
this.mvHandle = undefined;
|
||||||
* 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;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -574,38 +544,6 @@ define(
|
|||||||
return this.mvHandle;
|
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;
|
return FixedController;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -65,7 +65,7 @@ define(
|
|||||||
* Start a drag gesture. This should be called when a drag
|
* Start a drag gesture. This should be called when a drag
|
||||||
* begins to track initial state.
|
* begins to track initial state.
|
||||||
*/
|
*/
|
||||||
FixedDragHandle.prototype.startDrag = function () {
|
FixedDragHandle.prototype.startDrag = function startDrag() {
|
||||||
// Cache initial x/y positions
|
// Cache initial x/y positions
|
||||||
this.dragging = {
|
this.dragging = {
|
||||||
x: this.elementHandle.x(),
|
x: this.elementHandle.x(),
|
||||||
|
@ -27,11 +27,9 @@
|
|||||||
*/
|
*/
|
||||||
define(
|
define(
|
||||||
[
|
[
|
||||||
'zepto',
|
|
||||||
'./LayoutDrag'
|
'./LayoutDrag'
|
||||||
],
|
],
|
||||||
function (
|
function (
|
||||||
$,
|
|
||||||
LayoutDrag
|
LayoutDrag
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@ -52,12 +50,10 @@ define(
|
|||||||
* @constructor
|
* @constructor
|
||||||
* @param {Scope} $scope the controller's Angular scope
|
* @param {Scope} $scope the controller's Angular scope
|
||||||
*/
|
*/
|
||||||
function LayoutController($scope, $element, openmct) {
|
function LayoutController($scope) {
|
||||||
var self = this,
|
var self = this,
|
||||||
callbackCount = 0;
|
callbackCount = 0;
|
||||||
|
|
||||||
this.$element = $element;
|
|
||||||
|
|
||||||
// Update grid size when it changed
|
// Update grid size when it changed
|
||||||
function updateGridSize(layoutGrid) {
|
function updateGridSize(layoutGrid) {
|
||||||
var oldSize = self.gridSize;
|
var oldSize = self.gridSize;
|
||||||
@ -127,11 +123,12 @@ define(
|
|||||||
self.layoutPanels(ids);
|
self.layoutPanels(ids);
|
||||||
self.setFrames(ids);
|
self.setFrames(ids);
|
||||||
|
|
||||||
if (self.selectedId &&
|
// If there is a newly-dropped object, select it.
|
||||||
self.selectedId !== $scope.domainObject.getId() &&
|
if (self.droppedIdToSelectAfterRefresh) {
|
||||||
composition.indexOf(self.selectedId) === -1) {
|
self.select(null, self.droppedIdToSelectAfterRefresh);
|
||||||
// Click triggers selection of layout parent.
|
delete self.droppedIdToSelectAfterRefresh;
|
||||||
self.$element[0].click();
|
} else if (composition.indexOf(self.selectedId) === -1) {
|
||||||
|
self.clearSelection();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -163,39 +160,22 @@ define(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Sets the selectable object in response to the selection change event.
|
|
||||||
function setSelection(selectable) {
|
|
||||||
var selection = selectable[0];
|
|
||||||
|
|
||||||
if (!selection) {
|
|
||||||
delete self.selectedId;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.selectedId = selection.context.oldItem.getId();
|
|
||||||
self.drilledIn = undefined;
|
|
||||||
self.selectable = selectable;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.positions = {};
|
this.positions = {};
|
||||||
this.rawPositions = {};
|
this.rawPositions = {};
|
||||||
this.gridSize = DEFAULT_GRID_SIZE;
|
this.gridSize = DEFAULT_GRID_SIZE;
|
||||||
this.$scope = $scope;
|
this.$scope = $scope;
|
||||||
this.drilledIn = undefined;
|
|
||||||
this.openmct = openmct;
|
|
||||||
|
|
||||||
// Watch for changes to the grid size in the model
|
// Watch for changes to the grid size in the model
|
||||||
$scope.$watch("model.layoutGrid", updateGridSize);
|
$scope.$watch("model.layoutGrid", updateGridSize);
|
||||||
|
|
||||||
|
$scope.$watch("selection", function (selection) {
|
||||||
|
this.selection = selection;
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
// Update composed objects on screen, and position panes
|
// Update composed objects on screen, and position panes
|
||||||
$scope.$watchCollection("model.composition", refreshComposition);
|
$scope.$watchCollection("model.composition", refreshComposition);
|
||||||
|
|
||||||
openmct.selection.on('change', setSelection);
|
// Position panes where they are dropped
|
||||||
|
|
||||||
$scope.$on("$destroy", function () {
|
|
||||||
openmct.selection.off("change", setSelection);
|
|
||||||
});
|
|
||||||
|
|
||||||
$scope.$on("mctDrop", handleDrop);
|
$scope.$on("mctDrop", handleDrop);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -377,14 +357,37 @@ define(
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the object is currently selected.
|
* Check if the object is currently selected.
|
||||||
*
|
*
|
||||||
* @param {string} obj the object to check for selection
|
* @param {string} obj the object to check for selection
|
||||||
* @returns {boolean} true if selected, otherwise false
|
* @returns {boolean} true if selected, otherwise false
|
||||||
*/
|
*/
|
||||||
LayoutController.prototype.selected = function (obj) {
|
LayoutController.prototype.selected = function (obj) {
|
||||||
var sobj = this.openmct.selection.get()[0];
|
return !!this.selectedId && this.selectedId === obj.getId();
|
||||||
return (sobj && sobj.context.oldItem.getId() === obj.getId()) ? true : false;
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the active user selection in this view.
|
||||||
|
*
|
||||||
|
* @param event the mouse event
|
||||||
|
* @param {string} id the object id
|
||||||
|
*/
|
||||||
|
LayoutController.prototype.select = function (event, id) {
|
||||||
|
if (event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
if (this.selection) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.selectedId = id;
|
||||||
|
|
||||||
|
var selectedObj = {};
|
||||||
|
selectedObj[this.frames[id] ? 'hideFrame' : 'showFrame'] = this.toggleFrame.bind(this, id);
|
||||||
|
|
||||||
|
if (this.selection) {
|
||||||
|
this.selection.select(selectedObj);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -393,7 +396,7 @@ define(
|
|||||||
* @param {string} id the object id
|
* @param {string} id the object id
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
LayoutController.prototype.toggleFrame = function (id, domainObject) {
|
LayoutController.prototype.toggleFrame = function (id) {
|
||||||
var configuration = this.$scope.configuration;
|
var configuration = this.$scope.configuration;
|
||||||
|
|
||||||
if (!configuration.panels[id]) {
|
if (!configuration.panels[id]) {
|
||||||
@ -401,75 +404,21 @@ define(
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.frames[id] = configuration.panels[id].hasFrame = !this.frames[id];
|
this.frames[id] = configuration.panels[id].hasFrame = !this.frames[id];
|
||||||
|
this.select(undefined, id); // reselect so toolbar updates
|
||||||
var selection = this.openmct.selection.get();
|
|
||||||
selection[0].context.toolbar = this.getToolbar(id, domainObject);
|
|
||||||
this.openmct.selection.select(selection); // reselect so toolbar updates
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the toolbar object for the given domain object.
|
* Clear the current user selection.
|
||||||
*
|
|
||||||
* @param id the domain object id
|
|
||||||
* @param domainObject the domain object
|
|
||||||
* @returns {object}
|
|
||||||
* @private
|
|
||||||
*/
|
*/
|
||||||
LayoutController.prototype.getToolbar = function (id, domainObject) {
|
LayoutController.prototype.clearSelection = function () {
|
||||||
var toolbarObj = {};
|
|
||||||
toolbarObj[this.frames[id] ? 'hideFrame' : 'showFrame'] = this.toggleFrame.bind(this, id, domainObject);
|
|
||||||
return toolbarObj;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bypasses selection if drag is in progress.
|
|
||||||
*
|
|
||||||
* @param event the angular event object
|
|
||||||
*/
|
|
||||||
LayoutController.prototype.bypassSelection = function (event) {
|
|
||||||
if (this.dragInProgress) {
|
if (this.dragInProgress) {
|
||||||
if (event) {
|
|
||||||
event.stopPropagation();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the domain object is drilled in.
|
|
||||||
*
|
|
||||||
* @param domainObject the domain object
|
|
||||||
* @return true if the object is drilled in, false otherwise
|
|
||||||
*/
|
|
||||||
LayoutController.prototype.isDrilledIn = function (domainObject) {
|
|
||||||
return this.drilledIn === domainObject.getId();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Puts the given object in the drilled-in mode.
|
|
||||||
*
|
|
||||||
* @param event the angular event object
|
|
||||||
* @param domainObject the domain object
|
|
||||||
*/
|
|
||||||
LayoutController.prototype.drill = function (event, domainObject) {
|
|
||||||
if (event) {
|
|
||||||
event.stopPropagation();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!domainObject.getCapability('editor').inEditContext()) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!domainObject.hasCapability('composition')) {
|
if (this.selection) {
|
||||||
return;
|
this.selection.deselect();
|
||||||
|
delete this.selectedId;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable since fixed position doesn't use the selection API yet
|
|
||||||
if (domainObject.getModel().type === 'telemetry.fixed') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.drilledIn = domainObject.getId();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -491,36 +440,6 @@ define(
|
|||||||
return this.gridSize;
|
return this.gridSize;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the selection context.
|
|
||||||
*
|
|
||||||
* @param domainObject the domain object
|
|
||||||
* @returns {object} the context object which includes
|
|
||||||
* item, oldItem and toolbar
|
|
||||||
*/
|
|
||||||
LayoutController.prototype.getContext = function (domainObject, toolbar) {
|
|
||||||
return {
|
|
||||||
item: domainObject.useCapability('adapter'),
|
|
||||||
oldItem: domainObject,
|
|
||||||
toolbar: toolbar ? this.getToolbar(domainObject.getId(), domainObject) : undefined
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Selects a newly-dropped object.
|
|
||||||
*
|
|
||||||
* @param classSelector the css class selector
|
|
||||||
* @param domainObject the domain object
|
|
||||||
*/
|
|
||||||
LayoutController.prototype.selectIfNew = function (selector, domainObject) {
|
|
||||||
if (domainObject.getId() === this.droppedIdToSelectAfterRefresh) {
|
|
||||||
setTimeout(function () {
|
|
||||||
$('[data-layout-id="' + selector + '"]')[0].click();
|
|
||||||
delete this.droppedIdToSelectAfterRefresh;
|
|
||||||
}.bind(this), 0);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return LayoutController;
|
return LayoutController;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -55,8 +55,8 @@ define(
|
|||||||
* @param element the fixed position element, as stored in its
|
* @param element the fixed position element, as stored in its
|
||||||
* configuration
|
* configuration
|
||||||
* @param index the element's index within its array
|
* @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 {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) {
|
function ElementProxy(element, index, elements, gridSize) {
|
||||||
/**
|
/**
|
||||||
|
@ -21,14 +21,8 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
define(
|
define(
|
||||||
[
|
["../src/FixedController"],
|
||||||
"../src/FixedController",
|
function (FixedController) {
|
||||||
"zepto"
|
|
||||||
],
|
|
||||||
function (
|
|
||||||
FixedController,
|
|
||||||
$
|
|
||||||
) {
|
|
||||||
|
|
||||||
describe("The Fixed Position controller", function () {
|
describe("The Fixed Position controller", function () {
|
||||||
var mockScope,
|
var mockScope,
|
||||||
@ -52,9 +46,6 @@ define(
|
|||||||
mockMetadata,
|
mockMetadata,
|
||||||
mockTimeSystem,
|
mockTimeSystem,
|
||||||
mockLimitEvaluator,
|
mockLimitEvaluator,
|
||||||
mockSelection,
|
|
||||||
$element = [],
|
|
||||||
selectable = [],
|
|
||||||
controller;
|
controller;
|
||||||
|
|
||||||
// Utility function; find a watch for a given expression
|
// Utility function; find a watch for a given expression
|
||||||
@ -106,8 +97,8 @@ define(
|
|||||||
'telemetryFormatter',
|
'telemetryFormatter',
|
||||||
['format']
|
['format']
|
||||||
);
|
);
|
||||||
mockFormatter.format.andCallFake(function (valueMetadata) {
|
mockFormatter.format.andCallFake(function (value) {
|
||||||
return "Formatted " + valueMetadata.value;
|
return "Formatted " + value;
|
||||||
});
|
});
|
||||||
|
|
||||||
mockDomainObject = jasmine.createSpyObj(
|
mockDomainObject = jasmine.createSpyObj(
|
||||||
@ -150,13 +141,13 @@ define(
|
|||||||
[
|
[
|
||||||
'subscribe',
|
'subscribe',
|
||||||
'request',
|
'request',
|
||||||
'isTelemetryObject',
|
'canProvideTelemetry',
|
||||||
'getMetadata',
|
'getMetadata',
|
||||||
'limitEvaluator',
|
'limitEvaluator',
|
||||||
'getValueFormatter'
|
'getValueFormatter'
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
mockTelemetryAPI.isTelemetryObject.andReturn(true);
|
mockTelemetryAPI.canProvideTelemetry.andReturn(true);
|
||||||
mockTelemetryAPI.request.andReturn(Promise.resolve([]));
|
mockTelemetryAPI.request.andReturn(Promise.resolve([]));
|
||||||
|
|
||||||
testGrid = [123, 456];
|
testGrid = [123, 456];
|
||||||
@ -189,30 +180,17 @@ define(
|
|||||||
|
|
||||||
mockScope.model = testModel;
|
mockScope.model = testModel;
|
||||||
mockScope.configuration = testConfiguration;
|
mockScope.configuration = testConfiguration;
|
||||||
|
mockScope.selection = jasmine.createSpyObj(
|
||||||
selectable[0] = {
|
'selection',
|
||||||
context: {
|
['select', 'get', 'selected', 'deselect', 'proxy']
|
||||||
oldItem: mockDomainObject
|
);
|
||||||
}
|
|
||||||
};
|
|
||||||
mockSelection = jasmine.createSpyObj("selection", [
|
|
||||||
'select',
|
|
||||||
'on',
|
|
||||||
'off',
|
|
||||||
'get'
|
|
||||||
]);
|
|
||||||
mockSelection.get.andCallThrough();
|
|
||||||
|
|
||||||
mockOpenMCT = {
|
mockOpenMCT = {
|
||||||
time: mockConductor,
|
time: mockConductor,
|
||||||
telemetry: mockTelemetryAPI,
|
telemetry: mockTelemetryAPI,
|
||||||
composition: mockCompositionAPI,
|
composition: mockCompositionAPI
|
||||||
selection: mockSelection
|
|
||||||
};
|
};
|
||||||
|
|
||||||
$element = $('<div></div>');
|
|
||||||
spyOn($element[0], 'click');
|
|
||||||
|
|
||||||
mockMetadata = jasmine.createSpyObj('mockMetadata', [
|
mockMetadata = jasmine.createSpyObj('mockMetadata', [
|
||||||
'valuesForHints',
|
'valuesForHints',
|
||||||
'value',
|
'value',
|
||||||
@ -248,11 +226,11 @@ define(
|
|||||||
mockScope,
|
mockScope,
|
||||||
mockQ,
|
mockQ,
|
||||||
mockDialogService,
|
mockDialogService,
|
||||||
mockOpenMCT,
|
mockOpenMCT
|
||||||
$element
|
|
||||||
);
|
);
|
||||||
|
|
||||||
findWatch("model.layoutGrid")(testModel.layoutGrid);
|
findWatch("model.layoutGrid")(testModel.layoutGrid);
|
||||||
|
findWatch("selection")(mockScope.selection);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("subscribes when a domain object is available", function () {
|
it("subscribes when a domain object is available", function () {
|
||||||
@ -328,41 +306,41 @@ define(
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("allows elements to be selected", function () {
|
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;
|
var elements;
|
||||||
|
|
||||||
testModel.modified = 1;
|
testModel.modified = 1;
|
||||||
findWatch("model.modified")(testModel.modified);
|
findWatch("model.modified")(testModel.modified);
|
||||||
|
|
||||||
elements = controller.getElements();
|
elements = controller.getElements();
|
||||||
selectable[0].context.elementProxy = elements[1];
|
controller.select(elements[1]);
|
||||||
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
expect(mockScope.selection.select)
|
||||||
|
.toHaveBeenCalledWith(elements[1]);
|
||||||
expect(controller.getSelectedElement()).toEqual(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;
|
testModel.modified = 1;
|
||||||
findWatch("model.modified")(testModel.modified);
|
findWatch("model.modified")(testModel.modified);
|
||||||
|
|
||||||
var elements = controller.getElements();
|
elements = controller.getElements();
|
||||||
selectable[0].context.elementProxy = elements[1];
|
controller.select(elements[1]);
|
||||||
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
mockScope.selection.get.andReturn(elements[1]);
|
||||||
|
expect(controller.selected()).toEqual(elements[1]);
|
||||||
|
});
|
||||||
|
|
||||||
elements[1].remove();
|
it("allows selections to be cleared", function () {
|
||||||
testModel.modified = 2;
|
var elements;
|
||||||
|
|
||||||
|
testModel.modified = 1;
|
||||||
findWatch("model.modified")(testModel.modified);
|
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 () {
|
it("retains selections during refresh", function () {
|
||||||
@ -374,21 +352,23 @@ define(
|
|||||||
findWatch("model.modified")(testModel.modified);
|
findWatch("model.modified")(testModel.modified);
|
||||||
|
|
||||||
elements = controller.getElements();
|
elements = controller.getElements();
|
||||||
selectable[0].context.elementProxy = elements[1];
|
controller.select(elements[1]);
|
||||||
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
|
||||||
|
|
||||||
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();
|
elements[2].remove();
|
||||||
testModel.modified = 2;
|
testModel.modified = 2;
|
||||||
findWatch("model.modified")(testModel.modified);
|
findWatch("model.modified")(testModel.modified);
|
||||||
|
|
||||||
elements = controller.getElements();
|
elements = controller.getElements();
|
||||||
|
|
||||||
// Verify removal, as test assumes this
|
// Verify removal, as test assumes this
|
||||||
expect(elements.length).toEqual(2);
|
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 () {
|
it("Displays received values for telemetry elements", function () {
|
||||||
@ -525,25 +505,21 @@ define(
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("exposes a view-level selection proxy", function () {
|
it("exposes a view-level selection proxy", function () {
|
||||||
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
expect(mockScope.selection.proxy).toHaveBeenCalledWith(
|
||||||
var selection = mockOpenMCT.selection.select.mostRecentCall.args[0];
|
jasmine.any(Object)
|
||||||
|
);
|
||||||
expect(mockOpenMCT.selection.select).toHaveBeenCalled();
|
|
||||||
expect(selection.context.viewProxy).toBeDefined();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("exposes drag handles", function () {
|
it("exposes drag handles", function () {
|
||||||
var handles;
|
var handles;
|
||||||
|
|
||||||
|
// Select something so that drag handles are expected
|
||||||
testModel.modified = 1;
|
testModel.modified = 1;
|
||||||
findWatch("model.modified")(testModel.modified);
|
findWatch("model.modified")(testModel.modified);
|
||||||
|
controller.select(controller.getElements()[1]);
|
||||||
selectable[0].context.elementProxy = controller.getElements()[1];
|
|
||||||
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
|
||||||
|
|
||||||
// Should have a non-empty array of handles
|
// Should have a non-empty array of handles
|
||||||
handles = controller.handles();
|
handles = controller.handles();
|
||||||
|
|
||||||
expect(handles).toEqual(jasmine.any(Array));
|
expect(handles).toEqual(jasmine.any(Array));
|
||||||
expect(handles.length).not.toEqual(0);
|
expect(handles.length).not.toEqual(0);
|
||||||
|
|
||||||
@ -556,14 +532,15 @@ define(
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("exposes a move handle", function () {
|
it("exposes a move handle", function () {
|
||||||
|
var handle;
|
||||||
|
|
||||||
|
// Select something so that drag handles are expected
|
||||||
testModel.modified = 1;
|
testModel.modified = 1;
|
||||||
findWatch("model.modified")(testModel.modified);
|
findWatch("model.modified")(testModel.modified);
|
||||||
|
controller.select(controller.getElements()[1]);
|
||||||
selectable[0].context.elementProxy = controller.getElements()[1];
|
|
||||||
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
|
||||||
|
|
||||||
// Should have a move handle
|
// Should have a move handle
|
||||||
var handle = controller.moveHandle();
|
handle = controller.moveHandle();
|
||||||
|
|
||||||
// And it should have start/continue/end drag methods
|
// And it should have start/continue/end drag methods
|
||||||
expect(handle.startDrag).toEqual(jasmine.any(Function));
|
expect(handle.startDrag).toEqual(jasmine.any(Function));
|
||||||
@ -574,40 +551,26 @@ define(
|
|||||||
it("updates selection style during drag", function () {
|
it("updates selection style during drag", function () {
|
||||||
var oldStyle;
|
var oldStyle;
|
||||||
|
|
||||||
|
// Select something so that drag handles are expected
|
||||||
testModel.modified = 1;
|
testModel.modified = 1;
|
||||||
findWatch("model.modified")(testModel.modified);
|
findWatch("model.modified")(testModel.modified);
|
||||||
|
controller.select(controller.getElements()[1]);
|
||||||
selectable[0].context.elementProxy = controller.getElements()[1];
|
mockScope.selection.get.andReturn(controller.getElements()[1]);
|
||||||
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
|
|
||||||
|
|
||||||
// Get style
|
// Get style
|
||||||
oldStyle = controller.getSelectedElementStyle();
|
oldStyle = controller.selected().style;
|
||||||
|
|
||||||
// Start a drag gesture
|
// Start a drag gesture
|
||||||
controller.moveHandle().startDrag();
|
controller.moveHandle().startDrag();
|
||||||
|
|
||||||
// Haven't moved yet; style shouldn't have updated yet
|
// Haven't moved yet; style shouldn't have updated yet
|
||||||
expect(controller.getSelectedElementStyle()).toEqual(oldStyle);
|
expect(controller.selected().style).toEqual(oldStyle);
|
||||||
|
|
||||||
// Drag a little
|
// Drag a little
|
||||||
controller.moveHandle().continueDrag([1000, 100]);
|
controller.moveHandle().continueDrag([1000, 100]);
|
||||||
|
|
||||||
// Style should have been updated
|
// Style should have been updated
|
||||||
expect(controller.getSelectedElementStyle()).not.toEqual(oldStyle);
|
expect(controller.selected().style).not.toEqual(oldStyle);
|
||||||
});
|
|
||||||
|
|
||||||
it("cleans up slection on scope destroy", function () {
|
|
||||||
expect(mockScope.$on).toHaveBeenCalledWith(
|
|
||||||
'$destroy',
|
|
||||||
jasmine.any(Function)
|
|
||||||
);
|
|
||||||
|
|
||||||
mockScope.$on.mostRecentCall.args[1]();
|
|
||||||
|
|
||||||
expect(mockOpenMCT.selection.off).toHaveBeenCalledWith(
|
|
||||||
'change',
|
|
||||||
jasmine.any(Function)
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("on display bounds changes", function () {
|
describe("on display bounds changes", function () {
|
||||||
@ -697,7 +660,7 @@ define(
|
|||||||
source: 'range'
|
source: 'range'
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
var key = controller.chooseValueMetadataToDisplay(mockMetadata).source;
|
var key = controller.chooseTelemetryKeyToDisplay(mockMetadata);
|
||||||
expect(key).toEqual('range');
|
expect(key).toEqual('range');
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -719,7 +682,7 @@ define(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
var key = controller.chooseValueMetadataToDisplay(mockMetadata).source;
|
var key = controller.chooseTelemetryKeyToDisplay(mockMetadata);
|
||||||
expect(key).toEqual('image');
|
expect(key).toEqual('image');
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -739,14 +702,6 @@ define(
|
|||||||
expect(controller.getElements()[0].cssClass).toEqual("alarm-a");
|
expect(controller.getElements()[0].cssClass).toEqual("alarm-a");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("listens for selection change events", function () {
|
|
||||||
expect(mockOpenMCT.selection.on).toHaveBeenCalledWith(
|
|
||||||
'change',
|
|
||||||
jasmine.any(Function)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user