mirror of
https://github.com/nasa/openmct.git
synced 2025-02-20 17:33:23 +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.
|
||||
*/
|
||||
define(
|
||||
["./SinewaveTelemetry"],
|
||||
function (SinewaveTelemetry) {
|
||||
["./SinewaveTelemetrySeries"],
|
||||
function (SinewaveTelemetrySeries) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
@ -45,7 +45,7 @@ define(
|
||||
function generateData(request) {
|
||||
return {
|
||||
key: request.key,
|
||||
telemetry: new SinewaveTelemetry(request)
|
||||
telemetry: new SinewaveTelemetrySeries(request)
|
||||
};
|
||||
}
|
||||
|
||||
@ -112,4 +112,4 @@ define(
|
||||
|
||||
return SinewaveTelemetryProvider;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -29,35 +29,45 @@ define(
|
||||
function () {
|
||||
"use strict";
|
||||
|
||||
var firstObservedTime = Date.now();
|
||||
var firstObservedTime = Math.floor(Date.now() / 1000);
|
||||
|
||||
/**
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function SinewaveTelemetry(request) {
|
||||
var latestObservedTime = Date.now(),
|
||||
count = Math.floor((latestObservedTime - firstObservedTime) / 1000),
|
||||
function SinewaveTelemetrySeries(request) {
|
||||
var latestObservedTime = Math.floor(Date.now() / 1000),
|
||||
endTime = (request.end !== undefined) ?
|
||||
Math.floor(request.end / 1000) : latestObservedTime,
|
||||
count =
|
||||
Math.min(endTime, latestObservedTime) - firstObservedTime,
|
||||
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 () {
|
||||
return count;
|
||||
return count - offset;
|
||||
};
|
||||
|
||||
generatorData.getDomainValue = function (i, domain) {
|
||||
return i * 1000 +
|
||||
(domain !== 'delta' ? firstObservedTime : 0);
|
||||
return (i + offset) * 1000 +
|
||||
(domain !== 'delta' ? (firstObservedTime * 1000) : 0);
|
||||
};
|
||||
|
||||
generatorData.getRangeValue = function (i, range) {
|
||||
range = range || "sin";
|
||||
return Math[range](i * Math.PI * 2 / period);
|
||||
return Math[range]((i + offset) * Math.PI * 2 / period);
|
||||
};
|
||||
|
||||
return generatorData;
|
||||
}
|
||||
|
||||
return SinewaveTelemetry;
|
||||
return SinewaveTelemetrySeries;
|
||||
}
|
||||
);
|
||||
);
|
@ -45,6 +45,16 @@
|
||||
}
|
||||
],
|
||||
"controllers": [
|
||||
{
|
||||
"key": "TimeRangeController",
|
||||
"implementation": "controllers/TimeRangeController.js",
|
||||
"depends": [ "$scope", "now" ]
|
||||
},
|
||||
{
|
||||
"key": "DateTimePickerController",
|
||||
"implementation": "controllers/DateTimePickerController.js",
|
||||
"depends": [ "$scope", "now" ]
|
||||
},
|
||||
{
|
||||
"key": "TreeNodeController",
|
||||
"implementation": "controllers/TreeNodeController.js",
|
||||
@ -105,11 +115,21 @@
|
||||
"implementation": "directives/MCTDrag.js",
|
||||
"depends": [ "$document" ]
|
||||
},
|
||||
{
|
||||
"key": "mctClickElsewhere",
|
||||
"implementation": "directives/MCTClickElsewhere.js",
|
||||
"depends": [ "$document" ]
|
||||
},
|
||||
{
|
||||
"key": "mctResize",
|
||||
"implementation": "directives/MCTResize.js",
|
||||
"depends": [ "$timeout" ]
|
||||
},
|
||||
{
|
||||
"key": "mctPopup",
|
||||
"implementation": "directives/MCTPopup.js",
|
||||
"depends": [ "$window", "$document", "$compile", "$interval" ]
|
||||
},
|
||||
{
|
||||
"key": "mctScrollX",
|
||||
"implementation": "directives/MCTScroll.js",
|
||||
@ -213,6 +233,10 @@
|
||||
{
|
||||
"key": "selector",
|
||||
"templateUrl": "templates/controls/selector.html"
|
||||
},
|
||||
{
|
||||
"key": "datetime-picker",
|
||||
"templateUrl": "templates/controls/datetime-picker.html"
|
||||
}
|
||||
],
|
||||
"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:
|
||||
The thinking is to divide whatever the current time span is by 5,
|
||||
and assign values accordingly to 5 statically-positioned ticks. So the tick x-position is a static percentage
|
||||
of the total width available, and the labels change dynamically. This is consistent
|
||||
with our current approach to the time axis of plots.
|
||||
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.
|
||||
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-init="
|
||||
notes = 'Temporarily using an array to populate ticks so I can see what I\'m doing';
|
||||
ticks = [
|
||||
'00:00',
|
||||
'00:30',
|
||||
'01:00',
|
||||
'01:30',
|
||||
'02:00'
|
||||
];
|
||||
"></div>
|
||||
<div class="l-time-controller" ng-controller="TimeRangeController">
|
||||
<div class="l-time-range-inputs-holder">
|
||||
Start: {{startOuterText}}
|
||||
<span ng-controller="ToggleController as t">
|
||||
<a class="ui-symbol" ng-click="t.toggle()">p</a>
|
||||
<mct-popup ng-if="t.isActive()">
|
||||
<div style="background: #222;"
|
||||
mct-click-elsewhere="t.setState(false)">
|
||||
<mct-control key="'datetime-picker'"
|
||||
ng-model="ngModel.outer"
|
||||
field="'start'"
|
||||
options="{ hours: true }">
|
||||
</mct-control>
|
||||
</div>
|
||||
</mct-popup>
|
||||
</span>
|
||||
|
||||
<div class="l-time-controller">
|
||||
<div class="l-time-range-inputs-holder">
|
||||
Start: <input type="date" />
|
||||
End: <input type="date" />
|
||||
</div>
|
||||
End: {{endOuterText}}
|
||||
<span ng-controller="ToggleController as t2">
|
||||
<a class="ui-symbol" ng-click="t2.toggle()">p</a>
|
||||
<mct-popup ng-if="t2.isActive()">
|
||||
<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 class="l-time-range-ticks">
|
||||
<div
|
||||
ng-repeat="tick in ticks"
|
||||
ng-style="{ left: $index * 25 + '%' }"
|
||||
class="tick tick-x"
|
||||
>
|
||||
<span class="l-time-range-tick-label">{{tick}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="l-time-range-slider-holder">
|
||||
<div class="l-time-range-slider">
|
||||
<div class="slider"
|
||||
mct-resize="spanWidth = bounds.width">
|
||||
<div class="slot range-holder">
|
||||
<div class="range"
|
||||
mct-drag-down="startMiddleDrag()"
|
||||
mct-drag="middleDrag(delta[0])"
|
||||
ng-style="{ left: startInnerPct, right: endInnerPct}">
|
||||
</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/ClickAwayController",
|
||||
"controllers/ContextMenuController",
|
||||
"controllers/DateTimePickerController",
|
||||
"controllers/GetterSetterController",
|
||||
"controllers/SelectorController",
|
||||
"controllers/SplitPaneController",
|
||||
"controllers/TimeRangeController",
|
||||
"controllers/ToggleController",
|
||||
"controllers/TreeNodeController",
|
||||
"controllers/ViewSwitcherController",
|
||||
"directives/MCTClickElsewhere",
|
||||
"directives/MCTContainer",
|
||||
"directives/MCTDrag",
|
||||
"directives/MCTPopup",
|
||||
"directives/MCTResize",
|
||||
"directives/MCTScroll",
|
||||
"services/UrlService",
|
||||
|
@ -36,11 +36,16 @@ define(
|
||||
*
|
||||
* Returns a function that, when invoked, will invoke `fn` after
|
||||
* `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
|
||||
* 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}
|
||||
* @memberof platform/core
|
||||
*/
|
||||
@ -56,12 +61,14 @@ define(
|
||||
* @memberof platform/core.Throttle#
|
||||
*/
|
||||
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
|
||||
// a new one.
|
||||
function clearActiveTimeout() {
|
||||
activeTimeout = undefined;
|
||||
function invoke() {
|
||||
// Clear the active timeout so a new one starts next time.
|
||||
promise = undefined;
|
||||
// Invoke the function with the latest supplied arguments.
|
||||
return fn.apply(null, args);
|
||||
}
|
||||
|
||||
// Defaults
|
||||
@ -69,14 +76,13 @@ define(
|
||||
apply = apply || false;
|
||||
|
||||
return function () {
|
||||
// Store arguments from this invocation
|
||||
args = Array.prototype.slice.apply(arguments, [0]);
|
||||
// Start a timeout if needed
|
||||
if (!activeTimeout) {
|
||||
activeTimeout = $timeout(fn, delay, apply);
|
||||
activeTimeout.then(clearActiveTimeout);
|
||||
}
|
||||
promise = promise || $timeout(invoke, delay, apply);
|
||||
// Return whichever timeout is active (to get
|
||||
// a promise for the results of fn)
|
||||
return activeTimeout;
|
||||
return promise;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@ -45,7 +45,9 @@ define(
|
||||
// Verify precondition: Not called at throttle-time
|
||||
expect(mockTimeout).not.toHaveBeenCalled();
|
||||
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 () {
|
||||
@ -59,10 +61,11 @@ define(
|
||||
it("schedules additional invocations after resolution", function () {
|
||||
var throttled = throttle(mockFn);
|
||||
throttled();
|
||||
mockPromise.then.mostRecentCall.args[0](); // Resolve timeout
|
||||
mockTimeout.mostRecentCall.args[0](); // Resolve timeout
|
||||
throttled();
|
||||
mockPromise.then.mostRecentCall.args[0]();
|
||||
mockTimeout.mostRecentCall.args[0]();
|
||||
throttled();
|
||||
mockTimeout.mostRecentCall.args[0]();
|
||||
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",
|
||||
"$q",
|
||||
"dialogService",
|
||||
"telemetrySubscriber",
|
||||
"telemetryFormatter"
|
||||
"telemetryHandler",
|
||||
"telemetryFormatter",
|
||||
"throttle"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
@ -38,12 +38,13 @@ define(
|
||||
* @constructor
|
||||
* @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,
|
||||
subscription,
|
||||
handle,
|
||||
names = {}, // Cache names by ID
|
||||
values = {}, // Cache values by ID
|
||||
elementProxiesById = {};
|
||||
elementProxiesById = {},
|
||||
maxDomainValue = Number.POSITIVE_INFINITY;
|
||||
|
||||
// Convert from element x/y/width/height to an
|
||||
// appropriate ng-style argument, to position elements.
|
||||
@ -81,25 +82,50 @@ define(
|
||||
return element.handles().map(generateDragHandle);
|
||||
}
|
||||
|
||||
// Update the displayed value for this object
|
||||
function updateValue(telemetryObject) {
|
||||
var id = telemetryObject && telemetryObject.getId(),
|
||||
// Update the value displayed in elements of this telemetry object
|
||||
function setDisplayedValue(telemetryObject, value, alarm) {
|
||||
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 &&
|
||||
telemetryObject.getCapability('limit'),
|
||||
datum = telemetryObject &&
|
||||
subscription.getDatum(telemetryObject),
|
||||
alarm = limit && datum && limit.evaluate(datum);
|
||||
datum = telemetryObject && handle.getDatum(
|
||||
telemetryObject,
|
||||
index
|
||||
);
|
||||
|
||||
if (id) {
|
||||
(elementProxiesById[id] || []).forEach(function (element) {
|
||||
names[id] = telemetryObject.getModel().name;
|
||||
values[id] = telemetryFormatter.formatRangeValue(
|
||||
subscription.getRangeValue(telemetryObject)
|
||||
);
|
||||
element.name = names[id];
|
||||
element.value = values[id];
|
||||
element.cssClass = alarm && alarm.cssClass;
|
||||
});
|
||||
setDisplayedValue(
|
||||
telemetryObject,
|
||||
telemetrySeries.getRangeValue(index),
|
||||
limit && datum && limit.evaluate(datum)
|
||||
);
|
||||
}
|
||||
|
||||
// Update the displayed value for this object
|
||||
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
|
||||
function updateValues() {
|
||||
if (subscription) {
|
||||
subscription.getTelemetryObjects().forEach(updateValue);
|
||||
if (handle) {
|
||||
handle.getTelemetryObjects().forEach(updateValue);
|
||||
}
|
||||
}
|
||||
|
||||
@ -178,22 +204,29 @@ define(
|
||||
|
||||
// Free up subscription to telemetry
|
||||
function releaseSubscription() {
|
||||
if (subscription) {
|
||||
subscription.unsubscribe();
|
||||
subscription = undefined;
|
||||
if (handle) {
|
||||
handle.unsubscribe();
|
||||
handle = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// Subscribe to telemetry updates for this domain object
|
||||
function subscribe(domainObject) {
|
||||
// Release existing subscription (if any)
|
||||
if (subscription) {
|
||||
subscription.unsubscribe();
|
||||
if (handle) {
|
||||
handle.unsubscribe();
|
||||
}
|
||||
|
||||
// Make a new subscription
|
||||
subscription = domainObject &&
|
||||
telemetrySubscriber.subscribe(domainObject, updateValues);
|
||||
handle = domainObject && telemetryHandler.handle(
|
||||
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
|
||||
@ -204,6 +237,17 @@ define(
|
||||
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
|
||||
function addElement(element) {
|
||||
// Ensure that configuration field is populated
|
||||
@ -278,6 +322,9 @@ define(
|
||||
|
||||
// Position panes where they are dropped
|
||||
$scope.$on("mctDrop", handleDrop);
|
||||
|
||||
// Respond to external bounds changes
|
||||
$scope.$on("telemetry:display:bounds", updateDisplayBounds);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -30,10 +30,10 @@ define(
|
||||
var mockScope,
|
||||
mockQ,
|
||||
mockDialogService,
|
||||
mockSubscriber,
|
||||
mockHandler,
|
||||
mockFormatter,
|
||||
mockDomainObject,
|
||||
mockSubscription,
|
||||
mockHandle,
|
||||
mockEvent,
|
||||
testGrid,
|
||||
testModel,
|
||||
@ -78,9 +78,9 @@ define(
|
||||
'$scope',
|
||||
[ "$on", "$watch", "commit" ]
|
||||
);
|
||||
mockSubscriber = jasmine.createSpyObj(
|
||||
'telemetrySubscriber',
|
||||
[ 'subscribe' ]
|
||||
mockHandler = jasmine.createSpyObj(
|
||||
'telemetryHandler',
|
||||
[ 'handle' ]
|
||||
);
|
||||
mockQ = jasmine.createSpyObj('$q', ['when']);
|
||||
mockDialogService = jasmine.createSpyObj(
|
||||
@ -95,9 +95,16 @@ define(
|
||||
'domainObject',
|
||||
[ 'getId', 'getModel', 'getCapability' ]
|
||||
);
|
||||
mockSubscription = jasmine.createSpyObj(
|
||||
mockHandle = jasmine.createSpyObj(
|
||||
'subscription',
|
||||
[ 'unsubscribe', 'getTelemetryObjects', 'getRangeValue', 'getDatum' ]
|
||||
[
|
||||
'unsubscribe',
|
||||
'getDomainValue',
|
||||
'getTelemetryObjects',
|
||||
'getRangeValue',
|
||||
'getDatum',
|
||||
'request'
|
||||
]
|
||||
);
|
||||
mockEvent = jasmine.createSpyObj(
|
||||
'event',
|
||||
@ -116,13 +123,14 @@ define(
|
||||
{ type: "fixed.telemetry", id: 'c', x: 1, y: 1 }
|
||||
]};
|
||||
|
||||
mockSubscriber.subscribe.andReturn(mockSubscription);
|
||||
mockSubscription.getTelemetryObjects.andReturn(
|
||||
mockHandler.handle.andReturn(mockHandle);
|
||||
mockHandle.getTelemetryObjects.andReturn(
|
||||
testModel.composition.map(makeMockDomainObject)
|
||||
);
|
||||
mockSubscription.getRangeValue.andCallFake(function (o) {
|
||||
mockHandle.getRangeValue.andCallFake(function (o) {
|
||||
return testValues[o.getId()];
|
||||
});
|
||||
mockHandle.getDomainValue.andReturn(12321);
|
||||
mockFormatter.formatRangeValue.andCallFake(function (v) {
|
||||
return "Formatted " + v;
|
||||
});
|
||||
@ -137,7 +145,7 @@ define(
|
||||
mockScope,
|
||||
mockQ,
|
||||
mockDialogService,
|
||||
mockSubscriber,
|
||||
mockHandler,
|
||||
mockFormatter
|
||||
);
|
||||
});
|
||||
@ -145,7 +153,7 @@ define(
|
||||
it("subscribes when a domain object is available", function () {
|
||||
mockScope.domainObject = mockDomainObject;
|
||||
findWatch("domainObject")(mockDomainObject);
|
||||
expect(mockSubscriber.subscribe).toHaveBeenCalledWith(
|
||||
expect(mockHandler.handle).toHaveBeenCalledWith(
|
||||
mockDomainObject,
|
||||
jasmine.any(Function)
|
||||
);
|
||||
@ -156,13 +164,13 @@ define(
|
||||
|
||||
// First pass - should simply should subscribe
|
||||
findWatch("domainObject")(mockDomainObject);
|
||||
expect(mockSubscription.unsubscribe).not.toHaveBeenCalled();
|
||||
expect(mockSubscriber.subscribe.calls.length).toEqual(1);
|
||||
expect(mockHandle.unsubscribe).not.toHaveBeenCalled();
|
||||
expect(mockHandler.handle.calls.length).toEqual(1);
|
||||
|
||||
// Object changes - should unsubscribe then resubscribe
|
||||
findWatch("domainObject")(mockDomainObject);
|
||||
expect(mockSubscription.unsubscribe).toHaveBeenCalled();
|
||||
expect(mockSubscriber.subscribe.calls.length).toEqual(2);
|
||||
expect(mockHandle.unsubscribe).toHaveBeenCalled();
|
||||
expect(mockHandler.handle.calls.length).toEqual(2);
|
||||
});
|
||||
|
||||
it("exposes visible elements based on configuration", function () {
|
||||
@ -255,7 +263,7 @@ define(
|
||||
findWatch("model.composition")(mockScope.model.composition);
|
||||
|
||||
// Invoke the subscription callback
|
||||
mockSubscriber.subscribe.mostRecentCall.args[1]();
|
||||
mockHandler.handle.mostRecentCall.args[1]();
|
||||
|
||||
// Get elements that controller is now exposing
|
||||
elements = controller.getElements();
|
||||
@ -333,11 +341,11 @@ define(
|
||||
// Make an object available
|
||||
findWatch('domainObject')(mockDomainObject);
|
||||
// Also verify precondition
|
||||
expect(mockSubscription.unsubscribe).not.toHaveBeenCalled();
|
||||
expect(mockHandle.unsubscribe).not.toHaveBeenCalled();
|
||||
// Destroy the scope
|
||||
findOn('$destroy')();
|
||||
// Should have unsubscribed
|
||||
expect(mockSubscription.unsubscribe).toHaveBeenCalled();
|
||||
expect(mockHandle.unsubscribe).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("exposes its grid size", function () {
|
||||
|
@ -65,6 +65,8 @@ define(
|
||||
subPlotFactory = new SubPlotFactory(telemetryFormatter),
|
||||
cachedObjects = [],
|
||||
updater,
|
||||
lastBounds,
|
||||
throttledRequery,
|
||||
handle;
|
||||
|
||||
// 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
|
||||
// new subscription.) This will clear the plot.
|
||||
function recreateUpdater() {
|
||||
@ -107,10 +120,15 @@ define(
|
||||
handle,
|
||||
($scope.axes[1].active || {}).key
|
||||
);
|
||||
// Keep any externally-provided bounds
|
||||
if (lastBounds) {
|
||||
setBasePanZoom(lastBounds);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle new telemetry data in this plot
|
||||
function updateValues() {
|
||||
self.pending = false;
|
||||
if (handle) {
|
||||
setupModes(handle.getTelemetryObjects());
|
||||
}
|
||||
@ -126,6 +144,7 @@ define(
|
||||
|
||||
// Display new historical data as it becomes available
|
||||
function addHistoricalData(domainObject, series) {
|
||||
self.pending = false;
|
||||
updater.addHistorical(domainObject, series);
|
||||
self.modeOptions.getModeHandler().plotTelemetry(updater);
|
||||
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.updateValues = updateValues;
|
||||
|
||||
@ -174,12 +206,19 @@ define(
|
||||
.forEach(updateSubplot);
|
||||
});
|
||||
|
||||
self.pending = true;
|
||||
|
||||
// Subscribe to telemetry when a domain object becomes available
|
||||
$scope.$watch('domainObject', subscribe);
|
||||
|
||||
// Respond to external bounds changes
|
||||
$scope.$on("telemetry:display:bounds", changeDisplayBounds);
|
||||
|
||||
// Unsubscribe when the plot is destroyed
|
||||
$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 () {
|
||||
// Placeholder; this should reflect request state
|
||||
// when requesting historical telemetry
|
||||
return false;
|
||||
return this.pending;
|
||||
};
|
||||
|
||||
return PlotController;
|
||||
|
@ -143,8 +143,7 @@ define(
|
||||
PlotPanZoomStackGroup.prototype.getDepth = function () {
|
||||
// All stacks are kept in sync, so look up depth
|
||||
// from the first one.
|
||||
return this.stacks.length > 0 ?
|
||||
this.stacks[0].getDepth() : 0;
|
||||
return this.stacks.length > 0 ? this.stacks[0].getDepth() : 0;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -141,10 +141,10 @@ define(
|
||||
PlotUpdater.prototype.initializeDomainOffset = function (values) {
|
||||
this.domainOffset =
|
||||
((this.domainOffset === undefined) && (values.length > 0)) ?
|
||||
(values.reduce(function (a, b) {
|
||||
return (a || 0) + (b || 0);
|
||||
}, 0) / values.length) :
|
||||
this.domainOffset;
|
||||
(values.reduce(function (a, b) {
|
||||
return (a || 0) + (b || 0);
|
||||
}, 0) / values.length) :
|
||||
this.domainOffset;
|
||||
};
|
||||
|
||||
// Expand range slightly so points near edges are visible
|
||||
@ -159,7 +159,10 @@ define(
|
||||
|
||||
// Update dimensions and origin based on extrema of plots
|
||||
PlotUpdater.prototype.updateBounds = function () {
|
||||
var bufferArray = this.bufferArray;
|
||||
var bufferArray = this.bufferArray,
|
||||
priorDomainOrigin = this.origin[0],
|
||||
priorDomainDimensions = this.dimensions[0];
|
||||
|
||||
if (bufferArray.length > 0) {
|
||||
this.domainExtrema = bufferArray.map(function (lineBuffer) {
|
||||
return lineBuffer.getDomainExtrema();
|
||||
@ -178,6 +181,18 @@ define(
|
||||
// Enforce some minimum visible area
|
||||
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
|
||||
if (this.fixedDuration !== undefined) {
|
||||
this.origin[0] = this.origin[0] + this.dimensions[0] -
|
||||
@ -281,6 +296,21 @@ define(
|
||||
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.
|
||||
*/
|
||||
|
@ -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 () {
|
||||
mockScope = jasmine.createSpyObj(
|
||||
"$scope",
|
||||
[ "$watch", "$on" ]
|
||||
[ "$watch", "$on", "$emit" ]
|
||||
);
|
||||
mockFormatter = jasmine.createSpyObj(
|
||||
"formatter",
|
||||
@ -87,6 +95,7 @@ define(
|
||||
mockHandle.getMetadata.andReturn([{}]);
|
||||
mockHandle.getDomainValue.andReturn(123);
|
||||
mockHandle.getRangeValue.andReturn(42);
|
||||
mockScope.domainObject = mockDomainObject;
|
||||
|
||||
controller = new PlotController(
|
||||
mockScope,
|
||||
@ -212,7 +221,12 @@ define(
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
@ -233,10 +247,20 @@ define(
|
||||
// Also verify precondition
|
||||
expect(mockHandle.unsubscribe).not.toHaveBeenCalled();
|
||||
// Destroy the scope
|
||||
mockScope.$on.mostRecentCall.args[1]();
|
||||
fireEvent("$destroy");
|
||||
// Should have unsubscribed
|
||||
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,
|
||||
limit = domainObject.getCapability('limit'),
|
||||
value = datum[range],
|
||||
alarm = limit.evaluate(datum, range);
|
||||
alarm = limit && limit.evaluate(datum, range);
|
||||
|
||||
return {
|
||||
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
|
||||
// as appropriate for current representation key and
|
||||
// domain object.
|
||||
@ -152,10 +160,8 @@ define(
|
||||
// via the "inclusion" field
|
||||
$scope.inclusion = representation && getPath(representation);
|
||||
|
||||
// Any existing gestures are no longer valid; release them.
|
||||
activeRepresenters.forEach(function (activeRepresenter) {
|
||||
activeRepresenter.destroy();
|
||||
});
|
||||
// Any existing representers are no longer valid; release them.
|
||||
destroyRepresenters();
|
||||
|
||||
// Log if a key was given, but no matching representation
|
||||
// was found.
|
||||
@ -209,6 +215,10 @@ define(
|
||||
// model's "modified" field, by the mutation capability.
|
||||
$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
|
||||
// digest iteration just to populate the scope. Failure to
|
||||
// do this can result in unstable digest cycles, which
|
||||
|
@ -106,7 +106,7 @@ define(
|
||||
mockSce.trustAsResourceUrl.andCallFake(function (url) {
|
||||
return url;
|
||||
});
|
||||
mockScope = jasmine.createSpyObj("scope", [ "$watch" ]);
|
||||
mockScope = jasmine.createSpyObj("scope", [ "$watch", "$on" ]);
|
||||
mockElement = jasmine.createSpyObj("element", JQLITE_FUNCTIONS);
|
||||
mockDomainObject = jasmine.createSpyObj("domainObject", DOMAIN_OBJECT_METHODS);
|
||||
|
||||
|
@ -31,6 +31,30 @@ define(
|
||||
function () {
|
||||
"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.
|
||||
* @param {TelemetryRequest[]} requests and array of
|
||||
|
@ -79,8 +79,7 @@ define(
|
||||
|
||||
/**
|
||||
* Change the request duration.
|
||||
* @param {object|number} request the duration of historical
|
||||
* data to look at; or, the request to issue
|
||||
* @param {TelemetryRequest} request the request to issue
|
||||
* @param {Function} [callback] a callback that will be
|
||||
* invoked as new data becomes available, with the
|
||||
* domain object for which new data is available.
|
||||
@ -107,6 +106,26 @@ define(
|
||||
.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;
|
||||
}
|
||||
|
||||
|
@ -123,25 +123,6 @@ define(
|
||||
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
|
||||
// domain object. This will notify listeners.
|
||||
function update(domainObject, series) {
|
||||
@ -160,7 +141,7 @@ define(
|
||||
pool.put(domainObject.getId(), {
|
||||
domain: series.getDomainValue(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) {
|
||||
self.telemetryObjects = objects;
|
||||
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
|
||||
// telemetry objects are available, or these objects
|
||||
// will have changed.
|
||||
@ -241,6 +227,34 @@ define(
|
||||
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 () {
|
||||
var $q = this.$q;
|
||||
return this.unsubscribePromise.then(function (unsubscribes) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user