From bca5eb0fdbca93b9dab7b6e6382521478c50750c Mon Sep 17 00:00:00 2001 From: Henry Date: Thu, 7 Jul 2016 09:47:46 -0700 Subject: [PATCH] [Time Conductor] #933 Initial markup --- bower.json | 4 +- main.js | 11 +- .../browse/res/templates/browse-object.html | 7 +- platform/features/conductor-v2/bundle.js | 82 ++++++++ .../templates/mode-selector/mode-menu.html | 80 ++++++++ .../mode-selector/mode-selector.html | 50 +++++ .../res/templates/time-conductor.html | 94 +++++++++ .../conductor-v2/src/MctConductorAxis.js | 100 ++++++++++ .../conductor-v2/src/TimeConductor.js | 183 ++++++++++++++++++ .../src/TimeConductorController.js | 56 ++++++ .../conductor-v2/src/TimeConductorSpec.js | 110 +++++++++++ test-main.js | 6 +- 12 files changed, 779 insertions(+), 4 deletions(-) create mode 100644 platform/features/conductor-v2/bundle.js create mode 100644 platform/features/conductor-v2/res/templates/mode-selector/mode-menu.html create mode 100644 platform/features/conductor-v2/res/templates/mode-selector/mode-selector.html create mode 100644 platform/features/conductor-v2/res/templates/time-conductor.html create mode 100644 platform/features/conductor-v2/src/MctConductorAxis.js create mode 100644 platform/features/conductor-v2/src/TimeConductor.js create mode 100644 platform/features/conductor-v2/src/TimeConductorController.js create mode 100644 platform/features/conductor-v2/src/TimeConductorSpec.js diff --git a/bower.json b/bower.json index 7c913754cf..22294e9356 100644 --- a/bower.json +++ b/bower.json @@ -18,6 +18,8 @@ "node-uuid": "^1.4.7", "comma-separated-values": "^3.6.4", "FileSaver.js": "^0.0.2", - "zepto": "^1.1.6" + "zepto": "^1.1.6", + "eventemitter3": "^1.2.0", + "d3": "~4.1.0" } } diff --git a/main.js b/main.js index 4fba458a4c..5873209ade 100644 --- a/main.js +++ b/main.js @@ -28,13 +28,15 @@ requirejs.config({ "angular-route": "bower_components/angular-route/angular-route.min", "csv": "bower_components/comma-separated-values/csv.min", "es6-promise": "bower_components/es6-promise/promise.min", + "EventEmitter": "bower_components/eventemitter3/index", "moment": "bower_components/moment/moment", "moment-duration-format": "bower_components/moment-duration-format/lib/moment-duration-format", "saveAs": "bower_components/FileSaver.js/FileSaver.min", "screenfull": "bower_components/screenfull/dist/screenfull.min", "text": "bower_components/text/text", "uuid": "bower_components/node-uuid/uuid", - "zepto": "bower_components/zepto/zepto.min" + "zepto": "bower_components/zepto/zepto.min", + "d3": "bower_components/d3/d3.min" }, "shim": { "angular": { @@ -43,6 +45,9 @@ requirejs.config({ "angular-route": { "deps": ["angular"] }, + "EventEmitter": { + "exports": "EventEmitter" + }, "moment-duration-format": { "deps": ["moment"] }, @@ -51,6 +56,9 @@ requirejs.config({ }, "zepto": { "exports": "Zepto" + }, + "d3": { + "exports": "d3" } } }); @@ -82,6 +90,7 @@ define([ './platform/features/pages/bundle', './platform/features/plot/bundle', './platform/features/timeline/bundle', + './platform/features/conductor-v2/bundle', './platform/features/table/bundle', './platform/forms/bundle', './platform/identity/bundle', diff --git a/platform/commonUI/browse/res/templates/browse-object.html b/platform/commonUI/browse/res/templates/browse-object.html index 123d96bbd6..fffb017245 100644 --- a/platform/commonUI/browse/res/templates/browse-object.html +++ b/platform/commonUI/browse/res/templates/browse-object.html @@ -43,7 +43,7 @@ -
+
@@ -59,4 +59,9 @@
+ + +
diff --git a/platform/features/conductor-v2/bundle.js b/platform/features/conductor-v2/bundle.js new file mode 100644 index 0000000000..279e490458 --- /dev/null +++ b/platform/features/conductor-v2/bundle.js @@ -0,0 +1,82 @@ +/***************************************************************************** + * 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/TimeConductor", + "./src/TimeConductorController", + "./src/MCTConductorAxis", + "text!./res/templates/time-conductor.html", + "text!./res/templates/mode-selector/mode-selector.html", + "text!./res/templates/mode-selector/mode-menu.html", + 'legacyRegistry' +], function ( + TimeConductor, + TimeConductorController, + MCTConductorAxis, + timeConductorTemplate, + modeSelectorTemplate, + modeMenuTemplate, + legacyRegistry +) { + + legacyRegistry.register("platform/features/conductor-v2", { + "extensions": { + "services": [ + { + "key": "timeConductor", + "implementation": TimeConductor + } + ], + "controllers": [ + { + "key": "TimeConductorController", + "implementation": TimeConductorController, + "depends": [ + "$scope", + "timeConductor" + ] + } + ], + "directives": [ + { + "key": "mctConductorAxis", + "implementation": MCTConductorAxis, + "depends": ["$timeout"] + } + ], + "representations": [ + { + "key": "time-conductor", + "template": timeConductorTemplate + }, + { + "key": "mode-selector", + "template": modeSelectorTemplate + }, + { + "key": "mode-menu", + "template": modeMenuTemplate + } + ] + } + }); +}); diff --git a/platform/features/conductor-v2/res/templates/mode-selector/mode-menu.html b/platform/features/conductor-v2/res/templates/mode-selector/mode-menu.html new file mode 100644 index 0000000000..d926037cc8 --- /dev/null +++ b/platform/features/conductor-v2/res/templates/mode-selector/mode-menu.html @@ -0,0 +1,80 @@ + +
+ + +
diff --git a/platform/features/conductor-v2/res/templates/mode-selector/mode-selector.html b/platform/features/conductor-v2/res/templates/mode-selector/mode-selector.html new file mode 100644 index 0000000000..06cadb1182 --- /dev/null +++ b/platform/features/conductor-v2/res/templates/mode-selector/mode-selector.html @@ -0,0 +1,50 @@ + + + +
+ Fixed Time Span +
+ +
\ No newline at end of file diff --git a/platform/features/conductor-v2/res/templates/time-conductor.html b/platform/features/conductor-v2/res/templates/time-conductor.html new file mode 100644 index 0000000000..dd24b09a1b --- /dev/null +++ b/platform/features/conductor-v2/res/templates/time-conductor.html @@ -0,0 +1,94 @@ + + + \ No newline at end of file diff --git a/platform/features/conductor-v2/src/MctConductorAxis.js b/platform/features/conductor-v2/src/MctConductorAxis.js new file mode 100644 index 0000000000..54bf4520ae --- /dev/null +++ b/platform/features/conductor-v2/src/MctConductorAxis.js @@ -0,0 +1,100 @@ +/***************************************************************************** + * 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( + [ + "zepto", + "d3" + ], + function ($, d3) { + + /** + * The mct-control will dynamically include the control + * for a form element based on a symbolic key. Individual + * controls are defined under the extension category + * `controls`; this allows plug-ins to introduce new form + * control types while still making use of the form + * generator to ensure an overall consistent form style. + * @constructor + * @memberof platform/forms + */ + function MCTConductorAxis($timeout) { + + function link(scope, element, attrs, ngModelController) { + $timeout(function () { + var target = element[0].firstChild, + width = target.offsetWidth, + height = target.offsetHeight, + padding = 1; + + var vis = d3.select(target) + .append('svg:svg') + //.attr('viewBox', '0 0 ' + width + ' ' + + // height) + .attr('width', '100%') + .attr('height', height); + //.attr('preserveAspectRatio', 'xMidYMid meet'); + + // define the x scale (horizontal) + var mindate = new Date(2012,0,1), + maxdate = new Date(2016,0,1); + + var xScale = d3.scaleTime() + .domain([mindate, maxdate]) + .range([padding, width - padding * 2]); + + var xAxis = d3.axisTop() + .scale(xScale); + + // draw x axis with labels and move to the bottom of the chart area + var axisElement = vis.append("g") + .attr("class", "xaxis") // give it a class so it can be used to select only xaxis labels below + .attr("transform", "translate(0," + (height - padding) + ")"); + + axisElement.call(xAxis); + }, 1000); + } + + return { + // Only show at the element level + restrict: "E", + + template: "
", + + // ngOptions is terminal, so we need to be higher priority + priority: 1000, + + // Link function + link: link, + + // Pass through Angular's normal input field attributes + scope: { + // Used to choose which form control to use + start: "=", + end: "=" + } + }; + } + + return MCTConductorAxis; + } +); diff --git a/platform/features/conductor-v2/src/TimeConductor.js b/platform/features/conductor-v2/src/TimeConductor.js new file mode 100644 index 0000000000..dd4a29d8ab --- /dev/null +++ b/platform/features/conductor-v2/src/TimeConductor.js @@ -0,0 +1,183 @@ +/***************************************************************************** + * Open MCT Web, Copyright (c) 2014-2015, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT Web is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT Web includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ + +define(['EventEmitter'], function (EventEmitter) { + + /** + * The public API for setting and querying time conductor state. The + * time conductor is the means by which the temporal bounds of a view + * are controlled. Time-sensitive views will typically respond to + * changes to bounds or other properties of the time conductor and + * update the data displayed based on the time conductor state. + * + * The TimeConductor extends the EventEmitter class. A number of events are + * fired when properties of the time conductor change, which are + * documented below. + * @constructor + */ + function TimeConductor() { + EventEmitter.call(this); + + //The Time System + this.system = undefined; + //The Time Of Interest + this.toi = undefined; + + this.boundsVal = { + start: undefined, + end: undefined + }; + + //Default to fixed mode + this.followMode = false; + } + + TimeConductor.prototype = Object.create(EventEmitter.prototype); + + /** + * Validate the given bounds. This can be used for pre-validation of + * bounds, for example by views validating user inputs. + * @param bounds The start and end time of the conductor. + * @returns {string | true} A validation error, or true if valid + */ + TimeConductor.prototype.validateBounds = function (bounds) { + if ((bounds.start === undefined) || + (bounds.end === undefined) || + isNaN(bounds.start) || + isNaN(bounds.end) + ) { + return "Start and end must be specified as integer values"; + } else if (bounds.start > bounds.end) { + return "Specified start date exceeds end bound"; + } + return true; + }; + + function throwOnError(validationResult) { + if (validationResult !== true) { + throw new Error(validationResult); + } + } + + /** + * Get or set the follow mode of the time conductor. In follow mode the + * time conductor ticks, regularly updating the bounds from a timing + * source appropriate to the selected time system and mode of the time + * conductor. + * @fires TimeConductor#follow + * @param {boolean} followMode + * @returns {boolean} + */ + TimeConductor.prototype.follow = function (followMode) { + if (arguments.length > 0) { + this.followMode = followMode; + /** + * @event TimeConductor#follow The TimeConductor has toggled + * into or out of follow mode. + * @property {boolean} followMode true if follow mode is + * enabled, otherwise false. + */ + this.emit('follow', this.followMode); + } + return this.followMode; + }; + + /** + * @typedef {Object} TimeConductorBounds + * @property {number} start The start time displayed by the time conductor in ms since epoch. Epoch determined by current time system + * @property {number} end The end time displayed by the time conductor in ms since epoch. + */ + /** + * Get or set the start and end time of the time conductor. Basic validation + * of bounds is performed. + * + * @param {TimeConductorBounds} newBounds + * @throws {Error} Validation error + * @fires TimeConductor#bounds + * @returns {TimeConductorBounds} + */ + TimeConductor.prototype.bounds = function (newBounds) { + if (arguments.length > 0) { + throwOnError(this.validateBounds(newBounds)); + this.boundsVal = newBounds; + /** + * @event TimeConductor#bounds The start time, end time, or + * both have been updated + * @property {TimeConductorBounds} bounds + */ + this.emit('bounds', this.boundsVal); + } + return this.boundsVal; + }; + + /** + * Get or set the time system of the TimeConductor. Time systems determine + * units, epoch, and other aspects of time representation. When changing + * the time system in use, new valid bounds must also be provided. + * @param {TimeSystem} newTimeSystem + * @param {TimeConductorBounds} bounds + * @fires TimeConductor#timeSystem + * @returns {TimeSystem} The currently applied time system + */ + TimeConductor.prototype.timeSystem = function (newTimeSystem, bounds) { + if (arguments.length >= 2) { + this.system = newTimeSystem; + /** + * @event TimeConductor#timeSystem The time system used by the time + * conductor has changed. A change in Time System will always be + * followed by a bounds event specifying new query bounds + * @property {TimeSystem} The value of the currently applied + * Time System + * */ + this.emit('timeSystem', this.system); + // Do something with bounds here. Try and convert between + // time systems? Or just set defaults when time system changes? + // eg. + this.bounds(bounds); + } else if (arguments.length === 1) { + throw new Error('Must set bounds when changing time system'); + } + return this.system; + }; + + /** + * Get or set the Time of Interest. The Time of Interest is the temporal + * focus of the current view. It can be manipulated by the user from the + * time conductor or from other views. + * @fires TimeConductor#timeOfInterest + * @param newTOI + * @returns {number} the current time of interest + */ + TimeConductor.prototype.timeOfInterest = function (newTOI) { + if (arguments.length > 0) { + this.toi = newTOI; + /** + * @event TimeConductor#timeOfInterest The Time of Interest has moved. + * @property {number} Current time of interest + */ + this.emit('timeOfInterest', this.toi); + } + return this.toi; + }; + + return TimeConductor; +}); diff --git a/platform/features/conductor-v2/src/TimeConductorController.js b/platform/features/conductor-v2/src/TimeConductorController.js new file mode 100644 index 0000000000..6b714d2ba2 --- /dev/null +++ b/platform/features/conductor-v2/src/TimeConductorController.js @@ -0,0 +1,56 @@ +/***************************************************************************** + * 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 () { + + function TimeConductorController($scope, conductor) { + this.$scope = $scope; + this.conductor = conductor; + + /* + Stuff to put on scope + - parameters + - formModel + */ + this.$scope.formModel = { + start: '2012-01-01 00:00:00.000Z', + end: '2016-01-01 00:00:00.000Z' + }; + } + + TimeConductorController.prototype.validateStart = function () { + return true; + }; + + TimeConductorController.prototype.validateEnd = function () { + return true; + }; + + TimeConductorController.prototype.updateBoundsFromForm = function () { + + }; + + return TimeConductorController; + } +); diff --git a/platform/features/conductor-v2/src/TimeConductorSpec.js b/platform/features/conductor-v2/src/TimeConductorSpec.js new file mode 100644 index 0000000000..745b61cd22 --- /dev/null +++ b/platform/features/conductor-v2/src/TimeConductorSpec.js @@ -0,0 +1,110 @@ +/***************************************************************************** + * 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(['./TimeConductor'], function (TimeConductor) { + describe("The Time Conductor", function () { + var tc, + timeSystem, + bounds, + eventListener, + toi, + follow; + + beforeEach(function () { + tc = new TimeConductor(); + timeSystem = {}; + bounds = {start: 0, end: 0}; + eventListener = jasmine.createSpy("eventListener"); + toi = 111; + follow = true; + }); + + it("Supports setting and querying of time of interest and and follow mode", function () { + expect(tc.timeOfInterest()).not.toBe(toi); + tc.timeOfInterest(toi); + expect(tc.timeOfInterest()).toBe(toi); + + expect(tc.follow()).not.toBe(follow); + tc.follow(follow); + expect(tc.follow()).toBe(follow); + }); + + it("Allows setting of valid bounds", function () { + bounds = {start: 0, end: 1}; + expect(tc.bounds()).not.toBe(bounds); + expect(tc.bounds.bind(tc, bounds)).not.toThrow(); + expect(tc.bounds()).toBe(bounds); + }); + + it("Disallows setting of invalid bounds", function () { + bounds = {start: 1, end: 0}; + expect(tc.bounds()).not.toBe(bounds); + expect(tc.bounds.bind(tc, bounds)).toThrow(); + expect(tc.bounds()).not.toBe(bounds); + + bounds = {start: 1}; + expect(tc.bounds()).not.toBe(bounds); + expect(tc.bounds.bind(tc, bounds)).toThrow(); + expect(tc.bounds()).not.toBe(bounds); + }); + + it("Allows setting of time system with bounds", function () { + expect(tc.timeSystem()).not.toBe(timeSystem); + expect(tc.timeSystem.bind(tc, timeSystem, bounds)).not.toThrow(); + expect(tc.timeSystem()).toBe(timeSystem); + }); + + it("Disallows setting of time system without bounds", function () { + expect(tc.timeSystem()).not.toBe(timeSystem); + expect(tc.timeSystem.bind(tc, timeSystem)).toThrow(); + expect(tc.timeSystem()).not.toBe(timeSystem); + }); + + it("Emits an event when time system changes", function () { + expect(eventListener).not.toHaveBeenCalled(); + tc.on("timeSystem", eventListener); + tc.timeSystem(timeSystem, bounds); + expect(eventListener).toHaveBeenCalledWith(timeSystem); + }); + + it("Emits an event when time of interest changes", function () { + expect(eventListener).not.toHaveBeenCalled(); + tc.on("timeOfInterest", eventListener); + tc.timeOfInterest(toi); + expect(eventListener).toHaveBeenCalledWith(toi); + }); + + it("Emits an event when bounds change", function () { + expect(eventListener).not.toHaveBeenCalled(); + tc.on("bounds", eventListener); + tc.bounds(bounds); + expect(eventListener).toHaveBeenCalledWith(bounds); + }); + + it("Emits an event when follow mode changes", function () { + expect(eventListener).not.toHaveBeenCalled(); + tc.on("follow", eventListener); + tc.follow(follow); + expect(eventListener).toHaveBeenCalledWith(follow); + }); + }); +}); diff --git a/test-main.js b/test-main.js index 90da6dabb9..103b7bbac2 100644 --- a/test-main.js +++ b/test-main.js @@ -48,13 +48,14 @@ requirejs.config({ "angular-route": "bower_components/angular-route/angular-route.min", "csv": "bower_components/comma-separated-values/csv.min", "es6-promise": "bower_components/es6-promise/promise.min", + "EventEmitter": "bower_components/eventemitter3/index", "moment": "bower_components/moment/moment", "moment-duration-format": "bower_components/moment-duration-format/lib/moment-duration-format", "saveAs": "bower_components/FileSaver.js/FileSaver.min", "screenfull": "bower_components/screenfull/dist/screenfull.min", "text": "bower_components/text/text", "uuid": "bower_components/node-uuid/uuid", - "zepto": "bower_components/zepto/zepto.min" + "zepto": "bower_components/zepto/zepto.min", }, "shim": { @@ -64,6 +65,9 @@ requirejs.config({ "angular-route": { "deps": [ "angular" ] }, + "EventEmitter": { + "exports": "EventEmitter" + }, "moment-duration-format": { "deps": [ "moment" ] },