Compare commits
42 Commits
vue-table-
...
context-me
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
485e948abe | ||
|
|
64b9d4c24a | ||
|
|
88bcb6078e | ||
|
|
5f9f3cd8e8 | ||
|
|
814b404614 | ||
|
|
ba2bb2180b | ||
|
|
72cdb352f0 | ||
|
|
cedf942c0c | ||
|
|
27506a3757 | ||
|
|
acc4e03c88 | ||
|
|
9a6090cd02 | ||
|
|
f40c9fa6f9 | ||
|
|
e7cdb334de | ||
|
|
afca6cd2e9 | ||
|
|
3c324cbea0 | ||
|
|
56b9708ab7 | ||
|
|
e6e5b6a64a | ||
|
|
58da916d18 | ||
|
|
a0327b56aa | ||
|
|
57d60128a2 | ||
|
|
987740c649 | ||
|
|
944505a5f1 | ||
|
|
c5187d8509 | ||
|
|
c7b73bdc3f | ||
|
|
7483e886f1 | ||
|
|
ef92da4d9d | ||
|
|
80b02672a6 | ||
|
|
fbf1c68c7a | ||
|
|
5eac6e646b | ||
|
|
82e5bf2325 | ||
|
|
40b7117987 | ||
|
|
07ca60e13a | ||
|
|
08cd6b1d99 | ||
|
|
78ae7b334c | ||
|
|
3a28caac06 | ||
|
|
c883bbe6c2 | ||
|
|
eaa971cb56 | ||
|
|
0301d88033 | ||
|
|
e2e0cf17db | ||
|
|
01a39f4fb7 | ||
|
|
1bb1330cba | ||
|
|
78c731dbf7 |
25
app.js
25
app.js
@@ -16,7 +16,7 @@ const request = require('request');
|
|||||||
|
|
||||||
// Defaults
|
// Defaults
|
||||||
options.port = options.port || options.p || 8080;
|
options.port = options.port || options.p || 8080;
|
||||||
options.host = options.host || options.h || 'localhost'
|
options.host = options.host || options.h || 'localhost';
|
||||||
options.directory = options.directory || options.D || '.';
|
options.directory = options.directory || options.D || '.';
|
||||||
|
|
||||||
// Show command line options
|
// Show command line options
|
||||||
@@ -42,15 +42,30 @@ app.use('/proxyUrl', function proxyRequest(req, res, next) {
|
|||||||
|
|
||||||
const webpack = require('webpack');
|
const webpack = require('webpack');
|
||||||
const webpackConfig = require('./webpack.config.js');
|
const webpackConfig = require('./webpack.config.js');
|
||||||
|
webpackConfig.plugins.push(new webpack.HotModuleReplacementPlugin());
|
||||||
|
webpackConfig.plugins.push(function() { this.plugin('watch-run', function(watching, callback) { console.log('Begin compile at ' + new Date()); callback(); }) });
|
||||||
|
|
||||||
|
webpackConfig.entry.openmct = [
|
||||||
|
'webpack-hot-middleware/client',
|
||||||
|
webpackConfig.entry.openmct
|
||||||
|
];
|
||||||
|
|
||||||
const compiler = webpack(webpackConfig);
|
const compiler = webpack(webpackConfig);
|
||||||
const webpackDevRoute = require('webpack-dev-middleware')(
|
|
||||||
compiler, {
|
app.use(require('webpack-dev-middleware')(
|
||||||
|
compiler,
|
||||||
|
{
|
||||||
publicPath: '/dist',
|
publicPath: '/dist',
|
||||||
logLevel: 'warn'
|
logLevel: 'warn'
|
||||||
}
|
}
|
||||||
);
|
));
|
||||||
|
|
||||||
app.use(webpackDevRoute);
|
app.use(require('webpack-hot-middleware')(
|
||||||
|
compiler,
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
// Expose index.html for development users.
|
// Expose index.html for development users.
|
||||||
app.get('/', function (req, res) {
|
app.get('/', function (req, res) {
|
||||||
|
|||||||
@@ -18,10 +18,4 @@
|
|||||||
licenses. See the Open Source Licenses file (LICENSES.md) included with
|
licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
this source code distribution or the Licensing information page available
|
this source code distribution or the Licensing information page available
|
||||||
at runtime from the About dialog for additional information.
|
at runtime from the About dialog for additional information.
|
||||||
-->
|
-->
|
||||||
<div class='items-holder grid abs'>
|
|
||||||
<mct-representation key="'grid-item'"
|
|
||||||
ng-repeat="childObject in composition"
|
|
||||||
mct-object="childObject">
|
|
||||||
</mct-representation>
|
|
||||||
</div>
|
|
||||||
21
copyright-notice.js
Normal file
21
copyright-notice.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
@@ -27,8 +27,14 @@ define([
|
|||||||
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
var RED = 0.9,
|
var RED = {
|
||||||
YELLOW = 0.5,
|
sin: 0.9,
|
||||||
|
cos: 0.9
|
||||||
|
},
|
||||||
|
YELLOW = {
|
||||||
|
sin: 0.5,
|
||||||
|
cos: 0.5
|
||||||
|
},
|
||||||
LIMITS = {
|
LIMITS = {
|
||||||
rh: {
|
rh: {
|
||||||
cssClass: "s-limit-upr s-limit-red",
|
cssClass: "s-limit-upr s-limit-red",
|
||||||
@@ -67,17 +73,18 @@ define([
|
|||||||
SinewaveLimitProvider.prototype.getLimitEvaluator = function (domainObject) {
|
SinewaveLimitProvider.prototype.getLimitEvaluator = function (domainObject) {
|
||||||
return {
|
return {
|
||||||
evaluate: function (datum, valueMetadata) {
|
evaluate: function (datum, valueMetadata) {
|
||||||
var range = valueMetadata ? valueMetadata.key : 'sin'
|
var range = valueMetadata && valueMetadata.key;
|
||||||
if (datum[range] > RED) {
|
|
||||||
|
if (datum[range] > RED[range]) {
|
||||||
return LIMITS.rh;
|
return LIMITS.rh;
|
||||||
}
|
}
|
||||||
if (datum[range] < -RED) {
|
if (datum[range] < -RED[range]) {
|
||||||
return LIMITS.rl;
|
return LIMITS.rl;
|
||||||
}
|
}
|
||||||
if (datum[range] > YELLOW) {
|
if (datum[range] > YELLOW[range]) {
|
||||||
return LIMITS.yh;
|
return LIMITS.yh;
|
||||||
}
|
}
|
||||||
if (datum[range] < -YELLOW) {
|
if (datum[range] < -YELLOW[range]) {
|
||||||
return LIMITS.yl;
|
return LIMITS.yl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,12 +26,16 @@ define([
|
|||||||
"./src/NotificationLaunchController",
|
"./src/NotificationLaunchController",
|
||||||
"./src/DialogLaunchIndicator",
|
"./src/DialogLaunchIndicator",
|
||||||
"./src/NotificationLaunchIndicator",
|
"./src/NotificationLaunchIndicator",
|
||||||
|
"./res/dialog-launch.html",
|
||||||
|
"./res/notification-launch.html",
|
||||||
'legacyRegistry'
|
'legacyRegistry'
|
||||||
], function (
|
], function (
|
||||||
DialogLaunchController,
|
DialogLaunchController,
|
||||||
NotificationLaunchController,
|
NotificationLaunchController,
|
||||||
DialogLaunchIndicator,
|
DialogLaunchIndicator,
|
||||||
NotificationLaunchIndicator,
|
NotificationLaunchIndicator,
|
||||||
|
DialogLaunch,
|
||||||
|
NotificationLaunch,
|
||||||
legacyRegistry
|
legacyRegistry
|
||||||
) {
|
) {
|
||||||
"use strict";
|
"use strict";
|
||||||
@@ -41,11 +45,11 @@ define([
|
|||||||
"templates": [
|
"templates": [
|
||||||
{
|
{
|
||||||
"key": "dialogLaunchTemplate",
|
"key": "dialogLaunchTemplate",
|
||||||
"templateUrl": "dialog-launch.html"
|
"template": DialogLaunch
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "notificationLaunchTemplate",
|
"key": "notificationLaunchTemplate",
|
||||||
"templateUrl": "notification-launch.html"
|
"template": NotificationLaunch
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"controllers": [
|
"controllers": [
|
||||||
|
|||||||
@@ -51,76 +51,26 @@ define(
|
|||||||
return actionTexts[Math.floor(Math.random()*3)];
|
return actionTexts[Math.floor(Math.random()*3)];
|
||||||
}
|
}
|
||||||
|
|
||||||
function getExampleActions() {
|
|
||||||
var actions = [
|
|
||||||
{
|
|
||||||
label: "Try Again",
|
|
||||||
callback: function () {
|
|
||||||
$log.debug("Try Again pressed");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Remove",
|
|
||||||
callback: function () {
|
|
||||||
$log.debug("Remove pressed");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Cancel",
|
|
||||||
callback: function () {
|
|
||||||
$log.debug("Cancel pressed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
// Randomly remove some actions off the top; leave at least one
|
|
||||||
actions.splice(0,Math.floor(Math.random() * actions.length));
|
|
||||||
|
|
||||||
return actions;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getExampleSeverity() {
|
|
||||||
var severities = [
|
|
||||||
"info",
|
|
||||||
"alert",
|
|
||||||
"error"
|
|
||||||
];
|
|
||||||
return severities[Math.floor(Math.random() * severities.length)];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Launch a new notification with a severity level of 'Error'.
|
* Launch a new notification with a severity level of 'Error'.
|
||||||
*/
|
*/
|
||||||
$scope.newError = function(){
|
$scope.newError = function () {
|
||||||
|
|
||||||
notificationService.notify({
|
notificationService.notify({
|
||||||
title: "Example error notification " + messageCounter++,
|
title: "Example error notification " + messageCounter++,
|
||||||
hint: "An error has occurred",
|
hint: "An error has occurred",
|
||||||
severity: "error",
|
severity: "error"
|
||||||
primaryOption: {
|
});
|
||||||
label: 'Retry',
|
|
||||||
callback: function() {
|
|
||||||
$log.info('Retry clicked');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
options: getExampleActions()});
|
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* Launch a new notification with a severity of 'Alert'.
|
* Launch a new notification with a severity of 'Alert'.
|
||||||
*/
|
*/
|
||||||
$scope.newAlert = function(){
|
$scope.newAlert = function () {
|
||||||
|
|
||||||
notificationService.notify({
|
notificationService.notify({
|
||||||
title: "Alert notification " + (messageCounter++),
|
title: "Alert notification " + (messageCounter++),
|
||||||
hint: "This is an alert message",
|
hint: "This is an alert message",
|
||||||
severity: "alert",
|
severity: "alert",
|
||||||
primaryOption: {
|
autoDismiss: true
|
||||||
label: 'Retry',
|
});
|
||||||
callback: function() {
|
|
||||||
$log.info('Retry clicked');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
options: getExampleActions()});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -128,39 +78,38 @@ define(
|
|||||||
* Launch a new notification with a progress bar that is updated
|
* Launch a new notification with a progress bar that is updated
|
||||||
* periodically, tracking an ongoing process.
|
* periodically, tracking an ongoing process.
|
||||||
*/
|
*/
|
||||||
$scope.newProgress = function(){
|
$scope.newProgress = function () {
|
||||||
|
|
||||||
var notificationModel = {
|
var notificationModel = {
|
||||||
title: "Progress notification example",
|
title: "Progress notification example",
|
||||||
severity: "info",
|
severity: "info",
|
||||||
progress: 0,
|
progress: 0,
|
||||||
actionText: getExampleActionText(),
|
actionText: getExampleActionText()
|
||||||
unknownProgress: false
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simulate an ongoing process and update the progress bar.
|
* Simulate an ongoing process and update the progress bar.
|
||||||
* @param notification
|
* @param notification
|
||||||
*/
|
*/
|
||||||
function incrementProgress(notificationModel) {
|
function incrementProgress() {
|
||||||
notificationModel.progress = Math.min(100, Math.floor(notificationModel.progress + Math.random() * 30));
|
notificationModel.progress = Math.min(100, Math.floor(notificationModel.progress + Math.random() * 30));
|
||||||
notificationModel.progressText = ["Estimated time" +
|
notificationModel.progressText = ["Estimated time" +
|
||||||
" remaining:" +
|
" remaining:" +
|
||||||
" about ", 60 - Math.floor((notificationModel.progress / 100) * 60), " seconds"].join(" ");
|
" about ", 60 - Math.floor((notificationModel.progress / 100) * 60), " seconds"].join(" ");
|
||||||
if (notificationModel.progress < 100) {
|
if (notificationModel.progress < 100) {
|
||||||
$timeout(function(){incrementProgress(notificationModel);}, 1000);
|
$timeout(function () {
|
||||||
|
incrementProgress(notificationModel);
|
||||||
|
}, 1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
notificationService.notify(notificationModel);
|
notificationService.notify(notificationModel);
|
||||||
incrementProgress(notificationModel);
|
incrementProgress();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Launch a new notification with severity level of INFO.
|
* Launch a new notification with severity level of INFO.
|
||||||
*/
|
*/
|
||||||
$scope.newInfo = function(){
|
$scope.newInfo = function () {
|
||||||
|
|
||||||
notificationService.info({
|
notificationService.info({
|
||||||
title: "Example Info notification " + messageCounter++
|
title: "Example Info notification " + messageCounter++
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -34,9 +34,6 @@
|
|||||||
<link rel="shortcut icon" href="dist/favicons/favicon.ico">
|
<link rel="shortcut icon" href="dist/favicons/favicon.ico">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="l-splash-holder s-splash-holder">
|
|
||||||
<div class="l-splash s-splash"></div>
|
|
||||||
</div>
|
|
||||||
</body>
|
</body>
|
||||||
<script>
|
<script>
|
||||||
var THIRTY_MINUTES = 30 * 60 * 1000;
|
var THIRTY_MINUTES = 30 * 60 * 1000;
|
||||||
@@ -48,7 +45,6 @@
|
|||||||
);
|
);
|
||||||
openmct.install(openmct.plugins.MyItems());
|
openmct.install(openmct.plugins.MyItems());
|
||||||
openmct.install(openmct.plugins.LocalStorage());
|
openmct.install(openmct.plugins.LocalStorage());
|
||||||
openmct.install(openmct.plugins.Espresso());
|
|
||||||
openmct.install(openmct.plugins.Generator());
|
openmct.install(openmct.plugins.Generator());
|
||||||
openmct.install(openmct.plugins.ExampleImagery());
|
openmct.install(openmct.plugins.ExampleImagery());
|
||||||
openmct.install(openmct.plugins.UTCTimeSystem());
|
openmct.install(openmct.plugins.UTCTimeSystem());
|
||||||
@@ -79,6 +75,7 @@
|
|||||||
}));
|
}));
|
||||||
openmct.install(openmct.plugins.SummaryWidget());
|
openmct.install(openmct.plugins.SummaryWidget());
|
||||||
openmct.install(openmct.plugins.Notebook());
|
openmct.install(openmct.plugins.Notebook());
|
||||||
|
openmct.install(openmct.plugins.FolderView());
|
||||||
openmct.time.clock('local', {start: -THIRTY_MINUTES, end: 0});
|
openmct.time.clock('local', {start: -THIRTY_MINUTES, end: 0});
|
||||||
openmct.time.timeSystem('utc');
|
openmct.time.timeSystem('utc');
|
||||||
openmct.start();
|
openmct.start();
|
||||||
|
|||||||
15
openmct.js
15
openmct.js
@@ -29,22 +29,9 @@ if (document.currentScript) {
|
|||||||
__webpack_public_path__ = src.replace(matcher, '') + '/';
|
__webpack_public_path__ = src.replace(matcher, '') + '/';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const Main = require('./platform/framework/src/Main');
|
|
||||||
const defaultRegistry = require('./src/defaultRegistry');
|
|
||||||
const MCT = require('./src/MCT');
|
const MCT = require('./src/MCT');
|
||||||
const buildInfo = require('./src/plugins/buildInfo/plugin');
|
|
||||||
|
|
||||||
var openmct = new MCT();
|
var openmct = new MCT();
|
||||||
|
|
||||||
openmct.legacyRegistry = defaultRegistry;
|
|
||||||
openmct.install(openmct.plugins.Plot());
|
|
||||||
|
|
||||||
if (typeof BUILD_CONSTANTS !== 'undefined') {
|
|
||||||
openmct.install(buildInfo(BUILD_CONSTANTS));
|
|
||||||
}
|
|
||||||
|
|
||||||
openmct.on('start', function () {
|
|
||||||
return new Main().run(defaultRegistry);
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = openmct;
|
module.exports = openmct;
|
||||||
|
|||||||
@@ -43,6 +43,7 @@
|
|||||||
"karma-html-reporter": "^0.2.7",
|
"karma-html-reporter": "^0.2.7",
|
||||||
"karma-jasmine": "^1.1.2",
|
"karma-jasmine": "^1.1.2",
|
||||||
"karma-webpack": "^3.0.0",
|
"karma-webpack": "^3.0.0",
|
||||||
|
"location-bar": "^3.0.1",
|
||||||
"lodash": "^3.10.1",
|
"lodash": "^3.10.1",
|
||||||
"markdown-toc": "^0.11.7",
|
"markdown-toc": "^0.11.7",
|
||||||
"marked": "^0.3.5",
|
"marked": "^0.3.5",
|
||||||
@@ -67,6 +68,7 @@
|
|||||||
"webpack": "^4.16.2",
|
"webpack": "^4.16.2",
|
||||||
"webpack-cli": "^3.1.0",
|
"webpack-cli": "^3.1.0",
|
||||||
"webpack-dev-middleware": "^3.1.3",
|
"webpack-dev-middleware": "^3.1.3",
|
||||||
|
"webpack-hot-middleware": "^2.22.3",
|
||||||
"zepto": "^1.2.0"
|
"zepto": "^1.2.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -35,12 +35,10 @@ define([
|
|||||||
"./src/windowing/WindowTitler",
|
"./src/windowing/WindowTitler",
|
||||||
"./res/templates/browse.html",
|
"./res/templates/browse.html",
|
||||||
"./res/templates/browse-object.html",
|
"./res/templates/browse-object.html",
|
||||||
"./res/templates/items/grid-item.html",
|
|
||||||
"./res/templates/browse/object-header.html",
|
"./res/templates/browse/object-header.html",
|
||||||
"./res/templates/browse/object-header-frame.html",
|
"./res/templates/browse/object-header-frame.html",
|
||||||
"./res/templates/menu-arrow.html",
|
"./res/templates/menu-arrow.html",
|
||||||
"./res/templates/back-arrow.html",
|
"./res/templates/back-arrow.html",
|
||||||
"./res/templates/items/items.html",
|
|
||||||
"./res/templates/browse/object-properties.html",
|
"./res/templates/browse/object-properties.html",
|
||||||
"./res/templates/browse/inspector-region.html",
|
"./res/templates/browse/inspector-region.html",
|
||||||
'legacyRegistry'
|
'legacyRegistry'
|
||||||
@@ -59,12 +57,10 @@ define([
|
|||||||
WindowTitler,
|
WindowTitler,
|
||||||
browseTemplate,
|
browseTemplate,
|
||||||
browseObjectTemplate,
|
browseObjectTemplate,
|
||||||
gridItemTemplate,
|
|
||||||
objectHeaderTemplate,
|
objectHeaderTemplate,
|
||||||
objectHeaderFrameTemplate,
|
objectHeaderFrameTemplate,
|
||||||
menuArrowTemplate,
|
menuArrowTemplate,
|
||||||
backArrowTemplate,
|
backArrowTemplate,
|
||||||
itemsTemplate,
|
|
||||||
objectPropertiesTemplate,
|
objectPropertiesTemplate,
|
||||||
inspectorRegionTemplate,
|
inspectorRegionTemplate,
|
||||||
legacyRegistry
|
legacyRegistry
|
||||||
@@ -73,15 +69,6 @@ define([
|
|||||||
legacyRegistry.register("platform/commonUI/browse", {
|
legacyRegistry.register("platform/commonUI/browse", {
|
||||||
"extensions": {
|
"extensions": {
|
||||||
"routes": [
|
"routes": [
|
||||||
{
|
|
||||||
"when": "/browse/:ids*?",
|
|
||||||
"template": browseTemplate,
|
|
||||||
"reloadOnSearch": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"when": "",
|
|
||||||
"redirectTo": "/browse/"
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
"constants": [
|
"constants": [
|
||||||
{
|
{
|
||||||
@@ -165,19 +152,6 @@ define([
|
|||||||
"view"
|
"view"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"key": "grid-item",
|
|
||||||
"template": gridItemTemplate,
|
|
||||||
"uses": [
|
|
||||||
"type",
|
|
||||||
"action",
|
|
||||||
"location"
|
|
||||||
],
|
|
||||||
"gestures": [
|
|
||||||
"info",
|
|
||||||
"menu"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"key": "object-header",
|
"key": "object-header",
|
||||||
"template": objectHeaderTemplate,
|
"template": objectHeaderTemplate,
|
||||||
@@ -260,23 +234,6 @@ define([
|
|||||||
"priority": "default"
|
"priority": "default"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"views": [
|
|
||||||
{
|
|
||||||
"key": "items",
|
|
||||||
"name": "Grid",
|
|
||||||
"cssClass": "icon-thumbs-strip",
|
|
||||||
"description": "Grid of available items",
|
|
||||||
"template": itemsTemplate,
|
|
||||||
"uses": [
|
|
||||||
"composition"
|
|
||||||
],
|
|
||||||
"gestures": [
|
|
||||||
"drop"
|
|
||||||
],
|
|
||||||
"type": "folder",
|
|
||||||
"editable": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"runs": [
|
"runs": [
|
||||||
{
|
{
|
||||||
"implementation": WindowTitler,
|
"implementation": WindowTitler,
|
||||||
@@ -295,6 +252,20 @@ define([
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"templates": [
|
||||||
|
{
|
||||||
|
key: "browseRoot",
|
||||||
|
template: browseTemplate
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "browseObject",
|
||||||
|
template: browseObjectTemplate
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "inspectorRegion",
|
||||||
|
template: inspectorRegionTemplate
|
||||||
|
}
|
||||||
|
],
|
||||||
"licenses": [
|
"licenses": [
|
||||||
{
|
{
|
||||||
"name": "screenfull.js",
|
"name": "screenfull.js",
|
||||||
|
|||||||
@@ -66,5 +66,4 @@
|
|||||||
</mct-representation>
|
</mct-representation>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<mct-include key="'conductor'" class="abs holder flex-elem flex-fixed l-flex-row l-time-conductor-holder"></mct-include>
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
<!--
|
|
||||||
Open MCT, Copyright (c) 2014-2018, United States Government
|
|
||||||
as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
Administration. All rights reserved.
|
|
||||||
|
|
||||||
Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
"License"); you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
License for the specific language governing permissions and limitations
|
|
||||||
under the License.
|
|
||||||
|
|
||||||
Open MCT includes source code licensed under additional open source
|
|
||||||
licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
this source code distribution or the Licensing information page available
|
|
||||||
at runtime from the About dialog for additional information.
|
|
||||||
-->
|
|
||||||
<!-- For selected, add class 'selected' to outer div -->
|
|
||||||
<div class='item grid-item' ng-click='action.perform("navigate")'>
|
|
||||||
<div class='contents abs'>
|
|
||||||
<div class='top-bar bar abs'>
|
|
||||||
<span class='icon-people' title='Shared'></span>
|
|
||||||
<mct-representation class="desktop-hide" key="'info-button'" mct-object="domainObject"></mct-representation>
|
|
||||||
</div>
|
|
||||||
<div class='item-main abs lg'>
|
|
||||||
<span class="t-item-icon" ng-class="{ 'l-icon-link':location.isLink() }">
|
|
||||||
<span class="t-item-icon-glyph ng-binding {{type.getCssClass()}}"></span>
|
|
||||||
</span>
|
|
||||||
<div class='abs item-open icon-pointer-right'></div>
|
|
||||||
</div>
|
|
||||||
<div class='bottom-bar bar abs'>
|
|
||||||
<div class='title'>{{model.name}}</div>
|
|
||||||
<div class='details'>
|
|
||||||
<span>{{type.getName()}}</span>
|
|
||||||
<span ng-show="model.composition !== undefined">
|
|
||||||
- {{model.composition.length}} Item<span ng-show="model.composition.length > 1">s</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -47,6 +47,7 @@ define(
|
|||||||
urlService,
|
urlService,
|
||||||
defaultPath
|
defaultPath
|
||||||
) {
|
) {
|
||||||
|
window.browseScope = $scope;
|
||||||
var initialPath = ($route.current.params.ids || defaultPath).split("/"),
|
var initialPath = ($route.current.params.ids || defaultPath).split("/"),
|
||||||
currentIds;
|
currentIds;
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ define([], function () {
|
|||||||
|
|
||||||
function checkNavigation() {
|
function checkNavigation() {
|
||||||
var navigatedObject = navigationService.getNavigation();
|
var navigatedObject = navigationService.getNavigation();
|
||||||
if (navigatedObject.hasCapability('context')) {
|
if (navigatedObject && navigatedObject.hasCapability('context')) {
|
||||||
if (!navigatedObject.getCapability('editor').isEditContextRoot()) {
|
if (!navigatedObject.getCapability('editor').isEditContextRoot()) {
|
||||||
preventOrphanNavigation(navigatedObject);
|
preventOrphanNavigation(navigatedObject);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -319,6 +319,12 @@ define([
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"templates": [
|
||||||
|
{
|
||||||
|
key: "elementsPool",
|
||||||
|
template: elementsTemplate
|
||||||
|
}
|
||||||
|
],
|
||||||
"components": [
|
"components": [
|
||||||
{
|
{
|
||||||
"type": "decorator",
|
"type": "decorator",
|
||||||
@@ -399,7 +405,8 @@ define([
|
|||||||
"description": "Provides transactional editing capabilities",
|
"description": "Provides transactional editing capabilities",
|
||||||
"implementation": EditorCapability,
|
"implementation": EditorCapability,
|
||||||
"depends": [
|
"depends": [
|
||||||
"transactionService"
|
"transactionService",
|
||||||
|
"openmct"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -36,9 +36,11 @@ define(
|
|||||||
*/
|
*/
|
||||||
function EditorCapability(
|
function EditorCapability(
|
||||||
transactionService,
|
transactionService,
|
||||||
|
openmct,
|
||||||
domainObject
|
domainObject
|
||||||
) {
|
) {
|
||||||
this.transactionService = transactionService;
|
this.transactionService = transactionService;
|
||||||
|
this.openmct = openmct;
|
||||||
this.domainObject = domainObject;
|
this.domainObject = domainObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,27 +50,19 @@ define(
|
|||||||
* or finish() are called.
|
* or finish() are called.
|
||||||
*/
|
*/
|
||||||
EditorCapability.prototype.edit = function () {
|
EditorCapability.prototype.edit = function () {
|
||||||
this.transactionService.startTransaction();
|
console.warn('DEPRECATED: cannot edit via edit capability, use openmct.editor instead.');
|
||||||
|
this.openmct.editor.edit();
|
||||||
this.domainObject.getCapability('status').set('editing', true);
|
this.domainObject.getCapability('status').set('editing', true);
|
||||||
};
|
};
|
||||||
|
|
||||||
function isEditContextRoot(domainObject) {
|
|
||||||
return domainObject.getCapability('status').get('editing');
|
|
||||||
}
|
|
||||||
|
|
||||||
function isEditing(domainObject) {
|
|
||||||
return isEditContextRoot(domainObject) ||
|
|
||||||
domainObject.hasCapability('context') &&
|
|
||||||
isEditing(domainObject.getCapability('context').getParent());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines whether this object, or any of its ancestors are
|
* Determines whether this object, or any of its ancestors are
|
||||||
* currently being edited.
|
* currently being edited.
|
||||||
* @returns boolean
|
* @returns boolean
|
||||||
*/
|
*/
|
||||||
EditorCapability.prototype.inEditContext = function () {
|
EditorCapability.prototype.inEditContext = function () {
|
||||||
return isEditing(this.domainObject);
|
console.warn('DEPRECATION WARNING: isEditing checks must be done via openmct.editor.');
|
||||||
|
return this.openmct.editor.isEditing();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -77,7 +71,8 @@ define(
|
|||||||
* @returns {*}
|
* @returns {*}
|
||||||
*/
|
*/
|
||||||
EditorCapability.prototype.isEditContextRoot = function () {
|
EditorCapability.prototype.isEditContextRoot = function () {
|
||||||
return isEditContextRoot(this.domainObject);
|
console.warn('DEPRECATION WARNING: isEditing checks must be done via openmct.editor.');
|
||||||
|
return this.openmct.editor.isEditing();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -86,10 +81,7 @@ define(
|
|||||||
* @returns {*}
|
* @returns {*}
|
||||||
*/
|
*/
|
||||||
EditorCapability.prototype.save = function () {
|
EditorCapability.prototype.save = function () {
|
||||||
var transactionService = this.transactionService;
|
console.warn('DEPRECATED: cannot save via edit capability, use openmct.editor instead.');
|
||||||
return transactionService.commit().then(function () {
|
|
||||||
transactionService.startTransaction();
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
EditorCapability.prototype.invoke = EditorCapability.prototype.edit;
|
EditorCapability.prototype.invoke = EditorCapability.prototype.edit;
|
||||||
@@ -100,16 +92,7 @@ define(
|
|||||||
* @returns {*}
|
* @returns {*}
|
||||||
*/
|
*/
|
||||||
EditorCapability.prototype.finish = function () {
|
EditorCapability.prototype.finish = function () {
|
||||||
var domainObject = this.domainObject;
|
console.warn('DEPRECATED: cannot finish via edit capability, use openmct.editor instead.');
|
||||||
|
|
||||||
if (this.transactionService.isActive()) {
|
|
||||||
return this.transactionService.cancel().then(function () {
|
|
||||||
domainObject.getCapability("status").set("editing", false);
|
|
||||||
return domainObject;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return Promise.resolve(domainObject);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ define(
|
|||||||
|
|
||||||
// A view is editable unless explicitly flagged as not
|
// A view is editable unless explicitly flagged as not
|
||||||
(views || []).forEach(function (view) {
|
(views || []).forEach(function (view) {
|
||||||
if (view.editable === true ||
|
if (isEditable(view) ||
|
||||||
(view.key === 'plot' && type.getKey() === 'telemetry.panel') ||
|
(view.key === 'plot' && type.getKey() === 'telemetry.panel') ||
|
||||||
(view.key === 'table' && type.getKey() === 'table') ||
|
(view.key === 'table' && type.getKey() === 'table') ||
|
||||||
(view.key === 'rt-table' && type.getKey() === 'rttable')
|
(view.key === 'rt-table' && type.getKey() === 'rttable')
|
||||||
@@ -64,6 +64,14 @@ define(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function isEditable(view) {
|
||||||
|
if (typeof view.editable === Function) {
|
||||||
|
return view.editable(domainObject.useCapability('adapter'));
|
||||||
|
} else {
|
||||||
|
return view.editable === true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return count;
|
return count;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -55,16 +55,16 @@ define(
|
|||||||
navigatedObject = this.navigationService.getNavigation(),
|
navigatedObject = this.navigationService.getNavigation(),
|
||||||
actionMetadata = action.getMetadata ? action.getMetadata() : {};
|
actionMetadata = action.getMetadata ? action.getMetadata() : {};
|
||||||
|
|
||||||
if (navigatedObject.hasCapability("editor") && navigatedObject.getCapability("editor").isEditContextRoot()) {
|
// if (navigatedObject.hasCapability("editor") && navigatedObject.getCapability("editor").isEditContextRoot()) {
|
||||||
if (selectedObject.hasCapability("editor") && selectedObject.getCapability("editor").inEditContext()) {
|
if (selectedObject.hasCapability("editor") && selectedObject.getCapability("editor").inEditContext()) {
|
||||||
return this.editModeBlacklist.indexOf(actionMetadata.key) === -1;
|
return this.editModeBlacklist.indexOf(actionMetadata.key) === -1;
|
||||||
} else {
|
} else {
|
||||||
//Target is in the context menu
|
//Target is in the context menu
|
||||||
return this.nonEditContextBlacklist.indexOf(actionMetadata.key) === -1;
|
return this.nonEditContextBlacklist.indexOf(actionMetadata.key) === -1;
|
||||||
}
|
}
|
||||||
} else {
|
// } else {
|
||||||
return true;
|
// return true;
|
||||||
}
|
// }
|
||||||
};
|
};
|
||||||
|
|
||||||
return EditContextualActionPolicy;
|
return EditContextualActionPolicy;
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ define([
|
|||||||
};
|
};
|
||||||
|
|
||||||
DurationFormat.prototype.validate = function (text) {
|
DurationFormat.prototype.validate = function (text) {
|
||||||
return moment.utc(text, DATE_FORMATS).isValid();
|
return moment.utc(text, DATE_FORMATS, true).isValid();
|
||||||
};
|
};
|
||||||
|
|
||||||
return DurationFormat;
|
return DurationFormat;
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ define([
|
|||||||
var DATE_FORMAT = "YYYY-MM-DD HH:mm:ss.SSS",
|
var DATE_FORMAT = "YYYY-MM-DD HH:mm:ss.SSS",
|
||||||
DATE_FORMATS = [
|
DATE_FORMATS = [
|
||||||
DATE_FORMAT,
|
DATE_FORMAT,
|
||||||
|
DATE_FORMAT + "Z",
|
||||||
"YYYY-MM-DD HH:mm:ss",
|
"YYYY-MM-DD HH:mm:ss",
|
||||||
"YYYY-MM-DD HH:mm",
|
"YYYY-MM-DD HH:mm",
|
||||||
"YYYY-MM-DD"
|
"YYYY-MM-DD"
|
||||||
@@ -52,70 +53,14 @@ define([
|
|||||||
this.key = "utc";
|
this.key = "utc";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an appropriate time format based on the provided value and
|
|
||||||
* the threshold required.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
function getScaledFormat(d) {
|
|
||||||
var momentified = moment.utc(d);
|
|
||||||
/**
|
|
||||||
* Uses logic from d3 Time-Scales, v3 of the API. See
|
|
||||||
* https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Scales.md
|
|
||||||
*
|
|
||||||
* Licensed
|
|
||||||
*/
|
|
||||||
var format = [
|
|
||||||
[".SSS", function (m) {
|
|
||||||
return m.milliseconds();
|
|
||||||
}],
|
|
||||||
[":ss", function (m) {
|
|
||||||
return m.seconds();
|
|
||||||
}],
|
|
||||||
["HH:mm", function (m) {
|
|
||||||
return m.minutes();
|
|
||||||
}],
|
|
||||||
["HH", function (m) {
|
|
||||||
return m.hours();
|
|
||||||
}],
|
|
||||||
["ddd DD", function (m) {
|
|
||||||
return m.days() &&
|
|
||||||
m.date() !== 1;
|
|
||||||
}],
|
|
||||||
["MMM DD", function (m) {
|
|
||||||
return m.date() !== 1;
|
|
||||||
}],
|
|
||||||
["MMMM", function (m) {
|
|
||||||
return m.month();
|
|
||||||
}],
|
|
||||||
["YYYY", function () {
|
|
||||||
return true;
|
|
||||||
}]
|
|
||||||
].filter(function (row) {
|
|
||||||
return row[1](momentified);
|
|
||||||
})[0][0];
|
|
||||||
|
|
||||||
if (format !== undefined) {
|
|
||||||
return moment.utc(d).format(format);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {number} value The value to format.
|
* @param {number} value The value to format.
|
||||||
* @param {number} [minValue] Contextual information for scaled formatting used in linear scales such as conductor
|
|
||||||
* and plot axes. Specifies the smallest number on the scale.
|
|
||||||
* @param {number} [maxValue] Contextual information for scaled formatting used in linear scales such as conductor
|
|
||||||
* and plot axes. Specifies the largest number on the scale
|
|
||||||
* @param {number} [count] Contextual information for scaled formatting used in linear scales such as conductor
|
|
||||||
* and plot axes. The number of labels on the scale.
|
|
||||||
* @returns {string} the formatted date(s). If multiple values were requested, then an array of
|
* @returns {string} the formatted date(s). If multiple values were requested, then an array of
|
||||||
* formatted values will be returned. Where a value could not be formatted, `undefined` will be returned at its position
|
* formatted values will be returned. Where a value could not be formatted, `undefined` will be returned at its position
|
||||||
* in the array.
|
* in the array.
|
||||||
*/
|
*/
|
||||||
UTCTimeFormat.prototype.format = function (value) {
|
UTCTimeFormat.prototype.format = function (value) {
|
||||||
if (arguments.length > 1) {
|
if (value !== undefined) {
|
||||||
return getScaledFormat(value);
|
|
||||||
} else if (value !== undefined) {
|
|
||||||
return moment.utc(value).format(DATE_FORMAT) + "Z";
|
return moment.utc(value).format(DATE_FORMAT) + "Z";
|
||||||
} else {
|
} else {
|
||||||
return value;
|
return value;
|
||||||
@@ -130,7 +75,7 @@ define([
|
|||||||
};
|
};
|
||||||
|
|
||||||
UTCTimeFormat.prototype.validate = function (text) {
|
UTCTimeFormat.prototype.validate = function (text) {
|
||||||
return moment.utc(text, DATE_FORMATS).isValid();
|
return moment.utc(text, DATE_FORMATS, true).isValid();
|
||||||
};
|
};
|
||||||
|
|
||||||
return UTCTimeFormat;
|
return UTCTimeFormat;
|
||||||
|
|||||||
@@ -1,62 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define([
|
|
||||||
"../src/UTCTimeFormat",
|
|
||||||
"moment"
|
|
||||||
], function (
|
|
||||||
UTCTimeFormat,
|
|
||||||
moment
|
|
||||||
) {
|
|
||||||
describe("The UTCTimeFormat class", function () {
|
|
||||||
var format;
|
|
||||||
var scale;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
format = new UTCTimeFormat();
|
|
||||||
scale = {min: 0, max: 0};
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Provides an appropriately scaled time format based on the input" +
|
|
||||||
" time", function () {
|
|
||||||
var TWO_HUNDRED_MS = 200;
|
|
||||||
var THREE_SECONDS = 3000;
|
|
||||||
var FIVE_MINUTES = 5 * 60 * 1000;
|
|
||||||
var ONE_HOUR_TWENTY_MINS = (1 * 60 * 60 * 1000) + (20 * 60 * 1000);
|
|
||||||
var TEN_HOURS = (10 * 60 * 60 * 1000);
|
|
||||||
|
|
||||||
var JUNE_THIRD = moment.utc("2016-06-03", "YYYY-MM-DD");
|
|
||||||
var APRIL = moment.utc("2016-04", "YYYY-MM");
|
|
||||||
var TWENTY_SIXTEEN = moment.utc("2016", "YYYY");
|
|
||||||
|
|
||||||
expect(format.format(TWO_HUNDRED_MS, scale)).toBe(".200");
|
|
||||||
expect(format.format(THREE_SECONDS, scale)).toBe(":03");
|
|
||||||
expect(format.format(FIVE_MINUTES, scale)).toBe("00:05");
|
|
||||||
expect(format.format(ONE_HOUR_TWENTY_MINS, scale)).toBe("01:20");
|
|
||||||
expect(format.format(TEN_HOURS, scale)).toBe("10");
|
|
||||||
|
|
||||||
expect(format.format(JUNE_THIRD, scale)).toBe("Fri 03");
|
|
||||||
expect(format.format(APRIL, scale)).toBe("April");
|
|
||||||
expect(format.format(TWENTY_SIXTEEN, scale)).toBe("2016");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -60,7 +60,7 @@ define(
|
|||||||
};
|
};
|
||||||
//If the notification is dismissed by the user, close
|
//If the notification is dismissed by the user, close
|
||||||
// the dialog.
|
// the dialog.
|
||||||
notification.onDismiss(function () {
|
notification.on('dismiss', function () {
|
||||||
dialog.dismiss();
|
dialog.dismiss();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -45,15 +45,27 @@ define(
|
|||||||
|
|
||||||
// Link; install event handlers.
|
// Link; install event handlers.
|
||||||
function link(scope, element, attrs) {
|
function link(scope, element, attrs) {
|
||||||
var removeSelectable = openmct.selection.selectable(
|
var isDestroyed = false;
|
||||||
element[0],
|
|
||||||
scope.$eval(attrs.mctSelectable),
|
|
||||||
attrs.hasOwnProperty('mctInitSelect') && scope.$eval(attrs.mctInitSelect) !== false
|
|
||||||
);
|
|
||||||
|
|
||||||
scope.$on("$destroy", function () {
|
scope.$on("$destroy", function () {
|
||||||
removeSelectable();
|
isDestroyed = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
openmct.$injector.get('$timeout')(function () {
|
||||||
|
if (isDestroyed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var removeSelectable = openmct.selection.selectable(
|
||||||
|
element[0],
|
||||||
|
scope.$eval(attrs.mctSelectable),
|
||||||
|
attrs.hasOwnProperty('mctInitSelect') && scope.$eval(attrs.mctInitSelect) !== false
|
||||||
|
);
|
||||||
|
|
||||||
|
scope.$on("$destroy", function () {
|
||||||
|
removeSelectable();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -23,33 +23,17 @@
|
|||||||
define([
|
define([
|
||||||
"./src/NotificationIndicatorController",
|
"./src/NotificationIndicatorController",
|
||||||
"./src/NotificationIndicator",
|
"./src/NotificationIndicator",
|
||||||
"./src/NotificationService",
|
|
||||||
"./res/notification-indicator.html",
|
"./res/notification-indicator.html",
|
||||||
'legacyRegistry'
|
'legacyRegistry'
|
||||||
], function (
|
], function (
|
||||||
NotificationIndicatorController,
|
NotificationIndicatorController,
|
||||||
NotificationIndicator,
|
NotificationIndicator,
|
||||||
NotificationService,
|
|
||||||
notificationIndicatorTemplate,
|
notificationIndicatorTemplate,
|
||||||
legacyRegistry
|
legacyRegistry
|
||||||
) {
|
) {
|
||||||
|
|
||||||
legacyRegistry.register("platform/commonUI/notification", {
|
legacyRegistry.register("platform/commonUI/notification", {
|
||||||
"extensions": {
|
"extensions": {
|
||||||
"constants": [
|
|
||||||
{
|
|
||||||
"key": "DEFAULT_AUTO_DISMISS",
|
|
||||||
"value": 3000
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "FORCE_AUTO_DISMISS",
|
|
||||||
"value": 1000
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "MINIMIZE_TIMEOUT",
|
|
||||||
"value": 300
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"templates": [
|
"templates": [
|
||||||
{
|
{
|
||||||
"key": "notificationIndicatorTemplate",
|
"key": "notificationIndicatorTemplate",
|
||||||
@@ -76,12 +60,11 @@ define([
|
|||||||
"services": [
|
"services": [
|
||||||
{
|
{
|
||||||
"key": "notificationService",
|
"key": "notificationService",
|
||||||
"implementation": NotificationService,
|
"implementation": function (openmct) {
|
||||||
|
return openmct.notifications;
|
||||||
|
},
|
||||||
"depends": [
|
"depends": [
|
||||||
"$timeout",
|
"openmct"
|
||||||
"topic",
|
|
||||||
"DEFAULT_AUTO_DISMISS",
|
|
||||||
"MINIMIZE_TIMEOUT"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,437 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This bundle implements the notification service, which can be used to
|
|
||||||
* show banner notifications to the user. Banner notifications
|
|
||||||
* are used to inform users of events in a non-intrusive way. As
|
|
||||||
* much as possible, notifications share a model with blocking
|
|
||||||
* dialogs so that the same information can be provided in a dialog
|
|
||||||
* and then minimized to a banner notification if needed.
|
|
||||||
*
|
|
||||||
* @namespace platform/commonUI/notification
|
|
||||||
*/
|
|
||||||
define(
|
|
||||||
['moment'],
|
|
||||||
function (moment) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A representation of a user action. Options are provided to
|
|
||||||
* dialogs and notifications and are shown as buttons.
|
|
||||||
*
|
|
||||||
* @typedef {object} NotificationOption
|
|
||||||
* @property {string} label the label to appear on the button for
|
|
||||||
* this action
|
|
||||||
* @property {function} callback a callback function to be invoked
|
|
||||||
* when the button is clicked
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A representation of a banner notification. Banner notifications
|
|
||||||
* are used to inform users of events in a non-intrusive way. As
|
|
||||||
* much as possible, notifications share a model with blocking
|
|
||||||
* dialogs so that the same information can be provided in a dialog
|
|
||||||
* and then minimized to a banner notification if needed, or vice-versa.
|
|
||||||
*
|
|
||||||
* @typedef {object} NotificationModel
|
|
||||||
* @property {string} title The title of the message
|
|
||||||
* @property {string} severity The importance of the message (one of
|
|
||||||
* 'info', 'alert', or 'error' where info < alert <error)
|
|
||||||
* @property {number} [progress] The completion status of a task
|
|
||||||
* represented numerically
|
|
||||||
* @property {boolean} [unknownProgress] a boolean indicating that the
|
|
||||||
* progress of the underlying task is unknown. This will result in a
|
|
||||||
* visually distinct progress bar.
|
|
||||||
* @property {boolean} [autoDismiss] If truthy, dialog will
|
|
||||||
* be automatically minimized or dismissed (depending on severity).
|
|
||||||
* Additionally, if the provided value is a number, it will be used
|
|
||||||
* as the delay period before being dismissed.
|
|
||||||
* @property {boolean} [dismissable=true] If true, notification will
|
|
||||||
* include an option to dismiss it completely.
|
|
||||||
* @property {NotificationOption} [primaryOption] the default user
|
|
||||||
* response to
|
|
||||||
* this message. Will be represented as a button with the provided
|
|
||||||
* label and action. May be used by banner notifications to display
|
|
||||||
* only the most important option to users.
|
|
||||||
* @property {NotificationOption[]} [options] any additional
|
|
||||||
* actions the user can take. Will be represented as additional buttons
|
|
||||||
* that may or may not be available from a banner.
|
|
||||||
* @see DialogModel
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A wrapper object that is returned as a handle to a newly created
|
|
||||||
* notification. Wraps the notifications model and decorates with
|
|
||||||
* functions to dismiss or minimize the notification.
|
|
||||||
*
|
|
||||||
* @typedef {object} Notification
|
|
||||||
* @property {function} dismiss This method is added to the object
|
|
||||||
* returned by {@link NotificationService#notify} and can be used to
|
|
||||||
* dismiss this notification. Dismissing a notification will remove
|
|
||||||
* it completely and it will not appear in the notification indicator
|
|
||||||
* @property {function} minimize This method is added to the object
|
|
||||||
* returned by {@link NotificationService#notify} and can be used to
|
|
||||||
* minimize this notification. Minimizing a notification will send
|
|
||||||
* it to the notification indicator
|
|
||||||
* @property {function} dismissOrMinimize This method is added to the
|
|
||||||
* object returned by {@link NotificationService#notify}. It will
|
|
||||||
* hide the notification by either dismissing or minimizing it,
|
|
||||||
* depending on severity. Typically this is the method that should
|
|
||||||
* be used for dismissing a notification. If more control is
|
|
||||||
* required, then the minimize or dismiss functions can be called
|
|
||||||
* individually.
|
|
||||||
* @property {function} onDismiss Allows listening for on dismiss
|
|
||||||
* events. This allows cleanup etc. when the notification is dismissed.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The notification service is responsible for informing the user of
|
|
||||||
* events via the use of banner notifications.
|
|
||||||
* @memberof platform/commonUI/notification
|
|
||||||
* @constructor
|
|
||||||
* @param $timeout the Angular $timeout service
|
|
||||||
* @param defaultAutoDismissTimeout The period of time that an
|
|
||||||
* auto-dismissed message will be displayed for.
|
|
||||||
* @param minimizeAnimationTimeout When notifications are minimized, a brief
|
|
||||||
* animation is shown. This animation requires some time to execute,
|
|
||||||
* so a timeout is required before the notification is hidden
|
|
||||||
*/
|
|
||||||
function NotificationService($timeout, topic, defaultAutoDismissTimeout, minimizeAnimationTimeout) {
|
|
||||||
this.notifications = [];
|
|
||||||
this.$timeout = $timeout;
|
|
||||||
this.highest = { severity: "info" };
|
|
||||||
this.AUTO_DISMISS_TIMEOUT = defaultAutoDismissTimeout;
|
|
||||||
this.MINIMIZE_ANIMATION_TIMEOUT = minimizeAnimationTimeout;
|
|
||||||
this.topic = topic;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* A context in which to hold the active notification and a
|
|
||||||
* handle to its timeout.
|
|
||||||
*/
|
|
||||||
this.active = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Minimize a notification. The notification will still be available
|
|
||||||
* from the notification list. Typically notifications with a
|
|
||||||
* severity of 'info' should not be minimized, but rather
|
|
||||||
* dismissed. If you're not sure which is appropriate,
|
|
||||||
* use {@link Notification#dismissOrMinimize}
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
NotificationService.prototype.minimize = function (service, notification) {
|
|
||||||
//Check this is a known notification
|
|
||||||
var index = service.notifications.indexOf(notification);
|
|
||||||
|
|
||||||
if (service.active.timeout) {
|
|
||||||
/*
|
|
||||||
Method can be called manually (clicking dismiss) or
|
|
||||||
automatically from an auto-timeout. this.active.timeout
|
|
||||||
acts as a semaphore to prevent race conditions. Cancel any
|
|
||||||
timeout in progress (for the case where a manual dismiss
|
|
||||||
has shortcut an active auto-dismiss), and clear the
|
|
||||||
semaphore.
|
|
||||||
*/
|
|
||||||
service.$timeout.cancel(service.active.timeout);
|
|
||||||
delete service.active.timeout;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (index >= 0) {
|
|
||||||
notification.model.minimized = true;
|
|
||||||
//Add a brief timeout before showing the next notification
|
|
||||||
// in order to allow the minimize animation to run through.
|
|
||||||
service.$timeout(function () {
|
|
||||||
service.setActiveNotification(service.selectNextNotification());
|
|
||||||
}, service.MINIMIZE_ANIMATION_TIMEOUT);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Completely removes a notification. This will dismiss it from the
|
|
||||||
* message banner and remove it from the list of notifications.
|
|
||||||
* Typically only notifications with a severity of info should be
|
|
||||||
* dismissed. If you're not sure whether to dismiss or minimize a
|
|
||||||
* notification, use {@link Notification#dismissOrMinimize}.
|
|
||||||
* dismiss
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
NotificationService.prototype.dismiss = function (service, notification) {
|
|
||||||
//Check this is a known notification
|
|
||||||
var index = service.notifications.indexOf(notification);
|
|
||||||
|
|
||||||
if (service.active.timeout) {
|
|
||||||
/* Method can be called manually (clicking dismiss) or
|
|
||||||
* automatically from an auto-timeout. this.active.timeout
|
|
||||||
* acts as a semaphore to prevent race conditions. Cancel any
|
|
||||||
* timeout in progress (for the case where a manual dismiss
|
|
||||||
* has shortcut an active auto-dismiss), and clear the
|
|
||||||
* semaphore.
|
|
||||||
*/
|
|
||||||
|
|
||||||
service.$timeout.cancel(service.active.timeout);
|
|
||||||
delete service.active.timeout;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (index >= 0) {
|
|
||||||
service.notifications.splice(index, 1);
|
|
||||||
}
|
|
||||||
service.setActiveNotification(service.selectNextNotification());
|
|
||||||
|
|
||||||
this.setHighestSeverity();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Depending on the severity of the notification will selectively
|
|
||||||
* dismiss or minimize where appropriate.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
NotificationService.prototype.dismissOrMinimize = function (notification) {
|
|
||||||
var model = notification.model;
|
|
||||||
if (model.severity === "info") {
|
|
||||||
if (model.autoDismiss === false) {
|
|
||||||
notification.minimize();
|
|
||||||
} else {
|
|
||||||
notification.dismiss();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
notification.minimize();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the notification that is currently visible in the banner area
|
|
||||||
* @returns {Notification}
|
|
||||||
*/
|
|
||||||
NotificationService.prototype.getActiveNotification = function () {
|
|
||||||
return this.active.notification;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A convenience method for info notifications. Notifications
|
|
||||||
* created via this method will be auto-dismissed after a default
|
|
||||||
* wait period unless explicitly forbidden by the caller through
|
|
||||||
* the {autoDismiss} property on the {NotificationModel}, in which
|
|
||||||
* case the notification will be minimized after the wait.
|
|
||||||
* @param {NotificationModel | string} message either a string for
|
|
||||||
* the title of the notification message, or a {@link NotificationModel}
|
|
||||||
* defining the options notification to display
|
|
||||||
* @returns {Notification} the provided notification decorated with
|
|
||||||
* functions to dismiss or minimize
|
|
||||||
*/
|
|
||||||
NotificationService.prototype.info = function (message) {
|
|
||||||
var notificationModel = typeof message === "string" ? {title: message} : message;
|
|
||||||
notificationModel.severity = "info";
|
|
||||||
return this.notify(notificationModel);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A convenience method for alert notifications. Notifications
|
|
||||||
* created via this method will will have severity of "alert" enforced
|
|
||||||
* @param {NotificationModel | string} message either a string for
|
|
||||||
* the title of the alert message with default options, or a
|
|
||||||
* {@link NotificationModel} defining the options notification to
|
|
||||||
* display
|
|
||||||
* @returns {Notification} the provided notification decorated with
|
|
||||||
* functions to dismiss or minimize
|
|
||||||
*/
|
|
||||||
NotificationService.prototype.alert = function (message) {
|
|
||||||
var notificationModel = typeof message === "string" ? {title: message} : message;
|
|
||||||
notificationModel.severity = "alert";
|
|
||||||
return this.notify(notificationModel);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A convenience method for error notifications. Notifications
|
|
||||||
* created via this method will will have severity of "error" enforced
|
|
||||||
* @param {NotificationModel | string} message either a string for
|
|
||||||
* the title of the error message with default options, or a
|
|
||||||
* {@link NotificationModel} defining the options notification to
|
|
||||||
* display
|
|
||||||
* @returns {Notification} the provided notification decorated with
|
|
||||||
* functions to dismiss or minimize
|
|
||||||
*/
|
|
||||||
NotificationService.prototype.error = function (message) {
|
|
||||||
var notificationModel = typeof message === "string" ? {title: message} : message;
|
|
||||||
notificationModel.severity = "error";
|
|
||||||
return this.notify(notificationModel);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
NotificationService.prototype.setHighestSeverity = function () {
|
|
||||||
var severity = {
|
|
||||||
"info": 1,
|
|
||||||
"alert": 2,
|
|
||||||
"error": 3
|
|
||||||
};
|
|
||||||
this.highest.severity = this.notifications.reduce(function (previous, notification) {
|
|
||||||
if (severity[notification.model.severity] > severity[previous]) {
|
|
||||||
return notification.model.severity;
|
|
||||||
} else {
|
|
||||||
return previous;
|
|
||||||
}
|
|
||||||
}, "info");
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Notifies the user of an event. If there is a banner notification
|
|
||||||
* already active, then it will be dismissed or minimized automatically,
|
|
||||||
* and the provided notification displayed in its place.
|
|
||||||
*
|
|
||||||
* @param {NotificationModel} notificationModel The notification to
|
|
||||||
* display
|
|
||||||
* @returns {Notification} the provided notification decorated with
|
|
||||||
* functions to {@link Notification#dismiss} or {@link Notification#minimize}
|
|
||||||
*/
|
|
||||||
NotificationService.prototype.notify = function (notificationModel) {
|
|
||||||
var self = this,
|
|
||||||
notification,
|
|
||||||
activeNotification = self.active.notification,
|
|
||||||
topic = this.topic();
|
|
||||||
|
|
||||||
notificationModel.severity = notificationModel.severity || "info";
|
|
||||||
notificationModel.timestamp = moment.utc().format('YYYY-MM-DD hh:mm:ss.ms');
|
|
||||||
|
|
||||||
notification = {
|
|
||||||
model: notificationModel,
|
|
||||||
|
|
||||||
minimize: function () {
|
|
||||||
self.minimize(self, notification);
|
|
||||||
},
|
|
||||||
|
|
||||||
dismiss: function () {
|
|
||||||
self.dismiss(self, notification);
|
|
||||||
topic.notify();
|
|
||||||
},
|
|
||||||
|
|
||||||
dismissOrMinimize: function () {
|
|
||||||
self.dismissOrMinimize(notification);
|
|
||||||
},
|
|
||||||
|
|
||||||
onDismiss: function (callback) {
|
|
||||||
topic.listen(callback);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//Notifications support a 'dismissable' attribute. This is a
|
|
||||||
// convenience to support adding a 'dismiss' option to the
|
|
||||||
// notification for the common case of dismissing a
|
|
||||||
// notification. Could also be done manually by specifying an
|
|
||||||
// option on the model
|
|
||||||
if (notificationModel.dismissable !== false) {
|
|
||||||
notificationModel.options = notificationModel.options || [];
|
|
||||||
notificationModel.options.unshift({
|
|
||||||
label: "Dismiss",
|
|
||||||
callback: function () {
|
|
||||||
notification.dismiss();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.notifications.push(notification);
|
|
||||||
|
|
||||||
this.setHighestSeverity();
|
|
||||||
|
|
||||||
/*
|
|
||||||
Check if there is already an active (ie. visible) notification
|
|
||||||
*/
|
|
||||||
if (!this.active.notification) {
|
|
||||||
this.setActiveNotification(notification);
|
|
||||||
|
|
||||||
} else if (!this.active.timeout) {
|
|
||||||
/*
|
|
||||||
If there is already an active notification, time it out. If it's
|
|
||||||
already got a timeout in progress (either because it has had
|
|
||||||
timeout forced because of a queue of messages, or it had an
|
|
||||||
autodismiss specified), leave it to run. Otherwise force a
|
|
||||||
timeout.
|
|
||||||
|
|
||||||
This notification has been added to queue and will be
|
|
||||||
serviced as soon as possible.
|
|
||||||
*/
|
|
||||||
this.active.timeout = this.$timeout(function () {
|
|
||||||
activeNotification.dismissOrMinimize();
|
|
||||||
}, this.AUTO_DISMISS_TIMEOUT);
|
|
||||||
}
|
|
||||||
|
|
||||||
return notification;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used internally by the NotificationService
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
NotificationService.prototype.setActiveNotification = function (notification) {
|
|
||||||
var shouldAutoDismiss;
|
|
||||||
this.active.notification = notification;
|
|
||||||
|
|
||||||
if (!notification) {
|
|
||||||
delete this.active.timeout;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (notification.model.severity === "info") {
|
|
||||||
shouldAutoDismiss = true;
|
|
||||||
} else {
|
|
||||||
shouldAutoDismiss = notification.model.autoDismiss;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shouldAutoDismiss || this.selectNextNotification()) {
|
|
||||||
this.active.timeout = this.$timeout(function () {
|
|
||||||
notification.dismissOrMinimize();
|
|
||||||
}, this.AUTO_DISMISS_TIMEOUT);
|
|
||||||
} else {
|
|
||||||
delete this.active.timeout;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used internally by the NotificationService
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
NotificationService.prototype.selectNextNotification = function () {
|
|
||||||
var notification,
|
|
||||||
i = 0;
|
|
||||||
|
|
||||||
/*
|
|
||||||
Loop through the notifications queue and find the first one that
|
|
||||||
has not already been minimized (manually or otherwise).
|
|
||||||
*/
|
|
||||||
for (; i < this.notifications.length; i++) {
|
|
||||||
notification = this.notifications[i];
|
|
||||||
|
|
||||||
if (!notification.model.minimized &&
|
|
||||||
notification !== this.active.notification) {
|
|
||||||
|
|
||||||
return notification;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return NotificationService;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
@@ -1,275 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
/*global describe,it,expect,beforeEach,jasmine*/
|
|
||||||
|
|
||||||
define(
|
|
||||||
['../src/NotificationService'],
|
|
||||||
function (NotificationService) {
|
|
||||||
|
|
||||||
describe("The notification service ", function () {
|
|
||||||
var notificationService,
|
|
||||||
mockTimeout,
|
|
||||||
mockAutoDismiss,
|
|
||||||
mockMinimizeTimeout,
|
|
||||||
mockTopicFunction,
|
|
||||||
mockTopicObject,
|
|
||||||
infoModel,
|
|
||||||
alertModel,
|
|
||||||
errorModel;
|
|
||||||
|
|
||||||
function elapseTimeout() {
|
|
||||||
mockTimeout.calls.mostRecent().args[0]();
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
mockTimeout = jasmine.createSpy("$timeout");
|
|
||||||
mockTopicFunction = jasmine.createSpy("topic");
|
|
||||||
mockTopicObject = jasmine.createSpyObj("topicObject", ["listen", "notify"]);
|
|
||||||
mockTopicFunction.and.returnValue(mockTopicObject);
|
|
||||||
|
|
||||||
mockAutoDismiss = mockMinimizeTimeout = 1000;
|
|
||||||
notificationService = new NotificationService(mockTimeout, mockTopicFunction, mockAutoDismiss, mockMinimizeTimeout);
|
|
||||||
|
|
||||||
infoModel = {
|
|
||||||
title: "Mock Info Notification",
|
|
||||||
severity: "info"
|
|
||||||
};
|
|
||||||
|
|
||||||
alertModel = {
|
|
||||||
title: "Mock Alert Notification",
|
|
||||||
severity: "alert"
|
|
||||||
};
|
|
||||||
|
|
||||||
errorModel = {
|
|
||||||
title: "Mock Error Notification",
|
|
||||||
severity: "error"
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
it("notifies listeners on dismissal of notification", function () {
|
|
||||||
var dismissListener = jasmine.createSpy("ondismiss");
|
|
||||||
var notification = notificationService.notify(infoModel);
|
|
||||||
notification.onDismiss(dismissListener);
|
|
||||||
expect(mockTopicObject.listen).toHaveBeenCalled();
|
|
||||||
notification.dismiss();
|
|
||||||
expect(mockTopicObject.notify).toHaveBeenCalled();
|
|
||||||
mockTopicObject.listen.calls.mostRecent().args[0]();
|
|
||||||
expect(dismissListener).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("dismisses a notification when the notification's dismiss method is used", function () {
|
|
||||||
var notification = notificationService.info(infoModel);
|
|
||||||
notification.dismiss();
|
|
||||||
expect(notificationService.getActiveNotification()).toBeUndefined();
|
|
||||||
expect(notificationService.notifications.length).toEqual(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("minimizes a notification when the notification's minimize method is used", function () {
|
|
||||||
var notification = notificationService.info(infoModel);
|
|
||||||
notification.minimize();
|
|
||||||
elapseTimeout(); // needed for the minimize animation timeout
|
|
||||||
expect(notificationService.getActiveNotification()).toBeUndefined();
|
|
||||||
expect(notificationService.notifications.length).toEqual(1);
|
|
||||||
expect(notificationService.notifications[0]).toEqual(notification);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when receiving info notifications", function () {
|
|
||||||
it("minimizes info notifications if the caller disables auto-dismiss", function () {
|
|
||||||
infoModel.autoDismiss = false;
|
|
||||||
var notification = notificationService.info(infoModel);
|
|
||||||
elapseTimeout();
|
|
||||||
// 2nd elapse for the minimize animation timeout
|
|
||||||
elapseTimeout();
|
|
||||||
expect(notificationService.getActiveNotification()).toBeUndefined();
|
|
||||||
expect(notificationService.notifications.length).toEqual(1);
|
|
||||||
expect(notificationService.notifications[0]).toEqual(notification);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("dismisses info notifications if the caller ignores auto-dismiss", function () {
|
|
||||||
notificationService.info(infoModel);
|
|
||||||
elapseTimeout();
|
|
||||||
expect(notificationService.getActiveNotification()).toBeUndefined();
|
|
||||||
expect(notificationService.notifications.length).toEqual(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("dismisses info notifications if the caller requests auto-dismiss", function () {
|
|
||||||
infoModel.autoDismiss = true;
|
|
||||||
notificationService.info(infoModel);
|
|
||||||
elapseTimeout();
|
|
||||||
expect(notificationService.getActiveNotification()).toBeUndefined();
|
|
||||||
expect(notificationService.notifications.length).toEqual(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when receiving alert notifications", function () {
|
|
||||||
it("minimizes alert notifications if the caller enables auto-dismiss", function () {
|
|
||||||
alertModel.autoDismiss = true;
|
|
||||||
var notification = notificationService.alert(alertModel);
|
|
||||||
elapseTimeout();
|
|
||||||
elapseTimeout();
|
|
||||||
expect(notificationService.getActiveNotification()).toBeUndefined();
|
|
||||||
expect(notificationService.notifications.length).toEqual(1);
|
|
||||||
expect(notificationService.notifications[0]).toEqual(notification);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("keeps alert notifications active if the caller disables auto-dismiss", function () {
|
|
||||||
mockTimeout.and.callFake(function (callback, time) {
|
|
||||||
callback();
|
|
||||||
});
|
|
||||||
alertModel.autoDismiss = false;
|
|
||||||
var notification = notificationService.alert(alertModel);
|
|
||||||
expect(notificationService.getActiveNotification()).toEqual(notification);
|
|
||||||
expect(notificationService.notifications.length).toEqual(1);
|
|
||||||
expect(notificationService.notifications[0]).toEqual(notification);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("keeps alert notifications active if the caller ignores auto-dismiss", function () {
|
|
||||||
mockTimeout.and.callFake(function (callback, time) {
|
|
||||||
callback();
|
|
||||||
});
|
|
||||||
var notification = notificationService.alert(alertModel);
|
|
||||||
expect(notificationService.getActiveNotification()).toEqual(notification);
|
|
||||||
expect(notificationService.notifications.length).toEqual(1);
|
|
||||||
expect(notificationService.notifications[0]).toEqual(notification);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when receiving error notifications", function () {
|
|
||||||
it("minimizes error notifications if the caller enables auto-dismiss", function () {
|
|
||||||
errorModel.autoDismiss = true;
|
|
||||||
var notification = notificationService.error(errorModel);
|
|
||||||
elapseTimeout();
|
|
||||||
elapseTimeout();
|
|
||||||
expect(notificationService.getActiveNotification()).toBeUndefined();
|
|
||||||
expect(notificationService.notifications.length).toEqual(1);
|
|
||||||
expect(notificationService.notifications[0]).toEqual(notification);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("keeps error notifications active if the caller disables auto-dismiss", function () {
|
|
||||||
mockTimeout.and.callFake(function (callback, time) {
|
|
||||||
callback();
|
|
||||||
});
|
|
||||||
errorModel.autoDismiss = false;
|
|
||||||
var notification = notificationService.error(errorModel);
|
|
||||||
expect(notificationService.getActiveNotification()).toEqual(notification);
|
|
||||||
expect(notificationService.notifications.length).toEqual(1);
|
|
||||||
expect(notificationService.notifications[0]).toEqual(notification);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("keeps error notifications active if the caller ignores auto-dismiss", function () {
|
|
||||||
mockTimeout.and.callFake(function (callback, time) {
|
|
||||||
callback();
|
|
||||||
});
|
|
||||||
var notification = notificationService.error(errorModel);
|
|
||||||
expect(notificationService.getActiveNotification()).toEqual(notification);
|
|
||||||
expect(notificationService.notifications.length).toEqual(1);
|
|
||||||
expect(notificationService.notifications[0]).toEqual(notification);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when called with multiple notifications", function () {
|
|
||||||
it("auto-dismisses the previously active notification, making the new notification active", function () {
|
|
||||||
var activeNotification;
|
|
||||||
infoModel.autoDismiss = false;
|
|
||||||
//First pre-load with a info message
|
|
||||||
notificationService.notify(infoModel);
|
|
||||||
activeNotification = notificationService.getActiveNotification();
|
|
||||||
//Initially expect the active notification to be info
|
|
||||||
expect(activeNotification.model).toBe(infoModel);
|
|
||||||
//Then notify of an error
|
|
||||||
notificationService.notify(errorModel);
|
|
||||||
//But it should be auto-dismissed and replaced with the
|
|
||||||
// error notification
|
|
||||||
elapseTimeout();
|
|
||||||
//Two timeouts, one is to force minimization after
|
|
||||||
// displaying the message for a minimum period, the
|
|
||||||
// second is to allow minimization animation to take place.
|
|
||||||
elapseTimeout();
|
|
||||||
activeNotification = notificationService.getActiveNotification();
|
|
||||||
expect(activeNotification.model).toBe(errorModel);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("auto-minimizes an active error notification", function () {
|
|
||||||
var activeNotification;
|
|
||||||
//First pre-load with an error message
|
|
||||||
notificationService.notify(errorModel);
|
|
||||||
//Then notify of info
|
|
||||||
notificationService.notify(infoModel);
|
|
||||||
expect(notificationService.notifications.length).toEqual(2);
|
|
||||||
//Mock the auto-minimize
|
|
||||||
elapseTimeout();
|
|
||||||
//Two timeouts, one is to force minimization after
|
|
||||||
// displaying the message for a minimum period, the
|
|
||||||
// second is to allow minimization animation to take place.
|
|
||||||
elapseTimeout();
|
|
||||||
//Previous error message should be minimized, not
|
|
||||||
// dismissed
|
|
||||||
expect(notificationService.notifications.length).toEqual(2);
|
|
||||||
activeNotification = notificationService.getActiveNotification();
|
|
||||||
expect(activeNotification.model).toBe(infoModel);
|
|
||||||
expect(errorModel.minimized).toEqual(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("auto-minimizes errors when a number of them arrive in short succession", function () {
|
|
||||||
var activeNotification,
|
|
||||||
error2 = {
|
|
||||||
title: "Second Mock Error Notification",
|
|
||||||
severity: "error"
|
|
||||||
},
|
|
||||||
error3 = {
|
|
||||||
title: "Third Mock Error Notification",
|
|
||||||
severity: "error"
|
|
||||||
};
|
|
||||||
|
|
||||||
//First pre-load with a info message
|
|
||||||
notificationService.notify(errorModel);
|
|
||||||
//Then notify of a third error
|
|
||||||
notificationService.notify(error2);
|
|
||||||
notificationService.notify(error3);
|
|
||||||
expect(notificationService.notifications.length).toEqual(3);
|
|
||||||
//Mock the auto-minimize
|
|
||||||
elapseTimeout();
|
|
||||||
//Two timeouts, one is to force minimization after
|
|
||||||
// displaying the message for a minimum period, the
|
|
||||||
// second is to allow minimization animation to take place.
|
|
||||||
elapseTimeout();
|
|
||||||
//Previous error message should be minimized, not
|
|
||||||
// dismissed
|
|
||||||
expect(notificationService.notifications.length).toEqual(3);
|
|
||||||
activeNotification = notificationService.getActiveNotification();
|
|
||||||
expect(activeNotification.model).toBe(error2);
|
|
||||||
expect(errorModel.minimized).toEqual(true);
|
|
||||||
|
|
||||||
//Mock the second auto-minimize
|
|
||||||
elapseTimeout();
|
|
||||||
//Two timeouts, one is to force minimization after
|
|
||||||
// displaying the message for a minimum period, the
|
|
||||||
// second is to allow minimization animation to take place.
|
|
||||||
elapseTimeout();
|
|
||||||
activeNotification = notificationService.getActiveNotification();
|
|
||||||
expect(activeNotification.model).toBe(error3);
|
|
||||||
expect(error2.minimized).toEqual(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
@@ -76,7 +76,7 @@ define(
|
|||||||
* @returns a domain object
|
* @returns a domain object
|
||||||
*/
|
*/
|
||||||
InspectorController.prototype.selectedItem = function () {
|
InspectorController.prototype.selectedItem = function () {
|
||||||
return this.$scope.selection[0].context.oldItem;
|
return this.$scope.selection[0] && this.$scope.selection[0].context.oldItem;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define([
|
|
||||||
"./src/ConductorRepresenter",
|
|
||||||
'legacyRegistry'
|
|
||||||
], function (
|
|
||||||
ConductorRepresenter,
|
|
||||||
legacyRegistry
|
|
||||||
) {
|
|
||||||
|
|
||||||
legacyRegistry.register("platform/features/conductor/compatibility", {
|
|
||||||
"extensions": {
|
|
||||||
"representers": [
|
|
||||||
{
|
|
||||||
"implementation": ConductorRepresenter,
|
|
||||||
"depends": [
|
|
||||||
"openmct"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
[],
|
|
||||||
function () {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Representer that provides a compatibility layer between the new
|
|
||||||
* time conductor and existing representations / views. Listens to
|
|
||||||
* the v2 time conductor API and generates v1 style events using the
|
|
||||||
* Angular event bus. This is transitional code code and will be
|
|
||||||
* removed.
|
|
||||||
*
|
|
||||||
* Deprecated immediately as this is temporary code
|
|
||||||
*
|
|
||||||
* @deprecated
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function ConductorRepresenter(
|
|
||||||
openmct,
|
|
||||||
scope,
|
|
||||||
element
|
|
||||||
) {
|
|
||||||
this.timeAPI = openmct.time;
|
|
||||||
this.scope = scope;
|
|
||||||
this.element = element;
|
|
||||||
|
|
||||||
this.boundsListener = this.boundsListener.bind(this);
|
|
||||||
this.timeSystemListener = this.timeSystemListener.bind(this);
|
|
||||||
this.followListener = this.followListener.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
ConductorRepresenter.prototype.boundsListener = function (bounds) {
|
|
||||||
var timeSystem = this.timeAPI.timeSystem();
|
|
||||||
this.scope.$broadcast('telemetry:display:bounds', {
|
|
||||||
start: bounds.start,
|
|
||||||
end: bounds.end,
|
|
||||||
domain: timeSystem.key
|
|
||||||
}, this.timeAPI.clock() !== undefined);
|
|
||||||
};
|
|
||||||
|
|
||||||
ConductorRepresenter.prototype.timeSystemListener = function (timeSystem) {
|
|
||||||
var bounds = this.timeAPI.bounds();
|
|
||||||
this.scope.$broadcast('telemetry:display:bounds', {
|
|
||||||
start: bounds.start,
|
|
||||||
end: bounds.end,
|
|
||||||
domain: timeSystem.key
|
|
||||||
}, this.timeAPI.clock() !== undefined);
|
|
||||||
};
|
|
||||||
|
|
||||||
ConductorRepresenter.prototype.followListener = function () {
|
|
||||||
this.boundsListener(this.timeAPI.bounds());
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handle a specific representation of a specific domain object
|
|
||||||
ConductorRepresenter.prototype.represent = function represent(representation) {
|
|
||||||
if (representation.key === 'browse-object') {
|
|
||||||
this.destroy();
|
|
||||||
|
|
||||||
this.timeAPI.on("bounds", this.boundsListener);
|
|
||||||
this.timeAPI.on("timeSystem", this.timeSystemListener);
|
|
||||||
this.timeAPI.on("follow", this.followListener);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ConductorRepresenter.prototype.destroy = function destroy() {
|
|
||||||
this.timeAPI.off("bounds", this.boundsListener);
|
|
||||||
this.timeAPI.off("timeSystem", this.timeSystemListener);
|
|
||||||
this.timeAPI.off("follow", this.followListener);
|
|
||||||
};
|
|
||||||
|
|
||||||
return ConductorRepresenter;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
@@ -1,148 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define([
|
|
||||||
"./src/ui/TimeConductorController",
|
|
||||||
"./src/ui/ConductorAxisController",
|
|
||||||
"./src/ui/ConductorTOIController",
|
|
||||||
"./src/ui/ConductorTOIDirective",
|
|
||||||
"./src/ui/TimeOfInterestController",
|
|
||||||
"./src/ui/ConductorAxisDirective",
|
|
||||||
"./src/ui/NumberFormat",
|
|
||||||
"./src/ui/StringFormat",
|
|
||||||
"./res/templates/time-conductor.html",
|
|
||||||
"./res/templates/mode-selector/mode-selector.html",
|
|
||||||
"./res/templates/mode-selector/mode-menu.html",
|
|
||||||
"./res/templates/time-of-interest.html",
|
|
||||||
"legacyRegistry"
|
|
||||||
], function (
|
|
||||||
TimeConductorController,
|
|
||||||
ConductorAxisController,
|
|
||||||
ConductorTOIController,
|
|
||||||
ConductorTOIDirective,
|
|
||||||
TimeOfInterestController,
|
|
||||||
ConductorAxisDirective,
|
|
||||||
NumberFormat,
|
|
||||||
StringFormat,
|
|
||||||
timeConductorTemplate,
|
|
||||||
modeSelectorTemplate,
|
|
||||||
modeMenuTemplate,
|
|
||||||
timeOfInterest,
|
|
||||||
legacyRegistry
|
|
||||||
) {
|
|
||||||
|
|
||||||
legacyRegistry.register("platform/features/conductor/core", {
|
|
||||||
"extensions": {
|
|
||||||
"controllers": [
|
|
||||||
{
|
|
||||||
"key": "TimeConductorController",
|
|
||||||
"implementation": TimeConductorController,
|
|
||||||
"depends": [
|
|
||||||
"$scope",
|
|
||||||
"$window",
|
|
||||||
"openmct",
|
|
||||||
"formatService",
|
|
||||||
"CONDUCTOR_CONFIG"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "ConductorTOIController",
|
|
||||||
"implementation": ConductorTOIController,
|
|
||||||
"depends": [
|
|
||||||
"$scope",
|
|
||||||
"openmct",
|
|
||||||
"formatService"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "TimeOfInterestController",
|
|
||||||
"implementation": TimeOfInterestController,
|
|
||||||
"depends": [
|
|
||||||
"$scope",
|
|
||||||
"openmct",
|
|
||||||
"formatService"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"directives": [
|
|
||||||
{
|
|
||||||
"key": "conductorAxis",
|
|
||||||
"implementation": ConductorAxisDirective,
|
|
||||||
"depends": [
|
|
||||||
"openmct",
|
|
||||||
"formatService"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "conductorToi",
|
|
||||||
"implementation": ConductorTOIDirective
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"templates": [
|
|
||||||
{
|
|
||||||
"key": "conductor",
|
|
||||||
"template": timeConductorTemplate
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "mode-menu",
|
|
||||||
"template": modeMenuTemplate
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "mode-selector",
|
|
||||||
"template": modeSelectorTemplate
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "time-of-interest",
|
|
||||||
"template": timeOfInterest
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"representations": [
|
|
||||||
{
|
|
||||||
"key": "time-conductor",
|
|
||||||
"template": timeConductorTemplate
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"licenses": [
|
|
||||||
{
|
|
||||||
"name": "D3: Data-Driven Documents",
|
|
||||||
"version": "4.1.0",
|
|
||||||
"author": "Mike Bostock",
|
|
||||||
"description": "D3 (or D3.js) is a JavaScript library for visualizing data using web standards. D3 helps you bring data to life using SVG, Canvas and HTML. D3 combines powerful visualization and interaction techniques with a data-driven approach to DOM manipulation, giving you the full capabilities of modern browsers and the freedom to design the right visual interface for your data.",
|
|
||||||
"website": "https://d3js.org/",
|
|
||||||
"copyright": "Copyright 2010-2016 Mike Bostock",
|
|
||||||
"license": "BSD-3-Clause",
|
|
||||||
"link": "https://github.com/d3/d3/blob/master/LICENSE"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"formats": [
|
|
||||||
{
|
|
||||||
"key": "number",
|
|
||||||
"implementation": NumberFormat
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "string",
|
|
||||||
"implementation": StringFormat
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
<!--
|
|
||||||
Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
Administration. All rights reserved.
|
|
||||||
|
|
||||||
Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
"License"); you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
License for the specific language governing permissions and limitations
|
|
||||||
under the License.
|
|
||||||
|
|
||||||
Open MCT Web includes source code licensed under additional open source
|
|
||||||
licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
this source code distribution or the Licensing information page available
|
|
||||||
at runtime from the About dialog for additional information.
|
|
||||||
-->
|
|
||||||
<div class="w-menu">
|
|
||||||
<div class="col menu-items">
|
|
||||||
<ul>
|
|
||||||
<li ng-repeat="metadata in ngModel.options"
|
|
||||||
ng-click="ngModel.select(metadata)">
|
|
||||||
<a ng-mouseover="ngModel.activeMetadata = metadata"
|
|
||||||
ng-mouseleave="ngModel.activeMetadata = undefined"
|
|
||||||
class="menu-item-a {{metadata.cssClass}}">
|
|
||||||
{{metadata.name}}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="col menu-item-description">
|
|
||||||
<div class="desc-area ui-symbol icon type-icon {{ngModel.activeMetadata.cssClass}}"></div>
|
|
||||||
<div class="w-title-desc">
|
|
||||||
<div class="desc-area title">
|
|
||||||
{{ngModel.activeMetadata.name}}
|
|
||||||
</div>
|
|
||||||
<div class="desc-area description">
|
|
||||||
{{ngModel.activeMetadata.description}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
<!--
|
|
||||||
Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
Administration. All rights reserved.
|
|
||||||
|
|
||||||
Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
"License"); you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
License for the specific language governing permissions and limitations
|
|
||||||
under the License.
|
|
||||||
|
|
||||||
Open MCT Web includes source code licensed under additional open source
|
|
||||||
licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
this source code distribution or the Licensing information page available
|
|
||||||
at runtime from the About dialog for additional information.
|
|
||||||
-->
|
|
||||||
<span ng-controller="ClickAwayController as modeController">
|
|
||||||
<div class="s-menu-button"
|
|
||||||
ng-click="modeController.toggle()">
|
|
||||||
<span class="title-label">{{ngModel.selected.name}}</span>
|
|
||||||
</div>
|
|
||||||
<div class="menu super-menu mini l-mode-selector-menu"
|
|
||||||
ng-show="modeController.isActive()">
|
|
||||||
<mct-include key="'mode-menu'"
|
|
||||||
ng-model="ngModel">
|
|
||||||
</mct-include>
|
|
||||||
</div>
|
|
||||||
</span>
|
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
<!-- Parent holder for time conductor. follow-mode | fixed-mode -->
|
|
||||||
<div ng-controller="TimeConductorController as tcController"
|
|
||||||
class="holder grows flex-elem l-flex-row l-time-conductor {{tcController.isFixed ? 'fixed-mode' : 'realtime-mode'}} {{timeSystemModel.selected.metadata.key}}-time-system"
|
|
||||||
ng-class="{'status-panning': tcController.panning}">
|
|
||||||
<div class="flex-elem holder time-conductor-icon">
|
|
||||||
<div class="hand-little"></div>
|
|
||||||
<div class="hand-big"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex-elem holder grows l-flex-col l-time-conductor-inner">
|
|
||||||
<!-- Holds inputs and ticks -->
|
|
||||||
<div class="l-time-conductor-inputs-and-ticks l-row-elem flex-elem no-margin">
|
|
||||||
<form class="l-time-conductor-inputs-holder"
|
|
||||||
ng-submit="tcController.isFixed ? tcController.setBoundsFromView(boundsModel) : tcController.setOffsetsFromView(boundsModel)">
|
|
||||||
<span class="l-time-range-w start-w">
|
|
||||||
<span class="l-time-conductor-inputs">
|
|
||||||
<span class="l-time-range-input-w start-date">
|
|
||||||
<span class="title"></span>
|
|
||||||
<mct-control key="'datetime-field'"
|
|
||||||
structure="{
|
|
||||||
format: timeSystemModel.format,
|
|
||||||
validate: tcController.validation.validateStart
|
|
||||||
}"
|
|
||||||
ng-model="boundsModel"
|
|
||||||
ng-blur="tcController.setBoundsFromView(boundsModel)"
|
|
||||||
field="'start'"
|
|
||||||
class="time-range-input">
|
|
||||||
</mct-control>
|
|
||||||
</span>
|
|
||||||
<span class="l-time-range-input-w time-delta start-delta"
|
|
||||||
ng-class="{'hide':tcController.isFixed}">
|
|
||||||
-
|
|
||||||
<mct-control key="'datetime-field'"
|
|
||||||
structure="{
|
|
||||||
format: timeSystemModel.durationFormat,
|
|
||||||
validate: tcController.validation.validateStartOffset
|
|
||||||
}"
|
|
||||||
ng-model="boundsModel"
|
|
||||||
ng-blur="tcController.setOffsetsFromView(boundsModel)"
|
|
||||||
field="'startOffset'"
|
|
||||||
class="s-input-inline hrs-min-input">
|
|
||||||
</mct-control>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
<span class="l-time-range-w end-w">
|
|
||||||
<span class="l-time-conductor-inputs">
|
|
||||||
<span class="l-time-range-input-w end-date"
|
|
||||||
ng-controller="ToggleController as t2">
|
|
||||||
<span class="title"></span>
|
|
||||||
<mct-control key="'datetime-field'"
|
|
||||||
structure="{
|
|
||||||
format: timeSystemModel.format,
|
|
||||||
validate: tcController.validation.validateEnd
|
|
||||||
}"
|
|
||||||
ng-model="boundsModel"
|
|
||||||
ng-blur="tcController.setBoundsFromView(boundsModel)"
|
|
||||||
ng-disabled="!tcController.isFixed"
|
|
||||||
field="'end'"
|
|
||||||
class="time-range-input">
|
|
||||||
</mct-control>
|
|
||||||
</span>
|
|
||||||
<span class="l-time-range-input-w time-delta end-delta"
|
|
||||||
ng-class="{'hide': tcController.isFixed}">
|
|
||||||
+
|
|
||||||
<mct-control key="'datetime-field'"
|
|
||||||
structure="{
|
|
||||||
format: timeSystemModel.durationFormat,
|
|
||||||
validate: tcController.validation.validateEndOffset
|
|
||||||
}"
|
|
||||||
ng-model="boundsModel"
|
|
||||||
ng-blur="tcController.setOffsetsFromView(boundsModel)"
|
|
||||||
field="'endOffset'"
|
|
||||||
class="s-input-inline hrs-min-input">
|
|
||||||
</mct-control>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<input type="submit" class="invisible">
|
|
||||||
</form>
|
|
||||||
<conductor-axis class="mobile-hide" view-service="tcController.conductorViewService"></conductor-axis>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Holds time system and session selectors, and zoom control -->
|
|
||||||
<div class="l-time-conductor-controls l-row-elem l-flex-row flex-elem">
|
|
||||||
<mct-include
|
|
||||||
key="'mode-selector'"
|
|
||||||
ng-model="tcController.menu"
|
|
||||||
class="holder flex-elem menus-up mode-selector">
|
|
||||||
</mct-include>
|
|
||||||
<mct-control
|
|
||||||
key="'menu-button'"
|
|
||||||
class="holder flex-elem menus-up time-system"
|
|
||||||
structure="{
|
|
||||||
text: timeSystemModel.selected.name,
|
|
||||||
click: tcController.setTimeSystemFromView,
|
|
||||||
options: tcController.timeSystemsForClocks[tcController.menu.selected.key]
|
|
||||||
}">
|
|
||||||
</mct-control>
|
|
||||||
<!-- Zoom control -->
|
|
||||||
<div ng-if="tcController.zoom"
|
|
||||||
class="l-time-conductor-zoom-w grows flex-elem l-flex-row">
|
|
||||||
{{currentZoom}}
|
|
||||||
<span class="time-conductor-zoom-current-range flex-elem flex-fixed holder">{{timeUnits}}</span>
|
|
||||||
<input class="time-conductor-zoom flex-elem" type="range"
|
|
||||||
ng-model="tcController.currentZoom"
|
|
||||||
ng-mouseUp="tcController.onZoomStop(tcController.currentZoom)"
|
|
||||||
ng-change="tcController.onZoom(tcController.currentZoom)"
|
|
||||||
min="0.01"
|
|
||||||
step="0.01"
|
|
||||||
max="0.99" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
<div class="abs angular-controller"
|
|
||||||
ng-controller="TimeOfInterestController as toi">
|
|
||||||
<div class="l-flex-row l-toi">
|
|
||||||
<span class="flex-elem l-flex-row l-toi-buttons">
|
|
||||||
<a class="flex-elem t-button-resync icon-button" title="Re-sync Time of Interest"
|
|
||||||
ng-click="toi.resync()"></a>
|
|
||||||
<a class="flex-elem t-button-unpin icon-button" title="Unset Time of Interest"
|
|
||||||
ng-click="toi.dismiss()"></a>
|
|
||||||
</span>
|
|
||||||
<span class="flex-elem l-toi-val">{{toi.toiText}}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,236 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
[
|
|
||||||
"d3-selection",
|
|
||||||
"d3-scale",
|
|
||||||
"d3-axis"
|
|
||||||
],
|
|
||||||
function (d3Selection, d3Scale, d3Axis) {
|
|
||||||
var PADDING = 1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Controller that renders a horizontal time scale spanning the current bounds defined in the time conductor.
|
|
||||||
* Used by the mct-conductor-axis directive
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function ConductorAxisController(openmct, formatService, scope, element) {
|
|
||||||
// Dependencies
|
|
||||||
this.formatService = formatService;
|
|
||||||
this.timeAPI = openmct.time;
|
|
||||||
|
|
||||||
this.scope = scope;
|
|
||||||
|
|
||||||
this.bounds = this.timeAPI.bounds();
|
|
||||||
|
|
||||||
//Bind all class functions to 'this'
|
|
||||||
Object.keys(ConductorAxisController.prototype).filter(function (key) {
|
|
||||||
return typeof ConductorAxisController.prototype[key] === 'function';
|
|
||||||
}).forEach(function (key) {
|
|
||||||
this[key] = ConductorAxisController.prototype[key].bind(this);
|
|
||||||
}.bind(this));
|
|
||||||
|
|
||||||
this.initialize(element);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
ConductorAxisController.prototype.destroy = function () {
|
|
||||||
this.timeAPI.off('timeSystem', this.changeTimeSystem);
|
|
||||||
this.timeAPI.off('bounds', this.changeBounds);
|
|
||||||
this.viewService.off("zoom", this.onZoom);
|
|
||||||
this.viewService.off("zoom-stop", this.onZoomStop);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
ConductorAxisController.prototype.initialize = function (element) {
|
|
||||||
this.target = element[0].firstChild;
|
|
||||||
var height = this.target.offsetHeight;
|
|
||||||
var vis = d3Selection.select(this.target)
|
|
||||||
.append("svg:svg")
|
|
||||||
.attr("width", "100%")
|
|
||||||
.attr("height", height);
|
|
||||||
|
|
||||||
this.xAxis = d3Axis.axisTop();
|
|
||||||
|
|
||||||
// draw x axis with labels. CSS is used to position them.
|
|
||||||
this.axisElement = vis.append("g");
|
|
||||||
|
|
||||||
if (this.timeAPI.timeSystem() !== undefined) {
|
|
||||||
this.changeTimeSystem(this.timeAPI.timeSystem());
|
|
||||||
this.setScale();
|
|
||||||
}
|
|
||||||
|
|
||||||
//Respond to changes in conductor
|
|
||||||
this.timeAPI.on("timeSystem", this.changeTimeSystem);
|
|
||||||
this.timeAPI.on("bounds", this.changeBounds);
|
|
||||||
|
|
||||||
this.scope.$on("$destroy", this.destroy);
|
|
||||||
|
|
||||||
this.viewService.on("zoom", this.onZoom);
|
|
||||||
this.viewService.on("zoom-stop", this.onZoomStop);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
ConductorAxisController.prototype.changeBounds = function (bounds) {
|
|
||||||
this.bounds = bounds;
|
|
||||||
if (!this.zooming) {
|
|
||||||
this.setScale();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the scale of the axis, based on current conductor bounds.
|
|
||||||
*/
|
|
||||||
ConductorAxisController.prototype.setScale = function () {
|
|
||||||
var width = this.target.offsetWidth;
|
|
||||||
var timeSystem = this.timeAPI.timeSystem();
|
|
||||||
var bounds = this.bounds;
|
|
||||||
|
|
||||||
if (timeSystem.isUTCBased) {
|
|
||||||
this.xScale = this.xScale || d3Scale.scaleUtc();
|
|
||||||
this.xScale.domain([new Date(bounds.start), new Date(bounds.end)]);
|
|
||||||
} else {
|
|
||||||
this.xScale = this.xScale || d3Scale.scaleLinear();
|
|
||||||
this.xScale.domain([bounds.start, bounds.end]);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.xAxis.scale(this.xScale);
|
|
||||||
|
|
||||||
this.xScale.range([PADDING, width - PADDING * 2]);
|
|
||||||
this.axisElement.call(this.xAxis);
|
|
||||||
|
|
||||||
this.msPerPixel = (bounds.end - bounds.start) / width;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When the time system changes, update the scale and formatter used for showing times.
|
|
||||||
* @param timeSystem
|
|
||||||
*/
|
|
||||||
ConductorAxisController.prototype.changeTimeSystem = function (timeSystem) {
|
|
||||||
var key = timeSystem.timeFormat;
|
|
||||||
if (key !== undefined) {
|
|
||||||
var format = this.formatService.getFormat(key);
|
|
||||||
var bounds = this.timeAPI.bounds();
|
|
||||||
|
|
||||||
//The D3 scale used depends on the type of time system as d3
|
|
||||||
// supports UTC out of the box.
|
|
||||||
if (timeSystem.isUTCBased) {
|
|
||||||
this.xScale = d3Scale.scaleUtc();
|
|
||||||
} else {
|
|
||||||
this.xScale = d3Scale.scaleLinear();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.xAxis.scale(this.xScale);
|
|
||||||
|
|
||||||
//Define a custom format function
|
|
||||||
this.xAxis.tickFormat(function (tickValue) {
|
|
||||||
// Normalize date representations to numbers
|
|
||||||
if (tickValue instanceof Date) {
|
|
||||||
tickValue = tickValue.getTime();
|
|
||||||
}
|
|
||||||
return format.format(tickValue, {
|
|
||||||
min: bounds.start,
|
|
||||||
max: bounds.end
|
|
||||||
});
|
|
||||||
});
|
|
||||||
this.axisElement.call(this.xAxis);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The user has stopped panning the time conductor scale element.
|
|
||||||
* @event panStop
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Called on release of mouse button after dragging the scale left or right.
|
|
||||||
* @fires platform.features.conductor.ConductorAxisController~panStop
|
|
||||||
*/
|
|
||||||
ConductorAxisController.prototype.panStop = function () {
|
|
||||||
//resync view bounds with time conductor bounds
|
|
||||||
this.viewService.emit("pan-stop");
|
|
||||||
this.timeAPI.bounds(this.bounds);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rescales the axis when the user zooms. Although zoom ultimately results in a bounds change once the user
|
|
||||||
* releases the zoom slider, dragging the slider will not immediately change the conductor bounds. It will
|
|
||||||
* however immediately update the scale and the bounds displayed in the UI.
|
|
||||||
* @private
|
|
||||||
* @param {ZoomLevel}
|
|
||||||
*/
|
|
||||||
ConductorAxisController.prototype.onZoom = function (zoom) {
|
|
||||||
this.zooming = true;
|
|
||||||
|
|
||||||
this.bounds = zoom.bounds;
|
|
||||||
this.setScale();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
ConductorAxisController.prototype.onZoomStop = function (zoom) {
|
|
||||||
this.zooming = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @event platform.features.conductor.ConductorAxisController~pan
|
|
||||||
* Fired when the time conductor is panned
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Initiate panning via a click + drag gesture on the time conductor
|
|
||||||
* scale. Panning triggers a "pan" event
|
|
||||||
* @param {number} delta the offset from the original click event
|
|
||||||
* @see TimeConductorViewService#
|
|
||||||
* @fires platform.features.conductor.ConductorAxisController~pan
|
|
||||||
*/
|
|
||||||
ConductorAxisController.prototype.pan = function (delta) {
|
|
||||||
if (this.timeAPI.clock() === undefined) {
|
|
||||||
var deltaInMs = delta[0] * this.msPerPixel;
|
|
||||||
var bounds = this.timeAPI.bounds();
|
|
||||||
var start = Math.floor((bounds.start - deltaInMs) / 1000) * 1000;
|
|
||||||
var end = Math.floor((bounds.end - deltaInMs) / 1000) * 1000;
|
|
||||||
this.bounds = {
|
|
||||||
start: start,
|
|
||||||
end: end
|
|
||||||
};
|
|
||||||
this.setScale();
|
|
||||||
this.viewService.emit("pan", this.bounds);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoked on element resize. Will rebuild the scale based on the new dimensions of the element.
|
|
||||||
*/
|
|
||||||
ConductorAxisController.prototype.resize = function () {
|
|
||||||
this.setScale();
|
|
||||||
};
|
|
||||||
|
|
||||||
return ConductorAxisController;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
@@ -1,169 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define([
|
|
||||||
'./ConductorAxisController',
|
|
||||||
'zepto',
|
|
||||||
'd3-selection',
|
|
||||||
'd3-scale'
|
|
||||||
], function (
|
|
||||||
ConductorAxisController,
|
|
||||||
$,
|
|
||||||
d3Selection,
|
|
||||||
d3Scale
|
|
||||||
) {
|
|
||||||
describe("The ConductorAxisController", function () {
|
|
||||||
var controller,
|
|
||||||
mockConductor,
|
|
||||||
mockConductorViewService,
|
|
||||||
mockFormatService,
|
|
||||||
mockScope,
|
|
||||||
mockBounds,
|
|
||||||
element,
|
|
||||||
mockTimeSystem,
|
|
||||||
mockFormat;
|
|
||||||
|
|
||||||
function getCallback(target, name) {
|
|
||||||
return target.calls.all().filter(function (call) {
|
|
||||||
return call.args[0] === name;
|
|
||||||
})[0].args[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
mockScope = jasmine.createSpyObj("scope", [
|
|
||||||
"$on"
|
|
||||||
]);
|
|
||||||
|
|
||||||
//Add some HTML elements
|
|
||||||
mockBounds = {
|
|
||||||
start: 100,
|
|
||||||
end: 200
|
|
||||||
};
|
|
||||||
mockConductor = jasmine.createSpyObj("conductor", [
|
|
||||||
"timeSystem",
|
|
||||||
"bounds",
|
|
||||||
"on",
|
|
||||||
"off",
|
|
||||||
"clock"
|
|
||||||
]);
|
|
||||||
mockConductor.bounds.and.returnValue(mockBounds);
|
|
||||||
|
|
||||||
mockFormatService = jasmine.createSpyObj("formatService", [
|
|
||||||
"getFormat"
|
|
||||||
]);
|
|
||||||
|
|
||||||
mockConductorViewService = jasmine.createSpyObj("conductorViewService", [
|
|
||||||
"on",
|
|
||||||
"off",
|
|
||||||
"emit"
|
|
||||||
]);
|
|
||||||
|
|
||||||
spyOn(d3Scale, 'scaleUtc').and.callThrough();
|
|
||||||
spyOn(d3Scale, 'scaleLinear').and.callThrough();
|
|
||||||
|
|
||||||
element = $('<div style="width: 100px;"><div style="width: 100%;"></div></div>');
|
|
||||||
$(document).find('body').append(element);
|
|
||||||
ConductorAxisController.prototype.viewService = mockConductorViewService;
|
|
||||||
controller = new ConductorAxisController({time: mockConductor}, mockFormatService, mockScope, element);
|
|
||||||
|
|
||||||
mockTimeSystem = {};
|
|
||||||
mockFormat = jasmine.createSpyObj("format", [
|
|
||||||
"format"
|
|
||||||
]);
|
|
||||||
|
|
||||||
mockTimeSystem.timeFormat = "mockFormat";
|
|
||||||
mockFormatService.getFormat.and.returnValue(mockFormat);
|
|
||||||
mockConductor.timeSystem.and.returnValue(mockTimeSystem);
|
|
||||||
mockTimeSystem.isUTCBased = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
it("listens for changes to time system and bounds", function () {
|
|
||||||
expect(mockConductor.on).toHaveBeenCalledWith("timeSystem", controller.changeTimeSystem);
|
|
||||||
expect(mockConductor.on).toHaveBeenCalledWith("bounds", controller.changeBounds);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("on scope destruction, deregisters listeners", function () {
|
|
||||||
expect(mockScope.$on).toHaveBeenCalledWith("$destroy", controller.destroy);
|
|
||||||
controller.destroy();
|
|
||||||
expect(mockConductor.off).toHaveBeenCalledWith("timeSystem", controller.changeTimeSystem);
|
|
||||||
expect(mockConductor.off).toHaveBeenCalledWith("bounds", controller.changeBounds);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when the time system changes", function () {
|
|
||||||
it("uses a UTC scale for UTC time systems", function () {
|
|
||||||
mockTimeSystem.isUTCBased = true;
|
|
||||||
controller.changeTimeSystem(mockTimeSystem);
|
|
||||||
|
|
||||||
expect(d3Scale.scaleUtc).toHaveBeenCalled();
|
|
||||||
expect(d3Scale.scaleLinear).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("uses a linear scale for non-UTC time systems", function () {
|
|
||||||
mockTimeSystem.isUTCBased = false;
|
|
||||||
controller.changeTimeSystem(mockTimeSystem);
|
|
||||||
expect(d3Scale.scaleLinear).toHaveBeenCalled();
|
|
||||||
expect(d3Scale.scaleUtc).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("sets axis domain to time conductor bounds", function () {
|
|
||||||
mockTimeSystem.isUTCBased = false;
|
|
||||||
controller.setScale();
|
|
||||||
expect(controller.xScale.domain()).toEqual([mockBounds.start, mockBounds.end]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("uses the format specified by the time system to format tick" +
|
|
||||||
" labels", function () {
|
|
||||||
controller.changeTimeSystem(mockTimeSystem);
|
|
||||||
expect(mockFormat.format).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('responds to zoom events', function () {
|
|
||||||
expect(mockConductorViewService.on).toHaveBeenCalledWith("zoom", controller.onZoom);
|
|
||||||
var cb = getCallback(mockConductorViewService.on, "zoom");
|
|
||||||
spyOn(controller, 'setScale').and.callThrough();
|
|
||||||
cb({bounds: {start: 0, end: 100}});
|
|
||||||
expect(controller.setScale).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('adjusts scale on pan', function () {
|
|
||||||
spyOn(controller, 'setScale').and.callThrough();
|
|
||||||
controller.pan(100);
|
|
||||||
expect(controller.setScale).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('emits event on pan', function () {
|
|
||||||
spyOn(controller, 'setScale').and.callThrough();
|
|
||||||
controller.pan(100);
|
|
||||||
expect(mockConductorViewService.emit).toHaveBeenCalledWith("pan", jasmine.any(Object));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('cleans up listeners on destruction', function () {
|
|
||||||
controller.destroy();
|
|
||||||
expect(mockConductor.off).toHaveBeenCalledWith("bounds", controller.changeBounds);
|
|
||||||
expect(mockConductor.off).toHaveBeenCalledWith("timeSystem", controller.changeTimeSystem);
|
|
||||||
|
|
||||||
expect(mockConductorViewService.off).toHaveBeenCalledWith("zoom", controller.onZoom);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(['./ConductorAxisController'], function (ConductorAxisController) {
|
|
||||||
function ConductorAxisDirective() {
|
|
||||||
/**
|
|
||||||
* The mct-conductor-axis renders a horizontal axis with regular
|
|
||||||
* labelled 'ticks'. It requires 'start' and 'end' integer values to
|
|
||||||
* be specified as attributes.
|
|
||||||
*/
|
|
||||||
return {
|
|
||||||
controller: [
|
|
||||||
'openmct',
|
|
||||||
'formatService',
|
|
||||||
'$scope',
|
|
||||||
'$element',
|
|
||||||
ConductorAxisController
|
|
||||||
],
|
|
||||||
controllerAs: 'axis',
|
|
||||||
scope: {
|
|
||||||
viewService: "="
|
|
||||||
},
|
|
||||||
bindToController: true,
|
|
||||||
|
|
||||||
restrict: 'E',
|
|
||||||
priority: 1000,
|
|
||||||
|
|
||||||
template: '<div class="l-axis-holder" ' +
|
|
||||||
' mct-drag-down="axis.panStart()"' +
|
|
||||||
' mct-drag-up="axis.panStop(delta)"' +
|
|
||||||
' mct-drag="axis.pan(delta)"' +
|
|
||||||
' mct-resize="axis.resize()"></div>'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return ConductorAxisDirective;
|
|
||||||
});
|
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
["zepto"],
|
|
||||||
function ($) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Controller for the Time of Interest indicator in the conductor itself. Sets the horizontal position of the
|
|
||||||
* TOI indicator based on the current value of the TOI, and the width of the TOI conductor.
|
|
||||||
* @memberof platform.features.conductor
|
|
||||||
*/
|
|
||||||
function ConductorTOIController($scope, openmct) {
|
|
||||||
this.timeAPI = openmct.time;
|
|
||||||
|
|
||||||
//Bind all class functions to 'this'
|
|
||||||
Object.keys(ConductorTOIController.prototype).filter(function (key) {
|
|
||||||
return typeof ConductorTOIController.prototype[key] === 'function';
|
|
||||||
}).forEach(function (key) {
|
|
||||||
this[key] = ConductorTOIController.prototype[key].bind(this);
|
|
||||||
}.bind(this));
|
|
||||||
|
|
||||||
this.timeAPI.on('timeOfInterest', this.changeTimeOfInterest);
|
|
||||||
this.viewService.on('zoom', this.setOffsetFromZoom);
|
|
||||||
this.viewService.on('pan', this.setOffsetFromBounds);
|
|
||||||
|
|
||||||
var timeOfInterest = this.timeAPI.timeOfInterest();
|
|
||||||
if (timeOfInterest) {
|
|
||||||
this.changeTimeOfInterest(timeOfInterest);
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.$on('$destroy', this.destroy);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
ConductorTOIController.prototype.destroy = function () {
|
|
||||||
this.timeAPI.off('timeOfInterest', this.changeTimeOfInterest);
|
|
||||||
this.viewService.off('zoom', this.setOffsetFromZoom);
|
|
||||||
this.viewService.off('pan', this.setOffsetFromBounds);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given some bounds, set horizontal position of TOI indicator based
|
|
||||||
* on current conductor TOI value. Bounds are provided so that
|
|
||||||
* ephemeral bounds from zoom and pan events can be used as well
|
|
||||||
* as current conductor bounds, allowing TOI to be updated in
|
|
||||||
* realtime during scroll and zoom.
|
|
||||||
* @param {TimeConductorBounds} bounds
|
|
||||||
*/
|
|
||||||
ConductorTOIController.prototype.setOffsetFromBounds = function (bounds) {
|
|
||||||
var toi = this.timeAPI.timeOfInterest();
|
|
||||||
if (toi !== undefined) {
|
|
||||||
var offset = toi - bounds.start;
|
|
||||||
var duration = bounds.end - bounds.start;
|
|
||||||
this.left = offset / duration * 100;
|
|
||||||
this.pinned = true;
|
|
||||||
} else {
|
|
||||||
this.left = 0;
|
|
||||||
this.pinned = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
ConductorTOIController.prototype.setOffsetFromZoom = function (zoom) {
|
|
||||||
return this.setOffsetFromBounds(zoom.bounds);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoked when time of interest changes. Will set the horizontal offset of the TOI indicator.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
ConductorTOIController.prototype.changeTimeOfInterest = function () {
|
|
||||||
var bounds = this.timeAPI.bounds();
|
|
||||||
if (bounds) {
|
|
||||||
this.setOffsetFromBounds(bounds);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On a mouse click event within the TOI element, convert position within element to a time of interest, and
|
|
||||||
* set the time of interest on the conductor.
|
|
||||||
* @param e The angular $event object
|
|
||||||
*/
|
|
||||||
ConductorTOIController.prototype.setTOIFromPosition = function (e) {
|
|
||||||
//TOI is set using the alt key modified + primary click
|
|
||||||
if (e.altKey) {
|
|
||||||
var element = $(e.currentTarget);
|
|
||||||
var width = element.width();
|
|
||||||
var relativeX = e.pageX - element.offset().left;
|
|
||||||
var percX = relativeX / width;
|
|
||||||
var bounds = this.timeAPI.bounds();
|
|
||||||
var timeRange = bounds.end - bounds.start;
|
|
||||||
|
|
||||||
this.timeAPI.timeOfInterest(timeRange * percX + bounds.start);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return ConductorTOIController;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
@@ -1,153 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define([
|
|
||||||
'./ConductorTOIController'
|
|
||||||
], function (
|
|
||||||
ConductorTOIController
|
|
||||||
) {
|
|
||||||
var mockConductor;
|
|
||||||
var mockConductorViewService;
|
|
||||||
var mockScope;
|
|
||||||
var mockAPI;
|
|
||||||
var conductorTOIController;
|
|
||||||
|
|
||||||
function getNamedCallback(thing, name) {
|
|
||||||
return thing.calls.all().filter(function (call) {
|
|
||||||
return call.args[0] === name;
|
|
||||||
}).map(function (call) {
|
|
||||||
return call.args;
|
|
||||||
})[0][1];
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("The ConductorTOIController", function () {
|
|
||||||
beforeEach(function () {
|
|
||||||
mockConductor = jasmine.createSpyObj("conductor", [
|
|
||||||
"bounds",
|
|
||||||
"timeOfInterest",
|
|
||||||
"on",
|
|
||||||
"off"
|
|
||||||
]);
|
|
||||||
mockAPI = {time: mockConductor};
|
|
||||||
|
|
||||||
mockConductorViewService = jasmine.createSpyObj("conductorViewService", [
|
|
||||||
"on",
|
|
||||||
"off"
|
|
||||||
]);
|
|
||||||
|
|
||||||
mockScope = jasmine.createSpyObj("openMCT", [
|
|
||||||
"$on"
|
|
||||||
]);
|
|
||||||
ConductorTOIController.prototype.viewService = mockConductorViewService;
|
|
||||||
conductorTOIController = new ConductorTOIController(mockScope, mockAPI);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("listens to changes in the time of interest on the conductor", function () {
|
|
||||||
expect(mockConductor.on).toHaveBeenCalledWith("timeOfInterest", jasmine.any(Function));
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when responding to changes in the time of interest", function () {
|
|
||||||
var toiCallback;
|
|
||||||
beforeEach(function () {
|
|
||||||
var bounds = {
|
|
||||||
start: 0,
|
|
||||||
end: 200
|
|
||||||
};
|
|
||||||
mockConductor.bounds.and.returnValue(bounds);
|
|
||||||
toiCallback = getNamedCallback(mockConductor.on, "timeOfInterest");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("calculates the correct horizontal offset based on bounds and current TOI", function () {
|
|
||||||
//Expect time of interest position to be 50% of element width
|
|
||||||
mockConductor.timeOfInterest.and.returnValue(100);
|
|
||||||
toiCallback();
|
|
||||||
expect(conductorTOIController.left).toBe(50);
|
|
||||||
|
|
||||||
//Expect time of interest position to be 25% of element width
|
|
||||||
mockConductor.timeOfInterest.and.returnValue(50);
|
|
||||||
toiCallback();
|
|
||||||
expect(conductorTOIController.left).toBe(25);
|
|
||||||
|
|
||||||
//Expect time of interest position to be 0% of element width
|
|
||||||
mockConductor.timeOfInterest.and.returnValue(0);
|
|
||||||
toiCallback();
|
|
||||||
expect(conductorTOIController.left).toBe(0);
|
|
||||||
|
|
||||||
//Expect time of interest position to be 100% of element width
|
|
||||||
mockConductor.timeOfInterest.and.returnValue(200);
|
|
||||||
toiCallback();
|
|
||||||
expect(conductorTOIController.left).toBe(100);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("renders the TOI indicator visible", function () {
|
|
||||||
expect(conductorTOIController.pinned).toBeFalsy();
|
|
||||||
mockConductor.timeOfInterest.and.returnValue(100);
|
|
||||||
toiCallback();
|
|
||||||
expect(conductorTOIController.pinned).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("responds to zoom events", function () {
|
|
||||||
var mockZoom = {
|
|
||||||
bounds: {
|
|
||||||
start: 500,
|
|
||||||
end: 1000
|
|
||||||
}
|
|
||||||
};
|
|
||||||
expect(mockConductorViewService.on).toHaveBeenCalledWith("zoom", jasmine.any(Function));
|
|
||||||
|
|
||||||
// Should correspond to horizontal offset of 50%
|
|
||||||
mockConductor.timeOfInterest.and.returnValue(750);
|
|
||||||
var zoomCallback = getNamedCallback(mockConductorViewService.on, "zoom");
|
|
||||||
zoomCallback(mockZoom);
|
|
||||||
expect(conductorTOIController.left).toBe(50);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("responds to pan events", function () {
|
|
||||||
var mockPanBounds = {
|
|
||||||
start: 1000,
|
|
||||||
end: 3000
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(mockConductorViewService.on).toHaveBeenCalledWith("pan", jasmine.any(Function));
|
|
||||||
|
|
||||||
// Should correspond to horizontal offset of 25%
|
|
||||||
mockConductor.timeOfInterest.and.returnValue(1500);
|
|
||||||
var panCallback = getNamedCallback(mockConductorViewService.on, "pan");
|
|
||||||
panCallback(mockPanBounds);
|
|
||||||
expect(conductorTOIController.left).toBe(25);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it("Cleans up all listeners when controller destroyed", function () {
|
|
||||||
var zoomCB = getNamedCallback(mockConductorViewService.on, "zoom");
|
|
||||||
var panCB = getNamedCallback(mockConductorViewService.on, "pan");
|
|
||||||
var toiCB = getNamedCallback(mockConductor.on, "timeOfInterest");
|
|
||||||
|
|
||||||
expect(mockScope.$on).toHaveBeenCalledWith("$destroy", jasmine.any(Function));
|
|
||||||
getNamedCallback(mockScope.$on, "$destroy")();
|
|
||||||
expect(mockConductorViewService.off).toHaveBeenCalledWith("zoom", zoomCB);
|
|
||||||
expect(mockConductorViewService.off).toHaveBeenCalledWith("pan", panCB);
|
|
||||||
expect(mockConductor.off).toHaveBeenCalledWith("timeOfInterest", toiCB);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(['./ConductorTOIController'], function (ConductorTOIController) {
|
|
||||||
/**
|
|
||||||
* A directive that encapsulates the TOI specific behavior of the Time Conductor UI.
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function ConductorTOIDirective() {
|
|
||||||
/**
|
|
||||||
* The mct-conductor-axis renders a horizontal axis with regular
|
|
||||||
* labelled 'ticks'. It requires 'start' and 'end' integer values to
|
|
||||||
* be specified as attributes.
|
|
||||||
*/
|
|
||||||
return {
|
|
||||||
controller: [
|
|
||||||
'$scope',
|
|
||||||
'openmct',
|
|
||||||
ConductorTOIController
|
|
||||||
],
|
|
||||||
controllerAs: 'toi',
|
|
||||||
scope: {
|
|
||||||
viewService: "="
|
|
||||||
},
|
|
||||||
bindToController: true,
|
|
||||||
|
|
||||||
restrict: 'E',
|
|
||||||
priority: 1000,
|
|
||||||
|
|
||||||
template:
|
|
||||||
'<div class="l-data-visualization-holder l-row-elem flex-elem">' +
|
|
||||||
' <a class="l-page-button s-icon-button icon-pointer-left"></a>' +
|
|
||||||
' <div class="l-data-visualization" ng-click="toi.setTOIFromPosition($event)">' +
|
|
||||||
' <mct-include key="\'time-of-interest\'" class="l-toi-holder show-val" ' +
|
|
||||||
' ng-class="{ pinned: toi.pinned, \'val-to-left\': toi.left > 80 }" ' +
|
|
||||||
' ng-style="{\'left\': toi.left + \'%\'}"></mct-include>' +
|
|
||||||
' </div>' +
|
|
||||||
' <a class="l-page-button align-right s-icon-button icon-pointer-right"></a>' +
|
|
||||||
'</div>'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return ConductorTOIDirective;
|
|
||||||
});
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(['./NumberFormat'], function (NumberFormat) {
|
|
||||||
describe("The NumberFormat class", function () {
|
|
||||||
var format;
|
|
||||||
beforeEach(function () {
|
|
||||||
format = new NumberFormat();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("The format function takes a string and produces a number", function () {
|
|
||||||
var text = format.format(1);
|
|
||||||
expect(text).toBe("1");
|
|
||||||
expect(typeof text).toBe("string");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("The parse function takes a string and produces a number", function () {
|
|
||||||
var number = format.parse("1");
|
|
||||||
expect(number).toBe(1);
|
|
||||||
expect(typeof number).toBe("number");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("validates that the input is a number", function () {
|
|
||||||
expect(format.validate("1")).toBe(true);
|
|
||||||
expect(format.validate(1)).toBe(true);
|
|
||||||
expect(format.validate("1.1")).toBe(true);
|
|
||||||
expect(format.validate("abc")).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,554 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
[
|
|
||||||
'moment',
|
|
||||||
'./TimeConductorValidation',
|
|
||||||
'./TimeConductorViewService'
|
|
||||||
],
|
|
||||||
function (
|
|
||||||
moment,
|
|
||||||
TimeConductorValidation,
|
|
||||||
TimeConductorViewService
|
|
||||||
) {
|
|
||||||
|
|
||||||
var timeUnitsMegastructure = [
|
|
||||||
["Decades", function (r) {
|
|
||||||
return r.years() > 15;
|
|
||||||
}],
|
|
||||||
["Years", function (r) {
|
|
||||||
return r.years() > 1;
|
|
||||||
}],
|
|
||||||
["Months", function (r) {
|
|
||||||
return r.years() === 1 || r.months() > 1;
|
|
||||||
}],
|
|
||||||
["Days", function (r) {
|
|
||||||
return r.months() === 1 || r.days() > 1;
|
|
||||||
}],
|
|
||||||
["Hours", function (r) {
|
|
||||||
return r.days() === 1 || r.hours() > 1;
|
|
||||||
}],
|
|
||||||
["Minutes", function (r) {
|
|
||||||
return r.hours() === 1 || r.minutes() > 1;
|
|
||||||
}],
|
|
||||||
["Seconds", function (r) {
|
|
||||||
return r.minutes() === 1 || r.seconds() > 1;
|
|
||||||
}],
|
|
||||||
["Milliseconds", function (r) {
|
|
||||||
return true;
|
|
||||||
}]
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Controller for the Time Conductor UI element. The Time Conductor
|
|
||||||
* includes form fields for specifying time bounds and relative time
|
|
||||||
* offsets for queries, as well as controls for selection mode,
|
|
||||||
* time systems, and zooming.
|
|
||||||
* @memberof platform.features.conductor
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function TimeConductorController(
|
|
||||||
$scope,
|
|
||||||
$window,
|
|
||||||
openmct,
|
|
||||||
formatService,
|
|
||||||
config
|
|
||||||
) {
|
|
||||||
|
|
||||||
//Bind functions that are used as callbacks to 'this'.
|
|
||||||
[
|
|
||||||
"selectMenuOption",
|
|
||||||
"onPan",
|
|
||||||
"onPanStop",
|
|
||||||
"setViewFromBounds",
|
|
||||||
"setViewFromClock",
|
|
||||||
"setViewFromOffsets",
|
|
||||||
"setViewFromTimeSystem",
|
|
||||||
"setTimeSystemFromView",
|
|
||||||
"destroy"
|
|
||||||
].forEach(function (name) {
|
|
||||||
this[name] = this[name].bind(this);
|
|
||||||
}.bind(this));
|
|
||||||
|
|
||||||
this.$scope = $scope;
|
|
||||||
this.$window = $window;
|
|
||||||
this.timeAPI = openmct.time;
|
|
||||||
this.conductorViewService = new TimeConductorViewService(openmct);
|
|
||||||
this.validation = new TimeConductorValidation(this.timeAPI);
|
|
||||||
this.formatService = formatService;
|
|
||||||
this.config = config;
|
|
||||||
this.timeSystemsForClocks = {};
|
|
||||||
this.$scope.timeSystemModel = {};
|
|
||||||
this.$scope.boundsModel = {};
|
|
||||||
|
|
||||||
this.timeSystems = this.timeAPI.getAllTimeSystems().reduce(function (map, timeSystem) {
|
|
||||||
map[timeSystem.key] = timeSystem;
|
|
||||||
return map;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
this.isFixed = this.timeAPI.clock() === undefined;
|
|
||||||
|
|
||||||
var options = this.optionsFromConfig(config);
|
|
||||||
this.menu = {
|
|
||||||
selected: undefined,
|
|
||||||
options: options,
|
|
||||||
select: this.selectMenuOption
|
|
||||||
};
|
|
||||||
|
|
||||||
//Set the initial state of the UI from the conductor state
|
|
||||||
var timeSystem = this.timeAPI.timeSystem();
|
|
||||||
if (timeSystem) {
|
|
||||||
this.setViewFromTimeSystem(timeSystem);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setViewFromClock(this.timeAPI.clock());
|
|
||||||
|
|
||||||
var offsets = this.timeAPI.clockOffsets();
|
|
||||||
if (offsets) {
|
|
||||||
this.setViewFromOffsets(offsets);
|
|
||||||
}
|
|
||||||
|
|
||||||
var bounds = this.timeAPI.bounds();
|
|
||||||
if (bounds && bounds.start !== undefined && bounds.end !== undefined) {
|
|
||||||
this.setViewFromBounds(bounds);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.conductorViewService.on('pan', this.onPan);
|
|
||||||
this.conductorViewService.on('pan-stop', this.onPanStop);
|
|
||||||
|
|
||||||
//Respond to any subsequent conductor changes
|
|
||||||
this.timeAPI.on('bounds', this.setViewFromBounds);
|
|
||||||
this.timeAPI.on('timeSystem', this.setViewFromTimeSystem);
|
|
||||||
this.timeAPI.on('clock', this.setViewFromClock);
|
|
||||||
this.timeAPI.on('clockOffsets', this.setViewFromOffsets);
|
|
||||||
this.$scope.$on('$destroy', this.destroy);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given a key for a clock, retrieve the clock object.
|
|
||||||
* @private
|
|
||||||
* @param key
|
|
||||||
* @returns {Clock}
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.getClock = function (key) {
|
|
||||||
return this.timeAPI.getAllClocks().filter(function (clock) {
|
|
||||||
return clock.key === key;
|
|
||||||
})[0];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Activate the selected menu option. Menu options correspond to clocks.
|
|
||||||
* A distinction is made to avoid confusion between the menu options and
|
|
||||||
* their metadata, and actual {@link Clock} objects.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @param newOption
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.selectMenuOption = function (newOption) {
|
|
||||||
if (this.menu.selected.key === newOption.key) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.menu.selected = newOption;
|
|
||||||
|
|
||||||
var config = this.getConfig(this.timeAPI.timeSystem(), newOption.clock);
|
|
||||||
if (!config) {
|
|
||||||
// Clock does not support this timeSystem, fallback to first
|
|
||||||
// option provided for clock.
|
|
||||||
config = this.config.menuOptions.filter(function (menuOption) {
|
|
||||||
return menuOption.clock === (newOption.clock && newOption.clock.key);
|
|
||||||
})[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.clock) {
|
|
||||||
this.timeAPI.clock(config.clock, config.clockOffsets);
|
|
||||||
this.timeAPI.timeSystem(config.timeSystem);
|
|
||||||
} else {
|
|
||||||
this.timeAPI.stopClock();
|
|
||||||
this.timeAPI.timeSystem(config.timeSystem, config.bounds);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* From the provided configuration, build the available menu options.
|
|
||||||
* @private
|
|
||||||
* @param config
|
|
||||||
* @returns {*[]}
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.optionsFromConfig = function (config) {
|
|
||||||
/*
|
|
||||||
* "Fixed Mode" is always the first available option.
|
|
||||||
*/
|
|
||||||
var options = [{
|
|
||||||
key: 'fixed',
|
|
||||||
name: 'Fixed Timespan Mode',
|
|
||||||
description: 'Query and explore data that falls between two fixed datetimes.',
|
|
||||||
cssClass: 'icon-calendar'
|
|
||||||
}];
|
|
||||||
var clocks = {};
|
|
||||||
var timeSystemsForClocks = this.timeSystemsForClocks;
|
|
||||||
|
|
||||||
(config.menuOptions || []).forEach(function (menuOption) {
|
|
||||||
var clockKey = menuOption.clock || 'fixed';
|
|
||||||
var clock = this.getClock(clockKey);
|
|
||||||
|
|
||||||
if (clock !== undefined) {
|
|
||||||
clocks[clock.key] = clock;
|
|
||||||
}
|
|
||||||
|
|
||||||
var timeSystem = this.timeSystems[menuOption.timeSystem];
|
|
||||||
if (timeSystem !== undefined) {
|
|
||||||
timeSystemsForClocks[clockKey] = timeSystemsForClocks[clockKey] || [];
|
|
||||||
timeSystemsForClocks[clockKey].push(timeSystem);
|
|
||||||
}
|
|
||||||
}, this);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Populate the clocks menu with metadata from the available clocks
|
|
||||||
*/
|
|
||||||
Object.values(clocks).forEach(function (clock) {
|
|
||||||
options.push({
|
|
||||||
key: clock.key,
|
|
||||||
name: clock.name,
|
|
||||||
description: "Monitor streaming data in real-time. The Time " +
|
|
||||||
"Conductor and displays will automatically advance themselves based on this clock. " + clock.description,
|
|
||||||
cssClass: clock.cssClass || 'icon-clock',
|
|
||||||
clock: clock
|
|
||||||
});
|
|
||||||
}.bind(this));
|
|
||||||
|
|
||||||
return options;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When bounds change, set UI values from the new bounds.
|
|
||||||
* @param {TimeBounds} bounds the bounds
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.setViewFromBounds = function (bounds) {
|
|
||||||
if (!this.zooming && !this.panning) {
|
|
||||||
this.$scope.boundsModel.start = bounds.start;
|
|
||||||
this.$scope.boundsModel.end = bounds.end;
|
|
||||||
|
|
||||||
if (this.supportsZoom()) {
|
|
||||||
var config = this.getConfig(this.timeAPI.timeSystem(), this.timeAPI.clock());
|
|
||||||
this.currentZoom = this.toSliderValue(bounds.end - bounds.start, config.zoomOutLimit, config.zoomInLimit);
|
|
||||||
this.toTimeUnits(bounds.end - bounds.start);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Ensure that a digest occurs, capped at the browser's refresh
|
|
||||||
rate.
|
|
||||||
*/
|
|
||||||
if (!this.pendingUpdate) {
|
|
||||||
this.pendingUpdate = true;
|
|
||||||
this.$window.requestAnimationFrame(function () {
|
|
||||||
this.pendingUpdate = false;
|
|
||||||
this.$scope.$digest();
|
|
||||||
}.bind(this));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve any configuration defined for the provided time system and
|
|
||||||
* clock
|
|
||||||
* @private
|
|
||||||
* @param timeSystem
|
|
||||||
* @param clock
|
|
||||||
* @returns {object} The Time Conductor configuration corresponding to
|
|
||||||
* the provided combination of time system and clock
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.getConfig = function (timeSystem, clock) {
|
|
||||||
var clockKey = clock && clock.key;
|
|
||||||
var timeSystemKey = timeSystem && timeSystem.key;
|
|
||||||
|
|
||||||
var option = this.config.menuOptions.filter(function (menuOption) {
|
|
||||||
return menuOption.timeSystem === timeSystemKey && menuOption.clock === clockKey;
|
|
||||||
})[0];
|
|
||||||
return option;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When the clock offsets change, update the values in the UI
|
|
||||||
* @param {ClockOffsets} offsets
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.setViewFromOffsets = function (offsets) {
|
|
||||||
this.$scope.boundsModel.startOffset = Math.abs(offsets.start);
|
|
||||||
this.$scope.boundsModel.endOffset = offsets.end;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When form values for bounds change, update the bounds in the Time API
|
|
||||||
* to trigger an application-wide bounds change.
|
|
||||||
* @param {object} boundsModel
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.setBoundsFromView = function (boundsModel) {
|
|
||||||
var bounds = this.timeAPI.bounds();
|
|
||||||
if (boundsModel.start !== bounds.start || boundsModel.end !== bounds.end) {
|
|
||||||
this.timeAPI.bounds({
|
|
||||||
start: boundsModel.start,
|
|
||||||
end: boundsModel.end
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When form values for bounds change, update the bounds in the Time API
|
|
||||||
* to trigger an application-wide bounds change.
|
|
||||||
* @param {object} formModel
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.setOffsetsFromView = function (boundsModel) {
|
|
||||||
if (this.validation.validateStartOffset(boundsModel.startOffset) && this.validation.validateEndOffset(boundsModel.endOffset)) {
|
|
||||||
var offsets = {
|
|
||||||
start: 0 - boundsModel.startOffset,
|
|
||||||
end: boundsModel.endOffset
|
|
||||||
};
|
|
||||||
var existingOffsets = this.timeAPI.clockOffsets();
|
|
||||||
|
|
||||||
if (offsets.start !== existingOffsets.start || offsets.end !== existingOffsets.end) {
|
|
||||||
//Sychronize offsets between form and time API
|
|
||||||
this.timeAPI.clockOffsets(offsets);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.supportsZoom = function () {
|
|
||||||
var config = this.getConfig(this.timeAPI.timeSystem(), this.timeAPI.clock());
|
|
||||||
return config && (config.zoomInLimit !== undefined && config.zoomOutLimit !== undefined);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the UI state to reflect a change in clock. Provided conductor
|
|
||||||
* configuration will be checked for compatibility between the new clock
|
|
||||||
* and the currently selected time system. If configuration is not available,
|
|
||||||
* an attempt will be made to default to a time system that is compatible
|
|
||||||
* with the new clock
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @param {Clock} clock
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.setViewFromClock = function (clock) {
|
|
||||||
var newClockKey = clock ? clock.key : 'fixed';
|
|
||||||
var timeSystems = this.timeSystemsForClocks[newClockKey];
|
|
||||||
var menuOption = this.menu.options.filter(function (option) {
|
|
||||||
return option.key === (newClockKey);
|
|
||||||
})[0];
|
|
||||||
|
|
||||||
this.menu.selected = menuOption;
|
|
||||||
|
|
||||||
//Try to find currently selected time system in time systems for clock
|
|
||||||
var selectedTimeSystem = timeSystems.filter(function (timeSystem) {
|
|
||||||
return timeSystem.key === this.$scope.timeSystemModel.selected.key;
|
|
||||||
}.bind(this))[0];
|
|
||||||
|
|
||||||
var config = this.getConfig(selectedTimeSystem, clock);
|
|
||||||
|
|
||||||
if (selectedTimeSystem === undefined) {
|
|
||||||
selectedTimeSystem = timeSystems[0];
|
|
||||||
config = this.getConfig(selectedTimeSystem, clock);
|
|
||||||
|
|
||||||
if (clock === undefined) {
|
|
||||||
this.timeAPI.timeSystem(selectedTimeSystem, config.bounds);
|
|
||||||
} else {
|
|
||||||
//When time system changes, some start bounds need to be provided
|
|
||||||
this.timeAPI.timeSystem(selectedTimeSystem, {
|
|
||||||
start: clock.currentValue() + config.clockOffsets.start,
|
|
||||||
end: clock.currentValue() + config.clockOffsets.end
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.isFixed = clock === undefined;
|
|
||||||
|
|
||||||
if (clock === undefined) {
|
|
||||||
this.setViewFromBounds(this.timeAPI.bounds());
|
|
||||||
}
|
|
||||||
|
|
||||||
this.zoom = this.supportsZoom();
|
|
||||||
this.$scope.timeSystemModel.options = timeSystems;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Respond to time system selection from UI
|
|
||||||
*
|
|
||||||
* Allows time system to be changed by key. This supports selection
|
|
||||||
* from the menu. Resolves a TimeSystem object and then invokes
|
|
||||||
* TimeConductorController#setTimeSystem
|
|
||||||
* @param key
|
|
||||||
* @see TimeConductorController#setTimeSystem
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.setTimeSystemFromView = function (key) {
|
|
||||||
var clock = this.menu.selected.clock;
|
|
||||||
var timeSystem = this.timeSystems[key];
|
|
||||||
var config = this.getConfig(timeSystem, clock);
|
|
||||||
|
|
||||||
this.$scope.timeSystemModel.selected = timeSystem;
|
|
||||||
this.$scope.timeSystemModel.format = timeSystem.timeFormat;
|
|
||||||
|
|
||||||
if (clock === undefined) {
|
|
||||||
this.timeAPI.timeSystem(timeSystem, config.bounds);
|
|
||||||
} else {
|
|
||||||
this.timeAPI.clock(clock, config.clockOffsets);
|
|
||||||
this.timeAPI.timeSystem(timeSystem);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles time system change from time conductor
|
|
||||||
*
|
|
||||||
* Sets the selected time system. Will populate form with the default
|
|
||||||
* bounds and offsets defined in the selected time system.
|
|
||||||
*
|
|
||||||
* @param newTimeSystem
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.setViewFromTimeSystem = function (timeSystem) {
|
|
||||||
var oldKey = (this.$scope.timeSystemModel.selected || {}).key;
|
|
||||||
var timeSystemModel = this.$scope.timeSystemModel;
|
|
||||||
|
|
||||||
if (timeSystem && (timeSystem.key !== oldKey)) {
|
|
||||||
var config = this.getConfig(timeSystem, this.timeAPI.clock());
|
|
||||||
|
|
||||||
timeSystemModel.selected = timeSystem;
|
|
||||||
timeSystemModel.format = timeSystem.timeFormat;
|
|
||||||
timeSystemModel.durationFormat = timeSystem.durationFormat;
|
|
||||||
|
|
||||||
if (this.supportsZoom()) {
|
|
||||||
timeSystemModel.minZoom = config.zoomOutLimit;
|
|
||||||
timeSystemModel.maxZoom = config.zoomInLimit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.zoom = this.supportsZoom();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Takes a time span and calculates a slider increment value, used
|
|
||||||
* to set the horizontal offset of the slider.
|
|
||||||
* @private
|
|
||||||
* @param {number} timeSpan a duration of time, in ms
|
|
||||||
* @returns {number} a value between 0.01 and 0.99, in increments of .01
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.toSliderValue = function (timeSpan, zoomOutLimit, zoomInLimit) {
|
|
||||||
var perc = timeSpan / (zoomOutLimit - zoomInLimit);
|
|
||||||
return 1 - Math.pow(perc, 1 / 4);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given a time span, set a label for the units of time that it,
|
|
||||||
* roughly, represents. Leverages
|
|
||||||
* @param {TimeSpan} timeSpan
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.toTimeUnits = function (timeSpan) {
|
|
||||||
var timeSystem = this.timeAPI.timeSystem();
|
|
||||||
if (timeSystem && timeSystem.isUTCBased) {
|
|
||||||
var momentified = moment.duration(timeSpan);
|
|
||||||
|
|
||||||
this.$scope.timeUnits = timeUnitsMegastructure.filter(function (row) {
|
|
||||||
return row[1](momentified);
|
|
||||||
})[0][0];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Zooming occurs when the user manipulates the zoom slider.
|
|
||||||
* Zooming updates the scale and bounds fields immediately, but does
|
|
||||||
* not trigger a bounds change to other views until the mouse button
|
|
||||||
* is released.
|
|
||||||
* @param bounds
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.onZoom = function (sliderValue) {
|
|
||||||
var config = this.getConfig(this.timeAPI.timeSystem(), this.timeAPI.clock());
|
|
||||||
var timeSpan = Math.pow((1 - sliderValue), 4) * (config.zoomOutLimit - config.zoomInLimit);
|
|
||||||
|
|
||||||
var zoom = this.conductorViewService.zoom(timeSpan);
|
|
||||||
this.zooming = true;
|
|
||||||
|
|
||||||
this.$scope.boundsModel.start = zoom.bounds.start;
|
|
||||||
this.$scope.boundsModel.end = zoom.bounds.end;
|
|
||||||
this.toTimeUnits(zoom.bounds.end - zoom.bounds.start);
|
|
||||||
|
|
||||||
if (zoom.offsets) {
|
|
||||||
this.setViewFromOffsets(zoom.offsets);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fired when user has released the zoom slider
|
|
||||||
* @event platform.features.conductor.TimeConductorController~zoomStop
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Invoked when zoom slider is released by user. Will update the time conductor with the new bounds, triggering
|
|
||||||
* a global bounds change event.
|
|
||||||
* @fires platform.features.conductor.TimeConductorController~zoomStop
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.onZoomStop = function () {
|
|
||||||
if (this.timeAPI.clock() !== undefined) {
|
|
||||||
this.setOffsetsFromView(this.$scope.boundsModel);
|
|
||||||
}
|
|
||||||
this.setBoundsFromView(this.$scope.boundsModel);
|
|
||||||
|
|
||||||
this.zooming = false;
|
|
||||||
this.conductorViewService.emit('zoom-stop');
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Panning occurs when the user grabs the conductor scale and drags
|
|
||||||
* it left or right to slide the window of time represented by the
|
|
||||||
* conductor. Panning updates the scale and bounds fields
|
|
||||||
* immediately, but does not trigger a bounds change to other views
|
|
||||||
* until the mouse button is released.
|
|
||||||
* @param {TimeConductorBounds} bounds
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.onPan = function (bounds) {
|
|
||||||
this.panning = true;
|
|
||||||
this.$scope.boundsModel.start = bounds.start;
|
|
||||||
this.$scope.boundsModel.end = bounds.end;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the user releases the mouse button after panning.
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.onPanStop = function () {
|
|
||||||
this.panning = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.destroy = function () {
|
|
||||||
this.timeAPI.off('bounds', this.setViewFromBounds);
|
|
||||||
this.timeAPI.off('timeSystem', this.setViewFromTimeSystem);
|
|
||||||
this.timeAPI.off('clock', this.setViewFromClock);
|
|
||||||
this.timeAPI.off('follow', this.setFollow);
|
|
||||||
this.timeAPI.off('clockOffsets', this.setViewFromOffsets);
|
|
||||||
|
|
||||||
this.conductorViewService.off('pan', this.onPan);
|
|
||||||
this.conductorViewService.off('pan-stop', this.onPanStop);
|
|
||||||
};
|
|
||||||
|
|
||||||
return TimeConductorController;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
@@ -1,513 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(['./TimeConductorController'], function (TimeConductorController) {
|
|
||||||
xdescribe("The time conductor controller", function () {
|
|
||||||
var mockScope;
|
|
||||||
var mockWindow;
|
|
||||||
var mockTimeConductor;
|
|
||||||
var mockConductorViewService;
|
|
||||||
var mockTimeSystems;
|
|
||||||
var controller;
|
|
||||||
var mockFormatService;
|
|
||||||
var mockFormat;
|
|
||||||
var mockLocation;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
mockScope = jasmine.createSpyObj("$scope", [
|
|
||||||
"$watch",
|
|
||||||
"$on"
|
|
||||||
]);
|
|
||||||
|
|
||||||
mockWindow = jasmine.createSpyObj("$window", ["requestAnimationFrame"]);
|
|
||||||
mockTimeConductor = jasmine.createSpyObj(
|
|
||||||
"TimeConductor",
|
|
||||||
[
|
|
||||||
"bounds",
|
|
||||||
"timeSystem",
|
|
||||||
"on",
|
|
||||||
"off"
|
|
||||||
]
|
|
||||||
);
|
|
||||||
mockTimeConductor.bounds.and.returnValue({start: undefined, end: undefined});
|
|
||||||
|
|
||||||
mockConductorViewService = jasmine.createSpyObj(
|
|
||||||
"ConductorViewService",
|
|
||||||
[
|
|
||||||
"availableModes",
|
|
||||||
"mode",
|
|
||||||
"availableTimeSystems",
|
|
||||||
"deltas",
|
|
||||||
"deltas",
|
|
||||||
"on",
|
|
||||||
"off"
|
|
||||||
]
|
|
||||||
);
|
|
||||||
mockConductorViewService.availableModes.and.returnValue([]);
|
|
||||||
mockConductorViewService.availableTimeSystems.and.returnValue([]);
|
|
||||||
|
|
||||||
mockFormatService = jasmine.createSpyObj('formatService', [
|
|
||||||
'getFormat'
|
|
||||||
]);
|
|
||||||
mockFormat = jasmine.createSpyObj('format', [
|
|
||||||
'format'
|
|
||||||
]);
|
|
||||||
mockFormatService.getFormat.and.returnValue(mockFormat);
|
|
||||||
mockLocation = jasmine.createSpyObj('location', [
|
|
||||||
'search'
|
|
||||||
]);
|
|
||||||
mockLocation.search.and.returnValue({});
|
|
||||||
|
|
||||||
mockTimeSystems = [];
|
|
||||||
});
|
|
||||||
|
|
||||||
function getListener(target, event) {
|
|
||||||
return target.calls.all().filter(function (call) {
|
|
||||||
return call.args[0] === event;
|
|
||||||
})[0].args[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("when time conductor state changes", function () {
|
|
||||||
var mockDeltaFormat;
|
|
||||||
var defaultBounds;
|
|
||||||
var defaultDeltas;
|
|
||||||
var mockDefaults;
|
|
||||||
var timeSystem;
|
|
||||||
var tsListener;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
mockFormat = {};
|
|
||||||
mockDeltaFormat = {};
|
|
||||||
defaultBounds = {
|
|
||||||
start: 2,
|
|
||||||
end: 3
|
|
||||||
};
|
|
||||||
defaultDeltas = {
|
|
||||||
start: 10,
|
|
||||||
end: 20
|
|
||||||
};
|
|
||||||
mockDefaults = {
|
|
||||||
deltas: defaultDeltas,
|
|
||||||
bounds: defaultBounds
|
|
||||||
};
|
|
||||||
timeSystem = {
|
|
||||||
metadata: {
|
|
||||||
key: 'mock'
|
|
||||||
},
|
|
||||||
formats: function () {
|
|
||||||
return [mockFormat];
|
|
||||||
},
|
|
||||||
deltaFormat: function () {
|
|
||||||
return mockDeltaFormat;
|
|
||||||
},
|
|
||||||
defaults: function () {
|
|
||||||
return mockDefaults;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
controller = new TimeConductorController(
|
|
||||||
mockScope,
|
|
||||||
mockWindow,
|
|
||||||
mockLocation,
|
|
||||||
{conductor: mockTimeConductor},
|
|
||||||
mockConductorViewService,
|
|
||||||
mockFormatService,
|
|
||||||
'fixed',
|
|
||||||
true
|
|
||||||
|
|
||||||
);
|
|
||||||
|
|
||||||
tsListener = getListener(mockTimeConductor.on, "timeSystem");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("listens for changes to conductor state", function () {
|
|
||||||
expect(mockTimeConductor.on).toHaveBeenCalledWith("timeSystem", controller.changeTimeSystem);
|
|
||||||
expect(mockTimeConductor.on).toHaveBeenCalledWith("bounds", controller.changeBounds);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("deregisters conductor listens when scope is destroyed", function () {
|
|
||||||
expect(mockScope.$on).toHaveBeenCalledWith("$destroy", controller.destroy);
|
|
||||||
|
|
||||||
controller.destroy();
|
|
||||||
expect(mockTimeConductor.off).toHaveBeenCalledWith("timeSystem", controller.changeTimeSystem);
|
|
||||||
expect(mockTimeConductor.off).toHaveBeenCalledWith("bounds", controller.changeBounds);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("when time system changes, sets time system on scope", function () {
|
|
||||||
expect(tsListener).toBeDefined();
|
|
||||||
tsListener(timeSystem);
|
|
||||||
|
|
||||||
expect(mockScope.timeSystemModel).toBeDefined();
|
|
||||||
expect(mockScope.timeSystemModel.selected).toBe(timeSystem);
|
|
||||||
expect(mockScope.timeSystemModel.format).toBe(mockFormat);
|
|
||||||
expect(mockScope.timeSystemModel.deltaFormat).toBe(mockDeltaFormat);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("when time system changes, sets defaults on scope", function () {
|
|
||||||
mockDefaults.zoom = {
|
|
||||||
min: 100,
|
|
||||||
max: 10
|
|
||||||
};
|
|
||||||
mockTimeConductor.timeSystem.and.returnValue(timeSystem);
|
|
||||||
tsListener(timeSystem);
|
|
||||||
|
|
||||||
expect(mockScope.boundsModel.start).toEqual(defaultBounds.start);
|
|
||||||
expect(mockScope.boundsModel.end).toEqual(defaultBounds.end);
|
|
||||||
|
|
||||||
expect(mockScope.boundsModel.startDelta).toEqual(defaultDeltas.start);
|
|
||||||
expect(mockScope.boundsModel.endDelta).toEqual(defaultDeltas.end);
|
|
||||||
|
|
||||||
expect(mockScope.timeSystemModel.minZoom).toBe(mockDefaults.zoom.min);
|
|
||||||
expect(mockScope.timeSystemModel.maxZoom).toBe(mockDefaults.zoom.max);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("supports zoom if time system defines zoom defaults", function () {
|
|
||||||
|
|
||||||
mockDefaults.zoom = undefined;
|
|
||||||
|
|
||||||
tsListener(timeSystem);
|
|
||||||
expect(controller.supportsZoom).toBe(false);
|
|
||||||
|
|
||||||
mockDefaults.zoom = {
|
|
||||||
min: 100,
|
|
||||||
max: 10
|
|
||||||
};
|
|
||||||
|
|
||||||
var anotherTimeSystem = Object.create(timeSystem);
|
|
||||||
timeSystem.defaults = function () {
|
|
||||||
return mockDefaults;
|
|
||||||
};
|
|
||||||
|
|
||||||
tsListener(anotherTimeSystem);
|
|
||||||
expect(controller.supportsZoom).toBe(true);
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
it("when bounds change, sets the correct zoom slider value", function () {
|
|
||||||
var bounds = {
|
|
||||||
start: 0,
|
|
||||||
end: 50
|
|
||||||
};
|
|
||||||
mockDefaults.zoom = {
|
|
||||||
min: 100,
|
|
||||||
max: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
function exponentializer(rawValue) {
|
|
||||||
return 1 - Math.pow(rawValue, 1 / 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
mockTimeConductor.timeSystem.and.returnValue(timeSystem);
|
|
||||||
//Set zoom defaults
|
|
||||||
tsListener(timeSystem);
|
|
||||||
|
|
||||||
controller.changeBounds(bounds);
|
|
||||||
expect(controller.currentZoom).toEqual(exponentializer(0.5));
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
it("when bounds change, sets them on scope", function () {
|
|
||||||
var bounds = {
|
|
||||||
start: 1,
|
|
||||||
end: 2
|
|
||||||
};
|
|
||||||
|
|
||||||
var boundsListener = getListener(mockTimeConductor.on, "bounds");
|
|
||||||
expect(boundsListener).toBeDefined();
|
|
||||||
boundsListener(bounds);
|
|
||||||
|
|
||||||
expect(mockScope.boundsModel).toBeDefined();
|
|
||||||
expect(mockScope.boundsModel.start).toEqual(bounds.start);
|
|
||||||
expect(mockScope.boundsModel.end).toEqual(bounds.end);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when user makes changes from UI", function () {
|
|
||||||
var mode = "realtime";
|
|
||||||
var ts1Metadata;
|
|
||||||
var ts2Metadata;
|
|
||||||
var ts3Metadata;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
mode = "realtime";
|
|
||||||
ts1Metadata = {
|
|
||||||
'key': 'ts1',
|
|
||||||
'name': 'Time System One',
|
|
||||||
'cssClass': 'cssClassOne'
|
|
||||||
};
|
|
||||||
ts2Metadata = {
|
|
||||||
'key': 'ts2',
|
|
||||||
'name': 'Time System Two',
|
|
||||||
'cssClass': 'cssClassTwo'
|
|
||||||
};
|
|
||||||
ts3Metadata = {
|
|
||||||
'key': 'ts3',
|
|
||||||
'name': 'Time System Three',
|
|
||||||
'cssClass': 'cssClassThree'
|
|
||||||
};
|
|
||||||
mockTimeSystems = [
|
|
||||||
{
|
|
||||||
metadata: ts1Metadata
|
|
||||||
},
|
|
||||||
{
|
|
||||||
metadata: ts2Metadata
|
|
||||||
},
|
|
||||||
{
|
|
||||||
metadata: ts3Metadata
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
//Wrap in mock constructors
|
|
||||||
mockConductorViewService.systems = mockTimeSystems;
|
|
||||||
|
|
||||||
controller = new TimeConductorController(
|
|
||||||
mockScope,
|
|
||||||
mockWindow,
|
|
||||||
mockLocation,
|
|
||||||
{conductor: mockTimeConductor},
|
|
||||||
mockConductorViewService,
|
|
||||||
mockFormatService,
|
|
||||||
"fixed",
|
|
||||||
true
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("sets the mode on scope", function () {
|
|
||||||
mockConductorViewService.availableTimeSystems.and.returnValue(mockTimeSystems);
|
|
||||||
controller.setMode(mode);
|
|
||||||
|
|
||||||
expect(mockScope.modeModel.selectedKey).toEqual(mode);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("sets available time systems on scope when mode changes", function () {
|
|
||||||
mockConductorViewService.availableTimeSystems.and.returnValue(mockTimeSystems);
|
|
||||||
controller.setMode(mode);
|
|
||||||
|
|
||||||
expect(mockScope.timeSystemModel.options.length).toEqual(3);
|
|
||||||
expect(mockScope.timeSystemModel.options[0]).toEqual(ts1Metadata);
|
|
||||||
expect(mockScope.timeSystemModel.options[1]).toEqual(ts2Metadata);
|
|
||||||
expect(mockScope.timeSystemModel.options[2]).toEqual(ts3Metadata);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("sets bounds on the time conductor", function () {
|
|
||||||
var formModel = {
|
|
||||||
start: 1,
|
|
||||||
end: 10
|
|
||||||
};
|
|
||||||
|
|
||||||
controller.setBounds(formModel);
|
|
||||||
expect(mockTimeConductor.bounds).toHaveBeenCalledWith(formModel);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("applies deltas when they change in form", function () {
|
|
||||||
var deltas = {
|
|
||||||
start: 1000,
|
|
||||||
end: 2000
|
|
||||||
};
|
|
||||||
var formModel = {
|
|
||||||
startDelta: deltas.start,
|
|
||||||
endDelta: deltas.end
|
|
||||||
};
|
|
||||||
|
|
||||||
controller.setDeltas(formModel);
|
|
||||||
expect(mockConductorViewService.deltas).toHaveBeenCalledWith(deltas);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("sets the time system on the time conductor", function () {
|
|
||||||
var defaultBounds = {
|
|
||||||
start: 5,
|
|
||||||
end: 6
|
|
||||||
};
|
|
||||||
var timeSystem = {
|
|
||||||
metadata: {
|
|
||||||
key: 'testTimeSystem'
|
|
||||||
},
|
|
||||||
defaults: function () {
|
|
||||||
return {
|
|
||||||
bounds: defaultBounds
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
controller.timeSystems = [timeSystem];
|
|
||||||
|
|
||||||
controller.selectTimeSystemByKey('testTimeSystem');
|
|
||||||
expect(mockTimeConductor.timeSystem).toHaveBeenCalledWith(timeSystem, defaultBounds);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("updates form bounds during pan events", function () {
|
|
||||||
var testBounds = {
|
|
||||||
start: 10,
|
|
||||||
end: 20
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(controller.$scope.boundsModel.start).not.toBe(testBounds.start);
|
|
||||||
expect(controller.$scope.boundsModel.end).not.toBe(testBounds.end);
|
|
||||||
|
|
||||||
expect(controller.conductorViewService.on).toHaveBeenCalledWith("pan",
|
|
||||||
controller.onPan);
|
|
||||||
|
|
||||||
getListener(controller.conductorViewService.on, "pan")(testBounds);
|
|
||||||
|
|
||||||
expect(controller.$scope.boundsModel.start).toBe(testBounds.start);
|
|
||||||
expect(controller.$scope.boundsModel.end).toBe(testBounds.end);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when the URL defines conductor state", function () {
|
|
||||||
var urlBounds;
|
|
||||||
var urlTimeSystem;
|
|
||||||
var urlDeltas;
|
|
||||||
|
|
||||||
var mockDeltaFormat;
|
|
||||||
var defaultBounds;
|
|
||||||
var defaultDeltas;
|
|
||||||
var mockDefaults;
|
|
||||||
var timeSystem;
|
|
||||||
var otherTimeSystem;
|
|
||||||
var mockSearchObject;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
|
|
||||||
mockFormat = {};
|
|
||||||
mockDeltaFormat = {};
|
|
||||||
defaultBounds = {
|
|
||||||
start: 2,
|
|
||||||
end: 3
|
|
||||||
};
|
|
||||||
defaultDeltas = {
|
|
||||||
start: 10,
|
|
||||||
end: 20
|
|
||||||
};
|
|
||||||
mockDefaults = {
|
|
||||||
deltas: defaultDeltas,
|
|
||||||
bounds: defaultBounds
|
|
||||||
};
|
|
||||||
timeSystem = {
|
|
||||||
metadata: {
|
|
||||||
key: 'mockTimeSystem'
|
|
||||||
},
|
|
||||||
formats: function () {
|
|
||||||
return [mockFormat];
|
|
||||||
},
|
|
||||||
deltaFormat: function () {
|
|
||||||
return mockDeltaFormat;
|
|
||||||
},
|
|
||||||
defaults: function () {
|
|
||||||
return mockDefaults;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
otherTimeSystem = {
|
|
||||||
metadata: {
|
|
||||||
key: 'otherTimeSystem'
|
|
||||||
},
|
|
||||||
formats: function () {
|
|
||||||
return [mockFormat];
|
|
||||||
},
|
|
||||||
deltaFormat: function () {
|
|
||||||
return mockDeltaFormat;
|
|
||||||
},
|
|
||||||
defaults: function () {
|
|
||||||
return mockDefaults;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
mockConductorViewService.systems = [timeSystem, otherTimeSystem];
|
|
||||||
|
|
||||||
urlBounds = {
|
|
||||||
start: 100,
|
|
||||||
end: 200
|
|
||||||
};
|
|
||||||
urlTimeSystem = "otherTimeSystem";
|
|
||||||
urlDeltas = {
|
|
||||||
start: 300,
|
|
||||||
end: 400
|
|
||||||
};
|
|
||||||
mockSearchObject = {
|
|
||||||
"tc.startBound": urlBounds.start,
|
|
||||||
"tc.endBound": urlBounds.end,
|
|
||||||
"tc.startDelta": urlDeltas.start,
|
|
||||||
"tc.endDelta": urlDeltas.end,
|
|
||||||
"tc.timeSystem": urlTimeSystem
|
|
||||||
};
|
|
||||||
mockLocation.search.and.returnValue(mockSearchObject);
|
|
||||||
mockTimeConductor.timeSystem.and.returnValue(timeSystem);
|
|
||||||
|
|
||||||
controller = new TimeConductorController(
|
|
||||||
mockScope,
|
|
||||||
mockWindow,
|
|
||||||
mockLocation,
|
|
||||||
{conductor: mockTimeConductor},
|
|
||||||
mockConductorViewService,
|
|
||||||
mockFormatService,
|
|
||||||
"fixed",
|
|
||||||
true
|
|
||||||
);
|
|
||||||
|
|
||||||
spyOn(controller, "setMode");
|
|
||||||
spyOn(controller, "selectTimeSystemByKey");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("sets conductor state from URL", function () {
|
|
||||||
mockSearchObject["tc.mode"] = "fixed";
|
|
||||||
controller.setStateFromSearchParams(mockSearchObject);
|
|
||||||
expect(controller.selectTimeSystemByKey).toHaveBeenCalledWith("otherTimeSystem");
|
|
||||||
expect(mockTimeConductor.bounds).toHaveBeenCalledWith(urlBounds);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("sets mode from URL", function () {
|
|
||||||
mockTimeConductor.bounds.reset();
|
|
||||||
mockSearchObject["tc.mode"] = "realtime";
|
|
||||||
controller.setStateFromSearchParams(mockSearchObject);
|
|
||||||
expect(controller.setMode).toHaveBeenCalledWith("realtime");
|
|
||||||
expect(controller.selectTimeSystemByKey).toHaveBeenCalledWith("otherTimeSystem");
|
|
||||||
expect(mockConductorViewService.deltas).toHaveBeenCalledWith(urlDeltas);
|
|
||||||
expect(mockTimeConductor.bounds).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when conductor state changes", function () {
|
|
||||||
it("updates the URL with the mode", function () {
|
|
||||||
controller.setMode("realtime", "fixed");
|
|
||||||
expect(mockLocation.search).toHaveBeenCalledWith("tc.mode", "fixed");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("updates the URL with the bounds", function () {
|
|
||||||
mockConductorViewService.mode.and.returnValue("fixed");
|
|
||||||
controller.changeBounds({start: 500, end: 600});
|
|
||||||
expect(mockLocation.search).toHaveBeenCalledWith("tc.startBound", 500);
|
|
||||||
expect(mockLocation.search).toHaveBeenCalledWith("tc.endBound", 600);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("updates the URL with the deltas", function () {
|
|
||||||
controller.setDeltas({startDelta: 700, endDelta: 800});
|
|
||||||
expect(mockLocation.search).toHaveBeenCalledWith("tc.startDelta", 700);
|
|
||||||
expect(mockLocation.search).toHaveBeenCalledWith("tc.endDelta", 800);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("updates the URL with the time system", function () {
|
|
||||||
controller.changeTimeSystem(otherTimeSystem);
|
|
||||||
expect(mockLocation.search).toHaveBeenCalledWith("tc.timeSystem", "otherTimeSystem");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
[],
|
|
||||||
function () {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Form validation for the TimeConductorController.
|
|
||||||
* @param conductor
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function TimeConductorValidation(timeAPI) {
|
|
||||||
var self = this;
|
|
||||||
this.timeAPI = timeAPI;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Bind all class functions to 'this'
|
|
||||||
*/
|
|
||||||
Object.keys(TimeConductorValidation.prototype).filter(function (key) {
|
|
||||||
return typeof TimeConductorValidation.prototype[key] === 'function';
|
|
||||||
}).forEach(function (key) {
|
|
||||||
self[key] = self[key].bind(self);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validation methods below are invoked directly from controls in the TimeConductor form
|
|
||||||
*/
|
|
||||||
TimeConductorValidation.prototype.validateStart = function (start) {
|
|
||||||
var bounds = this.timeAPI.bounds();
|
|
||||||
return this.timeAPI.validateBounds({start: start, end: bounds.end}) === true;
|
|
||||||
};
|
|
||||||
|
|
||||||
TimeConductorValidation.prototype.validateEnd = function (end) {
|
|
||||||
var bounds = this.timeAPI.bounds();
|
|
||||||
return this.timeAPI.validateBounds({start: bounds.start, end: end}) === true;
|
|
||||||
};
|
|
||||||
|
|
||||||
TimeConductorValidation.prototype.validateStartOffset = function (startOffset) {
|
|
||||||
return !isNaN(startOffset) && startOffset > 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
TimeConductorValidation.prototype.validateEndOffset = function (endOffset) {
|
|
||||||
return !isNaN(endOffset) && endOffset >= 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
return TimeConductorValidation;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(['./TimeConductorValidation'], function (TimeConductorValidation) {
|
|
||||||
describe("The Time Conductor Validation class", function () {
|
|
||||||
var timeConductorValidation,
|
|
||||||
mockTimeConductor;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
mockTimeConductor = jasmine.createSpyObj("timeConductor", [
|
|
||||||
"validateBounds",
|
|
||||||
"bounds"
|
|
||||||
]);
|
|
||||||
timeConductorValidation = new TimeConductorValidation(mockTimeConductor);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Validates start and end values using Time Conductor", function () {
|
|
||||||
beforeEach(function () {
|
|
||||||
var mockBounds = {
|
|
||||||
start: 10,
|
|
||||||
end: 20
|
|
||||||
};
|
|
||||||
|
|
||||||
mockTimeConductor.bounds.and.returnValue(mockBounds);
|
|
||||||
|
|
||||||
});
|
|
||||||
it("Validates start values using Time Conductor", function () {
|
|
||||||
var startValue = 30;
|
|
||||||
timeConductorValidation.validateStart(startValue);
|
|
||||||
expect(mockTimeConductor.validateBounds).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
it("Validates end values using Time Conductor", function () {
|
|
||||||
var endValue = 40;
|
|
||||||
timeConductorValidation.validateEnd(endValue);
|
|
||||||
expect(mockTimeConductor.validateBounds).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Validates that start Offset is valid number > 0", function () {
|
|
||||||
expect(timeConductorValidation.validateStartOffset(-1)).toBe(false);
|
|
||||||
expect(timeConductorValidation.validateStartOffset("abc")).toBe(false);
|
|
||||||
expect(timeConductorValidation.validateStartOffset("1")).toBe(true);
|
|
||||||
expect(timeConductorValidation.validateStartOffset(1)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Validates that end Offset is valid number >= 0", function () {
|
|
||||||
expect(timeConductorValidation.validateEndOffset(-1)).toBe(false);
|
|
||||||
expect(timeConductorValidation.validateEndOffset("abc")).toBe(false);
|
|
||||||
expect(timeConductorValidation.validateEndOffset("1")).toBe(true);
|
|
||||||
expect(timeConductorValidation.validateEndOffset(0)).toBe(true);
|
|
||||||
expect(timeConductorValidation.validateEndOffset(1)).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
[
|
|
||||||
'EventEmitter'
|
|
||||||
],
|
|
||||||
function (EventEmitter) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The TimeConductorViewService acts as an event bus between different
|
|
||||||
* elements of the Time Conductor UI. Zooming and panning occur via this
|
|
||||||
* service, as they are specific behaviour of the UI, and not general
|
|
||||||
* functions of the time API.
|
|
||||||
*
|
|
||||||
* Synchronization of conductor state between the Time API and the URL
|
|
||||||
* also occurs from the conductor view service, whose lifecycle persists
|
|
||||||
* between view changes.
|
|
||||||
*
|
|
||||||
* @memberof platform.features.conductor
|
|
||||||
* @param conductor
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function TimeConductorViewService(openmct) {
|
|
||||||
|
|
||||||
EventEmitter.call(this);
|
|
||||||
|
|
||||||
this.timeAPI = openmct.time;
|
|
||||||
}
|
|
||||||
|
|
||||||
TimeConductorViewService.prototype = Object.create(EventEmitter.prototype);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An event to indicate that zooming is taking place
|
|
||||||
* @event platform.features.conductor.TimeConductorViewService~zoom
|
|
||||||
* @property {ZoomLevel} zoom the new zoom level.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Zoom to given time span. Will fire a zoom event with new zoom
|
|
||||||
* bounds. Zoom bounds emitted in this way are considered ephemeral
|
|
||||||
* and should be overridden by any time conductor bounds events. Does
|
|
||||||
* not set bounds globally.
|
|
||||||
* @param {number} zoom A time duration in ms
|
|
||||||
* @fires platform.features.conductor.TimeConductorViewService~zoom
|
|
||||||
* @see module:openmct.TimeConductor#bounds
|
|
||||||
*/
|
|
||||||
TimeConductorViewService.prototype.zoom = function (timeSpan) {
|
|
||||||
var zoom = {};
|
|
||||||
|
|
||||||
// If a tick source is defined, then the concept of 'now' is
|
|
||||||
// important. Calculate zoom based on 'now'.
|
|
||||||
if (this.timeAPI.clock() !== undefined) {
|
|
||||||
zoom.offsets = {
|
|
||||||
start: -timeSpan,
|
|
||||||
end: this.timeAPI.clockOffsets().end
|
|
||||||
};
|
|
||||||
|
|
||||||
var currentVal = this.timeAPI.clock().currentValue();
|
|
||||||
|
|
||||||
zoom.bounds = {
|
|
||||||
start: currentVal + zoom.offsets.start,
|
|
||||||
end: currentVal + zoom.offsets.end
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
var bounds = this.timeAPI.bounds();
|
|
||||||
var center = bounds.start + ((bounds.end - bounds.start)) / 2;
|
|
||||||
bounds.start = center - timeSpan / 2;
|
|
||||||
bounds.end = center + timeSpan / 2;
|
|
||||||
zoom.bounds = bounds;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.emit("zoom", zoom);
|
|
||||||
return zoom;
|
|
||||||
};
|
|
||||||
|
|
||||||
return TimeConductorViewService;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
[],
|
|
||||||
function () {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Controller for the Time of Interest element used in various views to display the TOI. Responsible for setting
|
|
||||||
* the text label for the current TOI, and for toggling the (un)pinned state which determines whether the TOI
|
|
||||||
* indicator is visible.
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function TimeOfInterestController($scope, openmct, formatService) {
|
|
||||||
this.timeAPI = openmct.time;
|
|
||||||
this.formatService = formatService;
|
|
||||||
this.format = undefined;
|
|
||||||
this.toiText = undefined;
|
|
||||||
this.$scope = $scope;
|
|
||||||
|
|
||||||
//Bind all class functions to 'this'
|
|
||||||
Object.keys(TimeOfInterestController.prototype).filter(function (key) {
|
|
||||||
return typeof TimeOfInterestController.prototype[key] === 'function';
|
|
||||||
}).forEach(function (key) {
|
|
||||||
this[key] = TimeOfInterestController.prototype[key].bind(this);
|
|
||||||
}.bind(this));
|
|
||||||
|
|
||||||
this.timeAPI.on('timeOfInterest', this.changeTimeOfInterest);
|
|
||||||
this.timeAPI.on('timeSystem', this.changeTimeSystem);
|
|
||||||
if (this.timeAPI.timeSystem() !== undefined) {
|
|
||||||
this.changeTimeSystem(this.timeAPI.timeSystem());
|
|
||||||
var toi = this.timeAPI.timeOfInterest();
|
|
||||||
if (toi) {
|
|
||||||
this.changeTimeOfInterest(toi);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.$on('$destroy', this.destroy);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the time of interest changes on the conductor. Will pin (display) the TOI indicator, and set the
|
|
||||||
* text using the default formatter of the currently active Time System.
|
|
||||||
* @private
|
|
||||||
* @param {integer} toi Current time of interest in ms
|
|
||||||
*/
|
|
||||||
TimeOfInterestController.prototype.changeTimeOfInterest = function (toi) {
|
|
||||||
if (toi !== undefined) {
|
|
||||||
this.$scope.pinned = true;
|
|
||||||
this.toiText = this.format.format(toi);
|
|
||||||
} else {
|
|
||||||
this.$scope.pinned = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When time system is changed, update the formatter used to
|
|
||||||
* display the current TOI label
|
|
||||||
*/
|
|
||||||
TimeOfInterestController.prototype.changeTimeSystem = function (timeSystem) {
|
|
||||||
this.format = this.formatService.getFormat(timeSystem.timeFormat);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
TimeOfInterestController.prototype.destroy = function () {
|
|
||||||
this.timeAPI.off('timeOfInterest', this.changeTimeOfInterest);
|
|
||||||
this.timeAPI.off('timeSystem', this.changeTimeSystem);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Will unpin (hide) the TOI indicator. Has the effect of setting the time of interest to `undefined` on the
|
|
||||||
* Time Conductor
|
|
||||||
*/
|
|
||||||
TimeOfInterestController.prototype.dismiss = function () {
|
|
||||||
this.timeAPI.timeOfInterest(undefined);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends out a time of interest event with the effect of resetting
|
|
||||||
* the TOI displayed in views.
|
|
||||||
*/
|
|
||||||
TimeOfInterestController.prototype.resync = function () {
|
|
||||||
this.timeAPI.timeOfInterest(this.timeAPI.timeOfInterest());
|
|
||||||
};
|
|
||||||
|
|
||||||
return TimeOfInterestController;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
@@ -19,10 +19,10 @@
|
|||||||
this source code distribution or the Licensing information page available
|
this source code distribution or the Licensing information page available
|
||||||
at runtime from the About dialog for additional information.
|
at runtime from the About dialog for additional information.
|
||||||
-->
|
-->
|
||||||
<a class="l-hyperlink s-hyperlink" ng-controller="HyperlinkController as hyperlink" href="{{domainObject.getModel().url}}"
|
<a class="c-hyperlink u-links" ng-controller="HyperlinkController as hyperlink" href="{{domainObject.getModel().url}}"
|
||||||
ng-attr-target="{{hyperlink.openNewTab() ? '_blank' : undefined}}"
|
ng-attr-target="{{hyperlink.openNewTab() ? '_blank' : undefined}}"
|
||||||
ng-class="{
|
ng-class="{
|
||||||
's-button': hyperlink.isButton()
|
'c-hyperlink--button u-fills-container' : hyperlink.isButton(),
|
||||||
}">
|
'c-hyperlink--link' : !hyperlink.isButton() }">
|
||||||
<span class="label">{{domainObject.getModel().displayText}}</span>
|
<span class="c-hyperlink__label">{{domainObject.getModel().displayText}}</span>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -1,83 +0,0 @@
|
|||||||
<!--
|
|
||||||
Open MCT, Copyright (c) 2014-2018, United States Government
|
|
||||||
as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
Administration. All rights reserved.
|
|
||||||
|
|
||||||
Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
"License"); you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
License for the specific language governing permissions and limitations
|
|
||||||
under the License.
|
|
||||||
|
|
||||||
Open MCT includes source code licensed under additional open source
|
|
||||||
licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
this source code distribution or the Licensing information page available
|
|
||||||
at runtime from the About dialog for additional information.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<div class="abs l-layout {{ domainObject.getModel().layoutAdvancedCss }}"
|
|
||||||
ng-controller="LayoutController as controller"
|
|
||||||
ng-click="controller.bypassSelection($event)">
|
|
||||||
|
|
||||||
<!-- Background grid -->
|
|
||||||
<div class="l-grid-holder"
|
|
||||||
ng-show="!controller.drilledIn"
|
|
||||||
ng-click="controller.bypassSelection($event)">
|
|
||||||
<div class="l-grid l-grid-x"
|
|
||||||
ng-if="!controller.getGridSize()[0] < 3"
|
|
||||||
ng-style="{ 'background-size': controller.getGridSize() [0] + 'px 100%' }"></div>
|
|
||||||
<div class="l-grid l-grid-y"
|
|
||||||
ng-if="!controller.getGridSize()[1] < 3"
|
|
||||||
ng-style="{ 'background-size': '100% ' + controller.getGridSize() [1] + 'px' }"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="frame t-frame-outer child-frame panel s-selectable s-moveable s-hover-border t-object-type-{{ childObject.getModel().type }}"
|
|
||||||
data-layout-id="{{childObject.getId() + '-' + $id}}"
|
|
||||||
ng-class="{ 'no-frame': !controller.hasFrame(childObject), 's-drilled-in': controller.isDrilledIn(childObject) }"
|
|
||||||
ng-repeat="childObject in composition"
|
|
||||||
ng-init="controller.selectIfNew(childObject.getId() + '-' + $id, childObject)"
|
|
||||||
mct-selectable="controller.getContext(childObject)"
|
|
||||||
ng-dblclick="controller.drill($event, childObject)"
|
|
||||||
ng-style="controller.getFrameStyle(childObject.getId())">
|
|
||||||
|
|
||||||
<mct-representation key="'frame'"
|
|
||||||
class="t-rep-frame holder contents abs"
|
|
||||||
mct-object="childObject">
|
|
||||||
</mct-representation>
|
|
||||||
<!-- Drag handles -->
|
|
||||||
<span class="abs t-edit-handle-holder" ng-if="controller.selected(childObject) && !controller.isDrilledIn(childObject)">
|
|
||||||
<span class="edit-handle edit-move"
|
|
||||||
mct-drag-down="controller.startDrag(childObject.getId(), [1,1], [0,0])"
|
|
||||||
mct-drag="controller.continueDrag(delta)"
|
|
||||||
mct-drag-up="controller.endDrag()">
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span class="edit-corner edit-resize-nw"
|
|
||||||
mct-drag-down="controller.startDrag(childObject.getId(), [1,1], [-1,-1])"
|
|
||||||
mct-drag="controller.continueDrag(delta)"
|
|
||||||
mct-drag-up="controller.endDrag()">
|
|
||||||
</span>
|
|
||||||
<span class="edit-corner edit-resize-ne"
|
|
||||||
mct-drag-down="controller.startDrag(childObject.getId(), [0,1], [1,-1])"
|
|
||||||
mct-drag="controller.continueDrag(delta)"
|
|
||||||
mct-drag-up="controller.endDrag()">
|
|
||||||
</span>
|
|
||||||
<span class="edit-corner edit-resize-sw"
|
|
||||||
mct-drag-down="controller.startDrag(childObject.getId(), [1,0], [-1,1])"
|
|
||||||
mct-drag="controller.continueDrag(delta)"
|
|
||||||
mct-drag-up="controller.endDrag()">
|
|
||||||
</span>
|
|
||||||
<span class="edit-corner edit-resize-se"
|
|
||||||
mct-drag-down="controller.startDrag(childObject.getId(), [0,0], [1,1])"
|
|
||||||
mct-drag="controller.continueDrag(delta)"
|
|
||||||
mct-drag-up="controller.endDrag()">
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
@@ -1,524 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This bundle implements object types and associated views for
|
|
||||||
* display-building.
|
|
||||||
* @namespace platform/features/layout
|
|
||||||
*/
|
|
||||||
define(
|
|
||||||
[
|
|
||||||
'zepto',
|
|
||||||
'./LayoutDrag'
|
|
||||||
],
|
|
||||||
function (
|
|
||||||
$,
|
|
||||||
LayoutDrag
|
|
||||||
) {
|
|
||||||
|
|
||||||
var DEFAULT_DIMENSIONS = [12, 8],
|
|
||||||
DEFAULT_GRID_SIZE = [32, 32],
|
|
||||||
MINIMUM_FRAME_SIZE = [320, 180];
|
|
||||||
|
|
||||||
var DEFAULT_HIDDEN_FRAME_TYPES = [
|
|
||||||
'hyperlink'
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The LayoutController is responsible for supporting the
|
|
||||||
* Layout view. It arranges frames according to saved configuration
|
|
||||||
* and provides methods for updating these based on mouse
|
|
||||||
* movement.
|
|
||||||
* @memberof platform/features/layout
|
|
||||||
* @constructor
|
|
||||||
* @param {Scope} $scope the controller's Angular scope
|
|
||||||
*/
|
|
||||||
function LayoutController($scope, $element, openmct) {
|
|
||||||
var self = this,
|
|
||||||
callbackCount = 0;
|
|
||||||
|
|
||||||
this.$element = $element;
|
|
||||||
|
|
||||||
// Update grid size when it changed
|
|
||||||
function updateGridSize(layoutGrid) {
|
|
||||||
var oldSize = self.gridSize;
|
|
||||||
|
|
||||||
self.gridSize = layoutGrid || DEFAULT_GRID_SIZE;
|
|
||||||
|
|
||||||
// Only update panel positions if this actually changed things
|
|
||||||
if (self.gridSize[0] !== oldSize[0] ||
|
|
||||||
self.gridSize[1] !== oldSize[1]) {
|
|
||||||
self.layoutPanels(Object.keys(self.positions));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Position a panel after a drop event
|
|
||||||
function handleDrop(e, id, position) {
|
|
||||||
if (e.defaultPrevented) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.configuration = $scope.configuration || {};
|
|
||||||
$scope.configuration.panels = $scope.configuration.panels || {};
|
|
||||||
|
|
||||||
self.openmct.objects.get(id).then(function (object) {
|
|
||||||
$scope.configuration.panels[id] = {
|
|
||||||
position: [
|
|
||||||
Math.floor(position.x / self.gridSize[0]),
|
|
||||||
Math.floor(position.y / self.gridSize[1])
|
|
||||||
],
|
|
||||||
dimensions: self.defaultDimensions(),
|
|
||||||
hasFrame: self.getDefaultFrame(object.type)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Store the id so that the newly-dropped object
|
|
||||||
// gets selected during refresh composition
|
|
||||||
self.droppedIdToSelectAfterRefresh = id;
|
|
||||||
|
|
||||||
self.commit();
|
|
||||||
|
|
||||||
// Populate template-facing position for this id
|
|
||||||
self.rawPositions[id] = $scope.configuration.panels[id];
|
|
||||||
self.populatePosition(id);
|
|
||||||
refreshComposition();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Layout may contain embedded views which will
|
|
||||||
// listen for drops, so call preventDefault() so
|
|
||||||
// that they can recognize that this event is handled.
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
//Will fetch fully contextualized composed objects, and populate
|
|
||||||
// scope with them.
|
|
||||||
function refreshComposition() {
|
|
||||||
//Keep a track of how many composition callbacks have been made
|
|
||||||
var thisCount = ++callbackCount;
|
|
||||||
|
|
||||||
$scope.domainObject.useCapability('composition').then(function (composition) {
|
|
||||||
var ids;
|
|
||||||
|
|
||||||
//Is this callback for the most recent composition
|
|
||||||
// request? If not, discard it. Prevents race condition
|
|
||||||
if (thisCount === callbackCount) {
|
|
||||||
ids = composition.map(function (object) {
|
|
||||||
return object.getId();
|
|
||||||
}) || [];
|
|
||||||
|
|
||||||
$scope.composition = composition;
|
|
||||||
self.layoutPanels(ids);
|
|
||||||
self.setFrames(ids);
|
|
||||||
|
|
||||||
if (self.selectedId &&
|
|
||||||
self.selectedId !== $scope.domainObject.getId() &&
|
|
||||||
composition.indexOf(self.selectedId) === -1) {
|
|
||||||
// Click triggers selection of layout parent.
|
|
||||||
self.$element[0].click();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// End drag; we don't want to put $scope into this
|
|
||||||
// because it triggers "cpws" (copy window or scope)
|
|
||||||
// errors in Angular.
|
|
||||||
this.endDragInScope = function () {
|
|
||||||
// Write to configuration; this is watched and
|
|
||||||
// saved by the EditRepresenter.
|
|
||||||
$scope.configuration =
|
|
||||||
$scope.configuration || {};
|
|
||||||
|
|
||||||
$scope.configuration.panels =
|
|
||||||
$scope.configuration.panels || {};
|
|
||||||
|
|
||||||
$scope.configuration.panels[self.activeDragId] =
|
|
||||||
$scope.configuration.panels[self.activeDragId] || {};
|
|
||||||
|
|
||||||
$scope.configuration.panels[self.activeDragId].position =
|
|
||||||
self.rawPositions[self.activeDragId].position;
|
|
||||||
$scope.configuration.panels[self.activeDragId].dimensions =
|
|
||||||
self.rawPositions[self.activeDragId].dimensions;
|
|
||||||
|
|
||||||
self.commit();
|
|
||||||
};
|
|
||||||
|
|
||||||
// Sets the selectable object in response to the selection change event.
|
|
||||||
function setSelection(selectable) {
|
|
||||||
var selection = selectable[0];
|
|
||||||
|
|
||||||
if (!selection) {
|
|
||||||
delete self.selectedId;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.selectedId = selection.context.oldItem.getId();
|
|
||||||
self.drilledIn = undefined;
|
|
||||||
self.selectable = selectable;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.positions = {};
|
|
||||||
this.rawPositions = {};
|
|
||||||
this.gridSize = DEFAULT_GRID_SIZE;
|
|
||||||
this.$scope = $scope;
|
|
||||||
this.drilledIn = undefined;
|
|
||||||
this.openmct = openmct;
|
|
||||||
|
|
||||||
// Watch for changes to the grid size in the model
|
|
||||||
$scope.$watch("model.layoutGrid", updateGridSize);
|
|
||||||
|
|
||||||
// Update composed objects on screen, and position panes
|
|
||||||
$scope.$watchCollection("model.composition", refreshComposition);
|
|
||||||
|
|
||||||
openmct.selection.on('change', setSelection);
|
|
||||||
|
|
||||||
$scope.$on("$destroy", function () {
|
|
||||||
openmct.selection.off("change", setSelection);
|
|
||||||
self.unlisten();
|
|
||||||
});
|
|
||||||
|
|
||||||
$scope.$on("mctDrop", handleDrop);
|
|
||||||
|
|
||||||
self.unlisten = self.$scope.domainObject.getCapability('mutation').listen(function (model) {
|
|
||||||
$scope.configuration = model.configuration.layout;
|
|
||||||
$scope.model = model;
|
|
||||||
var panels = $scope.configuration.panels;
|
|
||||||
|
|
||||||
Object.keys(panels).forEach(function (key) {
|
|
||||||
if (self.frames && self.frames.hasOwnProperty(key)) {
|
|
||||||
self.frames[key] = panels[key].hasFrame;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Utility function to copy raw positions from configuration,
|
|
||||||
// without writing directly to configuration (to avoid triggering
|
|
||||||
// persistence from watchers during drags).
|
|
||||||
function shallowCopy(obj, keys) {
|
|
||||||
var copy = {};
|
|
||||||
keys.forEach(function (k) {
|
|
||||||
copy[k] = obj[k];
|
|
||||||
});
|
|
||||||
return copy;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the frames value. If a configuration panel has "hasFrame' property,
|
|
||||||
* use that value, otherwise set a default value. A 'hyperlink' object should
|
|
||||||
* have no frame by default.
|
|
||||||
*
|
|
||||||
* @param {string[]} ids the object ids
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
LayoutController.prototype.setFrames = function (ids) {
|
|
||||||
var panels = shallowCopy(this.$scope.configuration.panels || {}, ids);
|
|
||||||
this.frames = {};
|
|
||||||
|
|
||||||
this.$scope.composition.forEach(function (object) {
|
|
||||||
var id = object.getId();
|
|
||||||
panels[id] = panels[id] || {};
|
|
||||||
|
|
||||||
if (panels[id].hasOwnProperty('hasFrame')) {
|
|
||||||
this.frames[id] = panels[id].hasFrame;
|
|
||||||
} else {
|
|
||||||
this.frames[id] = this.getDefaultFrame(object.getModel().type);
|
|
||||||
}
|
|
||||||
}, this);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the default value for frame.
|
|
||||||
*
|
|
||||||
* @param type the domain object type
|
|
||||||
* @return {boolean} true if the object should have
|
|
||||||
* frame by default, false, otherwise
|
|
||||||
*/
|
|
||||||
LayoutController.prototype.getDefaultFrame = function (type) {
|
|
||||||
return DEFAULT_HIDDEN_FRAME_TYPES.indexOf(type) === -1;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Convert from { positions: ..., dimensions: ... } to an
|
|
||||||
// appropriate ng-style argument, to position frames.
|
|
||||||
LayoutController.prototype.convertPosition = function (raw) {
|
|
||||||
var gridSize = this.gridSize;
|
|
||||||
// Multiply position/dimensions by grid size
|
|
||||||
return {
|
|
||||||
left: (gridSize[0] * raw.position[0]) + 'px',
|
|
||||||
top: (gridSize[1] * raw.position[1]) + 'px',
|
|
||||||
width: (gridSize[0] * raw.dimensions[0]) + 'px',
|
|
||||||
height: (gridSize[1] * raw.dimensions[1]) + 'px',
|
|
||||||
minWidth: (gridSize[0] * raw.dimensions[0]) + 'px',
|
|
||||||
minHeight: (gridSize[1] * raw.dimensions[1]) + 'px'
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// Generate default positions for a new panel
|
|
||||||
LayoutController.prototype.defaultDimensions = function () {
|
|
||||||
var gridSize = this.gridSize;
|
|
||||||
return MINIMUM_FRAME_SIZE.map(function (min, i) {
|
|
||||||
return Math.max(
|
|
||||||
Math.ceil(min / gridSize[i]),
|
|
||||||
DEFAULT_DIMENSIONS[i]
|
|
||||||
);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Generate a default position (in its raw format) for a frame.
|
|
||||||
// Use an index to ensure that default positions are unique.
|
|
||||||
LayoutController.prototype.defaultPosition = function (index) {
|
|
||||||
return {
|
|
||||||
position: [index, index],
|
|
||||||
dimensions: this.defaultDimensions()
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// Store a computed position for a contained frame by its
|
|
||||||
// domain object id. Called in a forEach loop, so arguments
|
|
||||||
// are as expected there.
|
|
||||||
LayoutController.prototype.populatePosition = function (id, index) {
|
|
||||||
this.rawPositions[id] =
|
|
||||||
this.rawPositions[id] || this.defaultPosition(index || 0);
|
|
||||||
this.positions[id] =
|
|
||||||
this.convertPosition(this.rawPositions[id]);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a style object for a frame with the specified domain
|
|
||||||
* object identifier, suitable for use in an `ng-style`
|
|
||||||
* directive to position a frame as configured for this layout.
|
|
||||||
* @param {string} id the object identifier
|
|
||||||
* @returns {Object.<string, string>} an object with
|
|
||||||
* appropriate left, width, etc fields for positioning
|
|
||||||
*/
|
|
||||||
LayoutController.prototype.getFrameStyle = function (id) {
|
|
||||||
// Called in a loop, so just look up; the "positions"
|
|
||||||
// object is kept up to date by a watch.
|
|
||||||
return this.positions[id];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start a drag gesture to move/resize a frame.
|
|
||||||
*
|
|
||||||
* The provided position and dimensions factors will determine
|
|
||||||
* whether this is a move or a resize, and what type it
|
|
||||||
* will be. For instance, a position factor of [1, 1]
|
|
||||||
* will move a frame along with the mouse as the drag
|
|
||||||
* proceeds, while a dimension factor of [0, 0] will leave
|
|
||||||
* dimensions unchanged. Combining these in different
|
|
||||||
* ways results in different handles; a position factor of
|
|
||||||
* [1, 0] and a dimensions factor of [-1, 0] will implement
|
|
||||||
* a left-edge resize, as the horizontal position will move
|
|
||||||
* with the mouse while the horizontal dimensions shrink in
|
|
||||||
* kind (and vertical properties remain unmodified.)
|
|
||||||
*
|
|
||||||
* @param {string} id the identifier of the domain object
|
|
||||||
* in the frame being manipulated
|
|
||||||
* @param {number[]} posFactor the position factor
|
|
||||||
* @param {number[]} dimFactor the dimensions factor
|
|
||||||
*/
|
|
||||||
LayoutController.prototype.startDrag = function (id, posFactor, dimFactor) {
|
|
||||||
this.activeDragId = id;
|
|
||||||
this.activeDrag = new LayoutDrag(
|
|
||||||
this.rawPositions[id],
|
|
||||||
posFactor,
|
|
||||||
dimFactor,
|
|
||||||
this.gridSize
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Continue an active drag gesture.
|
|
||||||
* @param {number[]} delta the offset, in pixels,
|
|
||||||
* of the current pointer position, relative
|
|
||||||
* to its position when the drag started
|
|
||||||
*/
|
|
||||||
LayoutController.prototype.continueDrag = function (delta) {
|
|
||||||
if (this.activeDrag) {
|
|
||||||
this.rawPositions[this.activeDragId] =
|
|
||||||
this.activeDrag.getAdjustedPosition(delta);
|
|
||||||
this.populatePosition(this.activeDragId);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compute panel positions based on the layout's object model.
|
|
||||||
* Defined as member function to facilitate testing.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
LayoutController.prototype.layoutPanels = function (ids) {
|
|
||||||
var configuration = this.$scope.configuration || {},
|
|
||||||
self = this;
|
|
||||||
|
|
||||||
// Pull panel positions from configuration
|
|
||||||
this.rawPositions =
|
|
||||||
shallowCopy(configuration.panels || {}, ids);
|
|
||||||
|
|
||||||
// Clear prior computed positions
|
|
||||||
this.positions = {};
|
|
||||||
|
|
||||||
// Update width/height that we are tracking
|
|
||||||
this.gridSize =
|
|
||||||
(this.$scope.model || {}).layoutGrid || DEFAULT_GRID_SIZE;
|
|
||||||
|
|
||||||
// Compute positions and add defaults where needed
|
|
||||||
ids.forEach(function (id, index) {
|
|
||||||
self.populatePosition(id, index);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* End the active drag gesture. This will update the
|
|
||||||
* view configuration.
|
|
||||||
*/
|
|
||||||
LayoutController.prototype.endDrag = function () {
|
|
||||||
this.dragInProgress = true;
|
|
||||||
|
|
||||||
setTimeout(function () {
|
|
||||||
this.dragInProgress = false;
|
|
||||||
}.bind(this), 0);
|
|
||||||
|
|
||||||
this.endDragInScope();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the object is currently selected.
|
|
||||||
*
|
|
||||||
* @param {string} obj the object to check for selection
|
|
||||||
* @returns {boolean} true if selected, otherwise false
|
|
||||||
*/
|
|
||||||
LayoutController.prototype.selected = function (obj) {
|
|
||||||
var sobj = this.openmct.selection.get()[0];
|
|
||||||
return (sobj && sobj.context.oldItem.getId() === obj.getId()) ? true : false;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bypasses selection if drag is in progress.
|
|
||||||
*
|
|
||||||
* @param event the angular event object
|
|
||||||
*/
|
|
||||||
LayoutController.prototype.bypassSelection = function (event) {
|
|
||||||
if (this.dragInProgress) {
|
|
||||||
if (event) {
|
|
||||||
event.stopPropagation();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the domain object is drilled in.
|
|
||||||
*
|
|
||||||
* @param domainObject the domain object
|
|
||||||
* @return true if the object is drilled in, false otherwise
|
|
||||||
*/
|
|
||||||
LayoutController.prototype.isDrilledIn = function (domainObject) {
|
|
||||||
return this.drilledIn === domainObject.getId();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Puts the given object in the drilled-in mode.
|
|
||||||
*
|
|
||||||
* @param event the angular event object
|
|
||||||
* @param domainObject the domain object
|
|
||||||
*/
|
|
||||||
LayoutController.prototype.drill = function (event, domainObject) {
|
|
||||||
if (event) {
|
|
||||||
event.stopPropagation();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!domainObject.getCapability('editor').inEditContext()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!domainObject.hasCapability('composition')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disable since fixed position doesn't use the selection API yet
|
|
||||||
if (domainObject.getModel().type === 'telemetry.fixed') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.drilledIn = domainObject.getId();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the object has frame.
|
|
||||||
*
|
|
||||||
* @param {object} obj the object
|
|
||||||
* @return {boolean} true if object has frame, otherwise false
|
|
||||||
*/
|
|
||||||
LayoutController.prototype.hasFrame = function (obj) {
|
|
||||||
return this.frames[obj.getId()];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the size of the grid, in pixels. The returned array
|
|
||||||
* is in the form `[x, y]`.
|
|
||||||
* @returns {number[]} the grid size
|
|
||||||
*/
|
|
||||||
LayoutController.prototype.getGridSize = function () {
|
|
||||||
return this.gridSize;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the selection context.
|
|
||||||
*
|
|
||||||
* @param domainObject the domain object
|
|
||||||
* @returns {object} the context object which includes item and oldItem
|
|
||||||
*/
|
|
||||||
LayoutController.prototype.getContext = function (domainObject) {
|
|
||||||
return {
|
|
||||||
item: domainObject.useCapability('adapter'),
|
|
||||||
oldItem: domainObject
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
LayoutController.prototype.commit = function () {
|
|
||||||
var model = this.$scope.model;
|
|
||||||
model.configuration = model.configuration || {};
|
|
||||||
model.configuration.layout = this.$scope.configuration;
|
|
||||||
|
|
||||||
this.$scope.domainObject.useCapability('mutation', function () {
|
|
||||||
return model;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Selects a newly-dropped object.
|
|
||||||
*
|
|
||||||
* @param classSelector the css class selector
|
|
||||||
* @param domainObject the domain object
|
|
||||||
*/
|
|
||||||
LayoutController.prototype.selectIfNew = function (selector, domainObject) {
|
|
||||||
if (domainObject.getId() === this.droppedIdToSelectAfterRefresh) {
|
|
||||||
setTimeout(function () {
|
|
||||||
$('[data-layout-id="' + selector + '"]')[0].click();
|
|
||||||
delete this.droppedIdToSelectAfterRefresh;
|
|
||||||
}.bind(this), 0);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return LayoutController;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
@@ -1,479 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
[
|
|
||||||
"../src/LayoutController",
|
|
||||||
"zepto"
|
|
||||||
],
|
|
||||||
function (
|
|
||||||
LayoutController,
|
|
||||||
$
|
|
||||||
) {
|
|
||||||
|
|
||||||
describe("The Layout controller", function () {
|
|
||||||
var mockScope,
|
|
||||||
mockEvent,
|
|
||||||
testModel,
|
|
||||||
testConfiguration,
|
|
||||||
controller,
|
|
||||||
mockCompositionCapability,
|
|
||||||
mockComposition,
|
|
||||||
mockCompositionObjects,
|
|
||||||
mockOpenMCT,
|
|
||||||
mockSelection,
|
|
||||||
mockDomainObjectCapability,
|
|
||||||
mockObjects,
|
|
||||||
unlistenFunc,
|
|
||||||
$element = [],
|
|
||||||
selectable = [];
|
|
||||||
|
|
||||||
function mockPromise(value) {
|
|
||||||
return {
|
|
||||||
then: function (thenFunc) {
|
|
||||||
return mockPromise(thenFunc(value));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function mockDomainObject(id) {
|
|
||||||
return {
|
|
||||||
getId: function () {
|
|
||||||
return id;
|
|
||||||
},
|
|
||||||
useCapability: function () {
|
|
||||||
return mockCompositionCapability;
|
|
||||||
},
|
|
||||||
getModel: function () {
|
|
||||||
if (id === 'b') {
|
|
||||||
return {
|
|
||||||
type : 'hyperlink'
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getCapability: function () {
|
|
||||||
return mockDomainObjectCapability;
|
|
||||||
},
|
|
||||||
hasCapability: function (param) {
|
|
||||||
if (param === 'composition') {
|
|
||||||
return id !== 'b';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
type: "testType"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
mockScope = jasmine.createSpyObj(
|
|
||||||
"$scope",
|
|
||||||
["$watch", "$watchCollection", "$on"]
|
|
||||||
);
|
|
||||||
mockEvent = jasmine.createSpyObj(
|
|
||||||
'event',
|
|
||||||
['preventDefault', 'stopPropagation']
|
|
||||||
);
|
|
||||||
|
|
||||||
testModel = {};
|
|
||||||
|
|
||||||
mockComposition = ["a", "b", "c"];
|
|
||||||
mockCompositionObjects = mockComposition.map(mockDomainObject);
|
|
||||||
|
|
||||||
testConfiguration = {
|
|
||||||
panels: {
|
|
||||||
a: {
|
|
||||||
position: [20, 10],
|
|
||||||
dimensions: [5, 5]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
unlistenFunc = jasmine.createSpy("unlisten");
|
|
||||||
mockDomainObjectCapability = jasmine.createSpyObj('capability',
|
|
||||||
['inEditContext', 'listen']
|
|
||||||
);
|
|
||||||
mockDomainObjectCapability.listen.and.returnValue(unlistenFunc);
|
|
||||||
|
|
||||||
mockCompositionCapability = mockPromise(mockCompositionObjects);
|
|
||||||
|
|
||||||
mockScope.domainObject = mockDomainObject("mockDomainObject");
|
|
||||||
mockScope.model = testModel;
|
|
||||||
mockScope.configuration = testConfiguration;
|
|
||||||
|
|
||||||
selectable[0] = {
|
|
||||||
context: {
|
|
||||||
oldItem: mockScope.domainObject
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
mockSelection = jasmine.createSpyObj("selection", [
|
|
||||||
'select',
|
|
||||||
'on',
|
|
||||||
'off',
|
|
||||||
'get'
|
|
||||||
]);
|
|
||||||
mockSelection.get.and.returnValue(selectable);
|
|
||||||
|
|
||||||
mockObjects = jasmine.createSpyObj('objects', [
|
|
||||||
'get'
|
|
||||||
]);
|
|
||||||
mockObjects.get.and.returnValue(mockPromise(mockDomainObject("mockObject")));
|
|
||||||
mockOpenMCT = {
|
|
||||||
selection: mockSelection,
|
|
||||||
objects: mockObjects
|
|
||||||
};
|
|
||||||
|
|
||||||
$element = $('<div></div>');
|
|
||||||
$(document).find('body').append($element);
|
|
||||||
spyOn($element[0], 'click');
|
|
||||||
|
|
||||||
spyOn(mockScope.domainObject, "useCapability").and.callThrough();
|
|
||||||
|
|
||||||
controller = new LayoutController(mockScope, $element, mockOpenMCT);
|
|
||||||
spyOn(controller, "layoutPanels").and.callThrough();
|
|
||||||
spyOn(controller, "commit");
|
|
||||||
|
|
||||||
jasmine.clock().install();
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(function () {
|
|
||||||
$element.remove();
|
|
||||||
jasmine.clock().uninstall();
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it("listens for selection change events", function () {
|
|
||||||
expect(mockOpenMCT.selection.on).toHaveBeenCalledWith(
|
|
||||||
'change',
|
|
||||||
jasmine.any(Function)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("cleans up on scope destroy", function () {
|
|
||||||
expect(mockScope.$on).toHaveBeenCalledWith(
|
|
||||||
'$destroy',
|
|
||||||
jasmine.any(Function)
|
|
||||||
);
|
|
||||||
|
|
||||||
mockScope.$on.calls.all()[0].args[1]();
|
|
||||||
|
|
||||||
expect(mockOpenMCT.selection.off).toHaveBeenCalledWith(
|
|
||||||
'change',
|
|
||||||
jasmine.any(Function)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Model changes will indicate that panel positions
|
|
||||||
// may have changed, for instance.
|
|
||||||
it("watches for changes to composition", function () {
|
|
||||||
expect(mockScope.$watchCollection).toHaveBeenCalledWith(
|
|
||||||
"model.composition",
|
|
||||||
jasmine.any(Function)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Retrieves updated composition from composition capability", function () {
|
|
||||||
mockScope.$watchCollection.calls.mostRecent().args[1]();
|
|
||||||
expect(mockScope.domainObject.useCapability).toHaveBeenCalledWith(
|
|
||||||
"composition"
|
|
||||||
);
|
|
||||||
expect(controller.layoutPanels).toHaveBeenCalledWith(
|
|
||||||
mockComposition
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Is robust to concurrent changes to composition", function () {
|
|
||||||
var secondMockComposition = ["a", "b", "c", "d"],
|
|
||||||
secondMockCompositionObjects = secondMockComposition.map(mockDomainObject),
|
|
||||||
firstCompositionCB,
|
|
||||||
secondCompositionCB;
|
|
||||||
|
|
||||||
spyOn(mockCompositionCapability, "then");
|
|
||||||
mockScope.$watchCollection.calls.mostRecent().args[1]();
|
|
||||||
mockScope.$watchCollection.calls.mostRecent().args[1]();
|
|
||||||
|
|
||||||
firstCompositionCB = mockCompositionCapability.then.calls.all()[0].args[0];
|
|
||||||
secondCompositionCB = mockCompositionCapability.then.calls.all()[1].args[0];
|
|
||||||
|
|
||||||
//Resolve promises in reverse order
|
|
||||||
secondCompositionCB(secondMockCompositionObjects);
|
|
||||||
firstCompositionCB(mockCompositionObjects);
|
|
||||||
|
|
||||||
//Expect the promise call that was initiated most recently to
|
|
||||||
// be the one used to populate scope, irrespective of order that
|
|
||||||
// it was eventually resolved
|
|
||||||
expect(mockScope.composition).toBe(secondMockCompositionObjects);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it("provides styles for frames, from configuration", function () {
|
|
||||||
mockScope.$watchCollection.calls.mostRecent().args[1]();
|
|
||||||
expect(controller.getFrameStyle("a")).toEqual({
|
|
||||||
top: "320px",
|
|
||||||
left: "640px",
|
|
||||||
width: "160px",
|
|
||||||
height: "160px",
|
|
||||||
minWidth : '160px',
|
|
||||||
minHeight : '160px'
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("provides default styles for frames", function () {
|
|
||||||
var styleB, styleC;
|
|
||||||
|
|
||||||
// b and c do not have configured positions
|
|
||||||
mockScope.$watchCollection.calls.mostRecent().args[1]();
|
|
||||||
|
|
||||||
styleB = controller.getFrameStyle("b");
|
|
||||||
styleC = controller.getFrameStyle("c");
|
|
||||||
|
|
||||||
// Should have a position, but we don't care what
|
|
||||||
expect(styleB.left).toBeDefined();
|
|
||||||
expect(styleB.top).toBeDefined();
|
|
||||||
expect(styleC.left).toBeDefined();
|
|
||||||
expect(styleC.top).toBeDefined();
|
|
||||||
|
|
||||||
// Should have ensured some difference in position
|
|
||||||
expect(styleB).not.toEqual(styleC);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("allows panels to be dragged", function () {
|
|
||||||
// Populate scope
|
|
||||||
mockScope.$watchCollection.calls.mostRecent().args[1]();
|
|
||||||
|
|
||||||
// Verify precondition
|
|
||||||
expect(testConfiguration.panels.b).not.toBeDefined();
|
|
||||||
|
|
||||||
// Do a drag
|
|
||||||
controller.startDrag("b", [1, 1], [0, 0]);
|
|
||||||
controller.continueDrag([100, 100]);
|
|
||||||
controller.endDrag();
|
|
||||||
|
|
||||||
// We do not look closely at the details here;
|
|
||||||
// that is tested in LayoutDragSpec. Just make sure
|
|
||||||
// that a configuration for b has been defined.
|
|
||||||
expect(testConfiguration.panels.b).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it("invokes commit after drag", function () {
|
|
||||||
// Populate scope
|
|
||||||
mockScope.$watchCollection.calls.mostRecent().args[1]();
|
|
||||||
|
|
||||||
// Do a drag
|
|
||||||
controller.startDrag("b", [1, 1], [0, 0]);
|
|
||||||
controller.continueDrag([100, 100]);
|
|
||||||
controller.endDrag();
|
|
||||||
|
|
||||||
expect(controller.commit).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("listens for drop events", function () {
|
|
||||||
// Layout should position panels according to
|
|
||||||
// where the user dropped them, so it needs to
|
|
||||||
// listen for drop events.
|
|
||||||
expect(mockScope.$on).toHaveBeenCalledWith(
|
|
||||||
'mctDrop',
|
|
||||||
jasmine.any(Function)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Verify precondition
|
|
||||||
expect(testConfiguration.panels.d).not.toBeDefined();
|
|
||||||
|
|
||||||
// Notify that a drop occurred
|
|
||||||
mockScope.$on.calls.mostRecent().args[1](
|
|
||||||
mockEvent,
|
|
||||||
'd',
|
|
||||||
{ x: 300, y: 100 }
|
|
||||||
);
|
|
||||||
expect(testConfiguration.panels.d).toBeDefined();
|
|
||||||
expect(mockEvent.preventDefault).toHaveBeenCalled();
|
|
||||||
expect(controller.commit).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("ignores drops when default has been prevented", function () {
|
|
||||||
// Avoids redundant drop-handling, WTD-1233
|
|
||||||
mockEvent.defaultPrevented = true;
|
|
||||||
|
|
||||||
// Notify that a drop occurred
|
|
||||||
mockScope.$on.calls.mostRecent().args[1](
|
|
||||||
mockEvent,
|
|
||||||
'd',
|
|
||||||
{ x: 300, y: 100 }
|
|
||||||
);
|
|
||||||
expect(testConfiguration.panels.d).not.toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("ensures a minimum frame size", function () {
|
|
||||||
var styleB;
|
|
||||||
|
|
||||||
// Start with a very small frame size
|
|
||||||
testModel.layoutGrid = [1, 1];
|
|
||||||
|
|
||||||
// White-boxy; we know which watch is which
|
|
||||||
mockScope.$watch.calls.all()[0].args[1](testModel.layoutGrid);
|
|
||||||
mockScope.$watchCollection.calls.all()[0].args[1](testModel.composition);
|
|
||||||
|
|
||||||
styleB = controller.getFrameStyle("b");
|
|
||||||
|
|
||||||
// Resulting size should still be reasonably large pixel-size
|
|
||||||
expect(parseInt(styleB.width, 10)).toBeGreaterThan(63);
|
|
||||||
expect(parseInt(styleB.width, 10)).toBeGreaterThan(31);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("ensures a minimum frame size on drop", function () {
|
|
||||||
var style;
|
|
||||||
|
|
||||||
// Start with a very small frame size
|
|
||||||
testModel.layoutGrid = [1, 1];
|
|
||||||
mockScope.$watch.calls.all()[0].args[1](testModel.layoutGrid);
|
|
||||||
|
|
||||||
// Add a new object to the composition
|
|
||||||
mockComposition = ["a", "b", "c", "d"];
|
|
||||||
mockCompositionObjects = mockComposition.map(mockDomainObject);
|
|
||||||
mockCompositionCapability = mockPromise(mockCompositionObjects);
|
|
||||||
|
|
||||||
// Notify that a drop occurred
|
|
||||||
mockScope.$on.calls.mostRecent().args[1](
|
|
||||||
mockEvent,
|
|
||||||
'd',
|
|
||||||
{ x: 300, y: 100 }
|
|
||||||
);
|
|
||||||
|
|
||||||
style = controller.getFrameStyle("d");
|
|
||||||
|
|
||||||
// Resulting size should still be reasonably large pixel-size
|
|
||||||
expect(parseInt(style.width, 10)).toBeGreaterThan(63);
|
|
||||||
expect(parseInt(style.height, 10)).toBeGreaterThan(31);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("updates positions of existing objects on a drop", function () {
|
|
||||||
var oldStyle;
|
|
||||||
|
|
||||||
mockScope.$watchCollection.calls.mostRecent().args[1]();
|
|
||||||
|
|
||||||
oldStyle = controller.getFrameStyle("b");
|
|
||||||
|
|
||||||
expect(oldStyle).toBeDefined();
|
|
||||||
|
|
||||||
// ...drop event...
|
|
||||||
mockScope.$on.calls.mostRecent()
|
|
||||||
.args[1](mockEvent, 'b', { x: 300, y: 100 });
|
|
||||||
|
|
||||||
expect(controller.getFrameStyle("b"))
|
|
||||||
.not.toEqual(oldStyle);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("allows objects to be selected", function () {
|
|
||||||
mockScope.$watchCollection.calls.mostRecent().args[1]();
|
|
||||||
var childObj = mockCompositionObjects[0];
|
|
||||||
selectable[0].context.oldItem = childObj;
|
|
||||||
mockOpenMCT.selection.on.calls.mostRecent().args[1](selectable);
|
|
||||||
|
|
||||||
expect(controller.selected(childObj)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("prevents event bubbling while drag is in progress", function () {
|
|
||||||
mockScope.$watchCollection.calls.mostRecent().args[1]();
|
|
||||||
var childObj = mockCompositionObjects[0];
|
|
||||||
|
|
||||||
// Do a drag
|
|
||||||
controller.startDrag(childObj.getId(), [1, 1], [0, 0]);
|
|
||||||
controller.continueDrag([100, 100]);
|
|
||||||
controller.endDrag();
|
|
||||||
|
|
||||||
// Because mouse position could cause the parent object to be selected, this should be ignored.
|
|
||||||
controller.bypassSelection(mockEvent);
|
|
||||||
|
|
||||||
expect(mockEvent.stopPropagation).toHaveBeenCalled();
|
|
||||||
|
|
||||||
// Shoud be able to select another object when dragging is done.
|
|
||||||
jasmine.clock().tick(0);
|
|
||||||
mockEvent.stopPropagation.calls.reset();
|
|
||||||
controller.bypassSelection(mockEvent);
|
|
||||||
|
|
||||||
expect(mockEvent.stopPropagation).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("shows frames by default", function () {
|
|
||||||
mockScope.$watchCollection.calls.mostRecent().args[1]();
|
|
||||||
|
|
||||||
expect(controller.hasFrame(mockCompositionObjects[0])).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("hyperlinks hide frame by default", function () {
|
|
||||||
mockScope.$watchCollection.calls.mostRecent().args[1]();
|
|
||||||
|
|
||||||
expect(controller.hasFrame(mockCompositionObjects[1])).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("selects the parent object when selected object is removed", function () {
|
|
||||||
mockScope.$watchCollection.calls.mostRecent().args[1]();
|
|
||||||
var childObj = mockCompositionObjects[0];
|
|
||||||
selectable[0].context.oldItem = childObj;
|
|
||||||
mockOpenMCT.selection.on.calls.mostRecent().args[1](selectable);
|
|
||||||
|
|
||||||
var composition = ["b", "c"];
|
|
||||||
mockScope.$watchCollection.calls.mostRecent().args[1](composition);
|
|
||||||
|
|
||||||
expect($element[0].click).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("allows objects to be drilled-in only when editing", function () {
|
|
||||||
mockScope.$watchCollection.calls.mostRecent().args[1]();
|
|
||||||
var childObj = mockCompositionObjects[0];
|
|
||||||
childObj.getCapability().inEditContext.and.returnValue(false);
|
|
||||||
controller.drill(mockEvent, childObj);
|
|
||||||
|
|
||||||
expect(controller.isDrilledIn(childObj)).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("allows objects to be drilled-in only if it has sub objects", function () {
|
|
||||||
mockScope.$watchCollection.calls.mostRecent().args[1]();
|
|
||||||
var childObj = mockCompositionObjects[1];
|
|
||||||
childObj.getCapability().inEditContext.and.returnValue(true);
|
|
||||||
controller.drill(mockEvent, childObj);
|
|
||||||
|
|
||||||
expect(controller.isDrilledIn(childObj)).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("selects a newly-dropped object", function () {
|
|
||||||
mockScope.$on.calls.mostRecent().args[1](
|
|
||||||
mockEvent,
|
|
||||||
'd',
|
|
||||||
{ x: 300, y: 100 }
|
|
||||||
);
|
|
||||||
|
|
||||||
var childObj = mockDomainObject("d");
|
|
||||||
var testElement = $("<div data-layout-id='some-id'></div>");
|
|
||||||
$element.append(testElement);
|
|
||||||
spyOn(testElement[0], 'click');
|
|
||||||
|
|
||||||
controller.selectIfNew('some-id', childObj);
|
|
||||||
jasmine.clock().tick(0);
|
|
||||||
|
|
||||||
expect(testElement[0].click).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define([
|
|
||||||
'./src/controllers/ListViewController',
|
|
||||||
'./src/directives/MCTGesture',
|
|
||||||
'./res/templates/listview.html',
|
|
||||||
'legacyRegistry'
|
|
||||||
], function (
|
|
||||||
ListViewController,
|
|
||||||
MCTGesture,
|
|
||||||
listViewTemplate,
|
|
||||||
legacyRegistry
|
|
||||||
) {
|
|
||||||
legacyRegistry.register("platform/features/listview", {
|
|
||||||
"name": "List View Plugin",
|
|
||||||
"description": "Allows folder contents to be shown in list format",
|
|
||||||
"extensions":
|
|
||||||
{
|
|
||||||
"views": [
|
|
||||||
{
|
|
||||||
"key": "list",
|
|
||||||
"type": "folder",
|
|
||||||
"name": "List",
|
|
||||||
"cssClass": "icon-list-view",
|
|
||||||
"template": listViewTemplate
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"controllers": [
|
|
||||||
{
|
|
||||||
"key": "ListViewController",
|
|
||||||
"implementation": ListViewController,
|
|
||||||
"depends": ["$scope", "formatService"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"directives": [
|
|
||||||
{
|
|
||||||
"key": "mctGesture",
|
|
||||||
"implementation" : MCTGesture,
|
|
||||||
"depends": ["gestureService"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
<!--
|
|
||||||
Open MCT, Copyright (c) 2014-2018, United States Government
|
|
||||||
as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
Administration. All rights reserved.
|
|
||||||
|
|
||||||
Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
"License"); you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
License for the specific language governing permissions and limitations
|
|
||||||
under the License.
|
|
||||||
|
|
||||||
Open MCT includes source code licensed under additional open source
|
|
||||||
licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
this source code distribution or the Licensing information page available
|
|
||||||
at runtime from the About dialog for additional information.
|
|
||||||
-->
|
|
||||||
<div ng-controller="ListViewController">
|
|
||||||
<table class="list-view">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th class="sortable"
|
|
||||||
ng-click="reverseSort = (orderByField === 'title')? !reverseSort:false; orderByField='title';"
|
|
||||||
ng-class="{
|
|
||||||
sort: orderByField == 'title',
|
|
||||||
asc: !reverseSort,
|
|
||||||
desc: reverseSort
|
|
||||||
}">
|
|
||||||
Name
|
|
||||||
</th>
|
|
||||||
|
|
||||||
|
|
||||||
<th class="sortable"
|
|
||||||
ng-click="reverseSort = (orderByField === 'type')? !reverseSort:false; orderByField='type';"
|
|
||||||
ng-class="{
|
|
||||||
sort: orderByField == 'type',
|
|
||||||
asc: !reverseSort,
|
|
||||||
desc: reverseSort
|
|
||||||
}">
|
|
||||||
Type
|
|
||||||
</th>
|
|
||||||
|
|
||||||
<th class="sortable"
|
|
||||||
ng-click="reverseSort = (orderByField === 'persisted')? !reverseSort:true; orderByField='persisted';"
|
|
||||||
ng-class="{
|
|
||||||
sort: orderByField == 'persisted',
|
|
||||||
asc: !reverseSort,
|
|
||||||
desc: reverseSort
|
|
||||||
}">
|
|
||||||
Created Date
|
|
||||||
</th>
|
|
||||||
|
|
||||||
<th class="sortable"
|
|
||||||
ng-click="reverseSort = (orderByField === 'modified')? !reverseSort:true; orderByField='modified';"
|
|
||||||
ng-class="{
|
|
||||||
sort: orderByField == 'modified',
|
|
||||||
asc: !reverseSort,
|
|
||||||
desc: reverseSort
|
|
||||||
}">
|
|
||||||
Update Date
|
|
||||||
</th>
|
|
||||||
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr ng-repeat="child in children|orderBy:orderByField:reverseSort"
|
|
||||||
mct-gesture="['menu','info']"
|
|
||||||
mct-object="child.asDomainObject"
|
|
||||||
ng-click="child.action.perform('navigate')">
|
|
||||||
<td>
|
|
||||||
<div class="l-flex-row">
|
|
||||||
<span class="flex-elem t-item-icon" ng-class="{ 'l-icon-link': child.location.isLink()}">
|
|
||||||
<span class="t-item-icon-glyph {{child.icon}}"></span>
|
|
||||||
</span>
|
|
||||||
<span class="t-title-label flex-elem grows">{{child.title}}</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>{{child.type}}</td>
|
|
||||||
<td>{{child.persisted}}</td>
|
|
||||||
<td>{{child.modified}}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(function () {
|
|
||||||
function ListViewController($scope, formatService) {
|
|
||||||
this.$scope = $scope;
|
|
||||||
$scope.orderByField = 'title';
|
|
||||||
$scope.reverseSort = false;
|
|
||||||
|
|
||||||
this.updateView();
|
|
||||||
var unlisten = $scope.domainObject.getCapability('mutation')
|
|
||||||
.listen(this.updateView.bind(this));
|
|
||||||
|
|
||||||
this.utc = formatService.getFormat('utc');
|
|
||||||
|
|
||||||
//Trigger digestive cycle with $apply to update list view
|
|
||||||
setTimeout(function () {
|
|
||||||
$scope.$apply();
|
|
||||||
});
|
|
||||||
|
|
||||||
$scope.$on('$destroy', function () {
|
|
||||||
unlisten();
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
ListViewController.prototype.updateView = function () {
|
|
||||||
this.$scope.domainObject.useCapability('composition')
|
|
||||||
.then(function (children) {
|
|
||||||
var formattedChildren = this.formatChildren(children);
|
|
||||||
this.$scope.children = formattedChildren;
|
|
||||||
this.$scope.data = {children: formattedChildren};
|
|
||||||
}.bind(this)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
ListViewController.prototype.formatChildren = function (children) {
|
|
||||||
return children.map(function (child) {
|
|
||||||
return {
|
|
||||||
icon: child.getCapability('type').getCssClass(),
|
|
||||||
title: child.getModel().name,
|
|
||||||
type: child.getCapability('type').getName(),
|
|
||||||
persisted: this.utc.format(child.getModel().persisted),
|
|
||||||
modified: this.utc.format(child.getModel().modified),
|
|
||||||
asDomainObject: child,
|
|
||||||
location: child.getCapability('location'),
|
|
||||||
action: child.getCapability('action')
|
|
||||||
};
|
|
||||||
}, this);
|
|
||||||
};
|
|
||||||
|
|
||||||
return ListViewController;
|
|
||||||
});
|
|
||||||
@@ -1,157 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
["../../src/controllers/ListViewController"],
|
|
||||||
function (ListViewController) {
|
|
||||||
describe("The Controller for the ListView", function () {
|
|
||||||
var scope,
|
|
||||||
unlistenFunc,
|
|
||||||
domainObject,
|
|
||||||
childObject,
|
|
||||||
childModel,
|
|
||||||
typeCapability,
|
|
||||||
mutationCapability,
|
|
||||||
formatService,
|
|
||||||
compositionPromise,
|
|
||||||
controller;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
unlistenFunc = jasmine.createSpy("unlisten");
|
|
||||||
|
|
||||||
mutationCapability = jasmine.createSpyObj(
|
|
||||||
"mutationCapability",
|
|
||||||
["listen"]
|
|
||||||
);
|
|
||||||
mutationCapability.listen.and.returnValue(unlistenFunc);
|
|
||||||
|
|
||||||
formatService = jasmine.createSpyObj(
|
|
||||||
"formatService",
|
|
||||||
["getFormat"]
|
|
||||||
);
|
|
||||||
formatService.getFormat.and.returnValue(jasmine.createSpyObj(
|
|
||||||
'utc',
|
|
||||||
["format"]
|
|
||||||
));
|
|
||||||
formatService.getFormat().format.and.callFake(function (v) {
|
|
||||||
return "formatted " + v;
|
|
||||||
});
|
|
||||||
|
|
||||||
typeCapability = jasmine.createSpyObj(
|
|
||||||
"typeCapability",
|
|
||||||
["getCssClass", "getName"]
|
|
||||||
);
|
|
||||||
typeCapability.getCssClass.and.returnValue("icon-folder");
|
|
||||||
typeCapability.getName.and.returnValue("Folder");
|
|
||||||
|
|
||||||
|
|
||||||
childModel = jasmine.createSpyObj(
|
|
||||||
"childModel",
|
|
||||||
["persisted", "modified", "name"]
|
|
||||||
);
|
|
||||||
childModel.persisted = 1496867697303;
|
|
||||||
childModel.modified = 1496867697303;
|
|
||||||
childModel.name = "Battery Charge Status";
|
|
||||||
|
|
||||||
childObject = jasmine.createSpyObj(
|
|
||||||
"childObject",
|
|
||||||
["getModel", "getCapability"]
|
|
||||||
);
|
|
||||||
childObject.getModel.and.returnValue(
|
|
||||||
childModel
|
|
||||||
);
|
|
||||||
|
|
||||||
childObject.getCapability.and.callFake(function (arg) {
|
|
||||||
if (arg === 'location') {
|
|
||||||
return '';
|
|
||||||
} else if (arg === 'type') {
|
|
||||||
return typeCapability;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
childObject.location = '';
|
|
||||||
|
|
||||||
domainObject = jasmine.createSpyObj(
|
|
||||||
"domainObject",
|
|
||||||
["getCapability", "useCapability"]
|
|
||||||
);
|
|
||||||
compositionPromise = Promise.resolve([childObject]);
|
|
||||||
domainObject.useCapability.and.returnValue(compositionPromise);
|
|
||||||
domainObject.getCapability.and.returnValue(
|
|
||||||
mutationCapability
|
|
||||||
);
|
|
||||||
|
|
||||||
scope = jasmine.createSpyObj(
|
|
||||||
"$scope",
|
|
||||||
["$on", "$apply"]
|
|
||||||
);
|
|
||||||
scope.domainObject = domainObject;
|
|
||||||
|
|
||||||
controller = new ListViewController(scope, formatService);
|
|
||||||
|
|
||||||
return compositionPromise;
|
|
||||||
});
|
|
||||||
|
|
||||||
it("uses the UTC time format", function () {
|
|
||||||
expect(formatService.getFormat).toHaveBeenCalledWith('utc');
|
|
||||||
});
|
|
||||||
|
|
||||||
it("updates the view", function () {
|
|
||||||
var child = scope.children[0];
|
|
||||||
var testChild = {
|
|
||||||
icon: "icon-folder",
|
|
||||||
title: "Battery Charge Status",
|
|
||||||
type: "Folder",
|
|
||||||
persisted: formatService.getFormat('utc')
|
|
||||||
.format(childModel.persisted),
|
|
||||||
modified: formatService.getFormat('utc')
|
|
||||||
.format(childModel.modified),
|
|
||||||
asDomainObject: childObject,
|
|
||||||
location: '',
|
|
||||||
action: childObject.getCapability('action')
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(child).toEqual(testChild);
|
|
||||||
});
|
|
||||||
it("updates the scope when mutation occurs", function () {
|
|
||||||
var applyPromise = new Promise(function (resolve) {
|
|
||||||
scope.$apply.and.callFake(resolve);
|
|
||||||
});
|
|
||||||
|
|
||||||
domainObject.useCapability.and.returnValue(Promise.resolve([]));
|
|
||||||
expect(mutationCapability.listen).toHaveBeenCalledWith(jasmine.any(Function));
|
|
||||||
mutationCapability.listen.calls.mostRecent().args[0]();
|
|
||||||
|
|
||||||
return applyPromise.then(function () {
|
|
||||||
expect(scope.children.length).toEqual(0);
|
|
||||||
expect(scope.$apply).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it("releases listeners on $destroy", function () {
|
|
||||||
expect(scope.$on).toHaveBeenCalledWith('$destroy', jasmine.any(Function));
|
|
||||||
scope.$on.calls.mostRecent().args[1]();
|
|
||||||
expect(unlistenFunc).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
["../../src/directives/MCTGesture"],
|
|
||||||
function (MCTGesture) {
|
|
||||||
describe("The Gesture Listener for the ListView items", function () {
|
|
||||||
var mctGesture,
|
|
||||||
gestureService,
|
|
||||||
scope,
|
|
||||||
element,
|
|
||||||
attrs,
|
|
||||||
attachedGesture;
|
|
||||||
beforeEach(function () {
|
|
||||||
attachedGesture = jasmine.createSpyObj(
|
|
||||||
"attachedGesture",
|
|
||||||
['destroy']
|
|
||||||
);
|
|
||||||
gestureService = jasmine.createSpyObj(
|
|
||||||
"gestureService",
|
|
||||||
["attachGestures"]
|
|
||||||
);
|
|
||||||
gestureService.attachGestures.and.returnValue(
|
|
||||||
attachedGesture
|
|
||||||
);
|
|
||||||
mctGesture = MCTGesture(gestureService);
|
|
||||||
});
|
|
||||||
it("creates a directive Object", function () {
|
|
||||||
expect(mctGesture).toBeDefined();
|
|
||||||
});
|
|
||||||
it("has link function that attaches gesture to gestureService",
|
|
||||||
function () {
|
|
||||||
attrs = {
|
|
||||||
mctGesture: "menu,info"
|
|
||||||
};
|
|
||||||
element = jasmine.createSpy("element");
|
|
||||||
scope = jasmine.createSpyObj(
|
|
||||||
"$scope",
|
|
||||||
["$on", "$eval"]
|
|
||||||
);
|
|
||||||
scope.domainObject = "fake domainObject";
|
|
||||||
mctGesture.link(scope, element, attrs);
|
|
||||||
expect(gestureService.attachGestures).toHaveBeenCalled();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
it("release gesture service on $destroy", function () {
|
|
||||||
attrs = {
|
|
||||||
mctGesture: "menu,info"
|
|
||||||
};
|
|
||||||
element = jasmine.createSpy("element");
|
|
||||||
scope = jasmine.createSpyObj(
|
|
||||||
"$scope",
|
|
||||||
["$on", "$eval"]
|
|
||||||
);
|
|
||||||
scope.domainObject = "fake domainObject";
|
|
||||||
mctGesture.link(scope, element, attrs);
|
|
||||||
expect(scope.$on).toHaveBeenCalledWith(
|
|
||||||
'$destroy',
|
|
||||||
jasmine.any(Function)
|
|
||||||
);
|
|
||||||
scope.$on.calls.mostRecent().args[1]();
|
|
||||||
expect(attachedGesture.destroy).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
@@ -1,294 +0,0 @@
|
|||||||
define([
|
|
||||||
"legacyRegistry",
|
|
||||||
"./src/controllers/NotebookController",
|
|
||||||
"./src/controllers/NewEntryController",
|
|
||||||
"./src/controllers/SelectSnapshotController",
|
|
||||||
"./src/controllers/LayoutNotebookController",
|
|
||||||
"./src/directives/MCTSnapshot",
|
|
||||||
"./src/directives/EntryDnd",
|
|
||||||
"./src/actions/ViewSnapshot",
|
|
||||||
"./src/actions/AnnotateSnapshot",
|
|
||||||
"./src/actions/RemoveEmbed",
|
|
||||||
"./src/actions/RemoveSnapshot",
|
|
||||||
"./src/actions/NewEntryContextual",
|
|
||||||
"./src/capabilities/NotebookCapability",
|
|
||||||
"./src/policies/CompositionPolicy",
|
|
||||||
"./res/templates/notebook.html",
|
|
||||||
"./res/templates/entry.html",
|
|
||||||
"./res/templates/annotation.html",
|
|
||||||
"./res/templates/notifications.html",
|
|
||||||
"../layout/res/templates/frame.html",
|
|
||||||
"./res/templates/controls/embedControl.html",
|
|
||||||
"./res/templates/controls/snapSelect.html"
|
|
||||||
], function (
|
|
||||||
legacyRegistry,
|
|
||||||
NotebookController,
|
|
||||||
NewEntryController,
|
|
||||||
SelectSnapshotController,
|
|
||||||
LayoutNotebookController,
|
|
||||||
MCTSnapshot,
|
|
||||||
MCTEntryDnd,
|
|
||||||
ViewSnapshotAction,
|
|
||||||
AnnotateSnapshotAction,
|
|
||||||
RemoveEmbedAction,
|
|
||||||
RemoveSnapshotAction,
|
|
||||||
newEntryAction,
|
|
||||||
NotebookCapability,
|
|
||||||
CompositionPolicy,
|
|
||||||
notebookTemplate,
|
|
||||||
entryTemplate,
|
|
||||||
annotationTemplate,
|
|
||||||
notificationsTemplate,
|
|
||||||
frameTemplate,
|
|
||||||
embedControlTemplate,
|
|
||||||
snapSelectTemplate
|
|
||||||
) {
|
|
||||||
legacyRegistry.register("platform/features/notebook", {
|
|
||||||
"name": "Notebook Plugin",
|
|
||||||
"description": "Create and save timestamped notes with embedded object snapshots.",
|
|
||||||
"extensions":
|
|
||||||
{
|
|
||||||
"types": [
|
|
||||||
{
|
|
||||||
"key": "notebook",
|
|
||||||
"name": "Notebook",
|
|
||||||
"cssClass": "icon-notebook",
|
|
||||||
"description": "Create and save timestamped notes with embedded object snapshots.",
|
|
||||||
"features": ["creation"],
|
|
||||||
"model": {
|
|
||||||
"entries": [],
|
|
||||||
"composition": [],
|
|
||||||
"entryTypes": [],
|
|
||||||
"defaultSort": "-createdOn"
|
|
||||||
},
|
|
||||||
"properties": [
|
|
||||||
{
|
|
||||||
"key": "defaultSort",
|
|
||||||
"name": "Default Sort",
|
|
||||||
"control": "select",
|
|
||||||
"options": [
|
|
||||||
{
|
|
||||||
"name": "Newest First",
|
|
||||||
"value": "-createdOn"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Oldest First",
|
|
||||||
"value": "createdOn"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"cssClass": "l-inline"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"views": [
|
|
||||||
{
|
|
||||||
"key": "notebook.view",
|
|
||||||
"type": "notebook",
|
|
||||||
"cssClass": "icon-notebook",
|
|
||||||
"name": "notebook",
|
|
||||||
"template": notebookTemplate,
|
|
||||||
"editable": false,
|
|
||||||
"uses": [
|
|
||||||
"composition",
|
|
||||||
"action"
|
|
||||||
],
|
|
||||||
"gestures": [
|
|
||||||
"drop"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"controllers": [
|
|
||||||
{
|
|
||||||
"key": "NotebookController",
|
|
||||||
"implementation": NotebookController,
|
|
||||||
"depends": [
|
|
||||||
"$scope",
|
|
||||||
"dialogService",
|
|
||||||
"popupService",
|
|
||||||
"agentService",
|
|
||||||
"objectService",
|
|
||||||
"navigationService",
|
|
||||||
"now",
|
|
||||||
"actionService",
|
|
||||||
"$timeout",
|
|
||||||
"$element",
|
|
||||||
"$sce"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "NewEntryController",
|
|
||||||
"implementation": NewEntryController,
|
|
||||||
"depends": ["$scope",
|
|
||||||
"$rootScope"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "selectSnapshotController",
|
|
||||||
"implementation": SelectSnapshotController,
|
|
||||||
"depends": ["$scope",
|
|
||||||
"$rootScope"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "LayoutNotebookController",
|
|
||||||
"implementation": LayoutNotebookController,
|
|
||||||
"depends": ["$scope"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"representations": [
|
|
||||||
{
|
|
||||||
"key": "draggedEntry",
|
|
||||||
"template": entryTemplate
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "frameLayoutNotebook",
|
|
||||||
"template": frameTemplate
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"templates": [
|
|
||||||
{
|
|
||||||
"key": "annotate-snapshot",
|
|
||||||
"template": annotationTemplate
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "notificationTemplate",
|
|
||||||
"template": notificationsTemplate
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"directives": [
|
|
||||||
{
|
|
||||||
"key": "mctSnapshot",
|
|
||||||
"implementation": MCTSnapshot,
|
|
||||||
"depends": [
|
|
||||||
"$rootScope",
|
|
||||||
"$document",
|
|
||||||
"exportImageService",
|
|
||||||
"dialogService",
|
|
||||||
"notificationService"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "mctEntryDnd",
|
|
||||||
"implementation": MCTEntryDnd,
|
|
||||||
"depends": [
|
|
||||||
"$rootScope",
|
|
||||||
"$compile",
|
|
||||||
"dndService",
|
|
||||||
"typeService",
|
|
||||||
"notificationService"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"actions": [
|
|
||||||
{
|
|
||||||
"key": "view-snapshot",
|
|
||||||
"implementation": ViewSnapshotAction,
|
|
||||||
"name": "View Snapshot",
|
|
||||||
"description": "View the large image in a modal",
|
|
||||||
"category": "embed",
|
|
||||||
"depends": [
|
|
||||||
"$compile"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "annotate-snapshot",
|
|
||||||
"implementation": AnnotateSnapshotAction,
|
|
||||||
"name": "Annotate Snapshot",
|
|
||||||
"cssClass": "icon-pencil labeled",
|
|
||||||
"description": "Annotate embed's snapshot",
|
|
||||||
"category": "embed",
|
|
||||||
"depends": [
|
|
||||||
"dialogService",
|
|
||||||
"dndService",
|
|
||||||
"$rootScope"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"key": "remove-embed",
|
|
||||||
"implementation": RemoveEmbedAction,
|
|
||||||
"name": "Remove...",
|
|
||||||
"cssClass": "icon-trash labeled",
|
|
||||||
"description": "Remove this embed",
|
|
||||||
"category": [
|
|
||||||
"embed",
|
|
||||||
"embed-no-snap"
|
|
||||||
],
|
|
||||||
"depends": [
|
|
||||||
"dialogService"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "remove-snapshot",
|
|
||||||
"implementation": RemoveSnapshotAction,
|
|
||||||
"name": "Remove Snapshot",
|
|
||||||
"cssClass": "icon-trash labeled",
|
|
||||||
"description": "Remove Snapshot of the embed",
|
|
||||||
"category": "embed",
|
|
||||||
"depends": [
|
|
||||||
"dialogService"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "notebook-new-entry",
|
|
||||||
"implementation": newEntryAction,
|
|
||||||
"name": "New Notebook Entry",
|
|
||||||
"cssClass": "icon-notebook labeled",
|
|
||||||
"description": "Add a new Notebook entry",
|
|
||||||
"category": [
|
|
||||||
"view-control"
|
|
||||||
],
|
|
||||||
"depends": [
|
|
||||||
"$compile",
|
|
||||||
"$rootScope",
|
|
||||||
"dialogService",
|
|
||||||
"notificationService",
|
|
||||||
"linkService"
|
|
||||||
],
|
|
||||||
"priority": "preferred"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"licenses": [
|
|
||||||
{
|
|
||||||
"name": "painterro",
|
|
||||||
"version": "0.2.65",
|
|
||||||
"author": "Ivan Borshchov",
|
|
||||||
"description": "Painterro is JavaScript paint widget which allows editing images directly in a browser.",
|
|
||||||
"website": "https://github.com/ivictbor/painterro",
|
|
||||||
"copyright": "Copyright 2017 Ivan Borshchov",
|
|
||||||
"license": "MIT",
|
|
||||||
"link": "https://github.com/ivictbor/painterro/blob/master/LICENSE"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"capabilities": [
|
|
||||||
{
|
|
||||||
"key": "notebook",
|
|
||||||
"name": "Notebook Capability",
|
|
||||||
"description": "Provides a capability for looking for a notebook domain object",
|
|
||||||
"implementation": NotebookCapability,
|
|
||||||
"depends": [
|
|
||||||
"typeService"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"policies": [
|
|
||||||
{
|
|
||||||
"category": "composition",
|
|
||||||
"implementation": CompositionPolicy,
|
|
||||||
"message": "Objects of this type cannot contain objects of that type."
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"controls": [
|
|
||||||
{
|
|
||||||
"key": "embed-control",
|
|
||||||
"template": embedControlTemplate
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "snapshot-select",
|
|
||||||
"template": snapSelectTemplate
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
<div ng-controller="NotebookController as controller" class="mct-notebook w-notebook l-flex-col">
|
|
||||||
<div class="l-notebook-head holder l-flex-row flex-elem">
|
|
||||||
<div class="c-search flex-elem holder grows">
|
|
||||||
<input class="c-search__search-input"
|
|
||||||
type="text" tabindex="10000"
|
|
||||||
ng-model="entrySearch"
|
|
||||||
ng-keyup="controller.search()"/>
|
|
||||||
<a class="c-search__clear-input clear-icon icon-x-in-circle"
|
|
||||||
ng-class="{show: !(entrySearch === '' || entrySearch === undefined)}"
|
|
||||||
ng-click="entrySearch = ''; controller.search()"></a>
|
|
||||||
</div>
|
|
||||||
<div class="notebook-view-controls l-flex-row flex-elem holder">
|
|
||||||
<div class="select notebook-view-controls__filter-time">
|
|
||||||
<select ng-model="showTime">
|
|
||||||
<option value="0" selected="selected">Show all</option>
|
|
||||||
<option value="1">Last hour</option>
|
|
||||||
<option value="8">Last 8 hours</option>
|
|
||||||
<option value="24">Last 24 hours</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="select notebook-view-controls__sort">
|
|
||||||
<select ng-model="sortEntries">
|
|
||||||
<option value="-createdOn" selected="selected">Newest first</option>
|
|
||||||
<option value="createdOn">Oldest first</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- drag area -->
|
|
||||||
<div class="holder flex-elem l-flex-row icon-plus labeled l-notebook-drag-area" ng-click="newEntry($event)"
|
|
||||||
id="newEntry" mct-entry-dnd>
|
|
||||||
<span class="label">To start a new entry, click here or drag and drop any object</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- entries -->
|
|
||||||
<div class="holder flex-elem grows w-notebook-entries t-entries-list" ng-mouseover="handleActive()">
|
|
||||||
<ul>
|
|
||||||
<li class="l-flex-row has-local-controls l-notebook-entry s-notebook-entry"
|
|
||||||
id="{{'entry_'+ entry.id}}"
|
|
||||||
ng-if="hoursFilter(showTime,entry.createdOn)"
|
|
||||||
ng-repeat="entry in model.entries | filter:entrySearch | orderBy: sortEntries track by $index"
|
|
||||||
ng-init="$last && finished(model.entries)"
|
|
||||||
mct-entry-dnd>
|
|
||||||
<div class="holder flex-elem l-flex-row grows w-notebook-entry-time-and-content">
|
|
||||||
<div class="holder flex-elem s-notebook-entry-time">
|
|
||||||
<span>{{entry.createdOn | date:'yyyy-MM-dd'}}</span>
|
|
||||||
<span>{{entry.createdOn | date:'HH:mm:ss'}}</span>
|
|
||||||
</div>
|
|
||||||
<div class="holder flex-elem l-flex-col grows l-notebook-entry-content">
|
|
||||||
<div contenteditable="true"
|
|
||||||
ng-blur="textBlur($event, entry.id)"
|
|
||||||
ng-focus="textFocus($event, entry.id)"
|
|
||||||
ng-model="entry.text"
|
|
||||||
placeholder="Enter text here"
|
|
||||||
class="flex-elem s-input-inline t-notebook-entry-input s-notebook-entry-text"
|
|
||||||
ng-bind="entry.text">
|
|
||||||
</div>
|
|
||||||
<!-- embeds -->
|
|
||||||
<div class="flex-elem entry-embeds l-flex-row">
|
|
||||||
<div class="l-flex-row l-entry-embed {{embed.cssClass}}"
|
|
||||||
ng-repeat="embed in entry.embeds track by $index"
|
|
||||||
ng-class="{ 'has-snapshot' : embed.snapshot }"
|
|
||||||
id="{{embed.id}}">
|
|
||||||
<div class="snap-thumb"
|
|
||||||
ng-if="embed.snapshot"
|
|
||||||
ng-click="viewSnapshot($event,embed.snapshot.src,embed.id,entry.createdOn,this,embed)">
|
|
||||||
<img ng-src="{{embed.snapshot.src}}" src="//:0" alt="{{embed.id}}">
|
|
||||||
</div>
|
|
||||||
<div class="embed-info l-flex-col">
|
|
||||||
<div class="embed-title object-header">
|
|
||||||
<a ng-click='navigate($event,embed.type)'>{{embed.name}}</a>
|
|
||||||
<a class='context-available' ng-click='openMenu($event,embed.type)'></a>
|
|
||||||
</div>
|
|
||||||
<div class="hide-menu" ng-show="false">
|
|
||||||
<div class="menu-element context-menu-wrapper mobile-disable-select">
|
|
||||||
<div class="menu context-menu">
|
|
||||||
<ul>
|
|
||||||
<li ng-repeat="menu in menuEmbed"
|
|
||||||
ng-click="menu.perform($event,embed.snapshot.src,embed.id,entry.createdOn,this,embed)"
|
|
||||||
title="{{menu.getMetadata().description}}"
|
|
||||||
class="{{menu.getMetadata().cssClass}}"
|
|
||||||
ng-if="embed.snapshot">
|
|
||||||
{{menu.getMetadata().name}}
|
|
||||||
</li>
|
|
||||||
<li ng-repeat="menu in menuEmbedNoSnap"
|
|
||||||
ng-click="menu.perform($event,embed.snapshot.src,embed.id,entry.createdOn,this)"
|
|
||||||
title="{{menu.getMetadata().description}}"
|
|
||||||
class="{{menu.getMetadata().cssClass}}"
|
|
||||||
ng-if="!embed.snapshot">
|
|
||||||
{{menu.getMetadata().name}}
|
|
||||||
</li>
|
|
||||||
<li ng-repeat="menu in embedActions"
|
|
||||||
ng-click="menu.perform()"
|
|
||||||
title="{{menu.name}}"
|
|
||||||
class="{{menu.cssClass}}">
|
|
||||||
{{menu.name}}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="embed-date"
|
|
||||||
ng-if="embed.snapshot">{{embed.id| date:'yyyy-MM-dd HH:mm:ss'}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- delete entry -->
|
|
||||||
<div class="holder flex-elem local-control local-controls-hidden notebook-entry-delete">
|
|
||||||
<a class="s-icon-button icon-trash" id={{entry.id}} title="Delete Entry" ng-click="deleteEntry($event)"></a>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
<span class="status block">
|
|
||||||
<!-- DO NOT ADD SPACES BETWEEN THE SPANS - IT ADDS WHITE SPACE!! -->
|
|
||||||
<span class="status-indicator icon-bell"></span>
|
|
||||||
<span class="label">
|
|
||||||
Notifications
|
|
||||||
</span>
|
|
||||||
<span class="count"></span>
|
|
||||||
</span>
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
<div class="t-snapshot abs l-view-header">
|
|
||||||
<div class="abs object-browse-bar l-flex-row">
|
|
||||||
<div class="left flex-elem l-flex-row grows">
|
|
||||||
<div class="object-header flex-elem l-flex-row grows">
|
|
||||||
<div class="type-icon flex-elem embed-icon holder" ng-class="cssClass"></div>
|
|
||||||
<div class="title-label flex-elem holder flex-can-shrink">{{entryName}}</div>
|
|
||||||
<a class="context-available flex-elem holder" ng-click="openMenu($event,embedType)"></a>
|
|
||||||
<div class="hide-menu" ng-show="false">
|
|
||||||
<div class="menu-element menu-view context-menu-wrapper mobile-disable-select">
|
|
||||||
<div class="menu context-menu">
|
|
||||||
<ul>
|
|
||||||
<li ng-repeat="menu in embedActions"
|
|
||||||
ng-click="menuPerform(menu)"
|
|
||||||
title="{{menu.name}}"
|
|
||||||
class="{{menu.cssClass}}">
|
|
||||||
{{menu.name}}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="btn-bar right l-flex-row flex-elem flex-justify-end flex-fixed">
|
|
||||||
<div class="flex-elem holder flex-can-shrink s-snapshot-datetime">
|
|
||||||
SNAPSHOT {{snapDate | date:'yyyy-MM-dd HH:mm:ss'}}
|
|
||||||
</div>
|
|
||||||
<a class="s-button icon-pencil" title="Annotate">
|
|
||||||
<span class="title-label">Annotate</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2017, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
[],
|
|
||||||
function () {
|
|
||||||
|
|
||||||
function RemoveEmbed(dialogService,context) {
|
|
||||||
context = context || {};
|
|
||||||
|
|
||||||
this.domainObject = context.selectedObject || context.domainObject;
|
|
||||||
this.dialogService = dialogService;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
RemoveEmbed.prototype.perform = function ($event,snapshot,embedId,entryId) {
|
|
||||||
var domainObject = this.domainObject;
|
|
||||||
var errorDialog = this.dialogService.showBlockingMessage({
|
|
||||||
severity: "error",
|
|
||||||
title: "This action will permanently delete this Embed. Do you want to continue?",
|
|
||||||
minimized: true, // want the notification to be minimized initially (don't show banner)
|
|
||||||
options: [{
|
|
||||||
label: "OK",
|
|
||||||
callback: function () {
|
|
||||||
errorDialog.dismiss();
|
|
||||||
remove();
|
|
||||||
}
|
|
||||||
},{
|
|
||||||
label: "Cancel",
|
|
||||||
callback: function () {
|
|
||||||
errorDialog.dismiss();
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
});
|
|
||||||
|
|
||||||
function remove() {
|
|
||||||
domainObject.useCapability('mutation', function (model) {
|
|
||||||
var elementPos = model.entries.map(function (x) {
|
|
||||||
return x.createdOn;
|
|
||||||
}).indexOf(entryId);
|
|
||||||
var entryEmbeds = model.entries[elementPos].embeds;
|
|
||||||
var embedPos = entryEmbeds.map(function (x) {
|
|
||||||
return x.id;
|
|
||||||
}).indexOf(embedId);
|
|
||||||
model.entries[elementPos].embeds.splice(embedPos, 1);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
return RemoveEmbed;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2017, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
[],
|
|
||||||
function () {
|
|
||||||
|
|
||||||
function RemoveSnapshot(dialogService, context) {
|
|
||||||
context = context || {};
|
|
||||||
|
|
||||||
this.domainObject = context.selectedObject || context.domainObject;
|
|
||||||
this.dialogService = dialogService;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
RemoveSnapshot.prototype.perform = function ($event, snapshot, embedId, entryId) {
|
|
||||||
|
|
||||||
var domainObject = this.domainObject;
|
|
||||||
var errorDialog = this.dialogService.showBlockingMessage({
|
|
||||||
severity: "error",
|
|
||||||
title: "This action will permanently delete this Snapshot. Do you want to continue?",
|
|
||||||
minimized: true, // want the notification to be minimized initially (don't show banner)
|
|
||||||
options: [{
|
|
||||||
label: "OK",
|
|
||||||
callback: function () {
|
|
||||||
errorDialog.dismiss();
|
|
||||||
remove();
|
|
||||||
}
|
|
||||||
},{
|
|
||||||
label: "Cancel",
|
|
||||||
callback: function () {
|
|
||||||
errorDialog.dismiss();
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
});
|
|
||||||
|
|
||||||
function remove() {
|
|
||||||
domainObject.useCapability('mutation', function (model) {
|
|
||||||
var elementPos = model.entries.map(function (x) {
|
|
||||||
return x.createdOn;
|
|
||||||
}).indexOf(entryId);
|
|
||||||
var entryEmbeds = model.entries[elementPos].embeds;
|
|
||||||
var embedPos = entryEmbeds.map(function (x) {
|
|
||||||
return x.id;
|
|
||||||
}).indexOf(embedId);
|
|
||||||
model.entries[elementPos].embeds[embedPos].snapshot = "";
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
return RemoveSnapshot;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
@@ -1,132 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2017, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module defining ViewSnapshot
|
|
||||||
*/
|
|
||||||
|
|
||||||
var OVERLAY_TEMPLATE = '' +
|
|
||||||
' <div class="abs blocker"></div>' +
|
|
||||||
' <div class="abs outer-holder">' +
|
|
||||||
' <a class="close icon-x-in-circle"></a>' +
|
|
||||||
' <div class="abs inner-holder l-flex-col">' +
|
|
||||||
' <div class="t-contents flex-elem holder grows"></div>' +
|
|
||||||
' <div class="bottom-bar flex-elem holder">' +
|
|
||||||
' <a class="t-done s-button major">Done</a>' +
|
|
||||||
' </div>' +
|
|
||||||
' </div>' +
|
|
||||||
' </div>';
|
|
||||||
|
|
||||||
define([
|
|
||||||
'zepto',
|
|
||||||
"../../res/templates/snapshotHeader.html"
|
|
||||||
],
|
|
||||||
function ($, headerTemplate) {
|
|
||||||
|
|
||||||
var toggleOverlay,
|
|
||||||
overlay,
|
|
||||||
closeButton,
|
|
||||||
doneButton,
|
|
||||||
blocker,
|
|
||||||
overlayContainer,
|
|
||||||
img,
|
|
||||||
annotateButton,
|
|
||||||
annotateImg;
|
|
||||||
|
|
||||||
function ViewSnapshot($compile) {
|
|
||||||
this.$compile = $compile;
|
|
||||||
}
|
|
||||||
|
|
||||||
function openOverlay(url, header) {
|
|
||||||
overlay = document.createElement('div');
|
|
||||||
$(overlay).addClass('abs overlay l-large-view');
|
|
||||||
overlay.innerHTML = OVERLAY_TEMPLATE;
|
|
||||||
overlayContainer = overlay.querySelector('.t-contents');
|
|
||||||
closeButton = overlay.querySelector('a.close');
|
|
||||||
closeButton.addEventListener('click', toggleOverlay);
|
|
||||||
doneButton = overlay.querySelector('a.t-done');
|
|
||||||
doneButton.addEventListener('click', toggleOverlay);
|
|
||||||
blocker = overlay.querySelector('.abs.blocker');
|
|
||||||
blocker.addEventListener('click', toggleOverlay);
|
|
||||||
annotateButton = header.querySelector('a.icon-pencil');
|
|
||||||
annotateButton.addEventListener('click', annotateImg);
|
|
||||||
document.body.appendChild(overlay);
|
|
||||||
img = document.createElement('div');
|
|
||||||
$(img).addClass('abs object-holder t-image-holder s-image-holder');
|
|
||||||
img.innerHTML = '<div class="image-main s-image-main" style="background-image: url(' + url + ');"></div>';
|
|
||||||
overlayContainer.appendChild(header);
|
|
||||||
overlayContainer.appendChild(img);
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeOverlay() {
|
|
||||||
overlayContainer.removeChild(img);
|
|
||||||
document.body.removeChild(overlay);
|
|
||||||
closeButton.removeEventListener('click', toggleOverlay);
|
|
||||||
closeButton = undefined;
|
|
||||||
doneButton.removeEventListener('click', toggleOverlay);
|
|
||||||
doneButton = undefined;
|
|
||||||
blocker.removeEventListener('click', toggleOverlay);
|
|
||||||
blocker = undefined;
|
|
||||||
overlayContainer = undefined;
|
|
||||||
overlay = undefined;
|
|
||||||
img = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
ViewSnapshot.prototype.perform = function ($event, snapshot, embedId, entryId, $scope, embed) {
|
|
||||||
var isOpen = false;
|
|
||||||
|
|
||||||
// onclick for menu items in overlay header context menu
|
|
||||||
$scope.menuPerform = function (menu) {
|
|
||||||
menu.perform();
|
|
||||||
closeOverlay();
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create the overlay element and add it to the document's body
|
|
||||||
$scope.cssClass = embed.cssClass;
|
|
||||||
$scope.embedType = embed.type;
|
|
||||||
$scope.entryName = embed.name;
|
|
||||||
$scope.snapDate = +embedId;
|
|
||||||
var element = this.$compile(headerTemplate)($scope);
|
|
||||||
|
|
||||||
var annotateAction = $scope.action.getActions({category: 'embed'})[1];
|
|
||||||
|
|
||||||
toggleOverlay = function () {
|
|
||||||
if (!isOpen) {
|
|
||||||
openOverlay(snapshot, element[0]);
|
|
||||||
isOpen = true;
|
|
||||||
} else {
|
|
||||||
closeOverlay();
|
|
||||||
isOpen = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
annotateImg = function () {
|
|
||||||
closeOverlay();
|
|
||||||
annotateAction.perform($event, snapshot, embedId, entryId, $scope);
|
|
||||||
};
|
|
||||||
|
|
||||||
toggleOverlay();
|
|
||||||
};
|
|
||||||
|
|
||||||
return ViewSnapshot;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2017, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
|
|
||||||
define(
|
|
||||||
function () {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The notebook capability allows a domain object to know whether the
|
|
||||||
* notebook plugin is present or not.
|
|
||||||
*
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function NotebookCapability(typeService, domainObject) {
|
|
||||||
this.domainObject = domainObject;
|
|
||||||
this.typeService = typeService;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if there is a notebook domain Object.
|
|
||||||
*
|
|
||||||
* @returns {Boolean}
|
|
||||||
*/
|
|
||||||
NotebookCapability.prototype.isNotebook = function () {
|
|
||||||
return !!this.typeService.getType('notebook');
|
|
||||||
};
|
|
||||||
|
|
||||||
return NotebookCapability;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
@@ -1,367 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2017, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
/*-- main controller file, here is the core functionality of the notebook plugin --*/
|
|
||||||
|
|
||||||
define(
|
|
||||||
['zepto'],
|
|
||||||
function ($) {
|
|
||||||
|
|
||||||
|
|
||||||
function NotebookController(
|
|
||||||
$scope,
|
|
||||||
dialogService,
|
|
||||||
popupService,
|
|
||||||
agentService,
|
|
||||||
objectService,
|
|
||||||
navigationService,
|
|
||||||
now,
|
|
||||||
actionService,
|
|
||||||
$timeout,
|
|
||||||
$element,
|
|
||||||
$sce
|
|
||||||
) {
|
|
||||||
|
|
||||||
$scope.entriesEl = $(document.body).find('.t-entries-list');
|
|
||||||
$scope.sortEntries = $scope.domainObject.getModel().defaultSort;
|
|
||||||
$scope.showTime = "0";
|
|
||||||
$scope.editEntry = false;
|
|
||||||
$scope.entrySearch = '';
|
|
||||||
$scope.entryTypes = [];
|
|
||||||
$scope.embedActions = [];
|
|
||||||
$scope.currentEntryValue = '';
|
|
||||||
|
|
||||||
var SECONDS_IN_AN_HOUR = 60 * 60 * 1000;
|
|
||||||
|
|
||||||
this.scope = $scope;
|
|
||||||
|
|
||||||
$scope.hoursFilter = function (hours,entryTime) {
|
|
||||||
if (+hours) {
|
|
||||||
return entryTime > (Date.now() - SECONDS_IN_AN_HOUR * (+hours));
|
|
||||||
}else {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.scrollToTop = function () {
|
|
||||||
var entriesContainer = $scope.entriesEl.parent();
|
|
||||||
entriesContainer[0].scrollTop = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.findEntryEl = function (entryId) {
|
|
||||||
var element = $($scope.entriesEl).find('#entry_' + entryId);
|
|
||||||
|
|
||||||
if (element[0]) {
|
|
||||||
return element.find("[contenteditable='true']");
|
|
||||||
} else {
|
|
||||||
var entries = $scope.entriesEl.children().children(),
|
|
||||||
lastOrFirst = $scope.sortEntries === "-createdOn" ? 0 : (entries.length - 1);
|
|
||||||
|
|
||||||
return $(entries[lastOrFirst]).find("[contenteditable='true']");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.findEntryPositionById = function (id) {
|
|
||||||
var foundId = -1;
|
|
||||||
|
|
||||||
$scope.domainObject.model.entries.forEach(function (element, index) {
|
|
||||||
if (element.id === id) {
|
|
||||||
foundId = index;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return foundId;
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.newEntry = function ($event) {
|
|
||||||
$scope.scrollToTop();
|
|
||||||
|
|
||||||
var entries = $scope.domainObject.model.entries,
|
|
||||||
lastEntry = entries[entries.length - 1],
|
|
||||||
id = Date.now();
|
|
||||||
|
|
||||||
if (lastEntry === undefined || lastEntry.text || lastEntry.embeds) {
|
|
||||||
var createdEntry = {'id': id, 'createdOn': id};
|
|
||||||
|
|
||||||
$scope.domainObject.useCapability('mutation', function (model) {
|
|
||||||
model.entries.push(createdEntry);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
$scope.findEntryEl(lastEntry.id).focus();
|
|
||||||
|
|
||||||
$scope.domainObject.useCapability('mutation', function (model) {
|
|
||||||
model.entries[entries.length - 1].createdOn = id;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
$scope.entrySearch = '';
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
$scope.deleteEntry = function ($event) {
|
|
||||||
var delId = +$event.currentTarget.id;
|
|
||||||
var errorDialog = dialogService.showBlockingMessage({
|
|
||||||
severity: "error",
|
|
||||||
title: "This action will permanently delete this Notebook entry. Do you want to continue?",
|
|
||||||
minimized: true, // want the notification to be minimized initially (don't show banner)
|
|
||||||
options: [{
|
|
||||||
label: "OK",
|
|
||||||
callback: function () {
|
|
||||||
errorDialog.dismiss();
|
|
||||||
var elementPos = $scope.findEntryPositionById(delId);
|
|
||||||
|
|
||||||
if (elementPos !== -1) {
|
|
||||||
$scope.domainObject.useCapability('mutation', function (model) {
|
|
||||||
model.entries.splice(elementPos, 1);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
window.console.log('delete error');
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
},{
|
|
||||||
label: "Cancel",
|
|
||||||
callback: function () {
|
|
||||||
errorDialog.dismiss();
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.textFocus = function ($event, entryId) {
|
|
||||||
if ($event.srcElement) {
|
|
||||||
$scope.currentEntryValue = $event.srcElement.innerText;
|
|
||||||
} else {
|
|
||||||
$event.target.innerText = '';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//On text blur(when focus is removed)
|
|
||||||
$scope.textBlur = function ($event, entryId) {
|
|
||||||
// entryId is the unique numeric based on the original createdOn
|
|
||||||
if ($event.target) {
|
|
||||||
var elementPos = $scope.findEntryPositionById(+entryId);
|
|
||||||
|
|
||||||
// If the text of an entry has been changed, then update the text and the modifiedOn numeric
|
|
||||||
// Otherwise, don't do anything
|
|
||||||
if ($scope.currentEntryValue !== $event.target.innerText) {
|
|
||||||
$scope.domainObject.useCapability('mutation', function (model) {
|
|
||||||
model.entries[elementPos].text = $event.target.innerText;
|
|
||||||
model.entries[elementPos].modified = Date.now();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.finished = function (model) {
|
|
||||||
var lastEntry = model[model.length - 1];
|
|
||||||
|
|
||||||
if (!lastEntry.text) {
|
|
||||||
$scope.findEntryEl(lastEntry.id).focus();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.handleActive = function () {
|
|
||||||
var newEntry = $scope.entriesEl.find('.active');
|
|
||||||
if (newEntry) {
|
|
||||||
newEntry.removeClass('active');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
$scope.clearSearch = function () {
|
|
||||||
$scope.entrySearch = '';
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.viewSnapshot = function ($event,snapshot,embedId,entryId,$innerScope,domainObject) {
|
|
||||||
var viewAction = $scope.action.getActions({category: 'embed'})[0];
|
|
||||||
viewAction.perform($event, snapshot, embedId, entryId, $innerScope, domainObject);
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.renderImage = function (img) {
|
|
||||||
return URL.createObjectURL(img);
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.getDomainObj = function (id) {
|
|
||||||
return objectService.getObjects([id]);
|
|
||||||
};
|
|
||||||
|
|
||||||
function refreshComp(change) {
|
|
||||||
if (change && change.length) {
|
|
||||||
change[0].getCapability('action').getActions({key: 'remove'})[0].perform();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.actionToMenuOption = function (action) {
|
|
||||||
return {
|
|
||||||
key: action.getMetadata().key,
|
|
||||||
name: action.getMetadata().name,
|
|
||||||
cssClass: action.getMetadata().cssClass,
|
|
||||||
perform: action.perform
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// Maintain all "conclude-editing" and "save" actions in the
|
|
||||||
// present context.
|
|
||||||
function updateActions() {
|
|
||||||
$scope.menuEmbed = $scope.action ?
|
|
||||||
$scope.action.getActions({category: 'embed'}) :
|
|
||||||
[];
|
|
||||||
|
|
||||||
$scope.menuEmbedNoSnap = $scope.action ?
|
|
||||||
$scope.action.getActions({category: 'embed-no-snap'}) :
|
|
||||||
[];
|
|
||||||
|
|
||||||
$scope.menuActions = $scope.action ?
|
|
||||||
$scope.action.getActions({key: 'window'}) :
|
|
||||||
[];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update set of actions whenever the action capability
|
|
||||||
// changes or becomes available.
|
|
||||||
$scope.$watch("action", updateActions);
|
|
||||||
|
|
||||||
$scope.navigate = function ($event,embedType) {
|
|
||||||
if ($event) {
|
|
||||||
$event.preventDefault();
|
|
||||||
}
|
|
||||||
$scope.getDomainObj(embedType).then(function (resp) {
|
|
||||||
navigationService.setNavigation(resp[embedType]);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.saveSnap = function (url,embedPos,entryPos) {
|
|
||||||
var snapshot = false;
|
|
||||||
if (url) {
|
|
||||||
if (embedPos !== -1 && entryPos !== -1) {
|
|
||||||
var reader = new window.FileReader();
|
|
||||||
reader.readAsDataURL(url);
|
|
||||||
reader.onloadend = function () {
|
|
||||||
snapshot = reader.result;
|
|
||||||
$scope.domainObject.useCapability('mutation', function (model) {
|
|
||||||
if (model.entries[entryPos]) {
|
|
||||||
model.entries[entryPos].embeds[embedPos].snapshot = {
|
|
||||||
'src': snapshot,
|
|
||||||
'type': url.type,
|
|
||||||
'size': url.size,
|
|
||||||
'modified': Date.now()
|
|
||||||
};
|
|
||||||
model.entries[entryPos].embeds[embedPos].id = Date.now();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}else {
|
|
||||||
$scope.domainObject.useCapability('mutation', function (model) {
|
|
||||||
model.entries[entryPos].embeds[embedPos].snapshot = snapshot;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/*---popups menu embeds----*/
|
|
||||||
|
|
||||||
function getEmbedActions(embedType) {
|
|
||||||
if (!$scope.embedActions.length) {
|
|
||||||
$scope.getDomainObj(embedType).then(function (resp) {
|
|
||||||
$scope.embedActions = [];
|
|
||||||
$scope.embedActions.push($scope.actionToMenuOption(
|
|
||||||
$scope.action.getActions({key: 'mct-preview-action', selectedObject: resp[embedType]})[0]
|
|
||||||
));
|
|
||||||
$scope.embedActions.push($scope.actionToMenuOption(
|
|
||||||
$scope.action.getActions({key: 'window', selectedObject: resp[embedType]})[0]
|
|
||||||
));
|
|
||||||
$scope.embedActions.push({
|
|
||||||
key: 'navigate',
|
|
||||||
name: 'Go to Original',
|
|
||||||
cssClass: '',
|
|
||||||
perform: function () {
|
|
||||||
$scope.navigate('', embedType);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.openMenu = function ($event,embedType) {
|
|
||||||
$event.preventDefault();
|
|
||||||
|
|
||||||
getEmbedActions(embedType);
|
|
||||||
|
|
||||||
var body = $(document).find('body'),
|
|
||||||
initiatingEvent = agentService.isMobile() ?
|
|
||||||
'touchstart' : 'mousedown',
|
|
||||||
dismissExistingMenu,
|
|
||||||
menu;
|
|
||||||
|
|
||||||
var container = $($event.currentTarget).parent().parent();
|
|
||||||
|
|
||||||
menu = container.find('.menu-element');
|
|
||||||
|
|
||||||
// Remove the context menu
|
|
||||||
function dismiss() {
|
|
||||||
container.find('.hide-menu').append(menu);
|
|
||||||
body.off("mousedown", dismiss);
|
|
||||||
dismissExistingMenu = undefined;
|
|
||||||
$scope.embedActions = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dismiss any menu which was already showing
|
|
||||||
if (dismissExistingMenu) {
|
|
||||||
dismissExistingMenu();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ...and record the presence of this menu.
|
|
||||||
dismissExistingMenu = dismiss;
|
|
||||||
|
|
||||||
popupService.display(menu, [$event.pageX,$event.pageY], {
|
|
||||||
marginX: 0,
|
|
||||||
marginY: -50
|
|
||||||
});
|
|
||||||
|
|
||||||
// Stop propagation so that clicks or touches on the menu do not close the menu
|
|
||||||
menu.on(initiatingEvent, function (event) {
|
|
||||||
event.stopPropagation();
|
|
||||||
$timeout(dismiss, 300);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Dismiss the menu when body is clicked/touched elsewhere
|
|
||||||
// ('mousedown' because 'click' breaks left-click context menus)
|
|
||||||
// ('touchstart' because 'touch' breaks context menus up)
|
|
||||||
body.on(initiatingEvent, dismiss);
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
$scope.$watchCollection("composition", refreshComp);
|
|
||||||
|
|
||||||
$scope.$watch('domainObject.getModel().defaultSort', function (newDefaultSort, oldDefaultSort) {
|
|
||||||
if (newDefaultSort !== oldDefaultSort) {
|
|
||||||
$scope.sortEntries = newDefaultSort;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$scope.$on('$destroy', function () {});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return NotebookController;
|
|
||||||
});
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define([
|
|
||||||
"./src/directives/MCTTable",
|
|
||||||
"./src/controllers/TelemetryTableController",
|
|
||||||
"./src/controllers/TableOptionsController",
|
|
||||||
'../../commonUI/regions/src/Region',
|
|
||||||
'../../commonUI/browse/src/InspectorRegion',
|
|
||||||
"./res/templates/table-options-edit.html",
|
|
||||||
"./res/templates/telemetry-table.html",
|
|
||||||
"legacyRegistry"
|
|
||||||
], function (
|
|
||||||
MCTTable,
|
|
||||||
TelemetryTableController,
|
|
||||||
TableOptionsController,
|
|
||||||
Region,
|
|
||||||
InspectorRegion,
|
|
||||||
tableOptionsEditTemplate,
|
|
||||||
telemetryTableTemplate,
|
|
||||||
legacyRegistry
|
|
||||||
) {
|
|
||||||
/**
|
|
||||||
* Two region parts are defined here. One that appears only in browse
|
|
||||||
* mode, and one that appears only in edit mode. For not they both point
|
|
||||||
* to the same representation, but a different key could be used here to
|
|
||||||
* include a customized representation for edit mode.
|
|
||||||
*/
|
|
||||||
var tableInspector = new InspectorRegion(),
|
|
||||||
tableOptionsEditRegion = new Region({
|
|
||||||
name: "table-options",
|
|
||||||
title: "Table Options",
|
|
||||||
modes: ['edit'],
|
|
||||||
content: {
|
|
||||||
key: "table-options-edit"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
tableInspector.addRegion(tableOptionsEditRegion);
|
|
||||||
|
|
||||||
legacyRegistry.register("platform/features/table", {
|
|
||||||
"extensions": {
|
|
||||||
"types": [
|
|
||||||
{
|
|
||||||
"key": "table",
|
|
||||||
"name": "Telemetry Table",
|
|
||||||
"cssClass": "icon-tabular-realtime",
|
|
||||||
"description": "A table of values over a given time period. The table will be automatically updated with new values as they become available",
|
|
||||||
"priority": 861,
|
|
||||||
"features": "creation",
|
|
||||||
"delegates": [
|
|
||||||
"telemetry"
|
|
||||||
],
|
|
||||||
"inspector": "table-options-edit",
|
|
||||||
"contains": [
|
|
||||||
{
|
|
||||||
"has": "telemetry"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"model": {
|
|
||||||
"composition": []
|
|
||||||
},
|
|
||||||
"views": [
|
|
||||||
"table"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"controllers": [
|
|
||||||
{
|
|
||||||
"key": "TelemetryTableController",
|
|
||||||
"implementation": TelemetryTableController,
|
|
||||||
"depends": ["$scope", "$timeout", "openmct"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "TableOptionsController",
|
|
||||||
"implementation": TableOptionsController,
|
|
||||||
"depends": ["$scope"]
|
|
||||||
}
|
|
||||||
|
|
||||||
],
|
|
||||||
"views": [
|
|
||||||
{
|
|
||||||
"name": "Telemetry Table",
|
|
||||||
"key": "table",
|
|
||||||
"cssClass": "icon-tabular-realtime",
|
|
||||||
"template": telemetryTableTemplate,
|
|
||||||
"needs": [
|
|
||||||
"telemetry"
|
|
||||||
],
|
|
||||||
"delegation": true,
|
|
||||||
"editable": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"directives": [
|
|
||||||
{
|
|
||||||
"key": "mctTable",
|
|
||||||
"implementation": MCTTable,
|
|
||||||
"depends": ["$timeout"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"representations": [
|
|
||||||
{
|
|
||||||
"key": "table-options-edit",
|
|
||||||
"template": tableOptionsEditTemplate
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
<div class="l-control-bar">
|
|
||||||
<a class="s-button t-export icon-download labeled"
|
|
||||||
ng-click="exportAsCSV()"
|
|
||||||
title="Export This View's Data">
|
|
||||||
Export
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="mct-table-headers-w" mct-scroll-x="scroll.x">
|
|
||||||
<table class="mct-table l-tabular-headers filterable"
|
|
||||||
ng-style="{
|
|
||||||
'max-width': totalWidth
|
|
||||||
}">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th ng-repeat="header in displayHeaders"
|
|
||||||
ng-style="{
|
|
||||||
width: columnWidths[$index] + 'px',
|
|
||||||
'max-width': columnWidths[$index] + 'px',
|
|
||||||
}"
|
|
||||||
ng-class="[
|
|
||||||
enableSort ? 'sortable' : '',
|
|
||||||
sortColumn === header ? 'sort' : '',
|
|
||||||
sortDirection || ''
|
|
||||||
].join(' ')"
|
|
||||||
ng-click="toggleSort(header)">
|
|
||||||
{{ header }}
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
<tr ng-if="enableFilter" class="s-filters">
|
|
||||||
<th ng-repeat="header in displayHeaders"
|
|
||||||
ng-style="{
|
|
||||||
width: columnWidths[$index] + 'px',
|
|
||||||
'max-width': columnWidths[$index] + 'px',
|
|
||||||
}">
|
|
||||||
<div class="holder l-filter flex-elem grows"
|
|
||||||
ng-class="{active: filters[header]}">
|
|
||||||
<input type="text"
|
|
||||||
ng-model="filters[header]"/>
|
|
||||||
<a class="clear-icon clear-input icon-x-in-circle"
|
|
||||||
ng-class="{show: filters[header]}"
|
|
||||||
ng-click="filters[header] = undefined"></a>
|
|
||||||
</div>
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<table class="mct-sizing-table t-sizing-table"
|
|
||||||
ng-style="{
|
|
||||||
width: calcTableWidthPx
|
|
||||||
}">
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td ng-repeat="header in displayHeaders">{{header}}</td>
|
|
||||||
</tr>
|
|
||||||
<tr><td ng-repeat="header in displayHeaders" >
|
|
||||||
{{sizingRow[header].text}}
|
|
||||||
</td></tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<div class="l-tabular-body t-scrolling vscroll--persist" mct-resize="resize()" mct-scroll-x="scroll.x">
|
|
||||||
<div class="mct-table-scroll-forcer"
|
|
||||||
ng-style="{
|
|
||||||
width: totalWidth
|
|
||||||
}"></div>
|
|
||||||
<table class="mct-table"
|
|
||||||
ng-style="{
|
|
||||||
height: totalHeight + 'px',
|
|
||||||
'max-width': totalWidth
|
|
||||||
}">
|
|
||||||
<tbody>
|
|
||||||
<tr ng-repeat-start="visibleRow in visibleRows track by $index"
|
|
||||||
ng-if="visibleRow.rowIndex === toiRowIndex"
|
|
||||||
ng-style="{ top: visibleRow.offsetY + 'px' }"
|
|
||||||
class="l-toi-tablerow">
|
|
||||||
<td colspan="999">
|
|
||||||
<mct-include key="'time-of-interest'"
|
|
||||||
class="l-toi-holder pinned"></mct-include>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr ng-repeat-end
|
|
||||||
ng-style="{ top: visibleRow.offsetY + 'px' }"
|
|
||||||
ng-click="table.onRowClick($event, visibleRow.rowIndex)">
|
|
||||||
<td ng-repeat="header in displayHeaders"
|
|
||||||
ng-style="{
|
|
||||||
width: columnWidths[$index] + 'px',
|
|
||||||
'max-width': columnWidths[$index] + 'px',
|
|
||||||
}"
|
|
||||||
class="{{visibleRow.contents[header].cssClass}}">
|
|
||||||
{{ visibleRow.contents[header].text }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
<div ng-controller="TelemetryTableController as tableController"
|
|
||||||
ng-class="{'loading': loading}">
|
|
||||||
<mct-table
|
|
||||||
headers="headers"
|
|
||||||
rows="rows"
|
|
||||||
time-columns="[tableController.table.timeSystemColumnTitle]"
|
|
||||||
format-cell="formatCell"
|
|
||||||
enableFilter="true"
|
|
||||||
enableSort="true"
|
|
||||||
auto-scroll="autoScroll"
|
|
||||||
default-sort="defaultSort"
|
|
||||||
export-as="{{ exportAs }}"
|
|
||||||
class="tabular-holder l-sticky-headers has-control-bar">
|
|
||||||
</mct-table>
|
|
||||||
</div>
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
define(function () {
|
|
||||||
function TableColumn(openmct, telemetryObject, metadatum) {
|
|
||||||
this.openmct = openmct;
|
|
||||||
this.telemetryObject = telemetryObject;
|
|
||||||
this.metadatum = metadatum;
|
|
||||||
this.formatter = openmct.telemetry.getValueFormatter(metadatum);
|
|
||||||
|
|
||||||
this.titleValue = this.metadatum.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
TableColumn.prototype.title = function (title) {
|
|
||||||
if (arguments.length > 0) {
|
|
||||||
this.titleValue = title;
|
|
||||||
}
|
|
||||||
return this.titleValue;
|
|
||||||
};
|
|
||||||
|
|
||||||
TableColumn.prototype.isCurrentTimeSystem = function () {
|
|
||||||
var isCurrentTimeSystem = this.metadatum.hints.hasOwnProperty('domain') &&
|
|
||||||
this.metadatum.key === this.openmct.time.timeSystem().key;
|
|
||||||
|
|
||||||
return isCurrentTimeSystem;
|
|
||||||
};
|
|
||||||
|
|
||||||
TableColumn.prototype.hasValue = function (telemetryObject, telemetryDatum) {
|
|
||||||
var keyStringForDatum = this.openmct.objects.makeKeyString(telemetryObject.identifier);
|
|
||||||
var keyStringForColumn = this.openmct.objects.makeKeyString(this.telemetryObject.identifier);
|
|
||||||
return keyStringForDatum === keyStringForColumn && telemetryDatum.hasOwnProperty(this.metadatum.source);
|
|
||||||
};
|
|
||||||
|
|
||||||
TableColumn.prototype.getValue = function (telemetryDatum, limitEvaluator) {
|
|
||||||
var alarm = limitEvaluator &&
|
|
||||||
limitEvaluator.evaluate(telemetryDatum, this.metadatum);
|
|
||||||
var value = {
|
|
||||||
text: this.formatter.format(telemetryDatum),
|
|
||||||
value: this.formatter.parse(telemetryDatum)
|
|
||||||
};
|
|
||||||
|
|
||||||
if (alarm) {
|
|
||||||
value.cssClass = alarm.cssClass;
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
};
|
|
||||||
|
|
||||||
return TableColumn;
|
|
||||||
});
|
|
||||||
@@ -1,164 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
/* global Set */
|
|
||||||
define(
|
|
||||||
['./TableColumn'],
|
|
||||||
function (TableColumn) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class that manages table metadata, state, and contents.
|
|
||||||
* @memberof platform/features/table
|
|
||||||
* @param domainObject
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function TableConfiguration(domainObject, openmct) {
|
|
||||||
this.domainObject = domainObject;
|
|
||||||
this.openmct = openmct;
|
|
||||||
this.timeSystemColumn = undefined;
|
|
||||||
this.columns = [];
|
|
||||||
this.headers = new Set();
|
|
||||||
this.timeSystemColumnTitle = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build column definition based on supplied telemetry metadata
|
|
||||||
* @param telemetryObject the telemetry producing object associated with this column
|
|
||||||
* @param metadata Metadata describing the domains and ranges available
|
|
||||||
* @returns {TableConfiguration} This object
|
|
||||||
*/
|
|
||||||
TableConfiguration.prototype.addColumn = function (telemetryObject, metadatum) {
|
|
||||||
var column = new TableColumn(this.openmct, telemetryObject, metadatum);
|
|
||||||
|
|
||||||
if (column.isCurrentTimeSystem()) {
|
|
||||||
if (!this.timeSystemColumnTitle) {
|
|
||||||
this.timeSystemColumnTitle = column.title();
|
|
||||||
}
|
|
||||||
column.title(this.timeSystemColumnTitle);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.columns.push(column);
|
|
||||||
this.headers.add(column.title());
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve and format values for a given telemetry datum.
|
|
||||||
* @param telemetryObject The object that the telemetry data is
|
|
||||||
* associated with
|
|
||||||
* @param datum The telemetry datum to retrieve values from
|
|
||||||
* @returns {Object} Key value pairs where the key is the column
|
|
||||||
* title, and the value is the formatted value from the provided datum.
|
|
||||||
*/
|
|
||||||
TableConfiguration.prototype.getRowValues = function (telemetryObject, limitEvaluator, datum) {
|
|
||||||
return this.columns.reduce(function (rowObject, column) {
|
|
||||||
var columnTitle = column.title();
|
|
||||||
var columnValue = {
|
|
||||||
text: '',
|
|
||||||
value: undefined
|
|
||||||
};
|
|
||||||
if (rowObject[columnTitle] === undefined) {
|
|
||||||
rowObject[columnTitle] = columnValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (column.hasValue(telemetryObject, datum)) {
|
|
||||||
columnValue = column.getValue(datum, limitEvaluator);
|
|
||||||
|
|
||||||
if (columnValue.text === undefined) {
|
|
||||||
columnValue.text = '';
|
|
||||||
}
|
|
||||||
// Don't replace something with nothing.
|
|
||||||
// This occurs when there are multiple columns with the same
|
|
||||||
// column title
|
|
||||||
if (rowObject[columnTitle].text === undefined ||
|
|
||||||
rowObject[columnTitle].text.length === 0) {
|
|
||||||
rowObject[columnTitle] = columnValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return rowObject;
|
|
||||||
}, {});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
TableConfiguration.prototype.defaultColumnConfiguration = function () {
|
|
||||||
return ((this.domainObject.getModel().configuration || {}).table || {}).columns || {};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the established configuration on the domain object
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
TableConfiguration.prototype.saveColumnConfiguration = function (columnConfig) {
|
|
||||||
this.domainObject.useCapability('mutation', function (model) {
|
|
||||||
model.configuration = model.configuration || {};
|
|
||||||
model.configuration.table = model.configuration.table || {};
|
|
||||||
model.configuration.table.columns = columnConfig;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
function configChanged(config1, config2) {
|
|
||||||
var config1Keys = Object.keys(config1),
|
|
||||||
config2Keys = Object.keys(config2);
|
|
||||||
|
|
||||||
return (config1Keys.length !== config2Keys.length) ||
|
|
||||||
config1Keys.some(function (key) {
|
|
||||||
return config1[key] !== config2[key];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* As part of the process of building the table definition, extract
|
|
||||||
* configuration from column definitions.
|
|
||||||
* @returns {Object} A configuration object consisting of key-value
|
|
||||||
* pairs where the key is the column title, and the value is a
|
|
||||||
* boolean indicating whether the column should be shown.
|
|
||||||
*/
|
|
||||||
TableConfiguration.prototype.buildColumnConfiguration = function () {
|
|
||||||
var configuration = {},
|
|
||||||
//Use existing persisted config, or default it
|
|
||||||
defaultConfig = this.defaultColumnConfiguration();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For each column header, define a configuration value
|
|
||||||
* specifying whether the column is visible or not. Default to
|
|
||||||
* existing (persisted) configuration if available
|
|
||||||
*/
|
|
||||||
this.headers.forEach(function (columnTitle) {
|
|
||||||
configuration[columnTitle] =
|
|
||||||
typeof defaultConfig[columnTitle] === 'undefined' ? true :
|
|
||||||
defaultConfig[columnTitle];
|
|
||||||
});
|
|
||||||
|
|
||||||
//Synchronize column configuration with model
|
|
||||||
if (this.domainObject.hasCapability('editor') &&
|
|
||||||
this.domainObject.getCapability('editor').isEditContextRoot() &&
|
|
||||||
configChanged(configuration, defaultConfig)) {
|
|
||||||
this.saveColumnConfiguration(configuration);
|
|
||||||
}
|
|
||||||
|
|
||||||
return configuration;
|
|
||||||
};
|
|
||||||
|
|
||||||
return TableConfiguration;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
@@ -1,249 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
[
|
|
||||||
'lodash',
|
|
||||||
'EventEmitter'
|
|
||||||
],
|
|
||||||
function (_, EventEmitter) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function TelemetryCollection() {
|
|
||||||
EventEmitter.call(this, arguments);
|
|
||||||
this.dupeCheck = false;
|
|
||||||
this.telemetry = [];
|
|
||||||
this.highBuffer = [];
|
|
||||||
this.sortField = undefined;
|
|
||||||
this.lastBounds = {};
|
|
||||||
|
|
||||||
_.bindAll(this, [
|
|
||||||
'addOne',
|
|
||||||
'iteratee'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
TelemetryCollection.prototype = Object.create(EventEmitter.prototype);
|
|
||||||
|
|
||||||
TelemetryCollection.prototype.iteratee = function (item) {
|
|
||||||
return _.get(item, this.sortField);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function is optimized for ticking - it assumes that start and end
|
|
||||||
* bounds will only increase and as such this cannot be used for decreasing
|
|
||||||
* bounds changes.
|
|
||||||
*
|
|
||||||
* An implication of this is that data will not be discarded that exceeds
|
|
||||||
* the given end bounds. For arbitrary bounds changes, it's assumed that
|
|
||||||
* a telemetry requery is performed anyway, and the collection is cleared
|
|
||||||
* and repopulated.
|
|
||||||
*
|
|
||||||
* @fires TelemetryCollection#added
|
|
||||||
* @fires TelemetryCollection#discarded
|
|
||||||
* @param bounds
|
|
||||||
*/
|
|
||||||
TelemetryCollection.prototype.bounds = function (bounds) {
|
|
||||||
var startChanged = this.lastBounds.start !== bounds.start;
|
|
||||||
var endChanged = this.lastBounds.end !== bounds.end;
|
|
||||||
var startIndex = 0;
|
|
||||||
var endIndex = 0;
|
|
||||||
var discarded;
|
|
||||||
var added;
|
|
||||||
var testValue;
|
|
||||||
|
|
||||||
this.lastBounds = bounds;
|
|
||||||
|
|
||||||
// If collection is not sorted by a time field, we cannot respond to
|
|
||||||
// bounds events
|
|
||||||
if (this.sortField === undefined) {
|
|
||||||
this.lastBounds = bounds;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (startChanged) {
|
|
||||||
testValue = _.set({}, this.sortField, bounds.start);
|
|
||||||
// Calculate the new index of the first item within the bounds
|
|
||||||
startIndex = _.sortedIndex(this.telemetry, testValue, this.sortField);
|
|
||||||
discarded = this.telemetry.splice(0, startIndex);
|
|
||||||
}
|
|
||||||
if (endChanged) {
|
|
||||||
testValue = _.set({}, this.sortField, bounds.end);
|
|
||||||
// Calculate the new index of the last item in bounds
|
|
||||||
endIndex = _.sortedLastIndex(this.highBuffer, testValue, this.sortField);
|
|
||||||
added = this.highBuffer.splice(0, endIndex);
|
|
||||||
added.forEach(function (datum) {
|
|
||||||
this.telemetry.push(datum);
|
|
||||||
}.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (discarded && discarded.length > 0) {
|
|
||||||
/**
|
|
||||||
* A `discarded` event is emitted when telemetry data fall out of
|
|
||||||
* bounds due to a bounds change event
|
|
||||||
* @type {object[]} discarded the telemetry data
|
|
||||||
* discarded as a result of the bounds change
|
|
||||||
*/
|
|
||||||
this.emit('discarded', discarded);
|
|
||||||
}
|
|
||||||
if (added && added.length > 0) {
|
|
||||||
/**
|
|
||||||
* An `added` event is emitted when a bounds change results in
|
|
||||||
* received telemetry falling within the new bounds.
|
|
||||||
* @type {object[]} added the telemetry data that is now within bounds
|
|
||||||
*/
|
|
||||||
this.emit('added', added);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds an individual item to the collection. Used internally only
|
|
||||||
* @private
|
|
||||||
* @param item
|
|
||||||
*/
|
|
||||||
TelemetryCollection.prototype.addOne = function (item) {
|
|
||||||
var isDuplicate = false;
|
|
||||||
var boundsDefined = this.lastBounds &&
|
|
||||||
(this.lastBounds.start !== undefined && this.lastBounds.end !== undefined);
|
|
||||||
var array;
|
|
||||||
var boundsLow;
|
|
||||||
var boundsHigh;
|
|
||||||
|
|
||||||
// If collection is not sorted by a time field, we cannot respond to
|
|
||||||
// bounds events, so no bounds checking necessary
|
|
||||||
if (this.sortField === undefined) {
|
|
||||||
this.telemetry.push(item);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insert into either in-bounds array, or the out of bounds high buffer.
|
|
||||||
// Data in the high buffer will be re-evaluated for possible insertion on next tick
|
|
||||||
|
|
||||||
if (boundsDefined) {
|
|
||||||
boundsHigh = _.get(item, this.sortField) > this.lastBounds.end;
|
|
||||||
boundsLow = _.get(item, this.sortField) < this.lastBounds.start;
|
|
||||||
|
|
||||||
if (!boundsHigh && !boundsLow) {
|
|
||||||
array = this.telemetry;
|
|
||||||
} else if (boundsHigh) {
|
|
||||||
array = this.highBuffer;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
array = this.telemetry;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If out of bounds low, disregard data
|
|
||||||
if (!boundsLow) {
|
|
||||||
// Going to check for duplicates. Bound the search problem to
|
|
||||||
// items around the given time. Use sortedIndex because it
|
|
||||||
// employs a binary search which is O(log n). Can use binary search
|
|
||||||
// based on time stamp because the array is guaranteed ordered due
|
|
||||||
// to sorted insertion.
|
|
||||||
var startIx = _.sortedIndex(array, item, this.sortField);
|
|
||||||
var endIx;
|
|
||||||
|
|
||||||
if (this.dupeCheck && startIx !== array.length) {
|
|
||||||
endIx = _.sortedLastIndex(array, item, this.sortField);
|
|
||||||
|
|
||||||
// Create an array of potential dupes, based on having the
|
|
||||||
// same time stamp
|
|
||||||
var potentialDupes = array.slice(startIx, endIx + 1);
|
|
||||||
// Search potential dupes for exact dupe
|
|
||||||
isDuplicate = _.findIndex(potentialDupes, _.isEqual.bind(undefined, item)) > -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isDuplicate) {
|
|
||||||
array.splice(endIx || startIx, 0, item);
|
|
||||||
|
|
||||||
//Return true if it was added and in bounds
|
|
||||||
return array === this.telemetry;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add an array of objects to this telemetry collection
|
|
||||||
* @fires TelemetryCollection#added
|
|
||||||
* @param {object[]} items
|
|
||||||
*/
|
|
||||||
TelemetryCollection.prototype.add = function (items) {
|
|
||||||
var added = items.filter(this.addOne);
|
|
||||||
this.emit('added', added);
|
|
||||||
this.dupeCheck = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clears the contents of the telemetry collection
|
|
||||||
*/
|
|
||||||
TelemetryCollection.prototype.clear = function () {
|
|
||||||
this.telemetry = [];
|
|
||||||
this.highBuffer = [];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sorts the telemetry collection based on the provided sort field
|
|
||||||
* specifier. Subsequent inserts are sorted to maintain specified sport
|
|
||||||
* order.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* // First build some mock telemetry for the purpose of an example
|
|
||||||
* let now = Date.now();
|
|
||||||
* let telemetry = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(function (value) {
|
|
||||||
* return {
|
|
||||||
* // define an object property to demonstrate nested paths
|
|
||||||
* timestamp: {
|
|
||||||
* ms: now - value * 1000,
|
|
||||||
* text:
|
|
||||||
* },
|
|
||||||
* value: value
|
|
||||||
* }
|
|
||||||
* });
|
|
||||||
* let collection = new TelemetryCollection();
|
|
||||||
*
|
|
||||||
* collection.add(telemetry);
|
|
||||||
*
|
|
||||||
* // Sort by telemetry value
|
|
||||||
* collection.sort("value");
|
|
||||||
*
|
|
||||||
* // Sort by ms since epoch
|
|
||||||
* collection.sort("timestamp.ms");
|
|
||||||
*
|
|
||||||
* // Sort by formatted date text
|
|
||||||
* collection.sort("timestamp.text");
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param {string} sortField An object property path.
|
|
||||||
*/
|
|
||||||
TelemetryCollection.prototype.sort = function (sortField) {
|
|
||||||
this.sortField = sortField;
|
|
||||||
if (sortField !== undefined) {
|
|
||||||
this.telemetry = _.sortBy(this.telemetry, this.iteratee);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return TelemetryCollection;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
@@ -1,828 +0,0 @@
|
|||||||
|
|
||||||
define(
|
|
||||||
[
|
|
||||||
'zepto',
|
|
||||||
'lodash'
|
|
||||||
],
|
|
||||||
function ($, _) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A controller for the MCTTable directive. Populates scope with
|
|
||||||
* data used for populating, sorting, and filtering
|
|
||||||
* tables.
|
|
||||||
* @param $scope
|
|
||||||
* @param $timeout
|
|
||||||
* @param element
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function MCTTableController($scope, $window, element, exportService, formatService, openmct) {
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
this.$scope = $scope;
|
|
||||||
this.element = $(element[0]);
|
|
||||||
this.$window = $window;
|
|
||||||
this.maxDisplayRows = 100;
|
|
||||||
|
|
||||||
this.scrollable = this.element.find('.t-scrolling').first();
|
|
||||||
this.resultsHeader = this.element.find('.mct-table>thead').first();
|
|
||||||
this.sizingTableBody = this.element.find('.t-sizing-table>tbody').first();
|
|
||||||
this.$scope.sizingRow = {};
|
|
||||||
this.$scope.calcTableWidthPx = '100%';
|
|
||||||
this.timeApi = openmct.time;
|
|
||||||
this.toiFormatter = undefined;
|
|
||||||
this.formatService = formatService;
|
|
||||||
this.callbacks = {};
|
|
||||||
|
|
||||||
//Bind all class functions to 'this'
|
|
||||||
_.bindAll(this, [
|
|
||||||
'addRows',
|
|
||||||
'binarySearch',
|
|
||||||
'buildLargestRow',
|
|
||||||
'changeBounds',
|
|
||||||
'changeTimeOfInterest',
|
|
||||||
'changeTimeSystem',
|
|
||||||
'destroyConductorListeners',
|
|
||||||
'digest',
|
|
||||||
'filterAndSort',
|
|
||||||
'filterRows',
|
|
||||||
'firstVisible',
|
|
||||||
'insertSorted',
|
|
||||||
'lastVisible',
|
|
||||||
'onRowClick',
|
|
||||||
'onScroll',
|
|
||||||
'removeRows',
|
|
||||||
'resize',
|
|
||||||
'scrollToBottom',
|
|
||||||
'scrollToRow',
|
|
||||||
'setElementSizes',
|
|
||||||
'setHeaders',
|
|
||||||
'setRows',
|
|
||||||
'setTimeOfInterestRow',
|
|
||||||
'setVisibleRows',
|
|
||||||
'sortComparator',
|
|
||||||
'sortRows'
|
|
||||||
]);
|
|
||||||
|
|
||||||
this.scrollable.on('scroll', this.onScroll);
|
|
||||||
|
|
||||||
$scope.visibleRows = [];
|
|
||||||
$scope.displayRows = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set default values for optional parameters on a given scope
|
|
||||||
*/
|
|
||||||
function setDefaults(scope) {
|
|
||||||
if (typeof scope.enableFilter === 'undefined') {
|
|
||||||
scope.enableFilter = true;
|
|
||||||
scope.filters = {};
|
|
||||||
}
|
|
||||||
if (typeof scope.enableSort === 'undefined') {
|
|
||||||
scope.enableSort = true;
|
|
||||||
scope.sortColumn = undefined;
|
|
||||||
scope.sortDirection = undefined;
|
|
||||||
}
|
|
||||||
if (scope.sortColumn !== undefined) {
|
|
||||||
scope.sortDirection = "asc";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setDefaults($scope);
|
|
||||||
|
|
||||||
$scope.exportAsCSV = function () {
|
|
||||||
var headers = $scope.displayHeaders,
|
|
||||||
filename = $(element[0]).attr('export-as');
|
|
||||||
|
|
||||||
exportService.exportCSV($scope.displayRows.map(function (row) {
|
|
||||||
return headers.reduce(function (r, header) {
|
|
||||||
r[header] = row[header].text;
|
|
||||||
return r;
|
|
||||||
}, {});
|
|
||||||
}), {
|
|
||||||
headers: headers,
|
|
||||||
filename: filename
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.toggleSort = function (key) {
|
|
||||||
if (!$scope.enableSort) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ($scope.sortColumn !== key) {
|
|
||||||
$scope.sortColumn = key;
|
|
||||||
$scope.sortDirection = 'asc';
|
|
||||||
} else if ($scope.sortDirection === 'asc') {
|
|
||||||
$scope.sortDirection = 'desc';
|
|
||||||
} else if ($scope.sortDirection === 'desc') {
|
|
||||||
$scope.sortColumn = undefined;
|
|
||||||
$scope.sortDirection = undefined;
|
|
||||||
} else if ($scope.sortColumn !== undefined &&
|
|
||||||
$scope.sortDirection === undefined) {
|
|
||||||
$scope.sortDirection = 'asc';
|
|
||||||
}
|
|
||||||
self.setRows($scope.rows);
|
|
||||||
self.setTimeOfInterestRow(self.timeApi.timeOfInterest());
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Define watches to listen for changes to headers and rows.
|
|
||||||
*/
|
|
||||||
$scope.$watchCollection('filters', function () {
|
|
||||||
self.setRows($scope.rows);
|
|
||||||
});
|
|
||||||
$scope.$watch('headers', function (newHeaders, oldHeaders) {
|
|
||||||
if (newHeaders !== oldHeaders) {
|
|
||||||
this.setHeaders(newHeaders);
|
|
||||||
}
|
|
||||||
}.bind(this));
|
|
||||||
$scope.$watch('rows', this.setRows);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Listen for rows added individually (eg. for real-time tables)
|
|
||||||
*/
|
|
||||||
$scope.$on('add:rows', this.addRows);
|
|
||||||
$scope.$on('remove:rows', this.removeRows);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Populated from the default-sort attribute on MctTable
|
|
||||||
* directive tag.
|
|
||||||
*/
|
|
||||||
$scope.$watch('defaultSort', function (newColumn, oldColumn) {
|
|
||||||
if (newColumn !== oldColumn) {
|
|
||||||
$scope.toggleSort(newColumn);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Listen for resize events to trigger recalculation of table width
|
|
||||||
*/
|
|
||||||
$scope.resize = this.setElementSizes;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Scope variable that is populated from the 'time-columns'
|
|
||||||
* attribute on the MctTable tag. Indicates which columns, while
|
|
||||||
* sorted, can be used for indicated time of interest.
|
|
||||||
*/
|
|
||||||
$scope.$watch("timeColumns", function (timeColumns) {
|
|
||||||
if (timeColumns) {
|
|
||||||
this.destroyConductorListeners();
|
|
||||||
|
|
||||||
this.timeApi.on('timeSystem', this.changeTimeSystem);
|
|
||||||
this.timeApi.on('timeOfInterest', this.changeTimeOfInterest);
|
|
||||||
this.timeApi.on('bounds', this.changeBounds);
|
|
||||||
|
|
||||||
// If time system defined, set initially
|
|
||||||
if (this.timeApi.timeSystem() !== undefined) {
|
|
||||||
this.changeTimeSystem(this.timeApi.timeSystem());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.bind(this));
|
|
||||||
|
|
||||||
$scope.$on('$destroy', function () {
|
|
||||||
this.scrollable.off('scroll', this.onScroll);
|
|
||||||
this.destroyConductorListeners();
|
|
||||||
|
|
||||||
}.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
MCTTableController.prototype.destroyConductorListeners = function () {
|
|
||||||
this.timeApi.off('timeSystem', this.changeTimeSystem);
|
|
||||||
this.timeApi.off('timeOfInterest', this.changeTimeOfInterest);
|
|
||||||
this.timeApi.off('bounds', this.changeBounds);
|
|
||||||
};
|
|
||||||
|
|
||||||
MCTTableController.prototype.changeTimeSystem = function (timeSystem) {
|
|
||||||
var format = timeSystem.timeFormat;
|
|
||||||
this.toiFormatter = this.formatService.getFormat(format);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If auto-scroll is enabled, this function will scroll to the
|
|
||||||
* bottom of the page
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
MCTTableController.prototype.scrollToBottom = function () {
|
|
||||||
this.scrollable[0].scrollTop = this.scrollable[0].scrollHeight;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles a row add event. Rows can be added as needed using the
|
|
||||||
* `add:row` broadcast event.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
MCTTableController.prototype.addRows = function (event, rows) {
|
|
||||||
//Does the row pass the current filter?
|
|
||||||
if (this.filterRows(rows).length > 0) {
|
|
||||||
rows.forEach(this.insertSorted.bind(this, this.$scope.displayRows));
|
|
||||||
|
|
||||||
//Resize the columns , then update the rows visible in the table
|
|
||||||
this.resize([this.$scope.sizingRow].concat(rows))
|
|
||||||
.then(this.setVisibleRows)
|
|
||||||
.then(function () {
|
|
||||||
if (this.$scope.autoScroll) {
|
|
||||||
this.scrollToBottom();
|
|
||||||
}
|
|
||||||
}.bind(this));
|
|
||||||
|
|
||||||
var toi = this.timeApi.timeOfInterest();
|
|
||||||
if (toi !== -1) {
|
|
||||||
this.setTimeOfInterestRow(toi);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles a row remove event. Rows can be removed as needed using the
|
|
||||||
* `remove:row` broadcast event.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
MCTTableController.prototype.removeRows = function (event, rows) {
|
|
||||||
var indexInDisplayRows;
|
|
||||||
rows.forEach(function (row) {
|
|
||||||
// Do a sequential search here. Only way of finding row is by
|
|
||||||
// object equality, so array is in effect unsorted.
|
|
||||||
indexInDisplayRows = this.$scope.displayRows.indexOf(row);
|
|
||||||
if (indexInDisplayRows !== -1) {
|
|
||||||
this.$scope.displayRows.splice(indexInDisplayRows, 1);
|
|
||||||
}
|
|
||||||
}, this);
|
|
||||||
|
|
||||||
this.$scope.sizingRow = this.buildLargestRow([this.$scope.sizingRow].concat(rows));
|
|
||||||
|
|
||||||
this.setElementSizes();
|
|
||||||
this.setVisibleRows()
|
|
||||||
.then(function () {
|
|
||||||
if (this.$scope.autoScroll) {
|
|
||||||
this.scrollToBottom();
|
|
||||||
}
|
|
||||||
}.bind(this));
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
MCTTableController.prototype.onScroll = function (event) {
|
|
||||||
this.scrollWindow = {
|
|
||||||
top: this.scrollable[0].scrollTop,
|
|
||||||
bottom: this.scrollable[0].scrollTop + this.scrollable[0].offsetHeight,
|
|
||||||
offsetHeight: this.scrollable[0].offsetHeight,
|
|
||||||
height: this.scrollable[0].scrollHeight
|
|
||||||
};
|
|
||||||
this.$window.requestAnimationFrame(function () {
|
|
||||||
this.setVisibleRows();
|
|
||||||
this.digest();
|
|
||||||
|
|
||||||
// If user scrolls away from bottom, disable auto-scroll.
|
|
||||||
// Auto-scroll will be re-enabled if user scrolls to bottom again.
|
|
||||||
if (this.scrollWindow.top <
|
|
||||||
(this.scrollWindow.height - this.scrollWindow.offsetHeight) - 20) {
|
|
||||||
this.$scope.autoScroll = false;
|
|
||||||
} else {
|
|
||||||
this.$scope.autoScroll = true;
|
|
||||||
}
|
|
||||||
this.scrolling = false;
|
|
||||||
delete this.scrollWindow;
|
|
||||||
}.bind(this));
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return first visible row, based on current scroll state.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
MCTTableController.prototype.firstVisible = function () {
|
|
||||||
var topScroll = this.scrollWindow ?
|
|
||||||
this.scrollWindow.top :
|
|
||||||
this.scrollable[0].scrollTop;
|
|
||||||
|
|
||||||
return Math.floor(
|
|
||||||
(topScroll) / this.$scope.rowHeight
|
|
||||||
);
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return last visible row, based on current scroll state.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
MCTTableController.prototype.lastVisible = function () {
|
|
||||||
var bottomScroll = this.scrollWindow ?
|
|
||||||
this.scrollWindow.bottom :
|
|
||||||
this.scrollable[0].scrollTop + this.scrollable[0].offsetHeight;
|
|
||||||
|
|
||||||
return Math.ceil(
|
|
||||||
(bottomScroll) /
|
|
||||||
this.$scope.rowHeight
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets visible rows based on array
|
|
||||||
* content and current scroll state.
|
|
||||||
*/
|
|
||||||
MCTTableController.prototype.setVisibleRows = function () {
|
|
||||||
var self = this,
|
|
||||||
totalVisible,
|
|
||||||
numberOffscreen,
|
|
||||||
firstVisible,
|
|
||||||
lastVisible,
|
|
||||||
start,
|
|
||||||
end;
|
|
||||||
|
|
||||||
//No need to scroll
|
|
||||||
if (this.$scope.displayRows.length < this.maxDisplayRows) {
|
|
||||||
start = 0;
|
|
||||||
end = this.$scope.displayRows.length;
|
|
||||||
} else {
|
|
||||||
firstVisible = this.firstVisible();
|
|
||||||
lastVisible = this.lastVisible();
|
|
||||||
totalVisible = lastVisible - firstVisible;
|
|
||||||
numberOffscreen = this.maxDisplayRows - totalVisible;
|
|
||||||
start = firstVisible - Math.floor(numberOffscreen / 2);
|
|
||||||
end = lastVisible + Math.ceil(numberOffscreen / 2);
|
|
||||||
|
|
||||||
if (start < 0) {
|
|
||||||
start = 0;
|
|
||||||
end = Math.min(this.maxDisplayRows,
|
|
||||||
this.$scope.displayRows.length);
|
|
||||||
} else if (end >= this.$scope.displayRows.length) {
|
|
||||||
end = this.$scope.displayRows.length;
|
|
||||||
start = end - this.maxDisplayRows + 1;
|
|
||||||
}
|
|
||||||
if (this.$scope.visibleRows[0] &&
|
|
||||||
this.$scope.visibleRows[0].rowIndex === start &&
|
|
||||||
this.$scope.visibleRows[this.$scope.visibleRows.length - 1]
|
|
||||||
.rowIndex === end) {
|
|
||||||
return this.digest();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//Set visible rows from display rows, based on calculated offset.
|
|
||||||
this.$scope.visibleRows = this.$scope.displayRows.slice(start, end)
|
|
||||||
.map(function (row, i) {
|
|
||||||
return {
|
|
||||||
rowIndex: start + i,
|
|
||||||
offsetY: ((start + i) * self.$scope.rowHeight),
|
|
||||||
contents: row
|
|
||||||
};
|
|
||||||
});
|
|
||||||
return this.digest();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update table headers with new headers. If filtering is
|
|
||||||
* enabled, reset filters. If sorting is enabled, reset
|
|
||||||
* sorting.
|
|
||||||
*/
|
|
||||||
MCTTableController.prototype.setHeaders = function (newHeaders) {
|
|
||||||
if (!newHeaders) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$scope.displayHeaders = newHeaders;
|
|
||||||
if (this.$scope.enableFilter) {
|
|
||||||
this.$scope.filters = {};
|
|
||||||
}
|
|
||||||
// Reset column sort information unless the new headers
|
|
||||||
// contain the column currently sorted on.
|
|
||||||
if (this.$scope.enableSort &&
|
|
||||||
newHeaders.indexOf(this.$scope.sortColumn) === -1) {
|
|
||||||
this.$scope.sortColumn = undefined;
|
|
||||||
this.$scope.sortDirection = undefined;
|
|
||||||
}
|
|
||||||
this.setRows(this.$scope.rows);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read styles from the DOM and use them to calculate offsets
|
|
||||||
* for individual rows.
|
|
||||||
*/
|
|
||||||
MCTTableController.prototype.setElementSizes = function () {
|
|
||||||
var tbody = this.sizingTableBody,
|
|
||||||
firstRow = tbody.find('tr'),
|
|
||||||
column = firstRow.find('td'),
|
|
||||||
rowHeight = firstRow.prop('offsetHeight'),
|
|
||||||
columnWidth,
|
|
||||||
tableWidth = 0,
|
|
||||||
overallHeight = (rowHeight *
|
|
||||||
(this.$scope.displayRows ? this.$scope.displayRows.length - 1 : 0));
|
|
||||||
|
|
||||||
this.$scope.columnWidths = [];
|
|
||||||
|
|
||||||
while (column.length) {
|
|
||||||
columnWidth = column.prop('offsetWidth');
|
|
||||||
this.$scope.columnWidths.push(column.prop('offsetWidth'));
|
|
||||||
tableWidth += columnWidth;
|
|
||||||
column = column.next();
|
|
||||||
}
|
|
||||||
this.$scope.rowHeight = rowHeight;
|
|
||||||
this.$scope.totalHeight = overallHeight;
|
|
||||||
|
|
||||||
var scrollW = this.scrollable[0].offsetWidth - this.scrollable[0].clientWidth;
|
|
||||||
if (scrollW && scrollW > 0) {
|
|
||||||
this.$scope.calcTableWidthPx = 'calc(100% - ' + scrollW + 'px)';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tableWidth > 0) {
|
|
||||||
this.$scope.totalWidth = tableWidth + 'px';
|
|
||||||
} else {
|
|
||||||
this.$scope.totalWidth = 'none';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds the correct insertion point for a new row, which takes into
|
|
||||||
* account duplicates to make sure new rows are inserted in a way that
|
|
||||||
* maintains arrival order.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @param {Array} searchArray
|
|
||||||
* @param {Object} searchElement Object to find the insertion point for
|
|
||||||
*/
|
|
||||||
MCTTableController.prototype.findInsertionPoint = function (searchArray, searchElement) {
|
|
||||||
var index;
|
|
||||||
var testIndex;
|
|
||||||
var first = searchArray[0];
|
|
||||||
var last = searchArray[searchArray.length - 1];
|
|
||||||
|
|
||||||
if (first) {
|
|
||||||
first = first[this.$scope.sortColumn].text;
|
|
||||||
}
|
|
||||||
if (last) {
|
|
||||||
last = last[this.$scope.sortColumn].text;
|
|
||||||
}
|
|
||||||
// Shortcut check for append/prepend
|
|
||||||
if (first && this.sortComparator(first, searchElement) >= 0) {
|
|
||||||
index = testIndex = 0;
|
|
||||||
} else if (last && this.sortComparator(last, searchElement) <= 0) {
|
|
||||||
index = testIndex = searchArray.length;
|
|
||||||
} else {
|
|
||||||
// use a binary search to find the correct insertion point
|
|
||||||
index = testIndex = this.binarySearch(
|
|
||||||
searchArray,
|
|
||||||
searchElement,
|
|
||||||
0,
|
|
||||||
searchArray.length - 1
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
//It's possible that the insertion point is a duplicate of the element to be inserted
|
|
||||||
var isDupe = function () {
|
|
||||||
return this.sortComparator(searchElement,
|
|
||||||
searchArray[testIndex][this.$scope.sortColumn].text) === 0;
|
|
||||||
}.bind(this);
|
|
||||||
|
|
||||||
// In the event of a duplicate, scan left or right (depending on
|
|
||||||
// sort order) to find an insertion point that maintains order received
|
|
||||||
while (testIndex >= 0 && testIndex < searchArray.length && isDupe()) {
|
|
||||||
if (this.$scope.sortDirection === 'asc') {
|
|
||||||
index = ++testIndex;
|
|
||||||
} else {
|
|
||||||
index = testIndex--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return index;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
MCTTableController.prototype.binarySearch = function (searchArray, searchElement, min, max) {
|
|
||||||
var sampleAt = Math.floor((max - min) / 2) + min;
|
|
||||||
|
|
||||||
if (max < min) {
|
|
||||||
return min; // Element is not in array, min gives direction
|
|
||||||
}
|
|
||||||
switch (this.sortComparator(searchElement,
|
|
||||||
searchArray[sampleAt][this.$scope.sortColumn].text)) {
|
|
||||||
case -1:
|
|
||||||
return this.binarySearch(searchArray, searchElement, min,
|
|
||||||
sampleAt - 1);
|
|
||||||
case 0:
|
|
||||||
return sampleAt;
|
|
||||||
case 1:
|
|
||||||
return this.binarySearch(searchArray, searchElement,
|
|
||||||
sampleAt + 1, max);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
MCTTableController.prototype.insertSorted = function (array, element) {
|
|
||||||
var index = -1;
|
|
||||||
|
|
||||||
if (!this.$scope.sortColumn || !this.$scope.sortDirection) {
|
|
||||||
//No sorting applied, push it on the end.
|
|
||||||
index = array.length;
|
|
||||||
} else {
|
|
||||||
//Sort is enabled, perform binary search to find insertion point
|
|
||||||
index = this.findInsertionPoint(array, element[this.$scope.sortColumn].text);
|
|
||||||
}
|
|
||||||
if (index === -1) {
|
|
||||||
array.unshift(element);
|
|
||||||
} else if (index === array.length) {
|
|
||||||
array.push(element);
|
|
||||||
} else {
|
|
||||||
array.splice(index, 0, element);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compare two variables, returning a number that represents
|
|
||||||
* which is larger. Similar to the default array sort
|
|
||||||
* comparator, but does not coerce all values to string before
|
|
||||||
* conversion. Strings are lowercased before comparison.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
MCTTableController.prototype.sortComparator = function (a, b) {
|
|
||||||
var result = 0,
|
|
||||||
sortDirectionMultiplier,
|
|
||||||
numberA,
|
|
||||||
numberB;
|
|
||||||
/**
|
|
||||||
* Given a value, if it is a number, or a string representation of a
|
|
||||||
* number, then return a number representation. Otherwise, return
|
|
||||||
* the original value. It's a little more robust than using just
|
|
||||||
* Number() or parseFloat, or isNaN in isolation, all of which are
|
|
||||||
* fairly inconsistent in their results.
|
|
||||||
* @param value The value to return as a number.
|
|
||||||
* @returns {*} The value cast to a Number, or the original value if
|
|
||||||
* a Number representation is not possible.
|
|
||||||
*/
|
|
||||||
function toNumber(value) {
|
|
||||||
var val = !isNaN(Number(value)) && !isNaN(parseFloat(value)) ? Number(value) : value;
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
|
|
||||||
numberA = toNumber(a);
|
|
||||||
numberB = toNumber(b);
|
|
||||||
|
|
||||||
//If they're both numbers, then compare them as numbers
|
|
||||||
if (typeof numberA === "number" && typeof numberB === "number") {
|
|
||||||
a = numberA;
|
|
||||||
b = numberB;
|
|
||||||
}
|
|
||||||
|
|
||||||
//If they're both strings, then ignore case
|
|
||||||
if (typeof a === "string" && typeof b === "string") {
|
|
||||||
a = a.toLowerCase();
|
|
||||||
b = b.toLowerCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (a < b) {
|
|
||||||
result = -1;
|
|
||||||
}
|
|
||||||
if (a > b) {
|
|
||||||
result = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.$scope.sortDirection === 'asc') {
|
|
||||||
sortDirectionMultiplier = 1;
|
|
||||||
} else if (this.$scope.sortDirection === 'desc') {
|
|
||||||
sortDirectionMultiplier = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result * sortDirectionMultiplier;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a new array which is a result of applying the sort
|
|
||||||
* criteria defined in $scope.
|
|
||||||
*
|
|
||||||
* Does not modify the array that was passed in.
|
|
||||||
*/
|
|
||||||
MCTTableController.prototype.sortRows = function (rowsToSort) {
|
|
||||||
var self = this,
|
|
||||||
sortKey = this.$scope.sortColumn;
|
|
||||||
|
|
||||||
if (!this.$scope.sortColumn || !this.$scope.sortDirection) {
|
|
||||||
return rowsToSort;
|
|
||||||
}
|
|
||||||
|
|
||||||
return rowsToSort.sort(function (a, b) {
|
|
||||||
return self.sortComparator(a[sortKey].text, b[sortKey].text);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an object which contains the largest values
|
|
||||||
* for each key in the given set of rows. This is used to
|
|
||||||
* pre-calculate optimal column sizes without having to render
|
|
||||||
* every row.
|
|
||||||
*/
|
|
||||||
MCTTableController.prototype.buildLargestRow = function (rows) {
|
|
||||||
var largestRow = rows.reduce(function (prevLargest, row) {
|
|
||||||
Object.keys(row).forEach(function (key) {
|
|
||||||
var currentColumn,
|
|
||||||
currentColumnLength,
|
|
||||||
largestColumn,
|
|
||||||
largestColumnLength;
|
|
||||||
if (row[key]) {
|
|
||||||
currentColumn = (row[key]).text;
|
|
||||||
currentColumnLength =
|
|
||||||
(currentColumn && currentColumn.length) ?
|
|
||||||
currentColumn.length :
|
|
||||||
currentColumn;
|
|
||||||
largestColumn = prevLargest[key] ? prevLargest[key].text : "";
|
|
||||||
largestColumnLength = largestColumn.length;
|
|
||||||
|
|
||||||
if (currentColumnLength > largestColumnLength) {
|
|
||||||
prevLargest[key] = JSON.parse(JSON.stringify(row[key]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return prevLargest;
|
|
||||||
}, JSON.parse(JSON.stringify(rows[0] || {})));
|
|
||||||
return largestRow;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Will effectively cap digests at 60Hz
|
|
||||||
// Also turns digest into a promise allowing code to force digest, then
|
|
||||||
// schedule something to happen afterwards
|
|
||||||
MCTTableController.prototype.digest = function () {
|
|
||||||
var scope = this.$scope;
|
|
||||||
var self = this;
|
|
||||||
var raf = this.$window.requestAnimationFrame;
|
|
||||||
var promise = this.digestPromise;
|
|
||||||
|
|
||||||
if (!promise) {
|
|
||||||
self.digestPromise = promise = new Promise(function (resolve) {
|
|
||||||
raf(function () {
|
|
||||||
scope.$digest();
|
|
||||||
self.digestPromise = undefined;
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return promise;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates the widest row in the table, and if necessary, resizes
|
|
||||||
* the table accordingly
|
|
||||||
*
|
|
||||||
* @param rows the rows on which to resize
|
|
||||||
* @returns {Promise} a promise that will resolve when resizing has
|
|
||||||
* occurred.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
MCTTableController.prototype.resize = function (rows) {
|
|
||||||
this.$scope.sizingRow = this.buildLargestRow(rows);
|
|
||||||
return this.digest().then(this.setElementSizes);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
MCTTableController.prototype.filterAndSort = function (rows) {
|
|
||||||
var displayRows = rows;
|
|
||||||
if (this.$scope.enableFilter) {
|
|
||||||
displayRows = this.filterRows(displayRows);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.$scope.enableSort) {
|
|
||||||
displayRows = this.sortRows(displayRows.slice(0));
|
|
||||||
}
|
|
||||||
return displayRows;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update rows with new data. If filtering is enabled, rows
|
|
||||||
* will be sorted before display.
|
|
||||||
*/
|
|
||||||
MCTTableController.prototype.setRows = function (newRows) {
|
|
||||||
//Nothing to show because no columns visible
|
|
||||||
if (!this.$scope.displayHeaders || !newRows) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$scope.displayRows = this.filterAndSort(newRows || []);
|
|
||||||
return this.resize(newRows)
|
|
||||||
.then(function (rows) {
|
|
||||||
return this.setVisibleRows(rows);
|
|
||||||
}.bind(this))
|
|
||||||
//Timeout following setVisibleRows to allow digest to
|
|
||||||
// perform DOM changes, otherwise scrollTo won't work.
|
|
||||||
.then(function () {
|
|
||||||
//If TOI specified, scroll to it
|
|
||||||
var timeOfInterest = this.timeApi.timeOfInterest();
|
|
||||||
if (timeOfInterest) {
|
|
||||||
this.setTimeOfInterestRow(timeOfInterest);
|
|
||||||
this.scrollToRow(this.$scope.toiRowIndex);
|
|
||||||
}
|
|
||||||
}.bind(this));
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Applies user defined filters to rows. These filters are based on
|
|
||||||
* the text entered in the search areas in each column.
|
|
||||||
* @param rowsToFilter {Object[]} The rows to apply filters to
|
|
||||||
* @returns {Object[]} A filtered copy of the supplied rows
|
|
||||||
*/
|
|
||||||
MCTTableController.prototype.filterRows = function (rowsToFilter) {
|
|
||||||
var filters = {},
|
|
||||||
self = this;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if row matches all filters.
|
|
||||||
*/
|
|
||||||
function matchRow(filterMap, row) {
|
|
||||||
return Object.keys(filterMap).every(function (key) {
|
|
||||||
if (!row[key]) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
var testVal = String(row[key].text).toLowerCase();
|
|
||||||
return testVal.indexOf(filterMap[key]) !== -1;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Object.keys(this.$scope.filters).length) {
|
|
||||||
return rowsToFilter;
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.keys(this.$scope.filters).forEach(function (key) {
|
|
||||||
if (!self.$scope.filters[key]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
filters[key] = self.$scope.filters[key].toLowerCase();
|
|
||||||
});
|
|
||||||
|
|
||||||
return rowsToFilter.filter(matchRow.bind(null, filters));
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Scroll the view to a given row index
|
|
||||||
* @param displayRowIndex {number} The index in the displayed rows
|
|
||||||
* to scroll to.
|
|
||||||
*/
|
|
||||||
MCTTableController.prototype.scrollToRow = function (displayRowIndex) {
|
|
||||||
|
|
||||||
var visible = displayRowIndex > this.firstVisible() && displayRowIndex < this.lastVisible();
|
|
||||||
|
|
||||||
if (!visible) {
|
|
||||||
var scrollTop = displayRowIndex * this.$scope.rowHeight +
|
|
||||||
(this.scrollable[0].offsetHeight / 2);
|
|
||||||
this.scrollable[0].scrollTop = scrollTop;
|
|
||||||
this.setVisibleRows();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update rows with new data. If filtering is enabled, rows
|
|
||||||
* will be sorted before display.
|
|
||||||
*/
|
|
||||||
MCTTableController.prototype.setTimeOfInterestRow = function (newTOI) {
|
|
||||||
var isSortedByTime =
|
|
||||||
this.$scope.timeColumns &&
|
|
||||||
this.$scope.timeColumns.indexOf(this.$scope.sortColumn) !== -1;
|
|
||||||
|
|
||||||
this.$scope.toiRowIndex = -1;
|
|
||||||
|
|
||||||
if (newTOI && isSortedByTime) {
|
|
||||||
var formattedTOI = this.toiFormatter.format(newTOI);
|
|
||||||
var rowIndex = this.binarySearch(
|
|
||||||
this.$scope.displayRows,
|
|
||||||
formattedTOI,
|
|
||||||
0,
|
|
||||||
this.$scope.displayRows.length - 1);
|
|
||||||
|
|
||||||
if (rowIndex > 0 && rowIndex < this.$scope.displayRows.length) {
|
|
||||||
this.$scope.toiRowIndex = rowIndex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
MCTTableController.prototype.changeTimeOfInterest = function (newTOI) {
|
|
||||||
this.setTimeOfInterestRow(newTOI);
|
|
||||||
this.scrollToRow(this.$scope.toiRowIndex);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On zoom, pan, etc. reset TOI
|
|
||||||
* @param bounds
|
|
||||||
*/
|
|
||||||
MCTTableController.prototype.changeBounds = function (bounds) {
|
|
||||||
this.setTimeOfInterestRow(this.timeApi.timeOfInterest());
|
|
||||||
if (this.$scope.toiRowIndex !== -1) {
|
|
||||||
this.scrollToRow(this.$scope.toiRowIndex);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
MCTTableController.prototype.onRowClick = function (event, rowIndex) {
|
|
||||||
if (this.$scope.timeColumns.indexOf(this.$scope.sortColumn) !== -1) {
|
|
||||||
var selectedTime = this.$scope.displayRows[rowIndex][this.$scope.sortColumn].text;
|
|
||||||
if (selectedTime &&
|
|
||||||
this.toiFormatter.validate(selectedTime) &&
|
|
||||||
event.altKey) {
|
|
||||||
this.timeApi.timeOfInterest(this.toiFormatter.parse(selectedTime));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return MCTTableController;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
[],
|
|
||||||
function () {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Notes on implementation of plot options
|
|
||||||
*
|
|
||||||
* Multiple y-axes will have to be handled with multiple forms as
|
|
||||||
* they will need to be stored on distinct model object
|
|
||||||
*
|
|
||||||
* Likewise plot series options per-child will need to be separate
|
|
||||||
* forms.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The LayoutController is responsible for supporting the
|
|
||||||
* Layout view. It arranges frames according to saved configuration
|
|
||||||
* and provides methods for updating these based on mouse
|
|
||||||
* movement.
|
|
||||||
* @memberof platform/features/plot
|
|
||||||
* @constructor
|
|
||||||
* @param {Scope} $scope the controller's Angular scope
|
|
||||||
*/
|
|
||||||
function TableOptionsController($scope) {
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
this.$scope = $scope;
|
|
||||||
this.domainObject = $scope.domainObject;
|
|
||||||
this.listeners = [];
|
|
||||||
|
|
||||||
$scope.columnsForm = {};
|
|
||||||
|
|
||||||
function unlisten() {
|
|
||||||
self.listeners.forEach(function (listener) {
|
|
||||||
listener();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.$watch('domainObject', function (domainObject) {
|
|
||||||
unlisten();
|
|
||||||
self.populateForm(domainObject.getModel());
|
|
||||||
|
|
||||||
self.listeners.push(self.domainObject.getCapability('mutation').listen(function (model) {
|
|
||||||
self.populateForm(model);
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maintain a configuration object on scope that stores column
|
|
||||||
* configuration. On change, synchronize with object model.
|
|
||||||
*/
|
|
||||||
$scope.$watchCollection('configuration.table.columns', function (newColumns, oldColumns) {
|
|
||||||
if (newColumns !== oldColumns) {
|
|
||||||
self.domainObject.useCapability('mutation', function (model) {
|
|
||||||
model.configuration.table.columns = newColumns;
|
|
||||||
});
|
|
||||||
self.domainObject.getCapability('persistence').persist();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Destroy all mutation listeners
|
|
||||||
*/
|
|
||||||
$scope.$on('$destroy', unlisten);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
TableOptionsController.prototype.populateForm = function (model) {
|
|
||||||
var columnsDefinition = (((model.configuration || {}).table || {}).columns || {}),
|
|
||||||
rows = [];
|
|
||||||
this.$scope.columnsForm = {
|
|
||||||
'name': 'Columns',
|
|
||||||
'sections': [{
|
|
||||||
'name': 'Columns',
|
|
||||||
'rows': rows
|
|
||||||
}]};
|
|
||||||
|
|
||||||
Object.keys(columnsDefinition).forEach(function (key) {
|
|
||||||
rows.push({
|
|
||||||
'name': key,
|
|
||||||
'control': 'checkbox',
|
|
||||||
'key': key
|
|
||||||
});
|
|
||||||
});
|
|
||||||
this.$scope.configuration = JSON.parse(JSON.stringify(model.configuration || {}));
|
|
||||||
};
|
|
||||||
|
|
||||||
return TableOptionsController;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
@@ -1,450 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
/* global console*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This bundle adds a table view for displaying telemetry data.
|
|
||||||
* @namespace platform/features/table
|
|
||||||
*/
|
|
||||||
define(
|
|
||||||
[
|
|
||||||
'../TableConfiguration',
|
|
||||||
'../../../../../src/api/objects/object-utils',
|
|
||||||
'../TelemetryCollection',
|
|
||||||
'lodash'
|
|
||||||
|
|
||||||
],
|
|
||||||
function (TableConfiguration, objectUtils, TelemetryCollection, _) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The TableController is responsible for getting data onto the page
|
|
||||||
* in the table widget. This includes handling composition,
|
|
||||||
* configuration, and telemetry subscriptions.
|
|
||||||
* @memberof platform/features/table
|
|
||||||
* @param $scope
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function TelemetryTableController(
|
|
||||||
$scope,
|
|
||||||
$timeout,
|
|
||||||
openmct
|
|
||||||
) {
|
|
||||||
|
|
||||||
this.$scope = $scope;
|
|
||||||
this.$timeout = $timeout;
|
|
||||||
this.openmct = openmct;
|
|
||||||
this.batchSize = 1000;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Initialization block
|
|
||||||
*/
|
|
||||||
this.columns = {}; //Range and Domain columns
|
|
||||||
this.unobserveObject = undefined;
|
|
||||||
this.subscriptions = [];
|
|
||||||
this.timeColumns = [];
|
|
||||||
$scope.rows = [];
|
|
||||||
this.table = new TableConfiguration($scope.domainObject,
|
|
||||||
openmct);
|
|
||||||
this.lastBounds = this.openmct.time.bounds();
|
|
||||||
this.lastRequestTime = 0;
|
|
||||||
this.telemetry = new TelemetryCollection();
|
|
||||||
if (this.lastBounds) {
|
|
||||||
this.telemetry.bounds(this.lastBounds);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Create a new format object from legacy object, and replace it
|
|
||||||
* when it changes
|
|
||||||
*/
|
|
||||||
this.domainObject = objectUtils.toNewFormat($scope.domainObject.getModel(),
|
|
||||||
$scope.domainObject.getId());
|
|
||||||
|
|
||||||
this.$scope.exportAs = this.$scope.domainObject.getModel().name;
|
|
||||||
|
|
||||||
_.bindAll(this, [
|
|
||||||
'destroy',
|
|
||||||
'sortByTimeSystem',
|
|
||||||
'loadColumns',
|
|
||||||
'getHistoricalData',
|
|
||||||
'subscribeToNewData',
|
|
||||||
'changeBounds',
|
|
||||||
'setClock',
|
|
||||||
'addRowsToTable',
|
|
||||||
'removeRowsFromTable'
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Retrieve data when domain object is available.
|
|
||||||
// Also deferring telemetry request makes testing easier as controller
|
|
||||||
// construction has no unintended consequences.
|
|
||||||
$scope.$watch("domainObject", function () {
|
|
||||||
this.getData();
|
|
||||||
this.registerChangeListeners();
|
|
||||||
}.bind(this));
|
|
||||||
|
|
||||||
this.setClock(this.openmct.time.clock());
|
|
||||||
|
|
||||||
this.$scope.$on("$destroy", this.destroy);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @param {boolean} scroll
|
|
||||||
*/
|
|
||||||
TelemetryTableController.prototype.setClock = function (clock) {
|
|
||||||
this.$scope.autoScroll = clock !== undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Based on the selected time system, find a matching domain column
|
|
||||||
* to sort by. By default will just match on key.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
TelemetryTableController.prototype.sortByTimeSystem = function () {
|
|
||||||
var scope = this.$scope;
|
|
||||||
var sortColumn;
|
|
||||||
scope.defaultSort = undefined;
|
|
||||||
|
|
||||||
sortColumn = this.table.columns.filter(function (column) {
|
|
||||||
return column.isCurrentTimeSystem();
|
|
||||||
})[0];
|
|
||||||
if (sortColumn) {
|
|
||||||
scope.defaultSort = sortColumn.title();
|
|
||||||
this.telemetry.sort(sortColumn.title() + '.value');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attaches listeners that respond to state change in domain object,
|
|
||||||
* conductor, and receipt of telemetry
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
TelemetryTableController.prototype.registerChangeListeners = function () {
|
|
||||||
if (this.unobserveObject) {
|
|
||||||
this.unobserveObject();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.unobserveObject = this.openmct.objects.observe(this.domainObject, "*",
|
|
||||||
function (domainObject) {
|
|
||||||
this.domainObject = domainObject;
|
|
||||||
this.getData();
|
|
||||||
}.bind(this)
|
|
||||||
);
|
|
||||||
|
|
||||||
this.openmct.time.on('timeSystem', this.sortByTimeSystem);
|
|
||||||
this.openmct.time.on('bounds', this.changeBounds);
|
|
||||||
this.openmct.time.on('clock', this.setClock);
|
|
||||||
|
|
||||||
this.telemetry.on('added', this.addRowsToTable);
|
|
||||||
this.telemetry.on('discarded', this.removeRowsFromTable);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On receipt of new telemetry, informs mct-table directive that new rows
|
|
||||||
* are available and passes populated rows to it
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @param rows
|
|
||||||
*/
|
|
||||||
TelemetryTableController.prototype.addRowsToTable = function (rows) {
|
|
||||||
this.$scope.$broadcast('add:rows', rows);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When rows are to be removed, informs mct-table directive. Row removal
|
|
||||||
* happens when rows call outside the bounds of the time conductor
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @param rows
|
|
||||||
*/
|
|
||||||
TelemetryTableController.prototype.removeRowsFromTable = function (rows) {
|
|
||||||
this.$scope.$broadcast('remove:rows', rows);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On Time Conductor bounds change, update displayed telemetry. In the
|
|
||||||
* case of a tick, previously visible telemetry that is now out of band
|
|
||||||
* will be removed from the table.
|
|
||||||
* @param {openmct.TimeConductorBounds~TimeConductorBounds} bounds
|
|
||||||
*/
|
|
||||||
TelemetryTableController.prototype.changeBounds = function (bounds, isTick) {
|
|
||||||
if (isTick) {
|
|
||||||
this.telemetry.bounds(bounds);
|
|
||||||
} else {
|
|
||||||
// Is fixed bounds change
|
|
||||||
this.getData();
|
|
||||||
}
|
|
||||||
this.lastBounds = bounds;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clean controller, deregistering listeners etc.
|
|
||||||
*/
|
|
||||||
TelemetryTableController.prototype.destroy = function () {
|
|
||||||
|
|
||||||
this.openmct.time.off('timeSystem', this.sortByTimeSystem);
|
|
||||||
this.openmct.time.off('bounds', this.changeBounds);
|
|
||||||
this.openmct.time.off('clock', this.setClock);
|
|
||||||
|
|
||||||
this.subscriptions.forEach(function (subscription) {
|
|
||||||
subscription();
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.unobserveObject) {
|
|
||||||
this.unobserveObject();
|
|
||||||
}
|
|
||||||
this.subscriptions = [];
|
|
||||||
|
|
||||||
if (this.timeoutHandle) {
|
|
||||||
this.$timeout.cancel(this.timeoutHandle);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For given objects, populate column metadata and table headers.
|
|
||||||
* @private
|
|
||||||
* @param {module:openmct.DomainObject[]} objects the domain objects for
|
|
||||||
* which columns should be populated
|
|
||||||
*/
|
|
||||||
TelemetryTableController.prototype.loadColumns = function (objects) {
|
|
||||||
var telemetryApi = this.openmct.telemetry;
|
|
||||||
|
|
||||||
this.table = new TableConfiguration(this.$scope.domainObject,
|
|
||||||
this.openmct);
|
|
||||||
|
|
||||||
this.$scope.headers = [];
|
|
||||||
|
|
||||||
if (objects.length > 0) {
|
|
||||||
objects.forEach(function (object) {
|
|
||||||
var metadataValues = telemetryApi.getMetadata(object).values();
|
|
||||||
metadataValues.forEach(function (metadatum) {
|
|
||||||
this.table.addColumn(object, metadatum);
|
|
||||||
}.bind(this));
|
|
||||||
}.bind(this));
|
|
||||||
|
|
||||||
this.filterColumns();
|
|
||||||
this.sortByTimeSystem();
|
|
||||||
}
|
|
||||||
|
|
||||||
return objects;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request telemetry data from an historical store for given objects.
|
|
||||||
* @private
|
|
||||||
* @param {object[]} The domain objects to request telemetry for
|
|
||||||
* @returns {Promise} resolved when historical data is available
|
|
||||||
*/
|
|
||||||
TelemetryTableController.prototype.getHistoricalData = function (objects) {
|
|
||||||
var self = this;
|
|
||||||
var openmct = this.openmct;
|
|
||||||
var bounds = openmct.time.bounds();
|
|
||||||
var scope = this.$scope;
|
|
||||||
var rowData = [];
|
|
||||||
var processedObjects = 0;
|
|
||||||
var requestTime = this.lastRequestTime = Date.now();
|
|
||||||
var telemetryCollection = this.telemetry;
|
|
||||||
|
|
||||||
var promise = new Promise(function (resolve, reject) {
|
|
||||||
/*
|
|
||||||
* On completion of batched processing, set the rows on scope
|
|
||||||
*/
|
|
||||||
function finishProcessing() {
|
|
||||||
telemetryCollection.add(rowData);
|
|
||||||
scope.rows = telemetryCollection.telemetry;
|
|
||||||
self.loading(false);
|
|
||||||
|
|
||||||
resolve(scope.rows);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Process a batch of historical data
|
|
||||||
*/
|
|
||||||
function processData(object, historicalData, index, limitEvaluator) {
|
|
||||||
if (index >= historicalData.length) {
|
|
||||||
processedObjects++;
|
|
||||||
|
|
||||||
if (processedObjects === objects.length) {
|
|
||||||
finishProcessing();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
rowData = rowData.concat(historicalData.slice(index, index + self.batchSize)
|
|
||||||
.map(self.table.getRowValues.bind(self.table, object, limitEvaluator)));
|
|
||||||
/*
|
|
||||||
Use timeout to yield process to other UI activities. On
|
|
||||||
return, process next batch
|
|
||||||
*/
|
|
||||||
self.timeoutHandle = self.$timeout(function () {
|
|
||||||
processData(object, historicalData, index + self.batchSize, limitEvaluator);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function makeTableRows(object, historicalData) {
|
|
||||||
// Only process the most recent request
|
|
||||||
if (requestTime === self.lastRequestTime) {
|
|
||||||
var limitEvaluator = openmct.telemetry.limitEvaluator(object);
|
|
||||||
processData(object, historicalData, 0, limitEvaluator);
|
|
||||||
} else {
|
|
||||||
resolve(rowData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Use the telemetry API to request telemetry for a given object
|
|
||||||
*/
|
|
||||||
function requestData(object) {
|
|
||||||
return openmct.telemetry.request(object, {
|
|
||||||
start: bounds.start,
|
|
||||||
end: bounds.end
|
|
||||||
}).then(makeTableRows.bind(undefined, object))
|
|
||||||
.catch(reject);
|
|
||||||
}
|
|
||||||
this.$timeout.cancel(this.timeoutHandle);
|
|
||||||
|
|
||||||
if (objects.length > 0) {
|
|
||||||
objects.forEach(requestData);
|
|
||||||
} else {
|
|
||||||
self.loading(false);
|
|
||||||
resolve([]);
|
|
||||||
}
|
|
||||||
}.bind(this));
|
|
||||||
|
|
||||||
return promise;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Subscribe to real-time data for the given objects.
|
|
||||||
* @private
|
|
||||||
* @param {object[]} objects The objects to subscribe to.
|
|
||||||
*/
|
|
||||||
TelemetryTableController.prototype.subscribeToNewData = function (objects) {
|
|
||||||
var telemetryApi = this.openmct.telemetry;
|
|
||||||
var telemetryCollection = this.telemetry;
|
|
||||||
//Set table max length to avoid unbounded growth.
|
|
||||||
var limitEvaluator;
|
|
||||||
var table = this.table;
|
|
||||||
|
|
||||||
this.subscriptions.forEach(function (subscription) {
|
|
||||||
subscription();
|
|
||||||
});
|
|
||||||
this.subscriptions = [];
|
|
||||||
|
|
||||||
function newData(domainObject, datum) {
|
|
||||||
limitEvaluator = telemetryApi.limitEvaluator(domainObject);
|
|
||||||
telemetryCollection.add([table.getRowValues(domainObject, limitEvaluator, datum)]);
|
|
||||||
}
|
|
||||||
|
|
||||||
objects.forEach(function (object) {
|
|
||||||
this.subscriptions.push(
|
|
||||||
telemetryApi.subscribe(object, newData.bind(this, object), {}));
|
|
||||||
}.bind(this));
|
|
||||||
|
|
||||||
return objects;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return an array of telemetry objects in this view that should be
|
|
||||||
* subscribed to.
|
|
||||||
* @private
|
|
||||||
* @returns {Promise<Array>} a promise that resolves with an array of
|
|
||||||
* telemetry objects in this view.
|
|
||||||
*/
|
|
||||||
TelemetryTableController.prototype.getTelemetryObjects = function () {
|
|
||||||
var telemetryApi = this.openmct.telemetry;
|
|
||||||
var compositionApi = this.openmct.composition;
|
|
||||||
|
|
||||||
function filterForTelemetry(objects) {
|
|
||||||
return objects.filter(telemetryApi.isTelemetryObject.bind(telemetryApi));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If parent object is a telemetry object, subscribe to it. Do not
|
|
||||||
* test composees.
|
|
||||||
*/
|
|
||||||
if (telemetryApi.isTelemetryObject(this.domainObject)) {
|
|
||||||
return Promise.resolve([this.domainObject]);
|
|
||||||
} else {
|
|
||||||
/*
|
|
||||||
* If parent object is not a telemetry object, subscribe to all
|
|
||||||
* composees that are telemetry producing objects.
|
|
||||||
*/
|
|
||||||
var composition = compositionApi.get(this.domainObject);
|
|
||||||
|
|
||||||
if (composition) {
|
|
||||||
return composition
|
|
||||||
.load()
|
|
||||||
.then(filterForTelemetry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request historical data, and subscribe to for real-time data.
|
|
||||||
* @private
|
|
||||||
* @returns {Promise} A promise that is resolved once subscription is
|
|
||||||
* established, and historical telemetry is received and processed.
|
|
||||||
*/
|
|
||||||
TelemetryTableController.prototype.getData = function () {
|
|
||||||
var scope = this.$scope;
|
|
||||||
|
|
||||||
this.telemetry.clear();
|
|
||||||
this.telemetry.bounds(this.openmct.time.bounds());
|
|
||||||
|
|
||||||
this.loading(true);
|
|
||||||
scope.rows = [];
|
|
||||||
|
|
||||||
return this.getTelemetryObjects()
|
|
||||||
.then(this.loadColumns)
|
|
||||||
.then(this.subscribeToNewData)
|
|
||||||
.then(this.getHistoricalData)
|
|
||||||
.catch(function error(e) {
|
|
||||||
this.loading(false);
|
|
||||||
console.error(e.stack || e);
|
|
||||||
}.bind(this));
|
|
||||||
};
|
|
||||||
|
|
||||||
TelemetryTableController.prototype.loading = function (loading) {
|
|
||||||
this.$timeout(function () {
|
|
||||||
this.$scope.loading = loading;
|
|
||||||
}.bind(this));
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When column configuration changes, update the visible headers
|
|
||||||
* accordingly.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
TelemetryTableController.prototype.filterColumns = function () {
|
|
||||||
var columnConfig = this.table.buildColumnConfiguration();
|
|
||||||
|
|
||||||
//Populate headers with visible columns (determined by configuration)
|
|
||||||
this.$scope.headers = Object.keys(columnConfig).filter(function (column) {
|
|
||||||
return columnConfig[column];
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return TelemetryTableController;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
[
|
|
||||||
"../controllers/MCTTableController",
|
|
||||||
"../../res/templates/mct-table.html"
|
|
||||||
],
|
|
||||||
function (MCTTableController, TableTemplate) {
|
|
||||||
/**
|
|
||||||
* Defines a generic 'Table' component. The table can be populated
|
|
||||||
* en-masse by setting the rows attribute, or rows can be added as
|
|
||||||
* needed via a broadcast 'addRow' event.
|
|
||||||
*
|
|
||||||
* This directive accepts parameters specifying header and row
|
|
||||||
* content, as well as some additional options.
|
|
||||||
*
|
|
||||||
* Two broadcast events for notifying the table that the rows have
|
|
||||||
* changed. For performance reasons, the table does not monitor the
|
|
||||||
* content of `rows` constantly.
|
|
||||||
* - 'add:row': A $broadcast event that will notify the table that
|
|
||||||
* a new row has been added to the table.
|
|
||||||
* eg.
|
|
||||||
* <pre><code>
|
|
||||||
* $scope.rows.push(newRow);
|
|
||||||
* $scope.$broadcast('add:row', $scope.rows.length-1);
|
|
||||||
* </code></pre>
|
|
||||||
* The code above adds a new row, and alerts the table using the
|
|
||||||
* add:row event. Sorting and filtering will be applied
|
|
||||||
* automatically by the table component.
|
|
||||||
*
|
|
||||||
* - 'remove:row': A $broadcast event that will notify the table that a
|
|
||||||
* row should be removed from the table.
|
|
||||||
* eg.
|
|
||||||
* <pre><code>
|
|
||||||
* $scope.rows.slice(5, 1);
|
|
||||||
* $scope.$broadcast('remove:row', 5);
|
|
||||||
* </code></pre>
|
|
||||||
* The code above removes a row from the rows array, and then alerts
|
|
||||||
* the table to its removal.
|
|
||||||
*
|
|
||||||
* @memberof platform/features/table
|
|
||||||
* @param {string[]} headers The column titles to appear at the top
|
|
||||||
* of the table. Corresponding values are specified in the rows
|
|
||||||
* using the header title provided here.
|
|
||||||
* @param {Object[]} rows The row content. Each row is an object
|
|
||||||
* with key-value pairs where the key corresponds to a header
|
|
||||||
* specified in the headers parameter.
|
|
||||||
* @param {boolean} enableFilter If true, values will be searchable
|
|
||||||
* and results filtered
|
|
||||||
* @param {boolean} enableSort If true, sorting will be enabled
|
|
||||||
* allowing sorting by clicking on column headers
|
|
||||||
* @param {boolean} autoScroll If true, table will automatically
|
|
||||||
* scroll to the bottom as new data arrives. Auto-scroll can be
|
|
||||||
* disengaged manually by scrolling away from the bottom of the
|
|
||||||
* table, and can also be enabled manually by scrolling to the bottom of
|
|
||||||
* the table rows.
|
|
||||||
*
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function MCTTable() {
|
|
||||||
return {
|
|
||||||
restrict: "E",
|
|
||||||
template: TableTemplate,
|
|
||||||
controller: [
|
|
||||||
'$scope',
|
|
||||||
'$window',
|
|
||||||
'$element',
|
|
||||||
'exportService',
|
|
||||||
'formatService',
|
|
||||||
'openmct',
|
|
||||||
MCTTableController
|
|
||||||
],
|
|
||||||
controllerAs: "table",
|
|
||||||
scope: {
|
|
||||||
headers: "=",
|
|
||||||
rows: "=",
|
|
||||||
formatCell: "=?",
|
|
||||||
enableFilter: "=?",
|
|
||||||
enableSort: "=?",
|
|
||||||
autoScroll: "=?",
|
|
||||||
// Used to indicate which columns contain time data. This
|
|
||||||
// will be used for determining when the table is sorted
|
|
||||||
// by the column that can be used for time conductor
|
|
||||||
// time of interest.
|
|
||||||
timeColumns: "=?",
|
|
||||||
// Indicate a column to sort on. Allows control of sort
|
|
||||||
// via configuration (eg. for default sort column).
|
|
||||||
defaultSort: "=?"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return MCTTable;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
@@ -1,214 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
[
|
|
||||||
"../src/TableConfiguration"
|
|
||||||
],
|
|
||||||
function (Table) {
|
|
||||||
|
|
||||||
describe("A table", function () {
|
|
||||||
var mockTableObject,
|
|
||||||
mockTelemetryObject,
|
|
||||||
mockAPI,
|
|
||||||
mockTelemetryAPI,
|
|
||||||
table,
|
|
||||||
mockTimeAPI,
|
|
||||||
mockObjectsAPI,
|
|
||||||
mockModel;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
mockTableObject = jasmine.createSpyObj('domainObject',
|
|
||||||
['getModel', 'useCapability', 'getCapability', 'hasCapability']
|
|
||||||
);
|
|
||||||
mockModel = {};
|
|
||||||
mockTableObject.getModel.and.returnValue(mockModel);
|
|
||||||
mockTableObject.getCapability.and.callFake(function (name) {
|
|
||||||
return name === 'editor' && {
|
|
||||||
isEditContextRoot: function () {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
mockTelemetryObject = {
|
|
||||||
identifier: {
|
|
||||||
namespace: 'mock',
|
|
||||||
key: 'domainObject'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
mockTelemetryAPI = jasmine.createSpyObj('telemetryAPI', [
|
|
||||||
'getValueFormatter'
|
|
||||||
]);
|
|
||||||
mockTimeAPI = jasmine.createSpyObj('timeAPI', [
|
|
||||||
'timeSystem'
|
|
||||||
]);
|
|
||||||
mockObjectsAPI = jasmine.createSpyObj('objectsAPI', [
|
|
||||||
'makeKeyString'
|
|
||||||
]);
|
|
||||||
mockObjectsAPI.makeKeyString.and.callFake(function (identifier) {
|
|
||||||
return [identifier.namespace, identifier.key].join(':');
|
|
||||||
});
|
|
||||||
|
|
||||||
mockAPI = {
|
|
||||||
telemetry: mockTelemetryAPI,
|
|
||||||
time: mockTimeAPI,
|
|
||||||
objects: mockObjectsAPI
|
|
||||||
};
|
|
||||||
mockTelemetryAPI.getValueFormatter.and.callFake(function (metadata) {
|
|
||||||
var formatter = jasmine.createSpyObj(
|
|
||||||
'telemetryFormatter:' + metadata.key,
|
|
||||||
[
|
|
||||||
'format',
|
|
||||||
'parse'
|
|
||||||
]
|
|
||||||
);
|
|
||||||
var getter = function (datum) {
|
|
||||||
return datum[metadata.key];
|
|
||||||
};
|
|
||||||
formatter.format.and.callFake(getter);
|
|
||||||
formatter.parse.and.callFake(getter);
|
|
||||||
return formatter;
|
|
||||||
});
|
|
||||||
|
|
||||||
table = new Table(mockTableObject, mockAPI);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Building columns from telemetry metadata", function () {
|
|
||||||
var metadata = [
|
|
||||||
{
|
|
||||||
name: 'Range 1',
|
|
||||||
key: 'range1',
|
|
||||||
source: 'range1',
|
|
||||||
hints: {
|
|
||||||
range: 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Range 2',
|
|
||||||
key: 'range2',
|
|
||||||
source: 'range2',
|
|
||||||
hints: {
|
|
||||||
range: 2
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Domain 1',
|
|
||||||
key: 'domain1',
|
|
||||||
source: 'domain1',
|
|
||||||
format: 'utc',
|
|
||||||
hints: {
|
|
||||||
domain: 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Domain 2',
|
|
||||||
key: 'domain2',
|
|
||||||
source: 'domain2',
|
|
||||||
format: 'utc',
|
|
||||||
hints: {
|
|
||||||
domain: 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
mockTimeAPI.timeSystem.and.returnValue({
|
|
||||||
key: 'domain1'
|
|
||||||
});
|
|
||||||
metadata.forEach(function (metadatum) {
|
|
||||||
table.addColumn(mockTelemetryObject, metadatum);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("populates columns", function () {
|
|
||||||
expect(table.columns.length).toBe(4);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Produces headers for each column based on metadata name", function () {
|
|
||||||
expect(table.headers.size).toBe(4);
|
|
||||||
Array.from(table.headers.values).forEach(function (header, i) {
|
|
||||||
expect(header).toEqual(metadata[i].name);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Provides a default configuration with all columns" +
|
|
||||||
" visible", function () {
|
|
||||||
var configuration = table.buildColumnConfiguration();
|
|
||||||
|
|
||||||
expect(configuration).toBeDefined();
|
|
||||||
expect(Object.keys(configuration).every(function (key) {
|
|
||||||
return configuration[key];
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Column configuration exposes persisted configuration", function () {
|
|
||||||
var tableConfig,
|
|
||||||
modelConfig = {
|
|
||||||
table: {
|
|
||||||
columns : {
|
|
||||||
'Range 1': false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
mockModel.configuration = modelConfig;
|
|
||||||
|
|
||||||
tableConfig = table.buildColumnConfiguration();
|
|
||||||
|
|
||||||
expect(tableConfig).toBeDefined();
|
|
||||||
expect(tableConfig['Range 1']).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('retrieving row values', function () {
|
|
||||||
var datum,
|
|
||||||
rowValues;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
datum = {
|
|
||||||
'range1': 10,
|
|
||||||
'range2': 20,
|
|
||||||
'domain1': 0,
|
|
||||||
'domain2': 1
|
|
||||||
};
|
|
||||||
var limitEvaluator = {
|
|
||||||
evaluate: function () {
|
|
||||||
return {
|
|
||||||
"cssClass": "alarm-class"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
rowValues = table.getRowValues(mockTelemetryObject, limitEvaluator, datum);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Returns a value for every column", function () {
|
|
||||||
expect(rowValues['Range 1'].text).toEqual(10);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Applies appropriate css class if limit violated.", function () {
|
|
||||||
expect(rowValues['Range 1'].cssClass).toEqual("alarm-class");
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
@@ -1,212 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
[
|
|
||||||
"../src/TelemetryCollection"
|
|
||||||
],
|
|
||||||
function (TelemetryCollection) {
|
|
||||||
|
|
||||||
describe("A telemetry collection", function () {
|
|
||||||
|
|
||||||
var collection;
|
|
||||||
var telemetryObjects;
|
|
||||||
var ms;
|
|
||||||
var integerTextMap = ["ZERO", "ONE", "TWO", "THREE", "FOUR", "FIVE",
|
|
||||||
"SIX", "SEVEN", "EIGHT", "NINE", "TEN", "ELEVEN"];
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
telemetryObjects = [0,9,2,4,7,8,5,1,3,6].map(function (number) {
|
|
||||||
ms = number * 1000;
|
|
||||||
return {
|
|
||||||
timestamp: ms,
|
|
||||||
value: {
|
|
||||||
integer: number,
|
|
||||||
text: integerTextMap[number]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
collection = new TelemetryCollection();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Sorts inserted telemetry by specified field",
|
|
||||||
function () {
|
|
||||||
collection.sort('value.integer');
|
|
||||||
collection.add(telemetryObjects);
|
|
||||||
expect(collection.telemetry[0].value.integer).toBe(0);
|
|
||||||
expect(collection.telemetry[1].value.integer).toBe(1);
|
|
||||||
expect(collection.telemetry[2].value.integer).toBe(2);
|
|
||||||
expect(collection.telemetry[3].value.integer).toBe(3);
|
|
||||||
|
|
||||||
collection.sort('value.text');
|
|
||||||
expect(collection.telemetry[0].value.text).toBe("EIGHT");
|
|
||||||
expect(collection.telemetry[1].value.text).toBe("FIVE");
|
|
||||||
expect(collection.telemetry[2].value.text).toBe("FOUR");
|
|
||||||
expect(collection.telemetry[3].value.text).toBe("NINE");
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
describe("on bounds change", function () {
|
|
||||||
var discardedCallback;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
discardedCallback = jasmine.createSpy("discarded");
|
|
||||||
collection.on("discarded", discardedCallback);
|
|
||||||
collection.sort("timestamp");
|
|
||||||
collection.add(telemetryObjects);
|
|
||||||
collection.bounds({start: 5000, end: 8000});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it("emits an event indicating that telemetry has " +
|
|
||||||
"been discarded", function () {
|
|
||||||
expect(discardedCallback).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("discards telemetry data with a time stamp " +
|
|
||||||
"before specified start bound", function () {
|
|
||||||
var discarded = discardedCallback.calls.mostRecent().args[0];
|
|
||||||
|
|
||||||
// Expect 5 because as an optimization, the TelemetryCollection
|
|
||||||
// will not consider telemetry values that exceed the upper
|
|
||||||
// bounds. Arbitrary bounds changes in which the end bound is
|
|
||||||
// decreased is assumed to require a new historical query, and
|
|
||||||
// hence re-population of the collection anyway
|
|
||||||
expect(discarded.length).toBe(5);
|
|
||||||
expect(discarded[0].value.integer).toBe(0);
|
|
||||||
expect(discarded[1].value.integer).toBe(1);
|
|
||||||
expect(discarded[4].value.integer).toBe(4);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when adding telemetry to a collection", function () {
|
|
||||||
var addedCallback;
|
|
||||||
beforeEach(function () {
|
|
||||||
collection.sort("timestamp");
|
|
||||||
collection.add(telemetryObjects);
|
|
||||||
addedCallback = jasmine.createSpy("added");
|
|
||||||
collection.on("added", addedCallback);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("emits an event",
|
|
||||||
function () {
|
|
||||||
var addedObject = {
|
|
||||||
timestamp: 10000,
|
|
||||||
value: {
|
|
||||||
integer: 10,
|
|
||||||
text: integerTextMap[10]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
collection.add([addedObject]);
|
|
||||||
expect(addedCallback).toHaveBeenCalledWith([addedObject]);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
it("inserts in the correct order",
|
|
||||||
function () {
|
|
||||||
var addedObjectA = {
|
|
||||||
timestamp: 10000,
|
|
||||||
value: {
|
|
||||||
integer: 10,
|
|
||||||
text: integerTextMap[10]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
var addedObjectB = {
|
|
||||||
timestamp: 11000,
|
|
||||||
value: {
|
|
||||||
integer: 11,
|
|
||||||
text: integerTextMap[11]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
collection.add([addedObjectB, addedObjectA]);
|
|
||||||
|
|
||||||
expect(collection.telemetry[11]).toBe(addedObjectB);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
it("maintains insertion order in the case of duplicate time stamps",
|
|
||||||
function () {
|
|
||||||
var addedObjectA = {
|
|
||||||
timestamp: 10000,
|
|
||||||
value: {
|
|
||||||
integer: 10,
|
|
||||||
text: integerTextMap[10]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
var addedObjectB = {
|
|
||||||
timestamp: 10000,
|
|
||||||
value: {
|
|
||||||
integer: 11,
|
|
||||||
text: integerTextMap[11]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
collection.add([addedObjectA, addedObjectB]);
|
|
||||||
|
|
||||||
expect(collection.telemetry[11]).toBe(addedObjectB);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("buffers telemetry", function () {
|
|
||||||
var addedObjectA;
|
|
||||||
var addedObjectB;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
collection.sort("timestamp");
|
|
||||||
collection.add(telemetryObjects);
|
|
||||||
|
|
||||||
addedObjectA = {
|
|
||||||
timestamp: 10000,
|
|
||||||
value: {
|
|
||||||
integer: 10,
|
|
||||||
text: integerTextMap[10]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
addedObjectB = {
|
|
||||||
timestamp: 11000,
|
|
||||||
value: {
|
|
||||||
integer: 11,
|
|
||||||
text: integerTextMap[11]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
collection.bounds({start: 0, end: 10000});
|
|
||||||
collection.add([addedObjectA, addedObjectB]);
|
|
||||||
});
|
|
||||||
it("when it falls outside of bounds", function () {
|
|
||||||
expect(collection.highBuffer).toBeDefined();
|
|
||||||
expect(collection.highBuffer.length).toBe(1);
|
|
||||||
expect(collection.highBuffer[0]).toBe(addedObjectB);
|
|
||||||
});
|
|
||||||
it("and adds it to collection when it falls within bounds", function () {
|
|
||||||
expect(collection.telemetry.length).toBe(11);
|
|
||||||
collection.bounds({start: 0, end: 11000});
|
|
||||||
expect(collection.telemetry.length).toBe(12);
|
|
||||||
expect(collection.telemetry[11]).toBe(addedObjectB);
|
|
||||||
});
|
|
||||||
it("and removes it from the buffer when it falls within bounds", function () {
|
|
||||||
expect(collection.highBuffer.length).toBe(1);
|
|
||||||
collection.bounds({start: 0, end: 11000});
|
|
||||||
expect(collection.highBuffer.length).toBe(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
@@ -1,598 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
[
|
|
||||||
"zepto",
|
|
||||||
"moment",
|
|
||||||
"../../src/controllers/MCTTableController"
|
|
||||||
],
|
|
||||||
function ($, moment, MCTTableController) {
|
|
||||||
|
|
||||||
var MOCK_ELEMENT_TEMPLATE =
|
|
||||||
'<div><div class="l-view-section t-scrolling">' +
|
|
||||||
'<table class="sizing-table"><tbody></tbody></table>' +
|
|
||||||
'<table class="mct-table"><thead></thead></table>' +
|
|
||||||
'</div></div>';
|
|
||||||
|
|
||||||
describe('The MCTTable Controller', function () {
|
|
||||||
|
|
||||||
var controller,
|
|
||||||
mockScope,
|
|
||||||
watches,
|
|
||||||
mockWindow,
|
|
||||||
mockElement,
|
|
||||||
mockExportService,
|
|
||||||
mockConductor,
|
|
||||||
mockFormatService,
|
|
||||||
mockFormat;
|
|
||||||
|
|
||||||
function getCallback(target, event) {
|
|
||||||
return target.calls.all().filter(function (call) {
|
|
||||||
return call.args[0] === event;
|
|
||||||
})[0].args[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
watches = {};
|
|
||||||
|
|
||||||
mockScope = jasmine.createSpyObj('scope', [
|
|
||||||
'$watch',
|
|
||||||
'$on',
|
|
||||||
'$watchCollection',
|
|
||||||
'$digest'
|
|
||||||
]);
|
|
||||||
mockScope.$watchCollection.and.callFake(function (event, callback) {
|
|
||||||
watches[event] = callback;
|
|
||||||
});
|
|
||||||
|
|
||||||
mockElement = $(MOCK_ELEMENT_TEMPLATE);
|
|
||||||
mockExportService = jasmine.createSpyObj('exportService', [
|
|
||||||
'exportCSV'
|
|
||||||
]);
|
|
||||||
|
|
||||||
mockConductor = jasmine.createSpyObj('conductor', [
|
|
||||||
'bounds',
|
|
||||||
'timeOfInterest',
|
|
||||||
'timeSystem',
|
|
||||||
'on',
|
|
||||||
'off'
|
|
||||||
]);
|
|
||||||
|
|
||||||
mockScope.displayHeaders = true;
|
|
||||||
mockWindow = jasmine.createSpyObj('$window', ['requestAnimationFrame']);
|
|
||||||
mockWindow.requestAnimationFrame.and.callFake(function (f) {
|
|
||||||
return f();
|
|
||||||
});
|
|
||||||
|
|
||||||
mockFormat = jasmine.createSpyObj('formatter', [
|
|
||||||
'parse',
|
|
||||||
'format'
|
|
||||||
]);
|
|
||||||
mockFormatService = jasmine.createSpyObj('formatService', [
|
|
||||||
'getFormat'
|
|
||||||
]);
|
|
||||||
mockFormatService.getFormat.and.returnValue(mockFormat);
|
|
||||||
|
|
||||||
controller = new MCTTableController(
|
|
||||||
mockScope,
|
|
||||||
mockWindow,
|
|
||||||
mockElement,
|
|
||||||
mockExportService,
|
|
||||||
mockFormatService,
|
|
||||||
{time: mockConductor}
|
|
||||||
);
|
|
||||||
spyOn(controller, 'setVisibleRows').and.callThrough();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Reacts to changes to filters, headers, and rows', function () {
|
|
||||||
expect(mockScope.$watchCollection).toHaveBeenCalledWith('filters', jasmine.any(Function));
|
|
||||||
expect(mockScope.$watch).toHaveBeenCalledWith('headers', jasmine.any(Function));
|
|
||||||
expect(mockScope.$watch).toHaveBeenCalledWith('rows', jasmine.any(Function));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('unregisters listeners on destruction', function () {
|
|
||||||
expect(mockScope.$on).toHaveBeenCalledWith('$destroy', jasmine.any(Function));
|
|
||||||
getCallback(mockScope.$on, '$destroy')();
|
|
||||||
|
|
||||||
expect(mockConductor.off).toHaveBeenCalledWith('timeSystem', controller.changeTimeSystem);
|
|
||||||
expect(mockConductor.off).toHaveBeenCalledWith('timeOfInterest', controller.changeTimeOfInterest);
|
|
||||||
expect(mockConductor.off).toHaveBeenCalledWith('bounds', controller.changeBounds);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('The time of interest', function () {
|
|
||||||
var rowsAsc = [];
|
|
||||||
var rowsDesc = [];
|
|
||||||
beforeEach(function () {
|
|
||||||
rowsAsc = [
|
|
||||||
{
|
|
||||||
'col1': {'text': 'row1 col1 match'},
|
|
||||||
'col2': {'text': '2012-10-31 00:00:00.000Z'},
|
|
||||||
'col3': {'text': 'row1 col3'}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'col1': {'text': 'row2 col1 match'},
|
|
||||||
'col2': {'text': '2012-11-01 00:00:00.000Z'},
|
|
||||||
'col3': {'text': 'row2 col3'}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'col1': {'text': 'row3 col1'},
|
|
||||||
'col2': {'text': '2012-11-03 00:00:00.000Z'},
|
|
||||||
'col3': {'text': 'row3 col3'}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'col1': {'text': 'row3 col1'},
|
|
||||||
'col2': {'text': '2012-11-04 00:00:00.000Z'},
|
|
||||||
'col3': {'text': 'row3 col3'}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
rowsDesc = [
|
|
||||||
{
|
|
||||||
'col1': {'text': 'row1 col1 match'},
|
|
||||||
'col2': {'text': '2012-11-02 00:00:00.000Z'},
|
|
||||||
'col3': {'text': 'row1 col3'}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'col1': {'text': 'row2 col1 match'},
|
|
||||||
'col2': {'text': '2012-11-01 00:00:00.000Z'},
|
|
||||||
'col3': {'text': 'row2 col3'}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'col1': {'text': 'row3 col1'},
|
|
||||||
'col2': {'text': '2012-10-30 00:00:00.000Z'},
|
|
||||||
'col3': {'text': 'row3 col3'}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'col1': {'text': 'row3 col1'},
|
|
||||||
'col2': {'text': '2012-10-29 00:00:00.000Z'},
|
|
||||||
'col3': {'text': 'row3 col3'}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
mockScope.timeColumns = ['col2'];
|
|
||||||
mockScope.sortColumn = 'col2';
|
|
||||||
controller.toiFormatter = mockFormat;
|
|
||||||
});
|
|
||||||
it("is observed for changes", function () {
|
|
||||||
//Mock setting time columns
|
|
||||||
getCallback(mockScope.$watch, 'timeColumns')(['col2']);
|
|
||||||
|
|
||||||
expect(mockConductor.on).toHaveBeenCalledWith('timeOfInterest',
|
|
||||||
jasmine.any(Function));
|
|
||||||
});
|
|
||||||
describe("causes corresponding row to be highlighted", function () {
|
|
||||||
it("when changed and rows sorted ascending", function () {
|
|
||||||
var testDate = "2012-11-02 00:00:00.000Z";
|
|
||||||
mockScope.rows = rowsAsc;
|
|
||||||
mockScope.displayRows = rowsAsc;
|
|
||||||
mockScope.sortDirection = 'asc';
|
|
||||||
|
|
||||||
var toi = moment.utc(testDate).valueOf();
|
|
||||||
mockFormat.parse.and.returnValue(toi);
|
|
||||||
mockFormat.format.and.returnValue(testDate);
|
|
||||||
|
|
||||||
//mock setting the timeColumns parameter
|
|
||||||
getCallback(mockScope.$watch, 'timeColumns')(['col2']);
|
|
||||||
|
|
||||||
var toiCallback = getCallback(mockConductor.on, 'timeOfInterest');
|
|
||||||
toiCallback(toi);
|
|
||||||
|
|
||||||
expect(mockScope.toiRowIndex).toBe(2);
|
|
||||||
});
|
|
||||||
it("when changed and rows sorted descending", function () {
|
|
||||||
var testDate = "2012-10-31 00:00:00.000Z";
|
|
||||||
mockScope.rows = rowsDesc;
|
|
||||||
mockScope.displayRows = rowsDesc;
|
|
||||||
mockScope.sortDirection = 'desc';
|
|
||||||
|
|
||||||
var toi = moment.utc(testDate).valueOf();
|
|
||||||
mockFormat.parse.and.returnValue(toi);
|
|
||||||
mockFormat.format.and.returnValue(testDate);
|
|
||||||
|
|
||||||
//mock setting the timeColumns parameter
|
|
||||||
getCallback(mockScope.$watch, 'timeColumns')(['col2']);
|
|
||||||
|
|
||||||
var toiCallback = getCallback(mockConductor.on, 'timeOfInterest');
|
|
||||||
toiCallback(toi);
|
|
||||||
|
|
||||||
expect(mockScope.toiRowIndex).toBe(2);
|
|
||||||
});
|
|
||||||
it("when rows are set and sorted ascending", function () {
|
|
||||||
var testDate = "2012-11-02 00:00:00.000Z";
|
|
||||||
mockScope.sortDirection = 'asc';
|
|
||||||
|
|
||||||
var toi = moment.utc(testDate).valueOf();
|
|
||||||
mockFormat.parse.and.returnValue(toi);
|
|
||||||
mockFormat.format.and.returnValue(testDate);
|
|
||||||
mockConductor.timeOfInterest.and.returnValue(toi);
|
|
||||||
|
|
||||||
//mock setting the timeColumns parameter
|
|
||||||
getCallback(mockScope.$watch, 'timeColumns')(['col2']);
|
|
||||||
|
|
||||||
//Mock setting the rows on scope
|
|
||||||
var rowsCallback = getCallback(mockScope.$watch, 'rows');
|
|
||||||
var setRowsPromise = rowsCallback(rowsAsc);
|
|
||||||
|
|
||||||
return setRowsPromise.then(function () {
|
|
||||||
expect(mockScope.toiRowIndex).toBe(2);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('rows', function () {
|
|
||||||
var testRows = [];
|
|
||||||
beforeEach(function () {
|
|
||||||
testRows = [
|
|
||||||
{
|
|
||||||
'col1': {'text': 'row1 col1 match'},
|
|
||||||
'col2': {'text': 'def'},
|
|
||||||
'col3': {'text': 'row1 col3'}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'col1': {'text': 'row2 col1 match'},
|
|
||||||
'col2': {'text': 'abc'},
|
|
||||||
'col3': {'text': 'row2 col3'}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'col1': {'text': 'row3 col1'},
|
|
||||||
'col2': {'text': 'ghi'},
|
|
||||||
'col3': {'text': 'row3 col3'}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
mockScope.rows = testRows;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Filters results based on filter input', function () {
|
|
||||||
var filters = {},
|
|
||||||
filteredRows;
|
|
||||||
|
|
||||||
mockScope.filters = filters;
|
|
||||||
|
|
||||||
filteredRows = controller.filterRows(testRows);
|
|
||||||
expect(filteredRows.length).toBe(3);
|
|
||||||
filters.col1 = 'row1';
|
|
||||||
filteredRows = controller.filterRows(testRows);
|
|
||||||
expect(filteredRows.length).toBe(1);
|
|
||||||
filters.col1 = 'match';
|
|
||||||
filteredRows = controller.filterRows(testRows);
|
|
||||||
expect(filteredRows.length).toBe(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Sets rows on scope when rows change', function () {
|
|
||||||
controller.setRows(testRows);
|
|
||||||
expect(mockScope.displayRows.length).toBe(3);
|
|
||||||
expect(mockScope.displayRows).toEqual(testRows);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Supports adding rows individually', function () {
|
|
||||||
var addRowFunc = getCallback(mockScope.$on, 'add:rows'),
|
|
||||||
row4 = {
|
|
||||||
'col1': {'text': 'row3 col1'},
|
|
||||||
'col2': {'text': 'ghi'},
|
|
||||||
'col3': {'text': 'row3 col3'}
|
|
||||||
};
|
|
||||||
controller.setRows(testRows);
|
|
||||||
expect(mockScope.displayRows.length).toBe(3);
|
|
||||||
testRows.push(row4);
|
|
||||||
addRowFunc(undefined, [row4]);
|
|
||||||
expect(mockScope.displayRows.length).toBe(4);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Supports removing rows individually', function () {
|
|
||||||
var removeRowFunc = getCallback(mockScope.$on, 'remove:rows');
|
|
||||||
controller.setRows(testRows);
|
|
||||||
expect(mockScope.displayRows.length).toBe(3);
|
|
||||||
removeRowFunc(undefined, [testRows[2]]);
|
|
||||||
expect(mockScope.displayRows.length).toBe(2);
|
|
||||||
expect(controller.setVisibleRows).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("can be exported as CSV", function () {
|
|
||||||
controller.setRows(testRows);
|
|
||||||
controller.setHeaders(Object.keys(testRows[0]));
|
|
||||||
mockScope.exportAsCSV();
|
|
||||||
expect(mockExportService.exportCSV)
|
|
||||||
.toHaveBeenCalled();
|
|
||||||
mockExportService.exportCSV.calls.mostRecent().args[0]
|
|
||||||
.forEach(function (row, i) {
|
|
||||||
Object.keys(row).forEach(function (k) {
|
|
||||||
expect(row[k]).toEqual(
|
|
||||||
mockScope.displayRows[i][k].text
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('sorting', function () {
|
|
||||||
var sortedRows;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
sortedRows = [];
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Sorts rows ascending', function () {
|
|
||||||
mockScope.sortColumn = 'col1';
|
|
||||||
mockScope.sortDirection = 'asc';
|
|
||||||
|
|
||||||
sortedRows = controller.sortRows(testRows);
|
|
||||||
expect(sortedRows[0].col1.text).toEqual('row1 col1 match');
|
|
||||||
expect(sortedRows[1].col1.text).toEqual('row2 col1' +
|
|
||||||
' match');
|
|
||||||
expect(sortedRows[2].col1.text).toEqual('row3 col1');
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Sorts rows descending', function () {
|
|
||||||
mockScope.sortColumn = 'col1';
|
|
||||||
mockScope.sortDirection = 'desc';
|
|
||||||
|
|
||||||
sortedRows = controller.sortRows(testRows);
|
|
||||||
expect(sortedRows[0].col1.text).toEqual('row3 col1');
|
|
||||||
expect(sortedRows[1].col1.text).toEqual('row2 col1 match');
|
|
||||||
expect(sortedRows[2].col1.text).toEqual('row1 col1 match');
|
|
||||||
});
|
|
||||||
it('Sorts rows descending based on selected sort column', function () {
|
|
||||||
mockScope.sortColumn = 'col2';
|
|
||||||
mockScope.sortDirection = 'desc';
|
|
||||||
|
|
||||||
sortedRows = controller.sortRows(testRows);
|
|
||||||
expect(sortedRows[0].col2.text).toEqual('ghi');
|
|
||||||
expect(sortedRows[1].col2.text).toEqual('def');
|
|
||||||
expect(sortedRows[2].col2.text).toEqual('abc');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Allows sort column to be changed externally by ' +
|
|
||||||
'setting or changing sortBy attribute', function () {
|
|
||||||
mockScope.displayRows = testRows;
|
|
||||||
var sortByCB = getCallback(mockScope.$watch, 'defaultSort');
|
|
||||||
sortByCB('col2');
|
|
||||||
|
|
||||||
expect(mockScope.sortDirection).toEqual('asc');
|
|
||||||
|
|
||||||
expect(mockScope.displayRows[0].col2.text).toEqual('abc');
|
|
||||||
expect(mockScope.displayRows[1].col2.text).toEqual('def');
|
|
||||||
expect(mockScope.displayRows[2].col2.text).toEqual('ghi');
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
// https://github.com/nasa/openmct/issues/910
|
|
||||||
it('updates visible rows in scope', function () {
|
|
||||||
var oldRows;
|
|
||||||
mockScope.rows = testRows;
|
|
||||||
var setRowsPromise = controller.setRows(testRows);
|
|
||||||
|
|
||||||
oldRows = mockScope.visibleRows;
|
|
||||||
mockScope.toggleSort('col2');
|
|
||||||
|
|
||||||
return setRowsPromise.then(function () {
|
|
||||||
expect(mockScope.visibleRows).not.toEqual(oldRows);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('correctly sorts rows of differing types', function () {
|
|
||||||
mockScope.sortColumn = 'col2';
|
|
||||||
mockScope.sortDirection = 'desc';
|
|
||||||
|
|
||||||
testRows.push({
|
|
||||||
'col1': {'text': 'row4 col1'},
|
|
||||||
'col2': {'text': '123'},
|
|
||||||
'col3': {'text': 'row4 col3'}
|
|
||||||
});
|
|
||||||
testRows.push({
|
|
||||||
'col1': {'text': 'row5 col1'},
|
|
||||||
'col2': {'text': '456'},
|
|
||||||
'col3': {'text': 'row5 col3'}
|
|
||||||
});
|
|
||||||
testRows.push({
|
|
||||||
'col1': {'text': 'row5 col1'},
|
|
||||||
'col2': {'text': ''},
|
|
||||||
'col3': {'text': 'row5 col3'}
|
|
||||||
});
|
|
||||||
|
|
||||||
sortedRows = controller.sortRows(testRows);
|
|
||||||
expect(sortedRows[0].col2.text).toEqual('ghi');
|
|
||||||
expect(sortedRows[1].col2.text).toEqual('def');
|
|
||||||
expect(sortedRows[2].col2.text).toEqual('abc');
|
|
||||||
|
|
||||||
expect(sortedRows[sortedRows.length - 3].col2.text).toEqual('456');
|
|
||||||
expect(sortedRows[sortedRows.length - 2].col2.text).toEqual('123');
|
|
||||||
expect(sortedRows[sortedRows.length - 1].col2.text).toEqual('');
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('The sort comparator', function () {
|
|
||||||
it('Correctly sorts different data types', function () {
|
|
||||||
var val1 = "",
|
|
||||||
val2 = "1",
|
|
||||||
val3 = "2016-04-05 18:41:30.713Z",
|
|
||||||
val4 = "1.1",
|
|
||||||
val5 = "8.945520958175627e-13";
|
|
||||||
mockScope.sortDirection = "asc";
|
|
||||||
|
|
||||||
expect(controller.sortComparator(val1, val2)).toEqual(-1);
|
|
||||||
expect(controller.sortComparator(val3, val1)).toEqual(1);
|
|
||||||
expect(controller.sortComparator(val3, val2)).toEqual(1);
|
|
||||||
expect(controller.sortComparator(val4, val2)).toEqual(1);
|
|
||||||
expect(controller.sortComparator(val2, val5)).toEqual(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Adding new rows', function () {
|
|
||||||
var row4,
|
|
||||||
row5,
|
|
||||||
row6;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
row4 = {
|
|
||||||
'col1': {'text': 'row4 col1'},
|
|
||||||
'col2': {'text': 'xyz'},
|
|
||||||
'col3': {'text': 'row4 col3'}
|
|
||||||
};
|
|
||||||
row5 = {
|
|
||||||
'col1': {'text': 'row5 col1'},
|
|
||||||
'col2': {'text': 'aaa'},
|
|
||||||
'col3': {'text': 'row5 col3'}
|
|
||||||
};
|
|
||||||
row6 = {
|
|
||||||
'col1': {'text': 'row6 col1'},
|
|
||||||
'col2': {'text': 'ggg'},
|
|
||||||
'col3': {'text': 'row6 col3'}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Adds new rows at the correct sort position when' +
|
|
||||||
' sorted ', function () {
|
|
||||||
mockScope.sortColumn = 'col2';
|
|
||||||
mockScope.sortDirection = 'desc';
|
|
||||||
|
|
||||||
mockScope.displayRows = controller.sortRows(testRows.slice(0));
|
|
||||||
|
|
||||||
controller.addRows(undefined, [row4, row5, row6, row6]);
|
|
||||||
expect(mockScope.displayRows[0].col2.text).toEqual('xyz');
|
|
||||||
expect(mockScope.displayRows[6].col2.text).toEqual('aaa');
|
|
||||||
//Added a duplicate row
|
|
||||||
expect(mockScope.displayRows[2].col2.text).toEqual('ggg');
|
|
||||||
expect(mockScope.displayRows[3].col2.text).toEqual('ggg');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Inserts duplicate values for sort column in order received when sorted descending', function () {
|
|
||||||
mockScope.sortColumn = 'col2';
|
|
||||||
mockScope.sortDirection = 'desc';
|
|
||||||
|
|
||||||
mockScope.displayRows = controller.sortRows(testRows.slice(0));
|
|
||||||
|
|
||||||
var row6b = {
|
|
||||||
'col1': {'text': 'row6b col1'},
|
|
||||||
'col2': {'text': 'ggg'},
|
|
||||||
'col3': {'text': 'row6b col3'}
|
|
||||||
};
|
|
||||||
var row6c = {
|
|
||||||
'col1': {'text': 'row6c col1'},
|
|
||||||
'col2': {'text': 'ggg'},
|
|
||||||
'col3': {'text': 'row6c col3'}
|
|
||||||
};
|
|
||||||
|
|
||||||
controller.addRows(undefined, [row4, row5]);
|
|
||||||
controller.addRows(undefined, [row6, row6b, row6c]);
|
|
||||||
expect(mockScope.displayRows[0].col2.text).toEqual('xyz');
|
|
||||||
expect(mockScope.displayRows[7].col2.text).toEqual('aaa');
|
|
||||||
|
|
||||||
// Added duplicate rows
|
|
||||||
expect(mockScope.displayRows[2].col2.text).toEqual('ggg');
|
|
||||||
expect(mockScope.displayRows[3].col2.text).toEqual('ggg');
|
|
||||||
expect(mockScope.displayRows[4].col2.text).toEqual('ggg');
|
|
||||||
|
|
||||||
// Check that original order is maintained with dupes
|
|
||||||
expect(mockScope.displayRows[2].col3.text).toEqual('row6c col3');
|
|
||||||
expect(mockScope.displayRows[3].col3.text).toEqual('row6b col3');
|
|
||||||
expect(mockScope.displayRows[4].col3.text).toEqual('row6 col3');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Inserts duplicate values for sort column in order received when sorted ascending', function () {
|
|
||||||
mockScope.sortColumn = 'col2';
|
|
||||||
mockScope.sortDirection = 'asc';
|
|
||||||
|
|
||||||
mockScope.displayRows = controller.sortRows(testRows.slice(0));
|
|
||||||
|
|
||||||
var row6b = {
|
|
||||||
'col1': {'text': 'row6b col1'},
|
|
||||||
'col2': {'text': 'ggg'},
|
|
||||||
'col3': {'text': 'row6b col3'}
|
|
||||||
};
|
|
||||||
var row6c = {
|
|
||||||
'col1': {'text': 'row6c col1'},
|
|
||||||
'col2': {'text': 'ggg'},
|
|
||||||
'col3': {'text': 'row6c col3'}
|
|
||||||
};
|
|
||||||
|
|
||||||
controller.addRows(undefined, [row4, row5, row6]);
|
|
||||||
controller.addRows(undefined, [row6b, row6c]);
|
|
||||||
expect(mockScope.displayRows[0].col2.text).toEqual('aaa');
|
|
||||||
expect(mockScope.displayRows[7].col2.text).toEqual('xyz');
|
|
||||||
|
|
||||||
// Added duplicate rows
|
|
||||||
expect(mockScope.displayRows[3].col2.text).toEqual('ggg');
|
|
||||||
expect(mockScope.displayRows[4].col2.text).toEqual('ggg');
|
|
||||||
expect(mockScope.displayRows[5].col2.text).toEqual('ggg');
|
|
||||||
// Check that original order is maintained with dupes
|
|
||||||
expect(mockScope.displayRows[3].col3.text).toEqual('row6 col3');
|
|
||||||
expect(mockScope.displayRows[4].col3.text).toEqual('row6b col3');
|
|
||||||
expect(mockScope.displayRows[5].col3.text).toEqual('row6c col3');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Adds new rows at the correct sort position when' +
|
|
||||||
' sorted and filtered', function () {
|
|
||||||
mockScope.sortColumn = 'col2';
|
|
||||||
mockScope.sortDirection = 'desc';
|
|
||||||
mockScope.filters = {'col2': 'a'};//Include only
|
|
||||||
// rows with 'a'
|
|
||||||
|
|
||||||
mockScope.displayRows = controller.sortRows(testRows.slice(0));
|
|
||||||
mockScope.displayRows = controller.filterRows(testRows);
|
|
||||||
|
|
||||||
controller.addRows(undefined, [row5]);
|
|
||||||
expect(mockScope.displayRows.length).toBe(2);
|
|
||||||
expect(mockScope.displayRows[1].col2.text).toEqual('aaa');
|
|
||||||
|
|
||||||
controller.addRows(undefined, [row6]);
|
|
||||||
expect(mockScope.displayRows.length).toBe(2);
|
|
||||||
//Row was not added because does not match filter
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Adds new rows at the correct sort position when' +
|
|
||||||
' not sorted ', function () {
|
|
||||||
mockScope.sortColumn = undefined;
|
|
||||||
mockScope.sortDirection = undefined;
|
|
||||||
mockScope.filters = {};
|
|
||||||
|
|
||||||
mockScope.displayRows = testRows.slice(0);
|
|
||||||
|
|
||||||
controller.addRows(undefined, [row5]);
|
|
||||||
expect(mockScope.displayRows[3].col2.text).toEqual('aaa');
|
|
||||||
|
|
||||||
controller.addRows(undefined, [row6]);
|
|
||||||
expect(mockScope.displayRows[4].col2.text).toEqual('ggg');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Resizes columns if length of any columns in new' +
|
|
||||||
' row exceeds corresponding existing column', function () {
|
|
||||||
var row7 = {
|
|
||||||
'col1': {'text': 'row6 col1'},
|
|
||||||
'col2': {'text': 'some longer string'},
|
|
||||||
'col3': {'text': 'row6 col3'}
|
|
||||||
};
|
|
||||||
|
|
||||||
mockScope.sortColumn = undefined;
|
|
||||||
mockScope.sortDirection = undefined;
|
|
||||||
mockScope.filters = {};
|
|
||||||
|
|
||||||
mockScope.displayRows = testRows.slice(0);
|
|
||||||
|
|
||||||
controller.addRows(undefined, [row7]);
|
|
||||||
expect(controller.$scope.sizingRow.col2).toEqual({text: 'some longer string'});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
[
|
|
||||||
"../../src/controllers/TableOptionsController"
|
|
||||||
],
|
|
||||||
function (TableOptionsController) {
|
|
||||||
|
|
||||||
describe('The Table Options Controller', function () {
|
|
||||||
var mockDomainObject,
|
|
||||||
mockCapability,
|
|
||||||
controller,
|
|
||||||
mockScope;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
mockCapability = jasmine.createSpyObj('mutationCapability', [
|
|
||||||
'listen'
|
|
||||||
]);
|
|
||||||
mockDomainObject = jasmine.createSpyObj('domainObject', [
|
|
||||||
'getCapability',
|
|
||||||
'getModel'
|
|
||||||
]);
|
|
||||||
mockDomainObject.getCapability.and.returnValue(mockCapability);
|
|
||||||
mockDomainObject.getModel.and.returnValue({});
|
|
||||||
|
|
||||||
mockScope = jasmine.createSpyObj('scope', [
|
|
||||||
'$watchCollection',
|
|
||||||
'$watch',
|
|
||||||
'$on'
|
|
||||||
]);
|
|
||||||
mockScope.domainObject = mockDomainObject;
|
|
||||||
|
|
||||||
controller = new TableOptionsController(mockScope);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Listens for changing domain object', function () {
|
|
||||||
expect(mockScope.$watch).toHaveBeenCalledWith('domainObject', jasmine.any(Function));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('On destruction of controller, destroys listeners', function () {
|
|
||||||
var unlistenFunc = jasmine.createSpy("unlisten");
|
|
||||||
controller.listeners.push(unlistenFunc);
|
|
||||||
expect(mockScope.$on).toHaveBeenCalledWith('$destroy', jasmine.any(Function));
|
|
||||||
mockScope.$on.calls.mostRecent().args[1]();
|
|
||||||
expect(unlistenFunc).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Registers a listener for mutation events on the object', function () {
|
|
||||||
mockScope.$watch.calls.mostRecent().args[1](mockDomainObject);
|
|
||||||
expect(mockCapability.listen).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Listens for changes to object composition and updates' +
|
|
||||||
' options accordingly', function () {
|
|
||||||
expect(mockScope.$watchCollection).toHaveBeenCalledWith('configuration.table.columns', jasmine.any(Function));
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Populates scope with a form definition based on provided' +
|
|
||||||
' column configuration', function () {
|
|
||||||
var mockModel;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
mockModel = {
|
|
||||||
configuration: {
|
|
||||||
table: {
|
|
||||||
columns: {
|
|
||||||
'column1': true,
|
|
||||||
'column2': true,
|
|
||||||
'column3': false,
|
|
||||||
'column4': true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
controller.populateForm(mockModel);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('creates form on scope', function () {
|
|
||||||
expect(mockScope.columnsForm).toBeDefined();
|
|
||||||
expect(mockScope.columnsForm.sections[0]).toBeDefined();
|
|
||||||
expect(mockScope.columnsForm.sections[0].rows).toBeDefined();
|
|
||||||
expect(mockScope.columnsForm.sections[0].rows.length).toBe(4);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('presents columns as checkboxes', function () {
|
|
||||||
expect(mockScope.columnsForm.sections[0].rows.every(function (row) {
|
|
||||||
return row.control === 'checkbox';
|
|
||||||
})).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
@@ -1,417 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
[
|
|
||||||
'../../src/controllers/TelemetryTableController',
|
|
||||||
'../../../../../src/api/objects/object-utils',
|
|
||||||
'lodash'
|
|
||||||
],
|
|
||||||
function (TelemetryTableController, objectUtils, _) {
|
|
||||||
|
|
||||||
describe('The TelemetryTableController', function () {
|
|
||||||
|
|
||||||
var controller,
|
|
||||||
mockScope,
|
|
||||||
mockTimeout,
|
|
||||||
mockConductor,
|
|
||||||
mockAPI,
|
|
||||||
mockDomainObject,
|
|
||||||
mockTelemetryAPI,
|
|
||||||
mockObjectAPI,
|
|
||||||
mockCompositionAPI,
|
|
||||||
unobserve,
|
|
||||||
mockBounds;
|
|
||||||
|
|
||||||
function getCallback(target, event) {
|
|
||||||
return target.calls.all().filter(function (call) {
|
|
||||||
return call.args[0] === event;
|
|
||||||
})[0].args[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
mockBounds = {
|
|
||||||
start: 0,
|
|
||||||
end: 10
|
|
||||||
};
|
|
||||||
mockConductor = jasmine.createSpyObj("conductor", [
|
|
||||||
"bounds",
|
|
||||||
"clock",
|
|
||||||
"on",
|
|
||||||
"off",
|
|
||||||
"timeSystem"
|
|
||||||
]);
|
|
||||||
mockConductor.bounds.and.returnValue(mockBounds);
|
|
||||||
mockConductor.clock.and.returnValue(undefined);
|
|
||||||
|
|
||||||
mockDomainObject = jasmine.createSpyObj("domainObject", [
|
|
||||||
"getModel",
|
|
||||||
"getId",
|
|
||||||
"useCapability",
|
|
||||||
"hasCapability"
|
|
||||||
]);
|
|
||||||
mockDomainObject.getModel.and.returnValue({});
|
|
||||||
mockDomainObject.getId.and.returnValue("mockId");
|
|
||||||
mockDomainObject.useCapability.and.returnValue(true);
|
|
||||||
|
|
||||||
mockCompositionAPI = jasmine.createSpyObj("compositionAPI", [
|
|
||||||
"get"
|
|
||||||
]);
|
|
||||||
|
|
||||||
mockObjectAPI = jasmine.createSpyObj("objectAPI", [
|
|
||||||
"observe",
|
|
||||||
"makeKeyString"
|
|
||||||
]);
|
|
||||||
unobserve = jasmine.createSpy("unobserve");
|
|
||||||
mockObjectAPI.observe.and.returnValue(unobserve);
|
|
||||||
|
|
||||||
mockScope = jasmine.createSpyObj("scope", [
|
|
||||||
"$on",
|
|
||||||
"$watch",
|
|
||||||
"$broadcast"
|
|
||||||
]);
|
|
||||||
mockScope.domainObject = mockDomainObject;
|
|
||||||
|
|
||||||
mockTelemetryAPI = jasmine.createSpyObj("telemetryAPI", [
|
|
||||||
"isTelemetryObject",
|
|
||||||
"subscribe",
|
|
||||||
"getMetadata",
|
|
||||||
"commonValuesForHints",
|
|
||||||
"request",
|
|
||||||
"limitEvaluator",
|
|
||||||
"getValueFormatter"
|
|
||||||
]);
|
|
||||||
mockTelemetryAPI.commonValuesForHints.and.returnValue([]);
|
|
||||||
mockTelemetryAPI.request.and.returnValue(Promise.resolve([]));
|
|
||||||
mockTelemetryAPI.getMetadata.and.returnValue({
|
|
||||||
values: function () {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
mockTelemetryAPI.getValueFormatter.and.callFake(function (metadata) {
|
|
||||||
var formatter = jasmine.createSpyObj(
|
|
||||||
'telemetryFormatter:' + metadata.key,
|
|
||||||
[
|
|
||||||
'format',
|
|
||||||
'parse'
|
|
||||||
]
|
|
||||||
);
|
|
||||||
var getter = function (datum) {
|
|
||||||
return datum[metadata.key];
|
|
||||||
};
|
|
||||||
formatter.format.and.callFake(getter);
|
|
||||||
formatter.parse.and.callFake(getter);
|
|
||||||
return formatter;
|
|
||||||
});
|
|
||||||
|
|
||||||
mockTelemetryAPI.isTelemetryObject.and.returnValue(false);
|
|
||||||
|
|
||||||
mockTimeout = jasmine.createSpy("timeout");
|
|
||||||
mockTimeout.and.returnValue(1); // Return something
|
|
||||||
mockTimeout.cancel = jasmine.createSpy("cancel");
|
|
||||||
|
|
||||||
mockAPI = {
|
|
||||||
time: mockConductor,
|
|
||||||
objects: mockObjectAPI,
|
|
||||||
telemetry: mockTelemetryAPI,
|
|
||||||
composition: mockCompositionAPI
|
|
||||||
};
|
|
||||||
controller = new TelemetryTableController(mockScope, mockTimeout, mockAPI);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('listens for', function () {
|
|
||||||
beforeEach(function () {
|
|
||||||
controller.registerChangeListeners();
|
|
||||||
});
|
|
||||||
it('object mutation', function () {
|
|
||||||
var calledObject = mockObjectAPI.observe.calls.mostRecent().args[0];
|
|
||||||
|
|
||||||
expect(mockObjectAPI.observe).toHaveBeenCalled();
|
|
||||||
expect(calledObject.identifier.key).toEqual(mockDomainObject.getId());
|
|
||||||
});
|
|
||||||
it('conductor changes', function () {
|
|
||||||
expect(mockConductor.on).toHaveBeenCalledWith("timeSystem", jasmine.any(Function));
|
|
||||||
expect(mockConductor.on).toHaveBeenCalledWith("bounds", jasmine.any(Function));
|
|
||||||
expect(mockConductor.on).toHaveBeenCalledWith("clock", jasmine.any(Function));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('deregisters all listeners on scope destruction', function () {
|
|
||||||
var timeSystemListener,
|
|
||||||
boundsListener,
|
|
||||||
clockListener;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
controller.registerChangeListeners();
|
|
||||||
|
|
||||||
timeSystemListener = getCallback(mockConductor.on, "timeSystem");
|
|
||||||
boundsListener = getCallback(mockConductor.on, "bounds");
|
|
||||||
clockListener = getCallback(mockConductor.on, "clock");
|
|
||||||
|
|
||||||
var destroy = getCallback(mockScope.$on, "$destroy");
|
|
||||||
destroy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('object mutation', function () {
|
|
||||||
expect(unobserve).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
it('conductor changes', function () {
|
|
||||||
expect(mockConductor.off).toHaveBeenCalledWith("timeSystem", timeSystemListener);
|
|
||||||
expect(mockConductor.off).toHaveBeenCalledWith("bounds", boundsListener);
|
|
||||||
expect(mockConductor.off).toHaveBeenCalledWith("clock", clockListener);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe ('when getting telemetry', function () {
|
|
||||||
var mockComposition,
|
|
||||||
mockTelemetryObject,
|
|
||||||
mockChildren,
|
|
||||||
unsubscribe;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
mockComposition = jasmine.createSpyObj("composition", [
|
|
||||||
"load"
|
|
||||||
]);
|
|
||||||
|
|
||||||
mockTelemetryObject = {};
|
|
||||||
mockTelemetryObject.identifier = {
|
|
||||||
key: "mockTelemetryObject"
|
|
||||||
};
|
|
||||||
|
|
||||||
unsubscribe = jasmine.createSpy("unsubscribe");
|
|
||||||
mockTelemetryAPI.subscribe.and.returnValue(unsubscribe);
|
|
||||||
|
|
||||||
mockChildren = [mockTelemetryObject];
|
|
||||||
mockComposition.load.and.returnValue(Promise.resolve(mockChildren));
|
|
||||||
mockCompositionAPI.get.and.returnValue(mockComposition);
|
|
||||||
|
|
||||||
mockTelemetryAPI.isTelemetryObject.and.callFake(function (obj) {
|
|
||||||
return obj.identifier.key === mockTelemetryObject.identifier.key;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('fetches historical data for the time period specified by the conductor bounds', function () {
|
|
||||||
return controller.getData().then(function () {
|
|
||||||
expect(mockTelemetryAPI.request).toHaveBeenCalledWith(mockTelemetryObject, mockBounds);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('unsubscribes on view destruction', function () {
|
|
||||||
return controller.getData().then(function () {
|
|
||||||
var destroy = getCallback(mockScope.$on, "$destroy");
|
|
||||||
destroy();
|
|
||||||
|
|
||||||
expect(unsubscribe).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it('fetches historical data for the time period specified by the conductor bounds', function () {
|
|
||||||
return controller.getData().then(function () {
|
|
||||||
expect(mockTelemetryAPI.request).toHaveBeenCalledWith(mockTelemetryObject, mockBounds);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('fetches data for, and subscribes to parent object if it is a telemetry object', function () {
|
|
||||||
return controller.getData().then(function () {
|
|
||||||
expect(mockTelemetryAPI.subscribe).toHaveBeenCalledWith(mockTelemetryObject, jasmine.any(Function), {});
|
|
||||||
expect(mockTelemetryAPI.request).toHaveBeenCalledWith(mockTelemetryObject, jasmine.any(Object));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it('fetches data for, and subscribes to parent object if it is a telemetry object', function () {
|
|
||||||
return controller.getData().then(function () {
|
|
||||||
expect(mockTelemetryAPI.subscribe).toHaveBeenCalledWith(mockTelemetryObject, jasmine.any(Function), {});
|
|
||||||
expect(mockTelemetryAPI.request).toHaveBeenCalledWith(mockTelemetryObject, jasmine.any(Object));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('fetches data for, and subscribes to any composees that are telemetry objects if parent is not', function () {
|
|
||||||
mockChildren = [
|
|
||||||
{name: "child 1"}
|
|
||||||
];
|
|
||||||
var mockTelemetryChildren = [
|
|
||||||
{name: "child 2"},
|
|
||||||
{name: "child 3"},
|
|
||||||
{name: "child 4"}
|
|
||||||
];
|
|
||||||
mockChildren = mockChildren.concat(mockTelemetryChildren);
|
|
||||||
mockComposition.load.and.returnValue(Promise.resolve(mockChildren));
|
|
||||||
|
|
||||||
mockTelemetryAPI.isTelemetryObject.and.callFake(function (object) {
|
|
||||||
if (object === mockTelemetryObject) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
return mockTelemetryChildren.indexOf(object) !== -1;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return controller.getData().then(function () {
|
|
||||||
mockTelemetryChildren.forEach(function (child) {
|
|
||||||
expect(mockTelemetryAPI.subscribe).toHaveBeenCalledWith(child, jasmine.any(Function), {});
|
|
||||||
});
|
|
||||||
|
|
||||||
mockTelemetryChildren.forEach(function (child) {
|
|
||||||
expect(mockTelemetryAPI.request).toHaveBeenCalledWith(child, jasmine.any(Object));
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(mockTelemetryAPI.subscribe).not.toHaveBeenCalledWith(mockChildren[0], jasmine.any(Function), {});
|
|
||||||
expect(mockTelemetryAPI.subscribe).not.toHaveBeenCalledWith(mockTelemetryObject[0], jasmine.any(Function), {});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('When in real-time mode, enables auto-scroll', function () {
|
|
||||||
controller.registerChangeListeners();
|
|
||||||
|
|
||||||
var clockCallback = getCallback(mockConductor.on, "clock");
|
|
||||||
//Confirm pre-condition
|
|
||||||
expect(mockScope.autoScroll).toBeFalsy();
|
|
||||||
|
|
||||||
//Mock setting the a clock in the Time API
|
|
||||||
clockCallback({});
|
|
||||||
expect(mockScope.autoScroll).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('populates table columns', function () {
|
|
||||||
var allMetadata;
|
|
||||||
var mockTimeSystem1;
|
|
||||||
var mockTimeSystem2;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
allMetadata = [{
|
|
||||||
key: "column1",
|
|
||||||
name: "Column 1",
|
|
||||||
hints: {
|
|
||||||
domain: 1
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
key: "column2",
|
|
||||||
name: "Column 2",
|
|
||||||
hints: {
|
|
||||||
domain: 2
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
key: "column3",
|
|
||||||
name: "Column 3",
|
|
||||||
hints: {}
|
|
||||||
}];
|
|
||||||
|
|
||||||
mockTimeSystem1 = {
|
|
||||||
key: "column1"
|
|
||||||
};
|
|
||||||
mockTimeSystem2 = {
|
|
||||||
key: "column2"
|
|
||||||
};
|
|
||||||
|
|
||||||
mockConductor.timeSystem.and.returnValue(mockTimeSystem1);
|
|
||||||
|
|
||||||
mockTelemetryAPI.getMetadata.and.returnValue({
|
|
||||||
values: function () {
|
|
||||||
return allMetadata;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
controller.loadColumns([mockDomainObject]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('based on metadata for given objects', function () {
|
|
||||||
expect(mockScope.headers).toBeDefined();
|
|
||||||
expect(mockScope.headers.length).toBeGreaterThan(0);
|
|
||||||
expect(mockScope.headers.indexOf(allMetadata[0].name)).not.toBe(-1);
|
|
||||||
expect(mockScope.headers.indexOf(allMetadata[1].name)).not.toBe(-1);
|
|
||||||
expect(mockScope.headers.indexOf(allMetadata[2].name)).not.toBe(-1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('and sorts by column matching time system', function () {
|
|
||||||
expect(mockScope.defaultSort).toEqual("Column 1");
|
|
||||||
|
|
||||||
mockConductor.timeSystem.and.returnValue(mockTimeSystem2);
|
|
||||||
controller.sortByTimeSystem();
|
|
||||||
|
|
||||||
expect(mockScope.defaultSort).toEqual("Column 2");
|
|
||||||
});
|
|
||||||
|
|
||||||
it('batches processing of rows for performance when receiving historical telemetry', function () {
|
|
||||||
var mockHistoricalData = [
|
|
||||||
{
|
|
||||||
"column1": 1,
|
|
||||||
"column2": 2,
|
|
||||||
"column3": 3
|
|
||||||
},{
|
|
||||||
"column1": 4,
|
|
||||||
"column2": 5,
|
|
||||||
"column3": 6
|
|
||||||
}, {
|
|
||||||
"column1": 7,
|
|
||||||
"column2": 8,
|
|
||||||
"column3": 9
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
controller.batchSize = 2;
|
|
||||||
mockTelemetryAPI.request.and.returnValue(Promise.resolve(mockHistoricalData));
|
|
||||||
controller.getHistoricalData([mockDomainObject]);
|
|
||||||
|
|
||||||
return new Promise(function (resolve) {
|
|
||||||
mockTimeout.and.callFake(function () {
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
}).then(function () {
|
|
||||||
mockTimeout.calls.mostRecent().args[0]();
|
|
||||||
expect(mockTimeout.calls.count()).toBe(2);
|
|
||||||
mockTimeout.calls.mostRecent().args[0]();
|
|
||||||
expect(mockScope.rows.length).toBe(3);
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Removes telemetry rows from table when they fall out of bounds', function () {
|
|
||||||
var discardedRows = [
|
|
||||||
{"column1": "value 1"},
|
|
||||||
{"column2": "value 2"},
|
|
||||||
{"column3": "value 3"}
|
|
||||||
];
|
|
||||||
|
|
||||||
spyOn(controller.telemetry, "on").and.callThrough();
|
|
||||||
|
|
||||||
controller.registerChangeListeners();
|
|
||||||
expect(controller.telemetry.on).toHaveBeenCalledWith("discarded", jasmine.any(Function));
|
|
||||||
var onDiscard = getCallback(controller.telemetry.on, "discarded");
|
|
||||||
onDiscard(discardedRows);
|
|
||||||
expect(mockScope.$broadcast).toHaveBeenCalledWith("remove:rows", discardedRows);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when telemetry is added', function () {
|
|
||||||
var testRows;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
testRows = [{ a: 0 }, { a: 1 }, { a: 2 }];
|
|
||||||
|
|
||||||
controller.registerChangeListeners();
|
|
||||||
controller.telemetry.add(testRows);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Adds the rows to the MCTTable directive", function () {
|
|
||||||
expect(mockScope.$broadcast).toHaveBeenCalledWith("add:rows", testRows);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -93,7 +93,7 @@ define([
|
|||||||
|
|
||||||
// Initialize the application
|
// Initialize the application
|
||||||
$log.info("Initializing application.");
|
$log.info("Initializing application.");
|
||||||
initializer.runApplication();
|
return initializer.runApplication();
|
||||||
};
|
};
|
||||||
|
|
||||||
return FrameworkLayer;
|
return FrameworkLayer;
|
||||||
|
|||||||
@@ -52,10 +52,7 @@ define(
|
|||||||
return match ? match[1] : "";
|
return match ? match[1] : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return injector.instantiate(['$http', '$log', FrameworkLayer])
|
||||||
// Reconfigure base url, since bundle paths will all be relative
|
|
||||||
// to the root now.
|
|
||||||
injector.instantiate(['$http', '$log', FrameworkLayer])
|
|
||||||
.initializeApplication(angular, legacyRegistry, logLevel());
|
.initializeApplication(angular, legacyRegistry, logLevel());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -55,9 +55,12 @@ define(
|
|||||||
var angular = this.angular,
|
var angular = this.angular,
|
||||||
document = this.document,
|
document = this.document,
|
||||||
$log = this.$log;
|
$log = this.$log;
|
||||||
$log.info("Bootstrapping application " + (app || {}).name);
|
return new Promise(function (resolve, reject) {
|
||||||
angular.element(document).ready(function () {
|
$log.info("Bootstrapping application " + (app || {}).name);
|
||||||
angular.bootstrap(document, [app.name], { strictDi: true });
|
angular.element(document).ready(function () {
|
||||||
|
angular.bootstrap(document, [app.name], { strictDi: true });
|
||||||
|
resolve(angular);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ define(function () {
|
|||||||
function SearchController($scope, searchService) {
|
function SearchController($scope, searchService) {
|
||||||
var controller = this;
|
var controller = this;
|
||||||
this.$scope = $scope;
|
this.$scope = $scope;
|
||||||
|
this.$scope.ngModel = this.$scope.ngModel || {};
|
||||||
this.searchService = searchService;
|
this.searchService = searchService;
|
||||||
this.numberToDisplay = this.RESULTS_PER_PAGE;
|
this.numberToDisplay = this.RESULTS_PER_PAGE;
|
||||||
this.availabileResults = 0;
|
this.availabileResults = 0;
|
||||||
|
|||||||
94
src/MCT.js
94
src/MCT.js
@@ -24,28 +24,46 @@ define([
|
|||||||
'EventEmitter',
|
'EventEmitter',
|
||||||
'legacyRegistry',
|
'legacyRegistry',
|
||||||
'uuid',
|
'uuid',
|
||||||
|
'./defaultRegistry',
|
||||||
'./api/api',
|
'./api/api',
|
||||||
'./selection/Selection',
|
'./selection/Selection',
|
||||||
'./api/objects/object-utils',
|
'./api/objects/object-utils',
|
||||||
'./plugins/plugins',
|
'./plugins/plugins',
|
||||||
'./ui/ViewRegistry',
|
|
||||||
'./ui/InspectorViewRegistry',
|
|
||||||
'./ui/ToolbarRegistry',
|
|
||||||
'./adapter/indicators/legacy-indicators-plugin',
|
'./adapter/indicators/legacy-indicators-plugin',
|
||||||
'./styles/core.scss'
|
'./plugins/buildInfo/plugin',
|
||||||
|
'./ui/registries/ViewRegistry',
|
||||||
|
'./ui/registries/InspectorViewRegistry',
|
||||||
|
'./ui/registries/ToolbarRegistry',
|
||||||
|
'./ui/router/ApplicationRouter',
|
||||||
|
'./ui/router/Browse',
|
||||||
|
'../platform/framework/src/Main',
|
||||||
|
'./styles-new/core.scss',
|
||||||
|
'./styles-new/notebook.scss',
|
||||||
|
'./ui/components/layout/Layout.vue',
|
||||||
|
'./ui/overlayService/overlayService',
|
||||||
|
'vue'
|
||||||
], function (
|
], function (
|
||||||
EventEmitter,
|
EventEmitter,
|
||||||
legacyRegistry,
|
legacyRegistry,
|
||||||
uuid,
|
uuid,
|
||||||
|
defaultRegistry,
|
||||||
api,
|
api,
|
||||||
Selection,
|
Selection,
|
||||||
objectUtils,
|
objectUtils,
|
||||||
plugins,
|
plugins,
|
||||||
|
LegacyIndicatorsPlugin,
|
||||||
|
buildInfoPlugin,
|
||||||
ViewRegistry,
|
ViewRegistry,
|
||||||
InspectorViewRegistry,
|
InspectorViewRegistry,
|
||||||
ToolbarRegistry,
|
ToolbarRegistry,
|
||||||
LegacyIndicatorsPlugin,
|
ApplicationRouter,
|
||||||
coreStyles
|
Browse,
|
||||||
|
Main,
|
||||||
|
coreStyles,
|
||||||
|
NotebookStyles,
|
||||||
|
Layout,
|
||||||
|
OverlayService,
|
||||||
|
Vue
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
* Open MCT is an extensible web application for building mission
|
* Open MCT is an extensible web application for building mission
|
||||||
@@ -205,8 +223,23 @@ define([
|
|||||||
*/
|
*/
|
||||||
this.indicators = new api.IndicatorAPI(this);
|
this.indicators = new api.IndicatorAPI(this);
|
||||||
|
|
||||||
|
this.notifications = new api.NotificationAPI();
|
||||||
|
|
||||||
this.Dialog = api.Dialog;
|
this.Dialog = api.Dialog;
|
||||||
|
|
||||||
|
this.editor = new api.EditorAPI.default(this);
|
||||||
|
|
||||||
|
this.OverlayService = new OverlayService();
|
||||||
|
|
||||||
|
this.legacyRegistry = defaultRegistry;
|
||||||
|
this.install(this.plugins.Plot());
|
||||||
|
this.install(this.plugins.TelemetryTable());
|
||||||
|
this.install(this.plugins.DisplayLayout());
|
||||||
|
|
||||||
|
if (typeof BUILD_CONSTANTS !== 'undefined') {
|
||||||
|
this.install(buildInfoPlugin(BUILD_CONSTANTS));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MCT.prototype = Object.create(EventEmitter.prototype);
|
MCT.prototype = Object.create(EventEmitter.prototype);
|
||||||
@@ -245,11 +278,6 @@ define([
|
|||||||
domElement = document.body;
|
domElement = document.body;
|
||||||
}
|
}
|
||||||
|
|
||||||
var appDiv = document.createElement('div');
|
|
||||||
appDiv.setAttribute('ng-view', '');
|
|
||||||
appDiv.className = 'user-environ';
|
|
||||||
domElement.appendChild(appDiv);
|
|
||||||
|
|
||||||
this.legacyExtension('runs', {
|
this.legacyExtension('runs', {
|
||||||
depends: ['navigationService'],
|
depends: ['navigationService'],
|
||||||
implementation: function (navigationService) {
|
implementation: function (navigationService) {
|
||||||
@@ -258,6 +286,7 @@ define([
|
|||||||
}.bind(this)
|
}.bind(this)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// TODO: remove with legacy types.
|
||||||
this.types.listKeys().forEach(function (typeKey) {
|
this.types.listKeys().forEach(function (typeKey) {
|
||||||
var type = this.types.get(typeKey);
|
var type = this.types.get(typeKey);
|
||||||
var legacyDefinition = type.toLegacyDefinition();
|
var legacyDefinition = type.toLegacyDefinition();
|
||||||
@@ -265,32 +294,47 @@ define([
|
|||||||
this.legacyExtension('types', legacyDefinition);
|
this.legacyExtension('types', legacyDefinition);
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
|
|
||||||
this.objectViews.getAllProviders().forEach(function (p) {
|
|
||||||
this.legacyExtension('views', {
|
|
||||||
key: p.key,
|
|
||||||
provider: p,
|
|
||||||
name: p.name,
|
|
||||||
cssClass: p.cssClass,
|
|
||||||
description: p.description,
|
|
||||||
editable: p.editable,
|
|
||||||
template: '<mct-view mct-provider-key="' + p.key + '"/>'
|
|
||||||
});
|
|
||||||
}, this);
|
|
||||||
|
|
||||||
legacyRegistry.register('adapter', this.legacyBundle);
|
legacyRegistry.register('adapter', this.legacyBundle);
|
||||||
legacyRegistry.enable('adapter');
|
legacyRegistry.enable('adapter');
|
||||||
|
|
||||||
this.install(LegacyIndicatorsPlugin());
|
this.install(LegacyIndicatorsPlugin());
|
||||||
|
|
||||||
|
this.router = new ApplicationRouter();
|
||||||
|
|
||||||
|
this.router.route(/^\/$/, () => {
|
||||||
|
this.router.setPath('/browse/mine');
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fired by [MCT]{@link module:openmct.MCT} when the application
|
* Fired by [MCT]{@link module:openmct.MCT} when the application
|
||||||
* is started.
|
* is started.
|
||||||
* @event start
|
* @event start
|
||||||
* @memberof module:openmct.MCT~
|
* @memberof module:openmct.MCT~
|
||||||
*/
|
*/
|
||||||
this.emit('start');
|
var startPromise = new Main().run(this.legacyRegistry)
|
||||||
};
|
.then(function (angular) {
|
||||||
|
this.$angular = angular;
|
||||||
|
// OpenMCT Object provider doesn't operate properly unless
|
||||||
|
// something has depended upon objectService. Cool, right?
|
||||||
|
this.$injector.get('objectService');
|
||||||
|
|
||||||
|
var appLayout = new Vue({
|
||||||
|
components: {
|
||||||
|
'Layout': Layout.default
|
||||||
|
},
|
||||||
|
provide: {
|
||||||
|
openmct: this
|
||||||
|
},
|
||||||
|
template: '<Layout ref="layout"></Layout>'
|
||||||
|
});
|
||||||
|
domElement.appendChild(appLayout.$mount().$el);
|
||||||
|
|
||||||
|
this.layout = appLayout.$refs.layout;
|
||||||
|
Browse(this);
|
||||||
|
this.router.start();
|
||||||
|
this.emit('start');
|
||||||
|
}.bind(this));
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Install a plugin in MCT.
|
* Install a plugin in MCT.
|
||||||
|
|||||||
@@ -34,7 +34,9 @@ define([
|
|||||||
'./runs/TimeSettingsURLHandler',
|
'./runs/TimeSettingsURLHandler',
|
||||||
'./runs/TypeDeprecationChecker',
|
'./runs/TypeDeprecationChecker',
|
||||||
'./runs/LegacyTelemetryProvider',
|
'./runs/LegacyTelemetryProvider',
|
||||||
'./services/LegacyObjectAPIInterceptor'
|
'./runs/RegisterLegacyTypes',
|
||||||
|
'./services/LegacyObjectAPIInterceptor',
|
||||||
|
'./views/installLegacyViews'
|
||||||
], function (
|
], function (
|
||||||
legacyRegistry,
|
legacyRegistry,
|
||||||
ActionDialogDecorator,
|
ActionDialogDecorator,
|
||||||
@@ -49,7 +51,9 @@ define([
|
|||||||
TimeSettingsURLHandler,
|
TimeSettingsURLHandler,
|
||||||
TypeDeprecationChecker,
|
TypeDeprecationChecker,
|
||||||
LegacyTelemetryProvider,
|
LegacyTelemetryProvider,
|
||||||
LegacyObjectAPIInterceptor
|
RegisterLegacyTypes,
|
||||||
|
LegacyObjectAPIInterceptor,
|
||||||
|
installLegacyViews
|
||||||
) {
|
) {
|
||||||
legacyRegistry.register('src/adapter', {
|
legacyRegistry.register('src/adapter', {
|
||||||
"extensions": {
|
"extensions": {
|
||||||
@@ -149,6 +153,21 @@ define([
|
|||||||
"openmct",
|
"openmct",
|
||||||
"instantiate"
|
"instantiate"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
implementation: installLegacyViews,
|
||||||
|
depends: [
|
||||||
|
"openmct",
|
||||||
|
"views[]",
|
||||||
|
"instantiate"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
implementation: RegisterLegacyTypes,
|
||||||
|
depends: [
|
||||||
|
"types[]",
|
||||||
|
"openmct"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
licenses: [
|
licenses: [
|
||||||
|
|||||||
17
src/adapter/runs/RegisterLegacyTypes.js
Normal file
17
src/adapter/runs/RegisterLegacyTypes.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
define([
|
||||||
|
|
||||||
|
], function (
|
||||||
|
|
||||||
|
) {
|
||||||
|
function RegisterLegacyTypes(types, openmct) {
|
||||||
|
types.forEach(function (legacyDefinition) {
|
||||||
|
if (!openmct.types.get(legacyDefinition.key)) {
|
||||||
|
console.warn(`DEPRECATION WARNING: Migrate type ${legacyDefinition.key} from ${legacyDefinition.bundle.path} to use the new Types API. Legacy type support will be removed soon.`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
openmct.types.importLegacyTypes(types);
|
||||||
|
}
|
||||||
|
|
||||||
|
return RegisterLegacyTypes;
|
||||||
|
});
|
||||||
110
src/adapter/views/LegacyViewProvider.js
Normal file
110
src/adapter/views/LegacyViewProvider.js
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
define([
|
||||||
|
|
||||||
|
], function (
|
||||||
|
|
||||||
|
) {
|
||||||
|
const DEFAULT_VIEW_PRIORITY = 100;
|
||||||
|
|
||||||
|
const PRIORITY_LEVELS = {
|
||||||
|
"fallback": Number.NEGATIVE_INFINITY,
|
||||||
|
"default": -100,
|
||||||
|
"none": 0,
|
||||||
|
"optional": DEFAULT_VIEW_PRIORITY,
|
||||||
|
"preferred": 1000,
|
||||||
|
"mandatory": Number.POSITIVE_INFINITY
|
||||||
|
};
|
||||||
|
|
||||||
|
function LegacyViewProvider(legacyView, openmct, convertToLegacyObject) {
|
||||||
|
console.warn(`DEPRECATION WARNING: Migrate ${legacyView.key} from ${legacyView.bundle.path} to use the new View APIs. Legacy view support will be removed soon.`);
|
||||||
|
return {
|
||||||
|
key: legacyView.key,
|
||||||
|
name: legacyView.name,
|
||||||
|
cssClass: legacyView.cssClass,
|
||||||
|
description: legacyView.description,
|
||||||
|
editable: legacyView.editable,
|
||||||
|
canView: function (domainObject) {
|
||||||
|
if (!domainObject || !domainObject.identifier) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (legacyView.type) {
|
||||||
|
return domainObject.type === legacyView.type;
|
||||||
|
}
|
||||||
|
let legacyObject = convertToLegacyObject(domainObject);
|
||||||
|
if (legacyView.needs) {
|
||||||
|
let meetsNeeds = legacyView.needs.every(k => legacyObject.hasCapability(k));
|
||||||
|
if (!meetsNeeds) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return openmct.$injector.get('policyService').allow(
|
||||||
|
'view', legacyView, legacyObject
|
||||||
|
);
|
||||||
|
},
|
||||||
|
view: function (domainObject) {
|
||||||
|
let $rootScope = openmct.$injector.get('$rootScope');
|
||||||
|
let templateLinker = openmct.$injector.get('templateLinker');
|
||||||
|
let scope = $rootScope.$new();
|
||||||
|
let legacyObject = convertToLegacyObject(domainObject);
|
||||||
|
let isDestroyed = false;
|
||||||
|
scope.domainObject = legacyObject;
|
||||||
|
scope.model = legacyObject.getModel();
|
||||||
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
show: function (container) {
|
||||||
|
// TODO: implement "gestures" support ?
|
||||||
|
let uses = legacyView.uses || [];
|
||||||
|
let promises = [];
|
||||||
|
let results = uses.map(function (capabilityKey, i) {
|
||||||
|
let result = legacyObject.useCapability(capabilityKey);
|
||||||
|
if (result.then) {
|
||||||
|
promises.push(result.then(function (r) {
|
||||||
|
results[i] = r;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
|
function link() {
|
||||||
|
if (isDestroyed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uses.forEach(function (key, i) {
|
||||||
|
scope[key] = results[i];
|
||||||
|
});
|
||||||
|
templateLinker.link(
|
||||||
|
scope,
|
||||||
|
openmct.$angular.element(container),
|
||||||
|
legacyView
|
||||||
|
);
|
||||||
|
container.style.height = '100%';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (promises.length) {
|
||||||
|
Promise.all(promises)
|
||||||
|
.then(function () {
|
||||||
|
link();
|
||||||
|
scope.$digest();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
link();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
destroy: function () {
|
||||||
|
scope.$destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
priority: function () {
|
||||||
|
let priority = legacyView.priority || DEFAULT_VIEW_PRIORITY;
|
||||||
|
if (typeof priority === 'string') {
|
||||||
|
priority = PRIORITY_LEVELS[priority];
|
||||||
|
}
|
||||||
|
return priority;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return LegacyViewProvider;
|
||||||
|
|
||||||
|
});
|
||||||
95
src/adapter/views/TypeInspectorViewProvider.js
Normal file
95
src/adapter/views/TypeInspectorViewProvider.js
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
define([
|
||||||
|
|
||||||
|
], function (
|
||||||
|
|
||||||
|
) {
|
||||||
|
const DEFAULT_VIEW_PRIORITY = 100;
|
||||||
|
|
||||||
|
const PRIORITY_LEVELS = {
|
||||||
|
"fallback": Number.NEGATIVE_INFINITY,
|
||||||
|
"default": -100,
|
||||||
|
"none": 0,
|
||||||
|
"optional": DEFAULT_VIEW_PRIORITY,
|
||||||
|
"preferred": 1000,
|
||||||
|
"mandatory": Number.POSITIVE_INFINITY
|
||||||
|
};
|
||||||
|
|
||||||
|
function TypeInspectorViewProvider(typeDefinition, openmct, convertToLegacyObject) {
|
||||||
|
console.warn(`DEPRECATION WARNING: Migrate ${typeDefinition.key} from ${typeDefinition.bundle.path} to use the new Inspector View APIs. Legacy Inspector view support will be removed soon.`);
|
||||||
|
let representation = openmct.$injector.get('representations[]')
|
||||||
|
.filter((r) => r.key === typeDefinition.inspector)[0];
|
||||||
|
|
||||||
|
return {
|
||||||
|
key: representation.key,
|
||||||
|
name: representation.name,
|
||||||
|
cssClass: representation.cssClass,
|
||||||
|
description: representation.description,
|
||||||
|
canView: function (selection) {
|
||||||
|
if (!selection[0] || !selection[0].context.item) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let domainObject = selection[0].context.item;
|
||||||
|
return domainObject.type === typeDefinition.key;
|
||||||
|
},
|
||||||
|
view: function (selection) {
|
||||||
|
let domainObject = selection[0].context.item;
|
||||||
|
let $rootScope = openmct.$injector.get('$rootScope');
|
||||||
|
let templateLinker = openmct.$injector.get('templateLinker');
|
||||||
|
let scope = $rootScope.$new();
|
||||||
|
let legacyObject = convertToLegacyObject(domainObject);
|
||||||
|
let isDestroyed = false;
|
||||||
|
scope.domainObject = legacyObject;
|
||||||
|
scope.model = legacyObject.getModel();
|
||||||
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
show: function (container) {
|
||||||
|
// TODO: implement "gestures" support ?
|
||||||
|
let uses = representation.uses || [];
|
||||||
|
let promises = [];
|
||||||
|
let results = uses.map(function (capabilityKey, i) {
|
||||||
|
let result = legacyObject.useCapability(capabilityKey);
|
||||||
|
if (result.then) {
|
||||||
|
promises.push(result.then(function (r) {
|
||||||
|
results[i] = r;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
|
function link() {
|
||||||
|
if (isDestroyed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uses.forEach(function (key, i) {
|
||||||
|
scope[key] = results[i];
|
||||||
|
});
|
||||||
|
templateLinker.link(
|
||||||
|
scope,
|
||||||
|
openmct.$angular.element(container),
|
||||||
|
representation
|
||||||
|
);
|
||||||
|
container.style.height = '100%';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (promises.length) {
|
||||||
|
Promise.all(promises)
|
||||||
|
.then(function () {
|
||||||
|
link();
|
||||||
|
scope.$digest();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
link();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
destroy: function () {
|
||||||
|
scope.$destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return TypeInspectorViewProvider;
|
||||||
|
|
||||||
|
});
|
||||||
31
src/adapter/views/installLegacyViews.js
Normal file
31
src/adapter/views/installLegacyViews.js
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
define([
|
||||||
|
'./LegacyViewProvider',
|
||||||
|
'./TypeInspectorViewProvider',
|
||||||
|
'../../api/objects/object-utils'
|
||||||
|
], function (
|
||||||
|
LegacyViewProvider,
|
||||||
|
TypeInspectorViewProvider,
|
||||||
|
objectUtils
|
||||||
|
) {
|
||||||
|
function installLegacyViews(openmct, legacyViews, instantiate) {
|
||||||
|
|
||||||
|
function convertToLegacyObject(domainObject) {
|
||||||
|
let keyString = objectUtils.makeKeyString(domainObject.identifier);
|
||||||
|
let oldModel = objectUtils.toOldFormat(domainObject);
|
||||||
|
return instantiate(oldModel, keyString);
|
||||||
|
}
|
||||||
|
|
||||||
|
legacyViews.forEach(function (legacyView) {
|
||||||
|
openmct.objectViews.addProvider(new LegacyViewProvider(legacyView, openmct, convertToLegacyObject));
|
||||||
|
});
|
||||||
|
|
||||||
|
let inspectorTypes = openmct.$injector.get('types[]')
|
||||||
|
.filter((t) => t.hasOwnProperty('inspector'));
|
||||||
|
|
||||||
|
inspectorTypes.forEach(function (typeDefinition) {
|
||||||
|
openmct.inspectorViews.addProvider(new TypeInspectorViewProvider(typeDefinition, openmct, convertToLegacyObject));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return installLegacyViews;
|
||||||
|
});
|
||||||
83
src/api/Editor.js
Normal file
83
src/api/Editor.js
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
|
||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
import EventEmitter from 'EventEmitter';
|
||||||
|
|
||||||
|
export default class Editor extends EventEmitter {
|
||||||
|
constructor(openmct) {
|
||||||
|
super();
|
||||||
|
this.editing = false;
|
||||||
|
this.openmct = openmct;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initiate an editing session. This will start a transaction during
|
||||||
|
* which any persist operations will be deferred until either save()
|
||||||
|
* or finish() are called.
|
||||||
|
*/
|
||||||
|
edit() {
|
||||||
|
this.editing = true;
|
||||||
|
this.getTransactionService().startTransaction();
|
||||||
|
this.emit('isEditing', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns true if the application is in edit mode, false otherwise.
|
||||||
|
*/
|
||||||
|
isEditing() {
|
||||||
|
return this.editing;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save any unsaved changes from this editing session. This will
|
||||||
|
* end the current transaction.
|
||||||
|
*/
|
||||||
|
save() {
|
||||||
|
return this.getTransactionService().commit().then((result)=>{
|
||||||
|
this.editing = false;
|
||||||
|
this.emit('isEditing', false);
|
||||||
|
return result
|
||||||
|
}).catch((error)=>{
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* End the currently active transaction and discard unsaved changes.
|
||||||
|
*/
|
||||||
|
cancel() {
|
||||||
|
this.getTransactionService().cancel();
|
||||||
|
this.editing = false;
|
||||||
|
this.emit('isEditing', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
getTransactionService() {
|
||||||
|
if (!this.transactionService) {
|
||||||
|
this.transactionService = this.openmct.$injector.get('transactionService');
|
||||||
|
}
|
||||||
|
return this.transactionService;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,7 +28,10 @@ define([
|
|||||||
'./ui/Dialog',
|
'./ui/Dialog',
|
||||||
'./ui/GestureAPI',
|
'./ui/GestureAPI',
|
||||||
'./telemetry/TelemetryAPI',
|
'./telemetry/TelemetryAPI',
|
||||||
'./indicators/IndicatorAPI'
|
'./indicators/IndicatorAPI',
|
||||||
|
'./notifications/NotificationAPI',
|
||||||
|
'./Editor'
|
||||||
|
|
||||||
], function (
|
], function (
|
||||||
TimeAPI,
|
TimeAPI,
|
||||||
ObjectAPI,
|
ObjectAPI,
|
||||||
@@ -37,7 +40,9 @@ define([
|
|||||||
Dialog,
|
Dialog,
|
||||||
GestureAPI,
|
GestureAPI,
|
||||||
TelemetryAPI,
|
TelemetryAPI,
|
||||||
IndicatorAPI
|
IndicatorAPI,
|
||||||
|
NotificationAPI,
|
||||||
|
EditorAPI
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
TimeAPI: TimeAPI,
|
TimeAPI: TimeAPI,
|
||||||
@@ -47,6 +52,8 @@ define([
|
|||||||
TypeRegistry: TypeRegistry,
|
TypeRegistry: TypeRegistry,
|
||||||
GestureAPI: GestureAPI,
|
GestureAPI: GestureAPI,
|
||||||
TelemetryAPI: TelemetryAPI,
|
TelemetryAPI: TelemetryAPI,
|
||||||
IndicatorAPI: IndicatorAPI
|
IndicatorAPI: IndicatorAPI,
|
||||||
|
NotificationAPI: NotificationAPI.default,
|
||||||
|
EditorAPI: EditorAPI
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ define([
|
|||||||
) {
|
) {
|
||||||
function IndicatorAPI(openmct) {
|
function IndicatorAPI(openmct) {
|
||||||
this.openmct = openmct;
|
this.openmct = openmct;
|
||||||
this.indicatorElements = [];
|
this.indicatorObjects = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
IndicatorAPI.prototype.simpleIndicator = function () {
|
IndicatorAPI.prototype.simpleIndicator = function () {
|
||||||
@@ -55,12 +55,7 @@ define([
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
IndicatorAPI.prototype.add = function (indicator) {
|
IndicatorAPI.prototype.add = function (indicator) {
|
||||||
// So that we can consistently position indicator elements,
|
this.indicatorObjects.push(indicator);
|
||||||
// guarantee that they are wrapped in an element we control
|
|
||||||
var wrapperNode = document.createElement('div');
|
|
||||||
wrapperNode.className = 'h-indicator';
|
|
||||||
wrapperNode.appendChild(indicator.element);
|
|
||||||
this.indicatorElements.push(wrapperNode);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return IndicatorAPI;
|
return IndicatorAPI;
|
||||||
|
|||||||
@@ -20,35 +20,29 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
define([], function () {
|
import EventEmitter from 'EventEmitter';
|
||||||
|
export default class MCTNotification extends EventEmitter {
|
||||||
|
|
||||||
/**
|
constructor(notificationModel, notificationAPI) {
|
||||||
* Formatter for basic numbers. Provides basic support for non-UTC
|
super();
|
||||||
* numbering systems
|
this.notifications = notificationAPI;
|
||||||
*
|
this.model = notificationModel;
|
||||||
* @implements {Format}
|
this.initializeModel();
|
||||||
* @constructor
|
|
||||||
* @memberof platform/commonUI/formats
|
|
||||||
*/
|
|
||||||
function NumberFormat() {
|
|
||||||
this.key = 'number';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NumberFormat.prototype.format = function (value) {
|
minimize() {
|
||||||
if (isNaN(value)) {
|
this.notifications.minimize(this);
|
||||||
return '';
|
}
|
||||||
} else {
|
|
||||||
return '' + value;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
NumberFormat.prototype.parse = function (text) {
|
dismiss() {
|
||||||
return parseFloat(text);
|
this.notifications.dismiss(this)
|
||||||
};
|
}
|
||||||
|
|
||||||
NumberFormat.prototype.validate = function (text) {
|
dismissOrMinimize() {
|
||||||
return !isNaN(text);
|
this.notifications.dismissOrMinimize(this);
|
||||||
};
|
}
|
||||||
|
|
||||||
return NumberFormat;
|
initializeModel() {
|
||||||
});
|
this.model.minimized = this.model.minimized || false;
|
||||||
|
}
|
||||||
|
}
|
||||||
353
src/api/notifications/NotificationApi.js
Normal file
353
src/api/notifications/NotificationApi.js
Normal file
@@ -0,0 +1,353 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This bundle implements the notification service, which can be used to
|
||||||
|
* show banner notifications to the user. Banner notifications
|
||||||
|
* are used to inform users of events in a non-intrusive way. As
|
||||||
|
* much as possible, notifications share a model with blocking
|
||||||
|
* dialogs so that the same information can be provided in a dialog
|
||||||
|
* and then minimized to a banner notification if needed.
|
||||||
|
*
|
||||||
|
* @namespace platform/api/notifications
|
||||||
|
*/
|
||||||
|
import moment from 'moment';
|
||||||
|
import EventEmitter from 'EventEmitter';
|
||||||
|
import MCTNotification from './MCTNotification.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A representation of a banner notification. Banner notifications
|
||||||
|
* are used to inform users of events in a non-intrusive way. As
|
||||||
|
* much as possible, notifications share a model with blocking
|
||||||
|
* dialogs so that the same information can be provided in a dialog
|
||||||
|
* and then minimized to a banner notification if needed, or vice-versa.
|
||||||
|
*
|
||||||
|
* @typedef {object} NotificationModel
|
||||||
|
* @property {string} title The title of the message
|
||||||
|
* @property {string} severity The importance of the message (one of
|
||||||
|
* 'info', 'alert', or 'error' where info < alert <error)
|
||||||
|
* @property {number} [progress] The completion status of a task
|
||||||
|
* represented numerically
|
||||||
|
* @property {boolean} [unknownProgress] a boolean indicating that the
|
||||||
|
* progress of the underlying task is unknown. This will result in a
|
||||||
|
* visually distinct progress bar.
|
||||||
|
* @property {boolean} [autoDismiss] If truthy, dialog will
|
||||||
|
* be automatically minimized or dismissed (depending on severity).
|
||||||
|
* Additionally, if the provided value is a number, it will be used
|
||||||
|
* as the delay period before being dismissed.
|
||||||
|
* @property {boolean} [dismissable=true] If true, notification will
|
||||||
|
* include an option to dismiss it completely.
|
||||||
|
* @see DialogModel
|
||||||
|
*/
|
||||||
|
|
||||||
|
const DEFAULT_AUTO_DISMISS_TIMEOUT = 3000;
|
||||||
|
const MINIMIZE_ANIMATION_TIMEOUT = 300;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The notification service is responsible for informing the user of
|
||||||
|
* events via the use of banner notifications.
|
||||||
|
* @memberof platform/commonUI/notification
|
||||||
|
* @constructor
|
||||||
|
* @param defaultAutoDismissTimeout The period of time that an
|
||||||
|
* auto-dismissed message will be displayed for.
|
||||||
|
* @param minimizeAnimationTimeout When notifications are minimized, a brief
|
||||||
|
* animation is shown. This animation requires some time to execute,
|
||||||
|
* so a timeout is required before the notification is hidden
|
||||||
|
*/
|
||||||
|
export default class NotificationAPI extends EventEmitter {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.notifications = [];
|
||||||
|
this.highest = { severity: "info" };
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A context in which to hold the active notification and a
|
||||||
|
* handle to its timeout.
|
||||||
|
*/
|
||||||
|
this.activeNotification = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimize a notification. The notification will still be available
|
||||||
|
* from the notification list. Typically notifications with a
|
||||||
|
* severity of 'info' should not be minimized, but rather
|
||||||
|
* dismissed. If you're not sure which is appropriate,
|
||||||
|
* use {@link Notification#dismissOrMinimize}
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
minimize(notification) {
|
||||||
|
//Check this is a known notification
|
||||||
|
let index = this.notifications.indexOf(notification);
|
||||||
|
|
||||||
|
if (this.activeTimeout) {
|
||||||
|
/*
|
||||||
|
Method can be called manually (clicking dismiss) or
|
||||||
|
automatically from an auto-timeout. this.activeTimeout
|
||||||
|
acts as a semaphore to prevent race conditions. Cancel any
|
||||||
|
timeout in progress (for the case where a manual dismiss
|
||||||
|
has shortcut an active auto-dismiss), and clear the
|
||||||
|
semaphore.
|
||||||
|
*/
|
||||||
|
clearTimeout(this.activeTimeout);
|
||||||
|
delete this.activeTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index >= 0) {
|
||||||
|
notification.model.minimized = true;
|
||||||
|
//Add a brief timeout before showing the next notification
|
||||||
|
// in order to allow the minimize animation to run through.
|
||||||
|
setTimeout(() => {
|
||||||
|
notification.emit('destroy');
|
||||||
|
this.setActiveNotification(this.selectNextNotification());
|
||||||
|
}, MINIMIZE_ANIMATION_TIMEOUT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Completely removes a notification. This will dismiss it from the
|
||||||
|
* message banner and remove it from the list of notifications.
|
||||||
|
* Typically only notifications with a severity of info should be
|
||||||
|
* dismissed. If you're not sure whether to dismiss or minimize a
|
||||||
|
* notification, use {@link Notification#dismissOrMinimize}.
|
||||||
|
* dismiss
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
dismiss(notification) {
|
||||||
|
//Check this is a known notification
|
||||||
|
let index = this.notifications.indexOf(notification);
|
||||||
|
|
||||||
|
if (this.activeTimeout) {
|
||||||
|
/* Method can be called manually (clicking dismiss) or
|
||||||
|
* automatically from an auto-timeout. this.activeTimeout
|
||||||
|
* acts as a semaphore to prevent race conditions. Cancel any
|
||||||
|
* timeout in progress (for the case where a manual dismiss
|
||||||
|
* has shortcut an active auto-dismiss), and clear the
|
||||||
|
* semaphore.
|
||||||
|
*/
|
||||||
|
|
||||||
|
clearTimeout(this.activeTimeout);
|
||||||
|
delete this.activeTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index >= 0) {
|
||||||
|
this.notifications.splice(index, 1);
|
||||||
|
}
|
||||||
|
this.setActiveNotification(this.selectNextNotification());
|
||||||
|
this.setHighestSeverity();
|
||||||
|
notification.emit('destroy');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Depending on the severity of the notification will selectively
|
||||||
|
* dismiss or minimize where appropriate.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
dismissOrMinimize(notification) {
|
||||||
|
let model = notification.model;
|
||||||
|
if (model.severity === "info") {
|
||||||
|
if (model.autoDismiss === false) {
|
||||||
|
this.minimize(notification);
|
||||||
|
} else {
|
||||||
|
this.dismiss(notification);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.minimize(notification);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the notification that is currently visible in the banner area
|
||||||
|
* @returns {Notification}
|
||||||
|
*/
|
||||||
|
getActiveNotification() {
|
||||||
|
return this.activeNotification;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A convenience method for info notifications. Notifications
|
||||||
|
* created via this method will be auto-destroy after a default
|
||||||
|
* wait period unless explicitly forbidden by the caller through
|
||||||
|
* the {autoDismiss} property on the {NotificationModel}, in which
|
||||||
|
* case the notification will be minimized after the wait.
|
||||||
|
* @param {NotificationModel | string} message either a string for
|
||||||
|
* the title of the notification message, or a {@link NotificationModel}
|
||||||
|
* defining the options notification to display
|
||||||
|
* @returns {Notification} the provided notification decorated with
|
||||||
|
* functions to dismiss or minimize
|
||||||
|
*/
|
||||||
|
info(message) {
|
||||||
|
let notificationModel = typeof message === "string" ? {title: message} : message;
|
||||||
|
notificationModel.severity = "info";
|
||||||
|
return this.notify(notificationModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A convenience method for alert notifications. Notifications
|
||||||
|
* created via this method will will have severity of "alert" enforced
|
||||||
|
* @param {NotificationModel | string} message either a string for
|
||||||
|
* the title of the alert message with default options, or a
|
||||||
|
* {@link NotificationModel} defining the options notification to
|
||||||
|
* display
|
||||||
|
* @returns {Notification} the provided notification decorated with
|
||||||
|
* functions to dismiss or minimize
|
||||||
|
*/
|
||||||
|
alert(message) {
|
||||||
|
let notificationModel = typeof message === "string" ? {title: message} : message;
|
||||||
|
notificationModel.severity = "alert";
|
||||||
|
return this.notify(notificationModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A convenience method for error notifications. Notifications
|
||||||
|
* created via this method will will have severity of "error" enforced
|
||||||
|
* @param {NotificationModel | string} message either a string for
|
||||||
|
* the title of the error message with default options, or a
|
||||||
|
* {@link NotificationModel} defining the options of the notification to
|
||||||
|
* display
|
||||||
|
* @returns {Notification} the provided notification decorated with
|
||||||
|
* functions to dismiss or minimize
|
||||||
|
*/
|
||||||
|
error(message) {
|
||||||
|
let notificationModel = typeof message === "string" ? {title: message} : message;
|
||||||
|
notificationModel.severity = "error";
|
||||||
|
return this.notify(notificationModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
setHighestSeverity() {
|
||||||
|
let severity = {
|
||||||
|
"info": 1,
|
||||||
|
"alert": 2,
|
||||||
|
"error": 3
|
||||||
|
};
|
||||||
|
this.highest.severity = this.notifications.reduce((previous, notification) => {
|
||||||
|
if (severity[notification.model.severity] > severity[previous]) {
|
||||||
|
return notification.model.severity;
|
||||||
|
} else {
|
||||||
|
return previous;
|
||||||
|
}
|
||||||
|
}, "info");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies the user of an event. If there is a banner notification
|
||||||
|
* already active, then it will be dismissed or minimized automatically,
|
||||||
|
* and the provided notification displayed in its place.
|
||||||
|
*
|
||||||
|
* @param {NotificationModel} notificationModel The notification to
|
||||||
|
* display
|
||||||
|
* @returns {Notification} the provided notification decorated with
|
||||||
|
* functions to {@link Notification#dismiss} or {@link Notification#minimize}
|
||||||
|
*/
|
||||||
|
notify(notificationModel) {
|
||||||
|
let notification;
|
||||||
|
let activeNotification = this.activeNotification;
|
||||||
|
|
||||||
|
notificationModel.severity = notificationModel.severity || "info";
|
||||||
|
notificationModel.timestamp = moment.utc().format('YYYY-MM-DD hh:mm:ss.ms');
|
||||||
|
|
||||||
|
notification = new MCTNotification(notificationModel, this);
|
||||||
|
|
||||||
|
this.notifications.push(notification);
|
||||||
|
this.setHighestSeverity();
|
||||||
|
|
||||||
|
/*
|
||||||
|
Check if there is already an active (ie. visible) notification
|
||||||
|
*/
|
||||||
|
if (!this.activeNotification) {
|
||||||
|
this.setActiveNotification(notification);
|
||||||
|
} else if (!this.activeTimeout) {
|
||||||
|
/*
|
||||||
|
If there is already an active notification, time it out. If it's
|
||||||
|
already got a timeout in progress (either because it has had
|
||||||
|
timeout forced because of a queue of messages, or it had an
|
||||||
|
autodismiss specified), leave it to run. Otherwise force a
|
||||||
|
timeout.
|
||||||
|
|
||||||
|
This notification has been added to queue and will be
|
||||||
|
serviced as soon as possible.
|
||||||
|
*/
|
||||||
|
this.activeTimeout = setTimeout(() => {
|
||||||
|
this.dismissOrMinimize(activeNotification);
|
||||||
|
}, DEFAULT_AUTO_DISMISS_TIMEOUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
return notification;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used internally by the NotificationService
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
setActiveNotification(notification) {
|
||||||
|
let shouldAutoDismiss;
|
||||||
|
this.activeNotification = notification;
|
||||||
|
|
||||||
|
if (!notification) {
|
||||||
|
delete this.activeTimeout;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.emit('notification', notification);
|
||||||
|
|
||||||
|
if (notification.model.severity === "info") {
|
||||||
|
shouldAutoDismiss = true;
|
||||||
|
} else {
|
||||||
|
shouldAutoDismiss = notification.model.autoDismiss;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldAutoDismiss || this.selectNextNotification()) {
|
||||||
|
this.activeTimeout = setTimeout(() => {
|
||||||
|
this.dismissOrMinimize(notification);
|
||||||
|
}, DEFAULT_AUTO_DISMISS_TIMEOUT);
|
||||||
|
} else {
|
||||||
|
delete this.activeTimeout;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used internally by the NotificationService
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
selectNextNotification() {
|
||||||
|
let notification;
|
||||||
|
let i = 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
Loop through the notifications queue and find the first one that
|
||||||
|
has not already been minimized (manually or otherwise).
|
||||||
|
*/
|
||||||
|
for (; i < this.notifications.length; i++) {
|
||||||
|
notification = this.notifications[i];
|
||||||
|
|
||||||
|
if (!notification.model.minimized &&
|
||||||
|
notification !== this.activeNotification) {
|
||||||
|
return notification;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,6 +32,9 @@ define(function () {
|
|||||||
*/
|
*/
|
||||||
function Type(definition) {
|
function Type(definition) {
|
||||||
this.definition = definition;
|
this.definition = definition;
|
||||||
|
if (definition.key) {
|
||||||
|
this.key = definition.key;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -70,5 +73,29 @@ define(function () {
|
|||||||
return def;
|
return def;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a type definition from a legacy definition.
|
||||||
|
*/
|
||||||
|
Type.definitionFromLegacyDefinition = function (legacyDefinition) {
|
||||||
|
let definition = {};
|
||||||
|
definition.name = legacyDefinition.name;
|
||||||
|
definition.cssClass = legacyDefinition.cssClass;
|
||||||
|
definition.description = legacyDefinition.description;
|
||||||
|
definition.form = legacyDefinition.properties;
|
||||||
|
if (legacyDefinition.model) {
|
||||||
|
definition.initialize = function (model) {
|
||||||
|
for (let [k, v] of Object.entries(legacyDefinition.model)) {
|
||||||
|
model[k] = JSON.parse(JSON.stringify(v));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (legacyDefinition.features && legacyDefinition.features.includes("creation")) {
|
||||||
|
definition.creatable = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return definition;
|
||||||
|
};
|
||||||
|
|
||||||
return Type;
|
return Type;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -98,6 +98,14 @@ define(['./Type'], function (Type) {
|
|||||||
return this.types[typeKey];
|
return this.types[typeKey];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
TypeRegistry.prototype.importLegacyTypes = function (types) {
|
||||||
|
types.filter((t) => !this.get(t.key))
|
||||||
|
.forEach((type) => {
|
||||||
|
let def = Type.definitionFromLegacyDefinition(type);
|
||||||
|
this.addType(type.key, def);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return TypeRegistry;
|
return TypeRegistry;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -55,16 +55,11 @@ define([
|
|||||||
'../platform/exporters/bundle',
|
'../platform/exporters/bundle',
|
||||||
'../platform/features/clock/bundle',
|
'../platform/features/clock/bundle',
|
||||||
'../platform/features/fixed/bundle',
|
'../platform/features/fixed/bundle',
|
||||||
'../platform/features/conductor/core/bundle',
|
|
||||||
'../platform/features/conductor/compatibility/bundle',
|
|
||||||
'../platform/features/imagery/bundle',
|
'../platform/features/imagery/bundle',
|
||||||
'../platform/features/layout/bundle',
|
|
||||||
'../platform/features/listview/bundle',
|
|
||||||
'../platform/features/my-items/bundle',
|
'../platform/features/my-items/bundle',
|
||||||
'../platform/features/pages/bundle',
|
'../platform/features/pages/bundle',
|
||||||
'../platform/features/hyperlink/bundle',
|
'../platform/features/hyperlink/bundle',
|
||||||
'../platform/features/static-markup/bundle',
|
'../platform/features/static-markup/bundle',
|
||||||
'../platform/features/table/bundle',
|
|
||||||
'../platform/features/timeline/bundle',
|
'../platform/features/timeline/bundle',
|
||||||
'../platform/forms/bundle',
|
'../platform/forms/bundle',
|
||||||
'../platform/framework/bundle',
|
'../platform/framework/bundle',
|
||||||
@@ -103,12 +98,9 @@ define([
|
|||||||
'platform/features/clock',
|
'platform/features/clock',
|
||||||
'platform/features/fixed',
|
'platform/features/fixed',
|
||||||
'platform/features/imagery',
|
'platform/features/imagery',
|
||||||
'platform/features/layout',
|
|
||||||
'platform/features/listview',
|
|
||||||
'platform/features/pages',
|
'platform/features/pages',
|
||||||
'platform/features/hyperlink',
|
'platform/features/hyperlink',
|
||||||
'platform/features/timeline',
|
'platform/features/timeline',
|
||||||
'platform/features/table',
|
|
||||||
'platform/forms',
|
'platform/forms',
|
||||||
'platform/identity',
|
'platform/identity',
|
||||||
'platform/persistence/aggregator',
|
'platform/persistence/aggregator',
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/******************************************************************************
|
/*****************************************************************************
|
||||||
* Open MCT, Copyright (c) 2014-2017, United States Government
|
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
* Administration. All rights reserved.
|
* Administration. All rights reserved.
|
||||||
*
|
*
|
||||||
@@ -20,25 +20,18 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
/**
|
import CSV from 'comma-separated-values';
|
||||||
* This bundle implements "containment" rules, which determine which objects
|
import {saveAs} from 'file-saver/FileSaver';
|
||||||
* can be contained within a notebook.
|
|
||||||
*/
|
|
||||||
define(
|
|
||||||
[],
|
|
||||||
function () {
|
|
||||||
function CompositionPolicy() {
|
|
||||||
}
|
|
||||||
|
|
||||||
CompositionPolicy.prototype.allow = function (parent, child) {
|
class CSVExporter {
|
||||||
var parentDef = parent.getCapability('type').getName();
|
export(rows, options) {
|
||||||
|
let headers = (options && options.headers) ||
|
||||||
if (parentDef === 'Notebook' && child.getCapability('status').list().length) {
|
(Object.keys((rows[0] || {})).sort());
|
||||||
return false;
|
let filename = (options && options.filename) || "export.csv";
|
||||||
}
|
let csvText = new CSV(rows, { header: headers }).encode();
|
||||||
return true;
|
let blob = new Blob([csvText], { type: "text/csv" });
|
||||||
};
|
saveAs(blob, filename);
|
||||||
|
|
||||||
return CompositionPolicy;
|
|
||||||
}
|
}
|
||||||
);
|
};
|
||||||
|
|
||||||
|
export default CSVExporter;
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
define([
|
define([
|
||||||
'./AutoflowTabularController',
|
'./AutoflowTabularController',
|
||||||
'./AutoflowTabularConstants',
|
'./AutoflowTabularConstants',
|
||||||
'../../ui/VueView',
|
'./VueView',
|
||||||
'./autoflow-tabular.html'
|
'./autoflow-tabular.html'
|
||||||
], function (
|
], function (
|
||||||
AutoflowTabularController,
|
AutoflowTabularController,
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user