mirror of
https://github.com/nasa/openmct.git
synced 2025-01-19 03:06:54 +00:00
[Time Conductor] merged from open1182
This commit is contained in:
parent
22da34870d
commit
5cd0c8a4fa
@ -95,6 +95,38 @@ define([
|
||||
})[0][0];
|
||||
}
|
||||
|
||||
UTCTimeFormat.prototype.timeUnits = function (timeRange) {
|
||||
var momentified = moment.duration(timeRange);
|
||||
return [
|
||||
["Decades", function (r) {
|
||||
return r.years() > 15;
|
||||
}],
|
||||
["Years", function (r) {
|
||||
return r.years() > 1;
|
||||
}],
|
||||
["Months", function (r) {
|
||||
return r.years() === 1 || r.months() > 1;
|
||||
}],
|
||||
["Days", function (r) {
|
||||
return r.months() === 1 || r.days() > 1;
|
||||
}],
|
||||
["Hours", function (r) {
|
||||
return r.days() === 1 || r.hours() > 1;
|
||||
}],
|
||||
["Minutes", function (r) {
|
||||
return r.hours() === 1 || r.minutes() > 1;
|
||||
}],
|
||||
["Seconds", function (r) {
|
||||
return r.minutes() === 1 || r.seconds() > 1;
|
||||
}],
|
||||
["Milliseconds", function (r) {
|
||||
return true;
|
||||
}]
|
||||
].filter(function (row){
|
||||
return row[1](momentified);
|
||||
})[0][0];
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param value
|
||||
|
@ -85,6 +85,7 @@ define(
|
||||
ConductorRepresenter.prototype.destroy = function destroy() {
|
||||
this.conductor.off("bounds", this.boundsListener);
|
||||
this.conductor.off("timeSystem", this.timeSystemListener);
|
||||
this.conductor.off("follow", this.followListener);
|
||||
};
|
||||
|
||||
return ConductorRepresenter;
|
||||
|
@ -69,7 +69,17 @@ define([
|
||||
"$window",
|
||||
"timeConductor",
|
||||
"timeConductorViewService",
|
||||
"timeSystems[]"
|
||||
"timeSystems[]",
|
||||
"formatService"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "ConductorAxisController",
|
||||
"implementation": ConductorAxisController,
|
||||
"depends": [
|
||||
"timeConductor",
|
||||
"formatService",
|
||||
"timeConductorViewService"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -312,13 +312,13 @@
|
||||
.l-time-conductor-zoom-w {
|
||||
@include justify-content(flex-end);
|
||||
.time-conductor-zoom {
|
||||
display: none; // TEMP per request from Andrew 8/1/16
|
||||
//display: none; // TEMP per request from Andrew 8/1/16
|
||||
height: $r3H;
|
||||
min-width: 100px;
|
||||
width: 20%;
|
||||
}
|
||||
.time-conductor-zoom-current-range {
|
||||
display: none; // TEMP per request from Andrew 8/1/16
|
||||
//display: none; // TEMP per request from Andrew 8/1/16
|
||||
color: $colorTick;
|
||||
}
|
||||
}
|
||||
|
@ -131,8 +131,15 @@
|
||||
</mct-control>
|
||||
<!-- Zoom control -->
|
||||
<div class="l-time-conductor-zoom-w grows flex-elem l-flex-row">
|
||||
<span class="time-conductor-zoom-current-range flex-elem flex-fixed holder"></span>
|
||||
<input class="time-conductor-zoom flex-elem" type="range" />
|
||||
<span
|
||||
class="time-conductor-zoom-current-range flex-elem flex-fixed holder">{{timeUnits}}</span>
|
||||
<input class="time-conductor-zoom flex-elem" type="range"
|
||||
ng-model="currentZoom"
|
||||
ng-mouseUp="tcController.zoomStop(currentZoom)"
|
||||
ng-change="tcController.zoom(currentZoom)"
|
||||
min="0.01"
|
||||
step="0.01"
|
||||
max="0.99" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -73,8 +73,20 @@ define([], function () {
|
||||
throw new Error('Not implemented');
|
||||
};
|
||||
|
||||
/**
|
||||
/***
|
||||
*
|
||||
* @typedef {object} TimeConductorZoom
|
||||
* @property {number} min The largest time span that the time
|
||||
* conductor can display in this time system
|
||||
* @property {number} max The smallest time span that the time
|
||||
* conductor can display in this time system
|
||||
*
|
||||
* @typedef {object} TimeSystemDefault
|
||||
* @property {TimeConductorDeltas} deltas The deltas to apply by default
|
||||
* when this time system is active. Applies to real-time modes only
|
||||
* @property {TimeConductorBounds} bounds The bounds to apply by default
|
||||
* when this time system is active
|
||||
* @property {TimeConductorZoom} zoom Default min and max zoom levels
|
||||
* @returns {TimeSystemDefault[]} At least one set of default values for
|
||||
* this time system.
|
||||
*/
|
||||
|
@ -32,11 +32,12 @@ define(
|
||||
* labelled 'ticks'. It requires 'start' and 'end' integer values to
|
||||
* be specified as attributes.
|
||||
*/
|
||||
function ConductorAxisController(conductor, formatService) {
|
||||
function ConductorAxisController(conductor, formatService, conductorViewService) {
|
||||
// Dependencies
|
||||
this.d3 = d3;
|
||||
this.formatService = formatService;
|
||||
this.conductor = conductor;
|
||||
this.conductorViewService = conductorViewService;
|
||||
|
||||
// Runtime properties (set by 'link' function)
|
||||
this.target = undefined;
|
||||
@ -46,14 +47,22 @@ define(
|
||||
this.initialized = false;
|
||||
this.msPerPixel = undefined;
|
||||
|
||||
this.setScale = this.setScale.bind(this);
|
||||
this.changeBounds = this.changeBounds.bind(this);
|
||||
this.changeTimeSystem = this.changeTimeSystem.bind(this);
|
||||
|
||||
this.bounds = conductor.bounds();
|
||||
this.timeSystem = conductor.timeSystem();
|
||||
|
||||
//Bind all class functions to 'this'
|
||||
Object.keys(ConductorAxisController.prototype).filter(function (key) {
|
||||
return typeof ConductorAxisController.prototype[key] === 'function';
|
||||
}).forEach(function (key) {
|
||||
this[key] = ConductorAxisController.prototype[key].bind(this);
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
ConductorAxisController.prototype.destroy = function () {
|
||||
this.conductor.off('timeSystem', this.changeTimeSystem);
|
||||
this.conductor.off('bounds', this.setScale);
|
||||
};
|
||||
|
||||
ConductorAxisController.prototype.changeBounds = function (bounds) {
|
||||
this.bounds = bounds;
|
||||
if (this.initialized) {
|
||||
@ -129,18 +138,26 @@ define(
|
||||
|
||||
if (this.timeSystem !== undefined) {
|
||||
this.changeTimeSystem(this.timeSystem);
|
||||
this.setScale(this.bounds);
|
||||
this.setScale();
|
||||
}
|
||||
|
||||
//Respond to changes in conductor
|
||||
this.conductor.on("timeSystem", this.changeTimeSystem);
|
||||
this.conductor.on("bounds", this.changeBounds);
|
||||
|
||||
this.scope.$on("$destroy", this.destroy);
|
||||
|
||||
this.conductorViewService.on("zoom", this.zoom);
|
||||
};
|
||||
|
||||
ConductorAxisController.prototype.panEnd = function () {
|
||||
ConductorAxisController.prototype.panStop = function () {
|
||||
//resync view bounds with time conductor bounds
|
||||
this.conductor.bounds(this.bounds);
|
||||
this.scope.$emit("pan-stop");
|
||||
this.conductorViewService.emit("pan-stop");
|
||||
};
|
||||
|
||||
ConductorAxisController.prototype.zoom = function (bounds) {
|
||||
this.changeBounds(bounds);
|
||||
};
|
||||
|
||||
ConductorAxisController.prototype.pan = function (delta) {
|
||||
@ -154,7 +171,7 @@ define(
|
||||
end: end
|
||||
};
|
||||
this.setScale();
|
||||
this.scope.$emit("pan", this.bounds);
|
||||
this.conductorViewService.emit("pan", this.bounds);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -41,7 +41,7 @@ define([], function () {
|
||||
|
||||
template: '<div class="l-axis-holder" ' +
|
||||
' mct-drag-down="axis.panStart()"' +
|
||||
' mct-drag-up="axis.panEnd(delta)"' +
|
||||
' mct-drag-up="axis.panStop(delta)"' +
|
||||
' mct-drag="axis.pan(delta)"' +
|
||||
' mct-resize="axis.resize()"></div>'
|
||||
}
|
||||
|
@ -32,7 +32,9 @@ define(['./MctConductorAxis'], function (MctConductorAxis) {
|
||||
d3;
|
||||
|
||||
beforeEach(function () {
|
||||
mockScope = {};
|
||||
mockScope = jasmine.createSpyObj("scope", [
|
||||
"$on"
|
||||
]);
|
||||
|
||||
//Add some HTML elements
|
||||
mockTarget = {
|
||||
@ -49,7 +51,8 @@ define(['./MctConductorAxis'], function (MctConductorAxis) {
|
||||
mockConductor = jasmine.createSpyObj("conductor", [
|
||||
"timeSystem",
|
||||
"bounds",
|
||||
"on"
|
||||
"on",
|
||||
"off"
|
||||
]);
|
||||
mockConductor.bounds.andReturn(mockBounds);
|
||||
|
||||
@ -85,6 +88,13 @@ define(['./MctConductorAxis'], function (MctConductorAxis) {
|
||||
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;
|
||||
|
@ -26,7 +26,7 @@ define(
|
||||
],
|
||||
function (TimeConductorValidation) {
|
||||
|
||||
function TimeConductorController($scope, $window, timeConductor, conductorViewService, timeSystems) {
|
||||
function TimeConductorController($scope, $window, timeConductor, conductorViewService, timeSystems, formatService) {
|
||||
|
||||
var self = this;
|
||||
|
||||
@ -43,6 +43,7 @@ define(
|
||||
this.conductor = timeConductor;
|
||||
this.modes = conductorViewService.availableModes();
|
||||
this.validation = new TimeConductorValidation(this.conductor);
|
||||
this.formatService = formatService;
|
||||
|
||||
// Construct the provided time system definitions
|
||||
this.timeSystems = timeSystems.map(function (timeSystemConstructor) {
|
||||
@ -53,9 +54,6 @@ define(
|
||||
this.initializeScope();
|
||||
|
||||
this.conductor.on('bounds', this.setFormFromBounds);
|
||||
this.conductor.on('follow', function (follow) {
|
||||
$scope.followMode = follow;
|
||||
});
|
||||
this.conductor.on('timeSystem', this.changeTimeSystem);
|
||||
|
||||
// If no mode selected, select fixed as the default
|
||||
@ -100,14 +98,25 @@ define(
|
||||
|
||||
// Watch scope for selection of mode or time system by user
|
||||
this.$scope.$watch('modeModel.selectedKey', this.setMode);
|
||||
this.$scope.$on('pan', function (e, bounds) {
|
||||
this.$scope.panning = true;
|
||||
this.setFormFromBounds(bounds);
|
||||
}.bind(this));
|
||||
this.conductorViewService.on('pan', this.pan);
|
||||
|
||||
this.$scope.$on('pan-stop', function () {
|
||||
this.$scope.panning = false;
|
||||
}.bind(this));
|
||||
this.conductorViewService.on('pan-stop', this.panStop);
|
||||
|
||||
this.$scope.$on('$destroy', this.destroy);
|
||||
};
|
||||
|
||||
TimeConductorController.prototype.destroy = function () {
|
||||
this.conductor.off('bounds', this.setFormFromBounds);
|
||||
this.conductor.off('timeSystem', this.changeTimeSystem);
|
||||
};
|
||||
|
||||
TimeConductorController.prototype.pan = function (bounds) {
|
||||
this.$scope.panning = true;
|
||||
this.setFormFromBounds(bounds);
|
||||
};
|
||||
|
||||
TimeConductorController.prototype.panStop = function () {
|
||||
this.$scope.panning = false;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -119,6 +128,10 @@ define(
|
||||
TimeConductorController.prototype.setFormFromBounds = function (bounds) {
|
||||
this.$scope.boundsModel.start = bounds.start;
|
||||
this.$scope.boundsModel.end = bounds.end;
|
||||
|
||||
this.$scope.currentZoom = this.toSliderValue(bounds.end - bounds.start);
|
||||
this.toTimeUnits(bounds.end - bounds.start);
|
||||
|
||||
if (!this.pendingUpdate) {
|
||||
this.pendingUpdate = true;
|
||||
this.$window.requestAnimationFrame(function () {
|
||||
@ -153,9 +166,12 @@ define(
|
||||
* @private
|
||||
*/
|
||||
TimeConductorController.prototype.setFormFromTimeSystem = function (timeSystem) {
|
||||
this.$scope.timeSystemModel.selected = timeSystem;
|
||||
this.$scope.timeSystemModel.format = timeSystem.formats()[0];
|
||||
this.$scope.timeSystemModel.deltaFormat = timeSystem.deltaFormat();
|
||||
var timeSystemModel = this.$scope.timeSystemModel;
|
||||
timeSystemModel.selected = timeSystem;
|
||||
timeSystemModel.format = timeSystem.formats()[0];
|
||||
timeSystemModel.deltaFormat = timeSystem.deltaFormat();
|
||||
timeSystemModel.minZoom = timeSystem.defaults().zoom.min;
|
||||
timeSystemModel.maxZoom = timeSystem.defaults().zoom.max;
|
||||
};
|
||||
|
||||
|
||||
@ -242,6 +258,41 @@ define(
|
||||
}
|
||||
};
|
||||
|
||||
TimeConductorController.prototype.toSliderValue = function (timeSpan) {
|
||||
var timeSystem = this.conductor.timeSystem();
|
||||
if (timeSystem) {
|
||||
var zoomDefaults = this.conductor.timeSystem().defaults().zoom;
|
||||
var perc = timeSpan / (zoomDefaults.min - zoomDefaults.max);
|
||||
return 1 - Math.pow(perc, 1 / 4);
|
||||
}
|
||||
};
|
||||
|
||||
TimeConductorController.prototype.toTimeSpan = function (sliderValue) {
|
||||
var center = this.$scope.boundsModel.start +
|
||||
((this.$scope.boundsModel.end - this.$scope.boundsModel.start) / 2);
|
||||
var zoomDefaults = this.conductor.timeSystem().defaults().zoom;
|
||||
var timeSpan = Math.pow((1 - sliderValue), 4) * (zoomDefaults.min - zoomDefaults.max);
|
||||
return {start: center - timeSpan / 2, end: center + timeSpan / 2};
|
||||
};
|
||||
|
||||
TimeConductorController.prototype.toTimeUnits = function (timeSpan) {
|
||||
if (this.conductor.timeSystem()) {
|
||||
var timeFormat = this.formatService.getFormat(this.conductor.timeSystem().formats()[0]);
|
||||
this.$scope.timeUnits = timeFormat.timeUnits && timeFormat.timeUnits(timeSpan);
|
||||
}
|
||||
}
|
||||
|
||||
TimeConductorController.prototype.zoom = function(sliderValue) {
|
||||
var bounds = this.toTimeSpan(sliderValue);
|
||||
this.setFormFromBounds(bounds);
|
||||
this.conductorViewService.emit("zoom", bounds);
|
||||
};
|
||||
|
||||
TimeConductorController.prototype.zoomStop = function (sliderValue) {
|
||||
var bounds = this.toTimeSpan(sliderValue);
|
||||
this.conductor.bounds(bounds);
|
||||
};
|
||||
|
||||
return TimeConductorController;
|
||||
}
|
||||
);
|
||||
|
@ -30,14 +30,18 @@ define(['./TimeConductorController'], function (TimeConductorController) {
|
||||
var controller;
|
||||
|
||||
beforeEach(function () {
|
||||
mockScope = jasmine.createSpyObj("$scope", ["$watch"]);
|
||||
mockScope = jasmine.createSpyObj("$scope", [
|
||||
"$watch",
|
||||
"$on"
|
||||
]);
|
||||
mockWindow = jasmine.createSpyObj("$window", ["requestAnimationFrame"]);
|
||||
mockTimeConductor = jasmine.createSpyObj(
|
||||
"TimeConductor",
|
||||
[
|
||||
"bounds",
|
||||
"timeSystem",
|
||||
"on"
|
||||
"on",
|
||||
"off"
|
||||
]
|
||||
);
|
||||
mockTimeConductor.bounds.andReturn({start: undefined, end: undefined});
|
||||
@ -124,9 +128,16 @@ define(['./TimeConductorController'], function (TimeConductorController) {
|
||||
});
|
||||
|
||||
it("listens for changes to conductor state", function () {
|
||||
expect(mockTimeConductor.on).toHaveBeenCalledWith("timeSystem", jasmine.any(Function));
|
||||
expect(mockTimeConductor.on).toHaveBeenCalledWith("bounds", jasmine.any(Function));
|
||||
expect(mockTimeConductor.on).toHaveBeenCalledWith("follow", jasmine.any(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 () {
|
||||
@ -164,17 +175,6 @@ define(['./TimeConductorController'], function (TimeConductorController) {
|
||||
expect(mockScope.boundsModel.start).toEqual(bounds.start);
|
||||
expect(mockScope.boundsModel.end).toEqual(bounds.end);
|
||||
});
|
||||
|
||||
it("responds to a change in 'follow' state of the time conductor", function () {
|
||||
var followListener = getListener("follow");
|
||||
expect(followListener).toBeDefined();
|
||||
|
||||
followListener(true);
|
||||
expect(mockScope.followMode).toEqual(true);
|
||||
|
||||
followListener(false);
|
||||
expect(mockScope.followMode).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when user makes changes from UI", function () {
|
||||
|
@ -22,9 +22,10 @@
|
||||
|
||||
define(
|
||||
[
|
||||
'EventEmitter',
|
||||
'./TimeConductorMode'
|
||||
],
|
||||
function (TimeConductorMode) {
|
||||
function (EventEmitter, TimeConductorMode) {
|
||||
|
||||
/**
|
||||
* A class representing the state of the time conductor view. This
|
||||
@ -36,6 +37,9 @@ define(
|
||||
* @constructor
|
||||
*/
|
||||
function TimeConductorViewService(conductor, timeSystems) {
|
||||
|
||||
EventEmitter.call(this);
|
||||
|
||||
this.systems = timeSystems.map(function (timeSystemConstructor) {
|
||||
return timeSystemConstructor();
|
||||
});
|
||||
@ -97,6 +101,8 @@ define(
|
||||
}
|
||||
}
|
||||
|
||||
TimeConductorViewService.prototype = Object.create(EventEmitter.prototype);
|
||||
|
||||
/**
|
||||
* Getter/Setter for the Time Conductor Mode. Modes determine the
|
||||
* behavior of the time conductor, especially with regards to the
|
||||
|
@ -64,13 +64,17 @@ define([
|
||||
return this.sources;
|
||||
};
|
||||
|
||||
UTCTimeSystem.prototype.defaults = function (key) {
|
||||
UTCTimeSystem.prototype.defaults = function () {
|
||||
var now = Math.ceil(Date.now() / 1000) * 1000;
|
||||
var ONE_MINUTE = 60 * 1 * 1000;
|
||||
var FIFTY_YEARS = 50 * 365 * 24 * 60 * 60 * 1000;
|
||||
|
||||
return {
|
||||
key: 'utc-default',
|
||||
name: 'UTC time system defaults',
|
||||
deltas: {start: FIFTEEN_MINUTES, end: 0},
|
||||
bounds: {start: now - FIFTEEN_MINUTES, end: now}
|
||||
bounds: {start: now - FIFTEEN_MINUTES, end: now},
|
||||
zoom: {min: FIFTY_YEARS, max: ONE_MINUTE}
|
||||
};
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user