[Time Conductor] Misc. bug fixes, additional documentation for conductor elements, moved and renamed LAD tick source

This commit is contained in:
Henry
2017-04-29 16:02:25 -07:00
parent 6628f93823
commit 31897ec520
10 changed files with 176 additions and 97 deletions

View File

@ -26,14 +26,6 @@ define(
'./TimeConductorValidation' './TimeConductorValidation'
], ],
function (moment, TimeConductorValidation) { function (moment, TimeConductorValidation) {
var SEARCH = {
MODE: 'tc.mode',
TIME_SYSTEM: 'tc.timeSystem',
START_BOUND: 'tc.startBound',
END_BOUND: 'tc.endBound',
START_DELTA: 'tc.startDelta',
END_DELTA: 'tc.endDelta'
};
var timeUnitsMegastructure = [ var timeUnitsMegastructure = [
["Decades", function (r) { ["Decades", function (r) {
@ -63,8 +55,10 @@ define(
]; ];
/** /**
* Controller for the Time Conductor UI element. The Time Conductor includes form fields for specifying time * Controller for the Time Conductor UI element. The Time Conductor
* bounds and relative time offsets for queries, as well as controls for selection mode, time systems, and zooming. * includes form fields for specifying time bounds and relative time
* offsets for queries, as well as controls for selection mode,
* time systems, and zooming.
* @memberof platform.features.conductor * @memberof platform.features.conductor
* @constructor * @constructor
*/ */
@ -77,14 +71,20 @@ define(
config config
) { ) {
var self = this; //Bind functions that are used as callbacks to 'this'.
[
//Bind all class functions to 'this' "selectMenuOption",
Object.keys(TimeConductorController.prototype).filter(function (key) { "onPan",
return typeof TimeConductorController.prototype[key] === 'function'; "onPanStop",
}).forEach(function (key) { "setViewFromBounds",
self[key] = self[key].bind(self); "setViewFromClock",
}); "setViewFromOffsets",
"setViewFromTimeSystem",
"setTimeSystemFromView",
"destroy"
].forEach(function (name) {
this[name] = this[name].bind(this);
}.bind(this));
this.$scope = $scope; this.$scope = $scope;
this.$window = $window; this.$window = $window;
@ -142,13 +142,24 @@ define(
this.$scope.$on('$destroy', this.destroy); this.$scope.$on('$destroy', this.destroy);
} }
/**
* Given a key for a clock, retrieve the clock object.
* @private
* @param key
* @returns {Clock}
*/
TimeConductorController.prototype.getClock = function (key) { TimeConductorController.prototype.getClock = function (key) {
return this.timeAPI.getAllClocks().filter(function (clock) { return this.timeAPI.getAllClocks().filter(function (clock) {
return clock.key === key; return clock.key === key;
})[0]; })[0];
}; };
/**
* Given a key for a time system, retrieve the time system object.
* @private
* @param key
* @returns {TimeSystem}
*/
TimeConductorController.prototype.getTimeSystem = function (key) { TimeConductorController.prototype.getTimeSystem = function (key) {
return this.timeAPI.getAllTimeSystems().filter(function (timeSystem) { return this.timeAPI.getAllTimeSystems().filter(function (timeSystem) {
return timeSystem.key === key; return timeSystem.key === key;
@ -156,6 +167,10 @@ define(
}; };
/** /**
* Activate the selected menu option. Menu options correspond to clocks.
* A distinction is made to avoid confusion between the menu options and
* their metadata, and actual {@link Clock} objects.
*
* @private * @private
* @param newOption * @param newOption
* @param oldOption * @param oldOption
@ -163,8 +178,13 @@ define(
TimeConductorController.prototype.selectMenuOption = function (newOption, oldOption){ TimeConductorController.prototype.selectMenuOption = function (newOption, oldOption){
if (newOption !== oldOption) { if (newOption !== oldOption) {
var config = this.getConfig(this.timeAPI.timeSystem(), newOption.clock); var config = this.getConfig(this.timeAPI.timeSystem(), newOption.clock);
/*
* If there is no configuration defined for the selected clock
* and time system default to the first time system that
* configuration is available for.
*/
if (config === undefined) { 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]; var timeSystem = this.timeSystemsForClocks[newOption.key][0];
this.$scope.timeSystemModel.selected = timeSystem; this.$scope.timeSystemModel.selected = timeSystem;
this.setTimeSystemFromView(timeSystem.key); this.setTimeSystemFromView(timeSystem.key);
@ -180,11 +200,15 @@ define(
}; };
/** /**
* From the provided configuration, build the available menu options.
* @private * @private
* @param config * @param config
* @returns {*[]} * @returns {*[]}
*/ */
TimeConductorController.prototype.optionsFromConfig = function (config) { TimeConductorController.prototype.optionsFromConfig = function (config) {
/*
* "Fixed Mode" is always the first available option.
*/
var options = [{ var options = [{
key: 'fixed', key: 'fixed',
name: 'Fixed Timespan Mode', name: 'Fixed Timespan Mode',
@ -202,6 +226,8 @@ define(
var timeSystem = this.getTimeSystem(menuOption.timeSystem); var timeSystem = this.getTimeSystem(menuOption.timeSystem);
if (timeSystem !== undefined) { if (timeSystem !== undefined) {
if (clock !== undefined) { if (clock !== undefined) {
// Use an associative array to built a set of unique
// clocks
clocks[clock.key] = clock; clocks[clock.key] = clock;
clocksForTimeSystem[timeSystem.key] = clocksForTimeSystem[timeSystem.key] || []; clocksForTimeSystem[timeSystem.key] = clocksForTimeSystem[timeSystem.key] || [];
clocksForTimeSystem[timeSystem.key].push(clock); clocksForTimeSystem[timeSystem.key].push(clock);
@ -213,6 +239,9 @@ define(
} }
}.bind(this)); }.bind(this));
/*
* Populate the clocks menu with metadata from the available clocks
*/
Object.values(clocks).forEach(function (clock) { Object.values(clocks).forEach(function (clock) {
options.push({ options.push({
key: clock.key, key: clock.key,
@ -228,23 +257,8 @@ define(
}; };
/** /**
* @private * When bounds change, set UI values from the new bounds.
*/ * @param {TimeBounds} bounds the bounds
TimeConductorController.prototype.destroy = function () {
this.timeAPI.off('bounds', this.setViewFromBounds);
this.timeAPI.off('timeSystem', this.setViewFromTimeSystem);
this.timeAPI.off('clock', this.setViewFromClock);
this.timeAPI.off('follow', this.setFollow);
this.timeAPI.off('clockOffsets', this.setViewFromOffsets);
this.conductorViewService.off('pan', this.onPan);
this.conductorViewService.off('pan-stop', this.onPanStop);
};
/**
* 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.setViewFromBounds = function (bounds) { TimeConductorController.prototype.setViewFromBounds = function (bounds) {
if (!this.zooming && !this.panning) { if (!this.zooming && !this.panning) {
@ -257,6 +271,10 @@ define(
this.toTimeUnits(bounds.end - bounds.start); this.toTimeUnits(bounds.end - bounds.start);
} }
/*
Ensure that a digest occurs, capped at the browser's refresh
rate.
*/
if (!this.pendingUpdate) { if (!this.pendingUpdate) {
this.pendingUpdate = true; this.pendingUpdate = true;
this.$window.requestAnimationFrame(function () { this.$window.requestAnimationFrame(function () {
@ -268,10 +286,13 @@ define(
}; };
/** /**
* Retrieve any configuration defined for the provided time system and
* clock
* @private * @private
* @param timeSystem * @param timeSystem
* @param clock * @param clock
* @returns {T} * @returns {object} The Time Conductor configuration corresponding to
* the provided combination of time system and clock
*/ */
TimeConductorController.prototype.getConfig = function (timeSystem, clock) { TimeConductorController.prototype.getConfig = function (timeSystem, clock) {
var clockKey = clock && clock.key; var clockKey = clock && clock.key;
@ -284,7 +305,8 @@ define(
}; };
/** /**
* When the offsets change, update the values in the UI * When the clock offsets change, update the values in the UI
* @param {ClockOffsets} offsets
* @private * @private
*/ */
TimeConductorController.prototype.setViewFromOffsets = function (offsets) { TimeConductorController.prototype.setViewFromOffsets = function (offsets) {
@ -293,8 +315,9 @@ define(
}; };
/** /**
* Called when form values are changed. * When form values for bounds change, update the bounds in the Time API
* @param formModel * to trigger an application-wide bounds change.
* @param {object} boundsModel
*/ */
TimeConductorController.prototype.setBoundsFromView = function (boundsModel) { TimeConductorController.prototype.setBoundsFromView = function (boundsModel) {
var bounds = this.timeAPI.bounds(); var bounds = this.timeAPI.bounds();
@ -307,8 +330,9 @@ define(
}; };
/** /**
* Called when form values are changed. * When form values for bounds change, update the bounds in the Time API
* @param formModel * to trigger an application-wide bounds change.
* @param {object} formModel
*/ */
TimeConductorController.prototype.setOffsetsFromView = function (boundsModel) { TimeConductorController.prototype.setOffsetsFromView = function (boundsModel) {
if (this.validation.validateStartOffset(boundsModel.startOffset) && this.validation.validateEndOffset(boundsModel.endOffset)) { if (this.validation.validateStartOffset(boundsModel.startOffset) && this.validation.validateEndOffset(boundsModel.endOffset)) {
@ -335,13 +359,14 @@ define(
}; };
/** /**
* Change the selected Time Conductor mode. This will call destroy * Update the UI state to reflect a change in clock. Provided conductor
* and initialization functions on the relevant modes, setting * configuration will be checked for compatibility between the new clock
* default values for bound and offsets in the form. * and the currently selected time system. If configuration is not available,
* an attempt will be made to default to a time system that is compatible
* with the new clock
* *
* @private * @private
* @param newClockKey * @param {Clock} clock
* @param oldModeKey
*/ */
TimeConductorController.prototype.setViewFromClock = function (clock) { TimeConductorController.prototype.setViewFromClock = function (clock) {
var newClockKey = clock && clock.key; var newClockKey = clock && clock.key;
@ -411,13 +436,20 @@ define(
*/ */
if (clock === undefined) { if (clock === undefined) {
bounds = config.bounds; bounds = config.bounds;
this.timeAPI.timeSystem(timeSystem, bounds);
} else { } else {
bounds = { bounds = {
start: clock.currentValue() + config.clockOffsets.start, start: clock.currentValue() + config.clockOffsets.start,
end: clock.currentValue() + config.clockOffsets.end end: clock.currentValue() + config.clockOffsets.end
}; };
} //Has time system change resulted in offsets change (based on config)?
this.timeAPI.timeSystem(timeSystem, bounds); this.timeAPI.timeSystem(timeSystem, bounds);
var configOffsets = config.clockOffsets;
var apiOffsets = this.timeAPI.clockOffsets();
if (configOffsets.start !== apiOffsets.start || configOffsets.end !== apiOffsets.end) {
this.timeAPI.clockOffsets(configOffsets);
}
}
}; };
/** /**
@ -450,6 +482,7 @@ define(
/** /**
* Takes a time span and calculates a slider increment value, used * Takes a time span and calculates a slider increment value, used
* to set the horizontal offset of the slider. * to set the horizontal offset of the slider.
* @private
* @param {number} timeSpan a duration of time, in ms * @param {number} timeSpan a duration of time, in ms
* @returns {number} a value between 0.01 and 0.99, in increments of .01 * @returns {number} a value between 0.01 and 0.99, in increments of .01
*/ */
@ -486,6 +519,7 @@ define(
var timeSpan = Math.pow((1 - sliderValue), 4) * (config.zoomOutLimit - config.zoomInLimit); var timeSpan = Math.pow((1 - sliderValue), 4) * (config.zoomOutLimit - config.zoomInLimit);
var zoom = this.conductorViewService.zoom(timeSpan); var zoom = this.conductorViewService.zoom(timeSpan);
this.zooming = true;
this.$scope.boundsModel.start = zoom.bounds.start; this.$scope.boundsModel.start = zoom.bounds.start;
this.$scope.boundsModel.end = zoom.bounds.end; this.$scope.boundsModel.end = zoom.bounds.end;
@ -506,10 +540,12 @@ define(
* @fires platform.features.conductor.TimeConductorController~zoomStop * @fires platform.features.conductor.TimeConductorController~zoomStop
*/ */
TimeConductorController.prototype.onZoomStop = function () { TimeConductorController.prototype.onZoomStop = function () {
this.setBoundsFromView(this.$scope.boundsModel); if (this.timeAPI.clock() !== undefined) {
this.setOffsetsFromView(this.$scope.boundsModel); this.setOffsetsFromView(this.$scope.boundsModel);
this.zooming = false; }
this.setBoundsFromView(this.$scope.boundsModel);
this.zooming = false;
this.conductorViewService.emit('zoom-stop'); this.conductorViewService.emit('zoom-stop');
}; };
@ -534,6 +570,20 @@ define(
this.panning = false; this.panning = false;
}; };
/**
* @private
*/
TimeConductorController.prototype.destroy = function () {
this.timeAPI.off('bounds', this.setViewFromBounds);
this.timeAPI.off('timeSystem', this.setViewFromTimeSystem);
this.timeAPI.off('clock', this.setViewFromClock);
this.timeAPI.off('follow', this.setFollow);
this.timeAPI.off('clockOffsets', this.setViewFromOffsets);
this.conductorViewService.off('pan', this.onPan);
this.conductorViewService.off('pan-stop', this.onPanStop);
};
return TimeConductorController; return TimeConductorController;
} }
); );

View File

@ -27,16 +27,20 @@ define(
function (EventEmitter) { function (EventEmitter) {
/** /**
* A class representing the state of the time conductor view. This * The TimeConductorViewService acts as an event bus between different
* exposes details of the UI that are not represented on the * elements of the Time Conductor UI. Zooming and panning occur via this
* TimeConductor API itself such as modes and offsets. * service, as they are specific behaviour of the UI, and not general
* functions of the time API.
*
* Synchronization of conductor state between the Time API and the URL
* also occurs from the conductor view service, whose lifecycle persists
* between view changes.
* *
* @memberof platform.features.conductor * @memberof platform.features.conductor
* @param conductor * @param conductor
* @param timeSystems
* @constructor * @constructor
*/ */
function TimeConductorViewService(openmct, timeSystems) { function TimeConductorViewService(openmct) {
EventEmitter.call(this); EventEmitter.call(this);
@ -45,21 +49,6 @@ define(
TimeConductorViewService.prototype = Object.create(EventEmitter.prototype); TimeConductorViewService.prototype = Object.create(EventEmitter.prototype);
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;
}
var bounds = {
start: oldEnd - offsets.start,
end: oldEnd + offsets.end
};
return bounds;
};
/** /**
* An event to indicate that zooming is taking place * An event to indicate that zooming is taking place
* @event platform.features.conductor.TimeConductorViewService~zoom * @event platform.features.conductor.TimeConductorViewService~zoom
@ -81,10 +70,16 @@ define(
// important. Calculate zoom based on 'now'. // important. Calculate zoom based on 'now'.
if (this.timeAPI.clock() !== undefined) { if (this.timeAPI.clock() !== undefined) {
zoom.offsets = { zoom.offsets = {
start: timeSpan, start: -timeSpan,
end: this.timeAPI.clockOffsets().end end: this.timeAPI.clockOffsets().end
}; };
zoom.bounds = this.calculateBoundsFromOffsets(zoom.offsets);
var currentVal = this.timeAPI.clock().currentValue();
zoom.bounds = {
start: currentVal + zoom.offsets.start,
end: currentVal + zoom.offsets.end
};
} else { } else {
var bounds = this.timeAPI.bounds(); var bounds = this.timeAPI.bounds();
var center = bounds.start + ((bounds.end - bounds.start)) / 2; var center = bounds.start + ((bounds.end - bounds.start)) / 2;

View File

@ -22,20 +22,21 @@
define(['../../../src/plugins/utcTimeSystem/LocalClock'], function (LocalClock) { define(['../../../src/plugins/utcTimeSystem/LocalClock'], function (LocalClock) {
/** /**
* @implements TickSource * A {@link Clock} that mocks a "latest available data" type tick source.
* This is for testing purposes only, and behaves identically to a local clock.
* It DOES NOT tick on receipt of data.
* @constructor * @constructor
*/ */
function LADTickSource (period) { function LADClock (period) {
LocalClock.call(this, period); LocalClock.call(this, period);
this.key = 'test-lad'; this.key = 'test-lad';
this.mode = 'lad'; this.mode = 'lad';
this.cssClass = 'icon-database'; this.cssClass = 'icon-database';
this.label = 'Latest Available Data';
this.name = 'Latest available data'; this.name = 'Latest available data';
this.description = "Updates when when new data is available"; this.description = "Updates when when new data is available";
} }
LADTickSource.prototype = Object.create(LocalClock.prototype); LADClock.prototype = Object.create(LocalClock.prototype);
return LADTickSource; return LADClock;
}); });

View File

@ -0,0 +1,33 @@
/*****************************************************************************
* 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([
"./LADClock"
], function (
LADClock
) {
return function () {
return function (openmct) {
openmct.time.addClock(new LADClock());
}
};
});

View File

@ -20,12 +20,7 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
define([ define([], function () {
'../../../src/plugins/utcTimeSystem/LocalClock',
'./LADTickSource'
], function (LocalClock, LADTickSource) {
var THIRTY_MINUTES = 30 * 60 * 1000,
DEFAULT_PERIOD = 1000;
/** /**
* This time system supports UTC dates and provides a ticking clock source. * This time system supports UTC dates and provides a ticking clock source.

View File

@ -22,17 +22,14 @@
define([ define([
"./LocalTimeSystem", "./LocalTimeSystem",
"./LocalTimeFormat", "./LocalTimeFormat"
"./LADTickSource"
], function ( ], function (
LocalTimeSystem, LocalTimeSystem,
LocalTimeFormat, LocalTimeFormat
LADTickSource
) { ) {
return function () { return function () {
return function (openmct) { return function (openmct) {
openmct.time.addTimeSystem(new LocalTimeSystem()); openmct.time.addTimeSystem(new LocalTimeSystem());
openmct.time.addClock(new LADTickSource());
openmct.legacyExtension('formats', { openmct.legacyExtension('formats', {
key: 'local-format', key: 'local-format',

View File

@ -22,7 +22,10 @@
define(['EventEmitter'], function (EventEmitter) { define(['EventEmitter'], function (EventEmitter) {
/** /**
* @implements TickSource * A {@link openmct.TimeAPI.Clock} that updates the temporal bounds of the
* application based on UTC time values provided by a ticking local clock,
* with the periodicity specified.
* @param {number} period The periodicity with which the clock should tick
* @constructor * @constructor
*/ */
function LocalClock(period) { function LocalClock(period) {
@ -33,7 +36,6 @@ define(['EventEmitter'], function (EventEmitter) {
*/ */
this.key = 'local'; this.key = 'local';
this.cssClass = 'icon-clock'; this.cssClass = 'icon-clock';
this.label = 'Local Clock';
this.name = 'Local Clock'; this.name = 'Local Clock';
this.description = "Updates every second, providing UTC timestamps from " + this.description = "Updates every second, providing UTC timestamps from " +
"user's local computer."; "user's local computer.";
@ -62,6 +64,9 @@ define(['EventEmitter'], function (EventEmitter) {
} }
}; };
/**
* @private
*/
LocalClock.prototype.tick = function () { LocalClock.prototype.tick = function () {
var now = Date.now(); var now = Date.now();
this.emit("tick", now); this.emit("tick", now);
@ -102,6 +107,9 @@ define(['EventEmitter'], function (EventEmitter) {
return result; return result;
}; };
/**
* @returns {number} The last value provided for a clock tick
*/
LocalClock.prototype.currentValue = function () { LocalClock.prototype.currentValue = function () {
return this.lastTick; return this.lastTick;
}; };

View File

@ -22,24 +22,21 @@
define([], function () { define([], function () {
/** /**
* This time system supports UTC dates and provides a ticking clock source. * This time system supports UTC dates.
* @implements TimeSystem * @implements TimeSystem
* @constructor * @constructor
*/ */
function UTCTimeSystem() { function UTCTimeSystem() {
/** /**
* Some metadata, which will be used to identify the time system in * Metadata used to identify the time system in
* the UI * the UI
* @type {{key: string, name: string, cssClass: string}}
*/ */
this.key = 'utc'; this.key = 'utc';
this.name = 'UTC'; this.name = 'UTC';
this.cssClass = 'icon-clock'; this.cssClass = 'icon-clock';
this.timeFormat = 'utc'; this.timeFormat = 'utc';
this.durationFormat = 'duration'; this.durationFormat = 'duration';
this.isUTCBased = true; this.isUTCBased = true;
} }

View File

@ -27,7 +27,10 @@ define([
UTCTimeSystem, UTCTimeSystem,
LocalClock LocalClock
) { ) {
var ONE_DAY = 24 * 60 * 60 * 1000; /**
* Install a time system that supports UTC times. It also installs a local
* clock source that ticks every 100ms, providing UTC times.
*/
return function () { return function () {
return function (openmct) { return function (openmct) {
var timeSystem = new UTCTimeSystem(); var timeSystem = new UTCTimeSystem();