mirror of
https://github.com/nasa/openmct.git
synced 2025-06-28 03:42:21 +00:00
Compare commits
43 Commits
vue-table-
...
vue-conduc
Author | SHA1 | Date | |
---|---|---|---|
ad89d647e2 | |||
0cd81198ca | |||
a070aac579 | |||
80eb3a9668 | |||
f503eae959 | |||
c86ed3be78 | |||
ae3d566854 | |||
68da31f092 | |||
7355767b2d | |||
56b9708ab7 | |||
70d9ad6f2c | |||
1a3500ed1f | |||
40ad56d3cb | |||
e6e5b6a64a | |||
d298e614cf | |||
e32a85b099 | |||
943e61cf8f | |||
15f6b75334 | |||
1ce79a76dd | |||
27783d8c27 | |||
58da916d18 | |||
a0327b56aa | |||
57d60128a2 | |||
987740c649 | |||
944505a5f1 | |||
c5187d8509 | |||
c7b73bdc3f | |||
7483e886f1 | |||
ef92da4d9d | |||
80b02672a6 | |||
fbf1c68c7a | |||
5eac6e646b | |||
82e5bf2325 | |||
40b7117987 | |||
07ca60e13a | |||
08cd6b1d99 | |||
78ae7b334c | |||
3a28caac06 | |||
c883bbe6c2 | |||
eaa971cb56 | |||
0301d88033 | |||
e2e0cf17db | |||
01a39f4fb7 |
2
app.js
2
app.js
@ -16,7 +16,7 @@ const request = require('request');
|
|||||||
|
|
||||||
// Defaults
|
// Defaults
|
||||||
options.port = options.port || options.p || 8080;
|
options.port = options.port || options.p || 8080;
|
||||||
options.host = options.host || options.h || 'localhost'
|
options.host = options.host || options.h || 'localhost';
|
||||||
options.directory = options.directory || options.D || '.';
|
options.directory = options.directory || options.D || '.';
|
||||||
|
|
||||||
// Show command line options
|
// Show command line options
|
||||||
|
@ -19,9 +19,3 @@
|
|||||||
this source code distribution or the Licensing information page available
|
this source code distribution or the Licensing information page available
|
||||||
at runtime from the About dialog for additional information.
|
at runtime from the About dialog for additional information.
|
||||||
-->
|
-->
|
||||||
<div class='items-holder grid abs'>
|
|
||||||
<mct-representation key="'grid-item'"
|
|
||||||
ng-repeat="childObject in composition"
|
|
||||||
mct-object="childObject">
|
|
||||||
</mct-representation>
|
|
||||||
</div>
|
|
@ -19,26 +19,3 @@
|
|||||||
* this source code distribution or the Licensing information page available
|
* this source code distribution or the Licensing information page available
|
||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
define(function () {
|
|
||||||
function MCTGesture(gestureService) {
|
|
||||||
return {
|
|
||||||
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 MCTGesture;
|
|
||||||
});
|
|
@ -75,6 +75,7 @@
|
|||||||
}));
|
}));
|
||||||
openmct.install(openmct.plugins.SummaryWidget());
|
openmct.install(openmct.plugins.SummaryWidget());
|
||||||
openmct.install(openmct.plugins.Notebook());
|
openmct.install(openmct.plugins.Notebook());
|
||||||
|
openmct.install(openmct.plugins.FolderView());
|
||||||
openmct.time.clock('local', {start: -THIRTY_MINUTES, end: 0});
|
openmct.time.clock('local', {start: -THIRTY_MINUTES, end: 0});
|
||||||
openmct.time.timeSystem('utc');
|
openmct.time.timeSystem('utc');
|
||||||
openmct.start();
|
openmct.start();
|
||||||
|
@ -35,12 +35,10 @@ define([
|
|||||||
"./src/windowing/WindowTitler",
|
"./src/windowing/WindowTitler",
|
||||||
"./res/templates/browse.html",
|
"./res/templates/browse.html",
|
||||||
"./res/templates/browse-object.html",
|
"./res/templates/browse-object.html",
|
||||||
"./res/templates/items/grid-item.html",
|
|
||||||
"./res/templates/browse/object-header.html",
|
"./res/templates/browse/object-header.html",
|
||||||
"./res/templates/browse/object-header-frame.html",
|
"./res/templates/browse/object-header-frame.html",
|
||||||
"./res/templates/menu-arrow.html",
|
"./res/templates/menu-arrow.html",
|
||||||
"./res/templates/back-arrow.html",
|
"./res/templates/back-arrow.html",
|
||||||
"./res/templates/items/items.html",
|
|
||||||
"./res/templates/browse/object-properties.html",
|
"./res/templates/browse/object-properties.html",
|
||||||
"./res/templates/browse/inspector-region.html",
|
"./res/templates/browse/inspector-region.html",
|
||||||
'legacyRegistry'
|
'legacyRegistry'
|
||||||
@ -59,12 +57,10 @@ define([
|
|||||||
WindowTitler,
|
WindowTitler,
|
||||||
browseTemplate,
|
browseTemplate,
|
||||||
browseObjectTemplate,
|
browseObjectTemplate,
|
||||||
gridItemTemplate,
|
|
||||||
objectHeaderTemplate,
|
objectHeaderTemplate,
|
||||||
objectHeaderFrameTemplate,
|
objectHeaderFrameTemplate,
|
||||||
menuArrowTemplate,
|
menuArrowTemplate,
|
||||||
backArrowTemplate,
|
backArrowTemplate,
|
||||||
itemsTemplate,
|
|
||||||
objectPropertiesTemplate,
|
objectPropertiesTemplate,
|
||||||
inspectorRegionTemplate,
|
inspectorRegionTemplate,
|
||||||
legacyRegistry
|
legacyRegistry
|
||||||
@ -156,19 +152,6 @@ define([
|
|||||||
"view"
|
"view"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"key": "grid-item",
|
|
||||||
"template": gridItemTemplate,
|
|
||||||
"uses": [
|
|
||||||
"type",
|
|
||||||
"action",
|
|
||||||
"location"
|
|
||||||
],
|
|
||||||
"gestures": [
|
|
||||||
"info",
|
|
||||||
"menu"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"key": "object-header",
|
"key": "object-header",
|
||||||
"template": objectHeaderTemplate,
|
"template": objectHeaderTemplate,
|
||||||
@ -251,23 +234,6 @@ define([
|
|||||||
"priority": "default"
|
"priority": "default"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"views": [
|
|
||||||
{
|
|
||||||
"key": "items",
|
|
||||||
"name": "Grid",
|
|
||||||
"cssClass": "icon-thumbs-strip",
|
|
||||||
"description": "Grid of available items",
|
|
||||||
"template": itemsTemplate,
|
|
||||||
"uses": [
|
|
||||||
"composition"
|
|
||||||
],
|
|
||||||
"gestures": [
|
|
||||||
"drop"
|
|
||||||
],
|
|
||||||
"type": "folder",
|
|
||||||
"editable": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"runs": [
|
"runs": [
|
||||||
{
|
{
|
||||||
"implementation": WindowTitler,
|
"implementation": WindowTitler,
|
||||||
|
@ -66,5 +66,4 @@
|
|||||||
</mct-representation>
|
</mct-representation>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<mct-include key="'conductor'" class="abs holder flex-elem flex-fixed l-flex-row l-time-conductor-holder"></mct-include>
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,45 +0,0 @@
|
|||||||
<!--
|
|
||||||
Open MCT, Copyright (c) 2014-2018, United States Government
|
|
||||||
as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
Administration. All rights reserved.
|
|
||||||
|
|
||||||
Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
"License"); you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
License for the specific language governing permissions and limitations
|
|
||||||
under the License.
|
|
||||||
|
|
||||||
Open MCT includes source code licensed under additional open source
|
|
||||||
licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
this source code distribution or the Licensing information page available
|
|
||||||
at runtime from the About dialog for additional information.
|
|
||||||
-->
|
|
||||||
<!-- For selected, add class 'selected' to outer div -->
|
|
||||||
<div class='item grid-item' ng-click='action.perform("navigate")'>
|
|
||||||
<div class='contents abs'>
|
|
||||||
<div class='top-bar bar abs'>
|
|
||||||
<span class='icon-people' title='Shared'></span>
|
|
||||||
<mct-representation class="desktop-hide" key="'info-button'" mct-object="domainObject"></mct-representation>
|
|
||||||
</div>
|
|
||||||
<div class='item-main abs lg'>
|
|
||||||
<span class="t-item-icon" ng-class="{ 'l-icon-link':location.isLink() }">
|
|
||||||
<span class="t-item-icon-glyph ng-binding {{type.getCssClass()}}"></span>
|
|
||||||
</span>
|
|
||||||
<div class='abs item-open icon-pointer-right'></div>
|
|
||||||
</div>
|
|
||||||
<div class='bottom-bar bar abs'>
|
|
||||||
<div class='title'>{{model.name}}</div>
|
|
||||||
<div class='details'>
|
|
||||||
<span>{{type.getName()}}</span>
|
|
||||||
<span ng-show="model.composition !== undefined">
|
|
||||||
- {{model.composition.length}} Item<span ng-show="model.composition.length > 1">s</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -319,6 +319,12 @@ define([
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"templates": [
|
||||||
|
{
|
||||||
|
key: "elementsPool",
|
||||||
|
template: elementsTemplate
|
||||||
|
}
|
||||||
|
],
|
||||||
"components": [
|
"components": [
|
||||||
{
|
{
|
||||||
"type": "decorator",
|
"type": "decorator",
|
||||||
|
@ -55,7 +55,7 @@ define(
|
|||||||
|
|
||||||
// A view is editable unless explicitly flagged as not
|
// A view is editable unless explicitly flagged as not
|
||||||
(views || []).forEach(function (view) {
|
(views || []).forEach(function (view) {
|
||||||
if (view.editable === true ||
|
if (isEditable(view) ||
|
||||||
(view.key === 'plot' && type.getKey() === 'telemetry.panel') ||
|
(view.key === 'plot' && type.getKey() === 'telemetry.panel') ||
|
||||||
(view.key === 'table' && type.getKey() === 'table') ||
|
(view.key === 'table' && type.getKey() === 'table') ||
|
||||||
(view.key === 'rt-table' && type.getKey() === 'rttable')
|
(view.key === 'rt-table' && type.getKey() === 'rttable')
|
||||||
@ -64,6 +64,14 @@ define(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function isEditable(view) {
|
||||||
|
if (typeof view.editable === Function) {
|
||||||
|
return view.editable(domainObject.useCapability('adapter'));
|
||||||
|
} else {
|
||||||
|
return view.editable === true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return count;
|
return count;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -56,7 +56,7 @@ define([
|
|||||||
};
|
};
|
||||||
|
|
||||||
DurationFormat.prototype.validate = function (text) {
|
DurationFormat.prototype.validate = function (text) {
|
||||||
return moment.utc(text, DATE_FORMATS).isValid();
|
return moment.utc(text, DATE_FORMATS, true).isValid();
|
||||||
};
|
};
|
||||||
|
|
||||||
return DurationFormat;
|
return DurationFormat;
|
||||||
|
@ -29,6 +29,7 @@ define([
|
|||||||
var DATE_FORMAT = "YYYY-MM-DD HH:mm:ss.SSS",
|
var DATE_FORMAT = "YYYY-MM-DD HH:mm:ss.SSS",
|
||||||
DATE_FORMATS = [
|
DATE_FORMATS = [
|
||||||
DATE_FORMAT,
|
DATE_FORMAT,
|
||||||
|
DATE_FORMAT + "Z",
|
||||||
"YYYY-MM-DD HH:mm:ss",
|
"YYYY-MM-DD HH:mm:ss",
|
||||||
"YYYY-MM-DD HH:mm",
|
"YYYY-MM-DD HH:mm",
|
||||||
"YYYY-MM-DD"
|
"YYYY-MM-DD"
|
||||||
@ -52,70 +53,14 @@ define([
|
|||||||
this.key = "utc";
|
this.key = "utc";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an appropriate time format based on the provided value and
|
|
||||||
* the threshold required.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
function getScaledFormat(d) {
|
|
||||||
var momentified = moment.utc(d);
|
|
||||||
/**
|
|
||||||
* Uses logic from d3 Time-Scales, v3 of the API. See
|
|
||||||
* https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Scales.md
|
|
||||||
*
|
|
||||||
* Licensed
|
|
||||||
*/
|
|
||||||
var format = [
|
|
||||||
[".SSS", function (m) {
|
|
||||||
return m.milliseconds();
|
|
||||||
}],
|
|
||||||
[":ss", function (m) {
|
|
||||||
return m.seconds();
|
|
||||||
}],
|
|
||||||
["HH:mm", function (m) {
|
|
||||||
return m.minutes();
|
|
||||||
}],
|
|
||||||
["HH", function (m) {
|
|
||||||
return m.hours();
|
|
||||||
}],
|
|
||||||
["ddd DD", function (m) {
|
|
||||||
return m.days() &&
|
|
||||||
m.date() !== 1;
|
|
||||||
}],
|
|
||||||
["MMM DD", function (m) {
|
|
||||||
return m.date() !== 1;
|
|
||||||
}],
|
|
||||||
["MMMM", function (m) {
|
|
||||||
return m.month();
|
|
||||||
}],
|
|
||||||
["YYYY", function () {
|
|
||||||
return true;
|
|
||||||
}]
|
|
||||||
].filter(function (row) {
|
|
||||||
return row[1](momentified);
|
|
||||||
})[0][0];
|
|
||||||
|
|
||||||
if (format !== undefined) {
|
|
||||||
return moment.utc(d).format(format);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {number} value The value to format.
|
* @param {number} value The value to format.
|
||||||
* @param {number} [minValue] Contextual information for scaled formatting used in linear scales such as conductor
|
|
||||||
* and plot axes. Specifies the smallest number on the scale.
|
|
||||||
* @param {number} [maxValue] Contextual information for scaled formatting used in linear scales such as conductor
|
|
||||||
* and plot axes. Specifies the largest number on the scale
|
|
||||||
* @param {number} [count] Contextual information for scaled formatting used in linear scales such as conductor
|
|
||||||
* and plot axes. The number of labels on the scale.
|
|
||||||
* @returns {string} the formatted date(s). If multiple values were requested, then an array of
|
* @returns {string} the formatted date(s). If multiple values were requested, then an array of
|
||||||
* formatted values will be returned. Where a value could not be formatted, `undefined` will be returned at its position
|
* formatted values will be returned. Where a value could not be formatted, `undefined` will be returned at its position
|
||||||
* in the array.
|
* in the array.
|
||||||
*/
|
*/
|
||||||
UTCTimeFormat.prototype.format = function (value) {
|
UTCTimeFormat.prototype.format = function (value) {
|
||||||
if (arguments.length > 1) {
|
if (value !== undefined) {
|
||||||
return getScaledFormat(value);
|
|
||||||
} else if (value !== undefined) {
|
|
||||||
return moment.utc(value).format(DATE_FORMAT) + "Z";
|
return moment.utc(value).format(DATE_FORMAT) + "Z";
|
||||||
} else {
|
} else {
|
||||||
return value;
|
return value;
|
||||||
@ -130,7 +75,7 @@ define([
|
|||||||
};
|
};
|
||||||
|
|
||||||
UTCTimeFormat.prototype.validate = function (text) {
|
UTCTimeFormat.prototype.validate = function (text) {
|
||||||
return moment.utc(text, DATE_FORMATS).isValid();
|
return moment.utc(text, DATE_FORMATS, true).isValid();
|
||||||
};
|
};
|
||||||
|
|
||||||
return UTCTimeFormat;
|
return UTCTimeFormat;
|
||||||
|
@ -1,62 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define([
|
|
||||||
"../src/UTCTimeFormat",
|
|
||||||
"moment"
|
|
||||||
], function (
|
|
||||||
UTCTimeFormat,
|
|
||||||
moment
|
|
||||||
) {
|
|
||||||
describe("The UTCTimeFormat class", function () {
|
|
||||||
var format;
|
|
||||||
var scale;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
format = new UTCTimeFormat();
|
|
||||||
scale = {min: 0, max: 0};
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Provides an appropriately scaled time format based on the input" +
|
|
||||||
" time", function () {
|
|
||||||
var TWO_HUNDRED_MS = 200;
|
|
||||||
var THREE_SECONDS = 3000;
|
|
||||||
var FIVE_MINUTES = 5 * 60 * 1000;
|
|
||||||
var ONE_HOUR_TWENTY_MINS = (1 * 60 * 60 * 1000) + (20 * 60 * 1000);
|
|
||||||
var TEN_HOURS = (10 * 60 * 60 * 1000);
|
|
||||||
|
|
||||||
var JUNE_THIRD = moment.utc("2016-06-03", "YYYY-MM-DD");
|
|
||||||
var APRIL = moment.utc("2016-04", "YYYY-MM");
|
|
||||||
var TWENTY_SIXTEEN = moment.utc("2016", "YYYY");
|
|
||||||
|
|
||||||
expect(format.format(TWO_HUNDRED_MS, scale)).toBe(".200");
|
|
||||||
expect(format.format(THREE_SECONDS, scale)).toBe(":03");
|
|
||||||
expect(format.format(FIVE_MINUTES, scale)).toBe("00:05");
|
|
||||||
expect(format.format(ONE_HOUR_TWENTY_MINS, scale)).toBe("01:20");
|
|
||||||
expect(format.format(TEN_HOURS, scale)).toBe("10");
|
|
||||||
|
|
||||||
expect(format.format(JUNE_THIRD, scale)).toBe("Fri 03");
|
|
||||||
expect(format.format(APRIL, scale)).toBe("April");
|
|
||||||
expect(format.format(TWENTY_SIXTEEN, scale)).toBe("2016");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,43 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define([
|
|
||||||
"./src/ConductorRepresenter",
|
|
||||||
'legacyRegistry'
|
|
||||||
], function (
|
|
||||||
ConductorRepresenter,
|
|
||||||
legacyRegistry
|
|
||||||
) {
|
|
||||||
|
|
||||||
legacyRegistry.register("platform/features/conductor/compatibility", {
|
|
||||||
"extensions": {
|
|
||||||
"representers": [
|
|
||||||
{
|
|
||||||
"implementation": ConductorRepresenter,
|
|
||||||
"depends": [
|
|
||||||
"openmct"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,95 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
[],
|
|
||||||
function () {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Representer that provides a compatibility layer between the new
|
|
||||||
* time conductor and existing representations / views. Listens to
|
|
||||||
* the v2 time conductor API and generates v1 style events using the
|
|
||||||
* Angular event bus. This is transitional code code and will be
|
|
||||||
* removed.
|
|
||||||
*
|
|
||||||
* Deprecated immediately as this is temporary code
|
|
||||||
*
|
|
||||||
* @deprecated
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function ConductorRepresenter(
|
|
||||||
openmct,
|
|
||||||
scope,
|
|
||||||
element
|
|
||||||
) {
|
|
||||||
this.timeAPI = openmct.time;
|
|
||||||
this.scope = scope;
|
|
||||||
this.element = element;
|
|
||||||
|
|
||||||
this.boundsListener = this.boundsListener.bind(this);
|
|
||||||
this.timeSystemListener = this.timeSystemListener.bind(this);
|
|
||||||
this.followListener = this.followListener.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
ConductorRepresenter.prototype.boundsListener = function (bounds) {
|
|
||||||
var timeSystem = this.timeAPI.timeSystem();
|
|
||||||
this.scope.$broadcast('telemetry:display:bounds', {
|
|
||||||
start: bounds.start,
|
|
||||||
end: bounds.end,
|
|
||||||
domain: timeSystem.key
|
|
||||||
}, this.timeAPI.clock() !== undefined);
|
|
||||||
};
|
|
||||||
|
|
||||||
ConductorRepresenter.prototype.timeSystemListener = function (timeSystem) {
|
|
||||||
var bounds = this.timeAPI.bounds();
|
|
||||||
this.scope.$broadcast('telemetry:display:bounds', {
|
|
||||||
start: bounds.start,
|
|
||||||
end: bounds.end,
|
|
||||||
domain: timeSystem.key
|
|
||||||
}, this.timeAPI.clock() !== undefined);
|
|
||||||
};
|
|
||||||
|
|
||||||
ConductorRepresenter.prototype.followListener = function () {
|
|
||||||
this.boundsListener(this.timeAPI.bounds());
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handle a specific representation of a specific domain object
|
|
||||||
ConductorRepresenter.prototype.represent = function represent(representation) {
|
|
||||||
if (representation.key === 'browse-object') {
|
|
||||||
this.destroy();
|
|
||||||
|
|
||||||
this.timeAPI.on("bounds", this.boundsListener);
|
|
||||||
this.timeAPI.on("timeSystem", this.timeSystemListener);
|
|
||||||
this.timeAPI.on("follow", this.followListener);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ConductorRepresenter.prototype.destroy = function destroy() {
|
|
||||||
this.timeAPI.off("bounds", this.boundsListener);
|
|
||||||
this.timeAPI.off("timeSystem", this.timeSystemListener);
|
|
||||||
this.timeAPI.off("follow", this.followListener);
|
|
||||||
};
|
|
||||||
|
|
||||||
return ConductorRepresenter;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
@ -1,148 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define([
|
|
||||||
"./src/ui/TimeConductorController",
|
|
||||||
"./src/ui/ConductorAxisController",
|
|
||||||
"./src/ui/ConductorTOIController",
|
|
||||||
"./src/ui/ConductorTOIDirective",
|
|
||||||
"./src/ui/TimeOfInterestController",
|
|
||||||
"./src/ui/ConductorAxisDirective",
|
|
||||||
"./src/ui/NumberFormat",
|
|
||||||
"./src/ui/StringFormat",
|
|
||||||
"./res/templates/time-conductor.html",
|
|
||||||
"./res/templates/mode-selector/mode-selector.html",
|
|
||||||
"./res/templates/mode-selector/mode-menu.html",
|
|
||||||
"./res/templates/time-of-interest.html",
|
|
||||||
"legacyRegistry"
|
|
||||||
], function (
|
|
||||||
TimeConductorController,
|
|
||||||
ConductorAxisController,
|
|
||||||
ConductorTOIController,
|
|
||||||
ConductorTOIDirective,
|
|
||||||
TimeOfInterestController,
|
|
||||||
ConductorAxisDirective,
|
|
||||||
NumberFormat,
|
|
||||||
StringFormat,
|
|
||||||
timeConductorTemplate,
|
|
||||||
modeSelectorTemplate,
|
|
||||||
modeMenuTemplate,
|
|
||||||
timeOfInterest,
|
|
||||||
legacyRegistry
|
|
||||||
) {
|
|
||||||
|
|
||||||
legacyRegistry.register("platform/features/conductor/core", {
|
|
||||||
"extensions": {
|
|
||||||
"controllers": [
|
|
||||||
{
|
|
||||||
"key": "TimeConductorController",
|
|
||||||
"implementation": TimeConductorController,
|
|
||||||
"depends": [
|
|
||||||
"$scope",
|
|
||||||
"$window",
|
|
||||||
"openmct",
|
|
||||||
"formatService",
|
|
||||||
"CONDUCTOR_CONFIG"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "ConductorTOIController",
|
|
||||||
"implementation": ConductorTOIController,
|
|
||||||
"depends": [
|
|
||||||
"$scope",
|
|
||||||
"openmct",
|
|
||||||
"formatService"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "TimeOfInterestController",
|
|
||||||
"implementation": TimeOfInterestController,
|
|
||||||
"depends": [
|
|
||||||
"$scope",
|
|
||||||
"openmct",
|
|
||||||
"formatService"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"directives": [
|
|
||||||
{
|
|
||||||
"key": "conductorAxis",
|
|
||||||
"implementation": ConductorAxisDirective,
|
|
||||||
"depends": [
|
|
||||||
"openmct",
|
|
||||||
"formatService"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "conductorToi",
|
|
||||||
"implementation": ConductorTOIDirective
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"templates": [
|
|
||||||
{
|
|
||||||
"key": "conductor",
|
|
||||||
"template": timeConductorTemplate
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "mode-menu",
|
|
||||||
"template": modeMenuTemplate
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "mode-selector",
|
|
||||||
"template": modeSelectorTemplate
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "time-of-interest",
|
|
||||||
"template": timeOfInterest
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"representations": [
|
|
||||||
{
|
|
||||||
"key": "time-conductor",
|
|
||||||
"template": timeConductorTemplate
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"licenses": [
|
|
||||||
{
|
|
||||||
"name": "D3: Data-Driven Documents",
|
|
||||||
"version": "4.1.0",
|
|
||||||
"author": "Mike Bostock",
|
|
||||||
"description": "D3 (or D3.js) is a JavaScript library for visualizing data using web standards. D3 helps you bring data to life using SVG, Canvas and HTML. D3 combines powerful visualization and interaction techniques with a data-driven approach to DOM manipulation, giving you the full capabilities of modern browsers and the freedom to design the right visual interface for your data.",
|
|
||||||
"website": "https://d3js.org/",
|
|
||||||
"copyright": "Copyright 2010-2016 Mike Bostock",
|
|
||||||
"license": "BSD-3-Clause",
|
|
||||||
"link": "https://github.com/d3/d3/blob/master/LICENSE"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"formats": [
|
|
||||||
{
|
|
||||||
"key": "number",
|
|
||||||
"implementation": NumberFormat
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "string",
|
|
||||||
"implementation": StringFormat
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,46 +0,0 @@
|
|||||||
<!--
|
|
||||||
Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
Administration. All rights reserved.
|
|
||||||
|
|
||||||
Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
"License"); you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
License for the specific language governing permissions and limitations
|
|
||||||
under the License.
|
|
||||||
|
|
||||||
Open MCT Web includes source code licensed under additional open source
|
|
||||||
licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
this source code distribution or the Licensing information page available
|
|
||||||
at runtime from the About dialog for additional information.
|
|
||||||
-->
|
|
||||||
<div class="w-menu">
|
|
||||||
<div class="col menu-items">
|
|
||||||
<ul>
|
|
||||||
<li ng-repeat="metadata in ngModel.options"
|
|
||||||
ng-click="ngModel.select(metadata)">
|
|
||||||
<a ng-mouseover="ngModel.activeMetadata = metadata"
|
|
||||||
ng-mouseleave="ngModel.activeMetadata = undefined"
|
|
||||||
class="menu-item-a {{metadata.cssClass}}">
|
|
||||||
{{metadata.name}}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="col menu-item-description">
|
|
||||||
<div class="desc-area ui-symbol icon type-icon {{ngModel.activeMetadata.cssClass}}"></div>
|
|
||||||
<div class="w-title-desc">
|
|
||||||
<div class="desc-area title">
|
|
||||||
{{ngModel.activeMetadata.name}}
|
|
||||||
</div>
|
|
||||||
<div class="desc-area description">
|
|
||||||
{{ngModel.activeMetadata.description}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -1,33 +0,0 @@
|
|||||||
<!--
|
|
||||||
Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
Administration. All rights reserved.
|
|
||||||
|
|
||||||
Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
"License"); you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
License for the specific language governing permissions and limitations
|
|
||||||
under the License.
|
|
||||||
|
|
||||||
Open MCT Web includes source code licensed under additional open source
|
|
||||||
licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
this source code distribution or the Licensing information page available
|
|
||||||
at runtime from the About dialog for additional information.
|
|
||||||
-->
|
|
||||||
<span ng-controller="ClickAwayController as modeController">
|
|
||||||
<div class="s-menu-button"
|
|
||||||
ng-click="modeController.toggle()">
|
|
||||||
<span class="title-label">{{ngModel.selected.name}}</span>
|
|
||||||
</div>
|
|
||||||
<div class="menu super-menu mini l-mode-selector-menu"
|
|
||||||
ng-show="modeController.isActive()">
|
|
||||||
<mct-include key="'mode-menu'"
|
|
||||||
ng-model="ngModel">
|
|
||||||
</mct-include>
|
|
||||||
</div>
|
|
||||||
</span>
|
|
@ -1,117 +0,0 @@
|
|||||||
<!-- Parent holder for time conductor. follow-mode | fixed-mode -->
|
|
||||||
<div ng-controller="TimeConductorController as tcController"
|
|
||||||
class="holder grows flex-elem l-flex-row l-time-conductor {{tcController.isFixed ? 'fixed-mode' : 'realtime-mode'}} {{timeSystemModel.selected.metadata.key}}-time-system"
|
|
||||||
ng-class="{'status-panning': tcController.panning}">
|
|
||||||
<div class="flex-elem holder time-conductor-icon">
|
|
||||||
<div class="hand-little"></div>
|
|
||||||
<div class="hand-big"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex-elem holder grows l-flex-col l-time-conductor-inner">
|
|
||||||
<!-- Holds inputs and ticks -->
|
|
||||||
<div class="l-time-conductor-inputs-and-ticks l-row-elem flex-elem no-margin">
|
|
||||||
<form class="l-time-conductor-inputs-holder"
|
|
||||||
ng-submit="tcController.isFixed ? tcController.setBoundsFromView(boundsModel) : tcController.setOffsetsFromView(boundsModel)">
|
|
||||||
<span class="l-time-range-w start-w">
|
|
||||||
<span class="l-time-conductor-inputs">
|
|
||||||
<span class="l-time-range-input-w start-date">
|
|
||||||
<span class="title"></span>
|
|
||||||
<mct-control key="'datetime-field'"
|
|
||||||
structure="{
|
|
||||||
format: timeSystemModel.format,
|
|
||||||
validate: tcController.validation.validateStart
|
|
||||||
}"
|
|
||||||
ng-model="boundsModel"
|
|
||||||
ng-blur="tcController.setBoundsFromView(boundsModel)"
|
|
||||||
field="'start'"
|
|
||||||
class="time-range-input">
|
|
||||||
</mct-control>
|
|
||||||
</span>
|
|
||||||
<span class="l-time-range-input-w time-delta start-delta"
|
|
||||||
ng-class="{'hide':tcController.isFixed}">
|
|
||||||
-
|
|
||||||
<mct-control key="'datetime-field'"
|
|
||||||
structure="{
|
|
||||||
format: timeSystemModel.durationFormat,
|
|
||||||
validate: tcController.validation.validateStartOffset
|
|
||||||
}"
|
|
||||||
ng-model="boundsModel"
|
|
||||||
ng-blur="tcController.setOffsetsFromView(boundsModel)"
|
|
||||||
field="'startOffset'"
|
|
||||||
class="s-input-inline hrs-min-input">
|
|
||||||
</mct-control>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
<span class="l-time-range-w end-w">
|
|
||||||
<span class="l-time-conductor-inputs">
|
|
||||||
<span class="l-time-range-input-w end-date"
|
|
||||||
ng-controller="ToggleController as t2">
|
|
||||||
<span class="title"></span>
|
|
||||||
<mct-control key="'datetime-field'"
|
|
||||||
structure="{
|
|
||||||
format: timeSystemModel.format,
|
|
||||||
validate: tcController.validation.validateEnd
|
|
||||||
}"
|
|
||||||
ng-model="boundsModel"
|
|
||||||
ng-blur="tcController.setBoundsFromView(boundsModel)"
|
|
||||||
ng-disabled="!tcController.isFixed"
|
|
||||||
field="'end'"
|
|
||||||
class="time-range-input">
|
|
||||||
</mct-control>
|
|
||||||
</span>
|
|
||||||
<span class="l-time-range-input-w time-delta end-delta"
|
|
||||||
ng-class="{'hide': tcController.isFixed}">
|
|
||||||
+
|
|
||||||
<mct-control key="'datetime-field'"
|
|
||||||
structure="{
|
|
||||||
format: timeSystemModel.durationFormat,
|
|
||||||
validate: tcController.validation.validateEndOffset
|
|
||||||
}"
|
|
||||||
ng-model="boundsModel"
|
|
||||||
ng-blur="tcController.setOffsetsFromView(boundsModel)"
|
|
||||||
field="'endOffset'"
|
|
||||||
class="s-input-inline hrs-min-input">
|
|
||||||
</mct-control>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<input type="submit" class="invisible">
|
|
||||||
</form>
|
|
||||||
<conductor-axis class="mobile-hide" view-service="tcController.conductorViewService"></conductor-axis>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Holds time system and session selectors, and zoom control -->
|
|
||||||
<div class="l-time-conductor-controls l-row-elem l-flex-row flex-elem">
|
|
||||||
<mct-include
|
|
||||||
key="'mode-selector'"
|
|
||||||
ng-model="tcController.menu"
|
|
||||||
class="holder flex-elem menus-up mode-selector">
|
|
||||||
</mct-include>
|
|
||||||
<mct-control
|
|
||||||
key="'menu-button'"
|
|
||||||
class="holder flex-elem menus-up time-system"
|
|
||||||
structure="{
|
|
||||||
text: timeSystemModel.selected.name,
|
|
||||||
click: tcController.setTimeSystemFromView,
|
|
||||||
options: tcController.timeSystemsForClocks[tcController.menu.selected.key]
|
|
||||||
}">
|
|
||||||
</mct-control>
|
|
||||||
<!-- Zoom control -->
|
|
||||||
<div ng-if="tcController.zoom"
|
|
||||||
class="l-time-conductor-zoom-w grows flex-elem l-flex-row">
|
|
||||||
{{currentZoom}}
|
|
||||||
<span class="time-conductor-zoom-current-range flex-elem flex-fixed holder">{{timeUnits}}</span>
|
|
||||||
<input class="time-conductor-zoom flex-elem" type="range"
|
|
||||||
ng-model="tcController.currentZoom"
|
|
||||||
ng-mouseUp="tcController.onZoomStop(tcController.currentZoom)"
|
|
||||||
ng-change="tcController.onZoom(tcController.currentZoom)"
|
|
||||||
min="0.01"
|
|
||||||
step="0.01"
|
|
||||||
max="0.99" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -1,12 +0,0 @@
|
|||||||
<div class="abs angular-controller"
|
|
||||||
ng-controller="TimeOfInterestController as toi">
|
|
||||||
<div class="l-flex-row l-toi">
|
|
||||||
<span class="flex-elem l-flex-row l-toi-buttons">
|
|
||||||
<a class="flex-elem t-button-resync icon-button" title="Re-sync Time of Interest"
|
|
||||||
ng-click="toi.resync()"></a>
|
|
||||||
<a class="flex-elem t-button-unpin icon-button" title="Unset Time of Interest"
|
|
||||||
ng-click="toi.dismiss()"></a>
|
|
||||||
</span>
|
|
||||||
<span class="flex-elem l-toi-val">{{toi.toiText}}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -1,236 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
[
|
|
||||||
"d3-selection",
|
|
||||||
"d3-scale",
|
|
||||||
"d3-axis"
|
|
||||||
],
|
|
||||||
function (d3Selection, d3Scale, d3Axis) {
|
|
||||||
var PADDING = 1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Controller that renders a horizontal time scale spanning the current bounds defined in the time conductor.
|
|
||||||
* Used by the mct-conductor-axis directive
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function ConductorAxisController(openmct, formatService, scope, element) {
|
|
||||||
// Dependencies
|
|
||||||
this.formatService = formatService;
|
|
||||||
this.timeAPI = openmct.time;
|
|
||||||
|
|
||||||
this.scope = scope;
|
|
||||||
|
|
||||||
this.bounds = this.timeAPI.bounds();
|
|
||||||
|
|
||||||
//Bind all class functions to 'this'
|
|
||||||
Object.keys(ConductorAxisController.prototype).filter(function (key) {
|
|
||||||
return typeof ConductorAxisController.prototype[key] === 'function';
|
|
||||||
}).forEach(function (key) {
|
|
||||||
this[key] = ConductorAxisController.prototype[key].bind(this);
|
|
||||||
}.bind(this));
|
|
||||||
|
|
||||||
this.initialize(element);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
ConductorAxisController.prototype.destroy = function () {
|
|
||||||
this.timeAPI.off('timeSystem', this.changeTimeSystem);
|
|
||||||
this.timeAPI.off('bounds', this.changeBounds);
|
|
||||||
this.viewService.off("zoom", this.onZoom);
|
|
||||||
this.viewService.off("zoom-stop", this.onZoomStop);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
ConductorAxisController.prototype.initialize = function (element) {
|
|
||||||
this.target = element[0].firstChild;
|
|
||||||
var height = this.target.offsetHeight;
|
|
||||||
var vis = d3Selection.select(this.target)
|
|
||||||
.append("svg:svg")
|
|
||||||
.attr("width", "100%")
|
|
||||||
.attr("height", height);
|
|
||||||
|
|
||||||
this.xAxis = d3Axis.axisTop();
|
|
||||||
|
|
||||||
// draw x axis with labels. CSS is used to position them.
|
|
||||||
this.axisElement = vis.append("g");
|
|
||||||
|
|
||||||
if (this.timeAPI.timeSystem() !== undefined) {
|
|
||||||
this.changeTimeSystem(this.timeAPI.timeSystem());
|
|
||||||
this.setScale();
|
|
||||||
}
|
|
||||||
|
|
||||||
//Respond to changes in conductor
|
|
||||||
this.timeAPI.on("timeSystem", this.changeTimeSystem);
|
|
||||||
this.timeAPI.on("bounds", this.changeBounds);
|
|
||||||
|
|
||||||
this.scope.$on("$destroy", this.destroy);
|
|
||||||
|
|
||||||
this.viewService.on("zoom", this.onZoom);
|
|
||||||
this.viewService.on("zoom-stop", this.onZoomStop);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
ConductorAxisController.prototype.changeBounds = function (bounds) {
|
|
||||||
this.bounds = bounds;
|
|
||||||
if (!this.zooming) {
|
|
||||||
this.setScale();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the scale of the axis, based on current conductor bounds.
|
|
||||||
*/
|
|
||||||
ConductorAxisController.prototype.setScale = function () {
|
|
||||||
var width = this.target.offsetWidth;
|
|
||||||
var timeSystem = this.timeAPI.timeSystem();
|
|
||||||
var bounds = this.bounds;
|
|
||||||
|
|
||||||
if (timeSystem.isUTCBased) {
|
|
||||||
this.xScale = this.xScale || d3Scale.scaleUtc();
|
|
||||||
this.xScale.domain([new Date(bounds.start), new Date(bounds.end)]);
|
|
||||||
} else {
|
|
||||||
this.xScale = this.xScale || d3Scale.scaleLinear();
|
|
||||||
this.xScale.domain([bounds.start, bounds.end]);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.xAxis.scale(this.xScale);
|
|
||||||
|
|
||||||
this.xScale.range([PADDING, width - PADDING * 2]);
|
|
||||||
this.axisElement.call(this.xAxis);
|
|
||||||
|
|
||||||
this.msPerPixel = (bounds.end - bounds.start) / width;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When the time system changes, update the scale and formatter used for showing times.
|
|
||||||
* @param timeSystem
|
|
||||||
*/
|
|
||||||
ConductorAxisController.prototype.changeTimeSystem = function (timeSystem) {
|
|
||||||
var key = timeSystem.timeFormat;
|
|
||||||
if (key !== undefined) {
|
|
||||||
var format = this.formatService.getFormat(key);
|
|
||||||
var bounds = this.timeAPI.bounds();
|
|
||||||
|
|
||||||
//The D3 scale used depends on the type of time system as d3
|
|
||||||
// supports UTC out of the box.
|
|
||||||
if (timeSystem.isUTCBased) {
|
|
||||||
this.xScale = d3Scale.scaleUtc();
|
|
||||||
} else {
|
|
||||||
this.xScale = d3Scale.scaleLinear();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.xAxis.scale(this.xScale);
|
|
||||||
|
|
||||||
//Define a custom format function
|
|
||||||
this.xAxis.tickFormat(function (tickValue) {
|
|
||||||
// Normalize date representations to numbers
|
|
||||||
if (tickValue instanceof Date) {
|
|
||||||
tickValue = tickValue.getTime();
|
|
||||||
}
|
|
||||||
return format.format(tickValue, {
|
|
||||||
min: bounds.start,
|
|
||||||
max: bounds.end
|
|
||||||
});
|
|
||||||
});
|
|
||||||
this.axisElement.call(this.xAxis);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The user has stopped panning the time conductor scale element.
|
|
||||||
* @event panStop
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Called on release of mouse button after dragging the scale left or right.
|
|
||||||
* @fires platform.features.conductor.ConductorAxisController~panStop
|
|
||||||
*/
|
|
||||||
ConductorAxisController.prototype.panStop = function () {
|
|
||||||
//resync view bounds with time conductor bounds
|
|
||||||
this.viewService.emit("pan-stop");
|
|
||||||
this.timeAPI.bounds(this.bounds);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rescales the axis when the user zooms. Although zoom ultimately results in a bounds change once the user
|
|
||||||
* releases the zoom slider, dragging the slider will not immediately change the conductor bounds. It will
|
|
||||||
* however immediately update the scale and the bounds displayed in the UI.
|
|
||||||
* @private
|
|
||||||
* @param {ZoomLevel}
|
|
||||||
*/
|
|
||||||
ConductorAxisController.prototype.onZoom = function (zoom) {
|
|
||||||
this.zooming = true;
|
|
||||||
|
|
||||||
this.bounds = zoom.bounds;
|
|
||||||
this.setScale();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
ConductorAxisController.prototype.onZoomStop = function (zoom) {
|
|
||||||
this.zooming = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @event platform.features.conductor.ConductorAxisController~pan
|
|
||||||
* Fired when the time conductor is panned
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Initiate panning via a click + drag gesture on the time conductor
|
|
||||||
* scale. Panning triggers a "pan" event
|
|
||||||
* @param {number} delta the offset from the original click event
|
|
||||||
* @see TimeConductorViewService#
|
|
||||||
* @fires platform.features.conductor.ConductorAxisController~pan
|
|
||||||
*/
|
|
||||||
ConductorAxisController.prototype.pan = function (delta) {
|
|
||||||
if (this.timeAPI.clock() === undefined) {
|
|
||||||
var deltaInMs = delta[0] * this.msPerPixel;
|
|
||||||
var bounds = this.timeAPI.bounds();
|
|
||||||
var start = Math.floor((bounds.start - deltaInMs) / 1000) * 1000;
|
|
||||||
var end = Math.floor((bounds.end - deltaInMs) / 1000) * 1000;
|
|
||||||
this.bounds = {
|
|
||||||
start: start,
|
|
||||||
end: end
|
|
||||||
};
|
|
||||||
this.setScale();
|
|
||||||
this.viewService.emit("pan", this.bounds);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoked on element resize. Will rebuild the scale based on the new dimensions of the element.
|
|
||||||
*/
|
|
||||||
ConductorAxisController.prototype.resize = function () {
|
|
||||||
this.setScale();
|
|
||||||
};
|
|
||||||
|
|
||||||
return ConductorAxisController;
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,169 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define([
|
|
||||||
'./ConductorAxisController',
|
|
||||||
'zepto',
|
|
||||||
'd3-selection',
|
|
||||||
'd3-scale'
|
|
||||||
], function (
|
|
||||||
ConductorAxisController,
|
|
||||||
$,
|
|
||||||
d3Selection,
|
|
||||||
d3Scale
|
|
||||||
) {
|
|
||||||
describe("The ConductorAxisController", function () {
|
|
||||||
var controller,
|
|
||||||
mockConductor,
|
|
||||||
mockConductorViewService,
|
|
||||||
mockFormatService,
|
|
||||||
mockScope,
|
|
||||||
mockBounds,
|
|
||||||
element,
|
|
||||||
mockTimeSystem,
|
|
||||||
mockFormat;
|
|
||||||
|
|
||||||
function getCallback(target, name) {
|
|
||||||
return target.calls.all().filter(function (call) {
|
|
||||||
return call.args[0] === name;
|
|
||||||
})[0].args[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
mockScope = jasmine.createSpyObj("scope", [
|
|
||||||
"$on"
|
|
||||||
]);
|
|
||||||
|
|
||||||
//Add some HTML elements
|
|
||||||
mockBounds = {
|
|
||||||
start: 100,
|
|
||||||
end: 200
|
|
||||||
};
|
|
||||||
mockConductor = jasmine.createSpyObj("conductor", [
|
|
||||||
"timeSystem",
|
|
||||||
"bounds",
|
|
||||||
"on",
|
|
||||||
"off",
|
|
||||||
"clock"
|
|
||||||
]);
|
|
||||||
mockConductor.bounds.and.returnValue(mockBounds);
|
|
||||||
|
|
||||||
mockFormatService = jasmine.createSpyObj("formatService", [
|
|
||||||
"getFormat"
|
|
||||||
]);
|
|
||||||
|
|
||||||
mockConductorViewService = jasmine.createSpyObj("conductorViewService", [
|
|
||||||
"on",
|
|
||||||
"off",
|
|
||||||
"emit"
|
|
||||||
]);
|
|
||||||
|
|
||||||
spyOn(d3Scale, 'scaleUtc').and.callThrough();
|
|
||||||
spyOn(d3Scale, 'scaleLinear').and.callThrough();
|
|
||||||
|
|
||||||
element = $('<div style="width: 100px;"><div style="width: 100%;"></div></div>');
|
|
||||||
$(document).find('body').append(element);
|
|
||||||
ConductorAxisController.prototype.viewService = mockConductorViewService;
|
|
||||||
controller = new ConductorAxisController({time: mockConductor}, mockFormatService, mockScope, element);
|
|
||||||
|
|
||||||
mockTimeSystem = {};
|
|
||||||
mockFormat = jasmine.createSpyObj("format", [
|
|
||||||
"format"
|
|
||||||
]);
|
|
||||||
|
|
||||||
mockTimeSystem.timeFormat = "mockFormat";
|
|
||||||
mockFormatService.getFormat.and.returnValue(mockFormat);
|
|
||||||
mockConductor.timeSystem.and.returnValue(mockTimeSystem);
|
|
||||||
mockTimeSystem.isUTCBased = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
it("listens for changes to time system and bounds", function () {
|
|
||||||
expect(mockConductor.on).toHaveBeenCalledWith("timeSystem", controller.changeTimeSystem);
|
|
||||||
expect(mockConductor.on).toHaveBeenCalledWith("bounds", controller.changeBounds);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("on scope destruction, deregisters listeners", function () {
|
|
||||||
expect(mockScope.$on).toHaveBeenCalledWith("$destroy", controller.destroy);
|
|
||||||
controller.destroy();
|
|
||||||
expect(mockConductor.off).toHaveBeenCalledWith("timeSystem", controller.changeTimeSystem);
|
|
||||||
expect(mockConductor.off).toHaveBeenCalledWith("bounds", controller.changeBounds);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when the time system changes", function () {
|
|
||||||
it("uses a UTC scale for UTC time systems", function () {
|
|
||||||
mockTimeSystem.isUTCBased = true;
|
|
||||||
controller.changeTimeSystem(mockTimeSystem);
|
|
||||||
|
|
||||||
expect(d3Scale.scaleUtc).toHaveBeenCalled();
|
|
||||||
expect(d3Scale.scaleLinear).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("uses a linear scale for non-UTC time systems", function () {
|
|
||||||
mockTimeSystem.isUTCBased = false;
|
|
||||||
controller.changeTimeSystem(mockTimeSystem);
|
|
||||||
expect(d3Scale.scaleLinear).toHaveBeenCalled();
|
|
||||||
expect(d3Scale.scaleUtc).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("sets axis domain to time conductor bounds", function () {
|
|
||||||
mockTimeSystem.isUTCBased = false;
|
|
||||||
controller.setScale();
|
|
||||||
expect(controller.xScale.domain()).toEqual([mockBounds.start, mockBounds.end]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("uses the format specified by the time system to format tick" +
|
|
||||||
" labels", function () {
|
|
||||||
controller.changeTimeSystem(mockTimeSystem);
|
|
||||||
expect(mockFormat.format).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('responds to zoom events', function () {
|
|
||||||
expect(mockConductorViewService.on).toHaveBeenCalledWith("zoom", controller.onZoom);
|
|
||||||
var cb = getCallback(mockConductorViewService.on, "zoom");
|
|
||||||
spyOn(controller, 'setScale').and.callThrough();
|
|
||||||
cb({bounds: {start: 0, end: 100}});
|
|
||||||
expect(controller.setScale).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('adjusts scale on pan', function () {
|
|
||||||
spyOn(controller, 'setScale').and.callThrough();
|
|
||||||
controller.pan(100);
|
|
||||||
expect(controller.setScale).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('emits event on pan', function () {
|
|
||||||
spyOn(controller, 'setScale').and.callThrough();
|
|
||||||
controller.pan(100);
|
|
||||||
expect(mockConductorViewService.emit).toHaveBeenCalledWith("pan", jasmine.any(Object));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('cleans up listeners on destruction', function () {
|
|
||||||
controller.destroy();
|
|
||||||
expect(mockConductor.off).toHaveBeenCalledWith("bounds", controller.changeBounds);
|
|
||||||
expect(mockConductor.off).toHaveBeenCalledWith("timeSystem", controller.changeTimeSystem);
|
|
||||||
|
|
||||||
expect(mockConductorViewService.off).toHaveBeenCalledWith("zoom", controller.onZoom);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,56 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(['./ConductorAxisController'], function (ConductorAxisController) {
|
|
||||||
function ConductorAxisDirective() {
|
|
||||||
/**
|
|
||||||
* The mct-conductor-axis renders a horizontal axis with regular
|
|
||||||
* labelled 'ticks'. It requires 'start' and 'end' integer values to
|
|
||||||
* be specified as attributes.
|
|
||||||
*/
|
|
||||||
return {
|
|
||||||
controller: [
|
|
||||||
'openmct',
|
|
||||||
'formatService',
|
|
||||||
'$scope',
|
|
||||||
'$element',
|
|
||||||
ConductorAxisController
|
|
||||||
],
|
|
||||||
controllerAs: 'axis',
|
|
||||||
scope: {
|
|
||||||
viewService: "="
|
|
||||||
},
|
|
||||||
bindToController: true,
|
|
||||||
|
|
||||||
restrict: 'E',
|
|
||||||
priority: 1000,
|
|
||||||
|
|
||||||
template: '<div class="l-axis-holder" ' +
|
|
||||||
' mct-drag-down="axis.panStart()"' +
|
|
||||||
' mct-drag-up="axis.panStop(delta)"' +
|
|
||||||
' mct-drag="axis.pan(delta)"' +
|
|
||||||
' mct-resize="axis.resize()"></div>'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return ConductorAxisDirective;
|
|
||||||
});
|
|
@ -1,123 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
["zepto"],
|
|
||||||
function ($) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Controller for the Time of Interest indicator in the conductor itself. Sets the horizontal position of the
|
|
||||||
* TOI indicator based on the current value of the TOI, and the width of the TOI conductor.
|
|
||||||
* @memberof platform.features.conductor
|
|
||||||
*/
|
|
||||||
function ConductorTOIController($scope, openmct) {
|
|
||||||
this.timeAPI = openmct.time;
|
|
||||||
|
|
||||||
//Bind all class functions to 'this'
|
|
||||||
Object.keys(ConductorTOIController.prototype).filter(function (key) {
|
|
||||||
return typeof ConductorTOIController.prototype[key] === 'function';
|
|
||||||
}).forEach(function (key) {
|
|
||||||
this[key] = ConductorTOIController.prototype[key].bind(this);
|
|
||||||
}.bind(this));
|
|
||||||
|
|
||||||
this.timeAPI.on('timeOfInterest', this.changeTimeOfInterest);
|
|
||||||
this.viewService.on('zoom', this.setOffsetFromZoom);
|
|
||||||
this.viewService.on('pan', this.setOffsetFromBounds);
|
|
||||||
|
|
||||||
var timeOfInterest = this.timeAPI.timeOfInterest();
|
|
||||||
if (timeOfInterest) {
|
|
||||||
this.changeTimeOfInterest(timeOfInterest);
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.$on('$destroy', this.destroy);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
ConductorTOIController.prototype.destroy = function () {
|
|
||||||
this.timeAPI.off('timeOfInterest', this.changeTimeOfInterest);
|
|
||||||
this.viewService.off('zoom', this.setOffsetFromZoom);
|
|
||||||
this.viewService.off('pan', this.setOffsetFromBounds);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given some bounds, set horizontal position of TOI indicator based
|
|
||||||
* on current conductor TOI value. Bounds are provided so that
|
|
||||||
* ephemeral bounds from zoom and pan events can be used as well
|
|
||||||
* as current conductor bounds, allowing TOI to be updated in
|
|
||||||
* realtime during scroll and zoom.
|
|
||||||
* @param {TimeConductorBounds} bounds
|
|
||||||
*/
|
|
||||||
ConductorTOIController.prototype.setOffsetFromBounds = function (bounds) {
|
|
||||||
var toi = this.timeAPI.timeOfInterest();
|
|
||||||
if (toi !== undefined) {
|
|
||||||
var offset = toi - bounds.start;
|
|
||||||
var duration = bounds.end - bounds.start;
|
|
||||||
this.left = offset / duration * 100;
|
|
||||||
this.pinned = true;
|
|
||||||
} else {
|
|
||||||
this.left = 0;
|
|
||||||
this.pinned = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
ConductorTOIController.prototype.setOffsetFromZoom = function (zoom) {
|
|
||||||
return this.setOffsetFromBounds(zoom.bounds);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoked when time of interest changes. Will set the horizontal offset of the TOI indicator.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
ConductorTOIController.prototype.changeTimeOfInterest = function () {
|
|
||||||
var bounds = this.timeAPI.bounds();
|
|
||||||
if (bounds) {
|
|
||||||
this.setOffsetFromBounds(bounds);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On a mouse click event within the TOI element, convert position within element to a time of interest, and
|
|
||||||
* set the time of interest on the conductor.
|
|
||||||
* @param e The angular $event object
|
|
||||||
*/
|
|
||||||
ConductorTOIController.prototype.setTOIFromPosition = function (e) {
|
|
||||||
//TOI is set using the alt key modified + primary click
|
|
||||||
if (e.altKey) {
|
|
||||||
var element = $(e.currentTarget);
|
|
||||||
var width = element.width();
|
|
||||||
var relativeX = e.pageX - element.offset().left;
|
|
||||||
var percX = relativeX / width;
|
|
||||||
var bounds = this.timeAPI.bounds();
|
|
||||||
var timeRange = bounds.end - bounds.start;
|
|
||||||
|
|
||||||
this.timeAPI.timeOfInterest(timeRange * percX + bounds.start);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return ConductorTOIController;
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,153 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define([
|
|
||||||
'./ConductorTOIController'
|
|
||||||
], function (
|
|
||||||
ConductorTOIController
|
|
||||||
) {
|
|
||||||
var mockConductor;
|
|
||||||
var mockConductorViewService;
|
|
||||||
var mockScope;
|
|
||||||
var mockAPI;
|
|
||||||
var conductorTOIController;
|
|
||||||
|
|
||||||
function getNamedCallback(thing, name) {
|
|
||||||
return thing.calls.all().filter(function (call) {
|
|
||||||
return call.args[0] === name;
|
|
||||||
}).map(function (call) {
|
|
||||||
return call.args;
|
|
||||||
})[0][1];
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("The ConductorTOIController", function () {
|
|
||||||
beforeEach(function () {
|
|
||||||
mockConductor = jasmine.createSpyObj("conductor", [
|
|
||||||
"bounds",
|
|
||||||
"timeOfInterest",
|
|
||||||
"on",
|
|
||||||
"off"
|
|
||||||
]);
|
|
||||||
mockAPI = {time: mockConductor};
|
|
||||||
|
|
||||||
mockConductorViewService = jasmine.createSpyObj("conductorViewService", [
|
|
||||||
"on",
|
|
||||||
"off"
|
|
||||||
]);
|
|
||||||
|
|
||||||
mockScope = jasmine.createSpyObj("openMCT", [
|
|
||||||
"$on"
|
|
||||||
]);
|
|
||||||
ConductorTOIController.prototype.viewService = mockConductorViewService;
|
|
||||||
conductorTOIController = new ConductorTOIController(mockScope, mockAPI);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("listens to changes in the time of interest on the conductor", function () {
|
|
||||||
expect(mockConductor.on).toHaveBeenCalledWith("timeOfInterest", jasmine.any(Function));
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when responding to changes in the time of interest", function () {
|
|
||||||
var toiCallback;
|
|
||||||
beforeEach(function () {
|
|
||||||
var bounds = {
|
|
||||||
start: 0,
|
|
||||||
end: 200
|
|
||||||
};
|
|
||||||
mockConductor.bounds.and.returnValue(bounds);
|
|
||||||
toiCallback = getNamedCallback(mockConductor.on, "timeOfInterest");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("calculates the correct horizontal offset based on bounds and current TOI", function () {
|
|
||||||
//Expect time of interest position to be 50% of element width
|
|
||||||
mockConductor.timeOfInterest.and.returnValue(100);
|
|
||||||
toiCallback();
|
|
||||||
expect(conductorTOIController.left).toBe(50);
|
|
||||||
|
|
||||||
//Expect time of interest position to be 25% of element width
|
|
||||||
mockConductor.timeOfInterest.and.returnValue(50);
|
|
||||||
toiCallback();
|
|
||||||
expect(conductorTOIController.left).toBe(25);
|
|
||||||
|
|
||||||
//Expect time of interest position to be 0% of element width
|
|
||||||
mockConductor.timeOfInterest.and.returnValue(0);
|
|
||||||
toiCallback();
|
|
||||||
expect(conductorTOIController.left).toBe(0);
|
|
||||||
|
|
||||||
//Expect time of interest position to be 100% of element width
|
|
||||||
mockConductor.timeOfInterest.and.returnValue(200);
|
|
||||||
toiCallback();
|
|
||||||
expect(conductorTOIController.left).toBe(100);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("renders the TOI indicator visible", function () {
|
|
||||||
expect(conductorTOIController.pinned).toBeFalsy();
|
|
||||||
mockConductor.timeOfInterest.and.returnValue(100);
|
|
||||||
toiCallback();
|
|
||||||
expect(conductorTOIController.pinned).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("responds to zoom events", function () {
|
|
||||||
var mockZoom = {
|
|
||||||
bounds: {
|
|
||||||
start: 500,
|
|
||||||
end: 1000
|
|
||||||
}
|
|
||||||
};
|
|
||||||
expect(mockConductorViewService.on).toHaveBeenCalledWith("zoom", jasmine.any(Function));
|
|
||||||
|
|
||||||
// Should correspond to horizontal offset of 50%
|
|
||||||
mockConductor.timeOfInterest.and.returnValue(750);
|
|
||||||
var zoomCallback = getNamedCallback(mockConductorViewService.on, "zoom");
|
|
||||||
zoomCallback(mockZoom);
|
|
||||||
expect(conductorTOIController.left).toBe(50);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("responds to pan events", function () {
|
|
||||||
var mockPanBounds = {
|
|
||||||
start: 1000,
|
|
||||||
end: 3000
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(mockConductorViewService.on).toHaveBeenCalledWith("pan", jasmine.any(Function));
|
|
||||||
|
|
||||||
// Should correspond to horizontal offset of 25%
|
|
||||||
mockConductor.timeOfInterest.and.returnValue(1500);
|
|
||||||
var panCallback = getNamedCallback(mockConductorViewService.on, "pan");
|
|
||||||
panCallback(mockPanBounds);
|
|
||||||
expect(conductorTOIController.left).toBe(25);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it("Cleans up all listeners when controller destroyed", function () {
|
|
||||||
var zoomCB = getNamedCallback(mockConductorViewService.on, "zoom");
|
|
||||||
var panCB = getNamedCallback(mockConductorViewService.on, "pan");
|
|
||||||
var toiCB = getNamedCallback(mockConductor.on, "timeOfInterest");
|
|
||||||
|
|
||||||
expect(mockScope.$on).toHaveBeenCalledWith("$destroy", jasmine.any(Function));
|
|
||||||
getNamedCallback(mockScope.$on, "$destroy")();
|
|
||||||
expect(mockConductorViewService.off).toHaveBeenCalledWith("zoom", zoomCB);
|
|
||||||
expect(mockConductorViewService.off).toHaveBeenCalledWith("pan", panCB);
|
|
||||||
expect(mockConductor.off).toHaveBeenCalledWith("timeOfInterest", toiCB);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,63 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(['./ConductorTOIController'], function (ConductorTOIController) {
|
|
||||||
/**
|
|
||||||
* A directive that encapsulates the TOI specific behavior of the Time Conductor UI.
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function ConductorTOIDirective() {
|
|
||||||
/**
|
|
||||||
* The mct-conductor-axis renders a horizontal axis with regular
|
|
||||||
* labelled 'ticks'. It requires 'start' and 'end' integer values to
|
|
||||||
* be specified as attributes.
|
|
||||||
*/
|
|
||||||
return {
|
|
||||||
controller: [
|
|
||||||
'$scope',
|
|
||||||
'openmct',
|
|
||||||
ConductorTOIController
|
|
||||||
],
|
|
||||||
controllerAs: 'toi',
|
|
||||||
scope: {
|
|
||||||
viewService: "="
|
|
||||||
},
|
|
||||||
bindToController: true,
|
|
||||||
|
|
||||||
restrict: 'E',
|
|
||||||
priority: 1000,
|
|
||||||
|
|
||||||
template:
|
|
||||||
'<div class="l-data-visualization-holder l-row-elem flex-elem">' +
|
|
||||||
' <a class="l-page-button s-icon-button icon-pointer-left"></a>' +
|
|
||||||
' <div class="l-data-visualization" ng-click="toi.setTOIFromPosition($event)">' +
|
|
||||||
' <mct-include key="\'time-of-interest\'" class="l-toi-holder show-val" ' +
|
|
||||||
' ng-class="{ pinned: toi.pinned, \'val-to-left\': toi.left > 80 }" ' +
|
|
||||||
' ng-style="{\'left\': toi.left + \'%\'}"></mct-include>' +
|
|
||||||
' </div>' +
|
|
||||||
' <a class="l-page-button align-right s-icon-button icon-pointer-right"></a>' +
|
|
||||||
'</div>'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return ConductorTOIDirective;
|
|
||||||
});
|
|
@ -1,49 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(['./NumberFormat'], function (NumberFormat) {
|
|
||||||
describe("The NumberFormat class", function () {
|
|
||||||
var format;
|
|
||||||
beforeEach(function () {
|
|
||||||
format = new NumberFormat();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("The format function takes a string and produces a number", function () {
|
|
||||||
var text = format.format(1);
|
|
||||||
expect(text).toBe("1");
|
|
||||||
expect(typeof text).toBe("string");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("The parse function takes a string and produces a number", function () {
|
|
||||||
var number = format.parse("1");
|
|
||||||
expect(number).toBe(1);
|
|
||||||
expect(typeof number).toBe("number");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("validates that the input is a number", function () {
|
|
||||||
expect(format.validate("1")).toBe(true);
|
|
||||||
expect(format.validate(1)).toBe(true);
|
|
||||||
expect(format.validate("1.1")).toBe(true);
|
|
||||||
expect(format.validate("abc")).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,554 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
[
|
|
||||||
'moment',
|
|
||||||
'./TimeConductorValidation',
|
|
||||||
'./TimeConductorViewService'
|
|
||||||
],
|
|
||||||
function (
|
|
||||||
moment,
|
|
||||||
TimeConductorValidation,
|
|
||||||
TimeConductorViewService
|
|
||||||
) {
|
|
||||||
|
|
||||||
var timeUnitsMegastructure = [
|
|
||||||
["Decades", function (r) {
|
|
||||||
return r.years() > 15;
|
|
||||||
}],
|
|
||||||
["Years", function (r) {
|
|
||||||
return r.years() > 1;
|
|
||||||
}],
|
|
||||||
["Months", function (r) {
|
|
||||||
return r.years() === 1 || r.months() > 1;
|
|
||||||
}],
|
|
||||||
["Days", function (r) {
|
|
||||||
return r.months() === 1 || r.days() > 1;
|
|
||||||
}],
|
|
||||||
["Hours", function (r) {
|
|
||||||
return r.days() === 1 || r.hours() > 1;
|
|
||||||
}],
|
|
||||||
["Minutes", function (r) {
|
|
||||||
return r.hours() === 1 || r.minutes() > 1;
|
|
||||||
}],
|
|
||||||
["Seconds", function (r) {
|
|
||||||
return r.minutes() === 1 || r.seconds() > 1;
|
|
||||||
}],
|
|
||||||
["Milliseconds", function (r) {
|
|
||||||
return true;
|
|
||||||
}]
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Controller for the Time Conductor UI element. The Time Conductor
|
|
||||||
* includes form fields for specifying time bounds and relative time
|
|
||||||
* offsets for queries, as well as controls for selection mode,
|
|
||||||
* time systems, and zooming.
|
|
||||||
* @memberof platform.features.conductor
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function TimeConductorController(
|
|
||||||
$scope,
|
|
||||||
$window,
|
|
||||||
openmct,
|
|
||||||
formatService,
|
|
||||||
config
|
|
||||||
) {
|
|
||||||
|
|
||||||
//Bind functions that are used as callbacks to 'this'.
|
|
||||||
[
|
|
||||||
"selectMenuOption",
|
|
||||||
"onPan",
|
|
||||||
"onPanStop",
|
|
||||||
"setViewFromBounds",
|
|
||||||
"setViewFromClock",
|
|
||||||
"setViewFromOffsets",
|
|
||||||
"setViewFromTimeSystem",
|
|
||||||
"setTimeSystemFromView",
|
|
||||||
"destroy"
|
|
||||||
].forEach(function (name) {
|
|
||||||
this[name] = this[name].bind(this);
|
|
||||||
}.bind(this));
|
|
||||||
|
|
||||||
this.$scope = $scope;
|
|
||||||
this.$window = $window;
|
|
||||||
this.timeAPI = openmct.time;
|
|
||||||
this.conductorViewService = new TimeConductorViewService(openmct);
|
|
||||||
this.validation = new TimeConductorValidation(this.timeAPI);
|
|
||||||
this.formatService = formatService;
|
|
||||||
this.config = config;
|
|
||||||
this.timeSystemsForClocks = {};
|
|
||||||
this.$scope.timeSystemModel = {};
|
|
||||||
this.$scope.boundsModel = {};
|
|
||||||
|
|
||||||
this.timeSystems = this.timeAPI.getAllTimeSystems().reduce(function (map, timeSystem) {
|
|
||||||
map[timeSystem.key] = timeSystem;
|
|
||||||
return map;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
this.isFixed = this.timeAPI.clock() === undefined;
|
|
||||||
|
|
||||||
var options = this.optionsFromConfig(config);
|
|
||||||
this.menu = {
|
|
||||||
selected: undefined,
|
|
||||||
options: options,
|
|
||||||
select: this.selectMenuOption
|
|
||||||
};
|
|
||||||
|
|
||||||
//Set the initial state of the UI from the conductor state
|
|
||||||
var timeSystem = this.timeAPI.timeSystem();
|
|
||||||
if (timeSystem) {
|
|
||||||
this.setViewFromTimeSystem(timeSystem);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setViewFromClock(this.timeAPI.clock());
|
|
||||||
|
|
||||||
var offsets = this.timeAPI.clockOffsets();
|
|
||||||
if (offsets) {
|
|
||||||
this.setViewFromOffsets(offsets);
|
|
||||||
}
|
|
||||||
|
|
||||||
var bounds = this.timeAPI.bounds();
|
|
||||||
if (bounds && bounds.start !== undefined && bounds.end !== undefined) {
|
|
||||||
this.setViewFromBounds(bounds);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.conductorViewService.on('pan', this.onPan);
|
|
||||||
this.conductorViewService.on('pan-stop', this.onPanStop);
|
|
||||||
|
|
||||||
//Respond to any subsequent conductor changes
|
|
||||||
this.timeAPI.on('bounds', this.setViewFromBounds);
|
|
||||||
this.timeAPI.on('timeSystem', this.setViewFromTimeSystem);
|
|
||||||
this.timeAPI.on('clock', this.setViewFromClock);
|
|
||||||
this.timeAPI.on('clockOffsets', this.setViewFromOffsets);
|
|
||||||
this.$scope.$on('$destroy', this.destroy);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given a key for a clock, retrieve the clock object.
|
|
||||||
* @private
|
|
||||||
* @param key
|
|
||||||
* @returns {Clock}
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.getClock = function (key) {
|
|
||||||
return this.timeAPI.getAllClocks().filter(function (clock) {
|
|
||||||
return clock.key === key;
|
|
||||||
})[0];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Activate the selected menu option. Menu options correspond to clocks.
|
|
||||||
* A distinction is made to avoid confusion between the menu options and
|
|
||||||
* their metadata, and actual {@link Clock} objects.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @param newOption
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.selectMenuOption = function (newOption) {
|
|
||||||
if (this.menu.selected.key === newOption.key) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.menu.selected = newOption;
|
|
||||||
|
|
||||||
var config = this.getConfig(this.timeAPI.timeSystem(), newOption.clock);
|
|
||||||
if (!config) {
|
|
||||||
// Clock does not support this timeSystem, fallback to first
|
|
||||||
// option provided for clock.
|
|
||||||
config = this.config.menuOptions.filter(function (menuOption) {
|
|
||||||
return menuOption.clock === (newOption.clock && newOption.clock.key);
|
|
||||||
})[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.clock) {
|
|
||||||
this.timeAPI.clock(config.clock, config.clockOffsets);
|
|
||||||
this.timeAPI.timeSystem(config.timeSystem);
|
|
||||||
} else {
|
|
||||||
this.timeAPI.stopClock();
|
|
||||||
this.timeAPI.timeSystem(config.timeSystem, config.bounds);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* From the provided configuration, build the available menu options.
|
|
||||||
* @private
|
|
||||||
* @param config
|
|
||||||
* @returns {*[]}
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.optionsFromConfig = function (config) {
|
|
||||||
/*
|
|
||||||
* "Fixed Mode" is always the first available option.
|
|
||||||
*/
|
|
||||||
var options = [{
|
|
||||||
key: 'fixed',
|
|
||||||
name: 'Fixed Timespan Mode',
|
|
||||||
description: 'Query and explore data that falls between two fixed datetimes.',
|
|
||||||
cssClass: 'icon-calendar'
|
|
||||||
}];
|
|
||||||
var clocks = {};
|
|
||||||
var timeSystemsForClocks = this.timeSystemsForClocks;
|
|
||||||
|
|
||||||
(config.menuOptions || []).forEach(function (menuOption) {
|
|
||||||
var clockKey = menuOption.clock || 'fixed';
|
|
||||||
var clock = this.getClock(clockKey);
|
|
||||||
|
|
||||||
if (clock !== undefined) {
|
|
||||||
clocks[clock.key] = clock;
|
|
||||||
}
|
|
||||||
|
|
||||||
var timeSystem = this.timeSystems[menuOption.timeSystem];
|
|
||||||
if (timeSystem !== undefined) {
|
|
||||||
timeSystemsForClocks[clockKey] = timeSystemsForClocks[clockKey] || [];
|
|
||||||
timeSystemsForClocks[clockKey].push(timeSystem);
|
|
||||||
}
|
|
||||||
}, this);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Populate the clocks menu with metadata from the available clocks
|
|
||||||
*/
|
|
||||||
Object.values(clocks).forEach(function (clock) {
|
|
||||||
options.push({
|
|
||||||
key: clock.key,
|
|
||||||
name: clock.name,
|
|
||||||
description: "Monitor streaming data in real-time. The Time " +
|
|
||||||
"Conductor and displays will automatically advance themselves based on this clock. " + clock.description,
|
|
||||||
cssClass: clock.cssClass || 'icon-clock',
|
|
||||||
clock: clock
|
|
||||||
});
|
|
||||||
}.bind(this));
|
|
||||||
|
|
||||||
return options;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When bounds change, set UI values from the new bounds.
|
|
||||||
* @param {TimeBounds} bounds the bounds
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.setViewFromBounds = function (bounds) {
|
|
||||||
if (!this.zooming && !this.panning) {
|
|
||||||
this.$scope.boundsModel.start = bounds.start;
|
|
||||||
this.$scope.boundsModel.end = bounds.end;
|
|
||||||
|
|
||||||
if (this.supportsZoom()) {
|
|
||||||
var config = this.getConfig(this.timeAPI.timeSystem(), this.timeAPI.clock());
|
|
||||||
this.currentZoom = this.toSliderValue(bounds.end - bounds.start, config.zoomOutLimit, config.zoomInLimit);
|
|
||||||
this.toTimeUnits(bounds.end - bounds.start);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Ensure that a digest occurs, capped at the browser's refresh
|
|
||||||
rate.
|
|
||||||
*/
|
|
||||||
if (!this.pendingUpdate) {
|
|
||||||
this.pendingUpdate = true;
|
|
||||||
this.$window.requestAnimationFrame(function () {
|
|
||||||
this.pendingUpdate = false;
|
|
||||||
this.$scope.$digest();
|
|
||||||
}.bind(this));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve any configuration defined for the provided time system and
|
|
||||||
* clock
|
|
||||||
* @private
|
|
||||||
* @param timeSystem
|
|
||||||
* @param clock
|
|
||||||
* @returns {object} The Time Conductor configuration corresponding to
|
|
||||||
* the provided combination of time system and clock
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.getConfig = function (timeSystem, clock) {
|
|
||||||
var clockKey = clock && clock.key;
|
|
||||||
var timeSystemKey = timeSystem && timeSystem.key;
|
|
||||||
|
|
||||||
var option = this.config.menuOptions.filter(function (menuOption) {
|
|
||||||
return menuOption.timeSystem === timeSystemKey && menuOption.clock === clockKey;
|
|
||||||
})[0];
|
|
||||||
return option;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When the clock offsets change, update the values in the UI
|
|
||||||
* @param {ClockOffsets} offsets
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.setViewFromOffsets = function (offsets) {
|
|
||||||
this.$scope.boundsModel.startOffset = Math.abs(offsets.start);
|
|
||||||
this.$scope.boundsModel.endOffset = offsets.end;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When form values for bounds change, update the bounds in the Time API
|
|
||||||
* to trigger an application-wide bounds change.
|
|
||||||
* @param {object} boundsModel
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.setBoundsFromView = function (boundsModel) {
|
|
||||||
var bounds = this.timeAPI.bounds();
|
|
||||||
if (boundsModel.start !== bounds.start || boundsModel.end !== bounds.end) {
|
|
||||||
this.timeAPI.bounds({
|
|
||||||
start: boundsModel.start,
|
|
||||||
end: boundsModel.end
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When form values for bounds change, update the bounds in the Time API
|
|
||||||
* to trigger an application-wide bounds change.
|
|
||||||
* @param {object} formModel
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.setOffsetsFromView = function (boundsModel) {
|
|
||||||
if (this.validation.validateStartOffset(boundsModel.startOffset) && this.validation.validateEndOffset(boundsModel.endOffset)) {
|
|
||||||
var offsets = {
|
|
||||||
start: 0 - boundsModel.startOffset,
|
|
||||||
end: boundsModel.endOffset
|
|
||||||
};
|
|
||||||
var existingOffsets = this.timeAPI.clockOffsets();
|
|
||||||
|
|
||||||
if (offsets.start !== existingOffsets.start || offsets.end !== existingOffsets.end) {
|
|
||||||
//Sychronize offsets between form and time API
|
|
||||||
this.timeAPI.clockOffsets(offsets);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.supportsZoom = function () {
|
|
||||||
var config = this.getConfig(this.timeAPI.timeSystem(), this.timeAPI.clock());
|
|
||||||
return config && (config.zoomInLimit !== undefined && config.zoomOutLimit !== undefined);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the UI state to reflect a change in clock. Provided conductor
|
|
||||||
* configuration will be checked for compatibility between the new clock
|
|
||||||
* and the currently selected time system. If configuration is not available,
|
|
||||||
* an attempt will be made to default to a time system that is compatible
|
|
||||||
* with the new clock
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @param {Clock} clock
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.setViewFromClock = function (clock) {
|
|
||||||
var newClockKey = clock ? clock.key : 'fixed';
|
|
||||||
var timeSystems = this.timeSystemsForClocks[newClockKey];
|
|
||||||
var menuOption = this.menu.options.filter(function (option) {
|
|
||||||
return option.key === (newClockKey);
|
|
||||||
})[0];
|
|
||||||
|
|
||||||
this.menu.selected = menuOption;
|
|
||||||
|
|
||||||
//Try to find currently selected time system in time systems for clock
|
|
||||||
var selectedTimeSystem = timeSystems.filter(function (timeSystem) {
|
|
||||||
return timeSystem.key === this.$scope.timeSystemModel.selected.key;
|
|
||||||
}.bind(this))[0];
|
|
||||||
|
|
||||||
var config = this.getConfig(selectedTimeSystem, clock);
|
|
||||||
|
|
||||||
if (selectedTimeSystem === undefined) {
|
|
||||||
selectedTimeSystem = timeSystems[0];
|
|
||||||
config = this.getConfig(selectedTimeSystem, clock);
|
|
||||||
|
|
||||||
if (clock === undefined) {
|
|
||||||
this.timeAPI.timeSystem(selectedTimeSystem, config.bounds);
|
|
||||||
} else {
|
|
||||||
//When time system changes, some start bounds need to be provided
|
|
||||||
this.timeAPI.timeSystem(selectedTimeSystem, {
|
|
||||||
start: clock.currentValue() + config.clockOffsets.start,
|
|
||||||
end: clock.currentValue() + config.clockOffsets.end
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.isFixed = clock === undefined;
|
|
||||||
|
|
||||||
if (clock === undefined) {
|
|
||||||
this.setViewFromBounds(this.timeAPI.bounds());
|
|
||||||
}
|
|
||||||
|
|
||||||
this.zoom = this.supportsZoom();
|
|
||||||
this.$scope.timeSystemModel.options = timeSystems;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Respond to time system selection from UI
|
|
||||||
*
|
|
||||||
* Allows time system to be changed by key. This supports selection
|
|
||||||
* from the menu. Resolves a TimeSystem object and then invokes
|
|
||||||
* TimeConductorController#setTimeSystem
|
|
||||||
* @param key
|
|
||||||
* @see TimeConductorController#setTimeSystem
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.setTimeSystemFromView = function (key) {
|
|
||||||
var clock = this.menu.selected.clock;
|
|
||||||
var timeSystem = this.timeSystems[key];
|
|
||||||
var config = this.getConfig(timeSystem, clock);
|
|
||||||
|
|
||||||
this.$scope.timeSystemModel.selected = timeSystem;
|
|
||||||
this.$scope.timeSystemModel.format = timeSystem.timeFormat;
|
|
||||||
|
|
||||||
if (clock === undefined) {
|
|
||||||
this.timeAPI.timeSystem(timeSystem, config.bounds);
|
|
||||||
} else {
|
|
||||||
this.timeAPI.clock(clock, config.clockOffsets);
|
|
||||||
this.timeAPI.timeSystem(timeSystem);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles time system change from time conductor
|
|
||||||
*
|
|
||||||
* Sets the selected time system. Will populate form with the default
|
|
||||||
* bounds and offsets defined in the selected time system.
|
|
||||||
*
|
|
||||||
* @param newTimeSystem
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.setViewFromTimeSystem = function (timeSystem) {
|
|
||||||
var oldKey = (this.$scope.timeSystemModel.selected || {}).key;
|
|
||||||
var timeSystemModel = this.$scope.timeSystemModel;
|
|
||||||
|
|
||||||
if (timeSystem && (timeSystem.key !== oldKey)) {
|
|
||||||
var config = this.getConfig(timeSystem, this.timeAPI.clock());
|
|
||||||
|
|
||||||
timeSystemModel.selected = timeSystem;
|
|
||||||
timeSystemModel.format = timeSystem.timeFormat;
|
|
||||||
timeSystemModel.durationFormat = timeSystem.durationFormat;
|
|
||||||
|
|
||||||
if (this.supportsZoom()) {
|
|
||||||
timeSystemModel.minZoom = config.zoomOutLimit;
|
|
||||||
timeSystemModel.maxZoom = config.zoomInLimit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.zoom = this.supportsZoom();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Takes a time span and calculates a slider increment value, used
|
|
||||||
* to set the horizontal offset of the slider.
|
|
||||||
* @private
|
|
||||||
* @param {number} timeSpan a duration of time, in ms
|
|
||||||
* @returns {number} a value between 0.01 and 0.99, in increments of .01
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.toSliderValue = function (timeSpan, zoomOutLimit, zoomInLimit) {
|
|
||||||
var perc = timeSpan / (zoomOutLimit - zoomInLimit);
|
|
||||||
return 1 - Math.pow(perc, 1 / 4);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given a time span, set a label for the units of time that it,
|
|
||||||
* roughly, represents. Leverages
|
|
||||||
* @param {TimeSpan} timeSpan
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.toTimeUnits = function (timeSpan) {
|
|
||||||
var timeSystem = this.timeAPI.timeSystem();
|
|
||||||
if (timeSystem && timeSystem.isUTCBased) {
|
|
||||||
var momentified = moment.duration(timeSpan);
|
|
||||||
|
|
||||||
this.$scope.timeUnits = timeUnitsMegastructure.filter(function (row) {
|
|
||||||
return row[1](momentified);
|
|
||||||
})[0][0];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Zooming occurs when the user manipulates the zoom slider.
|
|
||||||
* Zooming updates the scale and bounds fields immediately, but does
|
|
||||||
* not trigger a bounds change to other views until the mouse button
|
|
||||||
* is released.
|
|
||||||
* @param bounds
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.onZoom = function (sliderValue) {
|
|
||||||
var config = this.getConfig(this.timeAPI.timeSystem(), this.timeAPI.clock());
|
|
||||||
var timeSpan = Math.pow((1 - sliderValue), 4) * (config.zoomOutLimit - config.zoomInLimit);
|
|
||||||
|
|
||||||
var zoom = this.conductorViewService.zoom(timeSpan);
|
|
||||||
this.zooming = true;
|
|
||||||
|
|
||||||
this.$scope.boundsModel.start = zoom.bounds.start;
|
|
||||||
this.$scope.boundsModel.end = zoom.bounds.end;
|
|
||||||
this.toTimeUnits(zoom.bounds.end - zoom.bounds.start);
|
|
||||||
|
|
||||||
if (zoom.offsets) {
|
|
||||||
this.setViewFromOffsets(zoom.offsets);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fired when user has released the zoom slider
|
|
||||||
* @event platform.features.conductor.TimeConductorController~zoomStop
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Invoked when zoom slider is released by user. Will update the time conductor with the new bounds, triggering
|
|
||||||
* a global bounds change event.
|
|
||||||
* @fires platform.features.conductor.TimeConductorController~zoomStop
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.onZoomStop = function () {
|
|
||||||
if (this.timeAPI.clock() !== undefined) {
|
|
||||||
this.setOffsetsFromView(this.$scope.boundsModel);
|
|
||||||
}
|
|
||||||
this.setBoundsFromView(this.$scope.boundsModel);
|
|
||||||
|
|
||||||
this.zooming = false;
|
|
||||||
this.conductorViewService.emit('zoom-stop');
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Panning occurs when the user grabs the conductor scale and drags
|
|
||||||
* it left or right to slide the window of time represented by the
|
|
||||||
* conductor. Panning updates the scale and bounds fields
|
|
||||||
* immediately, but does not trigger a bounds change to other views
|
|
||||||
* until the mouse button is released.
|
|
||||||
* @param {TimeConductorBounds} bounds
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.onPan = function (bounds) {
|
|
||||||
this.panning = true;
|
|
||||||
this.$scope.boundsModel.start = bounds.start;
|
|
||||||
this.$scope.boundsModel.end = bounds.end;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the user releases the mouse button after panning.
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.onPanStop = function () {
|
|
||||||
this.panning = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
TimeConductorController.prototype.destroy = function () {
|
|
||||||
this.timeAPI.off('bounds', this.setViewFromBounds);
|
|
||||||
this.timeAPI.off('timeSystem', this.setViewFromTimeSystem);
|
|
||||||
this.timeAPI.off('clock', this.setViewFromClock);
|
|
||||||
this.timeAPI.off('follow', this.setFollow);
|
|
||||||
this.timeAPI.off('clockOffsets', this.setViewFromOffsets);
|
|
||||||
|
|
||||||
this.conductorViewService.off('pan', this.onPan);
|
|
||||||
this.conductorViewService.off('pan-stop', this.onPanStop);
|
|
||||||
};
|
|
||||||
|
|
||||||
return TimeConductorController;
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,513 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(['./TimeConductorController'], function (TimeConductorController) {
|
|
||||||
xdescribe("The time conductor controller", function () {
|
|
||||||
var mockScope;
|
|
||||||
var mockWindow;
|
|
||||||
var mockTimeConductor;
|
|
||||||
var mockConductorViewService;
|
|
||||||
var mockTimeSystems;
|
|
||||||
var controller;
|
|
||||||
var mockFormatService;
|
|
||||||
var mockFormat;
|
|
||||||
var mockLocation;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
mockScope = jasmine.createSpyObj("$scope", [
|
|
||||||
"$watch",
|
|
||||||
"$on"
|
|
||||||
]);
|
|
||||||
|
|
||||||
mockWindow = jasmine.createSpyObj("$window", ["requestAnimationFrame"]);
|
|
||||||
mockTimeConductor = jasmine.createSpyObj(
|
|
||||||
"TimeConductor",
|
|
||||||
[
|
|
||||||
"bounds",
|
|
||||||
"timeSystem",
|
|
||||||
"on",
|
|
||||||
"off"
|
|
||||||
]
|
|
||||||
);
|
|
||||||
mockTimeConductor.bounds.and.returnValue({start: undefined, end: undefined});
|
|
||||||
|
|
||||||
mockConductorViewService = jasmine.createSpyObj(
|
|
||||||
"ConductorViewService",
|
|
||||||
[
|
|
||||||
"availableModes",
|
|
||||||
"mode",
|
|
||||||
"availableTimeSystems",
|
|
||||||
"deltas",
|
|
||||||
"deltas",
|
|
||||||
"on",
|
|
||||||
"off"
|
|
||||||
]
|
|
||||||
);
|
|
||||||
mockConductorViewService.availableModes.and.returnValue([]);
|
|
||||||
mockConductorViewService.availableTimeSystems.and.returnValue([]);
|
|
||||||
|
|
||||||
mockFormatService = jasmine.createSpyObj('formatService', [
|
|
||||||
'getFormat'
|
|
||||||
]);
|
|
||||||
mockFormat = jasmine.createSpyObj('format', [
|
|
||||||
'format'
|
|
||||||
]);
|
|
||||||
mockFormatService.getFormat.and.returnValue(mockFormat);
|
|
||||||
mockLocation = jasmine.createSpyObj('location', [
|
|
||||||
'search'
|
|
||||||
]);
|
|
||||||
mockLocation.search.and.returnValue({});
|
|
||||||
|
|
||||||
mockTimeSystems = [];
|
|
||||||
});
|
|
||||||
|
|
||||||
function getListener(target, event) {
|
|
||||||
return target.calls.all().filter(function (call) {
|
|
||||||
return call.args[0] === event;
|
|
||||||
})[0].args[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("when time conductor state changes", function () {
|
|
||||||
var mockDeltaFormat;
|
|
||||||
var defaultBounds;
|
|
||||||
var defaultDeltas;
|
|
||||||
var mockDefaults;
|
|
||||||
var timeSystem;
|
|
||||||
var tsListener;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
mockFormat = {};
|
|
||||||
mockDeltaFormat = {};
|
|
||||||
defaultBounds = {
|
|
||||||
start: 2,
|
|
||||||
end: 3
|
|
||||||
};
|
|
||||||
defaultDeltas = {
|
|
||||||
start: 10,
|
|
||||||
end: 20
|
|
||||||
};
|
|
||||||
mockDefaults = {
|
|
||||||
deltas: defaultDeltas,
|
|
||||||
bounds: defaultBounds
|
|
||||||
};
|
|
||||||
timeSystem = {
|
|
||||||
metadata: {
|
|
||||||
key: 'mock'
|
|
||||||
},
|
|
||||||
formats: function () {
|
|
||||||
return [mockFormat];
|
|
||||||
},
|
|
||||||
deltaFormat: function () {
|
|
||||||
return mockDeltaFormat;
|
|
||||||
},
|
|
||||||
defaults: function () {
|
|
||||||
return mockDefaults;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
controller = new TimeConductorController(
|
|
||||||
mockScope,
|
|
||||||
mockWindow,
|
|
||||||
mockLocation,
|
|
||||||
{conductor: mockTimeConductor},
|
|
||||||
mockConductorViewService,
|
|
||||||
mockFormatService,
|
|
||||||
'fixed',
|
|
||||||
true
|
|
||||||
|
|
||||||
);
|
|
||||||
|
|
||||||
tsListener = getListener(mockTimeConductor.on, "timeSystem");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("listens for changes to conductor state", function () {
|
|
||||||
expect(mockTimeConductor.on).toHaveBeenCalledWith("timeSystem", controller.changeTimeSystem);
|
|
||||||
expect(mockTimeConductor.on).toHaveBeenCalledWith("bounds", controller.changeBounds);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("deregisters conductor listens when scope is destroyed", function () {
|
|
||||||
expect(mockScope.$on).toHaveBeenCalledWith("$destroy", controller.destroy);
|
|
||||||
|
|
||||||
controller.destroy();
|
|
||||||
expect(mockTimeConductor.off).toHaveBeenCalledWith("timeSystem", controller.changeTimeSystem);
|
|
||||||
expect(mockTimeConductor.off).toHaveBeenCalledWith("bounds", controller.changeBounds);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("when time system changes, sets time system on scope", function () {
|
|
||||||
expect(tsListener).toBeDefined();
|
|
||||||
tsListener(timeSystem);
|
|
||||||
|
|
||||||
expect(mockScope.timeSystemModel).toBeDefined();
|
|
||||||
expect(mockScope.timeSystemModel.selected).toBe(timeSystem);
|
|
||||||
expect(mockScope.timeSystemModel.format).toBe(mockFormat);
|
|
||||||
expect(mockScope.timeSystemModel.deltaFormat).toBe(mockDeltaFormat);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("when time system changes, sets defaults on scope", function () {
|
|
||||||
mockDefaults.zoom = {
|
|
||||||
min: 100,
|
|
||||||
max: 10
|
|
||||||
};
|
|
||||||
mockTimeConductor.timeSystem.and.returnValue(timeSystem);
|
|
||||||
tsListener(timeSystem);
|
|
||||||
|
|
||||||
expect(mockScope.boundsModel.start).toEqual(defaultBounds.start);
|
|
||||||
expect(mockScope.boundsModel.end).toEqual(defaultBounds.end);
|
|
||||||
|
|
||||||
expect(mockScope.boundsModel.startDelta).toEqual(defaultDeltas.start);
|
|
||||||
expect(mockScope.boundsModel.endDelta).toEqual(defaultDeltas.end);
|
|
||||||
|
|
||||||
expect(mockScope.timeSystemModel.minZoom).toBe(mockDefaults.zoom.min);
|
|
||||||
expect(mockScope.timeSystemModel.maxZoom).toBe(mockDefaults.zoom.max);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("supports zoom if time system defines zoom defaults", function () {
|
|
||||||
|
|
||||||
mockDefaults.zoom = undefined;
|
|
||||||
|
|
||||||
tsListener(timeSystem);
|
|
||||||
expect(controller.supportsZoom).toBe(false);
|
|
||||||
|
|
||||||
mockDefaults.zoom = {
|
|
||||||
min: 100,
|
|
||||||
max: 10
|
|
||||||
};
|
|
||||||
|
|
||||||
var anotherTimeSystem = Object.create(timeSystem);
|
|
||||||
timeSystem.defaults = function () {
|
|
||||||
return mockDefaults;
|
|
||||||
};
|
|
||||||
|
|
||||||
tsListener(anotherTimeSystem);
|
|
||||||
expect(controller.supportsZoom).toBe(true);
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
it("when bounds change, sets the correct zoom slider value", function () {
|
|
||||||
var bounds = {
|
|
||||||
start: 0,
|
|
||||||
end: 50
|
|
||||||
};
|
|
||||||
mockDefaults.zoom = {
|
|
||||||
min: 100,
|
|
||||||
max: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
function exponentializer(rawValue) {
|
|
||||||
return 1 - Math.pow(rawValue, 1 / 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
mockTimeConductor.timeSystem.and.returnValue(timeSystem);
|
|
||||||
//Set zoom defaults
|
|
||||||
tsListener(timeSystem);
|
|
||||||
|
|
||||||
controller.changeBounds(bounds);
|
|
||||||
expect(controller.currentZoom).toEqual(exponentializer(0.5));
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
it("when bounds change, sets them on scope", function () {
|
|
||||||
var bounds = {
|
|
||||||
start: 1,
|
|
||||||
end: 2
|
|
||||||
};
|
|
||||||
|
|
||||||
var boundsListener = getListener(mockTimeConductor.on, "bounds");
|
|
||||||
expect(boundsListener).toBeDefined();
|
|
||||||
boundsListener(bounds);
|
|
||||||
|
|
||||||
expect(mockScope.boundsModel).toBeDefined();
|
|
||||||
expect(mockScope.boundsModel.start).toEqual(bounds.start);
|
|
||||||
expect(mockScope.boundsModel.end).toEqual(bounds.end);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when user makes changes from UI", function () {
|
|
||||||
var mode = "realtime";
|
|
||||||
var ts1Metadata;
|
|
||||||
var ts2Metadata;
|
|
||||||
var ts3Metadata;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
mode = "realtime";
|
|
||||||
ts1Metadata = {
|
|
||||||
'key': 'ts1',
|
|
||||||
'name': 'Time System One',
|
|
||||||
'cssClass': 'cssClassOne'
|
|
||||||
};
|
|
||||||
ts2Metadata = {
|
|
||||||
'key': 'ts2',
|
|
||||||
'name': 'Time System Two',
|
|
||||||
'cssClass': 'cssClassTwo'
|
|
||||||
};
|
|
||||||
ts3Metadata = {
|
|
||||||
'key': 'ts3',
|
|
||||||
'name': 'Time System Three',
|
|
||||||
'cssClass': 'cssClassThree'
|
|
||||||
};
|
|
||||||
mockTimeSystems = [
|
|
||||||
{
|
|
||||||
metadata: ts1Metadata
|
|
||||||
},
|
|
||||||
{
|
|
||||||
metadata: ts2Metadata
|
|
||||||
},
|
|
||||||
{
|
|
||||||
metadata: ts3Metadata
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
//Wrap in mock constructors
|
|
||||||
mockConductorViewService.systems = mockTimeSystems;
|
|
||||||
|
|
||||||
controller = new TimeConductorController(
|
|
||||||
mockScope,
|
|
||||||
mockWindow,
|
|
||||||
mockLocation,
|
|
||||||
{conductor: mockTimeConductor},
|
|
||||||
mockConductorViewService,
|
|
||||||
mockFormatService,
|
|
||||||
"fixed",
|
|
||||||
true
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("sets the mode on scope", function () {
|
|
||||||
mockConductorViewService.availableTimeSystems.and.returnValue(mockTimeSystems);
|
|
||||||
controller.setMode(mode);
|
|
||||||
|
|
||||||
expect(mockScope.modeModel.selectedKey).toEqual(mode);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("sets available time systems on scope when mode changes", function () {
|
|
||||||
mockConductorViewService.availableTimeSystems.and.returnValue(mockTimeSystems);
|
|
||||||
controller.setMode(mode);
|
|
||||||
|
|
||||||
expect(mockScope.timeSystemModel.options.length).toEqual(3);
|
|
||||||
expect(mockScope.timeSystemModel.options[0]).toEqual(ts1Metadata);
|
|
||||||
expect(mockScope.timeSystemModel.options[1]).toEqual(ts2Metadata);
|
|
||||||
expect(mockScope.timeSystemModel.options[2]).toEqual(ts3Metadata);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("sets bounds on the time conductor", function () {
|
|
||||||
var formModel = {
|
|
||||||
start: 1,
|
|
||||||
end: 10
|
|
||||||
};
|
|
||||||
|
|
||||||
controller.setBounds(formModel);
|
|
||||||
expect(mockTimeConductor.bounds).toHaveBeenCalledWith(formModel);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("applies deltas when they change in form", function () {
|
|
||||||
var deltas = {
|
|
||||||
start: 1000,
|
|
||||||
end: 2000
|
|
||||||
};
|
|
||||||
var formModel = {
|
|
||||||
startDelta: deltas.start,
|
|
||||||
endDelta: deltas.end
|
|
||||||
};
|
|
||||||
|
|
||||||
controller.setDeltas(formModel);
|
|
||||||
expect(mockConductorViewService.deltas).toHaveBeenCalledWith(deltas);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("sets the time system on the time conductor", function () {
|
|
||||||
var defaultBounds = {
|
|
||||||
start: 5,
|
|
||||||
end: 6
|
|
||||||
};
|
|
||||||
var timeSystem = {
|
|
||||||
metadata: {
|
|
||||||
key: 'testTimeSystem'
|
|
||||||
},
|
|
||||||
defaults: function () {
|
|
||||||
return {
|
|
||||||
bounds: defaultBounds
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
controller.timeSystems = [timeSystem];
|
|
||||||
|
|
||||||
controller.selectTimeSystemByKey('testTimeSystem');
|
|
||||||
expect(mockTimeConductor.timeSystem).toHaveBeenCalledWith(timeSystem, defaultBounds);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("updates form bounds during pan events", function () {
|
|
||||||
var testBounds = {
|
|
||||||
start: 10,
|
|
||||||
end: 20
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(controller.$scope.boundsModel.start).not.toBe(testBounds.start);
|
|
||||||
expect(controller.$scope.boundsModel.end).not.toBe(testBounds.end);
|
|
||||||
|
|
||||||
expect(controller.conductorViewService.on).toHaveBeenCalledWith("pan",
|
|
||||||
controller.onPan);
|
|
||||||
|
|
||||||
getListener(controller.conductorViewService.on, "pan")(testBounds);
|
|
||||||
|
|
||||||
expect(controller.$scope.boundsModel.start).toBe(testBounds.start);
|
|
||||||
expect(controller.$scope.boundsModel.end).toBe(testBounds.end);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when the URL defines conductor state", function () {
|
|
||||||
var urlBounds;
|
|
||||||
var urlTimeSystem;
|
|
||||||
var urlDeltas;
|
|
||||||
|
|
||||||
var mockDeltaFormat;
|
|
||||||
var defaultBounds;
|
|
||||||
var defaultDeltas;
|
|
||||||
var mockDefaults;
|
|
||||||
var timeSystem;
|
|
||||||
var otherTimeSystem;
|
|
||||||
var mockSearchObject;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
|
|
||||||
mockFormat = {};
|
|
||||||
mockDeltaFormat = {};
|
|
||||||
defaultBounds = {
|
|
||||||
start: 2,
|
|
||||||
end: 3
|
|
||||||
};
|
|
||||||
defaultDeltas = {
|
|
||||||
start: 10,
|
|
||||||
end: 20
|
|
||||||
};
|
|
||||||
mockDefaults = {
|
|
||||||
deltas: defaultDeltas,
|
|
||||||
bounds: defaultBounds
|
|
||||||
};
|
|
||||||
timeSystem = {
|
|
||||||
metadata: {
|
|
||||||
key: 'mockTimeSystem'
|
|
||||||
},
|
|
||||||
formats: function () {
|
|
||||||
return [mockFormat];
|
|
||||||
},
|
|
||||||
deltaFormat: function () {
|
|
||||||
return mockDeltaFormat;
|
|
||||||
},
|
|
||||||
defaults: function () {
|
|
||||||
return mockDefaults;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
otherTimeSystem = {
|
|
||||||
metadata: {
|
|
||||||
key: 'otherTimeSystem'
|
|
||||||
},
|
|
||||||
formats: function () {
|
|
||||||
return [mockFormat];
|
|
||||||
},
|
|
||||||
deltaFormat: function () {
|
|
||||||
return mockDeltaFormat;
|
|
||||||
},
|
|
||||||
defaults: function () {
|
|
||||||
return mockDefaults;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
mockConductorViewService.systems = [timeSystem, otherTimeSystem];
|
|
||||||
|
|
||||||
urlBounds = {
|
|
||||||
start: 100,
|
|
||||||
end: 200
|
|
||||||
};
|
|
||||||
urlTimeSystem = "otherTimeSystem";
|
|
||||||
urlDeltas = {
|
|
||||||
start: 300,
|
|
||||||
end: 400
|
|
||||||
};
|
|
||||||
mockSearchObject = {
|
|
||||||
"tc.startBound": urlBounds.start,
|
|
||||||
"tc.endBound": urlBounds.end,
|
|
||||||
"tc.startDelta": urlDeltas.start,
|
|
||||||
"tc.endDelta": urlDeltas.end,
|
|
||||||
"tc.timeSystem": urlTimeSystem
|
|
||||||
};
|
|
||||||
mockLocation.search.and.returnValue(mockSearchObject);
|
|
||||||
mockTimeConductor.timeSystem.and.returnValue(timeSystem);
|
|
||||||
|
|
||||||
controller = new TimeConductorController(
|
|
||||||
mockScope,
|
|
||||||
mockWindow,
|
|
||||||
mockLocation,
|
|
||||||
{conductor: mockTimeConductor},
|
|
||||||
mockConductorViewService,
|
|
||||||
mockFormatService,
|
|
||||||
"fixed",
|
|
||||||
true
|
|
||||||
);
|
|
||||||
|
|
||||||
spyOn(controller, "setMode");
|
|
||||||
spyOn(controller, "selectTimeSystemByKey");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("sets conductor state from URL", function () {
|
|
||||||
mockSearchObject["tc.mode"] = "fixed";
|
|
||||||
controller.setStateFromSearchParams(mockSearchObject);
|
|
||||||
expect(controller.selectTimeSystemByKey).toHaveBeenCalledWith("otherTimeSystem");
|
|
||||||
expect(mockTimeConductor.bounds).toHaveBeenCalledWith(urlBounds);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("sets mode from URL", function () {
|
|
||||||
mockTimeConductor.bounds.reset();
|
|
||||||
mockSearchObject["tc.mode"] = "realtime";
|
|
||||||
controller.setStateFromSearchParams(mockSearchObject);
|
|
||||||
expect(controller.setMode).toHaveBeenCalledWith("realtime");
|
|
||||||
expect(controller.selectTimeSystemByKey).toHaveBeenCalledWith("otherTimeSystem");
|
|
||||||
expect(mockConductorViewService.deltas).toHaveBeenCalledWith(urlDeltas);
|
|
||||||
expect(mockTimeConductor.bounds).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when conductor state changes", function () {
|
|
||||||
it("updates the URL with the mode", function () {
|
|
||||||
controller.setMode("realtime", "fixed");
|
|
||||||
expect(mockLocation.search).toHaveBeenCalledWith("tc.mode", "fixed");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("updates the URL with the bounds", function () {
|
|
||||||
mockConductorViewService.mode.and.returnValue("fixed");
|
|
||||||
controller.changeBounds({start: 500, end: 600});
|
|
||||||
expect(mockLocation.search).toHaveBeenCalledWith("tc.startBound", 500);
|
|
||||||
expect(mockLocation.search).toHaveBeenCalledWith("tc.endBound", 600);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("updates the URL with the deltas", function () {
|
|
||||||
controller.setDeltas({startDelta: 700, endDelta: 800});
|
|
||||||
expect(mockLocation.search).toHaveBeenCalledWith("tc.startDelta", 700);
|
|
||||||
expect(mockLocation.search).toHaveBeenCalledWith("tc.endDelta", 800);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("updates the URL with the time system", function () {
|
|
||||||
controller.changeTimeSystem(otherTimeSystem);
|
|
||||||
expect(mockLocation.search).toHaveBeenCalledWith("tc.timeSystem", "otherTimeSystem");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,69 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
[],
|
|
||||||
function () {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Form validation for the TimeConductorController.
|
|
||||||
* @param conductor
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function TimeConductorValidation(timeAPI) {
|
|
||||||
var self = this;
|
|
||||||
this.timeAPI = timeAPI;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Bind all class functions to 'this'
|
|
||||||
*/
|
|
||||||
Object.keys(TimeConductorValidation.prototype).filter(function (key) {
|
|
||||||
return typeof TimeConductorValidation.prototype[key] === 'function';
|
|
||||||
}).forEach(function (key) {
|
|
||||||
self[key] = self[key].bind(self);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validation methods below are invoked directly from controls in the TimeConductor form
|
|
||||||
*/
|
|
||||||
TimeConductorValidation.prototype.validateStart = function (start) {
|
|
||||||
var bounds = this.timeAPI.bounds();
|
|
||||||
return this.timeAPI.validateBounds({start: start, end: bounds.end}) === true;
|
|
||||||
};
|
|
||||||
|
|
||||||
TimeConductorValidation.prototype.validateEnd = function (end) {
|
|
||||||
var bounds = this.timeAPI.bounds();
|
|
||||||
return this.timeAPI.validateBounds({start: bounds.start, end: end}) === true;
|
|
||||||
};
|
|
||||||
|
|
||||||
TimeConductorValidation.prototype.validateStartOffset = function (startOffset) {
|
|
||||||
return !isNaN(startOffset) && startOffset > 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
TimeConductorValidation.prototype.validateEndOffset = function (endOffset) {
|
|
||||||
return !isNaN(endOffset) && endOffset >= 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
return TimeConductorValidation;
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,73 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(['./TimeConductorValidation'], function (TimeConductorValidation) {
|
|
||||||
describe("The Time Conductor Validation class", function () {
|
|
||||||
var timeConductorValidation,
|
|
||||||
mockTimeConductor;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
mockTimeConductor = jasmine.createSpyObj("timeConductor", [
|
|
||||||
"validateBounds",
|
|
||||||
"bounds"
|
|
||||||
]);
|
|
||||||
timeConductorValidation = new TimeConductorValidation(mockTimeConductor);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Validates start and end values using Time Conductor", function () {
|
|
||||||
beforeEach(function () {
|
|
||||||
var mockBounds = {
|
|
||||||
start: 10,
|
|
||||||
end: 20
|
|
||||||
};
|
|
||||||
|
|
||||||
mockTimeConductor.bounds.and.returnValue(mockBounds);
|
|
||||||
|
|
||||||
});
|
|
||||||
it("Validates start values using Time Conductor", function () {
|
|
||||||
var startValue = 30;
|
|
||||||
timeConductorValidation.validateStart(startValue);
|
|
||||||
expect(mockTimeConductor.validateBounds).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
it("Validates end values using Time Conductor", function () {
|
|
||||||
var endValue = 40;
|
|
||||||
timeConductorValidation.validateEnd(endValue);
|
|
||||||
expect(mockTimeConductor.validateBounds).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Validates that start Offset is valid number > 0", function () {
|
|
||||||
expect(timeConductorValidation.validateStartOffset(-1)).toBe(false);
|
|
||||||
expect(timeConductorValidation.validateStartOffset("abc")).toBe(false);
|
|
||||||
expect(timeConductorValidation.validateStartOffset("1")).toBe(true);
|
|
||||||
expect(timeConductorValidation.validateStartOffset(1)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Validates that end Offset is valid number >= 0", function () {
|
|
||||||
expect(timeConductorValidation.validateEndOffset(-1)).toBe(false);
|
|
||||||
expect(timeConductorValidation.validateEndOffset("abc")).toBe(false);
|
|
||||||
expect(timeConductorValidation.validateEndOffset("1")).toBe(true);
|
|
||||||
expect(timeConductorValidation.validateEndOffset(0)).toBe(true);
|
|
||||||
expect(timeConductorValidation.validateEndOffset(1)).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,97 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
[
|
|
||||||
'EventEmitter'
|
|
||||||
],
|
|
||||||
function (EventEmitter) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The TimeConductorViewService acts as an event bus between different
|
|
||||||
* elements of the Time Conductor UI. Zooming and panning occur via this
|
|
||||||
* service, as they are specific behaviour of the UI, and not general
|
|
||||||
* functions of the time API.
|
|
||||||
*
|
|
||||||
* Synchronization of conductor state between the Time API and the URL
|
|
||||||
* also occurs from the conductor view service, whose lifecycle persists
|
|
||||||
* between view changes.
|
|
||||||
*
|
|
||||||
* @memberof platform.features.conductor
|
|
||||||
* @param conductor
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function TimeConductorViewService(openmct) {
|
|
||||||
|
|
||||||
EventEmitter.call(this);
|
|
||||||
|
|
||||||
this.timeAPI = openmct.time;
|
|
||||||
}
|
|
||||||
|
|
||||||
TimeConductorViewService.prototype = Object.create(EventEmitter.prototype);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An event to indicate that zooming is taking place
|
|
||||||
* @event platform.features.conductor.TimeConductorViewService~zoom
|
|
||||||
* @property {ZoomLevel} zoom the new zoom level.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Zoom to given time span. Will fire a zoom event with new zoom
|
|
||||||
* bounds. Zoom bounds emitted in this way are considered ephemeral
|
|
||||||
* and should be overridden by any time conductor bounds events. Does
|
|
||||||
* not set bounds globally.
|
|
||||||
* @param {number} zoom A time duration in ms
|
|
||||||
* @fires platform.features.conductor.TimeConductorViewService~zoom
|
|
||||||
* @see module:openmct.TimeConductor#bounds
|
|
||||||
*/
|
|
||||||
TimeConductorViewService.prototype.zoom = function (timeSpan) {
|
|
||||||
var zoom = {};
|
|
||||||
|
|
||||||
// If a tick source is defined, then the concept of 'now' is
|
|
||||||
// important. Calculate zoom based on 'now'.
|
|
||||||
if (this.timeAPI.clock() !== undefined) {
|
|
||||||
zoom.offsets = {
|
|
||||||
start: -timeSpan,
|
|
||||||
end: this.timeAPI.clockOffsets().end
|
|
||||||
};
|
|
||||||
|
|
||||||
var currentVal = this.timeAPI.clock().currentValue();
|
|
||||||
|
|
||||||
zoom.bounds = {
|
|
||||||
start: currentVal + zoom.offsets.start,
|
|
||||||
end: currentVal + zoom.offsets.end
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
var bounds = this.timeAPI.bounds();
|
|
||||||
var center = bounds.start + ((bounds.end - bounds.start)) / 2;
|
|
||||||
bounds.start = center - timeSpan / 2;
|
|
||||||
bounds.end = center + timeSpan / 2;
|
|
||||||
zoom.bounds = bounds;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.emit("zoom", zoom);
|
|
||||||
return zoom;
|
|
||||||
};
|
|
||||||
|
|
||||||
return TimeConductorViewService;
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,109 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
[],
|
|
||||||
function () {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Controller for the Time of Interest element used in various views to display the TOI. Responsible for setting
|
|
||||||
* the text label for the current TOI, and for toggling the (un)pinned state which determines whether the TOI
|
|
||||||
* indicator is visible.
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function TimeOfInterestController($scope, openmct, formatService) {
|
|
||||||
this.timeAPI = openmct.time;
|
|
||||||
this.formatService = formatService;
|
|
||||||
this.format = undefined;
|
|
||||||
this.toiText = undefined;
|
|
||||||
this.$scope = $scope;
|
|
||||||
|
|
||||||
//Bind all class functions to 'this'
|
|
||||||
Object.keys(TimeOfInterestController.prototype).filter(function (key) {
|
|
||||||
return typeof TimeOfInterestController.prototype[key] === 'function';
|
|
||||||
}).forEach(function (key) {
|
|
||||||
this[key] = TimeOfInterestController.prototype[key].bind(this);
|
|
||||||
}.bind(this));
|
|
||||||
|
|
||||||
this.timeAPI.on('timeOfInterest', this.changeTimeOfInterest);
|
|
||||||
this.timeAPI.on('timeSystem', this.changeTimeSystem);
|
|
||||||
if (this.timeAPI.timeSystem() !== undefined) {
|
|
||||||
this.changeTimeSystem(this.timeAPI.timeSystem());
|
|
||||||
var toi = this.timeAPI.timeOfInterest();
|
|
||||||
if (toi) {
|
|
||||||
this.changeTimeOfInterest(toi);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.$on('$destroy', this.destroy);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the time of interest changes on the conductor. Will pin (display) the TOI indicator, and set the
|
|
||||||
* text using the default formatter of the currently active Time System.
|
|
||||||
* @private
|
|
||||||
* @param {integer} toi Current time of interest in ms
|
|
||||||
*/
|
|
||||||
TimeOfInterestController.prototype.changeTimeOfInterest = function (toi) {
|
|
||||||
if (toi !== undefined) {
|
|
||||||
this.$scope.pinned = true;
|
|
||||||
this.toiText = this.format.format(toi);
|
|
||||||
} else {
|
|
||||||
this.$scope.pinned = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When time system is changed, update the formatter used to
|
|
||||||
* display the current TOI label
|
|
||||||
*/
|
|
||||||
TimeOfInterestController.prototype.changeTimeSystem = function (timeSystem) {
|
|
||||||
this.format = this.formatService.getFormat(timeSystem.timeFormat);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
TimeOfInterestController.prototype.destroy = function () {
|
|
||||||
this.timeAPI.off('timeOfInterest', this.changeTimeOfInterest);
|
|
||||||
this.timeAPI.off('timeSystem', this.changeTimeSystem);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Will unpin (hide) the TOI indicator. Has the effect of setting the time of interest to `undefined` on the
|
|
||||||
* Time Conductor
|
|
||||||
*/
|
|
||||||
TimeOfInterestController.prototype.dismiss = function () {
|
|
||||||
this.timeAPI.timeOfInterest(undefined);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends out a time of interest event with the effect of resetting
|
|
||||||
* the TOI displayed in views.
|
|
||||||
*/
|
|
||||||
TimeOfInterestController.prototype.resync = function () {
|
|
||||||
this.timeAPI.timeOfInterest(this.timeAPI.timeOfInterest());
|
|
||||||
};
|
|
||||||
|
|
||||||
return TimeOfInterestController;
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,64 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define([
|
|
||||||
'./src/controllers/ListViewController',
|
|
||||||
'./src/directives/MCTGesture',
|
|
||||||
'./res/templates/listview.html',
|
|
||||||
'legacyRegistry'
|
|
||||||
], function (
|
|
||||||
ListViewController,
|
|
||||||
MCTGesture,
|
|
||||||
listViewTemplate,
|
|
||||||
legacyRegistry
|
|
||||||
) {
|
|
||||||
legacyRegistry.register("platform/features/listview", {
|
|
||||||
"name": "List View Plugin",
|
|
||||||
"description": "Allows folder contents to be shown in list format",
|
|
||||||
"extensions":
|
|
||||||
{
|
|
||||||
"views": [
|
|
||||||
{
|
|
||||||
"key": "list",
|
|
||||||
"type": "folder",
|
|
||||||
"name": "List",
|
|
||||||
"cssClass": "icon-list-view",
|
|
||||||
"template": listViewTemplate
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"controllers": [
|
|
||||||
{
|
|
||||||
"key": "ListViewController",
|
|
||||||
"implementation": ListViewController,
|
|
||||||
"depends": ["$scope", "formatService"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"directives": [
|
|
||||||
{
|
|
||||||
"key": "mctGesture",
|
|
||||||
"implementation" : MCTGesture,
|
|
||||||
"depends": ["gestureService"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,88 +0,0 @@
|
|||||||
<!--
|
|
||||||
Open MCT, Copyright (c) 2014-2018, United States Government
|
|
||||||
as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
Administration. All rights reserved.
|
|
||||||
|
|
||||||
Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
"License"); you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
License for the specific language governing permissions and limitations
|
|
||||||
under the License.
|
|
||||||
|
|
||||||
Open MCT includes source code licensed under additional open source
|
|
||||||
licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
this source code distribution or the Licensing information page available
|
|
||||||
at runtime from the About dialog for additional information.
|
|
||||||
-->
|
|
||||||
<div ng-controller="ListViewController">
|
|
||||||
<table class="list-view">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th class="sortable"
|
|
||||||
ng-click="reverseSort = (orderByField === 'title')? !reverseSort:false; orderByField='title';"
|
|
||||||
ng-class="{
|
|
||||||
sort: orderByField == 'title',
|
|
||||||
asc: !reverseSort,
|
|
||||||
desc: reverseSort
|
|
||||||
}">
|
|
||||||
Name
|
|
||||||
</th>
|
|
||||||
|
|
||||||
|
|
||||||
<th class="sortable"
|
|
||||||
ng-click="reverseSort = (orderByField === 'type')? !reverseSort:false; orderByField='type';"
|
|
||||||
ng-class="{
|
|
||||||
sort: orderByField == 'type',
|
|
||||||
asc: !reverseSort,
|
|
||||||
desc: reverseSort
|
|
||||||
}">
|
|
||||||
Type
|
|
||||||
</th>
|
|
||||||
|
|
||||||
<th class="sortable"
|
|
||||||
ng-click="reverseSort = (orderByField === 'persisted')? !reverseSort:true; orderByField='persisted';"
|
|
||||||
ng-class="{
|
|
||||||
sort: orderByField == 'persisted',
|
|
||||||
asc: !reverseSort,
|
|
||||||
desc: reverseSort
|
|
||||||
}">
|
|
||||||
Created Date
|
|
||||||
</th>
|
|
||||||
|
|
||||||
<th class="sortable"
|
|
||||||
ng-click="reverseSort = (orderByField === 'modified')? !reverseSort:true; orderByField='modified';"
|
|
||||||
ng-class="{
|
|
||||||
sort: orderByField == 'modified',
|
|
||||||
asc: !reverseSort,
|
|
||||||
desc: reverseSort
|
|
||||||
}">
|
|
||||||
Update Date
|
|
||||||
</th>
|
|
||||||
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr ng-repeat="child in children|orderBy:orderByField:reverseSort"
|
|
||||||
mct-gesture="['menu','info']"
|
|
||||||
mct-object="child.asDomainObject"
|
|
||||||
ng-click="child.action.perform('navigate')">
|
|
||||||
<td>
|
|
||||||
<div class="l-flex-row">
|
|
||||||
<span class="flex-elem t-item-icon" ng-class="{ 'l-icon-link': child.location.isLink()}">
|
|
||||||
<span class="t-item-icon-glyph {{child.icon}}"></span>
|
|
||||||
</span>
|
|
||||||
<span class="t-title-label flex-elem grows">{{child.title}}</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>{{child.type}}</td>
|
|
||||||
<td>{{child.persisted}}</td>
|
|
||||||
<td>{{child.modified}}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
@ -1,70 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(function () {
|
|
||||||
function ListViewController($scope, formatService) {
|
|
||||||
this.$scope = $scope;
|
|
||||||
$scope.orderByField = 'title';
|
|
||||||
$scope.reverseSort = false;
|
|
||||||
|
|
||||||
this.updateView();
|
|
||||||
var unlisten = $scope.domainObject.getCapability('mutation')
|
|
||||||
.listen(this.updateView.bind(this));
|
|
||||||
|
|
||||||
this.utc = formatService.getFormat('utc');
|
|
||||||
|
|
||||||
//Trigger digestive cycle with $apply to update list view
|
|
||||||
setTimeout(function () {
|
|
||||||
$scope.$apply();
|
|
||||||
});
|
|
||||||
|
|
||||||
$scope.$on('$destroy', function () {
|
|
||||||
unlisten();
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
ListViewController.prototype.updateView = function () {
|
|
||||||
this.$scope.domainObject.useCapability('composition')
|
|
||||||
.then(function (children) {
|
|
||||||
var formattedChildren = this.formatChildren(children);
|
|
||||||
this.$scope.children = formattedChildren;
|
|
||||||
this.$scope.data = {children: formattedChildren};
|
|
||||||
}.bind(this)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
ListViewController.prototype.formatChildren = function (children) {
|
|
||||||
return children.map(function (child) {
|
|
||||||
return {
|
|
||||||
icon: child.getCapability('type').getCssClass(),
|
|
||||||
title: child.getModel().name,
|
|
||||||
type: child.getCapability('type').getName(),
|
|
||||||
persisted: this.utc.format(child.getModel().persisted),
|
|
||||||
modified: this.utc.format(child.getModel().modified),
|
|
||||||
asDomainObject: child,
|
|
||||||
location: child.getCapability('location'),
|
|
||||||
action: child.getCapability('action')
|
|
||||||
};
|
|
||||||
}, this);
|
|
||||||
};
|
|
||||||
|
|
||||||
return ListViewController;
|
|
||||||
});
|
|
@ -1,157 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
["../../src/controllers/ListViewController"],
|
|
||||||
function (ListViewController) {
|
|
||||||
describe("The Controller for the ListView", function () {
|
|
||||||
var scope,
|
|
||||||
unlistenFunc,
|
|
||||||
domainObject,
|
|
||||||
childObject,
|
|
||||||
childModel,
|
|
||||||
typeCapability,
|
|
||||||
mutationCapability,
|
|
||||||
formatService,
|
|
||||||
compositionPromise,
|
|
||||||
controller;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
unlistenFunc = jasmine.createSpy("unlisten");
|
|
||||||
|
|
||||||
mutationCapability = jasmine.createSpyObj(
|
|
||||||
"mutationCapability",
|
|
||||||
["listen"]
|
|
||||||
);
|
|
||||||
mutationCapability.listen.and.returnValue(unlistenFunc);
|
|
||||||
|
|
||||||
formatService = jasmine.createSpyObj(
|
|
||||||
"formatService",
|
|
||||||
["getFormat"]
|
|
||||||
);
|
|
||||||
formatService.getFormat.and.returnValue(jasmine.createSpyObj(
|
|
||||||
'utc',
|
|
||||||
["format"]
|
|
||||||
));
|
|
||||||
formatService.getFormat().format.and.callFake(function (v) {
|
|
||||||
return "formatted " + v;
|
|
||||||
});
|
|
||||||
|
|
||||||
typeCapability = jasmine.createSpyObj(
|
|
||||||
"typeCapability",
|
|
||||||
["getCssClass", "getName"]
|
|
||||||
);
|
|
||||||
typeCapability.getCssClass.and.returnValue("icon-folder");
|
|
||||||
typeCapability.getName.and.returnValue("Folder");
|
|
||||||
|
|
||||||
|
|
||||||
childModel = jasmine.createSpyObj(
|
|
||||||
"childModel",
|
|
||||||
["persisted", "modified", "name"]
|
|
||||||
);
|
|
||||||
childModel.persisted = 1496867697303;
|
|
||||||
childModel.modified = 1496867697303;
|
|
||||||
childModel.name = "Battery Charge Status";
|
|
||||||
|
|
||||||
childObject = jasmine.createSpyObj(
|
|
||||||
"childObject",
|
|
||||||
["getModel", "getCapability"]
|
|
||||||
);
|
|
||||||
childObject.getModel.and.returnValue(
|
|
||||||
childModel
|
|
||||||
);
|
|
||||||
|
|
||||||
childObject.getCapability.and.callFake(function (arg) {
|
|
||||||
if (arg === 'location') {
|
|
||||||
return '';
|
|
||||||
} else if (arg === 'type') {
|
|
||||||
return typeCapability;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
childObject.location = '';
|
|
||||||
|
|
||||||
domainObject = jasmine.createSpyObj(
|
|
||||||
"domainObject",
|
|
||||||
["getCapability", "useCapability"]
|
|
||||||
);
|
|
||||||
compositionPromise = Promise.resolve([childObject]);
|
|
||||||
domainObject.useCapability.and.returnValue(compositionPromise);
|
|
||||||
domainObject.getCapability.and.returnValue(
|
|
||||||
mutationCapability
|
|
||||||
);
|
|
||||||
|
|
||||||
scope = jasmine.createSpyObj(
|
|
||||||
"$scope",
|
|
||||||
["$on", "$apply"]
|
|
||||||
);
|
|
||||||
scope.domainObject = domainObject;
|
|
||||||
|
|
||||||
controller = new ListViewController(scope, formatService);
|
|
||||||
|
|
||||||
return compositionPromise;
|
|
||||||
});
|
|
||||||
|
|
||||||
it("uses the UTC time format", function () {
|
|
||||||
expect(formatService.getFormat).toHaveBeenCalledWith('utc');
|
|
||||||
});
|
|
||||||
|
|
||||||
it("updates the view", function () {
|
|
||||||
var child = scope.children[0];
|
|
||||||
var testChild = {
|
|
||||||
icon: "icon-folder",
|
|
||||||
title: "Battery Charge Status",
|
|
||||||
type: "Folder",
|
|
||||||
persisted: formatService.getFormat('utc')
|
|
||||||
.format(childModel.persisted),
|
|
||||||
modified: formatService.getFormat('utc')
|
|
||||||
.format(childModel.modified),
|
|
||||||
asDomainObject: childObject,
|
|
||||||
location: '',
|
|
||||||
action: childObject.getCapability('action')
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(child).toEqual(testChild);
|
|
||||||
});
|
|
||||||
it("updates the scope when mutation occurs", function () {
|
|
||||||
var applyPromise = new Promise(function (resolve) {
|
|
||||||
scope.$apply.and.callFake(resolve);
|
|
||||||
});
|
|
||||||
|
|
||||||
domainObject.useCapability.and.returnValue(Promise.resolve([]));
|
|
||||||
expect(mutationCapability.listen).toHaveBeenCalledWith(jasmine.any(Function));
|
|
||||||
mutationCapability.listen.calls.mostRecent().args[0]();
|
|
||||||
|
|
||||||
return applyPromise.then(function () {
|
|
||||||
expect(scope.children.length).toEqual(0);
|
|
||||||
expect(scope.$apply).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it("releases listeners on $destroy", function () {
|
|
||||||
expect(scope.$on).toHaveBeenCalledWith('$destroy', jasmine.any(Function));
|
|
||||||
scope.$on.calls.mostRecent().args[1]();
|
|
||||||
expect(unlistenFunc).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,86 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
["../../src/directives/MCTGesture"],
|
|
||||||
function (MCTGesture) {
|
|
||||||
describe("The Gesture Listener for the ListView items", function () {
|
|
||||||
var mctGesture,
|
|
||||||
gestureService,
|
|
||||||
scope,
|
|
||||||
element,
|
|
||||||
attrs,
|
|
||||||
attachedGesture;
|
|
||||||
beforeEach(function () {
|
|
||||||
attachedGesture = jasmine.createSpyObj(
|
|
||||||
"attachedGesture",
|
|
||||||
['destroy']
|
|
||||||
);
|
|
||||||
gestureService = jasmine.createSpyObj(
|
|
||||||
"gestureService",
|
|
||||||
["attachGestures"]
|
|
||||||
);
|
|
||||||
gestureService.attachGestures.and.returnValue(
|
|
||||||
attachedGesture
|
|
||||||
);
|
|
||||||
mctGesture = MCTGesture(gestureService);
|
|
||||||
});
|
|
||||||
it("creates a directive Object", function () {
|
|
||||||
expect(mctGesture).toBeDefined();
|
|
||||||
});
|
|
||||||
it("has link function that attaches gesture to gestureService",
|
|
||||||
function () {
|
|
||||||
attrs = {
|
|
||||||
mctGesture: "menu,info"
|
|
||||||
};
|
|
||||||
element = jasmine.createSpy("element");
|
|
||||||
scope = jasmine.createSpyObj(
|
|
||||||
"$scope",
|
|
||||||
["$on", "$eval"]
|
|
||||||
);
|
|
||||||
scope.domainObject = "fake domainObject";
|
|
||||||
mctGesture.link(scope, element, attrs);
|
|
||||||
expect(gestureService.attachGestures).toHaveBeenCalled();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
it("release gesture service on $destroy", function () {
|
|
||||||
attrs = {
|
|
||||||
mctGesture: "menu,info"
|
|
||||||
};
|
|
||||||
element = jasmine.createSpy("element");
|
|
||||||
scope = jasmine.createSpyObj(
|
|
||||||
"$scope",
|
|
||||||
["$on", "$eval"]
|
|
||||||
);
|
|
||||||
scope.domainObject = "fake domainObject";
|
|
||||||
mctGesture.link(scope, element, attrs);
|
|
||||||
expect(scope.$on).toHaveBeenCalledWith(
|
|
||||||
'$destroy',
|
|
||||||
jasmine.any(Function)
|
|
||||||
);
|
|
||||||
scope.$on.calls.mostRecent().args[1]();
|
|
||||||
expect(attachedGesture.destroy).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,294 +0,0 @@
|
|||||||
define([
|
|
||||||
"legacyRegistry",
|
|
||||||
"./src/controllers/NotebookController",
|
|
||||||
"./src/controllers/NewEntryController",
|
|
||||||
"./src/controllers/SelectSnapshotController",
|
|
||||||
"./src/controllers/LayoutNotebookController",
|
|
||||||
"./src/directives/MCTSnapshot",
|
|
||||||
"./src/directives/EntryDnd",
|
|
||||||
"./src/actions/ViewSnapshot",
|
|
||||||
"./src/actions/AnnotateSnapshot",
|
|
||||||
"./src/actions/RemoveEmbed",
|
|
||||||
"./src/actions/RemoveSnapshot",
|
|
||||||
"./src/actions/NewEntryContextual",
|
|
||||||
"./src/capabilities/NotebookCapability",
|
|
||||||
"./src/policies/CompositionPolicy",
|
|
||||||
"./res/templates/notebook.html",
|
|
||||||
"./res/templates/entry.html",
|
|
||||||
"./res/templates/annotation.html",
|
|
||||||
"./res/templates/notifications.html",
|
|
||||||
"../layout/res/templates/frame.html",
|
|
||||||
"./res/templates/controls/embedControl.html",
|
|
||||||
"./res/templates/controls/snapSelect.html"
|
|
||||||
], function (
|
|
||||||
legacyRegistry,
|
|
||||||
NotebookController,
|
|
||||||
NewEntryController,
|
|
||||||
SelectSnapshotController,
|
|
||||||
LayoutNotebookController,
|
|
||||||
MCTSnapshot,
|
|
||||||
MCTEntryDnd,
|
|
||||||
ViewSnapshotAction,
|
|
||||||
AnnotateSnapshotAction,
|
|
||||||
RemoveEmbedAction,
|
|
||||||
RemoveSnapshotAction,
|
|
||||||
newEntryAction,
|
|
||||||
NotebookCapability,
|
|
||||||
CompositionPolicy,
|
|
||||||
notebookTemplate,
|
|
||||||
entryTemplate,
|
|
||||||
annotationTemplate,
|
|
||||||
notificationsTemplate,
|
|
||||||
frameTemplate,
|
|
||||||
embedControlTemplate,
|
|
||||||
snapSelectTemplate
|
|
||||||
) {
|
|
||||||
legacyRegistry.register("platform/features/notebook", {
|
|
||||||
"name": "Notebook Plugin",
|
|
||||||
"description": "Create and save timestamped notes with embedded object snapshots.",
|
|
||||||
"extensions":
|
|
||||||
{
|
|
||||||
"types": [
|
|
||||||
{
|
|
||||||
"key": "notebook",
|
|
||||||
"name": "Notebook",
|
|
||||||
"cssClass": "icon-notebook",
|
|
||||||
"description": "Create and save timestamped notes with embedded object snapshots.",
|
|
||||||
"features": ["creation"],
|
|
||||||
"model": {
|
|
||||||
"entries": [],
|
|
||||||
"composition": [],
|
|
||||||
"entryTypes": [],
|
|
||||||
"defaultSort": "-createdOn"
|
|
||||||
},
|
|
||||||
"properties": [
|
|
||||||
{
|
|
||||||
"key": "defaultSort",
|
|
||||||
"name": "Default Sort",
|
|
||||||
"control": "select",
|
|
||||||
"options": [
|
|
||||||
{
|
|
||||||
"name": "Newest First",
|
|
||||||
"value": "-createdOn"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Oldest First",
|
|
||||||
"value": "createdOn"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"cssClass": "l-inline"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"views": [
|
|
||||||
{
|
|
||||||
"key": "notebook.view",
|
|
||||||
"type": "notebook",
|
|
||||||
"cssClass": "icon-notebook",
|
|
||||||
"name": "notebook",
|
|
||||||
"template": notebookTemplate,
|
|
||||||
"editable": false,
|
|
||||||
"uses": [
|
|
||||||
"composition",
|
|
||||||
"action"
|
|
||||||
],
|
|
||||||
"gestures": [
|
|
||||||
"drop"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"controllers": [
|
|
||||||
{
|
|
||||||
"key": "NotebookController",
|
|
||||||
"implementation": NotebookController,
|
|
||||||
"depends": [
|
|
||||||
"$scope",
|
|
||||||
"dialogService",
|
|
||||||
"popupService",
|
|
||||||
"agentService",
|
|
||||||
"objectService",
|
|
||||||
"navigationService",
|
|
||||||
"now",
|
|
||||||
"actionService",
|
|
||||||
"$timeout",
|
|
||||||
"$element",
|
|
||||||
"$sce"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "NewEntryController",
|
|
||||||
"implementation": NewEntryController,
|
|
||||||
"depends": ["$scope",
|
|
||||||
"$rootScope"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "selectSnapshotController",
|
|
||||||
"implementation": SelectSnapshotController,
|
|
||||||
"depends": ["$scope",
|
|
||||||
"$rootScope"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "LayoutNotebookController",
|
|
||||||
"implementation": LayoutNotebookController,
|
|
||||||
"depends": ["$scope"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"representations": [
|
|
||||||
{
|
|
||||||
"key": "draggedEntry",
|
|
||||||
"template": entryTemplate
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "frameLayoutNotebook",
|
|
||||||
"template": frameTemplate
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"templates": [
|
|
||||||
{
|
|
||||||
"key": "annotate-snapshot",
|
|
||||||
"template": annotationTemplate
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "notificationTemplate",
|
|
||||||
"template": notificationsTemplate
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"directives": [
|
|
||||||
{
|
|
||||||
"key": "mctSnapshot",
|
|
||||||
"implementation": MCTSnapshot,
|
|
||||||
"depends": [
|
|
||||||
"$rootScope",
|
|
||||||
"$document",
|
|
||||||
"exportImageService",
|
|
||||||
"dialogService",
|
|
||||||
"notificationService"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "mctEntryDnd",
|
|
||||||
"implementation": MCTEntryDnd,
|
|
||||||
"depends": [
|
|
||||||
"$rootScope",
|
|
||||||
"$compile",
|
|
||||||
"dndService",
|
|
||||||
"typeService",
|
|
||||||
"notificationService"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"actions": [
|
|
||||||
{
|
|
||||||
"key": "view-snapshot",
|
|
||||||
"implementation": ViewSnapshotAction,
|
|
||||||
"name": "View Snapshot",
|
|
||||||
"description": "View the large image in a modal",
|
|
||||||
"category": "embed",
|
|
||||||
"depends": [
|
|
||||||
"$compile"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "annotate-snapshot",
|
|
||||||
"implementation": AnnotateSnapshotAction,
|
|
||||||
"name": "Annotate Snapshot",
|
|
||||||
"cssClass": "icon-pencil labeled",
|
|
||||||
"description": "Annotate embed's snapshot",
|
|
||||||
"category": "embed",
|
|
||||||
"depends": [
|
|
||||||
"dialogService",
|
|
||||||
"dndService",
|
|
||||||
"$rootScope"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"key": "remove-embed",
|
|
||||||
"implementation": RemoveEmbedAction,
|
|
||||||
"name": "Remove...",
|
|
||||||
"cssClass": "icon-trash labeled",
|
|
||||||
"description": "Remove this embed",
|
|
||||||
"category": [
|
|
||||||
"embed",
|
|
||||||
"embed-no-snap"
|
|
||||||
],
|
|
||||||
"depends": [
|
|
||||||
"dialogService"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "remove-snapshot",
|
|
||||||
"implementation": RemoveSnapshotAction,
|
|
||||||
"name": "Remove Snapshot",
|
|
||||||
"cssClass": "icon-trash labeled",
|
|
||||||
"description": "Remove Snapshot of the embed",
|
|
||||||
"category": "embed",
|
|
||||||
"depends": [
|
|
||||||
"dialogService"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "notebook-new-entry",
|
|
||||||
"implementation": newEntryAction,
|
|
||||||
"name": "New Notebook Entry",
|
|
||||||
"cssClass": "icon-notebook labeled",
|
|
||||||
"description": "Add a new Notebook entry",
|
|
||||||
"category": [
|
|
||||||
"view-control"
|
|
||||||
],
|
|
||||||
"depends": [
|
|
||||||
"$compile",
|
|
||||||
"$rootScope",
|
|
||||||
"dialogService",
|
|
||||||
"notificationService",
|
|
||||||
"linkService"
|
|
||||||
],
|
|
||||||
"priority": "preferred"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"licenses": [
|
|
||||||
{
|
|
||||||
"name": "painterro",
|
|
||||||
"version": "0.2.65",
|
|
||||||
"author": "Ivan Borshchov",
|
|
||||||
"description": "Painterro is JavaScript paint widget which allows editing images directly in a browser.",
|
|
||||||
"website": "https://github.com/ivictbor/painterro",
|
|
||||||
"copyright": "Copyright 2017 Ivan Borshchov",
|
|
||||||
"license": "MIT",
|
|
||||||
"link": "https://github.com/ivictbor/painterro/blob/master/LICENSE"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"capabilities": [
|
|
||||||
{
|
|
||||||
"key": "notebook",
|
|
||||||
"name": "Notebook Capability",
|
|
||||||
"description": "Provides a capability for looking for a notebook domain object",
|
|
||||||
"implementation": NotebookCapability,
|
|
||||||
"depends": [
|
|
||||||
"typeService"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"policies": [
|
|
||||||
{
|
|
||||||
"category": "composition",
|
|
||||||
"implementation": CompositionPolicy,
|
|
||||||
"message": "Objects of this type cannot contain objects of that type."
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"controls": [
|
|
||||||
{
|
|
||||||
"key": "embed-control",
|
|
||||||
"template": embedControlTemplate
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "snapshot-select",
|
|
||||||
"template": snapSelectTemplate
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,118 +0,0 @@
|
|||||||
<div ng-controller="NotebookController as controller" class="mct-notebook w-notebook l-flex-col">
|
|
||||||
<div class="l-notebook-head holder l-flex-row flex-elem">
|
|
||||||
<div class="c-search flex-elem holder grows">
|
|
||||||
<input class="c-search__search-input"
|
|
||||||
type="text" tabindex="10000"
|
|
||||||
ng-model="entrySearch"
|
|
||||||
ng-keyup="controller.search()"/>
|
|
||||||
<a class="c-search__clear-input clear-icon icon-x-in-circle"
|
|
||||||
ng-class="{show: !(entrySearch === '' || entrySearch === undefined)}"
|
|
||||||
ng-click="entrySearch = ''; controller.search()"></a>
|
|
||||||
</div>
|
|
||||||
<div class="notebook-view-controls l-flex-row flex-elem holder">
|
|
||||||
<div class="select notebook-view-controls__filter-time">
|
|
||||||
<select ng-model="showTime">
|
|
||||||
<option value="0" selected="selected">Show all</option>
|
|
||||||
<option value="1">Last hour</option>
|
|
||||||
<option value="8">Last 8 hours</option>
|
|
||||||
<option value="24">Last 24 hours</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="select notebook-view-controls__sort">
|
|
||||||
<select ng-model="sortEntries">
|
|
||||||
<option value="-createdOn" selected="selected">Newest first</option>
|
|
||||||
<option value="createdOn">Oldest first</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- drag area -->
|
|
||||||
<div class="holder flex-elem l-flex-row icon-plus labeled l-notebook-drag-area" ng-click="newEntry($event)"
|
|
||||||
id="newEntry" mct-entry-dnd>
|
|
||||||
<span class="label">To start a new entry, click here or drag and drop any object</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- entries -->
|
|
||||||
<div class="holder flex-elem grows w-notebook-entries t-entries-list" ng-mouseover="handleActive()">
|
|
||||||
<ul>
|
|
||||||
<li class="l-flex-row has-local-controls l-notebook-entry s-notebook-entry"
|
|
||||||
id="{{'entry_'+ entry.id}}"
|
|
||||||
ng-if="hoursFilter(showTime,entry.createdOn)"
|
|
||||||
ng-repeat="entry in model.entries | filter:entrySearch | orderBy: sortEntries track by $index"
|
|
||||||
ng-init="$last && finished(model.entries)"
|
|
||||||
mct-entry-dnd>
|
|
||||||
<div class="holder flex-elem l-flex-row grows w-notebook-entry-time-and-content">
|
|
||||||
<div class="holder flex-elem s-notebook-entry-time">
|
|
||||||
<span>{{entry.createdOn | date:'yyyy-MM-dd'}}</span>
|
|
||||||
<span>{{entry.createdOn | date:'HH:mm:ss'}}</span>
|
|
||||||
</div>
|
|
||||||
<div class="holder flex-elem l-flex-col grows l-notebook-entry-content">
|
|
||||||
<div contenteditable="true"
|
|
||||||
ng-blur="textBlur($event, entry.id)"
|
|
||||||
ng-focus="textFocus($event, entry.id)"
|
|
||||||
ng-model="entry.text"
|
|
||||||
placeholder="Enter text here"
|
|
||||||
class="flex-elem s-input-inline t-notebook-entry-input s-notebook-entry-text"
|
|
||||||
ng-bind="entry.text">
|
|
||||||
</div>
|
|
||||||
<!-- embeds -->
|
|
||||||
<div class="flex-elem entry-embeds l-flex-row">
|
|
||||||
<div class="l-flex-row l-entry-embed {{embed.cssClass}}"
|
|
||||||
ng-repeat="embed in entry.embeds track by $index"
|
|
||||||
ng-class="{ 'has-snapshot' : embed.snapshot }"
|
|
||||||
id="{{embed.id}}">
|
|
||||||
<div class="snap-thumb"
|
|
||||||
ng-if="embed.snapshot"
|
|
||||||
ng-click="viewSnapshot($event,embed.snapshot.src,embed.id,entry.createdOn,this,embed)">
|
|
||||||
<img ng-src="{{embed.snapshot.src}}" src="//:0" alt="{{embed.id}}">
|
|
||||||
</div>
|
|
||||||
<div class="embed-info l-flex-col">
|
|
||||||
<div class="embed-title object-header">
|
|
||||||
<a ng-click='navigate($event,embed.type)'>{{embed.name}}</a>
|
|
||||||
<a class='context-available' ng-click='openMenu($event,embed.type)'></a>
|
|
||||||
</div>
|
|
||||||
<div class="hide-menu" ng-show="false">
|
|
||||||
<div class="menu-element context-menu-wrapper mobile-disable-select">
|
|
||||||
<div class="menu context-menu">
|
|
||||||
<ul>
|
|
||||||
<li ng-repeat="menu in menuEmbed"
|
|
||||||
ng-click="menu.perform($event,embed.snapshot.src,embed.id,entry.createdOn,this,embed)"
|
|
||||||
title="{{menu.getMetadata().description}}"
|
|
||||||
class="{{menu.getMetadata().cssClass}}"
|
|
||||||
ng-if="embed.snapshot">
|
|
||||||
{{menu.getMetadata().name}}
|
|
||||||
</li>
|
|
||||||
<li ng-repeat="menu in menuEmbedNoSnap"
|
|
||||||
ng-click="menu.perform($event,embed.snapshot.src,embed.id,entry.createdOn,this)"
|
|
||||||
title="{{menu.getMetadata().description}}"
|
|
||||||
class="{{menu.getMetadata().cssClass}}"
|
|
||||||
ng-if="!embed.snapshot">
|
|
||||||
{{menu.getMetadata().name}}
|
|
||||||
</li>
|
|
||||||
<li ng-repeat="menu in embedActions"
|
|
||||||
ng-click="menu.perform()"
|
|
||||||
title="{{menu.name}}"
|
|
||||||
class="{{menu.cssClass}}">
|
|
||||||
{{menu.name}}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="embed-date"
|
|
||||||
ng-if="embed.snapshot">{{embed.id| date:'yyyy-MM-dd HH:mm:ss'}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- delete entry -->
|
|
||||||
<div class="holder flex-elem local-control local-controls-hidden notebook-entry-delete">
|
|
||||||
<a class="s-icon-button icon-trash" id={{entry.id}} title="Delete Entry" ng-click="deleteEntry($event)"></a>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -1,8 +0,0 @@
|
|||||||
<span class="status block">
|
|
||||||
<!-- DO NOT ADD SPACES BETWEEN THE SPANS - IT ADDS WHITE SPACE!! -->
|
|
||||||
<span class="status-indicator icon-bell"></span>
|
|
||||||
<span class="label">
|
|
||||||
Notifications
|
|
||||||
</span>
|
|
||||||
<span class="count"></span>
|
|
||||||
</span>
|
|
@ -1,34 +0,0 @@
|
|||||||
<div class="t-snapshot abs l-view-header">
|
|
||||||
<div class="abs object-browse-bar l-flex-row">
|
|
||||||
<div class="left flex-elem l-flex-row grows">
|
|
||||||
<div class="object-header flex-elem l-flex-row grows">
|
|
||||||
<div class="type-icon flex-elem embed-icon holder" ng-class="cssClass"></div>
|
|
||||||
<div class="title-label flex-elem holder flex-can-shrink">{{entryName}}</div>
|
|
||||||
<a class="context-available flex-elem holder" ng-click="openMenu($event,embedType)"></a>
|
|
||||||
<div class="hide-menu" ng-show="false">
|
|
||||||
<div class="menu-element menu-view context-menu-wrapper mobile-disable-select">
|
|
||||||
<div class="menu context-menu">
|
|
||||||
<ul>
|
|
||||||
<li ng-repeat="menu in embedActions"
|
|
||||||
ng-click="menuPerform(menu)"
|
|
||||||
title="{{menu.name}}"
|
|
||||||
class="{{menu.cssClass}}">
|
|
||||||
{{menu.name}}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="btn-bar right l-flex-row flex-elem flex-justify-end flex-fixed">
|
|
||||||
<div class="flex-elem holder flex-can-shrink s-snapshot-datetime">
|
|
||||||
SNAPSHOT {{snapDate | date:'yyyy-MM-dd HH:mm:ss'}}
|
|
||||||
</div>
|
|
||||||
<a class="s-button icon-pencil" title="Annotate">
|
|
||||||
<span class="title-label">Annotate</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -1,72 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2017, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
[],
|
|
||||||
function () {
|
|
||||||
|
|
||||||
function RemoveEmbed(dialogService,context) {
|
|
||||||
context = context || {};
|
|
||||||
|
|
||||||
this.domainObject = context.selectedObject || context.domainObject;
|
|
||||||
this.dialogService = dialogService;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
RemoveEmbed.prototype.perform = function ($event,snapshot,embedId,entryId) {
|
|
||||||
var domainObject = this.domainObject;
|
|
||||||
var errorDialog = this.dialogService.showBlockingMessage({
|
|
||||||
severity: "error",
|
|
||||||
title: "This action will permanently delete this Embed. Do you want to continue?",
|
|
||||||
minimized: true, // want the notification to be minimized initially (don't show banner)
|
|
||||||
options: [{
|
|
||||||
label: "OK",
|
|
||||||
callback: function () {
|
|
||||||
errorDialog.dismiss();
|
|
||||||
remove();
|
|
||||||
}
|
|
||||||
},{
|
|
||||||
label: "Cancel",
|
|
||||||
callback: function () {
|
|
||||||
errorDialog.dismiss();
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
});
|
|
||||||
|
|
||||||
function remove() {
|
|
||||||
domainObject.useCapability('mutation', function (model) {
|
|
||||||
var elementPos = model.entries.map(function (x) {
|
|
||||||
return x.createdOn;
|
|
||||||
}).indexOf(entryId);
|
|
||||||
var entryEmbeds = model.entries[elementPos].embeds;
|
|
||||||
var embedPos = entryEmbeds.map(function (x) {
|
|
||||||
return x.id;
|
|
||||||
}).indexOf(embedId);
|
|
||||||
model.entries[elementPos].embeds.splice(embedPos, 1);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
return RemoveEmbed;
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,74 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2017, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
[],
|
|
||||||
function () {
|
|
||||||
|
|
||||||
function RemoveSnapshot(dialogService, context) {
|
|
||||||
context = context || {};
|
|
||||||
|
|
||||||
this.domainObject = context.selectedObject || context.domainObject;
|
|
||||||
this.dialogService = dialogService;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
RemoveSnapshot.prototype.perform = function ($event, snapshot, embedId, entryId) {
|
|
||||||
|
|
||||||
var domainObject = this.domainObject;
|
|
||||||
var errorDialog = this.dialogService.showBlockingMessage({
|
|
||||||
severity: "error",
|
|
||||||
title: "This action will permanently delete this Snapshot. Do you want to continue?",
|
|
||||||
minimized: true, // want the notification to be minimized initially (don't show banner)
|
|
||||||
options: [{
|
|
||||||
label: "OK",
|
|
||||||
callback: function () {
|
|
||||||
errorDialog.dismiss();
|
|
||||||
remove();
|
|
||||||
}
|
|
||||||
},{
|
|
||||||
label: "Cancel",
|
|
||||||
callback: function () {
|
|
||||||
errorDialog.dismiss();
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
});
|
|
||||||
|
|
||||||
function remove() {
|
|
||||||
domainObject.useCapability('mutation', function (model) {
|
|
||||||
var elementPos = model.entries.map(function (x) {
|
|
||||||
return x.createdOn;
|
|
||||||
}).indexOf(entryId);
|
|
||||||
var entryEmbeds = model.entries[elementPos].embeds;
|
|
||||||
var embedPos = entryEmbeds.map(function (x) {
|
|
||||||
return x.id;
|
|
||||||
}).indexOf(embedId);
|
|
||||||
model.entries[elementPos].embeds[embedPos].snapshot = "";
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
return RemoveSnapshot;
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,132 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2017, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module defining ViewSnapshot
|
|
||||||
*/
|
|
||||||
|
|
||||||
var OVERLAY_TEMPLATE = '' +
|
|
||||||
' <div class="abs blocker"></div>' +
|
|
||||||
' <div class="abs outer-holder">' +
|
|
||||||
' <a class="close icon-x-in-circle"></a>' +
|
|
||||||
' <div class="abs inner-holder l-flex-col">' +
|
|
||||||
' <div class="t-contents flex-elem holder grows"></div>' +
|
|
||||||
' <div class="bottom-bar flex-elem holder">' +
|
|
||||||
' <a class="t-done s-button major">Done</a>' +
|
|
||||||
' </div>' +
|
|
||||||
' </div>' +
|
|
||||||
' </div>';
|
|
||||||
|
|
||||||
define([
|
|
||||||
'zepto',
|
|
||||||
"../../res/templates/snapshotHeader.html"
|
|
||||||
],
|
|
||||||
function ($, headerTemplate) {
|
|
||||||
|
|
||||||
var toggleOverlay,
|
|
||||||
overlay,
|
|
||||||
closeButton,
|
|
||||||
doneButton,
|
|
||||||
blocker,
|
|
||||||
overlayContainer,
|
|
||||||
img,
|
|
||||||
annotateButton,
|
|
||||||
annotateImg;
|
|
||||||
|
|
||||||
function ViewSnapshot($compile) {
|
|
||||||
this.$compile = $compile;
|
|
||||||
}
|
|
||||||
|
|
||||||
function openOverlay(url, header) {
|
|
||||||
overlay = document.createElement('div');
|
|
||||||
$(overlay).addClass('abs overlay l-large-view');
|
|
||||||
overlay.innerHTML = OVERLAY_TEMPLATE;
|
|
||||||
overlayContainer = overlay.querySelector('.t-contents');
|
|
||||||
closeButton = overlay.querySelector('a.close');
|
|
||||||
closeButton.addEventListener('click', toggleOverlay);
|
|
||||||
doneButton = overlay.querySelector('a.t-done');
|
|
||||||
doneButton.addEventListener('click', toggleOverlay);
|
|
||||||
blocker = overlay.querySelector('.abs.blocker');
|
|
||||||
blocker.addEventListener('click', toggleOverlay);
|
|
||||||
annotateButton = header.querySelector('a.icon-pencil');
|
|
||||||
annotateButton.addEventListener('click', annotateImg);
|
|
||||||
document.body.appendChild(overlay);
|
|
||||||
img = document.createElement('div');
|
|
||||||
$(img).addClass('abs object-holder t-image-holder s-image-holder');
|
|
||||||
img.innerHTML = '<div class="image-main s-image-main" style="background-image: url(' + url + ');"></div>';
|
|
||||||
overlayContainer.appendChild(header);
|
|
||||||
overlayContainer.appendChild(img);
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeOverlay() {
|
|
||||||
overlayContainer.removeChild(img);
|
|
||||||
document.body.removeChild(overlay);
|
|
||||||
closeButton.removeEventListener('click', toggleOverlay);
|
|
||||||
closeButton = undefined;
|
|
||||||
doneButton.removeEventListener('click', toggleOverlay);
|
|
||||||
doneButton = undefined;
|
|
||||||
blocker.removeEventListener('click', toggleOverlay);
|
|
||||||
blocker = undefined;
|
|
||||||
overlayContainer = undefined;
|
|
||||||
overlay = undefined;
|
|
||||||
img = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
ViewSnapshot.prototype.perform = function ($event, snapshot, embedId, entryId, $scope, embed) {
|
|
||||||
var isOpen = false;
|
|
||||||
|
|
||||||
// onclick for menu items in overlay header context menu
|
|
||||||
$scope.menuPerform = function (menu) {
|
|
||||||
menu.perform();
|
|
||||||
closeOverlay();
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create the overlay element and add it to the document's body
|
|
||||||
$scope.cssClass = embed.cssClass;
|
|
||||||
$scope.embedType = embed.type;
|
|
||||||
$scope.entryName = embed.name;
|
|
||||||
$scope.snapDate = +embedId;
|
|
||||||
var element = this.$compile(headerTemplate)($scope);
|
|
||||||
|
|
||||||
var annotateAction = $scope.action.getActions({category: 'embed'})[1];
|
|
||||||
|
|
||||||
toggleOverlay = function () {
|
|
||||||
if (!isOpen) {
|
|
||||||
openOverlay(snapshot, element[0]);
|
|
||||||
isOpen = true;
|
|
||||||
} else {
|
|
||||||
closeOverlay();
|
|
||||||
isOpen = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
annotateImg = function () {
|
|
||||||
closeOverlay();
|
|
||||||
annotateAction.perform($event, snapshot, embedId, entryId, $scope);
|
|
||||||
};
|
|
||||||
|
|
||||||
toggleOverlay();
|
|
||||||
};
|
|
||||||
|
|
||||||
return ViewSnapshot;
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,50 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2017, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
|
|
||||||
define(
|
|
||||||
function () {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The notebook capability allows a domain object to know whether the
|
|
||||||
* notebook plugin is present or not.
|
|
||||||
*
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function NotebookCapability(typeService, domainObject) {
|
|
||||||
this.domainObject = domainObject;
|
|
||||||
this.typeService = typeService;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if there is a notebook domain Object.
|
|
||||||
*
|
|
||||||
* @returns {Boolean}
|
|
||||||
*/
|
|
||||||
NotebookCapability.prototype.isNotebook = function () {
|
|
||||||
return !!this.typeService.getType('notebook');
|
|
||||||
};
|
|
||||||
|
|
||||||
return NotebookCapability;
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,367 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2017, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
/*-- main controller file, here is the core functionality of the notebook plugin --*/
|
|
||||||
|
|
||||||
define(
|
|
||||||
['zepto'],
|
|
||||||
function ($) {
|
|
||||||
|
|
||||||
|
|
||||||
function NotebookController(
|
|
||||||
$scope,
|
|
||||||
dialogService,
|
|
||||||
popupService,
|
|
||||||
agentService,
|
|
||||||
objectService,
|
|
||||||
navigationService,
|
|
||||||
now,
|
|
||||||
actionService,
|
|
||||||
$timeout,
|
|
||||||
$element,
|
|
||||||
$sce
|
|
||||||
) {
|
|
||||||
|
|
||||||
$scope.entriesEl = $(document.body).find('.t-entries-list');
|
|
||||||
$scope.sortEntries = $scope.domainObject.getModel().defaultSort;
|
|
||||||
$scope.showTime = "0";
|
|
||||||
$scope.editEntry = false;
|
|
||||||
$scope.entrySearch = '';
|
|
||||||
$scope.entryTypes = [];
|
|
||||||
$scope.embedActions = [];
|
|
||||||
$scope.currentEntryValue = '';
|
|
||||||
|
|
||||||
var SECONDS_IN_AN_HOUR = 60 * 60 * 1000;
|
|
||||||
|
|
||||||
this.scope = $scope;
|
|
||||||
|
|
||||||
$scope.hoursFilter = function (hours,entryTime) {
|
|
||||||
if (+hours) {
|
|
||||||
return entryTime > (Date.now() - SECONDS_IN_AN_HOUR * (+hours));
|
|
||||||
}else {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.scrollToTop = function () {
|
|
||||||
var entriesContainer = $scope.entriesEl.parent();
|
|
||||||
entriesContainer[0].scrollTop = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.findEntryEl = function (entryId) {
|
|
||||||
var element = $($scope.entriesEl).find('#entry_' + entryId);
|
|
||||||
|
|
||||||
if (element[0]) {
|
|
||||||
return element.find("[contenteditable='true']");
|
|
||||||
} else {
|
|
||||||
var entries = $scope.entriesEl.children().children(),
|
|
||||||
lastOrFirst = $scope.sortEntries === "-createdOn" ? 0 : (entries.length - 1);
|
|
||||||
|
|
||||||
return $(entries[lastOrFirst]).find("[contenteditable='true']");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.findEntryPositionById = function (id) {
|
|
||||||
var foundId = -1;
|
|
||||||
|
|
||||||
$scope.domainObject.model.entries.forEach(function (element, index) {
|
|
||||||
if (element.id === id) {
|
|
||||||
foundId = index;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return foundId;
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.newEntry = function ($event) {
|
|
||||||
$scope.scrollToTop();
|
|
||||||
|
|
||||||
var entries = $scope.domainObject.model.entries,
|
|
||||||
lastEntry = entries[entries.length - 1],
|
|
||||||
id = Date.now();
|
|
||||||
|
|
||||||
if (lastEntry === undefined || lastEntry.text || lastEntry.embeds) {
|
|
||||||
var createdEntry = {'id': id, 'createdOn': id};
|
|
||||||
|
|
||||||
$scope.domainObject.useCapability('mutation', function (model) {
|
|
||||||
model.entries.push(createdEntry);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
$scope.findEntryEl(lastEntry.id).focus();
|
|
||||||
|
|
||||||
$scope.domainObject.useCapability('mutation', function (model) {
|
|
||||||
model.entries[entries.length - 1].createdOn = id;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
$scope.entrySearch = '';
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
$scope.deleteEntry = function ($event) {
|
|
||||||
var delId = +$event.currentTarget.id;
|
|
||||||
var errorDialog = dialogService.showBlockingMessage({
|
|
||||||
severity: "error",
|
|
||||||
title: "This action will permanently delete this Notebook entry. Do you want to continue?",
|
|
||||||
minimized: true, // want the notification to be minimized initially (don't show banner)
|
|
||||||
options: [{
|
|
||||||
label: "OK",
|
|
||||||
callback: function () {
|
|
||||||
errorDialog.dismiss();
|
|
||||||
var elementPos = $scope.findEntryPositionById(delId);
|
|
||||||
|
|
||||||
if (elementPos !== -1) {
|
|
||||||
$scope.domainObject.useCapability('mutation', function (model) {
|
|
||||||
model.entries.splice(elementPos, 1);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
window.console.log('delete error');
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
},{
|
|
||||||
label: "Cancel",
|
|
||||||
callback: function () {
|
|
||||||
errorDialog.dismiss();
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.textFocus = function ($event, entryId) {
|
|
||||||
if ($event.srcElement) {
|
|
||||||
$scope.currentEntryValue = $event.srcElement.innerText;
|
|
||||||
} else {
|
|
||||||
$event.target.innerText = '';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//On text blur(when focus is removed)
|
|
||||||
$scope.textBlur = function ($event, entryId) {
|
|
||||||
// entryId is the unique numeric based on the original createdOn
|
|
||||||
if ($event.target) {
|
|
||||||
var elementPos = $scope.findEntryPositionById(+entryId);
|
|
||||||
|
|
||||||
// If the text of an entry has been changed, then update the text and the modifiedOn numeric
|
|
||||||
// Otherwise, don't do anything
|
|
||||||
if ($scope.currentEntryValue !== $event.target.innerText) {
|
|
||||||
$scope.domainObject.useCapability('mutation', function (model) {
|
|
||||||
model.entries[elementPos].text = $event.target.innerText;
|
|
||||||
model.entries[elementPos].modified = Date.now();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.finished = function (model) {
|
|
||||||
var lastEntry = model[model.length - 1];
|
|
||||||
|
|
||||||
if (!lastEntry.text) {
|
|
||||||
$scope.findEntryEl(lastEntry.id).focus();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.handleActive = function () {
|
|
||||||
var newEntry = $scope.entriesEl.find('.active');
|
|
||||||
if (newEntry) {
|
|
||||||
newEntry.removeClass('active');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
$scope.clearSearch = function () {
|
|
||||||
$scope.entrySearch = '';
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.viewSnapshot = function ($event,snapshot,embedId,entryId,$innerScope,domainObject) {
|
|
||||||
var viewAction = $scope.action.getActions({category: 'embed'})[0];
|
|
||||||
viewAction.perform($event, snapshot, embedId, entryId, $innerScope, domainObject);
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.renderImage = function (img) {
|
|
||||||
return URL.createObjectURL(img);
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.getDomainObj = function (id) {
|
|
||||||
return objectService.getObjects([id]);
|
|
||||||
};
|
|
||||||
|
|
||||||
function refreshComp(change) {
|
|
||||||
if (change && change.length) {
|
|
||||||
change[0].getCapability('action').getActions({key: 'remove'})[0].perform();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.actionToMenuOption = function (action) {
|
|
||||||
return {
|
|
||||||
key: action.getMetadata().key,
|
|
||||||
name: action.getMetadata().name,
|
|
||||||
cssClass: action.getMetadata().cssClass,
|
|
||||||
perform: action.perform
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// Maintain all "conclude-editing" and "save" actions in the
|
|
||||||
// present context.
|
|
||||||
function updateActions() {
|
|
||||||
$scope.menuEmbed = $scope.action ?
|
|
||||||
$scope.action.getActions({category: 'embed'}) :
|
|
||||||
[];
|
|
||||||
|
|
||||||
$scope.menuEmbedNoSnap = $scope.action ?
|
|
||||||
$scope.action.getActions({category: 'embed-no-snap'}) :
|
|
||||||
[];
|
|
||||||
|
|
||||||
$scope.menuActions = $scope.action ?
|
|
||||||
$scope.action.getActions({key: 'window'}) :
|
|
||||||
[];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update set of actions whenever the action capability
|
|
||||||
// changes or becomes available.
|
|
||||||
$scope.$watch("action", updateActions);
|
|
||||||
|
|
||||||
$scope.navigate = function ($event,embedType) {
|
|
||||||
if ($event) {
|
|
||||||
$event.preventDefault();
|
|
||||||
}
|
|
||||||
$scope.getDomainObj(embedType).then(function (resp) {
|
|
||||||
navigationService.setNavigation(resp[embedType]);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.saveSnap = function (url,embedPos,entryPos) {
|
|
||||||
var snapshot = false;
|
|
||||||
if (url) {
|
|
||||||
if (embedPos !== -1 && entryPos !== -1) {
|
|
||||||
var reader = new window.FileReader();
|
|
||||||
reader.readAsDataURL(url);
|
|
||||||
reader.onloadend = function () {
|
|
||||||
snapshot = reader.result;
|
|
||||||
$scope.domainObject.useCapability('mutation', function (model) {
|
|
||||||
if (model.entries[entryPos]) {
|
|
||||||
model.entries[entryPos].embeds[embedPos].snapshot = {
|
|
||||||
'src': snapshot,
|
|
||||||
'type': url.type,
|
|
||||||
'size': url.size,
|
|
||||||
'modified': Date.now()
|
|
||||||
};
|
|
||||||
model.entries[entryPos].embeds[embedPos].id = Date.now();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}else {
|
|
||||||
$scope.domainObject.useCapability('mutation', function (model) {
|
|
||||||
model.entries[entryPos].embeds[embedPos].snapshot = snapshot;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/*---popups menu embeds----*/
|
|
||||||
|
|
||||||
function getEmbedActions(embedType) {
|
|
||||||
if (!$scope.embedActions.length) {
|
|
||||||
$scope.getDomainObj(embedType).then(function (resp) {
|
|
||||||
$scope.embedActions = [];
|
|
||||||
$scope.embedActions.push($scope.actionToMenuOption(
|
|
||||||
$scope.action.getActions({key: 'mct-preview-action', selectedObject: resp[embedType]})[0]
|
|
||||||
));
|
|
||||||
$scope.embedActions.push($scope.actionToMenuOption(
|
|
||||||
$scope.action.getActions({key: 'window', selectedObject: resp[embedType]})[0]
|
|
||||||
));
|
|
||||||
$scope.embedActions.push({
|
|
||||||
key: 'navigate',
|
|
||||||
name: 'Go to Original',
|
|
||||||
cssClass: '',
|
|
||||||
perform: function () {
|
|
||||||
$scope.navigate('', embedType);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.openMenu = function ($event,embedType) {
|
|
||||||
$event.preventDefault();
|
|
||||||
|
|
||||||
getEmbedActions(embedType);
|
|
||||||
|
|
||||||
var body = $(document).find('body'),
|
|
||||||
initiatingEvent = agentService.isMobile() ?
|
|
||||||
'touchstart' : 'mousedown',
|
|
||||||
dismissExistingMenu,
|
|
||||||
menu;
|
|
||||||
|
|
||||||
var container = $($event.currentTarget).parent().parent();
|
|
||||||
|
|
||||||
menu = container.find('.menu-element');
|
|
||||||
|
|
||||||
// Remove the context menu
|
|
||||||
function dismiss() {
|
|
||||||
container.find('.hide-menu').append(menu);
|
|
||||||
body.off("mousedown", dismiss);
|
|
||||||
dismissExistingMenu = undefined;
|
|
||||||
$scope.embedActions = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dismiss any menu which was already showing
|
|
||||||
if (dismissExistingMenu) {
|
|
||||||
dismissExistingMenu();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ...and record the presence of this menu.
|
|
||||||
dismissExistingMenu = dismiss;
|
|
||||||
|
|
||||||
popupService.display(menu, [$event.pageX,$event.pageY], {
|
|
||||||
marginX: 0,
|
|
||||||
marginY: -50
|
|
||||||
});
|
|
||||||
|
|
||||||
// Stop propagation so that clicks or touches on the menu do not close the menu
|
|
||||||
menu.on(initiatingEvent, function (event) {
|
|
||||||
event.stopPropagation();
|
|
||||||
$timeout(dismiss, 300);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Dismiss the menu when body is clicked/touched elsewhere
|
|
||||||
// ('mousedown' because 'click' breaks left-click context menus)
|
|
||||||
// ('touchstart' because 'touch' breaks context menus up)
|
|
||||||
body.on(initiatingEvent, dismiss);
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
$scope.$watchCollection("composition", refreshComp);
|
|
||||||
|
|
||||||
$scope.$watch('domainObject.getModel().defaultSort', function (newDefaultSort, oldDefaultSort) {
|
|
||||||
if (newDefaultSort !== oldDefaultSort) {
|
|
||||||
$scope.sortEntries = newDefaultSort;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$scope.$on('$destroy', function () {});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return NotebookController;
|
|
||||||
});
|
|
@ -1,44 +0,0 @@
|
|||||||
/******************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2017, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This bundle implements "containment" rules, which determine which objects
|
|
||||||
* can be contained within a notebook.
|
|
||||||
*/
|
|
||||||
define(
|
|
||||||
[],
|
|
||||||
function () {
|
|
||||||
function CompositionPolicy() {
|
|
||||||
}
|
|
||||||
|
|
||||||
CompositionPolicy.prototype.allow = function (parent, child) {
|
|
||||||
var parentDef = parent.getCapability('type').getName();
|
|
||||||
|
|
||||||
if (parentDef === 'Notebook' && child.getCapability('status').list().length) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
return CompositionPolicy;
|
|
||||||
}
|
|
||||||
);
|
|
35
src/MCT.js
35
src/MCT.js
@ -31,13 +31,14 @@ define([
|
|||||||
'./plugins/plugins',
|
'./plugins/plugins',
|
||||||
'./adapter/indicators/legacy-indicators-plugin',
|
'./adapter/indicators/legacy-indicators-plugin',
|
||||||
'./plugins/buildInfo/plugin',
|
'./plugins/buildInfo/plugin',
|
||||||
'./adapter/vue-adapter/install',
|
|
||||||
'./ui/registries/ViewRegistry',
|
'./ui/registries/ViewRegistry',
|
||||||
'./ui/registries/InspectorViewRegistry',
|
'./ui/registries/InspectorViewRegistry',
|
||||||
'./ui/registries/ToolbarRegistry',
|
'./ui/registries/ToolbarRegistry',
|
||||||
'./ui/router/ApplicationRouter',
|
'./ui/router/ApplicationRouter',
|
||||||
|
'./ui/router/Browse',
|
||||||
'../platform/framework/src/Main',
|
'../platform/framework/src/Main',
|
||||||
'./styles-new/core.scss',
|
'./styles-new/core.scss',
|
||||||
|
'./styles-new/notebook.scss',
|
||||||
'./ui/components/layout/Layout.vue',
|
'./ui/components/layout/Layout.vue',
|
||||||
'vue'
|
'vue'
|
||||||
], function (
|
], function (
|
||||||
@ -51,13 +52,14 @@ define([
|
|||||||
plugins,
|
plugins,
|
||||||
LegacyIndicatorsPlugin,
|
LegacyIndicatorsPlugin,
|
||||||
buildInfoPlugin,
|
buildInfoPlugin,
|
||||||
installVueAdapter,
|
|
||||||
ViewRegistry,
|
ViewRegistry,
|
||||||
InspectorViewRegistry,
|
InspectorViewRegistry,
|
||||||
ToolbarRegistry,
|
ToolbarRegistry,
|
||||||
ApplicationRouter,
|
ApplicationRouter,
|
||||||
|
Browse,
|
||||||
Main,
|
Main,
|
||||||
coreStyles,
|
coreStyles,
|
||||||
|
NotebookStyles,
|
||||||
Layout,
|
Layout,
|
||||||
Vue
|
Vue
|
||||||
) {
|
) {
|
||||||
@ -275,6 +277,7 @@ define([
|
|||||||
}.bind(this)
|
}.bind(this)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// TODO: remove with legacy types.
|
||||||
this.types.listKeys().forEach(function (typeKey) {
|
this.types.listKeys().forEach(function (typeKey) {
|
||||||
var type = this.types.get(typeKey);
|
var type = this.types.get(typeKey);
|
||||||
var legacyDefinition = type.toLegacyDefinition();
|
var legacyDefinition = type.toLegacyDefinition();
|
||||||
@ -282,26 +285,6 @@ define([
|
|||||||
this.legacyExtension('types', legacyDefinition);
|
this.legacyExtension('types', legacyDefinition);
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
|
|
||||||
// TODO: move this to adapter bundle.
|
|
||||||
this.legacyExtension('runs', {
|
|
||||||
depends: ['types[]'],
|
|
||||||
implementation: (types) => {
|
|
||||||
this.types.importLegacyTypes(types);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.objectViews.getAllProviders().forEach(function (p) {
|
|
||||||
this.legacyExtension('views', {
|
|
||||||
key: p.key,
|
|
||||||
provider: p,
|
|
||||||
name: p.name,
|
|
||||||
cssClass: p.cssClass,
|
|
||||||
description: p.description,
|
|
||||||
editable: p.editable,
|
|
||||||
template: '<mct-view mct-provider-key="' + p.key + '"/>'
|
|
||||||
});
|
|
||||||
}, this);
|
|
||||||
|
|
||||||
legacyRegistry.register('adapter', this.legacyBundle);
|
legacyRegistry.register('adapter', this.legacyBundle);
|
||||||
legacyRegistry.enable('adapter');
|
legacyRegistry.enable('adapter');
|
||||||
|
|
||||||
@ -326,8 +309,6 @@ define([
|
|||||||
// something has depended upon objectService. Cool, right?
|
// something has depended upon objectService. Cool, right?
|
||||||
this.$injector.get('objectService');
|
this.$injector.get('objectService');
|
||||||
|
|
||||||
console.log('Rendering app layout.');
|
|
||||||
|
|
||||||
var appLayout = new Vue({
|
var appLayout = new Vue({
|
||||||
mixins: [Layout.default],
|
mixins: [Layout.default],
|
||||||
provide: {
|
provide: {
|
||||||
@ -335,10 +316,8 @@ define([
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
domElement.appendChild(appLayout.$mount().$el);
|
domElement.appendChild(appLayout.$mount().$el);
|
||||||
|
this.layout = appLayout;
|
||||||
console.log('Attaching adapter');
|
Browse(this);
|
||||||
installVueAdapter(appLayout, this);
|
|
||||||
|
|
||||||
this.router.start();
|
this.router.start();
|
||||||
this.emit('start');
|
this.emit('start');
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
|
@ -34,7 +34,9 @@ define([
|
|||||||
'./runs/TimeSettingsURLHandler',
|
'./runs/TimeSettingsURLHandler',
|
||||||
'./runs/TypeDeprecationChecker',
|
'./runs/TypeDeprecationChecker',
|
||||||
'./runs/LegacyTelemetryProvider',
|
'./runs/LegacyTelemetryProvider',
|
||||||
'./services/LegacyObjectAPIInterceptor'
|
'./runs/RegisterLegacyTypes',
|
||||||
|
'./services/LegacyObjectAPIInterceptor',
|
||||||
|
'./views/installLegacyViews'
|
||||||
], function (
|
], function (
|
||||||
legacyRegistry,
|
legacyRegistry,
|
||||||
ActionDialogDecorator,
|
ActionDialogDecorator,
|
||||||
@ -49,7 +51,9 @@ define([
|
|||||||
TimeSettingsURLHandler,
|
TimeSettingsURLHandler,
|
||||||
TypeDeprecationChecker,
|
TypeDeprecationChecker,
|
||||||
LegacyTelemetryProvider,
|
LegacyTelemetryProvider,
|
||||||
LegacyObjectAPIInterceptor
|
RegisterLegacyTypes,
|
||||||
|
LegacyObjectAPIInterceptor,
|
||||||
|
installLegacyViews
|
||||||
) {
|
) {
|
||||||
legacyRegistry.register('src/adapter', {
|
legacyRegistry.register('src/adapter', {
|
||||||
"extensions": {
|
"extensions": {
|
||||||
@ -149,6 +153,21 @@ define([
|
|||||||
"openmct",
|
"openmct",
|
||||||
"instantiate"
|
"instantiate"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
implementation: installLegacyViews,
|
||||||
|
depends: [
|
||||||
|
"openmct",
|
||||||
|
"views[]",
|
||||||
|
"instantiate"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
implementation: RegisterLegacyTypes,
|
||||||
|
depends: [
|
||||||
|
"types[]",
|
||||||
|
"openmct"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
licenses: [
|
licenses: [
|
||||||
|
17
src/adapter/runs/RegisterLegacyTypes.js
Normal file
17
src/adapter/runs/RegisterLegacyTypes.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
define([
|
||||||
|
|
||||||
|
], function (
|
||||||
|
|
||||||
|
) {
|
||||||
|
function RegisterLegacyTypes(types, openmct) {
|
||||||
|
types.forEach(function (legacyDefinition) {
|
||||||
|
if (!openmct.types.get(legacyDefinition.key)) {
|
||||||
|
console.warn(`DEPRECATION WARNING: Migrate type ${legacyDefinition.key} from ${legacyDefinition.bundle.path} to use the new Types API. Legacy type support will be removed soon.`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
openmct.types.importLegacyTypes(types);
|
||||||
|
}
|
||||||
|
|
||||||
|
return RegisterLegacyTypes;
|
||||||
|
});
|
110
src/adapter/views/LegacyViewProvider.js
Normal file
110
src/adapter/views/LegacyViewProvider.js
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
define([
|
||||||
|
|
||||||
|
], function (
|
||||||
|
|
||||||
|
) {
|
||||||
|
const DEFAULT_VIEW_PRIORITY = 100;
|
||||||
|
|
||||||
|
const PRIORITY_LEVELS = {
|
||||||
|
"fallback": Number.NEGATIVE_INFINITY,
|
||||||
|
"default": -100,
|
||||||
|
"none": 0,
|
||||||
|
"optional": DEFAULT_VIEW_PRIORITY,
|
||||||
|
"preferred": 1000,
|
||||||
|
"mandatory": Number.POSITIVE_INFINITY
|
||||||
|
};
|
||||||
|
|
||||||
|
function LegacyViewProvider(legacyView, openmct, convertToLegacyObject) {
|
||||||
|
console.warn(`DEPRECATION WARNING: Migrate ${legacyView.key} from ${legacyView.bundle.path} to use the new View APIs. Legacy view support will be removed soon.`);
|
||||||
|
return {
|
||||||
|
key: legacyView.key,
|
||||||
|
name: legacyView.name,
|
||||||
|
cssClass: legacyView.cssClass,
|
||||||
|
description: legacyView.description,
|
||||||
|
editable: legacyView.editable,
|
||||||
|
canView: function (domainObject) {
|
||||||
|
if (!domainObject || !domainObject.identifier) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (legacyView.type) {
|
||||||
|
return domainObject.type === legacyView.type;
|
||||||
|
}
|
||||||
|
let legacyObject = convertToLegacyObject(domainObject);
|
||||||
|
if (legacyView.needs) {
|
||||||
|
let meetsNeeds = legacyView.needs.every(k => legacyObject.hasCapability(k));
|
||||||
|
if (!meetsNeeds) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return openmct.$injector.get('policyService').allow(
|
||||||
|
'view', legacyView, legacyObject
|
||||||
|
);
|
||||||
|
},
|
||||||
|
view: function (domainObject) {
|
||||||
|
let $rootScope = openmct.$injector.get('$rootScope');
|
||||||
|
let templateLinker = openmct.$injector.get('templateLinker');
|
||||||
|
let scope = $rootScope.$new();
|
||||||
|
let legacyObject = convertToLegacyObject(domainObject);
|
||||||
|
let isDestroyed = false;
|
||||||
|
scope.domainObject = legacyObject;
|
||||||
|
scope.model = legacyObject.getModel();
|
||||||
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
show: function (container) {
|
||||||
|
// TODO: implement "gestures" support ?
|
||||||
|
let uses = legacyView.uses || [];
|
||||||
|
let promises = [];
|
||||||
|
let results = uses.map(function (capabilityKey, i) {
|
||||||
|
let result = legacyObject.useCapability(capabilityKey);
|
||||||
|
if (result.then) {
|
||||||
|
promises.push(result.then(function (r) {
|
||||||
|
results[i] = r;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
|
function link() {
|
||||||
|
if (isDestroyed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uses.forEach(function (key, i) {
|
||||||
|
scope[key] = results[i];
|
||||||
|
});
|
||||||
|
templateLinker.link(
|
||||||
|
scope,
|
||||||
|
openmct.$angular.element(container),
|
||||||
|
legacyView
|
||||||
|
);
|
||||||
|
container.style.height = '100%';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (promises.length) {
|
||||||
|
Promise.all(promises)
|
||||||
|
.then(function () {
|
||||||
|
link();
|
||||||
|
scope.$digest();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
link();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
destroy: function () {
|
||||||
|
scope.$destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
priority: function () {
|
||||||
|
let priority = legacyView.priority || DEFAULT_VIEW_PRIORITY;
|
||||||
|
if (typeof priority === 'string') {
|
||||||
|
priority = PRIORITY_LEVELS[priority];
|
||||||
|
}
|
||||||
|
return priority;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return LegacyViewProvider;
|
||||||
|
|
||||||
|
});
|
22
src/adapter/views/installLegacyViews.js
Normal file
22
src/adapter/views/installLegacyViews.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
define([
|
||||||
|
'./LegacyViewProvider',
|
||||||
|
'../../api/objects/object-utils'
|
||||||
|
], function (
|
||||||
|
LegacyViewProvider,
|
||||||
|
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));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return installLegacyViews;
|
||||||
|
});
|
@ -1,41 +0,0 @@
|
|||||||
define([
|
|
||||||
|
|
||||||
], function (
|
|
||||||
|
|
||||||
) {
|
|
||||||
|
|
||||||
class InspectorAdapter {
|
|
||||||
constructor(layout, openmct) {
|
|
||||||
console.log('installing inspector adapter');
|
|
||||||
|
|
||||||
this.openmct = openmct;
|
|
||||||
this.layout = layout;
|
|
||||||
this.$injector = openmct.$injector;
|
|
||||||
this.angular = openmct.$angular;
|
|
||||||
|
|
||||||
this.objectService = this.$injector.get('objectService');
|
|
||||||
this.templateLinker = this.$injector.get('templateLinker');
|
|
||||||
this.$timeout = this.$injector.get('$timeout');
|
|
||||||
|
|
||||||
this.templateMap = {};
|
|
||||||
this.$injector.get('templates[]').forEach((t) => {
|
|
||||||
this.templateMap[t.key] = this.templateMap[t.key] || t;
|
|
||||||
});
|
|
||||||
|
|
||||||
var $rootScope = this.$injector.get('$rootScope');
|
|
||||||
this.scope = $rootScope.$new();
|
|
||||||
|
|
||||||
this.templateLinker.link(
|
|
||||||
this.scope,
|
|
||||||
angular.element(layout.$refs.inspector.$refs.properties),
|
|
||||||
this.templateMap["inspectorRegion"]
|
|
||||||
);
|
|
||||||
this.$timeout(function () {
|
|
||||||
//hello!
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return InspectorAdapter;
|
|
||||||
|
|
||||||
});
|
|
@ -1,16 +0,0 @@
|
|||||||
define([
|
|
||||||
'./main-adapter',
|
|
||||||
'./tree-adapter',
|
|
||||||
'./inspector-adapter'
|
|
||||||
], function (
|
|
||||||
MainAdapter,
|
|
||||||
TreeAdapter,
|
|
||||||
InspectorAdapter
|
|
||||||
) {
|
|
||||||
|
|
||||||
return function install(layout, openmct) {
|
|
||||||
let main = new MainAdapter(layout, openmct);
|
|
||||||
let tree = new TreeAdapter(layout, openmct);
|
|
||||||
let inspector = new InspectorAdapter(layout, openmct);
|
|
||||||
}
|
|
||||||
});
|
|
@ -1,153 +0,0 @@
|
|||||||
define([
|
|
||||||
|
|
||||||
], function (
|
|
||||||
|
|
||||||
) {
|
|
||||||
|
|
||||||
// Find an object in an array of objects.
|
|
||||||
function findObject(domainObjects, id) {
|
|
||||||
var i;
|
|
||||||
for (i = 0; i < domainObjects.length; i += 1) {
|
|
||||||
if (domainObjects[i].getId() === id) {
|
|
||||||
return domainObjects[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// recursively locate and return an object inside of a container
|
|
||||||
// via a path. If at any point in the recursion it fails to find
|
|
||||||
// the next object, it will return the parent.
|
|
||||||
function findViaComposition(containerObject, path) {
|
|
||||||
var nextId = path.shift();
|
|
||||||
if (!nextId) {
|
|
||||||
return containerObject;
|
|
||||||
}
|
|
||||||
return containerObject.useCapability('composition')
|
|
||||||
.then(function (composees) {
|
|
||||||
var nextObject = findObject(composees, nextId);
|
|
||||||
if (!nextObject) {
|
|
||||||
return containerObject;
|
|
||||||
}
|
|
||||||
if (!nextObject.hasCapability('composition')) {
|
|
||||||
return nextObject;
|
|
||||||
}
|
|
||||||
return findViaComposition(nextObject, path);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getLastChildIfRoot(object) {
|
|
||||||
if (object.getId() !== 'ROOT') {
|
|
||||||
return object;
|
|
||||||
}
|
|
||||||
return object.useCapability('composition')
|
|
||||||
.then(function (composees) {
|
|
||||||
return composees[composees.length - 1];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function pathForObject(domainObject) {
|
|
||||||
var context = domainObject.getCapability('context'),
|
|
||||||
objectPath = context ? context.getPath() : [],
|
|
||||||
ids = objectPath.map(function (domainObj) {
|
|
||||||
return domainObj.getId();
|
|
||||||
});
|
|
||||||
return "/browse/" + ids.slice(1).join("/");
|
|
||||||
}
|
|
||||||
|
|
||||||
class MainAdapter {
|
|
||||||
constructor(layout, openmct) {
|
|
||||||
this.openmct = openmct;
|
|
||||||
this.layout = layout;
|
|
||||||
this.$injector = openmct.$injector;
|
|
||||||
this.angular = openmct.$angular;
|
|
||||||
|
|
||||||
this.objectService = this.$injector.get('objectService');
|
|
||||||
this.templateLinker = this.$injector.get('templateLinker');
|
|
||||||
this.navigationService = this.$injector.get('navigationService');
|
|
||||||
this.$timeout = this.$injector.get('$timeout');
|
|
||||||
|
|
||||||
this.templateMap = {};
|
|
||||||
this.$injector.get('templates[]').forEach((t) => {
|
|
||||||
this.templateMap[t.key] = this.templateMap[t.key] || t;
|
|
||||||
});
|
|
||||||
|
|
||||||
var $rootScope = this.$injector.get('$rootScope');
|
|
||||||
this.scope = $rootScope.$new();
|
|
||||||
this.scope.representation = {};
|
|
||||||
|
|
||||||
openmct.router.route(/^\/browse\/(.*)$/, (path, results) => {
|
|
||||||
let navigatePath = results[1];
|
|
||||||
if (!navigatePath) {
|
|
||||||
navigatePath = 'mine';
|
|
||||||
}
|
|
||||||
this.navigateToPath(navigatePath);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.navigationService.addListener(o => this.navigateToObject(o));
|
|
||||||
}
|
|
||||||
|
|
||||||
navigateToPath(path) {
|
|
||||||
if (!Array.isArray(path)) {
|
|
||||||
path = path.split('/');
|
|
||||||
}
|
|
||||||
return this.getObject('ROOT')
|
|
||||||
.then(root => {
|
|
||||||
return findViaComposition(root, path);
|
|
||||||
})
|
|
||||||
.then(getLastChildIfRoot)
|
|
||||||
.then(object => {
|
|
||||||
this.setMainViewObject(object);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setMainViewObject(object) {
|
|
||||||
this.scope.domainObject = object;
|
|
||||||
this.scope.navigatedObject = object;
|
|
||||||
this.templateLinker.link(
|
|
||||||
this.scope,
|
|
||||||
angular.element(this.layout.$refs.mainContainer),
|
|
||||||
this.templateMap["browseObject"]
|
|
||||||
);
|
|
||||||
document.title = object.getModel().name;
|
|
||||||
this.scheduleDigest();
|
|
||||||
}
|
|
||||||
|
|
||||||
idsForObject(domainObject) {
|
|
||||||
return this.urlService
|
|
||||||
.urlForLocation("", domainObject)
|
|
||||||
.replace('/', '');
|
|
||||||
}
|
|
||||||
|
|
||||||
navigateToObject(object) {
|
|
||||||
let path = pathForObject(object);
|
|
||||||
let views = object.useCapability('view');
|
|
||||||
let params = this.openmct.router.getParams();
|
|
||||||
let currentViewIsValid = views.some(v => v.key === params['view']);
|
|
||||||
if (!currentViewIsValid) {
|
|
||||||
this.scope.representation = {
|
|
||||||
selected: views[0]
|
|
||||||
}
|
|
||||||
this.openmct.router.update(path, {
|
|
||||||
view: views[0].key
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.openmct.router.setPath(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
scheduleDigest() {
|
|
||||||
this.$timeout(function () {
|
|
||||||
// digest done!
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
getObject(id) {
|
|
||||||
return this.objectService.getObjects([id])
|
|
||||||
.then(function (results) {
|
|
||||||
return results[id];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return MainAdapter;
|
|
||||||
});
|
|
@ -1,15 +0,0 @@
|
|||||||
define([
|
|
||||||
|
|
||||||
], function (
|
|
||||||
|
|
||||||
) {
|
|
||||||
|
|
||||||
class TreeAdapter {
|
|
||||||
constructor(layout, openmct) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return TreeAdapter;
|
|
||||||
|
|
||||||
});
|
|
@ -89,12 +89,13 @@ define(function () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (Array.isArray(legacyDefinition.creatable) && 'creation' in legacyDefinition.creatable) {
|
|
||||||
|
if (legacyDefinition.features && legacyDefinition.features.includes("creation")) {
|
||||||
definition.creatable = true;
|
definition.creatable = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return definition;
|
return definition;
|
||||||
}
|
};
|
||||||
|
|
||||||
return Type;
|
return Type;
|
||||||
});
|
});
|
||||||
|
@ -55,11 +55,8 @@ define([
|
|||||||
'../platform/exporters/bundle',
|
'../platform/exporters/bundle',
|
||||||
'../platform/features/clock/bundle',
|
'../platform/features/clock/bundle',
|
||||||
'../platform/features/fixed/bundle',
|
'../platform/features/fixed/bundle',
|
||||||
'../platform/features/conductor/core/bundle',
|
|
||||||
'../platform/features/conductor/compatibility/bundle',
|
|
||||||
'../platform/features/imagery/bundle',
|
'../platform/features/imagery/bundle',
|
||||||
'../platform/features/layout/bundle',
|
'../platform/features/layout/bundle',
|
||||||
'../platform/features/listview/bundle',
|
|
||||||
'../platform/features/my-items/bundle',
|
'../platform/features/my-items/bundle',
|
||||||
'../platform/features/pages/bundle',
|
'../platform/features/pages/bundle',
|
||||||
'../platform/features/hyperlink/bundle',
|
'../platform/features/hyperlink/bundle',
|
||||||
@ -103,7 +100,6 @@ define([
|
|||||||
'platform/features/fixed',
|
'platform/features/fixed',
|
||||||
'platform/features/imagery',
|
'platform/features/imagery',
|
||||||
'platform/features/layout',
|
'platform/features/layout',
|
||||||
'platform/features/listview',
|
|
||||||
'platform/features/pages',
|
'platform/features/pages',
|
||||||
'platform/features/hyperlink',
|
'platform/features/hyperlink',
|
||||||
'platform/features/timeline',
|
'platform/features/timeline',
|
||||||
|
@ -20,11 +20,10 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
define([
|
import CSV from 'comma-separated-values';
|
||||||
'csv',
|
import {saveAs} from 'file-saver/FileSaver';
|
||||||
'saveAs'
|
|
||||||
], function (CSV, saveAs) {
|
class CSVExporter {
|
||||||
class CSVExporter {
|
|
||||||
export(rows, options) {
|
export(rows, options) {
|
||||||
let headers = (options && options.headers) ||
|
let headers = (options && options.headers) ||
|
||||||
(Object.keys((rows[0] || {})).sort());
|
(Object.keys((rows[0] || {})).sort());
|
||||||
@ -33,7 +32,6 @@ define([
|
|||||||
let blob = new Blob([csvText], { type: "text/csv" });
|
let blob = new Blob([csvText], { type: "text/csv" });
|
||||||
saveAs(blob, filename);
|
saveAs(blob, filename);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
return CSVExporter;
|
export default CSVExporter;
|
||||||
});
|
|
||||||
|
67
src/plugins/folderView/FolderGridView.js
Normal file
67
src/plugins/folderView/FolderGridView.js
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
define([
|
||||||
|
'./components/GridView.vue',
|
||||||
|
'vue'
|
||||||
|
], function (
|
||||||
|
GridViewComponent,
|
||||||
|
Vue
|
||||||
|
) {
|
||||||
|
function FolderGridView(openmct) {
|
||||||
|
return {
|
||||||
|
key: 'grid',
|
||||||
|
name: 'Grid Vue',
|
||||||
|
cssClass: 'icon-thumbs-strip',
|
||||||
|
canView: function (domainObject) {
|
||||||
|
return domainObject.type === 'folder';
|
||||||
|
},
|
||||||
|
view: function (domainObject) {
|
||||||
|
let component;
|
||||||
|
|
||||||
|
return {
|
||||||
|
show: function (element) {
|
||||||
|
component = new Vue({
|
||||||
|
components: {
|
||||||
|
gridViewComponent: GridViewComponent.default
|
||||||
|
},
|
||||||
|
provide: {
|
||||||
|
openmct,
|
||||||
|
domainObject
|
||||||
|
},
|
||||||
|
el: element,
|
||||||
|
template: '<grid-view-component></grid-view-component>'
|
||||||
|
});
|
||||||
|
},
|
||||||
|
destroy: function (element) {
|
||||||
|
component.$destroy();
|
||||||
|
component = undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
priority: function () {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return FolderGridView;
|
||||||
|
});
|
70
src/plugins/folderView/FolderListView.js
Normal file
70
src/plugins/folderView/FolderListView.js
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
define([
|
||||||
|
'./components/ListView.vue',
|
||||||
|
'vue',
|
||||||
|
'moment'
|
||||||
|
], function (
|
||||||
|
ListViewComponent,
|
||||||
|
Vue,
|
||||||
|
Moment
|
||||||
|
) {
|
||||||
|
function FolderListView(openmct) {
|
||||||
|
return {
|
||||||
|
key: 'list-view',
|
||||||
|
name: 'List Vue',
|
||||||
|
cssClass: 'icon-list-view',
|
||||||
|
canView: function (domainObject) {
|
||||||
|
return domainObject.type === 'folder';
|
||||||
|
},
|
||||||
|
view: function (domainObject) {
|
||||||
|
let component;
|
||||||
|
|
||||||
|
return {
|
||||||
|
show: function (element) {
|
||||||
|
component = new Vue({
|
||||||
|
components: {
|
||||||
|
listViewComponent: ListViewComponent.default
|
||||||
|
},
|
||||||
|
provide: {
|
||||||
|
openmct,
|
||||||
|
domainObject,
|
||||||
|
Moment
|
||||||
|
},
|
||||||
|
el: element,
|
||||||
|
template: '<list-view-component></list-view-component>'
|
||||||
|
});
|
||||||
|
},
|
||||||
|
destroy: function (element) {
|
||||||
|
component.$destroy();
|
||||||
|
component = undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
priority: function () {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return FolderListView;
|
||||||
|
});
|
200
src/plugins/folderView/components/GridView.vue
Normal file
200
src/plugins/folderView/components/GridView.vue
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
<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>
|
215
src/plugins/folderView/components/ListView.vue
Normal file
215
src/plugins/folderView/components/ListView.vue
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
<template>
|
||||||
|
<div class="c-table c-table--sortable c-list-view">
|
||||||
|
<table class="c-table__body">
|
||||||
|
<thead class="c-table__header">
|
||||||
|
<tr>
|
||||||
|
<th class="is-sortable"
|
||||||
|
v-bind:class="[orderByField == 'name' ? 'is-sorting' : '', sortClass]"
|
||||||
|
@click="sortTrigger('name', 'asc')">
|
||||||
|
Name
|
||||||
|
</th>
|
||||||
|
<th class="is-sortable"
|
||||||
|
v-bind:class="[orderByField == 'type' ? 'is-sorting' : '', sortClass]"
|
||||||
|
@click="sortTrigger('type', 'asc')">
|
||||||
|
Type
|
||||||
|
</th>
|
||||||
|
<th class="is-sortable"
|
||||||
|
v-bind:class="[orderByField == 'createdDate' ? 'is-sorting' : '', sortClass]"
|
||||||
|
@click="sortTrigger('createdDate', 'desc')">
|
||||||
|
Created Date
|
||||||
|
</th>
|
||||||
|
<th class="is-sortable"
|
||||||
|
v-bind:class="[orderByField == 'updatedDate' ? 'is-sorting' : '', sortClass]"
|
||||||
|
@click="sortTrigger('updatedDate', 'desc')">
|
||||||
|
Updated Date
|
||||||
|
</th>
|
||||||
|
<th class="is-sortable"
|
||||||
|
v-bind:class="[orderByField == 'items' ? 'is-sorting' : '', sortClass]"
|
||||||
|
@click="sortTrigger('items', 'asc')">
|
||||||
|
Items
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr class="c-list-item"
|
||||||
|
v-for="(item,index) in sortedItems"
|
||||||
|
v-bind:key="index"
|
||||||
|
:class="{ 'is-alias': item.isAlias === true }"
|
||||||
|
@click="navigate(item.identifier)">
|
||||||
|
<td class="c-list-item__name">
|
||||||
|
<div class="c-list-item__type-icon" :class="(item.cssClass != undefined) ? item.cssClass : 'icon-object-unknown'"></div>
|
||||||
|
{{item.name}}
|
||||||
|
</td>
|
||||||
|
<td class="c-list-item__type">{{ item.type }}</td>
|
||||||
|
<td class="c-list-item__date-created">{{ formatTime(item.createdDate, 'YYYY-MM-DD HH:mm:ss:SSS') }}Z</td>
|
||||||
|
<td class="c-list-item__date-updated">{{ formatTime(item.updatedDate, 'YYYY-MM-DD HH:mm:ss:SSS') }}Z</td>
|
||||||
|
<td class="c-list-item__items">{{ item.items }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import "~styles/sass-base";
|
||||||
|
|
||||||
|
/******************************* LIST VIEW */
|
||||||
|
.c-list-view {
|
||||||
|
overflow-x: auto !important;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
tbody tr {
|
||||||
|
background: $colorListItemBg;
|
||||||
|
transition: $transOut;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.desktop & {
|
||||||
|
tbody tr {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: $colorListItemBgHov;
|
||||||
|
transition: $transIn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
$p: floor($interiorMargin * 1.5);
|
||||||
|
font-size: 1.1em;
|
||||||
|
padding-top: $p;
|
||||||
|
padding-bottom: $p;
|
||||||
|
|
||||||
|
&:not(.c-list-item__name) {
|
||||||
|
color: $colorItemFgDetails;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-list-item {
|
||||||
|
&__name {
|
||||||
|
@include ellipsize();
|
||||||
|
}
|
||||||
|
|
||||||
|
&__type-icon {
|
||||||
|
color: $colorKey;
|
||||||
|
display: inline-block;
|
||||||
|
width: 1em;
|
||||||
|
margin-right:$interiorMarginSm;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-alias {
|
||||||
|
// Object is an alias to an original.
|
||||||
|
[class*='__type-icon'] {
|
||||||
|
&:after {
|
||||||
|
color: $colorIconAlias;
|
||||||
|
content: $glyph-icon-link;
|
||||||
|
font-family: symbolsfont;
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
text-shadow: rgba(black, 0.5) 0 1px 2px;
|
||||||
|
top: auto; left: -1px; bottom: 1px; right: auto;
|
||||||
|
transform-origin: bottom left;
|
||||||
|
transform: scale(0.65);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/******************************* LIST ITEM */
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
inject: ['openmct', 'domainObject', 'Moment'],
|
||||||
|
data() {
|
||||||
|
var items = [],
|
||||||
|
unknownObjectType = {
|
||||||
|
definition: {
|
||||||
|
cssClass: 'icon-object-unknown',
|
||||||
|
name: 'Unknown Type'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
composition = this.openmct.composition.get(this.domainObject);
|
||||||
|
|
||||||
|
if (composition) {
|
||||||
|
|
||||||
|
composition.load().then((array) => {
|
||||||
|
if (Array.isArray(array)) {
|
||||||
|
array.forEach(model => {
|
||||||
|
var type = this.openmct.types.get(model.type) || unknownObjectType;
|
||||||
|
|
||||||
|
items.push({
|
||||||
|
name: model.name,
|
||||||
|
identifier: model.identifier.key,
|
||||||
|
type: type.definition.name,
|
||||||
|
isAlias: false,
|
||||||
|
cssClass: type.definition.cssClass,
|
||||||
|
createdDate: model.persisted,
|
||||||
|
updatedDate: model.modified,
|
||||||
|
items: model.composition ? model.composition.length : 0,
|
||||||
|
isAlias: this.domainObject.identifier.key !== model.location
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
items: items,
|
||||||
|
orderByField: 'name',
|
||||||
|
sortClass: 'asc',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
sortedItems () {
|
||||||
|
if (this.sortClass === 'asc') {
|
||||||
|
return this.items.sort(this.ascending.bind(this));
|
||||||
|
} else if (this.sortClass === 'desc') {
|
||||||
|
return this.items.sort(this.descending.bind(this));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
formatTime () {
|
||||||
|
return function (timestamp, format) {
|
||||||
|
return this.Moment(timestamp).format(format);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
navigate(identifier) {
|
||||||
|
let currentLocation = this.openmct.router.currentLocation.path,
|
||||||
|
navigateToPath = `${currentLocation}/${identifier}`;
|
||||||
|
|
||||||
|
this.openmct.router.setPath(navigateToPath);
|
||||||
|
},
|
||||||
|
sortTrigger(field, sortOrder) {
|
||||||
|
if (this.orderByField === field) {
|
||||||
|
this.sortClass = (this.sortClass === 'asc') ? 'desc' : 'asc';
|
||||||
|
} else {
|
||||||
|
this.sortClass = sortOrder;
|
||||||
|
}
|
||||||
|
this.orderByField = field;
|
||||||
|
},
|
||||||
|
ascending(first, second) {
|
||||||
|
if (first[this.orderByField] < second[this.orderByField]) {
|
||||||
|
return -1;
|
||||||
|
} else if (first[this.orderByField] > second[this.orderByField]) {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
descending(first, second) {
|
||||||
|
if (first[this.orderByField] > second[this.orderByField]) {
|
||||||
|
return -1;
|
||||||
|
} else if (first[this.orderByField] < second[this.orderByField]) {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
@ -20,34 +20,17 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
define([], function () {
|
define([
|
||||||
|
'./FolderGridView',
|
||||||
/**
|
'./FolderListView'
|
||||||
* Formatter for basic strings.
|
], function (
|
||||||
*
|
FolderGridView,
|
||||||
* @implements {Format}
|
FolderListView
|
||||||
* @constructor
|
) {
|
||||||
* @memberof platform/commonUI/formats
|
return function plugin() {
|
||||||
*/
|
return function install(openmct) {
|
||||||
function StringFormat() {
|
openmct.objectViews.addProvider(new FolderGridView(openmct));
|
||||||
this.key = 'string';
|
openmct.objectViews.addProvider(new FolderListView(openmct));
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
});
|
});
|
@ -51,64 +51,12 @@ define([
|
|||||||
function LocalTimeFormat() {
|
function LocalTimeFormat() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an appropriate time format based on the provided value and
|
|
||||||
* the threshold required.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
function getScaledFormat(d) {
|
|
||||||
var momentified = moment.utc(d);
|
|
||||||
/**
|
|
||||||
* Uses logic from d3 Time-Scales, v3 of the API. See
|
|
||||||
* https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Scales.md
|
|
||||||
*
|
|
||||||
* Licensed
|
|
||||||
*/
|
|
||||||
return [
|
|
||||||
[".SSS", function (m) {
|
|
||||||
return m.milliseconds();
|
|
||||||
}],
|
|
||||||
[":ss", function (m) {
|
|
||||||
return m.seconds();
|
|
||||||
}],
|
|
||||||
["hh:mma", function (m) {
|
|
||||||
return m.minutes();
|
|
||||||
}],
|
|
||||||
["hha", function (m) {
|
|
||||||
return m.hours();
|
|
||||||
}],
|
|
||||||
["ddd DD", function (m) {
|
|
||||||
return m.days() &&
|
|
||||||
m.date() !== 1;
|
|
||||||
}],
|
|
||||||
["MMM DD", function (m) {
|
|
||||||
return m.date() !== 1;
|
|
||||||
}],
|
|
||||||
["MMMM", function (m) {
|
|
||||||
return m.month();
|
|
||||||
}],
|
|
||||||
["YYYY", function () {
|
|
||||||
return true;
|
|
||||||
}]
|
|
||||||
].filter(function (row) {
|
|
||||||
return row[1](momentified);
|
|
||||||
})[0][0];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param value
|
* @param value
|
||||||
* @param {Scale} [scale] Optionally provides context to the
|
|
||||||
* format request, allowing for scale-appropriate formatting.
|
|
||||||
* @returns {string} the formatted date
|
* @returns {string} the formatted date
|
||||||
*/
|
*/
|
||||||
LocalTimeFormat.prototype.format = function (value, scale) {
|
LocalTimeFormat.prototype.format = function (value, scale) {
|
||||||
if (scale !== undefined) {
|
|
||||||
var scaledFormat = getScaledFormat(value, scale);
|
|
||||||
if (scaledFormat) {
|
|
||||||
return moment.utc(value).format(scaledFormat);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return moment(value).format(DATE_FORMAT);
|
return moment(value).format(DATE_FORMAT);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ define([], function () {
|
|||||||
this.timeFormat = 'local-format';
|
this.timeFormat = 'local-format';
|
||||||
this.durationFormat = 'duration';
|
this.durationFormat = 'duration';
|
||||||
|
|
||||||
this.isUTCBased = true;
|
this.isUTCBased = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return LocalTimeSystem;
|
return LocalTimeSystem;
|
||||||
|
215
src/plugins/notebook/plugin.js
Normal file
215
src/plugins/notebook/plugin.js
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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;
|
||||||
|
});
|
@ -25,6 +25,5 @@
|
|||||||
ng-options="opt.value as opt.name for opt in options"
|
ng-options="opt.value as opt.name for opt in options"
|
||||||
ng-required="ngRequired"
|
ng-required="ngRequired"
|
||||||
name="mctControl">
|
name="mctControl">
|
||||||
<!-- <option value="" ng-show="!ngModel[field]">- Select One -</option> -->
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
32
src/plugins/notebook/res/templates/embed.html
Normal file
32
src/plugins/notebook/res/templates/embed.html
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<div class="c-ne__embed">
|
||||||
|
<div class="c-ne__embed__snap-thumb"
|
||||||
|
v-if="embed.snapshot"
|
||||||
|
v-on:click="openSnapshot">
|
||||||
|
<img v-bind:src="embed.snapshot.src">
|
||||||
|
</div>
|
||||||
|
<div class="c-ne__embed__info">
|
||||||
|
<div class="c-ne__embed__name">
|
||||||
|
<a class="c-ne__embed__link"
|
||||||
|
v-on:click="navigate(embed.type)"
|
||||||
|
v-bind:class="[embed.cssClass]">{{embed.name}}</a>
|
||||||
|
<a class="c-ne__embed__context-available icon-arrow-down"
|
||||||
|
v-on:click="toggleActionMenu"></a>
|
||||||
|
</div>
|
||||||
|
<div class="hide-menu hidden">
|
||||||
|
<div class="menu-element context-menu-wrapper mobile-disable-select">
|
||||||
|
<div class="menu context-menu">
|
||||||
|
<ul>
|
||||||
|
<li v-for="action in actions"
|
||||||
|
v-bind:class="[action.cssClass]"
|
||||||
|
v-on:click="action.perform(embed, entry)">
|
||||||
|
{{ action.name }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="c-ne__embed__time" v-if="embed.snapshot">
|
||||||
|
{{formatTime(embed.createdOn, 'YYYY-MM-DD HH:mm:ss')}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
35
src/plugins/notebook/res/templates/entry.html
Normal file
35
src/plugins/notebook/res/templates/entry.html
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<li class="c-notebook__entry c-ne has-local-controls"
|
||||||
|
v-on:drop="dropOnEntry(entry.id)"
|
||||||
|
v-on:dragover="dragoverOnEntry"
|
||||||
|
>
|
||||||
|
<div class="c-ne__time-and-content">
|
||||||
|
<div class="c-ne__time">
|
||||||
|
<span>{{formatTime(entry.createdOn, 'YYYY-MM-DD')}}</span>
|
||||||
|
<span>{{formatTime(entry.createdOn, 'HH:mm:ss')}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="c-ne__content">
|
||||||
|
<!-- TODO: fix styling for c-input-inline when SCSS is merged and remove s-input-inline class here -->
|
||||||
|
<div class="c-ne__text c-input-inline s-input-inline"
|
||||||
|
contenteditable="true"
|
||||||
|
ref="contenteditable"
|
||||||
|
v-on:blur="textBlur($event, entry.id)"
|
||||||
|
v-on:focus="textFocus($event, entry.id)"
|
||||||
|
v-bind:key="entry.id"
|
||||||
|
v-html="entry.text">
|
||||||
|
</div>
|
||||||
|
<div class="c-ne__embeds">
|
||||||
|
<notebook-embed
|
||||||
|
v-for="(embed, index) in entry.embeds"
|
||||||
|
v-bind:embed="embed"
|
||||||
|
v-bind:entry="entry"
|
||||||
|
></notebook-embed>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="c-ne__local-controls--hidden">
|
||||||
|
<button class="c-click-icon icon-trash"
|
||||||
|
title="Delete this entry"
|
||||||
|
v-on:click="deleteEntry"></button>
|
||||||
|
</div>
|
||||||
|
</li>
|
37
src/plugins/notebook/res/templates/notebook.html
Normal file
37
src/plugins/notebook/res/templates/notebook.html
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<div class="c-notebook">
|
||||||
|
<div class="c-notebook__head">
|
||||||
|
<search class="c-notebook__search"
|
||||||
|
v-model="entrySearch"
|
||||||
|
v-on:input="search($event)"
|
||||||
|
v-on:clear="entrySearch = ''; search($event)"></search>
|
||||||
|
<div class="c-notebook__controls">
|
||||||
|
<div class="select c-notebook__controls__time">
|
||||||
|
<select v-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 c-notebook__controls__sort">
|
||||||
|
<select v-model="sortEntries">
|
||||||
|
<option value="-createdOn" selected="selected">Newest first</option>
|
||||||
|
<option value="createdOn">Oldest first</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="c-notebook__drag-area icon-plus"
|
||||||
|
v-on:click="newEntry($event)"
|
||||||
|
id="newEntry" mct-entry-dnd>
|
||||||
|
<span class="c-notebook__drag-area__label">To start a new entry, click here or drag and drop any object</span>
|
||||||
|
</div>
|
||||||
|
<div class="c-notebook__entries" ng-mouseover="handleActive()">
|
||||||
|
<ul>
|
||||||
|
<notebook-entry
|
||||||
|
v-for="entry in filterBySearch(entries, entrySearch)"
|
||||||
|
v-bind:entry="entry"
|
||||||
|
></notebook-entry>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
50
src/plugins/notebook/res/templates/viewSnapshot.html
Normal file
50
src/plugins/notebook/res/templates/viewSnapshot.html
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<div class="abs overlay l-large-view">
|
||||||
|
<div class="abs blocker" v-on:click="close"></div>
|
||||||
|
|
||||||
|
<div class="abs outer-holder">
|
||||||
|
|
||||||
|
<a
|
||||||
|
class="close icon-x-in-circle"
|
||||||
|
v-on:click="close">
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="abs inner-holder l-flex-col">
|
||||||
|
<div class="t-contents flex-elem holder grows">
|
||||||
|
|
||||||
|
<div class="t-snapshot abs l-view-header">
|
||||||
|
<div class="abs object-browse-bar l-flex-row">
|
||||||
|
<div class="left flex-elem l-flex-row grows">
|
||||||
|
<div class="object-header flex-elem l-flex-row grows">
|
||||||
|
<div class="type-icon flex-elem embed-icon holder" v-bind:class="embed.cssClass"></div>
|
||||||
|
<div class="title-label flex-elem holder flex-can-shrink">{{embed.name}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="btn-bar right l-flex-row flex-elem flex-justify-end flex-fixed">
|
||||||
|
<div class="flex-elem holder flex-can-shrink s-snapshot-datetime">
|
||||||
|
SNAPSHOT {{formatTime(embed.createdOn, 'YYYY-MM-DD HH:mm:ss')}}
|
||||||
|
</div>
|
||||||
|
<a class="s-button icon-pencil" title="Annotate">
|
||||||
|
<span class="title-label">Annotate</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="abs object-holder t-image-holder s-image-holder">
|
||||||
|
<div
|
||||||
|
class="image-main s-image-main"
|
||||||
|
v-bind:style="{ backgroundImage: 'url(' + embed.snapshot.src + ')' }">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="bottom-bar flex-elem holder"
|
||||||
|
v-on:click="close">
|
||||||
|
|
||||||
|
<a class="t-done s-button major">Done</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -26,6 +26,7 @@
|
|||||||
define(
|
define(
|
||||||
["painterro", "zepto"],
|
["painterro", "zepto"],
|
||||||
function (Painterro, $) {
|
function (Painterro, $) {
|
||||||
|
|
||||||
var annotationStruct = {
|
var annotationStruct = {
|
||||||
title: "Annotate Snapshot",
|
title: "Annotate Snapshot",
|
||||||
template: "annotate-snapshot",
|
template: "annotate-snapshot",
|
||||||
@ -107,9 +108,6 @@ define(
|
|||||||
done(true);
|
done(true);
|
||||||
}
|
}
|
||||||
}).show(snapshot);
|
}).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 dialogService = this.dialogService;
|
||||||
var rootScope = this.$rootScope;
|
var rootScope = this.$rootScope;
|
||||||
rootScope.newEntryText = '';
|
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.selObj = domainObj;
|
||||||
this.$rootScope.selValue = "";
|
this.$rootScope.selValue = "";
|
||||||
var newScope = rootScope.$new();
|
var newScope = rootScope.$new();
|
||||||
@ -187,7 +187,7 @@ define(
|
|||||||
var domainObject = context.domainObject;
|
var domainObject = context.domainObject;
|
||||||
|
|
||||||
if (domainObject) {
|
if (domainObject) {
|
||||||
if (domainObject.getModel().type === 'Notebook') {
|
if (domainObject.getModel().type === 'notebook') {
|
||||||
// do not allow in context of a notebook
|
// do not allow in context of a notebook
|
||||||
return false;
|
return false;
|
||||||
} else if (domainObject.getModel().type.includes('imagery')) {
|
} else if (domainObject.getModel().type.includes('imagery')) {
|
130
src/plugins/notebook/src/actions/snapshotAction.js
Normal file
130
src/plugins/notebook/src/actions/snapshotAction.js
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
define(
|
||||||
|
['zepto'],
|
||||||
|
function ($) {
|
||||||
|
|
||||||
|
function SnapshotAction (exportImageService, dialogService, context) {
|
||||||
|
this.exportImageService = exportImageService;
|
||||||
|
this.dialogService = dialogService;
|
||||||
|
this.domainObject = context.domainObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
SnapshotAction.prototype.perform = function () {
|
||||||
|
var elementToSnapshot =
|
||||||
|
$(document.body).find(".overlay .object-holder")[0] ||
|
||||||
|
$(document.body).find("[key='representation.selected.key']")[0];
|
||||||
|
|
||||||
|
$(elementToSnapshot).addClass("s-status-taking-snapshot");
|
||||||
|
|
||||||
|
this.exportImageService.exportPNGtoSRC(elementToSnapshot).then(function (blob) {
|
||||||
|
$(elementToSnapshot).removeClass("s-status-taking-snapshot");
|
||||||
|
|
||||||
|
if (blob) {
|
||||||
|
var reader = new window.FileReader();
|
||||||
|
reader.readAsDataURL(blob);
|
||||||
|
reader.onloadend = function () {
|
||||||
|
this.saveSnapshot(reader.result, blob.type, blob.size);
|
||||||
|
}.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
}.bind(this));
|
||||||
|
};
|
||||||
|
|
||||||
|
SnapshotAction.prototype.saveSnapshot = function (imageURL, imageType, imageSize) {
|
||||||
|
var taskForm = this.generateTaskForm(),
|
||||||
|
domainObject = this.domainObject,
|
||||||
|
domainObjectId = domainObject.getId(),
|
||||||
|
cssClass = domainObject.getCapability('type').typeDef.cssClass,
|
||||||
|
name = domainObject.model.name;
|
||||||
|
|
||||||
|
this.dialogService.getDialogResponse(
|
||||||
|
'overlay-dialog',
|
||||||
|
taskForm,
|
||||||
|
function () {
|
||||||
|
return taskForm.value;
|
||||||
|
}
|
||||||
|
).then(function (options) {
|
||||||
|
var snapshotObject = {
|
||||||
|
src: imageURL,
|
||||||
|
type: imageType,
|
||||||
|
size: imageSize
|
||||||
|
};
|
||||||
|
|
||||||
|
options.notebook.useCapability('mutation', function (model) {
|
||||||
|
var date = Date.now();
|
||||||
|
|
||||||
|
model.entries.push({
|
||||||
|
id: 'entry-' + date,
|
||||||
|
createdOn: date,
|
||||||
|
text: options.entry,
|
||||||
|
embeds: [{
|
||||||
|
name: name,
|
||||||
|
cssClass: cssClass,
|
||||||
|
type: domainObjectId,
|
||||||
|
id: 'embed-' + date,
|
||||||
|
createdOn: date,
|
||||||
|
snapshot: snapshotObject
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
SnapshotAction.prototype.generateTaskForm = function () {
|
||||||
|
var taskForm = {
|
||||||
|
name: "Create a Notebook Entry",
|
||||||
|
hint: "Please select a Notebook",
|
||||||
|
sections: [{
|
||||||
|
rows: [{
|
||||||
|
name: 'Entry',
|
||||||
|
key: 'entry',
|
||||||
|
control: 'textarea',
|
||||||
|
required: false,
|
||||||
|
"cssClass": "l-textarea-sm"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Save in Notebook',
|
||||||
|
key: 'notebook',
|
||||||
|
control: 'locator',
|
||||||
|
validate: validateLocation
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
|
||||||
|
var overlayModel = {
|
||||||
|
title: taskForm.name,
|
||||||
|
message: 'AHAHAH',
|
||||||
|
structure: taskForm,
|
||||||
|
value: {'entry': ""}
|
||||||
|
};
|
||||||
|
|
||||||
|
function validateLocation(newParentObj) {
|
||||||
|
return newParentObj.model.type === 'notebook';
|
||||||
|
}
|
||||||
|
|
||||||
|
return overlayModel;
|
||||||
|
};
|
||||||
|
|
||||||
|
return SnapshotAction;
|
||||||
|
}
|
||||||
|
);
|
198
src/plugins/notebook/src/controllers/EmbedController.js
Normal file
198
src/plugins/notebook/src/controllers/EmbedController.js
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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([
|
||||||
|
'moment',
|
||||||
|
'zepto',
|
||||||
|
'../utils/SnapshotOverlay',
|
||||||
|
],
|
||||||
|
function (
|
||||||
|
Moment,
|
||||||
|
$,
|
||||||
|
SnapshotOverlay
|
||||||
|
) {
|
||||||
|
function EmbedController (openmct, domainObject) {
|
||||||
|
this.openmct = openmct;
|
||||||
|
this.domainObject = domainObject;
|
||||||
|
this.objectService = openmct.$injector.get('objectService');
|
||||||
|
this.navigationService = openmct.$injector.get('navigationService');
|
||||||
|
this.popupService = openmct.$injector.get('popupService');
|
||||||
|
this.agentService = openmct.$injector.get('agentService');
|
||||||
|
this.dialogService = openmct.$injector.get('dialogService');
|
||||||
|
|
||||||
|
|
||||||
|
this.navigate = this.navigate.bind(this);
|
||||||
|
this.exposedData = this.exposedData.bind(this);
|
||||||
|
this.exposedMethods = this.exposedMethods.bind(this);
|
||||||
|
this.toggleActionMenu = this.toggleActionMenu.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
EmbedController.prototype.navigate = function (embedType) {
|
||||||
|
this.objectService.getObjects([embedType]).then(function (objects) {
|
||||||
|
this.navigationService.setNavigation(objects[embedType]);
|
||||||
|
}.bind(this));
|
||||||
|
};
|
||||||
|
|
||||||
|
EmbedController.prototype.openSnapshot = function () {
|
||||||
|
if (!this.snapshotOverlay) {
|
||||||
|
this.snapShotOverlay = new SnapshotOverlay(this.embed, this.formatTime);
|
||||||
|
} else {
|
||||||
|
this.snapShotOverlay = undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
EmbedController.prototype.formatTime = function (unixTime, timeFormat) {
|
||||||
|
return Moment(unixTime).format(timeFormat);
|
||||||
|
};
|
||||||
|
|
||||||
|
EmbedController.prototype.findInArray = function (array, id) {
|
||||||
|
var foundId = -1;
|
||||||
|
|
||||||
|
array.forEach(function (element, index) {
|
||||||
|
if (element.id === id) {
|
||||||
|
foundId = index;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return foundId;
|
||||||
|
};
|
||||||
|
|
||||||
|
EmbedController.prototype.actionToMenuDecorator = function (action) {
|
||||||
|
return {
|
||||||
|
name: action.getMetadata().name,
|
||||||
|
cssClass: action.getMetadata().cssClass,
|
||||||
|
perform: action.perform
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
EmbedController.prototype.populateActionMenu = function (objectService, actionService) {
|
||||||
|
return function () {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
objectService.getObjects([self.embed.type]).then(function (resp) {
|
||||||
|
var domainObject = resp[self.embed.type],
|
||||||
|
previewAction = actionService.getActions({key: 'mct-preview-action', domainObject: domainObject})[0];
|
||||||
|
|
||||||
|
self.actions.push(self.actionToMenuDecorator(previewAction));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
EmbedController.prototype.removeEmbedAction = function () {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: 'Remove Embed',
|
||||||
|
cssClass: 'icon-trash',
|
||||||
|
perform: function (embed, entry) {
|
||||||
|
var entryPosition = self.findInArray(self.domainObject.entries, entry.id),
|
||||||
|
embedPosition = self.findInArray(entry.embeds, embed.id);
|
||||||
|
|
||||||
|
var warningDialog = self.dialogService.showBlockingMessage({
|
||||||
|
severity: "error",
|
||||||
|
title: "This action will permanently delete this embed. Do you wish to continue?",
|
||||||
|
options: [{
|
||||||
|
label: "OK",
|
||||||
|
callback: function () {
|
||||||
|
entry.embeds.splice(embedPosition, 1);
|
||||||
|
var dirString = 'entries[' + entryPosition + '].embeds';
|
||||||
|
|
||||||
|
self.openmct.objects.mutate(self.domainObject, dirString, entry.embeds);
|
||||||
|
|
||||||
|
warningDialog.dismiss();
|
||||||
|
}
|
||||||
|
},{
|
||||||
|
label: "Cancel",
|
||||||
|
callback: function () {
|
||||||
|
warningDialog.dismiss();
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
EmbedController.prototype.toggleActionMenu = function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
var body = $(document.body),
|
||||||
|
container = $(event.target.parentElement.parentElement),
|
||||||
|
initiatingEvent = this.agentService.isMobile() ?
|
||||||
|
'touchstart' : 'mousedown',
|
||||||
|
menu = container.find('.menu-element'),
|
||||||
|
dismissExistingMenu;
|
||||||
|
|
||||||
|
// Remove the context menu
|
||||||
|
function dismiss() {
|
||||||
|
container.find('.hide-menu').append(menu);
|
||||||
|
body.off(initiatingEvent, dismiss);
|
||||||
|
menu.off(initiatingEvent, menuClickHandler);
|
||||||
|
dismissExistingMenu = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function menuClickHandler(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
window.setTimeout(dismiss, 300);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dismiss any menu which was already showing
|
||||||
|
if (dismissExistingMenu) {
|
||||||
|
dismissExistingMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ...and record the presence of this menu.
|
||||||
|
dismissExistingMenu = dismiss;
|
||||||
|
|
||||||
|
this.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, menuClickHandler);
|
||||||
|
|
||||||
|
body.on(initiatingEvent, dismiss);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
EmbedController.prototype.exposedData = function () {
|
||||||
|
return {
|
||||||
|
actions: [this.removeEmbedAction()],
|
||||||
|
showActionMenu: false
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
EmbedController.prototype.exposedMethods = function () {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
return {
|
||||||
|
navigate: self.navigate,
|
||||||
|
openSnapshot: self.openSnapshot,
|
||||||
|
formatTime: self.formatTime,
|
||||||
|
toggleActionMenu: self.toggleActionMenu,
|
||||||
|
actionToMenuDecorator: self.actionToMenuDecorator
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return EmbedController;
|
||||||
|
});
|
150
src/plugins/notebook/src/controllers/EntryController.js
Normal file
150
src/plugins/notebook/src/controllers/EntryController.js
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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([
|
||||||
|
'moment'
|
||||||
|
],
|
||||||
|
function (
|
||||||
|
Moment
|
||||||
|
) {
|
||||||
|
|
||||||
|
function EntryController (openmct, domainObject) {
|
||||||
|
this.openmct = openmct;
|
||||||
|
this.domainObject = domainObject;
|
||||||
|
this.dndService = this.openmct.$injector.get('dndService');
|
||||||
|
this.dialogService = this.openmct.$injector.get('dialogService');
|
||||||
|
|
||||||
|
this.currentEntryValue = '';
|
||||||
|
|
||||||
|
this.exposedMethods = this.exposedMethods.bind(this);
|
||||||
|
this.exposedData = this.exposedData.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
EntryController.prototype.entryPosById = function (entryId) {
|
||||||
|
var foundId = -1;
|
||||||
|
|
||||||
|
this.domainObject.entries.forEach(function (element, index) {
|
||||||
|
if (element.id === entryId) {
|
||||||
|
foundId = index;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return foundId;
|
||||||
|
};
|
||||||
|
|
||||||
|
EntryController.prototype.textFocus = function ($event) {
|
||||||
|
if ($event.target) {
|
||||||
|
this.currentEntryValue = $event.target.innerText;
|
||||||
|
} else {
|
||||||
|
$event.target.innerText = '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
EntryController.prototype.textBlur = function ($event, entryId) {
|
||||||
|
if ($event.target) {
|
||||||
|
var entryPos = this.entryPosById(entryId);
|
||||||
|
|
||||||
|
if (this.currentEntryValue !== $event.target.innerText) {
|
||||||
|
this.openmct.objects.mutate(this.domainObject, 'entries[' + entryPos + '].text', $event.target.innerText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
EntryController.prototype.formatTime = function (unixTime, timeFormat) {
|
||||||
|
return Moment(unixTime).format(timeFormat);
|
||||||
|
};
|
||||||
|
|
||||||
|
EntryController.prototype.deleteEntry = function () {
|
||||||
|
var entryPos = this.entryPosById(this.entry.id),
|
||||||
|
domainObject = this.domainObject,
|
||||||
|
openmct = this.openmct;
|
||||||
|
|
||||||
|
if (entryPos !== -1) {
|
||||||
|
|
||||||
|
var errorDialog = this.dialogService.showBlockingMessage({
|
||||||
|
severity: "error",
|
||||||
|
title: "This action will permanently delete this Notebook entry. Do you wish to continue?",
|
||||||
|
options: [{
|
||||||
|
label: "OK",
|
||||||
|
callback: function () {
|
||||||
|
domainObject.entries.splice(entryPos, 1);
|
||||||
|
openmct.objects.mutate(domainObject, 'entries', domainObject.entries);
|
||||||
|
|
||||||
|
errorDialog.dismiss();
|
||||||
|
}
|
||||||
|
},{
|
||||||
|
label: "Cancel",
|
||||||
|
callback: function () {
|
||||||
|
errorDialog.dismiss();
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
EntryController.prototype.dropOnEntry = function (entryId) {
|
||||||
|
var selectedObject = this.dndService.getData('mct-domain-object'),
|
||||||
|
selectedObjectId = selectedObject.getId(),
|
||||||
|
selectedModel = selectedObject.getModel(),
|
||||||
|
cssClass = selectedObject.getCapability('type').typeDef.cssClass,
|
||||||
|
entryPos = this.entryPosById(entryId),
|
||||||
|
currentEntryEmbeds = this.domainObject.entries[entryPos].embeds,
|
||||||
|
newEmbed = {
|
||||||
|
type: selectedObjectId,
|
||||||
|
id: '' + Date.now(),
|
||||||
|
cssClass: cssClass,
|
||||||
|
name: selectedModel.name,
|
||||||
|
snapshot: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
currentEntryEmbeds.push(newEmbed);
|
||||||
|
this.openmct.objects.mutate(this.domainObject, 'entries[' + entryPos + '].embeds', currentEntryEmbeds);
|
||||||
|
};
|
||||||
|
|
||||||
|
EntryController.prototype.dragoverOnEntry = function () {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
EntryController.prototype.exposedData = function () {
|
||||||
|
return {
|
||||||
|
openmct: this.openmct,
|
||||||
|
domainObject: this.domainObject,
|
||||||
|
dndService: this.dndService,
|
||||||
|
dialogService: this.dialogService,
|
||||||
|
currentEntryValue: this.currentEntryValue
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
EntryController.prototype.exposedMethods = function () {
|
||||||
|
return {
|
||||||
|
entryPosById: this.entryPosById,
|
||||||
|
textFocus: this.textFocus,
|
||||||
|
textBlur: this.textBlur,
|
||||||
|
formatTime: this.formatTime,
|
||||||
|
deleteEntry: this.deleteEntry,
|
||||||
|
dropOnEntry: this.dropOnEntry,
|
||||||
|
dragoverOnEntry: this.dragoverOnEntry
|
||||||
|
};
|
||||||
|
};
|
||||||
|
return EntryController;
|
||||||
|
});
|
@ -31,8 +31,7 @@ define(
|
|||||||
$scope.snapshot = undefined;
|
$scope.snapshot = undefined;
|
||||||
$scope.snapToggle = true;
|
$scope.snapToggle = true;
|
||||||
$scope.entryText = '';
|
$scope.entryText = '';
|
||||||
var annotateAction = $rootScope.selObj.getCapability('action').getActions(
|
var annotateAction = $rootScope.selObj.getCapability('action').getActions({key: 'annotate-snapshot'})[0];
|
||||||
{category: 'embed'})[1];
|
|
||||||
|
|
||||||
$scope.$parent.$parent.ngModel[$scope.$parent.$parent.field] = $rootScope.selObj;
|
$scope.$parent.$parent.ngModel[$scope.$parent.$parent.field] = $rootScope.selObj;
|
||||||
$scope.objectName = $rootScope.selObj.getModel().name;
|
$scope.objectName = $rootScope.selObj.getModel().name;
|
177
src/plugins/notebook/src/controllers/NotebookController.js
Normal file
177
src/plugins/notebook/src/controllers/NotebookController.js
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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([
|
||||||
|
'vue',
|
||||||
|
'./EntryController',
|
||||||
|
'./EmbedController',
|
||||||
|
'../../res/templates/notebook.html',
|
||||||
|
'../../res/templates/entry.html',
|
||||||
|
'../../res/templates/embed.html',
|
||||||
|
'../../../../ui/components/controls/search.vue'
|
||||||
|
],
|
||||||
|
function (
|
||||||
|
Vue,
|
||||||
|
EntryController,
|
||||||
|
EmbedController,
|
||||||
|
NotebookTemplate,
|
||||||
|
EntryTemplate,
|
||||||
|
EmbedTemplate,
|
||||||
|
search
|
||||||
|
) {
|
||||||
|
|
||||||
|
function NotebookController(openmct, domainObject) {
|
||||||
|
this.openmct = openmct;
|
||||||
|
this.domainObject = domainObject;
|
||||||
|
this.entrySearch = '';
|
||||||
|
this.objectService = openmct.$injector.get('objectService');
|
||||||
|
this.actionService = openmct.$injector.get('actionService');
|
||||||
|
|
||||||
|
this.show = this.show.bind(this);
|
||||||
|
this.destroy = this.destroy.bind(this);
|
||||||
|
this.newEntry = this.newEntry.bind(this);
|
||||||
|
this.entryPosById = this.entryPosById.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
NotebookController.prototype.initializeVue = function (container) {
|
||||||
|
var self = this,
|
||||||
|
entryController = new EntryController(this.openmct, this.domainObject),
|
||||||
|
embedController = new EmbedController(this.openmct, this.domainObject);
|
||||||
|
|
||||||
|
this.container = container;
|
||||||
|
|
||||||
|
var notebookEmbed = {
|
||||||
|
props:['embed', 'entry'],
|
||||||
|
template: EmbedTemplate,
|
||||||
|
data: embedController.exposedData,
|
||||||
|
methods: embedController.exposedMethods(),
|
||||||
|
beforeMount: embedController.populateActionMenu(self.objectService, self.actionService)
|
||||||
|
};
|
||||||
|
|
||||||
|
var entryComponent = {
|
||||||
|
props:['entry'],
|
||||||
|
template: EntryTemplate,
|
||||||
|
components: {
|
||||||
|
'notebook-embed': notebookEmbed
|
||||||
|
},
|
||||||
|
data: entryController.exposedData,
|
||||||
|
methods: entryController.exposedMethods(),
|
||||||
|
mounted: self.focusOnEntry
|
||||||
|
};
|
||||||
|
|
||||||
|
var notebookVue = Vue.extend({
|
||||||
|
template: NotebookTemplate,
|
||||||
|
components: {
|
||||||
|
'notebook-entry': entryComponent,
|
||||||
|
'search': search.default
|
||||||
|
},
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
entrySearch: self.entrySearch,
|
||||||
|
showTime: '0',
|
||||||
|
sortEntries: '-createdOn',
|
||||||
|
entries: self.domainObject.entries,
|
||||||
|
currentEntryValue: ''
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
search: function (event) {
|
||||||
|
if (event.target.value) {
|
||||||
|
this.entrySearch = event.target.value;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
newEntry: self.newEntry,
|
||||||
|
filterBySearch: self.filterBySearch
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.NotebookVue = new notebookVue();
|
||||||
|
container.appendChild(this.NotebookVue.$mount().$el);
|
||||||
|
};
|
||||||
|
|
||||||
|
NotebookController.prototype.newEntry = function (event) {
|
||||||
|
|
||||||
|
var entries = this.domainObject.entries,
|
||||||
|
lastEntryIndex = entries.length - 1,
|
||||||
|
lastEntry = entries[lastEntryIndex],
|
||||||
|
date = Date.now();
|
||||||
|
|
||||||
|
if (lastEntry === undefined || lastEntry.text || lastEntry.embeds.length) {
|
||||||
|
var createdEntry = {'id': 'entry-' + date, 'createdOn': date, 'embeds':[]};
|
||||||
|
|
||||||
|
entries.push(createdEntry);
|
||||||
|
this.openmct.objects.mutate(this.domainObject, 'entries', entries);
|
||||||
|
} else {
|
||||||
|
lastEntry.createdOn = date;
|
||||||
|
|
||||||
|
this.openmct.objects.mutate(this.domainObject, 'entries[entries.length-1]', lastEntry);
|
||||||
|
this.focusOnEntry.bind(this.NotebookVue.$children[lastEntryIndex])();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.entrySearch = '';
|
||||||
|
};
|
||||||
|
|
||||||
|
NotebookController.prototype.entryPosById = function (entryId) {
|
||||||
|
var foundId = -1;
|
||||||
|
|
||||||
|
this.domainObject.entries.forEach(function (element, index) {
|
||||||
|
if (element.id === entryId) {
|
||||||
|
foundId = index;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return foundId;
|
||||||
|
};
|
||||||
|
|
||||||
|
NotebookController.prototype.focusOnEntry = function () {
|
||||||
|
if (!this.entry.text) {
|
||||||
|
this.$refs.contenteditable.focus();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
NotebookController.prototype.filterBySearch = function (entryArray, filterString) {
|
||||||
|
if (filterString) {
|
||||||
|
var lowerCaseFilterString = filterString.toLowerCase();
|
||||||
|
|
||||||
|
return entryArray.filter(function (entry) {
|
||||||
|
if (entry.text) {
|
||||||
|
return entry.text.toLowerCase().includes(lowerCaseFilterString);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return entryArray;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
NotebookController.prototype.show = function (container) {
|
||||||
|
this.initializeVue(container);
|
||||||
|
};
|
||||||
|
|
||||||
|
NotebookController.prototype.destroy = function (container) {
|
||||||
|
this.NotebookVue.$destroy(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
return NotebookController;
|
||||||
|
});
|
@ -31,9 +31,11 @@ define(['zepto'], function ($) {
|
|||||||
var selectedModel = selectedObject.getModel();
|
var selectedModel = selectedObject.getModel();
|
||||||
var cssClass = selectedObject.getCapability('type').typeDef.cssClass;
|
var cssClass = selectedObject.getCapability('type').typeDef.cssClass;
|
||||||
var entryId = -1;
|
var entryId = -1;
|
||||||
|
var embedId = -1;
|
||||||
$scope.clearSearch();
|
$scope.clearSearch();
|
||||||
if ($element[0].id === 'newEntry') {
|
if ($element[0].id === 'newEntry') {
|
||||||
entryId = $scope.domainObject.model.entries.length;
|
entryId = $scope.domainObject.model.entries.length;
|
||||||
|
embedId = 0;
|
||||||
var lastEntry = $scope.domainObject.model.entries[entryId - 1];
|
var lastEntry = $scope.domainObject.model.entries[entryId - 1];
|
||||||
if (lastEntry === undefined || lastEntry.text || lastEntry.embeds) {
|
if (lastEntry === undefined || lastEntry.text || lastEntry.embeds) {
|
||||||
$scope.domainObject.useCapability('mutation', function (model) {
|
$scope.domainObject.useCapability('mutation', function (model) {
|
||||||
@ -82,6 +84,8 @@ define(['zepto'], function ($) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
embedId = $scope.domainObject.model.entries[entryId].embeds.length - 1;
|
||||||
|
|
||||||
if (selectedObject) {
|
if (selectedObject) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
@ -1,5 +1,5 @@
|
|||||||
/*****************************************************************************
|
/*****************************************************************************
|
||||||
* Open MCT, Copyright (c) 2014-2017, United States Government
|
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
* Administration. All rights reserved.
|
* Administration. All rights reserved.
|
||||||
*
|
*
|
||||||
@ -20,35 +20,47 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
/**
|
|
||||||
* This bundle implements object types and associated views for
|
|
||||||
* display-building.
|
|
||||||
*/
|
|
||||||
define(
|
|
||||||
[],
|
|
||||||
function () {
|
|
||||||
|
|
||||||
/**
|
define([
|
||||||
* The LayoutNotebookController is responsible for supporting the
|
'vue',
|
||||||
* notebook feature creation on theLayout view.
|
'../../res/templates/viewSnapshot.html'
|
||||||
**/
|
], function (
|
||||||
|
Vue,
|
||||||
|
snapshotOverlayTemplate
|
||||||
|
) {
|
||||||
|
function SnapshotOverlay (embedObject, formatTime) {
|
||||||
|
this.embedObject = embedObject;
|
||||||
|
|
||||||
function LayoutNotebookController($scope) {
|
this.snapshotOverlayVue = new Vue({
|
||||||
$scope.hasNotebookAction = undefined;
|
template: snapshotOverlayTemplate,
|
||||||
|
data: function () {
|
||||||
$scope.newNotebook = undefined;
|
return {
|
||||||
|
embed: embedObject
|
||||||
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();
|
|
||||||
};
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
close: this.close.bind(this),
|
||||||
|
formatTime: formatTime
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
return LayoutNotebookController;
|
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;
|
||||||
|
});
|
@ -27,14 +27,15 @@ define([
|
|||||||
'./autoflow/AutoflowTabularPlugin',
|
'./autoflow/AutoflowTabularPlugin',
|
||||||
'./timeConductor/plugin',
|
'./timeConductor/plugin',
|
||||||
'../../example/imagery/plugin',
|
'../../example/imagery/plugin',
|
||||||
'../../platform/features/notebook/bundle',
|
|
||||||
'../../platform/import-export/bundle',
|
'../../platform/import-export/bundle',
|
||||||
'./summaryWidget/plugin',
|
'./summaryWidget/plugin',
|
||||||
'./URLIndicatorPlugin/URLIndicatorPlugin',
|
'./URLIndicatorPlugin/URLIndicatorPlugin',
|
||||||
'./telemetryMean/plugin',
|
'./telemetryMean/plugin',
|
||||||
'./plot/plugin',
|
'./plot/plugin',
|
||||||
'./telemetryTable/plugin',
|
'./telemetryTable/plugin',
|
||||||
'./staticRootPlugin/plugin'
|
'./staticRootPlugin/plugin',
|
||||||
|
'./notebook/plugin',
|
||||||
|
'./folderView/plugin'
|
||||||
], function (
|
], function (
|
||||||
_,
|
_,
|
||||||
UTCTimeSystem,
|
UTCTimeSystem,
|
||||||
@ -42,19 +43,19 @@ define([
|
|||||||
AutoflowPlugin,
|
AutoflowPlugin,
|
||||||
TimeConductorPlugin,
|
TimeConductorPlugin,
|
||||||
ExampleImagery,
|
ExampleImagery,
|
||||||
Notebook,
|
|
||||||
ImportExport,
|
ImportExport,
|
||||||
SummaryWidget,
|
SummaryWidget,
|
||||||
URLIndicatorPlugin,
|
URLIndicatorPlugin,
|
||||||
TelemetryMean,
|
TelemetryMean,
|
||||||
PlotPlugin,
|
PlotPlugin,
|
||||||
TelemetryTablePlugin,
|
TelemetryTablePlugin,
|
||||||
StaticRootPlugin
|
StaticRootPlugin,
|
||||||
|
Notebook,
|
||||||
|
FolderView
|
||||||
) {
|
) {
|
||||||
var bundleMap = {
|
var bundleMap = {
|
||||||
LocalStorage: 'platform/persistence/local',
|
LocalStorage: 'platform/persistence/local',
|
||||||
MyItems: 'platform/features/my-items',
|
MyItems: 'platform/features/my-items'
|
||||||
Notebook: 'platform/features/notebook'
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var plugins = _.mapValues(bundleMap, function (bundleName, pluginName) {
|
var plugins = _.mapValues(bundleMap, function (bundleName, pluginName) {
|
||||||
@ -102,7 +103,7 @@ define([
|
|||||||
*/
|
*/
|
||||||
plugins.AutoflowView = AutoflowPlugin;
|
plugins.AutoflowView = AutoflowPlugin;
|
||||||
|
|
||||||
plugins.Conductor = TimeConductorPlugin;
|
plugins.Conductor = TimeConductorPlugin.default;
|
||||||
|
|
||||||
plugins.CouchDB = function (url) {
|
plugins.CouchDB = function (url) {
|
||||||
return function (openmct) {
|
return function (openmct) {
|
||||||
@ -159,6 +160,8 @@ define([
|
|||||||
plugins.SummaryWidget = SummaryWidget;
|
plugins.SummaryWidget = SummaryWidget;
|
||||||
plugins.TelemetryMean = TelemetryMean;
|
plugins.TelemetryMean = TelemetryMean;
|
||||||
plugins.URLIndicator = URLIndicatorPlugin;
|
plugins.URLIndicator = URLIndicatorPlugin;
|
||||||
|
plugins.Notebook = Notebook;
|
||||||
|
plugins.FolderView = FolderView;
|
||||||
|
|
||||||
return plugins;
|
return plugins;
|
||||||
});
|
});
|
||||||
|
@ -8,14 +8,15 @@ define([
|
|||||||
objectUtils
|
objectUtils
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
const DEFAULT_VIEW_PRIORITY = 100;
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
function SummaryWidgetViewProvider(openmct) {
|
function SummaryWidgetViewProvider(openmct) {
|
||||||
return {
|
return {
|
||||||
key: 'summary-widget-viewer',
|
key: 'summary-widget-viewer',
|
||||||
name: 'Widget View',
|
name: 'Summary View',
|
||||||
|
cssClass: 'icon-summary-widget',
|
||||||
canView: function (domainObject) {
|
canView: function (domainObject) {
|
||||||
return domainObject.type === 'summary-widget';
|
return domainObject.type === 'summary-widget';
|
||||||
},
|
},
|
||||||
@ -33,7 +34,11 @@ define([
|
|||||||
},
|
},
|
||||||
editable: true,
|
editable: true,
|
||||||
priority: function (domainObject) {
|
priority: function (domainObject) {
|
||||||
return 1;
|
if (domainObject.type === 'summary-widget') {
|
||||||
|
return Number.MAX_VALUE;
|
||||||
|
} else {
|
||||||
|
return DEFAULT_VIEW_PRIORITY;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,87 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define([
|
|
||||||
'lodash',
|
|
||||||
'vue',
|
|
||||||
'./table-configuration.html',
|
|
||||||
'./TelemetryTableConfiguration'
|
|
||||||
],function (
|
|
||||||
_,
|
|
||||||
Vue,
|
|
||||||
TableConfigurationTemplate,
|
|
||||||
TelemetryTableConfiguration
|
|
||||||
) {
|
|
||||||
return function TableConfigurationComponent(domainObject, openmct) {
|
|
||||||
const tableConfiguration = new TelemetryTableConfiguration(domainObject, openmct);
|
|
||||||
let unlisteners = [];
|
|
||||||
|
|
||||||
return new Vue({
|
|
||||||
template: TableConfigurationTemplate,
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
headers: {},
|
|
||||||
configuration: tableConfiguration.getConfiguration()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
updateHeaders(headers) {
|
|
||||||
this.headers = headers;
|
|
||||||
},
|
|
||||||
toggleColumn(key) {
|
|
||||||
let isHidden = this.configuration.hiddenColumns[key] === true;
|
|
||||||
|
|
||||||
this.configuration.hiddenColumns[key] = !isHidden;
|
|
||||||
tableConfiguration.updateConfiguration(this.configuration);
|
|
||||||
},
|
|
||||||
addObject(domainObject) {
|
|
||||||
tableConfiguration.addColumnsForObject(domainObject, true);
|
|
||||||
this.updateHeaders(tableConfiguration.getAllHeaders());
|
|
||||||
},
|
|
||||||
removeObject(objectIdentifier) {
|
|
||||||
tableConfiguration.removeColumnsForObject(objectIdentifier, true);
|
|
||||||
this.updateHeaders(tableConfiguration.getAllHeaders());
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
let compositionCollection = openmct.composition.get(domainObject);
|
|
||||||
|
|
||||||
compositionCollection.load()
|
|
||||||
.then((composition) => {
|
|
||||||
tableConfiguration.addColumnsForAllObjects(composition);
|
|
||||||
this.updateHeaders(tableConfiguration.getAllHeaders());
|
|
||||||
|
|
||||||
compositionCollection.on('add', this.addObject);
|
|
||||||
unlisteners.push(compositionCollection.off.bind(compositionCollection, 'add', this.addObject));
|
|
||||||
|
|
||||||
compositionCollection.on('remove', this.removeObject);
|
|
||||||
unlisteners.push(compositionCollection.off.bind(compositionCollection, 'remove', this.removeObject));
|
|
||||||
});
|
|
||||||
},
|
|
||||||
destroyed() {
|
|
||||||
tableConfiguration.destroy();
|
|
||||||
unlisteners.forEach((unlisten) => unlisten());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
@ -22,11 +22,16 @@
|
|||||||
|
|
||||||
define([
|
define([
|
||||||
'../../api/objects/object-utils',
|
'../../api/objects/object-utils',
|
||||||
'./TableConfigurationComponent'
|
'./components/table-configuration.vue',
|
||||||
|
'./TelemetryTableConfiguration',
|
||||||
|
'vue'
|
||||||
], function (
|
], function (
|
||||||
objectUtils,
|
objectUtils,
|
||||||
TableConfigurationComponent
|
TableConfigurationComponent,
|
||||||
|
TelemetryTableConfiguration,
|
||||||
|
Vue
|
||||||
) {
|
) {
|
||||||
|
|
||||||
function TableConfigurationViewProvider(openmct) {
|
function TableConfigurationViewProvider(openmct) {
|
||||||
let instantiateService;
|
let instantiateService;
|
||||||
|
|
||||||
@ -51,27 +56,38 @@ define([
|
|||||||
return instantiateService(model, id);
|
return instantiateService(model, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
key: 'table-configuration',
|
key: 'table-configuration',
|
||||||
name: 'Telemetry Table Configuration',
|
name: 'Telemetry Table Configuration',
|
||||||
canView: function (selection) {
|
canView: function (selection) {
|
||||||
|
if (selection.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
let object = selection[0].context.item;
|
let object = selection[0].context.item;
|
||||||
|
return object.type === 'table' &&
|
||||||
return selection.length > 0 &&
|
|
||||||
object.type === 'table' &&
|
|
||||||
isBeingEdited(object);
|
isBeingEdited(object);
|
||||||
},
|
},
|
||||||
view: function (selection) {
|
view: function (selection) {
|
||||||
let component;
|
let component;
|
||||||
let domainObject = selection[0].context.item;
|
let domainObject = selection[0].context.item;
|
||||||
|
const tableConfiguration = new TelemetryTableConfiguration(domainObject, openmct);
|
||||||
return {
|
return {
|
||||||
show: function (element) {
|
show: function (element) {
|
||||||
component = TableConfigurationComponent(domainObject, openmct);
|
component = new Vue({
|
||||||
element.appendChild(component.$mount().$el);
|
provide: {
|
||||||
|
openmct,
|
||||||
|
tableConfiguration
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
TableConfiguration: TableConfigurationComponent.default
|
||||||
|
},
|
||||||
|
template: '<table-configuration></table-configuration>',
|
||||||
|
el: element
|
||||||
|
});
|
||||||
},
|
},
|
||||||
destroy: function (element) {
|
destroy: function (element) {
|
||||||
component.$destroy();
|
component.$destroy();
|
||||||
element.removeChild(component.$el);
|
|
||||||
component = undefined;
|
component = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,12 +36,12 @@ define([
|
|||||||
TelemetryTableConfiguration
|
TelemetryTableConfiguration
|
||||||
) {
|
) {
|
||||||
class TelemetryTable extends EventEmitter {
|
class TelemetryTable extends EventEmitter {
|
||||||
constructor(domainObject, rowCount, openmct) {
|
constructor(domainObject, openmct) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.domainObject = domainObject;
|
this.domainObject = domainObject;
|
||||||
this.openmct = openmct;
|
this.openmct = openmct;
|
||||||
this.rowCount = rowCount;
|
this.rowCount = 100;
|
||||||
this.subscriptions = {};
|
this.subscriptions = {};
|
||||||
this.tableComposition = undefined;
|
this.tableComposition = undefined;
|
||||||
this.telemetryObjects = [];
|
this.telemetryObjects = [];
|
||||||
|
@ -48,7 +48,12 @@ define(function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getFormattedValue(telemetryDatum) {
|
getFormattedValue(telemetryDatum) {
|
||||||
return this.formatter.format(telemetryDatum);
|
let formattedValue = this.formatter.format(telemetryDatum);
|
||||||
|
if (typeof formattedValue !== 'string') {
|
||||||
|
return formattedValue.toString();
|
||||||
|
} else {
|
||||||
|
return formattedValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -1,315 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define([
|
|
||||||
'lodash',
|
|
||||||
'vue',
|
|
||||||
'./telemetry-table.html',
|
|
||||||
'./TelemetryTable',
|
|
||||||
'./TelemetryTableRowComponent',
|
|
||||||
'../../exporters/CSVExporter'
|
|
||||||
],function (
|
|
||||||
_,
|
|
||||||
Vue,
|
|
||||||
TelemetryTableTemplate,
|
|
||||||
TelemetryTable,
|
|
||||||
TelemetryTableRowComponent,
|
|
||||||
CSVExporter
|
|
||||||
) {
|
|
||||||
const VISIBLE_ROW_COUNT = 100;
|
|
||||||
const ROW_HEIGHT = 17;
|
|
||||||
const RESIZE_POLL_INTERVAL = 200;
|
|
||||||
const AUTO_SCROLL_TRIGGER_HEIGHT = 20;
|
|
||||||
|
|
||||||
return function TelemetryTableComponent(domainObject, openmct) {
|
|
||||||
const csvExporter = new CSVExporter();
|
|
||||||
const table = new TelemetryTable(domainObject, VISIBLE_ROW_COUNT, openmct);
|
|
||||||
let processingScroll = false;
|
|
||||||
let updatingView = false;
|
|
||||||
|
|
||||||
return new Vue({
|
|
||||||
template: TelemetryTableTemplate,
|
|
||||||
components: {
|
|
||||||
'telemetry-table-row': TelemetryTableRowComponent
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
headers: {},
|
|
||||||
configuration: table.configuration.getConfiguration(),
|
|
||||||
headersCount: 0,
|
|
||||||
visibleRows: [],
|
|
||||||
columnWidths: [],
|
|
||||||
sizingRows: {},
|
|
||||||
rowHeight: ROW_HEIGHT,
|
|
||||||
scrollOffset: 0,
|
|
||||||
totalHeight: 0,
|
|
||||||
totalWidth: 0,
|
|
||||||
rowOffset: 0,
|
|
||||||
autoScroll: true,
|
|
||||||
sortOptions: {},
|
|
||||||
filters: {},
|
|
||||||
loading: false,
|
|
||||||
scrollable: undefined,
|
|
||||||
tableEl: undefined,
|
|
||||||
headersHolderEl: undefined,
|
|
||||||
calcTableWidth: '100%'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
updateVisibleRows() {
|
|
||||||
|
|
||||||
let start = 0;
|
|
||||||
let end = VISIBLE_ROW_COUNT;
|
|
||||||
let filteredRows = table.filteredRows.getRows();
|
|
||||||
let filteredRowsLength = filteredRows.length;
|
|
||||||
|
|
||||||
this.totalHeight = this.rowHeight * filteredRowsLength - 1;
|
|
||||||
|
|
||||||
if (filteredRowsLength < VISIBLE_ROW_COUNT) {
|
|
||||||
end = filteredRowsLength;
|
|
||||||
} else {
|
|
||||||
let firstVisible = this.calculateFirstVisibleRow();
|
|
||||||
let lastVisible = this.calculateLastVisibleRow();
|
|
||||||
let totalVisible = lastVisible - firstVisible;
|
|
||||||
|
|
||||||
let numberOffscreen = VISIBLE_ROW_COUNT - totalVisible;
|
|
||||||
start = firstVisible - Math.floor(numberOffscreen / 2);
|
|
||||||
end = lastVisible + Math.ceil(numberOffscreen / 2);
|
|
||||||
|
|
||||||
if (start < 0) {
|
|
||||||
start = 0;
|
|
||||||
end = Math.min(VISIBLE_ROW_COUNT, filteredRowsLength);
|
|
||||||
} else if (end >= filteredRowsLength) {
|
|
||||||
end = filteredRowsLength;
|
|
||||||
start = end - VISIBLE_ROW_COUNT + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.rowOffset = start;
|
|
||||||
this.visibleRows = filteredRows.slice(start, end);
|
|
||||||
},
|
|
||||||
calculateFirstVisibleRow() {
|
|
||||||
return Math.floor(this.scrollable.scrollTop / this.rowHeight);
|
|
||||||
},
|
|
||||||
calculateLastVisibleRow() {
|
|
||||||
let bottomScroll = this.scrollable.scrollTop + this.scrollable.offsetHeight;
|
|
||||||
return Math.floor(bottomScroll / this.rowHeight);
|
|
||||||
},
|
|
||||||
updateHeaders() {
|
|
||||||
let headers = table.configuration.getVisibleHeaders();
|
|
||||||
|
|
||||||
this.headers = headers;
|
|
||||||
this.headersCount = Object.values(headers).length;
|
|
||||||
Vue.nextTick().then(this.calculateColumnWidths);
|
|
||||||
},
|
|
||||||
setSizingTableWidth() {
|
|
||||||
let scrollW = this.scrollable.offsetWidth - this.scrollable.clientWidth;
|
|
||||||
|
|
||||||
if (scrollW && scrollW > 0) {
|
|
||||||
this.calcTableWidth = 'calc(100% - ' + scrollW + 'px)';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
calculateColumnWidths() {
|
|
||||||
let columnWidths = [];
|
|
||||||
let totalWidth = 0;
|
|
||||||
let sizingRowEl = this.sizingTable.children[0];
|
|
||||||
let sizingCells = Array.from(sizingRowEl.children);
|
|
||||||
|
|
||||||
sizingCells.forEach((cell) => {
|
|
||||||
let columnWidth = cell.offsetWidth;
|
|
||||||
columnWidths.push(columnWidth + 'px');
|
|
||||||
totalWidth += columnWidth;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.columnWidths = columnWidths;
|
|
||||||
this.totalWidth = totalWidth;
|
|
||||||
},
|
|
||||||
sortBy(columnKey) {
|
|
||||||
// If sorting by the same column, flip the sort direction.
|
|
||||||
if (this.sortOptions.key === columnKey) {
|
|
||||||
if (this.sortOptions.direction === 'asc') {
|
|
||||||
this.sortOptions.direction = 'desc';
|
|
||||||
} else {
|
|
||||||
this.sortOptions.direction = 'asc';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.sortOptions = {
|
|
||||||
key: columnKey,
|
|
||||||
direction: 'asc'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
table.filteredRows.sortBy(this.sortOptions);
|
|
||||||
},
|
|
||||||
scroll() {
|
|
||||||
if (!processingScroll) {
|
|
||||||
processingScroll = true;
|
|
||||||
requestAnimationFrame(()=> {
|
|
||||||
this.updateVisibleRows();
|
|
||||||
this.synchronizeScrollX();
|
|
||||||
|
|
||||||
if (this.shouldSnapToBottom()) {
|
|
||||||
this.autoScroll = true;
|
|
||||||
} else {
|
|
||||||
// If user scrolls away from bottom, disable auto-scroll.
|
|
||||||
// Auto-scroll will be re-enabled if user scrolls to bottom again.
|
|
||||||
this.autoScroll = false;
|
|
||||||
}
|
|
||||||
processingScroll = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
shouldSnapToBottom() {
|
|
||||||
return this.scrollable.scrollTop >= (this.scrollable.scrollHeight - this.scrollable.offsetHeight - AUTO_SCROLL_TRIGGER_HEIGHT);
|
|
||||||
},
|
|
||||||
scrollToBottom() {
|
|
||||||
this.scrollable.scrollTop = this.scrollable.scrollHeight;
|
|
||||||
},
|
|
||||||
synchronizeScrollX() {
|
|
||||||
this.headersHolderEl.scrollLeft = this.scrollable.scrollLeft;
|
|
||||||
},
|
|
||||||
filterChanged(columnKey) {
|
|
||||||
table.filteredRows.setColumnFilter(columnKey, this.filters[columnKey]);
|
|
||||||
},
|
|
||||||
clearFilter(columnKey) {
|
|
||||||
this.filters[columnKey] = '';
|
|
||||||
table.filteredRows.setColumnFilter(columnKey, '');
|
|
||||||
},
|
|
||||||
rowsAdded(rows) {
|
|
||||||
let sizingRow;
|
|
||||||
if (Array.isArray(rows)) {
|
|
||||||
sizingRow = rows[0];
|
|
||||||
} else {
|
|
||||||
sizingRow = rows;
|
|
||||||
}
|
|
||||||
if (!this.sizingRows[sizingRow.objectKeyString]) {
|
|
||||||
this.sizingRows[sizingRow.objectKeyString] = sizingRow;
|
|
||||||
Vue.nextTick().then(this.calculateColumnWidths);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!updatingView) {
|
|
||||||
updatingView = true;
|
|
||||||
requestAnimationFrame(()=> {
|
|
||||||
this.updateVisibleRows();
|
|
||||||
if (this.autoScroll) {
|
|
||||||
Vue.nextTick().then(this.scrollToBottom);
|
|
||||||
}
|
|
||||||
updatingView = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
rowsRemoved(rows) {
|
|
||||||
if (!updatingView) {
|
|
||||||
updatingView = true;
|
|
||||||
requestAnimationFrame(()=> {
|
|
||||||
this.updateVisibleRows();
|
|
||||||
updatingView = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
exportAsCSV() {
|
|
||||||
const justTheData = table.filteredRows.getRows()
|
|
||||||
.map(row => row.getFormattedDatum());
|
|
||||||
const headers = Object.keys(this.headers);
|
|
||||||
csvExporter.export(justTheData, {
|
|
||||||
filename: table.domainObject.name + '.csv',
|
|
||||||
headers: headers
|
|
||||||
});
|
|
||||||
},
|
|
||||||
outstandingRequests(loading) {
|
|
||||||
this.loading = loading;
|
|
||||||
},
|
|
||||||
calculateTableSize() {
|
|
||||||
this.setSizingTableWidth();
|
|
||||||
Vue.nextTick().then(this.calculateColumnWidths);
|
|
||||||
},
|
|
||||||
pollForResize() {
|
|
||||||
let el = this.$el;
|
|
||||||
let width = el.clientWidth;
|
|
||||||
let height = el.clientHeight;
|
|
||||||
|
|
||||||
this.resizePollHandle = setInterval(() => {
|
|
||||||
if (el.clientWidth !== width || el.clientHeight !== height) {
|
|
||||||
this.calculateTableSize();
|
|
||||||
width = el.clientWidth;
|
|
||||||
height = el.clientHeight;
|
|
||||||
}
|
|
||||||
}, RESIZE_POLL_INTERVAL);
|
|
||||||
},
|
|
||||||
updateConfiguration(configuration) {
|
|
||||||
this.configuration = configuration;
|
|
||||||
this.updateHeaders();
|
|
||||||
},
|
|
||||||
addObject() {
|
|
||||||
this.updateHeaders();
|
|
||||||
},
|
|
||||||
removeObject(objectIdentifier) {
|
|
||||||
let objectKeyString = openmct.objects.makeKeyString(objectIdentifier);
|
|
||||||
delete this.sizingRows[objectKeyString];
|
|
||||||
this.updateHeaders();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.filterChanged = _.debounce(this.filterChanged, 500);
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
table.on('object-added', this.addObject);
|
|
||||||
table.on('object-removed', this.removeObject);
|
|
||||||
table.on('outstanding-requests', this.outstandingRequests);
|
|
||||||
|
|
||||||
table.filteredRows.on('add', this.rowsAdded);
|
|
||||||
table.filteredRows.on('remove', this.rowsRemoved);
|
|
||||||
table.filteredRows.on('sort', this.updateVisibleRows);
|
|
||||||
table.filteredRows.on('filter', this.updateVisibleRows);
|
|
||||||
|
|
||||||
//Default sort
|
|
||||||
this.sortOptions = table.filteredRows.sortBy();
|
|
||||||
this.scrollable = this.$el.querySelector('.t-scrolling');
|
|
||||||
this.sizingTable = this.$el.querySelector('.js-sizing-table');
|
|
||||||
this.headersHolderEl = this.$el.querySelector('.mct-table-headers-w');
|
|
||||||
|
|
||||||
table.configuration.on('change', this.updateConfiguration);
|
|
||||||
|
|
||||||
this.calculateTableSize();
|
|
||||||
this.pollForResize();
|
|
||||||
|
|
||||||
table.initialize();
|
|
||||||
},
|
|
||||||
destroyed() {
|
|
||||||
table.off('object-added', this.addObject);
|
|
||||||
table.off('object-removed', this.removeObject);
|
|
||||||
table.off('outstanding-requests', this.outstandingRequests);
|
|
||||||
|
|
||||||
table.filteredRows.off('add', this.rowsAdded);
|
|
||||||
table.filteredRows.off('remove', this.rowsRemoved);
|
|
||||||
table.filteredRows.off('sort', this.updateVisibleRows);
|
|
||||||
table.filteredRows.off('filter', this.updateVisibleRows);
|
|
||||||
|
|
||||||
table.configuration.off('change', this.updateConfiguration);
|
|
||||||
|
|
||||||
clearInterval(this.resizePollHandle);
|
|
||||||
|
|
||||||
table.configuration.destroy();
|
|
||||||
|
|
||||||
table.destroy();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
@ -30,17 +30,16 @@ define([], function () {
|
|||||||
this.objectKeyString = objectKeyString;
|
this.objectKeyString = objectKeyString;
|
||||||
}
|
}
|
||||||
|
|
||||||
getFormattedDatum() {
|
getFormattedDatum(headers) {
|
||||||
return Object.values(this.columns)
|
return Object.keys(headers).reduce((formattedDatum, columnKey) => {
|
||||||
.reduce((formattedDatum, column) => {
|
formattedDatum[columnKey] = this.getFormattedValue(columnKey);
|
||||||
formattedDatum[column.getKey()] = this.getFormattedValue(column.getKey());
|
|
||||||
return formattedDatum;
|
return formattedDatum;
|
||||||
}, {});
|
}, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
getFormattedValue(key) {
|
getFormattedValue(key) {
|
||||||
let column = this.columns[key];
|
let column = this.columns[key];
|
||||||
return column.getFormattedValue(this.datum[key]);
|
return column && column.getFormattedValue(this.datum[key]);
|
||||||
}
|
}
|
||||||
|
|
||||||
getRowLimitClass() {
|
getRowLimitClass() {
|
||||||
|
@ -1,90 +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([
|
|
||||||
'./telemetry-table-row.html',
|
|
||||||
],function (
|
|
||||||
TelemetryTableRowTemplate
|
|
||||||
) {
|
|
||||||
return {
|
|
||||||
template: TelemetryTableRowTemplate,
|
|
||||||
data: function () {
|
|
||||||
return {
|
|
||||||
rowTop: (this.rowOffset + this.rowIndex) * this.rowHeight + 'px',
|
|
||||||
formattedRow: this.row.getFormattedDatum(),
|
|
||||||
rowLimitClass: this.row.getRowLimitClass(),
|
|
||||||
cellLimitClasses: this.row.getCellLimitClasses()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
headers: {
|
|
||||||
type: Object,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
row: {
|
|
||||||
type: Object,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
columnWidths: {
|
|
||||||
type: Array,
|
|
||||||
required: false,
|
|
||||||
default: [],
|
|
||||||
},
|
|
||||||
rowIndex: {
|
|
||||||
type: Number,
|
|
||||||
required: false,
|
|
||||||
default: undefined
|
|
||||||
},
|
|
||||||
rowOffset: {
|
|
||||||
type: Number,
|
|
||||||
required: false,
|
|
||||||
default: 0
|
|
||||||
},
|
|
||||||
rowHeight: {
|
|
||||||
type: Number,
|
|
||||||
required: false,
|
|
||||||
default: 0
|
|
||||||
},
|
|
||||||
configuration: {
|
|
||||||
type: Object,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
calculateRowTop: function (rowOffset) {
|
|
||||||
this.rowTop = (rowOffset + this.rowIndex) * this.rowHeight + 'px';
|
|
||||||
},
|
|
||||||
formatRow: function (row) {
|
|
||||||
this.formattedRow = row.getFormattedDatum();
|
|
||||||
this.rowLimitClass = row.getRowLimitClass();
|
|
||||||
this.cellLimitClasses = row.getCellLimitClasses();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
rowOffset: 'calculateRowTop',
|
|
||||||
row: {
|
|
||||||
handler: 'formatRow',
|
|
||||||
deep: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
@ -20,25 +20,49 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
define(['./TelemetryTableComponent'], function (TelemetryTableComponent) {
|
define([
|
||||||
|
'./components/table.vue',
|
||||||
|
'../../exporters/CSVExporter',
|
||||||
|
'./TelemetryTable',
|
||||||
|
'vue'
|
||||||
|
], function (
|
||||||
|
TableComponent,
|
||||||
|
CSVExporter,
|
||||||
|
TelemetryTable,
|
||||||
|
Vue
|
||||||
|
) {
|
||||||
function TelemetryTableViewProvider(openmct) {
|
function TelemetryTableViewProvider(openmct) {
|
||||||
return {
|
return {
|
||||||
key: 'table',
|
key: 'table',
|
||||||
name: 'Telemetry Table',
|
name: 'Telemetry Table',
|
||||||
editable: true,
|
cssClass: 'icon-tabular-realtime',
|
||||||
|
editable: function(domainObject) {
|
||||||
|
return domainObject.type === 'table';
|
||||||
|
},
|
||||||
canView: function (domainObject) {
|
canView: function (domainObject) {
|
||||||
return domainObject.type === 'table' || domainObject.hasOwnProperty('telemetry');
|
return domainObject.type === 'table' || domainObject.hasOwnProperty('telemetry');
|
||||||
},
|
},
|
||||||
view: function (domainObject) {
|
view: function (domainObject) {
|
||||||
|
let csvExporter = new CSVExporter.default();
|
||||||
|
let table = new TelemetryTable(domainObject, openmct);
|
||||||
let component;
|
let component;
|
||||||
return {
|
return {
|
||||||
show: function (element) {
|
show: function (element) {
|
||||||
component = new TelemetryTableComponent(domainObject, openmct);
|
component = new Vue({
|
||||||
element.appendChild(component.$mount().$el);
|
components: {
|
||||||
|
TableComponent: TableComponent.default,
|
||||||
|
},
|
||||||
|
provide: {
|
||||||
|
openmct,
|
||||||
|
csvExporter,
|
||||||
|
table
|
||||||
|
},
|
||||||
|
el: element,
|
||||||
|
template: '<table-component></table-component>'
|
||||||
|
});
|
||||||
},
|
},
|
||||||
destroy: function (element) {
|
destroy: function (element) {
|
||||||
component.$destroy();
|
component.$destroy();
|
||||||
element.removeChild(component.$el);
|
|
||||||
component = undefined;
|
component = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -82,8 +82,7 @@ define(
|
|||||||
// Going to check for duplicates. Bound the search problem to
|
// Going to check for duplicates. Bound the search problem to
|
||||||
// items around the given time. Use sortedIndex because it
|
// items around the given time. Use sortedIndex because it
|
||||||
// employs a binary search which is O(log n). Can use binary search
|
// employs a binary search which is O(log n). Can use binary search
|
||||||
// based on time stamp because the array is guaranteed ordered due
|
// because the array is guaranteed ordered due to sorted insertion.
|
||||||
// to sorted insertion.
|
|
||||||
let startIx = this.sortedIndex(this.rows, row);
|
let startIx = this.sortedIndex(this.rows, row);
|
||||||
let endIx = undefined;
|
let endIx = undefined;
|
||||||
|
|
||||||
@ -113,15 +112,37 @@ define(
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
sortedIndex(rows, testRow, lodashFunction) {
|
sortedIndex(rows, testRow, lodashFunction) {
|
||||||
|
if (this.rows.length === 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
const sortOptionsKey = this.sortOptions.key;
|
const sortOptionsKey = this.sortOptions.key;
|
||||||
|
const testRowValue = testRow.datum[sortOptionsKey];
|
||||||
|
const firstValue = this.rows[0].datum[sortOptionsKey];
|
||||||
|
const lastValue = this.rows[this.rows.length - 1].datum[sortOptionsKey];
|
||||||
|
|
||||||
lodashFunction = lodashFunction || _.sortedIndex;
|
lodashFunction = lodashFunction || _.sortedIndex;
|
||||||
|
|
||||||
if (this.sortOptions.direction === 'asc') {
|
if (this.sortOptions.direction === 'asc') {
|
||||||
|
if (testRowValue > lastValue) {
|
||||||
|
return this.rows.length;
|
||||||
|
} else if (testRowValue === lastValue) {
|
||||||
|
return this.rows.length - 1;
|
||||||
|
} else if (testRowValue <= firstValue) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
return lodashFunction(rows, testRow, (thisRow) => {
|
return lodashFunction(rows, testRow, (thisRow) => {
|
||||||
return thisRow.datum[sortOptionsKey];
|
return thisRow.datum[sortOptionsKey];
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (testRowValue >= firstValue) {
|
||||||
|
return 0;
|
||||||
|
} else if (testRowValue < lastValue) {
|
||||||
|
return this.rows.length;
|
||||||
|
} else if (testRowValue === lastValue) {
|
||||||
|
return this.rows.length - 1;
|
||||||
} else {
|
} else {
|
||||||
const testRowValue = testRow.datum[this.sortOptions.key];
|
|
||||||
// Use a custom comparison function to support descending sort.
|
// Use a custom comparison function to support descending sort.
|
||||||
return lodashFunction(rows, testRow, (thisRow) => {
|
return lodashFunction(rows, testRow, (thisRow) => {
|
||||||
const thisRowValue = thisRow.datum[sortOptionsKey];
|
const thisRowValue = thisRow.datum[sortOptionsKey];
|
||||||
@ -135,6 +156,7 @@ define(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sorts the telemetry collection based on the provided sort field
|
* Sorts the telemetry collection based on the provided sort field
|
||||||
|
@ -0,0 +1,68 @@
|
|||||||
|
<template>
|
||||||
|
<div class="grid-properties">
|
||||||
|
<!--form class="form" -->
|
||||||
|
<ul class="l-inspector-part">
|
||||||
|
<h2>Table Columns</h2>
|
||||||
|
<li class="grid-row" v-for="(title, key) in headers">
|
||||||
|
<div class="grid-cell label" title="Show or Hide Column"><label :for="key + 'ColumnControl'">{{title}}</label></div>
|
||||||
|
<div class="grid-cell value"><input type="checkbox" :id="key + 'ColumnControl'" :checked="configuration.hiddenColumns[key] !== true" @change="toggleColumn(key)"></div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<!--/form -->
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
inject: ['tableConfiguration', 'openmct'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
headers: {},
|
||||||
|
configuration: this.tableConfiguration.getConfiguration()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateHeaders(headers) {
|
||||||
|
this.headers = headers;
|
||||||
|
},
|
||||||
|
toggleColumn(key) {
|
||||||
|
let isHidden = this.configuration.hiddenColumns[key] === true;
|
||||||
|
|
||||||
|
this.configuration.hiddenColumns[key] = !isHidden;
|
||||||
|
this.tableConfiguration.updateConfiguration(this.configuration);
|
||||||
|
},
|
||||||
|
addObject(domainObject) {
|
||||||
|
this.tableConfiguration.addColumnsForObject(domainObject, true);
|
||||||
|
this.updateHeaders(this.tableConfiguration.getAllHeaders());
|
||||||
|
},
|
||||||
|
removeObject(objectIdentifier) {
|
||||||
|
this.tableConfiguration.removeColumnsForObject(objectIdentifier, true);
|
||||||
|
this.updateHeaders(this.tableConfiguration.getAllHeaders());
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.unlisteners = [];
|
||||||
|
let compositionCollection = this.openmct.composition.get(this.tableConfiguration.domainObject);
|
||||||
|
|
||||||
|
compositionCollection.load()
|
||||||
|
.then((composition) => {
|
||||||
|
this.tableConfiguration.addColumnsForAllObjects(composition);
|
||||||
|
this.updateHeaders(this.tableConfiguration.getAllHeaders());
|
||||||
|
|
||||||
|
compositionCollection.on('add', this.addObject);
|
||||||
|
this.unlisteners.push(compositionCollection.off.bind(compositionCollection, 'add', this.addObject));
|
||||||
|
|
||||||
|
compositionCollection.on('remove', this.removeObject);
|
||||||
|
this.unlisteners.push(compositionCollection.off.bind(compositionCollection, 'remove', this.removeObject));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
destroyed() {
|
||||||
|
this.tableConfiguration.destroy();
|
||||||
|
this.unlisteners.forEach((unlisten) => unlisten());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
74
src/plugins/telemetryTable/components/table-row.vue
Normal file
74
src/plugins/telemetryTable/components/table-row.vue
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
<template>
|
||||||
|
<tr :style="{ top: rowTop }" :class="rowLimitClass">
|
||||||
|
<td v-for="(title, key, headerIndex) in headers"
|
||||||
|
:style="{ width: columnWidths[headerIndex], 'max-width': columnWidths[headerIndex]}"
|
||||||
|
:title="formattedRow[key]"
|
||||||
|
:class="cellLimitClasses[key]">{{formattedRow[key]}}</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
rowTop: (this.rowOffset + this.rowIndex) * this.rowHeight + 'px',
|
||||||
|
formattedRow: this.row.getFormattedDatum(this.headers),
|
||||||
|
rowLimitClass: this.row.getRowLimitClass(),
|
||||||
|
cellLimitClasses: this.row.getCellLimitClasses()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
headers: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
row: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
columnWidths: {
|
||||||
|
type: Array,
|
||||||
|
required: false,
|
||||||
|
default() {
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rowIndex: {
|
||||||
|
type: Number,
|
||||||
|
required: false,
|
||||||
|
default: undefined
|
||||||
|
},
|
||||||
|
rowOffset: {
|
||||||
|
type: Number,
|
||||||
|
required: false,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
rowHeight: {
|
||||||
|
type: Number,
|
||||||
|
required: false,
|
||||||
|
default: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
calculateRowTop: function (rowOffset) {
|
||||||
|
this.rowTop = (rowOffset + this.rowIndex) * this.rowHeight + 'px';
|
||||||
|
},
|
||||||
|
formatRow: function (row) {
|
||||||
|
this.formattedRow = row.getFormattedDatum(this.headers);
|
||||||
|
this.rowLimitClass = row.getRowLimitClass();
|
||||||
|
this.cellLimitClasses = row.getCellLimitClasses();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// TODO: use computed properties
|
||||||
|
watch: {
|
||||||
|
rowOffset: 'calculateRowTop',
|
||||||
|
row: {
|
||||||
|
handler: 'formatRow',
|
||||||
|
deep: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
454
src/plugins/telemetryTable/components/table.vue
Normal file
454
src/plugins/telemetryTable/components/table.vue
Normal file
@ -0,0 +1,454 @@
|
|||||||
|
<template>
|
||||||
|
<div class="c-table c-telemetry-table c-table--filterable c-table--sortable has-control-bar"
|
||||||
|
:class="{'loading': loading}">
|
||||||
|
<div class="c-table__control-bar c-control-bar">
|
||||||
|
<a class="s-button t-export icon-download labeled"
|
||||||
|
v-on:click="exportAsCSV()"
|
||||||
|
title="Export This View's Data">
|
||||||
|
Export As CSV
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<!-- Headers table -->
|
||||||
|
<div class="c-telemetry-table__headers-w js-table__headers-w">
|
||||||
|
<table class="c-table__headers c-telemetry-table__headers"
|
||||||
|
:style="{ 'max-width': totalWidth + 'px'}">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th v-for="(title, key, headerIndex) in headers"
|
||||||
|
v-on:click="sortBy(key)"
|
||||||
|
:class="['is-sortable', sortOptions.key === key ? 'is-sorting' : '', sortOptions.direction].join(' ')"
|
||||||
|
:style="{ width: columnWidths[headerIndex], 'max-width': columnWidths[headerIndex]}">{{title}}</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th v-for="(title, key, headerIndex) in headers"
|
||||||
|
:style="{
|
||||||
|
width: columnWidths[headerIndex],
|
||||||
|
'max-width': columnWidths[headerIndex],
|
||||||
|
}">
|
||||||
|
<search class="c-table__search"
|
||||||
|
v-model="filters[key]"
|
||||||
|
v-on:input="filterChanged(key)"
|
||||||
|
v-on:clear="clearFilter(key)" />
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!-- Content table -->
|
||||||
|
<div class="c-table__body-w c-telemetry-table__body-w js-telemetry-table__body-w" @scroll="scroll">
|
||||||
|
<div class="c-telemetry-table__scroll-forcer" :style="{ width: totalWidth }"></div>
|
||||||
|
<table class="c-table__body c-telemetry-table__body"
|
||||||
|
:style="{ height: totalHeight + 'px', 'max-width': totalWidth + 'px'}">
|
||||||
|
<tbody>
|
||||||
|
<telemetry-table-row v-for="(row, rowIndex) in visibleRows"
|
||||||
|
:headers="headers"
|
||||||
|
:columnWidths="columnWidths"
|
||||||
|
:rowIndex="rowIndex"
|
||||||
|
:rowOffset="rowOffset"
|
||||||
|
:rowHeight="rowHeight"
|
||||||
|
:row="row"
|
||||||
|
>
|
||||||
|
</telemetry-table-row>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!-- Sizing table -->
|
||||||
|
<table class="c-telemetry-table__sizing js-telemetry-table__sizing"
|
||||||
|
:style="{width: calcTableWidth}">
|
||||||
|
<tr>
|
||||||
|
<th v-for="(title, key, headerIndex) in headers">{{title}}</th>
|
||||||
|
</tr>
|
||||||
|
<telemetry-table-row v-for="(sizingRowData, objectKeyString) in sizingRows"
|
||||||
|
:headers="headers"
|
||||||
|
:row="sizingRowData">
|
||||||
|
</telemetry-table-row>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import "~styles/sass-base";
|
||||||
|
@import "~styles/table";
|
||||||
|
|
||||||
|
.c-telemetry-table {
|
||||||
|
// Table that displays telemetry in a scrolling body area
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
th, td {
|
||||||
|
display: block;
|
||||||
|
flex: 1 0 auto;
|
||||||
|
vertical-align: middle; // This is crucial to hiding f**king 4px height injected by browser by default
|
||||||
|
}
|
||||||
|
|
||||||
|
/******************************* WRAPPERS */
|
||||||
|
&__headers-w {
|
||||||
|
// Wraps __headers table
|
||||||
|
flex: 0 0 auto;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/******************************* TABLES */
|
||||||
|
&__headers,
|
||||||
|
&__body {
|
||||||
|
tr {
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__headers {
|
||||||
|
// A table
|
||||||
|
thead {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
&:not(:first-child) {
|
||||||
|
border-left: 1px solid $colorTabHeaderBorder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/******************************* ELEMENTS */
|
||||||
|
&__scroll-forcer {
|
||||||
|
// Force horz scroll when needed; width set via JS
|
||||||
|
font-size: 0;
|
||||||
|
height: 1px; // Height 0 won't force scroll properly
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/******************************* WRAPPERS */
|
||||||
|
&__body-w {
|
||||||
|
// Wraps __body table provides scrolling
|
||||||
|
flex: 1 1 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
/******************************* TABLES */
|
||||||
|
&__body {
|
||||||
|
// A table
|
||||||
|
flex: 1 1 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
|
||||||
|
tr {
|
||||||
|
display: flex; // flex-flow defaults to row nowrap (which is what we want) so no need to define
|
||||||
|
align-items: stretch;
|
||||||
|
position: absolute;
|
||||||
|
height: 18px; // Needed when a row has empty values in its cells
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__sizing {
|
||||||
|
// A table
|
||||||
|
display: table;
|
||||||
|
z-index: -1;
|
||||||
|
visibility: hidden;
|
||||||
|
pointer-events: none;
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
//Add some padding to allow for decorations such as limits indicator
|
||||||
|
tr {
|
||||||
|
display: table-row;
|
||||||
|
}
|
||||||
|
|
||||||
|
th, td {
|
||||||
|
display: table-cell;
|
||||||
|
padding-right: 10px;
|
||||||
|
padding-left: 10px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/******************************* LEGACY */
|
||||||
|
.s-status-taking-snapshot,
|
||||||
|
.overlay.snapshot {
|
||||||
|
// Handle overflow-y issues with tables and html2canvas
|
||||||
|
// Replaces .l-sticky-headers .l-tabular-body { overflow: auto; }
|
||||||
|
.c-table__body-w { overflow: auto; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import TelemetryTableRow from './table-row.vue';
|
||||||
|
import search from '../../../ui/components/controls/search.vue';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
const VISIBLE_ROW_COUNT = 100;
|
||||||
|
const ROW_HEIGHT = 17;
|
||||||
|
const RESIZE_POLL_INTERVAL = 200;
|
||||||
|
const AUTO_SCROLL_TRIGGER_HEIGHT = 20;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
TelemetryTableRow,
|
||||||
|
search
|
||||||
|
},
|
||||||
|
inject: ['table', 'openmct', 'csvExporter'],
|
||||||
|
props: ['configuration'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
headers: {},
|
||||||
|
visibleRows: [],
|
||||||
|
columnWidths: [],
|
||||||
|
sizingRows: {},
|
||||||
|
rowHeight: ROW_HEIGHT,
|
||||||
|
scrollOffset: 0,
|
||||||
|
totalHeight: 0,
|
||||||
|
totalWidth: 0,
|
||||||
|
rowOffset: 0,
|
||||||
|
autoScroll: true,
|
||||||
|
sortOptions: {},
|
||||||
|
filters: {},
|
||||||
|
loading: false,
|
||||||
|
scrollable: undefined,
|
||||||
|
tableEl: undefined,
|
||||||
|
headersHolderEl: undefined,
|
||||||
|
calcTableWidth: '100%',
|
||||||
|
processingScroll: false,
|
||||||
|
updatingView: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateVisibleRows() {
|
||||||
|
|
||||||
|
let start = 0;
|
||||||
|
let end = VISIBLE_ROW_COUNT;
|
||||||
|
let filteredRows = this.table.filteredRows.getRows();
|
||||||
|
let filteredRowsLength = filteredRows.length;
|
||||||
|
|
||||||
|
this.totalHeight = this.rowHeight * filteredRowsLength - 1;
|
||||||
|
|
||||||
|
if (filteredRowsLength < VISIBLE_ROW_COUNT) {
|
||||||
|
end = filteredRowsLength;
|
||||||
|
} else {
|
||||||
|
let firstVisible = this.calculateFirstVisibleRow();
|
||||||
|
let lastVisible = this.calculateLastVisibleRow();
|
||||||
|
let totalVisible = lastVisible - firstVisible;
|
||||||
|
|
||||||
|
let numberOffscreen = VISIBLE_ROW_COUNT - totalVisible;
|
||||||
|
start = firstVisible - Math.floor(numberOffscreen / 2);
|
||||||
|
end = lastVisible + Math.ceil(numberOffscreen / 2);
|
||||||
|
|
||||||
|
if (start < 0) {
|
||||||
|
start = 0;
|
||||||
|
end = Math.min(VISIBLE_ROW_COUNT, filteredRowsLength);
|
||||||
|
} else if (end >= filteredRowsLength) {
|
||||||
|
end = filteredRowsLength;
|
||||||
|
start = end - VISIBLE_ROW_COUNT + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.rowOffset = start;
|
||||||
|
this.visibleRows = filteredRows.slice(start, end);
|
||||||
|
},
|
||||||
|
calculateFirstVisibleRow() {
|
||||||
|
return Math.floor(this.scrollable.scrollTop / this.rowHeight);
|
||||||
|
},
|
||||||
|
calculateLastVisibleRow() {
|
||||||
|
let bottomScroll = this.scrollable.scrollTop + this.scrollable.offsetHeight;
|
||||||
|
return Math.floor(bottomScroll / this.rowHeight);
|
||||||
|
},
|
||||||
|
updateHeaders() {
|
||||||
|
let headers = this.table.configuration.getVisibleHeaders();
|
||||||
|
|
||||||
|
this.headers = headers;
|
||||||
|
this.$nextTick().then(this.calculateColumnWidths);
|
||||||
|
},
|
||||||
|
setSizingTableWidth() {
|
||||||
|
let scrollW = this.scrollable.offsetWidth - this.scrollable.clientWidth;
|
||||||
|
|
||||||
|
if (scrollW && scrollW > 0) {
|
||||||
|
this.calcTableWidth = 'calc(100% - ' + scrollW + 'px)';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
calculateColumnWidths() {
|
||||||
|
let columnWidths = [];
|
||||||
|
let totalWidth = 0;
|
||||||
|
let sizingRowEl = this.sizingTable.children[0];
|
||||||
|
let sizingCells = Array.from(sizingRowEl.children);
|
||||||
|
|
||||||
|
sizingCells.forEach((cell) => {
|
||||||
|
let columnWidth = cell.offsetWidth;
|
||||||
|
columnWidths.push(columnWidth + 'px');
|
||||||
|
totalWidth += columnWidth;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.columnWidths = columnWidths;
|
||||||
|
this.totalWidth = totalWidth;
|
||||||
|
},
|
||||||
|
sortBy(columnKey) {
|
||||||
|
// If sorting by the same column, flip the sort direction.
|
||||||
|
if (this.sortOptions.key === columnKey) {
|
||||||
|
if (this.sortOptions.direction === 'asc') {
|
||||||
|
this.sortOptions.direction = 'desc';
|
||||||
|
} else {
|
||||||
|
this.sortOptions.direction = 'asc';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.sortOptions = {
|
||||||
|
key: columnKey,
|
||||||
|
direction: 'asc'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.table.filteredRows.sortBy(this.sortOptions);
|
||||||
|
},
|
||||||
|
scroll() {
|
||||||
|
if (!this.processingScroll) {
|
||||||
|
this.processingScroll = true;
|
||||||
|
requestAnimationFrame(()=> {
|
||||||
|
this.updateVisibleRows();
|
||||||
|
this.synchronizeScrollX();
|
||||||
|
|
||||||
|
if (this.shouldSnapToBottom()) {
|
||||||
|
this.autoScroll = true;
|
||||||
|
} else {
|
||||||
|
// If user scrolls away from bottom, disable auto-scroll.
|
||||||
|
// Auto-scroll will be re-enabled if user scrolls to bottom again.
|
||||||
|
this.autoScroll = false;
|
||||||
|
}
|
||||||
|
this.processingScroll = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
shouldSnapToBottom() {
|
||||||
|
return this.scrollable.scrollTop >= (this.scrollable.scrollHeight - this.scrollable.offsetHeight - AUTO_SCROLL_TRIGGER_HEIGHT);
|
||||||
|
},
|
||||||
|
scrollToBottom() {
|
||||||
|
this.scrollable.scrollTop = this.scrollable.scrollHeight;
|
||||||
|
},
|
||||||
|
synchronizeScrollX() {
|
||||||
|
this.headersHolderEl.scrollLeft = this.scrollable.scrollLeft;
|
||||||
|
},
|
||||||
|
filterChanged(columnKey) {
|
||||||
|
this.table.filteredRows.setColumnFilter(columnKey, this.filters[columnKey]);
|
||||||
|
},
|
||||||
|
clearFilter(columnKey) {
|
||||||
|
this.filters[columnKey] = '';
|
||||||
|
this.table.filteredRows.setColumnFilter(columnKey, '');
|
||||||
|
},
|
||||||
|
rowsAdded(rows) {
|
||||||
|
let sizingRow;
|
||||||
|
if (Array.isArray(rows)) {
|
||||||
|
sizingRow = rows[0];
|
||||||
|
} else {
|
||||||
|
sizingRow = rows;
|
||||||
|
}
|
||||||
|
if (!this.sizingRows[sizingRow.objectKeyString]) {
|
||||||
|
this.sizingRows[sizingRow.objectKeyString] = sizingRow;
|
||||||
|
this.$nextTick().then(this.calculateColumnWidths);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.updatingView) {
|
||||||
|
this.updatingView = true;
|
||||||
|
requestAnimationFrame(()=> {
|
||||||
|
this.updateVisibleRows();
|
||||||
|
if (this.autoScroll) {
|
||||||
|
this.$nextTick().then(this.scrollToBottom);
|
||||||
|
}
|
||||||
|
this.updatingView = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rowsRemoved(rows) {
|
||||||
|
if (!this.updatingView) {
|
||||||
|
this.updatingView = true;
|
||||||
|
requestAnimationFrame(()=> {
|
||||||
|
this.updateVisibleRows();
|
||||||
|
this.updatingView = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
exportAsCSV() {
|
||||||
|
const headerKeys = Object.keys(this.headers);
|
||||||
|
const justTheData = this.table.filteredRows.getRows()
|
||||||
|
.map(row => row.getFormattedDatum(this.headers));
|
||||||
|
this.csvExporter.export(justTheData, {
|
||||||
|
filename: this.table.domainObject.name + '.csv',
|
||||||
|
headers: headerKeys
|
||||||
|
});
|
||||||
|
},
|
||||||
|
outstandingRequests(loading) {
|
||||||
|
this.loading = loading;
|
||||||
|
},
|
||||||
|
calculateTableSize() {
|
||||||
|
this.setSizingTableWidth();
|
||||||
|
this.$nextTick().then(this.calculateColumnWidths);
|
||||||
|
},
|
||||||
|
pollForResize() {
|
||||||
|
let el = this.$el;
|
||||||
|
let width = el.clientWidth;
|
||||||
|
let height = el.clientHeight;
|
||||||
|
|
||||||
|
this.resizePollHandle = setInterval(() => {
|
||||||
|
if (el.clientWidth !== width || el.clientHeight !== height) {
|
||||||
|
this.calculateTableSize();
|
||||||
|
width = el.clientWidth;
|
||||||
|
height = el.clientHeight;
|
||||||
|
}
|
||||||
|
}, RESIZE_POLL_INTERVAL);
|
||||||
|
},
|
||||||
|
updateConfiguration(configuration) {
|
||||||
|
this.configuration = configuration;
|
||||||
|
this.updateHeaders();
|
||||||
|
},
|
||||||
|
addObject() {
|
||||||
|
this.updateHeaders();
|
||||||
|
},
|
||||||
|
removeObject(objectIdentifier) {
|
||||||
|
let objectKeyString = this.openmct.objects.makeKeyString(objectIdentifier);
|
||||||
|
delete this.sizingRows[objectKeyString];
|
||||||
|
this.updateHeaders();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.filterChanged = _.debounce(this.filterChanged, 500);
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.table.on('object-added', this.addObject);
|
||||||
|
this.table.on('object-removed', this.removeObject);
|
||||||
|
this.table.on('outstanding-requests', this.outstandingRequests);
|
||||||
|
|
||||||
|
this.table.filteredRows.on('add', this.rowsAdded);
|
||||||
|
this.table.filteredRows.on('remove', this.rowsRemoved);
|
||||||
|
this.table.filteredRows.on('sort', this.updateVisibleRows);
|
||||||
|
this.table.filteredRows.on('filter', this.updateVisibleRows);
|
||||||
|
|
||||||
|
//Default sort
|
||||||
|
this.sortOptions = this.table.filteredRows.sortBy();
|
||||||
|
this.scrollable = this.$el.querySelector('.js-telemetry-table__body-w');
|
||||||
|
this.sizingTable = this.$el.querySelector('.js-telemetry-table__sizing');
|
||||||
|
this.headersHolderEl = this.$el.querySelector('.js-table__headers-w');
|
||||||
|
|
||||||
|
this.table.configuration.on('change', this.updateConfiguration);
|
||||||
|
|
||||||
|
this.calculateTableSize();
|
||||||
|
this.pollForResize();
|
||||||
|
|
||||||
|
this.table.initialize();
|
||||||
|
},
|
||||||
|
destroyed() {
|
||||||
|
this.table.off('object-added', this.addObject);
|
||||||
|
this.table.off('object-removed', this.removeObject);
|
||||||
|
this.table.off('outstanding-requests', this.outstandingRequests);
|
||||||
|
|
||||||
|
this.table.filteredRows.off('add', this.rowsAdded);
|
||||||
|
this.table.filteredRows.off('remove', this.rowsRemoved);
|
||||||
|
this.table.filteredRows.off('sort', this.updateVisibleRows);
|
||||||
|
this.table.filteredRows.off('filter', this.updateVisibleRows);
|
||||||
|
|
||||||
|
this.table.configuration.off('change', this.updateConfiguration);
|
||||||
|
|
||||||
|
clearInterval(this.resizePollHandle);
|
||||||
|
|
||||||
|
this.table.configuration.destroy();
|
||||||
|
|
||||||
|
this.table.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user