diff --git a/bower.json b/bower.json
index ed3dbaf899..161ee04186 100644
--- a/bower.json
+++ b/bower.json
@@ -22,6 +22,7 @@
"eventemitter3": "^1.2.0",
"lodash": "3.10.1",
"almond": "~0.3.2",
+ "d3": "~4.1.0",
"html2canvas": "^0.4.1"
}
}
diff --git a/example/localTimeSystem/bundle.js b/example/localTimeSystem/bundle.js
new file mode 100644
index 0000000000..1d1ff2cf39
--- /dev/null
+++ b/example/localTimeSystem/bundle.js
@@ -0,0 +1,48 @@
+/*****************************************************************************
+ * Open MCT Web, Copyright (c) 2014-2015, United States Government
+ * as represented by the Administrator of the National Aeronautics and Space
+ * Administration. All rights reserved.
+ *
+ * Open MCT Web is licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * Open MCT Web includes source code licensed under additional open source
+ * licenses. See the Open Source Licenses file (LICENSES.md) included with
+ * this source code distribution or the Licensing information page available
+ * at runtime from the About dialog for additional information.
+ *****************************************************************************/
+
+define([
+ "./src/LocalTimeSystem",
+ "./src/LocalTimeFormat",
+ 'legacyRegistry'
+], function (
+ LocalTimeSystem,
+ LocalTimeFormat,
+ legacyRegistry
+) {
+ legacyRegistry.register("example/localTimeSystem", {
+ "extensions": {
+ "formats": [
+ {
+ "key": "local-format",
+ "implementation": LocalTimeFormat
+ }
+ ],
+ "timeSystems": [
+ {
+ "implementation": LocalTimeSystem,
+ "depends": ["$timeout"]
+ }
+ ]
+ }
+ });
+});
diff --git a/example/localTimeSystem/src/LADTickSource.js b/example/localTimeSystem/src/LADTickSource.js
new file mode 100644
index 0000000000..35fff32f54
--- /dev/null
+++ b/example/localTimeSystem/src/LADTickSource.js
@@ -0,0 +1,43 @@
+/*****************************************************************************
+ * Open MCT Web, Copyright (c) 2014-2015, United States Government
+ * as represented by the Administrator of the National Aeronautics and Space
+ * Administration. All rights reserved.
+ *
+ * Open MCT Web is licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * Open MCT Web includes source code licensed under additional open source
+ * licenses. See the Open Source Licenses file (LICENSES.md) included with
+ * this source code distribution or the Licensing information page available
+ * at runtime from the About dialog for additional information.
+ *****************************************************************************/
+
+define(['../../../platform/features/conductor-v2/conductor/src/timeSystems/LocalClock'], function (LocalClock) {
+ /**
+ * @implements TickSource
+ * @constructor
+ */
+ function LADTickSource ($timeout, period) {
+ LocalClock.call(this, $timeout, period);
+
+ this.metadata = {
+ key: 'test-lad',
+ mode: 'lad',
+ cssclass: 'icon-clock',
+ label: 'Latest Available Data',
+ name: 'Latest available data',
+ description: 'Monitor real-time streaming data as it comes in. The Time Conductor and displays will automatically advance themselves based on a UTC clock.'
+ };
+ }
+ LADTickSource.prototype = Object.create(LocalClock.prototype);
+
+ return LADTickSource;
+});
diff --git a/example/localTimeSystem/src/LocalTimeFormat.js b/example/localTimeSystem/src/LocalTimeFormat.js
new file mode 100644
index 0000000000..a9b26ed4ef
--- /dev/null
+++ b/example/localTimeSystem/src/LocalTimeFormat.js
@@ -0,0 +1,112 @@
+/*****************************************************************************
+ * Open MCT Web, Copyright (c) 2014-2015, United States Government
+ * as represented by the Administrator of the National Aeronautics and Space
+ * Administration. All rights reserved.
+ *
+ * Open MCT Web is licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * Open MCT Web includes source code licensed under additional open source
+ * licenses. See the Open Source Licenses file (LICENSES.md) included with
+ * this source code distribution or the Licensing information page available
+ * at runtime from the About dialog for additional information.
+ *****************************************************************************/
+
+define([
+ 'moment'
+], function (
+ moment
+) {
+
+ var DATE_FORMAT = "YYYY-MM-DD h:mm:ss.SSS a",
+ DATE_FORMATS = [
+ DATE_FORMAT,
+ "YYYY-MM-DD h:mm:ss a",
+ "YYYY-MM-DD h:mm a",
+ "YYYY-MM-DD"
+ ];
+
+ /**
+ * @typedef Scale
+ * @property {number} min the minimum scale value, in ms
+ * @property {number} max the maximum scale value, in ms
+ */
+
+ /**
+ * Formatter for UTC timestamps. Interprets numeric values as
+ * milliseconds since the start of 1970.
+ *
+ * @implements {Format}
+ * @constructor
+ * @memberof platform/commonUI/formats
+ */
+ function LocalTimeFormat() {
+ }
+
+ /**
+ * Returns an appropriate time format based on the provided value and
+ * the threshold required.
+ * @private
+ */
+ function getScaledFormat (d) {
+ var m = moment.utc(d);
+ /**
+ * Uses logic from d3 Time-Scales, v3 of the API. See
+ * https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Scales.md
+ *
+ * Licensed
+ */
+ return [
+ [".SSS", function(m) { return m.milliseconds(); }],
+ [":ss", function(m) { return m.seconds(); }],
+ ["hh:mma", function(m) { return m.minutes(); }],
+ ["hha", function(m) { return m.hours(); }],
+ ["ddd DD", function(m) {
+ return m.days() &&
+ m.date() != 1;
+ }],
+ ["MMM DD", function(m) { return m.date() != 1; }],
+ ["MMMM", function(m) {
+ return m.month();
+ }],
+ ["YYYY", function() { return true; }]
+ ].filter(function (row){
+ return row[1](m);
+ })[0][0];
+ };
+
+ /**
+ *
+ * @param value
+ * @param {Scale} [scale] Optionally provides context to the
+ * format request, allowing for scale-appropriate formatting.
+ * @returns {string} the formatted date
+ */
+ LocalTimeFormat.prototype.format = function (value, scale) {
+ if (scale !== undefined){
+ var scaledFormat = getScaledFormat(value, scale);
+ if (scaledFormat) {
+ return moment.utc(value).format(scaledFormat);
+ }
+ }
+ return moment(value).format(DATE_FORMAT);
+ };
+
+ LocalTimeFormat.prototype.parse = function (text) {
+ return moment(text, DATE_FORMATS).valueOf();
+ };
+
+ LocalTimeFormat.prototype.validate = function (text) {
+ return moment(text, DATE_FORMATS).isValid();
+ };
+
+ return LocalTimeFormat;
+});
diff --git a/example/localTimeSystem/src/LocalTimeSystem.js b/example/localTimeSystem/src/LocalTimeSystem.js
new file mode 100644
index 0000000000..95485b7e69
--- /dev/null
+++ b/example/localTimeSystem/src/LocalTimeSystem.js
@@ -0,0 +1,79 @@
+/*****************************************************************************
+ * Open MCT Web, Copyright (c) 2014-2015, United States Government
+ * as represented by the Administrator of the National Aeronautics and Space
+ * Administration. All rights reserved.
+ *
+ * Open MCT Web is licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * Open MCT Web includes source code licensed under additional open source
+ * licenses. See the Open Source Licenses file (LICENSES.md) included with
+ * this source code distribution or the Licensing information page available
+ * at runtime from the About dialog for additional information.
+ *****************************************************************************/
+
+define([
+ '../../../platform/features/conductor-v2/conductor/src/timeSystems/TimeSystem',
+ '../../../platform/features/conductor-v2/conductor/src/timeSystems/LocalClock',
+ './LADTickSource'
+], function (TimeSystem, LocalClock, LADTickSource) {
+ var THIRTY_MINUTES = 30 * 60 * 1000,
+ DEFAULT_PERIOD = 1000;
+
+ /**
+ * This time system supports UTC dates and provides a ticking clock source.
+ * @implements TimeSystem
+ * @constructor
+ */
+ function LocalTimeSystem ($timeout) {
+ TimeSystem.call(this);
+
+ /**
+ * Some metadata, which will be used to identify the time system in
+ * the UI
+ * @type {{key: string, name: string, glyph: string}}
+ */
+ this.metadata = {
+ 'key': 'local',
+ 'name': 'Local',
+ 'glyph': '\u0043'
+ };
+
+ this.fmts = ['local-format'];
+ this.sources = [new LocalClock($timeout, DEFAULT_PERIOD), new LADTickSource($timeout, DEFAULT_PERIOD)];
+ }
+
+ LocalTimeSystem.prototype = Object.create(TimeSystem.prototype);
+
+ LocalTimeSystem.prototype.formats = function () {
+ return this.fmts;
+ };
+
+ LocalTimeSystem.prototype.deltaFormat = function () {
+ return 'duration';
+ };
+
+ LocalTimeSystem.prototype.tickSources = function () {
+ return this.sources;
+ };
+
+ LocalTimeSystem.prototype.defaults = function (key) {
+ var now = Math.ceil(Date.now() / 1000) * 1000;
+ return {
+ key: 'local-default',
+ name: 'Local 12 hour time system defaults',
+ deltas: {start: THIRTY_MINUTES, end: 0},
+ bounds: {start: now - THIRTY_MINUTES, end: now}
+ };
+ };
+
+ return LocalTimeSystem;
+});
diff --git a/openmct.js b/openmct.js
index 864bf243cd..f1bb593860 100644
--- a/openmct.js
+++ b/openmct.js
@@ -37,7 +37,8 @@ requirejs.config({
"text": "bower_components/text/text",
"uuid": "bower_components/node-uuid/uuid",
"zepto": "bower_components/zepto/zepto.min",
- "lodash": "bower_components/lodash/lodash"
+ "lodash": "bower_components/lodash/lodash",
+ "d3": "bower_components/d3/d3.min"
},
"shim": {
"angular": {
@@ -52,6 +53,9 @@ requirejs.config({
"html2canvas": {
"exports": "html2canvas"
},
+ "EventEmitter": {
+ "exports": "EventEmitter"
+ },
"moment-duration-format": {
"deps": ["moment"]
},
@@ -63,6 +67,9 @@ requirejs.config({
},
"lodash": {
"exports": "lodash"
+ },
+ "d3": {
+ "exports": "d3"
}
}
});
diff --git a/platform/commonUI/browse/res/templates/browse-object.html b/platform/commonUI/browse/res/templates/browse-object.html
index eb32a15648..f431003e47 100644
--- a/platform/commonUI/browse/res/templates/browse-object.html
+++ b/platform/commonUI/browse/res/templates/browse-object.html
@@ -43,7 +43,7 @@
-
+
\ No newline at end of file
diff --git a/platform/features/conductor-v2/conductor/res/templates/time-conductor.html b/platform/features/conductor-v2/conductor/res/templates/time-conductor.html
new file mode 100644
index 0000000000..19c86b2b74
--- /dev/null
+++ b/platform/features/conductor-v2/conductor/res/templates/time-conductor.html
@@ -0,0 +1,108 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/platform/features/conductor-v2/conductor/src/TimeConductor.js b/platform/features/conductor-v2/conductor/src/TimeConductor.js
new file mode 100644
index 0000000000..2c2194b5a0
--- /dev/null
+++ b/platform/features/conductor-v2/conductor/src/TimeConductor.js
@@ -0,0 +1,179 @@
+/*****************************************************************************
+ * 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;
+ };
+
+ /**
+ * 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) {
+ var validationResult = this.validateBounds(newBounds);
+ if (validationResult !== true) {
+ throw new Error(validationResult);
+ }
+ //Create a copy to avoid direct mutation of conductor bounds
+ this.boundsVal = JSON.parse(JSON.stringify(newBounds));
+ /**
+ * @event TimeConductor#bounds The start time, end time, or
+ * both have been updated
+ * @property {TimeConductorBounds} bounds
+ */
+ this.emit('bounds', this.boundsVal);
+ }
+ //Return a copy to prevent direct mutation of time conductor bounds.
+ return JSON.parse(JSON.stringify(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);
+ 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/conductor/src/TimeConductorSpec.js b/platform/features/conductor-v2/conductor/src/TimeConductorSpec.js
new file mode 100644
index 0000000000..7701f5e9b4
--- /dev/null
+++ b/platform/features/conductor-v2/conductor/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.toEqual(bounds);
+ expect(tc.bounds.bind(tc, bounds)).not.toThrow();
+ expect(tc.bounds()).toEqual(bounds);
+ });
+
+ it("Disallows setting of invalid bounds", function () {
+ bounds = {start: 1, end: 0};
+ expect(tc.bounds()).not.toEqual(bounds);
+ expect(tc.bounds.bind(tc, bounds)).toThrow();
+ expect(tc.bounds()).not.toEqual(bounds);
+
+ bounds = {start: 1};
+ expect(tc.bounds()).not.toEqual(bounds);
+ expect(tc.bounds.bind(tc, bounds)).toThrow();
+ expect(tc.bounds()).not.toEqual(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/platform/features/conductor-v2/conductor/src/timeSystems/LocalClock.js b/platform/features/conductor-v2/conductor/src/timeSystems/LocalClock.js
new file mode 100644
index 0000000000..09ddd35612
--- /dev/null
+++ b/platform/features/conductor-v2/conductor/src/timeSystems/LocalClock.js
@@ -0,0 +1,89 @@
+/*****************************************************************************
+ * 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;
+});
diff --git a/platform/features/conductor-v2/conductor/src/timeSystems/LocalClockSpec.js b/platform/features/conductor-v2/conductor/src/timeSystems/LocalClockSpec.js
new file mode 100644
index 0000000000..b34b625f43
--- /dev/null
+++ b/platform/features/conductor-v2/conductor/src/timeSystems/LocalClockSpec.js
@@ -0,0 +1,50 @@
+/*****************************************************************************
+ * 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);
+ });
+ });
+});
diff --git a/platform/features/conductor-v2/conductor/src/timeSystems/TickSource.js b/platform/features/conductor-v2/conductor/src/timeSystems/TickSource.js
new file mode 100644
index 0000000000..8cb9fd308d
--- /dev/null
+++ b/platform/features/conductor-v2/conductor/src/timeSystems/TickSource.js
@@ -0,0 +1,47 @@
+/*****************************************************************************
+ * 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;
+});
diff --git a/platform/features/conductor-v2/conductor/src/timeSystems/TimeSystem.js b/platform/features/conductor-v2/conductor/src/timeSystems/TimeSystem.js
new file mode 100644
index 0000000000..652ea9ed0f
--- /dev/null
+++ b/platform/features/conductor-v2/conductor/src/timeSystems/TimeSystem.js
@@ -0,0 +1,93 @@
+/*****************************************************************************
+ * 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');
+ };
+
+ /**
+ *
+ * @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;
+});
diff --git a/platform/features/conductor-v2/conductor/src/ui/MctConductorAxis.js b/platform/features/conductor-v2/conductor/src/ui/MctConductorAxis.js
new file mode 100644
index 0000000000..58cb60befc
--- /dev/null
+++ b/platform/features/conductor-v2/conductor/src/ui/MctConductorAxis.js
@@ -0,0 +1,146 @@
+/*****************************************************************************
+ * 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(
+ [
+ "d3"
+ ],
+ function (d3) {
+ var PADDING = 1;
+
+ /**
+ * The mct-conductor-axis renders a horizontal axis with regular
+ * labelled 'ticks'. It requires 'start' and 'end' integer values to
+ * be specified as attributes.
+ */
+ function MCTConductorAxis(conductor, formatService) {
+ // Dependencies
+ this.d3 = d3;
+ this.conductor = conductor;
+ this.formatService = formatService;
+
+ // Runtime properties (set by 'link' function)
+ this.target = undefined;
+ this.xScale = undefined;
+ this.xAxis = undefined;
+ this.axisElement = undefined;
+
+ // Angular Directive interface
+ this.link = this.link.bind(this);
+ this.restrict = "E";
+ this.template =
+ "";
+ this.priority = 1000;
+
+ //Bind all class functions to 'this'
+ Object.keys(MCTConductorAxis.prototype).filter(function (key) {
+ return typeof MCTConductorAxis.prototype[key] === 'function';
+ }).forEach(function (key) {
+ this[key] = this[key].bind(this);
+ }.bind(this));
+ }
+
+ MCTConductorAxis.prototype.setScale = function () {
+ var width = this.target.offsetWidth;
+ var timeSystem = this.conductor.timeSystem();
+ var bounds = this.conductor.bounds();
+
+ if (timeSystem.isUTCBased()) {
+ this.xScale = this.xScale || this.d3.scaleUtc();
+ this.xScale.domain([new Date(bounds.start), new Date(bounds.end)]);
+ } else {
+ this.xScale = this.xScale || this.d3.scaleLinear();
+ this.xScale.domain([bounds.start, bounds.end]);
+ }
+
+ this.xScale.range([PADDING, width - PADDING * 2]);
+ this.axisElement.call(this.xAxis);
+ };
+
+ MCTConductorAxis.prototype.changeTimeSystem = function (timeSystem) {
+ var key = timeSystem.formats()[0];
+ if (key !== undefined) {
+ var format = this.formatService.getFormat(key);
+ var bounds = this.conductor.bounds();
+
+ if (timeSystem.isUTCBased()) {
+ this.xScale = this.d3.scaleUtc();
+ } else {
+ this.xScale = this.d3.scaleLinear();
+ }
+
+ this.xAxis.scale(this.xScale);
+ //Define a custom format function
+ this.xAxis.tickFormat(function (tickValue) {
+ // Normalize date representations to numbers
+ if (tickValue instanceof Date) {
+ tickValue = tickValue.getTime();
+ }
+ return format.format(tickValue, {
+ min: bounds.start,
+ max: bounds.end
+ });
+ });
+ this.axisElement.call(this.xAxis);
+ }
+ };
+
+ MCTConductorAxis.prototype.destroy = function () {
+ this.conductor.off('timeSystem', this.changeTimeSystem);
+ this.conductor.off('bounds', this.setScale);
+ };
+
+ MCTConductorAxis.prototype.link = function (scope, element) {
+ var conductor = this.conductor;
+ this.target = element[0].firstChild;
+ var height = this.target.offsetHeight;
+ var vis = this.d3.select(this.target)
+ .append('svg:svg')
+ .attr('width', '100%')
+ .attr('height', height);
+
+ this.xAxis = this.d3.axisTop();
+
+ // draw x axis with labels and move to the bottom of the chart area
+ this.axisElement = vis.append("g")
+ .attr("transform", "translate(0," + (height - PADDING) + ")");
+
+ scope.resize = this.setScale;
+
+ conductor.on('timeSystem', this.changeTimeSystem);
+
+ //On conductor bounds changes, redraw ticks
+ conductor.on('bounds', this.setScale);
+
+ scope.$on("$destroy", this.destroy);
+
+ if (conductor.timeSystem() !== undefined) {
+ this.changeTimeSystem(conductor.timeSystem());
+ this.setScale();
+ }
+ };
+
+ return function (conductor, formatService) {
+ return new MCTConductorAxis(conductor, formatService);
+ };
+ }
+);
diff --git a/platform/features/conductor-v2/conductor/src/ui/MctConductorAxisSpec.js b/platform/features/conductor-v2/conductor/src/ui/MctConductorAxisSpec.js
new file mode 100644
index 0000000000..0fe3c4cf3f
--- /dev/null
+++ b/platform/features/conductor-v2/conductor/src/ui/MctConductorAxisSpec.js
@@ -0,0 +1,146 @@
+/*****************************************************************************
+ * 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(['./MctConductorAxis'], function (MctConductorAxis) {
+ describe("The MctConductorAxis directive", function () {
+ var directive,
+ mockConductor,
+ mockFormatService,
+ mockScope,
+ mockElement,
+ mockTarget,
+ mockBounds,
+ d3;
+
+ beforeEach(function () {
+ mockScope = jasmine.createSpyObj("scope", [
+ "$on"
+ ]);
+
+ //Add some HTML elements
+ mockTarget = {
+ offsetWidth: 0,
+ offsetHeight: 0
+ };
+ mockElement = {
+ firstChild: mockTarget
+ };
+ mockBounds = {
+ start: 100,
+ end: 200
+ };
+ mockConductor = jasmine.createSpyObj("conductor", [
+ "timeSystem",
+ "bounds",
+ "on",
+ "off"
+ ]);
+ mockConductor.bounds.andReturn(mockBounds);
+
+ mockFormatService = jasmine.createSpyObj("formatService", [
+ "getFormat"
+ ]);
+
+ var d3Functions = [
+ "scale",
+ "scaleUtc",
+ "scaleLinear",
+ "select",
+ "append",
+ "attr",
+ "axisTop",
+ "call",
+ "tickFormat",
+ "domain",
+ "range"
+ ];
+ d3 = jasmine.createSpyObj("d3", d3Functions);
+ d3Functions.forEach(function (func) {
+ d3[func].andReturn(d3);
+ });
+
+ directive = new MctConductorAxis(mockConductor, mockFormatService);
+ directive.d3 = d3;
+ directive.link(mockScope, [mockElement]);
+ });
+
+ it("listens for changes to time system and bounds", function () {
+ expect(mockConductor.on).toHaveBeenCalledWith("timeSystem", directive.changeTimeSystem);
+ expect(mockConductor.on).toHaveBeenCalledWith("bounds", directive.setScale);
+ });
+
+ it("on scope destruction, deregisters listeners", function () {
+ expect(mockScope.$on).toHaveBeenCalledWith("$destroy", directive.destroy);
+ directive.destroy();
+ expect(mockConductor.off).toHaveBeenCalledWith("timeSystem", directive.changeTimeSystem);
+ expect(mockConductor.off).toHaveBeenCalledWith("bounds", directive.setScale);
+ });
+
+ describe("when the time system changes", function () {
+ var mockTimeSystem;
+ var mockFormat;
+
+ beforeEach(function () {
+ mockTimeSystem = jasmine.createSpyObj("timeSystem", [
+ "formats",
+ "isUTCBased"
+ ]);
+ mockFormat = jasmine.createSpyObj("format", [
+ "format"
+ ]);
+
+ mockTimeSystem.formats.andReturn(["mockFormat"]);
+ mockFormatService.getFormat.andReturn(mockFormat);
+ });
+
+ it("uses a UTC scale for UTC time systems", function () {
+ mockTimeSystem.isUTCBased.andReturn(true);
+ directive.changeTimeSystem(mockTimeSystem);
+ expect(d3.scaleUtc).toHaveBeenCalled();
+ expect(d3.scaleLinear).not.toHaveBeenCalled();
+ });
+
+ it("uses a linear scale for non-UTC time systems", function () {
+ mockTimeSystem.isUTCBased.andReturn(false);
+ directive.changeTimeSystem(mockTimeSystem);
+ expect(d3.scaleLinear).toHaveBeenCalled();
+ expect(d3.scaleUtc).not.toHaveBeenCalled();
+ });
+
+ it("sets axis domain to time conductor bounds", function () {
+ mockTimeSystem.isUTCBased.andReturn(false);
+ mockConductor.timeSystem.andReturn(mockTimeSystem);
+
+ directive.setScale();
+ expect(d3.domain).toHaveBeenCalledWith([mockBounds.start, mockBounds.end]);
+ });
+
+ it("uses the format specified by the time system to format tick" +
+ " labels", function () {
+ directive.changeTimeSystem(mockTimeSystem);
+ expect(d3.tickFormat).toHaveBeenCalled();
+ d3.tickFormat.mostRecentCall.args[0]();
+ expect(mockFormat.format).toHaveBeenCalled();
+ });
+ });
+ });
+});
diff --git a/platform/features/conductor-v2/conductor/src/ui/NumberFormat.js b/platform/features/conductor-v2/conductor/src/ui/NumberFormat.js
new file mode 100644
index 0000000000..ac3f40cb7b
--- /dev/null
+++ b/platform/features/conductor-v2/conductor/src/ui/NumberFormat.js
@@ -0,0 +1,53 @@
+/*****************************************************************************
+ * Open MCT, Copyright (c) 2014-2016, United States Government
+ * as represented by the Administrator of the National Aeronautics and Space
+ * Administration. All rights reserved.
+ *
+ * Open MCT is licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * Open MCT includes source code licensed under additional open source
+ * licenses. See the Open Source Licenses file (LICENSES.md) included with
+ * this source code distribution or the Licensing information page available
+ * at runtime from the About dialog for additional information.
+ *****************************************************************************/
+
+define([], function () {
+
+ /**
+ * Formatter for basic numbers. Provides basic support for non-UTC
+ * numbering systems
+ *
+ * @implements {Format}
+ * @constructor
+ * @memberof platform/commonUI/formats
+ */
+ function NumberFormat() {
+ }
+
+ NumberFormat.prototype.format = function (value) {
+ if (isNaN(value)) {
+ return '';
+ } else {
+ return '' + value;
+ }
+ };
+
+ NumberFormat.prototype.parse = function (text) {
+ return parseFloat(text);
+ };
+
+ NumberFormat.prototype.validate = function (text) {
+ return !isNaN(text);
+ };
+
+ return NumberFormat;
+});
diff --git a/platform/features/conductor-v2/conductor/src/ui/NumberFormatSpec.js b/platform/features/conductor-v2/conductor/src/ui/NumberFormatSpec.js
new file mode 100644
index 0000000000..f56c6e67d3
--- /dev/null
+++ b/platform/features/conductor-v2/conductor/src/ui/NumberFormatSpec.js
@@ -0,0 +1,49 @@
+/*****************************************************************************
+ * 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(['./NumberFormat'], function (NumberFormat) {
+ describe("The NumberFormat class", function () {
+ var format;
+ beforeEach(function () {
+ format = new NumberFormat();
+ });
+
+ it("The format function takes a string and produces a number", function () {
+ var text = format.format(1);
+ expect(text).toBe("1");
+ expect(typeof text).toBe("string");
+ });
+
+ it("The parse function takes a string and produces a number", function () {
+ var number = format.parse("1");
+ expect(number).toBe(1);
+ expect(typeof number).toBe("number");
+ });
+
+ it("validates that the input is a number", function () {
+ expect(format.validate("1")).toBe(true);
+ expect(format.validate(1)).toBe(true);
+ expect(format.validate("1.1")).toBe(true);
+ expect(format.validate("abc")).toBe(false);
+ });
+ });
+});
diff --git a/platform/features/conductor-v2/conductor/src/ui/TimeConductorController.js b/platform/features/conductor-v2/conductor/src/ui/TimeConductorController.js
new file mode 100644
index 0000000000..83bc0558c6
--- /dev/null
+++ b/platform/features/conductor-v2/conductor/src/ui/TimeConductorController.js
@@ -0,0 +1,243 @@
+/*****************************************************************************
+ * 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(
+ [
+ './TimeConductorValidation'
+ ],
+ function (TimeConductorValidation) {
+
+ function TimeConductorController($scope, $window, timeConductor, conductorViewService, timeSystems) {
+
+ 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);
+ });
+
+ this.$scope = $scope;
+ this.$window = $window;
+ this.conductorViewService = conductorViewService;
+ this.conductor = timeConductor;
+ this.modes = conductorViewService.availableModes();
+ this.validation = new TimeConductorValidation(this.conductor);
+
+ // Construct the provided time system definitions
+ this.timeSystems = timeSystems.map(function (timeSystemConstructor) {
+ return timeSystemConstructor();
+ });
+
+ //Set the initial state of the view based on current time conductor
+ this.initializeScope();
+
+ this.conductor.on('bounds', this.setFormFromBounds);
+ this.conductor.on('timeSystem', this.changeTimeSystem);
+
+ // If no mode selected, select fixed as the default
+ if (!this.conductorViewService.mode()) {
+ this.setMode('fixed');
+ }
+ }
+
+ /**
+ * @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.$scope.timeSystemModel = {};
+ if (this.conductor.timeSystem()) {
+ this.setFormFromTimeSystem(this.conductor.timeSystem());
+ }
+
+ //Represents the various modes, and the currently selected mode
+ //in the view
+ this.$scope.modeModel = {
+ options: this.conductorViewService.availableModes()
+ };
+
+ var mode = this.conductorViewService.mode();
+ if (mode) {
+ //If view already defines a mode (eg. controller is being
+ // initialized after navigation), then pre-populate form.
+ this.setFormFromMode(mode);
+ var deltas = this.conductorViewService.deltas();
+ if (deltas) {
+ this.setFormFromDeltas(deltas);
+ }
+
+ }
+
+ this.setFormFromBounds(this.conductor.bounds());
+
+ // Watch scope for selection of mode or time system by user
+ this.$scope.$watch('modeModel.selectedKey', this.setMode);
+
+ this.$scope.$on('$destroy', this.destroy);
+ };
+
+ TimeConductorController.prototype.destroy = function () {
+ this.conductor.off('bounds', this.setFormFromBounds);
+ this.conductor.off('timeSystem', this.changeTimeSystem);
+ };
+
+ /**
+ * Called when the bounds change in the time conductor. Synchronizes
+ * the bounds values in the time conductor with those in the form
+ *
+ * @private
+ */
+ TimeConductorController.prototype.setFormFromBounds = function (bounds) {
+ this.$scope.boundsModel.start = bounds.start;
+ this.$scope.boundsModel.end = bounds.end;
+ if (!this.pendingUpdate) {
+ this.pendingUpdate = true;
+ this.$window.requestAnimationFrame(function () {
+ this.pendingUpdate = false;
+ this.$scope.$digest();
+ }.bind(this));
+ }
+ };
+
+ /**
+ * @private
+ */
+ 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;
+ });
+ };
+
+ /**
+ * @private
+ */
+ TimeConductorController.prototype.setFormFromDeltas = function (deltas) {
+ this.$scope.boundsModel.startDelta = deltas.start;
+ this.$scope.boundsModel.endDelta = deltas.end;
+ };
+
+ /**
+ * @private
+ */
+ TimeConductorController.prototype.setFormFromTimeSystem = function (timeSystem) {
+ this.$scope.timeSystemModel.selected = timeSystem;
+ this.$scope.timeSystemModel.format = timeSystem.formats()[0];
+ this.$scope.timeSystemModel.deltaFormat = timeSystem.deltaFormat();
+ };
+
+
+ /**
+ * Called when form values are changed. Synchronizes the form with
+ * the time conductor
+ * @param formModel
+ */
+ TimeConductorController.prototype.updateBoundsFromForm = function (boundsModel) {
+ this.conductor.bounds({
+ start: boundsModel.start,
+ end: boundsModel.end
+ });
+ };
+
+ /**
+ * Called when the delta values in the form change. Validates and
+ * sets the new deltas on the Mode.
+ * @param boundsModel
+ * @see TimeConductorMode
+ */
+ TimeConductorController.prototype.updateDeltasFromForm = 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);
+ }
+ };
+
+ /**
+ * Change the selected Time Conductor mode. This will call destroy
+ * and initialization functions on the relevant modes, setting
+ * default values for bound and deltas in the form.
+ *
+ * @private
+ * @param newModeKey
+ * @param oldModeKey
+ */
+ TimeConductorController.prototype.setMode = function (newModeKey, oldModeKey) {
+ if (newModeKey !== oldModeKey) {
+ this.conductorViewService.mode(newModeKey);
+ this.setFormFromMode(newModeKey);
+ }
+ };
+
+ /**
+ * Respond to time system selection from UI
+ *
+ * Allows time system to be changed by key. This supports selection
+ * from the menu. Resolves a TimeSystem object and then invokes
+ * TimeConductorController#setTimeSystem
+ * @param key
+ * @see TimeConductorController#setTimeSystem
+ */
+ TimeConductorController.prototype.selectTimeSystemByKey = function (key) {
+ var selected = this.timeSystems.filter(function (timeSystem) {
+ return timeSystem.metadata.key === key;
+ })[0];
+ this.conductor.timeSystem(selected, selected.defaults().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.
+ *
+ * @private
+ * @param newTimeSystem
+ */
+ TimeConductorController.prototype.changeTimeSystem = function (newTimeSystem) {
+ if (newTimeSystem && (newTimeSystem !== this.$scope.timeSystemModel.selected)) {
+ if (newTimeSystem.defaults()) {
+ var deltas = newTimeSystem.defaults().deltas || {start: 0, end: 0};
+ var bounds = newTimeSystem.defaults().bounds || {start: 0, end: 0};
+
+ this.setFormFromDeltas(deltas);
+ this.setFormFromBounds(bounds);
+ }
+ this.setFormFromTimeSystem(newTimeSystem);
+ }
+ };
+
+ return TimeConductorController;
+ }
+);
diff --git a/platform/features/conductor-v2/conductor/src/ui/TimeConductorControllerSpec.js b/platform/features/conductor-v2/conductor/src/ui/TimeConductorControllerSpec.js
new file mode 100644
index 0000000000..39759c60f5
--- /dev/null
+++ b/platform/features/conductor-v2/conductor/src/ui/TimeConductorControllerSpec.js
@@ -0,0 +1,335 @@
+/*****************************************************************************
+ * 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(['./TimeConductorController'], function (TimeConductorController) {
+ describe("The time conductor controller", function () {
+ var mockScope;
+ var mockWindow;
+ var mockTimeConductor;
+ var mockConductorViewService;
+ var mockTimeSystems;
+ var controller;
+
+ beforeEach(function () {
+ mockScope = jasmine.createSpyObj("$scope", [
+ "$watch",
+ "$on"
+ ]);
+ mockWindow = jasmine.createSpyObj("$window", ["requestAnimationFrame"]);
+ mockTimeConductor = jasmine.createSpyObj(
+ "TimeConductor",
+ [
+ "bounds",
+ "timeSystem",
+ "on",
+ "off"
+ ]
+ );
+ mockTimeConductor.bounds.andReturn({start: undefined, end: undefined});
+
+ mockConductorViewService = jasmine.createSpyObj(
+ "ConductorViewService",
+ [
+ "availableModes",
+ "mode",
+ "availableTimeSystems",
+ "deltas"
+ ]
+ );
+ mockConductorViewService.availableModes.andReturn([]);
+ mockConductorViewService.availableTimeSystems.andReturn([]);
+
+ mockTimeSystems = [];
+ });
+
+ function getListener(name) {
+ return mockTimeConductor.on.calls.filter(function (call) {
+ return call.args[0] === name;
+ })[0].args[1];
+ }
+
+ describe("", function () {
+ beforeEach(function () {
+ controller = new TimeConductorController(
+ mockScope,
+ mockWindow,
+ mockTimeConductor,
+ mockConductorViewService,
+ mockTimeSystems
+ );
+ });
+
+ });
+
+ describe("when time conductor state changes", function () {
+ var mockFormat;
+ var mockDeltaFormat;
+ var defaultBounds;
+ var defaultDeltas;
+ var mockDefaults;
+ var timeSystem;
+ var tsListener;
+
+ beforeEach(function () {
+ mockFormat = {};
+ mockDeltaFormat = {};
+ defaultBounds = {
+ start: 2,
+ end: 3
+ };
+ defaultDeltas = {
+ start: 10,
+ end: 20
+ };
+ mockDefaults = {
+ deltas: defaultDeltas,
+ bounds: defaultBounds
+ };
+ timeSystem = {
+ formats: function () {
+ return [mockFormat];
+ },
+ deltaFormat: function () {
+ return mockDeltaFormat;
+ },
+ defaults: function () {
+ return mockDefaults;
+ }
+ };
+
+ controller = new TimeConductorController(
+ mockScope,
+ mockWindow,
+ mockTimeConductor,
+ mockConductorViewService,
+ mockTimeSystems
+ );
+
+ tsListener = getListener("timeSystem");
+ });
+
+ it("listens for changes to conductor state", function () {
+ expect(mockTimeConductor.on).toHaveBeenCalledWith("timeSystem", controller.changeTimeSystem);
+ expect(mockTimeConductor.on).toHaveBeenCalledWith("bounds", controller.setFormFromBounds);
+ });
+
+ it("deregisters conductor listens when scope is destroyed", function () {
+ expect(mockScope.$on).toHaveBeenCalledWith("$destroy", controller.destroy);
+
+ controller.destroy();
+ expect(mockTimeConductor.off).toHaveBeenCalledWith("timeSystem", controller.changeTimeSystem);
+ expect(mockTimeConductor.off).toHaveBeenCalledWith("bounds", controller.setFormFromBounds);
+ });
+
+ it("when time system changes, sets time system on scope", function () {
+ expect(tsListener).toBeDefined();
+ tsListener(timeSystem);
+
+ expect(mockScope.timeSystemModel).toBeDefined();
+ expect(mockScope.timeSystemModel.selected).toBe(timeSystem);
+ expect(mockScope.timeSystemModel.format).toBe(mockFormat);
+ expect(mockScope.timeSystemModel.deltaFormat).toBe(mockDeltaFormat);
+ });
+
+ it("when time system changes, sets defaults on scope", function () {
+ expect(tsListener).toBeDefined();
+ tsListener(timeSystem);
+
+ expect(mockScope.boundsModel.start).toEqual(defaultBounds.start);
+ expect(mockScope.boundsModel.end).toEqual(defaultBounds.end);
+
+ expect(mockScope.boundsModel.startDelta).toEqual(defaultDeltas.start);
+ expect(mockScope.boundsModel.endDelta).toEqual(defaultDeltas.end);
+ });
+
+ it("when bounds change, sets them on scope", function () {
+ var bounds = {
+ start: 1,
+ end: 2
+ };
+
+ var boundsListener = getListener("bounds");
+ expect(boundsListener).toBeDefined();
+ boundsListener(bounds);
+
+ expect(mockScope.boundsModel).toBeDefined();
+ expect(mockScope.boundsModel.start).toEqual(bounds.start);
+ expect(mockScope.boundsModel.end).toEqual(bounds.end);
+ });
+ });
+
+ describe("when user makes changes from UI", function () {
+ var mode = "realtime";
+ var ts1Metadata;
+ var ts2Metadata;
+ var ts3Metadata;
+ var mockTimeSystemConstructors;
+
+ beforeEach(function () {
+ mode = "realtime";
+ ts1Metadata = {
+ 'key': 'ts1',
+ 'name': 'Time System One',
+ 'cssClass': 'cssClassOne'
+ };
+ ts2Metadata = {
+ 'key': 'ts2',
+ 'name': 'Time System Two',
+ 'cssClass': 'cssClassTwo'
+ };
+ ts3Metadata = {
+ 'key': 'ts3',
+ 'name': 'Time System Three',
+ 'cssClass': 'cssClassThree'
+ };
+ mockTimeSystems = [
+ {
+ metadata: ts1Metadata
+ },
+ {
+ metadata: ts2Metadata
+ },
+ {
+ metadata: ts3Metadata
+ }
+ ];
+
+ //Wrap in mock constructors
+ mockTimeSystemConstructors = mockTimeSystems.map(function (mockTimeSystem) {
+ return function () {
+ return mockTimeSystem;
+ };
+ });
+ });
+
+ it("sets the mode on scope", function () {
+ controller = new TimeConductorController(
+ mockScope,
+ mockWindow,
+ mockTimeConductor,
+ mockConductorViewService,
+ mockTimeSystemConstructors
+ );
+
+ mockConductorViewService.availableTimeSystems.andReturn(mockTimeSystems);
+ controller.setMode(mode);
+
+ expect(mockScope.modeModel.selectedKey).toEqual(mode);
+ });
+
+ it("sets available time systems on scope when mode changes", function () {
+ controller = new TimeConductorController(
+ mockScope,
+ mockWindow,
+ mockTimeConductor,
+ mockConductorViewService,
+ mockTimeSystemConstructors
+ );
+
+ mockConductorViewService.availableTimeSystems.andReturn(mockTimeSystems);
+ controller.setMode(mode);
+
+ expect(mockScope.timeSystemModel.options.length).toEqual(3);
+ expect(mockScope.timeSystemModel.options[0]).toEqual(ts1Metadata);
+ expect(mockScope.timeSystemModel.options[1]).toEqual(ts2Metadata);
+ expect(mockScope.timeSystemModel.options[2]).toEqual(ts3Metadata);
+ });
+
+ it("sets bounds on the time conductor", function () {
+ var formModel = {
+ start: 1,
+ end: 10
+ };
+
+
+ controller = new TimeConductorController(
+ mockScope,
+ mockWindow,
+ mockTimeConductor,
+ mockConductorViewService,
+ mockTimeSystemConstructors
+ );
+
+ controller.updateBoundsFromForm(formModel);
+ expect(mockTimeConductor.bounds).toHaveBeenCalledWith(formModel);
+ });
+
+ it("applies deltas when they change in form", function () {
+ var deltas = {
+ start: 1000,
+ end: 2000
+ };
+ var formModel = {
+ startDelta: deltas.start,
+ endDelta: deltas.end
+ };
+
+ controller = new TimeConductorController(
+ mockScope,
+ mockWindow,
+ mockTimeConductor,
+ mockConductorViewService,
+ mockTimeSystemConstructors
+ );
+
+ controller.updateDeltasFromForm(formModel);
+ expect(mockConductorViewService.deltas).toHaveBeenCalledWith(deltas);
+ });
+
+ it("sets the time system on the time conductor", function () {
+ var defaultBounds = {
+ start: 5,
+ end: 6
+ };
+ var timeSystem = {
+ metadata: {
+ key: 'testTimeSystem'
+ },
+ defaults: function () {
+ return {
+ bounds: defaultBounds
+ };
+ }
+ };
+
+ mockTimeSystems = [
+ // Wrap as constructor function
+ function () {
+ return timeSystem;
+ }
+ ];
+
+ controller = new TimeConductorController(
+ mockScope,
+ mockWindow,
+ mockTimeConductor,
+ mockConductorViewService,
+ mockTimeSystems
+ );
+
+ controller.selectTimeSystemByKey('testTimeSystem');
+ expect(mockTimeConductor.timeSystem).toHaveBeenCalledWith(timeSystem, defaultBounds);
+ });
+ });
+
+ });
+});
diff --git a/platform/features/conductor-v2/conductor/src/ui/TimeConductorMode.js b/platform/features/conductor-v2/conductor/src/ui/TimeConductorMode.js
new file mode 100644
index 0000000000..1f9d3656af
--- /dev/null
+++ b/platform/features/conductor-v2/conductor/src/ui/TimeConductorMode.js
@@ -0,0 +1,201 @@
+/*****************************************************************************
+ * 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
+ * @param {TimeConductorMetadata} metadata
+ */
+ function TimeConductorMode(metadata, conductor, timeSystems) {
+ this.conductor = conductor;
+
+ this.mdata = metadata;
+ this.dlts = 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.}
+ */
+ 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;
+ };
+
+ 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 oldEnd = this.conductor.bounds().end;
+
+ if (this.dlts && this.dlts.end !== undefined) {
+ //Calculate the previous raw end value (without delta)
+ oldEnd = oldEnd - this.dlts.end;
+ }
+
+ this.dlts = deltas;
+
+ var newBounds = {
+ start: oldEnd - this.dlts.start,
+ end: oldEnd + this.dlts.end
+ };
+
+ this.conductor.bounds(newBounds);
+ }
+ return this.dlts;
+ };
+
+ return TimeConductorMode;
+ }
+);
diff --git a/platform/features/conductor-v2/conductor/src/ui/TimeConductorModeSpec.js b/platform/features/conductor-v2/conductor/src/ui/TimeConductorModeSpec.js
new file mode 100644
index 0000000000..6ea5ce79c2
--- /dev/null
+++ b/platform/features/conductor-v2/conductor/src/ui/TimeConductorModeSpec.js
@@ -0,0 +1,210 @@
+/*****************************************************************************
+ * 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
+ });
+ });
+ });
+ });
+});
diff --git a/platform/features/conductor-v2/conductor/src/ui/TimeConductorValidation.js b/platform/features/conductor-v2/conductor/src/ui/TimeConductorValidation.js
new file mode 100644
index 0000000000..3778c26a21
--- /dev/null
+++ b/platform/features/conductor-v2/conductor/src/ui/TimeConductorValidation.js
@@ -0,0 +1,69 @@
+/*****************************************************************************
+ * 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 () {
+
+ /**
+ * Form validation for the TimeConductorController.
+ * @param conductor
+ * @constructor
+ */
+ function TimeConductorValidation(conductor) {
+ var self = this;
+ this.conductor = conductor;
+
+ /*
+ * Bind all class functions to 'this'
+ */
+ Object.keys(TimeConductorValidation.prototype).filter(function (key) {
+ return typeof TimeConductorValidation.prototype[key] === 'function';
+ }).forEach(function (key) {
+ self[key] = self[key].bind(self);
+ });
+ }
+
+ /**
+ * 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;
+ };
+
+ TimeConductorValidation.prototype.validateEnd = function (end) {
+ var bounds = this.conductor.bounds();
+ return this.conductor.validateBounds({start: bounds.start, end: end}) === true;
+ };
+
+ TimeConductorValidation.prototype.validateStartDelta = function (startDelta) {
+ return !isNaN(startDelta) && startDelta > 0;
+ };
+
+ TimeConductorValidation.prototype.validateEndDelta = function (endDelta) {
+ return !isNaN(endDelta) && endDelta >= 0;
+ };
+
+ return TimeConductorValidation;
+ }
+);
diff --git a/platform/features/conductor-v2/conductor/src/ui/TimeConductorValidationSpec.js b/platform/features/conductor-v2/conductor/src/ui/TimeConductorValidationSpec.js
new file mode 100644
index 0000000000..833ba30993
--- /dev/null
+++ b/platform/features/conductor-v2/conductor/src/ui/TimeConductorValidationSpec.js
@@ -0,0 +1,73 @@
+/*****************************************************************************
+ * 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(['./TimeConductorValidation'], function (TimeConductorValidation) {
+ describe("The Time Conductor Validation class", function () {
+ var timeConductorValidation,
+ mockTimeConductor;
+
+ beforeEach(function () {
+ mockTimeConductor = jasmine.createSpyObj("timeConductor", [
+ "validateBounds",
+ "bounds"
+ ]);
+ timeConductorValidation = new TimeConductorValidation(mockTimeConductor);
+ });
+
+ describe("Validates start and end values using Time Conductor", function () {
+ beforeEach(function () {
+ var mockBounds = {
+ start: 10,
+ end: 20
+ };
+
+ mockTimeConductor.bounds.andReturn(mockBounds);
+
+ });
+ it("Validates start values using Time Conductor", function () {
+ var startValue = 30;
+ timeConductorValidation.validateStart(startValue);
+ expect(mockTimeConductor.validateBounds).toHaveBeenCalled();
+ });
+ it("Validates end values using Time Conductor", function () {
+ var endValue = 40;
+ timeConductorValidation.validateEnd(endValue);
+ expect(mockTimeConductor.validateBounds).toHaveBeenCalled();
+ });
+ });
+
+ it("Validates that start delta is valid number > 0", function () {
+ expect(timeConductorValidation.validateStartDelta(-1)).toBe(false);
+ expect(timeConductorValidation.validateStartDelta("abc")).toBe(false);
+ expect(timeConductorValidation.validateStartDelta("1")).toBe(true);
+ expect(timeConductorValidation.validateStartDelta(1)).toBe(true);
+ });
+
+ it("Validates that end delta is valid number >= 0", function () {
+ expect(timeConductorValidation.validateEndDelta(-1)).toBe(false);
+ expect(timeConductorValidation.validateEndDelta("abc")).toBe(false);
+ expect(timeConductorValidation.validateEndDelta("1")).toBe(true);
+ expect(timeConductorValidation.validateEndDelta(0)).toBe(true);
+ expect(timeConductorValidation.validateEndDelta(1)).toBe(true);
+ });
+ });
+});
diff --git a/platform/features/conductor-v2/conductor/src/ui/TimeConductorViewService.js b/platform/features/conductor-v2/conductor/src/ui/TimeConductorViewService.js
new file mode 100644
index 0000000000..8cbc349520
--- /dev/null
+++ b/platform/features/conductor-v2/conductor/src/ui/TimeConductorViewService.js
@@ -0,0 +1,202 @@
+/*****************************************************************************
+ * 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) {
+
+ /**
+ * 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.
+ *
+ * @param conductor
+ * @param timeSystems
+ * @constructor
+ */
+ function TimeConductorViewService(conductor, timeSystems) {
+ this.systems = timeSystems.map(function (timeSystemConstructor) {
+ return timeSystemConstructor();
+ });
+
+ this.conductor = 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;
+ }
+ }
+
+ /**
+ * Getter/Setter for the Time Conductor Mode. Modes determine the
+ * behavior of the time conductor, especially with regards to the
+ * bounds and how they change with time.
+ *
+ * In fixed mode, the bounds do not change with time, but can be
+ * modified by the used
+ *
+ * In realtime mode, the bounds change with time. Bounds are not
+ * directly modifiable by the user, however deltas can be.
+ *
+ * In Latest Available Data (LAD) mode, the bounds are updated when
+ * data is received. As with realtime mode the
+ *
+ * @param {string} newModeKey One of 'fixed', 'realtime', or 'LAD'
+ * @returns {string} the current mode, one of 'fixed', 'realtime',
+ * or 'LAD'.
+ *
+ */
+ TimeConductorViewService.prototype.mode = function (newModeKey) {
+ function contains(timeSystems, ts) {
+ return timeSystems.filter(function (t) {
+ return t.metadata.key === ts.metadata.key;
+ }).length > 0;
+ }
+
+ if (arguments.length === 1) {
+ var timeSystem = this.conductor.timeSystem();
+ var modes = this.availableModes();
+ var modeMetaData = modes[newModeKey];
+
+ if (this.currentMode) {
+ this.currentMode.destroy();
+ }
+ this.currentMode = new TimeConductorMode(modeMetaData, this.conductor, this.systems);
+
+ // If no time system set on time conductor, or the currently selected time system is not available in
+ // the new mode, default to first available time system
+ if (!timeSystem || !contains(this.currentMode.availableTimeSystems(), timeSystem)) {
+ timeSystem = this.currentMode.availableTimeSystems()[0];
+ this.conductor.timeSystem(timeSystem, timeSystem.defaults().bounds);
+ }
+ }
+ return this.currentMode ? this.currentMode.metadata().key : undefined;
+ };
+
+ /**
+ * @typedef {object} Delta
+ * @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 {Delta} 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();
+ };
+
+ return TimeConductorViewService;
+ }
+);
diff --git a/platform/features/conductor-v2/conductor/src/ui/TimeConductorViewServiceSpec.js b/platform/features/conductor-v2/conductor/src/ui/TimeConductorViewServiceSpec.js
new file mode 100644
index 0000000000..39e7810320
--- /dev/null
+++ b/platform/features/conductor-v2/conductor/src/ui/TimeConductorViewServiceSpec.js
@@ -0,0 +1,185 @@
+/*****************************************************************************
+ * 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(['./TimeConductorViewService'], function (TimeConductorViewService) {
+ describe("The Time Conductor view service", function () {
+ var mockTimeConductor;
+ var basicTimeSystem;
+ var tickingTimeSystem;
+ var viewService;
+ var tickingTimeSystemDefaults;
+
+ function mockConstructor(object) {
+ return function () {
+ return object;
+ };
+ }
+
+ beforeEach(function () {
+ mockTimeConductor = jasmine.createSpyObj("timeConductor", [
+ "timeSystem",
+ "bounds",
+ "follow",
+ "on",
+ "off"
+ ]);
+
+ basicTimeSystem = jasmine.createSpyObj("basicTimeSystem", [
+ "tickSources",
+ "defaults"
+ ]);
+ basicTimeSystem.metadata = {
+ key: "basic"
+ };
+ basicTimeSystem.tickSources.andReturn([]);
+ basicTimeSystem.defaults.andReturn({
+ bounds: {
+ start: 0,
+ end: 1
+ },
+ deltas: {
+ start: 0,
+ end: 0
+ }
+ });
+ //Initialize conductor
+ mockTimeConductor.timeSystem.andReturn(basicTimeSystem);
+ mockTimeConductor.bounds.andReturn({start: 0, end: 1});
+
+ tickingTimeSystem = jasmine.createSpyObj("tickingTimeSystem", [
+ "tickSources",
+ "defaults"
+ ]);
+ tickingTimeSystem.metadata = {
+ key: "ticking"
+ };
+ tickingTimeSystemDefaults = {
+ bounds: {
+ start: 100,
+ end: 200
+ },
+ deltas: {
+ start: 1000,
+ end: 500
+ }
+ };
+ tickingTimeSystem.defaults.andReturn(tickingTimeSystemDefaults);
+ });
+
+ it("At a minimum supports fixed mode", function () {
+ var mockTimeSystems = [mockConstructor(basicTimeSystem)];
+ viewService = new TimeConductorViewService(mockTimeConductor, mockTimeSystems);
+
+ var availableModes = viewService.availableModes();
+ expect(availableModes.fixed).toBeDefined();
+ });
+
+ it("Supports realtime mode if appropriate tick source(s) availables", function () {
+ var mockTimeSystems = [mockConstructor(tickingTimeSystem)];
+ var mockRealtimeTickSource = {
+ metadata: {
+ mode: 'realtime'
+ }
+ };
+ tickingTimeSystem.tickSources.andReturn([mockRealtimeTickSource]);
+
+ viewService = new TimeConductorViewService(mockTimeConductor, mockTimeSystems);
+
+ var availableModes = viewService.availableModes();
+ expect(availableModes.realtime).toBeDefined();
+ });
+
+ it("Supports LAD mode if appropriate tick source(s) available", function () {
+ var mockTimeSystems = [mockConstructor(tickingTimeSystem)];
+ var mockLADTickSource = {
+ metadata: {
+ mode: 'lad'
+ }
+ };
+ tickingTimeSystem.tickSources.andReturn([mockLADTickSource]);
+
+ viewService = new TimeConductorViewService(mockTimeConductor, mockTimeSystems);
+
+ var availableModes = viewService.availableModes();
+ expect(availableModes.lad).toBeDefined();
+ });
+
+ describe("when mode is changed", function () {
+
+ it("destroys previous mode", function () {
+ var mockTimeSystems = [mockConstructor(basicTimeSystem)];
+
+ var oldMode = jasmine.createSpyObj("conductorMode", [
+ "destroy"
+ ]);
+
+ viewService = new TimeConductorViewService(mockTimeConductor, mockTimeSystems);
+ viewService.currentMode = oldMode;
+ viewService.mode('fixed');
+ expect(oldMode.destroy).toHaveBeenCalled();
+ });
+
+ describe("the time system", function () {
+ it("is retained if available in new mode", function () {
+ var mockTimeSystems = [mockConstructor(basicTimeSystem), mockConstructor(tickingTimeSystem)];
+ var mockRealtimeTickSource = {
+ metadata: {
+ mode: 'realtime'
+ },
+ listen: function () {}
+ };
+ tickingTimeSystem.tickSources.andReturn([mockRealtimeTickSource]);
+
+ viewService = new TimeConductorViewService(mockTimeConductor, mockTimeSystems);
+
+ //Set time system to one known to support realtime mode
+ mockTimeConductor.timeSystem.andReturn(tickingTimeSystem);
+
+ //Select realtime mode
+ mockTimeConductor.timeSystem.reset();
+ viewService.mode('realtime');
+ expect(mockTimeConductor.timeSystem).not.toHaveBeenCalledWith(tickingTimeSystem, tickingTimeSystemDefaults.bounds);
+ });
+ it("is defaulted if selected time system not available in new mode", function () {
+ var mockTimeSystems = [mockConstructor(basicTimeSystem), mockConstructor(tickingTimeSystem)];
+ var mockRealtimeTickSource = {
+ metadata: {
+ mode: 'realtime'
+ },
+ listen: function () {}
+ };
+ tickingTimeSystem.tickSources.andReturn([mockRealtimeTickSource]);
+
+ viewService = new TimeConductorViewService(mockTimeConductor, mockTimeSystems);
+
+ //Set time system to one known to not support realtime mode
+ mockTimeConductor.timeSystem.andReturn(basicTimeSystem);
+
+ //Select realtime mode
+ mockTimeConductor.timeSystem.reset();
+ viewService.mode('realtime');
+ expect(mockTimeConductor.timeSystem).toHaveBeenCalledWith(tickingTimeSystem, tickingTimeSystemDefaults.bounds);
+ });
+ });
+ });
+ });
+});
diff --git a/platform/features/conductor-v2/utcTimeSystem/bundle.js b/platform/features/conductor-v2/utcTimeSystem/bundle.js
new file mode 100644
index 0000000000..5b13e605d4
--- /dev/null
+++ b/platform/features/conductor-v2/utcTimeSystem/bundle.js
@@ -0,0 +1,40 @@
+/*****************************************************************************
+ * 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-v2/utcTimeSystem", {
+ "extensions": {
+ "timeSystems": [
+ {
+ "implementation": UTCTimeSystem,
+ "depends": ["$timeout"]
+ }
+ ]
+ }
+ });
+});
diff --git a/platform/features/conductor-v2/utcTimeSystem/src/UTCTimeSystem.js b/platform/features/conductor-v2/utcTimeSystem/src/UTCTimeSystem.js
new file mode 100644
index 0000000000..b6e969c3eb
--- /dev/null
+++ b/platform/features/conductor-v2/utcTimeSystem/src/UTCTimeSystem.js
@@ -0,0 +1,78 @@
+/*****************************************************************************
+ * 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([
+ '../../conductor/src/timeSystems/TimeSystem',
+ '../../conductor/src/timeSystems/LocalClock'
+], function (TimeSystem, LocalClock) {
+ var FIFTEEN_MINUTES = 15 * 60 * 1000,
+ DEFAULT_PERIOD = 1000;
+
+ /**
+ * 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 (key) {
+ var now = Math.ceil(Date.now() / 1000) * 1000;
+ return {
+ key: 'utc-default',
+ name: 'UTC time system defaults',
+ deltas: {start: FIFTEEN_MINUTES, end: 0},
+ bounds: {start: now - FIFTEEN_MINUTES, end: now}
+ };
+ };
+
+ return UTCTimeSystem;
+});
diff --git a/platform/features/conductor-v2/utcTimeSystem/src/UTCTimeSystemSpec.js b/platform/features/conductor-v2/utcTimeSystem/src/UTCTimeSystemSpec.js
new file mode 100644
index 0000000000..808eade72e
--- /dev/null
+++ b/platform/features/conductor-v2/utcTimeSystem/src/UTCTimeSystemSpec.js
@@ -0,0 +1,46 @@
+/*****************************************************************************
+ * 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();
+ });
+ });
+});
diff --git a/platform/features/conductor/bundle.js b/platform/features/conductor/bundle.js
index 5def15a6b9..c1349b4e58 100644
--- a/platform/features/conductor/bundle.js
+++ b/platform/features/conductor/bundle.js
@@ -47,6 +47,11 @@ define([
]
}
],
+ "stylesheets": [
+ {
+ "stylesheetUrl": "css/time-conductor.css"
+ }
+ ],
"components": [
{
"type": "decorator",
diff --git a/platform/features/conductor/res/sass/time-conductor.scss b/platform/features/conductor/res/sass/time-conductor.scss
new file mode 100644
index 0000000000..bc50b42da5
--- /dev/null
+++ b/platform/features/conductor/res/sass/time-conductor.scss
@@ -0,0 +1,300 @@
+/*****************************************************************************
+ * 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.
+ *****************************************************************************/
+@import "bourbon";
+
+@import "../../../../commonUI/general/res/sass/constants";
+@import "../../../../commonUI/general/res/sass/mixins";
+@import "../../../../commonUI/general/res/sass/mobile/constants";
+@import "../../../../commonUI/general/res/sass/mobile/mixins";
+@import "../../../../commonUI/themes/espresso/res/sass/constants";
+@import "../../../../commonUI/themes/espresso/res/sass/mixins";
+
+$ueTimeConductorH: (33px, 18px, 20px);
+
+@mixin toiLineHovEffects() {
+ &:before,
+ &:after {
+ background-color: $timeControllerToiLineColorHov;
+ }
+}
+
+.l-time-controller {
+ $minW: 500px;
+ $knobHOffset: 0px;
+ $knobM: ($sliderKnobW + $knobHOffset) * -1;
+ $rangeValPad: $interiorMargin;
+ $rangeValOffset: $sliderKnobW + $interiorMargin;
+ $timeRangeSliderLROffset: 150px + ($sliderKnobW * 2);
+ $r1H: nth($ueTimeConductorH,1);
+ $r2H: nth($ueTimeConductorH,2);
+ $r3H: nth($ueTimeConductorH,3);
+
+ min-width: $minW;
+ font-size: 0.8rem;
+
+ .l-time-range-inputs-holder,
+ .l-time-range-slider-holder,
+ .l-time-range-ticks-holder
+ {
+ box-sizing: border-box;
+ position: relative;
+ &:not(:first-child) {
+ margin-top: $interiorMargin;
+ }
+ }
+ .l-time-range-slider,
+ .l-time-range-ticks {
+ @include absPosDefault(0, visible);
+ left: $timeRangeSliderLROffset; right: $timeRangeSliderLROffset;
+ }
+
+ .l-time-range-inputs-holder {
+ border-top: 1px solid $colorInteriorBorder;
+ padding-top: $interiorMargin;
+ &.l-flex-row,
+ .l-flex-row {
+ @include align-items(center);
+ .flex-elem {
+ height: auto;
+ line-height: normal;
+ }
+ }
+ .type-icon {
+ font-size: 120%;
+ vertical-align: middle;
+ }
+ .l-time-range-input-w,
+ .l-time-range-inputs-elem {
+ margin-right: $interiorMargin;
+ .lbl {
+ color: $colorPlotLabelFg;
+ }
+ .ui-symbol.icon {
+ font-size: 11px;
+ }
+ }
+ .l-time-range-input-w {
+ // Wraps a datetime text input field
+ position: relative;
+ input[type="text"] {
+ width: 200px;
+ &.picker-icon {
+ padding-right: 20px;
+ }
+ }
+ .icon-calendar {
+ position: absolute;
+ right: 5px;
+ top: 5px;
+ }
+ }
+ }
+
+ .l-time-range-slider-holder {
+ height: $r2H;
+ .range-holder {
+ box-shadow: none;
+ background: none;
+ border: none;
+ .range {
+ .toi-line {
+ $myC: $timeControllerToiLineColor;
+ $myW: 8px;
+ @include transform(translateX(50%));
+ position: absolute;
+ top: 0; right: 0; bottom: 0px; left: auto;
+ width: $myW;
+ height: auto;
+ z-index: 2;
+ &:before {
+ // Vert line
+ background-color: $myC;
+ position: absolute;
+ content: "";
+ top: 0; right: auto; bottom: -10px; left: floor($myW/2) - 1;
+ width: 1px;
+ }
+ }
+ &:hover .toi-line {
+ @include toiLineHovEffects;
+ }
+ }
+ }
+ &:not(:active) {
+ .knob,
+ .range {
+ @include transition-property(left, right);
+ @include transition-duration(500ms);
+ @include transition-timing-function(ease-in-out);
+ }
+ }
+ }
+
+ .l-time-range-ticks-holder {
+ height: $r3H;
+ .l-time-range-ticks {
+ border-top: 1px solid $colorTick;
+ .tick {
+ background-color: $colorTick;
+ border:none;
+ height: 5px;
+ width: 1px;
+ margin-left: -1px;
+ position: absolute;
+ &:first-child {
+ margin-left: 0;
+ }
+ .l-time-range-tick-label {
+ @include webkitProp(transform, translateX(-50%));
+ color: $colorPlotLabelFg;
+ display: inline-block;
+ font-size: 0.7rem;
+ position: absolute;
+ top: 5px;
+ white-space: nowrap;
+ z-index: 2;
+ }
+ }
+ }
+ }
+
+ .knob {
+ z-index: 2;
+ &:before {
+ $mTB: 2px;
+ $grippyW: 3px;
+ $mLR: ($sliderKnobW - $grippyW)/2;
+ @include bgStripes($c: pullForward($sliderColorKnob, 20%), $a: 1, $bgsize: 4px, $angle: 0deg);
+ content: '';
+ display: block;
+ position: absolute;
+ top: $mTB; right: $mLR; bottom: $mTB; left: $mLR;
+ }
+ .range-value {
+ @include trans-prop-nice-fade(.25s);
+ font-size: 0.7rem;
+ position: absolute;
+ height: $r2H;
+ line-height: $r2H;
+ white-space: nowrap;
+ z-index: 1;
+ }
+ &:hover {
+ .range-value {
+ color: $sliderColorKnobHov;
+ }
+ }
+ &.knob-l {
+ margin-left: $knobM;
+ .range-value {
+ text-align: right;
+ right: $rangeValOffset;
+ }
+ }
+ &.knob-r {
+ margin-right: $knobM;
+ .range-value {
+ left: $rangeValOffset;
+ }
+ &:hover + .range-holder .range .toi-line {
+ @include toiLineHovEffects;
+ }
+ }
+ }
+
+ .l-time-domain-selector {
+ position: absolute;
+ right: 0px;
+ top: $interiorMargin;
+ }
+
+}
+
+.s-time-range-val {
+ border-radius: $controlCr;
+ background-color: $colorInputBg;
+ padding: 1px 1px 0 $interiorMargin;
+}
+
+/******************************************************************** MOBILE */
+
+@include phoneandtablet {
+ .l-time-controller {
+ min-width: 0;
+ .l-time-range-slider-holder,
+ .l-time-range-ticks-holder {
+ display: none;
+ }
+ }
+}
+
+@include phone {
+ .l-time-controller {
+ .l-time-range-inputs-holder {
+ &.l-flex-row,
+ .l-flex-row {
+ @include align-items(flex-start);
+ }
+ .l-time-range-inputs-elem {
+ &.type-icon {
+ margin-top: 3px;
+ }
+ }
+ .t-inputs-w,
+ .l-time-range-inputs-elem {
+ @include flex-direction(column);
+ .l-time-range-input-w:not(:first-child) {
+ &:not(:first-child) {
+ margin-top: $interiorMargin;
+ }
+ margin-right: 0;
+ }
+ .l-time-range-inputs-elem {
+ &.lbl { display: none; }
+ }
+ }
+ }
+ }
+}
+
+@include phonePortrait {
+ .l-time-controller {
+ .l-time-range-inputs-holder {
+ .t-inputs-w,
+ .l-time-range-inputs-elem {
+ @include flex(1 1 auto);
+ padding-top: 25px; // Make room for the ever lovin' Time Domain Selector
+ .flex-elem {
+ @include flex(1 1 auto);
+ width: 100%;
+ }
+ input[type="text"] {
+ width: 100%;
+ }
+ }
+ }
+ }
+ .l-time-domain-selector {
+ right: auto;
+ left: 20px;
+ }
+}
\ No newline at end of file
diff --git a/platform/features/layout/res/templates/layout.html b/platform/features/layout/res/templates/layout.html
index ed1f9a50c8..b2ffa16808 100644
--- a/platform/features/layout/res/templates/layout.html
+++ b/platform/features/layout/res/templates/layout.html
@@ -26,11 +26,10 @@
ng-repeat="childObject in composition"
ng-style="controller.getFrameStyle(childObject.getId())">
-