mirror of
https://github.com/nasa/openmct.git
synced 2025-05-06 18:48:27 +00:00
Merge remote-tracking branch 'github/master' into open233b
Resolve conflicts on nasa/openmctweb#233 Conflicts: platform/commonUI/general/bundle.json platform/commonUI/general/res/fonts/symbols/icomoon.io-WTD-symbols-project.json platform/commonUI/general/res/fonts/symbols/wtdsymbols.eot platform/commonUI/general/res/fonts/symbols/wtdsymbols.svg platform/commonUI/general/res/fonts/symbols/wtdsymbols.ttf platform/commonUI/general/res/fonts/symbols/wtdsymbols.woff platform/commonUI/general/res/sass/_main.scss platform/commonUI/general/res/sass/user-environ/_layout.scss platform/commonUI/themes/espresso/res/css/theme-espresso.css platform/commonUI/themes/espresso/res/sass/_constants.scss platform/commonUI/themes/snow/res/css/theme-snow.css
This commit is contained in:
commit
f7839b758d
@ -6,10 +6,12 @@
|
|||||||
"platform/commonUI/browse",
|
"platform/commonUI/browse",
|
||||||
"platform/commonUI/edit",
|
"platform/commonUI/edit",
|
||||||
"platform/commonUI/dialog",
|
"platform/commonUI/dialog",
|
||||||
|
"platform/commonUI/formats",
|
||||||
"platform/commonUI/general",
|
"platform/commonUI/general",
|
||||||
"platform/commonUI/inspect",
|
"platform/commonUI/inspect",
|
||||||
"platform/commonUI/mobile",
|
"platform/commonUI/mobile",
|
||||||
"platform/commonUI/themes/espresso",
|
"platform/commonUI/themes/espresso",
|
||||||
|
"platform/commonUI/notification",
|
||||||
"platform/containment",
|
"platform/containment",
|
||||||
"platform/execution",
|
"platform/execution",
|
||||||
"platform/telemetry",
|
"platform/telemetry",
|
||||||
|
@ -16,6 +16,23 @@
|
|||||||
"implementation": "SinewaveLimitCapability.js"
|
"implementation": "SinewaveLimitCapability.js"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"formats": [
|
||||||
|
{
|
||||||
|
"key": "example.delta",
|
||||||
|
"implementation": "SinewaveDeltaFormat.js"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"constants": [
|
||||||
|
{
|
||||||
|
"key": "TIME_CONDUCTOR_DOMAINS",
|
||||||
|
"value": [
|
||||||
|
{ "key": "time", "name": "Time" },
|
||||||
|
{ "key": "yesterday", "name": "Yesterday" },
|
||||||
|
{ "key": "delta", "name": "Delta", "format": "example.delta" }
|
||||||
|
],
|
||||||
|
"priority": -1
|
||||||
|
}
|
||||||
|
],
|
||||||
"types": [
|
"types": [
|
||||||
{
|
{
|
||||||
"key": "generator",
|
"key": "generator",
|
||||||
@ -38,6 +55,11 @@
|
|||||||
{
|
{
|
||||||
"key": "yesterday",
|
"key": "yesterday",
|
||||||
"name": "Yesterday"
|
"name": "Yesterday"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "delta",
|
||||||
|
"name": "Delta",
|
||||||
|
"format": "example.delta"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"ranges": [
|
"ranges": [
|
||||||
|
26
example/generator/src/SinewaveConstants.js
Normal file
26
example/generator/src/SinewaveConstants.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT Web includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
/*global define,Promise*/
|
||||||
|
|
||||||
|
define({
|
||||||
|
START_TIME: Date.now() - 24 * 60 * 60 * 1000 // Now minus a day.
|
||||||
|
});
|
68
example/generator/src/SinewaveDeltaFormat.js
Normal file
68
example/generator/src/SinewaveDeltaFormat.js
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT Web includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
/*global define,Promise*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
['./SinewaveConstants', 'moment'],
|
||||||
|
function (SinewaveConstants, moment) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var START_TIME = SinewaveConstants.START_TIME,
|
||||||
|
FORMAT_REGEX = /^-?\d+:\d+:\d+$/,
|
||||||
|
SECOND = 1000,
|
||||||
|
MINUTE = SECOND * 60,
|
||||||
|
HOUR = MINUTE * 60;
|
||||||
|
|
||||||
|
function SinewaveDeltaFormat() {
|
||||||
|
}
|
||||||
|
|
||||||
|
function twoDigit(v) {
|
||||||
|
return v >= 10 ? String(v) : ('0' + v);
|
||||||
|
}
|
||||||
|
|
||||||
|
SinewaveDeltaFormat.prototype.format = function (value) {
|
||||||
|
var delta = Math.abs(value - START_TIME),
|
||||||
|
negative = value < START_TIME,
|
||||||
|
seconds = Math.floor(delta / SECOND) % 60,
|
||||||
|
minutes = Math.floor(delta / MINUTE) % 60,
|
||||||
|
hours = Math.floor(delta / HOUR);
|
||||||
|
return (negative ? "-" : "") +
|
||||||
|
[ hours, minutes, seconds ].map(twoDigit).join(":");
|
||||||
|
};
|
||||||
|
|
||||||
|
SinewaveDeltaFormat.prototype.validate = function (text) {
|
||||||
|
return FORMAT_REGEX.test(text);
|
||||||
|
};
|
||||||
|
|
||||||
|
SinewaveDeltaFormat.prototype.parse = function (text) {
|
||||||
|
var negative = text[0] === "-",
|
||||||
|
parts = text.replace("-", "").split(":");
|
||||||
|
return [ HOUR, MINUTE, SECOND ].map(function (sz, i) {
|
||||||
|
return parseInt(parts[i], 10) * sz;
|
||||||
|
}).reduce(function (a, b) {
|
||||||
|
return a + b;
|
||||||
|
}, 0) * (negative ? -1 : 1) + START_TIME;
|
||||||
|
};
|
||||||
|
|
||||||
|
return SinewaveDeltaFormat;
|
||||||
|
}
|
||||||
|
);
|
@ -25,12 +25,12 @@
|
|||||||
* Module defining SinewaveTelemetry. Created by vwoeltje on 11/12/14.
|
* Module defining SinewaveTelemetry. Created by vwoeltje on 11/12/14.
|
||||||
*/
|
*/
|
||||||
define(
|
define(
|
||||||
[],
|
['./SinewaveConstants'],
|
||||||
function () {
|
function (SinewaveConstants) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var ONE_DAY = 60 * 60 * 24,
|
var ONE_DAY = 60 * 60 * 24,
|
||||||
firstObservedTime = Math.floor(Date.now() / 1000) - ONE_DAY;
|
firstObservedTime = Math.floor(SinewaveConstants.START_TIME / 1000);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -58,6 +58,9 @@ define(
|
|||||||
};
|
};
|
||||||
|
|
||||||
generatorData.getDomainValue = function (i, domain) {
|
generatorData.getDomainValue = function (i, domain) {
|
||||||
|
// delta uses the same numeric values as the default domain,
|
||||||
|
// so it's not checked for here, just formatted for display
|
||||||
|
// differently.
|
||||||
return (i + offset) * 1000 + firstTime * 1000 -
|
return (i + offset) * 1000 + firstTime * 1000 -
|
||||||
(domain === 'yesterday' ? ONE_DAY : 0);
|
(domain === 'yesterday' ? ONE_DAY : 0);
|
||||||
};
|
};
|
||||||
|
47
example/notifications/bundle.json
Normal file
47
example/notifications/bundle.json
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
{
|
||||||
|
"extensions": {
|
||||||
|
"templates": [
|
||||||
|
{
|
||||||
|
"key": "dialogLaunchTemplate",
|
||||||
|
"templateUrl": "dialog-launch.html"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "notificationLaunchTemplate",
|
||||||
|
"templateUrl": "notification-launch.html"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"controllers": [
|
||||||
|
{
|
||||||
|
"key": "DialogLaunchController",
|
||||||
|
"implementation": "DialogLaunchController.js",
|
||||||
|
"depends": [
|
||||||
|
"$scope",
|
||||||
|
"$timeout",
|
||||||
|
"$log",
|
||||||
|
"dialogService",
|
||||||
|
"notificationService"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "NotificationLaunchController",
|
||||||
|
"implementation": "NotificationLaunchController.js",
|
||||||
|
"depends": [
|
||||||
|
"$scope",
|
||||||
|
"$timeout",
|
||||||
|
"$log",
|
||||||
|
"notificationService"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"indicators": [
|
||||||
|
{
|
||||||
|
"implementation": "DialogLaunchIndicator.js",
|
||||||
|
"priority": "fallback"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"implementation": "NotificationLaunchIndicator.js",
|
||||||
|
"priority": "fallback"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
10
example/notifications/res/dialog-launch.html
Normal file
10
example/notifications/res/dialog-launch.html
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<span class="status block ok" ng-controller="DialogLaunchController">
|
||||||
|
<span class="ui-symbol status-indicator"></span>
|
||||||
|
<span class="label">
|
||||||
|
<a ng-click="launchProgress(true)">Known</a> |
|
||||||
|
<a ng-click="launchProgress(false)">Unknown</a> |
|
||||||
|
<a ng-click="launchError()">Error</a> |
|
||||||
|
<a ng-click="launchInfo()">Info</a>
|
||||||
|
</span>
|
||||||
|
<span class="count">Dialogs</span>
|
||||||
|
</span>
|
10
example/notifications/res/notification-launch.html
Normal file
10
example/notifications/res/notification-launch.html
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<span class="status block ok" ng-controller="NotificationLaunchController">
|
||||||
|
<span class="ui-symbol status-indicator"></span>
|
||||||
|
<span class="label">
|
||||||
|
<a ng-click="newInfo()">Success</a> |
|
||||||
|
<a ng-click="newError()">Error</a> |
|
||||||
|
<a ng-click="newAlert()">Alert</a> |
|
||||||
|
<a ng-click="newProgress()">Progress</a>
|
||||||
|
</span>
|
||||||
|
<span class="count">Notifications</span>
|
||||||
|
</span>
|
150
example/notifications/src/DialogLaunchController.js
Normal file
150
example/notifications/src/DialogLaunchController.js
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT Web includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
/*global define*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
[],
|
||||||
|
function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A controller for the dialog launch view. This view allows manual
|
||||||
|
* launching of dialogs for demonstration and testing purposes. It
|
||||||
|
* also demonstrates the use of the DialogService.
|
||||||
|
* @param $scope
|
||||||
|
* @param $timeout
|
||||||
|
* @param $log
|
||||||
|
* @param dialogService
|
||||||
|
* @param notificationService
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function DialogLaunchController($scope, $timeout, $log, dialogService, notificationService) {
|
||||||
|
|
||||||
|
/*
|
||||||
|
Demonstrates launching a progress dialog and updating it
|
||||||
|
periodically with the progress of an ongoing process.
|
||||||
|
*/
|
||||||
|
$scope.launchProgress = function (knownProgress) {
|
||||||
|
var model = {
|
||||||
|
title: "Progress Dialog Example",
|
||||||
|
progress: 0,
|
||||||
|
hint: "Do not navigate away from this page or close this browser tab while this operation is in progress.",
|
||||||
|
actionText: "Calculating...",
|
||||||
|
unknownProgress: !knownProgress,
|
||||||
|
unknownDuration: false,
|
||||||
|
severity: "info",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: "Cancel Operation",
|
||||||
|
callback: function () {
|
||||||
|
$log.debug("Operation cancelled");
|
||||||
|
dialogService.dismiss();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Do something else...",
|
||||||
|
callback: function () {
|
||||||
|
$log.debug("Something else pressed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
function incrementProgress() {
|
||||||
|
model.progress = Math.min(100, Math.floor(model.progress + Math.random() * 30));
|
||||||
|
model.progressText = ["Estimated time remaining: about ", 60 - Math.floor((model.progress / 100) * 60), " seconds"].join(" ");
|
||||||
|
if (model.progress < 100) {
|
||||||
|
$timeout(incrementProgress, 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dialogService.showBlockingMessage(model)) {
|
||||||
|
//Do processing here
|
||||||
|
model.actionText = "Processing 100 objects...";
|
||||||
|
if (knownProgress) {
|
||||||
|
$timeout(incrementProgress, 1000);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$log.error("Could not display modal dialog");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Demonstrates launching an error dialog
|
||||||
|
*/
|
||||||
|
$scope.launchError = function () {
|
||||||
|
var model = {
|
||||||
|
title: "Error Dialog Example",
|
||||||
|
actionText: "Something happened, and it was not good.",
|
||||||
|
severity: "error",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: "Try Again",
|
||||||
|
callback: function () {
|
||||||
|
$log.debug("Try Again Pressed");
|
||||||
|
dialogService.dismiss();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Cancel",
|
||||||
|
callback: function () {
|
||||||
|
$log.debug("Cancel Pressed");
|
||||||
|
dialogService.dismiss();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!dialogService.showBlockingMessage(model)) {
|
||||||
|
$log.error("Could not display modal dialog");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Demonstrates launching an error dialog
|
||||||
|
*/
|
||||||
|
$scope.launchInfo = function () {
|
||||||
|
var model = {
|
||||||
|
title: "Info Dialog Example",
|
||||||
|
actionText: "This is an example of a blocking info" +
|
||||||
|
" dialog. This dialog can be used to draw the user's" +
|
||||||
|
" attention to an event.",
|
||||||
|
severity: "info",
|
||||||
|
primaryOption: {
|
||||||
|
label: "OK",
|
||||||
|
callback: function () {
|
||||||
|
$log.debug("OK Pressed");
|
||||||
|
dialogService.dismiss();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!dialogService.showBlockingMessage(model)) {
|
||||||
|
$log.error("Could not display modal dialog");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
return DialogLaunchController;
|
||||||
|
}
|
||||||
|
);
|
56
example/notifications/src/DialogLaunchIndicator.js
Normal file
56
example/notifications/src/DialogLaunchIndicator.js
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT Web includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
/*global define,window*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
[],
|
||||||
|
function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A tool for manually invoking dialogs. When included this
|
||||||
|
* indicator will allow for dialogs of different types to be
|
||||||
|
* launched for demonstration and testing purposes.
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function DialogLaunchIndicator() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
DialogLaunchIndicator.template = 'dialogLaunchTemplate';
|
||||||
|
|
||||||
|
DialogLaunchIndicator.prototype.getGlyph = function () {
|
||||||
|
return "i";
|
||||||
|
};
|
||||||
|
DialogLaunchIndicator.prototype.getGlyphClass = function () {
|
||||||
|
return 'caution';
|
||||||
|
};
|
||||||
|
DialogLaunchIndicator.prototype.getText = function () {
|
||||||
|
return "Launch test dialog";
|
||||||
|
};
|
||||||
|
DialogLaunchIndicator.prototype.getDescription = function () {
|
||||||
|
return "Launch test dialog";
|
||||||
|
};
|
||||||
|
|
||||||
|
return DialogLaunchIndicator;
|
||||||
|
}
|
||||||
|
);
|
172
example/notifications/src/NotificationLaunchController.js
Normal file
172
example/notifications/src/NotificationLaunchController.js
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT Web includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
/*global define*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
[],
|
||||||
|
function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows launching of notification messages for the purposes of
|
||||||
|
* demonstration and testing. Also demonstrates use of
|
||||||
|
* the NotificationService. Notifications are non-blocking messages that
|
||||||
|
* appear at the bottom of the screen to inform the user of events
|
||||||
|
* in a non-intrusive way. For more information see the
|
||||||
|
* {@link NotificationService}
|
||||||
|
* @param $scope
|
||||||
|
* @param $timeout
|
||||||
|
* @param $log
|
||||||
|
* @param notificationService
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function NotificationLaunchController($scope, $timeout, $log, notificationService) {
|
||||||
|
var messageCounter = 1;
|
||||||
|
|
||||||
|
function getExampleActionText() {
|
||||||
|
var actionTexts = [
|
||||||
|
"Adipiscing turpis mauris in enim elementu hac, enim aliquam etiam.",
|
||||||
|
"Eros turpis, pulvinar turpis eros eu",
|
||||||
|
"Lundium nascetur a, lectus montes ac, parturient in natoque, duis risus risus pulvinar pid rhoncus, habitasse auctor natoque!"
|
||||||
|
];
|
||||||
|
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'.
|
||||||
|
*/
|
||||||
|
$scope.newError = function(){
|
||||||
|
|
||||||
|
notificationService.notify({
|
||||||
|
title: "Example error notification " + messageCounter++,
|
||||||
|
hint: "An error has occurred",
|
||||||
|
severity: "error",
|
||||||
|
primaryOption: {
|
||||||
|
label: 'Retry',
|
||||||
|
callback: function() {
|
||||||
|
$log.info('Retry clicked');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
options: getExampleActions()});
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Launch a new notification with a severity of 'Alert'.
|
||||||
|
*/
|
||||||
|
$scope.newAlert = function(){
|
||||||
|
|
||||||
|
notificationService.notify({
|
||||||
|
title: "Alert notification " + (messageCounter++),
|
||||||
|
hint: "This is an alert message",
|
||||||
|
severity: "alert",
|
||||||
|
primaryOption: {
|
||||||
|
label: 'Retry',
|
||||||
|
callback: function() {
|
||||||
|
$log.info('Retry clicked');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
options: getExampleActions()});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Launch a new notification with a progress bar that is updated
|
||||||
|
* periodically, tracking an ongoing process.
|
||||||
|
*/
|
||||||
|
$scope.newProgress = function(){
|
||||||
|
|
||||||
|
var notificationModel = {
|
||||||
|
title: "Progress notification example",
|
||||||
|
severity: "info",
|
||||||
|
progress: 0,
|
||||||
|
actionText: getExampleActionText(),
|
||||||
|
unknownProgress: false
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simulate an ongoing process and update the progress bar.
|
||||||
|
* @param notification
|
||||||
|
*/
|
||||||
|
function incrementProgress(notificationModel) {
|
||||||
|
notificationModel.progress = Math.min(100, Math.floor(notificationModel.progress + Math.random() * 30));
|
||||||
|
notificationModel.progressText = ["Estimated time" +
|
||||||
|
" remaining:" +
|
||||||
|
" about ", 60 - Math.floor((notificationModel.progress / 100) * 60), " seconds"].join(" ");
|
||||||
|
if (notificationModel.progress < 100) {
|
||||||
|
$timeout(function(){incrementProgress(notificationModel);}, 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
notificationService.notify(notificationModel);
|
||||||
|
incrementProgress(notificationModel);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Launch a new notification with severity level of INFO.
|
||||||
|
*/
|
||||||
|
$scope.newInfo = function(){
|
||||||
|
|
||||||
|
notificationService.info({
|
||||||
|
title: "Example Info notification " + messageCounter++
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
return NotificationLaunchController;
|
||||||
|
}
|
||||||
|
);
|
50
example/notifications/src/NotificationLaunchIndicator.js
Normal file
50
example/notifications/src/NotificationLaunchIndicator.js
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT Web includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
/*global define,window*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
[],
|
||||||
|
function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
function NotificationLaunchIndicator() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
NotificationLaunchIndicator.template = 'notificationLaunchTemplate';
|
||||||
|
|
||||||
|
NotificationLaunchIndicator.prototype.getGlyph = function () {
|
||||||
|
return "i";
|
||||||
|
};
|
||||||
|
NotificationLaunchIndicator.prototype.getGlyphClass = function () {
|
||||||
|
return 'caution';
|
||||||
|
};
|
||||||
|
NotificationLaunchIndicator.prototype.getText = function () {
|
||||||
|
return "Launch notification";
|
||||||
|
};
|
||||||
|
NotificationLaunchIndicator.prototype.getDescription = function () {
|
||||||
|
return "Launch notification";
|
||||||
|
};
|
||||||
|
|
||||||
|
return NotificationLaunchIndicator;
|
||||||
|
}
|
||||||
|
);
|
@ -40,10 +40,9 @@
|
|||||||
</mct-representation>
|
</mct-representation>
|
||||||
<div class='holder search-holder abs'
|
<div class='holder search-holder abs'
|
||||||
ng-class="{active: treeModel.search}">
|
ng-class="{active: treeModel.search}">
|
||||||
<mct-representation key="'search'"
|
<mct-include key="'search'"
|
||||||
mct-object="domainObject"
|
|
||||||
ng-model="treeModel">
|
ng-model="treeModel">
|
||||||
</mct-representation>
|
</mct-include>
|
||||||
</div>
|
</div>
|
||||||
<div class='tree-holder abs mobile-tree-holder'
|
<div class='tree-holder abs mobile-tree-holder'
|
||||||
ng-hide="treeModel.search">
|
ng-hide="treeModel.search">
|
||||||
|
@ -24,6 +24,18 @@
|
|||||||
{
|
{
|
||||||
"key": "form-dialog",
|
"key": "form-dialog",
|
||||||
"templateUrl": "templates/dialog.html"
|
"templateUrl": "templates/dialog.html"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "overlay-blocking-message",
|
||||||
|
"templateUrl": "templates/overlay-blocking-message.html"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "message",
|
||||||
|
"templateUrl": "templates/message.html"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "overlay-message-list",
|
||||||
|
"templateUrl": "templates/overlay-message-list.html"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"containers": [
|
"containers": [
|
||||||
|
@ -21,17 +21,13 @@
|
|||||||
-->
|
-->
|
||||||
<div class="abs top-bar">
|
<div class="abs top-bar">
|
||||||
<div class="title">{{ngModel.title}}</div>
|
<div class="title">{{ngModel.title}}</div>
|
||||||
<div class="hint">
|
<div class="hint">All fields marked <span class="ui-symbol req">*</span> are required.</div>
|
||||||
All fields marked <span class="ui-symbol req">*</span> are required.
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="abs form editor">
|
<div class='abs editor'>
|
||||||
<div class='abs contents l-dialog'>
|
<mct-form ng-model="ngModel.value"
|
||||||
<mct-form ng-model="ngModel.value"
|
structure="ngModel.structure"
|
||||||
structure="ngModel.structure"
|
name="createForm">
|
||||||
name="createForm">
|
</mct-form>
|
||||||
</mct-form>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="abs bottom-bar">
|
<div class="abs bottom-bar">
|
||||||
<a class='s-btn major'
|
<a class='s-btn major'
|
||||||
|
32
platform/commonUI/dialog/res/templates/message.html
Normal file
32
platform/commonUI/dialog/res/templates/message.html
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<div class="l-message"
|
||||||
|
ng-class="'message-severity-' + ngModel.severity">
|
||||||
|
<div class="ui-symbol type-icon message-type"></div>
|
||||||
|
<div class="message-contents">
|
||||||
|
<div class="top-bar">
|
||||||
|
<div class="title">{{ngModel.title}}</div>
|
||||||
|
<div class="hint" ng-hide="ngModel.hint === undefined">{{ngModel.hint}}</div>
|
||||||
|
</div>
|
||||||
|
<div class="message-body">
|
||||||
|
<div class="message-action">
|
||||||
|
{{ngModel.actionText}}
|
||||||
|
</div>
|
||||||
|
<mct-include key="'progress-bar'"
|
||||||
|
ng-model="ngModel"
|
||||||
|
ng-show="ngModel.progress !== undefined || ngModel.unknownProgress"></mct-include>
|
||||||
|
</div>
|
||||||
|
<div class="bottom-bar">
|
||||||
|
<a ng-repeat="dialogOption in ngModel.options"
|
||||||
|
class="s-btn major"
|
||||||
|
ng-click="dialogOption.callback()">
|
||||||
|
{{dialogOption.label}}
|
||||||
|
</a>
|
||||||
|
<a class="s-btn major"
|
||||||
|
ng-if="ngModel.primaryOption"
|
||||||
|
ng-click="ngModel.primaryOption.callback()">
|
||||||
|
{{ngModel.primaryOption.label}}
|
||||||
|
</a>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,25 @@
|
|||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
<mct-container key="overlay" class="t-message-single">
|
||||||
|
<mct-include key="'message'" ng-model="ngModel">
|
||||||
|
</mct-include>
|
||||||
|
</mct-container>
|
@ -0,0 +1,19 @@
|
|||||||
|
<mct-container key="overlay" class="t-message-list">
|
||||||
|
<div class="message-contents">
|
||||||
|
<div class="abs top-bar">
|
||||||
|
<div class="title">{{ngModel.dialog.title}}</div>
|
||||||
|
<div class="hint">Displaying {{ngModel.dialog.messages.length}} message<span ng-show="ngModel.dialog.messages.length > 1">s</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="abs message-body">
|
||||||
|
<mct-include ng-repeat="msg in ngModel.dialog.messages | orderBy: '-'" key="'message'" ng-model="msg"></mct-include>
|
||||||
|
</div>
|
||||||
|
<div class="abs bottom-bar">
|
||||||
|
<a ng-repeat="dialogAction in ngModel.dialog.actions"
|
||||||
|
class="s-btn major"
|
||||||
|
ng-click="dialogAction.action()">
|
||||||
|
{{dialogAction.label}}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</mct-container>
|
@ -24,13 +24,11 @@
|
|||||||
<div class="title">{{ngModel.dialog.title}}</div>
|
<div class="title">{{ngModel.dialog.title}}</div>
|
||||||
<div class="hint">{{ngModel.dialog.hint}}</div>
|
<div class="hint">{{ngModel.dialog.hint}}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="abs form outline editor">
|
<div class='abs editor'>
|
||||||
<div class='abs contents l-dialog'>
|
<mct-include key="ngModel.dialog.template"
|
||||||
<mct-include key="ngModel.dialog.template"
|
parameters="ngModel.dialog.parameters"
|
||||||
parameters="ngModel.dialog.parameters"
|
ng-model="ngModel.dialog.model">
|
||||||
ng-model="ngModel.dialog.model">
|
</mct-include>
|
||||||
</mct-include>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="abs bottom-bar">
|
<div class="abs bottom-bar">
|
||||||
<a ng-repeat="option in ngModel.dialog.options"
|
<a ng-repeat="option in ngModel.dialog.options"
|
||||||
|
@ -22,14 +22,9 @@
|
|||||||
<div class="abs overlay">
|
<div class="abs overlay">
|
||||||
<div class="abs blocker"></div>
|
<div class="abs blocker"></div>
|
||||||
<div class="abs holder">
|
<div class="abs holder">
|
||||||
<a href=""
|
<a ng-click="ngModel.cancel()"
|
||||||
ng-click="ngModel.cancel()"
|
|
||||||
ng-if="ngModel.cancel"
|
ng-if="ngModel.cancel"
|
||||||
class="clk-icon icon ui-symbol close">
|
class="clk-icon icon ui-symbol close">x</a>
|
||||||
x
|
<div class="abs contents" ng-transclude></div>
|
||||||
</a>
|
|
||||||
<div class="abs contents" ng-transclude>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
@ -55,7 +55,7 @@ define(
|
|||||||
this.dialogVisible = false;
|
this.dialogVisible = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
DialogService.prototype.getDialogResponse = function (key, model, resultGetter) {
|
DialogService.prototype.getDialogResponse = function (key, model, resultGetter, typeClass) {
|
||||||
// We will return this result as a promise, because user
|
// We will return this result as a promise, because user
|
||||||
// input is asynchronous.
|
// input is asynchronous.
|
||||||
var deferred = this.$q.defer(),
|
var deferred = this.$q.defer(),
|
||||||
@ -84,27 +84,20 @@ define(
|
|||||||
model.confirm = confirm;
|
model.confirm = confirm;
|
||||||
model.cancel = cancel;
|
model.cancel = cancel;
|
||||||
|
|
||||||
if (this.dialogVisible) {
|
if (this.canShowDialog(model)) {
|
||||||
// Only one dialog should be shown at a time.
|
|
||||||
// The application design should be such that
|
|
||||||
// we never even try to do this.
|
|
||||||
this.$log.warn([
|
|
||||||
"Dialog already showing; ",
|
|
||||||
"unable to show ",
|
|
||||||
model.name
|
|
||||||
].join(""));
|
|
||||||
deferred.reject();
|
|
||||||
} else {
|
|
||||||
// Add the overlay using the OverlayService, which
|
// Add the overlay using the OverlayService, which
|
||||||
// will handle actual insertion into the DOM
|
// will handle actual insertion into the DOM
|
||||||
this.overlay = this.overlayService.createOverlay(
|
this.overlay = this.overlayService.createOverlay(
|
||||||
key,
|
key,
|
||||||
model
|
model,
|
||||||
|
typeClass || "t-dialog"
|
||||||
);
|
);
|
||||||
|
|
||||||
// Track that a dialog is already visible, to
|
// Track that a dialog is already visible, to
|
||||||
// avoid spawning multiple dialogs at once.
|
// avoid spawning multiple dialogs at once.
|
||||||
this.dialogVisible = true;
|
this.dialogVisible = true;
|
||||||
|
} else {
|
||||||
|
deferred.reject();
|
||||||
}
|
}
|
||||||
|
|
||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
@ -157,6 +150,99 @@ define(
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests if a dialog can be displayed. A modal dialog may only be
|
||||||
|
* displayed if one is not already visible.
|
||||||
|
* Will log a warning message if it can't display a dialog.
|
||||||
|
* @returns {boolean} true if dialog is currently visible, false
|
||||||
|
* otherwise
|
||||||
|
*/
|
||||||
|
DialogService.prototype.canShowDialog = function(dialogModel){
|
||||||
|
if (this.dialogVisible){
|
||||||
|
// Only one dialog should be shown at a time.
|
||||||
|
// The application design should be such that
|
||||||
|
// we never even try to do this.
|
||||||
|
this.$log.warn([
|
||||||
|
"Dialog already showing; ",
|
||||||
|
"unable to show ",
|
||||||
|
dialogModel.title
|
||||||
|
].join(""));
|
||||||
|
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A user action that can be performed from a blocking dialog. These
|
||||||
|
* actions will be rendered as buttons within a blocking dialog.
|
||||||
|
*
|
||||||
|
* @typedef DialogOption
|
||||||
|
* @property {string} label a label to be displayed as the button
|
||||||
|
* text for this action
|
||||||
|
* @property {function} action a function to be called when the
|
||||||
|
* button is clicked
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A description of the model options that may be passed to the
|
||||||
|
* showBlockingMessage method. Note that the DialogModel desribed
|
||||||
|
* here is shared with the Notifications framework.
|
||||||
|
* @see NotificationService
|
||||||
|
*
|
||||||
|
* @typedef DialogModel
|
||||||
|
* @property {string} title the title to use for the dialog
|
||||||
|
* @property {string} severity the severity level of this message.
|
||||||
|
* These are defined in a bundle constant with key 'dialogSeverity'
|
||||||
|
* @property {string} hint the 'hint' message to show below the title
|
||||||
|
* @property {string} actionText text that indicates a current action,
|
||||||
|
* shown above a progress bar to indicate what's happening.
|
||||||
|
* @property {number} progress a percentage value (1-100)
|
||||||
|
* indicating the completion of the blocking task
|
||||||
|
* @property {string} progressText the message to show below a
|
||||||
|
* progress bar to indicate progress. For example, this might be
|
||||||
|
* used to indicate time remaining, or items still to process.
|
||||||
|
* @property {boolean} unknownProgress some tasks may be
|
||||||
|
* impossible to provide an estimate for. Providing a true value for
|
||||||
|
* this attribute will indicate to the user that the progress and
|
||||||
|
* duration cannot be estimated.
|
||||||
|
* @property {DialogOption} primaryOption an action that will
|
||||||
|
* be added to the dialog as a button. The primary action can be
|
||||||
|
* used as the suggested course of action for the user. Making it
|
||||||
|
* distinct from other actions allows it to be styled differently,
|
||||||
|
* and treated preferentially in banner mode.
|
||||||
|
* @property {DialogOption[]} options a list of actions that will
|
||||||
|
* be added to the dialog as buttons.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a blocking (modal) dialog. This dialog can be used for
|
||||||
|
* displaying messages that require the user's
|
||||||
|
* immediate attention. The message may include an indication of
|
||||||
|
* progress, as well as a series of actions that
|
||||||
|
* the user can take if necessary
|
||||||
|
* @param {DialogModel} dialogModel defines options for the dialog
|
||||||
|
* @param {typeClass} string tells overlayService that this overlay should use appropriate CSS class
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
DialogService.prototype.showBlockingMessage = function(dialogModel) {
|
||||||
|
if (this.canShowDialog(dialogModel)) {
|
||||||
|
// Add the overlay using the OverlayService, which
|
||||||
|
// will handle actual insertion into the DOM
|
||||||
|
this.overlay = this.overlayService.createOverlay(
|
||||||
|
"overlay-blocking-message",
|
||||||
|
dialogModel,
|
||||||
|
"t-dialog-sm"
|
||||||
|
);
|
||||||
|
// Track that a dialog is already visible, to
|
||||||
|
// avoid spawning multiple dialogs at once.
|
||||||
|
this.dialogVisible = true;
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return DialogService;
|
return DialogService;
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ define(
|
|||||||
|
|
||||||
// Template to inject into the DOM to show the dialog; really just points to
|
// Template to inject into the DOM to show the dialog; really just points to
|
||||||
// the a specific template that can be included via mct-include
|
// the a specific template that can be included via mct-include
|
||||||
var TEMPLATE = '<mct-include ng-model="overlay" key="key"></mct-include>';
|
var TEMPLATE = '<mct-include ng-model="overlay" key="key" ng-class="typeClass"></mct-include>';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -71,8 +71,11 @@ define(
|
|||||||
* @param {object} overlayModel the model to pass to the
|
* @param {object} overlayModel the model to pass to the
|
||||||
* included overlay template (this will be passed
|
* included overlay template (this will be passed
|
||||||
* in via ng-model)
|
* in via ng-model)
|
||||||
|
* @param {string} typeClass the element class to use in rendering
|
||||||
|
* the overlay. Can be specified to provide custom styling of
|
||||||
|
* overlays
|
||||||
*/
|
*/
|
||||||
OverlayService.prototype.createOverlay = function (key, overlayModel) {
|
OverlayService.prototype.createOverlay = function (key, overlayModel, typeClass) {
|
||||||
// Create a new scope for this overlay
|
// Create a new scope for this overlay
|
||||||
var scope = this.newScope(),
|
var scope = this.newScope(),
|
||||||
element;
|
element;
|
||||||
@ -90,6 +93,7 @@ define(
|
|||||||
// Populate the scope; will be passed directly to the template
|
// Populate the scope; will be passed directly to the template
|
||||||
scope.overlay = overlayModel;
|
scope.overlay = overlayModel;
|
||||||
scope.key = key;
|
scope.key = key;
|
||||||
|
scope.typeClass = typeClass || 't-dialog';
|
||||||
|
|
||||||
// Create the overlay element and add it to the document's body
|
// Create the overlay element and add it to the document's body
|
||||||
element = this.$compile(TEMPLATE)(scope);
|
element = this.$compile(TEMPLATE)(scope);
|
||||||
|
@ -116,10 +116,22 @@ define(
|
|||||||
dialog: dialogModel,
|
dialog: dialogModel,
|
||||||
confirm: jasmine.any(Function),
|
confirm: jasmine.any(Function),
|
||||||
cancel: jasmine.any(Function)
|
cancel: jasmine.any(Function)
|
||||||
}
|
},
|
||||||
|
't-dialog'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("invokes the overlay service with the correct parameters when" +
|
||||||
|
" a blocking dialog is requested", function() {
|
||||||
|
var dialogModel = {};
|
||||||
|
expect(dialogService.showBlockingMessage(dialogModel)).toBe(true);
|
||||||
|
expect(mockOverlayService.createOverlay).toHaveBeenCalledWith(
|
||||||
|
"overlay-blocking-message",
|
||||||
|
dialogModel,
|
||||||
|
"t-dialog-sm"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
26
platform/commonUI/formats/bundle.json
Normal file
26
platform/commonUI/formats/bundle.json
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"name": "Time services bundle",
|
||||||
|
"description": "Defines interfaces and provides default implementations for handling different time systems.",
|
||||||
|
"extensions": {
|
||||||
|
"components": [
|
||||||
|
{
|
||||||
|
"provides": "formatService",
|
||||||
|
"type": "provider",
|
||||||
|
"implementation": "FormatProvider.js",
|
||||||
|
"depends": [ "formats[]" ]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"formats": [
|
||||||
|
{
|
||||||
|
"key": "utc",
|
||||||
|
"implementation": "UTCTimeFormat.js"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"constants": [
|
||||||
|
{
|
||||||
|
"key": "DEFAULT_TIME_FORMAT",
|
||||||
|
"value": "utc"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
114
platform/commonUI/formats/src/FormatProvider.js
Normal file
114
platform/commonUI/formats/src/FormatProvider.js
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT Web includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
/*global define*/
|
||||||
|
|
||||||
|
define([
|
||||||
|
|
||||||
|
], function (
|
||||||
|
|
||||||
|
) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object used to convert between numeric values and text values,
|
||||||
|
* typically used to display these values to the user and to convert
|
||||||
|
* user input to a numeric format, particularly for time formats.
|
||||||
|
* @interface {Format}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse text (typically user input) to a numeric value.
|
||||||
|
* Behavior is undefined when the text cannot be parsed;
|
||||||
|
* `validate` should be called first if the text may be invalid.
|
||||||
|
* @method parse
|
||||||
|
* @memberof Format#
|
||||||
|
* @param {string} text the text to parse
|
||||||
|
* @returns {number} the parsed numeric value
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether or not some text (typically user input) can
|
||||||
|
* be parsed to a numeric value by this format.
|
||||||
|
* @method validate
|
||||||
|
* @memberof Format#
|
||||||
|
* @param {string} text the text to parse
|
||||||
|
* @returns {boolean} true if the text can be parsed
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a numeric value to a text value for display using
|
||||||
|
* this format.
|
||||||
|
* @method format
|
||||||
|
* @memberof Format#
|
||||||
|
* @param {number} value the numeric value to format
|
||||||
|
* @returns {string} the text representation of the value
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides access to `Format` objects which can be used to
|
||||||
|
* convert values between human-readable text and numeric
|
||||||
|
* representations.
|
||||||
|
* @interface FormatService
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Look up a format by its symbolic identifier.
|
||||||
|
* @method getFormat
|
||||||
|
* @memberof FormatService#
|
||||||
|
* @param {string} key the identifier for this format
|
||||||
|
* @returns {Format} the format
|
||||||
|
* @throws {Error} errors when the requested format is unrecognized
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides formats from the `formats` extension category.
|
||||||
|
* @constructor
|
||||||
|
* @implements {FormatService}
|
||||||
|
* @memberof platform/commonUI/formats
|
||||||
|
* @param {Array.<function(new : Format)>} format constructors,
|
||||||
|
* from the `formats` extension category.
|
||||||
|
*/
|
||||||
|
function FormatProvider(formats) {
|
||||||
|
var formatMap = {};
|
||||||
|
|
||||||
|
function addToMap(Format) {
|
||||||
|
var key = Format.key;
|
||||||
|
if (key && !formatMap[key]) {
|
||||||
|
formatMap[key] = new Format();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
formats.forEach(addToMap);
|
||||||
|
this.formatMap = formatMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
FormatProvider.prototype.getFormat = function (key) {
|
||||||
|
var format = this.formatMap[key];
|
||||||
|
if (!format) {
|
||||||
|
throw new Error("FormatProvider: No format found for " + key);
|
||||||
|
}
|
||||||
|
return format;
|
||||||
|
};
|
||||||
|
|
||||||
|
return FormatProvider;
|
||||||
|
|
||||||
|
});
|
63
platform/commonUI/formats/src/UTCTimeFormat.js
Normal file
63
platform/commonUI/formats/src/UTCTimeFormat.js
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT Web includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
/*global define*/
|
||||||
|
|
||||||
|
define([
|
||||||
|
'moment'
|
||||||
|
], function (
|
||||||
|
moment
|
||||||
|
) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var DATE_FORMAT = "YYYY-MM-DD HH:mm:ss",
|
||||||
|
DATE_FORMATS = [
|
||||||
|
DATE_FORMAT,
|
||||||
|
"YYYY-MM-DD HH:mm",
|
||||||
|
"YYYY-MM-DD"
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formatter for UTC timestamps. Interprets numeric values as
|
||||||
|
* milliseconds since the start of 1970.
|
||||||
|
*
|
||||||
|
* @implements {Format}
|
||||||
|
* @constructor
|
||||||
|
* @memberof platform/commonUI/formats
|
||||||
|
*/
|
||||||
|
function UTCTimeFormat() {
|
||||||
|
}
|
||||||
|
|
||||||
|
UTCTimeFormat.prototype.format = function (value) {
|
||||||
|
return moment.utc(value).format(DATE_FORMAT);
|
||||||
|
};
|
||||||
|
|
||||||
|
UTCTimeFormat.prototype.parse = function (text) {
|
||||||
|
return moment.utc(text, DATE_FORMATS).valueOf();
|
||||||
|
};
|
||||||
|
|
||||||
|
UTCTimeFormat.prototype.validate = function (text) {
|
||||||
|
return moment.utc(text, DATE_FORMATS).isValid();
|
||||||
|
};
|
||||||
|
|
||||||
|
return UTCTimeFormat;
|
||||||
|
});
|
68
platform/commonUI/formats/test/FormatProviderSpec.js
Normal file
68
platform/commonUI/formats/test/FormatProviderSpec.js
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT Web includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
['../src/FormatProvider'],
|
||||||
|
function (FormatProvider) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var KEYS = [ 'a', 'b', 'c' ];
|
||||||
|
|
||||||
|
describe("The FormatProvider", function () {
|
||||||
|
var mockFormats,
|
||||||
|
mockLog,
|
||||||
|
mockFormatInstances,
|
||||||
|
provider;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
mockFormatInstances = KEYS.map(function (k) {
|
||||||
|
return jasmine.createSpyObj(
|
||||||
|
'format-' + k,
|
||||||
|
[ 'parse', 'validate', 'format' ]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
// Return constructors
|
||||||
|
mockFormats = KEYS.map(function (k, i) {
|
||||||
|
function MockFormat() { return mockFormatInstances[i]; }
|
||||||
|
MockFormat.key = k;
|
||||||
|
return MockFormat;
|
||||||
|
});
|
||||||
|
provider = new FormatProvider(mockFormats);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("looks up formats by key", function () {
|
||||||
|
KEYS.forEach(function (k, i) {
|
||||||
|
expect(provider.getFormat(k))
|
||||||
|
.toEqual(mockFormatInstances[i]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("throws an error about unknown formats", function () {
|
||||||
|
expect(function () {
|
||||||
|
provider.getFormat('some-unknown-format');
|
||||||
|
}).toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
56
platform/commonUI/formats/test/UTCTimeFormatSpec.js
Normal file
56
platform/commonUI/formats/test/UTCTimeFormatSpec.js
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT Web includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
['../src/UTCTimeFormat', 'moment'],
|
||||||
|
function (UTCTimeFormat, moment) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
describe("The UTCTimeFormat", function () {
|
||||||
|
var format;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
format = new UTCTimeFormat();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("formats UTC timestamps", function () {
|
||||||
|
var timestamp = 12345670000,
|
||||||
|
formatted = format.format(timestamp);
|
||||||
|
expect(formatted).toEqual(jasmine.any(String));
|
||||||
|
expect(moment.utc(formatted).valueOf()).toEqual(timestamp);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("validates time inputs", function () {
|
||||||
|
expect(format.validate("1977-05-25 11:21:22")).toBe(true);
|
||||||
|
expect(format.validate("garbage text")).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("parses valid input", function () {
|
||||||
|
var text = "1977-05-25 11:21:22",
|
||||||
|
parsed = format.parse(text);
|
||||||
|
expect(parsed).toEqual(jasmine.any(Number));
|
||||||
|
expect(parsed).toEqual(moment.utc(text).valueOf());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
4
platform/commonUI/formats/test/suite.json
Normal file
4
platform/commonUI/formats/test/suite.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
[
|
||||||
|
"FormatProvider",
|
||||||
|
"UTCTimeFormat"
|
||||||
|
]
|
@ -44,6 +44,14 @@
|
|||||||
"key": "indicator",
|
"key": "indicator",
|
||||||
"templateUrl": "templates/indicator.html"
|
"templateUrl": "templates/indicator.html"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"key": "message-banner",
|
||||||
|
"templateUrl": "templates/message-banner.html"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "progress-bar",
|
||||||
|
"templateUrl": "templates/progress-bar.html"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"key": "time-controller",
|
"key": "time-controller",
|
||||||
"templateUrl": "templates/controls/time-controller.html"
|
"templateUrl": "templates/controls/time-controller.html"
|
||||||
@ -53,13 +61,18 @@
|
|||||||
{
|
{
|
||||||
"key": "TimeRangeController",
|
"key": "TimeRangeController",
|
||||||
"implementation": "controllers/TimeRangeController.js",
|
"implementation": "controllers/TimeRangeController.js",
|
||||||
"depends": [ "$scope", "now" ]
|
"depends": [ "$scope", "formatService", "DEFAULT_TIME_FORMAT", "now" ]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "DateTimePickerController",
|
"key": "DateTimePickerController",
|
||||||
"implementation": "controllers/DateTimePickerController.js",
|
"implementation": "controllers/DateTimePickerController.js",
|
||||||
"depends": [ "$scope", "now" ]
|
"depends": [ "$scope", "now" ]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"key": "DateTimeFieldController",
|
||||||
|
"implementation": "controllers/DateTimeFieldController.js",
|
||||||
|
"depends": [ "$scope", "formatService", "DEFAULT_TIME_FORMAT" ]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"key": "TreeNodeController",
|
"key": "TreeNodeController",
|
||||||
"implementation": "controllers/TreeNodeController.js",
|
"implementation": "controllers/TreeNodeController.js",
|
||||||
@ -112,6 +125,11 @@
|
|||||||
"key": "ObjectInspectorController",
|
"key": "ObjectInspectorController",
|
||||||
"implementation": "controllers/ObjectInspectorController.js",
|
"implementation": "controllers/ObjectInspectorController.js",
|
||||||
"depends": [ "$scope", "objectService" ]
|
"depends": [ "$scope", "objectService" ]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "BannerController",
|
||||||
|
"implementation": "controllers/BannerController.js",
|
||||||
|
"depends": ["$scope", "notificationService", "dialogService"]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"directives": [
|
"directives": [
|
||||||
@ -251,6 +269,10 @@
|
|||||||
{
|
{
|
||||||
"key": "datetime-picker",
|
"key": "datetime-picker",
|
||||||
"templateUrl": "templates/controls/datetime-picker.html"
|
"templateUrl": "templates/controls/datetime-picker.html"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "datetime-field",
|
||||||
|
"templateUrl": "templates/controls/datetime-field.html"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"licenses": [
|
"licenses": [
|
||||||
|
@ -51,8 +51,8 @@ $ueEditLeftPaneW: 75%;
|
|||||||
$treeSearchInputBarH: 25px;
|
$treeSearchInputBarH: 25px;
|
||||||
$ueTimeControlH: (33px, 20px, 20px);
|
$ueTimeControlH: (33px, 20px, 20px);
|
||||||
// Overlay
|
// Overlay
|
||||||
$ovrTopBarH: 60px;
|
$ovrTopBarH: 45px;
|
||||||
$ovrFooterH: 30px;
|
$ovrFooterH: 24px;
|
||||||
$overlayMargin: 25px;
|
$overlayMargin: 25px;
|
||||||
// Items
|
// Items
|
||||||
$ueBrowseGridItemLg: 200px;
|
$ueBrowseGridItemLg: 200px;
|
||||||
|
@ -29,7 +29,6 @@
|
|||||||
@import "helpers/bubbles";
|
@import "helpers/bubbles";
|
||||||
@import "helpers/splitter";
|
@import "helpers/splitter";
|
||||||
@import "helpers/wait-spinner";
|
@import "helpers/wait-spinner";
|
||||||
@import "messages";
|
|
||||||
@import "inspector";
|
@import "inspector";
|
||||||
|
|
||||||
/********************************* CONTROLS */
|
/********************************* CONTROLS */
|
||||||
@ -39,6 +38,7 @@
|
|||||||
@import "controls/controls";
|
@import "controls/controls";
|
||||||
@import "controls/lists";
|
@import "controls/lists";
|
||||||
@import "controls/menus";
|
@import "controls/menus";
|
||||||
|
@import "controls/messages";
|
||||||
@import "controls/time-controller";
|
@import "controls/time-controller";
|
||||||
@import "mobile/controls/menus";
|
@import "mobile/controls/menus";
|
||||||
|
|
||||||
@ -62,7 +62,6 @@
|
|||||||
@import "mobile/tree";
|
@import "mobile/tree";
|
||||||
@import "user-environ/frame";
|
@import "user-environ/frame";
|
||||||
@import "user-environ/top-bar";
|
@import "user-environ/top-bar";
|
||||||
@import "user-environ/bottom-bar";
|
|
||||||
@import "user-environ/tool-bar";
|
@import "user-environ/tool-bar";
|
||||||
|
|
||||||
/********************************* VIEWS */
|
/********************************* VIEWS */
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
/* Styles for messages */
|
|
||||||
|
|
||||||
.message {
|
|
||||||
&.block {
|
|
||||||
@include border-radius($basicCr);
|
|
||||||
padding: $interiorMarginLg;
|
|
||||||
}
|
|
||||||
&.error {
|
|
||||||
background-color: rgba($colorAlert,0.3);
|
|
||||||
color: lighten($colorAlert, 20%);
|
|
||||||
}
|
|
||||||
}
|
|
@ -99,7 +99,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@mixin triangle($dir: "left", $size: 5px, $ratio: 1, $color: red) {
|
@mixin triangle($dir: "left", $size: 5px, $ratio: 1, $color: red) {
|
||||||
//$size: $size*2;
|
|
||||||
width: 0;
|
width: 0;
|
||||||
height: 0;
|
height: 0;
|
||||||
$slopedB: $size/$ratio solid transparent;
|
$slopedB: $size/$ratio solid transparent;
|
||||||
@ -134,6 +133,24 @@
|
|||||||
background-size: $d $d;
|
background-size: $d $d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@mixin bgVertStripes($c: yellow, $a: 0.1, $d: 40px) {
|
||||||
|
@include background-image(linear-gradient(-90deg,
|
||||||
|
rgba($c, $a) 0%, rgba($c, $a) 50%,
|
||||||
|
transparent 50%, transparent 100%
|
||||||
|
));
|
||||||
|
background-repeat: repeat;
|
||||||
|
background-size: $d $d;
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin bgVertFuzzyStripes($c: yellow, $a: 0.1, $d: 40px) {
|
||||||
|
@include background-image(linear-gradient(-90deg,
|
||||||
|
rgba($c, $a) 0%, transparent 50%,
|
||||||
|
transparent 50%, rgba($c, $a) 100%
|
||||||
|
));
|
||||||
|
background-repeat: repeat;
|
||||||
|
background-size: $d $d;
|
||||||
|
}
|
||||||
|
|
||||||
@mixin bgTicks($c: $colorBodyFg, $repeatDir: 'x') {
|
@mixin bgTicks($c: $colorBodyFg, $repeatDir: 'x') {
|
||||||
$deg: 90deg;
|
$deg: 90deg;
|
||||||
@if ($repeatDir != 'x') {
|
@if ($repeatDir != 'x') {
|
||||||
@ -409,4 +426,4 @@
|
|||||||
@mixin s-stale($a: 0.5) {
|
@mixin s-stale($a: 0.5) {
|
||||||
color: rgba($colorTelemFresh, $a) !important;
|
color: rgba($colorTelemFresh, $a) !important;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
@ -19,34 +19,6 @@
|
|||||||
* 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.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
/*.control {
|
|
||||||
// UNUSED?
|
|
||||||
&.view-control {
|
|
||||||
.icon {
|
|
||||||
display: inline-block;
|
|
||||||
margin: -1px 5px 1px 2px;
|
|
||||||
vertical-align: middle;
|
|
||||||
&.triangle-down {
|
|
||||||
margin: 2px 2px -2px 0px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.label {
|
|
||||||
display: inline-block;
|
|
||||||
font-size: 11px;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toggle {
|
|
||||||
@include border-radius(3px);
|
|
||||||
display: inline-block;
|
|
||||||
padding: 1px 6px 4px 4px;
|
|
||||||
&:hover {
|
|
||||||
background: rgba(white, 0.1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
.accordion {
|
.accordion {
|
||||||
$accordionHeadH: 18px;
|
$accordionHeadH: 18px;
|
||||||
@ -291,6 +263,88 @@ label.checkbox.custom {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/******************************************************** PROGRESS BAR */
|
||||||
|
@include keyframes(progress) {
|
||||||
|
100% { background-position: $progressBarStripeW center; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin bgProgressAnim($c: yellow, $a: 0.1, $d: 20px) {
|
||||||
|
@include background-image(linear-gradient(-90deg,
|
||||||
|
rgba($c, $a) 0%, transparent 50%,
|
||||||
|
transparent 50%, rgba($c, $a) 100%
|
||||||
|
));
|
||||||
|
background-position: 0 center;
|
||||||
|
background-repeat: repeat-x;
|
||||||
|
background-size: $d 40%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.l-progress-bar {
|
||||||
|
// Assume will be determinate by default
|
||||||
|
display: inline-block;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.progress-amt-holder {
|
||||||
|
@include absPosDefault(1px);
|
||||||
|
}
|
||||||
|
.progress-amt,
|
||||||
|
.progress-amt:before,
|
||||||
|
.progress-amt:after {
|
||||||
|
@include absPosDefault();
|
||||||
|
display: block;
|
||||||
|
content: '';
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-amt {
|
||||||
|
right: auto; // Allow inline width to control }
|
||||||
|
}
|
||||||
|
|
||||||
|
&.indeterminate {
|
||||||
|
.progress-amt {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.s-progress-bar {
|
||||||
|
@include border-radius($basicCr);
|
||||||
|
@include boxIncised(0.3, 4px);
|
||||||
|
background: $colorProgressBarOuter;
|
||||||
|
//border:1px solid $colorProgressBarOuter;
|
||||||
|
.progress-amt {
|
||||||
|
@include border-radius($basicCr);
|
||||||
|
@include boxShdw();
|
||||||
|
@include border-radius($basicCr - 1);
|
||||||
|
@include trans-prop-nice(width);
|
||||||
|
&:before {
|
||||||
|
background-color: $colorProgressBarAmt;
|
||||||
|
}
|
||||||
|
&:after {
|
||||||
|
// Sheen
|
||||||
|
@include background-image(linear-gradient(
|
||||||
|
transparent 5%, rgba(#fff,0.25) 30%, transparent 100%
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(.indeterminate) {
|
||||||
|
.progress-amt:before {
|
||||||
|
// More subtle anim for determinate progress
|
||||||
|
@include animation(progress .4s linear infinite);
|
||||||
|
@include bgProgressAnim(#fff, 0.1, $progressBarStripeW);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.indeterminate .progress-amt {
|
||||||
|
&:before {
|
||||||
|
// More visible std diag stripe anim for indeterminate progress
|
||||||
|
@include animation(progress .6s linear infinite);
|
||||||
|
@include bgDiagonalStripes(#fff, 0.2, $progressBarStripeW);
|
||||||
|
}
|
||||||
|
&:after { display: none; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/******************************************************** SLIDERS */
|
/******************************************************** SLIDERS */
|
||||||
|
|
||||||
.slider {
|
.slider {
|
||||||
|
306
platform/commonUI/general/res/sass/controls/_messages.scss
Normal file
306
platform/commonUI/general/res/sass/controls/_messages.scss
Normal file
@ -0,0 +1,306 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
@mixin statusBannerColors($bg, $fg: $colorStatusFg) {
|
||||||
|
$bgPb: 30%;
|
||||||
|
$bgPbD: 10%;
|
||||||
|
background-color: darken($bg, $bgPb);
|
||||||
|
color: $fg;
|
||||||
|
&:hover {
|
||||||
|
background-color: darken($bg, $bgPb - $bgPbD);
|
||||||
|
}
|
||||||
|
.s-action {
|
||||||
|
background-color: darken($bg, $bgPb + $bgPbD);
|
||||||
|
&:hover {
|
||||||
|
background-color: darken($bg, $bgPb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.status.block {
|
||||||
|
color: $colorStatusDefault;
|
||||||
|
cursor: default;
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: $interiorMargin;
|
||||||
|
.status-indicator,
|
||||||
|
.label,
|
||||||
|
.count {
|
||||||
|
//@include test(#00ff00);
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
.status-indicator {
|
||||||
|
margin-right: $interiorMarginSm;
|
||||||
|
}
|
||||||
|
&.ok .status-indicator,
|
||||||
|
&.info .status-indicator {
|
||||||
|
color: $colorStatusInfo;
|
||||||
|
}
|
||||||
|
&.alert .status-indicator,
|
||||||
|
&.warning .status-indicator,
|
||||||
|
&.caution .status-indicator {
|
||||||
|
color: $colorStatusAlert;
|
||||||
|
}
|
||||||
|
&.error .status-indicator {
|
||||||
|
color: $colorStatusError;
|
||||||
|
}
|
||||||
|
.label {
|
||||||
|
// Max-width silliness is necessary for width transition
|
||||||
|
@include trans-prop-nice(max-width, .25s);
|
||||||
|
overflow: hidden;
|
||||||
|
max-width: 0px;
|
||||||
|
}
|
||||||
|
.count {
|
||||||
|
@include trans-prop-nice(opacity, .25s);
|
||||||
|
font-weight: bold;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
.label {
|
||||||
|
max-width: 450px;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
.count {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Styles for messages and message banners */
|
||||||
|
.message {
|
||||||
|
&.block {
|
||||||
|
@include border-radius($basicCr);
|
||||||
|
padding: $interiorMarginLg;
|
||||||
|
}
|
||||||
|
&.error {
|
||||||
|
background-color: rgba($colorAlert,0.3);
|
||||||
|
color: lighten($colorAlert, 20%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.l-message-banner {
|
||||||
|
$m: $interiorMarginSm;
|
||||||
|
$lh: $ueFooterH - ($m*2) - 1;
|
||||||
|
@include box-sizing(border-box);
|
||||||
|
@include ellipsize();
|
||||||
|
@include display-flex;
|
||||||
|
@include flex-direction(row);
|
||||||
|
@include align-items(center);
|
||||||
|
position: absolute;
|
||||||
|
top: $m; right: auto; bottom: $m; left: 50%;
|
||||||
|
height: auto; width: auto;
|
||||||
|
line-height: $lh;
|
||||||
|
max-width: 300px;
|
||||||
|
padding: 0 $interiorMargin 0 $interiorMargin;
|
||||||
|
@include transform(translateX(-50%));
|
||||||
|
|
||||||
|
&.minimized {
|
||||||
|
@include transition-property(left, opacity);
|
||||||
|
@include transition-duration(0.3s);
|
||||||
|
@include transition-timing-function(ease-in-out);
|
||||||
|
left: 0;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.new {
|
||||||
|
left: 50%;
|
||||||
|
opacity: 1;
|
||||||
|
&:not(.info) {
|
||||||
|
@include pulse(100ms, 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.banner-elem {
|
||||||
|
@include flex(0 1 auto);
|
||||||
|
margin-left: $interiorMargin;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.l-action {
|
||||||
|
line-height: $lh - 3;
|
||||||
|
padding: 0 $interiorMargin;
|
||||||
|
}
|
||||||
|
.close {
|
||||||
|
//@include test(red, 0.7);
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 7px;
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
.l-progress-bar {
|
||||||
|
$h: $lh - 10;
|
||||||
|
height: $h;
|
||||||
|
line-height: $h;
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
.progress-info { display: none; }
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.s-message-banner {
|
||||||
|
//@include transition-property(left, opacity);
|
||||||
|
//@include transition-duration(0.35s);
|
||||||
|
//@include transition-timing-function(ease-in-out);
|
||||||
|
}
|
||||||
|
|
||||||
|
.s-message-banner {
|
||||||
|
@include border-radius($controlCr);
|
||||||
|
@include statusBannerColors($colorStatusDefault, $colorStatusFg);
|
||||||
|
cursor: pointer;
|
||||||
|
a { color: inherit; }
|
||||||
|
.s-action {
|
||||||
|
@include border-radius($basicCr);
|
||||||
|
@include trans-prop-nice(background-color);
|
||||||
|
}
|
||||||
|
.close {
|
||||||
|
opacity: 0.5;
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.ok,
|
||||||
|
&.info {
|
||||||
|
@include statusBannerColors($colorStatusInfo);
|
||||||
|
}
|
||||||
|
&.caution,
|
||||||
|
&.warning,
|
||||||
|
&.alert {
|
||||||
|
@include statusBannerColors($colorStatusAlert);
|
||||||
|
}
|
||||||
|
&.error {
|
||||||
|
@include statusBannerColors($colorStatusError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin messageBlock($iconW: 32px) {
|
||||||
|
.type-icon.message-type {
|
||||||
|
@include txtShdw($shdwStatusIc);
|
||||||
|
&:before { content:"\e608"; }
|
||||||
|
color: $colorStatusDefault;
|
||||||
|
font-size: $iconW;
|
||||||
|
padding: 1px;
|
||||||
|
width: $iconW + 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-severity-info .type-icon.message-type {
|
||||||
|
&:before { content:"\e608"; }
|
||||||
|
color: $colorStatusInfo;
|
||||||
|
}
|
||||||
|
.message-severity-alert .type-icon.message-type {
|
||||||
|
&:before { content:"\e610"; }
|
||||||
|
color: $colorStatusAlert;
|
||||||
|
}
|
||||||
|
.message-severity-error .type-icon.message-type {
|
||||||
|
&:before { content:"\21"; }
|
||||||
|
color: $colorStatusError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Paths:
|
||||||
|
t-dialog | t-dialog-sm > t-message-single | t-message-list > overlay > holder > contents > l-message >
|
||||||
|
message-type > (icon)
|
||||||
|
message-contents >
|
||||||
|
top-bar >
|
||||||
|
title
|
||||||
|
hint
|
||||||
|
editor >
|
||||||
|
(if displaying list of messages)
|
||||||
|
ul > li > l-message >
|
||||||
|
... same as above
|
||||||
|
bottom-bar
|
||||||
|
*/
|
||||||
|
|
||||||
|
.l-message {
|
||||||
|
@include display-flex;
|
||||||
|
@include flex-direction(row);
|
||||||
|
@include align-items(stretch);
|
||||||
|
.type-icon.message-type {
|
||||||
|
//@include test(red);
|
||||||
|
@include flex(0 1 auto);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.message-contents {
|
||||||
|
//@include test(blue);
|
||||||
|
@include flex(1 1 auto);
|
||||||
|
margin-left: $overlayMargin;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.top-bar,
|
||||||
|
.message-body {
|
||||||
|
margin-bottom: $interiorMarginLg * 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Message as singleton
|
||||||
|
.t-message-single {
|
||||||
|
@include messageBlock(80px);
|
||||||
|
|
||||||
|
@include desktop {
|
||||||
|
.l-message,
|
||||||
|
.bottom-bar {
|
||||||
|
@include absPosDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-bar {
|
||||||
|
top: auto;
|
||||||
|
height: $ovrFooterH;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Messages in list
|
||||||
|
.t-message-list {
|
||||||
|
@include messageBlock(32px);
|
||||||
|
|
||||||
|
.message-contents {
|
||||||
|
.l-message {
|
||||||
|
//border-bottom: 1px solid pullForward($colorOvrBg, 20%);
|
||||||
|
@include border-radius($controlCr);
|
||||||
|
background: rgba($colorOvrFg, 0.1);
|
||||||
|
margin-bottom: $interiorMargin;
|
||||||
|
padding: $interiorMarginLg;
|
||||||
|
|
||||||
|
.message-contents,
|
||||||
|
.bottom-bar {
|
||||||
|
//@include test(green);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-contents {
|
||||||
|
font-size: 0.9em;
|
||||||
|
margin-left: $interiorMarginLg;
|
||||||
|
.message-action { color: pushBack($colorOvrFg, 20%); }
|
||||||
|
.bottom-bar { text-align: left; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-bar,
|
||||||
|
.message-body {
|
||||||
|
margin-bottom: $interiorMarginLg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include desktop {
|
||||||
|
.message-contents .l-message { margin-right: $interiorMarginLg; }
|
||||||
|
}
|
||||||
|
}
|
@ -23,7 +23,7 @@
|
|||||||
/************************** MOBILE REPRESENTATION ITEMS DIMENSIONS */
|
/************************** MOBILE REPRESENTATION ITEMS DIMENSIONS */
|
||||||
$mobileListIconSize: 30px;
|
$mobileListIconSize: 30px;
|
||||||
$mobileTitleDescH: 35px;
|
$mobileTitleDescH: 35px;
|
||||||
$mobileOverlayMargin: 10px;
|
$mobileOverlayMargin: 20px;
|
||||||
$phoneItemH: floor($ueBrowseGridItemLg/4);
|
$phoneItemH: floor($ueBrowseGridItemLg/4);
|
||||||
$tabletItemH: floor($ueBrowseGridItemLg/3);
|
$tabletItemH: floor($ueBrowseGridItemLg/3);
|
||||||
|
|
||||||
|
@ -1,16 +1,12 @@
|
|||||||
@include phoneandtablet {
|
@include phoneandtablet {
|
||||||
.overlay {
|
.overlay {
|
||||||
$m: 0;
|
|
||||||
.clk-icon.close {
|
.clk-icon.close {
|
||||||
top: $mobileOverlayMargin; right: $mobileOverlayMargin;
|
top: $mobileOverlayMargin; right: $mobileOverlayMargin;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .holder {
|
> .holder {
|
||||||
@include border-radius($m);
|
height: 90%; width: 90%;
|
||||||
top: $m;
|
|
||||||
right: $m;
|
|
||||||
bottom: $m;
|
|
||||||
left: $m;
|
|
||||||
> .contents {
|
> .contents {
|
||||||
top: $mobileOverlayMargin;
|
top: $mobileOverlayMargin;
|
||||||
right: $mobileOverlayMargin;
|
right: $mobileOverlayMargin;
|
||||||
@ -22,35 +18,64 @@
|
|||||||
margin-right: 1.2em;
|
margin-right: 1.2em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.form.editor {
|
|
||||||
border: none;
|
|
||||||
|
|
||||||
.contents {
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@include phone {
|
@include phone {
|
||||||
.overlay > .holder > .contents .form.editor .contents .form-row {
|
.overlay > .holder {
|
||||||
> .label,
|
//@include test(orange); // This works!
|
||||||
> .controls {
|
$m: 0;
|
||||||
//@include test(blue);
|
@include border-radius($m);
|
||||||
display: block;
|
top: $m;
|
||||||
float: none;
|
right: $m;
|
||||||
width: 100%;
|
bottom: $m;
|
||||||
|
left: $m;
|
||||||
|
height: auto; width: auto;
|
||||||
|
min-width: 200px; min-height: 200px;
|
||||||
|
max-height: 100%; max-width: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
@include transform(none);
|
||||||
|
|
||||||
|
.editor .form .form-row {
|
||||||
|
> .label,
|
||||||
|
> .controls {
|
||||||
|
//@include test(blue);
|
||||||
|
display: block;
|
||||||
|
float: none;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
> .label {
|
||||||
|
&:after {
|
||||||
|
float: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.contents {
|
||||||
|
.abs.top-bar,
|
||||||
|
.abs.editor,
|
||||||
|
.abs.message-body,
|
||||||
|
.abs.bottom-bar {
|
||||||
|
//@include test(orange);
|
||||||
|
top: auto; right: auto; bottom: auto; left: auto;
|
||||||
|
height: auto; width: auto;
|
||||||
|
margin-bottom: $interiorMarginLg * 2;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.t-dialog-sm .overlay > .holder {
|
||||||
|
//@include test(blue);
|
||||||
|
height: auto; max-height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include phonePortrait {
|
||||||
|
.overlay > .holder {
|
||||||
|
.contents .bottom-bar {
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
> .label {
|
|
||||||
&:after {
|
|
||||||
float: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -20,79 +20,124 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
.overlay {
|
.overlay {
|
||||||
.blocker {
|
font-size: 90%;
|
||||||
background: $colorOvrBlocker;
|
.blocker {
|
||||||
z-index: 100;
|
background: $colorOvrBlocker;
|
||||||
}
|
z-index: 100;
|
||||||
|
}
|
||||||
.clk-icon.close {
|
.clk-icon.close {
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: $interiorMarginLg; right: $interiorMarginLg; bottom: auto; left: auto;
|
top: $interiorMarginLg;
|
||||||
z-index: 100;
|
right: $interiorMarginLg;
|
||||||
|
bottom: auto;
|
||||||
|
left: auto;
|
||||||
|
z-index: 100;
|
||||||
}
|
}
|
||||||
>.holder {
|
> .holder {
|
||||||
$i: 15%;
|
//$i: 15%;
|
||||||
@include containerSubtle($colorOvrBg, $colorOvrFg);
|
@include containerSubtle($colorOvrBg, $colorOvrFg);
|
||||||
@include border-radius($basicCr * 3);
|
@include border-radius($basicCr * 3);
|
||||||
color: $colorOvrFg;
|
color: $colorOvrFg;
|
||||||
top: $i; right: $i; bottom: $i; left: $i;
|
top: 50%;
|
||||||
z-index: 101;
|
right: auto;
|
||||||
>.contents {
|
bottom: auto;
|
||||||
|
left: 50%;
|
||||||
|
@include transform(translateX(-50%) translateY(-50%));
|
||||||
|
height: 70%;
|
||||||
|
width: 50%;
|
||||||
|
min-height: 300px;
|
||||||
|
max-height: 800px;
|
||||||
|
min-width: 600px;
|
||||||
|
max-width: 1000px;
|
||||||
|
z-index: 101;
|
||||||
|
> .contents {
|
||||||
$m: $overlayMargin;
|
$m: $overlayMargin;
|
||||||
top: $m; right: $m; bottom: $m; left: $m;
|
top: $m;
|
||||||
|
right: $m;
|
||||||
|
bottom: $m;
|
||||||
|
left: $m;
|
||||||
|
|
||||||
|
//.top-bar,
|
||||||
|
//.editor,
|
||||||
|
//.bottom-bar {
|
||||||
|
// @include absPosDefault();
|
||||||
|
//}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
.title {
|
|
||||||
@include ellipsize();
|
|
||||||
font-size: 1.2em;
|
|
||||||
margin-bottom: $interiorMargin;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.top-bar {
|
.title {
|
||||||
|
@include ellipsize();
|
||||||
|
font-size: 1.2em;
|
||||||
|
line-height: 120%;
|
||||||
|
margin-bottom: $interiorMargin;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hint {
|
||||||
|
color: pushBack($colorOvrFg, 20%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.abs.top-bar {
|
||||||
height: $ovrTopBarH;
|
height: $ovrTopBarH;
|
||||||
}
|
}
|
||||||
|
|
||||||
.editor {
|
.abs.editor,
|
||||||
top: $ovrTopBarH + ($interiorMargin * 2);
|
.abs.message-body {
|
||||||
bottom: $ovrFooterH + $interiorMargin * 2;
|
top: $ovrTopBarH + $interiorMarginLg;
|
||||||
left: 0; right: 0;
|
bottom: $ovrFooterH + $interiorMarginLg;
|
||||||
}
|
left: 0;
|
||||||
|
right: 0;
|
||||||
.bottom-bar {
|
overflow: auto;
|
||||||
top: auto; right: 0; bottom: 0; left: 0;
|
.field.l-med {
|
||||||
overflow: visible;
|
input[type='text'] {
|
||||||
//font-size: 1em;
|
width: 100%;
|
||||||
height: $ovrFooterH;
|
}
|
||||||
text-align: right;
|
|
||||||
.s-btn {
|
|
||||||
$bg: $colorOvrBtnBg;
|
|
||||||
&:not(.major) {
|
|
||||||
@include btnSubtle($bg, pullForward($bg, 10%), $colorOvrBtnFg, $colorOvrBtnFg);
|
|
||||||
}
|
|
||||||
font-size: 95%;
|
|
||||||
height: $ovrFooterH;
|
|
||||||
line-height: $ovrFooterH;
|
|
||||||
margin-left: $interiorMargin;
|
|
||||||
padding: 0 $interiorMargin * 3;
|
|
||||||
//&.major {
|
|
||||||
// @extend .s-btn.major;
|
|
||||||
// &:hover {
|
|
||||||
// @extend .s-btn.major:hover;
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.contents.l-dialog {
|
|
||||||
$myM: $interiorMargin;
|
.bottom-bar {
|
||||||
top: $myM;
|
text-align: right;
|
||||||
right: $myM;
|
.s-btn {
|
||||||
bottom: $myM;
|
$bg: $colorOvrBtnBg;
|
||||||
left: $myM;
|
&:not(.major) {
|
||||||
overflow: auto;
|
@include btnSubtle($bg, pullForward($bg, 10%), $colorOvrBtnFg, $colorOvrBtnFg);
|
||||||
.field.l-med {
|
}
|
||||||
input[type='text'] {
|
font-size: 95%;
|
||||||
width: 100%;
|
height: $ovrFooterH;
|
||||||
}
|
line-height: $ovrFooterH;
|
||||||
}
|
margin-left: $interiorMargin;
|
||||||
|
padding: 0 $interiorMargin * 3;
|
||||||
|
&:first-child {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.abs.bottom-bar {
|
||||||
|
top: auto;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
overflow: visible;
|
||||||
|
//font-size: 1em;
|
||||||
|
height: $ovrFooterH;
|
||||||
|
}
|
||||||
|
|
||||||
|
.l-progress-bar {
|
||||||
|
$h: $progressBarHOverlay;
|
||||||
|
display: block;
|
||||||
|
height: $h;
|
||||||
|
line-height: $h;
|
||||||
|
margin: .5em 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.t-dialog-sm .overlay > .holder {
|
||||||
|
// Used for blocker and in-progress dialogs, modal alerts, etc.
|
||||||
|
//@include test(red);
|
||||||
|
$h: 225px;
|
||||||
|
min-height: $h;
|
||||||
|
height: $h;
|
||||||
}
|
}
|
@ -1,72 +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.
|
|
||||||
*****************************************************************************/
|
|
||||||
.ue-bottom-bar {
|
|
||||||
background: $colorFooterBg;
|
|
||||||
color: lighten($colorBodyBg, 30%);
|
|
||||||
font-size: .7rem;
|
|
||||||
//line-height: $ueFooterH - 4px;
|
|
||||||
//line-height: $ueFooterH; // New status bar design
|
|
||||||
.status-holder {
|
|
||||||
//@include border-radius($basicCr * 1.75); // New status bar design
|
|
||||||
@include box-sizing(border-box);
|
|
||||||
//background: $colorFooterBg;
|
|
||||||
//border-bottom: 1px solid lighten($colorBodyBg, 10%); // New status bar design
|
|
||||||
@include absPosDefault($interiorMargin);
|
|
||||||
@include ellipsize();
|
|
||||||
line-height: $ueFooterH - ($interiorMargin * 2);
|
|
||||||
right: 120px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
.app-logo {
|
|
||||||
@include box-sizing(border-box);
|
|
||||||
@include absPosDefault($interiorMargin);
|
|
||||||
left: auto;
|
|
||||||
cursor: pointer;
|
|
||||||
//font-size: 0.8em;
|
|
||||||
//line-height: $ueFooterH - 10px;
|
|
||||||
//padding-top: 1px;
|
|
||||||
//text-transform: uppercase;
|
|
||||||
&.logo-openmctweb {
|
|
||||||
background: url($dirImgs + 'logo-openmctweb.svg') no-repeat center center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.status.block {
|
|
||||||
//display: inline-block;
|
|
||||||
display: inline; // New status bar design. Inline to support ellipsis overflow
|
|
||||||
margin-right: $interiorMarginLg;
|
|
||||||
.status-indicator {
|
|
||||||
//@include border-radius($controlCr * 0.9);
|
|
||||||
//@include box-shadow(inset rgba(black, 0.5) 0 0 3px);
|
|
||||||
//@include text-shadow(rgba(black, 0.3) 0 0 2px);
|
|
||||||
display: inline-block;
|
|
||||||
margin-right: $interiorMarginSm;
|
|
||||||
color: $colorKey;
|
|
||||||
&.ok {
|
|
||||||
color: #009900;
|
|
||||||
}
|
|
||||||
&.caution {
|
|
||||||
color: #ffaa00;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -125,21 +125,37 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ue-bottom-bar {
|
// from _bottom-bar.scss
|
||||||
//@include absPosDefault($bodyMargin);
|
.ue-bottom-bar {
|
||||||
@include absPosDefault(0); // New status bar design
|
@include absPosDefault(0);// New status bar design
|
||||||
top: auto;
|
top: auto;
|
||||||
height: $ueFooterH;
|
height: $ueFooterH;
|
||||||
.status-holder {
|
line-height: $ueFooterH - ($interiorMargin * 2);
|
||||||
//right: $ueAppLogoW + $bodyMargin; New status bar design
|
background: $colorFooterBg;
|
||||||
z-index: 1;
|
color: lighten($colorBodyBg, 30%);
|
||||||
}
|
font-size: .7rem;
|
||||||
.app-logo {
|
|
||||||
left: auto;
|
.status-holder {
|
||||||
width: $ueAppLogoW;
|
@include box-sizing(border-box);
|
||||||
z-index: 2;
|
@include absPosDefault($interiorMargin);
|
||||||
}
|
@include ellipsize();
|
||||||
}
|
//line-height: $ueFooterH - ($interiorMargin * 2);
|
||||||
|
right: 120px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
.app-logo {
|
||||||
|
@include box-sizing(border-box);
|
||||||
|
@include absPosDefault($interiorMargin);
|
||||||
|
cursor: pointer;
|
||||||
|
left: auto;
|
||||||
|
width: $ueAppLogoW;
|
||||||
|
z-index: 2;
|
||||||
|
&.logo-openmctweb {
|
||||||
|
background: url($dirImgs + 'logo-openmctweb.svg') no-repeat center center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.cols {
|
.cols {
|
||||||
@ -518,4 +534,4 @@
|
|||||||
.pane:not(.resizing) {
|
.pane:not(.resizing) {
|
||||||
@include trans-prop-nice-resize-w(250ms);
|
@include trans-prop-nice-resize-w(250ms);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,5 +26,6 @@
|
|||||||
key="indicator.template">
|
key="indicator.template">
|
||||||
</mct-include>
|
</mct-include>
|
||||||
</div>
|
</div>
|
||||||
|
<mct-include key="'message-banner'"></mct-include>
|
||||||
<mct-include key="'about-logo'"></mct-include>
|
<mct-include key="'about-logo'"></mct-include>
|
||||||
</div>
|
</div>
|
@ -0,0 +1,20 @@
|
|||||||
|
<span class="s-btn"
|
||||||
|
ng-controller="DateTimeFieldController">
|
||||||
|
<input type="text"
|
||||||
|
ng-model="textValue"
|
||||||
|
ng-class="{ error: textInvalid }">
|
||||||
|
</input>
|
||||||
|
<a class="ui-symbol icon icon-calendar"
|
||||||
|
ng-if="structure.format === 'utc' || !structure.format"
|
||||||
|
ng-click="pickerActive = !pickerActive">
|
||||||
|
</a>
|
||||||
|
<mct-popup ng-if="pickerActive">
|
||||||
|
<div mct-click-elsewhere="pickerActive = false">
|
||||||
|
<mct-control key="'datetime-picker'"
|
||||||
|
ng-model="ngModel"
|
||||||
|
field="field"
|
||||||
|
options="{ hours: true }">
|
||||||
|
</mct-control>
|
||||||
|
</div>
|
||||||
|
</mct-popup>
|
||||||
|
</span>
|
@ -22,47 +22,24 @@
|
|||||||
<div ng-controller="TimeRangeController">
|
<div ng-controller="TimeRangeController">
|
||||||
<div class="l-time-range-inputs-holder">
|
<div class="l-time-range-inputs-holder">
|
||||||
<span class="l-time-range-inputs-elem ui-symbol type-icon">C</span>
|
<span class="l-time-range-inputs-elem ui-symbol type-icon">C</span>
|
||||||
<span class="l-time-range-input" ng-controller="ToggleController as t1">
|
<span class="l-time-range-input">
|
||||||
<!--<span class="lbl">Start</span>-->
|
<mct-control key="'datetime-field'"
|
||||||
<span class="s-btn time-range-start">
|
structure="{ format: parameters.format }"
|
||||||
<input type="text"
|
ng-model="ngModel.outer"
|
||||||
ng-model="boundsModel.start"
|
field="'start'"
|
||||||
ng-class="{ error: !boundsModel.startValid }">
|
class="time-range-start">
|
||||||
</input>
|
</mct-control>
|
||||||
<a class="ui-symbol icon icon-calendar" ng-click="t1.toggle()"></a>
|
|
||||||
<mct-popup ng-if="t1.isActive()">
|
|
||||||
<div mct-click-elsewhere="t1.setState(false)">
|
|
||||||
<mct-control key="'datetime-picker'"
|
|
||||||
ng-model="ngModel.outer"
|
|
||||||
field="'start'"
|
|
||||||
options="{ hours: true }">
|
|
||||||
</mct-control>
|
|
||||||
</div>
|
|
||||||
</mct-popup>
|
|
||||||
</span>
|
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="l-time-range-inputs-elem lbl">to</span>
|
<span class="l-time-range-inputs-elem lbl">to</span>
|
||||||
|
|
||||||
<span class="l-time-range-input" ng-controller="ToggleController as t2">
|
<span class="l-time-range-input" ng-controller="ToggleController as t2">
|
||||||
<!--<span class="lbl">End</span>-->
|
<mct-control key="'datetime-field'"
|
||||||
<span class="s-btn l-time-range-input">
|
structure="{ format: parameters.format }"
|
||||||
<input type="text"
|
ng-model="ngModel.outer"
|
||||||
ng-model="boundsModel.end"
|
field="'end'"
|
||||||
ng-class="{ error: !boundsModel.endValid }">
|
class="time-range-end">
|
||||||
</input>
|
</mct-control>
|
||||||
<a class="ui-symbol icon icon-calendar" ng-click="t2.toggle()">
|
|
||||||
</a>
|
|
||||||
<mct-popup ng-if="t2.isActive()">
|
|
||||||
<div mct-click-elsewhere="t2.setState(false)">
|
|
||||||
<mct-control key="'datetime-picker'"
|
|
||||||
ng-model="ngModel.outer"
|
|
||||||
field="'end'"
|
|
||||||
options="{ hours: true }">
|
|
||||||
</mct-control>
|
|
||||||
</div>
|
|
||||||
</mct-popup>
|
|
||||||
</span>
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -97,7 +74,7 @@
|
|||||||
<div class="l-time-range-ticks-holder">
|
<div class="l-time-range-ticks-holder">
|
||||||
<div class="l-time-range-ticks">
|
<div class="l-time-range-ticks">
|
||||||
<div
|
<div
|
||||||
ng-repeat="tick in ticks"
|
ng-repeat="tick in ticks track by $index"
|
||||||
ng-style="{ left: $index * (100 / (ticks.length - 1)) + '%' }"
|
ng-style="{ left: $index * (100 / (ticks.length - 1)) + '%' }"
|
||||||
class="tick tick-x"
|
class="tick tick-x"
|
||||||
>
|
>
|
||||||
|
@ -19,20 +19,21 @@
|
|||||||
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.
|
||||||
-->
|
-->
|
||||||
|
<!-- DO NOT ADD SPACES BETWEEN THE SPANS - IT ADDS WHITE SPACE!! -->
|
||||||
|
<!--<div ng-init="reps = [1,2,3]"></div>-->
|
||||||
<div class='status block'
|
<div class='status block'
|
||||||
title="{{ngModel.getDescription()}}"
|
title="{{ngModel.getDescription()}}"
|
||||||
ng-click='ngModel.configure()'
|
ng-click='ngModel.configure()'
|
||||||
ng-class='ngModel.getClass()'
|
ng-class='ngModel.getGlyphClass()'
|
||||||
ng-show="ngModel.getText().length > 0">
|
ng-show="ngModel.getText().length > 0">
|
||||||
<span class="ui-symbol status-indicator"
|
<span class="ui-symbol status-indicator">
|
||||||
ng-class='ngModel.getGlyphClass()'>
|
{{ngModel.getGlyph()}}
|
||||||
{{ngModel.getGlyph()}}
|
</span><span class="label"
|
||||||
</span>
|
ng-class='ngModel.getTextClass()'>
|
||||||
<span class="label"
|
{{ngModel.getText()}}
|
||||||
ng-class='ngModel.getTextClass()'>
|
</span><span class="count">
|
||||||
{{ngModel.getText()}}
|
<!-- Add int count value here if this type of indicator has one or more messages associated with it -->
|
||||||
</span>
|
</span><a href=''
|
||||||
<a href=''
|
|
||||||
class="ui-symbol"
|
class="ui-symbol"
|
||||||
ng-if="ngModel.configure">
|
ng-if="ngModel.configure">
|
||||||
G
|
G
|
||||||
|
21
platform/commonUI/general/res/templates/message-banner.html
Normal file
21
platform/commonUI/general/res/templates/message-banner.html
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<div ng-controller="BannerController" ng-show="active.notification"
|
||||||
|
class="l-message-banner s-message-banner {{active.notification.model.severity}}" ng-class="{
|
||||||
|
'minimized': active.notification.model.minimized,
|
||||||
|
'new': !active.notification.model.minimized}"
|
||||||
|
ng-click="maximize(active.notification)">
|
||||||
|
<span class="banner-elem label">
|
||||||
|
{{active.notification.model.title}}
|
||||||
|
</span>
|
||||||
|
<span ng-show="active.notification.model.progress !== undefined || active.notification.model.unknownProgress">
|
||||||
|
<mct-include key="'progress-bar'" class="banner-elem"
|
||||||
|
ng-model="active.notification.model">
|
||||||
|
</mct-include>
|
||||||
|
</span>
|
||||||
|
<a ng-hide="active.notification.model.primaryOption === undefined"
|
||||||
|
class="banner-elem l-action s-action"
|
||||||
|
ng-click="action(active.notification.model.primaryOption.callback, $event)">
|
||||||
|
{{active.notification.model.primaryOption.label}}
|
||||||
|
</a>
|
||||||
|
<a class="banner-elem ui-symbol close" ng-click="dismiss(active.notification, $event)">
|
||||||
|
x</a>
|
||||||
|
</div>
|
10
platform/commonUI/general/res/templates/progress-bar.html
Normal file
10
platform/commonUI/general/res/templates/progress-bar.html
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<span class="l-progress-bar s-progress-bar"
|
||||||
|
ng-class="{ indeterminate:ngModel.unknownProgress }">
|
||||||
|
<span class="progress-amt-holder">
|
||||||
|
<span class="progress-amt" style="width: {{ngModel.progress}}%"></span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<div class="progress-info hint" ng-hide="ngModel.progressText === undefined">
|
||||||
|
<span class="progress-amt-text" ng-show="ngModel.progress > 0">{{ngModel.progress}}% complete. </span>
|
||||||
|
{{ngModel.progressText}}
|
||||||
|
</div>
|
@ -0,0 +1,68 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT Web includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
/*global define*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
[],
|
||||||
|
function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A controller for banner notifications. Banner notifications are a
|
||||||
|
* non-blocking way of drawing the user's attention to an event such
|
||||||
|
* as system errors, or the progress or successful completion of an
|
||||||
|
* ongoing task. This controller provides scoped functions for
|
||||||
|
* dismissing and 'maximizing' notifications. See {@link NotificationService}
|
||||||
|
* for more details on Notifications.
|
||||||
|
*
|
||||||
|
* @param $scope
|
||||||
|
* @param notificationService
|
||||||
|
* @param dialogService
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function BannerController($scope, notificationService, dialogService) {
|
||||||
|
$scope.active = notificationService.active;
|
||||||
|
|
||||||
|
$scope.action = function (action, $event){
|
||||||
|
/*
|
||||||
|
Prevents default 'maximize' behaviour when clicking on
|
||||||
|
notification button
|
||||||
|
*/
|
||||||
|
$event.stopPropagation();
|
||||||
|
return action();
|
||||||
|
};
|
||||||
|
$scope.dismiss = function(notification, $event) {
|
||||||
|
$event.stopPropagation();
|
||||||
|
notification.dismissOrMinimize();
|
||||||
|
};
|
||||||
|
$scope.maximize = function(notification) {
|
||||||
|
if (notification.model.severity !== "info"){
|
||||||
|
|
||||||
|
notification.model.cancel = function(){
|
||||||
|
dialogService.dismiss();
|
||||||
|
};
|
||||||
|
dialogService.showBlockingMessage(notification.model);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return BannerController;
|
||||||
|
});
|
@ -0,0 +1,79 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT Web includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
/*global define,Promise*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
[],
|
||||||
|
function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller to support the date-time entry field.
|
||||||
|
*
|
||||||
|
* Accepts a `format` property in the `structure` attribute
|
||||||
|
* which allows a date/time to be specified via its symbolic
|
||||||
|
* key (as will be used to look up said format from the
|
||||||
|
* `formatService`.)
|
||||||
|
*
|
||||||
|
* {@see FormatService}
|
||||||
|
* @constructor
|
||||||
|
* @memberof platform/commonUI/general
|
||||||
|
* @param $scope the Angular scope for this controller
|
||||||
|
* @param {FormatService} formatService the service to user to format
|
||||||
|
* domain values
|
||||||
|
* @param {string} defaultFormat the format to request when no
|
||||||
|
* format has been otherwise specified
|
||||||
|
*/
|
||||||
|
function DateTimeFieldController($scope, formatService, defaultFormat) {
|
||||||
|
var formatter = formatService.getFormat(defaultFormat);
|
||||||
|
|
||||||
|
function updateFromModel(value) {
|
||||||
|
// Only reformat if the value is different from user
|
||||||
|
// input (to avoid reformatting valid input while typing.)
|
||||||
|
if (!formatter.validate($scope.textValue) ||
|
||||||
|
formatter.parse($scope.textValue) !== value) {
|
||||||
|
$scope.textValue = formatter.format(value);
|
||||||
|
$scope.textInvalid = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateFromView(textValue) {
|
||||||
|
$scope.textInvalid = !formatter.validate(textValue);
|
||||||
|
if (!$scope.textInvalid) {
|
||||||
|
$scope.ngModel[$scope.field] =
|
||||||
|
formatter.parse(textValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setFormat(format) {
|
||||||
|
formatter = formatService.getFormat(format || defaultFormat);
|
||||||
|
updateFromModel($scope.ngModel[$scope.field]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.$watch('structure.format', setFormat);
|
||||||
|
$scope.$watch('ngModel[field]', updateFromModel);
|
||||||
|
$scope.$watch('textValue', updateFromView);
|
||||||
|
}
|
||||||
|
|
||||||
|
return DateTimeFieldController;
|
||||||
|
}
|
||||||
|
);
|
@ -26,33 +26,32 @@ define(
|
|||||||
function (moment) {
|
function (moment) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var DATE_FORMAT = "YYYY-MM-DD HH:mm:ss",
|
var TICK_SPACING_PX = 150;
|
||||||
TICK_SPACING_PX = 150;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Controller used by the `time-controller` template.
|
||||||
* @memberof platform/commonUI/general
|
* @memberof platform/commonUI/general
|
||||||
* @constructor
|
* @constructor
|
||||||
|
* @param $scope the Angular scope for this controller
|
||||||
|
* @param {FormatService} formatService the service to user to format
|
||||||
|
* domain values
|
||||||
|
* @param {string} defaultFormat the format to request when no
|
||||||
|
* format has been otherwise specified
|
||||||
|
* @param {Function} now a function to return current system time
|
||||||
*/
|
*/
|
||||||
function TimeConductorController($scope, now) {
|
function TimeRangeController($scope, formatService, defaultFormat, now) {
|
||||||
var tickCount = 2,
|
var tickCount = 2,
|
||||||
innerMinimumSpan = 1000, // 1 second
|
innerMinimumSpan = 1000, // 1 second
|
||||||
outerMinimumSpan = 1000 * 60 * 60, // 1 hour
|
outerMinimumSpan = 1000 * 60 * 60, // 1 hour
|
||||||
initialDragValue;
|
initialDragValue,
|
||||||
|
formatter = formatService.getFormat(defaultFormat);
|
||||||
|
|
||||||
function formatTimestamp(ts) {
|
function formatTimestamp(ts) {
|
||||||
return moment.utc(ts).format(DATE_FORMAT);
|
return formatter.format(ts);
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseTimestamp(text) {
|
// From 0.0-1.0 to "0%"-"100%"
|
||||||
var m = moment.utc(text, DATE_FORMAT);
|
|
||||||
if (m.isValid()) {
|
|
||||||
return m.valueOf();
|
|
||||||
} else {
|
|
||||||
throw new Error("Could not parse " + text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// From 0.0-1.0 to "0%"-"1%"
|
|
||||||
function toPercent(p) {
|
function toPercent(p) {
|
||||||
return (100 * p) + "%";
|
return (100 * p) + "%";
|
||||||
}
|
}
|
||||||
@ -101,41 +100,15 @@ define(
|
|||||||
return { start: bounds.start, end: bounds.end };
|
return { start: bounds.start, end: bounds.end };
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateBoundsTextForProperty(ngModel, property) {
|
|
||||||
try {
|
|
||||||
if (!$scope.boundsModel[property] ||
|
|
||||||
parseTimestamp($scope.boundsModel[property]) !==
|
|
||||||
ngModel.outer[property]) {
|
|
||||||
$scope.boundsModel[property] =
|
|
||||||
formatTimestamp(ngModel.outer[property]);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// User-entered text is invalid, so leave it be
|
|
||||||
// until they fix it.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateBoundsText(ngModel) {
|
|
||||||
updateBoundsTextForProperty(ngModel, 'start');
|
|
||||||
updateBoundsTextForProperty(ngModel, 'end');
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateViewFromModel(ngModel) {
|
function updateViewFromModel(ngModel) {
|
||||||
var t = now();
|
|
||||||
|
|
||||||
ngModel = ngModel || {};
|
ngModel = ngModel || {};
|
||||||
ngModel.outer = ngModel.outer || defaultBounds();
|
ngModel.outer = ngModel.outer || defaultBounds();
|
||||||
ngModel.inner = ngModel.inner || copyBounds(ngModel.outer);
|
ngModel.inner = ngModel.inner || copyBounds(ngModel.outer);
|
||||||
|
|
||||||
// First, dates for the date pickers for outer bounds
|
|
||||||
updateBoundsText(ngModel);
|
|
||||||
|
|
||||||
// Then various updates for the inner span
|
|
||||||
updateViewForInnerSpanFromModel(ngModel);
|
|
||||||
|
|
||||||
// Stick it back is scope (in case we just set defaults)
|
// Stick it back is scope (in case we just set defaults)
|
||||||
$scope.ngModel = ngModel;
|
$scope.ngModel = ngModel;
|
||||||
|
|
||||||
|
updateViewForInnerSpanFromModel(ngModel);
|
||||||
updateTicks();
|
updateTicks();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,7 +128,8 @@ define(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function toMillis(pixels) {
|
function toMillis(pixels) {
|
||||||
var span = $scope.ngModel.outer.end - $scope.ngModel.outer.start;
|
var span =
|
||||||
|
$scope.ngModel.outer.end - $scope.ngModel.outer.start;
|
||||||
return (pixels / $scope.spanWidth) * span;
|
return (pixels / $scope.spanWidth) * span;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -243,36 +217,10 @@ define(
|
|||||||
updateTicks();
|
updateTicks();
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateStartFromText(value) {
|
function updateFormat(key) {
|
||||||
try {
|
formatter = formatService.getFormat(key || defaultFormat);
|
||||||
updateOuterStart(parseTimestamp(value));
|
updateViewForInnerSpanFromModel($scope.ngModel);
|
||||||
updateBoundsTextForProperty($scope.ngModel, 'end');
|
updateTicks();
|
||||||
$scope.boundsModel.startValid = true;
|
|
||||||
} catch (e) {
|
|
||||||
$scope.boundsModel.startValid = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateEndFromText(value) {
|
|
||||||
try {
|
|
||||||
updateOuterEnd(parseTimestamp(value));
|
|
||||||
updateBoundsTextForProperty($scope.ngModel, 'start');
|
|
||||||
$scope.boundsModel.endValid = true;
|
|
||||||
} catch (e) {
|
|
||||||
$scope.boundsModel.endValid = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateStartFromPicker(value) {
|
|
||||||
updateOuterStart(value);
|
|
||||||
updateBoundsText($scope.ngModel);
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateEndFromPicker(value) {
|
|
||||||
updateOuterEnd(value);
|
|
||||||
updateBoundsText($scope.ngModel);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.startLeftDrag = startLeftDrag;
|
$scope.startLeftDrag = startLeftDrag;
|
||||||
@ -282,21 +230,18 @@ define(
|
|||||||
$scope.rightDrag = rightDrag;
|
$scope.rightDrag = rightDrag;
|
||||||
$scope.middleDrag = middleDrag;
|
$scope.middleDrag = middleDrag;
|
||||||
|
|
||||||
$scope.state = false;
|
|
||||||
$scope.ticks = [];
|
$scope.ticks = [];
|
||||||
$scope.boundsModel = {};
|
|
||||||
|
|
||||||
// Initialize scope to defaults
|
// Initialize scope to defaults
|
||||||
updateViewFromModel($scope.ngModel);
|
updateViewFromModel($scope.ngModel);
|
||||||
|
|
||||||
$scope.$watchCollection("ngModel", updateViewFromModel);
|
$scope.$watchCollection("ngModel", updateViewFromModel);
|
||||||
$scope.$watch("spanWidth", updateSpanWidth);
|
$scope.$watch("spanWidth", updateSpanWidth);
|
||||||
$scope.$watch("ngModel.outer.start", updateStartFromPicker);
|
$scope.$watch("ngModel.outer.start", updateOuterStart);
|
||||||
$scope.$watch("ngModel.outer.end", updateEndFromPicker);
|
$scope.$watch("ngModel.outer.end", updateOuterEnd);
|
||||||
$scope.$watch("boundsModel.start", updateStartFromText);
|
$scope.$watch("parameters.format", updateFormat);
|
||||||
$scope.$watch("boundsModel.end", updateEndFromText);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return TimeConductorController;
|
return TimeRangeController;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -0,0 +1,183 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT Web includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
["../../src/controllers/DateTimeFieldController", "moment"],
|
||||||
|
function (DateTimeFieldController, moment) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var TEST_FORMAT = "YYYY-MM-DD HH:mm:ss";
|
||||||
|
|
||||||
|
describe("The DateTimeFieldController", function () {
|
||||||
|
var mockScope,
|
||||||
|
mockFormatService,
|
||||||
|
mockFormat,
|
||||||
|
controller;
|
||||||
|
|
||||||
|
function fireWatch(expr, value) {
|
||||||
|
mockScope.$watch.calls.forEach(function (call) {
|
||||||
|
if (call.args[0] === expr) {
|
||||||
|
call.args[1](value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
mockScope = jasmine.createSpyObj('$scope', ['$watch']);
|
||||||
|
mockFormatService =
|
||||||
|
jasmine.createSpyObj('formatService', ['getFormat']);
|
||||||
|
mockFormat = jasmine.createSpyObj('format', [
|
||||||
|
'parse',
|
||||||
|
'validate',
|
||||||
|
'format'
|
||||||
|
]);
|
||||||
|
|
||||||
|
mockFormatService.getFormat.andReturn(mockFormat);
|
||||||
|
|
||||||
|
mockFormat.validate.andCallFake(function (text) {
|
||||||
|
return moment.utc(text, TEST_FORMAT).isValid();
|
||||||
|
});
|
||||||
|
mockFormat.parse.andCallFake(function (text) {
|
||||||
|
return moment.utc(text, TEST_FORMAT).valueOf();
|
||||||
|
});
|
||||||
|
mockFormat.format.andCallFake(function (value) {
|
||||||
|
return moment.utc(value).format(TEST_FORMAT);
|
||||||
|
});
|
||||||
|
|
||||||
|
mockScope.ngModel = { testField: 12321 };
|
||||||
|
mockScope.field = "testField";
|
||||||
|
mockScope.structure = { format: "someFormat" };
|
||||||
|
|
||||||
|
controller = new DateTimeFieldController(
|
||||||
|
mockScope,
|
||||||
|
mockFormatService
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updates models from user-entered text", function () {
|
||||||
|
var newText = "1977-05-25 17:30:00";
|
||||||
|
|
||||||
|
mockScope.textValue = newText;
|
||||||
|
fireWatch("textValue", newText);
|
||||||
|
expect(mockScope.ngModel.testField)
|
||||||
|
.toEqual(mockFormat.parse(newText));
|
||||||
|
expect(mockScope.textInvalid).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updates text from model values", function () {
|
||||||
|
var testTime = mockFormat.parse("1977-05-25 17:30:00");
|
||||||
|
mockScope.ngModel.testField = testTime;
|
||||||
|
fireWatch("ngModel[field]", testTime);
|
||||||
|
expect(mockScope.textValue).toEqual("1977-05-25 17:30:00");
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when user input is invalid", function () {
|
||||||
|
var newText, oldValue;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
newText = "Not a date";
|
||||||
|
oldValue = mockScope.ngModel.testField;
|
||||||
|
mockScope.textValue = newText;
|
||||||
|
fireWatch("textValue", newText);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("displays error state", function () {
|
||||||
|
expect(mockScope.textInvalid).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not modify model state", function () {
|
||||||
|
expect(mockScope.ngModel.testField).toEqual(oldValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not modify user input", function () {
|
||||||
|
expect(mockScope.textValue).toEqual(newText);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not modify valid but irregular user input", function () {
|
||||||
|
// Don't want the controller "fixing" bad or
|
||||||
|
// irregularly-formatted input out from under
|
||||||
|
// the user's fingertips.
|
||||||
|
var newText = "2015-3-3 01:02:04",
|
||||||
|
oldValue = mockScope.ngModel.testField;
|
||||||
|
|
||||||
|
mockFormat.validate.andReturn(true);
|
||||||
|
mockFormat.parse.andReturn(42);
|
||||||
|
mockScope.textValue = newText;
|
||||||
|
fireWatch("textValue", newText);
|
||||||
|
|
||||||
|
expect(mockScope.textValue).toEqual(newText);
|
||||||
|
expect(mockScope.ngModel.testField).toEqual(42);
|
||||||
|
expect(mockScope.ngModel.testField).not.toEqual(oldValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("obtains a format from the format service", function () {
|
||||||
|
fireWatch('structure.format', mockScope.structure.format);
|
||||||
|
expect(mockFormatService.getFormat)
|
||||||
|
.toHaveBeenCalledWith(mockScope.structure.format);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("throws an error for unknown formats", function () {
|
||||||
|
mockFormatService.getFormat.andReturn(undefined);
|
||||||
|
expect(function () {
|
||||||
|
fireWatch("structure.format", "some-format");
|
||||||
|
}).toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("using the obtained format", function () {
|
||||||
|
var testValue = 1234321,
|
||||||
|
testText = "some text";
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
mockFormat.validate.andReturn(true);
|
||||||
|
mockFormat.parse.andReturn(testValue);
|
||||||
|
mockFormat.format.andReturn(testText);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("parses user input", function () {
|
||||||
|
var newText = "some other new text";
|
||||||
|
mockScope.textValue = newText;
|
||||||
|
fireWatch("textValue", newText);
|
||||||
|
expect(mockFormat.parse).toHaveBeenCalledWith(newText);
|
||||||
|
expect(mockScope.ngModel.testField).toEqual(testValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("validates user input", function () {
|
||||||
|
var newText = "some other new text";
|
||||||
|
mockScope.textValue = newText;
|
||||||
|
fireWatch("textValue", newText);
|
||||||
|
expect(mockFormat.validate).toHaveBeenCalledWith(newText);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("formats model data for display", function () {
|
||||||
|
var newValue = 42;
|
||||||
|
mockScope.ngModel.testField = newValue;
|
||||||
|
fireWatch("ngModel[field]", newValue);
|
||||||
|
expect(mockFormat.format).toHaveBeenCalledWith(newValue);
|
||||||
|
expect(mockScope.textValue).toEqual(testText);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
@ -33,7 +33,10 @@ define(
|
|||||||
|
|
||||||
describe("The TimeRangeController", function () {
|
describe("The TimeRangeController", function () {
|
||||||
var mockScope,
|
var mockScope,
|
||||||
|
mockFormatService,
|
||||||
|
testDefaultFormat,
|
||||||
mockNow,
|
mockNow,
|
||||||
|
mockFormat,
|
||||||
controller;
|
controller;
|
||||||
|
|
||||||
function fireWatch(expr, value) {
|
function fireWatch(expr, value) {
|
||||||
@ -57,8 +60,30 @@ define(
|
|||||||
"$scope",
|
"$scope",
|
||||||
[ "$apply", "$watch", "$watchCollection" ]
|
[ "$apply", "$watch", "$watchCollection" ]
|
||||||
);
|
);
|
||||||
|
mockFormatService = jasmine.createSpyObj(
|
||||||
|
"formatService",
|
||||||
|
[ "getFormat" ]
|
||||||
|
);
|
||||||
|
testDefaultFormat = 'utc';
|
||||||
|
mockFormat = jasmine.createSpyObj(
|
||||||
|
"format",
|
||||||
|
[ "validate", "format", "parse" ]
|
||||||
|
);
|
||||||
|
|
||||||
|
mockFormatService.getFormat.andReturn(mockFormat);
|
||||||
|
|
||||||
|
mockFormat.format.andCallFake(function (value) {
|
||||||
|
return moment.utc(value).format("YYYY-MM-DD HH:mm:ss");
|
||||||
|
});
|
||||||
|
|
||||||
mockNow = jasmine.createSpy('now');
|
mockNow = jasmine.createSpy('now');
|
||||||
controller = new TimeRangeController(mockScope, mockNow);
|
|
||||||
|
controller = new TimeRangeController(
|
||||||
|
mockScope,
|
||||||
|
mockFormatService,
|
||||||
|
testDefaultFormat,
|
||||||
|
mockNow
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("watches the model that was passed in", function () {
|
it("watches the model that was passed in", function () {
|
||||||
@ -167,70 +192,22 @@ define(
|
|||||||
.toBeGreaterThan(mockScope.ngModel.inner.start);
|
.toBeGreaterThan(mockScope.ngModel.inner.start);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("by typing", function () {
|
|
||||||
it("updates models", function () {
|
|
||||||
var newStart = "1977-05-25 17:30:00",
|
|
||||||
newEnd = "2015-12-18 03:30:00";
|
|
||||||
|
|
||||||
mockScope.boundsModel.start = newStart;
|
|
||||||
fireWatch("boundsModel.start", newStart);
|
|
||||||
expect(mockScope.ngModel.outer.start)
|
|
||||||
.toEqual(moment.utc(newStart).valueOf());
|
|
||||||
expect(mockScope.boundsModel.startValid)
|
|
||||||
.toBeTruthy();
|
|
||||||
|
|
||||||
mockScope.boundsModel.end = newEnd;
|
|
||||||
fireWatch("boundsModel.end", newEnd);
|
|
||||||
expect(mockScope.ngModel.outer.end)
|
|
||||||
.toEqual(moment.utc(newEnd).valueOf());
|
|
||||||
expect(mockScope.boundsModel.endValid)
|
|
||||||
.toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("displays error state", function () {
|
|
||||||
var newStart = "Not a date",
|
|
||||||
newEnd = "Definitely not a date",
|
|
||||||
oldStart = mockScope.ngModel.outer.start,
|
|
||||||
oldEnd = mockScope.ngModel.outer.end;
|
|
||||||
|
|
||||||
mockScope.boundsModel.start = newStart;
|
|
||||||
fireWatch("boundsModel.start", newStart);
|
|
||||||
expect(mockScope.ngModel.outer.start)
|
|
||||||
.toEqual(oldStart);
|
|
||||||
expect(mockScope.boundsModel.startValid)
|
|
||||||
.toBeFalsy();
|
|
||||||
|
|
||||||
mockScope.boundsModel.end = newEnd;
|
|
||||||
fireWatch("boundsModel.end", newEnd);
|
|
||||||
expect(mockScope.ngModel.outer.end)
|
|
||||||
.toEqual(oldEnd);
|
|
||||||
expect(mockScope.boundsModel.endValid)
|
|
||||||
.toBeFalsy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("does not modify user input", function () {
|
|
||||||
// Don't want the controller "fixing" bad or
|
|
||||||
// irregularly-formatted input out from under
|
|
||||||
// the user's fingertips.
|
|
||||||
var newStart = "Not a date",
|
|
||||||
newEnd = "2015-3-3 01:02:04",
|
|
||||||
oldStart = mockScope.ngModel.outer.start,
|
|
||||||
oldEnd = mockScope.ngModel.outer.end;
|
|
||||||
|
|
||||||
mockScope.boundsModel.start = newStart;
|
|
||||||
fireWatch("boundsModel.start", newStart);
|
|
||||||
expect(mockScope.boundsModel.start)
|
|
||||||
.toEqual(newStart);
|
|
||||||
|
|
||||||
mockScope.boundsModel.end = newEnd;
|
|
||||||
fireWatch("boundsModel.end", newEnd);
|
|
||||||
expect(mockScope.boundsModel.end)
|
|
||||||
.toEqual(newEnd);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("watches for changes in format selection", function () {
|
||||||
|
expect(mockFormatService.getFormat)
|
||||||
|
.not.toHaveBeenCalledWith('test-format');
|
||||||
|
fireWatch("parameters.format", 'test-format');
|
||||||
|
expect(mockFormatService.getFormat)
|
||||||
|
.toHaveBeenCalledWith('test-format');
|
||||||
|
});
|
||||||
|
|
||||||
|
it("throws an error for unknown formats", function () {
|
||||||
|
mockFormatService.getFormat.andReturn(undefined);
|
||||||
|
expect(function () {
|
||||||
|
fireWatch("parameters.format", "some-format");
|
||||||
|
}).toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
"controllers/BottomBarController",
|
"controllers/BottomBarController",
|
||||||
"controllers/ClickAwayController",
|
"controllers/ClickAwayController",
|
||||||
"controllers/ContextMenuController",
|
"controllers/ContextMenuController",
|
||||||
|
"controllers/DateTimeFieldController",
|
||||||
"controllers/DateTimePickerController",
|
"controllers/DateTimePickerController",
|
||||||
"controllers/GetterSetterController",
|
"controllers/GetterSetterController",
|
||||||
"controllers/ObjectInspectorController",
|
"controllers/ObjectInspectorController",
|
||||||
|
45
platform/commonUI/notification/bundle.json
Normal file
45
platform/commonUI/notification/bundle.json
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
{
|
||||||
|
"extensions": {
|
||||||
|
"constants": [
|
||||||
|
{
|
||||||
|
"key": "DEFAULT_AUTO_DISMISS",
|
||||||
|
"value": 3000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "FORCE_AUTO_DISMISS",
|
||||||
|
"value": 1000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "MINIMIZE_TIMEOUT",
|
||||||
|
"value": 300
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"templates": [
|
||||||
|
{
|
||||||
|
"key":"notificationIndicatorTemplate",
|
||||||
|
"templateUrl": "notification-indicator.html"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"controllers": [
|
||||||
|
{
|
||||||
|
"key": "NotificationIndicatorController",
|
||||||
|
"implementation": "NotificationIndicatorController.js",
|
||||||
|
"depends": ["$scope", "notificationService", "dialogService"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"indicators": [
|
||||||
|
{
|
||||||
|
"implementation": "NotificationIndicator.js",
|
||||||
|
"priority": "fallback"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"services": [
|
||||||
|
{
|
||||||
|
"key": "notificationService",
|
||||||
|
"implementation": "NotificationService.js",
|
||||||
|
"depends": [ "$timeout", "DEFAULT_AUTO_DISMISS",
|
||||||
|
"MINIMIZE_TIMEOUT" ]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
<span ng-show="notifications.length > 0" class="status block"
|
||||||
|
ng-class="highest.severity"
|
||||||
|
ng-controller="NotificationIndicatorController">
|
||||||
|
<span class="ui-symbol status-indicator"></span>
|
||||||
|
<span class="label">
|
||||||
|
<a ng-click="showNotificationsList()">{{notifications.length}}
|
||||||
|
Notifications</a>
|
||||||
|
</span>
|
||||||
|
<span class="count">{{notifications.length}}</span>
|
||||||
|
</span>
|
35
platform/commonUI/notification/src/NotificationIndicator.js
Normal file
35
platform/commonUI/notification/src/NotificationIndicator.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT Web includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
/*global define,window*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
[],
|
||||||
|
function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
function NotificationIndicator() {}
|
||||||
|
|
||||||
|
NotificationIndicator.template = 'notificationIndicatorTemplate';
|
||||||
|
|
||||||
|
return NotificationIndicator;
|
||||||
|
}
|
||||||
|
);
|
@ -0,0 +1,67 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT Web includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
/*global define*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
[],
|
||||||
|
function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides an indicator that is visible when there are
|
||||||
|
* banner notifications that have been minimized. Will also indicate
|
||||||
|
* the number of notifications. Notifications can be viewed by
|
||||||
|
* clicking on the indicator to launch a dialog showing a list of
|
||||||
|
* notifications.
|
||||||
|
* @param $scope
|
||||||
|
* @param notificationService
|
||||||
|
* @param dialogService
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function NotificationIndicatorController($scope, notificationService, dialogService) {
|
||||||
|
$scope.notifications = notificationService.notifications;
|
||||||
|
$scope.highest = notificationService.highest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Launch a dialog showing a list of current notifications.
|
||||||
|
*/
|
||||||
|
$scope.showNotificationsList = function(){
|
||||||
|
dialogService.getDialogResponse('overlay-message-list', {
|
||||||
|
dialog: {
|
||||||
|
title: "Messages",
|
||||||
|
//Launch the message list dialog with the models
|
||||||
|
// from the notifications
|
||||||
|
messages: notificationService.notifications && notificationService.notifications.map(function(notification){
|
||||||
|
return notification.model;
|
||||||
|
})
|
||||||
|
},
|
||||||
|
cancel: function(){
|
||||||
|
dialogService.dismiss();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return NotificationIndicatorController;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
387
platform/commonUI/notification/src/NotificationService.js
Normal file
387
platform/commonUI/notification/src/NotificationService.js
Normal file
@ -0,0 +1,387 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT Web includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
/*global define*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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(
|
||||||
|
[],
|
||||||
|
function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 | number} 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 {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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 DEFAULT_AUTO_DISMISS The period of time that an
|
||||||
|
* auto-dismissed message will be displayed for.
|
||||||
|
* @param MINIMIZE_TIMEOUT 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, DEFAULT_AUTO_DISMISS, MINIMIZE_TIMEOUT) {
|
||||||
|
this.notifications = [];
|
||||||
|
this.$timeout = $timeout;
|
||||||
|
this.highest ={ severity: "info" };
|
||||||
|
this.DEFAULT_AUTO_DISMISS = DEFAULT_AUTO_DISMISS;
|
||||||
|
this.MINIMIZE_TIMEOUT = MINIMIZE_TIMEOUT;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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}
|
||||||
|
*/
|
||||||
|
function minimize (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_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
|
||||||
|
*/
|
||||||
|
function dismiss (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());
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Depending on the severity of the notification will selectively
|
||||||
|
* dismiss or minimize where appropriate.
|
||||||
|
*/
|
||||||
|
function dismissOrMinimize (notification){
|
||||||
|
|
||||||
|
//For now minimize everything, and have discussion around which
|
||||||
|
//kind of messages should or should not be in the minimized
|
||||||
|
//notifications list
|
||||||
|
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
|
||||||
|
* @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.autoDismiss = notificationModel.autoDismiss || true;
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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,
|
||||||
|
ordinality = {
|
||||||
|
"info": 1,
|
||||||
|
"alert": 2,
|
||||||
|
"error": 3
|
||||||
|
},
|
||||||
|
activeNotification = self.active.notification;
|
||||||
|
|
||||||
|
notification = {
|
||||||
|
model: notificationModel,
|
||||||
|
minimize: function() {
|
||||||
|
minimize(self, notification);
|
||||||
|
},
|
||||||
|
dismiss: function(){
|
||||||
|
dismiss(self, notification);
|
||||||
|
},
|
||||||
|
dismissOrMinimize: function(){
|
||||||
|
dismissOrMinimize(notification);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
notificationModel.severity = notificationModel.severity || "info";
|
||||||
|
if (notificationModel.autoDismiss === true){
|
||||||
|
notificationModel.autoDismiss = this.DEFAULT_AUTO_DISMISS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ordinality[notificationModel.severity.toLowerCase()] > ordinality[this.highest.severity.toLowerCase()]){
|
||||||
|
this.highest.severity = notificationModel.severity;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.notifications.push(notification);
|
||||||
|
|
||||||
|
/*
|
||||||
|
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 notifcation has been added to queue and will be
|
||||||
|
serviced as soon as possible.
|
||||||
|
*/
|
||||||
|
this.active.timeout = this.$timeout(function () {
|
||||||
|
activeNotification.dismissOrMinimize();
|
||||||
|
}, this.DEFAULT_AUTO_DISMISS);
|
||||||
|
}
|
||||||
|
|
||||||
|
return notification;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used internally by the NotificationService
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
NotificationService.prototype.setActiveNotification =
|
||||||
|
function (notification) {
|
||||||
|
|
||||||
|
var self = this,
|
||||||
|
timeout;
|
||||||
|
this.active.notification = notification;
|
||||||
|
/*
|
||||||
|
If autoDismiss has been specified, OR there are other
|
||||||
|
notifications queued for display, setup a timeout to
|
||||||
|
dismiss the dialog.
|
||||||
|
*/
|
||||||
|
if (notification && (notification.model.autoDismiss
|
||||||
|
|| this.selectNextNotification())) {
|
||||||
|
|
||||||
|
timeout = notification.model.autoDismiss || this.DEFAULT_AUTO_DISMISS;
|
||||||
|
this.active.timeout = this.$timeout(function () {
|
||||||
|
notification.dismissOrMinimize();
|
||||||
|
}, 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;
|
||||||
|
}
|
||||||
|
);
|
@ -0,0 +1,78 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT Web includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
/*global define,describe,it,expect,beforeEach,waitsFor,jasmine */
|
||||||
|
|
||||||
|
define(
|
||||||
|
['../src/NotificationIndicatorController'],
|
||||||
|
function (NotificationIndicatorController) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
describe("The notification indicator controller ", function () {
|
||||||
|
var mockNotificationService,
|
||||||
|
mockScope,
|
||||||
|
mockDialogService;
|
||||||
|
|
||||||
|
beforeEach(function(){
|
||||||
|
mockNotificationService = jasmine.createSpy("notificationService");
|
||||||
|
mockScope = jasmine.createSpy("$scope");
|
||||||
|
mockDialogService = jasmine.createSpyObj(
|
||||||
|
"dialogService",
|
||||||
|
["getDialogResponse","dismiss"]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("exposes the highest notification severity to the template", function() {
|
||||||
|
mockNotificationService.highest = {
|
||||||
|
severity: "error"
|
||||||
|
};
|
||||||
|
var controller = new NotificationIndicatorController(mockScope, mockNotificationService, mockDialogService);
|
||||||
|
expect(mockScope.highest).toBeTruthy();
|
||||||
|
expect(mockScope.highest.severity).toBe("error");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("invokes the dialog service to show list of messages", function() {
|
||||||
|
var controller = new NotificationIndicatorController(mockScope, mockNotificationService, mockDialogService);
|
||||||
|
expect(mockScope.showNotificationsList).toBeDefined();
|
||||||
|
mockScope.showNotificationsList();
|
||||||
|
expect(mockDialogService.getDialogResponse).toHaveBeenCalled();
|
||||||
|
expect(mockDialogService.getDialogResponse.mostRecentCall.args[0]).toBe('overlay-message-list');
|
||||||
|
expect(mockDialogService.getDialogResponse.mostRecentCall.args[1].dialog).toBeDefined();
|
||||||
|
expect(mockDialogService.getDialogResponse.mostRecentCall.args[1].cancel).toBeDefined();
|
||||||
|
//Invoke the cancel callback
|
||||||
|
mockDialogService.getDialogResponse.mostRecentCall.args[1].cancel();
|
||||||
|
expect(mockDialogService.dismiss).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("provides a means of dismissing the message list", function() {
|
||||||
|
var controller = new NotificationIndicatorController(mockScope, mockNotificationService, mockDialogService);
|
||||||
|
expect(mockScope.showNotificationsList).toBeDefined();
|
||||||
|
mockScope.showNotificationsList();
|
||||||
|
expect(mockDialogService.getDialogResponse).toHaveBeenCalled();
|
||||||
|
expect(mockDialogService.getDialogResponse.mostRecentCall.args[1].cancel).toBeDefined();
|
||||||
|
//Invoke the cancel callback
|
||||||
|
mockDialogService.getDialogResponse.mostRecentCall.args[1].cancel();
|
||||||
|
expect(mockDialogService.dismiss).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
219
platform/commonUI/notification/test/NotificationServiceSpec.js
Normal file
219
platform/commonUI/notification/test/NotificationServiceSpec.js
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT Web includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
/*global define,describe,it,expect,beforeEach,waitsFor,jasmine */
|
||||||
|
|
||||||
|
define(
|
||||||
|
['../src/NotificationService'],
|
||||||
|
function (NotificationService) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
describe("The notification service ", function () {
|
||||||
|
var notificationService,
|
||||||
|
mockTimeout,
|
||||||
|
mockAutoDismiss,
|
||||||
|
mockMinimizeTimeout,
|
||||||
|
successModel,
|
||||||
|
errorModel;
|
||||||
|
|
||||||
|
beforeEach(function(){
|
||||||
|
mockTimeout = jasmine.createSpy("$timeout");
|
||||||
|
mockAutoDismiss = mockMinimizeTimeout = 1000;
|
||||||
|
notificationService = new NotificationService(
|
||||||
|
mockTimeout, mockAutoDismiss, mockMinimizeTimeout);
|
||||||
|
successModel = {
|
||||||
|
title: "Mock Success Notification",
|
||||||
|
severity: "info"
|
||||||
|
};
|
||||||
|
errorModel = {
|
||||||
|
title: "Mock Error Notification",
|
||||||
|
severity: "error"
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it("gets a new success notification, making" +
|
||||||
|
" the notification active", function() {
|
||||||
|
var activeNotification;
|
||||||
|
notificationService.notify(successModel);
|
||||||
|
activeNotification = notificationService.getActiveNotification();
|
||||||
|
expect(activeNotification.model).toBe(successModel);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows specification of an info notification given just a" +
|
||||||
|
" title, making the notification active", function() {
|
||||||
|
var activeNotification,
|
||||||
|
notificationTitle = "Test info notification";
|
||||||
|
notificationService.info(notificationTitle);
|
||||||
|
activeNotification = notificationService.getActiveNotification();
|
||||||
|
expect(activeNotification.model.title).toBe(notificationTitle);
|
||||||
|
expect(activeNotification.model.severity).toBe("info");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("gets a new success notification with" +
|
||||||
|
" numerical auto-dismiss specified. ", function() {
|
||||||
|
var activeNotification;
|
||||||
|
successModel.autoDismiss = 1000;
|
||||||
|
notificationService.notify(successModel);
|
||||||
|
activeNotification = notificationService.getActiveNotification();
|
||||||
|
expect(activeNotification.model).toBe(successModel);
|
||||||
|
mockTimeout.mostRecentCall.args[0]();
|
||||||
|
expect(mockTimeout.calls.length).toBe(2);
|
||||||
|
mockTimeout.mostRecentCall.args[0]();
|
||||||
|
activeNotification = notificationService.getActiveNotification();
|
||||||
|
expect(activeNotification).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("gets a new notification with" +
|
||||||
|
" boolean auto-dismiss specified. ", function() {
|
||||||
|
var activeNotification;
|
||||||
|
successModel.autoDismiss = true;
|
||||||
|
notificationService.notify(successModel);
|
||||||
|
activeNotification = notificationService.getActiveNotification();
|
||||||
|
expect(activeNotification.model).toBe(successModel);
|
||||||
|
mockTimeout.mostRecentCall.args[0]();
|
||||||
|
expect(mockTimeout.calls.length).toBe(2);
|
||||||
|
mockTimeout.mostRecentCall.args[0]();
|
||||||
|
activeNotification = notificationService.getActiveNotification();
|
||||||
|
expect(activeNotification).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows minimization of notifications", function() {
|
||||||
|
var notification,
|
||||||
|
activeNotification;
|
||||||
|
|
||||||
|
successModel.autoDismiss = false;
|
||||||
|
notification = notificationService.notify(successModel);
|
||||||
|
|
||||||
|
activeNotification = notificationService.getActiveNotification();
|
||||||
|
expect(activeNotification.model).toBe(successModel);
|
||||||
|
notification.minimize();
|
||||||
|
mockTimeout.mostRecentCall.args[0]();
|
||||||
|
activeNotification = notificationService.getActiveNotification();
|
||||||
|
expect(activeNotification).toBeUndefined();
|
||||||
|
expect(notificationService.notifications.length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows dismissal of notifications", function() {
|
||||||
|
var notification,
|
||||||
|
activeNotification;
|
||||||
|
|
||||||
|
successModel.autoDismiss = false;
|
||||||
|
notification = notificationService.notify(successModel);
|
||||||
|
|
||||||
|
activeNotification = notificationService.getActiveNotification();
|
||||||
|
expect(activeNotification.model).toBe(successModel);
|
||||||
|
notification.dismiss();
|
||||||
|
activeNotification = notificationService.getActiveNotification();
|
||||||
|
expect(activeNotification).toBeUndefined();
|
||||||
|
expect(notificationService.notifications.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe(" gets called with multiple notifications", function(){
|
||||||
|
it("auto-dismisses the previously active notification, making" +
|
||||||
|
" the new notification active", function() {
|
||||||
|
var activeNotification;
|
||||||
|
//First pre-load with a info message
|
||||||
|
notificationService.notify(successModel);
|
||||||
|
activeNotification =
|
||||||
|
notificationService.getActiveNotification();
|
||||||
|
//Initially expect the active notification to be info
|
||||||
|
expect(activeNotification.model).toBe(successModel);
|
||||||
|
//Then notify of an error
|
||||||
|
notificationService.notify(errorModel);
|
||||||
|
//But it should be auto-dismissed and replaced with the
|
||||||
|
// error notification
|
||||||
|
mockTimeout.mostRecentCall.args[0]();
|
||||||
|
//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.
|
||||||
|
mockTimeout.mostRecentCall.args[0]();
|
||||||
|
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(successModel);
|
||||||
|
expect(notificationService.notifications.length).toEqual(2);
|
||||||
|
//Mock the auto-minimize
|
||||||
|
mockTimeout.mostRecentCall.args[0]();
|
||||||
|
//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.
|
||||||
|
mockTimeout.mostRecentCall.args[0]();
|
||||||
|
//Previous error message should be minimized, not
|
||||||
|
// dismissed
|
||||||
|
expect(notificationService.notifications.length).toEqual(2);
|
||||||
|
activeNotification =
|
||||||
|
notificationService.getActiveNotification();
|
||||||
|
expect(activeNotification.model).toBe(successModel);
|
||||||
|
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
|
||||||
|
mockTimeout.mostRecentCall.args[0]();
|
||||||
|
//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.
|
||||||
|
mockTimeout.mostRecentCall.args[0]();
|
||||||
|
//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
|
||||||
|
mockTimeout.mostRecentCall.args[0]();
|
||||||
|
//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.
|
||||||
|
mockTimeout.mostRecentCall.args[0]();
|
||||||
|
activeNotification =
|
||||||
|
notificationService.getActiveNotification();
|
||||||
|
expect(activeNotification.model).toBe(error3);
|
||||||
|
expect(error2.minimized).toEqual(true);
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
4
platform/commonUI/notification/test/suite.json
Normal file
4
platform/commonUI/notification/test/suite.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
[
|
||||||
|
"NotificationService",
|
||||||
|
"NotificationIndicatorController"
|
||||||
|
]
|
File diff suppressed because it is too large
Load Diff
@ -10,7 +10,7 @@ $colorInteriorBorder: rgba($colorBodyFg, 0.1);
|
|||||||
$colorA: #ccc;
|
$colorA: #ccc;
|
||||||
$colorAHov: #fff;
|
$colorAHov: #fff;
|
||||||
$contrastRatioPercent: 7%;
|
$contrastRatioPercent: 7%;
|
||||||
$basicCr: 2px;
|
$basicCr: 3px;
|
||||||
$controlCr: 3px;
|
$controlCr: 3px;
|
||||||
$smallCr: 2px;
|
$smallCr: 2px;
|
||||||
|
|
||||||
@ -68,6 +68,8 @@ $colorFormRequired: $colorAlt1;
|
|||||||
$colorFormValid: #33cc33;
|
$colorFormValid: #33cc33;
|
||||||
$colorFormError: #cc0000;
|
$colorFormError: #cc0000;
|
||||||
$colorFormInvalid: #ff3300;
|
$colorFormInvalid: #ff3300;
|
||||||
|
$colorFormLines: rgba(#fff, 0.1);
|
||||||
|
$colorFormSectionHeader: rgba(#000, 0.2);
|
||||||
$colorInputBg: rgba(#fff, 0.1);
|
$colorInputBg: rgba(#fff, 0.1);
|
||||||
$colorInputFg: pullForward($colorBodyFg, 20%);
|
$colorInputFg: pullForward($colorBodyFg, 20%);
|
||||||
$colorFormText: rgba(#fff, 0.5);
|
$colorFormText: rgba(#fff, 0.5);
|
||||||
@ -81,6 +83,18 @@ $colorInspectorPropVal: pullForward($colorInspectorFg, 15%);
|
|||||||
$colorInspectorSectionHeaderBg: pullForward($colorInspectorBg, 5%);
|
$colorInspectorSectionHeaderBg: pullForward($colorInspectorBg, 5%);
|
||||||
$colorInspectorSectionHeaderFg: pullForward($colorInspectorBg, 40%);
|
$colorInspectorSectionHeaderFg: pullForward($colorInspectorBg, 40%);
|
||||||
|
|
||||||
|
// Status colors, mainly used for messaging and item ancillary symbols
|
||||||
|
$colorStatusFg: #ccc;
|
||||||
|
$colorStatusDefault: #ccc;
|
||||||
|
$colorStatusInfo: #62ba72;
|
||||||
|
$colorStatusAlert: #ffa66d;
|
||||||
|
$colorStatusError: #d4585c;
|
||||||
|
$colorProgressBarOuter: rgba(#000, 0.1);
|
||||||
|
$colorProgressBarAmt: $colorKey;
|
||||||
|
$progressBarHOverlay: 15px;
|
||||||
|
$progressBarStripeW: 20px;
|
||||||
|
$shdwStatusIc: rgba(black, 0.4) 0 1px 2px;
|
||||||
|
|
||||||
// Selects
|
// Selects
|
||||||
$colorSelectBg: $colorBtnBg;
|
$colorSelectBg: $colorBtnBg;
|
||||||
$colorSelectFg: $colorBtnFg;
|
$colorSelectFg: $colorBtnFg;
|
||||||
@ -178,4 +192,4 @@ $colorAboutLink: #84b3ff;
|
|||||||
|
|
||||||
// Loading
|
// Loading
|
||||||
$colorLoadingBg: rgba($colorBodyFg, 0.2);
|
$colorLoadingBg: rgba($colorBodyFg, 0.2);
|
||||||
$colorLoadingFg: $colorAlt1;
|
$colorLoadingFg: $colorAlt1;
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -72,6 +72,20 @@ $colorInputBg: $colorGenBg;
|
|||||||
$colorInputFg: $colorBodyFg;
|
$colorInputFg: $colorBodyFg;
|
||||||
$colorFormText: pushBack($colorBodyFg, 10%);
|
$colorFormText: pushBack($colorBodyFg, 10%);
|
||||||
$colorInputIcon: pushBack($colorBodyFg, 25%);
|
$colorInputIcon: pushBack($colorBodyFg, 25%);
|
||||||
|
|
||||||
|
// Status colors, mainly used for messaging and item ancillary symbols
|
||||||
|
$colorStatusFg: #fff;
|
||||||
|
$colorStatusDefault: #ccc;
|
||||||
|
$colorStatusInfo: #60ba7b;
|
||||||
|
$colorStatusAlert: #ffb66c;
|
||||||
|
$colorStatusError: #c96b68;
|
||||||
|
$colorProgressBarOuter: rgba(#000, 0.1);
|
||||||
|
$colorProgressBarAmt: #0a0;
|
||||||
|
$progressBarHOverlay: 15px;
|
||||||
|
$progressBarStripeW: 20px;
|
||||||
|
$shdwStatusIc: rgba(white, 0.8) 0 0px 5px;
|
||||||
|
|
||||||
|
// Selects
|
||||||
$colorSelectBg: #ddd;
|
$colorSelectBg: #ddd;
|
||||||
$colorSelectFg: $colorBodyFg;
|
$colorSelectFg: $colorBodyFg;
|
||||||
|
|
||||||
|
@ -36,9 +36,9 @@
|
|||||||
{
|
{
|
||||||
"key": "TIME_CONDUCTOR_DOMAINS",
|
"key": "TIME_CONDUCTOR_DOMAINS",
|
||||||
"value": [
|
"value": [
|
||||||
{ "key": "time", "name": "Time" },
|
{ "key": "time", "name": "UTC", "format": "utc" }
|
||||||
{ "key": "yesterday", "name": "Yesterday" }
|
|
||||||
],
|
],
|
||||||
|
"priority": "fallback",
|
||||||
"comment": "Placeholder; to be replaced by inspection of available domains."
|
"comment": "Placeholder; to be replaced by inspection of available domains."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
<mct-include key="'time-controller'"
|
<mct-include key="'time-controller'"
|
||||||
|
parameters='parameters'
|
||||||
ng-model='ngModel.conductor'>
|
ng-model='ngModel.conductor'>
|
||||||
</mct-include>
|
</mct-include>
|
||||||
<mct-control key="'select'"
|
<mct-control key="'select'"
|
||||||
|
@ -27,7 +27,10 @@ define(
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var TEMPLATE = [
|
var TEMPLATE = [
|
||||||
"<mct-include key=\"'time-conductor'\" ng-model='ngModel' class='l-time-controller'>",
|
"<mct-include key=\"'time-conductor'\" ",
|
||||||
|
"ng-model='ngModel' ",
|
||||||
|
"parameters='parameters' ",
|
||||||
|
"class='l-time-controller'>",
|
||||||
"</mct-include>"
|
"</mct-include>"
|
||||||
].join(''),
|
].join(''),
|
||||||
THROTTLE_MS = 200,
|
THROTTLE_MS = 200,
|
||||||
@ -74,11 +77,11 @@ define(
|
|||||||
broadcastBounds;
|
broadcastBounds;
|
||||||
|
|
||||||
// Combine start/end times into a single object
|
// Combine start/end times into a single object
|
||||||
function bounds(start, end) {
|
function bounds() {
|
||||||
return {
|
return {
|
||||||
start: conductor.displayStart(),
|
start: conductor.displayStart(),
|
||||||
end: conductor.displayEnd(),
|
end: conductor.displayEnd(),
|
||||||
domain: conductor.domain()
|
domain: conductor.domain().key
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,12 +100,9 @@ define(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateDomain(value) {
|
function updateDomain(value) {
|
||||||
conductor.domain(value);
|
var newDomain = conductor.domain(value);
|
||||||
repScope.$broadcast('telemetry:display:bounds', bounds(
|
conductorScope.parameters.format = newDomain.format;
|
||||||
conductor.displayStart(),
|
repScope.$broadcast('telemetry:display:bounds', bounds());
|
||||||
conductor.displayEnd(),
|
|
||||||
conductor.domain()
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// telemetry domain metadata -> option for a select control
|
// telemetry domain metadata -> option for a select control
|
||||||
@ -130,7 +130,8 @@ define(
|
|||||||
{ outer: bounds(), inner: bounds() };
|
{ outer: bounds(), inner: bounds() };
|
||||||
conductorScope.ngModel.options =
|
conductorScope.ngModel.options =
|
||||||
conductor.domainOptions().map(makeOption);
|
conductor.domainOptions().map(makeOption);
|
||||||
conductorScope.ngModel.domain = conductor.domain();
|
conductorScope.ngModel.domain = conductor.domain().key;
|
||||||
|
conductorScope.parameters = {};
|
||||||
|
|
||||||
conductorScope
|
conductorScope
|
||||||
.$watch('ngModel.conductor.inner.start', updateConductorInner);
|
.$watch('ngModel.conductor.inner.start', updateConductorInner);
|
||||||
|
@ -51,7 +51,7 @@ define(
|
|||||||
request = request || {};
|
request = request || {};
|
||||||
request.start = start;
|
request.start = start;
|
||||||
request.end = end;
|
request.end = end;
|
||||||
request.domain = domain;
|
request.domain = domain.key;
|
||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ define(
|
|||||||
function TimeConductor(start, end, domains) {
|
function TimeConductor(start, end, domains) {
|
||||||
this.range = { start: start, end: end };
|
this.range = { start: start, end: end };
|
||||||
this.domains = domains;
|
this.domains = domains;
|
||||||
this.activeDomain = domains[0].key;
|
this.activeDomain = domains[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -73,7 +73,7 @@ define(
|
|||||||
/**
|
/**
|
||||||
* Get available domain options which can be used to bound time
|
* Get available domain options which can be used to bound time
|
||||||
* selection.
|
* selection.
|
||||||
* @returns {TelemetryDomain[]} available domains
|
* @returns {TelemetryDomainMetadata[]} available domains
|
||||||
*/
|
*/
|
||||||
TimeConductor.prototype.domainOptions = function () {
|
TimeConductor.prototype.domainOptions = function () {
|
||||||
return this.domains;
|
return this.domains;
|
||||||
@ -82,19 +82,21 @@ define(
|
|||||||
/**
|
/**
|
||||||
* Get or set (if called with an argument) the active domain.
|
* Get or set (if called with an argument) the active domain.
|
||||||
* @param {string} [key] the key identifying the domain choice
|
* @param {string} [key] the key identifying the domain choice
|
||||||
* @returns {TelemetryDomain} the active telemetry domain
|
* @returns {TelemetryDomainMetadata} the active telemetry domain
|
||||||
*/
|
*/
|
||||||
TimeConductor.prototype.domain = function (key) {
|
TimeConductor.prototype.domain = function (key) {
|
||||||
function matchesKey(domain) {
|
var i;
|
||||||
return domain.key === key;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (arguments.length > 0) {
|
if (arguments.length > 0) {
|
||||||
if (!this.domains.some(matchesKey)) {
|
for (i = 0; i < this.domains.length; i += 1) {
|
||||||
throw new Error("Unknown domain " + key);
|
if (this.domains[i].key === key) {
|
||||||
|
return (this.activeDomain = this.domains[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.activeDomain = key;
|
|
||||||
|
throw new Error("Unknown domain " + key);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.activeDomain;
|
return this.activeDomain;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -129,7 +129,7 @@ define(
|
|||||||
it("exposes conductor state in scope", function () {
|
it("exposes conductor state in scope", function () {
|
||||||
mockConductor.displayStart.andReturn(1977);
|
mockConductor.displayStart.andReturn(1977);
|
||||||
mockConductor.displayEnd.andReturn(1984);
|
mockConductor.displayEnd.andReturn(1984);
|
||||||
mockConductor.domain.andReturn('d');
|
mockConductor.domain.andReturn({ key: 'd' });
|
||||||
representer.represent(testViews[0], {});
|
representer.represent(testViews[0], {});
|
||||||
|
|
||||||
expect(mockNewScope.ngModel.conductor).toEqual({
|
expect(mockNewScope.ngModel.conductor).toEqual({
|
||||||
@ -219,7 +219,7 @@ define(
|
|||||||
representer.represent(testViews[0], null);
|
representer.represent(testViews[0], null);
|
||||||
|
|
||||||
expect(mockNewScope.ngModel.domain)
|
expect(mockNewScope.ngModel.domain)
|
||||||
.toEqual(mockConductor.domain());
|
.toEqual(mockConductor.domain().key);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("exposes domain options in scope", function () {
|
it("exposes domain options in scope", function () {
|
||||||
|
@ -77,7 +77,7 @@ define(
|
|||||||
|
|
||||||
mockConductor.displayStart.andReturn(42);
|
mockConductor.displayStart.andReturn(42);
|
||||||
mockConductor.displayEnd.andReturn(1977);
|
mockConductor.displayEnd.andReturn(1977);
|
||||||
mockConductor.domain.andReturn("testDomain");
|
mockConductor.domain.andReturn({ key: "testDomain" });
|
||||||
|
|
||||||
decorator = new ConductorTelemetryDecorator(
|
decorator = new ConductorTelemetryDecorator(
|
||||||
mockConductorService,
|
mockConductorService,
|
||||||
@ -104,7 +104,7 @@ define(
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("with domain selection", function () {
|
it("with domain selection", function () {
|
||||||
expect(request.domain).toEqual(mockConductor.domain());
|
expect(request.domain).toEqual(mockConductor.domain().key);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -127,7 +127,7 @@ define(
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("with domain selection", function () {
|
it("with domain selection", function () {
|
||||||
expect(request.domain).toEqual(mockConductor.domain());
|
expect(request.domain).toEqual(mockConductor.domain().key);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -59,12 +59,12 @@ define(
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("exposes the current domain choice", function () {
|
it("exposes the current domain choice", function () {
|
||||||
expect(conductor.domain()).toEqual(testDomains[0].key);
|
expect(conductor.domain()).toEqual(testDomains[0]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("allows the domain choice to be changed", function () {
|
it("allows the domain choice to be changed", function () {
|
||||||
conductor.domain(testDomains[1].key);
|
conductor.domain(testDomains[1].key);
|
||||||
expect(conductor.domain()).toEqual(testDomains[1].key);
|
expect(conductor.domain()).toEqual(testDomains[1]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("throws an error on attempts to set an invalid domain", function () {
|
it("throws an error on attempts to set an invalid domain", function () {
|
||||||
|
@ -31,10 +31,19 @@ define(
|
|||||||
"./elements/PlotPalette",
|
"./elements/PlotPalette",
|
||||||
"./elements/PlotAxis",
|
"./elements/PlotAxis",
|
||||||
"./elements/PlotLimitTracker",
|
"./elements/PlotLimitTracker",
|
||||||
|
"./elements/PlotTelemetryFormatter",
|
||||||
"./modes/PlotModeOptions",
|
"./modes/PlotModeOptions",
|
||||||
"./SubPlotFactory"
|
"./SubPlotFactory"
|
||||||
],
|
],
|
||||||
function (PlotUpdater, PlotPalette, PlotAxis, PlotLimitTracker, PlotModeOptions, SubPlotFactory) {
|
function (
|
||||||
|
PlotUpdater,
|
||||||
|
PlotPalette,
|
||||||
|
PlotAxis,
|
||||||
|
PlotLimitTracker,
|
||||||
|
PlotTelemetryFormatter,
|
||||||
|
PlotModeOptions,
|
||||||
|
SubPlotFactory
|
||||||
|
) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var AXIS_DEFAULTS = [
|
var AXIS_DEFAULTS = [
|
||||||
@ -62,7 +71,10 @@ define(
|
|||||||
PLOT_FIXED_DURATION
|
PLOT_FIXED_DURATION
|
||||||
) {
|
) {
|
||||||
var self = this,
|
var self = this,
|
||||||
subPlotFactory = new SubPlotFactory(telemetryFormatter),
|
plotTelemetryFormatter =
|
||||||
|
new PlotTelemetryFormatter(telemetryFormatter),
|
||||||
|
subPlotFactory =
|
||||||
|
new SubPlotFactory(plotTelemetryFormatter),
|
||||||
cachedObjects = [],
|
cachedObjects = [],
|
||||||
updater,
|
updater,
|
||||||
lastBounds,
|
lastBounds,
|
||||||
@ -71,10 +83,9 @@ define(
|
|||||||
// Populate the scope with axis information (specifically, options
|
// Populate the scope with axis information (specifically, options
|
||||||
// available for each axis.)
|
// available for each axis.)
|
||||||
function setupAxes(metadatas) {
|
function setupAxes(metadatas) {
|
||||||
$scope.axes = [
|
$scope.axes.forEach(function (axis) {
|
||||||
new PlotAxis("domain", metadatas, AXIS_DEFAULTS[0]),
|
axis.updateMetadata(metadatas);
|
||||||
new PlotAxis("range", metadatas, AXIS_DEFAULTS[1])
|
});
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trigger an update of a specific subplot;
|
// Trigger an update of a specific subplot;
|
||||||
@ -125,37 +136,49 @@ define(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getUpdater() {
|
||||||
|
if (!updater) {
|
||||||
|
recreateUpdater();
|
||||||
|
}
|
||||||
|
return updater;
|
||||||
|
}
|
||||||
|
|
||||||
// Handle new telemetry data in this plot
|
// Handle new telemetry data in this plot
|
||||||
function updateValues() {
|
function updateValues() {
|
||||||
self.pending = false;
|
self.pending = false;
|
||||||
if (handle) {
|
if (handle) {
|
||||||
setupModes(handle.getTelemetryObjects());
|
setupModes(handle.getTelemetryObjects());
|
||||||
}
|
setupAxes(handle.getMetadata());
|
||||||
if (updater) {
|
getUpdater().update();
|
||||||
updater.update();
|
|
||||||
self.modeOptions.getModeHandler().plotTelemetry(updater);
|
self.modeOptions.getModeHandler().plotTelemetry(updater);
|
||||||
}
|
|
||||||
if (self.limitTracker) {
|
|
||||||
self.limitTracker.update();
|
self.limitTracker.update();
|
||||||
|
self.update();
|
||||||
}
|
}
|
||||||
self.update();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display new historical data as it becomes available
|
// Display new historical data as it becomes available
|
||||||
function addHistoricalData(domainObject, series) {
|
function addHistoricalData(domainObject, series) {
|
||||||
self.pending = false;
|
self.pending = false;
|
||||||
updater.addHistorical(domainObject, series);
|
getUpdater().addHistorical(domainObject, series);
|
||||||
self.modeOptions.getModeHandler().plotTelemetry(updater);
|
self.modeOptions.getModeHandler().plotTelemetry(updater);
|
||||||
self.update();
|
self.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Issue a new request for historical telemetry
|
// Issue a new request for historical telemetry
|
||||||
function requestTelemetry() {
|
function requestTelemetry() {
|
||||||
if (handle && updater) {
|
if (handle) {
|
||||||
handle.request({}, addHistoricalData);
|
handle.request({}, addHistoricalData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Requery for data entirely
|
||||||
|
function replot() {
|
||||||
|
if (handle) {
|
||||||
|
updater = undefined;
|
||||||
|
requestTelemetry();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create a new subscription; telemetrySubscriber gets
|
// Create a new subscription; telemetrySubscriber gets
|
||||||
// to do the meaningful work here.
|
// to do the meaningful work here.
|
||||||
function subscribe(domainObject) {
|
function subscribe(domainObject) {
|
||||||
@ -167,12 +190,7 @@ define(
|
|||||||
updateValues,
|
updateValues,
|
||||||
true // Lossless
|
true // Lossless
|
||||||
);
|
);
|
||||||
if (handle) {
|
replot();
|
||||||
setupModes(handle.getTelemetryObjects());
|
|
||||||
setupAxes(handle.getMetadata());
|
|
||||||
recreateUpdater();
|
|
||||||
requestTelemetry();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Release the current subscription (called when scope is destroyed)
|
// Release the current subscription (called when scope is destroyed)
|
||||||
@ -185,12 +203,22 @@ define(
|
|||||||
|
|
||||||
// Respond to a display bounds change (requery for data)
|
// Respond to a display bounds change (requery for data)
|
||||||
function changeDisplayBounds(event, bounds) {
|
function changeDisplayBounds(event, bounds) {
|
||||||
|
var domainAxis = $scope.axes[0];
|
||||||
|
|
||||||
|
domainAxis.chooseOption(bounds.domain);
|
||||||
|
plotTelemetryFormatter
|
||||||
|
.setDomainFormat(domainAxis.active.format);
|
||||||
|
|
||||||
self.pending = true;
|
self.pending = true;
|
||||||
releaseSubscription();
|
releaseSubscription();
|
||||||
subscribe($scope.domainObject);
|
subscribe($scope.domainObject);
|
||||||
setBasePanZoom(bounds);
|
setBasePanZoom(bounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateDomainFormat(format) {
|
||||||
|
plotTelemetryFormatter.setDomainFormat(format);
|
||||||
|
}
|
||||||
|
|
||||||
this.modeOptions = new PlotModeOptions([], subPlotFactory);
|
this.modeOptions = new PlotModeOptions([], subPlotFactory);
|
||||||
this.updateValues = updateValues;
|
this.updateValues = updateValues;
|
||||||
|
|
||||||
@ -202,6 +230,13 @@ define(
|
|||||||
|
|
||||||
self.pending = true;
|
self.pending = true;
|
||||||
|
|
||||||
|
// Initialize axes; will get repopulated when telemetry
|
||||||
|
// metadata becomes available.
|
||||||
|
$scope.axes = [
|
||||||
|
new PlotAxis("domains", [], AXIS_DEFAULTS[0]),
|
||||||
|
new PlotAxis("ranges", [], AXIS_DEFAULTS[1])
|
||||||
|
];
|
||||||
|
|
||||||
// Subscribe to telemetry when a domain object becomes available
|
// Subscribe to telemetry when a domain object becomes available
|
||||||
$scope.$watch('domainObject', subscribe);
|
$scope.$watch('domainObject', subscribe);
|
||||||
|
|
||||||
|
@ -121,9 +121,9 @@ define(
|
|||||||
// Utility, for map/forEach loops. Index 0 is domain,
|
// Utility, for map/forEach loops. Index 0 is domain,
|
||||||
// index 1 is range.
|
// index 1 is range.
|
||||||
function formatValue(v, i) {
|
function formatValue(v, i) {
|
||||||
return (i ?
|
return i ?
|
||||||
formatter.formatRangeValue :
|
formatter.formatRangeValue(v) :
|
||||||
formatter.formatDomainValue)(v);
|
formatter.formatDomainValue(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.hoverCoordinates = this.mousePosition &&
|
this.hoverCoordinates = this.mousePosition &&
|
||||||
|
@ -46,21 +46,9 @@ define(
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
function PlotAxis(axisType, metadatas, defaultValue) {
|
function PlotAxis(axisType, metadatas, defaultValue) {
|
||||||
var keys = {},
|
this.axisType = axisType;
|
||||||
options = [];
|
this.defaultValue = defaultValue;
|
||||||
|
this.optionKeys = {};
|
||||||
// Look through all metadata objects and assemble a list
|
|
||||||
// of all possible domain or range options
|
|
||||||
function buildOptionsForMetadata(m) {
|
|
||||||
(m[axisType] || []).forEach(function (option) {
|
|
||||||
if (!keys[option.key]) {
|
|
||||||
keys[option.key] = true;
|
|
||||||
options.push(option);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
(metadatas || []).forEach(buildOptionsForMetadata);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The currently chosen option for this axis. An
|
* The currently chosen option for this axis. An
|
||||||
@ -68,7 +56,7 @@ define(
|
|||||||
* directly form the plot template.
|
* directly form the plot template.
|
||||||
* @memberof platform/features/plot.PlotAxis#
|
* @memberof platform/features/plot.PlotAxis#
|
||||||
*/
|
*/
|
||||||
this.active = options[0] || defaultValue;
|
this.active = defaultValue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The set of options applicable for this axis;
|
* The set of options applicable for this axis;
|
||||||
@ -77,9 +65,71 @@ define(
|
|||||||
* human-readable names respectively)
|
* human-readable names respectively)
|
||||||
* @memberof platform/features/plot.PlotAxis#
|
* @memberof platform/features/plot.PlotAxis#
|
||||||
*/
|
*/
|
||||||
this.options = options;
|
this.options = [];
|
||||||
|
|
||||||
|
// Initialize options from metadata objects
|
||||||
|
this.updateMetadata(metadatas);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update axis options to reflect current metadata.
|
||||||
|
* @param {TelemetryMetadata[]} metadata objects describing
|
||||||
|
* applicable telemetry
|
||||||
|
*/
|
||||||
|
PlotAxis.prototype.updateMetadata = function (metadatas) {
|
||||||
|
var axisType = this.axisType,
|
||||||
|
optionKeys = this.optionKeys,
|
||||||
|
newOptions = {},
|
||||||
|
toAdd = [];
|
||||||
|
|
||||||
|
function isValid(option) {
|
||||||
|
return option && optionKeys[option.key];
|
||||||
|
}
|
||||||
|
|
||||||
|
metadatas.forEach(function (m) {
|
||||||
|
(m[axisType] || []).forEach(function (option) {
|
||||||
|
var key = option.key;
|
||||||
|
if (!optionKeys[key] && !newOptions[key]) {
|
||||||
|
toAdd.push(option);
|
||||||
|
}
|
||||||
|
newOptions[key] = true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
optionKeys = this.optionKeys = newOptions;
|
||||||
|
|
||||||
|
// General approach here is to avoid changing object
|
||||||
|
// instances unless something has really changed, since
|
||||||
|
// Angular is watching; don't want to trigger extra digests.
|
||||||
|
if (!this.options.every(isValid)) {
|
||||||
|
this.options = this.options.filter(isValid);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toAdd.length > 0) {
|
||||||
|
this.options = this.options.concat(toAdd);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isValid(this.active)) {
|
||||||
|
this.active = this.options[0] || this.defaultValue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the domain/range selection for this axis. If the
|
||||||
|
* provided `key` is not recognized as an option, no change
|
||||||
|
* will occur.
|
||||||
|
* @param {string} key the identifier for the domain/range
|
||||||
|
*/
|
||||||
|
PlotAxis.prototype.chooseOption = function (key) {
|
||||||
|
var self = this;
|
||||||
|
this.options.forEach(function (option) {
|
||||||
|
if (option.key === key) {
|
||||||
|
self.active = option;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return PlotAxis;
|
return PlotAxis;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,72 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT Web includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
/*global define*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
[],
|
||||||
|
function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps a `TelemetryFormatter` to provide formats for domain and
|
||||||
|
* range values; provides a single place to track domain/range
|
||||||
|
* formats within a plot, allowing other plot elements to simply
|
||||||
|
* request that values be formatted.
|
||||||
|
* @constructor
|
||||||
|
* @memberof platform/features/plot
|
||||||
|
* @implements {platform/telemetry.TelemetryFormatter}
|
||||||
|
* @param {TelemetryFormatter} telemetryFormatter the formatter
|
||||||
|
* to wrap.
|
||||||
|
*/
|
||||||
|
function PlotTelemetryFormatter(telemetryFormatter) {
|
||||||
|
this.telemetryFormatter = telemetryFormatter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify the format to use for domain values.
|
||||||
|
* @param {string} key the format's identifier
|
||||||
|
*/
|
||||||
|
PlotTelemetryFormatter.prototype.setDomainFormat = function (key) {
|
||||||
|
this.domainFormat = key;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify the format to use for range values.
|
||||||
|
* @param {string} key the format's identifier
|
||||||
|
*/
|
||||||
|
PlotTelemetryFormatter.prototype.setRangeFormat = function (key) {
|
||||||
|
this.rangeFormat = key;
|
||||||
|
};
|
||||||
|
|
||||||
|
PlotTelemetryFormatter.prototype.formatDomainValue = function (value) {
|
||||||
|
return this.telemetryFormatter
|
||||||
|
.formatDomainValue(value, this.domainFormat);
|
||||||
|
};
|
||||||
|
|
||||||
|
PlotTelemetryFormatter.prototype.formatRangeValue = function (value) {
|
||||||
|
return this.telemetryFormatter
|
||||||
|
.formatRangeValue(value, this.rangeFormat);
|
||||||
|
};
|
||||||
|
|
||||||
|
return PlotTelemetryFormatter;
|
||||||
|
}
|
||||||
|
);
|
@ -43,6 +43,14 @@ define(
|
|||||||
this.formatter = formatter;
|
this.formatter = formatter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For phantomjs compatibility, for headless testing
|
||||||
|
// (Function.prototype.bind unsupported)
|
||||||
|
function bind(fn, thisObj) {
|
||||||
|
return fn.bind ? fn.bind(thisObj) : function () {
|
||||||
|
return fn.apply(thisObj, arguments);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Generate ticks; interpolate from start up to
|
// Generate ticks; interpolate from start up to
|
||||||
// start + span in count steps, using the provided
|
// start + span in count steps, using the provided
|
||||||
// formatter to represent each value.
|
// formatter to represent each value.
|
||||||
@ -72,7 +80,7 @@ define(
|
|||||||
panZoom.origin[0],
|
panZoom.origin[0],
|
||||||
panZoom.dimensions[0],
|
panZoom.dimensions[0],
|
||||||
count,
|
count,
|
||||||
this.formatter.formatDomainValue
|
bind(this.formatter.formatDomainValue, this.formatter)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -87,7 +95,7 @@ define(
|
|||||||
panZoom.origin[1],
|
panZoom.origin[1],
|
||||||
panZoom.dimensions[1],
|
panZoom.dimensions[1],
|
||||||
count,
|
count,
|
||||||
this.formatter.formatRangeValue
|
bind(this.formatter.formatRangeValue, this.formatter)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -169,8 +169,9 @@ define(
|
|||||||
mockDomainObject
|
mockDomainObject
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Make an object available
|
// Make an object available; invoke handler's callback
|
||||||
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||||
|
mockHandler.handle.mostRecentCall.args[1]();
|
||||||
|
|
||||||
expect(controller.getModeOptions().length).toEqual(1);
|
expect(controller.getModeOptions().length).toEqual(1);
|
||||||
|
|
||||||
@ -181,8 +182,9 @@ define(
|
|||||||
mockDomainObject
|
mockDomainObject
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Make an object available
|
// Make an object available; invoke handler's callback
|
||||||
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||||
|
mockHandler.handle.mostRecentCall.args[1]();
|
||||||
|
|
||||||
expect(controller.getModeOptions().length).toEqual(2);
|
expect(controller.getModeOptions().length).toEqual(2);
|
||||||
});
|
});
|
||||||
|
@ -30,7 +30,12 @@ define(
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
describe("A plot axis", function () {
|
describe("A plot axis", function () {
|
||||||
var testMetadatas = [
|
var testMetadatas,
|
||||||
|
testDefault,
|
||||||
|
axis;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
testMetadatas = [
|
||||||
{
|
{
|
||||||
tests: [
|
tests: [
|
||||||
{ key: "t0", name: "T0" },
|
{ key: "t0", name: "T0" },
|
||||||
@ -52,13 +57,14 @@ define(
|
|||||||
{ key: "t6", name: "T6" }
|
{ key: "t6", name: "T6" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
];
|
||||||
testDefault = { key: "test", name: "Test" },
|
testDefault = { key: "test", name: "Test" };
|
||||||
controller = new PlotAxis("tests", testMetadatas, testDefault);
|
axis = new PlotAxis("tests", testMetadatas, testDefault);
|
||||||
|
});
|
||||||
|
|
||||||
it("pulls out a list of domain or range options", function () {
|
it("pulls out a list of domain or range options", function () {
|
||||||
// Should have filtered out duplicates, etc
|
// Should have filtered out duplicates, etc
|
||||||
expect(controller.options).toEqual([
|
expect(axis.options).toEqual([
|
||||||
{ key: "t0", name: "T0" },
|
{ key: "t0", name: "T0" },
|
||||||
{ key: "t1", name: "T1" },
|
{ key: "t1", name: "T1" },
|
||||||
{ key: "t2", name: "T2" },
|
{ key: "t2", name: "T2" },
|
||||||
@ -70,7 +76,7 @@ define(
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("chooses the first option as a default", function () {
|
it("chooses the first option as a default", function () {
|
||||||
expect(controller.active).toEqual({ key: "t0", name: "T0" });
|
expect(axis.active).toEqual({ key: "t0", name: "T0" });
|
||||||
});
|
});
|
||||||
|
|
||||||
it("falls back to a provided default if no options are present", function () {
|
it("falls back to a provided default if no options are present", function () {
|
||||||
@ -78,6 +84,26 @@ define(
|
|||||||
.toEqual(testDefault);
|
.toEqual(testDefault);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("allows options to be chosen by key", function () {
|
||||||
|
axis.chooseOption("t3");
|
||||||
|
expect(axis.active).toEqual({ key: "t3", name: "T3" });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("reflects changes to applicable metadata", function () {
|
||||||
|
axis.updateMetadata([ testMetadatas[1] ]);
|
||||||
|
expect(axis.options).toEqual([
|
||||||
|
{ key: "t0", name: "T0" },
|
||||||
|
{ key: "t2", name: "T2" }
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns the same array instance for unchanged metadata", function () {
|
||||||
|
// ...to avoid triggering extra digest cycles.
|
||||||
|
var oldInstance = axis.options;
|
||||||
|
axis.updateMetadata(testMetadatas);
|
||||||
|
expect(axis.options).toBe(oldInstance);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -0,0 +1,70 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT Web includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
["../../src/elements/PlotTelemetryFormatter"],
|
||||||
|
function (PlotTelemetryFormatter) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
describe("The PlotTelemetryFormatter", function () {
|
||||||
|
var mockFormatter,
|
||||||
|
formatter;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
mockFormatter = jasmine.createSpyObj(
|
||||||
|
'telemetryFormatter',
|
||||||
|
['formatDomainValue', 'formatRangeValue']
|
||||||
|
);
|
||||||
|
formatter = new PlotTelemetryFormatter(mockFormatter);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("using domain & range format keys", function () {
|
||||||
|
var rangeFormat = "someRangeFormat",
|
||||||
|
domainFormat = "someDomainFormat";
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
formatter.setRangeFormat(rangeFormat);
|
||||||
|
formatter.setDomainFormat(domainFormat);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("includes format in formatDomainValue calls", function () {
|
||||||
|
mockFormatter.formatDomainValue.andReturn("formatted!");
|
||||||
|
expect(formatter.formatDomainValue(12321))
|
||||||
|
.toEqual("formatted!");
|
||||||
|
expect(mockFormatter.formatDomainValue)
|
||||||
|
.toHaveBeenCalledWith(12321, domainFormat);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("includes format in formatRangeValue calls", function () {
|
||||||
|
mockFormatter.formatRangeValue.andReturn("formatted!");
|
||||||
|
expect(formatter.formatRangeValue(12321))
|
||||||
|
.toEqual("formatted!");
|
||||||
|
expect(mockFormatter.formatRangeValue)
|
||||||
|
.toHaveBeenCalledWith(12321, rangeFormat);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
@ -14,6 +14,7 @@
|
|||||||
"elements/PlotPosition",
|
"elements/PlotPosition",
|
||||||
"elements/PlotPreparer",
|
"elements/PlotPreparer",
|
||||||
"elements/PlotSeriesWindow",
|
"elements/PlotSeriesWindow",
|
||||||
|
"elements/PlotTelemetryFormatter",
|
||||||
"elements/PlotTickGenerator",
|
"elements/PlotTickGenerator",
|
||||||
"elements/PlotUpdater",
|
"elements/PlotUpdater",
|
||||||
"modes/PlotModeOptions",
|
"modes/PlotModeOptions",
|
||||||
|
@ -54,7 +54,8 @@ define(
|
|||||||
DomainColumn.prototype.getValue = function (domainObject, datum) {
|
DomainColumn.prototype.getValue = function (domainObject, datum) {
|
||||||
return {
|
return {
|
||||||
text: this.telemetryFormatter.formatDomainValue(
|
text: this.telemetryFormatter.formatDomainValue(
|
||||||
datum[this.domainMetadata.key]
|
datum[this.domainMetadata.key],
|
||||||
|
this.domainMetadata.format
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -31,7 +31,6 @@ define(
|
|||||||
function () {
|
function () {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines the mct-representation directive. This may be used to
|
* Defines the mct-representation directive. This may be used to
|
||||||
* present domain objects as HTML (with event wiring), with the
|
* present domain objects as HTML (with event wiring), with the
|
||||||
@ -96,6 +95,9 @@ define(
|
|||||||
}),
|
}),
|
||||||
toClear = [], // Properties to clear out of scope on change
|
toClear = [], // Properties to clear out of scope on change
|
||||||
counter = 0,
|
counter = 0,
|
||||||
|
couldRepresent = false,
|
||||||
|
lastId,
|
||||||
|
lastKey,
|
||||||
changeTemplate = templateLinker.link($scope, element);
|
changeTemplate = templateLinker.link($scope, element);
|
||||||
|
|
||||||
// Populate scope with any capabilities indicated by the
|
// Populate scope with any capabilities indicated by the
|
||||||
@ -141,6 +143,13 @@ define(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function unchanged(canRepresent, id, key) {
|
||||||
|
return canRepresent &&
|
||||||
|
couldRepresent &&
|
||||||
|
id === lastId &&
|
||||||
|
key === lastKey;
|
||||||
|
}
|
||||||
|
|
||||||
// General-purpose refresh mechanism; should set up the scope
|
// General-purpose refresh mechanism; should set up the scope
|
||||||
// as appropriate for current representation key and
|
// as appropriate for current representation key and
|
||||||
// domain object.
|
// domain object.
|
||||||
@ -149,7 +158,13 @@ define(
|
|||||||
representation = lookup($scope.key, domainObject),
|
representation = lookup($scope.key, domainObject),
|
||||||
path = representation && getPath(representation),
|
path = representation && getPath(representation),
|
||||||
uses = ((representation || {}).uses || []),
|
uses = ((representation || {}).uses || []),
|
||||||
canRepresent = !!(path && domainObject);
|
canRepresent = !!(path && domainObject),
|
||||||
|
id = domainObject && domainObject.getId(),
|
||||||
|
key = $scope.key;
|
||||||
|
|
||||||
|
if (unchanged(canRepresent, id, key)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Create an empty object named "representation", for this
|
// Create an empty object named "representation", for this
|
||||||
// representation to store local variables into.
|
// representation to store local variables into.
|
||||||
@ -173,6 +188,11 @@ define(
|
|||||||
delete $scope[property];
|
delete $scope[property];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// To allow simplified change detection next time around
|
||||||
|
couldRepresent = canRepresent;
|
||||||
|
lastId = id;
|
||||||
|
lastKey = key;
|
||||||
|
|
||||||
// Populate scope with fields associated with the current
|
// Populate scope with fields associated with the current
|
||||||
// domain object (if one has been passed in)
|
// domain object (if one has been passed in)
|
||||||
if (canRepresent) {
|
if (canRepresent) {
|
||||||
|
@ -92,10 +92,19 @@ define(
|
|||||||
var activeElement = element,
|
var activeElement = element,
|
||||||
activeTemplateUrl,
|
activeTemplateUrl,
|
||||||
comment = this.$compile('<!-- hidden mct element -->')(scope),
|
comment = this.$compile('<!-- hidden mct element -->')(scope),
|
||||||
|
activeScope,
|
||||||
self = this;
|
self = this;
|
||||||
|
|
||||||
|
function destroyScope() {
|
||||||
|
if (activeScope) {
|
||||||
|
activeScope.$destroy();
|
||||||
|
activeScope = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function removeElement() {
|
function removeElement() {
|
||||||
if (activeElement !== comment) {
|
if (activeElement !== comment) {
|
||||||
|
destroyScope();
|
||||||
activeElement.replaceWith(comment);
|
activeElement.replaceWith(comment);
|
||||||
activeElement = comment;
|
activeElement = comment;
|
||||||
}
|
}
|
||||||
@ -110,8 +119,10 @@ define(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function populateElement(template) {
|
function populateElement(template) {
|
||||||
element.empty();
|
destroyScope();
|
||||||
element.append(self.$compile(template)(scope));
|
activeScope = scope.$new(false);
|
||||||
|
element.html(template);
|
||||||
|
self.$compile(element.contents())(activeScope);
|
||||||
}
|
}
|
||||||
|
|
||||||
function badTemplate(templateUrl) {
|
function badTemplate(templateUrl) {
|
||||||
@ -120,22 +131,21 @@ define(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function changeTemplate(templateUrl) {
|
function changeTemplate(templateUrl) {
|
||||||
if (templateUrl !== activeTemplateUrl) {
|
if (templateUrl) {
|
||||||
if (templateUrl) {
|
destroyScope();
|
||||||
addElement();
|
addElement();
|
||||||
self.load(templateUrl).then(function (template) {
|
self.load(templateUrl).then(function (template) {
|
||||||
// Avoid race conditions
|
// Avoid race conditions
|
||||||
if (templateUrl === activeTemplateUrl) {
|
if (templateUrl === activeTemplateUrl) {
|
||||||
populateElement(template);
|
populateElement(template);
|
||||||
}
|
}
|
||||||
}, function () {
|
}, function () {
|
||||||
badTemplate(templateUrl);
|
badTemplate(templateUrl);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
removeElement();
|
removeElement();
|
||||||
}
|
|
||||||
activeTemplateUrl = templateUrl;
|
|
||||||
}
|
}
|
||||||
|
activeTemplateUrl = templateUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (templateUrl) {
|
if (templateUrl) {
|
||||||
|
@ -27,7 +27,8 @@ define(
|
|||||||
function (TemplateLinker) {
|
function (TemplateLinker) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var JQLITE_METHODS = [ 'replaceWith', 'empty', 'append' ];
|
var JQLITE_METHODS = [ 'replaceWith', 'empty', 'html', 'contents' ],
|
||||||
|
SCOPE_METHODS = [ '$on', '$new', '$destroy' ];
|
||||||
|
|
||||||
describe("TemplateLinker", function () {
|
describe("TemplateLinker", function () {
|
||||||
var mockTemplateRequest,
|
var mockTemplateRequest,
|
||||||
@ -38,6 +39,8 @@ define(
|
|||||||
mockElement,
|
mockElement,
|
||||||
mockTemplates,
|
mockTemplates,
|
||||||
mockElements,
|
mockElements,
|
||||||
|
mockContents,
|
||||||
|
mockNewScope,
|
||||||
mockPromise,
|
mockPromise,
|
||||||
linker;
|
linker;
|
||||||
|
|
||||||
@ -46,14 +49,18 @@ define(
|
|||||||
mockSce = jasmine.createSpyObj('$sce', ['trustAsResourceUrl']);
|
mockSce = jasmine.createSpyObj('$sce', ['trustAsResourceUrl']);
|
||||||
mockCompile = jasmine.createSpy('$compile');
|
mockCompile = jasmine.createSpy('$compile');
|
||||||
mockLog = jasmine.createSpyObj('$log', ['error', 'warn']);
|
mockLog = jasmine.createSpyObj('$log', ['error', 'warn']);
|
||||||
mockScope = jasmine.createSpyObj('$scope', ['$on']);
|
mockScope = jasmine.createSpyObj('$scope', SCOPE_METHODS);
|
||||||
|
mockNewScope = jasmine.createSpyObj('$scope', SCOPE_METHODS);
|
||||||
mockElement = jasmine.createSpyObj('element', JQLITE_METHODS);
|
mockElement = jasmine.createSpyObj('element', JQLITE_METHODS);
|
||||||
mockPromise = jasmine.createSpyObj('promise', ['then']);
|
mockPromise = jasmine.createSpyObj('promise', ['then']);
|
||||||
mockTemplates = {};
|
mockTemplates = {};
|
||||||
mockElements = {};
|
mockElements = {};
|
||||||
|
mockContents = {};
|
||||||
|
|
||||||
mockTemplateRequest.andReturn(mockPromise);
|
mockTemplateRequest.andReturn(mockPromise);
|
||||||
mockCompile.andCallFake(function (html) {
|
mockCompile.andCallFake(function (toCompile) {
|
||||||
|
var html = typeof toCompile === 'string' ?
|
||||||
|
toCompile : toCompile.testHtml;
|
||||||
mockTemplates[html] = jasmine.createSpy('template');
|
mockTemplates[html] = jasmine.createSpy('template');
|
||||||
mockElements[html] =
|
mockElements[html] =
|
||||||
jasmine.createSpyObj('templateEl', JQLITE_METHODS);
|
jasmine.createSpyObj('templateEl', JQLITE_METHODS);
|
||||||
@ -63,6 +70,17 @@ define(
|
|||||||
mockSce.trustAsResourceUrl.andCallFake(function (url) {
|
mockSce.trustAsResourceUrl.andCallFake(function (url) {
|
||||||
return { trusted: url };
|
return { trusted: url };
|
||||||
});
|
});
|
||||||
|
mockScope.$new.andReturn(mockNewScope);
|
||||||
|
mockElement.html.andCallFake(function (html) {
|
||||||
|
mockContents[html] =
|
||||||
|
jasmine.createSpyObj('contentsEl', JQLITE_METHODS);
|
||||||
|
mockContents[html].testHtml = html;
|
||||||
|
});
|
||||||
|
mockElement.contents.andCallFake(function () {
|
||||||
|
return mockContents[
|
||||||
|
mockElement.html.mostRecentCall.args[0]
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
linker = new TemplateLinker(
|
linker = new TemplateLinker(
|
||||||
mockTemplateRequest,
|
mockTemplateRequest,
|
||||||
@ -131,10 +149,11 @@ define(
|
|||||||
}, false);
|
}, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("compiles loaded templates with linked scope", function () {
|
it("compiles element contents with a new scope", function () {
|
||||||
expect(mockCompile).toHaveBeenCalledWith(testTemplate);
|
expect(mockCompile)
|
||||||
|
.toHaveBeenCalledWith(mockContents[testTemplate]);
|
||||||
expect(mockTemplates[testTemplate])
|
expect(mockTemplates[testTemplate])
|
||||||
.toHaveBeenCalledWith(mockScope);
|
.toHaveBeenCalledWith(mockNewScope);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("replaces comments with specified element", function () {
|
it("replaces comments with specified element", function () {
|
||||||
@ -142,9 +161,9 @@ define(
|
|||||||
.toHaveBeenCalledWith(mockElement);
|
.toHaveBeenCalledWith(mockElement);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("appends rendered content to the specified element", function () {
|
it("inserts HTML content into the specified element", function () {
|
||||||
expect(mockElement.append)
|
expect(mockElement.html)
|
||||||
.toHaveBeenCalledWith(mockElements[testTemplate]);
|
.toHaveBeenCalledWith(testTemplate);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("clears templates when called with undefined", function () {
|
it("clears templates when called with undefined", function () {
|
||||||
|
@ -27,6 +27,12 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"representations": [
|
"representations": [
|
||||||
|
{
|
||||||
|
"key": "search-item",
|
||||||
|
"templateUrl": "templates/search-item.html"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"templates": [
|
||||||
{
|
{
|
||||||
"key": "search",
|
"key": "search",
|
||||||
"templateUrl": "templates/search.html"
|
"templateUrl": "templates/search.html"
|
||||||
@ -34,10 +40,6 @@
|
|||||||
{
|
{
|
||||||
"key": "search-menu",
|
"key": "search-menu",
|
||||||
"templateUrl": "templates/search-menu.html"
|
"templateUrl": "templates/search-menu.html"
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "search-item",
|
|
||||||
"templateUrl": "templates/search-item.html"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"components": [
|
"components": [
|
||||||
|
@ -43,7 +43,7 @@
|
|||||||
<a class="ui-symbol clear-icon"
|
<a class="ui-symbol clear-icon"
|
||||||
ng-class="{content: !(ngModel.input === '' || ngModel.input === undefined)}"
|
ng-class="{content: !(ngModel.input === '' || ngModel.input === undefined)}"
|
||||||
ng-click="ngModel.input = ''; controller.search()">
|
ng-click="ngModel.input = ''; controller.search()">
|
||||||

|

|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<!-- Menu icon/button 'v' -->
|
<!-- Menu icon/button 'v' -->
|
||||||
@ -53,12 +53,12 @@
|
|||||||
</a>
|
</a>
|
||||||
|
|
||||||
<!-- Menu -->
|
<!-- Menu -->
|
||||||
<mct-representation key="'search-menu'"
|
<mct-include key="'search-menu'"
|
||||||
class="menu-element search-menu-holder"
|
class="menu-element search-menu-holder"
|
||||||
ng-class="{off: !toggle.isActive()}"
|
ng-class="{off: !toggle.isActive()}"
|
||||||
ng-model="ngModel"
|
ng-model="ngModel"
|
||||||
ng-click="toggle.setState(true)">
|
ng-click="toggle.setState(true)">
|
||||||
</mct-representation>
|
</mct-include>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Active filter display -->
|
<!-- Active filter display -->
|
||||||
@ -71,7 +71,7 @@
|
|||||||

|

|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
Filtered by: {{ ngModel.filtersString }}
|
Filtered by: {{ ngModel.filtersString }}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -37,7 +37,8 @@
|
|||||||
"services": [
|
"services": [
|
||||||
{
|
{
|
||||||
"key": "telemetryFormatter",
|
"key": "telemetryFormatter",
|
||||||
"implementation": "TelemetryFormatter.js"
|
"implementation": "TelemetryFormatter.js",
|
||||||
|
"depends": [ "formatService", "DEFAULT_TIME_FORMAT" ]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "telemetrySubscriber",
|
"key": "telemetrySubscriber",
|
||||||
@ -63,4 +64,4 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,64 @@ define(
|
|||||||
getRangeValue: ZERO
|
getRangeValue: ZERO
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides metadata about telemetry associated with a
|
||||||
|
* given domain object.
|
||||||
|
*
|
||||||
|
* @typedef TelemetryMetadata
|
||||||
|
* @property {string} source the machine-readable identifier for
|
||||||
|
* the source of telemetry data for this object; used by
|
||||||
|
* {@link TelemetryService} implementations to determine
|
||||||
|
* whether or not they provide data for this object.
|
||||||
|
* @property {string} key the machine-readable identifier for
|
||||||
|
* telemetry data associated with this specific object,
|
||||||
|
* within that `source`.
|
||||||
|
* @property {TelemetryDomainMetadata[]} domains supported domain
|
||||||
|
* options for telemetry data associated with this object,
|
||||||
|
* to use in interpreting a {@link TelemetrySeries}
|
||||||
|
* @property {TelemetryRangeMetadata[]} ranges supported range
|
||||||
|
* options for telemetry data associated with this object,
|
||||||
|
* to use in interpreting a {@link TelemetrySeries}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides metadata about range options within a telemetry series.
|
||||||
|
* Range options describe distinct properties within any given datum
|
||||||
|
* of a telemetry series; for instance, a telemetry series containing
|
||||||
|
* both raw and uncalibrated values may provide separate ranges for
|
||||||
|
* each.
|
||||||
|
*
|
||||||
|
* @typedef TelemetryRangeMetadata
|
||||||
|
* @property {string} key machine-readable identifier for this range
|
||||||
|
* @property {string} name human-readable name for this range
|
||||||
|
* @property {string} [units] human-readable units for this range
|
||||||
|
* @property {string} [format] data format for this range; usually,
|
||||||
|
* one of `number`, or `string`. If `undefined`,
|
||||||
|
* should presume to be a `number`. Custom formats
|
||||||
|
* may be indicated here.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides metadata about domain options within a telemetry series.
|
||||||
|
* Domain options describe distinct properties within any given datum
|
||||||
|
* of a telemtry series; for instance, a telemetry series containing
|
||||||
|
* both spacecraft event time and earth received times may provide
|
||||||
|
* separate domains for each.
|
||||||
|
*
|
||||||
|
* Domains are typically used to represent timestamps in a telemetry
|
||||||
|
* series, but more generally may express any property which will
|
||||||
|
* have unique values for each datum in a series. It is this property
|
||||||
|
* which makes domains distinct from ranges, as it makes these values
|
||||||
|
* appropriate and meaningful for use to sort and bound a series.
|
||||||
|
*
|
||||||
|
* @typedef TelemetryDomainMetadata
|
||||||
|
* @property {string} key machine-readable identifier for this range
|
||||||
|
* @property {string} name human-readable name for this range
|
||||||
|
* @property {string} [system] machine-readable identifier for the
|
||||||
|
* time/date system associated with this domain;
|
||||||
|
* used by {@link DateService}
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A telemetry capability provides a means of requesting telemetry
|
* A telemetry capability provides a means of requesting telemetry
|
||||||
* for a specific object, and for unwrapping the response (to get
|
* for a specific object, and for unwrapping the response (to get
|
||||||
|
@ -22,14 +22,13 @@
|
|||||||
/*global define,moment*/
|
/*global define,moment*/
|
||||||
|
|
||||||
define(
|
define(
|
||||||
['moment'],
|
[],
|
||||||
function (moment) {
|
function () {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
// Date format to use for domain values; in particular,
|
// Date format to use for domain values; in particular,
|
||||||
// use day-of-year instead of month/day
|
// use day-of-year instead of month/day
|
||||||
var DATE_FORMAT = "YYYY-DDD HH:mm:ss",
|
var VALUE_FORMAT_DIGITS = 3;
|
||||||
VALUE_FORMAT_DIGITS = 3;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The TelemetryFormatter is responsible for formatting (as text
|
* The TelemetryFormatter is responsible for formatting (as text
|
||||||
@ -37,22 +36,31 @@ define(
|
|||||||
* the range (usually value) of a data series.
|
* the range (usually value) of a data series.
|
||||||
* @memberof platform/telemetry
|
* @memberof platform/telemetry
|
||||||
* @constructor
|
* @constructor
|
||||||
|
* @param {FormatService} formatService the service to user to format
|
||||||
|
* domain values
|
||||||
|
* @param {string} defaultFormatKey the format to request when no
|
||||||
|
* format has been otherwise specified
|
||||||
*/
|
*/
|
||||||
function TelemetryFormatter() {
|
function TelemetryFormatter(formatService, defaultFormatKey) {
|
||||||
|
this.formatService = formatService;
|
||||||
|
this.defaultFormat = formatService.getFormat(defaultFormatKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Format a domain value.
|
* Format a domain value.
|
||||||
* @param {number} v the domain value; a timestamp
|
* @param {number} v the domain value; usually, a timestamp
|
||||||
* in milliseconds since start of 1970
|
* in milliseconds since start of 1970
|
||||||
* @param {string} [key] the key which identifies the
|
* @param {string} [key] a key which identifies the format
|
||||||
* domain; if unspecified or unknown, this will
|
* to use
|
||||||
* be treated as a standard timestamp.
|
|
||||||
* @returns {string} a textual representation of the
|
* @returns {string} a textual representation of the
|
||||||
* data and time, suitable for display.
|
* data and time, suitable for display.
|
||||||
*/
|
*/
|
||||||
TelemetryFormatter.prototype.formatDomainValue = function (v, key) {
|
TelemetryFormatter.prototype.formatDomainValue = function (v, key) {
|
||||||
return isNaN(v) ? "" : moment.utc(v).format(DATE_FORMAT);
|
var formatter = (key === undefined) ?
|
||||||
|
this.defaultFormat :
|
||||||
|
this.formatService.getFormat(key);
|
||||||
|
|
||||||
|
return isNaN(v) ? "" : formatter.format(v);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -27,16 +27,35 @@ define(
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
describe("The telemetry formatter", function () {
|
describe("The telemetry formatter", function () {
|
||||||
var formatter;
|
var mockFormatService,
|
||||||
|
mockFormat,
|
||||||
|
formatter;
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
formatter = new TelemetryFormatter();
|
mockFormatService =
|
||||||
|
jasmine.createSpyObj("formatService", ["getFormat"]);
|
||||||
|
mockFormat = jasmine.createSpyObj("format", [
|
||||||
|
"validate",
|
||||||
|
"parse",
|
||||||
|
"format"
|
||||||
|
]);
|
||||||
|
mockFormatService.getFormat.andReturn(mockFormat);
|
||||||
|
formatter = new TelemetryFormatter(mockFormatService);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("formats domains using YYYY-DDD style", function () {
|
it("formats domains using the formatService", function () {
|
||||||
expect(formatter.formatDomainValue(402513731000)).toEqual(
|
var testValue = 12321, testResult = "some result";
|
||||||
"1982-276 17:22:11"
|
mockFormat.format.andReturn(testResult);
|
||||||
);
|
|
||||||
|
expect(formatter.formatDomainValue(testValue))
|
||||||
|
.toEqual(testResult);
|
||||||
|
expect(mockFormat.format).toHaveBeenCalledWith(testValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("passes format keys to the formatService", function () {
|
||||||
|
formatter.formatDomainValue(12321, "someKey");
|
||||||
|
expect(mockFormatService.getFormat)
|
||||||
|
.toHaveBeenCalledWith("someKey");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("formats ranges as values", function () {
|
it("formats ranges as values", function () {
|
||||||
@ -44,4 +63,4 @@ define(
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user