mirror of
https://github.com/nasa/openmct.git
synced 2025-07-04 05:53:23 +00:00
Compare commits
47 Commits
new-tutori
...
time-api
Author | SHA1 | Date | |
---|---|---|---|
be476aff43 | |||
d62a3ca494 | |||
d77ea6c024 | |||
fae7cd69da | |||
80432710e0 | |||
7dd5da8993 | |||
95ac304afb | |||
80dc5a13b8 | |||
b5abe6527b | |||
616aab4a2d | |||
12e693941c | |||
59c61e72b8 | |||
3ce954c68c | |||
df0d4dff6f | |||
a867cd6be0 | |||
b48dd4b63b | |||
35c457b7fb | |||
6870055033 | |||
5f968b50f8 | |||
251438eefd | |||
c07a372c6e | |||
f666a7ca09 | |||
c27b37918a | |||
67eab82bd1 | |||
8d86ca05da | |||
4d6a0d4931 | |||
4ae35576a5 | |||
af622599a5 | |||
12d1302138 | |||
09419398e9 | |||
b69e03368a | |||
4e457f1cf0 | |||
4196da9f52 | |||
a59177447b | |||
529abcc4b0 | |||
1cb5dd021f | |||
399b745084 | |||
f09a76e23b | |||
16b6a70e22 | |||
1a7260cc14 | |||
8a75381a3d | |||
c394fe9287 | |||
06f4a955b5 | |||
a2bf92db97 | |||
784114e256 | |||
d1e7e7894e | |||
65bf38d5e6 |
558
API.md
558
API.md
@ -1,76 +1,150 @@
|
|||||||
# Open MCT API
|
# Building Applications With Open MCT
|
||||||
|
|
||||||
The Open MCT framework public api can be utilized by building the application
|
## Scope and purpose of this document
|
||||||
(`gulp install`) and then copying the file from `dist/main.js` to your
|
|
||||||
directory of choice.
|
|
||||||
|
|
||||||
Open MCT supports AMD, CommonJS, and loading via a script tag; it's easy to use
|
This document is intended to serve as a reference for developing an application
|
||||||
in your project. The [`openmct`]{@link module:openmct} module is exported
|
based on Open MCT. It will provide details of the API functions necessary to extend the
|
||||||
via AMD and CommonJS, and is also exposed as `openmct` in the global scope
|
Open MCT platform meet common use cases such as integrating with a telemetry source.
|
||||||
if loaded via a script tag.
|
|
||||||
|
|
||||||
## Overview
|
The best place to start is with the [Open MCT Tutorials](https://github.com/nasa/openmct-tutorial).
|
||||||
|
These will walk you through the process of getting up and running with Open MCT,
|
||||||
|
as well as addressing some common developer use cases.
|
||||||
|
|
||||||
Open MCT's goal is to allow you to browse, create, edit, and visualize all of
|
## Building From Source
|
||||||
the domain knowledge you need on a daily basis.
|
|
||||||
|
|
||||||
To do this, the main building block provided by Open MCT is the _domain object_.
|
The latest version of Open MCT is available from [our GitHub repository](https://github.com/nasa/openmct).
|
||||||
The temperature sensor on the starboard solar panel,
|
If you have `git`, and `node` installed, you can build Open MCT with the commands
|
||||||
an overlay plot comparing the results of all temperature sensor,
|
```
|
||||||
the command dictionary for a spacecraft,
|
git clone https://github.com/nasa/openmct.git
|
||||||
the individual commands in that dictionary, your "my documents" folder:
|
cd openmct
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
These commands will fetch the Open MCT source from our GitHub repository, and build
|
||||||
|
a minified version that can be included in your application. The output of the
|
||||||
|
build process is placed in a `dist` folder under the openmct source directory,
|
||||||
|
which can be copied out to another location as needed. The contents of this
|
||||||
|
folder will include a minified javascript file named `openmct.js` as well as
|
||||||
|
assets such as html, css, and images necessary for the UI.
|
||||||
|
|
||||||
|
## Starting an Open MCT application
|
||||||
|
|
||||||
|
To start a minimally functional Open MCT application, it is necessary to include
|
||||||
|
the Open MCT distributable, enable some basic plugins, and bootstrap the application.
|
||||||
|
The tutorials walk through the process of getting Open MCT up and running from scratch,
|
||||||
|
but provided below is a minimal HTML template that includes Open MCT, installs
|
||||||
|
some basic plugins, and bootstraps the application. It assumes that Open MCT is
|
||||||
|
installed under an `openmct` subdirectory, as described in [Building From Source](#building-from-source).
|
||||||
|
|
||||||
|
This approach includes openmct using a simple script tag, resulting in a global
|
||||||
|
variable named `openmct`. This `openmct` object is used subsequently to make API
|
||||||
|
calls.
|
||||||
|
|
||||||
|
Open MCT is packaged as a UMD (Universal Module Definition) module, so common
|
||||||
|
script loaders are also supported.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Open MCT</title>
|
||||||
|
<script src="openmct.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script>
|
||||||
|
openmct.setAssetPath('openmct/dist');
|
||||||
|
openmct.install(openmct.plugins.LocalStorage());
|
||||||
|
openmct.install(openmct.plugins.MyItems());
|
||||||
|
openmct.install(openmct.plugins.UTCTimeSystem());
|
||||||
|
openmct.install(openmct.plugins.Espresso());
|
||||||
|
openmct.start();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
The Open MCT library included above requires certain assets such as html templates,
|
||||||
|
images, and css. If you installed Open MCT from GitHub as described in the section
|
||||||
|
on [Building from Source](#building-from-source) then these assets will have been
|
||||||
|
downloaded along with the Open MCT javascript library. You can specify the
|
||||||
|
location of these assets by calling `openmct.setAssetPath()`. Typically this will
|
||||||
|
be the same location as the `openmct.js` library is included from.
|
||||||
|
|
||||||
|
There are some plugins bundled with the application that provide UI, persistence,
|
||||||
|
and other default configuration which are necessary to be able to do anything with
|
||||||
|
the application initially. Any of these plugins can, in principle, be replaced with a custom
|
||||||
|
plugin. The included plugins are documented in the [Included Plugins](#included-plugins)
|
||||||
|
section.
|
||||||
|
|
||||||
|
## Plugins
|
||||||
|
|
||||||
|
### Defining and Installing a New Plugin
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
openmct.install(function install(openmctAPI) {
|
||||||
|
// Do things here
|
||||||
|
// ...
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
New plugins are installed in Open MCT by calling `openmct.install`, and providing
|
||||||
|
a plugin installation function. This function will be invoked on application
|
||||||
|
startup with one parameter - the openmct API object. A common approach used in
|
||||||
|
the Open MCT codebase is to define a plugin as a function that returns this
|
||||||
|
installation function. This allows configuration to be specified when the plugin is included.
|
||||||
|
|
||||||
|
eg.
|
||||||
|
```javascript
|
||||||
|
openmct.install(openmct.plugins.Elasticsearch("http://localhost:8002/openmct"));
|
||||||
|
```
|
||||||
|
This approach can be seen in all of the [plugins provided with Open MCT](https://github.com/nasa/openmct/blob/master/src/plugins/plugins.js).
|
||||||
|
|
||||||
|
## Domain Objects and Identifiers
|
||||||
|
|
||||||
|
_Domain Objects_ are the basic entities that represent domain knowledge in Open MCT.
|
||||||
|
The temperature sensor on a solar panel, an overlay plot comparing
|
||||||
|
the results of all temperature sensors, the command dictionary for a spacecraft,
|
||||||
|
the individual commands in that dictionary, the "My Items" folder:
|
||||||
All of these things are domain objects.
|
All of these things are domain objects.
|
||||||
|
|
||||||
Domain objects have Types, so a specific instrument temperature sensor is a
|
A _Domain Object_ is simply a javascript object with some standard attributes.
|
||||||
"Telemetry Point," and turning on a drill for a certain duration of time is
|
An example of a _Domain Object_ is the "My Items" object which is a folder in
|
||||||
an "Activity". Types allow you to form an ontology of knowledge and provide
|
which a user can persist any objects that they create. The My Items object
|
||||||
an abstraction for grouping, visualizing, and interpreting data.
|
looks like this:
|
||||||
|
|
||||||
And then we have Views. Views allow you to visualize domain objects. Views can
|
```javascript
|
||||||
apply to specific domain objects; they may also apply to certain types of
|
{
|
||||||
domain objects, or they may apply to everything. Views are simply a method
|
identifier: {
|
||||||
of visualizing domain objects.
|
namespace: ""
|
||||||
|
key: "mine"
|
||||||
Regions allow you to specify what views are displayed for specific types of
|
}
|
||||||
domain objects in response to different user actions. For instance, you may
|
name:"My Items",
|
||||||
want to display a different view while editing, or you may want to update the
|
type:"folder",
|
||||||
toolbar display when objects are selected. Regions allow you to map views to
|
location:"ROOT",
|
||||||
specific user actions.
|
composition: []
|
||||||
|
}
|
||||||
Domain objects can be mutated and persisted, developers can create custom
|
|
||||||
actions and apply them to domain objects, and many more things can be done.
|
|
||||||
For more information, read on!
|
|
||||||
|
|
||||||
## Running Open MCT
|
|
||||||
|
|
||||||
Once the [`openmct`](@link module:openmct) module has been loaded, you can
|
|
||||||
simply invoke [`start`]{@link module:openmct.MCT#start} to run Open MCT:
|
|
||||||
|
|
||||||
|
|
||||||
```
|
|
||||||
openmct.start();
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Generally, however, you will want to configure Open MCT by adding plugins
|
### Object Attributes
|
||||||
before starting it. It is important to install plugins and configure Open MCT
|
|
||||||
_before_ calling [`start`]{@link module:openmct.MCT#start}; Open MCT is not
|
|
||||||
designed to be reconfigured once started.
|
|
||||||
|
|
||||||
## Configuring Open MCT
|
The main attributes to note are the `identifier`, and `type` attributes.
|
||||||
|
* `identifier`: A composite key that provides a universally unique identifier for
|
||||||
|
this object. The `namespace` and `key` are used to identify the object. The `key`
|
||||||
|
must be unique within the namespace.
|
||||||
|
* `type`: All objects in Open MCT have a type. Types allow you to form an
|
||||||
|
ontology of knowledge and provide an abstraction for grouping, visualizing, and
|
||||||
|
interpreting data. Details on how to define a new object type are provided below.
|
||||||
|
|
||||||
The [`openmct`]{@link module:openmct} module (more specifically, the
|
Open MCT uses a number of builtin types. Typically you are going to want to
|
||||||
[`MCT`]{@link module:openmct.MCT} class, of which `openmct` is an instance)
|
define your own if extending Open MCT.
|
||||||
exposes a variety of methods to allow the application to be configured,
|
|
||||||
extended, and customized before running.
|
|
||||||
|
|
||||||
Short examples follow; see the linked documentation for further details.
|
### Domain Object Types
|
||||||
|
|
||||||
### Adding Domain Object Types
|
Custom types may be registered via the `addType` function on the opencmt Type
|
||||||
|
registry.
|
||||||
|
|
||||||
Custom types may be registered via
|
eg.
|
||||||
[`openmct.types`]{@link module:openmct.MCT#types}:
|
```javascript
|
||||||
|
|
||||||
```
|
|
||||||
openmct.types.addType('my-type', {
|
openmct.types.addType('my-type', {
|
||||||
label: "My Type",
|
label: "My Type",
|
||||||
description: "This is a type that I added!",
|
description: "This is a type that I added!",
|
||||||
@ -78,66 +152,98 @@ openmct.types.addType('my-type', {
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
### Adding Views
|
The `addType` function accepts two arguments:
|
||||||
|
* A `string` key identifying the type. This key is used when specifying a type
|
||||||
|
for an object.
|
||||||
|
* An object type specification. An object type definition supports the following
|
||||||
|
attributes
|
||||||
|
* `label`: a `string` naming this object type
|
||||||
|
* `description`: a `string` specifying a longer-form description of this type
|
||||||
|
* `initialize`: a `function` which initializes the model for new domain objects
|
||||||
|
of this type. This can be used for setting default values on an object when
|
||||||
|
it is instantiated.
|
||||||
|
* `creatable`: A `boolean` indicating whether users should be allowed to create
|
||||||
|
this type (default: `false`). This will determine whether the type appears
|
||||||
|
in the `Create` menu.
|
||||||
|
* `cssClass`: A `string` specifying a CSS class to apply to each representation
|
||||||
|
of this object. This is used for specifying an icon to appear next to each
|
||||||
|
object of this type.
|
||||||
|
|
||||||
Custom views may be registered based on the region in the application
|
The [Open MCT Tutorials](https://github.com/openmct/openmct-tutorial) provide a
|
||||||
where they should appear:
|
step-by-step examples of writing code for Open MCT that includes a [section on
|
||||||
|
defining a new object type](https://github.com/nasa/openmct-tutorial#step-3---providing-objects).
|
||||||
|
|
||||||
* [`openmct.mainViews`]{@link module:openmct.MCT#mainViews} is a registry
|
## Root Objects
|
||||||
of views of domain objects which should appear in the main viewing area.
|
|
||||||
* [`openmct.inspectors`]{@link module:openmct.MCT#inspectors} is a registry
|
|
||||||
of views of domain objects and/or active selections, which should appear in
|
|
||||||
the Inspector.
|
|
||||||
* [`openmct.toolbars`]{@link module:openmct.MCT#toolbars} is a registry
|
|
||||||
of views of domain objects and/or active selections, which should appear in
|
|
||||||
the toolbar area while editing.
|
|
||||||
* [`openmct.indicators`]{@link module:openmct.MCT#inspectors} is a registry
|
|
||||||
of views which should appear in the status area of the application.
|
|
||||||
|
|
||||||
Example:
|
In many cases, you'd like a certain object (or a certain hierarchy of objects)
|
||||||
|
to be accessible from the top level of the application (the tree on the left-hand
|
||||||
|
side of Open MCT.) For example, it is typical to expose a telemetry dictionary
|
||||||
|
as a hierarchy of telemetry-providing domain objects in this fashion.
|
||||||
|
|
||||||
```
|
To do so, use the `addRoot` method of the object API.
|
||||||
openmct.mainViews.addProvider({
|
|
||||||
canView: function (domainObject) {
|
eg.
|
||||||
return domainObject.type === 'my-type';
|
```javascript
|
||||||
},
|
openmct.objects.addRoot({
|
||||||
view: function (domainObject) {
|
namespace: "my-namespace",
|
||||||
return new MyView(domainObject);
|
key: "my-key"
|
||||||
}
|
});
|
||||||
});
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Adding a Root-level Object
|
The `addRoot` function takes a single [object identifier](#domain-objects-and-identifiers)
|
||||||
|
as an argument.
|
||||||
In many cases, you'd like a certain object (or a certain hierarchy of
|
|
||||||
objects) to be accessible from the top level of the application (the
|
|
||||||
tree on the left-hand side of Open MCT.) It is typical to expose a telemetry
|
|
||||||
dictionary as a hierarchy of telemetry-providing domain objects in this
|
|
||||||
fashion.
|
|
||||||
|
|
||||||
To do so, use the [`addRoot`]{@link module:openmct.ObjectAPI#addRoot} method
|
|
||||||
of the [object API]{@link module:openmct.ObjectAPI}:
|
|
||||||
|
|
||||||
```
|
|
||||||
openmct.objects.addRoot({ key: "my-key", namespace: "my-namespace" });
|
|
||||||
```
|
|
||||||
|
|
||||||
Root objects are loaded just like any other objects, i.e. via an object
|
Root objects are loaded just like any other objects, i.e. via an object
|
||||||
provider.
|
provider.
|
||||||
|
|
||||||
### Adding Composition Providers
|
## Object Providers
|
||||||
|
|
||||||
The "composition" of a domain object is the list of objects it contains,
|
An Object Provider is used to build _Domain Objects_, typically retrieved from
|
||||||
as shown (for example) in the tree for browsing. Open MCT provides a
|
some source such as a persistence store or telemetry dictionary. In order to
|
||||||
|
integrate telemetry from a new source an object provider will need to be created
|
||||||
|
that can build objects representing telemetry points exposed by the telemetry
|
||||||
|
source. The API call to define a new object provider is fairly straightforward.
|
||||||
|
Here's a very simple example:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
openmct.objects.addProvider('example.namespace', {
|
||||||
|
get: function (identifier) {
|
||||||
|
return Promise.resolve({
|
||||||
|
identifier: identifier,
|
||||||
|
name: 'Example Object',
|
||||||
|
type: 'example-object-type'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
The `addProvider` function takes two arguments:
|
||||||
|
|
||||||
|
* `namespace`: A `string` representing the namespace that this object provider
|
||||||
|
will provide objects for.
|
||||||
|
* `provider`: An `object` with a single function, `get`. This function accepts an
|
||||||
|
[Identifier](#domain-objects-and-identifiers) for the object to be provided.
|
||||||
|
It is expected that the `get` function will return a
|
||||||
|
[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
|
||||||
|
that resolves with the object being requested.
|
||||||
|
|
||||||
|
In future, object providers will support other methods to enable other operations
|
||||||
|
with persistence stores, such as creating, updating, and deleting objects.
|
||||||
|
|
||||||
|
## Composition Providers
|
||||||
|
|
||||||
|
The _composition_ of a domain object is the list of objects it contains, as shown
|
||||||
|
(for example) in the tree for browsing. Open MCT provides a
|
||||||
[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.
|
||||||
|
|
||||||
For instance, you may want to populate a hierarchy under a custom root-level
|
### Adding Composition Providers
|
||||||
object based on the contents of a telemetry dictionary.
|
|
||||||
To do this, you can add a new CompositionProvider:
|
|
||||||
|
|
||||||
```
|
You may want to populate a hierarchy under a custom root-level object based on
|
||||||
|
the contents of a telemetry dictionary. To do this, you can add a new
|
||||||
|
Composition Provider:
|
||||||
|
|
||||||
|
```javascript
|
||||||
openmct.composition.addProvider({
|
openmct.composition.addProvider({
|
||||||
appliesTo: function (domainObject) {
|
appliesTo: function (domainObject) {
|
||||||
return domainObject.type === 'my-type';
|
return domainObject.type === 'my-type';
|
||||||
@ -147,20 +253,27 @@ openmct.composition.addProvider({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
The `addProvider` function accepts a Composition Provider object as its sole
|
||||||
|
argument. A Composition Provider is a javascript object exposing two functions:
|
||||||
|
* `appliesTo`: A `function` that accepts a `domainObject` argument, and returns
|
||||||
|
a `boolean` value indicating whether this composition provider applies to the
|
||||||
|
given object.
|
||||||
|
* `load`: A `function` that accepts a `domainObject` as an argument, and returns
|
||||||
|
a `Promise` that resolves with an array of [Identifier](#domain-objects-and-identifiers).
|
||||||
|
These identifiers will be used to fetch Domain Objects from an [Object Provider](#object-provider)
|
||||||
|
|
||||||
#### Default Composition Provider
|
### Default Composition Provider
|
||||||
|
|
||||||
The default composition provider applies to any domain object with
|
The default composition provider applies to any domain object with a `composition`
|
||||||
a `composition` property. The value of `composition` should be an
|
property. The value of `composition` should be an array of identifiers, e.g.:
|
||||||
array of identifiers, e.g.:
|
|
||||||
|
|
||||||
```js
|
```javascript
|
||||||
var domainObject = {
|
var domainObject = {
|
||||||
name: "My Object",
|
name: "My Object",
|
||||||
type: 'folder',
|
type: 'folder',
|
||||||
composition: [
|
composition: [
|
||||||
{
|
{
|
||||||
key: '412229c3-922c-444b-8624-736d85516247',
|
id: '412229c3-922c-444b-8624-736d85516247',
|
||||||
namespace: 'foo'
|
namespace: 'foo'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -171,169 +284,146 @@ var domainObject = {
|
|||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
### Adding Telemetry Providers
|
## Telemetry Providers
|
||||||
|
|
||||||
When connecting to a new telemetry source, you will want to register a new
|
When connecting to a new telemetry source, you will need to register a new
|
||||||
[telemetry provider]{@link module:openmct.TelemetryAPI~TelemetryProvider}
|
_Telemetry Provider_. A _Telemetry Provider_ retrieves telemetry data from some telemetry
|
||||||
with the [telemetry API]{@link module:openmct.TelemetryAPI#addProvider}:
|
source, and exposes them in a way that can be used by Open MCT. A telemetry
|
||||||
|
provider typically can support a one off __request__ for a batch of telemetry data,
|
||||||
|
or it can provide the ability to __subscribe__ to receive new telemetry data when
|
||||||
|
it becomes available, or both.
|
||||||
|
|
||||||
```
|
```javascript
|
||||||
openmct.telemetry.addProvider({
|
openmct.telemetry.addProvider({
|
||||||
canProvideTelemetry: function (domainObject) {
|
supportsRequest: function (domainObject) {
|
||||||
return domainObject.type === 'my-type';
|
//...
|
||||||
},
|
},
|
||||||
properties: function (domainObject) {
|
supportsSubscribe: function (domainObject) {
|
||||||
return [
|
//...
|
||||||
{ key: 'value', name: "Temperature", units: "degC" },
|
|
||||||
{ key: 'time', name: "UTC" }
|
|
||||||
];
|
|
||||||
},
|
},
|
||||||
request: function (domainObject, options) {
|
request: function (domainObject, options) {
|
||||||
var telemetryId = domainObject.myTelemetryId;
|
//...
|
||||||
return myAdapter.request(telemetryId, options.start, options.end);
|
|
||||||
},
|
},
|
||||||
subscribe: function (domainObject, callback) {
|
subscribe: function (domainObject, callback, options) {
|
||||||
var telemetryId = domainObject.myTelemetryId;
|
//...
|
||||||
myAdapter.subscribe(telemetryId, callback);
|
|
||||||
return myAdapter.unsubscribe.bind(myAdapter, telemetryId, callback);
|
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
A telemetry provider is an object with the following functions defined:
|
||||||
|
|
||||||
|
* `supportsRequest`: An __optional__ `function` that accepts a
|
||||||
|
[Domain Object](#domain-objects-and-identifiers) and returns a `boolean` value
|
||||||
|
indicating whether or not this provider supports telemetry requests for the
|
||||||
|
given object. If this returns `true` then a `request` function must be defined.
|
||||||
|
* `supportsSubscribe`: An __optional__ `function` that accepts a
|
||||||
|
[Domain Object](#domain-objects-and-identifiers) and returns a `boolean` value
|
||||||
|
indicating whether or not this provider supports telemetry subscriptions. If this
|
||||||
|
returns `true` then a `subscribe` function must also be defined. As with `request`,
|
||||||
|
the return value will typically be conditional, and based on attributes of
|
||||||
|
`domainObject` such as its identifier.
|
||||||
|
* `request`: A `function` that returns a `Promise` that will resolve with an `Array`
|
||||||
|
of telemetry in a single query. This function accepts as arguments a
|
||||||
|
[Domain Object](#domain-objects-and-identifiers) and an object containing some
|
||||||
|
[request options](#telemetry-requests).
|
||||||
|
* `subscribe`: A `function` that accepts a [Domain Object](#domain-objects-and-identifiers),
|
||||||
|
a callback `function`, and a [telemetry request](#telemetry-requests). The
|
||||||
|
callback is invoked whenever telemetry is available, and
|
||||||
|
|
||||||
|
|
||||||
The implementations for `request` and `subscribe` can vary depending on the
|
The implementations for `request` and `subscribe` can vary depending on the
|
||||||
nature of the endpoint which will provide telemetry. In the example above,
|
nature of the endpoint which will provide telemetry. In the example above,
|
||||||
it is assumed that `myAdapter` contains the specific implementations
|
it is assumed that `myAdapter` contains the implementation details
|
||||||
(HTTP requests, WebSocket connections, etc.) associated with some telemetry
|
(such as HTTP requests, WebSocket connections, etc.) associated with some telemetry
|
||||||
source.
|
source.
|
||||||
|
|
||||||
## Using Open MCT
|
For a step-by-step guide to building a telemetry adapter, please see the
|
||||||
|
[Open MCT Tutorials](https://github.com/larkin/openmct-tutorial).
|
||||||
|
|
||||||
When implementing new features, it is useful and sometimes necessary to
|
### Telemetry Requests
|
||||||
utilize functionality exposed by Open MCT.
|
Telemetry requests support time bounded queries. A call to a _Telemetry Provider_'s
|
||||||
|
`request` function will include an `options` argument. These are simply javascript
|
||||||
### Retrieving Composition
|
objects with attributes for the request parameters. An example of a telemetry
|
||||||
|
request object with a start and end time is included below:
|
||||||
To limit which objects are loaded at any given time, the composition of
|
```javascript
|
||||||
a domain object must be requested asynchronously:
|
{
|
||||||
|
start: 1487981997240,
|
||||||
```
|
end: 1487982897240
|
||||||
openmct.composition(myObject).load().then(function (childObjects) {
|
}
|
||||||
childObjects.forEach(doSomething);
|
|
||||||
});
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Support Common Gestures
|
### Telemetry Data
|
||||||
|
|
||||||
Custom views may also want to support common gestures using the
|
Telemetry data is provided to Open MCT by _[Telemetry Providers](#telemetry-providers)_
|
||||||
[gesture API]{@link module:openmct.GestureAPI}. For instance, to make
|
in the form of javascript objects. A collection of telemetry values (for example,
|
||||||
a view (or part of a view) selectable:
|
retrieved in response to a `request`) is represented by an `Array` of javascript
|
||||||
|
objects. These telemetry javascript objects are simply key value pairs.
|
||||||
|
|
||||||
```
|
Typically a telemetry datum will have some timestamp associated with it. This
|
||||||
openmct.gestures.selectable(myHtmlElement, myDomainObject);
|
time stamp should have a key that corresponds to some time system supported by
|
||||||
|
Open MCT. If the `UTCTimeSystem` plugin is installed, then the key `utc` can be used.
|
||||||
|
|
||||||
|
An example of a telemetry provider request function that returns a collection of
|
||||||
|
mock telemtry data is below:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
openmct.telemetry.addProvider({
|
||||||
|
supportsRequest: function (domainObject) {
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
request: function (domainObject, options) {
|
||||||
|
return Promise.resolve([
|
||||||
|
{
|
||||||
|
'utc': Date.now() - 2000,
|
||||||
|
'value': 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'utc': Date.now() - 1000,
|
||||||
|
'value': 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'utc': Date.now(),
|
||||||
|
'value': 3,
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
### Working with Domain Objects
|
## Included Plugins
|
||||||
|
|
||||||
The [object API]{@link module:openmct.ObjectAPI} provides useful methods
|
|
||||||
for working with domain objects.
|
|
||||||
|
|
||||||
To make changes to a domain object, use the
|
|
||||||
[`mutate`]{@link module:openmct.ObjectAPI#mutate} method:
|
|
||||||
|
|
||||||
```
|
|
||||||
openmct.objects.mutate(myDomainObject, "name", "New name!");
|
|
||||||
```
|
|
||||||
|
|
||||||
Making modifications in this fashion allows other usages of the domain
|
|
||||||
object to remain up to date using the
|
|
||||||
[`observe`]{@link module:openmct.ObjectAPI#observe} method:
|
|
||||||
|
|
||||||
```
|
|
||||||
openmct.objects.observe(myDomainObject, "name", function (newName) {
|
|
||||||
myLabel.textContent = newName;
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Using Telemetry
|
|
||||||
|
|
||||||
Very often in Open MCT, you wish to work with telemetry data (for instance,
|
|
||||||
to display it in a custom visualization.)
|
|
||||||
|
|
||||||
|
|
||||||
### Synchronizing with the Time Conductor
|
|
||||||
|
|
||||||
Views which wish to remain synchronized with the state of Open MCT's
|
|
||||||
time conductor should utilize
|
|
||||||
[`openmct.conductor`]{@link module:openmct.TimeConductor}:
|
|
||||||
|
|
||||||
```
|
|
||||||
openmct.conductor.on('bounds', function (newBounds) {
|
|
||||||
requestTelemetry(newBounds.start, newBounds.end).then(displayTelemetry);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Plugins
|
|
||||||
|
|
||||||
While you can register new features with Open MCT directly, it is generally
|
|
||||||
more useful to package these as a plugin. A plugin is a function that takes
|
|
||||||
[`openmct`]{@link module:openmct} as an argument, and performs configuration
|
|
||||||
upon `openmct` when invoked.
|
|
||||||
|
|
||||||
### Installing Plugins
|
|
||||||
|
|
||||||
To install plugins, use the [`install`]{@link module:openmct.MCT#install}
|
|
||||||
method:
|
|
||||||
|
|
||||||
```
|
|
||||||
openmct.install(myPlugin);
|
|
||||||
```
|
|
||||||
|
|
||||||
The plugin will be invoked to configure Open MCT before it is started.
|
|
||||||
|
|
||||||
### Included Plugins
|
|
||||||
|
|
||||||
Open MCT is packaged along with a few general-purpose plugins:
|
Open MCT is packaged along with a few general-purpose plugins:
|
||||||
|
|
||||||
* `openmct.plugins.CouchDB` is an adapter for using CouchDB for persistence
|
* `openmct.plugins.CouchDB` is an adapter for using CouchDB for persistence
|
||||||
of user-created objects. This is a constructor that takes the URL for the
|
of user-created objects. This is a constructor that takes the URL for the
|
||||||
CouchDB database as a parameter, e.g.
|
CouchDB database as a parameter, e.g.
|
||||||
`openmct.install(new openmct.plugins.CouchDB('http://localhost:5984/openmct'))`
|
```javascript
|
||||||
|
openmct.install(openmct.plugins.CouchDB('http://localhost:5984/openmct'))
|
||||||
|
```
|
||||||
* `openmct.plugins.Elasticsearch` is an adapter for using Elasticsearch for
|
* `openmct.plugins.Elasticsearch` is an adapter for using Elasticsearch for
|
||||||
persistence of user-created objects. This is a
|
persistence of user-created objects. This is a
|
||||||
constructor that takes the URL for the Elasticsearch instance as a
|
constructor that takes the URL for the Elasticsearch instance as a
|
||||||
parameter, e.g.
|
parameter. eg.
|
||||||
`openmct.install(new openmct.plugins.CouchDB('http://localhost:9200'))`.
|
```javascript
|
||||||
Domain objects will be indexed at `/mct/domain_object`.
|
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.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.
|
||||||
* `openmct.plugins.myItems` adds a top-level folder named "My Items"
|
* `openmct.plugins.MyItems` adds a top-level folder named "My Items"
|
||||||
when the application is first started, providing a place for a
|
when the application is first started, providing a place for a
|
||||||
user to store created items.
|
user to store created items.
|
||||||
* `openmct.plugins.utcTimeSystem` provides support for using the time
|
* `openmct.plugins.UTCTimeSystem` provides a default time system for Open MCT.
|
||||||
conductor with UTC time.
|
|
||||||
|
|
||||||
Generally, you will want to either install these plugins, or install
|
Generally, you will want to either install these plugins, or install
|
||||||
different plugins that provide persistence and an initial folder
|
different plugins that provide persistence and an initial folder
|
||||||
hierarchy. Installation is as described [above](#installing-plugins):
|
hierarchy.
|
||||||
|
|
||||||
|
eg.
|
||||||
|
```javascript
|
||||||
|
openmct.install(openmct.plugins.LocalStorage());
|
||||||
|
openmct.install(openmct.plugins.MyItems());
|
||||||
```
|
```
|
||||||
openmct.install(openmct.plugins.localStorage);
|
|
||||||
openmct.install(openmct.plugins.myItems);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Writing Plugins
|
|
||||||
|
|
||||||
Plugins configure Open MCT, and should utilize the
|
|
||||||
[`openmct`]{@link module:openmct} module to do so, as summarized above in
|
|
||||||
"Configuring Open MCT" and "Using Open MCT" above.
|
|
||||||
|
|
||||||
### Distributing Plugins
|
|
||||||
|
|
||||||
Hosting or downloading plugins is outside of the scope of this documentation.
|
|
||||||
We recommend distributing plugins as UMD modules which export a single
|
|
||||||
function.
|
|
||||||
|
|
||||||
|
@ -2261,7 +2261,7 @@ The platform understands the following policy categories (specifiable as the
|
|||||||
|
|
||||||
* `action`: Determines whether or not a given action is allowable. The candidate
|
* `action`: Determines whether or not a given action is allowable. The candidate
|
||||||
argument here is an Action; the context is its action context object.
|
argument here is an Action; the context is its action context object.
|
||||||
* `composition`: Determines whether or not domain objects of a given type (first argument, `parentType`) can contain a given object (second argument, `child`).
|
* `composition`: Determines whether or not a given domain object(first argument, `parent`) can contain a candidate child object (second argument, `child`).
|
||||||
* `view`: Determines whether or not a view is applicable for a domain object.
|
* `view`: Determines whether or not a view is applicable for a domain object.
|
||||||
The candidate argument is the view's extension definition; the context argument
|
The candidate argument is the view's extension definition; the context argument
|
||||||
is the `DomainObject` to be viewed.
|
is the `DomainObject` to be viewed.
|
||||||
|
@ -31,10 +31,25 @@ define(
|
|||||||
|
|
||||||
var firstObservedTime = Date.now(),
|
var firstObservedTime = Date.now(),
|
||||||
images = [
|
images = [
|
||||||
"http://www.nasa.gov/393811main_Palomar_ao_bouchez_10s_after_impact_4x3_946-710.png",
|
"https://www.hq.nasa.gov/alsj/a16/AS16-117-18731.jpg",
|
||||||
"http://www.nasa.gov/393821main_Palomar_ao_bouchez_15s_after_impact_4x3_946-710.png",
|
"https://www.hq.nasa.gov/alsj/a16/AS16-117-18732.jpg",
|
||||||
"http://www.nasa.gov/images/content/393801main_CfhtVeillet2_4x3_516-387.jpg",
|
"https://www.hq.nasa.gov/alsj/a16/AS16-117-18733.jpg",
|
||||||
"http://www.nasa.gov/images/content/392790main_1024_768_GeminiNorth_NightBeforeImpact_946-710.jpg"
|
"https://www.hq.nasa.gov/alsj/a16/AS16-117-18734.jpg",
|
||||||
|
"https://www.hq.nasa.gov/alsj/a16/AS16-117-18735.jpg",
|
||||||
|
"https://www.hq.nasa.gov/alsj/a16/AS16-117-18736.jpg",
|
||||||
|
"https://www.hq.nasa.gov/alsj/a16/AS16-117-18737.jpg",
|
||||||
|
"https://www.hq.nasa.gov/alsj/a16/AS16-117-18738.jpg",
|
||||||
|
"https://www.hq.nasa.gov/alsj/a16/AS16-117-18739.jpg",
|
||||||
|
"https://www.hq.nasa.gov/alsj/a16/AS16-117-18740.jpg",
|
||||||
|
"https://www.hq.nasa.gov/alsj/a16/AS16-117-18741.jpg",
|
||||||
|
"https://www.hq.nasa.gov/alsj/a16/AS16-117-18742.jpg",
|
||||||
|
"https://www.hq.nasa.gov/alsj/a16/AS16-117-18743.jpg",
|
||||||
|
"https://www.hq.nasa.gov/alsj/a16/AS16-117-18744.jpg",
|
||||||
|
"https://www.hq.nasa.gov/alsj/a16/AS16-117-18745.jpg",
|
||||||
|
"https://www.hq.nasa.gov/alsj/a16/AS16-117-18746.jpg",
|
||||||
|
"https://www.hq.nasa.gov/alsj/a16/AS16-117-18747.jpg",
|
||||||
|
"https://www.hq.nasa.gov/alsj/a16/AS16-117-18748.jpg"
|
||||||
|
|
||||||
].map(function (url, index) {
|
].map(function (url, index) {
|
||||||
return {
|
return {
|
||||||
timestamp: firstObservedTime + 1000 * index,
|
timestamp: firstObservedTime + 1000 * index,
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
define(['../../../platform/features/conductor/core/src/timeSystems/LocalClock'], function (LocalClock) {
|
define(['../../../src/plugins/utcTimeSystem/LocalClock'], function (LocalClock) {
|
||||||
/**
|
/**
|
||||||
* @implements TickSource
|
* @implements TickSource
|
||||||
* @constructor
|
* @constructor
|
||||||
@ -28,14 +28,12 @@ define(['../../../platform/features/conductor/core/src/timeSystems/LocalClock'],
|
|||||||
function LADTickSource ($timeout, period) {
|
function LADTickSource ($timeout, period) {
|
||||||
LocalClock.call(this, $timeout, period);
|
LocalClock.call(this, $timeout, period);
|
||||||
|
|
||||||
this.metadata = {
|
this.key = 'test-lad';
|
||||||
key: 'test-lad',
|
this.mode = 'lad';
|
||||||
mode: 'lad',
|
this.cssClass = 'icon-clock';
|
||||||
cssClass: 'icon-clock',
|
this.label = 'Latest Available Data';
|
||||||
label: 'Latest Available Data',
|
this.name = 'Latest available data';
|
||||||
name: 'Latest available data',
|
this.description = 'Monitor real-time streaming data as it comes in. The Time Conductor and displays will automatically advance themselves based on a UTC clock.';
|
||||||
description: 'Monitor real-time streaming data as it comes in. The Time Conductor and displays will automatically advance themselves based on a UTC clock.'
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
LADTickSource.prototype = Object.create(LocalClock.prototype);
|
LADTickSource.prototype = Object.create(LocalClock.prototype);
|
||||||
|
|
||||||
|
@ -21,10 +21,9 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
define([
|
define([
|
||||||
'../../../platform/features/conductor/core/src/timeSystems/TimeSystem',
|
'../../../src/plugins/utcTimeSystem/LocalClock',
|
||||||
'../../../platform/features/conductor/core/src/timeSystems/LocalClock',
|
|
||||||
'./LADTickSource'
|
'./LADTickSource'
|
||||||
], function (TimeSystem, LocalClock, LADTickSource) {
|
], function (LocalClock, LADTickSource) {
|
||||||
var THIRTY_MINUTES = 30 * 60 * 1000,
|
var THIRTY_MINUTES = 30 * 60 * 1000,
|
||||||
DEFAULT_PERIOD = 1000;
|
DEFAULT_PERIOD = 1000;
|
||||||
|
|
||||||
@ -34,25 +33,20 @@ define([
|
|||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
function LocalTimeSystem ($timeout) {
|
function LocalTimeSystem ($timeout) {
|
||||||
TimeSystem.call(this);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Some metadata, which will be used to identify the time system in
|
* Some metadata, which will be used to identify the time system in
|
||||||
* the UI
|
* the UI
|
||||||
* @type {{key: string, name: string, glyph: string}}
|
* @type {{key: string, name: string, glyph: string}}
|
||||||
*/
|
*/
|
||||||
this.metadata = {
|
this.key = 'local';
|
||||||
'key': 'local',
|
this.name = 'Local';
|
||||||
'name': 'Local',
|
this.cssClass = '\u0043';
|
||||||
'glyph': '\u0043'
|
|
||||||
};
|
|
||||||
|
|
||||||
this.fmts = ['local-format'];
|
this.fmts = ['local-format'];
|
||||||
this.sources = [new LocalClock($timeout, DEFAULT_PERIOD), new LADTickSource($timeout, DEFAULT_PERIOD)];
|
this.sources = [new LocalClock($timeout, DEFAULT_PERIOD), new LADTickSource($timeout, DEFAULT_PERIOD)];
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalTimeSystem.prototype = Object.create(TimeSystem.prototype);
|
|
||||||
|
|
||||||
LocalTimeSystem.prototype.formats = function () {
|
LocalTimeSystem.prototype.formats = function () {
|
||||||
return this.fmts;
|
return this.fmts;
|
||||||
};
|
};
|
||||||
@ -65,6 +59,10 @@ define([
|
|||||||
return this.sources;
|
return this.sources;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
LocalTimeSystem.prototype.isUTCBased = function () {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
LocalTimeSystem.prototype.defaults = function (key) {
|
LocalTimeSystem.prototype.defaults = function (key) {
|
||||||
var now = Math.ceil(Date.now() / 1000) * 1000;
|
var now = Math.ceil(Date.now() / 1000) * 1000;
|
||||||
return {
|
return {
|
||||||
|
@ -92,7 +92,7 @@ define([
|
|||||||
{
|
{
|
||||||
"key":"rems.adapter",
|
"key":"rems.adapter",
|
||||||
"implementation": RemsTelemetryServerAdapter,
|
"implementation": RemsTelemetryServerAdapter,
|
||||||
"depends": ["$q", "$http", "$log", "REMS_WS_URL"]
|
"depends": ["$http", "$log", "REMS_WS_URL"]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"components": [
|
"components": [
|
||||||
|
@ -42,14 +42,12 @@ define(
|
|||||||
* @param REMS_WS_URL The location of the REMS telemetry data.
|
* @param REMS_WS_URL The location of the REMS telemetry data.
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
function RemsTelemetryServerAdapter($q, $http, $log, REMS_WS_URL) {
|
function RemsTelemetryServerAdapter($http, $log, REMS_WS_URL) {
|
||||||
this.localDataURI = module.uri.substring(0, module.uri.lastIndexOf('/') + 1) + LOCAL_DATA;
|
this.localDataURI = module.uri.substring(0, module.uri.lastIndexOf('/') + 1) + LOCAL_DATA;
|
||||||
this.deferreds = {};
|
|
||||||
this.REMS_WS_URL = REMS_WS_URL;
|
this.REMS_WS_URL = REMS_WS_URL;
|
||||||
this.$q = $q;
|
|
||||||
this.$http = $http;
|
this.$http = $http;
|
||||||
this.$log = $log;
|
this.$log = $log;
|
||||||
this.cache = undefined;
|
this.promise = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -65,15 +63,10 @@ define(
|
|||||||
*/
|
*/
|
||||||
RemsTelemetryServerAdapter.prototype.requestHistory = function(request) {
|
RemsTelemetryServerAdapter.prototype.requestHistory = function(request) {
|
||||||
var self = this,
|
var self = this,
|
||||||
id = request.key,
|
id = request.key;
|
||||||
deferred = this.$q.defer();
|
|
||||||
|
|
||||||
function processResponse(response){
|
function processResponse(response){
|
||||||
var data = [];
|
var data = [];
|
||||||
/*
|
|
||||||
* Currently all data is returned for entire history of the mission. Cache response to avoid unnecessary re-queries.
|
|
||||||
*/
|
|
||||||
self.cache = response;
|
|
||||||
/*
|
/*
|
||||||
* History data is organised by Sol. Iterate over sols...
|
* History data is organised by Sol. Iterate over sols...
|
||||||
*/
|
*/
|
||||||
@ -110,17 +103,15 @@ define(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function packageAndResolve(results){
|
function packageAndResolve(results){
|
||||||
deferred.resolve({id: id, values: results});
|
return {id: id, values: results};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
this.$q.when(this.cache || this.$http.get(this.REMS_WS_URL))
|
return (this.promise = this.promise || this.$http.get(this.REMS_WS_URL))
|
||||||
.catch(fallbackToLocal)
|
.catch(fallbackToLocal)
|
||||||
.then(processResponse)
|
.then(processResponse)
|
||||||
.then(filterResults)
|
.then(filterResults)
|
||||||
.then(packageAndResolve);
|
.then(packageAndResolve);
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -132,7 +123,6 @@ define(
|
|||||||
* @returns {Promise | Array<RemsTelemetryValue>} that resolves with an Array of {@link RemsTelemetryValue} objects for the request data key.
|
* @returns {Promise | Array<RemsTelemetryValue>} that resolves with an Array of {@link RemsTelemetryValue} objects for the request data key.
|
||||||
*/
|
*/
|
||||||
RemsTelemetryServerAdapter.prototype.history = function(request) {
|
RemsTelemetryServerAdapter.prototype.history = function(request) {
|
||||||
var id = request.key;
|
|
||||||
return this.requestHistory(request);
|
return this.requestHistory(request);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ module.exports = function(config) {
|
|||||||
// Preprocess matching files before serving them to the browser.
|
// Preprocess matching files before serving them to the browser.
|
||||||
// https://npmjs.org/browse/keyword/karma-preprocessor
|
// https://npmjs.org/browse/keyword/karma-preprocessor
|
||||||
preprocessors: {
|
preprocessors: {
|
||||||
'src/**/src/**/!(*Spec).js': [ 'coverage' ],
|
'src/**/!(*Spec).js': [ 'coverage' ],
|
||||||
'platform/**/src/**/!(*Spec).js': [ 'coverage' ]
|
'platform/**/src/**/!(*Spec).js': [ 'coverage' ]
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -84,11 +84,5 @@ define([
|
|||||||
return new Main().run(defaultRegistry);
|
return new Main().run(defaultRegistry);
|
||||||
});
|
});
|
||||||
|
|
||||||
// For now, install conductor by default
|
|
||||||
openmct.install(openmct.plugins.Conductor({
|
|
||||||
showConductor: false
|
|
||||||
}));
|
|
||||||
|
|
||||||
|
|
||||||
return openmct;
|
return openmct;
|
||||||
});
|
});
|
||||||
|
@ -34,6 +34,7 @@ define([
|
|||||||
"./src/actions/SaveAsAction",
|
"./src/actions/SaveAsAction",
|
||||||
"./src/actions/CancelAction",
|
"./src/actions/CancelAction",
|
||||||
"./src/policies/EditActionPolicy",
|
"./src/policies/EditActionPolicy",
|
||||||
|
"./src/policies/EditPersistableObjectsPolicy",
|
||||||
"./src/policies/EditableLinkPolicy",
|
"./src/policies/EditableLinkPolicy",
|
||||||
"./src/policies/EditableMovePolicy",
|
"./src/policies/EditableMovePolicy",
|
||||||
"./src/policies/EditContextualActionPolicy",
|
"./src/policies/EditContextualActionPolicy",
|
||||||
@ -72,6 +73,7 @@ define([
|
|||||||
SaveAsAction,
|
SaveAsAction,
|
||||||
CancelAction,
|
CancelAction,
|
||||||
EditActionPolicy,
|
EditActionPolicy,
|
||||||
|
EditPersistableObjectsPolicy,
|
||||||
EditableLinkPolicy,
|
EditableLinkPolicy,
|
||||||
EditableMovePolicy,
|
EditableMovePolicy,
|
||||||
EditContextualActionPolicy,
|
EditContextualActionPolicy,
|
||||||
@ -247,6 +249,11 @@ define([
|
|||||||
"category": "action",
|
"category": "action",
|
||||||
"implementation": EditActionPolicy
|
"implementation": EditActionPolicy
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"category": "action",
|
||||||
|
"implementation": EditPersistableObjectsPolicy,
|
||||||
|
"depends": ["openmct"]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"category": "action",
|
"category": "action",
|
||||||
"implementation": EditContextualActionPolicy,
|
"implementation": EditContextualActionPolicy,
|
||||||
|
@ -60,11 +60,9 @@ define(
|
|||||||
policyService = this.policyService;
|
policyService = this.policyService;
|
||||||
|
|
||||||
function validateLocation(parent) {
|
function validateLocation(parent) {
|
||||||
var parentType = parent &&
|
return parent && policyService.allow(
|
||||||
parent.getCapability('type');
|
|
||||||
return parentType && policyService.allow(
|
|
||||||
"composition",
|
"composition",
|
||||||
parentType,
|
parent,
|
||||||
domainObject
|
domainObject
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,59 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2016, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
define(
|
||||||
|
['../../../../../src/api/objects/object-utils'],
|
||||||
|
function (objectUtils) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Policy that prevents editing of any object from a provider that does not
|
||||||
|
* support persistence (ie. the 'save' operation). Editing is prevented
|
||||||
|
* as a subsequent save would fail, causing the loss of a user's changes.
|
||||||
|
* @param openmct
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function EditPersistableObjectsPolicy(openmct) {
|
||||||
|
this.openmct = openmct;
|
||||||
|
}
|
||||||
|
|
||||||
|
EditPersistableObjectsPolicy.prototype.allow = function (action, context) {
|
||||||
|
var identifier;
|
||||||
|
var provider;
|
||||||
|
var domainObject = context.domainObject;
|
||||||
|
var key = action.getMetadata().key;
|
||||||
|
var category = (context || {}).category;
|
||||||
|
|
||||||
|
// Use category to selectively block edit from the view. Edit action
|
||||||
|
// is also invoked during the create process which should be allowed,
|
||||||
|
// because it may be saved elsewhere
|
||||||
|
if ((key === 'edit' && category === 'view-control') || key === 'properties') {
|
||||||
|
identifier = objectUtils.parseKeyString(domainObject.getId());
|
||||||
|
provider = this.openmct.objects.getProvider(identifier);
|
||||||
|
return provider.save !== undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
return EditPersistableObjectsPolicy;
|
||||||
|
}
|
||||||
|
);
|
@ -161,6 +161,7 @@ define(
|
|||||||
'otherType',
|
'otherType',
|
||||||
['getKey']
|
['getKey']
|
||||||
),
|
),
|
||||||
|
|
||||||
//Create a form structure with location
|
//Create a form structure with location
|
||||||
structure = wizard.getFormStructure(true),
|
structure = wizard.getFormStructure(true),
|
||||||
sections = structure.sections,
|
sections = structure.sections,
|
||||||
@ -174,7 +175,7 @@ define(
|
|||||||
// can actually contain objects of this type
|
// can actually contain objects of this type
|
||||||
expect(mockPolicyService.allow).toHaveBeenCalledWith(
|
expect(mockPolicyService.allow).toHaveBeenCalledWith(
|
||||||
'composition',
|
'composition',
|
||||||
mockOtherType,
|
mockDomainObj,
|
||||||
mockDomainObject
|
mockDomainObject
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -0,0 +1,104 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2016, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
define(
|
||||||
|
["../../src/policies/EditPersistableObjectsPolicy"],
|
||||||
|
function (EditPersistableObjectsPolicy) {
|
||||||
|
|
||||||
|
describe("The Edit persistable objects policy", function () {
|
||||||
|
var mockDomainObject,
|
||||||
|
mockEditAction,
|
||||||
|
mockPropertiesAction,
|
||||||
|
mockOtherAction,
|
||||||
|
mockAPI,
|
||||||
|
mockObjectAPI,
|
||||||
|
testContext,
|
||||||
|
policy;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
mockDomainObject = jasmine.createSpyObj(
|
||||||
|
'domainObject',
|
||||||
|
[
|
||||||
|
'getId'
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
mockObjectAPI = jasmine.createSpyObj('objectAPI', [
|
||||||
|
'getProvider'
|
||||||
|
]);
|
||||||
|
|
||||||
|
mockAPI = {
|
||||||
|
objects: mockObjectAPI
|
||||||
|
};
|
||||||
|
|
||||||
|
mockEditAction = jasmine.createSpyObj('edit', ['getMetadata']);
|
||||||
|
mockPropertiesAction = jasmine.createSpyObj('properties', ['getMetadata']);
|
||||||
|
mockOtherAction = jasmine.createSpyObj('other', ['getMetadata']);
|
||||||
|
|
||||||
|
mockEditAction.getMetadata.andReturn({ key: 'edit' });
|
||||||
|
mockPropertiesAction.getMetadata.andReturn({ key: 'properties' });
|
||||||
|
mockOtherAction.getMetadata.andReturn({key: 'other'});
|
||||||
|
|
||||||
|
mockDomainObject.getId.andReturn('test:testId');
|
||||||
|
|
||||||
|
testContext = {
|
||||||
|
domainObject: mockDomainObject,
|
||||||
|
category: 'view-control'
|
||||||
|
};
|
||||||
|
|
||||||
|
policy = new EditPersistableObjectsPolicy(mockAPI);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Applies to edit action", function () {
|
||||||
|
mockObjectAPI.getProvider.andReturn({});
|
||||||
|
expect(mockObjectAPI.getProvider).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
policy.allow(mockEditAction, testContext);
|
||||||
|
expect(mockObjectAPI.getProvider).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Applies to properties action", function () {
|
||||||
|
mockObjectAPI.getProvider.andReturn({});
|
||||||
|
expect(mockObjectAPI.getProvider).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
policy.allow(mockPropertiesAction, testContext);
|
||||||
|
expect(mockObjectAPI.getProvider).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not apply to other actions", function () {
|
||||||
|
mockObjectAPI.getProvider.andReturn({});
|
||||||
|
expect(mockObjectAPI.getProvider).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
policy.allow(mockOtherAction, testContext);
|
||||||
|
expect(mockObjectAPI.getProvider).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Tests object provider for editability", function () {
|
||||||
|
mockObjectAPI.getProvider.andReturn({});
|
||||||
|
expect(policy.allow(mockEditAction, testContext)).toBe(false);
|
||||||
|
expect(mockObjectAPI.getProvider).toHaveBeenCalled();
|
||||||
|
mockObjectAPI.getProvider.andReturn({save: function () {}});
|
||||||
|
expect(policy.allow(mockEditAction, testContext)).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
@ -76,17 +76,18 @@
|
|||||||
&:not(.first) {
|
&:not(.first) {
|
||||||
border-top: 1px solid $colorFormLines;
|
border-top: 1px solid $colorFormLines;
|
||||||
}
|
}
|
||||||
.form-row {
|
}
|
||||||
@include align-items(center);
|
.form-row {
|
||||||
border: none;
|
@include align-items(center);
|
||||||
padding: $interiorMarginSm 0;
|
border: none !important;
|
||||||
.label {
|
margin-bottom: 0 !important;
|
||||||
min-width: 80px;
|
padding: $interiorMarginSm 0;
|
||||||
}
|
.label {
|
||||||
input[type='text'],
|
min-width: 80px;
|
||||||
input[type='search'] {
|
}
|
||||||
width: 100%;
|
input[type='text'],
|
||||||
}
|
input[type='search'] {
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,12 +25,14 @@ define([
|
|||||||
"./src/CompositionMutabilityPolicy",
|
"./src/CompositionMutabilityPolicy",
|
||||||
"./src/CompositionModelPolicy",
|
"./src/CompositionModelPolicy",
|
||||||
"./src/ComposeActionPolicy",
|
"./src/ComposeActionPolicy",
|
||||||
|
"./src/PersistableCompositionPolicy",
|
||||||
'legacyRegistry'
|
'legacyRegistry'
|
||||||
], function (
|
], function (
|
||||||
CompositionPolicy,
|
CompositionPolicy,
|
||||||
CompositionMutabilityPolicy,
|
CompositionMutabilityPolicy,
|
||||||
CompositionModelPolicy,
|
CompositionModelPolicy,
|
||||||
ComposeActionPolicy,
|
ComposeActionPolicy,
|
||||||
|
PersistableCompositionPolicy,
|
||||||
legacyRegistry
|
legacyRegistry
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@ -59,6 +61,12 @@ define([
|
|||||||
"$injector"
|
"$injector"
|
||||||
],
|
],
|
||||||
"message": "Objects of this type cannot contain objects of that type."
|
"message": "Objects of this type cannot contain objects of that type."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"category": "composition",
|
||||||
|
"implementation": PersistableCompositionPolicy,
|
||||||
|
"depends": ["openmct"],
|
||||||
|
"message": "Change cannot be made to composition of non-persistable object"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -43,9 +43,6 @@ define(
|
|||||||
}
|
}
|
||||||
|
|
||||||
ComposeActionPolicy.prototype.allowComposition = function (containerObject, selectedObject) {
|
ComposeActionPolicy.prototype.allowComposition = function (containerObject, selectedObject) {
|
||||||
// Get the object types involved in the compose action
|
|
||||||
var containerType = containerObject &&
|
|
||||||
containerObject.getCapability('type');
|
|
||||||
|
|
||||||
// Get a reference to the policy service if needed...
|
// Get a reference to the policy service if needed...
|
||||||
this.policyService = this.policyService || this.getPolicyService();
|
this.policyService = this.policyService || this.getPolicyService();
|
||||||
@ -54,7 +51,7 @@ define(
|
|||||||
return containerObject.getId() !== selectedObject.getId() &&
|
return containerObject.getId() !== selectedObject.getId() &&
|
||||||
this.policyService.allow(
|
this.policyService.allow(
|
||||||
'composition',
|
'composition',
|
||||||
containerType,
|
containerObject,
|
||||||
selectedObject
|
selectedObject
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -14,8 +14,9 @@ define(
|
|||||||
}
|
}
|
||||||
|
|
||||||
CompositionModelPolicy.prototype.allow = function (candidate) {
|
CompositionModelPolicy.prototype.allow = function (candidate) {
|
||||||
|
var candidateType = candidate.getCapability('type');
|
||||||
return Array.isArray(
|
return Array.isArray(
|
||||||
(candidate.getInitialModel() || {}).composition
|
(candidateType.getInitialModel() || {}).composition
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ define(
|
|||||||
// Equate creatability with mutability; that is, users
|
// Equate creatability with mutability; that is, users
|
||||||
// can only modify objects of types they can create, and
|
// can only modify objects of types they can create, and
|
||||||
// vice versa.
|
// vice versa.
|
||||||
return candidate.hasFeature('creation');
|
return candidate.getCapability('type').hasFeature('creation');
|
||||||
};
|
};
|
||||||
|
|
||||||
return CompositionMutabilityPolicy;
|
return CompositionMutabilityPolicy;
|
||||||
|
@ -30,16 +30,16 @@ define(
|
|||||||
function () {
|
function () {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines composition policy as driven by type metadata.
|
* Determines whether a given object can contain a candidate child object.
|
||||||
* @constructor
|
* @constructor
|
||||||
* @memberof platform/containment
|
* @memberof platform/containment
|
||||||
* @implements {Policy.<Type, Type>}
|
* @implements {Policy.<DomainObjectImpl, DomainObjectImpl>}
|
||||||
*/
|
*/
|
||||||
function CompositionPolicy() {
|
function CompositionPolicy() {
|
||||||
}
|
}
|
||||||
|
|
||||||
CompositionPolicy.prototype.allow = function (parentType, child) {
|
CompositionPolicy.prototype.allow = function (parent, child) {
|
||||||
var parentDef = parentType.getDefinition();
|
var parentDef = parent.getCapability('type').getDefinition();
|
||||||
|
|
||||||
// A parent without containment rules can contain anything.
|
// A parent without containment rules can contain anything.
|
||||||
if (!parentDef.contains) {
|
if (!parentDef.contains) {
|
||||||
|
60
platform/containment/src/PersistableCompositionPolicy.js
Normal file
60
platform/containment/src/PersistableCompositionPolicy.js
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2016, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This bundle implements "containment" rules, which determine which objects
|
||||||
|
* can be contained within which other objects.
|
||||||
|
* @namespace platform/containment
|
||||||
|
*/
|
||||||
|
define(
|
||||||
|
['../../../src/api/objects/object-utils'],
|
||||||
|
function (objectUtils) {
|
||||||
|
|
||||||
|
function PersistableCompositionPolicy(openmct) {
|
||||||
|
this.openmct = openmct;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only allow changes to composition if the changes can be saved. This in
|
||||||
|
* effect prevents selection of objects from the locator that do not
|
||||||
|
* support persistence.
|
||||||
|
* @param parent
|
||||||
|
* @param child
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
PersistableCompositionPolicy.prototype.allow = function (parent) {
|
||||||
|
// If object is in edit mode, allow composition because it is
|
||||||
|
// part of object creation, and the object may be saved to another
|
||||||
|
// namespace that does support persistence. The EditPersistableObjectsPolicy
|
||||||
|
// prevents editing of objects that cannot be persisted, so we can assume that this
|
||||||
|
// is a new object.
|
||||||
|
if (!(parent.hasCapability('editor') && parent.getCapability('editor').isEditContextRoot())) {
|
||||||
|
var identifier = objectUtils.parseKeyString(parent.getId());
|
||||||
|
var provider = this.openmct.objects.getProvider(identifier);
|
||||||
|
return provider.save !== undefined;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
return PersistableCompositionPolicy;
|
||||||
|
}
|
||||||
|
);
|
@ -78,7 +78,7 @@ define(
|
|||||||
|
|
||||||
expect(mockPolicyService.allow).toHaveBeenCalledWith(
|
expect(mockPolicyService.allow).toHaveBeenCalledWith(
|
||||||
'composition',
|
'composition',
|
||||||
mockTypes[0],
|
mockDomainObjects[0],
|
||||||
mockDomainObjects[1]
|
mockDomainObjects[1]
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -4,19 +4,25 @@ define(
|
|||||||
function (CompositionModelPolicy) {
|
function (CompositionModelPolicy) {
|
||||||
|
|
||||||
describe("The composition model policy", function () {
|
describe("The composition model policy", function () {
|
||||||
var mockType,
|
var mockObject,
|
||||||
|
mockType,
|
||||||
policy;
|
policy;
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
mockType = jasmine.createSpyObj('type', ['getInitialModel']);
|
mockType = jasmine.createSpyObj('type', ['getInitialModel']);
|
||||||
|
mockObject = {
|
||||||
|
getCapability: function () {
|
||||||
|
return mockType;
|
||||||
|
}
|
||||||
|
};
|
||||||
policy = new CompositionModelPolicy();
|
policy = new CompositionModelPolicy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("only allows composition for types which will have a composition property", function () {
|
it("only allows composition for types which will have a composition property", function () {
|
||||||
mockType.getInitialModel.andReturn({});
|
mockType.getInitialModel.andReturn({});
|
||||||
expect(policy.allow(mockType)).toBeFalsy();
|
expect(policy.allow(mockObject)).toBeFalsy();
|
||||||
mockType.getInitialModel.andReturn({ composition: [] });
|
mockType.getInitialModel.andReturn({ composition: [] });
|
||||||
expect(policy.allow(mockType)).toBeTruthy();
|
expect(policy.allow(mockObject)).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -25,18 +25,24 @@ define(
|
|||||||
function (CompositionMutabilityPolicy) {
|
function (CompositionMutabilityPolicy) {
|
||||||
|
|
||||||
describe("The composition mutability policy", function () {
|
describe("The composition mutability policy", function () {
|
||||||
var mockType,
|
var mockObject,
|
||||||
|
mockType,
|
||||||
policy;
|
policy;
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
mockType = jasmine.createSpyObj('type', ['hasFeature']);
|
mockType = jasmine.createSpyObj('type', ['hasFeature']);
|
||||||
|
mockObject = {
|
||||||
|
getCapability: function () {
|
||||||
|
return mockType;
|
||||||
|
}
|
||||||
|
};
|
||||||
policy = new CompositionMutabilityPolicy();
|
policy = new CompositionMutabilityPolicy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("only allows composition for types which can be created/modified", function () {
|
it("only allows composition for types which can be created/modified", function () {
|
||||||
expect(policy.allow(mockType)).toBeFalsy();
|
expect(policy.allow(mockObject)).toBeFalsy();
|
||||||
mockType.hasFeature.andReturn(true);
|
mockType.hasFeature.andReturn(true);
|
||||||
expect(policy.allow(mockType)).toBeTruthy();
|
expect(policy.allow(mockObject)).toBeTruthy();
|
||||||
expect(mockType.hasFeature).toHaveBeenCalledWith('creation');
|
expect(mockType.hasFeature).toHaveBeenCalledWith('creation');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -24,13 +24,18 @@ define(
|
|||||||
["../src/CompositionPolicy"],
|
["../src/CompositionPolicy"],
|
||||||
function (CompositionPolicy) {
|
function (CompositionPolicy) {
|
||||||
describe("Composition policy", function () {
|
describe("Composition policy", function () {
|
||||||
var typeA,
|
var mockParentObject,
|
||||||
|
typeA,
|
||||||
typeB,
|
typeB,
|
||||||
typeC,
|
typeC,
|
||||||
mockChildObject,
|
mockChildObject,
|
||||||
policy;
|
policy;
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
|
mockParentObject = jasmine.createSpyObj('domainObject', [
|
||||||
|
'getCapability'
|
||||||
|
]);
|
||||||
|
|
||||||
typeA = jasmine.createSpyObj(
|
typeA = jasmine.createSpyObj(
|
||||||
'type A-- the particular kind',
|
'type A-- the particular kind',
|
||||||
['getKey', 'getDefinition']
|
['getKey', 'getDefinition']
|
||||||
@ -70,27 +75,31 @@ define(
|
|||||||
describe('enforces simple containment rules', function () {
|
describe('enforces simple containment rules', function () {
|
||||||
|
|
||||||
it('allows when type matches', function () {
|
it('allows when type matches', function () {
|
||||||
|
mockParentObject.getCapability.andReturn(typeA);
|
||||||
|
|
||||||
mockChildObject.getCapability.andReturn(typeA);
|
mockChildObject.getCapability.andReturn(typeA);
|
||||||
expect(policy.allow(typeA, mockChildObject))
|
expect(policy.allow(mockParentObject, mockChildObject))
|
||||||
.toBeTruthy();
|
.toBeTruthy();
|
||||||
|
|
||||||
expect(policy.allow(typeB, mockChildObject))
|
mockParentObject.getCapability.andReturn(typeB);
|
||||||
|
expect(policy.allow(mockParentObject, mockChildObject))
|
||||||
.toBeTruthy();
|
.toBeTruthy();
|
||||||
|
|
||||||
mockChildObject.getCapability.andReturn(typeB);
|
mockChildObject.getCapability.andReturn(typeB);
|
||||||
expect(policy.allow(typeB, mockChildObject))
|
expect(policy.allow(mockParentObject, mockChildObject))
|
||||||
.toBeTruthy();
|
.toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('disallows when type doesn\'t match', function () {
|
it('disallows when type doesn\'t match', function () {
|
||||||
|
|
||||||
|
mockParentObject.getCapability.andReturn(typeA);
|
||||||
mockChildObject.getCapability.andReturn(typeB);
|
mockChildObject.getCapability.andReturn(typeB);
|
||||||
expect(policy.allow(typeA, mockChildObject))
|
expect(policy.allow(mockParentObject, mockChildObject))
|
||||||
.toBeFalsy();
|
.toBeFalsy();
|
||||||
|
|
||||||
mockChildObject.getCapability.andReturn(typeC);
|
mockChildObject.getCapability.andReturn(typeC);
|
||||||
expect(policy.allow(typeA, mockChildObject))
|
expect(policy.allow(mockParentObject, mockChildObject))
|
||||||
.toBeFalsy();
|
.toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -98,8 +107,10 @@ define(
|
|||||||
|
|
||||||
describe('enforces capability-based containment rules', function () {
|
describe('enforces capability-based containment rules', function () {
|
||||||
it('allows when object has capability', function () {
|
it('allows when object has capability', function () {
|
||||||
|
mockParentObject.getCapability.andReturn(typeC);
|
||||||
|
|
||||||
mockChildObject.hasCapability.andReturn(true);
|
mockChildObject.hasCapability.andReturn(true);
|
||||||
expect(policy.allow(typeC, mockChildObject))
|
expect(policy.allow(mockParentObject, mockChildObject))
|
||||||
.toBeTruthy();
|
.toBeTruthy();
|
||||||
expect(mockChildObject.hasCapability)
|
expect(mockChildObject.hasCapability)
|
||||||
.toHaveBeenCalledWith('telemetry');
|
.toHaveBeenCalledWith('telemetry');
|
||||||
@ -107,7 +118,10 @@ define(
|
|||||||
|
|
||||||
it('skips when object doesn\'t have capability', function () {
|
it('skips when object doesn\'t have capability', function () {
|
||||||
mockChildObject.hasCapability.andReturn(false);
|
mockChildObject.hasCapability.andReturn(false);
|
||||||
expect(policy.allow(typeC, mockChildObject))
|
|
||||||
|
mockParentObject.getCapability.andReturn(typeC);
|
||||||
|
|
||||||
|
expect(policy.allow(mockParentObject, mockChildObject))
|
||||||
.toBeFalsy();
|
.toBeFalsy();
|
||||||
expect(mockChildObject.hasCapability)
|
expect(mockChildObject.hasCapability)
|
||||||
.toHaveBeenCalledWith('telemetry');
|
.toHaveBeenCalledWith('telemetry');
|
||||||
|
@ -0,0 +1,85 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2016, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
define(
|
||||||
|
["../src/PersistableCompositionPolicy"],
|
||||||
|
function (PersistableCompositionPolicy) {
|
||||||
|
describe("Persistable Composition policy", function () {
|
||||||
|
var objectAPI;
|
||||||
|
var mockOpenMCT;
|
||||||
|
var persistableCompositionPolicy;
|
||||||
|
var mockParent;
|
||||||
|
var mockChild;
|
||||||
|
var mockEditorCapability;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
objectAPI = jasmine.createSpyObj('objectsAPI', [
|
||||||
|
'getProvider'
|
||||||
|
]);
|
||||||
|
|
||||||
|
mockOpenMCT = {
|
||||||
|
objects: objectAPI
|
||||||
|
};
|
||||||
|
mockParent = jasmine.createSpyObj('domainObject', [
|
||||||
|
'hasCapability',
|
||||||
|
'getCapability',
|
||||||
|
'getId'
|
||||||
|
]);
|
||||||
|
mockParent.hasCapability.andReturn(true);
|
||||||
|
mockParent.getId.andReturn('someNamespace:someId');
|
||||||
|
mockChild = {};
|
||||||
|
mockEditorCapability = jasmine.createSpyObj('domainObject', [
|
||||||
|
'isEditContextRoot'
|
||||||
|
]);
|
||||||
|
mockParent.getCapability.andReturn(mockEditorCapability);
|
||||||
|
|
||||||
|
objectAPI.getProvider.andReturn({
|
||||||
|
save: function () {}
|
||||||
|
});
|
||||||
|
persistableCompositionPolicy = new PersistableCompositionPolicy(mockOpenMCT);
|
||||||
|
});
|
||||||
|
|
||||||
|
//Parent
|
||||||
|
// - getCapability ('editor')
|
||||||
|
// - isEditContextRoot
|
||||||
|
// - openMct.objects.getProvider
|
||||||
|
|
||||||
|
it("Does not allow composition for objects that are not persistable", function () {
|
||||||
|
mockEditorCapability.isEditContextRoot.andReturn(false);
|
||||||
|
expect(persistableCompositionPolicy.allow(mockParent, mockChild)).toBe(true);
|
||||||
|
objectAPI.getProvider.andReturn({});
|
||||||
|
expect(persistableCompositionPolicy.allow(mockParent, mockChild)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Always allows composition of objects in edit mode to support object creation", function () {
|
||||||
|
mockEditorCapability.isEditContextRoot.andReturn(true);
|
||||||
|
expect(persistableCompositionPolicy.allow(mockParent, mockChild)).toBe(true);
|
||||||
|
expect(objectAPI.getProvider).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
mockEditorCapability.isEditContextRoot.andReturn(false);
|
||||||
|
expect(persistableCompositionPolicy.allow(mockParent, mockChild)).toBe(true);
|
||||||
|
expect(objectAPI.getProvider).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
@ -49,7 +49,6 @@ define([
|
|||||||
"./src/services/Now",
|
"./src/services/Now",
|
||||||
"./src/services/Throttle",
|
"./src/services/Throttle",
|
||||||
"./src/services/Topic",
|
"./src/services/Topic",
|
||||||
"./src/services/Contextualize",
|
|
||||||
"./src/services/Instantiate",
|
"./src/services/Instantiate",
|
||||||
'legacyRegistry'
|
'legacyRegistry'
|
||||||
], function (
|
], function (
|
||||||
@ -81,7 +80,6 @@ define([
|
|||||||
Now,
|
Now,
|
||||||
Throttle,
|
Throttle,
|
||||||
Topic,
|
Topic,
|
||||||
Contextualize,
|
|
||||||
Instantiate,
|
Instantiate,
|
||||||
legacyRegistry
|
legacyRegistry
|
||||||
) {
|
) {
|
||||||
@ -284,8 +282,7 @@ define([
|
|||||||
"key": "composition",
|
"key": "composition",
|
||||||
"implementation": CompositionCapability,
|
"implementation": CompositionCapability,
|
||||||
"depends": [
|
"depends": [
|
||||||
"$injector",
|
"$injector"
|
||||||
"contextualize"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -380,13 +377,6 @@ define([
|
|||||||
"$log"
|
"$log"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"key": "contextualize",
|
|
||||||
"implementation": Contextualize,
|
|
||||||
"depends": [
|
|
||||||
"$log"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"key": "instantiate",
|
"key": "instantiate",
|
||||||
"implementation": Instantiate,
|
"implementation": Instantiate,
|
||||||
|
@ -24,7 +24,8 @@
|
|||||||
* Module defining CompositionCapability. Created by vwoeltje on 11/7/14.
|
* Module defining CompositionCapability. Created by vwoeltje on 11/7/14.
|
||||||
*/
|
*/
|
||||||
define(
|
define(
|
||||||
function () {
|
['./ContextualDomainObject'],
|
||||||
|
function (ContextualDomainObject) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Composition capability. A domain object's composition is the set of
|
* Composition capability. A domain object's composition is the set of
|
||||||
@ -38,13 +39,12 @@ define(
|
|||||||
* @constructor
|
* @constructor
|
||||||
* @implements {Capability}
|
* @implements {Capability}
|
||||||
*/
|
*/
|
||||||
function CompositionCapability($injector, contextualize, domainObject) {
|
function CompositionCapability($injector, domainObject) {
|
||||||
// Get a reference to the object service from $injector
|
// Get a reference to the object service from $injector
|
||||||
this.injectObjectService = function () {
|
this.injectObjectService = function () {
|
||||||
this.objectService = $injector.get("objectService");
|
this.objectService = $injector.get("objectService");
|
||||||
};
|
};
|
||||||
|
|
||||||
this.contextualize = contextualize;
|
|
||||||
this.domainObject = domainObject;
|
this.domainObject = domainObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,7 +115,6 @@ define(
|
|||||||
CompositionCapability.prototype.invoke = function () {
|
CompositionCapability.prototype.invoke = function () {
|
||||||
var domainObject = this.domainObject,
|
var domainObject = this.domainObject,
|
||||||
model = domainObject.getModel(),
|
model = domainObject.getModel(),
|
||||||
contextualize = this.contextualize,
|
|
||||||
ids;
|
ids;
|
||||||
|
|
||||||
// Then filter out non-existent objects,
|
// Then filter out non-existent objects,
|
||||||
@ -125,7 +124,7 @@ define(
|
|||||||
return ids.filter(function (id) {
|
return ids.filter(function (id) {
|
||||||
return objects[id];
|
return objects[id];
|
||||||
}).map(function (id) {
|
}).map(function (id) {
|
||||||
return contextualize(objects[id], domainObject);
|
return new ContextualDomainObject(objects[id], domainObject);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,8 +21,8 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
define(
|
define(
|
||||||
[],
|
['./ContextualDomainObject'],
|
||||||
function () {
|
function (ContextualDomainObject) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements the `instantiation` capability. This allows new domain
|
* Implements the `instantiation` capability. This allows new domain
|
||||||
@ -70,11 +70,7 @@ define(
|
|||||||
|
|
||||||
var newObject = this.instantiateFn(model, id);
|
var newObject = this.instantiateFn(model, id);
|
||||||
|
|
||||||
this.contextualizeFn = this.contextualizeFn ||
|
return new ContextualDomainObject(newObject, this.domainObject);
|
||||||
this.$injector.get("contextualize");
|
|
||||||
|
|
||||||
|
|
||||||
return this.contextualizeFn(newObject, this.domainObject);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,82 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2016, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
['../capabilities/ContextualDomainObject'],
|
|
||||||
function (ContextualDomainObject) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wrap a domain object such that it has a `context` capability
|
|
||||||
* referring to a specific parent.
|
|
||||||
*
|
|
||||||
* Usage:
|
|
||||||
*
|
|
||||||
* contextualize(domainObject, parentObject)
|
|
||||||
*
|
|
||||||
* Attempting to contextualize an object with a parent that does
|
|
||||||
* not include that object in its composition may have
|
|
||||||
* unpredictable results; a warning will be logged if this occurs.
|
|
||||||
*
|
|
||||||
* @returns {Function}
|
|
||||||
* @memberof platform/core
|
|
||||||
*/
|
|
||||||
function Contextualize($log) {
|
|
||||||
function validate(id, parentObject) {
|
|
||||||
var model = parentObject && parentObject.getModel(),
|
|
||||||
composition = (model || {}).composition || [];
|
|
||||||
if (composition.indexOf(id) === -1) {
|
|
||||||
$log.warn([
|
|
||||||
"Attempted to contextualize",
|
|
||||||
id,
|
|
||||||
"in",
|
|
||||||
parentObject && parentObject.getId(),
|
|
||||||
"but that object does not contain",
|
|
||||||
id,
|
|
||||||
"in its composition.",
|
|
||||||
"Unexpected behavior may follow."
|
|
||||||
].join(" "));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Contextualize this domain object.
|
|
||||||
* @param {DomainObject} domainObject the domain object
|
|
||||||
* to wrap with a context
|
|
||||||
* @param {DomainObject} parentObject the domain object
|
|
||||||
* which should appear as the contextual parent
|
|
||||||
*/
|
|
||||||
return function (domainObject, parentObject) {
|
|
||||||
// Don't validate while editing; consistency is not
|
|
||||||
// necessarily expected due to unsaved changes.
|
|
||||||
var editor = domainObject.getCapability('editor');
|
|
||||||
if (!editor || !editor.inEditContext()) {
|
|
||||||
validate(domainObject.getId(), parentObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ContextualDomainObject(domainObject, parentObject);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return Contextualize;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
@ -41,7 +41,6 @@ define(
|
|||||||
describe("The composition capability", function () {
|
describe("The composition capability", function () {
|
||||||
var mockDomainObject,
|
var mockDomainObject,
|
||||||
mockInjector,
|
mockInjector,
|
||||||
mockContextualize,
|
|
||||||
mockObjectService,
|
mockObjectService,
|
||||||
composition;
|
composition;
|
||||||
|
|
||||||
@ -72,19 +71,11 @@ define(
|
|||||||
return (name === "objectService") && mockObjectService;
|
return (name === "objectService") && mockObjectService;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
mockContextualize = jasmine.createSpy('contextualize');
|
|
||||||
|
|
||||||
// Provide a minimal (e.g. no error-checking) implementation
|
|
||||||
// of contextualize for simplicity
|
|
||||||
mockContextualize.andCallFake(function (domainObject, parentObject) {
|
|
||||||
return new ContextualDomainObject(domainObject, parentObject);
|
|
||||||
});
|
|
||||||
|
|
||||||
mockObjectService.getObjects.andReturn(mockPromise([]));
|
mockObjectService.getObjects.andReturn(mockPromise([]));
|
||||||
|
|
||||||
composition = new CompositionCapability(
|
composition = new CompositionCapability(
|
||||||
mockInjector,
|
mockInjector,
|
||||||
mockContextualize,
|
|
||||||
mockDomainObject
|
mockDomainObject
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -28,7 +28,6 @@ define(
|
|||||||
var mockInjector,
|
var mockInjector,
|
||||||
mockIdentifierService,
|
mockIdentifierService,
|
||||||
mockInstantiate,
|
mockInstantiate,
|
||||||
mockContextualize,
|
|
||||||
mockIdentifier,
|
mockIdentifier,
|
||||||
mockNow,
|
mockNow,
|
||||||
mockDomainObject,
|
mockDomainObject,
|
||||||
@ -37,7 +36,6 @@ define(
|
|||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
mockInjector = jasmine.createSpyObj("$injector", ["get"]);
|
mockInjector = jasmine.createSpyObj("$injector", ["get"]);
|
||||||
mockInstantiate = jasmine.createSpy("instantiate");
|
mockInstantiate = jasmine.createSpy("instantiate");
|
||||||
mockContextualize = jasmine.createSpy("contextualize");
|
|
||||||
mockIdentifierService = jasmine.createSpyObj(
|
mockIdentifierService = jasmine.createSpyObj(
|
||||||
'identifierService',
|
'identifierService',
|
||||||
['parse', 'generate']
|
['parse', 'generate']
|
||||||
@ -53,8 +51,7 @@ define(
|
|||||||
|
|
||||||
mockInjector.get.andCallFake(function (key) {
|
mockInjector.get.andCallFake(function (key) {
|
||||||
return {
|
return {
|
||||||
'instantiate': mockInstantiate,
|
'instantiate': mockInstantiate
|
||||||
'contextualize': mockContextualize
|
|
||||||
}[key];
|
}[key];
|
||||||
});
|
});
|
||||||
mockIdentifierService.parse.andReturn(mockIdentifier);
|
mockIdentifierService.parse.andReturn(mockIdentifier);
|
||||||
@ -85,18 +82,12 @@ define(
|
|||||||
'hasCapability'
|
'hasCapability'
|
||||||
]), testModel = { someKey: "some value" };
|
]), testModel = { someKey: "some value" };
|
||||||
mockInstantiate.andReturn(mockDomainObj);
|
mockInstantiate.andReturn(mockDomainObj);
|
||||||
mockContextualize.andCallFake(function (x) {
|
instantiation.instantiate(testModel);
|
||||||
return x;
|
|
||||||
});
|
|
||||||
expect(instantiation.instantiate(testModel))
|
|
||||||
.toBe(mockDomainObj);
|
|
||||||
expect(mockInstantiate)
|
expect(mockInstantiate)
|
||||||
.toHaveBeenCalledWith({
|
.toHaveBeenCalledWith({
|
||||||
someKey: "some value",
|
someKey: "some value",
|
||||||
modified: mockNow()
|
modified: mockNow()
|
||||||
}, jasmine.any(String));
|
}, jasmine.any(String));
|
||||||
expect(mockContextualize)
|
|
||||||
.toHaveBeenCalledWith(mockDomainObj, mockDomainObject);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -1,103 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2016, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
["../../src/services/Contextualize"],
|
|
||||||
function (Contextualize) {
|
|
||||||
|
|
||||||
var DOMAIN_OBJECT_METHODS = [
|
|
||||||
'getId',
|
|
||||||
'getModel',
|
|
||||||
'getCapability',
|
|
||||||
'hasCapability',
|
|
||||||
'useCapability'
|
|
||||||
];
|
|
||||||
|
|
||||||
describe("The 'contextualize' service", function () {
|
|
||||||
var mockLog,
|
|
||||||
mockDomainObject,
|
|
||||||
mockParentObject,
|
|
||||||
mockEditor,
|
|
||||||
testParentModel,
|
|
||||||
contextualize;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
testParentModel = { composition: ["abc"] };
|
|
||||||
|
|
||||||
mockLog = jasmine.createSpyObj(
|
|
||||||
"$log",
|
|
||||||
["error", "warn", "info", "debug"]
|
|
||||||
);
|
|
||||||
|
|
||||||
mockDomainObject =
|
|
||||||
jasmine.createSpyObj('domainObject', DOMAIN_OBJECT_METHODS);
|
|
||||||
mockParentObject =
|
|
||||||
jasmine.createSpyObj('parentObject', DOMAIN_OBJECT_METHODS);
|
|
||||||
|
|
||||||
mockEditor =
|
|
||||||
jasmine.createSpyObj('editor', ['inEditContext']);
|
|
||||||
|
|
||||||
mockDomainObject.getId.andReturn("abc");
|
|
||||||
mockDomainObject.getModel.andReturn({});
|
|
||||||
mockParentObject.getId.andReturn("parent");
|
|
||||||
mockParentObject.getModel.andReturn(testParentModel);
|
|
||||||
|
|
||||||
mockEditor.inEditContext.andReturn(false);
|
|
||||||
mockDomainObject.getCapability.andCallFake(function (c) {
|
|
||||||
return c === 'editor' && mockEditor;
|
|
||||||
});
|
|
||||||
|
|
||||||
contextualize = new Contextualize(mockLog);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("attaches a context capability", function () {
|
|
||||||
var contextualizedObject =
|
|
||||||
contextualize(mockDomainObject, mockParentObject);
|
|
||||||
|
|
||||||
expect(contextualizedObject.getId()).toEqual("abc");
|
|
||||||
expect(contextualizedObject.getCapability("context"))
|
|
||||||
.toBeDefined();
|
|
||||||
expect(contextualizedObject.getCapability("context").getParent())
|
|
||||||
.toBe(mockParentObject);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("issues a warning if composition does not match", function () {
|
|
||||||
// Precondition - normally it should not issue a warning
|
|
||||||
contextualize(mockDomainObject, mockParentObject);
|
|
||||||
expect(mockLog.warn).not.toHaveBeenCalled();
|
|
||||||
|
|
||||||
testParentModel.composition = ["xyz"];
|
|
||||||
|
|
||||||
contextualize(mockDomainObject, mockParentObject);
|
|
||||||
expect(mockLog.warn).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("does not issue warnings for objects being edited", function () {
|
|
||||||
mockEditor.inEditContext.andReturn(true);
|
|
||||||
testParentModel.composition = ["xyz"];
|
|
||||||
contextualize(mockDomainObject, mockParentObject);
|
|
||||||
expect(mockLog.warn).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
@ -132,7 +132,6 @@ define([
|
|||||||
"provides": "objectService",
|
"provides": "objectService",
|
||||||
"implementation": LocatingObjectDecorator,
|
"implementation": LocatingObjectDecorator,
|
||||||
"depends": [
|
"depends": [
|
||||||
"contextualize",
|
|
||||||
"$q",
|
"$q",
|
||||||
"$log"
|
"$log"
|
||||||
]
|
]
|
||||||
|
@ -47,7 +47,7 @@ define(
|
|||||||
}
|
}
|
||||||
return this.policyService.allow(
|
return this.policyService.allow(
|
||||||
"composition",
|
"composition",
|
||||||
parentCandidate.getCapability('type'),
|
parentCandidate,
|
||||||
object
|
object
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -51,7 +51,7 @@ define(
|
|||||||
}
|
}
|
||||||
return this.policyService.allow(
|
return this.policyService.allow(
|
||||||
"composition",
|
"composition",
|
||||||
parentCandidate.getCapability('type'),
|
parentCandidate,
|
||||||
object
|
object
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -22,7 +22,8 @@
|
|||||||
|
|
||||||
|
|
||||||
define(
|
define(
|
||||||
function () {
|
['../../../core/src/capabilities/ContextualDomainObject'],
|
||||||
|
function (ContextualDomainObject) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensures that domain objects are loaded with a context capability
|
* Ensures that domain objects are loaded with a context capability
|
||||||
@ -31,8 +32,7 @@ define(
|
|||||||
* @implements {ObjectService}
|
* @implements {ObjectService}
|
||||||
* @memberof platform/entanglement
|
* @memberof platform/entanglement
|
||||||
*/
|
*/
|
||||||
function LocatingObjectDecorator(contextualize, $q, $log, objectService) {
|
function LocatingObjectDecorator($q, $log, objectService) {
|
||||||
this.contextualize = contextualize;
|
|
||||||
this.$log = $log;
|
this.$log = $log;
|
||||||
this.objectService = objectService;
|
this.objectService = objectService;
|
||||||
this.$q = $q;
|
this.$q = $q;
|
||||||
@ -41,7 +41,6 @@ define(
|
|||||||
LocatingObjectDecorator.prototype.getObjects = function (ids) {
|
LocatingObjectDecorator.prototype.getObjects = function (ids) {
|
||||||
var $q = this.$q,
|
var $q = this.$q,
|
||||||
$log = this.$log,
|
$log = this.$log,
|
||||||
contextualize = this.contextualize,
|
|
||||||
objectService = this.objectService,
|
objectService = this.objectService,
|
||||||
result = {};
|
result = {};
|
||||||
|
|
||||||
@ -76,7 +75,7 @@ define(
|
|||||||
return loadObjectInContext(location, exclude)
|
return loadObjectInContext(location, exclude)
|
||||||
.then(function (parent) {
|
.then(function (parent) {
|
||||||
// ...and then contextualize with it!
|
// ...and then contextualize with it!
|
||||||
return contextualize(domainObject, parent);
|
return new ContextualDomainObject(domainObject, parent);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ define(
|
|||||||
}
|
}
|
||||||
return this.policyService.allow(
|
return this.policyService.allow(
|
||||||
"composition",
|
"composition",
|
||||||
parentCandidate.getCapability('type'),
|
parentCandidate,
|
||||||
object
|
object
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -103,7 +103,7 @@ define(
|
|||||||
validate();
|
validate();
|
||||||
expect(policyService.allow).toHaveBeenCalledWith(
|
expect(policyService.allow).toHaveBeenCalledWith(
|
||||||
"composition",
|
"composition",
|
||||||
parentCandidate.capabilities.type,
|
parentCandidate,
|
||||||
object
|
object
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -113,7 +113,7 @@ define(
|
|||||||
validate();
|
validate();
|
||||||
expect(mockPolicyService.allow).toHaveBeenCalledWith(
|
expect(mockPolicyService.allow).toHaveBeenCalledWith(
|
||||||
"composition",
|
"composition",
|
||||||
parentCandidate.capabilities.type,
|
parentCandidate,
|
||||||
object
|
object
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -23,13 +23,13 @@
|
|||||||
|
|
||||||
define(
|
define(
|
||||||
[
|
[
|
||||||
'../../src/services/LocatingObjectDecorator'
|
'../../src/services/LocatingObjectDecorator',
|
||||||
|
'../../../core/src/capabilities/ContextualDomainObject'
|
||||||
],
|
],
|
||||||
function (LocatingObjectDecorator) {
|
function (LocatingObjectDecorator, ContextualDomainObject) {
|
||||||
|
|
||||||
describe("LocatingObjectDecorator", function () {
|
describe("LocatingObjectDecorator", function () {
|
||||||
var mockContextualize,
|
var mockQ,
|
||||||
mockQ,
|
|
||||||
mockLog,
|
mockLog,
|
||||||
mockObjectService,
|
mockObjectService,
|
||||||
mockCallback,
|
mockCallback,
|
||||||
@ -57,21 +57,12 @@ define(
|
|||||||
};
|
};
|
||||||
testObjects = {};
|
testObjects = {};
|
||||||
|
|
||||||
mockContextualize = jasmine.createSpy("contextualize");
|
|
||||||
mockQ = jasmine.createSpyObj("$q", ["when", "all"]);
|
mockQ = jasmine.createSpyObj("$q", ["when", "all"]);
|
||||||
mockLog =
|
mockLog =
|
||||||
jasmine.createSpyObj("$log", ["error", "warn", "info", "debug"]);
|
jasmine.createSpyObj("$log", ["error", "warn", "info", "debug"]);
|
||||||
mockObjectService =
|
mockObjectService =
|
||||||
jasmine.createSpyObj("objectService", ["getObjects"]);
|
jasmine.createSpyObj("objectService", ["getObjects"]);
|
||||||
|
|
||||||
mockContextualize.andCallFake(function (domainObject, parentObject) {
|
|
||||||
// Not really what contextualize does, but easy to test!
|
|
||||||
return {
|
|
||||||
testObject: domainObject,
|
|
||||||
testParent: parentObject
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
mockQ.when.andCallFake(testPromise);
|
mockQ.when.andCallFake(testPromise);
|
||||||
mockQ.all.andCallFake(function (promises) {
|
mockQ.all.andCallFake(function (promises) {
|
||||||
var result = {};
|
var result = {};
|
||||||
@ -97,28 +88,21 @@ define(
|
|||||||
});
|
});
|
||||||
|
|
||||||
decorator = new LocatingObjectDecorator(
|
decorator = new LocatingObjectDecorator(
|
||||||
mockContextualize,
|
|
||||||
mockQ,
|
mockQ,
|
||||||
mockLog,
|
mockLog,
|
||||||
mockObjectService
|
mockObjectService
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("contextualizes domain objects by location", function () {
|
it("contextualizes domain objects", function () {
|
||||||
decorator.getObjects(['b', 'c']).then(mockCallback);
|
decorator.getObjects(['b', 'c']).then(mockCallback);
|
||||||
expect(mockCallback).toHaveBeenCalledWith({
|
expect(mockCallback).toHaveBeenCalled();
|
||||||
b: {
|
|
||||||
testObject: testObjects.b,
|
var callbackObj = mockCallback.mostRecentCall.args[0];
|
||||||
testParent: testObjects.a
|
expect(testObjects.b.getCapability('context')).not.toBeDefined();
|
||||||
},
|
expect(testObjects.c.getCapability('context')).not.toBeDefined();
|
||||||
c: {
|
expect(callbackObj.b.getCapability('context')).toBeDefined();
|
||||||
testObject: testObjects.c,
|
expect(callbackObj.c.getCapability('context')).toBeDefined();
|
||||||
testParent: {
|
|
||||||
testObject: testObjects.b,
|
|
||||||
testParent: testObjects.a
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("warns on cycle detection", function () {
|
it("warns on cycle detection", function () {
|
||||||
|
@ -123,7 +123,7 @@ define(
|
|||||||
validate();
|
validate();
|
||||||
expect(policyService.allow).toHaveBeenCalledWith(
|
expect(policyService.allow).toHaveBeenCalledWith(
|
||||||
"composition",
|
"composition",
|
||||||
parentCandidate.capabilities.type,
|
parentCandidate,
|
||||||
object
|
object
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
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));
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
@ -41,7 +41,7 @@ define(
|
|||||||
scope,
|
scope,
|
||||||
element
|
element
|
||||||
) {
|
) {
|
||||||
this.conductor = openmct.conductor;
|
this.timeAPI = openmct.time;
|
||||||
this.scope = scope;
|
this.scope = scope;
|
||||||
this.element = element;
|
this.element = element;
|
||||||
|
|
||||||
@ -51,24 +51,26 @@ define(
|
|||||||
}
|
}
|
||||||
|
|
||||||
ConductorRepresenter.prototype.boundsListener = function (bounds) {
|
ConductorRepresenter.prototype.boundsListener = function (bounds) {
|
||||||
|
var timeSystem = this.timeAPI.getTimeSystem(this.timeAPI.timeSystem());
|
||||||
this.scope.$broadcast('telemetry:display:bounds', {
|
this.scope.$broadcast('telemetry:display:bounds', {
|
||||||
start: bounds.start,
|
start: bounds.start,
|
||||||
end: bounds.end,
|
end: bounds.end,
|
||||||
domain: this.conductor.timeSystem().metadata.key
|
domain: timeSystem
|
||||||
}, this.conductor.follow());
|
}, this.timeAPI.follow());
|
||||||
};
|
};
|
||||||
|
|
||||||
ConductorRepresenter.prototype.timeSystemListener = function (timeSystem) {
|
ConductorRepresenter.prototype.timeSystemListener = function (key) {
|
||||||
var bounds = this.conductor.bounds();
|
var bounds = this.timeAPI.bounds();
|
||||||
|
var timeSystem = this.timeAPI.getTimeSystem(key);
|
||||||
this.scope.$broadcast('telemetry:display:bounds', {
|
this.scope.$broadcast('telemetry:display:bounds', {
|
||||||
start: bounds.start,
|
start: bounds.start,
|
||||||
end: bounds.end,
|
end: bounds.end,
|
||||||
domain: timeSystem.metadata.key
|
domain: timeSystem
|
||||||
}, this.conductor.follow());
|
}, this.timeAPI.follow());
|
||||||
};
|
};
|
||||||
|
|
||||||
ConductorRepresenter.prototype.followListener = function () {
|
ConductorRepresenter.prototype.followListener = function () {
|
||||||
this.boundsListener(this.conductor.bounds());
|
this.boundsListener(this.timeAPI.bounds());
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle a specific representation of a specific domain object
|
// Handle a specific representation of a specific domain object
|
||||||
@ -76,16 +78,16 @@ define(
|
|||||||
if (representation.key === 'browse-object') {
|
if (representation.key === 'browse-object') {
|
||||||
this.destroy();
|
this.destroy();
|
||||||
|
|
||||||
this.conductor.on("bounds", this.boundsListener);
|
this.timeAPI.on("bounds", this.boundsListener);
|
||||||
this.conductor.on("timeSystem", this.timeSystemListener);
|
this.timeAPI.on("timeSystem", this.timeSystemListener);
|
||||||
this.conductor.on("follow", this.followListener);
|
this.timeAPI.on("follow", this.followListener);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ConductorRepresenter.prototype.destroy = function destroy() {
|
ConductorRepresenter.prototype.destroy = function destroy() {
|
||||||
this.conductor.off("bounds", this.boundsListener);
|
this.timeAPI.off("bounds", this.boundsListener);
|
||||||
this.conductor.off("timeSystem", this.timeSystemListener);
|
this.timeAPI.off("timeSystem", this.timeSystemListener);
|
||||||
this.conductor.off("follow", this.followListener);
|
this.timeAPI.off("follow", this.followListener);
|
||||||
};
|
};
|
||||||
|
|
||||||
return ConductorRepresenter;
|
return ConductorRepresenter;
|
||||||
|
@ -71,8 +71,7 @@ define([
|
|||||||
"openmct",
|
"openmct",
|
||||||
"timeConductorViewService",
|
"timeConductorViewService",
|
||||||
"formatService",
|
"formatService",
|
||||||
"DEFAULT_TIMECONDUCTOR_MODE",
|
"CONDUCTOR_CONFIG"
|
||||||
"SHOW_TIMECONDUCTOR"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -151,13 +150,6 @@ define([
|
|||||||
"link": "https://github.com/d3/d3/blob/master/LICENSE"
|
"link": "https://github.com/d3/d3/blob/master/LICENSE"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"constants": [
|
|
||||||
{
|
|
||||||
"key": "DEFAULT_TIMECONDUCTOR_MODE",
|
|
||||||
"value": "realtime",
|
|
||||||
"priority": "fallback"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"formats": [
|
"formats": [
|
||||||
{
|
{
|
||||||
"key": "number",
|
"key": "number",
|
||||||
|
@ -346,7 +346,28 @@
|
|||||||
content: $i;
|
content: $i;
|
||||||
}
|
}
|
||||||
.l-axis-holder {
|
.l-axis-holder {
|
||||||
|
$c0: rgba($colorBodyFg, 0.1);
|
||||||
|
$c2: transparent;
|
||||||
|
$grabTicksH: 3px;
|
||||||
|
$grabTicksXSpace: 4px;
|
||||||
|
$grabTicksYOffset: 0;
|
||||||
@include cursorGrab();
|
@include cursorGrab();
|
||||||
|
svg {
|
||||||
|
$c1: rgba($colorBodyFg, 0.2);
|
||||||
|
$angle: 90deg;
|
||||||
|
@include background-image(linear-gradient($angle,
|
||||||
|
$c1 1px, $c2 1px,
|
||||||
|
$c2 100%
|
||||||
|
));
|
||||||
|
background-position: center $grabTicksYOffset;
|
||||||
|
background-repeat: repeat-x;
|
||||||
|
background-size: $grabTicksXSpace $grabTicksH;
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
@include background-image(linear-gradient(
|
||||||
|
$c0 70%, $c2 100%
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,8 +22,8 @@
|
|||||||
<div class="contents">
|
<div class="contents">
|
||||||
<div class="pane left menu-items">
|
<div class="pane left menu-items">
|
||||||
<ul>
|
<ul>
|
||||||
<li ng-repeat="(key, metadata) in ngModel.options"
|
<li ng-repeat="metadata in ngModel.options"
|
||||||
ng-click="ngModel.selectedKey=key">
|
ng-click="ngModel.selected = metadata">
|
||||||
<a ng-mouseover="ngModel.activeMetadata = metadata"
|
<a ng-mouseover="ngModel.activeMetadata = metadata"
|
||||||
ng-mouseleave="ngModel.activeMetadata = undefined"
|
ng-mouseleave="ngModel.activeMetadata = undefined"
|
||||||
class="menu-item-a {{metadata.cssClass}}">
|
class="menu-item-a {{metadata.cssClass}}">
|
||||||
@ -33,8 +33,7 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="pane right menu-item-description">
|
<div class="pane right menu-item-description">
|
||||||
<div
|
<div class="desc-area ui-symbol icon type-icon {{ngModel.activeMetadata.cssClass}}"></div>
|
||||||
class="desc-area ui-symbol icon type-icon {{ngModel.activeMetadata.cssClass}}"></div>
|
|
||||||
<div class="desc-area title">
|
<div class="desc-area title">
|
||||||
{{ngModel.activeMetadata.name}}
|
{{ngModel.activeMetadata.name}}
|
||||||
</div>
|
</div>
|
||||||
|
@ -22,8 +22,7 @@
|
|||||||
<span ng-controller="ClickAwayController as modeController">
|
<span ng-controller="ClickAwayController as modeController">
|
||||||
<div class="s-menu-button"
|
<div class="s-menu-button"
|
||||||
ng-click="modeController.toggle()">
|
ng-click="modeController.toggle()">
|
||||||
<span class="title-label">{{ngModel.options[ngModel.selectedKey]
|
<span class="title-label">{{ngModel.selected.name}}</span>
|
||||||
.label}}</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="menu super-menu mini mode-selector-menu"
|
<div class="menu super-menu mini mode-selector-menu"
|
||||||
ng-show="modeController.isActive()">
|
ng-show="modeController.isActive()">
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<!-- Parent holder for time conductor. follow-mode | fixed-mode -->
|
<!-- Parent holder for time conductor. follow-mode | fixed-mode -->
|
||||||
<div ng-controller="TimeConductorController as tcController"
|
<div ng-controller="TimeConductorController as tcController"
|
||||||
class="holder grows flex-elem l-flex-row l-time-conductor {{modeModel.selectedKey}}-mode {{timeSystemModel.selected.metadata.key}}-time-system"
|
class="holder grows flex-elem l-flex-row l-time-conductor {{modeModel.selectedKey}}-mode {{timeSystemModel.selected.metadata.key}}-time-system"
|
||||||
ng-class="{'status-panning': tcController.panning}" ng-show="showTimeConductor">
|
ng-class="{'status-panning': tcController.panning}">
|
||||||
<div class="flex-elem holder time-conductor-icon">
|
<div class="flex-elem holder time-conductor-icon">
|
||||||
<div class="hand-little"></div>
|
<div class="hand-little"></div>
|
||||||
<div class="hand-big"></div>
|
<div class="hand-big"></div>
|
||||||
@ -99,14 +99,14 @@
|
|||||||
<div class="l-time-conductor-controls l-row-elem l-flex-row flex-elem">
|
<div class="l-time-conductor-controls l-row-elem l-flex-row flex-elem">
|
||||||
<mct-include
|
<mct-include
|
||||||
key="'mode-selector'"
|
key="'mode-selector'"
|
||||||
ng-model="modeModel"
|
ng-model="tcController.menu"
|
||||||
class="holder flex-elem menus-up mode-selector">
|
class="holder flex-elem menus-up mode-selector">
|
||||||
</mct-include>
|
</mct-include>
|
||||||
<mct-control
|
<mct-control
|
||||||
key="'menu-button'"
|
key="'menu-button'"
|
||||||
class="holder flex-elem menus-up time-system"
|
class="holder flex-elem menus-up time-system"
|
||||||
structure="{
|
structure="{
|
||||||
text: timeSystemModel.selected.metadata.name,
|
text: timeSystemModel.selected.name,
|
||||||
click: tcController.selectTimeSystemByKey,
|
click: tcController.selectTimeSystemByKey,
|
||||||
options: timeSystemModel.options
|
options: timeSystemModel.options
|
||||||
}">
|
}">
|
||||||
|
@ -1,47 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web 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 Web 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 () {
|
|
||||||
/**
|
|
||||||
* A tick source is an event generator such as a timing signal, or
|
|
||||||
* indicator of data availability, which can be used to advance the Time
|
|
||||||
* Conductor. Usage is simple, a listener registers a callback which is
|
|
||||||
* invoked when this source 'ticks'.
|
|
||||||
*
|
|
||||||
* @interface
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function TickSource() {
|
|
||||||
this.listeners = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param callback Function to be called when this tick source ticks.
|
|
||||||
* @returns an 'unlisten' function that will remove the callback from
|
|
||||||
* the registered listeners
|
|
||||||
*/
|
|
||||||
TickSource.prototype.listen = function (callback) {
|
|
||||||
throw new Error('Not implemented');
|
|
||||||
};
|
|
||||||
|
|
||||||
return TickSource;
|
|
||||||
});
|
|
@ -1,107 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web 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 Web 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 () {
|
|
||||||
/**
|
|
||||||
* @interface
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function TimeSystem() {
|
|
||||||
/**
|
|
||||||
* @typedef TimeSystemMetadata
|
|
||||||
* @property {string} key
|
|
||||||
* @property {string} name
|
|
||||||
* @property {string} description
|
|
||||||
*
|
|
||||||
* @type {TimeSystemMetadata}
|
|
||||||
*/
|
|
||||||
this.metadata = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Time formats are defined as extensions. Time systems that implement
|
|
||||||
* this interface should provide an array of format keys supported by them.
|
|
||||||
*
|
|
||||||
* @returns {string[]} An array of time format keys
|
|
||||||
*/
|
|
||||||
TimeSystem.prototype.formats = function () {
|
|
||||||
throw new Error('Not implemented');
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef DeltaFormat
|
|
||||||
* @property {string} type the type of MctControl used to represent this
|
|
||||||
* field. Typically 'datetime-field' for UTC based dates, or 'textfield'
|
|
||||||
* otherwise
|
|
||||||
* @property {string} [format] An optional field specifying the
|
|
||||||
* Format to use for delta fields in this time system.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Specifies a format for deltas in this time system.
|
|
||||||
*
|
|
||||||
* @returns {DeltaFormat} a delta format specifier
|
|
||||||
*/
|
|
||||||
TimeSystem.prototype.deltaFormat = function () {
|
|
||||||
throw new Error('Not implemented');
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the tick sources supported by this time system. Tick sources
|
|
||||||
* are event generators that can be used to advance the time conductor
|
|
||||||
* @returns {TickSource[]} The tick sources supported by this time system.
|
|
||||||
*/
|
|
||||||
TimeSystem.prototype.tickSources = function () {
|
|
||||||
throw new Error('Not implemented');
|
|
||||||
};
|
|
||||||
|
|
||||||
/***
|
|
||||||
*
|
|
||||||
* @typedef {object} TimeConductorZoom
|
|
||||||
* @property {number} min The largest time span that the time
|
|
||||||
* conductor can display in this time system. ie. the span of the time
|
|
||||||
* conductor in its most zoomed out state.
|
|
||||||
* @property {number} max The smallest time span that the time
|
|
||||||
* conductor can display in this time system. ie. the span of the time
|
|
||||||
* conductor bounds in its most zoomed in state.
|
|
||||||
*
|
|
||||||
* @typedef {object} TimeSystemDefault
|
|
||||||
* @property {TimeConductorDeltas} deltas The deltas to apply by default
|
|
||||||
* when this time system is active. Applies to real-time modes only
|
|
||||||
* @property {TimeConductorBounds} bounds The bounds to apply by default
|
|
||||||
* when this time system is active
|
|
||||||
* @property {TimeConductorZoom} zoom Default min and max zoom levels
|
|
||||||
* @returns {TimeSystemDefault[]} At least one set of default values for
|
|
||||||
* this time system.
|
|
||||||
*/
|
|
||||||
TimeSystem.prototype.defaults = function () {
|
|
||||||
throw new Error('Not implemented');
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return {boolean}
|
|
||||||
*/
|
|
||||||
TimeSystem.prototype.isUTCBased = function () {
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
return TimeSystem;
|
|
||||||
});
|
|
@ -35,14 +35,14 @@ define(
|
|||||||
function ConductorAxisController(openmct, formatService, conductorViewService, scope, element) {
|
function ConductorAxisController(openmct, formatService, conductorViewService, scope, element) {
|
||||||
// Dependencies
|
// Dependencies
|
||||||
this.formatService = formatService;
|
this.formatService = formatService;
|
||||||
this.conductor = openmct.conductor;
|
this.timeAPI = openmct.time;
|
||||||
this.conductorViewService = conductorViewService;
|
this.conductorViewService = conductorViewService;
|
||||||
|
|
||||||
this.scope = scope;
|
this.scope = scope;
|
||||||
this.initialized = false;
|
this.initialized = false;
|
||||||
|
|
||||||
this.bounds = this.conductor.bounds();
|
this.bounds = this.timeAPI.bounds();
|
||||||
this.timeSystem = this.conductor.timeSystem();
|
this.timeSystem = this.timeAPI.timeSystem();
|
||||||
|
|
||||||
//Bind all class functions to 'this'
|
//Bind all class functions to 'this'
|
||||||
Object.keys(ConductorAxisController.prototype).filter(function (key) {
|
Object.keys(ConductorAxisController.prototype).filter(function (key) {
|
||||||
@ -58,8 +58,8 @@ define(
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
ConductorAxisController.prototype.destroy = function () {
|
ConductorAxisController.prototype.destroy = function () {
|
||||||
this.conductor.off('timeSystem', this.changeTimeSystem);
|
this.timeAPI.off('timeSystem', this.changeTimeSystem);
|
||||||
this.conductor.off('bounds', this.changeBounds);
|
this.timeAPI.off('bounds', this.changeBounds);
|
||||||
this.conductorViewService.off("zoom", this.onZoom);
|
this.conductorViewService.off("zoom", this.onZoom);
|
||||||
this.conductorViewService.off("zoom-stop", this.onZoomStop);
|
this.conductorViewService.off("zoom-stop", this.onZoomStop);
|
||||||
};
|
};
|
||||||
@ -87,8 +87,8 @@ define(
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Respond to changes in conductor
|
//Respond to changes in conductor
|
||||||
this.conductor.on("timeSystem", this.changeTimeSystem);
|
this.timeAPI.on("timeSystem", this.changeTimeSystem);
|
||||||
this.conductor.on("bounds", this.changeBounds);
|
this.timeAPI.on("bounds", this.changeBounds);
|
||||||
|
|
||||||
this.scope.$on("$destroy", this.destroy);
|
this.scope.$on("$destroy", this.destroy);
|
||||||
|
|
||||||
@ -111,7 +111,7 @@ define(
|
|||||||
*/
|
*/
|
||||||
ConductorAxisController.prototype.setScale = function () {
|
ConductorAxisController.prototype.setScale = function () {
|
||||||
var width = this.target.offsetWidth;
|
var width = this.target.offsetWidth;
|
||||||
var timeSystem = this.conductor.timeSystem();
|
var timeSystem = this.timeAPI.getTimeSystem(this.timeAPI.timeSystem());
|
||||||
var bounds = this.bounds;
|
var bounds = this.bounds;
|
||||||
|
|
||||||
if (timeSystem.isUTCBased()) {
|
if (timeSystem.isUTCBased()) {
|
||||||
@ -134,13 +134,15 @@ define(
|
|||||||
* When the time system changes, update the scale and formatter used for showing times.
|
* When the time system changes, update the scale and formatter used for showing times.
|
||||||
* @param timeSystem
|
* @param timeSystem
|
||||||
*/
|
*/
|
||||||
ConductorAxisController.prototype.changeTimeSystem = function (timeSystem) {
|
ConductorAxisController.prototype.changeTimeSystem = function (key) {
|
||||||
this.timeSystem = timeSystem;
|
var timeSystem = this.timeAPI.getTimeSystem(key);
|
||||||
|
|
||||||
|
this.timeSystem = key;
|
||||||
|
|
||||||
var key = timeSystem.formats()[0];
|
var key = timeSystem.formats()[0];
|
||||||
if (key !== undefined) {
|
if (key !== undefined) {
|
||||||
var format = this.formatService.getFormat(key);
|
var format = this.formatService.getFormat(key);
|
||||||
var bounds = this.conductor.bounds();
|
var bounds = this.timeAPI.bounds();
|
||||||
|
|
||||||
//The D3 scale used depends on the type of time system as d3
|
//The D3 scale used depends on the type of time system as d3
|
||||||
// supports UTC out of the box.
|
// supports UTC out of the box.
|
||||||
@ -178,7 +180,7 @@ define(
|
|||||||
ConductorAxisController.prototype.panStop = function () {
|
ConductorAxisController.prototype.panStop = function () {
|
||||||
//resync view bounds with time conductor bounds
|
//resync view bounds with time conductor bounds
|
||||||
this.conductorViewService.emit("pan-stop");
|
this.conductorViewService.emit("pan-stop");
|
||||||
this.conductor.bounds(this.bounds);
|
this.timeAPI.bounds(this.bounds);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -214,9 +216,9 @@ define(
|
|||||||
* @fires platform.features.conductor.ConductorAxisController~pan
|
* @fires platform.features.conductor.ConductorAxisController~pan
|
||||||
*/
|
*/
|
||||||
ConductorAxisController.prototype.pan = function (delta) {
|
ConductorAxisController.prototype.pan = function (delta) {
|
||||||
if (!this.conductor.follow()) {
|
if (!this.timeAPI.follow()) {
|
||||||
var deltaInMs = delta[0] * this.msPerPixel;
|
var deltaInMs = delta[0] * this.msPerPixel;
|
||||||
var bounds = this.conductor.bounds();
|
var bounds = this.timeAPI.bounds();
|
||||||
var start = Math.floor((bounds.start - deltaInMs) / 1000) * 1000;
|
var start = Math.floor((bounds.start - deltaInMs) / 1000) * 1000;
|
||||||
var end = Math.floor((bounds.end - deltaInMs) / 1000) * 1000;
|
var end = Math.floor((bounds.end - deltaInMs) / 1000) * 1000;
|
||||||
this.bounds = {
|
this.bounds = {
|
||||||
|
@ -30,7 +30,7 @@ define(
|
|||||||
* @memberof platform.features.conductor
|
* @memberof platform.features.conductor
|
||||||
*/
|
*/
|
||||||
function ConductorTOIController($scope, openmct, conductorViewService) {
|
function ConductorTOIController($scope, openmct, conductorViewService) {
|
||||||
this.conductor = openmct.conductor;
|
this.timeAPI = openmct.time;
|
||||||
this.conductorViewService = conductorViewService;
|
this.conductorViewService = conductorViewService;
|
||||||
|
|
||||||
//Bind all class functions to 'this'
|
//Bind all class functions to 'this'
|
||||||
@ -40,11 +40,11 @@ define(
|
|||||||
this[key] = ConductorTOIController.prototype[key].bind(this);
|
this[key] = ConductorTOIController.prototype[key].bind(this);
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
|
|
||||||
this.conductor.on('timeOfInterest', this.changeTimeOfInterest);
|
this.timeAPI.on('timeOfInterest', this.changeTimeOfInterest);
|
||||||
this.conductorViewService.on('zoom', this.setOffsetFromZoom);
|
this.conductorViewService.on('zoom', this.setOffsetFromZoom);
|
||||||
this.conductorViewService.on('pan', this.setOffsetFromBounds);
|
this.conductorViewService.on('pan', this.setOffsetFromBounds);
|
||||||
|
|
||||||
var timeOfInterest = this.conductor.timeOfInterest();
|
var timeOfInterest = this.timeAPI.timeOfInterest();
|
||||||
if (timeOfInterest) {
|
if (timeOfInterest) {
|
||||||
this.changeTimeOfInterest(timeOfInterest);
|
this.changeTimeOfInterest(timeOfInterest);
|
||||||
}
|
}
|
||||||
@ -56,7 +56,7 @@ define(
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
ConductorTOIController.prototype.destroy = function () {
|
ConductorTOIController.prototype.destroy = function () {
|
||||||
this.conductor.off('timeOfInterest', this.changeTimeOfInterest);
|
this.timeAPI.off('timeOfInterest', this.changeTimeOfInterest);
|
||||||
this.conductorViewService.off('zoom', this.setOffsetFromZoom);
|
this.conductorViewService.off('zoom', this.setOffsetFromZoom);
|
||||||
this.conductorViewService.off('pan', this.setOffsetFromBounds);
|
this.conductorViewService.off('pan', this.setOffsetFromBounds);
|
||||||
};
|
};
|
||||||
@ -70,7 +70,7 @@ define(
|
|||||||
* @param {TimeConductorBounds} bounds
|
* @param {TimeConductorBounds} bounds
|
||||||
*/
|
*/
|
||||||
ConductorTOIController.prototype.setOffsetFromBounds = function (bounds) {
|
ConductorTOIController.prototype.setOffsetFromBounds = function (bounds) {
|
||||||
var toi = this.conductor.timeOfInterest();
|
var toi = this.timeAPI.timeOfInterest();
|
||||||
if (toi !== undefined) {
|
if (toi !== undefined) {
|
||||||
var offset = toi - bounds.start;
|
var offset = toi - bounds.start;
|
||||||
var duration = bounds.end - bounds.start;
|
var duration = bounds.end - bounds.start;
|
||||||
@ -94,7 +94,7 @@ define(
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
ConductorTOIController.prototype.changeTimeOfInterest = function () {
|
ConductorTOIController.prototype.changeTimeOfInterest = function () {
|
||||||
var bounds = this.conductor.bounds();
|
var bounds = this.timeAPI.bounds();
|
||||||
if (bounds) {
|
if (bounds) {
|
||||||
this.setOffsetFromBounds(bounds);
|
this.setOffsetFromBounds(bounds);
|
||||||
}
|
}
|
||||||
@ -112,10 +112,10 @@ define(
|
|||||||
var width = element.width();
|
var width = element.width();
|
||||||
var relativeX = e.pageX - element.offset().left;
|
var relativeX = e.pageX - element.offset().left;
|
||||||
var percX = relativeX / width;
|
var percX = relativeX / width;
|
||||||
var bounds = this.conductor.bounds();
|
var bounds = this.timeAPI.bounds();
|
||||||
var timeRange = bounds.end - bounds.start;
|
var timeRange = bounds.end - bounds.start;
|
||||||
|
|
||||||
this.conductor.timeOfInterest(timeRange * percX + bounds.start);
|
this.timeAPI.timeOfInterest(timeRange * percX + bounds.start);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -47,8 +47,7 @@ define(
|
|||||||
openmct,
|
openmct,
|
||||||
conductorViewService,
|
conductorViewService,
|
||||||
formatService,
|
formatService,
|
||||||
DEFAULT_MODE,
|
config
|
||||||
SHOW_TIMECONDUCTOR
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
@ -64,19 +63,22 @@ define(
|
|||||||
this.$window = $window;
|
this.$window = $window;
|
||||||
this.$location = $location;
|
this.$location = $location;
|
||||||
this.conductorViewService = conductorViewService;
|
this.conductorViewService = conductorViewService;
|
||||||
this.conductor = openmct.conductor;
|
this.timeAPI = openmct.time;
|
||||||
this.modes = conductorViewService.availableModes();
|
this.modes = conductorViewService.availableModes();
|
||||||
this.validation = new TimeConductorValidation(this.conductor);
|
this.validation = new TimeConductorValidation(this.timeAPI);
|
||||||
this.formatService = formatService;
|
this.formatService = formatService;
|
||||||
|
this.config = config;
|
||||||
|
|
||||||
//Check if the default mode defined is actually available
|
var options = this.optionsFromConfig(config);
|
||||||
if (this.modes[DEFAULT_MODE] === undefined) {
|
this.menu = {
|
||||||
DEFAULT_MODE = 'fixed';
|
selected: options[0],
|
||||||
}
|
options: options
|
||||||
this.DEFAULT_MODE = DEFAULT_MODE;
|
};
|
||||||
|
|
||||||
// Construct the provided time system definitions
|
// Construct the provided time system definitions
|
||||||
this.timeSystems = conductorViewService.systems;
|
this.timeSystems = config.menuOptions.map(function (menuOption){
|
||||||
|
return openmct.time.getTimeSystem(menuOption.timeSystem);
|
||||||
|
});
|
||||||
|
|
||||||
this.initializeScope();
|
this.initializeScope();
|
||||||
var searchParams = JSON.parse(JSON.stringify(this.$location.search()));
|
var searchParams = JSON.parse(JSON.stringify(this.$location.search()));
|
||||||
@ -84,9 +86,9 @@ define(
|
|||||||
this.setStateFromSearchParams(searchParams);
|
this.setStateFromSearchParams(searchParams);
|
||||||
|
|
||||||
//Set the initial state of the UI from the conductor state
|
//Set the initial state of the UI from the conductor state
|
||||||
var timeSystem = this.conductor.timeSystem();
|
var timeSystem = this.timeAPI.timeSystem();
|
||||||
if (timeSystem) {
|
if (timeSystem) {
|
||||||
this.changeTimeSystem(this.conductor.timeSystem());
|
this.changeTimeSystem(timeSystem);
|
||||||
}
|
}
|
||||||
|
|
||||||
var deltas = this.conductorViewService.deltas();
|
var deltas = this.conductorViewService.deltas();
|
||||||
@ -94,7 +96,7 @@ define(
|
|||||||
this.setFormFromDeltas(deltas);
|
this.setFormFromDeltas(deltas);
|
||||||
}
|
}
|
||||||
|
|
||||||
var bounds = this.conductor.bounds();
|
var bounds = this.timeAPI.bounds();
|
||||||
if (bounds && bounds.start !== undefined && bounds.end !== undefined) {
|
if (bounds && bounds.start !== undefined && bounds.end !== undefined) {
|
||||||
this.changeBounds(bounds);
|
this.changeBounds(bounds);
|
||||||
}
|
}
|
||||||
@ -105,12 +107,31 @@ define(
|
|||||||
}.bind(this));
|
}.bind(this));
|
||||||
|
|
||||||
//Respond to any subsequent conductor changes
|
//Respond to any subsequent conductor changes
|
||||||
this.conductor.on('bounds', this.changeBounds);
|
this.timeAPI.on('bounds', this.changeBounds);
|
||||||
this.conductor.on('timeSystem', this.changeTimeSystem);
|
this.timeAPI.on('timeSystem', this.changeTimeSystem);
|
||||||
|
|
||||||
this.$scope.showTimeConductor = SHOW_TIMECONDUCTOR;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TimeConductorController.prototype.optionsFromConfig = function (config) {
|
||||||
|
var options = [{
|
||||||
|
name: 'Fixed Timespan Mode',
|
||||||
|
description: 'Query and explore data that falls between two fixed datetimes',
|
||||||
|
cssClass: 'icon-calendar',
|
||||||
|
clock: undefined
|
||||||
|
}];
|
||||||
|
var timeAPI = this.timeAPI;
|
||||||
|
|
||||||
|
(config.menuOptions || []).forEach(function (menuOption) {
|
||||||
|
options.push({
|
||||||
|
name: menuOption.name,
|
||||||
|
description: menuOption.description,
|
||||||
|
cssClass: menuOption.cssClass || '',
|
||||||
|
clock: timeAPI.getClock(menuOption.clock)
|
||||||
|
});
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
return options;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used as a url search param setter in place of $location.search(...)
|
* Used as a url search param setter in place of $location.search(...)
|
||||||
*
|
*
|
||||||
@ -129,7 +150,7 @@ define(
|
|||||||
*/
|
*/
|
||||||
TimeConductorController.prototype.initializeScope = function () {
|
TimeConductorController.prototype.initializeScope = function () {
|
||||||
//Set time Conductor bounds in the form
|
//Set time Conductor bounds in the form
|
||||||
this.$scope.boundsModel = this.conductor.bounds();
|
this.$scope.boundsModel = this.timeAPI.bounds();
|
||||||
|
|
||||||
//If conductor has a time system selected already, populate the
|
//If conductor has a time system selected already, populate the
|
||||||
//form from it
|
//form from it
|
||||||
@ -154,11 +175,11 @@ define(
|
|||||||
//Set mode from url if changed
|
//Set mode from url if changed
|
||||||
if (searchParams[SEARCH.MODE] === undefined ||
|
if (searchParams[SEARCH.MODE] === undefined ||
|
||||||
searchParams[SEARCH.MODE] !== this.$scope.modeModel.selectedKey) {
|
searchParams[SEARCH.MODE] !== this.$scope.modeModel.selectedKey) {
|
||||||
this.setMode(searchParams[SEARCH.MODE] || this.DEFAULT_MODE);
|
this.setMode(searchParams[SEARCH.MODE] || 'fixed');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (searchParams[SEARCH.TIME_SYSTEM] &&
|
if (searchParams[SEARCH.TIME_SYSTEM] &&
|
||||||
searchParams[SEARCH.TIME_SYSTEM] !== this.conductor.timeSystem().metadata.key) {
|
searchParams[SEARCH.TIME_SYSTEM] !== this.timeAPI.timeSystem()) {
|
||||||
//Will select the specified time system on the conductor
|
//Will select the specified time system on the conductor
|
||||||
this.selectTimeSystemByKey(searchParams[SEARCH.TIME_SYSTEM]);
|
this.selectTimeSystemByKey(searchParams[SEARCH.TIME_SYSTEM]);
|
||||||
}
|
}
|
||||||
@ -184,7 +205,7 @@ define(
|
|||||||
!isNaN(searchParams[SEARCH.END_BOUND]);
|
!isNaN(searchParams[SEARCH.END_BOUND]);
|
||||||
|
|
||||||
if (validBounds) {
|
if (validBounds) {
|
||||||
this.conductor.bounds({
|
this.timeAPI.bounds({
|
||||||
start: parseInt(searchParams[SEARCH.START_BOUND]),
|
start: parseInt(searchParams[SEARCH.START_BOUND]),
|
||||||
end: parseInt(searchParams[SEARCH.END_BOUND])
|
end: parseInt(searchParams[SEARCH.END_BOUND])
|
||||||
});
|
});
|
||||||
@ -195,8 +216,8 @@ define(
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
TimeConductorController.prototype.destroy = function () {
|
TimeConductorController.prototype.destroy = function () {
|
||||||
this.conductor.off('bounds', this.changeBounds);
|
this.timeAPI.off('bounds', this.changeBounds);
|
||||||
this.conductor.off('timeSystem', this.changeTimeSystem);
|
this.timeAPI.off('timeSystem', this.changeTimeSystem);
|
||||||
|
|
||||||
this.conductorViewService.off('pan', this.onPan);
|
this.conductorViewService.off('pan', this.onPan);
|
||||||
this.conductorViewService.off('pan-stop', this.onPanStop);
|
this.conductorViewService.off('pan-stop', this.onPanStop);
|
||||||
@ -253,10 +274,7 @@ define(
|
|||||||
this.$scope.modeModel.selectedKey = mode;
|
this.$scope.modeModel.selectedKey = mode;
|
||||||
//Synchronize scope with time system on mode
|
//Synchronize scope with time system on mode
|
||||||
this.$scope.timeSystemModel.options =
|
this.$scope.timeSystemModel.options =
|
||||||
this.conductorViewService.availableTimeSystems()
|
this.conductorViewService.availableTimeSystems();
|
||||||
.map(function (t) {
|
|
||||||
return t.metadata;
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -289,7 +307,7 @@ define(
|
|||||||
* @param formModel
|
* @param formModel
|
||||||
*/
|
*/
|
||||||
TimeConductorController.prototype.setBounds = function (boundsModel) {
|
TimeConductorController.prototype.setBounds = function (boundsModel) {
|
||||||
this.conductor.bounds({
|
this.timeAPI.bounds({
|
||||||
start: boundsModel.start,
|
start: boundsModel.start,
|
||||||
end: boundsModel.end
|
end: boundsModel.end
|
||||||
});
|
});
|
||||||
@ -364,7 +382,7 @@ define(
|
|||||||
})[0];
|
})[0];
|
||||||
if (selected) {
|
if (selected) {
|
||||||
this.supportsZoom = !!(selected.defaults() && selected.defaults().zoom);
|
this.supportsZoom = !!(selected.defaults() && selected.defaults().zoom);
|
||||||
this.conductor.timeSystem(selected, selected.defaults().bounds);
|
this.timeAPI.timeSystem(selected, selected.defaults().bounds);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -376,9 +394,11 @@ define(
|
|||||||
*
|
*
|
||||||
* @param newTimeSystem
|
* @param newTimeSystem
|
||||||
*/
|
*/
|
||||||
TimeConductorController.prototype.changeTimeSystem = function (newTimeSystem) {
|
TimeConductorController.prototype.changeTimeSystem = function (key) {
|
||||||
|
var newTimeSystem = this.timeAPI.getTimeSystem(key);
|
||||||
|
|
||||||
//Set time system in URL on change
|
//Set time system in URL on change
|
||||||
this.setParam(SEARCH.TIME_SYSTEM, newTimeSystem.metadata.key);
|
this.setParam(SEARCH.TIME_SYSTEM, key);
|
||||||
|
|
||||||
if (newTimeSystem && (newTimeSystem !== this.$scope.timeSystemModel.selected)) {
|
if (newTimeSystem && (newTimeSystem !== this.$scope.timeSystemModel.selected)) {
|
||||||
this.supportsZoom = !!(newTimeSystem.defaults() && newTimeSystem.defaults().zoom);
|
this.supportsZoom = !!(newTimeSystem.defaults() && newTimeSystem.defaults().zoom);
|
||||||
@ -401,9 +421,9 @@ define(
|
|||||||
* @returns {number} a value between 0.01 and 0.99, in increments of .01
|
* @returns {number} a value between 0.01 and 0.99, in increments of .01
|
||||||
*/
|
*/
|
||||||
TimeConductorController.prototype.toSliderValue = function (timeSpan) {
|
TimeConductorController.prototype.toSliderValue = function (timeSpan) {
|
||||||
var timeSystem = this.conductor.timeSystem();
|
var timeSystem = this.timeAPI.getTimeSystem(this.timeAPI.timeSystem());
|
||||||
if (timeSystem) {
|
if (timeSystem) {
|
||||||
var zoomDefaults = this.conductor.timeSystem().defaults().zoom;
|
var zoomDefaults = timeSystem.defaults().zoom;
|
||||||
var perc = timeSpan / (zoomDefaults.min - zoomDefaults.max);
|
var perc = timeSpan / (zoomDefaults.min - zoomDefaults.max);
|
||||||
return 1 - Math.pow(perc, 1 / 4);
|
return 1 - Math.pow(perc, 1 / 4);
|
||||||
}
|
}
|
||||||
@ -415,8 +435,9 @@ define(
|
|||||||
* @param {TimeSpan} timeSpan
|
* @param {TimeSpan} timeSpan
|
||||||
*/
|
*/
|
||||||
TimeConductorController.prototype.toTimeUnits = function (timeSpan) {
|
TimeConductorController.prototype.toTimeUnits = function (timeSpan) {
|
||||||
if (this.conductor.timeSystem()) {
|
var timeSystem = this.timeAPI.getTimeSystem(this.timeAPI.timeSystem());
|
||||||
var timeFormat = this.formatService.getFormat(this.conductor.timeSystem().formats()[0]);
|
if (this.timeAPI.timeSystem()) {
|
||||||
|
var timeFormat = this.formatService.getFormat(timeSystem.formats()[0]);
|
||||||
this.$scope.timeUnits = timeFormat.timeUnits && timeFormat.timeUnits(timeSpan);
|
this.$scope.timeUnits = timeFormat.timeUnits && timeFormat.timeUnits(timeSpan);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -429,7 +450,8 @@ define(
|
|||||||
* @param bounds
|
* @param bounds
|
||||||
*/
|
*/
|
||||||
TimeConductorController.prototype.onZoom = function (sliderValue) {
|
TimeConductorController.prototype.onZoom = function (sliderValue) {
|
||||||
var zoomDefaults = this.conductor.timeSystem().defaults().zoom;
|
var timeSystem = this.timeAPI.getTimeSystem(this.timeAPI.timeSystem());
|
||||||
|
var zoomDefaults = timeSystem.defaults().zoom;
|
||||||
var timeSpan = Math.pow((1 - sliderValue), 4) * (zoomDefaults.min - zoomDefaults.max);
|
var timeSpan = Math.pow((1 - sliderValue), 4) * (zoomDefaults.min - zoomDefaults.max);
|
||||||
|
|
||||||
var zoom = this.conductorViewService.zoom(timeSpan);
|
var zoom = this.conductorViewService.zoom(timeSpan);
|
||||||
|
@ -31,38 +31,23 @@ define(
|
|||||||
* @memberof platform.features.conductor
|
* @memberof platform.features.conductor
|
||||||
* @param {TimeConductorMetadata} metadata
|
* @param {TimeConductorMetadata} metadata
|
||||||
*/
|
*/
|
||||||
function TimeConductorMode(metadata, conductor, timeSystems) {
|
function TimeConductorMode(metadata, timeAPI) {
|
||||||
this.conductor = conductor;
|
this.timeAPI = timeAPI;
|
||||||
|
|
||||||
this.mdata = metadata;
|
this.mdata = metadata;
|
||||||
this.deltasVal = undefined;
|
|
||||||
this.source = undefined;
|
|
||||||
this.sourceUnlisten = undefined;
|
|
||||||
this.systems = timeSystems;
|
|
||||||
this.availableSources = undefined;
|
|
||||||
this.changeTimeSystem = this.changeTimeSystem.bind(this);
|
this.changeTimeSystem = this.changeTimeSystem.bind(this);
|
||||||
this.tick = this.tick.bind(this);
|
|
||||||
|
var timeSystem = this.timeAPI.timeSystem();
|
||||||
|
|
||||||
//Set the time system initially
|
//Set the time system initially
|
||||||
if (conductor.timeSystem()) {
|
if (timeSystem) {
|
||||||
this.changeTimeSystem(conductor.timeSystem());
|
this.changeTimeSystem(timeSystem);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Listen for subsequent changes to time system
|
//Listen for subsequent changes to time system
|
||||||
conductor.on('timeSystem', this.changeTimeSystem);
|
timeAPI.on('timeSystem', this.changeTimeSystem);
|
||||||
|
|
||||||
if (metadata.key === 'fixed') {
|
this.availableSystems = timeAPI.availableTimeSystems();
|
||||||
//Fixed automatically supports all time systems
|
|
||||||
this.availableSystems = timeSystems;
|
|
||||||
} else {
|
|
||||||
this.availableSystems = timeSystems.filter(function (timeSystem) {
|
|
||||||
//Only include time systems that have tick sources that
|
|
||||||
// support the current mode
|
|
||||||
return timeSystem.tickSources().some(function (tickSource) {
|
|
||||||
return metadata.key === tickSource.metadata.mode;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -70,7 +55,8 @@ define(
|
|||||||
* @param timeSystem
|
* @param timeSystem
|
||||||
* @returns {TimeSystem} the currently selected time system
|
* @returns {TimeSystem} the currently selected time system
|
||||||
*/
|
*/
|
||||||
TimeConductorMode.prototype.changeTimeSystem = function (timeSystem) {
|
TimeConductorMode.prototype.changeTimeSystem = function (key) {
|
||||||
|
var timeSystem = this.timeAPI.getTimeSystem(key);
|
||||||
// On time system change, apply default deltas
|
// On time system change, apply default deltas
|
||||||
var defaults = timeSystem.defaults() || {
|
var defaults = timeSystem.defaults() || {
|
||||||
bounds: {
|
bounds: {
|
||||||
@ -83,20 +69,8 @@ define(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.conductor.bounds(defaults.bounds);
|
this.timeAPI.bounds(defaults.bounds);
|
||||||
this.deltas(defaults.deltas);
|
this.deltas(defaults.deltas);
|
||||||
|
|
||||||
// Tick sources are mode-specific, so restrict tick sources to only those supported by the current mode.
|
|
||||||
var key = this.mdata.key;
|
|
||||||
var tickSources = timeSystem.tickSources();
|
|
||||||
if (tickSources) {
|
|
||||||
this.availableSources = tickSources.filter(function (source) {
|
|
||||||
return source.metadata.mode === key;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set an appropriate tick source from the new time system
|
|
||||||
this.tickSource(this.availableTickSources(timeSystem)[0]);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -110,15 +84,6 @@ define(
|
|||||||
return this.availableSystems;
|
return this.availableSystems;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Tick sources are mode-specific. This returns a filtered list of the tick sources available in the currently selected mode
|
|
||||||
* @param timeSystem
|
|
||||||
* @returns {Array.<T>}
|
|
||||||
*/
|
|
||||||
TimeConductorMode.prototype.availableTickSources = function (timeSystem) {
|
|
||||||
return this.availableSources;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get or set tick source. Setting tick source will also start
|
* Get or set tick source. Setting tick source will also start
|
||||||
* listening to it and unlisten from any existing tick source
|
* listening to it and unlisten from any existing tick source
|
||||||
@ -127,49 +92,28 @@ define(
|
|||||||
*/
|
*/
|
||||||
TimeConductorMode.prototype.tickSource = function (tickSource) {
|
TimeConductorMode.prototype.tickSource = function (tickSource) {
|
||||||
if (arguments.length > 0) {
|
if (arguments.length > 0) {
|
||||||
if (this.sourceUnlisten) {
|
var timeSystem = this.timeAPI.getTimeSystem(this.timeAPI.timeSystem());
|
||||||
this.sourceUnlisten();
|
var defaults = timeSystem.defaults() || {
|
||||||
}
|
bounds: {
|
||||||
this.source = tickSource;
|
start: 0,
|
||||||
if (tickSource) {
|
end: 0
|
||||||
this.sourceUnlisten = tickSource.listen(this.tick);
|
},
|
||||||
//Now following a tick source
|
deltas: {
|
||||||
this.conductor.follow(true);
|
start: 0,
|
||||||
} else {
|
end: 0
|
||||||
this.conductor.follow(false);
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
this.timeAPI.tickSource(tickSource, defaults.deltas);
|
||||||
}
|
}
|
||||||
return this.source;
|
return this.timeAPI.tickSource();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
TimeConductorMode.prototype.destroy = function () {
|
TimeConductorMode.prototype.destroy = function () {
|
||||||
this.conductor.off('timeSystem', this.changeTimeSystem);
|
this.timeAPI.off('timeSystem', this.changeTimeSystem);
|
||||||
|
|
||||||
if (this.sourceUnlisten) {
|
|
||||||
this.sourceUnlisten();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @param {number} time some value that is valid in the current TimeSystem
|
|
||||||
*/
|
|
||||||
TimeConductorMode.prototype.tick = function (time) {
|
|
||||||
var deltas = this.deltas();
|
|
||||||
var startTime = time;
|
|
||||||
var endTime = time;
|
|
||||||
|
|
||||||
if (deltas) {
|
|
||||||
startTime = time - deltas.start;
|
|
||||||
endTime = time + deltas.end;
|
|
||||||
}
|
|
||||||
this.conductor.bounds({
|
|
||||||
start: startTime,
|
|
||||||
end: endTime
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -182,12 +126,13 @@ define(
|
|||||||
TimeConductorMode.prototype.deltas = function (deltas) {
|
TimeConductorMode.prototype.deltas = function (deltas) {
|
||||||
if (arguments.length !== 0) {
|
if (arguments.length !== 0) {
|
||||||
var bounds = this.calculateBoundsFromDeltas(deltas);
|
var bounds = this.calculateBoundsFromDeltas(deltas);
|
||||||
this.deltasVal = deltas;
|
this.timeAPI.clockOffsets(deltas);
|
||||||
|
|
||||||
if (this.metadata().key !== 'fixed') {
|
if (this.metadata().key !== 'fixed') {
|
||||||
this.conductor.bounds(bounds);
|
this.timeAPI.bounds(bounds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this.deltasVal;
|
return this.timeAPI.clockOffsets();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -195,11 +140,12 @@ define(
|
|||||||
* @returns {TimeConductorBounds}
|
* @returns {TimeConductorBounds}
|
||||||
*/
|
*/
|
||||||
TimeConductorMode.prototype.calculateBoundsFromDeltas = function (deltas) {
|
TimeConductorMode.prototype.calculateBoundsFromDeltas = function (deltas) {
|
||||||
var oldEnd = this.conductor.bounds().end;
|
var oldEnd = this.timeAPI.bounds().end;
|
||||||
|
var offsets = this.timeAPI.clockOffsets();
|
||||||
|
|
||||||
if (this.deltasVal && this.deltasVal.end !== undefined) {
|
if (offsets && offsets.end !== undefined) {
|
||||||
//Calculate the previous raw end value (without delta)
|
//Calculate the previous raw end value (without delta)
|
||||||
oldEnd = oldEnd - this.deltasVal.end;
|
oldEnd = oldEnd - offsets.end;
|
||||||
}
|
}
|
||||||
|
|
||||||
var bounds = {
|
var bounds = {
|
||||||
@ -222,18 +168,19 @@ define(
|
|||||||
*/
|
*/
|
||||||
TimeConductorMode.prototype.calculateZoom = function (timeSpan) {
|
TimeConductorMode.prototype.calculateZoom = function (timeSpan) {
|
||||||
var zoom = {};
|
var zoom = {};
|
||||||
|
var offsets;
|
||||||
|
|
||||||
// If a tick source is defined, then the concept of 'now' is
|
// If a tick source is defined, then the concept of 'now' is
|
||||||
// important. Calculate zoom based on 'now'.
|
// important. Calculate zoom based on 'now'.
|
||||||
if (this.tickSource()) {
|
if (this.timeAPI.follow()) {
|
||||||
|
offsets = this.timeAPI.clockOffsets();
|
||||||
zoom.deltas = {
|
zoom.deltas = {
|
||||||
start: timeSpan,
|
start: timeSpan,
|
||||||
end: this.deltasVal.end
|
end: offsets.end
|
||||||
};
|
};
|
||||||
zoom.bounds = this.calculateBoundsFromDeltas(zoom.deltas);
|
zoom.bounds = this.calculateBoundsFromDeltas(zoom.deltas);
|
||||||
// Calculate bounds based on deltas;
|
|
||||||
} else {
|
} else {
|
||||||
var bounds = this.conductor.bounds();
|
var bounds = this.timeAPI.bounds();
|
||||||
var center = bounds.start + ((bounds.end - bounds.start)) / 2;
|
var center = bounds.start + ((bounds.end - bounds.start)) / 2;
|
||||||
bounds.start = center - timeSpan / 2;
|
bounds.start = center - timeSpan / 2;
|
||||||
bounds.end = center + timeSpan / 2;
|
bounds.end = center + timeSpan / 2;
|
||||||
|
@ -29,9 +29,9 @@ define(
|
|||||||
* @param conductor
|
* @param conductor
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
function TimeConductorValidation(conductor) {
|
function TimeConductorValidation(timeAPI) {
|
||||||
var self = this;
|
var self = this;
|
||||||
this.conductor = conductor;
|
this.timeAPI = timeAPI;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Bind all class functions to 'this'
|
* Bind all class functions to 'this'
|
||||||
@ -47,13 +47,13 @@ define(
|
|||||||
* Validation methods below are invoked directly from controls in the TimeConductor form
|
* Validation methods below are invoked directly from controls in the TimeConductor form
|
||||||
*/
|
*/
|
||||||
TimeConductorValidation.prototype.validateStart = function (start) {
|
TimeConductorValidation.prototype.validateStart = function (start) {
|
||||||
var bounds = this.conductor.bounds();
|
var bounds = this.timeAPI.bounds();
|
||||||
return this.conductor.validateBounds({start: start, end: bounds.end}) === true;
|
return this.timeAPI.validateBounds({start: start, end: bounds.end}) === true;
|
||||||
};
|
};
|
||||||
|
|
||||||
TimeConductorValidation.prototype.validateEnd = function (end) {
|
TimeConductorValidation.prototype.validateEnd = function (end) {
|
||||||
var bounds = this.conductor.bounds();
|
var bounds = this.timeAPI.bounds();
|
||||||
return this.conductor.validateBounds({start: bounds.start, end: end}) === true;
|
return this.timeAPI.validateBounds({start: bounds.start, end: end}) === true;
|
||||||
};
|
};
|
||||||
|
|
||||||
TimeConductorValidation.prototype.validateStartDelta = function (startDelta) {
|
TimeConductorValidation.prototype.validateStartDelta = function (startDelta) {
|
||||||
|
@ -41,11 +41,7 @@ define(
|
|||||||
|
|
||||||
EventEmitter.call(this);
|
EventEmitter.call(this);
|
||||||
|
|
||||||
this.systems = timeSystems.map(function (timeSystemConstructor) {
|
this.timeAPI = openmct.time;
|
||||||
return timeSystemConstructor();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.conductor = openmct.conductor;
|
|
||||||
this.currentMode = undefined;
|
this.currentMode = undefined;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -67,39 +63,25 @@ define(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function hasTickSource(sourceType, timeSystem) {
|
|
||||||
return timeSystem.tickSources().some(function (tickSource) {
|
|
||||||
return tickSource.metadata.mode === sourceType;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var timeSystemsForMode = function (sourceType) {
|
|
||||||
return this.systems.filter(hasTickSource.bind(this, sourceType));
|
|
||||||
}.bind(this);
|
|
||||||
|
|
||||||
//Only show 'real-time mode' if appropriate time systems available
|
//Only show 'real-time mode' if appropriate time systems available
|
||||||
if (timeSystemsForMode('realtime').length > 0) {
|
var realtimeMode = {
|
||||||
var realtimeMode = {
|
key: 'realtime',
|
||||||
key: 'realtime',
|
cssClass: 'icon-clock',
|
||||||
cssClass: 'icon-clock',
|
label: 'Real-time',
|
||||||
label: 'Real-time',
|
name: 'Real-time Mode',
|
||||||
name: 'Real-time Mode',
|
description: 'Monitor real-time streaming data as it comes in. The Time Conductor and displays will automatically advance themselves based on a UTC clock.'
|
||||||
description: 'Monitor real-time streaming data as it comes in. The Time Conductor and displays will automatically advance themselves based on a UTC clock.'
|
};
|
||||||
};
|
this.availModes[realtimeMode.key] = realtimeMode;
|
||||||
this.availModes[realtimeMode.key] = realtimeMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Only show 'LAD mode' if appropriate time systems available
|
//Only show 'LAD mode' if appropriate time systems available
|
||||||
if (timeSystemsForMode('lad').length > 0) {
|
var ladMode = {
|
||||||
var ladMode = {
|
key: 'lad',
|
||||||
key: 'lad',
|
cssClass: 'icon-database',
|
||||||
cssClass: 'icon-database',
|
label: 'LAD',
|
||||||
label: 'LAD',
|
name: 'LAD Mode',
|
||||||
name: 'LAD Mode',
|
description: 'Latest Available Data mode monitors real-time streaming data as it comes in. The Time Conductor and displays will only advance when data becomes available.'
|
||||||
description: 'Latest Available Data mode monitors real-time streaming data as it comes in. The Time Conductor and displays will only advance when data becomes available.'
|
};
|
||||||
};
|
this.availModes[ladMode.key] = ladMode;
|
||||||
this.availModes[ladMode.key] = ladMode;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TimeConductorViewService.prototype = Object.create(EventEmitter.prototype);
|
TimeConductorViewService.prototype = Object.create(EventEmitter.prototype);
|
||||||
@ -126,25 +108,25 @@ define(
|
|||||||
TimeConductorViewService.prototype.mode = function (newModeKey) {
|
TimeConductorViewService.prototype.mode = function (newModeKey) {
|
||||||
function contains(timeSystems, ts) {
|
function contains(timeSystems, ts) {
|
||||||
return timeSystems.filter(function (t) {
|
return timeSystems.filter(function (t) {
|
||||||
return t.metadata.key === ts.metadata.key;
|
return t.key === ts.key;
|
||||||
}).length > 0;
|
}).length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (arguments.length === 1) {
|
if (arguments.length === 1) {
|
||||||
var timeSystem = this.conductor.timeSystem();
|
var timeSystem = this.timeAPI.getTimeSystem(this.timeAPI.timeSystem());
|
||||||
var modes = this.availableModes();
|
var modes = this.availableModes();
|
||||||
var modeMetaData = modes[newModeKey];
|
var modeMetaData = modes[newModeKey];
|
||||||
|
|
||||||
if (this.currentMode) {
|
if (this.currentMode) {
|
||||||
this.currentMode.destroy();
|
this.currentMode.destroy();
|
||||||
}
|
}
|
||||||
this.currentMode = new TimeConductorMode(modeMetaData, this.conductor, this.systems);
|
this.currentMode = new TimeConductorMode(modeMetaData, this.timeAPI);
|
||||||
|
|
||||||
// If no time system set on time conductor, or the currently selected time system is not available in
|
// If no time system set on time conductor, or the currently selected time system is not available in
|
||||||
// the new mode, default to first available time system
|
// the new mode, default to first available time system
|
||||||
if (!timeSystem || !contains(this.currentMode.availableTimeSystems(), timeSystem)) {
|
if (!timeSystem || !contains(this.currentMode.availableTimeSystems(), timeSystem)) {
|
||||||
timeSystem = this.currentMode.availableTimeSystems()[0];
|
timeSystem = this.currentMode.availableTimeSystems()[0];
|
||||||
this.conductor.timeSystem(timeSystem, timeSystem.defaults().bounds);
|
this.timeAPI.timeSystem(timeSystem.key, timeSystem.defaults().bounds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this.currentMode ? this.currentMode.metadata().key : undefined;
|
return this.currentMode ? this.currentMode.metadata().key : undefined;
|
||||||
@ -201,7 +183,7 @@ define(
|
|||||||
* mode. Time systems and tick sources are mode dependent
|
* mode. Time systems and tick sources are mode dependent
|
||||||
*/
|
*/
|
||||||
TimeConductorViewService.prototype.availableTimeSystems = function () {
|
TimeConductorViewService.prototype.availableTimeSystems = function () {
|
||||||
return this.currentMode.availableTimeSystems();
|
return this.timeAPI.availableTimeSystems();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -31,7 +31,7 @@ define(
|
|||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
function TimeOfInterestController($scope, openmct, formatService) {
|
function TimeOfInterestController($scope, openmct, formatService) {
|
||||||
this.conductor = openmct.conductor;
|
this.timeAPI = openmct.time;
|
||||||
this.formatService = formatService;
|
this.formatService = formatService;
|
||||||
this.format = undefined;
|
this.format = undefined;
|
||||||
this.toiText = undefined;
|
this.toiText = undefined;
|
||||||
@ -44,11 +44,11 @@ define(
|
|||||||
this[key] = TimeOfInterestController.prototype[key].bind(this);
|
this[key] = TimeOfInterestController.prototype[key].bind(this);
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
|
|
||||||
this.conductor.on('timeOfInterest', this.changeTimeOfInterest);
|
this.timeAPI.on('timeOfInterest', this.changeTimeOfInterest);
|
||||||
this.conductor.on('timeSystem', this.changeTimeSystem);
|
this.timeAPI.on('timeSystem', this.changeTimeSystem);
|
||||||
if (this.conductor.timeSystem()) {
|
if (this.timeAPI.timeSystem()) {
|
||||||
this.changeTimeSystem(this.conductor.timeSystem());
|
this.changeTimeSystem(this.timeAPI.timeSystem());
|
||||||
var toi = this.conductor.timeOfInterest();
|
var toi = this.timeAPI.timeOfInterest();
|
||||||
if (toi) {
|
if (toi) {
|
||||||
this.changeTimeOfInterest(toi);
|
this.changeTimeOfInterest(toi);
|
||||||
}
|
}
|
||||||
@ -76,7 +76,8 @@ define(
|
|||||||
* When time system is changed, update the formatter used to
|
* When time system is changed, update the formatter used to
|
||||||
* display the current TOI label
|
* display the current TOI label
|
||||||
*/
|
*/
|
||||||
TimeOfInterestController.prototype.changeTimeSystem = function (timeSystem) {
|
TimeOfInterestController.prototype.changeTimeSystem = function (key) {
|
||||||
|
var timeSystem = this.timeAPI.getTimeSystem(key);
|
||||||
this.format = this.formatService.getFormat(timeSystem.formats()[0]);
|
this.format = this.formatService.getFormat(timeSystem.formats()[0]);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -84,8 +85,8 @@ define(
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
TimeOfInterestController.prototype.destroy = function () {
|
TimeOfInterestController.prototype.destroy = function () {
|
||||||
this.conductor.off('timeOfInterest', this.changeTimeOfInterest);
|
this.timeAPI.off('timeOfInterest', this.changeTimeOfInterest);
|
||||||
this.conductor.off('timeSystem', this.changeTimeSystem);
|
this.timeAPI.off('timeSystem', this.changeTimeSystem);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -93,7 +94,7 @@ define(
|
|||||||
* Time Conductor
|
* Time Conductor
|
||||||
*/
|
*/
|
||||||
TimeOfInterestController.prototype.dismiss = function () {
|
TimeOfInterestController.prototype.dismiss = function () {
|
||||||
this.conductor.timeOfInterest(undefined);
|
this.timeAPI.timeOfInterest(undefined);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -101,7 +102,7 @@ define(
|
|||||||
* the TOI displayed in views.
|
* the TOI displayed in views.
|
||||||
*/
|
*/
|
||||||
TimeOfInterestController.prototype.resync = function () {
|
TimeOfInterestController.prototype.resync = function () {
|
||||||
this.conductor.timeOfInterest(this.conductor.timeOfInterest());
|
this.timeAPI.timeOfInterest(this.timeAPI.timeOfInterest());
|
||||||
};
|
};
|
||||||
|
|
||||||
return TimeOfInterestController;
|
return TimeOfInterestController;
|
||||||
|
@ -34,7 +34,8 @@ define(
|
|||||||
function LayoutCompositionPolicy() {
|
function LayoutCompositionPolicy() {
|
||||||
}
|
}
|
||||||
|
|
||||||
LayoutCompositionPolicy.prototype.allow = function (parentType, child) {
|
LayoutCompositionPolicy.prototype.allow = function (parent, child) {
|
||||||
|
var parentType = parent.getCapability('type');
|
||||||
if (parentType.instanceOf('layout') &&
|
if (parentType.instanceOf('layout') &&
|
||||||
child.getCapability('type').instanceOf('folder')) {
|
child.getCapability('type').instanceOf('folder')) {
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@ define(
|
|||||||
function (LayoutCompositionPolicy) {
|
function (LayoutCompositionPolicy) {
|
||||||
describe("Layout's composition policy", function () {
|
describe("Layout's composition policy", function () {
|
||||||
var mockChild,
|
var mockChild,
|
||||||
|
mockCandidateObj,
|
||||||
mockCandidate,
|
mockCandidate,
|
||||||
mockContext,
|
mockContext,
|
||||||
candidateType,
|
candidateType,
|
||||||
@ -41,6 +42,11 @@ define(
|
|||||||
mockContext =
|
mockContext =
|
||||||
jasmine.createSpyObj('contextType', ['instanceOf']);
|
jasmine.createSpyObj('contextType', ['instanceOf']);
|
||||||
|
|
||||||
|
mockCandidateObj = jasmine.createSpyObj('domainObj', [
|
||||||
|
'getCapability'
|
||||||
|
]);
|
||||||
|
mockCandidateObj.getCapability.andReturn(mockCandidate);
|
||||||
|
|
||||||
mockChild.getCapability.andReturn(mockContext);
|
mockChild.getCapability.andReturn(mockContext);
|
||||||
|
|
||||||
mockCandidate.instanceOf.andCallFake(function (t) {
|
mockCandidate.instanceOf.andCallFake(function (t) {
|
||||||
@ -56,19 +62,19 @@ define(
|
|||||||
it("disallows folders in layouts", function () {
|
it("disallows folders in layouts", function () {
|
||||||
candidateType = 'layout';
|
candidateType = 'layout';
|
||||||
contextType = 'folder';
|
contextType = 'folder';
|
||||||
expect(policy.allow(mockCandidate, mockChild)).toBe(false);
|
expect(policy.allow(mockCandidateObj, mockChild)).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not disallow folders elsewhere", function () {
|
it("does not disallow folders elsewhere", function () {
|
||||||
candidateType = 'nonlayout';
|
candidateType = 'nonlayout';
|
||||||
contextType = 'folder';
|
contextType = 'folder';
|
||||||
expect(policy.allow(mockCandidate, mockChild)).toBe(true);
|
expect(policy.allow(mockCandidateObj, mockChild)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("allows things other than folders in layouts", function () {
|
it("allows things other than folders in layouts", function () {
|
||||||
candidateType = 'layout';
|
candidateType = 'layout';
|
||||||
contextType = 'nonfolder';
|
contextType = 'nonfolder';
|
||||||
expect(policy.allow(mockCandidate, mockChild)).toBe(true);
|
expect(policy.allow(mockCandidateObj, mockChild)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -47,7 +47,7 @@ define([
|
|||||||
"key": "url",
|
"key": "url",
|
||||||
"name": "URL",
|
"name": "URL",
|
||||||
"control": "textfield",
|
"control": "textfield",
|
||||||
"pattern": "^(ftp|https?)\\:\\/\\/\\w+(\\.\\w+)*(\\:\\d+)?(\\/\\S*)*$",
|
"pattern": "^(ftp|https?)\\:\\/\\/",
|
||||||
"required": true,
|
"required": true,
|
||||||
"cssClass": "l-input-lg"
|
"cssClass": "l-input-lg"
|
||||||
}
|
}
|
||||||
|
@ -82,7 +82,7 @@ define(
|
|||||||
lastRange,
|
lastRange,
|
||||||
lastDomain,
|
lastDomain,
|
||||||
handle;
|
handle;
|
||||||
var conductor = openmct.conductor;
|
var timeAPI = openmct.time;
|
||||||
|
|
||||||
// Populate the scope with axis information (specifically, options
|
// Populate the scope with axis information (specifically, options
|
||||||
// available for each axis.)
|
// available for each axis.)
|
||||||
@ -185,7 +185,7 @@ define(
|
|||||||
|
|
||||||
function changeTimeOfInterest(timeOfInterest) {
|
function changeTimeOfInterest(timeOfInterest) {
|
||||||
if (timeOfInterest !== undefined) {
|
if (timeOfInterest !== undefined) {
|
||||||
var bounds = conductor.bounds();
|
var bounds = timeAPI.bounds();
|
||||||
var range = bounds.end - bounds.start;
|
var range = bounds.end - bounds.start;
|
||||||
$scope.toiPerc = ((timeOfInterest - bounds.start) / range) * 100;
|
$scope.toiPerc = ((timeOfInterest - bounds.start) / range) * 100;
|
||||||
$scope.toiPinned = true;
|
$scope.toiPinned = true;
|
||||||
@ -208,8 +208,8 @@ define(
|
|||||||
);
|
);
|
||||||
replot();
|
replot();
|
||||||
|
|
||||||
changeTimeOfInterest(conductor.timeOfInterest());
|
changeTimeOfInterest(timeAPI.timeOfInterest());
|
||||||
conductor.on("timeOfInterest", changeTimeOfInterest);
|
timeAPI.on("timeOfInterest", changeTimeOfInterest);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Release the current subscription (called when scope is destroyed)
|
// Release the current subscription (called when scope is destroyed)
|
||||||
@ -218,7 +218,7 @@ define(
|
|||||||
handle.unsubscribe();
|
handle.unsubscribe();
|
||||||
handle = undefined;
|
handle = undefined;
|
||||||
}
|
}
|
||||||
conductor.off("timeOfInterest", changeTimeOfInterest);
|
timeAPI.off("timeOfInterest", changeTimeOfInterest);
|
||||||
}
|
}
|
||||||
|
|
||||||
function requery() {
|
function requery() {
|
||||||
@ -262,7 +262,7 @@ define(
|
|||||||
requery();
|
requery();
|
||||||
}
|
}
|
||||||
self.setUnsynchedStatus($scope.domainObject, follow && self.isZoomed());
|
self.setUnsynchedStatus($scope.domainObject, follow && self.isZoomed());
|
||||||
changeTimeOfInterest(conductor.timeOfInterest());
|
changeTimeOfInterest(timeAPI.timeOfInterest());
|
||||||
}
|
}
|
||||||
|
|
||||||
this.modeOptions = new PlotModeOptions([], subPlotFactory);
|
this.modeOptions = new PlotModeOptions([], subPlotFactory);
|
||||||
@ -286,11 +286,11 @@ define(
|
|||||||
];
|
];
|
||||||
|
|
||||||
//Are some initialized bounds defined?
|
//Are some initialized bounds defined?
|
||||||
var bounds = conductor.bounds();
|
var bounds = timeAPI.bounds();
|
||||||
if (bounds &&
|
if (bounds &&
|
||||||
bounds.start !== undefined &&
|
bounds.start !== undefined &&
|
||||||
bounds.end !== undefined) {
|
bounds.end !== undefined) {
|
||||||
changeDisplayBounds(undefined, conductor.bounds(), conductor.follow());
|
changeDisplayBounds(undefined, timeAPI.bounds(), timeAPI.follow());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Watch for changes to the selected axis
|
// Watch for changes to the selected axis
|
||||||
|
@ -112,22 +112,6 @@ define(
|
|||||||
this.lastBounds = bounds;
|
this.lastBounds = bounds;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines is a given telemetry datum is within the bounds currently
|
|
||||||
* defined for this telemetry collection.
|
|
||||||
* @private
|
|
||||||
* @param datum
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
TelemetryCollection.prototype.inBounds = function (datum) {
|
|
||||||
var noBoundsDefined = !this.lastBounds || (this.lastBounds.start === undefined && this.lastBounds.end === undefined);
|
|
||||||
var withinBounds =
|
|
||||||
_.get(datum, this.sortField) >= this.lastBounds.start &&
|
|
||||||
_.get(datum, this.sortField) <= this.lastBounds.end;
|
|
||||||
|
|
||||||
return noBoundsDefined || withinBounds;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds an individual item to the collection. Used internally only
|
* Adds an individual item to the collection. Used internally only
|
||||||
* @private
|
* @private
|
||||||
@ -173,9 +157,10 @@ define(
|
|||||||
// based on time stamp because the array is guaranteed ordered due
|
// based on time stamp because the array is guaranteed ordered due
|
||||||
// to sorted insertion.
|
// to sorted insertion.
|
||||||
var startIx = _.sortedIndex(array, item, this.sortField);
|
var startIx = _.sortedIndex(array, item, this.sortField);
|
||||||
|
var endIx;
|
||||||
|
|
||||||
if (startIx !== array.length) {
|
if (startIx !== array.length) {
|
||||||
var endIx = _.sortedLastIndex(array, item, this.sortField);
|
endIx = _.sortedLastIndex(array, item, this.sortField);
|
||||||
|
|
||||||
// Create an array of potential dupes, based on having the
|
// Create an array of potential dupes, based on having the
|
||||||
// same time stamp
|
// same time stamp
|
||||||
@ -185,7 +170,7 @@ define(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!isDuplicate) {
|
if (!isDuplicate) {
|
||||||
array.splice(startIx, 0, item);
|
array.splice(endIx || startIx, 0, item);
|
||||||
|
|
||||||
//Return true if it was added and in bounds
|
//Return true if it was added and in bounds
|
||||||
return array === this.telemetry;
|
return array === this.telemetry;
|
||||||
|
@ -27,7 +27,7 @@ define(
|
|||||||
this.resultsHeader = this.element.find('.mct-table>thead').first();
|
this.resultsHeader = this.element.find('.mct-table>thead').first();
|
||||||
this.sizingTableBody = this.element.find('.sizing-table>tbody').first();
|
this.sizingTableBody = this.element.find('.sizing-table>tbody').first();
|
||||||
this.$scope.sizingRow = {};
|
this.$scope.sizingRow = {};
|
||||||
this.conductor = openmct.conductor;
|
this.timeApi = openmct.time;
|
||||||
this.toiFormatter = undefined;
|
this.toiFormatter = undefined;
|
||||||
this.formatService = formatService;
|
this.formatService = formatService;
|
||||||
this.callbacks = {};
|
this.callbacks = {};
|
||||||
@ -65,6 +65,7 @@ define(
|
|||||||
this.scrollable.on('scroll', this.onScroll);
|
this.scrollable.on('scroll', this.onScroll);
|
||||||
|
|
||||||
$scope.visibleRows = [];
|
$scope.visibleRows = [];
|
||||||
|
$scope.displayRows = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set default values for optional parameters on a given scope
|
* Set default values for optional parameters on a given scope
|
||||||
@ -113,7 +114,7 @@ define(
|
|||||||
$scope.sortDirection = 'asc';
|
$scope.sortDirection = 'asc';
|
||||||
}
|
}
|
||||||
self.setRows($scope.rows);
|
self.setRows($scope.rows);
|
||||||
self.setTimeOfInterestRow(self.conductor.timeOfInterest());
|
self.setTimeOfInterestRow(self.timeApi.timeOfInterest());
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -159,13 +160,13 @@ define(
|
|||||||
if (timeColumns) {
|
if (timeColumns) {
|
||||||
this.destroyConductorListeners();
|
this.destroyConductorListeners();
|
||||||
|
|
||||||
this.conductor.on('timeSystem', this.changeTimeSystem);
|
this.timeApi.on('timeSystem', this.changeTimeSystem);
|
||||||
this.conductor.on('timeOfInterest', this.changeTimeOfInterest);
|
this.timeApi.on('timeOfInterest', this.changeTimeOfInterest);
|
||||||
this.conductor.on('bounds', this.changeBounds);
|
this.timeApi.on('bounds', this.changeBounds);
|
||||||
|
|
||||||
// If time system defined, set initially
|
// If time system defined, set initially
|
||||||
if (this.conductor.timeSystem()) {
|
if (this.timeApi.timeSystem()) {
|
||||||
this.changeTimeSystem(this.conductor.timeSystem());
|
this.changeTimeSystem(this.timeApi.timeSystem());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
@ -182,13 +183,14 @@ define(
|
|||||||
}
|
}
|
||||||
|
|
||||||
MCTTableController.prototype.destroyConductorListeners = function () {
|
MCTTableController.prototype.destroyConductorListeners = function () {
|
||||||
this.conductor.off('timeSystem', this.changeTimeSystem);
|
this.timeApi.off('timeSystem', this.changeTimeSystem);
|
||||||
this.conductor.off('timeOfInterest', this.changeTimeOfInterest);
|
this.timeApi.off('timeOfInterest', this.changeTimeOfInterest);
|
||||||
this.conductor.off('bounds', this.changeBounds);
|
this.timeApi.off('bounds', this.changeBounds);
|
||||||
};
|
};
|
||||||
|
|
||||||
MCTTableController.prototype.changeTimeSystem = function () {
|
MCTTableController.prototype.changeTimeSystem = function () {
|
||||||
var format = this.conductor.timeSystem().formats()[0];
|
var timeSystem = this.timeApi.getTimeSystem(this.timeApi.timeSystem());
|
||||||
|
var format = timeSystem.formats()[0];
|
||||||
this.toiFormatter = this.formatService.getFormat(format);
|
this.toiFormatter = this.formatService.getFormat(format);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -220,7 +222,7 @@ define(
|
|||||||
}
|
}
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
|
|
||||||
var toi = this.conductor.timeOfInterest();
|
var toi = this.timeApi.timeOfInterest();
|
||||||
if (toi !== -1) {
|
if (toi !== -1) {
|
||||||
this.setTimeOfInterestRow(toi);
|
this.setTimeOfInterestRow(toi);
|
||||||
}
|
}
|
||||||
@ -425,6 +427,38 @@ define(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the correct insertion point for a new row, which takes into
|
||||||
|
* account duplicates to make sure new rows are inserted in a way that
|
||||||
|
* maintains arrival order.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Array} searchArray
|
||||||
|
* @param {Object} searchElement Object to find the insertion point for
|
||||||
|
*/
|
||||||
|
MCTTableController.prototype.findInsertionPoint = function (searchArray, searchElement) {
|
||||||
|
//First, use a binary search to find the correct insertion point
|
||||||
|
var index = this.binarySearch(searchArray, searchElement, 0, searchArray.length - 1);
|
||||||
|
var testIndex = index;
|
||||||
|
|
||||||
|
//It's possible that the insertion point is a duplicate of the element to be inserted
|
||||||
|
var isDupe = function () {
|
||||||
|
return this.sortComparator(searchElement,
|
||||||
|
searchArray[testIndex][this.$scope.sortColumn].text) === 0;
|
||||||
|
}.bind(this);
|
||||||
|
|
||||||
|
// In the event of a duplicate, scan left or right (depending on
|
||||||
|
// sort order) to find an insertion point that maintains order received
|
||||||
|
while (testIndex >= 0 && testIndex < searchArray.length && isDupe()) {
|
||||||
|
if (this.$scope.sortDirection === 'asc') {
|
||||||
|
index = ++testIndex;
|
||||||
|
} else {
|
||||||
|
index = testIndex--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return index;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
@ -439,9 +473,9 @@ define(
|
|||||||
case -1:
|
case -1:
|
||||||
return this.binarySearch(searchArray, searchElement, min,
|
return this.binarySearch(searchArray, searchElement, min,
|
||||||
sampleAt - 1);
|
sampleAt - 1);
|
||||||
case 0 :
|
case 0:
|
||||||
return sampleAt;
|
return sampleAt;
|
||||||
case 1 :
|
case 1:
|
||||||
return this.binarySearch(searchArray, searchElement,
|
return this.binarySearch(searchArray, searchElement,
|
||||||
sampleAt + 1, max);
|
sampleAt + 1, max);
|
||||||
}
|
}
|
||||||
@ -458,7 +492,7 @@ define(
|
|||||||
index = array.length;
|
index = array.length;
|
||||||
} else {
|
} else {
|
||||||
//Sort is enabled, perform binary search to find insertion point
|
//Sort is enabled, perform binary search to find insertion point
|
||||||
index = this.binarySearch(array, element[this.$scope.sortColumn].text, 0, array.length - 1);
|
index = this.findInsertionPoint(array, element[this.$scope.sortColumn].text);
|
||||||
}
|
}
|
||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
array.unshift(element);
|
array.unshift(element);
|
||||||
@ -649,7 +683,7 @@ define(
|
|||||||
// perform DOM changes, otherwise scrollTo won't work.
|
// perform DOM changes, otherwise scrollTo won't work.
|
||||||
.then(function () {
|
.then(function () {
|
||||||
//If TOI specified, scroll to it
|
//If TOI specified, scroll to it
|
||||||
var timeOfInterest = this.conductor.timeOfInterest();
|
var timeOfInterest = this.timeApi.timeOfInterest();
|
||||||
if (timeOfInterest) {
|
if (timeOfInterest) {
|
||||||
this.setTimeOfInterestRow(timeOfInterest);
|
this.setTimeOfInterestRow(timeOfInterest);
|
||||||
this.scrollToRow(this.$scope.toiRowIndex);
|
this.scrollToRow(this.$scope.toiRowIndex);
|
||||||
@ -747,7 +781,7 @@ define(
|
|||||||
* @param bounds
|
* @param bounds
|
||||||
*/
|
*/
|
||||||
MCTTableController.prototype.changeBounds = function (bounds) {
|
MCTTableController.prototype.changeBounds = function (bounds) {
|
||||||
this.setTimeOfInterestRow(this.conductor.timeOfInterest());
|
this.setTimeOfInterestRow(this.timeApi.timeOfInterest());
|
||||||
if (this.$scope.toiRowIndex !== -1) {
|
if (this.$scope.toiRowIndex !== -1) {
|
||||||
this.scrollToRow(this.$scope.toiRowIndex);
|
this.scrollToRow(this.$scope.toiRowIndex);
|
||||||
}
|
}
|
||||||
@ -762,7 +796,7 @@ define(
|
|||||||
if (selectedTime &&
|
if (selectedTime &&
|
||||||
this.toiFormatter.validate(selectedTime) &&
|
this.toiFormatter.validate(selectedTime) &&
|
||||||
event.altKey) {
|
event.altKey) {
|
||||||
this.conductor.timeOfInterest(this.toiFormatter.parse(selectedTime));
|
this.timeApi.timeOfInterest(this.toiFormatter.parse(selectedTime));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -64,7 +64,7 @@ define(
|
|||||||
$scope.rows = [];
|
$scope.rows = [];
|
||||||
this.table = new TableConfiguration($scope.domainObject,
|
this.table = new TableConfiguration($scope.domainObject,
|
||||||
openmct);
|
openmct);
|
||||||
this.lastBounds = this.openmct.conductor.bounds();
|
this.lastBounds = this.openmct.time.bounds();
|
||||||
this.lastRequestTime = 0;
|
this.lastRequestTime = 0;
|
||||||
this.telemetry = new TelemetryCollection();
|
this.telemetry = new TelemetryCollection();
|
||||||
|
|
||||||
@ -72,7 +72,7 @@ define(
|
|||||||
* Create a new format object from legacy object, and replace it
|
* Create a new format object from legacy object, and replace it
|
||||||
* when it changes
|
* when it changes
|
||||||
*/
|
*/
|
||||||
this.newObject = objectUtils.toNewFormat($scope.domainObject.getModel(),
|
this.domainObject = objectUtils.toNewFormat($scope.domainObject.getModel(),
|
||||||
$scope.domainObject.getId());
|
$scope.domainObject.getId());
|
||||||
|
|
||||||
_.bindAll(this, [
|
_.bindAll(this, [
|
||||||
@ -95,7 +95,7 @@ define(
|
|||||||
this.registerChangeListeners();
|
this.registerChangeListeners();
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
|
|
||||||
this.setScroll(this.openmct.conductor.follow());
|
this.setScroll(this.openmct.time.follow());
|
||||||
|
|
||||||
this.$scope.$on("$destroy", this.destroy);
|
this.$scope.$on("$destroy", this.destroy);
|
||||||
}
|
}
|
||||||
@ -122,7 +122,7 @@ define(
|
|||||||
|
|
||||||
if (timeSystem) {
|
if (timeSystem) {
|
||||||
this.table.columns.forEach(function (column) {
|
this.table.columns.forEach(function (column) {
|
||||||
if (column.getKey() === timeSystem.metadata.key) {
|
if (column.getKey() === timeSystem) {
|
||||||
sortColumn = column;
|
sortColumn = column;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -144,16 +144,16 @@ define(
|
|||||||
this.unobserveObject();
|
this.unobserveObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.unobserveObject = this.openmct.objects.observe(this.newObject, "*",
|
this.unobserveObject = this.openmct.objects.observe(this.domainObject, "*",
|
||||||
function (domainObject) {
|
function (domainObject) {
|
||||||
this.newObject = domainObject;
|
this.domainObject = domainObject;
|
||||||
this.getData();
|
this.getData();
|
||||||
}.bind(this)
|
}.bind(this)
|
||||||
);
|
);
|
||||||
|
|
||||||
this.openmct.conductor.on('timeSystem', this.sortByTimeSystem);
|
this.openmct.time.on('timeSystem', this.sortByTimeSystem);
|
||||||
this.openmct.conductor.on('bounds', this.changeBounds);
|
this.openmct.time.on('bounds', this.changeBounds);
|
||||||
this.openmct.conductor.on('follow', this.setScroll);
|
this.openmct.time.on('follow', this.setScroll);
|
||||||
|
|
||||||
this.telemetry.on('added', this.addRowsToTable);
|
this.telemetry.on('added', this.addRowsToTable);
|
||||||
this.telemetry.on('discarded', this.removeRowsFromTable);
|
this.telemetry.on('discarded', this.removeRowsFromTable);
|
||||||
@ -188,7 +188,7 @@ define(
|
|||||||
* @param {openmct.TimeConductorBounds~TimeConductorBounds} bounds
|
* @param {openmct.TimeConductorBounds~TimeConductorBounds} bounds
|
||||||
*/
|
*/
|
||||||
TelemetryTableController.prototype.changeBounds = function (bounds) {
|
TelemetryTableController.prototype.changeBounds = function (bounds) {
|
||||||
var follow = this.openmct.conductor.follow();
|
var follow = this.openmct.time.follow();
|
||||||
var isTick = follow &&
|
var isTick = follow &&
|
||||||
bounds.start !== this.lastBounds.start &&
|
bounds.start !== this.lastBounds.start &&
|
||||||
bounds.end !== this.lastBounds.end;
|
bounds.end !== this.lastBounds.end;
|
||||||
@ -207,9 +207,9 @@ define(
|
|||||||
*/
|
*/
|
||||||
TelemetryTableController.prototype.destroy = function () {
|
TelemetryTableController.prototype.destroy = function () {
|
||||||
|
|
||||||
this.openmct.conductor.off('timeSystem', this.sortByTimeSystem);
|
this.openmct.time.off('timeSystem', this.sortByTimeSystem);
|
||||||
this.openmct.conductor.off('bounds', this.changeBounds);
|
this.openmct.time.off('bounds', this.changeBounds);
|
||||||
this.openmct.conductor.off('follow', this.setScroll);
|
this.openmct.time.off('follow', this.setScroll);
|
||||||
|
|
||||||
this.subscriptions.forEach(function (subscription) {
|
this.subscriptions.forEach(function (subscription) {
|
||||||
subscription();
|
subscription();
|
||||||
@ -260,7 +260,7 @@ define(
|
|||||||
// if data matches selected time system
|
// if data matches selected time system
|
||||||
this.telemetry.sort(undefined);
|
this.telemetry.sort(undefined);
|
||||||
|
|
||||||
var timeSystem = this.openmct.conductor.timeSystem();
|
var timeSystem = this.openmct.time.timeSystem();
|
||||||
if (timeSystem) {
|
if (timeSystem) {
|
||||||
this.sortByTimeSystem(timeSystem);
|
this.sortByTimeSystem(timeSystem);
|
||||||
}
|
}
|
||||||
@ -278,7 +278,7 @@ define(
|
|||||||
TelemetryTableController.prototype.getHistoricalData = function (objects) {
|
TelemetryTableController.prototype.getHistoricalData = function (objects) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var openmct = this.openmct;
|
var openmct = this.openmct;
|
||||||
var bounds = openmct.conductor.bounds();
|
var bounds = openmct.time.bounds();
|
||||||
var scope = this.$scope;
|
var scope = this.$scope;
|
||||||
var rowData = [];
|
var rowData = [];
|
||||||
var processedObjects = 0;
|
var processedObjects = 0;
|
||||||
@ -364,11 +364,8 @@ define(
|
|||||||
var telemetryApi = this.openmct.telemetry;
|
var telemetryApi = this.openmct.telemetry;
|
||||||
var telemetryCollection = this.telemetry;
|
var telemetryCollection = this.telemetry;
|
||||||
//Set table max length to avoid unbounded growth.
|
//Set table max length to avoid unbounded growth.
|
||||||
//var maxRows = 100000;
|
|
||||||
var maxRows = Number.MAX_VALUE;
|
|
||||||
var limitEvaluator;
|
var limitEvaluator;
|
||||||
var added = false;
|
var added = false;
|
||||||
var scope = this.$scope;
|
|
||||||
var table = this.table;
|
var table = this.table;
|
||||||
|
|
||||||
this.subscriptions.forEach(function (subscription) {
|
this.subscriptions.forEach(function (subscription) {
|
||||||
@ -379,16 +376,6 @@ define(
|
|||||||
function newData(domainObject, datum) {
|
function newData(domainObject, datum) {
|
||||||
limitEvaluator = telemetryApi.limitEvaluator(domainObject);
|
limitEvaluator = telemetryApi.limitEvaluator(domainObject);
|
||||||
added = telemetryCollection.add([table.getRowValues(limitEvaluator, datum)]);
|
added = telemetryCollection.add([table.getRowValues(limitEvaluator, datum)]);
|
||||||
|
|
||||||
//Inform table that a new row has been added
|
|
||||||
if (scope.rows.length > maxRows) {
|
|
||||||
scope.$broadcast('remove:rows', scope.rows[0]);
|
|
||||||
scope.rows.shift();
|
|
||||||
}
|
|
||||||
if (!scope.loading && added) {
|
|
||||||
scope.$broadcast('add:row',
|
|
||||||
scope.rows.length - 1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
objects.forEach(function (object) {
|
objects.forEach(function (object) {
|
||||||
@ -399,6 +386,42 @@ define(
|
|||||||
return objects;
|
return objects;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return an array of telemetry objects in this view that should be
|
||||||
|
* subscribed to.
|
||||||
|
* @private
|
||||||
|
* @returns {Promise<Array>} a promise that resolves with an array of
|
||||||
|
* telemetry objects in this view.
|
||||||
|
*/
|
||||||
|
TelemetryTableController.prototype.getTelemetryObjects = function () {
|
||||||
|
var telemetryApi = this.openmct.telemetry;
|
||||||
|
var compositionApi = this.openmct.composition;
|
||||||
|
|
||||||
|
function filterForTelemetry(objects) {
|
||||||
|
return objects.filter(telemetryApi.canProvideTelemetry.bind(telemetryApi));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If parent object is a telemetry object, subscribe to it. Do not
|
||||||
|
* test composees.
|
||||||
|
*/
|
||||||
|
if (telemetryApi.canProvideTelemetry(this.domainObject)) {
|
||||||
|
return Promise.resolve([this.domainObject]);
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* If parent object is not a telemetry object, subscribe to all
|
||||||
|
* composees that are telemetry producing objects.
|
||||||
|
*/
|
||||||
|
var composition = compositionApi.get(this.domainObject);
|
||||||
|
|
||||||
|
if (composition) {
|
||||||
|
return composition
|
||||||
|
.load()
|
||||||
|
.then(filterForTelemetry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request historical data, and subscribe to for real-time data.
|
* Request historical data, and subscribe to for real-time data.
|
||||||
* @private
|
* @private
|
||||||
@ -406,13 +429,10 @@ define(
|
|||||||
* established, and historical telemetry is received and processed.
|
* established, and historical telemetry is received and processed.
|
||||||
*/
|
*/
|
||||||
TelemetryTableController.prototype.getData = function () {
|
TelemetryTableController.prototype.getData = function () {
|
||||||
var telemetryApi = this.openmct.telemetry;
|
|
||||||
var compositionApi = this.openmct.composition;
|
|
||||||
var scope = this.$scope;
|
var scope = this.$scope;
|
||||||
var newObject = this.newObject;
|
|
||||||
|
|
||||||
this.telemetry.clear();
|
this.telemetry.clear();
|
||||||
this.telemetry.bounds(this.openmct.conductor.bounds());
|
this.telemetry.bounds(this.openmct.time.bounds());
|
||||||
|
|
||||||
this.$scope.loading = true;
|
this.$scope.loading = true;
|
||||||
|
|
||||||
@ -421,28 +441,9 @@ define(
|
|||||||
console.error(e.stack);
|
console.error(e.stack);
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterForTelemetry(objects) {
|
|
||||||
return objects.filter(telemetryApi.canProvideTelemetry.bind(telemetryApi));
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDomainObjects() {
|
|
||||||
var objects = [newObject];
|
|
||||||
var composition = compositionApi.get(newObject);
|
|
||||||
|
|
||||||
if (composition) {
|
|
||||||
return composition
|
|
||||||
.load()
|
|
||||||
.then(function (children) {
|
|
||||||
return objects.concat(children);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return Promise.resolve(objects);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
scope.rows = [];
|
scope.rows = [];
|
||||||
|
|
||||||
return getDomainObjects()
|
return this.getTelemetryObjects()
|
||||||
.then(filterForTelemetry)
|
|
||||||
.then(this.loadColumns)
|
.then(this.loadColumns)
|
||||||
.then(this.subscribeToNewData)
|
.then(this.subscribeToNewData)
|
||||||
.then(this.getHistoricalData)
|
.then(this.getHistoricalData)
|
||||||
|
@ -138,6 +138,27 @@ define(
|
|||||||
};
|
};
|
||||||
collection.add([addedObjectB, addedObjectA]);
|
collection.add([addedObjectB, addedObjectA]);
|
||||||
|
|
||||||
|
expect(collection.telemetry[11]).toBe(addedObjectB);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
it("maintains insertion order in the case of duplicate time stamps",
|
||||||
|
function () {
|
||||||
|
var addedObjectA = {
|
||||||
|
timestamp: 10000,
|
||||||
|
value: {
|
||||||
|
integer: 10,
|
||||||
|
text: integerTextMap[10]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var addedObjectB = {
|
||||||
|
timestamp: 10000,
|
||||||
|
value: {
|
||||||
|
integer: 11,
|
||||||
|
text: integerTextMap[11]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
collection.add([addedObjectA, addedObjectB]);
|
||||||
|
|
||||||
expect(collection.telemetry[11]).toBe(addedObjectB);
|
expect(collection.telemetry[11]).toBe(addedObjectB);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -459,14 +459,14 @@ define(
|
|||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
row4 = {
|
row4 = {
|
||||||
'col1': {'text': 'row5 col1'},
|
'col1': {'text': 'row4 col1'},
|
||||||
'col2': {'text': 'xyz'},
|
'col2': {'text': 'xyz'},
|
||||||
'col3': {'text': 'row5 col3'}
|
'col3': {'text': 'row4 col3'}
|
||||||
};
|
};
|
||||||
row5 = {
|
row5 = {
|
||||||
'col1': {'text': 'row6 col1'},
|
'col1': {'text': 'row5 col1'},
|
||||||
'col2': {'text': 'aaa'},
|
'col2': {'text': 'aaa'},
|
||||||
'col3': {'text': 'row6 col3'}
|
'col3': {'text': 'row5 col3'}
|
||||||
};
|
};
|
||||||
row6 = {
|
row6 = {
|
||||||
'col1': {'text': 'row6 col1'},
|
'col1': {'text': 'row6 col1'},
|
||||||
@ -490,6 +490,71 @@ define(
|
|||||||
expect(mockScope.displayRows[3].col2.text).toEqual('ggg');
|
expect(mockScope.displayRows[3].col2.text).toEqual('ggg');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Inserts duplicate values for sort column in order received when sorted descending', function () {
|
||||||
|
mockScope.sortColumn = 'col2';
|
||||||
|
mockScope.sortDirection = 'desc';
|
||||||
|
|
||||||
|
mockScope.displayRows = controller.sortRows(testRows.slice(0));
|
||||||
|
|
||||||
|
var row6b = {
|
||||||
|
'col1': {'text': 'row6b col1'},
|
||||||
|
'col2': {'text': 'ggg'},
|
||||||
|
'col3': {'text': 'row6b col3'}
|
||||||
|
};
|
||||||
|
var row6c = {
|
||||||
|
'col1': {'text': 'row6c col1'},
|
||||||
|
'col2': {'text': 'ggg'},
|
||||||
|
'col3': {'text': 'row6c col3'}
|
||||||
|
};
|
||||||
|
|
||||||
|
controller.addRows(undefined, [row4, row5]);
|
||||||
|
controller.addRows(undefined, [row6, row6b, row6c]);
|
||||||
|
expect(mockScope.displayRows[0].col2.text).toEqual('xyz');
|
||||||
|
expect(mockScope.displayRows[7].col2.text).toEqual('aaa');
|
||||||
|
|
||||||
|
// Added duplicate rows
|
||||||
|
expect(mockScope.displayRows[2].col2.text).toEqual('ggg');
|
||||||
|
expect(mockScope.displayRows[3].col2.text).toEqual('ggg');
|
||||||
|
expect(mockScope.displayRows[4].col2.text).toEqual('ggg');
|
||||||
|
|
||||||
|
// Check that original order is maintained with dupes
|
||||||
|
expect(mockScope.displayRows[2].col3.text).toEqual('row6c col3');
|
||||||
|
expect(mockScope.displayRows[3].col3.text).toEqual('row6b col3');
|
||||||
|
expect(mockScope.displayRows[4].col3.text).toEqual('row6 col3');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Inserts duplicate values for sort column in order received when sorted ascending', function () {
|
||||||
|
mockScope.sortColumn = 'col2';
|
||||||
|
mockScope.sortDirection = 'asc';
|
||||||
|
|
||||||
|
mockScope.displayRows = controller.sortRows(testRows.slice(0));
|
||||||
|
|
||||||
|
var row6b = {
|
||||||
|
'col1': {'text': 'row6b col1'},
|
||||||
|
'col2': {'text': 'ggg'},
|
||||||
|
'col3': {'text': 'row6b col3'}
|
||||||
|
};
|
||||||
|
var row6c = {
|
||||||
|
'col1': {'text': 'row6c col1'},
|
||||||
|
'col2': {'text': 'ggg'},
|
||||||
|
'col3': {'text': 'row6c col3'}
|
||||||
|
};
|
||||||
|
|
||||||
|
controller.addRows(undefined, [row4, row5, row6]);
|
||||||
|
controller.addRows(undefined, [row6b, row6c]);
|
||||||
|
expect(mockScope.displayRows[0].col2.text).toEqual('aaa');
|
||||||
|
expect(mockScope.displayRows[7].col2.text).toEqual('xyz');
|
||||||
|
|
||||||
|
// Added duplicate rows
|
||||||
|
expect(mockScope.displayRows[3].col2.text).toEqual('ggg');
|
||||||
|
expect(mockScope.displayRows[4].col2.text).toEqual('ggg');
|
||||||
|
expect(mockScope.displayRows[5].col2.text).toEqual('ggg');
|
||||||
|
// Check that original order is maintained with dupes
|
||||||
|
expect(mockScope.displayRows[3].col3.text).toEqual('row6 col3');
|
||||||
|
expect(mockScope.displayRows[4].col3.text).toEqual('row6b col3');
|
||||||
|
expect(mockScope.displayRows[5].col3.text).toEqual('row6c col3');
|
||||||
|
});
|
||||||
|
|
||||||
it('Adds new rows at the correct sort position when' +
|
it('Adds new rows at the correct sort position when' +
|
||||||
' sorted and filtered', function () {
|
' sorted and filtered', function () {
|
||||||
mockScope.sortColumn = 'col2';
|
mockScope.sortColumn = 'col2';
|
||||||
|
@ -161,7 +161,7 @@ define(
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe ('Subscribes to new data', function () {
|
describe ('when getting telemetry', function () {
|
||||||
var mockComposition,
|
var mockComposition,
|
||||||
mockTelemetryObject,
|
mockTelemetryObject,
|
||||||
mockChildren,
|
mockChildren,
|
||||||
@ -173,9 +173,7 @@ define(
|
|||||||
"load"
|
"load"
|
||||||
]);
|
]);
|
||||||
|
|
||||||
mockTelemetryObject = jasmine.createSpyObj("mockTelemetryObject", [
|
mockTelemetryObject = {};
|
||||||
"something"
|
|
||||||
]);
|
|
||||||
mockTelemetryObject.identifier = {
|
mockTelemetryObject.identifier = {
|
||||||
key: "mockTelemetryObject"
|
key: "mockTelemetryObject"
|
||||||
};
|
};
|
||||||
@ -192,22 +190,12 @@ define(
|
|||||||
});
|
});
|
||||||
|
|
||||||
done = false;
|
done = false;
|
||||||
controller.getData().then(function () {
|
|
||||||
done = true;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('fetches historical data', function () {
|
|
||||||
waitsFor(function () {
|
|
||||||
return done;
|
|
||||||
}, "getData to return", 100);
|
|
||||||
|
|
||||||
runs(function () {
|
|
||||||
expect(mockTelemetryAPI.request).toHaveBeenCalledWith(mockTelemetryObject, jasmine.any(Object));
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('fetches historical data for the time period specified by the conductor bounds', function () {
|
it('fetches historical data for the time period specified by the conductor bounds', function () {
|
||||||
|
controller.getData().then(function () {
|
||||||
|
done = true;
|
||||||
|
});
|
||||||
waitsFor(function () {
|
waitsFor(function () {
|
||||||
return done;
|
return done;
|
||||||
}, "getData to return", 100);
|
}, "getData to return", 100);
|
||||||
@ -217,17 +205,11 @@ define(
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('subscribes to new data', function () {
|
it('unsubscribes on view destruction', function () {
|
||||||
waitsFor(function () {
|
controller.getData().then(function () {
|
||||||
return done;
|
done = true;
|
||||||
}, "getData to return", 100);
|
|
||||||
|
|
||||||
runs(function () {
|
|
||||||
expect(mockTelemetryAPI.subscribe).toHaveBeenCalledWith(mockTelemetryObject, jasmine.any(Function), {});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
|
||||||
it('and unsubscribes on view destruction', function () {
|
|
||||||
waitsFor(function () {
|
waitsFor(function () {
|
||||||
return done;
|
return done;
|
||||||
}, "getData to return", 100);
|
}, "getData to return", 100);
|
||||||
@ -239,6 +221,87 @@ define(
|
|||||||
expect(unsubscribe).toHaveBeenCalled();
|
expect(unsubscribe).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
it('fetches historical data for the time period specified by the conductor bounds', function () {
|
||||||
|
controller.getData().then(function () {
|
||||||
|
done = true;
|
||||||
|
});
|
||||||
|
waitsFor(function () {
|
||||||
|
return done;
|
||||||
|
}, "getData to return", 100);
|
||||||
|
|
||||||
|
runs(function () {
|
||||||
|
expect(mockTelemetryAPI.request).toHaveBeenCalledWith(mockTelemetryObject, mockBounds);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fetches data for, and subscribes to parent object if it is a telemetry object', function () {
|
||||||
|
controller.getData().then(function () {
|
||||||
|
done = true;
|
||||||
|
});
|
||||||
|
waitsFor(function () {
|
||||||
|
return done;
|
||||||
|
}, "getData to return", 100);
|
||||||
|
|
||||||
|
runs(function () {
|
||||||
|
expect(mockTelemetryAPI.subscribe).toHaveBeenCalledWith(mockTelemetryObject, jasmine.any(Function), {});
|
||||||
|
expect(mockTelemetryAPI.request).toHaveBeenCalledWith(mockTelemetryObject, jasmine.any(Object));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('fetches data for, and subscribes to parent object if it is a telemetry object', function () {
|
||||||
|
controller.getData().then(function () {
|
||||||
|
done = true;
|
||||||
|
});
|
||||||
|
waitsFor(function () {
|
||||||
|
return done;
|
||||||
|
}, "getData to return", 100);
|
||||||
|
|
||||||
|
runs(function () {
|
||||||
|
expect(mockTelemetryAPI.subscribe).toHaveBeenCalledWith(mockTelemetryObject, jasmine.any(Function), {});
|
||||||
|
expect(mockTelemetryAPI.request).toHaveBeenCalledWith(mockTelemetryObject, jasmine.any(Object));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fetches data for, and subscribes to any composees that are telemetry objects if parent is not', function () {
|
||||||
|
mockChildren = [
|
||||||
|
{name: "child 1"}
|
||||||
|
];
|
||||||
|
var mockTelemetryChildren = [
|
||||||
|
{name: "child 2"},
|
||||||
|
{name: "child 3"},
|
||||||
|
{name: "child 4"}
|
||||||
|
];
|
||||||
|
mockChildren = mockChildren.concat(mockTelemetryChildren);
|
||||||
|
mockComposition.load.andReturn(Promise.resolve(mockChildren));
|
||||||
|
|
||||||
|
mockTelemetryAPI.canProvideTelemetry.andCallFake(function (object) {
|
||||||
|
if (object === mockTelemetryObject) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return mockTelemetryChildren.indexOf(object) !== -1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
controller.getData().then(function () {
|
||||||
|
done = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
waitsFor(function () {
|
||||||
|
return done;
|
||||||
|
}, "getData to return", 100);
|
||||||
|
|
||||||
|
runs(function () {
|
||||||
|
mockTelemetryChildren.forEach(function (child) {
|
||||||
|
expect(mockTelemetryAPI.subscribe).toHaveBeenCalledWith(child, jasmine.any(Function), {});
|
||||||
|
});
|
||||||
|
|
||||||
|
mockTelemetryChildren.forEach(function (child) {
|
||||||
|
expect(mockTelemetryAPI.request).toHaveBeenCalledWith(child, jasmine.any(Object));
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockTelemetryAPI.subscribe).not.toHaveBeenCalledWith(mockChildren[0], jasmine.any(Function), {});
|
||||||
|
expect(mockTelemetryAPI.subscribe).not.toHaveBeenCalledWith(mockTelemetryObject[0], jasmine.any(Function), {});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('When in real-time mode, enables auto-scroll', function () {
|
it('When in real-time mode, enables auto-scroll', function () {
|
||||||
|
@ -140,7 +140,8 @@ define(
|
|||||||
typeRequest = (type && type.getDefinition().telemetry) || {},
|
typeRequest = (type && type.getDefinition().telemetry) || {},
|
||||||
modelTelemetry = domainObject.getModel().telemetry,
|
modelTelemetry = domainObject.getModel().telemetry,
|
||||||
fullRequest = Object.create(typeRequest),
|
fullRequest = Object.create(typeRequest),
|
||||||
bounds;
|
bounds,
|
||||||
|
timeSystem;
|
||||||
|
|
||||||
// Add properties from the telemetry field of this
|
// Add properties from the telemetry field of this
|
||||||
// specific domain object.
|
// specific domain object.
|
||||||
@ -162,11 +163,18 @@ define(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (request.start === undefined && request.end === undefined) {
|
if (request.start === undefined && request.end === undefined) {
|
||||||
bounds = this.openmct.conductor.bounds();
|
bounds = this.openmct.time.bounds();
|
||||||
fullRequest.start = bounds.start;
|
fullRequest.start = bounds.start;
|
||||||
fullRequest.end = bounds.end;
|
fullRequest.end = bounds.end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (request.domain === undefined) {
|
||||||
|
timeSystem = this.openmct.time.timeSystem();
|
||||||
|
if (timeSystem !== undefined) {
|
||||||
|
fullRequest.domain = timeSystem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return fullRequest;
|
return fullRequest;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -104,6 +104,13 @@ define(
|
|||||||
start: 0,
|
start: 0,
|
||||||
end: 1
|
end: 1
|
||||||
};
|
};
|
||||||
|
},
|
||||||
|
timeSystem: function () {
|
||||||
|
return {
|
||||||
|
metadata: {
|
||||||
|
key: 'mockTimeSystem'
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -141,7 +148,8 @@ define(
|
|||||||
id: "testId", // from domain object
|
id: "testId", // from domain object
|
||||||
source: "testSource", // from model
|
source: "testSource", // from model
|
||||||
key: "testKey", // from model
|
key: "testKey", // from model
|
||||||
start: 42 // from argument
|
start: 42, // from argument
|
||||||
|
domain: 'mockTimeSystem'
|
||||||
}]);
|
}]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -160,7 +168,8 @@ define(
|
|||||||
source: "testSource",
|
source: "testSource",
|
||||||
key: "testKey",
|
key: "testKey",
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 1
|
end: 1,
|
||||||
|
domain: 'mockTimeSystem'
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -176,7 +185,8 @@ define(
|
|||||||
source: "testSource", // from model
|
source: "testSource", // from model
|
||||||
key: "testId", // from domain object
|
key: "testId", // from domain object
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 1
|
end: 1,
|
||||||
|
domain: 'mockTimeSystem'
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -257,7 +267,8 @@ define(
|
|||||||
source: "testSource",
|
source: "testSource",
|
||||||
key: "testKey",
|
key: "testKey",
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 1
|
end: 1,
|
||||||
|
domain: 'mockTimeSystem'
|
||||||
}]
|
}]
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -274,8 +285,28 @@ define(
|
|||||||
expect(mockUnsubscribe).not.toHaveBeenCalled();
|
expect(mockUnsubscribe).not.toHaveBeenCalled();
|
||||||
subscription(); // should be an unsubscribe function
|
subscription(); // should be an unsubscribe function
|
||||||
expect(mockUnsubscribe).toHaveBeenCalled();
|
expect(mockUnsubscribe).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("applies time conductor bounds if request bounds not defined", function () {
|
||||||
|
var fullRequest = telemetry.buildRequest({});
|
||||||
|
var mockBounds = mockAPI.conductor.bounds();
|
||||||
|
|
||||||
|
expect(fullRequest.start).toBe(mockBounds.start);
|
||||||
|
expect(fullRequest.end).toBe(mockBounds.end);
|
||||||
|
|
||||||
|
fullRequest = telemetry.buildRequest({start: 10, end: 20});
|
||||||
|
|
||||||
|
expect(fullRequest.start).toBe(10);
|
||||||
|
expect(fullRequest.end).toBe(20);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("applies domain from time system if none defined", function () {
|
||||||
|
var fullRequest = telemetry.buildRequest({});
|
||||||
|
var mockTimeSystem = mockAPI.conductor.timeSystem();
|
||||||
|
expect(fullRequest.domain).toBe(mockTimeSystem.metadata.key);
|
||||||
|
|
||||||
|
fullRequest = telemetry.buildRequest({domain: 'someOtherDomain'});
|
||||||
|
expect(fullRequest.domain).toBe('someOtherDomain');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -83,7 +83,7 @@ define([
|
|||||||
* @memberof module:openmct.MCT#
|
* @memberof module:openmct.MCT#
|
||||||
* @name conductor
|
* @name conductor
|
||||||
*/
|
*/
|
||||||
this.conductor = new api.TimeConductor();
|
this.time = new api.TimeAPI();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An interface for interacting with the composition of domain objects.
|
* An interface for interacting with the composition of domain objects.
|
||||||
@ -194,15 +194,13 @@ define([
|
|||||||
*/
|
*/
|
||||||
this.telemetry = new api.TelemetryAPI(this);
|
this.telemetry = new api.TelemetryAPI(this);
|
||||||
|
|
||||||
this.TimeConductor = this.conductor; // compatibility for prototype
|
this.Dialog = api.Dialog;
|
||||||
|
|
||||||
this.on('navigation', this.selection.clear.bind(this.selection));
|
this.on('navigation', this.selection.clear.bind(this.selection));
|
||||||
}
|
}
|
||||||
|
|
||||||
MCT.prototype = Object.create(EventEmitter.prototype);
|
MCT.prototype = Object.create(EventEmitter.prototype);
|
||||||
|
|
||||||
Object.keys(api).forEach(function (k) {
|
|
||||||
MCT.prototype[k] = api[k];
|
|
||||||
});
|
|
||||||
MCT.prototype.MCT = MCT;
|
MCT.prototype.MCT = MCT;
|
||||||
|
|
||||||
MCT.prototype.legacyExtension = function (category, extension) {
|
MCT.prototype.legacyExtension = function (category, extension) {
|
||||||
|
100
src/MCTSpec.js
Normal file
100
src/MCTSpec.js
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2016, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
define([
|
||||||
|
'./MCT',
|
||||||
|
'./plugins/plugins',
|
||||||
|
'legacyRegistry'
|
||||||
|
], function (MCT, plugins, legacyRegistry) {
|
||||||
|
describe("MCT", function () {
|
||||||
|
var openmct;
|
||||||
|
var mockPlugin;
|
||||||
|
var mockPlugin2;
|
||||||
|
var mockListener;
|
||||||
|
var oldBundles;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
mockPlugin = jasmine.createSpy('plugin');
|
||||||
|
mockPlugin2 = jasmine.createSpy('plugin2');
|
||||||
|
mockListener = jasmine.createSpy('listener');
|
||||||
|
oldBundles = legacyRegistry.list();
|
||||||
|
|
||||||
|
openmct = new MCT();
|
||||||
|
|
||||||
|
openmct.install(mockPlugin);
|
||||||
|
openmct.install(mockPlugin2);
|
||||||
|
openmct.on('start', mockListener);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clean up the dirty singleton.
|
||||||
|
afterEach(function () {
|
||||||
|
legacyRegistry.list().forEach(function (bundle) {
|
||||||
|
if (oldBundles.indexOf(bundle) === -1) {
|
||||||
|
legacyRegistry.delete(bundle);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("exposes plugins", function () {
|
||||||
|
expect(openmct.plugins).toEqual(plugins);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not issue a start event before started", function () {
|
||||||
|
expect(mockListener).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("start", function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
openmct.start();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("calls plugins for configuration", function () {
|
||||||
|
expect(mockPlugin).toHaveBeenCalledWith(openmct);
|
||||||
|
expect(mockPlugin2).toHaveBeenCalledWith(openmct);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("emits a start event", function () {
|
||||||
|
expect(mockListener).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("setAssetPath", function () {
|
||||||
|
var testAssetPath;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
testAssetPath = "some/path";
|
||||||
|
openmct.legacyExtension = jasmine.createSpy('legacyExtension');
|
||||||
|
openmct.setAssetPath(testAssetPath);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("internally configures the path for assets", function () {
|
||||||
|
expect(openmct.legacyExtension).toHaveBeenCalledWith(
|
||||||
|
'constants',
|
||||||
|
{
|
||||||
|
key: "ASSETS_PATH",
|
||||||
|
value: testAssetPath
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -24,13 +24,13 @@
|
|||||||
* Module defining AlternateCompositionCapability. Created by vwoeltje on 11/7/14.
|
* Module defining AlternateCompositionCapability. Created by vwoeltje on 11/7/14.
|
||||||
*/
|
*/
|
||||||
define([
|
define([
|
||||||
'../../api/objects/object-utils'
|
'../../api/objects/object-utils',
|
||||||
], function (objectUtils) {
|
'../../../platform/core/src/capabilities/ContextualDomainObject'
|
||||||
|
], function (objectUtils, ContextualDomainObject) {
|
||||||
function AlternateCompositionCapability($injector, domainObject) {
|
function AlternateCompositionCapability($injector, domainObject) {
|
||||||
this.domainObject = domainObject;
|
this.domainObject = domainObject;
|
||||||
this.getDependencies = function () {
|
this.getDependencies = function () {
|
||||||
this.instantiate = $injector.get("instantiate");
|
this.instantiate = $injector.get("instantiate");
|
||||||
this.contextualize = $injector.get("contextualize");
|
|
||||||
this.getDependencies = undefined;
|
this.getDependencies = undefined;
|
||||||
this.openmct = $injector.get("openmct");
|
this.openmct = $injector.get("openmct");
|
||||||
}.bind(this);
|
}.bind(this);
|
||||||
@ -74,7 +74,7 @@ define([
|
|||||||
var keyString = objectUtils.makeKeyString(child.identifier);
|
var keyString = objectUtils.makeKeyString(child.identifier);
|
||||||
var oldModel = objectUtils.toOldFormat(child);
|
var oldModel = objectUtils.toOldFormat(child);
|
||||||
var newDO = this.instantiate(oldModel, keyString);
|
var newDO = this.instantiate(oldModel, keyString);
|
||||||
return this.contextualize(newDO, this.domainObject);
|
return new ContextualDomainObject(newDO, this.domainObject);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -26,13 +26,11 @@ define([], function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
AdapterCompositionPolicy.prototype.allow = function (
|
AdapterCompositionPolicy.prototype.allow = function (
|
||||||
parentType,
|
parent,
|
||||||
child
|
child
|
||||||
) {
|
) {
|
||||||
var container = parentType.getInitialModel();
|
|
||||||
|
|
||||||
return this.openmct.composition.checkPolicy(
|
return this.openmct.composition.checkPolicy(
|
||||||
container,
|
parent,
|
||||||
child
|
child
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,122 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2016, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(['./TimeConductor'], function (TimeConductor) {
|
|
||||||
describe("The Time Conductor", function () {
|
|
||||||
var tc,
|
|
||||||
timeSystem,
|
|
||||||
bounds,
|
|
||||||
eventListener,
|
|
||||||
toi,
|
|
||||||
follow;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
tc = new TimeConductor();
|
|
||||||
timeSystem = {};
|
|
||||||
bounds = {start: 0, end: 0};
|
|
||||||
eventListener = jasmine.createSpy("eventListener");
|
|
||||||
toi = 111;
|
|
||||||
follow = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Supports setting and querying of time of interest and and follow mode", function () {
|
|
||||||
expect(tc.timeOfInterest()).not.toBe(toi);
|
|
||||||
tc.timeOfInterest(toi);
|
|
||||||
expect(tc.timeOfInterest()).toBe(toi);
|
|
||||||
|
|
||||||
expect(tc.follow()).not.toBe(follow);
|
|
||||||
tc.follow(follow);
|
|
||||||
expect(tc.follow()).toBe(follow);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Allows setting of valid bounds", function () {
|
|
||||||
bounds = {start: 0, end: 1};
|
|
||||||
expect(tc.bounds()).not.toBe(bounds);
|
|
||||||
expect(tc.bounds.bind(tc, bounds)).not.toThrow();
|
|
||||||
expect(tc.bounds()).toEqual(bounds);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Disallows setting of invalid bounds", function () {
|
|
||||||
bounds = {start: 1, end: 0};
|
|
||||||
expect(tc.bounds()).not.toEqual(bounds);
|
|
||||||
expect(tc.bounds.bind(tc, bounds)).toThrow();
|
|
||||||
expect(tc.bounds()).not.toEqual(bounds);
|
|
||||||
|
|
||||||
bounds = {start: 1};
|
|
||||||
expect(tc.bounds()).not.toEqual(bounds);
|
|
||||||
expect(tc.bounds.bind(tc, bounds)).toThrow();
|
|
||||||
expect(tc.bounds()).not.toEqual(bounds);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Allows setting of time system with bounds", function () {
|
|
||||||
expect(tc.timeSystem()).not.toBe(timeSystem);
|
|
||||||
expect(tc.timeSystem.bind(tc, timeSystem, bounds)).not.toThrow();
|
|
||||||
expect(tc.timeSystem()).toBe(timeSystem);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Disallows setting of time system without bounds", function () {
|
|
||||||
expect(tc.timeSystem()).not.toBe(timeSystem);
|
|
||||||
expect(tc.timeSystem.bind(tc, timeSystem)).toThrow();
|
|
||||||
expect(tc.timeSystem()).not.toBe(timeSystem);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Emits an event when time system changes", function () {
|
|
||||||
expect(eventListener).not.toHaveBeenCalled();
|
|
||||||
tc.on("timeSystem", eventListener);
|
|
||||||
tc.timeSystem(timeSystem, bounds);
|
|
||||||
expect(eventListener).toHaveBeenCalledWith(timeSystem);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Emits an event when time of interest changes", function () {
|
|
||||||
expect(eventListener).not.toHaveBeenCalled();
|
|
||||||
tc.on("timeOfInterest", eventListener);
|
|
||||||
tc.timeOfInterest(toi);
|
|
||||||
expect(eventListener).toHaveBeenCalledWith(toi);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Emits an event when bounds change", function () {
|
|
||||||
expect(eventListener).not.toHaveBeenCalled();
|
|
||||||
tc.on("bounds", eventListener);
|
|
||||||
tc.bounds(bounds);
|
|
||||||
expect(eventListener).toHaveBeenCalledWith(bounds);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Emits an event when follow mode changes", function () {
|
|
||||||
expect(eventListener).not.toHaveBeenCalled();
|
|
||||||
tc.on("follow", eventListener);
|
|
||||||
tc.follow(follow);
|
|
||||||
expect(eventListener).toHaveBeenCalledWith(follow);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("If bounds are set and TOI lies inside them, do not change TOI", function () {
|
|
||||||
tc.timeOfInterest(6);
|
|
||||||
tc.bounds({start: 1, end: 10});
|
|
||||||
expect(tc.timeOfInterest()).toEqual(6);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("If bounds are set and TOI lies outside them, reset TOI", function () {
|
|
||||||
tc.timeOfInterest(11);
|
|
||||||
tc.bounds({start: 1, end: 10});
|
|
||||||
expect(tc.timeOfInterest()).toBeUndefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -21,7 +21,7 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
define([
|
define([
|
||||||
'./TimeConductor',
|
'./time/TimeAPI',
|
||||||
'./objects/ObjectAPI',
|
'./objects/ObjectAPI',
|
||||||
'./composition/CompositionAPI',
|
'./composition/CompositionAPI',
|
||||||
'./types/TypeRegistry',
|
'./types/TypeRegistry',
|
||||||
@ -29,7 +29,7 @@ define([
|
|||||||
'./ui/GestureAPI',
|
'./ui/GestureAPI',
|
||||||
'./telemetry/TelemetryAPI'
|
'./telemetry/TelemetryAPI'
|
||||||
], function (
|
], function (
|
||||||
TimeConductor,
|
TimeAPI,
|
||||||
ObjectAPI,
|
ObjectAPI,
|
||||||
CompositionAPI,
|
CompositionAPI,
|
||||||
TypeRegistry,
|
TypeRegistry,
|
||||||
@ -38,7 +38,7 @@ define([
|
|||||||
TelemetryAPI
|
TelemetryAPI
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
TimeConductor: TimeConductor,
|
TimeAPI: TimeAPI,
|
||||||
ObjectAPI: ObjectAPI,
|
ObjectAPI: ObjectAPI,
|
||||||
CompositionAPI: CompositionAPI,
|
CompositionAPI: CompositionAPI,
|
||||||
Dialog: Dialog,
|
Dialog: Dialog,
|
||||||
|
@ -22,6 +22,8 @@
|
|||||||
|
|
||||||
define(['EventEmitter'], function (EventEmitter) {
|
define(['EventEmitter'], function (EventEmitter) {
|
||||||
|
|
||||||
|
var tick;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The public API for setting and querying time conductor state. The
|
* The public API for setting and querying time conductor state. The
|
||||||
* time conductor is the means by which the temporal bounds of a view
|
* time conductor is the means by which the temporal bounds of a view
|
||||||
@ -35,7 +37,7 @@ define(['EventEmitter'], function (EventEmitter) {
|
|||||||
* @interface
|
* @interface
|
||||||
* @memberof module:openmct
|
* @memberof module:openmct
|
||||||
*/
|
*/
|
||||||
function TimeConductor() {
|
function TimeAPI() {
|
||||||
EventEmitter.call(this);
|
EventEmitter.call(this);
|
||||||
|
|
||||||
//The Time System
|
//The Time System
|
||||||
@ -48,21 +50,68 @@ define(['EventEmitter'], function (EventEmitter) {
|
|||||||
end: undefined
|
end: undefined
|
||||||
};
|
};
|
||||||
|
|
||||||
//Default to fixed mode
|
this.timeSystems = new Map();
|
||||||
this.followMode = false;
|
this.clocks = new Map();
|
||||||
|
this.activeClock = undefined;
|
||||||
|
this.offsets = undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tick is not exposed via public API, even @privately to avoid misuse.
|
||||||
|
*/
|
||||||
|
|
||||||
|
tick = function (timestamp) {
|
||||||
|
var newBounds = {
|
||||||
|
start: timestamp + this.offsets.start,
|
||||||
|
end: timestamp + this.offsets.end
|
||||||
|
};
|
||||||
|
|
||||||
|
this.boundsVal = newBounds;
|
||||||
|
this.emit('bounds', this.boundsVal, true);
|
||||||
|
|
||||||
|
// If a bounds change results in a TOI outside of the current
|
||||||
|
// bounds, unset it
|
||||||
|
if (this.toi < newBounds.start || this.toi > newBounds.end) {
|
||||||
|
this.timeOfInterest(undefined);
|
||||||
|
}
|
||||||
|
}.bind(this);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TimeConductor.prototype = Object.create(EventEmitter.prototype);
|
TimeAPI.prototype = Object.create(EventEmitter.prototype);
|
||||||
|
|
||||||
|
TimeAPI.prototype.addTimeSystem = function (timeSystem) {
|
||||||
|
this.timeSystems.set(timeSystem.key, timeSystem);
|
||||||
|
};
|
||||||
|
|
||||||
|
TimeAPI.prototype.availableTimeSystems = function (key) {
|
||||||
|
return Array.from(this.timeSystems.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeAPI.prototype.getTimeSystem = function (key) {
|
||||||
|
return this.timeSystems.get(key);
|
||||||
|
};
|
||||||
|
|
||||||
|
TimeAPI.prototype.addClock = function (clock) {
|
||||||
|
this.clocks.set(clock.key, clock);
|
||||||
|
};
|
||||||
|
|
||||||
|
TimeAPI.prototype.getClock = function (key) {
|
||||||
|
return this.clocks.get(key);
|
||||||
|
};
|
||||||
|
|
||||||
|
TimeAPI.prototype.availableClocks = function (key) {
|
||||||
|
return Array.from(this.availableClocks.values());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate the given bounds. This can be used for pre-validation of
|
* Validate the given bounds. This can be used for pre-validation of
|
||||||
* bounds, for example by views validating user inputs.
|
* bounds, for example by views validating user inputs.
|
||||||
* @param bounds The start and end time of the conductor.
|
* @param bounds The start and end time of the conductor.
|
||||||
* @returns {string | true} A validation error, or true if valid
|
* @returns {string | true} A validation error, or true if valid
|
||||||
* @memberof module:openmct.TimeConductor#
|
* @memberof module:openmct.TimeAPI#
|
||||||
* @method validateBounds
|
* @method validateBounds
|
||||||
*/
|
*/
|
||||||
TimeConductor.prototype.validateBounds = function (bounds) {
|
TimeAPI.prototype.validateBounds = function (bounds) {
|
||||||
if ((bounds.start === undefined) ||
|
if ((bounds.start === undefined) ||
|
||||||
(bounds.end === undefined) ||
|
(bounds.end === undefined) ||
|
||||||
isNaN(bounds.start) ||
|
isNaN(bounds.start) ||
|
||||||
@ -75,51 +124,25 @@ define(['EventEmitter'], function (EventEmitter) {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Get or set the follow mode of the time conductor. In follow mode the
|
|
||||||
* time conductor ticks, regularly updating the bounds from a timing
|
|
||||||
* source appropriate to the selected time system and mode of the time
|
|
||||||
* conductor.
|
|
||||||
* @fires module:openmct.TimeConductor~follow
|
|
||||||
* @param {boolean} followMode
|
|
||||||
* @returns {boolean}
|
|
||||||
* @memberof module:openmct.TimeConductor#
|
|
||||||
* @method follow
|
|
||||||
*/
|
|
||||||
TimeConductor.prototype.follow = function (followMode) {
|
|
||||||
if (arguments.length > 0) {
|
|
||||||
this.followMode = followMode;
|
|
||||||
/**
|
|
||||||
* The TimeConductor has toggled into or out of follow mode.
|
|
||||||
* @event follow
|
|
||||||
* @memberof module:openmct.TimeConductor~
|
|
||||||
* @property {boolean} followMode true if follow mode is
|
|
||||||
* enabled, otherwise false.
|
|
||||||
*/
|
|
||||||
this.emit('follow', this.followMode);
|
|
||||||
}
|
|
||||||
return this.followMode;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} TimeConductorBounds
|
* @typedef {Object} TimeConductorBounds
|
||||||
* @property {number} start The start time displayed by the time conductor in ms since epoch. Epoch determined by current time system
|
* @property {number} start The start time displayed by the time conductor in ms since epoch. Epoch determined by current time system
|
||||||
* @property {number} end The end time displayed by the time conductor in ms since epoch.
|
* @property {number} end The end time displayed by the time conductor in ms since epoch.
|
||||||
* @memberof module:openmct.TimeConductor~
|
* @memberof module:openmct.TimeAPI~
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get or set the start and end time of the time conductor. Basic validation
|
* Get or set the start and end time of the time conductor. Basic validation
|
||||||
* of bounds is performed.
|
* of bounds is performed.
|
||||||
*
|
*
|
||||||
* @param {module:openmct.TimeConductorBounds~TimeConductorBounds} newBounds
|
* @param {module:openmct.TimeAPI~TimeConductorBounds} newBounds
|
||||||
* @throws {Error} Validation error
|
* @throws {Error} Validation error
|
||||||
* @fires module:openmct.TimeConductor~bounds
|
* @fires module:openmct.TimeAPI~bounds
|
||||||
* @returns {module:openmct.TimeConductorBounds~TimeConductorBounds}
|
* @returns {module:openmct.TimeAPI~TimeConductorBounds}
|
||||||
* @memberof module:openmct.TimeConductor#
|
* @memberof module:openmct.TimeAPI#
|
||||||
* @method bounds
|
* @method bounds
|
||||||
*/
|
*/
|
||||||
TimeConductor.prototype.bounds = function (newBounds) {
|
TimeAPI.prototype.bounds = function (newBounds) {
|
||||||
if (arguments.length > 0) {
|
if (arguments.length > 0) {
|
||||||
var validationResult = this.validateBounds(newBounds);
|
var validationResult = this.validateBounds(newBounds);
|
||||||
if (validationResult !== true) {
|
if (validationResult !== true) {
|
||||||
@ -130,10 +153,12 @@ define(['EventEmitter'], function (EventEmitter) {
|
|||||||
/**
|
/**
|
||||||
* The start time, end time, or both have been updated.
|
* The start time, end time, or both have been updated.
|
||||||
* @event bounds
|
* @event bounds
|
||||||
* @memberof module:openmct.TimeConductor~
|
* @memberof module:openmct.TimeAPI~
|
||||||
* @property {TimeConductorBounds} bounds
|
* @property {TimeConductorBounds} bounds The newly updated bounds
|
||||||
|
* @property {boolean} [tick] `true` if the bounds update was due to
|
||||||
|
* a "tick" event (ie. was an automatic update), false otherwise.
|
||||||
*/
|
*/
|
||||||
this.emit('bounds', this.boundsVal);
|
this.emit('bounds', this.boundsVal, false);
|
||||||
|
|
||||||
// If a bounds change results in a TOI outside of the current
|
// If a bounds change results in a TOI outside of the current
|
||||||
// bounds, unset it
|
// bounds, unset it
|
||||||
@ -146,34 +171,40 @@ define(['EventEmitter'], function (EventEmitter) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get or set the time system of the TimeConductor. Time systems determine
|
* Get or set the time system of the TimeAPI. Time systems determine
|
||||||
* units, epoch, and other aspects of time representation. When changing
|
* units, epoch, and other aspects of time representation. When changing
|
||||||
* the time system in use, new valid bounds must also be provided.
|
* the time system in use, new valid bounds must also be provided.
|
||||||
* @param {TimeSystem} newTimeSystem
|
* @param {TimeSystem} newTimeSystem
|
||||||
* @param {module:openmct.TimeConductor~TimeConductorBounds} bounds
|
* @param {module:openmct.TimeAPI~TimeConductorBounds} bounds
|
||||||
* @fires module:openmct.TimeConductor~timeSystem
|
* @fires module:openmct.TimeAPI~timeSystem
|
||||||
* @returns {TimeSystem} The currently applied time system
|
* @returns {TimeSystem} The currently applied time system
|
||||||
* @memberof module:openmct.TimeConductor#
|
* @memberof module:openmct.TimeAPI#
|
||||||
* @method timeSystem
|
* @method timeSystem
|
||||||
*/
|
*/
|
||||||
TimeConductor.prototype.timeSystem = function (newTimeSystem, bounds) {
|
TimeAPI.prototype.timeSystem = function (newTimeSystem, bounds) {
|
||||||
if (arguments.length >= 2) {
|
if (arguments.length >= 2) {
|
||||||
this.system = newTimeSystem;
|
if (newTimeSystem === 'undefined') {
|
||||||
|
throw "Please provide a time system";
|
||||||
|
}
|
||||||
|
if (this.timeSystems.get(newTimeSystem) === undefined){
|
||||||
|
throw "Unknown time system " + newTimeSystem + ". Has it been registered with 'addTimeSystem'?";
|
||||||
|
}
|
||||||
|
this.system = this.timeSystems.get(newTimeSystem);
|
||||||
/**
|
/**
|
||||||
* The time system used by the time
|
* The time system used by the time
|
||||||
* conductor has changed. A change in Time System will always be
|
* conductor has changed. A change in Time System will always be
|
||||||
* followed by a bounds event specifying new query bounds.
|
* followed by a bounds event specifying new query bounds.
|
||||||
*
|
*
|
||||||
* @event module:openmct.TimeConductor~timeSystem
|
* @event module:openmct.TimeAPI~timeSystem
|
||||||
* @property {TimeSystem} The value of the currently applied
|
* @property {TimeSystem} The value of the currently applied
|
||||||
* Time System
|
* Time System
|
||||||
* */
|
* */
|
||||||
this.emit('timeSystem', this.system);
|
this.emit('timeSystem', this.system.key);
|
||||||
this.bounds(bounds);
|
this.bounds(bounds);
|
||||||
} else if (arguments.length === 1) {
|
} else if (arguments.length === 1) {
|
||||||
throw new Error('Must set bounds when changing time system');
|
throw new Error('Must set bounds when changing time system');
|
||||||
}
|
}
|
||||||
return this.system;
|
return this.system && this.system.key;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -181,19 +212,19 @@ define(['EventEmitter'], function (EventEmitter) {
|
|||||||
* focus of the current view. It can be manipulated by the user from the
|
* focus of the current view. It can be manipulated by the user from the
|
||||||
* time conductor or from other views.The time of interest can
|
* time conductor or from other views.The time of interest can
|
||||||
* effectively be unset by assigning a value of 'undefined'.
|
* effectively be unset by assigning a value of 'undefined'.
|
||||||
* @fires module:openmct.TimeConductor~timeOfInterest
|
* @fires module:openmct.TimeAPI~timeOfInterest
|
||||||
* @param newTOI
|
* @param newTOI
|
||||||
* @returns {number} the current time of interest
|
* @returns {number} the current time of interest
|
||||||
* @memberof module:openmct.TimeConductor#
|
* @memberof module:openmct.TimeAPI#
|
||||||
* @method timeOfInterest
|
* @method timeOfInterest
|
||||||
*/
|
*/
|
||||||
TimeConductor.prototype.timeOfInterest = function (newTOI) {
|
TimeAPI.prototype.timeOfInterest = function (newTOI) {
|
||||||
if (arguments.length > 0) {
|
if (arguments.length > 0) {
|
||||||
this.toi = newTOI;
|
this.toi = newTOI;
|
||||||
/**
|
/**
|
||||||
* The Time of Interest has moved.
|
* The Time of Interest has moved.
|
||||||
* @event timeOfInterest
|
* @event timeOfInterest
|
||||||
* @memberof module:openmct.TimeConductor~
|
* @memberof module:openmct.TimeAPI~
|
||||||
* @property {number} Current time of interest
|
* @property {number} Current time of interest
|
||||||
*/
|
*/
|
||||||
this.emit('timeOfInterest', this.toi);
|
this.emit('timeOfInterest', this.toi);
|
||||||
@ -201,5 +232,63 @@ define(['EventEmitter'], function (EventEmitter) {
|
|||||||
return this.toi;
|
return this.toi;
|
||||||
};
|
};
|
||||||
|
|
||||||
return TimeConductor;
|
/**
|
||||||
|
* Return the follow state of the Conductor.
|
||||||
|
* @returns {boolean} `true` if conductor is currently following a tick source.
|
||||||
|
* `false` otherwise.
|
||||||
|
*/
|
||||||
|
TimeAPI.prototype.follow = function () {
|
||||||
|
return this.activeClock !== undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the active clock. Tick source will be immediately subscribed to
|
||||||
|
* and ticking will begin. Offsets from 'now' must also be provided.
|
||||||
|
* @param {string} key the key of the tick source to activate
|
||||||
|
* @param {ClockOffsets} offsets on each tick these will be used to calculate
|
||||||
|
* the start and end bounds. This maintains a sliding time window of a fixed
|
||||||
|
* width that automatically updates.
|
||||||
|
*/
|
||||||
|
TimeAPI.prototype.clock = function (key, offsets) {
|
||||||
|
if (arguments.length === 2) {
|
||||||
|
if (!this.clocks.has(key)){
|
||||||
|
throw "Unknown clock '" + key + "'. Has it been registered with 'addClock'?";
|
||||||
|
}
|
||||||
|
if (this.activeClock !== undefined) {
|
||||||
|
this.activeClock.off("tick", tick);
|
||||||
|
} else {
|
||||||
|
this.emit("follow", true);
|
||||||
|
}
|
||||||
|
if (key !== undefined) {
|
||||||
|
this.activeClock = this.clocks.get(key);
|
||||||
|
this.offsets = offsets;
|
||||||
|
this.activeClock.on("tick", tick);
|
||||||
|
} else {
|
||||||
|
this.activeClock = undefined;
|
||||||
|
this.emit("follow", false);
|
||||||
|
}
|
||||||
|
} else if (arguments.length === 1){
|
||||||
|
throw "When setting clock, clock offsets must also be provided"
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.activeClock && this.activeClock.key;
|
||||||
|
};
|
||||||
|
|
||||||
|
TimeAPI.prototype.clockOffsets = function (offsets) {
|
||||||
|
if (arguments.length > 0) {
|
||||||
|
this.offsets = offsets;
|
||||||
|
this.emit("clockOffsets", offsets);
|
||||||
|
}
|
||||||
|
// TODO: Should setting clock offsets trigger a bounds event, or wait until next tick?
|
||||||
|
return this.offsets;
|
||||||
|
};
|
||||||
|
|
||||||
|
TimeAPI.prototype.stopClock = function () {
|
||||||
|
if (this.activeClock) {
|
||||||
|
this.activeClock.off("tick", tick);
|
||||||
|
this.activeClock = undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return TimeAPI;
|
||||||
});
|
});
|
216
src/api/time/TimeAPISpec.js
Normal file
216
src/api/time/TimeAPISpec.js
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2016, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
define(['./TimeAPI'], function (TimeAPI) {
|
||||||
|
describe("The Time API", function () {
|
||||||
|
var api,
|
||||||
|
timeSystemKey,
|
||||||
|
timeSystem,
|
||||||
|
bounds,
|
||||||
|
eventListener,
|
||||||
|
toi,
|
||||||
|
follow;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
api = new TimeAPI();
|
||||||
|
timeSystemKey = "timeSystemKey";
|
||||||
|
timeSystem = {key: timeSystemKey};
|
||||||
|
bounds = {start: 0, end: 0};
|
||||||
|
eventListener = jasmine.createSpy("eventListener");
|
||||||
|
toi = 111;
|
||||||
|
follow = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Supports setting and querying of time of interest", function () {
|
||||||
|
expect(api.timeOfInterest()).not.toBe(toi);
|
||||||
|
api.timeOfInterest(toi);
|
||||||
|
expect(api.timeOfInterest()).toBe(toi);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Allows setting of valid bounds", function () {
|
||||||
|
bounds = {start: 0, end: 1};
|
||||||
|
expect(api.bounds()).not.toBe(bounds);
|
||||||
|
expect(api.bounds.bind(api, bounds)).not.toThrow();
|
||||||
|
expect(api.bounds()).toEqual(bounds);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Disallows setting of invalid bounds", function () {
|
||||||
|
bounds = {start: 1, end: 0};
|
||||||
|
expect(api.bounds()).not.toEqual(bounds);
|
||||||
|
expect(api.bounds.bind(api, bounds)).toThrow();
|
||||||
|
expect(api.bounds()).not.toEqual(bounds);
|
||||||
|
|
||||||
|
bounds = {start: 1};
|
||||||
|
expect(api.bounds()).not.toEqual(bounds);
|
||||||
|
expect(api.bounds.bind(api, bounds)).toThrow();
|
||||||
|
expect(api.bounds()).not.toEqual(bounds);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Allows setting of previously registered time system with bounds", function () {
|
||||||
|
api.addTimeSystem(timeSystem);
|
||||||
|
expect(api.timeSystem()).not.toBe(timeSystemKey);
|
||||||
|
expect(function() {
|
||||||
|
api.timeSystem(timeSystemKey, bounds);
|
||||||
|
}).not.toThrow();
|
||||||
|
expect(api.timeSystem()).toBe(timeSystemKey);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Disallows setting of time system without bounds", function () {
|
||||||
|
api.addTimeSystem(timeSystem);
|
||||||
|
expect(api.timeSystem()).not.toBe(timeSystemKey);
|
||||||
|
expect(function () {
|
||||||
|
api.timeSystem(timeSystemKey)
|
||||||
|
}).toThrow();
|
||||||
|
expect(api.timeSystem()).not.toBe(timeSystemKey);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Emits an event when time system changes", function () {
|
||||||
|
api.addTimeSystem(timeSystem);
|
||||||
|
expect(eventListener).not.toHaveBeenCalled();
|
||||||
|
api.on("timeSystem", eventListener);
|
||||||
|
api.timeSystem(timeSystemKey, bounds);
|
||||||
|
expect(eventListener).toHaveBeenCalledWith(timeSystemKey);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Emits an event when time of interest changes", function () {
|
||||||
|
expect(eventListener).not.toHaveBeenCalled();
|
||||||
|
api.on("timeOfInterest", eventListener);
|
||||||
|
api.timeOfInterest(toi);
|
||||||
|
expect(eventListener).toHaveBeenCalledWith(toi);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Emits an event when bounds change", function () {
|
||||||
|
expect(eventListener).not.toHaveBeenCalled();
|
||||||
|
api.on("bounds", eventListener);
|
||||||
|
api.bounds(bounds);
|
||||||
|
expect(eventListener).toHaveBeenCalledWith(bounds, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("If bounds are set and TOI lies inside them, do not change TOI", function () {
|
||||||
|
api.timeOfInterest(6);
|
||||||
|
api.bounds({start: 1, end: 10});
|
||||||
|
expect(api.timeOfInterest()).toEqual(6);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("If bounds are set and TOI lies outside them, reset TOI", function () {
|
||||||
|
api.timeOfInterest(11);
|
||||||
|
api.bounds({start: 1, end: 10});
|
||||||
|
expect(api.timeOfInterest()).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Maintains delta during tick", function () {
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it("Allows registered time system to be activated", function () {
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Allows a registered tick source to be activated", function () {
|
||||||
|
var mockTickSource = jasmine.createSpyObj("mockTickSource", [
|
||||||
|
"on",
|
||||||
|
"off",
|
||||||
|
"currentValue"
|
||||||
|
]);
|
||||||
|
mockTickSource.key = 'mockTickSource'
|
||||||
|
});
|
||||||
|
|
||||||
|
describe(" when enabling a tick source", function () {
|
||||||
|
var mockTickSource;
|
||||||
|
var anotherMockTickSource;
|
||||||
|
var mockOffsets = {
|
||||||
|
start: 0,
|
||||||
|
end: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
mockTickSource = jasmine.createSpyObj("clock", [
|
||||||
|
"on",
|
||||||
|
"off"
|
||||||
|
]);
|
||||||
|
mockTickSource.key = "mts";
|
||||||
|
|
||||||
|
anotherMockTickSource = jasmine.createSpyObj("clock", [
|
||||||
|
"on",
|
||||||
|
"off"
|
||||||
|
]);
|
||||||
|
anotherMockTickSource.key = "amts";
|
||||||
|
|
||||||
|
api.addClock(mockTickSource);
|
||||||
|
api.addClock(anotherMockTickSource);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("a new tick listener is registered", function () {
|
||||||
|
api.clock("mts", mockOffsets);
|
||||||
|
expect(mockTickSource.on).toHaveBeenCalledWith("tick", jasmine.any(Function));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("listener of existing tick source is reregistered", function () {
|
||||||
|
api.clock("mts", mockOffsets);
|
||||||
|
api.clock("amts", mockOffsets);
|
||||||
|
expect(mockTickSource.off).toHaveBeenCalledWith("tick", jasmine.any(Function));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Follow correctly reflects whether the conductor is following a " +
|
||||||
|
"tick source", function () {
|
||||||
|
expect(api.follow()).toBe(false);
|
||||||
|
api.clock("mts", mockOffsets);
|
||||||
|
expect(api.follow()).toBe(true);
|
||||||
|
api.stopClock();
|
||||||
|
expect(api.follow()).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("emits an event when follow mode changes", function () {
|
||||||
|
var callback = jasmine.createSpy("followCallback");
|
||||||
|
expect(api.follow()).toBe(false);
|
||||||
|
|
||||||
|
api.on("follow", callback);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it("on tick, observes deltas, and indicates tick in bounds callback", function () {
|
||||||
|
var mockTickSource = jasmine.createSpyObj("clock", [
|
||||||
|
"on",
|
||||||
|
"off"
|
||||||
|
]);
|
||||||
|
var tickCallback;
|
||||||
|
var boundsCallback = jasmine.createSpy("boundsCallback");
|
||||||
|
var clockOffsets = {
|
||||||
|
start: -100,
|
||||||
|
end: 100
|
||||||
|
};
|
||||||
|
mockTickSource.key = "mts";
|
||||||
|
|
||||||
|
api.addClock(mockTickSource);
|
||||||
|
api.clock("mts", clockOffsets);
|
||||||
|
|
||||||
|
api.on("bounds", boundsCallback);
|
||||||
|
|
||||||
|
tickCallback = mockTickSource.on.mostRecentCall.args[1]
|
||||||
|
tickCallback(1000);
|
||||||
|
expect(boundsCallback).toHaveBeenCalledWith({
|
||||||
|
start: 900,
|
||||||
|
end: 1100
|
||||||
|
}, true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -44,7 +44,7 @@ define(['./Type'], function (Type) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a new type of view.
|
* Register a new object type.
|
||||||
*
|
*
|
||||||
* @param {string} typeKey a string identifier for this type
|
* @param {string} typeKey a string identifier for this type
|
||||||
* @param {module:openmct.Type} type the type to add
|
* @param {module:openmct.Type} type the type to add
|
||||||
|
@ -68,7 +68,6 @@ define([
|
|||||||
'../platform/features/fixed/bundle',
|
'../platform/features/fixed/bundle',
|
||||||
'../platform/features/conductor/core/bundle',
|
'../platform/features/conductor/core/bundle',
|
||||||
'../platform/features/conductor/compatibility/bundle',
|
'../platform/features/conductor/compatibility/bundle',
|
||||||
'../platform/features/conductor/utcTimeSystem/bundle',
|
|
||||||
'../platform/features/imagery/bundle',
|
'../platform/features/imagery/bundle',
|
||||||
'../platform/features/layout/bundle',
|
'../platform/features/layout/bundle',
|
||||||
'../platform/features/my-items/bundle',
|
'../platform/features/my-items/bundle',
|
||||||
|
@ -22,12 +22,14 @@
|
|||||||
|
|
||||||
define([
|
define([
|
||||||
'lodash',
|
'lodash',
|
||||||
'../../platform/features/conductor/utcTimeSystem/src/UTCTimeSystem',
|
'./utcTimeSystem/plugin',
|
||||||
'../../example/generator/plugin'
|
'../../example/generator/plugin',
|
||||||
|
'../../platform/features/autoflow/plugin'
|
||||||
], function (
|
], function (
|
||||||
_,
|
_,
|
||||||
UTCTimeSystem,
|
UTCTimeSystem,
|
||||||
GeneratorPlugin
|
GeneratorPlugin,
|
||||||
|
AutoflowPlugin
|
||||||
) {
|
) {
|
||||||
var bundleMap = {
|
var bundleMap = {
|
||||||
CouchDB: 'platform/persistence/couch',
|
CouchDB: 'platform/persistence/couch',
|
||||||
@ -46,65 +48,38 @@ define([
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
plugins.UTCTimeSystem = function () {
|
plugins.UTCTimeSystem = UTCTimeSystem;
|
||||||
return function (openmct) {
|
|
||||||
openmct.legacyExtension("timeSystems", {
|
|
||||||
"implementation": UTCTimeSystem,
|
|
||||||
"depends": ["$timeout"]
|
|
||||||
});
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
var conductorInstalled = false;
|
/**
|
||||||
|
* A tabular view showing the latest values of multiple telemetry points at
|
||||||
|
* once. Formatted so that labels and values are aligned.
|
||||||
|
*
|
||||||
|
* @param {Object} [options] Optional settings to apply to the autoflow
|
||||||
|
* tabular view. Currently supports one option, 'type'.
|
||||||
|
* @param {string} [options.type] The key of an object type to apply this view
|
||||||
|
* to exclusively.
|
||||||
|
*/
|
||||||
|
plugins.AutoflowView = AutoflowPlugin;
|
||||||
|
|
||||||
plugins.Conductor = function (options) {
|
plugins.Conductor = function (config) {
|
||||||
if (!options) {
|
/*
|
||||||
options = {};
|
{
|
||||||
|
// Default 'fixed' configuration shows last 30 mins of data. May also provide specific bounds.
|
||||||
|
{timeSystems: ['utc'], defaultDeltas: {start: 30 * ONE_MINUTE, end: 0}, zoomOutLimit: ONE_YEAR, zoomInLimit: ONE_MINUTE},
|
||||||
|
// Some tick source driven menu options
|
||||||
|
{clock: 'localClock', timeSystems: ['utc'], defaultDeltas: {start: 15 * ONE_MINUTE, end: 0}, zoomOutLimit: ONE_YEAR, zoomInLimit: ONE_MINUTE},
|
||||||
|
{clock: 'latestAvailable', timeSystems: ['utc'], defaultDeltas: {start: 15 * 60 * 1000, end: 0}}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
function applyDefaults(openmct, timeConductorViewService) {
|
|
||||||
var defaults = {};
|
|
||||||
var timeSystem = timeConductorViewService.systems.find(function (ts) {
|
|
||||||
return ts.metadata.key === options.defaultTimeSystem;
|
|
||||||
});
|
|
||||||
if (timeSystem !== undefined) {
|
|
||||||
defaults = timeSystem.defaults();
|
|
||||||
|
|
||||||
if (options.defaultTimespan !== undefined) {
|
|
||||||
defaults.deltas.start = options.defaultTimespan;
|
|
||||||
defaults.bounds.start = defaults.bounds.end - options.defaultTimespan;
|
|
||||||
timeSystem.defaults(defaults);
|
|
||||||
}
|
|
||||||
|
|
||||||
openmct.conductor.timeSystem(timeSystem, defaults.bounds);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return function (openmct) {
|
return function (openmct) {
|
||||||
openmct.legacyExtension('constants', {
|
openmct.legacyExtension('constants', {
|
||||||
key: 'DEFAULT_TIMECONDUCTOR_MODE',
|
key: 'CONDUCTOR_CONFIG',
|
||||||
value: options.showConductor ? 'fixed' : 'realtime',
|
value: config,
|
||||||
priority: conductorInstalled ? 'mandatory' : 'fallback'
|
priority: 'mandatory'
|
||||||
});
|
});
|
||||||
if (options.showConductor !== undefined) {
|
|
||||||
openmct.legacyExtension('constants', {
|
|
||||||
key: 'SHOW_TIMECONDUCTOR',
|
|
||||||
value: options.showConductor,
|
|
||||||
priority: conductorInstalled ? 'mandatory' : 'fallback'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (options.defaultTimeSystem !== undefined || options.defaultTimespan !== undefined) {
|
|
||||||
openmct.legacyExtension('runs', {
|
|
||||||
implementation: applyDefaults,
|
|
||||||
depends: ["openmct", "timeConductorViewService"]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!conductorInstalled) {
|
openmct.legacyRegistry.enable('platform/features/conductor/core');
|
||||||
openmct.legacyRegistry.enable('platform/features/conductor/core');
|
openmct.legacyRegistry.enable('platform/features/conductor/compatibility');
|
||||||
openmct.legacyRegistry.enable('platform/features/conductor/compatibility');
|
|
||||||
}
|
|
||||||
conductorInstalled = true;
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -20,46 +20,53 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
define(['./TickSource'], function (TickSource) {
|
define(['EventEmitter'], function (EventEmitter) {
|
||||||
/**
|
/**
|
||||||
* @implements TickSource
|
* @implements TickSource
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
function LocalClock($timeout, period) {
|
function LocalClock(period) {
|
||||||
TickSource.call(this);
|
EventEmitter.call(this);
|
||||||
|
|
||||||
this.metadata = {
|
/*
|
||||||
key: 'local',
|
Metadata fields
|
||||||
mode: 'realtime',
|
*/
|
||||||
cssClass: 'icon-clock',
|
this.key = 'local';
|
||||||
label: 'Real-time',
|
this.mode = 'realtime';
|
||||||
name: 'Real-time Mode',
|
this.cssClass = 'icon-clock';
|
||||||
description: 'Monitor real-time streaming data as it comes in. The Time Conductor and displays will automatically advance themselves based on a UTC clock.'
|
this.label = 'Real-time';
|
||||||
};
|
this.name = 'Real-time Mode';
|
||||||
|
this.description = 'Monitor real-time streaming data as it comes in. The ' +
|
||||||
|
'Time Conductor and displays will automatically advance themselves ' +
|
||||||
|
'based on a UTC clock.';
|
||||||
|
|
||||||
this.period = period;
|
this.period = period;
|
||||||
this.$timeout = $timeout;
|
|
||||||
this.timeoutHandle = undefined;
|
this.timeoutHandle = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalClock.prototype = Object.create(TickSource.prototype);
|
LocalClock.prototype = Object.create(EventEmitter.prototype);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
LocalClock.prototype.start = function () {
|
LocalClock.prototype.start = function () {
|
||||||
this.timeoutHandle = this.$timeout(this.tick.bind(this), this.period);
|
this.timeoutHandle = setTimeout(this.tick.bind(this), this.period);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
LocalClock.prototype.stop = function () {
|
LocalClock.prototype.stop = function () {
|
||||||
if (this.timeoutHandle) {
|
if (this.timeoutHandle) {
|
||||||
this.$timeout.cancel(this.timeoutHandle);
|
clearTimeout(this.timeoutHandle);
|
||||||
|
this.timeoutHandle = undefined;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
LocalClock.prototype.tick = function () {
|
LocalClock.prototype.tick = function () {
|
||||||
var now = Date.now();
|
var now = Date.now();
|
||||||
this.listeners.forEach(function (listener) {
|
this.emit("tick", now);
|
||||||
listener(now);
|
this.timeoutHandle = setTimeout(this.tick.bind(this), this.period);
|
||||||
});
|
|
||||||
this.timeoutHandle = this.$timeout(this.tick.bind(this), this.period);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -69,20 +76,30 @@ define(['./TickSource'], function (TickSource) {
|
|||||||
* @param listener
|
* @param listener
|
||||||
* @returns {function} a function for deregistering the provided listener
|
* @returns {function} a function for deregistering the provided listener
|
||||||
*/
|
*/
|
||||||
LocalClock.prototype.listen = function (listener) {
|
LocalClock.prototype.on = function (event, listener) {
|
||||||
var listeners = this.listeners;
|
var result = this.on.apply(this, arguments);
|
||||||
listeners.push(listener);
|
|
||||||
|
|
||||||
if (listeners.length === 1) {
|
if (this.listeners(event).length === 1) {
|
||||||
this.start();
|
this.start();
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
return function () {
|
/**
|
||||||
listeners.splice(listeners.indexOf(listener));
|
* Register a listener for the local clock. When it ticks, the local
|
||||||
if (listeners.length === 0) {
|
* clock will provide the current local system time
|
||||||
this.stop();
|
*
|
||||||
}
|
* @param listener
|
||||||
}.bind(this);
|
* @returns {function} a function for deregistering the provided listener
|
||||||
|
*/
|
||||||
|
LocalClock.prototype.off = function (event, listener) {
|
||||||
|
var result = this.off.apply(this, arguments);
|
||||||
|
|
||||||
|
if (this.listeners(event).length === 0) {
|
||||||
|
this.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
return LocalClock;
|
return LocalClock;
|
@ -20,39 +20,28 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
define([
|
define([], function () {
|
||||||
'../../core/src/timeSystems/TimeSystem',
|
var FIFTEEN_MINUTES = 15 * 60 * 1000;
|
||||||
'../../core/src/timeSystems/LocalClock'
|
|
||||||
], function (TimeSystem, LocalClock) {
|
|
||||||
var FIFTEEN_MINUTES = 15 * 60 * 1000,
|
|
||||||
DEFAULT_PERIOD = 100;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This time system supports UTC dates and provides a ticking clock source.
|
* This time system supports UTC dates and provides a ticking clock source.
|
||||||
* @implements TimeSystem
|
* @implements TimeSystem
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
function UTCTimeSystem($timeout) {
|
function UTCTimeSystem() {
|
||||||
TimeSystem.call(this);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Some metadata, which will be used to identify the time system in
|
* Some metadata, which will be used to identify the time system in
|
||||||
* the UI
|
* the UI
|
||||||
* @type {{key: string, name: string, cssClass: string}}
|
* @type {{key: string, name: string, cssClass: string}}
|
||||||
*/
|
*/
|
||||||
this.metadata = {
|
this.key = 'utc';
|
||||||
'key': 'utc',
|
this.name = 'UTC';
|
||||||
'name': 'UTC',
|
this.cssClass = 'icon-clock';
|
||||||
'cssClass': 'icon-clock'
|
|
||||||
};
|
|
||||||
|
|
||||||
this.fmts = ['utc'];
|
this.fmts = ['utc'];
|
||||||
this.sources = [new LocalClock($timeout, DEFAULT_PERIOD)];
|
|
||||||
this.defaultValues = undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
UTCTimeSystem.prototype = Object.create(TimeSystem.prototype);
|
|
||||||
|
|
||||||
UTCTimeSystem.prototype.formats = function () {
|
UTCTimeSystem.prototype.formats = function () {
|
||||||
return this.fmts;
|
return this.fmts;
|
||||||
};
|
};
|
||||||
@ -61,29 +50,22 @@ define([
|
|||||||
return 'duration';
|
return 'duration';
|
||||||
};
|
};
|
||||||
|
|
||||||
UTCTimeSystem.prototype.tickSources = function () {
|
UTCTimeSystem.prototype.isUTCBased = function () {
|
||||||
return this.sources;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
UTCTimeSystem.prototype.defaults = function (defaults) {
|
UTCTimeSystem.prototype.defaults = function () {
|
||||||
if (arguments.length > 0) {
|
var now = Math.ceil(Date.now() / 1000) * 1000;
|
||||||
this.defaultValues = defaults;
|
var ONE_MINUTE = 60 * 1 * 1000;
|
||||||
}
|
var FIFTY_YEARS = 50 * 365 * 24 * 60 * 60 * 1000;
|
||||||
|
|
||||||
if (this.defaultValues === undefined) {
|
return {
|
||||||
var now = Math.ceil(Date.now() / 1000) * 1000;
|
key: 'utc-default',
|
||||||
var ONE_MINUTE = 60 * 1 * 1000;
|
name: 'UTC time system defaults',
|
||||||
var FIFTY_YEARS = 50 * 365 * 24 * 60 * 60 * 1000;
|
deltas: {start: FIFTEEN_MINUTES, end: 0},
|
||||||
|
bounds: {start: now - FIFTEEN_MINUTES, end: now},
|
||||||
this.defaultValues = {
|
zoom: {min: FIFTY_YEARS, max: ONE_MINUTE}
|
||||||
key: 'utc-default',
|
};
|
||||||
name: 'UTC time system defaults',
|
|
||||||
deltas: {start: FIFTEEN_MINUTES, end: 0},
|
|
||||||
bounds: {start: now - FIFTEEN_MINUTES, end: now},
|
|
||||||
zoom: {min: FIFTY_YEARS, max: ONE_MINUTE}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return this.defaultValues;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return UTCTimeSystem;
|
return UTCTimeSystem;
|
@ -21,20 +21,16 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
define([
|
define([
|
||||||
"./src/UTCTimeSystem",
|
"./UTCTimeSystem",
|
||||||
"legacyRegistry"
|
"./LocalClock"
|
||||||
], function (
|
], function (
|
||||||
UTCTimeSystem,
|
UTCTimeSystem,
|
||||||
legacyRegistry
|
LocalClock
|
||||||
) {
|
) {
|
||||||
legacyRegistry.register("platform/features/conductor/utcTimeSystem", {
|
return function () {
|
||||||
"extensions": {
|
return function (openmct) {
|
||||||
"timeSystems": [
|
openmct.time.addTimeSystem(new UTCTimeSystem());
|
||||||
{
|
openmct.time.addClock(new LocalClock());
|
||||||
"implementation": UTCTimeSystem,
|
|
||||||
"depends": ["$timeout"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
});
|
});
|
Reference in New Issue
Block a user