Merge remote-tracking branch 'origin/master' into open-623

This commit is contained in:
Victor Woeltjen 2017-05-03 16:05:03 -07:00
commit 906646704e
101 changed files with 3760 additions and 2609 deletions

433
API.md
View File

@ -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.

View File

@ -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;
});

View File

@ -44,31 +44,31 @@ define(
{
"name": "Min. Air Temperature",
"identifier": "min_temp",
"units": "degrees",
"units": "Degrees (C)",
"type": "float"
},
{
"name": "Max. Air Temperature",
"identifier": "max_temp",
"units": "degrees",
"units": "Degrees (C)",
"type": "float"
},
{
"name": "Atmospheric Pressure",
"identifier": "pressure",
"units": "pascals",
"units": "Millibars",
"type": "float"
},
{
"name": "Min. Ground Temperature",
"identifier": "min_gts_temp",
"units": "degrees",
"units": "Degrees (C)",
"type": "float"
},
{
"name": "Max. Ground Temperature",
"identifier": "max_gts_temp",
"units": "degrees",
"units": "Degrees (C)",
"type": "float"
}
]
@ -76,4 +76,4 @@ define(
]
};
}
);
);

View File

@ -48,6 +48,13 @@ define(
this.$http = $http;
this.$log = $log;
this.promise = undefined;
this.dataTransforms = {
//Convert from pascals to millibars
'pressure': function pascalsToMillibars(pascals) {
return pascals / 100;
}
};
}
/**
@ -65,6 +72,8 @@ define(
var self = this,
id = request.key;
var dataTransforms = this.dataTransforms;
function processResponse(response){
var data = [];
/*
@ -75,13 +84,14 @@ define(
* Check that valid data exists
*/
if (!isNaN(solData[id])) {
var dataTransform = dataTransforms[id];
/*
* Append each data point to the array of values
* for this data point property (min. temp, etc).
*/
data.unshift({
date: Date.parse(solData[TERRESTRIAL_DATE]),
value: solData[id]
value: dataTransform ? dataTransform(solData[id]) : solData[id]
});
}
});

View File

@ -0,0 +1,98 @@
define([
"./src/ExampleStyleGuideModelProvider",
"./src/MCTExample",
'legacyRegistry'
], function (
ExampleStyleGuideModelProvider,
MCTExample,
legacyRegistry
) {
legacyRegistry.register("example/styleguide", {
"name": "Open MCT Style Guide",
"description": "Examples and documentation illustrating UI styles in use in Open MCT.",
"extensions":
{
"types": [
{ "key": "styleguide.intro", "name": "Introduction", "cssClass": "icon-page", "description": "Introduction and overview to the style guide" },
{ "key": "styleguide.standards", "name": "Standards", "cssClass": "icon-page", "description": "" },
{ "key": "styleguide.colors", "name": "Colors", "cssClass": "icon-page", "description": "" },
{ "key": "styleguide.glyphs", "name": "Glyphs", "cssClass": "icon-page", "description": "Glyphs overview" },
{ "key": "styleguide.controls", "name": "Controls", "cssClass": "icon-page", "description": "Buttons, selects, HTML controls" },
{ "key": "styleguide.input", "name": "Text Inputs", "cssClass": "icon-page", "description": "Various text inputs" },
{ "key": "styleguide.menus", "name": "Menus", "cssClass": "icon-page", "description": "Context menus, dropdowns" }
],
"views": [
{ "key": "styleguide.intro", "type": "styleguide.intro", "templateUrl": "templates/intro.html", "editable": false },
{ "key": "styleguide.standards", "type": "styleguide.standards", "templateUrl": "templates/standards.html", "editable": false },
{ "key": "styleguide.colors", "type": "styleguide.colors", "templateUrl": "templates/colors.html", "editable": false },
{ "key": "styleguide.glyphs", "type": "styleguide.glyphs", "templateUrl": "templates/glyphs.html", "editable": false },
{ "key": "styleguide.controls", "type": "styleguide.controls", "templateUrl": "templates/controls.html", "editable": false },
{ "key": "styleguide.input", "type": "styleguide.input", "templateUrl": "templates/input.html", "editable": false },
{ "key": "styleguide.menus", "type": "styleguide.menus", "templateUrl": "templates/menus.html", "editable": false }
],
"roots": [
{
"id": "styleguide:home"
}
],
"models": [
{
"id": "styleguide:home",
"priority" : "preferred",
"model": {
"type": "folder",
"name": "Style Guide Home",
"location": "ROOT",
"composition": [
"intro",
"standards",
"colors",
"glyphs",
"styleguide:ui-elements"
]
}
},
{
"id": "styleguide:ui-elements",
"priority" : "preferred",
"model": {
"type": "folder",
"name": "UI Elements",
"location": "styleguide:home",
"composition": [
"controls",
"input",
"menus"
]
}
}
],
"directives": [
{
"key": "mctExample",
"implementation": MCTExample
}
],
"components": [
{
"provides": "modelService",
"type": "provider",
"implementation": ExampleStyleGuideModelProvider,
"depends": [
"$q"
]
}
],
"stylesheets": [
{
"stylesheetUrl": "css/style-guide-espresso.css",
"theme": "espresso"
},
{
"stylesheetUrl": "css/style-guide-snow.css",
"theme": "snow"
}
]
}
});
});

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 10 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.7 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 800 622.47"><defs><style>.a,.d{fill:#fff;}.b,.c,.k,.m,.n,.o{fill:none;}.b{stroke:#000;stroke-linecap:square;}.b,.c,.d,.f,.k,.m,.n,.o{stroke-miterlimit:10;}.b,.k{stroke-width:2px;}.c{stroke:#ababab;stroke-dasharray:5;}.d,.k,.o{stroke:#737373;}.e{opacity:0.5;fill:url(#a);}.f{fill:#d6d6d6;fill-opacity:0.8;stroke:#fff;}.g{mask:url(#b);}.h{fill:#a8a8a8;}.i{fill:#00b1e2;}.j{fill:#f16e86;}.k{stroke-linecap:round;}.l{mask:url(#c);}.m{stroke:#00b1e2;}.m,.n{stroke-width:10px;}.n{stroke:#f16e86;}</style><linearGradient id="a" x1="523.15" y1="-401.02" x2="513.75" y2="-437.16" gradientTransform="translate(90.62 338.26) rotate(45)" gradientUnits="userSpaceOnUse"><stop offset="0"/><stop offset="1" stop-opacity="0"/></linearGradient><mask id="b" x="-1187.45" y="-269.68" width="1813.84" height="554.26" maskUnits="userSpaceOnUse"><polygon class="a" points="625.33 284.58 452.55 238.2 452.55 170.5 625.33 216.88 625.33 284.58"/><line class="b" x1="486.25" y1="179.55" x2="486.25" y2="247.32"/><line class="b" x1="573.58" y1="202.99" x2="573.58" y2="270.76"/><line class="b" x1="452.46" y1="192.89" x2="625.16" y2="239.25"/><line class="b" x1="452.46" y1="215.48" x2="625.16" y2="261.83"/></mask><mask id="c" x="-1468.9" y="-163.59" width="1866.4" height="673.87" maskUnits="userSpaceOnUse"><polygon class="a" points="344.11 510.28 171.1 462.49 171.1 289.49 344.11 337.28 344.11 510.28"/></mask></defs><title>objects-diagram</title><polyline class="c" points="460.63 153.71 480.14 142.45 513.15 123.39"/><polyline class="c" points="312.69 239.13 336.82 225.2 365.42 208.68"/><polygon class="d" points="742.15 65.53 519.88 5.97 519.88 368.97 742.15 428.53 742.15 65.53"/><polygon class="e" points="742.15 428.53 742.15 377.47 786.44 403 742.15 428.53"/><polyline class="c" points="619.05 308.21 626.95 303.65 636.44 298.17"/><polygon class="f" points="637.26 156.11 443.71 104.25 443.71 402.92 637.26 454.78 637.26 156.11"/><g class="g"><polygon class="h" points="625.33 239.44 452.55 193.07 452.55 170.5 625.33 216.88 625.33 239.44"/><polygon class="i" points="625.33 262.01 452.55 215.64 452.55 193.07 625.33 239.44 625.33 262.01"/><polygon class="j" points="625.33 284.58 452.55 238.2 452.55 215.64 625.33 262.01 625.33 284.58"/></g><polyline class="c" points="467.58 395.66 481.45 387.65 490.54 382.4"/><polygon class="f" points="490.33 240.94 296.78 189.08 296.78 487.75 490.33 539.62 490.33 240.94"/><polygon class="h" points="386.94 387.36 314 367.78 314 238.33 386.94 257.91 386.94 387.36"/><line class="k" x1="402.02" y1="280.97" x2="355.33" y2="284.32"/><line class="k" x1="401.71" y1="316.74" x2="355.33" y2="291.74"/><polygon class="i" points="473.63 301.9 400.69 282.32 400.69 261.6 473.63 281.18 473.63 301.9"/><polygon class="j" points="473.63 337.63 400.69 318.05 400.69 297.32 473.63 316.9 473.63 337.63"/><polyline class="c" points="331.67 474.13 343.85 467.1 356.86 459.58"/><polyline class="c" points="183.35 313.8 207.44 299.89 231.58 285.96"/><polygon class="c" points="710.61 148.05 546.55 104.09 546.55 144.26 710.61 188.22 710.61 148.05"/><polygon class="c" points="710.61 215.05 546.55 171.09 546.55 211.26 710.61 255.22 710.61 215.05"/><line class="c" x1="162.68" y1="266.5" x2="533" y2="52.7"/><line class="c" x1="356.23" y1="617.04" x2="726.54" y2="403.24"/><polygon class="f" points="356.23 318.36 162.68 266.5 162.68 565.18 356.23 617.04 356.23 318.36"/><g class="l"><path class="m" d="M165.1,350.83c12-19.68,24-57.37,47,7s29,42,46,35.71,27,34.46,50,51.81,75,16.72,89,15.59"/><path class="n" d="M349.1,427.78c-9.13-13.05-13.7-31.26-31.2-9.54s-22.07,9.47-35,.86-20.55,6.69-38.05,6.89-67.08-25.38-77.73-30.61"/></g><polygon class="i" points="211.4 436.27 47.34 392.31 47.34 432.48 211.4 476.44 211.4 436.27"/><polygon class="o" points="211.4 436.27 47.34 392.31 47.34 432.48 211.4 476.44 211.4 436.27"/><polygon class="j" points="211.4 503.27 47.34 459.31 47.34 499.48 211.4 543.44 211.4 503.27"/><polygon class="o" points="211.4 503.27 47.34 459.31 47.34 499.48 211.4 543.44 211.4 503.27"/><line class="c" x1="513.15" y1="123.39" x2="544.97" y2="105.02"/><line class="c" x1="365.42" y1="208.68" x2="460.63" y2="153.71"/><line class="c" x1="231.58" y1="285.96" x2="312.69" y2="239.13"/><line class="c" x1="48.79" y1="391.49" x2="183.35" y2="313.8"/><line class="c" x1="636.44" y1="298.17" x2="709.92" y2="255.75"/><line class="c" x1="490.54" y1="382.4" x2="619.05" y2="308.21"/><line class="c" x1="356.86" y1="459.58" x2="467.58" y2="395.66"/><line class="c" x1="212.34" y1="543.03" x2="331.67" y2="474.13"/></svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@ -0,0 +1,199 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2016, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
.l-style-guide {
font-size: 0.9em;
text-align: justify;
margin: auto 10%;
a.link {
color: $colorKey;
}
h1, h2 {
color: pullForward($colorBodyFg, 20%);
}
h2 {
font-size: 1.25em;
}
h3 {
font-size: 0.9em;
margin: $interiorMargin 0;
&:not(:first-child) {
margin-top: $interiorMarginLg * 2;
}
text-transform: uppercase;
}
.w-markup {
//Wrap markup example "pre" element
background-color: $colorCode;
border-radius: $interiorMargin;
display: block;
padding: $interiorMarginLg $interiorMarginLg;
position: relative;
}
code,
pre {
font-size: 0.8rem;
}
code {
background-color: $colorCode;
border-radius: $controlCr;
display: inline-block;
padding: 1px $interiorMargin;
}
pre {
display: block;
margin: 0;
max-height: 300px;
overflow: auto;
padding-bottom: $interiorMarginLg;
white-space: pre;
}
table, ul {
margin-bottom: $interiorMarginLg;
width: auto;
}
.themed {
display: none; // Each themed styleguide file will set this to block.
}
.doc-title {
color: rgba(#fff, 0.3);
text-transform: uppercase;
}
.l-section {
border-top: 1px solid rgba(#999, 0.3);
margin-top: 2em;
padding-top: 1em;
.cols {
@include display(flex);
@include flex-direction(row);
.col {
@include flex(1 1 auto);
&:not(:last-child) {
$v: $interiorMargin * 4;
border-right: 1px solid $colorInteriorBorder;
margin-right: $v;
padding-right: $v;
}
min-width: 300px;
img {
width: 100%;
}
}
&.cols1-1 {
// 2 cols, equal width
.col {
width: 50%;
}
}
&.cols1-2 {
// 3 cols, first is 1/3 of the width
.col:first-child {
width: 33%;
}
.col:last-child {
width: 66%;
}
}
&.cols2-1 {
// 3 cols, first is 2/3 of the width
.col:first-child {
width: 66%;
}
.col:last-child {
width: 33%;
}
}
}
}
// Example grid of glyphs
.items-holder.grid {
.item.glyph-item,
.item.swatch-item {
margin-bottom: 50px;
margin-right: 10px;
position: relative;
text-align: left;
.glyph {
color: $colorGlyphExample;
font-size: 5em;
margin: $interiorMarginLg 0;
text-align: center;
}
table.details td {
font-size: inherit;
&.label {
color: pushBack($colorBodyFg, 10%);
text-transform: uppercase;
white-space: nowrap;
}
}
}
.item.glyph-item {
width: 225px;
height: 200px;
}
.item.swatch-item {
$h: 150px;
$d: 75px;
width: 200px;
height: $h;
.glyph {
text-shadow: 0px 1px 10px rgba(black, 0.3);
}
.h-swatch {
position: relative;
height: $d + $interiorMarginLg;
}
.swatch {
height: $d; width: $d;
border: 1px solid $colorInteriorBorder;
border-radius: 15%;
position: absolute;
left: 50%;
@include transform(translateX(-50%));
}
}
}
}

View File

@ -0,0 +1,37 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2015, 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.
*****************************************************************************/
@import "bourbon";
@import "../../../../platform/commonUI/general/res/sass/constants";
@import "../../../../platform/commonUI/general/res/sass/mixins";
@import "../../../../platform/commonUI/themes/espresso/res/sass/constants";
@import "../../../../platform/commonUI/themes/espresso/res/sass/mixins";
@import "../../../../platform/commonUI/general/res/sass/glyphs";
@import "../../../../platform/commonUI/general/res/sass/icons";
// Thematic constants
$colorCode: rgba(black, 0.2);
$colorGlyphExample: #fff;
@import "style-guide-base";
div.themed.espresso { display: block; }
span.themed.espresso { display: inline; }

View File

@ -0,0 +1,37 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2015, 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.
*****************************************************************************/
@import "bourbon";
@import "../../../../platform/commonUI/general/res/sass/constants";
@import "../../../../platform/commonUI/general/res/sass/mixins";
@import "../../../../platform/commonUI/themes/snow/res/sass/constants";
@import "../../../../platform/commonUI/themes/snow/res/sass/mixins";
@import "../../../../platform/commonUI/general/res/sass/glyphs";
@import "../../../../platform/commonUI/general/res/sass/icons";
// Thematic constants
$colorCode: rgba(black, 0.1);
$colorGlyphExample: darken($colorBodyBg, 30%);
@import "style-guide-base";
div.themed.snow { display: block; }
span.themed.snow { display: inline; }

View File

@ -0,0 +1,84 @@
<!--
Open MCT, Copyright (c) 2014-2016, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
Open MCT is licensed under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
Open MCT includes source code licensed under additional open source
licenses. See the Open Source Licenses file (LICENSES.md) included with
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<div ng-init="colors = [
{ 'category': 'Interface', 'description': 'Colors used in the application envelope, buttons and controls.', 'items': [{ 'name': 'Body Background', 'constant': '$colorBodyBg', 'valEspresso': '#333', 'valSnow': '#fcfcfc' },
{ 'name': 'Body Foreground', 'constant': '$colorBodyFg', 'valEspresso': '#999', 'valSnow': '#666' },
{ 'name': 'Key Color Background', 'constant': '$colorKey', 'valEspresso': '#0099cc', 'valSnow': '#0099cc' },
{ 'name': 'Key Color Foreground', 'constant': '$colorKeyFg', 'valEspresso': '#fff', 'valSnow': '#fff' },
{ 'name': 'Paused Color Background', 'constant': '$colorPausedBg', 'valEspresso': '#c56f01', 'valSnow': '#ff9900' },
{ 'name': 'Paused Color Foreground', 'constant': '$colorPausedFg', 'valEspresso': '#fff', 'valSnow': '#fff' }]},
{ 'category': 'Forms', 'description': 'Colors in forms, mainly to articulate validation status.', 'items': [{ 'name': 'Valid Entry', 'constant': '$colorFormValid', 'valEspresso': '#33cc33', 'valSnow': '#33cc33' },
{ 'name': 'Errorenous Entry', 'constant': '$colorFormError', 'valEspresso': '#990000', 'valSnow': '#990000' },
{ 'name': 'Invalid Entry', 'constant': '$colorFormInvalid', 'valEspresso': '#ff3300', 'valSnow': '#ff2200' }]},
{ 'category': 'Application Status', 'description': 'Colors related to the status of application objects, such as a successful connection to a service.', 'items': [{ 'name': 'Alert Color', 'constant': '$colorAlert', 'valEspresso': '#ff3c00', 'valSnow': '#ff3c00' },
{ 'name': 'Status Color Foreground', 'constant': '$colorStatusFg', 'valEspresso': '#ccc', 'valSnow': '#fff' },
{ 'name': 'Default Status Color', 'constant': '$colorStatusDefault', 'valEspresso': '#ccc', 'valSnow': '#ccc' },
{ 'name': 'Status: Informational Color', 'constant': '$colorStatusInfo', 'valEspresso': '#62ba72', 'valSnow': '#60ba7b' },
{ 'name': 'Status: Alert Color', 'constant': '$colorStatusAlert', 'valEspresso': '#ffa66d', 'valSnow': '#ffb66c' },
{ 'name': 'Status: Error Color', 'constant': '$colorStatusError', 'valEspresso': '#d4585c', 'valSnow': '#c96b68' }]},
{ 'category': 'Telemetry Status', 'description': 'Telemetry status colors used to indicate limit violations and alarm states. Note that these colors should be reserved exclusively for this usage.', 'items': [{ 'name': 'Yellow Limit Color Background', 'constant': '$colorLimitYellowBg', 'valEspresso': 'rgba(255,170,0,0.3)', 'valSnow': 'rgba(255,170,0,0.3)' },
{ 'name': 'Yellow Limit Color Icon', 'constant': '$colorLimitYellowIc', 'valEspresso': '#ffaa00', 'valSnow': '#ffaa00' },
{ 'name': 'Red Limit Color Background', 'constant': '$colorLimitRedBg', 'valEspresso': 'rgba(255,0,0,0.3)', 'valSnow': 'rgba(255,0,0,0.3)' },
{ 'name': 'Red Limit Color Icon', 'constant': '$colorLimitRedIc', 'valEspresso': 'red', 'valSnow': 'red' }]}
]"></div>
<div class="l-style-guide s-text">
<p class="doc-title">Open MCT Style Guide</p>
<h1>Colors</h1>
<div class="l-section">
<h2>Overview</h2>
<p>In mission operations, color is used to convey meaning. Alerts, warnings and status conditions are by convention communicated with colors in the green, yellow and red families. Colors must also be reserved for use in plots. As a result, Open MCT uses color selectively and sparingly. Follow these guidelines:</p>
<ul>
<li>Don't use red, orange, yellow or green colors in any element that isn't conveying some kind of status information.</li>
<li>Each theme has a key color (typically blue-ish) that should be used to emphasize interactive elements and important UI controls.</li>
<li>Within each theme values are used to push elements back or bring them forward, lowering or raising them in visual importance.
<span class="themed espresso">In this theme, Espresso, lighter colors are placed on a dark background. The lighter a color is, the more it comes toward the observer and is raised in importance.</span>
<span class="themed snow">In this theme, Snow, darker colors are placed on a light background. The darker a color is, the more it comes toward the observer and is raised in importance.</span>
</li>
<li>For consistency, use a theme's pre-defined status colors.</li>
</ul>
</div>
<div class="l-section" ng-repeat="colorSet in colors">
<h2>{{ colorSet.category }}</h2>
<p>{{ colorSet.description }}</p>
<div class="items-holder grid">
<div class="item swatch-item" ng-repeat="color in colorSet.items">
<div class="h-swatch">
<div class="swatch themed espresso" style="background-color: {{ color.valEspresso }}"></div>
<div class="swatch themed snow" style="background-color: {{ color.valSnow }}"></div>
</div>
<table class="details">
<tr><td class="label">Name</td><td class="value">{{color.name}}</td></tr>
<tr><td class="label">SASS</td><td class="value">{{color.constant}}</td></tr>
<tr><td class="label">Value</td><td class="value">
<span class="themed espresso">{{color.valEspresso}}</span>
<span class="themed snow">{{color.valSnow}}</span>
</td></tr>
</table>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,163 @@
<!--
Open MCT, Copyright (c) 2014-2016, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
Open MCT is licensed under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
Open MCT includes source code licensed under additional open source
licenses. See the Open Source Licenses file (LICENSES.md) included with
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<div class="l-style-guide s-text">
<p class="doc-title">Open MCT Style Guide</p>
<h1>Controls</h1>
<div class="l-section">
<h2>Standard Buttons</h2>
<div class="cols cols1-1">
<div class="col">
<p>Use a standard button in locations where there's sufficient room and you must make it clear that the element is an interactive button element. Buttons can be displayed with only an icon, only text, or with icon and text combined.</p>
<p>Use an icon whenever possible to aid the user's recognition and recall. If both and icon and text are to be used, the text must be within a <code>span</code> with class <code>.title-label</code>.</p>
</div>
<mct-example><a class="s-button icon-pencil" title="Edit"></a>
<a class="s-button" title="Edit">Edit</a>
<a class="s-button icon-pencil" title="Edit">
<span class="title-label">Edit</span>
</a>
</mct-example>
</div>
</div>
<div class="l-section">
<h2>&quot;Major&quot; Buttons</h2>
<div class="cols cols1-1">
<div class="col">
<p>Major buttons allow emphasis to be placed on a button. Use this on a single button when the user has a small number of choices, and one choice is a normal default. Just add <code>.major</code> to any element that uses <code>.s-button</code>.</p>
</div>
<mct-example><a class="s-button major">Ok</a>
<a class="s-button">Cancel</a>
</mct-example>
</div>
</div>
<div class="l-section">
<h2>Button Sets</h2>
<div class="cols cols1-1">
<div class="col">
<p>Use button sets to connect buttons that have related purpose or functionality. Buttons in a set round the outer corners of only the first and last buttons, any other buttons in the middle simply get division spacers.</p>
<p>To use, simply wrap two or more <code>.s-button</code> elements within <code>.l-btn-set</code>.</p>
</div>
<mct-example><span class="l-btn-set">
<a class="s-button icon-magnify"></a>
<a class="s-button icon-magnify-in"></a>
<a class="s-button icon-magnify-out"></a>
</span>
</mct-example>
</div>
</div>
<div class="l-section">
<h2>Icon-only Buttons</h2>
<div class="cols cols1-1">
<div class="col">
<p>When a button is presented within another control it may be advantageous to avoid visual clutter by using an icon-only button. These type of controls present an icon without the &quot;base&quot; of standard buttons. Icon-only buttons should only be used in a context where they are clearly an interactive element and not an object-type identifier, and should not be used with text.</p>
</div>
<mct-example><a class="s-icon-button icon-pencil" title="Edit"></a>
</mct-example>
</div>
</div>
<div class="l-section">
<h2>Checkboxes</h2>
<div class="cols cols1-1">
<div class="col">
<p>Checkboxes use a combination of minimal additional markup with CSS to present a custom and common look-and-feel across platforms.</p>
<p>The basic structure is a <code>label</code> with a checkbox-type input and an <code>em</code> element inside. The <code>em</code> is needed as the holder of the custom element; the input itself is hidden. Putting everything inside the <code>label</code> allows the label itself to act as a clickable element.</p>
</div>
<mct-example><label class="checkbox custom no-text">
<input type="checkbox" />
<em></em>
</label>
<br />
<label class="checkbox custom no-text">
<input type="checkbox" checked />
<em></em>
</label>
<br />
<label class="checkbox custom">Labeled checkbox
<input type="checkbox" />
<em></em>
</label></mct-example>
</div>
</div>
<div class="l-section">
<h2>Radio Buttons</h2>
<div class="cols cols1-1">
<div class="col">
<p>Radio buttons use the same technique as checkboxes above.</p>
</div>
<mct-example><label class="radio custom">Red
<input name="Alarm Status" type="radio" />
<em></em>
</label>
<br />
<label class="radio custom">Orange
<input name="Alarm Status" type="radio" checked />
<em></em>
</label>
<br />
<label class="radio custom">Yellow
<input name="Alarm Status" type="radio" />
<em></em>
</label>
</mct-example>
</div>
</div>
<div class="l-section">
<h2>Selects</h2>
<div class="cols cols1-1">
<div class="col">
<p>Similar to checkboxes and radio buttons, selects use a combination of minimal additional markup with CSS to present a custom and common look-and-feel across platforms. The <code>select</code> element is wrapped by another element, such as a <code>div</code>, which acts as the main display element for the styling. The <code>select</code> provides the click and select functionality, while having all of its native look-and-feel suppressed.</p>
</div>
<mct-example><div class="select">
<select>
<option value="" selected="selected">- Select One -</option>
<option value="Colussus">Colussus</option>
<option value="HAL 9000">HAL 9000</option>
<option value="Mother">Mother</option>
<option value="Skynet">Skynet</option>
</select>
</div>
</mct-example>
</div>
</div>
<div class="l-section">
<h2>Local Controls</h2>
<div class="cols cols1-1">
<div class="col">
<p>Local controls are typically buttons and selects that provide local control to an individual element. Typically, these controls are hidden in order to not block data display until the user hovers their cursor over an element, when the controls are displayed using a transition fade. Mousing out of the element fades the controls from view.</p>
</div>
<mct-example><div class="plot-display-area" style="height: 100px; padding: 10px; position: relative;">Hover over me
<div class="l-local-controls gl-plot-local-controls t-plot-display-controls">
<a class="s-button icon-arrow-left" title="Restore previous pan/zoom"></a>
<a class="s-button icon-arrows-out" title="Reset pan/zoom"></a>
</div>
</div></mct-example>
</div>
</div>
</div>

View File

@ -0,0 +1,205 @@
<!--
Open MCT, Copyright (c) 2014-2016, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
Open MCT is licensed under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
Open MCT includes source code licensed under additional open source
licenses. See the Open Source Licenses file (LICENSES.md) included with
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<div ng-init="general= [{ 'meaning': 'Pay attention', 'cssClass': 'icon-alert-rect', 'cssContent': 'e900', 'htmlEntity': '&amp;#xe900' },
{ 'meaning': 'Warning', 'cssClass': 'icon-alert-triangle', 'cssContent': 'e901', 'htmlEntity': '&amp;#xe901' },
{ 'meaning': 'Invoke menu', 'cssClass': 'icon-arrow-down', 'cssContent': 'e902', 'htmlEntity': '&amp;#xe902' },
{ 'meaning': 'General usage arrow pointing left', 'cssClass': 'icon-arrow-left', 'cssContent': 'e903', 'htmlEntity': '&amp;#xe903' },
{ 'meaning': 'General usage arrow pointing right', 'cssClass': 'icon-arrow-right', 'cssContent': 'e904', 'htmlEntity': '&amp;#xe904' },
{ 'meaning': 'Upper limit, red', 'cssClass': 'icon-arrow-double-up', 'cssContent': 'e905', 'htmlEntity': '&amp;#xe905' },
{ 'meaning': 'Upper limit, yellow', 'cssClass': 'icon-arrow-tall-up', 'cssContent': 'e906', 'htmlEntity': '&amp;#xe906' },
{ 'meaning': 'Lower limit, yellow', 'cssClass': 'icon-arrow-tall-down', 'cssContent': 'e907', 'htmlEntity': '&amp;#xe907' },
{ 'meaning': 'Lower limit, red', 'cssClass': 'icon-arrow-double-down', 'cssContent': 'e908', 'htmlEntity': '&amp;#xe908' },
{ 'meaning': 'General usage arrow pointing up', 'cssClass': 'icon-arrow-up', 'cssContent': 'e909', 'htmlEntity': '&amp;#xe909' },
{ 'meaning': 'Required form element', 'cssClass': 'icon-asterisk', 'cssContent': 'e910', 'htmlEntity': '&amp;#xe910' },
{ 'meaning': 'Alert', 'cssClass': 'icon-bell', 'cssContent': 'e911', 'htmlEntity': '&amp;#xe911' },
{ 'meaning': 'General usage box symbol', 'cssClass': 'icon-box', 'cssContent': 'e912', 'htmlEntity': '&amp;#xe912' },
{ 'meaning': 'Click on or into', 'cssClass': 'icon-box-with-arrow', 'cssContent': 'e913', 'htmlEntity': '&amp;#xe913' },
{ 'meaning': 'General usage checkmark, used in checkboxes; complete', 'cssClass': 'icon-check', 'cssContent': 'e914', 'htmlEntity': '&amp;#xe914' },
{ 'meaning': 'Connected', 'cssClass': 'icon-connectivity', 'cssContent': 'e915', 'htmlEntity': '&amp;#xe915' },
{ 'meaning': 'Status: DB connected', 'cssClass': 'icon-database-in-brackets', 'cssContent': 'e916', 'htmlEntity': '&amp;#xe916' },
{ 'meaning': 'View or make visible', 'cssClass': 'icon-eye-open', 'cssContent': 'e917', 'htmlEntity': '&amp;#xe917' },
{ 'meaning': 'Settings, properties', 'cssClass': 'icon-gear', 'cssContent': 'e918', 'htmlEntity': '&amp;#xe918' },
{ 'meaning': 'Process, progress, time', 'cssClass': 'icon-hourglass', 'cssContent': 'e919', 'htmlEntity': '&amp;#xe919' },
{ 'meaning': 'Info', 'cssClass': 'icon-info', 'cssContent': 'e920', 'htmlEntity': '&amp;#xe920' },
{ 'meaning': 'Link (alias)', 'cssClass': 'icon-link', 'cssContent': 'e921', 'htmlEntity': '&amp;#xe921' },
{ 'meaning': 'Locked', 'cssClass': 'icon-lock', 'cssContent': 'e922', 'htmlEntity': '&amp;#xe922' },
{ 'meaning': 'General usage minus symbol; used in timer object', 'cssClass': 'icon-minus', 'cssContent': 'e923', 'htmlEntity': '&amp;#xe923' },
{ 'meaning': 'An item that is shared', 'cssClass': 'icon-people', 'cssContent': 'e924', 'htmlEntity': '&amp;#xe924' },
{ 'meaning': 'User profile or belonging to an individual', 'cssClass': 'icon-person', 'cssContent': 'e925', 'htmlEntity': '&amp;#xe925' },
{ 'meaning': 'General usage plus symbol; used in timer object', 'cssClass': 'icon-plus', 'cssContent': 'e926', 'htmlEntity': '&amp;#xe926' },
{ 'meaning': 'Delete', 'cssClass': 'icon-trash', 'cssContent': 'e927', 'htmlEntity': '&amp;#xe927' },
{ 'meaning': 'Close, remove', 'cssClass': 'icon-x', 'cssContent': 'e928', 'htmlEntity': '&amp;#xe928' },
{ 'meaning': 'Enclosing, inclusive; used in Time Conductor', 'cssClass': 'icon-brackets', 'cssContent': 'e929', 'htmlEntity': '&amp;#xe929' }
]; controls= [{ 'meaning': 'Reset zoom/pam', 'cssClass': 'icon-arrows-out', 'cssContent': 'e1000', 'htmlEntity': '&amp;#xe1000' },
{ 'meaning': 'Expand vertically', 'cssClass': 'icon-arrows-right-left', 'cssContent': 'e1001', 'htmlEntity': '&amp;#xe1001' },
{ 'meaning': 'View scrolling', 'cssClass': 'icon-arrows-up-down', 'cssContent': 'e1002', 'htmlEntity': '&amp;#xe1002' },
{ 'meaning': 'Bullet; used in radio buttons', 'cssClass': 'icon-bullet', 'cssContent': 'e1004', 'htmlEntity': '&amp;#xe1004' },
{ 'meaning': 'Invoke datetime picker', 'cssClass': 'icon-calendar', 'cssContent': 'e1005', 'htmlEntity': '&amp;#xe1005' },
{ 'meaning': 'Web link', 'cssClass': 'icon-chain-links', 'cssContent': 'e1006', 'htmlEntity': '&amp;#xe1006' },
{ 'meaning': 'Collapse left', 'cssClass': 'icon-collapse-pane-left', 'cssContent': 'e1007', 'htmlEntity': '&amp;#xe1007' },
{ 'meaning': 'Collapse right', 'cssClass': 'icon-collapse-pane-right', 'cssContent': 'e1008', 'htmlEntity': '&amp;#xe1008' },
{ 'meaning': 'Download', 'cssClass': 'icon-download', 'cssContent': 'e1009', 'htmlEntity': '&amp;#xe1009' },
{ 'meaning': 'Copy/Duplicate', 'cssClass': 'icon-duplicate', 'cssContent': 'e1010', 'htmlEntity': '&amp;#xe1010' },
{ 'meaning': 'New folder', 'cssClass': 'icon-folder-new', 'cssContent': 'e1011', 'htmlEntity': '&amp;#xe1011' },
{ 'meaning': 'Exit fullscreen mode', 'cssClass': 'icon-fullscreen-collapse', 'cssContent': 'e1012', 'htmlEntity': '&amp;#xe1012' },
{ 'meaning': 'Display fullscreen', 'cssClass': 'icon-fullscreen-expand', 'cssContent': 'e1013', 'htmlEntity': '&amp;#xe1013' },
{ 'meaning': 'Layer order', 'cssClass': 'icon-layers', 'cssContent': 'e1014', 'htmlEntity': '&amp;#xe1014' },
{ 'meaning': 'Line color', 'cssClass': 'icon-line-horz', 'cssContent': 'e1015', 'htmlEntity': '&amp;#xe1015' },
{ 'meaning': 'Search', 'cssClass': 'icon-magnify', 'cssContent': 'e1016', 'htmlEntity': '&amp;#xe1016' },
{ 'meaning': 'Zoom in', 'cssClass': 'icon-magnify-in', 'cssContent': 'e1017', 'htmlEntity': '&amp;#xe1017' },
{ 'meaning': 'Zoom out', 'cssClass': 'icon-magnify-out', 'cssContent': 'e1018', 'htmlEntity': '&amp;#xe1018' },
{ 'meaning': 'Menu', 'cssClass': 'icon-menu-hamburger', 'cssContent': 'e1019', 'htmlEntity': '&amp;#xe1019' },
{ 'meaning': 'Move', 'cssClass': 'icon-move', 'cssContent': 'e1020', 'htmlEntity': '&amp;#xe1020' },
{ 'meaning': 'Open in new window', 'cssClass': 'icon-new-window', 'cssContent': 'e1021', 'htmlEntity': '&amp;#xe1021' },
{ 'meaning': 'Fill', 'cssClass': 'icon-paint-bucket', 'cssContent': 'e1022', 'htmlEntity': '&amp;#xe1022' },
{ 'meaning': 'Pause real-time streaming', 'cssClass': 'icon-pause', 'cssContent': 'e1023', 'htmlEntity': '&amp;#xe1023' },
{ 'meaning': 'Edit', 'cssClass': 'icon-pencil', 'cssContent': 'e1024', 'htmlEntity': '&amp;#xe1024' },
{ 'meaning': 'Stop pause, resume real-time streaming', 'cssClass': 'icon-play', 'cssContent': 'e1025', 'htmlEntity': '&amp;#xe1025' },
{ 'meaning': 'Plot resources', 'cssClass': 'icon-plot-resource', 'cssContent': 'e1026', 'htmlEntity': '&amp;#xe1026' },
{ 'meaning': 'Previous', 'cssClass': 'icon-pointer-left', 'cssContent': 'e1027', 'htmlEntity': '&amp;#xe1027' },
{ 'meaning': 'Next, navigate to', 'cssClass': 'icon-pointer-right', 'cssContent': 'e1028', 'htmlEntity': '&amp;#xe1028' },
{ 'meaning': 'Refresh', 'cssClass': 'icon-refresh', 'cssContent': 'e1029', 'htmlEntity': '&amp;#xe1029' },
{ 'meaning': 'Save', 'cssClass': 'icon-save', 'cssContent': 'e1030', 'htmlEntity': '&amp;#xe1030' },
{ 'meaning': 'View plot', 'cssClass': 'icon-sine', 'cssContent': 'e1031', 'htmlEntity': '&amp;#xe1031' },
{ 'meaning': 'Text color', 'cssClass': 'icon-T', 'cssContent': 'e1032', 'htmlEntity': '&amp;#xe1032' },
{ 'meaning': 'Image thumbs strip; view items grid', 'cssClass': 'icon-thumbs-strip', 'cssContent': 'e1033', 'htmlEntity': '&amp;#xe1033' },
{ 'meaning': 'Two part item, both parts', 'cssClass': 'icon-two-parts-both', 'cssContent': 'e1034', 'htmlEntity': '&amp;#xe1034' },
{ 'meaning': 'Two part item, one only', 'cssClass': 'icon-two-parts-one-only', 'cssContent': 'e1035', 'htmlEntity': '&amp;#xe1035' },
{ 'meaning': 'Clear', 'cssClass': 'icon-x-in-circle', 'cssContent': 'e1038', 'htmlEntity': '&amp;#xe1038' },
{ 'meaning': 'Brightness', 'cssClass': 'icon-brightness', 'cssContent': 'e1039', 'htmlEntity': '&amp;#xe1039' },
{ 'meaning': 'Contrast', 'cssClass': 'icon-contrast', 'cssContent': 'e1040', 'htmlEntity': '&amp;#xe1040' },
{ 'meaning': 'Expand', 'cssClass': 'icon-expand', 'cssContent': 'e1041', 'htmlEntity': '&amp;#xe1041' }
]; objects= [{ 'meaning': 'Activity', 'cssClass': 'icon-activity', 'cssContent': 'e1100', 'htmlEntity': '&amp;#xe1100' },
{ 'meaning': 'Activity Mode', 'cssClass': 'icon-activity-mode', 'cssContent': 'e1101', 'htmlEntity': '&amp;#xe1101' },
{ 'meaning': 'Auto-flow Tabular view', 'cssClass': 'icon-autoflow-tabular', 'cssContent': 'e1102', 'htmlEntity': '&amp;#xe1102' },
{ 'meaning': 'Clock object type', 'cssClass': 'icon-clock', 'cssContent': 'e1103', 'htmlEntity': '&amp;#xe1103' },
{ 'meaning': 'Database', 'cssClass': 'icon-database', 'cssContent': 'e1104', 'htmlEntity': '&amp;#xe1104' },
{ 'meaning': 'Data query', 'cssClass': 'icon-database-query', 'cssContent': 'e1105', 'htmlEntity': '&amp;#xe1105' },
{ 'meaning': 'Data Set domain object', 'cssClass': 'icon-dataset', 'cssContent': 'e1106', 'htmlEntity': '&amp;#xe1106' },
{ 'meaning': 'Datatable, channel table', 'cssClass': 'icon-datatable', 'cssContent': 'e1107', 'htmlEntity': '&amp;#xe1107' },
{ 'meaning': 'Dictionary', 'cssClass': 'icon-dictionary', 'cssContent': 'e1108', 'htmlEntity': '&amp;#xe1108' },
{ 'meaning': 'Folder', 'cssClass': 'icon-folder', 'cssContent': 'e1109', 'htmlEntity': '&amp;#xe1109' },
{ 'meaning': 'Imagery', 'cssClass': 'icon-image', 'cssContent': 'e1110', 'htmlEntity': '&amp;#xe1110' },
{ 'meaning': 'Display Layout', 'cssClass': 'icon-layout', 'cssContent': 'e1111', 'htmlEntity': '&amp;#xe1111' },
{ 'meaning': 'Generic Object', 'cssClass': 'icon-object', 'cssContent': 'e1112', 'htmlEntity': '&amp;#xe1112' },
{ 'meaning': 'Unknown object type', 'cssClass': 'icon-object-unknown', 'cssContent': 'e1113', 'htmlEntity': '&amp;#xe1113' },
{ 'meaning': 'Packet domain object', 'cssClass': 'icon-packet', 'cssContent': 'e1114', 'htmlEntity': '&amp;#xe1114' },
{ 'meaning': 'Page', 'cssClass': 'icon-page', 'cssContent': 'e1115', 'htmlEntity': '&amp;#xe1115' },
{ 'meaning': 'Overlay plot', 'cssClass': 'icon-plot-overlay', 'cssContent': 'e1116', 'htmlEntity': '&amp;#xe1116' },
{ 'meaning': 'Stacked plot', 'cssClass': 'icon-plot-stacked', 'cssContent': 'e1117', 'htmlEntity': '&amp;#xe1117' },
{ 'meaning': 'Session object', 'cssClass': 'icon-session', 'cssContent': 'e1118', 'htmlEntity': '&amp;#xe1118' },
{ 'meaning': 'Table', 'cssClass': 'icon-tabular', 'cssContent': 'e1119', 'htmlEntity': '&amp;#xe1119' },
{ 'meaning': 'Latest available data object', 'cssClass': 'icon-tabular-lad', 'cssContent': 'e1120', 'htmlEntity': '&amp;#xe1120' },
{ 'meaning': 'Latest available data set', 'cssClass': 'icon-tabular-lad-set', 'cssContent': 'e1121', 'htmlEntity': '&amp;#xe1121' },
{ 'meaning': 'Real-time table view', 'cssClass': 'icon-tabular-realtime', 'cssContent': 'e1122', 'htmlEntity': '&amp;#xe1122' },
{ 'meaning': 'Real-time scrolling table', 'cssClass': 'icon-tabular-scrolling', 'cssContent': 'e1123', 'htmlEntity': '&amp;#xe1123' },
{ 'meaning': 'Telemetry element', 'cssClass': 'icon-telemetry', 'cssContent': 'e1124', 'htmlEntity': '&amp;#xe1124' },
{ 'meaning': 'Telemetry Panel object', 'cssClass': 'icon-telemetry-panel', 'cssContent': 'e1125', 'htmlEntity': '&amp;#xe1125' },
{ 'meaning': 'Timeline object', 'cssClass': 'icon-timeline', 'cssContent': 'e1126', 'htmlEntity': '&amp;#xe1126' },
{ 'meaning': 'Timer object', 'cssClass': 'icon-timer', 'cssContent': 'e1127', 'htmlEntity': '&amp;#xe1127' },
{ 'meaning': 'Data Topic', 'cssClass': 'icon-topic', 'cssContent': 'e1128', 'htmlEntity': '&amp;#xe1128' },
{ 'meaning': 'Fixed Position object', 'cssClass': 'icon-box-with-dashed-lines', 'cssContent': 'e1129', 'htmlEntity': '&amp;#xe1129' }
];
"></div>
<div class="l-style-guide s-text">
<p class="doc-title">Open MCT Style Guide</p>
<h1>Glyphs</h1>
<div class="l-section">
<p>Symbolic glyphs are used extensively in Open MCT to call attention to interactive elements, identify objects, and aid in visual recall. Glyphs are made available in a custom symbols font, and have associated CSS classes for their usage. Using a font in this way (versus using images or sprites) has advantages in that each symbol is in effect a scalable vector that can be sized up or down as needed. Color can also quite easily be applied via CSS.</p>
<p>New glyphs can be added if needed. Take care to observe the following guidelines:
<ul>
<li>Symbols should be created at 512 pixels high, and no more than 512 pixels wide. This size is based on a &quot;crisp&quot; 16px approach. Find out more about <a class="link" target="_blank" href="http://asimpleframe.com/writing/custom-icon-font-tutorial-icomoon">crisp symbol fonts</a>.</li>
<li>In general, the symbol should occupy most of a square area as possible; avoid symbol aspect ratios that are squat or tall.</li>
<li>For consistency and legibility, symbols are designed as mostly solid shapes. Avoid using thin lines or fine detail that will be lost when the icon is sized down. In general, no stroke should be less than 32 pixels.</li>
<li>Symbols should be legible down to a minimum of 12 x 12 pixels.</li>
</ul>
</p>
</div>
<div class="l-section">
<h2>How to Use Glyphs</h2>
<div class="cols cols1-1">
<div class="col">
<p>The easiest way to use a glyph is to include its CSS class in element. The CSS adds a psuedo <code>:before</code> HTML element to whatever element it's attached to that makes proper use of the symbols font.</p>
<p>Alternately, you can use the <code>.ui-symbol</code> class in an object that contains encoded HTML entities. This method is only recommended if you cannot use the aforementioned CSS class approach for some reason.</p>
</div>
<mct-example><a class="s-button icon-gear" title="Settings"></a>
<br /><br />
<a class="s-icon-button icon-gear" title="Settings"></a>
<br /><br />
<div class="ui-symbol">&#xe901 &#xe914 &#xe922</div>
</mct-example>
</div>
</div>
<div class="l-section">
<h2>General User Interface Glyphs</h2>
<p>Glyphs suitable for denoting general user interface verbs and nouns.</p>
<div class="items-holder grid">
<div class="item glyph-item" ng-repeat="glyph in general">
<div class="glyph" ng-class="glyph.cssClass"></div>
<table class="details">
<tr><td class="label">Meaning</td><td class="value">{{glyph.meaning}}</td></tr>
<tr><td class="label">Class</td><td class="value">.{{glyph.cssClass}}</td></tr>
<tr><td class="label">CSS Content</td><td class="value">\{{glyph.cssContent}}</td></tr>
<tr><td class="label">HTML Entity</td><td class="value">{{glyph.htmlEntity}}</td></tr>
</table>
</div>
</div>
</div>
<div class="l-section">
<h2>Control Glyphs</h2>
<p>Glyphs created for use in various controls.</p>
<div class="items-holder grid">
<div class="item glyph-item" ng-repeat="glyph in controls">
<div class="glyph" ng-class="glyph.cssClass"></div>
<table class="details">
<tr><td class="label">Meaning</td><td class="value">{{glyph.meaning}}</td></tr>
<tr><td class="label">Class</td><td class="value">.{{glyph.cssClass}}</td></tr>
<tr><td class="label">CSS Content</td><td class="value">\{{glyph.cssContent}}</td></tr>
<tr><td class="label">HTML Entity</td><td class="value">{{glyph.htmlEntity}}</td></tr>
</table>
</div>
</div>
</div>
<div class="l-section">
<h2>Object Type Glyphs</h2>
<p>These glyphs are reserved exclusively to denote types of objects in the application. Only use them if you are referring to a pre-existing object type.</p>
<div class="items-holder grid">
<div class="item glyph-item" ng-repeat="glyph in objects">
<div class="glyph" ng-class="glyph.cssClass"></div>
<table class="details">
<tr><td class="label">Meaning</td><td class="value">{{glyph.meaning}}</td></tr>
<tr><td class="label">Class</td><td class="value">.{{glyph.cssClass}}</td></tr>
<tr><td class="label">CSS Content</td><td class="value">\{{glyph.cssContent}}</td></tr>
<tr><td class="label">HTML Entity</td><td class="value">{{glyph.htmlEntity}}</td></tr>
</table>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,75 @@
<!--
Open MCT, Copyright (c) 2014-2016, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
Open MCT is licensed under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
Open MCT includes source code licensed under additional open source
licenses. See the Open Source Licenses file (LICENSES.md) included with
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<div class="l-style-guide s-text">
<p class="doc-title">Open MCT Style Guide</p>
<h1>Text Input</h1>
<div class="l-section">
<p>Text inputs and textareas have a consistent look-and-feel across the application. The input's <code>placeholder</code> attribute is styled to appear visually different from an entered value.</p>
</div>
<div class="l-section">
<h2>Text Inputs</h2>
<div class="cols cols1-1">
<div class="col">
<p>Use a text input where the user should enter relatively short text entries.</p>
<p>A variety of size styles are available: <code>.lg</code>, <code>.med</code> and <code>.sm</code>. <code>.lg</code> text inputs dynamically scale their width to 100% of their container's width. Numeric inputs that benefit from right-alignment can be styled by adding <code>.numeric</code>.</p>
</div>
<mct-example><input type="text" placeholder="Enter a value" />
<br /><br />
<input type="text" placeholder="Enter a value" value="An entered value" />
<br /><br />
<input type="text" placeholder="Enter a value" class="sm" value="Small" />
<br /><br />
<input type="text" placeholder="Enter a value" class="med" value="A medium input" />
<br /><br />
<input type="text" placeholder="Enter a value" class="lg" value="A large input" />
<br /><br />
<input type="text" placeholder="Enter a value" class="sm numeric" value="10.9" />
</mct-example>
</div>
</div>
<div class="l-section">
<h2>Textareas</h2>
<div class="cols cols1-1">
<div class="col">
<p>Use a textarea where the user should enter relatively longer or multi-line text entries.</p>
<p>By default, textareas are styled to expand to 100% of the width and height of their container; additionally there are three size styles available that control the height of the element: <code>.lg</code>, <code>.med</code> and <code>.sm</code>.</p>
</div>
<mct-example><div style="position: relative; height: 100px">
<textarea placeholder="Enter a value"></textarea>
</div>
<br />
<div style="position: relative; height: 100px">
<textarea placeholder="Enter a value">An entered value</textarea>
</div>
<br /><br />
<textarea placeholder="Enter a value" class="sm">A small textarea</textarea>
<br /><br />
<textarea placeholder="Enter a value" class="med">A medium textarea</textarea>
<br /><br />
<textarea placeholder="Enter a value" class="lg">A large textarea</textarea>
</mct-example>
</div>
</div>
</div>

View File

@ -0,0 +1,73 @@
<!--
Open MCT, Copyright (c) 2014-2016, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
Open MCT is licensed under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
Open MCT includes source code licensed under additional open source
licenses. See the Open Source Licenses file (LICENSES.md) included with
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<div class="l-style-guide s-text">
<p class="doc-title">Open MCT Style Guide</p>
<h1>Introduction</h1>
<div class="l-section">
<p>Open MCT is a robust, extensible telemetry monitoring and situational awareness system that provides a framework supporting fast and efficient multi-mission deployment. This guide will explore the major concepts and design elements of Open MCT. Its overall goal is to guide you in creating new features and plugins that seamlessly integrate with the base application.</p>
</div>
<div class="l-section">
<h2>Everything Is An Object</h2>
<div class="cols cols1-1">
<div class="col">
<p>First and foremost, Open MCT uses a “object-oriented” approach: everything in the system is an object. Objects come in different types, and some objects can contain other objects of given types. This is similar to how the file management system of all modern computers works: a folder object can contain any other type of object, a presentation file can contain an image. This is conceptually the same in Open MCT.</p>
<p>As you develop plugins for Open MCT, consider how a generalized component might be combined with others when designing to create a rich and powerful larger object, rather than adding a single monolithic, non-modular plugin. To solve a particular problem or allow a new feature in Open MCT, you may need to introduce more than just one new object type.</p>
</div>
<div class="col">
<img src="/example/styleguide/res/images/diagram-objects.svg" />
</div>
</div>
</div>
<div class="l-section">
<h2>Object Types</h2>
<div class="cols cols1-1">
<div class="col">
<p>In the same way that different types of files might be opened and edited by different applications, objects in Open MCT also have different types. For example, a Display Layout provides a way that other objects that display information can be combined and laid out in a canvas area to create a recallable display that suits the needs of the user that created it. A Telemetry Panel allows a user to collect together Telemetry Points and visualize them as a plot or a table.</p>
<p>Object types provide a containment model that guides the user in their choices while creating a new object, and allows view normalization when flipping between different views. When a given object may only contain other objects of certain types, advantages emerge: the result of adding new objects is more predictable, more alternate views can be provided because the similarities between the contained objects is close, and we can provide more helpful and pointed guidance to the user because we know what types of objects they might be working with at a given time.</p>
<p>The types of objects that a container can hold should be based on the purpose of the container and the views that it affords. For example, a Folders purpose is to allow a user to conceptually organize objects of all other types; a Folder must therefore be able to contain an object of any type.</p>
</div>
<div class="col">
<img src="/example/styleguide/res/images/diagram-containment.svg" />
</div>
</div>
</div>
<div class="l-section">
<h2>Object Views</h2>
<div class="cols cols1-1">
<div class="col">
<p>Views are simply different ways to view the content of a given object. For example, telemetry data could be viewed as a plot or a table. A clock can display its time in analog fashion or with digital numbers. In each view, all of the content is present; its just represented differently. When providing views for an object, all the content of the object should be present in each view.</p>
</div>
<div class="col">
<img src="/example/styleguide/res/images/diagram-views.svg" />
</div>
</div>
</div>
<p></p>
<p></p>
<p></p>
</div>

View File

@ -0,0 +1,8 @@
<div class="col">
<h3>Markup</h3>
<span class="w-markup">
<pre></pre>
</span>
<h3>Example</h3>
<div></div>
</div>

View File

@ -0,0 +1,168 @@
<!--
Open MCT, Copyright (c) 2014-2016, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
Open MCT is licensed under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
Open MCT includes source code licensed under additional open source
licenses. See the Open Source Licenses file (LICENSES.md) included with
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<div class="l-style-guide s-text">
<p class="doc-title">Open MCT Style Guide</p>
<h1>Menus</h1>
<div class="l-section">
<h2>Context Menus</h2>
<div class="cols cols1-1">
<div class="col">
<p>Context menus are used extensively in Open MCT. They are created dynamically upon a contextual click and positioned at the user's cursor position coincident with the element that invoked them. Context menus must use absolute position and utilize a z-index that places them above other in-page elements.</p>
<p>See <a class="link" href="http://localhost:8080/#/browse/styleguide:home/controls?view=styleguide.standards">User Interface Standards</a> for more details on z-indexing in Open MCT. Context menus should be destroyed if the user clicks outside the menu element.</p>
</div>
<mct-example><div style="height: 120px">
<div class="menu-element context-menu-wrapper mobile-disable-select">
<div class="menu context-menu">
<ul>
<li onclick="alert('Perform an action')" title="Open in a new browser tab" class="icon-new-window">Open In New Tab</li>
<li onclick="alert('Perform an action')" title="Remove this object from its containing object." class="icon-trash">Remove</li>
<li onclick="alert('Perform an action')" title="Create Link to object in another location." class="icon-link">Create Link</li>
</ul>
</div>
</div>
</div></mct-example>
</div>
</div>
<div class="l-section">
<h2>Dropdown Menus</h2>
<div class="cols cols1-1">
<div class="col">
<p>Dropdown menus are a dedicated, more discoverable context menu for a given object. Like context menus, dropdown menus are used extensively in Open MCT, and are most often associated with object header elements. They visually manifest as a downward pointing arrow <span class="context-available"></span> associated with an element, and when clicked displays a context menu at that location. See guidelines above about context menus in regards to z-indexing and element lifecycle.</p>
<p>Use a dropdown menu to encapsulate important the actions of an object in the object's header, or in a place that you'd use a context menu, but want to make the availability of the menu more apparent.</p>
</div>
<mct-example><div style="height: 220px" title="Ignore me, I'm just here to provide space for this example.">
<div class="l-flex-row flex-elem grows object-header">
<span class="type-icon flex-elem icon-layout"></span>
<span class="l-elem-wrapper l-flex-row flex-elem grows">
<span class="title-label flex-elem holder flex-can-shrink ng-binding">Object Header</span>
<span class="flex-elem context-available-w">
<span ng-controller="MenuArrowController as menuArrow" class="ng-scope">
<a class="context-available" ng-click="menuArrow.showMenu($event)"></a>
</span>
</span>
</span>
</div>
</div></mct-example>
</div>
</div>
<div class="l-section">
<h2>Checkbox Menus</h2>
<div class="cols cols1-1">
<div class="col">
<p>Checkbox menus add checkbox options to each item of a dropdown menu. Use this to </p>
<p>Use a dropdown menu to encapsulate important the actions of an object in the object's header, or in a place that you'd use a context menu, but want to make the availability of the menu more apparent.</p>
</div>
<mct-example><div style="height: 220px" title="Ignore me, I'm just here to provide space for this example.">
<div ng-controller="SearchMenuController as controller" class="ng-scope">
<div class="menu checkbox-menu" mct-click-elsewhere="parameters.menuVisible(false)">
<ul>
<!-- First element is special - it's a reset option -->
<li class="search-menu-item special icon-asterisk" title="Select all filters" ng-click="ngModel.checkAll = !ngModel.checkAll; controller.checkAll()">
<label class="checkbox custom no-text">
<input type="checkbox" class="checkbox ng-untouched ng-valid ng-dirty" ng-model="ngModel.checkAll" ng-change="controller.checkAll()">
<em></em>
</label>
All
</li>
<li class="search-menu-item icon-folder">
<label class="checkbox custom no-text">
<input type="checkbox" class="checkbox">
<em></em>
</label>
Folder
</li>
<li class="search-menu-item icon-layout">
<label class="checkbox custom no-text">
<input type="checkbox" class="checkbox">
<em></em>
</label>
Display Layout
</li>
<li class="search-menu-item icon-box-with-dashed-lines">
<label class="checkbox custom no-text">
<input type="checkbox" class="checkbox">
<em></em>
</label>
Fixed Position Display
</li>
</ul>
</div>
</div>
</div>
</mct-example>
</div>
</div>
<div class="l-section">
<h2>Palettes</h2>
<div class="cols cols1-1">
<div class="col">
<p>Use a palette to provide color choices. Similar to context menus and dropdowns, palettes should be dismissed when a choice is made within them, or if the user clicks outside one.</p>
<p>Note that while this example uses static markup for illustrative purposes, don't do this - use a front-end framework with repeaters to build the color choices.</p>
</div>
<mct-example><div style="height: 220px" title="Ignore me, I'm just here to provide space for this example.">
<div class="s-button s-menu-button menu-element t-color-palette icon-paint-bucket" ng-controller="ClickAwayController as toggle">
<span class="l-click-area" ng-click="toggle.toggle()"></span>
<span class="color-swatch" style="background: rgb(255, 0, 0);"></span>
<div class="menu l-color-palette" ng-show="toggle.isActive()">
<div class="l-palette-row l-option-row">
<div class="l-palette-item s-palette-item " ng-click="ngModel[field] = 'transparent'"></div>
<span class="l-palette-item-label">None</span>
</div>
<div class="l-palette-row">
<div class="l-palette-item s-palette-item" style="background: rgb(0, 0, 0);"></div>
<div class="l-palette-item s-palette-item" style="background: rgb(28, 28, 28);"></div>
<div class="l-palette-item s-palette-item" style="background: rgb(57, 57, 57);"></div>
<div class="l-palette-item s-palette-item" style="background: rgb(85, 85, 85);"></div>
<div class="l-palette-item s-palette-item" style="background: rgb(113, 113, 113);"></div>
<div class="l-palette-item s-palette-item" style="background: rgb(142, 142, 142);"></div>
<div class="l-palette-item s-palette-item" style="background: rgb(170, 170, 170);"></div>
<div class="l-palette-item s-palette-item" style="background: rgb(198, 198, 198);"></div>
<div class="l-palette-item s-palette-item" style="background: rgb(227, 227, 227);"></div>
<div class="l-palette-item s-palette-item" style="background: rgb(255, 255, 255);"></div>
</div>
<div class="l-palette-row">
<div class="l-palette-item s-palette-item" style="background: rgb(136, 32, 32);"></div>
<div class="l-palette-item s-palette-item" style="background: rgb(224, 64, 64);"></div>
<div class="l-palette-item s-palette-item" style="background: rgb(240, 160, 72);"></div>
<div class="l-palette-item s-palette-item" style="background: rgb(255, 248, 96);"></div>
<div class="l-palette-item s-palette-item" style="background: rgb(128, 240, 72);"></div>
<div class="l-palette-item s-palette-item" style="background: rgb(128, 248, 248);"></div>
<div class="l-palette-item s-palette-item" style="background: rgb(88, 144, 224);"></div>
<div class="l-palette-item s-palette-item" style="background: rgb(0, 72, 240);"></div>
<div class="l-palette-item s-palette-item" style="background: rgb(136, 80, 240);"></div>
<div class="l-palette-item s-palette-item" style="background: rgb(224, 96, 248);"></div>
</div>
</div>
</div>
</div></mct-example>
</div>
</div>
</div>

View File

@ -0,0 +1,48 @@
<!--
Open MCT, Copyright (c) 2014-2016, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
Open MCT is licensed under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
Open MCT includes source code licensed under additional open source
licenses. See the Open Source Licenses file (LICENSES.md) included with
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<div class="l-style-guide s-text">
<p class="doc-title">Open MCT Style Guide</p>
<h1>Standards</h1>
<div class="l-section">
<h2>Absolute Positioning and Z-Indexing</h2>
<p>Absolute positioning is used in Open MCT in the main envelope interface to handle layout and draggable pane splitters, for elements that must be dynamically created and positioned (like context menus) and for buttons that are placed over other elements, such as a plot's zoom/pan history and reset buttons. When using absolute positioning, follow these guidelines:</p>
<ul>
<li>Don't specify a z-index if you don't have to.</li>
<li>If you must specify a z-index, use the lowest number you that prevents your element from being covered and puts it at the correct level per the table below.</li>
</ul>
<!-- This content maintained at https://docs.google.com/spreadsheets/d/1AzhUY0P3hLCfT8yPa2Cb1dwOOsQXBuSgCrOkhIoVm0A/edit#gid=0 -->
<table>
<tr class='header'><td>Type</td><td>Description</td><td>Z-index Range</td></tr>
<tr><td>Base interface items</td><td>Base level elements</td><td>0 - 1</td></tr>
<tr><td>Primary pane</td><td>Elements in the primary "view area" pane</td><td>2</td></tr>
<tr><td>Inspector pane, splitters</td><td>Elements in the Inspector, and splitters themselves</td><td>3</td></tr>
<tr><td>More base interface stuff</td><td>Base level elements</td><td>4 - 9</td></tr>
<tr><td>Treeview</td><td>Lefthand treeview elements</td><td>30 - 39</td></tr>
<tr><td>Help bubbles, rollover hints</td><td>Infobubbles, and similar</td><td>50 - 59</td></tr>
<tr><td>Context, button and dropdown menus</td><td>Context menus, button menus, etc. that must overlay other elements</td><td>70 - 79</td></tr>
<tr><td>Overlays</td><td>Modal overlay displays</td><td>100 - 109</td></tr>
<tr><td>Event messages</td><td>Alerts, event dialogs</td><td>1000</td></tr>
</table>
</div>
</div>

View File

@ -0,0 +1,50 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2016, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define(
[],
function () {
"use strict";
function ExampleStyleGuideModelProvider($q) {
var pages = {};
// Add pages
pages['intro'] = { name: "Introduction", type: "styleguide.intro", location: "styleguide:home" };
pages['standards'] = { name: "Standards", type: "styleguide.standards", location: "styleguide:home" };
pages['colors'] = { name: "Colors", type: "styleguide.colors", location: "styleguide:home" };
pages['glyphs'] = { name: "Glyphs", type: "styleguide.glyphs", location: "styleguide:home" };
pages['controls'] = { name: "Controls", type: "styleguide.controls", location: "styleguide:ui-elements" };
pages['input'] = { name: "Text Inputs", type: "styleguide.input", location: "styleguide:ui-elements" };
pages['menus'] = { name: "Menus", type: "styleguide.menus", location: "styleguide:ui-elements" };
return {
getModels: function () {
return $q.when(pages);
}
};
}
return ExampleStyleGuideModelProvider
}
);

View File

@ -0,0 +1,30 @@
define([
'text!../res/templates/mct-example.html'
], function (
MCTExampleTemplate
) {
function MCTExample() {
function link($scope, $element, $attrs, controller, $transclude) {
var codeEl = $element.find('pre');
var exampleEl = $element.find('div');
$transclude(function (clone) {
exampleEl.append(clone);
codeEl.text(exampleEl.html()
.replace(/ class="ng-scope"/g, "")
.replace(/ ng-scope"/g, '"'));
});
}
return {
restrict: "E",
template: MCTExampleTemplate,
transclude: true,
link: link,
replace: true
};
}
return MCTExample;
});

View File

@ -28,10 +28,13 @@
<script src="bower_components/requirejs/require.js">
</script>
<script>
var THIRTY_MINUTES = 30 * 60 * 1000;
require(['openmct'], function (openmct) {
[
'example/imagery',
'example/eventGenerator'
'example/eventGenerator',
'example/styleguide'
].forEach(
openmct.legacyRegistry.enable.bind(openmct.legacyRegistry)
);
@ -40,6 +43,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>

View File

@ -37,6 +37,7 @@ module.exports = function(config) {
{pattern: 'bower_components/**/*.js', included: false},
{pattern: 'node_modules/d3-*/**/*.js', included: false},
{pattern: 'src/**/*.js', included: false},
{pattern: 'example/**/*.html', included: false},
{pattern: 'example/**/*.js', included: false},
{pattern: 'example/**/*.json', included: false},
{pattern: 'platform/**/*.js', included: false},

View File

@ -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;
});

View File

@ -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",

View File

@ -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
*/

View File

@ -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());
});
});
}
);

View File

@ -1,8 +1,8 @@
{
"metadata": {
"name": "openmct-symbols-16px",
"lastOpened": 1481575258437,
"created": 1481575255265
"lastOpened": 1487197651675,
"created": 1487194069058
},
"iconSets": [
{
@ -540,13 +540,21 @@
"code": 921654,
"tempChar": ""
},
{
"order": 120,
"id": 105,
"name": "icon-reset",
"prevSize": 24,
"code": 921655,
"tempChar": ""
},
{
"order": 121,
"id": 103,
"name": "icon-x-in-circle",
"prevSize": 24,
"code": 921656,
"tempChar": ""
"tempChar": ""
},
{
"order": 118,
@ -554,7 +562,7 @@
"name": "icon-brightness",
"prevSize": 24,
"code": 921657,
"tempChar": ""
"tempChar": ""
},
{
"order": 119,
@ -562,15 +570,15 @@
"name": "icon-contrast",
"prevSize": 24,
"code": 921664,
"tempChar": ""
"tempChar": ""
},
{
"order": 120,
"id": 105,
"name": "icon-reset",
"order": 122,
"id": 106,
"name": "icon-expand",
"prevSize": 24,
"code": 921655,
"tempChar": ""
"code": 921665,
"tempChar": ""
},
{
"order": 37,
@ -578,7 +586,7 @@
"name": "icon-activity",
"id": 32,
"code": 921856,
"tempChar": ""
"tempChar": ""
},
{
"order": 36,
@ -586,7 +594,7 @@
"name": "icon-activity-mode",
"id": 31,
"code": 921857,
"tempChar": ""
"tempChar": ""
},
{
"order": 52,
@ -594,7 +602,7 @@
"name": "icon-autoflow-tabular",
"id": 47,
"code": 921858,
"tempChar": ""
"tempChar": ""
},
{
"order": 55,
@ -602,7 +610,7 @@
"name": "icon-clock",
"id": 50,
"code": 921859,
"tempChar": ""
"tempChar": ""
},
{
"order": 58,
@ -610,7 +618,7 @@
"name": "icon-database",
"id": 53,
"code": 921860,
"tempChar": ""
"tempChar": ""
},
{
"order": 57,
@ -618,7 +626,7 @@
"name": "icon-database-query",
"id": 52,
"code": 921861,
"tempChar": ""
"tempChar": ""
},
{
"order": 17,
@ -626,7 +634,7 @@
"name": "icon-dataset",
"id": 12,
"code": 921862,
"tempChar": ""
"tempChar": ""
},
{
"order": 22,
@ -634,7 +642,7 @@
"name": "icon-datatable",
"id": 17,
"code": 921863,
"tempChar": ""
"tempChar": ""
},
{
"order": 59,
@ -642,7 +650,7 @@
"name": "icon-dictionary",
"id": 54,
"code": 921864,
"tempChar": ""
"tempChar": ""
},
{
"order": 62,
@ -650,7 +658,7 @@
"name": "icon-folder",
"id": 57,
"code": 921865,
"tempChar": ""
"tempChar": ""
},
{
"order": 66,
@ -658,7 +666,7 @@
"name": "icon-image",
"id": 61,
"code": 921872,
"tempChar": ""
"tempChar": ""
},
{
"order": 68,
@ -666,7 +674,7 @@
"name": "icon-layout",
"id": 63,
"code": 921873,
"tempChar": ""
"tempChar": ""
},
{
"order": 77,
@ -674,7 +682,7 @@
"name": "icon-object",
"id": 72,
"code": 921874,
"tempChar": ""
"tempChar": ""
},
{
"order": 78,
@ -682,7 +690,7 @@
"name": "icon-object-unknown",
"id": 73,
"code": 921875,
"tempChar": ""
"tempChar": ""
},
{
"order": 79,
@ -690,7 +698,7 @@
"name": "icon-packet",
"id": 74,
"code": 921876,
"tempChar": ""
"tempChar": ""
},
{
"order": 80,
@ -698,7 +706,7 @@
"name": "icon-page",
"id": 75,
"code": 921877,
"tempChar": ""
"tempChar": ""
},
{
"order": 114,
@ -706,7 +714,7 @@
"name": "icon-plot-overlay",
"prevSize": 24,
"code": 921878,
"tempChar": ""
"tempChar": ""
},
{
"order": 113,
@ -714,7 +722,7 @@
"name": "icon-plot-stacked",
"prevSize": 24,
"code": 921879,
"tempChar": ""
"tempChar": ""
},
{
"order": 10,
@ -722,7 +730,7 @@
"name": "icon-session",
"id": 5,
"code": 921880,
"tempChar": ""
"tempChar": ""
},
{
"order": 24,
@ -730,7 +738,7 @@
"name": "icon-tabular",
"id": 19,
"code": 921881,
"tempChar": ""
"tempChar": ""
},
{
"order": 7,
@ -738,7 +746,7 @@
"name": "icon-tabular-lad",
"id": 2,
"code": 921888,
"tempChar": ""
"tempChar": ""
},
{
"order": 6,
@ -746,7 +754,7 @@
"name": "icon-tabular-lad-set",
"id": 1,
"code": 921889,
"tempChar": ""
"tempChar": ""
},
{
"order": 8,
@ -754,7 +762,7 @@
"name": "icon-tabular-realtime",
"id": 3,
"code": 921890,
"tempChar": ""
"tempChar": ""
},
{
"order": 23,
@ -762,7 +770,7 @@
"name": "icon-tabular-scrolling",
"id": 18,
"code": 921891,
"tempChar": ""
"tempChar": ""
},
{
"order": 112,
@ -770,7 +778,7 @@
"name": "icon-telemetry",
"id": 86,
"code": 921892,
"tempChar": ""
"tempChar": ""
},
{
"order": 90,
@ -778,7 +786,7 @@
"name": "icon-telemetry-panel",
"id": 85,
"code": 921893,
"tempChar": ""
"tempChar": ""
},
{
"order": 93,
@ -786,7 +794,7 @@
"name": "icon-timeline",
"id": 88,
"code": 921894,
"tempChar": ""
"tempChar": ""
},
{
"order": 116,
@ -794,7 +802,7 @@
"name": "icon-timer-v1.5",
"prevSize": 24,
"code": 921895,
"tempChar": ""
"tempChar": ""
},
{
"order": 11,
@ -802,7 +810,7 @@
"name": "icon-topic",
"id": 6,
"code": 921896,
"tempChar": ""
"tempChar": ""
},
{
"order": 115,
@ -810,7 +818,7 @@
"name": "icon-box-with-dashed-lines",
"id": 29,
"code": 921897,
"tempChar": ""
"tempChar": ""
}
],
"metadata": {
@ -2183,6 +2191,30 @@
]
}
},
{
"id": 104,
"paths": [
"M460.8 460.8l-187.8-187.8c57.2-42.8 128-68.2 204.8-68.2 188.2 0 341.6 153.2 341.6 341.4s-153.2 341.2-341.4 341.2c-165 0-302.8-117.6-334.6-273h-138.4c14.2 101.8 61 195.6 135 269.6 90.2 90.2 210.4 140 338 140s247.6-49.8 338-140 140-210.4 140-338-49.8-247.6-140-338-210.4-140-338-140c-111.4 0-217 38-302 107.6l-176-175.6v460.8h460.8z"
],
"attrs": [
{
"fill": "rgb(0, 161, 75)"
}
],
"isMulticolor": false,
"isMulticolor2": false,
"grid": 16,
"tags": [
"icon-reset"
],
"colorPermutations": {
"1161751207457516161751": [
{
"f": 1
}
]
}
},
{
"paths": [
"M512 0c-282.8 0-512 229.2-512 512s229.2 512 512 512 512-229.2 512-512-229.2-512-512-512zM832 704l-128 128-192-192-192 192-128-128 192-192-192-192 128-128 192 192 192-192 128 128-192 192 192 192z"
@ -2303,26 +2335,31 @@
}
},
{
"id": 104,
"id": 106,
"paths": [
"M460.8 460.8l-187.8-187.8c57.2-42.8 128-68.2 204.8-68.2 188.2 0 341.6 153.2 341.6 341.4s-153.2 341.2-341.4 341.2c-165 0-302.8-117.6-334.6-273h-138.4c14.2 101.8 61 195.6 135 269.6 90.2 90.2 210.4 140 338 140s247.6-49.8 338-140 140-210.4 140-338-49.8-247.6-140-338-210.4-140-338-140c-111.4 0-217 38-302 107.6l-176-175.6v460.8h460.8z"
"M960 0c0 0 0 0 0 0h-320v128h165.4l-210.6 210.8c-25 25-25 65.6 0 90.6 12.4 12.4 28.8 18.8 45.2 18.8s32.8-6.2 45.2-18.8l210.8-210.8v165.4h128v-384h-64z",
"M896 805.4l-210.8-210.6c-25-25-65.6-25-90.6 0s-25 65.6 0 90.6l210.8 210.6h-165.4v128h384v-384h-128v165.4z",
"M218.6 128h165.4v-128h-320c0 0 0 0 0 0h-64v384h128v-165.4l210.8 210.8c12.4 12.4 28.8 18.8 45.2 18.8s32.8-6.2 45.2-18.8c25-25 25-65.6 0-90.6l-210.6-210.8z",
"M338.8 594.8l-210.8 210.6v-165.4h-128v384h384v-128h-165.4l210.8-210.8c25-25 25-65.6 0-90.6-25.2-24.8-65.6-24.8-90.6 0.2z"
],
"attrs": [
{
"fill": "rgb(0, 161, 75)"
}
{},
{},
{},
{}
],
"grid": 16,
"tags": [
"icon-expand"
],
"isMulticolor": false,
"isMulticolor2": false,
"grid": 16,
"tags": [
"icon-reset"
],
"colorPermutations": {
"1161751207457516161751": [
{
"f": 1
}
{},
{},
{},
{}
]
}
},

View File

@ -77,6 +77,7 @@
<glyph unicode="&#xe1038;" glyph-name="icon-x-in-circle" d="M512 960c-282.8 0-512-229.2-512-512s229.2-512 512-512 512 229.2 512 512-229.2 512-512 512zM832 256l-128-128-192 192-192-192-128 128 192 192-192 192 128 128 192-192 192 192 128-128-192-192 192-192z" />
<glyph unicode="&#xe1039;" glyph-name="icon-brightness" d="M253.414 641.939l-155.172 116.384c-50.233-66.209-85.127-146.713-97.91-234.39l191.586-30.216c8.145 56.552 29.998 106.879 62.068 149.006zM191.98 402.283l-191.919-27.434c13.115-90.459 48.009-170.963 99.174-238.453l154.18 117.665c-31.476 41.347-53.309 91.675-61.231 146.504zM466.283 768.020l-27.434 191.919c-90.459-13.115-170.963-48.009-238.453-99.174l117.665-154.18c41.347 31.476 91.675 53.309 146.504 61.231zM822.323 861.758c-66.209 50.233-146.713 85.127-234.39 97.91l-30.216-191.586c56.552-8.145 106.879-29.998 149.006-62.068zM832.020 493.717l191.919 27.434c-13.115 90.459-48.009 170.963-99.174 238.453l-154.18-117.665c31.476-41.347 53.309-91.675 61.231-146.504zM201.677 34.242c66.209-50.233 146.713-85.127 234.39-97.91l30.216 191.586c-56.552 8.145-106.879 29.998-149.006 62.068zM770.586 254.061l155.131-116.343c50.233 66.209 85.127 146.713 97.91 234.39l-191.586 30.216c-8.125-56.564-29.966-106.906-62.028-149.049zM557.717 127.98l27.434-191.919c90.459 13.115 170.963 48.009 238.453 99.174l-117.665 154.18c-41.347-31.476-91.675-53.309-146.504-61.231zM770.586 448c0-142.813-115.773-258.586-258.586-258.586s-258.586 115.773-258.586 258.586c0 142.813 115.773 258.586 258.586 258.586s258.586-115.773 258.586-258.586z" />
<glyph unicode="&#xe1040;" glyph-name="icon-contrast" d="M512 960c-282.78 0-512-229.24-512-512s229.22-512 512-512 512 229.24 512 512-229.22 512-512 512zM783.52 176.48c-69.111-69.481-164.785-112.481-270.502-112.481-0.358 0-0.716 0-1.074 0.001l0.055 768c212.070-0.010 383.982-171.929 383.982-384 0-106.034-42.977-202.031-112.462-271.52z" />
<glyph unicode="&#xe1041;" glyph-name="icon-expand" d="M960 960c0 0 0 0 0 0h-320v-128h165.4l-210.6-210.8c-25-25-25-65.6 0-90.6 12.4-12.4 28.8-18.8 45.2-18.8s32.8 6.2 45.2 18.8l210.8 210.8v-165.4h128v384h-64zM896 154.6l-210.8 210.6c-25 25-65.6 25-90.6 0s-25-65.6 0-90.6l210.8-210.6h-165.4v-128h384v384h-128v-165.4zM218.6 832h165.4v128h-320c0 0 0 0 0 0h-64v-384h128v165.4l210.8-210.8c12.4-12.4 28.8-18.8 45.2-18.8s32.8 6.2 45.2 18.8c25 25 25 65.6 0 90.6l-210.6 210.8zM338.8 365.2l-210.8-210.6v165.4h-128v-384h384v128h-165.4l210.8 210.8c25 25 25 65.6 0 90.6-25.2 24.8-65.6 24.8-90.6-0.2z" />
<glyph unicode="&#xe1100;" glyph-name="icon-activity" d="M576 896h-256l320-320h-290.256c-44.264 76.516-126.99 128-221.744 128h-128v-512h128c94.754 0 177.48 51.484 221.744 128h290.256l-320-320h256l448 448-448 448z" />
<glyph unicode="&#xe1101;" glyph-name="icon-activity-mode" d="M512 960c-214.866 0-398.786-132.372-474.744-320h90.744c56.86 0 107.938-24.724 143.094-64h240.906l-192 192h256l320-320-320-320h-256l192 192h-240.906c-35.156-39.276-86.234-64-143.094-64h-90.744c75.958-187.628 259.878-320 474.744-320 282.77 0 512 229.23 512 512s-229.23 512-512 512z" />
<glyph unicode="&#xe1102;" glyph-name="icon-autoflow-tabular" d="M192 960c-105.6 0-192-86.4-192-192v-640c0-105.6 86.4-192 192-192h64v1024h-64zM384 960h256v-1024h-256v1024zM832 960h-64v-704h256v512c0 105.6-86.4 192-192 192z" />

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 40 KiB

View File

@ -42,6 +42,27 @@
a {
color: $colorAboutLink;
}
h1, h2, h3 {
color: pullForward($colorBodyFg, 20%);
margin-bottom: 1em;
}
h1 {
font-size: 2.25em;
}
h2 {
border-top: 1px solid $colorInteriorBorder;
font-size: 1.5em;
margin-top: 2em;
padding-top: 1em;
}
h3 {
margin-top: 2em;
}
.s-description,
.s-button {
line-height: 2em;

View File

@ -21,7 +21,7 @@
*****************************************************************************/
/********************************************* COLUMN LAYOUTS STYLES */
@mixin cols($totalCols, $span) {
/*@mixin cols($totalCols, $span) {
$cw: 100% / $totalCols;
min-width: (500px / $totalCols) * $span;
@if ($totalCols != $span) {
@ -89,7 +89,7 @@
@include clearfix;
padding: $interiorMargin 0;
}
}
}*/
/********************************************* FLEX STYLES */
.l-flex-row,

View File

@ -66,7 +66,7 @@ body, html {
color: $colorBodyFg;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 100%;
font-weight: 200;
font-weight: normal;
height: 100%;
width: 100%;
}
@ -76,7 +76,9 @@ em {
}
input, textarea {
font-family: Helvetica, Arial, sans-serif;
font-family: inherit;
font-weight: inherit;
letter-spacing: inherit;
}
input[type="text"],
@ -86,12 +88,14 @@ input[type="search"] {
}
h1, h2, h3 {
letter-spacing: 0.04em;
margin: 0;
}
h1 {
font-size: 1.7em;
font-weight: normal !important;
letter-spacing: 0.04em;
line-height: 120%;
margin-bottom: 20px;
margin-top: 0;

View File

@ -73,10 +73,11 @@ $glyph-icon-thumbs-strip: '\e1033';
$glyph-icon-two-parts-both: '\e1034';
$glyph-icon-two-parts-one-only: '\e1035';
$glyph-icon-resync: '\e1036';
$glyph-icon-reset: '\e1037';
$glyph-icon-x-in-circle: '\e1038';
$glyph-icon-brightness: '\e1039';
$glyph-icon-contrast: '\e1040';
$glyph-icon-reset: '\e1037';
$glyph-icon-expand: '\e1041';
$glyph-icon-activity: '\e1100';
$glyph-icon-activity-mode: '\e1101';
$glyph-icon-autoflow-tabular: '\e1102';
@ -175,11 +176,12 @@ $glyph-icon-box-with-dashed-lines: '\e1129';
.icon-thumbs-strip { @include glyph($glyph-icon-thumbs-strip); }
.icon-two-parts-both { @include glyph($glyph-icon-two-parts-both); }
.icon-two-parts-one-only { @include glyph($glyph-icon-two-parts-one-only); }
.icon-resync { @include glyph($glyph-icon-resync); }
.icon-reset { @include glyph($glyph-icon-reset); }
.icon-x-in-circle { @include glyph($glyph-icon-x-in-circle); }
.icon-brightness { @include glyph($glyph-icon-brightness); }
.icon-contrast { @include glyph($glyph-icon-contrast); }
.icon-reset { @include glyph($glyph-icon-reset); }
.icon-resync { @include glyph($glyph-icon-resync); }
.icon-expand { @include glyph($glyph-icon-expand); }
.icon-activity { @include glyph($glyph-icon-activity); }
.icon-activity-mode { @include glyph($glyph-icon-activity-mode); }
.icon-autoflow-tabular { @include glyph($glyph-icon-autoflow-tabular); }

View File

@ -37,20 +37,13 @@
}
h1, h2, h3 {
color: pullForward($colorBodyFg, 20%);
font-weight: normal !important;
margin-bottom: 1em;
}
h2 {
border-top: 1px solid $colorInteriorBorder;
font-size: 1.5em;
margin-top: 2em;
padding-top: 1em;
}
h3 {
margin-top: 2em;
}
font-weight: 200 !important;
}
table {
td { font-size: inherit; }
tr.header {
background-color: rgba($colorBodyFg, 0.2);
}
}
}

View File

@ -212,6 +212,16 @@ label.form-control.checkbox {
}
}
/******************************************************** PLACEHOLDERS */
input[type="text"],
input[type="search"],
textarea {
@include placeholder {
color: rgba($colorInputFg, 0.4);
font-style: italic;
}
}
/******************************************************** INPUTS */
input[type="text"],
input[type="search"] {
@ -221,11 +231,21 @@ input[type="search"] {
}
}
.l-input-lg input[type="text"] { width: 100% !important; }
.l-input-med input[type="text"] { width: 200px !important; }
.l-input-sm input[type="text"] { width: 50px !important; }
.l-textarea-sm textarea { position: relative; height: 50px; }
.l-numeric input[type="text"] { text-align: right; }
.l-input-lg input[type="text"],
input[type="text"].lg { width: 100% !important; }
.l-input-med input[type="text"],
input[type="text"].med { width: 200px !important; }
.l-input-sm input[type="text"],
input[type="text"].sm { width: 50px !important; }
.l-numeric input[type="text"],
input[type="text"].numeric { text-align: right; }
.l-textarea-sm textarea,
textarea.sm { position: relative; height: 50px; }
.l-textarea-med textarea,
textarea.med { position: relative; height: 150px; }
.l-textarea-lg textarea,
textarea.lg { position: relative; height: 300px; }
.input-labeled {
// Used in toolbar

View File

@ -26,20 +26,20 @@
top: 0;
}
.item {
$d: $ueBrowseGridItemLg;
$iconMargin: 40px;
$iconD: ($d - ($iconMargin * 2)) * 0.85;
box-sizing: border-box;
float: left;
height: $d;
width: $d;
margin-bottom: $interiorMarginSm;
margin-right: $interiorMarginSm;
position: relative;
&.grid-item {
$d: $ueBrowseGridItemLg;
$iconMargin: 40px;
$iconD: ($d - ($iconMargin * 2)) * 0.85;
$transTime: 200ms;
@include btnSubtle($bg: $colorItemBg, $bgHov: $colorItemBgHov, $fg: $colorItemFg, $fgHov: pullForward($colorItemFg, $hoverRatioPercent), $ic: $colorItemIc);
box-sizing: border-box;
cursor: pointer;
float: left;
height: $d;
width: $d;
margin-bottom: $interiorMarginSm;
margin-right: $interiorMarginSm;
position: relative;
&:hover .item-main {
.item-type {
color: lighten($colorKey, 10%);
@ -51,7 +51,7 @@
opacity: 1;
}
}
.contents {
.abs.contents {
$m: $interiorMarginLg;
top: $m; right: $m; bottom: $m; left: $m;
}

View File

@ -32,20 +32,6 @@
height: 100%;
min-height: $plotMinH;
.gl-plot-local-controls {
@include trans-prop-nice(opacity, 150ms);
opacity: 0;
pointer-events: none;
}
.gl-plot-display-area,
.gl-plot-axis-area {
&:hover .gl-plot-local-controls {
opacity: 1;
pointer-events: inherit;
}
}
.gl-plot-wrapper-display-area-and-x-axis {
// Holds the plot area and the X-axis only
position: absolute;
@ -55,17 +41,11 @@
left: nth($plotDisplayArea, 4);
.gl-plot-display-area {
//@include test(yellow);
@if $colorPlotBg != none {
background-color: $colorPlotBg;
}
position: absolute;
top: 0;
right: 0;
bottom: nth($plotDisplayArea, 3);
left: 0;
cursor: crosshair;
border: 1px solid $colorPlotAreaBorder;
}
.gl-plot-axis-area.gl-plot-x {
@ -232,6 +212,29 @@
}
}
.gl-plot-display-area,
.plot-display-area,
.gl-plot-axis-area {
.gl-plot-local-controls,
.l-local-controls {
@include trans-prop-nice(opacity, 150ms);
opacity: 0;
pointer-events: none;
}
&:hover .gl-plot-local-controls,
&:hover .l-local-controls {
opacity: 1;
pointer-events: inherit;
}
}
.gl-plot-display-area,
.plot-display-area {
@if $colorPlotBg != none { background-color: $colorPlotBg; }
cursor: crosshair;
border: 1px solid $colorPlotAreaBorder;
}
.gl-plot-legend,
.legend {
.plot-legend-item,

View File

@ -92,6 +92,7 @@ $colorFormLines: rgba(#fff, 0.1);
$colorFormSectionHeader: rgba(#fff, 0.1);
$colorInputBg: rgba(#000, 0.1);
$colorInputFg: pullForward($colorBodyFg, 20%);
$colorInputPlaceholder: pushBack($colorBodyFg, 20%);
$colorFormText: rgba(#fff, 0.5);
$colorInputIcon: pushBack($colorBodyFg, 15%);
$colorFieldHint: pullForward($colorBodyFg, 20%);

View File

@ -92,6 +92,7 @@ $colorFormLines: rgba(#000, 0.1);
$colorFormSectionHeader: rgba(#000, 0.05);
$colorInputBg: $colorGenBg;
$colorInputFg: $colorBodyFg;
$colorInputPlaceholder: pushBack($colorBodyFg, 20%);
$colorFormText: pushBack($colorBodyFg, 10%);
$colorInputIcon: pushBack($colorBodyFg, 25%);
$colorFieldHint: pullForward($colorBodyFg, 40%);

View File

@ -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;

View File

@ -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",

View File

@ -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>

View File

@ -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()">

View File

@ -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)"

View File

@ -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;
});

View File

@ -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;
});

View File

@ -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);
}
};

View File

@ -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]);
});

View File

@ -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;
});

View File

@ -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);
}
};

View File

@ -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 () {

View File

@ -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;
});

View File

@ -31,6 +31,7 @@ define([], function () {
* @memberof platform/commonUI/formats
*/
function NumberFormat() {
this.key = 'number';
}
NumberFormat.prototype.format = function (value) {

View File

@ -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;
}
);

View File

@ -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;

View File

@ -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;
}
);

View File

@ -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
});
});
});
});
});

View File

@ -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;

View File

@ -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);
});
});
});

View File

@ -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;
};

View File

@ -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);
});
});
});
});
});

View File

@ -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;

View File

@ -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);
});
});
});
});

View File

@ -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;
});

View File

@ -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) {

View File

@ -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);
});
});

View File

@ -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

View File

@ -140,7 +140,7 @@ define(
mockHandler,
mockThrottle,
undefined,
{conductor: mockConductor}
{time: mockConductor}
);
});

View File

@ -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;
};
/**

View File

@ -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));
}
}
};

View File

@ -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;

View File

@ -99,7 +99,7 @@ define(
mockElement,
mockExportService,
mockFormatService,
{conductor: mockConductor}
{time: mockConductor}
);
spyOn(controller, 'setVisibleRows').andCallThrough();
});

View File

@ -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) {

View File

@ -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;
}
}

View File

@ -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');

View File

@ -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.

View File

@ -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;
});

View File

@ -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();
});
});
});

View File

@ -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,

View File

@ -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
View 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
View 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);
});
});
});

View File

@ -43,9 +43,9 @@ define([
'../example/policy/bundle',
'../example/profiling/bundle',
'../example/scratchpad/bundle',
'../example/styleguide/bundle',
'../example/taxonomy/bundle',
'../example/worker/bundle',
'../example/localTimeSystem/bundle',
'../platform/commonUI/about/bundle',
'../platform/commonUI/browse/bundle',
@ -68,7 +68,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',

View File

@ -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;
});

View File

@ -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());
};
};
});

View File

@ -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);

View File

@ -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;
});

View File

@ -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
});
};
};
});

View File

@ -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) {

View 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 + '"';
}
});
};
};
});

View File

@ -44,6 +44,7 @@ define([
* @memberof platform/commonUI/formats
*/
function DurationFormat() {
this.key = "duration";
}
DurationFormat.prototype.format = function (value) {

View 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;
});

View File

@ -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);
});
});
});

View File

@ -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) {

View File

@ -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");
});
});
});

View 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;
});

View File

@ -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();
});
});
});

Some files were not shown because too many files have changed in this diff Show More