mirror of
https://github.com/nasa/openmct.git
synced 2025-06-26 03:00:13 +00:00
Compare commits
24 Commits
vue-toolba
...
context-me
Author | SHA1 | Date | |
---|---|---|---|
485e948abe | |||
64b9d4c24a | |||
88bcb6078e | |||
5f9f3cd8e8 | |||
814b404614 | |||
ba2bb2180b | |||
72cdb352f0 | |||
cedf942c0c | |||
27506a3757 | |||
acc4e03c88 | |||
9a6090cd02 | |||
f40c9fa6f9 | |||
e7cdb334de | |||
afca6cd2e9 | |||
3c324cbea0 | |||
56b9708ab7 | |||
e6e5b6a64a | |||
58da916d18 | |||
a0327b56aa | |||
57d60128a2 | |||
987740c649 | |||
944505a5f1 | |||
c5187d8509 | |||
c7b73bdc3f |
4
app.js
4
app.js
@ -16,7 +16,7 @@ const request = require('request');
|
|||||||
|
|
||||||
// Defaults
|
// Defaults
|
||||||
options.port = options.port || options.p || 8080;
|
options.port = options.port || options.p || 8080;
|
||||||
options.host = options.host || options.h || 'localhost'
|
options.host = options.host || options.h || 'localhost';
|
||||||
options.directory = options.directory || options.D || '.';
|
options.directory = options.directory || options.D || '.';
|
||||||
|
|
||||||
// Show command line options
|
// Show command line options
|
||||||
@ -46,7 +46,7 @@ webpackConfig.plugins.push(new webpack.HotModuleReplacementPlugin());
|
|||||||
webpackConfig.plugins.push(function() { this.plugin('watch-run', function(watching, callback) { console.log('Begin compile at ' + new Date()); callback(); }) });
|
webpackConfig.plugins.push(function() { this.plugin('watch-run', function(watching, callback) { console.log('Begin compile at ' + new Date()); callback(); }) });
|
||||||
|
|
||||||
webpackConfig.entry.openmct = [
|
webpackConfig.entry.openmct = [
|
||||||
'webpack-hot-middleware/client?reload=true',
|
'webpack-hot-middleware/client',
|
||||||
webpackConfig.entry.openmct
|
webpackConfig.entry.openmct
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -26,12 +26,16 @@ define([
|
|||||||
"./src/NotificationLaunchController",
|
"./src/NotificationLaunchController",
|
||||||
"./src/DialogLaunchIndicator",
|
"./src/DialogLaunchIndicator",
|
||||||
"./src/NotificationLaunchIndicator",
|
"./src/NotificationLaunchIndicator",
|
||||||
|
"./res/dialog-launch.html",
|
||||||
|
"./res/notification-launch.html",
|
||||||
'legacyRegistry'
|
'legacyRegistry'
|
||||||
], function (
|
], function (
|
||||||
DialogLaunchController,
|
DialogLaunchController,
|
||||||
NotificationLaunchController,
|
NotificationLaunchController,
|
||||||
DialogLaunchIndicator,
|
DialogLaunchIndicator,
|
||||||
NotificationLaunchIndicator,
|
NotificationLaunchIndicator,
|
||||||
|
DialogLaunch,
|
||||||
|
NotificationLaunch,
|
||||||
legacyRegistry
|
legacyRegistry
|
||||||
) {
|
) {
|
||||||
"use strict";
|
"use strict";
|
||||||
@ -41,11 +45,11 @@ define([
|
|||||||
"templates": [
|
"templates": [
|
||||||
{
|
{
|
||||||
"key": "dialogLaunchTemplate",
|
"key": "dialogLaunchTemplate",
|
||||||
"templateUrl": "dialog-launch.html"
|
"template": DialogLaunch
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "notificationLaunchTemplate",
|
"key": "notificationLaunchTemplate",
|
||||||
"templateUrl": "notification-launch.html"
|
"template": NotificationLaunch
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"controllers": [
|
"controllers": [
|
||||||
|
@ -51,76 +51,26 @@ define(
|
|||||||
return actionTexts[Math.floor(Math.random()*3)];
|
return actionTexts[Math.floor(Math.random()*3)];
|
||||||
}
|
}
|
||||||
|
|
||||||
function getExampleActions() {
|
|
||||||
var actions = [
|
|
||||||
{
|
|
||||||
label: "Try Again",
|
|
||||||
callback: function () {
|
|
||||||
$log.debug("Try Again pressed");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Remove",
|
|
||||||
callback: function () {
|
|
||||||
$log.debug("Remove pressed");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Cancel",
|
|
||||||
callback: function () {
|
|
||||||
$log.debug("Cancel pressed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
// Randomly remove some actions off the top; leave at least one
|
|
||||||
actions.splice(0,Math.floor(Math.random() * actions.length));
|
|
||||||
|
|
||||||
return actions;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getExampleSeverity() {
|
|
||||||
var severities = [
|
|
||||||
"info",
|
|
||||||
"alert",
|
|
||||||
"error"
|
|
||||||
];
|
|
||||||
return severities[Math.floor(Math.random() * severities.length)];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Launch a new notification with a severity level of 'Error'.
|
* Launch a new notification with a severity level of 'Error'.
|
||||||
*/
|
*/
|
||||||
$scope.newError = function(){
|
$scope.newError = function () {
|
||||||
|
|
||||||
notificationService.notify({
|
notificationService.notify({
|
||||||
title: "Example error notification " + messageCounter++,
|
title: "Example error notification " + messageCounter++,
|
||||||
hint: "An error has occurred",
|
hint: "An error has occurred",
|
||||||
severity: "error",
|
severity: "error"
|
||||||
primaryOption: {
|
});
|
||||||
label: 'Retry',
|
|
||||||
callback: function() {
|
|
||||||
$log.info('Retry clicked');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
options: getExampleActions()});
|
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* Launch a new notification with a severity of 'Alert'.
|
* Launch a new notification with a severity of 'Alert'.
|
||||||
*/
|
*/
|
||||||
$scope.newAlert = function(){
|
$scope.newAlert = function () {
|
||||||
|
|
||||||
notificationService.notify({
|
notificationService.notify({
|
||||||
title: "Alert notification " + (messageCounter++),
|
title: "Alert notification " + (messageCounter++),
|
||||||
hint: "This is an alert message",
|
hint: "This is an alert message",
|
||||||
severity: "alert",
|
severity: "alert",
|
||||||
primaryOption: {
|
autoDismiss: true
|
||||||
label: 'Retry',
|
});
|
||||||
callback: function() {
|
|
||||||
$log.info('Retry clicked');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
options: getExampleActions()});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -128,39 +78,38 @@ define(
|
|||||||
* Launch a new notification with a progress bar that is updated
|
* Launch a new notification with a progress bar that is updated
|
||||||
* periodically, tracking an ongoing process.
|
* periodically, tracking an ongoing process.
|
||||||
*/
|
*/
|
||||||
$scope.newProgress = function(){
|
$scope.newProgress = function () {
|
||||||
|
|
||||||
var notificationModel = {
|
var notificationModel = {
|
||||||
title: "Progress notification example",
|
title: "Progress notification example",
|
||||||
severity: "info",
|
severity: "info",
|
||||||
progress: 0,
|
progress: 0,
|
||||||
actionText: getExampleActionText(),
|
actionText: getExampleActionText()
|
||||||
unknownProgress: false
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simulate an ongoing process and update the progress bar.
|
* Simulate an ongoing process and update the progress bar.
|
||||||
* @param notification
|
* @param notification
|
||||||
*/
|
*/
|
||||||
function incrementProgress(notificationModel) {
|
function incrementProgress() {
|
||||||
notificationModel.progress = Math.min(100, Math.floor(notificationModel.progress + Math.random() * 30));
|
notificationModel.progress = Math.min(100, Math.floor(notificationModel.progress + Math.random() * 30));
|
||||||
notificationModel.progressText = ["Estimated time" +
|
notificationModel.progressText = ["Estimated time" +
|
||||||
" remaining:" +
|
" remaining:" +
|
||||||
" about ", 60 - Math.floor((notificationModel.progress / 100) * 60), " seconds"].join(" ");
|
" about ", 60 - Math.floor((notificationModel.progress / 100) * 60), " seconds"].join(" ");
|
||||||
if (notificationModel.progress < 100) {
|
if (notificationModel.progress < 100) {
|
||||||
$timeout(function(){incrementProgress(notificationModel);}, 1000);
|
$timeout(function () {
|
||||||
|
incrementProgress(notificationModel);
|
||||||
|
}, 1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
notificationService.notify(notificationModel);
|
notificationService.notify(notificationModel);
|
||||||
incrementProgress(notificationModel);
|
incrementProgress();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Launch a new notification with severity level of INFO.
|
* Launch a new notification with severity level of INFO.
|
||||||
*/
|
*/
|
||||||
$scope.newInfo = function(){
|
$scope.newInfo = function () {
|
||||||
|
|
||||||
notificationService.info({
|
notificationService.info({
|
||||||
title: "Example Info notification " + messageCounter++
|
title: "Example Info notification " + messageCounter++
|
||||||
});
|
});
|
||||||
|
@ -75,6 +75,7 @@
|
|||||||
}));
|
}));
|
||||||
openmct.install(openmct.plugins.SummaryWidget());
|
openmct.install(openmct.plugins.SummaryWidget());
|
||||||
openmct.install(openmct.plugins.Notebook());
|
openmct.install(openmct.plugins.Notebook());
|
||||||
|
openmct.install(openmct.plugins.FolderView());
|
||||||
openmct.time.clock('local', {start: -THIRTY_MINUTES, end: 0});
|
openmct.time.clock('local', {start: -THIRTY_MINUTES, end: 0});
|
||||||
openmct.time.timeSystem('utc');
|
openmct.time.timeSystem('utc');
|
||||||
openmct.start();
|
openmct.start();
|
||||||
|
@ -35,12 +35,10 @@ define([
|
|||||||
"./src/windowing/WindowTitler",
|
"./src/windowing/WindowTitler",
|
||||||
"./res/templates/browse.html",
|
"./res/templates/browse.html",
|
||||||
"./res/templates/browse-object.html",
|
"./res/templates/browse-object.html",
|
||||||
"./res/templates/items/grid-item.html",
|
|
||||||
"./res/templates/browse/object-header.html",
|
"./res/templates/browse/object-header.html",
|
||||||
"./res/templates/browse/object-header-frame.html",
|
"./res/templates/browse/object-header-frame.html",
|
||||||
"./res/templates/menu-arrow.html",
|
"./res/templates/menu-arrow.html",
|
||||||
"./res/templates/back-arrow.html",
|
"./res/templates/back-arrow.html",
|
||||||
"./res/templates/items/items.html",
|
|
||||||
"./res/templates/browse/object-properties.html",
|
"./res/templates/browse/object-properties.html",
|
||||||
"./res/templates/browse/inspector-region.html",
|
"./res/templates/browse/inspector-region.html",
|
||||||
'legacyRegistry'
|
'legacyRegistry'
|
||||||
@ -59,12 +57,10 @@ define([
|
|||||||
WindowTitler,
|
WindowTitler,
|
||||||
browseTemplate,
|
browseTemplate,
|
||||||
browseObjectTemplate,
|
browseObjectTemplate,
|
||||||
gridItemTemplate,
|
|
||||||
objectHeaderTemplate,
|
objectHeaderTemplate,
|
||||||
objectHeaderFrameTemplate,
|
objectHeaderFrameTemplate,
|
||||||
menuArrowTemplate,
|
menuArrowTemplate,
|
||||||
backArrowTemplate,
|
backArrowTemplate,
|
||||||
itemsTemplate,
|
|
||||||
objectPropertiesTemplate,
|
objectPropertiesTemplate,
|
||||||
inspectorRegionTemplate,
|
inspectorRegionTemplate,
|
||||||
legacyRegistry
|
legacyRegistry
|
||||||
@ -156,19 +152,6 @@ define([
|
|||||||
"view"
|
"view"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"key": "grid-item",
|
|
||||||
"template": gridItemTemplate,
|
|
||||||
"uses": [
|
|
||||||
"type",
|
|
||||||
"action",
|
|
||||||
"location"
|
|
||||||
],
|
|
||||||
"gestures": [
|
|
||||||
"info",
|
|
||||||
"menu"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"key": "object-header",
|
"key": "object-header",
|
||||||
"template": objectHeaderTemplate,
|
"template": objectHeaderTemplate,
|
||||||
@ -251,23 +234,6 @@ define([
|
|||||||
"priority": "default"
|
"priority": "default"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"views": [
|
|
||||||
{
|
|
||||||
"key": "items",
|
|
||||||
"name": "Grid",
|
|
||||||
"cssClass": "icon-thumbs-strip",
|
|
||||||
"description": "Grid of available items",
|
|
||||||
"template": itemsTemplate,
|
|
||||||
"uses": [
|
|
||||||
"composition"
|
|
||||||
],
|
|
||||||
"gestures": [
|
|
||||||
"drop"
|
|
||||||
],
|
|
||||||
"type": "folder",
|
|
||||||
"editable": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"runs": [
|
"runs": [
|
||||||
{
|
{
|
||||||
"implementation": WindowTitler,
|
"implementation": WindowTitler,
|
||||||
|
@ -1,45 +0,0 @@
|
|||||||
<!--
|
|
||||||
Open MCT, Copyright (c) 2014-2018, United States Government
|
|
||||||
as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
Administration. All rights reserved.
|
|
||||||
|
|
||||||
Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
"License"); you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
License for the specific language governing permissions and limitations
|
|
||||||
under the License.
|
|
||||||
|
|
||||||
Open MCT includes source code licensed under additional open source
|
|
||||||
licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
this source code distribution or the Licensing information page available
|
|
||||||
at runtime from the About dialog for additional information.
|
|
||||||
-->
|
|
||||||
<!-- For selected, add class 'selected' to outer div -->
|
|
||||||
<div class='item grid-item' ng-click='action.perform("navigate")'>
|
|
||||||
<div class='contents abs'>
|
|
||||||
<div class='top-bar bar abs'>
|
|
||||||
<span class='icon-people' title='Shared'></span>
|
|
||||||
<mct-representation class="desktop-hide" key="'info-button'" mct-object="domainObject"></mct-representation>
|
|
||||||
</div>
|
|
||||||
<div class='item-main abs lg'>
|
|
||||||
<span class="t-item-icon" ng-class="{ 'l-icon-link':location.isLink() }">
|
|
||||||
<span class="t-item-icon-glyph ng-binding {{type.getCssClass()}}"></span>
|
|
||||||
</span>
|
|
||||||
<div class='abs item-open icon-pointer-right'></div>
|
|
||||||
</div>
|
|
||||||
<div class='bottom-bar bar abs'>
|
|
||||||
<div class='title'>{{model.name}}</div>
|
|
||||||
<div class='details'>
|
|
||||||
<span>{{type.getName()}}</span>
|
|
||||||
<span ng-show="model.composition !== undefined">
|
|
||||||
- {{model.composition.length}} Item<span ng-show="model.composition.length > 1">s</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -58,7 +58,7 @@ define([], function () {
|
|||||||
|
|
||||||
function checkNavigation() {
|
function checkNavigation() {
|
||||||
var navigatedObject = navigationService.getNavigation();
|
var navigatedObject = navigationService.getNavigation();
|
||||||
if (navigatedObject.hasCapability('context')) {
|
if (navigatedObject && navigatedObject.hasCapability('context')) {
|
||||||
if (!navigatedObject.getCapability('editor').isEditContextRoot()) {
|
if (!navigatedObject.getCapability('editor').isEditContextRoot()) {
|
||||||
preventOrphanNavigation(navigatedObject);
|
preventOrphanNavigation(navigatedObject);
|
||||||
}
|
}
|
||||||
|
@ -405,7 +405,8 @@ define([
|
|||||||
"description": "Provides transactional editing capabilities",
|
"description": "Provides transactional editing capabilities",
|
||||||
"implementation": EditorCapability,
|
"implementation": EditorCapability,
|
||||||
"depends": [
|
"depends": [
|
||||||
"transactionService"
|
"transactionService",
|
||||||
|
"openmct"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -36,9 +36,11 @@ define(
|
|||||||
*/
|
*/
|
||||||
function EditorCapability(
|
function EditorCapability(
|
||||||
transactionService,
|
transactionService,
|
||||||
|
openmct,
|
||||||
domainObject
|
domainObject
|
||||||
) {
|
) {
|
||||||
this.transactionService = transactionService;
|
this.transactionService = transactionService;
|
||||||
|
this.openmct = openmct;
|
||||||
this.domainObject = domainObject;
|
this.domainObject = domainObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,27 +50,19 @@ define(
|
|||||||
* or finish() are called.
|
* or finish() are called.
|
||||||
*/
|
*/
|
||||||
EditorCapability.prototype.edit = function () {
|
EditorCapability.prototype.edit = function () {
|
||||||
this.transactionService.startTransaction();
|
console.warn('DEPRECATED: cannot edit via edit capability, use openmct.editor instead.');
|
||||||
|
this.openmct.editor.edit();
|
||||||
this.domainObject.getCapability('status').set('editing', true);
|
this.domainObject.getCapability('status').set('editing', true);
|
||||||
};
|
};
|
||||||
|
|
||||||
function isEditContextRoot(domainObject) {
|
|
||||||
return domainObject.getCapability('status').get('editing');
|
|
||||||
}
|
|
||||||
|
|
||||||
function isEditing(domainObject) {
|
|
||||||
return isEditContextRoot(domainObject) ||
|
|
||||||
domainObject.hasCapability('context') &&
|
|
||||||
isEditing(domainObject.getCapability('context').getParent());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines whether this object, or any of its ancestors are
|
* Determines whether this object, or any of its ancestors are
|
||||||
* currently being edited.
|
* currently being edited.
|
||||||
* @returns boolean
|
* @returns boolean
|
||||||
*/
|
*/
|
||||||
EditorCapability.prototype.inEditContext = function () {
|
EditorCapability.prototype.inEditContext = function () {
|
||||||
return isEditing(this.domainObject);
|
console.warn('DEPRECATION WARNING: isEditing checks must be done via openmct.editor.');
|
||||||
|
return this.openmct.editor.isEditing();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -77,7 +71,8 @@ define(
|
|||||||
* @returns {*}
|
* @returns {*}
|
||||||
*/
|
*/
|
||||||
EditorCapability.prototype.isEditContextRoot = function () {
|
EditorCapability.prototype.isEditContextRoot = function () {
|
||||||
return isEditContextRoot(this.domainObject);
|
console.warn('DEPRECATION WARNING: isEditing checks must be done via openmct.editor.');
|
||||||
|
return this.openmct.editor.isEditing();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -86,10 +81,7 @@ define(
|
|||||||
* @returns {*}
|
* @returns {*}
|
||||||
*/
|
*/
|
||||||
EditorCapability.prototype.save = function () {
|
EditorCapability.prototype.save = function () {
|
||||||
var transactionService = this.transactionService;
|
console.warn('DEPRECATED: cannot save via edit capability, use openmct.editor instead.');
|
||||||
return transactionService.commit().then(function () {
|
|
||||||
transactionService.startTransaction();
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
EditorCapability.prototype.invoke = EditorCapability.prototype.edit;
|
EditorCapability.prototype.invoke = EditorCapability.prototype.edit;
|
||||||
@ -100,16 +92,7 @@ define(
|
|||||||
* @returns {*}
|
* @returns {*}
|
||||||
*/
|
*/
|
||||||
EditorCapability.prototype.finish = function () {
|
EditorCapability.prototype.finish = function () {
|
||||||
var domainObject = this.domainObject;
|
console.warn('DEPRECATED: cannot finish via edit capability, use openmct.editor instead.');
|
||||||
|
|
||||||
if (this.transactionService.isActive()) {
|
|
||||||
return this.transactionService.cancel().then(function () {
|
|
||||||
domainObject.getCapability("status").set("editing", false);
|
|
||||||
return domainObject;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return Promise.resolve(domainObject);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -56,7 +56,7 @@ define([
|
|||||||
};
|
};
|
||||||
|
|
||||||
DurationFormat.prototype.validate = function (text) {
|
DurationFormat.prototype.validate = function (text) {
|
||||||
return moment.utc(text, DATE_FORMATS).isValid();
|
return moment.utc(text, DATE_FORMATS, true).isValid();
|
||||||
};
|
};
|
||||||
|
|
||||||
return DurationFormat;
|
return DurationFormat;
|
||||||
|
@ -29,6 +29,7 @@ define([
|
|||||||
var DATE_FORMAT = "YYYY-MM-DD HH:mm:ss.SSS",
|
var DATE_FORMAT = "YYYY-MM-DD HH:mm:ss.SSS",
|
||||||
DATE_FORMATS = [
|
DATE_FORMATS = [
|
||||||
DATE_FORMAT,
|
DATE_FORMAT,
|
||||||
|
DATE_FORMAT + "Z",
|
||||||
"YYYY-MM-DD HH:mm:ss",
|
"YYYY-MM-DD HH:mm:ss",
|
||||||
"YYYY-MM-DD HH:mm",
|
"YYYY-MM-DD HH:mm",
|
||||||
"YYYY-MM-DD"
|
"YYYY-MM-DD"
|
||||||
@ -52,70 +53,14 @@ define([
|
|||||||
this.key = "utc";
|
this.key = "utc";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an appropriate time format based on the provided value and
|
|
||||||
* the threshold required.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
function getScaledFormat(d) {
|
|
||||||
var momentified = moment.utc(d);
|
|
||||||
/**
|
|
||||||
* Uses logic from d3 Time-Scales, v3 of the API. See
|
|
||||||
* https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Scales.md
|
|
||||||
*
|
|
||||||
* Licensed
|
|
||||||
*/
|
|
||||||
var format = [
|
|
||||||
[".SSS", function (m) {
|
|
||||||
return m.milliseconds();
|
|
||||||
}],
|
|
||||||
[":ss", function (m) {
|
|
||||||
return m.seconds();
|
|
||||||
}],
|
|
||||||
["HH:mm", function (m) {
|
|
||||||
return m.minutes();
|
|
||||||
}],
|
|
||||||
["HH", function (m) {
|
|
||||||
return m.hours();
|
|
||||||
}],
|
|
||||||
["ddd DD", function (m) {
|
|
||||||
return m.days() &&
|
|
||||||
m.date() !== 1;
|
|
||||||
}],
|
|
||||||
["MMM DD", function (m) {
|
|
||||||
return m.date() !== 1;
|
|
||||||
}],
|
|
||||||
["MMMM", function (m) {
|
|
||||||
return m.month();
|
|
||||||
}],
|
|
||||||
["YYYY", function () {
|
|
||||||
return true;
|
|
||||||
}]
|
|
||||||
].filter(function (row) {
|
|
||||||
return row[1](momentified);
|
|
||||||
})[0][0];
|
|
||||||
|
|
||||||
if (format !== undefined) {
|
|
||||||
return moment.utc(d).format(format);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {number} value The value to format.
|
* @param {number} value The value to format.
|
||||||
* @param {number} [minValue] Contextual information for scaled formatting used in linear scales such as conductor
|
|
||||||
* and plot axes. Specifies the smallest number on the scale.
|
|
||||||
* @param {number} [maxValue] Contextual information for scaled formatting used in linear scales such as conductor
|
|
||||||
* and plot axes. Specifies the largest number on the scale
|
|
||||||
* @param {number} [count] Contextual information for scaled formatting used in linear scales such as conductor
|
|
||||||
* and plot axes. The number of labels on the scale.
|
|
||||||
* @returns {string} the formatted date(s). If multiple values were requested, then an array of
|
* @returns {string} the formatted date(s). If multiple values were requested, then an array of
|
||||||
* formatted values will be returned. Where a value could not be formatted, `undefined` will be returned at its position
|
* formatted values will be returned. Where a value could not be formatted, `undefined` will be returned at its position
|
||||||
* in the array.
|
* in the array.
|
||||||
*/
|
*/
|
||||||
UTCTimeFormat.prototype.format = function (value) {
|
UTCTimeFormat.prototype.format = function (value) {
|
||||||
if (arguments.length > 1) {
|
if (value !== undefined) {
|
||||||
return getScaledFormat(value);
|
|
||||||
} else if (value !== undefined) {
|
|
||||||
return moment.utc(value).format(DATE_FORMAT) + "Z";
|
return moment.utc(value).format(DATE_FORMAT) + "Z";
|
||||||
} else {
|
} else {
|
||||||
return value;
|
return value;
|
||||||
@ -130,7 +75,7 @@ define([
|
|||||||
};
|
};
|
||||||
|
|
||||||
UTCTimeFormat.prototype.validate = function (text) {
|
UTCTimeFormat.prototype.validate = function (text) {
|
||||||
return moment.utc(text, DATE_FORMATS).isValid();
|
return moment.utc(text, DATE_FORMATS, true).isValid();
|
||||||
};
|
};
|
||||||
|
|
||||||
return UTCTimeFormat;
|
return UTCTimeFormat;
|
||||||
|
@ -1,62 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define([
|
|
||||||
"../src/UTCTimeFormat",
|
|
||||||
"moment"
|
|
||||||
], function (
|
|
||||||
UTCTimeFormat,
|
|
||||||
moment
|
|
||||||
) {
|
|
||||||
describe("The UTCTimeFormat class", function () {
|
|
||||||
var format;
|
|
||||||
var scale;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
format = new UTCTimeFormat();
|
|
||||||
scale = {min: 0, max: 0};
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Provides an appropriately scaled time format based on the input" +
|
|
||||||
" time", function () {
|
|
||||||
var TWO_HUNDRED_MS = 200;
|
|
||||||
var THREE_SECONDS = 3000;
|
|
||||||
var FIVE_MINUTES = 5 * 60 * 1000;
|
|
||||||
var ONE_HOUR_TWENTY_MINS = (1 * 60 * 60 * 1000) + (20 * 60 * 1000);
|
|
||||||
var TEN_HOURS = (10 * 60 * 60 * 1000);
|
|
||||||
|
|
||||||
var JUNE_THIRD = moment.utc("2016-06-03", "YYYY-MM-DD");
|
|
||||||
var APRIL = moment.utc("2016-04", "YYYY-MM");
|
|
||||||
var TWENTY_SIXTEEN = moment.utc("2016", "YYYY");
|
|
||||||
|
|
||||||
expect(format.format(TWO_HUNDRED_MS, scale)).toBe(".200");
|
|
||||||
expect(format.format(THREE_SECONDS, scale)).toBe(":03");
|
|
||||||
expect(format.format(FIVE_MINUTES, scale)).toBe("00:05");
|
|
||||||
expect(format.format(ONE_HOUR_TWENTY_MINS, scale)).toBe("01:20");
|
|
||||||
expect(format.format(TEN_HOURS, scale)).toBe("10");
|
|
||||||
|
|
||||||
expect(format.format(JUNE_THIRD, scale)).toBe("Fri 03");
|
|
||||||
expect(format.format(APRIL, scale)).toBe("April");
|
|
||||||
expect(format.format(TWENTY_SIXTEEN, scale)).toBe("2016");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -60,7 +60,7 @@ define(
|
|||||||
};
|
};
|
||||||
//If the notification is dismissed by the user, close
|
//If the notification is dismissed by the user, close
|
||||||
// the dialog.
|
// the dialog.
|
||||||
notification.onDismiss(function () {
|
notification.on('dismiss', function () {
|
||||||
dialog.dismiss();
|
dialog.dismiss();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -23,33 +23,17 @@
|
|||||||
define([
|
define([
|
||||||
"./src/NotificationIndicatorController",
|
"./src/NotificationIndicatorController",
|
||||||
"./src/NotificationIndicator",
|
"./src/NotificationIndicator",
|
||||||
"./src/NotificationService",
|
|
||||||
"./res/notification-indicator.html",
|
"./res/notification-indicator.html",
|
||||||
'legacyRegistry'
|
'legacyRegistry'
|
||||||
], function (
|
], function (
|
||||||
NotificationIndicatorController,
|
NotificationIndicatorController,
|
||||||
NotificationIndicator,
|
NotificationIndicator,
|
||||||
NotificationService,
|
|
||||||
notificationIndicatorTemplate,
|
notificationIndicatorTemplate,
|
||||||
legacyRegistry
|
legacyRegistry
|
||||||
) {
|
) {
|
||||||
|
|
||||||
legacyRegistry.register("platform/commonUI/notification", {
|
legacyRegistry.register("platform/commonUI/notification", {
|
||||||
"extensions": {
|
"extensions": {
|
||||||
"constants": [
|
|
||||||
{
|
|
||||||
"key": "DEFAULT_AUTO_DISMISS",
|
|
||||||
"value": 3000
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "FORCE_AUTO_DISMISS",
|
|
||||||
"value": 1000
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "MINIMIZE_TIMEOUT",
|
|
||||||
"value": 300
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"templates": [
|
"templates": [
|
||||||
{
|
{
|
||||||
"key": "notificationIndicatorTemplate",
|
"key": "notificationIndicatorTemplate",
|
||||||
@ -76,12 +60,11 @@ define([
|
|||||||
"services": [
|
"services": [
|
||||||
{
|
{
|
||||||
"key": "notificationService",
|
"key": "notificationService",
|
||||||
"implementation": NotificationService,
|
"implementation": function (openmct) {
|
||||||
|
return openmct.notifications;
|
||||||
|
},
|
||||||
"depends": [
|
"depends": [
|
||||||
"$timeout",
|
"openmct"
|
||||||
"topic",
|
|
||||||
"DEFAULT_AUTO_DISMISS",
|
|
||||||
"MINIMIZE_TIMEOUT"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -1,437 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This bundle implements the notification service, which can be used to
|
|
||||||
* show banner notifications to the user. Banner notifications
|
|
||||||
* are used to inform users of events in a non-intrusive way. As
|
|
||||||
* much as possible, notifications share a model with blocking
|
|
||||||
* dialogs so that the same information can be provided in a dialog
|
|
||||||
* and then minimized to a banner notification if needed.
|
|
||||||
*
|
|
||||||
* @namespace platform/commonUI/notification
|
|
||||||
*/
|
|
||||||
define(
|
|
||||||
['moment'],
|
|
||||||
function (moment) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A representation of a user action. Options are provided to
|
|
||||||
* dialogs and notifications and are shown as buttons.
|
|
||||||
*
|
|
||||||
* @typedef {object} NotificationOption
|
|
||||||
* @property {string} label the label to appear on the button for
|
|
||||||
* this action
|
|
||||||
* @property {function} callback a callback function to be invoked
|
|
||||||
* when the button is clicked
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A representation of a banner notification. Banner notifications
|
|
||||||
* are used to inform users of events in a non-intrusive way. As
|
|
||||||
* much as possible, notifications share a model with blocking
|
|
||||||
* dialogs so that the same information can be provided in a dialog
|
|
||||||
* and then minimized to a banner notification if needed, or vice-versa.
|
|
||||||
*
|
|
||||||
* @typedef {object} NotificationModel
|
|
||||||
* @property {string} title The title of the message
|
|
||||||
* @property {string} severity The importance of the message (one of
|
|
||||||
* 'info', 'alert', or 'error' where info < alert <error)
|
|
||||||
* @property {number} [progress] The completion status of a task
|
|
||||||
* represented numerically
|
|
||||||
* @property {boolean} [unknownProgress] a boolean indicating that the
|
|
||||||
* progress of the underlying task is unknown. This will result in a
|
|
||||||
* visually distinct progress bar.
|
|
||||||
* @property {boolean} [autoDismiss] If truthy, dialog will
|
|
||||||
* be automatically minimized or dismissed (depending on severity).
|
|
||||||
* Additionally, if the provided value is a number, it will be used
|
|
||||||
* as the delay period before being dismissed.
|
|
||||||
* @property {boolean} [dismissable=true] If true, notification will
|
|
||||||
* include an option to dismiss it completely.
|
|
||||||
* @property {NotificationOption} [primaryOption] the default user
|
|
||||||
* response to
|
|
||||||
* this message. Will be represented as a button with the provided
|
|
||||||
* label and action. May be used by banner notifications to display
|
|
||||||
* only the most important option to users.
|
|
||||||
* @property {NotificationOption[]} [options] any additional
|
|
||||||
* actions the user can take. Will be represented as additional buttons
|
|
||||||
* that may or may not be available from a banner.
|
|
||||||
* @see DialogModel
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A wrapper object that is returned as a handle to a newly created
|
|
||||||
* notification. Wraps the notifications model and decorates with
|
|
||||||
* functions to dismiss or minimize the notification.
|
|
||||||
*
|
|
||||||
* @typedef {object} Notification
|
|
||||||
* @property {function} dismiss This method is added to the object
|
|
||||||
* returned by {@link NotificationService#notify} and can be used to
|
|
||||||
* dismiss this notification. Dismissing a notification will remove
|
|
||||||
* it completely and it will not appear in the notification indicator
|
|
||||||
* @property {function} minimize This method is added to the object
|
|
||||||
* returned by {@link NotificationService#notify} and can be used to
|
|
||||||
* minimize this notification. Minimizing a notification will send
|
|
||||||
* it to the notification indicator
|
|
||||||
* @property {function} dismissOrMinimize This method is added to the
|
|
||||||
* object returned by {@link NotificationService#notify}. It will
|
|
||||||
* hide the notification by either dismissing or minimizing it,
|
|
||||||
* depending on severity. Typically this is the method that should
|
|
||||||
* be used for dismissing a notification. If more control is
|
|
||||||
* required, then the minimize or dismiss functions can be called
|
|
||||||
* individually.
|
|
||||||
* @property {function} onDismiss Allows listening for on dismiss
|
|
||||||
* events. This allows cleanup etc. when the notification is dismissed.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The notification service is responsible for informing the user of
|
|
||||||
* events via the use of banner notifications.
|
|
||||||
* @memberof platform/commonUI/notification
|
|
||||||
* @constructor
|
|
||||||
* @param $timeout the Angular $timeout service
|
|
||||||
* @param defaultAutoDismissTimeout The period of time that an
|
|
||||||
* auto-dismissed message will be displayed for.
|
|
||||||
* @param minimizeAnimationTimeout When notifications are minimized, a brief
|
|
||||||
* animation is shown. This animation requires some time to execute,
|
|
||||||
* so a timeout is required before the notification is hidden
|
|
||||||
*/
|
|
||||||
function NotificationService($timeout, topic, defaultAutoDismissTimeout, minimizeAnimationTimeout) {
|
|
||||||
this.notifications = [];
|
|
||||||
this.$timeout = $timeout;
|
|
||||||
this.highest = { severity: "info" };
|
|
||||||
this.AUTO_DISMISS_TIMEOUT = defaultAutoDismissTimeout;
|
|
||||||
this.MINIMIZE_ANIMATION_TIMEOUT = minimizeAnimationTimeout;
|
|
||||||
this.topic = topic;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* A context in which to hold the active notification and a
|
|
||||||
* handle to its timeout.
|
|
||||||
*/
|
|
||||||
this.active = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Minimize a notification. The notification will still be available
|
|
||||||
* from the notification list. Typically notifications with a
|
|
||||||
* severity of 'info' should not be minimized, but rather
|
|
||||||
* dismissed. If you're not sure which is appropriate,
|
|
||||||
* use {@link Notification#dismissOrMinimize}
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
NotificationService.prototype.minimize = function (service, notification) {
|
|
||||||
//Check this is a known notification
|
|
||||||
var index = service.notifications.indexOf(notification);
|
|
||||||
|
|
||||||
if (service.active.timeout) {
|
|
||||||
/*
|
|
||||||
Method can be called manually (clicking dismiss) or
|
|
||||||
automatically from an auto-timeout. this.active.timeout
|
|
||||||
acts as a semaphore to prevent race conditions. Cancel any
|
|
||||||
timeout in progress (for the case where a manual dismiss
|
|
||||||
has shortcut an active auto-dismiss), and clear the
|
|
||||||
semaphore.
|
|
||||||
*/
|
|
||||||
service.$timeout.cancel(service.active.timeout);
|
|
||||||
delete service.active.timeout;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (index >= 0) {
|
|
||||||
notification.model.minimized = true;
|
|
||||||
//Add a brief timeout before showing the next notification
|
|
||||||
// in order to allow the minimize animation to run through.
|
|
||||||
service.$timeout(function () {
|
|
||||||
service.setActiveNotification(service.selectNextNotification());
|
|
||||||
}, service.MINIMIZE_ANIMATION_TIMEOUT);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Completely removes a notification. This will dismiss it from the
|
|
||||||
* message banner and remove it from the list of notifications.
|
|
||||||
* Typically only notifications with a severity of info should be
|
|
||||||
* dismissed. If you're not sure whether to dismiss or minimize a
|
|
||||||
* notification, use {@link Notification#dismissOrMinimize}.
|
|
||||||
* dismiss
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
NotificationService.prototype.dismiss = function (service, notification) {
|
|
||||||
//Check this is a known notification
|
|
||||||
var index = service.notifications.indexOf(notification);
|
|
||||||
|
|
||||||
if (service.active.timeout) {
|
|
||||||
/* Method can be called manually (clicking dismiss) or
|
|
||||||
* automatically from an auto-timeout. this.active.timeout
|
|
||||||
* acts as a semaphore to prevent race conditions. Cancel any
|
|
||||||
* timeout in progress (for the case where a manual dismiss
|
|
||||||
* has shortcut an active auto-dismiss), and clear the
|
|
||||||
* semaphore.
|
|
||||||
*/
|
|
||||||
|
|
||||||
service.$timeout.cancel(service.active.timeout);
|
|
||||||
delete service.active.timeout;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (index >= 0) {
|
|
||||||
service.notifications.splice(index, 1);
|
|
||||||
}
|
|
||||||
service.setActiveNotification(service.selectNextNotification());
|
|
||||||
|
|
||||||
this.setHighestSeverity();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Depending on the severity of the notification will selectively
|
|
||||||
* dismiss or minimize where appropriate.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
NotificationService.prototype.dismissOrMinimize = function (notification) {
|
|
||||||
var model = notification.model;
|
|
||||||
if (model.severity === "info") {
|
|
||||||
if (model.autoDismiss === false) {
|
|
||||||
notification.minimize();
|
|
||||||
} else {
|
|
||||||
notification.dismiss();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
notification.minimize();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the notification that is currently visible in the banner area
|
|
||||||
* @returns {Notification}
|
|
||||||
*/
|
|
||||||
NotificationService.prototype.getActiveNotification = function () {
|
|
||||||
return this.active.notification;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A convenience method for info notifications. Notifications
|
|
||||||
* created via this method will be auto-dismissed after a default
|
|
||||||
* wait period unless explicitly forbidden by the caller through
|
|
||||||
* the {autoDismiss} property on the {NotificationModel}, in which
|
|
||||||
* case the notification will be minimized after the wait.
|
|
||||||
* @param {NotificationModel | string} message either a string for
|
|
||||||
* the title of the notification message, or a {@link NotificationModel}
|
|
||||||
* defining the options notification to display
|
|
||||||
* @returns {Notification} the provided notification decorated with
|
|
||||||
* functions to dismiss or minimize
|
|
||||||
*/
|
|
||||||
NotificationService.prototype.info = function (message) {
|
|
||||||
var notificationModel = typeof message === "string" ? {title: message} : message;
|
|
||||||
notificationModel.severity = "info";
|
|
||||||
return this.notify(notificationModel);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A convenience method for alert notifications. Notifications
|
|
||||||
* created via this method will will have severity of "alert" enforced
|
|
||||||
* @param {NotificationModel | string} message either a string for
|
|
||||||
* the title of the alert message with default options, or a
|
|
||||||
* {@link NotificationModel} defining the options notification to
|
|
||||||
* display
|
|
||||||
* @returns {Notification} the provided notification decorated with
|
|
||||||
* functions to dismiss or minimize
|
|
||||||
*/
|
|
||||||
NotificationService.prototype.alert = function (message) {
|
|
||||||
var notificationModel = typeof message === "string" ? {title: message} : message;
|
|
||||||
notificationModel.severity = "alert";
|
|
||||||
return this.notify(notificationModel);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A convenience method for error notifications. Notifications
|
|
||||||
* created via this method will will have severity of "error" enforced
|
|
||||||
* @param {NotificationModel | string} message either a string for
|
|
||||||
* the title of the error message with default options, or a
|
|
||||||
* {@link NotificationModel} defining the options notification to
|
|
||||||
* display
|
|
||||||
* @returns {Notification} the provided notification decorated with
|
|
||||||
* functions to dismiss or minimize
|
|
||||||
*/
|
|
||||||
NotificationService.prototype.error = function (message) {
|
|
||||||
var notificationModel = typeof message === "string" ? {title: message} : message;
|
|
||||||
notificationModel.severity = "error";
|
|
||||||
return this.notify(notificationModel);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
NotificationService.prototype.setHighestSeverity = function () {
|
|
||||||
var severity = {
|
|
||||||
"info": 1,
|
|
||||||
"alert": 2,
|
|
||||||
"error": 3
|
|
||||||
};
|
|
||||||
this.highest.severity = this.notifications.reduce(function (previous, notification) {
|
|
||||||
if (severity[notification.model.severity] > severity[previous]) {
|
|
||||||
return notification.model.severity;
|
|
||||||
} else {
|
|
||||||
return previous;
|
|
||||||
}
|
|
||||||
}, "info");
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Notifies the user of an event. If there is a banner notification
|
|
||||||
* already active, then it will be dismissed or minimized automatically,
|
|
||||||
* and the provided notification displayed in its place.
|
|
||||||
*
|
|
||||||
* @param {NotificationModel} notificationModel The notification to
|
|
||||||
* display
|
|
||||||
* @returns {Notification} the provided notification decorated with
|
|
||||||
* functions to {@link Notification#dismiss} or {@link Notification#minimize}
|
|
||||||
*/
|
|
||||||
NotificationService.prototype.notify = function (notificationModel) {
|
|
||||||
var self = this,
|
|
||||||
notification,
|
|
||||||
activeNotification = self.active.notification,
|
|
||||||
topic = this.topic();
|
|
||||||
|
|
||||||
notificationModel.severity = notificationModel.severity || "info";
|
|
||||||
notificationModel.timestamp = moment.utc().format('YYYY-MM-DD hh:mm:ss.ms');
|
|
||||||
|
|
||||||
notification = {
|
|
||||||
model: notificationModel,
|
|
||||||
|
|
||||||
minimize: function () {
|
|
||||||
self.minimize(self, notification);
|
|
||||||
},
|
|
||||||
|
|
||||||
dismiss: function () {
|
|
||||||
self.dismiss(self, notification);
|
|
||||||
topic.notify();
|
|
||||||
},
|
|
||||||
|
|
||||||
dismissOrMinimize: function () {
|
|
||||||
self.dismissOrMinimize(notification);
|
|
||||||
},
|
|
||||||
|
|
||||||
onDismiss: function (callback) {
|
|
||||||
topic.listen(callback);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//Notifications support a 'dismissable' attribute. This is a
|
|
||||||
// convenience to support adding a 'dismiss' option to the
|
|
||||||
// notification for the common case of dismissing a
|
|
||||||
// notification. Could also be done manually by specifying an
|
|
||||||
// option on the model
|
|
||||||
if (notificationModel.dismissable !== false) {
|
|
||||||
notificationModel.options = notificationModel.options || [];
|
|
||||||
notificationModel.options.unshift({
|
|
||||||
label: "Dismiss",
|
|
||||||
callback: function () {
|
|
||||||
notification.dismiss();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.notifications.push(notification);
|
|
||||||
|
|
||||||
this.setHighestSeverity();
|
|
||||||
|
|
||||||
/*
|
|
||||||
Check if there is already an active (ie. visible) notification
|
|
||||||
*/
|
|
||||||
if (!this.active.notification) {
|
|
||||||
this.setActiveNotification(notification);
|
|
||||||
|
|
||||||
} else if (!this.active.timeout) {
|
|
||||||
/*
|
|
||||||
If there is already an active notification, time it out. If it's
|
|
||||||
already got a timeout in progress (either because it has had
|
|
||||||
timeout forced because of a queue of messages, or it had an
|
|
||||||
autodismiss specified), leave it to run. Otherwise force a
|
|
||||||
timeout.
|
|
||||||
|
|
||||||
This notification has been added to queue and will be
|
|
||||||
serviced as soon as possible.
|
|
||||||
*/
|
|
||||||
this.active.timeout = this.$timeout(function () {
|
|
||||||
activeNotification.dismissOrMinimize();
|
|
||||||
}, this.AUTO_DISMISS_TIMEOUT);
|
|
||||||
}
|
|
||||||
|
|
||||||
return notification;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used internally by the NotificationService
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
NotificationService.prototype.setActiveNotification = function (notification) {
|
|
||||||
var shouldAutoDismiss;
|
|
||||||
this.active.notification = notification;
|
|
||||||
|
|
||||||
if (!notification) {
|
|
||||||
delete this.active.timeout;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (notification.model.severity === "info") {
|
|
||||||
shouldAutoDismiss = true;
|
|
||||||
} else {
|
|
||||||
shouldAutoDismiss = notification.model.autoDismiss;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shouldAutoDismiss || this.selectNextNotification()) {
|
|
||||||
this.active.timeout = this.$timeout(function () {
|
|
||||||
notification.dismissOrMinimize();
|
|
||||||
}, this.AUTO_DISMISS_TIMEOUT);
|
|
||||||
} else {
|
|
||||||
delete this.active.timeout;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used internally by the NotificationService
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
NotificationService.prototype.selectNextNotification = function () {
|
|
||||||
var notification,
|
|
||||||
i = 0;
|
|
||||||
|
|
||||||
/*
|
|
||||||
Loop through the notifications queue and find the first one that
|
|
||||||
has not already been minimized (manually or otherwise).
|
|
||||||
*/
|
|
||||||
for (; i < this.notifications.length; i++) {
|
|
||||||
notification = this.notifications[i];
|
|
||||||
|
|
||||||
if (!notification.model.minimized &&
|
|
||||||
notification !== this.active.notification) {
|
|
||||||
|
|
||||||
return notification;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return NotificationService;
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,275 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
/*global describe,it,expect,beforeEach,jasmine*/
|
|
||||||
|
|
||||||
define(
|
|
||||||
['../src/NotificationService'],
|
|
||||||
function (NotificationService) {
|
|
||||||
|
|
||||||
describe("The notification service ", function () {
|
|
||||||
var notificationService,
|
|
||||||
mockTimeout,
|
|
||||||
mockAutoDismiss,
|
|
||||||
mockMinimizeTimeout,
|
|
||||||
mockTopicFunction,
|
|
||||||
mockTopicObject,
|
|
||||||
infoModel,
|
|
||||||
alertModel,
|
|
||||||
errorModel;
|
|
||||||
|
|
||||||
function elapseTimeout() {
|
|
||||||
mockTimeout.calls.mostRecent().args[0]();
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
mockTimeout = jasmine.createSpy("$timeout");
|
|
||||||
mockTopicFunction = jasmine.createSpy("topic");
|
|
||||||
mockTopicObject = jasmine.createSpyObj("topicObject", ["listen", "notify"]);
|
|
||||||
mockTopicFunction.and.returnValue(mockTopicObject);
|
|
||||||
|
|
||||||
mockAutoDismiss = mockMinimizeTimeout = 1000;
|
|
||||||
notificationService = new NotificationService(mockTimeout, mockTopicFunction, mockAutoDismiss, mockMinimizeTimeout);
|
|
||||||
|
|
||||||
infoModel = {
|
|
||||||
title: "Mock Info Notification",
|
|
||||||
severity: "info"
|
|
||||||
};
|
|
||||||
|
|
||||||
alertModel = {
|
|
||||||
title: "Mock Alert Notification",
|
|
||||||
severity: "alert"
|
|
||||||
};
|
|
||||||
|
|
||||||
errorModel = {
|
|
||||||
title: "Mock Error Notification",
|
|
||||||
severity: "error"
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
it("notifies listeners on dismissal of notification", function () {
|
|
||||||
var dismissListener = jasmine.createSpy("ondismiss");
|
|
||||||
var notification = notificationService.notify(infoModel);
|
|
||||||
notification.onDismiss(dismissListener);
|
|
||||||
expect(mockTopicObject.listen).toHaveBeenCalled();
|
|
||||||
notification.dismiss();
|
|
||||||
expect(mockTopicObject.notify).toHaveBeenCalled();
|
|
||||||
mockTopicObject.listen.calls.mostRecent().args[0]();
|
|
||||||
expect(dismissListener).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("dismisses a notification when the notification's dismiss method is used", function () {
|
|
||||||
var notification = notificationService.info(infoModel);
|
|
||||||
notification.dismiss();
|
|
||||||
expect(notificationService.getActiveNotification()).toBeUndefined();
|
|
||||||
expect(notificationService.notifications.length).toEqual(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("minimizes a notification when the notification's minimize method is used", function () {
|
|
||||||
var notification = notificationService.info(infoModel);
|
|
||||||
notification.minimize();
|
|
||||||
elapseTimeout(); // needed for the minimize animation timeout
|
|
||||||
expect(notificationService.getActiveNotification()).toBeUndefined();
|
|
||||||
expect(notificationService.notifications.length).toEqual(1);
|
|
||||||
expect(notificationService.notifications[0]).toEqual(notification);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when receiving info notifications", function () {
|
|
||||||
it("minimizes info notifications if the caller disables auto-dismiss", function () {
|
|
||||||
infoModel.autoDismiss = false;
|
|
||||||
var notification = notificationService.info(infoModel);
|
|
||||||
elapseTimeout();
|
|
||||||
// 2nd elapse for the minimize animation timeout
|
|
||||||
elapseTimeout();
|
|
||||||
expect(notificationService.getActiveNotification()).toBeUndefined();
|
|
||||||
expect(notificationService.notifications.length).toEqual(1);
|
|
||||||
expect(notificationService.notifications[0]).toEqual(notification);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("dismisses info notifications if the caller ignores auto-dismiss", function () {
|
|
||||||
notificationService.info(infoModel);
|
|
||||||
elapseTimeout();
|
|
||||||
expect(notificationService.getActiveNotification()).toBeUndefined();
|
|
||||||
expect(notificationService.notifications.length).toEqual(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("dismisses info notifications if the caller requests auto-dismiss", function () {
|
|
||||||
infoModel.autoDismiss = true;
|
|
||||||
notificationService.info(infoModel);
|
|
||||||
elapseTimeout();
|
|
||||||
expect(notificationService.getActiveNotification()).toBeUndefined();
|
|
||||||
expect(notificationService.notifications.length).toEqual(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when receiving alert notifications", function () {
|
|
||||||
it("minimizes alert notifications if the caller enables auto-dismiss", function () {
|
|
||||||
alertModel.autoDismiss = true;
|
|
||||||
var notification = notificationService.alert(alertModel);
|
|
||||||
elapseTimeout();
|
|
||||||
elapseTimeout();
|
|
||||||
expect(notificationService.getActiveNotification()).toBeUndefined();
|
|
||||||
expect(notificationService.notifications.length).toEqual(1);
|
|
||||||
expect(notificationService.notifications[0]).toEqual(notification);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("keeps alert notifications active if the caller disables auto-dismiss", function () {
|
|
||||||
mockTimeout.and.callFake(function (callback, time) {
|
|
||||||
callback();
|
|
||||||
});
|
|
||||||
alertModel.autoDismiss = false;
|
|
||||||
var notification = notificationService.alert(alertModel);
|
|
||||||
expect(notificationService.getActiveNotification()).toEqual(notification);
|
|
||||||
expect(notificationService.notifications.length).toEqual(1);
|
|
||||||
expect(notificationService.notifications[0]).toEqual(notification);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("keeps alert notifications active if the caller ignores auto-dismiss", function () {
|
|
||||||
mockTimeout.and.callFake(function (callback, time) {
|
|
||||||
callback();
|
|
||||||
});
|
|
||||||
var notification = notificationService.alert(alertModel);
|
|
||||||
expect(notificationService.getActiveNotification()).toEqual(notification);
|
|
||||||
expect(notificationService.notifications.length).toEqual(1);
|
|
||||||
expect(notificationService.notifications[0]).toEqual(notification);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when receiving error notifications", function () {
|
|
||||||
it("minimizes error notifications if the caller enables auto-dismiss", function () {
|
|
||||||
errorModel.autoDismiss = true;
|
|
||||||
var notification = notificationService.error(errorModel);
|
|
||||||
elapseTimeout();
|
|
||||||
elapseTimeout();
|
|
||||||
expect(notificationService.getActiveNotification()).toBeUndefined();
|
|
||||||
expect(notificationService.notifications.length).toEqual(1);
|
|
||||||
expect(notificationService.notifications[0]).toEqual(notification);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("keeps error notifications active if the caller disables auto-dismiss", function () {
|
|
||||||
mockTimeout.and.callFake(function (callback, time) {
|
|
||||||
callback();
|
|
||||||
});
|
|
||||||
errorModel.autoDismiss = false;
|
|
||||||
var notification = notificationService.error(errorModel);
|
|
||||||
expect(notificationService.getActiveNotification()).toEqual(notification);
|
|
||||||
expect(notificationService.notifications.length).toEqual(1);
|
|
||||||
expect(notificationService.notifications[0]).toEqual(notification);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("keeps error notifications active if the caller ignores auto-dismiss", function () {
|
|
||||||
mockTimeout.and.callFake(function (callback, time) {
|
|
||||||
callback();
|
|
||||||
});
|
|
||||||
var notification = notificationService.error(errorModel);
|
|
||||||
expect(notificationService.getActiveNotification()).toEqual(notification);
|
|
||||||
expect(notificationService.notifications.length).toEqual(1);
|
|
||||||
expect(notificationService.notifications[0]).toEqual(notification);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when called with multiple notifications", function () {
|
|
||||||
it("auto-dismisses the previously active notification, making the new notification active", function () {
|
|
||||||
var activeNotification;
|
|
||||||
infoModel.autoDismiss = false;
|
|
||||||
//First pre-load with a info message
|
|
||||||
notificationService.notify(infoModel);
|
|
||||||
activeNotification = notificationService.getActiveNotification();
|
|
||||||
//Initially expect the active notification to be info
|
|
||||||
expect(activeNotification.model).toBe(infoModel);
|
|
||||||
//Then notify of an error
|
|
||||||
notificationService.notify(errorModel);
|
|
||||||
//But it should be auto-dismissed and replaced with the
|
|
||||||
// error notification
|
|
||||||
elapseTimeout();
|
|
||||||
//Two timeouts, one is to force minimization after
|
|
||||||
// displaying the message for a minimum period, the
|
|
||||||
// second is to allow minimization animation to take place.
|
|
||||||
elapseTimeout();
|
|
||||||
activeNotification = notificationService.getActiveNotification();
|
|
||||||
expect(activeNotification.model).toBe(errorModel);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("auto-minimizes an active error notification", function () {
|
|
||||||
var activeNotification;
|
|
||||||
//First pre-load with an error message
|
|
||||||
notificationService.notify(errorModel);
|
|
||||||
//Then notify of info
|
|
||||||
notificationService.notify(infoModel);
|
|
||||||
expect(notificationService.notifications.length).toEqual(2);
|
|
||||||
//Mock the auto-minimize
|
|
||||||
elapseTimeout();
|
|
||||||
//Two timeouts, one is to force minimization after
|
|
||||||
// displaying the message for a minimum period, the
|
|
||||||
// second is to allow minimization animation to take place.
|
|
||||||
elapseTimeout();
|
|
||||||
//Previous error message should be minimized, not
|
|
||||||
// dismissed
|
|
||||||
expect(notificationService.notifications.length).toEqual(2);
|
|
||||||
activeNotification = notificationService.getActiveNotification();
|
|
||||||
expect(activeNotification.model).toBe(infoModel);
|
|
||||||
expect(errorModel.minimized).toEqual(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("auto-minimizes errors when a number of them arrive in short succession", function () {
|
|
||||||
var activeNotification,
|
|
||||||
error2 = {
|
|
||||||
title: "Second Mock Error Notification",
|
|
||||||
severity: "error"
|
|
||||||
},
|
|
||||||
error3 = {
|
|
||||||
title: "Third Mock Error Notification",
|
|
||||||
severity: "error"
|
|
||||||
};
|
|
||||||
|
|
||||||
//First pre-load with a info message
|
|
||||||
notificationService.notify(errorModel);
|
|
||||||
//Then notify of a third error
|
|
||||||
notificationService.notify(error2);
|
|
||||||
notificationService.notify(error3);
|
|
||||||
expect(notificationService.notifications.length).toEqual(3);
|
|
||||||
//Mock the auto-minimize
|
|
||||||
elapseTimeout();
|
|
||||||
//Two timeouts, one is to force minimization after
|
|
||||||
// displaying the message for a minimum period, the
|
|
||||||
// second is to allow minimization animation to take place.
|
|
||||||
elapseTimeout();
|
|
||||||
//Previous error message should be minimized, not
|
|
||||||
// dismissed
|
|
||||||
expect(notificationService.notifications.length).toEqual(3);
|
|
||||||
activeNotification = notificationService.getActiveNotification();
|
|
||||||
expect(activeNotification.model).toBe(error2);
|
|
||||||
expect(errorModel.minimized).toEqual(true);
|
|
||||||
|
|
||||||
//Mock the second auto-minimize
|
|
||||||
elapseTimeout();
|
|
||||||
//Two timeouts, one is to force minimization after
|
|
||||||
// displaying the message for a minimum period, the
|
|
||||||
// second is to allow minimization animation to take place.
|
|
||||||
elapseTimeout();
|
|
||||||
activeNotification = notificationService.getActiveNotification();
|
|
||||||
expect(activeNotification.model).toBe(error3);
|
|
||||||
expect(error2.minimized).toEqual(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,43 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define([
|
|
||||||
"./src/ConductorRepresenter",
|
|
||||||
'legacyRegistry'
|
|
||||||
], function (
|
|
||||||
ConductorRepresenter,
|
|
||||||
legacyRegistry
|
|
||||||
) {
|
|
||||||
|
|
||||||
legacyRegistry.register("platform/features/conductor/compatibility", {
|
|
||||||
"extensions": {
|
|
||||||
"representers": [
|
|
||||||
{
|
|
||||||
"implementation": ConductorRepresenter,
|
|
||||||
"depends": [
|
|
||||||
"openmct"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,95 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
[],
|
|
||||||
function () {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Representer that provides a compatibility layer between the new
|
|
||||||
* time conductor and existing representations / views. Listens to
|
|
||||||
* the v2 time conductor API and generates v1 style events using the
|
|
||||||
* Angular event bus. This is transitional code code and will be
|
|
||||||
* removed.
|
|
||||||
*
|
|
||||||
* Deprecated immediately as this is temporary code
|
|
||||||
*
|
|
||||||
* @deprecated
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function ConductorRepresenter(
|
|
||||||
openmct,
|
|
||||||
scope,
|
|
||||||
element
|
|
||||||
) {
|
|
||||||
this.timeAPI = openmct.time;
|
|
||||||
this.scope = scope;
|
|
||||||
this.element = element;
|
|
||||||
|
|
||||||
this.boundsListener = this.boundsListener.bind(this);
|
|
||||||
this.timeSystemListener = this.timeSystemListener.bind(this);
|
|
||||||
this.followListener = this.followListener.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
ConductorRepresenter.prototype.boundsListener = function (bounds) {
|
|
||||||
var timeSystem = this.timeAPI.timeSystem();
|
|
||||||
this.scope.$broadcast('telemetry:display:bounds', {
|
|
||||||
start: bounds.start,
|
|
||||||
end: bounds.end,
|
|
||||||
domain: timeSystem.key
|
|
||||||
}, this.timeAPI.clock() !== undefined);
|
|
||||||
};
|
|
||||||
|
|
||||||
ConductorRepresenter.prototype.timeSystemListener = function (timeSystem) {
|
|
||||||
var bounds = this.timeAPI.bounds();
|
|
||||||
this.scope.$broadcast('telemetry:display:bounds', {
|
|
||||||
start: bounds.start,
|
|
||||||
end: bounds.end,
|
|
||||||
domain: timeSystem.key
|
|
||||||
}, this.timeAPI.clock() !== undefined);
|
|
||||||
};
|
|
||||||
|
|
||||||
ConductorRepresenter.prototype.followListener = function () {
|
|
||||||
this.boundsListener(this.timeAPI.bounds());
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handle a specific representation of a specific domain object
|
|
||||||
ConductorRepresenter.prototype.represent = function represent(representation) {
|
|
||||||
if (representation.key === 'browse-object') {
|
|
||||||
this.destroy();
|
|
||||||
|
|
||||||
this.timeAPI.on("bounds", this.boundsListener);
|
|
||||||
this.timeAPI.on("timeSystem", this.timeSystemListener);
|
|
||||||
this.timeAPI.on("follow", this.followListener);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ConductorRepresenter.prototype.destroy = function destroy() {
|
|
||||||
this.timeAPI.off("bounds", this.boundsListener);
|
|
||||||
this.timeAPI.off("timeSystem", this.timeSystemListener);
|
|
||||||
this.timeAPI.off("follow", this.followListener);
|
|
||||||
};
|
|
||||||
|
|
||||||
return ConductorRepresenter;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
@ -1,148 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define([
|
|
||||||
"./src/ui/TimeConductorController",
|
|
||||||
"./src/ui/ConductorAxisController",
|
|
||||||
"./src/ui/ConductorTOIController",
|
|
||||||
"./src/ui/ConductorTOIDirective",
|
|
||||||
"./src/ui/TimeOfInterestController",
|
|
||||||
"./src/ui/ConductorAxisDirective",
|
|
||||||
"./src/ui/NumberFormat",
|
|
||||||
"./src/ui/StringFormat",
|
|
||||||
"./res/templates/time-conductor.html",
|
|
||||||
"./res/templates/mode-selector/mode-selector.html",
|
|
||||||
"./res/templates/mode-selector/mode-menu.html",
|
|
||||||
"./res/templates/time-of-interest.html",
|
|
||||||
"legacyRegistry"
|
|
||||||
], function (
|
|
||||||
TimeConductorController,
|
|
||||||
ConductorAxisController,
|
|
||||||
ConductorTOIController,
|
|
||||||
ConductorTOIDirective,
|
|
||||||
TimeOfInterestController,
|
|
||||||
ConductorAxisDirective,
|
|
||||||
NumberFormat,
|
|
||||||
StringFormat,
|
|
||||||
timeConductorTemplate,
|
|
||||||
modeSelectorTemplate,
|
|
||||||
modeMenuTemplate,
|
|
||||||
timeOfInterest,
|
|
||||||
legacyRegistry
|
|
||||||
) {
|
|
||||||
|
|
||||||
legacyRegistry.register("platform/features/conductor/core", {
|
|
||||||
"extensions": {
|
|
||||||
"controllers": [
|
|
||||||
{
|
|
||||||
"key": "TimeConductorController",
|
|
||||||
"implementation": TimeConductorController,
|
|
||||||
"depends": [
|
|
||||||
"$scope",
|
|
||||||
"$window",
|
|
||||||
"openmct",
|
|
||||||
"formatService",
|
|
||||||
"CONDUCTOR_CONFIG"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "ConductorTOIController",
|
|
||||||
"implementation": ConductorTOIController,
|
|
||||||
"depends": [
|
|
||||||
"$scope",
|
|
||||||
"openmct",
|
|
||||||
"formatService"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "TimeOfInterestController",
|
|
||||||
"implementation": TimeOfInterestController,
|
|
||||||
"depends": [
|
|
||||||
"$scope",
|
|
||||||
"openmct",
|
|
||||||
"formatService"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"directives": [
|
|
||||||
{
|
|
||||||
"key": "conductorAxis",
|
|
||||||
"implementation": ConductorAxisDirective,
|
|
||||||
"depends": [
|
|
||||||
"openmct",
|
|
||||||
"formatService"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "conductorToi",
|
|
||||||
"implementation": ConductorTOIDirective
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"templates": [
|
|
||||||
{
|
|
||||||
"key": "conductor",
|
|
||||||
"template": timeConductorTemplate
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "mode-menu",
|
|
||||||
"template": modeMenuTemplate
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "mode-selector",
|
|
||||||
"template": modeSelectorTemplate
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "time-of-interest",
|
|
||||||
"template": timeOfInterest
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"representations": [
|
|
||||||
{
|
|
||||||
"key": "time-conductor",
|
|
||||||
"template": timeConductorTemplate
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"licenses": [
|
|
||||||
{
|
|
||||||
"name": "D3: Data-Driven Documents",
|
|
||||||
"version": "4.1.0",
|
|
||||||
"author": "Mike Bostock",
|
|
||||||
"description": "D3 (or D3.js) is a JavaScript library for visualizing data using web standards. D3 helps you bring data to life using SVG, Canvas and HTML. D3 combines powerful visualization and interaction techniques with a data-driven approach to DOM manipulation, giving you the full capabilities of modern browsers and the freedom to design the right visual interface for your data.",
|
|
||||||
"website": "https://d3js.org/",
|
|
||||||
"copyright": "Copyright 2010-2016 Mike Bostock",
|
|
||||||
"license": "BSD-3-Clause",
|
|
||||||
"link": "https://github.com/d3/d3/blob/master/LICENSE"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"formats": [
|
|
||||||
{
|
|
||||||
"key": "number",
|
|
||||||
"implementation": NumberFormat
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "string",
|
|
||||||
"implementation": StringFormat
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,46 +0,0 @@
|
|||||||
<!--
|
|
||||||
Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
Administration. All rights reserved.
|
|
||||||
|
|
||||||
Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
"License"); you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
License for the specific language governing permissions and limitations
|
|
||||||
under the License.
|
|
||||||
|
|
||||||
Open MCT Web includes source code licensed under additional open source
|
|
||||||
licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
this source code distribution or the Licensing information page available
|
|
||||||
at runtime from the About dialog for additional information.
|
|
||||||
-->
|
|
||||||
<div class="w-menu">
|
|
||||||
<div class="col menu-items">
|
|
||||||
<ul>
|
|
||||||
<li ng-repeat="metadata in ngModel.options"
|
|
||||||
ng-click="ngModel.select(metadata)">
|
|
||||||
<a ng-mouseover="ngModel.activeMetadata = metadata"
|
|
||||||
ng-mouseleave="ngModel.activeMetadata = undefined"
|
|
||||||
class="menu-item-a {{metadata.cssClass}}">
|
|
||||||
{{metadata.name}}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="col menu-item-description">
|
|
||||||
<div class="desc-area ui-symbol icon type-icon {{ngModel.activeMetadata.cssClass}}"></div>
|
|
||||||
<div class="w-title-desc">
|
|
||||||
<div class="desc-area title">
|
|
||||||
{{ngModel.activeMetadata.name}}
|
|
||||||
</div>
|
|
||||||
<div class="desc-area description">
|
|
||||||
{{ngModel.activeMetadata.description}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -1,33 +0,0 @@
|
|||||||
<!--
|
|
||||||
Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
Administration. All rights reserved.
|
|
||||||
|
|
||||||
Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
"License"); you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
License for the specific language governing permissions and limitations
|
|
||||||
under the License.
|
|
||||||
|
|
||||||
Open MCT Web includes source code licensed under additional open source
|
|
||||||
licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
this source code distribution or the Licensing information page available
|
|
||||||
at runtime from the About dialog for additional information.
|
|
||||||
-->
|
|
||||||
<span ng-controller="ClickAwayController as modeController">
|
|
||||||
<div class="s-menu-button"
|
|
||||||
ng-click="modeController.toggle()">
|
|
||||||
<span class="title-label">{{ngModel.selected.name}}</span>
|
|
||||||
</div>
|
|
||||||
<div class="menu super-menu mini l-mode-selector-menu"
|
|
||||||
ng-show="modeController.isActive()">
|
|
||||||
<mct-include key="'mode-menu'"
|
|
||||||
ng-model="ngModel">
|
|
||||||
</mct-include>
|
|
||||||
</div>
|
|
||||||
</span>
|
|
@ -1,117 +0,0 @@
|
|||||||
<!-- Parent holder for time conductor. follow-mode | fixed-mode -->
|
|
||||||
<div ng-controller="TimeConductorController as tcController"
|
|
||||||
class="holder grows flex-elem l-flex-row l-time-conductor {{tcController.isFixed ? 'fixed-mode' : 'realtime-mode'}} {{timeSystemModel.selected.metadata.key}}-time-system"
|
|
||||||
ng-class="{'status-panning': tcController.panning}">
|
|
||||||
<div class="flex-elem holder time-conductor-icon">
|
|
||||||
<div class="hand-little"></div>
|
|
||||||
<div class="hand-big"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex-elem holder grows l-flex-col l-time-conductor-inner">
|
|
||||||
<!-- Holds inputs and ticks -->
|
|
||||||
<div class="l-time-conductor-inputs-and-ticks l-row-elem flex-elem no-margin">
|
|
||||||
<form class="l-time-conductor-inputs-holder"
|
|
||||||
ng-submit="tcController.isFixed ? tcController.setBoundsFromView(boundsModel) : tcController.setOffsetsFromView(boundsModel)">
|
|
||||||
<span class="l-time-range-w start-w">
|
|
||||||
<span class="l-time-conductor-inputs">
|
|
||||||
<span class="l-time-range-input-w start-date">
|
|
||||||
<span class="title"></span>
|
|
||||||
<mct-control key="'datetime-field'"
|
|
||||||
structure="{
|
|
||||||
format: timeSystemModel.format,
|
|
||||||
validate: tcController.validation.validateStart
|
|
||||||
}"
|
|
||||||
ng-model="boundsModel"
|
|
||||||
ng-blur="tcController.setBoundsFromView(boundsModel)"
|
|
||||||
field="'start'"
|
|
||||||
class="time-range-input">
|
|
||||||
</mct-control>
|
|
||||||
</span>
|
|
||||||
<span class="l-time-range-input-w time-delta start-delta"
|
|
||||||
ng-class="{'hide':tcController.isFixed}">
|
|
||||||
-
|
|
||||||
<mct-control key="'datetime-field'"
|
|
||||||
structure="{
|
|
||||||
format: timeSystemModel.durationFormat,
|
|
||||||
validate: tcController.validation.validateStartOffset
|
|
||||||
}"
|
|
||||||
ng-model="boundsModel"
|
|
||||||
ng-blur="tcController.setOffsetsFromView(boundsModel)"
|
|
||||||
field="'startOffset'"
|
|
||||||
class="s-input-inline hrs-min-input">
|
|
||||||
</mct-control>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
<span class="l-time-range-w end-w">
|
|
||||||
<span class="l-time-conductor-inputs">
|
|
||||||
<span class="l-time-range-input-w end-date"
|
|
||||||
ng-controller="ToggleController as t2">
|
|
||||||
<span class="title"></span>
|
|
||||||
<mct-control key="'datetime-field'"
|
|
||||||
structure="{
|
|
||||||
format: timeSystemModel.format,
|
|
||||||
validate: tcController.validation.validateEnd
|
|
||||||
}"
|
|
||||||
ng-model="boundsModel"
|
|
||||||
ng-blur="tcController.setBoundsFromView(boundsModel)"
|
|
||||||
ng-disabled="!tcController.isFixed"
|
|
||||||
field="'end'"
|
|
||||||
class="time-range-input">
|
|
||||||
</mct-control>
|
|
||||||
</span>
|
|
||||||
<span class="l-time-range-input-w time-delta end-delta"
|
|
||||||
ng-class="{'hide': tcController.isFixed}">
|
|
||||||
+
|
|
||||||
<mct-control key="'datetime-field'"
|
|
||||||
structure="{
|
|
||||||
format: timeSystemModel.durationFormat,
|
|
||||||
validate: tcController.validation.validateEndOffset
|
|
||||||
}"
|
|
||||||
ng-model="boundsModel"
|
|
||||||
ng-blur="tcController.setOffsetsFromView(boundsModel)"
|
|
||||||
field="'endOffset'"
|
|
||||||
class="s-input-inline hrs-min-input">
|
|
||||||
</mct-control>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<input type="submit" class="invisible">
|
|
||||||
</form>
|
|
||||||
<conductor-axis class="mobile-hide" view-service="tcController.conductorViewService"></conductor-axis>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Holds time system and session selectors, and zoom control -->
|
|
||||||
<div class="l-time-conductor-controls l-row-elem l-flex-row flex-elem">
|
|
||||||
<mct-include
|
|
||||||
key="'mode-selector'"
|
|
||||||
ng-model="tcController.menu"
|
|
||||||
class="holder flex-elem menus-up mode-selector">
|
|
||||||
</mct-include>
|
|
||||||
<mct-control
|
|
||||||
key="'menu-button'"
|
|
||||||
class="holder flex-elem menus-up time-system"
|
|
||||||
structure="{
|
|
||||||
text: timeSystemModel.selected.name,
|
|
||||||
click: tcController.setTimeSystemFromView,
|
|
||||||
options: tcController.timeSystemsForClocks[tcController.menu.selected.key]
|
|
||||||
}">
|
|
||||||
</mct-control>
|
|
||||||
<!-- Zoom control -->
|
|
||||||
<div ng-if="tcController.zoom"
|
|
||||||
class="l-time-conductor-zoom-w grows flex-elem l-flex-row">
|
|
||||||
{{currentZoom}}
|
|
||||||
<span class="time-conductor-zoom-current-range flex-elem flex-fixed holder">{{timeUnits}}</span>
|
|
||||||
<input class="time-conductor-zoom flex-elem" type="range"
|
|
||||||
ng-model="tcController.currentZoom"
|
|
||||||
ng-mouseUp="tcController.onZoomStop(tcController.currentZoom)"
|
|
||||||
ng-change="tcController.onZoom(tcController.currentZoom)"
|
|
||||||
min="0.01"
|
|
||||||
step="0.01"
|
|
||||||
max="0.99" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -1,12 +0,0 @@
|
|||||||
<div class="abs angular-controller"
|
|
||||||
ng-controller="TimeOfInterestController as toi">
|
|
||||||
<div class="l-flex-row l-toi">
|
|
||||||
<span class="flex-elem l-flex-row l-toi-buttons">
|
|
||||||
<a class="flex-elem t-button-resync icon-button" title="Re-sync Time of Interest"
|
|
||||||
ng-click="toi.resync()"></a>
|
|
||||||
<a class="flex-elem t-button-unpin icon-button" title="Unset Time of Interest"
|
|
||||||
ng-click="toi.dismiss()"></a>
|
|
||||||
</span>
|
|
||||||
<span class="flex-elem l-toi-val">{{toi.toiText}}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -1,236 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
[
|
|
||||||
"d3-selection",
|
|
||||||
"d3-scale",
|
|
||||||
"d3-axis"
|
|
||||||
],
|
|
||||||
function (d3Selection, d3Scale, d3Axis) {
|
|
||||||
var PADDING = 1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Controller that renders a horizontal time scale spanning the current bounds defined in the time conductor.
|
|
||||||
* Used by the mct-conductor-axis directive
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function ConductorAxisController(openmct, formatService, scope, element) {
|
|
||||||
// Dependencies
|
|
||||||
this.formatService = formatService;
|
|
||||||
this.timeAPI = openmct.time;
|
|
||||||
|
|
||||||
this.scope = scope;
|
|
||||||
|
|
||||||
this.bounds = this.timeAPI.bounds();
|
|
||||||
|
|
||||||
//Bind all class functions to 'this'
|
|
||||||
Object.keys(ConductorAxisController.prototype).filter(function (key) {
|
|
||||||
return typeof ConductorAxisController.prototype[key] === 'function';
|
|
||||||
}).forEach(function (key) {
|
|
||||||
this[key] = ConductorAxisController.prototype[key].bind(this);
|
|
||||||
}.bind(this));
|
|
||||||
|
|
||||||
this.initialize(element);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
ConductorAxisController.prototype.destroy = function () {
|
|
||||||
this.timeAPI.off('timeSystem', this.changeTimeSystem);
|
|
||||||
this.timeAPI.off('bounds', this.changeBounds);
|
|
||||||
this.viewService.off("zoom", this.onZoom);
|
|
||||||
this.viewService.off("zoom-stop", this.onZoomStop);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
ConductorAxisController.prototype.initialize = function (element) {
|
|
||||||
this.target = element[0].firstChild;
|
|
||||||
var height = this.target.offsetHeight;
|
|
||||||
var vis = d3Selection.select(this.target)
|
|
||||||
.append("svg:svg")
|
|
||||||
.attr("width", "100%")
|
|
||||||
.attr("height", height);
|
|
||||||
|
|
||||||
this.xAxis = d3Axis.axisTop();
|
|
||||||
|
|
||||||
// draw x axis with labels. CSS is used to position them.
|
|
||||||
this.axisElement = vis.append("g");
|
|
||||||
|
|
||||||
if (this.timeAPI.timeSystem() !== undefined) {
|
|
||||||
this.changeTimeSystem(this.timeAPI.timeSystem());
|
|
||||||
this.setScale();
|
|
||||||
}
|
|
||||||
|
|
||||||
//Respond to changes in conductor
|
|
||||||
this.timeAPI.on("timeSystem", this.changeTimeSystem);
|
|
||||||
this.timeAPI.on("bounds", this.changeBounds);
|
|
||||||
|
|
||||||
this.scope.$on("$destroy", this.destroy);
|
|
||||||
|
|
||||||
this.viewService.on("zoom", this.onZoom);
|
|
||||||
this.viewService.on("zoom-stop", this.onZoomStop);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
ConductorAxisController.prototype.changeBounds = function (bounds) {
|
|
||||||
this.bounds = bounds;
|
|
||||||
if (!this.zooming) {
|
|
||||||
this.setScale();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the scale of the axis, based on current conductor bounds.
|
|
||||||
*/
|
|
||||||
ConductorAxisController.prototype.setScale = function () {
|
|
||||||
var width = this.target.offsetWidth;
|
|
||||||
var timeSystem = this.timeAPI.timeSystem();
|
|
||||||
var bounds = this.bounds;
|
|
||||||
|
|
||||||
if (timeSystem.isUTCBased) {
|
|
||||||
this.xScale = this.xScale || d3Scale.scaleUtc();
|
|
||||||
this.xScale.domain([new Date(bounds.start), new Date(bounds.end)]);
|
|
||||||
} else {
|
|
||||||
this.xScale = this.xScale || d3Scale.scaleLinear();
|
|
||||||
this.xScale.domain([bounds.start, bounds.end]);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.xAxis.scale(this.xScale);
|
|
||||||
|
|
||||||
this.xScale.range([PADDING, width - PADDING * 2]);
|
|
||||||
this.axisElement.call(this.xAxis);
|
|
||||||
|
|
||||||
this.msPerPixel = (bounds.end - bounds.start) / width;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When the time system changes, update the scale and formatter used for showing times.
|
|
||||||
* @param timeSystem
|
|
||||||
*/
|
|
||||||
ConductorAxisController.prototype.changeTimeSystem = function (timeSystem) {
|
|
||||||
var key = timeSystem.timeFormat;
|
|
||||||
if (key !== undefined) {
|
|
||||||
var format = this.formatService.getFormat(key);
|
|
||||||
var bounds = this.timeAPI.bounds();
|
|
||||||
|
|
||||||
//The D3 scale used depends on the type of time system as d3
|
|
||||||
// supports UTC out of the box.
|
|
||||||
if (timeSystem.isUTCBased) {
|
|
||||||
this.xScale = d3Scale.scaleUtc();
|
|
||||||
} else {
|
|
||||||
this.xScale = d3Scale.scaleLinear();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.xAxis.scale(this.xScale);
|
|
||||||
|
|
||||||
//Define a custom format function
|
|
||||||
this.xAxis.tickFormat(function (tickValue) {
|
|
||||||
// Normalize date representations to numbers
|
|
||||||
if (tickValue instanceof Date) {
|
|
||||||
tickValue = tickValue.getTime();
|
|
||||||
}
|
|
||||||
return format.format(tickValue, {
|
|
||||||
min: bounds.start,
|
|
||||||
max: bounds.end
|
|
||||||
});
|
|
||||||
});
|
|
||||||
this.axisElement.call(this.xAxis);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The user has stopped panning the time conductor scale element.
|
|
||||||
* @event panStop
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Called on release of mouse button after dragging the scale left or right.
|
|
||||||
* @fires platform.features.conductor.ConductorAxisController~panStop
|
|
||||||
*/
|
|
||||||
ConductorAxisController.prototype.panStop = function () {
|
|
||||||
//resync view bounds with time conductor bounds
|
|
||||||
this.viewService.emit("pan-stop");
|
|
||||||
this.timeAPI.bounds(this.bounds);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rescales the axis when the user zooms. Although zoom ultimately results in a bounds change once the user
|
|
||||||
* releases the zoom slider, dragging the slider will not immediately change the conductor bounds. It will
|
|
||||||
* however immediately update the scale and the bounds displayed in the UI.
|
|
||||||
* @private
|
|
||||||
* @param {ZoomLevel}
|
|
||||||
*/
|
|
||||||
ConductorAxisController.prototype.onZoom = function (zoom) {
|
|
||||||
this.zooming = true;
|
|
||||||
|
|
||||||
this.bounds = zoom.bounds;
|
|
||||||
this.setScale();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
ConductorAxisController.prototype.onZoomStop = function (zoom) {
|
|
||||||
this.zooming = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @event platform.features.conductor.ConductorAxisController~pan
|
|
||||||
* Fired when the time conductor is panned
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Initiate panning via a click + drag gesture on the time conductor
|
|
||||||
* scale. Panning triggers a "pan" event
|
|
||||||
* @param {number} delta the offset from the original click event
|
|
||||||
* @see TimeConductorViewService#
|
|
||||||
* @fires platform.features.conductor.ConductorAxisController~pan
|
|
||||||
*/
|
|
||||||
ConductorAxisController.prototype.pan = function (delta) {
|
|
||||||
if (this.timeAPI.clock() === undefined) {
|
|
||||||
var deltaInMs = delta[0] * this.msPerPixel;
|
|
||||||
var bounds = this.timeAPI.bounds();
|
|
||||||
var start = Math.floor((bounds.start - deltaInMs) / 1000) * 1000;
|
|
||||||
var end = Math.floor((bounds.end - deltaInMs) / 1000) * 1000;
|
|
||||||
this.bounds = {
|
|
||||||
start: start,
|
|
||||||
end: end
|
|
||||||
};
|
|
||||||
this.setScale();
|
|
||||||
this.viewService.emit("pan", this.bounds);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoked on element resize. Will rebuild the scale based on the new dimensions of the element.
|
|
||||||
*/
|
|
||||||
ConductorAxisController.prototype.resize = function () {
|
|
||||||
this.setScale();
|
|
||||||
};
|
|
||||||
|
|
||||||
return ConductorAxisController;
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,169 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define([
|
|
||||||
'./ConductorAxisController',
|
|
||||||
'zepto',
|
|
||||||
'd3-selection',
|
|
||||||
'd3-scale'
|
|
||||||
], function (
|
|
||||||
ConductorAxisController,
|
|
||||||
$,
|
|
||||||
d3Selection,
|
|
||||||
d3Scale
|
|
||||||
) {
|
|
||||||
describe("The ConductorAxisController", function () {
|
|
||||||
var controller,
|
|
||||||
mockConductor,
|
|
||||||
mockConductorViewService,
|
|
||||||
mockFormatService,
|
|
||||||
mockScope,
|
|
||||||
mockBounds,
|
|
||||||
element,
|
|
||||||
mockTimeSystem,
|
|
||||||
mockFormat;
|
|
||||||
|
|
||||||
function getCallback(target, name) {
|
|
||||||
return target.calls.all().filter(function (call) {
|
|
||||||
return call.args[0] === name;
|
|
||||||
})[0].args[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
mockScope = jasmine.createSpyObj("scope", [
|
|
||||||
"$on"
|
|
||||||
]);
|
|
||||||
|
|
||||||
//Add some HTML elements
|
|
||||||
mockBounds = {
|
|
||||||
start: 100,
|
|
||||||
end: 200
|
|
||||||
};
|
|
||||||
mockConductor = jasmine.createSpyObj("conductor", [
|
|
||||||
"timeSystem",
|
|
||||||
"bounds",
|
|
||||||
"on",
|
|
||||||
"off",
|
|
||||||
"clock"
|
|
||||||
]);
|
|
||||||
mockConductor.bounds.and.returnValue(mockBounds);
|
|
||||||
|
|
||||||
mockFormatService = jasmine.createSpyObj("formatService", [
|
|
||||||
"getFormat"
|
|
||||||
]);
|
|
||||||
|
|
||||||
mockConductorViewService = jasmine.createSpyObj("conductorViewService", [
|
|
||||||
"on",
|
|
||||||
"off",
|
|
||||||
"emit"
|
|
||||||
]);
|
|
||||||
|
|
||||||
spyOn(d3Scale, 'scaleUtc').and.callThrough();
|
|
||||||
spyOn(d3Scale, 'scaleLinear').and.callThrough();
|
|
||||||
|
|
||||||
element = $('<div style="width: 100px;"><div style="width: 100%;"></div></div>');
|
|
||||||
$(document).find('body').append(element);
|
|
||||||
ConductorAxisController.prototype.viewService = mockConductorViewService;
|
|
||||||
controller = new ConductorAxisController({time: mockConductor}, mockFormatService, mockScope, element);
|
|
||||||
|
|
||||||
mockTimeSystem = {};
|
|
||||||
mockFormat = jasmine.createSpyObj("format", [
|
|
||||||
"format"
|
|
||||||
]);
|
|
||||||
|
|
||||||
mockTimeSystem.timeFormat = "mockFormat";
|
|
||||||
mockFormatService.getFormat.and.returnValue(mockFormat);
|
|
||||||
mockConductor.timeSystem.and.returnValue(mockTimeSystem);
|
|
||||||
mockTimeSystem.isUTCBased = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
it("listens for changes to time system and bounds", function () {
|
|
||||||
expect(mockConductor.on).toHaveBeenCalledWith("timeSystem", controller.changeTimeSystem);
|
|
||||||
expect(mockConductor.on).toHaveBeenCalledWith("bounds", controller.changeBounds);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("on scope destruction, deregisters listeners", function () {
|
|
||||||
expect(mockScope.$on).toHaveBeenCalledWith("$destroy", controller.destroy);
|
|
||||||
controller.destroy();
|
|
||||||
expect(mockConductor.off).toHaveBeenCalledWith("timeSystem", controller.changeTimeSystem);
|
|
||||||
expect(mockConductor.off).toHaveBeenCalledWith("bounds", controller.changeBounds);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when the time system changes", function () {
|
|
||||||
it("uses a UTC scale for UTC time systems", function () {
|
|
||||||
mockTimeSystem.isUTCBased = true;
|
|
||||||
controller.changeTimeSystem(mockTimeSystem);
|
|
||||||
|
|
||||||
expect(d3Scale.scaleUtc).toHaveBeenCalled();
|
|
||||||
expect(d3Scale.scaleLinear).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("uses a linear scale for non-UTC time systems", function () {
|
|
||||||
mockTimeSystem.isUTCBased = false;
|
|
||||||
controller.changeTimeSystem(mockTimeSystem);
|
|
||||||
expect(d3Scale.scaleLinear).toHaveBeenCalled();
|
|
||||||
expect(d3Scale.scaleUtc).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("sets axis domain to time conductor bounds", function () {
|
|
||||||
mockTimeSystem.isUTCBased = false;
|
|
||||||
controller.setScale();
|
|
||||||
expect(controller.xScale.domain()).toEqual([mockBounds.start, mockBounds.end]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("uses the format specified by the time system to format tick" +
|
|
||||||
" labels", function () {
|
|
||||||
controller.changeTimeSystem(mockTimeSystem);
|
|
||||||
expect(mockFormat.format).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('responds to zoom events', function () {
|
|
||||||
expect(mockConductorViewService.on).toHaveBeenCalledWith("zoom", controller.onZoom);
|
|
||||||
var cb = getCallback(mockConductorViewService.on, "zoom");
|
|
||||||
spyOn(controller, 'setScale').and.callThrough();
|
|
||||||
cb({bounds: {start: 0, end: 100}});
|
|
||||||
expect(controller.setScale).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('adjusts scale on pan', function () {
|
|
||||||
spyOn(controller, 'setScale').and.callThrough();
|
|
||||||
controller.pan(100);
|
|
||||||
expect(controller.setScale).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('emits event on pan', function () {
|
|
||||||
spyOn(controller, 'setScale').and.callThrough();
|
|
||||||
controller.pan(100);
|
|
||||||
expect(mockConductorViewService.emit).toHaveBeenCalledWith("pan", jasmine.any(Object));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('cleans up listeners on destruction', function () {
|
|
||||||
controller.destroy();
|
|
||||||
expect(mockConductor.off).toHaveBeenCalledWith("bounds", controller.changeBounds);
|
|
||||||
expect(mockConductor.off).toHaveBeenCalledWith("timeSystem", controller.changeTimeSystem);
|
|
||||||
|
|
||||||
expect(mockConductorViewService.off).toHaveBeenCalledWith("zoom", controller.onZoom);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,56 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(['./ConductorAxisController'], function (ConductorAxisController) {
|
|
||||||
function ConductorAxisDirective() {
|
|
||||||
/**
|
|
||||||
* The mct-conductor-axis renders a horizontal axis with regular
|
|
||||||
* labelled 'ticks'. It requires 'start' and 'end' integer values to
|
|
||||||
* be specified as attributes.
|
|
||||||
*/
|
|
||||||
return {
|
|
||||||
controller: [
|
|
||||||
'openmct',
|
|
||||||
'formatService',
|
|
||||||
'$scope',
|
|
||||||
'$element',
|
|
||||||
ConductorAxisController
|
|
||||||
],
|
|
||||||
controllerAs: 'axis',
|
|
||||||
scope: {
|
|
||||||
viewService: "="
|
|
||||||
},
|
|
||||||
bindToController: true,
|
|
||||||
|
|
||||||
restrict: 'E',
|
|
||||||
priority: 1000,
|
|
||||||
|
|
||||||
template: '<div class="l-axis-holder" ' +
|
|
||||||
' mct-drag-down="axis.panStart()"' +
|
|
||||||
' mct-drag-up="axis.panStop(delta)"' +
|
|
||||||
' mct-drag="axis.pan(delta)"' +
|
|
||||||
' mct-resize="axis.resize()"></div>'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return ConductorAxisDirective;
|
|
||||||
});
|
|
@ -1,123 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
["zepto"],
|
|
||||||
function ($) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Controller for the Time of Interest indicator in the conductor itself. Sets the horizontal position of the
|
|
||||||
* TOI indicator based on the current value of the TOI, and the width of the TOI conductor.
|
|
||||||
* @memberof platform.features.conductor
|
|
||||||
*/
|
|
||||||
function ConductorTOIController($scope, openmct) {
|
|
||||||
this.timeAPI = openmct.time;
|
|
||||||
|
|
||||||
//Bind all class functions to 'this'
|
|
||||||
Object.keys(ConductorTOIController.prototype).filter(function (key) {
|
|
||||||
return typeof ConductorTOIController.prototype[key] === 'function';
|
|
||||||
}).forEach(function (key) {
|
|
||||||
this[key] = ConductorTOIController.prototype[key].bind(this);
|
|
||||||
}.bind(this));
|
|
||||||
|
|
||||||
this.timeAPI.on('timeOfInterest', this.changeTimeOfInterest);
|
|
||||||
this.viewService.on('zoom', this.setOffsetFromZoom);
|
|
||||||
this.viewService.on('pan', this.setOffsetFromBounds);
|
|
||||||
|
|
||||||
var timeOfInterest = this.timeAPI.timeOfInterest();
|
|
||||||
if (timeOfInterest) {
|
|
||||||
this.changeTimeOfInterest(timeOfInterest);
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.$on('$destroy', this.destroy);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
ConductorTOIController.prototype.destroy = function () {
|
|
||||||
this.timeAPI.off('timeOfInterest', this.changeTimeOfInterest);
|
|
||||||
this.viewService.off('zoom', this.setOffsetFromZoom);
|
|
||||||
this.viewService.off('pan', this.setOffsetFromBounds);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given some bounds, set horizontal position of TOI indicator based
|
|
||||||
* on current conductor TOI value. Bounds are provided so that
|
|
||||||
* ephemeral bounds from zoom and pan events can be used as well
|
|
||||||
* as current conductor bounds, allowing TOI to be updated in
|
|
||||||
* realtime during scroll and zoom.
|
|
||||||
* @param {TimeConductorBounds} bounds
|
|
||||||
*/
|
|
||||||
ConductorTOIController.prototype.setOffsetFromBounds = function (bounds) {
|
|
||||||
var toi = this.timeAPI.timeOfInterest();
|
|
||||||
if (toi !== undefined) {
|
|
||||||
var offset = toi - bounds.start;
|
|
||||||
var duration = bounds.end - bounds.start;
|
|
||||||
this.left = offset / duration * 100;
|
|
||||||
this.pinned = true;
|
|
||||||
} else {
|
|
||||||
this.left = 0;
|
|
||||||
this.pinned = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
ConductorTOIController.prototype.setOffsetFromZoom = function (zoom) {
|
|
||||||
return this.setOffsetFromBounds(zoom.bounds);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoked when time of interest changes. Will set the horizontal offset of the TOI indicator.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
ConductorTOIController.prototype.changeTimeOfInterest = function () {
|
|
||||||
var bounds = this.timeAPI.bounds();
|
|
||||||
if (bounds) {
|
|
||||||
this.setOffsetFromBounds(bounds);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On a mouse click event within the TOI element, convert position within element to a time of interest, and
|
|
||||||
* set the time of interest on the conductor.
|
|
||||||
* @param e The angular $event object
|
|
||||||
*/
|
|
||||||
ConductorTOIController.prototype.setTOIFromPosition = function (e) {
|
|
||||||
//TOI is set using the alt key modified + primary click
|
|
||||||
if (e.altKey) {
|
|
||||||
var element = $(e.currentTarget);
|
|
||||||
var width = element.width();
|
|
||||||
var relativeX = e.pageX - element.offset().left;
|
|
||||||
var percX = relativeX / width;
|
|
||||||
var bounds = this.timeAPI.bounds();
|
|
||||||
var timeRange = bounds.end - bounds.start;
|
|
||||||
|
|
||||||
this.timeAPI.timeOfInterest(timeRange * percX + bounds.start);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return ConductorTOIController;
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,153 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define([
|
|
||||||
'./ConductorTOIController'
|
|
||||||
], function (
|
|
||||||
ConductorTOIController
|
|
||||||
) {
|
|
||||||
var mockConductor;
|
|
||||||
var mockConductorViewService;
|
|
||||||
var mockScope;
|
|
||||||
var mockAPI;
|
|
||||||
var conductorTOIController;
|
|
||||||
|
|
||||||
function getNamedCallback(thing, name) {
|
|
||||||
return thing.calls.all().filter(function (call) {
|
|
||||||
return call.args[0] === name;
|
|
||||||
}).map(function (call) {
|
|
||||||
return call.args;
|
|
||||||
})[0][1];
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("The ConductorTOIController", function () {
|
|
||||||
beforeEach(function () {
|
|
||||||
mockConductor = jasmine.createSpyObj("conductor", [
|
|
||||||
"bounds",
|
|
||||||
"timeOfInterest",
|
|
||||||
"on",
|
|
||||||
"off"
|
|
||||||
]);
|
|
||||||
mockAPI = {time: mockConductor};
|
|
||||||
|
|
||||||
mockConductorViewService = jasmine.createSpyObj("conductorViewService", [
|
|
||||||
"on",
|
|
||||||
"off"
|
|
||||||
]);
|
|
||||||
|
|
||||||
mockScope = jasmine.createSpyObj("openMCT", [
|
|
||||||
"$on"
|
|
||||||
]);
|
|
||||||
ConductorTOIController.prototype.viewService = mockConductorViewService;
|
|
||||||
conductorTOIController = new ConductorTOIController(mockScope, mockAPI);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("listens to changes in the time of interest on the conductor", function () {
|
|
||||||
expect(mockConductor.on).toHaveBeenCalledWith("timeOfInterest", jasmine.any(Function));
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when responding to changes in the time of interest", function () {
|
|
||||||
var toiCallback;
|
|
||||||
beforeEach(function () {
|
|
||||||
var bounds = {
|
|
||||||
start: 0,
|
|
||||||
end: 200
|
|
||||||
};
|
|
||||||
mockConductor.bounds.and.returnValue(bounds);
|
|
||||||
toiCallback = getNamedCallback(mockConductor.on, "timeOfInterest");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("calculates the correct horizontal offset based on bounds and current TOI", function () {
|
|
||||||
//Expect time of interest position to be 50% of element width
|
|
||||||
mockConductor.timeOfInterest.and.returnValue(100);
|
|
||||||
toiCallback();
|
|
||||||
expect(conductorTOIController.left).toBe(50);
|
|
||||||
|
|
||||||
//Expect time of interest position to be 25% of element width
|
|
||||||
mockConductor.timeOfInterest.and.returnValue(50);
|
|
||||||
toiCallback();
|
|
||||||
expect(conductorTOIController.left).toBe(25);
|
|
||||||
|
|
||||||
//Expect time of interest position to be 0% of element width
|
|
||||||
mockConductor.timeOfInterest.and.returnValue(0);
|
|
||||||
toiCallback();
|
|
||||||
expect(conductorTOIController.left).toBe(0);
|
|
||||||
|
|
||||||
//Expect time of interest position to be 100% of element width
|
|
||||||
mockConductor.timeOfInterest.and.returnValue(200);
|
|
||||||
toiCallback();
|
|
||||||
expect(conductorTOIController.left).toBe(100);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("renders the TOI indicator visible", function () {
|
|
||||||
expect(conductorTOIController.pinned).toBeFalsy();
|
|
||||||
mockConductor.timeOfInterest.and.returnValue(100);
|
|
||||||
toiCallback();
|
|
||||||
expect(conductorTOIController.pinned).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("responds to zoom events", function () {
|
|
||||||
var mockZoom = {
|
|
||||||
bounds: {
|
|
||||||
start: 500,
|
|
||||||
end: 1000
|
|
||||||
}
|
|
||||||
};
|
|
||||||
expect(mockConductorViewService.on).toHaveBeenCalledWith("zoom", jasmine.any(Function));
|
|
||||||
|
|
||||||
// Should correspond to horizontal offset of 50%
|
|
||||||
mockConductor.timeOfInterest.and.returnValue(750);
|
|
||||||
var zoomCallback = getNamedCallback(mockConductorViewService.on, "zoom");
|
|
||||||
zoomCallback(mockZoom);
|
|
||||||
expect(conductorTOIController.left).toBe(50);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("responds to pan events", function () {
|
|
||||||
var mockPanBounds = {
|
|
||||||
start: 1000,
|
|
||||||
end: 3000
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(mockConductorViewService.on).toHaveBeenCalledWith("pan", jasmine.any(Function));
|
|
||||||
|
|
||||||
// Should correspond to horizontal offset of 25%
|
|
||||||
mockConductor.timeOfInterest.and.returnValue(1500);
|
|
||||||
var panCallback = getNamedCallback(mockConductorViewService.on, "pan");
|
|
||||||
panCallback(mockPanBounds);
|
|
||||||
expect(conductorTOIController.left).toBe(25);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it("Cleans up all listeners when controller destroyed", function () {
|
|
||||||
var zoomCB = getNamedCallback(mockConductorViewService.on, "zoom");
|
|
||||||
var panCB = getNamedCallback(mockConductorViewService.on, "pan");
|
|
||||||
var toiCB = getNamedCallback(mockConductor.on, "timeOfInterest");
|
|
||||||
|
|
||||||
expect(mockScope.$on).toHaveBeenCalledWith("$destroy", jasmine.any(Function));
|
|
||||||
getNamedCallback(mockScope.$on, "$destroy")();
|
|
||||||
expect(mockConductorViewService.off).toHaveBeenCalledWith("zoom", zoomCB);
|
|
||||||
expect(mockConductorViewService.off).toHaveBeenCalledWith("pan", panCB);
|
|
||||||
expect(mockConductor.off).toHaveBeenCalledWith("timeOfInterest", toiCB);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,63 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(['./ConductorTOIController'], function (ConductorTOIController) {
|
|
||||||
/**
|
|
||||||
* A directive that encapsulates the TOI specific behavior of the Time Conductor UI.
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function ConductorTOIDirective() {
|
|
||||||
/**
|
|
||||||
* The mct-conductor-axis renders a horizontal axis with regular
|
|
||||||
* labelled 'ticks'. It requires 'start' and 'end' integer values to
|
|
||||||
* be specified as attributes.
|
|
||||||
*/
|
|
||||||
return {
|
|
||||||
controller: [
|
|
||||||
'$scope',
|
|
||||||
'openmct',
|
|
||||||
ConductorTOIController
|
|
||||||
],
|
|
||||||
controllerAs: 'toi',
|
|
||||||
scope: {
|
|
||||||
viewService: "="
|
|
||||||
},
|
|
||||||
bindToController: true,
|
|
||||||
|
|
||||||
restrict: 'E',
|
|
||||||
priority: 1000,
|
|
||||||
|
|
||||||
template:
|
|
||||||
'<div class="l-data-visualization-holder l-row-elem flex-elem">' +
|
|
||||||
' <a class="l-page-button s-icon-button icon-pointer-left"></a>' +
|
|
||||||
' <div class="l-data-visualization" ng-click="toi.setTOIFromPosition($event)">' +
|
|
||||||
' <mct-include key="\'time-of-interest\'" class="l-toi-holder show-val" ' +
|
|
||||||
' ng-class="{ pinned: toi.pinned, \'val-to-left\': toi.left > 80 }" ' +
|
|
||||||
' ng-style="{\'left\': toi.left + \'%\'}"></mct-include>' +
|
|
||||||
' </div>' +
|
|
||||||
' <a class="l-page-button align-right s-icon-button icon-pointer-right"></a>' +
|
|
||||||
'</div>'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return ConductorTOIDirective;
|
|
||||||
});
|
|
@ -1,49 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(['./NumberFormat'], function (NumberFormat) {
|
|
||||||
describe("The NumberFormat class", function () {
|
|
||||||
var format;
|
|
||||||
beforeEach(function () {
|
|
||||||
format = new NumberFormat();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("The format function takes a string and produces a number", function () {
|
|
||||||
var text = format.format(1);
|
|
||||||
expect(text).toBe("1");
|
|
||||||
expect(typeof text).toBe("string");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("The parse function takes a string and produces a number", function () {
|
|
||||||
var number = format.parse("1");
|
|
||||||
expect(number).toBe(1);
|
|
||||||
expect(typeof number).toBe("number");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("validates that the input is a number", function () {
|
|
||||||
expect(format.validate("1")).toBe(true);
|
|
||||||
expect(format.validate(1)).toBe(true);
|
|
||||||
expect(format.validate("1.1")).toBe(true);
|
|
||||||
expect(format.validate("abc")).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,554 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
[
|
|
||||||
'moment',
|
|
||||||
'./TimeConductorValidation',
|
|
||||||
'./TimeConductorViewService'
|
|
||||||
],
|
|
||||||
function (
|
|
||||||
moment,
|
|
||||||
TimeConductorValidation,
|
|
||||||
TimeConductorViewService
|
|
||||||
) {
|
|
||||||
|
|
||||||
var timeUnitsMegastructure = [
|
|
||||||
["Decades", function (r) {
|
|
||||||
return r.years() > 15;
|
|
||||||
}],
|
|
||||||
["Years", function (r) {
|
|
||||||
return r.years() > 1;
|
|
||||||
}],
|
|
||||||
["Months", function (r) {
|
|
||||||
return r.years() === 1 || r.months() > 1;
|
|
||||||
}],
|
|
||||||
["Days", function (r) {
|
|
||||||
return r.months() === 1 || r.days() > 1;
|
|
||||||
}],
|
|
||||||
["Hours", function (r) {
|
|
||||||
return r.days() === 1 || r.hours() > 1;
|
|
||||||
}],
|
|
||||||
["Minutes", function (r) {
|
|
||||||
return r.hours() === 1 || r.minutes() > 1;
|
|
||||||
}],
|
|
||||||
["Seconds", function (r) {
|
|
||||||
return r.minutes() === 1 || r.seconds() > 1;
|
|
||||||
}],
|
|
||||||
["Milliseconds", function (r) {
|
|
||||||
return true;
|
|
||||||
}]
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Controller for the Time Conductor UI element. The Time Conductor
|
|
||||||
* includes form fields for specifying time bounds and relative time
|
|
||||||
* offsets for queries, as well as controls for selection mode,
|
|
||||||
* time systems, and zooming.
|
|
||||||
* @memberof platform.features.conductor
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function TimeConductorController(
|
|
||||||
$scope,
|
|
||||||
$window,
|
|
||||||
openmct,
|
|
||||||
formatService,
|
|
||||||
config
|
|
||||||
) {
|
|
||||||
|
|
||||||
//Bind functions that are used as callbacks to 'this'.
|
|
||||||
[
|
|
||||||
"selectMenuOption",
|
|
||||||
"onPan",
|
|
||||||
"onPanStop",
|
|
||||||
"setViewFromBounds",
|
|
||||||
"setViewFromClock",
|
|
||||||
"setViewFromOffsets",
|
|
||||||
"setViewFromTimeSystem",
|
|
||||||
"setTimeSystemFromView",
|
|
||||||
"destroy"
|
|
||||||
].forEach(function (name) {
|
|
||||||
this[name] = this[name].bind(this);
|
|
||||||
}.bind(this));
|
|
||||||
|
|
||||||
this.$scope = $scope;
|
|
||||||
this.$window = $window;
|
|
||||||
this.timeAPI = openmct.time;
|
|
||||||
this.conductorViewService = new TimeConductorViewService(openmct);
|
|
||||||
this.validation = new TimeConductorValidation(this.timeAPI);
|
|
||||||
this.formatService = formatService;
|
|
||||||
this.config = config;
|
|
||||||
this.timeSystemsForClocks = {};
|
|
||||||
this.$scope.timeSystemModel = {};
|
|
||||||
this.$scope.boundsModel = {};
|
|
||||||
|
|
||||||
this.timeSystems = this.timeAPI.getAllTimeSystems().reduce(function (map, timeSystem) {
|
|
||||||
map[timeSystem.key] = timeSystem;
|
|
||||||
return map;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
this.isFixed = this.timeAPI.clock() === undefined;
|
|
||||||
|
|
||||||
var options = this.optionsFromConfig(config);
|
|
||||||
this.menu = {
|
|
||||||
selected: undefined,
|
|
||||||
options: options,
|
|
||||||
select: this.selectMenuOption
|
|
||||||
};
|
|
||||||
|
|
||||||
//Set the initial state of the UI from the conductor state
|
|
||||||
var timeSystem = this.timeAPI.timeSystem();
|
|
||||||
if (timeSystem) {
|
|
||||||
this.setViewFromTimeSystem(timeSystem);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setViewFromClock(this.timeAPI.clock());
|
|
||||||
|
|
||||||
var offsets = this.timeAPI.clockOffsets();
|
|
||||||
if (offsets) {
|
|
||||||
this.setViewFromOffsets(offsets);
|
|
||||||
}
|
|
||||||
|
|
||||||
var bounds = this.timeAPI.bounds();
|
|
||||||
if (bounds && bounds.start !== undefined && bounds.end !== undefined) {
|
|
||||||
this.setViewFromBounds(bounds);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.conductorViewService.on('pan', this.onPan);
|
|
||||||
this.conductorViewService.on('pan-stop', this.onPanStop);
|
|
||||||
|
|
||||||
//Respond to any subsequent conductor changes
|
|
||||||
this.timeAPI.on('bounds', this.setViewFromBounds);
|
|
||||||
this.timeAPI.on('timeSystem', this.setViewFromTimeSystem);
|
|
||||||
this.timeAPI.on('clock', this.setViewFromClock);
|
|
||||||
this.timeAPI.on('clockOffsets', this.setViewFromOffsets);
|
|
||||||
this.$scope.$on('$destroy', this.destroy);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given a key for a clock, retrieve the clock object.
|
|
||||||
* @private
|
|
||||||
* @param key
|
|
||||||
* @returns {Clock}
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.getClock = function (key) {
|
|
||||||
return this.timeAPI.getAllClocks().filter(function (clock) {
|
|
||||||
return clock.key === key;
|
|
||||||
})[0];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Activate the selected menu option. Menu options correspond to clocks.
|
|
||||||
* A distinction is made to avoid confusion between the menu options and
|
|
||||||
* their metadata, and actual {@link Clock} objects.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @param newOption
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.selectMenuOption = function (newOption) {
|
|
||||||
if (this.menu.selected.key === newOption.key) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.menu.selected = newOption;
|
|
||||||
|
|
||||||
var config = this.getConfig(this.timeAPI.timeSystem(), newOption.clock);
|
|
||||||
if (!config) {
|
|
||||||
// Clock does not support this timeSystem, fallback to first
|
|
||||||
// option provided for clock.
|
|
||||||
config = this.config.menuOptions.filter(function (menuOption) {
|
|
||||||
return menuOption.clock === (newOption.clock && newOption.clock.key);
|
|
||||||
})[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.clock) {
|
|
||||||
this.timeAPI.clock(config.clock, config.clockOffsets);
|
|
||||||
this.timeAPI.timeSystem(config.timeSystem);
|
|
||||||
} else {
|
|
||||||
this.timeAPI.stopClock();
|
|
||||||
this.timeAPI.timeSystem(config.timeSystem, config.bounds);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* From the provided configuration, build the available menu options.
|
|
||||||
* @private
|
|
||||||
* @param config
|
|
||||||
* @returns {*[]}
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.optionsFromConfig = function (config) {
|
|
||||||
/*
|
|
||||||
* "Fixed Mode" is always the first available option.
|
|
||||||
*/
|
|
||||||
var options = [{
|
|
||||||
key: 'fixed',
|
|
||||||
name: 'Fixed Timespan Mode',
|
|
||||||
description: 'Query and explore data that falls between two fixed datetimes.',
|
|
||||||
cssClass: 'icon-calendar'
|
|
||||||
}];
|
|
||||||
var clocks = {};
|
|
||||||
var timeSystemsForClocks = this.timeSystemsForClocks;
|
|
||||||
|
|
||||||
(config.menuOptions || []).forEach(function (menuOption) {
|
|
||||||
var clockKey = menuOption.clock || 'fixed';
|
|
||||||
var clock = this.getClock(clockKey);
|
|
||||||
|
|
||||||
if (clock !== undefined) {
|
|
||||||
clocks[clock.key] = clock;
|
|
||||||
}
|
|
||||||
|
|
||||||
var timeSystem = this.timeSystems[menuOption.timeSystem];
|
|
||||||
if (timeSystem !== undefined) {
|
|
||||||
timeSystemsForClocks[clockKey] = timeSystemsForClocks[clockKey] || [];
|
|
||||||
timeSystemsForClocks[clockKey].push(timeSystem);
|
|
||||||
}
|
|
||||||
}, this);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Populate the clocks menu with metadata from the available clocks
|
|
||||||
*/
|
|
||||||
Object.values(clocks).forEach(function (clock) {
|
|
||||||
options.push({
|
|
||||||
key: clock.key,
|
|
||||||
name: clock.name,
|
|
||||||
description: "Monitor streaming data in real-time. The Time " +
|
|
||||||
"Conductor and displays will automatically advance themselves based on this clock. " + clock.description,
|
|
||||||
cssClass: clock.cssClass || 'icon-clock',
|
|
||||||
clock: clock
|
|
||||||
});
|
|
||||||
}.bind(this));
|
|
||||||
|
|
||||||
return options;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When bounds change, set UI values from the new bounds.
|
|
||||||
* @param {TimeBounds} bounds the bounds
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.setViewFromBounds = function (bounds) {
|
|
||||||
if (!this.zooming && !this.panning) {
|
|
||||||
this.$scope.boundsModel.start = bounds.start;
|
|
||||||
this.$scope.boundsModel.end = bounds.end;
|
|
||||||
|
|
||||||
if (this.supportsZoom()) {
|
|
||||||
var config = this.getConfig(this.timeAPI.timeSystem(), this.timeAPI.clock());
|
|
||||||
this.currentZoom = this.toSliderValue(bounds.end - bounds.start, config.zoomOutLimit, config.zoomInLimit);
|
|
||||||
this.toTimeUnits(bounds.end - bounds.start);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Ensure that a digest occurs, capped at the browser's refresh
|
|
||||||
rate.
|
|
||||||
*/
|
|
||||||
if (!this.pendingUpdate) {
|
|
||||||
this.pendingUpdate = true;
|
|
||||||
this.$window.requestAnimationFrame(function () {
|
|
||||||
this.pendingUpdate = false;
|
|
||||||
this.$scope.$digest();
|
|
||||||
}.bind(this));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve any configuration defined for the provided time system and
|
|
||||||
* clock
|
|
||||||
* @private
|
|
||||||
* @param timeSystem
|
|
||||||
* @param clock
|
|
||||||
* @returns {object} The Time Conductor configuration corresponding to
|
|
||||||
* the provided combination of time system and clock
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.getConfig = function (timeSystem, clock) {
|
|
||||||
var clockKey = clock && clock.key;
|
|
||||||
var timeSystemKey = timeSystem && timeSystem.key;
|
|
||||||
|
|
||||||
var option = this.config.menuOptions.filter(function (menuOption) {
|
|
||||||
return menuOption.timeSystem === timeSystemKey && menuOption.clock === clockKey;
|
|
||||||
})[0];
|
|
||||||
return option;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When the clock offsets change, update the values in the UI
|
|
||||||
* @param {ClockOffsets} offsets
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.setViewFromOffsets = function (offsets) {
|
|
||||||
this.$scope.boundsModel.startOffset = Math.abs(offsets.start);
|
|
||||||
this.$scope.boundsModel.endOffset = offsets.end;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When form values for bounds change, update the bounds in the Time API
|
|
||||||
* to trigger an application-wide bounds change.
|
|
||||||
* @param {object} boundsModel
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.setBoundsFromView = function (boundsModel) {
|
|
||||||
var bounds = this.timeAPI.bounds();
|
|
||||||
if (boundsModel.start !== bounds.start || boundsModel.end !== bounds.end) {
|
|
||||||
this.timeAPI.bounds({
|
|
||||||
start: boundsModel.start,
|
|
||||||
end: boundsModel.end
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When form values for bounds change, update the bounds in the Time API
|
|
||||||
* to trigger an application-wide bounds change.
|
|
||||||
* @param {object} formModel
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.setOffsetsFromView = function (boundsModel) {
|
|
||||||
if (this.validation.validateStartOffset(boundsModel.startOffset) && this.validation.validateEndOffset(boundsModel.endOffset)) {
|
|
||||||
var offsets = {
|
|
||||||
start: 0 - boundsModel.startOffset,
|
|
||||||
end: boundsModel.endOffset
|
|
||||||
};
|
|
||||||
var existingOffsets = this.timeAPI.clockOffsets();
|
|
||||||
|
|
||||||
if (offsets.start !== existingOffsets.start || offsets.end !== existingOffsets.end) {
|
|
||||||
//Sychronize offsets between form and time API
|
|
||||||
this.timeAPI.clockOffsets(offsets);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.supportsZoom = function () {
|
|
||||||
var config = this.getConfig(this.timeAPI.timeSystem(), this.timeAPI.clock());
|
|
||||||
return config && (config.zoomInLimit !== undefined && config.zoomOutLimit !== undefined);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the UI state to reflect a change in clock. Provided conductor
|
|
||||||
* configuration will be checked for compatibility between the new clock
|
|
||||||
* and the currently selected time system. If configuration is not available,
|
|
||||||
* an attempt will be made to default to a time system that is compatible
|
|
||||||
* with the new clock
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @param {Clock} clock
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.setViewFromClock = function (clock) {
|
|
||||||
var newClockKey = clock ? clock.key : 'fixed';
|
|
||||||
var timeSystems = this.timeSystemsForClocks[newClockKey];
|
|
||||||
var menuOption = this.menu.options.filter(function (option) {
|
|
||||||
return option.key === (newClockKey);
|
|
||||||
})[0];
|
|
||||||
|
|
||||||
this.menu.selected = menuOption;
|
|
||||||
|
|
||||||
//Try to find currently selected time system in time systems for clock
|
|
||||||
var selectedTimeSystem = timeSystems.filter(function (timeSystem) {
|
|
||||||
return timeSystem.key === this.$scope.timeSystemModel.selected.key;
|
|
||||||
}.bind(this))[0];
|
|
||||||
|
|
||||||
var config = this.getConfig(selectedTimeSystem, clock);
|
|
||||||
|
|
||||||
if (selectedTimeSystem === undefined) {
|
|
||||||
selectedTimeSystem = timeSystems[0];
|
|
||||||
config = this.getConfig(selectedTimeSystem, clock);
|
|
||||||
|
|
||||||
if (clock === undefined) {
|
|
||||||
this.timeAPI.timeSystem(selectedTimeSystem, config.bounds);
|
|
||||||
} else {
|
|
||||||
//When time system changes, some start bounds need to be provided
|
|
||||||
this.timeAPI.timeSystem(selectedTimeSystem, {
|
|
||||||
start: clock.currentValue() + config.clockOffsets.start,
|
|
||||||
end: clock.currentValue() + config.clockOffsets.end
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.isFixed = clock === undefined;
|
|
||||||
|
|
||||||
if (clock === undefined) {
|
|
||||||
this.setViewFromBounds(this.timeAPI.bounds());
|
|
||||||
}
|
|
||||||
|
|
||||||
this.zoom = this.supportsZoom();
|
|
||||||
this.$scope.timeSystemModel.options = timeSystems;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Respond to time system selection from UI
|
|
||||||
*
|
|
||||||
* Allows time system to be changed by key. This supports selection
|
|
||||||
* from the menu. Resolves a TimeSystem object and then invokes
|
|
||||||
* TimeConductorController#setTimeSystem
|
|
||||||
* @param key
|
|
||||||
* @see TimeConductorController#setTimeSystem
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.setTimeSystemFromView = function (key) {
|
|
||||||
var clock = this.menu.selected.clock;
|
|
||||||
var timeSystem = this.timeSystems[key];
|
|
||||||
var config = this.getConfig(timeSystem, clock);
|
|
||||||
|
|
||||||
this.$scope.timeSystemModel.selected = timeSystem;
|
|
||||||
this.$scope.timeSystemModel.format = timeSystem.timeFormat;
|
|
||||||
|
|
||||||
if (clock === undefined) {
|
|
||||||
this.timeAPI.timeSystem(timeSystem, config.bounds);
|
|
||||||
} else {
|
|
||||||
this.timeAPI.clock(clock, config.clockOffsets);
|
|
||||||
this.timeAPI.timeSystem(timeSystem);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles time system change from time conductor
|
|
||||||
*
|
|
||||||
* Sets the selected time system. Will populate form with the default
|
|
||||||
* bounds and offsets defined in the selected time system.
|
|
||||||
*
|
|
||||||
* @param newTimeSystem
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.setViewFromTimeSystem = function (timeSystem) {
|
|
||||||
var oldKey = (this.$scope.timeSystemModel.selected || {}).key;
|
|
||||||
var timeSystemModel = this.$scope.timeSystemModel;
|
|
||||||
|
|
||||||
if (timeSystem && (timeSystem.key !== oldKey)) {
|
|
||||||
var config = this.getConfig(timeSystem, this.timeAPI.clock());
|
|
||||||
|
|
||||||
timeSystemModel.selected = timeSystem;
|
|
||||||
timeSystemModel.format = timeSystem.timeFormat;
|
|
||||||
timeSystemModel.durationFormat = timeSystem.durationFormat;
|
|
||||||
|
|
||||||
if (this.supportsZoom()) {
|
|
||||||
timeSystemModel.minZoom = config.zoomOutLimit;
|
|
||||||
timeSystemModel.maxZoom = config.zoomInLimit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.zoom = this.supportsZoom();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Takes a time span and calculates a slider increment value, used
|
|
||||||
* to set the horizontal offset of the slider.
|
|
||||||
* @private
|
|
||||||
* @param {number} timeSpan a duration of time, in ms
|
|
||||||
* @returns {number} a value between 0.01 and 0.99, in increments of .01
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.toSliderValue = function (timeSpan, zoomOutLimit, zoomInLimit) {
|
|
||||||
var perc = timeSpan / (zoomOutLimit - zoomInLimit);
|
|
||||||
return 1 - Math.pow(perc, 1 / 4);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given a time span, set a label for the units of time that it,
|
|
||||||
* roughly, represents. Leverages
|
|
||||||
* @param {TimeSpan} timeSpan
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.toTimeUnits = function (timeSpan) {
|
|
||||||
var timeSystem = this.timeAPI.timeSystem();
|
|
||||||
if (timeSystem && timeSystem.isUTCBased) {
|
|
||||||
var momentified = moment.duration(timeSpan);
|
|
||||||
|
|
||||||
this.$scope.timeUnits = timeUnitsMegastructure.filter(function (row) {
|
|
||||||
return row[1](momentified);
|
|
||||||
})[0][0];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Zooming occurs when the user manipulates the zoom slider.
|
|
||||||
* Zooming updates the scale and bounds fields immediately, but does
|
|
||||||
* not trigger a bounds change to other views until the mouse button
|
|
||||||
* is released.
|
|
||||||
* @param bounds
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.onZoom = function (sliderValue) {
|
|
||||||
var config = this.getConfig(this.timeAPI.timeSystem(), this.timeAPI.clock());
|
|
||||||
var timeSpan = Math.pow((1 - sliderValue), 4) * (config.zoomOutLimit - config.zoomInLimit);
|
|
||||||
|
|
||||||
var zoom = this.conductorViewService.zoom(timeSpan);
|
|
||||||
this.zooming = true;
|
|
||||||
|
|
||||||
this.$scope.boundsModel.start = zoom.bounds.start;
|
|
||||||
this.$scope.boundsModel.end = zoom.bounds.end;
|
|
||||||
this.toTimeUnits(zoom.bounds.end - zoom.bounds.start);
|
|
||||||
|
|
||||||
if (zoom.offsets) {
|
|
||||||
this.setViewFromOffsets(zoom.offsets);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fired when user has released the zoom slider
|
|
||||||
* @event platform.features.conductor.TimeConductorController~zoomStop
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Invoked when zoom slider is released by user. Will update the time conductor with the new bounds, triggering
|
|
||||||
* a global bounds change event.
|
|
||||||
* @fires platform.features.conductor.TimeConductorController~zoomStop
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.onZoomStop = function () {
|
|
||||||
if (this.timeAPI.clock() !== undefined) {
|
|
||||||
this.setOffsetsFromView(this.$scope.boundsModel);
|
|
||||||
}
|
|
||||||
this.setBoundsFromView(this.$scope.boundsModel);
|
|
||||||
|
|
||||||
this.zooming = false;
|
|
||||||
this.conductorViewService.emit('zoom-stop');
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Panning occurs when the user grabs the conductor scale and drags
|
|
||||||
* it left or right to slide the window of time represented by the
|
|
||||||
* conductor. Panning updates the scale and bounds fields
|
|
||||||
* immediately, but does not trigger a bounds change to other views
|
|
||||||
* until the mouse button is released.
|
|
||||||
* @param {TimeConductorBounds} bounds
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.onPan = function (bounds) {
|
|
||||||
this.panning = true;
|
|
||||||
this.$scope.boundsModel.start = bounds.start;
|
|
||||||
this.$scope.boundsModel.end = bounds.end;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the user releases the mouse button after panning.
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.onPanStop = function () {
|
|
||||||
this.panning = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.destroy = function () {
|
|
||||||
this.timeAPI.off('bounds', this.setViewFromBounds);
|
|
||||||
this.timeAPI.off('timeSystem', this.setViewFromTimeSystem);
|
|
||||||
this.timeAPI.off('clock', this.setViewFromClock);
|
|
||||||
this.timeAPI.off('follow', this.setFollow);
|
|
||||||
this.timeAPI.off('clockOffsets', this.setViewFromOffsets);
|
|
||||||
|
|
||||||
this.conductorViewService.off('pan', this.onPan);
|
|
||||||
this.conductorViewService.off('pan-stop', this.onPanStop);
|
|
||||||
};
|
|
||||||
|
|
||||||
return TimeConductorController;
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,513 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(['./TimeConductorController'], function (TimeConductorController) {
|
|
||||||
xdescribe("The time conductor controller", function () {
|
|
||||||
var mockScope;
|
|
||||||
var mockWindow;
|
|
||||||
var mockTimeConductor;
|
|
||||||
var mockConductorViewService;
|
|
||||||
var mockTimeSystems;
|
|
||||||
var controller;
|
|
||||||
var mockFormatService;
|
|
||||||
var mockFormat;
|
|
||||||
var mockLocation;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
mockScope = jasmine.createSpyObj("$scope", [
|
|
||||||
"$watch",
|
|
||||||
"$on"
|
|
||||||
]);
|
|
||||||
|
|
||||||
mockWindow = jasmine.createSpyObj("$window", ["requestAnimationFrame"]);
|
|
||||||
mockTimeConductor = jasmine.createSpyObj(
|
|
||||||
"TimeConductor",
|
|
||||||
[
|
|
||||||
"bounds",
|
|
||||||
"timeSystem",
|
|
||||||
"on",
|
|
||||||
"off"
|
|
||||||
]
|
|
||||||
);
|
|
||||||
mockTimeConductor.bounds.and.returnValue({start: undefined, end: undefined});
|
|
||||||
|
|
||||||
mockConductorViewService = jasmine.createSpyObj(
|
|
||||||
"ConductorViewService",
|
|
||||||
[
|
|
||||||
"availableModes",
|
|
||||||
"mode",
|
|
||||||
"availableTimeSystems",
|
|
||||||
"deltas",
|
|
||||||
"deltas",
|
|
||||||
"on",
|
|
||||||
"off"
|
|
||||||
]
|
|
||||||
);
|
|
||||||
mockConductorViewService.availableModes.and.returnValue([]);
|
|
||||||
mockConductorViewService.availableTimeSystems.and.returnValue([]);
|
|
||||||
|
|
||||||
mockFormatService = jasmine.createSpyObj('formatService', [
|
|
||||||
'getFormat'
|
|
||||||
]);
|
|
||||||
mockFormat = jasmine.createSpyObj('format', [
|
|
||||||
'format'
|
|
||||||
]);
|
|
||||||
mockFormatService.getFormat.and.returnValue(mockFormat);
|
|
||||||
mockLocation = jasmine.createSpyObj('location', [
|
|
||||||
'search'
|
|
||||||
]);
|
|
||||||
mockLocation.search.and.returnValue({});
|
|
||||||
|
|
||||||
mockTimeSystems = [];
|
|
||||||
});
|
|
||||||
|
|
||||||
function getListener(target, event) {
|
|
||||||
return target.calls.all().filter(function (call) {
|
|
||||||
return call.args[0] === event;
|
|
||||||
})[0].args[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("when time conductor state changes", function () {
|
|
||||||
var mockDeltaFormat;
|
|
||||||
var defaultBounds;
|
|
||||||
var defaultDeltas;
|
|
||||||
var mockDefaults;
|
|
||||||
var timeSystem;
|
|
||||||
var tsListener;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
mockFormat = {};
|
|
||||||
mockDeltaFormat = {};
|
|
||||||
defaultBounds = {
|
|
||||||
start: 2,
|
|
||||||
end: 3
|
|
||||||
};
|
|
||||||
defaultDeltas = {
|
|
||||||
start: 10,
|
|
||||||
end: 20
|
|
||||||
};
|
|
||||||
mockDefaults = {
|
|
||||||
deltas: defaultDeltas,
|
|
||||||
bounds: defaultBounds
|
|
||||||
};
|
|
||||||
timeSystem = {
|
|
||||||
metadata: {
|
|
||||||
key: 'mock'
|
|
||||||
},
|
|
||||||
formats: function () {
|
|
||||||
return [mockFormat];
|
|
||||||
},
|
|
||||||
deltaFormat: function () {
|
|
||||||
return mockDeltaFormat;
|
|
||||||
},
|
|
||||||
defaults: function () {
|
|
||||||
return mockDefaults;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
controller = new TimeConductorController(
|
|
||||||
mockScope,
|
|
||||||
mockWindow,
|
|
||||||
mockLocation,
|
|
||||||
{conductor: mockTimeConductor},
|
|
||||||
mockConductorViewService,
|
|
||||||
mockFormatService,
|
|
||||||
'fixed',
|
|
||||||
true
|
|
||||||
|
|
||||||
);
|
|
||||||
|
|
||||||
tsListener = getListener(mockTimeConductor.on, "timeSystem");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("listens for changes to conductor state", function () {
|
|
||||||
expect(mockTimeConductor.on).toHaveBeenCalledWith("timeSystem", controller.changeTimeSystem);
|
|
||||||
expect(mockTimeConductor.on).toHaveBeenCalledWith("bounds", controller.changeBounds);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("deregisters conductor listens when scope is destroyed", function () {
|
|
||||||
expect(mockScope.$on).toHaveBeenCalledWith("$destroy", controller.destroy);
|
|
||||||
|
|
||||||
controller.destroy();
|
|
||||||
expect(mockTimeConductor.off).toHaveBeenCalledWith("timeSystem", controller.changeTimeSystem);
|
|
||||||
expect(mockTimeConductor.off).toHaveBeenCalledWith("bounds", controller.changeBounds);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("when time system changes, sets time system on scope", function () {
|
|
||||||
expect(tsListener).toBeDefined();
|
|
||||||
tsListener(timeSystem);
|
|
||||||
|
|
||||||
expect(mockScope.timeSystemModel).toBeDefined();
|
|
||||||
expect(mockScope.timeSystemModel.selected).toBe(timeSystem);
|
|
||||||
expect(mockScope.timeSystemModel.format).toBe(mockFormat);
|
|
||||||
expect(mockScope.timeSystemModel.deltaFormat).toBe(mockDeltaFormat);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("when time system changes, sets defaults on scope", function () {
|
|
||||||
mockDefaults.zoom = {
|
|
||||||
min: 100,
|
|
||||||
max: 10
|
|
||||||
};
|
|
||||||
mockTimeConductor.timeSystem.and.returnValue(timeSystem);
|
|
||||||
tsListener(timeSystem);
|
|
||||||
|
|
||||||
expect(mockScope.boundsModel.start).toEqual(defaultBounds.start);
|
|
||||||
expect(mockScope.boundsModel.end).toEqual(defaultBounds.end);
|
|
||||||
|
|
||||||
expect(mockScope.boundsModel.startDelta).toEqual(defaultDeltas.start);
|
|
||||||
expect(mockScope.boundsModel.endDelta).toEqual(defaultDeltas.end);
|
|
||||||
|
|
||||||
expect(mockScope.timeSystemModel.minZoom).toBe(mockDefaults.zoom.min);
|
|
||||||
expect(mockScope.timeSystemModel.maxZoom).toBe(mockDefaults.zoom.max);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("supports zoom if time system defines zoom defaults", function () {
|
|
||||||
|
|
||||||
mockDefaults.zoom = undefined;
|
|
||||||
|
|
||||||
tsListener(timeSystem);
|
|
||||||
expect(controller.supportsZoom).toBe(false);
|
|
||||||
|
|
||||||
mockDefaults.zoom = {
|
|
||||||
min: 100,
|
|
||||||
max: 10
|
|
||||||
};
|
|
||||||
|
|
||||||
var anotherTimeSystem = Object.create(timeSystem);
|
|
||||||
timeSystem.defaults = function () {
|
|
||||||
return mockDefaults;
|
|
||||||
};
|
|
||||||
|
|
||||||
tsListener(anotherTimeSystem);
|
|
||||||
expect(controller.supportsZoom).toBe(true);
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
it("when bounds change, sets the correct zoom slider value", function () {
|
|
||||||
var bounds = {
|
|
||||||
start: 0,
|
|
||||||
end: 50
|
|
||||||
};
|
|
||||||
mockDefaults.zoom = {
|
|
||||||
min: 100,
|
|
||||||
max: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
function exponentializer(rawValue) {
|
|
||||||
return 1 - Math.pow(rawValue, 1 / 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
mockTimeConductor.timeSystem.and.returnValue(timeSystem);
|
|
||||||
//Set zoom defaults
|
|
||||||
tsListener(timeSystem);
|
|
||||||
|
|
||||||
controller.changeBounds(bounds);
|
|
||||||
expect(controller.currentZoom).toEqual(exponentializer(0.5));
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
it("when bounds change, sets them on scope", function () {
|
|
||||||
var bounds = {
|
|
||||||
start: 1,
|
|
||||||
end: 2
|
|
||||||
};
|
|
||||||
|
|
||||||
var boundsListener = getListener(mockTimeConductor.on, "bounds");
|
|
||||||
expect(boundsListener).toBeDefined();
|
|
||||||
boundsListener(bounds);
|
|
||||||
|
|
||||||
expect(mockScope.boundsModel).toBeDefined();
|
|
||||||
expect(mockScope.boundsModel.start).toEqual(bounds.start);
|
|
||||||
expect(mockScope.boundsModel.end).toEqual(bounds.end);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when user makes changes from UI", function () {
|
|
||||||
var mode = "realtime";
|
|
||||||
var ts1Metadata;
|
|
||||||
var ts2Metadata;
|
|
||||||
var ts3Metadata;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
mode = "realtime";
|
|
||||||
ts1Metadata = {
|
|
||||||
'key': 'ts1',
|
|
||||||
'name': 'Time System One',
|
|
||||||
'cssClass': 'cssClassOne'
|
|
||||||
};
|
|
||||||
ts2Metadata = {
|
|
||||||
'key': 'ts2',
|
|
||||||
'name': 'Time System Two',
|
|
||||||
'cssClass': 'cssClassTwo'
|
|
||||||
};
|
|
||||||
ts3Metadata = {
|
|
||||||
'key': 'ts3',
|
|
||||||
'name': 'Time System Three',
|
|
||||||
'cssClass': 'cssClassThree'
|
|
||||||
};
|
|
||||||
mockTimeSystems = [
|
|
||||||
{
|
|
||||||
metadata: ts1Metadata
|
|
||||||
},
|
|
||||||
{
|
|
||||||
metadata: ts2Metadata
|
|
||||||
},
|
|
||||||
{
|
|
||||||
metadata: ts3Metadata
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
//Wrap in mock constructors
|
|
||||||
mockConductorViewService.systems = mockTimeSystems;
|
|
||||||
|
|
||||||
controller = new TimeConductorController(
|
|
||||||
mockScope,
|
|
||||||
mockWindow,
|
|
||||||
mockLocation,
|
|
||||||
{conductor: mockTimeConductor},
|
|
||||||
mockConductorViewService,
|
|
||||||
mockFormatService,
|
|
||||||
"fixed",
|
|
||||||
true
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("sets the mode on scope", function () {
|
|
||||||
mockConductorViewService.availableTimeSystems.and.returnValue(mockTimeSystems);
|
|
||||||
controller.setMode(mode);
|
|
||||||
|
|
||||||
expect(mockScope.modeModel.selectedKey).toEqual(mode);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("sets available time systems on scope when mode changes", function () {
|
|
||||||
mockConductorViewService.availableTimeSystems.and.returnValue(mockTimeSystems);
|
|
||||||
controller.setMode(mode);
|
|
||||||
|
|
||||||
expect(mockScope.timeSystemModel.options.length).toEqual(3);
|
|
||||||
expect(mockScope.timeSystemModel.options[0]).toEqual(ts1Metadata);
|
|
||||||
expect(mockScope.timeSystemModel.options[1]).toEqual(ts2Metadata);
|
|
||||||
expect(mockScope.timeSystemModel.options[2]).toEqual(ts3Metadata);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("sets bounds on the time conductor", function () {
|
|
||||||
var formModel = {
|
|
||||||
start: 1,
|
|
||||||
end: 10
|
|
||||||
};
|
|
||||||
|
|
||||||
controller.setBounds(formModel);
|
|
||||||
expect(mockTimeConductor.bounds).toHaveBeenCalledWith(formModel);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("applies deltas when they change in form", function () {
|
|
||||||
var deltas = {
|
|
||||||
start: 1000,
|
|
||||||
end: 2000
|
|
||||||
};
|
|
||||||
var formModel = {
|
|
||||||
startDelta: deltas.start,
|
|
||||||
endDelta: deltas.end
|
|
||||||
};
|
|
||||||
|
|
||||||
controller.setDeltas(formModel);
|
|
||||||
expect(mockConductorViewService.deltas).toHaveBeenCalledWith(deltas);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("sets the time system on the time conductor", function () {
|
|
||||||
var defaultBounds = {
|
|
||||||
start: 5,
|
|
||||||
end: 6
|
|
||||||
};
|
|
||||||
var timeSystem = {
|
|
||||||
metadata: {
|
|
||||||
key: 'testTimeSystem'
|
|
||||||
},
|
|
||||||
defaults: function () {
|
|
||||||
return {
|
|
||||||
bounds: defaultBounds
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
controller.timeSystems = [timeSystem];
|
|
||||||
|
|
||||||
controller.selectTimeSystemByKey('testTimeSystem');
|
|
||||||
expect(mockTimeConductor.timeSystem).toHaveBeenCalledWith(timeSystem, defaultBounds);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("updates form bounds during pan events", function () {
|
|
||||||
var testBounds = {
|
|
||||||
start: 10,
|
|
||||||
end: 20
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(controller.$scope.boundsModel.start).not.toBe(testBounds.start);
|
|
||||||
expect(controller.$scope.boundsModel.end).not.toBe(testBounds.end);
|
|
||||||
|
|
||||||
expect(controller.conductorViewService.on).toHaveBeenCalledWith("pan",
|
|
||||||
controller.onPan);
|
|
||||||
|
|
||||||
getListener(controller.conductorViewService.on, "pan")(testBounds);
|
|
||||||
|
|
||||||
expect(controller.$scope.boundsModel.start).toBe(testBounds.start);
|
|
||||||
expect(controller.$scope.boundsModel.end).toBe(testBounds.end);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when the URL defines conductor state", function () {
|
|
||||||
var urlBounds;
|
|
||||||
var urlTimeSystem;
|
|
||||||
var urlDeltas;
|
|
||||||
|
|
||||||
var mockDeltaFormat;
|
|
||||||
var defaultBounds;
|
|
||||||
var defaultDeltas;
|
|
||||||
var mockDefaults;
|
|
||||||
var timeSystem;
|
|
||||||
var otherTimeSystem;
|
|
||||||
var mockSearchObject;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
|
|
||||||
mockFormat = {};
|
|
||||||
mockDeltaFormat = {};
|
|
||||||
defaultBounds = {
|
|
||||||
start: 2,
|
|
||||||
end: 3
|
|
||||||
};
|
|
||||||
defaultDeltas = {
|
|
||||||
start: 10,
|
|
||||||
end: 20
|
|
||||||
};
|
|
||||||
mockDefaults = {
|
|
||||||
deltas: defaultDeltas,
|
|
||||||
bounds: defaultBounds
|
|
||||||
};
|
|
||||||
timeSystem = {
|
|
||||||
metadata: {
|
|
||||||
key: 'mockTimeSystem'
|
|
||||||
},
|
|
||||||
formats: function () {
|
|
||||||
return [mockFormat];
|
|
||||||
},
|
|
||||||
deltaFormat: function () {
|
|
||||||
return mockDeltaFormat;
|
|
||||||
},
|
|
||||||
defaults: function () {
|
|
||||||
return mockDefaults;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
otherTimeSystem = {
|
|
||||||
metadata: {
|
|
||||||
key: 'otherTimeSystem'
|
|
||||||
},
|
|
||||||
formats: function () {
|
|
||||||
return [mockFormat];
|
|
||||||
},
|
|
||||||
deltaFormat: function () {
|
|
||||||
return mockDeltaFormat;
|
|
||||||
},
|
|
||||||
defaults: function () {
|
|
||||||
return mockDefaults;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
mockConductorViewService.systems = [timeSystem, otherTimeSystem];
|
|
||||||
|
|
||||||
urlBounds = {
|
|
||||||
start: 100,
|
|
||||||
end: 200
|
|
||||||
};
|
|
||||||
urlTimeSystem = "otherTimeSystem";
|
|
||||||
urlDeltas = {
|
|
||||||
start: 300,
|
|
||||||
end: 400
|
|
||||||
};
|
|
||||||
mockSearchObject = {
|
|
||||||
"tc.startBound": urlBounds.start,
|
|
||||||
"tc.endBound": urlBounds.end,
|
|
||||||
"tc.startDelta": urlDeltas.start,
|
|
||||||
"tc.endDelta": urlDeltas.end,
|
|
||||||
"tc.timeSystem": urlTimeSystem
|
|
||||||
};
|
|
||||||
mockLocation.search.and.returnValue(mockSearchObject);
|
|
||||||
mockTimeConductor.timeSystem.and.returnValue(timeSystem);
|
|
||||||
|
|
||||||
controller = new TimeConductorController(
|
|
||||||
mockScope,
|
|
||||||
mockWindow,
|
|
||||||
mockLocation,
|
|
||||||
{conductor: mockTimeConductor},
|
|
||||||
mockConductorViewService,
|
|
||||||
mockFormatService,
|
|
||||||
"fixed",
|
|
||||||
true
|
|
||||||
);
|
|
||||||
|
|
||||||
spyOn(controller, "setMode");
|
|
||||||
spyOn(controller, "selectTimeSystemByKey");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("sets conductor state from URL", function () {
|
|
||||||
mockSearchObject["tc.mode"] = "fixed";
|
|
||||||
controller.setStateFromSearchParams(mockSearchObject);
|
|
||||||
expect(controller.selectTimeSystemByKey).toHaveBeenCalledWith("otherTimeSystem");
|
|
||||||
expect(mockTimeConductor.bounds).toHaveBeenCalledWith(urlBounds);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("sets mode from URL", function () {
|
|
||||||
mockTimeConductor.bounds.reset();
|
|
||||||
mockSearchObject["tc.mode"] = "realtime";
|
|
||||||
controller.setStateFromSearchParams(mockSearchObject);
|
|
||||||
expect(controller.setMode).toHaveBeenCalledWith("realtime");
|
|
||||||
expect(controller.selectTimeSystemByKey).toHaveBeenCalledWith("otherTimeSystem");
|
|
||||||
expect(mockConductorViewService.deltas).toHaveBeenCalledWith(urlDeltas);
|
|
||||||
expect(mockTimeConductor.bounds).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when conductor state changes", function () {
|
|
||||||
it("updates the URL with the mode", function () {
|
|
||||||
controller.setMode("realtime", "fixed");
|
|
||||||
expect(mockLocation.search).toHaveBeenCalledWith("tc.mode", "fixed");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("updates the URL with the bounds", function () {
|
|
||||||
mockConductorViewService.mode.and.returnValue("fixed");
|
|
||||||
controller.changeBounds({start: 500, end: 600});
|
|
||||||
expect(mockLocation.search).toHaveBeenCalledWith("tc.startBound", 500);
|
|
||||||
expect(mockLocation.search).toHaveBeenCalledWith("tc.endBound", 600);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("updates the URL with the deltas", function () {
|
|
||||||
controller.setDeltas({startDelta: 700, endDelta: 800});
|
|
||||||
expect(mockLocation.search).toHaveBeenCalledWith("tc.startDelta", 700);
|
|
||||||
expect(mockLocation.search).toHaveBeenCalledWith("tc.endDelta", 800);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("updates the URL with the time system", function () {
|
|
||||||
controller.changeTimeSystem(otherTimeSystem);
|
|
||||||
expect(mockLocation.search).toHaveBeenCalledWith("tc.timeSystem", "otherTimeSystem");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,69 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
[],
|
|
||||||
function () {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Form validation for the TimeConductorController.
|
|
||||||
* @param conductor
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function TimeConductorValidation(timeAPI) {
|
|
||||||
var self = this;
|
|
||||||
this.timeAPI = timeAPI;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Bind all class functions to 'this'
|
|
||||||
*/
|
|
||||||
Object.keys(TimeConductorValidation.prototype).filter(function (key) {
|
|
||||||
return typeof TimeConductorValidation.prototype[key] === 'function';
|
|
||||||
}).forEach(function (key) {
|
|
||||||
self[key] = self[key].bind(self);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validation methods below are invoked directly from controls in the TimeConductor form
|
|
||||||
*/
|
|
||||||
TimeConductorValidation.prototype.validateStart = function (start) {
|
|
||||||
var bounds = this.timeAPI.bounds();
|
|
||||||
return this.timeAPI.validateBounds({start: start, end: bounds.end}) === true;
|
|
||||||
};
|
|
||||||
|
|
||||||
TimeConductorValidation.prototype.validateEnd = function (end) {
|
|
||||||
var bounds = this.timeAPI.bounds();
|
|
||||||
return this.timeAPI.validateBounds({start: bounds.start, end: end}) === true;
|
|
||||||
};
|
|
||||||
|
|
||||||
TimeConductorValidation.prototype.validateStartOffset = function (startOffset) {
|
|
||||||
return !isNaN(startOffset) && startOffset > 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
TimeConductorValidation.prototype.validateEndOffset = function (endOffset) {
|
|
||||||
return !isNaN(endOffset) && endOffset >= 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
return TimeConductorValidation;
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,73 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(['./TimeConductorValidation'], function (TimeConductorValidation) {
|
|
||||||
describe("The Time Conductor Validation class", function () {
|
|
||||||
var timeConductorValidation,
|
|
||||||
mockTimeConductor;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
mockTimeConductor = jasmine.createSpyObj("timeConductor", [
|
|
||||||
"validateBounds",
|
|
||||||
"bounds"
|
|
||||||
]);
|
|
||||||
timeConductorValidation = new TimeConductorValidation(mockTimeConductor);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Validates start and end values using Time Conductor", function () {
|
|
||||||
beforeEach(function () {
|
|
||||||
var mockBounds = {
|
|
||||||
start: 10,
|
|
||||||
end: 20
|
|
||||||
};
|
|
||||||
|
|
||||||
mockTimeConductor.bounds.and.returnValue(mockBounds);
|
|
||||||
|
|
||||||
});
|
|
||||||
it("Validates start values using Time Conductor", function () {
|
|
||||||
var startValue = 30;
|
|
||||||
timeConductorValidation.validateStart(startValue);
|
|
||||||
expect(mockTimeConductor.validateBounds).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
it("Validates end values using Time Conductor", function () {
|
|
||||||
var endValue = 40;
|
|
||||||
timeConductorValidation.validateEnd(endValue);
|
|
||||||
expect(mockTimeConductor.validateBounds).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Validates that start Offset is valid number > 0", function () {
|
|
||||||
expect(timeConductorValidation.validateStartOffset(-1)).toBe(false);
|
|
||||||
expect(timeConductorValidation.validateStartOffset("abc")).toBe(false);
|
|
||||||
expect(timeConductorValidation.validateStartOffset("1")).toBe(true);
|
|
||||||
expect(timeConductorValidation.validateStartOffset(1)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Validates that end Offset is valid number >= 0", function () {
|
|
||||||
expect(timeConductorValidation.validateEndOffset(-1)).toBe(false);
|
|
||||||
expect(timeConductorValidation.validateEndOffset("abc")).toBe(false);
|
|
||||||
expect(timeConductorValidation.validateEndOffset("1")).toBe(true);
|
|
||||||
expect(timeConductorValidation.validateEndOffset(0)).toBe(true);
|
|
||||||
expect(timeConductorValidation.validateEndOffset(1)).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,97 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
[
|
|
||||||
'EventEmitter'
|
|
||||||
],
|
|
||||||
function (EventEmitter) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The TimeConductorViewService acts as an event bus between different
|
|
||||||
* elements of the Time Conductor UI. Zooming and panning occur via this
|
|
||||||
* service, as they are specific behaviour of the UI, and not general
|
|
||||||
* functions of the time API.
|
|
||||||
*
|
|
||||||
* Synchronization of conductor state between the Time API and the URL
|
|
||||||
* also occurs from the conductor view service, whose lifecycle persists
|
|
||||||
* between view changes.
|
|
||||||
*
|
|
||||||
* @memberof platform.features.conductor
|
|
||||||
* @param conductor
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function TimeConductorViewService(openmct) {
|
|
||||||
|
|
||||||
EventEmitter.call(this);
|
|
||||||
|
|
||||||
this.timeAPI = openmct.time;
|
|
||||||
}
|
|
||||||
|
|
||||||
TimeConductorViewService.prototype = Object.create(EventEmitter.prototype);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An event to indicate that zooming is taking place
|
|
||||||
* @event platform.features.conductor.TimeConductorViewService~zoom
|
|
||||||
* @property {ZoomLevel} zoom the new zoom level.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Zoom to given time span. Will fire a zoom event with new zoom
|
|
||||||
* bounds. Zoom bounds emitted in this way are considered ephemeral
|
|
||||||
* and should be overridden by any time conductor bounds events. Does
|
|
||||||
* not set bounds globally.
|
|
||||||
* @param {number} zoom A time duration in ms
|
|
||||||
* @fires platform.features.conductor.TimeConductorViewService~zoom
|
|
||||||
* @see module:openmct.TimeConductor#bounds
|
|
||||||
*/
|
|
||||||
TimeConductorViewService.prototype.zoom = function (timeSpan) {
|
|
||||||
var zoom = {};
|
|
||||||
|
|
||||||
// If a tick source is defined, then the concept of 'now' is
|
|
||||||
// important. Calculate zoom based on 'now'.
|
|
||||||
if (this.timeAPI.clock() !== undefined) {
|
|
||||||
zoom.offsets = {
|
|
||||||
start: -timeSpan,
|
|
||||||
end: this.timeAPI.clockOffsets().end
|
|
||||||
};
|
|
||||||
|
|
||||||
var currentVal = this.timeAPI.clock().currentValue();
|
|
||||||
|
|
||||||
zoom.bounds = {
|
|
||||||
start: currentVal + zoom.offsets.start,
|
|
||||||
end: currentVal + zoom.offsets.end
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
var bounds = this.timeAPI.bounds();
|
|
||||||
var center = bounds.start + ((bounds.end - bounds.start)) / 2;
|
|
||||||
bounds.start = center - timeSpan / 2;
|
|
||||||
bounds.end = center + timeSpan / 2;
|
|
||||||
zoom.bounds = bounds;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.emit("zoom", zoom);
|
|
||||||
return zoom;
|
|
||||||
};
|
|
||||||
|
|
||||||
return TimeConductorViewService;
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,109 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
[],
|
|
||||||
function () {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Controller for the Time of Interest element used in various views to display the TOI. Responsible for setting
|
|
||||||
* the text label for the current TOI, and for toggling the (un)pinned state which determines whether the TOI
|
|
||||||
* indicator is visible.
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function TimeOfInterestController($scope, openmct, formatService) {
|
|
||||||
this.timeAPI = openmct.time;
|
|
||||||
this.formatService = formatService;
|
|
||||||
this.format = undefined;
|
|
||||||
this.toiText = undefined;
|
|
||||||
this.$scope = $scope;
|
|
||||||
|
|
||||||
//Bind all class functions to 'this'
|
|
||||||
Object.keys(TimeOfInterestController.prototype).filter(function (key) {
|
|
||||||
return typeof TimeOfInterestController.prototype[key] === 'function';
|
|
||||||
}).forEach(function (key) {
|
|
||||||
this[key] = TimeOfInterestController.prototype[key].bind(this);
|
|
||||||
}.bind(this));
|
|
||||||
|
|
||||||
this.timeAPI.on('timeOfInterest', this.changeTimeOfInterest);
|
|
||||||
this.timeAPI.on('timeSystem', this.changeTimeSystem);
|
|
||||||
if (this.timeAPI.timeSystem() !== undefined) {
|
|
||||||
this.changeTimeSystem(this.timeAPI.timeSystem());
|
|
||||||
var toi = this.timeAPI.timeOfInterest();
|
|
||||||
if (toi) {
|
|
||||||
this.changeTimeOfInterest(toi);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.$on('$destroy', this.destroy);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the time of interest changes on the conductor. Will pin (display) the TOI indicator, and set the
|
|
||||||
* text using the default formatter of the currently active Time System.
|
|
||||||
* @private
|
|
||||||
* @param {integer} toi Current time of interest in ms
|
|
||||||
*/
|
|
||||||
TimeOfInterestController.prototype.changeTimeOfInterest = function (toi) {
|
|
||||||
if (toi !== undefined) {
|
|
||||||
this.$scope.pinned = true;
|
|
||||||
this.toiText = this.format.format(toi);
|
|
||||||
} else {
|
|
||||||
this.$scope.pinned = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When time system is changed, update the formatter used to
|
|
||||||
* display the current TOI label
|
|
||||||
*/
|
|
||||||
TimeOfInterestController.prototype.changeTimeSystem = function (timeSystem) {
|
|
||||||
this.format = this.formatService.getFormat(timeSystem.timeFormat);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
TimeOfInterestController.prototype.destroy = function () {
|
|
||||||
this.timeAPI.off('timeOfInterest', this.changeTimeOfInterest);
|
|
||||||
this.timeAPI.off('timeSystem', this.changeTimeSystem);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Will unpin (hide) the TOI indicator. Has the effect of setting the time of interest to `undefined` on the
|
|
||||||
* Time Conductor
|
|
||||||
*/
|
|
||||||
TimeOfInterestController.prototype.dismiss = function () {
|
|
||||||
this.timeAPI.timeOfInterest(undefined);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends out a time of interest event with the effect of resetting
|
|
||||||
* the TOI displayed in views.
|
|
||||||
*/
|
|
||||||
TimeOfInterestController.prototype.resync = function () {
|
|
||||||
this.timeAPI.timeOfInterest(this.timeAPI.timeOfInterest());
|
|
||||||
};
|
|
||||||
|
|
||||||
return TimeOfInterestController;
|
|
||||||
}
|
|
||||||
);
|
|
@ -19,10 +19,10 @@
|
|||||||
this source code distribution or the Licensing information page available
|
this source code distribution or the Licensing information page available
|
||||||
at runtime from the About dialog for additional information.
|
at runtime from the About dialog for additional information.
|
||||||
-->
|
-->
|
||||||
<a class="l-hyperlink s-hyperlink" ng-controller="HyperlinkController as hyperlink" href="{{domainObject.getModel().url}}"
|
<a class="c-hyperlink u-links" ng-controller="HyperlinkController as hyperlink" href="{{domainObject.getModel().url}}"
|
||||||
ng-attr-target="{{hyperlink.openNewTab() ? '_blank' : undefined}}"
|
ng-attr-target="{{hyperlink.openNewTab() ? '_blank' : undefined}}"
|
||||||
ng-class="{
|
ng-class="{
|
||||||
's-button': hyperlink.isButton()
|
'c-hyperlink--button u-fills-container' : hyperlink.isButton(),
|
||||||
}">
|
'c-hyperlink--link' : !hyperlink.isButton() }">
|
||||||
<span class="label">{{domainObject.getModel().displayText}}</span>
|
<span class="c-hyperlink__label">{{domainObject.getModel().displayText}}</span>
|
||||||
</a>
|
</a>
|
||||||
|
@ -1,83 +0,0 @@
|
|||||||
<!--
|
|
||||||
Open MCT, Copyright (c) 2014-2018, United States Government
|
|
||||||
as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
Administration. All rights reserved.
|
|
||||||
|
|
||||||
Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
"License"); you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
License for the specific language governing permissions and limitations
|
|
||||||
under the License.
|
|
||||||
|
|
||||||
Open MCT includes source code licensed under additional open source
|
|
||||||
licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
this source code distribution or the Licensing information page available
|
|
||||||
at runtime from the About dialog for additional information.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<div class="abs l-layout {{ domainObject.getModel().layoutAdvancedCss }}"
|
|
||||||
ng-controller="LayoutController as controller"
|
|
||||||
ng-click="controller.bypassSelection($event)">
|
|
||||||
|
|
||||||
<!-- Background grid -->
|
|
||||||
<div class="l-grid-holder"
|
|
||||||
ng-show="!controller.drilledIn"
|
|
||||||
ng-click="controller.bypassSelection($event)">
|
|
||||||
<div class="l-grid l-grid-x"
|
|
||||||
ng-if="!controller.getGridSize()[0] < 3"
|
|
||||||
ng-style="{ 'background-size': controller.getGridSize() [0] + 'px 100%' }"></div>
|
|
||||||
<div class="l-grid l-grid-y"
|
|
||||||
ng-if="!controller.getGridSize()[1] < 3"
|
|
||||||
ng-style="{ 'background-size': '100% ' + controller.getGridSize() [1] + 'px' }"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="frame t-frame-outer child-frame panel s-selectable s-moveable s-hover-border t-object-type-{{ childObject.getModel().type }}"
|
|
||||||
data-layout-id="{{childObject.getId() + '-' + $id}}"
|
|
||||||
ng-class="{ 'no-frame': !controller.hasFrame(childObject), 's-drilled-in': controller.isDrilledIn(childObject) }"
|
|
||||||
ng-repeat="childObject in composition"
|
|
||||||
ng-init="controller.selectIfNew(childObject.getId() + '-' + $id, childObject)"
|
|
||||||
mct-selectable="controller.getContext(childObject)"
|
|
||||||
ng-dblclick="controller.drill($event, childObject)"
|
|
||||||
ng-style="controller.getFrameStyle(childObject.getId())">
|
|
||||||
|
|
||||||
<mct-representation key="'frame'"
|
|
||||||
class="t-rep-frame holder contents abs"
|
|
||||||
mct-object="childObject">
|
|
||||||
</mct-representation>
|
|
||||||
<!-- Drag handles -->
|
|
||||||
<span class="abs t-edit-handle-holder" ng-if="controller.selected(childObject) && !controller.isDrilledIn(childObject)">
|
|
||||||
<span class="edit-handle edit-move"
|
|
||||||
mct-drag-down="controller.startDrag(childObject.getId(), [1,1], [0,0])"
|
|
||||||
mct-drag="controller.continueDrag(delta)"
|
|
||||||
mct-drag-up="controller.endDrag()">
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span class="edit-corner edit-resize-nw"
|
|
||||||
mct-drag-down="controller.startDrag(childObject.getId(), [1,1], [-1,-1])"
|
|
||||||
mct-drag="controller.continueDrag(delta)"
|
|
||||||
mct-drag-up="controller.endDrag()">
|
|
||||||
</span>
|
|
||||||
<span class="edit-corner edit-resize-ne"
|
|
||||||
mct-drag-down="controller.startDrag(childObject.getId(), [0,1], [1,-1])"
|
|
||||||
mct-drag="controller.continueDrag(delta)"
|
|
||||||
mct-drag-up="controller.endDrag()">
|
|
||||||
</span>
|
|
||||||
<span class="edit-corner edit-resize-sw"
|
|
||||||
mct-drag-down="controller.startDrag(childObject.getId(), [1,0], [-1,1])"
|
|
||||||
mct-drag="controller.continueDrag(delta)"
|
|
||||||
mct-drag-up="controller.endDrag()">
|
|
||||||
</span>
|
|
||||||
<span class="edit-corner edit-resize-se"
|
|
||||||
mct-drag-down="controller.startDrag(childObject.getId(), [0,0], [1,1])"
|
|
||||||
mct-drag="controller.continueDrag(delta)"
|
|
||||||
mct-drag-up="controller.endDrag()">
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
@ -1,524 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This bundle implements object types and associated views for
|
|
||||||
* display-building.
|
|
||||||
* @namespace platform/features/layout
|
|
||||||
*/
|
|
||||||
define(
|
|
||||||
[
|
|
||||||
'zepto',
|
|
||||||
'./LayoutDrag'
|
|
||||||
],
|
|
||||||
function (
|
|
||||||
$,
|
|
||||||
LayoutDrag
|
|
||||||
) {
|
|
||||||
|
|
||||||
var DEFAULT_DIMENSIONS = [12, 8],
|
|
||||||
DEFAULT_GRID_SIZE = [32, 32],
|
|
||||||
MINIMUM_FRAME_SIZE = [320, 180];
|
|
||||||
|
|
||||||
var DEFAULT_HIDDEN_FRAME_TYPES = [
|
|
||||||
'hyperlink'
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The LayoutController is responsible for supporting the
|
|
||||||
* Layout view. It arranges frames according to saved configuration
|
|
||||||
* and provides methods for updating these based on mouse
|
|
||||||
* movement.
|
|
||||||
* @memberof platform/features/layout
|
|
||||||
* @constructor
|
|
||||||
* @param {Scope} $scope the controller's Angular scope
|
|
||||||
*/
|
|
||||||
function LayoutController($scope, $element, openmct) {
|
|
||||||
var self = this,
|
|
||||||
callbackCount = 0;
|
|
||||||
|
|
||||||
this.$element = $element;
|
|
||||||
|
|
||||||
// Update grid size when it changed
|
|
||||||
function updateGridSize(layoutGrid) {
|
|
||||||
var oldSize = self.gridSize;
|
|
||||||
|
|
||||||
self.gridSize = layoutGrid || DEFAULT_GRID_SIZE;
|
|
||||||
|
|
||||||
// Only update panel positions if this actually changed things
|
|
||||||
if (self.gridSize[0] !== oldSize[0] ||
|
|
||||||
self.gridSize[1] !== oldSize[1]) {
|
|
||||||
self.layoutPanels(Object.keys(self.positions));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Position a panel after a drop event
|
|
||||||
function handleDrop(e, id, position) {
|
|
||||||
if (e.defaultPrevented) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.configuration = $scope.configuration || {};
|
|
||||||
$scope.configuration.panels = $scope.configuration.panels || {};
|
|
||||||
|
|
||||||
self.openmct.objects.get(id).then(function (object) {
|
|
||||||
$scope.configuration.panels[id] = {
|
|
||||||
position: [
|
|
||||||
Math.floor(position.x / self.gridSize[0]),
|
|
||||||
Math.floor(position.y / self.gridSize[1])
|
|
||||||
],
|
|
||||||
dimensions: self.defaultDimensions(),
|
|
||||||
hasFrame: self.getDefaultFrame(object.type)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Store the id so that the newly-dropped object
|
|
||||||
// gets selected during refresh composition
|
|
||||||
self.droppedIdToSelectAfterRefresh = id;
|
|
||||||
|
|
||||||
self.commit();
|
|
||||||
|
|
||||||
// Populate template-facing position for this id
|
|
||||||
self.rawPositions[id] = $scope.configuration.panels[id];
|
|
||||||
self.populatePosition(id);
|
|
||||||
refreshComposition();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Layout may contain embedded views which will
|
|
||||||
// listen for drops, so call preventDefault() so
|
|
||||||
// that they can recognize that this event is handled.
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
//Will fetch fully contextualized composed objects, and populate
|
|
||||||
// scope with them.
|
|
||||||
function refreshComposition() {
|
|
||||||
//Keep a track of how many composition callbacks have been made
|
|
||||||
var thisCount = ++callbackCount;
|
|
||||||
|
|
||||||
$scope.domainObject.useCapability('composition').then(function (composition) {
|
|
||||||
var ids;
|
|
||||||
|
|
||||||
//Is this callback for the most recent composition
|
|
||||||
// request? If not, discard it. Prevents race condition
|
|
||||||
if (thisCount === callbackCount) {
|
|
||||||
ids = composition.map(function (object) {
|
|
||||||
return object.getId();
|
|
||||||
}) || [];
|
|
||||||
|
|
||||||
$scope.composition = composition;
|
|
||||||
self.layoutPanels(ids);
|
|
||||||
self.setFrames(ids);
|
|
||||||
|
|
||||||
if (self.selectedId &&
|
|
||||||
self.selectedId !== $scope.domainObject.getId() &&
|
|
||||||
composition.indexOf(self.selectedId) === -1) {
|
|
||||||
// Click triggers selection of layout parent.
|
|
||||||
self.$element[0].click();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// End drag; we don't want to put $scope into this
|
|
||||||
// because it triggers "cpws" (copy window or scope)
|
|
||||||
// errors in Angular.
|
|
||||||
this.endDragInScope = function () {
|
|
||||||
// Write to configuration; this is watched and
|
|
||||||
// saved by the EditRepresenter.
|
|
||||||
$scope.configuration =
|
|
||||||
$scope.configuration || {};
|
|
||||||
|
|
||||||
$scope.configuration.panels =
|
|
||||||
$scope.configuration.panels || {};
|
|
||||||
|
|
||||||
$scope.configuration.panels[self.activeDragId] =
|
|
||||||
$scope.configuration.panels[self.activeDragId] || {};
|
|
||||||
|
|
||||||
$scope.configuration.panels[self.activeDragId].position =
|
|
||||||
self.rawPositions[self.activeDragId].position;
|
|
||||||
$scope.configuration.panels[self.activeDragId].dimensions =
|
|
||||||
self.rawPositions[self.activeDragId].dimensions;
|
|
||||||
|
|
||||||
self.commit();
|
|
||||||
};
|
|
||||||
|
|
||||||
// Sets the selectable object in response to the selection change event.
|
|
||||||
function setSelection(selectable) {
|
|
||||||
var selection = selectable[0];
|
|
||||||
|
|
||||||
if (!selection) {
|
|
||||||
delete self.selectedId;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.selectedId = selection.context.oldItem.getId();
|
|
||||||
self.drilledIn = undefined;
|
|
||||||
self.selectable = selectable;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.positions = {};
|
|
||||||
this.rawPositions = {};
|
|
||||||
this.gridSize = DEFAULT_GRID_SIZE;
|
|
||||||
this.$scope = $scope;
|
|
||||||
this.drilledIn = undefined;
|
|
||||||
this.openmct = openmct;
|
|
||||||
|
|
||||||
// Watch for changes to the grid size in the model
|
|
||||||
$scope.$watch("model.layoutGrid", updateGridSize);
|
|
||||||
|
|
||||||
// Update composed objects on screen, and position panes
|
|
||||||
$scope.$watchCollection("model.composition", refreshComposition);
|
|
||||||
|
|
||||||
openmct.selection.on('change', setSelection);
|
|
||||||
|
|
||||||
$scope.$on("$destroy", function () {
|
|
||||||
openmct.selection.off("change", setSelection);
|
|
||||||
self.unlisten();
|
|
||||||
});
|
|
||||||
|
|
||||||
$scope.$on("mctDrop", handleDrop);
|
|
||||||
|
|
||||||
self.unlisten = self.$scope.domainObject.getCapability('mutation').listen(function (model) {
|
|
||||||
$scope.configuration = model.configuration.layout;
|
|
||||||
$scope.model = model;
|
|
||||||
var panels = $scope.configuration.panels;
|
|
||||||
|
|
||||||
Object.keys(panels).forEach(function (key) {
|
|
||||||
if (self.frames && self.frames.hasOwnProperty(key)) {
|
|
||||||
self.frames[key] = panels[key].hasFrame;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Utility function to copy raw positions from configuration,
|
|
||||||
// without writing directly to configuration (to avoid triggering
|
|
||||||
// persistence from watchers during drags).
|
|
||||||
function shallowCopy(obj, keys) {
|
|
||||||
var copy = {};
|
|
||||||
keys.forEach(function (k) {
|
|
||||||
copy[k] = obj[k];
|
|
||||||
});
|
|
||||||
return copy;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the frames value. If a configuration panel has "hasFrame' property,
|
|
||||||
* use that value, otherwise set a default value. A 'hyperlink' object should
|
|
||||||
* have no frame by default.
|
|
||||||
*
|
|
||||||
* @param {string[]} ids the object ids
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
LayoutController.prototype.setFrames = function (ids) {
|
|
||||||
var panels = shallowCopy(this.$scope.configuration.panels || {}, ids);
|
|
||||||
this.frames = {};
|
|
||||||
|
|
||||||
this.$scope.composition.forEach(function (object) {
|
|
||||||
var id = object.getId();
|
|
||||||
panels[id] = panels[id] || {};
|
|
||||||
|
|
||||||
if (panels[id].hasOwnProperty('hasFrame')) {
|
|
||||||
this.frames[id] = panels[id].hasFrame;
|
|
||||||
} else {
|
|
||||||
this.frames[id] = this.getDefaultFrame(object.getModel().type);
|
|
||||||
}
|
|
||||||
}, this);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the default value for frame.
|
|
||||||
*
|
|
||||||
* @param type the domain object type
|
|
||||||
* @return {boolean} true if the object should have
|
|
||||||
* frame by default, false, otherwise
|
|
||||||
*/
|
|
||||||
LayoutController.prototype.getDefaultFrame = function (type) {
|
|
||||||
return DEFAULT_HIDDEN_FRAME_TYPES.indexOf(type) === -1;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Convert from { positions: ..., dimensions: ... } to an
|
|
||||||
// appropriate ng-style argument, to position frames.
|
|
||||||
LayoutController.prototype.convertPosition = function (raw) {
|
|
||||||
var gridSize = this.gridSize;
|
|
||||||
// Multiply position/dimensions by grid size
|
|
||||||
return {
|
|
||||||
left: (gridSize[0] * raw.position[0]) + 'px',
|
|
||||||
top: (gridSize[1] * raw.position[1]) + 'px',
|
|
||||||
width: (gridSize[0] * raw.dimensions[0]) + 'px',
|
|
||||||
height: (gridSize[1] * raw.dimensions[1]) + 'px',
|
|
||||||
minWidth: (gridSize[0] * raw.dimensions[0]) + 'px',
|
|
||||||
minHeight: (gridSize[1] * raw.dimensions[1]) + 'px'
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// Generate default positions for a new panel
|
|
||||||
LayoutController.prototype.defaultDimensions = function () {
|
|
||||||
var gridSize = this.gridSize;
|
|
||||||
return MINIMUM_FRAME_SIZE.map(function (min, i) {
|
|
||||||
return Math.max(
|
|
||||||
Math.ceil(min / gridSize[i]),
|
|
||||||
DEFAULT_DIMENSIONS[i]
|
|
||||||
);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Generate a default position (in its raw format) for a frame.
|
|
||||||
// Use an index to ensure that default positions are unique.
|
|
||||||
LayoutController.prototype.defaultPosition = function (index) {
|
|
||||||
return {
|
|
||||||
position: [index, index],
|
|
||||||
dimensions: this.defaultDimensions()
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// Store a computed position for a contained frame by its
|
|
||||||
// domain object id. Called in a forEach loop, so arguments
|
|
||||||
// are as expected there.
|
|
||||||
LayoutController.prototype.populatePosition = function (id, index) {
|
|
||||||
this.rawPositions[id] =
|
|
||||||
this.rawPositions[id] || this.defaultPosition(index || 0);
|
|
||||||
this.positions[id] =
|
|
||||||
this.convertPosition(this.rawPositions[id]);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a style object for a frame with the specified domain
|
|
||||||
* object identifier, suitable for use in an `ng-style`
|
|
||||||
* directive to position a frame as configured for this layout.
|
|
||||||
* @param {string} id the object identifier
|
|
||||||
* @returns {Object.<string, string>} an object with
|
|
||||||
* appropriate left, width, etc fields for positioning
|
|
||||||
*/
|
|
||||||
LayoutController.prototype.getFrameStyle = function (id) {
|
|
||||||
// Called in a loop, so just look up; the "positions"
|
|
||||||
// object is kept up to date by a watch.
|
|
||||||
return this.positions[id];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start a drag gesture to move/resize a frame.
|
|
||||||
*
|
|
||||||
* The provided position and dimensions factors will determine
|
|
||||||
* whether this is a move or a resize, and what type it
|
|
||||||
* will be. For instance, a position factor of [1, 1]
|
|
||||||
* will move a frame along with the mouse as the drag
|
|
||||||
* proceeds, while a dimension factor of [0, 0] will leave
|
|
||||||
* dimensions unchanged. Combining these in different
|
|
||||||
* ways results in different handles; a position factor of
|
|
||||||
* [1, 0] and a dimensions factor of [-1, 0] will implement
|
|
||||||
* a left-edge resize, as the horizontal position will move
|
|
||||||
* with the mouse while the horizontal dimensions shrink in
|
|
||||||
* kind (and vertical properties remain unmodified.)
|
|
||||||
*
|
|
||||||
* @param {string} id the identifier of the domain object
|
|
||||||
* in the frame being manipulated
|
|
||||||
* @param {number[]} posFactor the position factor
|
|
||||||
* @param {number[]} dimFactor the dimensions factor
|
|
||||||
*/
|
|
||||||
LayoutController.prototype.startDrag = function (id, posFactor, dimFactor) {
|
|
||||||
this.activeDragId = id;
|
|
||||||
this.activeDrag = new LayoutDrag(
|
|
||||||
this.rawPositions[id],
|
|
||||||
posFactor,
|
|
||||||
dimFactor,
|
|
||||||
this.gridSize
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Continue an active drag gesture.
|
|
||||||
* @param {number[]} delta the offset, in pixels,
|
|
||||||
* of the current pointer position, relative
|
|
||||||
* to its position when the drag started
|
|
||||||
*/
|
|
||||||
LayoutController.prototype.continueDrag = function (delta) {
|
|
||||||
if (this.activeDrag) {
|
|
||||||
this.rawPositions[this.activeDragId] =
|
|
||||||
this.activeDrag.getAdjustedPosition(delta);
|
|
||||||
this.populatePosition(this.activeDragId);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compute panel positions based on the layout's object model.
|
|
||||||
* Defined as member function to facilitate testing.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
LayoutController.prototype.layoutPanels = function (ids) {
|
|
||||||
var configuration = this.$scope.configuration || {},
|
|
||||||
self = this;
|
|
||||||
|
|
||||||
// Pull panel positions from configuration
|
|
||||||
this.rawPositions =
|
|
||||||
shallowCopy(configuration.panels || {}, ids);
|
|
||||||
|
|
||||||
// Clear prior computed positions
|
|
||||||
this.positions = {};
|
|
||||||
|
|
||||||
// Update width/height that we are tracking
|
|
||||||
this.gridSize =
|
|
||||||
(this.$scope.model || {}).layoutGrid || DEFAULT_GRID_SIZE;
|
|
||||||
|
|
||||||
// Compute positions and add defaults where needed
|
|
||||||
ids.forEach(function (id, index) {
|
|
||||||
self.populatePosition(id, index);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* End the active drag gesture. This will update the
|
|
||||||
* view configuration.
|
|
||||||
*/
|
|
||||||
LayoutController.prototype.endDrag = function () {
|
|
||||||
this.dragInProgress = true;
|
|
||||||
|
|
||||||
setTimeout(function () {
|
|
||||||
this.dragInProgress = false;
|
|
||||||
}.bind(this), 0);
|
|
||||||
|
|
||||||
this.endDragInScope();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the object is currently selected.
|
|
||||||
*
|
|
||||||
* @param {string} obj the object to check for selection
|
|
||||||
* @returns {boolean} true if selected, otherwise false
|
|
||||||
*/
|
|
||||||
LayoutController.prototype.selected = function (obj) {
|
|
||||||
var sobj = this.openmct.selection.get()[0];
|
|
||||||
return (sobj && sobj.context.oldItem.getId() === obj.getId()) ? true : false;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bypasses selection if drag is in progress.
|
|
||||||
*
|
|
||||||
* @param event the angular event object
|
|
||||||
*/
|
|
||||||
LayoutController.prototype.bypassSelection = function (event) {
|
|
||||||
if (this.dragInProgress) {
|
|
||||||
if (event) {
|
|
||||||
event.stopPropagation();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the domain object is drilled in.
|
|
||||||
*
|
|
||||||
* @param domainObject the domain object
|
|
||||||
* @return true if the object is drilled in, false otherwise
|
|
||||||
*/
|
|
||||||
LayoutController.prototype.isDrilledIn = function (domainObject) {
|
|
||||||
return this.drilledIn === domainObject.getId();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Puts the given object in the drilled-in mode.
|
|
||||||
*
|
|
||||||
* @param event the angular event object
|
|
||||||
* @param domainObject the domain object
|
|
||||||
*/
|
|
||||||
LayoutController.prototype.drill = function (event, domainObject) {
|
|
||||||
if (event) {
|
|
||||||
event.stopPropagation();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!domainObject.getCapability('editor').inEditContext()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!domainObject.hasCapability('composition')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disable since fixed position doesn't use the selection API yet
|
|
||||||
if (domainObject.getModel().type === 'telemetry.fixed') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.drilledIn = domainObject.getId();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the object has frame.
|
|
||||||
*
|
|
||||||
* @param {object} obj the object
|
|
||||||
* @return {boolean} true if object has frame, otherwise false
|
|
||||||
*/
|
|
||||||
LayoutController.prototype.hasFrame = function (obj) {
|
|
||||||
return this.frames[obj.getId()];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the size of the grid, in pixels. The returned array
|
|
||||||
* is in the form `[x, y]`.
|
|
||||||
* @returns {number[]} the grid size
|
|
||||||
*/
|
|
||||||
LayoutController.prototype.getGridSize = function () {
|
|
||||||
return this.gridSize;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the selection context.
|
|
||||||
*
|
|
||||||
* @param domainObject the domain object
|
|
||||||
* @returns {object} the context object which includes item and oldItem
|
|
||||||
*/
|
|
||||||
LayoutController.prototype.getContext = function (domainObject) {
|
|
||||||
return {
|
|
||||||
item: domainObject.useCapability('adapter'),
|
|
||||||
oldItem: domainObject
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
LayoutController.prototype.commit = function () {
|
|
||||||
var model = this.$scope.model;
|
|
||||||
model.configuration = model.configuration || {};
|
|
||||||
model.configuration.layout = this.$scope.configuration;
|
|
||||||
|
|
||||||
this.$scope.domainObject.useCapability('mutation', function () {
|
|
||||||
return model;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Selects a newly-dropped object.
|
|
||||||
*
|
|
||||||
* @param classSelector the css class selector
|
|
||||||
* @param domainObject the domain object
|
|
||||||
*/
|
|
||||||
LayoutController.prototype.selectIfNew = function (selector, domainObject) {
|
|
||||||
if (domainObject.getId() === this.droppedIdToSelectAfterRefresh) {
|
|
||||||
setTimeout(function () {
|
|
||||||
$('[data-layout-id="' + selector + '"]')[0].click();
|
|
||||||
delete this.droppedIdToSelectAfterRefresh;
|
|
||||||
}.bind(this), 0);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return LayoutController;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
@ -1,479 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
[
|
|
||||||
"../src/LayoutController",
|
|
||||||
"zepto"
|
|
||||||
],
|
|
||||||
function (
|
|
||||||
LayoutController,
|
|
||||||
$
|
|
||||||
) {
|
|
||||||
|
|
||||||
describe("The Layout controller", function () {
|
|
||||||
var mockScope,
|
|
||||||
mockEvent,
|
|
||||||
testModel,
|
|
||||||
testConfiguration,
|
|
||||||
controller,
|
|
||||||
mockCompositionCapability,
|
|
||||||
mockComposition,
|
|
||||||
mockCompositionObjects,
|
|
||||||
mockOpenMCT,
|
|
||||||
mockSelection,
|
|
||||||
mockDomainObjectCapability,
|
|
||||||
mockObjects,
|
|
||||||
unlistenFunc,
|
|
||||||
$element = [],
|
|
||||||
selectable = [];
|
|
||||||
|
|
||||||
function mockPromise(value) {
|
|
||||||
return {
|
|
||||||
then: function (thenFunc) {
|
|
||||||
return mockPromise(thenFunc(value));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function mockDomainObject(id) {
|
|
||||||
return {
|
|
||||||
getId: function () {
|
|
||||||
return id;
|
|
||||||
},
|
|
||||||
useCapability: function () {
|
|
||||||
return mockCompositionCapability;
|
|
||||||
},
|
|
||||||
getModel: function () {
|
|
||||||
if (id === 'b') {
|
|
||||||
return {
|
|
||||||
type : 'hyperlink'
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getCapability: function () {
|
|
||||||
return mockDomainObjectCapability;
|
|
||||||
},
|
|
||||||
hasCapability: function (param) {
|
|
||||||
if (param === 'composition') {
|
|
||||||
return id !== 'b';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
type: "testType"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
mockScope = jasmine.createSpyObj(
|
|
||||||
"$scope",
|
|
||||||
["$watch", "$watchCollection", "$on"]
|
|
||||||
);
|
|
||||||
mockEvent = jasmine.createSpyObj(
|
|
||||||
'event',
|
|
||||||
['preventDefault', 'stopPropagation']
|
|
||||||
);
|
|
||||||
|
|
||||||
testModel = {};
|
|
||||||
|
|
||||||
mockComposition = ["a", "b", "c"];
|
|
||||||
mockCompositionObjects = mockComposition.map(mockDomainObject);
|
|
||||||
|
|
||||||
testConfiguration = {
|
|
||||||
panels: {
|
|
||||||
a: {
|
|
||||||
position: [20, 10],
|
|
||||||
dimensions: [5, 5]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
unlistenFunc = jasmine.createSpy("unlisten");
|
|
||||||
mockDomainObjectCapability = jasmine.createSpyObj('capability',
|
|
||||||
['inEditContext', 'listen']
|
|
||||||
);
|
|
||||||
mockDomainObjectCapability.listen.and.returnValue(unlistenFunc);
|
|
||||||
|
|
||||||
mockCompositionCapability = mockPromise(mockCompositionObjects);
|
|
||||||
|
|
||||||
mockScope.domainObject = mockDomainObject("mockDomainObject");
|
|
||||||
mockScope.model = testModel;
|
|
||||||
mockScope.configuration = testConfiguration;
|
|
||||||
|
|
||||||
selectable[0] = {
|
|
||||||
context: {
|
|
||||||
oldItem: mockScope.domainObject
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
mockSelection = jasmine.createSpyObj("selection", [
|
|
||||||
'select',
|
|
||||||
'on',
|
|
||||||
'off',
|
|
||||||
'get'
|
|
||||||
]);
|
|
||||||
mockSelection.get.and.returnValue(selectable);
|
|
||||||
|
|
||||||
mockObjects = jasmine.createSpyObj('objects', [
|
|
||||||
'get'
|
|
||||||
]);
|
|
||||||
mockObjects.get.and.returnValue(mockPromise(mockDomainObject("mockObject")));
|
|
||||||
mockOpenMCT = {
|
|
||||||
selection: mockSelection,
|
|
||||||
objects: mockObjects
|
|
||||||
};
|
|
||||||
|
|
||||||
$element = $('<div></div>');
|
|
||||||
$(document).find('body').append($element);
|
|
||||||
spyOn($element[0], 'click');
|
|
||||||
|
|
||||||
spyOn(mockScope.domainObject, "useCapability").and.callThrough();
|
|
||||||
|
|
||||||
controller = new LayoutController(mockScope, $element, mockOpenMCT);
|
|
||||||
spyOn(controller, "layoutPanels").and.callThrough();
|
|
||||||
spyOn(controller, "commit");
|
|
||||||
|
|
||||||
jasmine.clock().install();
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(function () {
|
|
||||||
$element.remove();
|
|
||||||
jasmine.clock().uninstall();
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it("listens for selection change events", function () {
|
|
||||||
expect(mockOpenMCT.selection.on).toHaveBeenCalledWith(
|
|
||||||
'change',
|
|
||||||
jasmine.any(Function)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("cleans up on scope destroy", function () {
|
|
||||||
expect(mockScope.$on).toHaveBeenCalledWith(
|
|
||||||
'$destroy',
|
|
||||||
jasmine.any(Function)
|
|
||||||
);
|
|
||||||
|
|
||||||
mockScope.$on.calls.all()[0].args[1]();
|
|
||||||
|
|
||||||
expect(mockOpenMCT.selection.off).toHaveBeenCalledWith(
|
|
||||||
'change',
|
|
||||||
jasmine.any(Function)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Model changes will indicate that panel positions
|
|
||||||
// may have changed, for instance.
|
|
||||||
it("watches for changes to composition", function () {
|
|
||||||
expect(mockScope.$watchCollection).toHaveBeenCalledWith(
|
|
||||||
"model.composition",
|
|
||||||
jasmine.any(Function)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Retrieves updated composition from composition capability", function () {
|
|
||||||
mockScope.$watchCollection.calls.mostRecent().args[1]();
|
|
||||||
expect(mockScope.domainObject.useCapability).toHaveBeenCalledWith(
|
|
||||||
"composition"
|
|
||||||
);
|
|
||||||
expect(controller.layoutPanels).toHaveBeenCalledWith(
|
|
||||||
mockComposition
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Is robust to concurrent changes to composition", function () {
|
|
||||||
var secondMockComposition = ["a", "b", "c", "d"],
|
|
||||||
secondMockCompositionObjects = secondMockComposition.map(mockDomainObject),
|
|
||||||
firstCompositionCB,
|
|
||||||
secondCompositionCB;
|
|
||||||
|
|
||||||
spyOn(mockCompositionCapability, "then");
|
|
||||||
mockScope.$watchCollection.calls.mostRecent().args[1]();
|
|
||||||
mockScope.$watchCollection.calls.mostRecent().args[1]();
|
|
||||||
|
|
||||||
firstCompositionCB = mockCompositionCapability.then.calls.all()[0].args[0];
|
|
||||||
secondCompositionCB = mockCompositionCapability.then.calls.all()[1].args[0];
|
|
||||||
|
|
||||||
//Resolve promises in reverse order
|
|
||||||
secondCompositionCB(secondMockCompositionObjects);
|
|
||||||
firstCompositionCB(mockCompositionObjects);
|
|
||||||
|
|
||||||
//Expect the promise call that was initiated most recently to
|
|
||||||
// be the one used to populate scope, irrespective of order that
|
|
||||||
// it was eventually resolved
|
|
||||||
expect(mockScope.composition).toBe(secondMockCompositionObjects);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it("provides styles for frames, from configuration", function () {
|
|
||||||
mockScope.$watchCollection.calls.mostRecent().args[1]();
|
|
||||||
expect(controller.getFrameStyle("a")).toEqual({
|
|
||||||
top: "320px",
|
|
||||||
left: "640px",
|
|
||||||
width: "160px",
|
|
||||||
height: "160px",
|
|
||||||
minWidth : '160px',
|
|
||||||
minHeight : '160px'
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("provides default styles for frames", function () {
|
|
||||||
var styleB, styleC;
|
|
||||||
|
|
||||||
// b and c do not have configured positions
|
|
||||||
mockScope.$watchCollection.calls.mostRecent().args[1]();
|
|
||||||
|
|
||||||
styleB = controller.getFrameStyle("b");
|
|
||||||
styleC = controller.getFrameStyle("c");
|
|
||||||
|
|
||||||
// Should have a position, but we don't care what
|
|
||||||
expect(styleB.left).toBeDefined();
|
|
||||||
expect(styleB.top).toBeDefined();
|
|
||||||
expect(styleC.left).toBeDefined();
|
|
||||||
expect(styleC.top).toBeDefined();
|
|
||||||
|
|
||||||
// Should have ensured some difference in position
|
|
||||||
expect(styleB).not.toEqual(styleC);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("allows panels to be dragged", function () {
|
|
||||||
// Populate scope
|
|
||||||
mockScope.$watchCollection.calls.mostRecent().args[1]();
|
|
||||||
|
|
||||||
// Verify precondition
|
|
||||||
expect(testConfiguration.panels.b).not.toBeDefined();
|
|
||||||
|
|
||||||
// Do a drag
|
|
||||||
controller.startDrag("b", [1, 1], [0, 0]);
|
|
||||||
controller.continueDrag([100, 100]);
|
|
||||||
controller.endDrag();
|
|
||||||
|
|
||||||
// We do not look closely at the details here;
|
|
||||||
// that is tested in LayoutDragSpec. Just make sure
|
|
||||||
// that a configuration for b has been defined.
|
|
||||||
expect(testConfiguration.panels.b).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it("invokes commit after drag", function () {
|
|
||||||
// Populate scope
|
|
||||||
mockScope.$watchCollection.calls.mostRecent().args[1]();
|
|
||||||
|
|
||||||
// Do a drag
|
|
||||||
controller.startDrag("b", [1, 1], [0, 0]);
|
|
||||||
controller.continueDrag([100, 100]);
|
|
||||||
controller.endDrag();
|
|
||||||
|
|
||||||
expect(controller.commit).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("listens for drop events", function () {
|
|
||||||
// Layout should position panels according to
|
|
||||||
// where the user dropped them, so it needs to
|
|
||||||
// listen for drop events.
|
|
||||||
expect(mockScope.$on).toHaveBeenCalledWith(
|
|
||||||
'mctDrop',
|
|
||||||
jasmine.any(Function)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Verify precondition
|
|
||||||
expect(testConfiguration.panels.d).not.toBeDefined();
|
|
||||||
|
|
||||||
// Notify that a drop occurred
|
|
||||||
mockScope.$on.calls.mostRecent().args[1](
|
|
||||||
mockEvent,
|
|
||||||
'd',
|
|
||||||
{ x: 300, y: 100 }
|
|
||||||
);
|
|
||||||
expect(testConfiguration.panels.d).toBeDefined();
|
|
||||||
expect(mockEvent.preventDefault).toHaveBeenCalled();
|
|
||||||
expect(controller.commit).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("ignores drops when default has been prevented", function () {
|
|
||||||
// Avoids redundant drop-handling, WTD-1233
|
|
||||||
mockEvent.defaultPrevented = true;
|
|
||||||
|
|
||||||
// Notify that a drop occurred
|
|
||||||
mockScope.$on.calls.mostRecent().args[1](
|
|
||||||
mockEvent,
|
|
||||||
'd',
|
|
||||||
{ x: 300, y: 100 }
|
|
||||||
);
|
|
||||||
expect(testConfiguration.panels.d).not.toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("ensures a minimum frame size", function () {
|
|
||||||
var styleB;
|
|
||||||
|
|
||||||
// Start with a very small frame size
|
|
||||||
testModel.layoutGrid = [1, 1];
|
|
||||||
|
|
||||||
// White-boxy; we know which watch is which
|
|
||||||
mockScope.$watch.calls.all()[0].args[1](testModel.layoutGrid);
|
|
||||||
mockScope.$watchCollection.calls.all()[0].args[1](testModel.composition);
|
|
||||||
|
|
||||||
styleB = controller.getFrameStyle("b");
|
|
||||||
|
|
||||||
// Resulting size should still be reasonably large pixel-size
|
|
||||||
expect(parseInt(styleB.width, 10)).toBeGreaterThan(63);
|
|
||||||
expect(parseInt(styleB.width, 10)).toBeGreaterThan(31);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("ensures a minimum frame size on drop", function () {
|
|
||||||
var style;
|
|
||||||
|
|
||||||
// Start with a very small frame size
|
|
||||||
testModel.layoutGrid = [1, 1];
|
|
||||||
mockScope.$watch.calls.all()[0].args[1](testModel.layoutGrid);
|
|
||||||
|
|
||||||
// Add a new object to the composition
|
|
||||||
mockComposition = ["a", "b", "c", "d"];
|
|
||||||
mockCompositionObjects = mockComposition.map(mockDomainObject);
|
|
||||||
mockCompositionCapability = mockPromise(mockCompositionObjects);
|
|
||||||
|
|
||||||
// Notify that a drop occurred
|
|
||||||
mockScope.$on.calls.mostRecent().args[1](
|
|
||||||
mockEvent,
|
|
||||||
'd',
|
|
||||||
{ x: 300, y: 100 }
|
|
||||||
);
|
|
||||||
|
|
||||||
style = controller.getFrameStyle("d");
|
|
||||||
|
|
||||||
// Resulting size should still be reasonably large pixel-size
|
|
||||||
expect(parseInt(style.width, 10)).toBeGreaterThan(63);
|
|
||||||
expect(parseInt(style.height, 10)).toBeGreaterThan(31);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("updates positions of existing objects on a drop", function () {
|
|
||||||
var oldStyle;
|
|
||||||
|
|
||||||
mockScope.$watchCollection.calls.mostRecent().args[1]();
|
|
||||||
|
|
||||||
oldStyle = controller.getFrameStyle("b");
|
|
||||||
|
|
||||||
expect(oldStyle).toBeDefined();
|
|
||||||
|
|
||||||
// ...drop event...
|
|
||||||
mockScope.$on.calls.mostRecent()
|
|
||||||
.args[1](mockEvent, 'b', { x: 300, y: 100 });
|
|
||||||
|
|
||||||
expect(controller.getFrameStyle("b"))
|
|
||||||
.not.toEqual(oldStyle);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("allows objects to be selected", function () {
|
|
||||||
mockScope.$watchCollection.calls.mostRecent().args[1]();
|
|
||||||
var childObj = mockCompositionObjects[0];
|
|
||||||
selectable[0].context.oldItem = childObj;
|
|
||||||
mockOpenMCT.selection.on.calls.mostRecent().args[1](selectable);
|
|
||||||
|
|
||||||
expect(controller.selected(childObj)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("prevents event bubbling while drag is in progress", function () {
|
|
||||||
mockScope.$watchCollection.calls.mostRecent().args[1]();
|
|
||||||
var childObj = mockCompositionObjects[0];
|
|
||||||
|
|
||||||
// Do a drag
|
|
||||||
controller.startDrag(childObj.getId(), [1, 1], [0, 0]);
|
|
||||||
controller.continueDrag([100, 100]);
|
|
||||||
controller.endDrag();
|
|
||||||
|
|
||||||
// Because mouse position could cause the parent object to be selected, this should be ignored.
|
|
||||||
controller.bypassSelection(mockEvent);
|
|
||||||
|
|
||||||
expect(mockEvent.stopPropagation).toHaveBeenCalled();
|
|
||||||
|
|
||||||
// Shoud be able to select another object when dragging is done.
|
|
||||||
jasmine.clock().tick(0);
|
|
||||||
mockEvent.stopPropagation.calls.reset();
|
|
||||||
controller.bypassSelection(mockEvent);
|
|
||||||
|
|
||||||
expect(mockEvent.stopPropagation).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("shows frames by default", function () {
|
|
||||||
mockScope.$watchCollection.calls.mostRecent().args[1]();
|
|
||||||
|
|
||||||
expect(controller.hasFrame(mockCompositionObjects[0])).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("hyperlinks hide frame by default", function () {
|
|
||||||
mockScope.$watchCollection.calls.mostRecent().args[1]();
|
|
||||||
|
|
||||||
expect(controller.hasFrame(mockCompositionObjects[1])).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("selects the parent object when selected object is removed", function () {
|
|
||||||
mockScope.$watchCollection.calls.mostRecent().args[1]();
|
|
||||||
var childObj = mockCompositionObjects[0];
|
|
||||||
selectable[0].context.oldItem = childObj;
|
|
||||||
mockOpenMCT.selection.on.calls.mostRecent().args[1](selectable);
|
|
||||||
|
|
||||||
var composition = ["b", "c"];
|
|
||||||
mockScope.$watchCollection.calls.mostRecent().args[1](composition);
|
|
||||||
|
|
||||||
expect($element[0].click).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("allows objects to be drilled-in only when editing", function () {
|
|
||||||
mockScope.$watchCollection.calls.mostRecent().args[1]();
|
|
||||||
var childObj = mockCompositionObjects[0];
|
|
||||||
childObj.getCapability().inEditContext.and.returnValue(false);
|
|
||||||
controller.drill(mockEvent, childObj);
|
|
||||||
|
|
||||||
expect(controller.isDrilledIn(childObj)).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("allows objects to be drilled-in only if it has sub objects", function () {
|
|
||||||
mockScope.$watchCollection.calls.mostRecent().args[1]();
|
|
||||||
var childObj = mockCompositionObjects[1];
|
|
||||||
childObj.getCapability().inEditContext.and.returnValue(true);
|
|
||||||
controller.drill(mockEvent, childObj);
|
|
||||||
|
|
||||||
expect(controller.isDrilledIn(childObj)).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("selects a newly-dropped object", function () {
|
|
||||||
mockScope.$on.calls.mostRecent().args[1](
|
|
||||||
mockEvent,
|
|
||||||
'd',
|
|
||||||
{ x: 300, y: 100 }
|
|
||||||
);
|
|
||||||
|
|
||||||
var childObj = mockDomainObject("d");
|
|
||||||
var testElement = $("<div data-layout-id='some-id'></div>");
|
|
||||||
$element.append(testElement);
|
|
||||||
spyOn(testElement[0], 'click');
|
|
||||||
|
|
||||||
controller.selectIfNew('some-id', childObj);
|
|
||||||
jasmine.clock().tick(0);
|
|
||||||
|
|
||||||
expect(testElement[0].click).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,64 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define([
|
|
||||||
'./src/controllers/ListViewController',
|
|
||||||
'./src/directives/MCTGesture',
|
|
||||||
'./res/templates/listview.html',
|
|
||||||
'legacyRegistry'
|
|
||||||
], function (
|
|
||||||
ListViewController,
|
|
||||||
MCTGesture,
|
|
||||||
listViewTemplate,
|
|
||||||
legacyRegistry
|
|
||||||
) {
|
|
||||||
legacyRegistry.register("platform/features/listview", {
|
|
||||||
"name": "List View Plugin",
|
|
||||||
"description": "Allows folder contents to be shown in list format",
|
|
||||||
"extensions":
|
|
||||||
{
|
|
||||||
"views": [
|
|
||||||
{
|
|
||||||
"key": "list",
|
|
||||||
"type": "folder",
|
|
||||||
"name": "List",
|
|
||||||
"cssClass": "icon-list-view",
|
|
||||||
"template": listViewTemplate
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"controllers": [
|
|
||||||
{
|
|
||||||
"key": "ListViewController",
|
|
||||||
"implementation": ListViewController,
|
|
||||||
"depends": ["$scope", "formatService"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"directives": [
|
|
||||||
{
|
|
||||||
"key": "mctGesture",
|
|
||||||
"implementation" : MCTGesture,
|
|
||||||
"depends": ["gestureService"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,88 +0,0 @@
|
|||||||
<!--
|
|
||||||
Open MCT, Copyright (c) 2014-2018, United States Government
|
|
||||||
as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
Administration. All rights reserved.
|
|
||||||
|
|
||||||
Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
"License"); you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
License for the specific language governing permissions and limitations
|
|
||||||
under the License.
|
|
||||||
|
|
||||||
Open MCT includes source code licensed under additional open source
|
|
||||||
licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
this source code distribution or the Licensing information page available
|
|
||||||
at runtime from the About dialog for additional information.
|
|
||||||
-->
|
|
||||||
<div ng-controller="ListViewController">
|
|
||||||
<table class="list-view">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th class="sortable"
|
|
||||||
ng-click="reverseSort = (orderByField === 'title')? !reverseSort:false; orderByField='title';"
|
|
||||||
ng-class="{
|
|
||||||
sort: orderByField == 'title',
|
|
||||||
asc: !reverseSort,
|
|
||||||
desc: reverseSort
|
|
||||||
}">
|
|
||||||
Name
|
|
||||||
</th>
|
|
||||||
|
|
||||||
|
|
||||||
<th class="sortable"
|
|
||||||
ng-click="reverseSort = (orderByField === 'type')? !reverseSort:false; orderByField='type';"
|
|
||||||
ng-class="{
|
|
||||||
sort: orderByField == 'type',
|
|
||||||
asc: !reverseSort,
|
|
||||||
desc: reverseSort
|
|
||||||
}">
|
|
||||||
Type
|
|
||||||
</th>
|
|
||||||
|
|
||||||
<th class="sortable"
|
|
||||||
ng-click="reverseSort = (orderByField === 'persisted')? !reverseSort:true; orderByField='persisted';"
|
|
||||||
ng-class="{
|
|
||||||
sort: orderByField == 'persisted',
|
|
||||||
asc: !reverseSort,
|
|
||||||
desc: reverseSort
|
|
||||||
}">
|
|
||||||
Created Date
|
|
||||||
</th>
|
|
||||||
|
|
||||||
<th class="sortable"
|
|
||||||
ng-click="reverseSort = (orderByField === 'modified')? !reverseSort:true; orderByField='modified';"
|
|
||||||
ng-class="{
|
|
||||||
sort: orderByField == 'modified',
|
|
||||||
asc: !reverseSort,
|
|
||||||
desc: reverseSort
|
|
||||||
}">
|
|
||||||
Update Date
|
|
||||||
</th>
|
|
||||||
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr ng-repeat="child in children|orderBy:orderByField:reverseSort"
|
|
||||||
mct-gesture="['menu','info']"
|
|
||||||
mct-object="child.asDomainObject"
|
|
||||||
ng-click="child.action.perform('navigate')">
|
|
||||||
<td>
|
|
||||||
<div class="l-flex-row">
|
|
||||||
<span class="flex-elem t-item-icon" ng-class="{ 'l-icon-link': child.location.isLink()}">
|
|
||||||
<span class="t-item-icon-glyph {{child.icon}}"></span>
|
|
||||||
</span>
|
|
||||||
<span class="t-title-label flex-elem grows">{{child.title}}</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>{{child.type}}</td>
|
|
||||||
<td>{{child.persisted}}</td>
|
|
||||||
<td>{{child.modified}}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
@ -1,70 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(function () {
|
|
||||||
function ListViewController($scope, formatService) {
|
|
||||||
this.$scope = $scope;
|
|
||||||
$scope.orderByField = 'title';
|
|
||||||
$scope.reverseSort = false;
|
|
||||||
|
|
||||||
this.updateView();
|
|
||||||
var unlisten = $scope.domainObject.getCapability('mutation')
|
|
||||||
.listen(this.updateView.bind(this));
|
|
||||||
|
|
||||||
this.utc = formatService.getFormat('utc');
|
|
||||||
|
|
||||||
//Trigger digestive cycle with $apply to update list view
|
|
||||||
setTimeout(function () {
|
|
||||||
$scope.$apply();
|
|
||||||
});
|
|
||||||
|
|
||||||
$scope.$on('$destroy', function () {
|
|
||||||
unlisten();
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
ListViewController.prototype.updateView = function () {
|
|
||||||
this.$scope.domainObject.useCapability('composition')
|
|
||||||
.then(function (children) {
|
|
||||||
var formattedChildren = this.formatChildren(children);
|
|
||||||
this.$scope.children = formattedChildren;
|
|
||||||
this.$scope.data = {children: formattedChildren};
|
|
||||||
}.bind(this)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
ListViewController.prototype.formatChildren = function (children) {
|
|
||||||
return children.map(function (child) {
|
|
||||||
return {
|
|
||||||
icon: child.getCapability('type').getCssClass(),
|
|
||||||
title: child.getModel().name,
|
|
||||||
type: child.getCapability('type').getName(),
|
|
||||||
persisted: this.utc.format(child.getModel().persisted),
|
|
||||||
modified: this.utc.format(child.getModel().modified),
|
|
||||||
asDomainObject: child,
|
|
||||||
location: child.getCapability('location'),
|
|
||||||
action: child.getCapability('action')
|
|
||||||
};
|
|
||||||
}, this);
|
|
||||||
};
|
|
||||||
|
|
||||||
return ListViewController;
|
|
||||||
});
|
|
@ -1,157 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
["../../src/controllers/ListViewController"],
|
|
||||||
function (ListViewController) {
|
|
||||||
describe("The Controller for the ListView", function () {
|
|
||||||
var scope,
|
|
||||||
unlistenFunc,
|
|
||||||
domainObject,
|
|
||||||
childObject,
|
|
||||||
childModel,
|
|
||||||
typeCapability,
|
|
||||||
mutationCapability,
|
|
||||||
formatService,
|
|
||||||
compositionPromise,
|
|
||||||
controller;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
unlistenFunc = jasmine.createSpy("unlisten");
|
|
||||||
|
|
||||||
mutationCapability = jasmine.createSpyObj(
|
|
||||||
"mutationCapability",
|
|
||||||
["listen"]
|
|
||||||
);
|
|
||||||
mutationCapability.listen.and.returnValue(unlistenFunc);
|
|
||||||
|
|
||||||
formatService = jasmine.createSpyObj(
|
|
||||||
"formatService",
|
|
||||||
["getFormat"]
|
|
||||||
);
|
|
||||||
formatService.getFormat.and.returnValue(jasmine.createSpyObj(
|
|
||||||
'utc',
|
|
||||||
["format"]
|
|
||||||
));
|
|
||||||
formatService.getFormat().format.and.callFake(function (v) {
|
|
||||||
return "formatted " + v;
|
|
||||||
});
|
|
||||||
|
|
||||||
typeCapability = jasmine.createSpyObj(
|
|
||||||
"typeCapability",
|
|
||||||
["getCssClass", "getName"]
|
|
||||||
);
|
|
||||||
typeCapability.getCssClass.and.returnValue("icon-folder");
|
|
||||||
typeCapability.getName.and.returnValue("Folder");
|
|
||||||
|
|
||||||
|
|
||||||
childModel = jasmine.createSpyObj(
|
|
||||||
"childModel",
|
|
||||||
["persisted", "modified", "name"]
|
|
||||||
);
|
|
||||||
childModel.persisted = 1496867697303;
|
|
||||||
childModel.modified = 1496867697303;
|
|
||||||
childModel.name = "Battery Charge Status";
|
|
||||||
|
|
||||||
childObject = jasmine.createSpyObj(
|
|
||||||
"childObject",
|
|
||||||
["getModel", "getCapability"]
|
|
||||||
);
|
|
||||||
childObject.getModel.and.returnValue(
|
|
||||||
childModel
|
|
||||||
);
|
|
||||||
|
|
||||||
childObject.getCapability.and.callFake(function (arg) {
|
|
||||||
if (arg === 'location') {
|
|
||||||
return '';
|
|
||||||
} else if (arg === 'type') {
|
|
||||||
return typeCapability;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
childObject.location = '';
|
|
||||||
|
|
||||||
domainObject = jasmine.createSpyObj(
|
|
||||||
"domainObject",
|
|
||||||
["getCapability", "useCapability"]
|
|
||||||
);
|
|
||||||
compositionPromise = Promise.resolve([childObject]);
|
|
||||||
domainObject.useCapability.and.returnValue(compositionPromise);
|
|
||||||
domainObject.getCapability.and.returnValue(
|
|
||||||
mutationCapability
|
|
||||||
);
|
|
||||||
|
|
||||||
scope = jasmine.createSpyObj(
|
|
||||||
"$scope",
|
|
||||||
["$on", "$apply"]
|
|
||||||
);
|
|
||||||
scope.domainObject = domainObject;
|
|
||||||
|
|
||||||
controller = new ListViewController(scope, formatService);
|
|
||||||
|
|
||||||
return compositionPromise;
|
|
||||||
});
|
|
||||||
|
|
||||||
it("uses the UTC time format", function () {
|
|
||||||
expect(formatService.getFormat).toHaveBeenCalledWith('utc');
|
|
||||||
});
|
|
||||||
|
|
||||||
it("updates the view", function () {
|
|
||||||
var child = scope.children[0];
|
|
||||||
var testChild = {
|
|
||||||
icon: "icon-folder",
|
|
||||||
title: "Battery Charge Status",
|
|
||||||
type: "Folder",
|
|
||||||
persisted: formatService.getFormat('utc')
|
|
||||||
.format(childModel.persisted),
|
|
||||||
modified: formatService.getFormat('utc')
|
|
||||||
.format(childModel.modified),
|
|
||||||
asDomainObject: childObject,
|
|
||||||
location: '',
|
|
||||||
action: childObject.getCapability('action')
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(child).toEqual(testChild);
|
|
||||||
});
|
|
||||||
it("updates the scope when mutation occurs", function () {
|
|
||||||
var applyPromise = new Promise(function (resolve) {
|
|
||||||
scope.$apply.and.callFake(resolve);
|
|
||||||
});
|
|
||||||
|
|
||||||
domainObject.useCapability.and.returnValue(Promise.resolve([]));
|
|
||||||
expect(mutationCapability.listen).toHaveBeenCalledWith(jasmine.any(Function));
|
|
||||||
mutationCapability.listen.calls.mostRecent().args[0]();
|
|
||||||
|
|
||||||
return applyPromise.then(function () {
|
|
||||||
expect(scope.children.length).toEqual(0);
|
|
||||||
expect(scope.$apply).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it("releases listeners on $destroy", function () {
|
|
||||||
expect(scope.$on).toHaveBeenCalledWith('$destroy', jasmine.any(Function));
|
|
||||||
scope.$on.calls.mostRecent().args[1]();
|
|
||||||
expect(unlistenFunc).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,86 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
["../../src/directives/MCTGesture"],
|
|
||||||
function (MCTGesture) {
|
|
||||||
describe("The Gesture Listener for the ListView items", function () {
|
|
||||||
var mctGesture,
|
|
||||||
gestureService,
|
|
||||||
scope,
|
|
||||||
element,
|
|
||||||
attrs,
|
|
||||||
attachedGesture;
|
|
||||||
beforeEach(function () {
|
|
||||||
attachedGesture = jasmine.createSpyObj(
|
|
||||||
"attachedGesture",
|
|
||||||
['destroy']
|
|
||||||
);
|
|
||||||
gestureService = jasmine.createSpyObj(
|
|
||||||
"gestureService",
|
|
||||||
["attachGestures"]
|
|
||||||
);
|
|
||||||
gestureService.attachGestures.and.returnValue(
|
|
||||||
attachedGesture
|
|
||||||
);
|
|
||||||
mctGesture = MCTGesture(gestureService);
|
|
||||||
});
|
|
||||||
it("creates a directive Object", function () {
|
|
||||||
expect(mctGesture).toBeDefined();
|
|
||||||
});
|
|
||||||
it("has link function that attaches gesture to gestureService",
|
|
||||||
function () {
|
|
||||||
attrs = {
|
|
||||||
mctGesture: "menu,info"
|
|
||||||
};
|
|
||||||
element = jasmine.createSpy("element");
|
|
||||||
scope = jasmine.createSpyObj(
|
|
||||||
"$scope",
|
|
||||||
["$on", "$eval"]
|
|
||||||
);
|
|
||||||
scope.domainObject = "fake domainObject";
|
|
||||||
mctGesture.link(scope, element, attrs);
|
|
||||||
expect(gestureService.attachGestures).toHaveBeenCalled();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
it("release gesture service on $destroy", function () {
|
|
||||||
attrs = {
|
|
||||||
mctGesture: "menu,info"
|
|
||||||
};
|
|
||||||
element = jasmine.createSpy("element");
|
|
||||||
scope = jasmine.createSpyObj(
|
|
||||||
"$scope",
|
|
||||||
["$on", "$eval"]
|
|
||||||
);
|
|
||||||
scope.domainObject = "fake domainObject";
|
|
||||||
mctGesture.link(scope, element, attrs);
|
|
||||||
expect(scope.$on).toHaveBeenCalledWith(
|
|
||||||
'$destroy',
|
|
||||||
jasmine.any(Function)
|
|
||||||
);
|
|
||||||
scope.$on.calls.mostRecent().args[1]();
|
|
||||||
expect(attachedGesture.destroy).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
17
src/MCT.js
17
src/MCT.js
@ -40,6 +40,7 @@ define([
|
|||||||
'./styles-new/core.scss',
|
'./styles-new/core.scss',
|
||||||
'./styles-new/notebook.scss',
|
'./styles-new/notebook.scss',
|
||||||
'./ui/components/layout/Layout.vue',
|
'./ui/components/layout/Layout.vue',
|
||||||
|
'./ui/overlayService/overlayService',
|
||||||
'vue'
|
'vue'
|
||||||
], function (
|
], function (
|
||||||
EventEmitter,
|
EventEmitter,
|
||||||
@ -61,6 +62,7 @@ define([
|
|||||||
coreStyles,
|
coreStyles,
|
||||||
NotebookStyles,
|
NotebookStyles,
|
||||||
Layout,
|
Layout,
|
||||||
|
OverlayService,
|
||||||
Vue
|
Vue
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
@ -221,8 +223,14 @@ define([
|
|||||||
*/
|
*/
|
||||||
this.indicators = new api.IndicatorAPI(this);
|
this.indicators = new api.IndicatorAPI(this);
|
||||||
|
|
||||||
|
this.notifications = new api.NotificationAPI();
|
||||||
|
|
||||||
this.Dialog = api.Dialog;
|
this.Dialog = api.Dialog;
|
||||||
|
|
||||||
|
this.editor = new api.EditorAPI.default(this);
|
||||||
|
|
||||||
|
this.OverlayService = new OverlayService();
|
||||||
|
|
||||||
this.legacyRegistry = defaultRegistry;
|
this.legacyRegistry = defaultRegistry;
|
||||||
this.install(this.plugins.Plot());
|
this.install(this.plugins.Plot());
|
||||||
this.install(this.plugins.TelemetryTable());
|
this.install(this.plugins.TelemetryTable());
|
||||||
@ -311,14 +319,17 @@ define([
|
|||||||
this.$injector.get('objectService');
|
this.$injector.get('objectService');
|
||||||
|
|
||||||
var appLayout = new Vue({
|
var appLayout = new Vue({
|
||||||
mixins: [Layout.default],
|
components: {
|
||||||
|
'Layout': Layout.default
|
||||||
|
},
|
||||||
provide: {
|
provide: {
|
||||||
openmct: this
|
openmct: this
|
||||||
}
|
},
|
||||||
|
template: '<Layout ref="layout"></Layout>'
|
||||||
});
|
});
|
||||||
domElement.appendChild(appLayout.$mount().$el);
|
domElement.appendChild(appLayout.$mount().$el);
|
||||||
|
|
||||||
this.layout = appLayout;
|
this.layout = appLayout.$refs.layout;
|
||||||
Browse(this);
|
Browse(this);
|
||||||
this.router.start();
|
this.router.start();
|
||||||
this.emit('start');
|
this.emit('start');
|
||||||
|
@ -3,6 +3,16 @@ define([
|
|||||||
], function (
|
], function (
|
||||||
|
|
||||||
) {
|
) {
|
||||||
|
const DEFAULT_VIEW_PRIORITY = 100;
|
||||||
|
|
||||||
|
const PRIORITY_LEVELS = {
|
||||||
|
"fallback": Number.NEGATIVE_INFINITY,
|
||||||
|
"default": -100,
|
||||||
|
"none": 0,
|
||||||
|
"optional": DEFAULT_VIEW_PRIORITY,
|
||||||
|
"preferred": 1000,
|
||||||
|
"mandatory": Number.POSITIVE_INFINITY
|
||||||
|
};
|
||||||
|
|
||||||
function LegacyViewProvider(legacyView, openmct, convertToLegacyObject) {
|
function LegacyViewProvider(legacyView, openmct, convertToLegacyObject) {
|
||||||
console.warn(`DEPRECATION WARNING: Migrate ${legacyView.key} from ${legacyView.bundle.path} to use the new View APIs. Legacy view support will be removed soon.`);
|
console.warn(`DEPRECATION WARNING: Migrate ${legacyView.key} from ${legacyView.bundle.path} to use the new View APIs. Legacy view support will be removed soon.`);
|
||||||
@ -84,6 +94,13 @@ define([
|
|||||||
scope.$destroy();
|
scope.$destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
priority: function () {
|
||||||
|
let priority = legacyView.priority || DEFAULT_VIEW_PRIORITY;
|
||||||
|
if (typeof priority === 'string') {
|
||||||
|
priority = PRIORITY_LEVELS[priority];
|
||||||
|
}
|
||||||
|
return priority;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
95
src/adapter/views/TypeInspectorViewProvider.js
Normal file
95
src/adapter/views/TypeInspectorViewProvider.js
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
define([
|
||||||
|
|
||||||
|
], function (
|
||||||
|
|
||||||
|
) {
|
||||||
|
const DEFAULT_VIEW_PRIORITY = 100;
|
||||||
|
|
||||||
|
const PRIORITY_LEVELS = {
|
||||||
|
"fallback": Number.NEGATIVE_INFINITY,
|
||||||
|
"default": -100,
|
||||||
|
"none": 0,
|
||||||
|
"optional": DEFAULT_VIEW_PRIORITY,
|
||||||
|
"preferred": 1000,
|
||||||
|
"mandatory": Number.POSITIVE_INFINITY
|
||||||
|
};
|
||||||
|
|
||||||
|
function TypeInspectorViewProvider(typeDefinition, openmct, convertToLegacyObject) {
|
||||||
|
console.warn(`DEPRECATION WARNING: Migrate ${typeDefinition.key} from ${typeDefinition.bundle.path} to use the new Inspector View APIs. Legacy Inspector view support will be removed soon.`);
|
||||||
|
let representation = openmct.$injector.get('representations[]')
|
||||||
|
.filter((r) => r.key === typeDefinition.inspector)[0];
|
||||||
|
|
||||||
|
return {
|
||||||
|
key: representation.key,
|
||||||
|
name: representation.name,
|
||||||
|
cssClass: representation.cssClass,
|
||||||
|
description: representation.description,
|
||||||
|
canView: function (selection) {
|
||||||
|
if (!selection[0] || !selection[0].context.item) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let domainObject = selection[0].context.item;
|
||||||
|
return domainObject.type === typeDefinition.key;
|
||||||
|
},
|
||||||
|
view: function (selection) {
|
||||||
|
let domainObject = selection[0].context.item;
|
||||||
|
let $rootScope = openmct.$injector.get('$rootScope');
|
||||||
|
let templateLinker = openmct.$injector.get('templateLinker');
|
||||||
|
let scope = $rootScope.$new();
|
||||||
|
let legacyObject = convertToLegacyObject(domainObject);
|
||||||
|
let isDestroyed = false;
|
||||||
|
scope.domainObject = legacyObject;
|
||||||
|
scope.model = legacyObject.getModel();
|
||||||
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
show: function (container) {
|
||||||
|
// TODO: implement "gestures" support ?
|
||||||
|
let uses = representation.uses || [];
|
||||||
|
let promises = [];
|
||||||
|
let results = uses.map(function (capabilityKey, i) {
|
||||||
|
let result = legacyObject.useCapability(capabilityKey);
|
||||||
|
if (result.then) {
|
||||||
|
promises.push(result.then(function (r) {
|
||||||
|
results[i] = r;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
|
function link() {
|
||||||
|
if (isDestroyed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uses.forEach(function (key, i) {
|
||||||
|
scope[key] = results[i];
|
||||||
|
});
|
||||||
|
templateLinker.link(
|
||||||
|
scope,
|
||||||
|
openmct.$angular.element(container),
|
||||||
|
representation
|
||||||
|
);
|
||||||
|
container.style.height = '100%';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (promises.length) {
|
||||||
|
Promise.all(promises)
|
||||||
|
.then(function () {
|
||||||
|
link();
|
||||||
|
scope.$digest();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
link();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
destroy: function () {
|
||||||
|
scope.$destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return TypeInspectorViewProvider;
|
||||||
|
|
||||||
|
});
|
@ -1,8 +1,10 @@
|
|||||||
define([
|
define([
|
||||||
'./LegacyViewProvider',
|
'./LegacyViewProvider',
|
||||||
|
'./TypeInspectorViewProvider',
|
||||||
'../../api/objects/object-utils'
|
'../../api/objects/object-utils'
|
||||||
], function (
|
], function (
|
||||||
LegacyViewProvider,
|
LegacyViewProvider,
|
||||||
|
TypeInspectorViewProvider,
|
||||||
objectUtils
|
objectUtils
|
||||||
) {
|
) {
|
||||||
function installLegacyViews(openmct, legacyViews, instantiate) {
|
function installLegacyViews(openmct, legacyViews, instantiate) {
|
||||||
@ -16,6 +18,13 @@ define([
|
|||||||
legacyViews.forEach(function (legacyView) {
|
legacyViews.forEach(function (legacyView) {
|
||||||
openmct.objectViews.addProvider(new LegacyViewProvider(legacyView, openmct, convertToLegacyObject));
|
openmct.objectViews.addProvider(new LegacyViewProvider(legacyView, openmct, convertToLegacyObject));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let inspectorTypes = openmct.$injector.get('types[]')
|
||||||
|
.filter((t) => t.hasOwnProperty('inspector'));
|
||||||
|
|
||||||
|
inspectorTypes.forEach(function (typeDefinition) {
|
||||||
|
openmct.inspectorViews.addProvider(new TypeInspectorViewProvider(typeDefinition, openmct, convertToLegacyObject));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return installLegacyViews;
|
return installLegacyViews;
|
||||||
|
83
src/api/Editor.js
Normal file
83
src/api/Editor.js
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
|
||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
import EventEmitter from 'EventEmitter';
|
||||||
|
|
||||||
|
export default class Editor extends EventEmitter {
|
||||||
|
constructor(openmct) {
|
||||||
|
super();
|
||||||
|
this.editing = false;
|
||||||
|
this.openmct = openmct;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initiate an editing session. This will start a transaction during
|
||||||
|
* which any persist operations will be deferred until either save()
|
||||||
|
* or finish() are called.
|
||||||
|
*/
|
||||||
|
edit() {
|
||||||
|
this.editing = true;
|
||||||
|
this.getTransactionService().startTransaction();
|
||||||
|
this.emit('isEditing', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns true if the application is in edit mode, false otherwise.
|
||||||
|
*/
|
||||||
|
isEditing() {
|
||||||
|
return this.editing;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save any unsaved changes from this editing session. This will
|
||||||
|
* end the current transaction.
|
||||||
|
*/
|
||||||
|
save() {
|
||||||
|
return this.getTransactionService().commit().then((result)=>{
|
||||||
|
this.editing = false;
|
||||||
|
this.emit('isEditing', false);
|
||||||
|
return result
|
||||||
|
}).catch((error)=>{
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* End the currently active transaction and discard unsaved changes.
|
||||||
|
*/
|
||||||
|
cancel() {
|
||||||
|
this.getTransactionService().cancel();
|
||||||
|
this.editing = false;
|
||||||
|
this.emit('isEditing', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
getTransactionService() {
|
||||||
|
if (!this.transactionService) {
|
||||||
|
this.transactionService = this.openmct.$injector.get('transactionService');
|
||||||
|
}
|
||||||
|
return this.transactionService;
|
||||||
|
}
|
||||||
|
}
|
@ -28,7 +28,10 @@ define([
|
|||||||
'./ui/Dialog',
|
'./ui/Dialog',
|
||||||
'./ui/GestureAPI',
|
'./ui/GestureAPI',
|
||||||
'./telemetry/TelemetryAPI',
|
'./telemetry/TelemetryAPI',
|
||||||
'./indicators/IndicatorAPI'
|
'./indicators/IndicatorAPI',
|
||||||
|
'./notifications/NotificationAPI',
|
||||||
|
'./Editor'
|
||||||
|
|
||||||
], function (
|
], function (
|
||||||
TimeAPI,
|
TimeAPI,
|
||||||
ObjectAPI,
|
ObjectAPI,
|
||||||
@ -37,7 +40,9 @@ define([
|
|||||||
Dialog,
|
Dialog,
|
||||||
GestureAPI,
|
GestureAPI,
|
||||||
TelemetryAPI,
|
TelemetryAPI,
|
||||||
IndicatorAPI
|
IndicatorAPI,
|
||||||
|
NotificationAPI,
|
||||||
|
EditorAPI
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
TimeAPI: TimeAPI,
|
TimeAPI: TimeAPI,
|
||||||
@ -47,6 +52,8 @@ define([
|
|||||||
TypeRegistry: TypeRegistry,
|
TypeRegistry: TypeRegistry,
|
||||||
GestureAPI: GestureAPI,
|
GestureAPI: GestureAPI,
|
||||||
TelemetryAPI: TelemetryAPI,
|
TelemetryAPI: TelemetryAPI,
|
||||||
IndicatorAPI: IndicatorAPI
|
IndicatorAPI: IndicatorAPI,
|
||||||
|
NotificationAPI: NotificationAPI.default,
|
||||||
|
EditorAPI: EditorAPI
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -28,7 +28,7 @@ define([
|
|||||||
) {
|
) {
|
||||||
function IndicatorAPI(openmct) {
|
function IndicatorAPI(openmct) {
|
||||||
this.openmct = openmct;
|
this.openmct = openmct;
|
||||||
this.indicatorElements = [];
|
this.indicatorObjects = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
IndicatorAPI.prototype.simpleIndicator = function () {
|
IndicatorAPI.prototype.simpleIndicator = function () {
|
||||||
@ -55,12 +55,7 @@ define([
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
IndicatorAPI.prototype.add = function (indicator) {
|
IndicatorAPI.prototype.add = function (indicator) {
|
||||||
// So that we can consistently position indicator elements,
|
this.indicatorObjects.push(indicator);
|
||||||
// guarantee that they are wrapped in an element we control
|
|
||||||
var wrapperNode = document.createElement('div');
|
|
||||||
wrapperNode.className = 'h-indicator';
|
|
||||||
wrapperNode.appendChild(indicator.element);
|
|
||||||
this.indicatorElements.push(wrapperNode);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return IndicatorAPI;
|
return IndicatorAPI;
|
||||||
|
@ -20,34 +20,29 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
define([], function () {
|
import EventEmitter from 'EventEmitter';
|
||||||
|
export default class MCTNotification extends EventEmitter {
|
||||||
|
|
||||||
/**
|
constructor(notificationModel, notificationAPI) {
|
||||||
* Formatter for basic strings.
|
super();
|
||||||
*
|
this.notifications = notificationAPI;
|
||||||
* @implements {Format}
|
this.model = notificationModel;
|
||||||
* @constructor
|
this.initializeModel();
|
||||||
* @memberof platform/commonUI/formats
|
|
||||||
*/
|
|
||||||
function StringFormat() {
|
|
||||||
this.key = 'string';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
StringFormat.prototype.format = function (string) {
|
minimize() {
|
||||||
if (typeof string === 'string') {
|
this.notifications.minimize(this);
|
||||||
return string;
|
|
||||||
} else {
|
|
||||||
return '' + string;
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
StringFormat.prototype.parse = function (string) {
|
dismiss() {
|
||||||
return string;
|
this.notifications.dismiss(this)
|
||||||
};
|
}
|
||||||
|
|
||||||
StringFormat.prototype.validate = function (string) {
|
dismissOrMinimize() {
|
||||||
return typeof string === 'string';
|
this.notifications.dismissOrMinimize(this);
|
||||||
};
|
}
|
||||||
|
|
||||||
return StringFormat;
|
initializeModel() {
|
||||||
});
|
this.model.minimized = this.model.minimized || false;
|
||||||
|
}
|
||||||
|
}
|
353
src/api/notifications/NotificationApi.js
Normal file
353
src/api/notifications/NotificationApi.js
Normal file
@ -0,0 +1,353 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This bundle implements the notification service, which can be used to
|
||||||
|
* show banner notifications to the user. Banner notifications
|
||||||
|
* are used to inform users of events in a non-intrusive way. As
|
||||||
|
* much as possible, notifications share a model with blocking
|
||||||
|
* dialogs so that the same information can be provided in a dialog
|
||||||
|
* and then minimized to a banner notification if needed.
|
||||||
|
*
|
||||||
|
* @namespace platform/api/notifications
|
||||||
|
*/
|
||||||
|
import moment from 'moment';
|
||||||
|
import EventEmitter from 'EventEmitter';
|
||||||
|
import MCTNotification from './MCTNotification.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A representation of a banner notification. Banner notifications
|
||||||
|
* are used to inform users of events in a non-intrusive way. As
|
||||||
|
* much as possible, notifications share a model with blocking
|
||||||
|
* dialogs so that the same information can be provided in a dialog
|
||||||
|
* and then minimized to a banner notification if needed, or vice-versa.
|
||||||
|
*
|
||||||
|
* @typedef {object} NotificationModel
|
||||||
|
* @property {string} title The title of the message
|
||||||
|
* @property {string} severity The importance of the message (one of
|
||||||
|
* 'info', 'alert', or 'error' where info < alert <error)
|
||||||
|
* @property {number} [progress] The completion status of a task
|
||||||
|
* represented numerically
|
||||||
|
* @property {boolean} [unknownProgress] a boolean indicating that the
|
||||||
|
* progress of the underlying task is unknown. This will result in a
|
||||||
|
* visually distinct progress bar.
|
||||||
|
* @property {boolean} [autoDismiss] If truthy, dialog will
|
||||||
|
* be automatically minimized or dismissed (depending on severity).
|
||||||
|
* Additionally, if the provided value is a number, it will be used
|
||||||
|
* as the delay period before being dismissed.
|
||||||
|
* @property {boolean} [dismissable=true] If true, notification will
|
||||||
|
* include an option to dismiss it completely.
|
||||||
|
* @see DialogModel
|
||||||
|
*/
|
||||||
|
|
||||||
|
const DEFAULT_AUTO_DISMISS_TIMEOUT = 3000;
|
||||||
|
const MINIMIZE_ANIMATION_TIMEOUT = 300;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The notification service is responsible for informing the user of
|
||||||
|
* events via the use of banner notifications.
|
||||||
|
* @memberof platform/commonUI/notification
|
||||||
|
* @constructor
|
||||||
|
* @param defaultAutoDismissTimeout The period of time that an
|
||||||
|
* auto-dismissed message will be displayed for.
|
||||||
|
* @param minimizeAnimationTimeout When notifications are minimized, a brief
|
||||||
|
* animation is shown. This animation requires some time to execute,
|
||||||
|
* so a timeout is required before the notification is hidden
|
||||||
|
*/
|
||||||
|
export default class NotificationAPI extends EventEmitter {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.notifications = [];
|
||||||
|
this.highest = { severity: "info" };
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A context in which to hold the active notification and a
|
||||||
|
* handle to its timeout.
|
||||||
|
*/
|
||||||
|
this.activeNotification = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimize a notification. The notification will still be available
|
||||||
|
* from the notification list. Typically notifications with a
|
||||||
|
* severity of 'info' should not be minimized, but rather
|
||||||
|
* dismissed. If you're not sure which is appropriate,
|
||||||
|
* use {@link Notification#dismissOrMinimize}
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
minimize(notification) {
|
||||||
|
//Check this is a known notification
|
||||||
|
let index = this.notifications.indexOf(notification);
|
||||||
|
|
||||||
|
if (this.activeTimeout) {
|
||||||
|
/*
|
||||||
|
Method can be called manually (clicking dismiss) or
|
||||||
|
automatically from an auto-timeout. this.activeTimeout
|
||||||
|
acts as a semaphore to prevent race conditions. Cancel any
|
||||||
|
timeout in progress (for the case where a manual dismiss
|
||||||
|
has shortcut an active auto-dismiss), and clear the
|
||||||
|
semaphore.
|
||||||
|
*/
|
||||||
|
clearTimeout(this.activeTimeout);
|
||||||
|
delete this.activeTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index >= 0) {
|
||||||
|
notification.model.minimized = true;
|
||||||
|
//Add a brief timeout before showing the next notification
|
||||||
|
// in order to allow the minimize animation to run through.
|
||||||
|
setTimeout(() => {
|
||||||
|
notification.emit('destroy');
|
||||||
|
this.setActiveNotification(this.selectNextNotification());
|
||||||
|
}, MINIMIZE_ANIMATION_TIMEOUT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Completely removes a notification. This will dismiss it from the
|
||||||
|
* message banner and remove it from the list of notifications.
|
||||||
|
* Typically only notifications with a severity of info should be
|
||||||
|
* dismissed. If you're not sure whether to dismiss or minimize a
|
||||||
|
* notification, use {@link Notification#dismissOrMinimize}.
|
||||||
|
* dismiss
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
dismiss(notification) {
|
||||||
|
//Check this is a known notification
|
||||||
|
let index = this.notifications.indexOf(notification);
|
||||||
|
|
||||||
|
if (this.activeTimeout) {
|
||||||
|
/* Method can be called manually (clicking dismiss) or
|
||||||
|
* automatically from an auto-timeout. this.activeTimeout
|
||||||
|
* acts as a semaphore to prevent race conditions. Cancel any
|
||||||
|
* timeout in progress (for the case where a manual dismiss
|
||||||
|
* has shortcut an active auto-dismiss), and clear the
|
||||||
|
* semaphore.
|
||||||
|
*/
|
||||||
|
|
||||||
|
clearTimeout(this.activeTimeout);
|
||||||
|
delete this.activeTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index >= 0) {
|
||||||
|
this.notifications.splice(index, 1);
|
||||||
|
}
|
||||||
|
this.setActiveNotification(this.selectNextNotification());
|
||||||
|
this.setHighestSeverity();
|
||||||
|
notification.emit('destroy');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Depending on the severity of the notification will selectively
|
||||||
|
* dismiss or minimize where appropriate.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
dismissOrMinimize(notification) {
|
||||||
|
let model = notification.model;
|
||||||
|
if (model.severity === "info") {
|
||||||
|
if (model.autoDismiss === false) {
|
||||||
|
this.minimize(notification);
|
||||||
|
} else {
|
||||||
|
this.dismiss(notification);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.minimize(notification);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the notification that is currently visible in the banner area
|
||||||
|
* @returns {Notification}
|
||||||
|
*/
|
||||||
|
getActiveNotification() {
|
||||||
|
return this.activeNotification;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A convenience method for info notifications. Notifications
|
||||||
|
* created via this method will be auto-destroy after a default
|
||||||
|
* wait period unless explicitly forbidden by the caller through
|
||||||
|
* the {autoDismiss} property on the {NotificationModel}, in which
|
||||||
|
* case the notification will be minimized after the wait.
|
||||||
|
* @param {NotificationModel | string} message either a string for
|
||||||
|
* the title of the notification message, or a {@link NotificationModel}
|
||||||
|
* defining the options notification to display
|
||||||
|
* @returns {Notification} the provided notification decorated with
|
||||||
|
* functions to dismiss or minimize
|
||||||
|
*/
|
||||||
|
info(message) {
|
||||||
|
let notificationModel = typeof message === "string" ? {title: message} : message;
|
||||||
|
notificationModel.severity = "info";
|
||||||
|
return this.notify(notificationModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A convenience method for alert notifications. Notifications
|
||||||
|
* created via this method will will have severity of "alert" enforced
|
||||||
|
* @param {NotificationModel | string} message either a string for
|
||||||
|
* the title of the alert message with default options, or a
|
||||||
|
* {@link NotificationModel} defining the options notification to
|
||||||
|
* display
|
||||||
|
* @returns {Notification} the provided notification decorated with
|
||||||
|
* functions to dismiss or minimize
|
||||||
|
*/
|
||||||
|
alert(message) {
|
||||||
|
let notificationModel = typeof message === "string" ? {title: message} : message;
|
||||||
|
notificationModel.severity = "alert";
|
||||||
|
return this.notify(notificationModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A convenience method for error notifications. Notifications
|
||||||
|
* created via this method will will have severity of "error" enforced
|
||||||
|
* @param {NotificationModel | string} message either a string for
|
||||||
|
* the title of the error message with default options, or a
|
||||||
|
* {@link NotificationModel} defining the options of the notification to
|
||||||
|
* display
|
||||||
|
* @returns {Notification} the provided notification decorated with
|
||||||
|
* functions to dismiss or minimize
|
||||||
|
*/
|
||||||
|
error(message) {
|
||||||
|
let notificationModel = typeof message === "string" ? {title: message} : message;
|
||||||
|
notificationModel.severity = "error";
|
||||||
|
return this.notify(notificationModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
setHighestSeverity() {
|
||||||
|
let severity = {
|
||||||
|
"info": 1,
|
||||||
|
"alert": 2,
|
||||||
|
"error": 3
|
||||||
|
};
|
||||||
|
this.highest.severity = this.notifications.reduce((previous, notification) => {
|
||||||
|
if (severity[notification.model.severity] > severity[previous]) {
|
||||||
|
return notification.model.severity;
|
||||||
|
} else {
|
||||||
|
return previous;
|
||||||
|
}
|
||||||
|
}, "info");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies the user of an event. If there is a banner notification
|
||||||
|
* already active, then it will be dismissed or minimized automatically,
|
||||||
|
* and the provided notification displayed in its place.
|
||||||
|
*
|
||||||
|
* @param {NotificationModel} notificationModel The notification to
|
||||||
|
* display
|
||||||
|
* @returns {Notification} the provided notification decorated with
|
||||||
|
* functions to {@link Notification#dismiss} or {@link Notification#minimize}
|
||||||
|
*/
|
||||||
|
notify(notificationModel) {
|
||||||
|
let notification;
|
||||||
|
let activeNotification = this.activeNotification;
|
||||||
|
|
||||||
|
notificationModel.severity = notificationModel.severity || "info";
|
||||||
|
notificationModel.timestamp = moment.utc().format('YYYY-MM-DD hh:mm:ss.ms');
|
||||||
|
|
||||||
|
notification = new MCTNotification(notificationModel, this);
|
||||||
|
|
||||||
|
this.notifications.push(notification);
|
||||||
|
this.setHighestSeverity();
|
||||||
|
|
||||||
|
/*
|
||||||
|
Check if there is already an active (ie. visible) notification
|
||||||
|
*/
|
||||||
|
if (!this.activeNotification) {
|
||||||
|
this.setActiveNotification(notification);
|
||||||
|
} else if (!this.activeTimeout) {
|
||||||
|
/*
|
||||||
|
If there is already an active notification, time it out. If it's
|
||||||
|
already got a timeout in progress (either because it has had
|
||||||
|
timeout forced because of a queue of messages, or it had an
|
||||||
|
autodismiss specified), leave it to run. Otherwise force a
|
||||||
|
timeout.
|
||||||
|
|
||||||
|
This notification has been added to queue and will be
|
||||||
|
serviced as soon as possible.
|
||||||
|
*/
|
||||||
|
this.activeTimeout = setTimeout(() => {
|
||||||
|
this.dismissOrMinimize(activeNotification);
|
||||||
|
}, DEFAULT_AUTO_DISMISS_TIMEOUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
return notification;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used internally by the NotificationService
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
setActiveNotification(notification) {
|
||||||
|
let shouldAutoDismiss;
|
||||||
|
this.activeNotification = notification;
|
||||||
|
|
||||||
|
if (!notification) {
|
||||||
|
delete this.activeTimeout;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.emit('notification', notification);
|
||||||
|
|
||||||
|
if (notification.model.severity === "info") {
|
||||||
|
shouldAutoDismiss = true;
|
||||||
|
} else {
|
||||||
|
shouldAutoDismiss = notification.model.autoDismiss;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldAutoDismiss || this.selectNextNotification()) {
|
||||||
|
this.activeTimeout = setTimeout(() => {
|
||||||
|
this.dismissOrMinimize(notification);
|
||||||
|
}, DEFAULT_AUTO_DISMISS_TIMEOUT);
|
||||||
|
} else {
|
||||||
|
delete this.activeTimeout;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used internally by the NotificationService
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
selectNextNotification() {
|
||||||
|
let notification;
|
||||||
|
let i = 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
Loop through the notifications queue and find the first one that
|
||||||
|
has not already been minimized (manually or otherwise).
|
||||||
|
*/
|
||||||
|
for (; i < this.notifications.length; i++) {
|
||||||
|
notification = this.notifications[i];
|
||||||
|
|
||||||
|
if (!notification.model.minimized &&
|
||||||
|
notification !== this.activeNotification) {
|
||||||
|
return notification;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -55,11 +55,7 @@ define([
|
|||||||
'../platform/exporters/bundle',
|
'../platform/exporters/bundle',
|
||||||
'../platform/features/clock/bundle',
|
'../platform/features/clock/bundle',
|
||||||
'../platform/features/fixed/bundle',
|
'../platform/features/fixed/bundle',
|
||||||
'../platform/features/conductor/core/bundle',
|
|
||||||
'../platform/features/conductor/compatibility/bundle',
|
|
||||||
'../platform/features/imagery/bundle',
|
'../platform/features/imagery/bundle',
|
||||||
'../platform/features/layout/bundle',
|
|
||||||
'../platform/features/listview/bundle',
|
|
||||||
'../platform/features/my-items/bundle',
|
'../platform/features/my-items/bundle',
|
||||||
'../platform/features/pages/bundle',
|
'../platform/features/pages/bundle',
|
||||||
'../platform/features/hyperlink/bundle',
|
'../platform/features/hyperlink/bundle',
|
||||||
@ -102,8 +98,6 @@ define([
|
|||||||
'platform/features/clock',
|
'platform/features/clock',
|
||||||
'platform/features/fixed',
|
'platform/features/fixed',
|
||||||
'platform/features/imagery',
|
'platform/features/imagery',
|
||||||
'platform/features/layout',
|
|
||||||
'platform/features/listview',
|
|
||||||
'platform/features/pages',
|
'platform/features/pages',
|
||||||
'platform/features/hyperlink',
|
'platform/features/hyperlink',
|
||||||
'platform/features/timeline',
|
'platform/features/timeline',
|
||||||
|
@ -1,8 +1,34 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="l-layout">
|
<div class="l-layout"
|
||||||
|
@dragover="handleDragOver"
|
||||||
|
@click="bypassSelection"
|
||||||
|
@drop="handleDrop">
|
||||||
<div class="l-layout__object">
|
<div class="l-layout__object">
|
||||||
<!-- Background grid -->
|
<!-- Background grid -->
|
||||||
<div class="l-layout__grid-holder c-grid" v-if="!drilledIn">
|
<div class="l-layout__grid-holder c-grid"
|
||||||
|
v-if="!drilledIn">
|
||||||
<div class="c-grid__x l-grid l-grid-x"
|
<div class="c-grid__x l-grid l-grid-x"
|
||||||
v-if="gridSize[0] >= 3"
|
v-if="gridSize[0] >= 3"
|
||||||
:style="[{ backgroundSize: gridSize[0] + 'px 100%' }]">
|
:style="[{ backgroundSize: gridSize[0] + 'px 100%' }]">
|
||||||
@ -11,13 +37,14 @@
|
|||||||
v-if="gridSize[1] >= 3"
|
v-if="gridSize[1] >= 3"
|
||||||
:style="[{ backgroundSize: '100%' + gridSize[1] + 'px' }]"></div>
|
:style="[{ backgroundSize: '100%' + gridSize[1] + 'px' }]"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<layout-frame v-for="item in frameItems"
|
<layout-frame v-for="item in frameItems"
|
||||||
class="l-layout__frame"
|
class="l-layout__frame"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
:item="item"
|
:item="item"
|
||||||
|
:gridSize="gridSize"
|
||||||
@drilledIn="updateDrilledInState"
|
@drilledIn="updateDrilledInState"
|
||||||
@selected="updateSelectedState">
|
@dragInProgress="updatePosition"
|
||||||
|
@endDrag="endDrag">
|
||||||
</layout-frame>
|
</layout-frame>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -52,7 +79,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.c-grid {
|
.c-grid {
|
||||||
z-index: -1;
|
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|
||||||
&__x { @include bgTicks($colorGridLines, 'x'); }
|
&__x { @include bgTicks($colorGridLines, 'x'); }
|
||||||
@ -60,14 +86,23 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.is-editing {
|
.is-editing {
|
||||||
.l-layout {
|
.l-shell__main-container > .l-layout {
|
||||||
background: rgba($colorKey, 0.1);
|
// Target the top-most layout container and color its background
|
||||||
|
background: rgba($editColor, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
&.s-selected,
|
.s-selected,
|
||||||
&.s-selected-parent {
|
.s-selected-parent {
|
||||||
|
.l-layout {
|
||||||
|
// Show the layout grid for the top-most child of the current selection,
|
||||||
|
// and hide the grid for deeper nested levels.
|
||||||
[class*="__grid-holder"] {
|
[class*="__grid-holder"] {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.l-layout [class*="__grid-holder"] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -79,18 +114,20 @@
|
|||||||
|
|
||||||
const DEFAULT_GRID_SIZE = [32, 32],
|
const DEFAULT_GRID_SIZE = [32, 32],
|
||||||
DEFAULT_DIMENSIONS = [12, 8],
|
DEFAULT_DIMENSIONS = [12, 8],
|
||||||
MINIMUM_FRAME_SIZE = [320, 180];
|
DEFAULT_POSITION = [0, 0],
|
||||||
|
MINIMUM_FRAME_SIZE = [320, 180],
|
||||||
|
DEFAULT_HIDDEN_FRAME_TYPES = [
|
||||||
|
'hyperlink'
|
||||||
|
];
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
gridSize: DEFAULT_GRID_SIZE,
|
gridSize: [],
|
||||||
frameItems: [],
|
frameItems: [],
|
||||||
frames: [],
|
frames: [],
|
||||||
composition: Object,
|
|
||||||
frameStyles: [],
|
frameStyles: [],
|
||||||
rawPositions: {},
|
rawPositions: {},
|
||||||
isEditing: true,
|
|
||||||
initSelect: true,
|
initSelect: true,
|
||||||
drilledIn: undefined
|
drilledIn: undefined
|
||||||
}
|
}
|
||||||
@ -101,44 +138,71 @@
|
|||||||
LayoutFrame
|
LayoutFrame
|
||||||
},
|
},
|
||||||
created: function () {
|
created: function () {
|
||||||
console.log("domainObject", JSON.parse(JSON.stringify(this.domainObject)));
|
this.newDomainObject = this.domainObject;
|
||||||
|
this.gridSize = this.newDomainObject.layoutGrid || DEFAULT_GRID_SIZE;
|
||||||
|
this.composition = this.openmct.composition.get(this.newDomainObject);
|
||||||
|
let panels = (((this.newDomainObject.configuration || {}).layout || {}).panels || {});
|
||||||
|
|
||||||
this.populatePositions(this.domainObject.configuration.layout.panels);
|
if (this.composition !== undefined) {
|
||||||
|
this.composition.load().then((composition) => {
|
||||||
this.composition = this.openmct.composition.get(this.domainObject);
|
composition.forEach(function (domainObject) {
|
||||||
|
this.readLayoutConfiguration(domainObject, panels);
|
||||||
|
this.makeFrameItem(domainObject, false);
|
||||||
|
}.bind(this));
|
||||||
this.composition.on('add', this.onAddComposition);
|
this.composition.on('add', this.onAddComposition);
|
||||||
this.composition.on('remove', this.onRemoveComposition);
|
this.composition.on('remove', this.onRemoveComposition);
|
||||||
this.composition.load();
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.unlisten = this.openmct.objects.observe(this.newDomainObject, '*', function (obj) {
|
||||||
|
this.newDomainObject = JSON.parse(JSON.stringify(obj));
|
||||||
|
this.gridSize = this.newDomainObject.layoutGrid || DEFAULT_GRID_SIZE;;
|
||||||
|
}.bind(this));
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onAddComposition(domainObject) {
|
readLayoutConfiguration(domainObject, panels) {
|
||||||
console.log('composition object', domainObject);
|
let id = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||||
const id = this.openmct.objects.makeKeyString(domainObject.identifier)
|
this.rawPositions[id] = {
|
||||||
|
position: panels[id].position || DEFAULT_POSITION,
|
||||||
|
dimensions: panels[id].dimensions || this.defaultDimensions()
|
||||||
|
};
|
||||||
|
this.frameStyles[id] = this.convertPosition(this.rawPositions[id]);
|
||||||
|
this.frames[id] = panels[id].hasOwnProperty('hasFrame') ?
|
||||||
|
panels[id].hasFrame :
|
||||||
|
this.hasFrameByDefault(domainObject.type);
|
||||||
|
},
|
||||||
|
makeFrameItem(domainObject, initSelect) {
|
||||||
|
let id = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||||
this.frameItems.push({
|
this.frameItems.push({
|
||||||
id: id,
|
id: id,
|
||||||
hasFrame: this.hasFrame(id),
|
hasFrame: this.frames[id],
|
||||||
domainObject,
|
domainObject,
|
||||||
style: this.frameStyles[id],
|
style: this.frameStyles[id],
|
||||||
drilledIn: this.isDrilledIn(id),
|
drilledIn: this.isDrilledIn(id),
|
||||||
selected: false
|
initSelect: initSelect,
|
||||||
|
rawPosition: this.rawPositions[id]
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
onAddComposition(domainObject) {
|
||||||
|
let id = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||||
|
this.rawPositions[id] = {
|
||||||
|
position: [
|
||||||
|
Math.floor(this.droppedObjectPosition.x / this.gridSize[0]),
|
||||||
|
Math.floor(this.droppedObjectPosition.y / this.gridSize[1])
|
||||||
|
],
|
||||||
|
dimensions: this.defaultDimensions()
|
||||||
|
};
|
||||||
|
this.frameStyles[id] = this.convertPosition(this.rawPositions[id]);
|
||||||
|
this.frames[id] = this.hasFrameByDefault(domainObject.type);
|
||||||
|
|
||||||
|
let newPanel = this.rawPositions[id];
|
||||||
|
newPanel.hasFrame = this.frames[id];
|
||||||
|
this.mutate("configuration.layout.panels[" + id + "]", newPanel);
|
||||||
|
this.makeFrameItem(domainObject, true);
|
||||||
|
},
|
||||||
onRemoveComposition(identifier) {
|
onRemoveComposition(identifier) {
|
||||||
// TODO: remove the object from frameItems
|
// TODO: remove the object from frameItems
|
||||||
},
|
},
|
||||||
populatePositions(panels) {
|
|
||||||
Object.keys(panels).forEach(function (key, index) {
|
|
||||||
this.rawPositions[key] = {
|
|
||||||
position: panels[key].position || this.defaultPosition(index),
|
|
||||||
dimensions: panels[key].dimensions || this.defaultDimensions()
|
|
||||||
};
|
|
||||||
this.frameStyles[key] = this.convertPosition(this.rawPositions[key]);
|
|
||||||
this.frames[key] = panels[key].hasFrame;
|
|
||||||
}.bind(this));
|
|
||||||
},
|
|
||||||
defaultPosition(index) {
|
|
||||||
return [index, index];
|
|
||||||
},
|
|
||||||
defaultDimensions() {
|
defaultDimensions() {
|
||||||
let gridSize = this.gridSize;
|
let gridSize = this.gridSize;
|
||||||
return MINIMUM_FRAME_SIZE.map(function (min, i) {
|
return MINIMUM_FRAME_SIZE.map(function (min, i) {
|
||||||
@ -158,8 +222,15 @@
|
|||||||
minHeight: (this.gridSize[1] * raw.dimensions[1]) + 'px'
|
minHeight: (this.gridSize[1] * raw.dimensions[1]) + 'px'
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
hasFrame(id) {
|
/**
|
||||||
return this.frames[id]
|
* Checks if the frame should be hidden or not.
|
||||||
|
*
|
||||||
|
* @param type the domain object type
|
||||||
|
* @return {boolean} true if the object should have
|
||||||
|
* frame by default, false, otherwise
|
||||||
|
*/
|
||||||
|
hasFrameByDefault(type) {
|
||||||
|
return DEFAULT_HIDDEN_FRAME_TYPES.indexOf(type) === -1;
|
||||||
},
|
},
|
||||||
setSelection(selection) {
|
setSelection(selection) {
|
||||||
if (selection.length === 0) {
|
if (selection.length === 0) {
|
||||||
@ -167,7 +238,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.updateDrilledInState();
|
this.updateDrilledInState();
|
||||||
this.updateSelectedState();
|
|
||||||
},
|
},
|
||||||
updateDrilledInState(id) {
|
updateDrilledInState(id) {
|
||||||
this.drilledIn = id;
|
this.drilledIn = id;
|
||||||
@ -175,31 +245,82 @@
|
|||||||
item.drilledIn = item.id === id;
|
item.drilledIn = item.id === id;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
updateSelectedState(id) {
|
|
||||||
this.frameItems.forEach(function (item) {
|
|
||||||
item.selected = item.id === id;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
isDrilledIn(id) {
|
isDrilledIn(id) {
|
||||||
return this.drilledIn === id;
|
return this.drilledIn === id;
|
||||||
|
},
|
||||||
|
updatePosition(id, newPosition) {
|
||||||
|
let newStyle = this.convertPosition(newPosition);
|
||||||
|
this.frameStyles[id] = newStyle;
|
||||||
|
this.rawPositions[id] = newPosition;
|
||||||
|
this.frameItems.forEach(function (item) {
|
||||||
|
if (item.id === id) {
|
||||||
|
item.style = newStyle;
|
||||||
|
item.rawPosition = newPosition;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
bypassSelection($event) {
|
||||||
|
if (this.dragInProgress) {
|
||||||
|
if ($event) {
|
||||||
|
$event.stopImmediatePropagation();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
endDrag(id) {
|
||||||
|
this.dragInProgress = true;
|
||||||
|
setTimeout(function () {
|
||||||
|
this.dragInProgress = false;
|
||||||
|
}.bind(this), 0);
|
||||||
|
|
||||||
|
let path = "configuration.layout.panels[" + id + "]";
|
||||||
|
this.mutate(path + ".dimensions", this.rawPositions[id].dimensions);
|
||||||
|
this.mutate(path + ".position", this.rawPositions[id].position);
|
||||||
|
},
|
||||||
|
mutate(path, value) {
|
||||||
|
this.openmct.objects.mutate(this.newDomainObject, path, value);
|
||||||
|
},
|
||||||
|
handleDrop($event) {
|
||||||
|
$event.preventDefault();
|
||||||
|
|
||||||
|
let child = JSON.parse($event.dataTransfer.getData('domainObject'));
|
||||||
|
let duplicates = [];
|
||||||
|
let composition = this.newDomainObject.composition;
|
||||||
|
composition.forEach((object) => {
|
||||||
|
if (this.openmct.objects.makeKeyString(JSON.parse(JSON.stringify(object))) ===
|
||||||
|
this.openmct.objects.makeKeyString(child.identifier)) {
|
||||||
|
duplicates.push(object);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Disallow adding a duplicate object to the composition
|
||||||
|
if (duplicates.length !== 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let elementRect = this.$el.getBoundingClientRect();
|
||||||
|
this.droppedObjectPosition = {
|
||||||
|
x: $event.pageX - elementRect.left,
|
||||||
|
y: $event.pageY - elementRect.top
|
||||||
|
}
|
||||||
|
// TODO: use the composition API to add child once the default composition
|
||||||
|
// provider supports it instead of mutating the composition directly.
|
||||||
|
// this.composition.add(child).
|
||||||
|
composition.push(child.identifier);
|
||||||
|
this.mutate('composition', composition);
|
||||||
|
},
|
||||||
|
handleDragOver($event){
|
||||||
|
$event.preventDefault();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.removeSelectable = this.openmct.selection.selectable(
|
|
||||||
this.$el,
|
|
||||||
{
|
|
||||||
item: this.domainObject
|
|
||||||
},
|
|
||||||
this.initSelect
|
|
||||||
);
|
|
||||||
|
|
||||||
this.openmct.selection.on('change', this.setSelection);
|
this.openmct.selection.on('change', this.setSelection);
|
||||||
},
|
},
|
||||||
destroyed: function () {
|
destroyed: function () {
|
||||||
this.composition.off('add', this.onAddComposition);
|
this.composition.off('add', this.onAddComposition);
|
||||||
this.composition.off('remove', this.onRemoveComposition);
|
this.composition.off('remove', this.onRemoveComposition);
|
||||||
this.openmct.off('change', this.selection);
|
this.openmct.off('change', this.selection);
|
||||||
this.removeSelectable();
|
this.unlisten();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,24 +21,21 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
define(function () {
|
define(function () {
|
||||||
function MCTGesture(gestureService) {
|
function DisplayLayoutType() {
|
||||||
return {
|
return {
|
||||||
restrict : 'A',
|
name: "Display Layout",
|
||||||
scope: {
|
creatable: true,
|
||||||
domainObject: '=mctObject'
|
cssClass: 'icon-layout',
|
||||||
},
|
initialize(domainObject) {
|
||||||
link : function ($scope, $element, attrs) {
|
domainObject.composition = [];
|
||||||
var activeGestures = gestureService.attachGestures(
|
domainObject.configuration = {
|
||||||
$element,
|
layout: {
|
||||||
$scope.domainObject,
|
panels: {}
|
||||||
$scope.$eval(attrs.mctGesture)
|
|
||||||
);
|
|
||||||
$scope.$on('$destroy', function () {
|
|
||||||
activeGestures.destroy();
|
|
||||||
delete this.activeGestures;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return MCTGesture;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return DisplayLayoutType;
|
||||||
});
|
});
|
115
src/plugins/displayLayout/LayoutDrag.js
Normal file
115
src/plugins/displayLayout/LayoutDrag.js
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
define(
|
||||||
|
[],
|
||||||
|
function () {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles drag interactions on frames in layouts. This will
|
||||||
|
* provides new positions/dimensions for frames based on
|
||||||
|
* relative pixel positions provided; these will take into account
|
||||||
|
* the grid size (in a snap-to sense) and will enforce some minimums
|
||||||
|
* on both position and dimensions.
|
||||||
|
*
|
||||||
|
* The provided position and dimensions factors will determine
|
||||||
|
* whether this is a move or a resize, and what type of resize it
|
||||||
|
* will be. For instance, a position factor of [1, 1]
|
||||||
|
* will move a frame along with the mouse as the drag
|
||||||
|
* proceeds, while a dimension factor of [0, 0] will leave
|
||||||
|
* dimensions unchanged. Combining these in different
|
||||||
|
* ways results in different handles; a position factor of
|
||||||
|
* [1, 0] and a dimensions factor of [-1, 0] will implement
|
||||||
|
* a left-edge resize, as the horizontal position will move
|
||||||
|
* with the mouse while the horizontal dimensions shrink in
|
||||||
|
* kind (and vertical properties remain unmodified.)
|
||||||
|
*
|
||||||
|
* @param {object} rawPosition the initial position/dimensions
|
||||||
|
* of the frame being interacted with
|
||||||
|
* @param {number[]} posFactor the position factor
|
||||||
|
* @param {number[]} dimFactor the dimensions factor
|
||||||
|
* @param {number[]} the size of each grid element, in pixels
|
||||||
|
* @constructor
|
||||||
|
* @memberof platform/features/layout
|
||||||
|
*/
|
||||||
|
function LayoutDrag(rawPosition, posFactor, dimFactor, gridSize) {
|
||||||
|
this.rawPosition = rawPosition;
|
||||||
|
this.posFactor = posFactor;
|
||||||
|
this.dimFactor = dimFactor;
|
||||||
|
this.gridSize = gridSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert a delta from pixel coordinates to grid coordinates,
|
||||||
|
// rounding to whole-number grid coordinates.
|
||||||
|
function toGridDelta(gridSize, pixelDelta) {
|
||||||
|
return pixelDelta.map(function (v, i) {
|
||||||
|
return Math.round(v / gridSize[i]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utility function to perform element-by-element multiplication
|
||||||
|
function multiply(array, factors) {
|
||||||
|
return array.map(function (v, i) {
|
||||||
|
return v * factors[i];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utility function to perform element-by-element addition
|
||||||
|
function add(array, other) {
|
||||||
|
return array.map(function (v, i) {
|
||||||
|
return v + other[i];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utility function to perform element-by-element max-choosing
|
||||||
|
function max(array, other) {
|
||||||
|
return array.map(function (v, i) {
|
||||||
|
return Math.max(v, other[i]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a new position object in grid coordinates, with
|
||||||
|
* position and dimensions both offset appropriately
|
||||||
|
* according to the factors supplied in the constructor.
|
||||||
|
* @param {number[]} pixelDelta the offset from the
|
||||||
|
* original position, in pixels
|
||||||
|
*/
|
||||||
|
LayoutDrag.prototype.getAdjustedPosition = function (pixelDelta) {
|
||||||
|
var gridDelta = toGridDelta(this.gridSize, pixelDelta);
|
||||||
|
return {
|
||||||
|
position: max(add(
|
||||||
|
this.rawPosition.position,
|
||||||
|
multiply(gridDelta, this.posFactor)
|
||||||
|
), [0, 0]),
|
||||||
|
dimensions: max(add(
|
||||||
|
this.rawPosition.dimensions,
|
||||||
|
multiply(gridDelta, this.dimFactor)
|
||||||
|
), [1, 1])
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return LayoutDrag;
|
||||||
|
|
||||||
|
}
|
||||||
|
);
|
@ -1,29 +1,54 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<!-- - TODO: styles for selectable, moveable, etc. -->
|
<div class="c-frame has-local-controls is-selectable is-moveable"
|
||||||
<div class="c-frame has-local-controls"
|
|
||||||
:style="item.style"
|
:style="item.style"
|
||||||
:class="classObject"
|
:class="classObject"
|
||||||
@dblclick="drill(item.id, $event)">
|
@dblclick="drill(item.id, $event)">
|
||||||
<div class="c-frame__header">
|
<div class="c-frame__header">
|
||||||
<div class="c-frame__header__start">
|
<div class="c-frame__header__start">
|
||||||
<div class="c-frame__name icon-object">Header</div>
|
<div class="c-frame__name icon-object">{{ item.domainObject.name }}</div>
|
||||||
<div class="c-frame__context-actions c-disclosure-button"></div>
|
<div class="c-frame__context-actions c-disclosure-button"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="c-frame__header__end">
|
<div class="c-frame__header__end">
|
||||||
<div class="c-button icon-expand local-controls--hidden"></div>
|
<div class="c-button icon-expand local-controls--hidden"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<object-view
|
<object-view class="c-frame__object-view"
|
||||||
class="c-frame__object-view"
|
|
||||||
:object="item.domainObject"></object-view>
|
:object="item.domainObject"></object-view>
|
||||||
|
|
||||||
<!-- Drag handles -->
|
<!-- Drag handles -->
|
||||||
<div class="c-frame-edit">
|
<div class="c-frame-edit">
|
||||||
<div class="c-frame-edit__move"></div>
|
<div class="c-frame-edit__move"
|
||||||
<div class="c-frame-edit__handle --nw"></div>
|
@mousedown="startDrag([1,1], [0,0], $event)"></div>
|
||||||
<div class="c-frame-edit__handle --ne"></div>
|
<div class="c-frame-edit__handle --nw"
|
||||||
<div class="c-frame-edit__handle --se"></div>
|
@mousedown="startDrag([1,1], [-1,-1], $event)"></div>
|
||||||
<div class="c-frame-edit__handle --sw"></div>
|
<div class="c-frame-edit__handle --ne"
|
||||||
|
@mousedown="startDrag([0,1], [1,-1], $event)"></div>
|
||||||
|
<div class="c-frame-edit__handle --sw"
|
||||||
|
@mousedown="startDrag([1,0], [-1,1], $event)"></div>
|
||||||
|
<div class="c-frame-edit__handle --se"
|
||||||
|
@mousedown="startDrag([0,0], [1,1], $event)"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -35,6 +60,8 @@
|
|||||||
.c-frame {
|
.c-frame {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
border-width: 1px;
|
||||||
|
border-color: transparent;
|
||||||
|
|
||||||
/*************************** HEADER */
|
/*************************** HEADER */
|
||||||
&__header {
|
&__header {
|
||||||
@ -48,13 +75,18 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
> * + * {
|
||||||
|
margin-left: $interiorMargin;
|
||||||
|
}
|
||||||
|
|
||||||
[class*="__start"] {
|
[class*="__start"] {
|
||||||
flex: 0 0 auto;
|
flex: 1 1 auto;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
[class*="__end"] {
|
[class*="__end"] {
|
||||||
justify-content: flex-end;
|
//justify-content: flex-end;
|
||||||
flex: 1 1 auto;
|
flex: 0 0 auto;
|
||||||
|
|
||||||
[class*="button"] {
|
[class*="button"] {
|
||||||
font-size: 0.7em;
|
font-size: 0.7em;
|
||||||
@ -78,11 +110,18 @@
|
|||||||
&__object-view {
|
&__object-view {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
|
||||||
|
.c-object-view {
|
||||||
|
.u-fills-container {
|
||||||
|
// Expand component types that fill a container
|
||||||
|
@include abs();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*************************** NO-FRAME */
|
/*************************** NO-FRAME */
|
||||||
&.no-frame {
|
&.no-frame {
|
||||||
[class*="__header"] {
|
> [class*="__header"] {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -94,32 +133,55 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*************************** SELECTION */
|
/*************************** SELECTION */
|
||||||
&.s-selected {
|
&.is-selectable {
|
||||||
//Legacy name for now
|
&:hover {
|
||||||
border-color: $colorKey;
|
box-shadow: $browseShdwSelectableHov;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.is-drilled-in {
|
&.s-selected, // LEGACY
|
||||||
border: 1px dashed deeppink;
|
&.is-selected {
|
||||||
|
border: $browseBorderSelected;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*************************** EDITING */
|
/*************************** EDITING */
|
||||||
.is-editing .c-frame:not(.is-drilled-in) {
|
.is-editing {
|
||||||
border: 1px dotted rgba($colorKey, 0.5);
|
.c-frame {
|
||||||
|
&:not(.is-drilled-in).is-selectable {
|
||||||
|
border: $editBorderSelectable;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border: $editBorderSelectableHov;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.s-selected,
|
||||||
|
&.is-selected {
|
||||||
|
border: $editBorderSelected;
|
||||||
|
|
||||||
&.s-selected {
|
|
||||||
> .c-frame-edit {
|
> .c-frame-edit {
|
||||||
display: block;
|
display: block; // Show the editing rect and handles
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-drilled-in {
|
||||||
|
border: $editBorderDrilledIn;
|
||||||
|
}
|
||||||
|
|
||||||
|
.u-links {
|
||||||
|
// Applied in markup to objects that provide links. Disable while editing.
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.c-frame-edit {
|
.c-frame-edit {
|
||||||
|
// The editing rect and handles
|
||||||
$z: 10;
|
$z: 10;
|
||||||
|
|
||||||
@include abs();
|
@include abs();
|
||||||
box-shadow: rgba($colorKey, 1) 0 0 10px;
|
box-shadow: rgba($editColor, 0.5) 0 0 10px;
|
||||||
display: none;
|
display: none;
|
||||||
|
|
||||||
&__move {
|
&__move {
|
||||||
@ -131,8 +193,8 @@
|
|||||||
&__handle {
|
&__handle {
|
||||||
$d: 8px;
|
$d: 8px;
|
||||||
$o: floor($d * -0.5);
|
$o: floor($d * -0.5);
|
||||||
background: rgba($colorKey, 0.3);
|
background: rgba($editColor, 0.3);
|
||||||
border: 1px solid $colorKey;
|
border: 1px solid $editColor;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: $d; height: $d;
|
width: $d; height: $d;
|
||||||
top: auto; right: auto; bottom: auto; left: auto;
|
top: auto; right: auto; bottom: auto; left: auto;
|
||||||
@ -149,7 +211,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: $colorKey;
|
background: $editColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.--nw {
|
&.--nw {
|
||||||
@ -179,11 +241,13 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import ObjectView from '../../ui/components/layout/ObjectView.vue'
|
import ObjectView from '../../ui/components/layout/ObjectView.vue'
|
||||||
|
import LayoutDrag from './LayoutDrag'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
inject: ['openmct'],
|
inject: ['openmct'],
|
||||||
props: {
|
props: {
|
||||||
item: Object
|
item: Object,
|
||||||
|
gridSize: Array
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
ObjectView
|
ObjectView
|
||||||
@ -197,16 +261,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
setSelection(selection) {
|
|
||||||
if (selection.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let id = this.openmct.objects.makeKeyString(selection[0].context.item.identifier);
|
|
||||||
if (this.item.id === id) {
|
|
||||||
this.$emit('selected', id);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
drill(id, $event) {
|
drill(id, $event) {
|
||||||
if ($event) {
|
if ($event) {
|
||||||
$event.stopPropagation();
|
$event.stopPropagation();
|
||||||
@ -230,6 +284,42 @@
|
|||||||
isBeingEdited(object) {
|
isBeingEdited(object) {
|
||||||
// TODO: add logic when inEditContext() is implemented in Vue.
|
// TODO: add logic when inEditContext() is implemented in Vue.
|
||||||
return true;
|
return true;
|
||||||
|
},
|
||||||
|
updatePosition(event) {
|
||||||
|
let currentPosition = [event.pageX, event.pageY];
|
||||||
|
this.initialPosition = this.initialPosition || currentPosition;
|
||||||
|
this.delta = currentPosition.map(function (value, index) {
|
||||||
|
return value - this.initialPosition[index];
|
||||||
|
}.bind(this));
|
||||||
|
},
|
||||||
|
startDrag(posFactor, dimFactor, event) {
|
||||||
|
document.body.addEventListener('mousemove', this.continueDrag);
|
||||||
|
document.body.addEventListener('mouseup', this.endDrag);
|
||||||
|
|
||||||
|
this.updatePosition(event);
|
||||||
|
this.activeDrag = new LayoutDrag(
|
||||||
|
this.item.rawPosition,
|
||||||
|
posFactor,
|
||||||
|
dimFactor,
|
||||||
|
this.gridSize
|
||||||
|
);
|
||||||
|
event.preventDefault();
|
||||||
|
},
|
||||||
|
continueDrag(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
this.updatePosition(event);
|
||||||
|
|
||||||
|
if (this.activeDrag) {
|
||||||
|
this.$emit('dragInProgress', this.item.id, this.activeDrag.getAdjustedPosition(this.delta));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
endDrag(event) {
|
||||||
|
document.body.removeEventListener('mousemove', this.continueDrag);
|
||||||
|
document.body.removeEventListener('mouseup', this.endDrag);
|
||||||
|
this.continueDrag(event);
|
||||||
|
this.$emit('endDrag', this.item.id);
|
||||||
|
this.initialPosition = undefined;
|
||||||
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
@ -238,13 +328,10 @@
|
|||||||
{
|
{
|
||||||
item: this.item.domainObject
|
item: this.item.domainObject
|
||||||
},
|
},
|
||||||
this.item.selected
|
this.item.initSelect
|
||||||
);
|
);
|
||||||
|
|
||||||
this.openmct.selection.on('change', this.setSelection);
|
|
||||||
},
|
},
|
||||||
destroyed() {
|
destroyed() {
|
||||||
this.openmct.off('change', this.selection);
|
|
||||||
this.removeSelectable();
|
this.removeSelectable();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,32 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
import Layout from './DisplayLayout.vue'
|
import Layout from './DisplayLayout.vue'
|
||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import objectUtils from '../../api/objects/object-utils.js'
|
import objectUtils from '../../api/objects/object-utils.js'
|
||||||
|
import DisplayLayoutType from './DisplayLayoutType.js'
|
||||||
|
|
||||||
export default function () {
|
export default function () {
|
||||||
return function (openmct) {
|
return function (openmct) {
|
||||||
console.log("Installing Layout component...");
|
|
||||||
|
|
||||||
openmct.objectViews.addProvider({
|
openmct.objectViews.addProvider({
|
||||||
key: 'layout.view',
|
key: 'layout.view',
|
||||||
canView: function (domainObject) {
|
canView: function (domainObject) {
|
||||||
@ -38,8 +59,9 @@ export default function () {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
priority() {
|
priority() {
|
||||||
return 1;
|
return 100;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
openmct.types.addType('layout', DisplayLayoutType());
|
||||||
}
|
}
|
||||||
}
|
}
|
67
src/plugins/folderView/FolderGridView.js
Normal file
67
src/plugins/folderView/FolderGridView.js
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
define([
|
||||||
|
'./components/GridView.vue',
|
||||||
|
'vue'
|
||||||
|
], function (
|
||||||
|
GridViewComponent,
|
||||||
|
Vue
|
||||||
|
) {
|
||||||
|
function FolderGridView(openmct) {
|
||||||
|
return {
|
||||||
|
key: 'grid',
|
||||||
|
name: 'Grid Vue',
|
||||||
|
cssClass: 'icon-thumbs-strip',
|
||||||
|
canView: function (domainObject) {
|
||||||
|
return domainObject.type === 'folder';
|
||||||
|
},
|
||||||
|
view: function (domainObject) {
|
||||||
|
let component;
|
||||||
|
|
||||||
|
return {
|
||||||
|
show: function (element) {
|
||||||
|
component = new Vue({
|
||||||
|
components: {
|
||||||
|
gridViewComponent: GridViewComponent.default
|
||||||
|
},
|
||||||
|
provide: {
|
||||||
|
openmct,
|
||||||
|
domainObject
|
||||||
|
},
|
||||||
|
el: element,
|
||||||
|
template: '<grid-view-component></grid-view-component>'
|
||||||
|
});
|
||||||
|
},
|
||||||
|
destroy: function (element) {
|
||||||
|
component.$destroy();
|
||||||
|
component = undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
priority: function () {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return FolderGridView;
|
||||||
|
});
|
70
src/plugins/folderView/FolderListView.js
Normal file
70
src/plugins/folderView/FolderListView.js
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
define([
|
||||||
|
'./components/ListView.vue',
|
||||||
|
'vue',
|
||||||
|
'moment'
|
||||||
|
], function (
|
||||||
|
ListViewComponent,
|
||||||
|
Vue,
|
||||||
|
Moment
|
||||||
|
) {
|
||||||
|
function FolderListView(openmct) {
|
||||||
|
return {
|
||||||
|
key: 'list-view',
|
||||||
|
name: 'List Vue',
|
||||||
|
cssClass: 'icon-list-view',
|
||||||
|
canView: function (domainObject) {
|
||||||
|
return domainObject.type === 'folder';
|
||||||
|
},
|
||||||
|
view: function (domainObject) {
|
||||||
|
let component;
|
||||||
|
|
||||||
|
return {
|
||||||
|
show: function (element) {
|
||||||
|
component = new Vue({
|
||||||
|
components: {
|
||||||
|
listViewComponent: ListViewComponent.default
|
||||||
|
},
|
||||||
|
provide: {
|
||||||
|
openmct,
|
||||||
|
domainObject,
|
||||||
|
Moment
|
||||||
|
},
|
||||||
|
el: element,
|
||||||
|
template: '<list-view-component></list-view-component>'
|
||||||
|
});
|
||||||
|
},
|
||||||
|
destroy: function (element) {
|
||||||
|
component.$destroy();
|
||||||
|
component = undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
priority: function () {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return FolderListView;
|
||||||
|
});
|
223
src/plugins/folderView/components/GridView.vue
Normal file
223
src/plugins/folderView/components/GridView.vue
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
<template>
|
||||||
|
<div class="l-grid-view">
|
||||||
|
<div v-for="(item, index) in items"
|
||||||
|
v-bind:key="index"
|
||||||
|
class="l-grid-view__item c-grid-item"
|
||||||
|
:class="{ 'is-alias': item.isAlias === true }"
|
||||||
|
@click="navigate(item.model.identifier.key)">
|
||||||
|
<div class="c-grid-item__type-icon"
|
||||||
|
:class="(item.type.cssClass != undefined) ? 'bg-' + item.type.cssClass : 'bg-icon-object-unknown'">
|
||||||
|
</div>
|
||||||
|
<div class="c-grid-item__details">
|
||||||
|
<!-- Name and metadata -->
|
||||||
|
<div class="c-grid-item__name"
|
||||||
|
:title="item.model.name">{{item.model.name}}</div>
|
||||||
|
<div class="c-grid-item__metadata"
|
||||||
|
:title="item.type.name">
|
||||||
|
<span class="c-grid-item__metadata__type">{{item.type.name}}</span>
|
||||||
|
<span class="c-grid-item__metadata__item-count" v-if="item.model.composition !== undefined">
|
||||||
|
{{item.model.composition.length}} item<span v-if="item.model.composition.length !== 1">s</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="c-grid-item__controls">
|
||||||
|
<div class="icon-people" title='Shared'></div>
|
||||||
|
<button class="c-click-icon icon-info c-info-button" title='More Info'></button>
|
||||||
|
<div class="icon-pointer-right c-pointer-icon"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import "~styles/sass-base";
|
||||||
|
|
||||||
|
/******************************* GRID VIEW */
|
||||||
|
.l-grid-view {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column nowrap;
|
||||||
|
|
||||||
|
&__item {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
+ .l-grid-view__item { margin-top: $interiorMargin; }
|
||||||
|
}
|
||||||
|
|
||||||
|
body.desktop & {
|
||||||
|
flex-flow: row wrap;
|
||||||
|
&__item {
|
||||||
|
height: $gridItemDesk;
|
||||||
|
width: $gridItemDesk;
|
||||||
|
margin: 0 $interiorMargin $interiorMargin 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/******************************* GRID ITEMS */
|
||||||
|
.c-grid-item {
|
||||||
|
// Mobile-first
|
||||||
|
@include button($bg: $colorItemBg, $fg: $colorItemFg);
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
padding: $interiorMarginLg;
|
||||||
|
|
||||||
|
&__type-icon {
|
||||||
|
filter: $colorKeyFilter;
|
||||||
|
flex: 0 0 $gridItemMobile;
|
||||||
|
font-size: floor($gridItemMobile / 2);
|
||||||
|
margin-right: $interiorMarginLg;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-alias {
|
||||||
|
// Object is an alias to an original.
|
||||||
|
[class*='__type-icon'] {
|
||||||
|
@include isAlias();
|
||||||
|
color: $colorIconAliasForKeyFilter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__details {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column nowrap;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__name {
|
||||||
|
@include ellipsize();
|
||||||
|
color: $colorItemFg;
|
||||||
|
font-size: 1.2em;
|
||||||
|
font-weight: 400;
|
||||||
|
margin-bottom: $interiorMarginSm;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__metadata {
|
||||||
|
color: $colorItemFgDetails;
|
||||||
|
font-size: 0.9em;
|
||||||
|
|
||||||
|
body.mobile & {
|
||||||
|
[class*='__item-count'] {
|
||||||
|
&:before {
|
||||||
|
content: ' - ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__controls {
|
||||||
|
color: $colorItemFgDetails;
|
||||||
|
flex: 0 0 64px;
|
||||||
|
font-size: 1.2em;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
|
||||||
|
> * + * {
|
||||||
|
margin-left: $interiorMargin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body.desktop & {
|
||||||
|
$transOutMs: 300ms;
|
||||||
|
flex-flow: column nowrap;
|
||||||
|
transition: background $transOutMs ease-in-out;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: $colorItemBgHov;
|
||||||
|
transition: $transIn;
|
||||||
|
|
||||||
|
.c-grid-item__type-icon {
|
||||||
|
filter: $colorKeyFilterHov;
|
||||||
|
transform: scale(1);
|
||||||
|
transition: $transInBounce;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> * {
|
||||||
|
margin: 0; // Reset from mobile
|
||||||
|
}
|
||||||
|
|
||||||
|
&__controls {
|
||||||
|
align-items: start;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
order: 1;
|
||||||
|
.c-info-button,
|
||||||
|
.c-pointer-icon { display: none; }
|
||||||
|
}
|
||||||
|
|
||||||
|
&__type-icon {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
font-size: floor($gridItemDesk / 3);
|
||||||
|
margin: $interiorMargin 22.5% $interiorMargin * 3 22.5%;
|
||||||
|
order: 2;
|
||||||
|
transform: scale(0.9);
|
||||||
|
transform-origin: center;
|
||||||
|
transition: all $transOutMs ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__details {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
justify-content: flex-end;
|
||||||
|
order: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__metadata {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
&__type {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
@include ellipsize();
|
||||||
|
}
|
||||||
|
|
||||||
|
&__item-count {
|
||||||
|
opacity: 0.7;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
inject: ['openmct', 'domainObject'],
|
||||||
|
data() {
|
||||||
|
var items = [],
|
||||||
|
unknownObjectType = {
|
||||||
|
definition: {
|
||||||
|
cssClass: 'icon-object-unknown',
|
||||||
|
name: 'Unknown Type'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var composition = this.openmct.composition.get(this.domainObject);
|
||||||
|
|
||||||
|
if (composition) {
|
||||||
|
|
||||||
|
composition.load().then((array) => {
|
||||||
|
if (Array.isArray(array)) {
|
||||||
|
array.forEach((model) => {
|
||||||
|
var type = this.openmct.types.get(model.type) || unknownObjectType;
|
||||||
|
|
||||||
|
items.push({
|
||||||
|
model: model,
|
||||||
|
type: type.definition,
|
||||||
|
isAlias: this.domainObject.identifier.key !== model.location
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
items: items
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
navigate(identifier) {
|
||||||
|
let currentLocation = this.openmct.router.currentLocation.path,
|
||||||
|
navigateToPath = `${currentLocation}/${identifier}`;
|
||||||
|
|
||||||
|
this.openmct.router.setPath(navigateToPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
215
src/plugins/folderView/components/ListView.vue
Normal file
215
src/plugins/folderView/components/ListView.vue
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
<template>
|
||||||
|
<div class="c-table c-table--sortable c-list-view">
|
||||||
|
<table class="c-table__body">
|
||||||
|
<thead class="c-table__header">
|
||||||
|
<tr>
|
||||||
|
<th class="is-sortable"
|
||||||
|
v-bind:class="[orderByField == 'name' ? 'is-sorting' : '', sortClass]"
|
||||||
|
@click="sortTrigger('name', 'asc')">
|
||||||
|
Name
|
||||||
|
</th>
|
||||||
|
<th class="is-sortable"
|
||||||
|
v-bind:class="[orderByField == 'type' ? 'is-sorting' : '', sortClass]"
|
||||||
|
@click="sortTrigger('type', 'asc')">
|
||||||
|
Type
|
||||||
|
</th>
|
||||||
|
<th class="is-sortable"
|
||||||
|
v-bind:class="[orderByField == 'createdDate' ? 'is-sorting' : '', sortClass]"
|
||||||
|
@click="sortTrigger('createdDate', 'desc')">
|
||||||
|
Created Date
|
||||||
|
</th>
|
||||||
|
<th class="is-sortable"
|
||||||
|
v-bind:class="[orderByField == 'updatedDate' ? 'is-sorting' : '', sortClass]"
|
||||||
|
@click="sortTrigger('updatedDate', 'desc')">
|
||||||
|
Updated Date
|
||||||
|
</th>
|
||||||
|
<th class="is-sortable"
|
||||||
|
v-bind:class="[orderByField == 'items' ? 'is-sorting' : '', sortClass]"
|
||||||
|
@click="sortTrigger('items', 'asc')">
|
||||||
|
Items
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr class="c-list-item"
|
||||||
|
v-for="(item,index) in sortedItems"
|
||||||
|
v-bind:key="index"
|
||||||
|
:class="{ 'is-alias': item.isAlias === true }"
|
||||||
|
@click="navigate(item.identifier)">
|
||||||
|
<td class="c-list-item__name">
|
||||||
|
<div class="c-list-item__type-icon" :class="(item.cssClass != undefined) ? item.cssClass : 'icon-object-unknown'"></div>
|
||||||
|
{{item.name}}
|
||||||
|
</td>
|
||||||
|
<td class="c-list-item__type">{{ item.type }}</td>
|
||||||
|
<td class="c-list-item__date-created">{{ formatTime(item.createdDate, 'YYYY-MM-DD HH:mm:ss:SSS') }}Z</td>
|
||||||
|
<td class="c-list-item__date-updated">{{ formatTime(item.updatedDate, 'YYYY-MM-DD HH:mm:ss:SSS') }}Z</td>
|
||||||
|
<td class="c-list-item__items">{{ item.items }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import "~styles/sass-base";
|
||||||
|
|
||||||
|
/******************************* LIST VIEW */
|
||||||
|
.c-list-view {
|
||||||
|
overflow-x: auto !important;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
tbody tr {
|
||||||
|
background: $colorListItemBg;
|
||||||
|
transition: $transOut;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.desktop & {
|
||||||
|
tbody tr {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: $colorListItemBgHov;
|
||||||
|
transition: $transIn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
$p: floor($interiorMargin * 1.5);
|
||||||
|
font-size: 1.1em;
|
||||||
|
padding-top: $p;
|
||||||
|
padding-bottom: $p;
|
||||||
|
|
||||||
|
&:not(.c-list-item__name) {
|
||||||
|
color: $colorItemFgDetails;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-list-item {
|
||||||
|
&__name {
|
||||||
|
@include ellipsize();
|
||||||
|
}
|
||||||
|
|
||||||
|
&__type-icon {
|
||||||
|
color: $colorKey;
|
||||||
|
display: inline-block;
|
||||||
|
width: 1em;
|
||||||
|
margin-right:$interiorMarginSm;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-alias {
|
||||||
|
// Object is an alias to an original.
|
||||||
|
[class*='__type-icon'] {
|
||||||
|
&:after {
|
||||||
|
color: $colorIconAlias;
|
||||||
|
content: $glyph-icon-link;
|
||||||
|
font-family: symbolsfont;
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
text-shadow: rgba(black, 0.5) 0 1px 2px;
|
||||||
|
top: auto; left: -1px; bottom: 1px; right: auto;
|
||||||
|
transform-origin: bottom left;
|
||||||
|
transform: scale(0.65);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/******************************* LIST ITEM */
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
inject: ['openmct', 'domainObject', 'Moment'],
|
||||||
|
data() {
|
||||||
|
var items = [],
|
||||||
|
unknownObjectType = {
|
||||||
|
definition: {
|
||||||
|
cssClass: 'icon-object-unknown',
|
||||||
|
name: 'Unknown Type'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
composition = this.openmct.composition.get(this.domainObject);
|
||||||
|
|
||||||
|
if (composition) {
|
||||||
|
|
||||||
|
composition.load().then((array) => {
|
||||||
|
if (Array.isArray(array)) {
|
||||||
|
array.forEach(model => {
|
||||||
|
var type = this.openmct.types.get(model.type) || unknownObjectType;
|
||||||
|
|
||||||
|
items.push({
|
||||||
|
name: model.name,
|
||||||
|
identifier: model.identifier.key,
|
||||||
|
type: type.definition.name,
|
||||||
|
isAlias: false,
|
||||||
|
cssClass: type.definition.cssClass,
|
||||||
|
createdDate: model.persisted,
|
||||||
|
updatedDate: model.modified,
|
||||||
|
items: model.composition ? model.composition.length : 0,
|
||||||
|
isAlias: this.domainObject.identifier.key !== model.location
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
items: items,
|
||||||
|
orderByField: 'name',
|
||||||
|
sortClass: 'asc',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
sortedItems () {
|
||||||
|
if (this.sortClass === 'asc') {
|
||||||
|
return this.items.sort(this.ascending.bind(this));
|
||||||
|
} else if (this.sortClass === 'desc') {
|
||||||
|
return this.items.sort(this.descending.bind(this));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
formatTime () {
|
||||||
|
return function (timestamp, format) {
|
||||||
|
return this.Moment(timestamp).format(format);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
navigate(identifier) {
|
||||||
|
let currentLocation = this.openmct.router.currentLocation.path,
|
||||||
|
navigateToPath = `${currentLocation}/${identifier}`;
|
||||||
|
|
||||||
|
this.openmct.router.setPath(navigateToPath);
|
||||||
|
},
|
||||||
|
sortTrigger(field, sortOrder) {
|
||||||
|
if (this.orderByField === field) {
|
||||||
|
this.sortClass = (this.sortClass === 'asc') ? 'desc' : 'asc';
|
||||||
|
} else {
|
||||||
|
this.sortClass = sortOrder;
|
||||||
|
}
|
||||||
|
this.orderByField = field;
|
||||||
|
},
|
||||||
|
ascending(first, second) {
|
||||||
|
if (first[this.orderByField] < second[this.orderByField]) {
|
||||||
|
return -1;
|
||||||
|
} else if (first[this.orderByField] > second[this.orderByField]) {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
descending(first, second) {
|
||||||
|
if (first[this.orderByField] > second[this.orderByField]) {
|
||||||
|
return -1;
|
||||||
|
} else if (first[this.orderByField] < second[this.orderByField]) {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
@ -20,35 +20,17 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
define([], function () {
|
define([
|
||||||
|
'./FolderGridView',
|
||||||
/**
|
'./FolderListView'
|
||||||
* Formatter for basic numbers. Provides basic support for non-UTC
|
], function (
|
||||||
* numbering systems
|
FolderGridView,
|
||||||
*
|
FolderListView
|
||||||
* @implements {Format}
|
) {
|
||||||
* @constructor
|
return function plugin() {
|
||||||
* @memberof platform/commonUI/formats
|
return function install(openmct) {
|
||||||
*/
|
openmct.objectViews.addProvider(new FolderGridView(openmct));
|
||||||
function NumberFormat() {
|
openmct.objectViews.addProvider(new FolderListView(openmct));
|
||||||
this.key = 'number';
|
|
||||||
}
|
|
||||||
|
|
||||||
NumberFormat.prototype.format = function (value) {
|
|
||||||
if (isNaN(value)) {
|
|
||||||
return '';
|
|
||||||
} else {
|
|
||||||
return '' + value;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
NumberFormat.prototype.parse = function (text) {
|
|
||||||
return parseFloat(text);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
NumberFormat.prototype.validate = function (text) {
|
|
||||||
return !isNaN(text);
|
|
||||||
};
|
|
||||||
|
|
||||||
return NumberFormat;
|
|
||||||
});
|
});
|
@ -51,64 +51,12 @@ define([
|
|||||||
function LocalTimeFormat() {
|
function LocalTimeFormat() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an appropriate time format based on the provided value and
|
|
||||||
* the threshold required.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
function getScaledFormat(d) {
|
|
||||||
var momentified = moment.utc(d);
|
|
||||||
/**
|
|
||||||
* Uses logic from d3 Time-Scales, v3 of the API. See
|
|
||||||
* https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Scales.md
|
|
||||||
*
|
|
||||||
* Licensed
|
|
||||||
*/
|
|
||||||
return [
|
|
||||||
[".SSS", function (m) {
|
|
||||||
return m.milliseconds();
|
|
||||||
}],
|
|
||||||
[":ss", function (m) {
|
|
||||||
return m.seconds();
|
|
||||||
}],
|
|
||||||
["hh:mma", function (m) {
|
|
||||||
return m.minutes();
|
|
||||||
}],
|
|
||||||
["hha", function (m) {
|
|
||||||
return m.hours();
|
|
||||||
}],
|
|
||||||
["ddd DD", function (m) {
|
|
||||||
return m.days() &&
|
|
||||||
m.date() !== 1;
|
|
||||||
}],
|
|
||||||
["MMM DD", function (m) {
|
|
||||||
return m.date() !== 1;
|
|
||||||
}],
|
|
||||||
["MMMM", function (m) {
|
|
||||||
return m.month();
|
|
||||||
}],
|
|
||||||
["YYYY", function () {
|
|
||||||
return true;
|
|
||||||
}]
|
|
||||||
].filter(function (row) {
|
|
||||||
return row[1](momentified);
|
|
||||||
})[0][0];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param value
|
* @param value
|
||||||
* @param {Scale} [scale] Optionally provides context to the
|
|
||||||
* format request, allowing for scale-appropriate formatting.
|
|
||||||
* @returns {string} the formatted date
|
* @returns {string} the formatted date
|
||||||
*/
|
*/
|
||||||
LocalTimeFormat.prototype.format = function (value, scale) {
|
LocalTimeFormat.prototype.format = function (value, scale) {
|
||||||
if (scale !== undefined) {
|
|
||||||
var scaledFormat = getScaledFormat(value, scale);
|
|
||||||
if (scaledFormat) {
|
|
||||||
return moment.utc(value).format(scaledFormat);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return moment(value).format(DATE_FORMAT);
|
return moment(value).format(DATE_FORMAT);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ define([], function () {
|
|||||||
this.timeFormat = 'local-format';
|
this.timeFormat = 'local-format';
|
||||||
this.durationFormat = 'duration';
|
this.durationFormat = 'duration';
|
||||||
|
|
||||||
this.isUTCBased = true;
|
this.isUTCBased = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return LocalTimeSystem;
|
return LocalTimeSystem;
|
||||||
|
@ -8,8 +8,7 @@
|
|||||||
<span>{{formatTime(entry.createdOn, 'HH:mm:ss')}}</span>
|
<span>{{formatTime(entry.createdOn, 'HH:mm:ss')}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="c-ne__content">
|
<div class="c-ne__content">
|
||||||
<!-- TODO: fix styling for c-input-inline when SCSS is merged and remove s-input-inline class here -->
|
<div class="c-ne__text c-input-inline"
|
||||||
<div class="c-ne__text c-input-inline s-input-inline"
|
|
||||||
contenteditable="true"
|
contenteditable="true"
|
||||||
ref="contenteditable"
|
ref="contenteditable"
|
||||||
v-on:blur="textBlur($event, entry.id)"
|
v-on:blur="textBlur($event, entry.id)"
|
||||||
@ -28,8 +27,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="c-ne__local-controls--hidden">
|
<div class="c-ne__local-controls--hidden">
|
||||||
<a class="c-click-icon icon-trash"
|
<button class="c-click-icon icon-trash"
|
||||||
title="Delete this entry"
|
title="Delete this entry"
|
||||||
v-on:click="deleteEntry"></a>
|
v-on:click="deleteEntry"></button>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
29
src/plugins/notebook/res/templates/snapshotTemplate.html
Normal file
29
src/plugins/notebook/res/templates/snapshotTemplate.html
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<div class="u-contents">
|
||||||
|
|
||||||
|
<div class="t-snapshot abs l-view-header">
|
||||||
|
<div class="abs object-browse-bar l-flex-row">
|
||||||
|
<div class="left flex-elem l-flex-row grows">
|
||||||
|
<div class="object-header flex-elem l-flex-row grows">
|
||||||
|
<div class="type-icon flex-elem embed-icon holder" v-bind:class="embed.cssClass"></div>
|
||||||
|
<div class="title-label flex-elem holder flex-can-shrink">{{embed.name}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="btn-bar right l-flex-row flex-elem flex-justify-end flex-fixed">
|
||||||
|
<div class="flex-elem holder flex-can-shrink s-snapshot-datetime">
|
||||||
|
SNAPSHOT {{formatTime(embed.createdOn, 'YYYY-MM-DD HH:mm:ss')}}
|
||||||
|
</div>
|
||||||
|
<a class="s-button icon-pencil" title="Annotate">
|
||||||
|
<span class="title-label">Annotate</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="abs object-holder t-image-holder s-image-holder">
|
||||||
|
<div
|
||||||
|
class="image-main s-image-main"
|
||||||
|
v-bind:style="{ backgroundImage: 'url(' + embed.snapshot.src + ')' }">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -24,11 +24,15 @@ define([
|
|||||||
'moment',
|
'moment',
|
||||||
'zepto',
|
'zepto',
|
||||||
'../utils/SnapshotOverlay',
|
'../utils/SnapshotOverlay',
|
||||||
|
'../../res/templates/snapshotTemplate.html',
|
||||||
|
'vue'
|
||||||
],
|
],
|
||||||
function (
|
function (
|
||||||
Moment,
|
Moment,
|
||||||
$,
|
$,
|
||||||
SnapshotOverlay
|
SnapshotOverlay,
|
||||||
|
SnapshotTemplate,
|
||||||
|
Vue
|
||||||
) {
|
) {
|
||||||
function EmbedController (openmct, domainObject) {
|
function EmbedController (openmct, domainObject) {
|
||||||
this.openmct = openmct;
|
this.openmct = openmct;
|
||||||
@ -53,11 +57,24 @@ function (
|
|||||||
};
|
};
|
||||||
|
|
||||||
EmbedController.prototype.openSnapshot = function () {
|
EmbedController.prototype.openSnapshot = function () {
|
||||||
if (!this.snapshotOverlay) {
|
var self = this,
|
||||||
this.snapShotOverlay = new SnapshotOverlay(this.embed, this.formatTime);
|
snapshot = new Vue({
|
||||||
} else {
|
template: SnapshotTemplate,
|
||||||
this.snapShotOverlay = undefined;
|
data: function () {
|
||||||
|
return {
|
||||||
|
embed: self.embed
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
formatTime: self.formatTime
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function onDestroyCallback() {
|
||||||
|
snapshot.$destroy(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.openmct.OverlayService.show(snapshot.$mount().$el, {onDestroy: onDestroyCallback, cssClass: 'l-large-view'});
|
||||||
};
|
};
|
||||||
|
|
||||||
EmbedController.prototype.formatTime = function (unixTime, timeFormat) {
|
EmbedController.prototype.formatTime = function (unixTime, timeFormat) {
|
||||||
|
@ -60,6 +60,7 @@ function (
|
|||||||
this.container = container;
|
this.container = container;
|
||||||
|
|
||||||
var notebookEmbed = {
|
var notebookEmbed = {
|
||||||
|
inject:['openmct'],
|
||||||
props:['embed', 'entry'],
|
props:['embed', 'entry'],
|
||||||
template: EmbedTemplate,
|
template: EmbedTemplate,
|
||||||
data: embedController.exposedData,
|
data: embedController.exposedData,
|
||||||
@ -80,6 +81,7 @@ function (
|
|||||||
|
|
||||||
var notebookVue = Vue.extend({
|
var notebookVue = Vue.extend({
|
||||||
template: NotebookTemplate,
|
template: NotebookTemplate,
|
||||||
|
provide: {openmct: self.openmct},
|
||||||
components: {
|
components: {
|
||||||
'notebook-entry': entryComponent,
|
'notebook-entry': entryComponent,
|
||||||
'search': search.default
|
'search': search.default
|
||||||
|
@ -35,7 +35,8 @@ define([
|
|||||||
'./telemetryTable/plugin',
|
'./telemetryTable/plugin',
|
||||||
'./staticRootPlugin/plugin',
|
'./staticRootPlugin/plugin',
|
||||||
'./notebook/plugin',
|
'./notebook/plugin',
|
||||||
'./displayLayout/plugin'
|
'./displayLayout/plugin',
|
||||||
|
'./folderView/plugin'
|
||||||
], function (
|
], function (
|
||||||
_,
|
_,
|
||||||
UTCTimeSystem,
|
UTCTimeSystem,
|
||||||
@ -51,7 +52,8 @@ define([
|
|||||||
TelemetryTablePlugin,
|
TelemetryTablePlugin,
|
||||||
StaticRootPlugin,
|
StaticRootPlugin,
|
||||||
Notebook,
|
Notebook,
|
||||||
DisplayLayoutPlugin
|
DisplayLayoutPlugin,
|
||||||
|
FolderView
|
||||||
) {
|
) {
|
||||||
var bundleMap = {
|
var bundleMap = {
|
||||||
LocalStorage: 'platform/persistence/local',
|
LocalStorage: 'platform/persistence/local',
|
||||||
@ -103,7 +105,7 @@ define([
|
|||||||
*/
|
*/
|
||||||
plugins.AutoflowView = AutoflowPlugin;
|
plugins.AutoflowView = AutoflowPlugin;
|
||||||
|
|
||||||
plugins.Conductor = TimeConductorPlugin;
|
plugins.Conductor = TimeConductorPlugin.default;
|
||||||
|
|
||||||
plugins.CouchDB = function (url) {
|
plugins.CouchDB = function (url) {
|
||||||
return function (openmct) {
|
return function (openmct) {
|
||||||
@ -162,6 +164,7 @@ define([
|
|||||||
plugins.URLIndicator = URLIndicatorPlugin;
|
plugins.URLIndicator = URLIndicatorPlugin;
|
||||||
plugins.Notebook = Notebook;
|
plugins.Notebook = Notebook;
|
||||||
plugins.DisplayLayout = DisplayLayoutPlugin.default;
|
plugins.DisplayLayout = DisplayLayoutPlugin.default;
|
||||||
|
plugins.FolderView = FolderView;
|
||||||
|
|
||||||
return plugins;
|
return plugins;
|
||||||
});
|
});
|
||||||
|
@ -264,7 +264,7 @@ define([
|
|||||||
this.applyStyle($('#widget', this.domElement), activeRule.getProperty('style'));
|
this.applyStyle($('#widget', this.domElement), activeRule.getProperty('style'));
|
||||||
$('#widget', this.domElement).prop('title', activeRule.getProperty('message'));
|
$('#widget', this.domElement).prop('title', activeRule.getProperty('message'));
|
||||||
$('#widgetLabel', this.domElement).html(activeRule.getProperty('label'));
|
$('#widgetLabel', this.domElement).html(activeRule.getProperty('label'));
|
||||||
$('#widgetLabel', this.domElement).removeClass().addClass('label widget-label ' + activeRule.getProperty('icon'));
|
$('#widgetLabel', this.domElement).removeClass().addClass('label widget-label c-summary-widget__label ' + activeRule.getProperty('icon'));
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -18,7 +18,7 @@ define([
|
|||||||
this.widget.title = datum.message;
|
this.widget.title = datum.message;
|
||||||
this.label.title = datum.message;
|
this.label.title = datum.message;
|
||||||
this.label.innerHTML = datum.ruleLabel;
|
this.label.innerHTML = datum.ruleLabel;
|
||||||
this.label.className = 'label widget-label ' + datum.icon;
|
this.label.className = 'label widget-label c-summary-widget__label ' + datum.icon;
|
||||||
};
|
};
|
||||||
|
|
||||||
SummaryWidgetView.prototype.render = function () {
|
SummaryWidgetView.prototype.render = function () {
|
||||||
|
@ -8,7 +8,7 @@ define([
|
|||||||
objectUtils
|
objectUtils
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
const DEFAULT_VIEW_PRIORITY = 100;
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@ -34,7 +34,11 @@ define([
|
|||||||
},
|
},
|
||||||
editable: true,
|
editable: true,
|
||||||
priority: function (domainObject) {
|
priority: function (domainObject) {
|
||||||
return 1;
|
if (domainObject.type === 'summary-widget') {
|
||||||
|
return Number.MAX_VALUE;
|
||||||
|
} else {
|
||||||
|
return DEFAULT_VIEW_PRIORITY;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<div class="w-summary-widget s-status-no-data">
|
<div class="w-summary-widget s-status-no-data c-widget-wrapper u-contents">
|
||||||
<a class="t-summary-widget l-summary-widget s-summary-widget labeled">
|
<a class="t-summary-widget c-button c-summary-widget u-links u-fills-container">
|
||||||
<span class="label widget-label">Loading...</span>
|
<span class="label widget-label c-summary-widget__label">Loading...</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -33,30 +33,6 @@ define([
|
|||||||
) {
|
) {
|
||||||
|
|
||||||
function TableConfigurationViewProvider(openmct) {
|
function TableConfigurationViewProvider(openmct) {
|
||||||
let instantiateService;
|
|
||||||
|
|
||||||
function isBeingEdited(object) {
|
|
||||||
let oldStyleObject = getOldStyleObject(object);
|
|
||||||
|
|
||||||
return oldStyleObject.hasCapability('editor') &&
|
|
||||||
oldStyleObject.getCapability('editor').inEditContext();
|
|
||||||
}
|
|
||||||
|
|
||||||
function getOldStyleObject(object) {
|
|
||||||
let oldFormatModel = objectUtils.toOldFormat(object);
|
|
||||||
let oldFormatId = objectUtils.makeKeyString(object.identifier);
|
|
||||||
|
|
||||||
return instantiate(oldFormatModel, oldFormatId);
|
|
||||||
}
|
|
||||||
|
|
||||||
function instantiate(model, id) {
|
|
||||||
if (!instantiateService) {
|
|
||||||
instantiateService = openmct.$injector.get('instantiate');
|
|
||||||
}
|
|
||||||
return instantiateService(model, id);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
key: 'table-configuration',
|
key: 'table-configuration',
|
||||||
name: 'Telemetry Table Configuration',
|
name: 'Telemetry Table Configuration',
|
||||||
@ -65,8 +41,7 @@ define([
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
let object = selection[0].context.item;
|
let object = selection[0].context.item;
|
||||||
return object.type === 'table' &&
|
return object.type === 'table';
|
||||||
isBeingEdited(object);
|
|
||||||
},
|
},
|
||||||
view: function (selection) {
|
view: function (selection) {
|
||||||
let component;
|
let component;
|
||||||
@ -86,7 +61,7 @@ define([
|
|||||||
el: element
|
el: element
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
destroy: function (element) {
|
destroy: function () {
|
||||||
component.$destroy();
|
component.$destroy();
|
||||||
component = undefined;
|
component = undefined;
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,12 @@ define(function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getFormattedValue(telemetryDatum) {
|
getFormattedValue(telemetryDatum) {
|
||||||
return this.formatter.format(telemetryDatum);
|
let formattedValue = this.formatter.format(telemetryDatum);
|
||||||
|
if (typeof formattedValue !== 'string') {
|
||||||
|
return formattedValue.toString();
|
||||||
|
} else {
|
||||||
|
return formattedValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -23,10 +23,10 @@
|
|||||||
define([
|
define([
|
||||||
'lodash',
|
'lodash',
|
||||||
'EventEmitter',
|
'EventEmitter',
|
||||||
'./TelemetryTableColumn',
|
'./TelemetryTableColumn'
|
||||||
], function (_, EventEmitter, TelemetryTableColumn) {
|
], function (_, EventEmitter, TelemetryTableColumn) {
|
||||||
|
|
||||||
class TelemetryTableConfiguration extends EventEmitter{
|
class TelemetryTableConfiguration extends EventEmitter {
|
||||||
constructor(domainObject, openmct) {
|
constructor(domainObject, openmct) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
@ -37,6 +37,8 @@ define([
|
|||||||
this.addColumnsForObject = this.addColumnsForObject.bind(this);
|
this.addColumnsForObject = this.addColumnsForObject.bind(this);
|
||||||
this.removeColumnsForObject = this.removeColumnsForObject.bind(this);
|
this.removeColumnsForObject = this.removeColumnsForObject.bind(this);
|
||||||
this.objectMutated = this.objectMutated.bind(this);
|
this.objectMutated = this.objectMutated.bind(this);
|
||||||
|
//Make copy of configuration, otherwise change detection is impossible if shared instance is being modified.
|
||||||
|
this.oldConfiguration = JSON.parse(JSON.stringify(this.getConfiguration()));
|
||||||
|
|
||||||
this.unlistenFromMutation = openmct.objects.observe(domainObject, '*', this.objectMutated);
|
this.unlistenFromMutation = openmct.objects.observe(domainObject, '*', this.objectMutated);
|
||||||
}
|
}
|
||||||
@ -56,11 +58,11 @@ define([
|
|||||||
* @param {*} object
|
* @param {*} object
|
||||||
*/
|
*/
|
||||||
objectMutated(object) {
|
objectMutated(object) {
|
||||||
let oldConfiguration = this.domainObject.configuration;
|
|
||||||
|
|
||||||
//Synchronize domain object reference. Duplicate object otherwise change detection becomes impossible.
|
//Synchronize domain object reference. Duplicate object otherwise change detection becomes impossible.
|
||||||
this.domainObject = JSON.parse(JSON.stringify(object));
|
this.domainObject = object;
|
||||||
if (!_.eq(object.configuration, oldConfiguration)){
|
if (!_.eq(object.configuration, this.oldConfiguration)) {
|
||||||
|
//Make copy of configuration, otherwise change detection is impossible if shared instance is being modified.
|
||||||
|
this.oldConfiguration = JSON.parse(JSON.stringify(this.getConfiguration()));
|
||||||
this.emit('change', object.configuration);
|
this.emit('change', object.configuration);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,7 @@ define([], function () {
|
|||||||
|
|
||||||
getFormattedValue(key) {
|
getFormattedValue(key) {
|
||||||
let column = this.columns[key];
|
let column = this.columns[key];
|
||||||
return column.getFormattedValue(this.datum[key]);
|
return column && column.getFormattedValue(this.datum[key]);
|
||||||
}
|
}
|
||||||
|
|
||||||
getRowLimitClass() {
|
getRowLimitClass() {
|
||||||
|
@ -1,14 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="grid-properties">
|
<div class="c-properties" v-if="isEditing">
|
||||||
<!--form class="form" -->
|
<div class="c-properties__header">Table Columns</div>
|
||||||
<ul class="l-inspector-part">
|
<ul class="c-properties__section">
|
||||||
<h2>Table Columns</h2>
|
<li class="c-properties__row" v-for="(title, key) in headers">
|
||||||
<li class="grid-row" v-for="(title, key) in headers">
|
<div class="c-properties__label" title="Show or Hide Column"><label :for="key + 'ColumnControl'">{{title}}</label></div>
|
||||||
<div class="grid-cell label" title="Show or Hide Column"><label :for="key + 'ColumnControl'">{{title}}</label></div>
|
<div class="c-properties__value"><input type="checkbox" :id="key + 'ColumnControl'" :checked="configuration.hiddenColumns[key] !== true" @change="toggleColumn(key)"></div>
|
||||||
<div class="grid-cell value"><input type="checkbox" :id="key + 'ColumnControl'" :checked="configuration.hiddenColumns[key] !== true" @change="toggleColumn(key)"></div>
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<!--/form -->
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -21,6 +19,7 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
headers: {},
|
headers: {},
|
||||||
|
isEditing: this.openmct.editor.isEditing(),
|
||||||
configuration: this.tableConfiguration.getConfiguration()
|
configuration: this.tableConfiguration.getConfiguration()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -41,11 +40,14 @@ export default {
|
|||||||
removeObject(objectIdentifier) {
|
removeObject(objectIdentifier) {
|
||||||
this.tableConfiguration.removeColumnsForObject(objectIdentifier, true);
|
this.tableConfiguration.removeColumnsForObject(objectIdentifier, true);
|
||||||
this.updateHeaders(this.tableConfiguration.getAllHeaders());
|
this.updateHeaders(this.tableConfiguration.getAllHeaders());
|
||||||
|
},
|
||||||
|
toggleEdit(isEditing) {
|
||||||
|
this.isEditing = isEditing;
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.unlisteners = [];
|
this.unlisteners = [];
|
||||||
|
this.openmct.editor.on('isEditing', this.toggleEdit);
|
||||||
let compositionCollection = this.openmct.composition.get(this.tableConfiguration.domainObject);
|
let compositionCollection = this.openmct.composition.get(this.tableConfiguration.domainObject);
|
||||||
|
|
||||||
compositionCollection.load()
|
compositionCollection.load()
|
||||||
@ -62,6 +64,7 @@ export default {
|
|||||||
},
|
},
|
||||||
destroyed() {
|
destroyed() {
|
||||||
this.tableConfiguration.destroy();
|
this.tableConfiguration.destroy();
|
||||||
|
this.openmct.editor.off('isEditing', this.toggleEdit);
|
||||||
this.unlisteners.forEach((unlisten) => unlisten());
|
this.unlisteners.forEach((unlisten) => unlisten());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,9 @@ export default {
|
|||||||
columnWidths: {
|
columnWidths: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: false,
|
required: false,
|
||||||
default: [],
|
default() {
|
||||||
|
return [];
|
||||||
|
},
|
||||||
},
|
},
|
||||||
rowIndex: {
|
rowIndex: {
|
||||||
type: Number,
|
type: Number,
|
||||||
@ -48,10 +50,6 @@ export default {
|
|||||||
type: Number,
|
type: Number,
|
||||||
required: false,
|
required: false,
|
||||||
default: 0
|
default: 0
|
||||||
},
|
|
||||||
configuration: {
|
|
||||||
type: Object,
|
|
||||||
required: true
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<!-- Headers table -->
|
<!-- Headers table -->
|
||||||
<div class="c-table__headers-w js-table__headers-w">
|
<div class="c-telemetry-table__headers-w js-table__headers-w">
|
||||||
<table class="c-table__headers c-telemetry-table__headers"
|
<table class="c-table__headers c-telemetry-table__headers"
|
||||||
:style="{ 'max-width': totalWidth + 'px'}">
|
:style="{ 'max-width': totalWidth + 'px'}">
|
||||||
<thead>
|
<thead>
|
||||||
@ -68,48 +68,22 @@
|
|||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import "~styles/sass-base";
|
@import "~styles/sass-base";
|
||||||
|
@import "~styles/table";
|
||||||
|
|
||||||
.c-table {
|
.c-telemetry-table {
|
||||||
// Can be used by any type of table, scrolling, LAD, etc.
|
// Table that displays telemetry in a scrolling body area
|
||||||
$min-w: 50px;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-flow: column nowrap;
|
|
||||||
justify-content: flex-start;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: absolute;
|
|
||||||
top: 0; right: 0; bottom: 0; left: 0;
|
|
||||||
|
|
||||||
&__control-bar,
|
|
||||||
&__headers-w {
|
|
||||||
// Don't allow top level elements to grow or shrink
|
|
||||||
flex: 0 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/******************************* ELEMENTS */
|
|
||||||
th, td {
|
th, td {
|
||||||
display: block;
|
display: block;
|
||||||
flex: 1 0 auto;
|
flex: 1 0 auto;
|
||||||
font-size: 0.7rem; // TEMP LEGACY TODO: refactor this when __main-container font-size is dealt with
|
|
||||||
white-space: nowrap;
|
|
||||||
min-width: $min-w;
|
|
||||||
padding: $tabularTdPadTB $tabularTdPadLR;
|
|
||||||
vertical-align: middle; // This is crucial to hiding f**king 4px height injected by browser by default
|
vertical-align: middle; // This is crucial to hiding f**king 4px height injected by browser by default
|
||||||
}
|
}
|
||||||
|
|
||||||
td {
|
|
||||||
color: $colorTelemFresh;
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__control-bar {
|
|
||||||
margin-bottom: $interiorMarginSm;
|
|
||||||
}
|
|
||||||
|
|
||||||
/******************************* WRAPPERS */
|
/******************************* WRAPPERS */
|
||||||
&__headers-w {
|
&__headers-w {
|
||||||
// Wraps __headers table
|
// Wraps __headers table
|
||||||
background: $colorTabHeaderBg;
|
flex: 0 0 auto;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,65 +109,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__body {
|
|
||||||
// A table
|
|
||||||
tr {
|
|
||||||
&:not(:first-child) {
|
|
||||||
border-top: 1px solid $colorTabBorder;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/******************************* MODIFIERS */
|
|
||||||
&--filterable {
|
|
||||||
// TODO: discuss using the search.vue custom control here
|
|
||||||
|
|
||||||
.l-filter {
|
|
||||||
input[type="text"],
|
|
||||||
input[type="search"] {
|
|
||||||
$p: 20px;
|
|
||||||
transition: padding 200ms ease-in-out;
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding-right: $p; // Fend off from icon
|
|
||||||
padding-left: $p; // Fend off from icon
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
&.active {
|
|
||||||
// When user has typed something, hide the icon and collapse left padding
|
|
||||||
&:before {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
input[type="text"],
|
|
||||||
input[type="search"] {
|
|
||||||
padding-left: $interiorMargin;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&--sortable {
|
|
||||||
.is-sorting {
|
|
||||||
&:after {
|
|
||||||
color: $colorIconLink;
|
|
||||||
content: $glyph-icon-arrow-tall-up;
|
|
||||||
font-family: symbolsfont;
|
|
||||||
font-size: 8px;
|
|
||||||
display: inline-block;
|
|
||||||
margin-left: $interiorMarginSm;
|
|
||||||
}
|
|
||||||
&.desc:after {
|
|
||||||
content: $glyph-icon-arrow-tall-down;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.is-sortable {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.c-telemetry-table {
|
|
||||||
// Table that displays telemetry in a scrolling body area
|
|
||||||
|
|
||||||
/******************************* ELEMENTS */
|
/******************************* ELEMENTS */
|
||||||
&__scroll-forcer {
|
&__scroll-forcer {
|
||||||
// Force horz scroll when needed; width set via JS
|
// Force horz scroll when needed; width set via JS
|
||||||
@ -251,10 +166,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.c-table__control-bar {
|
|
||||||
margin-bottom: $interiorMarginSm;
|
|
||||||
}
|
|
||||||
|
|
||||||
/******************************* LEGACY */
|
/******************************* LEGACY */
|
||||||
.s-status-taking-snapshot,
|
.s-status-taking-snapshot,
|
||||||
.overlay.snapshot {
|
.overlay.snapshot {
|
||||||
|
436
src/plugins/timeConductor/Conductor.vue
Normal file
436
src/plugins/timeConductor/Conductor.vue
Normal file
@ -0,0 +1,436 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT Web, Copyright (c) 2014-2018, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT Web includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
<template>
|
||||||
|
<div class="c-conductor"
|
||||||
|
:class="[isFixed ? 'is-fixed-mode' : 'is-realtime-mode', panning ? 'status-panning' : '']">
|
||||||
|
<form class="u-contents" ref="conductorForm"
|
||||||
|
@submit="isFixed ? setBoundsFromView($event) : setOffsetsFromView($event)">
|
||||||
|
|
||||||
|
<ConductorModeIcon class="c-conductor__mode-icon"></ConductorModeIcon>
|
||||||
|
|
||||||
|
<div class="c-ctrl-wrapper c-conductor-input c-conductor__start-fixed"
|
||||||
|
v-if="isFixed">
|
||||||
|
<!-- Fixed start -->
|
||||||
|
<div class="c-conductor__start-fixed__label">Start</div>
|
||||||
|
<input class="c-input--datetime"
|
||||||
|
type="text" autocorrect="off" spellcheck="false"
|
||||||
|
ref="startDate"
|
||||||
|
v-model="formattedBounds.start"
|
||||||
|
@change="validateBounds('start', $event.target); setBoundsFromView()" />
|
||||||
|
<date-picker
|
||||||
|
:default-date-time="formattedBounds.start"
|
||||||
|
:formatter="timeFormatter"
|
||||||
|
@date-selected="startDateSelected"></date-picker>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="c-ctrl-wrapper c-conductor-input c-conductor__start-delta"
|
||||||
|
v-if="!isFixed">
|
||||||
|
<!-- RT start -->
|
||||||
|
<div class="c-direction-indicator icon-minus"></div>
|
||||||
|
<input class="c-input--hrs-min-sec"
|
||||||
|
type="text" autocorrect="off"
|
||||||
|
spellcheck="false"
|
||||||
|
v-model="offsets.start"
|
||||||
|
@change="validateOffsets($event); setOffsetsFromView()">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="c-ctrl-wrapper c-conductor-input c-conductor__end-fixed">
|
||||||
|
<!-- Fixed end and RT 'last update' display -->
|
||||||
|
<div class="c-conductor__end-fixed__label">
|
||||||
|
{{ isFixed ? 'End' : 'Updated' }}
|
||||||
|
</div>
|
||||||
|
<input class="c-input--datetime"
|
||||||
|
type="text" autocorrect="off" spellcheck="false"
|
||||||
|
v-model="formattedBounds.end"
|
||||||
|
:disabled="!isFixed"
|
||||||
|
ref="endDate"
|
||||||
|
@change="validateBounds('end', $event.target); setBoundsFromView()">
|
||||||
|
<date-picker
|
||||||
|
class="c-ctrl-wrapper--menus-left"
|
||||||
|
:default-date-time="formattedBounds.end"
|
||||||
|
:formatter="timeFormatter"
|
||||||
|
@date-selected="endDateSelected"
|
||||||
|
v-if="isFixed"></date-picker>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="c-ctrl-wrapper c-conductor-input c-conductor__end-delta"
|
||||||
|
v-if="!isFixed">
|
||||||
|
<!-- RT end -->
|
||||||
|
<div class="c-direction-indicator icon-plus"></div>
|
||||||
|
<input class="c-input--hrs-min-sec"
|
||||||
|
type="text"
|
||||||
|
autocorrect="off"
|
||||||
|
spellcheck="false"
|
||||||
|
v-model="offsets.end"
|
||||||
|
@change="validateOffsets($event); setOffsetsFromView()">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<conductor-axis
|
||||||
|
class="c-conductor__ticks"
|
||||||
|
:bounds="rawBounds"
|
||||||
|
@panAxis="setViewFromBounds"></conductor-axis>
|
||||||
|
<div class="c-conductor__controls">
|
||||||
|
<!-- Mode, time system menu buttons and duration slider -->
|
||||||
|
<ConductorMode class="c-conductor__mode-select"></ConductorMode>
|
||||||
|
<ConductorTimeSystem class="c-conductor__time-system-select"></ConductorTimeSystem>
|
||||||
|
</div>
|
||||||
|
<input type="submit" class="invisible">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import "~styles/sass-base";
|
||||||
|
|
||||||
|
/*********************************************** CONDUCTOR LAYOUT */
|
||||||
|
.c-conductor {
|
||||||
|
display: grid;
|
||||||
|
grid-column-gap: $interiorMargin;
|
||||||
|
grid-row-gap: $interiorMargin;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
// Default: fixed mode, desktop
|
||||||
|
grid-template-rows: 1fr 1fr;
|
||||||
|
grid-template-columns: 20px auto 1fr auto;
|
||||||
|
grid-template-areas:
|
||||||
|
"tc-mode-icon tc-start tc-ticks tc-end"
|
||||||
|
"tc-controls tc-controls tc-controls tc-controls";
|
||||||
|
|
||||||
|
&__mode-icon {
|
||||||
|
grid-area: tc-mode-icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__start-fixed,
|
||||||
|
&__start-delta {
|
||||||
|
grid-area: tc-start;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__end-fixed,
|
||||||
|
&__end-delta {
|
||||||
|
grid-area: tc-end;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__ticks {
|
||||||
|
grid-area: tc-ticks;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__controls {
|
||||||
|
grid-area: tc-controls;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
> * + * {
|
||||||
|
margin-left: $interiorMargin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[class*='-delta'] {
|
||||||
|
&:before {
|
||||||
|
content: $glyph-icon-clock;
|
||||||
|
font-family: symbolsfont;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-realtime-mode {
|
||||||
|
grid-template-columns: 20px auto 1fr auto auto;
|
||||||
|
grid-template-areas:
|
||||||
|
"tc-mode-icon tc-start tc-ticks tc-updated tc-end"
|
||||||
|
"tc-controls tc-controls tc-controls tc-controls tc-controls";
|
||||||
|
|
||||||
|
.c-conductor__end-fixed {
|
||||||
|
grid-area: tc-updated;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body.phone.portrait & {
|
||||||
|
grid-row-gap: $interiorMargin;
|
||||||
|
grid-template-rows: auto auto auto;
|
||||||
|
grid-template-columns: 20px auto auto;
|
||||||
|
|
||||||
|
&__mode-icon {
|
||||||
|
grid-row: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__ticks,
|
||||||
|
&__zoom {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-fixed-mode {
|
||||||
|
[class*='__start-fixed'],
|
||||||
|
[class*='__end-fixed'] {
|
||||||
|
[class*='__label'] {
|
||||||
|
// Start and end are in separate columns; make the labels line up
|
||||||
|
width: 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[class*='__end-input'] {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
grid-template-areas:
|
||||||
|
"tc-mode-icon tc-start tc-start"
|
||||||
|
"tc-mode-icon tc-end tc-end"
|
||||||
|
"tc-mode-icon tc-controls tc-controls";
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-realtime-mode {
|
||||||
|
grid-template-areas:
|
||||||
|
"tc-mode-icon tc-start tc-updated"
|
||||||
|
"tc-mode-icon tc-end tc-end"
|
||||||
|
"tc-mode-icon tc-controls tc-controls";
|
||||||
|
|
||||||
|
.c-conductor__end-fixed {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-conductor-input {
|
||||||
|
color: $colorInputFg;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
|
||||||
|
> * + * {
|
||||||
|
margin-left: $interiorMarginSm;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
// Realtime-mode clock icon symbol
|
||||||
|
margin-right: $interiorMarginSm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-direction-indicator {
|
||||||
|
// Holds realtime-mode + and - symbols
|
||||||
|
font-size: 0.7em;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:invalid {
|
||||||
|
background: rgba($colorFormInvalid, 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-realtime-mode {
|
||||||
|
button {
|
||||||
|
@include themedButton($colorTimeBg);
|
||||||
|
color: $colorTimeFg;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: $colorTimeHov !important;
|
||||||
|
color: $colorTimeFg !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-conductor-input {
|
||||||
|
&:before {
|
||||||
|
color: $colorTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-conductor__end-fixed {
|
||||||
|
// Displays last RT udpate
|
||||||
|
color: $colorTime;
|
||||||
|
|
||||||
|
input {
|
||||||
|
// Remove input look
|
||||||
|
background: none;
|
||||||
|
box-shadow: none;
|
||||||
|
color: $colorTime;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import moment from 'moment';
|
||||||
|
import ConductorMode from './ConductorMode.vue';
|
||||||
|
import ConductorTimeSystem from './ConductorTimeSystem.vue';
|
||||||
|
import DatePicker from './DatePicker.vue';
|
||||||
|
import ConductorAxis from './ConductorAxis.vue';
|
||||||
|
import ConductorModeIcon from './ConductorModeIcon.vue';
|
||||||
|
|
||||||
|
const DEFAULT_DURATION_FORMATTER = 'duration';
|
||||||
|
const SECONDS = 1000;
|
||||||
|
const DAYS = 24 * 60 * 60 * SECONDS;
|
||||||
|
const YEARS = 365 * DAYS;
|
||||||
|
|
||||||
|
const RESIZE_POLL_INTERVAL = 200;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
inject: ['openmct', 'configuration'],
|
||||||
|
components: {
|
||||||
|
ConductorMode,
|
||||||
|
ConductorTimeSystem,
|
||||||
|
DatePicker,
|
||||||
|
ConductorAxis,
|
||||||
|
ConductorModeIcon
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
let bounds = this.openmct.time.bounds();
|
||||||
|
let offsets = this.openmct.time.clockOffsets();
|
||||||
|
let timeSystem = this.openmct.time.timeSystem();
|
||||||
|
let timeFormatter = this.getFormatter(timeSystem.timeFormat);
|
||||||
|
let durationFormatter = this.getFormatter(timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
||||||
|
|
||||||
|
return {
|
||||||
|
timeFormatter: timeFormatter,
|
||||||
|
durationFormatter: durationFormatter,
|
||||||
|
offsets: {
|
||||||
|
start: offsets && durationFormatter.format(Math.abs(offsets.start)),
|
||||||
|
end: offsets && durationFormatter.format(Math.abs(offsets.end)),
|
||||||
|
},
|
||||||
|
formattedBounds: {
|
||||||
|
start: timeFormatter.format(bounds.start),
|
||||||
|
end: timeFormatter.format(bounds.end)
|
||||||
|
},
|
||||||
|
rawBounds: {
|
||||||
|
start: bounds.start,
|
||||||
|
end: bounds.end
|
||||||
|
},
|
||||||
|
isFixed: this.openmct.time.clock() === undefined,
|
||||||
|
isUTCBased: timeSystem.isUTCBased,
|
||||||
|
showDatePicker: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
setTimeSystem(timeSystem) {
|
||||||
|
this.timeFormatter = this.getFormatter(timeSystem.timeFormat);
|
||||||
|
this.durationFormatter = this.getFormatter(
|
||||||
|
timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
||||||
|
|
||||||
|
this.isUTCBased = timeSystem.isUTCBased;
|
||||||
|
},
|
||||||
|
setOffsetsFromView($event) {
|
||||||
|
if (this.$refs.conductorForm.checkValidity()){
|
||||||
|
let startOffset = 0 - this.durationFormatter.parse(this.offsets.start);
|
||||||
|
let endOffset = this.durationFormatter.parse(this.offsets.end);
|
||||||
|
|
||||||
|
this.openmct.time.clockOffsets({
|
||||||
|
start: startOffset,
|
||||||
|
end: endOffset
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if ($event) {
|
||||||
|
$event.preventDefault();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setBoundsFromView($event) {
|
||||||
|
if (this.$refs.conductorForm.checkValidity()){
|
||||||
|
let start = this.timeFormatter.parse(this.formattedBounds.start);
|
||||||
|
let end = this.timeFormatter.parse(this.formattedBounds.end);
|
||||||
|
|
||||||
|
this.openmct.time.bounds({
|
||||||
|
start: start,
|
||||||
|
end: end
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if ($event) {
|
||||||
|
$event.preventDefault();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setViewFromClock(clock) {
|
||||||
|
this.isFixed = clock === undefined;
|
||||||
|
},
|
||||||
|
setViewFromBounds(bounds) {
|
||||||
|
this.formattedBounds.start = this.timeFormatter.format(bounds.start);
|
||||||
|
this.formattedBounds.end = this.timeFormatter.format(bounds.end);
|
||||||
|
this.rawBounds.start = bounds.start;
|
||||||
|
this.rawBounds.end = bounds.end;
|
||||||
|
},
|
||||||
|
setViewFromOffsets(offsets) {
|
||||||
|
this.offsets.start = this.durationFormatter.format(Math.abs(offsets.start));
|
||||||
|
this.offsets.end = this.durationFormatter.format(Math.abs(offsets.end));
|
||||||
|
},
|
||||||
|
validateBounds(startOrEnd, input) {
|
||||||
|
let validationResult = true;
|
||||||
|
|
||||||
|
if (!this.timeFormatter.validate(input.value)){
|
||||||
|
validationResult = 'Invalid date value';
|
||||||
|
} else {
|
||||||
|
let boundsValues = {
|
||||||
|
start: this.timeFormatter.parse(this.formattedBounds.start),
|
||||||
|
end: this.timeFormatter.parse(this.formattedBounds.end)
|
||||||
|
};
|
||||||
|
validationResult = this.openmct.time.validateBounds(boundsValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validationResult !== true){
|
||||||
|
input.setCustomValidity(validationResult);
|
||||||
|
} else {
|
||||||
|
input.setCustomValidity('');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
validateOffsets(event) {
|
||||||
|
let input = event.target;
|
||||||
|
let validationResult = true;
|
||||||
|
|
||||||
|
if (!this.durationFormatter.validate(input.value)) {
|
||||||
|
validationResult = 'Invalid offset value';
|
||||||
|
} else {
|
||||||
|
let offsetValues = {
|
||||||
|
start: 0 - this.durationFormatter.parse(this.offsets.start),
|
||||||
|
end: this.durationFormatter.parse(this.offsets.end)
|
||||||
|
};
|
||||||
|
validationResult = this.openmct.time.validateOffsets(offsetValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validationResult !== true){
|
||||||
|
input.setCustomValidity(validationResult);
|
||||||
|
} else {
|
||||||
|
input.setCustomValidity('');
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
getFormatter(key) {
|
||||||
|
return this.openmct.telemetry.getValueFormatter({
|
||||||
|
format: key
|
||||||
|
}).formatter;
|
||||||
|
},
|
||||||
|
startDateSelected(date){
|
||||||
|
this.formattedBounds.start = this.timeFormatter.format(date);
|
||||||
|
this.validateBounds('start', this.$refs.startDate);
|
||||||
|
this.setBoundsFromView();
|
||||||
|
},
|
||||||
|
endDateSelected(date){
|
||||||
|
this.formattedBounds.end = this.timeFormatter.format(date);
|
||||||
|
this.validateBounds('end', this.$refs.endDate);
|
||||||
|
this.setBoundsFromView();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.setTimeSystem(JSON.parse(JSON.stringify(this.openmct.time.timeSystem())));
|
||||||
|
|
||||||
|
this.openmct.time.on('bounds', this.setViewFromBounds);
|
||||||
|
this.openmct.time.on('timeSystem', this.setTimeSystem);
|
||||||
|
this.openmct.time.on('clock', this.setViewFromClock);
|
||||||
|
this.openmct.time.on('clockOffsets', this.setViewFromOffsets)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
265
src/plugins/timeConductor/ConductorAxis.vue
Normal file
265
src/plugins/timeConductor/ConductorAxis.vue
Normal file
@ -0,0 +1,265 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT Web, Copyright (c) 2014-2018, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT Web includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
<template>
|
||||||
|
<div class="c-conductor-axis"
|
||||||
|
ref="axisHolder"
|
||||||
|
@mousedown="dragStart($event)">
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import "~styles/sass-base";
|
||||||
|
|
||||||
|
.c-conductor-axis {
|
||||||
|
$h: 18px;
|
||||||
|
$tickYPos: ($h / 2) + 12px;
|
||||||
|
|
||||||
|
@include userSelectNone();
|
||||||
|
@include bgTicks($c: rgba($colorBodyFg, 0.4));
|
||||||
|
background-position: 0 50%;
|
||||||
|
background-size: 5px 2px;
|
||||||
|
border-radius: $controlCr;
|
||||||
|
height: $h;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
text-rendering: geometricPrecision;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
> g {
|
||||||
|
// Overall Tick holder
|
||||||
|
transform: translateY($tickYPos);
|
||||||
|
path {
|
||||||
|
// Domain line
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
g {
|
||||||
|
// Each tick. These move on drag.
|
||||||
|
line {
|
||||||
|
// Line beneath ticks
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
text {
|
||||||
|
// Tick labels
|
||||||
|
fill: $colorBodyFg;
|
||||||
|
font-size: 1em;
|
||||||
|
paint-order: stroke;
|
||||||
|
font-weight: bold;
|
||||||
|
stroke: $colorBodyBg;
|
||||||
|
stroke-linecap: butt;
|
||||||
|
stroke-linejoin: bevel;
|
||||||
|
stroke-width: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body.desktop .is-fixed-mode & {
|
||||||
|
@include cursorGrab();
|
||||||
|
background-size: 3px 30%;
|
||||||
|
background-color: $colorBodyBgSubtle;
|
||||||
|
box-shadow: inset rgba(black, 0.4) 0 1px 1px;
|
||||||
|
transition: $transOut;
|
||||||
|
|
||||||
|
svg text {
|
||||||
|
fill: $colorBodyFg;
|
||||||
|
stroke: $colorBodyBgSubtle;
|
||||||
|
transition: $transOut;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:active {
|
||||||
|
$c: $colorKeySubtle;
|
||||||
|
background-color: $c;
|
||||||
|
transition: $transIn;
|
||||||
|
svg text {
|
||||||
|
stroke: $c;
|
||||||
|
transition: $transIn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-realtime-mode & {
|
||||||
|
$c: 1px solid rgba($colorTime, 0.7);
|
||||||
|
border-left: $c;
|
||||||
|
border-right: $c;
|
||||||
|
svg text {
|
||||||
|
fill: $colorTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import * as d3Selection from 'd3-selection';
|
||||||
|
import * as d3Axis from 'd3-axis';
|
||||||
|
import * as d3Scale from 'd3-scale';
|
||||||
|
import utcMultiTimeFormat from './utcMultiTimeFormat.js';
|
||||||
|
|
||||||
|
const PADDING = 1;
|
||||||
|
const DEFAULT_DURATION_FORMATTER = 'duration';
|
||||||
|
const RESIZE_POLL_INTERVAL = 200;
|
||||||
|
const PIXELS_PER_TICK = 100;
|
||||||
|
const PIXELS_PER_TICK_WIDE = 200;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
inject: ['openmct'],
|
||||||
|
props: {
|
||||||
|
bounds: Object
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
setScale() {
|
||||||
|
let timeSystem = this.openmct.time.timeSystem();
|
||||||
|
let bounds = this.bounds;
|
||||||
|
|
||||||
|
if (timeSystem.isUTCBased) {
|
||||||
|
this.xScale.domain([new Date(bounds.start), new Date(bounds.end)]);
|
||||||
|
} else {
|
||||||
|
this.xScale.domain([bounds.start, bounds.end]);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.xAxis.scale(this.xScale);
|
||||||
|
|
||||||
|
this.xScale.range([PADDING, this.width - PADDING * 2]);
|
||||||
|
this.axisElement.call(this.xAxis);
|
||||||
|
|
||||||
|
if (this.width > 1800) {
|
||||||
|
this.xAxis.ticks(this.width / PIXELS_PER_TICK_WIDE);
|
||||||
|
} else {
|
||||||
|
this.xAxis.ticks(this.width / PIXELS_PER_TICK);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.msPerPixel = (bounds.end - bounds.start) / this.width;
|
||||||
|
},
|
||||||
|
setViewFromTimeSystem(timeSystem) {
|
||||||
|
let format = this.getActiveFormatter();
|
||||||
|
let bounds = this.openmct.time.bounds();
|
||||||
|
|
||||||
|
//The D3 scale used depends on the type of time system as d3
|
||||||
|
// supports UTC out of the box.
|
||||||
|
if (timeSystem.isUTCBased) {
|
||||||
|
this.xScale = d3Scale.scaleUtc();
|
||||||
|
} else {
|
||||||
|
this.xScale = d3Scale.scaleLinear();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.xAxis.scale(this.xScale);
|
||||||
|
this.xAxis.tickFormat(utcMultiTimeFormat);
|
||||||
|
this.axisElement.call(this.xAxis);
|
||||||
|
},
|
||||||
|
getActiveFormatter() {
|
||||||
|
let timeSystem = this.openmct.time.timeSystem();
|
||||||
|
let isFixed = this.openmct.time.clock() === undefined;
|
||||||
|
|
||||||
|
if (isFixed) {
|
||||||
|
return this.getFormatter(timeSystem.timeFormat);
|
||||||
|
} else {
|
||||||
|
return this.getFormatter(timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getFormatter(key) {
|
||||||
|
return this.openmct.telemetry.getValueFormatter({
|
||||||
|
format: key
|
||||||
|
}).formatter;
|
||||||
|
},
|
||||||
|
dragStart($event){
|
||||||
|
let isFixed = this.openmct.time.clock() === undefined;
|
||||||
|
if (isFixed){
|
||||||
|
this.dragStartX = $event.clientX;
|
||||||
|
|
||||||
|
document.addEventListener('mousemove', this.drag);
|
||||||
|
document.addEventListener('mouseup', this.dragEnd, {
|
||||||
|
once: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
drag($event) {
|
||||||
|
if (!this.dragging){
|
||||||
|
this.dragging = true;
|
||||||
|
requestAnimationFrame(()=>{
|
||||||
|
let deltaX = $event.clientX - this.dragStartX;
|
||||||
|
let percX = deltaX / this.width;
|
||||||
|
let bounds = this.openmct.time.bounds();
|
||||||
|
let deltaTime = bounds.end - bounds.start;
|
||||||
|
let newStart = bounds.start - percX * deltaTime;
|
||||||
|
this.bounds = {
|
||||||
|
start: newStart,
|
||||||
|
end: newStart + deltaTime
|
||||||
|
};
|
||||||
|
this.$emit('panAxis', this.bounds);
|
||||||
|
this.dragging = false;
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
console.log('Rejected drag due to RAF cap');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dragEnd() {
|
||||||
|
document.removeEventListener('mousemove', this.drag);
|
||||||
|
this.openmct.time.bounds({
|
||||||
|
start: this.bounds.start,
|
||||||
|
end: this.bounds.end
|
||||||
|
});
|
||||||
|
},
|
||||||
|
resize() {
|
||||||
|
if (this.$refs.axisHolder.clientWidth !== this.width) {
|
||||||
|
this.width = this.$refs.axisHolder.clientWidth;
|
||||||
|
this.setScale();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
bounds: {
|
||||||
|
handler(bounds) {
|
||||||
|
this.setScale();
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
let axisHolder = this.$refs.axisHolder;
|
||||||
|
let height = axisHolder.offsetHeight;
|
||||||
|
let vis = d3Selection.select(axisHolder)
|
||||||
|
.append("svg:svg")
|
||||||
|
.attr("width", "100%")
|
||||||
|
.attr("height", height);
|
||||||
|
|
||||||
|
this.width = this.$refs.axisHolder.clientWidth;
|
||||||
|
this.xAxis = d3Axis.axisTop();
|
||||||
|
this.dragging = false;
|
||||||
|
|
||||||
|
// draw x axis with labels. CSS is used to position them.
|
||||||
|
this.axisElement = vis.append("g");
|
||||||
|
|
||||||
|
this.setViewFromTimeSystem(this.openmct.time.timeSystem());
|
||||||
|
this.setScale();
|
||||||
|
|
||||||
|
//Respond to changes in conductor
|
||||||
|
this.openmct.time.on("timeSystem", this.setViewFromTimeSystem);
|
||||||
|
setInterval(this.resize, RESIZE_POLL_INTERVAL);
|
||||||
|
},
|
||||||
|
destroyed() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
</script>
|
202
src/plugins/timeConductor/ConductorMode.vue
Normal file
202
src/plugins/timeConductor/ConductorMode.vue
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT Web, Copyright (c) 2014-2018, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT Web includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
<template>
|
||||||
|
<div class="c-ctrl-wrapper c-ctrl-wrapper--menus-up">
|
||||||
|
<button class="c-button--menu c-mode-button"
|
||||||
|
@click="toggleMenu($event)">
|
||||||
|
<span class="c-button__label">{{selectedMode.name}}</span>
|
||||||
|
</button>
|
||||||
|
<div class="c-menu c-super-menu c-conductor__mode-menu"
|
||||||
|
v-if="showMenu">
|
||||||
|
<div class="c-super-menu__menu">
|
||||||
|
<ul>
|
||||||
|
<li v-for="mode in modes"
|
||||||
|
:key="mode.key"
|
||||||
|
@click="setOption(mode)"
|
||||||
|
@mouseover="hoveredMode = mode"
|
||||||
|
@mouseleave="hoveredMode = {}"
|
||||||
|
class="menu-item-a"
|
||||||
|
:class="mode.cssClass">
|
||||||
|
{{mode.name}}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="c-super-menu__item-description">
|
||||||
|
<div :class="['l-item-description__icon', 'bg-' + hoveredMode.cssClass]"></div>
|
||||||
|
<div class="l-item-description__name">{{hoveredMode.name}}</div>
|
||||||
|
<div class="l-item-description__description">{{hoveredMode.description}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import "~styles/sass-base";
|
||||||
|
|
||||||
|
.c-conductor__mode-menu {
|
||||||
|
max-height: 80vh;
|
||||||
|
max-width: 500px;
|
||||||
|
min-height: 250px;
|
||||||
|
z-index: 70;
|
||||||
|
|
||||||
|
[class*="__icon"] {
|
||||||
|
filter: $colorKeyFilter;
|
||||||
|
}
|
||||||
|
|
||||||
|
[class*="__item-description"] {
|
||||||
|
min-width: 200px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
inject: ['openmct', 'configuration'],
|
||||||
|
data: function () {
|
||||||
|
let activeClock = this.openmct.time.clock();
|
||||||
|
if (activeClock !== undefined) {
|
||||||
|
//Create copy of active clock so the time API does not get reactified.
|
||||||
|
activeClock = Object.create(activeClock);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
selectedMode: this.getModeOptionForClock(activeClock),
|
||||||
|
selectedTimeSystem: JSON.parse(JSON.stringify(this.openmct.time.timeSystem())),
|
||||||
|
modes: [],
|
||||||
|
hoveredMode: {},
|
||||||
|
showMenu: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
loadClocksFromConfiguration() {
|
||||||
|
let clocks = this.configuration.menuOptions
|
||||||
|
.map(menuOption => menuOption.clock)
|
||||||
|
.filter(isDefinedAndUnique)
|
||||||
|
.map(this.getClock);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Populate the modes menu with metadata from the available clocks
|
||||||
|
* "Fixed Mode" is always first, and has no defined clock
|
||||||
|
*/
|
||||||
|
this.modes = [undefined]
|
||||||
|
.concat(clocks)
|
||||||
|
.map(this.getModeOptionForClock);
|
||||||
|
|
||||||
|
function isDefinedAndUnique(key, index, array) {
|
||||||
|
return key!== undefined && array.indexOf(key) === index;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getModeOptionForClock(clock) {
|
||||||
|
if (clock === undefined) {
|
||||||
|
return {
|
||||||
|
key: 'fixed',
|
||||||
|
name: 'Fixed Timespan Mode',
|
||||||
|
description: 'Query and explore data that falls between two fixed datetimes.',
|
||||||
|
cssClass: 'icon-tabular'
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
key: clock.key,
|
||||||
|
name: clock.name,
|
||||||
|
description: "Monitor streaming data in real-time. The Time " +
|
||||||
|
"Conductor and displays will automatically advance themselves based on this clock. " + clock.description,
|
||||||
|
cssClass: clock.cssClass || 'icon-clock'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getClock(key) {
|
||||||
|
return this.openmct.time.getAllClocks().filter(function (clock) {
|
||||||
|
return clock.key === key;
|
||||||
|
})[0];
|
||||||
|
},
|
||||||
|
|
||||||
|
setOption(option) {
|
||||||
|
let clockKey = option.key;
|
||||||
|
if (clockKey === 'fixed') {
|
||||||
|
clockKey = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
let configuration = this.getMatchingConfig({
|
||||||
|
clock: clockKey,
|
||||||
|
timeSystem: this.openmct.time.timeSystem().key
|
||||||
|
});
|
||||||
|
|
||||||
|
if (configuration === undefined) {
|
||||||
|
configuration = this.getMatchingConfig({
|
||||||
|
clock: clockKey
|
||||||
|
});
|
||||||
|
|
||||||
|
this.openmct.time.timeSystem(configuration.timeSystem, configuration.bounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clockKey === undefined) {
|
||||||
|
this.openmct.time.stopClock();
|
||||||
|
} else {
|
||||||
|
this.openmct.time.clock(clockKey, configuration.clockOffsets);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getMatchingConfig(options) {
|
||||||
|
const matchers = {
|
||||||
|
clock(config) {
|
||||||
|
return options.clock === config.clock
|
||||||
|
},
|
||||||
|
timeSystem(config) {
|
||||||
|
return options.timeSystem === config.timeSystem
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function configMatches(config) {
|
||||||
|
return Object.keys(options).reduce((match, option) => {
|
||||||
|
return match && matchers[option](config);
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.configuration.menuOptions.filter(configMatches)[0];
|
||||||
|
},
|
||||||
|
|
||||||
|
setViewFromClock(clock) {
|
||||||
|
this.selectedMode = this.getModeOptionForClock(clock);
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleMenu(event) {
|
||||||
|
this.showMenu = !this.showMenu;
|
||||||
|
|
||||||
|
if (this.showMenu) {
|
||||||
|
document.addEventListener('click', this.toggleMenu, true);
|
||||||
|
} else {
|
||||||
|
document.removeEventListener('click', this.toggleMenu, true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted: function () {
|
||||||
|
this.loadClocksFromConfiguration();
|
||||||
|
|
||||||
|
this.openmct.time.on('clock', this.setViewFromClock);
|
||||||
|
},
|
||||||
|
destroyed: function () {
|
||||||
|
this.openmct.time.off('clock', this.setViewFromClock);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
</script>
|
139
src/plugins/timeConductor/ConductorModeIcon.vue
Normal file
139
src/plugins/timeConductor/ConductorModeIcon.vue
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT Web, Copyright (c) 2014-2018, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT Web includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
<template>
|
||||||
|
<div class="c-clock-symbol">
|
||||||
|
<div class="hand-little"></div>
|
||||||
|
<div class="hand-big"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import "~styles/sass-base";
|
||||||
|
|
||||||
|
@keyframes clock-hands {
|
||||||
|
0% { transform: translate(-50%, -50%) rotate(0deg); }
|
||||||
|
100% { transform: translate(-50%, -50%) rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes clock-hands-sticky {
|
||||||
|
0% { transform: translate(-50%, -50%) rotate(0deg); }
|
||||||
|
7% { transform: translate(-50%, -50%) rotate(0deg); }
|
||||||
|
8% { transform: translate(-50%, -50%) rotate(30deg); }
|
||||||
|
15% { transform: translate(-50%, -50%) rotate(30deg); }
|
||||||
|
16% { transform: translate(-50%, -50%) rotate(60deg); }
|
||||||
|
24% { transform: translate(-50%, -50%) rotate(60deg); }
|
||||||
|
25% { transform: translate(-50%, -50%) rotate(90deg); }
|
||||||
|
32% { transform: translate(-50%, -50%) rotate(90deg); }
|
||||||
|
33% { transform: translate(-50%, -50%) rotate(120deg); }
|
||||||
|
40% { transform: translate(-50%, -50%) rotate(120deg); }
|
||||||
|
41% { transform: translate(-50%, -50%) rotate(150deg); }
|
||||||
|
49% { transform: translate(-50%, -50%) rotate(150deg); }
|
||||||
|
50% { transform: translate(-50%, -50%) rotate(180deg); }
|
||||||
|
57% { transform: translate(-50%, -50%) rotate(180deg); }
|
||||||
|
58% { transform: translate(-50%, -50%) rotate(210deg); }
|
||||||
|
65% { transform: translate(-50%, -50%) rotate(210deg); }
|
||||||
|
66% { transform: translate(-50%, -50%) rotate(240deg); }
|
||||||
|
74% { transform: translate(-50%, -50%) rotate(240deg); }
|
||||||
|
75% { transform: translate(-50%, -50%) rotate(270deg); }
|
||||||
|
82% { transform: translate(-50%, -50%) rotate(270deg); }
|
||||||
|
83% { transform: translate(-50%, -50%) rotate(300deg); }
|
||||||
|
90% { transform: translate(-50%, -50%) rotate(300deg); }
|
||||||
|
91% { transform: translate(-50%, -50%) rotate(330deg); }
|
||||||
|
99% { transform: translate(-50%, -50%) rotate(330deg); }
|
||||||
|
100% { transform: translate(-50%, -50%) rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.c-clock-symbol {
|
||||||
|
$c: $colorBtnBg; //$colorObjHdrIc;
|
||||||
|
$d: 18px;
|
||||||
|
height: $d;
|
||||||
|
width: $d;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
font-family: symbolsfont;
|
||||||
|
color: $c;
|
||||||
|
content: $glyph-icon-brackets;
|
||||||
|
font-size: $d;
|
||||||
|
line-height: normal;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clock hands
|
||||||
|
div[class*="hand"] {
|
||||||
|
$handW: 2px;
|
||||||
|
$handH: $d * 0.4;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
animation-timing-function: linear;
|
||||||
|
transform-origin: bottom;
|
||||||
|
position: absolute;
|
||||||
|
height: $handW;
|
||||||
|
width: $handW;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
z-index: 2;
|
||||||
|
&:before {
|
||||||
|
background: $c;
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
bottom: -1px;
|
||||||
|
}
|
||||||
|
&.hand-little {
|
||||||
|
z-index: 2;
|
||||||
|
animation-duration: 12s;
|
||||||
|
transform: translate(-50%, -50%) rotate(120deg);
|
||||||
|
&:before {
|
||||||
|
height: ceil($handH * 0.6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.hand-big {
|
||||||
|
z-index: 1;
|
||||||
|
animation-duration: 1s;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
&:before {
|
||||||
|
height: $handH;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modes
|
||||||
|
.is-realtime-mode &,
|
||||||
|
.is-lad-mode & {
|
||||||
|
&:before {
|
||||||
|
// Brackets icon
|
||||||
|
color: $colorTime;
|
||||||
|
}
|
||||||
|
div[class*="hand"] {
|
||||||
|
animation-name: clock-hands;
|
||||||
|
&:before {
|
||||||
|
background: $colorTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
126
src/plugins/timeConductor/ConductorTimeSystem.vue
Normal file
126
src/plugins/timeConductor/ConductorTimeSystem.vue
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT Web, Copyright (c) 2014-2018, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT Web includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
<template>
|
||||||
|
<div class="c-ctrl-wrapper c-ctrl-wrapper--menus-up"
|
||||||
|
v-if="selectedTimeSystem.name">
|
||||||
|
<button class="c-button--menu c-time-system-button"
|
||||||
|
:class="selectedTimeSystem.cssClass"
|
||||||
|
@click="toggleMenu($event)">
|
||||||
|
<span class="c-button__label">{{selectedTimeSystem.name}}</span>
|
||||||
|
</button>
|
||||||
|
<div class="c-menu" v-if="showMenu">
|
||||||
|
<ul>
|
||||||
|
<li @click="setTimeSystemFromView(timeSystem)"
|
||||||
|
v-for="timeSystem in timeSystems"
|
||||||
|
:key="timeSystem.key"
|
||||||
|
:class="timeSystem.cssClass">
|
||||||
|
{{timeSystem.name}}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
inject: ['openmct', 'configuration'],
|
||||||
|
data: function () {
|
||||||
|
let activeClock = this.openmct.time.clock();
|
||||||
|
|
||||||
|
return {
|
||||||
|
selectedTimeSystem: JSON.parse(JSON.stringify(this.openmct.time.timeSystem())),
|
||||||
|
timeSystems: this.getValidTimesystemsForClock(activeClock),
|
||||||
|
showMenu: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getValidTimesystemsForClock(clock) {
|
||||||
|
return this.configuration.menuOptions
|
||||||
|
.filter(menuOption => menuOption.clock === (clock && clock.key))
|
||||||
|
.map(menuOption => JSON.parse(JSON.stringify(this.openmct.time.timeSystems.get(menuOption.timeSystem))));
|
||||||
|
},
|
||||||
|
|
||||||
|
setTimeSystemFromView(timeSystem) {
|
||||||
|
if (timeSystem.key !== this.selectedTimeSystem.key) {
|
||||||
|
let activeClock = this.openmct.time.clock();
|
||||||
|
let configuration = this.getMatchingConfig({
|
||||||
|
clock: activeClock && activeClock.key,
|
||||||
|
timeSystem: timeSystem.key
|
||||||
|
});
|
||||||
|
if (activeClock === undefined) {
|
||||||
|
this.openmct.time.timeSystem(timeSystem.key, configuration.bounds);
|
||||||
|
} else {
|
||||||
|
this.openmct.time.timeSystem(timeSystem.key);
|
||||||
|
this.openmct.time.clockOffsets(configuration.clockOffsets);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getMatchingConfig(options) {
|
||||||
|
const matchers = {
|
||||||
|
clock(config) {
|
||||||
|
return options.clock === config.clock
|
||||||
|
},
|
||||||
|
timeSystem(config) {
|
||||||
|
return options.timeSystem === config.timeSystem
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function configMatches(config) {
|
||||||
|
return Object.keys(options).reduce((match, option) => {
|
||||||
|
return match && matchers[option](config);
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.configuration.menuOptions.filter(configMatches)[0];
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleMenu(event) {
|
||||||
|
this.showMenu = !this.showMenu;
|
||||||
|
|
||||||
|
if (this.showMenu) {
|
||||||
|
document.addEventListener('click', this.toggleMenu, true);
|
||||||
|
} else {
|
||||||
|
document.removeEventListener('click', this.toggleMenu, true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
setViewFromTimeSystem(timeSystem) {
|
||||||
|
this.selectedTimeSystem = timeSystem;
|
||||||
|
},
|
||||||
|
|
||||||
|
setViewFromClock(clock) {
|
||||||
|
let activeClock = this.openmct.time.clock();
|
||||||
|
this.timeSystems = this.getValidTimesystemsForClock(activeClock);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted: function () {
|
||||||
|
this.openmct.time.on('timeSystem', this.setViewFromTimeSystem);
|
||||||
|
this.openmct.time.on('clock', this.setViewFromClock);
|
||||||
|
},
|
||||||
|
destroyed: function () {
|
||||||
|
this.openmct.time.off('timeSystem', this.setViewFromTimeSystem);
|
||||||
|
this.openmct.time.on('clock', this.setViewFromClock);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
</script>
|
347
src/plugins/timeConductor/DatePicker.vue
Normal file
347
src/plugins/timeConductor/DatePicker.vue
Normal file
@ -0,0 +1,347 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT Web, Copyright (c) 2014-2018, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT Web includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
<template>
|
||||||
|
<div class="c-ctrl-wrapper c-ctrl-wrapper--menus-up c-datetime-picker__wrapper" ref="calendarHolder">
|
||||||
|
<a class="c-click-icon icon-calendar"
|
||||||
|
@click="togglePicker()"></a>
|
||||||
|
<div class="c-menu c-menu--mobile-modal c-datetime-picker"
|
||||||
|
v-if="showPicker">
|
||||||
|
<div class="c-datetime-picker__close-button">
|
||||||
|
<button class="c-click-icon icon-x-in-circle"
|
||||||
|
@click="togglePicker()"></button>
|
||||||
|
</div>
|
||||||
|
<div class="c-datetime-picker__pager c-pager l-month-year-pager">
|
||||||
|
<div class="c-pager__prev c-click-icon icon-arrow-left"
|
||||||
|
@click="changeMonth(-1)"></div>
|
||||||
|
<div class="c-pager__month-year">{{model.month}} {{model.year}}</div>
|
||||||
|
<div class="c-pager__next c-click-icon icon-arrow-right"
|
||||||
|
@click="changeMonth(1)"></div>
|
||||||
|
</div>
|
||||||
|
<div class="c-datetime-picker__calendar c-calendar">
|
||||||
|
<ul class="c-calendar__row--header l-cal-row">
|
||||||
|
<li v-for="day in ['Su','Mo','Tu','We','Th','Fr','Sa']"
|
||||||
|
:key="day">{{day}}</li>
|
||||||
|
</ul>
|
||||||
|
<ul class="c-calendar__row--body"
|
||||||
|
v-for="(row, index) in table"
|
||||||
|
:key="index">
|
||||||
|
<li v-for="(cell, index) in row"
|
||||||
|
:key="index"
|
||||||
|
@click="select(cell)"
|
||||||
|
:class="{ 'is-in-month': isInCurrentMonth(cell), selected: isSelected(cell) }">
|
||||||
|
<div class="c-calendar__day--prime">{{cell.day}}</div>
|
||||||
|
<div class="c-calendar__day--sub">{{cell.dayOfYear}}</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import "~styles/sass-base";
|
||||||
|
|
||||||
|
/******************************************************** PICKER */
|
||||||
|
.c-datetime-picker {
|
||||||
|
@include userSelectNone();
|
||||||
|
padding: $interiorMarginLg !important;
|
||||||
|
display: flex !important; // Override .c-menu display: block;
|
||||||
|
flex-direction: column;
|
||||||
|
> * + * {
|
||||||
|
margin-top: $interiorMargin;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__close-button {
|
||||||
|
display: none; // Only show when body.phone, see below.
|
||||||
|
}
|
||||||
|
|
||||||
|
&__pager {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__calendar {
|
||||||
|
border-top: 1px solid $colorInteriorBorder;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-pager {
|
||||||
|
display: grid;
|
||||||
|
grid-column-gap: $interiorMargin;
|
||||||
|
grid-template-rows: 1fr;
|
||||||
|
grid-template-columns: auto 1fr auto;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.c-click-icon {
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__month-year {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/******************************************************** CALENDAR */
|
||||||
|
.c-calendar {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(7, min-content);
|
||||||
|
grid-template-rows: auto;
|
||||||
|
grid-gap: 1px;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
$mutedOpacity: 0.5;
|
||||||
|
|
||||||
|
ul {
|
||||||
|
display: contents;
|
||||||
|
&[class*='--header'] {
|
||||||
|
pointer-events: none;
|
||||||
|
li {
|
||||||
|
opacity: $mutedOpacity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center !important;
|
||||||
|
padding: $interiorMargin;
|
||||||
|
|
||||||
|
&.is-in-month {
|
||||||
|
background: $colorMenuElementHilite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__day {
|
||||||
|
&--sub {
|
||||||
|
opacity: $mutedOpacity;
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/******************************************************** MOBILE */
|
||||||
|
body.phone {
|
||||||
|
.c-datetime-picker {
|
||||||
|
&.c-menu {
|
||||||
|
@include modalFullScreen();
|
||||||
|
}
|
||||||
|
|
||||||
|
&__close-button {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-calendar {
|
||||||
|
grid-template-columns: repeat(7, auto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
|
const TIME_NAMES = {
|
||||||
|
'hours': "Hour",
|
||||||
|
'minutes': "Minute",
|
||||||
|
'seconds': "Second"
|
||||||
|
};
|
||||||
|
const MONTHS = moment.months();
|
||||||
|
const TIME_OPTIONS = (function makeRanges() {
|
||||||
|
let arr = [];
|
||||||
|
while (arr.length < 60) {
|
||||||
|
arr.push(arr.length);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
hours: arr.slice(0, 24),
|
||||||
|
minutes: arr,
|
||||||
|
seconds: arr
|
||||||
|
};
|
||||||
|
}());
|
||||||
|
|
||||||
|
export default {
|
||||||
|
inject: ['openmct'],
|
||||||
|
props: {
|
||||||
|
defaultDateTime: String,
|
||||||
|
formatter: Object
|
||||||
|
},
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
showPicker: false,
|
||||||
|
picker: {
|
||||||
|
year: undefined,
|
||||||
|
month: undefined,
|
||||||
|
interacted: false
|
||||||
|
},
|
||||||
|
model: {
|
||||||
|
year: undefined,
|
||||||
|
month: undefined,
|
||||||
|
},
|
||||||
|
table: undefined,
|
||||||
|
date: undefined,
|
||||||
|
time: undefined
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
generateTable() {
|
||||||
|
let m = moment.utc({ year: this.picker.year, month: this.picker.month }).day(0),
|
||||||
|
table = [],
|
||||||
|
row,
|
||||||
|
col;
|
||||||
|
|
||||||
|
for (row = 0; row < 6; row += 1) {
|
||||||
|
table.push([]);
|
||||||
|
for (col = 0; col < 7; col += 1) {
|
||||||
|
table[row].push({
|
||||||
|
year: m.year(),
|
||||||
|
month: m.month(),
|
||||||
|
day: m.date(),
|
||||||
|
dayOfYear: m.dayOfYear()
|
||||||
|
});
|
||||||
|
m.add(1, 'days'); // Next day!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return table;
|
||||||
|
},
|
||||||
|
|
||||||
|
updateViewForMonth() {
|
||||||
|
this.model.month = MONTHS[this.picker.month];
|
||||||
|
this.model.year = this.picker.year;
|
||||||
|
this.table = this.generateTable();
|
||||||
|
},
|
||||||
|
|
||||||
|
updateFromModel(defaultDateTime) {
|
||||||
|
let m;
|
||||||
|
|
||||||
|
m = moment.utc(defaultDateTime);
|
||||||
|
|
||||||
|
this.date = {
|
||||||
|
year: m.year(),
|
||||||
|
month: m.month(),
|
||||||
|
day: m.date()
|
||||||
|
};
|
||||||
|
this.time = {
|
||||||
|
hours: m.hour(),
|
||||||
|
minutes: m.minute(),
|
||||||
|
seconds: m.second()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Zoom to that date in the picker, but
|
||||||
|
// only if the user hasn't interacted with it yet.
|
||||||
|
if (!this.picker.interacted) {
|
||||||
|
this.picker.year = m.year();
|
||||||
|
this.picker.month = m.month();
|
||||||
|
this.updateViewForMonth();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
updateFromView() {
|
||||||
|
let m = moment.utc({
|
||||||
|
year: this.date.year,
|
||||||
|
month: this.date.month,
|
||||||
|
day: this.date.day,
|
||||||
|
hour: this.time.hours,
|
||||||
|
minute: this.time.minutes,
|
||||||
|
second: this.time.seconds
|
||||||
|
});
|
||||||
|
this.$emit('date-selected', m.valueOf());
|
||||||
|
},
|
||||||
|
|
||||||
|
isInCurrentMonth(cell) {
|
||||||
|
return cell.month === this.picker.month;
|
||||||
|
},
|
||||||
|
|
||||||
|
isSelected(cell) {
|
||||||
|
let date = this.date || {};
|
||||||
|
return cell.day === date.day &&
|
||||||
|
cell.month === date.month &&
|
||||||
|
cell.year === date.year;
|
||||||
|
},
|
||||||
|
|
||||||
|
select(cell) {
|
||||||
|
this.date = this.date || {};
|
||||||
|
this.date.month = cell.month;
|
||||||
|
this.date.year = cell.year;
|
||||||
|
this.date.day = cell.day;
|
||||||
|
this.updateFromView();
|
||||||
|
this.showPicker = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
dateEquals(d1, d2) {
|
||||||
|
return d1.year === d2.year &&
|
||||||
|
d1.month === d2.month &&
|
||||||
|
d1.day === d2.day;
|
||||||
|
},
|
||||||
|
|
||||||
|
changeMonth(delta) {
|
||||||
|
this.picker.month += delta;
|
||||||
|
if (this.picker.month > 11) {
|
||||||
|
this.picker.month = 0;
|
||||||
|
this.picker.year += 1;
|
||||||
|
}
|
||||||
|
if (this.picker.month < 0) {
|
||||||
|
this.picker.month = 11;
|
||||||
|
this.picker.year -= 1;
|
||||||
|
}
|
||||||
|
this.picker.interacted = true;
|
||||||
|
this.updateViewForMonth();
|
||||||
|
},
|
||||||
|
|
||||||
|
nameFor(key) {
|
||||||
|
return TIME_NAMES[key];
|
||||||
|
},
|
||||||
|
|
||||||
|
optionsFor(key) {
|
||||||
|
return TIME_OPTIONS[key];
|
||||||
|
},
|
||||||
|
|
||||||
|
hidePicker(event) {
|
||||||
|
let path = event.composedPath();
|
||||||
|
if (path.indexOf(this.$refs.calendarHolder) === -1) {
|
||||||
|
this.showPicker = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
togglePicker() {
|
||||||
|
this.showPicker = !this.showPicker;
|
||||||
|
|
||||||
|
if (this.showPicker) {
|
||||||
|
document.addEventListener('click', this.hidePicker, {
|
||||||
|
capture: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted: function () {
|
||||||
|
this.updateFromModel(this.defaultDateTime);
|
||||||
|
this.updateViewForMonth();
|
||||||
|
},
|
||||||
|
destroyed: function () {
|
||||||
|
document.addEventListener('click', this.hidePicker, {
|
||||||
|
capture: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
</script>
|
@ -20,13 +20,14 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
define([], function () {
|
import Conductor from './Conductor.vue';
|
||||||
|
import Vue from 'vue';
|
||||||
|
|
||||||
function isTruthy(a) {
|
function isTruthy(a) {
|
||||||
return !!a;
|
return !!a;
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateMenuOption(menuOption, index) {
|
function validateMenuOption(menuOption, index) {
|
||||||
if (menuOption.clock && !menuOption.clockOffsets) {
|
if (menuOption.clock && !menuOption.clockOffsets) {
|
||||||
return "clock-based menuOption at index " + index + " is " +
|
return "clock-based menuOption at index " + index + " is " +
|
||||||
"missing required property 'clockOffsets'.";
|
"missing required property 'clockOffsets'.";
|
||||||
@ -39,9 +40,9 @@ define([], function () {
|
|||||||
return "fixed-bounds menuOption at index " + index + " is " +
|
return "fixed-bounds menuOption at index " + index + " is " +
|
||||||
"missing required property 'bounds'";
|
"missing required property 'bounds'";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateConfiguration(config) {
|
function validateConfiguration(config) {
|
||||||
if (config === undefined ||
|
if (config === undefined ||
|
||||||
config.menuOptions === undefined ||
|
config.menuOptions === undefined ||
|
||||||
config.menuOptions.length === 0) {
|
config.menuOptions.length === 0) {
|
||||||
@ -53,9 +54,9 @@ define([], function () {
|
|||||||
.join('\n');
|
.join('\n');
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateRuntimeConfiguration(config, openmct) {
|
function validateRuntimeConfiguration(config, openmct) {
|
||||||
var systems = openmct.time.getAllTimeSystems()
|
var systems = openmct.time.getAllTimeSystems()
|
||||||
.reduce(function (m, ts) {
|
.reduce(function (m, ts) {
|
||||||
m[ts.key] = ts;
|
m[ts.key] = ts;
|
||||||
@ -76,44 +77,40 @@ define([], function () {
|
|||||||
return "menuOption at index " + index + " specifies a " +
|
return "menuOption at index " + index + " specifies a " +
|
||||||
"clock that does not exist: " + menuOption.clock;
|
"clock that does not exist: " + menuOption.clock;
|
||||||
}
|
}
|
||||||
})
|
}).filter(isTruthy).join('\n');
|
||||||
.filter(isTruthy)
|
}
|
||||||
.join('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
function throwConfigErrorIfExists(error) {
|
function throwIfError(configResult) {
|
||||||
if (error) {
|
if (configResult) {
|
||||||
throw new Error("Invalid Time Conductor Configuration: \n" +
|
throw new Error("Invalid Time Conductor Configuration: \n" +
|
||||||
error + '\n' +
|
configResult + '\n' +
|
||||||
"https://github.com/nasa/openmct/blob/master/API.md#the-time-conductor");
|
"https://github.com/nasa/openmct/blob/master/API.md#the-time-conductor");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mountComponent(openmct, configuration) {
|
||||||
|
openmct.layout.conductorComponent = Object.create({
|
||||||
|
components: {
|
||||||
|
Conductor
|
||||||
|
},
|
||||||
|
template: "<conductor></conductor>",
|
||||||
|
provide: {
|
||||||
|
openmct: openmct,
|
||||||
|
configuration: configuration
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return function (config) {
|
export default function (config){
|
||||||
|
|
||||||
throwConfigErrorIfExists(validateConfiguration(config));
|
let configResult = validateConfiguration(config);
|
||||||
|
throwIfError(configResult);
|
||||||
|
|
||||||
return function (openmct) {
|
return function (openmct) {
|
||||||
|
|
||||||
openmct.legacyExtension('constants', {
|
|
||||||
key: 'CONDUCTOR_CONFIG',
|
|
||||||
value: config,
|
|
||||||
priority: 'mandatory'
|
|
||||||
});
|
|
||||||
|
|
||||||
openmct.legacyRegistry.enable('platform/features/conductor/core');
|
|
||||||
openmct.legacyRegistry.enable('platform/features/conductor/compatibility');
|
|
||||||
|
|
||||||
openmct.on('start', function () {
|
openmct.on('start', function () {
|
||||||
|
configResult = validateRuntimeConfiguration(config, openmct);
|
||||||
throwConfigErrorIfExists(validateRuntimeConfiguration(config, openmct));
|
throwIfError(configResult);
|
||||||
|
|
||||||
/*
|
|
||||||
On app startup, default the conductor if not already set.
|
|
||||||
*/
|
|
||||||
if (openmct.time.timeSystem() !== undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var defaults = config.menuOptions[0];
|
var defaults = config.menuOptions[0];
|
||||||
if (defaults.clock) {
|
if (defaults.clock) {
|
||||||
@ -123,7 +120,8 @@ define([], function () {
|
|||||||
openmct.time.timeSystem(defaults.timeSystem, defaults.bounds);
|
openmct.time.timeSystem(defaults.timeSystem, defaults.bounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mountComponent(openmct, config);
|
||||||
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
});
|
|
||||||
|
66
src/plugins/timeConductor/utcMultiTimeFormat.js
Normal file
66
src/plugins/timeConductor/utcMultiTimeFormat.js
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
|
export default function multiFormat(date) {
|
||||||
|
var momentified = moment.utc(date);
|
||||||
|
/**
|
||||||
|
* Uses logic from d3 Time-Scales, v3 of the API. See
|
||||||
|
* https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Scales.md
|
||||||
|
*
|
||||||
|
* Licensed
|
||||||
|
*/
|
||||||
|
var format = [
|
||||||
|
[".SSS", function (m) {
|
||||||
|
return m.milliseconds();
|
||||||
|
}],
|
||||||
|
[":ss", function (m) {
|
||||||
|
return m.seconds();
|
||||||
|
}],
|
||||||
|
["HH:mm", function (m) {
|
||||||
|
return m.minutes();
|
||||||
|
}],
|
||||||
|
["HH:mm", function (m) {
|
||||||
|
return m.hours();
|
||||||
|
}],
|
||||||
|
["ddd DD", function (m) {
|
||||||
|
return m.days() &&
|
||||||
|
m.date() !== 1;
|
||||||
|
}],
|
||||||
|
["MMM DD", function (m) {
|
||||||
|
return m.date() !== 1;
|
||||||
|
}],
|
||||||
|
["MMMM", function (m) {
|
||||||
|
return m.month();
|
||||||
|
}],
|
||||||
|
["YYYY", function () {
|
||||||
|
return true;
|
||||||
|
}]
|
||||||
|
].filter(function (row) {
|
||||||
|
return row[1](momentified);
|
||||||
|
})[0][0];
|
||||||
|
|
||||||
|
if (format !== undefined) {
|
||||||
|
return moment.utc(date).format(format);
|
||||||
|
}
|
||||||
|
}
|
@ -58,7 +58,7 @@ define(['EventEmitter'], function (EventEmitter) {
|
|||||||
this.selected[0].element.classList.remove('s-selected');
|
this.selected[0].element.classList.remove('s-selected');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.selected[1]) {
|
if (this.selected[1] && this.selected[1].element) {
|
||||||
this.selected[1].element.classList.remove('s-selected-parent');
|
this.selected[1].element.classList.remove('s-selected-parent');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,7 +66,7 @@ define(['EventEmitter'], function (EventEmitter) {
|
|||||||
selectable[0].element.classList.add('s-selected');
|
selectable[0].element.classList.add('s-selected');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectable[1]) {
|
if (selectable[1] && selectable[1].element) {
|
||||||
selectable[1].element.classList.add('s-selected-parent');
|
selectable[1].element.classList.add('s-selected-parent');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,7 +132,7 @@ define(['EventEmitter'], function (EventEmitter) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return function () {
|
return function () {
|
||||||
element.removeEventListener('click', capture);
|
element.removeEventListener('click', capture, true);
|
||||||
element.removeEventListener('click', selectCapture);
|
element.removeEventListener('click', selectCapture);
|
||||||
|
|
||||||
if (unlisten) {
|
if (unlisten) {
|
||||||
|
330
src/styles-new/_constants-espresso.scss
Normal file
330
src/styles-new/_constants-espresso.scss
Normal file
@ -0,0 +1,330 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
/************************************************** ESPRESSO THEME CONSTANTS */
|
||||||
|
|
||||||
|
@import "constants";
|
||||||
|
|
||||||
|
// Functions
|
||||||
|
@function buttonBg($c: $colorBtnBg) {
|
||||||
|
@return linear-gradient(lighten($c, 5%), $c);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
$fontBaseSize: 12px;
|
||||||
|
$smallCr: 2px;
|
||||||
|
$controlCr: 3px;
|
||||||
|
$basicCr: 4px;
|
||||||
|
|
||||||
|
// Base colors
|
||||||
|
$colorBodyBg: #393939;
|
||||||
|
$colorBodyFg: #aaa;
|
||||||
|
$colorGenBg: #222;
|
||||||
|
$colorHeadBg: #262626;
|
||||||
|
$colorHeadFg: $colorBodyFg;
|
||||||
|
$colorStatusBarBg: $colorHeadBg;
|
||||||
|
$colorStatusBarFg: $colorBodyFg;
|
||||||
|
$colorStatusBarFgHov: #aaa;
|
||||||
|
$colorKey: #0099cc;
|
||||||
|
$colorKeyFg: #fff;
|
||||||
|
$colorKeyHov: #00c0f6;
|
||||||
|
$colorKeyFilter: brightness(1) sepia(1) hue-rotate(145deg) saturate(6);
|
||||||
|
$colorKeyFilterHov: brightness(1) sepia(1) hue-rotate(145deg) saturate(7);
|
||||||
|
$colorInteriorBorder: rgba($colorBodyFg, 0.2);
|
||||||
|
$colorA: #ccc;
|
||||||
|
$colorAHov: #fff;
|
||||||
|
|
||||||
|
// Layout
|
||||||
|
$shellMainPad: 4px 0;
|
||||||
|
$shellPanePad: $interiorMargin, 7px; // TODO: Sync this with Snow theme
|
||||||
|
|
||||||
|
// Status colors, mainly used for messaging and item ancillary symbols
|
||||||
|
$colorStatusFg: #999;
|
||||||
|
$colorStatusDefault: #ccc;
|
||||||
|
$colorStatusInfo: #60ba7b;
|
||||||
|
$colorStatusAlert: #ffb66c;
|
||||||
|
$colorStatusError: #da0004;
|
||||||
|
$colorStatusBtnBg: #666;
|
||||||
|
|
||||||
|
// States
|
||||||
|
$colorPausedBg: #ff9900;
|
||||||
|
$colorPausedFg: #fff;
|
||||||
|
$colorOk: #33cc33;
|
||||||
|
|
||||||
|
// Base variations
|
||||||
|
$colorBodyBgSubtle: lighten($colorBodyBg, 5%);
|
||||||
|
$colorBodyBgSubtleHov: darken($colorKey, 50%);
|
||||||
|
$colorKeySubtle: darken($colorKey, 10%);
|
||||||
|
|
||||||
|
// Time Colors
|
||||||
|
$colorTime: #618cff;
|
||||||
|
$colorTimeBg: $colorTime;
|
||||||
|
$colorTimeFg: lighten($colorTimeBg, 30%);
|
||||||
|
$colorTimeHov: lighten($colorTime, 10%);
|
||||||
|
$colorTimeSubtle: darken($colorTime, 20%);
|
||||||
|
$colorTOI: $colorBodyFg; // was $timeControllerToiLineColor
|
||||||
|
$colorTOIHov: $colorTime; // was $timeControllerToiLineColorHov
|
||||||
|
|
||||||
|
// Edit Colors
|
||||||
|
$editColor: #00c7c3;
|
||||||
|
$editColorFg: $colorBodyFg;
|
||||||
|
$browseBorderSelectableHov: 1px dotted rgba($colorBodyFg, 0.2);
|
||||||
|
$browseShdwSelectableHov: rgba($colorBodyFg, 0.2) 0 0 3px;
|
||||||
|
$browseBorderSelected: 1px solid rgba($colorBodyFg, 0.6);
|
||||||
|
$editBorderSelectable: 1px dotted rgba($editColor, 1);
|
||||||
|
$editBorderSelectableHov: 1px dashed rgba($editColor, 1);
|
||||||
|
$editBorderSelected: 1px solid $editColor;
|
||||||
|
$editBorderDrilledIn: 1px dashed #ff4d9a;
|
||||||
|
$colorGridLines: rgba($editColor, 0.2);
|
||||||
|
|
||||||
|
// UI elements
|
||||||
|
$colorIconAlias: #4af6f3;
|
||||||
|
$colorIconAliasForKeyFilter: #aaa;
|
||||||
|
$colorProgressBarOuter: rgba(#000, 0.1);
|
||||||
|
$colorProgressBarAmt: #0a0;
|
||||||
|
|
||||||
|
// Buttons and Controls
|
||||||
|
$colorBtnBg: lighten($colorBodyBg, 10%); // !
|
||||||
|
$colorBtnBgHov: lighten($colorBtnBg, 10%); // !
|
||||||
|
$colorBtnFg: lighten($colorBodyFg, 10%); // !
|
||||||
|
$colorBtnFgHov: $colorBtnFg;
|
||||||
|
$colorBtnMajorBg: $colorKey;
|
||||||
|
$colorBtnMajorBgHov: $colorKeyHov;
|
||||||
|
$colorBtnMajorFg: $colorKeyFg;
|
||||||
|
$colorBtnMajorFgHov: darken($colorBtnMajorFg, 10%);
|
||||||
|
$colorBtnCautionBg: #f16f6f;
|
||||||
|
$colorBtnCautionBgHov: #f1504e;
|
||||||
|
$colorBtnCautionFg: $colorBtnFg;
|
||||||
|
$colorClickIcon: $colorKey;
|
||||||
|
$colorClickIconBgHov: rgba($colorKey, 0.6);
|
||||||
|
$colorClickIconFgHov: $colorKeyHov;
|
||||||
|
|
||||||
|
// Menus
|
||||||
|
$colorMenuBg: lighten($colorBodyBg, 20%);
|
||||||
|
$colorMenuFg: lighten($colorBodyFg, 30%);
|
||||||
|
$colorMenuIc: lighten($colorKey, 15%);
|
||||||
|
$colorMenuHovBg: $colorMenuIc;
|
||||||
|
$colorMenuHovFg: lighten($colorMenuFg, 10%);
|
||||||
|
$colorMenuHovIc: $colorMenuHovFg;
|
||||||
|
$colorMenuElementHilite: lighten($colorMenuBg, 10%);
|
||||||
|
$shdwMenu: rgba(black, 0.5) 0 1px 5px;
|
||||||
|
$shdwMenuText: none;
|
||||||
|
$menuItemPad: $interiorMargin, floor($interiorMargin * 1.25);
|
||||||
|
|
||||||
|
// Palettes and Swatches
|
||||||
|
$paletteItemBorderOuterColorSelected: black;
|
||||||
|
$paletteItemBorderInnerColorSelected: white;
|
||||||
|
$paletteItemBorderInnerColor: rgba($paletteItemBorderOuterColorSelected, 0.3);
|
||||||
|
|
||||||
|
// Form colors
|
||||||
|
$colorCheck: $colorKey;
|
||||||
|
$colorFormRequired: $colorKey;
|
||||||
|
$colorFormValid: $colorOk;
|
||||||
|
$colorFormError: #990000;
|
||||||
|
$colorFormInvalid: #ff2200;
|
||||||
|
$colorFormFieldErrorBg: $colorFormError;
|
||||||
|
$colorFormFieldErrorFg: rgba(#fff, 0.6);
|
||||||
|
$colorFormLines: rgba(#000, 0.1);
|
||||||
|
$colorFormSectionHeader: rgba(#000, 0.05);
|
||||||
|
$colorInputBg: rgba(black, 0.2);
|
||||||
|
$colorInputFg: $colorBodyFg;
|
||||||
|
$colorInputPlaceholder: darken($colorBodyFg, 20%);
|
||||||
|
$colorFormText: darken($colorBodyFg, 10%);
|
||||||
|
$colorInputIcon: darken($colorBodyFg, 25%);
|
||||||
|
$colorFieldHint: lighten($colorBodyFg, 40%);
|
||||||
|
$shdwInput: inset rgba(black, 0.4) 0 0 1px;
|
||||||
|
$shdwInputHov: inset rgba(black, 0.7) 0 0 2px;
|
||||||
|
$shdwInputFoc: inset rgba(black, 0.8) 0 0.25px 3px;
|
||||||
|
|
||||||
|
// Inspector
|
||||||
|
$colorInspectorBg: lighten($colorBodyBg, 5%);
|
||||||
|
$colorInspectorFg: $colorBodyFg;
|
||||||
|
$colorInspectorPropName: darken($colorBodyFg, 20%);
|
||||||
|
$colorInspectorPropVal: lighten($colorInspectorFg, 15%);
|
||||||
|
$colorInspectorSectionHeaderBg: lighten($colorInspectorBg, 5%);
|
||||||
|
$colorInspectorSectionHeaderFg: lighten($colorInspectorBg, 40%);
|
||||||
|
|
||||||
|
// Overlay
|
||||||
|
$overlayColorBg: $colorMenuBg;
|
||||||
|
$overlayColorFg: $colorMenuFg;
|
||||||
|
$overlayButtonColorBg: lighten($overlayColorBg, 20%);
|
||||||
|
$overlayButtonColorFg: #fff;
|
||||||
|
$overlayCr: $interiorMarginLg;
|
||||||
|
|
||||||
|
// Indicator colors
|
||||||
|
$colorIndicatorAvailable: $colorKey;
|
||||||
|
$colorIndicatorDisabled: #444;
|
||||||
|
$colorIndicatorOn: $colorOk;
|
||||||
|
$colorIndicatorOff: #666;
|
||||||
|
|
||||||
|
// Limits and staleness colors//
|
||||||
|
$colorTelemFresh: lighten($colorBodyFg, 20%);
|
||||||
|
$colorTelemStale: darken($colorBodyFg, 20%);
|
||||||
|
$styleTelemStale: italic;
|
||||||
|
$colorLimitYellowBg: rgba(#ffaa00, 0.3);
|
||||||
|
$colorLimitYellowIc: #ffaa00;
|
||||||
|
$colorLimitRedBg: rgba(red, 0.3);
|
||||||
|
$colorLimitRedIc: red;
|
||||||
|
|
||||||
|
// Bubble colors
|
||||||
|
$colorInfoBubbleBg: $colorMenuBg;
|
||||||
|
$colorInfoBubbleFg: #666;
|
||||||
|
|
||||||
|
// Items
|
||||||
|
$colorItemBg: buttonBg($colorBtnBg);
|
||||||
|
$colorItemBgHov: buttonBg(lighten($colorBtnBg, 5%));
|
||||||
|
$colorListItemBg: transparent;
|
||||||
|
$colorListItemBgHov: rgba($colorKey, 0.1);
|
||||||
|
$colorItemFg: $colorBtnFg;
|
||||||
|
$colorItemFgDetails: darken($colorItemFg, 20%);
|
||||||
|
$shdwItemText: none;
|
||||||
|
|
||||||
|
// Tabular
|
||||||
|
$colorTabBorder: lighten($colorBodyBg, 10%);
|
||||||
|
$colorTabBodyBg: $colorBodyBg;
|
||||||
|
$colorTabBodyFg: lighten($colorBodyFg, 20%);
|
||||||
|
$colorTabHeaderBg: lighten($colorBodyBg, 10%);
|
||||||
|
$colorTabHeaderFg: lighten($colorBodyFg, 20%);
|
||||||
|
$colorTabHeaderBorder: $colorBodyBg;
|
||||||
|
|
||||||
|
// Plot
|
||||||
|
$colorPlotBg: rgba(black, 0.05);
|
||||||
|
$colorPlotFg: $colorBodyFg;
|
||||||
|
$colorPlotHash: black;
|
||||||
|
$opacityPlotHash: 0.2;
|
||||||
|
$stylePlotHash: dashed;
|
||||||
|
$colorPlotAreaBorder: $colorInteriorBorder;
|
||||||
|
$colorPlotLabelFg: darken($colorPlotFg, 20%);
|
||||||
|
$legendCollapsedNameMaxW: 50%;
|
||||||
|
$legendHoverValueBg: rgba($colorBodyFg, 0.2);
|
||||||
|
|
||||||
|
// Tree
|
||||||
|
$colorTreeBg: transparent;
|
||||||
|
$colorItemTreeHoverBg: lighten($colorBodyBg, 10%);
|
||||||
|
$colorItemTreeHoverFg: lighten($colorBodyFg, 10%);
|
||||||
|
$colorItemTreeIcon: $colorKey; // Used
|
||||||
|
$colorItemTreeIconHover: $colorItemTreeIcon; // Used
|
||||||
|
$colorItemTreeFg: $colorBodyFg;
|
||||||
|
$colorItemTreeSelectedBg: darken($colorKey, 15%);
|
||||||
|
$colorItemTreeSelectedFg: $colorBodyBg;
|
||||||
|
$colorItemTreeEditingBg: $editColor;
|
||||||
|
$colorItemTreeEditingFg: $editColorFg;
|
||||||
|
$colorItemTreeVC: $colorBodyFg;
|
||||||
|
$colorItemTreeVCHover: $colorKey;
|
||||||
|
$shdwItemTreeIcon: none;
|
||||||
|
|
||||||
|
// Images
|
||||||
|
$colorThumbHoverBg: $colorItemTreeHoverBg;
|
||||||
|
|
||||||
|
// Scrollbar
|
||||||
|
$scrollbarTrackSize: 7px;
|
||||||
|
$scrollbarTrackShdw: rgba(#000, 0.2) 0 1px 2px;
|
||||||
|
$scrollbarTrackColorBg: rgba(#000, 0.2);
|
||||||
|
$scrollbarThumbColor: darken($colorBodyBg, 50%);
|
||||||
|
$scrollbarThumbColorHov: $colorKey;
|
||||||
|
$scrollbarThumbColorMenu: lighten($colorMenuBg, 10%);
|
||||||
|
$scrollbarThumbColorMenuHov: lighten($scrollbarThumbColorMenu, 2%);
|
||||||
|
|
||||||
|
// Splitter
|
||||||
|
$splitterHandleD: 2px;
|
||||||
|
$splitterHandleHitMargin: 4px;
|
||||||
|
$colorSplitterBaseBg: $colorBodyBg;
|
||||||
|
$colorSplitterBg: lighten($colorSplitterBaseBg, 10%);
|
||||||
|
$colorSplitterFg: $colorBodyBg;
|
||||||
|
$colorSplitterHover: $colorKey;
|
||||||
|
$colorSplitterActive: $colorKey;
|
||||||
|
$splitterBtnD: (16px, 35px); // height, width
|
||||||
|
$splitterBtnColorBg: $colorBtnBg;
|
||||||
|
$splitterBtnColorFg: #999;
|
||||||
|
$splitterBtnLabelColorFg: #666;
|
||||||
|
$splitterCollapsedBtnColorBg: #222;
|
||||||
|
$splitterCollapsedBtnColorFg: #666;
|
||||||
|
$splitterCollapsedBtnColorBgHov: $colorKey;
|
||||||
|
$splitterCollapsedBtnColorFgHov: $colorKeyFg;
|
||||||
|
|
||||||
|
// Mobile
|
||||||
|
$colorMobilePaneLeft: darken($colorBodyBg, 2%);
|
||||||
|
$colorMobilePaneLeftTreeItemBg: rgba($colorBodyFg, 0.1);
|
||||||
|
$colorMobilePaneLeftTreeItemFg: $colorItemTreeFg;
|
||||||
|
$colorMobileSelectListTreeItemBg: rgba(#000, 0.05);
|
||||||
|
|
||||||
|
// About Screen
|
||||||
|
$colorAboutLink: $colorKeySubtle;
|
||||||
|
|
||||||
|
// Loading
|
||||||
|
$colorLoadingFg: #776ba2;
|
||||||
|
$colorLoadingBg: rgba($colorLoadingFg, 0.1);
|
||||||
|
|
||||||
|
// Transitions
|
||||||
|
$transIn: all 50ms ease-in-out;
|
||||||
|
$transOut: all 250ms ease-in-out;
|
||||||
|
$transInBounce: all 200ms cubic-bezier(.47,.01,.25,1.5);
|
||||||
|
$transInBounceBig: all 300ms cubic-bezier(.2,1.6,.6,3);
|
||||||
|
|
||||||
|
// Discrete items, like Notebook entries, Widget rules
|
||||||
|
$createBtnTextTransform: uppercase;
|
||||||
|
|
||||||
|
@mixin discreteItem() {
|
||||||
|
background: rgba($colorBodyFg,0.1);
|
||||||
|
border: none;
|
||||||
|
border-radius: $controlCr;
|
||||||
|
|
||||||
|
.c-input-inline:hover {
|
||||||
|
background: $colorBodyBg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin discreteItemInnerElem() {
|
||||||
|
border: 1px solid rgba(#fff, 0.1);
|
||||||
|
border-radius: $controlCr;
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin themedButton($c: $colorBtnBg) {
|
||||||
|
background: linear-gradient(lighten($c, 5%), $c);
|
||||||
|
box-shadow: rgba(black, 0.5) 0 0 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**************************************************** NOT USED, LEAVE FOR NOW */
|
||||||
|
// Slider controls, not in use
|
||||||
|
/*
|
||||||
|
$sliderColorBase: $colorKey;
|
||||||
|
$sliderColorRangeHolder: rgba(black, 0.07);
|
||||||
|
$sliderColorRange: rgba($sliderColorBase, 0.2);
|
||||||
|
$sliderColorRangeHov: rgba($sliderColorBase, 0.4);
|
||||||
|
$sliderColorKnob: darken($sliderColorBase, 20%);
|
||||||
|
$sliderColorKnobHov: rgba($sliderColorBase, 0.7);
|
||||||
|
$sliderColorRangeValHovBg: $sliderColorRange;
|
||||||
|
$sliderColorRangeValHovFg: $colorBodyFg;
|
||||||
|
$sliderKnobW: 15px;
|
||||||
|
$sliderKnobR: 2px;
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Content status
|
||||||
|
/*
|
||||||
|
$colorAlert: #ff3c00;
|
||||||
|
$colorWarningHi: #990000;
|
||||||
|
$colorWarningLo: #ff9900;
|
||||||
|
$colorDiagnostic: #a4b442;
|
||||||
|
$colorCommand: #3693bd;
|
||||||
|
$colorInfo: #2294a2;
|
||||||
|
$colorOk: #33cc33;
|
||||||
|
*/
|
@ -1,114 +1,134 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
/****************************************************** SNOW THEME CONSTANTS */
|
||||||
|
|
||||||
@import "constants";
|
@import "constants";
|
||||||
|
|
||||||
// Functions
|
// Functions
|
||||||
@function pullForward($c: $colorBodyBg, $p: 20%) {
|
@function buttonBg($c: $colorBtnBg) {
|
||||||
// For dark interfaces, lighter things come forward - opposite for light interfaces
|
@return $c;
|
||||||
@return darken($c, $p);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@function pushBack($c: $colorBodyBg, $p: 20%) {
|
// Constants
|
||||||
// For dark interfaces, darker things move back - opposite for light interfaces
|
|
||||||
@return lighten($c, $p);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Global
|
|
||||||
$fontBaseSize: 12px;
|
$fontBaseSize: 12px;
|
||||||
|
$smallCr: 2px;
|
||||||
|
$controlCr: 3px;
|
||||||
|
$basicCr: 4px;
|
||||||
|
|
||||||
|
// Base colors
|
||||||
$colorBodyBg: #fcfcfc;
|
$colorBodyBg: #fcfcfc;
|
||||||
$colorBodyFg: #666;
|
$colorBodyFg: #666;
|
||||||
$colorGenBg: #fff;
|
$colorGenBg: #fff;
|
||||||
|
$colorHeadBg: #eee;
|
||||||
|
$colorHeadFg: $colorBodyFg;
|
||||||
$colorStatusBarBg: #000;
|
$colorStatusBarBg: #000;
|
||||||
$colorStatusBarFg: #999;
|
$colorStatusBarFg: #999;
|
||||||
$colorStatusBarFgHov: #aaa;
|
$colorStatusBarFgHov: #aaa;
|
||||||
$colorKey: #0099cc;
|
$colorKey: #0099cc;
|
||||||
$colorKeyFilter: brightness(0.9) sepia(1) hue-rotate(145deg) saturate(6);
|
|
||||||
$colorKeySelectedBg: $colorKey;
|
|
||||||
$colorKeyFg: #fff;
|
$colorKeyFg: #fff;
|
||||||
$colorKeyHov: #00c0f6;
|
$colorKeyHov: #00c0f6;
|
||||||
$colorEditAreaBg: #eafaff;
|
$colorKeyFilter: brightness(0.9) sepia(1) hue-rotate(145deg) saturate(6);
|
||||||
$colorEditAreaFg: #4bb1c7;
|
$colorKeyFilterHov: brightness(1) sepia(1) hue-rotate(145deg) saturate(7);
|
||||||
$colorInteriorBorder: rgba($colorBodyFg, 0.2);
|
$colorInteriorBorder: rgba($colorBodyFg, 0.2);
|
||||||
$colorA: #999;
|
$colorA: #999;
|
||||||
$colorAHov: $colorKey;
|
$colorAHov: $colorKey;
|
||||||
$contrastRatioPercent: 40%;
|
|
||||||
$hoverRatioPercent: 10%;
|
// Layout
|
||||||
$basicCr: 4px;
|
$shellMainPad: 4px 0;
|
||||||
$controlCr: 3px;
|
$shellPanePad: 0 7px;
|
||||||
$smallCr: 2px;
|
|
||||||
$overlayCr: 11px;
|
// Status colors, mainly used for messaging and item ancillary symbols
|
||||||
$shdwTextSubtle: rgba(black, 0.2) 0 1px 2px;
|
$colorStatusFg: #999;
|
||||||
|
$colorStatusDefault: #ccc;
|
||||||
|
$colorStatusInfo: #60ba7b;
|
||||||
|
$colorStatusAlert: #ffb66c;
|
||||||
|
$colorStatusError: #da0004;
|
||||||
|
$colorStatusBtnBg: #666;
|
||||||
|
|
||||||
|
// States
|
||||||
|
$colorPausedBg: #ff9900;
|
||||||
|
$colorPausedFg: #fff;
|
||||||
|
$colorOk: #33cc33;
|
||||||
|
|
||||||
|
// Base variations
|
||||||
|
$colorBodyBgSubtle: darken($colorBodyBg, 5%);
|
||||||
|
$colorBodyBgSubtleHov: lighten($colorKey, 50%);
|
||||||
|
$colorKeySubtle: lighten($colorKey, 50%);
|
||||||
|
|
||||||
|
// Time Colors
|
||||||
|
$colorTime: #618cff;
|
||||||
|
$colorTimeBg: $colorTime;
|
||||||
|
$colorTimeFg: $colorBodyBg;
|
||||||
|
$colorTimeHov: lighten($colorTime, 5%);
|
||||||
|
$colorTimeSubtle: lighten($colorTime, 20%);
|
||||||
|
$colorTOI: $colorBodyFg; // was $timeControllerToiLineColor
|
||||||
|
$colorTOIHov: $colorTime; // was $timeControllerToiLineColorHov
|
||||||
|
|
||||||
|
// Edit Colors
|
||||||
|
$editColor: #00c7c3;
|
||||||
|
$editColorFg: $colorBodyFg;
|
||||||
|
$browseBorderSelectableHov: 1px dotted rgba($colorBodyFg, 0.2);
|
||||||
|
$browseShdwSelectableHov: rgba($colorBodyFg, 0.2) 0 0 3px;
|
||||||
|
$browseBorderSelected: 1px solid rgba($colorBodyFg, 0.6);
|
||||||
|
$editBorderSelectable: 1px dotted rgba($editColor, 1);
|
||||||
|
$editBorderSelectableHov: 1px dashed rgba($editColor, 1);
|
||||||
|
$editBorderSelected: 1px solid $editColor;
|
||||||
|
$editBorderDrilledIn: 1px dashed #ff4d9a;
|
||||||
|
$colorGridLines: rgba($editColor, 0.2);
|
||||||
|
|
||||||
|
// UI elements
|
||||||
|
$colorIconAlias: #4af6f3;
|
||||||
|
$colorIconAliasForKeyFilter: #aaa;
|
||||||
|
$colorProgressBarOuter: rgba(#000, 0.1);
|
||||||
|
$colorProgressBarAmt: #0a0;
|
||||||
|
|
||||||
// Buttons and Controls
|
// Buttons and Controls
|
||||||
$btnPad: $interiorMargin, $interiorMargin * 1.25;
|
|
||||||
$colorBtnBg: #aaaaaa;
|
$colorBtnBg: #aaaaaa;
|
||||||
$colorBtnBgHov: pullForward($colorBtnBg, $hoverRatioPercent);
|
$colorBtnBgHov: darken($colorBtnBg, 10%);
|
||||||
$colorBtnFg: #fff;
|
$colorBtnFg: #fff;
|
||||||
$colorBtnFgHov: $colorBtnFg;
|
$colorBtnFgHov: $colorBtnFg;
|
||||||
$colorBtnIcon: #eee;
|
|
||||||
$colorBtnIconHov: $colorBtnFgHov;
|
|
||||||
$colorBtnMajorBg: $colorKey;
|
$colorBtnMajorBg: $colorKey;
|
||||||
$colorBtnMajorBgHov: $colorKeyHov;
|
$colorBtnMajorBgHov: $colorKeyHov;
|
||||||
$colorBtnMajorFg: $colorKeyFg;
|
$colorBtnMajorFg: $colorKeyFg;
|
||||||
$colorBtnMajorFgHov: pushBack($colorBtnMajorFg, $hoverRatioPercent);
|
$colorBtnMajorFgHov: lighten($colorBtnMajorFg, 10%);
|
||||||
$colorBtnCautionBg: #f16f6f;
|
$colorBtnCautionBg: #f16f6f;
|
||||||
$colorBtnCautionBgHov: #f1504e;
|
$colorBtnCautionBgHov: #f1504e;
|
||||||
$colorBtnCautionFg: $colorBtnFg;
|
$colorBtnCautionFg: $colorBtnFg;
|
||||||
$colorClickIcon: $colorKey;
|
$colorClickIcon: $colorKey;
|
||||||
$colorClickIconHov: $colorKeyHov;
|
$colorClickIconBgHov: rgba($colorKey, 0.2);
|
||||||
$colorToggleIcon: rgba($colorClickIcon, 0.5);
|
$colorClickIconFgHov: $colorKeyHov;
|
||||||
$colorToggleIconActive: $colorKey;
|
|
||||||
$colorToggleIconHov: rgba($colorToggleIconActive, 0.5);
|
|
||||||
$colorInvokeMenu: #000;
|
|
||||||
$contrastInvokeMenuPercent: 40%;
|
|
||||||
$shdwBtns: none;
|
|
||||||
$shdwBtnsOverlay: none;
|
|
||||||
$sliderColorBase: $colorKey;
|
|
||||||
$sliderColorRangeHolder: rgba(black, 0.07);
|
|
||||||
$sliderColorRange: rgba($sliderColorBase, 0.2);
|
|
||||||
$sliderColorRangeHov: rgba($sliderColorBase, 0.4);
|
|
||||||
$sliderColorKnob: pushBack($sliderColorBase, 20%);
|
|
||||||
$sliderColorKnobHov: rgba($sliderColorBase, 0.7);
|
|
||||||
$sliderColorRangeValHovBg: $sliderColorRange;
|
|
||||||
$sliderColorRangeValHovFg: $colorBodyFg;
|
|
||||||
$sliderKnobW: 15px;
|
|
||||||
$sliderKnobR: 2px;
|
|
||||||
$timeControllerToiLineColor: $colorBodyFg;
|
|
||||||
$timeControllerToiLineColorHov: #0052b5;
|
|
||||||
$colorTransLucBg: #666; // Used as a visual blocking element over variable backgrounds, like imagery
|
|
||||||
$createBtnTextTransform: uppercase;
|
|
||||||
|
|
||||||
// Foundation Colors
|
|
||||||
$colorAlt1: #776ba2;
|
|
||||||
$colorAlert: #ff3c00;
|
|
||||||
$colorWarningHi: #990000;
|
|
||||||
$colorWarningLo: #ff9900;
|
|
||||||
$colorDiagnostic: #a4b442;
|
|
||||||
$colorCommand: #3693bd;
|
|
||||||
$colorInfo: #2294a2;
|
|
||||||
$colorOk: #33cc33;
|
|
||||||
$colorIconLink: #49dedb;
|
|
||||||
$colorPausedBg: #ff9900;
|
|
||||||
$colorPausedFg: #fff;
|
|
||||||
$colorCreateBtn: $colorKey;
|
|
||||||
$colorGridLines: rgba(#000, 0.05);
|
|
||||||
$colorInvokeMenu: #fff;
|
|
||||||
$colorObjHdrTxt: $colorBodyFg;
|
|
||||||
$colorObjHdrIc: lighten($colorObjHdrTxt, 30%);
|
|
||||||
$colorTick: rgba(black, 0.2);
|
|
||||||
$colorSelectableSelectedPrimary: $colorKey;
|
|
||||||
$colorSelectableHov: rgba($colorBodyFg, 0.4);
|
|
||||||
|
|
||||||
// Menus
|
// Menus
|
||||||
$colorMenuBg: pushBack($colorBodyBg, 10%);
|
$colorMenuBg: lighten($colorBodyBg, 10%);
|
||||||
$colorMenuFg: pullForward($colorMenuBg, 70%);
|
$colorMenuFg: darken($colorMenuBg, 70%);
|
||||||
$colorMenuIc: $colorKey;
|
$colorMenuIc: $colorKey;
|
||||||
$colorMenuHovBg: $colorMenuIc; //pullForward($colorMenuBg, $hoverRatioPercent);
|
$colorMenuHovBg: $colorMenuIc;
|
||||||
$colorMenuHovFg: $colorMenuBg;
|
$colorMenuHovFg: $colorMenuBg;
|
||||||
$colorMenuHovIc: $colorMenuBg;
|
$colorMenuHovIc: $colorMenuBg;
|
||||||
|
$colorMenuElementHilite: darken($colorMenuBg, 10%);
|
||||||
$shdwMenu: rgba(black, 0.5) 0 1px 5px;
|
$shdwMenu: rgba(black, 0.5) 0 1px 5px;
|
||||||
$shdwMenuText: none;
|
$shdwMenuText: none;
|
||||||
$colorCreateMenuLgIcon: $colorKey;
|
$menuItemPad: $interiorMargin, floor($interiorMargin * 1.25);
|
||||||
$colorCreateMenuText: $colorBodyFg;
|
|
||||||
$menuItemPad: ($interiorMargin, nth($btnPad, 2));
|
|
||||||
|
|
||||||
// Palettes and Swatches
|
// Palettes and Swatches
|
||||||
$paletteItemBorderOuterColorSelected: black;
|
$paletteItemBorderOuterColorSelected: black;
|
||||||
@ -119,7 +139,7 @@ $paletteItemBorderInnerColor: rgba($paletteItemBorderOuterColorSelected, 0.3);
|
|||||||
$colorCheck: $colorKey;
|
$colorCheck: $colorKey;
|
||||||
$colorFormRequired: $colorKey;
|
$colorFormRequired: $colorKey;
|
||||||
$colorFormValid: $colorOk;
|
$colorFormValid: $colorOk;
|
||||||
$colorFormError: $colorWarningHi;
|
$colorFormError: #990000;
|
||||||
$colorFormInvalid: #ff2200;
|
$colorFormInvalid: #ff2200;
|
||||||
$colorFormFieldErrorBg: $colorFormError;
|
$colorFormFieldErrorBg: $colorFormError;
|
||||||
$colorFormFieldErrorFg: rgba(#fff, 0.6);
|
$colorFormFieldErrorFg: rgba(#fff, 0.6);
|
||||||
@ -127,32 +147,28 @@ $colorFormLines: rgba(#000, 0.1);
|
|||||||
$colorFormSectionHeader: rgba(#000, 0.05);
|
$colorFormSectionHeader: rgba(#000, 0.05);
|
||||||
$colorInputBg: $colorGenBg;
|
$colorInputBg: $colorGenBg;
|
||||||
$colorInputFg: $colorBodyFg;
|
$colorInputFg: $colorBodyFg;
|
||||||
$colorInputPlaceholder: pushBack($colorBodyFg, 20%);
|
$colorInputPlaceholder: lighten($colorBodyFg, 20%);
|
||||||
$colorFormText: pushBack($colorBodyFg, 10%);
|
$colorFormText: lighten($colorBodyFg, 10%);
|
||||||
$colorInputIcon: pushBack($colorBodyFg, 25%);
|
$colorInputIcon: lighten($colorBodyFg, 25%);
|
||||||
$colorFieldHint: pullForward($colorBodyFg, 40%);
|
$colorFieldHint: darken($colorBodyFg, 40%);
|
||||||
|
$shdwInput: inset rgba(black, 0.7) 0 0 1px;
|
||||||
|
$shdwInputHov: inset rgba(black, 0.7) 0 0 2px;
|
||||||
|
$shdwInputFoc: inset rgba(black, 0.8) 0 0.25px 3px;
|
||||||
|
|
||||||
// Inspector
|
// Inspector
|
||||||
$colorInspectorBg: pullForward($colorBodyBg, 5%);
|
$colorInspectorBg: darken($colorBodyBg, 5%);
|
||||||
$colorInspectorFg: $colorBodyFg;
|
$colorInspectorFg: $colorBodyFg;
|
||||||
$colorInspectorPropName: pushBack($colorBodyFg, 20%);
|
$colorInspectorPropName: lighten($colorBodyFg, 20%);
|
||||||
$colorInspectorPropVal: pullForward($colorInspectorFg, 15%);
|
$colorInspectorPropVal: darken($colorInspectorFg, 15%);
|
||||||
$colorInspectorSectionHeaderBg: pullForward($colorInspectorBg, 5%);
|
$colorInspectorSectionHeaderBg: darken($colorInspectorBg, 5%);
|
||||||
$colorInspectorSectionHeaderFg: pullForward($colorInspectorBg, 40%);
|
$colorInspectorSectionHeaderFg: darken($colorInspectorBg, 40%);
|
||||||
|
|
||||||
// Status colors, mainly used for messaging and item ancillary symbols
|
// Overlay
|
||||||
$colorStatusFg: #999;
|
$overlayColorBg: $colorMenuBg;
|
||||||
$colorStatusDefault: #ccc;
|
$overlayColorFg: $colorMenuFg;
|
||||||
$colorStatusInfo: #60ba7b;
|
$overlayButtonColorBg: $colorBtnBg;
|
||||||
$colorStatusAlert: #ffb66c;
|
$overlayButtonColorFg: $colorBtnFg;
|
||||||
$colorStatusError: #da0004;
|
$overlayCr: $interiorMarginLg;
|
||||||
$colorStatusBtnBg: #666;
|
|
||||||
$colorProgressBarOuter: rgba(#000, 0.1);
|
|
||||||
$colorProgressBarAmt: #0a0;
|
|
||||||
$progressBarHOverlay: 15px;
|
|
||||||
$progressBarStripeW: 20px;
|
|
||||||
$shdwStatusIc: rgba(white, 0.8) 0 0px 5px;
|
|
||||||
$animPausedPulseDur: 1s;
|
|
||||||
|
|
||||||
// Indicator colors
|
// Indicator colors
|
||||||
$colorIndicatorAvailable: $colorKey;
|
$colorIndicatorAvailable: $colorKey;
|
||||||
@ -160,13 +176,9 @@ $colorIndicatorDisabled: #444;
|
|||||||
$colorIndicatorOn: $colorOk;
|
$colorIndicatorOn: $colorOk;
|
||||||
$colorIndicatorOff: #666;
|
$colorIndicatorOff: #666;
|
||||||
|
|
||||||
// Selects
|
|
||||||
$colorSelectBg: $colorBtnBg;
|
|
||||||
$colorSelectFg: $colorBtnFg;
|
|
||||||
|
|
||||||
// Limits and staleness colors//
|
// Limits and staleness colors//
|
||||||
$colorTelemFresh: pullForward($colorBodyFg, 20%);
|
$colorTelemFresh: darken($colorBodyFg, 20%);
|
||||||
$colorTelemStale: pushBack($colorBodyFg, 20%);
|
$colorTelemStale: lighten($colorBodyFg, 20%);
|
||||||
$styleTelemStale: italic;
|
$styleTelemStale: italic;
|
||||||
$colorLimitYellowBg: rgba(#ffaa00, 0.3);
|
$colorLimitYellowBg: rgba(#ffaa00, 0.3);
|
||||||
$colorLimitYellowIc: #ffaa00;
|
$colorLimitYellowIc: #ffaa00;
|
||||||
@ -176,35 +188,22 @@ $colorLimitRedIc: red;
|
|||||||
// Bubble colors
|
// Bubble colors
|
||||||
$colorInfoBubbleBg: $colorMenuBg;
|
$colorInfoBubbleBg: $colorMenuBg;
|
||||||
$colorInfoBubbleFg: #666;
|
$colorInfoBubbleFg: #666;
|
||||||
$colorThumbsBubbleFg: pullForward($colorBodyFg, 10%);
|
|
||||||
$colorThumbsBubbleBg: pullForward($colorBodyBg, 10%);
|
|
||||||
|
|
||||||
// Overlay
|
|
||||||
$colorOvrBlocker: rgba(black, 0.7);//
|
|
||||||
$colorOvrBg: $colorBodyBg;
|
|
||||||
$colorOvrFg: $colorBodyFg;
|
|
||||||
$colorOvrBtnBg: pullForward($colorOvrBg, 40%);
|
|
||||||
$colorOvrBtnFg: #fff;
|
|
||||||
$colorFieldHintOverlay: pullForward($colorOvrBg, 40%);
|
|
||||||
$durLargeViewExpand: 250ms;
|
|
||||||
|
|
||||||
// Items
|
// Items
|
||||||
$colorItemBg: #ddd;
|
$colorItemBg: lighten($colorBtnBg, 20%);
|
||||||
$colorItemBgHov: pullForward($colorItemBg, $hoverRatioPercent * 0.7);
|
$colorItemBgHov: lighten($colorItemBg, 5%);
|
||||||
|
$colorListItemBg: transparent;
|
||||||
|
$colorListItemBgHov: rgba($colorKey, 0.1);
|
||||||
$colorItemFg: $colorBodyFg;
|
$colorItemFg: $colorBodyFg;
|
||||||
$colorItemFgDetails: pushBack($colorItemFg, 15%);
|
$colorItemFgDetails: lighten($colorItemFg, 15%);
|
||||||
$colorItemIc: $colorKey;
|
|
||||||
$colorItemSubIcons: $colorItemFgDetails;
|
|
||||||
$colorItemOpenIcon: $colorItemFgDetails;
|
|
||||||
$shdwItemText: none;
|
$shdwItemText: none;
|
||||||
$colorItemBgSelected: $colorKey;
|
|
||||||
|
|
||||||
// Tabular
|
// Tabular
|
||||||
$colorTabBorder: pullForward($colorBodyBg, 10%);
|
$colorTabBorder: darken($colorBodyBg, 10%);
|
||||||
$colorTabBodyBg: $colorBodyBg;
|
$colorTabBodyBg: $colorBodyBg;
|
||||||
$colorTabBodyFg: pullForward($colorBodyFg, 20%);
|
$colorTabBodyFg: darken($colorBodyFg, 20%);
|
||||||
$colorTabHeaderBg: pullForward($colorBodyBg, 10%);
|
$colorTabHeaderBg: darken($colorBodyBg, 10%);
|
||||||
$colorTabHeaderFg: pullForward($colorBodyFg, 20%);
|
$colorTabHeaderFg: darken($colorBodyFg, 20%);
|
||||||
$colorTabHeaderBorder: $colorBodyBg;
|
$colorTabHeaderBorder: $colorBodyBg;
|
||||||
|
|
||||||
// Plot
|
// Plot
|
||||||
@ -214,24 +213,23 @@ $colorPlotHash: black;
|
|||||||
$opacityPlotHash: 0.2;
|
$opacityPlotHash: 0.2;
|
||||||
$stylePlotHash: dashed;
|
$stylePlotHash: dashed;
|
||||||
$colorPlotAreaBorder: $colorInteriorBorder;
|
$colorPlotAreaBorder: $colorInteriorBorder;
|
||||||
$colorPlotLabelFg: pushBack($colorPlotFg, 20%);
|
$colorPlotLabelFg: lighten($colorPlotFg, 20%);
|
||||||
$legendCollapsedNameMaxW: 50%;
|
$legendCollapsedNameMaxW: 50%;
|
||||||
$legendHoverValueBg: rgba($colorBodyFg, 0.2);
|
$legendHoverValueBg: rgba($colorBodyFg, 0.2);
|
||||||
|
|
||||||
// Tree
|
// Tree
|
||||||
$colorTreeBg: #f0f0f0; // Used
|
$colorTreeBg: transparent;
|
||||||
$colorItemTreeHoverBg: pullForward($colorBodyBg, $hoverRatioPercent);
|
$colorItemTreeHoverBg: darken($colorBodyBg, 10%);
|
||||||
$colorItemTreeHoverFg: pullForward($colorBodyFg, $hoverRatioPercent);
|
$colorItemTreeHoverFg: darken($colorBodyFg, 10%);
|
||||||
$colorItemTreeIcon: $colorKey; // Used
|
$colorItemTreeIcon: $colorKey; // Used
|
||||||
$colorItemTreeIconHover: $colorItemTreeIcon; // Used
|
$colorItemTreeIconHover: $colorItemTreeIcon; // Used
|
||||||
$colorItemTreeFg: $colorBodyFg;
|
$colorItemTreeFg: $colorBodyFg;
|
||||||
$colorItemTreeSelectedBg: pushBack($colorKey, 15%);
|
$colorItemTreeSelectedBg: lighten($colorKey, 15%);
|
||||||
$colorItemTreeSelectedFg: $colorBodyBg;
|
$colorItemTreeSelectedFg: $colorBodyBg;
|
||||||
$colorItemTreeEditingBg: #caf1ff;
|
$colorItemTreeEditingBg: $editColor;
|
||||||
$colorItemTreeEditingFg: $colorEditAreaFg;
|
$colorItemTreeEditingFg: $editColorFg;
|
||||||
$colorItemTreeVC: $colorBodyFg;
|
$colorItemTreeVC: $colorBodyFg;
|
||||||
$colorItemTreeVCHover: $colorKey;
|
$colorItemTreeVCHover: $colorKey;
|
||||||
$colorItemTreeSelectedVC: $colorBodyBg;
|
|
||||||
$shdwItemTreeIcon: none;
|
$shdwItemTreeIcon: none;
|
||||||
|
|
||||||
// Images
|
// Images
|
||||||
@ -243,69 +241,90 @@ $scrollbarTrackShdw: rgba(#000, 0.2) 0 1px 2px;
|
|||||||
$scrollbarTrackColorBg: rgba(#000, 0.2);
|
$scrollbarTrackColorBg: rgba(#000, 0.2);
|
||||||
$scrollbarThumbColor: darken($colorBodyBg, 50%);
|
$scrollbarThumbColor: darken($colorBodyBg, 50%);
|
||||||
$scrollbarThumbColorHov: $colorKey;
|
$scrollbarThumbColorHov: $colorKey;
|
||||||
$scrollbarThumbColorOverlay: darken($colorOvrBg, 50%);
|
$scrollbarThumbColorMenu: darken($colorMenuBg, 10%);
|
||||||
$scrollbarThumbColorOverlayHov: $scrollbarThumbColorHov;
|
$scrollbarThumbColorMenuHov: darken($scrollbarThumbColorMenu, 2%);
|
||||||
$scrollbarThumbColorMenu: pullForward($colorMenuBg, 10%);
|
|
||||||
$scrollbarThumbColorMenuHov: pullForward($scrollbarThumbColorMenu, 2%);
|
|
||||||
|
|
||||||
// Splitter
|
// Splitter
|
||||||
$splitterD: 7px;
|
|
||||||
$splitterHandleD: 2px;
|
$splitterHandleD: 2px;
|
||||||
$splitterHandleHitMargin: 4px;
|
$splitterHandleHitMargin: 4px;
|
||||||
$splitterGrippyD: ($splitterHandleD - 4, 75px, 50px); // thickness, length, min-length
|
|
||||||
$colorSplitterBaseBg: $colorBodyBg;
|
$colorSplitterBaseBg: $colorBodyBg;
|
||||||
$colorSplitterBg: pullForward($colorSplitterBaseBg, 20%);
|
$colorSplitterBg: darken($colorSplitterBaseBg, 20%);
|
||||||
$colorSplitterFg: $colorBodyBg;
|
$colorSplitterFg: $colorBodyBg;
|
||||||
$colorSplitterHover: $colorKey; // pullForward($colorSplitterBg, $hoverRatioPercent * 2);
|
$colorSplitterHover: $colorKey;
|
||||||
$colorSplitterActive: $colorKey;
|
$colorSplitterActive: $colorKey;
|
||||||
$splitterBtnD: (16px, 35px); // height, width
|
$splitterBtnD: (16px, 35px); // height, width
|
||||||
$splitterBtnColorBg: #eee;
|
$splitterBtnColorBg: $colorBtnBg;
|
||||||
$splitterBtnColorFg: #999;
|
$splitterBtnColorFg: #ddd;
|
||||||
$splitterBtnColorHoverBg: rgba($colorKey, 1);
|
$splitterBtnLabelColorFg: #999;
|
||||||
$splitterBtnColorHoverFg: $colorBodyBg;
|
$splitterCollapsedBtnColorBg: #ccc;
|
||||||
$colorSplitterGrippy: pullForward($colorSplitterBaseBg, 30%);
|
$splitterCollapsedBtnColorFg: #666;
|
||||||
$splitterShdw: none;
|
$splitterCollapsedBtnColorBgHov: $colorKey;
|
||||||
$splitterEndCr: none;
|
$splitterCollapsedBtnColorFgHov: $colorKeyFg;
|
||||||
|
|
||||||
// Minitabs
|
|
||||||
$colorMiniTabBg: $colorSplitterBg;
|
|
||||||
$colorMiniTabFg: pullForward($colorMiniTabBg, 30%);
|
|
||||||
$colorMiniTabBgHov: $colorSplitterHover;
|
|
||||||
$colorMiniTabFgHov: #fff;
|
|
||||||
|
|
||||||
// Mobile
|
// Mobile
|
||||||
$colorMobilePaneLeft: darken($colorBodyBg, 2%);
|
$colorMobilePaneLeft: darken($colorBodyBg, 2%);
|
||||||
$colorMobilePaneLeftTreeItemBg: rgba($colorBodyFg, 0.1); //pullForward($colorMobilePaneLeft, 3%);
|
$colorMobilePaneLeftTreeItemBg: rgba($colorBodyFg, 0.1);
|
||||||
$colorMobilePaneLeftTreeItemFg: $colorItemTreeFg;
|
$colorMobilePaneLeftTreeItemFg: $colorItemTreeFg;
|
||||||
$colorMobileSelectListTreeItemBg: rgba(#000, 0.05);
|
$colorMobileSelectListTreeItemBg: rgba(#000, 0.05);
|
||||||
|
|
||||||
// Datetime Picker, Calendar
|
|
||||||
$colorCalCellHovBg: $colorKey;
|
|
||||||
$colorCalCellHovFg: $colorKeyFg;
|
|
||||||
$colorCalCellSelectedBg: $colorItemTreeSelectedBg;
|
|
||||||
$colorCalCellSelectedFg: $colorItemTreeSelectedFg;
|
|
||||||
$colorCalCellInMonthBg: pullForward($colorMenuBg, 5%);
|
|
||||||
|
|
||||||
// About Screen
|
// About Screen
|
||||||
$colorAboutLink: #84b3ff;
|
$colorAboutLink: $colorKeySubtle;
|
||||||
|
|
||||||
// Loading
|
// Loading
|
||||||
$colorLoadingFg: $colorAlt1;
|
$colorLoadingFg: #776ba2;
|
||||||
$colorLoadingBg: rgba($colorLoadingFg, 0.1);
|
$colorLoadingBg: rgba($colorLoadingFg, 0.1);
|
||||||
|
|
||||||
// Transitions
|
// Transitions
|
||||||
$transIn: all 50ms ease-in;
|
$transIn: all 50ms ease-in-out;
|
||||||
$transOut: all 250ms ease-out;
|
$transOut: all 250ms ease-in-out;
|
||||||
|
$transInBounce: all 200ms cubic-bezier(.47,.01,.25,1.5);
|
||||||
|
$transInBounceBig: all 300ms cubic-bezier(.2,1.6,.6,3);
|
||||||
|
|
||||||
// Discrete items, like Notebook entries, Widget rules
|
// Discrete items, like Notebook entries, Widget rules
|
||||||
|
$createBtnTextTransform: uppercase;
|
||||||
|
|
||||||
@mixin discreteItem() {
|
@mixin discreteItem() {
|
||||||
background: rgba($colorBodyFg,0.1);
|
background: rgba($colorBodyFg,0.1);
|
||||||
border: 1px solid $colorInteriorBorder;
|
border: 1px solid $colorInteriorBorder;
|
||||||
border-radius: $controlCr;
|
border-radius: $controlCr;
|
||||||
|
|
||||||
.c-input-inline:hover {
|
.c-input-inline:hover {
|
||||||
background: $colorBodyBg;
|
background: $colorBodyBg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin discreteItemInnerElem() {
|
@mixin discreteItemInnerElem() {
|
||||||
border: 1px solid $colorBodyBg;
|
border: 1px solid $colorBodyBg;
|
||||||
border-radius: $controlCr; }
|
border-radius: $controlCr;
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin themedButton($c: $colorBtnBg) {
|
||||||
|
background: $c;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**************************************************** NOT USED, LEAVE FOR NOW */
|
||||||
|
// Slider controls, not in use
|
||||||
|
/*
|
||||||
|
$sliderColorBase: $colorKey;
|
||||||
|
$sliderColorRangeHolder: rgba(black, 0.07);
|
||||||
|
$sliderColorRange: rgba($sliderColorBase, 0.2);
|
||||||
|
$sliderColorRangeHov: rgba($sliderColorBase, 0.4);
|
||||||
|
$sliderColorKnob: lighten($sliderColorBase, 20%);
|
||||||
|
$sliderColorKnobHov: rgba($sliderColorBase, 0.7);
|
||||||
|
$sliderColorRangeValHovBg: $sliderColorRange;
|
||||||
|
$sliderColorRangeValHovFg: $colorBodyFg;
|
||||||
|
$sliderKnobW: 15px;
|
||||||
|
$sliderKnobR: 2px;
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Content status
|
||||||
|
/*
|
||||||
|
$colorAlert: #ff3c00;
|
||||||
|
$colorWarningHi: #990000;
|
||||||
|
$colorWarningLo: #ff9900;
|
||||||
|
$colorDiagnostic: #a4b442;
|
||||||
|
$colorCommand: #3693bd;
|
||||||
|
$colorInfo: #2294a2;
|
||||||
|
$colorOk: #33cc33;
|
||||||
|
*/
|
||||||
|
@ -1,3 +1,25 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
/************************** PATHS */
|
/************************** PATHS */
|
||||||
// Paths need to be relative to /platform/commonUI/theme/<theme-name>/css/ directory
|
// Paths need to be relative to /platform/commonUI/theme/<theme-name>/css/ directory
|
||||||
$dirFonts: 'fonts/';
|
$dirFonts: 'fonts/';
|
||||||
@ -18,15 +40,21 @@ $inputTextP: $inputTextPTopBtm $inputTextPLeftRight;
|
|||||||
$menuLineH: 1.5rem;
|
$menuLineH: 1.5rem;
|
||||||
$treeItemIndent: 16px;
|
$treeItemIndent: 16px;
|
||||||
$treeTypeIconW: 18px;
|
$treeTypeIconW: 18px;
|
||||||
|
$overlayOuterMargin: 5%;
|
||||||
|
$overlayInnerMargin: 25px;
|
||||||
|
|
||||||
/*************** Items */
|
/*************** Items */
|
||||||
$itemPadLR: 5px;
|
$itemPadLR: 5px;
|
||||||
$ueBrowseGridItemLg: 200px;
|
$gridItemDesk: 175px;
|
||||||
|
$gridItemMobile: 32px;
|
||||||
/*************** Tabular */
|
/*************** Tabular */
|
||||||
$tabularHeaderH: 22px;
|
$tabularHeaderH: 22px;
|
||||||
$tabularTdPadLR: $itemPadLR;
|
$tabularTdPadLR: $itemPadLR;
|
||||||
$tabularTdPadTB: 2px;
|
$tabularTdPadTB: 2px;
|
||||||
|
|
||||||
|
/************************** MOBILE */
|
||||||
|
$mobileMenuIconD: 24px; // Used
|
||||||
|
$mobileTreeItemH: 35px; // Used
|
||||||
|
|
||||||
/************************** VISUAL */
|
/************************** VISUAL */
|
||||||
$controlDisabledOpacity: 0.3;
|
$controlDisabledOpacity: 0.3;
|
||||||
@ -152,7 +180,7 @@ $glyph-icon-notebook: '\e1131';
|
|||||||
$bg-icon-activity: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23666666' d='M288 32H160l160 160H174.872C152.74 153.742 111.377 128 64 128H0v256h64c47.377 0 88.74-25.742 110.872-64H320L160 480h128l224-224L288 32z'/%3e%3c/svg%3e");
|
$bg-icon-activity: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23666666' d='M288 32H160l160 160H174.872C152.74 153.742 111.377 128 64 128H0v256h64c47.377 0 88.74-25.742 110.872-64H320L160 480h128l224-224L288 32z'/%3e%3c/svg%3e");
|
||||||
$bg-icon-activity-mode: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23666666' d='M256 0C148.6 0 56.6 66.2 18.6 160H64c28.4 0 54 12.4 71.5 32H256l-96-96h128l160 160-160 160H160l96-96H135.5C118 339.6 92.4 352 64 352H18.6c38 93.8 129.9 160 237.4 160 141.4 0 256-114.6 256-256S397.4 0 256 0z'/%3e%3c/svg%3e");
|
$bg-icon-activity-mode: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23666666' d='M256 0C148.6 0 56.6 66.2 18.6 160H64c28.4 0 54 12.4 71.5 32H256l-96-96h128l160 160-160 160H160l96-96H135.5C118 339.6 92.4 352 64 352H18.6c38 93.8 129.9 160 237.4 160 141.4 0 256-114.6 256-256S397.4 0 256 0z'/%3e%3c/svg%3e");
|
||||||
$bg-icon-autoflow-tabular: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23666666' d='M96 0C43.2 0 0 43.2 0 96v320c0 52.8 43.2 96 96 96h32V0H96zM192 0h128v512H192zM416 0h-32v352h128V96c0-52.8-43.2-96-96-96z'/%3e%3c/svg%3e");
|
$bg-icon-autoflow-tabular: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23666666' d='M96 0C43.2 0 0 43.2 0 96v320c0 52.8 43.2 96 96 96h32V0H96zM192 0h128v512H192zM416 0h-32v352h128V96c0-52.8-43.2-96-96-96z'/%3e%3c/svg%3e");
|
||||||
$bg-icon-clock: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23666666' d='M256 0C114.62 0 0 114.62 0 256s114.62 256 256 256 256-114.62 256-256S397.38 0 256 0zm135 345a36 36 0 0 1-31.21 18 35.83 35.83 0 0 1-18-4.83l-110.85-64-.14-.08q-.6-.35-1.19-.73l-.43-.28-.93-.64-.63-.45-.63-.48-.85-.67-.32-.27c-.36-.3-.72-.61-1.07-.92a35.76 35.76 0 0 1-6.52-7.9c-.14-.23-.27-.47-.41-.7s-.29-.49-.43-.74a35.75 35.75 0 0 1-3.58-9.59v-.06c-.1-.46-.19-.92-.27-1.38 0-.14-.05-.28-.08-.42-.06-.35-.11-.71-.15-1.07s-.07-.52-.1-.79-.05-.51-.07-.77l-.09-1.12v-.52-1.39V81a36 36 0 0 1 36-36 36 36 0 0 1 36 36v161.22l92.85 53.61A36 36 0 0 1 391 345z' fill='%2300a14b'/%3e%3c/svg%3e");
|
$bg-icon-clock: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23666666' d='M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256 256-114.6 256-256S397.4 0 256 0zm135 345c-6.4 11.1-18.3 18-31.2 18-6.3 0-12.5-1.7-18-4.8l-110.9-64-.1-.1c-.4-.2-.8-.5-1.2-.7l-.4-.3-.9-.6-.6-.5-.6-.5-.9-.7-.3-.3c-.4-.3-.7-.6-1.1-.9-2.5-2.3-4.7-5-6.5-7.9-.1-.2-.3-.5-.4-.7s-.3-.5-.4-.7c-1.6-3-2.9-6.2-3.6-9.6v-.1c-.1-.5-.2-.9-.3-1.4 0-.1 0-.3-.1-.4-.1-.3-.1-.7-.1-1.1s-.1-.5-.1-.8 0-.5-.1-.8-.1-.8-.1-1.1v-.5-1.4V81c0-19.9 16.1-36 36-36s36 16.1 36 36v161.2l92.9 53.6c17.1 10 22.9 32 13 49.2z'/%3e%3c/svg%3e");
|
||||||
$bg-icon-database: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23666666' d='M256 256C114.615 256 0 213.019 0 160v256c0 53.019 114.615 96 256 96s256-42.981 256-96V160c0 53.019-114.615 96-256 96z'/%3e%3cellipse fill='%23666666' cx='256' cy='96' rx='256' ry='96'/%3e%3c/svg%3e");
|
$bg-icon-database: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23666666' d='M256 256C114.615 256 0 213.019 0 160v256c0 53.019 114.615 96 256 96s256-42.981 256-96V160c0 53.019-114.615 96-256 96z'/%3e%3cellipse fill='%23666666' cx='256' cy='96' rx='256' ry='96'/%3e%3c/svg%3e");
|
||||||
$bg-icon-database-query: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23666666' d='M341.76 409.643C316.369 423.871 287.118 432 256 432c-97.047 0-176-78.953-176-176S158.953 80 256 80s176 78.953 176 176c0 31.118-8.129 60.369-22.357 85.76l95.846 95.846C509.747 430.661 512 423.429 512 416V96c0-53.019-114.615-96-256-96S0 42.981 0 96v320c0 53.019 114.615 96 256 96 63.055 0 120.774-8.554 165.388-22.73l-79.628-79.627z'/%3e%3cpath fill='%23666666' d='M176 256c0 44.112 35.888 80 80 80s80-35.888 80-80-35.888-80-80-80-80 35.888-80 80z'/%3e%3c/svg%3e");
|
$bg-icon-database-query: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23666666' d='M341.76 409.643C316.369 423.871 287.118 432 256 432c-97.047 0-176-78.953-176-176S158.953 80 256 80s176 78.953 176 176c0 31.118-8.129 60.369-22.357 85.76l95.846 95.846C509.747 430.661 512 423.429 512 416V96c0-53.019-114.615-96-256-96S0 42.981 0 96v320c0 53.019 114.615 96 256 96 63.055 0 120.774-8.554 165.388-22.73l-79.628-79.627z'/%3e%3cpath fill='%23666666' d='M176 256c0 44.112 35.888 80 80 80s80-35.888 80-80-35.888-80-80-80-80 35.888-80 80z'/%3e%3c/svg%3e");
|
||||||
$bg-icon-dataset: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23666666' d='M448 96H288l-54.6-54.6-18.7-18.7C202.2 10.2 177.6 0 160 0H32C14.4 0 0 14.4 0 32v192c0-35.2 28.8-64 64-64h384c35.2 0 64 28.8 64 64v-64c0-35.2-28.8-64-64-64zM448 224H64c-35.2 0-64 28.8-64 64v160c0 35.2 28.8 64 64 64h384c35.2 0 64-28.8 64-64V288c0-35.2-28.8-64-64-64zM160 448H96V288h64v160zm128 0h-64V288h64v160zm128 0h-64V288h64v160z'/%3e%3c/svg%3e");
|
$bg-icon-dataset: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23666666' d='M448 96H288l-54.6-54.6-18.7-18.7C202.2 10.2 177.6 0 160 0H32C14.4 0 0 14.4 0 32v192c0-35.2 28.8-64 64-64h384c35.2 0 64 28.8 64 64v-64c0-35.2-28.8-64-64-64zM448 224H64c-35.2 0-64 28.8-64 64v160c0 35.2 28.8 64 64 64h384c35.2 0 64-28.8 64-64V288c0-35.2-28.8-64-64-64zM160 448H96V288h64v160zm128 0h-64V288h64v160zm128 0h-64V288h64v160z'/%3e%3c/svg%3e");
|
||||||
|
@ -20,122 +20,14 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
// VERSION MANUALLY RESTORED FROM VUE-LAYOUT
|
|
||||||
|
|
||||||
/******************************************************** PLACEHOLDERS */
|
|
||||||
@mixin cControl() {
|
|
||||||
$fs: 1em;
|
|
||||||
@include userSelectNone();
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
&:before,
|
|
||||||
&:after {
|
|
||||||
font-family: symbolsfont;
|
|
||||||
display: block;
|
|
||||||
flex: 0 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:after {
|
|
||||||
font-size: 0.8em;
|
|
||||||
}
|
|
||||||
|
|
||||||
[class*="__label"] {
|
|
||||||
@include ellipsize();
|
|
||||||
display: block;
|
|
||||||
line-height: $fs; // Remove effect on top and bottom padding
|
|
||||||
font-size: $fs;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin cButton() {
|
|
||||||
@include cControl();
|
|
||||||
background: $colorBtnBg;
|
|
||||||
border-radius: $controlCr;
|
|
||||||
color: $colorBtnFg;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: nth($btnPad, 1) nth($btnPad, 2);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $colorBtnBgHov;
|
|
||||||
color: $colorBtnFgHov;
|
|
||||||
}
|
|
||||||
|
|
||||||
&[class*="--major"] {
|
|
||||||
background: $colorBtnMajorBg;
|
|
||||||
color: $colorBtnMajorFg;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $colorBtnMajorBgHov;
|
|
||||||
color: $colorBtnMajorFgHov;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&[class*='--caution'] {
|
|
||||||
background: $colorBtnCautionBg;
|
|
||||||
color: $colorBtnCautionFg;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $colorBtnCautionBgHov;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin cClickIcon() {
|
|
||||||
// A clickable element that just includes the icon, no background
|
|
||||||
// Padding is included to facilitate a bigger hit area
|
|
||||||
// Make the icon bigger relative to its container
|
|
||||||
@include cControl();
|
|
||||||
$pLR: 3px;
|
|
||||||
$pTB: 3px;
|
|
||||||
border-radius: $controlCr;
|
|
||||||
color: $colorKey;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: $pTB $pLR ;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: rgba($colorKey, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:before,
|
|
||||||
*:before {
|
|
||||||
// *:before handles any nested containers that may contain glyph elements
|
|
||||||
// Needed for c-togglebutton.
|
|
||||||
font-size: 1.3em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin cCtrlWrapper {
|
|
||||||
// Provides a wrapper around buttons and other controls
|
|
||||||
// Contains control and provides positioning context for contained menu/palette.
|
|
||||||
// Wraps --menu elements, contains button and menu
|
|
||||||
overflow: visible;
|
|
||||||
|
|
||||||
.c-menu {
|
|
||||||
// Default position of contained menu
|
|
||||||
top: 100%; left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&[class*='--menus-up'] {
|
|
||||||
.c-menu {
|
|
||||||
top: auto; bottom: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&[class*='--menus-left'] {
|
|
||||||
.c-menu {
|
|
||||||
left: auto; right: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/********* Buttons */
|
/********* Buttons */
|
||||||
// Optionally can include icon in :before via markup
|
// Optionally can include icon in :before via markup
|
||||||
.c-button,
|
|
||||||
.c-button--menu,
|
|
||||||
button {
|
button {
|
||||||
|
@include htmlInputReset();
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-button,
|
||||||
|
.c-button--menu {
|
||||||
@include cButton();
|
@include cButton();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,7 +97,7 @@ button {
|
|||||||
font-size: 0.7em;
|
font-size: 0.7em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/********* Disclosure Triangle */
|
/********* Disclosure Triangle */
|
||||||
// Provides an arrow icon that when clicked expands an element to reveal its contents.
|
// Provides an arrow icon that when clicked expands an element to reveal its contents.
|
||||||
// Used in tree items. Always placed BEFORE an element.
|
// Used in tree items. Always placed BEFORE an element.
|
||||||
.c-disclosure-triangle {
|
.c-disclosure-triangle {
|
||||||
@ -235,13 +127,45 @@ button {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/******************************************************** FORM ELEMENTS */
|
/******************************************************** FORM ELEMENTS */
|
||||||
/********* Inline inputs */
|
input, textarea {
|
||||||
.c-input-inline {
|
font-family: inherit;
|
||||||
|
font-weight: inherit;
|
||||||
|
letter-spacing: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=text],
|
||||||
|
input[type=search],
|
||||||
|
input[type=number] {
|
||||||
|
@include reactive-input();
|
||||||
|
padding: $inputTextP;
|
||||||
|
&.numeric {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=number]::-webkit-inner-spin-button,
|
||||||
|
input[type=number]::-webkit-outer-spin-button {
|
||||||
|
margin-right: -5px !important;
|
||||||
|
margin-top: -1px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-input {
|
||||||
|
&--datetime {
|
||||||
|
// Sized for values such as 2018-09-28 22:32:33.468Z
|
||||||
|
width: 160px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--hrs-min-sec {
|
||||||
|
// Sized for values such as 00:25:00
|
||||||
|
width: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-inline,
|
||||||
|
&--inline {
|
||||||
// A text input or contenteditable element that indicates edit affordance on hover and looks like an input on focus
|
// A text input or contenteditable element that indicates edit affordance on hover and looks like an input on focus
|
||||||
@include input-base();
|
@include reactive-input($bg: transparent);
|
||||||
border: 1px solid transparent;
|
box-shadow: none;
|
||||||
display: block !important;
|
display: block !important;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
@ -256,15 +180,21 @@ button {
|
|||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
&:focus {
|
&:focus {
|
||||||
|
background: $colorInputBg;
|
||||||
padding-left: $inputTextPLeftRight;
|
padding-left: $inputTextPLeftRight;
|
||||||
padding-right: $inputTextPLeftRight;
|
padding-right: $inputTextPLeftRight;
|
||||||
}
|
}
|
||||||
&:hover {
|
|
||||||
border-color: rgba($colorBodyFg, 0.2);
|
|
||||||
}
|
}
|
||||||
&:focus {
|
|
||||||
@include nice-input($shdw: rgba(0, 0, 0, 0.6) 0 1px 3px);
|
&--labeled {
|
||||||
border-color: transparent;
|
// TODO: replace .c-labeled-input with this
|
||||||
|
// An input used in the Toolbar
|
||||||
|
// Assumes label is before the input
|
||||||
|
@include cControl();
|
||||||
|
|
||||||
|
input {
|
||||||
|
margin-left: $interiorMarginSm;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -302,11 +232,11 @@ button {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@mixin menuInner() {
|
@mixin menuInner() {
|
||||||
color: $colorMenuFg;
|
|
||||||
li {
|
li {
|
||||||
@include cControl();
|
@include cControl();
|
||||||
justify-content: start;
|
justify-content: start;
|
||||||
color: $colorMenuFg;
|
color: $colorMenuFg;
|
||||||
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: nth($menuItemPad, 1) nth($menuItemPad, 2);
|
padding: nth($menuItemPad, 1) nth($menuItemPad, 2);
|
||||||
transition: $transIn;
|
transition: $transIn;
|
||||||
@ -371,11 +301,6 @@ button {
|
|||||||
justify-content: stretch;
|
justify-content: stretch;
|
||||||
|
|
||||||
.l-item-description {
|
.l-item-description {
|
||||||
&__icon,
|
|
||||||
&__description {
|
|
||||||
//flex: 1 1 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__name,
|
&__name,
|
||||||
&__description {
|
&__description {
|
||||||
margin-top: $interiorMarginLg;
|
margin-top: $interiorMarginLg;
|
||||||
@ -387,6 +312,7 @@ button {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__name {
|
&__name {
|
||||||
|
color: $colorMenuFg;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
font-size: 1.25em;
|
font-size: 1.25em;
|
||||||
}
|
}
|
||||||
@ -550,3 +476,9 @@ button {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/***************************************************** SLIDERS */
|
||||||
|
.c-slider {
|
||||||
|
@include cControl();
|
||||||
|
> * + * { margin-left: $interiorMargin; }
|
||||||
|
}
|
||||||
|
@ -1,3 +1,25 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
/************************** FONTS */
|
/************************** FONTS */
|
||||||
@font-face {
|
@font-face {
|
||||||
/*
|
/*
|
||||||
@ -70,30 +92,16 @@ body.desktop {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.overlay ::-webkit-scrollbar-thumb {
|
::-webkit-scrollbar-corner {
|
||||||
background: $scrollbarThumbColorOverlay;
|
background: transparent;
|
||||||
&:hover {
|
|
||||||
background: $scrollbarThumbColorOverlayHov;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu ::-webkit-scrollbar-thumb {
|
.c-menu ::-webkit-scrollbar-thumb {
|
||||||
background: $scrollbarThumbColorMenu;
|
background: $scrollbarThumbColorMenu;
|
||||||
&:hover {
|
&:hover {
|
||||||
background: $scrollbarThumbColorMenuHov;
|
background: $scrollbarThumbColorMenuHov;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-corner {
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type=number]::-webkit-inner-spin-button,
|
|
||||||
input[type=number]::-webkit-outer-spin-button {
|
|
||||||
//margin: -1px -5px inherit -5px !important;
|
|
||||||
margin-right: -5px !important;
|
|
||||||
margin-top: -1px !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/************************** HTML ENTITIES */
|
/************************** HTML ENTITIES */
|
||||||
@ -125,22 +133,6 @@ em {
|
|||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
input, textarea {
|
|
||||||
font-family: inherit;
|
|
||||||
font-weight: inherit;
|
|
||||||
letter-spacing: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type=text],
|
|
||||||
input[type=search],
|
|
||||||
input[type=number] {
|
|
||||||
@include nice-input();
|
|
||||||
padding: $inputTextP;
|
|
||||||
&.numeric {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
h1, h2, h3 {
|
h1, h2, h3 {
|
||||||
letter-spacing: 0.04em;
|
letter-spacing: 0.04em;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@ -176,7 +168,6 @@ li {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/******************************************************** HAS */
|
/******************************************************** HAS */
|
||||||
// Local Controls: Controls placed in proximity to or overlaid on components and views
|
// Local Controls: Controls placed in proximity to or overlaid on components and views
|
||||||
body.desktop .has-local-controls {
|
body.desktop .has-local-controls {
|
||||||
@ -290,6 +281,21 @@ a.disabled {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.no-selection {
|
||||||
|
// aka selection = "None". Used in palettes and their menu buttons.
|
||||||
|
$c: red;
|
||||||
|
$s: 48%;
|
||||||
|
$e: 52%;
|
||||||
|
background-image: linear-gradient(-45deg,
|
||||||
|
transparent $s - 5%,
|
||||||
|
$c $s,
|
||||||
|
$c $e,
|
||||||
|
transparent $e + 5%
|
||||||
|
);
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: contain;
|
||||||
|
}
|
||||||
|
|
||||||
.scrolling,
|
.scrolling,
|
||||||
.scroll {
|
.scroll {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
@ -1,3 +1,25 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
@mixin glyphBefore($unicode, $family: 'symbolsfont') {
|
@mixin glyphBefore($unicode, $family: 'symbolsfont') {
|
||||||
&:before {
|
&:before {
|
||||||
content: $unicode;
|
content: $unicode;
|
||||||
|
@ -20,8 +20,6 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
// VERSION MANUALLY RESTORED FROM VUE-LAYOUT
|
|
||||||
|
|
||||||
/************************** VISUALS */
|
/************************** VISUALS */
|
||||||
@mixin ancillaryIcon($d, $c) {
|
@mixin ancillaryIcon($d, $c) {
|
||||||
// Used for small icons used in combination with larger icons,
|
// Used for small icons used in combination with larger icons,
|
||||||
@ -33,6 +31,20 @@
|
|||||||
width: $d;
|
width: $d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@mixin isAlias() {
|
||||||
|
&:after {
|
||||||
|
color:$colorIconAlias;
|
||||||
|
content: $glyph-icon-link;
|
||||||
|
display: block;
|
||||||
|
font-family: symbolsfont;
|
||||||
|
position: absolute;
|
||||||
|
text-shadow: rgba(black, 0.5) 0 1px 4px;
|
||||||
|
top: auto; left: 0; bottom: 10%; right: auto;
|
||||||
|
transform-origin: left bottom;
|
||||||
|
transform: scale(0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@mixin bgDiagonalStripes($c: yellow, $a: 0.1, $d: 40px) {
|
@mixin bgDiagonalStripes($c: yellow, $a: 0.1, $d: 40px) {
|
||||||
background-image: linear-gradient(-45deg,
|
background-image: linear-gradient(-45deg,
|
||||||
rgba($c, $a) 25%, transparent 25%,
|
rgba($c, $a) 25%, transparent 25%,
|
||||||
@ -101,33 +113,21 @@
|
|||||||
top: $m; right: $m; bottom: $m; left: $m;
|
top: $m; right: $m; bottom: $m; left: $m;
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin gridTwoColumn() {
|
@mixin propertiesHeader() {
|
||||||
display: grid;
|
|
||||||
grid-row-gap: 0;
|
|
||||||
grid-template-columns: 1fr 2fr;
|
|
||||||
align-items: start;
|
|
||||||
|
|
||||||
[class*="header"] {
|
|
||||||
border-radius: $smallCr;
|
border-radius: $smallCr;
|
||||||
background-color: $colorInspectorSectionHeaderBg;
|
background-color: $colorInspectorSectionHeaderBg;
|
||||||
color: $colorInspectorSectionHeaderFg;
|
color: $colorInspectorSectionHeaderFg;
|
||||||
|
font-weight: normal;
|
||||||
margin: 0 0 $interiorMarginSm 0;
|
margin: 0 0 $interiorMarginSm 0;
|
||||||
padding: $interiorMarginSm $interiorMargin;
|
padding: $interiorMarginSm $interiorMargin;
|
||||||
|
text-transform: uppercase;
|
||||||
&:not(:first-child) {
|
|
||||||
// Allow multiple headers within a component
|
|
||||||
margin-top: $interiorMarginLg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[class*="span-all"],
|
|
||||||
[class*="header"] {
|
|
||||||
@include gridTwoColumnSpanCols();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin gridTwoColumnSpanCols() {
|
@mixin modalFullScreen() {
|
||||||
grid-column: 1 / 3;
|
// Optional modifier that makes a c-menu more mobile-friendly
|
||||||
|
position: fixed;
|
||||||
|
border-radius: 0;
|
||||||
|
top: 0; right: 0; bottom: 0; left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/************************** TEXT */
|
/************************** TEXT */
|
||||||
@ -143,41 +143,190 @@
|
|||||||
unicode-bidi:bidi-override;
|
unicode-bidi:bidi-override;
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin test($c: #ffcc00, $a: 0.2) {
|
/************************** CONTROLS, BUTTONS */
|
||||||
background-color: rgba($c, $a) !important;
|
@mixin hover {
|
||||||
|
body.desktop & {
|
||||||
|
&:hover {
|
||||||
|
@content
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/************************** CONTROLS, BUTTONS */
|
@mixin htmlInputReset() {
|
||||||
@mixin input-base() {
|
|
||||||
appearance: none;
|
appearance: none;
|
||||||
|
background: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: $controlCr;
|
border-radius: 0;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
text-align: inherit;
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: 0;
|
outline: 0;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin input-base() {
|
||||||
|
@include htmlInputReset();
|
||||||
|
border-radius: $controlCr;
|
||||||
|
|
||||||
&.error {
|
&.error {
|
||||||
background-color: $colorFormFieldErrorBg;
|
background: $colorFormFieldErrorBg;
|
||||||
color: $colorFormFieldErrorFg;
|
color: $colorFormFieldErrorFg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin nice-input($bg: $colorInputBg, $fg: $colorInputFg, $shdw: rgba(black, 0.5) 0 0px 2px) {
|
@mixin nice-input($bg: $colorInputBg, $fg: $colorInputFg, $shdw: rgba(black, 0.5) 0 0 2px) {
|
||||||
@include input-base();
|
@include input-base();
|
||||||
background: $bg;
|
background: $bg;
|
||||||
color: $fg;
|
color: $fg;
|
||||||
box-shadow: inset $shdw;
|
box-shadow: inset $shdw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@mixin reactive-input($bg: $colorInputBg, $fg: $colorInputFg) {
|
||||||
|
@include input-base();
|
||||||
|
background: $bg;
|
||||||
|
box-shadow: $shdwInput;
|
||||||
|
color: $fg;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
box-shadow: $shdwInputFoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include hover() {
|
||||||
|
&:not(:focus) {
|
||||||
|
box-shadow: $shdwInputHov;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@mixin button($bg: $colorBtnBg, $fg: $colorBtnFg, $radius: $controlCr, $shdw: none) {
|
@mixin button($bg: $colorBtnBg, $fg: $colorBtnFg, $radius: $controlCr, $shdw: none) {
|
||||||
|
// Is this being used? Remove if not.
|
||||||
background: $bg;
|
background: $bg;
|
||||||
color: $fg;
|
color: $fg;
|
||||||
border-radius: $radius;
|
border-radius: $radius;
|
||||||
box-shadow: $shdw;
|
box-shadow: $shdw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@mixin cControl() {
|
||||||
|
$fs: 1em;
|
||||||
|
@include userSelectNone();
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&:before,
|
||||||
|
&:after {
|
||||||
|
font-family: symbolsfont;
|
||||||
|
display: block;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
[class*="__label"] {
|
||||||
|
@include ellipsize();
|
||||||
|
display: block;
|
||||||
|
line-height: $fs; // Remove effect on top and bottom padding
|
||||||
|
font-size: $fs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin cButton() {
|
||||||
|
@include cControl();
|
||||||
|
@include themedButton();
|
||||||
|
border-radius: $controlCr;
|
||||||
|
color: $colorBtnFg;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: $interiorMargin floor($interiorMargin * 1.25);
|
||||||
|
|
||||||
|
@include hover() {
|
||||||
|
background: $colorBtnBgHov;
|
||||||
|
color: $colorBtnFgHov;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[class*="--major"] {
|
||||||
|
background: $colorBtnMajorBg;
|
||||||
|
color: $colorBtnMajorFg;
|
||||||
|
|
||||||
|
@include hover() {
|
||||||
|
background: $colorBtnMajorBgHov;
|
||||||
|
color: $colorBtnMajorFgHov;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[class*='--caution'] {
|
||||||
|
background: $colorBtnCautionBg;
|
||||||
|
color: $colorBtnCautionFg;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: $colorBtnCautionBgHov;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin cClickIcon() {
|
||||||
|
// A clickable element that just includes the icon, no background
|
||||||
|
// Padding is included to facilitate a bigger hit area
|
||||||
|
// Make the icon bigger relative to its container
|
||||||
|
@include cControl();
|
||||||
|
$pLR: 4px;
|
||||||
|
$pTB: 3px;
|
||||||
|
background: none;
|
||||||
|
box-shadow: none;
|
||||||
|
border-radius: $controlCr;
|
||||||
|
color: $colorKey;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: $pTB $pLR ;
|
||||||
|
|
||||||
|
@include hover() {
|
||||||
|
background: $colorClickIconBgHov;
|
||||||
|
color: $colorClickIconFgHov;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:before,
|
||||||
|
*:before {
|
||||||
|
// *:before handles any nested containers that may contain glyph elements
|
||||||
|
// Needed for c-togglebutton.
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin cCtrlWrapper {
|
||||||
|
// Provides a wrapper around buttons and other controls
|
||||||
|
// Contains control and provides positioning context for contained menu/palette.
|
||||||
|
// Wraps --menu elements, contains button and menu
|
||||||
|
overflow: visible;
|
||||||
|
|
||||||
|
.c-menu {
|
||||||
|
// Default position of contained menu
|
||||||
|
top: 100%; left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[class*='--menus-up'] {
|
||||||
|
.c-menu {
|
||||||
|
top: auto; bottom: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[class*='--menus-left'] {
|
||||||
|
.c-menu {
|
||||||
|
left: auto; right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@mixin wrappedInput() {
|
@mixin wrappedInput() {
|
||||||
// An input that is wrapped. Optionally includes a __label or icon element.
|
// An input that is wrapped. Optionally includes a __label or icon element.
|
||||||
// Based on .c-search.
|
// Based on .c-search.
|
||||||
@ -255,3 +404,16 @@
|
|||||||
@mixin userSelectNone() {
|
@mixin userSelectNone() {
|
||||||
@include browserPrefix(user-select, none);
|
@include browserPrefix(user-select, none);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@mixin cursorGrab() {
|
||||||
|
cursor: grab;
|
||||||
|
cursor: -webkit-grab;
|
||||||
|
&:active {
|
||||||
|
cursor: grabbing;
|
||||||
|
cursor: -webkit-grabbing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin test($c: deeppink, $a: 0.3) {
|
||||||
|
background-color: rgba($c, $a) !important;
|
||||||
|
}
|
||||||
|
91
src/styles-new/_table.scss
Normal file
91
src/styles-new/_table.scss
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
/******************************************************** TABLE */
|
||||||
|
.c-table {
|
||||||
|
// Can be used by any type of table, scrolling, LAD, etc.
|
||||||
|
$min-w: 50px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column nowrap;
|
||||||
|
justify-content: flex-start;
|
||||||
|
position: absolute;
|
||||||
|
top: 0; right: 0; bottom: 0; left: 0;
|
||||||
|
|
||||||
|
&__control-bar,
|
||||||
|
&__headers-w {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/******************************* ELEMENTS */
|
||||||
|
th, td {
|
||||||
|
white-space: nowrap;
|
||||||
|
min-width: $min-w;
|
||||||
|
padding: $tabularTdPadTB $tabularTdPadLR;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
color: $colorTelemFresh;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__control-bar {
|
||||||
|
margin-bottom: $interiorMarginSm;
|
||||||
|
}
|
||||||
|
|
||||||
|
[class*="__header"] {
|
||||||
|
background: $colorTabHeaderBg;
|
||||||
|
|
||||||
|
th {
|
||||||
|
&:not(:first-child) {
|
||||||
|
border-left: 1px solid $colorTabHeaderBorder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__body {
|
||||||
|
tr {
|
||||||
|
&:not(:first-child) {
|
||||||
|
border-top: 1px solid $colorTabBorder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&--sortable {
|
||||||
|
.is-sorting {
|
||||||
|
&:after {
|
||||||
|
color: $colorIconAlias;
|
||||||
|
content: $glyph-icon-arrow-tall-up;
|
||||||
|
font-family: symbolsfont;
|
||||||
|
font-size: 8px;
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: $interiorMarginSm;
|
||||||
|
}
|
||||||
|
&.desc:after {
|
||||||
|
content: $glyph-icon-arrow-tall-down;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.is-sortable {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user