[Time API] Modified public Time API to support registration of time systems and clocks, setting of active clock, simplification of clocks and time systems, setting of offsets(deltas) and other changes as per #1265 and #1474. Refactoring of UI code to support changes.

This commit is contained in:
Henry
2017-04-14 17:06:46 -07:00
parent fd3312734c
commit f17417a541
43 changed files with 1287 additions and 1800 deletions

View File

@@ -95,45 +95,6 @@ define([
})[0][0];
}
/**
* Returns a description of the current range of the time conductor's
* bounds.
* @param timeRange
* @returns {*}
*/
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

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

@@ -67,12 +67,10 @@ define([
"depends": [
"$scope",
"$window",
"$location",
"openmct",
"timeConductorViewService",
"formatService",
"DEFAULT_TIMECONDUCTOR_MODE",
"SHOW_TIMECONDUCTOR"
"CONDUCTOR_CONFIG"
]
},
{
@@ -151,13 +149,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.mode}}-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.mode === 'fixed')}">
-
<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.mode !== 'fixed'"
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.mode === 'fixed')}">
+
<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>
@@ -99,24 +99,23 @@
<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,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

@@ -37,14 +37,12 @@ define(
function ConductorAxisController(openmct, formatService, conductorViewService, scope, element) {
// Dependencies
this.formatService = formatService;
this.conductor = openmct.conductor;
this.timeAPI = openmct.time;
this.conductorViewService = conductorViewService;
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,8 +58,8 @@ define(
* @private
*/
ConductorAxisController.prototype.destroy = function () {
this.conductor.off('timeSystem', this.changeTimeSystem);
this.conductor.off('bounds', this.changeBounds);
this.timeAPI.off('timeSystem', this.changeTimeSystem);
this.timeAPI.off('bounds', this.changeBounds);
this.conductorViewService.off("zoom", this.onZoom);
this.conductorViewService.off("zoom-stop", this.onZoomStop);
};
@@ -83,14 +81,14 @@ 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);
@@ -113,10 +111,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 +135,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();
@@ -180,7 +176,7 @@ define(
ConductorAxisController.prototype.panStop = function () {
//resync view bounds with time conductor bounds
this.conductorViewService.emit("pan-stop");
this.conductor.bounds(this.bounds);
this.timeAPI.bounds(this.bounds);
};
/**
@@ -216,9 +212,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 = {

View File

@@ -30,7 +30,7 @@ define(
* @memberof platform.features.conductor
*/
function ConductorTOIController($scope, openmct, conductorViewService) {
this.conductor = openmct.conductor;
this.timeAPI = openmct.time;
this.conductorViewService = conductorViewService;
//Bind all class functions to 'this'
@@ -40,11 +40,11 @@ define(
this[key] = ConductorTOIController.prototype[key].bind(this);
}.bind(this));
this.conductor.on('timeOfInterest', this.changeTimeOfInterest);
this.timeAPI.on('timeOfInterest', this.changeTimeOfInterest);
this.conductorViewService.on('zoom', this.setOffsetFromZoom);
this.conductorViewService.on('pan', this.setOffsetFromBounds);
var timeOfInterest = this.conductor.timeOfInterest();
var timeOfInterest = this.timeAPI.timeOfInterest();
if (timeOfInterest) {
this.changeTimeOfInterest(timeOfInterest);
}
@@ -56,7 +56,7 @@ define(
* @private
*/
ConductorTOIController.prototype.destroy = function () {
this.conductor.off('timeOfInterest', this.changeTimeOfInterest);
this.timeAPI.off('timeOfInterest', this.changeTimeOfInterest);
this.conductorViewService.off('zoom', this.setOffsetFromZoom);
this.conductorViewService.off('pan', this.setOffsetFromBounds);
};
@@ -70,7 +70,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 +94,7 @@ define(
* @private
*/
ConductorTOIController.prototype.changeTimeOfInterest = function () {
var bounds = this.conductor.bounds();
var bounds = this.timeAPI.bounds();
if (bounds) {
this.setOffsetFromBounds(bounds);
}
@@ -112,10 +112,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

@@ -22,9 +22,10 @@
define(
[
'moment',
'./TimeConductorValidation'
],
function (TimeConductorValidation) {
function (moment, TimeConductorValidation) {
var SEARCH = {
MODE: 'tc.mode',
TIME_SYSTEM: 'tc.timeSystem',
@@ -34,21 +35,46 @@ define(
END_DELTA: 'tc.endDelta'
};
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.
* 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;
@@ -62,175 +88,172 @@ define(
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.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.mode = this.timeAPI.clock() === undefined ? 'fixed' : 'realtime';
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);
// Construct the provided time system definitions
this.timeSystems = config.menuOptions.map(function (menuOption){
return this.getTimeSystem(menuOption.timeSystem);
}.bind(this));
//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.getClock = function (key) {
return this.timeAPI.allClocks().filter(function (clock) {
return clock.key === key;
})[0];
};
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);
TimeConductorController.prototype.getTimeSystem = function (key) {
return this.timeAPI.allTimeSystems().filter(function (timeSystem) {
return timeSystem.key === key;
})[0];
};
/**
* @private
* @param newOption
* @param oldOption
*/
TimeConductorController.prototype.selectMenuOption = function (newOption, oldOption){
if (newOption !== oldOption) {
var config = this.getConfig(this.timeAPI.timeSystem(), newOption.clock);
if (config === undefined) {
//Default to first time system available if the current one is not compatible with the new clock
var timeSystem = this.timeSystemsForClocks[newOption.key][0];
this.$scope.timeSystemModel.selected = timeSystem;
this.setTimeSystemFromView(timeSystem.key);
config = this.getConfig(timeSystem, newOption.clock);
}
if (newOption.key === 'fixed') {
this.timeAPI.stopClock();
} else {
this.timeAPI.clock(newOption.key, config.clockOffsets);
}
}
};
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]);
}
/**
* @private
* @param config
* @returns {*[]}
*/
TimeConductorController.prototype.optionsFromConfig = function (config) {
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;
var validDeltas = searchParams[SEARCH.MODE] !== 'fixed' &&
searchParams[SEARCH.START_DELTA] &&
searchParams[SEARCH.END_DELTA] &&
!isNaN(searchParams[SEARCH.START_DELTA]) &&
!isNaN(searchParams[SEARCH.END_DELTA]);
(config.menuOptions || []).forEach(function (menuOption) {
var clock = this.getClock(menuOption.clock);
var clockKey = menuOption.clock || 'fixed';
if (validDeltas) {
//Sets deltas from some form model
this.setDeltas({
startDelta: parseInt(searchParams[SEARCH.START_DELTA]),
endDelta: parseInt(searchParams[SEARCH.END_DELTA])
var timeSystem = this.getTimeSystem(menuOption.timeSystem);
if (timeSystem !== undefined) {
if (clock !== undefined) {
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.log('Unknown clock "' + clockKey + '", has it been registered?');
}
}.bind(this));
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));
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])
});
}
return options;
};
/**
* @private
*/
TimeConductorController.prototype.destroy = function () {
this.conductor.off('bounds', this.changeBounds);
this.conductor.off('timeSystem', this.changeTimeSystem);
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);
};
/**
* 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);
}
}
};
/**
* Called when the bounds change in the time conductor. Synchronizes
* the bounds values in the time conductor with those in the form
* @param {TimeConductorBounds}
*/
TimeConductorController.prototype.setFormFromBounds = function (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);
}
@@ -245,42 +268,41 @@ define(
};
/**
* On mode change, populate form based on time systems available
* from the selected mode.
* @param mode
* @private
* @param timeSystem
* @param clock
* @returns {T}
*/
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 deltas change, update the values in the UI
* When the offsets 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;
TimeConductorController.prototype.setViewFromOffsets = function (offsets) {
this.$scope.boundsModel.startOffset = Math.abs(offsets.start);
this.$scope.boundsModel.endOffset = offsets.end;
};
/**
* Initialize the form when time system changes.
* @param {TimeSystem} timeSystem
* Called when form values are changed.
* @param formModel
*/
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;
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
});
}
};
@@ -288,65 +310,82 @@ define(
* Called when form values are changed.
* @param 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();
if (offsets.start !== existingOffsets.start || offsets.end !== existingOffsets.end) {
//Sychronize offsets between form and time API
this.timeAPI.clockOffsets(offsets);
}
}
};
/**
* Called when the delta values in the form change. Validates and
* sets the new deltas on the Mode.
* @param boundsModel
* @see TimeConductorMode
* @private
* @returns {boolean}
*/
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);
}
TimeConductorController.prototype.supportsZoom = function () {
var config = this.getConfig(this.timeAPI.timeSystem(), this.timeAPI.clock());
return config && (config.zoomInLimit !== undefined && config.zoomOutLimit !== undefined);
};
/**
* 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.
* default values for bound and offsets in the form.
*
* @private
* @param newModeKey
* @param newClockKey
* @param oldModeKey
*/
TimeConductorController.prototype.setMode = function (newModeKey, oldModeKey) {
//Set mode in URL on change
this.setParam(SEARCH.MODE, newModeKey);
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];
if (newModeKey !== oldModeKey) {
this.conductorViewService.mode(newModeKey);
this.setFormFromMode(newModeKey);
this.menu.selected = menuOption;
if (newModeKey === "fixed") {
this.setParam(SEARCH.START_DELTA, undefined);
this.setParam(SEARCH.END_DELTA, undefined);
//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) {
var bounds = config.bounds;
this.timeAPI.timeSystem(selectedTimeSystem, bounds);
} 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);
}
//When time system changes, some start bounds need to be provided
var bounds = {
start: clock.currentValue() + config.clockOffsets.start,
end: clock.currentValue() + config.clockOffsets.end
};
this.timeAPI.timeSystem(selectedTimeSystem, bounds);
}
}
this.mode = clock === undefined ? 'fixed' : 'realtime';
if (clock !== undefined) {
this.setViewFromOffsets(this.timeAPI.clockOffsets());
} else {
this.setViewFromBounds(this.timeAPI.bounds());
}
this.zoom = this.supportsZoom();
this.$scope.timeSystemModel.options = timeSystems;
};
/**
@@ -358,40 +397,54 @@ 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.getTimeSystem(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;
} else {
bounds = {
start: clock.currentValue() + config.clockOffsets.start,
end: clock.currentValue() + config.clockOffsets.end
};
}
this.timeAPI.timeSystem(timeSystem, bounds);
};
/**
* 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();
};
/**
@@ -400,13 +453,9 @@ define(
* @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 +464,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,8 +482,8 @@ 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);
@@ -438,8 +491,8 @@ define(
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,8 +506,8 @@ define(
* @fires platform.features.conductor.TimeConductorController~zoomStop
*/
TimeConductorController.prototype.onZoomStop = function () {
this.setBounds(this.$scope.boundsModel);
this.setDeltas(this.$scope.boundsModel);
this.setBoundsFromView(this.$scope.boundsModel);
this.setOffsetsFromView(this.$scope.boundsModel);
this.zooming = false;
this.conductorViewService.emit('zoom-stop');

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

@@ -22,15 +22,14 @@
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.
* TimeConductor API itself such as modes and offsets.
*
* @memberof platform.features.conductor
* @param conductor
@@ -41,167 +40,24 @@ define(
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;
TimeConductorViewService.prototype.calculateBoundsFromOffsets = function (offsets) {
var oldEnd = this.timeAPI.bounds().end;
if (offsets && offsets.end !== undefined) {
//Calculate the previous raw end value (without delta)
oldEnd = oldEnd - offsets.end;
}
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();
var bounds = {
start: oldEnd - offsets.start,
end: oldEnd + offsets.end
};
return bounds;
};
/**
@@ -219,7 +75,24 @@ 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
};
zoom.bounds = this.calculateBoundsFromOffsets(zoom.offsets);
} 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

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

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

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

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

@@ -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
@@ -95,7 +98,7 @@ define(
this.registerChangeListeners();
}.bind(this));
this.setScroll(this.openmct.conductor.follow());
this.setScroll(this.openmct.time.clock() !== undefined);
this.$scope.$on("$destroy", this.destroy);
}
@@ -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('follow', this.setScroll);
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('follow', this.setScroll);
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

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