mirror of
https://github.com/nasa/openmct.git
synced 2025-06-25 10:44:21 +00:00
Compare commits
14 Commits
misc-ui-4
...
vue-table-
Author | SHA1 | Date | |
---|---|---|---|
24fcad8152 | |||
a6f9e0420c | |||
ccfd9eed00 | |||
df0ee1f99b | |||
dacbf928a1 | |||
6153dce261 | |||
0fcddb3547 | |||
c6628b6e72 | |||
ec7889e5ff | |||
416d8f60fe | |||
74293d4fda | |||
73fc686851 | |||
d21abd95b1 | |||
eb5ef28a73 |
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
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -1,21 +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.
|
|
||||||
*****************************************************************************/
|
|
@ -49,7 +49,7 @@ define([
|
|||||||
{
|
{
|
||||||
"key": "eventGenerator",
|
"key": "eventGenerator",
|
||||||
"name": "Event Message Generator",
|
"name": "Event Message Generator",
|
||||||
"cssClass": "icon-generator-events",
|
"cssClass": "icon-folder-new",
|
||||||
"description": "For development use. Creates sample event message data that mimics a live data stream.",
|
"description": "For development use. Creates sample event message data that mimics a live data stream.",
|
||||||
"priority": 10,
|
"priority": 10,
|
||||||
"features": "creation",
|
"features": "creation",
|
||||||
|
@ -37,25 +37,25 @@ define([
|
|||||||
},
|
},
|
||||||
LIMITS = {
|
LIMITS = {
|
||||||
rh: {
|
rh: {
|
||||||
cssClass: "is-limit--upr is-limit--red",
|
cssClass: "s-limit-upr s-limit-red",
|
||||||
low: RED,
|
low: RED,
|
||||||
high: Number.POSITIVE_INFINITY,
|
high: Number.POSITIVE_INFINITY,
|
||||||
name: "Red High"
|
name: "Red High"
|
||||||
},
|
},
|
||||||
rl: {
|
rl: {
|
||||||
cssClass: "is-limit--lwr is-limit--red",
|
cssClass: "s-limit-lwr s-limit-red",
|
||||||
high: -RED,
|
high: -RED,
|
||||||
low: Number.NEGATIVE_INFINITY,
|
low: Number.NEGATIVE_INFINITY,
|
||||||
name: "Red Low"
|
name: "Red Low"
|
||||||
},
|
},
|
||||||
yh: {
|
yh: {
|
||||||
cssClass: "is-limit--upr is-limit--yellow",
|
cssClass: "s-limit-upr s-limit-yellow",
|
||||||
low: YELLOW,
|
low: YELLOW,
|
||||||
high: RED,
|
high: RED,
|
||||||
name: "Yellow High"
|
name: "Yellow High"
|
||||||
},
|
},
|
||||||
yl: {
|
yl: {
|
||||||
cssClass: "is-limit--lwr is-limit--yellow",
|
cssClass: "s-limit-lwr s-limit-yellow",
|
||||||
low: -RED,
|
low: -RED,
|
||||||
high: -YELLOW,
|
high: -YELLOW,
|
||||||
name: "Yellow Low"
|
name: "Yellow Low"
|
||||||
|
@ -38,7 +38,7 @@ define([
|
|||||||
openmct.types.addType("example.state-generator", {
|
openmct.types.addType("example.state-generator", {
|
||||||
name: "State Generator",
|
name: "State Generator",
|
||||||
description: "For development use. Generates test enumerated telemetry by cycling through a given set of states",
|
description: "For development use. Generates test enumerated telemetry by cycling through a given set of states",
|
||||||
cssClass: "icon-generator-telemetry",
|
cssClass: "icon-telemetry",
|
||||||
creatable: true,
|
creatable: true,
|
||||||
form: [
|
form: [
|
||||||
{
|
{
|
||||||
@ -66,7 +66,7 @@ define([
|
|||||||
openmct.types.addType("generator", {
|
openmct.types.addType("generator", {
|
||||||
name: "Sine Wave Generator",
|
name: "Sine Wave Generator",
|
||||||
description: "For development use. Generates example streaming telemetry data using a simple sine wave algorithm.",
|
description: "For development use. Generates example streaming telemetry data using a simple sine wave algorithm.",
|
||||||
cssClass: "icon-generator-telemetry",
|
cssClass: "icon-telemetry",
|
||||||
creatable: true,
|
creatable: true,
|
||||||
form: [
|
form: [
|
||||||
{
|
{
|
||||||
|
@ -26,16 +26,12 @@ 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";
|
||||||
@ -45,11 +41,11 @@ define([
|
|||||||
"templates": [
|
"templates": [
|
||||||
{
|
{
|
||||||
"key": "dialogLaunchTemplate",
|
"key": "dialogLaunchTemplate",
|
||||||
"template": DialogLaunch
|
"templateUrl": "dialog-launch.html"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "notificationLaunchTemplate",
|
"key": "notificationLaunchTemplate",
|
||||||
"template": NotificationLaunch
|
"templateUrl": "notification-launch.html"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"controllers": [
|
"controllers": [
|
||||||
|
@ -51,26 +51,76 @@ 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",
|
||||||
autoDismiss: true
|
primaryOption: {
|
||||||
});
|
label: 'Retry',
|
||||||
|
callback: function() {
|
||||||
|
$log.info('Retry clicked');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
options: getExampleActions()});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -78,42 +128,39 @@ 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(){
|
||||||
let progress = 0;
|
|
||||||
var notificationModel = {
|
var notificationModel = {
|
||||||
title: "Progress notification example",
|
title: "Progress notification example",
|
||||||
severity: "info",
|
severity: "info",
|
||||||
progress: progress,
|
progress: 0,
|
||||||
actionText: getExampleActionText()
|
actionText: getExampleActionText(),
|
||||||
|
unknownProgress: false
|
||||||
};
|
};
|
||||||
let notification;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simulate an ongoing process and update the progress bar.
|
* Simulate an ongoing process and update the progress bar.
|
||||||
* @param notification
|
* @param notification
|
||||||
*/
|
*/
|
||||||
function incrementProgress() {
|
function incrementProgress(notificationModel) {
|
||||||
progress = Math.min(100, Math.floor(progress + Math.random() * 30))
|
notificationModel.progress = Math.min(100, Math.floor(notificationModel.progress + Math.random() * 30));
|
||||||
let progressText = ["Estimated time" +
|
notificationModel.progressText = ["Estimated time" +
|
||||||
" remaining:" +
|
" remaining:" +
|
||||||
" about ", 60 - Math.floor((progress / 100) * 60), " seconds"].join(" ");
|
" about ", 60 - Math.floor((notificationModel.progress / 100) * 60), " seconds"].join(" ");
|
||||||
notification.progress(progress, progressText);
|
if (notificationModel.progress < 100) {
|
||||||
|
$timeout(function(){incrementProgress(notificationModel);}, 1000);
|
||||||
if (progress < 100) {
|
|
||||||
$timeout(function () {
|
|
||||||
incrementProgress(notificationModel);
|
|
||||||
}, 1000);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
notification = notificationService.notify(notificationModel);
|
notificationService.notify(notificationModel);
|
||||||
incrementProgress();
|
incrementProgress(notificationModel);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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++
|
||||||
});
|
});
|
||||||
|
203
index.html
203
index.html
@ -49,7 +49,6 @@
|
|||||||
openmct.install(openmct.plugins.ExampleImagery());
|
openmct.install(openmct.plugins.ExampleImagery());
|
||||||
openmct.install(openmct.plugins.UTCTimeSystem());
|
openmct.install(openmct.plugins.UTCTimeSystem());
|
||||||
openmct.install(openmct.plugins.ImportExport());
|
openmct.install(openmct.plugins.ImportExport());
|
||||||
openmct.install(openmct.plugins.FixedView());
|
|
||||||
openmct.install(openmct.plugins.AutoflowView({
|
openmct.install(openmct.plugins.AutoflowView({
|
||||||
type: "telemetry.panel"
|
type: "telemetry.panel"
|
||||||
}));
|
}));
|
||||||
@ -76,210 +75,8 @@
|
|||||||
}));
|
}));
|
||||||
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.install(openmct.plugins.Tabs());
|
|
||||||
openmct.install(openmct.plugins.FlexibleLayout());
|
|
||||||
openmct.install(openmct.plugins.LADTable());
|
|
||||||
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();
|
||||||
|
|
||||||
// openmct.toolbars.addProvider({
|
|
||||||
// name: "Testing Toolbar",
|
|
||||||
// key: "testing",
|
|
||||||
// description: "a mock toolbar that exercises all controls",
|
|
||||||
// forSelection: function (selection) {
|
|
||||||
// return true; // always applies.
|
|
||||||
// },
|
|
||||||
// toolbar: function (selection) {
|
|
||||||
// return [
|
|
||||||
// {
|
|
||||||
// control: 'menu',
|
|
||||||
// icon: 'icon-plus',
|
|
||||||
// label: 'Add',
|
|
||||||
// options: [
|
|
||||||
// { name: 'Box', class: 'icon-box', title: 'Add Box' },
|
|
||||||
// { name: 'Line', class: 'icon-line-horz', title: 'Add Line' },
|
|
||||||
// { name: 'Text', class: 'icon-font', title: 'Add Text' },
|
|
||||||
// { name: 'Image', class: 'icon-image', title: 'Add Image' }
|
|
||||||
// ]
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// control: 'separator'
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// control: 'color-picker',
|
|
||||||
// icon: 'icon-paint-bucket',
|
|
||||||
// value: '#33ff00',
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// control: 'color-picker',
|
|
||||||
// icon: 'icon-pencil',
|
|
||||||
// value: '#ffffff',
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// control: 'color-picker',
|
|
||||||
// icon: 'icon-font',
|
|
||||||
// value: '#333333',
|
|
||||||
// },
|
|
||||||
//
|
|
||||||
// {
|
|
||||||
// control: 'separator'
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// control: 'select-menu',
|
|
||||||
// value: 11,
|
|
||||||
// options: [
|
|
||||||
// { value: 9, name: '9 px' },
|
|
||||||
// { value: 10, name: '10 px' },
|
|
||||||
// { value: 11, name: '11 px' },
|
|
||||||
// { value: 12, name: '12 px' },
|
|
||||||
// { value: 13, name: '13 px' },
|
|
||||||
// { value: 14, name: '14 px' },
|
|
||||||
// { value: 16, name: '16 px' },
|
|
||||||
// { value: 18, name: '18 px' },
|
|
||||||
// { value: 20, name: '20 px' },
|
|
||||||
// { value: 24, name: '24 px' },
|
|
||||||
// { value: 28, name: '28 px' },
|
|
||||||
// { value: 32, name: '32 px' },
|
|
||||||
// { value: 40, name: '40 px' },
|
|
||||||
// { value: 48, name: '48 px' },
|
|
||||||
// { value: 56, name: '56 px' },
|
|
||||||
// { value: 64, name: '64 px' },
|
|
||||||
// { value: 72, name: '72 px' },
|
|
||||||
// { value: 80, name: '80 px' },
|
|
||||||
// { value: 88, name: '88 px' },
|
|
||||||
// { value: 96, name: '96 px' },
|
|
||||||
// { value: 128, name: '128 px' },
|
|
||||||
// { value: 160, name: '160 px' }
|
|
||||||
// ]
|
|
||||||
// },
|
|
||||||
//
|
|
||||||
// {
|
|
||||||
// control: 'separator'
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// control: 'menu',
|
|
||||||
// icon: 'icon-layers',
|
|
||||||
// options: [
|
|
||||||
// { name: 'Move to top', class: 'icon-arrow-double-up', title: 'Move to top' },
|
|
||||||
// { name: 'Move up', class: 'icon-arrow-up', title: 'Move up' },
|
|
||||||
// { name: 'Move down', class: 'icon-arrow-down', title: 'Move down' },
|
|
||||||
// { name: 'Move to bottom', class: 'icon-arrow-double-down', title: 'Move to bottom' }
|
|
||||||
// ]
|
|
||||||
// },
|
|
||||||
//
|
|
||||||
// {
|
|
||||||
// control: 'separator'
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// control: 'button',
|
|
||||||
// icon: 'icon-gear'
|
|
||||||
// },
|
|
||||||
//
|
|
||||||
// {
|
|
||||||
// control: 'separator'
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// control: 'input',
|
|
||||||
// type: 'number',
|
|
||||||
// label: 'X',
|
|
||||||
// value: 1,
|
|
||||||
// title: 'X position'
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// control: 'input',
|
|
||||||
// type: 'number',
|
|
||||||
// label: 'Y',
|
|
||||||
// value: 2,
|
|
||||||
// title: 'Y position'
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// control: 'input',
|
|
||||||
// type: 'number',
|
|
||||||
// label: 'W',
|
|
||||||
// value: 3,
|
|
||||||
// title: 'Width'
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// control: 'input',
|
|
||||||
// type: 'number',
|
|
||||||
// label: 'H',
|
|
||||||
// value: 4,
|
|
||||||
// title: 'Height'
|
|
||||||
// },
|
|
||||||
//
|
|
||||||
// {
|
|
||||||
// control: 'separator'
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// control: 'button',
|
|
||||||
// icon: 'icon-trash',
|
|
||||||
// label: 'delete',
|
|
||||||
// modifier: 'caution'
|
|
||||||
// },
|
|
||||||
//
|
|
||||||
// {
|
|
||||||
// control: 'separator'
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// control: 'checkbox',
|
|
||||||
// name: 'this is a checkbox',
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// control: 'separator'
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// control: 'toggle-button',
|
|
||||||
// title: 'Toggle Frame',
|
|
||||||
// property: 'hideFrame',
|
|
||||||
// value: false,
|
|
||||||
// options: [
|
|
||||||
// {
|
|
||||||
// value: true,
|
|
||||||
// icon: 'icon-frame-hide'
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// value: false,
|
|
||||||
// icon: 'icon-frame-show'
|
|
||||||
// }
|
|
||||||
// ]
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// control: 'toggle-button',
|
|
||||||
// title: 'Snap to grid',
|
|
||||||
// property: 'snapToGrid',
|
|
||||||
// value: true,
|
|
||||||
// options: [
|
|
||||||
// {
|
|
||||||
// value: true,
|
|
||||||
// icon: 'icon-grid-snap-to'
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// value: false,
|
|
||||||
// icon: 'icon-grid-snap-no'
|
|
||||||
// }
|
|
||||||
// ]
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// control: 'toggle-button',
|
|
||||||
// title: 'Toggle label',
|
|
||||||
// property: 'showLabel',
|
|
||||||
// value: true,
|
|
||||||
// options: [
|
|
||||||
// {
|
|
||||||
// value: true,
|
|
||||||
// icon: 'icon-two-parts-both'
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// value: false,
|
|
||||||
// icon: 'icon-two-parts-one-only'
|
|
||||||
// }
|
|
||||||
// ]
|
|
||||||
// }
|
|
||||||
// ];
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</html>
|
</html>
|
||||||
|
@ -58,6 +58,7 @@
|
|||||||
"printj": "^1.1.0",
|
"printj": "^1.1.0",
|
||||||
"raw-loader": "^0.5.1",
|
"raw-loader": "^0.5.1",
|
||||||
"request": "^2.69.0",
|
"request": "^2.69.0",
|
||||||
|
"screenfull": "^3.3.2",
|
||||||
"split": "^1.0.0",
|
"split": "^1.0.0",
|
||||||
"style-loader": "^0.21.0",
|
"style-loader": "^0.21.0",
|
||||||
"v8-compile-cache": "^1.1.0",
|
"v8-compile-cache": "^1.1.0",
|
||||||
|
@ -31,13 +31,16 @@ define([
|
|||||||
"./src/navigation/NavigateAction",
|
"./src/navigation/NavigateAction",
|
||||||
"./src/navigation/OrphanNavigationHandler",
|
"./src/navigation/OrphanNavigationHandler",
|
||||||
"./src/windowing/NewTabAction",
|
"./src/windowing/NewTabAction",
|
||||||
|
"./src/windowing/FullscreenAction",
|
||||||
"./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'
|
||||||
@ -52,13 +55,16 @@ define([
|
|||||||
NavigateAction,
|
NavigateAction,
|
||||||
OrphanNavigationHandler,
|
OrphanNavigationHandler,
|
||||||
NewTabAction,
|
NewTabAction,
|
||||||
|
FullscreenAction,
|
||||||
WindowTitler,
|
WindowTitler,
|
||||||
browseTemplate,
|
browseTemplate,
|
||||||
browseObjectTemplate,
|
browseObjectTemplate,
|
||||||
|
gridItemTemplate,
|
||||||
objectHeaderTemplate,
|
objectHeaderTemplate,
|
||||||
objectHeaderFrameTemplate,
|
objectHeaderFrameTemplate,
|
||||||
menuArrowTemplate,
|
menuArrowTemplate,
|
||||||
backArrowTemplate,
|
backArrowTemplate,
|
||||||
|
itemsTemplate,
|
||||||
objectPropertiesTemplate,
|
objectPropertiesTemplate,
|
||||||
inspectorRegionTemplate,
|
inspectorRegionTemplate,
|
||||||
legacyRegistry
|
legacyRegistry
|
||||||
@ -150,6 +156,19 @@ 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,
|
||||||
@ -223,6 +242,30 @@ define([
|
|||||||
"group": "windowing",
|
"group": "windowing",
|
||||||
"cssClass": "icon-new-window",
|
"cssClass": "icon-new-window",
|
||||||
"priority": "preferred"
|
"priority": "preferred"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "fullscreen",
|
||||||
|
"implementation": FullscreenAction,
|
||||||
|
"category": "view-control",
|
||||||
|
"group": "windowing",
|
||||||
|
"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": [
|
||||||
@ -256,6 +299,18 @@ define([
|
|||||||
key: "inspectorRegion",
|
key: "inspectorRegion",
|
||||||
template: inspectorRegionTemplate
|
template: inspectorRegionTemplate
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"licenses": [
|
||||||
|
{
|
||||||
|
"name": "screenfull.js",
|
||||||
|
"version": "1.2.0",
|
||||||
|
"description": "Wrapper for cross-browser usage of fullscreen API",
|
||||||
|
"author": "Sindre Sorhus",
|
||||||
|
"website": "https://github.com/sindresorhus/screenfull.js/",
|
||||||
|
"copyright": "Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)",
|
||||||
|
"license": "license-mit",
|
||||||
|
"link": "https://github.com/sindresorhus/screenfull.js/blob/gh-pages/license"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -66,4 +66,5 @@
|
|||||||
</mct-representation>
|
</mct-representation>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<mct-include key="'conductor'" class="abs holder flex-elem flex-fixed l-flex-row l-time-conductor-holder"></mct-include>
|
||||||
</div>
|
</div>
|
||||||
|
45
platform/commonUI/browse/res/templates/items/grid-item.html
Normal file
45
platform/commonUI/browse/res/templates/items/grid-item.html
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<!--
|
||||||
|
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>
|
@ -18,4 +18,10 @@
|
|||||||
licenses. See the Open Source Licenses file (LICENSES.md) included with
|
licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
this source code distribution or the Licensing information page available
|
this source code distribution or the Licensing information page available
|
||||||
at runtime from the About dialog for additional information.
|
at runtime from the About dialog for additional information.
|
||||||
-->
|
-->
|
||||||
|
<div class='items-holder grid abs'>
|
||||||
|
<mct-representation key="'grid-item'"
|
||||||
|
ng-repeat="childObject in composition"
|
||||||
|
mct-object="childObject">
|
||||||
|
</mct-representation>
|
||||||
|
</div>
|
@ -58,7 +58,7 @@ define([], function () {
|
|||||||
|
|
||||||
function checkNavigation() {
|
function checkNavigation() {
|
||||||
var navigatedObject = navigationService.getNavigation();
|
var navigatedObject = navigationService.getNavigation();
|
||||||
if (navigatedObject && navigatedObject.hasCapability('context')) {
|
if (navigatedObject.hasCapability('context')) {
|
||||||
if (!navigatedObject.getCapability('editor').isEditContextRoot()) {
|
if (!navigatedObject.getCapability('editor').isEditContextRoot()) {
|
||||||
preventOrphanNavigation(navigatedObject);
|
preventOrphanNavigation(navigatedObject);
|
||||||
}
|
}
|
||||||
|
64
platform/commonUI/browse/src/windowing/FullscreenAction.js
Normal file
64
platform/commonUI/browse/src/windowing/FullscreenAction.js
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module defining FullscreenAction. Created by vwoeltje on 11/18/14.
|
||||||
|
*/
|
||||||
|
define(
|
||||||
|
["screenfull"],
|
||||||
|
function (screenfull) {
|
||||||
|
|
||||||
|
var ENTER_FULLSCREEN = "Enter full screen mode",
|
||||||
|
EXIT_FULLSCREEN = "Exit full screen mode";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The fullscreen action toggles between fullscreen display
|
||||||
|
* and regular in-window display.
|
||||||
|
* @memberof platform/commonUI/browse
|
||||||
|
* @constructor
|
||||||
|
* @implements {Action}
|
||||||
|
*/
|
||||||
|
function FullscreenAction(context) {
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
FullscreenAction.prototype.perform = function () {
|
||||||
|
screenfull.toggle();
|
||||||
|
};
|
||||||
|
|
||||||
|
FullscreenAction.prototype.getMetadata = function () {
|
||||||
|
// We override getMetadata, because the icon cssClass and
|
||||||
|
// description need to be determined at run-time
|
||||||
|
// based on whether or not we are currently
|
||||||
|
// full screen.
|
||||||
|
var metadata = Object.create(FullscreenAction);
|
||||||
|
metadata.cssClass = screenfull.isFullscreen ? "icon-fullscreen-expand" : "icon-fullscreen-collapse";
|
||||||
|
metadata.description = screenfull.isFullscreen ?
|
||||||
|
EXIT_FULLSCREEN : ENTER_FULLSCREEN;
|
||||||
|
metadata.group = "windowing";
|
||||||
|
metadata.context = this.context;
|
||||||
|
return metadata;
|
||||||
|
};
|
||||||
|
|
||||||
|
return FullscreenAction;
|
||||||
|
}
|
||||||
|
);
|
@ -0,0 +1,59 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MCTRepresentationSpec. Created by vwoeltje on 11/6/14.
|
||||||
|
*/
|
||||||
|
define(
|
||||||
|
["../../src/windowing/FullscreenAction", "screenfull"],
|
||||||
|
function (FullscreenAction, screenfull) {
|
||||||
|
|
||||||
|
describe("The fullscreen action", function () {
|
||||||
|
var action,
|
||||||
|
oldToggle;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
// Screenfull is not shimmed or injected, so
|
||||||
|
// we need to spy on it in the global scope.
|
||||||
|
oldToggle = screenfull.toggle;
|
||||||
|
|
||||||
|
screenfull.toggle = jasmine.createSpy("toggle");
|
||||||
|
|
||||||
|
action = new FullscreenAction({});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function () {
|
||||||
|
screenfull.toggle = oldToggle;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("toggles fullscreen mode when performed", function () {
|
||||||
|
action.perform();
|
||||||
|
expect(screenfull.toggle).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("provides displayable metadata", function () {
|
||||||
|
expect(action.getMetadata().cssClass).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
@ -2,14 +2,31 @@
|
|||||||
ng-class="'message-severity-' + ngModel.severity">
|
ng-class="'message-severity-' + ngModel.severity">
|
||||||
<div class="w-message-contents">
|
<div class="w-message-contents">
|
||||||
<div class="top-bar">
|
<div class="top-bar">
|
||||||
<div class="title">{{ngModel.message}}</div>
|
<div class="title">{{ngModel.title}}</div>
|
||||||
|
</div>
|
||||||
|
<div class="hint" ng-hide="ngModel.hint === undefined">
|
||||||
|
{{ngModel.hint}}
|
||||||
|
<span ng-if="ngModel.timestamp !== undefined">[{{ngModel.timestamp}}]</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="message-body">
|
<div class="message-body">
|
||||||
|
<div class="message-action">
|
||||||
|
{{ngModel.actionText}}
|
||||||
|
</div>
|
||||||
<mct-include key="'progress-bar'"
|
<mct-include key="'progress-bar'"
|
||||||
ng-model="ngModel"
|
ng-model="ngModel"
|
||||||
ng-show="ngModel.progressPerc !== undefined"></mct-include>
|
ng-show="ngModel.progress !== undefined || ngModel.unknownProgress"></mct-include>
|
||||||
</div>
|
</div>
|
||||||
<div class="bottom-bar">
|
<div class="bottom-bar">
|
||||||
|
<a ng-repeat="dialogOption in ngModel.options"
|
||||||
|
class="s-button"
|
||||||
|
ng-click="dialogOption.callback()">
|
||||||
|
{{dialogOption.label}}
|
||||||
|
</a>
|
||||||
|
<a class="s-button major"
|
||||||
|
ng-if="ngModel.primaryOption"
|
||||||
|
ng-click="ngModel.primaryOption.callback()">
|
||||||
|
{{ngModel.primaryOption.label}}
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -95,10 +95,7 @@ define(
|
|||||||
|
|
||||||
// Create the overlay element and add it to the document's body
|
// Create the overlay element and add it to the document's body
|
||||||
element = this.$compile(TEMPLATE)(scope);
|
element = this.$compile(TEMPLATE)(scope);
|
||||||
|
this.findBody().prepend(element);
|
||||||
// Append so that most recent dialog is last in DOM. This means the most recent dialog will be on top when
|
|
||||||
// multiple overlays with the same z-index are active.
|
|
||||||
this.findBody().append(element);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
dismiss: dismiss
|
dismiss: dismiss
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
define([
|
define([
|
||||||
"./src/controllers/EditActionController",
|
"./src/controllers/EditActionController",
|
||||||
"./src/controllers/EditPanesController",
|
"./src/controllers/EditPanesController",
|
||||||
|
"./src/controllers/ElementsController",
|
||||||
"./src/controllers/EditObjectController",
|
"./src/controllers/EditObjectController",
|
||||||
"./src/actions/EditAndComposeAction",
|
"./src/actions/EditAndComposeAction",
|
||||||
"./src/actions/EditAction",
|
"./src/actions/EditAction",
|
||||||
@ -32,7 +33,11 @@ define([
|
|||||||
"./src/actions/SaveAndStopEditingAction",
|
"./src/actions/SaveAndStopEditingAction",
|
||||||
"./src/actions/SaveAsAction",
|
"./src/actions/SaveAsAction",
|
||||||
"./src/actions/CancelAction",
|
"./src/actions/CancelAction",
|
||||||
|
"./src/policies/EditActionPolicy",
|
||||||
"./src/policies/EditPersistableObjectsPolicy",
|
"./src/policies/EditPersistableObjectsPolicy",
|
||||||
|
"./src/policies/EditableLinkPolicy",
|
||||||
|
"./src/policies/EditableMovePolicy",
|
||||||
|
"./src/policies/EditContextualActionPolicy",
|
||||||
"./src/representers/EditRepresenter",
|
"./src/representers/EditRepresenter",
|
||||||
"./src/capabilities/EditorCapability",
|
"./src/capabilities/EditorCapability",
|
||||||
"./src/capabilities/TransactionCapabilityDecorator",
|
"./src/capabilities/TransactionCapabilityDecorator",
|
||||||
@ -42,6 +47,7 @@ define([
|
|||||||
"./src/creation/LocatorController",
|
"./src/creation/LocatorController",
|
||||||
"./src/creation/CreationPolicy",
|
"./src/creation/CreationPolicy",
|
||||||
"./src/creation/CreateActionProvider",
|
"./src/creation/CreateActionProvider",
|
||||||
|
"./src/creation/AddActionProvider",
|
||||||
"./src/creation/CreationService",
|
"./src/creation/CreationService",
|
||||||
"./res/templates/create/locator.html",
|
"./res/templates/create/locator.html",
|
||||||
"./res/templates/create/create-button.html",
|
"./res/templates/create/create-button.html",
|
||||||
@ -49,11 +55,13 @@ define([
|
|||||||
"./res/templates/library.html",
|
"./res/templates/library.html",
|
||||||
"./res/templates/edit-object.html",
|
"./res/templates/edit-object.html",
|
||||||
"./res/templates/edit-action-buttons.html",
|
"./res/templates/edit-action-buttons.html",
|
||||||
|
"./res/templates/elements.html",
|
||||||
"./res/templates/topbar-edit.html",
|
"./res/templates/topbar-edit.html",
|
||||||
'legacyRegistry'
|
'legacyRegistry'
|
||||||
], function (
|
], function (
|
||||||
EditActionController,
|
EditActionController,
|
||||||
EditPanesController,
|
EditPanesController,
|
||||||
|
ElementsController,
|
||||||
EditObjectController,
|
EditObjectController,
|
||||||
EditAndComposeAction,
|
EditAndComposeAction,
|
||||||
EditAction,
|
EditAction,
|
||||||
@ -63,7 +71,11 @@ define([
|
|||||||
SaveAndStopEditingAction,
|
SaveAndStopEditingAction,
|
||||||
SaveAsAction,
|
SaveAsAction,
|
||||||
CancelAction,
|
CancelAction,
|
||||||
|
EditActionPolicy,
|
||||||
EditPersistableObjectsPolicy,
|
EditPersistableObjectsPolicy,
|
||||||
|
EditableLinkPolicy,
|
||||||
|
EditableMovePolicy,
|
||||||
|
EditContextualActionPolicy,
|
||||||
EditRepresenter,
|
EditRepresenter,
|
||||||
EditorCapability,
|
EditorCapability,
|
||||||
TransactionCapabilityDecorator,
|
TransactionCapabilityDecorator,
|
||||||
@ -73,6 +85,7 @@ define([
|
|||||||
LocatorController,
|
LocatorController,
|
||||||
CreationPolicy,
|
CreationPolicy,
|
||||||
CreateActionProvider,
|
CreateActionProvider,
|
||||||
|
AddActionProvider,
|
||||||
CreationService,
|
CreationService,
|
||||||
locatorTemplate,
|
locatorTemplate,
|
||||||
createButtonTemplate,
|
createButtonTemplate,
|
||||||
@ -80,6 +93,7 @@ define([
|
|||||||
libraryTemplate,
|
libraryTemplate,
|
||||||
editObjectTemplate,
|
editObjectTemplate,
|
||||||
editActionButtonsTemplate,
|
editActionButtonsTemplate,
|
||||||
|
elementsTemplate,
|
||||||
topbarEditTemplate,
|
topbarEditTemplate,
|
||||||
legacyRegistry
|
legacyRegistry
|
||||||
) {
|
) {
|
||||||
@ -101,6 +115,14 @@ define([
|
|||||||
"$scope"
|
"$scope"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"key": "ElementsController",
|
||||||
|
"implementation": ElementsController,
|
||||||
|
"depends": [
|
||||||
|
"$scope",
|
||||||
|
"openmct"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"key": "EditObjectController",
|
"key": "EditObjectController",
|
||||||
"implementation": EditObjectController,
|
"implementation": EditObjectController,
|
||||||
@ -166,7 +188,7 @@ define([
|
|||||||
"name": "Remove",
|
"name": "Remove",
|
||||||
"description": "Remove this object from its containing object.",
|
"description": "Remove this object from its containing object.",
|
||||||
"depends": [
|
"depends": [
|
||||||
"openmct",
|
"dialogService",
|
||||||
"navigationService"
|
"navigationService"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -203,10 +225,10 @@ define([
|
|||||||
"description": "Save changes made to these objects.",
|
"description": "Save changes made to these objects.",
|
||||||
"depends": [
|
"depends": [
|
||||||
"$injector",
|
"$injector",
|
||||||
|
"policyService",
|
||||||
"dialogService",
|
"dialogService",
|
||||||
"copyService",
|
"copyService",
|
||||||
"notificationService",
|
"notificationService"
|
||||||
"openmct"
|
|
||||||
],
|
],
|
||||||
"priority": "mandatory"
|
"priority": "mandatory"
|
||||||
},
|
},
|
||||||
@ -223,11 +245,28 @@ define([
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"policies": [
|
"policies": [
|
||||||
|
{
|
||||||
|
"category": "action",
|
||||||
|
"implementation": EditActionPolicy
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"category": "action",
|
"category": "action",
|
||||||
"implementation": EditPersistableObjectsPolicy,
|
"implementation": EditPersistableObjectsPolicy,
|
||||||
"depends": ["openmct"]
|
"depends": ["openmct"]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"category": "action",
|
||||||
|
"implementation": EditContextualActionPolicy,
|
||||||
|
"depends": ["navigationService", "editModeBlacklist", "nonEditContextBlacklist"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"category": "action",
|
||||||
|
"implementation": EditableMovePolicy
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"category": "action",
|
||||||
|
"implementation": EditableLinkPolicy
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"implementation": CreationPolicy,
|
"implementation": CreationPolicy,
|
||||||
"category": "creation"
|
"category": "creation"
|
||||||
@ -257,6 +296,13 @@ define([
|
|||||||
"action"
|
"action"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"key": "edit-elements",
|
||||||
|
"template": elementsTemplate,
|
||||||
|
"gestures": [
|
||||||
|
"drop"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"key": "topbar-edit",
|
"key": "topbar-edit",
|
||||||
"template": topbarEditTemplate
|
"template": topbarEditTemplate
|
||||||
@ -304,6 +350,18 @@ define([
|
|||||||
"policyService"
|
"policyService"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"key": "AddActionProvider",
|
||||||
|
"provides": "actionService",
|
||||||
|
"type": "provider",
|
||||||
|
"implementation": AddActionProvider,
|
||||||
|
"depends": [
|
||||||
|
"$q",
|
||||||
|
"typeService",
|
||||||
|
"dialogService",
|
||||||
|
"policyService"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"key": "CreationService",
|
"key": "CreationService",
|
||||||
"provides": "creationService",
|
"provides": "creationService",
|
||||||
@ -324,6 +382,16 @@ define([
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"constants": [
|
||||||
|
{
|
||||||
|
"key": "editModeBlacklist",
|
||||||
|
"value": ["copy", "follow", "link", "locate"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "nonEditContextBlacklist",
|
||||||
|
"value": ["copy", "follow", "properties", "move", "link", "remove", "locate"]
|
||||||
|
}
|
||||||
|
],
|
||||||
"capabilities": [
|
"capabilities": [
|
||||||
{
|
{
|
||||||
"key": "editor",
|
"key": "editor",
|
||||||
@ -331,8 +399,7 @@ define([
|
|||||||
"description": "Provides transactional editing capabilities",
|
"description": "Provides transactional editing capabilities",
|
||||||
"implementation": EditorCapability,
|
"implementation": EditorCapability,
|
||||||
"depends": [
|
"depends": [
|
||||||
"transactionService",
|
"transactionService"
|
||||||
"openmct"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
49
platform/commonUI/edit/res/templates/elements.html
Normal file
49
platform/commonUI/edit/res/templates/elements.html
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<!--
|
||||||
|
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="ElementsController" class="flex-elem l-flex-col holder grows">
|
||||||
|
<mct-include key="'input-filter'"
|
||||||
|
class="flex-elem holder"
|
||||||
|
ng-model="filterBy">
|
||||||
|
</mct-include>
|
||||||
|
<div class="flex-elem grows vscroll scroll-pad">
|
||||||
|
<ul class="tree" id="inspector-elements-tree"
|
||||||
|
ng-if="composition.length > 0">
|
||||||
|
<li ng-repeat="containedObject in composition | filter:searchElements">
|
||||||
|
<span class="tree-item">
|
||||||
|
<span class="grippy-sm"
|
||||||
|
ng-if="composition.length > 1"
|
||||||
|
data-id="{{ containedObject.id }}"
|
||||||
|
mct-drag-down="dragDown($event)"
|
||||||
|
mct-drag="drag($event)"
|
||||||
|
mct-drag-up="dragUp($event)">
|
||||||
|
</span>
|
||||||
|
<mct-representation
|
||||||
|
class="rep-object-label"
|
||||||
|
key="'label'"
|
||||||
|
mct-object="containedObject">
|
||||||
|
</mct-representation>
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div ng-if="composition.length === 0">No contained elements</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -42,9 +42,9 @@ define([
|
|||||||
* @constructor
|
* @constructor
|
||||||
* @implements {Action}
|
* @implements {Action}
|
||||||
*/
|
*/
|
||||||
function RemoveAction(openmct, navigationService, context) {
|
function RemoveAction(dialogService, navigationService, context) {
|
||||||
this.domainObject = (context || {}).domainObject;
|
this.domainObject = (context || {}).domainObject;
|
||||||
this.openmct = openmct;
|
this.dialogService = dialogService;
|
||||||
this.navigationService = navigationService;
|
this.navigationService = navigationService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,6 +53,7 @@ define([
|
|||||||
*/
|
*/
|
||||||
RemoveAction.prototype.perform = function () {
|
RemoveAction.prototype.perform = function () {
|
||||||
var dialog,
|
var dialog,
|
||||||
|
dialogService = this.dialogService,
|
||||||
domainObject = this.domainObject,
|
domainObject = this.domainObject,
|
||||||
navigationService = this.navigationService;
|
navigationService = this.navigationService;
|
||||||
/*
|
/*
|
||||||
@ -103,13 +104,13 @@ define([
|
|||||||
* capability. Based on object's location and selected object's location
|
* capability. Based on object's location and selected object's location
|
||||||
* user may be navigated to existing parent object
|
* user may be navigated to existing parent object
|
||||||
*/
|
*/
|
||||||
function removeFromContext() {
|
function removeFromContext(object) {
|
||||||
var contextCapability = domainObject.getCapability('context'),
|
var contextCapability = object.getCapability('context'),
|
||||||
parent = contextCapability.getParent();
|
parent = contextCapability.getParent();
|
||||||
|
|
||||||
// If currently within path of removed object(s),
|
// If currently within path of removed object(s),
|
||||||
// navigates to existing object up tree
|
// navigates to existing object up tree
|
||||||
checkObjectNavigation(domainObject, parent);
|
checkObjectNavigation(object, parent);
|
||||||
|
|
||||||
return parent.useCapability('mutation', doMutate);
|
return parent.useCapability('mutation', doMutate);
|
||||||
}
|
}
|
||||||
@ -118,7 +119,7 @@ define([
|
|||||||
* Pass in the function to remove the domain object so it can be
|
* Pass in the function to remove the domain object so it can be
|
||||||
* associated with an 'OK' button press
|
* associated with an 'OK' button press
|
||||||
*/
|
*/
|
||||||
dialog = new RemoveDialog(this.openmct, domainObject, removeFromContext);
|
dialog = new RemoveDialog(dialogService, domainObject, removeFromContext);
|
||||||
dialog.show();
|
dialog.show();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -36,8 +36,8 @@ define([], function () {
|
|||||||
* @memberof platform/commonUI/edit
|
* @memberof platform/commonUI/edit
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
function RemoveDialog(openmct, domainObject, removeCallback) {
|
function RemoveDialog(dialogService, domainObject, removeCallback) {
|
||||||
this.openmct = openmct;
|
this.dialogService = dialogService;
|
||||||
this.domainObject = domainObject;
|
this.domainObject = domainObject;
|
||||||
this.removeCallback = removeCallback;
|
this.removeCallback = removeCallback;
|
||||||
}
|
}
|
||||||
@ -46,26 +46,31 @@ define([], function () {
|
|||||||
* Display a dialog to confirm the removal of a domain object.
|
* Display a dialog to confirm the removal of a domain object.
|
||||||
*/
|
*/
|
||||||
RemoveDialog.prototype.show = function () {
|
RemoveDialog.prototype.show = function () {
|
||||||
let dialog = this.openmct.overlays.dialog({
|
var dialog,
|
||||||
title: 'Remove ' + this.domainObject.getModel().name,
|
domainObject = this.domainObject,
|
||||||
iconClass: 'alert',
|
removeCallback = this.removeCallback,
|
||||||
message: 'Warning! This action will permanently remove this object. Are you sure you want to continue?',
|
model = {
|
||||||
buttons: [
|
title: 'Remove ' + domainObject.getModel().name,
|
||||||
{
|
actionText: 'Warning! This action will permanently remove this object. Are you sure you want to continue?',
|
||||||
|
severity: 'alert',
|
||||||
|
primaryOption: {
|
||||||
label: 'OK',
|
label: 'OK',
|
||||||
callback: () => {
|
callback: function () {
|
||||||
this.removeCallback();
|
removeCallback(domainObject);
|
||||||
dialog.dismiss();
|
dialog.dismiss();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
options: [
|
||||||
label: 'Cancel',
|
{
|
||||||
callback: () => {
|
label: 'Cancel',
|
||||||
dialog.dismiss();
|
callback: function () {
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
]
|
||||||
]
|
};
|
||||||
});
|
|
||||||
|
dialog = this.dialogService.showBlockingMessage(model);
|
||||||
};
|
};
|
||||||
|
|
||||||
return RemoveDialog;
|
return RemoveDialog;
|
||||||
|
@ -40,20 +40,20 @@ function (
|
|||||||
*/
|
*/
|
||||||
function SaveAsAction(
|
function SaveAsAction(
|
||||||
$injector,
|
$injector,
|
||||||
|
policyService,
|
||||||
dialogService,
|
dialogService,
|
||||||
copyService,
|
copyService,
|
||||||
notificationService,
|
notificationService,
|
||||||
openmct,
|
|
||||||
context
|
context
|
||||||
) {
|
) {
|
||||||
this.domainObject = (context || {}).domainObject;
|
this.domainObject = (context || {}).domainObject;
|
||||||
this.injectObjectService = function () {
|
this.injectObjectService = function () {
|
||||||
this.objectService = $injector.get("objectService");
|
this.objectService = $injector.get("objectService");
|
||||||
};
|
};
|
||||||
|
this.policyService = policyService;
|
||||||
this.dialogService = dialogService;
|
this.dialogService = dialogService;
|
||||||
this.copyService = copyService;
|
this.copyService = copyService;
|
||||||
this.notificationService = notificationService;
|
this.notificationService = notificationService;
|
||||||
this.openmct = openmct;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -63,7 +63,7 @@ function (
|
|||||||
return new CreateWizard(
|
return new CreateWizard(
|
||||||
this.domainObject,
|
this.domainObject,
|
||||||
parent,
|
parent,
|
||||||
this.openmct
|
this.policyService
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -36,11 +36,9 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,22 +48,27 @@ define(
|
|||||||
* or finish() are called.
|
* or finish() are called.
|
||||||
*/
|
*/
|
||||||
EditorCapability.prototype.edit = function () {
|
EditorCapability.prototype.edit = function () {
|
||||||
console.warn('DEPRECATED: cannot edit via edit capability, use openmct.editor instead.');
|
this.transactionService.startTransaction();
|
||||||
|
this.domainObject.getCapability('status').set('editing', true);
|
||||||
if (!this.openmct.editor.isEditing()) {
|
|
||||||
this.openmct.editor.edit();
|
|
||||||
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 () {
|
||||||
console.warn('DEPRECATION WARNING: isEditing checks must be done via openmct.editor.');
|
return isEditing(this.domainObject);
|
||||||
return this.openmct.editor.isEditing();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -74,8 +77,7 @@ define(
|
|||||||
* @returns {*}
|
* @returns {*}
|
||||||
*/
|
*/
|
||||||
EditorCapability.prototype.isEditContextRoot = function () {
|
EditorCapability.prototype.isEditContextRoot = function () {
|
||||||
console.warn('DEPRECATION WARNING: isEditing checks must be done via openmct.editor.');
|
return isEditContextRoot(this.domainObject);
|
||||||
return this.openmct.editor.isEditing();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -84,8 +86,10 @@ define(
|
|||||||
* @returns {*}
|
* @returns {*}
|
||||||
*/
|
*/
|
||||||
EditorCapability.prototype.save = function () {
|
EditorCapability.prototype.save = function () {
|
||||||
console.warn('DEPRECATED: cannot save via edit capability, use openmct.editor instead.');
|
var transactionService = this.transactionService;
|
||||||
return Promise.resolve();
|
return transactionService.commit().then(function () {
|
||||||
|
transactionService.startTransaction();
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
EditorCapability.prototype.invoke = EditorCapability.prototype.edit;
|
EditorCapability.prototype.invoke = EditorCapability.prototype.edit;
|
||||||
@ -96,8 +100,16 @@ define(
|
|||||||
* @returns {*}
|
* @returns {*}
|
||||||
*/
|
*/
|
||||||
EditorCapability.prototype.finish = function () {
|
EditorCapability.prototype.finish = function () {
|
||||||
console.warn('DEPRECATED: cannot finish via edit capability, use openmct.editor instead.');
|
var domainObject = this.domainObject;
|
||||||
return Promise.resolve();
|
|
||||||
|
if (this.transactionService.isActive()) {
|
||||||
|
return this.transactionService.cancel().then(function () {
|
||||||
|
domainObject.getCapability("status").set("editing", false);
|
||||||
|
return domainObject;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return Promise.resolve(domainObject);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
197
platform/commonUI/edit/src/controllers/ElementsController.js
Normal file
197
platform/commonUI/edit/src/controllers/ElementsController.js
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
define(
|
||||||
|
['zepto'],
|
||||||
|
function ($) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ElementsController prepares the elements view for display
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function ElementsController($scope, openmct) {
|
||||||
|
this.scope = $scope;
|
||||||
|
this.scope.composition = [];
|
||||||
|
this.openmct = openmct;
|
||||||
|
this.dragDown = this.dragDown.bind(this);
|
||||||
|
this.dragUp = this.dragUp.bind(this);
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
function filterBy(text) {
|
||||||
|
if (typeof text === 'undefined') {
|
||||||
|
return $scope.searchText;
|
||||||
|
} else {
|
||||||
|
$scope.searchText = text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function searchElements(value) {
|
||||||
|
if ($scope.searchText) {
|
||||||
|
return value.getModel().name.toLowerCase().search(
|
||||||
|
$scope.searchText.toLowerCase()) !== -1;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setSelection(selection) {
|
||||||
|
if (!selection[0]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.mutationListener) {
|
||||||
|
self.mutationListener();
|
||||||
|
delete self.mutationListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
var domainObject = selection[0].context.oldItem;
|
||||||
|
self.refreshComposition(domainObject);
|
||||||
|
|
||||||
|
if (domainObject) {
|
||||||
|
|
||||||
|
self.mutationListener = domainObject.getCapability('mutation')
|
||||||
|
.listen(self.refreshComposition.bind(self, domainObject));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.filterBy = filterBy;
|
||||||
|
$scope.searchElements = searchElements;
|
||||||
|
|
||||||
|
openmct.selection.on('change', setSelection);
|
||||||
|
setSelection(openmct.selection.get());
|
||||||
|
|
||||||
|
$scope.dragDown = this.dragDown;
|
||||||
|
$scope.drag = this.drag;
|
||||||
|
$scope.dragUp = this.dragUp;
|
||||||
|
|
||||||
|
$scope.$on("$destroy", function () {
|
||||||
|
openmct.selection.off("change", setSelection);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked on DragStart - Adds reordering class to parent UL element
|
||||||
|
* Sets selected object ID, to be used on Drag End
|
||||||
|
*
|
||||||
|
* @param {object} event | Mouse Event
|
||||||
|
*/
|
||||||
|
ElementsController.prototype.dragDown = function (event) {
|
||||||
|
if (!this.parentUL) {
|
||||||
|
this.parentUL = $(document).find('#inspector-elements-tree');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.selectedTreeItem = $(event.target).parent();
|
||||||
|
this.selectedObjectId = event.target.getAttribute('data-id');
|
||||||
|
|
||||||
|
this.parentUL.addClass('reordering');
|
||||||
|
this.selectedTreeItem.addClass('reorder-actor');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked on dragEnd - Removes selected object from position in composition
|
||||||
|
* and replaces it at the target position. Composition is then updated with current
|
||||||
|
* scope
|
||||||
|
*
|
||||||
|
* @param {object} event - Mouse Event
|
||||||
|
*/
|
||||||
|
ElementsController.prototype.dragUp = function (event) {
|
||||||
|
this.targetObjectId = event.target.getAttribute('data-id');
|
||||||
|
|
||||||
|
if (this.targetObjectId && this.selectedObjectId) {
|
||||||
|
var selectedObjectPosition,
|
||||||
|
targetObjectPosition;
|
||||||
|
|
||||||
|
selectedObjectPosition = findObjectInCompositionFromId(this.selectedObjectId, this.scope.composition);
|
||||||
|
targetObjectPosition = findObjectInCompositionFromId(this.targetObjectId, this.scope.composition);
|
||||||
|
|
||||||
|
if ((selectedObjectPosition !== -1) && (targetObjectPosition !== -1)) {
|
||||||
|
var selectedObject = this.scope.composition.splice(selectedObjectPosition, 1),
|
||||||
|
selection = this.openmct.selection.get(),
|
||||||
|
domainObject = selection ? selection[0].context.oldItem : undefined;
|
||||||
|
|
||||||
|
this.scope.composition.splice(targetObjectPosition, 0, selectedObject[0]);
|
||||||
|
|
||||||
|
if (domainObject) {
|
||||||
|
domainObject.getCapability('mutation').mutate(function (model) {
|
||||||
|
model.composition = this.scope.composition.map(function (dObject) {
|
||||||
|
return dObject.id;
|
||||||
|
});
|
||||||
|
}.bind(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.parentUL) {
|
||||||
|
this.parentUL.removeClass('reordering');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.selectedTreeItem) {
|
||||||
|
this.selectedTreeItem.removeClass('reorder-actor');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ElementsController.prototype.drag = function (event) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the composition for the selected object and populates the scope with it.
|
||||||
|
*
|
||||||
|
* @param domainObject the selected object
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
ElementsController.prototype.refreshComposition = function (domainObject) {
|
||||||
|
var refreshTracker = {};
|
||||||
|
this.currentRefresh = refreshTracker;
|
||||||
|
|
||||||
|
var selectedObjectComposition = domainObject && domainObject.useCapability('composition');
|
||||||
|
if (selectedObjectComposition) {
|
||||||
|
selectedObjectComposition.then(function (composition) {
|
||||||
|
if (this.currentRefresh === refreshTracker) {
|
||||||
|
this.scope.composition = composition;
|
||||||
|
}
|
||||||
|
}.bind(this));
|
||||||
|
} else {
|
||||||
|
this.scope.composition = [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds position of object with given ID in Composition
|
||||||
|
*
|
||||||
|
* @param {String} id
|
||||||
|
* @param {Array} composition
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function findObjectInCompositionFromId(id, composition) {
|
||||||
|
var mapped = composition.map(function (element) {
|
||||||
|
return element.id;
|
||||||
|
});
|
||||||
|
|
||||||
|
return mapped.indexOf(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ElementsController;
|
||||||
|
}
|
||||||
|
);
|
133
platform/commonUI/edit/src/creation/AddAction.js
Normal file
133
platform/commonUI/edit/src/creation/AddAction.js
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module defining AddAction. Created by ahenry on 01/21/16.
|
||||||
|
*/
|
||||||
|
define(
|
||||||
|
[
|
||||||
|
'./CreateWizard'
|
||||||
|
],
|
||||||
|
function (CreateWizard) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Add Action is performed to create new instances of
|
||||||
|
* domain objects of a specific type that are subobjects of an
|
||||||
|
* object being edited. This is the action that is performed when a
|
||||||
|
* user uses the Add menu option.
|
||||||
|
*
|
||||||
|
* @memberof platform/commonUI/browse
|
||||||
|
* @implements {Action}
|
||||||
|
* @constructor
|
||||||
|
*
|
||||||
|
* @param {Type} type the type of domain object to create
|
||||||
|
* @param {DomainObject} parent the domain object that should
|
||||||
|
* act as a container for the newly-created object
|
||||||
|
* (note that the user will have an opportunity to
|
||||||
|
* override this)
|
||||||
|
* @param {ActionContext} context the context in which the
|
||||||
|
* action is being performed
|
||||||
|
* @param {DialogService} dialogService
|
||||||
|
*/
|
||||||
|
function AddAction(type, parent, context, $q, dialogService, policyService) {
|
||||||
|
this.metadata = {
|
||||||
|
key: 'add',
|
||||||
|
cssClass: type.getCssClass(),
|
||||||
|
name: type.getName(),
|
||||||
|
type: type.getKey(),
|
||||||
|
description: type.getDescription(),
|
||||||
|
context: context
|
||||||
|
};
|
||||||
|
|
||||||
|
this.type = type;
|
||||||
|
this.parent = parent;
|
||||||
|
this.$q = $q;
|
||||||
|
this.dialogService = dialogService;
|
||||||
|
this.policyService = policyService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Create a new object of the given type.
|
||||||
|
* This will prompt for user input first.
|
||||||
|
*
|
||||||
|
* @returns {Promise} that will be resolved with the object that the
|
||||||
|
* action was originally invoked on (ie. the 'parent')
|
||||||
|
*/
|
||||||
|
AddAction.prototype.perform = function () {
|
||||||
|
var newModel = this.type.getInitialModel(),
|
||||||
|
newObject,
|
||||||
|
parentObject = this.parent,
|
||||||
|
wizard;
|
||||||
|
|
||||||
|
newModel.type = this.type.getKey();
|
||||||
|
newObject = parentObject.getCapability('instantiation').instantiate(newModel);
|
||||||
|
newObject.useCapability('mutation', function (model) {
|
||||||
|
model.location = parentObject.getId();
|
||||||
|
});
|
||||||
|
|
||||||
|
wizard = new CreateWizard(newObject, this.parent, this.policyService);
|
||||||
|
|
||||||
|
function populateObjectFromInput(formValue) {
|
||||||
|
return wizard.populateObjectFromInput(formValue, newObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
function persistAndReturn(domainObject) {
|
||||||
|
return domainObject.getCapability('persistence')
|
||||||
|
.persist()
|
||||||
|
.then(function () {
|
||||||
|
return domainObject;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function addToParent(populatedObject) {
|
||||||
|
parentObject.getCapability('composition').add(populatedObject);
|
||||||
|
return persistAndReturn(parentObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.dialogService
|
||||||
|
.getUserInput(wizard.getFormStructure(false), wizard.getInitialFormValue())
|
||||||
|
.then(populateObjectFromInput)
|
||||||
|
.then(persistAndReturn)
|
||||||
|
.then(addToParent);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Metadata associated with a Add action.
|
||||||
|
* @typedef {ActionMetadata} AddActionMetadata
|
||||||
|
* @property {string} type the key for the type of domain object
|
||||||
|
* to be created
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get metadata about this action.
|
||||||
|
* @returns {AddActionMetadata} metadata about this action
|
||||||
|
*/
|
||||||
|
AddAction.prototype.getMetadata = function () {
|
||||||
|
return this.metadata;
|
||||||
|
};
|
||||||
|
|
||||||
|
return AddAction;
|
||||||
|
}
|
||||||
|
);
|
82
platform/commonUI/edit/src/creation/AddActionProvider.js
Normal file
82
platform/commonUI/edit/src/creation/AddActionProvider.js
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module defining AddActionProvider.js. Created by ahenry on 01/21/16.
|
||||||
|
*/
|
||||||
|
define(
|
||||||
|
["./AddAction"],
|
||||||
|
function (AddAction) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The AddActionProvider is an ActionProvider which introduces
|
||||||
|
* an Add action for creating sub objects.
|
||||||
|
*
|
||||||
|
* @memberof platform/commonUI/browse
|
||||||
|
* @constructor
|
||||||
|
* @implements {ActionService}
|
||||||
|
*
|
||||||
|
* @param {TypeService} typeService the type service, used to discover
|
||||||
|
* available types
|
||||||
|
* @param {DialogService} dialogService the dialog service, used by
|
||||||
|
* specific Create actions to get user input to populate the
|
||||||
|
* model of the newly-created domain object.
|
||||||
|
* @param {CreationService} creationService the creation service (also
|
||||||
|
* introduced in this bundle), responsible for handling actual
|
||||||
|
* object creation.
|
||||||
|
*/
|
||||||
|
function AddActionProvider($q, typeService, dialogService, policyService) {
|
||||||
|
this.typeService = typeService;
|
||||||
|
this.dialogService = dialogService;
|
||||||
|
this.$q = $q;
|
||||||
|
this.policyService = policyService;
|
||||||
|
}
|
||||||
|
|
||||||
|
AddActionProvider.prototype.getActions = function (actionContext) {
|
||||||
|
var context = actionContext || {},
|
||||||
|
key = context.key,
|
||||||
|
destination = context.domainObject;
|
||||||
|
|
||||||
|
// We only provide Add actions, and we need a
|
||||||
|
// domain object to serve as the container for the
|
||||||
|
// newly-created object (although the user may later
|
||||||
|
// make a different selection)
|
||||||
|
if (key !== 'add' || !destination) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Introduce one create action per type
|
||||||
|
return ['timeline', 'activity'].map(function (type) {
|
||||||
|
return new AddAction(
|
||||||
|
this.typeService.getType(type),
|
||||||
|
destination,
|
||||||
|
context,
|
||||||
|
this.$q,
|
||||||
|
this.dialogService,
|
||||||
|
this.policyService
|
||||||
|
);
|
||||||
|
}, this);
|
||||||
|
};
|
||||||
|
|
||||||
|
return AddActionProvider;
|
||||||
|
}
|
||||||
|
);
|
@ -44,7 +44,7 @@ define(
|
|||||||
* @param {ActionContext} context the context in which the
|
* @param {ActionContext} context the context in which the
|
||||||
* action is being performed
|
* action is being performed
|
||||||
*/
|
*/
|
||||||
function CreateAction(type, parent, context, openmct) {
|
function CreateAction(type, parent, context) {
|
||||||
this.metadata = {
|
this.metadata = {
|
||||||
key: 'create',
|
key: 'create',
|
||||||
cssClass: type.getCssClass(),
|
cssClass: type.getCssClass(),
|
||||||
@ -55,7 +55,6 @@ define(
|
|||||||
};
|
};
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
this.openmct = openmct;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -64,28 +63,37 @@ define(
|
|||||||
*/
|
*/
|
||||||
CreateAction.prototype.perform = function () {
|
CreateAction.prototype.perform = function () {
|
||||||
var newModel = this.type.getInitialModel(),
|
var newModel = this.type.getInitialModel(),
|
||||||
openmct = this.openmct,
|
|
||||||
newObject,
|
newObject,
|
||||||
editAction;
|
editAction,
|
||||||
|
editorCapability;
|
||||||
|
|
||||||
|
function closeEditor() {
|
||||||
|
return editorCapability.finish();
|
||||||
|
}
|
||||||
|
|
||||||
function onSave() {
|
function onSave() {
|
||||||
openmct.editor.save();
|
return editorCapability.save()
|
||||||
|
.then(closeEditor);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onCancel() {
|
function onCancel() {
|
||||||
openmct.editor.cancel();
|
return closeEditor();
|
||||||
}
|
}
|
||||||
|
|
||||||
newModel.type = this.type.getKey();
|
newModel.type = this.type.getKey();
|
||||||
newModel.location = this.parent.getId();
|
newModel.location = this.parent.getId();
|
||||||
newObject = this.parent.useCapability('instantiation', newModel);
|
newObject = this.parent.useCapability('instantiation', newModel);
|
||||||
|
editorCapability = newObject.hasCapability('editor') && newObject.getCapability("editor");
|
||||||
|
|
||||||
openmct.editor.edit();
|
|
||||||
editAction = newObject.getCapability("action").getActions("edit")[0];
|
editAction = newObject.getCapability("action").getActions("edit")[0];
|
||||||
newObject.getCapability("action").perform("save-as").then(onSave, onCancel);
|
//If an edit action is available, perform it
|
||||||
// TODO: support editing object without saving object first.
|
if (editAction) {
|
||||||
// Which means we have to toggle createwizard afterwards. For now,
|
return editAction.perform();
|
||||||
// We will disable this.
|
} else if (editorCapability) {
|
||||||
|
//otherwise, use the save as action
|
||||||
|
editorCapability.edit();
|
||||||
|
return newObject.getCapability("action").perform("save-as").then(onSave, onCancel);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -34,13 +34,13 @@ define(
|
|||||||
* @memberof platform/commonUI/browse
|
* @memberof platform/commonUI/browse
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
function CreateWizard(domainObject, parent, openmct) {
|
function CreateWizard(domainObject, parent, policyService) {
|
||||||
this.type = domainObject.getCapability('type');
|
this.type = domainObject.getCapability('type');
|
||||||
this.model = domainObject.getModel();
|
this.model = domainObject.getModel();
|
||||||
this.domainObject = domainObject;
|
this.domainObject = domainObject;
|
||||||
this.properties = this.type.getProperties();
|
this.properties = this.type.getProperties();
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
this.openmct = openmct;
|
this.policyService = policyService;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -56,10 +56,15 @@ define(
|
|||||||
*/
|
*/
|
||||||
CreateWizard.prototype.getFormStructure = function (includeLocation) {
|
CreateWizard.prototype.getFormStructure = function (includeLocation) {
|
||||||
var sections = [],
|
var sections = [],
|
||||||
domainObject = this.domainObject;
|
domainObject = this.domainObject,
|
||||||
|
policyService = this.policyService;
|
||||||
|
|
||||||
function validateLocation(parent) {
|
function validateLocation(parent) {
|
||||||
return parent && this.openmct.composition.checkPolicy(parent.useCapability('adapter'), domainObject.useCapability('adapter'));
|
return parent && policyService.allow(
|
||||||
|
"composition",
|
||||||
|
parent,
|
||||||
|
domainObject
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
sections.push({
|
sections.push({
|
||||||
@ -88,7 +93,7 @@ define(
|
|||||||
rows: [{
|
rows: [{
|
||||||
name: "Save In",
|
name: "Save In",
|
||||||
control: "locator",
|
control: "locator",
|
||||||
validate: validateLocation.bind(this),
|
validate: validateLocation,
|
||||||
key: "createParent"
|
key: "createParent"
|
||||||
}]
|
}]
|
||||||
});
|
});
|
||||||
|
111
platform/commonUI/edit/src/policies/EditActionPolicy.js
Normal file
111
platform/commonUI/edit/src/policies/EditActionPolicy.js
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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 () {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Policy controlling when the `edit` and/or `properties` actions
|
||||||
|
* can appear as applicable actions of the `view-control` category
|
||||||
|
* (shown as buttons in the top-right of browse mode.)
|
||||||
|
* @memberof platform/commonUI/edit
|
||||||
|
* @constructor
|
||||||
|
* @implements {Policy.<Action, ActionContext>}
|
||||||
|
*/
|
||||||
|
function EditActionPolicy(policyService) {
|
||||||
|
this.policyService = policyService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a count of views which are not flagged as non-editable.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
EditActionPolicy.prototype.countEditableViews = function (context) {
|
||||||
|
var domainObject = context.domainObject,
|
||||||
|
count = 0,
|
||||||
|
type, views;
|
||||||
|
|
||||||
|
if (!domainObject) {
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
type = domainObject.getCapability('type');
|
||||||
|
views = domainObject.useCapability('view');
|
||||||
|
|
||||||
|
|
||||||
|
// A view is editable unless explicitly flagged as not
|
||||||
|
(views || []).forEach(function (view) {
|
||||||
|
if (isEditable(view) ||
|
||||||
|
(view.key === 'plot' && type.getKey() === 'telemetry.panel') ||
|
||||||
|
(view.key === 'table' && type.getKey() === 'table') ||
|
||||||
|
(view.key === 'rt-table' && type.getKey() === 'rttable')
|
||||||
|
) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function isEditable(view) {
|
||||||
|
if (typeof view.editable === Function) {
|
||||||
|
return view.editable(domainObject.useCapability('adapter'));
|
||||||
|
} else {
|
||||||
|
return view.editable === true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the domain object is currently being edited. If
|
||||||
|
* so, the edit action is not applicable.
|
||||||
|
* @param context
|
||||||
|
* @returns {*|boolean}
|
||||||
|
*/
|
||||||
|
function isEditing(context) {
|
||||||
|
var domainObject = (context || {}).domainObject;
|
||||||
|
return domainObject &&
|
||||||
|
domainObject.hasCapability('editor') &&
|
||||||
|
domainObject.getCapability('editor').isEditContextRoot();
|
||||||
|
}
|
||||||
|
|
||||||
|
EditActionPolicy.prototype.allow = function (action, context) {
|
||||||
|
var key = action.getMetadata().key,
|
||||||
|
category = (context || {}).category;
|
||||||
|
|
||||||
|
// Restrict 'edit' to cases where there are editable
|
||||||
|
// views (similarly, restrict 'properties' to when
|
||||||
|
// the converse is true), and where the domain object is not
|
||||||
|
// already being edited.
|
||||||
|
if (key === 'edit') {
|
||||||
|
return this.countEditableViews(context) > 0 && !isEditing(context);
|
||||||
|
} else if (key === 'properties' && category === 'view-control') {
|
||||||
|
return this.countEditableViews(context) < 1 && !isEditing(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Like all policies, allow by default.
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
return EditActionPolicy;
|
||||||
|
}
|
||||||
|
);
|
@ -0,0 +1,72 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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 () {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Policy controlling whether the context menu is visible when
|
||||||
|
* objects are being edited
|
||||||
|
* @param navigationService
|
||||||
|
* @param editModeBlacklist A blacklist of actions disallowed from
|
||||||
|
* context menu when navigated object is being edited
|
||||||
|
* @param nonEditContextBlacklist A blacklist of actions disallowed
|
||||||
|
* from context menu of non-editable objects, when navigated object
|
||||||
|
* is being edited
|
||||||
|
* @constructor
|
||||||
|
* @param editModeBlacklist A blacklist of actions disallowed from
|
||||||
|
* context menu when navigated object is being edited
|
||||||
|
* @param nonEditContextBlacklist A blacklist of actions disallowed
|
||||||
|
* from context menu of non-editable objects, when navigated object
|
||||||
|
* @implements {Policy.<Action, ActionContext>}
|
||||||
|
*/
|
||||||
|
function EditContextualActionPolicy(navigationService, editModeBlacklist, nonEditContextBlacklist) {
|
||||||
|
this.navigationService = navigationService;
|
||||||
|
|
||||||
|
//The list of objects disallowed on target object when in edit mode
|
||||||
|
this.editModeBlacklist = editModeBlacklist;
|
||||||
|
//The list of objects disallowed on target object that is not in
|
||||||
|
// edit mode (ie. the context menu in the tree on the LHS).
|
||||||
|
this.nonEditContextBlacklist = nonEditContextBlacklist;
|
||||||
|
}
|
||||||
|
|
||||||
|
EditContextualActionPolicy.prototype.allow = function (action, context) {
|
||||||
|
var selectedObject = context.domainObject,
|
||||||
|
navigatedObject = this.navigationService.getNavigation(),
|
||||||
|
actionMetadata = action.getMetadata ? action.getMetadata() : {};
|
||||||
|
|
||||||
|
// if (navigatedObject.hasCapability("editor") && navigatedObject.getCapability("editor").isEditContextRoot()) {
|
||||||
|
if (selectedObject.hasCapability("editor") && selectedObject.getCapability("editor").inEditContext()) {
|
||||||
|
return this.editModeBlacklist.indexOf(actionMetadata.key) === -1;
|
||||||
|
} else {
|
||||||
|
//Target is in the context menu
|
||||||
|
return this.nonEditContextBlacklist.indexOf(actionMetadata.key) === -1;
|
||||||
|
}
|
||||||
|
// } else {
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
};
|
||||||
|
|
||||||
|
return EditContextualActionPolicy;
|
||||||
|
}
|
||||||
|
);
|
51
platform/commonUI/edit/src/policies/EditableLinkPolicy.js
Normal file
51
platform/commonUI/edit/src/policies/EditableLinkPolicy.js
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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 () {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Policy suppressing links when the linked-to domain object is in
|
||||||
|
* edit mode. Domain objects being edited may not have been persisted,
|
||||||
|
* so creating links to these can result in inconsistent state.
|
||||||
|
*
|
||||||
|
* @memberof platform/commonUI/edit
|
||||||
|
* @constructor
|
||||||
|
* @implements {Policy.<View, DomainObject>}
|
||||||
|
*/
|
||||||
|
function EditableLinkPolicy() {
|
||||||
|
}
|
||||||
|
|
||||||
|
EditableLinkPolicy.prototype.allow = function (action, context) {
|
||||||
|
var key = action.getMetadata().key,
|
||||||
|
object;
|
||||||
|
|
||||||
|
if (key === 'link') {
|
||||||
|
object = context.selectedObject || context.domainObject;
|
||||||
|
return !(object.hasCapability("editor") && object.getCapability("editor").inEditContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Like all policies, allow by default.
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
return EditableLinkPolicy;
|
||||||
|
});
|
52
platform/commonUI/edit/src/policies/EditableMovePolicy.js
Normal file
52
platform/commonUI/edit/src/policies/EditableMovePolicy.js
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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 () {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Policy suppressing move actions among editable and non-editable
|
||||||
|
* domain objects.
|
||||||
|
* @memberof platform/commonUI/edit
|
||||||
|
* @constructor
|
||||||
|
* @implements {Policy.<View, DomainObject>}
|
||||||
|
*/
|
||||||
|
function EditableMovePolicy() {
|
||||||
|
}
|
||||||
|
|
||||||
|
EditableMovePolicy.prototype.allow = function (action, context) {
|
||||||
|
var domainObject = context.domainObject,
|
||||||
|
selectedObject = context.selectedObject,
|
||||||
|
key = action.getMetadata().key,
|
||||||
|
isDomainObjectEditing = domainObject.hasCapability('editor') &&
|
||||||
|
domainObject.getCapability('editor').inEditContext();
|
||||||
|
|
||||||
|
if (key === 'move' && isDomainObjectEditing) {
|
||||||
|
return !!selectedObject && selectedObject.hasCapability('editor') &&
|
||||||
|
selectedObject.getCapability('editor').inEditContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Like all policies, allow by default.
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
return EditableMovePolicy;
|
||||||
|
});
|
49
platform/commonUI/edit/src/policies/EditableViewPolicy.js
Normal file
49
platform/commonUI/edit/src/policies/EditableViewPolicy.js
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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 () {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Policy controlling which views should be visible in Edit mode.
|
||||||
|
* @memberof platform/commonUI/edit
|
||||||
|
* @constructor
|
||||||
|
* @implements {Policy.<View, DomainObject>}
|
||||||
|
*/
|
||||||
|
function EditableViewPolicy() {
|
||||||
|
}
|
||||||
|
|
||||||
|
EditableViewPolicy.prototype.allow = function (view, domainObject) {
|
||||||
|
// If a view is flagged as non-editable, only allow it
|
||||||
|
// while we're not in Edit mode.
|
||||||
|
if ((view || {}).editable === false) {
|
||||||
|
return !(domainObject.hasCapability('editor') && domainObject.getCapability('editor').inEditContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Like all policies, allow by default.
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
return EditableViewPolicy;
|
||||||
|
}
|
||||||
|
);
|
254
platform/commonUI/edit/src/representers/EditToolbar.js
Normal file
254
platform/commonUI/edit/src/representers/EditToolbar.js
Normal file
@ -0,0 +1,254 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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/api/objects/object-utils',
|
||||||
|
'lodash'
|
||||||
|
],
|
||||||
|
function (
|
||||||
|
objectUtils,
|
||||||
|
_
|
||||||
|
) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides initial structure and state (as suitable for provision
|
||||||
|
* to the `mct-toolbar` directive) for a view's toolbar, based on
|
||||||
|
* that view's declaration of what belongs in its toolbar and on
|
||||||
|
* the current selection.
|
||||||
|
*
|
||||||
|
* @param $scope the Angular scope
|
||||||
|
* @param {Object} openmct the openmct object
|
||||||
|
* @param structure the toolbar structure
|
||||||
|
* @memberof platform/commonUI/edit
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function EditToolbar($scope, openmct, structure) {
|
||||||
|
this.toolbarStructure = [];
|
||||||
|
this.properties = [];
|
||||||
|
this.toolbarState = [];
|
||||||
|
this.openmct = openmct;
|
||||||
|
this.domainObjectsById = {};
|
||||||
|
this.unobserveObjects = [];
|
||||||
|
this.stateTracker = [];
|
||||||
|
|
||||||
|
$scope.$watchCollection(this.getState.bind(this), this.handleStateChanges.bind(this));
|
||||||
|
$scope.$on("$destroy", this.destroy.bind(this));
|
||||||
|
|
||||||
|
this.updateToolbar(structure);
|
||||||
|
this.registerListeners(structure);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the toolbar with a new structure.
|
||||||
|
*
|
||||||
|
* @param {Array} structure the toolbar structure
|
||||||
|
*/
|
||||||
|
EditToolbar.prototype.updateToolbar = function (structure) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
function addKey(item) {
|
||||||
|
self.stateTracker.push({
|
||||||
|
id: objectUtils.makeKeyString(item.domainObject.identifier),
|
||||||
|
domainObject: item.domainObject,
|
||||||
|
property: item.property
|
||||||
|
});
|
||||||
|
self.properties.push(item.property);
|
||||||
|
|
||||||
|
return self.properties.length - 1; // Return index of property
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertItem(item) {
|
||||||
|
var converted = Object.create(item || {});
|
||||||
|
|
||||||
|
if (item.property) {
|
||||||
|
converted.key = addKey(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.method) {
|
||||||
|
converted.click = function (value) {
|
||||||
|
item.method(value);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return converted;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get initial value for a given property
|
||||||
|
function initializeState(property) {
|
||||||
|
var result;
|
||||||
|
structure.forEach(function (item) {
|
||||||
|
if (item.property === property) {
|
||||||
|
result = _.get(item.domainObject, item.property);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tracks the domain object and property for every element in the state array
|
||||||
|
this.stateTracker = [];
|
||||||
|
this.toolbarStructure = structure.map(convertItem);
|
||||||
|
this.toolbarState = this.properties.map(initializeState);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the structure of the toolbar, as appropriate to
|
||||||
|
* pass to `mct-toolbar`.
|
||||||
|
*
|
||||||
|
* @returns {Array} the toolbar structure
|
||||||
|
*/
|
||||||
|
EditToolbar.prototype.getStructure = function () {
|
||||||
|
return this.toolbarStructure;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current state of the toolbar, as appropriate
|
||||||
|
* to two-way bind to the state handled by `mct-toolbar`.
|
||||||
|
*
|
||||||
|
* @returns {Array} state of the toolbar
|
||||||
|
*/
|
||||||
|
EditToolbar.prototype.getState = function () {
|
||||||
|
return this.toolbarState;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mutates the domain object's property with a new value.
|
||||||
|
*
|
||||||
|
* @param {Object} dominObject the domain object
|
||||||
|
* @param {string} property the domain object's property to update
|
||||||
|
* @param value the property's new value
|
||||||
|
*/
|
||||||
|
EditToolbar.prototype.updateDomainObject = function (domainObject, property, value) {
|
||||||
|
this.openmct.objects.mutate(domainObject, property, value);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates state with the new value.
|
||||||
|
*
|
||||||
|
* @param {number} index the index of the corresponding
|
||||||
|
* element in the state array
|
||||||
|
* @param value the new value to update the state array with
|
||||||
|
*/
|
||||||
|
EditToolbar.prototype.updateState = function (index, value) {
|
||||||
|
this.toolbarState[index] = value;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register listeners for domain objects to watch for updates.
|
||||||
|
*
|
||||||
|
* @param {Array} the toolbar structure
|
||||||
|
*/
|
||||||
|
EditToolbar.prototype.registerListeners = function (structure) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
function observeObject(domainObject, id) {
|
||||||
|
var unobserveObject = self.openmct.objects.observe(domainObject, '*', function (newObject) {
|
||||||
|
self.domainObjectsById[id].newObject = JSON.parse(JSON.stringify(newObject));
|
||||||
|
self.scheduleStateUpdate();
|
||||||
|
});
|
||||||
|
self.unobserveObjects.push(unobserveObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
structure.forEach(function (item) {
|
||||||
|
var domainObject = item.domainObject;
|
||||||
|
var id = objectUtils.makeKeyString(domainObject.identifier);
|
||||||
|
|
||||||
|
if (!self.domainObjectsById[id]) {
|
||||||
|
self.domainObjectsById[id] = {
|
||||||
|
domainObject: domainObject,
|
||||||
|
properties: []
|
||||||
|
};
|
||||||
|
observeObject(domainObject, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.domainObjectsById[id].properties.push(item.property);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delays updating the state.
|
||||||
|
*/
|
||||||
|
EditToolbar.prototype.scheduleStateUpdate = function () {
|
||||||
|
if (this.stateUpdateScheduled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.stateUpdateScheduled = true;
|
||||||
|
setTimeout(this.updateStateAfterMutation.bind(this));
|
||||||
|
};
|
||||||
|
|
||||||
|
EditToolbar.prototype.updateStateAfterMutation = function () {
|
||||||
|
this.stateTracker.forEach(function (state, index) {
|
||||||
|
if (!this.domainObjectsById[state.id].newObject) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var domainObject = this.domainObjectsById[state.id].domainObject;
|
||||||
|
var newObject = this.domainObjectsById[state.id].newObject;
|
||||||
|
var currentValue = _.get(domainObject, state.property);
|
||||||
|
var newValue = _.get(newObject, state.property);
|
||||||
|
|
||||||
|
state.domainObject = newObject;
|
||||||
|
|
||||||
|
if (currentValue !== newValue) {
|
||||||
|
this.updateState(index, newValue);
|
||||||
|
}
|
||||||
|
}, this);
|
||||||
|
|
||||||
|
Object.values(this.domainObjectsById).forEach(function (tracker) {
|
||||||
|
if (tracker.newObject) {
|
||||||
|
tracker.domainObject = tracker.newObject;
|
||||||
|
}
|
||||||
|
delete tracker.newObject;
|
||||||
|
});
|
||||||
|
this.stateUpdateScheduled = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the listeners.
|
||||||
|
*/
|
||||||
|
EditToolbar.prototype.deregisterListeners = function () {
|
||||||
|
this.unobserveObjects.forEach(function (unobserveObject) {
|
||||||
|
unobserveObject();
|
||||||
|
});
|
||||||
|
this.unobserveObjects = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
EditToolbar.prototype.handleStateChanges = function (state) {
|
||||||
|
(state || []).map(function (newValue, index) {
|
||||||
|
var domainObject = this.stateTracker[index].domainObject;
|
||||||
|
var property = this.stateTracker[index].property;
|
||||||
|
var currentValue = _.get(domainObject, property);
|
||||||
|
|
||||||
|
if (currentValue !== newValue) {
|
||||||
|
this.updateDomainObject(domainObject, property, newValue);
|
||||||
|
}
|
||||||
|
}, this);
|
||||||
|
};
|
||||||
|
|
||||||
|
EditToolbar.prototype.destroy = function () {
|
||||||
|
this.deregisterListeners();
|
||||||
|
};
|
||||||
|
|
||||||
|
return EditToolbar;
|
||||||
|
}
|
||||||
|
);
|
@ -29,7 +29,7 @@ define(
|
|||||||
actionContext,
|
actionContext,
|
||||||
capabilities,
|
capabilities,
|
||||||
mockContext,
|
mockContext,
|
||||||
mockOverlayAPI,
|
mockDialogService,
|
||||||
mockDomainObject,
|
mockDomainObject,
|
||||||
mockMutation,
|
mockMutation,
|
||||||
mockNavigationService,
|
mockNavigationService,
|
||||||
@ -68,9 +68,9 @@ define(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
mockOverlayAPI = jasmine.createSpyObj(
|
mockDialogService = jasmine.createSpyObj(
|
||||||
"overlayAPI",
|
"dialogService",
|
||||||
["dialog"]
|
["showBlockingMessage"]
|
||||||
);
|
);
|
||||||
|
|
||||||
mockNavigationService = jasmine.createSpyObj(
|
mockNavigationService = jasmine.createSpyObj(
|
||||||
@ -96,7 +96,7 @@ define(
|
|||||||
|
|
||||||
actionContext = { domainObject: mockDomainObject };
|
actionContext = { domainObject: mockDomainObject };
|
||||||
|
|
||||||
action = new RemoveAction({overlays: mockOverlayAPI}, mockNavigationService, actionContext);
|
action = new RemoveAction(mockDialogService, mockNavigationService, actionContext);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("only applies to objects with parents", function () {
|
it("only applies to objects with parents", function () {
|
||||||
@ -118,7 +118,7 @@ define(
|
|||||||
|
|
||||||
action.perform();
|
action.perform();
|
||||||
|
|
||||||
expect(mockOverlayAPI.dialog).toHaveBeenCalled();
|
expect(mockDialogService.showBlockingMessage).toHaveBeenCalled();
|
||||||
|
|
||||||
// Also check that no mutation happens at this point
|
// Also check that no mutation happens at this point
|
||||||
expect(mockParent.useCapability).not.toHaveBeenCalledWith("mutation", jasmine.any(Function));
|
expect(mockParent.useCapability).not.toHaveBeenCalledWith("mutation", jasmine.any(Function));
|
||||||
@ -158,13 +158,13 @@ define(
|
|||||||
mockGrandchildContext = jasmine.createSpyObj("context", ["getParent"]);
|
mockGrandchildContext = jasmine.createSpyObj("context", ["getParent"]);
|
||||||
mockRootContext = jasmine.createSpyObj("context", ["getParent"]);
|
mockRootContext = jasmine.createSpyObj("context", ["getParent"]);
|
||||||
|
|
||||||
mockOverlayAPI.dialog.and.returnValue(mockDialogHandle);
|
mockDialogService.showBlockingMessage.and.returnValue(mockDialogHandle);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("mutates the parent when performed", function () {
|
it("mutates the parent when performed", function () {
|
||||||
action.perform();
|
action.perform();
|
||||||
mockOverlayAPI.dialog.calls.mostRecent().args[0]
|
mockDialogService.showBlockingMessage.calls.mostRecent().args[0]
|
||||||
.buttons[0].callback();
|
.primaryOption.callback();
|
||||||
|
|
||||||
expect(mockMutation.invoke)
|
expect(mockMutation.invoke)
|
||||||
.toHaveBeenCalledWith(jasmine.any(Function));
|
.toHaveBeenCalledWith(jasmine.any(Function));
|
||||||
@ -174,8 +174,8 @@ define(
|
|||||||
var mutator, result;
|
var mutator, result;
|
||||||
|
|
||||||
action.perform();
|
action.perform();
|
||||||
mockOverlayAPI.dialog.calls.mostRecent().args[0]
|
mockDialogService.showBlockingMessage.calls.mostRecent().args[0]
|
||||||
.buttons[0].callback();
|
.primaryOption.callback();
|
||||||
|
|
||||||
mutator = mockMutation.invoke.calls.mostRecent().args[0];
|
mutator = mockMutation.invoke.calls.mostRecent().args[0];
|
||||||
result = mutator(model);
|
result = mutator(model);
|
||||||
@ -212,8 +212,8 @@ define(
|
|||||||
mockType.hasFeature.and.returnValue(true);
|
mockType.hasFeature.and.returnValue(true);
|
||||||
|
|
||||||
action.perform();
|
action.perform();
|
||||||
mockOverlayAPI.dialog.calls.mostRecent().args[0]
|
mockDialogService.showBlockingMessage.calls.mostRecent().args[0]
|
||||||
.buttons[0].callback();
|
.primaryOption.callback();
|
||||||
|
|
||||||
// Expects navigation to parent of domainObject (removed object)
|
// Expects navigation to parent of domainObject (removed object)
|
||||||
expect(mockNavigationService.setNavigation).toHaveBeenCalledWith(mockParent);
|
expect(mockNavigationService.setNavigation).toHaveBeenCalledWith(mockParent);
|
||||||
@ -242,8 +242,8 @@ define(
|
|||||||
mockType.hasFeature.and.returnValue(true);
|
mockType.hasFeature.and.returnValue(true);
|
||||||
|
|
||||||
action.perform();
|
action.perform();
|
||||||
mockOverlayAPI.dialog.calls.mostRecent().args[0]
|
mockDialogService.showBlockingMessage.calls.mostRecent().args[0]
|
||||||
.buttons[0].callback();
|
.primaryOption.callback();
|
||||||
|
|
||||||
// Expects no navigation to occur
|
// Expects no navigation to occur
|
||||||
expect(mockNavigationService.setNavigation).not.toHaveBeenCalled();
|
expect(mockNavigationService.setNavigation).not.toHaveBeenCalled();
|
||||||
|
@ -0,0 +1,184 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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/controllers/ElementsController"],
|
||||||
|
function (ElementsController) {
|
||||||
|
|
||||||
|
describe("The Elements Pane controller", function () {
|
||||||
|
var mockScope,
|
||||||
|
mockOpenMCT,
|
||||||
|
mockSelection,
|
||||||
|
mockDomainObject,
|
||||||
|
mockMutationCapability,
|
||||||
|
mockCompositionCapability,
|
||||||
|
mockCompositionObjects,
|
||||||
|
mockComposition,
|
||||||
|
mockUnlisten,
|
||||||
|
selectable = [],
|
||||||
|
controller;
|
||||||
|
|
||||||
|
function mockPromise(value) {
|
||||||
|
return {
|
||||||
|
then: function (thenFunc) {
|
||||||
|
return mockPromise(thenFunc(value));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createDomainObject() {
|
||||||
|
return {
|
||||||
|
useCapability: function () {
|
||||||
|
return mockCompositionCapability;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
mockComposition = ["a", "b"];
|
||||||
|
mockCompositionObjects = mockComposition.map(createDomainObject);
|
||||||
|
mockCompositionCapability = mockPromise(mockCompositionObjects);
|
||||||
|
|
||||||
|
mockUnlisten = jasmine.createSpy('unlisten');
|
||||||
|
mockMutationCapability = jasmine.createSpyObj("mutationCapability", [
|
||||||
|
"listen"
|
||||||
|
]);
|
||||||
|
mockMutationCapability.listen.and.returnValue(mockUnlisten);
|
||||||
|
mockDomainObject = jasmine.createSpyObj("domainObject", [
|
||||||
|
"getCapability",
|
||||||
|
"useCapability"
|
||||||
|
]);
|
||||||
|
mockDomainObject.useCapability.and.returnValue(mockCompositionCapability);
|
||||||
|
mockDomainObject.getCapability.and.returnValue(mockMutationCapability);
|
||||||
|
|
||||||
|
mockScope = jasmine.createSpyObj("$scope", ['$on']);
|
||||||
|
mockSelection = jasmine.createSpyObj("selection", [
|
||||||
|
'on',
|
||||||
|
'off',
|
||||||
|
'get'
|
||||||
|
]);
|
||||||
|
mockSelection.get.and.returnValue([]);
|
||||||
|
mockOpenMCT = {
|
||||||
|
selection: mockSelection
|
||||||
|
};
|
||||||
|
|
||||||
|
selectable[0] = {
|
||||||
|
context: {
|
||||||
|
oldItem: mockDomainObject
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
spyOn(ElementsController.prototype, 'refreshComposition').and.callThrough();
|
||||||
|
|
||||||
|
controller = new ElementsController(mockScope, mockOpenMCT);
|
||||||
|
});
|
||||||
|
|
||||||
|
function getModel(model) {
|
||||||
|
return function () {
|
||||||
|
return model;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
it("filters objects in elements pool based on input text and" +
|
||||||
|
" object name", function () {
|
||||||
|
var objects = [
|
||||||
|
{
|
||||||
|
getModel: getModel({name: "first element"})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
getModel: getModel({name: "second element"})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
getModel: getModel({name: "third element"})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
getModel: getModel({name: "THIRD Element 1"})
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
mockScope.filterBy("third element");
|
||||||
|
expect(objects.filter(mockScope.searchElements).length).toBe(2);
|
||||||
|
mockScope.filterBy("element");
|
||||||
|
expect(objects.filter(mockScope.searchElements).length).toBe(4);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("refreshes composition on selection", function () {
|
||||||
|
mockOpenMCT.selection.on.calls.mostRecent().args[1](selectable);
|
||||||
|
|
||||||
|
expect(ElementsController.prototype.refreshComposition).toHaveBeenCalledWith(mockDomainObject);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("listens on mutation and refreshes composition", function () {
|
||||||
|
mockOpenMCT.selection.on.calls.mostRecent().args[1](selectable);
|
||||||
|
|
||||||
|
expect(mockDomainObject.getCapability).toHaveBeenCalledWith('mutation');
|
||||||
|
expect(mockMutationCapability.listen).toHaveBeenCalled();
|
||||||
|
expect(ElementsController.prototype.refreshComposition.calls.count()).toBe(1);
|
||||||
|
|
||||||
|
mockMutationCapability.listen.calls.mostRecent().args[0](mockDomainObject);
|
||||||
|
|
||||||
|
expect(ElementsController.prototype.refreshComposition.calls.count()).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("cleans up mutation listener when selection changes", function () {
|
||||||
|
mockOpenMCT.selection.on.calls.mostRecent().args[1](selectable);
|
||||||
|
|
||||||
|
expect(mockMutationCapability.listen).toHaveBeenCalled();
|
||||||
|
|
||||||
|
mockOpenMCT.selection.on.calls.mostRecent().args[1](selectable);
|
||||||
|
|
||||||
|
expect(mockUnlisten).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not listen on mutation for element proxy selectable", function () {
|
||||||
|
selectable[0] = {
|
||||||
|
context: {
|
||||||
|
elementProxy: {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
mockOpenMCT.selection.on.calls.mostRecent().args[1](selectable);
|
||||||
|
|
||||||
|
expect(mockDomainObject.getCapability).not.toHaveBeenCalledWith('mutation');
|
||||||
|
});
|
||||||
|
|
||||||
|
it("checks concurrent changes to composition", function () {
|
||||||
|
var secondMockComposition = ["a", "b", "c"],
|
||||||
|
secondMockCompositionObjects = secondMockComposition.map(createDomainObject),
|
||||||
|
firstCompositionCallback,
|
||||||
|
secondCompositionCallback;
|
||||||
|
|
||||||
|
spyOn(mockCompositionCapability, "then").and.callThrough();
|
||||||
|
|
||||||
|
controller.refreshComposition(mockDomainObject);
|
||||||
|
controller.refreshComposition(mockDomainObject);
|
||||||
|
|
||||||
|
firstCompositionCallback = mockCompositionCapability.then.calls.all()[0].args[0];
|
||||||
|
secondCompositionCallback = mockCompositionCapability.then.calls.all()[1].args[0];
|
||||||
|
secondCompositionCallback(secondMockCompositionObjects);
|
||||||
|
firstCompositionCallback(mockCompositionObjects);
|
||||||
|
|
||||||
|
expect(mockScope.composition).toBe(secondMockCompositionObjects);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
105
platform/commonUI/edit/test/creation/AddActionProviderSpec.js
Normal file
105
platform/commonUI/edit/test/creation/AddActionProviderSpec.js
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MCTRepresentationSpec. Created by ahenry on 01/21/14.
|
||||||
|
*/
|
||||||
|
define(
|
||||||
|
["../../src/creation/AddActionProvider"],
|
||||||
|
function (AddActionProvider) {
|
||||||
|
|
||||||
|
describe("The add action provider", function () {
|
||||||
|
var mockTypeService,
|
||||||
|
mockDialogService,
|
||||||
|
mockPolicyService,
|
||||||
|
mockTypeMap,
|
||||||
|
mockTypes,
|
||||||
|
mockDomainObject,
|
||||||
|
mockQ,
|
||||||
|
provider;
|
||||||
|
|
||||||
|
function createMockType(name) {
|
||||||
|
var mockType = jasmine.createSpyObj(
|
||||||
|
"type" + name,
|
||||||
|
[
|
||||||
|
"getKey",
|
||||||
|
"getGlyph",
|
||||||
|
"getCssClass",
|
||||||
|
"getName",
|
||||||
|
"getDescription",
|
||||||
|
"getProperties",
|
||||||
|
"getInitialModel",
|
||||||
|
"hasFeature"
|
||||||
|
]
|
||||||
|
);
|
||||||
|
mockType.hasFeature.and.returnValue(true);
|
||||||
|
mockType.getName.and.returnValue(name);
|
||||||
|
mockType.getKey.and.returnValue(name);
|
||||||
|
return mockType;
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
mockTypeService = jasmine.createSpyObj(
|
||||||
|
"typeService",
|
||||||
|
["getType"]
|
||||||
|
);
|
||||||
|
mockDialogService = {};
|
||||||
|
mockPolicyService = {};
|
||||||
|
mockDomainObject = {};
|
||||||
|
|
||||||
|
mockTypes = [
|
||||||
|
"timeline",
|
||||||
|
"activity",
|
||||||
|
"other"
|
||||||
|
].map(createMockType);
|
||||||
|
mockTypeMap = {};
|
||||||
|
|
||||||
|
mockTypes.forEach(function (type) {
|
||||||
|
mockTypeMap[type.getKey()] = type;
|
||||||
|
});
|
||||||
|
|
||||||
|
mockTypeService.getType.and.callFake(function (key) {
|
||||||
|
return mockTypeMap[key];
|
||||||
|
});
|
||||||
|
|
||||||
|
provider = new AddActionProvider(
|
||||||
|
mockQ,
|
||||||
|
mockTypeService,
|
||||||
|
mockDialogService,
|
||||||
|
mockPolicyService
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("provides actions for timeline and activity", function () {
|
||||||
|
var actions = provider.getActions({
|
||||||
|
key: "add",
|
||||||
|
domainObject: mockDomainObject
|
||||||
|
});
|
||||||
|
expect(actions.length).toBe(2);
|
||||||
|
expect(actions[0].metadata.type).toBe('timeline');
|
||||||
|
expect(actions[1].metadata.type).toBe('activity');
|
||||||
|
|
||||||
|
// Make sure it was creation which was used to check
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
138
platform/commonUI/edit/test/policies/EditActionPolicySpec.js
Normal file
138
platform/commonUI/edit/test/policies/EditActionPolicySpec.js
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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/policies/EditActionPolicy"],
|
||||||
|
function (EditActionPolicy) {
|
||||||
|
|
||||||
|
describe("The Edit action policy", function () {
|
||||||
|
var editableView,
|
||||||
|
nonEditableView,
|
||||||
|
testViews,
|
||||||
|
testContext,
|
||||||
|
mockDomainObject,
|
||||||
|
mockEditAction,
|
||||||
|
mockPropertiesAction,
|
||||||
|
mockTypeCapability,
|
||||||
|
mockEditorCapability,
|
||||||
|
capabilities,
|
||||||
|
plotView,
|
||||||
|
policy;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
mockDomainObject = jasmine.createSpyObj(
|
||||||
|
'domainObject',
|
||||||
|
[
|
||||||
|
'useCapability',
|
||||||
|
'hasCapability',
|
||||||
|
'getCapability'
|
||||||
|
]
|
||||||
|
);
|
||||||
|
mockEditorCapability = jasmine.createSpyObj('editorCapability', ['isEditContextRoot']);
|
||||||
|
mockTypeCapability = jasmine.createSpyObj('type', ['getKey']);
|
||||||
|
capabilities = {
|
||||||
|
'editor': mockEditorCapability,
|
||||||
|
'type': mockTypeCapability
|
||||||
|
};
|
||||||
|
|
||||||
|
mockEditAction = jasmine.createSpyObj('edit', ['getMetadata']);
|
||||||
|
mockPropertiesAction = jasmine.createSpyObj('edit', ['getMetadata']);
|
||||||
|
|
||||||
|
mockDomainObject.getCapability.and.callFake(function (capability) {
|
||||||
|
return capabilities[capability];
|
||||||
|
});
|
||||||
|
mockDomainObject.hasCapability.and.callFake(function (capability) {
|
||||||
|
return !!capabilities[capability];
|
||||||
|
});
|
||||||
|
|
||||||
|
editableView = { editable: true };
|
||||||
|
nonEditableView = { editable: false };
|
||||||
|
plotView = { key: "plot", editable: false };
|
||||||
|
testViews = [];
|
||||||
|
|
||||||
|
mockDomainObject.useCapability.and.callFake(function (c) {
|
||||||
|
// Provide test views, only for the view capability
|
||||||
|
return c === 'view' && testViews;
|
||||||
|
});
|
||||||
|
|
||||||
|
mockEditAction.getMetadata.and.returnValue({ key: 'edit' });
|
||||||
|
mockPropertiesAction.getMetadata.and.returnValue({ key: 'properties' });
|
||||||
|
|
||||||
|
testContext = {
|
||||||
|
domainObject: mockDomainObject,
|
||||||
|
category: 'view-control'
|
||||||
|
};
|
||||||
|
|
||||||
|
policy = new EditActionPolicy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows the edit action when there are editable views", function () {
|
||||||
|
testViews = [editableView];
|
||||||
|
expect(policy.allow(mockEditAction, testContext)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows the edit properties action when there are no editable views", function () {
|
||||||
|
testViews = [nonEditableView, nonEditableView];
|
||||||
|
expect(policy.allow(mockPropertiesAction, testContext)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("disallows the edit action when there are no editable views", function () {
|
||||||
|
testViews = [nonEditableView, nonEditableView];
|
||||||
|
expect(policy.allow(mockEditAction, testContext)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("disallows the edit properties action when there are" +
|
||||||
|
" editable views", function () {
|
||||||
|
testViews = [editableView];
|
||||||
|
expect(policy.allow(mockPropertiesAction, testContext)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("disallows the edit action when object is already being" +
|
||||||
|
" edited", function () {
|
||||||
|
testViews = [editableView];
|
||||||
|
mockEditorCapability.isEditContextRoot.and.returnValue(true);
|
||||||
|
expect(policy.allow(mockEditAction, testContext)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows editing of panels in plot view", function () {
|
||||||
|
testViews = [plotView];
|
||||||
|
mockTypeCapability.getKey.and.returnValue('telemetry.panel');
|
||||||
|
|
||||||
|
expect(policy.allow(mockEditAction, testContext)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("disallows editing of plot view when object not a panel type", function () {
|
||||||
|
testViews = [plotView];
|
||||||
|
mockTypeCapability.getKey.and.returnValue('something.else');
|
||||||
|
|
||||||
|
expect(policy.allow(mockEditAction, testContext)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it("allows the edit properties outside of the 'view-control' category", function () {
|
||||||
|
testViews = [nonEditableView];
|
||||||
|
testContext.category = "something-else";
|
||||||
|
expect(policy.allow(mockPropertiesAction, testContext)).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
@ -0,0 +1,120 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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/policies/EditContextualActionPolicy"],
|
||||||
|
function (EditContextualActionPolicy) {
|
||||||
|
|
||||||
|
describe("The Edit contextual action policy", function () {
|
||||||
|
var policy,
|
||||||
|
navigationService,
|
||||||
|
mockAction,
|
||||||
|
context,
|
||||||
|
navigatedObject,
|
||||||
|
mockDomainObject,
|
||||||
|
mockEditorCapability,
|
||||||
|
metadata,
|
||||||
|
editModeBlacklist = ["copy", "follow", "window", "link", "locate"],
|
||||||
|
nonEditContextBlacklist = ["copy", "follow", "properties", "move", "link", "remove", "locate"];
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
mockEditorCapability = jasmine.createSpyObj("editorCapability", ["isEditContextRoot", "inEditContext"]);
|
||||||
|
|
||||||
|
navigatedObject = jasmine.createSpyObj("navigatedObject", ["hasCapability", "getCapability"]);
|
||||||
|
navigatedObject.getCapability.and.returnValue(mockEditorCapability);
|
||||||
|
navigatedObject.hasCapability.and.returnValue(false);
|
||||||
|
|
||||||
|
|
||||||
|
mockDomainObject = jasmine.createSpyObj("domainObject", ["hasCapability", "getCapability"]);
|
||||||
|
mockDomainObject.hasCapability.and.returnValue(false);
|
||||||
|
mockDomainObject.getCapability.and.returnValue(mockEditorCapability);
|
||||||
|
|
||||||
|
navigationService = jasmine.createSpyObj("navigationService", ["getNavigation"]);
|
||||||
|
navigationService.getNavigation.and.returnValue(navigatedObject);
|
||||||
|
|
||||||
|
metadata = {key: "move"};
|
||||||
|
mockAction = jasmine.createSpyObj("action", ["getMetadata"]);
|
||||||
|
mockAction.getMetadata.and.returnValue(metadata);
|
||||||
|
|
||||||
|
context = {domainObject: mockDomainObject};
|
||||||
|
|
||||||
|
policy = new EditContextualActionPolicy(navigationService, editModeBlacklist, nonEditContextBlacklist);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Allows all actions when navigated object not in edit mode', function () {
|
||||||
|
expect(policy.allow(mockAction, context)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Allows "window" action when navigated object in edit mode,' +
|
||||||
|
' but selected object not in edit mode ', function () {
|
||||||
|
navigatedObject.hasCapability.and.returnValue(true);
|
||||||
|
mockEditorCapability.isEditContextRoot.and.returnValue(true);
|
||||||
|
metadata.key = "window";
|
||||||
|
expect(policy.allow(mockAction, context)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Allows "remove" action when navigated object in edit mode,' +
|
||||||
|
' and selected object not editable, but its parent is.',
|
||||||
|
function () {
|
||||||
|
var mockParent = jasmine.createSpyObj("parentObject", ["hasCapability"]),
|
||||||
|
mockContextCapability = jasmine.createSpyObj("contextCapability", ["getParent"]);
|
||||||
|
|
||||||
|
mockParent.hasCapability.and.returnValue(true);
|
||||||
|
mockContextCapability.getParent.and.returnValue(mockParent);
|
||||||
|
navigatedObject.hasCapability.and.returnValue(true);
|
||||||
|
|
||||||
|
mockDomainObject.getCapability.and.returnValue(mockContextCapability);
|
||||||
|
mockDomainObject.hasCapability.and.callFake(function (capability) {
|
||||||
|
switch (capability) {
|
||||||
|
case "editor": return false;
|
||||||
|
case "context": return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
metadata.key = "remove";
|
||||||
|
|
||||||
|
expect(policy.allow(mockAction, context)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Disallows "move" action when navigated object in edit mode,' +
|
||||||
|
' but selected object not in edit mode ', function () {
|
||||||
|
navigatedObject.hasCapability.and.returnValue(true);
|
||||||
|
mockEditorCapability.isEditContextRoot.and.returnValue(true);
|
||||||
|
mockEditorCapability.inEditContext.and.returnValue(false);
|
||||||
|
metadata.key = "move";
|
||||||
|
expect(policy.allow(mockAction, context)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Disallows copy action when navigated object and' +
|
||||||
|
' selected object in edit mode', function () {
|
||||||
|
navigatedObject.hasCapability.and.returnValue(true);
|
||||||
|
mockDomainObject.hasCapability.and.returnValue(true);
|
||||||
|
mockEditorCapability.isEditContextRoot.and.returnValue(true);
|
||||||
|
mockEditorCapability.inEditContext.and.returnValue(true);
|
||||||
|
|
||||||
|
metadata.key = "copy";
|
||||||
|
expect(policy.allow(mockAction, context)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
@ -0,0 +1,79 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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/policies/EditableViewPolicy"],
|
||||||
|
function (EditableViewPolicy) {
|
||||||
|
|
||||||
|
describe("The editable view policy", function () {
|
||||||
|
var mockDomainObject,
|
||||||
|
testMode,
|
||||||
|
policy;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
testMode = true; // Act as if we're in Edit mode by default
|
||||||
|
mockDomainObject = jasmine.createSpyObj(
|
||||||
|
'domainObject',
|
||||||
|
['hasCapability', 'getCapability']
|
||||||
|
);
|
||||||
|
mockDomainObject.getCapability.and.returnValue({
|
||||||
|
inEditContext: function () {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mockDomainObject.hasCapability.and.callFake(function (c) {
|
||||||
|
return (c === 'editor') && testMode;
|
||||||
|
});
|
||||||
|
|
||||||
|
policy = new EditableViewPolicy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("disallows views in edit mode that are flagged as non-editable", function () {
|
||||||
|
expect(policy.allow({ editable: false }, mockDomainObject))
|
||||||
|
.toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows views in edit mode that are flagged as editable", function () {
|
||||||
|
expect(policy.allow({ editable: true }, mockDomainObject))
|
||||||
|
.toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows any view outside of edit mode", function () {
|
||||||
|
var testViews = [
|
||||||
|
{ editable: false },
|
||||||
|
{ editable: true },
|
||||||
|
{ someKey: "some value" }
|
||||||
|
];
|
||||||
|
testMode = false; // Act as if we're not in Edit mode
|
||||||
|
|
||||||
|
testViews.forEach(function (testView) {
|
||||||
|
expect(policy.allow(testView, mockDomainObject)).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("treats views with no defined 'editable' property as editable", function () {
|
||||||
|
expect(policy.allow({ someKey: "some value" }, mockDomainObject))
|
||||||
|
.toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
75
platform/commonUI/edit/test/representers/EditToolbarSpec.js
Normal file
75
platform/commonUI/edit/test/representers/EditToolbarSpec.js
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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/representers/EditToolbar'],
|
||||||
|
function (EditToolbar) {
|
||||||
|
|
||||||
|
describe("An Edit mode toolbar", function () {
|
||||||
|
var mockOpenMCT,
|
||||||
|
mockScope,
|
||||||
|
mockObjects,
|
||||||
|
mockDomainObject,
|
||||||
|
testStructure,
|
||||||
|
toolbar;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
mockOpenMCT = jasmine.createSpy('openmct', ['objects']);
|
||||||
|
mockObjects = jasmine.createSpyObj('objects', ['observe']);
|
||||||
|
mockObjects.observe.and.returnValue();
|
||||||
|
mockOpenMCT.objects = mockObjects;
|
||||||
|
mockScope = jasmine.createSpyObj("$scope", [
|
||||||
|
"$watchCollection",
|
||||||
|
"$on"
|
||||||
|
]);
|
||||||
|
mockScope.$watchCollection.and.returnValue();
|
||||||
|
mockDomainObject = jasmine.createSpyObj("domainObject", [
|
||||||
|
'identifier'
|
||||||
|
]);
|
||||||
|
|
||||||
|
testStructure = [
|
||||||
|
{ name: "A", property: "a", domainObject: mockDomainObject },
|
||||||
|
{ name: "B", property: "b", domainObject: mockDomainObject },
|
||||||
|
{ name: "C", property: "c", domainObject: mockDomainObject },
|
||||||
|
{ name: "X", property: "x", domainObject: mockDomainObject },
|
||||||
|
{ name: "Y", property: "y", domainObject: mockDomainObject },
|
||||||
|
{ name: "Z", property: "z", domainObject: mockDomainObject },
|
||||||
|
{ name: "M", method: "m", domainObject: mockDomainObject }
|
||||||
|
];
|
||||||
|
|
||||||
|
toolbar = new EditToolbar(mockScope, mockOpenMCT, testStructure);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("adds click functions when a method is specified", function () {
|
||||||
|
var structure = toolbar.getStructure();
|
||||||
|
expect(structure[6].click).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("adds key for controls that define a property", function () {
|
||||||
|
var structure = toolbar.getStructure();
|
||||||
|
expect(structure[0].key).toEqual(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
@ -56,7 +56,7 @@ define([
|
|||||||
};
|
};
|
||||||
|
|
||||||
DurationFormat.prototype.validate = function (text) {
|
DurationFormat.prototype.validate = function (text) {
|
||||||
return moment.utc(text, DATE_FORMATS, true).isValid();
|
return moment.utc(text, DATE_FORMATS).isValid();
|
||||||
};
|
};
|
||||||
|
|
||||||
return DurationFormat;
|
return DurationFormat;
|
||||||
|
@ -29,7 +29,6 @@ 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"
|
||||||
@ -53,14 +52,70 @@ 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 (value !== undefined) {
|
if (arguments.length > 1) {
|
||||||
|
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;
|
||||||
@ -75,7 +130,7 @@ define([
|
|||||||
};
|
};
|
||||||
|
|
||||||
UTCTimeFormat.prototype.validate = function (text) {
|
UTCTimeFormat.prototype.validate = function (text) {
|
||||||
return moment.utc(text, DATE_FORMATS, true).isValid();
|
return moment.utc(text, DATE_FORMATS).isValid();
|
||||||
};
|
};
|
||||||
|
|
||||||
return UTCTimeFormat;
|
return UTCTimeFormat;
|
||||||
|
62
platform/commonUI/formats/test/UTCTimeFormatSpec.js
Normal file
62
platform/commonUI/formats/test/UTCTimeFormatSpec.js
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -31,6 +31,7 @@ define([
|
|||||||
"./src/controllers/TreeNodeController",
|
"./src/controllers/TreeNodeController",
|
||||||
"./src/controllers/ActionGroupController",
|
"./src/controllers/ActionGroupController",
|
||||||
"./src/controllers/ToggleController",
|
"./src/controllers/ToggleController",
|
||||||
|
"./src/controllers/ContextMenuController",
|
||||||
"./src/controllers/ClickAwayController",
|
"./src/controllers/ClickAwayController",
|
||||||
"./src/controllers/ViewSwitcherController",
|
"./src/controllers/ViewSwitcherController",
|
||||||
"./src/controllers/GetterSetterController",
|
"./src/controllers/GetterSetterController",
|
||||||
@ -48,6 +49,8 @@ define([
|
|||||||
"./src/directives/MCTSplitter",
|
"./src/directives/MCTSplitter",
|
||||||
"./src/directives/MCTTree",
|
"./src/directives/MCTTree",
|
||||||
"./src/directives/MCTIndicators",
|
"./src/directives/MCTIndicators",
|
||||||
|
"./src/directives/MCTPreview",
|
||||||
|
"./src/actions/MCTPreviewAction",
|
||||||
"./src/filters/ReverseFilter",
|
"./src/filters/ReverseFilter",
|
||||||
"./res/templates/bottombar.html",
|
"./res/templates/bottombar.html",
|
||||||
"./res/templates/controls/action-button.html",
|
"./res/templates/controls/action-button.html",
|
||||||
@ -62,11 +65,13 @@ define([
|
|||||||
"./res/templates/tree-node.html",
|
"./res/templates/tree-node.html",
|
||||||
"./res/templates/label.html",
|
"./res/templates/label.html",
|
||||||
"./res/templates/controls/action-group.html",
|
"./res/templates/controls/action-group.html",
|
||||||
|
"./res/templates/menu/context-menu.html",
|
||||||
"./res/templates/controls/switcher.html",
|
"./res/templates/controls/switcher.html",
|
||||||
"./res/templates/object-inspector.html",
|
"./res/templates/object-inspector.html",
|
||||||
"./res/templates/controls/selector.html",
|
"./res/templates/controls/selector.html",
|
||||||
"./res/templates/controls/datetime-picker.html",
|
"./res/templates/controls/datetime-picker.html",
|
||||||
"./res/templates/controls/datetime-field.html",
|
"./res/templates/controls/datetime-field.html",
|
||||||
|
"./res/templates/preview.html",
|
||||||
'legacyRegistry'
|
'legacyRegistry'
|
||||||
], function (
|
], function (
|
||||||
UrlService,
|
UrlService,
|
||||||
@ -79,6 +84,7 @@ define([
|
|||||||
TreeNodeController,
|
TreeNodeController,
|
||||||
ActionGroupController,
|
ActionGroupController,
|
||||||
ToggleController,
|
ToggleController,
|
||||||
|
ContextMenuController,
|
||||||
ClickAwayController,
|
ClickAwayController,
|
||||||
ViewSwitcherController,
|
ViewSwitcherController,
|
||||||
GetterSetterController,
|
GetterSetterController,
|
||||||
@ -96,6 +102,8 @@ define([
|
|||||||
MCTSplitter,
|
MCTSplitter,
|
||||||
MCTTree,
|
MCTTree,
|
||||||
MCTIndicators,
|
MCTIndicators,
|
||||||
|
MCTPreview,
|
||||||
|
MCTPreviewAction,
|
||||||
ReverseFilter,
|
ReverseFilter,
|
||||||
bottombarTemplate,
|
bottombarTemplate,
|
||||||
actionButtonTemplate,
|
actionButtonTemplate,
|
||||||
@ -110,11 +118,13 @@ define([
|
|||||||
treeNodeTemplate,
|
treeNodeTemplate,
|
||||||
labelTemplate,
|
labelTemplate,
|
||||||
actionGroupTemplate,
|
actionGroupTemplate,
|
||||||
|
contextMenuTemplate,
|
||||||
switcherTemplate,
|
switcherTemplate,
|
||||||
objectInspectorTemplate,
|
objectInspectorTemplate,
|
||||||
selectorTemplate,
|
selectorTemplate,
|
||||||
datetimePickerTemplate,
|
datetimePickerTemplate,
|
||||||
datetimeFieldTemplate,
|
datetimeFieldTemplate,
|
||||||
|
previewTemplate,
|
||||||
legacyRegistry
|
legacyRegistry
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@ -242,6 +252,13 @@ define([
|
|||||||
"key": "ToggleController",
|
"key": "ToggleController",
|
||||||
"implementation": ToggleController
|
"implementation": ToggleController
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"key": "ContextMenuController",
|
||||||
|
"implementation": ContextMenuController,
|
||||||
|
"depends": [
|
||||||
|
"$scope"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"key": "ClickAwayController",
|
"key": "ClickAwayController",
|
||||||
"implementation": ClickAwayController,
|
"implementation": ClickAwayController,
|
||||||
@ -377,6 +394,31 @@ define([
|
|||||||
"key": "mctIndicators",
|
"key": "mctIndicators",
|
||||||
"implementation": MCTIndicators,
|
"implementation": MCTIndicators,
|
||||||
"depends": ['openmct']
|
"depends": ['openmct']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "mctPreview",
|
||||||
|
"implementation": MCTPreview,
|
||||||
|
"depends": [
|
||||||
|
"$document"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"key": "mct-preview-action",
|
||||||
|
"implementation": MCTPreviewAction,
|
||||||
|
"name": "Preview",
|
||||||
|
"cssClass": "hide-in-t-main-view icon-eye-open",
|
||||||
|
"description": "Preview in large dialog",
|
||||||
|
"category": [
|
||||||
|
"contextual",
|
||||||
|
"view-control"
|
||||||
|
],
|
||||||
|
"depends": [
|
||||||
|
"$compile",
|
||||||
|
"$rootScope"
|
||||||
|
],
|
||||||
|
"priority": "preferred"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"constants": [
|
"constants": [
|
||||||
@ -475,6 +517,13 @@ define([
|
|||||||
"action"
|
"action"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"key": "context-menu",
|
||||||
|
"template": contextMenuTemplate,
|
||||||
|
"uses": [
|
||||||
|
"action"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"key": "switcher",
|
"key": "switcher",
|
||||||
"template": switcherTemplate,
|
"template": switcherTemplate,
|
||||||
@ -485,6 +534,10 @@ define([
|
|||||||
{
|
{
|
||||||
"key": "object-inspector",
|
"key": "object-inspector",
|
||||||
"template": objectInspectorTemplate
|
"template": objectInspectorTemplate
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "mct-preview",
|
||||||
|
"template": previewTemplate
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"controls": [
|
"controls": [
|
||||||
|
@ -2,41 +2,32 @@
|
|||||||
Open MCT, Copyright (c) 2014-2018, United States Government
|
Open MCT, Copyright (c) 2014-2018, United States Government
|
||||||
as represented by the Administrator of the National Aeronautics and Space
|
as represented by the Administrator of the National Aeronautics and Space
|
||||||
Administration. All rights reserved.
|
Administration. All rights reserved.
|
||||||
|
|
||||||
Open MCT is licensed under the Apache License, Version 2.0 (the
|
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.
|
"License"); you may not use this file except in compliance with the License.
|
||||||
You may obtain a copy of the License at
|
You may obtain a copy of the License at
|
||||||
http://www.apache.org/licenses/LICENSE-2.0.
|
http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
Unless required by applicable law or agreed to in writing, software
|
||||||
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
License for the specific language governing permissions and limitations
|
License for the specific language governing permissions and limitations
|
||||||
under the License.
|
under the License.
|
||||||
|
|
||||||
Open MCT includes source code licensed under additional open source
|
Open MCT includes source code licensed under additional open source
|
||||||
licenses. See the Open Source Licenses file (LICENSES.md) included with
|
licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
this source code distribution or the Licensing information page available
|
this source code distribution or the Licensing information page available
|
||||||
at runtime from the About dialog for additional information.
|
at runtime from the About dialog for additional information.
|
||||||
-->
|
-->
|
||||||
<template>
|
<div class="menu-element context-menu-wrapper mobile-disable-select" ng-controller="ContextMenuController">
|
||||||
<span class="c-status">
|
<div class="menu context-menu">
|
||||||
<indicators></indicators>
|
<ul>
|
||||||
<notification-banner></notification-banner>
|
<li ng-repeat="menuAction in menuActions"
|
||||||
</span>
|
ng-click="menuAction.perform()"
|
||||||
</template>
|
title="{{menuAction.getMetadata().description}}"
|
||||||
|
class="{{menuAction.getMetadata().cssClass}}">
|
||||||
<style lang="scss">
|
{{menuAction.getMetadata().name}}
|
||||||
.c-status {
|
</li>
|
||||||
width: 100%;
|
</ul>
|
||||||
}
|
</div>
|
||||||
</style>
|
</div>
|
||||||
|
|
||||||
<script>
|
|
||||||
import Indicators from './Indicators.vue';
|
|
||||||
import NotificationBanner from './NotificationBanner.vue';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
Indicators,
|
|
||||||
NotificationBanner
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
45
platform/commonUI/general/res/templates/preview.html
Normal file
45
platform/commonUI/general/res/templates/preview.html
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<!--
|
||||||
|
Open MCT, Copyright (c) 2014-2017, United States Government
|
||||||
|
as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
Administration. All rights reserved.
|
||||||
|
|
||||||
|
Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
License for the specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
|
||||||
|
Open MCT includes source code licensed under additional open source
|
||||||
|
licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
this source code distribution or the Licensing information page available
|
||||||
|
at runtime from the About dialog for additional information.
|
||||||
|
-->
|
||||||
|
<div class="t-frame-inner abs t-object-type-{{ domainObject.getModel().type }}" mct-preview>
|
||||||
|
<div class="abs object-browse-bar l-flex-row">
|
||||||
|
<div class="left flex-elem l-flex-row grows">
|
||||||
|
<mct-representation
|
||||||
|
key="'object-header-frame'"
|
||||||
|
mct-object="domainObject"
|
||||||
|
class="l-flex-row flex-elem object-header grows">
|
||||||
|
</mct-representation>
|
||||||
|
</div>
|
||||||
|
<div class="btn-bar right l-flex-row flex-elem flex-justify-end flex-fixed">
|
||||||
|
<mct-representation
|
||||||
|
key="'switcher'"
|
||||||
|
ng-model="representation"
|
||||||
|
mct-object="domainObject">
|
||||||
|
</mct-representation>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="abs object-holder">
|
||||||
|
<mct-representation
|
||||||
|
key="representation.selected.key"
|
||||||
|
mct-object="representation.selected.key && domainObject">
|
||||||
|
</mct-representation>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -1,10 +1,10 @@
|
|||||||
<span class="l-progress-bar s-progress-bar"
|
<span class="l-progress-bar s-progress-bar"
|
||||||
ng-class="{ indeterminate:ngModel.progressPerc === 'unknown' }">
|
ng-class="{ indeterminate:ngModel.unknownProgress }">
|
||||||
<span class="progress-amt-holder">
|
<span class="progress-amt-holder">
|
||||||
<span class="progress-amt" style="width: {{ngModel.progressPerc === 'unknown' ? 100 : ngModel.progressPerc}}%"></span>
|
<span class="progress-amt" style="width: {{ngModel.progress}}%"></span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<div class="progress-info hint" ng-hide="ngModel.progressText === undefined">
|
<div class="progress-info hint" ng-hide="ngModel.progressText === undefined">
|
||||||
<span class="progress-amt-text" ng-show="ngModel.progressPerc !== 'unknown' && ngModel.progressPerc > 0">{{ngModel.progressPerc}}% complete. </span>
|
<span class="progress-amt-text" ng-show="ngModel.progress > 0">{{ngModel.progress}}% complete. </span>
|
||||||
{{ngModel.progressText}}
|
{{ngModel.progressText}}
|
||||||
</div>
|
</div>
|
||||||
|
55
platform/commonUI/general/src/actions/MCTPreviewAction.js
Normal file
55
platform/commonUI/general/src/actions/MCTPreviewAction.js
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2017, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
define(
|
||||||
|
[],
|
||||||
|
function () {
|
||||||
|
|
||||||
|
var PREVIEW_TEMPLATE = '<mct-representation key="\'mct-preview\'"' +
|
||||||
|
'class="t-rep-frame holder"' +
|
||||||
|
'mct-object="domainObject">' +
|
||||||
|
'</mct-representation>';
|
||||||
|
|
||||||
|
function MCTPreviewAction($compile, $rootScope, context) {
|
||||||
|
context = context || {};
|
||||||
|
this.domainObject = context.selectedObject || context.domainObject;
|
||||||
|
this.$rootScope = $rootScope;
|
||||||
|
this.$compile = $compile;
|
||||||
|
}
|
||||||
|
|
||||||
|
MCTPreviewAction.prototype.perform = function () {
|
||||||
|
var newScope = this.$rootScope.$new();
|
||||||
|
newScope.domainObject = this.domainObject;
|
||||||
|
|
||||||
|
this.$compile(PREVIEW_TEMPLATE)(newScope);
|
||||||
|
};
|
||||||
|
|
||||||
|
MCTPreviewAction.appliesTo = function (context) {
|
||||||
|
var domainObject = (context || {}).domainObject,
|
||||||
|
status = domainObject.getCapability('status');
|
||||||
|
|
||||||
|
return !(status && status.get('editing'));
|
||||||
|
};
|
||||||
|
|
||||||
|
return MCTPreviewAction;
|
||||||
|
}
|
||||||
|
);
|
@ -50,7 +50,7 @@ define(
|
|||||||
};
|
};
|
||||||
$scope.dismiss = function (notification, $event) {
|
$scope.dismiss = function (notification, $event) {
|
||||||
$event.stopPropagation();
|
$event.stopPropagation();
|
||||||
notification.dismiss();
|
notification.dismissOrMinimize();
|
||||||
};
|
};
|
||||||
$scope.maximize = function (notification) {
|
$scope.maximize = function (notification) {
|
||||||
if (notification.model.severity !== "info") {
|
if (notification.model.severity !== "info") {
|
||||||
@ -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.on('dismiss', function () {
|
notification.onDismiss(function () {
|
||||||
dialog.dismiss();
|
dialog.dismiss();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -20,23 +20,32 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
export default function legacyCompositionPolicyAdapter(openmct) {
|
/**
|
||||||
const instantiate = this.openmct.$injector.get('instantiate');
|
* Module defining ContextMenuController. Created by vwoeltje on 11/17/14.
|
||||||
const policyService = this.openmct.$injector.get('policyService');
|
*/
|
||||||
|
define(
|
||||||
|
[],
|
||||||
|
function () {
|
||||||
|
|
||||||
openmct.composition.addPolicy((parent, child) => {
|
/**
|
||||||
|
* Controller for the context menu. Maintains an up-to-date
|
||||||
|
* list of applicable actions (those from category "contextual")
|
||||||
|
*
|
||||||
|
* @memberof platform/commonUI/general
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function ContextMenuController($scope) {
|
||||||
|
// Refresh variable "menuActions" in the scope
|
||||||
|
function updateActions() {
|
||||||
|
$scope.menuActions = $scope.action ?
|
||||||
|
$scope.action.getActions({ category: 'contextual' }) :
|
||||||
|
[];
|
||||||
|
}
|
||||||
|
|
||||||
let parentId = this.openmct.objects.makeKeyString(parent.identifier);
|
// Update using the action capability
|
||||||
let childId = this.openmct.objects.makeKeyString(child.identifier);
|
$scope.$watch("action", updateActions);
|
||||||
|
}
|
||||||
|
|
||||||
let legacyParent = instantiate(parent, parentId);
|
return ContextMenuController;
|
||||||
let legacyChild = instantiate(child, childId);
|
}
|
||||||
let result = policyService.allow(
|
);
|
||||||
'composition',
|
|
||||||
legacyParent,
|
|
||||||
legacyChild
|
|
||||||
);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
});
|
|
||||||
}
|
|
64
platform/commonUI/general/src/directives/MCTPreview.js
Normal file
64
platform/commonUI/general/src/directives/MCTPreview.js
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2016, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
define(['zepto', '../services/Overlay'], function ($, Overlay) {
|
||||||
|
function MCTPreview($document) {
|
||||||
|
|
||||||
|
function link($scope, $element) {
|
||||||
|
var actions = $scope.domainObject.getCapability('action'),
|
||||||
|
notebookAction = actions.getActions({key: 'notebook-new-entry'})[0];
|
||||||
|
|
||||||
|
var notebookButton = notebookAction ?
|
||||||
|
[
|
||||||
|
{
|
||||||
|
class: 'icon-notebook new-notebook-entry',
|
||||||
|
title: 'New Notebook Entry',
|
||||||
|
clickHandler: function (event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
notebookAction.perform();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
] : [];
|
||||||
|
|
||||||
|
var overlayService = new Overlay({
|
||||||
|
$document: $document,
|
||||||
|
$element: $element[0],
|
||||||
|
$scope: $scope,
|
||||||
|
browseBarButtons: notebookButton
|
||||||
|
});
|
||||||
|
|
||||||
|
overlayService.toggleOverlay();
|
||||||
|
|
||||||
|
$scope.$on('$destroy', function () {
|
||||||
|
$element.remove();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
restrict: 'A',
|
||||||
|
link: link
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return MCTPreview;
|
||||||
|
|
||||||
|
});
|
@ -82,7 +82,7 @@ define(
|
|||||||
}
|
}
|
||||||
var searchPath = "?" + arr.join('&'),
|
var searchPath = "?" + arr.join('&'),
|
||||||
newTabPath =
|
newTabPath =
|
||||||
"#" + this.urlForLocation(mode, domainObject) +
|
"index.html#" + this.urlForLocation(mode, domainObject) +
|
||||||
searchPath;
|
searchPath;
|
||||||
return newTabPath;
|
return newTabPath;
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,60 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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/ContextMenuController"],
|
||||||
|
function (ContextMenuController) {
|
||||||
|
|
||||||
|
describe("The context menu controller", function () {
|
||||||
|
var mockScope,
|
||||||
|
mockActions,
|
||||||
|
controller;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
mockActions = jasmine.createSpyObj("action", ["getActions"]);
|
||||||
|
mockScope = jasmine.createSpyObj("$scope", ["$watch"]);
|
||||||
|
controller = new ContextMenuController(mockScope);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("watches scope that may change applicable actions", function () {
|
||||||
|
// The action capability
|
||||||
|
expect(mockScope.$watch).toHaveBeenCalledWith(
|
||||||
|
"action",
|
||||||
|
jasmine.any(Function)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("populates the scope with grouped and ungrouped actions", function () {
|
||||||
|
mockScope.action = mockActions;
|
||||||
|
mockScope.parameters = { category: "test" };
|
||||||
|
|
||||||
|
mockActions.getActions.and.returnValue(["a", "b", "c"]);
|
||||||
|
|
||||||
|
// Call the watch
|
||||||
|
mockScope.$watch.calls.mostRecent().args[1]();
|
||||||
|
|
||||||
|
// Should have grouped and ungrouped actions in scope now
|
||||||
|
expect(mockScope.menuActions.length).toEqual(3);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
@ -36,6 +36,20 @@ define([
|
|||||||
|
|
||||||
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",
|
||||||
@ -48,7 +62,7 @@ define([
|
|||||||
"implementation": NotificationIndicatorController,
|
"implementation": NotificationIndicatorController,
|
||||||
"depends": [
|
"depends": [
|
||||||
"$scope",
|
"$scope",
|
||||||
"openmct",
|
"notificationService",
|
||||||
"dialogService"
|
"dialogService"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -62,11 +76,12 @@ define([
|
|||||||
"services": [
|
"services": [
|
||||||
{
|
{
|
||||||
"key": "notificationService",
|
"key": "notificationService",
|
||||||
"implementation": function (openmct) {
|
"implementation": NotificationService,
|
||||||
return new NotificationService.default(openmct);
|
|
||||||
},
|
|
||||||
"depends": [
|
"depends": [
|
||||||
"openmct"
|
"$timeout",
|
||||||
|
"topic",
|
||||||
|
"DEFAULT_AUTO_DISMISS",
|
||||||
|
"MINIMIZE_TIMEOUT"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -35,9 +35,9 @@ define(
|
|||||||
* @param dialogService
|
* @param dialogService
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
function NotificationIndicatorController($scope, openmct, dialogService) {
|
function NotificationIndicatorController($scope, notificationService, dialogService) {
|
||||||
$scope.notifications = openmct.notifications.notifications;
|
$scope.notifications = notificationService.notifications;
|
||||||
$scope.highest = openmct.notifications.highest;
|
$scope.highest = notificationService.highest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Launch a dialog showing a list of current notifications.
|
* Launch a dialog showing a list of current notifications.
|
||||||
@ -48,7 +48,7 @@ define(
|
|||||||
title: "Messages",
|
title: "Messages",
|
||||||
//Launch the message list dialog with the models
|
//Launch the message list dialog with the models
|
||||||
// from the notifications
|
// from the notifications
|
||||||
messages: openmct.notifications.notifications
|
messages: notificationService.notifications
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -19,46 +19,419 @@
|
|||||||
* 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.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
export default class NotificationService {
|
|
||||||
constructor(openmct) {
|
/**
|
||||||
this.openmct = openmct;
|
* This bundle implements the notification service, which can be used to
|
||||||
}
|
* show banner notifications to the user. Banner notifications
|
||||||
info(message) {
|
* are used to inform users of events in a non-intrusive way. As
|
||||||
if (typeof message === 'string') {
|
* much as possible, notifications share a model with blocking
|
||||||
return this.openmct.notifications.info(message);
|
* dialogs so that the same information can be provided in a dialog
|
||||||
} else {
|
* and then minimized to a banner notification if needed.
|
||||||
if (message.hasOwnProperty('progress')) {
|
*
|
||||||
return this.openmct.notifications.progress(message.title, message.progress, message.progressText);
|
* @namespace platform/commonUI/notification
|
||||||
} else {
|
*/
|
||||||
return this.openmct.notifications.info(message.title);
|
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;
|
||||||
}
|
}
|
||||||
alert(message) {
|
);
|
||||||
if (typeof message === 'string') {
|
|
||||||
return this.openmct.notifications.alert(message);
|
|
||||||
} else {
|
|
||||||
return this.openmct.notifications.alert(message.title);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
error(message) {
|
|
||||||
if (typeof message === 'string') {
|
|
||||||
return this.openmct.notifications.error(message);
|
|
||||||
} else {
|
|
||||||
return this.openmct.notifications.error(message.title);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
notify(options) {
|
|
||||||
switch (options.severity) {
|
|
||||||
case 'info':
|
|
||||||
return this.info(options);
|
|
||||||
case 'alert':
|
|
||||||
return this.alert(options);
|
|
||||||
case 'error':
|
|
||||||
return this.error(options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
getAllNotifications() {
|
|
||||||
return this.openmct.notifications.notifications;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
275
platform/commonUI/notification/test/NotificationServiceSpec.js
Normal file
275
platform/commonUI/notification/test/NotificationServiceSpec.js
Normal file
@ -0,0 +1,275 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
@ -58,8 +58,7 @@ define([
|
|||||||
"category": "action",
|
"category": "action",
|
||||||
"implementation": ComposeActionPolicy,
|
"implementation": ComposeActionPolicy,
|
||||||
"depends": [
|
"depends": [
|
||||||
"$injector",
|
"$injector"
|
||||||
"openmct"
|
|
||||||
],
|
],
|
||||||
"message": "Objects of this type cannot contain objects of that type."
|
"message": "Objects of this type cannot contain objects of that type."
|
||||||
},
|
},
|
||||||
|
@ -36,11 +36,10 @@ define(
|
|||||||
* @memberof platform/containment
|
* @memberof platform/containment
|
||||||
* @implements {Policy.<Action, ActionContext>}
|
* @implements {Policy.<Action, ActionContext>}
|
||||||
*/
|
*/
|
||||||
function ComposeActionPolicy($injector, openmct) {
|
function ComposeActionPolicy($injector) {
|
||||||
this.getPolicyService = function () {
|
this.getPolicyService = function () {
|
||||||
return $injector.get('policyService');
|
return $injector.get('policyService');
|
||||||
};
|
};
|
||||||
this.openmct = openmct;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ComposeActionPolicy.prototype.allowComposition = function (containerObject, selectedObject) {
|
ComposeActionPolicy.prototype.allowComposition = function (containerObject, selectedObject) {
|
||||||
@ -50,8 +49,11 @@ define(
|
|||||||
|
|
||||||
// ...and delegate to the composition policy
|
// ...and delegate to the composition policy
|
||||||
return containerObject.getId() !== selectedObject.getId() &&
|
return containerObject.getId() !== selectedObject.getId() &&
|
||||||
this.openmct.composition.checkPolicy(containerObject.useCapability('adapter'),
|
this.policyService.allow(
|
||||||
selectedObject.useCapability('adapter'));
|
'composition',
|
||||||
|
containerObject,
|
||||||
|
selectedObject
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -170,7 +170,7 @@ define([
|
|||||||
"description": "Provides a service for moving objects",
|
"description": "Provides a service for moving objects",
|
||||||
"implementation": MoveService,
|
"implementation": MoveService,
|
||||||
"depends": [
|
"depends": [
|
||||||
"openmct",
|
"policyService",
|
||||||
"linkService",
|
"linkService",
|
||||||
"$q"
|
"$q"
|
||||||
]
|
]
|
||||||
@ -181,7 +181,7 @@ define([
|
|||||||
"description": "Provides a service for linking objects",
|
"description": "Provides a service for linking objects",
|
||||||
"implementation": LinkService,
|
"implementation": LinkService,
|
||||||
"depends": [
|
"depends": [
|
||||||
"openmct"
|
"policyService"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -192,7 +192,7 @@ define([
|
|||||||
"depends": [
|
"depends": [
|
||||||
"$q",
|
"$q",
|
||||||
"policyService",
|
"policyService",
|
||||||
"openmct"
|
"now"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -33,10 +33,9 @@ define(
|
|||||||
* @memberof platform/entanglement
|
* @memberof platform/entanglement
|
||||||
* @implements {platform/entanglement.AbstractComposeService}
|
* @implements {platform/entanglement.AbstractComposeService}
|
||||||
*/
|
*/
|
||||||
function CopyService($q, policyService, openmct) {
|
function CopyService($q, policyService) {
|
||||||
this.$q = $q;
|
this.$q = $q;
|
||||||
this.policyService = policyService;
|
this.policyService = policyService;
|
||||||
this.openmct = openmct;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CopyService.prototype.validate = function (object, parentCandidate) {
|
CopyService.prototype.validate = function (object, parentCandidate) {
|
||||||
@ -46,7 +45,11 @@ define(
|
|||||||
if (parentCandidate.getId() === object.getId()) {
|
if (parentCandidate.getId() === object.getId()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return this.openmct.composition.checkPolicy(parentCandidate.useCapability('adapter'), object.useCapability('adapter'));
|
return this.policyService.allow(
|
||||||
|
"composition",
|
||||||
|
parentCandidate,
|
||||||
|
object
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -32,8 +32,8 @@ define(
|
|||||||
* @memberof platform/entanglement
|
* @memberof platform/entanglement
|
||||||
* @implements {platform/entanglement.AbstractComposeService}
|
* @implements {platform/entanglement.AbstractComposeService}
|
||||||
*/
|
*/
|
||||||
function LinkService(openmct) {
|
function LinkService(policyService) {
|
||||||
this.openmct = openmct;
|
this.policyService = policyService;
|
||||||
}
|
}
|
||||||
|
|
||||||
LinkService.prototype.validate = function (object, parentCandidate) {
|
LinkService.prototype.validate = function (object, parentCandidate) {
|
||||||
@ -49,7 +49,11 @@ define(
|
|||||||
if (parentCandidate.getModel().composition.indexOf(object.getId()) !== -1) {
|
if (parentCandidate.getModel().composition.indexOf(object.getId()) !== -1) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return this.openmct.composition.checkPolicy(parentCandidate.useCapability('adapter'), object.useCapability('adapter'));
|
return this.policyService.allow(
|
||||||
|
"composition",
|
||||||
|
parentCandidate,
|
||||||
|
object
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
LinkService.prototype.perform = function (object, parentObject) {
|
LinkService.prototype.perform = function (object, parentObject) {
|
||||||
|
@ -31,8 +31,8 @@ define(
|
|||||||
* @memberof platform/entanglement
|
* @memberof platform/entanglement
|
||||||
* @implements {platform/entanglement.AbstractComposeService}
|
* @implements {platform/entanglement.AbstractComposeService}
|
||||||
*/
|
*/
|
||||||
function MoveService(openmct, linkService) {
|
function MoveService(policyService, linkService) {
|
||||||
this.openmct = openmct;
|
this.policyService = policyService;
|
||||||
this.linkService = linkService;
|
this.linkService = linkService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,9 +53,10 @@ define(
|
|||||||
if (parentCandidate.getModel().composition.indexOf(object.getId()) !== -1) {
|
if (parentCandidate.getModel().composition.indexOf(object.getId()) !== -1) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return this.openmct.composition.checkPolicy(
|
return this.policyService.allow(
|
||||||
parentCandidate.useCapability('adapter'),
|
"composition",
|
||||||
object.useCapability('adapter')
|
parentCandidate,
|
||||||
|
object
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ define([
|
|||||||
{
|
{
|
||||||
key: "exportService",
|
key: "exportService",
|
||||||
implementation: function () {
|
implementation: function () {
|
||||||
return new ExportService(saveAs.saveAs);
|
return new ExportService(saveAs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -19,14 +19,16 @@
|
|||||||
this source code distribution or the Licensing information page available
|
this source code distribution or the Licensing information page available
|
||||||
at runtime from the About dialog for additional information.
|
at runtime from the About dialog for additional information.
|
||||||
-->
|
-->
|
||||||
<div class="c-clock l-time-display" ng-controller="ClockController as clock">
|
<div class="l-time-display l-digital l-clock s-clock" ng-controller="ClockController as clock">
|
||||||
<div class="c-clock__timezone">
|
<div class="l-elem-wrapper">
|
||||||
{{clock.zone()}}
|
<span class="l-elem timezone">
|
||||||
</div>
|
{{clock.zone()}}
|
||||||
<div class="c-clock__value">
|
</span>
|
||||||
{{clock.text()}}
|
<span class="l-elem value active">
|
||||||
</div>
|
{{clock.text()}}
|
||||||
<div class="c-clock__ampm">
|
</span>
|
||||||
{{clock.ampm()}}
|
<span class="l-elem ampm">
|
||||||
|
{{clock.ampm()}}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -19,19 +19,21 @@
|
|||||||
this source code distribution or the Licensing information page available
|
this source code distribution or the Licensing information page available
|
||||||
at runtime from the About dialog for additional information.
|
at runtime from the About dialog for additional information.
|
||||||
-->
|
-->
|
||||||
<div class="c-timer is-{{timer.timerState}}" ng-controller="TimerController as timer">
|
<div class="l-time-display l-digital l-timer s-timer s-state-{{timer.timerState}}" ng-controller="TimerController as timer">
|
||||||
<div class="c-timer__controls">
|
<div class="l-elem-wrapper l-flex-row">
|
||||||
<button ng-click="timer.clickStopButton()"
|
<div class="l-elem-wrapper l-flex-row controls">
|
||||||
ng-hide="timer.timerState == 'stopped'"
|
<a ng-click="timer.clickStopButton()"
|
||||||
title="Reset"
|
title="Stop"
|
||||||
class="c-timer__ctrl-reset c-click-icon c-click-icon--major icon-reset"></button>
|
class="flex-elem s-icon-button t-btn-stop icon-box"></a>
|
||||||
<button ng-click="timer.clickButton()"
|
<a ng-click="timer.clickButton()"
|
||||||
title="{{timer.buttonText()}}"
|
title="{{timer.buttonText()}}"
|
||||||
class="c-timer__ctrl-pause-play c-click-icon c-click-icon--major {{timer.buttonCssClass()}}"></button>
|
class="flex-elem s-icon-button t-btn-pauseplay {{timer.buttonCssClass()}}"></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="c-timer__direction {{timer.signClass()}}"
|
<span class="flex-elem l-value {{timer.signClass()}}">
|
||||||
ng-hide="!timer.signClass()"></div>
|
<span class="value"
|
||||||
<div class="c-timer__value">{{timer.text() || "--:--:--"}}
|
ng-class="{ active:timer.text() }">{{timer.text() || "--:--:--"}}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span ng-controller="RefreshingController"></span>
|
||||||
</div>
|
</div>
|
||||||
<span class="c-timer__ng-controller u-contents" ng-controller="RefreshingController"></span>
|
|
||||||
</div>
|
</div>
|
||||||
|
43
platform/features/conductor/compatibility/bundle.js
Normal file
43
platform/features/conductor/compatibility/bundle.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,95 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
148
platform/features/conductor/core/bundle.js
Normal file
148
platform/features/conductor/core/bundle.js
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,46 @@
|
|||||||
|
<!--
|
||||||
|
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>
|
@ -0,0 +1,33 @@
|
|||||||
|
<!--
|
||||||
|
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>
|
@ -0,0 +1,117 @@
|
|||||||
|
<!-- 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>
|
@ -0,0 +1,12 @@
|
|||||||
|
<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>
|
@ -0,0 +1,236 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
);
|
@ -0,0 +1,169 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,56 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT Web includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
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;
|
||||||
|
});
|
@ -0,0 +1,123 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
);
|
@ -0,0 +1,153 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,63 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT Web includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
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;
|
||||||
|
});
|
54
platform/features/conductor/core/src/ui/NumberFormat.js
Normal file
54
platform/features/conductor/core/src/ui/NumberFormat.js
Normal file
@ -0,0 +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.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
define([], function () {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formatter for basic numbers. Provides basic support for non-UTC
|
||||||
|
* numbering systems
|
||||||
|
*
|
||||||
|
* @implements {Format}
|
||||||
|
* @constructor
|
||||||
|
* @memberof platform/commonUI/formats
|
||||||
|
*/
|
||||||
|
function NumberFormat() {
|
||||||
|
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;
|
||||||
|
});
|
49
platform/features/conductor/core/src/ui/NumberFormatSpec.js
Normal file
49
platform/features/conductor/core/src/ui/NumberFormatSpec.js
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -19,10 +19,35 @@
|
|||||||
* 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.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
import PreviewAction from './PreviewAction.js';
|
|
||||||
|
|
||||||
export default function () {
|
define([], function () {
|
||||||
return function (openmct) {
|
|
||||||
openmct.contextMenu.registerAction(new PreviewAction(openmct));
|
/**
|
||||||
|
* Formatter for basic strings.
|
||||||
|
*
|
||||||
|
* @implements {Format}
|
||||||
|
* @constructor
|
||||||
|
* @memberof platform/commonUI/formats
|
||||||
|
*/
|
||||||
|
function StringFormat() {
|
||||||
|
this.key = 'string';
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
StringFormat.prototype.format = function (string) {
|
||||||
|
if (typeof string === 'string') {
|
||||||
|
return string;
|
||||||
|
} else {
|
||||||
|
return '' + string;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
StringFormat.prototype.parse = function (string) {
|
||||||
|
return string;
|
||||||
|
};
|
||||||
|
|
||||||
|
StringFormat.prototype.validate = function (string) {
|
||||||
|
return typeof string === 'string';
|
||||||
|
};
|
||||||
|
|
||||||
|
return StringFormat;
|
||||||
|
});
|
@ -0,0 +1,554 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
);
|
@ -0,0 +1,513 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,69 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
);
|
@ -0,0 +1,73 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,97 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
);
|
@ -0,0 +1,109 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
);
|
399
platform/features/fixed/bundle.js
Normal file
399
platform/features/fixed/bundle.js
Normal file
@ -0,0 +1,399 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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([
|
||||||
|
"../layout/res/templates/fixed.html",
|
||||||
|
'legacyRegistry'
|
||||||
|
], function (
|
||||||
|
fixedTemplate,
|
||||||
|
legacyRegistry
|
||||||
|
) {
|
||||||
|
|
||||||
|
legacyRegistry.register("platform/features/fixed", {
|
||||||
|
"name": "Fixed position components.",
|
||||||
|
"description": "Plug in adding Fixed Position object type.",
|
||||||
|
"extensions": {
|
||||||
|
"views": [
|
||||||
|
{
|
||||||
|
"key": "fixed-display",
|
||||||
|
"name": "Fixed Position Display",
|
||||||
|
"cssClass": "icon-box-with-dashed-lines",
|
||||||
|
"type": "telemetry.fixed",
|
||||||
|
"template": fixedTemplate,
|
||||||
|
"uses": [],
|
||||||
|
"editable": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"toolbars": [
|
||||||
|
{
|
||||||
|
name: "Fixed Position Toolbar",
|
||||||
|
key: "fixed.position",
|
||||||
|
description: "Toolbar for the selected element inside a fixed position display.",
|
||||||
|
forSelection: function (selection) {
|
||||||
|
if (!selection) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
selection[0] && selection[0].context.elementProxy &&
|
||||||
|
selection[1] && selection[1].context.item.type === 'telemetry.fixed' ||
|
||||||
|
selection[0] && selection[0].context.item.type === 'telemetry.fixed'
|
||||||
|
);
|
||||||
|
},
|
||||||
|
toolbar: function (selection) {
|
||||||
|
var imageProperties = ["add", "remove", "order", "stroke", "useGrid", "x", "y", "height", "width", "url"];
|
||||||
|
var boxProperties = ["add", "remove", "order", "stroke", "useGrid", "x", "y", "height", "width", "fill"];
|
||||||
|
var textProperties = ["add", "remove", "order", "stroke", "useGrid", "x", "y", "height", "width", "fill", "color", "size", "text"];
|
||||||
|
var lineProperties = ["add", "remove", "order", "stroke", "useGrid", "x", "y", "x2", "y2"];
|
||||||
|
var telemetryProperties = ["add", "remove", "order", "stroke", "useGrid", "x", "y", "height", "width", "fill", "color", "size", "titled"];
|
||||||
|
var fixedPageProperties = ["add"];
|
||||||
|
|
||||||
|
var properties = [],
|
||||||
|
fixedItem = selection[0] && selection[0].context.item,
|
||||||
|
elementProxy = selection[0] && selection[0].context.elementProxy,
|
||||||
|
domainObject = selection[1] && selection[1].context.item,
|
||||||
|
path;
|
||||||
|
|
||||||
|
if (elementProxy) {
|
||||||
|
var type = elementProxy.element.type;
|
||||||
|
path = "configuration['fixed-display'].elements[" + elementProxy.index + "]";
|
||||||
|
properties =
|
||||||
|
type === 'fixed.image' ? imageProperties :
|
||||||
|
type === 'fixed.text' ? textProperties :
|
||||||
|
type === 'fixed.box' ? boxProperties :
|
||||||
|
type === 'fixed.line' ? lineProperties :
|
||||||
|
type === 'fixed.telemetry' ? telemetryProperties : [];
|
||||||
|
} else if (fixedItem) {
|
||||||
|
properties = domainObject && domainObject.type === 'layout' ? [] : fixedPageProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
control: "menu-button",
|
||||||
|
domainObject: domainObject || selection[0].context.item,
|
||||||
|
method: function (value) {
|
||||||
|
selection[0].context.fixedController.add(value);
|
||||||
|
},
|
||||||
|
key: "add",
|
||||||
|
cssClass: "icon-plus",
|
||||||
|
text: "Add",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
"name": "Box",
|
||||||
|
"cssClass": "icon-box",
|
||||||
|
"key": "fixed.box"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Line",
|
||||||
|
"cssClass": "icon-line-horz",
|
||||||
|
"key": "fixed.line"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Text",
|
||||||
|
"cssClass": "icon-T",
|
||||||
|
"key": "fixed.text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Image",
|
||||||
|
"cssClass": "icon-image",
|
||||||
|
"key": "fixed.image"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: "menu-button",
|
||||||
|
domainObject: domainObject,
|
||||||
|
method: function (value) {
|
||||||
|
selection[0].context.fixedController.order(
|
||||||
|
selection[0].context.elementProxy,
|
||||||
|
value
|
||||||
|
);
|
||||||
|
},
|
||||||
|
key: "order",
|
||||||
|
cssClass: "icon-layers",
|
||||||
|
title: "Layering",
|
||||||
|
description: "Move the selected object above or below other objects",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
"name": "Move to Top",
|
||||||
|
"cssClass": "icon-arrow-double-up",
|
||||||
|
"key": "top"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Move Up",
|
||||||
|
"cssClass": "icon-arrow-up",
|
||||||
|
"key": "up"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Move Down",
|
||||||
|
"cssClass": "icon-arrow-down",
|
||||||
|
"key": "down"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Move to Bottom",
|
||||||
|
"cssClass": "icon-arrow-double-down",
|
||||||
|
"key": "bottom"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: "color",
|
||||||
|
domainObject: domainObject,
|
||||||
|
property: path + ".fill",
|
||||||
|
cssClass: "icon-paint-bucket",
|
||||||
|
title: "Fill color",
|
||||||
|
description: "Set fill color",
|
||||||
|
key: 'fill'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: "color",
|
||||||
|
domainObject: domainObject,
|
||||||
|
property: path + ".stroke",
|
||||||
|
cssClass: "icon-line-horz",
|
||||||
|
title: "Border color",
|
||||||
|
description: "Set border color",
|
||||||
|
key: 'stroke'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: "dialog-button",
|
||||||
|
domainObject: domainObject,
|
||||||
|
property: path + ".url",
|
||||||
|
cssClass: "icon-image",
|
||||||
|
title: "Image Properties",
|
||||||
|
description: "Edit image properties",
|
||||||
|
key: 'url',
|
||||||
|
dialog: {
|
||||||
|
control: "textfield",
|
||||||
|
name: "Image URL",
|
||||||
|
cssClass: "l-input-lg",
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: "color",
|
||||||
|
domainObject: domainObject,
|
||||||
|
property: path + ".color",
|
||||||
|
cssClass: "icon-T",
|
||||||
|
title: "Text color",
|
||||||
|
mandatory: true,
|
||||||
|
description: "Set text color",
|
||||||
|
key: 'color'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: "select",
|
||||||
|
domainObject: domainObject,
|
||||||
|
property: path + ".size",
|
||||||
|
title: "Text size",
|
||||||
|
description: "Set text size",
|
||||||
|
"options": [9, 10, 11, 12, 13, 14, 15, 16, 20, 24, 30, 36, 48, 72, 96].map(function (size) {
|
||||||
|
return { "name": size + " px", "value": size + "px" };
|
||||||
|
}),
|
||||||
|
key: 'size'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: "numberfield",
|
||||||
|
domainObject: domainObject,
|
||||||
|
property: path + ".x",
|
||||||
|
text: "X",
|
||||||
|
name: "X",
|
||||||
|
key: "x",
|
||||||
|
cssClass: "l-input-sm",
|
||||||
|
min: "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: "numberfield",
|
||||||
|
domainObject: domainObject,
|
||||||
|
property: path + ".y",
|
||||||
|
text: "Y",
|
||||||
|
name: "Y",
|
||||||
|
key: "y",
|
||||||
|
cssClass: "l-input-sm",
|
||||||
|
min: "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: "numberfield",
|
||||||
|
domainObject: domainObject,
|
||||||
|
property: path + ".x",
|
||||||
|
text: "X1",
|
||||||
|
name: "X1",
|
||||||
|
key: "x1",
|
||||||
|
cssClass: "l-input-sm",
|
||||||
|
min: "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: "numberfield",
|
||||||
|
domainObject: domainObject,
|
||||||
|
property: path + ".y",
|
||||||
|
text: "Y1",
|
||||||
|
name: "Y1",
|
||||||
|
key: "y1",
|
||||||
|
cssClass: "l-input-sm",
|
||||||
|
min: "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: "numberfield",
|
||||||
|
domainObject: domainObject,
|
||||||
|
property: path + ".x2",
|
||||||
|
text: "X2",
|
||||||
|
name: "X2",
|
||||||
|
key: "x2",
|
||||||
|
cssClass: "l-input-sm",
|
||||||
|
min: "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: "numberfield",
|
||||||
|
domainObject: domainObject,
|
||||||
|
property: path + ".y2",
|
||||||
|
text: "Y2",
|
||||||
|
name: "Y2",
|
||||||
|
key: "y2",
|
||||||
|
cssClass: "l-input-sm",
|
||||||
|
min: "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: "numberfield",
|
||||||
|
domainObject: domainObject,
|
||||||
|
property: path + ".height",
|
||||||
|
text: "H",
|
||||||
|
name: "H",
|
||||||
|
key: "height",
|
||||||
|
cssClass: "l-input-sm",
|
||||||
|
description: "Resize object height",
|
||||||
|
min: "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: "numberfield",
|
||||||
|
domainObject: domainObject,
|
||||||
|
property: path + ".width",
|
||||||
|
text: "W",
|
||||||
|
name: "W",
|
||||||
|
key: "width",
|
||||||
|
cssClass: "l-input-sm",
|
||||||
|
description: "Resize object width",
|
||||||
|
min: "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: "checkbox",
|
||||||
|
domainObject: domainObject,
|
||||||
|
property: path + ".useGrid",
|
||||||
|
name: "Snap to Grid",
|
||||||
|
key: "useGrid"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: "dialog-button",
|
||||||
|
domainObject: domainObject,
|
||||||
|
property: path + ".text",
|
||||||
|
cssClass: "icon-gear",
|
||||||
|
title: "Text Properties",
|
||||||
|
description: "Edit text properties",
|
||||||
|
key: "text",
|
||||||
|
dialog: {
|
||||||
|
control: "textfield",
|
||||||
|
name: "Text",
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: "checkbox",
|
||||||
|
domainObject: domainObject,
|
||||||
|
property: path + ".titled",
|
||||||
|
name: "Show Title",
|
||||||
|
key: "titled"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: "button",
|
||||||
|
domainObject: domainObject,
|
||||||
|
method: function () {
|
||||||
|
selection[0].context.fixedController.remove(
|
||||||
|
selection[0].context.elementProxy
|
||||||
|
);
|
||||||
|
},
|
||||||
|
key: "remove",
|
||||||
|
cssClass: "icon-trash"
|
||||||
|
}
|
||||||
|
].filter(function (item) {
|
||||||
|
var filtered;
|
||||||
|
|
||||||
|
properties.forEach(function (property) {
|
||||||
|
if (item.property && item.key === property ||
|
||||||
|
item.method && item.key === property) {
|
||||||
|
filtered = item;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return filtered;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"types": [
|
||||||
|
{
|
||||||
|
"key": "telemetry.fixed",
|
||||||
|
"name": "Fixed Position Display",
|
||||||
|
"cssClass": "icon-box-with-dashed-lines",
|
||||||
|
"description": "Collect and display telemetry elements in " +
|
||||||
|
"alphanumeric format in a simple canvas workspace. " +
|
||||||
|
"Elements can be positioned and sized. " +
|
||||||
|
"Lines, boxes and images can be added as well.",
|
||||||
|
"priority": 899,
|
||||||
|
"delegates": [
|
||||||
|
"telemetry"
|
||||||
|
],
|
||||||
|
"features": "creation",
|
||||||
|
"contains": [
|
||||||
|
{
|
||||||
|
"has": "telemetry"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"model": {
|
||||||
|
"layoutGrid": [64, 16],
|
||||||
|
"composition": []
|
||||||
|
},
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"name": "Layout Grid",
|
||||||
|
"control": "composite",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"name": "Horizontal grid (px)",
|
||||||
|
"control": "textfield",
|
||||||
|
"cssClass": "l-input-sm l-numeric"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Vertical grid (px)",
|
||||||
|
"control": "textfield",
|
||||||
|
"cssClass": "l-input-sm l-numeric"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pattern": "^(\\d*[1-9]\\d*)?$",
|
||||||
|
"property": "layoutGrid",
|
||||||
|
"conversion": "number[]"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"views": [
|
||||||
|
"fixed-display"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
@ -1,475 +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/FixedController",
|
|
||||||
"./templates/fixed.html",
|
|
||||||
"./templates/frame.html",
|
|
||||||
"./templates/elements/telemetry.html",
|
|
||||||
"./templates/elements/box.html",
|
|
||||||
"./templates/elements/line.html",
|
|
||||||
"./templates/elements/text.html",
|
|
||||||
"./templates/elements/image.html",
|
|
||||||
"legacyRegistry"
|
|
||||||
], function (
|
|
||||||
FixedController,
|
|
||||||
fixedTemplate,
|
|
||||||
frameTemplate,
|
|
||||||
telemetryTemplate,
|
|
||||||
boxTemplate,
|
|
||||||
lineTemplate,
|
|
||||||
textTemplate,
|
|
||||||
imageTemplate,
|
|
||||||
legacyRegistry
|
|
||||||
) {
|
|
||||||
return function() {
|
|
||||||
return function (openmct) {
|
|
||||||
openmct.legacyRegistry.register("platform/features/fixed", {
|
|
||||||
"name": "Fixed position components.",
|
|
||||||
"description": "Plug in adding Fixed Position object type.",
|
|
||||||
"extensions": {
|
|
||||||
"views": [
|
|
||||||
{
|
|
||||||
"key": "fixed-display",
|
|
||||||
"name": "Fixed Position Display",
|
|
||||||
"cssClass": "icon-box-with-dashed-lines",
|
|
||||||
"type": "telemetry.fixed",
|
|
||||||
"template": fixedTemplate,
|
|
||||||
"uses": ["composition"],
|
|
||||||
"editable": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"templates": [
|
|
||||||
{
|
|
||||||
"key": "fixed.telemetry",
|
|
||||||
"template": telemetryTemplate
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "fixed.box",
|
|
||||||
"template": boxTemplate
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "fixed.line",
|
|
||||||
"template": lineTemplate
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "fixed.text",
|
|
||||||
"template": textTemplate
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "fixed.image",
|
|
||||||
"template": imageTemplate
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"controllers": [
|
|
||||||
{
|
|
||||||
"key": "FixedController",
|
|
||||||
"implementation": FixedController,
|
|
||||||
"depends": [
|
|
||||||
"$scope",
|
|
||||||
"$q",
|
|
||||||
"dialogService",
|
|
||||||
"openmct",
|
|
||||||
"$element"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"toolbars": [
|
|
||||||
{
|
|
||||||
name: "Fixed Position Toolbar",
|
|
||||||
key: "fixed.position",
|
|
||||||
description: "Toolbar for the selected element inside a fixed position display.",
|
|
||||||
forSelection: function (selection) {
|
|
||||||
if (!selection) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (openmct.editor.isEditing() &&
|
|
||||||
selection[0] && selection[0].context.elementProxy &&
|
|
||||||
((selection[1] && selection[1].context.item.type === 'telemetry.fixed') ||
|
|
||||||
(selection[0] && selection[0].context.item && selection[0].context.item.type === 'telemetry.fixed')));
|
|
||||||
},
|
|
||||||
toolbar: function (selection) {
|
|
||||||
var imageProperties = ["add", "remove", "order", "stroke", "useGrid", "x", "y", "height", "width", "url"];
|
|
||||||
var boxProperties = ["add", "remove", "order", "stroke", "useGrid", "x", "y", "height", "width", "fill"];
|
|
||||||
var textProperties = ["add", "remove", "order", "stroke", "useGrid", "x", "y", "height", "width", "fill", "color", "size", "text"];
|
|
||||||
var lineProperties = ["add", "remove", "order", "stroke", "useGrid", "x", "y", "x2", "y2"];
|
|
||||||
var telemetryProperties = ["add", "remove", "order", "stroke", "useGrid", "x", "y", "height", "width", "fill", "color", "size", "titled"];
|
|
||||||
var fixedPageProperties = ["add"];
|
|
||||||
|
|
||||||
var properties = [],
|
|
||||||
fixedItem = selection[0] && selection[0].context.item,
|
|
||||||
elementProxy = selection[0] && selection[0].context.elementProxy,
|
|
||||||
domainObject = selection[1] && selection[1].context.item,
|
|
||||||
path;
|
|
||||||
|
|
||||||
if (elementProxy) {
|
|
||||||
var type = elementProxy.element.type;
|
|
||||||
path = "configuration['fixed-display'].elements[" + elementProxy.index + "]";
|
|
||||||
properties =
|
|
||||||
type === 'fixed.image' ? imageProperties :
|
|
||||||
type === 'fixed.text' ? textProperties :
|
|
||||||
type === 'fixed.box' ? boxProperties :
|
|
||||||
type === 'fixed.line' ? lineProperties :
|
|
||||||
type === 'fixed.telemetry' ? telemetryProperties : [];
|
|
||||||
} else if (fixedItem) {
|
|
||||||
properties = domainObject && domainObject.type === 'layout' ? [] : fixedPageProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
control: "menu",
|
|
||||||
domainObject: domainObject || selection[0].context.item,
|
|
||||||
method: function (option) {
|
|
||||||
selection[0].context.fixedController.add(option.key);
|
|
||||||
},
|
|
||||||
key: "add",
|
|
||||||
icon: "icon-plus",
|
|
||||||
label: "Add",
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
"name": "Box",
|
|
||||||
"class": "icon-box",
|
|
||||||
"key": "fixed.box"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Line",
|
|
||||||
"class": "icon-line-horz",
|
|
||||||
"key": "fixed.line"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Text",
|
|
||||||
"class": "icon-T",
|
|
||||||
"key": "fixed.text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Image",
|
|
||||||
"class": "icon-image",
|
|
||||||
"key": "fixed.image"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
control: "menu",
|
|
||||||
domainObject: domainObject,
|
|
||||||
method: function (option) {
|
|
||||||
console.log('option', option)
|
|
||||||
selection[0].context.fixedController.order(
|
|
||||||
selection[0].context.elementProxy,
|
|
||||||
option.key
|
|
||||||
);
|
|
||||||
},
|
|
||||||
key: "order",
|
|
||||||
icon: "icon-layers",
|
|
||||||
title: "Move the selected object above or below other objects",
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
"name": "Move to Top",
|
|
||||||
"class": "icon-arrow-double-up",
|
|
||||||
"key": "top"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Move Up",
|
|
||||||
"class": "icon-arrow-up",
|
|
||||||
"key": "up"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Move Down",
|
|
||||||
"class": "icon-arrow-down",
|
|
||||||
"key": "down"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Move to Bottom",
|
|
||||||
"class": "icon-arrow-double-down",
|
|
||||||
"key": "bottom"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
control: "color-picker",
|
|
||||||
domainObject: domainObject,
|
|
||||||
property: path + ".fill",
|
|
||||||
icon: "icon-paint-bucket",
|
|
||||||
title: "Set fill color",
|
|
||||||
key: 'fill'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
control: "color-picker",
|
|
||||||
domainObject: domainObject,
|
|
||||||
property: path + ".stroke",
|
|
||||||
icon: "icon-line-horz",
|
|
||||||
title: "Set border color",
|
|
||||||
key: 'stroke'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
control: "button",
|
|
||||||
domainObject: domainObject,
|
|
||||||
property: path + ".url",
|
|
||||||
icon: "icon-image",
|
|
||||||
title: "Edit image properties",
|
|
||||||
key: 'url',
|
|
||||||
dialog: {
|
|
||||||
control: "input",
|
|
||||||
type: "text",
|
|
||||||
name: "Image URL",
|
|
||||||
class: "l-input-lg",
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
control: "color-picker",
|
|
||||||
domainObject: domainObject,
|
|
||||||
property: path + ".color",
|
|
||||||
icon: "icon-T",
|
|
||||||
mandatory: true,
|
|
||||||
title: "Set text color",
|
|
||||||
key: 'color'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
control: "select-menu",
|
|
||||||
domainObject: domainObject,
|
|
||||||
property: path + ".size",
|
|
||||||
title: "Set text size",
|
|
||||||
key: 'size',
|
|
||||||
options: [9, 10, 11, 12, 13, 14, 15, 16, 20, 24, 30, 36, 48, 72, 96].map(function (size) {
|
|
||||||
return { "value": size + "px"};
|
|
||||||
})
|
|
||||||
},
|
|
||||||
{
|
|
||||||
control: "input",
|
|
||||||
type: "number",
|
|
||||||
domainObject: domainObject,
|
|
||||||
property: path + ".x",
|
|
||||||
label: "X",
|
|
||||||
title: "X position",
|
|
||||||
key: "x",
|
|
||||||
class: "l-input-sm",
|
|
||||||
min: "0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
control: "input",
|
|
||||||
type: "number",
|
|
||||||
domainObject: domainObject,
|
|
||||||
property: path + ".y",
|
|
||||||
label: "Y",
|
|
||||||
title: "Y position",
|
|
||||||
key: "y",
|
|
||||||
class: "l-input-sm",
|
|
||||||
min: "0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
control: "input",
|
|
||||||
type: "number",
|
|
||||||
domainObject: domainObject,
|
|
||||||
property: path + ".x",
|
|
||||||
label: "X1",
|
|
||||||
title: "X1 position",
|
|
||||||
key: "x1",
|
|
||||||
class: "l-input-sm",
|
|
||||||
min: "0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
control: "input",
|
|
||||||
type: "number",
|
|
||||||
domainObject: domainObject,
|
|
||||||
property: path + ".y",
|
|
||||||
label: "Y1",
|
|
||||||
title: "Y1 position",
|
|
||||||
key: "y1",
|
|
||||||
class: "l-input-sm",
|
|
||||||
min: "0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
control: "input",
|
|
||||||
type: "number",
|
|
||||||
domainObject: domainObject,
|
|
||||||
property: path + ".x2",
|
|
||||||
label: "X2",
|
|
||||||
title: "X2 position",
|
|
||||||
key: "x2",
|
|
||||||
class: "l-input-sm",
|
|
||||||
min: "0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
control: "input",
|
|
||||||
type: "number",
|
|
||||||
domainObject: domainObject,
|
|
||||||
property: path + ".y2",
|
|
||||||
label: "Y2",
|
|
||||||
title: "Y2 position",
|
|
||||||
key: "y2",
|
|
||||||
class: "l-input-sm",
|
|
||||||
min: "0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
control: "input",
|
|
||||||
type: "number",
|
|
||||||
domainObject: domainObject,
|
|
||||||
property: path + ".height",
|
|
||||||
label: "H",
|
|
||||||
title: "Resize object height",
|
|
||||||
key: "height",
|
|
||||||
class: "l-input-sm",
|
|
||||||
min: "1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
control: "input",
|
|
||||||
type: "number",
|
|
||||||
domainObject: domainObject,
|
|
||||||
property: path + ".width",
|
|
||||||
label: "W",
|
|
||||||
title: "Resize object width",
|
|
||||||
key: "width",
|
|
||||||
class: "l-input-sm",
|
|
||||||
min: "1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
control: "toggle-button",
|
|
||||||
domainObject: domainObject,
|
|
||||||
property: path + ".useGrid",
|
|
||||||
key: "useGrid",
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
value: true,
|
|
||||||
icon: 'icon-grid-snap-to',
|
|
||||||
title: 'Snap to grid'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: false,
|
|
||||||
icon: 'icon-grid-snap-no',
|
|
||||||
title: "Do not snap to grid"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
control: "button",
|
|
||||||
domainObject: domainObject,
|
|
||||||
property: path + ".text",
|
|
||||||
icon: "icon-gear",
|
|
||||||
title: "Edit text properties",
|
|
||||||
key: "text",
|
|
||||||
dialog: {
|
|
||||||
control: "input",
|
|
||||||
type: "text",
|
|
||||||
name: "Text",
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
control: "toggle-button",
|
|
||||||
domainObject: domainObject,
|
|
||||||
property: path + ".titled",
|
|
||||||
key: "titled",
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
value: true,
|
|
||||||
icon: 'icon-two-parts-both',
|
|
||||||
title: 'Show label'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: false,
|
|
||||||
icon: 'icon-two-parts-one-only',
|
|
||||||
title: "Hide label"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
control: "button",
|
|
||||||
domainObject: domainObject,
|
|
||||||
method: function () {
|
|
||||||
selection[0].context.fixedController.remove(
|
|
||||||
selection[0].context.elementProxy
|
|
||||||
);
|
|
||||||
},
|
|
||||||
key: "remove",
|
|
||||||
icon: "icon-trash"
|
|
||||||
}
|
|
||||||
].filter(function (item) {
|
|
||||||
var filtered;
|
|
||||||
|
|
||||||
properties.forEach(function (property) {
|
|
||||||
if (item.property && item.key === property ||
|
|
||||||
item.method && item.key === property) {
|
|
||||||
filtered = item;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return filtered;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"types": [
|
|
||||||
{
|
|
||||||
"key": "telemetry.fixed",
|
|
||||||
"name": "Fixed Position Display",
|
|
||||||
"cssClass": "icon-box-with-dashed-lines",
|
|
||||||
"description": "Collect and display telemetry elements in " +
|
|
||||||
"alphanumeric format in a simple canvas workspace. " +
|
|
||||||
"Elements can be positioned and sized. " +
|
|
||||||
"Lines, boxes and images can be added as well.",
|
|
||||||
"priority": 899,
|
|
||||||
"delegates": [
|
|
||||||
"telemetry"
|
|
||||||
],
|
|
||||||
"features": "creation",
|
|
||||||
"contains": [
|
|
||||||
{
|
|
||||||
"has": "telemetry"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"model": {
|
|
||||||
"layoutGrid": [64, 16],
|
|
||||||
"composition": []
|
|
||||||
},
|
|
||||||
"properties": [
|
|
||||||
{
|
|
||||||
"name": "Layout Grid",
|
|
||||||
"control": "composite",
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"name": "Horizontal grid (px)",
|
|
||||||
"control": "textfield",
|
|
||||||
"cssClass": "l-input-sm l-numeric"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Vertical grid (px)",
|
|
||||||
"control": "textfield",
|
|
||||||
"cssClass": "l-input-sm l-numeric"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"pattern": "^(\\d*[1-9]\\d*)?$",
|
|
||||||
"property": "layoutGrid",
|
|
||||||
"conversion": "number[]"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"views": [
|
|
||||||
"fixed-display"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
openmct.legacyRegistry.enable("platform/features/fixed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
@ -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="c-hyperlink u-links" ng-controller="HyperlinkController as hyperlink" href="{{domainObject.getModel().url}}"
|
<a class="l-hyperlink s-hyperlink" 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="{
|
||||||
'c-hyperlink--button u-fills-container' : hyperlink.isButton(),
|
's-button': hyperlink.isButton()
|
||||||
'c-hyperlink--link' : !hyperlink.isButton() }">
|
}">
|
||||||
<span class="c-hyperlink__label">{{domainObject.getModel().displayText}}</span>
|
<span class="label">{{domainObject.getModel().displayText}}</span>
|
||||||
</a>
|
</a>
|
||||||
|
356
platform/features/layout/bundle.js
Normal file
356
platform/features/layout/bundle.js
Normal file
@ -0,0 +1,356 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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",
|
||||||
|
"./src/FixedController",
|
||||||
|
"./src/LayoutCompositionPolicy",
|
||||||
|
'./src/MCTTriggerModal',
|
||||||
|
"./res/templates/layout.html",
|
||||||
|
"./res/templates/fixed.html",
|
||||||
|
"./res/templates/frame.html",
|
||||||
|
"./res/templates/elements/telemetry.html",
|
||||||
|
"./res/templates/elements/box.html",
|
||||||
|
"./res/templates/elements/line.html",
|
||||||
|
"./res/templates/elements/text.html",
|
||||||
|
"./res/templates/elements/image.html",
|
||||||
|
'legacyRegistry'
|
||||||
|
], function (
|
||||||
|
LayoutController,
|
||||||
|
FixedController,
|
||||||
|
LayoutCompositionPolicy,
|
||||||
|
MCTTriggerModal,
|
||||||
|
layoutTemplate,
|
||||||
|
fixedTemplate,
|
||||||
|
frameTemplate,
|
||||||
|
telemetryTemplate,
|
||||||
|
boxTemplate,
|
||||||
|
lineTemplate,
|
||||||
|
textTemplate,
|
||||||
|
imageTemplate,
|
||||||
|
legacyRegistry
|
||||||
|
) {
|
||||||
|
|
||||||
|
legacyRegistry.register("platform/features/layout", {
|
||||||
|
"name": "Layout components.",
|
||||||
|
"description": "Plug in adding Layout capabilities.",
|
||||||
|
"extensions": {
|
||||||
|
"views": [
|
||||||
|
{
|
||||||
|
"key": "layout",
|
||||||
|
"name": "Display Layout",
|
||||||
|
"cssClass": "icon-layout",
|
||||||
|
"type": "layout",
|
||||||
|
"template": layoutTemplate,
|
||||||
|
"editable": true,
|
||||||
|
"uses": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "fixed",
|
||||||
|
"name": "Fixed Position",
|
||||||
|
"cssClass": "icon-box-with-dashed-lines",
|
||||||
|
"type": "telemetry.panel",
|
||||||
|
"template": fixedTemplate,
|
||||||
|
"uses": [
|
||||||
|
"composition"
|
||||||
|
],
|
||||||
|
"toolbar": {
|
||||||
|
"sections": [
|
||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"method": "add",
|
||||||
|
"cssClass": "icon-plus",
|
||||||
|
"control": "menu-button",
|
||||||
|
"text": "Add",
|
||||||
|
"title": "Add",
|
||||||
|
"description": "Add new items",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"name": "Box",
|
||||||
|
"cssClass": "icon-box",
|
||||||
|
"key": "fixed.box"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Line",
|
||||||
|
"cssClass": "icon-line-horz",
|
||||||
|
"key": "fixed.line"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Text",
|
||||||
|
"cssClass": "icon-T",
|
||||||
|
"key": "fixed.text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Image",
|
||||||
|
"cssClass": "icon-image",
|
||||||
|
"key": "fixed.image"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"method": "order",
|
||||||
|
"cssClass": "icon-layers",
|
||||||
|
"control": "menu-button",
|
||||||
|
"title": "Layering",
|
||||||
|
"description": "Move the selected object above or below other objects",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"name": "Move to Top",
|
||||||
|
"cssClass": "icon-arrow-double-up",
|
||||||
|
"key": "top"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Move Up",
|
||||||
|
"cssClass": "icon-arrow-up",
|
||||||
|
"key": "up"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Move Down",
|
||||||
|
"cssClass": "icon-arrow-down",
|
||||||
|
"key": "down"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Move to Bottom",
|
||||||
|
"cssClass": "icon-arrow-double-down",
|
||||||
|
"key": "bottom"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"property": "fill",
|
||||||
|
"cssClass": "icon-paint-bucket",
|
||||||
|
"title": "Fill color",
|
||||||
|
"description": "Set fill color",
|
||||||
|
"control": "color"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"property": "stroke",
|
||||||
|
"cssClass": "icon-line-horz",
|
||||||
|
"title": "Border color",
|
||||||
|
"description": "Set border color",
|
||||||
|
"control": "color"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"property": "color",
|
||||||
|
"cssClass": "icon-T",
|
||||||
|
"title": "Text color",
|
||||||
|
"description": "Set text color",
|
||||||
|
"mandatory": true,
|
||||||
|
"control": "color"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"property": "url",
|
||||||
|
"cssClass": "icon-image",
|
||||||
|
"control": "dialog-button",
|
||||||
|
"title": "Image Properties",
|
||||||
|
"description": "Edit image properties",
|
||||||
|
"dialog": {
|
||||||
|
"control": "textfield",
|
||||||
|
"name": "Image URL",
|
||||||
|
"cssClass": "l-input-lg",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"property": "text",
|
||||||
|
"cssClass": "icon-gear",
|
||||||
|
"control": "dialog-button",
|
||||||
|
"title": "Text Properties",
|
||||||
|
"description": "Edit text properties",
|
||||||
|
"dialog": {
|
||||||
|
"control": "textfield",
|
||||||
|
"name": "Text",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"method": "showTitle",
|
||||||
|
"cssClass": "icon-two-parts-both",
|
||||||
|
"control": "button",
|
||||||
|
"title": "Show title",
|
||||||
|
"description": "Show telemetry element title"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"method": "hideTitle",
|
||||||
|
"cssClass": "icon-two-parts-one-only",
|
||||||
|
"control": "button",
|
||||||
|
"title": "Hide title",
|
||||||
|
"description": "Hide telemetry element title"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"method": "remove",
|
||||||
|
"control": "button",
|
||||||
|
"cssClass": "icon-trash",
|
||||||
|
"title": "Delete",
|
||||||
|
"description": "Delete the selected item"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"representations": [
|
||||||
|
{
|
||||||
|
"key": "frame",
|
||||||
|
"template": frameTemplate
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"directives": [
|
||||||
|
{
|
||||||
|
"key": "mctTriggerModal",
|
||||||
|
"implementation": MCTTriggerModal,
|
||||||
|
"depends": [
|
||||||
|
"$document"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"controllers": [
|
||||||
|
{
|
||||||
|
"key": "LayoutController",
|
||||||
|
"implementation": LayoutController,
|
||||||
|
"depends": [
|
||||||
|
"$scope",
|
||||||
|
"$element",
|
||||||
|
"openmct"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "FixedController",
|
||||||
|
"implementation": FixedController,
|
||||||
|
"depends": [
|
||||||
|
"$scope",
|
||||||
|
"$q",
|
||||||
|
"dialogService",
|
||||||
|
"openmct",
|
||||||
|
"$element"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"templates": [
|
||||||
|
{
|
||||||
|
"key": "fixed.telemetry",
|
||||||
|
"template": telemetryTemplate
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "fixed.box",
|
||||||
|
"template": boxTemplate
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "fixed.line",
|
||||||
|
"template": lineTemplate
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "fixed.text",
|
||||||
|
"template": textTemplate
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "fixed.image",
|
||||||
|
"template": imageTemplate
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"policies": [
|
||||||
|
{
|
||||||
|
"category": "composition",
|
||||||
|
"implementation": LayoutCompositionPolicy
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"toolbars": [
|
||||||
|
{
|
||||||
|
name: "Display Layout Toolbar",
|
||||||
|
key: "layout",
|
||||||
|
description: "A toolbar for objects inside a display layout.",
|
||||||
|
forSelection: function (selection) {
|
||||||
|
// Apply the layout toolbar if the selected object is inside a layout.
|
||||||
|
return (selection && selection[1] && selection[1].context.item.type === 'layout');
|
||||||
|
},
|
||||||
|
toolbar: function (selection) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
control: "checkbox",
|
||||||
|
name: "Show frame",
|
||||||
|
domainObject: selection[1].context.item,
|
||||||
|
property: "configuration.layout.panels[" + selection[0].context.oldItem.id + "].hasFrame"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"types": [
|
||||||
|
{
|
||||||
|
"key": "layout",
|
||||||
|
"name": "Display Layout",
|
||||||
|
"cssClass": "icon-layout",
|
||||||
|
"description": "Assemble other objects and components together into a reusable screen layout. Working in a simple canvas workspace, simply drag in the objects you want, position and size them. Save your design and view or edit it at any time.",
|
||||||
|
"priority": 900,
|
||||||
|
"features": "creation",
|
||||||
|
"model": {
|
||||||
|
"composition": [],
|
||||||
|
configuration: {
|
||||||
|
layout: {
|
||||||
|
panels: {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"name": "Layout Grid",
|
||||||
|
"control": "composite",
|
||||||
|
"pattern": "^(\\d*[1-9]\\d*)?$",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"name": "Horizontal grid (px)",
|
||||||
|
"control": "textfield",
|
||||||
|
"cssClass": "l-input-sm l-numeric"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Vertical grid (px)",
|
||||||
|
"control": "textfield",
|
||||||
|
"cssClass": "l-input-sm l-numeric"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"key": "layoutGrid",
|
||||||
|
"conversion": "number[]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Advanced",
|
||||||
|
"control": "textfield",
|
||||||
|
"key": "layoutAdvancedCss",
|
||||||
|
"cssClass": "l-input-lg"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
@ -23,18 +23,18 @@
|
|||||||
ng-controller="FixedController as controller">
|
ng-controller="FixedController as controller">
|
||||||
|
|
||||||
<!-- Background grid -->
|
<!-- Background grid -->
|
||||||
<div class="l-fixed-position__grid-holder l-grid-holder c-grid" ng-click="controller.bypassSelection($event)">
|
<div class="l-grid-holder" ng-click="controller.bypassSelection($event)">
|
||||||
<div class="c-grid__x l-grid l-grid-x"
|
<div class="l-grid l-grid-x"
|
||||||
ng-if="!controller.getGridSize()[0] < 3"
|
ng-if="!controller.getGridSize()[0] < 3"
|
||||||
ng-style="{ 'background-size': controller.getGridSize() [0] + 'px 100%' }"></div>
|
ng-style="{ 'background-size': controller.getGridSize() [0] + 'px 100%' }"></div>
|
||||||
<div class="c-grid__y l-grid l-grid-y"
|
<div class="l-grid l-grid-y"
|
||||||
ng-if="!controller.getGridSize()[1] < 3"
|
ng-if="!controller.getGridSize()[1] < 3"
|
||||||
ng-style="{ 'background-size': '100% ' + controller.getGridSize() [1] + 'px' }"></div>
|
ng-style="{ 'background-size': '100% ' + controller.getGridSize() [1] + 'px' }"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Fixed position elements -->
|
<!-- Fixed position elements -->
|
||||||
<div ng-repeat="element in controller.getElements()"
|
<div ng-repeat="element in controller.getElements()"
|
||||||
class="l-fixed-position-item s-selectable s-moveable is-selectable is-moveable"
|
class="l-fixed-position-item s-selectable s-moveable s-hover-border"
|
||||||
ng-style="element.style"
|
ng-style="element.style"
|
||||||
mct-selectable="controller.getContext(element)"
|
mct-selectable="controller.getContext(element)"
|
||||||
mct-init-select="controller.shouldSelect(element)">
|
mct-init-select="controller.shouldSelect(element)">
|
||||||
@ -44,15 +44,15 @@
|
|||||||
</mct-include>
|
</mct-include>
|
||||||
</div>
|
</div>
|
||||||
<!-- Selection highlight, handles -->
|
<!-- Selection highlight, handles -->
|
||||||
<span class="c-frame-edit" ng-if="controller.isElementSelected()">
|
<span class="s-selected s-moveable" ng-if="controller.isElementSelected()">
|
||||||
<div class="c-frame-edit__move"
|
<div class="l-fixed-position-item t-edit-handle-holder"
|
||||||
mct-drag-down="controller.moveHandle().startDrag()"
|
mct-drag-down="controller.moveHandle().startDrag()"
|
||||||
mct-drag="controller.moveHandle().continueDrag(delta)"
|
mct-drag="controller.moveHandle().continueDrag(delta)"
|
||||||
mct-drag-up="controller.endDrag()"
|
mct-drag-up="controller.endDrag()"
|
||||||
ng-style="controller.getSelectedElementStyle()">
|
ng-style="controller.getSelectedElementStyle()">
|
||||||
</div>
|
</div>
|
||||||
<div ng-repeat="handle in controller.handles()"
|
<div ng-repeat="handle in controller.handles()"
|
||||||
class="c-frame-edit__handle c-frame-edit__handle--nwse"
|
class="l-fixed-position-item-handle edit-corner"
|
||||||
ng-style="handle.style()"
|
ng-style="handle.style()"
|
||||||
mct-drag-down="handle.startDrag()"
|
mct-drag-down="handle.startDrag()"
|
||||||
mct-drag="handle.continueDrag(delta)"
|
mct-drag="handle.continueDrag(delta)"
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user