From 31897ec520694e30ddc556a42aa9bc7e826b5a07 Mon Sep 17 00:00:00 2001 From: Henry Date: Sat, 29 Apr 2017 16:02:25 -0700 Subject: [PATCH] [Time Conductor] Misc. bug fixes, additional documentation for conductor elements, moved and renamed LAD tick source --- .../core/src/ui/TimeConductorController.js | 152 ++++++++++++------ .../core/src/ui/TimeConductorViewService.js | 39 ++--- .../latestDataClock/LADClock.js} | 11 +- src/plugins/latestDataClock/plugin.js | 33 ++++ .../localTimeSystem/LocalTimeFormat.js | 0 .../localTimeSystem/LocalTimeSystem.js | 7 +- .../localTimeSystem/plugin.js | 7 +- src/plugins/utcTimeSystem/LocalClock.js | 12 +- src/plugins/utcTimeSystem/UTCTimeSystem.js | 7 +- src/plugins/utcTimeSystem/plugin.js | 5 +- 10 files changed, 176 insertions(+), 97 deletions(-) rename src/{example/localTimeSystem/LADTickSource.js => plugins/latestDataClock/LADClock.js} (83%) create mode 100644 src/plugins/latestDataClock/plugin.js rename src/{example => plugins}/localTimeSystem/LocalTimeFormat.js (100%) rename src/{example => plugins}/localTimeSystem/LocalTimeSystem.js (90%) rename src/{example => plugins}/localTimeSystem/plugin.js (91%) diff --git a/platform/features/conductor/core/src/ui/TimeConductorController.js b/platform/features/conductor/core/src/ui/TimeConductorController.js index 003e8f5faf..863fd3236c 100644 --- a/platform/features/conductor/core/src/ui/TimeConductorController.js +++ b/platform/features/conductor/core/src/ui/TimeConductorController.js @@ -26,14 +26,6 @@ define( './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 = [ ["Decades", function (r) { @@ -63,8 +55,10 @@ define( ]; /** - * Controller for the Time Conductor UI element. The Time Conductor includes form fields for specifying time - * bounds and relative time offsets for queries, as well as controls for selection mode, time systems, and zooming. + * Controller for the Time Conductor UI element. The Time Conductor + * includes form fields for specifying time bounds and relative time + * offsets for queries, as well as controls for selection mode, + * time systems, and zooming. * @memberof platform.features.conductor * @constructor */ @@ -77,14 +71,20 @@ define( config ) { - var self = this; - - //Bind all class functions to 'this' - Object.keys(TimeConductorController.prototype).filter(function (key) { - return typeof TimeConductorController.prototype[key] === 'function'; - }).forEach(function (key) { - self[key] = self[key].bind(self); - }); + //Bind functions that are used as callbacks to 'this'. + [ + "selectMenuOption", + "onPan", + "onPanStop", + "setViewFromBounds", + "setViewFromClock", + "setViewFromOffsets", + "setViewFromTimeSystem", + "setTimeSystemFromView", + "destroy" + ].forEach(function (name) { + this[name] = this[name].bind(this); + }.bind(this)); this.$scope = $scope; this.$window = $window; @@ -142,13 +142,24 @@ define( 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) { return this.timeAPI.getAllClocks().filter(function (clock) { return clock.key === key; })[0]; }; + /** + * Given a key for a time system, retrieve the time system object. + * @private + * @param key + * @returns {TimeSystem} + */ TimeConductorController.prototype.getTimeSystem = function (key) { return this.timeAPI.getAllTimeSystems().filter(function (timeSystem) { 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 * @param newOption * @param oldOption @@ -163,8 +178,13 @@ define( TimeConductorController.prototype.selectMenuOption = function (newOption, oldOption){ if (newOption !== oldOption) { 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) { - //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); @@ -180,11 +200,15 @@ define( }; /** + * From the provided configuration, build the available menu options. * @private * @param config * @returns {*[]} */ TimeConductorController.prototype.optionsFromConfig = function (config) { + /* + * "Fixed Mode" is always the first available option. + */ var options = [{ key: 'fixed', name: 'Fixed Timespan Mode', @@ -202,6 +226,8 @@ define( var timeSystem = this.getTimeSystem(menuOption.timeSystem); if (timeSystem !== undefined) { if (clock !== undefined) { + // Use an associative array to built a set of unique + // clocks clocks[clock.key] = clock; clocksForTimeSystem[timeSystem.key] = clocksForTimeSystem[timeSystem.key] || []; clocksForTimeSystem[timeSystem.key].push(clock); @@ -213,6 +239,9 @@ define( } }.bind(this)); + /* + * Populate the clocks menu with metadata from the available clocks + */ Object.values(clocks).forEach(function (clock) { options.push({ key: clock.key, @@ -228,23 +257,8 @@ define( }; /** - * @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); - }; - - /** - * Called when the bounds change in the time conductor. Synchronizes - * the bounds values in the time conductor with those in the form - * @param {TimeConductorBounds} + * When bounds change, set UI values from the new bounds. + * @param {TimeBounds} bounds the bounds */ TimeConductorController.prototype.setViewFromBounds = function (bounds) { if (!this.zooming && !this.panning) { @@ -257,6 +271,10 @@ define( this.toTimeUnits(bounds.end - bounds.start); } + /* + Ensure that a digest occurs, capped at the browser's refresh + rate. + */ if (!this.pendingUpdate) { this.pendingUpdate = true; this.$window.requestAnimationFrame(function () { @@ -268,10 +286,13 @@ define( }; /** + * Retrieve any configuration defined for the provided time system and + * clock * @private * @param timeSystem * @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) { 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 */ TimeConductorController.prototype.setViewFromOffsets = function (offsets) { @@ -293,8 +315,9 @@ define( }; /** - * Called when form values are changed. - * @param formModel + * When form values for bounds change, update the bounds in the Time API + * to trigger an application-wide bounds change. + * @param {object} boundsModel */ TimeConductorController.prototype.setBoundsFromView = function (boundsModel) { var bounds = this.timeAPI.bounds(); @@ -307,8 +330,9 @@ define( }; /** - * Called when form values are changed. - * @param formModel + * When form values for bounds change, update the bounds in the Time API + * to trigger an application-wide bounds change. + * @param {object} formModel */ TimeConductorController.prototype.setOffsetsFromView = function (boundsModel) { 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 - * and initialization functions on the relevant modes, setting - * default values for bound and offsets in the form. + * Update the UI state to reflect a change in clock. Provided conductor + * configuration will be checked for compatibility between the new clock + * and the currently selected time system. If configuration is not available, + * an attempt will be made to default to a time system that is compatible + * with the new clock * * @private - * @param newClockKey - * @param oldModeKey + * @param {Clock} clock */ TimeConductorController.prototype.setViewFromClock = function (clock) { var newClockKey = clock && clock.key; @@ -411,13 +436,20 @@ define( */ if (clock === undefined) { bounds = config.bounds; + this.timeAPI.timeSystem(timeSystem, bounds); } else { bounds = { start: clock.currentValue() + config.clockOffsets.start, end: clock.currentValue() + config.clockOffsets.end }; + //Has time system change resulted in offsets change (based on config)? + this.timeAPI.timeSystem(timeSystem, bounds); + var configOffsets = config.clockOffsets; + var apiOffsets = this.timeAPI.clockOffsets(); + if (configOffsets.start !== apiOffsets.start || configOffsets.end !== apiOffsets.end) { + this.timeAPI.clockOffsets(configOffsets); + } } - this.timeAPI.timeSystem(timeSystem, bounds); }; /** @@ -450,6 +482,7 @@ define( /** * Takes a time span and calculates a slider increment value, used * to set the horizontal offset of the slider. + * @private * @param {number} timeSpan a duration of time, in ms * @returns {number} a value between 0.01 and 0.99, in increments of .01 */ @@ -486,6 +519,7 @@ define( var timeSpan = Math.pow((1 - sliderValue), 4) * (config.zoomOutLimit - config.zoomInLimit); var zoom = this.conductorViewService.zoom(timeSpan); + this.zooming = true; this.$scope.boundsModel.start = zoom.bounds.start; this.$scope.boundsModel.end = zoom.bounds.end; @@ -506,10 +540,12 @@ define( * @fires platform.features.conductor.TimeConductorController~zoomStop */ TimeConductorController.prototype.onZoomStop = function () { + if (this.timeAPI.clock() !== undefined) { + this.setOffsetsFromView(this.$scope.boundsModel); + } this.setBoundsFromView(this.$scope.boundsModel); - this.setOffsetsFromView(this.$scope.boundsModel); - this.zooming = false; + this.zooming = false; this.conductorViewService.emit('zoom-stop'); }; @@ -534,6 +570,20 @@ define( this.panning = false; }; + /** + * @private + */ + TimeConductorController.prototype.destroy = function () { + this.timeAPI.off('bounds', this.setViewFromBounds); + this.timeAPI.off('timeSystem', this.setViewFromTimeSystem); + this.timeAPI.off('clock', this.setViewFromClock); + this.timeAPI.off('follow', this.setFollow); + this.timeAPI.off('clockOffsets', this.setViewFromOffsets); + + this.conductorViewService.off('pan', this.onPan); + this.conductorViewService.off('pan-stop', this.onPanStop); + }; + return TimeConductorController; } ); diff --git a/platform/features/conductor/core/src/ui/TimeConductorViewService.js b/platform/features/conductor/core/src/ui/TimeConductorViewService.js index 1598db34a1..001e8221d0 100644 --- a/platform/features/conductor/core/src/ui/TimeConductorViewService.js +++ b/platform/features/conductor/core/src/ui/TimeConductorViewService.js @@ -27,16 +27,20 @@ define( 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 offsets. + * The TimeConductorViewService acts as an event bus between different + * elements of the Time Conductor UI. Zooming and panning occur via this + * service, as they are specific behaviour of the UI, and not general + * functions of the time API. + * + * Synchronization of conductor state between the Time API and the URL + * also occurs from the conductor view service, whose lifecycle persists + * between view changes. * * @memberof platform.features.conductor * @param conductor - * @param timeSystems * @constructor */ - function TimeConductorViewService(openmct, timeSystems) { + function TimeConductorViewService(openmct) { EventEmitter.call(this); @@ -45,21 +49,6 @@ define( 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 * @event platform.features.conductor.TimeConductorViewService~zoom @@ -81,10 +70,16 @@ define( // important. Calculate zoom based on 'now'. if (this.timeAPI.clock() !== undefined) { zoom.offsets = { - start: timeSpan, + start: -timeSpan, 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 { var bounds = this.timeAPI.bounds(); var center = bounds.start + ((bounds.end - bounds.start)) / 2; diff --git a/src/example/localTimeSystem/LADTickSource.js b/src/plugins/latestDataClock/LADClock.js similarity index 83% rename from src/example/localTimeSystem/LADTickSource.js rename to src/plugins/latestDataClock/LADClock.js index af04252858..08e92986d8 100644 --- a/src/example/localTimeSystem/LADTickSource.js +++ b/src/plugins/latestDataClock/LADClock.js @@ -22,20 +22,21 @@ define(['../../../src/plugins/utcTimeSystem/LocalClock'], function (LocalClock) { /** - * @implements TickSource + * A {@link Clock} that mocks a "latest available data" type tick source. + * This is for testing purposes only, and behaves identically to a local clock. + * It DOES NOT tick on receipt of data. * @constructor */ - function LADTickSource (period) { + function LADClock (period) { LocalClock.call(this, period); this.key = 'test-lad'; this.mode = 'lad'; this.cssClass = 'icon-database'; - this.label = 'Latest Available Data'; this.name = 'Latest available data'; this.description = "Updates when when new data is available"; } - LADTickSource.prototype = Object.create(LocalClock.prototype); + LADClock.prototype = Object.create(LocalClock.prototype); - return LADTickSource; + return LADClock; }); diff --git a/src/plugins/latestDataClock/plugin.js b/src/plugins/latestDataClock/plugin.js new file mode 100644 index 0000000000..0cbcccce20 --- /dev/null +++ b/src/plugins/latestDataClock/plugin.js @@ -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()); + } + }; +}); diff --git a/src/example/localTimeSystem/LocalTimeFormat.js b/src/plugins/localTimeSystem/LocalTimeFormat.js similarity index 100% rename from src/example/localTimeSystem/LocalTimeFormat.js rename to src/plugins/localTimeSystem/LocalTimeFormat.js diff --git a/src/example/localTimeSystem/LocalTimeSystem.js b/src/plugins/localTimeSystem/LocalTimeSystem.js similarity index 90% rename from src/example/localTimeSystem/LocalTimeSystem.js rename to src/plugins/localTimeSystem/LocalTimeSystem.js index 4804c32d1a..0c8d2fbca4 100644 --- a/src/example/localTimeSystem/LocalTimeSystem.js +++ b/src/plugins/localTimeSystem/LocalTimeSystem.js @@ -20,12 +20,7 @@ * at runtime from the About dialog for additional information. *****************************************************************************/ -define([ - '../../../src/plugins/utcTimeSystem/LocalClock', - './LADTickSource' -], function (LocalClock, LADTickSource) { - var THIRTY_MINUTES = 30 * 60 * 1000, - DEFAULT_PERIOD = 1000; +define([], function () { /** * This time system supports UTC dates and provides a ticking clock source. diff --git a/src/example/localTimeSystem/plugin.js b/src/plugins/localTimeSystem/plugin.js similarity index 91% rename from src/example/localTimeSystem/plugin.js rename to src/plugins/localTimeSystem/plugin.js index 7fd7c75eb5..4cee78cce3 100644 --- a/src/example/localTimeSystem/plugin.js +++ b/src/plugins/localTimeSystem/plugin.js @@ -22,17 +22,14 @@ define([ "./LocalTimeSystem", - "./LocalTimeFormat", - "./LADTickSource" + "./LocalTimeFormat" ], function ( LocalTimeSystem, - LocalTimeFormat, - LADTickSource + LocalTimeFormat ) { return function () { return function (openmct) { openmct.time.addTimeSystem(new LocalTimeSystem()); - openmct.time.addClock(new LADTickSource()); openmct.legacyExtension('formats', { key: 'local-format', diff --git a/src/plugins/utcTimeSystem/LocalClock.js b/src/plugins/utcTimeSystem/LocalClock.js index af95799cc6..4bfa45ba11 100644 --- a/src/plugins/utcTimeSystem/LocalClock.js +++ b/src/plugins/utcTimeSystem/LocalClock.js @@ -22,7 +22,10 @@ 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 */ function LocalClock(period) { @@ -33,7 +36,6 @@ define(['EventEmitter'], function (EventEmitter) { */ this.key = 'local'; this.cssClass = 'icon-clock'; - this.label = 'Local Clock'; this.name = 'Local Clock'; this.description = "Updates every second, providing UTC timestamps from " + "user's local computer."; @@ -62,6 +64,9 @@ define(['EventEmitter'], function (EventEmitter) { } }; + /** + * @private + */ LocalClock.prototype.tick = function () { var now = Date.now(); this.emit("tick", now); @@ -102,6 +107,9 @@ define(['EventEmitter'], function (EventEmitter) { return result; }; + /** + * @returns {number} The last value provided for a clock tick + */ LocalClock.prototype.currentValue = function () { return this.lastTick; }; diff --git a/src/plugins/utcTimeSystem/UTCTimeSystem.js b/src/plugins/utcTimeSystem/UTCTimeSystem.js index bf5af2d265..e092b64541 100644 --- a/src/plugins/utcTimeSystem/UTCTimeSystem.js +++ b/src/plugins/utcTimeSystem/UTCTimeSystem.js @@ -22,24 +22,21 @@ define([], function () { /** - * This time system supports UTC dates and provides a ticking clock source. + * This time system supports UTC dates. * @implements TimeSystem * @constructor */ function UTCTimeSystem() { /** - * Some metadata, which will be used to identify the time system in + * Metadata used to identify the time system in * the UI - * @type {{key: string, name: string, cssClass: string}} */ this.key = 'utc'; this.name = 'UTC'; this.cssClass = 'icon-clock'; - this.timeFormat = 'utc'; this.durationFormat = 'duration'; - this.isUTCBased = true; } diff --git a/src/plugins/utcTimeSystem/plugin.js b/src/plugins/utcTimeSystem/plugin.js index 9cb51b5b59..e6bcb3edf6 100644 --- a/src/plugins/utcTimeSystem/plugin.js +++ b/src/plugins/utcTimeSystem/plugin.js @@ -27,7 +27,10 @@ define([ UTCTimeSystem, 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 (openmct) { var timeSystem = new UTCTimeSystem();