mirror of
https://github.com/nasa/openmct.git
synced 2025-04-14 14:36:50 +00:00
Merge pull request #1553 from nasa/time-api-redo
[Time API] V1.0 Time API and associated refactoring
This commit is contained in:
commit
1f250dd8e7
433
API.md
433
API.md
@ -352,6 +352,59 @@ request object with a start and end time is included below:
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### Telemetry Formats
|
||||
|
||||
Telemetry format objects define how to interpret and display telemetry data.
|
||||
They have a simple structure:
|
||||
|
||||
* `key`: A `string` that uniquely identifies this formatter.
|
||||
* `format`: A `function` that takes a raw telemetry value, and returns a human-readable
|
||||
`string` representation of that value. It has one required argument, and three
|
||||
optional arguments that provide context and can be used for returning scaled
|
||||
representations of a value. An example of this is representing time values in a
|
||||
scale such as the time conductor scale. There are multiple ways of representing
|
||||
a point in time, and by providing a minimum scale value, maximum scale value,
|
||||
and a count, it's possible to provide more useful representations of time given
|
||||
the provided limitations.
|
||||
* `value`: The raw telemetry value in its native type.
|
||||
* `minValue`: An __optional__ argument specifying the minimum displayed value.
|
||||
* `maxValue`: An __optional__ argument specifying the maximum displayed value.
|
||||
* `count`: An __optional__ argument specifying the number of displayed values.
|
||||
* `parse`: A `function` that takes a `string` representation of a telemetry value,
|
||||
and returns the value in its native type. It accepts one argument:
|
||||
* `text`: A `string` representation of a telemetry value.
|
||||
* `validate`: A `function` that takes a `string` representation of a telemetry
|
||||
value, and returns a `boolean` value indicating whether the provided string can be
|
||||
parsed.
|
||||
|
||||
#### Registering Formats
|
||||
|
||||
Formats are registered with the Telemetry API using the `addFormat` function. eg.
|
||||
|
||||
``` javascript
|
||||
openmct.telemetry.addFormat({
|
||||
key: 'number-to-string',
|
||||
format: function (number) {
|
||||
return number + '';
|
||||
},
|
||||
parse: function (text) {
|
||||
return Number(text);
|
||||
},
|
||||
validate: function (text) {
|
||||
return !isNaN(text);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
#### Examples of Formats in Use
|
||||
* The [NumberFormat](https://github.com/nasa/openmct/blob/time-api-redo/platform/features/conductor/core/src/ui/NumberFormat.js)
|
||||
provides an example of a simple format available by default
|
||||
in OpenMCT.
|
||||
* The [UTCTimeFormat](https://github.com/nasa/openmct/blob/master/src/plugins/utcTimeSystem/UTCTimeFormat.js)
|
||||
is a more complex implementation of a format that uses the optional context
|
||||
arguments in `format` to provide scale-appropriate values.
|
||||
|
||||
### Telemetry Data
|
||||
|
||||
Telemetry data is provided to Open MCT by _[Telemetry Providers](#telemetry-providers)_
|
||||
@ -390,10 +443,390 @@ openmct.telemetry.addProvider({
|
||||
})
|
||||
```
|
||||
|
||||
## Time API
|
||||
Open MCT provides API for managing the temporal state of the application. Central
|
||||
to this is the concept of "time bounds". Views in Open MCT will typically show
|
||||
telemetry data for some prescribed date range, and the Time API provides a way
|
||||
to centrally manage these bounds.
|
||||
|
||||
The Time API exposes a number of methods for querying and setting the temporal
|
||||
state of the application, and emits events to inform listeners when the state changes.
|
||||
|
||||
Because the data displayed tends to be time domain data, Open MCT must always
|
||||
have at least one time system installed and activated. When you download Open MCT,
|
||||
it will be pre-configured to use the UTC time system, which is installed and
|
||||
activated, along with other default plugins, in `index.html`. Installing and
|
||||
activating a time system is simple, and is covered
|
||||
[in the next section](#defining-and-registering-time-systems).
|
||||
|
||||
### Time Systems and Bounds
|
||||
#### Defining and Registering Time Systems
|
||||
The time bounds of an Open MCT application are defined as numbers, and a Time
|
||||
System gives meaning and context to these numbers so that they can be correctly
|
||||
interpreted. Time Systems are javscript objects that provide some information
|
||||
about the current time reference frame. An example of defining and registering
|
||||
a new time system is given below:
|
||||
|
||||
``` javascript
|
||||
openmct.time.addTimeSystem({
|
||||
key: 'utc',
|
||||
name: 'UTC Time',
|
||||
cssClass = 'icon-clock',
|
||||
timeFormat = 'utc',
|
||||
durationFormat = 'duration',
|
||||
isUTCBased = true
|
||||
});
|
||||
```
|
||||
|
||||
The example above defines a new utc based time system. In fact, this time system
|
||||
is configured and activated by default from `index.html` in the default
|
||||
installation of Open MCT if you download the source from GitHub. Some details of
|
||||
each of the required properties is provided below.
|
||||
|
||||
* `key`: A `string` that uniquely identifies this time system.
|
||||
* `name`: A `string` providing a brief human readable label. If the [Time Conductor](#the-time-conductor)
|
||||
plugin is enabled, this name will identify the time system in a dropdown menu.
|
||||
* `cssClass`: A class name `string` that will be applied to the time system when
|
||||
it appears in the UI. This will be used to represent the time system with an icon.
|
||||
There are a number of built-in icon classes [available in Open MCT](https://github.com/nasa/openmct/blob/master/platform/commonUI/general/res/sass/_glyphs.scss),
|
||||
or a custom class can be used here.
|
||||
* `timeFormat`: A `string` corresponding to the key of a registered
|
||||
[telemetry time format](#telemetry-formats). The format will be used for
|
||||
displaying discrete timestamps from telemetry streams when this time system is
|
||||
activated. If the [UTCTimeSystem](#included-plugins) is enabled, then the `utc`
|
||||
format can be used if this is a utc-based time system
|
||||
* `durationFormat`: A `string` corresponding to the key of a registered
|
||||
[telemetry time format](#telemetry-formats). The format will be used for
|
||||
displaying time ranges, for example `00:15:00` might be used to represent a time
|
||||
period of fifteen minutes. These are used by the Time Conductor plugin to specify
|
||||
relative time offsets. If the [UTCTimeSystem](#included-plugins) is enabled,
|
||||
then the `duration` format can be used if this is a utc-based time system
|
||||
* `isUTCBased`: A `boolean` that defines whether this time system represents
|
||||
numbers in UTC terrestrial time.
|
||||
|
||||
#### Getting and Setting the Active Time System
|
||||
|
||||
Once registered, a time system can be activated using a key, or an instance of
|
||||
the time system itself.
|
||||
|
||||
```javascript
|
||||
openmct.time.timeSystem('utc');
|
||||
```
|
||||
|
||||
A time system can be immediately activated upon registration:
|
||||
|
||||
```javascript
|
||||
var utcTimeSystem = {
|
||||
key: 'utc',
|
||||
name: 'UTC Time',
|
||||
cssClass = 'icon-clock',
|
||||
timeFormat = 'utc',
|
||||
durationFormat = 'duration',
|
||||
isUTCBased = true
|
||||
};
|
||||
openmct.time.addTimeSystem(utcTimeSystem);
|
||||
openmct.time.timeSystem(utcTimeSystem);
|
||||
```
|
||||
|
||||
Setting the active time system will trigger a [time system event](#time-events).
|
||||
|
||||
### Time Bounds
|
||||
|
||||
The TimeAPI provides a getter/setter for querying and setting time bounds. Time
|
||||
bounds are simply an object with a `start` and an end `end` attribute.
|
||||
|
||||
* `start`: A `number` representing a moment in time in the active [Time System](#defining-and-registering-time-systems).
|
||||
This will be used as the beginning of the time period displayed by time-responsive
|
||||
telemetry views.
|
||||
* `end`: A `number` representing a moment in time in the active [Time System](#defining-and-registering-time-systems).
|
||||
This will be used as the end of the time period displayed by time-responsive
|
||||
telemetry views.
|
||||
|
||||
If invoked with bounds, it will set the new time bounds system-wide. If invoked
|
||||
without any parameters, it will return the current application-wide time bounds.
|
||||
|
||||
``` javascript
|
||||
const ONE_HOUR = 60 * 60 * 1000;
|
||||
let now = Date.now();
|
||||
openmct.time.bounds({start: now - ONE_HOUR, now);
|
||||
```
|
||||
|
||||
To respond to bounds change events, simply register a callback against the `bounds`
|
||||
event. For more information on the bounds event, please see the section on [Time Events](#time-events).
|
||||
|
||||
## Clocks
|
||||
|
||||
The Time API can be set to follow a clock source which will cause the bounds
|
||||
to be updated automatically whenever the clock source "ticks". A clock is simply
|
||||
an object that supports registration of listeners and periodically invokes its
|
||||
listeners with a number. Open MCT supports registration of new clock sources that
|
||||
tick on almost anything. A tick occurs when the clock invokes callback functions
|
||||
registered by its listeners with a new time value.
|
||||
|
||||
An example of a clock source is the [LocalClock](https://github.com/nasa/openmct/blob/master/src/plugins/utcTimeSystem/LocalClock.js)
|
||||
which emits the current time in UTC every 100ms. Clocks can tick on anything. For
|
||||
example, a clock could be defined to provide the timestamp of any new data
|
||||
received via a telemetry subscription. This would have the effect of advancing
|
||||
the bounds of views automatically whenever data is received. A clock could also
|
||||
be defined to tick on some remote timing source.
|
||||
|
||||
The values provided by clocks are simple `number`s, which are interpreted in the
|
||||
context of the active [Time System](#defining-and-registering-time-systems).
|
||||
|
||||
### Defining and registering clocks
|
||||
A clock is an object that defines certain required metadata and functions:
|
||||
|
||||
* `key`: A `string` uniquely identifying this clock. This can be used later to
|
||||
reference the clock in places such as the [Time Conductor configuration](#time-conductor-configuration)
|
||||
* `cssClass`: A `string` identifying a CSS class to apply to this clock when it's
|
||||
displayed in the UI. This will be used to represent the time system with an icon.
|
||||
There are a number of built-in icon classes [available in Open MCT](https://github.com/nasa/openmct/blob/master/platform/commonUI/general/res/sass/_glyphs.scss),
|
||||
or a custom class can be used here.
|
||||
* `name`: A `string` providing a human-readable identifier for the clock source.
|
||||
This will be displayed in the clock selector menu in the Time Conductor UI
|
||||
component, if active.
|
||||
* `description`: An __optional__ `string` providing a longer description of the
|
||||
clock. The description will be visible in the clock selection menu in the Time
|
||||
Conductor plugin.
|
||||
* `on`: A `function` supporting registration of a new callback that will be
|
||||
invoked when the clock next ticks. It will be invoked with two arguments:
|
||||
* `eventName`: A `string` specifying the event to listen on. For now, clocks
|
||||
support one event - `tick`.
|
||||
* `callback`: A `function` that will be invoked when this clock ticks. The
|
||||
function must be invoked with one parameter - a `number` representing a valid
|
||||
time in the current time system.
|
||||
* `off`: A `function` that allows deregistration of a tick listener. It accepts
|
||||
the same arguments as `on`.
|
||||
* `currentValue`: A `function` that returns a `number` representing a point in
|
||||
time in the active time system. It should be the last value provided by a tick,
|
||||
or some default value if no ticking has yet occurred.
|
||||
|
||||
A new clock can be registered using the `addClock` function exposed by the Time
|
||||
API:
|
||||
|
||||
```javascript
|
||||
var someClock = {
|
||||
key: 'someClock',
|
||||
cssClass: 'icon-clock',
|
||||
name: 'Some clock',
|
||||
description: "Presumably does something useful",
|
||||
on: function (event, callback) {
|
||||
// Some function that registers listeners, and updates them on a tick
|
||||
},
|
||||
off: function (event, callback) {
|
||||
// Some function that unregisters listeners.
|
||||
},
|
||||
currentValue: function () {
|
||||
// A function that returns the last ticked value for the clock
|
||||
}
|
||||
}
|
||||
|
||||
openmct.time.addClock(someClock);
|
||||
```
|
||||
|
||||
An example clock implementation is provided in the form of the [LocalClock](https://github.com/nasa/openmct/blob/master/src/plugins/utcTimeSystem/LocalClock.js)
|
||||
|
||||
#### Getting and setting active clock
|
||||
|
||||
Once registered a clock can be activated by calling the `clock` function on the
|
||||
Time API passing in the key or instance of a registered clock. Only one clock
|
||||
may be active at once, so activating a clock will deactivate any currently
|
||||
active clock. Setting the clock will also trigger a ['clock' event](#time-events).
|
||||
|
||||
```
|
||||
openmct.time.clock(someClock);
|
||||
```
|
||||
|
||||
Upon being activated, a clock's `on` function will be immediately called to subscribe
|
||||
to `tick` events.
|
||||
|
||||
The currently active clock (if any) can be retrieved by calling the same
|
||||
function without any arguments.
|
||||
|
||||
#### Stopping an active clock
|
||||
|
||||
The `stopClock` method can be used to stop an active clock, and to clear it. It
|
||||
will stop the clock from ticking, and set the active clock to `undefined`.
|
||||
|
||||
``` javascript
|
||||
openmct.time.stopClock();
|
||||
```
|
||||
|
||||
#### Clock Offsets
|
||||
|
||||
When a clock is active, the time bounds of the application will be updated
|
||||
automatically each time the clock "ticks". The bounds are calculated based on
|
||||
the current value provided by the active clock (via its `tick` event, or its
|
||||
`currentValue()` method).
|
||||
|
||||
Unlike bounds, which represent absolute time values, clock offsets represent
|
||||
relative time spans. Offsets are defined as an object with two properties:
|
||||
|
||||
* `start`: A `number` that must be < 0 and which is used to calculate the start
|
||||
bounds on each clock tick. The start offset will be calculated relative to the
|
||||
value provided by a clock's tick callback, or its `currentValue()` function.
|
||||
* `end`: A `number` that must be >=0 and which is used to calculate the end
|
||||
bounds on each clock tick.
|
||||
|
||||
The `clockOffsets` function can be used to get or set clock offsets. For example,
|
||||
to show the last fifteen minutes in a ms-based time system:
|
||||
|
||||
```javascript
|
||||
var FIFTEEN_MINUTES = 15 * 60 * 1000;
|
||||
|
||||
openmct.time.clockOffsets({
|
||||
start: -FIFTEEN_MINUTES,
|
||||
end: 0
|
||||
})
|
||||
```
|
||||
|
||||
__Note:__ Setting the clock offsets will trigger an immediate bounds change, as
|
||||
new bounds will be calculated based on the `currentValue()` of the active clock.
|
||||
Clock offsets are only relevant when a clock source is active.
|
||||
|
||||
## Time Events
|
||||
The time API supports the registration of listeners that will be invoked when the
|
||||
application's temporal state changes. Events listeners can be registered using
|
||||
the `on` function. They can be deregistered using the `off` function. The arguments
|
||||
accepted by the `on` and `off` functions are:
|
||||
|
||||
* `event`: A `string` naming the event to subscribe to. Event names correspond to
|
||||
the property of the Time API you're interested in. A [full list of time events](#list-of-time-events)
|
||||
is provided later.
|
||||
|
||||
As an example, the code to listen to bounds change events looks like:
|
||||
|
||||
``` javascript
|
||||
openmct.time.on('bounds', function callback (newBounds, tick) {
|
||||
// Do something with new bounds
|
||||
});
|
||||
```
|
||||
|
||||
#### List of Time Events
|
||||
The events supported by the Time API are:
|
||||
|
||||
* `bounds`: Listen for changes to current bounds. The callback will be invoked
|
||||
with two arguments:
|
||||
* `bounds`: A [bounds](#getting-and-setting-bounds) bounds object representing
|
||||
a new time period bound by the specified start and send times.
|
||||
* `tick`: A `boolean` indicating whether or not this bounds change is due to a
|
||||
"tick" from a [clock source](#clocks). This information can be useful when
|
||||
determining a strategy for fetching telemetry data in response to a bounds
|
||||
change event. For example, if the bounds change was automatic, and is due
|
||||
to a tick then it's unlikely that you would need to perform a historical
|
||||
data query. It should be sufficient to just show any new telemetry received
|
||||
via subscription since the last tick, and optionally to discard any older
|
||||
data that now falls outside of the currently set bounds. If `tick` is false,
|
||||
then the bounds change was not due to an automatic tick, and a query for
|
||||
historical data may be necessary, depending on your data caching strategy,
|
||||
and how significantly the start bound has changed.
|
||||
* `timeSystem`: Listen for changes to the active [time system](#defining-and-registering-time-systems).
|
||||
The callback will be invoked with a single argument - the newly active time system.
|
||||
* `timeSystem`: The newly active [time system](#defining-and-registering-time-systems) object.
|
||||
* `clock`: Listen for changes to the active clock. When invoked, the callback
|
||||
will be provided with the new clock.
|
||||
* `clock`: The newly active [clock](#clocks), or `undefined` if an active clock
|
||||
has been deactivated.
|
||||
* `clockOffsets`: Listen for changes to active clock offsets. When invoked the
|
||||
callback will be provided with the new clock offsets.
|
||||
* `clockOffsets`: A [clock offsets](#clock-offsets) object.
|
||||
|
||||
|
||||
## The Time Conductor
|
||||
The Time Conductor provides a user interface for managing time bounds in Open MCT.
|
||||
It allows a user to select from configured time systems and clocks, and to set bounds
|
||||
and clock offsets.
|
||||
|
||||
If activated, the time conductor must be provided with configuration options,
|
||||
detailed below.
|
||||
|
||||
#### Time Conductor Configuration
|
||||
The time conductor is configured by specifying the options that will be
|
||||
available to the user from the menus in the time conductor. These will determine
|
||||
the clocks available from the conductor, the time systems available for each
|
||||
clock, and some default bounds and clock offsets for each combination of clock
|
||||
and time system. By default, the conductor always supports a `fixed` mode where
|
||||
no clock is active. To specify configuration for fixed mode, simply leave out a
|
||||
`clock` attribute in the configuration entry object.
|
||||
|
||||
Configuration is provided as an `array` of menu options. Each entry of the
|
||||
array is an object with some properties specifying configuration. The configuration
|
||||
options specified are slightly different depending on whether or not it is for
|
||||
an active clock mode.
|
||||
|
||||
__Configuration for Fixed Time Mode (no active clock)__
|
||||
|
||||
* `timeSystem`: A `string`, the key for the time system that this configuration
|
||||
relates to.
|
||||
* `bounds`: A [`Time Bounds`](#time-bounds) object. These bounds will be applied
|
||||
when the user selects the time system specified in the previous `timeSystem`
|
||||
property.
|
||||
* `zoomOutLimit`: An __optional__ `number` specifying the longest period of time
|
||||
that can be represented by the conductor when zooming. If a `zoomOutLimit` is
|
||||
provided, then a `zoomInLimit` must also be provided. If provided, the zoom
|
||||
slider will automatically become available in the Time Conductor UI.
|
||||
* `zoomInLimit`: An __optional__ `number` specifying the shortest period of time
|
||||
that can be represented by the conductor when zooming. If a `zoomInLimit` is
|
||||
provided, then a `zoomOutLimit` must also be provided. If provided, the zoom
|
||||
slider will automatically become available in the Time Conductor UI.
|
||||
|
||||
__Configuration for Active Clock__
|
||||
|
||||
* `clock`: A `string`, the `key` of the clock that this configuration applies to.
|
||||
* `timeSystem`: A `string`, the key for the time system that this configuration
|
||||
relates to. Separate configuration must be provided for each time system that you
|
||||
wish to be available to users when they select the specified clock.
|
||||
* `clockOffsets`: A [`clockOffsets`](#clock-offsets) object that will be
|
||||
automatically applied when the combination of clock and time system specified in
|
||||
this configuration is selected from the UI.
|
||||
|
||||
#### Example conductor configuration
|
||||
An example time conductor configuration is provided below. It sets up some
|
||||
default options for the [UTCTimeSystem](https://github.com/nasa/openmct/blob/master/src/plugins/utcTimeSystem/UTCTimeSystem.js)
|
||||
and [LocalTimeSystem](https://github.com/nasa/openmct/blob/master/src/plugins/localTimeSystem/LocalTimeSystem.js),
|
||||
in both fixed mode, and for the [LocalClock](https://github.com/nasa/openmct/blob/master/src/plugins/utcTimeSystem/LocalClock.js)
|
||||
source. In this configutation, the local clock supports both the UTCTimeSystem
|
||||
and LocalTimeSystem. Configuration for fixed bounds mode is specified by omitting
|
||||
a clock key.
|
||||
|
||||
``` javascript
|
||||
const ONE_YEAR = 365 * 24 * 60 * 60 * 1000;
|
||||
const ONE_MINUTE = 60 * 1000;
|
||||
|
||||
openmct.install(openmct.plugins.Conductor({
|
||||
menuOptions: [
|
||||
// 'Fixed' bounds mode configuation for the UTCTimeSystem
|
||||
{
|
||||
timeSystem: 'utc',
|
||||
bounds: {start: Date.now() - 30 * ONE_MINUTE, end: Date.now()},
|
||||
zoomOutLimit: ONE_YEAR,
|
||||
zoomInLimit: ONE_MINUTE
|
||||
},
|
||||
// Configuration for the LocalClock in the UTC time system
|
||||
{
|
||||
clock: 'local',
|
||||
timeSystem: 'utc',
|
||||
clockOffsets: {start: - 30 * ONE_MINUTE, end: 0},
|
||||
zoomOutLimit: ONE_YEAR,
|
||||
zoomInLimit: ONE_MINUTE
|
||||
},
|
||||
//Configuration for the LocaLClock in the Local time system
|
||||
{
|
||||
clock: 'local',
|
||||
timeSystem: 'local',
|
||||
clockOffsets: {start: - 15 * ONE_MINUTE, end: 0}
|
||||
}
|
||||
]
|
||||
}));
|
||||
```
|
||||
|
||||
## Included Plugins
|
||||
|
||||
Open MCT is packaged along with a few general-purpose plugins:
|
||||
|
||||
* `openmct.plugins.Conductor` provides a user interface for working with time
|
||||
within the application. If activated, configuration must be provided. This is
|
||||
detailed in the section on [Time Conductor Configuration](#time-conductor-configuration).
|
||||
* `openmct.plugins.CouchDB` is an adapter for using CouchDB for persistence
|
||||
of user-created objects. This is a constructor that takes the URL for the
|
||||
CouchDB database as a parameter, e.g.
|
||||
|
@ -1,79 +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([
|
||||
'../../../platform/features/conductor/core/src/timeSystems/TimeSystem',
|
||||
'../../../platform/features/conductor/core/src/timeSystems/LocalClock',
|
||||
'./LADTickSource'
|
||||
], function (TimeSystem, LocalClock, LADTickSource) {
|
||||
var THIRTY_MINUTES = 30 * 60 * 1000,
|
||||
DEFAULT_PERIOD = 1000;
|
||||
|
||||
/**
|
||||
* This time system supports UTC dates and provides a ticking clock source.
|
||||
* @implements TimeSystem
|
||||
* @constructor
|
||||
*/
|
||||
function LocalTimeSystem ($timeout) {
|
||||
TimeSystem.call(this);
|
||||
|
||||
/**
|
||||
* Some metadata, which will be used to identify the time system in
|
||||
* the UI
|
||||
* @type {{key: string, name: string, glyph: string}}
|
||||
*/
|
||||
this.metadata = {
|
||||
'key': 'local',
|
||||
'name': 'Local',
|
||||
'glyph': '\u0043'
|
||||
};
|
||||
|
||||
this.fmts = ['local-format'];
|
||||
this.sources = [new LocalClock($timeout, DEFAULT_PERIOD), new LADTickSource($timeout, DEFAULT_PERIOD)];
|
||||
}
|
||||
|
||||
LocalTimeSystem.prototype = Object.create(TimeSystem.prototype);
|
||||
|
||||
LocalTimeSystem.prototype.formats = function () {
|
||||
return this.fmts;
|
||||
};
|
||||
|
||||
LocalTimeSystem.prototype.deltaFormat = function () {
|
||||
return 'duration';
|
||||
};
|
||||
|
||||
LocalTimeSystem.prototype.tickSources = function () {
|
||||
return this.sources;
|
||||
};
|
||||
|
||||
LocalTimeSystem.prototype.defaults = function (key) {
|
||||
var now = Math.ceil(Date.now() / 1000) * 1000;
|
||||
return {
|
||||
key: 'local-default',
|
||||
name: 'Local 12 hour time system defaults',
|
||||
deltas: {start: THIRTY_MINUTES, end: 0},
|
||||
bounds: {start: now - THIRTY_MINUTES, end: now}
|
||||
};
|
||||
};
|
||||
|
||||
return LocalTimeSystem;
|
||||
});
|
@ -28,6 +28,8 @@
|
||||
<script src="bower_components/requirejs/require.js">
|
||||
</script>
|
||||
<script>
|
||||
var THIRTY_MINUTES = 30 * 60 * 1000;
|
||||
|
||||
require(['openmct'], function (openmct) {
|
||||
[
|
||||
'example/imagery',
|
||||
@ -40,6 +42,7 @@
|
||||
openmct.install(openmct.plugins.Espresso());
|
||||
openmct.install(openmct.plugins.Generator());
|
||||
openmct.install(openmct.plugins.UTCTimeSystem());
|
||||
openmct.time.timeSystem("utc", {start: Date.now() - THIRTY_MINUTES, end: Date.now()});
|
||||
openmct.start();
|
||||
});
|
||||
</script>
|
||||
|
@ -100,11 +100,5 @@ define([
|
||||
return new Main().run(defaultRegistry);
|
||||
});
|
||||
|
||||
// For now, install conductor by default
|
||||
openmct.install(openmct.plugins.Conductor({
|
||||
showConductor: false
|
||||
}));
|
||||
|
||||
|
||||
return openmct;
|
||||
});
|
||||
|
@ -22,13 +22,9 @@
|
||||
|
||||
define([
|
||||
"./src/FormatProvider",
|
||||
"./src/UTCTimeFormat",
|
||||
"./src/DurationFormat",
|
||||
'legacyRegistry'
|
||||
], function (
|
||||
FormatProvider,
|
||||
UTCTimeFormat,
|
||||
DurationFormat,
|
||||
legacyRegistry
|
||||
) {
|
||||
|
||||
@ -46,22 +42,6 @@ define([
|
||||
]
|
||||
}
|
||||
],
|
||||
"formats": [
|
||||
{
|
||||
"key": "utc",
|
||||
"implementation": UTCTimeFormat
|
||||
},
|
||||
{
|
||||
"key": "duration",
|
||||
"implementation": DurationFormat
|
||||
}
|
||||
],
|
||||
"constants": [
|
||||
{
|
||||
"key": "DEFAULT_TIME_FORMAT",
|
||||
"value": "utc"
|
||||
}
|
||||
],
|
||||
"licenses": [
|
||||
{
|
||||
"name": "d3",
|
||||
|
@ -30,19 +30,23 @@ define([
|
||||
* An object used to convert between numeric values and text values,
|
||||
* typically used to display these values to the user and to convert
|
||||
* user input to a numeric format, particularly for time formats.
|
||||
* @interface {Format}
|
||||
* @interface Format
|
||||
*/
|
||||
|
||||
/**
|
||||
* Parse text (typically user input) to a numeric value.
|
||||
* Behavior is undefined when the text cannot be parsed;
|
||||
* `validate` should be called first if the text may be invalid.
|
||||
* @method parse
|
||||
* @method Format#parse
|
||||
* @memberof Format#
|
||||
* @param {string} text the text to parse
|
||||
* @returns {number} the parsed numeric value
|
||||
*/
|
||||
|
||||
/**
|
||||
* @property {string} key A unique identifier for this formatter.
|
||||
* @memberof Format#
|
||||
*/
|
||||
|
||||
/**
|
||||
* Determine whether or not some text (typically user input) can
|
||||
* be parsed to a numeric value by this format.
|
||||
@ -58,10 +62,12 @@ define([
|
||||
* @method format
|
||||
* @memberof Format#
|
||||
* @param {number} value the numeric value to format
|
||||
* @param {number} [threshold] Optionally provides context to the
|
||||
* format request, allowing for scale-appropriate formatting. This value
|
||||
* should be the minimum unit to be represented by this format, in ms. For
|
||||
* example, to display seconds, a threshold of 1 * 1000 should be provided.
|
||||
* @param {number} [minValue] Contextual information for scaled formatting used in linear scales such as conductor
|
||||
* and plot axes. Specifies the smallest number on the scale.
|
||||
* @param {number} [maxValue] Contextual information for scaled formatting used in linear scales such as conductor
|
||||
* and plot axes. Specifies the largest number on the scale
|
||||
* @param {number} [count] Contextual information for scaled formatting used in linear scales such as conductor
|
||||
* and plot axes. The number of labels on the scale.
|
||||
* @returns {string} the text representation of the value
|
||||
*/
|
||||
|
||||
|
@ -1,60 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2017, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define(
|
||||
['../src/UTCTimeFormat', 'moment'],
|
||||
function (UTCTimeFormat, moment) {
|
||||
|
||||
describe("The UTCTimeFormat", function () {
|
||||
var format;
|
||||
|
||||
beforeEach(function () {
|
||||
format = new UTCTimeFormat();
|
||||
});
|
||||
|
||||
it("formats UTC timestamps", function () {
|
||||
var timestamp = 12345670000,
|
||||
formatted = format.format(timestamp);
|
||||
expect(formatted).toEqual(jasmine.any(String));
|
||||
expect(moment.utc(formatted).valueOf()).toEqual(timestamp);
|
||||
});
|
||||
|
||||
it("displays with millisecond precision", function () {
|
||||
var timestamp = 12345670789,
|
||||
formatted = format.format(timestamp);
|
||||
expect(moment.utc(formatted).valueOf()).toEqual(timestamp);
|
||||
});
|
||||
|
||||
it("validates time inputs", function () {
|
||||
expect(format.validate("1977-05-25 11:21:22")).toBe(true);
|
||||
expect(format.validate("garbage text")).toBe(false);
|
||||
});
|
||||
|
||||
it("parses valid input", function () {
|
||||
var text = "1977-05-25 11:21:22",
|
||||
parsed = format.parse(text);
|
||||
expect(parsed).toEqual(jasmine.any(Number));
|
||||
expect(parsed).toEqual(moment.utc(text).valueOf());
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
@ -41,7 +41,7 @@ define(
|
||||
scope,
|
||||
element
|
||||
) {
|
||||
this.conductor = openmct.conductor;
|
||||
this.timeAPI = openmct.time;
|
||||
this.scope = scope;
|
||||
this.element = element;
|
||||
|
||||
@ -51,24 +51,25 @@ define(
|
||||
}
|
||||
|
||||
ConductorRepresenter.prototype.boundsListener = function (bounds) {
|
||||
var timeSystem = this.timeAPI.timeSystem();
|
||||
this.scope.$broadcast('telemetry:display:bounds', {
|
||||
start: bounds.start,
|
||||
end: bounds.end,
|
||||
domain: this.conductor.timeSystem().metadata.key
|
||||
}, this.conductor.follow());
|
||||
domain: timeSystem.key
|
||||
}, this.timeAPI.clock() !== undefined);
|
||||
};
|
||||
|
||||
ConductorRepresenter.prototype.timeSystemListener = function (timeSystem) {
|
||||
var bounds = this.conductor.bounds();
|
||||
var bounds = this.timeAPI.bounds();
|
||||
this.scope.$broadcast('telemetry:display:bounds', {
|
||||
start: bounds.start,
|
||||
end: bounds.end,
|
||||
domain: timeSystem.metadata.key
|
||||
}, this.conductor.follow());
|
||||
domain: timeSystem.key
|
||||
}, this.timeAPI.clock() !== undefined);
|
||||
};
|
||||
|
||||
ConductorRepresenter.prototype.followListener = function () {
|
||||
this.boundsListener(this.conductor.bounds());
|
||||
this.boundsListener(this.timeAPI.bounds());
|
||||
};
|
||||
|
||||
// Handle a specific representation of a specific domain object
|
||||
@ -76,16 +77,16 @@ define(
|
||||
if (representation.key === 'browse-object') {
|
||||
this.destroy();
|
||||
|
||||
this.conductor.on("bounds", this.boundsListener);
|
||||
this.conductor.on("timeSystem", this.timeSystemListener);
|
||||
this.conductor.on("follow", this.followListener);
|
||||
this.timeAPI.on("bounds", this.boundsListener);
|
||||
this.timeAPI.on("timeSystem", this.timeSystemListener);
|
||||
this.timeAPI.on("follow", this.followListener);
|
||||
}
|
||||
};
|
||||
|
||||
ConductorRepresenter.prototype.destroy = function destroy() {
|
||||
this.conductor.off("bounds", this.boundsListener);
|
||||
this.conductor.off("timeSystem", this.timeSystemListener);
|
||||
this.conductor.off("follow", this.followListener);
|
||||
this.timeAPI.off("bounds", this.boundsListener);
|
||||
this.timeAPI.off("timeSystem", this.timeSystemListener);
|
||||
this.timeAPI.off("follow", this.followListener);
|
||||
};
|
||||
|
||||
return ConductorRepresenter;
|
||||
|
@ -21,12 +21,12 @@
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
"./src/ui/TimeConductorViewService",
|
||||
"./src/ui/TimeConductorController",
|
||||
"./src/ui/ConductorAxisController",
|
||||
"./src/ui/ConductorTOIController",
|
||||
"./src/ui/ConductorTOIDirective",
|
||||
"./src/ui/TimeOfInterestController",
|
||||
"./src/ui/MctConductorAxis",
|
||||
"./src/ui/ConductorAxisDirective",
|
||||
"./src/ui/NumberFormat",
|
||||
"text!./res/templates/time-conductor.html",
|
||||
"text!./res/templates/mode-selector/mode-selector.html",
|
||||
@ -34,12 +34,12 @@ define([
|
||||
"text!./res/templates/time-of-interest.html",
|
||||
"legacyRegistry"
|
||||
], function (
|
||||
TimeConductorViewService,
|
||||
TimeConductorController,
|
||||
ConductorAxisController,
|
||||
ConductorTOIController,
|
||||
ConductorTOIDirective,
|
||||
TimeOfInterestController,
|
||||
MCTConductorAxis,
|
||||
ConductorAxisDirective,
|
||||
NumberFormat,
|
||||
timeConductorTemplate,
|
||||
modeSelectorTemplate,
|
||||
@ -50,16 +50,6 @@ define([
|
||||
|
||||
legacyRegistry.register("platform/features/conductor/core", {
|
||||
"extensions": {
|
||||
"services": [
|
||||
{
|
||||
"key": "timeConductorViewService",
|
||||
"implementation": TimeConductorViewService,
|
||||
"depends": [
|
||||
"openmct",
|
||||
"timeSystems[]"
|
||||
]
|
||||
}
|
||||
],
|
||||
"controllers": [
|
||||
{
|
||||
"key": "TimeConductorController",
|
||||
@ -67,12 +57,9 @@ define([
|
||||
"depends": [
|
||||
"$scope",
|
||||
"$window",
|
||||
"$location",
|
||||
"openmct",
|
||||
"timeConductorViewService",
|
||||
"formatService",
|
||||
"DEFAULT_TIMECONDUCTOR_MODE",
|
||||
"SHOW_TIMECONDUCTOR"
|
||||
"CONDUCTOR_CONFIG"
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -81,7 +68,6 @@ define([
|
||||
"depends": [
|
||||
"$scope",
|
||||
"openmct",
|
||||
"timeConductorViewService",
|
||||
"formatService"
|
||||
]
|
||||
},
|
||||
@ -97,12 +83,16 @@ define([
|
||||
],
|
||||
"directives": [
|
||||
{
|
||||
"key": "mctConductorAxis",
|
||||
"implementation": MCTConductorAxis,
|
||||
"key": "conductorAxis",
|
||||
"implementation": ConductorAxisDirective,
|
||||
"depends": [
|
||||
"openmct",
|
||||
"formatService"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "conductorToi",
|
||||
"implementation": ConductorTOIDirective
|
||||
}
|
||||
],
|
||||
"stylesheets": [
|
||||
@ -151,13 +141,6 @@ define([
|
||||
"link": "https://github.com/d3/d3/blob/master/LICENSE"
|
||||
}
|
||||
],
|
||||
"constants": [
|
||||
{
|
||||
"key": "DEFAULT_TIMECONDUCTOR_MODE",
|
||||
"value": "realtime",
|
||||
"priority": "fallback"
|
||||
}
|
||||
],
|
||||
"formats": [
|
||||
{
|
||||
"key": "number",
|
||||
|
@ -22,8 +22,8 @@
|
||||
<div class="contents">
|
||||
<div class="pane left menu-items">
|
||||
<ul>
|
||||
<li ng-repeat="(key, metadata) in ngModel.options"
|
||||
ng-click="ngModel.selectedKey=key">
|
||||
<li ng-repeat="metadata in ngModel.options"
|
||||
ng-click="ngModel.selected = metadata">
|
||||
<a ng-mouseover="ngModel.activeMetadata = metadata"
|
||||
ng-mouseleave="ngModel.activeMetadata = undefined"
|
||||
class="menu-item-a {{metadata.cssClass}}">
|
||||
@ -33,8 +33,7 @@
|
||||
</ul>
|
||||
</div>
|
||||
<div class="pane right menu-item-description">
|
||||
<div
|
||||
class="desc-area ui-symbol icon type-icon {{ngModel.activeMetadata.cssClass}}"></div>
|
||||
<div class="desc-area ui-symbol icon type-icon {{ngModel.activeMetadata.cssClass}}"></div>
|
||||
<div class="desc-area title">
|
||||
{{ngModel.activeMetadata.name}}
|
||||
</div>
|
||||
|
@ -22,8 +22,7 @@
|
||||
<span ng-controller="ClickAwayController as modeController">
|
||||
<div class="s-menu-button"
|
||||
ng-click="modeController.toggle()">
|
||||
<span class="title-label">{{ngModel.options[ngModel.selectedKey]
|
||||
.label}}</span>
|
||||
<span class="title-label">{{ngModel.selected.name}}</span>
|
||||
</div>
|
||||
<div class="menu super-menu mini mode-selector-menu"
|
||||
ng-show="modeController.isActive()">
|
||||
|
@ -1,7 +1,7 @@
|
||||
<!-- Parent holder for time conductor. follow-mode | fixed-mode -->
|
||||
<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"
|
||||
ng-class="{'status-panning': tcController.panning}" ng-show="showTimeConductor">
|
||||
class="holder grows flex-elem l-flex-row l-time-conductor {{tcController.isFixed ? 'fixed-mode' : 'realtime-mode'}} {{timeSystemModel.selected.metadata.key}}-time-system"
|
||||
ng-class="{'status-panning': tcController.panning}">
|
||||
<div class="flex-elem holder time-conductor-icon">
|
||||
<div class="hand-little"></div>
|
||||
<div class="hand-big"></div>
|
||||
@ -11,7 +11,7 @@
|
||||
<!-- Holds inputs and ticks -->
|
||||
<div class="l-time-conductor-inputs-and-ticks l-row-elem flex-elem no-margin">
|
||||
<form class="l-time-conductor-inputs-holder"
|
||||
ng-submit="tcController.setBounds(boundsModel)">
|
||||
ng-submit="tcController.setBoundsFromView(boundsModel)">
|
||||
<span class="l-time-range-w start-w">
|
||||
<span class="l-time-conductor-inputs">
|
||||
<span class="l-time-range-input-w start-date">
|
||||
@ -22,22 +22,22 @@
|
||||
validate: tcController.validation.validateStart
|
||||
}"
|
||||
ng-model="boundsModel"
|
||||
ng-blur="tcController.setBounds(boundsModel)"
|
||||
ng-blur="tcController.setBoundsFromView(boundsModel)"
|
||||
field="'start'"
|
||||
class="time-range-input">
|
||||
</mct-control>
|
||||
</span>
|
||||
<span class="l-time-range-input-w time-delta start-delta"
|
||||
ng-class="{'hide':(modeModel.selectedKey === 'fixed')}">
|
||||
ng-class="{'hide':tcController.isFixed}">
|
||||
-
|
||||
<mct-control key="'datetime-field'"
|
||||
structure="{
|
||||
format: timeSystemModel.deltaFormat,
|
||||
validate: tcController.validation.validateStartDelta
|
||||
format: timeSystemModel.durationFormat,
|
||||
validate: tcController.validation.validateStartOffset
|
||||
}"
|
||||
ng-model="boundsModel"
|
||||
ng-blur="tcController.setDeltas(boundsModel)"
|
||||
field="'startDelta'"
|
||||
ng-blur="tcController.setOffsetsFromView(boundsModel)"
|
||||
field="'startOffset'"
|
||||
class="hrs-min-input">
|
||||
</mct-control>
|
||||
</span>
|
||||
@ -54,23 +54,23 @@
|
||||
validate: tcController.validation.validateEnd
|
||||
}"
|
||||
ng-model="boundsModel"
|
||||
ng-blur="tcController.setBounds(boundsModel)"
|
||||
ng-disabled="modeModel.selectedKey !== 'fixed'"
|
||||
ng-blur="tcController.setBoundsFromView(boundsModel)"
|
||||
ng-disabled="!tcController.isFixed"
|
||||
field="'end'"
|
||||
class="time-range-input">
|
||||
</mct-control>
|
||||
</span>
|
||||
<span class="l-time-range-input-w time-delta end-delta"
|
||||
ng-class="{'hide':(modeModel.selectedKey === 'fixed')}">
|
||||
ng-class="{'hide': tcController.isFixed}">
|
||||
+
|
||||
<mct-control key="'datetime-field'"
|
||||
structure="{
|
||||
format: timeSystemModel.deltaFormat,
|
||||
validate: tcController.validation.validateEndDelta
|
||||
format: timeSystemModel.durationFormat,
|
||||
validate: tcController.validation.validateEndOffset
|
||||
}"
|
||||
ng-model="boundsModel"
|
||||
ng-blur="tcController.setDeltas(boundsModel)"
|
||||
field="'endDelta'"
|
||||
ng-blur="tcController.setOffsetsFromView(boundsModel)"
|
||||
field="'endOffset'"
|
||||
class="hrs-min-input">
|
||||
</mct-control>
|
||||
</span>
|
||||
@ -79,44 +79,33 @@
|
||||
|
||||
<input type="submit" class="hidden">
|
||||
</form>
|
||||
<mct-conductor-axis></mct-conductor-axis>
|
||||
<conductor-axis view-service="tcController.conductorViewService"></conductor-axis>
|
||||
</div>
|
||||
|
||||
<!-- Holds data visualization, time of interest -->
|
||||
<div class="l-data-visualization-holder l-row-elem flex-elem"
|
||||
ng-controller="ConductorTOIController as toi">
|
||||
<a class="l-page-button s-icon-button icon-pointer-left"></a>
|
||||
<div class="l-data-visualization" ng-click="toi.setTOIFromPosition($event)">
|
||||
<mct-include key="'time-of-interest'"
|
||||
class="l-toi-holder show-val"
|
||||
ng-class="{ pinned: toi.pinned, 'val-to-left': toi.left > 80 }"
|
||||
ng-style="{'left': toi.left + '%'}"></mct-include>
|
||||
</div>
|
||||
<a class="l-page-button align-right s-icon-button icon-pointer-right"></a>
|
||||
</div>
|
||||
<conductor-toi view-service="tcController.conductorViewService"></conductor-toi>
|
||||
|
||||
<!-- Holds time system and session selectors, and zoom control -->
|
||||
<div class="l-time-conductor-controls l-row-elem l-flex-row flex-elem">
|
||||
<mct-include
|
||||
key="'mode-selector'"
|
||||
ng-model="modeModel"
|
||||
ng-model="tcController.menu"
|
||||
class="holder flex-elem menus-up mode-selector">
|
||||
</mct-include>
|
||||
<mct-control
|
||||
key="'menu-button'"
|
||||
class="holder flex-elem menus-up time-system"
|
||||
structure="{
|
||||
text: timeSystemModel.selected.metadata.name,
|
||||
click: tcController.selectTimeSystemByKey,
|
||||
options: timeSystemModel.options
|
||||
text: timeSystemModel.selected.name,
|
||||
click: tcController.setTimeSystemFromView,
|
||||
options: tcController.timeSystemsForClocks[tcController.menu.selected.key]
|
||||
}">
|
||||
</mct-control>
|
||||
<!-- Zoom control -->
|
||||
<div ng-if="tcController.supportsZoom"
|
||||
<div ng-if="tcController.zoom"
|
||||
class="l-time-conductor-zoom-w grows flex-elem l-flex-row">
|
||||
{{currentZoom}}
|
||||
<span
|
||||
class="time-conductor-zoom-current-range flex-elem flex-fixed holder">{{timeUnits}}</span>
|
||||
<span class="time-conductor-zoom-current-range flex-elem flex-fixed holder">{{timeUnits}}</span>
|
||||
<input class="time-conductor-zoom flex-elem" type="range"
|
||||
ng-model="tcController.currentZoom"
|
||||
ng-mouseUp="tcController.onZoomStop(tcController.currentZoom)"
|
||||
|
@ -1,89 +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(['./TickSource'], function (TickSource) {
|
||||
/**
|
||||
* @implements TickSource
|
||||
* @constructor
|
||||
*/
|
||||
function LocalClock($timeout, period) {
|
||||
TickSource.call(this);
|
||||
|
||||
this.metadata = {
|
||||
key: 'local',
|
||||
mode: 'realtime',
|
||||
cssClass: 'icon-clock',
|
||||
label: 'Real-time',
|
||||
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.'
|
||||
};
|
||||
|
||||
this.period = period;
|
||||
this.$timeout = $timeout;
|
||||
this.timeoutHandle = undefined;
|
||||
}
|
||||
|
||||
LocalClock.prototype = Object.create(TickSource.prototype);
|
||||
|
||||
LocalClock.prototype.start = function () {
|
||||
this.timeoutHandle = this.$timeout(this.tick.bind(this), this.period);
|
||||
};
|
||||
|
||||
LocalClock.prototype.stop = function () {
|
||||
if (this.timeoutHandle) {
|
||||
this.$timeout.cancel(this.timeoutHandle);
|
||||
}
|
||||
};
|
||||
|
||||
LocalClock.prototype.tick = function () {
|
||||
var now = Date.now();
|
||||
this.listeners.forEach(function (listener) {
|
||||
listener(now);
|
||||
});
|
||||
this.timeoutHandle = this.$timeout(this.tick.bind(this), this.period);
|
||||
};
|
||||
|
||||
/**
|
||||
* Register a listener for the local clock. When it ticks, the local
|
||||
* clock will provide the current local system time
|
||||
*
|
||||
* @param listener
|
||||
* @returns {function} a function for deregistering the provided listener
|
||||
*/
|
||||
LocalClock.prototype.listen = function (listener) {
|
||||
var listeners = this.listeners;
|
||||
listeners.push(listener);
|
||||
|
||||
if (listeners.length === 1) {
|
||||
this.start();
|
||||
}
|
||||
|
||||
return function () {
|
||||
listeners.splice(listeners.indexOf(listener));
|
||||
if (listeners.length === 0) {
|
||||
this.stop();
|
||||
}
|
||||
}.bind(this);
|
||||
};
|
||||
|
||||
return LocalClock;
|
||||
});
|
@ -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;
|
||||
});
|
@ -34,17 +34,14 @@ define(
|
||||
* Used by the mct-conductor-axis directive
|
||||
* @constructor
|
||||
*/
|
||||
function ConductorAxisController(openmct, formatService, conductorViewService, scope, element) {
|
||||
function ConductorAxisController(openmct, formatService, scope, element) {
|
||||
// Dependencies
|
||||
this.formatService = formatService;
|
||||
this.conductor = openmct.conductor;
|
||||
this.conductorViewService = conductorViewService;
|
||||
this.timeAPI = openmct.time;
|
||||
|
||||
this.scope = scope;
|
||||
this.initialized = false;
|
||||
|
||||
this.bounds = this.conductor.bounds();
|
||||
this.timeSystem = this.conductor.timeSystem();
|
||||
this.bounds = this.timeAPI.bounds();
|
||||
|
||||
//Bind all class functions to 'this'
|
||||
Object.keys(ConductorAxisController.prototype).filter(function (key) {
|
||||
@ -60,10 +57,10 @@ define(
|
||||
* @private
|
||||
*/
|
||||
ConductorAxisController.prototype.destroy = function () {
|
||||
this.conductor.off('timeSystem', this.changeTimeSystem);
|
||||
this.conductor.off('bounds', this.changeBounds);
|
||||
this.conductorViewService.off("zoom", this.onZoom);
|
||||
this.conductorViewService.off("zoom-stop", this.onZoomStop);
|
||||
this.timeAPI.off('timeSystem', this.changeTimeSystem);
|
||||
this.timeAPI.off('bounds', this.changeBounds);
|
||||
this.viewService.off("zoom", this.onZoom);
|
||||
this.viewService.off("zoom-stop", this.onZoomStop);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -83,19 +80,19 @@ define(
|
||||
this.axisElement = vis.append("g")
|
||||
.attr("transform", "translate(0," + (height - PADDING) + ")");
|
||||
|
||||
if (this.timeSystem !== undefined) {
|
||||
this.changeTimeSystem(this.timeSystem);
|
||||
if (this.timeAPI.timeSystem() !== undefined) {
|
||||
this.changeTimeSystem(this.timeAPI.timeSystem());
|
||||
this.setScale();
|
||||
}
|
||||
|
||||
//Respond to changes in conductor
|
||||
this.conductor.on("timeSystem", this.changeTimeSystem);
|
||||
this.conductor.on("bounds", this.changeBounds);
|
||||
this.timeAPI.on("timeSystem", this.changeTimeSystem);
|
||||
this.timeAPI.on("bounds", this.changeBounds);
|
||||
|
||||
this.scope.$on("$destroy", this.destroy);
|
||||
|
||||
this.conductorViewService.on("zoom", this.onZoom);
|
||||
this.conductorViewService.on("zoom-stop", this.onZoomStop);
|
||||
this.viewService.on("zoom", this.onZoom);
|
||||
this.viewService.on("zoom-stop", this.onZoomStop);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -113,10 +110,10 @@ define(
|
||||
*/
|
||||
ConductorAxisController.prototype.setScale = function () {
|
||||
var width = this.target.offsetWidth;
|
||||
var timeSystem = this.conductor.timeSystem();
|
||||
var timeSystem = this.timeAPI.timeSystem();
|
||||
var bounds = this.bounds;
|
||||
|
||||
if (timeSystem.isUTCBased()) {
|
||||
if (timeSystem.isUTCBased) {
|
||||
this.xScale = this.xScale || d3Scale.scaleUtc();
|
||||
this.xScale.domain([new Date(bounds.start), new Date(bounds.end)]);
|
||||
} else {
|
||||
@ -137,16 +134,14 @@ define(
|
||||
* @param timeSystem
|
||||
*/
|
||||
ConductorAxisController.prototype.changeTimeSystem = function (timeSystem) {
|
||||
this.timeSystem = timeSystem;
|
||||
|
||||
var key = timeSystem.formats()[0];
|
||||
var key = timeSystem.timeFormat;
|
||||
if (key !== undefined) {
|
||||
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
|
||||
// supports UTC out of the box.
|
||||
if (timeSystem.isUTCBased()) {
|
||||
if (timeSystem.isUTCBased) {
|
||||
this.xScale = d3Scale.scaleUtc();
|
||||
} else {
|
||||
this.xScale = d3Scale.scaleLinear();
|
||||
@ -179,8 +174,8 @@ define(
|
||||
*/
|
||||
ConductorAxisController.prototype.panStop = function () {
|
||||
//resync view bounds with time conductor bounds
|
||||
this.conductorViewService.emit("pan-stop");
|
||||
this.conductor.bounds(this.bounds);
|
||||
this.viewService.emit("pan-stop");
|
||||
this.timeAPI.bounds(this.bounds);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -216,9 +211,9 @@ define(
|
||||
* @fires platform.features.conductor.ConductorAxisController~pan
|
||||
*/
|
||||
ConductorAxisController.prototype.pan = function (delta) {
|
||||
if (!this.conductor.follow()) {
|
||||
if (this.timeAPI.clock() === undefined) {
|
||||
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 end = Math.floor((bounds.end - deltaInMs) / 1000) * 1000;
|
||||
this.bounds = {
|
||||
@ -226,7 +221,7 @@ define(
|
||||
end: end
|
||||
};
|
||||
this.setScale();
|
||||
this.conductorViewService.emit("pan", this.bounds);
|
||||
this.viewService.emit("pan", this.bounds);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -72,7 +72,7 @@ define([
|
||||
"bounds",
|
||||
"on",
|
||||
"off",
|
||||
"follow"
|
||||
"clock"
|
||||
]);
|
||||
mockConductor.bounds.andReturn(mockBounds);
|
||||
|
||||
@ -91,20 +91,18 @@ define([
|
||||
|
||||
element = $('<div style="width: 100px;"><div style="width: 100%;"></div></div>');
|
||||
$(document).find('body').append(element);
|
||||
controller = new ConductorAxisController({conductor: mockConductor}, mockFormatService, mockConductorViewService, mockScope, element);
|
||||
ConductorAxisController.prototype.viewService = mockConductorViewService;
|
||||
controller = new ConductorAxisController({time: mockConductor}, mockFormatService, mockScope, element);
|
||||
|
||||
mockTimeSystem = jasmine.createSpyObj("timeSystem", [
|
||||
"formats",
|
||||
"isUTCBased"
|
||||
]);
|
||||
mockTimeSystem = {};
|
||||
mockFormat = jasmine.createSpyObj("format", [
|
||||
"format"
|
||||
]);
|
||||
|
||||
mockTimeSystem.formats.andReturn(["mockFormat"]);
|
||||
mockTimeSystem.timeFormat = "mockFormat";
|
||||
mockFormatService.getFormat.andReturn(mockFormat);
|
||||
mockConductor.timeSystem.andReturn(mockTimeSystem);
|
||||
mockTimeSystem.isUTCBased.andReturn(false);
|
||||
mockTimeSystem.isUTCBased = false;
|
||||
});
|
||||
|
||||
it("listens for changes to time system and bounds", function () {
|
||||
@ -121,7 +119,7 @@ define([
|
||||
|
||||
describe("when the time system changes", function () {
|
||||
it("uses a UTC scale for UTC time systems", function () {
|
||||
mockTimeSystem.isUTCBased.andReturn(true);
|
||||
mockTimeSystem.isUTCBased = true;
|
||||
controller.changeTimeSystem(mockTimeSystem);
|
||||
|
||||
expect(d3Scale.scaleUtc).toHaveBeenCalled();
|
||||
@ -129,14 +127,14 @@ define([
|
||||
});
|
||||
|
||||
it("uses a linear scale for non-UTC time systems", function () {
|
||||
mockTimeSystem.isUTCBased.andReturn(false);
|
||||
mockTimeSystem.isUTCBased = false;
|
||||
controller.changeTimeSystem(mockTimeSystem);
|
||||
expect(d3Scale.scaleLinear).toHaveBeenCalled();
|
||||
expect(d3Scale.scaleUtc).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("sets axis domain to time conductor bounds", function () {
|
||||
mockTimeSystem.isUTCBased.andReturn(false);
|
||||
mockTimeSystem.isUTCBased = false;
|
||||
controller.setScale();
|
||||
expect(controller.xScale.domain()).toEqual([mockBounds.start, mockBounds.end]);
|
||||
});
|
||||
|
@ -21,23 +21,25 @@
|
||||
*****************************************************************************/
|
||||
|
||||
define(['./ConductorAxisController'], function (ConductorAxisController) {
|
||||
function MctConductorAxis() {
|
||||
function ConductorAxisDirective() {
|
||||
/**
|
||||
* The mct-conductor-axis renders a horizontal axis with regular
|
||||
* labelled 'ticks'. It requires 'start' and 'end' integer values to
|
||||
* be specified as attributes.
|
||||
*/
|
||||
|
||||
return {
|
||||
controller: [
|
||||
'openmct',
|
||||
'formatService',
|
||||
'timeConductorViewService',
|
||||
'$scope',
|
||||
'$element',
|
||||
ConductorAxisController
|
||||
],
|
||||
controllerAs: 'axis',
|
||||
scope: {
|
||||
viewService: "="
|
||||
},
|
||||
bindToController: true,
|
||||
|
||||
restrict: 'E',
|
||||
priority: 1000,
|
||||
@ -50,5 +52,5 @@ define(['./ConductorAxisController'], function (ConductorAxisController) {
|
||||
};
|
||||
}
|
||||
|
||||
return MctConductorAxis;
|
||||
return ConductorAxisDirective;
|
||||
});
|
@ -29,9 +29,8 @@ define(
|
||||
* TOI indicator based on the current value of the TOI, and the width of the TOI conductor.
|
||||
* @memberof platform.features.conductor
|
||||
*/
|
||||
function ConductorTOIController($scope, openmct, conductorViewService) {
|
||||
this.conductor = openmct.conductor;
|
||||
this.conductorViewService = conductorViewService;
|
||||
function ConductorTOIController($scope, openmct) {
|
||||
this.timeAPI = openmct.time;
|
||||
|
||||
//Bind all class functions to 'this'
|
||||
Object.keys(ConductorTOIController.prototype).filter(function (key) {
|
||||
@ -40,11 +39,11 @@ define(
|
||||
this[key] = ConductorTOIController.prototype[key].bind(this);
|
||||
}.bind(this));
|
||||
|
||||
this.conductor.on('timeOfInterest', this.changeTimeOfInterest);
|
||||
this.conductorViewService.on('zoom', this.setOffsetFromZoom);
|
||||
this.conductorViewService.on('pan', this.setOffsetFromBounds);
|
||||
this.timeAPI.on('timeOfInterest', this.changeTimeOfInterest);
|
||||
this.viewService.on('zoom', this.setOffsetFromZoom);
|
||||
this.viewService.on('pan', this.setOffsetFromBounds);
|
||||
|
||||
var timeOfInterest = this.conductor.timeOfInterest();
|
||||
var timeOfInterest = this.timeAPI.timeOfInterest();
|
||||
if (timeOfInterest) {
|
||||
this.changeTimeOfInterest(timeOfInterest);
|
||||
}
|
||||
@ -56,9 +55,9 @@ define(
|
||||
* @private
|
||||
*/
|
||||
ConductorTOIController.prototype.destroy = function () {
|
||||
this.conductor.off('timeOfInterest', this.changeTimeOfInterest);
|
||||
this.conductorViewService.off('zoom', this.setOffsetFromZoom);
|
||||
this.conductorViewService.off('pan', this.setOffsetFromBounds);
|
||||
this.timeAPI.off('timeOfInterest', this.changeTimeOfInterest);
|
||||
this.viewService.off('zoom', this.setOffsetFromZoom);
|
||||
this.viewService.off('pan', this.setOffsetFromBounds);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -70,7 +69,7 @@ define(
|
||||
* @param {TimeConductorBounds} bounds
|
||||
*/
|
||||
ConductorTOIController.prototype.setOffsetFromBounds = function (bounds) {
|
||||
var toi = this.conductor.timeOfInterest();
|
||||
var toi = this.timeAPI.timeOfInterest();
|
||||
if (toi !== undefined) {
|
||||
var offset = toi - bounds.start;
|
||||
var duration = bounds.end - bounds.start;
|
||||
@ -94,7 +93,7 @@ define(
|
||||
* @private
|
||||
*/
|
||||
ConductorTOIController.prototype.changeTimeOfInterest = function () {
|
||||
var bounds = this.conductor.bounds();
|
||||
var bounds = this.timeAPI.bounds();
|
||||
if (bounds) {
|
||||
this.setOffsetFromBounds(bounds);
|
||||
}
|
||||
@ -112,10 +111,10 @@ define(
|
||||
var width = element.width();
|
||||
var relativeX = e.pageX - element.offset().left;
|
||||
var percX = relativeX / width;
|
||||
var bounds = this.conductor.bounds();
|
||||
var bounds = this.timeAPI.bounds();
|
||||
var timeRange = bounds.end - bounds.start;
|
||||
|
||||
this.conductor.timeOfInterest(timeRange * percX + bounds.start);
|
||||
this.timeAPI.timeOfInterest(timeRange * percX + bounds.start);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -47,7 +47,7 @@ define([
|
||||
"on",
|
||||
"off"
|
||||
]);
|
||||
mockAPI = {conductor: mockConductor};
|
||||
mockAPI = {time: mockConductor};
|
||||
|
||||
mockConductorViewService = jasmine.createSpyObj("conductorViewService", [
|
||||
"on",
|
||||
@ -57,8 +57,8 @@ define([
|
||||
mockScope = jasmine.createSpyObj("openMCT", [
|
||||
"$on"
|
||||
]);
|
||||
|
||||
conductorTOIController = new ConductorTOIController(mockScope, mockAPI, mockConductorViewService);
|
||||
ConductorTOIController.prototype.viewService = mockConductorViewService;
|
||||
conductorTOIController = new ConductorTOIController(mockScope, mockAPI);
|
||||
});
|
||||
|
||||
it("listens to changes in the time of interest on the conductor", function () {
|
||||
|
@ -0,0 +1,63 @@
|
||||
/*****************************************************************************
|
||||
* 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(['./ConductorTOIController'], function (ConductorTOIController) {
|
||||
/**
|
||||
* A directive that encapsulates the TOI specific behavior of the Time Conductor UI.
|
||||
* @constructor
|
||||
*/
|
||||
function ConductorTOIDirective() {
|
||||
/**
|
||||
* The mct-conductor-axis renders a horizontal axis with regular
|
||||
* labelled 'ticks'. It requires 'start' and 'end' integer values to
|
||||
* be specified as attributes.
|
||||
*/
|
||||
return {
|
||||
controller: [
|
||||
'$scope',
|
||||
'openmct',
|
||||
ConductorTOIController
|
||||
],
|
||||
controllerAs: 'toi',
|
||||
scope: {
|
||||
viewService: "="
|
||||
},
|
||||
bindToController: true,
|
||||
|
||||
restrict: 'E',
|
||||
priority: 1000,
|
||||
|
||||
template:
|
||||
'<div class="l-data-visualization-holder l-row-elem flex-elem">' +
|
||||
' <a class="l-page-button s-icon-button icon-pointer-left"></a>' +
|
||||
' <div class="l-data-visualization" ng-click="toi.setTOIFromPosition($event)">' +
|
||||
' <mct-include key="\'time-of-interest\'" class="l-toi-holder show-val" ' +
|
||||
' ng-class="{ pinned: toi.pinned, \'val-to-left\': toi.left > 80 }" ' +
|
||||
' ng-style="{\'left\': toi.left + \'%\'}"></mct-include>' +
|
||||
' </div>' +
|
||||
' <a class="l-page-button align-right s-icon-button icon-pointer-right"></a>' +
|
||||
'</div>'
|
||||
};
|
||||
}
|
||||
|
||||
return ConductorTOIDirective;
|
||||
});
|
@ -31,6 +31,7 @@ define([], function () {
|
||||
* @memberof platform/commonUI/formats
|
||||
*/
|
||||
function NumberFormat() {
|
||||
this.key = 'number';
|
||||
}
|
||||
|
||||
NumberFormat.prototype.format = function (value) {
|
||||
|
@ -19,221 +19,255 @@
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
/* global console*/
|
||||
|
||||
define(
|
||||
[
|
||||
'./TimeConductorValidation'
|
||||
'moment',
|
||||
'./TimeConductorValidation',
|
||||
'./TimeConductorViewService'
|
||||
],
|
||||
function (TimeConductorValidation) {
|
||||
var SEARCH = {
|
||||
MODE: 'tc.mode',
|
||||
TIME_SYSTEM: 'tc.timeSystem',
|
||||
START_BOUND: 'tc.startBound',
|
||||
END_BOUND: 'tc.endBound',
|
||||
START_DELTA: 'tc.startDelta',
|
||||
END_DELTA: 'tc.endDelta'
|
||||
};
|
||||
function (
|
||||
moment,
|
||||
TimeConductorValidation,
|
||||
TimeConductorViewService
|
||||
) {
|
||||
|
||||
var timeUnitsMegastructure = [
|
||||
["Decades", function (r) {
|
||||
return r.years() > 15;
|
||||
}],
|
||||
["Years", function (r) {
|
||||
return r.years() > 1;
|
||||
}],
|
||||
["Months", function (r) {
|
||||
return r.years() === 1 || r.months() > 1;
|
||||
}],
|
||||
["Days", function (r) {
|
||||
return r.months() === 1 || r.days() > 1;
|
||||
}],
|
||||
["Hours", function (r) {
|
||||
return r.days() === 1 || r.hours() > 1;
|
||||
}],
|
||||
["Minutes", function (r) {
|
||||
return r.hours() === 1 || r.minutes() > 1;
|
||||
}],
|
||||
["Seconds", function (r) {
|
||||
return r.minutes() === 1 || r.seconds() > 1;
|
||||
}],
|
||||
["Milliseconds", function (r) {
|
||||
return true;
|
||||
}]
|
||||
];
|
||||
|
||||
/**
|
||||
* Controller for the Time Conductor UI element. The Time Conductor includes form fields for specifying time
|
||||
* bounds and relative time deltas for queries, as well as controls for selection mode, time systems, and zooming.
|
||||
* Controller for the Time Conductor UI element. The Time Conductor
|
||||
* includes form fields for specifying time bounds and relative time
|
||||
* offsets for queries, as well as controls for selection mode,
|
||||
* time systems, and zooming.
|
||||
* @memberof platform.features.conductor
|
||||
* @constructor
|
||||
*/
|
||||
function TimeConductorController(
|
||||
$scope,
|
||||
$window,
|
||||
$location,
|
||||
openmct,
|
||||
conductorViewService,
|
||||
formatService,
|
||||
DEFAULT_MODE,
|
||||
SHOW_TIMECONDUCTOR
|
||||
config
|
||||
) {
|
||||
|
||||
var self = this;
|
||||
|
||||
//Bind all class functions to 'this'
|
||||
Object.keys(TimeConductorController.prototype).filter(function (key) {
|
||||
return typeof TimeConductorController.prototype[key] === 'function';
|
||||
}).forEach(function (key) {
|
||||
self[key] = self[key].bind(self);
|
||||
});
|
||||
//Bind functions that are used as callbacks to 'this'.
|
||||
[
|
||||
"selectMenuOption",
|
||||
"onPan",
|
||||
"onPanStop",
|
||||
"setViewFromBounds",
|
||||
"setViewFromClock",
|
||||
"setViewFromOffsets",
|
||||
"setViewFromTimeSystem",
|
||||
"setTimeSystemFromView",
|
||||
"destroy"
|
||||
].forEach(function (name) {
|
||||
this[name] = this[name].bind(this);
|
||||
}.bind(this));
|
||||
|
||||
this.$scope = $scope;
|
||||
this.$window = $window;
|
||||
this.$location = $location;
|
||||
this.conductorViewService = conductorViewService;
|
||||
this.conductor = openmct.conductor;
|
||||
this.modes = conductorViewService.availableModes();
|
||||
this.validation = new TimeConductorValidation(this.conductor);
|
||||
this.timeAPI = openmct.time;
|
||||
this.conductorViewService = new TimeConductorViewService(openmct);
|
||||
this.validation = new TimeConductorValidation(this.timeAPI);
|
||||
this.formatService = formatService;
|
||||
|
||||
//Check if the default mode defined is actually available
|
||||
if (this.modes[DEFAULT_MODE] === undefined) {
|
||||
DEFAULT_MODE = 'fixed';
|
||||
}
|
||||
this.DEFAULT_MODE = DEFAULT_MODE;
|
||||
|
||||
// Construct the provided time system definitions
|
||||
this.timeSystems = conductorViewService.systems;
|
||||
|
||||
this.initializeScope();
|
||||
var searchParams = JSON.parse(JSON.stringify(this.$location.search()));
|
||||
//Set bounds, time systems, deltas, on conductor from URL
|
||||
this.setStateFromSearchParams(searchParams);
|
||||
|
||||
//Set the initial state of the UI from the conductor state
|
||||
var timeSystem = this.conductor.timeSystem();
|
||||
if (timeSystem) {
|
||||
this.changeTimeSystem(this.conductor.timeSystem());
|
||||
}
|
||||
|
||||
var deltas = this.conductorViewService.deltas();
|
||||
if (deltas) {
|
||||
this.setFormFromDeltas(deltas);
|
||||
}
|
||||
|
||||
var bounds = this.conductor.bounds();
|
||||
if (bounds && bounds.start !== undefined && bounds.end !== undefined) {
|
||||
this.changeBounds(bounds);
|
||||
}
|
||||
|
||||
//Listen for changes to URL and update state if necessary
|
||||
this.$scope.$on('$routeUpdate', function () {
|
||||
this.setStateFromSearchParams(this.$location.search());
|
||||
}.bind(this));
|
||||
|
||||
//Respond to any subsequent conductor changes
|
||||
this.conductor.on('bounds', this.changeBounds);
|
||||
this.conductor.on('timeSystem', this.changeTimeSystem);
|
||||
|
||||
this.$scope.showTimeConductor = SHOW_TIMECONDUCTOR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used as a url search param setter in place of $location.search(...)
|
||||
*
|
||||
* Invokes $location.search(...) but prevents an Angular route
|
||||
* change from occurring as a consequence which will cause
|
||||
* controllers to reload and strangeness to ensue.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
TimeConductorController.prototype.setParam = function (name, value) {
|
||||
this.$location.search(name, value);
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
TimeConductorController.prototype.initializeScope = function () {
|
||||
//Set time Conductor bounds in the form
|
||||
this.$scope.boundsModel = this.conductor.bounds();
|
||||
|
||||
//If conductor has a time system selected already, populate the
|
||||
//form from it
|
||||
this.config = config;
|
||||
this.clocksForTimeSystem = {};
|
||||
this.timeSystemsForClocks = {};
|
||||
this.$scope.timeSystemModel = {};
|
||||
this.$scope.boundsModel = {};
|
||||
|
||||
//Represents the various modes, and the currently selected mode
|
||||
//in the view
|
||||
this.$scope.modeModel = {
|
||||
options: this.conductorViewService.availableModes()
|
||||
this.timeSystems = this.timeAPI.getAllTimeSystems().reduce(function (map, timeSystem) {
|
||||
map[timeSystem.key] = timeSystem;
|
||||
return map;
|
||||
}, {});
|
||||
|
||||
this.isFixed = this.timeAPI.clock() === undefined;
|
||||
|
||||
var options = this.optionsFromConfig(config);
|
||||
this.menu = {
|
||||
selected: undefined,
|
||||
options: options
|
||||
};
|
||||
|
||||
// Watch scope for selection of mode or time system by user
|
||||
this.$scope.$watch('modeModel.selectedKey', this.setMode);
|
||||
//Set the initial state of the UI from the conductor state
|
||||
var timeSystem = this.timeAPI.timeSystem();
|
||||
if (timeSystem) {
|
||||
this.setViewFromTimeSystem(timeSystem);
|
||||
}
|
||||
|
||||
this.setViewFromClock(this.timeAPI.clock());
|
||||
|
||||
var offsets = this.timeAPI.clockOffsets();
|
||||
if (offsets) {
|
||||
this.setViewFromOffsets(offsets);
|
||||
}
|
||||
|
||||
var bounds = this.timeAPI.bounds();
|
||||
if (bounds && bounds.start !== undefined && bounds.end !== undefined) {
|
||||
this.setViewFromBounds(bounds);
|
||||
}
|
||||
|
||||
this.$scope.$watch("tcController.menu.selected", this.selectMenuOption);
|
||||
|
||||
this.conductorViewService.on('pan', this.onPan);
|
||||
this.conductorViewService.on('pan-stop', this.onPanStop);
|
||||
|
||||
//Respond to any subsequent conductor changes
|
||||
this.timeAPI.on('bounds', this.setViewFromBounds);
|
||||
this.timeAPI.on('timeSystem', this.setViewFromTimeSystem);
|
||||
this.timeAPI.on('clock', this.setViewFromClock);
|
||||
this.timeAPI.on('clockOffsets', this.setViewFromOffsets);
|
||||
this.$scope.$on('$destroy', this.destroy);
|
||||
};
|
||||
}
|
||||
|
||||
TimeConductorController.prototype.setStateFromSearchParams = function (searchParams) {
|
||||
//Set mode from url if changed
|
||||
if (searchParams[SEARCH.MODE] === undefined ||
|
||||
searchParams[SEARCH.MODE] !== this.$scope.modeModel.selectedKey) {
|
||||
this.setMode(searchParams[SEARCH.MODE] || this.DEFAULT_MODE);
|
||||
}
|
||||
|
||||
if (searchParams[SEARCH.TIME_SYSTEM] &&
|
||||
searchParams[SEARCH.TIME_SYSTEM] !== this.conductor.timeSystem().metadata.key) {
|
||||
//Will select the specified time system on the conductor
|
||||
this.selectTimeSystemByKey(searchParams[SEARCH.TIME_SYSTEM]);
|
||||
}
|
||||
|
||||
var validDeltas = searchParams[SEARCH.MODE] !== 'fixed' &&
|
||||
searchParams[SEARCH.START_DELTA] &&
|
||||
searchParams[SEARCH.END_DELTA] &&
|
||||
!isNaN(searchParams[SEARCH.START_DELTA]) &&
|
||||
!isNaN(searchParams[SEARCH.END_DELTA]);
|
||||
|
||||
if (validDeltas) {
|
||||
//Sets deltas from some form model
|
||||
this.setDeltas({
|
||||
startDelta: parseInt(searchParams[SEARCH.START_DELTA]),
|
||||
endDelta: parseInt(searchParams[SEARCH.END_DELTA])
|
||||
});
|
||||
}
|
||||
|
||||
var validBounds = searchParams[SEARCH.MODE] === 'fixed' &&
|
||||
searchParams[SEARCH.START_BOUND] &&
|
||||
searchParams[SEARCH.END_BOUND] &&
|
||||
!isNaN(searchParams[SEARCH.START_BOUND]) &&
|
||||
!isNaN(searchParams[SEARCH.END_BOUND]);
|
||||
|
||||
if (validBounds) {
|
||||
this.conductor.bounds({
|
||||
start: parseInt(searchParams[SEARCH.START_BOUND]),
|
||||
end: parseInt(searchParams[SEARCH.END_BOUND])
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Given a key for a clock, retrieve the clock object.
|
||||
* @private
|
||||
* @param key
|
||||
* @returns {Clock}
|
||||
*/
|
||||
TimeConductorController.prototype.getClock = function (key) {
|
||||
return this.timeAPI.getAllClocks().filter(function (clock) {
|
||||
return clock.key === key;
|
||||
})[0];
|
||||
};
|
||||
|
||||
/**
|
||||
* Activate the selected menu option. Menu options correspond to clocks.
|
||||
* A distinction is made to avoid confusion between the menu options and
|
||||
* their metadata, and actual {@link Clock} objects.
|
||||
*
|
||||
* @private
|
||||
* @param newOption
|
||||
* @param oldOption
|
||||
*/
|
||||
TimeConductorController.prototype.destroy = function () {
|
||||
this.conductor.off('bounds', this.changeBounds);
|
||||
this.conductor.off('timeSystem', this.changeTimeSystem);
|
||||
TimeConductorController.prototype.selectMenuOption = function (newOption, oldOption) {
|
||||
if (newOption !== oldOption) {
|
||||
var config = this.getConfig(this.timeAPI.timeSystem(), newOption.clock);
|
||||
|
||||
this.conductorViewService.off('pan', this.onPan);
|
||||
this.conductorViewService.off('pan-stop', this.onPanStop);
|
||||
};
|
||||
/*
|
||||
* If there is no configuration defined for the selected clock
|
||||
* and time system default to the first time system that
|
||||
* configuration is available for.
|
||||
*/
|
||||
if (config === undefined) {
|
||||
var timeSystem = this.timeSystemsForClocks[newOption.key][0];
|
||||
this.$scope.timeSystemModel.selected = timeSystem;
|
||||
this.setTimeSystemFromView(timeSystem.key);
|
||||
config = this.getConfig(timeSystem, newOption.clock);
|
||||
}
|
||||
|
||||
/**
|
||||
* When the conductor bounds change, set the bounds in the form.
|
||||
* @private
|
||||
* @param {TimeConductorBounds} bounds
|
||||
*/
|
||||
TimeConductorController.prototype.changeBounds = function (bounds) {
|
||||
//If a zoom or pan is currently in progress, do not override form values.
|
||||
if (!this.zooming && !this.panning) {
|
||||
this.setFormFromBounds(bounds);
|
||||
if (this.conductorViewService.mode() === 'fixed') {
|
||||
//Set bounds in URL on change
|
||||
this.setParam(SEARCH.START_BOUND, bounds.start);
|
||||
this.setParam(SEARCH.END_BOUND, bounds.end);
|
||||
if (newOption.key === 'fixed') {
|
||||
this.timeAPI.stopClock();
|
||||
} else {
|
||||
this.timeAPI.clock(newOption.key, config.clockOffsets);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when the bounds change in the time conductor. Synchronizes
|
||||
* the bounds values in the time conductor with those in the form
|
||||
* @param {TimeConductorBounds}
|
||||
* From the provided configuration, build the available menu options.
|
||||
* @private
|
||||
* @param config
|
||||
* @returns {*[]}
|
||||
*/
|
||||
TimeConductorController.prototype.setFormFromBounds = function (bounds) {
|
||||
TimeConductorController.prototype.optionsFromConfig = function (config) {
|
||||
/*
|
||||
* "Fixed Mode" is always the first available option.
|
||||
*/
|
||||
var options = [{
|
||||
key: 'fixed',
|
||||
name: 'Fixed Timespan Mode',
|
||||
description: 'Query and explore data that falls between two fixed datetimes',
|
||||
cssClass: 'icon-calendar'
|
||||
}];
|
||||
var clocks = {};
|
||||
var clocksForTimeSystem = this.clocksForTimeSystem;
|
||||
var timeSystemsForClocks = this.timeSystemsForClocks;
|
||||
|
||||
(config.menuOptions || []).forEach(function (menuOption) {
|
||||
var clock = this.getClock(menuOption.clock);
|
||||
var clockKey = menuOption.clock || 'fixed';
|
||||
|
||||
var timeSystem = this.timeSystems[menuOption.timeSystem];
|
||||
if (timeSystem !== undefined) {
|
||||
if (clock !== undefined) {
|
||||
// Use an associative array to built a set of unique
|
||||
// clocks
|
||||
clocks[clock.key] = clock;
|
||||
clocksForTimeSystem[timeSystem.key] = clocksForTimeSystem[timeSystem.key] || [];
|
||||
clocksForTimeSystem[timeSystem.key].push(clock);
|
||||
}
|
||||
timeSystemsForClocks[clockKey] = timeSystemsForClocks[clockKey] || [];
|
||||
timeSystemsForClocks[clockKey].push(timeSystem);
|
||||
} else if (menuOption.clock !== undefined) {
|
||||
console.error('Unknown clock "' + clockKey + '", has it been registered?');
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
/*
|
||||
* Populate the clocks menu with metadata from the available clocks
|
||||
*/
|
||||
Object.values(clocks).forEach(function (clock) {
|
||||
options.push({
|
||||
key: clock.key,
|
||||
name: clock.name,
|
||||
description: "Monitor streaming data in real-time. The Time " +
|
||||
"Conductor and displays will automatically advance themselves based on this clock. " + clock.description,
|
||||
cssClass: clock.cssClass || 'icon-clock',
|
||||
clock: clock
|
||||
});
|
||||
}.bind(this));
|
||||
|
||||
return options;
|
||||
};
|
||||
|
||||
/**
|
||||
* When bounds change, set UI values from the new bounds.
|
||||
* @param {TimeBounds} bounds the bounds
|
||||
*/
|
||||
TimeConductorController.prototype.setViewFromBounds = function (bounds) {
|
||||
if (!this.zooming && !this.panning) {
|
||||
this.$scope.boundsModel.start = bounds.start;
|
||||
this.$scope.boundsModel.end = bounds.end;
|
||||
|
||||
if (this.supportsZoom) {
|
||||
this.currentZoom = this.toSliderValue(bounds.end - bounds.start);
|
||||
if (this.supportsZoom()) {
|
||||
var config = this.getConfig(this.timeAPI.timeSystem(), this.timeAPI.clock());
|
||||
this.currentZoom = this.toSliderValue(bounds.end - bounds.start, config.zoomOutLimit, config.zoomInLimit);
|
||||
this.toTimeUnits(bounds.end - bounds.start);
|
||||
}
|
||||
|
||||
/*
|
||||
Ensure that a digest occurs, capped at the browser's refresh
|
||||
rate.
|
||||
*/
|
||||
if (!this.pendingUpdate) {
|
||||
this.pendingUpdate = true;
|
||||
this.$window.requestAnimationFrame(function () {
|
||||
@ -245,110 +279,131 @@ define(
|
||||
};
|
||||
|
||||
/**
|
||||
* On mode change, populate form based on time systems available
|
||||
* from the selected mode.
|
||||
* @param mode
|
||||
* Retrieve any configuration defined for the provided time system and
|
||||
* clock
|
||||
* @private
|
||||
* @param timeSystem
|
||||
* @param clock
|
||||
* @returns {object} The Time Conductor configuration corresponding to
|
||||
* the provided combination of time system and clock
|
||||
*/
|
||||
TimeConductorController.prototype.setFormFromMode = function (mode) {
|
||||
this.$scope.modeModel.selectedKey = mode;
|
||||
//Synchronize scope with time system on mode
|
||||
this.$scope.timeSystemModel.options =
|
||||
this.conductorViewService.availableTimeSystems()
|
||||
.map(function (t) {
|
||||
return t.metadata;
|
||||
TimeConductorController.prototype.getConfig = function (timeSystem, clock) {
|
||||
var clockKey = clock && clock.key;
|
||||
var timeSystemKey = timeSystem && timeSystem.key;
|
||||
|
||||
var option = this.config.menuOptions.filter(function (menuOption) {
|
||||
return menuOption.timeSystem === timeSystemKey && menuOption.clock === clockKey;
|
||||
})[0];
|
||||
return option;
|
||||
};
|
||||
|
||||
/**
|
||||
* When the clock offsets change, update the values in the UI
|
||||
* @param {ClockOffsets} offsets
|
||||
* @private
|
||||
*/
|
||||
TimeConductorController.prototype.setViewFromOffsets = function (offsets) {
|
||||
this.$scope.boundsModel.startOffset = Math.abs(offsets.start);
|
||||
this.$scope.boundsModel.endOffset = offsets.end;
|
||||
};
|
||||
|
||||
/**
|
||||
* When form values for bounds change, update the bounds in the Time API
|
||||
* to trigger an application-wide bounds change.
|
||||
* @param {object} boundsModel
|
||||
*/
|
||||
TimeConductorController.prototype.setBoundsFromView = function (boundsModel) {
|
||||
var bounds = this.timeAPI.bounds();
|
||||
if (boundsModel.start !== bounds.start || boundsModel.end !== bounds.end) {
|
||||
this.timeAPI.bounds({
|
||||
start: boundsModel.start,
|
||||
end: boundsModel.end
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* When the deltas change, update the values in the UI
|
||||
* @private
|
||||
*/
|
||||
TimeConductorController.prototype.setFormFromDeltas = function (deltas) {
|
||||
this.$scope.boundsModel.startDelta = deltas.start;
|
||||
this.$scope.boundsModel.endDelta = deltas.end;
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize the form when time system changes.
|
||||
* @param {TimeSystem} timeSystem
|
||||
*/
|
||||
TimeConductorController.prototype.setFormFromTimeSystem = function (timeSystem) {
|
||||
var timeSystemModel = this.$scope.timeSystemModel;
|
||||
timeSystemModel.selected = timeSystem;
|
||||
timeSystemModel.format = timeSystem.formats()[0];
|
||||
timeSystemModel.deltaFormat = timeSystem.deltaFormat();
|
||||
|
||||
if (this.supportsZoom) {
|
||||
timeSystemModel.minZoom = timeSystem.defaults().zoom.min;
|
||||
timeSystemModel.maxZoom = timeSystem.defaults().zoom.max;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when form values are changed.
|
||||
* @param formModel
|
||||
* When form values for bounds change, update the bounds in the Time API
|
||||
* to trigger an application-wide bounds change.
|
||||
* @param {object} formModel
|
||||
*/
|
||||
TimeConductorController.prototype.setBounds = function (boundsModel) {
|
||||
this.conductor.bounds({
|
||||
start: boundsModel.start,
|
||||
end: boundsModel.end
|
||||
});
|
||||
};
|
||||
TimeConductorController.prototype.setOffsetsFromView = function (boundsModel) {
|
||||
if (this.validation.validateStartOffset(boundsModel.startOffset) && this.validation.validateEndOffset(boundsModel.endOffset)) {
|
||||
var offsets = {
|
||||
start: 0 - boundsModel.startOffset,
|
||||
end: boundsModel.endOffset
|
||||
};
|
||||
var existingOffsets = this.timeAPI.clockOffsets();
|
||||
|
||||
/**
|
||||
* Called when the delta values in the form change. Validates and
|
||||
* sets the new deltas on the Mode.
|
||||
* @param boundsModel
|
||||
* @see TimeConductorMode
|
||||
*/
|
||||
TimeConductorController.prototype.setDeltas = function (boundsFormModel) {
|
||||
var deltas = {
|
||||
start: boundsFormModel.startDelta,
|
||||
end: boundsFormModel.endDelta
|
||||
};
|
||||
if (this.validation.validateStartDelta(deltas.start) && this.validation.validateEndDelta(deltas.end)) {
|
||||
//Sychronize deltas between form and mode
|
||||
this.conductorViewService.deltas(deltas);
|
||||
|
||||
//Set Deltas in URL on change
|
||||
this.setParam(SEARCH.START_DELTA, deltas.start);
|
||||
this.setParam(SEARCH.END_DELTA, deltas.end);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Change the selected Time Conductor mode. This will call destroy
|
||||
* and initialization functions on the relevant modes, setting
|
||||
* default values for bound and deltas in the form.
|
||||
*
|
||||
* @private
|
||||
* @param newModeKey
|
||||
* @param oldModeKey
|
||||
*/
|
||||
TimeConductorController.prototype.setMode = function (newModeKey, oldModeKey) {
|
||||
//Set mode in URL on change
|
||||
this.setParam(SEARCH.MODE, newModeKey);
|
||||
|
||||
if (newModeKey !== oldModeKey) {
|
||||
this.conductorViewService.mode(newModeKey);
|
||||
this.setFormFromMode(newModeKey);
|
||||
|
||||
if (newModeKey === "fixed") {
|
||||
this.setParam(SEARCH.START_DELTA, undefined);
|
||||
this.setParam(SEARCH.END_DELTA, undefined);
|
||||
} else {
|
||||
this.setParam(SEARCH.START_BOUND, undefined);
|
||||
this.setParam(SEARCH.END_BOUND, undefined);
|
||||
|
||||
var deltas = this.conductorViewService.deltas();
|
||||
if (deltas) {
|
||||
this.setParam(SEARCH.START_DELTA, deltas.start);
|
||||
this.setParam(SEARCH.END_DELTA, deltas.end);
|
||||
}
|
||||
if (offsets.start !== existingOffsets.start || offsets.end !== existingOffsets.end) {
|
||||
//Sychronize offsets between form and time API
|
||||
this.timeAPI.clockOffsets(offsets);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @returns {boolean}
|
||||
*/
|
||||
TimeConductorController.prototype.supportsZoom = function () {
|
||||
var config = this.getConfig(this.timeAPI.timeSystem(), this.timeAPI.clock());
|
||||
return config && (config.zoomInLimit !== undefined && config.zoomOutLimit !== undefined);
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the UI state to reflect a change in clock. Provided conductor
|
||||
* configuration will be checked for compatibility between the new clock
|
||||
* and the currently selected time system. If configuration is not available,
|
||||
* an attempt will be made to default to a time system that is compatible
|
||||
* with the new clock
|
||||
*
|
||||
* @private
|
||||
* @param {Clock} clock
|
||||
*/
|
||||
TimeConductorController.prototype.setViewFromClock = function (clock) {
|
||||
var newClockKey = clock && clock.key;
|
||||
var timeSystems = this.timeSystemsForClocks[newClockKey || 'fixed'];
|
||||
var menuOption = this.menu.options.filter(function (option) {
|
||||
return option.key === (newClockKey || 'fixed');
|
||||
})[0];
|
||||
|
||||
this.menu.selected = menuOption;
|
||||
|
||||
//Try to find currently selected time system in time systems for clock
|
||||
var selectedTimeSystem = timeSystems.filter(function (timeSystem) {
|
||||
return timeSystem.key === this.$scope.timeSystemModel.selected.key;
|
||||
}.bind(this))[0];
|
||||
|
||||
var config = this.getConfig(selectedTimeSystem, clock);
|
||||
|
||||
if (selectedTimeSystem === undefined) {
|
||||
selectedTimeSystem = timeSystems[0];
|
||||
config = this.getConfig(selectedTimeSystem, clock);
|
||||
|
||||
if (clock === undefined) {
|
||||
this.timeAPI.timeSystem(selectedTimeSystem, config.bounds);
|
||||
} else {
|
||||
//When time system changes, some start bounds need to be provided
|
||||
this.timeAPI.timeSystem(selectedTimeSystem, {
|
||||
start: clock.currentValue() + config.clockOffsets.start,
|
||||
end: clock.currentValue() + config.clockOffsets.end
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.isFixed = clock === undefined;
|
||||
|
||||
if (clock !== undefined) {
|
||||
this.setViewFromOffsets(this.timeAPI.clockOffsets());
|
||||
} else {
|
||||
this.setViewFromBounds(this.timeAPI.bounds());
|
||||
}
|
||||
|
||||
this.zoom = this.supportsZoom();
|
||||
this.$scope.timeSystemModel.options = timeSystems;
|
||||
};
|
||||
|
||||
/**
|
||||
* Respond to time system selection from UI
|
||||
*
|
||||
@ -358,13 +413,38 @@ define(
|
||||
* @param key
|
||||
* @see TimeConductorController#setTimeSystem
|
||||
*/
|
||||
TimeConductorController.prototype.selectTimeSystemByKey = function (key) {
|
||||
var selected = this.timeSystems.filter(function (timeSystem) {
|
||||
return timeSystem.metadata.key === key;
|
||||
})[0];
|
||||
if (selected) {
|
||||
this.supportsZoom = !!(selected.defaults() && selected.defaults().zoom);
|
||||
this.conductor.timeSystem(selected, selected.defaults().bounds);
|
||||
TimeConductorController.prototype.setTimeSystemFromView = function (key) {
|
||||
var clock = this.menu.selected.clock;
|
||||
var timeSystem = this.timeSystems[key];
|
||||
var config = this.getConfig(timeSystem, clock);
|
||||
var bounds;
|
||||
|
||||
this.$scope.timeSystemModel.selected = timeSystem;
|
||||
|
||||
/**
|
||||
* Time systems require default bounds to be specified when they
|
||||
* are set
|
||||
*/
|
||||
if (clock === undefined) {
|
||||
bounds = config.bounds;
|
||||
this.timeAPI.timeSystem(timeSystem, bounds);
|
||||
} else {
|
||||
bounds = {
|
||||
start: clock.currentValue() + config.clockOffsets.start,
|
||||
end: clock.currentValue() + config.clockOffsets.end
|
||||
};
|
||||
//Has time system change resulted in offsets change (based on config)?
|
||||
this.timeAPI.timeSystem(timeSystem, bounds);
|
||||
var configOffsets = config.clockOffsets;
|
||||
var apiOffsets = this.timeAPI.clockOffsets();
|
||||
|
||||
//Checking if a clock is actually set on the Time API before
|
||||
// trying to set offsets.
|
||||
if (this.timeAPI.clock() !== undefined &&
|
||||
(configOffsets.start !== apiOffsets.start ||
|
||||
configOffsets.end !== apiOffsets.end)) {
|
||||
this.timeAPI.clockOffsets(configOffsets);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -372,41 +452,39 @@ define(
|
||||
* Handles time system change from time conductor
|
||||
*
|
||||
* Sets the selected time system. Will populate form with the default
|
||||
* bounds and deltas defined in the selected time system.
|
||||
* bounds and offsets defined in the selected time system.
|
||||
*
|
||||
* @param newTimeSystem
|
||||
*/
|
||||
TimeConductorController.prototype.changeTimeSystem = function (newTimeSystem) {
|
||||
//Set time system in URL on change
|
||||
this.setParam(SEARCH.TIME_SYSTEM, newTimeSystem.metadata.key);
|
||||
TimeConductorController.prototype.setViewFromTimeSystem = function (timeSystem) {
|
||||
var oldKey = (this.$scope.timeSystemModel.selected || {}).key;
|
||||
var timeSystemModel = this.$scope.timeSystemModel;
|
||||
|
||||
if (newTimeSystem && (newTimeSystem !== this.$scope.timeSystemModel.selected)) {
|
||||
this.supportsZoom = !!(newTimeSystem.defaults() && newTimeSystem.defaults().zoom);
|
||||
this.setFormFromTimeSystem(newTimeSystem);
|
||||
if (timeSystem && (timeSystem.key !== oldKey)) {
|
||||
var config = this.getConfig(timeSystem, this.timeAPI.clock());
|
||||
|
||||
if (newTimeSystem.defaults()) {
|
||||
var deltas = newTimeSystem.defaults().deltas || {start: 0, end: 0};
|
||||
var bounds = newTimeSystem.defaults().bounds || {start: 0, end: 0};
|
||||
timeSystemModel.selected = timeSystem;
|
||||
timeSystemModel.format = timeSystem.timeFormat;
|
||||
timeSystemModel.durationFormat = timeSystem.durationFormat;
|
||||
|
||||
this.setFormFromDeltas(deltas);
|
||||
this.setFormFromBounds(bounds);
|
||||
if (this.supportsZoom()) {
|
||||
timeSystemModel.minZoom = config.zoomOutLimit;
|
||||
timeSystemModel.maxZoom = config.zoomInLimit;
|
||||
}
|
||||
}
|
||||
this.zoom = this.supportsZoom();
|
||||
};
|
||||
|
||||
/**
|
||||
* Takes a time span and calculates a slider increment value, used
|
||||
* to set the horizontal offset of the slider.
|
||||
* @private
|
||||
* @param {number} timeSpan a duration of time, in ms
|
||||
* @returns {number} a value between 0.01 and 0.99, in increments of .01
|
||||
*/
|
||||
TimeConductorController.prototype.toSliderValue = function (timeSpan) {
|
||||
var timeSystem = this.conductor.timeSystem();
|
||||
if (timeSystem) {
|
||||
var zoomDefaults = this.conductor.timeSystem().defaults().zoom;
|
||||
var perc = timeSpan / (zoomDefaults.min - zoomDefaults.max);
|
||||
return 1 - Math.pow(perc, 1 / 4);
|
||||
}
|
||||
TimeConductorController.prototype.toSliderValue = function (timeSpan, zoomOutLimit, zoomInLimit) {
|
||||
var perc = timeSpan / (zoomOutLimit - zoomInLimit);
|
||||
return 1 - Math.pow(perc, 1 / 4);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -415,9 +493,13 @@ define(
|
||||
* @param {TimeSpan} timeSpan
|
||||
*/
|
||||
TimeConductorController.prototype.toTimeUnits = function (timeSpan) {
|
||||
if (this.conductor.timeSystem()) {
|
||||
var timeFormat = this.formatService.getFormat(this.conductor.timeSystem().formats()[0]);
|
||||
this.$scope.timeUnits = timeFormat.timeUnits && timeFormat.timeUnits(timeSpan);
|
||||
var timeSystem = this.timeAPI.timeSystem();
|
||||
if (timeSystem && timeSystem.isUTCBased) {
|
||||
var momentified = moment.duration(timeSpan);
|
||||
|
||||
this.$scope.timeUnits = timeUnitsMegastructure.filter(function (row) {
|
||||
return row[1](momentified);
|
||||
})[0][0];
|
||||
}
|
||||
};
|
||||
|
||||
@ -429,17 +511,18 @@ define(
|
||||
* @param bounds
|
||||
*/
|
||||
TimeConductorController.prototype.onZoom = function (sliderValue) {
|
||||
var zoomDefaults = this.conductor.timeSystem().defaults().zoom;
|
||||
var timeSpan = Math.pow((1 - sliderValue), 4) * (zoomDefaults.min - zoomDefaults.max);
|
||||
var config = this.getConfig(this.timeAPI.timeSystem(), this.timeAPI.clock());
|
||||
var timeSpan = Math.pow((1 - sliderValue), 4) * (config.zoomOutLimit - config.zoomInLimit);
|
||||
|
||||
var zoom = this.conductorViewService.zoom(timeSpan);
|
||||
this.zooming = true;
|
||||
|
||||
this.$scope.boundsModel.start = zoom.bounds.start;
|
||||
this.$scope.boundsModel.end = zoom.bounds.end;
|
||||
this.toTimeUnits(zoom.bounds.end - zoom.bounds.start);
|
||||
|
||||
if (zoom.deltas) {
|
||||
this.setFormFromDeltas(zoom.deltas);
|
||||
if (zoom.offsets) {
|
||||
this.setViewFromOffsets(zoom.offsets);
|
||||
}
|
||||
};
|
||||
|
||||
@ -453,10 +536,12 @@ define(
|
||||
* @fires platform.features.conductor.TimeConductorController~zoomStop
|
||||
*/
|
||||
TimeConductorController.prototype.onZoomStop = function () {
|
||||
this.setBounds(this.$scope.boundsModel);
|
||||
this.setDeltas(this.$scope.boundsModel);
|
||||
this.zooming = false;
|
||||
if (this.timeAPI.clock() !== undefined) {
|
||||
this.setOffsetsFromView(this.$scope.boundsModel);
|
||||
}
|
||||
this.setBoundsFromView(this.$scope.boundsModel);
|
||||
|
||||
this.zooming = false;
|
||||
this.conductorViewService.emit('zoom-stop');
|
||||
};
|
||||
|
||||
@ -481,6 +566,20 @@ define(
|
||||
this.panning = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
TimeConductorController.prototype.destroy = function () {
|
||||
this.timeAPI.off('bounds', this.setViewFromBounds);
|
||||
this.timeAPI.off('timeSystem', this.setViewFromTimeSystem);
|
||||
this.timeAPI.off('clock', this.setViewFromClock);
|
||||
this.timeAPI.off('follow', this.setFollow);
|
||||
this.timeAPI.off('clockOffsets', this.setViewFromOffsets);
|
||||
|
||||
this.conductorViewService.off('pan', this.onPan);
|
||||
this.conductorViewService.off('pan-stop', this.onPanStop);
|
||||
};
|
||||
|
||||
return TimeConductorController;
|
||||
}
|
||||
);
|
||||
|
@ -21,7 +21,7 @@
|
||||
*****************************************************************************/
|
||||
|
||||
define(['./TimeConductorController'], function (TimeConductorController) {
|
||||
describe("The time conductor controller", function () {
|
||||
xdescribe("The time conductor controller", function () {
|
||||
var mockScope;
|
||||
var mockWindow;
|
||||
var mockTimeConductor;
|
||||
|
@ -1,248 +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 () {
|
||||
|
||||
/**
|
||||
* Supports mode-specific time conductor behavior.
|
||||
*
|
||||
* @constructor
|
||||
* @memberof platform.features.conductor
|
||||
* @param {TimeConductorMetadata} metadata
|
||||
*/
|
||||
function TimeConductorMode(metadata, conductor, timeSystems) {
|
||||
this.conductor = conductor;
|
||||
|
||||
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.tick = this.tick.bind(this);
|
||||
|
||||
//Set the time system initially
|
||||
if (conductor.timeSystem()) {
|
||||
this.changeTimeSystem(conductor.timeSystem());
|
||||
}
|
||||
|
||||
//Listen for subsequent changes to time system
|
||||
conductor.on('timeSystem', this.changeTimeSystem);
|
||||
|
||||
if (metadata.key === 'fixed') {
|
||||
//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;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or set the currently selected time system
|
||||
* @param timeSystem
|
||||
* @returns {TimeSystem} the currently selected time system
|
||||
*/
|
||||
TimeConductorMode.prototype.changeTimeSystem = function (timeSystem) {
|
||||
// On time system change, apply default deltas
|
||||
var defaults = timeSystem.defaults() || {
|
||||
bounds: {
|
||||
start: 0,
|
||||
end: 0
|
||||
},
|
||||
deltas: {
|
||||
start: 0,
|
||||
end: 0
|
||||
}
|
||||
};
|
||||
|
||||
this.conductor.bounds(defaults.bounds);
|
||||
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]);
|
||||
};
|
||||
|
||||
/**
|
||||
* @returns {ModeMetadata}
|
||||
*/
|
||||
TimeConductorMode.prototype.metadata = function () {
|
||||
return this.mdata;
|
||||
};
|
||||
|
||||
TimeConductorMode.prototype.availableTimeSystems = function () {
|
||||
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
|
||||
* listening to it and unlisten from any existing tick source
|
||||
* @param tickSource
|
||||
* @returns {TickSource}
|
||||
*/
|
||||
TimeConductorMode.prototype.tickSource = function (tickSource) {
|
||||
if (arguments.length > 0) {
|
||||
if (this.sourceUnlisten) {
|
||||
this.sourceUnlisten();
|
||||
}
|
||||
this.source = tickSource;
|
||||
if (tickSource) {
|
||||
this.sourceUnlisten = tickSource.listen(this.tick);
|
||||
//Now following a tick source
|
||||
this.conductor.follow(true);
|
||||
} else {
|
||||
this.conductor.follow(false);
|
||||
}
|
||||
}
|
||||
return this.source;
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
TimeConductorMode.prototype.destroy = function () {
|
||||
this.conductor.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
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get or set the current value for the deltas used by this time system.
|
||||
* On change, the new deltas will be used to calculate and set the
|
||||
* bounds on the time conductor.
|
||||
* @param deltas
|
||||
* @returns {TimeSystemDeltas}
|
||||
*/
|
||||
TimeConductorMode.prototype.deltas = function (deltas) {
|
||||
if (arguments.length !== 0) {
|
||||
var bounds = this.calculateBoundsFromDeltas(deltas);
|
||||
this.deltasVal = deltas;
|
||||
if (this.metadata().key !== 'fixed') {
|
||||
this.conductor.bounds(bounds);
|
||||
}
|
||||
}
|
||||
return this.deltasVal;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param deltas
|
||||
* @returns {TimeConductorBounds}
|
||||
*/
|
||||
TimeConductorMode.prototype.calculateBoundsFromDeltas = function (deltas) {
|
||||
var oldEnd = this.conductor.bounds().end;
|
||||
|
||||
if (this.deltasVal && this.deltasVal.end !== undefined) {
|
||||
//Calculate the previous raw end value (without delta)
|
||||
oldEnd = oldEnd - this.deltasVal.end;
|
||||
}
|
||||
|
||||
var bounds = {
|
||||
start: oldEnd - deltas.start,
|
||||
end: oldEnd + deltas.end
|
||||
};
|
||||
return bounds;
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {Object} ZoomLevel
|
||||
* @property {TimeConductorBounds} bounds The calculated bounds based on the zoom level
|
||||
* @property {TimeConductorDeltas} deltas The calculated deltas based on the zoom level
|
||||
*/
|
||||
/**
|
||||
* Calculates bounds and deltas based on provided timeSpan. Collectively
|
||||
* the bounds and deltas will constitute the new zoom level.
|
||||
* @param {number} timeSpan time duration in ms.
|
||||
* @return {ZoomLevel} The new zoom bounds and delta calculated for the provided time span
|
||||
*/
|
||||
TimeConductorMode.prototype.calculateZoom = function (timeSpan) {
|
||||
var zoom = {};
|
||||
|
||||
// If a tick source is defined, then the concept of 'now' is
|
||||
// important. Calculate zoom based on 'now'.
|
||||
if (this.tickSource()) {
|
||||
zoom.deltas = {
|
||||
start: timeSpan,
|
||||
end: this.deltasVal.end
|
||||
};
|
||||
zoom.bounds = this.calculateBoundsFromDeltas(zoom.deltas);
|
||||
// Calculate bounds based on deltas;
|
||||
} else {
|
||||
var bounds = this.conductor.bounds();
|
||||
var center = bounds.start + ((bounds.end - bounds.start)) / 2;
|
||||
bounds.start = center - timeSpan / 2;
|
||||
bounds.end = center + timeSpan / 2;
|
||||
zoom.bounds = bounds;
|
||||
}
|
||||
|
||||
return zoom;
|
||||
};
|
||||
|
||||
return TimeConductorMode;
|
||||
}
|
||||
);
|
@ -1,210 +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(['./TimeConductorMode'], function (TimeConductorMode) {
|
||||
describe("The Time Conductor Mode", function () {
|
||||
var mockTimeConductor,
|
||||
fixedModeMetaData,
|
||||
mockTimeSystems,
|
||||
fixedTimeSystem,
|
||||
|
||||
realtimeModeMetaData,
|
||||
realtimeTimeSystem,
|
||||
mockTickSource,
|
||||
|
||||
mockBounds,
|
||||
mode;
|
||||
|
||||
beforeEach(function () {
|
||||
fixedModeMetaData = {
|
||||
key: "fixed"
|
||||
};
|
||||
realtimeModeMetaData = {
|
||||
key: "realtime"
|
||||
};
|
||||
mockBounds = {
|
||||
start: 0,
|
||||
end: 1
|
||||
};
|
||||
|
||||
fixedTimeSystem = jasmine.createSpyObj("timeSystem", [
|
||||
"defaults",
|
||||
"tickSources"
|
||||
]);
|
||||
fixedTimeSystem.tickSources.andReturn([]);
|
||||
|
||||
mockTickSource = jasmine.createSpyObj("tickSource", [
|
||||
"listen"
|
||||
]);
|
||||
mockTickSource.metadata = {
|
||||
mode: "realtime"
|
||||
};
|
||||
realtimeTimeSystem = jasmine.createSpyObj("realtimeTimeSystem", [
|
||||
"defaults",
|
||||
"tickSources"
|
||||
]);
|
||||
realtimeTimeSystem.tickSources.andReturn([mockTickSource]);
|
||||
|
||||
//Do not return any time systems initially for a default
|
||||
// construction configuration that works without any additional work
|
||||
mockTimeSystems = [];
|
||||
|
||||
mockTimeConductor = jasmine.createSpyObj("timeConductor", [
|
||||
"bounds",
|
||||
"timeSystem",
|
||||
"on",
|
||||
"off",
|
||||
"follow"
|
||||
]);
|
||||
mockTimeConductor.bounds.andReturn(mockBounds);
|
||||
});
|
||||
|
||||
it("Reacts to changes in conductor time system", function () {
|
||||
mode = new TimeConductorMode(fixedModeMetaData, mockTimeConductor, mockTimeSystems);
|
||||
expect(mockTimeConductor.on).toHaveBeenCalledWith("timeSystem", mode.changeTimeSystem);
|
||||
});
|
||||
|
||||
it("Stops listening to time system changes on destroy", function () {
|
||||
mode = new TimeConductorMode(fixedModeMetaData, mockTimeConductor, mockTimeSystems);
|
||||
mode.destroy();
|
||||
expect(mockTimeConductor.off).toHaveBeenCalledWith("timeSystem", mode.changeTimeSystem);
|
||||
});
|
||||
|
||||
it("Filters available time systems to those with tick sources that" +
|
||||
" support this mode", function () {
|
||||
mockTimeSystems = [fixedTimeSystem, realtimeTimeSystem];
|
||||
mode = new TimeConductorMode(realtimeModeMetaData, mockTimeConductor, mockTimeSystems);
|
||||
|
||||
var availableTimeSystems = mode.availableTimeSystems();
|
||||
expect(availableTimeSystems.length).toBe(1);
|
||||
expect(availableTimeSystems.indexOf(fixedTimeSystem)).toBe(-1);
|
||||
expect(availableTimeSystems.indexOf(realtimeTimeSystem)).toBe(0);
|
||||
});
|
||||
|
||||
describe("Changing the time system", function () {
|
||||
var defaults;
|
||||
|
||||
beforeEach(function () {
|
||||
defaults = {
|
||||
bounds: {
|
||||
start: 1,
|
||||
end: 2
|
||||
},
|
||||
deltas: {
|
||||
start: 3,
|
||||
end: 4
|
||||
}
|
||||
};
|
||||
|
||||
fixedTimeSystem.defaults.andReturn(defaults);
|
||||
|
||||
});
|
||||
it ("sets defaults from new time system", function () {
|
||||
mode = new TimeConductorMode(fixedModeMetaData, mockTimeConductor, mockTimeSystems);
|
||||
spyOn(mode, "deltas");
|
||||
mode.deltas.andCallThrough();
|
||||
|
||||
mode.changeTimeSystem(fixedTimeSystem);
|
||||
expect(mockTimeConductor.bounds).toHaveBeenCalledWith(defaults.bounds);
|
||||
expect(mode.deltas).toHaveBeenCalledWith(defaults.deltas);
|
||||
});
|
||||
it ("If a tick source is available, sets the tick source", function () {
|
||||
mode = new TimeConductorMode(realtimeModeMetaData, mockTimeConductor, mockTimeSystems);
|
||||
mode.changeTimeSystem(realtimeTimeSystem);
|
||||
|
||||
var currentTickSource = mode.tickSource();
|
||||
expect(currentTickSource).toBe(mockTickSource);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Setting a tick source", function () {
|
||||
var mockUnlistener;
|
||||
|
||||
beforeEach(function () {
|
||||
mockUnlistener = jasmine.createSpy("unlistener");
|
||||
mockTickSource.listen.andReturn(mockUnlistener);
|
||||
|
||||
mode = new TimeConductorMode(realtimeModeMetaData, mockTimeConductor, mockTimeSystems);
|
||||
mode.tickSource(mockTickSource);
|
||||
});
|
||||
|
||||
it ("Unlistens from old tick source", function () {
|
||||
mode.tickSource(mockTickSource);
|
||||
expect(mockUnlistener).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it ("Listens to new tick source", function () {
|
||||
expect(mockTickSource.listen).toHaveBeenCalledWith(mode.tick);
|
||||
});
|
||||
|
||||
it ("Sets 'follow' state on time conductor", function () {
|
||||
expect(mockTimeConductor.follow).toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
it ("on destroy, unlistens from tick source", function () {
|
||||
mode.destroy();
|
||||
expect(mockUnlistener).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("setting deltas", function () {
|
||||
beforeEach(function () {
|
||||
mode = new TimeConductorMode(realtimeModeMetaData, mockTimeConductor, mockTimeSystems);
|
||||
});
|
||||
it ("sets the bounds on the time conductor based on new delta" +
|
||||
" values", function () {
|
||||
var deltas = {
|
||||
start: 20,
|
||||
end: 10
|
||||
};
|
||||
|
||||
mode.deltas(deltas);
|
||||
|
||||
expect(mockTimeConductor.bounds).toHaveBeenCalledWith({
|
||||
start: mockBounds.end - deltas.start,
|
||||
end: mockBounds.end + deltas.end
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("ticking", function () {
|
||||
beforeEach(function () {
|
||||
mode = new TimeConductorMode(realtimeModeMetaData, mockTimeConductor, mockTimeSystems);
|
||||
});
|
||||
it ("sets bounds based on current delta values", function () {
|
||||
var deltas = {
|
||||
start: 20,
|
||||
end: 10
|
||||
};
|
||||
var time = 100;
|
||||
|
||||
mode.deltas(deltas);
|
||||
mode.tick(time);
|
||||
|
||||
expect(mockTimeConductor.bounds).toHaveBeenCalledWith({
|
||||
start: time - deltas.start,
|
||||
end: time + deltas.end
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -29,9 +29,9 @@ define(
|
||||
* @param conductor
|
||||
* @constructor
|
||||
*/
|
||||
function TimeConductorValidation(conductor) {
|
||||
function TimeConductorValidation(timeAPI) {
|
||||
var self = this;
|
||||
this.conductor = conductor;
|
||||
this.timeAPI = timeAPI;
|
||||
|
||||
/*
|
||||
* Bind all class functions to 'this'
|
||||
@ -47,21 +47,21 @@ define(
|
||||
* Validation methods below are invoked directly from controls in the TimeConductor form
|
||||
*/
|
||||
TimeConductorValidation.prototype.validateStart = function (start) {
|
||||
var bounds = this.conductor.bounds();
|
||||
return this.conductor.validateBounds({start: start, end: bounds.end}) === true;
|
||||
var bounds = this.timeAPI.bounds();
|
||||
return this.timeAPI.validateBounds({start: start, end: bounds.end}) === true;
|
||||
};
|
||||
|
||||
TimeConductorValidation.prototype.validateEnd = function (end) {
|
||||
var bounds = this.conductor.bounds();
|
||||
return this.conductor.validateBounds({start: bounds.start, end: end}) === true;
|
||||
var bounds = this.timeAPI.bounds();
|
||||
return this.timeAPI.validateBounds({start: bounds.start, end: end}) === true;
|
||||
};
|
||||
|
||||
TimeConductorValidation.prototype.validateStartDelta = function (startDelta) {
|
||||
return !isNaN(startDelta) && startDelta > 0;
|
||||
TimeConductorValidation.prototype.validateStartOffset = function (startOffset) {
|
||||
return !isNaN(startOffset) && startOffset > 0;
|
||||
};
|
||||
|
||||
TimeConductorValidation.prototype.validateEndDelta = function (endDelta) {
|
||||
return !isNaN(endDelta) && endDelta >= 0;
|
||||
TimeConductorValidation.prototype.validateEndOffset = function (endOffset) {
|
||||
return !isNaN(endOffset) && endOffset >= 0;
|
||||
};
|
||||
|
||||
return TimeConductorValidation;
|
||||
|
@ -55,19 +55,19 @@ define(['./TimeConductorValidation'], function (TimeConductorValidation) {
|
||||
});
|
||||
});
|
||||
|
||||
it("Validates that start delta is valid number > 0", function () {
|
||||
expect(timeConductorValidation.validateStartDelta(-1)).toBe(false);
|
||||
expect(timeConductorValidation.validateStartDelta("abc")).toBe(false);
|
||||
expect(timeConductorValidation.validateStartDelta("1")).toBe(true);
|
||||
expect(timeConductorValidation.validateStartDelta(1)).toBe(true);
|
||||
it("Validates that start Offset is valid number > 0", function () {
|
||||
expect(timeConductorValidation.validateStartOffset(-1)).toBe(false);
|
||||
expect(timeConductorValidation.validateStartOffset("abc")).toBe(false);
|
||||
expect(timeConductorValidation.validateStartOffset("1")).toBe(true);
|
||||
expect(timeConductorValidation.validateStartOffset(1)).toBe(true);
|
||||
});
|
||||
|
||||
it("Validates that end delta is valid number >= 0", function () {
|
||||
expect(timeConductorValidation.validateEndDelta(-1)).toBe(false);
|
||||
expect(timeConductorValidation.validateEndDelta("abc")).toBe(false);
|
||||
expect(timeConductorValidation.validateEndDelta("1")).toBe(true);
|
||||
expect(timeConductorValidation.validateEndDelta(0)).toBe(true);
|
||||
expect(timeConductorValidation.validateEndDelta(1)).toBe(true);
|
||||
it("Validates that end Offset is valid number >= 0", function () {
|
||||
expect(timeConductorValidation.validateEndOffset(-1)).toBe(false);
|
||||
expect(timeConductorValidation.validateEndOffset("abc")).toBe(false);
|
||||
expect(timeConductorValidation.validateEndOffset("1")).toBe(true);
|
||||
expect(timeConductorValidation.validateEndOffset(0)).toBe(true);
|
||||
expect(timeConductorValidation.validateEndOffset(1)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -22,188 +22,33 @@
|
||||
|
||||
define(
|
||||
[
|
||||
'EventEmitter',
|
||||
'./TimeConductorMode'
|
||||
'EventEmitter'
|
||||
],
|
||||
function (EventEmitter, TimeConductorMode) {
|
||||
function (EventEmitter) {
|
||||
|
||||
/**
|
||||
* A class representing the state of the time conductor view. This
|
||||
* exposes details of the UI that are not represented on the
|
||||
* TimeConductor API itself such as modes and deltas.
|
||||
* The TimeConductorViewService acts as an event bus between different
|
||||
* elements of the Time Conductor UI. Zooming and panning occur via this
|
||||
* service, as they are specific behaviour of the UI, and not general
|
||||
* functions of the time API.
|
||||
*
|
||||
* Synchronization of conductor state between the Time API and the URL
|
||||
* also occurs from the conductor view service, whose lifecycle persists
|
||||
* between view changes.
|
||||
*
|
||||
* @memberof platform.features.conductor
|
||||
* @param conductor
|
||||
* @param timeSystems
|
||||
* @constructor
|
||||
*/
|
||||
function TimeConductorViewService(openmct, timeSystems) {
|
||||
function TimeConductorViewService(openmct) {
|
||||
|
||||
EventEmitter.call(this);
|
||||
|
||||
this.systems = timeSystems.map(function (timeSystemConstructor) {
|
||||
return timeSystemConstructor();
|
||||
});
|
||||
|
||||
this.conductor = openmct.conductor;
|
||||
this.currentMode = undefined;
|
||||
|
||||
/**
|
||||
* @typedef {object} ModeMetadata
|
||||
* @property {string} key A unique identifying key for this mode
|
||||
* @property {string} cssClass The css class for the glyph
|
||||
* representing this mode
|
||||
* @property {string} label A short label for this mode
|
||||
* @property {string} name A longer name for the mode
|
||||
* @property {string} description A description of the mode
|
||||
*/
|
||||
this.availModes = {
|
||||
'fixed': {
|
||||
key: 'fixed',
|
||||
cssClass: 'icon-calendar',
|
||||
label: 'Fixed',
|
||||
name: 'Fixed Timespan Mode',
|
||||
description: 'Query and explore data that falls between two fixed datetimes.'
|
||||
}
|
||||
};
|
||||
|
||||
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
|
||||
if (timeSystemsForMode('realtime').length > 0) {
|
||||
var realtimeMode = {
|
||||
key: 'realtime',
|
||||
cssClass: 'icon-clock',
|
||||
label: 'Real-time',
|
||||
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.'
|
||||
};
|
||||
this.availModes[realtimeMode.key] = realtimeMode;
|
||||
}
|
||||
|
||||
//Only show 'LAD mode' if appropriate time systems available
|
||||
if (timeSystemsForMode('lad').length > 0) {
|
||||
var ladMode = {
|
||||
key: 'lad',
|
||||
cssClass: 'icon-database',
|
||||
label: 'LAD',
|
||||
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.'
|
||||
};
|
||||
this.availModes[ladMode.key] = ladMode;
|
||||
}
|
||||
this.timeAPI = openmct.time;
|
||||
}
|
||||
|
||||
TimeConductorViewService.prototype = Object.create(EventEmitter.prototype);
|
||||
|
||||
/**
|
||||
* Getter/Setter for the Time Conductor Mode. Modes determine the
|
||||
* behavior of the time conductor, especially with regards to the
|
||||
* bounds and how they change with time.
|
||||
*
|
||||
* In fixed mode, the bounds do not change with time, but can be
|
||||
* modified by the used
|
||||
*
|
||||
* In realtime mode, the bounds change with time. Bounds are not
|
||||
* directly modifiable by the user, however deltas can be.
|
||||
*
|
||||
* In Latest Available Data (LAD) mode, the bounds are updated when
|
||||
* data is received. As with realtime mode the
|
||||
*
|
||||
* @param {string} newModeKey One of 'fixed', 'realtime', or 'LAD'
|
||||
* @returns {string} the current mode, one of 'fixed', 'realtime',
|
||||
* or 'LAD'.
|
||||
*
|
||||
*/
|
||||
TimeConductorViewService.prototype.mode = function (newModeKey) {
|
||||
function contains(timeSystems, ts) {
|
||||
return timeSystems.filter(function (t) {
|
||||
return t.metadata.key === ts.metadata.key;
|
||||
}).length > 0;
|
||||
}
|
||||
|
||||
if (arguments.length === 1) {
|
||||
var timeSystem = this.conductor.timeSystem();
|
||||
var modes = this.availableModes();
|
||||
var modeMetaData = modes[newModeKey];
|
||||
|
||||
if (this.currentMode) {
|
||||
this.currentMode.destroy();
|
||||
}
|
||||
this.currentMode = new TimeConductorMode(modeMetaData, this.conductor, this.systems);
|
||||
|
||||
// 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
|
||||
if (!timeSystem || !contains(this.currentMode.availableTimeSystems(), timeSystem)) {
|
||||
timeSystem = this.currentMode.availableTimeSystems()[0];
|
||||
this.conductor.timeSystem(timeSystem, timeSystem.defaults().bounds);
|
||||
}
|
||||
}
|
||||
return this.currentMode ? this.currentMode.metadata().key : undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {object} TimeConductorDeltas
|
||||
* @property {number} start Used to set the start bound of the
|
||||
* TimeConductor on tick. A positive value that will be subtracted
|
||||
* from the value provided by a tick source to determine the start
|
||||
* bound.
|
||||
* @property {number} end Used to set the end bound of the
|
||||
* TimeConductor on tick. A positive value that will be added
|
||||
* from the value provided by a tick source to determine the start
|
||||
* bound.
|
||||
*/
|
||||
/**
|
||||
* Deltas define the offset from the latest time value provided by
|
||||
* the current tick source. Deltas are only valid in realtime or LAD
|
||||
* modes.
|
||||
*
|
||||
* Realtime mode:
|
||||
* - start: A time in ms before now which will be used to
|
||||
* determine the 'start' bound on tick
|
||||
* - end: A time in ms after now which will be used to determine
|
||||
* the 'end' bound on tick
|
||||
*
|
||||
* LAD mode:
|
||||
* - start: A time in ms before the timestamp of the last data
|
||||
* received which will be used to determine the 'start' bound on
|
||||
* tick
|
||||
* - end: A time in ms after the timestamp of the last data received
|
||||
* which will be used to determine the 'end' bound on tick
|
||||
* @returns {TimeConductorDeltas} current value of the deltas
|
||||
*/
|
||||
TimeConductorViewService.prototype.deltas = function () {
|
||||
//Deltas stored on mode. Use .apply to preserve arguments
|
||||
return this.currentMode.deltas.apply(this.currentMode, arguments);
|
||||
};
|
||||
|
||||
/**
|
||||
* Availability of modes depends on the time systems and tick
|
||||
* sources available. For example, Latest Available Data mode will
|
||||
* not be available if there are no time systems and tick sources
|
||||
* that support LAD mode.
|
||||
* @returns {ModeMetadata[]}
|
||||
*/
|
||||
TimeConductorViewService.prototype.availableModes = function () {
|
||||
return this.availModes;
|
||||
};
|
||||
|
||||
/**
|
||||
* Availability of time systems depends on the currently selected
|
||||
* mode. Time systems and tick sources are mode dependent
|
||||
*/
|
||||
TimeConductorViewService.prototype.availableTimeSystems = function () {
|
||||
return this.currentMode.availableTimeSystems();
|
||||
};
|
||||
|
||||
/**
|
||||
* An event to indicate that zooming is taking place
|
||||
* @event platform.features.conductor.TimeConductorViewService~zoom
|
||||
@ -219,7 +64,30 @@ define(
|
||||
* @see module:openmct.TimeConductor#bounds
|
||||
*/
|
||||
TimeConductorViewService.prototype.zoom = function (timeSpan) {
|
||||
var zoom = this.currentMode.calculateZoom(timeSpan);
|
||||
var zoom = {};
|
||||
|
||||
// If a tick source is defined, then the concept of 'now' is
|
||||
// important. Calculate zoom based on 'now'.
|
||||
if (this.timeAPI.clock() !== undefined) {
|
||||
zoom.offsets = {
|
||||
start: -timeSpan,
|
||||
end: this.timeAPI.clockOffsets().end
|
||||
};
|
||||
|
||||
var currentVal = this.timeAPI.clock().currentValue();
|
||||
|
||||
zoom.bounds = {
|
||||
start: currentVal + zoom.offsets.start,
|
||||
end: currentVal + zoom.offsets.end
|
||||
};
|
||||
} else {
|
||||
var bounds = this.timeAPI.bounds();
|
||||
var center = bounds.start + ((bounds.end - bounds.start)) / 2;
|
||||
bounds.start = center - timeSpan / 2;
|
||||
bounds.end = center + timeSpan / 2;
|
||||
zoom.bounds = bounds;
|
||||
}
|
||||
|
||||
this.emit("zoom", zoom);
|
||||
return zoom;
|
||||
};
|
||||
|
@ -1,185 +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(['./TimeConductorViewService'], function (TimeConductorViewService) {
|
||||
describe("The Time Conductor view service", function () {
|
||||
var mockTimeConductor;
|
||||
var basicTimeSystem;
|
||||
var tickingTimeSystem;
|
||||
var viewService;
|
||||
var tickingTimeSystemDefaults;
|
||||
|
||||
function mockConstructor(object) {
|
||||
return function () {
|
||||
return object;
|
||||
};
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
mockTimeConductor = jasmine.createSpyObj("timeConductor", [
|
||||
"timeSystem",
|
||||
"bounds",
|
||||
"follow",
|
||||
"on",
|
||||
"off"
|
||||
]);
|
||||
|
||||
basicTimeSystem = jasmine.createSpyObj("basicTimeSystem", [
|
||||
"tickSources",
|
||||
"defaults"
|
||||
]);
|
||||
basicTimeSystem.metadata = {
|
||||
key: "basic"
|
||||
};
|
||||
basicTimeSystem.tickSources.andReturn([]);
|
||||
basicTimeSystem.defaults.andReturn({
|
||||
bounds: {
|
||||
start: 0,
|
||||
end: 1
|
||||
},
|
||||
deltas: {
|
||||
start: 0,
|
||||
end: 0
|
||||
}
|
||||
});
|
||||
//Initialize conductor
|
||||
mockTimeConductor.timeSystem.andReturn(basicTimeSystem);
|
||||
mockTimeConductor.bounds.andReturn({start: 0, end: 1});
|
||||
|
||||
tickingTimeSystem = jasmine.createSpyObj("tickingTimeSystem", [
|
||||
"tickSources",
|
||||
"defaults"
|
||||
]);
|
||||
tickingTimeSystem.metadata = {
|
||||
key: "ticking"
|
||||
};
|
||||
tickingTimeSystemDefaults = {
|
||||
bounds: {
|
||||
start: 100,
|
||||
end: 200
|
||||
},
|
||||
deltas: {
|
||||
start: 1000,
|
||||
end: 500
|
||||
}
|
||||
};
|
||||
tickingTimeSystem.defaults.andReturn(tickingTimeSystemDefaults);
|
||||
});
|
||||
|
||||
it("At a minimum supports fixed mode", function () {
|
||||
var mockTimeSystems = [mockConstructor(basicTimeSystem)];
|
||||
viewService = new TimeConductorViewService({conductor: mockTimeConductor}, mockTimeSystems);
|
||||
|
||||
var availableModes = viewService.availableModes();
|
||||
expect(availableModes.fixed).toBeDefined();
|
||||
});
|
||||
|
||||
it("Supports realtime mode if appropriate tick source(s) availables", function () {
|
||||
var mockTimeSystems = [mockConstructor(tickingTimeSystem)];
|
||||
var mockRealtimeTickSource = {
|
||||
metadata: {
|
||||
mode: 'realtime'
|
||||
}
|
||||
};
|
||||
tickingTimeSystem.tickSources.andReturn([mockRealtimeTickSource]);
|
||||
|
||||
viewService = new TimeConductorViewService({conductor: mockTimeConductor}, mockTimeSystems);
|
||||
|
||||
var availableModes = viewService.availableModes();
|
||||
expect(availableModes.realtime).toBeDefined();
|
||||
});
|
||||
|
||||
it("Supports LAD mode if appropriate tick source(s) available", function () {
|
||||
var mockTimeSystems = [mockConstructor(tickingTimeSystem)];
|
||||
var mockLADTickSource = {
|
||||
metadata: {
|
||||
mode: 'lad'
|
||||
}
|
||||
};
|
||||
tickingTimeSystem.tickSources.andReturn([mockLADTickSource]);
|
||||
|
||||
viewService = new TimeConductorViewService({conductor: mockTimeConductor}, mockTimeSystems);
|
||||
|
||||
var availableModes = viewService.availableModes();
|
||||
expect(availableModes.lad).toBeDefined();
|
||||
});
|
||||
|
||||
describe("when mode is changed", function () {
|
||||
|
||||
it("destroys previous mode", function () {
|
||||
var mockTimeSystems = [mockConstructor(basicTimeSystem)];
|
||||
|
||||
var oldMode = jasmine.createSpyObj("conductorMode", [
|
||||
"destroy"
|
||||
]);
|
||||
|
||||
viewService = new TimeConductorViewService({conductor: mockTimeConductor}, mockTimeSystems);
|
||||
viewService.currentMode = oldMode;
|
||||
viewService.mode('fixed');
|
||||
expect(oldMode.destroy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe("the time system", function () {
|
||||
it("is retained if available in new mode", function () {
|
||||
var mockTimeSystems = [mockConstructor(basicTimeSystem), mockConstructor(tickingTimeSystem)];
|
||||
var mockRealtimeTickSource = {
|
||||
metadata: {
|
||||
mode: 'realtime'
|
||||
},
|
||||
listen: function () {}
|
||||
};
|
||||
tickingTimeSystem.tickSources.andReturn([mockRealtimeTickSource]);
|
||||
|
||||
viewService = new TimeConductorViewService({conductor: mockTimeConductor}, mockTimeSystems);
|
||||
|
||||
//Set time system to one known to support realtime mode
|
||||
mockTimeConductor.timeSystem.andReturn(tickingTimeSystem);
|
||||
|
||||
//Select realtime mode
|
||||
mockTimeConductor.timeSystem.reset();
|
||||
viewService.mode('realtime');
|
||||
expect(mockTimeConductor.timeSystem).not.toHaveBeenCalledWith(tickingTimeSystem, tickingTimeSystemDefaults.bounds);
|
||||
});
|
||||
it("is defaulted if selected time system not available in new mode", function () {
|
||||
var mockTimeSystems = [mockConstructor(basicTimeSystem), mockConstructor(tickingTimeSystem)];
|
||||
var mockRealtimeTickSource = {
|
||||
metadata: {
|
||||
mode: 'realtime'
|
||||
},
|
||||
listen: function () {}
|
||||
};
|
||||
tickingTimeSystem.tickSources.andReturn([mockRealtimeTickSource]);
|
||||
|
||||
viewService = new TimeConductorViewService({conductor: mockTimeConductor}, mockTimeSystems);
|
||||
|
||||
//Set time system to one known to not support realtime mode
|
||||
mockTimeConductor.timeSystem.andReturn(basicTimeSystem);
|
||||
|
||||
//Select realtime mode
|
||||
mockTimeConductor.timeSystem.reset();
|
||||
viewService.mode('realtime');
|
||||
expect(mockTimeConductor.timeSystem).toHaveBeenCalledWith(tickingTimeSystem, tickingTimeSystemDefaults.bounds);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -31,7 +31,7 @@ define(
|
||||
* @constructor
|
||||
*/
|
||||
function TimeOfInterestController($scope, openmct, formatService) {
|
||||
this.conductor = openmct.conductor;
|
||||
this.timeAPI = openmct.time;
|
||||
this.formatService = formatService;
|
||||
this.format = undefined;
|
||||
this.toiText = undefined;
|
||||
@ -44,11 +44,11 @@ define(
|
||||
this[key] = TimeOfInterestController.prototype[key].bind(this);
|
||||
}.bind(this));
|
||||
|
||||
this.conductor.on('timeOfInterest', this.changeTimeOfInterest);
|
||||
this.conductor.on('timeSystem', this.changeTimeSystem);
|
||||
if (this.conductor.timeSystem()) {
|
||||
this.changeTimeSystem(this.conductor.timeSystem());
|
||||
var toi = this.conductor.timeOfInterest();
|
||||
this.timeAPI.on('timeOfInterest', this.changeTimeOfInterest);
|
||||
this.timeAPI.on('timeSystem', this.changeTimeSystem);
|
||||
if (this.timeAPI.timeSystem() !== undefined) {
|
||||
this.changeTimeSystem(this.timeAPI.timeSystem());
|
||||
var toi = this.timeAPI.timeOfInterest();
|
||||
if (toi) {
|
||||
this.changeTimeOfInterest(toi);
|
||||
}
|
||||
@ -77,15 +77,15 @@ define(
|
||||
* display the current TOI label
|
||||
*/
|
||||
TimeOfInterestController.prototype.changeTimeSystem = function (timeSystem) {
|
||||
this.format = this.formatService.getFormat(timeSystem.formats()[0]);
|
||||
this.format = this.formatService.getFormat(timeSystem.timeFormat);
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
TimeOfInterestController.prototype.destroy = function () {
|
||||
this.conductor.off('timeOfInterest', this.changeTimeOfInterest);
|
||||
this.conductor.off('timeSystem', this.changeTimeSystem);
|
||||
this.timeAPI.off('timeOfInterest', this.changeTimeOfInterest);
|
||||
this.timeAPI.off('timeSystem', this.changeTimeSystem);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -93,7 +93,7 @@ define(
|
||||
* Time Conductor
|
||||
*/
|
||||
TimeOfInterestController.prototype.dismiss = function () {
|
||||
this.conductor.timeOfInterest(undefined);
|
||||
this.timeAPI.timeOfInterest(undefined);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -101,7 +101,7 @@ define(
|
||||
* the TOI displayed in views.
|
||||
*/
|
||||
TimeOfInterestController.prototype.resync = function () {
|
||||
this.conductor.timeOfInterest(this.conductor.timeOfInterest());
|
||||
this.timeAPI.timeOfInterest(this.timeAPI.timeOfInterest());
|
||||
};
|
||||
|
||||
return TimeOfInterestController;
|
||||
|
@ -1,115 +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(['./TimeOfInterestController'], function (TimeOfInterestController) {
|
||||
|
||||
describe("The time of interest controller", function () {
|
||||
var controller;
|
||||
var mockScope;
|
||||
var mockConductor;
|
||||
var mockFormatService;
|
||||
var mockTimeSystem;
|
||||
var mockFormat;
|
||||
|
||||
beforeEach(function () {
|
||||
mockConductor = jasmine.createSpyObj("conductor", [
|
||||
"on",
|
||||
"timeSystem"
|
||||
]);
|
||||
mockScope = jasmine.createSpyObj("scope", [
|
||||
"$on"
|
||||
]);
|
||||
|
||||
mockFormat = jasmine.createSpyObj("format", [
|
||||
"format"
|
||||
]);
|
||||
|
||||
mockFormatService = jasmine.createSpyObj("formatService", [
|
||||
"getFormat"
|
||||
]);
|
||||
|
||||
mockFormatService.getFormat.andReturn(mockFormat);
|
||||
|
||||
mockTimeSystem = {
|
||||
formats: function () {
|
||||
return ["mockFormat"];
|
||||
}
|
||||
};
|
||||
|
||||
controller = new TimeOfInterestController(mockScope, {conductor: mockConductor}, mockFormatService);
|
||||
});
|
||||
|
||||
function getCallback(target, event) {
|
||||
return target.calls.filter(function (call) {
|
||||
return call.args[0] === event;
|
||||
})[0].args[1];
|
||||
}
|
||||
|
||||
it("Listens for changes to TOI", function () {
|
||||
expect(mockConductor.on).toHaveBeenCalledWith("timeOfInterest", controller.changeTimeOfInterest);
|
||||
});
|
||||
|
||||
it("updates format when time system changes", function () {
|
||||
expect(mockConductor.on).toHaveBeenCalledWith("timeSystem", controller.changeTimeSystem);
|
||||
getCallback(mockConductor.on, "timeSystem")(mockTimeSystem);
|
||||
expect(controller.format).toBe(mockFormat);
|
||||
});
|
||||
|
||||
describe("When TOI changes", function () {
|
||||
var toi;
|
||||
var toiCallback;
|
||||
var formattedTOI;
|
||||
|
||||
beforeEach(function () {
|
||||
var timeSystemCallback = getCallback(mockConductor.on, "timeSystem");
|
||||
toi = 1;
|
||||
mockConductor.timeSystem.andReturn(mockTimeSystem);
|
||||
|
||||
//Set time system
|
||||
timeSystemCallback(mockTimeSystem);
|
||||
|
||||
toiCallback = getCallback(mockConductor.on, "timeOfInterest");
|
||||
formattedTOI = "formatted TOI";
|
||||
|
||||
mockFormatService.getFormat.andReturn("mockFormat");
|
||||
mockFormat.format.andReturn(formattedTOI);
|
||||
});
|
||||
it("Uses the time system formatter to produce TOI text", function () {
|
||||
toiCallback = getCallback(mockConductor.on, "timeOfInterest");
|
||||
//Set TOI
|
||||
toiCallback(toi);
|
||||
expect(mockFormat.format).toHaveBeenCalled();
|
||||
});
|
||||
it("Sets the time of interest text", function () {
|
||||
//Set TOI
|
||||
toiCallback(toi);
|
||||
expect(controller.toiText).toBe(formattedTOI);
|
||||
});
|
||||
it("Pins the time of interest", function () {
|
||||
//Set TOI
|
||||
toiCallback(toi);
|
||||
expect(mockScope.pinned).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
@ -1,82 +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([
|
||||
'../../core/src/timeSystems/TimeSystem',
|
||||
'../../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.
|
||||
* @implements TimeSystem
|
||||
* @constructor
|
||||
*/
|
||||
function UTCTimeSystem($timeout) {
|
||||
TimeSystem.call(this);
|
||||
|
||||
/**
|
||||
* Some metadata, which will be used to identify the time system in
|
||||
* the UI
|
||||
* @type {{key: string, name: string, cssClass: string}}
|
||||
*/
|
||||
this.metadata = {
|
||||
'key': 'utc',
|
||||
'name': 'UTC',
|
||||
'cssClass': 'icon-clock'
|
||||
};
|
||||
|
||||
this.fmts = ['utc'];
|
||||
this.sources = [new LocalClock($timeout, DEFAULT_PERIOD)];
|
||||
}
|
||||
|
||||
UTCTimeSystem.prototype = Object.create(TimeSystem.prototype);
|
||||
|
||||
UTCTimeSystem.prototype.formats = function () {
|
||||
return this.fmts;
|
||||
};
|
||||
|
||||
UTCTimeSystem.prototype.deltaFormat = function () {
|
||||
return 'duration';
|
||||
};
|
||||
|
||||
UTCTimeSystem.prototype.tickSources = function () {
|
||||
return this.sources;
|
||||
};
|
||||
|
||||
UTCTimeSystem.prototype.defaults = function () {
|
||||
var now = Math.ceil(Date.now() / 1000) * 1000;
|
||||
var ONE_MINUTE = 60 * 1 * 1000;
|
||||
var FIFTY_YEARS = 50 * 365 * 24 * 60 * 60 * 1000;
|
||||
|
||||
return {
|
||||
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 UTCTimeSystem;
|
||||
});
|
@ -203,8 +203,8 @@ define(
|
||||
}
|
||||
|
||||
// Trigger a new query for telemetry data
|
||||
function updateDisplayBounds(bounds) {
|
||||
if (!self.openmct.conductor.follow()) {
|
||||
function updateDisplayBounds(bounds, isTick) {
|
||||
if (!isTick) {
|
||||
//Reset values
|
||||
self.values = {};
|
||||
refreshElements();
|
||||
@ -295,11 +295,11 @@ define(
|
||||
// Free up subscription on destroy
|
||||
$scope.$on("$destroy", function () {
|
||||
self.unsubscribe();
|
||||
self.openmct.conductor.off("bounds", updateDisplayBounds);
|
||||
self.openmct.time.off("bounds", updateDisplayBounds);
|
||||
});
|
||||
|
||||
// Respond to external bounds changes
|
||||
this.openmct.conductor.on("bounds", updateDisplayBounds);
|
||||
this.openmct.time.on("bounds", updateDisplayBounds);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -338,9 +338,11 @@ define(
|
||||
*/
|
||||
FixedController.prototype.subscribeToObjects = function (objects) {
|
||||
var self = this;
|
||||
var timeAPI = this.openmct.time;
|
||||
|
||||
this.subscriptions = objects.map(function (object) {
|
||||
return self.openmct.telemetry.subscribe(object, function (datum) {
|
||||
if (self.openmct.conductor.follow()) {
|
||||
if (timeAPI.clock() !== undefined) {
|
||||
self.updateView(object, datum);
|
||||
}
|
||||
}, {});
|
||||
@ -378,7 +380,7 @@ define(
|
||||
* @returns {object[]} the provided objects for chaining.
|
||||
*/
|
||||
FixedController.prototype.fetchHistoricalData = function (objects) {
|
||||
var bounds = this.openmct.conductor.bounds();
|
||||
var bounds = this.openmct.time.bounds();
|
||||
var self = this;
|
||||
|
||||
objects.forEach(function (object) {
|
||||
|
@ -122,7 +122,7 @@ define(
|
||||
'off',
|
||||
'bounds',
|
||||
'timeSystem',
|
||||
'follow'
|
||||
'clock'
|
||||
]);
|
||||
mockConductor.bounds.andReturn({});
|
||||
mockTimeSystem = {
|
||||
@ -187,7 +187,7 @@ define(
|
||||
);
|
||||
|
||||
mockOpenMCT = {
|
||||
conductor: mockConductor,
|
||||
time: mockConductor,
|
||||
telemetry: mockTelemetryAPI,
|
||||
composition: mockCompositionAPI
|
||||
};
|
||||
@ -383,12 +383,12 @@ define(
|
||||
key: '12345'
|
||||
}
|
||||
};
|
||||
mockConductor.clock.andReturn({});
|
||||
controller.elementProxiesById = {};
|
||||
controller.elementProxiesById['12345'] = [testElement];
|
||||
controller.elementProxies = [testElement];
|
||||
|
||||
controller.subscribeToObjects([telemetryObject]);
|
||||
mockConductor.follow.andReturn(true);
|
||||
mockTelemetryAPI.subscribe.mostRecentCall.args[1](mockTelemetry);
|
||||
|
||||
waitsFor(function () {
|
||||
@ -597,7 +597,7 @@ define(
|
||||
});
|
||||
|
||||
it("requests only a single point", function () {
|
||||
mockConductor.follow.andReturn(false);
|
||||
mockConductor.clock.andReturn(undefined);
|
||||
boundsChangeCallback(testBounds);
|
||||
expect(mockTelemetryAPI.request.calls.length).toBe(2);
|
||||
|
||||
@ -607,8 +607,7 @@ define(
|
||||
});
|
||||
|
||||
it("Does not fetch historical data on tick", function () {
|
||||
mockConductor.follow.andReturn(true);
|
||||
boundsChangeCallback(testBounds);
|
||||
boundsChangeCallback(testBounds, true);
|
||||
expect(mockTelemetryAPI.request.calls.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
@ -82,7 +82,7 @@ define(
|
||||
lastRange,
|
||||
lastDomain,
|
||||
handle;
|
||||
var conductor = openmct.conductor;
|
||||
var timeAPI = openmct.time;
|
||||
|
||||
// Populate the scope with axis information (specifically, options
|
||||
// available for each axis.)
|
||||
@ -185,7 +185,7 @@ define(
|
||||
|
||||
function changeTimeOfInterest(timeOfInterest) {
|
||||
if (timeOfInterest !== undefined) {
|
||||
var bounds = conductor.bounds();
|
||||
var bounds = timeAPI.bounds();
|
||||
var range = bounds.end - bounds.start;
|
||||
$scope.toiPerc = ((timeOfInterest - bounds.start) / range) * 100;
|
||||
$scope.toiPinned = true;
|
||||
@ -208,8 +208,8 @@ define(
|
||||
);
|
||||
replot();
|
||||
|
||||
changeTimeOfInterest(conductor.timeOfInterest());
|
||||
conductor.on("timeOfInterest", changeTimeOfInterest);
|
||||
changeTimeOfInterest(timeAPI.timeOfInterest());
|
||||
timeAPI.on("timeOfInterest", changeTimeOfInterest);
|
||||
}
|
||||
|
||||
// Release the current subscription (called when scope is destroyed)
|
||||
@ -218,7 +218,7 @@ define(
|
||||
handle.unsubscribe();
|
||||
handle = undefined;
|
||||
}
|
||||
conductor.off("timeOfInterest", changeTimeOfInterest);
|
||||
timeAPI.off("timeOfInterest", changeTimeOfInterest);
|
||||
}
|
||||
|
||||
function requery() {
|
||||
@ -262,7 +262,7 @@ define(
|
||||
requery();
|
||||
}
|
||||
self.setUnsynchedStatus($scope.domainObject, follow && self.isZoomed());
|
||||
changeTimeOfInterest(conductor.timeOfInterest());
|
||||
changeTimeOfInterest(timeAPI.timeOfInterest());
|
||||
}
|
||||
|
||||
this.modeOptions = new PlotModeOptions([], subPlotFactory);
|
||||
@ -286,11 +286,11 @@ define(
|
||||
];
|
||||
|
||||
//Are some initialized bounds defined?
|
||||
var bounds = conductor.bounds();
|
||||
var bounds = timeAPI.bounds();
|
||||
if (bounds &&
|
||||
bounds.start !== undefined &&
|
||||
bounds.end !== undefined) {
|
||||
changeDisplayBounds(undefined, conductor.bounds(), conductor.follow());
|
||||
changeDisplayBounds(undefined, timeAPI.bounds(), timeAPI.clock() !== undefined);
|
||||
}
|
||||
|
||||
// Watch for changes to the selected axis
|
||||
|
@ -140,7 +140,7 @@ define(
|
||||
mockHandler,
|
||||
mockThrottle,
|
||||
undefined,
|
||||
{conductor: mockConductor}
|
||||
{time: mockConductor}
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -72,6 +72,8 @@ define(
|
||||
var added;
|
||||
var testValue;
|
||||
|
||||
this.lastBounds = bounds;
|
||||
|
||||
// If collection is not sorted by a time field, we cannot respond to
|
||||
// bounds events
|
||||
if (this.sortField === undefined) {
|
||||
@ -110,7 +112,6 @@ define(
|
||||
*/
|
||||
this.emit('added', added);
|
||||
}
|
||||
this.lastBounds = bounds;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -27,7 +27,7 @@ define(
|
||||
this.resultsHeader = this.element.find('.mct-table>thead').first();
|
||||
this.sizingTableBody = this.element.find('.sizing-table>tbody').first();
|
||||
this.$scope.sizingRow = {};
|
||||
this.conductor = openmct.conductor;
|
||||
this.timeApi = openmct.time;
|
||||
this.toiFormatter = undefined;
|
||||
this.formatService = formatService;
|
||||
this.callbacks = {};
|
||||
@ -65,6 +65,7 @@ define(
|
||||
this.scrollable.on('scroll', this.onScroll);
|
||||
|
||||
$scope.visibleRows = [];
|
||||
$scope.displayRows = [];
|
||||
|
||||
/**
|
||||
* Set default values for optional parameters on a given scope
|
||||
@ -113,7 +114,7 @@ define(
|
||||
$scope.sortDirection = 'asc';
|
||||
}
|
||||
self.setRows($scope.rows);
|
||||
self.setTimeOfInterestRow(self.conductor.timeOfInterest());
|
||||
self.setTimeOfInterestRow(self.timeApi.timeOfInterest());
|
||||
};
|
||||
|
||||
/*
|
||||
@ -159,13 +160,13 @@ define(
|
||||
if (timeColumns) {
|
||||
this.destroyConductorListeners();
|
||||
|
||||
this.conductor.on('timeSystem', this.changeTimeSystem);
|
||||
this.conductor.on('timeOfInterest', this.changeTimeOfInterest);
|
||||
this.conductor.on('bounds', this.changeBounds);
|
||||
this.timeApi.on('timeSystem', this.changeTimeSystem);
|
||||
this.timeApi.on('timeOfInterest', this.changeTimeOfInterest);
|
||||
this.timeApi.on('bounds', this.changeBounds);
|
||||
|
||||
// If time system defined, set initially
|
||||
if (this.conductor.timeSystem()) {
|
||||
this.changeTimeSystem(this.conductor.timeSystem());
|
||||
if (this.timeApi.timeSystem() !== undefined) {
|
||||
this.changeTimeSystem(this.timeApi.timeSystem());
|
||||
}
|
||||
}
|
||||
}.bind(this));
|
||||
@ -182,13 +183,13 @@ define(
|
||||
}
|
||||
|
||||
MCTTableController.prototype.destroyConductorListeners = function () {
|
||||
this.conductor.off('timeSystem', this.changeTimeSystem);
|
||||
this.conductor.off('timeOfInterest', this.changeTimeOfInterest);
|
||||
this.conductor.off('bounds', this.changeBounds);
|
||||
this.timeApi.off('timeSystem', this.changeTimeSystem);
|
||||
this.timeApi.off('timeOfInterest', this.changeTimeOfInterest);
|
||||
this.timeApi.off('bounds', this.changeBounds);
|
||||
};
|
||||
|
||||
MCTTableController.prototype.changeTimeSystem = function () {
|
||||
var format = this.conductor.timeSystem().formats()[0];
|
||||
MCTTableController.prototype.changeTimeSystem = function (timeSystem) {
|
||||
var format = timeSystem.timeFormat;
|
||||
this.toiFormatter = this.formatService.getFormat(format);
|
||||
};
|
||||
|
||||
@ -220,7 +221,7 @@ define(
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
var toi = this.conductor.timeOfInterest();
|
||||
var toi = this.timeApi.timeOfInterest();
|
||||
if (toi !== -1) {
|
||||
this.setTimeOfInterestRow(toi);
|
||||
}
|
||||
@ -681,7 +682,7 @@ define(
|
||||
// perform DOM changes, otherwise scrollTo won't work.
|
||||
.then(function () {
|
||||
//If TOI specified, scroll to it
|
||||
var timeOfInterest = this.conductor.timeOfInterest();
|
||||
var timeOfInterest = this.timeApi.timeOfInterest();
|
||||
if (timeOfInterest) {
|
||||
this.setTimeOfInterestRow(timeOfInterest);
|
||||
this.scrollToRow(this.$scope.toiRowIndex);
|
||||
@ -779,7 +780,7 @@ define(
|
||||
* @param bounds
|
||||
*/
|
||||
MCTTableController.prototype.changeBounds = function (bounds) {
|
||||
this.setTimeOfInterestRow(this.conductor.timeOfInterest());
|
||||
this.setTimeOfInterestRow(this.timeApi.timeOfInterest());
|
||||
if (this.$scope.toiRowIndex !== -1) {
|
||||
this.scrollToRow(this.$scope.toiRowIndex);
|
||||
}
|
||||
@ -794,7 +795,7 @@ define(
|
||||
if (selectedTime &&
|
||||
this.toiFormatter.validate(selectedTime) &&
|
||||
event.altKey) {
|
||||
this.conductor.timeOfInterest(this.toiFormatter.parse(selectedTime));
|
||||
this.timeApi.timeOfInterest(this.toiFormatter.parse(selectedTime));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -64,9 +64,12 @@ define(
|
||||
$scope.rows = [];
|
||||
this.table = new TableConfiguration($scope.domainObject,
|
||||
openmct);
|
||||
this.lastBounds = this.openmct.conductor.bounds();
|
||||
this.lastBounds = this.openmct.time.bounds();
|
||||
this.lastRequestTime = 0;
|
||||
this.telemetry = new TelemetryCollection();
|
||||
if (this.lastBounds) {
|
||||
this.telemetry.bounds(this.lastBounds);
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a new format object from legacy object, and replace it
|
||||
@ -82,7 +85,7 @@ define(
|
||||
'getHistoricalData',
|
||||
'subscribeToNewData',
|
||||
'changeBounds',
|
||||
'setScroll',
|
||||
'setClock',
|
||||
'addRowsToTable',
|
||||
'removeRowsFromTable'
|
||||
]);
|
||||
@ -95,7 +98,7 @@ define(
|
||||
this.registerChangeListeners();
|
||||
}.bind(this));
|
||||
|
||||
this.setScroll(this.openmct.conductor.follow());
|
||||
this.setClock(this.openmct.time.clock());
|
||||
|
||||
this.$scope.$on("$destroy", this.destroy);
|
||||
}
|
||||
@ -104,8 +107,8 @@ define(
|
||||
* @private
|
||||
* @param {boolean} scroll
|
||||
*/
|
||||
TelemetryTableController.prototype.setScroll = function (scroll) {
|
||||
this.$scope.autoScroll = scroll;
|
||||
TelemetryTableController.prototype.setClock = function (clock) {
|
||||
this.$scope.autoScroll = clock !== undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -120,9 +123,9 @@ define(
|
||||
var sortColumn;
|
||||
scope.defaultSort = undefined;
|
||||
|
||||
if (timeSystem) {
|
||||
if (timeSystem !== undefined) {
|
||||
this.table.columns.forEach(function (column) {
|
||||
if (column.getKey() === timeSystem.metadata.key) {
|
||||
if (column.getKey() === timeSystem.key) {
|
||||
sortColumn = column;
|
||||
}
|
||||
});
|
||||
@ -151,9 +154,9 @@ define(
|
||||
}.bind(this)
|
||||
);
|
||||
|
||||
this.openmct.conductor.on('timeSystem', this.sortByTimeSystem);
|
||||
this.openmct.conductor.on('bounds', this.changeBounds);
|
||||
this.openmct.conductor.on('follow', this.setScroll);
|
||||
this.openmct.time.on('timeSystem', this.sortByTimeSystem);
|
||||
this.openmct.time.on('bounds', this.changeBounds);
|
||||
this.openmct.time.on('clock', this.setClock);
|
||||
|
||||
this.telemetry.on('added', this.addRowsToTable);
|
||||
this.telemetry.on('discarded', this.removeRowsFromTable);
|
||||
@ -187,12 +190,7 @@ define(
|
||||
* will be removed from the table.
|
||||
* @param {openmct.TimeConductorBounds~TimeConductorBounds} bounds
|
||||
*/
|
||||
TelemetryTableController.prototype.changeBounds = function (bounds) {
|
||||
var follow = this.openmct.conductor.follow();
|
||||
var isTick = follow &&
|
||||
bounds.start !== this.lastBounds.start &&
|
||||
bounds.end !== this.lastBounds.end;
|
||||
|
||||
TelemetryTableController.prototype.changeBounds = function (bounds, isTick) {
|
||||
if (isTick) {
|
||||
this.telemetry.bounds(bounds);
|
||||
} else {
|
||||
@ -207,9 +205,9 @@ define(
|
||||
*/
|
||||
TelemetryTableController.prototype.destroy = function () {
|
||||
|
||||
this.openmct.conductor.off('timeSystem', this.sortByTimeSystem);
|
||||
this.openmct.conductor.off('bounds', this.changeBounds);
|
||||
this.openmct.conductor.off('follow', this.setScroll);
|
||||
this.openmct.time.off('timeSystem', this.sortByTimeSystem);
|
||||
this.openmct.time.off('bounds', this.changeBounds);
|
||||
this.openmct.time.off('clock', this.setClock);
|
||||
|
||||
this.subscriptions.forEach(function (subscription) {
|
||||
subscription();
|
||||
@ -260,8 +258,8 @@ define(
|
||||
// if data matches selected time system
|
||||
this.telemetry.sort(undefined);
|
||||
|
||||
var timeSystem = this.openmct.conductor.timeSystem();
|
||||
if (timeSystem) {
|
||||
var timeSystem = this.openmct.time.timeSystem();
|
||||
if (timeSystem !== undefined) {
|
||||
this.sortByTimeSystem(timeSystem);
|
||||
}
|
||||
|
||||
@ -278,7 +276,7 @@ define(
|
||||
TelemetryTableController.prototype.getHistoricalData = function (objects) {
|
||||
var self = this;
|
||||
var openmct = this.openmct;
|
||||
var bounds = openmct.conductor.bounds();
|
||||
var bounds = openmct.time.bounds();
|
||||
var scope = this.$scope;
|
||||
var rowData = [];
|
||||
var processedObjects = 0;
|
||||
@ -432,7 +430,7 @@ define(
|
||||
var scope = this.$scope;
|
||||
|
||||
this.telemetry.clear();
|
||||
this.telemetry.bounds(this.openmct.conductor.bounds());
|
||||
this.telemetry.bounds(this.openmct.time.bounds());
|
||||
|
||||
this.$scope.loading = true;
|
||||
|
||||
|
@ -99,7 +99,7 @@ define(
|
||||
mockElement,
|
||||
mockExportService,
|
||||
mockFormatService,
|
||||
{conductor: mockConductor}
|
||||
{time: mockConductor}
|
||||
);
|
||||
spyOn(controller, 'setVisibleRows').andCallThrough();
|
||||
});
|
||||
|
@ -55,13 +55,13 @@ define(
|
||||
};
|
||||
mockConductor = jasmine.createSpyObj("conductor", [
|
||||
"bounds",
|
||||
"follow",
|
||||
"clock",
|
||||
"on",
|
||||
"off",
|
||||
"timeSystem"
|
||||
]);
|
||||
mockConductor.bounds.andReturn(mockBounds);
|
||||
mockConductor.follow.andReturn(false);
|
||||
mockConductor.clock.andReturn(undefined);
|
||||
|
||||
mockDomainObject = jasmine.createSpyObj("domainObject", [
|
||||
"getModel",
|
||||
@ -124,7 +124,7 @@ define(
|
||||
mockTimeout.cancel = jasmine.createSpy("cancel");
|
||||
|
||||
mockAPI = {
|
||||
conductor: mockConductor,
|
||||
time: mockConductor,
|
||||
objects: mockObjectAPI,
|
||||
telemetry: mockTelemetryAPI,
|
||||
composition: mockCompositionAPI
|
||||
@ -145,21 +145,21 @@ define(
|
||||
it('conductor changes', function () {
|
||||
expect(mockConductor.on).toHaveBeenCalledWith("timeSystem", jasmine.any(Function));
|
||||
expect(mockConductor.on).toHaveBeenCalledWith("bounds", jasmine.any(Function));
|
||||
expect(mockConductor.on).toHaveBeenCalledWith("follow", jasmine.any(Function));
|
||||
expect(mockConductor.on).toHaveBeenCalledWith("clock", jasmine.any(Function));
|
||||
});
|
||||
});
|
||||
|
||||
describe('deregisters all listeners on scope destruction', function () {
|
||||
var timeSystemListener,
|
||||
boundsListener,
|
||||
followListener;
|
||||
clockListener;
|
||||
|
||||
beforeEach(function () {
|
||||
controller.registerChangeListeners();
|
||||
|
||||
timeSystemListener = getCallback(mockConductor.on, "timeSystem");
|
||||
boundsListener = getCallback(mockConductor.on, "bounds");
|
||||
followListener = getCallback(mockConductor.on, "follow");
|
||||
clockListener = getCallback(mockConductor.on, "clock");
|
||||
|
||||
var destroy = getCallback(mockScope.$on, "$destroy");
|
||||
destroy();
|
||||
@ -171,7 +171,7 @@ define(
|
||||
it('conductor changes', function () {
|
||||
expect(mockConductor.off).toHaveBeenCalledWith("timeSystem", timeSystemListener);
|
||||
expect(mockConductor.off).toHaveBeenCalledWith("bounds", boundsListener);
|
||||
expect(mockConductor.off).toHaveBeenCalledWith("follow", followListener);
|
||||
expect(mockConductor.off).toHaveBeenCalledWith("clock", clockListener);
|
||||
});
|
||||
});
|
||||
|
||||
@ -321,12 +321,12 @@ define(
|
||||
it('When in real-time mode, enables auto-scroll', function () {
|
||||
controller.registerChangeListeners();
|
||||
|
||||
var followCallback = getCallback(mockConductor.on, "follow");
|
||||
var clockCallback = getCallback(mockConductor.on, "clock");
|
||||
//Confirm pre-condition
|
||||
expect(mockScope.autoScroll).toBeFalsy();
|
||||
|
||||
//Mock setting the conductor to 'follow' mode
|
||||
followCallback(true);
|
||||
//Mock setting the a clock in the Time API
|
||||
clockCallback({});
|
||||
expect(mockScope.autoScroll).toBe(true);
|
||||
});
|
||||
|
||||
@ -357,9 +357,7 @@ define(
|
||||
}];
|
||||
|
||||
mockTimeSystem = {
|
||||
metadata: {
|
||||
key: "column1"
|
||||
}
|
||||
key: "column1"
|
||||
};
|
||||
|
||||
mockTelemetryAPI.commonValuesForHints.andCallFake(function (metadata, hints) {
|
||||
|
@ -163,15 +163,15 @@ define(
|
||||
}
|
||||
|
||||
if (request.start === undefined && request.end === undefined) {
|
||||
bounds = this.openmct.conductor.bounds();
|
||||
bounds = this.openmct.time.bounds();
|
||||
fullRequest.start = bounds.start;
|
||||
fullRequest.end = bounds.end;
|
||||
}
|
||||
|
||||
if (request.domain === undefined) {
|
||||
timeSystem = this.openmct.conductor.timeSystem();
|
||||
timeSystem = this.openmct.time.timeSystem();
|
||||
if (timeSystem !== undefined) {
|
||||
fullRequest.domain = timeSystem.metadata.key;
|
||||
fullRequest.domain = timeSystem.key;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -98,7 +98,7 @@ define(
|
||||
|
||||
mockAPI = {
|
||||
telemetry: mockTelemetryAPI,
|
||||
conductor: {
|
||||
time: {
|
||||
bounds: function () {
|
||||
return {
|
||||
start: 0,
|
||||
@ -107,9 +107,7 @@ define(
|
||||
},
|
||||
timeSystem: function () {
|
||||
return {
|
||||
metadata: {
|
||||
key: 'mockTimeSystem'
|
||||
}
|
||||
key: 'mockTimeSystem'
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -289,7 +287,7 @@ define(
|
||||
|
||||
it("applies time conductor bounds if request bounds not defined", function () {
|
||||
var fullRequest = telemetry.buildRequest({});
|
||||
var mockBounds = mockAPI.conductor.bounds();
|
||||
var mockBounds = mockAPI.time.bounds();
|
||||
|
||||
expect(fullRequest.start).toBe(mockBounds.start);
|
||||
expect(fullRequest.end).toBe(mockBounds.end);
|
||||
@ -302,8 +300,8 @@ define(
|
||||
|
||||
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);
|
||||
var mockTimeSystem = mockAPI.time.timeSystem();
|
||||
expect(fullRequest.domain).toBe(mockTimeSystem.key);
|
||||
|
||||
fullRequest = telemetry.buildRequest({domain: 'someOtherDomain'});
|
||||
expect(fullRequest.domain).toBe('someOtherDomain');
|
||||
|
@ -83,7 +83,7 @@ define([
|
||||
* @memberof module:openmct.MCT#
|
||||
* @name conductor
|
||||
*/
|
||||
this.conductor = new api.TimeConductor();
|
||||
this.time = new api.TimeAPI();
|
||||
|
||||
/**
|
||||
* An interface for interacting with the composition of domain objects.
|
||||
|
@ -1,205 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2017, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define(['EventEmitter'], function (EventEmitter) {
|
||||
|
||||
/**
|
||||
* The public API for setting and querying time conductor state. The
|
||||
* time conductor is the means by which the temporal bounds of a view
|
||||
* are controlled. Time-sensitive views will typically respond to
|
||||
* changes to bounds or other properties of the time conductor and
|
||||
* update the data displayed based on the time conductor state.
|
||||
*
|
||||
* The TimeConductor extends the EventEmitter class. A number of events are
|
||||
* fired when properties of the time conductor change, which are
|
||||
* documented below.
|
||||
* @interface
|
||||
* @memberof module:openmct
|
||||
*/
|
||||
function TimeConductor() {
|
||||
EventEmitter.call(this);
|
||||
|
||||
//The Time System
|
||||
this.system = undefined;
|
||||
//The Time Of Interest
|
||||
this.toi = undefined;
|
||||
|
||||
this.boundsVal = {
|
||||
start: undefined,
|
||||
end: undefined
|
||||
};
|
||||
|
||||
//Default to fixed mode
|
||||
this.followMode = false;
|
||||
}
|
||||
|
||||
TimeConductor.prototype = Object.create(EventEmitter.prototype);
|
||||
|
||||
/**
|
||||
* Validate the given bounds. This can be used for pre-validation of
|
||||
* bounds, for example by views validating user inputs.
|
||||
* @param bounds The start and end time of the conductor.
|
||||
* @returns {string | true} A validation error, or true if valid
|
||||
* @memberof module:openmct.TimeConductor#
|
||||
* @method validateBounds
|
||||
*/
|
||||
TimeConductor.prototype.validateBounds = function (bounds) {
|
||||
if ((bounds.start === undefined) ||
|
||||
(bounds.end === undefined) ||
|
||||
isNaN(bounds.start) ||
|
||||
isNaN(bounds.end)
|
||||
) {
|
||||
return "Start and end must be specified as integer values";
|
||||
} else if (bounds.start > bounds.end) {
|
||||
return "Specified start date exceeds end bound";
|
||||
}
|
||||
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
|
||||
* @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.
|
||||
* @memberof module:openmct.TimeConductor~
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get or set the start and end time of the time conductor. Basic validation
|
||||
* of bounds is performed.
|
||||
*
|
||||
* @param {module:openmct.TimeConductorBounds~TimeConductorBounds} newBounds
|
||||
* @throws {Error} Validation error
|
||||
* @fires module:openmct.TimeConductor~bounds
|
||||
* @returns {module:openmct.TimeConductorBounds~TimeConductorBounds}
|
||||
* @memberof module:openmct.TimeConductor#
|
||||
* @method bounds
|
||||
*/
|
||||
TimeConductor.prototype.bounds = function (newBounds) {
|
||||
if (arguments.length > 0) {
|
||||
var validationResult = this.validateBounds(newBounds);
|
||||
if (validationResult !== true) {
|
||||
throw new Error(validationResult);
|
||||
}
|
||||
//Create a copy to avoid direct mutation of conductor bounds
|
||||
this.boundsVal = JSON.parse(JSON.stringify(newBounds));
|
||||
/**
|
||||
* The start time, end time, or both have been updated.
|
||||
* @event bounds
|
||||
* @memberof module:openmct.TimeConductor~
|
||||
* @property {TimeConductorBounds} bounds
|
||||
*/
|
||||
this.emit('bounds', this.boundsVal);
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
//Return a copy to prevent direct mutation of time conductor bounds.
|
||||
return JSON.parse(JSON.stringify(this.boundsVal));
|
||||
};
|
||||
|
||||
/**
|
||||
* Get or set the time system of the TimeConductor. Time systems determine
|
||||
* units, epoch, and other aspects of time representation. When changing
|
||||
* the time system in use, new valid bounds must also be provided.
|
||||
* @param {TimeSystem} newTimeSystem
|
||||
* @param {module:openmct.TimeConductor~TimeConductorBounds} bounds
|
||||
* @fires module:openmct.TimeConductor~timeSystem
|
||||
* @returns {TimeSystem} The currently applied time system
|
||||
* @memberof module:openmct.TimeConductor#
|
||||
* @method timeSystem
|
||||
*/
|
||||
TimeConductor.prototype.timeSystem = function (newTimeSystem, bounds) {
|
||||
if (arguments.length >= 2) {
|
||||
this.system = newTimeSystem;
|
||||
/**
|
||||
* The time system used by the time
|
||||
* conductor has changed. A change in Time System will always be
|
||||
* followed by a bounds event specifying new query bounds.
|
||||
*
|
||||
* @event module:openmct.TimeConductor~timeSystem
|
||||
* @property {TimeSystem} The value of the currently applied
|
||||
* Time System
|
||||
* */
|
||||
this.emit('timeSystem', this.system);
|
||||
this.bounds(bounds);
|
||||
} else if (arguments.length === 1) {
|
||||
throw new Error('Must set bounds when changing time system');
|
||||
}
|
||||
return this.system;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get or set the Time of Interest. The Time of Interest is the temporal
|
||||
* 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
|
||||
* effectively be unset by assigning a value of 'undefined'.
|
||||
* @fires module:openmct.TimeConductor~timeOfInterest
|
||||
* @param newTOI
|
||||
* @returns {number} the current time of interest
|
||||
* @memberof module:openmct.TimeConductor#
|
||||
* @method timeOfInterest
|
||||
*/
|
||||
TimeConductor.prototype.timeOfInterest = function (newTOI) {
|
||||
if (arguments.length > 0) {
|
||||
this.toi = newTOI;
|
||||
/**
|
||||
* The Time of Interest has moved.
|
||||
* @event timeOfInterest
|
||||
* @memberof module:openmct.TimeConductor~
|
||||
* @property {number} Current time of interest
|
||||
*/
|
||||
this.emit('timeOfInterest', this.toi);
|
||||
}
|
||||
return this.toi;
|
||||
};
|
||||
|
||||
return TimeConductor;
|
||||
});
|
@ -1,122 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2017, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define(['./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([
|
||||
'./TimeConductor',
|
||||
'./time/TimeAPI',
|
||||
'./objects/ObjectAPI',
|
||||
'./composition/CompositionAPI',
|
||||
'./types/TypeRegistry',
|
||||
@ -29,7 +29,7 @@ define([
|
||||
'./ui/GestureAPI',
|
||||
'./telemetry/TelemetryAPI'
|
||||
], function (
|
||||
TimeConductor,
|
||||
TimeAPI,
|
||||
ObjectAPI,
|
||||
CompositionAPI,
|
||||
TypeRegistry,
|
||||
@ -38,7 +38,7 @@ define([
|
||||
TelemetryAPI
|
||||
) {
|
||||
return {
|
||||
TimeConductor: TimeConductor,
|
||||
TimeAPI: TimeAPI,
|
||||
ObjectAPI: ObjectAPI,
|
||||
CompositionAPI: CompositionAPI,
|
||||
Dialog: Dialog,
|
||||
|
@ -316,6 +316,19 @@ define([
|
||||
return this.formatMapCache.get(metadata);
|
||||
};
|
||||
|
||||
/**
|
||||
* Register a new telemetry data formatter.
|
||||
* @param {Format} format the
|
||||
*/
|
||||
TelemetryAPI.prototype.addFormat = function (format) {
|
||||
this.MCT.legacyExtension('formats', {
|
||||
key: format.key,
|
||||
implementation: function () {
|
||||
return format;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a limit evaluator for this domain object.
|
||||
* Limit Evaluators help you evaluate limit and alarm status of individual telemetry datums for display purposes without having to interact directly with the Limit API.
|
||||
|
452
src/api/time/TimeAPI.js
Normal file
452
src/api/time/TimeAPI.js
Normal file
@ -0,0 +1,452 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2017, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define(['EventEmitter'], function (EventEmitter) {
|
||||
|
||||
/**
|
||||
* The public API for setting and querying the temporal state of the
|
||||
* application. The concept of time is integral to Open MCT, and at least
|
||||
* one {@link TimeSystem}, as well as some default time bounds must be
|
||||
* registered and enabled via {@link TimeAPI.addTimeSystem} and
|
||||
* {@link TimeAPI.timeSystem} respectively for Open MCT to work.
|
||||
*
|
||||
* Time-sensitive views will typically respond to changes to bounds or other
|
||||
* properties of the time conductor and update the data displayed based on
|
||||
* the temporal state of the application. The current time bounds are also
|
||||
* used in queries for historical data.
|
||||
*
|
||||
* The TimeAPI extends the EventEmitter class. A number of events are
|
||||
* fired when properties of the time conductor change, which are documented
|
||||
* below.
|
||||
*
|
||||
* @interface
|
||||
* @memberof module:openmct
|
||||
*/
|
||||
function TimeAPI() {
|
||||
EventEmitter.call(this);
|
||||
|
||||
//The Time System
|
||||
this.system = undefined;
|
||||
//The Time Of Interest
|
||||
this.toi = undefined;
|
||||
|
||||
this.boundsVal = {
|
||||
start: undefined,
|
||||
end: undefined
|
||||
};
|
||||
|
||||
this.timeSystems = new Map();
|
||||
this.clocks = new Map();
|
||||
this.activeClock = undefined;
|
||||
this.offsets = undefined;
|
||||
|
||||
this.tick = this.tick.bind(this);
|
||||
|
||||
}
|
||||
|
||||
TimeAPI.prototype = Object.create(EventEmitter.prototype);
|
||||
|
||||
/**
|
||||
* A TimeSystem provides meaning to the values returned by the TimeAPI. Open
|
||||
* MCT supports multiple different types of time values, although all are
|
||||
* intrinsically represented by numbers, the meaning of those numbers can
|
||||
* differ depending on context.
|
||||
*
|
||||
* A default time system is provided by Open MCT in the form of the {@link UTCTimeSystem},
|
||||
* which represents integer values as ms in the Unix epoch. An example of
|
||||
* another time system might be "sols" for a Martian mission. TimeSystems do
|
||||
* not address the issue of converting between time systems.
|
||||
*
|
||||
* @typedef {object} TimeSystem
|
||||
* @property {string} key A unique identifier
|
||||
* @property {string} name A human-readable descriptor
|
||||
* @property {string} [cssClass] Specify a css class defining an icon for
|
||||
* this time system. This will be visible next to the time system in the
|
||||
* menu in the Time Conductor
|
||||
* @property {string} timeFormat The key of a format to use when displaying
|
||||
* discrete timestamps from this time system
|
||||
* @property {string} [durationFormat] The key of a format to use when
|
||||
* displaying a duration or relative span of time in this time system.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Register a new time system. Once registered it can activated using
|
||||
* {@link TimeAPI.timeSystem}, and can be referenced via its key in [Time Conductor configuration](@link https://github.com/nasa/openmct/blob/master/API.md#time-conductor).
|
||||
* @memberof module:openmct.TimeAPI#
|
||||
* @param {TimeSystem} timeSystem A time system object.
|
||||
*/
|
||||
TimeAPI.prototype.addTimeSystem = function (timeSystem) {
|
||||
this.timeSystems.set(timeSystem.key, timeSystem);
|
||||
};
|
||||
|
||||
/**
|
||||
* @returns {TimeSystem[]}
|
||||
*/
|
||||
TimeAPI.prototype.getAllTimeSystems = function () {
|
||||
return Array.from(this.timeSystems.values());
|
||||
};
|
||||
|
||||
/**
|
||||
* Clocks provide a timing source that is used to
|
||||
* automatically update the time bounds of the data displayed in Open MCT.
|
||||
*
|
||||
* @typedef {object} Clock
|
||||
* @memberof openmct.timeAPI
|
||||
* @property {string} key A unique identifier
|
||||
* @property {string} name A human-readable name. The name will be used to
|
||||
* represent this clock in the Time Conductor UI
|
||||
* @property {string} description A longer description, ideally identifying
|
||||
* what the clock ticks on.
|
||||
* @property {function} currentValue Returns the last value generated by a tick, or a default value
|
||||
* if no ticking has yet occurred
|
||||
* @see {LocalClock}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Register a new Clock.
|
||||
* @memberof module:openmct.TimeAPI#
|
||||
* @param {Clock} clock
|
||||
*/
|
||||
TimeAPI.prototype.addClock = function (clock) {
|
||||
this.clocks.set(clock.key, clock);
|
||||
};
|
||||
|
||||
/**
|
||||
* @memberof module:openmct.TimeAPI#
|
||||
* @returns {Clock[]}
|
||||
* @memberof module:openmct.TimeAPI#
|
||||
*/
|
||||
TimeAPI.prototype.getAllClocks = function () {
|
||||
return Array.from(this.clocks.values());
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate the given bounds. This can be used for pre-validation of bounds,
|
||||
* for example by views validating user inputs.
|
||||
* @param {TimeBounds} bounds The start and end time of the conductor.
|
||||
* @returns {string | true} A validation error, or true if valid
|
||||
* @memberof module:openmct.TimeAPI#
|
||||
* @method validateBounds
|
||||
*/
|
||||
TimeAPI.prototype.validateBounds = function (bounds) {
|
||||
if ((bounds.start === undefined) ||
|
||||
(bounds.end === undefined) ||
|
||||
isNaN(bounds.start) ||
|
||||
isNaN(bounds.end)
|
||||
) {
|
||||
return "Start and end must be specified as integer values";
|
||||
} else if (bounds.start > bounds.end) {
|
||||
return "Specified start date exceeds end bound";
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate the given offsets. This can be used for pre-validation of
|
||||
* offsets, for example by views validating user inputs.
|
||||
* @param {ClockOffsets} offsets The start and end offsets from a 'now' value.
|
||||
* @returns {string | true} A validation error, or true if valid
|
||||
* @memberof module:openmct.TimeAPI#
|
||||
* @method validateBounds
|
||||
*/
|
||||
TimeAPI.prototype.validateOffsets = function (offsets) {
|
||||
if ((offsets.start === undefined) ||
|
||||
(offsets.end === undefined) ||
|
||||
isNaN(offsets.start) ||
|
||||
isNaN(offsets.end)
|
||||
) {
|
||||
return "Start and end offsets must be specified as integer values";
|
||||
} else if (offsets.start >= offsets.end) {
|
||||
return "Specified start offset must be < end offset";
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {Object} TimeBounds
|
||||
* @property {number} start The start time displayed by the time conductor
|
||||
* in ms since epoch. Epoch determined by currently active time system
|
||||
* @property {number} end The end time displayed by the time conductor in ms
|
||||
* since epoch.
|
||||
* @memberof module:openmct.TimeAPI~
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get or set the start and end time of the time conductor. Basic validation
|
||||
* of bounds is performed.
|
||||
*
|
||||
* @param {module:openmct.TimeAPI~TimeConductorBounds} newBounds
|
||||
* @throws {Error} Validation error
|
||||
* @fires module:openmct.TimeAPI~bounds
|
||||
* @returns {module:openmct.TimeAPI~TimeConductorBounds}
|
||||
* @memberof module:openmct.TimeAPI#
|
||||
* @method bounds
|
||||
*/
|
||||
TimeAPI.prototype.bounds = function (newBounds) {
|
||||
if (arguments.length > 0) {
|
||||
var validationResult = this.validateBounds(newBounds);
|
||||
if (validationResult !== true) {
|
||||
throw new Error(validationResult);
|
||||
}
|
||||
//Create a copy to avoid direct mutation of conductor bounds
|
||||
this.boundsVal = JSON.parse(JSON.stringify(newBounds));
|
||||
/**
|
||||
* The start time, end time, or both have been updated.
|
||||
* @event bounds
|
||||
* @memberof module:openmct.TimeAPI~
|
||||
* @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, false);
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
//Return a copy to prevent direct mutation of time conductor bounds.
|
||||
return JSON.parse(JSON.stringify(this.boundsVal));
|
||||
};
|
||||
|
||||
/**
|
||||
* Get or set the time system of the TimeAPI.
|
||||
* @param {TimeSystem | string} timeSystem
|
||||
* @param {module:openmct.TimeAPI~TimeConductorBounds} bounds
|
||||
* @fires module:openmct.TimeAPI~timeSystem
|
||||
* @returns {TimeSystem} The currently applied time system
|
||||
* @memberof module:openmct.TimeAPI#
|
||||
* @method timeSystem
|
||||
*/
|
||||
TimeAPI.prototype.timeSystem = function (timeSystemOrKey, bounds) {
|
||||
if (arguments.length >= 2) {
|
||||
var timeSystem;
|
||||
|
||||
if (timeSystemOrKey === undefined) {
|
||||
throw "Please provide a time system";
|
||||
}
|
||||
|
||||
if (typeof timeSystemOrKey === 'string') {
|
||||
timeSystem = this.timeSystems.get(timeSystemOrKey);
|
||||
|
||||
if (timeSystem === undefined) {
|
||||
throw "Unknown time system " + timeSystemOrKey + ". Has it been registered with 'addTimeSystem'?";
|
||||
}
|
||||
} else if (typeof timeSystemOrKey === 'object') {
|
||||
timeSystem = timeSystemOrKey;
|
||||
|
||||
if (!this.timeSystems.has(timeSystem.key)) {
|
||||
throw "Unknown time system " + timeSystem.key + ". Has it been registered with 'addTimeSystem'?";
|
||||
}
|
||||
} else {
|
||||
throw "Attempt to set invalid time system in Time API. Please provide a previously registered time system object or key";
|
||||
}
|
||||
|
||||
this.system = timeSystem;
|
||||
|
||||
/**
|
||||
* The time system used by the time
|
||||
* conductor has changed. A change in Time System will always be
|
||||
* followed by a bounds event specifying new query bounds.
|
||||
*
|
||||
* @event module:openmct.TimeAPI~timeSystem
|
||||
* @property {TimeSystem} The value of the currently applied
|
||||
* Time System
|
||||
* */
|
||||
this.emit('timeSystem', this.system);
|
||||
this.bounds(bounds);
|
||||
|
||||
} else if (arguments.length === 1) {
|
||||
throw new Error('Must set bounds when changing time system');
|
||||
}
|
||||
|
||||
return this.system;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get or set the Time of Interest. The Time of Interest is a single point
|
||||
* in time, and constitutes the temporal focus of application views. It can
|
||||
* be manipulated by the user from the time conductor or from other views.
|
||||
* The time of interest can effectively be unset by assigning a value of
|
||||
* 'undefined'.
|
||||
* @fires module:openmct.TimeAPI~timeOfInterest
|
||||
* @param newTOI
|
||||
* @returns {number} the current time of interest
|
||||
* @memberof module:openmct.TimeAPI#
|
||||
* @method timeOfInterest
|
||||
*/
|
||||
TimeAPI.prototype.timeOfInterest = function (newTOI) {
|
||||
if (arguments.length > 0) {
|
||||
this.toi = newTOI;
|
||||
/**
|
||||
* The Time of Interest has moved.
|
||||
* @event timeOfInterest
|
||||
* @memberof module:openmct.TimeAPI~
|
||||
* @property {number} Current time of interest
|
||||
*/
|
||||
this.emit('timeOfInterest', this.toi);
|
||||
}
|
||||
return this.toi;
|
||||
};
|
||||
|
||||
/**
|
||||
* Update bounds based on provided time and current offsets
|
||||
* @private
|
||||
* @param {number} timestamp A time from which boudns will be calculated
|
||||
* using current offsets.
|
||||
*/
|
||||
TimeAPI.prototype.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);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the active clock. Tick source will be immediately subscribed to
|
||||
* and ticking will begin. Offsets from 'now' must also be provided. A clock
|
||||
* can be unset by calling {@link stopClock}.
|
||||
*
|
||||
* @param {Clock || string} The clock to activate, or its key
|
||||
* @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.
|
||||
* @fires module:openmct.TimeAPI~clock
|
||||
* @return {Clock} the currently active clock;
|
||||
*/
|
||||
TimeAPI.prototype.clock = function (keyOrClock, offsets) {
|
||||
if (arguments.length === 2) {
|
||||
var clock;
|
||||
|
||||
if (typeof keyOrClock === 'string') {
|
||||
clock = this.clocks.get(keyOrClock);
|
||||
if (clock === undefined) {
|
||||
throw "Unknown clock '" + keyOrClock + "'. Has it been registered with 'addClock'?";
|
||||
}
|
||||
} else if (typeof keyOrClock === 'object') {
|
||||
clock = keyOrClock;
|
||||
if (!this.clocks.has(clock.key)) {
|
||||
throw "Unknown clock '" + keyOrClock.key + "'. Has it been registered with 'addClock'?";
|
||||
}
|
||||
}
|
||||
|
||||
var previousClock = this.activeClock;
|
||||
if (previousClock !== undefined) {
|
||||
previousClock.off("tick", this.tick);
|
||||
}
|
||||
|
||||
this.activeClock = clock;
|
||||
|
||||
if (this.activeClock !== undefined) {
|
||||
this.offsets = offsets;
|
||||
this.activeClock.on("tick", this.tick);
|
||||
}
|
||||
|
||||
/**
|
||||
* The active clock has changed. Clock can be unset by calling {@link stopClock}
|
||||
* @event clock
|
||||
* @memberof module:openmct.TimeAPI~
|
||||
* @property {Clock} clock The newly activated clock, or undefined
|
||||
* if the system is no longer following a clock source
|
||||
*/
|
||||
this.emit("clock", this.activeClock);
|
||||
|
||||
} else if (arguments.length === 1) {
|
||||
throw "When setting the clock, clock offsets must also be provided";
|
||||
}
|
||||
|
||||
return this.activeClock;
|
||||
};
|
||||
|
||||
/**
|
||||
* Clock offsets are used to calculate temporal bounds when the system is
|
||||
* ticking on a clock source.
|
||||
*
|
||||
* @typedef {object} ClockOffsets
|
||||
* @property {number} start A time span relative to the current value of the
|
||||
* ticking clock, from which start bounds will be calculated. This value must
|
||||
* be < 0. When a clock is active, bounds will be calculated automatically
|
||||
* based on the value provided by the clock, and the defined clock offsets.
|
||||
* @property {number} end A time span relative to the current value of the
|
||||
* ticking clock, from which end bounds will be calculated. This value must
|
||||
* be >= 0.
|
||||
*/
|
||||
/**
|
||||
* Get or set the currently applied clock offsets. If no parameter is provided,
|
||||
* the current value will be returned. If provided, the new value will be
|
||||
* used as the new clock offsets.
|
||||
* @param {ClockOffsets} offsets
|
||||
* @returns {ClockOffsets}
|
||||
*/
|
||||
TimeAPI.prototype.clockOffsets = function (offsets) {
|
||||
if (arguments.length > 0) {
|
||||
|
||||
var validationResult = this.validateOffsets(offsets);
|
||||
if (validationResult !== true) {
|
||||
throw new Error(validationResult);
|
||||
}
|
||||
|
||||
this.offsets = offsets;
|
||||
|
||||
var currentValue = this.activeClock.currentValue();
|
||||
var newBounds = {
|
||||
start: currentValue + offsets.start,
|
||||
end: currentValue + offsets.end
|
||||
};
|
||||
|
||||
this.bounds(newBounds);
|
||||
|
||||
/**
|
||||
* Event that is triggered when clock offsets change.
|
||||
* @event clockOffsets
|
||||
* @memberof module:openmct.TimeAPI~
|
||||
* @property {ClockOffsets} clockOffsets The newly activated clock
|
||||
* offsets.
|
||||
*/
|
||||
this.emit("clockOffsets", offsets);
|
||||
}
|
||||
return this.offsets;
|
||||
};
|
||||
|
||||
/**
|
||||
* Stop the currently active clock from ticking, and unset it. This will
|
||||
* revert all views to showing a static time frame defined by the current
|
||||
* bounds.
|
||||
*/
|
||||
TimeAPI.prototype.stopClock = function () {
|
||||
if (this.activeClock) {
|
||||
this.clock(undefined, undefined);
|
||||
}
|
||||
};
|
||||
|
||||
return TimeAPI;
|
||||
});
|
206
src/api/time/TimeAPISpec.js
Normal file
206
src/api/time/TimeAPISpec.js
Normal file
@ -0,0 +1,206 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2017, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define(['./TimeAPI'], function (TimeAPI) {
|
||||
describe("The Time API", function () {
|
||||
var api,
|
||||
timeSystemKey,
|
||||
timeSystem,
|
||||
bounds,
|
||||
eventListener,
|
||||
toi;
|
||||
|
||||
beforeEach(function () {
|
||||
api = new TimeAPI();
|
||||
timeSystemKey = "timeSystemKey";
|
||||
timeSystem = {key: timeSystemKey};
|
||||
bounds = {start: 0, end: 0};
|
||||
eventListener = jasmine.createSpy("eventListener");
|
||||
toi = 111;
|
||||
});
|
||||
|
||||
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(timeSystem);
|
||||
expect(function () {
|
||||
api.timeSystem(timeSystem, bounds);
|
||||
}).not.toThrow();
|
||||
expect(api.timeSystem()).toBe(timeSystem);
|
||||
});
|
||||
|
||||
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(timeSystem);
|
||||
});
|
||||
|
||||
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("Allows the active clock to be set and unset", function () {
|
||||
expect(api.clock()).toBeUndefined();
|
||||
api.clock("mts", mockOffsets);
|
||||
expect(api.clock()).toBeDefined();
|
||||
api.stopClock();
|
||||
expect(api.clock()).toBeUndefined();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it("on tick, observes offsets, 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);
|
||||
});
|
||||
});
|
||||
});
|
@ -45,7 +45,6 @@ define([
|
||||
'../example/scratchpad/bundle',
|
||||
'../example/taxonomy/bundle',
|
||||
'../example/worker/bundle',
|
||||
'../example/localTimeSystem/bundle',
|
||||
|
||||
'../platform/commonUI/about/bundle',
|
||||
'../platform/commonUI/browse/bundle',
|
||||
@ -68,7 +67,6 @@ define([
|
||||
'../platform/features/fixed/bundle',
|
||||
'../platform/features/conductor/core/bundle',
|
||||
'../platform/features/conductor/compatibility/bundle',
|
||||
'../platform/features/conductor/utcTimeSystem/bundle',
|
||||
'../platform/features/imagery/bundle',
|
||||
'../platform/features/layout/bundle',
|
||||
'../platform/features/my-items/bundle',
|
||||
|
@ -20,24 +20,23 @@
|
||||
* 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
|
||||
* A {@link Clock} that mocks a "latest available data" type tick source.
|
||||
* This is for testing purposes only, and behaves identically to a local clock.
|
||||
* It DOES NOT tick on receipt of data.
|
||||
* @constructor
|
||||
*/
|
||||
function LADTickSource ($timeout, period) {
|
||||
LocalClock.call(this, $timeout, period);
|
||||
function LADClock(period) {
|
||||
LocalClock.call(this, period);
|
||||
|
||||
this.metadata = {
|
||||
key: 'test-lad',
|
||||
mode: 'lad',
|
||||
cssClass: 'icon-clock',
|
||||
label: 'Latest Available Data',
|
||||
name: 'Latest available data',
|
||||
description: 'Monitor real-time streaming data as it comes in. The Time Conductor and displays will automatically advance themselves based on a UTC clock.'
|
||||
};
|
||||
this.key = 'test-lad';
|
||||
this.mode = 'lad';
|
||||
this.cssClass = 'icon-database';
|
||||
this.name = 'Latest available data';
|
||||
this.description = "Updates when when new data is available";
|
||||
}
|
||||
LADTickSource.prototype = Object.create(LocalClock.prototype);
|
||||
LADClock.prototype = Object.create(LocalClock.prototype);
|
||||
|
||||
return LADTickSource;
|
||||
return LADClock;
|
||||
});
|
@ -21,20 +21,13 @@
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
"./src/UTCTimeSystem",
|
||||
"legacyRegistry"
|
||||
"./LADClock"
|
||||
], function (
|
||||
UTCTimeSystem,
|
||||
legacyRegistry
|
||||
LADClock
|
||||
) {
|
||||
legacyRegistry.register("platform/features/conductor/utcTimeSystem", {
|
||||
"extensions": {
|
||||
"timeSystems": [
|
||||
{
|
||||
"implementation": UTCTimeSystem,
|
||||
"depends": ["$timeout"]
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
return function () {
|
||||
return function (openmct) {
|
||||
openmct.time.addClock(new LADClock());
|
||||
};
|
||||
};
|
||||
});
|
@ -56,8 +56,8 @@ define([
|
||||
* the threshold required.
|
||||
* @private
|
||||
*/
|
||||
function getScaledFormat (d) {
|
||||
var m = moment.utc(d);
|
||||
function getScaledFormat(d) {
|
||||
var momentified = moment.utc(d);
|
||||
/**
|
||||
* Uses logic from d3 Time-Scales, v3 of the API. See
|
||||
* https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Scales.md
|
||||
@ -65,23 +65,35 @@ define([
|
||||
* Licensed
|
||||
*/
|
||||
return [
|
||||
[".SSS", function(m) { return m.milliseconds(); }],
|
||||
[":ss", function(m) { return m.seconds(); }],
|
||||
["hh:mma", function(m) { return m.minutes(); }],
|
||||
["hha", function(m) { return m.hours(); }],
|
||||
["ddd DD", function(m) {
|
||||
return m.days() &&
|
||||
m.date() != 1;
|
||||
[".SSS", function (m) {
|
||||
return m.milliseconds();
|
||||
}],
|
||||
["MMM DD", function(m) { return m.date() != 1; }],
|
||||
["MMMM", function(m) {
|
||||
[":ss", function (m) {
|
||||
return m.seconds();
|
||||
}],
|
||||
["hh:mma", function (m) {
|
||||
return m.minutes();
|
||||
}],
|
||||
["hha", function (m) {
|
||||
return m.hours();
|
||||
}],
|
||||
["ddd DD", function (m) {
|
||||
return m.days() &&
|
||||
m.date() !== 1;
|
||||
}],
|
||||
["MMM DD", function (m) {
|
||||
return m.date() !== 1;
|
||||
}],
|
||||
["MMMM", function (m) {
|
||||
return m.month();
|
||||
}],
|
||||
["YYYY", function() { return true; }]
|
||||
].filter(function (row){
|
||||
return row[1](m);
|
||||
["YYYY", function () {
|
||||
return true;
|
||||
}]
|
||||
].filter(function (row) {
|
||||
return row[1](momentified);
|
||||
})[0][0];
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
@ -91,7 +103,7 @@ define([
|
||||
* @returns {string} the formatted date
|
||||
*/
|
||||
LocalTimeFormat.prototype.format = function (value, scale) {
|
||||
if (scale !== undefined){
|
||||
if (scale !== undefined) {
|
||||
var scaledFormat = getScaledFormat(value, scale);
|
||||
if (scaledFormat) {
|
||||
return moment.utc(value).format(scaledFormat);
|
@ -21,27 +21,28 @@
|
||||
*****************************************************************************/
|
||||
|
||||
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
|
||||
* This time system supports UTC dates and provides a ticking clock source.
|
||||
* @implements TimeSystem
|
||||
* @constructor
|
||||
*/
|
||||
function TickSource() {
|
||||
this.listeners = [];
|
||||
function LocalTimeSystem() {
|
||||
|
||||
/**
|
||||
* Some metadata, which will be used to identify the time system in
|
||||
* the UI
|
||||
* @type {{key: string, name: string, glyph: string}}
|
||||
*/
|
||||
this.key = 'local';
|
||||
this.name = 'Local';
|
||||
this.cssClass = 'icon-clock';
|
||||
|
||||
this.timeFormat = 'local-format';
|
||||
this.durationFormat = 'duration';
|
||||
|
||||
this.isUTCBased = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
return LocalTimeSystem;
|
||||
});
|
@ -21,28 +21,20 @@
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
"./src/LocalTimeSystem",
|
||||
"./src/LocalTimeFormat",
|
||||
'legacyRegistry'
|
||||
"./LocalTimeSystem",
|
||||
"./LocalTimeFormat"
|
||||
], function (
|
||||
LocalTimeSystem,
|
||||
LocalTimeFormat,
|
||||
legacyRegistry
|
||||
LocalTimeFormat
|
||||
) {
|
||||
legacyRegistry.register("example/localTimeSystem", {
|
||||
"extensions": {
|
||||
"formats": [
|
||||
{
|
||||
"key": "local-format",
|
||||
"implementation": LocalTimeFormat
|
||||
}
|
||||
],
|
||||
"timeSystems": [
|
||||
{
|
||||
"implementation": LocalTimeSystem,
|
||||
"depends": ["$timeout"]
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
return function () {
|
||||
return function (openmct) {
|
||||
openmct.time.addTimeSystem(new LocalTimeSystem());
|
||||
|
||||
openmct.legacyExtension('formats', {
|
||||
key: 'local-format',
|
||||
implementation: LocalTimeFormat
|
||||
});
|
||||
};
|
||||
};
|
||||
});
|
@ -22,14 +22,16 @@
|
||||
|
||||
define([
|
||||
'lodash',
|
||||
'../../platform/features/conductor/utcTimeSystem/src/UTCTimeSystem',
|
||||
'./utcTimeSystem/plugin',
|
||||
'../../example/generator/plugin',
|
||||
'../../platform/features/autoflow/plugin'
|
||||
'../../platform/features/autoflow/plugin',
|
||||
'./timeConductor/plugin'
|
||||
], function (
|
||||
_,
|
||||
UTCTimeSystem,
|
||||
GeneratorPlugin,
|
||||
AutoflowPlugin
|
||||
AutoflowPlugin,
|
||||
TimeConductorPlugin
|
||||
) {
|
||||
var bundleMap = {
|
||||
CouchDB: 'platform/persistence/couch',
|
||||
@ -48,14 +50,7 @@ define([
|
||||
};
|
||||
});
|
||||
|
||||
plugins.UTCTimeSystem = function () {
|
||||
return function (openmct) {
|
||||
openmct.legacyExtension("timeSystems", {
|
||||
"implementation": UTCTimeSystem,
|
||||
"depends": ["$timeout"]
|
||||
});
|
||||
};
|
||||
};
|
||||
plugins.UTCTimeSystem = UTCTimeSystem;
|
||||
|
||||
/**
|
||||
* A tabular view showing the latest values of multiple telemetry points at
|
||||
@ -68,50 +63,7 @@ define([
|
||||
*/
|
||||
plugins.AutoflowView = AutoflowPlugin;
|
||||
|
||||
var conductorInstalled = false;
|
||||
|
||||
plugins.Conductor = function (options) {
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
|
||||
function applyDefaults(openmct, timeConductorViewService) {
|
||||
var defaults = {};
|
||||
var timeSystem = timeConductorViewService.systems.find(function (ts) {
|
||||
return ts.metadata.key === options.defaultTimeSystem;
|
||||
});
|
||||
if (timeSystem !== undefined) {
|
||||
openmct.conductor.timeSystem(timeSystem, defaults.bounds);
|
||||
}
|
||||
}
|
||||
|
||||
return function (openmct) {
|
||||
openmct.legacyExtension('constants', {
|
||||
key: 'DEFAULT_TIMECONDUCTOR_MODE',
|
||||
value: options.showConductor ? 'fixed' : 'realtime',
|
||||
priority: conductorInstalled ? 'mandatory' : 'fallback'
|
||||
});
|
||||
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/compatibility');
|
||||
}
|
||||
conductorInstalled = true;
|
||||
};
|
||||
};
|
||||
plugins.Conductor = TimeConductorPlugin;
|
||||
|
||||
plugins.CouchDB = function (url) {
|
||||
return function (openmct) {
|
||||
|
93
src/plugins/timeConductor/plugin.js
Normal file
93
src/plugins/timeConductor/plugin.js
Normal file
@ -0,0 +1,93 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2017, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define([], function () {
|
||||
|
||||
return function (config) {
|
||||
|
||||
function validateConfiguration() {
|
||||
if (config === undefined || config.menuOptions === undefined || config.menuOptions.length === 0) {
|
||||
return "Please provide some configuration for the time conductor. https://github.com/nasa/openmct/blob/master/API.md#time-conductor";
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return function (openmct) {
|
||||
|
||||
function getTimeSystem(key) {
|
||||
return openmct.time.getAllTimeSystems().filter(function (timeSystem) {
|
||||
return timeSystem.key === key;
|
||||
})[0];
|
||||
}
|
||||
|
||||
var validationError = validateConfiguration();
|
||||
if (validationError) {
|
||||
throw validationError;
|
||||
}
|
||||
|
||||
openmct.legacyExtension('constants', {
|
||||
key: 'CONDUCTOR_CONFIG',
|
||||
value: config,
|
||||
priority: 'mandatory'
|
||||
});
|
||||
|
||||
openmct.legacyRegistry.enable('platform/features/conductor/core');
|
||||
openmct.legacyRegistry.enable('platform/features/conductor/compatibility');
|
||||
|
||||
openmct.on('start', function () {
|
||||
/*
|
||||
On app startup, default the conductor
|
||||
*/
|
||||
var timeSystem = openmct.time.timeSystem();
|
||||
var clock = openmct.time.clock();
|
||||
|
||||
if (timeSystem === undefined) {
|
||||
timeSystem = getTimeSystem(config.menuOptions[0].timeSystem);
|
||||
if (timeSystem === undefined) {
|
||||
throw 'Please install and configure at least one time system';
|
||||
}
|
||||
}
|
||||
|
||||
var configForTimeSystem = config.menuOptions.filter(function (menuOption) {
|
||||
return menuOption.timeSystem === timeSystem.key && menuOption.clock === (clock && clock.key);
|
||||
})[0];
|
||||
|
||||
if (configForTimeSystem !== undefined) {
|
||||
var bounds;
|
||||
if (clock === undefined) {
|
||||
bounds = configForTimeSystem.bounds;
|
||||
} else {
|
||||
var clockOffsets = configForTimeSystem.clockOffsets;
|
||||
|
||||
bounds = {
|
||||
start: clock.currentValue() + clockOffsets.start,
|
||||
end: clock.currentValue() + clockOffsets.end
|
||||
};
|
||||
}
|
||||
openmct.time.timeSystem(timeSystem, bounds);
|
||||
} else {
|
||||
throw 'Invalid time conductor configuration. Please define defaults for time system "' + timeSystem.key + '"';
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
||||
});
|
@ -44,6 +44,7 @@ define([
|
||||
* @memberof platform/commonUI/formats
|
||||
*/
|
||||
function DurationFormat() {
|
||||
this.key = "duration";
|
||||
}
|
||||
|
||||
DurationFormat.prototype.format = function (value) {
|
118
src/plugins/utcTimeSystem/LocalClock.js
Normal file
118
src/plugins/utcTimeSystem/LocalClock.js
Normal file
@ -0,0 +1,118 @@
|
||||
/*****************************************************************************
|
||||
* 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(['EventEmitter'], function (EventEmitter) {
|
||||
/**
|
||||
* A {@link openmct.TimeAPI.Clock} that updates the temporal bounds of the
|
||||
* application based on UTC time values provided by a ticking local clock,
|
||||
* with the periodicity specified.
|
||||
* @param {number} period The periodicity with which the clock should tick
|
||||
* @constructor
|
||||
*/
|
||||
function LocalClock(period) {
|
||||
EventEmitter.call(this);
|
||||
|
||||
/*
|
||||
Metadata fields
|
||||
*/
|
||||
this.key = 'local';
|
||||
this.cssClass = 'icon-clock';
|
||||
this.name = 'Local Clock';
|
||||
this.description = "Updates every second, providing UTC timestamps from " +
|
||||
"user's local computer.";
|
||||
|
||||
this.period = period;
|
||||
this.timeoutHandle = undefined;
|
||||
this.lastTick = Date.now();
|
||||
}
|
||||
|
||||
LocalClock.prototype = Object.create(EventEmitter.prototype);
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
LocalClock.prototype.start = function () {
|
||||
this.timeoutHandle = setTimeout(this.tick.bind(this), this.period);
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
LocalClock.prototype.stop = function () {
|
||||
if (this.timeoutHandle) {
|
||||
clearTimeout(this.timeoutHandle);
|
||||
this.timeoutHandle = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
LocalClock.prototype.tick = function () {
|
||||
var now = Date.now();
|
||||
this.emit("tick", now);
|
||||
this.lastTick = now;
|
||||
this.timeoutHandle = setTimeout(this.tick.bind(this), this.period);
|
||||
};
|
||||
|
||||
/**
|
||||
* Register a listener for the local clock. When it ticks, the local
|
||||
* clock will provide the current local system time
|
||||
*
|
||||
* @param listener
|
||||
* @returns {function} a function for deregistering the provided listener
|
||||
*/
|
||||
LocalClock.prototype.on = function (event, listener) {
|
||||
var result = EventEmitter.prototype.on.apply(this, arguments);
|
||||
|
||||
if (this.listeners(event).length === 1) {
|
||||
this.start();
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Register a listener for the local clock. When it ticks, the local
|
||||
* clock will provide the current local system time
|
||||
*
|
||||
* @param listener
|
||||
* @returns {function} a function for deregistering the provided listener
|
||||
*/
|
||||
LocalClock.prototype.off = function (event, listener) {
|
||||
var result = EventEmitter.prototype.off.apply(this, arguments);
|
||||
|
||||
if (this.listeners(event).length === 0) {
|
||||
this.stop();
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* @returns {number} The last value provided for a clock tick
|
||||
*/
|
||||
LocalClock.prototype.currentValue = function () {
|
||||
return this.lastTick;
|
||||
};
|
||||
|
||||
return LocalClock;
|
||||
});
|
@ -29,22 +29,16 @@ define(["./LocalClock"], function (LocalClock) {
|
||||
beforeEach(function () {
|
||||
mockTimeout = jasmine.createSpy("timeout");
|
||||
mockTimeout.andReturn(timeoutHandle);
|
||||
mockTimeout.cancel = jasmine.createSpy("cancel");
|
||||
|
||||
clock = new LocalClock(mockTimeout, 0);
|
||||
clock = new LocalClock(0);
|
||||
clock.start();
|
||||
});
|
||||
|
||||
it("calls listeners on tick with current time", function () {
|
||||
var mockListener = jasmine.createSpy("listener");
|
||||
clock.listen(mockListener);
|
||||
clock.on('tick', mockListener);
|
||||
clock.tick();
|
||||
expect(mockListener).toHaveBeenCalledWith(jasmine.any(Number));
|
||||
});
|
||||
|
||||
it("stops ticking when stop is called", function () {
|
||||
clock.stop();
|
||||
expect(mockTimeout.cancel).toHaveBeenCalledWith(timeoutHandle);
|
||||
});
|
||||
});
|
||||
});
|
@ -1,5 +1,5 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2017, United States Government
|
||||
* Open MCT, Copyright (c) 2014-2016, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
@ -49,6 +49,7 @@ define([
|
||||
* @memberof platform/commonUI/formats
|
||||
*/
|
||||
function UTCTimeFormat() {
|
||||
this.key = "utc";
|
||||
}
|
||||
|
||||
/**
|
||||
@ -64,7 +65,7 @@ define([
|
||||
*
|
||||
* Licensed
|
||||
*/
|
||||
return [
|
||||
var format = [
|
||||
[".SSS", function (m) {
|
||||
return m.milliseconds();
|
||||
}],
|
||||
@ -91,64 +92,32 @@ define([
|
||||
return true;
|
||||
}]
|
||||
].filter(function (row) {
|
||||
return row[1](momentified);
|
||||
})[0][0];
|
||||
return row[1](momentified);
|
||||
})[0][0];
|
||||
|
||||
if (format !== undefined) {
|
||||
return moment.utc(d).format(format);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a description of the current range of the time conductor's
|
||||
* bounds.
|
||||
* @param timeRange
|
||||
* @returns {*}
|
||||
* @param {number} value The value to format.
|
||||
* @param {number} [minValue] Contextual information for scaled formatting used in linear scales such as conductor
|
||||
* and plot axes. Specifies the smallest number on the scale.
|
||||
* @param {number} [maxValue] Contextual information for scaled formatting used in linear scales such as conductor
|
||||
* and plot axes. Specifies the largest number on the scale
|
||||
* @param {number} [count] Contextual information for scaled formatting used in linear scales such as conductor
|
||||
* and plot axes. The number of labels on the scale.
|
||||
* @returns {string} the formatted date(s). If multiple values were requested, then an array of
|
||||
* formatted values will be returned. Where a value could not be formatted, `undefined` will be returned at its position
|
||||
* in the array.
|
||||
*/
|
||||
UTCTimeFormat.prototype.timeUnits = function (timeRange) {
|
||||
var momentified = moment.duration(timeRange);
|
||||
|
||||
return [
|
||||
["Decades", function (r) {
|
||||
return r.years() > 15;
|
||||
}],
|
||||
["Years", function (r) {
|
||||
return r.years() > 1;
|
||||
}],
|
||||
["Months", function (r) {
|
||||
return r.years() === 1 || r.months() > 1;
|
||||
}],
|
||||
["Days", function (r) {
|
||||
return r.months() === 1 || r.days() > 1;
|
||||
}],
|
||||
["Hours", function (r) {
|
||||
return r.days() === 1 || r.hours() > 1;
|
||||
}],
|
||||
["Minutes", function (r) {
|
||||
return r.hours() === 1 || r.minutes() > 1;
|
||||
}],
|
||||
["Seconds", function (r) {
|
||||
return r.minutes() === 1 || r.seconds() > 1;
|
||||
}],
|
||||
["Milliseconds", function (r) {
|
||||
return true;
|
||||
}]
|
||||
].filter(function (row) {
|
||||
return row[1](momentified);
|
||||
})[0][0];
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param value
|
||||
* @param {Scale} [scale] Optionally provides context to the
|
||||
* format request, allowing for scale-appropriate formatting.
|
||||
* @returns {string} the formatted date
|
||||
*/
|
||||
UTCTimeFormat.prototype.format = function (value, scale) {
|
||||
if (scale !== undefined) {
|
||||
var scaledFormat = getScaledFormat(value, scale);
|
||||
if (scaledFormat) {
|
||||
return moment.utc(value).format(scaledFormat);
|
||||
}
|
||||
UTCTimeFormat.prototype.format = function (value, minValue, maxValue, count) {
|
||||
if (arguments.length > 1) {
|
||||
return getScaledFormat(value);
|
||||
} else {
|
||||
return moment.utc(value).format(DATE_FORMAT) + "Z";
|
||||
}
|
||||
return moment.utc(value).format(DATE_FORMAT) + "Z";
|
||||
};
|
||||
|
||||
UTCTimeFormat.prototype.parse = function (text) {
|
@ -58,26 +58,5 @@ define([
|
||||
expect(format.format(APRIL, scale)).toBe("April");
|
||||
expect(format.format(TWENTY_SIXTEEN, scale)).toBe("2016");
|
||||
});
|
||||
|
||||
it("Returns appropriate time units for a given time span", function () {
|
||||
var ONE_DAY = 1000 * 60 * 60 * 24;
|
||||
var FIVE_DAYS = 5 * ONE_DAY;
|
||||
var FIVE_MONTHS = 60 * ONE_DAY;
|
||||
|
||||
var ONE_YEAR = 365 * ONE_DAY;
|
||||
var SEVEN_YEARS = 7 * ONE_YEAR;
|
||||
var TWO_DECADES = 20 * ONE_YEAR;
|
||||
|
||||
//A span of one day should show a zoom label of "Hours"
|
||||
expect(format.timeUnits(ONE_DAY)).toEqual("Hours");
|
||||
//Multiple days should display "Days"
|
||||
expect(format.timeUnits(FIVE_DAYS)).toEqual("Days");
|
||||
expect(format.timeUnits(FIVE_MONTHS)).toEqual("Days");
|
||||
//A span of one year should show a zoom level of "Months".
|
||||
// Multiple years will show "Years"
|
||||
expect(format.timeUnits(ONE_YEAR)).toEqual("Months");
|
||||
expect(format.timeUnits(SEVEN_YEARS)).toEqual("Years");
|
||||
expect(format.timeUnits(TWO_DECADES)).toEqual("Decades");
|
||||
});
|
||||
});
|
||||
});
|
44
src/plugins/utcTimeSystem/UTCTimeSystem.js
Normal file
44
src/plugins/utcTimeSystem/UTCTimeSystem.js
Normal file
@ -0,0 +1,44 @@
|
||||
/*****************************************************************************
|
||||
* 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 () {
|
||||
/**
|
||||
* This time system supports UTC dates.
|
||||
* @implements TimeSystem
|
||||
* @constructor
|
||||
*/
|
||||
function UTCTimeSystem() {
|
||||
|
||||
/**
|
||||
* Metadata used to identify the time system in
|
||||
* the UI
|
||||
*/
|
||||
this.key = 'utc';
|
||||
this.name = 'UTC';
|
||||
this.cssClass = 'icon-clock';
|
||||
this.timeFormat = 'utc';
|
||||
this.durationFormat = 'duration';
|
||||
this.isUTCBased = true;
|
||||
}
|
||||
|
||||
return UTCTimeSystem;
|
||||
});
|
@ -30,17 +30,19 @@ define(['./UTCTimeSystem'], function (UTCTimeSystem) {
|
||||
timeSystem = new UTCTimeSystem(mockTimeout);
|
||||
});
|
||||
|
||||
it("defines at least one format", function () {
|
||||
expect(timeSystem.formats().length).toBeGreaterThan(0);
|
||||
it("Uses the UTC time format", function () {
|
||||
expect(timeSystem.timeFormat).toBe('utc');
|
||||
});
|
||||
|
||||
it("defines a tick source", function () {
|
||||
var tickSources = timeSystem.tickSources();
|
||||
expect(tickSources.length).toBeGreaterThan(0);
|
||||
it("is UTC based", function () {
|
||||
expect(timeSystem.isUTCBased).toBe(true);
|
||||
});
|
||||
|
||||
it("defines some defaults", function () {
|
||||
expect(timeSystem.defaults()).toBeDefined();
|
||||
it("defines expected metadata", function () {
|
||||
expect(timeSystem.key).toBeDefined();
|
||||
expect(timeSystem.name).toBeDefined();
|
||||
expect(timeSystem.cssClass).toBeDefined();
|
||||
expect(timeSystem.durationFormat).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
52
src/plugins/utcTimeSystem/plugin.js
Normal file
52
src/plugins/utcTimeSystem/plugin.js
Normal file
@ -0,0 +1,52 @@
|
||||
/*****************************************************************************
|
||||
* 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([
|
||||
"./UTCTimeSystem",
|
||||
"./LocalClock",
|
||||
"./UTCTimeFormat",
|
||||
"./DurationFormat"
|
||||
], function (
|
||||
UTCTimeSystem,
|
||||
LocalClock,
|
||||
UTCTimeFormat,
|
||||
DurationFormat
|
||||
) {
|
||||
/**
|
||||
* Install a time system that supports UTC times. It also installs a local
|
||||
* clock source that ticks every 100ms, providing UTC times.
|
||||
*/
|
||||
return function () {
|
||||
return function (openmct) {
|
||||
var timeSystem = new UTCTimeSystem();
|
||||
openmct.time.addTimeSystem(timeSystem);
|
||||
openmct.time.addClock(new LocalClock(100));
|
||||
openmct.telemetry.addFormat(new UTCTimeFormat());
|
||||
openmct.telemetry.addFormat(new DurationFormat());
|
||||
|
||||
openmct.legacyExtension("constants", {
|
||||
"key": "DEFAULT_TIME_FORMAT",
|
||||
"value": "utc"
|
||||
});
|
||||
};
|
||||
};
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user