mirror of
https://github.com/nasa/openmct.git
synced 2025-02-21 17:57:04 +00:00
Merge branch 'open1515' into open115
This commit is contained in:
commit
0260e6fff4
@ -25,8 +25,8 @@
|
|||||||
* Module defining SinewaveTelemetryProvider. Created by vwoeltje on 11/12/14.
|
* Module defining SinewaveTelemetryProvider. Created by vwoeltje on 11/12/14.
|
||||||
*/
|
*/
|
||||||
define(
|
define(
|
||||||
["./SinewaveTelemetry"],
|
["./SinewaveTelemetrySeries"],
|
||||||
function (SinewaveTelemetry) {
|
function (SinewaveTelemetrySeries) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -45,7 +45,7 @@ define(
|
|||||||
function generateData(request) {
|
function generateData(request) {
|
||||||
return {
|
return {
|
||||||
key: request.key,
|
key: request.key,
|
||||||
telemetry: new SinewaveTelemetry(request)
|
telemetry: new SinewaveTelemetrySeries(request)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,4 +112,4 @@ define(
|
|||||||
|
|
||||||
return SinewaveTelemetryProvider;
|
return SinewaveTelemetryProvider;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -29,35 +29,45 @@ define(
|
|||||||
function () {
|
function () {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var firstObservedTime = Date.now();
|
var firstObservedTime = Math.floor(Date.now() / 1000);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
function SinewaveTelemetry(request) {
|
function SinewaveTelemetrySeries(request) {
|
||||||
var latestObservedTime = Date.now(),
|
var latestObservedTime = Math.floor(Date.now() / 1000),
|
||||||
count = Math.floor((latestObservedTime - firstObservedTime) / 1000),
|
endTime = (request.end !== undefined) ?
|
||||||
|
Math.floor(request.end / 1000) : latestObservedTime,
|
||||||
|
count =
|
||||||
|
Math.min(endTime, latestObservedTime) - firstObservedTime,
|
||||||
period = request.period || 30,
|
period = request.period || 30,
|
||||||
generatorData = {};
|
generatorData = {},
|
||||||
|
offset = (request.start !== undefined) ?
|
||||||
|
Math.floor(request.start / 1000) - firstObservedTime :
|
||||||
|
0;
|
||||||
|
|
||||||
|
if (request.size !== undefined) {
|
||||||
|
offset = Math.max(offset, count - request.size);
|
||||||
|
}
|
||||||
|
|
||||||
generatorData.getPointCount = function () {
|
generatorData.getPointCount = function () {
|
||||||
return count;
|
return count - offset;
|
||||||
};
|
};
|
||||||
|
|
||||||
generatorData.getDomainValue = function (i, domain) {
|
generatorData.getDomainValue = function (i, domain) {
|
||||||
return i * 1000 +
|
return (i + offset) * 1000 +
|
||||||
(domain !== 'delta' ? firstObservedTime : 0);
|
(domain !== 'delta' ? (firstObservedTime * 1000) : 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
generatorData.getRangeValue = function (i, range) {
|
generatorData.getRangeValue = function (i, range) {
|
||||||
range = range || "sin";
|
range = range || "sin";
|
||||||
return Math[range](i * Math.PI * 2 / period);
|
return Math[range]((i + offset) * Math.PI * 2 / period);
|
||||||
};
|
};
|
||||||
|
|
||||||
return generatorData;
|
return generatorData;
|
||||||
}
|
}
|
||||||
|
|
||||||
return SinewaveTelemetry;
|
return SinewaveTelemetrySeries;
|
||||||
}
|
}
|
||||||
);
|
);
|
@ -45,6 +45,16 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"controllers": [
|
"controllers": [
|
||||||
|
{
|
||||||
|
"key": "TimeRangeController",
|
||||||
|
"implementation": "controllers/TimeRangeController.js",
|
||||||
|
"depends": [ "$scope", "now" ]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "DateTimePickerController",
|
||||||
|
"implementation": "controllers/DateTimePickerController.js",
|
||||||
|
"depends": [ "$scope", "now" ]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"key": "TreeNodeController",
|
"key": "TreeNodeController",
|
||||||
"implementation": "controllers/TreeNodeController.js",
|
"implementation": "controllers/TreeNodeController.js",
|
||||||
@ -105,11 +115,21 @@
|
|||||||
"implementation": "directives/MCTDrag.js",
|
"implementation": "directives/MCTDrag.js",
|
||||||
"depends": [ "$document" ]
|
"depends": [ "$document" ]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"key": "mctClickElsewhere",
|
||||||
|
"implementation": "directives/MCTClickElsewhere.js",
|
||||||
|
"depends": [ "$document" ]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"key": "mctResize",
|
"key": "mctResize",
|
||||||
"implementation": "directives/MCTResize.js",
|
"implementation": "directives/MCTResize.js",
|
||||||
"depends": [ "$timeout" ]
|
"depends": [ "$timeout" ]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"key": "mctPopup",
|
||||||
|
"implementation": "directives/MCTPopup.js",
|
||||||
|
"depends": [ "$window", "$document", "$compile", "$interval" ]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"key": "mctScrollX",
|
"key": "mctScrollX",
|
||||||
"implementation": "directives/MCTScroll.js",
|
"implementation": "directives/MCTScroll.js",
|
||||||
@ -213,6 +233,10 @@
|
|||||||
{
|
{
|
||||||
"key": "selector",
|
"key": "selector",
|
||||||
"templateUrl": "templates/controls/selector.html"
|
"templateUrl": "templates/controls/selector.html"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "datetime-picker",
|
||||||
|
"templateUrl": "templates/controls/datetime-picker.html"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"licenses": [
|
"licenses": [
|
||||||
|
@ -0,0 +1,61 @@
|
|||||||
|
<!--
|
||||||
|
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 ng-controller="DateTimePickerController">
|
||||||
|
<div style="vertical-align: top; display: inline-block">
|
||||||
|
<div style="text-align: center;">
|
||||||
|
<a ng-click="changeMonth(-1)"><</a>
|
||||||
|
{{month}} {{year}}
|
||||||
|
<a ng-click="changeMonth(1)">></a>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th ng-repeat="day in ['Su','Mo','Tu','We','Th','Fr','Sa']">
|
||||||
|
{{day}}
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
<tr ng-repeat="row in table">
|
||||||
|
<td style="text-align: center;"
|
||||||
|
ng-repeat="cell in row"
|
||||||
|
ng-click="select(cell)"
|
||||||
|
ng-class='{
|
||||||
|
disabled: !isSelectable(cell),
|
||||||
|
test: isSelected(cell)
|
||||||
|
}'>
|
||||||
|
<div>{{cell.day}}</div>
|
||||||
|
<div style="font-size: 80%">{{cell.dayOfYear}}</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="vertical-align: top; display: inline-block"
|
||||||
|
ng-repeat="key in ['hours', 'minutes', 'seconds']"
|
||||||
|
ng-if="options[key]">
|
||||||
|
<div>{{nameFor(key)}}</div>
|
||||||
|
<select size="10"
|
||||||
|
ng-model="time[key]"
|
||||||
|
ng-options="i for i in optionsFor(key)">
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -1,69 +1,96 @@
|
|||||||
<!--
|
<!--
|
||||||
|
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.
|
||||||
|
|
||||||
NOTES
|
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.
|
||||||
|
|
||||||
Ticks:
|
Unless required by applicable law or agreed to in writing, software
|
||||||
The thinking is to divide whatever the current time span is by 5,
|
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
and assign values accordingly to 5 statically-positioned ticks. So the tick x-position is a static percentage
|
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
of the total width available, and the labels change dynamically. This is consistent
|
License for the specific language governing permissions and limitations
|
||||||
with our current approach to the time axis of plots.
|
under the License.
|
||||||
I'm keeping the number of ticks low so that when the view portal gets narrow,
|
|
||||||
the tick labels won't collide with each other. For extra credit, add/remove ticks as the user resizes the view area.
|
|
||||||
Note: this eval needs to be based on the whatever is containing the
|
|
||||||
time-controller component, not the whole browser window.
|
|
||||||
|
|
||||||
Range indicator and slider knobs:
|
|
||||||
The left and right properties used in .slider .range-holder and the .knobs are
|
|
||||||
CSS offsets from the left and right of their respective containers. You
|
|
||||||
may want or need to calculate those positions as pure offsets from the start datetime
|
|
||||||
(or left, as it were) and set them as left properties. No problem if so, but
|
|
||||||
we'll need to tweak the CSS tiny bit to get the center of the knobs to line up
|
|
||||||
properly on the range left and right bounds.
|
|
||||||
|
|
||||||
|
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 ng-init="
|
<div class="l-time-controller" ng-controller="TimeRangeController">
|
||||||
notes = 'Temporarily using an array to populate ticks so I can see what I\'m doing';
|
<div class="l-time-range-inputs-holder">
|
||||||
ticks = [
|
Start: {{startOuterText}}
|
||||||
'00:00',
|
<span ng-controller="ToggleController as t">
|
||||||
'00:30',
|
<a class="ui-symbol" ng-click="t.toggle()">p</a>
|
||||||
'01:00',
|
<mct-popup ng-if="t.isActive()">
|
||||||
'01:30',
|
<div style="background: #222;"
|
||||||
'02:00'
|
mct-click-elsewhere="t.setState(false)">
|
||||||
];
|
<mct-control key="'datetime-picker'"
|
||||||
"></div>
|
ng-model="ngModel.outer"
|
||||||
|
field="'start'"
|
||||||
|
options="{ hours: true }">
|
||||||
|
</mct-control>
|
||||||
|
</div>
|
||||||
|
</mct-popup>
|
||||||
|
</span>
|
||||||
|
|
||||||
<div class="l-time-controller">
|
End: {{endOuterText}}
|
||||||
<div class="l-time-range-inputs-holder">
|
<span ng-controller="ToggleController as t2">
|
||||||
Start: <input type="date" />
|
<a class="ui-symbol" ng-click="t2.toggle()">p</a>
|
||||||
End: <input type="date" />
|
<mct-popup ng-if="t2.isActive()">
|
||||||
</div>
|
<div style="background: #222;"
|
||||||
|
mct-click-elsewhere="t2.setState(false)">
|
||||||
|
<mct-control key="'datetime-picker'"
|
||||||
|
ng-model="ngModel.outer"
|
||||||
|
field="'end'"
|
||||||
|
options="{ hours: true }">
|
||||||
|
</mct-control>
|
||||||
|
</div>
|
||||||
|
</mct-popup>
|
||||||
|
</span>
|
||||||
|
|
||||||
<div class="l-time-range-slider-holder">
|
|
||||||
<div class="l-time-range-slider">
|
|
||||||
<div class="slider">
|
|
||||||
<div class="slot range-holder">
|
|
||||||
<div class="range" style="left: 0%; right: 30%;"></div>
|
|
||||||
</div>
|
|
||||||
<div class="knob knob-l" style="left: 0%;">
|
|
||||||
<div class="range-value">05/22 14:46</div>
|
|
||||||
</div>
|
|
||||||
<div class="knob knob-r" style="right: 30%;">
|
|
||||||
<div class="range-value">07/22 01:21</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="l-time-range-ticks-holder">
|
</div>
|
||||||
<div class="l-time-range-ticks">
|
|
||||||
<div
|
<div class="l-time-range-slider-holder">
|
||||||
ng-repeat="tick in ticks"
|
<div class="l-time-range-slider">
|
||||||
ng-style="{ left: $index * 25 + '%' }"
|
<div class="slider"
|
||||||
class="tick tick-x"
|
mct-resize="spanWidth = bounds.width">
|
||||||
>
|
<div class="slot range-holder">
|
||||||
<span class="l-time-range-tick-label">{{tick}}</span>
|
<div class="range"
|
||||||
</div>
|
mct-drag-down="startMiddleDrag()"
|
||||||
</div>
|
mct-drag="middleDrag(delta[0])"
|
||||||
</div>
|
ng-style="{ left: startInnerPct, right: endInnerPct}">
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="knob knob-l"
|
||||||
|
mct-drag-down="startLeftDrag()"
|
||||||
|
mct-drag="leftDrag(delta[0])"
|
||||||
|
ng-style="{ left: startInnerPct }">
|
||||||
|
<div class="range-value">{{startInnerText}}</div>
|
||||||
|
</div>
|
||||||
|
<div class="knob knob-r"
|
||||||
|
mct-drag-down="startRightDrag()"
|
||||||
|
mct-drag="rightDrag(delta[0])"
|
||||||
|
ng-style="{ right: endInnerPct }">
|
||||||
|
<div class="range-value">{{endInnerText}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="l-time-range-ticks-holder">
|
||||||
|
<div class="l-time-range-ticks">
|
||||||
|
<div
|
||||||
|
ng-repeat="tick in ticks"
|
||||||
|
ng-style="{ left: $index * (100 / (ticks.length - 1)) + '%' }"
|
||||||
|
class="tick tick-x"
|
||||||
|
>
|
||||||
|
<span class="l-time-range-tick-label">{{tick}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@ -0,0 +1,202 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT Web includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
/*global define,Promise*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
[ 'moment' ],
|
||||||
|
function (moment) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var TIME_NAMES = {
|
||||||
|
'hours': "Hour",
|
||||||
|
'minutes': "Minute",
|
||||||
|
'seconds': "Second"
|
||||||
|
},
|
||||||
|
MONTHS = moment.months(),
|
||||||
|
TIME_OPTIONS = (function makeRanges() {
|
||||||
|
var arr = [];
|
||||||
|
while (arr.length < 60) {
|
||||||
|
arr.push(arr.length);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
hours: arr.slice(0, 24),
|
||||||
|
minutes: arr,
|
||||||
|
seconds: arr
|
||||||
|
};
|
||||||
|
}());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller to support the date-time picker.
|
||||||
|
*
|
||||||
|
* Adds/uses the following properties in scope:
|
||||||
|
* * `year`: Year being displayed in picker
|
||||||
|
* * `month`: Month being displayed
|
||||||
|
* * `table`: Table being displayed; array of arrays of
|
||||||
|
* * `day`: Day of month
|
||||||
|
* * `dayOfYear`: Day of year
|
||||||
|
* * `month`: Month associated with the day
|
||||||
|
* * `year`: Year associated with the day.
|
||||||
|
* * `date`: Date chosen
|
||||||
|
* * `year`: Year selected
|
||||||
|
* * `month`: Month selected (0-indexed)
|
||||||
|
* * `day`: Day of month selected
|
||||||
|
* * `time`: Chosen time (hours/minutes/seconds)
|
||||||
|
* * `hours`: Hours chosen
|
||||||
|
* * `minutes`: Minutes chosen
|
||||||
|
* * `seconds`: Seconds chosen
|
||||||
|
*
|
||||||
|
* Months are zero-indexed, day-of-months are one-indexed.
|
||||||
|
*/
|
||||||
|
function DateTimePickerController($scope, now) {
|
||||||
|
var year,
|
||||||
|
month, // For picker state, not model state
|
||||||
|
interacted = false;
|
||||||
|
|
||||||
|
function generateTable() {
|
||||||
|
var m = moment.utc({ year: year, month: 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateScopeForMonth() {
|
||||||
|
$scope.month = MONTHS[month];
|
||||||
|
$scope.year = year;
|
||||||
|
$scope.table = generateTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateFromModel(ngModel) {
|
||||||
|
var m;
|
||||||
|
|
||||||
|
m = moment.utc(ngModel);
|
||||||
|
|
||||||
|
$scope.date = {
|
||||||
|
year: m.year(),
|
||||||
|
month: m.month(),
|
||||||
|
day: m.date()
|
||||||
|
};
|
||||||
|
$scope.time = {
|
||||||
|
hours: m.hour(),
|
||||||
|
minutes: m.minute(),
|
||||||
|
seconds: m.second()
|
||||||
|
};
|
||||||
|
|
||||||
|
//window.alert($scope.date.day + " " + ngModel);
|
||||||
|
|
||||||
|
// Zoom to that date in the picker, but
|
||||||
|
// only if the user hasn't interacted with it yet.
|
||||||
|
if (!interacted) {
|
||||||
|
year = m.year();
|
||||||
|
month = m.month();
|
||||||
|
updateScopeForMonth();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateFromView() {
|
||||||
|
var m = moment.utc({
|
||||||
|
year: $scope.date.year,
|
||||||
|
month: $scope.date.month,
|
||||||
|
day: $scope.date.day,
|
||||||
|
hour: $scope.time.hours,
|
||||||
|
minute: $scope.time.minutes,
|
||||||
|
second: $scope.time.seconds
|
||||||
|
});
|
||||||
|
$scope.ngModel[$scope.field] = m.valueOf();
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.isSelectable = function (cell) {
|
||||||
|
return cell.month === month;
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.isSelected = function (cell) {
|
||||||
|
var date = $scope.date || {};
|
||||||
|
return cell.day === date.day &&
|
||||||
|
cell.month === date.month &&
|
||||||
|
cell.year === date.year;
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.select = function (cell) {
|
||||||
|
$scope.date = $scope.date || {};
|
||||||
|
$scope.date.month = cell.month;
|
||||||
|
$scope.date.year = cell.year;
|
||||||
|
$scope.date.day = cell.day;
|
||||||
|
updateFromView();
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.dateEquals = function (d1, d2) {
|
||||||
|
return d1.year === d2.year &&
|
||||||
|
d1.month === d2.month &&
|
||||||
|
d1.day === d2.day;
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.changeMonth = function (delta) {
|
||||||
|
month += delta;
|
||||||
|
if (month > 11) {
|
||||||
|
month = 0;
|
||||||
|
year += 1;
|
||||||
|
}
|
||||||
|
if (month < 0) {
|
||||||
|
month = 11;
|
||||||
|
year -= 1;
|
||||||
|
}
|
||||||
|
interacted = true;
|
||||||
|
updateScopeForMonth();
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.nameFor = function (key) {
|
||||||
|
return TIME_NAMES[key];
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.optionsFor = function (key) {
|
||||||
|
return TIME_OPTIONS[key];
|
||||||
|
};
|
||||||
|
|
||||||
|
updateScopeForMonth();
|
||||||
|
|
||||||
|
// Ensure some useful default
|
||||||
|
$scope.ngModel[$scope.field] =
|
||||||
|
$scope.ngModel[$scope.field] === undefined ?
|
||||||
|
now() : $scope.ngModel[$scope.field];
|
||||||
|
|
||||||
|
$scope.$watch('ngModel[field]', updateFromModel);
|
||||||
|
$scope.$watchCollection('date', updateFromView);
|
||||||
|
$scope.$watchCollection('time', updateFromView);
|
||||||
|
}
|
||||||
|
|
||||||
|
return DateTimePickerController;
|
||||||
|
}
|
||||||
|
);
|
224
platform/commonUI/general/src/controllers/TimeRangeController.js
Normal file
224
platform/commonUI/general/src/controllers/TimeRangeController.js
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
/*global define,Promise*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
['moment'],
|
||||||
|
function (moment) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var DATE_FORMAT = "YYYY-MM-DD HH:mm:ss";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @memberof platform/commonUI/general
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function TimeConductorController($scope, now) {
|
||||||
|
var tickCount = 2,
|
||||||
|
initialDragValue;
|
||||||
|
|
||||||
|
function formatTimestamp(ts) {
|
||||||
|
return moment.utc(ts).format(DATE_FORMAT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// From 0.0-1.0 to "0%"-"1%"
|
||||||
|
function toPercent(p) {
|
||||||
|
return (100 * p) + "%";
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTicks() {
|
||||||
|
var i, p, ts, start, end, span;
|
||||||
|
end = $scope.ngModel.outer.end;
|
||||||
|
start = $scope.ngModel.outer.start;
|
||||||
|
span = end - start;
|
||||||
|
$scope.ticks = [];
|
||||||
|
for (i = 0; i < tickCount; i += 1) {
|
||||||
|
p = i / (tickCount - 1);
|
||||||
|
ts = p * span + start;
|
||||||
|
$scope.ticks.push(formatTimestamp(ts));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSpanWidth(w) {
|
||||||
|
// Space about 100px apart
|
||||||
|
tickCount = Math.max(Math.floor(w / 100), 2);
|
||||||
|
updateTicks();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateViewForInnerSpanFromModel(ngModel) {
|
||||||
|
var span = ngModel.outer.end - ngModel.outer.start;
|
||||||
|
|
||||||
|
// Expose readable dates for the knobs
|
||||||
|
$scope.startInnerText = formatTimestamp(ngModel.inner.start);
|
||||||
|
$scope.endInnerText = formatTimestamp(ngModel.inner.end);
|
||||||
|
|
||||||
|
// And positions for the knobs
|
||||||
|
$scope.startInnerPct =
|
||||||
|
toPercent((ngModel.inner.start - ngModel.outer.start) / span);
|
||||||
|
$scope.endInnerPct =
|
||||||
|
toPercent((ngModel.outer.end - ngModel.inner.end) / span);
|
||||||
|
}
|
||||||
|
|
||||||
|
function defaultBounds() {
|
||||||
|
var t = now();
|
||||||
|
return {
|
||||||
|
start: t - 24 * 3600 * 1000, // One day
|
||||||
|
end: t
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyBounds(bounds) {
|
||||||
|
return { start: bounds.start, end: bounds.end };
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateViewFromModel(ngModel) {
|
||||||
|
var t = now();
|
||||||
|
|
||||||
|
ngModel = ngModel || {};
|
||||||
|
ngModel.outer = ngModel.outer || defaultBounds();
|
||||||
|
ngModel.inner = ngModel.inner || copyBounds(ngModel.outer);
|
||||||
|
|
||||||
|
// First, dates for the date pickers for outer bounds
|
||||||
|
$scope.startOuterDate = new Date(ngModel.outer.start);
|
||||||
|
$scope.endOuterDate = new Date(ngModel.outer.end);
|
||||||
|
|
||||||
|
// Then various updates for the inner span
|
||||||
|
updateViewForInnerSpanFromModel(ngModel);
|
||||||
|
|
||||||
|
// Stick it back is scope (in case we just set defaults)
|
||||||
|
$scope.ngModel = ngModel;
|
||||||
|
|
||||||
|
updateTicks();
|
||||||
|
}
|
||||||
|
|
||||||
|
function startLeftDrag() {
|
||||||
|
initialDragValue = $scope.ngModel.inner.start;
|
||||||
|
}
|
||||||
|
|
||||||
|
function startRightDrag() {
|
||||||
|
initialDragValue = $scope.ngModel.inner.end;
|
||||||
|
}
|
||||||
|
|
||||||
|
function startMiddleDrag() {
|
||||||
|
initialDragValue = {
|
||||||
|
start: $scope.ngModel.inner.start,
|
||||||
|
end: $scope.ngModel.inner.end
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function toMillis(pixels) {
|
||||||
|
var span = $scope.ngModel.outer.end - $scope.ngModel.outer.start;
|
||||||
|
return (pixels / $scope.spanWidth) * span;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clamp(value, low, high) {
|
||||||
|
return Math.max(low, Math.min(high, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
function leftDrag(pixels) {
|
||||||
|
var delta = toMillis(pixels);
|
||||||
|
$scope.ngModel.inner.start = clamp(
|
||||||
|
initialDragValue + delta,
|
||||||
|
$scope.ngModel.outer.start,
|
||||||
|
$scope.ngModel.inner.end
|
||||||
|
);
|
||||||
|
updateViewFromModel($scope.ngModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
function rightDrag(pixels) {
|
||||||
|
var delta = toMillis(pixels);
|
||||||
|
$scope.ngModel.inner.end = clamp(
|
||||||
|
initialDragValue + delta,
|
||||||
|
$scope.ngModel.inner.start,
|
||||||
|
$scope.ngModel.outer.end
|
||||||
|
);
|
||||||
|
updateViewFromModel($scope.ngModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
function middleDrag(pixels) {
|
||||||
|
var delta = toMillis(pixels),
|
||||||
|
edge = delta < 0 ? 'start' : 'end',
|
||||||
|
opposite = delta < 0 ? 'end' : 'start';
|
||||||
|
|
||||||
|
// Adjust the position of the edge in the direction of drag
|
||||||
|
$scope.ngModel.inner[edge] = clamp(
|
||||||
|
initialDragValue[edge] + delta,
|
||||||
|
$scope.ngModel.outer.start,
|
||||||
|
$scope.ngModel.outer.end
|
||||||
|
);
|
||||||
|
// Adjust opposite knob to maintain span
|
||||||
|
$scope.ngModel.inner[opposite] = $scope.ngModel.inner[edge] +
|
||||||
|
initialDragValue[opposite] - initialDragValue[edge];
|
||||||
|
|
||||||
|
updateViewFromModel($scope.ngModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateOuterStart(t) {
|
||||||
|
var ngModel = $scope.ngModel;
|
||||||
|
ngModel.outer.end =
|
||||||
|
Math.max(ngModel.outer.start, ngModel.outer.end);
|
||||||
|
ngModel.inner.start =
|
||||||
|
Math.max(ngModel.outer.start, ngModel.inner.start);
|
||||||
|
ngModel.inner.end =
|
||||||
|
Math.max(ngModel.outer.start, ngModel.inner.end);
|
||||||
|
|
||||||
|
$scope.startOuterText = formatTimestamp(t);
|
||||||
|
|
||||||
|
updateViewForInnerSpanFromModel(ngModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateOuterEnd(t) {
|
||||||
|
var ngModel = $scope.ngModel;
|
||||||
|
ngModel.outer.start =
|
||||||
|
Math.min(ngModel.outer.end, ngModel.outer.start);
|
||||||
|
ngModel.inner.start =
|
||||||
|
Math.min(ngModel.outer.end, ngModel.inner.start);
|
||||||
|
ngModel.inner.end =
|
||||||
|
Math.min(ngModel.outer.end, ngModel.inner.end);
|
||||||
|
|
||||||
|
$scope.endOuterText = formatTimestamp(t);
|
||||||
|
|
||||||
|
updateViewForInnerSpanFromModel(ngModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.startLeftDrag = startLeftDrag;
|
||||||
|
$scope.startRightDrag = startRightDrag;
|
||||||
|
$scope.startMiddleDrag = startMiddleDrag;
|
||||||
|
$scope.leftDrag = leftDrag;
|
||||||
|
$scope.rightDrag = rightDrag;
|
||||||
|
$scope.middleDrag = middleDrag;
|
||||||
|
|
||||||
|
$scope.state = false;
|
||||||
|
$scope.ticks = [];
|
||||||
|
|
||||||
|
// Initialize scope to defaults
|
||||||
|
updateViewFromModel($scope.ngModel);
|
||||||
|
|
||||||
|
$scope.$watchCollection("ngModel", updateViewFromModel);
|
||||||
|
$scope.$watch("spanWidth", updateSpanWidth);
|
||||||
|
$scope.$watch("ngModel.outer.start", updateOuterStart);
|
||||||
|
$scope.$watch("ngModel.outer.end", updateOuterEnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
return TimeConductorController;
|
||||||
|
}
|
||||||
|
);
|
@ -0,0 +1,77 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
/*global define*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
[],
|
||||||
|
function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `mct-click-elsewhere` directive will evaluate its
|
||||||
|
* associated expression whenever a `mousedown` occurs anywhere
|
||||||
|
* outside of the element that has the `mct-click-elsewhere`
|
||||||
|
* directive attached. This is useful for dismissing popups
|
||||||
|
* and the like.
|
||||||
|
*/
|
||||||
|
function MCTClickElsewhere($document) {
|
||||||
|
|
||||||
|
// Link; install event handlers.
|
||||||
|
function link(scope, element, attrs) {
|
||||||
|
// Keep a reference to the body, to attach/detach
|
||||||
|
// mouse event handlers; mousedown and mouseup cannot
|
||||||
|
// only be attached to the element being linked, as the
|
||||||
|
// mouse may leave this element during the drag.
|
||||||
|
var body = $document.find('body');
|
||||||
|
|
||||||
|
function clickBody(event) {
|
||||||
|
var x = event.clientX,
|
||||||
|
y = event.clientY,
|
||||||
|
rect = element[0].getBoundingClientRect(),
|
||||||
|
xMin = rect.left,
|
||||||
|
xMax = xMin + rect.width,
|
||||||
|
yMin = rect.top,
|
||||||
|
yMax = yMin + rect.height;
|
||||||
|
|
||||||
|
if (x < xMin || x > xMax || y < yMin || y > yMax) {
|
||||||
|
scope.$eval(attrs.mctClickElsewhere);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body.on("mousedown", clickBody);
|
||||||
|
scope.$on("$destroy", function () {
|
||||||
|
body.off("mousedown", clickBody);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
// mct-drag only makes sense as an attribute
|
||||||
|
restrict: "A",
|
||||||
|
// Link function, to install event handlers
|
||||||
|
link: link
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return MCTClickElsewhere;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
70
platform/commonUI/general/src/directives/MCTPopup.js
Normal file
70
platform/commonUI/general/src/directives/MCTPopup.js
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
/*global define*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var TEMPLATE = "<div></div>";
|
||||||
|
|
||||||
|
function MCTPopup($window, $document, $compile) {
|
||||||
|
function link(scope, element, attrs, ctrl, transclude) {
|
||||||
|
var body = $document.find('body'),
|
||||||
|
popup = $compile(TEMPLATE)(scope),
|
||||||
|
winDim = [$window.innerWidth, $window.innerHeight],
|
||||||
|
rect = element.parent()[0].getBoundingClientRect(),
|
||||||
|
position = [ rect.left, rect.top ],
|
||||||
|
isLeft = position[0] <= (winDim[0] / 2),
|
||||||
|
isTop = position[1] <= (winDim[1] / 2);
|
||||||
|
|
||||||
|
popup.css('position', 'absolute');
|
||||||
|
popup.css(
|
||||||
|
isLeft ? 'left' : 'right',
|
||||||
|
(isLeft ? position[0] : (winDim[0] - position[0])) + 'px'
|
||||||
|
);
|
||||||
|
popup.css(
|
||||||
|
isTop ? 'top' : 'bottom',
|
||||||
|
(isTop ? position[1] : (winDim[1] - position[1])) + 'px'
|
||||||
|
);
|
||||||
|
body.append(popup);
|
||||||
|
|
||||||
|
transclude(function (clone) {
|
||||||
|
popup.append(clone);
|
||||||
|
});
|
||||||
|
|
||||||
|
scope.$on('$destroy', function () {
|
||||||
|
popup.remove();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
restrict: "E",
|
||||||
|
transclude: true,
|
||||||
|
link: link,
|
||||||
|
scope: {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return MCTPopup;
|
||||||
|
}
|
||||||
|
);
|
@ -0,0 +1,63 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
["../../src/controllers/DateTimePickerController"],
|
||||||
|
function (DateTimePickerController) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
describe("The DateTimePickerController", function () {
|
||||||
|
var mockScope,
|
||||||
|
mockNow,
|
||||||
|
controller;
|
||||||
|
|
||||||
|
function fireWatch(expr, value) {
|
||||||
|
mockScope.$watch.calls.forEach(function (call) {
|
||||||
|
if (call.args[0] === expr) {
|
||||||
|
call.args[1](value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
mockScope = jasmine.createSpyObj(
|
||||||
|
"$scope",
|
||||||
|
[ "$apply", "$watch", "$watchCollection" ]
|
||||||
|
);
|
||||||
|
mockScope.ngModel = {};
|
||||||
|
mockScope.field = "testField";
|
||||||
|
mockNow = jasmine.createSpy('now');
|
||||||
|
controller = new DateTimePickerController(mockScope, mockNow);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("watches the model that was passed in", function () {
|
||||||
|
expect(mockScope.$watch).toHaveBeenCalledWith(
|
||||||
|
"ngModel[field]",
|
||||||
|
jasmine.any(Function)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
@ -0,0 +1,67 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
["../../src/controllers/TimeRangeController"],
|
||||||
|
function (TimeRangeController) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
describe("The TimeRangeController", function () {
|
||||||
|
var mockScope,
|
||||||
|
mockNow,
|
||||||
|
controller;
|
||||||
|
|
||||||
|
function fireWatch(expr, value) {
|
||||||
|
mockScope.$watch.calls.forEach(function (call) {
|
||||||
|
if (call.args[0] === expr) {
|
||||||
|
call.args[1](value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function fireWatchCollection(expr, value) {
|
||||||
|
mockScope.$watchCollection.calls.forEach(function (call) {
|
||||||
|
if (call.args[0] === expr) {
|
||||||
|
call.args[1](value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
mockScope = jasmine.createSpyObj(
|
||||||
|
"$scope",
|
||||||
|
[ "$apply", "$watch", "$watchCollection" ]
|
||||||
|
);
|
||||||
|
mockNow = jasmine.createSpy('now');
|
||||||
|
controller = new TimeRangeController(mockScope, mockNow);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("watches the model that was passed in", function () {
|
||||||
|
expect(mockScope.$watchCollection)
|
||||||
|
.toHaveBeenCalledWith("ngModel", jasmine.any(Function));
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
@ -0,0 +1,84 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
/*global define,describe,it,expect,beforeEach,jasmine*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
["../../src/directives/MCTClickElsewhere"],
|
||||||
|
function (MCTClickElsewhere) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var JQLITE_METHODS = [ "on", "off", "find", "parent" ];
|
||||||
|
|
||||||
|
describe("The mct-click-elsewhere directive", function () {
|
||||||
|
var mockDocument,
|
||||||
|
mockScope,
|
||||||
|
mockElement,
|
||||||
|
testAttrs,
|
||||||
|
mockBody,
|
||||||
|
mockParentEl,
|
||||||
|
testRect,
|
||||||
|
mctClickElsewhere;
|
||||||
|
|
||||||
|
function testEvent(x, y) {
|
||||||
|
return {
|
||||||
|
pageX: x,
|
||||||
|
pageY: y,
|
||||||
|
preventDefault: jasmine.createSpy("preventDefault")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
mockDocument =
|
||||||
|
jasmine.createSpyObj("$document", JQLITE_METHODS);
|
||||||
|
mockScope =
|
||||||
|
jasmine.createSpyObj("$scope", [ "$eval", "$apply", "$on" ]);
|
||||||
|
mockElement =
|
||||||
|
jasmine.createSpyObj("element", JQLITE_METHODS);
|
||||||
|
mockBody =
|
||||||
|
jasmine.createSpyObj("body", JQLITE_METHODS);
|
||||||
|
mockParentEl =
|
||||||
|
jasmine.createSpyObj("parent", ["getBoundingClientRect"]);
|
||||||
|
|
||||||
|
testAttrs = {
|
||||||
|
mctClickElsewhere: "some Angular expression"
|
||||||
|
};
|
||||||
|
testRect = {
|
||||||
|
left: 20,
|
||||||
|
top: 42,
|
||||||
|
width: 60,
|
||||||
|
height: 75
|
||||||
|
};
|
||||||
|
|
||||||
|
mockDocument.find.andReturn(mockBody);
|
||||||
|
|
||||||
|
mctClickElsewhere = new MCTClickElsewhere(mockDocument);
|
||||||
|
mctClickElsewhere.link(mockScope, mockElement, testAttrs);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("is valid as an attribute", function () {
|
||||||
|
expect(mctClickElsewhere.restrict).toEqual("A");
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
105
platform/commonUI/general/test/directives/MCTPopupSpec.js
Normal file
105
platform/commonUI/general/test/directives/MCTPopupSpec.js
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
/*global define,describe,it,expect,beforeEach,jasmine*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
["../../src/directives/MCTPopup"],
|
||||||
|
function (MCTPopup) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var JQLITE_METHODS = [ "on", "off", "find", "parent", "css", "append" ];
|
||||||
|
|
||||||
|
describe("The mct-popup directive", function () {
|
||||||
|
var testWindow,
|
||||||
|
mockDocument,
|
||||||
|
mockCompile,
|
||||||
|
mockScope,
|
||||||
|
mockElement,
|
||||||
|
testAttrs,
|
||||||
|
mockBody,
|
||||||
|
mockTransclude,
|
||||||
|
mockParentEl,
|
||||||
|
testRect,
|
||||||
|
mctPopup;
|
||||||
|
|
||||||
|
function testEvent(x, y) {
|
||||||
|
return {
|
||||||
|
pageX: x,
|
||||||
|
pageY: y,
|
||||||
|
preventDefault: jasmine.createSpy("preventDefault")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
testWindow =
|
||||||
|
{ innerWidth: 600, innerHeight: 300 };
|
||||||
|
mockDocument =
|
||||||
|
jasmine.createSpyObj("$document", JQLITE_METHODS);
|
||||||
|
mockCompile =
|
||||||
|
jasmine.createSpy("$compile");
|
||||||
|
mockScope =
|
||||||
|
jasmine.createSpyObj("$scope", [ "$eval", "$apply", "$on" ]);
|
||||||
|
mockElement =
|
||||||
|
jasmine.createSpyObj("element", JQLITE_METHODS);
|
||||||
|
mockBody =
|
||||||
|
jasmine.createSpyObj("body", JQLITE_METHODS);
|
||||||
|
mockTransclude =
|
||||||
|
jasmine.createSpy("transclude");
|
||||||
|
mockParentEl =
|
||||||
|
jasmine.createSpyObj("parent", ["getBoundingClientRect"]);
|
||||||
|
|
||||||
|
testAttrs = {
|
||||||
|
mctClickElsewhere: "some Angular expression"
|
||||||
|
};
|
||||||
|
testRect = {
|
||||||
|
left: 20,
|
||||||
|
top: 42,
|
||||||
|
width: 60,
|
||||||
|
height: 75
|
||||||
|
};
|
||||||
|
|
||||||
|
mockDocument.find.andReturn(mockBody);
|
||||||
|
mockCompile.andReturn(jasmine.createSpy());
|
||||||
|
mockCompile().andCallFake(function () {
|
||||||
|
return jasmine.createSpyObj("newElement", JQLITE_METHODS);
|
||||||
|
});
|
||||||
|
mockElement.parent.andReturn([mockParentEl]);
|
||||||
|
mockParentEl.getBoundingClientRect.andReturn(testRect);
|
||||||
|
|
||||||
|
mctPopup = new MCTPopup(testWindow, mockDocument, mockCompile);
|
||||||
|
mctPopup.link(
|
||||||
|
mockScope,
|
||||||
|
mockElement,
|
||||||
|
testAttrs,
|
||||||
|
null,
|
||||||
|
mockTransclude
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("is valid as an element", function () {
|
||||||
|
expect(mctPopup.restrict).toEqual("E");
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
@ -3,14 +3,18 @@
|
|||||||
"controllers/BottomBarController",
|
"controllers/BottomBarController",
|
||||||
"controllers/ClickAwayController",
|
"controllers/ClickAwayController",
|
||||||
"controllers/ContextMenuController",
|
"controllers/ContextMenuController",
|
||||||
|
"controllers/DateTimePickerController",
|
||||||
"controllers/GetterSetterController",
|
"controllers/GetterSetterController",
|
||||||
"controllers/SelectorController",
|
"controllers/SelectorController",
|
||||||
"controllers/SplitPaneController",
|
"controllers/SplitPaneController",
|
||||||
|
"controllers/TimeRangeController",
|
||||||
"controllers/ToggleController",
|
"controllers/ToggleController",
|
||||||
"controllers/TreeNodeController",
|
"controllers/TreeNodeController",
|
||||||
"controllers/ViewSwitcherController",
|
"controllers/ViewSwitcherController",
|
||||||
|
"directives/MCTClickElsewhere",
|
||||||
"directives/MCTContainer",
|
"directives/MCTContainer",
|
||||||
"directives/MCTDrag",
|
"directives/MCTDrag",
|
||||||
|
"directives/MCTPopup",
|
||||||
"directives/MCTResize",
|
"directives/MCTResize",
|
||||||
"directives/MCTScroll",
|
"directives/MCTScroll",
|
||||||
"services/UrlService",
|
"services/UrlService",
|
||||||
|
@ -36,11 +36,16 @@ define(
|
|||||||
*
|
*
|
||||||
* Returns a function that, when invoked, will invoke `fn` after
|
* Returns a function that, when invoked, will invoke `fn` after
|
||||||
* `delay` milliseconds, only if no other invocations are pending.
|
* `delay` milliseconds, only if no other invocations are pending.
|
||||||
* The optional argument `apply` determines whether.
|
* The optional argument `apply` determines whether or not a
|
||||||
|
* digest cycle should be triggered.
|
||||||
*
|
*
|
||||||
* The returned function will itself return a `Promise` which will
|
* The returned function will itself return a `Promise` which will
|
||||||
* resolve to the returned value of `fn` whenever that is invoked.
|
* resolve to the returned value of `fn` whenever that is invoked.
|
||||||
*
|
*
|
||||||
|
* In cases where arguments are provided, only the most recent
|
||||||
|
* set of arguments will be passed on to the throttled function
|
||||||
|
* at the time it is executed.
|
||||||
|
*
|
||||||
* @returns {Function}
|
* @returns {Function}
|
||||||
* @memberof platform/core
|
* @memberof platform/core
|
||||||
*/
|
*/
|
||||||
@ -56,12 +61,14 @@ define(
|
|||||||
* @memberof platform/core.Throttle#
|
* @memberof platform/core.Throttle#
|
||||||
*/
|
*/
|
||||||
return function (fn, delay, apply) {
|
return function (fn, delay, apply) {
|
||||||
var activeTimeout;
|
var promise, // Promise for the result of throttled function
|
||||||
|
args = [];
|
||||||
|
|
||||||
// Clear active timeout, so that next invocation starts
|
function invoke() {
|
||||||
// a new one.
|
// Clear the active timeout so a new one starts next time.
|
||||||
function clearActiveTimeout() {
|
promise = undefined;
|
||||||
activeTimeout = undefined;
|
// Invoke the function with the latest supplied arguments.
|
||||||
|
return fn.apply(null, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Defaults
|
// Defaults
|
||||||
@ -69,14 +76,13 @@ define(
|
|||||||
apply = apply || false;
|
apply = apply || false;
|
||||||
|
|
||||||
return function () {
|
return function () {
|
||||||
|
// Store arguments from this invocation
|
||||||
|
args = Array.prototype.slice.apply(arguments, [0]);
|
||||||
// Start a timeout if needed
|
// Start a timeout if needed
|
||||||
if (!activeTimeout) {
|
promise = promise || $timeout(invoke, delay, apply);
|
||||||
activeTimeout = $timeout(fn, delay, apply);
|
|
||||||
activeTimeout.then(clearActiveTimeout);
|
|
||||||
}
|
|
||||||
// Return whichever timeout is active (to get
|
// Return whichever timeout is active (to get
|
||||||
// a promise for the results of fn)
|
// a promise for the results of fn)
|
||||||
return activeTimeout;
|
return promise;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,9 @@ define(
|
|||||||
// Verify precondition: Not called at throttle-time
|
// Verify precondition: Not called at throttle-time
|
||||||
expect(mockTimeout).not.toHaveBeenCalled();
|
expect(mockTimeout).not.toHaveBeenCalled();
|
||||||
expect(throttled()).toEqual(mockPromise);
|
expect(throttled()).toEqual(mockPromise);
|
||||||
expect(mockTimeout).toHaveBeenCalledWith(mockFn, 0, false);
|
expect(mockFn).not.toHaveBeenCalled();
|
||||||
|
expect(mockTimeout)
|
||||||
|
.toHaveBeenCalledWith(jasmine.any(Function), 0, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("schedules only one timeout at a time", function () {
|
it("schedules only one timeout at a time", function () {
|
||||||
@ -59,10 +61,11 @@ define(
|
|||||||
it("schedules additional invocations after resolution", function () {
|
it("schedules additional invocations after resolution", function () {
|
||||||
var throttled = throttle(mockFn);
|
var throttled = throttle(mockFn);
|
||||||
throttled();
|
throttled();
|
||||||
mockPromise.then.mostRecentCall.args[0](); // Resolve timeout
|
mockTimeout.mostRecentCall.args[0](); // Resolve timeout
|
||||||
throttled();
|
throttled();
|
||||||
mockPromise.then.mostRecentCall.args[0]();
|
mockTimeout.mostRecentCall.args[0]();
|
||||||
throttled();
|
throttled();
|
||||||
|
mockTimeout.mostRecentCall.args[0]();
|
||||||
expect(mockTimeout.calls.length).toEqual(3);
|
expect(mockTimeout.calls.length).toEqual(3);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
9
platform/features/conductor/README.md
Normal file
9
platform/features/conductor/README.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
Provides the time conductor, a control which appears at the
|
||||||
|
bottom of the screen allowing telemetry start and end times
|
||||||
|
to be modified.
|
||||||
|
|
||||||
|
Note that the term "time controller" is generally preferred
|
||||||
|
outside of the code base (e.g. in UI documents, issues, etc.);
|
||||||
|
the term "time conductor" is being used in code to avoid
|
||||||
|
confusion with "controllers" in the Model-View-Controller
|
||||||
|
sense.
|
25
platform/features/conductor/bundle.json
Normal file
25
platform/features/conductor/bundle.json
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"extensions": {
|
||||||
|
"representers": [
|
||||||
|
{
|
||||||
|
"implementation": "ConductorRepresenter.js",
|
||||||
|
"depends": [ "conductorService", "$compile", "views[]" ]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"components": [
|
||||||
|
{
|
||||||
|
"type": "decorator",
|
||||||
|
"provides": "telemetryService",
|
||||||
|
"implementation": "ConductorTelemetryDecorator.js",
|
||||||
|
"depends": [ "conductorService" ]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"services": [
|
||||||
|
{
|
||||||
|
"key": "conductorService",
|
||||||
|
"implementation": "ConductorService.js",
|
||||||
|
"depends": [ "now" ]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
167
platform/features/conductor/src/ConductorRepresenter.js
Normal file
167
platform/features/conductor/src/ConductorRepresenter.js
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
/*global define*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
[],
|
||||||
|
function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var CONDUCTOR_HEIGHT = "100px",
|
||||||
|
TEMPLATE = [
|
||||||
|
'<div style=',
|
||||||
|
'"position: absolute; bottom: 0; width: 100%; ',
|
||||||
|
'overflow: hidden; ',
|
||||||
|
'height: ' + CONDUCTOR_HEIGHT + '">',
|
||||||
|
"<mct-include key=\"'time-controller'\" ng-model='conductor'>",
|
||||||
|
"</mct-include>",
|
||||||
|
'</div>'
|
||||||
|
].join(''),
|
||||||
|
GLOBAL_SHOWING = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ConductorRepresenter attaches the universal time conductor
|
||||||
|
* to views.
|
||||||
|
*
|
||||||
|
* @implements {Representer}
|
||||||
|
* @constructor
|
||||||
|
* @memberof platform/features/conductor
|
||||||
|
* @param {platform/features/conductor.ConductorService} conductorService
|
||||||
|
* service which provides the active time conductor
|
||||||
|
* @param $compile Angular's $compile
|
||||||
|
* @param {ViewDefinition[]} views all defined views
|
||||||
|
* @param {Scope} the scope of the representation
|
||||||
|
* @param element the jqLite-wrapped representation element
|
||||||
|
*/
|
||||||
|
function ConductorRepresenter(conductorService, $compile, views, scope, element) {
|
||||||
|
this.scope = scope;
|
||||||
|
this.conductorService = conductorService;
|
||||||
|
this.element = element;
|
||||||
|
this.views = views;
|
||||||
|
this.$compile = $compile;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combine start/end times into a single object
|
||||||
|
function bounds(start, end) {
|
||||||
|
return { start: start, end: end };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the time conductor from the scope
|
||||||
|
function wireScope(conductor, conductorScope, repScope) {
|
||||||
|
function updateConductorOuter() {
|
||||||
|
conductor.queryStart(conductorScope.conductor.outer.start);
|
||||||
|
conductor.queryEnd(conductorScope.conductor.outer.end);
|
||||||
|
repScope.$broadcast(
|
||||||
|
'telemetry:query:bounds',
|
||||||
|
bounds(conductor.queryStart(), conductor.queryEnd())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateConductorInner() {
|
||||||
|
conductor.displayStart(conductorScope.conductor.inner.start);
|
||||||
|
conductor.displayEnd(conductorScope.conductor.inner.end);
|
||||||
|
repScope.$broadcast(
|
||||||
|
'telemetry:display:bounds',
|
||||||
|
bounds(conductor.displayStart(), conductor.displayEnd())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
conductorScope.conductor = {
|
||||||
|
outer: bounds(conductor.queryStart(), conductor.queryEnd()),
|
||||||
|
inner: bounds(conductor.displayStart(), conductor.displayEnd())
|
||||||
|
};
|
||||||
|
|
||||||
|
conductorScope
|
||||||
|
.$watch('conductor.outer.start', updateConductorOuter);
|
||||||
|
conductorScope
|
||||||
|
.$watch('conductor.outer.end', updateConductorOuter);
|
||||||
|
conductorScope
|
||||||
|
.$watch('conductor.inner.start', updateConductorInner);
|
||||||
|
conductorScope
|
||||||
|
.$watch('conductor.inner.end', updateConductorInner);
|
||||||
|
|
||||||
|
repScope.$on('telemetry:view', updateConductorInner);
|
||||||
|
}
|
||||||
|
|
||||||
|
ConductorRepresenter.prototype.conductorScope = function (s) {
|
||||||
|
return (this.cScope = arguments.length > 0 ?
|
||||||
|
s : this.cScope);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle a specific representation of a specific domain object
|
||||||
|
ConductorRepresenter.prototype.represent = function represent(representation, representedObject) {
|
||||||
|
this.destroy();
|
||||||
|
|
||||||
|
if (this.views.indexOf(representation) !== -1 && !GLOBAL_SHOWING) {
|
||||||
|
// Track original states
|
||||||
|
this.originalHeight = this.element.css('height');
|
||||||
|
this.hadAbs = this.element.hasClass('abs');
|
||||||
|
|
||||||
|
// Create a new scope for the conductor
|
||||||
|
this.conductorScope(this.scope.$new());
|
||||||
|
wireScope(
|
||||||
|
this.conductorService.getConductor(),
|
||||||
|
this.conductorScope(),
|
||||||
|
this.scope
|
||||||
|
);
|
||||||
|
this.conductorElement =
|
||||||
|
this.$compile(TEMPLATE)(this.conductorScope());
|
||||||
|
this.element.after(this.conductorElement[0]);
|
||||||
|
this.element.addClass('abs');
|
||||||
|
this.element.css('bottom', CONDUCTOR_HEIGHT);
|
||||||
|
GLOBAL_SHOWING = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Respond to the destruction of the current representation.
|
||||||
|
ConductorRepresenter.prototype.destroy = function destroy() {
|
||||||
|
// We may not have decided to show in the first place,
|
||||||
|
// so circumvent any unnecessary cleanup
|
||||||
|
if (!this.conductorElement) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore the original size of the mct-representation
|
||||||
|
if (!this.hadAbs) {
|
||||||
|
this.element.removeClass('abs');
|
||||||
|
}
|
||||||
|
this.element.css('height', this.originalHeight);
|
||||||
|
|
||||||
|
// ...and remove the conductor
|
||||||
|
if (this.conductorElement) {
|
||||||
|
this.conductorElement.remove();
|
||||||
|
this.conductorElement = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, destroy its scope
|
||||||
|
if (this.conductorScope()) {
|
||||||
|
this.conductorScope().$destroy();
|
||||||
|
this.conductorScope(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
GLOBAL_SHOWING = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
return ConductorRepresenter;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
61
platform/features/conductor/src/ConductorService.js
Normal file
61
platform/features/conductor/src/ConductorService.js
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
/*global define*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
['./TimeConductor'],
|
||||||
|
function (TimeConductor) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var ONE_DAY_IN_MS = 1000 * 60 * 60 * 24,
|
||||||
|
SIX_HOURS_IN_MS = ONE_DAY_IN_MS / 4;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a single global instance of the time conductor, which
|
||||||
|
* controls both query ranges and displayed ranges for telemetry
|
||||||
|
* data.
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
* @memberof platform/features/conductor
|
||||||
|
* @param {Function} now a function which returns the current time
|
||||||
|
* as a UNIX timestamp, in milliseconds
|
||||||
|
*/
|
||||||
|
function ConductorService(now) {
|
||||||
|
var initialEnd =
|
||||||
|
Math.ceil(now() / SIX_HOURS_IN_MS) * SIX_HOURS_IN_MS;
|
||||||
|
|
||||||
|
this.conductor =
|
||||||
|
new TimeConductor(initialEnd - ONE_DAY_IN_MS, initialEnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the global instance of the time conductor.
|
||||||
|
* @returns {platform/features/conductor.TimeConductor} the
|
||||||
|
* time conductor
|
||||||
|
*/
|
||||||
|
ConductorService.prototype.getConductor = function () {
|
||||||
|
return this.conductor;
|
||||||
|
};
|
||||||
|
|
||||||
|
return ConductorService;
|
||||||
|
}
|
||||||
|
);
|
108
platform/features/conductor/src/ConductorTelemetryDecorator.js
Normal file
108
platform/features/conductor/src/ConductorTelemetryDecorator.js
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
/*global define*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
['./ConductorTelemetrySeries'],
|
||||||
|
function (ConductorTelemetrySeries) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decorates the `telemetryService` such that requests are
|
||||||
|
* mediated by the time conductor.
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
* @memberof platform/features/conductor
|
||||||
|
* @implements {TelemetryService}
|
||||||
|
* @param {platform/features/conductor.ConductorService} conductorServe
|
||||||
|
* the service which exposes the global time conductor
|
||||||
|
* @param {TelemetryService} telemetryService the decorated service
|
||||||
|
*/
|
||||||
|
function ConductorTelemetryDecorator(conductorService, telemetryService) {
|
||||||
|
this.conductorService = conductorService;
|
||||||
|
this.telemetryService = telemetryService;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strip out any realtime data series that is outside of the conductor's
|
||||||
|
// bounds.
|
||||||
|
ConductorTelemetryDecorator.prototype.pruneNonDisplayable = function (packaged) {
|
||||||
|
var conductor = this.conductorService.getConductor(),
|
||||||
|
repackaged = {};
|
||||||
|
|
||||||
|
function filterSource(packagedBySource) {
|
||||||
|
var repackagedBySource = {};
|
||||||
|
|
||||||
|
Object.keys(packagedBySource).forEach(function (k) {
|
||||||
|
repackagedBySource[k] = new ConductorTelemetrySeries(
|
||||||
|
packagedBySource[k],
|
||||||
|
conductor
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return repackagedBySource;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.keys(packaged).forEach(function (source) {
|
||||||
|
repackaged[source] = filterSource(packaged[source]);
|
||||||
|
});
|
||||||
|
|
||||||
|
return repackaged;
|
||||||
|
};
|
||||||
|
|
||||||
|
ConductorTelemetryDecorator.prototype.amendRequests = function (requests) {
|
||||||
|
var conductor = this.conductorService.getConductor(),
|
||||||
|
start = conductor.displayStart(),
|
||||||
|
end = conductor.displayEnd();
|
||||||
|
|
||||||
|
function amendRequest(request) {
|
||||||
|
request = request || {};
|
||||||
|
request.start = start;
|
||||||
|
request.end = end;
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (requests || []).map(amendRequest);
|
||||||
|
};
|
||||||
|
|
||||||
|
ConductorTelemetryDecorator.prototype.requestTelemetry = function (requests) {
|
||||||
|
var self = this;
|
||||||
|
return this.telemetryService
|
||||||
|
.requestTelemetry(this.amendRequests(requests))
|
||||||
|
.then(function (packaged) {
|
||||||
|
return self.pruneNonDisplayable(packaged);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
ConductorTelemetryDecorator.prototype.subscribe = function (callback, requests) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
function internalCallback(packagedSeries) {
|
||||||
|
return callback(self.pruneNonDisplayable(packagedSeries));
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.telemetryService
|
||||||
|
.subscribe(internalCallback, this.amendRequests(requests));
|
||||||
|
};
|
||||||
|
|
||||||
|
return ConductorTelemetryDecorator;
|
||||||
|
}
|
||||||
|
);
|
71
platform/features/conductor/src/ConductorTelemetrySeries.js
Normal file
71
platform/features/conductor/src/ConductorTelemetrySeries.js
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
/*global define*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bound a series of telemetry such that it only includes
|
||||||
|
* points from within the time conductor's displayable window.
|
||||||
|
*
|
||||||
|
* @param {TelemetrySeries} series the telemetry series
|
||||||
|
* @param {platform/features/conductor.TimeConductor} the
|
||||||
|
* time conductor instance which bounds this series
|
||||||
|
* @constructor
|
||||||
|
* @implements {TelemetrySeries}
|
||||||
|
*/
|
||||||
|
function ConductorTelemetrySeries(series, conductor) {
|
||||||
|
var max = series.getPointCount() - 1;
|
||||||
|
|
||||||
|
function binSearch(min, max, value) {
|
||||||
|
var mid = Math.floor((min + max) / 2);
|
||||||
|
|
||||||
|
return min > max ? min :
|
||||||
|
series.getDomainValue(mid) < value ?
|
||||||
|
binSearch(mid + 1, max, value) :
|
||||||
|
binSearch(min, mid - 1, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.startIndex = binSearch(0, max, conductor.displayStart());
|
||||||
|
this.endIndex = binSearch(0, max, conductor.displayEnd());
|
||||||
|
this.series = series;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConductorTelemetrySeries.prototype.getPointCount = function () {
|
||||||
|
return Math.max(0, this.endIndex - this.startIndex);
|
||||||
|
};
|
||||||
|
|
||||||
|
ConductorTelemetrySeries.prototype.getDomainValue = function (i, d) {
|
||||||
|
return this.series.getDomainValue(i + this.startIndex, d);
|
||||||
|
};
|
||||||
|
|
||||||
|
ConductorTelemetrySeries.prototype.getRangeValue = function (i, r) {
|
||||||
|
return this.series.getRangeValue(i + this.startIndex, r);
|
||||||
|
};
|
||||||
|
|
||||||
|
return ConductorTelemetrySeries;
|
||||||
|
}
|
||||||
|
);
|
99
platform/features/conductor/src/TimeConductor.js
Normal file
99
platform/features/conductor/src/TimeConductor.js
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
/*global define*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The time conductor bundle adds a global control to the bottom of the
|
||||||
|
* outermost viewing area. This controls both the range for time-based
|
||||||
|
* queries and for time-based displays.
|
||||||
|
*
|
||||||
|
* @namespace platform/features/conductor
|
||||||
|
*/
|
||||||
|
define(
|
||||||
|
function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tracks the current state of the time conductor.
|
||||||
|
*
|
||||||
|
* @memberof platform/features/conductor
|
||||||
|
* @constructor
|
||||||
|
* @param {number} start the initial start time
|
||||||
|
* @param {number} end the initial end time
|
||||||
|
*/
|
||||||
|
function TimeConductor(start, end) {
|
||||||
|
this.inner = { start: start, end: end };
|
||||||
|
this.outer = { start: start, end: end };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get or set (if called with an argument) the start time for queries.
|
||||||
|
* @param {number} [value] the start time to set
|
||||||
|
* @returns {number} the start time
|
||||||
|
*/
|
||||||
|
TimeConductor.prototype.queryStart = function (value) {
|
||||||
|
if (arguments.length > 0) {
|
||||||
|
this.outer.start = value;
|
||||||
|
}
|
||||||
|
return this.outer.start;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get or set (if called with an argument) the end time for queries.
|
||||||
|
* @param {number} [value] the end time to set
|
||||||
|
* @returns {number} the end time
|
||||||
|
*/
|
||||||
|
TimeConductor.prototype.queryEnd = function (value) {
|
||||||
|
if (arguments.length > 0) {
|
||||||
|
this.outer.end = value;
|
||||||
|
}
|
||||||
|
return this.outer.end;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get or set (if called with an argument) the start time for displays.
|
||||||
|
* @param {number} [value] the start time to set
|
||||||
|
* @returns {number} the start time
|
||||||
|
*/
|
||||||
|
TimeConductor.prototype.displayStart = function (value) {
|
||||||
|
if (arguments.length > 0) {
|
||||||
|
this.inner.start = value;
|
||||||
|
}
|
||||||
|
return this.inner.start;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get or set (if called with an argument) the end time for displays.
|
||||||
|
* @param {number} [value] the end time to set
|
||||||
|
* @returns {number} the end time
|
||||||
|
*/
|
||||||
|
TimeConductor.prototype.displayEnd = function (value) {
|
||||||
|
if (arguments.length > 0) {
|
||||||
|
this.inner.end = value;
|
||||||
|
}
|
||||||
|
return this.inner.end;
|
||||||
|
};
|
||||||
|
|
||||||
|
return TimeConductor;
|
||||||
|
}
|
||||||
|
);
|
167
platform/features/conductor/test/ConductorRepresenterSpec.js
Normal file
167
platform/features/conductor/test/ConductorRepresenterSpec.js
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
/*global define,describe,it,expect,beforeEach,waitsFor,afterEach,jasmine*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EventSpec. Created by vwoeltje on 11/6/14. Modified by shale on 06/23/2015.
|
||||||
|
*/
|
||||||
|
define(
|
||||||
|
["../src/ConductorRepresenter"],
|
||||||
|
function (ConductorRepresenter) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var SCOPE_METHODS = [
|
||||||
|
'$on',
|
||||||
|
'$watch',
|
||||||
|
'$broadcast',
|
||||||
|
'$emit',
|
||||||
|
'$new',
|
||||||
|
'$destroy'
|
||||||
|
],
|
||||||
|
ELEMENT_METHODS = [
|
||||||
|
'hasClass',
|
||||||
|
'addClass',
|
||||||
|
'removeClass',
|
||||||
|
'css',
|
||||||
|
'after',
|
||||||
|
'remove'
|
||||||
|
];
|
||||||
|
|
||||||
|
describe("ConductorRepresenter", function () {
|
||||||
|
var mockConductorService,
|
||||||
|
mockCompile,
|
||||||
|
testViews,
|
||||||
|
mockScope,
|
||||||
|
mockElement,
|
||||||
|
mockConductor,
|
||||||
|
mockCompiledTemplate,
|
||||||
|
mockNewScope,
|
||||||
|
mockNewElement,
|
||||||
|
representer;
|
||||||
|
|
||||||
|
function fireWatch(scope, watch, value) {
|
||||||
|
scope.$watch.calls.forEach(function (call) {
|
||||||
|
if (call.args[0] === watch) {
|
||||||
|
call.args[1](value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
mockConductorService = jasmine.createSpyObj(
|
||||||
|
'conductorService',
|
||||||
|
['getConductor']
|
||||||
|
);
|
||||||
|
mockCompile = jasmine.createSpy('$compile');
|
||||||
|
testViews = [ { someKey: "some value" } ];
|
||||||
|
mockScope = jasmine.createSpyObj('scope', SCOPE_METHODS);
|
||||||
|
mockElement = jasmine.createSpyObj('element', ELEMENT_METHODS);
|
||||||
|
mockConductor = jasmine.createSpyObj(
|
||||||
|
'conductor',
|
||||||
|
[ 'queryStart', 'queryEnd', 'displayStart', 'displayEnd' ]
|
||||||
|
);
|
||||||
|
mockCompiledTemplate = jasmine.createSpy('template');
|
||||||
|
mockNewScope = jasmine.createSpyObj('newScope', SCOPE_METHODS);
|
||||||
|
mockNewElement = jasmine.createSpyObj('newElement', ELEMENT_METHODS);
|
||||||
|
mockNewElement[0] = mockNewElement;
|
||||||
|
|
||||||
|
mockConductorService.getConductor.andReturn(mockConductor);
|
||||||
|
mockCompile.andReturn(mockCompiledTemplate);
|
||||||
|
mockCompiledTemplate.andReturn(mockNewElement);
|
||||||
|
mockScope.$new.andReturn(mockNewScope);
|
||||||
|
|
||||||
|
representer = new ConductorRepresenter(
|
||||||
|
mockConductorService,
|
||||||
|
mockCompile,
|
||||||
|
testViews,
|
||||||
|
mockScope,
|
||||||
|
mockElement
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function () {
|
||||||
|
representer.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("adds a conductor to views", function () {
|
||||||
|
representer.represent(testViews[0], {});
|
||||||
|
expect(mockElement.after).toHaveBeenCalledWith(mockNewElement);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("adds nothing to non-view representations", function () {
|
||||||
|
representer.represent({ someKey: "something else" }, {});
|
||||||
|
expect(mockElement.after).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("removes the conductor when destroyed", function () {
|
||||||
|
representer.represent(testViews[0], {});
|
||||||
|
expect(mockNewElement.remove).not.toHaveBeenCalled();
|
||||||
|
representer.destroy();
|
||||||
|
expect(mockNewElement.remove).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("destroys any new scope created", function () {
|
||||||
|
representer.represent(testViews[0], {});
|
||||||
|
representer.destroy();
|
||||||
|
expect(mockNewScope.$destroy.calls.length)
|
||||||
|
.toEqual(mockScope.$new.calls.length);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("exposes conductor state in scope", function () {
|
||||||
|
mockConductor.queryStart.andReturn(42);
|
||||||
|
mockConductor.queryEnd.andReturn(12321);
|
||||||
|
mockConductor.displayStart.andReturn(1977);
|
||||||
|
mockConductor.displayEnd.andReturn(1984);
|
||||||
|
representer.represent(testViews[0], {});
|
||||||
|
|
||||||
|
expect(mockNewScope.conductor).toEqual({
|
||||||
|
inner: { start: 1977, end: 1984 },
|
||||||
|
outer: { start: 42, end: 12321 }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updates conductor state from scope", function () {
|
||||||
|
var testState = {
|
||||||
|
inner: { start: 42, end: 1984 },
|
||||||
|
outer: { start: -1977, end: 12321 }
|
||||||
|
};
|
||||||
|
|
||||||
|
representer.represent(testViews[0], {});
|
||||||
|
|
||||||
|
mockNewScope.conductor = testState;
|
||||||
|
|
||||||
|
fireWatch(mockNewScope, 'conductor.inner.start', testState.inner.start);
|
||||||
|
expect(mockConductor.displayStart).toHaveBeenCalledWith(42);
|
||||||
|
|
||||||
|
fireWatch(mockNewScope, 'conductor.inner.end', testState.inner.end);
|
||||||
|
expect(mockConductor.displayEnd).toHaveBeenCalledWith(1984);
|
||||||
|
|
||||||
|
fireWatch(mockNewScope, 'conductor.outer.start', testState.outer.start);
|
||||||
|
expect(mockConductor.queryStart).toHaveBeenCalledWith(-1977);
|
||||||
|
|
||||||
|
fireWatch(mockNewScope, 'conductor.outer.end', testState.outer.end);
|
||||||
|
expect(mockConductor.queryEnd).toHaveBeenCalledWith(12321);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
58
platform/features/conductor/test/ConductorServiceSpec.js
Normal file
58
platform/features/conductor/test/ConductorServiceSpec.js
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
/*global define,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EventSpec. Created by vwoeltje on 11/6/14. Modified by shale on 06/23/2015.
|
||||||
|
*/
|
||||||
|
define(
|
||||||
|
["../src/ConductorService"],
|
||||||
|
function (ConductorService) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var TEST_NOW = 1020304050;
|
||||||
|
|
||||||
|
describe("ConductorService", function () {
|
||||||
|
var mockNow,
|
||||||
|
conductorService;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
mockNow = jasmine.createSpy('now');
|
||||||
|
mockNow.andReturn(TEST_NOW);
|
||||||
|
conductorService = new ConductorService(mockNow);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("initializes a time conductor around the current time", function () {
|
||||||
|
var conductor = conductorService.getConductor();
|
||||||
|
expect(conductor.queryStart() <= TEST_NOW).toBeTruthy();
|
||||||
|
expect(conductor.queryEnd() >= TEST_NOW).toBeTruthy();
|
||||||
|
expect(conductor.queryEnd() > conductor.queryStart())
|
||||||
|
.toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("provides a single shared time conductor instance", function () {
|
||||||
|
expect(conductorService.getConductor())
|
||||||
|
.toBe(conductorService.getConductor());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
@ -0,0 +1,137 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
/*global define,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||||
|
|
||||||
|
|
||||||
|
define(
|
||||||
|
["../src/ConductorTelemetryDecorator"],
|
||||||
|
function (ConductorTelemetryDecorator) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
describe("ConductorTelemetryDecorator", function () {
|
||||||
|
var mockTelemetryService,
|
||||||
|
mockConductorService,
|
||||||
|
mockConductor,
|
||||||
|
mockPromise,
|
||||||
|
mockSeries,
|
||||||
|
decorator;
|
||||||
|
|
||||||
|
function seriesIsInWindow(series) {
|
||||||
|
var i, v, inWindow = true;
|
||||||
|
for (i = 0; i < series.getPointCount(); i += 1) {
|
||||||
|
v = series.getDomainValue(i);
|
||||||
|
inWindow = inWindow && (v >= mockConductor.displayStart());
|
||||||
|
inWindow = inWindow && (v <= mockConductor.displayEnd());
|
||||||
|
}
|
||||||
|
return inWindow;
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
mockTelemetryService = jasmine.createSpyObj(
|
||||||
|
'telemetryService',
|
||||||
|
[ 'requestTelemetry', 'subscribe' ]
|
||||||
|
);
|
||||||
|
mockConductorService = jasmine.createSpyObj(
|
||||||
|
'conductorService',
|
||||||
|
['getConductor']
|
||||||
|
);
|
||||||
|
mockConductor = jasmine.createSpyObj(
|
||||||
|
'conductor',
|
||||||
|
[ 'queryStart', 'queryEnd', 'displayStart', 'displayEnd' ]
|
||||||
|
);
|
||||||
|
mockPromise = jasmine.createSpyObj(
|
||||||
|
'promise',
|
||||||
|
['then']
|
||||||
|
);
|
||||||
|
mockSeries = jasmine.createSpyObj(
|
||||||
|
'series',
|
||||||
|
[ 'getPointCount', 'getDomainValue', 'getRangeValue' ]
|
||||||
|
);
|
||||||
|
|
||||||
|
mockTelemetryService.requestTelemetry.andReturn(mockPromise);
|
||||||
|
mockConductorService.getConductor.andReturn(mockConductor);
|
||||||
|
|
||||||
|
// Prepare test series; make sure it has a broad range of
|
||||||
|
// domain values, with at least some in the query-able range
|
||||||
|
mockSeries.getPointCount.andReturn(1000);
|
||||||
|
mockSeries.getDomainValue.andCallFake(function (i) {
|
||||||
|
var j = i - 500;
|
||||||
|
return j * j * j;
|
||||||
|
});
|
||||||
|
|
||||||
|
mockConductor.queryStart.andReturn(-12321);
|
||||||
|
mockConductor.queryEnd.andReturn(-12321);
|
||||||
|
mockConductor.displayStart.andReturn(42);
|
||||||
|
mockConductor.displayEnd.andReturn(1977);
|
||||||
|
|
||||||
|
decorator = new ConductorTelemetryDecorator(
|
||||||
|
mockConductorService,
|
||||||
|
mockTelemetryService
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("adds display start/end times to historical requests", function () {
|
||||||
|
decorator.requestTelemetry([{ someKey: "some value" }]);
|
||||||
|
expect(mockTelemetryService.requestTelemetry)
|
||||||
|
.toHaveBeenCalledWith([{
|
||||||
|
someKey: "some value",
|
||||||
|
start: mockConductor.displayStart(),
|
||||||
|
end: mockConductor.displayEnd()
|
||||||
|
}]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("adds display start/end times to subscription requests", function () {
|
||||||
|
var mockCallback = jasmine.createSpy('callback');
|
||||||
|
decorator.subscribe(mockCallback, [{ someKey: "some value" }]);
|
||||||
|
expect(mockTelemetryService.subscribe)
|
||||||
|
.toHaveBeenCalledWith(jasmine.any(Function), [{
|
||||||
|
someKey: "some value",
|
||||||
|
start: mockConductor.displayStart(),
|
||||||
|
end: mockConductor.displayEnd()
|
||||||
|
}]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("prunes historical values to the displayable range", function () {
|
||||||
|
var packagedTelemetry;
|
||||||
|
decorator.requestTelemetry([{ source: "abc", key: "xyz" }]);
|
||||||
|
packagedTelemetry = mockPromise.then.mostRecentCall.args[0]({
|
||||||
|
"abc": { "xyz": mockSeries }
|
||||||
|
});
|
||||||
|
expect(seriesIsInWindow(packagedTelemetry.abc.xyz))
|
||||||
|
.toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("prunes subscribed values to the displayable range", function () {
|
||||||
|
var mockCallback = jasmine.createSpy('callback'),
|
||||||
|
packagedTelemetry;
|
||||||
|
decorator.subscribe(mockCallback, [{ source: "abc", key: "xyz" }]);
|
||||||
|
mockTelemetryService.subscribe.mostRecentCall.args[0]({
|
||||||
|
"abc": { "xyz": mockSeries }
|
||||||
|
});
|
||||||
|
packagedTelemetry = mockCallback.mostRecentCall.args[0];
|
||||||
|
expect(seriesIsInWindow(packagedTelemetry.abc.xyz))
|
||||||
|
.toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
@ -0,0 +1,86 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
/*global define,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
["../src/ConductorTelemetrySeries"],
|
||||||
|
function (ConductorTelemetrySeries) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
describe("ConductorTelemetrySeries", function () {
|
||||||
|
var mockSeries,
|
||||||
|
mockConductor,
|
||||||
|
testArray,
|
||||||
|
series;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
testArray = [ -10, 0, 42, 1977, 12321 ];
|
||||||
|
|
||||||
|
mockSeries = jasmine.createSpyObj(
|
||||||
|
'series',
|
||||||
|
[ 'getPointCount', 'getDomainValue', 'getRangeValue' ]
|
||||||
|
);
|
||||||
|
mockConductor = jasmine.createSpyObj(
|
||||||
|
'conductor',
|
||||||
|
[ 'queryStart', 'queryEnd', 'displayStart', 'displayEnd' ]
|
||||||
|
);
|
||||||
|
|
||||||
|
mockSeries.getPointCount.andCallFake(function () {
|
||||||
|
return testArray.length;
|
||||||
|
});
|
||||||
|
mockSeries.getDomainValue.andCallFake(function (i) {
|
||||||
|
return testArray[i];
|
||||||
|
});
|
||||||
|
mockSeries.getRangeValue.andCallFake(function (i) {
|
||||||
|
return testArray[i] * 2;
|
||||||
|
});
|
||||||
|
|
||||||
|
mockConductor.displayStart.andReturn(0);
|
||||||
|
mockConductor.displayEnd.andReturn(2000);
|
||||||
|
|
||||||
|
series = new ConductorTelemetrySeries(
|
||||||
|
mockSeries,
|
||||||
|
mockConductor
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("reduces the apparent size of a series", function () {
|
||||||
|
expect(series.getPointCount()).toEqual(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("maps domain value indexes to the displayable range", function () {
|
||||||
|
[0, 1, 2].forEach(function (i) {
|
||||||
|
expect(series.getDomainValue(i))
|
||||||
|
.toEqual(mockSeries.getDomainValue(i + 1));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("maps range value indexes to the displayable range", function () {
|
||||||
|
[0, 1, 2].forEach(function (i) {
|
||||||
|
expect(series.getRangeValue(i))
|
||||||
|
.toEqual(mockSeries.getRangeValue(i + 1));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
63
platform/features/conductor/test/TimeConductorSpec.js
Normal file
63
platform/features/conductor/test/TimeConductorSpec.js
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
/*global define,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EventSpec. Created by vwoeltje on 11/6/14. Modified by shale on 06/23/2015.
|
||||||
|
*/
|
||||||
|
define(
|
||||||
|
["../src/TimeConductor"],
|
||||||
|
function (TimeConductor) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
describe("TimeConductor", function () {
|
||||||
|
var testStart,
|
||||||
|
testEnd,
|
||||||
|
conductor;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
testStart = 42;
|
||||||
|
testEnd = 12321;
|
||||||
|
conductor = new TimeConductor(testStart, testEnd);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("provides accessors for query/display start/end times", function () {
|
||||||
|
expect(conductor.queryStart()).toEqual(testStart);
|
||||||
|
expect(conductor.queryEnd()).toEqual(testEnd);
|
||||||
|
expect(conductor.displayStart()).toEqual(testStart);
|
||||||
|
expect(conductor.displayEnd()).toEqual(testEnd);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("provides setters for query/display start/end times", function () {
|
||||||
|
expect(conductor.queryStart(1)).toEqual(1);
|
||||||
|
expect(conductor.queryEnd(2)).toEqual(2);
|
||||||
|
expect(conductor.displayStart(3)).toEqual(3);
|
||||||
|
expect(conductor.displayEnd(4)).toEqual(4);
|
||||||
|
expect(conductor.queryStart()).toEqual(1);
|
||||||
|
expect(conductor.queryEnd()).toEqual(2);
|
||||||
|
expect(conductor.displayStart()).toEqual(3);
|
||||||
|
expect(conductor.displayEnd()).toEqual(4);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
7
platform/features/conductor/test/suite.json
Normal file
7
platform/features/conductor/test/suite.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[
|
||||||
|
"ConductorRepresenter",
|
||||||
|
"ConductorService",
|
||||||
|
"ConductorTelemetryDecorator",
|
||||||
|
"ConductorTelemetrySeries",
|
||||||
|
"TimeConductor"
|
||||||
|
]
|
@ -167,8 +167,9 @@
|
|||||||
"$scope",
|
"$scope",
|
||||||
"$q",
|
"$q",
|
||||||
"dialogService",
|
"dialogService",
|
||||||
"telemetrySubscriber",
|
"telemetryHandler",
|
||||||
"telemetryFormatter"
|
"telemetryFormatter",
|
||||||
|
"throttle"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -38,12 +38,13 @@ define(
|
|||||||
* @constructor
|
* @constructor
|
||||||
* @param {Scope} $scope the controller's Angular scope
|
* @param {Scope} $scope the controller's Angular scope
|
||||||
*/
|
*/
|
||||||
function FixedController($scope, $q, dialogService, telemetrySubscriber, telemetryFormatter) {
|
function FixedController($scope, $q, dialogService, telemetryHandler, telemetryFormatter, throttle) {
|
||||||
var self = this,
|
var self = this,
|
||||||
subscription,
|
handle,
|
||||||
names = {}, // Cache names by ID
|
names = {}, // Cache names by ID
|
||||||
values = {}, // Cache values by ID
|
values = {}, // Cache values by ID
|
||||||
elementProxiesById = {};
|
elementProxiesById = {},
|
||||||
|
maxDomainValue = Number.POSITIVE_INFINITY;
|
||||||
|
|
||||||
// Convert from element x/y/width/height to an
|
// Convert from element x/y/width/height to an
|
||||||
// appropriate ng-style argument, to position elements.
|
// appropriate ng-style argument, to position elements.
|
||||||
@ -81,25 +82,50 @@ define(
|
|||||||
return element.handles().map(generateDragHandle);
|
return element.handles().map(generateDragHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the displayed value for this object
|
// Update the value displayed in elements of this telemetry object
|
||||||
function updateValue(telemetryObject) {
|
function setDisplayedValue(telemetryObject, value, alarm) {
|
||||||
var id = telemetryObject && telemetryObject.getId(),
|
var id = telemetryObject.getId();
|
||||||
|
(elementProxiesById[id] || []).forEach(function (element) {
|
||||||
|
names[id] = telemetryObject.getModel().name;
|
||||||
|
values[id] = telemetryFormatter.formatRangeValue(value);
|
||||||
|
element.name = names[id];
|
||||||
|
element.value = values[id];
|
||||||
|
element.cssClass = alarm && alarm.cssClass;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the displayed value for this object, from a specific
|
||||||
|
// telemetry series
|
||||||
|
function updateValueFromSeries(telemetryObject, telemetrySeries) {
|
||||||
|
var index = telemetrySeries.getPointCount() - 1,
|
||||||
limit = telemetryObject &&
|
limit = telemetryObject &&
|
||||||
telemetryObject.getCapability('limit'),
|
telemetryObject.getCapability('limit'),
|
||||||
datum = telemetryObject &&
|
datum = telemetryObject && handle.getDatum(
|
||||||
subscription.getDatum(telemetryObject),
|
telemetryObject,
|
||||||
alarm = limit && datum && limit.evaluate(datum);
|
index
|
||||||
|
);
|
||||||
|
|
||||||
if (id) {
|
setDisplayedValue(
|
||||||
(elementProxiesById[id] || []).forEach(function (element) {
|
telemetryObject,
|
||||||
names[id] = telemetryObject.getModel().name;
|
telemetrySeries.getRangeValue(index),
|
||||||
values[id] = telemetryFormatter.formatRangeValue(
|
limit && datum && limit.evaluate(datum)
|
||||||
subscription.getRangeValue(telemetryObject)
|
);
|
||||||
);
|
}
|
||||||
element.name = names[id];
|
|
||||||
element.value = values[id];
|
// Update the displayed value for this object
|
||||||
element.cssClass = alarm && alarm.cssClass;
|
function updateValue(telemetryObject) {
|
||||||
});
|
var limit = telemetryObject &&
|
||||||
|
telemetryObject.getCapability('limit'),
|
||||||
|
datum = telemetryObject &&
|
||||||
|
handle.getDatum(telemetryObject);
|
||||||
|
|
||||||
|
if (telemetryObject &&
|
||||||
|
(handle.getDomainValue(telemetryObject) < maxDomainValue)) {
|
||||||
|
setDisplayedValue(
|
||||||
|
telemetryObject,
|
||||||
|
handle.getRangeValue(telemetryObject),
|
||||||
|
limit && datum && limit.evaluate(datum)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,8 +141,8 @@ define(
|
|||||||
|
|
||||||
// Update telemetry values based on new data available
|
// Update telemetry values based on new data available
|
||||||
function updateValues() {
|
function updateValues() {
|
||||||
if (subscription) {
|
if (handle) {
|
||||||
subscription.getTelemetryObjects().forEach(updateValue);
|
handle.getTelemetryObjects().forEach(updateValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -178,22 +204,29 @@ define(
|
|||||||
|
|
||||||
// Free up subscription to telemetry
|
// Free up subscription to telemetry
|
||||||
function releaseSubscription() {
|
function releaseSubscription() {
|
||||||
if (subscription) {
|
if (handle) {
|
||||||
subscription.unsubscribe();
|
handle.unsubscribe();
|
||||||
subscription = undefined;
|
handle = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subscribe to telemetry updates for this domain object
|
// Subscribe to telemetry updates for this domain object
|
||||||
function subscribe(domainObject) {
|
function subscribe(domainObject) {
|
||||||
// Release existing subscription (if any)
|
// Release existing subscription (if any)
|
||||||
if (subscription) {
|
if (handle) {
|
||||||
subscription.unsubscribe();
|
handle.unsubscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make a new subscription
|
// Make a new subscription
|
||||||
subscription = domainObject &&
|
handle = domainObject && telemetryHandler.handle(
|
||||||
telemetrySubscriber.subscribe(domainObject, updateValues);
|
domainObject,
|
||||||
|
updateValues
|
||||||
|
);
|
||||||
|
// Request an initial historical telemetry value
|
||||||
|
handle.request(
|
||||||
|
{ size: 1 }, // Only need a single data point
|
||||||
|
updateValueFromSeries
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle changes in the object's composition
|
// Handle changes in the object's composition
|
||||||
@ -204,6 +237,17 @@ define(
|
|||||||
subscribe($scope.domainObject);
|
subscribe($scope.domainObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Trigger a new query for telemetry data
|
||||||
|
function updateDisplayBounds(event, bounds) {
|
||||||
|
maxDomainValue = bounds.end;
|
||||||
|
if (handle) {
|
||||||
|
handle.request(
|
||||||
|
{ size: 1 }, // Only need a single data point
|
||||||
|
updateValueFromSeries
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Add an element to this view
|
// Add an element to this view
|
||||||
function addElement(element) {
|
function addElement(element) {
|
||||||
// Ensure that configuration field is populated
|
// Ensure that configuration field is populated
|
||||||
@ -278,6 +322,9 @@ define(
|
|||||||
|
|
||||||
// Position panes where they are dropped
|
// Position panes where they are dropped
|
||||||
$scope.$on("mctDrop", handleDrop);
|
$scope.$on("mctDrop", handleDrop);
|
||||||
|
|
||||||
|
// Respond to external bounds changes
|
||||||
|
$scope.$on("telemetry:display:bounds", updateDisplayBounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -30,10 +30,10 @@ define(
|
|||||||
var mockScope,
|
var mockScope,
|
||||||
mockQ,
|
mockQ,
|
||||||
mockDialogService,
|
mockDialogService,
|
||||||
mockSubscriber,
|
mockHandler,
|
||||||
mockFormatter,
|
mockFormatter,
|
||||||
mockDomainObject,
|
mockDomainObject,
|
||||||
mockSubscription,
|
mockHandle,
|
||||||
mockEvent,
|
mockEvent,
|
||||||
testGrid,
|
testGrid,
|
||||||
testModel,
|
testModel,
|
||||||
@ -78,9 +78,9 @@ define(
|
|||||||
'$scope',
|
'$scope',
|
||||||
[ "$on", "$watch", "commit" ]
|
[ "$on", "$watch", "commit" ]
|
||||||
);
|
);
|
||||||
mockSubscriber = jasmine.createSpyObj(
|
mockHandler = jasmine.createSpyObj(
|
||||||
'telemetrySubscriber',
|
'telemetryHandler',
|
||||||
[ 'subscribe' ]
|
[ 'handle' ]
|
||||||
);
|
);
|
||||||
mockQ = jasmine.createSpyObj('$q', ['when']);
|
mockQ = jasmine.createSpyObj('$q', ['when']);
|
||||||
mockDialogService = jasmine.createSpyObj(
|
mockDialogService = jasmine.createSpyObj(
|
||||||
@ -95,9 +95,16 @@ define(
|
|||||||
'domainObject',
|
'domainObject',
|
||||||
[ 'getId', 'getModel', 'getCapability' ]
|
[ 'getId', 'getModel', 'getCapability' ]
|
||||||
);
|
);
|
||||||
mockSubscription = jasmine.createSpyObj(
|
mockHandle = jasmine.createSpyObj(
|
||||||
'subscription',
|
'subscription',
|
||||||
[ 'unsubscribe', 'getTelemetryObjects', 'getRangeValue', 'getDatum' ]
|
[
|
||||||
|
'unsubscribe',
|
||||||
|
'getDomainValue',
|
||||||
|
'getTelemetryObjects',
|
||||||
|
'getRangeValue',
|
||||||
|
'getDatum',
|
||||||
|
'request'
|
||||||
|
]
|
||||||
);
|
);
|
||||||
mockEvent = jasmine.createSpyObj(
|
mockEvent = jasmine.createSpyObj(
|
||||||
'event',
|
'event',
|
||||||
@ -116,13 +123,14 @@ define(
|
|||||||
{ type: "fixed.telemetry", id: 'c', x: 1, y: 1 }
|
{ type: "fixed.telemetry", id: 'c', x: 1, y: 1 }
|
||||||
]};
|
]};
|
||||||
|
|
||||||
mockSubscriber.subscribe.andReturn(mockSubscription);
|
mockHandler.handle.andReturn(mockHandle);
|
||||||
mockSubscription.getTelemetryObjects.andReturn(
|
mockHandle.getTelemetryObjects.andReturn(
|
||||||
testModel.composition.map(makeMockDomainObject)
|
testModel.composition.map(makeMockDomainObject)
|
||||||
);
|
);
|
||||||
mockSubscription.getRangeValue.andCallFake(function (o) {
|
mockHandle.getRangeValue.andCallFake(function (o) {
|
||||||
return testValues[o.getId()];
|
return testValues[o.getId()];
|
||||||
});
|
});
|
||||||
|
mockHandle.getDomainValue.andReturn(12321);
|
||||||
mockFormatter.formatRangeValue.andCallFake(function (v) {
|
mockFormatter.formatRangeValue.andCallFake(function (v) {
|
||||||
return "Formatted " + v;
|
return "Formatted " + v;
|
||||||
});
|
});
|
||||||
@ -137,7 +145,7 @@ define(
|
|||||||
mockScope,
|
mockScope,
|
||||||
mockQ,
|
mockQ,
|
||||||
mockDialogService,
|
mockDialogService,
|
||||||
mockSubscriber,
|
mockHandler,
|
||||||
mockFormatter
|
mockFormatter
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -145,7 +153,7 @@ define(
|
|||||||
it("subscribes when a domain object is available", function () {
|
it("subscribes when a domain object is available", function () {
|
||||||
mockScope.domainObject = mockDomainObject;
|
mockScope.domainObject = mockDomainObject;
|
||||||
findWatch("domainObject")(mockDomainObject);
|
findWatch("domainObject")(mockDomainObject);
|
||||||
expect(mockSubscriber.subscribe).toHaveBeenCalledWith(
|
expect(mockHandler.handle).toHaveBeenCalledWith(
|
||||||
mockDomainObject,
|
mockDomainObject,
|
||||||
jasmine.any(Function)
|
jasmine.any(Function)
|
||||||
);
|
);
|
||||||
@ -156,13 +164,13 @@ define(
|
|||||||
|
|
||||||
// First pass - should simply should subscribe
|
// First pass - should simply should subscribe
|
||||||
findWatch("domainObject")(mockDomainObject);
|
findWatch("domainObject")(mockDomainObject);
|
||||||
expect(mockSubscription.unsubscribe).not.toHaveBeenCalled();
|
expect(mockHandle.unsubscribe).not.toHaveBeenCalled();
|
||||||
expect(mockSubscriber.subscribe.calls.length).toEqual(1);
|
expect(mockHandler.handle.calls.length).toEqual(1);
|
||||||
|
|
||||||
// Object changes - should unsubscribe then resubscribe
|
// Object changes - should unsubscribe then resubscribe
|
||||||
findWatch("domainObject")(mockDomainObject);
|
findWatch("domainObject")(mockDomainObject);
|
||||||
expect(mockSubscription.unsubscribe).toHaveBeenCalled();
|
expect(mockHandle.unsubscribe).toHaveBeenCalled();
|
||||||
expect(mockSubscriber.subscribe.calls.length).toEqual(2);
|
expect(mockHandler.handle.calls.length).toEqual(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("exposes visible elements based on configuration", function () {
|
it("exposes visible elements based on configuration", function () {
|
||||||
@ -255,7 +263,7 @@ define(
|
|||||||
findWatch("model.composition")(mockScope.model.composition);
|
findWatch("model.composition")(mockScope.model.composition);
|
||||||
|
|
||||||
// Invoke the subscription callback
|
// Invoke the subscription callback
|
||||||
mockSubscriber.subscribe.mostRecentCall.args[1]();
|
mockHandler.handle.mostRecentCall.args[1]();
|
||||||
|
|
||||||
// Get elements that controller is now exposing
|
// Get elements that controller is now exposing
|
||||||
elements = controller.getElements();
|
elements = controller.getElements();
|
||||||
@ -333,11 +341,11 @@ define(
|
|||||||
// Make an object available
|
// Make an object available
|
||||||
findWatch('domainObject')(mockDomainObject);
|
findWatch('domainObject')(mockDomainObject);
|
||||||
// Also verify precondition
|
// Also verify precondition
|
||||||
expect(mockSubscription.unsubscribe).not.toHaveBeenCalled();
|
expect(mockHandle.unsubscribe).not.toHaveBeenCalled();
|
||||||
// Destroy the scope
|
// Destroy the scope
|
||||||
findOn('$destroy')();
|
findOn('$destroy')();
|
||||||
// Should have unsubscribed
|
// Should have unsubscribed
|
||||||
expect(mockSubscription.unsubscribe).toHaveBeenCalled();
|
expect(mockHandle.unsubscribe).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("exposes its grid size", function () {
|
it("exposes its grid size", function () {
|
||||||
|
@ -65,6 +65,8 @@ define(
|
|||||||
subPlotFactory = new SubPlotFactory(telemetryFormatter),
|
subPlotFactory = new SubPlotFactory(telemetryFormatter),
|
||||||
cachedObjects = [],
|
cachedObjects = [],
|
||||||
updater,
|
updater,
|
||||||
|
lastBounds,
|
||||||
|
throttledRequery,
|
||||||
handle;
|
handle;
|
||||||
|
|
||||||
// Populate the scope with axis information (specifically, options
|
// Populate the scope with axis information (specifically, options
|
||||||
@ -94,6 +96,17 @@ define(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Change the displayable bounds
|
||||||
|
function setBasePanZoom(bounds) {
|
||||||
|
var start = bounds.start,
|
||||||
|
end = bounds.end;
|
||||||
|
if (updater) {
|
||||||
|
updater.setDomainBounds(start, end);
|
||||||
|
self.update();
|
||||||
|
}
|
||||||
|
lastBounds = bounds;
|
||||||
|
}
|
||||||
|
|
||||||
// Reinstantiate the plot updater (e.g. because we have a
|
// Reinstantiate the plot updater (e.g. because we have a
|
||||||
// new subscription.) This will clear the plot.
|
// new subscription.) This will clear the plot.
|
||||||
function recreateUpdater() {
|
function recreateUpdater() {
|
||||||
@ -107,10 +120,15 @@ define(
|
|||||||
handle,
|
handle,
|
||||||
($scope.axes[1].active || {}).key
|
($scope.axes[1].active || {}).key
|
||||||
);
|
);
|
||||||
|
// Keep any externally-provided bounds
|
||||||
|
if (lastBounds) {
|
||||||
|
setBasePanZoom(lastBounds);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle new telemetry data in this plot
|
// Handle new telemetry data in this plot
|
||||||
function updateValues() {
|
function updateValues() {
|
||||||
|
self.pending = false;
|
||||||
if (handle) {
|
if (handle) {
|
||||||
setupModes(handle.getTelemetryObjects());
|
setupModes(handle.getTelemetryObjects());
|
||||||
}
|
}
|
||||||
@ -126,6 +144,7 @@ define(
|
|||||||
|
|
||||||
// Display new historical data as it becomes available
|
// Display new historical data as it becomes available
|
||||||
function addHistoricalData(domainObject, series) {
|
function addHistoricalData(domainObject, series) {
|
||||||
|
self.pending = false;
|
||||||
updater.addHistorical(domainObject, series);
|
updater.addHistorical(domainObject, series);
|
||||||
self.modeOptions.getModeHandler().plotTelemetry(updater);
|
self.modeOptions.getModeHandler().plotTelemetry(updater);
|
||||||
self.update();
|
self.update();
|
||||||
@ -165,6 +184,19 @@ define(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Respond to a display bounds change (requery for data)
|
||||||
|
function changeDisplayBounds(event, bounds) {
|
||||||
|
self.pending = true;
|
||||||
|
releaseSubscription();
|
||||||
|
throttledRequery();
|
||||||
|
setBasePanZoom(bounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reestablish/reissue request for telemetry
|
||||||
|
throttledRequery = throttle(function () {
|
||||||
|
subscribe($scope.domainObject);
|
||||||
|
}, 250);
|
||||||
|
|
||||||
this.modeOptions = new PlotModeOptions([], subPlotFactory);
|
this.modeOptions = new PlotModeOptions([], subPlotFactory);
|
||||||
this.updateValues = updateValues;
|
this.updateValues = updateValues;
|
||||||
|
|
||||||
@ -174,12 +206,19 @@ define(
|
|||||||
.forEach(updateSubplot);
|
.forEach(updateSubplot);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
self.pending = true;
|
||||||
|
|
||||||
// Subscribe to telemetry when a domain object becomes available
|
// Subscribe to telemetry when a domain object becomes available
|
||||||
$scope.$watch('domainObject', subscribe);
|
$scope.$watch('domainObject', subscribe);
|
||||||
|
|
||||||
|
// Respond to external bounds changes
|
||||||
|
$scope.$on("telemetry:display:bounds", changeDisplayBounds);
|
||||||
|
|
||||||
// Unsubscribe when the plot is destroyed
|
// Unsubscribe when the plot is destroyed
|
||||||
$scope.$on("$destroy", releaseSubscription);
|
$scope.$on("$destroy", releaseSubscription);
|
||||||
|
|
||||||
|
// Notify any external observers that a new telemetry view is here
|
||||||
|
$scope.$emit("telemetry:view");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -275,7 +314,7 @@ define(
|
|||||||
PlotController.prototype.isRequestPending = function () {
|
PlotController.prototype.isRequestPending = function () {
|
||||||
// Placeholder; this should reflect request state
|
// Placeholder; this should reflect request state
|
||||||
// when requesting historical telemetry
|
// when requesting historical telemetry
|
||||||
return false;
|
return this.pending;
|
||||||
};
|
};
|
||||||
|
|
||||||
return PlotController;
|
return PlotController;
|
||||||
|
@ -143,8 +143,7 @@ define(
|
|||||||
PlotPanZoomStackGroup.prototype.getDepth = function () {
|
PlotPanZoomStackGroup.prototype.getDepth = function () {
|
||||||
// All stacks are kept in sync, so look up depth
|
// All stacks are kept in sync, so look up depth
|
||||||
// from the first one.
|
// from the first one.
|
||||||
return this.stacks.length > 0 ?
|
return this.stacks.length > 0 ? this.stacks[0].getDepth() : 0;
|
||||||
this.stacks[0].getDepth() : 0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -141,10 +141,10 @@ define(
|
|||||||
PlotUpdater.prototype.initializeDomainOffset = function (values) {
|
PlotUpdater.prototype.initializeDomainOffset = function (values) {
|
||||||
this.domainOffset =
|
this.domainOffset =
|
||||||
((this.domainOffset === undefined) && (values.length > 0)) ?
|
((this.domainOffset === undefined) && (values.length > 0)) ?
|
||||||
(values.reduce(function (a, b) {
|
(values.reduce(function (a, b) {
|
||||||
return (a || 0) + (b || 0);
|
return (a || 0) + (b || 0);
|
||||||
}, 0) / values.length) :
|
}, 0) / values.length) :
|
||||||
this.domainOffset;
|
this.domainOffset;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Expand range slightly so points near edges are visible
|
// Expand range slightly so points near edges are visible
|
||||||
@ -159,7 +159,10 @@ define(
|
|||||||
|
|
||||||
// Update dimensions and origin based on extrema of plots
|
// Update dimensions and origin based on extrema of plots
|
||||||
PlotUpdater.prototype.updateBounds = function () {
|
PlotUpdater.prototype.updateBounds = function () {
|
||||||
var bufferArray = this.bufferArray;
|
var bufferArray = this.bufferArray,
|
||||||
|
priorDomainOrigin = this.origin[0],
|
||||||
|
priorDomainDimensions = this.dimensions[0];
|
||||||
|
|
||||||
if (bufferArray.length > 0) {
|
if (bufferArray.length > 0) {
|
||||||
this.domainExtrema = bufferArray.map(function (lineBuffer) {
|
this.domainExtrema = bufferArray.map(function (lineBuffer) {
|
||||||
return lineBuffer.getDomainExtrema();
|
return lineBuffer.getDomainExtrema();
|
||||||
@ -178,6 +181,18 @@ define(
|
|||||||
// Enforce some minimum visible area
|
// Enforce some minimum visible area
|
||||||
this.expandRange();
|
this.expandRange();
|
||||||
|
|
||||||
|
// Suppress domain changes when pinned
|
||||||
|
if (this.hasSpecificDomainBounds) {
|
||||||
|
this.origin[0] = priorDomainOrigin;
|
||||||
|
this.dimensions[0] = priorDomainDimensions;
|
||||||
|
if (this.following) {
|
||||||
|
this.origin[0] = Math.max(
|
||||||
|
this.domainExtrema[1] - this.dimensions[0],
|
||||||
|
this.origin[0]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ...then enforce a fixed duration if needed
|
// ...then enforce a fixed duration if needed
|
||||||
if (this.fixedDuration !== undefined) {
|
if (this.fixedDuration !== undefined) {
|
||||||
this.origin[0] = this.origin[0] + this.dimensions[0] -
|
this.origin[0] = this.origin[0] + this.dimensions[0] -
|
||||||
@ -281,6 +296,21 @@ define(
|
|||||||
return this.bufferArray;
|
return this.bufferArray;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the start and end boundaries (usually time) for the
|
||||||
|
* domain axis of this updater.
|
||||||
|
*/
|
||||||
|
PlotUpdater.prototype.setDomainBounds = function (start, end) {
|
||||||
|
this.fixedDuration = end - start;
|
||||||
|
this.origin[0] = start;
|
||||||
|
this.dimensions[0] = this.fixedDuration;
|
||||||
|
|
||||||
|
// Suppress follow behavior if we have windowed in on the past
|
||||||
|
this.hasSpecificDomainBounds = true;
|
||||||
|
this.following =
|
||||||
|
!this.domainExtrema || (end >= this.domainExtrema[1]);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fill in historical data.
|
* Fill in historical data.
|
||||||
*/
|
*/
|
||||||
|
@ -45,11 +45,19 @@ define(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function fireEvent(name, args) {
|
||||||
|
mockScope.$on.calls.forEach(function (call) {
|
||||||
|
if (call.args[0] === name) {
|
||||||
|
call.args[1].apply(null, args || []);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
mockScope = jasmine.createSpyObj(
|
mockScope = jasmine.createSpyObj(
|
||||||
"$scope",
|
"$scope",
|
||||||
[ "$watch", "$on" ]
|
[ "$watch", "$on", "$emit" ]
|
||||||
);
|
);
|
||||||
mockFormatter = jasmine.createSpyObj(
|
mockFormatter = jasmine.createSpyObj(
|
||||||
"formatter",
|
"formatter",
|
||||||
@ -87,6 +95,7 @@ define(
|
|||||||
mockHandle.getMetadata.andReturn([{}]);
|
mockHandle.getMetadata.andReturn([{}]);
|
||||||
mockHandle.getDomainValue.andReturn(123);
|
mockHandle.getDomainValue.andReturn(123);
|
||||||
mockHandle.getRangeValue.andReturn(42);
|
mockHandle.getRangeValue.andReturn(42);
|
||||||
|
mockScope.domainObject = mockDomainObject;
|
||||||
|
|
||||||
controller = new PlotController(
|
controller = new PlotController(
|
||||||
mockScope,
|
mockScope,
|
||||||
@ -212,7 +221,12 @@ define(
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("indicates if a request is pending", function () {
|
it("indicates if a request is pending", function () {
|
||||||
// Placeholder; need to support requesting telemetry
|
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||||
|
expect(controller.isRequestPending()).toBeTruthy();
|
||||||
|
mockHandle.request.mostRecentCall.args[1](
|
||||||
|
mockDomainObject,
|
||||||
|
mockSeries
|
||||||
|
);
|
||||||
expect(controller.isRequestPending()).toBeFalsy();
|
expect(controller.isRequestPending()).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -233,10 +247,20 @@ define(
|
|||||||
// Also verify precondition
|
// Also verify precondition
|
||||||
expect(mockHandle.unsubscribe).not.toHaveBeenCalled();
|
expect(mockHandle.unsubscribe).not.toHaveBeenCalled();
|
||||||
// Destroy the scope
|
// Destroy the scope
|
||||||
mockScope.$on.mostRecentCall.args[1]();
|
fireEvent("$destroy");
|
||||||
// Should have unsubscribed
|
// Should have unsubscribed
|
||||||
expect(mockHandle.unsubscribe).toHaveBeenCalled();
|
expect(mockHandle.unsubscribe).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("requeries when displayable bounds change", function () {
|
||||||
|
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||||
|
expect(mockHandle.request.calls.length).toEqual(1);
|
||||||
|
fireEvent("telemetry:display:bounds", [
|
||||||
|
{},
|
||||||
|
{ start: 10, end: 100 }
|
||||||
|
]);
|
||||||
|
expect(mockHandle.request.calls.length).toEqual(2);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -55,7 +55,7 @@ define(
|
|||||||
var range = this.rangeMetadata.key,
|
var range = this.rangeMetadata.key,
|
||||||
limit = domainObject.getCapability('limit'),
|
limit = domainObject.getCapability('limit'),
|
||||||
value = datum[range],
|
value = datum[range],
|
||||||
alarm = limit.evaluate(datum, range);
|
alarm = limit && limit.evaluate(datum, range);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
cssClass: alarm && alarm.cssClass,
|
cssClass: alarm && alarm.cssClass,
|
||||||
|
@ -136,6 +136,14 @@ define(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Destroy (deallocate any resources associated with) any
|
||||||
|
// active representers.
|
||||||
|
function destroyRepresenters() {
|
||||||
|
activeRepresenters.forEach(function (activeRepresenter) {
|
||||||
|
activeRepresenter.destroy();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// General-purpose refresh mechanism; should set up the scope
|
// General-purpose refresh mechanism; should set up the scope
|
||||||
// as appropriate for current representation key and
|
// as appropriate for current representation key and
|
||||||
// domain object.
|
// domain object.
|
||||||
@ -152,10 +160,8 @@ define(
|
|||||||
// via the "inclusion" field
|
// via the "inclusion" field
|
||||||
$scope.inclusion = representation && getPath(representation);
|
$scope.inclusion = representation && getPath(representation);
|
||||||
|
|
||||||
// Any existing gestures are no longer valid; release them.
|
// Any existing representers are no longer valid; release them.
|
||||||
activeRepresenters.forEach(function (activeRepresenter) {
|
destroyRepresenters();
|
||||||
activeRepresenter.destroy();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Log if a key was given, but no matching representation
|
// Log if a key was given, but no matching representation
|
||||||
// was found.
|
// was found.
|
||||||
@ -209,6 +215,10 @@ define(
|
|||||||
// model's "modified" field, by the mutation capability.
|
// model's "modified" field, by the mutation capability.
|
||||||
$scope.$watch("domainObject.getModel().modified", refreshCapabilities);
|
$scope.$watch("domainObject.getModel().modified", refreshCapabilities);
|
||||||
|
|
||||||
|
// Make sure any resources allocated by representers also get
|
||||||
|
// released.
|
||||||
|
$scope.$on("$destroy", destroyRepresenters);
|
||||||
|
|
||||||
// Do one initial refresh, so that we don't need another
|
// Do one initial refresh, so that we don't need another
|
||||||
// digest iteration just to populate the scope. Failure to
|
// digest iteration just to populate the scope. Failure to
|
||||||
// do this can result in unstable digest cycles, which
|
// do this can result in unstable digest cycles, which
|
||||||
|
@ -106,7 +106,7 @@ define(
|
|||||||
mockSce.trustAsResourceUrl.andCallFake(function (url) {
|
mockSce.trustAsResourceUrl.andCallFake(function (url) {
|
||||||
return url;
|
return url;
|
||||||
});
|
});
|
||||||
mockScope = jasmine.createSpyObj("scope", [ "$watch" ]);
|
mockScope = jasmine.createSpyObj("scope", [ "$watch", "$on" ]);
|
||||||
mockElement = jasmine.createSpyObj("element", JQLITE_FUNCTIONS);
|
mockElement = jasmine.createSpyObj("element", JQLITE_FUNCTIONS);
|
||||||
mockDomainObject = jasmine.createSpyObj("domainObject", DOMAIN_OBJECT_METHODS);
|
mockDomainObject = jasmine.createSpyObj("domainObject", DOMAIN_OBJECT_METHODS);
|
||||||
|
|
||||||
|
@ -31,6 +31,30 @@ define(
|
|||||||
function () {
|
function () {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes a request for telemetry data. Note that responses
|
||||||
|
* may contain either a sub- or superset of the requested data.
|
||||||
|
* @typedef TelemetryRequest
|
||||||
|
* @property {string} source an identifier for the relevant
|
||||||
|
* source of telemetry data
|
||||||
|
* @property {string} key an identifier for the specific
|
||||||
|
* series of telemetry data provided by that source
|
||||||
|
* @property {number} [start] the earliest domain value of
|
||||||
|
* interest for that telemetry data; for time-based
|
||||||
|
* domains, this is in milliseconds since the start
|
||||||
|
* of 1970
|
||||||
|
* @property {number} [end] the latest domain value of interest
|
||||||
|
* for that telemetry data; for time-based domains,
|
||||||
|
* this is in milliseconds since 1970
|
||||||
|
* @property {string} [domain] the domain for the query; if
|
||||||
|
* omitted, this will be whatever the "normal"
|
||||||
|
* domain is for a given telemetry series (the
|
||||||
|
* first domain from its metadata)
|
||||||
|
* @property {number} [size] if set, indicates the maximum number
|
||||||
|
* of data points of interest for this request (more
|
||||||
|
* recent domain values will be preferred)
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request telemetry data.
|
* Request telemetry data.
|
||||||
* @param {TelemetryRequest[]} requests and array of
|
* @param {TelemetryRequest[]} requests and array of
|
||||||
|
@ -79,8 +79,7 @@ define(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Change the request duration.
|
* Change the request duration.
|
||||||
* @param {object|number} request the duration of historical
|
* @param {TelemetryRequest} request the request to issue
|
||||||
* data to look at; or, the request to issue
|
|
||||||
* @param {Function} [callback] a callback that will be
|
* @param {Function} [callback] a callback that will be
|
||||||
* invoked as new data becomes available, with the
|
* invoked as new data becomes available, with the
|
||||||
* domain object for which new data is available.
|
* domain object for which new data is available.
|
||||||
@ -107,6 +106,26 @@ define(
|
|||||||
.then(issueRequests);
|
.then(issueRequests);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the latest telemetry datum for this domain object. This
|
||||||
|
* will be from real-time telemetry, unless an index is specified,
|
||||||
|
* in which case it will be pulled from the historical telemetry
|
||||||
|
* series at the specified index.
|
||||||
|
*
|
||||||
|
* @param {DomainObject} domainObject the object of interest
|
||||||
|
* @param {number} [index] the index of the data of interest
|
||||||
|
* @returns {TelemetryDatum} the most recent datum
|
||||||
|
*/
|
||||||
|
self.getDatum = function (telemetryObject, index) {
|
||||||
|
return typeof index !== 'number' ?
|
||||||
|
subscription.getDatum(telemetryObject) :
|
||||||
|
subscription.makeDatum(
|
||||||
|
telemetryObject,
|
||||||
|
this.getSeries(telemetryObject),
|
||||||
|
index
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,25 +123,6 @@ define(
|
|||||||
telemetryCapability.getMetadata();
|
telemetryCapability.getMetadata();
|
||||||
}
|
}
|
||||||
|
|
||||||
// From a telemetry series, retrieve a single data point
|
|
||||||
// containing all fields for domains/ranges
|
|
||||||
function makeDatum(domainObject, series, index) {
|
|
||||||
var metadata = lookupMetadata(domainObject),
|
|
||||||
result = {};
|
|
||||||
|
|
||||||
(metadata.domains || []).forEach(function (domain) {
|
|
||||||
result[domain.key] =
|
|
||||||
series.getDomainValue(index, domain.key);
|
|
||||||
});
|
|
||||||
|
|
||||||
(metadata.ranges || []).forEach(function (range) {
|
|
||||||
result[range.key] =
|
|
||||||
series.getRangeValue(index, range.key);
|
|
||||||
});
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the latest telemetry data for a specific
|
// Update the latest telemetry data for a specific
|
||||||
// domain object. This will notify listeners.
|
// domain object. This will notify listeners.
|
||||||
function update(domainObject, series) {
|
function update(domainObject, series) {
|
||||||
@ -160,7 +141,7 @@ define(
|
|||||||
pool.put(domainObject.getId(), {
|
pool.put(domainObject.getId(), {
|
||||||
domain: series.getDomainValue(count - 1),
|
domain: series.getDomainValue(count - 1),
|
||||||
range: series.getRangeValue(count - 1),
|
range: series.getRangeValue(count - 1),
|
||||||
datum: makeDatum(domainObject, series, count - 1)
|
datum: self.makeDatum(domainObject, series, count - 1)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -188,6 +169,11 @@ define(
|
|||||||
function cacheObjectReferences(objects) {
|
function cacheObjectReferences(objects) {
|
||||||
self.telemetryObjects = objects;
|
self.telemetryObjects = objects;
|
||||||
self.metadatas = objects.map(lookupMetadata);
|
self.metadatas = objects.map(lookupMetadata);
|
||||||
|
|
||||||
|
self.metadataById = {};
|
||||||
|
objects.forEach(function (obj, i) {
|
||||||
|
self.metadataById[obj.getId()] = self.metadatas[i];
|
||||||
|
});
|
||||||
// Fire callback, as this will be the first time that
|
// Fire callback, as this will be the first time that
|
||||||
// telemetry objects are available, or these objects
|
// telemetry objects are available, or these objects
|
||||||
// will have changed.
|
// will have changed.
|
||||||
@ -241,6 +227,34 @@ define(
|
|||||||
this.unlistenToMutation = addMutationListener();
|
this.unlistenToMutation = addMutationListener();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* From a telemetry series, retrieve a single data point
|
||||||
|
* containing all fields for domains/ranges
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
TelemetrySubscription.prototype.makeDatum = function (domainObject, series, index) {
|
||||||
|
var id = domainObject && domainObject.getId(),
|
||||||
|
metadata = (id && this.metadataById[id]) || {},
|
||||||
|
result = {};
|
||||||
|
|
||||||
|
(metadata.domains || []).forEach(function (domain) {
|
||||||
|
result[domain.key] =
|
||||||
|
series.getDomainValue(index, domain.key);
|
||||||
|
});
|
||||||
|
|
||||||
|
(metadata.ranges || []).forEach(function (range) {
|
||||||
|
result[range.key] =
|
||||||
|
series.getRangeValue(index, range.key);
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Terminate all underlying subscriptions.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
TelemetrySubscription.prototype.unsubscribeAll = function () {
|
TelemetrySubscription.prototype.unsubscribeAll = function () {
|
||||||
var $q = this.$q;
|
var $q = this.$q;
|
||||||
return this.unsubscribePromise.then(function (unsubscribes) {
|
return this.unsubscribePromise.then(function (unsubscribes) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user