mirror of
https://github.com/nasa/openmct.git
synced 2025-06-15 05:38:12 +00:00
Reimplementation of time conductor in Vue.js (#2173)
* Refactoring conductor to use Vue * Conditionally render Time Conductor * Created ConductorOptions SFC * Copyright notice examples * Added time system selector component * Use capture for event bubbling with popups * Added Conductor Axis. Simplified Axis formatting and removed scale formatting from formatters. Added date picker. * Sync axis on zoom * Fixed sync between conductor and axis * Changed 'InspectorComponent' to 'ConductorComponent' in Layout. Fixed race condition with panning and RequestAnimationFrame * Renamed properties in conductor to clarify their role. Fixed some bugs * Removed old conductor * Fix layout issue with legacy Conductor markup * Added missing copyright notice
This commit is contained in:
committed by
Deep Tailor
parent
944505a5f1
commit
987740c649
@ -52,70 +52,14 @@ define([
|
|||||||
this.key = "utc";
|
this.key = "utc";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an appropriate time format based on the provided value and
|
|
||||||
* the threshold required.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
function getScaledFormat(d) {
|
|
||||||
var momentified = 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
|
|
||||||
*/
|
|
||||||
var format = [
|
|
||||||
[".SSS", function (m) {
|
|
||||||
return m.milliseconds();
|
|
||||||
}],
|
|
||||||
[":ss", function (m) {
|
|
||||||
return m.seconds();
|
|
||||||
}],
|
|
||||||
["HH:mm", function (m) {
|
|
||||||
return m.minutes();
|
|
||||||
}],
|
|
||||||
["HH", 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](momentified);
|
|
||||||
})[0][0];
|
|
||||||
|
|
||||||
if (format !== undefined) {
|
|
||||||
return moment.utc(d).format(format);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {number} value The value to format.
|
* @param {number} value The value to format.
|
||||||
* @param {number} [minValue] Contextual information for scaled formatting used in linear scales such as conductor
|
|
||||||
* and plot axes. Specifies the smallest number on the scale.
|
|
||||||
* @param {number} [maxValue] Contextual information for scaled formatting used in linear scales such as conductor
|
|
||||||
* and plot axes. Specifies the largest number on the scale
|
|
||||||
* @param {number} [count] Contextual information for scaled formatting used in linear scales such as conductor
|
|
||||||
* and plot axes. The number of labels on the scale.
|
|
||||||
* @returns {string} the formatted date(s). If multiple values were requested, then an array of
|
* @returns {string} the formatted date(s). If multiple values were requested, then an array of
|
||||||
* formatted values will be returned. Where a value could not be formatted, `undefined` will be returned at its position
|
* formatted values will be returned. Where a value could not be formatted, `undefined` will be returned at its position
|
||||||
* in the array.
|
* in the array.
|
||||||
*/
|
*/
|
||||||
UTCTimeFormat.prototype.format = function (value) {
|
UTCTimeFormat.prototype.format = function (value) {
|
||||||
if (arguments.length > 1) {
|
if (value !== undefined) {
|
||||||
return getScaledFormat(value);
|
|
||||||
} else if (value !== undefined) {
|
|
||||||
return moment.utc(value).format(DATE_FORMAT) + "Z";
|
return moment.utc(value).format(DATE_FORMAT) + "Z";
|
||||||
} else {
|
} else {
|
||||||
return value;
|
return value;
|
||||||
|
@ -1,62 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* 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/UTCTimeFormat",
|
|
||||||
"moment"
|
|
||||||
], function (
|
|
||||||
UTCTimeFormat,
|
|
||||||
moment
|
|
||||||
) {
|
|
||||||
describe("The UTCTimeFormat class", function () {
|
|
||||||
var format;
|
|
||||||
var scale;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
format = new UTCTimeFormat();
|
|
||||||
scale = {min: 0, max: 0};
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Provides an appropriately scaled time format based on the input" +
|
|
||||||
" time", function () {
|
|
||||||
var TWO_HUNDRED_MS = 200;
|
|
||||||
var THREE_SECONDS = 3000;
|
|
||||||
var FIVE_MINUTES = 5 * 60 * 1000;
|
|
||||||
var ONE_HOUR_TWENTY_MINS = (1 * 60 * 60 * 1000) + (20 * 60 * 1000);
|
|
||||||
var TEN_HOURS = (10 * 60 * 60 * 1000);
|
|
||||||
|
|
||||||
var JUNE_THIRD = moment.utc("2016-06-03", "YYYY-MM-DD");
|
|
||||||
var APRIL = moment.utc("2016-04", "YYYY-MM");
|
|
||||||
var TWENTY_SIXTEEN = moment.utc("2016", "YYYY");
|
|
||||||
|
|
||||||
expect(format.format(TWO_HUNDRED_MS, scale)).toBe(".200");
|
|
||||||
expect(format.format(THREE_SECONDS, scale)).toBe(":03");
|
|
||||||
expect(format.format(FIVE_MINUTES, scale)).toBe("00:05");
|
|
||||||
expect(format.format(ONE_HOUR_TWENTY_MINS, scale)).toBe("01:20");
|
|
||||||
expect(format.format(TEN_HOURS, scale)).toBe("10");
|
|
||||||
|
|
||||||
expect(format.format(JUNE_THIRD, scale)).toBe("Fri 03");
|
|
||||||
expect(format.format(APRIL, scale)).toBe("April");
|
|
||||||
expect(format.format(TWENTY_SIXTEEN, scale)).toBe("2016");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,43 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* 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/ConductorRepresenter",
|
|
||||||
'legacyRegistry'
|
|
||||||
], function (
|
|
||||||
ConductorRepresenter,
|
|
||||||
legacyRegistry
|
|
||||||
) {
|
|
||||||
|
|
||||||
legacyRegistry.register("platform/features/conductor/compatibility", {
|
|
||||||
"extensions": {
|
|
||||||
"representers": [
|
|
||||||
{
|
|
||||||
"implementation": ConductorRepresenter,
|
|
||||||
"depends": [
|
|
||||||
"openmct"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,95 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* 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 () {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Representer that provides a compatibility layer between the new
|
|
||||||
* time conductor and existing representations / views. Listens to
|
|
||||||
* the v2 time conductor API and generates v1 style events using the
|
|
||||||
* Angular event bus. This is transitional code code and will be
|
|
||||||
* removed.
|
|
||||||
*
|
|
||||||
* Deprecated immediately as this is temporary code
|
|
||||||
*
|
|
||||||
* @deprecated
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function ConductorRepresenter(
|
|
||||||
openmct,
|
|
||||||
scope,
|
|
||||||
element
|
|
||||||
) {
|
|
||||||
this.timeAPI = openmct.time;
|
|
||||||
this.scope = scope;
|
|
||||||
this.element = element;
|
|
||||||
|
|
||||||
this.boundsListener = this.boundsListener.bind(this);
|
|
||||||
this.timeSystemListener = this.timeSystemListener.bind(this);
|
|
||||||
this.followListener = this.followListener.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
ConductorRepresenter.prototype.boundsListener = function (bounds) {
|
|
||||||
var timeSystem = this.timeAPI.timeSystem();
|
|
||||||
this.scope.$broadcast('telemetry:display:bounds', {
|
|
||||||
start: bounds.start,
|
|
||||||
end: bounds.end,
|
|
||||||
domain: timeSystem.key
|
|
||||||
}, this.timeAPI.clock() !== undefined);
|
|
||||||
};
|
|
||||||
|
|
||||||
ConductorRepresenter.prototype.timeSystemListener = function (timeSystem) {
|
|
||||||
var bounds = this.timeAPI.bounds();
|
|
||||||
this.scope.$broadcast('telemetry:display:bounds', {
|
|
||||||
start: bounds.start,
|
|
||||||
end: bounds.end,
|
|
||||||
domain: timeSystem.key
|
|
||||||
}, this.timeAPI.clock() !== undefined);
|
|
||||||
};
|
|
||||||
|
|
||||||
ConductorRepresenter.prototype.followListener = function () {
|
|
||||||
this.boundsListener(this.timeAPI.bounds());
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handle a specific representation of a specific domain object
|
|
||||||
ConductorRepresenter.prototype.represent = function represent(representation) {
|
|
||||||
if (representation.key === 'browse-object') {
|
|
||||||
this.destroy();
|
|
||||||
|
|
||||||
this.timeAPI.on("bounds", this.boundsListener);
|
|
||||||
this.timeAPI.on("timeSystem", this.timeSystemListener);
|
|
||||||
this.timeAPI.on("follow", this.followListener);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ConductorRepresenter.prototype.destroy = function destroy() {
|
|
||||||
this.timeAPI.off("bounds", this.boundsListener);
|
|
||||||
this.timeAPI.off("timeSystem", this.timeSystemListener);
|
|
||||||
this.timeAPI.off("follow", this.followListener);
|
|
||||||
};
|
|
||||||
|
|
||||||
return ConductorRepresenter;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
@ -1,148 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* 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/ui/TimeConductorController",
|
|
||||||
"./src/ui/ConductorAxisController",
|
|
||||||
"./src/ui/ConductorTOIController",
|
|
||||||
"./src/ui/ConductorTOIDirective",
|
|
||||||
"./src/ui/TimeOfInterestController",
|
|
||||||
"./src/ui/ConductorAxisDirective",
|
|
||||||
"./src/ui/NumberFormat",
|
|
||||||
"./src/ui/StringFormat",
|
|
||||||
"./res/templates/time-conductor.html",
|
|
||||||
"./res/templates/mode-selector/mode-selector.html",
|
|
||||||
"./res/templates/mode-selector/mode-menu.html",
|
|
||||||
"./res/templates/time-of-interest.html",
|
|
||||||
"legacyRegistry"
|
|
||||||
], function (
|
|
||||||
TimeConductorController,
|
|
||||||
ConductorAxisController,
|
|
||||||
ConductorTOIController,
|
|
||||||
ConductorTOIDirective,
|
|
||||||
TimeOfInterestController,
|
|
||||||
ConductorAxisDirective,
|
|
||||||
NumberFormat,
|
|
||||||
StringFormat,
|
|
||||||
timeConductorTemplate,
|
|
||||||
modeSelectorTemplate,
|
|
||||||
modeMenuTemplate,
|
|
||||||
timeOfInterest,
|
|
||||||
legacyRegistry
|
|
||||||
) {
|
|
||||||
|
|
||||||
legacyRegistry.register("platform/features/conductor/core", {
|
|
||||||
"extensions": {
|
|
||||||
"controllers": [
|
|
||||||
{
|
|
||||||
"key": "TimeConductorController",
|
|
||||||
"implementation": TimeConductorController,
|
|
||||||
"depends": [
|
|
||||||
"$scope",
|
|
||||||
"$window",
|
|
||||||
"openmct",
|
|
||||||
"formatService",
|
|
||||||
"CONDUCTOR_CONFIG"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "ConductorTOIController",
|
|
||||||
"implementation": ConductorTOIController,
|
|
||||||
"depends": [
|
|
||||||
"$scope",
|
|
||||||
"openmct",
|
|
||||||
"formatService"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "TimeOfInterestController",
|
|
||||||
"implementation": TimeOfInterestController,
|
|
||||||
"depends": [
|
|
||||||
"$scope",
|
|
||||||
"openmct",
|
|
||||||
"formatService"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"directives": [
|
|
||||||
{
|
|
||||||
"key": "conductorAxis",
|
|
||||||
"implementation": ConductorAxisDirective,
|
|
||||||
"depends": [
|
|
||||||
"openmct",
|
|
||||||
"formatService"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "conductorToi",
|
|
||||||
"implementation": ConductorTOIDirective
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"templates": [
|
|
||||||
{
|
|
||||||
"key": "conductor",
|
|
||||||
"template": timeConductorTemplate
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "mode-menu",
|
|
||||||
"template": modeMenuTemplate
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "mode-selector",
|
|
||||||
"template": modeSelectorTemplate
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "time-of-interest",
|
|
||||||
"template": timeOfInterest
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"representations": [
|
|
||||||
{
|
|
||||||
"key": "time-conductor",
|
|
||||||
"template": timeConductorTemplate
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"licenses": [
|
|
||||||
{
|
|
||||||
"name": "D3: Data-Driven Documents",
|
|
||||||
"version": "4.1.0",
|
|
||||||
"author": "Mike Bostock",
|
|
||||||
"description": "D3 (or D3.js) is a JavaScript library for visualizing data using web standards. D3 helps you bring data to life using SVG, Canvas and HTML. D3 combines powerful visualization and interaction techniques with a data-driven approach to DOM manipulation, giving you the full capabilities of modern browsers and the freedom to design the right visual interface for your data.",
|
|
||||||
"website": "https://d3js.org/",
|
|
||||||
"copyright": "Copyright 2010-2016 Mike Bostock",
|
|
||||||
"license": "BSD-3-Clause",
|
|
||||||
"link": "https://github.com/d3/d3/blob/master/LICENSE"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"formats": [
|
|
||||||
{
|
|
||||||
"key": "number",
|
|
||||||
"implementation": NumberFormat
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "string",
|
|
||||||
"implementation": StringFormat
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,46 +0,0 @@
|
|||||||
<!--
|
|
||||||
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.
|
|
||||||
-->
|
|
||||||
<div class="w-menu">
|
|
||||||
<div class="col menu-items">
|
|
||||||
<ul>
|
|
||||||
<li ng-repeat="metadata in ngModel.options"
|
|
||||||
ng-click="ngModel.select(metadata)">
|
|
||||||
<a ng-mouseover="ngModel.activeMetadata = metadata"
|
|
||||||
ng-mouseleave="ngModel.activeMetadata = undefined"
|
|
||||||
class="menu-item-a {{metadata.cssClass}}">
|
|
||||||
{{metadata.name}}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="col menu-item-description">
|
|
||||||
<div class="desc-area ui-symbol icon type-icon {{ngModel.activeMetadata.cssClass}}"></div>
|
|
||||||
<div class="w-title-desc">
|
|
||||||
<div class="desc-area title">
|
|
||||||
{{ngModel.activeMetadata.name}}
|
|
||||||
</div>
|
|
||||||
<div class="desc-area description">
|
|
||||||
{{ngModel.activeMetadata.description}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -1,33 +0,0 @@
|
|||||||
<!--
|
|
||||||
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.
|
|
||||||
-->
|
|
||||||
<span ng-controller="ClickAwayController as modeController">
|
|
||||||
<div class="s-menu-button"
|
|
||||||
ng-click="modeController.toggle()">
|
|
||||||
<span class="title-label">{{ngModel.selected.name}}</span>
|
|
||||||
</div>
|
|
||||||
<div class="menu super-menu mini l-mode-selector-menu"
|
|
||||||
ng-show="modeController.isActive()">
|
|
||||||
<mct-include key="'mode-menu'"
|
|
||||||
ng-model="ngModel">
|
|
||||||
</mct-include>
|
|
||||||
</div>
|
|
||||||
</span>
|
|
@ -1,117 +0,0 @@
|
|||||||
<!-- Parent holder for time conductor. follow-mode | fixed-mode -->
|
|
||||||
<div ng-controller="TimeConductorController as tcController"
|
|
||||||
class="holder grows flex-elem l-flex-row l-time-conductor {{tcController.isFixed ? 'fixed-mode' : 'realtime-mode'}} {{timeSystemModel.selected.metadata.key}}-time-system"
|
|
||||||
ng-class="{'status-panning': tcController.panning}">
|
|
||||||
<div class="flex-elem holder time-conductor-icon">
|
|
||||||
<div class="hand-little"></div>
|
|
||||||
<div class="hand-big"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex-elem holder grows l-flex-col l-time-conductor-inner">
|
|
||||||
<!-- Holds inputs and ticks -->
|
|
||||||
<div class="l-time-conductor-inputs-and-ticks l-row-elem flex-elem no-margin">
|
|
||||||
<form class="l-time-conductor-inputs-holder"
|
|
||||||
ng-submit="tcController.isFixed ? tcController.setBoundsFromView(boundsModel) : tcController.setOffsetsFromView(boundsModel)">
|
|
||||||
<span class="l-time-range-w start-w">
|
|
||||||
<span class="l-time-conductor-inputs">
|
|
||||||
<span class="l-time-range-input-w start-date">
|
|
||||||
<span class="title"></span>
|
|
||||||
<mct-control key="'datetime-field'"
|
|
||||||
structure="{
|
|
||||||
format: timeSystemModel.format,
|
|
||||||
validate: tcController.validation.validateStart
|
|
||||||
}"
|
|
||||||
ng-model="boundsModel"
|
|
||||||
ng-blur="tcController.setBoundsFromView(boundsModel)"
|
|
||||||
field="'start'"
|
|
||||||
class="time-range-input">
|
|
||||||
</mct-control>
|
|
||||||
</span>
|
|
||||||
<span class="l-time-range-input-w time-delta start-delta"
|
|
||||||
ng-class="{'hide':tcController.isFixed}">
|
|
||||||
-
|
|
||||||
<mct-control key="'datetime-field'"
|
|
||||||
structure="{
|
|
||||||
format: timeSystemModel.durationFormat,
|
|
||||||
validate: tcController.validation.validateStartOffset
|
|
||||||
}"
|
|
||||||
ng-model="boundsModel"
|
|
||||||
ng-blur="tcController.setOffsetsFromView(boundsModel)"
|
|
||||||
field="'startOffset'"
|
|
||||||
class="s-input-inline hrs-min-input">
|
|
||||||
</mct-control>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
<span class="l-time-range-w end-w">
|
|
||||||
<span class="l-time-conductor-inputs">
|
|
||||||
<span class="l-time-range-input-w end-date"
|
|
||||||
ng-controller="ToggleController as t2">
|
|
||||||
<span class="title"></span>
|
|
||||||
<mct-control key="'datetime-field'"
|
|
||||||
structure="{
|
|
||||||
format: timeSystemModel.format,
|
|
||||||
validate: tcController.validation.validateEnd
|
|
||||||
}"
|
|
||||||
ng-model="boundsModel"
|
|
||||||
ng-blur="tcController.setBoundsFromView(boundsModel)"
|
|
||||||
ng-disabled="!tcController.isFixed"
|
|
||||||
field="'end'"
|
|
||||||
class="time-range-input">
|
|
||||||
</mct-control>
|
|
||||||
</span>
|
|
||||||
<span class="l-time-range-input-w time-delta end-delta"
|
|
||||||
ng-class="{'hide': tcController.isFixed}">
|
|
||||||
+
|
|
||||||
<mct-control key="'datetime-field'"
|
|
||||||
structure="{
|
|
||||||
format: timeSystemModel.durationFormat,
|
|
||||||
validate: tcController.validation.validateEndOffset
|
|
||||||
}"
|
|
||||||
ng-model="boundsModel"
|
|
||||||
ng-blur="tcController.setOffsetsFromView(boundsModel)"
|
|
||||||
field="'endOffset'"
|
|
||||||
class="s-input-inline hrs-min-input">
|
|
||||||
</mct-control>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<input type="submit" class="invisible">
|
|
||||||
</form>
|
|
||||||
<conductor-axis class="mobile-hide" view-service="tcController.conductorViewService"></conductor-axis>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Holds time system and session selectors, and zoom control -->
|
|
||||||
<div class="l-time-conductor-controls l-row-elem l-flex-row flex-elem">
|
|
||||||
<mct-include
|
|
||||||
key="'mode-selector'"
|
|
||||||
ng-model="tcController.menu"
|
|
||||||
class="holder flex-elem menus-up mode-selector">
|
|
||||||
</mct-include>
|
|
||||||
<mct-control
|
|
||||||
key="'menu-button'"
|
|
||||||
class="holder flex-elem menus-up time-system"
|
|
||||||
structure="{
|
|
||||||
text: timeSystemModel.selected.name,
|
|
||||||
click: tcController.setTimeSystemFromView,
|
|
||||||
options: tcController.timeSystemsForClocks[tcController.menu.selected.key]
|
|
||||||
}">
|
|
||||||
</mct-control>
|
|
||||||
<!-- Zoom control -->
|
|
||||||
<div ng-if="tcController.zoom"
|
|
||||||
class="l-time-conductor-zoom-w grows flex-elem l-flex-row">
|
|
||||||
{{currentZoom}}
|
|
||||||
<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="tcController.currentZoom"
|
|
||||||
ng-mouseUp="tcController.onZoomStop(tcController.currentZoom)"
|
|
||||||
ng-change="tcController.onZoom(tcController.currentZoom)"
|
|
||||||
min="0.01"
|
|
||||||
step="0.01"
|
|
||||||
max="0.99" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -1,12 +0,0 @@
|
|||||||
<div class="abs angular-controller"
|
|
||||||
ng-controller="TimeOfInterestController as toi">
|
|
||||||
<div class="l-flex-row l-toi">
|
|
||||||
<span class="flex-elem l-flex-row l-toi-buttons">
|
|
||||||
<a class="flex-elem t-button-resync icon-button" title="Re-sync Time of Interest"
|
|
||||||
ng-click="toi.resync()"></a>
|
|
||||||
<a class="flex-elem t-button-unpin icon-button" title="Unset Time of Interest"
|
|
||||||
ng-click="toi.dismiss()"></a>
|
|
||||||
</span>
|
|
||||||
<span class="flex-elem l-toi-val">{{toi.toiText}}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -1,236 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* 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-selection",
|
|
||||||
"d3-scale",
|
|
||||||
"d3-axis"
|
|
||||||
],
|
|
||||||
function (d3Selection, d3Scale, d3Axis) {
|
|
||||||
var PADDING = 1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Controller that renders a horizontal time scale spanning the current bounds defined in the time conductor.
|
|
||||||
* Used by the mct-conductor-axis directive
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function ConductorAxisController(openmct, formatService, scope, element) {
|
|
||||||
// Dependencies
|
|
||||||
this.formatService = formatService;
|
|
||||||
this.timeAPI = openmct.time;
|
|
||||||
|
|
||||||
this.scope = scope;
|
|
||||||
|
|
||||||
this.bounds = this.timeAPI.bounds();
|
|
||||||
|
|
||||||
//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));
|
|
||||||
|
|
||||||
this.initialize(element);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
ConductorAxisController.prototype.destroy = function () {
|
|
||||||
this.timeAPI.off('timeSystem', this.changeTimeSystem);
|
|
||||||
this.timeAPI.off('bounds', this.changeBounds);
|
|
||||||
this.viewService.off("zoom", this.onZoom);
|
|
||||||
this.viewService.off("zoom-stop", this.onZoomStop);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
ConductorAxisController.prototype.initialize = function (element) {
|
|
||||||
this.target = element[0].firstChild;
|
|
||||||
var height = this.target.offsetHeight;
|
|
||||||
var vis = d3Selection.select(this.target)
|
|
||||||
.append("svg:svg")
|
|
||||||
.attr("width", "100%")
|
|
||||||
.attr("height", height);
|
|
||||||
|
|
||||||
this.xAxis = d3Axis.axisTop();
|
|
||||||
|
|
||||||
// draw x axis with labels. CSS is used to position them.
|
|
||||||
this.axisElement = vis.append("g");
|
|
||||||
|
|
||||||
if (this.timeAPI.timeSystem() !== undefined) {
|
|
||||||
this.changeTimeSystem(this.timeAPI.timeSystem());
|
|
||||||
this.setScale();
|
|
||||||
}
|
|
||||||
|
|
||||||
//Respond to changes in conductor
|
|
||||||
this.timeAPI.on("timeSystem", this.changeTimeSystem);
|
|
||||||
this.timeAPI.on("bounds", this.changeBounds);
|
|
||||||
|
|
||||||
this.scope.$on("$destroy", this.destroy);
|
|
||||||
|
|
||||||
this.viewService.on("zoom", this.onZoom);
|
|
||||||
this.viewService.on("zoom-stop", this.onZoomStop);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
ConductorAxisController.prototype.changeBounds = function (bounds) {
|
|
||||||
this.bounds = bounds;
|
|
||||||
if (!this.zooming) {
|
|
||||||
this.setScale();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the scale of the axis, based on current conductor bounds.
|
|
||||||
*/
|
|
||||||
ConductorAxisController.prototype.setScale = function () {
|
|
||||||
var width = this.target.offsetWidth;
|
|
||||||
var timeSystem = this.timeAPI.timeSystem();
|
|
||||||
var bounds = this.bounds;
|
|
||||||
|
|
||||||
if (timeSystem.isUTCBased) {
|
|
||||||
this.xScale = this.xScale || d3Scale.scaleUtc();
|
|
||||||
this.xScale.domain([new Date(bounds.start), new Date(bounds.end)]);
|
|
||||||
} else {
|
|
||||||
this.xScale = this.xScale || d3Scale.scaleLinear();
|
|
||||||
this.xScale.domain([bounds.start, bounds.end]);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.xAxis.scale(this.xScale);
|
|
||||||
|
|
||||||
this.xScale.range([PADDING, width - PADDING * 2]);
|
|
||||||
this.axisElement.call(this.xAxis);
|
|
||||||
|
|
||||||
this.msPerPixel = (bounds.end - bounds.start) / width;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When the time system changes, update the scale and formatter used for showing times.
|
|
||||||
* @param timeSystem
|
|
||||||
*/
|
|
||||||
ConductorAxisController.prototype.changeTimeSystem = function (timeSystem) {
|
|
||||||
var key = timeSystem.timeFormat;
|
|
||||||
if (key !== undefined) {
|
|
||||||
var format = this.formatService.getFormat(key);
|
|
||||||
var bounds = this.timeAPI.bounds();
|
|
||||||
|
|
||||||
//The D3 scale used depends on the type of time system as d3
|
|
||||||
// supports UTC out of the box.
|
|
||||||
if (timeSystem.isUTCBased) {
|
|
||||||
this.xScale = d3Scale.scaleUtc();
|
|
||||||
} else {
|
|
||||||
this.xScale = d3Scale.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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The user has stopped panning the time conductor scale element.
|
|
||||||
* @event panStop
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Called on release of mouse button after dragging the scale left or right.
|
|
||||||
* @fires platform.features.conductor.ConductorAxisController~panStop
|
|
||||||
*/
|
|
||||||
ConductorAxisController.prototype.panStop = function () {
|
|
||||||
//resync view bounds with time conductor bounds
|
|
||||||
this.viewService.emit("pan-stop");
|
|
||||||
this.timeAPI.bounds(this.bounds);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rescales the axis when the user zooms. Although zoom ultimately results in a bounds change once the user
|
|
||||||
* releases the zoom slider, dragging the slider will not immediately change the conductor bounds. It will
|
|
||||||
* however immediately update the scale and the bounds displayed in the UI.
|
|
||||||
* @private
|
|
||||||
* @param {ZoomLevel}
|
|
||||||
*/
|
|
||||||
ConductorAxisController.prototype.onZoom = function (zoom) {
|
|
||||||
this.zooming = true;
|
|
||||||
|
|
||||||
this.bounds = zoom.bounds;
|
|
||||||
this.setScale();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
ConductorAxisController.prototype.onZoomStop = function (zoom) {
|
|
||||||
this.zooming = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @event platform.features.conductor.ConductorAxisController~pan
|
|
||||||
* Fired when the time conductor is panned
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Initiate panning via a click + drag gesture on the time conductor
|
|
||||||
* scale. Panning triggers a "pan" event
|
|
||||||
* @param {number} delta the offset from the original click event
|
|
||||||
* @see TimeConductorViewService#
|
|
||||||
* @fires platform.features.conductor.ConductorAxisController~pan
|
|
||||||
*/
|
|
||||||
ConductorAxisController.prototype.pan = function (delta) {
|
|
||||||
if (this.timeAPI.clock() === undefined) {
|
|
||||||
var deltaInMs = delta[0] * this.msPerPixel;
|
|
||||||
var bounds = this.timeAPI.bounds();
|
|
||||||
var start = Math.floor((bounds.start - deltaInMs) / 1000) * 1000;
|
|
||||||
var end = Math.floor((bounds.end - deltaInMs) / 1000) * 1000;
|
|
||||||
this.bounds = {
|
|
||||||
start: start,
|
|
||||||
end: end
|
|
||||||
};
|
|
||||||
this.setScale();
|
|
||||||
this.viewService.emit("pan", this.bounds);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoked on element resize. Will rebuild the scale based on the new dimensions of the element.
|
|
||||||
*/
|
|
||||||
ConductorAxisController.prototype.resize = function () {
|
|
||||||
this.setScale();
|
|
||||||
};
|
|
||||||
|
|
||||||
return ConductorAxisController;
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,169 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* 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([
|
|
||||||
'./ConductorAxisController',
|
|
||||||
'zepto',
|
|
||||||
'd3-selection',
|
|
||||||
'd3-scale'
|
|
||||||
], function (
|
|
||||||
ConductorAxisController,
|
|
||||||
$,
|
|
||||||
d3Selection,
|
|
||||||
d3Scale
|
|
||||||
) {
|
|
||||||
describe("The ConductorAxisController", function () {
|
|
||||||
var controller,
|
|
||||||
mockConductor,
|
|
||||||
mockConductorViewService,
|
|
||||||
mockFormatService,
|
|
||||||
mockScope,
|
|
||||||
mockBounds,
|
|
||||||
element,
|
|
||||||
mockTimeSystem,
|
|
||||||
mockFormat;
|
|
||||||
|
|
||||||
function getCallback(target, name) {
|
|
||||||
return target.calls.all().filter(function (call) {
|
|
||||||
return call.args[0] === name;
|
|
||||||
})[0].args[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
mockScope = jasmine.createSpyObj("scope", [
|
|
||||||
"$on"
|
|
||||||
]);
|
|
||||||
|
|
||||||
//Add some HTML elements
|
|
||||||
mockBounds = {
|
|
||||||
start: 100,
|
|
||||||
end: 200
|
|
||||||
};
|
|
||||||
mockConductor = jasmine.createSpyObj("conductor", [
|
|
||||||
"timeSystem",
|
|
||||||
"bounds",
|
|
||||||
"on",
|
|
||||||
"off",
|
|
||||||
"clock"
|
|
||||||
]);
|
|
||||||
mockConductor.bounds.and.returnValue(mockBounds);
|
|
||||||
|
|
||||||
mockFormatService = jasmine.createSpyObj("formatService", [
|
|
||||||
"getFormat"
|
|
||||||
]);
|
|
||||||
|
|
||||||
mockConductorViewService = jasmine.createSpyObj("conductorViewService", [
|
|
||||||
"on",
|
|
||||||
"off",
|
|
||||||
"emit"
|
|
||||||
]);
|
|
||||||
|
|
||||||
spyOn(d3Scale, 'scaleUtc').and.callThrough();
|
|
||||||
spyOn(d3Scale, 'scaleLinear').and.callThrough();
|
|
||||||
|
|
||||||
element = $('<div style="width: 100px;"><div style="width: 100%;"></div></div>');
|
|
||||||
$(document).find('body').append(element);
|
|
||||||
ConductorAxisController.prototype.viewService = mockConductorViewService;
|
|
||||||
controller = new ConductorAxisController({time: mockConductor}, mockFormatService, mockScope, element);
|
|
||||||
|
|
||||||
mockTimeSystem = {};
|
|
||||||
mockFormat = jasmine.createSpyObj("format", [
|
|
||||||
"format"
|
|
||||||
]);
|
|
||||||
|
|
||||||
mockTimeSystem.timeFormat = "mockFormat";
|
|
||||||
mockFormatService.getFormat.and.returnValue(mockFormat);
|
|
||||||
mockConductor.timeSystem.and.returnValue(mockTimeSystem);
|
|
||||||
mockTimeSystem.isUTCBased = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
it("listens for changes to time system and bounds", function () {
|
|
||||||
expect(mockConductor.on).toHaveBeenCalledWith("timeSystem", controller.changeTimeSystem);
|
|
||||||
expect(mockConductor.on).toHaveBeenCalledWith("bounds", controller.changeBounds);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("on scope destruction, deregisters listeners", function () {
|
|
||||||
expect(mockScope.$on).toHaveBeenCalledWith("$destroy", controller.destroy);
|
|
||||||
controller.destroy();
|
|
||||||
expect(mockConductor.off).toHaveBeenCalledWith("timeSystem", controller.changeTimeSystem);
|
|
||||||
expect(mockConductor.off).toHaveBeenCalledWith("bounds", controller.changeBounds);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when the time system changes", function () {
|
|
||||||
it("uses a UTC scale for UTC time systems", function () {
|
|
||||||
mockTimeSystem.isUTCBased = true;
|
|
||||||
controller.changeTimeSystem(mockTimeSystem);
|
|
||||||
|
|
||||||
expect(d3Scale.scaleUtc).toHaveBeenCalled();
|
|
||||||
expect(d3Scale.scaleLinear).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("uses a linear scale for non-UTC time systems", function () {
|
|
||||||
mockTimeSystem.isUTCBased = false;
|
|
||||||
controller.changeTimeSystem(mockTimeSystem);
|
|
||||||
expect(d3Scale.scaleLinear).toHaveBeenCalled();
|
|
||||||
expect(d3Scale.scaleUtc).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("sets axis domain to time conductor bounds", function () {
|
|
||||||
mockTimeSystem.isUTCBased = false;
|
|
||||||
controller.setScale();
|
|
||||||
expect(controller.xScale.domain()).toEqual([mockBounds.start, mockBounds.end]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("uses the format specified by the time system to format tick" +
|
|
||||||
" labels", function () {
|
|
||||||
controller.changeTimeSystem(mockTimeSystem);
|
|
||||||
expect(mockFormat.format).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('responds to zoom events', function () {
|
|
||||||
expect(mockConductorViewService.on).toHaveBeenCalledWith("zoom", controller.onZoom);
|
|
||||||
var cb = getCallback(mockConductorViewService.on, "zoom");
|
|
||||||
spyOn(controller, 'setScale').and.callThrough();
|
|
||||||
cb({bounds: {start: 0, end: 100}});
|
|
||||||
expect(controller.setScale).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('adjusts scale on pan', function () {
|
|
||||||
spyOn(controller, 'setScale').and.callThrough();
|
|
||||||
controller.pan(100);
|
|
||||||
expect(controller.setScale).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('emits event on pan', function () {
|
|
||||||
spyOn(controller, 'setScale').and.callThrough();
|
|
||||||
controller.pan(100);
|
|
||||||
expect(mockConductorViewService.emit).toHaveBeenCalledWith("pan", jasmine.any(Object));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('cleans up listeners on destruction', function () {
|
|
||||||
controller.destroy();
|
|
||||||
expect(mockConductor.off).toHaveBeenCalledWith("bounds", controller.changeBounds);
|
|
||||||
expect(mockConductor.off).toHaveBeenCalledWith("timeSystem", controller.changeTimeSystem);
|
|
||||||
|
|
||||||
expect(mockConductorViewService.off).toHaveBeenCalledWith("zoom", controller.onZoom);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,56 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* 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(['./ConductorAxisController'], function (ConductorAxisController) {
|
|
||||||
function ConductorAxisDirective() {
|
|
||||||
/**
|
|
||||||
* The mct-conductor-axis renders a horizontal axis with regular
|
|
||||||
* labelled 'ticks'. It requires 'start' and 'end' integer values to
|
|
||||||
* be specified as attributes.
|
|
||||||
*/
|
|
||||||
return {
|
|
||||||
controller: [
|
|
||||||
'openmct',
|
|
||||||
'formatService',
|
|
||||||
'$scope',
|
|
||||||
'$element',
|
|
||||||
ConductorAxisController
|
|
||||||
],
|
|
||||||
controllerAs: 'axis',
|
|
||||||
scope: {
|
|
||||||
viewService: "="
|
|
||||||
},
|
|
||||||
bindToController: true,
|
|
||||||
|
|
||||||
restrict: 'E',
|
|
||||||
priority: 1000,
|
|
||||||
|
|
||||||
template: '<div class="l-axis-holder" ' +
|
|
||||||
' mct-drag-down="axis.panStart()"' +
|
|
||||||
' mct-drag-up="axis.panStop(delta)"' +
|
|
||||||
' mct-drag="axis.pan(delta)"' +
|
|
||||||
' mct-resize="axis.resize()"></div>'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return ConductorAxisDirective;
|
|
||||||
});
|
|
@ -1,123 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
["zepto"],
|
|
||||||
function ($) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Controller for the Time of Interest indicator in the conductor itself. Sets the horizontal position of the
|
|
||||||
* TOI indicator based on the current value of the TOI, and the width of the TOI conductor.
|
|
||||||
* @memberof platform.features.conductor
|
|
||||||
*/
|
|
||||||
function ConductorTOIController($scope, openmct) {
|
|
||||||
this.timeAPI = openmct.time;
|
|
||||||
|
|
||||||
//Bind all class functions to 'this'
|
|
||||||
Object.keys(ConductorTOIController.prototype).filter(function (key) {
|
|
||||||
return typeof ConductorTOIController.prototype[key] === 'function';
|
|
||||||
}).forEach(function (key) {
|
|
||||||
this[key] = ConductorTOIController.prototype[key].bind(this);
|
|
||||||
}.bind(this));
|
|
||||||
|
|
||||||
this.timeAPI.on('timeOfInterest', this.changeTimeOfInterest);
|
|
||||||
this.viewService.on('zoom', this.setOffsetFromZoom);
|
|
||||||
this.viewService.on('pan', this.setOffsetFromBounds);
|
|
||||||
|
|
||||||
var timeOfInterest = this.timeAPI.timeOfInterest();
|
|
||||||
if (timeOfInterest) {
|
|
||||||
this.changeTimeOfInterest(timeOfInterest);
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.$on('$destroy', this.destroy);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
ConductorTOIController.prototype.destroy = function () {
|
|
||||||
this.timeAPI.off('timeOfInterest', this.changeTimeOfInterest);
|
|
||||||
this.viewService.off('zoom', this.setOffsetFromZoom);
|
|
||||||
this.viewService.off('pan', this.setOffsetFromBounds);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given some bounds, set horizontal position of TOI indicator based
|
|
||||||
* on current conductor TOI value. Bounds are provided so that
|
|
||||||
* ephemeral bounds from zoom and pan events can be used as well
|
|
||||||
* as current conductor bounds, allowing TOI to be updated in
|
|
||||||
* realtime during scroll and zoom.
|
|
||||||
* @param {TimeConductorBounds} bounds
|
|
||||||
*/
|
|
||||||
ConductorTOIController.prototype.setOffsetFromBounds = function (bounds) {
|
|
||||||
var toi = this.timeAPI.timeOfInterest();
|
|
||||||
if (toi !== undefined) {
|
|
||||||
var offset = toi - bounds.start;
|
|
||||||
var duration = bounds.end - bounds.start;
|
|
||||||
this.left = offset / duration * 100;
|
|
||||||
this.pinned = true;
|
|
||||||
} else {
|
|
||||||
this.left = 0;
|
|
||||||
this.pinned = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
ConductorTOIController.prototype.setOffsetFromZoom = function (zoom) {
|
|
||||||
return this.setOffsetFromBounds(zoom.bounds);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoked when time of interest changes. Will set the horizontal offset of the TOI indicator.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
ConductorTOIController.prototype.changeTimeOfInterest = function () {
|
|
||||||
var bounds = this.timeAPI.bounds();
|
|
||||||
if (bounds) {
|
|
||||||
this.setOffsetFromBounds(bounds);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On a mouse click event within the TOI element, convert position within element to a time of interest, and
|
|
||||||
* set the time of interest on the conductor.
|
|
||||||
* @param e The angular $event object
|
|
||||||
*/
|
|
||||||
ConductorTOIController.prototype.setTOIFromPosition = function (e) {
|
|
||||||
//TOI is set using the alt key modified + primary click
|
|
||||||
if (e.altKey) {
|
|
||||||
var element = $(e.currentTarget);
|
|
||||||
var width = element.width();
|
|
||||||
var relativeX = e.pageX - element.offset().left;
|
|
||||||
var percX = relativeX / width;
|
|
||||||
var bounds = this.timeAPI.bounds();
|
|
||||||
var timeRange = bounds.end - bounds.start;
|
|
||||||
|
|
||||||
this.timeAPI.timeOfInterest(timeRange * percX + bounds.start);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return ConductorTOIController;
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,153 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* 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([
|
|
||||||
'./ConductorTOIController'
|
|
||||||
], function (
|
|
||||||
ConductorTOIController
|
|
||||||
) {
|
|
||||||
var mockConductor;
|
|
||||||
var mockConductorViewService;
|
|
||||||
var mockScope;
|
|
||||||
var mockAPI;
|
|
||||||
var conductorTOIController;
|
|
||||||
|
|
||||||
function getNamedCallback(thing, name) {
|
|
||||||
return thing.calls.all().filter(function (call) {
|
|
||||||
return call.args[0] === name;
|
|
||||||
}).map(function (call) {
|
|
||||||
return call.args;
|
|
||||||
})[0][1];
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("The ConductorTOIController", function () {
|
|
||||||
beforeEach(function () {
|
|
||||||
mockConductor = jasmine.createSpyObj("conductor", [
|
|
||||||
"bounds",
|
|
||||||
"timeOfInterest",
|
|
||||||
"on",
|
|
||||||
"off"
|
|
||||||
]);
|
|
||||||
mockAPI = {time: mockConductor};
|
|
||||||
|
|
||||||
mockConductorViewService = jasmine.createSpyObj("conductorViewService", [
|
|
||||||
"on",
|
|
||||||
"off"
|
|
||||||
]);
|
|
||||||
|
|
||||||
mockScope = jasmine.createSpyObj("openMCT", [
|
|
||||||
"$on"
|
|
||||||
]);
|
|
||||||
ConductorTOIController.prototype.viewService = mockConductorViewService;
|
|
||||||
conductorTOIController = new ConductorTOIController(mockScope, mockAPI);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("listens to changes in the time of interest on the conductor", function () {
|
|
||||||
expect(mockConductor.on).toHaveBeenCalledWith("timeOfInterest", jasmine.any(Function));
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when responding to changes in the time of interest", function () {
|
|
||||||
var toiCallback;
|
|
||||||
beforeEach(function () {
|
|
||||||
var bounds = {
|
|
||||||
start: 0,
|
|
||||||
end: 200
|
|
||||||
};
|
|
||||||
mockConductor.bounds.and.returnValue(bounds);
|
|
||||||
toiCallback = getNamedCallback(mockConductor.on, "timeOfInterest");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("calculates the correct horizontal offset based on bounds and current TOI", function () {
|
|
||||||
//Expect time of interest position to be 50% of element width
|
|
||||||
mockConductor.timeOfInterest.and.returnValue(100);
|
|
||||||
toiCallback();
|
|
||||||
expect(conductorTOIController.left).toBe(50);
|
|
||||||
|
|
||||||
//Expect time of interest position to be 25% of element width
|
|
||||||
mockConductor.timeOfInterest.and.returnValue(50);
|
|
||||||
toiCallback();
|
|
||||||
expect(conductorTOIController.left).toBe(25);
|
|
||||||
|
|
||||||
//Expect time of interest position to be 0% of element width
|
|
||||||
mockConductor.timeOfInterest.and.returnValue(0);
|
|
||||||
toiCallback();
|
|
||||||
expect(conductorTOIController.left).toBe(0);
|
|
||||||
|
|
||||||
//Expect time of interest position to be 100% of element width
|
|
||||||
mockConductor.timeOfInterest.and.returnValue(200);
|
|
||||||
toiCallback();
|
|
||||||
expect(conductorTOIController.left).toBe(100);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("renders the TOI indicator visible", function () {
|
|
||||||
expect(conductorTOIController.pinned).toBeFalsy();
|
|
||||||
mockConductor.timeOfInterest.and.returnValue(100);
|
|
||||||
toiCallback();
|
|
||||||
expect(conductorTOIController.pinned).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("responds to zoom events", function () {
|
|
||||||
var mockZoom = {
|
|
||||||
bounds: {
|
|
||||||
start: 500,
|
|
||||||
end: 1000
|
|
||||||
}
|
|
||||||
};
|
|
||||||
expect(mockConductorViewService.on).toHaveBeenCalledWith("zoom", jasmine.any(Function));
|
|
||||||
|
|
||||||
// Should correspond to horizontal offset of 50%
|
|
||||||
mockConductor.timeOfInterest.and.returnValue(750);
|
|
||||||
var zoomCallback = getNamedCallback(mockConductorViewService.on, "zoom");
|
|
||||||
zoomCallback(mockZoom);
|
|
||||||
expect(conductorTOIController.left).toBe(50);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("responds to pan events", function () {
|
|
||||||
var mockPanBounds = {
|
|
||||||
start: 1000,
|
|
||||||
end: 3000
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(mockConductorViewService.on).toHaveBeenCalledWith("pan", jasmine.any(Function));
|
|
||||||
|
|
||||||
// Should correspond to horizontal offset of 25%
|
|
||||||
mockConductor.timeOfInterest.and.returnValue(1500);
|
|
||||||
var panCallback = getNamedCallback(mockConductorViewService.on, "pan");
|
|
||||||
panCallback(mockPanBounds);
|
|
||||||
expect(conductorTOIController.left).toBe(25);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it("Cleans up all listeners when controller destroyed", function () {
|
|
||||||
var zoomCB = getNamedCallback(mockConductorViewService.on, "zoom");
|
|
||||||
var panCB = getNamedCallback(mockConductorViewService.on, "pan");
|
|
||||||
var toiCB = getNamedCallback(mockConductor.on, "timeOfInterest");
|
|
||||||
|
|
||||||
expect(mockScope.$on).toHaveBeenCalledWith("$destroy", jasmine.any(Function));
|
|
||||||
getNamedCallback(mockScope.$on, "$destroy")();
|
|
||||||
expect(mockConductorViewService.off).toHaveBeenCalledWith("zoom", zoomCB);
|
|
||||||
expect(mockConductorViewService.off).toHaveBeenCalledWith("pan", panCB);
|
|
||||||
expect(mockConductor.off).toHaveBeenCalledWith("timeOfInterest", toiCB);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,63 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* 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(['./ConductorTOIController'], function (ConductorTOIController) {
|
|
||||||
/**
|
|
||||||
* A directive that encapsulates the TOI specific behavior of the Time Conductor UI.
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function ConductorTOIDirective() {
|
|
||||||
/**
|
|
||||||
* The mct-conductor-axis renders a horizontal axis with regular
|
|
||||||
* labelled 'ticks'. It requires 'start' and 'end' integer values to
|
|
||||||
* be specified as attributes.
|
|
||||||
*/
|
|
||||||
return {
|
|
||||||
controller: [
|
|
||||||
'$scope',
|
|
||||||
'openmct',
|
|
||||||
ConductorTOIController
|
|
||||||
],
|
|
||||||
controllerAs: 'toi',
|
|
||||||
scope: {
|
|
||||||
viewService: "="
|
|
||||||
},
|
|
||||||
bindToController: true,
|
|
||||||
|
|
||||||
restrict: 'E',
|
|
||||||
priority: 1000,
|
|
||||||
|
|
||||||
template:
|
|
||||||
'<div class="l-data-visualization-holder l-row-elem flex-elem">' +
|
|
||||||
' <a class="l-page-button s-icon-button icon-pointer-left"></a>' +
|
|
||||||
' <div class="l-data-visualization" ng-click="toi.setTOIFromPosition($event)">' +
|
|
||||||
' <mct-include key="\'time-of-interest\'" class="l-toi-holder show-val" ' +
|
|
||||||
' ng-class="{ pinned: toi.pinned, \'val-to-left\': toi.left > 80 }" ' +
|
|
||||||
' ng-style="{\'left\': toi.left + \'%\'}"></mct-include>' +
|
|
||||||
' </div>' +
|
|
||||||
' <a class="l-page-button align-right s-icon-button icon-pointer-right"></a>' +
|
|
||||||
'</div>'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return ConductorTOIDirective;
|
|
||||||
});
|
|
@ -1,54 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2018, 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() {
|
|
||||||
this.key = 'number';
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
});
|
|
@ -1,49 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* 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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,554 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* 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',
|
|
||||||
'./TimeConductorValidation',
|
|
||||||
'./TimeConductorViewService'
|
|
||||||
],
|
|
||||||
function (
|
|
||||||
moment,
|
|
||||||
TimeConductorValidation,
|
|
||||||
TimeConductorViewService
|
|
||||||
) {
|
|
||||||
|
|
||||||
var timeUnitsMegastructure = [
|
|
||||||
["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;
|
|
||||||
}]
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Controller for the Time Conductor UI element. The Time Conductor
|
|
||||||
* includes form fields for specifying time bounds and relative time
|
|
||||||
* offsets for queries, as well as controls for selection mode,
|
|
||||||
* time systems, and zooming.
|
|
||||||
* @memberof platform.features.conductor
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function TimeConductorController(
|
|
||||||
$scope,
|
|
||||||
$window,
|
|
||||||
openmct,
|
|
||||||
formatService,
|
|
||||||
config
|
|
||||||
) {
|
|
||||||
|
|
||||||
//Bind functions that are used as callbacks to 'this'.
|
|
||||||
[
|
|
||||||
"selectMenuOption",
|
|
||||||
"onPan",
|
|
||||||
"onPanStop",
|
|
||||||
"setViewFromBounds",
|
|
||||||
"setViewFromClock",
|
|
||||||
"setViewFromOffsets",
|
|
||||||
"setViewFromTimeSystem",
|
|
||||||
"setTimeSystemFromView",
|
|
||||||
"destroy"
|
|
||||||
].forEach(function (name) {
|
|
||||||
this[name] = this[name].bind(this);
|
|
||||||
}.bind(this));
|
|
||||||
|
|
||||||
this.$scope = $scope;
|
|
||||||
this.$window = $window;
|
|
||||||
this.timeAPI = openmct.time;
|
|
||||||
this.conductorViewService = new TimeConductorViewService(openmct);
|
|
||||||
this.validation = new TimeConductorValidation(this.timeAPI);
|
|
||||||
this.formatService = formatService;
|
|
||||||
this.config = config;
|
|
||||||
this.timeSystemsForClocks = {};
|
|
||||||
this.$scope.timeSystemModel = {};
|
|
||||||
this.$scope.boundsModel = {};
|
|
||||||
|
|
||||||
this.timeSystems = this.timeAPI.getAllTimeSystems().reduce(function (map, timeSystem) {
|
|
||||||
map[timeSystem.key] = timeSystem;
|
|
||||||
return map;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
this.isFixed = this.timeAPI.clock() === undefined;
|
|
||||||
|
|
||||||
var options = this.optionsFromConfig(config);
|
|
||||||
this.menu = {
|
|
||||||
selected: undefined,
|
|
||||||
options: options,
|
|
||||||
select: this.selectMenuOption
|
|
||||||
};
|
|
||||||
|
|
||||||
//Set the initial state of the UI from the conductor state
|
|
||||||
var timeSystem = this.timeAPI.timeSystem();
|
|
||||||
if (timeSystem) {
|
|
||||||
this.setViewFromTimeSystem(timeSystem);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setViewFromClock(this.timeAPI.clock());
|
|
||||||
|
|
||||||
var offsets = this.timeAPI.clockOffsets();
|
|
||||||
if (offsets) {
|
|
||||||
this.setViewFromOffsets(offsets);
|
|
||||||
}
|
|
||||||
|
|
||||||
var bounds = this.timeAPI.bounds();
|
|
||||||
if (bounds && bounds.start !== undefined && bounds.end !== undefined) {
|
|
||||||
this.setViewFromBounds(bounds);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.conductorViewService.on('pan', this.onPan);
|
|
||||||
this.conductorViewService.on('pan-stop', this.onPanStop);
|
|
||||||
|
|
||||||
//Respond to any subsequent conductor changes
|
|
||||||
this.timeAPI.on('bounds', this.setViewFromBounds);
|
|
||||||
this.timeAPI.on('timeSystem', this.setViewFromTimeSystem);
|
|
||||||
this.timeAPI.on('clock', this.setViewFromClock);
|
|
||||||
this.timeAPI.on('clockOffsets', this.setViewFromOffsets);
|
|
||||||
this.$scope.$on('$destroy', this.destroy);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given a key for a clock, retrieve the clock object.
|
|
||||||
* @private
|
|
||||||
* @param key
|
|
||||||
* @returns {Clock}
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.getClock = function (key) {
|
|
||||||
return this.timeAPI.getAllClocks().filter(function (clock) {
|
|
||||||
return clock.key === key;
|
|
||||||
})[0];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Activate the selected menu option. Menu options correspond to clocks.
|
|
||||||
* A distinction is made to avoid confusion between the menu options and
|
|
||||||
* their metadata, and actual {@link Clock} objects.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @param newOption
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.selectMenuOption = function (newOption) {
|
|
||||||
if (this.menu.selected.key === newOption.key) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.menu.selected = newOption;
|
|
||||||
|
|
||||||
var config = this.getConfig(this.timeAPI.timeSystem(), newOption.clock);
|
|
||||||
if (!config) {
|
|
||||||
// Clock does not support this timeSystem, fallback to first
|
|
||||||
// option provided for clock.
|
|
||||||
config = this.config.menuOptions.filter(function (menuOption) {
|
|
||||||
return menuOption.clock === (newOption.clock && newOption.clock.key);
|
|
||||||
})[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.clock) {
|
|
||||||
this.timeAPI.clock(config.clock, config.clockOffsets);
|
|
||||||
this.timeAPI.timeSystem(config.timeSystem);
|
|
||||||
} else {
|
|
||||||
this.timeAPI.stopClock();
|
|
||||||
this.timeAPI.timeSystem(config.timeSystem, config.bounds);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* From the provided configuration, build the available menu options.
|
|
||||||
* @private
|
|
||||||
* @param config
|
|
||||||
* @returns {*[]}
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.optionsFromConfig = function (config) {
|
|
||||||
/*
|
|
||||||
* "Fixed Mode" is always the first available option.
|
|
||||||
*/
|
|
||||||
var options = [{
|
|
||||||
key: 'fixed',
|
|
||||||
name: 'Fixed Timespan Mode',
|
|
||||||
description: 'Query and explore data that falls between two fixed datetimes.',
|
|
||||||
cssClass: 'icon-calendar'
|
|
||||||
}];
|
|
||||||
var clocks = {};
|
|
||||||
var timeSystemsForClocks = this.timeSystemsForClocks;
|
|
||||||
|
|
||||||
(config.menuOptions || []).forEach(function (menuOption) {
|
|
||||||
var clockKey = menuOption.clock || 'fixed';
|
|
||||||
var clock = this.getClock(clockKey);
|
|
||||||
|
|
||||||
if (clock !== undefined) {
|
|
||||||
clocks[clock.key] = clock;
|
|
||||||
}
|
|
||||||
|
|
||||||
var timeSystem = this.timeSystems[menuOption.timeSystem];
|
|
||||||
if (timeSystem !== undefined) {
|
|
||||||
timeSystemsForClocks[clockKey] = timeSystemsForClocks[clockKey] || [];
|
|
||||||
timeSystemsForClocks[clockKey].push(timeSystem);
|
|
||||||
}
|
|
||||||
}, this);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Populate the clocks menu with metadata from the available clocks
|
|
||||||
*/
|
|
||||||
Object.values(clocks).forEach(function (clock) {
|
|
||||||
options.push({
|
|
||||||
key: clock.key,
|
|
||||||
name: clock.name,
|
|
||||||
description: "Monitor streaming data in real-time. The Time " +
|
|
||||||
"Conductor and displays will automatically advance themselves based on this clock. " + clock.description,
|
|
||||||
cssClass: clock.cssClass || 'icon-clock',
|
|
||||||
clock: clock
|
|
||||||
});
|
|
||||||
}.bind(this));
|
|
||||||
|
|
||||||
return options;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When bounds change, set UI values from the new bounds.
|
|
||||||
* @param {TimeBounds} bounds the bounds
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.setViewFromBounds = function (bounds) {
|
|
||||||
if (!this.zooming && !this.panning) {
|
|
||||||
this.$scope.boundsModel.start = bounds.start;
|
|
||||||
this.$scope.boundsModel.end = bounds.end;
|
|
||||||
|
|
||||||
if (this.supportsZoom()) {
|
|
||||||
var config = this.getConfig(this.timeAPI.timeSystem(), this.timeAPI.clock());
|
|
||||||
this.currentZoom = this.toSliderValue(bounds.end - bounds.start, config.zoomOutLimit, config.zoomInLimit);
|
|
||||||
this.toTimeUnits(bounds.end - bounds.start);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Ensure that a digest occurs, capped at the browser's refresh
|
|
||||||
rate.
|
|
||||||
*/
|
|
||||||
if (!this.pendingUpdate) {
|
|
||||||
this.pendingUpdate = true;
|
|
||||||
this.$window.requestAnimationFrame(function () {
|
|
||||||
this.pendingUpdate = false;
|
|
||||||
this.$scope.$digest();
|
|
||||||
}.bind(this));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve any configuration defined for the provided time system and
|
|
||||||
* clock
|
|
||||||
* @private
|
|
||||||
* @param timeSystem
|
|
||||||
* @param clock
|
|
||||||
* @returns {object} The Time Conductor configuration corresponding to
|
|
||||||
* the provided combination of time system and clock
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.getConfig = function (timeSystem, clock) {
|
|
||||||
var clockKey = clock && clock.key;
|
|
||||||
var timeSystemKey = timeSystem && timeSystem.key;
|
|
||||||
|
|
||||||
var option = this.config.menuOptions.filter(function (menuOption) {
|
|
||||||
return menuOption.timeSystem === timeSystemKey && menuOption.clock === clockKey;
|
|
||||||
})[0];
|
|
||||||
return option;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When the clock offsets change, update the values in the UI
|
|
||||||
* @param {ClockOffsets} offsets
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.setViewFromOffsets = function (offsets) {
|
|
||||||
this.$scope.boundsModel.startOffset = Math.abs(offsets.start);
|
|
||||||
this.$scope.boundsModel.endOffset = offsets.end;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When form values for bounds change, update the bounds in the Time API
|
|
||||||
* to trigger an application-wide bounds change.
|
|
||||||
* @param {object} boundsModel
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.setBoundsFromView = function (boundsModel) {
|
|
||||||
var bounds = this.timeAPI.bounds();
|
|
||||||
if (boundsModel.start !== bounds.start || boundsModel.end !== bounds.end) {
|
|
||||||
this.timeAPI.bounds({
|
|
||||||
start: boundsModel.start,
|
|
||||||
end: boundsModel.end
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When form values for bounds change, update the bounds in the Time API
|
|
||||||
* to trigger an application-wide bounds change.
|
|
||||||
* @param {object} formModel
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.setOffsetsFromView = function (boundsModel) {
|
|
||||||
if (this.validation.validateStartOffset(boundsModel.startOffset) && this.validation.validateEndOffset(boundsModel.endOffset)) {
|
|
||||||
var offsets = {
|
|
||||||
start: 0 - boundsModel.startOffset,
|
|
||||||
end: boundsModel.endOffset
|
|
||||||
};
|
|
||||||
var existingOffsets = this.timeAPI.clockOffsets();
|
|
||||||
|
|
||||||
if (offsets.start !== existingOffsets.start || offsets.end !== existingOffsets.end) {
|
|
||||||
//Sychronize offsets between form and time API
|
|
||||||
this.timeAPI.clockOffsets(offsets);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.supportsZoom = function () {
|
|
||||||
var config = this.getConfig(this.timeAPI.timeSystem(), this.timeAPI.clock());
|
|
||||||
return config && (config.zoomInLimit !== undefined && config.zoomOutLimit !== undefined);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the UI state to reflect a change in clock. Provided conductor
|
|
||||||
* configuration will be checked for compatibility between the new clock
|
|
||||||
* and the currently selected time system. If configuration is not available,
|
|
||||||
* an attempt will be made to default to a time system that is compatible
|
|
||||||
* with the new clock
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @param {Clock} clock
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.setViewFromClock = function (clock) {
|
|
||||||
var newClockKey = clock ? clock.key : 'fixed';
|
|
||||||
var timeSystems = this.timeSystemsForClocks[newClockKey];
|
|
||||||
var menuOption = this.menu.options.filter(function (option) {
|
|
||||||
return option.key === (newClockKey);
|
|
||||||
})[0];
|
|
||||||
|
|
||||||
this.menu.selected = menuOption;
|
|
||||||
|
|
||||||
//Try to find currently selected time system in time systems for clock
|
|
||||||
var selectedTimeSystem = timeSystems.filter(function (timeSystem) {
|
|
||||||
return timeSystem.key === this.$scope.timeSystemModel.selected.key;
|
|
||||||
}.bind(this))[0];
|
|
||||||
|
|
||||||
var config = this.getConfig(selectedTimeSystem, clock);
|
|
||||||
|
|
||||||
if (selectedTimeSystem === undefined) {
|
|
||||||
selectedTimeSystem = timeSystems[0];
|
|
||||||
config = this.getConfig(selectedTimeSystem, clock);
|
|
||||||
|
|
||||||
if (clock === undefined) {
|
|
||||||
this.timeAPI.timeSystem(selectedTimeSystem, config.bounds);
|
|
||||||
} else {
|
|
||||||
//When time system changes, some start bounds need to be provided
|
|
||||||
this.timeAPI.timeSystem(selectedTimeSystem, {
|
|
||||||
start: clock.currentValue() + config.clockOffsets.start,
|
|
||||||
end: clock.currentValue() + config.clockOffsets.end
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.isFixed = clock === undefined;
|
|
||||||
|
|
||||||
if (clock === undefined) {
|
|
||||||
this.setViewFromBounds(this.timeAPI.bounds());
|
|
||||||
}
|
|
||||||
|
|
||||||
this.zoom = this.supportsZoom();
|
|
||||||
this.$scope.timeSystemModel.options = timeSystems;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.setTimeSystemFromView = function (key) {
|
|
||||||
var clock = this.menu.selected.clock;
|
|
||||||
var timeSystem = this.timeSystems[key];
|
|
||||||
var config = this.getConfig(timeSystem, clock);
|
|
||||||
|
|
||||||
this.$scope.timeSystemModel.selected = timeSystem;
|
|
||||||
this.$scope.timeSystemModel.format = timeSystem.timeFormat;
|
|
||||||
|
|
||||||
if (clock === undefined) {
|
|
||||||
this.timeAPI.timeSystem(timeSystem, config.bounds);
|
|
||||||
} else {
|
|
||||||
this.timeAPI.clock(clock, config.clockOffsets);
|
|
||||||
this.timeAPI.timeSystem(timeSystem);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles time system change from time conductor
|
|
||||||
*
|
|
||||||
* Sets the selected time system. Will populate form with the default
|
|
||||||
* bounds and offsets defined in the selected time system.
|
|
||||||
*
|
|
||||||
* @param newTimeSystem
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.setViewFromTimeSystem = function (timeSystem) {
|
|
||||||
var oldKey = (this.$scope.timeSystemModel.selected || {}).key;
|
|
||||||
var timeSystemModel = this.$scope.timeSystemModel;
|
|
||||||
|
|
||||||
if (timeSystem && (timeSystem.key !== oldKey)) {
|
|
||||||
var config = this.getConfig(timeSystem, this.timeAPI.clock());
|
|
||||||
|
|
||||||
timeSystemModel.selected = timeSystem;
|
|
||||||
timeSystemModel.format = timeSystem.timeFormat;
|
|
||||||
timeSystemModel.durationFormat = timeSystem.durationFormat;
|
|
||||||
|
|
||||||
if (this.supportsZoom()) {
|
|
||||||
timeSystemModel.minZoom = config.zoomOutLimit;
|
|
||||||
timeSystemModel.maxZoom = config.zoomInLimit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.zoom = this.supportsZoom();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Takes a time span and calculates a slider increment value, used
|
|
||||||
* to set the horizontal offset of the slider.
|
|
||||||
* @private
|
|
||||||
* @param {number} timeSpan a duration of time, in ms
|
|
||||||
* @returns {number} a value between 0.01 and 0.99, in increments of .01
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.toSliderValue = function (timeSpan, zoomOutLimit, zoomInLimit) {
|
|
||||||
var perc = timeSpan / (zoomOutLimit - zoomInLimit);
|
|
||||||
return 1 - Math.pow(perc, 1 / 4);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given a time span, set a label for the units of time that it,
|
|
||||||
* roughly, represents. Leverages
|
|
||||||
* @param {TimeSpan} timeSpan
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.toTimeUnits = function (timeSpan) {
|
|
||||||
var timeSystem = this.timeAPI.timeSystem();
|
|
||||||
if (timeSystem && timeSystem.isUTCBased) {
|
|
||||||
var momentified = moment.duration(timeSpan);
|
|
||||||
|
|
||||||
this.$scope.timeUnits = timeUnitsMegastructure.filter(function (row) {
|
|
||||||
return row[1](momentified);
|
|
||||||
})[0][0];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Zooming occurs when the user manipulates the zoom slider.
|
|
||||||
* Zooming updates the scale and bounds fields immediately, but does
|
|
||||||
* not trigger a bounds change to other views until the mouse button
|
|
||||||
* is released.
|
|
||||||
* @param bounds
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.onZoom = function (sliderValue) {
|
|
||||||
var config = this.getConfig(this.timeAPI.timeSystem(), this.timeAPI.clock());
|
|
||||||
var timeSpan = Math.pow((1 - sliderValue), 4) * (config.zoomOutLimit - config.zoomInLimit);
|
|
||||||
|
|
||||||
var zoom = this.conductorViewService.zoom(timeSpan);
|
|
||||||
this.zooming = true;
|
|
||||||
|
|
||||||
this.$scope.boundsModel.start = zoom.bounds.start;
|
|
||||||
this.$scope.boundsModel.end = zoom.bounds.end;
|
|
||||||
this.toTimeUnits(zoom.bounds.end - zoom.bounds.start);
|
|
||||||
|
|
||||||
if (zoom.offsets) {
|
|
||||||
this.setViewFromOffsets(zoom.offsets);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fired when user has released the zoom slider
|
|
||||||
* @event platform.features.conductor.TimeConductorController~zoomStop
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Invoked when zoom slider is released by user. Will update the time conductor with the new bounds, triggering
|
|
||||||
* a global bounds change event.
|
|
||||||
* @fires platform.features.conductor.TimeConductorController~zoomStop
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.onZoomStop = function () {
|
|
||||||
if (this.timeAPI.clock() !== undefined) {
|
|
||||||
this.setOffsetsFromView(this.$scope.boundsModel);
|
|
||||||
}
|
|
||||||
this.setBoundsFromView(this.$scope.boundsModel);
|
|
||||||
|
|
||||||
this.zooming = false;
|
|
||||||
this.conductorViewService.emit('zoom-stop');
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Panning occurs when the user grabs the conductor scale and drags
|
|
||||||
* it left or right to slide the window of time represented by the
|
|
||||||
* conductor. Panning updates the scale and bounds fields
|
|
||||||
* immediately, but does not trigger a bounds change to other views
|
|
||||||
* until the mouse button is released.
|
|
||||||
* @param {TimeConductorBounds} bounds
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.onPan = function (bounds) {
|
|
||||||
this.panning = true;
|
|
||||||
this.$scope.boundsModel.start = bounds.start;
|
|
||||||
this.$scope.boundsModel.end = bounds.end;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the user releases the mouse button after panning.
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.onPanStop = function () {
|
|
||||||
this.panning = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.destroy = function () {
|
|
||||||
this.timeAPI.off('bounds', this.setViewFromBounds);
|
|
||||||
this.timeAPI.off('timeSystem', this.setViewFromTimeSystem);
|
|
||||||
this.timeAPI.off('clock', this.setViewFromClock);
|
|
||||||
this.timeAPI.off('follow', this.setFollow);
|
|
||||||
this.timeAPI.off('clockOffsets', this.setViewFromOffsets);
|
|
||||||
|
|
||||||
this.conductorViewService.off('pan', this.onPan);
|
|
||||||
this.conductorViewService.off('pan-stop', this.onPanStop);
|
|
||||||
};
|
|
||||||
|
|
||||||
return TimeConductorController;
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,513 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* 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) {
|
|
||||||
xdescribe("The time conductor controller", function () {
|
|
||||||
var mockScope;
|
|
||||||
var mockWindow;
|
|
||||||
var mockTimeConductor;
|
|
||||||
var mockConductorViewService;
|
|
||||||
var mockTimeSystems;
|
|
||||||
var controller;
|
|
||||||
var mockFormatService;
|
|
||||||
var mockFormat;
|
|
||||||
var mockLocation;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
mockScope = jasmine.createSpyObj("$scope", [
|
|
||||||
"$watch",
|
|
||||||
"$on"
|
|
||||||
]);
|
|
||||||
|
|
||||||
mockWindow = jasmine.createSpyObj("$window", ["requestAnimationFrame"]);
|
|
||||||
mockTimeConductor = jasmine.createSpyObj(
|
|
||||||
"TimeConductor",
|
|
||||||
[
|
|
||||||
"bounds",
|
|
||||||
"timeSystem",
|
|
||||||
"on",
|
|
||||||
"off"
|
|
||||||
]
|
|
||||||
);
|
|
||||||
mockTimeConductor.bounds.and.returnValue({start: undefined, end: undefined});
|
|
||||||
|
|
||||||
mockConductorViewService = jasmine.createSpyObj(
|
|
||||||
"ConductorViewService",
|
|
||||||
[
|
|
||||||
"availableModes",
|
|
||||||
"mode",
|
|
||||||
"availableTimeSystems",
|
|
||||||
"deltas",
|
|
||||||
"deltas",
|
|
||||||
"on",
|
|
||||||
"off"
|
|
||||||
]
|
|
||||||
);
|
|
||||||
mockConductorViewService.availableModes.and.returnValue([]);
|
|
||||||
mockConductorViewService.availableTimeSystems.and.returnValue([]);
|
|
||||||
|
|
||||||
mockFormatService = jasmine.createSpyObj('formatService', [
|
|
||||||
'getFormat'
|
|
||||||
]);
|
|
||||||
mockFormat = jasmine.createSpyObj('format', [
|
|
||||||
'format'
|
|
||||||
]);
|
|
||||||
mockFormatService.getFormat.and.returnValue(mockFormat);
|
|
||||||
mockLocation = jasmine.createSpyObj('location', [
|
|
||||||
'search'
|
|
||||||
]);
|
|
||||||
mockLocation.search.and.returnValue({});
|
|
||||||
|
|
||||||
mockTimeSystems = [];
|
|
||||||
});
|
|
||||||
|
|
||||||
function getListener(target, event) {
|
|
||||||
return target.calls.all().filter(function (call) {
|
|
||||||
return call.args[0] === event;
|
|
||||||
})[0].args[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("when time conductor state changes", function () {
|
|
||||||
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 = {
|
|
||||||
metadata: {
|
|
||||||
key: 'mock'
|
|
||||||
},
|
|
||||||
formats: function () {
|
|
||||||
return [mockFormat];
|
|
||||||
},
|
|
||||||
deltaFormat: function () {
|
|
||||||
return mockDeltaFormat;
|
|
||||||
},
|
|
||||||
defaults: function () {
|
|
||||||
return mockDefaults;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
controller = new TimeConductorController(
|
|
||||||
mockScope,
|
|
||||||
mockWindow,
|
|
||||||
mockLocation,
|
|
||||||
{conductor: mockTimeConductor},
|
|
||||||
mockConductorViewService,
|
|
||||||
mockFormatService,
|
|
||||||
'fixed',
|
|
||||||
true
|
|
||||||
|
|
||||||
);
|
|
||||||
|
|
||||||
tsListener = getListener(mockTimeConductor.on, "timeSystem");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("listens for changes to conductor state", function () {
|
|
||||||
expect(mockTimeConductor.on).toHaveBeenCalledWith("timeSystem", controller.changeTimeSystem);
|
|
||||||
expect(mockTimeConductor.on).toHaveBeenCalledWith("bounds", controller.changeBounds);
|
|
||||||
});
|
|
||||||
|
|
||||||
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.changeBounds);
|
|
||||||
});
|
|
||||||
|
|
||||||
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 () {
|
|
||||||
mockDefaults.zoom = {
|
|
||||||
min: 100,
|
|
||||||
max: 10
|
|
||||||
};
|
|
||||||
mockTimeConductor.timeSystem.and.returnValue(timeSystem);
|
|
||||||
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);
|
|
||||||
|
|
||||||
expect(mockScope.timeSystemModel.minZoom).toBe(mockDefaults.zoom.min);
|
|
||||||
expect(mockScope.timeSystemModel.maxZoom).toBe(mockDefaults.zoom.max);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("supports zoom if time system defines zoom defaults", function () {
|
|
||||||
|
|
||||||
mockDefaults.zoom = undefined;
|
|
||||||
|
|
||||||
tsListener(timeSystem);
|
|
||||||
expect(controller.supportsZoom).toBe(false);
|
|
||||||
|
|
||||||
mockDefaults.zoom = {
|
|
||||||
min: 100,
|
|
||||||
max: 10
|
|
||||||
};
|
|
||||||
|
|
||||||
var anotherTimeSystem = Object.create(timeSystem);
|
|
||||||
timeSystem.defaults = function () {
|
|
||||||
return mockDefaults;
|
|
||||||
};
|
|
||||||
|
|
||||||
tsListener(anotherTimeSystem);
|
|
||||||
expect(controller.supportsZoom).toBe(true);
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
it("when bounds change, sets the correct zoom slider value", function () {
|
|
||||||
var bounds = {
|
|
||||||
start: 0,
|
|
||||||
end: 50
|
|
||||||
};
|
|
||||||
mockDefaults.zoom = {
|
|
||||||
min: 100,
|
|
||||||
max: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
function exponentializer(rawValue) {
|
|
||||||
return 1 - Math.pow(rawValue, 1 / 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
mockTimeConductor.timeSystem.and.returnValue(timeSystem);
|
|
||||||
//Set zoom defaults
|
|
||||||
tsListener(timeSystem);
|
|
||||||
|
|
||||||
controller.changeBounds(bounds);
|
|
||||||
expect(controller.currentZoom).toEqual(exponentializer(0.5));
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
it("when bounds change, sets them on scope", function () {
|
|
||||||
var bounds = {
|
|
||||||
start: 1,
|
|
||||||
end: 2
|
|
||||||
};
|
|
||||||
|
|
||||||
var boundsListener = getListener(mockTimeConductor.on, "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;
|
|
||||||
|
|
||||||
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
|
|
||||||
mockConductorViewService.systems = mockTimeSystems;
|
|
||||||
|
|
||||||
controller = new TimeConductorController(
|
|
||||||
mockScope,
|
|
||||||
mockWindow,
|
|
||||||
mockLocation,
|
|
||||||
{conductor: mockTimeConductor},
|
|
||||||
mockConductorViewService,
|
|
||||||
mockFormatService,
|
|
||||||
"fixed",
|
|
||||||
true
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("sets the mode on scope", function () {
|
|
||||||
mockConductorViewService.availableTimeSystems.and.returnValue(mockTimeSystems);
|
|
||||||
controller.setMode(mode);
|
|
||||||
|
|
||||||
expect(mockScope.modeModel.selectedKey).toEqual(mode);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("sets available time systems on scope when mode changes", function () {
|
|
||||||
mockConductorViewService.availableTimeSystems.and.returnValue(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.setBounds(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.setDeltas(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
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
controller.timeSystems = [timeSystem];
|
|
||||||
|
|
||||||
controller.selectTimeSystemByKey('testTimeSystem');
|
|
||||||
expect(mockTimeConductor.timeSystem).toHaveBeenCalledWith(timeSystem, defaultBounds);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("updates form bounds during pan events", function () {
|
|
||||||
var testBounds = {
|
|
||||||
start: 10,
|
|
||||||
end: 20
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(controller.$scope.boundsModel.start).not.toBe(testBounds.start);
|
|
||||||
expect(controller.$scope.boundsModel.end).not.toBe(testBounds.end);
|
|
||||||
|
|
||||||
expect(controller.conductorViewService.on).toHaveBeenCalledWith("pan",
|
|
||||||
controller.onPan);
|
|
||||||
|
|
||||||
getListener(controller.conductorViewService.on, "pan")(testBounds);
|
|
||||||
|
|
||||||
expect(controller.$scope.boundsModel.start).toBe(testBounds.start);
|
|
||||||
expect(controller.$scope.boundsModel.end).toBe(testBounds.end);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when the URL defines conductor state", function () {
|
|
||||||
var urlBounds;
|
|
||||||
var urlTimeSystem;
|
|
||||||
var urlDeltas;
|
|
||||||
|
|
||||||
var mockDeltaFormat;
|
|
||||||
var defaultBounds;
|
|
||||||
var defaultDeltas;
|
|
||||||
var mockDefaults;
|
|
||||||
var timeSystem;
|
|
||||||
var otherTimeSystem;
|
|
||||||
var mockSearchObject;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
|
|
||||||
mockFormat = {};
|
|
||||||
mockDeltaFormat = {};
|
|
||||||
defaultBounds = {
|
|
||||||
start: 2,
|
|
||||||
end: 3
|
|
||||||
};
|
|
||||||
defaultDeltas = {
|
|
||||||
start: 10,
|
|
||||||
end: 20
|
|
||||||
};
|
|
||||||
mockDefaults = {
|
|
||||||
deltas: defaultDeltas,
|
|
||||||
bounds: defaultBounds
|
|
||||||
};
|
|
||||||
timeSystem = {
|
|
||||||
metadata: {
|
|
||||||
key: 'mockTimeSystem'
|
|
||||||
},
|
|
||||||
formats: function () {
|
|
||||||
return [mockFormat];
|
|
||||||
},
|
|
||||||
deltaFormat: function () {
|
|
||||||
return mockDeltaFormat;
|
|
||||||
},
|
|
||||||
defaults: function () {
|
|
||||||
return mockDefaults;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
otherTimeSystem = {
|
|
||||||
metadata: {
|
|
||||||
key: 'otherTimeSystem'
|
|
||||||
},
|
|
||||||
formats: function () {
|
|
||||||
return [mockFormat];
|
|
||||||
},
|
|
||||||
deltaFormat: function () {
|
|
||||||
return mockDeltaFormat;
|
|
||||||
},
|
|
||||||
defaults: function () {
|
|
||||||
return mockDefaults;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
mockConductorViewService.systems = [timeSystem, otherTimeSystem];
|
|
||||||
|
|
||||||
urlBounds = {
|
|
||||||
start: 100,
|
|
||||||
end: 200
|
|
||||||
};
|
|
||||||
urlTimeSystem = "otherTimeSystem";
|
|
||||||
urlDeltas = {
|
|
||||||
start: 300,
|
|
||||||
end: 400
|
|
||||||
};
|
|
||||||
mockSearchObject = {
|
|
||||||
"tc.startBound": urlBounds.start,
|
|
||||||
"tc.endBound": urlBounds.end,
|
|
||||||
"tc.startDelta": urlDeltas.start,
|
|
||||||
"tc.endDelta": urlDeltas.end,
|
|
||||||
"tc.timeSystem": urlTimeSystem
|
|
||||||
};
|
|
||||||
mockLocation.search.and.returnValue(mockSearchObject);
|
|
||||||
mockTimeConductor.timeSystem.and.returnValue(timeSystem);
|
|
||||||
|
|
||||||
controller = new TimeConductorController(
|
|
||||||
mockScope,
|
|
||||||
mockWindow,
|
|
||||||
mockLocation,
|
|
||||||
{conductor: mockTimeConductor},
|
|
||||||
mockConductorViewService,
|
|
||||||
mockFormatService,
|
|
||||||
"fixed",
|
|
||||||
true
|
|
||||||
);
|
|
||||||
|
|
||||||
spyOn(controller, "setMode");
|
|
||||||
spyOn(controller, "selectTimeSystemByKey");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("sets conductor state from URL", function () {
|
|
||||||
mockSearchObject["tc.mode"] = "fixed";
|
|
||||||
controller.setStateFromSearchParams(mockSearchObject);
|
|
||||||
expect(controller.selectTimeSystemByKey).toHaveBeenCalledWith("otherTimeSystem");
|
|
||||||
expect(mockTimeConductor.bounds).toHaveBeenCalledWith(urlBounds);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("sets mode from URL", function () {
|
|
||||||
mockTimeConductor.bounds.reset();
|
|
||||||
mockSearchObject["tc.mode"] = "realtime";
|
|
||||||
controller.setStateFromSearchParams(mockSearchObject);
|
|
||||||
expect(controller.setMode).toHaveBeenCalledWith("realtime");
|
|
||||||
expect(controller.selectTimeSystemByKey).toHaveBeenCalledWith("otherTimeSystem");
|
|
||||||
expect(mockConductorViewService.deltas).toHaveBeenCalledWith(urlDeltas);
|
|
||||||
expect(mockTimeConductor.bounds).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when conductor state changes", function () {
|
|
||||||
it("updates the URL with the mode", function () {
|
|
||||||
controller.setMode("realtime", "fixed");
|
|
||||||
expect(mockLocation.search).toHaveBeenCalledWith("tc.mode", "fixed");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("updates the URL with the bounds", function () {
|
|
||||||
mockConductorViewService.mode.and.returnValue("fixed");
|
|
||||||
controller.changeBounds({start: 500, end: 600});
|
|
||||||
expect(mockLocation.search).toHaveBeenCalledWith("tc.startBound", 500);
|
|
||||||
expect(mockLocation.search).toHaveBeenCalledWith("tc.endBound", 600);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("updates the URL with the deltas", function () {
|
|
||||||
controller.setDeltas({startDelta: 700, endDelta: 800});
|
|
||||||
expect(mockLocation.search).toHaveBeenCalledWith("tc.startDelta", 700);
|
|
||||||
expect(mockLocation.search).toHaveBeenCalledWith("tc.endDelta", 800);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("updates the URL with the time system", function () {
|
|
||||||
controller.changeTimeSystem(otherTimeSystem);
|
|
||||||
expect(mockLocation.search).toHaveBeenCalledWith("tc.timeSystem", "otherTimeSystem");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,69 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* 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(timeAPI) {
|
|
||||||
var self = this;
|
|
||||||
this.timeAPI = timeAPI;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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.timeAPI.bounds();
|
|
||||||
return this.timeAPI.validateBounds({start: start, end: bounds.end}) === true;
|
|
||||||
};
|
|
||||||
|
|
||||||
TimeConductorValidation.prototype.validateEnd = function (end) {
|
|
||||||
var bounds = this.timeAPI.bounds();
|
|
||||||
return this.timeAPI.validateBounds({start: bounds.start, end: end}) === true;
|
|
||||||
};
|
|
||||||
|
|
||||||
TimeConductorValidation.prototype.validateStartOffset = function (startOffset) {
|
|
||||||
return !isNaN(startOffset) && startOffset > 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
TimeConductorValidation.prototype.validateEndOffset = function (endOffset) {
|
|
||||||
return !isNaN(endOffset) && endOffset >= 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
return TimeConductorValidation;
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,73 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* 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.and.returnValue(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 Offset is valid number > 0", function () {
|
|
||||||
expect(timeConductorValidation.validateStartOffset(-1)).toBe(false);
|
|
||||||
expect(timeConductorValidation.validateStartOffset("abc")).toBe(false);
|
|
||||||
expect(timeConductorValidation.validateStartOffset("1")).toBe(true);
|
|
||||||
expect(timeConductorValidation.validateStartOffset(1)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Validates that end Offset is valid number >= 0", function () {
|
|
||||||
expect(timeConductorValidation.validateEndOffset(-1)).toBe(false);
|
|
||||||
expect(timeConductorValidation.validateEndOffset("abc")).toBe(false);
|
|
||||||
expect(timeConductorValidation.validateEndOffset("1")).toBe(true);
|
|
||||||
expect(timeConductorValidation.validateEndOffset(0)).toBe(true);
|
|
||||||
expect(timeConductorValidation.validateEndOffset(1)).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,97 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* 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 TimeConductorViewService acts as an event bus between different
|
|
||||||
* elements of the Time Conductor UI. Zooming and panning occur via this
|
|
||||||
* service, as they are specific behaviour of the UI, and not general
|
|
||||||
* functions of the time API.
|
|
||||||
*
|
|
||||||
* Synchronization of conductor state between the Time API and the URL
|
|
||||||
* also occurs from the conductor view service, whose lifecycle persists
|
|
||||||
* between view changes.
|
|
||||||
*
|
|
||||||
* @memberof platform.features.conductor
|
|
||||||
* @param conductor
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function TimeConductorViewService(openmct) {
|
|
||||||
|
|
||||||
EventEmitter.call(this);
|
|
||||||
|
|
||||||
this.timeAPI = openmct.time;
|
|
||||||
}
|
|
||||||
|
|
||||||
TimeConductorViewService.prototype = Object.create(EventEmitter.prototype);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An event to indicate that zooming is taking place
|
|
||||||
* @event platform.features.conductor.TimeConductorViewService~zoom
|
|
||||||
* @property {ZoomLevel} zoom the new zoom level.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Zoom to given time span. Will fire a zoom event with new zoom
|
|
||||||
* bounds. Zoom bounds emitted in this way are considered ephemeral
|
|
||||||
* and should be overridden by any time conductor bounds events. Does
|
|
||||||
* not set bounds globally.
|
|
||||||
* @param {number} zoom A time duration in ms
|
|
||||||
* @fires platform.features.conductor.TimeConductorViewService~zoom
|
|
||||||
* @see module:openmct.TimeConductor#bounds
|
|
||||||
*/
|
|
||||||
TimeConductorViewService.prototype.zoom = function (timeSpan) {
|
|
||||||
var zoom = {};
|
|
||||||
|
|
||||||
// If a tick source is defined, then the concept of 'now' is
|
|
||||||
// important. Calculate zoom based on 'now'.
|
|
||||||
if (this.timeAPI.clock() !== undefined) {
|
|
||||||
zoom.offsets = {
|
|
||||||
start: -timeSpan,
|
|
||||||
end: this.timeAPI.clockOffsets().end
|
|
||||||
};
|
|
||||||
|
|
||||||
var currentVal = this.timeAPI.clock().currentValue();
|
|
||||||
|
|
||||||
zoom.bounds = {
|
|
||||||
start: currentVal + zoom.offsets.start,
|
|
||||||
end: currentVal + zoom.offsets.end
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
var bounds = this.timeAPI.bounds();
|
|
||||||
var center = bounds.start + ((bounds.end - bounds.start)) / 2;
|
|
||||||
bounds.start = center - timeSpan / 2;
|
|
||||||
bounds.end = center + timeSpan / 2;
|
|
||||||
zoom.bounds = bounds;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.emit("zoom", zoom);
|
|
||||||
return zoom;
|
|
||||||
};
|
|
||||||
|
|
||||||
return TimeConductorViewService;
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,109 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* 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 () {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Controller for the Time of Interest element used in various views to display the TOI. Responsible for setting
|
|
||||||
* the text label for the current TOI, and for toggling the (un)pinned state which determines whether the TOI
|
|
||||||
* indicator is visible.
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function TimeOfInterestController($scope, openmct, formatService) {
|
|
||||||
this.timeAPI = openmct.time;
|
|
||||||
this.formatService = formatService;
|
|
||||||
this.format = undefined;
|
|
||||||
this.toiText = undefined;
|
|
||||||
this.$scope = $scope;
|
|
||||||
|
|
||||||
//Bind all class functions to 'this'
|
|
||||||
Object.keys(TimeOfInterestController.prototype).filter(function (key) {
|
|
||||||
return typeof TimeOfInterestController.prototype[key] === 'function';
|
|
||||||
}).forEach(function (key) {
|
|
||||||
this[key] = TimeOfInterestController.prototype[key].bind(this);
|
|
||||||
}.bind(this));
|
|
||||||
|
|
||||||
this.timeAPI.on('timeOfInterest', this.changeTimeOfInterest);
|
|
||||||
this.timeAPI.on('timeSystem', this.changeTimeSystem);
|
|
||||||
if (this.timeAPI.timeSystem() !== undefined) {
|
|
||||||
this.changeTimeSystem(this.timeAPI.timeSystem());
|
|
||||||
var toi = this.timeAPI.timeOfInterest();
|
|
||||||
if (toi) {
|
|
||||||
this.changeTimeOfInterest(toi);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.$on('$destroy', this.destroy);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the time of interest changes on the conductor. Will pin (display) the TOI indicator, and set the
|
|
||||||
* text using the default formatter of the currently active Time System.
|
|
||||||
* @private
|
|
||||||
* @param {integer} toi Current time of interest in ms
|
|
||||||
*/
|
|
||||||
TimeOfInterestController.prototype.changeTimeOfInterest = function (toi) {
|
|
||||||
if (toi !== undefined) {
|
|
||||||
this.$scope.pinned = true;
|
|
||||||
this.toiText = this.format.format(toi);
|
|
||||||
} else {
|
|
||||||
this.$scope.pinned = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When time system is changed, update the formatter used to
|
|
||||||
* display the current TOI label
|
|
||||||
*/
|
|
||||||
TimeOfInterestController.prototype.changeTimeSystem = function (timeSystem) {
|
|
||||||
this.format = this.formatService.getFormat(timeSystem.timeFormat);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
TimeOfInterestController.prototype.destroy = function () {
|
|
||||||
this.timeAPI.off('timeOfInterest', this.changeTimeOfInterest);
|
|
||||||
this.timeAPI.off('timeSystem', this.changeTimeSystem);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Will unpin (hide) the TOI indicator. Has the effect of setting the time of interest to `undefined` on the
|
|
||||||
* Time Conductor
|
|
||||||
*/
|
|
||||||
TimeOfInterestController.prototype.dismiss = function () {
|
|
||||||
this.timeAPI.timeOfInterest(undefined);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends out a time of interest event with the effect of resetting
|
|
||||||
* the TOI displayed in views.
|
|
||||||
*/
|
|
||||||
TimeOfInterestController.prototype.resync = function () {
|
|
||||||
this.timeAPI.timeOfInterest(this.timeAPI.timeOfInterest());
|
|
||||||
};
|
|
||||||
|
|
||||||
return TimeOfInterestController;
|
|
||||||
}
|
|
||||||
);
|
|
@ -316,7 +316,6 @@ define([
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
domElement.appendChild(appLayout.$mount().$el);
|
domElement.appendChild(appLayout.$mount().$el);
|
||||||
|
|
||||||
this.layout = appLayout;
|
this.layout = appLayout;
|
||||||
Browse(this);
|
Browse(this);
|
||||||
this.router.start();
|
this.router.start();
|
||||||
|
@ -55,8 +55,6 @@ define([
|
|||||||
'../platform/exporters/bundle',
|
'../platform/exporters/bundle',
|
||||||
'../platform/features/clock/bundle',
|
'../platform/features/clock/bundle',
|
||||||
'../platform/features/fixed/bundle',
|
'../platform/features/fixed/bundle',
|
||||||
'../platform/features/conductor/core/bundle',
|
|
||||||
'../platform/features/conductor/compatibility/bundle',
|
|
||||||
'../platform/features/imagery/bundle',
|
'../platform/features/imagery/bundle',
|
||||||
'../platform/features/layout/bundle',
|
'../platform/features/layout/bundle',
|
||||||
'../platform/features/listview/bundle',
|
'../platform/features/listview/bundle',
|
||||||
|
@ -51,64 +51,12 @@ define([
|
|||||||
function LocalTimeFormat() {
|
function LocalTimeFormat() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an appropriate time format based on the provided value and
|
|
||||||
* the threshold required.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
function getScaledFormat(d) {
|
|
||||||
var momentified = 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](momentified);
|
|
||||||
})[0][0];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param value
|
* @param value
|
||||||
* @param {Scale} [scale] Optionally provides context to the
|
|
||||||
* format request, allowing for scale-appropriate formatting.
|
|
||||||
* @returns {string} the formatted date
|
* @returns {string} the formatted date
|
||||||
*/
|
*/
|
||||||
LocalTimeFormat.prototype.format = function (value, scale) {
|
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);
|
return moment(value).format(DATE_FORMAT);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ define([], function () {
|
|||||||
this.timeFormat = 'local-format';
|
this.timeFormat = 'local-format';
|
||||||
this.durationFormat = 'duration';
|
this.durationFormat = 'duration';
|
||||||
|
|
||||||
this.isUTCBased = true;
|
this.isUTCBased = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return LocalTimeSystem;
|
return LocalTimeSystem;
|
||||||
|
@ -103,7 +103,7 @@ define([
|
|||||||
*/
|
*/
|
||||||
plugins.AutoflowView = AutoflowPlugin;
|
plugins.AutoflowView = AutoflowPlugin;
|
||||||
|
|
||||||
plugins.Conductor = TimeConductorPlugin;
|
plugins.Conductor = TimeConductorPlugin.default;
|
||||||
|
|
||||||
plugins.CouchDB = function (url) {
|
plugins.CouchDB = function (url) {
|
||||||
return function (openmct) {
|
return function (openmct) {
|
||||||
|
353
src/plugins/timeConductor/Conductor.vue
Normal file
353
src/plugins/timeConductor/Conductor.vue
Normal file
@ -0,0 +1,353 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT Web, Copyright (c) 2014-2018, 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
<template>
|
||||||
|
<!-- Parent holder for time conductor. follow-mode | fixed-mode -->
|
||||||
|
<div class="l-flex-row l-time-conductor"
|
||||||
|
:class="[isFixed ? 'fixed-mode' : 'realtime-mode', panning ? 'status-panning' : '']">
|
||||||
|
<div class="flex-elem holder time-conductor-icon">
|
||||||
|
<div class="hand-little"></div>
|
||||||
|
<div class="hand-big"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex-elem holder grows l-flex-col l-time-conductor-inner">
|
||||||
|
<!-- Holds inputs and ticks -->
|
||||||
|
<div class="l-time-conductor-inputs-and-ticks l-row-elem flex-elem no-margin">
|
||||||
|
<form class="l-time-conductor-inputs-holder" ref="conductorForm"
|
||||||
|
@submit="isFixed ? setBoundsFromView($event) : setOffsetsFromView($event)">
|
||||||
|
<span class="l-time-range-w start-w">
|
||||||
|
<span class="l-time-conductor-inputs">
|
||||||
|
<span class="l-time-range-input-w start-date">
|
||||||
|
<span class="title"></span>
|
||||||
|
<span class="time-range-input">
|
||||||
|
<input type="text" autocorrect="off" spellcheck="false"
|
||||||
|
ref="startDate"
|
||||||
|
v-model="formattedBounds.start"
|
||||||
|
@keyup="validateBounds('start', $event.target)"
|
||||||
|
@blur="setBoundsFromView()">
|
||||||
|
<date-picker :default-date-time="formattedBounds.start" :formatter="timeFormatter" @date-selected="startDateSelected"></date-picker>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span class="l-time-range-input-w time-delta start-delta"
|
||||||
|
:class="{'hide': isFixed}">
|
||||||
|
-
|
||||||
|
<span class="s-input-inline hrs-min-input">
|
||||||
|
<input type="text" autocorrect="off" spellcheck="false"
|
||||||
|
v-model="offsets.start"
|
||||||
|
@keyup="validateOffsets($event)"
|
||||||
|
@blur="setOffsetsFromView()">
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span class="l-time-range-w end-w">
|
||||||
|
<span class="l-time-conductor-inputs">
|
||||||
|
<span class="l-time-range-input-w end-date">
|
||||||
|
<span class="title"></span>
|
||||||
|
<span class="time-range-input">
|
||||||
|
<input type="text" autocorrect="off" spellcheck="false"
|
||||||
|
v-model="formattedBounds.end"
|
||||||
|
:disabled="!isFixed"
|
||||||
|
ref="endDate"
|
||||||
|
@keyup="validateBounds('end', $event.target)"
|
||||||
|
@blur="setBoundsFromView()">
|
||||||
|
<date-picker :default-date-time="formattedBounds.end" :formatter="timeFormatter" @date-selected="endDateSelected"></date-picker>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span class="l-time-range-input-w time-delta end-delta"
|
||||||
|
:class="{'hide': isFixed}">
|
||||||
|
+
|
||||||
|
<span class="s-input-inline hrs-min-input">
|
||||||
|
<input type="text" autocorrect="off" spellcheck="false"
|
||||||
|
v-model="offsets.end"
|
||||||
|
@keyup="validateOffsets($event)"
|
||||||
|
@blur="setOffsetsFromView()">
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<input type="submit" class="invisible">
|
||||||
|
</form>
|
||||||
|
<conductor-axis class="mobile-hide" :bounds="rawBounds" @panZoom="setViewFromBounds"></conductor-axis>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Holds time system and session selectors, and zoom control -->
|
||||||
|
<div class="l-time-conductor-controls l-row-elem l-flex-row flex-elem">
|
||||||
|
<ConductorMode></ConductorMode>
|
||||||
|
<ConductorTimeSystem></ConductorTimeSystem>
|
||||||
|
<!-- Zoom control -->
|
||||||
|
<div v-if="isUTCBased && isFixed"
|
||||||
|
class="l-time-conductor-zoom-w grows flex-elem l-flex-row">
|
||||||
|
{{currentZoomText}}
|
||||||
|
<span class="time-conductor-zoom-current-range flex-elem flex-fixed holder">{{timeUnits}}</span>
|
||||||
|
<input class="time-conductor-zoom flex-elem" type="range"
|
||||||
|
v-model="currentZoom"
|
||||||
|
@change="setBoundsFromView()"
|
||||||
|
min="0.01"
|
||||||
|
step="0.01"
|
||||||
|
max="0.99" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import "~styles/sass-base";
|
||||||
|
|
||||||
|
.l-time-conductor-inputs input:invalid {
|
||||||
|
border: 1px solid $colorFormInvalid !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.l-time-conductor-zoom-w {
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import moment from 'moment';
|
||||||
|
import ConductorMode from './ConductorMode.vue';
|
||||||
|
import ConductorTimeSystem from './ConductorTimeSystem.vue';
|
||||||
|
import DatePicker from './DatePicker.vue';
|
||||||
|
import ConductorAxis from './ConductorAxis.vue';
|
||||||
|
|
||||||
|
const DEFAULT_DURATION_FORMATTER = 'duration';
|
||||||
|
const SECONDS = 1000;
|
||||||
|
const DAYS = 24 * 60 * 60 * SECONDS;
|
||||||
|
const YEARS = 365 * DAYS;
|
||||||
|
|
||||||
|
const MAX_ZOOM_OUT = 10 * YEARS;
|
||||||
|
const MAX_ZOOM_IN = 5 * SECONDS;
|
||||||
|
const RESIZE_POLL_INTERVAL = 200;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
inject: ['openmct', 'configuration'],
|
||||||
|
components: {
|
||||||
|
ConductorMode,
|
||||||
|
ConductorTimeSystem,
|
||||||
|
DatePicker,
|
||||||
|
ConductorAxis
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
let bounds = this.openmct.time.bounds();
|
||||||
|
let offsets = this.openmct.time.clockOffsets();
|
||||||
|
let timeSystem = this.openmct.time.timeSystem();
|
||||||
|
let timeFormatter = this.getFormatter(timeSystem.timeFormat);
|
||||||
|
let durationFormatter = this.getFormatter(timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
||||||
|
|
||||||
|
return {
|
||||||
|
currentZoom: this.calculateZoomFromBounds(),
|
||||||
|
timeFormatter: timeFormatter,
|
||||||
|
durationFormatter: durationFormatter,
|
||||||
|
offsets: {
|
||||||
|
start: offsets && durationFormatter.format(Math.abs(offsets.start)),
|
||||||
|
end: offsets && durationFormatter.format(Math.abs(offsets.end)),
|
||||||
|
},
|
||||||
|
formattedBounds: {
|
||||||
|
start: timeFormatter.format(bounds.start),
|
||||||
|
end: timeFormatter.format(bounds.end)
|
||||||
|
},
|
||||||
|
rawBounds: {
|
||||||
|
start: bounds.start,
|
||||||
|
end: bounds.end
|
||||||
|
},
|
||||||
|
isFixed: this.openmct.time.clock() === undefined,
|
||||||
|
isUTCBased: timeSystem.isUTCBased,
|
||||||
|
showDatePicker: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
setTimeSystem(timeSystem) {
|
||||||
|
this.timeFormatter = this.getFormatter(timeSystem.timeFormat);
|
||||||
|
this.durationFormatter = this.getFormatter(
|
||||||
|
timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
||||||
|
|
||||||
|
this.isUTCBased = timeSystem.isUTCBased;
|
||||||
|
},
|
||||||
|
setOffsetsFromView($event) {
|
||||||
|
if (this.offsetsChanged() && this.$refs.conductorForm.checkValidity()){
|
||||||
|
let startOffset = 0 - this.durationFormatter.parse(this.offsets.start);
|
||||||
|
let endOffset = this.durationFormatter.parse(this.offsets.end);
|
||||||
|
|
||||||
|
this.openmct.time.clockOffsets({
|
||||||
|
start: startOffset,
|
||||||
|
end: endOffset
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if ($event) {
|
||||||
|
$event.preventDefault();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
offsetsChanged() {
|
||||||
|
let currentOffsets = this.openmct.time.clockOffsets();
|
||||||
|
return this.offsets.start !== currentOffsets.start ||
|
||||||
|
this.offsets.end !== currentOffsets.end;
|
||||||
|
},
|
||||||
|
setBoundsFromView($event) {
|
||||||
|
if (this.boundsChanged() && this.$refs.conductorForm.checkValidity()){
|
||||||
|
let start = this.timeFormatter.parse(this.formattedBounds.start);
|
||||||
|
let end = this.timeFormatter.parse(this.formattedBounds.end);
|
||||||
|
|
||||||
|
this.openmct.time.bounds({
|
||||||
|
start: start,
|
||||||
|
end: end
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if ($event) {
|
||||||
|
$event.preventDefault();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
boundsChanged() {
|
||||||
|
let currentBounds = this.openmct.time.bounds();
|
||||||
|
return this.timeFormatter.parse(this.formattedBounds.start) !== currentBounds.start ||
|
||||||
|
this.timeFormatter.parse(this.formattedBounds.end) !== currentBounds.end;
|
||||||
|
},
|
||||||
|
showValidityMessage($event) {
|
||||||
|
$event.target.reportValidity();
|
||||||
|
},
|
||||||
|
setViewFromClock(clock) {
|
||||||
|
this.isFixed = clock === undefined;
|
||||||
|
},
|
||||||
|
setViewFromBounds(bounds) {
|
||||||
|
this.formattedBounds.start = this.timeFormatter.format(bounds.start);
|
||||||
|
this.formattedBounds.end = this.timeFormatter.format(bounds.end);
|
||||||
|
this.rawBounds.start = bounds.start;
|
||||||
|
this.rawBounds.end = bounds.end;
|
||||||
|
},
|
||||||
|
setViewFromOffsets(offsets) {
|
||||||
|
this.offsets.start = this.durationFormatter.format(Math.abs(offsets.start));
|
||||||
|
this.offsets.end = this.durationFormatter.format(Math.abs(offsets.end));
|
||||||
|
},
|
||||||
|
showValidityMessage($event) {
|
||||||
|
$event.target.reportValidity();
|
||||||
|
},
|
||||||
|
validateBounds(startOrEnd, input) {
|
||||||
|
let validationResult = true;
|
||||||
|
|
||||||
|
if (!this.timeFormatter.validate(input.value)){
|
||||||
|
validationResult = 'Invalid date value';
|
||||||
|
} else {
|
||||||
|
let boundsValues = {
|
||||||
|
start: this.timeFormatter.parse(this.formattedBounds.start),
|
||||||
|
end: this.timeFormatter.parse(this.formattedBounds.end)
|
||||||
|
};
|
||||||
|
validationResult = this.openmct.time.validateBounds(boundsValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validationResult !== true){
|
||||||
|
input.setCustomValidity(validationResult);
|
||||||
|
} else {
|
||||||
|
input.setCustomValidity('');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
validateOffsets(event) {
|
||||||
|
let input = event.target;
|
||||||
|
let validationResult = true;
|
||||||
|
|
||||||
|
if (!this.durationFormatter.validate(input.value)) {
|
||||||
|
validationResult = 'Invalid offset value';
|
||||||
|
} else {
|
||||||
|
let offsetValues = {
|
||||||
|
start: 0 - this.durationFormatter.parse(this.offsets.start),
|
||||||
|
end: this.durationFormatter.parse(this.offsets.end)
|
||||||
|
};
|
||||||
|
validationResult = this.openmct.time.validateOffsets(offsetValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validationResult !== true){
|
||||||
|
input.setCustomValidity(validationResult);
|
||||||
|
} else {
|
||||||
|
input.setCustomValidity('');
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
getFormatter(key) {
|
||||||
|
return this.openmct.telemetry.getValueFormatter({
|
||||||
|
format: key
|
||||||
|
}).formatter;
|
||||||
|
},
|
||||||
|
startDateSelected(date){
|
||||||
|
this.formattedBounds.start = this.timeFormatter.format(date);
|
||||||
|
this.validateBounds('start', this.$refs.startDate);
|
||||||
|
this.setBoundsFromView();
|
||||||
|
},
|
||||||
|
endDateSelected(date){
|
||||||
|
this.formattedBounds.end = this.timeFormatter.format(date);
|
||||||
|
this.validateBounds('end', this.$refs.endDate);
|
||||||
|
this.setBoundsFromView();
|
||||||
|
},
|
||||||
|
zoomLevelToTimespan() {
|
||||||
|
let minMaxDelta = MAX_ZOOM_OUT - MAX_ZOOM_IN;
|
||||||
|
return minMaxDelta * Math.pow((1 - this.currentZoom), 4);
|
||||||
|
},
|
||||||
|
zoom() {
|
||||||
|
let zoomTimespan = this.zoomLevelToTimespan();
|
||||||
|
let start = this.openmct.time.bounds().start;
|
||||||
|
let end = this.openmct.time.bounds().end
|
||||||
|
let currentTimeSpan = end - start;
|
||||||
|
let delta = currentTimeSpan - zoomTimespan;
|
||||||
|
let bounds = {
|
||||||
|
start: start + delta / 2,
|
||||||
|
end: end - delta / 2
|
||||||
|
};
|
||||||
|
this.rawBounds = bounds;
|
||||||
|
this.setViewFromBounds(bounds);
|
||||||
|
this.zooming = false;
|
||||||
|
},
|
||||||
|
calculateZoomFromBounds() {
|
||||||
|
let start = this.openmct.time.bounds().start;
|
||||||
|
let end = this.openmct.time.bounds().end
|
||||||
|
let zoomMaxMinDelta = MAX_ZOOM_OUT - MAX_ZOOM_IN;
|
||||||
|
let currentBoundsDelta = end - start;
|
||||||
|
|
||||||
|
return 1 - Math.pow(currentBoundsDelta / zoomMaxMinDelta, 1 / 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
currentZoomText() {
|
||||||
|
return moment.duration(this.zoomLevelToTimespan()).humanize();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
currentZoom() {
|
||||||
|
if (!this.zooming) {
|
||||||
|
this.zooming = true;
|
||||||
|
requestAnimationFrame(this.zoom, RESIZE_POLL_INTERVAL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.zooming = false;
|
||||||
|
this.setTimeSystem(JSON.parse(JSON.stringify(this.openmct.time.timeSystem())));
|
||||||
|
|
||||||
|
this.openmct.time.on('bounds', this.setViewFromBounds);
|
||||||
|
this.openmct.time.on('timeSystem', this.setTimeSystem);
|
||||||
|
this.openmct.time.on('clock', this.setViewFromClock);
|
||||||
|
this.openmct.time.on('clockOffsets', this.setViewFromOffsets)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
177
src/plugins/timeConductor/ConductorAxis.vue
Normal file
177
src/plugins/timeConductor/ConductorAxis.vue
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT Web, Copyright (c) 2014-2018, 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
<template>
|
||||||
|
<div class="l-axis-holder" ref="axisHolder"
|
||||||
|
@mousedown="dragStart($event)"></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.l-axis-holder {
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import * as d3Selection from 'd3-selection';
|
||||||
|
import * as d3Axis from 'd3-axis';
|
||||||
|
import * as d3Scale from 'd3-scale';
|
||||||
|
import utcMultiTimeFormat from './utcMultiTimeFormat.js';
|
||||||
|
|
||||||
|
const PADDING = 1;
|
||||||
|
const DEFAULT_DURATION_FORMATTER = 'duration';
|
||||||
|
const RESIZE_POLL_INTERVAL = 200;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
inject: ['openmct'],
|
||||||
|
props: {
|
||||||
|
bounds: Object
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
setScale() {
|
||||||
|
let timeSystem = this.openmct.time.timeSystem();
|
||||||
|
let bounds = this.bounds;
|
||||||
|
|
||||||
|
if (timeSystem.isUTCBased) {
|
||||||
|
this.xScale.domain([new Date(bounds.start), new Date(bounds.end)]);
|
||||||
|
} else {
|
||||||
|
this.xScale.domain([bounds.start, bounds.end]);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.xAxis.scale(this.xScale);
|
||||||
|
|
||||||
|
this.xScale.range([PADDING, this.width - PADDING * 2]);
|
||||||
|
this.axisElement.call(this.xAxis);
|
||||||
|
|
||||||
|
this.msPerPixel = (bounds.end - bounds.start) / this.width;
|
||||||
|
},
|
||||||
|
setViewFromTimeSystem(timeSystem) {
|
||||||
|
let format = this.getActiveFormatter();
|
||||||
|
let bounds = this.openmct.time.bounds();
|
||||||
|
|
||||||
|
//The D3 scale used depends on the type of time system as d3
|
||||||
|
// supports UTC out of the box.
|
||||||
|
if (timeSystem.isUTCBased) {
|
||||||
|
this.xScale = d3Scale.scaleUtc();
|
||||||
|
} else {
|
||||||
|
this.xScale = d3Scale.scaleLinear();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.xAxis.scale(this.xScale);
|
||||||
|
this.xAxis.tickFormat(utcMultiTimeFormat);
|
||||||
|
this.axisElement.call(this.xAxis);
|
||||||
|
},
|
||||||
|
getActiveFormatter() {
|
||||||
|
let timeSystem = this.openmct.time.timeSystem();
|
||||||
|
let isFixed = this.openmct.time.clock() === undefined;
|
||||||
|
|
||||||
|
if (isFixed) {
|
||||||
|
return this.getFormatter(timeSystem.timeFormat);
|
||||||
|
} else {
|
||||||
|
return this.getFormatter(timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getFormatter(key) {
|
||||||
|
return this.openmct.telemetry.getValueFormatter({
|
||||||
|
format: key
|
||||||
|
}).formatter;
|
||||||
|
},
|
||||||
|
dragStart($event){
|
||||||
|
let isFixed = this.openmct.time.clock() === undefined;
|
||||||
|
if (isFixed){
|
||||||
|
this.dragStartX = $event.clientX;
|
||||||
|
|
||||||
|
document.addEventListener('mousemove', this.drag);
|
||||||
|
document.addEventListener('mouseup', this.dragEnd, {
|
||||||
|
once: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
drag($event) {
|
||||||
|
if (!this.dragging){
|
||||||
|
this.dragging = true;
|
||||||
|
requestAnimationFrame(()=>{
|
||||||
|
let deltaX = $event.clientX - this.dragStartX;
|
||||||
|
let percX = deltaX / this.width;
|
||||||
|
let bounds = this.openmct.time.bounds();
|
||||||
|
let deltaTime = bounds.end - bounds.start;
|
||||||
|
let newStart = bounds.start - percX * deltaTime;
|
||||||
|
this.bounds = {
|
||||||
|
start: newStart,
|
||||||
|
end: newStart + deltaTime
|
||||||
|
};
|
||||||
|
this.$emit('panZoom', this.bounds);
|
||||||
|
this.dragging = false;
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
console.log('Rejected drag due to RAF cap');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dragEnd() {
|
||||||
|
document.removeEventListener('mousemove', this.drag);
|
||||||
|
this.openmct.time.bounds({
|
||||||
|
start: this.bounds.start,
|
||||||
|
end: this.bounds.end
|
||||||
|
});
|
||||||
|
},
|
||||||
|
resize() {
|
||||||
|
if (this.$refs.axisHolder.clientWidth !== this.width) {
|
||||||
|
this.width = this.$refs.axisHolder.clientWidth;
|
||||||
|
this.setScale();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
bounds: {
|
||||||
|
handler(bounds) {
|
||||||
|
this.setScale();
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
let axisHolder = this.$refs.axisHolder;
|
||||||
|
let height = axisHolder.offsetHeight;
|
||||||
|
let vis = d3Selection.select(axisHolder)
|
||||||
|
.append("svg:svg")
|
||||||
|
.attr("width", "100%")
|
||||||
|
.attr("height", height);
|
||||||
|
|
||||||
|
this.width = this.$refs.axisHolder.clientWidth;
|
||||||
|
this.xAxis = d3Axis.axisTop();
|
||||||
|
this.dragging = false;
|
||||||
|
|
||||||
|
// draw x axis with labels. CSS is used to position them.
|
||||||
|
this.axisElement = vis.append("g");
|
||||||
|
|
||||||
|
this.setViewFromTimeSystem(this.openmct.time.timeSystem());
|
||||||
|
this.setScale();
|
||||||
|
|
||||||
|
//Respond to changes in conductor
|
||||||
|
this.openmct.time.on("timeSystem", this.setViewFromTimeSystem);
|
||||||
|
setInterval(this.resize, RESIZE_POLL_INTERVAL);
|
||||||
|
},
|
||||||
|
destroyed() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
</script>
|
196
src/plugins/timeConductor/ConductorMode.vue
Normal file
196
src/plugins/timeConductor/ConductorMode.vue
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT Web, Copyright (c) 2014-2018, 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
<template>
|
||||||
|
<div class="holder flex-elem menus-up time-system">
|
||||||
|
<span>
|
||||||
|
<div class="s-menu-button" @click="toggleMenu($event)">
|
||||||
|
<span class="title-label">{{selectedMode.name}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="menu super-menu mini l-mode-selector-menu"
|
||||||
|
v-if="showMenu">
|
||||||
|
<div class="w-menu">
|
||||||
|
<div class="col menu-items">
|
||||||
|
<ul>
|
||||||
|
<li v-for="mode in modes"
|
||||||
|
:key="mode.key"
|
||||||
|
@click="setOption(mode)">
|
||||||
|
<a @mouseover="hoveredMode = mode"
|
||||||
|
@mouseleave="hoveredMode = {}"
|
||||||
|
class="menu-item-a"
|
||||||
|
:class="mode.cssClass">
|
||||||
|
{{mode.name}}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="col menu-item-description">
|
||||||
|
<div class="desc-area ui-symbol icon type-icon" :class="hoveredMode.cssClass"></div>
|
||||||
|
<div class="w-title-desc">
|
||||||
|
<div class="desc-area title">
|
||||||
|
{{hoveredMode.name}}
|
||||||
|
</div>
|
||||||
|
<div class="desc-area description">
|
||||||
|
{{hoveredMode.description}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
inject: ['openmct', 'configuration'],
|
||||||
|
data: function () {
|
||||||
|
let activeClock = this.openmct.time.clock();
|
||||||
|
if (activeClock !== undefined) {
|
||||||
|
//Create copy of active clock so the time API does not get reactified.
|
||||||
|
activeClock = Object.create(activeClock);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
selectedMode: this.getModeOptionForClock(activeClock),
|
||||||
|
selectedTimeSystem: JSON.parse(JSON.stringify(this.openmct.time.timeSystem())),
|
||||||
|
modes: [],
|
||||||
|
hoveredMode: {},
|
||||||
|
showMenu: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
loadClocksFromConfiguration() {
|
||||||
|
let clocks = this.configuration.menuOptions
|
||||||
|
.map(menuOption => menuOption.clock)
|
||||||
|
.filter(isDefinedAndUnique)
|
||||||
|
.map(this.getClock);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Populate the modes menu with metadata from the available clocks
|
||||||
|
* "Fixed Mode" is always first, and has no defined clock
|
||||||
|
*/
|
||||||
|
this.modes = [this.getModeOptionForClock(undefined)]
|
||||||
|
.concat(clocks)
|
||||||
|
.map(this.getModeOptionForClock);
|
||||||
|
|
||||||
|
function isDefinedAndUnique(key, index, array) {
|
||||||
|
return key!== undefined && array.indexOf(key) === index;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getModeOptionForClock(clock) {
|
||||||
|
if (clock === undefined) {
|
||||||
|
return {
|
||||||
|
key: 'fixed',
|
||||||
|
name: 'Fixed Timespan Mode',
|
||||||
|
description: 'Query and explore data that falls between two fixed datetimes.',
|
||||||
|
cssClass: 'icon-calendar'
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
key: clock.key,
|
||||||
|
name: clock.name,
|
||||||
|
description: "Monitor streaming data in real-time. The Time " +
|
||||||
|
"Conductor and displays will automatically advance themselves based on this clock. " + clock.description,
|
||||||
|
cssClass: clock.cssClass || 'icon-clock'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getClock(key) {
|
||||||
|
return this.openmct.time.getAllClocks().filter(function (clock) {
|
||||||
|
return clock.key === key;
|
||||||
|
})[0];
|
||||||
|
},
|
||||||
|
|
||||||
|
setOption(option) {
|
||||||
|
let clockKey = option.key;
|
||||||
|
if (clockKey === 'fixed') {
|
||||||
|
clockKey = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
let configuration = this.getMatchingConfig({
|
||||||
|
clock: clockKey,
|
||||||
|
timeSystem: this.openmct.time.timeSystem().key
|
||||||
|
});
|
||||||
|
|
||||||
|
if (configuration === undefined) {
|
||||||
|
configuration = this.getMatchingConfig({
|
||||||
|
clock: clockKey
|
||||||
|
});
|
||||||
|
|
||||||
|
this.openmct.time.timeSystem(configuration.timeSystem, configuration.bounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clockKey === undefined) {
|
||||||
|
this.openmct.time.stopClock();
|
||||||
|
} else {
|
||||||
|
this.openmct.time.clock(clockKey, configuration.clockOffsets);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getMatchingConfig(options) {
|
||||||
|
const matchers = {
|
||||||
|
clock(config) {
|
||||||
|
return options.clock === config.clock
|
||||||
|
},
|
||||||
|
timeSystem(config) {
|
||||||
|
return options.timeSystem === config.timeSystem
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function configMatches(config) {
|
||||||
|
return Object.keys(options).reduce((match, option) => {
|
||||||
|
return match && matchers[option](config);
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.configuration.menuOptions.filter(configMatches)[0];
|
||||||
|
},
|
||||||
|
|
||||||
|
setViewFromClock(clock) {
|
||||||
|
this.selectedMode = this.getModeOptionForClock(clock);
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleMenu(event) {
|
||||||
|
this.showMenu = !this.showMenu;
|
||||||
|
|
||||||
|
if (this.showMenu) {
|
||||||
|
document.addEventListener('click', this.toggleMenu, true);
|
||||||
|
} else {
|
||||||
|
document.removeEventListener('click', this.toggleMenu, true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted: function () {
|
||||||
|
this.loadClocksFromConfiguration();
|
||||||
|
|
||||||
|
this.openmct.time.on('clock', this.setViewFromClock);
|
||||||
|
},
|
||||||
|
destroyed: function () {
|
||||||
|
this.openmct.time.off('clock', this.setViewFromClock);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
</script>
|
132
src/plugins/timeConductor/ConductorTimeSystem.vue
Normal file
132
src/plugins/timeConductor/ConductorTimeSystem.vue
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT Web, Copyright (c) 2014-2018, 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
<template>
|
||||||
|
<div class="holder flex-elem menus-up time-system">
|
||||||
|
<div class="s-menu-button menu-element"
|
||||||
|
:class="selectedTimeSystem.cssClass">
|
||||||
|
|
||||||
|
<span class="l-click-area" @click="toggleMenu($event)"></span>
|
||||||
|
<span class="title-label" v-if="selectedTimeSystem.name">
|
||||||
|
{{selectedTimeSystem.name}}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div class="menu" v-if="showMenu">
|
||||||
|
<ul>
|
||||||
|
<li @click="setTimeSystemFromView(timeSystem)"
|
||||||
|
v-for="timeSystem in timeSystems"
|
||||||
|
:key="timeSystem.key"
|
||||||
|
:class="timeSystem.cssClass">
|
||||||
|
{{timeSystem.name}}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
inject: ['openmct', 'configuration'],
|
||||||
|
data: function () {
|
||||||
|
let activeClock = this.openmct.time.clock();
|
||||||
|
|
||||||
|
return {
|
||||||
|
selectedTimeSystem: JSON.parse(JSON.stringify(this.openmct.time.timeSystem())),
|
||||||
|
timeSystems: this.getValidTimesystemsForClock(activeClock),
|
||||||
|
showMenu: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getValidTimesystemsForClock(clock) {
|
||||||
|
return this.configuration.menuOptions
|
||||||
|
.filter(menuOption => menuOption.clock === (clock && clock.key))
|
||||||
|
.map(menuOption => JSON.parse(JSON.stringify(this.openmct.time.timeSystems.get(menuOption.timeSystem))));
|
||||||
|
},
|
||||||
|
|
||||||
|
setTimeSystemFromView(timeSystem) {
|
||||||
|
if (timeSystem !== this.selectedTimeSystem) {
|
||||||
|
let activeClock = this.openmct.time.clock();
|
||||||
|
let configuration = this.getMatchingConfig({
|
||||||
|
clock: activeClock && activeClock.key,
|
||||||
|
timeSystem: timeSystem.key
|
||||||
|
});
|
||||||
|
if (activeClock === undefined) {
|
||||||
|
this.openmct.time.timeSystem(timeSystem.key, configuration.bounds);
|
||||||
|
} else {
|
||||||
|
this.openmct.time.timeSystem(timeSystem.key);
|
||||||
|
this.openmct.time.clockOffsets(configuration.clockOffsets);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getMatchingConfig(options) {
|
||||||
|
const matchers = {
|
||||||
|
clock(config) {
|
||||||
|
return options.clock === config.clock
|
||||||
|
},
|
||||||
|
timeSystem(config) {
|
||||||
|
return options.timeSystem === config.timeSystem
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function configMatches(config) {
|
||||||
|
return Object.keys(options).reduce((match, option) => {
|
||||||
|
return match && matchers[option](config);
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.configuration.menuOptions.filter(configMatches)[0];
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleMenu(event) {
|
||||||
|
this.showMenu = !this.showMenu;
|
||||||
|
|
||||||
|
if (this.showMenu) {
|
||||||
|
document.addEventListener('click', this.toggleMenu, true);
|
||||||
|
} else {
|
||||||
|
document.removeEventListener('click', this.toggleMenu, true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
setViewFromTimeSystem(timeSystem) {
|
||||||
|
this.selectedTimeSystem = timeSystem;
|
||||||
|
},
|
||||||
|
|
||||||
|
setViewFromClock(clock) {
|
||||||
|
let activeClock = this.openmct.time.clock();
|
||||||
|
this.timeSystems = this.getValidTimesystemsForClock(activeClock);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted: function () {
|
||||||
|
this.openmct.time.on('timeSystem', this.setViewFromTimeSystem);
|
||||||
|
this.openmct.time.on('clock', this.setViewFromClock);
|
||||||
|
},
|
||||||
|
destroyed: function () {
|
||||||
|
this.openmct.time.off('timeSystem', this.setViewFromTimeSystem);
|
||||||
|
this.openmct.time.on('clock', this.setViewFromClock);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
</script>
|
242
src/plugins/timeConductor/DatePicker.vue
Normal file
242
src/plugins/timeConductor/DatePicker.vue
Normal file
@ -0,0 +1,242 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT Web, Copyright (c) 2014-2018, 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
<template>
|
||||||
|
<span class="menus-up">
|
||||||
|
<a class="ui-symbol icon icon-calendar" ref="calendarButton" @click="togglePicker($event)"></a>
|
||||||
|
<div class="l-datetime-picker s-datetime-picker s-menu" v-if="showPicker" ref="popup">
|
||||||
|
<div class="holder">
|
||||||
|
<div class="l-month-year-pager">
|
||||||
|
<a class="pager prev" ng-click="changeMonth(-1)"></a>
|
||||||
|
<span class="val">{{model.month}} {{model.year}}</span>
|
||||||
|
<a class="pager next" ng-click="changeMonth(1)"></a>
|
||||||
|
</div>
|
||||||
|
<div class="l-calendar">
|
||||||
|
<ul class="l-cal-row l-header">
|
||||||
|
<li v-for="day in ['Su','Mo','Tu','We','Th','Fr','Sa']" :key="day">{{day}}</li>
|
||||||
|
</ul>
|
||||||
|
<ul class="l-cal-row l-body" v-for="(row, index) in table" :key="index">
|
||||||
|
<li v-for="(cell, index) in row"
|
||||||
|
:key="index"
|
||||||
|
@click="select(cell)"
|
||||||
|
:class='{ "in-month": isInCurrentMonth(cell), selected: isSelected(cell) }'>
|
||||||
|
<div class="prime">{{cell.day}}</div>
|
||||||
|
<div class="sub">{{cell.dayOfYear}}</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
|
const TIME_NAMES = {
|
||||||
|
'hours': "Hour",
|
||||||
|
'minutes': "Minute",
|
||||||
|
'seconds': "Second"
|
||||||
|
};
|
||||||
|
const MONTHS = moment.months();
|
||||||
|
const TIME_OPTIONS = (function makeRanges() {
|
||||||
|
let arr = [];
|
||||||
|
while (arr.length < 60) {
|
||||||
|
arr.push(arr.length);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
hours: arr.slice(0, 24),
|
||||||
|
minutes: arr,
|
||||||
|
seconds: arr
|
||||||
|
};
|
||||||
|
}());
|
||||||
|
|
||||||
|
export default {
|
||||||
|
inject: ['openmct'],
|
||||||
|
props: {
|
||||||
|
defaultDateTime: String,
|
||||||
|
formatter: Object
|
||||||
|
},
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
showPicker: false,
|
||||||
|
picker: {
|
||||||
|
year: undefined,
|
||||||
|
month: undefined,
|
||||||
|
interacted: false
|
||||||
|
},
|
||||||
|
model: {
|
||||||
|
year: undefined,
|
||||||
|
month: undefined,
|
||||||
|
},
|
||||||
|
table: undefined,
|
||||||
|
date: undefined,
|
||||||
|
time: undefined
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
generateTable() {
|
||||||
|
let m = moment.utc({ year: this.picker.year, month: this.picker.month }).day(0),
|
||||||
|
table = [],
|
||||||
|
row,
|
||||||
|
col;
|
||||||
|
|
||||||
|
for (row = 0; row < 6; row += 1) {
|
||||||
|
table.push([]);
|
||||||
|
for (col = 0; col < 7; col += 1) {
|
||||||
|
table[row].push({
|
||||||
|
year: m.year(),
|
||||||
|
month: m.month(),
|
||||||
|
day: m.date(),
|
||||||
|
dayOfYear: m.dayOfYear()
|
||||||
|
});
|
||||||
|
m.add(1, 'days'); // Next day!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return table;
|
||||||
|
},
|
||||||
|
|
||||||
|
updateViewForMonth() {
|
||||||
|
this.model.month = MONTHS[this.picker.month];
|
||||||
|
this.model.year = this.picker.year;
|
||||||
|
this.table = this.generateTable();
|
||||||
|
},
|
||||||
|
|
||||||
|
updateFromModel(defaultDateTime) {
|
||||||
|
let m;
|
||||||
|
|
||||||
|
m = moment.utc(defaultDateTime);
|
||||||
|
|
||||||
|
this.date = {
|
||||||
|
year: m.year(),
|
||||||
|
month: m.month(),
|
||||||
|
day: m.date()
|
||||||
|
};
|
||||||
|
this.time = {
|
||||||
|
hours: m.hour(),
|
||||||
|
minutes: m.minute(),
|
||||||
|
seconds: m.second()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Zoom to that date in the picker, but
|
||||||
|
// only if the user hasn't interacted with it yet.
|
||||||
|
if (!this.picker.interacted) {
|
||||||
|
this.picker.year = m.year();
|
||||||
|
this.picker.month = m.month();
|
||||||
|
this.updateViewForMonth();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
updateFromView() {
|
||||||
|
let m = moment.utc({
|
||||||
|
year: this.date.year,
|
||||||
|
month: this.date.month,
|
||||||
|
day: this.date.day,
|
||||||
|
hour: this.time.hours,
|
||||||
|
minute: this.time.minutes,
|
||||||
|
second: this.time.seconds
|
||||||
|
});
|
||||||
|
this.$emit('date-selected', m.valueOf());
|
||||||
|
},
|
||||||
|
|
||||||
|
isInCurrentMonth(cell) {
|
||||||
|
return cell.month === this.picker.month;
|
||||||
|
},
|
||||||
|
|
||||||
|
isSelected(cell) {
|
||||||
|
let date = this.date || {};
|
||||||
|
return cell.day === date.day &&
|
||||||
|
cell.month === date.month &&
|
||||||
|
cell.year === date.year;
|
||||||
|
},
|
||||||
|
|
||||||
|
select(cell) {
|
||||||
|
this.date = this.date || {};
|
||||||
|
this.date.month = cell.month;
|
||||||
|
this.date.year = cell.year;
|
||||||
|
this.date.day = cell.day;
|
||||||
|
this.updateFromView();
|
||||||
|
},
|
||||||
|
|
||||||
|
dateEquals(d1, d2) {
|
||||||
|
return d1.year === d2.year &&
|
||||||
|
d1.month === d2.month &&
|
||||||
|
d1.day === d2.day;
|
||||||
|
},
|
||||||
|
|
||||||
|
changeMonth(delta) {
|
||||||
|
picker.month += delta;
|
||||||
|
if (picker.month > 11) {
|
||||||
|
picker.month = 0;
|
||||||
|
picker.year += 1;
|
||||||
|
}
|
||||||
|
if (picker.month < 0) {
|
||||||
|
picker.month = 11;
|
||||||
|
picker.year -= 1;
|
||||||
|
}
|
||||||
|
this.picker.interacted = true;
|
||||||
|
this.updateViewForMonth();
|
||||||
|
},
|
||||||
|
|
||||||
|
nameFor(key) {
|
||||||
|
return TIME_NAMES[key];
|
||||||
|
},
|
||||||
|
|
||||||
|
optionsFor(key) {
|
||||||
|
return TIME_OPTIONS[key];
|
||||||
|
},
|
||||||
|
|
||||||
|
hidePicker(event) {
|
||||||
|
if (event.srcElement !== this.$refs.calendarButton){
|
||||||
|
this.showPicker = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
togglePicker(event) {
|
||||||
|
this.showPicker = !this.showPicker;
|
||||||
|
|
||||||
|
if (this.showPicker) {
|
||||||
|
document.addEventListener('click', this.hidePicker, {
|
||||||
|
capture: true,
|
||||||
|
once: true,
|
||||||
|
passive: true
|
||||||
|
});
|
||||||
|
this.$nextTick().then(this.setPopupPosition);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
setPopupPosition() {
|
||||||
|
this.$refs.popup.style.bottom = this.$refs.popup.offsetHeight + 20 + 'px';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted: function () {
|
||||||
|
this.updateFromModel(this.defaultDateTime);
|
||||||
|
this.updateViewForMonth();
|
||||||
|
},
|
||||||
|
destroyed: function () {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
</script>
|
@ -20,13 +20,14 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
define([], function () {
|
import Conductor from './Conductor.vue';
|
||||||
|
import Vue from 'vue';
|
||||||
|
|
||||||
function isTruthy(a) {
|
function isTruthy(a) {
|
||||||
return !!a;
|
return !!a;
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateMenuOption(menuOption, index) {
|
function validateMenuOption(menuOption, index) {
|
||||||
if (menuOption.clock && !menuOption.clockOffsets) {
|
if (menuOption.clock && !menuOption.clockOffsets) {
|
||||||
return "clock-based menuOption at index " + index + " is " +
|
return "clock-based menuOption at index " + index + " is " +
|
||||||
"missing required property 'clockOffsets'.";
|
"missing required property 'clockOffsets'.";
|
||||||
@ -39,9 +40,9 @@ define([], function () {
|
|||||||
return "fixed-bounds menuOption at index " + index + " is " +
|
return "fixed-bounds menuOption at index " + index + " is " +
|
||||||
"missing required property 'bounds'";
|
"missing required property 'bounds'";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateConfiguration(config) {
|
function validateConfiguration(config) {
|
||||||
if (config === undefined ||
|
if (config === undefined ||
|
||||||
config.menuOptions === undefined ||
|
config.menuOptions === undefined ||
|
||||||
config.menuOptions.length === 0) {
|
config.menuOptions.length === 0) {
|
||||||
@ -53,9 +54,9 @@ define([], function () {
|
|||||||
.join('\n');
|
.join('\n');
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateRuntimeConfiguration(config, openmct) {
|
function validateRuntimeConfiguration(config, openmct) {
|
||||||
var systems = openmct.time.getAllTimeSystems()
|
var systems = openmct.time.getAllTimeSystems()
|
||||||
.reduce(function (m, ts) {
|
.reduce(function (m, ts) {
|
||||||
m[ts.key] = ts;
|
m[ts.key] = ts;
|
||||||
@ -76,44 +77,40 @@ define([], function () {
|
|||||||
return "menuOption at index " + index + " specifies a " +
|
return "menuOption at index " + index + " specifies a " +
|
||||||
"clock that does not exist: " + menuOption.clock;
|
"clock that does not exist: " + menuOption.clock;
|
||||||
}
|
}
|
||||||
})
|
}).filter(isTruthy).join('\n');
|
||||||
.filter(isTruthy)
|
}
|
||||||
.join('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
function throwConfigErrorIfExists(error) {
|
function throwIfError(configResult) {
|
||||||
if (error) {
|
if (configResult) {
|
||||||
throw new Error("Invalid Time Conductor Configuration: \n" +
|
throw new Error("Invalid Time Conductor Configuration: \n" +
|
||||||
error + '\n' +
|
configResult + '\n' +
|
||||||
"https://github.com/nasa/openmct/blob/master/API.md#the-time-conductor");
|
"https://github.com/nasa/openmct/blob/master/API.md#the-time-conductor");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mountComponent(openmct, configuration) {
|
||||||
|
openmct.layout.conductorComponent = Object.create({
|
||||||
|
components: {
|
||||||
|
Conductor
|
||||||
|
},
|
||||||
|
template: "<conductor></conductor>",
|
||||||
|
provide: {
|
||||||
|
openmct: openmct,
|
||||||
|
configuration: configuration
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return function (config) {
|
export default function (config){
|
||||||
|
|
||||||
throwConfigErrorIfExists(validateConfiguration(config));
|
let configResult = validateConfiguration(config);
|
||||||
|
throwIfError(configResult);
|
||||||
|
|
||||||
return function (openmct) {
|
return function (openmct) {
|
||||||
|
|
||||||
openmct.legacyExtension('constants', {
|
|
||||||
key: 'CONDUCTOR_CONFIG',
|
|
||||||
value: config,
|
|
||||||
priority: 'mandatory'
|
|
||||||
});
|
|
||||||
|
|
||||||
openmct.legacyRegistry.enable('platform/features/conductor/core');
|
|
||||||
openmct.legacyRegistry.enable('platform/features/conductor/compatibility');
|
|
||||||
|
|
||||||
openmct.on('start', function () {
|
openmct.on('start', function () {
|
||||||
|
configResult = validateRuntimeConfiguration(config, openmct);
|
||||||
throwConfigErrorIfExists(validateRuntimeConfiguration(config, openmct));
|
throwIfError(configResult);
|
||||||
|
|
||||||
/*
|
|
||||||
On app startup, default the conductor if not already set.
|
|
||||||
*/
|
|
||||||
if (openmct.time.timeSystem() !== undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var defaults = config.menuOptions[0];
|
var defaults = config.menuOptions[0];
|
||||||
if (defaults.clock) {
|
if (defaults.clock) {
|
||||||
@ -123,7 +120,8 @@ define([], function () {
|
|||||||
openmct.time.timeSystem(defaults.timeSystem, defaults.bounds);
|
openmct.time.timeSystem(defaults.timeSystem, defaults.bounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mountComponent(openmct, config);
|
||||||
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
});
|
|
||||||
|
@ -20,34 +20,47 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
define([], function () {
|
import moment from 'moment';
|
||||||
|
|
||||||
|
export default function multiFormat(date) {
|
||||||
|
var momentified = moment.utc(date);
|
||||||
/**
|
/**
|
||||||
* Formatter for basic strings.
|
* 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
|
||||||
*
|
*
|
||||||
* @implements {Format}
|
* Licensed
|
||||||
* @constructor
|
|
||||||
* @memberof platform/commonUI/formats
|
|
||||||
*/
|
*/
|
||||||
function StringFormat() {
|
var format = [
|
||||||
this.key = 'string';
|
[".SSS", function (m) {
|
||||||
|
return m.milliseconds();
|
||||||
|
}],
|
||||||
|
[":ss", function (m) {
|
||||||
|
return m.seconds();
|
||||||
|
}],
|
||||||
|
["HH:mm", function (m) {
|
||||||
|
return m.minutes();
|
||||||
|
}],
|
||||||
|
["HH:mm", 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](momentified);
|
||||||
|
})[0][0];
|
||||||
|
|
||||||
|
if (format !== undefined) {
|
||||||
|
return moment.utc(date).format(format);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
StringFormat.prototype.format = function (string) {
|
|
||||||
if (typeof string === 'string') {
|
|
||||||
return string;
|
|
||||||
} else {
|
|
||||||
return '' + string;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
StringFormat.prototype.parse = function (string) {
|
|
||||||
return string;
|
|
||||||
};
|
|
||||||
|
|
||||||
StringFormat.prototype.validate = function (string) {
|
|
||||||
return typeof string === 'string';
|
|
||||||
};
|
|
||||||
|
|
||||||
return StringFormat;
|
|
||||||
});
|
|
@ -1,15 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div></div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
<mct-include key="'conductor'" class="abs holder flex-elem flex-fixed l-flex-row l-time-conductor-holder"></mct-include>
|
|
||||||
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
|
@ -34,9 +34,8 @@
|
|||||||
<object-view class="l-shell__main-container"
|
<object-view class="l-shell__main-container"
|
||||||
ref="browseObject">
|
ref="browseObject">
|
||||||
</object-view>
|
</object-view>
|
||||||
<mct-template template-key="conductor"
|
<component class="l-shell__time-conductor"
|
||||||
class="l-shell__time-conductor">
|
:is="conductorComponent"></component>
|
||||||
</mct-template>
|
|
||||||
</pane>
|
</pane>
|
||||||
<pane class="l-shell__pane-inspector l-pane--holds-multipane"
|
<pane class="l-shell__pane-inspector l-pane--holds-multipane"
|
||||||
handle="before"
|
handle="before"
|
||||||
@ -242,6 +241,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
conductorComponent: {}
|
||||||
|
}
|
||||||
|
},
|
||||||
components: {
|
components: {
|
||||||
Inspector,
|
Inspector,
|
||||||
MctStatus,
|
MctStatus,
|
||||||
|
Reference in New Issue
Block a user