Compare commits

...

10 Commits

43 changed files with 870 additions and 1345 deletions

View File

@ -1,48 +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([
"./src/LocalTimeSystem",
"./src/LocalTimeFormat",
'legacyRegistry'
], function (
LocalTimeSystem,
LocalTimeFormat,
legacyRegistry
) {
legacyRegistry.register("example/localTimeSystem", {
"extensions": {
"formats": [
{
"key": "local-format",
"implementation": LocalTimeFormat
}
],
"timeSystems": [
{
"implementation": LocalTimeSystem,
"depends": ["$timeout"]
}
]
}
});
});

View File

@ -1,43 +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/LocalClock'], function (LocalClock) {
/**
* @implements TickSource
* @constructor
*/
function LADTickSource ($timeout, period) {
LocalClock.call(this, $timeout, 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.'
};
}
LADTickSource.prototype = Object.create(LocalClock.prototype);
return LADTickSource;
});

View File

@ -1,112 +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([
'moment'
], function (
moment
) {
var DATE_FORMAT = "YYYY-MM-DD h:mm:ss.SSS a",
DATE_FORMATS = [
DATE_FORMAT,
"YYYY-MM-DD h:mm:ss a",
"YYYY-MM-DD h:mm a",
"YYYY-MM-DD"
];
/**
* @typedef Scale
* @property {number} min the minimum scale value, in ms
* @property {number} max the maximum scale value, in ms
*/
/**
* Formatter for UTC timestamps. Interprets numeric values as
* milliseconds since the start of 1970.
*
* @implements {Format}
* @constructor
* @memberof platform/commonUI/formats
*/
function LocalTimeFormat() {
}
/**
* Returns an appropriate time format based on the provided value and
* the threshold required.
* @private
*/
function getScaledFormat (d) {
var m = 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
*
* 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;
}],
["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);
})[0][0];
};
/**
*
* @param value
* @param {Scale} [scale] Optionally provides context to the
* format request, allowing for scale-appropriate formatting.
* @returns {string} the formatted date
*/
LocalTimeFormat.prototype.format = function (value, scale) {
if (scale !== undefined){
var scaledFormat = getScaledFormat(value, scale);
if (scaledFormat) {
return moment.utc(value).format(scaledFormat);
}
}
return moment(value).format(DATE_FORMAT);
};
LocalTimeFormat.prototype.parse = function (text) {
return moment(text, DATE_FORMATS).valueOf();
};
LocalTimeFormat.prototype.validate = function (text) {
return moment(text, DATE_FORMATS).isValid();
};
return LocalTimeFormat;
});

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

@ -28,7 +28,13 @@
<script src="bower_components/requirejs/require.js">
</script>
<script>
require(['openmct'], function (openmct) {
require([
'openmct',
'src/plugins/conductor/plugin'
], function (
openmct,
ConductorService
) {
[
'example/imagery',
'example/eventGenerator'
@ -40,6 +46,25 @@
openmct.install(openmct.plugins.Espresso());
openmct.install(openmct.plugins.Generator());
openmct.install(openmct.plugins.UTCTimeSystem());
/*
Will be installed by default...somehow
*/
openmct.install(ConductorService());
var ONE_MINUTE = 60 * 1000;
var ONE_YEAR = 365 * 24 * 60 * 60 * 1000;
// Will also provide a default configuration based on enabled time
// systems and tick sources.
openmct.install(openmct.plugins.Conductor({
menuOptions: [
// Default 'fixed' configuration shows last 30 mins of data. May also provide specific bounds.
{timeSystems: ['utc'], defaultOffsets: {start: 30 * ONE_MINUTE, end: 0}, zoomOutLimit: ONE_YEAR, zoomInLimit: ONE_MINUTE},
{tickSource: 'localClock', timeSystems: ['utc'], defaultOffsets: {start: 15 * ONE_MINUTE, end: 0}, zoomOut: ONE_YEAR, zoomIn: ONE_MINUTE},
{tickSource: 'latestAvailable', timeSystems: ['utc'], defaultOffsets: {start: 15 * 60 * 1000, end: 0}}
]
}));
openmct.start();
});
</script>

View File

@ -42,6 +42,7 @@ define([
"type": "provider",
"implementation": FormatProvider,
"depends": [
"openmct",
"formats[]"
]
}

View File

@ -89,22 +89,21 @@ define([
* @param {Array.<function(new : Format)>} format constructors,
* from the `formats` extension category.
*/
function FormatProvider(formats) {
var formatMap = {};
function FormatProvider(openmct, formats) {
this.telemetryAPI = openmct.telemetry;
function addToMap(Format) {
var key = Format.key;
if (key && !formatMap[key]) {
formatMap[key] = new Format();
if (key && !openmct.telemetry.getFormat(key)) {
openmct.telemetry.addFormat(new Format());
}
}
formats.forEach(addToMap);
this.formatMap = formatMap;
}
FormatProvider.prototype.getFormat = function (key) {
var format = this.formatMap[key];
var format = this.telemetryAPI.getFormat(key);
if (!format) {
throw new Error("FormatProvider: No format found for " + key);
}

View File

@ -64,7 +64,7 @@ define([
*
* Licensed
*/
return [
var format = [
[".SSS", function (m) {
return m.milliseconds();
}],
@ -93,62 +93,30 @@ define([
].filter(function (row) {
return row[1](momentified);
})[0][0];
if (format !== undefined) {
return moment.utc(value).format(scaledFormat);
}
}
/**
* 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 values.map(getScaledFormat);
} else {
return moment.utc(value).format(DATE_FORMAT) + "Z";
}
return moment.utc(value).format(DATE_FORMAT) + "Z";
};
UTCTimeFormat.prototype.parse = function (text) {

View File

@ -21,7 +21,6 @@
*****************************************************************************/
define([
"./src/ui/TimeConductorViewService",
"./src/ui/TimeConductorController",
"./src/ui/ConductorAxisController",
"./src/ui/ConductorTOIController",
@ -34,7 +33,6 @@ define([
"text!./res/templates/time-of-interest.html",
"legacyRegistry"
], function (
TimeConductorViewService,
TimeConductorController,
ConductorAxisController,
ConductorTOIController,
@ -50,16 +48,6 @@ define([
legacyRegistry.register("platform/features/conductor/core", {
"extensions": {
"services": [
{
"key": "timeConductorViewService",
"implementation": TimeConductorViewService,
"depends": [
"openmct",
"timeSystems[]"
]
}
],
"controllers": [
{
"key": "TimeConductorController",
@ -69,10 +57,8 @@ define([
"$window",
"$location",
"openmct",
"timeConductorViewService",
"formatService",
"DEFAULT_TIMECONDUCTOR_MODE",
"SHOW_TIMECONDUCTOR"
"timeConductorService",
"formatService"
]
},
{
@ -81,7 +67,7 @@ define([
"depends": [
"$scope",
"openmct",
"timeConductorViewService",
"timeConductorService",
"formatService"
]
},

View File

@ -22,19 +22,18 @@
<div class="contents">
<div class="pane left menu-items">
<ul>
<li ng-repeat="(key, metadata) in ngModel.options"
<li ng-repeat="option in ngModel.options"
ng-click="ngModel.selectedKey=key">
<a ng-mouseover="ngModel.activeMetadata = metadata"
<a ng-mouseover="ngModel.activeMetadata = option"
ng-mouseleave="ngModel.activeMetadata = undefined"
class="menu-item-a {{metadata.cssClass}}">
{{metadata.name}}
class="menu-item-a {{ngModel.activeMetadata.cssClass}}">
{{option.name}}
</a>
</li>
</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

@ -19,14 +19,13 @@
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<span ng-controller="ClickAwayController as modeController">
<span ng-controller="ClickAwayController as sourceController">
<div class="s-menu-button"
ng-click="modeController.toggle()">
<span class="title-label">{{ngModel.options[ngModel.selectedKey]
.label}}</span>
ng-click="sourceController.toggle()">
<span class="title-label">{{ngModel.sources[ngModel.selectedSource].name}}</span>
</div>
<div class="menu super-menu mini mode-selector-menu"
ng-show="modeController.isActive()">
ng-show="sourceController.isActive()">
<mct-include key="'mode-menu'"
ng-model="ngModel">
</mct-include>

View File

@ -99,7 +99,7 @@
<div class="l-time-conductor-controls l-row-elem l-flex-row flex-elem">
<mct-include
key="'mode-selector'"
ng-model="modeModel"
ng-model="sourceModel"
class="holder flex-elem menus-up mode-selector">
</mct-include>
<mct-control

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,50 +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(["./LocalClock"], function (LocalClock) {
describe("The LocalClock class", function () {
var clock,
mockTimeout,
timeoutHandle = {};
beforeEach(function () {
mockTimeout = jasmine.createSpy("timeout");
mockTimeout.andReturn(timeoutHandle);
mockTimeout.cancel = jasmine.createSpy("cancel");
clock = new LocalClock(mockTimeout, 0);
clock.start();
});
it("calls listeners on tick with current time", function () {
var mockListener = jasmine.createSpy("listener");
clock.listen(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,47 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([], function () {
/**
* A tick source is an event generator such as a timing signal, or
* indicator of data availability, which can be used to advance the Time
* Conductor. Usage is simple, a listener registers a callback which is
* invoked when this source 'ticks'.
*
* @interface
* @constructor
*/
function TickSource() {
this.listeners = [];
}
/**
* @param callback Function to be called when this tick source ticks.
* @returns an 'unlisten' function that will remove the callback from
* the registered listeners
*/
TickSource.prototype.listen = function (callback) {
throw new Error('Not implemented');
};
return TickSource;
});

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

@ -137,7 +137,7 @@ define(
ConductorAxisController.prototype.changeTimeSystem = function (timeSystem) {
this.timeSystem = timeSystem;
var key = timeSystem.formats()[0];
var key = timeSystem.format;
if (key !== undefined) {
var format = this.formatService.getFormat(key);
var bounds = this.conductor.bounds();
@ -158,10 +158,7 @@ define(
if (tickValue instanceof Date) {
tickValue = tickValue.getTime();
}
return format.format(tickValue, {
min: bounds.start,
max: bounds.end
});
return format.format(tickValue, bounds.start, bounds.end);
});
this.axisElement.call(this.xAxis);
}
@ -210,7 +207,7 @@ define(
* Initiate panning via a click + drag gesture on the time conductor
* scale. Panning triggers a "pan" event
* @param {number} delta the offset from the original click event
* @see TimeConductorViewService#
* @see TimeConductorService
* @fires platform.features.conductor.ConductorAxisController~pan
*/
ConductorAxisController.prototype.pan = function (delta) {

View File

@ -32,7 +32,7 @@ define(['./ConductorAxisController'], function (ConductorAxisController) {
controller: [
'openmct',
'formatService',
'timeConductorViewService',
'timeConductorService',
'$scope',
'$element',
ConductorAxisController

View File

@ -45,10 +45,8 @@ define(
$window,
$location,
openmct,
conductorViewService,
formatService,
DEFAULT_MODE,
SHOW_TIMECONDUCTOR
conductorService,
formatService
) {
var self = this;
@ -63,20 +61,14 @@ define(
this.$scope = $scope;
this.$window = $window;
this.$location = $location;
this.conductorViewService = conductorViewService;
this.conductorService = conductorService;
this.conductor = openmct.conductor;
this.modes = conductorViewService.availableModes();
this.modes = conductorService.availableModes();
this.validation = new TimeConductorValidation(this.conductor);
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.sourceModel.sources = conductorService.availableTickSources();
this.initializeScope();
var searchParams = JSON.parse(JSON.stringify(this.$location.search()));
@ -89,7 +81,7 @@ define(
this.changeTimeSystem(this.conductor.timeSystem());
}
var deltas = this.conductorViewService.deltas();
var deltas = this.conductorService.deltas();
if (deltas) {
this.setFormFromDeltas(deltas);
}
@ -107,8 +99,6 @@ define(
//Respond to any subsequent conductor changes
this.conductor.on('bounds', this.changeBounds);
this.conductor.on('timeSystem', this.changeTimeSystem);
this.$scope.showTimeConductor = SHOW_TIMECONDUCTOR;
}
/**
@ -138,14 +128,14 @@ define(
//Represents the various modes, and the currently selected mode
//in the view
this.$scope.modeModel = {
options: this.conductorViewService.availableModes()
options: this.conductorService.availableModes()
};
// Watch scope for selection of mode or time system by user
this.$scope.$watch('modeModel.selectedKey', this.setMode);
this.conductorViewService.on('pan', this.onPan);
this.conductorViewService.on('pan-stop', this.onPanStop);
this.conductorService.on('pan', this.onPan);
this.conductorService.on('pan-stop', this.onPanStop);
this.$scope.$on('$destroy', this.destroy);
};
@ -158,7 +148,7 @@ define(
}
if (searchParams[SEARCH.TIME_SYSTEM] &&
searchParams[SEARCH.TIME_SYSTEM] !== this.conductor.timeSystem().metadata.key) {
searchParams[SEARCH.TIME_SYSTEM] !== this.conductor.timeSystem().key) {
//Will select the specified time system on the conductor
this.selectTimeSystemByKey(searchParams[SEARCH.TIME_SYSTEM]);
}
@ -198,8 +188,8 @@ define(
this.conductor.off('bounds', this.changeBounds);
this.conductor.off('timeSystem', this.changeTimeSystem);
this.conductorViewService.off('pan', this.onPan);
this.conductorViewService.off('pan-stop', this.onPanStop);
this.conductorService.off('pan', this.onPan);
this.conductorService.off('pan-stop', this.onPanStop);
};
/**
@ -211,7 +201,7 @@ define(
//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') {
if (this.conductorService.mode() === 'fixed') {
//Set bounds in URL on change
this.setParam(SEARCH.START_BOUND, bounds.start);
this.setParam(SEARCH.END_BOUND, bounds.end);
@ -244,21 +234,6 @@ define(
}
};
/**
* On mode change, populate form based on time systems available
* from the selected mode.
* @param mode
*/
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;
});
};
/**
* When the deltas change, update the values in the UI
* @private
@ -308,7 +283,7 @@ define(
};
if (this.validation.validateStartDelta(deltas.start) && this.validation.validateEndDelta(deltas.end)) {
//Sychronize deltas between form and mode
this.conductorViewService.deltas(deltas);
this.conductorService.deltas(deltas);
//Set Deltas in URL on change
this.setParam(SEARCH.START_DELTA, deltas.start);
@ -316,39 +291,6 @@ define(
}
};
/**
* 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);
}
}
}
};
/**
* Respond to time system selection from UI
*
@ -432,7 +374,7 @@ define(
var zoomDefaults = this.conductor.timeSystem().defaults().zoom;
var timeSpan = Math.pow((1 - sliderValue), 4) * (zoomDefaults.min - zoomDefaults.max);
var zoom = this.conductorViewService.zoom(timeSpan);
var zoom = this.conductorService.zoom(timeSpan);
this.$scope.boundsModel.start = zoom.bounds.start;
this.$scope.boundsModel.end = zoom.bounds.end;
@ -457,7 +399,7 @@ define(
this.setDeltas(this.$scope.boundsModel);
this.zooming = false;
this.conductorViewService.emit('zoom-stop');
this.conductorService.emit('zoom-stop');
};
/**

View File

@ -153,25 +153,6 @@ define(
}
};
/**
* @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

View File

@ -1,229 +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(
[
'EventEmitter',
'./TimeConductorMode'
],
function (EventEmitter, TimeConductorMode) {
/**
* 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.
*
* @memberof platform.features.conductor
* @param conductor
* @param timeSystems
* @constructor
*/
function TimeConductorViewService(openmct, timeSystems) {
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;
}
}
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
* @property {ZoomLevel} zoom the new zoom level.
*/
/**
* Zoom to given time span. Will fire a zoom event with new zoom
* bounds. Zoom bounds emitted in this way are considered ephemeral
* and should be overridden by any time conductor bounds events. Does
* not set bounds globally.
* @param {number} zoom A time duration in ms
* @fires platform.features.conductor.TimeConductorViewService~zoom
* @see module:openmct.TimeConductor#bounds
*/
TimeConductorViewService.prototype.zoom = function (timeSpan) {
var zoom = this.currentMode.calculateZoom(timeSpan);
this.emit("zoom", zoom);
return zoom;
};
return TimeConductorViewService;
}
);

View File

@ -20,7 +20,7 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(['./TimeConductorViewService'], function (TimeConductorViewService) {
define(['./TimeConductorService'], function (TimeConductorService) {
describe("The Time Conductor view service", function () {
var mockTimeConductor;
var basicTimeSystem;

View File

@ -1,40 +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([
"./src/UTCTimeSystem",
"legacyRegistry"
], function (
UTCTimeSystem,
legacyRegistry
) {
legacyRegistry.register("platform/features/conductor/utcTimeSystem", {
"extensions": {
"timeSystems": [
{
"implementation": UTCTimeSystem,
"depends": ["$timeout"]
}
]
}
});
});

View File

@ -1,90 +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)];
this.defaultValues = undefined;
}
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 (defaults) {
if (arguments.length > 0) {
this.defaultValues = defaults;
}
if (this.defaultValues === undefined) {
var now = Math.ceil(Date.now() / 1000) * 1000;
var ONE_MINUTE = 60 * 1 * 1000;
var FIFTY_YEARS = 50 * 365 * 24 * 60 * 60 * 1000;
this.defaultValues = {
key: 'utc-default',
name: 'UTC time system defaults',
deltas: {start: FIFTEEN_MINUTES, end: 0},
bounds: {start: now - FIFTEEN_MINUTES, end: now},
zoom: {min: FIFTY_YEARS, max: ONE_MINUTE}
};
}
return this.defaultValues;
};
return UTCTimeSystem;
});

View File

@ -1,46 +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(['./UTCTimeSystem'], function (UTCTimeSystem) {
describe("The UTCTimeSystem class", function () {
var timeSystem,
mockTimeout;
beforeEach(function () {
mockTimeout = jasmine.createSpy("timeout");
timeSystem = new UTCTimeSystem(mockTimeout);
});
it("defines at least one format", function () {
expect(timeSystem.formats().length).toBeGreaterThan(0);
});
it("defines a tick source", function () {
var tickSources = timeSystem.tickSources();
expect(tickSources.length).toBeGreaterThan(0);
});
it("defines some defaults", function () {
expect(timeSystem.defaults()).toBeDefined();
});
});
});

View File

@ -112,22 +112,6 @@ define(
this.lastBounds = bounds;
};
/**
* Determines is a given telemetry datum is within the bounds currently
* defined for this telemetry collection.
* @private
* @param datum
* @returns {boolean}
*/
TelemetryCollection.prototype.inBounds = function (datum) {
var noBoundsDefined = !this.lastBounds || (this.lastBounds.start === undefined && this.lastBounds.end === undefined);
var withinBounds =
_.get(datum, this.sortField) >= this.lastBounds.start &&
_.get(datum, this.sortField) <= this.lastBounds.end;
return noBoundsDefined || withinBounds;
};
/**
* Adds an individual item to the collection. Used internally only
* @private
@ -173,9 +157,10 @@ define(
// based on time stamp because the array is guaranteed ordered due
// to sorted insertion.
var startIx = _.sortedIndex(array, item, this.sortField);
var endIx;
if (startIx !== array.length) {
var endIx = _.sortedLastIndex(array, item, this.sortField);
endIx = _.sortedLastIndex(array, item, this.sortField);
// Create an array of potential dupes, based on having the
// same time stamp
@ -185,7 +170,7 @@ define(
}
if (!isDuplicate) {
array.splice(startIx, 0, item);
array.splice(endIx || startIx, 0, item);
//Return true if it was added and in bounds
return array === this.telemetry;

View File

@ -425,6 +425,38 @@ define(
}
};
/**
* Finds the correct insertion point for a new row, which takes into
* account duplicates to make sure new rows are inserted in a way that
* maintains arrival order.
*
* @private
* @param {Array} searchArray
* @param {Object} searchElement Object to find the insertion point for
*/
MCTTableController.prototype.findInsertionPoint = function (searchArray, searchElement) {
//First, use a binary search to find the correct insertion point
var index = this.binarySearch(searchArray, searchElement, 0, searchArray.length - 1);
var testIndex = index;
//It's possible that the insertion point is a duplicate of the element to be inserted
var isDupe = function () {
return this.sortComparator(searchElement,
searchArray[testIndex][this.$scope.sortColumn].text) === 0;
}.bind(this);
// In the event of a duplicate, scan left or right (depending on
// sort order) to find an insertion point that maintains order received
while (testIndex >= 0 && testIndex < searchArray.length && isDupe()) {
if (this.$scope.sortDirection === 'asc') {
index = ++testIndex;
} else {
index = testIndex--;
}
}
return index;
};
/**
* @private
*/
@ -439,9 +471,9 @@ define(
case -1:
return this.binarySearch(searchArray, searchElement, min,
sampleAt - 1);
case 0 :
case 0:
return sampleAt;
case 1 :
case 1:
return this.binarySearch(searchArray, searchElement,
sampleAt + 1, max);
}
@ -458,7 +490,7 @@ define(
index = array.length;
} else {
//Sort is enabled, perform binary search to find insertion point
index = this.binarySearch(array, element[this.$scope.sortColumn].text, 0, array.length - 1);
index = this.findInsertionPoint(array, element[this.$scope.sortColumn].text);
}
if (index === -1) {
array.unshift(element);

View File

@ -72,7 +72,7 @@ define(
* Create a new format object from legacy object, and replace it
* when it changes
*/
this.newObject = objectUtils.toNewFormat($scope.domainObject.getModel(),
this.domainObject = objectUtils.toNewFormat($scope.domainObject.getModel(),
$scope.domainObject.getId());
_.bindAll(this, [
@ -144,9 +144,9 @@ define(
this.unobserveObject();
}
this.unobserveObject = this.openmct.objects.observe(this.newObject, "*",
this.unobserveObject = this.openmct.objects.observe(this.domainObject, "*",
function (domainObject) {
this.newObject = domainObject;
this.domainObject = domainObject;
this.getData();
}.bind(this)
);
@ -364,11 +364,8 @@ define(
var telemetryApi = this.openmct.telemetry;
var telemetryCollection = this.telemetry;
//Set table max length to avoid unbounded growth.
//var maxRows = 100000;
var maxRows = Number.MAX_VALUE;
var limitEvaluator;
var added = false;
var scope = this.$scope;
var table = this.table;
this.subscriptions.forEach(function (subscription) {
@ -379,16 +376,6 @@ define(
function newData(domainObject, datum) {
limitEvaluator = telemetryApi.limitEvaluator(domainObject);
added = telemetryCollection.add([table.getRowValues(limitEvaluator, datum)]);
//Inform table that a new row has been added
if (scope.rows.length > maxRows) {
scope.$broadcast('remove:rows', scope.rows[0]);
scope.rows.shift();
}
if (!scope.loading && added) {
scope.$broadcast('add:row',
scope.rows.length - 1);
}
}
objects.forEach(function (object) {
@ -399,6 +386,42 @@ define(
return objects;
};
/**
* Return an array of telemetry objects in this view that should be
* subscribed to.
* @private
* @returns {Promise<Array>} a promise that resolves with an array of
* telemetry objects in this view.
*/
TelemetryTableController.prototype.getTelemetryObjects = function () {
var telemetryApi = this.openmct.telemetry;
var compositionApi = this.openmct.composition;
function filterForTelemetry(objects) {
return objects.filter(telemetryApi.canProvideTelemetry.bind(telemetryApi));
}
/*
* If parent object is a telemetry object, subscribe to it. Do not
* test composees.
*/
if (telemetryApi.canProvideTelemetry(this.domainObject)) {
return Promise.resolve([this.domainObject]);
} else {
/*
* If parent object is not a telemetry object, subscribe to all
* composees that are telemetry producing objects.
*/
var composition = compositionApi.get(this.domainObject);
if (composition) {
return composition
.load()
.then(filterForTelemetry);
}
}
};
/**
* Request historical data, and subscribe to for real-time data.
* @private
@ -406,10 +429,7 @@ define(
* established, and historical telemetry is received and processed.
*/
TelemetryTableController.prototype.getData = function () {
var telemetryApi = this.openmct.telemetry;
var compositionApi = this.openmct.composition;
var scope = this.$scope;
var newObject = this.newObject;
this.telemetry.clear();
this.telemetry.bounds(this.openmct.conductor.bounds());
@ -421,28 +441,9 @@ define(
console.error(e.stack);
}
function filterForTelemetry(objects) {
return objects.filter(telemetryApi.canProvideTelemetry.bind(telemetryApi));
}
function getDomainObjects() {
var objects = [newObject];
var composition = compositionApi.get(newObject);
if (composition) {
return composition
.load()
.then(function (children) {
return objects.concat(children);
});
} else {
return Promise.resolve(objects);
}
}
scope.rows = [];
return getDomainObjects()
.then(filterForTelemetry)
return this.getTelemetryObjects()
.then(this.loadColumns)
.then(this.subscribeToNewData)
.then(this.getHistoricalData)

View File

@ -138,6 +138,27 @@ define(
};
collection.add([addedObjectB, addedObjectA]);
expect(collection.telemetry[11]).toBe(addedObjectB);
}
);
it("maintains insertion order in the case of duplicate time stamps",
function () {
var addedObjectA = {
timestamp: 10000,
value: {
integer: 10,
text: integerTextMap[10]
}
};
var addedObjectB = {
timestamp: 10000,
value: {
integer: 11,
text: integerTextMap[11]
}
};
collection.add([addedObjectA, addedObjectB]);
expect(collection.telemetry[11]).toBe(addedObjectB);
}
);

View File

@ -459,14 +459,14 @@ define(
beforeEach(function () {
row4 = {
'col1': {'text': 'row5 col1'},
'col1': {'text': 'row4 col1'},
'col2': {'text': 'xyz'},
'col3': {'text': 'row5 col3'}
'col3': {'text': 'row4 col3'}
};
row5 = {
'col1': {'text': 'row6 col1'},
'col1': {'text': 'row5 col1'},
'col2': {'text': 'aaa'},
'col3': {'text': 'row6 col3'}
'col3': {'text': 'row5 col3'}
};
row6 = {
'col1': {'text': 'row6 col1'},
@ -490,6 +490,71 @@ define(
expect(mockScope.displayRows[3].col2.text).toEqual('ggg');
});
it('Inserts duplicate values for sort column in order received when sorted descending', function () {
mockScope.sortColumn = 'col2';
mockScope.sortDirection = 'desc';
mockScope.displayRows = controller.sortRows(testRows.slice(0));
var row6b = {
'col1': {'text': 'row6b col1'},
'col2': {'text': 'ggg'},
'col3': {'text': 'row6b col3'}
};
var row6c = {
'col1': {'text': 'row6c col1'},
'col2': {'text': 'ggg'},
'col3': {'text': 'row6c col3'}
};
controller.addRows(undefined, [row4, row5]);
controller.addRows(undefined, [row6, row6b, row6c]);
expect(mockScope.displayRows[0].col2.text).toEqual('xyz');
expect(mockScope.displayRows[7].col2.text).toEqual('aaa');
// Added duplicate rows
expect(mockScope.displayRows[2].col2.text).toEqual('ggg');
expect(mockScope.displayRows[3].col2.text).toEqual('ggg');
expect(mockScope.displayRows[4].col2.text).toEqual('ggg');
// Check that original order is maintained with dupes
expect(mockScope.displayRows[2].col3.text).toEqual('row6c col3');
expect(mockScope.displayRows[3].col3.text).toEqual('row6b col3');
expect(mockScope.displayRows[4].col3.text).toEqual('row6 col3');
});
it('Inserts duplicate values for sort column in order received when sorted ascending', function () {
mockScope.sortColumn = 'col2';
mockScope.sortDirection = 'asc';
mockScope.displayRows = controller.sortRows(testRows.slice(0));
var row6b = {
'col1': {'text': 'row6b col1'},
'col2': {'text': 'ggg'},
'col3': {'text': 'row6b col3'}
};
var row6c = {
'col1': {'text': 'row6c col1'},
'col2': {'text': 'ggg'},
'col3': {'text': 'row6c col3'}
};
controller.addRows(undefined, [row4, row5, row6]);
controller.addRows(undefined, [row6b, row6c]);
expect(mockScope.displayRows[0].col2.text).toEqual('aaa');
expect(mockScope.displayRows[7].col2.text).toEqual('xyz');
// Added duplicate rows
expect(mockScope.displayRows[3].col2.text).toEqual('ggg');
expect(mockScope.displayRows[4].col2.text).toEqual('ggg');
expect(mockScope.displayRows[5].col2.text).toEqual('ggg');
// Check that original order is maintained with dupes
expect(mockScope.displayRows[3].col3.text).toEqual('row6 col3');
expect(mockScope.displayRows[4].col3.text).toEqual('row6b col3');
expect(mockScope.displayRows[5].col3.text).toEqual('row6c col3');
});
it('Adds new rows at the correct sort position when' +
' sorted and filtered', function () {
mockScope.sortColumn = 'col2';

View File

@ -161,7 +161,7 @@ define(
});
});
describe ('Subscribes to new data', function () {
describe ('when getting telemetry', function () {
var mockComposition,
mockTelemetryObject,
mockChildren,
@ -173,9 +173,7 @@ define(
"load"
]);
mockTelemetryObject = jasmine.createSpyObj("mockTelemetryObject", [
"something"
]);
mockTelemetryObject = {};
mockTelemetryObject.identifier = {
key: "mockTelemetryObject"
};
@ -192,22 +190,12 @@ define(
});
done = false;
controller.getData().then(function () {
done = true;
});
});
it('fetches historical data', function () {
waitsFor(function () {
return done;
}, "getData to return", 100);
runs(function () {
expect(mockTelemetryAPI.request).toHaveBeenCalledWith(mockTelemetryObject, jasmine.any(Object));
});
});
it('fetches historical data for the time period specified by the conductor bounds', function () {
controller.getData().then(function () {
done = true;
});
waitsFor(function () {
return done;
}, "getData to return", 100);
@ -217,17 +205,11 @@ define(
});
});
it('subscribes to new data', function () {
waitsFor(function () {
return done;
}, "getData to return", 100);
runs(function () {
expect(mockTelemetryAPI.subscribe).toHaveBeenCalledWith(mockTelemetryObject, jasmine.any(Function), {});
it('unsubscribes on view destruction', function () {
controller.getData().then(function () {
done = true;
});
});
it('and unsubscribes on view destruction', function () {
waitsFor(function () {
return done;
}, "getData to return", 100);
@ -239,6 +221,87 @@ define(
expect(unsubscribe).toHaveBeenCalled();
});
});
it('fetches historical data for the time period specified by the conductor bounds', function () {
controller.getData().then(function () {
done = true;
});
waitsFor(function () {
return done;
}, "getData to return", 100);
runs(function () {
expect(mockTelemetryAPI.request).toHaveBeenCalledWith(mockTelemetryObject, mockBounds);
});
});
it('fetches data for, and subscribes to parent object if it is a telemetry object', function () {
controller.getData().then(function () {
done = true;
});
waitsFor(function () {
return done;
}, "getData to return", 100);
runs(function () {
expect(mockTelemetryAPI.subscribe).toHaveBeenCalledWith(mockTelemetryObject, jasmine.any(Function), {});
expect(mockTelemetryAPI.request).toHaveBeenCalledWith(mockTelemetryObject, jasmine.any(Object));
});
});
it('fetches data for, and subscribes to parent object if it is a telemetry object', function () {
controller.getData().then(function () {
done = true;
});
waitsFor(function () {
return done;
}, "getData to return", 100);
runs(function () {
expect(mockTelemetryAPI.subscribe).toHaveBeenCalledWith(mockTelemetryObject, jasmine.any(Function), {});
expect(mockTelemetryAPI.request).toHaveBeenCalledWith(mockTelemetryObject, jasmine.any(Object));
});
});
it('fetches data for, and subscribes to any composees that are telemetry objects if parent is not', function () {
mockChildren = [
{name: "child 1"}
];
var mockTelemetryChildren = [
{name: "child 2"},
{name: "child 3"},
{name: "child 4"}
];
mockChildren = mockChildren.concat(mockTelemetryChildren);
mockComposition.load.andReturn(Promise.resolve(mockChildren));
mockTelemetryAPI.canProvideTelemetry.andCallFake(function (object) {
if (object === mockTelemetryObject) {
return false;
} else {
return mockTelemetryChildren.indexOf(object) !== -1;
}
});
controller.getData().then(function () {
done = true;
});
waitsFor(function () {
return done;
}, "getData to return", 100);
runs(function () {
mockTelemetryChildren.forEach(function (child) {
expect(mockTelemetryAPI.subscribe).toHaveBeenCalledWith(child, jasmine.any(Function), {});
});
mockTelemetryChildren.forEach(function (child) {
expect(mockTelemetryAPI.request).toHaveBeenCalledWith(child, jasmine.any(Object));
});
expect(mockTelemetryAPI.subscribe).not.toHaveBeenCalledWith(mockChildren[0], jasmine.any(Function), {});
expect(mockTelemetryAPI.subscribe).not.toHaveBeenCalledWith(mockTelemetryObject[0], jasmine.any(Function), {});
});
});
});
it('When in real-time mode, enables auto-scroll', function () {

View File

@ -21,7 +21,7 @@
*****************************************************************************/
define([
'./TimeConductor',
'./conductor/TimeConductor',
'./objects/ObjectAPI',
'./composition/CompositionAPI',
'./types/TypeRegistry',

View File

@ -50,10 +50,21 @@ define(['EventEmitter'], function (EventEmitter) {
//Default to fixed mode
this.followMode = false;
this.timeSystems = {};
this.tickSources = {};
}
TimeConductor.prototype = Object.create(EventEmitter.prototype);
TimeConductor.prototype.addTimeSystem = function (timeSystem) {
this.timeSystems[timeSystem.key] = timeSystem;
};
TimeConductor.prototype.addTickSource = function (tickSource) {
this.tickSources[tickSource.key] = tickSource;
};
/**
* Validate the given bounds. This can be used for pre-validation of
* bounds, for example by views validating user inputs.
@ -75,32 +86,6 @@ define(['EventEmitter'], function (EventEmitter) {
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

View File

@ -121,7 +121,7 @@ define([
* @memberof module:openmct.TelemetryAPI~
*/
var cachedFormatService;
/**
* An interface for retrieving telemetry data associated with a domain
@ -138,6 +138,7 @@ define([
this.metadataCache = new WeakMap();
this.formatMapCache = new WeakMap();
this.valueFormatterCache = new WeakMap();
this.formatMap = {};
}
/**
@ -281,6 +282,13 @@ define([
return _.sortByAll(options, sortKeys);
};
function getFormatService() {
if (cachedFormatService === undefined) {
cachedFormatService = this.MCT.$injector.get('formatService');
}
return cachedFormatService;
}
/**
* Get a value formatter for a given valueMetadata.
*
@ -288,12 +296,10 @@ define([
*/
TelemetryAPI.prototype.getValueFormatter = function (valueMetadata) {
if (!this.valueFormatterCache.has(valueMetadata)) {
if (!this.formatService) {
this.formatService = this.MCT.$injector.get('formatService');
}
var formatService = getFormatService.call(this);
this.valueFormatterCache.set(
valueMetadata,
new TelemetryValueFormatter(valueMetadata, this.formatService)
new TelemetryValueFormatter(valueMetadata, formatService)
);
}
return this.valueFormatterCache.get(valueMetadata);
@ -334,5 +340,17 @@ define([
return this.legacyProvider.limitEvaluator.apply(this.legacyProvider, arguments);
};
TelemetryAPI.prototype.getFormat = function (formatKey) {
return this.formatMap[formatKey];
};
/**
* Register a new formatter
* @param {Format} formatter
*/
TelemetryAPI.prototype.addFormat = function (format) {
this.formatMap[format.key] = format;
};
return TelemetryAPI;
});

View File

@ -45,7 +45,6 @@ define([
'../example/scratchpad/bundle',
'../example/taxonomy/bundle',
'../example/worker/bundle',
'../example/localTimeSystem/bundle',
'../platform/commonUI/about/bundle',
'../platform/commonUI/browse/bundle',
@ -68,7 +67,6 @@ define([
'../platform/features/fixed/bundle',
'../platform/features/conductor/core/bundle',
'../platform/features/conductor/compatibility/bundle',
'../platform/features/conductor/utcTimeSystem/bundle',
'../platform/features/imagery/bundle',
'../platform/features/layout/bundle',
'../platform/features/my-items/bundle',

View File

@ -0,0 +1,170 @@
/*****************************************************************************
* 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 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.
*
* @memberof platform.features.conductor
* @param conductor
* @param timeSystems
* @constructor
*/
function TimeConductorService(openmct) {
EventEmitter.call(this);
this.conductor = openmct.conductor;
}
TimeConductorService.prototype = Object.create(EventEmitter.prototype);
/**
* @private
* @param {number} time some value that is valid in the current TimeSystem
*/
TimeConductorService.prototype.tick = function (time) {
var deltas = this.deltas();
var startTime = time;
var endTime = time;
if (deltas) {
startTime = time - deltas.start;
endTime = time + deltas.end;
}
var newBounds = {
start: startTime,
end: endTime
};
this.conductor.boundsVal = newBounds;
this.conductor.emit('bounds', true);
// If a bounds change results in a TOI outside of the current
// bounds, unset it
if (this.conductor.toi < newBounds.start || this.conductor.toi > newBounds.end) {
this.conductor.timeOfInterest(undefined);
}
};
TimeConductorService.prototype.menuOptions = function (options) {
this.options = options;
};
TimeConductorService.prototype.activeTickSource = function (key) {
if (arguments.length > 0) {
if (this.activeTickSource !== undefined) {
this.activeTickSource.off('tick', this.tick);
}
var newTickSource = this.conductor.tickSource[key];
if (newTickSource) {
this.activeTickSource.on('tick', this.tick);
this.activeTickSource = newTickSource;
}
}
return this.activeTickSource;
};
/**
* @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
*/
TimeConductorService.prototype.deltas = function () {
// Get / Set deltas
};
/**
* 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[]}
*/
TimeConductorService.prototype.availableTickSources = function () {
var conductor = this.conductor;
//Return all tick sources
return _.uniq(this.options.map(function (option) {
return option.tickSource && conductor.tickSources(option.tickSource);
}.bind(this)));
};
TimeConductorService.prototype.availableTimeSystems = function () {
return Object.values(this.conductor.timeSystems);
};
/**
* An event to indicate that zooming is taking place
* @event platform.features.conductor.TimeConductorService~zoom
* @property {ZoomLevel} zoom the new zoom level.
*/
/**
* Zoom to given time span. Will fire a zoom event with new zoom
* bounds. Zoom bounds emitted in this way are considered ephemeral
* and should be overridden by any time conductor bounds events. Does
* not set bounds globally.
* @param {number} zoom A time duration in ms
* @fires platform.features.conductor.TimeConductorService~zoom
* @see module:openmct.TimeConductor#bounds
*/
TimeConductorService.prototype.zoom = function (timeSpan) {
var zoom = this.currentMode.calculateZoom(timeSpan);
this.emit("zoom", zoom);
return zoom;
};
return TimeConductorService;
}
);

View File

@ -0,0 +1,15 @@
define([
'./TimeConductorService'
], function (TimeConductorService) {
return function ConductorAPIPlugin() {
return function install(openmct) {
openmct.legacyExtension("services", {
"key": "timeConductorService",
"implementation": TimeConductorService,
"depends": [
"openmct"
]
});
}
}
});

View File

@ -22,7 +22,7 @@
define([
'lodash',
'../../platform/features/conductor/utcTimeSystem/src/UTCTimeSystem',
'./utcTimeSystem/plugin',
'../../example/generator/plugin'
], function (
_,
@ -46,65 +46,20 @@ define([
};
});
plugins.UTCTimeSystem = function () {
return function (openmct) {
openmct.legacyExtension("timeSystems", {
"implementation": UTCTimeSystem,
"depends": ["$timeout"]
});
};
};
var conductorInstalled = false;
plugins.UTCTimeSystem = UTCTimeSystem;
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) {
defaults = timeSystem.defaults();
if (options.defaultTimespan !== undefined) {
defaults.deltas.start = options.defaultTimespan;
defaults.bounds.start = defaults.bounds.end - options.defaultTimespan;
timeSystem.defaults(defaults);
}
openmct.conductor.timeSystem(timeSystem, defaults.bounds);
}
}
return function (openmct) {
openmct.legacyExtension('constants', {
key: 'DEFAULT_TIMECONDUCTOR_MODE',
value: options.showConductor ? 'fixed' : 'realtime',
priority: conductorInstalled ? 'mandatory' : 'fallback'
openmct.legacyExtension('runs', {
implementation: function applyDefaults(timeConductorService) {
timeConductorService.menuOptions(options.menuOptions);
},
depends: ["timeConductorService"]
});
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;
};
};

View File

@ -0,0 +1,65 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2016, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([], function () {
function LocalClock(tickPeriod) {
//Metadata
this.key = 'localClock';
this.name = 'A local time source';
this.cssClass = 'icon-clock';
//Members
this.listeners = {};
this.tickPeriod = tickPeriod;
this.tick = this.tick.bind(this);
}
LocalClock.prototype.tick = function () {
this.listeners['tick'].forEach(Date.now());
this.timeout = setTimeout(this.tick, this.tickPeriod);
};
LocalClock.prototype.on = function (event, listener) {
this.listeners[event] = this.listeners[event] || [];
this.listeners[event].push(listener);
if (timeout === undefined) {
setTimeout(this.tick, this.tickPeriod);
}
};
LocalClock.prototype.off = function (event, listener) {
if (this.listeners[event]) {
this.listeners[event] = this.listeners[event].filter(function (l) {
return l === listener;
});
var isEmpty = Object.keys(this.listeners).all(function (key){
return this.listeners[key] === undefined || this.listeners[key].length === 0
});
if (isEmpty) {
clearTimeout(this.timeout);
}
}
};
return LocalClock;
});

View File

@ -0,0 +1,131 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2016, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
'moment'
], function (
moment
) {
var DATE_FORMAT = "YYYY-MM-DD HH:mm:ss.SSS",
DATE_FORMATS = [
DATE_FORMAT,
"YYYY-MM-DD HH:mm:ss",
"YYYY-MM-DD HH:mm",
"YYYY-MM-DD"
];
/**
* @typedef Scale
* @property {number} min the minimum scale value, in ms
* @property {number} max the maximum scale value, in ms
*/
/**
* Formatter for UTC timestamps. Interprets numeric values as
* milliseconds since the start of 1970.
*
* @implements {Format}
* @constructor
* @memberof platform/commonUI/formats
*/
function UTCTimeFormat() {
}
/**
* Returns an appropriate time format based on the provided value and
* the threshold required.
* @private
*/
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
*
* Licensed
*/
var format = [
[".SSS", function (m) {
return m.milliseconds();
}],
[":ss", function (m) {
return m.seconds();
}],
["HH:mm", function (m) {
return m.minutes();
}],
["HH", 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](momentified);
})[0][0];
if (format !== undefined) {
return moment.utc(value).format(scaledFormat);
}
}
/**
* @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.format = function (value, minValue, maxValue, count) {
if (arguments.length > 1) {
return values.map(getScaledFormat);
} else {
return moment.utc(value).format(DATE_FORMAT) + "Z";
}
};
UTCTimeFormat.prototype.parse = function (text) {
return moment.utc(text, DATE_FORMATS).valueOf();
};
UTCTimeFormat.prototype.validate = function (text) {
return moment.utc(text, DATE_FORMATS).isValid();
};
return UTCTimeFormat;
});

View File

@ -0,0 +1,83 @@
/*****************************************************************************
* 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([
"./UTCTimeFormat",
"moment"
], function (
UTCTimeFormat,
moment
) {
describe("The UTCTimeFormat class", function () {
var format;
var scale;
beforeEach(function () {
format = new UTCTimeFormat();
scale = {min: 0, max: 0};
});
it("Provides an appropriately scaled time format based on the input" +
" time", function () {
var TWO_HUNDRED_MS = 200;
var THREE_SECONDS = 3000;
var FIVE_MINUTES = 5 * 60 * 1000;
var ONE_HOUR_TWENTY_MINS = (1 * 60 * 60 * 1000) + (20 * 60 * 1000);
var TEN_HOURS = (10 * 60 * 60 * 1000);
var JUNE_THIRD = moment.utc("2016-06-03", "YYYY-MM-DD");
var APRIL = moment.utc("2016-04", "YYYY-MM");
var TWENTY_SIXTEEN = moment.utc("2016", "YYYY");
expect(format.format(TWO_HUNDRED_MS, scale)).toBe(".200");
expect(format.format(THREE_SECONDS, scale)).toBe(":03");
expect(format.format(FIVE_MINUTES, scale)).toBe("00:05");
expect(format.format(ONE_HOUR_TWENTY_MINS, scale)).toBe("01:20");
expect(format.format(TEN_HOURS, scale)).toBe("10");
expect(format.format(JUNE_THIRD, scale)).toBe("Fri 03");
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,21 @@
define([
'./UTCTimeFormat',
'./LocalClock'
], function (
UtcTimeFormat,
LocalClock
) {
return function UtcTimeSystemPlugin(options) {
return function install(openmct) {
openmct.telemetry.addFormat(new UtcTimeFormat());
openmct.conductor.addTimeSystem({
key: 'utc',
name: 'UTC',
timeFormat: 'utc',
durationFormat: 'duration',
utcBased: true
});
openmct.conductor.addTickSource(new LocalClock());
}
}
});