mirror of
https://github.com/nasa/openmct.git
synced 2025-06-26 19:12:02 +00:00
Compare commits
7 Commits
dialog-ser
...
vue-table-
Author | SHA1 | Date | |
---|---|---|---|
aea7753ecb | |||
ecefed2b6a | |||
4d9a186d6e | |||
15c6d46674 | |||
4d316c3806 | |||
61d62eeaf6 | |||
f0753de949 |
25
app.js
25
app.js
@ -16,7 +16,7 @@ const request = require('request');
|
||||
|
||||
// Defaults
|
||||
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 || '.';
|
||||
|
||||
// Show command line options
|
||||
@ -42,30 +42,15 @@ app.use('/proxyUrl', function proxyRequest(req, res, next) {
|
||||
|
||||
const webpack = require('webpack');
|
||||
const webpackConfig = require('./webpack.config.js');
|
||||
webpackConfig.plugins.push(new webpack.HotModuleReplacementPlugin());
|
||||
webpackConfig.plugins.push(function() { this.plugin('watch-run', function(watching, callback) { console.log('Begin compile at ' + new Date()); callback(); }) });
|
||||
|
||||
webpackConfig.entry.openmct = [
|
||||
'webpack-hot-middleware/client',
|
||||
webpackConfig.entry.openmct
|
||||
];
|
||||
|
||||
const compiler = webpack(webpackConfig);
|
||||
|
||||
app.use(require('webpack-dev-middleware')(
|
||||
compiler,
|
||||
{
|
||||
const webpackDevRoute = require('webpack-dev-middleware')(
|
||||
compiler, {
|
||||
publicPath: '/dist',
|
||||
logLevel: 'warn'
|
||||
}
|
||||
));
|
||||
);
|
||||
|
||||
app.use(require('webpack-hot-middleware')(
|
||||
compiler,
|
||||
{
|
||||
|
||||
}
|
||||
));
|
||||
app.use(webpackDevRoute);
|
||||
|
||||
// Expose index.html for development users.
|
||||
app.get('/', function (req, res) {
|
||||
|
@ -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.
|
||||
*****************************************************************************/
|
@ -34,6 +34,9 @@
|
||||
<link rel="shortcut icon" href="dist/favicons/favicon.ico">
|
||||
</head>
|
||||
<body>
|
||||
<div class="l-splash-holder s-splash-holder">
|
||||
<div class="l-splash s-splash"></div>
|
||||
</div>
|
||||
</body>
|
||||
<script>
|
||||
var THIRTY_MINUTES = 30 * 60 * 1000;
|
||||
@ -45,6 +48,7 @@
|
||||
);
|
||||
openmct.install(openmct.plugins.MyItems());
|
||||
openmct.install(openmct.plugins.LocalStorage());
|
||||
openmct.install(openmct.plugins.Espresso());
|
||||
openmct.install(openmct.plugins.Generator());
|
||||
openmct.install(openmct.plugins.ExampleImagery());
|
||||
openmct.install(openmct.plugins.UTCTimeSystem());
|
||||
@ -75,7 +79,6 @@
|
||||
}));
|
||||
openmct.install(openmct.plugins.SummaryWidget());
|
||||
openmct.install(openmct.plugins.Notebook());
|
||||
openmct.install(openmct.plugins.FolderView());
|
||||
openmct.time.clock('local', {start: -THIRTY_MINUTES, end: 0});
|
||||
openmct.time.timeSystem('utc');
|
||||
openmct.start();
|
||||
|
16
openmct.js
16
openmct.js
@ -29,9 +29,23 @@ if (document.currentScript) {
|
||||
__webpack_public_path__ = src.replace(matcher, '') + '/';
|
||||
}
|
||||
}
|
||||
|
||||
const Main = require('./platform/framework/src/Main');
|
||||
const defaultRegistry = require('./src/defaultRegistry');
|
||||
const MCT = require('./src/MCT');
|
||||
const buildInfo = require('./src/plugins/buildInfo/plugin');
|
||||
|
||||
var openmct = new MCT();
|
||||
|
||||
openmct.legacyRegistry = defaultRegistry;
|
||||
openmct.install(openmct.plugins.Plot());
|
||||
openmct.install(openmct.plugins.TelemetryTable());
|
||||
|
||||
if (typeof BUILD_CONSTANTS !== 'undefined') {
|
||||
openmct.install(buildInfo(BUILD_CONSTANTS));
|
||||
}
|
||||
|
||||
openmct.on('start', function () {
|
||||
return new Main().run(defaultRegistry);
|
||||
});
|
||||
|
||||
module.exports = openmct;
|
||||
|
@ -43,7 +43,6 @@
|
||||
"karma-html-reporter": "^0.2.7",
|
||||
"karma-jasmine": "^1.1.2",
|
||||
"karma-webpack": "^3.0.0",
|
||||
"location-bar": "^3.0.1",
|
||||
"lodash": "^3.10.1",
|
||||
"markdown-toc": "^0.11.7",
|
||||
"marked": "^0.3.5",
|
||||
@ -68,7 +67,6 @@
|
||||
"webpack": "^4.16.2",
|
||||
"webpack-cli": "^3.1.0",
|
||||
"webpack-dev-middleware": "^3.1.3",
|
||||
"webpack-hot-middleware": "^2.22.3",
|
||||
"zepto": "^1.2.0"
|
||||
},
|
||||
"scripts": {
|
||||
|
@ -35,10 +35,12 @@ define([
|
||||
"./src/windowing/WindowTitler",
|
||||
"./res/templates/browse.html",
|
||||
"./res/templates/browse-object.html",
|
||||
"./res/templates/items/grid-item.html",
|
||||
"./res/templates/browse/object-header.html",
|
||||
"./res/templates/browse/object-header-frame.html",
|
||||
"./res/templates/menu-arrow.html",
|
||||
"./res/templates/back-arrow.html",
|
||||
"./res/templates/items/items.html",
|
||||
"./res/templates/browse/object-properties.html",
|
||||
"./res/templates/browse/inspector-region.html",
|
||||
'legacyRegistry'
|
||||
@ -57,10 +59,12 @@ define([
|
||||
WindowTitler,
|
||||
browseTemplate,
|
||||
browseObjectTemplate,
|
||||
gridItemTemplate,
|
||||
objectHeaderTemplate,
|
||||
objectHeaderFrameTemplate,
|
||||
menuArrowTemplate,
|
||||
backArrowTemplate,
|
||||
itemsTemplate,
|
||||
objectPropertiesTemplate,
|
||||
inspectorRegionTemplate,
|
||||
legacyRegistry
|
||||
@ -69,6 +73,15 @@ define([
|
||||
legacyRegistry.register("platform/commonUI/browse", {
|
||||
"extensions": {
|
||||
"routes": [
|
||||
{
|
||||
"when": "/browse/:ids*?",
|
||||
"template": browseTemplate,
|
||||
"reloadOnSearch": false
|
||||
},
|
||||
{
|
||||
"when": "",
|
||||
"redirectTo": "/browse/"
|
||||
}
|
||||
],
|
||||
"constants": [
|
||||
{
|
||||
@ -152,6 +165,19 @@ define([
|
||||
"view"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "grid-item",
|
||||
"template": gridItemTemplate,
|
||||
"uses": [
|
||||
"type",
|
||||
"action",
|
||||
"location"
|
||||
],
|
||||
"gestures": [
|
||||
"info",
|
||||
"menu"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "object-header",
|
||||
"template": objectHeaderTemplate,
|
||||
@ -234,6 +260,23 @@ define([
|
||||
"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": [
|
||||
{
|
||||
"implementation": WindowTitler,
|
||||
@ -252,20 +295,6 @@ define([
|
||||
]
|
||||
}
|
||||
],
|
||||
"templates": [
|
||||
{
|
||||
key: "browseRoot",
|
||||
template: browseTemplate
|
||||
},
|
||||
{
|
||||
key: "browseObject",
|
||||
template: browseObjectTemplate
|
||||
},
|
||||
{
|
||||
key: "inspectorRegion",
|
||||
template: inspectorRegionTemplate
|
||||
}
|
||||
],
|
||||
"licenses": [
|
||||
{
|
||||
"name": "screenfull.js",
|
||||
|
@ -66,4 +66,5 @@
|
||||
</mct-representation>
|
||||
</div>
|
||||
</div>
|
||||
<mct-include key="'conductor'" class="abs holder flex-elem flex-fixed l-flex-row l-time-conductor-holder"></mct-include>
|
||||
</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
|
||||
this source code distribution or the Licensing information page available
|
||||
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>
|
@ -47,7 +47,6 @@ define(
|
||||
urlService,
|
||||
defaultPath
|
||||
) {
|
||||
window.browseScope = $scope;
|
||||
var initialPath = ($route.current.params.ids || defaultPath).split("/"),
|
||||
currentIds;
|
||||
|
||||
|
@ -58,7 +58,7 @@ define([], function () {
|
||||
|
||||
function checkNavigation() {
|
||||
var navigatedObject = navigationService.getNavigation();
|
||||
if (navigatedObject && navigatedObject.hasCapability('context')) {
|
||||
if (navigatedObject.hasCapability('context')) {
|
||||
if (!navigatedObject.getCapability('editor').isEditContextRoot()) {
|
||||
preventOrphanNavigation(navigatedObject);
|
||||
}
|
||||
|
@ -319,12 +319,6 @@ define([
|
||||
]
|
||||
}
|
||||
],
|
||||
"templates": [
|
||||
{
|
||||
key: "elementsPool",
|
||||
template: elementsTemplate
|
||||
}
|
||||
],
|
||||
"components": [
|
||||
{
|
||||
"type": "decorator",
|
||||
@ -405,8 +399,7 @@ define([
|
||||
"description": "Provides transactional editing capabilities",
|
||||
"implementation": EditorCapability,
|
||||
"depends": [
|
||||
"transactionService",
|
||||
"openmct"
|
||||
"transactionService"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
@ -36,11 +36,9 @@ define(
|
||||
*/
|
||||
function EditorCapability(
|
||||
transactionService,
|
||||
openmct,
|
||||
domainObject
|
||||
) {
|
||||
this.transactionService = transactionService;
|
||||
this.openmct = openmct;
|
||||
this.domainObject = domainObject;
|
||||
}
|
||||
|
||||
@ -50,19 +48,27 @@ define(
|
||||
* or finish() are called.
|
||||
*/
|
||||
EditorCapability.prototype.edit = function () {
|
||||
console.warn('DEPRECATED: cannot edit via edit capability, use openmct.editor instead.');
|
||||
this.openmct.editor.edit();
|
||||
this.transactionService.startTransaction();
|
||||
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
|
||||
* currently being edited.
|
||||
* @returns boolean
|
||||
*/
|
||||
EditorCapability.prototype.inEditContext = function () {
|
||||
console.warn('DEPRECATION WARNING: isEditing checks must be done via openmct.editor.');
|
||||
return this.openmct.editor.isEditing();
|
||||
return isEditing(this.domainObject);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -71,8 +77,7 @@ define(
|
||||
* @returns {*}
|
||||
*/
|
||||
EditorCapability.prototype.isEditContextRoot = function () {
|
||||
console.warn('DEPRECATION WARNING: isEditing checks must be done via openmct.editor.');
|
||||
return this.openmct.editor.isEditing();
|
||||
return isEditContextRoot(this.domainObject);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -81,7 +86,10 @@ define(
|
||||
* @returns {*}
|
||||
*/
|
||||
EditorCapability.prototype.save = function () {
|
||||
console.warn('DEPRECATED: cannot save via edit capability, use openmct.editor instead.');
|
||||
var transactionService = this.transactionService;
|
||||
return transactionService.commit().then(function () {
|
||||
transactionService.startTransaction();
|
||||
});
|
||||
};
|
||||
|
||||
EditorCapability.prototype.invoke = EditorCapability.prototype.edit;
|
||||
@ -92,7 +100,16 @@ define(
|
||||
* @returns {*}
|
||||
*/
|
||||
EditorCapability.prototype.finish = function () {
|
||||
console.warn('DEPRECATED: cannot finish via edit capability, use openmct.editor instead.');
|
||||
var domainObject = this.domainObject;
|
||||
|
||||
if (this.transactionService.isActive()) {
|
||||
return this.transactionService.cancel().then(function () {
|
||||
domainObject.getCapability("status").set("editing", false);
|
||||
return domainObject;
|
||||
});
|
||||
} else {
|
||||
return Promise.resolve(domainObject);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -55,16 +55,16 @@ define(
|
||||
navigatedObject = this.navigationService.getNavigation(),
|
||||
actionMetadata = action.getMetadata ? action.getMetadata() : {};
|
||||
|
||||
// if (navigatedObject.hasCapability("editor") && navigatedObject.getCapability("editor").isEditContextRoot()) {
|
||||
if (navigatedObject.hasCapability("editor") && navigatedObject.getCapability("editor").isEditContextRoot()) {
|
||||
if (selectedObject.hasCapability("editor") && selectedObject.getCapability("editor").inEditContext()) {
|
||||
return this.editModeBlacklist.indexOf(actionMetadata.key) === -1;
|
||||
} else {
|
||||
//Target is in the context menu
|
||||
return this.nonEditContextBlacklist.indexOf(actionMetadata.key) === -1;
|
||||
}
|
||||
// } else {
|
||||
// return true;
|
||||
// }
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
return EditContextualActionPolicy;
|
||||
|
@ -56,7 +56,7 @@ define([
|
||||
};
|
||||
|
||||
DurationFormat.prototype.validate = function (text) {
|
||||
return moment.utc(text, DATE_FORMATS, true).isValid();
|
||||
return moment.utc(text, DATE_FORMATS).isValid();
|
||||
};
|
||||
|
||||
return DurationFormat;
|
||||
|
@ -29,7 +29,6 @@ define([
|
||||
var DATE_FORMAT = "YYYY-MM-DD HH:mm:ss.SSS",
|
||||
DATE_FORMATS = [
|
||||
DATE_FORMAT,
|
||||
DATE_FORMAT + "Z",
|
||||
"YYYY-MM-DD HH:mm:ss",
|
||||
"YYYY-MM-DD HH:mm",
|
||||
"YYYY-MM-DD"
|
||||
@ -53,14 +52,70 @@ define([
|
||||
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} [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
|
||||
* formatted values will be returned. Where a value could not be formatted, `undefined` will be returned at its position
|
||||
* in the array.
|
||||
*/
|
||||
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";
|
||||
} else {
|
||||
return value;
|
||||
@ -75,7 +130,7 @@ define([
|
||||
};
|
||||
|
||||
UTCTimeFormat.prototype.validate = function (text) {
|
||||
return moment.utc(text, DATE_FORMATS, true).isValid();
|
||||
return moment.utc(text, DATE_FORMATS).isValid();
|
||||
};
|
||||
|
||||
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");
|
||||
});
|
||||
});
|
||||
});
|
@ -45,27 +45,15 @@ define(
|
||||
|
||||
// Link; install event handlers.
|
||||
function link(scope, element, attrs) {
|
||||
var isDestroyed = false;
|
||||
var removeSelectable = openmct.selection.selectable(
|
||||
element[0],
|
||||
scope.$eval(attrs.mctSelectable),
|
||||
attrs.hasOwnProperty('mctInitSelect') && scope.$eval(attrs.mctInitSelect) !== false
|
||||
);
|
||||
|
||||
scope.$on("$destroy", function () {
|
||||
isDestroyed = true;
|
||||
removeSelectable();
|
||||
});
|
||||
|
||||
openmct.$injector.get('$timeout')(function () {
|
||||
if (isDestroyed) {
|
||||
return;
|
||||
}
|
||||
var removeSelectable = openmct.selection.selectable(
|
||||
element[0],
|
||||
scope.$eval(attrs.mctSelectable),
|
||||
attrs.hasOwnProperty('mctInitSelect') && scope.$eval(attrs.mctInitSelect) !== false
|
||||
);
|
||||
|
||||
scope.$on("$destroy", function () {
|
||||
removeSelectable();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -76,7 +76,7 @@ define(
|
||||
* @returns a domain object
|
||||
*/
|
||||
InspectorController.prototype.selectedItem = function () {
|
||||
return this.$scope.selection[0] && this.$scope.selection[0].context.oldItem;
|
||||
return this.$scope.selection[0].context.oldItem;
|
||||
};
|
||||
|
||||
/**
|
||||
|
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;
|
||||
});
|
@ -20,9 +20,35 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
// Imports only constants, mixins, etc.
|
||||
// Meant for use as a single line import in Vue SFC's.
|
||||
// Do not include anything that renders to CSS!
|
||||
@import "constants";
|
||||
@import "constants-snow"; // TEMP
|
||||
@import "mixins";
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
@ -20,17 +20,34 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
'./FolderGridView',
|
||||
'./FolderListView'
|
||||
], function (
|
||||
FolderGridView,
|
||||
FolderListView
|
||||
) {
|
||||
return function plugin() {
|
||||
return function install(openmct) {
|
||||
openmct.objectViews.addProvider(new FolderGridView(openmct));
|
||||
openmct.objectViews.addProvider(new FolderListView(openmct));
|
||||
};
|
||||
define([], function () {
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
);
|
@ -19,10 +19,10 @@
|
||||
this source code distribution or the Licensing information page available
|
||||
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-class="{
|
||||
'c-hyperlink--button u-fills-container' : hyperlink.isButton(),
|
||||
'c-hyperlink--link' : !hyperlink.isButton() }">
|
||||
<span class="c-hyperlink__label">{{domainObject.getModel().displayText}}</span>
|
||||
's-button': hyperlink.isButton()
|
||||
}">
|
||||
<span class="label">{{domainObject.getModel().displayText}}</span>
|
||||
</a>
|
||||
|
83
platform/features/layout/res/templates/layout.html
Normal file
83
platform/features/layout/res/templates/layout.html
Normal file
@ -0,0 +1,83 @@
|
||||
<!--
|
||||
Open MCT, Copyright (c) 2014-2018, United States Government
|
||||
as represented by the Administrator of the National Aeronautics and Space
|
||||
Administration. All rights reserved.
|
||||
|
||||
Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0.
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
License for the specific language governing permissions and limitations
|
||||
under the License.
|
||||
|
||||
Open MCT includes source code licensed under additional open source
|
||||
licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
this source code distribution or the Licensing information page available
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
|
||||
<div class="abs l-layout {{ domainObject.getModel().layoutAdvancedCss }}"
|
||||
ng-controller="LayoutController as controller"
|
||||
ng-click="controller.bypassSelection($event)">
|
||||
|
||||
<!-- Background grid -->
|
||||
<div class="l-grid-holder"
|
||||
ng-show="!controller.drilledIn"
|
||||
ng-click="controller.bypassSelection($event)">
|
||||
<div class="l-grid l-grid-x"
|
||||
ng-if="!controller.getGridSize()[0] < 3"
|
||||
ng-style="{ 'background-size': controller.getGridSize() [0] + 'px 100%' }"></div>
|
||||
<div class="l-grid l-grid-y"
|
||||
ng-if="!controller.getGridSize()[1] < 3"
|
||||
ng-style="{ 'background-size': '100% ' + controller.getGridSize() [1] + 'px' }"></div>
|
||||
</div>
|
||||
|
||||
<div class="frame t-frame-outer child-frame panel s-selectable s-moveable s-hover-border t-object-type-{{ childObject.getModel().type }}"
|
||||
data-layout-id="{{childObject.getId() + '-' + $id}}"
|
||||
ng-class="{ 'no-frame': !controller.hasFrame(childObject), 's-drilled-in': controller.isDrilledIn(childObject) }"
|
||||
ng-repeat="childObject in composition"
|
||||
ng-init="controller.selectIfNew(childObject.getId() + '-' + $id, childObject)"
|
||||
mct-selectable="controller.getContext(childObject)"
|
||||
ng-dblclick="controller.drill($event, childObject)"
|
||||
ng-style="controller.getFrameStyle(childObject.getId())">
|
||||
|
||||
<mct-representation key="'frame'"
|
||||
class="t-rep-frame holder contents abs"
|
||||
mct-object="childObject">
|
||||
</mct-representation>
|
||||
<!-- Drag handles -->
|
||||
<span class="abs t-edit-handle-holder" ng-if="controller.selected(childObject) && !controller.isDrilledIn(childObject)">
|
||||
<span class="edit-handle edit-move"
|
||||
mct-drag-down="controller.startDrag(childObject.getId(), [1,1], [0,0])"
|
||||
mct-drag="controller.continueDrag(delta)"
|
||||
mct-drag-up="controller.endDrag()">
|
||||
</span>
|
||||
|
||||
<span class="edit-corner edit-resize-nw"
|
||||
mct-drag-down="controller.startDrag(childObject.getId(), [1,1], [-1,-1])"
|
||||
mct-drag="controller.continueDrag(delta)"
|
||||
mct-drag-up="controller.endDrag()">
|
||||
</span>
|
||||
<span class="edit-corner edit-resize-ne"
|
||||
mct-drag-down="controller.startDrag(childObject.getId(), [0,1], [1,-1])"
|
||||
mct-drag="controller.continueDrag(delta)"
|
||||
mct-drag-up="controller.endDrag()">
|
||||
</span>
|
||||
<span class="edit-corner edit-resize-sw"
|
||||
mct-drag-down="controller.startDrag(childObject.getId(), [1,0], [-1,1])"
|
||||
mct-drag="controller.continueDrag(delta)"
|
||||
mct-drag-up="controller.endDrag()">
|
||||
</span>
|
||||
<span class="edit-corner edit-resize-se"
|
||||
mct-drag-down="controller.startDrag(childObject.getId(), [0,0], [1,1])"
|
||||
mct-drag="controller.continueDrag(delta)"
|
||||
mct-drag-up="controller.endDrag()">
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
</div>
|
524
platform/features/layout/src/LayoutController.js
Normal file
524
platform/features/layout/src/LayoutController.js
Normal file
@ -0,0 +1,524 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
/**
|
||||
* This bundle implements object types and associated views for
|
||||
* display-building.
|
||||
* @namespace platform/features/layout
|
||||
*/
|
||||
define(
|
||||
[
|
||||
'zepto',
|
||||
'./LayoutDrag'
|
||||
],
|
||||
function (
|
||||
$,
|
||||
LayoutDrag
|
||||
) {
|
||||
|
||||
var DEFAULT_DIMENSIONS = [12, 8],
|
||||
DEFAULT_GRID_SIZE = [32, 32],
|
||||
MINIMUM_FRAME_SIZE = [320, 180];
|
||||
|
||||
var DEFAULT_HIDDEN_FRAME_TYPES = [
|
||||
'hyperlink'
|
||||
];
|
||||
|
||||
/**
|
||||
* The LayoutController is responsible for supporting the
|
||||
* Layout view. It arranges frames according to saved configuration
|
||||
* and provides methods for updating these based on mouse
|
||||
* movement.
|
||||
* @memberof platform/features/layout
|
||||
* @constructor
|
||||
* @param {Scope} $scope the controller's Angular scope
|
||||
*/
|
||||
function LayoutController($scope, $element, openmct) {
|
||||
var self = this,
|
||||
callbackCount = 0;
|
||||
|
||||
this.$element = $element;
|
||||
|
||||
// Update grid size when it changed
|
||||
function updateGridSize(layoutGrid) {
|
||||
var oldSize = self.gridSize;
|
||||
|
||||
self.gridSize = layoutGrid || DEFAULT_GRID_SIZE;
|
||||
|
||||
// Only update panel positions if this actually changed things
|
||||
if (self.gridSize[0] !== oldSize[0] ||
|
||||
self.gridSize[1] !== oldSize[1]) {
|
||||
self.layoutPanels(Object.keys(self.positions));
|
||||
}
|
||||
}
|
||||
|
||||
// Position a panel after a drop event
|
||||
function handleDrop(e, id, position) {
|
||||
if (e.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.configuration = $scope.configuration || {};
|
||||
$scope.configuration.panels = $scope.configuration.panels || {};
|
||||
|
||||
self.openmct.objects.get(id).then(function (object) {
|
||||
$scope.configuration.panels[id] = {
|
||||
position: [
|
||||
Math.floor(position.x / self.gridSize[0]),
|
||||
Math.floor(position.y / self.gridSize[1])
|
||||
],
|
||||
dimensions: self.defaultDimensions(),
|
||||
hasFrame: self.getDefaultFrame(object.type)
|
||||
};
|
||||
|
||||
// Store the id so that the newly-dropped object
|
||||
// gets selected during refresh composition
|
||||
self.droppedIdToSelectAfterRefresh = id;
|
||||
|
||||
self.commit();
|
||||
|
||||
// Populate template-facing position for this id
|
||||
self.rawPositions[id] = $scope.configuration.panels[id];
|
||||
self.populatePosition(id);
|
||||
refreshComposition();
|
||||
});
|
||||
|
||||
// Layout may contain embedded views which will
|
||||
// listen for drops, so call preventDefault() so
|
||||
// that they can recognize that this event is handled.
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
//Will fetch fully contextualized composed objects, and populate
|
||||
// scope with them.
|
||||
function refreshComposition() {
|
||||
//Keep a track of how many composition callbacks have been made
|
||||
var thisCount = ++callbackCount;
|
||||
|
||||
$scope.domainObject.useCapability('composition').then(function (composition) {
|
||||
var ids;
|
||||
|
||||
//Is this callback for the most recent composition
|
||||
// request? If not, discard it. Prevents race condition
|
||||
if (thisCount === callbackCount) {
|
||||
ids = composition.map(function (object) {
|
||||
return object.getId();
|
||||
}) || [];
|
||||
|
||||
$scope.composition = composition;
|
||||
self.layoutPanels(ids);
|
||||
self.setFrames(ids);
|
||||
|
||||
if (self.selectedId &&
|
||||
self.selectedId !== $scope.domainObject.getId() &&
|
||||
composition.indexOf(self.selectedId) === -1) {
|
||||
// Click triggers selection of layout parent.
|
||||
self.$element[0].click();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// End drag; we don't want to put $scope into this
|
||||
// because it triggers "cpws" (copy window or scope)
|
||||
// errors in Angular.
|
||||
this.endDragInScope = function () {
|
||||
// Write to configuration; this is watched and
|
||||
// saved by the EditRepresenter.
|
||||
$scope.configuration =
|
||||
$scope.configuration || {};
|
||||
|
||||
$scope.configuration.panels =
|
||||
$scope.configuration.panels || {};
|
||||
|
||||
$scope.configuration.panels[self.activeDragId] =
|
||||
$scope.configuration.panels[self.activeDragId] || {};
|
||||
|
||||
$scope.configuration.panels[self.activeDragId].position =
|
||||
self.rawPositions[self.activeDragId].position;
|
||||
$scope.configuration.panels[self.activeDragId].dimensions =
|
||||
self.rawPositions[self.activeDragId].dimensions;
|
||||
|
||||
self.commit();
|
||||
};
|
||||
|
||||
// Sets the selectable object in response to the selection change event.
|
||||
function setSelection(selectable) {
|
||||
var selection = selectable[0];
|
||||
|
||||
if (!selection) {
|
||||
delete self.selectedId;
|
||||
return;
|
||||
}
|
||||
|
||||
self.selectedId = selection.context.oldItem.getId();
|
||||
self.drilledIn = undefined;
|
||||
self.selectable = selectable;
|
||||
}
|
||||
|
||||
this.positions = {};
|
||||
this.rawPositions = {};
|
||||
this.gridSize = DEFAULT_GRID_SIZE;
|
||||
this.$scope = $scope;
|
||||
this.drilledIn = undefined;
|
||||
this.openmct = openmct;
|
||||
|
||||
// Watch for changes to the grid size in the model
|
||||
$scope.$watch("model.layoutGrid", updateGridSize);
|
||||
|
||||
// Update composed objects on screen, and position panes
|
||||
$scope.$watchCollection("model.composition", refreshComposition);
|
||||
|
||||
openmct.selection.on('change', setSelection);
|
||||
|
||||
$scope.$on("$destroy", function () {
|
||||
openmct.selection.off("change", setSelection);
|
||||
self.unlisten();
|
||||
});
|
||||
|
||||
$scope.$on("mctDrop", handleDrop);
|
||||
|
||||
self.unlisten = self.$scope.domainObject.getCapability('mutation').listen(function (model) {
|
||||
$scope.configuration = model.configuration.layout;
|
||||
$scope.model = model;
|
||||
var panels = $scope.configuration.panels;
|
||||
|
||||
Object.keys(panels).forEach(function (key) {
|
||||
if (self.frames && self.frames.hasOwnProperty(key)) {
|
||||
self.frames[key] = panels[key].hasFrame;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Utility function to copy raw positions from configuration,
|
||||
// without writing directly to configuration (to avoid triggering
|
||||
// persistence from watchers during drags).
|
||||
function shallowCopy(obj, keys) {
|
||||
var copy = {};
|
||||
keys.forEach(function (k) {
|
||||
copy[k] = obj[k];
|
||||
});
|
||||
return copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the frames value. If a configuration panel has "hasFrame' property,
|
||||
* use that value, otherwise set a default value. A 'hyperlink' object should
|
||||
* have no frame by default.
|
||||
*
|
||||
* @param {string[]} ids the object ids
|
||||
* @private
|
||||
*/
|
||||
LayoutController.prototype.setFrames = function (ids) {
|
||||
var panels = shallowCopy(this.$scope.configuration.panels || {}, ids);
|
||||
this.frames = {};
|
||||
|
||||
this.$scope.composition.forEach(function (object) {
|
||||
var id = object.getId();
|
||||
panels[id] = panels[id] || {};
|
||||
|
||||
if (panels[id].hasOwnProperty('hasFrame')) {
|
||||
this.frames[id] = panels[id].hasFrame;
|
||||
} else {
|
||||
this.frames[id] = this.getDefaultFrame(object.getModel().type);
|
||||
}
|
||||
}, this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the default value for frame.
|
||||
*
|
||||
* @param type the domain object type
|
||||
* @return {boolean} true if the object should have
|
||||
* frame by default, false, otherwise
|
||||
*/
|
||||
LayoutController.prototype.getDefaultFrame = function (type) {
|
||||
return DEFAULT_HIDDEN_FRAME_TYPES.indexOf(type) === -1;
|
||||
};
|
||||
|
||||
// Convert from { positions: ..., dimensions: ... } to an
|
||||
// appropriate ng-style argument, to position frames.
|
||||
LayoutController.prototype.convertPosition = function (raw) {
|
||||
var gridSize = this.gridSize;
|
||||
// Multiply position/dimensions by grid size
|
||||
return {
|
||||
left: (gridSize[0] * raw.position[0]) + 'px',
|
||||
top: (gridSize[1] * raw.position[1]) + 'px',
|
||||
width: (gridSize[0] * raw.dimensions[0]) + 'px',
|
||||
height: (gridSize[1] * raw.dimensions[1]) + 'px',
|
||||
minWidth: (gridSize[0] * raw.dimensions[0]) + 'px',
|
||||
minHeight: (gridSize[1] * raw.dimensions[1]) + 'px'
|
||||
};
|
||||
};
|
||||
|
||||
// Generate default positions for a new panel
|
||||
LayoutController.prototype.defaultDimensions = function () {
|
||||
var gridSize = this.gridSize;
|
||||
return MINIMUM_FRAME_SIZE.map(function (min, i) {
|
||||
return Math.max(
|
||||
Math.ceil(min / gridSize[i]),
|
||||
DEFAULT_DIMENSIONS[i]
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
// Generate a default position (in its raw format) for a frame.
|
||||
// Use an index to ensure that default positions are unique.
|
||||
LayoutController.prototype.defaultPosition = function (index) {
|
||||
return {
|
||||
position: [index, index],
|
||||
dimensions: this.defaultDimensions()
|
||||
};
|
||||
};
|
||||
|
||||
// Store a computed position for a contained frame by its
|
||||
// domain object id. Called in a forEach loop, so arguments
|
||||
// are as expected there.
|
||||
LayoutController.prototype.populatePosition = function (id, index) {
|
||||
this.rawPositions[id] =
|
||||
this.rawPositions[id] || this.defaultPosition(index || 0);
|
||||
this.positions[id] =
|
||||
this.convertPosition(this.rawPositions[id]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a style object for a frame with the specified domain
|
||||
* object identifier, suitable for use in an `ng-style`
|
||||
* directive to position a frame as configured for this layout.
|
||||
* @param {string} id the object identifier
|
||||
* @returns {Object.<string, string>} an object with
|
||||
* appropriate left, width, etc fields for positioning
|
||||
*/
|
||||
LayoutController.prototype.getFrameStyle = function (id) {
|
||||
// Called in a loop, so just look up; the "positions"
|
||||
// object is kept up to date by a watch.
|
||||
return this.positions[id];
|
||||
};
|
||||
|
||||
/**
|
||||
* Start a drag gesture to move/resize a frame.
|
||||
*
|
||||
* The provided position and dimensions factors will determine
|
||||
* whether this is a move or a resize, and what type it
|
||||
* will be. For instance, a position factor of [1, 1]
|
||||
* will move a frame along with the mouse as the drag
|
||||
* proceeds, while a dimension factor of [0, 0] will leave
|
||||
* dimensions unchanged. Combining these in different
|
||||
* ways results in different handles; a position factor of
|
||||
* [1, 0] and a dimensions factor of [-1, 0] will implement
|
||||
* a left-edge resize, as the horizontal position will move
|
||||
* with the mouse while the horizontal dimensions shrink in
|
||||
* kind (and vertical properties remain unmodified.)
|
||||
*
|
||||
* @param {string} id the identifier of the domain object
|
||||
* in the frame being manipulated
|
||||
* @param {number[]} posFactor the position factor
|
||||
* @param {number[]} dimFactor the dimensions factor
|
||||
*/
|
||||
LayoutController.prototype.startDrag = function (id, posFactor, dimFactor) {
|
||||
this.activeDragId = id;
|
||||
this.activeDrag = new LayoutDrag(
|
||||
this.rawPositions[id],
|
||||
posFactor,
|
||||
dimFactor,
|
||||
this.gridSize
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Continue an active drag gesture.
|
||||
* @param {number[]} delta the offset, in pixels,
|
||||
* of the current pointer position, relative
|
||||
* to its position when the drag started
|
||||
*/
|
||||
LayoutController.prototype.continueDrag = function (delta) {
|
||||
if (this.activeDrag) {
|
||||
this.rawPositions[this.activeDragId] =
|
||||
this.activeDrag.getAdjustedPosition(delta);
|
||||
this.populatePosition(this.activeDragId);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Compute panel positions based on the layout's object model.
|
||||
* Defined as member function to facilitate testing.
|
||||
* @private
|
||||
*/
|
||||
LayoutController.prototype.layoutPanels = function (ids) {
|
||||
var configuration = this.$scope.configuration || {},
|
||||
self = this;
|
||||
|
||||
// Pull panel positions from configuration
|
||||
this.rawPositions =
|
||||
shallowCopy(configuration.panels || {}, ids);
|
||||
|
||||
// Clear prior computed positions
|
||||
this.positions = {};
|
||||
|
||||
// Update width/height that we are tracking
|
||||
this.gridSize =
|
||||
(this.$scope.model || {}).layoutGrid || DEFAULT_GRID_SIZE;
|
||||
|
||||
// Compute positions and add defaults where needed
|
||||
ids.forEach(function (id, index) {
|
||||
self.populatePosition(id, index);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* End the active drag gesture. This will update the
|
||||
* view configuration.
|
||||
*/
|
||||
LayoutController.prototype.endDrag = function () {
|
||||
this.dragInProgress = true;
|
||||
|
||||
setTimeout(function () {
|
||||
this.dragInProgress = false;
|
||||
}.bind(this), 0);
|
||||
|
||||
this.endDragInScope();
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if the object is currently selected.
|
||||
*
|
||||
* @param {string} obj the object to check for selection
|
||||
* @returns {boolean} true if selected, otherwise false
|
||||
*/
|
||||
LayoutController.prototype.selected = function (obj) {
|
||||
var sobj = this.openmct.selection.get()[0];
|
||||
return (sobj && sobj.context.oldItem.getId() === obj.getId()) ? true : false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Bypasses selection if drag is in progress.
|
||||
*
|
||||
* @param event the angular event object
|
||||
*/
|
||||
LayoutController.prototype.bypassSelection = function (event) {
|
||||
if (this.dragInProgress) {
|
||||
if (event) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if the domain object is drilled in.
|
||||
*
|
||||
* @param domainObject the domain object
|
||||
* @return true if the object is drilled in, false otherwise
|
||||
*/
|
||||
LayoutController.prototype.isDrilledIn = function (domainObject) {
|
||||
return this.drilledIn === domainObject.getId();
|
||||
};
|
||||
|
||||
/**
|
||||
* Puts the given object in the drilled-in mode.
|
||||
*
|
||||
* @param event the angular event object
|
||||
* @param domainObject the domain object
|
||||
*/
|
||||
LayoutController.prototype.drill = function (event, domainObject) {
|
||||
if (event) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
if (!domainObject.getCapability('editor').inEditContext()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!domainObject.hasCapability('composition')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Disable since fixed position doesn't use the selection API yet
|
||||
if (domainObject.getModel().type === 'telemetry.fixed') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.drilledIn = domainObject.getId();
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the object has frame.
|
||||
*
|
||||
* @param {object} obj the object
|
||||
* @return {boolean} true if object has frame, otherwise false
|
||||
*/
|
||||
LayoutController.prototype.hasFrame = function (obj) {
|
||||
return this.frames[obj.getId()];
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the size of the grid, in pixels. The returned array
|
||||
* is in the form `[x, y]`.
|
||||
* @returns {number[]} the grid size
|
||||
*/
|
||||
LayoutController.prototype.getGridSize = function () {
|
||||
return this.gridSize;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the selection context.
|
||||
*
|
||||
* @param domainObject the domain object
|
||||
* @returns {object} the context object which includes item and oldItem
|
||||
*/
|
||||
LayoutController.prototype.getContext = function (domainObject) {
|
||||
return {
|
||||
item: domainObject.useCapability('adapter'),
|
||||
oldItem: domainObject
|
||||
};
|
||||
};
|
||||
|
||||
LayoutController.prototype.commit = function () {
|
||||
var model = this.$scope.model;
|
||||
model.configuration = model.configuration || {};
|
||||
model.configuration.layout = this.$scope.configuration;
|
||||
|
||||
this.$scope.domainObject.useCapability('mutation', function () {
|
||||
return model;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Selects a newly-dropped object.
|
||||
*
|
||||
* @param classSelector the css class selector
|
||||
* @param domainObject the domain object
|
||||
*/
|
||||
LayoutController.prototype.selectIfNew = function (selector, domainObject) {
|
||||
if (domainObject.getId() === this.droppedIdToSelectAfterRefresh) {
|
||||
setTimeout(function () {
|
||||
$('[data-layout-id="' + selector + '"]')[0].click();
|
||||
delete this.droppedIdToSelectAfterRefresh;
|
||||
}.bind(this), 0);
|
||||
}
|
||||
};
|
||||
|
||||
return LayoutController;
|
||||
}
|
||||
);
|
||||
|
479
platform/features/layout/test/LayoutControllerSpec.js
Normal file
479
platform/features/layout/test/LayoutControllerSpec.js
Normal file
@ -0,0 +1,479 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define(
|
||||
[
|
||||
"../src/LayoutController",
|
||||
"zepto"
|
||||
],
|
||||
function (
|
||||
LayoutController,
|
||||
$
|
||||
) {
|
||||
|
||||
describe("The Layout controller", function () {
|
||||
var mockScope,
|
||||
mockEvent,
|
||||
testModel,
|
||||
testConfiguration,
|
||||
controller,
|
||||
mockCompositionCapability,
|
||||
mockComposition,
|
||||
mockCompositionObjects,
|
||||
mockOpenMCT,
|
||||
mockSelection,
|
||||
mockDomainObjectCapability,
|
||||
mockObjects,
|
||||
unlistenFunc,
|
||||
$element = [],
|
||||
selectable = [];
|
||||
|
||||
function mockPromise(value) {
|
||||
return {
|
||||
then: function (thenFunc) {
|
||||
return mockPromise(thenFunc(value));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function mockDomainObject(id) {
|
||||
return {
|
||||
getId: function () {
|
||||
return id;
|
||||
},
|
||||
useCapability: function () {
|
||||
return mockCompositionCapability;
|
||||
},
|
||||
getModel: function () {
|
||||
if (id === 'b') {
|
||||
return {
|
||||
type : 'hyperlink'
|
||||
};
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
getCapability: function () {
|
||||
return mockDomainObjectCapability;
|
||||
},
|
||||
hasCapability: function (param) {
|
||||
if (param === 'composition') {
|
||||
return id !== 'b';
|
||||
}
|
||||
},
|
||||
type: "testType"
|
||||
};
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
mockScope = jasmine.createSpyObj(
|
||||
"$scope",
|
||||
["$watch", "$watchCollection", "$on"]
|
||||
);
|
||||
mockEvent = jasmine.createSpyObj(
|
||||
'event',
|
||||
['preventDefault', 'stopPropagation']
|
||||
);
|
||||
|
||||
testModel = {};
|
||||
|
||||
mockComposition = ["a", "b", "c"];
|
||||
mockCompositionObjects = mockComposition.map(mockDomainObject);
|
||||
|
||||
testConfiguration = {
|
||||
panels: {
|
||||
a: {
|
||||
position: [20, 10],
|
||||
dimensions: [5, 5]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
unlistenFunc = jasmine.createSpy("unlisten");
|
||||
mockDomainObjectCapability = jasmine.createSpyObj('capability',
|
||||
['inEditContext', 'listen']
|
||||
);
|
||||
mockDomainObjectCapability.listen.and.returnValue(unlistenFunc);
|
||||
|
||||
mockCompositionCapability = mockPromise(mockCompositionObjects);
|
||||
|
||||
mockScope.domainObject = mockDomainObject("mockDomainObject");
|
||||
mockScope.model = testModel;
|
||||
mockScope.configuration = testConfiguration;
|
||||
|
||||
selectable[0] = {
|
||||
context: {
|
||||
oldItem: mockScope.domainObject
|
||||
}
|
||||
};
|
||||
|
||||
mockSelection = jasmine.createSpyObj("selection", [
|
||||
'select',
|
||||
'on',
|
||||
'off',
|
||||
'get'
|
||||
]);
|
||||
mockSelection.get.and.returnValue(selectable);
|
||||
|
||||
mockObjects = jasmine.createSpyObj('objects', [
|
||||
'get'
|
||||
]);
|
||||
mockObjects.get.and.returnValue(mockPromise(mockDomainObject("mockObject")));
|
||||
mockOpenMCT = {
|
||||
selection: mockSelection,
|
||||
objects: mockObjects
|
||||
};
|
||||
|
||||
$element = $('<div></div>');
|
||||
$(document).find('body').append($element);
|
||||
spyOn($element[0], 'click');
|
||||
|
||||
spyOn(mockScope.domainObject, "useCapability").and.callThrough();
|
||||
|
||||
controller = new LayoutController(mockScope, $element, mockOpenMCT);
|
||||
spyOn(controller, "layoutPanels").and.callThrough();
|
||||
spyOn(controller, "commit");
|
||||
|
||||
jasmine.clock().install();
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
$element.remove();
|
||||
jasmine.clock().uninstall();
|
||||
});
|
||||
|
||||
|
||||
it("listens for selection change events", function () {
|
||||
expect(mockOpenMCT.selection.on).toHaveBeenCalledWith(
|
||||
'change',
|
||||
jasmine.any(Function)
|
||||
);
|
||||
});
|
||||
|
||||
it("cleans up on scope destroy", function () {
|
||||
expect(mockScope.$on).toHaveBeenCalledWith(
|
||||
'$destroy',
|
||||
jasmine.any(Function)
|
||||
);
|
||||
|
||||
mockScope.$on.calls.all()[0].args[1]();
|
||||
|
||||
expect(mockOpenMCT.selection.off).toHaveBeenCalledWith(
|
||||
'change',
|
||||
jasmine.any(Function)
|
||||
);
|
||||
});
|
||||
|
||||
// Model changes will indicate that panel positions
|
||||
// may have changed, for instance.
|
||||
it("watches for changes to composition", function () {
|
||||
expect(mockScope.$watchCollection).toHaveBeenCalledWith(
|
||||
"model.composition",
|
||||
jasmine.any(Function)
|
||||
);
|
||||
});
|
||||
|
||||
it("Retrieves updated composition from composition capability", function () {
|
||||
mockScope.$watchCollection.calls.mostRecent().args[1]();
|
||||
expect(mockScope.domainObject.useCapability).toHaveBeenCalledWith(
|
||||
"composition"
|
||||
);
|
||||
expect(controller.layoutPanels).toHaveBeenCalledWith(
|
||||
mockComposition
|
||||
);
|
||||
});
|
||||
|
||||
it("Is robust to concurrent changes to composition", function () {
|
||||
var secondMockComposition = ["a", "b", "c", "d"],
|
||||
secondMockCompositionObjects = secondMockComposition.map(mockDomainObject),
|
||||
firstCompositionCB,
|
||||
secondCompositionCB;
|
||||
|
||||
spyOn(mockCompositionCapability, "then");
|
||||
mockScope.$watchCollection.calls.mostRecent().args[1]();
|
||||
mockScope.$watchCollection.calls.mostRecent().args[1]();
|
||||
|
||||
firstCompositionCB = mockCompositionCapability.then.calls.all()[0].args[0];
|
||||
secondCompositionCB = mockCompositionCapability.then.calls.all()[1].args[0];
|
||||
|
||||
//Resolve promises in reverse order
|
||||
secondCompositionCB(secondMockCompositionObjects);
|
||||
firstCompositionCB(mockCompositionObjects);
|
||||
|
||||
//Expect the promise call that was initiated most recently to
|
||||
// be the one used to populate scope, irrespective of order that
|
||||
// it was eventually resolved
|
||||
expect(mockScope.composition).toBe(secondMockCompositionObjects);
|
||||
});
|
||||
|
||||
|
||||
it("provides styles for frames, from configuration", function () {
|
||||
mockScope.$watchCollection.calls.mostRecent().args[1]();
|
||||
expect(controller.getFrameStyle("a")).toEqual({
|
||||
top: "320px",
|
||||
left: "640px",
|
||||
width: "160px",
|
||||
height: "160px",
|
||||
minWidth : '160px',
|
||||
minHeight : '160px'
|
||||
});
|
||||
});
|
||||
|
||||
it("provides default styles for frames", function () {
|
||||
var styleB, styleC;
|
||||
|
||||
// b and c do not have configured positions
|
||||
mockScope.$watchCollection.calls.mostRecent().args[1]();
|
||||
|
||||
styleB = controller.getFrameStyle("b");
|
||||
styleC = controller.getFrameStyle("c");
|
||||
|
||||
// Should have a position, but we don't care what
|
||||
expect(styleB.left).toBeDefined();
|
||||
expect(styleB.top).toBeDefined();
|
||||
expect(styleC.left).toBeDefined();
|
||||
expect(styleC.top).toBeDefined();
|
||||
|
||||
// Should have ensured some difference in position
|
||||
expect(styleB).not.toEqual(styleC);
|
||||
});
|
||||
|
||||
it("allows panels to be dragged", function () {
|
||||
// Populate scope
|
||||
mockScope.$watchCollection.calls.mostRecent().args[1]();
|
||||
|
||||
// Verify precondition
|
||||
expect(testConfiguration.panels.b).not.toBeDefined();
|
||||
|
||||
// Do a drag
|
||||
controller.startDrag("b", [1, 1], [0, 0]);
|
||||
controller.continueDrag([100, 100]);
|
||||
controller.endDrag();
|
||||
|
||||
// We do not look closely at the details here;
|
||||
// that is tested in LayoutDragSpec. Just make sure
|
||||
// that a configuration for b has been defined.
|
||||
expect(testConfiguration.panels.b).toBeDefined();
|
||||
});
|
||||
|
||||
|
||||
it("invokes commit after drag", function () {
|
||||
// Populate scope
|
||||
mockScope.$watchCollection.calls.mostRecent().args[1]();
|
||||
|
||||
// Do a drag
|
||||
controller.startDrag("b", [1, 1], [0, 0]);
|
||||
controller.continueDrag([100, 100]);
|
||||
controller.endDrag();
|
||||
|
||||
expect(controller.commit).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("listens for drop events", function () {
|
||||
// Layout should position panels according to
|
||||
// where the user dropped them, so it needs to
|
||||
// listen for drop events.
|
||||
expect(mockScope.$on).toHaveBeenCalledWith(
|
||||
'mctDrop',
|
||||
jasmine.any(Function)
|
||||
);
|
||||
|
||||
// Verify precondition
|
||||
expect(testConfiguration.panels.d).not.toBeDefined();
|
||||
|
||||
// Notify that a drop occurred
|
||||
mockScope.$on.calls.mostRecent().args[1](
|
||||
mockEvent,
|
||||
'd',
|
||||
{ x: 300, y: 100 }
|
||||
);
|
||||
expect(testConfiguration.panels.d).toBeDefined();
|
||||
expect(mockEvent.preventDefault).toHaveBeenCalled();
|
||||
expect(controller.commit).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("ignores drops when default has been prevented", function () {
|
||||
// Avoids redundant drop-handling, WTD-1233
|
||||
mockEvent.defaultPrevented = true;
|
||||
|
||||
// Notify that a drop occurred
|
||||
mockScope.$on.calls.mostRecent().args[1](
|
||||
mockEvent,
|
||||
'd',
|
||||
{ x: 300, y: 100 }
|
||||
);
|
||||
expect(testConfiguration.panels.d).not.toBeDefined();
|
||||
});
|
||||
|
||||
it("ensures a minimum frame size", function () {
|
||||
var styleB;
|
||||
|
||||
// Start with a very small frame size
|
||||
testModel.layoutGrid = [1, 1];
|
||||
|
||||
// White-boxy; we know which watch is which
|
||||
mockScope.$watch.calls.all()[0].args[1](testModel.layoutGrid);
|
||||
mockScope.$watchCollection.calls.all()[0].args[1](testModel.composition);
|
||||
|
||||
styleB = controller.getFrameStyle("b");
|
||||
|
||||
// Resulting size should still be reasonably large pixel-size
|
||||
expect(parseInt(styleB.width, 10)).toBeGreaterThan(63);
|
||||
expect(parseInt(styleB.width, 10)).toBeGreaterThan(31);
|
||||
});
|
||||
|
||||
it("ensures a minimum frame size on drop", function () {
|
||||
var style;
|
||||
|
||||
// Start with a very small frame size
|
||||
testModel.layoutGrid = [1, 1];
|
||||
mockScope.$watch.calls.all()[0].args[1](testModel.layoutGrid);
|
||||
|
||||
// Add a new object to the composition
|
||||
mockComposition = ["a", "b", "c", "d"];
|
||||
mockCompositionObjects = mockComposition.map(mockDomainObject);
|
||||
mockCompositionCapability = mockPromise(mockCompositionObjects);
|
||||
|
||||
// Notify that a drop occurred
|
||||
mockScope.$on.calls.mostRecent().args[1](
|
||||
mockEvent,
|
||||
'd',
|
||||
{ x: 300, y: 100 }
|
||||
);
|
||||
|
||||
style = controller.getFrameStyle("d");
|
||||
|
||||
// Resulting size should still be reasonably large pixel-size
|
||||
expect(parseInt(style.width, 10)).toBeGreaterThan(63);
|
||||
expect(parseInt(style.height, 10)).toBeGreaterThan(31);
|
||||
});
|
||||
|
||||
it("updates positions of existing objects on a drop", function () {
|
||||
var oldStyle;
|
||||
|
||||
mockScope.$watchCollection.calls.mostRecent().args[1]();
|
||||
|
||||
oldStyle = controller.getFrameStyle("b");
|
||||
|
||||
expect(oldStyle).toBeDefined();
|
||||
|
||||
// ...drop event...
|
||||
mockScope.$on.calls.mostRecent()
|
||||
.args[1](mockEvent, 'b', { x: 300, y: 100 });
|
||||
|
||||
expect(controller.getFrameStyle("b"))
|
||||
.not.toEqual(oldStyle);
|
||||
});
|
||||
|
||||
it("allows objects to be selected", function () {
|
||||
mockScope.$watchCollection.calls.mostRecent().args[1]();
|
||||
var childObj = mockCompositionObjects[0];
|
||||
selectable[0].context.oldItem = childObj;
|
||||
mockOpenMCT.selection.on.calls.mostRecent().args[1](selectable);
|
||||
|
||||
expect(controller.selected(childObj)).toBe(true);
|
||||
});
|
||||
|
||||
it("prevents event bubbling while drag is in progress", function () {
|
||||
mockScope.$watchCollection.calls.mostRecent().args[1]();
|
||||
var childObj = mockCompositionObjects[0];
|
||||
|
||||
// Do a drag
|
||||
controller.startDrag(childObj.getId(), [1, 1], [0, 0]);
|
||||
controller.continueDrag([100, 100]);
|
||||
controller.endDrag();
|
||||
|
||||
// Because mouse position could cause the parent object to be selected, this should be ignored.
|
||||
controller.bypassSelection(mockEvent);
|
||||
|
||||
expect(mockEvent.stopPropagation).toHaveBeenCalled();
|
||||
|
||||
// Shoud be able to select another object when dragging is done.
|
||||
jasmine.clock().tick(0);
|
||||
mockEvent.stopPropagation.calls.reset();
|
||||
controller.bypassSelection(mockEvent);
|
||||
|
||||
expect(mockEvent.stopPropagation).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("shows frames by default", function () {
|
||||
mockScope.$watchCollection.calls.mostRecent().args[1]();
|
||||
|
||||
expect(controller.hasFrame(mockCompositionObjects[0])).toBe(true);
|
||||
});
|
||||
|
||||
it("hyperlinks hide frame by default", function () {
|
||||
mockScope.$watchCollection.calls.mostRecent().args[1]();
|
||||
|
||||
expect(controller.hasFrame(mockCompositionObjects[1])).toBe(false);
|
||||
});
|
||||
|
||||
it("selects the parent object when selected object is removed", function () {
|
||||
mockScope.$watchCollection.calls.mostRecent().args[1]();
|
||||
var childObj = mockCompositionObjects[0];
|
||||
selectable[0].context.oldItem = childObj;
|
||||
mockOpenMCT.selection.on.calls.mostRecent().args[1](selectable);
|
||||
|
||||
var composition = ["b", "c"];
|
||||
mockScope.$watchCollection.calls.mostRecent().args[1](composition);
|
||||
|
||||
expect($element[0].click).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("allows objects to be drilled-in only when editing", function () {
|
||||
mockScope.$watchCollection.calls.mostRecent().args[1]();
|
||||
var childObj = mockCompositionObjects[0];
|
||||
childObj.getCapability().inEditContext.and.returnValue(false);
|
||||
controller.drill(mockEvent, childObj);
|
||||
|
||||
expect(controller.isDrilledIn(childObj)).toBe(false);
|
||||
});
|
||||
|
||||
it("allows objects to be drilled-in only if it has sub objects", function () {
|
||||
mockScope.$watchCollection.calls.mostRecent().args[1]();
|
||||
var childObj = mockCompositionObjects[1];
|
||||
childObj.getCapability().inEditContext.and.returnValue(true);
|
||||
controller.drill(mockEvent, childObj);
|
||||
|
||||
expect(controller.isDrilledIn(childObj)).toBe(false);
|
||||
});
|
||||
|
||||
it("selects a newly-dropped object", function () {
|
||||
mockScope.$on.calls.mostRecent().args[1](
|
||||
mockEvent,
|
||||
'd',
|
||||
{ x: 300, y: 100 }
|
||||
);
|
||||
|
||||
var childObj = mockDomainObject("d");
|
||||
var testElement = $("<div data-layout-id='some-id'></div>");
|
||||
$element.append(testElement);
|
||||
spyOn(testElement[0], 'click');
|
||||
|
||||
controller.selectIfNew('some-id', childObj);
|
||||
jasmine.clock().tick(0);
|
||||
|
||||
expect(testElement[0].click).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
@ -20,47 +20,45 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
define([
|
||||
'vue',
|
||||
'../../res/templates/viewSnapshot.html'
|
||||
'./src/controllers/ListViewController',
|
||||
'./src/directives/MCTGesture',
|
||||
'./res/templates/listview.html',
|
||||
'legacyRegistry'
|
||||
], function (
|
||||
Vue,
|
||||
snapshotOverlayTemplate
|
||||
ListViewController,
|
||||
MCTGesture,
|
||||
listViewTemplate,
|
||||
legacyRegistry
|
||||
) {
|
||||
function SnapshotOverlay (embedObject, formatTime) {
|
||||
this.embedObject = embedObject;
|
||||
|
||||
this.snapshotOverlayVue = new Vue({
|
||||
template: snapshotOverlayTemplate,
|
||||
data: function () {
|
||||
return {
|
||||
embed: embedObject
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
close: this.close.bind(this),
|
||||
formatTime: formatTime
|
||||
}
|
||||
});
|
||||
|
||||
this.open();
|
||||
}
|
||||
|
||||
SnapshotOverlay.prototype.open = function () {
|
||||
this.overlay = document.createElement('div');
|
||||
this.overlay.classList.add('abs');
|
||||
|
||||
document.body.appendChild(this.overlay);
|
||||
|
||||
this.overlay.appendChild(this.snapshotOverlayVue.$mount().$el);
|
||||
};
|
||||
|
||||
SnapshotOverlay.prototype.close = function (event) {
|
||||
event.stopPropagation();
|
||||
this.snapshotOverlayVue.$destroy();
|
||||
this.overlay.parentNode.removeChild(this.overlay);
|
||||
};
|
||||
|
||||
return SnapshotOverlay;
|
||||
legacyRegistry.register("platform/features/listview", {
|
||||
"name": "List View Plugin",
|
||||
"description": "Allows folder contents to be shown in list format",
|
||||
"extensions":
|
||||
{
|
||||
"views": [
|
||||
{
|
||||
"key": "list",
|
||||
"type": "folder",
|
||||
"name": "List",
|
||||
"cssClass": "icon-list-view",
|
||||
"template": listViewTemplate
|
||||
}
|
||||
],
|
||||
"controllers": [
|
||||
{
|
||||
"key": "ListViewController",
|
||||
"implementation": ListViewController,
|
||||
"depends": ["$scope", "formatService"]
|
||||
}
|
||||
],
|
||||
"directives": [
|
||||
{
|
||||
"key": "mctGesture",
|
||||
"implementation" : MCTGesture,
|
||||
"depends": ["gestureService"]
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
});
|
88
platform/features/listview/res/templates/listview.html
Normal file
88
platform/features/listview/res/templates/listview.html
Normal file
@ -0,0 +1,88 @@
|
||||
<!--
|
||||
Open MCT, Copyright (c) 2014-2018, United States Government
|
||||
as represented by the Administrator of the National Aeronautics and Space
|
||||
Administration. All rights reserved.
|
||||
|
||||
Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0.
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
License for the specific language governing permissions and limitations
|
||||
under the License.
|
||||
|
||||
Open MCT includes source code licensed under additional open source
|
||||
licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
this source code distribution or the Licensing information page available
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
<div ng-controller="ListViewController">
|
||||
<table class="list-view">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="sortable"
|
||||
ng-click="reverseSort = (orderByField === 'title')? !reverseSort:false; orderByField='title';"
|
||||
ng-class="{
|
||||
sort: orderByField == 'title',
|
||||
asc: !reverseSort,
|
||||
desc: reverseSort
|
||||
}">
|
||||
Name
|
||||
</th>
|
||||
|
||||
|
||||
<th class="sortable"
|
||||
ng-click="reverseSort = (orderByField === 'type')? !reverseSort:false; orderByField='type';"
|
||||
ng-class="{
|
||||
sort: orderByField == 'type',
|
||||
asc: !reverseSort,
|
||||
desc: reverseSort
|
||||
}">
|
||||
Type
|
||||
</th>
|
||||
|
||||
<th class="sortable"
|
||||
ng-click="reverseSort = (orderByField === 'persisted')? !reverseSort:true; orderByField='persisted';"
|
||||
ng-class="{
|
||||
sort: orderByField == 'persisted',
|
||||
asc: !reverseSort,
|
||||
desc: reverseSort
|
||||
}">
|
||||
Created Date
|
||||
</th>
|
||||
|
||||
<th class="sortable"
|
||||
ng-click="reverseSort = (orderByField === 'modified')? !reverseSort:true; orderByField='modified';"
|
||||
ng-class="{
|
||||
sort: orderByField == 'modified',
|
||||
asc: !reverseSort,
|
||||
desc: reverseSort
|
||||
}">
|
||||
Update Date
|
||||
</th>
|
||||
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="child in children|orderBy:orderByField:reverseSort"
|
||||
mct-gesture="['menu','info']"
|
||||
mct-object="child.asDomainObject"
|
||||
ng-click="child.action.perform('navigate')">
|
||||
<td>
|
||||
<div class="l-flex-row">
|
||||
<span class="flex-elem t-item-icon" ng-class="{ 'l-icon-link': child.location.isLink()}">
|
||||
<span class="t-item-icon-glyph {{child.icon}}"></span>
|
||||
</span>
|
||||
<span class="t-title-label flex-elem grows">{{child.title}}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>{{child.type}}</td>
|
||||
<td>{{child.persisted}}</td>
|
||||
<td>{{child.modified}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
@ -0,0 +1,70 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define(function () {
|
||||
function ListViewController($scope, formatService) {
|
||||
this.$scope = $scope;
|
||||
$scope.orderByField = 'title';
|
||||
$scope.reverseSort = false;
|
||||
|
||||
this.updateView();
|
||||
var unlisten = $scope.domainObject.getCapability('mutation')
|
||||
.listen(this.updateView.bind(this));
|
||||
|
||||
this.utc = formatService.getFormat('utc');
|
||||
|
||||
//Trigger digestive cycle with $apply to update list view
|
||||
setTimeout(function () {
|
||||
$scope.$apply();
|
||||
});
|
||||
|
||||
$scope.$on('$destroy', function () {
|
||||
unlisten();
|
||||
});
|
||||
|
||||
}
|
||||
ListViewController.prototype.updateView = function () {
|
||||
this.$scope.domainObject.useCapability('composition')
|
||||
.then(function (children) {
|
||||
var formattedChildren = this.formatChildren(children);
|
||||
this.$scope.children = formattedChildren;
|
||||
this.$scope.data = {children: formattedChildren};
|
||||
}.bind(this)
|
||||
);
|
||||
};
|
||||
ListViewController.prototype.formatChildren = function (children) {
|
||||
return children.map(function (child) {
|
||||
return {
|
||||
icon: child.getCapability('type').getCssClass(),
|
||||
title: child.getModel().name,
|
||||
type: child.getCapability('type').getName(),
|
||||
persisted: this.utc.format(child.getModel().persisted),
|
||||
modified: this.utc.format(child.getModel().modified),
|
||||
asDomainObject: child,
|
||||
location: child.getCapability('location'),
|
||||
action: child.getCapability('action')
|
||||
};
|
||||
}, this);
|
||||
};
|
||||
|
||||
return ListViewController;
|
||||
});
|
@ -21,21 +21,24 @@
|
||||
*****************************************************************************/
|
||||
|
||||
define(function () {
|
||||
function DisplayLayoutType() {
|
||||
function MCTGesture(gestureService) {
|
||||
return {
|
||||
name: "Display Layout",
|
||||
creatable: true,
|
||||
cssClass: 'icon-layout',
|
||||
initialize(domainObject) {
|
||||
domainObject.composition = [];
|
||||
domainObject.configuration = {
|
||||
layout: {
|
||||
panels: {}
|
||||
}
|
||||
};
|
||||
restrict : 'A',
|
||||
scope: {
|
||||
domainObject: '=mctObject'
|
||||
},
|
||||
link : function ($scope, $element, attrs) {
|
||||
var activeGestures = gestureService.attachGestures(
|
||||
$element,
|
||||
$scope.domainObject,
|
||||
$scope.$eval(attrs.mctGesture)
|
||||
);
|
||||
$scope.$on('$destroy', function () {
|
||||
activeGestures.destroy();
|
||||
delete this.activeGestures;
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return DisplayLayoutType;
|
||||
});
|
||||
return MCTGesture;
|
||||
});
|
@ -0,0 +1,157 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define(
|
||||
["../../src/controllers/ListViewController"],
|
||||
function (ListViewController) {
|
||||
describe("The Controller for the ListView", function () {
|
||||
var scope,
|
||||
unlistenFunc,
|
||||
domainObject,
|
||||
childObject,
|
||||
childModel,
|
||||
typeCapability,
|
||||
mutationCapability,
|
||||
formatService,
|
||||
compositionPromise,
|
||||
controller;
|
||||
|
||||
beforeEach(function () {
|
||||
unlistenFunc = jasmine.createSpy("unlisten");
|
||||
|
||||
mutationCapability = jasmine.createSpyObj(
|
||||
"mutationCapability",
|
||||
["listen"]
|
||||
);
|
||||
mutationCapability.listen.and.returnValue(unlistenFunc);
|
||||
|
||||
formatService = jasmine.createSpyObj(
|
||||
"formatService",
|
||||
["getFormat"]
|
||||
);
|
||||
formatService.getFormat.and.returnValue(jasmine.createSpyObj(
|
||||
'utc',
|
||||
["format"]
|
||||
));
|
||||
formatService.getFormat().format.and.callFake(function (v) {
|
||||
return "formatted " + v;
|
||||
});
|
||||
|
||||
typeCapability = jasmine.createSpyObj(
|
||||
"typeCapability",
|
||||
["getCssClass", "getName"]
|
||||
);
|
||||
typeCapability.getCssClass.and.returnValue("icon-folder");
|
||||
typeCapability.getName.and.returnValue("Folder");
|
||||
|
||||
|
||||
childModel = jasmine.createSpyObj(
|
||||
"childModel",
|
||||
["persisted", "modified", "name"]
|
||||
);
|
||||
childModel.persisted = 1496867697303;
|
||||
childModel.modified = 1496867697303;
|
||||
childModel.name = "Battery Charge Status";
|
||||
|
||||
childObject = jasmine.createSpyObj(
|
||||
"childObject",
|
||||
["getModel", "getCapability"]
|
||||
);
|
||||
childObject.getModel.and.returnValue(
|
||||
childModel
|
||||
);
|
||||
|
||||
childObject.getCapability.and.callFake(function (arg) {
|
||||
if (arg === 'location') {
|
||||
return '';
|
||||
} else if (arg === 'type') {
|
||||
return typeCapability;
|
||||
}
|
||||
});
|
||||
childObject.location = '';
|
||||
|
||||
domainObject = jasmine.createSpyObj(
|
||||
"domainObject",
|
||||
["getCapability", "useCapability"]
|
||||
);
|
||||
compositionPromise = Promise.resolve([childObject]);
|
||||
domainObject.useCapability.and.returnValue(compositionPromise);
|
||||
domainObject.getCapability.and.returnValue(
|
||||
mutationCapability
|
||||
);
|
||||
|
||||
scope = jasmine.createSpyObj(
|
||||
"$scope",
|
||||
["$on", "$apply"]
|
||||
);
|
||||
scope.domainObject = domainObject;
|
||||
|
||||
controller = new ListViewController(scope, formatService);
|
||||
|
||||
return compositionPromise;
|
||||
});
|
||||
|
||||
it("uses the UTC time format", function () {
|
||||
expect(formatService.getFormat).toHaveBeenCalledWith('utc');
|
||||
});
|
||||
|
||||
it("updates the view", function () {
|
||||
var child = scope.children[0];
|
||||
var testChild = {
|
||||
icon: "icon-folder",
|
||||
title: "Battery Charge Status",
|
||||
type: "Folder",
|
||||
persisted: formatService.getFormat('utc')
|
||||
.format(childModel.persisted),
|
||||
modified: formatService.getFormat('utc')
|
||||
.format(childModel.modified),
|
||||
asDomainObject: childObject,
|
||||
location: '',
|
||||
action: childObject.getCapability('action')
|
||||
};
|
||||
|
||||
expect(child).toEqual(testChild);
|
||||
});
|
||||
it("updates the scope when mutation occurs", function () {
|
||||
var applyPromise = new Promise(function (resolve) {
|
||||
scope.$apply.and.callFake(resolve);
|
||||
});
|
||||
|
||||
domainObject.useCapability.and.returnValue(Promise.resolve([]));
|
||||
expect(mutationCapability.listen).toHaveBeenCalledWith(jasmine.any(Function));
|
||||
mutationCapability.listen.calls.mostRecent().args[0]();
|
||||
|
||||
return applyPromise.then(function () {
|
||||
expect(scope.children.length).toEqual(0);
|
||||
expect(scope.$apply).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
it("releases listeners on $destroy", function () {
|
||||
expect(scope.$on).toHaveBeenCalledWith('$destroy', jasmine.any(Function));
|
||||
scope.$on.calls.mostRecent().args[1]();
|
||||
expect(unlistenFunc).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
);
|
86
platform/features/listview/test/directives/MCTGestureSpec.js
Normal file
86
platform/features/listview/test/directives/MCTGestureSpec.js
Normal file
@ -0,0 +1,86 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define(
|
||||
["../../src/directives/MCTGesture"],
|
||||
function (MCTGesture) {
|
||||
describe("The Gesture Listener for the ListView items", function () {
|
||||
var mctGesture,
|
||||
gestureService,
|
||||
scope,
|
||||
element,
|
||||
attrs,
|
||||
attachedGesture;
|
||||
beforeEach(function () {
|
||||
attachedGesture = jasmine.createSpyObj(
|
||||
"attachedGesture",
|
||||
['destroy']
|
||||
);
|
||||
gestureService = jasmine.createSpyObj(
|
||||
"gestureService",
|
||||
["attachGestures"]
|
||||
);
|
||||
gestureService.attachGestures.and.returnValue(
|
||||
attachedGesture
|
||||
);
|
||||
mctGesture = MCTGesture(gestureService);
|
||||
});
|
||||
it("creates a directive Object", function () {
|
||||
expect(mctGesture).toBeDefined();
|
||||
});
|
||||
it("has link function that attaches gesture to gestureService",
|
||||
function () {
|
||||
attrs = {
|
||||
mctGesture: "menu,info"
|
||||
};
|
||||
element = jasmine.createSpy("element");
|
||||
scope = jasmine.createSpyObj(
|
||||
"$scope",
|
||||
["$on", "$eval"]
|
||||
);
|
||||
scope.domainObject = "fake domainObject";
|
||||
mctGesture.link(scope, element, attrs);
|
||||
expect(gestureService.attachGestures).toHaveBeenCalled();
|
||||
}
|
||||
);
|
||||
it("release gesture service on $destroy", function () {
|
||||
attrs = {
|
||||
mctGesture: "menu,info"
|
||||
};
|
||||
element = jasmine.createSpy("element");
|
||||
scope = jasmine.createSpyObj(
|
||||
"$scope",
|
||||
["$on", "$eval"]
|
||||
);
|
||||
scope.domainObject = "fake domainObject";
|
||||
mctGesture.link(scope, element, attrs);
|
||||
expect(scope.$on).toHaveBeenCalledWith(
|
||||
'$destroy',
|
||||
jasmine.any(Function)
|
||||
);
|
||||
scope.$on.calls.mostRecent().args[1]();
|
||||
expect(attachedGesture.destroy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
294
platform/features/notebook/bundle.js
Normal file
294
platform/features/notebook/bundle.js
Normal file
@ -0,0 +1,294 @@
|
||||
define([
|
||||
"legacyRegistry",
|
||||
"./src/controllers/NotebookController",
|
||||
"./src/controllers/NewEntryController",
|
||||
"./src/controllers/SelectSnapshotController",
|
||||
"./src/controllers/LayoutNotebookController",
|
||||
"./src/directives/MCTSnapshot",
|
||||
"./src/directives/EntryDnd",
|
||||
"./src/actions/ViewSnapshot",
|
||||
"./src/actions/AnnotateSnapshot",
|
||||
"./src/actions/RemoveEmbed",
|
||||
"./src/actions/RemoveSnapshot",
|
||||
"./src/actions/NewEntryContextual",
|
||||
"./src/capabilities/NotebookCapability",
|
||||
"./src/policies/CompositionPolicy",
|
||||
"./res/templates/notebook.html",
|
||||
"./res/templates/entry.html",
|
||||
"./res/templates/annotation.html",
|
||||
"./res/templates/notifications.html",
|
||||
"../layout/res/templates/frame.html",
|
||||
"./res/templates/controls/embedControl.html",
|
||||
"./res/templates/controls/snapSelect.html"
|
||||
], function (
|
||||
legacyRegistry,
|
||||
NotebookController,
|
||||
NewEntryController,
|
||||
SelectSnapshotController,
|
||||
LayoutNotebookController,
|
||||
MCTSnapshot,
|
||||
MCTEntryDnd,
|
||||
ViewSnapshotAction,
|
||||
AnnotateSnapshotAction,
|
||||
RemoveEmbedAction,
|
||||
RemoveSnapshotAction,
|
||||
newEntryAction,
|
||||
NotebookCapability,
|
||||
CompositionPolicy,
|
||||
notebookTemplate,
|
||||
entryTemplate,
|
||||
annotationTemplate,
|
||||
notificationsTemplate,
|
||||
frameTemplate,
|
||||
embedControlTemplate,
|
||||
snapSelectTemplate
|
||||
) {
|
||||
legacyRegistry.register("platform/features/notebook", {
|
||||
"name": "Notebook Plugin",
|
||||
"description": "Create and save timestamped notes with embedded object snapshots.",
|
||||
"extensions":
|
||||
{
|
||||
"types": [
|
||||
{
|
||||
"key": "notebook",
|
||||
"name": "Notebook",
|
||||
"cssClass": "icon-notebook",
|
||||
"description": "Create and save timestamped notes with embedded object snapshots.",
|
||||
"features": ["creation"],
|
||||
"model": {
|
||||
"entries": [],
|
||||
"composition": [],
|
||||
"entryTypes": [],
|
||||
"defaultSort": "-createdOn"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"key": "defaultSort",
|
||||
"name": "Default Sort",
|
||||
"control": "select",
|
||||
"options": [
|
||||
{
|
||||
"name": "Newest First",
|
||||
"value": "-createdOn"
|
||||
},
|
||||
{
|
||||
"name": "Oldest First",
|
||||
"value": "createdOn"
|
||||
}
|
||||
],
|
||||
"cssClass": "l-inline"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"views": [
|
||||
{
|
||||
"key": "notebook.view",
|
||||
"type": "notebook",
|
||||
"cssClass": "icon-notebook",
|
||||
"name": "notebook",
|
||||
"template": notebookTemplate,
|
||||
"editable": false,
|
||||
"uses": [
|
||||
"composition",
|
||||
"action"
|
||||
],
|
||||
"gestures": [
|
||||
"drop"
|
||||
]
|
||||
}
|
||||
],
|
||||
"controllers": [
|
||||
{
|
||||
"key": "NotebookController",
|
||||
"implementation": NotebookController,
|
||||
"depends": [
|
||||
"$scope",
|
||||
"dialogService",
|
||||
"popupService",
|
||||
"agentService",
|
||||
"objectService",
|
||||
"navigationService",
|
||||
"now",
|
||||
"actionService",
|
||||
"$timeout",
|
||||
"$element",
|
||||
"$sce"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "NewEntryController",
|
||||
"implementation": NewEntryController,
|
||||
"depends": ["$scope",
|
||||
"$rootScope"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "selectSnapshotController",
|
||||
"implementation": SelectSnapshotController,
|
||||
"depends": ["$scope",
|
||||
"$rootScope"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "LayoutNotebookController",
|
||||
"implementation": LayoutNotebookController,
|
||||
"depends": ["$scope"]
|
||||
}
|
||||
],
|
||||
"representations": [
|
||||
{
|
||||
"key": "draggedEntry",
|
||||
"template": entryTemplate
|
||||
},
|
||||
{
|
||||
"key": "frameLayoutNotebook",
|
||||
"template": frameTemplate
|
||||
}
|
||||
],
|
||||
"templates": [
|
||||
{
|
||||
"key": "annotate-snapshot",
|
||||
"template": annotationTemplate
|
||||
},
|
||||
{
|
||||
"key": "notificationTemplate",
|
||||
"template": notificationsTemplate
|
||||
}
|
||||
],
|
||||
"directives": [
|
||||
{
|
||||
"key": "mctSnapshot",
|
||||
"implementation": MCTSnapshot,
|
||||
"depends": [
|
||||
"$rootScope",
|
||||
"$document",
|
||||
"exportImageService",
|
||||
"dialogService",
|
||||
"notificationService"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "mctEntryDnd",
|
||||
"implementation": MCTEntryDnd,
|
||||
"depends": [
|
||||
"$rootScope",
|
||||
"$compile",
|
||||
"dndService",
|
||||
"typeService",
|
||||
"notificationService"
|
||||
]
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"key": "view-snapshot",
|
||||
"implementation": ViewSnapshotAction,
|
||||
"name": "View Snapshot",
|
||||
"description": "View the large image in a modal",
|
||||
"category": "embed",
|
||||
"depends": [
|
||||
"$compile"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "annotate-snapshot",
|
||||
"implementation": AnnotateSnapshotAction,
|
||||
"name": "Annotate Snapshot",
|
||||
"cssClass": "icon-pencil labeled",
|
||||
"description": "Annotate embed's snapshot",
|
||||
"category": "embed",
|
||||
"depends": [
|
||||
"dialogService",
|
||||
"dndService",
|
||||
"$rootScope"
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
"key": "remove-embed",
|
||||
"implementation": RemoveEmbedAction,
|
||||
"name": "Remove...",
|
||||
"cssClass": "icon-trash labeled",
|
||||
"description": "Remove this embed",
|
||||
"category": [
|
||||
"embed",
|
||||
"embed-no-snap"
|
||||
],
|
||||
"depends": [
|
||||
"dialogService"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "remove-snapshot",
|
||||
"implementation": RemoveSnapshotAction,
|
||||
"name": "Remove Snapshot",
|
||||
"cssClass": "icon-trash labeled",
|
||||
"description": "Remove Snapshot of the embed",
|
||||
"category": "embed",
|
||||
"depends": [
|
||||
"dialogService"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "notebook-new-entry",
|
||||
"implementation": newEntryAction,
|
||||
"name": "New Notebook Entry",
|
||||
"cssClass": "icon-notebook labeled",
|
||||
"description": "Add a new Notebook entry",
|
||||
"category": [
|
||||
"view-control"
|
||||
],
|
||||
"depends": [
|
||||
"$compile",
|
||||
"$rootScope",
|
||||
"dialogService",
|
||||
"notificationService",
|
||||
"linkService"
|
||||
],
|
||||
"priority": "preferred"
|
||||
}
|
||||
],
|
||||
"licenses": [
|
||||
{
|
||||
"name": "painterro",
|
||||
"version": "0.2.65",
|
||||
"author": "Ivan Borshchov",
|
||||
"description": "Painterro is JavaScript paint widget which allows editing images directly in a browser.",
|
||||
"website": "https://github.com/ivictbor/painterro",
|
||||
"copyright": "Copyright 2017 Ivan Borshchov",
|
||||
"license": "MIT",
|
||||
"link": "https://github.com/ivictbor/painterro/blob/master/LICENSE"
|
||||
}
|
||||
],
|
||||
"capabilities": [
|
||||
{
|
||||
"key": "notebook",
|
||||
"name": "Notebook Capability",
|
||||
"description": "Provides a capability for looking for a notebook domain object",
|
||||
"implementation": NotebookCapability,
|
||||
"depends": [
|
||||
"typeService"
|
||||
]
|
||||
}
|
||||
],
|
||||
"policies": [
|
||||
{
|
||||
"category": "composition",
|
||||
"implementation": CompositionPolicy,
|
||||
"message": "Objects of this type cannot contain objects of that type."
|
||||
}
|
||||
],
|
||||
"controls": [
|
||||
{
|
||||
"key": "embed-control",
|
||||
"template": embedControlTemplate
|
||||
},
|
||||
{
|
||||
"key": "snapshot-select",
|
||||
"template": snapSelectTemplate
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
});
|
@ -25,5 +25,6 @@
|
||||
ng-options="opt.value as opt.name for opt in options"
|
||||
ng-required="ngRequired"
|
||||
name="mctControl">
|
||||
<!-- <option value="" ng-show="!ngModel[field]">- Select One -</option> -->
|
||||
</select>
|
||||
</div>
|
118
platform/features/notebook/res/templates/notebook.html
Normal file
118
platform/features/notebook/res/templates/notebook.html
Normal file
@ -0,0 +1,118 @@
|
||||
<div ng-controller="NotebookController as controller" class="mct-notebook w-notebook l-flex-col">
|
||||
<div class="l-notebook-head holder l-flex-row flex-elem">
|
||||
<div class="c-search flex-elem holder grows">
|
||||
<input class="c-search__search-input"
|
||||
type="text" tabindex="10000"
|
||||
ng-model="entrySearch"
|
||||
ng-keyup="controller.search()"/>
|
||||
<a class="c-search__clear-input clear-icon icon-x-in-circle"
|
||||
ng-class="{show: !(entrySearch === '' || entrySearch === undefined)}"
|
||||
ng-click="entrySearch = ''; controller.search()"></a>
|
||||
</div>
|
||||
<div class="notebook-view-controls l-flex-row flex-elem holder">
|
||||
<div class="select notebook-view-controls__filter-time">
|
||||
<select ng-model="showTime">
|
||||
<option value="0" selected="selected">Show all</option>
|
||||
<option value="1">Last hour</option>
|
||||
<option value="8">Last 8 hours</option>
|
||||
<option value="24">Last 24 hours</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="select notebook-view-controls__sort">
|
||||
<select ng-model="sortEntries">
|
||||
<option value="-createdOn" selected="selected">Newest first</option>
|
||||
<option value="createdOn">Oldest first</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- drag area -->
|
||||
<div class="holder flex-elem l-flex-row icon-plus labeled l-notebook-drag-area" ng-click="newEntry($event)"
|
||||
id="newEntry" mct-entry-dnd>
|
||||
<span class="label">To start a new entry, click here or drag and drop any object</span>
|
||||
</div>
|
||||
|
||||
<!-- entries -->
|
||||
<div class="holder flex-elem grows w-notebook-entries t-entries-list" ng-mouseover="handleActive()">
|
||||
<ul>
|
||||
<li class="l-flex-row has-local-controls l-notebook-entry s-notebook-entry"
|
||||
id="{{'entry_'+ entry.id}}"
|
||||
ng-if="hoursFilter(showTime,entry.createdOn)"
|
||||
ng-repeat="entry in model.entries | filter:entrySearch | orderBy: sortEntries track by $index"
|
||||
ng-init="$last && finished(model.entries)"
|
||||
mct-entry-dnd>
|
||||
<div class="holder flex-elem l-flex-row grows w-notebook-entry-time-and-content">
|
||||
<div class="holder flex-elem s-notebook-entry-time">
|
||||
<span>{{entry.createdOn | date:'yyyy-MM-dd'}}</span>
|
||||
<span>{{entry.createdOn | date:'HH:mm:ss'}}</span>
|
||||
</div>
|
||||
<div class="holder flex-elem l-flex-col grows l-notebook-entry-content">
|
||||
<div contenteditable="true"
|
||||
ng-blur="textBlur($event, entry.id)"
|
||||
ng-focus="textFocus($event, entry.id)"
|
||||
ng-model="entry.text"
|
||||
placeholder="Enter text here"
|
||||
class="flex-elem s-input-inline t-notebook-entry-input s-notebook-entry-text"
|
||||
ng-bind="entry.text">
|
||||
</div>
|
||||
<!-- embeds -->
|
||||
<div class="flex-elem entry-embeds l-flex-row">
|
||||
<div class="l-flex-row l-entry-embed {{embed.cssClass}}"
|
||||
ng-repeat="embed in entry.embeds track by $index"
|
||||
ng-class="{ 'has-snapshot' : embed.snapshot }"
|
||||
id="{{embed.id}}">
|
||||
<div class="snap-thumb"
|
||||
ng-if="embed.snapshot"
|
||||
ng-click="viewSnapshot($event,embed.snapshot.src,embed.id,entry.createdOn,this,embed)">
|
||||
<img ng-src="{{embed.snapshot.src}}" src="//:0" alt="{{embed.id}}">
|
||||
</div>
|
||||
<div class="embed-info l-flex-col">
|
||||
<div class="embed-title object-header">
|
||||
<a ng-click='navigate($event,embed.type)'>{{embed.name}}</a>
|
||||
<a class='context-available' ng-click='openMenu($event,embed.type)'></a>
|
||||
</div>
|
||||
<div class="hide-menu" ng-show="false">
|
||||
<div class="menu-element context-menu-wrapper mobile-disable-select">
|
||||
<div class="menu context-menu">
|
||||
<ul>
|
||||
<li ng-repeat="menu in menuEmbed"
|
||||
ng-click="menu.perform($event,embed.snapshot.src,embed.id,entry.createdOn,this,embed)"
|
||||
title="{{menu.getMetadata().description}}"
|
||||
class="{{menu.getMetadata().cssClass}}"
|
||||
ng-if="embed.snapshot">
|
||||
{{menu.getMetadata().name}}
|
||||
</li>
|
||||
<li ng-repeat="menu in menuEmbedNoSnap"
|
||||
ng-click="menu.perform($event,embed.snapshot.src,embed.id,entry.createdOn,this)"
|
||||
title="{{menu.getMetadata().description}}"
|
||||
class="{{menu.getMetadata().cssClass}}"
|
||||
ng-if="!embed.snapshot">
|
||||
{{menu.getMetadata().name}}
|
||||
</li>
|
||||
<li ng-repeat="menu in embedActions"
|
||||
ng-click="menu.perform()"
|
||||
title="{{menu.name}}"
|
||||
class="{{menu.cssClass}}">
|
||||
{{menu.name}}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="embed-date"
|
||||
ng-if="embed.snapshot">{{embed.id| date:'yyyy-MM-dd HH:mm:ss'}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- delete entry -->
|
||||
<div class="holder flex-elem local-control local-controls-hidden notebook-entry-delete">
|
||||
<a class="s-icon-button icon-trash" id={{entry.id}} title="Delete Entry" ng-click="deleteEntry($event)"></a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,8 @@
|
||||
<span class="status block">
|
||||
<!-- DO NOT ADD SPACES BETWEEN THE SPANS - IT ADDS WHITE SPACE!! -->
|
||||
<span class="status-indicator icon-bell"></span>
|
||||
<span class="label">
|
||||
Notifications
|
||||
</span>
|
||||
<span class="count"></span>
|
||||
</span>
|
34
platform/features/notebook/res/templates/snapshotHeader.html
Normal file
34
platform/features/notebook/res/templates/snapshotHeader.html
Normal file
@ -0,0 +1,34 @@
|
||||
<div class="t-snapshot abs l-view-header">
|
||||
<div class="abs object-browse-bar l-flex-row">
|
||||
<div class="left flex-elem l-flex-row grows">
|
||||
<div class="object-header flex-elem l-flex-row grows">
|
||||
<div class="type-icon flex-elem embed-icon holder" ng-class="cssClass"></div>
|
||||
<div class="title-label flex-elem holder flex-can-shrink">{{entryName}}</div>
|
||||
<a class="context-available flex-elem holder" ng-click="openMenu($event,embedType)"></a>
|
||||
<div class="hide-menu" ng-show="false">
|
||||
<div class="menu-element menu-view context-menu-wrapper mobile-disable-select">
|
||||
<div class="menu context-menu">
|
||||
<ul>
|
||||
<li ng-repeat="menu in embedActions"
|
||||
ng-click="menuPerform(menu)"
|
||||
title="{{menu.name}}"
|
||||
class="{{menu.cssClass}}">
|
||||
{{menu.name}}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="btn-bar right l-flex-row flex-elem flex-justify-end flex-fixed">
|
||||
<div class="flex-elem holder flex-can-shrink s-snapshot-datetime">
|
||||
SNAPSHOT {{snapDate | date:'yyyy-MM-dd HH:mm:ss'}}
|
||||
</div>
|
||||
<a class="s-button icon-pencil" title="Annotate">
|
||||
<span class="title-label">Annotate</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -26,7 +26,6 @@
|
||||
define(
|
||||
["painterro", "zepto"],
|
||||
function (Painterro, $) {
|
||||
|
||||
var annotationStruct = {
|
||||
title: "Annotate Snapshot",
|
||||
template: "annotate-snapshot",
|
||||
@ -108,6 +107,9 @@ define(
|
||||
done(true);
|
||||
}
|
||||
}).show(snapshot);
|
||||
|
||||
$(document.body).find('.ptro-icon-btn').addClass('s-button');
|
||||
$(document.body).find('.ptro-input').addClass('s-button');
|
||||
});
|
||||
}];
|
||||
|
@ -90,7 +90,7 @@ define(
|
||||
var dialogService = this.dialogService;
|
||||
var rootScope = this.$rootScope;
|
||||
rootScope.newEntryText = '';
|
||||
// // Create the overlay element and add it to the document's body
|
||||
// Create the overlay element and add it to the document's body
|
||||
this.$rootScope.selObj = domainObj;
|
||||
this.$rootScope.selValue = "";
|
||||
var newScope = rootScope.$new();
|
||||
@ -187,7 +187,7 @@ define(
|
||||
var domainObject = context.domainObject;
|
||||
|
||||
if (domainObject) {
|
||||
if (domainObject.getModel().type === 'notebook') {
|
||||
if (domainObject.getModel().type === 'Notebook') {
|
||||
// do not allow in context of a notebook
|
||||
return false;
|
||||
} else if (domainObject.getModel().type.includes('imagery')) {
|
72
platform/features/notebook/src/actions/RemoveEmbed.js
Normal file
72
platform/features/notebook/src/actions/RemoveEmbed.js
Normal file
@ -0,0 +1,72 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2017, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
|
||||
function RemoveEmbed(dialogService,context) {
|
||||
context = context || {};
|
||||
|
||||
this.domainObject = context.selectedObject || context.domainObject;
|
||||
this.dialogService = dialogService;
|
||||
}
|
||||
|
||||
|
||||
RemoveEmbed.prototype.perform = function ($event,snapshot,embedId,entryId) {
|
||||
var domainObject = this.domainObject;
|
||||
var errorDialog = this.dialogService.showBlockingMessage({
|
||||
severity: "error",
|
||||
title: "This action will permanently delete this Embed. Do you want to continue?",
|
||||
minimized: true, // want the notification to be minimized initially (don't show banner)
|
||||
options: [{
|
||||
label: "OK",
|
||||
callback: function () {
|
||||
errorDialog.dismiss();
|
||||
remove();
|
||||
}
|
||||
},{
|
||||
label: "Cancel",
|
||||
callback: function () {
|
||||
errorDialog.dismiss();
|
||||
}
|
||||
}]
|
||||
});
|
||||
|
||||
function remove() {
|
||||
domainObject.useCapability('mutation', function (model) {
|
||||
var elementPos = model.entries.map(function (x) {
|
||||
return x.createdOn;
|
||||
}).indexOf(entryId);
|
||||
var entryEmbeds = model.entries[elementPos].embeds;
|
||||
var embedPos = entryEmbeds.map(function (x) {
|
||||
return x.id;
|
||||
}).indexOf(embedId);
|
||||
model.entries[elementPos].embeds.splice(embedPos, 1);
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
return RemoveEmbed;
|
||||
}
|
||||
);
|
74
platform/features/notebook/src/actions/RemoveSnapshot.js
Normal file
74
platform/features/notebook/src/actions/RemoveSnapshot.js
Normal file
@ -0,0 +1,74 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2017, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
|
||||
function RemoveSnapshot(dialogService, context) {
|
||||
context = context || {};
|
||||
|
||||
this.domainObject = context.selectedObject || context.domainObject;
|
||||
this.dialogService = dialogService;
|
||||
}
|
||||
|
||||
|
||||
|
||||
RemoveSnapshot.prototype.perform = function ($event, snapshot, embedId, entryId) {
|
||||
|
||||
var domainObject = this.domainObject;
|
||||
var errorDialog = this.dialogService.showBlockingMessage({
|
||||
severity: "error",
|
||||
title: "This action will permanently delete this Snapshot. Do you want to continue?",
|
||||
minimized: true, // want the notification to be minimized initially (don't show banner)
|
||||
options: [{
|
||||
label: "OK",
|
||||
callback: function () {
|
||||
errorDialog.dismiss();
|
||||
remove();
|
||||
}
|
||||
},{
|
||||
label: "Cancel",
|
||||
callback: function () {
|
||||
errorDialog.dismiss();
|
||||
}
|
||||
}]
|
||||
});
|
||||
|
||||
function remove() {
|
||||
domainObject.useCapability('mutation', function (model) {
|
||||
var elementPos = model.entries.map(function (x) {
|
||||
return x.createdOn;
|
||||
}).indexOf(entryId);
|
||||
var entryEmbeds = model.entries[elementPos].embeds;
|
||||
var embedPos = entryEmbeds.map(function (x) {
|
||||
return x.id;
|
||||
}).indexOf(embedId);
|
||||
model.entries[elementPos].embeds[embedPos].snapshot = "";
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
return RemoveSnapshot;
|
||||
}
|
||||
);
|
132
platform/features/notebook/src/actions/ViewSnapshot.js
Normal file
132
platform/features/notebook/src/actions/ViewSnapshot.js
Normal file
@ -0,0 +1,132 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2017, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
/**
|
||||
* Module defining ViewSnapshot
|
||||
*/
|
||||
|
||||
var OVERLAY_TEMPLATE = '' +
|
||||
' <div class="abs blocker"></div>' +
|
||||
' <div class="abs outer-holder">' +
|
||||
' <a class="close icon-x-in-circle"></a>' +
|
||||
' <div class="abs inner-holder l-flex-col">' +
|
||||
' <div class="t-contents flex-elem holder grows"></div>' +
|
||||
' <div class="bottom-bar flex-elem holder">' +
|
||||
' <a class="t-done s-button major">Done</a>' +
|
||||
' </div>' +
|
||||
' </div>' +
|
||||
' </div>';
|
||||
|
||||
define([
|
||||
'zepto',
|
||||
"../../res/templates/snapshotHeader.html"
|
||||
],
|
||||
function ($, headerTemplate) {
|
||||
|
||||
var toggleOverlay,
|
||||
overlay,
|
||||
closeButton,
|
||||
doneButton,
|
||||
blocker,
|
||||
overlayContainer,
|
||||
img,
|
||||
annotateButton,
|
||||
annotateImg;
|
||||
|
||||
function ViewSnapshot($compile) {
|
||||
this.$compile = $compile;
|
||||
}
|
||||
|
||||
function openOverlay(url, header) {
|
||||
overlay = document.createElement('div');
|
||||
$(overlay).addClass('abs overlay l-large-view');
|
||||
overlay.innerHTML = OVERLAY_TEMPLATE;
|
||||
overlayContainer = overlay.querySelector('.t-contents');
|
||||
closeButton = overlay.querySelector('a.close');
|
||||
closeButton.addEventListener('click', toggleOverlay);
|
||||
doneButton = overlay.querySelector('a.t-done');
|
||||
doneButton.addEventListener('click', toggleOverlay);
|
||||
blocker = overlay.querySelector('.abs.blocker');
|
||||
blocker.addEventListener('click', toggleOverlay);
|
||||
annotateButton = header.querySelector('a.icon-pencil');
|
||||
annotateButton.addEventListener('click', annotateImg);
|
||||
document.body.appendChild(overlay);
|
||||
img = document.createElement('div');
|
||||
$(img).addClass('abs object-holder t-image-holder s-image-holder');
|
||||
img.innerHTML = '<div class="image-main s-image-main" style="background-image: url(' + url + ');"></div>';
|
||||
overlayContainer.appendChild(header);
|
||||
overlayContainer.appendChild(img);
|
||||
}
|
||||
|
||||
function closeOverlay() {
|
||||
overlayContainer.removeChild(img);
|
||||
document.body.removeChild(overlay);
|
||||
closeButton.removeEventListener('click', toggleOverlay);
|
||||
closeButton = undefined;
|
||||
doneButton.removeEventListener('click', toggleOverlay);
|
||||
doneButton = undefined;
|
||||
blocker.removeEventListener('click', toggleOverlay);
|
||||
blocker = undefined;
|
||||
overlayContainer = undefined;
|
||||
overlay = undefined;
|
||||
img = undefined;
|
||||
}
|
||||
|
||||
ViewSnapshot.prototype.perform = function ($event, snapshot, embedId, entryId, $scope, embed) {
|
||||
var isOpen = false;
|
||||
|
||||
// onclick for menu items in overlay header context menu
|
||||
$scope.menuPerform = function (menu) {
|
||||
menu.perform();
|
||||
closeOverlay();
|
||||
};
|
||||
|
||||
// Create the overlay element and add it to the document's body
|
||||
$scope.cssClass = embed.cssClass;
|
||||
$scope.embedType = embed.type;
|
||||
$scope.entryName = embed.name;
|
||||
$scope.snapDate = +embedId;
|
||||
var element = this.$compile(headerTemplate)($scope);
|
||||
|
||||
var annotateAction = $scope.action.getActions({category: 'embed'})[1];
|
||||
|
||||
toggleOverlay = function () {
|
||||
if (!isOpen) {
|
||||
openOverlay(snapshot, element[0]);
|
||||
isOpen = true;
|
||||
} else {
|
||||
closeOverlay();
|
||||
isOpen = false;
|
||||
}
|
||||
};
|
||||
|
||||
annotateImg = function () {
|
||||
closeOverlay();
|
||||
annotateAction.perform($event, snapshot, embedId, entryId, $scope);
|
||||
};
|
||||
|
||||
toggleOverlay();
|
||||
};
|
||||
|
||||
return ViewSnapshot;
|
||||
}
|
||||
);
|
@ -0,0 +1,50 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2017, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
define(
|
||||
function () {
|
||||
|
||||
/**
|
||||
* The notebook capability allows a domain object to know whether the
|
||||
* notebook plugin is present or not.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function NotebookCapability(typeService, domainObject) {
|
||||
this.domainObject = domainObject;
|
||||
this.typeService = typeService;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if there is a notebook domain Object.
|
||||
*
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
NotebookCapability.prototype.isNotebook = function () {
|
||||
return !!this.typeService.getType('notebook');
|
||||
};
|
||||
|
||||
return NotebookCapability;
|
||||
}
|
||||
);
|
@ -0,0 +1,54 @@
|
||||
/*****************************************************************************
|
||||
* 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.
|
||||
*****************************************************************************/
|
||||
|
||||
/**
|
||||
* This bundle implements object types and associated views for
|
||||
* display-building.
|
||||
*/
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
|
||||
/**
|
||||
* The LayoutNotebookController is responsible for supporting the
|
||||
* notebook feature creation on theLayout view.
|
||||
**/
|
||||
|
||||
function LayoutNotebookController($scope) {
|
||||
$scope.hasNotebookAction = undefined;
|
||||
|
||||
$scope.newNotebook = undefined;
|
||||
|
||||
var actions = $scope.domainObject.getCapability('action');
|
||||
var notebookAction = actions.getActions({'key': 'notebook-new-entry'});
|
||||
if (notebookAction.length > 0) {
|
||||
$scope.hasNotebookAction = true;
|
||||
$scope.newNotebook = function () {
|
||||
notebookAction[0].perform();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return LayoutNotebookController;
|
||||
}
|
||||
);
|
||||
|
@ -31,7 +31,8 @@ define(
|
||||
$scope.snapshot = undefined;
|
||||
$scope.snapToggle = true;
|
||||
$scope.entryText = '';
|
||||
var annotateAction = $rootScope.selObj.getCapability('action').getActions({key: 'annotate-snapshot'})[0];
|
||||
var annotateAction = $rootScope.selObj.getCapability('action').getActions(
|
||||
{category: 'embed'})[1];
|
||||
|
||||
$scope.$parent.$parent.ngModel[$scope.$parent.$parent.field] = $rootScope.selObj;
|
||||
$scope.objectName = $rootScope.selObj.getModel().name;
|
367
platform/features/notebook/src/controllers/NotebookController.js
Normal file
367
platform/features/notebook/src/controllers/NotebookController.js
Normal file
@ -0,0 +1,367 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2017, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
/*-- main controller file, here is the core functionality of the notebook plugin --*/
|
||||
|
||||
define(
|
||||
['zepto'],
|
||||
function ($) {
|
||||
|
||||
|
||||
function NotebookController(
|
||||
$scope,
|
||||
dialogService,
|
||||
popupService,
|
||||
agentService,
|
||||
objectService,
|
||||
navigationService,
|
||||
now,
|
||||
actionService,
|
||||
$timeout,
|
||||
$element,
|
||||
$sce
|
||||
) {
|
||||
|
||||
$scope.entriesEl = $(document.body).find('.t-entries-list');
|
||||
$scope.sortEntries = $scope.domainObject.getModel().defaultSort;
|
||||
$scope.showTime = "0";
|
||||
$scope.editEntry = false;
|
||||
$scope.entrySearch = '';
|
||||
$scope.entryTypes = [];
|
||||
$scope.embedActions = [];
|
||||
$scope.currentEntryValue = '';
|
||||
|
||||
var SECONDS_IN_AN_HOUR = 60 * 60 * 1000;
|
||||
|
||||
this.scope = $scope;
|
||||
|
||||
$scope.hoursFilter = function (hours,entryTime) {
|
||||
if (+hours) {
|
||||
return entryTime > (Date.now() - SECONDS_IN_AN_HOUR * (+hours));
|
||||
}else {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.scrollToTop = function () {
|
||||
var entriesContainer = $scope.entriesEl.parent();
|
||||
entriesContainer[0].scrollTop = 0;
|
||||
};
|
||||
|
||||
$scope.findEntryEl = function (entryId) {
|
||||
var element = $($scope.entriesEl).find('#entry_' + entryId);
|
||||
|
||||
if (element[0]) {
|
||||
return element.find("[contenteditable='true']");
|
||||
} else {
|
||||
var entries = $scope.entriesEl.children().children(),
|
||||
lastOrFirst = $scope.sortEntries === "-createdOn" ? 0 : (entries.length - 1);
|
||||
|
||||
return $(entries[lastOrFirst]).find("[contenteditable='true']");
|
||||
}
|
||||
};
|
||||
|
||||
$scope.findEntryPositionById = function (id) {
|
||||
var foundId = -1;
|
||||
|
||||
$scope.domainObject.model.entries.forEach(function (element, index) {
|
||||
if (element.id === id) {
|
||||
foundId = index;
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
return foundId;
|
||||
};
|
||||
|
||||
$scope.newEntry = function ($event) {
|
||||
$scope.scrollToTop();
|
||||
|
||||
var entries = $scope.domainObject.model.entries,
|
||||
lastEntry = entries[entries.length - 1],
|
||||
id = Date.now();
|
||||
|
||||
if (lastEntry === undefined || lastEntry.text || lastEntry.embeds) {
|
||||
var createdEntry = {'id': id, 'createdOn': id};
|
||||
|
||||
$scope.domainObject.useCapability('mutation', function (model) {
|
||||
model.entries.push(createdEntry);
|
||||
});
|
||||
} else {
|
||||
$scope.findEntryEl(lastEntry.id).focus();
|
||||
|
||||
$scope.domainObject.useCapability('mutation', function (model) {
|
||||
model.entries[entries.length - 1].createdOn = id;
|
||||
});
|
||||
}
|
||||
$scope.entrySearch = '';
|
||||
};
|
||||
|
||||
|
||||
$scope.deleteEntry = function ($event) {
|
||||
var delId = +$event.currentTarget.id;
|
||||
var errorDialog = dialogService.showBlockingMessage({
|
||||
severity: "error",
|
||||
title: "This action will permanently delete this Notebook entry. Do you want to continue?",
|
||||
minimized: true, // want the notification to be minimized initially (don't show banner)
|
||||
options: [{
|
||||
label: "OK",
|
||||
callback: function () {
|
||||
errorDialog.dismiss();
|
||||
var elementPos = $scope.findEntryPositionById(delId);
|
||||
|
||||
if (elementPos !== -1) {
|
||||
$scope.domainObject.useCapability('mutation', function (model) {
|
||||
model.entries.splice(elementPos, 1);
|
||||
});
|
||||
} else {
|
||||
window.console.log('delete error');
|
||||
}
|
||||
|
||||
}
|
||||
},{
|
||||
label: "Cancel",
|
||||
callback: function () {
|
||||
errorDialog.dismiss();
|
||||
}
|
||||
}]
|
||||
});
|
||||
};
|
||||
|
||||
$scope.textFocus = function ($event, entryId) {
|
||||
if ($event.srcElement) {
|
||||
$scope.currentEntryValue = $event.srcElement.innerText;
|
||||
} else {
|
||||
$event.target.innerText = '';
|
||||
}
|
||||
};
|
||||
|
||||
//On text blur(when focus is removed)
|
||||
$scope.textBlur = function ($event, entryId) {
|
||||
// entryId is the unique numeric based on the original createdOn
|
||||
if ($event.target) {
|
||||
var elementPos = $scope.findEntryPositionById(+entryId);
|
||||
|
||||
// If the text of an entry has been changed, then update the text and the modifiedOn numeric
|
||||
// Otherwise, don't do anything
|
||||
if ($scope.currentEntryValue !== $event.target.innerText) {
|
||||
$scope.domainObject.useCapability('mutation', function (model) {
|
||||
model.entries[elementPos].text = $event.target.innerText;
|
||||
model.entries[elementPos].modified = Date.now();
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$scope.finished = function (model) {
|
||||
var lastEntry = model[model.length - 1];
|
||||
|
||||
if (!lastEntry.text) {
|
||||
$scope.findEntryEl(lastEntry.id).focus();
|
||||
}
|
||||
};
|
||||
|
||||
$scope.handleActive = function () {
|
||||
var newEntry = $scope.entriesEl.find('.active');
|
||||
if (newEntry) {
|
||||
newEntry.removeClass('active');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
$scope.clearSearch = function () {
|
||||
$scope.entrySearch = '';
|
||||
};
|
||||
|
||||
$scope.viewSnapshot = function ($event,snapshot,embedId,entryId,$innerScope,domainObject) {
|
||||
var viewAction = $scope.action.getActions({category: 'embed'})[0];
|
||||
viewAction.perform($event, snapshot, embedId, entryId, $innerScope, domainObject);
|
||||
};
|
||||
|
||||
$scope.renderImage = function (img) {
|
||||
return URL.createObjectURL(img);
|
||||
};
|
||||
|
||||
$scope.getDomainObj = function (id) {
|
||||
return objectService.getObjects([id]);
|
||||
};
|
||||
|
||||
function refreshComp(change) {
|
||||
if (change && change.length) {
|
||||
change[0].getCapability('action').getActions({key: 'remove'})[0].perform();
|
||||
}
|
||||
}
|
||||
|
||||
$scope.actionToMenuOption = function (action) {
|
||||
return {
|
||||
key: action.getMetadata().key,
|
||||
name: action.getMetadata().name,
|
||||
cssClass: action.getMetadata().cssClass,
|
||||
perform: action.perform
|
||||
};
|
||||
};
|
||||
|
||||
// Maintain all "conclude-editing" and "save" actions in the
|
||||
// present context.
|
||||
function updateActions() {
|
||||
$scope.menuEmbed = $scope.action ?
|
||||
$scope.action.getActions({category: 'embed'}) :
|
||||
[];
|
||||
|
||||
$scope.menuEmbedNoSnap = $scope.action ?
|
||||
$scope.action.getActions({category: 'embed-no-snap'}) :
|
||||
[];
|
||||
|
||||
$scope.menuActions = $scope.action ?
|
||||
$scope.action.getActions({key: 'window'}) :
|
||||
[];
|
||||
}
|
||||
|
||||
// Update set of actions whenever the action capability
|
||||
// changes or becomes available.
|
||||
$scope.$watch("action", updateActions);
|
||||
|
||||
$scope.navigate = function ($event,embedType) {
|
||||
if ($event) {
|
||||
$event.preventDefault();
|
||||
}
|
||||
$scope.getDomainObj(embedType).then(function (resp) {
|
||||
navigationService.setNavigation(resp[embedType]);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.saveSnap = function (url,embedPos,entryPos) {
|
||||
var snapshot = false;
|
||||
if (url) {
|
||||
if (embedPos !== -1 && entryPos !== -1) {
|
||||
var reader = new window.FileReader();
|
||||
reader.readAsDataURL(url);
|
||||
reader.onloadend = function () {
|
||||
snapshot = reader.result;
|
||||
$scope.domainObject.useCapability('mutation', function (model) {
|
||||
if (model.entries[entryPos]) {
|
||||
model.entries[entryPos].embeds[embedPos].snapshot = {
|
||||
'src': snapshot,
|
||||
'type': url.type,
|
||||
'size': url.size,
|
||||
'modified': Date.now()
|
||||
};
|
||||
model.entries[entryPos].embeds[embedPos].id = Date.now();
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
}else {
|
||||
$scope.domainObject.useCapability('mutation', function (model) {
|
||||
model.entries[entryPos].embeds[embedPos].snapshot = snapshot;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/*---popups menu embeds----*/
|
||||
|
||||
function getEmbedActions(embedType) {
|
||||
if (!$scope.embedActions.length) {
|
||||
$scope.getDomainObj(embedType).then(function (resp) {
|
||||
$scope.embedActions = [];
|
||||
$scope.embedActions.push($scope.actionToMenuOption(
|
||||
$scope.action.getActions({key: 'mct-preview-action', selectedObject: resp[embedType]})[0]
|
||||
));
|
||||
$scope.embedActions.push($scope.actionToMenuOption(
|
||||
$scope.action.getActions({key: 'window', selectedObject: resp[embedType]})[0]
|
||||
));
|
||||
$scope.embedActions.push({
|
||||
key: 'navigate',
|
||||
name: 'Go to Original',
|
||||
cssClass: '',
|
||||
perform: function () {
|
||||
$scope.navigate('', embedType);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$scope.openMenu = function ($event,embedType) {
|
||||
$event.preventDefault();
|
||||
|
||||
getEmbedActions(embedType);
|
||||
|
||||
var body = $(document).find('body'),
|
||||
initiatingEvent = agentService.isMobile() ?
|
||||
'touchstart' : 'mousedown',
|
||||
dismissExistingMenu,
|
||||
menu;
|
||||
|
||||
var container = $($event.currentTarget).parent().parent();
|
||||
|
||||
menu = container.find('.menu-element');
|
||||
|
||||
// Remove the context menu
|
||||
function dismiss() {
|
||||
container.find('.hide-menu').append(menu);
|
||||
body.off("mousedown", dismiss);
|
||||
dismissExistingMenu = undefined;
|
||||
$scope.embedActions = [];
|
||||
}
|
||||
|
||||
// Dismiss any menu which was already showing
|
||||
if (dismissExistingMenu) {
|
||||
dismissExistingMenu();
|
||||
}
|
||||
|
||||
// ...and record the presence of this menu.
|
||||
dismissExistingMenu = dismiss;
|
||||
|
||||
popupService.display(menu, [$event.pageX,$event.pageY], {
|
||||
marginX: 0,
|
||||
marginY: -50
|
||||
});
|
||||
|
||||
// Stop propagation so that clicks or touches on the menu do not close the menu
|
||||
menu.on(initiatingEvent, function (event) {
|
||||
event.stopPropagation();
|
||||
$timeout(dismiss, 300);
|
||||
});
|
||||
|
||||
// Dismiss the menu when body is clicked/touched elsewhere
|
||||
// ('mousedown' because 'click' breaks left-click context menus)
|
||||
// ('touchstart' because 'touch' breaks context menus up)
|
||||
body.on(initiatingEvent, dismiss);
|
||||
|
||||
};
|
||||
|
||||
|
||||
$scope.$watchCollection("composition", refreshComp);
|
||||
|
||||
$scope.$watch('domainObject.getModel().defaultSort', function (newDefaultSort, oldDefaultSort) {
|
||||
if (newDefaultSort !== oldDefaultSort) {
|
||||
$scope.sortEntries = newDefaultSort;
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$on('$destroy', function () {});
|
||||
|
||||
}
|
||||
|
||||
return NotebookController;
|
||||
});
|
@ -31,34 +31,32 @@ define(['zepto'], function ($) {
|
||||
var selectedModel = selectedObject.getModel();
|
||||
var cssClass = selectedObject.getCapability('type').typeDef.cssClass;
|
||||
var entryId = -1;
|
||||
var embedId = -1;
|
||||
$scope.clearSearch();
|
||||
if ($element[0].id === 'newEntry') {
|
||||
entryId = $scope.domainObject.model.entries.length;
|
||||
embedId = 0;
|
||||
var lastEntry = $scope.domainObject.model.entries[entryId - 1];
|
||||
if (lastEntry === undefined || lastEntry.text || lastEntry.embeds) {
|
||||
$scope.domainObject.useCapability('mutation', function (model) {
|
||||
model.entries.push({'createdOn': +Date.now(),
|
||||
'id': +Date.now(),
|
||||
'embeds': [{'type': selectedObject.getId(),
|
||||
'id': '' + Date.now(),
|
||||
'cssClass': cssClass,
|
||||
'name': selectedModel.name,
|
||||
'snapshot': ''
|
||||
}]
|
||||
});
|
||||
'id': +Date.now(),
|
||||
'embeds': [{'type': selectedObject.getId(),
|
||||
'id': '' + Date.now(),
|
||||
'cssClass': cssClass,
|
||||
'name': selectedModel.name,
|
||||
'snapshot': ''
|
||||
}]
|
||||
});
|
||||
});
|
||||
}else {
|
||||
$scope.domainObject.useCapability('mutation', function (model) {
|
||||
model.entries[entryId - 1] =
|
||||
{'createdOn': +Date.now(),
|
||||
'embeds': [{'type': selectedObject.getId(),
|
||||
'id': '' + Date.now(),
|
||||
'cssClass': cssClass,
|
||||
'name': selectedModel.name,
|
||||
'snapshot': ''
|
||||
}]
|
||||
'embeds': [{'type': selectedObject.getId(),
|
||||
'id': '' + Date.now(),
|
||||
'cssClass': cssClass,
|
||||
'name': selectedModel.name,
|
||||
'snapshot': ''
|
||||
}]
|
||||
};
|
||||
});
|
||||
}
|
||||
@ -77,15 +75,13 @@ define(['zepto'], function ($) {
|
||||
|
||||
$scope.domainObject.useCapability('mutation', function (model) {
|
||||
model.entries[entryId].embeds.push({'type': selectedObject.getId(),
|
||||
'id': '' + Date.now(),
|
||||
'cssClass': cssClass,
|
||||
'name': selectedModel.name,
|
||||
'snapshot': ''
|
||||
});
|
||||
'id': '' + Date.now(),
|
||||
'cssClass': cssClass,
|
||||
'name': selectedModel.name,
|
||||
'snapshot': ''
|
||||
});
|
||||
});
|
||||
|
||||
embedId = $scope.domainObject.model.entries[entryId].embeds.length - 1;
|
||||
|
||||
if (selectedObject) {
|
||||
e.preventDefault();
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||
/******************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2017, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
@ -20,16 +20,25 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
@import "vendor/normalize.min.css";
|
||||
@import "sass-base.scss";
|
||||
/**
|
||||
* This bundle implements "containment" rules, which determine which objects
|
||||
* can be contained within a notebook.
|
||||
*/
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
function CompositionPolicy() {
|
||||
}
|
||||
|
||||
/******************** RENDERS CSS */
|
||||
@import "glyphs";
|
||||
@import "global";
|
||||
@import "controls";
|
||||
CompositionPolicy.prototype.allow = function (parent, child) {
|
||||
var parentDef = parent.getCapability('type').getName();
|
||||
|
||||
/******************** LEGACY CSS */
|
||||
$output-bourbon-deprecation-warnings: false;
|
||||
@import "bourbon";
|
||||
@import "legacy-styles";
|
||||
if (parentDef === 'Notebook' && child.getCapability('status').list().length) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
return CompositionPolicy;
|
||||
}
|
||||
);
|
@ -93,7 +93,7 @@ define([
|
||||
|
||||
// Initialize the application
|
||||
$log.info("Initializing application.");
|
||||
return initializer.runApplication();
|
||||
initializer.runApplication();
|
||||
};
|
||||
|
||||
return FrameworkLayer;
|
||||
|
@ -52,7 +52,10 @@ define(
|
||||
return match ? match[1] : "";
|
||||
}
|
||||
|
||||
return injector.instantiate(['$http', '$log', FrameworkLayer])
|
||||
|
||||
// Reconfigure base url, since bundle paths will all be relative
|
||||
// to the root now.
|
||||
injector.instantiate(['$http', '$log', FrameworkLayer])
|
||||
.initializeApplication(angular, legacyRegistry, logLevel());
|
||||
};
|
||||
|
||||
|
@ -55,12 +55,9 @@ define(
|
||||
var angular = this.angular,
|
||||
document = this.document,
|
||||
$log = this.$log;
|
||||
return new Promise(function (resolve, reject) {
|
||||
$log.info("Bootstrapping application " + (app || {}).name);
|
||||
angular.element(document).ready(function () {
|
||||
angular.bootstrap(document, [app.name], { strictDi: true });
|
||||
resolve(angular);
|
||||
});
|
||||
$log.info("Bootstrapping application " + (app || {}).name);
|
||||
angular.element(document).ready(function () {
|
||||
angular.bootstrap(document, [app.name], { strictDi: true });
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -44,7 +44,6 @@ define(function () {
|
||||
function SearchController($scope, searchService) {
|
||||
var controller = this;
|
||||
this.$scope = $scope;
|
||||
this.$scope.ngModel = this.$scope.ngModel || {};
|
||||
this.searchService = searchService;
|
||||
this.numberToDisplay = this.RESULTS_PER_PAGE;
|
||||
this.availabileResults = 0;
|
||||
|
92
src/MCT.js
92
src/MCT.js
@ -24,46 +24,28 @@ define([
|
||||
'EventEmitter',
|
||||
'legacyRegistry',
|
||||
'uuid',
|
||||
'./defaultRegistry',
|
||||
'./api/api',
|
||||
'./selection/Selection',
|
||||
'./api/objects/object-utils',
|
||||
'./plugins/plugins',
|
||||
'./ui/ViewRegistry',
|
||||
'./ui/InspectorViewRegistry',
|
||||
'./ui/ToolbarRegistry',
|
||||
'./adapter/indicators/legacy-indicators-plugin',
|
||||
'./plugins/buildInfo/plugin',
|
||||
'./ui/registries/ViewRegistry',
|
||||
'./ui/registries/InspectorViewRegistry',
|
||||
'./ui/registries/ToolbarRegistry',
|
||||
'./ui/router/ApplicationRouter',
|
||||
'./ui/router/Browse',
|
||||
'../platform/framework/src/Main',
|
||||
'./styles-new/core.scss',
|
||||
'./styles-new/notebook.scss',
|
||||
'./ui/components/layout/Layout.vue',
|
||||
'./ui/overlayService/overlayService',
|
||||
'vue'
|
||||
'./styles/core.scss'
|
||||
], function (
|
||||
EventEmitter,
|
||||
legacyRegistry,
|
||||
uuid,
|
||||
defaultRegistry,
|
||||
api,
|
||||
Selection,
|
||||
objectUtils,
|
||||
plugins,
|
||||
LegacyIndicatorsPlugin,
|
||||
buildInfoPlugin,
|
||||
ViewRegistry,
|
||||
InspectorViewRegistry,
|
||||
ToolbarRegistry,
|
||||
ApplicationRouter,
|
||||
Browse,
|
||||
Main,
|
||||
coreStyles,
|
||||
NotebookStyles,
|
||||
Layout,
|
||||
OverlayService,
|
||||
Vue
|
||||
LegacyIndicatorsPlugin,
|
||||
coreStyles
|
||||
) {
|
||||
/**
|
||||
* Open MCT is an extensible web application for building mission
|
||||
@ -225,19 +207,6 @@ define([
|
||||
|
||||
this.Dialog = api.Dialog;
|
||||
|
||||
this.editor = new api.EditorAPI.default(this);
|
||||
|
||||
this.OverlayService = new OverlayService();
|
||||
|
||||
this.legacyRegistry = defaultRegistry;
|
||||
this.install(this.plugins.Plot());
|
||||
this.install(this.plugins.TelemetryTable());
|
||||
this.install(this.plugins.DisplayLayout());
|
||||
|
||||
if (typeof BUILD_CONSTANTS !== 'undefined') {
|
||||
this.install(buildInfoPlugin(BUILD_CONSTANTS));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MCT.prototype = Object.create(EventEmitter.prototype);
|
||||
@ -276,6 +245,11 @@ define([
|
||||
domElement = document.body;
|
||||
}
|
||||
|
||||
var appDiv = document.createElement('div');
|
||||
appDiv.setAttribute('ng-view', '');
|
||||
appDiv.className = 'user-environ';
|
||||
domElement.appendChild(appDiv);
|
||||
|
||||
this.legacyExtension('runs', {
|
||||
depends: ['navigationService'],
|
||||
implementation: function (navigationService) {
|
||||
@ -284,7 +258,6 @@ define([
|
||||
}.bind(this)
|
||||
});
|
||||
|
||||
// TODO: remove with legacy types.
|
||||
this.types.listKeys().forEach(function (typeKey) {
|
||||
var type = this.types.get(typeKey);
|
||||
var legacyDefinition = type.toLegacyDefinition();
|
||||
@ -292,48 +265,33 @@ define([
|
||||
this.legacyExtension('types', legacyDefinition);
|
||||
}.bind(this));
|
||||
|
||||
this.objectViews.getAllProviders().forEach(function (p) {
|
||||
this.legacyExtension('views', {
|
||||
key: p.key,
|
||||
provider: p,
|
||||
name: p.name,
|
||||
cssClass: p.cssClass,
|
||||
description: p.description,
|
||||
editable: p.editable,
|
||||
template: '<mct-view mct-provider-key="' + p.key + '"/>'
|
||||
});
|
||||
}, this);
|
||||
|
||||
legacyRegistry.register('adapter', this.legacyBundle);
|
||||
legacyRegistry.enable('adapter');
|
||||
|
||||
this.install(LegacyIndicatorsPlugin());
|
||||
|
||||
this.router = new ApplicationRouter();
|
||||
|
||||
this.router.route(/^\/$/, () => {
|
||||
this.router.setPath('/browse/mine');
|
||||
});
|
||||
|
||||
/**
|
||||
* Fired by [MCT]{@link module:openmct.MCT} when the application
|
||||
* is started.
|
||||
* @event start
|
||||
* @memberof module:openmct.MCT~
|
||||
*/
|
||||
var startPromise = new Main().run(this.legacyRegistry)
|
||||
.then(function (angular) {
|
||||
this.$angular = angular;
|
||||
// OpenMCT Object provider doesn't operate properly unless
|
||||
// something has depended upon objectService. Cool, right?
|
||||
this.$injector.get('objectService');
|
||||
|
||||
var appLayout = new Vue({
|
||||
components: {
|
||||
'Layout': Layout.default
|
||||
},
|
||||
provide: {
|
||||
openmct: this
|
||||
},
|
||||
template: '<Layout ref="layout"></Layout>'
|
||||
});
|
||||
domElement.appendChild(appLayout.$mount().$el);
|
||||
|
||||
this.layout = appLayout.$refs.layout;
|
||||
Browse(this);
|
||||
this.router.start();
|
||||
this.emit('start');
|
||||
}.bind(this));
|
||||
this.emit('start');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Install a plugin in MCT.
|
||||
*
|
||||
|
@ -34,9 +34,7 @@ define([
|
||||
'./runs/TimeSettingsURLHandler',
|
||||
'./runs/TypeDeprecationChecker',
|
||||
'./runs/LegacyTelemetryProvider',
|
||||
'./runs/RegisterLegacyTypes',
|
||||
'./services/LegacyObjectAPIInterceptor',
|
||||
'./views/installLegacyViews'
|
||||
'./services/LegacyObjectAPIInterceptor'
|
||||
], function (
|
||||
legacyRegistry,
|
||||
ActionDialogDecorator,
|
||||
@ -51,9 +49,7 @@ define([
|
||||
TimeSettingsURLHandler,
|
||||
TypeDeprecationChecker,
|
||||
LegacyTelemetryProvider,
|
||||
RegisterLegacyTypes,
|
||||
LegacyObjectAPIInterceptor,
|
||||
installLegacyViews
|
||||
LegacyObjectAPIInterceptor
|
||||
) {
|
||||
legacyRegistry.register('src/adapter', {
|
||||
"extensions": {
|
||||
@ -153,21 +149,6 @@ define([
|
||||
"openmct",
|
||||
"instantiate"
|
||||
]
|
||||
},
|
||||
{
|
||||
implementation: installLegacyViews,
|
||||
depends: [
|
||||
"openmct",
|
||||
"views[]",
|
||||
"instantiate"
|
||||
]
|
||||
},
|
||||
{
|
||||
implementation: RegisterLegacyTypes,
|
||||
depends: [
|
||||
"types[]",
|
||||
"openmct"
|
||||
]
|
||||
}
|
||||
],
|
||||
licenses: [
|
||||
|
@ -1,17 +0,0 @@
|
||||
define([
|
||||
|
||||
], function (
|
||||
|
||||
) {
|
||||
function RegisterLegacyTypes(types, openmct) {
|
||||
types.forEach(function (legacyDefinition) {
|
||||
if (!openmct.types.get(legacyDefinition.key)) {
|
||||
console.warn(`DEPRECATION WARNING: Migrate type ${legacyDefinition.key} from ${legacyDefinition.bundle.path} to use the new Types API. Legacy type support will be removed soon.`);
|
||||
}
|
||||
});
|
||||
|
||||
openmct.types.importLegacyTypes(types);
|
||||
}
|
||||
|
||||
return RegisterLegacyTypes;
|
||||
});
|
@ -1,110 +0,0 @@
|
||||
define([
|
||||
|
||||
], function (
|
||||
|
||||
) {
|
||||
const DEFAULT_VIEW_PRIORITY = 100;
|
||||
|
||||
const PRIORITY_LEVELS = {
|
||||
"fallback": Number.NEGATIVE_INFINITY,
|
||||
"default": -100,
|
||||
"none": 0,
|
||||
"optional": DEFAULT_VIEW_PRIORITY,
|
||||
"preferred": 1000,
|
||||
"mandatory": Number.POSITIVE_INFINITY
|
||||
};
|
||||
|
||||
function LegacyViewProvider(legacyView, openmct, convertToLegacyObject) {
|
||||
console.warn(`DEPRECATION WARNING: Migrate ${legacyView.key} from ${legacyView.bundle.path} to use the new View APIs. Legacy view support will be removed soon.`);
|
||||
return {
|
||||
key: legacyView.key,
|
||||
name: legacyView.name,
|
||||
cssClass: legacyView.cssClass,
|
||||
description: legacyView.description,
|
||||
editable: legacyView.editable,
|
||||
canView: function (domainObject) {
|
||||
if (!domainObject || !domainObject.identifier) {
|
||||
return false;
|
||||
}
|
||||
if (legacyView.type) {
|
||||
return domainObject.type === legacyView.type;
|
||||
}
|
||||
let legacyObject = convertToLegacyObject(domainObject);
|
||||
if (legacyView.needs) {
|
||||
let meetsNeeds = legacyView.needs.every(k => legacyObject.hasCapability(k));
|
||||
if (!meetsNeeds) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return openmct.$injector.get('policyService').allow(
|
||||
'view', legacyView, legacyObject
|
||||
);
|
||||
},
|
||||
view: function (domainObject) {
|
||||
let $rootScope = openmct.$injector.get('$rootScope');
|
||||
let templateLinker = openmct.$injector.get('templateLinker');
|
||||
let scope = $rootScope.$new();
|
||||
let legacyObject = convertToLegacyObject(domainObject);
|
||||
let isDestroyed = false;
|
||||
scope.domainObject = legacyObject;
|
||||
scope.model = legacyObject.getModel();
|
||||
|
||||
|
||||
return {
|
||||
show: function (container) {
|
||||
// TODO: implement "gestures" support ?
|
||||
let uses = legacyView.uses || [];
|
||||
let promises = [];
|
||||
let results = uses.map(function (capabilityKey, i) {
|
||||
let result = legacyObject.useCapability(capabilityKey);
|
||||
if (result.then) {
|
||||
promises.push(result.then(function (r) {
|
||||
results[i] = r;
|
||||
}));
|
||||
}
|
||||
return result;
|
||||
});
|
||||
|
||||
function link() {
|
||||
if (isDestroyed) {
|
||||
return;
|
||||
}
|
||||
uses.forEach(function (key, i) {
|
||||
scope[key] = results[i];
|
||||
});
|
||||
templateLinker.link(
|
||||
scope,
|
||||
openmct.$angular.element(container),
|
||||
legacyView
|
||||
);
|
||||
container.style.height = '100%';
|
||||
}
|
||||
|
||||
if (promises.length) {
|
||||
Promise.all(promises)
|
||||
.then(function () {
|
||||
link();
|
||||
scope.$digest();
|
||||
});
|
||||
} else {
|
||||
link();
|
||||
}
|
||||
},
|
||||
destroy: function () {
|
||||
scope.$destroy();
|
||||
}
|
||||
}
|
||||
},
|
||||
priority: function () {
|
||||
let priority = legacyView.priority || DEFAULT_VIEW_PRIORITY;
|
||||
if (typeof priority === 'string') {
|
||||
priority = PRIORITY_LEVELS[priority];
|
||||
}
|
||||
return priority;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
return LegacyViewProvider;
|
||||
|
||||
});
|
@ -1,95 +0,0 @@
|
||||
define([
|
||||
|
||||
], function (
|
||||
|
||||
) {
|
||||
const DEFAULT_VIEW_PRIORITY = 100;
|
||||
|
||||
const PRIORITY_LEVELS = {
|
||||
"fallback": Number.NEGATIVE_INFINITY,
|
||||
"default": -100,
|
||||
"none": 0,
|
||||
"optional": DEFAULT_VIEW_PRIORITY,
|
||||
"preferred": 1000,
|
||||
"mandatory": Number.POSITIVE_INFINITY
|
||||
};
|
||||
|
||||
function TypeInspectorViewProvider(typeDefinition, openmct, convertToLegacyObject) {
|
||||
console.warn(`DEPRECATION WARNING: Migrate ${typeDefinition.key} from ${typeDefinition.bundle.path} to use the new Inspector View APIs. Legacy Inspector view support will be removed soon.`);
|
||||
let representation = openmct.$injector.get('representations[]')
|
||||
.filter((r) => r.key === typeDefinition.inspector)[0];
|
||||
|
||||
return {
|
||||
key: representation.key,
|
||||
name: representation.name,
|
||||
cssClass: representation.cssClass,
|
||||
description: representation.description,
|
||||
canView: function (selection) {
|
||||
if (!selection[0] || !selection[0].context.item) {
|
||||
return false;
|
||||
}
|
||||
let domainObject = selection[0].context.item;
|
||||
return domainObject.type === typeDefinition.key;
|
||||
},
|
||||
view: function (selection) {
|
||||
let domainObject = selection[0].context.item;
|
||||
let $rootScope = openmct.$injector.get('$rootScope');
|
||||
let templateLinker = openmct.$injector.get('templateLinker');
|
||||
let scope = $rootScope.$new();
|
||||
let legacyObject = convertToLegacyObject(domainObject);
|
||||
let isDestroyed = false;
|
||||
scope.domainObject = legacyObject;
|
||||
scope.model = legacyObject.getModel();
|
||||
|
||||
|
||||
return {
|
||||
show: function (container) {
|
||||
// TODO: implement "gestures" support ?
|
||||
let uses = representation.uses || [];
|
||||
let promises = [];
|
||||
let results = uses.map(function (capabilityKey, i) {
|
||||
let result = legacyObject.useCapability(capabilityKey);
|
||||
if (result.then) {
|
||||
promises.push(result.then(function (r) {
|
||||
results[i] = r;
|
||||
}));
|
||||
}
|
||||
return result;
|
||||
});
|
||||
|
||||
function link() {
|
||||
if (isDestroyed) {
|
||||
return;
|
||||
}
|
||||
uses.forEach(function (key, i) {
|
||||
scope[key] = results[i];
|
||||
});
|
||||
templateLinker.link(
|
||||
scope,
|
||||
openmct.$angular.element(container),
|
||||
representation
|
||||
);
|
||||
container.style.height = '100%';
|
||||
}
|
||||
|
||||
if (promises.length) {
|
||||
Promise.all(promises)
|
||||
.then(function () {
|
||||
link();
|
||||
scope.$digest();
|
||||
});
|
||||
} else {
|
||||
link();
|
||||
}
|
||||
},
|
||||
destroy: function () {
|
||||
scope.$destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
return TypeInspectorViewProvider;
|
||||
|
||||
});
|
@ -1,31 +0,0 @@
|
||||
define([
|
||||
'./LegacyViewProvider',
|
||||
'./TypeInspectorViewProvider',
|
||||
'../../api/objects/object-utils'
|
||||
], function (
|
||||
LegacyViewProvider,
|
||||
TypeInspectorViewProvider,
|
||||
objectUtils
|
||||
) {
|
||||
function installLegacyViews(openmct, legacyViews, instantiate) {
|
||||
|
||||
function convertToLegacyObject(domainObject) {
|
||||
let keyString = objectUtils.makeKeyString(domainObject.identifier);
|
||||
let oldModel = objectUtils.toOldFormat(domainObject);
|
||||
return instantiate(oldModel, keyString);
|
||||
}
|
||||
|
||||
legacyViews.forEach(function (legacyView) {
|
||||
openmct.objectViews.addProvider(new LegacyViewProvider(legacyView, openmct, convertToLegacyObject));
|
||||
});
|
||||
|
||||
let inspectorTypes = openmct.$injector.get('types[]')
|
||||
.filter((t) => t.hasOwnProperty('inspector'));
|
||||
|
||||
inspectorTypes.forEach(function (typeDefinition) {
|
||||
openmct.inspectorViews.addProvider(new TypeInspectorViewProvider(typeDefinition, openmct, convertToLegacyObject));
|
||||
});
|
||||
}
|
||||
|
||||
return installLegacyViews;
|
||||
});
|
@ -1,83 +0,0 @@
|
||||
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import EventEmitter from 'EventEmitter';
|
||||
|
||||
export default class Editor extends EventEmitter {
|
||||
constructor(openmct) {
|
||||
super();
|
||||
this.editing = false;
|
||||
this.openmct = openmct;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiate an editing session. This will start a transaction during
|
||||
* which any persist operations will be deferred until either save()
|
||||
* or finish() are called.
|
||||
*/
|
||||
edit() {
|
||||
this.editing = true;
|
||||
this.getTransactionService().startTransaction();
|
||||
this.emit('isEditing', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns true if the application is in edit mode, false otherwise.
|
||||
*/
|
||||
isEditing() {
|
||||
return this.editing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save any unsaved changes from this editing session. This will
|
||||
* end the current transaction.
|
||||
*/
|
||||
save() {
|
||||
return this.getTransactionService().commit().then((result)=>{
|
||||
this.editing = false;
|
||||
this.emit('isEditing', false);
|
||||
return result
|
||||
}).catch((error)=>{
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* End the currently active transaction and discard unsaved changes.
|
||||
*/
|
||||
cancel() {
|
||||
this.getTransactionService().cancel();
|
||||
this.editing = false;
|
||||
this.emit('isEditing', false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
getTransactionService() {
|
||||
if (!this.transactionService) {
|
||||
this.transactionService = this.openmct.$injector.get('transactionService');
|
||||
}
|
||||
return this.transactionService;
|
||||
}
|
||||
}
|
@ -28,9 +28,7 @@ define([
|
||||
'./ui/Dialog',
|
||||
'./ui/GestureAPI',
|
||||
'./telemetry/TelemetryAPI',
|
||||
'./indicators/IndicatorAPI',
|
||||
'./Editor'
|
||||
|
||||
'./indicators/IndicatorAPI'
|
||||
], function (
|
||||
TimeAPI,
|
||||
ObjectAPI,
|
||||
@ -39,8 +37,7 @@ define([
|
||||
Dialog,
|
||||
GestureAPI,
|
||||
TelemetryAPI,
|
||||
IndicatorAPI,
|
||||
EditorAPI
|
||||
IndicatorAPI
|
||||
) {
|
||||
return {
|
||||
TimeAPI: TimeAPI,
|
||||
@ -50,7 +47,6 @@ define([
|
||||
TypeRegistry: TypeRegistry,
|
||||
GestureAPI: GestureAPI,
|
||||
TelemetryAPI: TelemetryAPI,
|
||||
IndicatorAPI: IndicatorAPI,
|
||||
EditorAPI: EditorAPI
|
||||
IndicatorAPI: IndicatorAPI
|
||||
};
|
||||
});
|
||||
|
@ -32,9 +32,6 @@ define(function () {
|
||||
*/
|
||||
function Type(definition) {
|
||||
this.definition = definition;
|
||||
if (definition.key) {
|
||||
this.key = definition.key;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -73,29 +70,5 @@ define(function () {
|
||||
return def;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a type definition from a legacy definition.
|
||||
*/
|
||||
Type.definitionFromLegacyDefinition = function (legacyDefinition) {
|
||||
let definition = {};
|
||||
definition.name = legacyDefinition.name;
|
||||
definition.cssClass = legacyDefinition.cssClass;
|
||||
definition.description = legacyDefinition.description;
|
||||
definition.form = legacyDefinition.properties;
|
||||
if (legacyDefinition.model) {
|
||||
definition.initialize = function (model) {
|
||||
for (let [k, v] of Object.entries(legacyDefinition.model)) {
|
||||
model[k] = JSON.parse(JSON.stringify(v));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (legacyDefinition.features && legacyDefinition.features.includes("creation")) {
|
||||
definition.creatable = true;
|
||||
}
|
||||
|
||||
return definition;
|
||||
};
|
||||
|
||||
return Type;
|
||||
});
|
||||
|
@ -98,14 +98,6 @@ define(['./Type'], function (Type) {
|
||||
return this.types[typeKey];
|
||||
};
|
||||
|
||||
TypeRegistry.prototype.importLegacyTypes = function (types) {
|
||||
types.filter((t) => !this.get(t.key))
|
||||
.forEach((type) => {
|
||||
let def = Type.definitionFromLegacyDefinition(type);
|
||||
this.addType(type.key, def);
|
||||
});
|
||||
}
|
||||
|
||||
return TypeRegistry;
|
||||
});
|
||||
|
||||
|
@ -55,7 +55,11 @@ define([
|
||||
'../platform/exporters/bundle',
|
||||
'../platform/features/clock/bundle',
|
||||
'../platform/features/fixed/bundle',
|
||||
'../platform/features/conductor/core/bundle',
|
||||
'../platform/features/conductor/compatibility/bundle',
|
||||
'../platform/features/imagery/bundle',
|
||||
'../platform/features/layout/bundle',
|
||||
'../platform/features/listview/bundle',
|
||||
'../platform/features/my-items/bundle',
|
||||
'../platform/features/pages/bundle',
|
||||
'../platform/features/hyperlink/bundle',
|
||||
@ -98,6 +102,8 @@ define([
|
||||
'platform/features/clock',
|
||||
'platform/features/fixed',
|
||||
'platform/features/imagery',
|
||||
'platform/features/layout',
|
||||
'platform/features/listview',
|
||||
'platform/features/pages',
|
||||
'platform/features/hyperlink',
|
||||
'platform/features/timeline',
|
||||
|
@ -20,18 +20,20 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import CSV from 'comma-separated-values';
|
||||
import {saveAs} from 'file-saver/FileSaver';
|
||||
|
||||
class CSVExporter {
|
||||
export(rows, options) {
|
||||
let headers = (options && options.headers) ||
|
||||
(Object.keys((rows[0] || {})).sort());
|
||||
let filename = (options && options.filename) || "export.csv";
|
||||
let csvText = new CSV(rows, { header: headers }).encode();
|
||||
let blob = new Blob([csvText], { type: "text/csv" });
|
||||
saveAs(blob, filename);
|
||||
define([
|
||||
'csv',
|
||||
'saveAs'
|
||||
], function (CSV, saveAs) {
|
||||
class CSVExporter {
|
||||
export(rows, options) {
|
||||
let headers = (options && options.headers) ||
|
||||
(Object.keys((rows[0] || {})).sort());
|
||||
let filename = (options && options.filename) || "export.csv";
|
||||
let csvText = new CSV(rows, { header: headers }).encode();
|
||||
let blob = new Blob([csvText], { type: "text/csv" });
|
||||
saveAs(blob, filename);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default CSVExporter;
|
||||
return CSVExporter;
|
||||
});
|
@ -23,7 +23,7 @@
|
||||
define([
|
||||
'./AutoflowTabularController',
|
||||
'./AutoflowTabularConstants',
|
||||
'./VueView',
|
||||
'../../ui/VueView',
|
||||
'./autoflow-tabular.html'
|
||||
], function (
|
||||
AutoflowTabularController,
|
||||
|
@ -1,331 +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.
|
||||
*****************************************************************************/
|
||||
|
||||
<template>
|
||||
<div class="l-layout"
|
||||
@dragover="handleDragOver"
|
||||
@click="bypassSelection"
|
||||
@drop="handleDrop">
|
||||
<div class="l-layout__object">
|
||||
<!-- Background grid -->
|
||||
<div class="l-layout__grid-holder c-grid"
|
||||
v-if="!drilledIn">
|
||||
<div class="c-grid__x l-grid l-grid-x"
|
||||
v-if="gridSize[0] >= 3"
|
||||
:style="[{ backgroundSize: gridSize[0] + 'px 100%' }]">
|
||||
</div>
|
||||
<div class="c-grid__y l-grid l-grid-y"
|
||||
v-if="gridSize[1] >= 3"
|
||||
:style="[{ backgroundSize: '100%' + gridSize[1] + 'px' }]"></div>
|
||||
</div>
|
||||
<layout-frame v-for="item in frameItems"
|
||||
class="l-layout__frame"
|
||||
:key="item.id"
|
||||
:item="item"
|
||||
:gridSize="gridSize"
|
||||
@drilledIn="updateDrilledInState"
|
||||
@dragInProgress="updatePosition"
|
||||
@endDrag="endDrag">
|
||||
</layout-frame>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import "~styles/sass-base";
|
||||
|
||||
.l-layout,
|
||||
.c-grid,
|
||||
.c-grid__x,
|
||||
.c-grid__y {
|
||||
@include abs();
|
||||
}
|
||||
|
||||
.l-layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&__grid-holder {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&__object {
|
||||
flex: 1 1 auto;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
&__frame {
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
|
||||
.c-grid {
|
||||
z-index: -1;
|
||||
pointer-events: none;
|
||||
|
||||
&__x { @include bgTicks($colorGridLines, 'x'); }
|
||||
&__y { @include bgTicks($colorGridLines, 'y'); }
|
||||
}
|
||||
|
||||
.is-editing {
|
||||
.l-shell__main-container > .l-layout {
|
||||
// Target the top-most layout container and color its background
|
||||
background: rgba($editColor, 0.1);
|
||||
}
|
||||
|
||||
.c-frame,
|
||||
.l-layout {
|
||||
&.s-selected,
|
||||
&.s-selected-parent {
|
||||
[class*="__grid-holder"] {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
<script>
|
||||
import LayoutFrame from './LayoutFrame.vue';
|
||||
|
||||
const DEFAULT_GRID_SIZE = [32, 32],
|
||||
DEFAULT_DIMENSIONS = [12, 8],
|
||||
DEFAULT_POSITION = [0, 0],
|
||||
MINIMUM_FRAME_SIZE = [320, 180],
|
||||
DEFAULT_HIDDEN_FRAME_TYPES = [
|
||||
'hyperlink'
|
||||
];
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
gridSize: [],
|
||||
frameItems: [],
|
||||
frames: [],
|
||||
frameStyles: [],
|
||||
rawPositions: {},
|
||||
initSelect: true,
|
||||
drilledIn: undefined
|
||||
}
|
||||
},
|
||||
inject: ['openmct'],
|
||||
props: ['domainObject'],
|
||||
components: {
|
||||
LayoutFrame
|
||||
},
|
||||
created: function () {
|
||||
this.newDomainObject = this.domainObject;
|
||||
this.gridSize = this.newDomainObject.layoutGrid || DEFAULT_GRID_SIZE;
|
||||
this.composition = this.openmct.composition.get(this.newDomainObject);
|
||||
let panels = (((this.newDomainObject.configuration || {}).layout || {}).panels || {});
|
||||
|
||||
if (this.composition !== undefined) {
|
||||
this.composition.load().then((composition) => {
|
||||
composition.forEach(function (domainObject) {
|
||||
this.readLayoutConfiguration(domainObject, panels);
|
||||
this.makeFrameItem(domainObject, false);
|
||||
}.bind(this));
|
||||
this.composition.on('add', this.onAddComposition);
|
||||
this.composition.on('remove', this.onRemoveComposition);
|
||||
});
|
||||
}
|
||||
|
||||
this.unlisten = this.openmct.objects.observe(this.newDomainObject, '*', function (obj) {
|
||||
this.newDomainObject = JSON.parse(JSON.stringify(obj));
|
||||
this.gridSize = this.newDomainObject.layoutGrid || DEFAULT_GRID_SIZE;;
|
||||
}.bind(this));
|
||||
},
|
||||
methods: {
|
||||
readLayoutConfiguration(domainObject, panels) {
|
||||
let id = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||
this.rawPositions[id] = {
|
||||
position: panels[id].position || DEFAULT_POSITION,
|
||||
dimensions: panels[id].dimensions || this.defaultDimensions()
|
||||
};
|
||||
this.frameStyles[id] = this.convertPosition(this.rawPositions[id]);
|
||||
this.frames[id] = panels[id].hasOwnProperty('hasFrame') ?
|
||||
panels[id].hasFrame :
|
||||
this.hasFrameByDefault(domainObject.type);
|
||||
},
|
||||
makeFrameItem(domainObject, initSelect) {
|
||||
let id = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||
this.frameItems.push({
|
||||
id: id,
|
||||
hasFrame: this.frames[id],
|
||||
domainObject,
|
||||
style: this.frameStyles[id],
|
||||
drilledIn: this.isDrilledIn(id),
|
||||
initSelect: initSelect,
|
||||
rawPosition: this.rawPositions[id]
|
||||
});
|
||||
},
|
||||
onAddComposition(domainObject) {
|
||||
let id = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||
this.rawPositions[id] = {
|
||||
position: [
|
||||
Math.floor(this.droppedObjectPosition.x / this.gridSize[0]),
|
||||
Math.floor(this.droppedObjectPosition.y / this.gridSize[1])
|
||||
],
|
||||
dimensions: this.defaultDimensions()
|
||||
};
|
||||
this.frameStyles[id] = this.convertPosition(this.rawPositions[id]);
|
||||
this.frames[id] = this.hasFrameByDefault(domainObject.type);
|
||||
|
||||
let newPanel = this.rawPositions[id];
|
||||
newPanel.hasFrame = this.frames[id];
|
||||
this.mutate("configuration.layout.panels[" + id + "]", newPanel);
|
||||
this.makeFrameItem(domainObject, true);
|
||||
},
|
||||
onRemoveComposition(identifier) {
|
||||
// TODO: remove the object from frameItems
|
||||
},
|
||||
defaultDimensions() {
|
||||
let gridSize = this.gridSize;
|
||||
return MINIMUM_FRAME_SIZE.map(function (min, i) {
|
||||
return Math.max(
|
||||
Math.ceil(min / gridSize[i]),
|
||||
DEFAULT_DIMENSIONS[i]
|
||||
);
|
||||
});
|
||||
},
|
||||
convertPosition(raw) {
|
||||
return {
|
||||
left: (this.gridSize[0] * raw.position[0]) + 'px',
|
||||
top: (this.gridSize[1] * raw.position[1]) + 'px',
|
||||
width: (this.gridSize[0] * raw.dimensions[0]) + 'px',
|
||||
height: (this.gridSize[1] * raw.dimensions[1]) + 'px',
|
||||
minWidth: (this.gridSize[0] * raw.dimensions[0]) + 'px',
|
||||
minHeight: (this.gridSize[1] * raw.dimensions[1]) + 'px'
|
||||
};
|
||||
},
|
||||
/**
|
||||
* Checks if the frame should be hidden or not.
|
||||
*
|
||||
* @param type the domain object type
|
||||
* @return {boolean} true if the object should have
|
||||
* frame by default, false, otherwise
|
||||
*/
|
||||
hasFrameByDefault(type) {
|
||||
return DEFAULT_HIDDEN_FRAME_TYPES.indexOf(type) === -1;
|
||||
},
|
||||
setSelection(selection) {
|
||||
if (selection.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateDrilledInState();
|
||||
},
|
||||
updateDrilledInState(id) {
|
||||
this.drilledIn = id;
|
||||
this.frameItems.forEach(function (item) {
|
||||
item.drilledIn = item.id === id;
|
||||
});
|
||||
},
|
||||
isDrilledIn(id) {
|
||||
return this.drilledIn === id;
|
||||
},
|
||||
updatePosition(id, newPosition) {
|
||||
let newStyle = this.convertPosition(newPosition);
|
||||
this.frameStyles[id] = newStyle;
|
||||
this.rawPositions[id] = newPosition;
|
||||
this.frameItems.forEach(function (item) {
|
||||
if (item.id === id) {
|
||||
item.style = newStyle;
|
||||
item.rawPosition = newPosition;
|
||||
}
|
||||
});
|
||||
},
|
||||
bypassSelection($event) {
|
||||
if (this.dragInProgress) {
|
||||
if ($event) {
|
||||
$event.stopImmediatePropagation();
|
||||
}
|
||||
return;
|
||||
}
|
||||
},
|
||||
endDrag(id) {
|
||||
this.dragInProgress = true;
|
||||
setTimeout(function () {
|
||||
this.dragInProgress = false;
|
||||
}.bind(this), 0);
|
||||
|
||||
let path = "configuration.layout.panels[" + id + "]";
|
||||
this.mutate(path + ".dimensions", this.rawPositions[id].dimensions);
|
||||
this.mutate(path + ".position", this.rawPositions[id].position);
|
||||
},
|
||||
mutate(path, value) {
|
||||
this.openmct.objects.mutate(this.newDomainObject, path, value);
|
||||
},
|
||||
handleDrop($event) {
|
||||
$event.preventDefault();
|
||||
|
||||
let child = JSON.parse($event.dataTransfer.getData('domainObject'));
|
||||
let duplicates = [];
|
||||
let composition = this.newDomainObject.composition;
|
||||
composition.forEach((object) => {
|
||||
if (this.openmct.objects.makeKeyString(JSON.parse(JSON.stringify(object))) ===
|
||||
this.openmct.objects.makeKeyString(child.identifier)) {
|
||||
duplicates.push(object);
|
||||
}
|
||||
});
|
||||
|
||||
// Disallow adding a duplicate object to the composition
|
||||
if (duplicates.length !== 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let elementRect = this.$el.getBoundingClientRect();
|
||||
this.droppedObjectPosition = {
|
||||
x: $event.pageX - elementRect.left,
|
||||
y: $event.pageY - elementRect.top
|
||||
}
|
||||
// TODO: use the composition API to add child once the default composition
|
||||
// provider supports it instead of mutating the composition directly.
|
||||
// this.composition.add(child).
|
||||
composition.push(child.identifier);
|
||||
this.mutate('composition', composition);
|
||||
},
|
||||
handleDragOver($event){
|
||||
$event.preventDefault();
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.removeSelectable = this.openmct.selection.selectable(
|
||||
this.$el,
|
||||
{
|
||||
item: this.newDomainObject
|
||||
},
|
||||
this.initSelect
|
||||
);
|
||||
this.openmct.selection.on('change', this.setSelection);
|
||||
},
|
||||
destroyed: function () {
|
||||
this.composition.off('add', this.onAddComposition);
|
||||
this.composition.off('remove', this.onRemoveComposition);
|
||||
this.openmct.off('change', this.selection);
|
||||
this.removeSelectable();
|
||||
this.unlisten();
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
@ -1,115 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
|
||||
/**
|
||||
* Handles drag interactions on frames in layouts. This will
|
||||
* provides new positions/dimensions for frames based on
|
||||
* relative pixel positions provided; these will take into account
|
||||
* the grid size (in a snap-to sense) and will enforce some minimums
|
||||
* on both position and dimensions.
|
||||
*
|
||||
* The provided position and dimensions factors will determine
|
||||
* whether this is a move or a resize, and what type of resize it
|
||||
* will be. For instance, a position factor of [1, 1]
|
||||
* will move a frame along with the mouse as the drag
|
||||
* proceeds, while a dimension factor of [0, 0] will leave
|
||||
* dimensions unchanged. Combining these in different
|
||||
* ways results in different handles; a position factor of
|
||||
* [1, 0] and a dimensions factor of [-1, 0] will implement
|
||||
* a left-edge resize, as the horizontal position will move
|
||||
* with the mouse while the horizontal dimensions shrink in
|
||||
* kind (and vertical properties remain unmodified.)
|
||||
*
|
||||
* @param {object} rawPosition the initial position/dimensions
|
||||
* of the frame being interacted with
|
||||
* @param {number[]} posFactor the position factor
|
||||
* @param {number[]} dimFactor the dimensions factor
|
||||
* @param {number[]} the size of each grid element, in pixels
|
||||
* @constructor
|
||||
* @memberof platform/features/layout
|
||||
*/
|
||||
function LayoutDrag(rawPosition, posFactor, dimFactor, gridSize) {
|
||||
this.rawPosition = rawPosition;
|
||||
this.posFactor = posFactor;
|
||||
this.dimFactor = dimFactor;
|
||||
this.gridSize = gridSize;
|
||||
}
|
||||
|
||||
// Convert a delta from pixel coordinates to grid coordinates,
|
||||
// rounding to whole-number grid coordinates.
|
||||
function toGridDelta(gridSize, pixelDelta) {
|
||||
return pixelDelta.map(function (v, i) {
|
||||
return Math.round(v / gridSize[i]);
|
||||
});
|
||||
}
|
||||
|
||||
// Utility function to perform element-by-element multiplication
|
||||
function multiply(array, factors) {
|
||||
return array.map(function (v, i) {
|
||||
return v * factors[i];
|
||||
});
|
||||
}
|
||||
|
||||
// Utility function to perform element-by-element addition
|
||||
function add(array, other) {
|
||||
return array.map(function (v, i) {
|
||||
return v + other[i];
|
||||
});
|
||||
}
|
||||
|
||||
// Utility function to perform element-by-element max-choosing
|
||||
function max(array, other) {
|
||||
return array.map(function (v, i) {
|
||||
return Math.max(v, other[i]);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get a new position object in grid coordinates, with
|
||||
* position and dimensions both offset appropriately
|
||||
* according to the factors supplied in the constructor.
|
||||
* @param {number[]} pixelDelta the offset from the
|
||||
* original position, in pixels
|
||||
*/
|
||||
LayoutDrag.prototype.getAdjustedPosition = function (pixelDelta) {
|
||||
var gridDelta = toGridDelta(this.gridSize, pixelDelta);
|
||||
return {
|
||||
position: max(add(
|
||||
this.rawPosition.position,
|
||||
multiply(gridDelta, this.posFactor)
|
||||
), [0, 0]),
|
||||
dimensions: max(add(
|
||||
this.rawPosition.dimensions,
|
||||
multiply(gridDelta, this.dimFactor)
|
||||
), [1, 1])
|
||||
};
|
||||
};
|
||||
|
||||
return LayoutDrag;
|
||||
|
||||
}
|
||||
);
|
@ -1,338 +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.
|
||||
*****************************************************************************/
|
||||
|
||||
<template>
|
||||
<div class="c-frame has-local-controls is-selectable is-moveable"
|
||||
:style="item.style"
|
||||
:class="classObject"
|
||||
@dblclick="drill(item.id, $event)">
|
||||
<div class="c-frame__header">
|
||||
<div class="c-frame__header__start">
|
||||
<div class="c-frame__name icon-object">{{ item.domainObject.name }}</div>
|
||||
<div class="c-frame__context-actions c-disclosure-button"></div>
|
||||
</div>
|
||||
<div class="c-frame__header__end">
|
||||
<div class="c-button icon-expand local-controls--hidden"></div>
|
||||
</div>
|
||||
</div>
|
||||
<object-view class="c-frame__object-view"
|
||||
:object="item.domainObject"></object-view>
|
||||
|
||||
<!-- Drag handles -->
|
||||
<div class="c-frame-edit">
|
||||
<div class="c-frame-edit__move"
|
||||
@mousedown="startDrag([1,1], [0,0], $event)"></div>
|
||||
<div class="c-frame-edit__handle --nw"
|
||||
@mousedown="startDrag([1,1], [-1,-1], $event)"></div>
|
||||
<div class="c-frame-edit__handle --ne"
|
||||
@mousedown="startDrag([0,1], [1,-1], $event)"></div>
|
||||
<div class="c-frame-edit__handle --sw"
|
||||
@mousedown="startDrag([1,0], [-1,1], $event)"></div>
|
||||
<div class="c-frame-edit__handle --se"
|
||||
@mousedown="startDrag([0,0], [1,1], $event)"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import "~styles/sass-base";
|
||||
|
||||
/******************************* FRAME */
|
||||
.c-frame {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-width: 1px;
|
||||
border-color: transparent;
|
||||
|
||||
/*************************** HEADER */
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 0 0 auto;
|
||||
margin-bottom: $interiorMargin;
|
||||
|
||||
> [class*="__"] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
> * + * {
|
||||
margin-left: $interiorMargin;
|
||||
}
|
||||
|
||||
[class*="__start"] {
|
||||
flex: 1 1 auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
[class*="__end"] {
|
||||
//justify-content: flex-end;
|
||||
flex: 0 0 auto;
|
||||
|
||||
[class*="button"] {
|
||||
font-size: 0.7em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__name {
|
||||
@include ellipsize();
|
||||
flex: 0 1 auto;
|
||||
font-size: 1.2em;
|
||||
|
||||
&:before {
|
||||
// Object type icon
|
||||
flex: 0 0 auto;
|
||||
margin-right: $interiorMarginSm;
|
||||
}
|
||||
}
|
||||
|
||||
/*************************** OBJECT VIEW */
|
||||
&__object-view {
|
||||
flex: 1 1 auto;
|
||||
overflow: auto;
|
||||
|
||||
.c-object-view {
|
||||
.u-fills-container {
|
||||
// Expand component types that fill a container
|
||||
@include abs();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*************************** NO-FRAME */
|
||||
&.no-frame {
|
||||
> [class*="__header"] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.no-frame) {
|
||||
background: $colorBodyBg;
|
||||
border: 1px solid $colorInteriorBorder;
|
||||
padding: $interiorMargin;
|
||||
}
|
||||
|
||||
/*************************** SELECTION */
|
||||
&.is-selectable {
|
||||
&:hover {
|
||||
box-shadow: $browseShdwSelectableHov;
|
||||
}
|
||||
}
|
||||
|
||||
&.s-selected, // LEGACY
|
||||
&.is-selected {
|
||||
border: $browseBorderSelected;
|
||||
}
|
||||
}
|
||||
|
||||
/*************************** EDITING */
|
||||
.is-editing {
|
||||
.c-frame {
|
||||
&:not(.is-drilled-in).is-selectable {
|
||||
border: $editBorderSelectable;
|
||||
|
||||
&:hover {
|
||||
border: $editBorderSelectableHov;
|
||||
}
|
||||
|
||||
&.s-selected,
|
||||
&.is-selected {
|
||||
border: $editBorderSelected;
|
||||
|
||||
> .c-frame-edit {
|
||||
display: block; // Show the editing rect and handles
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.is-drilled-in {
|
||||
border: $editBorderDrilledIn;
|
||||
}
|
||||
|
||||
.u-links {
|
||||
// Applied in markup to objects that provide links. Disable while editing.
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.c-frame-edit {
|
||||
// The editing rect and handles
|
||||
$z: 10;
|
||||
|
||||
@include abs();
|
||||
box-shadow: rgba($editColor, 0.5) 0 0 10px;
|
||||
display: none;
|
||||
|
||||
&__move {
|
||||
@include abs();
|
||||
cursor: move;
|
||||
z-index: $z;
|
||||
}
|
||||
|
||||
&__handle {
|
||||
$d: 8px;
|
||||
$o: floor($d * -0.5);
|
||||
background: rgba($editColor, 0.3);
|
||||
border: 1px solid $editColor;
|
||||
position: absolute;
|
||||
width: $d; height: $d;
|
||||
top: auto; right: auto; bottom: auto; left: auto;
|
||||
z-index: $z + 1;
|
||||
|
||||
&:before {
|
||||
// Extended hit area
|
||||
$m: -5px;
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: $m; right: $m; bottom: $m; left: $m;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $editColor;
|
||||
}
|
||||
|
||||
&.--nw {
|
||||
cursor: nw-resize;
|
||||
left: $o; top: $o;
|
||||
}
|
||||
|
||||
&.--ne {
|
||||
cursor: ne-resize;
|
||||
right: $o; top: $o;
|
||||
}
|
||||
|
||||
&.--se {
|
||||
cursor: se-resize;
|
||||
right: $o; bottom: $o;
|
||||
}
|
||||
|
||||
&.--sw {
|
||||
cursor: sw-resize;
|
||||
left: $o; bottom: $o;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
|
||||
<script>
|
||||
import ObjectView from '../../ui/components/layout/ObjectView.vue'
|
||||
import LayoutDrag from './LayoutDrag'
|
||||
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
props: {
|
||||
item: Object,
|
||||
gridSize: Array
|
||||
},
|
||||
components: {
|
||||
ObjectView
|
||||
},
|
||||
computed: {
|
||||
classObject: function () {
|
||||
return {
|
||||
'is-drilled-in': this.item.drilledIn,
|
||||
'no-frame': !this.item.hasFrame
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
drill(id, $event) {
|
||||
if ($event) {
|
||||
$event.stopPropagation();
|
||||
}
|
||||
|
||||
if (!this.isBeingEdited(this.item.domainObject)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.openmct.composition.get(this.item.domainObject) === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Disable for fixed position.
|
||||
if (this.item.domainObject.type === 'telemetry.fixed') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$emit('drilledIn', id);
|
||||
},
|
||||
isBeingEdited(object) {
|
||||
// TODO: add logic when inEditContext() is implemented in Vue.
|
||||
return true;
|
||||
},
|
||||
updatePosition(event) {
|
||||
let currentPosition = [event.pageX, event.pageY];
|
||||
this.initialPosition = this.initialPosition || currentPosition;
|
||||
this.delta = currentPosition.map(function (value, index) {
|
||||
return value - this.initialPosition[index];
|
||||
}.bind(this));
|
||||
},
|
||||
startDrag(posFactor, dimFactor, event) {
|
||||
document.body.addEventListener('mousemove', this.continueDrag);
|
||||
document.body.addEventListener('mouseup', this.endDrag);
|
||||
|
||||
this.updatePosition(event);
|
||||
this.activeDrag = new LayoutDrag(
|
||||
this.item.rawPosition,
|
||||
posFactor,
|
||||
dimFactor,
|
||||
this.gridSize
|
||||
);
|
||||
event.preventDefault();
|
||||
},
|
||||
continueDrag(event) {
|
||||
event.preventDefault();
|
||||
this.updatePosition(event);
|
||||
|
||||
if (this.activeDrag) {
|
||||
this.$emit('dragInProgress', this.item.id, this.activeDrag.getAdjustedPosition(this.delta));
|
||||
}
|
||||
},
|
||||
endDrag(event) {
|
||||
document.body.removeEventListener('mousemove', this.continueDrag);
|
||||
document.body.removeEventListener('mouseup', this.endDrag);
|
||||
this.continueDrag(event);
|
||||
this.$emit('endDrag', this.item.id);
|
||||
this.initialPosition = undefined;
|
||||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.removeSelectable = this.openmct.selection.selectable(
|
||||
this.$el,
|
||||
{
|
||||
item: this.item.domainObject
|
||||
},
|
||||
this.item.initSelect
|
||||
);
|
||||
},
|
||||
destroyed() {
|
||||
this.removeSelectable();
|
||||
}
|
||||
}
|
||||
</script>
|
@ -1,67 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import Layout from './DisplayLayout.vue'
|
||||
import Vue from 'vue'
|
||||
import objectUtils from '../../api/objects/object-utils.js'
|
||||
import DisplayLayoutType from './DisplayLayoutType.js'
|
||||
|
||||
export default function () {
|
||||
return function (openmct) {
|
||||
openmct.objectViews.addProvider({
|
||||
key: 'layout.view',
|
||||
canView: function (domainObject) {
|
||||
return domainObject.type === 'layout';
|
||||
},
|
||||
view: function (domainObject) {
|
||||
let component;
|
||||
return {
|
||||
show(container) {
|
||||
component = new Vue({
|
||||
components: {
|
||||
Layout
|
||||
},
|
||||
template: '<layout :domain-object="domainObject"></layout>',
|
||||
provide: {
|
||||
openmct,
|
||||
objectUtils
|
||||
},
|
||||
el: container,
|
||||
data () {
|
||||
return {
|
||||
domainObject: domainObject
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
destroy() {
|
||||
component.$destroy();
|
||||
}
|
||||
};
|
||||
},
|
||||
priority() {
|
||||
return 100;
|
||||
}
|
||||
});
|
||||
openmct.types.addType('layout', DisplayLayoutType());
|
||||
}
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
'./components/GridView.vue',
|
||||
'vue'
|
||||
], function (
|
||||
GridViewComponent,
|
||||
Vue
|
||||
) {
|
||||
function FolderGridView(openmct) {
|
||||
return {
|
||||
key: 'grid',
|
||||
name: 'Grid Vue',
|
||||
cssClass: 'icon-thumbs-strip',
|
||||
canView: function (domainObject) {
|
||||
return domainObject.type === 'folder';
|
||||
},
|
||||
view: function (domainObject) {
|
||||
let component;
|
||||
|
||||
return {
|
||||
show: function (element) {
|
||||
component = new Vue({
|
||||
components: {
|
||||
gridViewComponent: GridViewComponent.default
|
||||
},
|
||||
provide: {
|
||||
openmct,
|
||||
domainObject
|
||||
},
|
||||
el: element,
|
||||
template: '<grid-view-component></grid-view-component>'
|
||||
});
|
||||
},
|
||||
destroy: function (element) {
|
||||
component.$destroy();
|
||||
component = undefined;
|
||||
}
|
||||
};
|
||||
},
|
||||
priority: function () {
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
}
|
||||
return FolderGridView;
|
||||
});
|
@ -1,70 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
'./components/ListView.vue',
|
||||
'vue',
|
||||
'moment'
|
||||
], function (
|
||||
ListViewComponent,
|
||||
Vue,
|
||||
Moment
|
||||
) {
|
||||
function FolderListView(openmct) {
|
||||
return {
|
||||
key: 'list-view',
|
||||
name: 'List Vue',
|
||||
cssClass: 'icon-list-view',
|
||||
canView: function (domainObject) {
|
||||
return domainObject.type === 'folder';
|
||||
},
|
||||
view: function (domainObject) {
|
||||
let component;
|
||||
|
||||
return {
|
||||
show: function (element) {
|
||||
component = new Vue({
|
||||
components: {
|
||||
listViewComponent: ListViewComponent.default
|
||||
},
|
||||
provide: {
|
||||
openmct,
|
||||
domainObject,
|
||||
Moment
|
||||
},
|
||||
el: element,
|
||||
template: '<list-view-component></list-view-component>'
|
||||
});
|
||||
},
|
||||
destroy: function (element) {
|
||||
component.$destroy();
|
||||
component = undefined;
|
||||
}
|
||||
};
|
||||
},
|
||||
priority: function () {
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
}
|
||||
return FolderListView;
|
||||
});
|
@ -1,200 +0,0 @@
|
||||
<template>
|
||||
<div class="l-grid-view">
|
||||
<div v-for="(item, index) in items"
|
||||
v-bind:key="index"
|
||||
class="l-grid-view__item c-grid-item"
|
||||
:class="{ 'is-alias': item.isAlias === true }"
|
||||
@click="navigate(item.model.identifier.key)">
|
||||
<div class="c-grid-item__type-icon"
|
||||
:class="(item.type.cssClass != undefined) ? 'bg-' + item.type.cssClass : 'bg-icon-object-unknown'">
|
||||
</div>
|
||||
<div class="c-grid-item__details">
|
||||
<!-- Name and metadata -->
|
||||
<div class="c-grid-item__name"
|
||||
:title="item.model.name">{{item.model.name}}</div>
|
||||
<div class="c-grid-item__metadata"
|
||||
:title="item.type.name">
|
||||
<span>{{item.type.name}}</span>
|
||||
<span v-if="item.model.composition !== undefined">
|
||||
- {{item.model.composition.length}} item<span v-if="item.model.composition.length !== 1">s</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="c-grid-item__controls">
|
||||
<div class="icon-people" title='Shared'></div>
|
||||
<button class="c-click-icon icon-info c-info-button" title='More Info'></button>
|
||||
<div class="icon-pointer-right c-pointer-icon"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import "~styles/sass-base";
|
||||
|
||||
/******************************* GRID VIEW */
|
||||
.l-grid-view {
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
|
||||
&__item {
|
||||
flex: 0 0 auto;
|
||||
+ .l-grid-view__item { margin-top: $interiorMargin; }
|
||||
}
|
||||
|
||||
body.desktop & {
|
||||
flex-flow: row wrap;
|
||||
&__item {
|
||||
height: $ueBrowseGridItemLg;
|
||||
width: $ueBrowseGridItemLg;
|
||||
margin: 0 $interiorMargin $interiorMargin 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/******************************* GRID ITEMS */
|
||||
.c-grid-item {
|
||||
// Mobile-first
|
||||
@include button($bg: $colorItemBg, $fg: $colorItemFg);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
padding: $interiorMarginLg;
|
||||
|
||||
&__type-icon {
|
||||
filter: $colorKeyFilter;
|
||||
flex: 0 0 32px;
|
||||
font-size: 2em; // Drives the size of the alias indicator when present
|
||||
margin-right: $interiorMarginLg;
|
||||
}
|
||||
|
||||
&.is-alias {
|
||||
// Object is an alias to an original.
|
||||
[class*='__type-icon'] {
|
||||
@include isAlias();
|
||||
color: $colorIconAliasForKeyFilter;
|
||||
}
|
||||
}
|
||||
|
||||
&__details {
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
&__name {
|
||||
@include ellipsize();
|
||||
color: $colorItemFg;
|
||||
font-size: 1.3em;
|
||||
font-weight: 400;
|
||||
margin-bottom: $interiorMarginSm;
|
||||
}
|
||||
|
||||
&__metadata {
|
||||
color: $colorItemFgDetails;
|
||||
}
|
||||
|
||||
&__controls {
|
||||
color: $colorItemFgDetails;
|
||||
flex: 0 0 64px;
|
||||
font-size: 1.2em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
|
||||
> * + * {
|
||||
margin-left: $interiorMargin;
|
||||
}
|
||||
}
|
||||
|
||||
body.desktop & {
|
||||
$transOutMs: 300ms;
|
||||
flex-flow: column nowrap;
|
||||
transition: background $transOutMs ease-in-out;
|
||||
|
||||
&:hover {
|
||||
background: $colorItemBgHov;
|
||||
transition: $transIn;
|
||||
|
||||
.c-grid-item__type-icon {
|
||||
filter: $colorKeyFilterHov;
|
||||
transform: scale(1);
|
||||
transition: $transInBounce;
|
||||
}
|
||||
}
|
||||
|
||||
> * {
|
||||
margin: 0; // Reset from mobile
|
||||
}
|
||||
|
||||
&__controls {
|
||||
align-items: start;
|
||||
flex: 0 0 auto;
|
||||
order: 1;
|
||||
.c-info-button,
|
||||
.c-pointer-icon { display: none; }
|
||||
}
|
||||
|
||||
&__type-icon {
|
||||
flex: 1 1 auto;
|
||||
font-size: 6em; // Drives the size of the alias indicator when present
|
||||
margin: $interiorMargin 22.5%;
|
||||
order: 2;
|
||||
transform: scale(0.9);
|
||||
transform-origin: center;
|
||||
transition: all $transOutMs ease-in-out;
|
||||
}
|
||||
|
||||
&__details {
|
||||
flex: 0 0 auto;
|
||||
justify-content: flex-end;
|
||||
order: 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
inject: ['openmct', 'domainObject'],
|
||||
data() {
|
||||
var items = [],
|
||||
unknownObjectType = {
|
||||
definition: {
|
||||
cssClass: 'icon-object-unknown',
|
||||
name: 'Unknown Type'
|
||||
}
|
||||
};
|
||||
|
||||
var composition = this.openmct.composition.get(this.domainObject);
|
||||
|
||||
if (composition) {
|
||||
|
||||
composition.load().then((array) => {
|
||||
if (Array.isArray(array)) {
|
||||
array.forEach((model) => {
|
||||
var type = this.openmct.types.get(model.type) || unknownObjectType;
|
||||
|
||||
items.push({
|
||||
model: model,
|
||||
type: type.definition,
|
||||
isAlias: this.domainObject.identifier.key !== model.location
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
items: items
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
navigate(identifier) {
|
||||
let currentLocation = this.openmct.router.currentLocation.path,
|
||||
navigateToPath = `${currentLocation}/${identifier}`;
|
||||
|
||||
this.openmct.router.setPath(navigateToPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@ -1,215 +0,0 @@
|
||||
<template>
|
||||
<div class="c-table c-table--sortable c-list-view">
|
||||
<table class="c-table__body">
|
||||
<thead class="c-table__header">
|
||||
<tr>
|
||||
<th class="is-sortable"
|
||||
v-bind:class="[orderByField == 'name' ? 'is-sorting' : '', sortClass]"
|
||||
@click="sortTrigger('name', 'asc')">
|
||||
Name
|
||||
</th>
|
||||
<th class="is-sortable"
|
||||
v-bind:class="[orderByField == 'type' ? 'is-sorting' : '', sortClass]"
|
||||
@click="sortTrigger('type', 'asc')">
|
||||
Type
|
||||
</th>
|
||||
<th class="is-sortable"
|
||||
v-bind:class="[orderByField == 'createdDate' ? 'is-sorting' : '', sortClass]"
|
||||
@click="sortTrigger('createdDate', 'desc')">
|
||||
Created Date
|
||||
</th>
|
||||
<th class="is-sortable"
|
||||
v-bind:class="[orderByField == 'updatedDate' ? 'is-sorting' : '', sortClass]"
|
||||
@click="sortTrigger('updatedDate', 'desc')">
|
||||
Updated Date
|
||||
</th>
|
||||
<th class="is-sortable"
|
||||
v-bind:class="[orderByField == 'items' ? 'is-sorting' : '', sortClass]"
|
||||
@click="sortTrigger('items', 'asc')">
|
||||
Items
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="c-list-item"
|
||||
v-for="(item,index) in sortedItems"
|
||||
v-bind:key="index"
|
||||
:class="{ 'is-alias': item.isAlias === true }"
|
||||
@click="navigate(item.identifier)">
|
||||
<td class="c-list-item__name">
|
||||
<div class="c-list-item__type-icon" :class="(item.cssClass != undefined) ? item.cssClass : 'icon-object-unknown'"></div>
|
||||
{{item.name}}
|
||||
</td>
|
||||
<td class="c-list-item__type">{{ item.type }}</td>
|
||||
<td class="c-list-item__date-created">{{ formatTime(item.createdDate, 'YYYY-MM-DD HH:mm:ss:SSS') }}Z</td>
|
||||
<td class="c-list-item__date-updated">{{ formatTime(item.updatedDate, 'YYYY-MM-DD HH:mm:ss:SSS') }}Z</td>
|
||||
<td class="c-list-item__items">{{ item.items }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import "~styles/sass-base";
|
||||
|
||||
/******************************* LIST VIEW */
|
||||
.c-list-view {
|
||||
overflow-x: auto !important;
|
||||
overflow-y: auto;
|
||||
|
||||
tbody tr {
|
||||
background: $colorListItemBg;
|
||||
transition: $transOut;
|
||||
}
|
||||
|
||||
body.desktop & {
|
||||
tbody tr {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background: $colorListItemBgHov;
|
||||
transition: $transIn;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
$p: floor($interiorMargin * 1.5);
|
||||
font-size: 1.1em;
|
||||
padding-top: $p;
|
||||
padding-bottom: $p;
|
||||
|
||||
&:not(.c-list-item__name) {
|
||||
color: $colorItemFgDetails;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.c-list-item {
|
||||
&__name {
|
||||
@include ellipsize();
|
||||
}
|
||||
|
||||
&__type-icon {
|
||||
color: $colorKey;
|
||||
display: inline-block;
|
||||
width: 1em;
|
||||
margin-right:$interiorMarginSm;
|
||||
}
|
||||
|
||||
&.is-alias {
|
||||
// Object is an alias to an original.
|
||||
[class*='__type-icon'] {
|
||||
&:after {
|
||||
color: $colorIconAlias;
|
||||
content: $glyph-icon-link;
|
||||
font-family: symbolsfont;
|
||||
display: block;
|
||||
position: absolute;
|
||||
text-shadow: rgba(black, 0.5) 0 1px 2px;
|
||||
top: auto; left: -1px; bottom: 1px; right: auto;
|
||||
transform-origin: bottom left;
|
||||
transform: scale(0.65);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/******************************* LIST ITEM */
|
||||
</style>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
inject: ['openmct', 'domainObject', 'Moment'],
|
||||
data() {
|
||||
var items = [],
|
||||
unknownObjectType = {
|
||||
definition: {
|
||||
cssClass: 'icon-object-unknown',
|
||||
name: 'Unknown Type'
|
||||
}
|
||||
},
|
||||
composition = this.openmct.composition.get(this.domainObject);
|
||||
|
||||
if (composition) {
|
||||
|
||||
composition.load().then((array) => {
|
||||
if (Array.isArray(array)) {
|
||||
array.forEach(model => {
|
||||
var type = this.openmct.types.get(model.type) || unknownObjectType;
|
||||
|
||||
items.push({
|
||||
name: model.name,
|
||||
identifier: model.identifier.key,
|
||||
type: type.definition.name,
|
||||
isAlias: false,
|
||||
cssClass: type.definition.cssClass,
|
||||
createdDate: model.persisted,
|
||||
updatedDate: model.modified,
|
||||
items: model.composition ? model.composition.length : 0,
|
||||
isAlias: this.domainObject.identifier.key !== model.location
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
items: items,
|
||||
orderByField: 'name',
|
||||
sortClass: 'asc',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
sortedItems () {
|
||||
if (this.sortClass === 'asc') {
|
||||
return this.items.sort(this.ascending.bind(this));
|
||||
} else if (this.sortClass === 'desc') {
|
||||
return this.items.sort(this.descending.bind(this));
|
||||
}
|
||||
},
|
||||
formatTime () {
|
||||
return function (timestamp, format) {
|
||||
return this.Moment(timestamp).format(format);
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
navigate(identifier) {
|
||||
let currentLocation = this.openmct.router.currentLocation.path,
|
||||
navigateToPath = `${currentLocation}/${identifier}`;
|
||||
|
||||
this.openmct.router.setPath(navigateToPath);
|
||||
},
|
||||
sortTrigger(field, sortOrder) {
|
||||
if (this.orderByField === field) {
|
||||
this.sortClass = (this.sortClass === 'asc') ? 'desc' : 'asc';
|
||||
} else {
|
||||
this.sortClass = sortOrder;
|
||||
}
|
||||
this.orderByField = field;
|
||||
},
|
||||
ascending(first, second) {
|
||||
if (first[this.orderByField] < second[this.orderByField]) {
|
||||
return -1;
|
||||
} else if (first[this.orderByField] > second[this.orderByField]) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
},
|
||||
descending(first, second) {
|
||||
if (first[this.orderByField] > second[this.orderByField]) {
|
||||
return -1;
|
||||
} else if (first[this.orderByField] < second[this.orderByField]) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@ -51,12 +51,64 @@ define([
|
||||
function LocalTimeFormat() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an appropriate time format based on the provided value and
|
||||
* the threshold required.
|
||||
* @private
|
||||
*/
|
||||
function getScaledFormat(d) {
|
||||
var momentified = moment.utc(d);
|
||||
/**
|
||||
* Uses logic from d3 Time-Scales, v3 of the API. See
|
||||
* https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Scales.md
|
||||
*
|
||||
* Licensed
|
||||
*/
|
||||
return [
|
||||
[".SSS", function (m) {
|
||||
return m.milliseconds();
|
||||
}],
|
||||
[":ss", function (m) {
|
||||
return m.seconds();
|
||||
}],
|
||||
["hh:mma", function (m) {
|
||||
return m.minutes();
|
||||
}],
|
||||
["hha", function (m) {
|
||||
return m.hours();
|
||||
}],
|
||||
["ddd DD", function (m) {
|
||||
return m.days() &&
|
||||
m.date() !== 1;
|
||||
}],
|
||||
["MMM DD", function (m) {
|
||||
return m.date() !== 1;
|
||||
}],
|
||||
["MMMM", function (m) {
|
||||
return m.month();
|
||||
}],
|
||||
["YYYY", function () {
|
||||
return true;
|
||||
}]
|
||||
].filter(function (row) {
|
||||
return row[1](momentified);
|
||||
})[0][0];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param value
|
||||
* @param {Scale} [scale] Optionally provides context to the
|
||||
* format request, allowing for scale-appropriate formatting.
|
||||
* @returns {string} the formatted date
|
||||
*/
|
||||
LocalTimeFormat.prototype.format = function (value, scale) {
|
||||
if (scale !== undefined) {
|
||||
var scaledFormat = getScaledFormat(value, scale);
|
||||
if (scaledFormat) {
|
||||
return moment.utc(value).format(scaledFormat);
|
||||
}
|
||||
}
|
||||
return moment(value).format(DATE_FORMAT);
|
||||
};
|
||||
|
||||
|
@ -41,7 +41,7 @@ define([], function () {
|
||||
this.timeFormat = 'local-format';
|
||||
this.durationFormat = 'duration';
|
||||
|
||||
this.isUTCBased = false;
|
||||
this.isUTCBased = true;
|
||||
}
|
||||
|
||||
return LocalTimeSystem;
|
||||
|
@ -1,215 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
"./src/controllers/NotebookController",
|
||||
"./src/controllers/NewEntryController",
|
||||
"./src/controllers/SelectSnapshotController",
|
||||
"./src/actions/NewEntryContextual",
|
||||
"./src/actions/AnnotateSnapshot",
|
||||
"./src/directives/MCTSnapshot",
|
||||
"./src/directives/EntryDnd",
|
||||
"./res/templates/controls/snapSelect.html",
|
||||
"./res/templates/controls/embedControl.html",
|
||||
"./res/templates/annotation.html",
|
||||
"./res/templates/draggedEntry.html"
|
||||
], function (
|
||||
NotebookController,
|
||||
NewEntryController,
|
||||
SelectSnapshotController,
|
||||
newEntryAction,
|
||||
AnnotateSnapshotAction,
|
||||
MCTSnapshotDirective,
|
||||
EntryDndDirective,
|
||||
snapSelectTemplate,
|
||||
embedControlTemplate,
|
||||
annotationTemplate,
|
||||
draggedEntryTemplate
|
||||
) {
|
||||
var installed = false;
|
||||
|
||||
function NotebookPlugin() {
|
||||
return function install(openmct) {
|
||||
if (installed) {
|
||||
return;
|
||||
}
|
||||
|
||||
installed = true;
|
||||
|
||||
openmct.legacyRegistry.register('notebook', {
|
||||
name: 'Notebook Plugin',
|
||||
extensions: {
|
||||
types: [
|
||||
{
|
||||
key: 'notebook',
|
||||
name: 'Notebook',
|
||||
cssClass: 'icon-notebook',
|
||||
description: 'Create and save timestamped notes with embedded object snapshots.',
|
||||
features: 'creation',
|
||||
model: {
|
||||
entries: [],
|
||||
composition: [],
|
||||
entryTypes: [],
|
||||
defaultSort: '-createdOn'
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
key: 'defaultSort',
|
||||
name: 'Default Sort',
|
||||
control: 'select',
|
||||
options: [
|
||||
{
|
||||
name: 'Newest First',
|
||||
value: "-createdOn",
|
||||
},
|
||||
{
|
||||
name: 'Oldest First',
|
||||
value: "createdOn"
|
||||
}
|
||||
],
|
||||
cssClass: 'l-inline'
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
actions: [
|
||||
{
|
||||
"key": "notebook-new-entry",
|
||||
"implementation": newEntryAction,
|
||||
"name": "New Notebook Entry",
|
||||
"cssClass": "icon-notebook labeled",
|
||||
"description": "Add a new Notebook entry",
|
||||
"category": [
|
||||
"view-control"
|
||||
],
|
||||
"depends": [
|
||||
"$compile",
|
||||
"$rootScope",
|
||||
"dialogService",
|
||||
"notificationService",
|
||||
"linkService"
|
||||
],
|
||||
"priority": "preferred"
|
||||
},
|
||||
{
|
||||
"key": "annotate-snapshot",
|
||||
"implementation": AnnotateSnapshotAction,
|
||||
"name": "Annotate Snapshot",
|
||||
"cssClass": "icon-pencil labeled",
|
||||
"description": "Annotate embed's snapshot",
|
||||
"category": "embed",
|
||||
"depends": [
|
||||
"dialogService",
|
||||
"dndService",
|
||||
"$rootScope"
|
||||
]
|
||||
}
|
||||
],
|
||||
controllers: [
|
||||
{
|
||||
"key": "NewEntryController",
|
||||
"implementation": NewEntryController,
|
||||
"depends": ["$scope",
|
||||
"$rootScope"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "selectSnapshotController",
|
||||
"implementation": SelectSnapshotController,
|
||||
"depends": ["$scope",
|
||||
"$rootScope"
|
||||
]
|
||||
}
|
||||
],
|
||||
controls: [
|
||||
{
|
||||
"key": "snapshot-select",
|
||||
"template": snapSelectTemplate
|
||||
},
|
||||
{
|
||||
"key": "embed-control",
|
||||
"template": embedControlTemplate
|
||||
}
|
||||
],
|
||||
templates: [
|
||||
{
|
||||
"key": "annotate-snapshot",
|
||||
"template": annotationTemplate
|
||||
}
|
||||
],
|
||||
directives: [
|
||||
{
|
||||
"key": "mctSnapshot",
|
||||
"implementation": MCTSnapshotDirective,
|
||||
"depends": [
|
||||
"$rootScope",
|
||||
"$document",
|
||||
"exportImageService",
|
||||
"dialogService",
|
||||
"notificationService"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "mctEntryDnd",
|
||||
"implementation": EntryDndDirective,
|
||||
"depends": [
|
||||
"$rootScope",
|
||||
"$compile",
|
||||
"dndService",
|
||||
"typeService",
|
||||
"notificationService"
|
||||
]
|
||||
}
|
||||
],
|
||||
representations: [
|
||||
{
|
||||
"key": "draggedEntry",
|
||||
"template": draggedEntryTemplate
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
openmct.legacyRegistry.enable('notebook');
|
||||
|
||||
openmct.objectViews.addProvider({
|
||||
key: 'notebook-vue',
|
||||
name: 'Notebook View',
|
||||
cssClass: 'icon-notebook',
|
||||
canView: function (domainObject) {
|
||||
return domainObject.type === 'notebook';
|
||||
},
|
||||
view: function (domainObject) {
|
||||
var controller = new NotebookController (openmct, domainObject);
|
||||
|
||||
return {
|
||||
show: controller.show,
|
||||
destroy: controller.destroy
|
||||
};
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
return NotebookPlugin;
|
||||
});
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user