Compare commits
82 Commits
plot-telem
...
summary-wi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
28a3ada6af | ||
|
|
ce0f24c86f | ||
|
|
6ca2a755dc | ||
|
|
f6c1488ccd | ||
|
|
26be1ecf37 | ||
|
|
38f0f072bb | ||
|
|
e5e969665f | ||
|
|
ffbb662c99 | ||
|
|
bd7b23f896 | ||
|
|
c238def902 | ||
|
|
2d430ece7f | ||
|
|
c92644a661 | ||
|
|
41ce3c04f7 | ||
|
|
fcf77f359f | ||
|
|
40a2737915 | ||
|
|
216489d67f | ||
|
|
418a393b26 | ||
|
|
1f3d744494 | ||
|
|
ff3f2dccba | ||
|
|
e69973bd29 | ||
|
|
05b352cc36 | ||
|
|
9735548999 | ||
|
|
f9529b1362 | ||
|
|
c598cec702 | ||
|
|
e9ea1c4a0f | ||
|
|
e9238ff282 | ||
|
|
4cebd72cba | ||
|
|
f8a44d6e71 | ||
|
|
d0745b300b | ||
|
|
2a4e0a3081 | ||
|
|
1ff19f9574 | ||
|
|
7ef84cb50d | ||
|
|
cd05c70d64 | ||
|
|
568473b82f | ||
|
|
c61b074755 | ||
|
|
8ed66ab4ab | ||
|
|
b2502dd998 | ||
|
|
856eedbf9d | ||
|
|
0c0ca6e6af | ||
|
|
498b797e49 | ||
|
|
02c33388ba | ||
|
|
8a8e3cc055 | ||
|
|
36d60b16e9 | ||
|
|
de3114568b | ||
|
|
eb5835faeb | ||
|
|
ff1ddb0b79 | ||
|
|
15b127bb2e | ||
|
|
e4ed881f6d | ||
|
|
7b62cf130c | ||
|
|
72fd2e531c | ||
|
|
4a5392ef78 | ||
|
|
0150a708ca | ||
|
|
eacc181d5e | ||
|
|
405bb55881 | ||
|
|
4a35508459 | ||
|
|
98a9d71a2e | ||
|
|
a1596d0b06 | ||
|
|
4b3be4c483 | ||
|
|
0fa8472db1 | ||
|
|
e1e2dca1d8 | ||
|
|
755c013ec8 | ||
|
|
eab702b763 | ||
|
|
d15446ac91 | ||
|
|
500733afb2 | ||
|
|
2aa04b0a56 | ||
|
|
c051f342af | ||
|
|
8aeb365f5f | ||
|
|
827a28313d | ||
|
|
c83de8aad2 | ||
|
|
b55f43b8df | ||
|
|
8466723a90 | ||
|
|
a103b4dbff | ||
|
|
826ac3a947 | ||
|
|
597327f138 | ||
|
|
bef79402ca | ||
|
|
e68e0c381f | ||
|
|
c73f7259c2 | ||
|
|
4c9235ba10 | ||
|
|
55e2a77df8 | ||
|
|
cfbff02e7f | ||
|
|
54980fb296 | ||
|
|
ba98d9315c |
2
API.md
2
API.md
@@ -505,7 +505,7 @@ MCT, it will be pre-configured to use the UTC time system, which is installed an
|
||||
|
||||
The time bounds of an Open MCT application are defined as numbers, and a Time
|
||||
System gives meaning and context to these numbers so that they can be correctly
|
||||
interpreted. Time Systems are JavaScript objects that provide some information
|
||||
interpreted. Time Systems are javscript objects that provide some information
|
||||
about the current time reference frame. An example of defining and registering
|
||||
a new time system is given below:
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"node-uuid": "^1.4.7",
|
||||
"comma-separated-values": "^3.6.4",
|
||||
"FileSaver.js": "^0.0.2",
|
||||
"zepto": "^1.1.6",
|
||||
"zepto": "1.2.0",
|
||||
"eventemitter3": "^1.2.0",
|
||||
"lodash": "3.10.1",
|
||||
"almond": "~0.3.2",
|
||||
|
||||
@@ -59,7 +59,7 @@ define([
|
||||
if (domainObject.telemetry && domainObject.telemetry.hasOwnProperty(prop)) {
|
||||
workerRequest[prop] = domainObject.telemetry[prop];
|
||||
}
|
||||
if (request.hasOwnProperty(prop)) {
|
||||
if (request && request.hasOwnProperty(prop)) {
|
||||
workerRequest[prop] = request[prop];
|
||||
}
|
||||
if (!workerRequest[prop]) {
|
||||
@@ -78,8 +78,8 @@ define([
|
||||
return this.workerInterface.request(workerRequest);
|
||||
};
|
||||
|
||||
GeneratorProvider.prototype.subscribe = function (domainObject, callback) {
|
||||
var workerRequest = this.makeWorkerRequest(domainObject, {});
|
||||
GeneratorProvider.prototype.subscribe = function (domainObject, callback, request) {
|
||||
var workerRequest = this.makeWorkerRequest(domainObject, request);
|
||||
return this.workerInterface.subscribe(workerRequest, callback);
|
||||
};
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ define([
|
||||
{ "key": "styleguide.intro", "name": "Introduction", "cssClass": "icon-page", "description": "Introduction and overview to the style guide" },
|
||||
{ "key": "styleguide.standards", "name": "Standards", "cssClass": "icon-page", "description": "" },
|
||||
{ "key": "styleguide.colors", "name": "Colors", "cssClass": "icon-page", "description": "" },
|
||||
{ "key": "styleguide.status", "name": "status", "cssClass": "icon-page", "description": "Limits, telemetry paused, etc." },
|
||||
{ "key": "styleguide.glyphs", "name": "Glyphs", "cssClass": "icon-page", "description": "Glyphs overview" },
|
||||
{ "key": "styleguide.controls", "name": "Controls", "cssClass": "icon-page", "description": "Buttons, selects, HTML controls" },
|
||||
{ "key": "styleguide.input", "name": "Text Inputs", "cssClass": "icon-page", "description": "Various text inputs" },
|
||||
@@ -26,7 +25,6 @@ define([
|
||||
{ "key": "styleguide.intro", "type": "styleguide.intro", "templateUrl": "templates/intro.html", "editable": false },
|
||||
{ "key": "styleguide.standards", "type": "styleguide.standards", "templateUrl": "templates/standards.html", "editable": false },
|
||||
{ "key": "styleguide.colors", "type": "styleguide.colors", "templateUrl": "templates/colors.html", "editable": false },
|
||||
{ "key": "styleguide.status", "type": "styleguide.status", "templateUrl": "templates/status.html", "editable": false },
|
||||
{ "key": "styleguide.glyphs", "type": "styleguide.glyphs", "templateUrl": "templates/glyphs.html", "editable": false },
|
||||
{ "key": "styleguide.controls", "type": "styleguide.controls", "templateUrl": "templates/controls.html", "editable": false },
|
||||
{ "key": "styleguide.input", "type": "styleguide.input", "templateUrl": "templates/input.html", "editable": false },
|
||||
@@ -49,7 +47,6 @@ define([
|
||||
"intro",
|
||||
"standards",
|
||||
"colors",
|
||||
"status",
|
||||
"glyphs",
|
||||
"styleguide:ui-elements"
|
||||
]
|
||||
|
||||
@@ -28,8 +28,8 @@
|
||||
color: $colorKey;
|
||||
}
|
||||
|
||||
h1, h2, strong, b {
|
||||
color: pullForward($colorBodyFg, 50%);
|
||||
h1, h2 {
|
||||
color: pullForward($colorBodyFg, 20%);
|
||||
}
|
||||
|
||||
h2 {
|
||||
@@ -45,10 +45,6 @@
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
strong, b {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.w-markup {
|
||||
//Wrap markup example "pre" element
|
||||
background-color: $colorCode;
|
||||
@@ -58,12 +54,6 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.w-mct-example {
|
||||
div {
|
||||
margin-bottom: $interiorMarginLg;
|
||||
}
|
||||
}
|
||||
|
||||
code,
|
||||
pre {
|
||||
font-size: 0.8rem;
|
||||
|
||||
@@ -4,5 +4,5 @@
|
||||
<pre></pre>
|
||||
</span>
|
||||
<h3>Example</h3>
|
||||
<div class="w-mct-example"></div>
|
||||
<div></div>
|
||||
</div>
|
||||
|
||||
@@ -121,7 +121,7 @@
|
||||
<h2>Palettes</h2>
|
||||
<div class="cols cols1-1">
|
||||
<div class="col">
|
||||
<p>Use a palette to provide color choices. Similar to context menus and dropdowns, palettes should be dismissed when a choice is made within them, or if the user clicks outside one.</p>
|
||||
<p>Use a palette to provide color choices. Similar to context menus and dropdowns, palettes should be dismissed when a choice is made within them, or if the user clicks outside one. Selected palette choices should utilize the <code>selected</code> CSS class to visualize indicate that state.</p>
|
||||
<p>Note that while this example uses static markup for illustrative purposes, don't do this - use a front-end framework with repeaters to build the color choices.</p>
|
||||
</div>
|
||||
<mct-example><div style="height: 220px" title="Ignore me, I'm just here to provide space for this example.">
|
||||
@@ -129,9 +129,9 @@
|
||||
<div class="s-button s-menu-button menu-element t-color-palette icon-paint-bucket" ng-controller="ClickAwayController as toggle">
|
||||
<span class="l-click-area" ng-click="toggle.toggle()"></span>
|
||||
<span class="color-swatch" style="background: rgb(255, 0, 0);"></span>
|
||||
<div class="menu l-color-palette" ng-show="toggle.isActive()">
|
||||
<div class="menu l-palette l-color-palette" ng-show="toggle.isActive()">
|
||||
<div class="l-palette-row l-option-row">
|
||||
<div class="l-palette-item s-palette-item " ng-click="ngModel[field] = 'transparent'"></div>
|
||||
<div class="l-palette-item s-palette-item no-selection"></div>
|
||||
<span class="l-palette-item-label">None</span>
|
||||
</div>
|
||||
<div class="l-palette-row">
|
||||
@@ -147,7 +147,7 @@
|
||||
<div class="l-palette-item s-palette-item" style="background: rgb(255, 255, 255);"></div>
|
||||
</div>
|
||||
<div class="l-palette-row">
|
||||
<div class="l-palette-item s-palette-item" style="background: rgb(136, 32, 32);"></div>
|
||||
<div class="l-palette-item s-palette-item selected" style="background: rgb(255, 0, 0);"></div>
|
||||
<div class="l-palette-item s-palette-item" style="background: rgb(224, 64, 64);"></div>
|
||||
<div class="l-palette-item s-palette-item" style="background: rgb(240, 160, 72);"></div>
|
||||
<div class="l-palette-item s-palette-item" style="background: rgb(255, 248, 96);"></div>
|
||||
|
||||
@@ -1,142 +0,0 @@
|
||||
<!--
|
||||
Open MCT, Copyright (c) 2014-2016, United States Government
|
||||
as represented by the Administrator of the National Aeronautics and Space
|
||||
Administration. All rights reserved.
|
||||
|
||||
Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0.
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
License for the specific language governing permissions and limitations
|
||||
under the License.
|
||||
|
||||
Open MCT includes source code licensed under additional open source
|
||||
licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
this source code distribution or the Licensing information page available
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
<style>
|
||||
.w-mct-example div[class*="s-limit"],
|
||||
.w-mct-example div[class*="s-status"],
|
||||
.w-mct-example div[class*="s-unsynced"],
|
||||
.w-mct-example span[class*="s-limit"] {
|
||||
border-radius: 4px;
|
||||
padding: 3px 7px;
|
||||
}
|
||||
.w-mct-example table {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
<div class="l-style-guide s-text">
|
||||
<p class="doc-title">Open MCT Style Guide</p>
|
||||
<h1>Status Indication</h1>
|
||||
|
||||
<div class="l-section">
|
||||
<h2>Overview</h2>
|
||||
<p>Many elements in Open MCT need to articulate a dynamic status; Open MCT provides the following styles and conventions to handle this:</p>
|
||||
<ul>
|
||||
<li><strong>Limits</strong>: when telemetry values exceed minimum or maximum values, they can be violating limits. Limit styles include both color and iconography; color is used to indicate severity while icons are used to indicate direction, upper or lower.</li>
|
||||
<li><strong>Status</strong>: Open MCT also provides a number or built-in Status styles allowing telemetry or other displayed information to be visually classified by type. Common uses for these classes are to visually denote event records.</li>
|
||||
<li><strong>Synchronization</strong>: When the system is displaying real-time data, it is very important that displays clearly indicate when they are not doing so, such as when a plot if frozen while panning or zooming. Open MCT provides a style for this.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="l-section">
|
||||
<h2>Limits</h2>
|
||||
<div class="cols cols1-1">
|
||||
<div class="col">
|
||||
<p>Limit CSS classes can be applied to any block or inline element. Open MCT limit classes set color and optionally an icon, but don't effect other properties. Yellow and red limit classes can be used as is, or allow the application of any custom icon available in Open MCT's glyphs library. "Level" limit classes - upper and lower - always use an icon in addition to a color; Open MCT doesn't support level limits without color.</p>
|
||||
<ul>
|
||||
<li>Color only</li>
|
||||
<ul>
|
||||
<li><code>s-limit-yellow</code>: A yellow limit.</li>
|
||||
<li><code>s-limit-red</code>: A red limit.</li>
|
||||
</ul>
|
||||
<li>Color and icon</li>
|
||||
<ul>
|
||||
<li><code>s-limit-yellow-icon</code>: A yellow limit with icon.</li>
|
||||
<li><code>s-limit-red-icon</code>: A red limit with icon.</li>
|
||||
</ul>
|
||||
<li>Upper and lower limit indicators. Must be used with a color limit class to be visible.</li>
|
||||
<ul>
|
||||
<li><code>s-limit-upr</code>: Upper limit.
|
||||
</li>
|
||||
<li><code>s-limit-lwr</code>: Lower limit.
|
||||
</li>
|
||||
</ul>
|
||||
</ul>
|
||||
</div>
|
||||
<mct-example><div class="s-limit-yellow">Yellow limit</div>
|
||||
<div class="s-limit-red">Red limit</div>
|
||||
<div class="s-limit-yellow-icon">Yellow limit with icon</div>
|
||||
<div class="s-limit-red-icon">Red limit with icon</div>
|
||||
<div class="s-limit-yellow s-limit-lwr">Lower yellow limit</div>
|
||||
<div class="s-limit-red s-limit-upr">Upper red limit</div>
|
||||
<div class="s-limit-red icon-bell">Red Limit with a custom icon</div>
|
||||
<div>Some text with an <span class="s-limit-yellow-icon">inline element</span> showing a yellow limit.</div>
|
||||
|
||||
<!-- Limits applied in a table -->
|
||||
<table>
|
||||
<tr class='header'><td>Name</td><td>Value 1</td><td>Value 2</td></tr>
|
||||
<tr><td>ENG_PWR 4991</td><td>7.023</td><td class="s-limit-yellow s-limit-upr">70.23</td></tr>
|
||||
<tr><td>ENG_PWR 4992</td><td>49.784</td><td class="s-limit-red s-limit-lwr">-121.22</td></tr>
|
||||
<tr><td>ENG_PWR 4993</td><td class="s-limit-yellow icon-bell">0.451</td><td>1.007</td></tr>
|
||||
</table>
|
||||
</mct-example>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="l-section">
|
||||
<h2>Status</h2>
|
||||
<div class="cols cols1-1">
|
||||
<div class="col">
|
||||
<p>Classes here can be applied to elements as needed.</p>
|
||||
<ul>
|
||||
<li>Color only</li>
|
||||
<ul>
|
||||
<li><code>s-status-warning-hi</code></li>
|
||||
<li><code>s-status-warning-lo</code></li>
|
||||
<li><code>s-status-diagnostic</code></li>
|
||||
<li><code>s-status-info</code></li>
|
||||
<li><code>s-status-ok</code></li>
|
||||
</ul>
|
||||
<li>Color and icon</li>
|
||||
<ul>
|
||||
<li><code>s-status-warning-hi-icon</code></li>
|
||||
<li><code>s-status-warning-lo-icon</code></li>
|
||||
<li><code>s-status-diagnostic-icon</code></li>
|
||||
<li><code>s-status-info-icon</code></li>
|
||||
<li><code>s-status-ok-icon</code></li>
|
||||
</ul>
|
||||
</ul>
|
||||
</div>
|
||||
<mct-example><div class="s-status-warning-hi">WARNING HI</div>
|
||||
<div class="s-status-warning-lo">WARNING LOW</div>
|
||||
<div class="s-status-diagnostic">DIAGNOSTIC</div>
|
||||
<div class="s-status-info">INFO</div>
|
||||
<div class="s-status-ok">OK</div>
|
||||
<div class="s-status-warning-hi-icon">WARNING HI with icon</div>
|
||||
<div class="s-status-warning-lo-icon">WARNING LOW with icon</div>
|
||||
<div class="s-status-diagnostic-icon">DIAGNOSTIC with icon</div>
|
||||
<div class="s-status-info-icon">INFO with icon</div>
|
||||
<div class="s-status-ok-icon">OK with icon</div>
|
||||
<div class="s-status-warning-hi icon-gear">WARNING HI with custom icon</div>
|
||||
</mct-example>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="l-section">
|
||||
<h2>Synchronization</h2>
|
||||
<div class="cols cols1-1">
|
||||
<div class="col">
|
||||
<p>When the system is operating in real-time streaming mode, it is important for views that display real-time data to clearly articulate when they are not, such as when a user zooms or pans a plot view, freezing that view. In that case, the CSS class <code>s-unsynced</code> should be applied to that view.</p>
|
||||
</div>
|
||||
<mct-example><div class="s-unsynced">This element is unsynced</div>
|
||||
</mct-example>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -34,7 +34,6 @@ define(
|
||||
pages['standards'] = { name: "Standards", type: "styleguide.standards", location: "styleguide:home" };
|
||||
pages['colors'] = { name: "Colors", type: "styleguide.colors", location: "styleguide:home" };
|
||||
pages['glyphs'] = { name: "Glyphs", type: "styleguide.glyphs", location: "styleguide:home" };
|
||||
pages['status'] = { name: "Status Indication", type: "styleguide.status", location: "styleguide:home" };
|
||||
pages['controls'] = { name: "Controls", type: "styleguide.controls", location: "styleguide:ui-elements" };
|
||||
pages['input'] = { name: "Text Inputs", type: "styleguide.input", location: "styleguide:ui-elements" };
|
||||
pages['menus'] = { name: "Menus", type: "styleguide.menus", location: "styleguide:ui-elements" };
|
||||
|
||||
@@ -25,8 +25,7 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||
<title></title>
|
||||
<script src="bower_components/requirejs/require.js">
|
||||
</script>
|
||||
<script src="bower_components/requirejs/require.js"> </script>
|
||||
<script>
|
||||
var THIRTY_MINUTES = 30 * 60 * 1000;
|
||||
|
||||
@@ -43,7 +42,6 @@
|
||||
openmct.install(openmct.plugins.Generator());
|
||||
openmct.install(openmct.plugins.ExampleImagery());
|
||||
openmct.install(openmct.plugins.UTCTimeSystem());
|
||||
openmct.install(openmct.plugins.ImportExport());
|
||||
openmct.install(openmct.plugins.Conductor({
|
||||
menuOptions: [
|
||||
{
|
||||
@@ -65,6 +63,7 @@
|
||||
}
|
||||
]
|
||||
}));
|
||||
openmct.install(openmct.plugins.SummaryWidget());
|
||||
openmct.time.clock('local', {start: -THIRTY_MINUTES, end: 0});
|
||||
openmct.time.timeSystem('utc');
|
||||
openmct.start();
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"metadata": {
|
||||
"name": "openmct-symbols-16px",
|
||||
"lastOpened": 0,
|
||||
"created": 1505151140023
|
||||
"created": 1502487054429
|
||||
},
|
||||
"iconSets": [
|
||||
{
|
||||
@@ -636,29 +636,13 @@
|
||||
"code": 921670,
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 138,
|
||||
"id": 115,
|
||||
"name": "icon-import",
|
||||
"prevSize": 24,
|
||||
"code": 921671,
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 136,
|
||||
"id": 116,
|
||||
"name": "icon-export",
|
||||
"prevSize": 24,
|
||||
"code": 921672,
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 37,
|
||||
"prevSize": 24,
|
||||
"name": "icon-activity",
|
||||
"id": 32,
|
||||
"code": 921856,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 36,
|
||||
@@ -666,7 +650,7 @@
|
||||
"name": "icon-activity-mode",
|
||||
"id": 31,
|
||||
"code": 921857,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 52,
|
||||
@@ -674,7 +658,7 @@
|
||||
"name": "icon-autoflow-tabular",
|
||||
"id": 47,
|
||||
"code": 921858,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 55,
|
||||
@@ -682,7 +666,7 @@
|
||||
"name": "icon-clock",
|
||||
"id": 50,
|
||||
"code": 921859,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 58,
|
||||
@@ -690,7 +674,7 @@
|
||||
"name": "icon-database",
|
||||
"id": 53,
|
||||
"code": 921860,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 57,
|
||||
@@ -698,7 +682,7 @@
|
||||
"name": "icon-database-query",
|
||||
"id": 52,
|
||||
"code": 921861,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 17,
|
||||
@@ -706,7 +690,7 @@
|
||||
"name": "icon-dataset",
|
||||
"id": 12,
|
||||
"code": 921862,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 22,
|
||||
@@ -714,7 +698,7 @@
|
||||
"name": "icon-datatable",
|
||||
"id": 17,
|
||||
"code": 921863,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 59,
|
||||
@@ -722,7 +706,7 @@
|
||||
"name": "icon-dictionary",
|
||||
"id": 54,
|
||||
"code": 921864,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 62,
|
||||
@@ -730,7 +714,7 @@
|
||||
"name": "icon-folder",
|
||||
"id": 57,
|
||||
"code": 921865,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 66,
|
||||
@@ -738,7 +722,7 @@
|
||||
"name": "icon-image",
|
||||
"id": 61,
|
||||
"code": 921872,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 68,
|
||||
@@ -746,7 +730,7 @@
|
||||
"name": "icon-layout",
|
||||
"id": 63,
|
||||
"code": 921873,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 77,
|
||||
@@ -754,7 +738,7 @@
|
||||
"name": "icon-object",
|
||||
"id": 72,
|
||||
"code": 921874,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 78,
|
||||
@@ -762,7 +746,7 @@
|
||||
"name": "icon-object-unknown",
|
||||
"id": 73,
|
||||
"code": 921875,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 79,
|
||||
@@ -770,7 +754,7 @@
|
||||
"name": "icon-packet",
|
||||
"id": 74,
|
||||
"code": 921876,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 80,
|
||||
@@ -778,7 +762,7 @@
|
||||
"name": "icon-page",
|
||||
"id": 75,
|
||||
"code": 921877,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 135,
|
||||
@@ -786,7 +770,7 @@
|
||||
"name": "icon-plot-overlay",
|
||||
"prevSize": 24,
|
||||
"code": 921878,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 113,
|
||||
@@ -794,7 +778,7 @@
|
||||
"name": "icon-plot-stacked",
|
||||
"prevSize": 24,
|
||||
"code": 921879,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 10,
|
||||
@@ -802,7 +786,7 @@
|
||||
"name": "icon-session",
|
||||
"id": 5,
|
||||
"code": 921880,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 24,
|
||||
@@ -810,7 +794,7 @@
|
||||
"name": "icon-tabular",
|
||||
"id": 19,
|
||||
"code": 921881,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 7,
|
||||
@@ -818,7 +802,7 @@
|
||||
"name": "icon-tabular-lad",
|
||||
"id": 2,
|
||||
"code": 921888,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 6,
|
||||
@@ -826,7 +810,7 @@
|
||||
"name": "icon-tabular-lad-set",
|
||||
"id": 1,
|
||||
"code": 921889,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 8,
|
||||
@@ -834,7 +818,7 @@
|
||||
"name": "icon-tabular-realtime",
|
||||
"id": 3,
|
||||
"code": 921890,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 23,
|
||||
@@ -842,7 +826,7 @@
|
||||
"name": "icon-tabular-scrolling",
|
||||
"id": 18,
|
||||
"code": 921891,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 112,
|
||||
@@ -850,7 +834,7 @@
|
||||
"name": "icon-telemetry",
|
||||
"id": 86,
|
||||
"code": 921892,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 90,
|
||||
@@ -858,7 +842,7 @@
|
||||
"name": "icon-telemetry-panel",
|
||||
"id": 85,
|
||||
"code": 921893,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 93,
|
||||
@@ -866,7 +850,7 @@
|
||||
"name": "icon-timeline",
|
||||
"id": 88,
|
||||
"code": 921894,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 116,
|
||||
@@ -874,7 +858,7 @@
|
||||
"name": "icon-timer-v1.5",
|
||||
"prevSize": 24,
|
||||
"code": 921895,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 11,
|
||||
@@ -882,7 +866,7 @@
|
||||
"name": "icon-topic",
|
||||
"id": 6,
|
||||
"code": 921896,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 115,
|
||||
@@ -890,7 +874,7 @@
|
||||
"name": "icon-box-with-dashed-lines",
|
||||
"id": 29,
|
||||
"code": 921897,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
},
|
||||
{
|
||||
"order": 126,
|
||||
@@ -898,7 +882,7 @@
|
||||
"name": "icon-summary-widget",
|
||||
"prevSize": 24,
|
||||
"code": 921904,
|
||||
"tempChar": ""
|
||||
"tempChar": ""
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
@@ -2699,52 +2683,6 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 115,
|
||||
"paths": [
|
||||
"M832 192.4v639.4c0 0.2-0.2 0.2-0.4 0.4h-319.6v192h320c105.6 0 192-86.4 192-192v-640.2c0-105.6-86.4-192-192-192h-320v192h319.6c0.2 0 0.4 0.2 0.4 0.4z",
|
||||
"M192 704v192l384-384-384-384v192h-192v384z"
|
||||
],
|
||||
"attrs": [
|
||||
{},
|
||||
{}
|
||||
],
|
||||
"grid": 16,
|
||||
"tags": [
|
||||
"icon-import"
|
||||
],
|
||||
"isMulticolor": false,
|
||||
"isMulticolor2": false,
|
||||
"colorPermutations": {
|
||||
"1161751207457516161751": [
|
||||
{},
|
||||
{}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 116,
|
||||
"paths": [
|
||||
"M192 831.66v-639.32l0.34-0.34h319.66v-192h-320c-105.6 0-192 86.4-192 192v640c0 105.6 86.4 192 192 192h320v-192h-319.66z",
|
||||
"M1024 512l-384-384v192h-192v384h192v192l384-384z"
|
||||
],
|
||||
"attrs": [
|
||||
{},
|
||||
{}
|
||||
],
|
||||
"isMulticolor": false,
|
||||
"isMulticolor2": false,
|
||||
"grid": 16,
|
||||
"tags": [
|
||||
"icon-export"
|
||||
],
|
||||
"colorPermutations": {
|
||||
"1161751207457516161751": [
|
||||
{},
|
||||
{}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"paths": [
|
||||
"M576 64h-256l320 320h-290.256c-44.264-76.516-126.99-128-221.744-128h-128v512h128c94.754 0 177.48-51.484 221.744-128h290.256l-320 320h256l448-448-448-448z"
|
||||
|
||||
Binary file not shown.
@@ -85,8 +85,6 @@
|
||||
<glyph unicode="󡁄" glyph-name="icon-grid-snap-no" d="M768 384h192v-64h-192v64zM256 384h192v-64h-192v64zM0 384h192v-64h-192v64zM640 448h-64v-64h-64v-64h64v-64h64v64h64v64h-64zM576 704h64v-192h-64v192zM576 960h64v-192h-64v192zM576 192h64v-192h-64v192z" />
|
||||
<glyph unicode="󡁅" glyph-name="icon-frame-show" d="M0 896v-896h1024v896h-1024zM896 128h-768v640h768v-640zM192 704h384v-128h-384v128z" />
|
||||
<glyph unicode="󡁆" glyph-name="icon-frame-hide" d="M128 770h420l104 128h-652v-802.4l128 157.4zM896 130h-420l-104-128h652v802.4l-128-157.4zM832 962l-832-1024h192l832 1024zM392 578l104 128h-304v-128z" />
|
||||
<glyph unicode="󡁇" glyph-name="icon-import" d="M832 767.6v-639.4c0-0.2-0.2-0.2-0.4-0.4h-319.6v-192h320c105.6 0 192 86.4 192 192v640.2c0 105.6-86.4 192-192 192h-320v-192h319.6c0.2 0 0.4-0.2 0.4-0.4zM192 256v-192l384 384-384 384v-192h-192v-384z" />
|
||||
<glyph unicode="󡁈" glyph-name="icon-export" d="M192 128.34v639.32l0.34 0.34h319.66v192h-320c-105.6 0-192-86.4-192-192v-640c0-105.6 86.4-192 192-192h320v192h-319.66zM1024 448l-384 384v-192h-192v-384h192v-192l384 384z" />
|
||||
<glyph unicode="󡄀" glyph-name="icon-activity" d="M576 896h-256l320-320h-290.256c-44.264 76.516-126.99 128-221.744 128h-128v-512h128c94.754 0 177.48 51.484 221.744 128h290.256l-320-320h256l448 448-448 448z" />
|
||||
<glyph unicode="󡄁" glyph-name="icon-activity-mode" d="M512 960c-214.866 0-398.786-132.372-474.744-320h90.744c56.86 0 107.938-24.724 143.094-64h240.906l-192 192h256l320-320-320-320h-256l192 192h-240.906c-35.156-39.276-86.234-64-143.094-64h-90.744c75.958-187.628 259.878-320 474.744-320 282.77 0 512 229.23 512 512s-229.23 512-512 512z" />
|
||||
<glyph unicode="󡄂" glyph-name="icon-autoflow-tabular" d="M192 960c-105.6 0-192-86.4-192-192v-640c0-105.6 86.4-192 192-192h64v1024h-64zM384 960h256v-1024h-256v1024zM832 960h-64v-704h256v512c0 105.6-86.4 192-192 192z" />
|
||||
|
||||
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 43 KiB |
Binary file not shown.
Binary file not shown.
@@ -21,7 +21,7 @@
|
||||
*****************************************************************************/
|
||||
|
||||
/********************************************* COLUMN LAYOUTS STYLES */
|
||||
@mixin cols($totalCols, $span) {
|
||||
/*@mixin cols($totalCols, $span) {
|
||||
$cw: 100% / $totalCols;
|
||||
min-width: (500px / $totalCols) * $span;
|
||||
@if ($totalCols != $span) {
|
||||
@@ -89,7 +89,7 @@
|
||||
@include clearfix;
|
||||
padding: $interiorMargin 0;
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
/********************************************* FLEX STYLES */
|
||||
.l-flex-row,
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
*****************************************************************************/
|
||||
|
||||
/************************** FEATURES */
|
||||
$enableImageryThumbs: true; // Set to true if historical imagery thumbnails are supported
|
||||
$enableImageryThumbs: false; // Set to true if historical imagery thumbnails are supported
|
||||
|
||||
/************************** VERY INFLUENTIAL GLOBAL DIMENSIONS */
|
||||
$bodyMargin: 10px;
|
||||
@@ -82,7 +82,7 @@ $tabularTdPadTB: 2px;
|
||||
/*************** Imagery */
|
||||
$imageMainControlBarH: 25px;
|
||||
$imageThumbsD: 120px;
|
||||
$imageThumbsWrapperH: 155px;
|
||||
$imageThumbsWrapperH: $imageThumbsD * 1.4;
|
||||
$imageThumbPad: 1px;
|
||||
/*************** Ticks */
|
||||
$ticksH: 25px;
|
||||
|
||||
@@ -52,7 +52,6 @@
|
||||
font-size: 0.8rem;
|
||||
$p: 1px;
|
||||
line-height: 100%;
|
||||
overflow: hidden;
|
||||
&.l-static-text {
|
||||
padding: $p;
|
||||
}
|
||||
|
||||
@@ -180,6 +180,20 @@ a.disabled {
|
||||
@include ellipsize();
|
||||
}
|
||||
|
||||
.no-selection {
|
||||
// aka selection = "None". Used in palettes and their menu buttons.
|
||||
$c: red; $s: 48%; $e: 52%;
|
||||
@include background-image(linear-gradient(-45deg,
|
||||
transparent $s - 5%,
|
||||
$c $s,
|
||||
$c $e,
|
||||
transparent $e + 5%
|
||||
));
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
|
||||
.scrolling,
|
||||
.scroll {
|
||||
overflow: auto;
|
||||
|
||||
@@ -1,24 +1,3 @@
|
||||
/*****************************************************************************
|
||||
* 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.
|
||||
*****************************************************************************/
|
||||
@mixin glyphBefore($unicode, $family: 'symbolsfont') {
|
||||
&:before {
|
||||
content: $unicode;
|
||||
@@ -113,8 +92,6 @@ $glyph-icon-grid-snap-to: '\e1043';
|
||||
$glyph-icon-grid-snap-no: '\e1044';
|
||||
$glyph-icon-frame-show: '\e1045';
|
||||
$glyph-icon-frame-hide: '\e1046';
|
||||
$glyph-icon-import: '\e1047';
|
||||
$glyph-icon-export: '\e1048';
|
||||
$glyph-icon-activity: '\e1100';
|
||||
$glyph-icon-activity-mode: '\e1101';
|
||||
$glyph-icon-autoflow-tabular: '\e1102';
|
||||
@@ -227,8 +204,6 @@ $glyph-icon-summary-widget: '\e1130';
|
||||
.icon-grid-snap-no { @include glyphBefore($glyph-icon-grid-snap-no); }
|
||||
.icon-frame-show { @include glyphBefore($glyph-icon-frame-show); }
|
||||
.icon-frame-hide { @include glyphBefore($glyph-icon-frame-hide); }
|
||||
.icon-import { @include glyphBefore($glyph-icon-import); }
|
||||
.icon-export { @include glyphBefore($glyph-icon-export); }
|
||||
.icon-activity { @include glyphBefore($glyph-icon-activity); }
|
||||
.icon-activity-mode { @include glyphBefore($glyph-icon-activity-mode); }
|
||||
.icon-autoflow-tabular { @include glyphBefore($glyph-icon-autoflow-tabular); }
|
||||
|
||||
39
platform/commonUI/general/res/sass/_limits.scss
Normal file
39
platform/commonUI/general/res/sass/_limits.scss
Normal file
@@ -0,0 +1,39 @@
|
||||
@mixin limitGlyph($iconColor, $glyph: $glyph-icon-alert-triangle) {
|
||||
&:before {
|
||||
color: $iconColor;
|
||||
content: $glyph;
|
||||
font-family: symbolsfont;
|
||||
font-size: 0.8em;
|
||||
display: inline;
|
||||
margin-right: $interiorMarginSm;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.s-limit-red { background: $colorLimitRedBg !important; }
|
||||
.s-limit-yellow { background: $colorLimitYellowBg !important; }
|
||||
|
||||
// Handle limit when applied to a tr
|
||||
tr[class*="s-limit"] {
|
||||
&.s-limit-red td:first-child {
|
||||
@include limitGlyph($colorLimitRedIc);
|
||||
}
|
||||
&.s-limit-yellow td:first-child {
|
||||
@include limitGlyph($colorLimitYellowIc);
|
||||
}
|
||||
&.s-limit-upr td:first-child:before { content: $glyph-icon-arrow-double-up; }
|
||||
&.s-limit-lwr td:first-child:before { content: $glyph-icon-arrow-double-down; }
|
||||
}
|
||||
|
||||
// Handle limit when applied directly to a non-tr element
|
||||
// Assume this is applied to the element that displays the limit value
|
||||
:not(tr)[class*="s-limit"] {
|
||||
&.s-limit-red {
|
||||
@include limitGlyph($colorLimitRedIc);
|
||||
}
|
||||
&.s-limit-yellow {
|
||||
@include limitGlyph($colorLimitYellowIc);
|
||||
}
|
||||
&.s-limit-upr:before { content: $glyph-icon-arrow-double-up; }
|
||||
&.s-limit-lwr:before { content: $glyph-icon-arrow-double-down; }
|
||||
}
|
||||
@@ -27,7 +27,7 @@
|
||||
@import "about";
|
||||
@import "text";
|
||||
@import "icons";
|
||||
@import "status";
|
||||
@import "limits";
|
||||
@import "data-status";
|
||||
@import "helpers/bubbles";
|
||||
@import "helpers/splitter";
|
||||
@@ -37,7 +37,7 @@
|
||||
/********************************* CONTROLS */
|
||||
@import "controls/breadcrumb";
|
||||
@import "controls/buttons";
|
||||
@import "controls/color-palette";
|
||||
@import "controls/palette";
|
||||
@import "controls/controls";
|
||||
@import "controls/lists";
|
||||
@import "controls/menus";
|
||||
@@ -80,3 +80,4 @@
|
||||
@import "autoflow";
|
||||
@import "features/imagery";
|
||||
@import "features/time-display";
|
||||
@import "widgets";
|
||||
|
||||
@@ -20,8 +20,8 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
@mixin absPosDefault($offset: 0px, $overflowHidden: hidden) {
|
||||
overflow: $overflowHidden;
|
||||
@mixin absPosDefault($offset: 0px, $overflow: hidden) {
|
||||
overflow: $overflow;
|
||||
position: absolute;
|
||||
top: $offset;
|
||||
right: $offset;
|
||||
|
||||
@@ -1,86 +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.
|
||||
*****************************************************************************/
|
||||
/*************************************************** MIXINS */
|
||||
@mixin formulateStatusColors($c) {
|
||||
// Sets bg and icon colors for elements
|
||||
background: rgba($c, 0.4) !important;
|
||||
&:before { color: $c !important; }
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*************************************************** GENERAL */
|
||||
.s-limit-yellow,
|
||||
.s-limit-red,
|
||||
.s-limit-yellow-icon,
|
||||
.s-limit-red-icon,
|
||||
.s-status-warning-lo,
|
||||
.s-status-warning-hi,
|
||||
.s-status-diagnostic,
|
||||
.s-status-command,
|
||||
.s-status-info,
|
||||
.s-status-ok,
|
||||
.s-status-warning-lo-icon,
|
||||
.s-status-warning-hi-icon,
|
||||
.s-status-diagnostic-icon,
|
||||
.s-status-command-icon,
|
||||
.s-status-info-icon,
|
||||
.s-status-ok-icon {
|
||||
@include trans-prop-nice($props: background, $dur: 500ms);
|
||||
&:before {
|
||||
content:'';
|
||||
font-family: symbolsfont;
|
||||
font-size: 0.8em;
|
||||
display: inline;
|
||||
margin-right: $interiorMarginSm;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*************************************************** LIMITS */
|
||||
.s-limit-yellow, .s-limit-yellow-icon {
|
||||
@include formulateStatusColors($colorWarningLo);
|
||||
}
|
||||
|
||||
.s-limit-red, .s-limit-red-icon {
|
||||
@include formulateStatusColors($colorWarningHi);
|
||||
}
|
||||
|
||||
.s-limit-upr:before { content: $glyph-icon-arrow-double-up; }
|
||||
.s-limit-lwr:before { content: $glyph-icon-arrow-double-down; }
|
||||
.s-limit-yellow-icon:before,
|
||||
.s-limit-red-icon:before { content: $glyph-icon-alert-triangle; }
|
||||
|
||||
/*************************************************** STATUS */
|
||||
.s-status-warning-hi, .s-status-warning-hi-icon { @include formulateStatusColors($colorWarningHi); }
|
||||
.s-status-warning-lo, .s-status-warning-lo-icon { @include formulateStatusColors($colorWarningLo); }
|
||||
.s-status-diagnostic, .s-status-diagnostic-icon { @include formulateStatusColors($colorDiagnostic); }
|
||||
.s-status-info, .s-status-info-icon { @include formulateStatusColors($colorInfo); }
|
||||
.s-status-ok, .s-status-ok-icon { @include formulateStatusColors($colorOk); }
|
||||
|
||||
.s-status-warning-hi-icon:before { content: $glyph-icon-alert-triangle; }
|
||||
.s-status-warning-lo-icon:before { content: $glyph-icon-alert-rect; }
|
||||
.s-status-diagnostic-icon:before { content: $glyph-icon-eye-open; }
|
||||
.s-status-info-icon:before { content: $glyph-icon-info; }
|
||||
.s-status-ok-icon:before { content: $glyph-icon-check; }
|
||||
|
||||
|
||||
213
platform/commonUI/general/res/sass/_widgets.scss
Normal file
213
platform/commonUI/general/res/sass/_widgets.scss
Normal file
@@ -0,0 +1,213 @@
|
||||
/*****************************************************************************
|
||||
* 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.
|
||||
*****************************************************************************/
|
||||
|
||||
/************************************************************* WIDGET OBJECT */
|
||||
.l-summary-widget {
|
||||
// Widget layout classes here
|
||||
@include ellipsize();
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.s-summary-widget {
|
||||
// Widget style classes here
|
||||
border-radius: $basicCr;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
box-sizing: border-box;
|
||||
padding: $interiorMarginLg $interiorMarginLg * 2;
|
||||
}
|
||||
|
||||
.l-widget-outer-w,
|
||||
.widget-holder,
|
||||
.widget-edit-holder {
|
||||
// In browse mode, all these things should be at .abs default
|
||||
@extend .abs;
|
||||
}
|
||||
|
||||
.widget-edit-holder {
|
||||
// Hide edit area when in browse mode
|
||||
display: none;
|
||||
}
|
||||
|
||||
.widget-rule-header {
|
||||
@extend .l-flex-row;
|
||||
@include align-items(center);
|
||||
margin-bottom: $interiorMargin;
|
||||
> .flex-elem {
|
||||
&:not(:first-child) {
|
||||
margin-left: $interiorMargin;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.widget-rule-content {
|
||||
@include trans-prop-nice($props: (height, min-height), $dur: 250ms);
|
||||
min-height: 0;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
&.expanded {
|
||||
min-height: 50px;
|
||||
height: auto;
|
||||
overflow: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.l-widget-thumb-w,
|
||||
.l-compact-form label {
|
||||
$ruleLabelW: 40%;
|
||||
$ruleLabelMaxW: 150px;
|
||||
@include display(flex);
|
||||
max-width: $ruleLabelMaxW;
|
||||
width: $ruleLabelW;
|
||||
}
|
||||
|
||||
/********************************************************** EDITING A WIDGET */
|
||||
.s-status-editing .l-widget-outer-w {
|
||||
$widgetHolderH: 100px;
|
||||
@extend .abs;
|
||||
//overflow: hidden; // Overflow scroll is handled by internal elements
|
||||
|
||||
.widget-holder {
|
||||
bottom: auto;
|
||||
height: $widgetHolderH;
|
||||
.l-summary-widget {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
top: 50%; left: 50%;
|
||||
@include transform(translateX(-50%) translateY(-50%));
|
||||
}
|
||||
}
|
||||
|
||||
.widget-edit-holder {
|
||||
@extend .l-flex-col;
|
||||
display: block; // Needed to counteract display: none while browsing
|
||||
overflow-y: scroll;
|
||||
padding-right: $interiorMargin;
|
||||
top: $widgetHolderH + $interiorMargin;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.widget-test-data,
|
||||
.widget-rules-w {
|
||||
@include test(blue, 0.1);
|
||||
}
|
||||
|
||||
.widget-test-data {
|
||||
|
||||
}
|
||||
|
||||
.widget-rules-w {
|
||||
// Wrapper area that holds n rules
|
||||
@extend .flex-elem;
|
||||
box-sizing: border-box;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.l-widget-rule {
|
||||
box-sizing: border-box;
|
||||
margin-bottom: $interiorMargin;
|
||||
padding: $interiorMarginLg;
|
||||
}
|
||||
|
||||
.l-widget-thumb-w {
|
||||
@extend .l-flex-row;
|
||||
@include align-items(center);
|
||||
> span { display: block; }
|
||||
.grippy-holder,
|
||||
.view-control {
|
||||
margin-right: $interiorMargin;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
}
|
||||
|
||||
.widget-thumb {
|
||||
@include flex(1 1 auto);
|
||||
}
|
||||
}
|
||||
|
||||
.rule-title {
|
||||
@include flex(0 1 auto);
|
||||
color: pullForward($colorBodyFg, 50%);
|
||||
}
|
||||
|
||||
.rule-description {
|
||||
@include flex(1 1 auto);
|
||||
@include ellipsize();
|
||||
color: pushBack($colorBodyFg, 20%);
|
||||
}
|
||||
|
||||
.s-widget-rule {
|
||||
background-color: rgba($colorBodyFg, 0.1);
|
||||
border-radius: $basicCr;
|
||||
}
|
||||
|
||||
.widget-thumb {
|
||||
@include ellipsize();
|
||||
@extend .s-summary-widget;
|
||||
@extend .l-summary-widget;
|
||||
padding: $interiorMarginSm $interiorMargin;
|
||||
.widget-icon {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
}
|
||||
|
||||
// Hide and show elements in the rule-header on hover
|
||||
.l-widget-rule {
|
||||
.grippy,
|
||||
.l-rule-action-buttons-w,
|
||||
.l-condition-action-buttons-w {
|
||||
@include trans-prop-nice($props: opacity, $dur: 500ms);
|
||||
opacity: 0;
|
||||
}
|
||||
&:hover {
|
||||
.grippy,
|
||||
.l-rule-action-buttons-w,
|
||||
.l-condition-action-buttons-w {
|
||||
@include trans-prop-nice($props: opacity, $dur: 0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.l-compact-form {
|
||||
// Overrides
|
||||
ul li {
|
||||
padding: $interiorMargin 0;
|
||||
&:not(.widget-rule-header) {
|
||||
&:not(.connects-to-previous) {
|
||||
border-top: 1px solid $colorFormLines;
|
||||
}
|
||||
}
|
||||
&.connects-to-previous {
|
||||
padding: $interiorMargin 0;
|
||||
}
|
||||
}
|
||||
|
||||
label {
|
||||
display: block; // Needed to align text to right
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,7 @@ $pad: $interiorMargin * $baseRatio;
|
||||
height: $btnStdH;
|
||||
line-height: $btnStdH;
|
||||
padding: 0 $pad;
|
||||
vertical-align: top;
|
||||
|
||||
&.labeled:before {
|
||||
// Icon when it's included
|
||||
|
||||
@@ -72,13 +72,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Hyperlink objects
|
||||
.s-hyperlink {
|
||||
.label {
|
||||
font-size: 0.8rem !important;
|
||||
}
|
||||
// Hyperlink objects
|
||||
&:not(.s-button) {
|
||||
color: $colorKey;
|
||||
font-size: 0.8rem;
|
||||
&:hover { color: $colorKeyHov; }
|
||||
}
|
||||
}
|
||||
@@ -287,14 +285,13 @@ textarea.lg { position: relative; height: 300px; }
|
||||
.select {
|
||||
@include btnSubtle($bg: $colorSelectBg);
|
||||
@extend .icon-arrow-down; // Context arrow
|
||||
@if $shdwBtns != none {
|
||||
margin: 0 0 2px 0; // Needed to avoid dropshadow from being clipped by parent containers
|
||||
}
|
||||
//@if $shdwBtns != none { margin: 0 0 2px 0; } // Needed to avoid dropshadow from being clipped by parent containers
|
||||
display: inline-block;
|
||||
padding: 0 $interiorMargin;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
line-height: $formInputH;
|
||||
height: $btnStdH;
|
||||
line-height: $btnStdH;
|
||||
select {
|
||||
@include appearance(none);
|
||||
box-sizing: border-box;
|
||||
@@ -309,11 +306,13 @@ textarea.lg { position: relative; height: 300px; }
|
||||
}
|
||||
}
|
||||
&:before {
|
||||
pointer-events: none;
|
||||
@include transform(translateY(-50%));
|
||||
color: rgba($colorInvokeMenu, percentToDecimal($contrastInvokeMenuPercent));
|
||||
display: block;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
right: $interiorMargin; top: 0;
|
||||
right: $interiorMargin;
|
||||
top: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -705,6 +704,30 @@ textarea {
|
||||
}
|
||||
}
|
||||
|
||||
.view-switcher,
|
||||
.t-btn-view-large {
|
||||
@include trans-prop-nice-fade($controlFadeMs);
|
||||
}
|
||||
|
||||
.view-control {
|
||||
@extend .icon-arrow-right;
|
||||
cursor: pointer;
|
||||
font-size: 0.75em;
|
||||
&:before {
|
||||
position: absolute;
|
||||
@include trans-prop-nice(transform, 100ms);
|
||||
@include transform-origin(center);
|
||||
}
|
||||
&.expanded:before {
|
||||
@include transform(rotate(90deg));
|
||||
}
|
||||
}
|
||||
|
||||
.grippy {
|
||||
@extend .icon-grippy;
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
/******************************************************** BROWSER ELEMENTS */
|
||||
body.desktop {
|
||||
::-webkit-scrollbar {
|
||||
|
||||
@@ -36,15 +36,20 @@
|
||||
margin-left: $interiorMarginSm;
|
||||
}
|
||||
|
||||
.icon-swatch,
|
||||
.color-swatch {
|
||||
// Used in color menu buttons in toolbar
|
||||
$d: 10px;
|
||||
display: inline-block;
|
||||
border: 1px solid rgba($colorBtnFg, 0.2);
|
||||
height: $d; width: $d;
|
||||
line-height: $d;
|
||||
vertical-align: middle;
|
||||
margin-left: $interiorMarginSm;
|
||||
margin-top: -2px;
|
||||
&:not(.no-selection) {
|
||||
border-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
&:after {
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
// Status coloring
|
||||
.ok, .info {
|
||||
.status-indicator {
|
||||
color: $colorInfo;
|
||||
color: $colorStatusInfo;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,15 +224,15 @@
|
||||
}
|
||||
&.ok,
|
||||
&.info {
|
||||
@include statusBannerColors($colorInfo);
|
||||
@include statusBannerColors($colorStatusInfo);
|
||||
}
|
||||
&.caution,
|
||||
&.warning,
|
||||
&.alert {
|
||||
@include statusBannerColors($colorWarningLo);
|
||||
@include statusBannerColors($colorStatusAlert);
|
||||
}
|
||||
&.error {
|
||||
@include statusBannerColors($colorWarningHi);
|
||||
@include statusBannerColors($colorStatusError);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,15 +248,15 @@
|
||||
|
||||
.message-severity-info .type-icon.message-type {
|
||||
@extend .icon-info;
|
||||
color: $colorInfo;
|
||||
color: $colorStatusInfo;
|
||||
}
|
||||
.message-severity-alert .type-icon.message-type {
|
||||
@extend .icon-bell;
|
||||
color: $colorWarningLo;
|
||||
color: $colorStatusAlert;
|
||||
}
|
||||
.message-severity-error .type-icon.message-type {
|
||||
@extend .icon-alert-rect;
|
||||
color: $colorWarningHi;
|
||||
color: $colorStatusError;
|
||||
}
|
||||
}
|
||||
/* Paths:
|
||||
|
||||
@@ -19,11 +19,10 @@
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
.l-color-palette {
|
||||
.l-palette {
|
||||
$d: 16px;
|
||||
$colorsPerRow: 10;
|
||||
$m: 1;
|
||||
$colorSelectedColor: #fff;
|
||||
|
||||
box-sizing: border-box;
|
||||
padding: $interiorMargin !important;
|
||||
@@ -33,46 +32,41 @@
|
||||
line-height: $d;
|
||||
width: ($d * $colorsPerRow) + ($m * $colorsPerRow);
|
||||
|
||||
&.l-option-row {
|
||||
margin-bottom: $interiorMargin;
|
||||
.s-palette-item {
|
||||
border-color: $colorPaletteFg;
|
||||
}
|
||||
}
|
||||
|
||||
.l-palette-item {
|
||||
box-sizing: border-box;
|
||||
@include txtShdwSubtle(0.8);
|
||||
@include trans-prop-nice-fade(0.25s);
|
||||
border: 1px solid transparent;
|
||||
color: $colorSelectedColor;
|
||||
display: block;
|
||||
float: left;
|
||||
height: $d; width: $d;
|
||||
line-height: $d * 0.9;
|
||||
margin: 0 ($m * 1px) ($m * 1px) 0;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
&:before {
|
||||
// Check mark for selected items
|
||||
font-size: 0.8em;
|
||||
}
|
||||
}
|
||||
|
||||
.s-palette-item {
|
||||
border: 1px solid transparent;
|
||||
color: $colorPaletteFg;
|
||||
text-shadow: $shdwPaletteFg;
|
||||
@include trans-prop-nice-fade(0.25s);
|
||||
&:hover {
|
||||
@include trans-prop-nice-fade(0);
|
||||
border-color: $colorSelectedColor !important;
|
||||
border-color: $colorPaletteSelected !important;
|
||||
}
|
||||
&.selected {
|
||||
border-color: $colorPaletteSelected;
|
||||
box-shadow: $shdwPaletteSelected; //Needed to see selection rect on light colored swatches
|
||||
}
|
||||
}
|
||||
|
||||
.l-palette-item-label {
|
||||
margin-left: $interiorMargin;
|
||||
}
|
||||
|
||||
&.l-option-row {
|
||||
margin-bottom: $interiorMargin;
|
||||
.s-palette-item {
|
||||
border-color: $colorBodyFg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@
|
||||
@if $enableImageryThumbs == true {
|
||||
bottom: $interiorMargin*2 + $imageThumbsWrapperH;
|
||||
}
|
||||
min-height: 100px;
|
||||
min-width: 150px;
|
||||
.l-image-main {
|
||||
background-color: $colorPlotBg;
|
||||
@@ -21,9 +22,7 @@
|
||||
|
||||
.l-image-thumbs-wrapper {
|
||||
top: auto;
|
||||
min-height: $imageThumbsWrapperH;
|
||||
max-height: 60%;
|
||||
box-sizing: border-box;
|
||||
height: $imageThumbsWrapperH;
|
||||
}
|
||||
|
||||
.l-date,
|
||||
@@ -44,16 +43,14 @@
|
||||
.l-image-main-controlbar {
|
||||
font-size: 0.8em;
|
||||
line-height: inherit;
|
||||
.l-datetime-w, .l-controls-w {
|
||||
.left, .right {
|
||||
direction: rtl;
|
||||
overflow: hidden;
|
||||
}
|
||||
.l-datetime-w {
|
||||
@include ellipsize();
|
||||
margin-right: $interiorMarginSm;
|
||||
.left {
|
||||
text-align: left;
|
||||
}
|
||||
.l-controls-w {
|
||||
.right {
|
||||
z-index: 2;
|
||||
}
|
||||
.l-date,
|
||||
@@ -85,8 +82,9 @@
|
||||
/*************************************** THUMBS */
|
||||
|
||||
.l-image-thumbs-wrapper {
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
//@include test(green);
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
padding-bottom: $interiorMargin;
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -94,17 +92,8 @@
|
||||
.l-image-thumb-item {
|
||||
@include transition(background-color, 0.25s);
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
direction: ltr;
|
||||
display: inline-block;
|
||||
float: left;
|
||||
font-size: 0.8em;
|
||||
padding: 1px;
|
||||
margin-left: $interiorMarginSm;
|
||||
position: relative;
|
||||
text-align: left;
|
||||
width: $imageThumbsD + $imageThumbPad*2;
|
||||
white-space: normal;
|
||||
position: relative;
|
||||
.l-thumb,
|
||||
.l-date,
|
||||
.l-time {
|
||||
@@ -114,7 +103,14 @@
|
||||
.l-time {
|
||||
padding: 2px 3px;
|
||||
}
|
||||
|
||||
cursor: pointer;
|
||||
direction: ltr;
|
||||
display: inline-block;
|
||||
font-size: 0.8em;
|
||||
margin-left: $interiorMarginSm;
|
||||
text-align: left;
|
||||
width: $imageThumbsD + $imageThumbPad*2;
|
||||
white-space: normal;
|
||||
&:hover {
|
||||
background: $colorThumbHoverBg;
|
||||
.l-date,
|
||||
@@ -140,7 +136,6 @@
|
||||
/*************************************** LOCAL CONTROLS */
|
||||
.l-local-controls {
|
||||
max-width: 200px;
|
||||
min-width: 100px;
|
||||
width: 35%;
|
||||
input[type="range"] {
|
||||
display: block;
|
||||
@@ -189,8 +184,7 @@
|
||||
/*************************************** WHEN IN FRAME */
|
||||
.frame .t-imagery {
|
||||
.l-image-main-wrapper {
|
||||
bottom: 0 !important;
|
||||
height: 100% !important;
|
||||
bottom: 0;
|
||||
.l-image-main-controlbar {
|
||||
font-size: 0.7em;
|
||||
}
|
||||
@@ -200,8 +194,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.l-image-thumbs-wrapper,
|
||||
mct-splitter {
|
||||
.l-image-thumbs-wrapper {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,12 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
.section-header {
|
||||
border-radius: $basicCr;
|
||||
background: $colorFormSectionHeader;
|
||||
color: lighten($colorBodyFg, 20%);
|
||||
font-size: 0.8em;
|
||||
margin: $interiorMargin 0;
|
||||
padding: $formTBPad $formLRPad;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
@@ -41,15 +47,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.section-header {
|
||||
border-radius: $basicCr;
|
||||
background: $colorFormSectionHeader;
|
||||
$c: lighten($colorBodyFg, 20%);
|
||||
color: $c;
|
||||
font-size: 0.8em;
|
||||
padding: $formTBPad $formLRPad;
|
||||
}
|
||||
|
||||
.form-row {
|
||||
$m: $interiorMargin;
|
||||
box-sizing: border-box;
|
||||
@@ -171,3 +168,105 @@
|
||||
padding: $interiorMargin;
|
||||
}
|
||||
}
|
||||
|
||||
/**************************************************************************** COMPACT FORM */
|
||||
// ul > li > label, control
|
||||
// Make a new UL for each form section
|
||||
// Allow control-first, controls-below
|
||||
// TO-DO: migrate work in branch ch-plot-styling that users .inspector-config to use classes below instead
|
||||
|
||||
.l-compact-form .tree ul li,
|
||||
.l-compact-form ul li {
|
||||
padding: 2px 0;
|
||||
}
|
||||
|
||||
|
||||
.l-compact-form {
|
||||
$labelW: 40%;
|
||||
$minW: $labelW;
|
||||
ul {
|
||||
margin-bottom: $interiorMarginLg;
|
||||
li {
|
||||
@include display(flex);
|
||||
@include flex-wrap(wrap);
|
||||
@include align-items(center);
|
||||
label,
|
||||
.control {
|
||||
@include display(flex);
|
||||
//min-width: $minW;
|
||||
}
|
||||
label {
|
||||
line-height: inherit;
|
||||
padding: $interiorMarginSm 0;
|
||||
width: $labelW;
|
||||
}
|
||||
.controls {
|
||||
@include flex-grow(1);
|
||||
margin-left: $interiorMargin ;
|
||||
.e-control {
|
||||
// Individual form controls
|
||||
&:not(:first-child) {
|
||||
margin-left: $interiorMarginSm;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.section-header) {
|
||||
&:not(.connects-to-previous) {
|
||||
//border-top: 1px solid $colorFormLines;
|
||||
}
|
||||
}
|
||||
|
||||
&.connects-to-previous {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
&.section-header {
|
||||
margin-top: $interiorMarginLg;
|
||||
border-top: 1px solid $colorFormLines;
|
||||
}
|
||||
|
||||
&.controls-first {
|
||||
.control {
|
||||
@include flex-grow(0);
|
||||
margin-right: $interiorMargin;
|
||||
min-width: 0;
|
||||
order: 1;
|
||||
width: auto;
|
||||
}
|
||||
label {
|
||||
@include flex-grow(1);
|
||||
order: 2;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
&.controls-under {
|
||||
display: block;
|
||||
.control, label {
|
||||
display: block;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
ul li {
|
||||
border-top: none !important;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-error {
|
||||
// Block element that visually flags an error and contains a message
|
||||
background-color: $colorFormFieldErrorBg;
|
||||
color: $colorFormFieldErrorFg;
|
||||
border-radius: $basicCr;
|
||||
display: block;
|
||||
padding: 1px 6px;
|
||||
&:before {
|
||||
content: $glyph-icon-alert-triangle;
|
||||
display: inline;
|
||||
font-family: symbolsfont;
|
||||
margin-right: $interiorMarginSm;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,21 +52,13 @@ ul.tree {
|
||||
|
||||
.view-control {
|
||||
color: $colorItemTreeVC;
|
||||
font-size: 0.75em;
|
||||
margin-right: $interiorMargin;
|
||||
height: 100%;
|
||||
line-height: inherit;
|
||||
width: $treeVCW;
|
||||
&:before { display: none; }
|
||||
&.has-children {
|
||||
&:before {
|
||||
position: absolute;
|
||||
@include trans-prop-nice(transform, 100ms);
|
||||
content: "\e904";
|
||||
@include transform-origin(center);
|
||||
}
|
||||
&.expanded:before {
|
||||
@include transform(rotate(90deg));
|
||||
}
|
||||
&:before { display: block; }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -122,10 +122,7 @@
|
||||
|
||||
/********************************************************** OBJECT TYPES */
|
||||
.t-object-type-hyperlink {
|
||||
.object-holder {
|
||||
overflow: hidden;
|
||||
}
|
||||
.l-hyperlink.s-button {
|
||||
.s-hyperlink.s-button {
|
||||
// When a hyperlink is a button in a frame, make it expand to fill out to the object-holder
|
||||
@extend .abs;
|
||||
.label {
|
||||
@@ -145,7 +142,7 @@
|
||||
body.desktop .frame {
|
||||
// Hide local controls initially and show it them on hover when they're in an element that's in a frame context
|
||||
// Frame template is used because we need to target the lowest nested frame
|
||||
.object-browse-bar .btn-bar {
|
||||
.right {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
@@ -153,7 +150,7 @@ body.desktop .frame {
|
||||
// Target the first descendant so that we only show the elements in the outermost container.
|
||||
// Handles the case where we have layouts in layouts.
|
||||
&:hover > .object-browse-bar {
|
||||
.btn-bar {
|
||||
.right {
|
||||
opacity: 1;
|
||||
pointer-events: inherit;
|
||||
}
|
||||
|
||||
@@ -240,9 +240,7 @@ body.desktop .pane .mini-tab-icon.toggle-pane {
|
||||
.top-bar .buttons-main .s-button,
|
||||
.top-bar .s-menu-button,
|
||||
.tool-bar .s-button,
|
||||
.tool-bar .s-menu-button,
|
||||
.tool-bar .select,
|
||||
.tool-bar .input-labeled {
|
||||
.tool-bar .s-menu-button {
|
||||
$h: $btnToolbarH;
|
||||
height: $h;
|
||||
line-height: $h;
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
.tool-bar {
|
||||
font-size: 0.7rem;
|
||||
&.btn-bar {
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -31,7 +30,9 @@
|
||||
input[type="search"],
|
||||
input[type="number"] {
|
||||
box-sizing: border-box;
|
||||
font-size: .8em;
|
||||
height: $btnToolbarH;
|
||||
margin-bottom: 1px;
|
||||
position: relative;
|
||||
&.sm {
|
||||
width: $btnToolbarH;
|
||||
|
||||
@@ -117,8 +117,6 @@ define(
|
||||
|
||||
// Apply styles to child elements
|
||||
function updateChildren(children) {
|
||||
position = userWidthPreference || position;
|
||||
|
||||
// Pick out correct elements to update, flowing from
|
||||
// selected anchor edge.
|
||||
var first = children.eq(anchor.reversed ? 2 : 0),
|
||||
@@ -128,7 +126,7 @@ define(
|
||||
|
||||
splitterSize = getSize(splitter[0]);
|
||||
first.css(anchor.edge, "0px");
|
||||
first.css(anchor.dimension, position + 'px');
|
||||
first.css(anchor.dimension, (userWidthPreference || position) + 'px');
|
||||
|
||||
// Get actual size (to obey min-width etc.)
|
||||
firstSize = getSize(first[0]);
|
||||
@@ -137,8 +135,8 @@ define(
|
||||
splitter.css(anchor.opposite, "auto");
|
||||
|
||||
last.css(anchor.edge, firstSize + splitterSize + 'px');
|
||||
last.css(anchor.opposite, '0px');
|
||||
position = firstSize;
|
||||
last.css(anchor.opposite, "0px");
|
||||
position = firstSize + splitterSize;
|
||||
}
|
||||
|
||||
// Update positioning of contained elements
|
||||
@@ -175,19 +173,17 @@ define(
|
||||
positionParsed.assign($scope, position);
|
||||
}
|
||||
}
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
function setUserWidthPreference(value) {
|
||||
if (alias) {
|
||||
userWidthPreference = value;
|
||||
}
|
||||
userWidthPreference = value - splitterSize;
|
||||
}
|
||||
|
||||
function persistToLocalStorage(value) {
|
||||
if (alias) {
|
||||
$window.localStorage.setItem(alias, value);
|
||||
userWidthPreference = value - splitterSize;
|
||||
$window.localStorage.setItem(alias, userWidthPreference);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,13 +225,13 @@ define(
|
||||
anchor: function () {
|
||||
return anchor;
|
||||
},
|
||||
position: function (newPosition) {
|
||||
if (arguments.length === 0) {
|
||||
position: function (value) {
|
||||
if (arguments.length > 0) {
|
||||
setUserWidthPreference(value);
|
||||
return getSetPosition(value);
|
||||
} else {
|
||||
return getSetPosition();
|
||||
}
|
||||
|
||||
setUserWidthPreference(newPosition);
|
||||
return getSetPosition(newPosition);
|
||||
},
|
||||
startResizing: function () {
|
||||
toggleClass('resizing');
|
||||
|
||||
@@ -57,10 +57,7 @@ define(
|
||||
|
||||
// Update the position of this splitter
|
||||
newPosition = initialPosition + pixelDelta;
|
||||
|
||||
if (initialPosition !== newPosition) {
|
||||
mctSplitPane.position(newPosition);
|
||||
}
|
||||
mctSplitPane.position(newPosition);
|
||||
},
|
||||
// Grab the event when the user is done moving
|
||||
// the splitter and pass it on
|
||||
|
||||
@@ -140,7 +140,7 @@ define(
|
||||
|
||||
it("exposes its splitter's initial position", function () {
|
||||
expect(controller.position()).toEqual(
|
||||
mockFirstPane[0].offsetWidth
|
||||
mockFirstPane[0].offsetWidth + mockSplitter[0].offsetWidth
|
||||
);
|
||||
});
|
||||
|
||||
@@ -168,7 +168,7 @@ define(
|
||||
controller.position(testValue);
|
||||
expect(mockFirstPane.css).toHaveBeenCalledWith(
|
||||
'width',
|
||||
(testValue) + 'px'
|
||||
(testValue - mockSplitter[0].offsetWidth) + 'px'
|
||||
);
|
||||
});
|
||||
|
||||
@@ -200,11 +200,11 @@ define(
|
||||
mockFirstPane[0].offsetWidth += 100;
|
||||
// Should not reflect the change yet
|
||||
expect(controller.position()).not.toEqual(
|
||||
mockFirstPane[0].offsetWidth
|
||||
mockFirstPane[0].offsetWidth + mockSplitter[0].offsetWidth
|
||||
);
|
||||
mockInterval.mostRecentCall.args[0]();
|
||||
expect(controller.position()).toEqual(
|
||||
mockFirstPane[0].offsetWidth
|
||||
mockFirstPane[0].offsetWidth + mockSplitter[0].offsetWidth
|
||||
);
|
||||
});
|
||||
|
||||
@@ -216,7 +216,7 @@ define(
|
||||
|
||||
it("saves user preference to localStorage when user is done resizing", function () {
|
||||
controller.endResizing(100);
|
||||
expect(Number(mockWindow.localStorage.getItem('mctSplitPane-rightSide'))).toEqual(100);
|
||||
expect(Number(mockWindow.localStorage.getItem('mctSplitPane-rightSide'))).toEqual(100 - mockSplitter[0].offsetWidth);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -53,15 +53,9 @@ $timeControllerToiLineColor: #00c2ff;
|
||||
$timeControllerToiLineColorHov: #fff;
|
||||
$colorTransLucBg: #666; // Used as a visual blocking element over variable backgrounds, like imagery
|
||||
|
||||
// Foundation Colors
|
||||
// General Colors
|
||||
$colorAlt1: #ffc700;
|
||||
$colorAlert: #ff3c00;
|
||||
$colorWarningHi: #cc0000;
|
||||
$colorWarningLo: #ff9900;
|
||||
$colorDiagnostic: #a4b442;
|
||||
$colorCommand: #3693bd;
|
||||
$colorInfo: #2294a2;
|
||||
$colorOk: #33cc33;
|
||||
$colorIconLink: #49dedb;
|
||||
$colorPausedBg: #c56f01;
|
||||
$colorPausedFg: #fff;
|
||||
@@ -90,8 +84,8 @@ $colorCreateMenuText: $colorMenuFg;
|
||||
// Form colors
|
||||
$colorCheck: $colorKey;
|
||||
$colorFormRequired: $colorAlt1;
|
||||
$colorFormValid: $colorOk;
|
||||
$colorFormError: $colorWarningHi;
|
||||
$colorFormValid: #33cc33;
|
||||
$colorFormError: #990000;
|
||||
$colorFormInvalid: #ff3300;
|
||||
$colorFormFieldErrorBg: $colorFormError;
|
||||
$colorFormFieldErrorFg: rgba(#fff, 0.6);
|
||||
@@ -115,8 +109,8 @@ $colorInspectorSectionHeaderFg: pullForward($colorInspectorBg, 40%);
|
||||
// Status colors, mainly used for messaging and item ancillary symbols
|
||||
$colorStatusFg: #ccc;
|
||||
$colorStatusDefault: #ccc;
|
||||
$colorStatusInfo: $colorInfo;
|
||||
$colorStatusAlert: $colorAlert;
|
||||
$colorStatusInfo: #62ba72;
|
||||
$colorStatusAlert: #ffa66d;
|
||||
$colorStatusError: #d4585c;
|
||||
$colorStatusAvailable: $colorKey;
|
||||
$colorStatusBtnBg: $colorBtnBg;
|
||||
@@ -131,14 +125,14 @@ $animPausedPulseDur: 500ms;
|
||||
$colorSelectBg: $colorBtnBg;
|
||||
$colorSelectFg: $colorBtnFg;
|
||||
|
||||
// Limits, status and staleness colors
|
||||
// Limits and staleness colors
|
||||
$colorTelemFresh: pullForward($colorBodyFg, 20%);
|
||||
$colorTelemStale: pushBack($colorBodyFg, 20%);
|
||||
$styleTelemStale: italic;
|
||||
$colorLimitYellowBg: rgba($colorWarningLo, 0.3);
|
||||
$colorLimitYellowIc: $colorWarningLo;
|
||||
$colorLimitRedBg: rgba($colorWarningHi, 0.3);
|
||||
$colorLimitRedIc: $colorWarningHi;
|
||||
$colorLimitYellowBg: rgba(#ffaa00, 0.3);
|
||||
$colorLimitYellowIc: #ffaa00;
|
||||
$colorLimitRedBg: rgba(red, 0.3);
|
||||
$colorLimitRedIc: red;
|
||||
|
||||
// Bubble colors
|
||||
$colorInfoBubbleBg: #ddd;
|
||||
@@ -241,6 +235,12 @@ $colorCalCellSelectedBg: $colorItemTreeSelectedBg;
|
||||
$colorCalCellSelectedFg: $colorItemTreeSelectedFg;
|
||||
$colorCalCellInMonthBg: pushBack($colorMenuBg, 5%);
|
||||
|
||||
// Palettes
|
||||
$colorPaletteFg: pullForward($colorMenuBg, 30%);
|
||||
$colorPaletteSelected: #fff;
|
||||
$shdwPaletteFg: black 0 0 2px;
|
||||
$shdwPaletteSelected: inset 0 0 0 1px #000;
|
||||
|
||||
// About Screen
|
||||
$colorAboutLink: #84b3ff;
|
||||
|
||||
|
||||
@@ -13,10 +13,3 @@
|
||||
// For dark interfaces, darker things move back - opposite for light interfaces
|
||||
@return darken($c, $p);
|
||||
}
|
||||
|
||||
@function bgFg($c) {
|
||||
// Given a single color, return valid background and foreground versions of that color
|
||||
$bg: darken($c, 20%);
|
||||
$fg: lighten($c, 20%);
|
||||
@return $bg, $fg;
|
||||
}
|
||||
|
||||
@@ -53,15 +53,9 @@ $timeControllerToiLineColor: $colorBodyFg;
|
||||
$timeControllerToiLineColorHov: #0052b5;
|
||||
$colorTransLucBg: #666; // Used as a visual blocking element over variable backgrounds, like imagery
|
||||
|
||||
// Foundation Colors
|
||||
// General Colors
|
||||
$colorAlt1: #776ba2;
|
||||
$colorAlert: #ff3c00;
|
||||
$colorWarningHi: #990000;
|
||||
$colorWarningLo: #ff9900;
|
||||
$colorDiagnostic: #a4b442;
|
||||
$colorCommand: #3693bd;
|
||||
$colorInfo: #2294a2;
|
||||
$colorOk: #33cc33;
|
||||
$colorIconLink: #49dedb;
|
||||
$colorPausedBg: #ff9900;
|
||||
$colorPausedFg: #fff;
|
||||
@@ -90,8 +84,8 @@ $colorCreateMenuText: $colorBodyFg;
|
||||
// Form colors
|
||||
$colorCheck: $colorKey;
|
||||
$colorFormRequired: $colorKey;
|
||||
$colorFormValid: $colorOk;
|
||||
$colorFormError: $colorWarningHi;
|
||||
$colorFormValid: #33cc33;
|
||||
$colorFormError: #990000;
|
||||
$colorFormInvalid: #ff2200;
|
||||
$colorFormFieldErrorBg: $colorFormError;
|
||||
$colorFormFieldErrorFg: rgba(#fff, 0.6);
|
||||
@@ -113,7 +107,7 @@ $colorInspectorSectionHeaderBg: pullForward($colorInspectorBg, 5%);
|
||||
$colorInspectorSectionHeaderFg: pullForward($colorInspectorBg, 40%);
|
||||
|
||||
// Status colors, mainly used for messaging and item ancillary symbols
|
||||
$colorStatusFg: #999;
|
||||
$colorStatusFg: #fff;
|
||||
$colorStatusDefault: #ccc;
|
||||
$colorStatusInfo: #60ba7b;
|
||||
$colorStatusAlert: #ffb66c;
|
||||
@@ -241,6 +235,12 @@ $colorCalCellSelectedBg: $colorItemTreeSelectedBg;
|
||||
$colorCalCellSelectedFg: $colorItemTreeSelectedFg;
|
||||
$colorCalCellInMonthBg: pullForward($colorMenuBg, 5%);
|
||||
|
||||
// Palettes
|
||||
$colorPaletteFg: pullForward($colorMenuBg, 30%);
|
||||
$colorPaletteSelected: #333;
|
||||
$shdwPaletteFg: none;
|
||||
$shdwPaletteSelected: inset 0 0 0 1px #fff;
|
||||
|
||||
// About Screen
|
||||
$colorAboutLink: #84b3ff;
|
||||
|
||||
|
||||
@@ -12,11 +12,3 @@
|
||||
// For dark interfaces, darker things move back - opposite for light interfaces
|
||||
@return lighten($c, $p);
|
||||
}
|
||||
|
||||
@function bgFg($c) {
|
||||
// Given a single color, return valid background and foreground versions of that color
|
||||
$bg: darken($c, 20%);
|
||||
$fg: lighten($c, 20%);
|
||||
@return $bg, $fg;
|
||||
}
|
||||
|
||||
|
||||
@@ -65,20 +65,6 @@ define(['csv'], function (CSV) {
|
||||
this.saveAs(blob, filename);
|
||||
};
|
||||
|
||||
/**
|
||||
* Export an object as a JSON file. Triggers a download using the function
|
||||
* provided when the ExportService was instantiated.
|
||||
*
|
||||
* @param {Object} obj an object to be exported as JSON
|
||||
* @param {ExportOptions} [options] additional parameters for the file
|
||||
* export
|
||||
*/
|
||||
ExportService.prototype.exportJSON = function (obj, options) {
|
||||
var filename = (options && options.filename) || "test-export.json";
|
||||
var jsonText = JSON.stringify(obj);
|
||||
var blob = new Blob([jsonText], {type: "application/json"});
|
||||
this.saveAs(blob, filename);
|
||||
};
|
||||
/**
|
||||
* Additional parameters for file export.
|
||||
* @typedef ExportOptions
|
||||
|
||||
@@ -122,6 +122,14 @@ define([
|
||||
"description": "Set border color",
|
||||
"control": "color"
|
||||
},
|
||||
{
|
||||
"property": "color",
|
||||
"cssClass": "icon-T",
|
||||
"title": "Text color",
|
||||
"description": "Set text color",
|
||||
"mandatory": true,
|
||||
"control": "color"
|
||||
},
|
||||
{
|
||||
"property": "url",
|
||||
"cssClass": "icon-image",
|
||||
@@ -137,27 +145,6 @@ define([
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"property": "color",
|
||||
"cssClass": "icon-T",
|
||||
"title": "Text color",
|
||||
"description": "Set text color",
|
||||
"mandatory": true,
|
||||
"control": "color"
|
||||
},
|
||||
{
|
||||
"property": "size",
|
||||
"title": "Text size",
|
||||
"description": "Set text size",
|
||||
"control": "select",
|
||||
"options": [9, 10, 11, 12, 13, 14, 15, 16, 20, 24, 30, 36, 48, 72, 96].map(function (size) {
|
||||
return { "name": size + " px", "value": size + "px" };
|
||||
})
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
@@ -225,7 +212,11 @@ define([
|
||||
"control": "numberfield",
|
||||
"description": "Resize object width",
|
||||
"min": "1"
|
||||
},
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"property": "useGrid",
|
||||
"name": "Snap to Grid",
|
||||
|
||||
@@ -19,10 +19,10 @@
|
||||
this source code distribution or the Licensing information page available
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
<a class="l-hyperlink s-hyperlink" ng-controller="HyperlinkController as hyperlink" href="{{domainObject.getModel().url}}"
|
||||
<a class="s-hyperlink" ng-controller="HyperlinkController as hyperlink" href="{{domainObject.getModel().url}}"
|
||||
ng-attr-target="{{hyperlink.openNewTab() ? '_blank' : undefined}}"
|
||||
ng-class="{
|
||||
's-button': hyperlink.isButton()
|
||||
}">
|
||||
<span class="label">{{domainObject.getModel().displayText}}</span>
|
||||
<div class="label">{{domainObject.getModel().displayText}}</div>
|
||||
</a>
|
||||
|
||||
@@ -25,12 +25,14 @@ define([
|
||||
"./src/controllers/ImageryController",
|
||||
"./src/directives/MCTBackgroundImage",
|
||||
"text!./res/templates/imagery.html",
|
||||
"text!./res/templates/imageryTimeline.html",
|
||||
'legacyRegistry'
|
||||
], function (
|
||||
ImageryViewPolicy,
|
||||
ImageryController,
|
||||
MCTBackgroundImage,
|
||||
imageryTemplate,
|
||||
imageryTimelineTemplate,
|
||||
legacyRegistry
|
||||
) {
|
||||
|
||||
@@ -48,6 +50,17 @@ define([
|
||||
"telemetry"
|
||||
],
|
||||
"editable": false
|
||||
},
|
||||
{
|
||||
"name": "Historical Imagery",
|
||||
"key": "historical-imagery",
|
||||
"cssClass": "icon-image",
|
||||
"template": imageryTimelineTemplate,
|
||||
"priority": "preferred",
|
||||
"needs": [
|
||||
"telemetry"
|
||||
],
|
||||
"editable": false
|
||||
}
|
||||
],
|
||||
"policies": [
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<div class="t-imagery" ng-controller="ImageryController as imagery">
|
||||
<mct-split-pane class='abs' anchor="bottom" alias="imagery">
|
||||
<div class="split-pane-component l-image-main-wrapper l-flex-col"
|
||||
<div class="l-image-main-wrapper l-flex-col"
|
||||
ng-mouseenter="showLocalControls = true;"
|
||||
ng-mouseleave="showLocalControls = false;">
|
||||
<div class="l-local-controls s-local-controls s-wrapper-transluc l-flex-row"
|
||||
@@ -33,12 +32,12 @@
|
||||
</div>
|
||||
|
||||
<div class="l-image-main-controlbar flex-elem l-flex-row">
|
||||
<div class="l-datetime-w flex-elem grows">
|
||||
<div class="left flex-elem grows">
|
||||
<a class="s-button show-thumbs sm hidden icon-thumbs-strip"
|
||||
ng-click="showThumbsBubble = (showThumbsBubble) ? false:true"></a>
|
||||
<span class="l-time">{{imagery.getTime()}}</span>
|
||||
</div>
|
||||
<div class="l-controls-w flex-elem">
|
||||
<div class="right flex-elem">
|
||||
<a class="s-button pause-play"
|
||||
ng-click="imagery.paused(!imagery.paused())"
|
||||
ng-class="{ paused: imagery.paused() }"></a>
|
||||
@@ -56,14 +55,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<mct-splitter></mct-splitter>
|
||||
<div class="split-pane-component l-image-thumbs-wrapper">
|
||||
<div class="l-image-thumb-item" ng-class="{selected: image.selected}" ng-repeat="image in imageHistory track by $index"
|
||||
ng-click="imagery.setSelectedImage(image)" ng-init="imagery.scrollToBottom()">
|
||||
<img class="l-thumb"
|
||||
ng-src={{imagery.getImageUrl(image)}}>
|
||||
<div class="l-time">{{imagery.getTime(image)}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</mct-split-pane>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
|
||||
<div class="l-image-thumbs-wrapper" ng-controller="ImageryController as imagery">
|
||||
<div class="l-image-thumb-item" ng-repeat="image in imageHistory track by $index">
|
||||
<img class="l-thumb" ng-init="imagery.scrollToRight()"
|
||||
ng-src={{imagery.getImageUrl(image)}} >
|
||||
<div class="l-time">{{imagery.getTime(image)}}</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -48,8 +48,9 @@ define(
|
||||
this.zone = "";
|
||||
this.imageUrl = "";
|
||||
this.requestCount = 0;
|
||||
this.scrollable = $(".l-image-thumbs-wrapper");
|
||||
this.scrollable = $(element[0]);
|
||||
this.autoScroll = openmct.time.clock() ? true : false;
|
||||
|
||||
this.$scope.imageHistory = [];
|
||||
this.$scope.filters = {
|
||||
brightness: 100,
|
||||
@@ -62,7 +63,6 @@ define(
|
||||
this.updateHistory = this.updateHistory.bind(this);
|
||||
this.onBoundsChange = this.onBoundsChange.bind(this);
|
||||
this.onScroll = this.onScroll.bind(this);
|
||||
this.setSelectedImage = this.setSelectedImage.bind(this);
|
||||
|
||||
this.subscribe(this.$scope.domainObject);
|
||||
|
||||
@@ -80,10 +80,10 @@ define(
|
||||
var metadata = this.openmct
|
||||
.telemetry
|
||||
.getMetadata(this.domainObject);
|
||||
this.timeKey = this.openmct.time.timeSystem().key;
|
||||
var timeKey = this.openmct.time.timeSystem().key;
|
||||
this.timeFormat = this.openmct
|
||||
.telemetry
|
||||
.getValueFormatter(metadata.value(this.timeKey));
|
||||
.getValueFormatter(metadata.value(timeKey));
|
||||
this.imageFormat = this.openmct
|
||||
.telemetry
|
||||
.getValueFormatter(metadata.valuesForHints(['image'])[0]);
|
||||
@@ -161,7 +161,7 @@ define(
|
||||
|
||||
/**
|
||||
* Updates displayable values to match those of the most
|
||||
* recently received datum.
|
||||
* recently recieved datum.
|
||||
* @param {object} [datum] the datum
|
||||
* @private
|
||||
*/
|
||||
@@ -170,6 +170,7 @@ define(
|
||||
this.nextDatum = datum;
|
||||
return;
|
||||
}
|
||||
|
||||
this.time = this.timeFormat.format(datum);
|
||||
this.imageUrl = this.imageFormat.format(datum);
|
||||
|
||||
@@ -184,7 +185,8 @@ define(
|
||||
ImageryController.prototype.updateHistory = function (datum) {
|
||||
if (this.$scope.imageHistory.length === 0 ||
|
||||
!_.isEqual(this.$scope.imageHistory.slice(-1)[0], datum)) {
|
||||
var index = _.sortedIndex(this.$scope.imageHistory, datum, this.timeFormat.format.bind(this.timeFormat));
|
||||
|
||||
var index = _.sortedIndex(this.$scope.imageHistory, datum, 'utc');
|
||||
this.$scope.imageHistory.splice(index, 0, datum);
|
||||
return true;
|
||||
}
|
||||
@@ -194,12 +196,8 @@ define(
|
||||
|
||||
ImageryController.prototype.onScroll = function (event) {
|
||||
this.$window.requestAnimationFrame(function () {
|
||||
var thumbnailWrapperHeight = this.scrollable[0].offsetHeight;
|
||||
var thumbnailWrapperWidth = this.scrollable[0].offsetWidth;
|
||||
if (this.scrollable[0].scrollLeft <
|
||||
(this.scrollable[0].scrollWidth - this.scrollable[0].clientWidth) - (thumbnailWrapperWidth) ||
|
||||
this.scrollable[0].scrollTop <
|
||||
(this.scrollable[0].scrollHeight - this.scrollable[0].clientHeight) - (thumbnailWrapperHeight)) {
|
||||
(this.scrollable[0].scrollWidth - this.scrollable[0].clientWidth) - 20) {
|
||||
this.autoScroll = false;
|
||||
} else {
|
||||
this.autoScroll = true;
|
||||
@@ -207,16 +205,12 @@ define(
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
/**
|
||||
* Force history imagery div to scroll to bottom.
|
||||
*/
|
||||
ImageryController.prototype.scrollToBottom = function () {
|
||||
ImageryController.prototype.scrollToRight = function () {
|
||||
if (this.autoScroll) {
|
||||
this.scrollable[0].scrollTop = this.scrollable[0].scrollHeight;
|
||||
this.scrollable[0].scrollLeft = this.scrollable[0].scrollWidth;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the time portion (hours, minutes, seconds) of the
|
||||
* timestamp associated with the incoming image telemetry
|
||||
@@ -249,38 +243,16 @@ define(
|
||||
* @returns {boolean} the current state
|
||||
*/
|
||||
ImageryController.prototype.paused = function (state) {
|
||||
if (arguments.length > 0 && state !== this.isPaused) {
|
||||
this.unselectAllImages();
|
||||
this.isPaused = state;
|
||||
if (this.nextDatum) {
|
||||
this.updateValues(this.nextDatum);
|
||||
delete this.nextDatum;
|
||||
if (arguments.length > 0 && state !== this.isPaused) {
|
||||
this.isPaused = state;
|
||||
if (this.nextDatum) {
|
||||
this.updateValues(this.nextDatum);
|
||||
delete this.nextDatum;
|
||||
}
|
||||
}
|
||||
this.autoScroll = true;
|
||||
}
|
||||
return this.isPaused;
|
||||
};
|
||||
return this.isPaused;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the selected image on the state for the large imagery div to use.
|
||||
* @param {object} [image] the image object to get url from.
|
||||
*/
|
||||
ImageryController.prototype.setSelectedImage = function (image) {
|
||||
this.imageUrl = this.getImageUrl(image);
|
||||
this.time = this.getTime(image);
|
||||
this.paused(true);
|
||||
this.unselectAllImages();
|
||||
image.selected = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Loop through the history imagery data to set all images to unselected.
|
||||
*/
|
||||
ImageryController.prototype.unselectAllImages = function () {
|
||||
for (var i = 0; i < this.$scope.imageHistory.length; i++) {
|
||||
this.$scope.imageHistory[i].selected = false;
|
||||
}
|
||||
};
|
||||
return ImageryController;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -226,28 +226,6 @@ define(
|
||||
expect(controller.updateHistory(mockDatum)).toBe(false);
|
||||
expect(controller.updateHistory(mockDatum)).toBe(false);
|
||||
});
|
||||
|
||||
describe("user clicks on imagery thumbnail", function () {
|
||||
var mockDatum = { utc: 1434600258123, url: 'some/url', selected: false};
|
||||
|
||||
it("pauses and adds selected class to imagery thumbnail", function () {
|
||||
controller.setSelectedImage(mockDatum);
|
||||
expect(controller.paused()).toBeTruthy();
|
||||
expect(mockDatum.selected).toBeTruthy();
|
||||
});
|
||||
|
||||
it("unselects previously selected image", function () {
|
||||
$scope.imageHistory = [{ utc: 1434600258123, url: 'some/url', selected: true}];
|
||||
controller.unselectAllImages();
|
||||
expect($scope.imageHistory[0].selected).toBeFalsy();
|
||||
});
|
||||
|
||||
it("updates larger image url and time", function () {
|
||||
controller.setSelectedImage(mockDatum);
|
||||
expect(controller.getImageUrl()).toEqual(controller.getImageUrl(mockDatum));
|
||||
expect(controller.getTime()).toEqual(controller.timeFormat.format(mockDatum.utc));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("initially shows an empty string for date/time", function () {
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
-->
|
||||
<div
|
||||
class="l-fixed-position-text l-telemetry"
|
||||
ng-style="{ background: ngModel.fill(), 'border-color': ngModel.stroke(), color: ngModel.color(), 'font-size': ngModel.size() }"
|
||||
ng-style="{ background: ngModel.fill(), 'border-color': ngModel.stroke(), color: ngModel.color() }"
|
||||
>
|
||||
<span
|
||||
class="l-elem l-value l-obj-val-format"
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
-->
|
||||
<div
|
||||
class="l-fixed-position-text l-static-text"
|
||||
ng-style="{ background: ngModel.fill(), 'border-color': ngModel.stroke(), color: ngModel.color(), 'font-size': ngModel.size() }"
|
||||
ng-style="{ background: ngModel.fill(), 'border-color': ngModel.stroke(), color: ngModel.color() }"
|
||||
>
|
||||
{{ngModel.element.text}}
|
||||
</div>
|
||||
|
||||
@@ -58,19 +58,6 @@ define(
|
||||
*/
|
||||
proxy.text = new AccessorMutator(element, 'text');
|
||||
|
||||
/**
|
||||
* Get and/or set the text size of this element.
|
||||
*
|
||||
* @param {string} [size] the new text size (if setting)
|
||||
* @returns {string} the text size
|
||||
* @memberof platform/features/layout.TextProxy#
|
||||
*/
|
||||
proxy.size = new AccessorMutator(element, 'size');
|
||||
|
||||
if (proxy.size() === undefined) {
|
||||
proxy.size("13px");
|
||||
}
|
||||
|
||||
return proxy;
|
||||
}
|
||||
|
||||
|
||||
@@ -35,8 +35,7 @@ define(
|
||||
y: 2,
|
||||
width: 42,
|
||||
height: 24,
|
||||
fill: "transparent",
|
||||
size: "20px"
|
||||
fill: "transparent"
|
||||
};
|
||||
testElements = [{}, {}, testElement, {}];
|
||||
proxy = new TextProxy(
|
||||
@@ -51,20 +50,6 @@ define(
|
||||
expect(proxy.fill('#FFF')).toEqual('#FFF');
|
||||
expect(proxy.fill()).toEqual('#FFF');
|
||||
});
|
||||
|
||||
it("provides getter/setter for text size", function () {
|
||||
expect(proxy.size()).toEqual('20px');
|
||||
expect(proxy.size('12px')).toEqual('12px');
|
||||
expect(proxy.size()).toEqual('12px');
|
||||
});
|
||||
|
||||
it("defaults to 13px for unspecified text size", function () {
|
||||
testElement = {x: 1, y: 2};
|
||||
proxy = new TextProxy(testElement, 0, [testElement]);
|
||||
|
||||
expect(proxy.size()).toEqual('13px');
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
@@ -118,10 +118,7 @@ define([
|
||||
"policies": [
|
||||
{
|
||||
"category": "view",
|
||||
"implementation": PlotViewPolicy,
|
||||
"depends": [
|
||||
"openmct"
|
||||
]
|
||||
"implementation": PlotViewPolicy
|
||||
}
|
||||
],
|
||||
"representations": [
|
||||
|
||||
@@ -30,25 +30,30 @@ define(
|
||||
* @constructor
|
||||
* @memberof platform/features/plot
|
||||
*/
|
||||
function PlotViewPolicy(openmct) {
|
||||
this.openmct = openmct;
|
||||
function PlotViewPolicy() {
|
||||
}
|
||||
|
||||
PlotViewPolicy.prototype.hasNumericTelemetry = function (domainObject) {
|
||||
var adaptedObject = domainObject.useCapability('adapter');
|
||||
var metadata = this.openmct.telemetry.getMetadata(adaptedObject);
|
||||
var rangeValues = metadata.valuesForHints(['range']);
|
||||
if (rangeValues.length === 0) {
|
||||
return false;
|
||||
}
|
||||
return !rangeValues.every(function (value) {
|
||||
return value.format === 'string';
|
||||
});
|
||||
};
|
||||
function hasNumericTelemetry(domainObject) {
|
||||
var telemetry = domainObject &&
|
||||
domainObject.getCapability('telemetry'),
|
||||
metadata = telemetry ? telemetry.getMetadata() : {},
|
||||
ranges = metadata.ranges || [];
|
||||
|
||||
// Generally, we want to allow Plot for telemetry-providing
|
||||
// objects (most telemetry is plottable.) We only want to
|
||||
// suppress this for telemetry which only has explicitly
|
||||
// non-numeric values.
|
||||
return ranges.length === 0 || ranges.some(function (range) {
|
||||
// Assume format is numeric if it is undefined
|
||||
// (numeric telemetry is the common case)
|
||||
return range.format === undefined ||
|
||||
range.format === 'number';
|
||||
});
|
||||
}
|
||||
|
||||
PlotViewPolicy.prototype.allow = function (view, domainObject) {
|
||||
if (view.key === 'plot') {
|
||||
return this.hasNumericTelemetry(domainObject);
|
||||
return hasNumericTelemetry(domainObject);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -27,71 +27,51 @@ define(
|
||||
describe("Plot view policy", function () {
|
||||
var testView,
|
||||
mockDomainObject,
|
||||
openmct,
|
||||
telemetryMetadata,
|
||||
mockTelemetry,
|
||||
testMetadata,
|
||||
policy;
|
||||
|
||||
beforeEach(function () {
|
||||
testView = { key: "plot" };
|
||||
testMetadata = {};
|
||||
mockDomainObject = jasmine.createSpyObj(
|
||||
'domainObject',
|
||||
['useCapability']
|
||||
['getId', 'getModel', 'getCapability']
|
||||
);
|
||||
mockDomainObject.useCapability.andReturn('adaptedObject');
|
||||
openmct = {
|
||||
telemetry: jasmine.createSpyObj('telemetryAPI', [
|
||||
'getMetadata'
|
||||
])
|
||||
};
|
||||
telemetryMetadata = jasmine.createSpyObj('telemetryMetadata', [
|
||||
'valuesForHints'
|
||||
]);
|
||||
telemetryMetadata.valuesForHints.andReturn([]);
|
||||
openmct.telemetry.getMetadata.andReturn(telemetryMetadata);
|
||||
policy = new PlotViewPolicy(openmct);
|
||||
mockTelemetry = jasmine.createSpyObj(
|
||||
'telemetry',
|
||||
['getMetadata']
|
||||
);
|
||||
mockDomainObject.getCapability.andCallFake(function (c) {
|
||||
return c === 'telemetry' ? mockTelemetry : undefined;
|
||||
});
|
||||
mockTelemetry.getMetadata.andReturn(testMetadata);
|
||||
|
||||
policy = new PlotViewPolicy();
|
||||
});
|
||||
|
||||
it('fetches metadata from telem api', function () {
|
||||
policy.allow(testView, mockDomainObject);
|
||||
expect(mockDomainObject.useCapability)
|
||||
.toHaveBeenCalledWith('adapter');
|
||||
expect(openmct.telemetry.getMetadata)
|
||||
.toHaveBeenCalledWith('adaptedObject');
|
||||
expect(telemetryMetadata.valuesForHints)
|
||||
.toHaveBeenCalledWith(['range']);
|
||||
it("allows the imagery view for domain objects with numeric telemetry", function () {
|
||||
testMetadata.ranges = [{ key: "foo", format: "number" }];
|
||||
expect(policy.allow(testView, mockDomainObject)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('returns false if no ranges exist', function () {
|
||||
telemetryMetadata.valuesForHints.andReturn([]);
|
||||
expect(policy.allow(testView, mockDomainObject)).toBe(false);
|
||||
it("allows the imagery view for domain objects with unspecified telemetry", function () {
|
||||
testMetadata.ranges = [{ key: "foo" }];
|
||||
expect(policy.allow(testView, mockDomainObject)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('returns true if any ranges exist', function () {
|
||||
telemetryMetadata.valuesForHints.andReturn([{}]);
|
||||
expect(policy.allow(testView, mockDomainObject)).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false if all ranges are strings', function () {
|
||||
telemetryMetadata.valuesForHints.andReturn([{
|
||||
format: 'string'
|
||||
}, {
|
||||
format: 'string'
|
||||
}]);
|
||||
expect(policy.allow(testView, mockDomainObject)).toBe(false);
|
||||
});
|
||||
|
||||
it('returns true if only some ranges are strings', function () {
|
||||
telemetryMetadata.valuesForHints.andReturn([{
|
||||
format: 'string'
|
||||
}, {}]);
|
||||
expect(policy.allow(testView, mockDomainObject)).toBe(true);
|
||||
it("disallows the imagery view for domain objects without image telemetry", function () {
|
||||
testMetadata.ranges = [{ key: "foo", format: "somethingElse" }];
|
||||
expect(policy.allow(testView, mockDomainObject)).toBeFalsy();
|
||||
});
|
||||
|
||||
it("allows other views", function () {
|
||||
testView.key = "somethingElse";
|
||||
expect(policy.allow(testView, mockDomainObject)).toBe(true);
|
||||
testMetadata.ranges = [{ key: "foo", format: "somethingElse" }];
|
||||
expect(policy.allow(testView, mockDomainObject)).toBeTruthy();
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -24,8 +24,6 @@ define([
|
||||
"./src/MCTForm",
|
||||
"./src/MCTToolbar",
|
||||
"./src/MCTControl",
|
||||
"./src/MCTFileInput",
|
||||
"./src/FileInputService",
|
||||
"./src/controllers/AutocompleteController",
|
||||
"./src/controllers/DateTimeController",
|
||||
"./src/controllers/CompositeController",
|
||||
@@ -44,14 +42,11 @@ define([
|
||||
"text!./res/templates/controls/menu-button.html",
|
||||
"text!./res/templates/controls/dialog.html",
|
||||
"text!./res/templates/controls/radio.html",
|
||||
"text!./res/templates/controls/file-input.html",
|
||||
'legacyRegistry'
|
||||
], function (
|
||||
MCTForm,
|
||||
MCTToolbar,
|
||||
MCTControl,
|
||||
MCTFileInput,
|
||||
FileInputService,
|
||||
AutocompleteController,
|
||||
DateTimeController,
|
||||
CompositeController,
|
||||
@@ -70,7 +65,6 @@ define([
|
||||
menuButtonTemplate,
|
||||
dialogTemplate,
|
||||
radioTemplate,
|
||||
fileInputTemplate,
|
||||
legacyRegistry
|
||||
) {
|
||||
|
||||
@@ -94,13 +88,6 @@ define([
|
||||
"templateLinker",
|
||||
"controls[]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "mctFileInput",
|
||||
"implementation": MCTFileInput,
|
||||
"depends": [
|
||||
"fileInputService"
|
||||
]
|
||||
}
|
||||
],
|
||||
"controls": [
|
||||
@@ -155,10 +142,6 @@ define([
|
||||
{
|
||||
"key": "dialog-button",
|
||||
"template": dialogTemplate
|
||||
},
|
||||
{
|
||||
"key": "file-input",
|
||||
"template": fileInputTemplate
|
||||
}
|
||||
],
|
||||
"controllers": [
|
||||
@@ -193,14 +176,6 @@ define([
|
||||
"dialogService"
|
||||
]
|
||||
}
|
||||
],
|
||||
"components": [
|
||||
{
|
||||
"provides": "fileInputService",
|
||||
"type": "provider",
|
||||
"implementation": FileInputService
|
||||
}
|
||||
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
@@ -24,21 +24,22 @@
|
||||
|
||||
<span class="l-click-area" ng-click="toggle.toggle()"></span>
|
||||
<span class="color-swatch"
|
||||
ng-class="{'no-selection':ngModel[field] === 'transparent'}"
|
||||
ng-style="{
|
||||
background: ngModel[field]
|
||||
'background-color': ngModel[field]
|
||||
}">
|
||||
</span>
|
||||
<span class="title-label" ng-if="structure.text">
|
||||
{{structure.text}}
|
||||
</span>
|
||||
|
||||
<div class="menu l-color-palette"
|
||||
<div class="menu l-palette l-color-palette"
|
||||
ng-controller="ColorController as colors"
|
||||
ng-show="toggle.isActive()">
|
||||
<div
|
||||
class="l-palette-row l-option-row"
|
||||
ng-if="!structure.mandatory">
|
||||
<div class="l-palette-item s-palette-item {{ngModel[field] === 'transparent' ? 'icon-check' : '' }}"
|
||||
<div class="l-palette-item s-palette-item no-selection {{ngModel[field] === 'transparent' ? 'selected' : '' }}"
|
||||
ng-click="ngModel[field] = 'transparent'">
|
||||
</div>
|
||||
<span class="l-palette-item-label">None</span>
|
||||
@@ -46,7 +47,7 @@
|
||||
<div
|
||||
class="l-palette-row"
|
||||
ng-repeat="group in colors.groups()">
|
||||
<div class="l-palette-item s-palette-item {{ngModel[field] === color ? 'icon-check' : '' }}"
|
||||
<div class="l-palette-item s-palette-item {{ngModel[field] === color ? 'selected' : '' }}"
|
||||
ng-repeat="color in group"
|
||||
ng-style="{ background: color }"
|
||||
ng-click="ngModel[field] = color">
|
||||
|
||||
@@ -1,30 +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.
|
||||
-->
|
||||
|
||||
<a class="s-button {{structure.cssClass}}"
|
||||
ng-model="ngModel[field]"
|
||||
ng-class="{ labeled: structure.text }"
|
||||
mct-file-input>
|
||||
<span class="title-label" ng-if="structure.text">
|
||||
{{structure.text}}
|
||||
</span>
|
||||
</a>
|
||||
@@ -1,90 +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(["zepto"], function ($) {
|
||||
|
||||
/**
|
||||
* The FileInputService provides an interface for triggering a file input.
|
||||
*
|
||||
* @constructor
|
||||
* @memberof platform/forms
|
||||
*/
|
||||
function FileInputService() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates, triggers, and destroys a file picker element and returns a
|
||||
* promise for an object containing the chosen file's name and contents.
|
||||
*
|
||||
* @returns {Promise} promise for an object containing file meta-data
|
||||
*/
|
||||
FileInputService.prototype.getInput = function () {
|
||||
var input = this.newInput();
|
||||
var read = this.readFile;
|
||||
var fileInfo = {};
|
||||
var file;
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
input.trigger("click");
|
||||
input.on('change', function (event) {
|
||||
file = this.files[0];
|
||||
input.remove();
|
||||
if (file) {
|
||||
read(file)
|
||||
.then(function (contents) {
|
||||
fileInfo.name = file.name;
|
||||
fileInfo.body = contents;
|
||||
resolve(fileInfo);
|
||||
}, function () {
|
||||
reject("File read error");
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
FileInputService.prototype.readFile = function (file) {
|
||||
var fileReader = new FileReader();
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
fileReader.onload = function (event) {
|
||||
resolve(event.target.result);
|
||||
};
|
||||
|
||||
fileReader.onerror = function () {
|
||||
return reject(event.target.result);
|
||||
};
|
||||
fileReader.readAsText(file);
|
||||
});
|
||||
};
|
||||
|
||||
FileInputService.prototype.newInput = function () {
|
||||
var input = $(document.createElement('input'));
|
||||
input.attr("type", "file");
|
||||
input.css("display", "none");
|
||||
$('body').append(input);
|
||||
return input;
|
||||
};
|
||||
|
||||
return FileInputService;
|
||||
});
|
||||
@@ -1,66 +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(
|
||||
['zepto'],
|
||||
function ($) {
|
||||
|
||||
/**
|
||||
* The mct-file-input handles behavior of the file input form control.
|
||||
* @constructor
|
||||
* @memberof platform/forms
|
||||
*/
|
||||
function MCTFileInput(fileInputService) {
|
||||
|
||||
function link(scope, element, attrs, control) {
|
||||
|
||||
function setText(fileName) {
|
||||
scope.structure.text = fileName.length > 20 ?
|
||||
fileName.substr(0, 20) + "..." :
|
||||
fileName;
|
||||
}
|
||||
|
||||
function handleClick() {
|
||||
fileInputService.getInput().then(function (result) {
|
||||
setText(result.name);
|
||||
scope.ngModel[scope.field] = result;
|
||||
control.$setValidity("file-input", true);
|
||||
}, function () {
|
||||
setText('Select File');
|
||||
control.$setValidity("file-input", false);
|
||||
});
|
||||
}
|
||||
|
||||
control.$setValidity("file-input", false);
|
||||
element.on('click', handleClick);
|
||||
}
|
||||
|
||||
return {
|
||||
restrict: "A",
|
||||
require: "^form",
|
||||
link: link
|
||||
};
|
||||
}
|
||||
|
||||
return MCTFileInput;
|
||||
}
|
||||
);
|
||||
@@ -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(
|
||||
["../src/FileInputService"],
|
||||
function (FileInputService) {
|
||||
|
||||
describe("The FileInputService", function () {
|
||||
var fileInputService,
|
||||
mockInput;
|
||||
|
||||
beforeEach(function () {
|
||||
fileInputService = new FileInputService();
|
||||
mockInput = jasmine.createSpyObj('input',
|
||||
[
|
||||
'on',
|
||||
'trigger',
|
||||
'remove'
|
||||
]
|
||||
);
|
||||
mockInput.on.andCallFake(function (event, changeHandler) {
|
||||
changeHandler.apply(mockInput);
|
||||
});
|
||||
spyOn(fileInputService, "newInput").andReturn(
|
||||
mockInput
|
||||
);
|
||||
|
||||
});
|
||||
|
||||
it("can read a file", function () {
|
||||
mockInput.files = [new File(["file content"], "file name")];
|
||||
fileInputService.getInput().then(function (result) {
|
||||
expect(result.name).toBe("file name");
|
||||
expect(result.body).toBe("file content");
|
||||
});
|
||||
|
||||
expect(mockInput.trigger).toHaveBeenCalledWith('click');
|
||||
expect(mockInput.remove).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("catches file read errors", function () {
|
||||
mockInput.files = ["GARBAGE"];
|
||||
fileInputService.getInput().then(
|
||||
function (result) {},
|
||||
function (err) {
|
||||
expect(err).toBe("File read error");
|
||||
}
|
||||
);
|
||||
|
||||
expect(mockInput.trigger).toHaveBeenCalledWith('click');
|
||||
expect(mockInput.remove).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -1,98 +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(
|
||||
["../src/MCTFileInput"],
|
||||
function (MCTFileInput) {
|
||||
|
||||
describe("The mct-file-input directive", function () {
|
||||
|
||||
var mockScope,
|
||||
mockFileInputService,
|
||||
mctFileInput,
|
||||
element,
|
||||
attrs,
|
||||
control;
|
||||
|
||||
beforeEach(function () {
|
||||
attrs = [];
|
||||
control = jasmine.createSpyObj('control', ['$setValidity']);
|
||||
element = jasmine.createSpyObj('element', ['on', 'trigger']);
|
||||
mockFileInputService = jasmine.createSpyObj('fileInputService',
|
||||
['getInput']
|
||||
);
|
||||
mockScope = jasmine.createSpyObj(
|
||||
'$scope',
|
||||
['$watch']
|
||||
);
|
||||
|
||||
mockScope.structure = {text: 'Select File'};
|
||||
mockScope.field = "file-input";
|
||||
mockScope.ngModel = {"file-input" : undefined};
|
||||
|
||||
element.on.andCallFake(function (event, clickHandler) {
|
||||
clickHandler();
|
||||
});
|
||||
mockFileInputService.getInput.andReturn(
|
||||
Promise.resolve({name: "file-name", body: "file-body"})
|
||||
);
|
||||
|
||||
mctFileInput = new MCTFileInput(mockFileInputService);
|
||||
|
||||
// Need to wait for mock promise
|
||||
var init = false;
|
||||
runs(function () {
|
||||
mctFileInput.link(mockScope, element, attrs, control);
|
||||
setTimeout(function () {
|
||||
init = true;
|
||||
}, 100);
|
||||
});
|
||||
|
||||
waitsFor(function () {
|
||||
return init;
|
||||
}, "File selection should have beeen simulated");
|
||||
});
|
||||
|
||||
it("is restricted to attributes", function () {
|
||||
expect(mctFileInput.restrict).toEqual("A");
|
||||
});
|
||||
|
||||
it("changes button text to match file name", function () {
|
||||
expect(element.on).toHaveBeenCalledWith(
|
||||
'click',
|
||||
jasmine.any(Function)
|
||||
);
|
||||
expect(mockScope.structure.text).toEqual("file-name");
|
||||
});
|
||||
|
||||
it("validates control on file selection", function () {
|
||||
expect(control.$setValidity.callCount).toBe(2);
|
||||
expect(control.$setValidity.argsForCall[0]).toEqual(
|
||||
['file-input', false]
|
||||
);
|
||||
expect(control.$setValidity.argsForCall[1]).toEqual(
|
||||
['file-input', true]
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -1,76 +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.
|
||||
*****************************************************************************/
|
||||
/*global define*/
|
||||
|
||||
define([
|
||||
"./src/actions/ExportAsJSONAction",
|
||||
"./src/actions/ImportAsJSONAction"
|
||||
], function (
|
||||
ExportAsJSONAction,
|
||||
ImportAsJSONAction
|
||||
) {
|
||||
|
||||
return function ImportExportPlugin() {
|
||||
return function (openmct) {
|
||||
ExportAsJSONAction.appliesTo = function (context) {
|
||||
return openmct.$injector.get('policyService')
|
||||
.allow("creation", context.domainObject.getCapability("type")
|
||||
);
|
||||
};
|
||||
|
||||
openmct.legacyRegistry.register("platform/import-export", {
|
||||
"name": "Import-export plugin",
|
||||
"description": "Allows importing / exporting of domain objects as JSON.",
|
||||
"extensions": {
|
||||
"actions": [
|
||||
{
|
||||
"key": "export.JSON",
|
||||
"name": "Export as JSON",
|
||||
"implementation": ExportAsJSONAction,
|
||||
"category": "contextual",
|
||||
"cssClass": "icon-save",
|
||||
"depends": [
|
||||
"exportService",
|
||||
"policyService",
|
||||
"identifierService"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "import.JSON",
|
||||
"name": "Import from JSON",
|
||||
"implementation": ImportAsJSONAction,
|
||||
"category": "contextual",
|
||||
"cssClass": "icon-download",
|
||||
"depends": [
|
||||
"exportService",
|
||||
"identifierService",
|
||||
"dialogService",
|
||||
"openmct"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
openmct.legacyRegistry.enable('platform/import-export');
|
||||
};
|
||||
};
|
||||
});
|
||||
@@ -1,162 +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 ExportAsJSONAction is available from context menus and allows a user
|
||||
* to export any creatable domain object as a JSON file.
|
||||
*
|
||||
* @implements {Action}
|
||||
* @constructor
|
||||
* @memberof platform/import-export
|
||||
*/
|
||||
function ExportAsJSONAction(
|
||||
exportService,
|
||||
policyService,
|
||||
identifierService,
|
||||
context
|
||||
) {
|
||||
|
||||
this.root = {};
|
||||
this.tree = {};
|
||||
this.calls = 0;
|
||||
this.context = context;
|
||||
this.externalIdentifiers = [];
|
||||
this.exportService = exportService;
|
||||
this.policyService = policyService;
|
||||
this.identifierService = identifierService;
|
||||
}
|
||||
|
||||
ExportAsJSONAction.prototype.perform = function () {
|
||||
this.root = this.context.domainObject;
|
||||
this.tree[this.root.getId()] = this.root.getModel();
|
||||
this.saveAs = function (completedTree) {
|
||||
this.exportService.exportJSON(
|
||||
completedTree,
|
||||
{filename: this.root.getModel().name + '.json'}
|
||||
);
|
||||
};
|
||||
|
||||
this.write(this.root);
|
||||
};
|
||||
|
||||
/**
|
||||
* Traverses object hierarchy and populates tree object with models and
|
||||
* identifiers.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} parent
|
||||
*/
|
||||
ExportAsJSONAction.prototype.write = function (parent) {
|
||||
|
||||
this.calls++;
|
||||
if (parent.hasCapability('composition')) {
|
||||
parent.useCapability('composition')
|
||||
.then(function (children) {
|
||||
children.forEach(function (child, index) {
|
||||
// Only export if object is creatable
|
||||
if (this.isCreatable(child)) {
|
||||
// Prevents infinite export of self-contained objs
|
||||
if (!this.tree.hasOwnProperty(child.getId())) {
|
||||
// If object is a link to something absent from
|
||||
// tree, generate new id and treat as new object
|
||||
if (this.isExternal(child, parent)) {
|
||||
this.rewriteLink(child, parent);
|
||||
} else {
|
||||
this.tree[child.getId()] = child.getModel();
|
||||
}
|
||||
this.write(child);
|
||||
}
|
||||
}
|
||||
}.bind(this));
|
||||
this.calls--;
|
||||
if (this.calls === 0) {
|
||||
this.saveAs(this.wrapTree());
|
||||
}
|
||||
}.bind(this));
|
||||
} else {
|
||||
this.calls--;
|
||||
if (this.calls === 0) {
|
||||
this.saveAs(this.wrapTree());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Exports an externally linked object as an entirely new object in the
|
||||
* case where the original is not present in the exported tree.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
ExportAsJSONAction.prototype.rewriteLink = function (child, parent) {
|
||||
this.externalIdentifiers.push(child.getId());
|
||||
var parentModel = parent.getModel();
|
||||
var childModel = child.getModel();
|
||||
var index = parentModel.composition.indexOf(child.getId());
|
||||
var newModel = this.copyModel(childModel);
|
||||
var newId = this.identifierService.generate();
|
||||
|
||||
newModel.location = parent.getId();
|
||||
this.tree[newId] = newModel;
|
||||
this.tree[parent.getId()] = this.copyModel(parentModel);
|
||||
this.tree[parent.getId()].composition[index] = newId;
|
||||
};
|
||||
|
||||
ExportAsJSONAction.prototype.copyModel = function (model) {
|
||||
var jsonString = JSON.stringify(model);
|
||||
return JSON.parse(jsonString);
|
||||
};
|
||||
|
||||
ExportAsJSONAction.prototype.isExternal = function (child, parent) {
|
||||
if (child.getModel().location !== parent.getId() &&
|
||||
!Object.keys(this.tree).includes(child.getModel().location) &&
|
||||
child.getId() !== this.root.getId() ||
|
||||
this.externalIdentifiers.includes(child.getId())) {
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Wraps root object for identification on reimport and wraps entire
|
||||
* exported JSON construct for validation.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
ExportAsJSONAction.prototype.wrapTree = function () {
|
||||
return {
|
||||
"openmct": this.tree,
|
||||
"rootId": this.root.getId()
|
||||
};
|
||||
};
|
||||
|
||||
ExportAsJSONAction.prototype.isCreatable = function (domainObject) {
|
||||
return this.policyService.allow(
|
||||
"creation",
|
||||
domainObject.getCapability("type")
|
||||
);
|
||||
};
|
||||
|
||||
return ExportAsJSONAction;
|
||||
});
|
||||
@@ -1,175 +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(['zepto'], function ($) {
|
||||
|
||||
/**
|
||||
* The ImportAsJSONAction is available from context menus and allows a user
|
||||
* to import a previously exported domain object into any domain object
|
||||
* that has the composition capability.
|
||||
*
|
||||
* @implements {Action}
|
||||
* @constructor
|
||||
* @memberof platform/import-export
|
||||
*/
|
||||
function ImportAsJSONAction(
|
||||
exportService,
|
||||
identifierService,
|
||||
dialogService,
|
||||
openmct,
|
||||
context
|
||||
) {
|
||||
|
||||
this.openmct = openmct;
|
||||
this.context = context;
|
||||
this.exportService = exportService;
|
||||
this.dialogService = dialogService;
|
||||
this.identifierService = identifierService;
|
||||
this.instantiate = openmct.$injector.get("instantiate");
|
||||
}
|
||||
|
||||
ImportAsJSONAction.prototype.perform = function () {
|
||||
this.dialogService.getUserInput(this.getFormModel(), {})
|
||||
.then(function (form) {
|
||||
var objectTree = form.selectFile.body;
|
||||
if (this.validateJSON(objectTree)) {
|
||||
this.importObjectTree(JSON.parse(objectTree));
|
||||
} else {
|
||||
this.displayError();
|
||||
}
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
ImportAsJSONAction.prototype.importObjectTree = function (objTree) {
|
||||
var parent = this.context.domainObject;
|
||||
var tree = this.generateNewIdentifiers(objTree);
|
||||
var rootId = tree.rootId;
|
||||
var rootObj = this.instantiate(tree.openmct[rootId], rootId);
|
||||
|
||||
// Instantiate all objects in tree with their newly genereated ids,
|
||||
// adding each to its rightful parent's composition
|
||||
rootObj.getCapability("location").setPrimaryLocation(parent.getId());
|
||||
this.deepInstantiate(rootObj, tree.openmct, []);
|
||||
parent.getCapability("composition").add(rootObj);
|
||||
};
|
||||
|
||||
ImportAsJSONAction.prototype.deepInstantiate = function (parent, tree, seen) {
|
||||
// Traverses object tree, instantiates all domain object w/ new IDs and
|
||||
// adds to parent's composition
|
||||
if (parent.hasCapability("composition")) {
|
||||
var parentModel = parent.getModel();
|
||||
var newObj;
|
||||
|
||||
seen.push(parent.getId());
|
||||
parentModel.composition.forEach(function (childId, index) {
|
||||
if (!tree[childId] || seen.includes(childId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
newObj = this.instantiate(tree[childId], childId);
|
||||
parent.getCapability("composition").add(newObj);
|
||||
newObj.getCapability("location")
|
||||
.setPrimaryLocation(tree[childId].location);
|
||||
this.deepInstantiate(newObj, tree, seen);
|
||||
}, this);
|
||||
}
|
||||
};
|
||||
|
||||
ImportAsJSONAction.prototype.generateNewIdentifiers = function (tree) {
|
||||
// For each domain object in the file, generate new ID, replace in tree
|
||||
Object.keys(tree.openmct).forEach(function (domainObjectId) {
|
||||
var newId = this.identifierService.generate();
|
||||
tree = this.rewriteId(domainObjectId, newId, tree);
|
||||
}, this);
|
||||
return tree;
|
||||
};
|
||||
|
||||
/**
|
||||
* Rewrites all instances of a given id in the tree with a newly generated
|
||||
* replacement to prevent collision.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
ImportAsJSONAction.prototype.rewriteId = function (oldID, newID, tree) {
|
||||
tree = JSON.stringify(tree).replace(new RegExp(oldID, 'g'), newID);
|
||||
return JSON.parse(tree);
|
||||
};
|
||||
|
||||
ImportAsJSONAction.prototype.getFormModel = function () {
|
||||
return {
|
||||
name: "Import as JSON",
|
||||
sections: [
|
||||
{
|
||||
name: "Import A File",
|
||||
rows: [
|
||||
{
|
||||
name: 'Select File',
|
||||
key: 'selectFile',
|
||||
control: 'file-input',
|
||||
required: true,
|
||||
text: 'Select File'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
};
|
||||
|
||||
ImportAsJSONAction.prototype.validateJSON = function (jsonString) {
|
||||
var json;
|
||||
try {
|
||||
json = JSON.parse(jsonString);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
if (!json.openmct || !json.rootId) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
ImportAsJSONAction.prototype.displayError = function () {
|
||||
var dialog,
|
||||
model = {
|
||||
title: "Invalid File",
|
||||
actionText: "The selected file was either invalid JSON or was " +
|
||||
"not formatted properly for import into Open MCT.",
|
||||
severity: "error",
|
||||
options: [
|
||||
{
|
||||
label: "Ok",
|
||||
callback: function () {
|
||||
dialog.dismiss();
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
dialog = this.dialogService.showBlockingMessage(model);
|
||||
};
|
||||
|
||||
ImportAsJSONAction.appliesTo = function (context) {
|
||||
return context.domainObject !== undefined &&
|
||||
context.domainObject.hasCapability("composition");
|
||||
};
|
||||
|
||||
return ImportAsJSONAction;
|
||||
});
|
||||
@@ -1,266 +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(
|
||||
[
|
||||
"../../src/actions/ExportAsJSONAction",
|
||||
"../../../entanglement/test/DomainObjectFactory"
|
||||
],
|
||||
function (ExportAsJSONAction, domainObjectFactory) {
|
||||
|
||||
describe("The export JSON action", function () {
|
||||
|
||||
var context,
|
||||
action,
|
||||
exportService,
|
||||
identifierService,
|
||||
policyService,
|
||||
mockType,
|
||||
exportedTree;
|
||||
|
||||
beforeEach(function () {
|
||||
exportService = jasmine.createSpyObj('exportService',
|
||||
['exportJSON']);
|
||||
identifierService = jasmine.createSpyObj('identifierService',
|
||||
['generate']);
|
||||
policyService = jasmine.createSpyObj('policyService',
|
||||
['allow']);
|
||||
mockType =
|
||||
jasmine.createSpyObj('type', ['hasFeature']);
|
||||
|
||||
mockType.hasFeature.andCallFake(function (feature) {
|
||||
return feature === 'creation';
|
||||
});
|
||||
context = {};
|
||||
context.domainObject = domainObjectFactory(
|
||||
{
|
||||
name: 'test',
|
||||
id: 'someID',
|
||||
capabilities: {type: mockType}
|
||||
});
|
||||
identifierService.generate.andReturn('brandNewId');
|
||||
exportService.exportJSON.andCallFake(function (tree, options) {
|
||||
exportedTree = tree;
|
||||
});
|
||||
policyService.allow.andCallFake(function (capability, type) {
|
||||
return type.hasFeature(capability);
|
||||
});
|
||||
|
||||
action = new ExportAsJSONAction(exportService, policyService,
|
||||
identifierService, context);
|
||||
});
|
||||
|
||||
it("initializes happily", function () {
|
||||
expect(action).toBeDefined();
|
||||
});
|
||||
|
||||
it("doesn't export non-creatable objects in tree", function () {
|
||||
var nonCreatableType = {
|
||||
hasFeature :
|
||||
function (feature) {
|
||||
return feature !== 'creation';
|
||||
}
|
||||
};
|
||||
|
||||
var parentComposition =
|
||||
jasmine.createSpyObj('parentComposition', ['invoke']);
|
||||
|
||||
var parent = domainObjectFactory({
|
||||
name: 'parent',
|
||||
model: { name: 'parent', location: 'ROOT'},
|
||||
id: 'parentId',
|
||||
capabilities: {
|
||||
composition: parentComposition,
|
||||
type: mockType
|
||||
}
|
||||
});
|
||||
|
||||
var child = domainObjectFactory({
|
||||
name: 'child',
|
||||
model: { name: 'child', location: 'parentId' },
|
||||
id: 'childId',
|
||||
capabilities: {
|
||||
type: nonCreatableType
|
||||
}
|
||||
});
|
||||
|
||||
parentComposition.invoke.andReturn(
|
||||
Promise.resolve([child])
|
||||
);
|
||||
context.domainObject = parent;
|
||||
|
||||
var init = false;
|
||||
runs(function () {
|
||||
action.perform();
|
||||
setTimeout(function () {
|
||||
init = true;
|
||||
}, 100);
|
||||
});
|
||||
|
||||
waitsFor(function () {
|
||||
return init;
|
||||
}, "Exported tree sohuld have been built");
|
||||
|
||||
runs(function () {
|
||||
expect(Object.keys(action.tree).length).toBe(1);
|
||||
expect(action.tree.hasOwnProperty("parentId"))
|
||||
.toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it("can export self-containing objects", function () {
|
||||
var infiniteParentComposition =
|
||||
jasmine.createSpyObj('infiniteParentComposition',
|
||||
['invoke']
|
||||
);
|
||||
|
||||
var infiniteChildComposition =
|
||||
jasmine.createSpyObj('infiniteChildComposition',
|
||||
['invoke']
|
||||
);
|
||||
|
||||
var parent = domainObjectFactory({
|
||||
name: 'parent',
|
||||
model: { name: 'parent', location: 'ROOT'},
|
||||
id: 'infiniteParentId',
|
||||
capabilities: {
|
||||
composition: infiniteParentComposition,
|
||||
type: mockType
|
||||
}
|
||||
});
|
||||
|
||||
var child = domainObjectFactory({
|
||||
name: 'child',
|
||||
model: { name: 'child', location: 'infiniteParentId' },
|
||||
id: 'infiniteChildId',
|
||||
capabilities: {
|
||||
composition: infiniteChildComposition,
|
||||
type: mockType
|
||||
}
|
||||
});
|
||||
|
||||
infiniteParentComposition.invoke.andReturn(
|
||||
Promise.resolve([child])
|
||||
);
|
||||
infiniteChildComposition.invoke.andReturn(
|
||||
Promise.resolve([parent])
|
||||
);
|
||||
context.domainObject = parent;
|
||||
|
||||
var init = false;
|
||||
runs(function () {
|
||||
action.perform();
|
||||
setTimeout(function () {
|
||||
init = true;
|
||||
}, 100);
|
||||
});
|
||||
|
||||
waitsFor(function () {
|
||||
return init;
|
||||
}, "Exported tree sohuld have been built");
|
||||
|
||||
runs(function () {
|
||||
expect(Object.keys(action.tree).length).toBe(2);
|
||||
expect(action.tree.hasOwnProperty("infiniteParentId"))
|
||||
.toBeTruthy();
|
||||
expect(action.tree.hasOwnProperty("infiniteChildId"))
|
||||
.toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it("exports links to external objects as new objects", function () {
|
||||
var externallyLinkedComposition =
|
||||
jasmine.createSpyObj('externallyLinkedComposition',
|
||||
['invoke']
|
||||
);
|
||||
|
||||
var parent = domainObjectFactory({
|
||||
name: 'parent',
|
||||
model: {
|
||||
name: 'parent',
|
||||
composition: ['externalId'],
|
||||
location: 'ROOT'},
|
||||
id: 'parentId',
|
||||
capabilities: {
|
||||
composition: externallyLinkedComposition,
|
||||
type: mockType
|
||||
}
|
||||
});
|
||||
|
||||
var externalObject = domainObjectFactory({
|
||||
name: 'external',
|
||||
model: { name: 'external', location: 'outsideOfTree'},
|
||||
id: 'externalId',
|
||||
capabilities: {
|
||||
type: mockType
|
||||
}
|
||||
});
|
||||
|
||||
externallyLinkedComposition.invoke.andReturn(
|
||||
Promise.resolve([externalObject])
|
||||
);
|
||||
context.domainObject = parent;
|
||||
|
||||
var init = false;
|
||||
runs(function () {
|
||||
action.perform();
|
||||
setTimeout(function () {
|
||||
init = true;
|
||||
}, 100);
|
||||
});
|
||||
|
||||
waitsFor(function () {
|
||||
return init;
|
||||
}, "Exported tree sohuld have been built");
|
||||
|
||||
runs(function () {
|
||||
expect(Object.keys(action.tree).length).toBe(2);
|
||||
expect(action.tree.hasOwnProperty('parentId'))
|
||||
.toBeTruthy();
|
||||
expect(action.tree.hasOwnProperty('brandNewId'))
|
||||
.toBeTruthy();
|
||||
expect(action.tree.brandNewId.location).toBe('parentId');
|
||||
});
|
||||
});
|
||||
|
||||
it("exports object tree in the correct format", function () {
|
||||
var init = false;
|
||||
runs(function () {
|
||||
action.perform();
|
||||
setTimeout(function () {
|
||||
init = true;
|
||||
}, 100);
|
||||
});
|
||||
|
||||
waitsFor(function () {
|
||||
return init;
|
||||
}, "Exported tree sohuld have been built");
|
||||
|
||||
runs(function () {
|
||||
expect(Object.keys(exportedTree).length).toBe(2);
|
||||
expect(exportedTree.hasOwnProperty('openmct')).toBeTruthy();
|
||||
expect(exportedTree.hasOwnProperty('rootId')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -1,240 +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(
|
||||
[
|
||||
"../../src/actions/ImportAsJSONAction",
|
||||
"../../../entanglement/test/DomainObjectFactory"
|
||||
],
|
||||
function (ImportAsJSONAction, domainObjectFactory) {
|
||||
|
||||
describe("The import JSON action", function () {
|
||||
|
||||
var context = {};
|
||||
var action,
|
||||
exportService,
|
||||
identifierService,
|
||||
dialogService,
|
||||
openmct,
|
||||
mockDialog,
|
||||
compositionCapability,
|
||||
mockInstantiate,
|
||||
uniqueId,
|
||||
newObjects;
|
||||
|
||||
|
||||
beforeEach(function () {
|
||||
|
||||
uniqueId = 0;
|
||||
newObjects = [];
|
||||
openmct = {
|
||||
$injector: jasmine.createSpyObj('$injector', ['get'])
|
||||
};
|
||||
mockInstantiate = jasmine.createSpy('instantiate').andCallFake(
|
||||
function (model, id) {
|
||||
var config = {
|
||||
"model": model,
|
||||
"id": id,
|
||||
"capabilities": {}
|
||||
};
|
||||
var locationCapability = {
|
||||
setPrimaryLocation: jasmine.createSpy
|
||||
('setPrimaryLocation').andCallFake(
|
||||
function (newLocation) {
|
||||
config.model.location = newLocation;
|
||||
}
|
||||
)
|
||||
};
|
||||
config.capabilities.location = locationCapability;
|
||||
if (model.composition) {
|
||||
var compCapability =
|
||||
jasmine.createSpy('compCapability')
|
||||
.andReturn(model.composition);
|
||||
compCapability.add = jasmine.createSpy('add')
|
||||
.andCallFake(function (newObj) {
|
||||
config.model.composition.push(newObj.getId());
|
||||
});
|
||||
config.capabilities.composition = compCapability;
|
||||
}
|
||||
newObjects.push(domainObjectFactory(config));
|
||||
return domainObjectFactory(config);
|
||||
});
|
||||
openmct.$injector.get.andReturn(mockInstantiate);
|
||||
dialogService = jasmine.createSpyObj('dialogService',
|
||||
[
|
||||
'getUserInput',
|
||||
'showBlockingMessage'
|
||||
]
|
||||
);
|
||||
identifierService = jasmine.createSpyObj('identifierService',
|
||||
[
|
||||
'generate'
|
||||
]
|
||||
);
|
||||
identifierService.generate.andCallFake(function () {
|
||||
uniqueId++;
|
||||
return uniqueId;
|
||||
});
|
||||
compositionCapability = jasmine.createSpy('compositionCapability');
|
||||
mockDialog = jasmine.createSpyObj("dialog", ["dismiss"]);
|
||||
dialogService.showBlockingMessage.andReturn(mockDialog);
|
||||
|
||||
action = new ImportAsJSONAction(exportService, identifierService,
|
||||
dialogService, openmct, context);
|
||||
});
|
||||
|
||||
it("initializes happily", function () {
|
||||
expect(action).toBeDefined();
|
||||
});
|
||||
|
||||
it("only applies to objects with composition capability", function () {
|
||||
var compDomainObject = domainObjectFactory({
|
||||
name: 'compObject',
|
||||
model: { name: 'compObject'},
|
||||
capabilities: {"composition": compositionCapability}
|
||||
});
|
||||
var noCompDomainObject = domainObjectFactory();
|
||||
|
||||
context.domainObject = compDomainObject;
|
||||
expect(ImportAsJSONAction.appliesTo(context)).toBe(true);
|
||||
context.domainObject = noCompDomainObject;
|
||||
expect(ImportAsJSONAction.appliesTo(context)).toBe(false);
|
||||
});
|
||||
|
||||
it("displays error dialog on invalid file choice", function () {
|
||||
dialogService.getUserInput.andReturn(Promise.resolve(
|
||||
{
|
||||
selectFile: {
|
||||
body: JSON.stringify({badKey: "INVALID"}),
|
||||
name: "fileName"
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
var init = false;
|
||||
runs(function () {
|
||||
action.perform();
|
||||
setTimeout(function () {
|
||||
init = true;
|
||||
}, 100);
|
||||
});
|
||||
|
||||
waitsFor(function () {
|
||||
return init;
|
||||
}, "Promise containing file data should have resolved");
|
||||
|
||||
runs(function () {
|
||||
expect(dialogService.getUserInput).toHaveBeenCalled();
|
||||
expect(dialogService.showBlockingMessage).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it("can import self-containing objects", function () {
|
||||
dialogService.getUserInput.andReturn(Promise.resolve(
|
||||
{
|
||||
selectFile: {
|
||||
body: JSON.stringify({
|
||||
"openmct": {
|
||||
"infiniteParent": {
|
||||
"composition": ["infinteChild"],
|
||||
"name": "1",
|
||||
"type": "folder",
|
||||
"modified": 1503598129176,
|
||||
"location": "mine",
|
||||
"persisted": 1503598129176
|
||||
},
|
||||
"infinteChild": {
|
||||
"composition": ["infiniteParent"],
|
||||
"name": "2",
|
||||
"type": "folder",
|
||||
"modified": 1503598132428,
|
||||
"location": "infiniteParent",
|
||||
"persisted": 1503598132428
|
||||
}
|
||||
},
|
||||
"rootId": "infiniteParent"
|
||||
}),
|
||||
name: "fileName"
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
var init = false;
|
||||
runs(function () {
|
||||
action.perform();
|
||||
setTimeout(function () {
|
||||
init = true;
|
||||
}, 100);
|
||||
});
|
||||
|
||||
waitsFor(function () {
|
||||
return init;
|
||||
}, "Promise containing file data should have resolved");
|
||||
|
||||
runs(function () {
|
||||
expect(mockInstantiate.calls.length).toEqual(2);
|
||||
});
|
||||
});
|
||||
|
||||
it("assigns new ids to each imported object", function () {
|
||||
dialogService.getUserInput.andReturn(Promise.resolve(
|
||||
{
|
||||
selectFile: {
|
||||
body: JSON.stringify({
|
||||
"openmct": {
|
||||
"cce9f107-5060-4f55-8151-a00120f4222f": {
|
||||
"composition": [],
|
||||
"name": "test",
|
||||
"type": "folder",
|
||||
"modified": 1503596596639,
|
||||
"location": "mine",
|
||||
"persisted": 1503596596639
|
||||
}
|
||||
},
|
||||
"rootId": "cce9f107-5060-4f55-8151-a00120f4222f"
|
||||
}),
|
||||
name: "fileName"
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
var init = false;
|
||||
runs(function () {
|
||||
action.perform();
|
||||
setTimeout(function () {
|
||||
init = true;
|
||||
}, 100);
|
||||
});
|
||||
|
||||
waitsFor(function () {
|
||||
return init;
|
||||
}, "Promise containing file data should have resolved");
|
||||
|
||||
runs(function () {
|
||||
expect(mockInstantiate.calls.length).toEqual(1);
|
||||
expect(newObjects[0].getId()).toBe('1');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -138,11 +138,6 @@ define(
|
||||
typeRequest = (type && type.getDefinition().telemetry) || {},
|
||||
modelTelemetry = domainObject.getModel().telemetry,
|
||||
fullRequest = Object.create(typeRequest),
|
||||
newObject = objectUtils.toNewFormat(
|
||||
domainObject.getModel(),
|
||||
domainObject.getId()
|
||||
),
|
||||
metadata = this.openmct.telemetry.getMetadata(newObject),
|
||||
bounds,
|
||||
timeSystem;
|
||||
|
||||
@@ -178,14 +173,6 @@ define(
|
||||
}
|
||||
}
|
||||
|
||||
if (!fullRequest.ranges) {
|
||||
fullRequest.ranges = metadata.valuesForHints(['range']);
|
||||
}
|
||||
|
||||
if (!fullRequest.domains) {
|
||||
fullRequest.domains = metadata.valuesForHints(['domain']);
|
||||
}
|
||||
|
||||
return fullRequest;
|
||||
};
|
||||
|
||||
|
||||
@@ -91,10 +91,8 @@ define(
|
||||
"findSubscriptionProvider"
|
||||
]);
|
||||
mockTelemetryAPI.getMetadata.andReturn({
|
||||
valuesForHints: function (hint) {
|
||||
var metadatum = {};
|
||||
metadatum[hint] = "foo";
|
||||
return [metadatum];
|
||||
valuesForHints: function () {
|
||||
return [{}];
|
||||
}
|
||||
});
|
||||
|
||||
@@ -149,9 +147,7 @@ define(
|
||||
source: "testSource", // from model
|
||||
key: "testKey", // from model
|
||||
start: 42, // from argument
|
||||
domain: 'mockTimeSystem',
|
||||
domains: [{ domain: "foo" }],
|
||||
ranges: [{ range: "foo" }]
|
||||
domain: 'mockTimeSystem'
|
||||
}]);
|
||||
});
|
||||
|
||||
@@ -171,9 +167,7 @@ define(
|
||||
key: "testKey",
|
||||
start: 0,
|
||||
end: 1,
|
||||
domain: 'mockTimeSystem',
|
||||
domains: [{ domain: "foo" }],
|
||||
ranges: [{ range: "foo" }]
|
||||
domain: 'mockTimeSystem'
|
||||
});
|
||||
});
|
||||
|
||||
@@ -190,9 +184,7 @@ define(
|
||||
key: "testId", // from domain object
|
||||
start: 0,
|
||||
end: 1,
|
||||
domain: 'mockTimeSystem',
|
||||
domains: [{ domain: "foo" }],
|
||||
ranges: [{ range: "foo" }]
|
||||
domain: 'mockTimeSystem'
|
||||
});
|
||||
});
|
||||
|
||||
@@ -274,9 +266,7 @@ define(
|
||||
key: "testKey",
|
||||
start: 0,
|
||||
end: 1,
|
||||
domain: 'mockTimeSystem',
|
||||
domains: [{ domain: "foo" }],
|
||||
ranges: [{ range: "foo" }]
|
||||
domain: 'mockTimeSystem'
|
||||
}]
|
||||
);
|
||||
|
||||
|
||||
17
src/MCT.js
17
src/MCT.js
@@ -106,9 +106,9 @@ define([
|
||||
*
|
||||
* @type {module:openmct.ViewRegistry}
|
||||
* @memberof module:openmct.MCT#
|
||||
* @name mainViews
|
||||
* @name objectViews
|
||||
*/
|
||||
this.mainViews = new ViewRegistry();
|
||||
this.objectViews = new ViewRegistry();
|
||||
|
||||
/**
|
||||
* Registry for views which should appear in the Inspector area.
|
||||
@@ -255,6 +255,19 @@ define([
|
||||
this.legacyExtension('types', legacyDefinition);
|
||||
}.bind(this));
|
||||
|
||||
this.objectViews.providers.forEach(function (p) {
|
||||
this.legacyExtension('views', {
|
||||
key: '_vpid' + p._vpid,
|
||||
vpid: p._vpid,
|
||||
provider: p,
|
||||
name: p.name,
|
||||
cssClass: p.cssClass,
|
||||
description: p.description,
|
||||
editable: p.editable,
|
||||
template: '<mct-view mct-vpid="' + p._vpid + '"/>'
|
||||
});
|
||||
}, this);
|
||||
|
||||
legacyRegistry.register('adapter', this.legacyBundle);
|
||||
legacyRegistry.enable('adapter');
|
||||
/**
|
||||
|
||||
@@ -24,7 +24,6 @@ define([
|
||||
'legacyRegistry',
|
||||
'./actions/ActionDialogDecorator',
|
||||
'./capabilities/AdapterCapability',
|
||||
'./controllers/AdaptedViewController',
|
||||
'./directives/MCTView',
|
||||
'./services/Instantiate',
|
||||
'./services/MissingModelCompatibilityDecorator',
|
||||
@@ -33,12 +32,10 @@ define([
|
||||
'./policies/AdaptedViewPolicy',
|
||||
'./runs/AlternateCompositionInitializer',
|
||||
'./runs/TimeSettingsURLHandler',
|
||||
'text!./templates/adapted-view-template.html'
|
||||
], function (
|
||||
legacyRegistry,
|
||||
ActionDialogDecorator,
|
||||
AdapterCapability,
|
||||
AdaptedViewController,
|
||||
MCTView,
|
||||
Instantiate,
|
||||
MissingModelCompatibilityDecorator,
|
||||
@@ -46,15 +43,15 @@ define([
|
||||
AdapterCompositionPolicy,
|
||||
AdaptedViewPolicy,
|
||||
AlternateCompositionInitializer,
|
||||
TimeSettingsURLHandler,
|
||||
adaptedViewTemplate
|
||||
TimeSettingsURLHandler
|
||||
) {
|
||||
legacyRegistry.register('src/adapter', {
|
||||
"extensions": {
|
||||
"directives": [
|
||||
{
|
||||
key: "mctView",
|
||||
implementation: MCTView
|
||||
implementation: MCTView,
|
||||
depends: ["openmct"]
|
||||
}
|
||||
],
|
||||
capabilities: [
|
||||
@@ -63,16 +60,6 @@ define([
|
||||
implementation: AdapterCapability
|
||||
}
|
||||
],
|
||||
controllers: [
|
||||
{
|
||||
key: "AdaptedViewController",
|
||||
implementation: AdaptedViewController,
|
||||
depends: [
|
||||
'$scope',
|
||||
'openmct'
|
||||
]
|
||||
}
|
||||
],
|
||||
services: [
|
||||
{
|
||||
key: "instantiate",
|
||||
@@ -135,12 +122,6 @@ define([
|
||||
depends: ["openmct", "$location", "$rootScope"]
|
||||
}
|
||||
],
|
||||
views: [
|
||||
{
|
||||
key: "adapted-view",
|
||||
template: adaptedViewTemplate
|
||||
}
|
||||
],
|
||||
licenses: [
|
||||
{
|
||||
"name": "almond",
|
||||
|
||||
@@ -22,10 +22,12 @@
|
||||
|
||||
define([
|
||||
'./synchronizeMutationCapability',
|
||||
'./AlternateCompositionCapability'
|
||||
'./AlternateCompositionCapability',
|
||||
'./patchViewCapability'
|
||||
], function (
|
||||
synchronizeMutationCapability,
|
||||
AlternateCompositionCapability
|
||||
AlternateCompositionCapability,
|
||||
patchViewCapability
|
||||
) {
|
||||
|
||||
/**
|
||||
@@ -46,6 +48,9 @@ define([
|
||||
capabilities.mutation =
|
||||
synchronizeMutationCapability(capabilities.mutation);
|
||||
}
|
||||
if (capabilities.view) {
|
||||
capabilities.view = patchViewCapability(capabilities.view);
|
||||
}
|
||||
if (AlternateCompositionCapability.appliesTo(model, id)) {
|
||||
capabilities.composition = function (domainObject) {
|
||||
return new AlternateCompositionCapability(this.$injector, domainObject);
|
||||
|
||||
@@ -20,21 +20,42 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define([], function () {
|
||||
function AdaptedViewController($scope, openmct) {
|
||||
function refresh(legacyObject) {
|
||||
if (!legacyObject) {
|
||||
$scope.view = undefined;
|
||||
return;
|
||||
}
|
||||
define([
|
||||
'lodash'
|
||||
], function (
|
||||
_
|
||||
) {
|
||||
|
||||
var domainObject = legacyObject.useCapability('adapter');
|
||||
var providers = openmct.mainViews.get(domainObject);
|
||||
$scope.view = providers[0] && providers[0].view(domainObject);
|
||||
function patchViewCapability(viewConstructor) {
|
||||
return function makeCapability(domainObject) {
|
||||
var capability = viewConstructor(domainObject);
|
||||
var oldInvoke = capability.invoke.bind(capability);
|
||||
|
||||
capability.invoke = function () {
|
||||
var availableViews = oldInvoke();
|
||||
var newDomainObject = capability
|
||||
.domainObject
|
||||
.useCapability('adapter');
|
||||
|
||||
return _(availableViews).map(function (v, i) {
|
||||
var vd = {
|
||||
view: v,
|
||||
priority: i + 100 // arbitrary to allow new views to
|
||||
// be defaults by returning priority less than 100.
|
||||
};
|
||||
if (v.provider) {
|
||||
vd.priority = v.provider.canView(newDomainObject);
|
||||
}
|
||||
return vd;
|
||||
})
|
||||
.sortBy('priority')
|
||||
.map('view')
|
||||
.value();
|
||||
}
|
||||
return capability;
|
||||
}
|
||||
|
||||
$scope.$watch('domainObject', refresh);
|
||||
}
|
||||
|
||||
return AdaptedViewController;
|
||||
return patchViewCapability;
|
||||
});
|
||||
@@ -21,18 +21,20 @@
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
'angular',
|
||||
'./Region'
|
||||
], function (
|
||||
angular,
|
||||
Region
|
||||
) {
|
||||
function MCTView() {
|
||||
function MCTView(openmct) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
restrict: 'E',
|
||||
link: function (scope, element, attrs) {
|
||||
var region = new Region(element[0]);
|
||||
scope.$watch(attrs.mctView, region.show.bind(region));
|
||||
var provider = openmct.objectViews._getByVPID(Number(attrs.mctVpid));
|
||||
var view = new provider.view(scope.domainObject.useCapability('adapter'));
|
||||
view.show(element[0]);
|
||||
if (view.destroy) {
|
||||
scope.$on('$destroy', function () {
|
||||
view.destroy(element[0])
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,45 +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 Region(element) {
|
||||
this.activeView = undefined;
|
||||
this.element = element;
|
||||
}
|
||||
|
||||
Region.prototype.clear = function () {
|
||||
if (this.activeView) {
|
||||
this.activeView.destroy();
|
||||
this.activeView = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
Region.prototype.show = function (view) {
|
||||
this.clear();
|
||||
this.activeView = view;
|
||||
if (this.activeView) {
|
||||
this.activeView.show(this.element);
|
||||
}
|
||||
};
|
||||
|
||||
return Region;
|
||||
});
|
||||
@@ -29,9 +29,9 @@ define([], function () {
|
||||
view,
|
||||
legacyObject
|
||||
) {
|
||||
if (view.key === 'adapted-view') {
|
||||
if (view.hasOwnProperty('vpid')) {
|
||||
var domainObject = legacyObject.useCapability('adapter');
|
||||
return this.openmct.mainViews.get(domainObject).length > 0;
|
||||
return view.provider.canView(domainObject);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -27,7 +27,7 @@ define([
|
||||
'../../platform/features/autoflow/plugin',
|
||||
'./timeConductor/plugin',
|
||||
'../../example/imagery/plugin',
|
||||
'../../platform/import-export/bundle'
|
||||
'./summaryWidget/plugin'
|
||||
], function (
|
||||
_,
|
||||
UTCTimeSystem,
|
||||
@@ -35,7 +35,7 @@ define([
|
||||
AutoflowPlugin,
|
||||
TimeConductorPlugin,
|
||||
ExampleImagery,
|
||||
ImportExport
|
||||
SummaryWidget
|
||||
) {
|
||||
var bundleMap = {
|
||||
CouchDB: 'platform/persistence/couch',
|
||||
@@ -56,8 +56,6 @@ define([
|
||||
|
||||
plugins.UTCTimeSystem = UTCTimeSystem;
|
||||
|
||||
plugins.ImportExport = ImportExport;
|
||||
|
||||
/**
|
||||
* A tabular view showing the latest values of multiple telemetry points at
|
||||
* once. Formatted so that labels and values are aligned.
|
||||
@@ -121,5 +119,7 @@ define([
|
||||
|
||||
plugins.ExampleImagery = ExampleImagery;
|
||||
|
||||
plugins.SummaryWidget = SummaryWidget;
|
||||
|
||||
return plugins;
|
||||
});
|
||||
|
||||
40
src/plugins/summaryWidget/plugin.js
Executable file
40
src/plugins/summaryWidget/plugin.js
Executable file
@@ -0,0 +1,40 @@
|
||||
define(['./src/SummaryWidget'], function (SummaryWidget) {
|
||||
|
||||
function plugin() {
|
||||
|
||||
var widgetType = {
|
||||
name: 'Summary Widget',
|
||||
description: 'A compact status update for collections of telemetry-producing items',
|
||||
creatable: true,
|
||||
cssClass: 'icon-summary-widget',
|
||||
initialize: function (domainObject) {
|
||||
domainObject.composition = [];
|
||||
domainObject.configuration = {};
|
||||
}
|
||||
};
|
||||
|
||||
function initViewProvider(openmct) {
|
||||
return {
|
||||
name: 'Widget View',
|
||||
view: function (domainObject) {
|
||||
var summaryWidget = new SummaryWidget(domainObject, openmct);
|
||||
return {
|
||||
show: summaryWidget.show,
|
||||
destroy: summaryWidget.destroy
|
||||
};
|
||||
},
|
||||
canView: function (domainObject) {
|
||||
return (domainObject.type === 'summary-widget');
|
||||
},
|
||||
editable: true
|
||||
};
|
||||
}
|
||||
|
||||
return function install(openmct) {
|
||||
openmct.types.addType('summary-widget', widgetType);
|
||||
openmct.objectViews.addProvider(initViewProvider(openmct));
|
||||
};
|
||||
}
|
||||
|
||||
return plugin;
|
||||
});
|
||||
11
src/plugins/summaryWidget/res/conditionTemplate.html
Normal file
11
src/plugins/summaryWidget/res/conditionTemplate.html
Normal file
@@ -0,0 +1,11 @@
|
||||
<li class="t-condition">
|
||||
<label>when</label>
|
||||
<span class="controls">
|
||||
<span class="t-configuration"> </span>
|
||||
<span class="t-value-inputs"> </span>
|
||||
</span>
|
||||
<span class="flex-elem l-condition-action-buttons-w">
|
||||
<a class="s-icon-button icon-duplicate t-duplicate"></a>
|
||||
<a class="s-icon-button icon-trash t-delete"></a>
|
||||
</span>
|
||||
</li>
|
||||
10
src/plugins/summaryWidget/res/input/paletteTemplate.html
Normal file
10
src/plugins/summaryWidget/res/input/paletteTemplate.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<a class="e-control s-button s-menu-button menu-element">
|
||||
<span class="l-click-area"></span>
|
||||
<span class="t-swatch"></span>
|
||||
<div class="menu l-palette">
|
||||
<div class="l-palette-row l-option-row">
|
||||
<div class="l-palette-item s-palette-item no-selection"></div>
|
||||
<span class="l-palette-item-label">None</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
4
src/plugins/summaryWidget/res/input/selectTemplate.html
Normal file
4
src/plugins/summaryWidget/res/input/selectTemplate.html
Normal file
@@ -0,0 +1,4 @@
|
||||
<div class="e-control select">
|
||||
<select>
|
||||
</select>
|
||||
</div>
|
||||
5
src/plugins/summaryWidget/res/ruleImageTemplate.html
Normal file
5
src/plugins/summaryWidget/res/ruleImageTemplate.html
Normal file
@@ -0,0 +1,5 @@
|
||||
<div class="l-edit-widget">
|
||||
<div class="holder widget-rules-w grows">
|
||||
<div class="t-drag-rule-image l-widget-rule s-widget-rule l-compact-form"> </div>
|
||||
</div>
|
||||
</div>
|
||||
76
src/plugins/summaryWidget/res/ruleTemplate.html
Normal file
76
src/plugins/summaryWidget/res/ruleTemplate.html
Normal file
@@ -0,0 +1,76 @@
|
||||
<div>
|
||||
<div class="l-widget-rule s-widget-rule l-compact-form">
|
||||
<div class="widget-rule-header">
|
||||
<span class="flex-elem l-widget-thumb-w">
|
||||
<span class="grippy-holder">
|
||||
<span class="t-grippy grippy"></span>
|
||||
</span>
|
||||
<span class="view-control expanded"></span>
|
||||
<span class="t-widget-thumb widget-thumb">
|
||||
<span class="widget-icon"></span>
|
||||
<span class="widget-label">DEF</span>
|
||||
</span>
|
||||
</span>
|
||||
<span class="flex-elem rule-title">Default Title</span>
|
||||
<span class="flex-elem rule-description grows">Rule description goes here</span>
|
||||
<span class="flex-elem l-rule-action-buttons-w">
|
||||
<a class="s-icon-button icon-duplicate t-duplicate"></a>
|
||||
<a class="s-icon-button icon-trash t-delete"></a>
|
||||
</span>
|
||||
</div>
|
||||
<div class="widget-rule-content expanded">
|
||||
<ul>
|
||||
<li>
|
||||
<label>Rule Name:</label>
|
||||
<span class="controls">
|
||||
<input class="t-rule-name-input" type="text" />
|
||||
</span>
|
||||
</li>
|
||||
<li class="connects-to-previous">
|
||||
<label>Label:</label>
|
||||
<span class="controls t-label-input">
|
||||
<input class="e-control t-rule-label-input" type="text" />
|
||||
</span>
|
||||
</li>
|
||||
<li class="connects-to-previous">
|
||||
<label>Message:</label>
|
||||
<span class="controls">
|
||||
<input type="text" class="lg s t-rule-message-input"
|
||||
placeholder="Will appear as tooltip when hovering on the widget"/>
|
||||
</span>
|
||||
</li>
|
||||
<li class="connects-to-previous">
|
||||
<label>Style:</label>
|
||||
<span class="controls t-style-input">
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="t-widget-rule-config">
|
||||
<li>
|
||||
<label>Trigger when</label>
|
||||
<span class="controls">
|
||||
<div class="e-control select">
|
||||
<select class="t-trigger">
|
||||
<option value="any">any condition is met</option>
|
||||
<option value="all">all conditions are met</option>
|
||||
<option value="js">the following JavaScript evaluates to true</<option>
|
||||
</select>
|
||||
</div>
|
||||
</span>
|
||||
</li>
|
||||
<li class="t-rule-js-condition-input-holder">
|
||||
<textarea placeholder="" class="med t-rule-js-condition-input"></textarea>
|
||||
</li>
|
||||
<li>
|
||||
<label></label>
|
||||
<span class="controls">
|
||||
<a class="e-control s-button labeled add-condition icon-plus">Add Condition</a>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="t-drag-indicator l-compact-form l-widget-rule s-widget-rule"
|
||||
style="opacity:0 " hidden>
|
||||
</div>
|
||||
</div>
|
||||
12
src/plugins/summaryWidget/res/testDataItemTemplate.html
Normal file
12
src/plugins/summaryWidget/res/testDataItemTemplate.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<li class="t-condition t-test-data-item">
|
||||
<label>Let </label>
|
||||
<span class="controls">
|
||||
<span class="t-configuration"> </span>
|
||||
<span> equal </span>
|
||||
<span class="t-value-inputs"> </span>
|
||||
</span>
|
||||
<span class="flex-elem l-condition-action-buttons-w">
|
||||
<a class="s-icon-button icon-duplicate t-duplicate"></a>
|
||||
<a class="s-icon-button icon-trash t-delete"></a>
|
||||
</span>
|
||||
</li>
|
||||
25
src/plugins/summaryWidget/res/testDataTemplate.html
Normal file
25
src/plugins/summaryWidget/res/testDataTemplate.html
Normal file
@@ -0,0 +1,25 @@
|
||||
<div class="l-widget-rule s-widget-rule l-compact-form l-widget-test-data">
|
||||
<div class="widget-rule-header widget-test-data-header">
|
||||
<span class="flex-elem l-widget-thumb-w">
|
||||
<span class="view-control"></span>
|
||||
</span>
|
||||
<span class="flex-elem rule-title">Test Data</span>
|
||||
<span class="flex-elem rule-description grows">Mock values for telemetry data to test rules</span>
|
||||
<span class="flex-elem">
|
||||
<label class="checkbox custom"> Use Test Data
|
||||
<input type="checkbox" class="t-test-data-checkbox">
|
||||
<em></em>
|
||||
</label>
|
||||
</span>
|
||||
</div>
|
||||
<div class="widget-rule-content widget-test-data-content">
|
||||
<ul class="t-widget-rule-config t-test-data-config">
|
||||
<li>
|
||||
<label></label>
|
||||
<span class="controls">
|
||||
<a class="e-control s-button labeled add-item icon-plus">Add Item</a>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
19
src/plugins/summaryWidget/res/widgetTemplate.html
Executable file
19
src/plugins/summaryWidget/res/widgetTemplate.html
Executable file
@@ -0,0 +1,19 @@
|
||||
<div class="l-widget-outer-w">
|
||||
<div class="holder widget-holder">
|
||||
<a id="widget" class="widget t-summary-widget l-summary-widget s-summary-widget labeled">
|
||||
<span id="widgetIcon" class="widget-icon"></span>
|
||||
<span id="widgetLabel" class="widget-label">Default Name</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="widget-edit-holder grows">
|
||||
<div class="section-header flex-elem">Test Data</div>
|
||||
<div class="widget-test-data flex-elem grows"></div>
|
||||
<div class="section-header flex-elem">Rules</div>
|
||||
<div class="holder widget-rules-w flex-elem grows">
|
||||
<div class="t-widget-rules"></div>
|
||||
<div class="holder add-rule-button-wrapper align-right">
|
||||
<a id="addRule" class="s-button major labeled add-rule-button icon-plus">Add Rule</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
192
src/plugins/summaryWidget/src/Condition.js
Normal file
192
src/plugins/summaryWidget/src/Condition.js
Normal file
@@ -0,0 +1,192 @@
|
||||
define([
|
||||
'text!../res/conditionTemplate.html',
|
||||
'./input/ObjectSelect',
|
||||
'./input/KeySelect',
|
||||
'./input/OperationSelect',
|
||||
'EventEmitter',
|
||||
'zepto'
|
||||
], function (
|
||||
conditionTemplate,
|
||||
ObjectSelect,
|
||||
KeySelect,
|
||||
OperationSelect,
|
||||
EventEmitter,
|
||||
$
|
||||
) {
|
||||
|
||||
/**
|
||||
* Represents an individual condition for a summary widget rule. Manages the
|
||||
* associated inputs and view.
|
||||
* @param {Object} conditionConfig The configurration for this condition, consisting
|
||||
* of object, key, operation, and values fields
|
||||
* @param {number} index the index of this Condition object in it's parent Rule's data model,
|
||||
* to be injected into callbacks for removes
|
||||
* @param {ConditionManager} conditionManager A ConditionManager instance for populating
|
||||
* selects with configuration data
|
||||
*/
|
||||
function Condition(conditionConfig, index, conditionManager) {
|
||||
this.config = conditionConfig;
|
||||
this.index = index;
|
||||
this.conditionManager = conditionManager;
|
||||
|
||||
this.domElement = $(conditionTemplate);
|
||||
this.eventEmitter = new EventEmitter();
|
||||
this.supportedCallbacks = ['remove', 'duplicate', 'change'];
|
||||
|
||||
this.deleteButton = $('.t-delete', this.domElement);
|
||||
this.duplicateButton = $('.t-duplicate', this.domElement);
|
||||
|
||||
this.selects = {};
|
||||
this.valueInputs = [];
|
||||
|
||||
this.remove = this.remove.bind(this);
|
||||
this.duplicate = this.duplicate.bind(this);
|
||||
|
||||
var self = this;
|
||||
|
||||
/**
|
||||
* Event handler for a change in one of this conditions' custom selects
|
||||
* @param {string} value The new value of this selects
|
||||
* @param {string} property The property of this condition to modify
|
||||
* @private
|
||||
*/
|
||||
function onSelectChange(value, property) {
|
||||
if (property === 'operation') {
|
||||
self.generateValueInputs(value);
|
||||
}
|
||||
self.eventEmitter.emit('change', {
|
||||
value: value,
|
||||
property: property,
|
||||
index: self.index
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler for this conditions value inputs
|
||||
* @param {Event} event The oninput event that triggered this callback
|
||||
* @private
|
||||
*/
|
||||
function onValueInput(event) {
|
||||
var elem = event.target,
|
||||
value = (isNaN(elem.valueAsNumber) ? elem.value : elem.valueAsNumber),
|
||||
inputIndex = self.valueInputs.indexOf(elem);
|
||||
|
||||
self.eventEmitter.emit('change', {
|
||||
value: value,
|
||||
property: 'values[' + inputIndex + ']',
|
||||
index: self.index
|
||||
});
|
||||
}
|
||||
|
||||
this.deleteButton.on('click', this.remove);
|
||||
this.duplicateButton.on('click', this.duplicate);
|
||||
|
||||
this.selects.object = new ObjectSelect(this.config, this.conditionManager, [
|
||||
['any', 'Any Telemetry'],
|
||||
['all', 'All Telemetry']
|
||||
]);
|
||||
this.selects.key = new KeySelect(this.config, this.selects.object, this.conditionManager);
|
||||
this.selects.operation = new OperationSelect(
|
||||
this.config,
|
||||
this.selects.key,
|
||||
this.conditionManager,
|
||||
function (value) {
|
||||
onSelectChange(value, 'operation');
|
||||
});
|
||||
|
||||
this.selects.object.on('change', function (value) {
|
||||
onSelectChange(value, 'object');
|
||||
});
|
||||
this.selects.key.on('change', function (value) {
|
||||
onSelectChange(value, 'key');
|
||||
});
|
||||
|
||||
Object.values(this.selects).forEach(function (select) {
|
||||
$('.t-configuration', self.domElement).append(select.getDOM());
|
||||
});
|
||||
|
||||
$(this.domElement).on('input', 'input', onValueInput);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the DOM element representing this condition in the view
|
||||
* @return {Element}
|
||||
*/
|
||||
Condition.prototype.getDOM = function (container) {
|
||||
return this.domElement;
|
||||
};
|
||||
|
||||
/**
|
||||
* Register a callback with this condition: supported callbacks are remove, change,
|
||||
* duplicate
|
||||
* @param {string} event The key for the event to listen to
|
||||
* @param {function} callback The function that this rule will envoke on this event
|
||||
* @param {Object} context A reference to a scope to use as the context for
|
||||
* context for the callback function
|
||||
*/
|
||||
Condition.prototype.on = function (event, callback, context) {
|
||||
if (this.supportedCallbacks.includes(event)) {
|
||||
this.eventEmitter.on(event, callback, context || this);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Hide the appropriate inputs when this is the only condition
|
||||
*/
|
||||
Condition.prototype.hideButtons = function () {
|
||||
this.deleteButton.hide();
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove this condition from the configuration. Invokes any registered
|
||||
* remove callbacks
|
||||
*/
|
||||
Condition.prototype.remove = function () {
|
||||
this.eventEmitter.emit('remove', this.index);
|
||||
};
|
||||
|
||||
/**
|
||||
* Make a deep clone of this condition's configuration and invoke any duplicate
|
||||
* callbacks with the cloned configuration and this rule's index
|
||||
*/
|
||||
Condition.prototype.duplicate = function () {
|
||||
var sourceCondition = JSON.parse(JSON.stringify(this.config));
|
||||
this.eventEmitter.emit('duplicate', {
|
||||
sourceCondition: sourceCondition,
|
||||
index: this.index
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* When an operation is selected, create the appropriate value inputs
|
||||
* and add them to the view
|
||||
* @param {string} operation The key of currently selected operation
|
||||
*/
|
||||
Condition.prototype.generateValueInputs = function (operation) {
|
||||
var evaluator = this.conditionManager.getEvaluator(),
|
||||
inputArea = $('.t-value-inputs', this.domElement),
|
||||
inputCount,
|
||||
inputType,
|
||||
newInput,
|
||||
index = 0;
|
||||
|
||||
inputArea.html('');
|
||||
this.valueInputs = [];
|
||||
|
||||
if (evaluator.getInputCount(operation)) {
|
||||
inputCount = evaluator.getInputCount(operation);
|
||||
inputType = evaluator.getInputType(operation);
|
||||
while (index < inputCount) {
|
||||
if (!this.config.values[index]) {
|
||||
this.config.values[index] = (inputType === 'number' ? 0 : '');
|
||||
}
|
||||
newInput = $('<input type = "' + inputType + '" value = "' + this.config.values[index] + '"> </input>');
|
||||
this.valueInputs.push(newInput.get(0));
|
||||
inputArea.append(newInput);
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return Condition;
|
||||
});
|
||||
449
src/plugins/summaryWidget/src/ConditionEvaluator.js
Normal file
449
src/plugins/summaryWidget/src/ConditionEvaluator.js
Normal file
@@ -0,0 +1,449 @@
|
||||
define([], function () {
|
||||
|
||||
/**
|
||||
* Responsible for maintaining the possible operations for conditions
|
||||
* in this widget, and evaluating the boolean value of conditions passed as
|
||||
* input.
|
||||
* @constructor
|
||||
* @param {Object} subscriptionCache A cache consisting of the latest available
|
||||
* data for any telemetry sources in the widget's
|
||||
* composition.
|
||||
* @param {Object} compositionObjs The current set of composition objects to
|
||||
* evaluate for 'any' and 'all' conditions
|
||||
*/
|
||||
function ConditionEvaluator(subscriptionCache, compositionObjs) {
|
||||
this.subscriptionCache = subscriptionCache;
|
||||
this.compositionObjs = compositionObjs;
|
||||
|
||||
this.testCache = {};
|
||||
this.useTestCache = false;
|
||||
|
||||
/**
|
||||
* Maps value types to HTML input field types. These
|
||||
* type of inputs will be generated by conditions expecting this data type
|
||||
*/
|
||||
this.inputTypes = {
|
||||
number: 'number',
|
||||
string: 'text'
|
||||
};
|
||||
|
||||
/**
|
||||
* Functions to validate that the input to an operation is of the type
|
||||
* that it expects, in order to prevent unexpected behavior. Will be
|
||||
* invoked before the corresponding operation is executed
|
||||
*/
|
||||
this.inputValidators = {
|
||||
number: this.validateNumberInput,
|
||||
string: this.validateStringInput
|
||||
};
|
||||
|
||||
/**
|
||||
* A library of operations supported by this rule evaluator. Each operation
|
||||
* consists of the following fields:
|
||||
* operation: a function with boolean return type to be invoked when this
|
||||
* operation is used. Will be called with an array of inputs
|
||||
* where input [0] is the telemetry value and input [1..n] are
|
||||
* any comparison values
|
||||
* text: a human-readable description of this operation to populate selects
|
||||
* appliesTo: an array of identifiers for types that operation may be used on
|
||||
* inputCount: the number of inputs required to get any necessary comparison
|
||||
* values for the operation
|
||||
* getDescription: A function returning a human-readable shorthand description of
|
||||
* this operation to populate the 'description' field in the rule header.
|
||||
* Will be invoked with an array of a condition's comparison values.
|
||||
*/
|
||||
this.operations = {
|
||||
equalTo: {
|
||||
operation: function (input) {
|
||||
return input[0] === input[1];
|
||||
},
|
||||
text: 'is Equal To',
|
||||
appliesTo: ['number'],
|
||||
inputCount: 1,
|
||||
getDescription: function (values) {
|
||||
return ' == ' + values[0];
|
||||
}
|
||||
},
|
||||
notEqualTo: {
|
||||
operation: function (input) {
|
||||
return input[0] !== input[1];
|
||||
},
|
||||
text: 'is Not Equal To',
|
||||
appliesTo: ['number'],
|
||||
inputCount: 1,
|
||||
getDescription: function (values) {
|
||||
return ' != ' + values[0];
|
||||
}
|
||||
},
|
||||
greaterThan: {
|
||||
operation: function (input) {
|
||||
return input[0] > input[1];
|
||||
},
|
||||
text: 'is Greater Than',
|
||||
appliesTo: ['number'],
|
||||
inputCount: 1,
|
||||
getDescription: function (values) {
|
||||
return ' > ' + values[0];
|
||||
}
|
||||
},
|
||||
lessThan: {
|
||||
operation: function (input) {
|
||||
return input[0] < input[1];
|
||||
},
|
||||
text: 'is Less Than',
|
||||
appliesTo: ['number'],
|
||||
inputCount: 1,
|
||||
getDescription: function (values) {
|
||||
return ' < ' + values[0];
|
||||
}
|
||||
},
|
||||
greaterThanOrEq: {
|
||||
operation: function (input) {
|
||||
return input[0] >= input[1];
|
||||
},
|
||||
text: 'is Greater Than or Equal To',
|
||||
appliesTo: ['number'],
|
||||
inputCount: 1,
|
||||
getDescription: function (values) {
|
||||
return ' >= ' + values[0];
|
||||
}
|
||||
},
|
||||
lessThanOrEq: {
|
||||
operation: function (input) {
|
||||
return input[0] <= input[1];
|
||||
},
|
||||
text: 'is Less Than or Equal To',
|
||||
appliesTo: ['number'],
|
||||
inputCount: 1,
|
||||
getDescription: function (values) {
|
||||
return ' <= ' + values[0];
|
||||
}
|
||||
},
|
||||
between: {
|
||||
operation: function (input) {
|
||||
return input[0] > input[1] && input[0] < input[2];
|
||||
},
|
||||
text: 'is Between',
|
||||
appliesTo: ['number'],
|
||||
inputCount: 2,
|
||||
getDescription: function (values) {
|
||||
return ' between ' + values[0] + ' and ' + values[1];
|
||||
}
|
||||
},
|
||||
notBetween: {
|
||||
operation: function (input) {
|
||||
return input[0] < input[1] || input[0] > input[2];
|
||||
},
|
||||
text: 'is Not Between',
|
||||
appliesTo: ['number'],
|
||||
inputCount: 2,
|
||||
getDescription: function (values) {
|
||||
return ' not between ' + values[0] + ' and ' + values[1];
|
||||
}
|
||||
},
|
||||
textContains: {
|
||||
operation: function (input) {
|
||||
return input[0] && input[1] && input[0].includes(input[1]);
|
||||
},
|
||||
text: 'Text Contains',
|
||||
appliesTo: ['string'],
|
||||
inputCount: 1,
|
||||
getDescription: function (values) {
|
||||
return ' contains ' + values[0];
|
||||
}
|
||||
},
|
||||
textDoesNotContain: {
|
||||
operation: function (input) {
|
||||
return input[0] && input[1] && !input[0].includes(input[1]);
|
||||
},
|
||||
text: 'Text Does Not Contain',
|
||||
appliesTo: ['string'],
|
||||
inputCount: 1,
|
||||
getDescription: function (values) {
|
||||
return ' does not contain ' + values[0];
|
||||
}
|
||||
},
|
||||
textStartsWith: {
|
||||
operation: function (input) {
|
||||
return input[0].startsWith(input[1]);
|
||||
},
|
||||
text: 'Text Starts With',
|
||||
appliesTo: ['string'],
|
||||
inputCount: 1,
|
||||
getDescription: function (values) {
|
||||
return ' starts with ' + values[0];
|
||||
}
|
||||
},
|
||||
textEndsWith: {
|
||||
operation: function (input) {
|
||||
return input[0].endsWith(input[1]);
|
||||
},
|
||||
text: 'Text Ends With',
|
||||
appliesTo: ['string'],
|
||||
inputCount: 1,
|
||||
getDescription: function (values) {
|
||||
return ' ends with ' + values[0];
|
||||
}
|
||||
},
|
||||
textIsExactly: {
|
||||
operation: function (input) {
|
||||
return input[0] === input[1];
|
||||
},
|
||||
text: 'Text is Exactly',
|
||||
appliesTo: ['string'],
|
||||
inputCount: 1,
|
||||
getDescription: function (values) {
|
||||
return ' is exactly ' + values[0];
|
||||
}
|
||||
},
|
||||
isUndefined: {
|
||||
operation: function (input) {
|
||||
return typeof input[0] === 'undefined';
|
||||
},
|
||||
text: 'is Undefined',
|
||||
appliesTo: ['string', 'number'],
|
||||
inputCount: 0,
|
||||
getDescription: function () {
|
||||
return ' is undefined';
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate the conditions passed in as an argument, and return the boolean
|
||||
* value of these conditions. Available evaluation modes are 'any', which will
|
||||
* return true if any of the conditions evaluates to true (i.e. logical OR); 'all',
|
||||
* which returns true only if all conditions evalute to true (i.e. logical AND);
|
||||
* or 'js', which returns the boolean value of a custom JavaScript conditional.
|
||||
* @param {} conditions Either an array of objects with object, key, operation,
|
||||
* and value fields, or a string representing a JavaScript
|
||||
* condition.
|
||||
* @param {string} mode The key of the mode to use when evaluating the conditions.
|
||||
* @return {boolean} The boolean value of the conditions
|
||||
*/
|
||||
ConditionEvaluator.prototype.execute = function (conditions, mode) {
|
||||
var active = false,
|
||||
conditionValue,
|
||||
conditionDefined = false,
|
||||
self = this,
|
||||
firstRuleEvaluated = false,
|
||||
compositionObjs = this.compositionObjs;
|
||||
|
||||
if (mode === 'js') {
|
||||
active = this.executeJavaScriptCondition(conditions);
|
||||
} else {
|
||||
(conditions || []).forEach(function (condition) {
|
||||
conditionDefined = false;
|
||||
if (condition.object === 'any') {
|
||||
conditionValue = false;
|
||||
Object.keys(compositionObjs).forEach(function (objId) {
|
||||
try {
|
||||
conditionValue = conditionValue ||
|
||||
self.executeCondition(objId, condition.key,
|
||||
condition.operation, condition.values);
|
||||
conditionDefined = true;
|
||||
} catch (e) {
|
||||
//ignore a malformed condition
|
||||
}
|
||||
});
|
||||
} else if (condition.object === 'all') {
|
||||
conditionValue = true;
|
||||
Object.keys(compositionObjs).forEach(function (objId) {
|
||||
try {
|
||||
conditionValue = conditionValue &&
|
||||
self.executeCondition(objId, condition.key,
|
||||
condition.operation, condition.values);
|
||||
conditionDefined = true;
|
||||
} catch (e) {
|
||||
//ignore a malformed condition
|
||||
}
|
||||
});
|
||||
} else {
|
||||
try {
|
||||
conditionValue = self.executeCondition(condition.object, condition.key,
|
||||
condition.operation, condition.values);
|
||||
conditionDefined = true;
|
||||
} catch (e) {
|
||||
//ignore malformed condition
|
||||
}
|
||||
}
|
||||
|
||||
if (conditionDefined) {
|
||||
active = (mode === 'all' && !firstRuleEvaluated ? true : active);
|
||||
firstRuleEvaluated = true;
|
||||
if (mode === 'any') {
|
||||
active = active || conditionValue;
|
||||
} else if (mode === 'all') {
|
||||
active = active && conditionValue;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return active;
|
||||
};
|
||||
|
||||
/**
|
||||
* Execute a condition defined as an object.
|
||||
* @param {string} object The identifier of the telemetry object to retrieve data from
|
||||
* @param {string} key The property of the telemetry object
|
||||
* @param {string} operation The key of the operation in this ConditionEvaluator to executeCondition
|
||||
* @param {string} values An array of comparison values to invoke the operation with
|
||||
* @return {boolean} The value of this condition
|
||||
*/
|
||||
ConditionEvaluator.prototype.executeCondition = function (object, key, operation, values) {
|
||||
var cache = (this.useTestCache ? this.testCache : this.subscriptionCache),
|
||||
telemetryValue,
|
||||
op,
|
||||
input,
|
||||
validator;
|
||||
|
||||
if (cache[object] && typeof cache[object][key] !== 'undefined') {
|
||||
telemetryValue = [cache[object][key]];
|
||||
}
|
||||
op = this.operations[operation] && this.operations[operation].operation;
|
||||
input = telemetryValue && telemetryValue.concat(values);
|
||||
validator = op && this.inputValidators[this.operations[operation].appliesTo[0]];
|
||||
|
||||
if (op && input && validator) {
|
||||
return validator(input) && op(input);
|
||||
} else {
|
||||
throw new Error('Malformed condition');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Interpret a string as a JavaScript conditional, and return its boolean value
|
||||
* @param {string} condition The string to interpreted as JavaScript
|
||||
* @return {boolean} The value of the conditions
|
||||
*/
|
||||
ConditionEvaluator.prototype.executeJavaScriptCondition = function (condition) {
|
||||
var conditionValue = false;
|
||||
//TODO: implement JavaScript execution
|
||||
return conditionValue;
|
||||
};
|
||||
|
||||
/**
|
||||
* A function that returns true only if each value in its input argument is
|
||||
* of a numerical type
|
||||
* @param {[]} input An array of values
|
||||
* @returns {boolean}
|
||||
*/
|
||||
ConditionEvaluator.prototype.validateNumberInput = function (input) {
|
||||
var valid = true;
|
||||
input.forEach(function (value) {
|
||||
valid = valid && (typeof value === 'number');
|
||||
});
|
||||
return valid;
|
||||
};
|
||||
|
||||
/**
|
||||
* A function that returns true only if each value in its input argument is
|
||||
* a string
|
||||
* @param {[]} input An array of values
|
||||
* @returns {boolean}
|
||||
*/
|
||||
ConditionEvaluator.prototype.validateStringInput = function (input) {
|
||||
var valid = true;
|
||||
input.forEach(function (value) {
|
||||
valid = valid && (typeof value === 'string');
|
||||
});
|
||||
return valid;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the keys of operations supported by this evaluator
|
||||
* @return {string[]} An array of the keys of supported operations
|
||||
*/
|
||||
ConditionEvaluator.prototype.getOperationKeys = function () {
|
||||
return Object.keys(this.operations);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the human-readable text corresponding to a given operation
|
||||
* @param {string} key The key of the operation
|
||||
* @return {string} The text description of the operation
|
||||
*/
|
||||
ConditionEvaluator.prototype.getOperationText = function (key) {
|
||||
return this.operations[key].text;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns true only of the given operation applies to a given type
|
||||
* @param {string} key The key of the operation
|
||||
* @param {string} type The value type to query
|
||||
* @returns {boolean} True if the condition applies, false otherwise
|
||||
*/
|
||||
ConditionEvaluator.prototype.operationAppliesTo = function (key, type) {
|
||||
return (this.operations[key].appliesTo.includes(type));
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the number of value inputs required by an operation
|
||||
* @param {string} key The key of the operation to query
|
||||
* @return {number}
|
||||
*/
|
||||
ConditionEvaluator.prototype.getInputCount = function (key) {
|
||||
if (this.operations[key]) {
|
||||
return this.operations[key].inputCount;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the human-readable shorthand description of the operation for a rule header
|
||||
* @param {string} key The key of the operation to query
|
||||
* @param {} values An array of values with which to invoke the getDescription function
|
||||
* of the operation
|
||||
* @return {string} A text description of this operation
|
||||
*/
|
||||
ConditionEvaluator.prototype.getOperationDescription = function (key, values) {
|
||||
if (this.operations[key]) {
|
||||
return this.operations[key].getDescription(values);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the HTML input type associated with a given operation
|
||||
* @param {string} key The key of the operation to query
|
||||
* @return {string} The key for an HTML5 input type
|
||||
*/
|
||||
ConditionEvaluator.prototype.getInputType = function (key) {
|
||||
var type;
|
||||
if (this.operations[key]) {
|
||||
type = this.operations[key].appliesTo[0];
|
||||
}
|
||||
if (this.inputTypes[type]) {
|
||||
return this.inputTypes[type];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the HTML input type associated with a value type
|
||||
* @param {string} dataType The JavaScript value type
|
||||
* @return {string} The key for an HTML5 input type
|
||||
*/
|
||||
ConditionEvaluator.prototype.getInputTypeById = function (dataType) {
|
||||
return this.inputTypes[dataType];
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the test data cache used by this rule evaluator
|
||||
* @param {object} testCache A mock cache following the format of the real
|
||||
* subscription cache
|
||||
*/
|
||||
ConditionEvaluator.prototype.setTestDataCache = function (testCache) {
|
||||
this.testCache = testCache;
|
||||
};
|
||||
|
||||
/**
|
||||
* Have this RuleEvaluator pull data values from the provided test cache
|
||||
* instead of its actual subscription cache when evaluating. If invoked with true,
|
||||
* will use the test cache; otherwise, will use the subscription cache
|
||||
* @param {boolean} useTestData Boolean flag
|
||||
*/
|
||||
ConditionEvaluator.prototype.useTestData = function (useTestCache) {
|
||||
this.useTestCache = useTestCache;
|
||||
};
|
||||
|
||||
return ConditionEvaluator;
|
||||
});
|
||||
364
src/plugins/summaryWidget/src/ConditionManager.js
Normal file
364
src/plugins/summaryWidget/src/ConditionManager.js
Normal file
@@ -0,0 +1,364 @@
|
||||
define ([
|
||||
'./ConditionEvaluator',
|
||||
'EventEmitter',
|
||||
'lodash'
|
||||
], function (
|
||||
ConditionEvaluator,
|
||||
EventEmitter,
|
||||
_
|
||||
) {
|
||||
|
||||
/**
|
||||
* Provides a centralized content manager for conditions in the summary widget.
|
||||
* Loads and caches composition and telemetry subscriptions, and maintains a
|
||||
* {ConditionEvaluator} instance to handle evaluation
|
||||
* @constructor
|
||||
* @param {Object} domainObject the Summary Widget domain object
|
||||
* @param {MCT} openmct an MCT instance
|
||||
*/
|
||||
function ConditionManager(domainObject, openmct) {
|
||||
this.domainObject = domainObject;
|
||||
this.openmct = openmct;
|
||||
|
||||
this.composition = this.openmct.composition.get(this.domainObject);
|
||||
this.compositionObjs = {};
|
||||
this.eventEmitter = new EventEmitter();
|
||||
this.supportedCallbacks = ['add', 'remove', 'load', 'metadata', 'receiveTelemetry'];
|
||||
|
||||
this.keywordLabels = {
|
||||
any: 'Any Telemetry',
|
||||
all: 'All Telemetry'
|
||||
};
|
||||
|
||||
this.telemetryMetadataById = {
|
||||
any: {},
|
||||
all: {}
|
||||
};
|
||||
|
||||
this.telemetryTypesById = {
|
||||
any: {},
|
||||
all: {}
|
||||
};
|
||||
|
||||
this.subscriptions = {};
|
||||
this.subscriptionCache = {};
|
||||
this.loadComplete = false;
|
||||
this.metadataLoadComplete = false;
|
||||
this.evaluator = new ConditionEvaluator(this.subscriptionCache, this.compositionObjs);
|
||||
|
||||
this.composition.on('add', this.onCompositionAdd, this);
|
||||
this.composition.on('remove', this.onCompositionRemove, this);
|
||||
this.composition.on('load', this.onCompositionLoad, this);
|
||||
|
||||
this.composition.load();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a callback with this ConditionManager: supported callbacks are add
|
||||
* remove, load, metadata, and receiveTelemetry
|
||||
* @param {string} event The key for the event to listen to
|
||||
* @param {function} callback The function that this rule will envoke on this event
|
||||
* @param {Object} context A reference to a scope to use as the context for
|
||||
* context for the callback function
|
||||
*/
|
||||
ConditionManager.prototype.on = function (event, callback, context) {
|
||||
if (this.supportedCallbacks.includes(event)) {
|
||||
this.eventEmitter.on(event, callback, context || this);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a set of rules, execute the conditions associated with each rule
|
||||
* and return the id of the last rule whose conditions evaluate to true
|
||||
* @param {string[]} ruleOrder An array of rule IDs indicating what order They
|
||||
* should be evaluated in
|
||||
* @param {Object} rules An object mapping rule IDs to rule configurations
|
||||
* @return {string} The ID of the rule to display on the widget
|
||||
*/
|
||||
ConditionManager.prototype.executeRules = function (ruleOrder, rules) {
|
||||
var self = this,
|
||||
activeId = ruleOrder[0],
|
||||
rule,
|
||||
conditions;
|
||||
|
||||
ruleOrder.forEach(function (ruleId) {
|
||||
rule = rules[ruleId];
|
||||
conditions = rule.getProperty('trigger') === 'js' ?
|
||||
rule.getProperty('jsCondition') : rule.getProperty('conditions');
|
||||
if (self.evaluator.execute(conditions, rule.getProperty('trigger'))) {
|
||||
activeId = ruleId;
|
||||
}
|
||||
});
|
||||
|
||||
return activeId;
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a field to the list of all available metadata fields in the widget
|
||||
* @param {Object} metadatum An object representing a set of telemetry metadata
|
||||
*/
|
||||
ConditionManager.prototype.addGlobalMetadata = function (metadatum) {
|
||||
this.telemetryMetadataById.any[metadatum.key] = metadatum;
|
||||
this.telemetryMetadataById.all[metadatum.key] = metadatum;
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a field to the list of properties for globally available metadata
|
||||
* @param {string} key The key for the property this type applies to
|
||||
* @param {string} type The type that should be associated with this property
|
||||
*/
|
||||
ConditionManager.prototype.addGlobalPropertyType = function (key, type) {
|
||||
this.telemetryTypesById.any[key] = type;
|
||||
this.telemetryTypesById.all[key] = type;
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a telemetry-producing domain object, associate each of it's telemetry
|
||||
* fields with a type, parsing from historical data.
|
||||
* @param {Object} object a domain object that can produce telemetry
|
||||
* @return {Promise} A promise that resolves when a telemetry request
|
||||
* has completed and types have been parsed
|
||||
*/
|
||||
ConditionManager.prototype.parsePropertyTypes = function (object) {
|
||||
var telemetryAPI = this.openmct.telemetry,
|
||||
key,
|
||||
type,
|
||||
self = this;
|
||||
|
||||
self.telemetryTypesById[object.identifier.key] = {};
|
||||
return telemetryAPI.request(object, {}).then(function (telemetry) {
|
||||
Object.entries(telemetry[telemetry.length - 1]).forEach(function (telem) {
|
||||
key = telem[0];
|
||||
type = typeof telem[1];
|
||||
self.telemetryTypesById[object.identifier.key][key] = type;
|
||||
self.subscriptionCache[object.identifier.key][key] = telem[1];
|
||||
self.addGlobalPropertyType(key, type);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse types of telemetry fields from all composition objects; used internally
|
||||
* to perform a block types load once initial composition load has completed
|
||||
* @return {Promise} A promise that resolves when all metadata has been loaded
|
||||
* and property types parsed
|
||||
*/
|
||||
ConditionManager.prototype.parseAllPropertyTypes = function () {
|
||||
var self = this,
|
||||
index = 0,
|
||||
objs = Object.values(self.compositionObjs),
|
||||
promise = new Promise(function (resolve, reject) {
|
||||
if (objs.length === 0) {
|
||||
resolve();
|
||||
}
|
||||
objs.forEach(function (obj) {
|
||||
self.parsePropertyTypes(obj).then(function () {
|
||||
if (index === objs.length - 1) {
|
||||
resolve();
|
||||
}
|
||||
index += 1;
|
||||
});
|
||||
});
|
||||
});
|
||||
return promise;
|
||||
};
|
||||
|
||||
/**
|
||||
* Invoked when a telemtry subscription yields new data. Updates the LAD
|
||||
* cache and invokes any registered receiveTelemetry callbacks
|
||||
* @param {string} objId The key associated with the telemetry source
|
||||
* @param {datum} datum The new data from the telemetry source
|
||||
* @private
|
||||
*/
|
||||
ConditionManager.prototype.handleSubscriptionCallback = function (objId, datum) {
|
||||
this.subscriptionCache[objId] = datum;
|
||||
this.eventEmitter.emit('receiveTelemetry');
|
||||
};
|
||||
|
||||
/**
|
||||
* Event handler for an add event in this Summary Widget's composition.
|
||||
* Sets up subscription handlers and parses its property types.
|
||||
* @param {Object} obj The newly added domain object
|
||||
* @private
|
||||
*/
|
||||
ConditionManager.prototype.onCompositionAdd = function (obj) {
|
||||
var compositionKeys,
|
||||
telemetryAPI = this.openmct.telemetry,
|
||||
objId = obj.identifier.key,
|
||||
telemetryMetadata,
|
||||
self = this;
|
||||
|
||||
if (telemetryAPI.canProvideTelemetry(obj)) {
|
||||
self.compositionObjs[objId] = obj;
|
||||
self.telemetryMetadataById[objId] = {};
|
||||
|
||||
compositionKeys = self.domainObject.composition.map(function (object) {
|
||||
return object.key;
|
||||
});
|
||||
if (!compositionKeys.includes(obj.identifier.key)) {
|
||||
self.domainObject.composition.push(obj.identifier);
|
||||
}
|
||||
|
||||
telemetryMetadata = telemetryAPI.getMetadata(obj).values();
|
||||
telemetryMetadata.forEach(function (metaDatum) {
|
||||
self.telemetryMetadataById[objId][metaDatum.key] = metaDatum;
|
||||
self.addGlobalMetadata(metaDatum);
|
||||
});
|
||||
|
||||
self.subscriptionCache[objId] = {};
|
||||
self.subscriptions[objId] = telemetryAPI.subscribe(obj, function (datum) {
|
||||
self.handleSubscriptionCallback(objId, datum);
|
||||
}, {});
|
||||
|
||||
/**
|
||||
* if this is the initial load, parsing property types will be postponed
|
||||
* until all composition objects have been loaded
|
||||
*/
|
||||
if (self.loadComplete) {
|
||||
self.parsePropertyTypes(obj);
|
||||
}
|
||||
|
||||
self.eventEmitter.emit('add', obj);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Invoked on a remove event in this Summary Widget's compostion. Removes
|
||||
* the object from the local composition, and untracks it
|
||||
* @param {object} identifier The identifier of the object to be removed
|
||||
* @private
|
||||
*/
|
||||
ConditionManager.prototype.onCompositionRemove = function (identifier) {
|
||||
_.remove(this.domainObject.composition, function (id) {
|
||||
return id.key === identifier.key;
|
||||
});
|
||||
delete this.compositionObjs[identifier.key];
|
||||
this.subscriptions[identifier.key](); //unsubscribe from telemetry source
|
||||
this.eventEmitter.emit('remove', identifier);
|
||||
};
|
||||
|
||||
/**
|
||||
* Invoked when the Summary Widget's composition finishes its initial load.
|
||||
* Invokes any registered load callbacks, does a block load of all metadata,
|
||||
* and then invokes any registered metadata load callbacks.
|
||||
* @private
|
||||
*/
|
||||
ConditionManager.prototype.onCompositionLoad = function () {
|
||||
var self = this;
|
||||
self.loadComplete = true;
|
||||
self.eventEmitter.emit('load');
|
||||
self.parseAllPropertyTypes().then(function () {
|
||||
self.metadataLoadComplete = true;
|
||||
self.eventEmitter.emit('metadata');
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the currently tracked telemetry sources
|
||||
* @return {Object} An object mapping object keys to domain objects
|
||||
*/
|
||||
ConditionManager.prototype.getComposition = function () {
|
||||
return this.compositionObjs;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the human-readable name of a domain object from its key
|
||||
* @param {string} id The key of the domain object
|
||||
* @return {string} The human-readable name of the domain object
|
||||
*/
|
||||
ConditionManager.prototype.getObjectName = function (id) {
|
||||
var name;
|
||||
|
||||
if (this.keywordLabels[id]) {
|
||||
name = this.keywordLabels[id];
|
||||
} else if (this.compositionObjs[id]) {
|
||||
name = this.compositionObjs[id].name;
|
||||
}
|
||||
|
||||
return name;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the property metadata associated with a given telemetry source
|
||||
* @param {string} id The key associated with the domain object
|
||||
* @return {Object} Returns an object with fields representing each telemetry field
|
||||
*/
|
||||
ConditionManager.prototype.getTelemetryMetadata = function (id) {
|
||||
return this.telemetryMetadataById[id];
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the type associated with a telemtry data field of a particular domain
|
||||
* object
|
||||
* @param {string} id The key associated with the domain object
|
||||
* @param {string} property The telemetry field key to retrieve the type of
|
||||
* @return {string} The type name
|
||||
*/
|
||||
ConditionManager.prototype.getTelemetryPropertyType = function (id, property) {
|
||||
if (this.telemetryTypesById[id]) {
|
||||
return this.telemetryTypesById[id][property];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the human-readable name of a telemtry data field of a particular domain
|
||||
* object
|
||||
* @param {string} id The key associated with the domain object
|
||||
* @param {string} property The telemetry field key to retrieve the type of
|
||||
* @return {string} The telemetry field name
|
||||
*/
|
||||
ConditionManager.prototype.getTelemetryPropertyName = function (id, property) {
|
||||
if (this.telemetryMetadataById[id] && this.telemetryMetadataById[id][property]) {
|
||||
return this.telemetryMetadataById[id][property].name;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the {ConditionEvaluator} instance associated with this condition
|
||||
* manager
|
||||
* @return {ConditionEvaluator}
|
||||
*/
|
||||
ConditionManager.prototype.getEvaluator = function () {
|
||||
return this.evaluator;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns true if the initial compostion load has completed
|
||||
* @return {boolean}
|
||||
*/
|
||||
ConditionManager.prototype.loadCompleted = function () {
|
||||
return this.loadComplete;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns true if the initial block metadata load has completed
|
||||
*/
|
||||
ConditionManager.prototype.metadataLoadCompleted = function () {
|
||||
return this.metadataLoadComplete;
|
||||
};
|
||||
|
||||
/**
|
||||
* Triggers the telemetryRecieve callbacks registered to this ConditionManager,
|
||||
* used by the {TestDataManager} to force a rule evaluation when test data is
|
||||
* enabled
|
||||
*/
|
||||
ConditionManager.prototype.triggerTelemetryCallback = function () {
|
||||
this.eventEmitter.emit('receiveTelemetry');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Unsubscribe from all registered telemetry sources and unregister all event
|
||||
* listeners registered with the Open MCT APIs
|
||||
*/
|
||||
ConditionManager.prototype.destroy = function () {
|
||||
Object.values(this.subscriptions).forEach(function (unsubscribeFunction) {
|
||||
unsubscribeFunction();
|
||||
});
|
||||
this.composition.off('add', this.onCompositionAdd, this);
|
||||
this.composition.off('remove', this.onCompositionRemove, this);
|
||||
this.composition.off('load', this.onCompositionLoad, this);
|
||||
};
|
||||
|
||||
return ConditionManager;
|
||||
});
|
||||
459
src/plugins/summaryWidget/src/Rule.js
Normal file
459
src/plugins/summaryWidget/src/Rule.js
Normal file
@@ -0,0 +1,459 @@
|
||||
define([
|
||||
'text!../res/ruleTemplate.html',
|
||||
'./Condition',
|
||||
'./input/ColorPalette',
|
||||
'./input/IconPalette',
|
||||
'EventEmitter',
|
||||
'lodash',
|
||||
'zepto'
|
||||
], function (
|
||||
ruleTemplate,
|
||||
Condition,
|
||||
ColorPalette,
|
||||
IconPalette,
|
||||
EventEmitter,
|
||||
_,
|
||||
$
|
||||
) {
|
||||
|
||||
/**
|
||||
* An object representing a summary widget rule. Maintains a set of text
|
||||
* and css properties for output, and a set of conditions for configuring
|
||||
* when the rule will be applied to the summary widget.
|
||||
* @constructor
|
||||
* @param {Object} ruleConfig A JavaScript object representing the configuration of this rule
|
||||
* @param {Object} domainObject The Summary Widget domain object which contains this rule
|
||||
* @param {MCT} openmct An MCT instance
|
||||
* @param {ConditionManager} conditionManager A ConditionManager instance
|
||||
* @param {WidgetDnD} widgetDnD A WidgetDnD instance to handle dragging and dropping rules
|
||||
* @param {element} container The DOM element which cotains this summary widget
|
||||
*/
|
||||
function Rule(ruleConfig, domainObject, openmct, conditionManager, widgetDnD, container) {
|
||||
var self = this;
|
||||
|
||||
this.config = ruleConfig;
|
||||
this.domainObject = domainObject;
|
||||
this.openmct = openmct;
|
||||
this.conditionManager = conditionManager;
|
||||
this.widgetDnD = widgetDnD;
|
||||
this.container = container;
|
||||
|
||||
this.domElement = $(ruleTemplate);
|
||||
this.eventEmitter = new EventEmitter();
|
||||
this.supportedCallbacks = ['remove', 'duplicate', 'change', 'conditionChange'];
|
||||
this.conditions = [];
|
||||
this.dragging = false;
|
||||
|
||||
this.remove = this.remove.bind(this);
|
||||
this.duplicate = this.duplicate.bind(this);
|
||||
|
||||
this.thumbnail = $('.t-widget-thumb', this.domElement);
|
||||
this.thumbnailIcon = $('.widget-icon', this.domElement);
|
||||
this.thumbnailLabel = $('.widget-label', this.domElement);
|
||||
this.title = $('.rule-title', this.domElement);
|
||||
this.description = $('.rule-description', this.domElement);
|
||||
this.trigger = $('.t-trigger', this.domElement);
|
||||
this.toggleConfigButton = $('.view-control', this.domElement);
|
||||
this.configArea = $('.widget-rule-content', this.domElement);
|
||||
this.grippy = $('.t-grippy', this.domElement);
|
||||
this.conditionArea = $('.t-widget-rule-config', this.domElement);
|
||||
this.jsConditionArea = $('.t-rule-js-condition-input-holder', this.domElement);
|
||||
this.deleteButton = $('.t-delete', this.domElement);
|
||||
this.duplicateButton = $('.t-duplicate', this.domElement);
|
||||
this.addConditionButton = $('.add-condition', this.domElement);
|
||||
|
||||
/**
|
||||
* The text inputs for this rule: any input included in this object will
|
||||
* have the appropriate event handlers registered to it, and it's corresponding
|
||||
* field in the domain object will be updated with its value
|
||||
*/
|
||||
this.textInputs = {
|
||||
name: $('.t-rule-name-input', this.domElement),
|
||||
label: $('.t-rule-label-input', this.domElement),
|
||||
message: $('.t-rule-message-input', this.domElement),
|
||||
jsCondition: $('.t-rule-js-condition-input', this.domElement)
|
||||
};
|
||||
|
||||
this.iconInput = new IconPalette('', container);
|
||||
this.colorInputs = {
|
||||
'background-color': new ColorPalette('icon-paint-bucket', container),
|
||||
'border-color': new ColorPalette('icon-line-horz', container),
|
||||
'color': new ColorPalette('icon-T', container)
|
||||
};
|
||||
|
||||
this.colorInputs.color.toggleNullOption();
|
||||
|
||||
/**
|
||||
* An onchange event handler method for this rule's icon palettes
|
||||
* @param {string} icon The css class name corresponding to this icon
|
||||
* @private
|
||||
*/
|
||||
function onIconInput(icon) {
|
||||
self.config.icon = icon;
|
||||
self.updateDomainObject('icon', icon);
|
||||
self.thumbnailIcon.removeClass().addClass('widget-icon ' + icon);
|
||||
self.eventEmitter.emit('change');
|
||||
}
|
||||
|
||||
/**
|
||||
* An onchange event handler method for this rule's color palettes palettes
|
||||
* @param {string} color The color selected in the palette
|
||||
* @param {string} property The css property which this color corresponds to
|
||||
* @private
|
||||
*/
|
||||
function onColorInput(color, property) {
|
||||
self.config.style[property] = color;
|
||||
self.updateDomainObject();
|
||||
self.thumbnail.css(property, color);
|
||||
self.eventEmitter.emit('change');
|
||||
}
|
||||
|
||||
/**
|
||||
* An onchange event handler method for this rule's trigger key
|
||||
* @param {event} event The change event from this rule's select element
|
||||
* @private
|
||||
*/
|
||||
function onTriggerInput(event) {
|
||||
var elem = event.target;
|
||||
self.config.trigger = elem.value;
|
||||
self.generateDescription();
|
||||
self.updateDomainObject();
|
||||
self.refreshConditions();
|
||||
self.eventEmitter.emit('conditionChange');
|
||||
}
|
||||
|
||||
/**
|
||||
* An onchange event handler method for this rule's text inputs
|
||||
* @param {element} elem The input element that generated the event
|
||||
* @param {string} inputKey The field of this rule's configuration to update
|
||||
* @private
|
||||
*/
|
||||
function onTextInput(elem, inputKey) {
|
||||
self.config[inputKey] = elem.value;
|
||||
self.updateDomainObject();
|
||||
if (inputKey === 'name') {
|
||||
self.title.html(elem.value);
|
||||
} else if (inputKey === 'label') {
|
||||
self.thumbnailLabel.html(elem.value);
|
||||
}
|
||||
self.eventEmitter.emit('change');
|
||||
}
|
||||
|
||||
/**
|
||||
* An onchange event handler for a mousedown event that initiates a drag gesture
|
||||
* @param {event} event A mouseup event that was registered on this rule's grippy
|
||||
* @private
|
||||
*/
|
||||
function onDragStart(event) {
|
||||
$('.t-drag-indicator').each(function () {
|
||||
$(this).html($('.widget-rule-header', self.domElement).clone().get(0));
|
||||
});
|
||||
self.widgetDnD.setDragImage($('.widget-rule-header', self.domElement).clone().get(0));
|
||||
self.widgetDnD.dragStart(self.config.id);
|
||||
self.domElement.hide();
|
||||
}
|
||||
/**
|
||||
* Show or hide this rule's configuration properties
|
||||
* @private
|
||||
*/
|
||||
function toggleConfig() {
|
||||
self.configArea.toggleClass('expanded');
|
||||
self.toggleConfigButton.toggleClass('expanded');
|
||||
self.config.expanded = !self.config.expanded;
|
||||
}
|
||||
|
||||
$('.t-rule-label-input', this.domElement).before(this.iconInput.getDOM());
|
||||
this.iconInput.set(self.config.icon);
|
||||
this.iconInput.on('change', function (value) {
|
||||
onIconInput(value);
|
||||
});
|
||||
|
||||
// Initialize thumbs when first loading
|
||||
this.thumbnailIcon.removeClass().addClass('widget-icon ' + self.config.icon);
|
||||
this.thumbnailLabel.html(self.config.label);
|
||||
|
||||
Object.keys(this.colorInputs).forEach(function (inputKey) {
|
||||
var input = self.colorInputs[inputKey];
|
||||
input.on('change', function (value) {
|
||||
onColorInput(value, inputKey);
|
||||
});
|
||||
input.set(self.config.style[inputKey]);
|
||||
$('.t-style-input', self.domElement).append(input.getDOM());
|
||||
});
|
||||
|
||||
Object.keys(this.textInputs).forEach(function (inputKey) {
|
||||
self.textInputs[inputKey].prop('value', self.config[inputKey] || '');
|
||||
self.textInputs[inputKey].on('input', function () {
|
||||
onTextInput(this, inputKey);
|
||||
});
|
||||
});
|
||||
|
||||
this.deleteButton.on('click', this.remove);
|
||||
this.duplicateButton.on('click', this.duplicate);
|
||||
this.addConditionButton.on('click', function () {
|
||||
self.initCondition();
|
||||
});
|
||||
this.toggleConfigButton.on('click', toggleConfig);
|
||||
this.trigger.on('change', onTriggerInput);
|
||||
|
||||
this.title.html(self.config.name);
|
||||
this.description.html(self.config.description);
|
||||
this.trigger.prop('value', self.config.trigger);
|
||||
|
||||
this.grippy.on('mousedown', onDragStart);
|
||||
this.widgetDnD.on('drop', function () {
|
||||
this.domElement.show();
|
||||
$('.t-drag-indicator').hide();
|
||||
}, this);
|
||||
|
||||
if (!this.conditionManager.loadCompleted()) {
|
||||
this.config.expanded = false;
|
||||
}
|
||||
|
||||
if (!this.config.expanded) {
|
||||
this.configArea.removeClass('expanded');
|
||||
this.toggleConfigButton.removeClass('expanded');
|
||||
}
|
||||
|
||||
if (this.domainObject.configuration.ruleOrder.length === 2) {
|
||||
$('.t-grippy', this.domElement).hide();
|
||||
}
|
||||
|
||||
this.refreshConditions();
|
||||
|
||||
//if this is the default rule, hide elements that don't apply
|
||||
if (this.config.id === 'default') {
|
||||
$('.t-delete', this.domElement).hide();
|
||||
$('.t-widget-rule-config', this.domElement).hide();
|
||||
$('.t-grippy', this.domElement).hide();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DOM element representing this rule
|
||||
* @return {Element} A DOM element
|
||||
*/
|
||||
Rule.prototype.getDOM = function () {
|
||||
return this.domElement;
|
||||
};
|
||||
|
||||
/**
|
||||
* Unregister any event handlers registered with external sources
|
||||
*/
|
||||
Rule.prototype.destroy = function () {
|
||||
Object.values(this.colorInputs).forEach(function (palette) {
|
||||
palette.destroy();
|
||||
});
|
||||
this.iconInput.destroy();
|
||||
};
|
||||
|
||||
/**
|
||||
* Register a callback with this rule: supported callbacks are remove, change,
|
||||
* conditionChange, and duplicate
|
||||
* @param {string} event The key for the event to listen to
|
||||
* @param {function} callback The function that this rule will envoke on this event
|
||||
* @param {Object} context A reference to a scope to use as the context for
|
||||
* context for the callback function
|
||||
*/
|
||||
Rule.prototype.on = function (event, callback, context) {
|
||||
if (this.supportedCallbacks.includes(event)) {
|
||||
this.eventEmitter.on(event, callback, context || this);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* An event handler for when a condition's configuration is modified
|
||||
* @param {} value
|
||||
* @param {string} property The path in the configuration to updateDomainObject
|
||||
* @param {number} index The index of the condition that initiated this change
|
||||
*/
|
||||
Rule.prototype.onConditionChange = function (event) {
|
||||
_.set(this.config.conditions[event.index], event.property, event.value);
|
||||
this.generateDescription();
|
||||
this.updateDomainObject();
|
||||
this.eventEmitter.emit('conditionChange');
|
||||
};
|
||||
|
||||
/**
|
||||
* During a rule drag event, show the placeholder element after this rule
|
||||
*/
|
||||
Rule.prototype.showDragIndicator = function () {
|
||||
$('.t-drag-indicator').hide();
|
||||
$('.t-drag-indicator', this.domElement).show();
|
||||
};
|
||||
|
||||
/**
|
||||
* Mutate thet domain object with this rule's local configuration
|
||||
*/
|
||||
Rule.prototype.updateDomainObject = function () {
|
||||
this.openmct.objects.mutate(this.domainObject, 'configuration.ruleConfigById.' +
|
||||
this.config.id, this.config);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a property of this rule by key
|
||||
* @param {string} prop They property key of this rule to get
|
||||
* @return {} The queried property
|
||||
*/
|
||||
Rule.prototype.getProperty = function (prop) {
|
||||
return this.config[prop];
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove this rule from the domain object's configuration and invoke any
|
||||
* registered remove callbacks
|
||||
*/
|
||||
Rule.prototype.remove = function () {
|
||||
var ruleOrder = this.domainObject.configuration.ruleOrder,
|
||||
ruleConfigById = this.domainObject.configuration.ruleConfigById,
|
||||
self = this;
|
||||
|
||||
ruleConfigById[self.config.id] = undefined;
|
||||
_.remove(ruleOrder, function (ruleId) {
|
||||
return ruleId === self.config.id;
|
||||
});
|
||||
|
||||
this.openmct.objects.mutate(this.domainObject, 'configuration.ruleConfigById', ruleConfigById);
|
||||
this.openmct.objects.mutate(this.domainObject, 'configuration.ruleOrder', ruleOrder);
|
||||
this.destroy();
|
||||
this.eventEmitter.emit('remove');
|
||||
};
|
||||
|
||||
/**
|
||||
* Makes a deep clone of this rule's configuration, and calls the duplicate event
|
||||
* callback with the cloned configuration as an argument if one has been registered
|
||||
*/
|
||||
Rule.prototype.duplicate = function () {
|
||||
var sourceRule = JSON.parse(JSON.stringify(this.config));
|
||||
sourceRule.expanded = true;
|
||||
this.eventEmitter.emit('duplicate', sourceRule);
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialze a new condition. If called with the sourceConfig and sourceIndex arguments,
|
||||
* will insert a new condition with the provided configuration after the sourceIndex
|
||||
* index. Otherwise, initializes a new blank rule and inserts it at the end
|
||||
* of the list.
|
||||
* @param {Object} [config] The configuration to initialize this rule from,
|
||||
* consisting of sourceCondition and index fields
|
||||
*/
|
||||
Rule.prototype.initCondition = function (config) {
|
||||
var ruleConfigById = this.domainObject.configuration.ruleConfigById,
|
||||
newConfig,
|
||||
sourceIndex = config && config.index,
|
||||
defaultConfig = {
|
||||
object: '',
|
||||
key: '',
|
||||
operation: '',
|
||||
values: []
|
||||
};
|
||||
|
||||
newConfig = (config !== undefined ? config.sourceCondition : defaultConfig);
|
||||
if (sourceIndex !== undefined) {
|
||||
ruleConfigById[this.config.id].conditions.splice(sourceIndex + 1, 0, newConfig);
|
||||
} else {
|
||||
ruleConfigById[this.config.id].conditions.push(newConfig);
|
||||
}
|
||||
this.domainObject.configuration.ruleConfigById = ruleConfigById;
|
||||
this.updateDomainObject();
|
||||
this.refreshConditions();
|
||||
};
|
||||
|
||||
/**
|
||||
* Build {Condition} objects from configuration and rebuild associated view
|
||||
*/
|
||||
Rule.prototype.refreshConditions = function () {
|
||||
var self = this;
|
||||
|
||||
self.conditions = [];
|
||||
$('.t-condition', this.domElement).remove();
|
||||
|
||||
this.config.conditions.forEach(function (condition, index) {
|
||||
var newCondition = new Condition(condition, index, self.conditionManager);
|
||||
newCondition.on('remove', self.removeCondition, self);
|
||||
newCondition.on('duplicate', self.initCondition, self);
|
||||
newCondition.on('change', self.onConditionChange, self);
|
||||
self.conditions.push(newCondition);
|
||||
});
|
||||
|
||||
if (this.config.trigger === 'js') {
|
||||
this.jsConditionArea.show();
|
||||
this.addConditionButton.hide();
|
||||
} else {
|
||||
this.jsConditionArea.hide();
|
||||
this.addConditionButton.show();
|
||||
self.conditions.forEach(function (condition) {
|
||||
$('li:last-of-type', self.conditionArea).before(condition.getDOM());
|
||||
});
|
||||
}
|
||||
|
||||
if (self.conditions.length === 1) {
|
||||
self.conditions[0].hideButtons();
|
||||
}
|
||||
|
||||
self.generateDescription();
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove a condition from this rule's configuration at the given index
|
||||
* @param {number} removeIndex The index of the condition to remove
|
||||
*/
|
||||
Rule.prototype.removeCondition = function (removeIndex) {
|
||||
var ruleConfigById = this.domainObject.configuration.ruleConfigById,
|
||||
conditions = ruleConfigById[this.config.id].conditions;
|
||||
|
||||
_.remove(conditions, function (condition, index) {
|
||||
return index === removeIndex;
|
||||
});
|
||||
|
||||
this.domainObject.configuration.ruleConfigById[this.config.id] = this.config;
|
||||
this.updateDomainObject();
|
||||
this.refreshConditions();
|
||||
this.eventEmitter.emit('conditionChange');
|
||||
};
|
||||
|
||||
/**
|
||||
* Build a human-readable description from this rule's conditions
|
||||
*/
|
||||
Rule.prototype.generateDescription = function () {
|
||||
var description = '',
|
||||
manager = this.conditionManager,
|
||||
evaluator = manager.getEvaluator(),
|
||||
name,
|
||||
property,
|
||||
operation,
|
||||
self = this;
|
||||
|
||||
if (this.config.conditions && this.config.id !== 'default') {
|
||||
if (self.config.trigger === 'js') {
|
||||
description = 'when a custom JavaScript condition evaluates to true';
|
||||
} else {
|
||||
this.config.conditions.forEach(function (condition, index) {
|
||||
name = manager.getObjectName(condition.object);
|
||||
property = manager.getTelemetryPropertyName(condition.object, condition.key);
|
||||
operation = evaluator.getOperationDescription(condition.operation, condition.values);
|
||||
if (name || property || operation) {
|
||||
description += 'when ' +
|
||||
(name ? name + '\'s ' : '') +
|
||||
(property ? property + ' ' : '') +
|
||||
(operation ? operation + ' ' : '') +
|
||||
(self.config.trigger === 'any' ? ' OR ' : ' AND ');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (description.endsWith('OR ')) {
|
||||
description = description.substring(0, description.length - 3);
|
||||
}
|
||||
if (description.endsWith('AND ')) {
|
||||
description = description.substring(0, description.length - 4);
|
||||
}
|
||||
description = (description === '' ? this.config.description : description);
|
||||
this.description.html(description);
|
||||
this.config.description = description;
|
||||
this.updateDomainObject();
|
||||
};
|
||||
|
||||
return Rule;
|
||||
});
|
||||
307
src/plugins/summaryWidget/src/SummaryWidget.js
Normal file
307
src/plugins/summaryWidget/src/SummaryWidget.js
Normal file
@@ -0,0 +1,307 @@
|
||||
define([
|
||||
'text!../res/widgetTemplate.html',
|
||||
'./Rule',
|
||||
'./ConditionManager',
|
||||
'./TestDataManager',
|
||||
'./WidgetDnD',
|
||||
'lodash',
|
||||
'zepto'
|
||||
], function (
|
||||
widgetTemplate,
|
||||
Rule,
|
||||
ConditionManager,
|
||||
TestDataManager,
|
||||
WidgetDnD,
|
||||
_,
|
||||
$
|
||||
) {
|
||||
|
||||
//default css configuration for new rules
|
||||
var DEFAULT_PROPS = {
|
||||
'color': '#000000',
|
||||
'background-color': '#00ff00',
|
||||
'border-color': '#666666'
|
||||
};
|
||||
|
||||
/**
|
||||
* A Summary Widget object, which allows a user to configure rules based
|
||||
* on telemetry producing domain objects, and update a compact display
|
||||
* accordingly.
|
||||
* @constructor
|
||||
* @param {Object} domainObject The domain Object represented by this Widget
|
||||
* @param {MCT} openmct An MCT instance
|
||||
*/
|
||||
function SummaryWidget(domainObject, openmct) {
|
||||
this.domainObject = domainObject;
|
||||
this.openmct = openmct;
|
||||
|
||||
this.domainObject.configuration = this.domainObject.configuration || {};
|
||||
this.domainObject.configuration.ruleConfigById = this.domainObject.configuration.ruleConfigById || {};
|
||||
this.domainObject.configuration.ruleOrder = this.domainObject.configuration.ruleOrder || ['default'];
|
||||
this.domainObject.configuration.testDataConfig = this.domainObject.configuration.testDataConfig || [{
|
||||
object: '',
|
||||
key: '',
|
||||
value: ''
|
||||
}];
|
||||
|
||||
this.activeId = 'default';
|
||||
this.rulesById = {};
|
||||
this.domElement = $(widgetTemplate);
|
||||
this.editing = false;
|
||||
this.container = '';
|
||||
this.editListenerUnsubscribe = $.noop;
|
||||
|
||||
this.ruleArea = $('.t-widget-rules', this.domElement);
|
||||
this.testDataArea = $('.widget-test-data', this.domElement);
|
||||
this.addRuleButton = $('#addRule', this.domElement);
|
||||
|
||||
this.conditionManager = new ConditionManager(this.domainObject, this.openmct);
|
||||
this.testDataManager = new TestDataManager(this.domainObject, this.conditionManager, this.openmct);
|
||||
|
||||
this.show = this.show.bind(this);
|
||||
this.destroy = this.destroy.bind(this);
|
||||
this.addRule = this.addRule.bind(this);
|
||||
this.onEdit = this.onEdit.bind(this);
|
||||
|
||||
var id = this.domainObject.identifier.key,
|
||||
self = this,
|
||||
oldDomainObject,
|
||||
statusCapability;
|
||||
|
||||
openmct.$injector.get('objectService')
|
||||
.getObjects([id])
|
||||
.then(function (objs) {
|
||||
oldDomainObject = objs[id];
|
||||
statusCapability = oldDomainObject.getCapability('status');
|
||||
self.editListenerUnsubscribe = statusCapability.listen(self.onEdit);
|
||||
if (statusCapability.get('editing')) {
|
||||
self.onEdit(['editing']);
|
||||
} else {
|
||||
self.onEdit([]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the Summary Widget's DOM, performs other necessary setup, and attaches
|
||||
* this Summary Widget's view to the supplied container.
|
||||
* @param {element} container The DOM element that will contain this Summary
|
||||
* Widget's view.
|
||||
*/
|
||||
SummaryWidget.prototype.show = function (container) {
|
||||
var self = this;
|
||||
this.container = container;
|
||||
$(container).append(this.domElement);
|
||||
$('.widget-test-data', this.domElement).append(this.testDataManager.getDOM());
|
||||
this.widgetDnD = new WidgetDnD(this.domElement, this.domainObject.configuration.ruleOrder, this.rulesById);
|
||||
this.initRule('default', 'Default');
|
||||
this.domainObject.configuration.ruleOrder.forEach(function (ruleId) {
|
||||
self.initRule(ruleId);
|
||||
});
|
||||
this.refreshRules();
|
||||
this.updateWidget();
|
||||
this.updateView();
|
||||
|
||||
this.addRuleButton.on('click', this.addRule);
|
||||
this.conditionManager.on('receiveTelemetry', this.executeRules, this);
|
||||
this.widgetDnD.on('drop', this.reorder, this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Unregister event listeners with the Open MCT APIs, unsubscribe from telemetry,
|
||||
* and clean up event handlers
|
||||
*/
|
||||
SummaryWidget.prototype.destroy = function (container) {
|
||||
this.editListenerUnsubscribe();
|
||||
this.conditionManager.destroy();
|
||||
this.widgetDnD.destroy();
|
||||
Object.values(this.rulesById).forEach(function (rule) {
|
||||
rule.destroy();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* A callback function for the Open MCT status capability listener. If the
|
||||
* view representing the domain object is in edit mode, update the internal
|
||||
* state and widget view accordingly.
|
||||
* @param {string[]} status an array containing the domain object's current status
|
||||
*/
|
||||
SummaryWidget.prototype.onEdit = function (status) {
|
||||
if (status && status.includes('editing')) {
|
||||
this.editing = true;
|
||||
} else {
|
||||
this.editing = false;
|
||||
}
|
||||
this.updateView();
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the view from the current rule configuration and order
|
||||
*/
|
||||
SummaryWidget.prototype.refreshRules = function () {
|
||||
var self = this,
|
||||
ruleOrder = self.domainObject.configuration.ruleOrder,
|
||||
rules = self.rulesById;
|
||||
|
||||
self.ruleArea.html('');
|
||||
Object.values(ruleOrder).forEach(function (ruleId) {
|
||||
self.ruleArea.append(rules[ruleId].getDOM());
|
||||
});
|
||||
|
||||
this.executeRules();
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the widget's appearance from the configuration of the active rule
|
||||
*/
|
||||
SummaryWidget.prototype.updateWidget = function () {
|
||||
var activeRule = this.rulesById[this.activeId];
|
||||
this.applyStyle($('#widget', this.domElement), activeRule.getProperty('style'));
|
||||
$('#widget', this.domElement).prop('title', activeRule.getProperty('message'));
|
||||
$('#widgetLabel', this.domElement).html(activeRule.getProperty('label'));
|
||||
$('#widgetIcon', this.domElement).removeClass().addClass(activeRule.getProperty('icon'));
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the active rule and update the Widget's appearance.
|
||||
*/
|
||||
SummaryWidget.prototype.executeRules = function () {
|
||||
this.activeId = this.conditionManager.executeRules(
|
||||
this.domainObject.configuration.ruleOrder,
|
||||
this.rulesById
|
||||
);
|
||||
this.updateWidget();
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a new rule to this widget
|
||||
*/
|
||||
SummaryWidget.prototype.addRule = function () {
|
||||
var ruleCount = 0,
|
||||
ruleId,
|
||||
ruleOrder = this.domainObject.configuration.ruleOrder;
|
||||
|
||||
while (Object.keys(this.rulesById).includes('rule' + ruleCount)) {
|
||||
ruleCount = ++ruleCount;
|
||||
}
|
||||
|
||||
ruleId = 'rule' + ruleCount;
|
||||
ruleOrder.push(ruleId);
|
||||
this.domainObject.configuration.ruleOrder = ruleOrder;
|
||||
this.updateDomainObject();
|
||||
this.initRule(ruleId, 'Rule');
|
||||
this.refreshRules();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Duplicate an existing widget rule from its configuration and splice it in
|
||||
* after the rule it duplicates
|
||||
* @param {Object} sourceConfig The configuration properties of the rule to be
|
||||
* instantiated
|
||||
*/
|
||||
SummaryWidget.prototype.duplicateRule = function (sourceConfig) {
|
||||
var ruleCount = 0,
|
||||
ruleId,
|
||||
sourceRuleId = sourceConfig.id,
|
||||
ruleOrder = this.domainObject.configuration.ruleOrder,
|
||||
ruleIds = Object.keys(this.rulesById);
|
||||
|
||||
while (ruleIds.includes('rule' + ruleCount)) {
|
||||
ruleCount = ++ruleCount;
|
||||
}
|
||||
|
||||
ruleId = 'rule' + ruleCount;
|
||||
sourceConfig.id = ruleId;
|
||||
sourceConfig.name += ' Copy';
|
||||
ruleOrder.splice(ruleOrder.indexOf(sourceRuleId) + 1, 0, ruleId);
|
||||
this.domainObject.configuration.ruleOrder = ruleOrder;
|
||||
this.domainObject.configuration.ruleConfigById[ruleId] = sourceConfig;
|
||||
this.updateDomainObject();
|
||||
this.initRule(ruleId, sourceConfig.name);
|
||||
this.refreshRules();
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialze a new rule from a default configuration, or build a {Rule} object
|
||||
* from it if already exists
|
||||
* @param {string} ruleId An key to be used to identify this ruleId, or the key
|
||||
of the rule to be instantiated
|
||||
* @param {string} ruleName The initial human-readable name of this rule
|
||||
*/
|
||||
SummaryWidget.prototype.initRule = function (ruleId, ruleName) {
|
||||
var ruleConfig,
|
||||
styleObj = {};
|
||||
|
||||
Object.assign(styleObj, DEFAULT_PROPS);
|
||||
if (!this.domainObject.configuration.ruleConfigById[ruleId]) {
|
||||
this.domainObject.configuration.ruleConfigById[ruleId] = {
|
||||
name: ruleName || 'Rule',
|
||||
label: this.domainObject.name,
|
||||
message: '',
|
||||
id: ruleId,
|
||||
icon: 'icon-alert-rect',
|
||||
style: styleObj,
|
||||
description: ruleId === 'default' ? 'Default appearance for the widget' : 'A new rule',
|
||||
conditions: [{
|
||||
object: '',
|
||||
key: '',
|
||||
operation: '',
|
||||
values: []
|
||||
}],
|
||||
jsCondition: '',
|
||||
trigger: 'any',
|
||||
expanded: 'true'
|
||||
};
|
||||
|
||||
}
|
||||
ruleConfig = this.domainObject.configuration.ruleConfigById[ruleId];
|
||||
this.rulesById[ruleId] = new Rule(ruleConfig, this.domainObject, this.openmct,
|
||||
this.conditionManager, this.widgetDnD, this.container);
|
||||
this.rulesById[ruleId].on('remove', this.refreshRules, this);
|
||||
this.rulesById[ruleId].on('duplicate', this.duplicateRule, this);
|
||||
this.rulesById[ruleId].on('change', this.updateWidget, this);
|
||||
this.rulesById[ruleId].on('conditionChange', this.executeRules, this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Given two ruleIds, move the source rule after the target rule and update
|
||||
* the view.
|
||||
* @param {Object} event An event object representing this drop with draggingId
|
||||
* and dropTarget fields
|
||||
*/
|
||||
SummaryWidget.prototype.reorder = function (event) {
|
||||
var ruleOrder = this.domainObject.configuration.ruleOrder,
|
||||
sourceIndex = ruleOrder.indexOf(event.draggingId),
|
||||
targetIndex;
|
||||
|
||||
if (event.draggingId !== event.dropTarget) {
|
||||
ruleOrder.splice(sourceIndex, 1);
|
||||
targetIndex = ruleOrder.indexOf(event.dropTarget);
|
||||
ruleOrder.splice(targetIndex + 1, 0, event.draggingId);
|
||||
this.domainObject.configuration.ruleOrder = ruleOrder;
|
||||
this.updateDomainObject();
|
||||
}
|
||||
this.refreshRules();
|
||||
};
|
||||
|
||||
/**
|
||||
* Apply a list of css properties to an element
|
||||
* @param {element} elem The DOM element to which the rules will be applied
|
||||
* @param {object} style an object representing the style
|
||||
*/
|
||||
SummaryWidget.prototype.applyStyle = function (elem, style) {
|
||||
Object.keys(style).forEach(function (propId) {
|
||||
elem.css(propId, style[propId]);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Mutate this domain object's configuration with the current local configuration
|
||||
*/
|
||||
SummaryWidget.prototype.updateDomainObject = function () {
|
||||
this.openmct.objects.mutate(this.domainObject, 'configuration', this.domainObject.configuration);
|
||||
};
|
||||
|
||||
return SummaryWidget;
|
||||
});
|
||||
177
src/plugins/summaryWidget/src/TestDataItem.js
Normal file
177
src/plugins/summaryWidget/src/TestDataItem.js
Normal file
@@ -0,0 +1,177 @@
|
||||
define([
|
||||
'text!../res/testDataItemTemplate.html',
|
||||
'./input/ObjectSelect',
|
||||
'./input/KeySelect',
|
||||
'EventEmitter',
|
||||
'zepto'
|
||||
], function (
|
||||
itemTemplate,
|
||||
ObjectSelect,
|
||||
KeySelect,
|
||||
EventEmitter,
|
||||
$
|
||||
) {
|
||||
|
||||
/**
|
||||
* An object representing a single mock telemetry value
|
||||
* @param {object} itemConfig the configuration for this item, consisting of
|
||||
* object, key, and value fields
|
||||
* @param {number} index the index of this TestDataItem object in the data
|
||||
* model of its parent {TestDataManager} o be injected into callbacks
|
||||
* for removes
|
||||
* @param {ConditionManager} conditionManager a conditionManager instance
|
||||
* for populating selects with configuration data
|
||||
* @constructor
|
||||
*/
|
||||
function TestDataItem(itemConfig, index, conditionManager) {
|
||||
this.config = itemConfig;
|
||||
this.index = index;
|
||||
this.conditionManager = conditionManager;
|
||||
|
||||
this.domElement = $(itemTemplate);
|
||||
this.eventEmitter = new EventEmitter();
|
||||
this.supportedCallbacks = ['remove', 'duplicate', 'change'];
|
||||
|
||||
this.deleteButton = $('.t-delete', this.domElement);
|
||||
this.duplicateButton = $('.t-duplicate', this.domElement);
|
||||
|
||||
this.selects = {};
|
||||
this.valueInputs = [];
|
||||
|
||||
this.remove = this.remove.bind(this);
|
||||
this.duplicate = this.duplicate.bind(this);
|
||||
|
||||
var self = this;
|
||||
|
||||
/**
|
||||
* A change event handler for this item's select inputs, which also invokes
|
||||
* change callbacks registered with this item
|
||||
* @param {string} value The new value of this select item
|
||||
* @param {string} property The property of this item to modify
|
||||
* @private
|
||||
*/
|
||||
function onSelectChange(value, property) {
|
||||
if (property === 'key') {
|
||||
self.generateValueInput(value);
|
||||
}
|
||||
self.eventEmitter.emit('change', {
|
||||
value: value,
|
||||
property: property,
|
||||
index: self.index
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* An input event handler for this item's value field. Invokes any change
|
||||
* callbacks associated with this item
|
||||
* @param {Event} event The input event that initiated this callback
|
||||
* @private
|
||||
*/
|
||||
function onValueInput(event) {
|
||||
var elem = event.target,
|
||||
value = (isNaN(elem.valueAsNumber) ? elem.value : elem.valueAsNumber);
|
||||
|
||||
self.eventEmitter.emit('change', {
|
||||
value: value,
|
||||
property: 'value',
|
||||
index: self.index
|
||||
});
|
||||
}
|
||||
|
||||
this.deleteButton.on('click', this.remove);
|
||||
this.duplicateButton.on('click', this.duplicate);
|
||||
|
||||
this.selects.object = new ObjectSelect(this.config, this.conditionManager);
|
||||
this.selects.key = new KeySelect(
|
||||
this.config,
|
||||
this.selects.object,
|
||||
this.conditionManager,
|
||||
function (value) {
|
||||
onSelectChange(value, 'key');
|
||||
});
|
||||
|
||||
this.selects.object.on('change', function (value) {
|
||||
onSelectChange(value, 'object');
|
||||
});
|
||||
|
||||
Object.values(this.selects).forEach(function (select) {
|
||||
$('.t-configuration', self.domElement).append(select.getDOM());
|
||||
});
|
||||
|
||||
$(this.domElement).on('input', 'input', onValueInput);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the DOM associated with this element's view
|
||||
* @return {Element}
|
||||
*/
|
||||
TestDataItem.prototype.getDOM = function (container) {
|
||||
return this.domElement;
|
||||
};
|
||||
|
||||
/**
|
||||
* Register a callback with this item: supported callbacks are remove, change,
|
||||
* and duplicate
|
||||
* @param {string} event The key for the event to listen to
|
||||
* @param {function} callback The function that this rule will envoke on this event
|
||||
* @param {Object} context A reference to a scope to use as the context for
|
||||
* context for the callback function
|
||||
*/
|
||||
TestDataItem.prototype.on = function (event, callback, context) {
|
||||
if (this.supportedCallbacks.includes(event)) {
|
||||
this.eventEmitter.on(event, callback, context || this);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Hide the appropriate inputs when this is the only item
|
||||
*/
|
||||
TestDataItem.prototype.hideButtons = function () {
|
||||
this.deleteButton.hide();
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove this item from the configuration. Invokes any registered
|
||||
* remove callbacks
|
||||
*/
|
||||
TestDataItem.prototype.remove = function () {
|
||||
var self = this;
|
||||
this.eventEmitter.emit('remove', self.index);
|
||||
};
|
||||
|
||||
/**
|
||||
* Makes a deep clone of this item's configuration, and invokes any registered
|
||||
* duplicate callbacks with the cloned configuration as an argument
|
||||
*/
|
||||
TestDataItem.prototype.duplicate = function () {
|
||||
var sourceItem = JSON.parse(JSON.stringify(this.config)),
|
||||
self = this;
|
||||
this.eventEmitter.emit('duplicate', {
|
||||
sourceItem: sourceItem,
|
||||
index: self.index
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* When a telemetry property key is selected, create the appropriate value input
|
||||
* and add it to the view
|
||||
* @param {string} key The key of currently selected telemetry property
|
||||
*/
|
||||
TestDataItem.prototype.generateValueInput = function (key) {
|
||||
var evaluator = this.conditionManager.getEvaluator(),
|
||||
inputArea = $('.t-value-inputs', this.domElement),
|
||||
dataType = this.conditionManager.getTelemetryPropertyType(this.config.object, key),
|
||||
inputType = evaluator.getInputTypeById(dataType);
|
||||
|
||||
inputArea.html('');
|
||||
if (inputType) {
|
||||
if (!this.config.value) {
|
||||
this.config.value = (inputType === 'number' ? 0 : '');
|
||||
}
|
||||
this.valueInput = $('<input type = "' + inputType + '" value = "' + this.config.value + '"> </input>').get(0);
|
||||
inputArea.append(this.valueInput);
|
||||
}
|
||||
};
|
||||
|
||||
return TestDataItem;
|
||||
});
|
||||
201
src/plugins/summaryWidget/src/TestDataManager.js
Normal file
201
src/plugins/summaryWidget/src/TestDataManager.js
Normal file
@@ -0,0 +1,201 @@
|
||||
define([
|
||||
'text!../res/testDataTemplate.html',
|
||||
'./TestDataItem',
|
||||
'zepto',
|
||||
'lodash'
|
||||
], function (
|
||||
testDataTemplate,
|
||||
TestDataItem,
|
||||
$,
|
||||
_
|
||||
) {
|
||||
|
||||
/**
|
||||
* Controls the input and usage of test data in the summary widget.
|
||||
* @constructor
|
||||
* @param {Object} domainObject The summary widget domain object
|
||||
* @param {ConditionManager} conditionManager A conditionManager instance
|
||||
* @param {MCT} openmct and MCT instance
|
||||
*/
|
||||
function TestDataManager(domainObject, conditionManager, openmct) {
|
||||
var self = this;
|
||||
|
||||
this.domainObject = domainObject;
|
||||
this.manager = conditionManager;
|
||||
this.openmct = openmct;
|
||||
|
||||
this.evaluator = this.manager.getEvaluator();
|
||||
this.domElement = $(testDataTemplate);
|
||||
this.config = this.domainObject.configuration.testDataConfig;
|
||||
this.testCache = {};
|
||||
|
||||
this.configArea = $('.widget-test-data-content', this.domElement);
|
||||
this.itemArea = $('.t-test-data-config', this.domElement);
|
||||
this.toggleConfigButton = $('.view-control', this.domElement);
|
||||
this.addItemButton = $('.add-item', this.domElement);
|
||||
this.testDataInput = $('.t-test-data-checkbox', this.domElement);
|
||||
|
||||
/**
|
||||
* Toggles the configuration area for test data in the view
|
||||
* @private
|
||||
*/
|
||||
function toggleConfig() {
|
||||
self.configArea.toggleClass('expanded');
|
||||
self.toggleConfigButton.toggleClass('expanded');
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles whether the associated {ConditionEvaluator} uses the actual
|
||||
* subscription cache or the test data cache
|
||||
* @param {Event} event The change event that triggered this callback
|
||||
* @private
|
||||
*/
|
||||
function toggleTestData(event) {
|
||||
var elem = event.target;
|
||||
self.evaluator.useTestData(elem.checked);
|
||||
self.updateTestCache();
|
||||
}
|
||||
|
||||
this.toggleConfigButton.on('click', toggleConfig);
|
||||
this.addItemButton.on('click', function () {
|
||||
self.initItem();
|
||||
});
|
||||
this.testDataInput.on('change', toggleTestData);
|
||||
|
||||
this.evaluator.setTestDataCache(this.testCache);
|
||||
this.evaluator.useTestData(false);
|
||||
|
||||
this.refreshItems();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the DOM element representing this test data manager in the view
|
||||
*/
|
||||
TestDataManager.prototype.getDOM = function () {
|
||||
return this.domElement;
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialze a new test data item, either from a source configuration, or with
|
||||
* the default empty configuration
|
||||
* @param {Object} [config] An object with sourceItem and index fields to instantiate
|
||||
* this rule from, optional
|
||||
*/
|
||||
TestDataManager.prototype.initItem = function (config) {
|
||||
var sourceIndex = config && config.index,
|
||||
defaultItem = {
|
||||
object: '',
|
||||
key: '',
|
||||
value: ''
|
||||
},
|
||||
newItem;
|
||||
|
||||
newItem = (config !== undefined ? config.sourceItem : defaultItem);
|
||||
if (sourceIndex !== undefined) {
|
||||
this.config.splice(sourceIndex + 1, 0, newItem);
|
||||
} else {
|
||||
this.config.push(newItem);
|
||||
}
|
||||
this.updateDomainObject();
|
||||
this.refreshItems();
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove an item from this TestDataManager at the given index
|
||||
* @param {number} removeIndex The index of the item to remove
|
||||
*/
|
||||
TestDataManager.prototype.removeItem = function (removeIndex) {
|
||||
_.remove(this.config, function (item, index) {
|
||||
return index === removeIndex;
|
||||
});
|
||||
this.updateDomainObject();
|
||||
this.refreshItems();
|
||||
};
|
||||
|
||||
/**
|
||||
* Change event handler for the test data items which compose this
|
||||
* test data generateor
|
||||
* @param {Object} event An object representing this event, with value, property,
|
||||
* and index fields
|
||||
*/
|
||||
TestDataManager.prototype.onItemChange = function (event) {
|
||||
this.config[event.index][event.property] = event.value;
|
||||
this.updateDomainObject();
|
||||
this.updateTestCache();
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds the test cache from the current item configuration, and passes
|
||||
* the new test cache to the associated {ConditionEvaluator} instance
|
||||
*/
|
||||
TestDataManager.prototype.updateTestCache = function () {
|
||||
this.generateTestCache();
|
||||
this.evaluator.setTestDataCache(this.testCache);
|
||||
this.manager.triggerTelemetryCallback();
|
||||
};
|
||||
|
||||
/**
|
||||
* Intantiate {TestDataItem} objects from the current configuration, and
|
||||
* update the view accordingly
|
||||
*/
|
||||
TestDataManager.prototype.refreshItems = function () {
|
||||
var self = this;
|
||||
|
||||
self.items = [];
|
||||
$('.t-test-data-item', this.domElement).remove();
|
||||
|
||||
this.config.forEach(function (item, index) {
|
||||
var newItem = new TestDataItem(item, index, self.manager);
|
||||
newItem.on('remove', self.removeItem, self);
|
||||
newItem.on('duplicate', self.initItem, self);
|
||||
newItem.on('change', self.onItemChange, self);
|
||||
self.items.push(newItem);
|
||||
});
|
||||
|
||||
self.items.forEach(function (item) {
|
||||
$('li:last-of-type', self.itemArea).before(item.getDOM());
|
||||
});
|
||||
|
||||
if (self.items.length === 1) {
|
||||
self.items[0].hideButtons();
|
||||
}
|
||||
|
||||
this.updateTestCache();
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds a test data cache in the format of a telemetry subscription cache
|
||||
* as expected by a {ConditionEvaluator}
|
||||
*/
|
||||
TestDataManager.prototype.generateTestCache = function () {
|
||||
var testCache = this.testCache,
|
||||
manager = this.manager,
|
||||
compositionObjs = manager.getComposition(),
|
||||
metadata;
|
||||
|
||||
testCache = {};
|
||||
Object.keys(compositionObjs).forEach(function (id) {
|
||||
testCache[id] = {};
|
||||
metadata = manager.getTelemetryMetadata(id);
|
||||
Object.keys(metadata).forEach(function (key) {
|
||||
testCache[id][key] = '';
|
||||
});
|
||||
});
|
||||
this.config.forEach(function (item) {
|
||||
if (testCache[item.object]) {
|
||||
testCache[item.object][item.key] = item.value;
|
||||
}
|
||||
});
|
||||
|
||||
this.testCache = testCache;
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the domain object configuration associated with this test data manager
|
||||
*/
|
||||
TestDataManager.prototype.updateDomainObject = function () {
|
||||
this.openmct.objects.mutate(this.domainObject, 'configuration.testDataConfig', this.config);
|
||||
};
|
||||
|
||||
return TestDataManager;
|
||||
});
|
||||
167
src/plugins/summaryWidget/src/WidgetDnD.js
Normal file
167
src/plugins/summaryWidget/src/WidgetDnD.js
Normal file
@@ -0,0 +1,167 @@
|
||||
define([
|
||||
'text!../res/ruleImageTemplate.html',
|
||||
'EventEmitter',
|
||||
'zepto'
|
||||
], function (
|
||||
ruleImageTemplate,
|
||||
EventEmitter,
|
||||
$
|
||||
) {
|
||||
|
||||
/**
|
||||
* Manages the Sortable List interface for reordering rules by drag and drop
|
||||
* @param {Element} container The DOM element that contains this Summary Widget's view
|
||||
* @param {string[]} ruleOrder An array of rule IDs representing the current rule order
|
||||
* @param {Object} rulesById An object mapping rule IDs to rule configurations
|
||||
*/
|
||||
function WidgetDnD(container, ruleOrder, rulesById) {
|
||||
this.container = container;
|
||||
this.ruleOrder = ruleOrder;
|
||||
this.rulesById = rulesById;
|
||||
|
||||
this.imageContainer = $(ruleImageTemplate);
|
||||
this.image = $('.t-drag-rule-image', this.imageContainer);
|
||||
this.draggingId = '';
|
||||
this.draggingRulePrevious = '';
|
||||
this.eventEmitter = new EventEmitter();
|
||||
this.supportedCallbacks = ['drop'];
|
||||
|
||||
this.drag = this.drag.bind(this);
|
||||
this.drop = this.drop.bind(this);
|
||||
|
||||
$(this.container).on('mousemove', this.drag);
|
||||
$(document).on('mouseup', this.drop);
|
||||
$(this.container).before(this.imageContainer);
|
||||
$(this.imageContainer).hide();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove event listeners registered to elements external to the widget
|
||||
*/
|
||||
WidgetDnD.prototype.destroy = function () {
|
||||
$(this.container).off('mousemove', this.drag);
|
||||
$(document).off('mouseup', this.drop);
|
||||
};
|
||||
|
||||
/**
|
||||
* Register a callback with this WidgetDnD: supported callback is drop
|
||||
* @param {string} event The key for the event to listen to
|
||||
* @param {function} callback The function that this rule will envoke on this event
|
||||
* @param {Object} context A reference to a scope to use as the context for
|
||||
* context for the callback function
|
||||
*/
|
||||
WidgetDnD.prototype.on = function (event, callback, context) {
|
||||
if (this.supportedCallbacks.includes(event)) {
|
||||
this.eventEmitter.on(event, callback, context || this);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the image for the dragged element to the given DOM element
|
||||
* @param {Element} image The HTML element to set as the drap image
|
||||
*/
|
||||
WidgetDnD.prototype.setDragImage = function (image) {
|
||||
this.image.html(image);
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculate where this rule has been dragged relative to the other rules
|
||||
* @param {Event} event The mousemove or mouseup event that triggered this
|
||||
event handler
|
||||
* @return {string} The ID of the rule whose drag indicator should be displayed
|
||||
*/
|
||||
WidgetDnD.prototype.getDropLocation = function (event) {
|
||||
var ruleOrder = this.ruleOrder,
|
||||
rulesById = this.rulesById,
|
||||
draggingId = this.draggingId,
|
||||
offset,
|
||||
y,
|
||||
height,
|
||||
dropY = event.pageY,
|
||||
target = '';
|
||||
|
||||
ruleOrder.forEach(function (ruleId, index) {
|
||||
offset = rulesById[ruleId].getDOM().offset();
|
||||
y = offset.top;
|
||||
height = offset.height;
|
||||
if (index === 0) {
|
||||
if (dropY < y + 7 * height / 3) {
|
||||
target = ruleId;
|
||||
}
|
||||
} else if (index === ruleOrder.length - 1 && ruleId !== draggingId) {
|
||||
if (y + height / 3 < dropY) {
|
||||
target = ruleId;
|
||||
}
|
||||
} else {
|
||||
if (y + height / 3 < dropY && dropY < y + 7 * height / 3) {
|
||||
target = ruleId;
|
||||
}
|
||||
}
|
||||
});
|
||||
return target;
|
||||
};
|
||||
|
||||
/**
|
||||
* Called by a {Rule} instance that initiates a drag gesture
|
||||
* @param {string} ruleId The identifier of the rule which is being dragged
|
||||
*/
|
||||
WidgetDnD.prototype.dragStart = function (ruleId) {
|
||||
var ruleOrder = this.ruleOrder;
|
||||
this.draggingId = ruleId;
|
||||
this.draggingRulePrevious = ruleOrder[ruleOrder.indexOf(ruleId) - 1];
|
||||
this.rulesById[this.draggingRulePrevious].showDragIndicator();
|
||||
this.imageContainer.show();
|
||||
this.imageContainer.offset({
|
||||
top: event.pageY - this.image.height() / 2,
|
||||
left: event.pageX - $('.t-grippy', this.image).width()
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* An event handler for a mousemove event, once a rule has begun a drag gesture
|
||||
* @param {Event} event The mousemove event that triggered this callback
|
||||
*/
|
||||
WidgetDnD.prototype.drag = function (event) {
|
||||
var dragTarget;
|
||||
if (this.draggingId && this.draggingId !== '') {
|
||||
event.preventDefault();
|
||||
dragTarget = this.getDropLocation(event);
|
||||
this.imageContainer.offset({
|
||||
top: event.pageY - this.image.height() / 2,
|
||||
left: event.pageX - $('.t-grippy', this.image).width()
|
||||
});
|
||||
if (this.rulesById[dragTarget]) {
|
||||
this.rulesById[dragTarget].showDragIndicator();
|
||||
} else {
|
||||
this.rulesById[this.draggingRulePrevious].showDragIndicator();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles the mouseup event that corresponds to the user dropping the rule
|
||||
* in its final location. Invokes any registered drop callbacks with the dragged
|
||||
* rule's ID and the ID of the target rule that the dragged rule should be
|
||||
* inserted after
|
||||
* @param {Event} event The mouseup event that triggered this callback
|
||||
*/
|
||||
WidgetDnD.prototype.drop = function (event) {
|
||||
var dropTarget = this.getDropLocation(event),
|
||||
draggingId = this.draggingId;
|
||||
|
||||
if (this.draggingId && this.draggingId !== '') {
|
||||
if (!this.rulesById[dropTarget]) {
|
||||
dropTarget = this.draggingId;
|
||||
}
|
||||
this.eventEmitter.emit('drop', {
|
||||
draggingId: draggingId,
|
||||
dropTarget: dropTarget
|
||||
});
|
||||
this.draggingId = '';
|
||||
this.draggingRulePrevious = '';
|
||||
this.imageContainer.hide();
|
||||
}
|
||||
};
|
||||
|
||||
return WidgetDnD;
|
||||
});
|
||||
64
src/plugins/summaryWidget/src/input/ColorPalette.js
Normal file
64
src/plugins/summaryWidget/src/input/ColorPalette.js
Normal file
@@ -0,0 +1,64 @@
|
||||
define([
|
||||
'./Palette',
|
||||
'zepto'
|
||||
],
|
||||
function (
|
||||
Palette,
|
||||
$
|
||||
) {
|
||||
|
||||
//The colors that will be used to instantiate this palette if none are provided
|
||||
var DEFAULT_COLORS = [
|
||||
'#000000','#434343','#666666','#999999','#b7b7b7','#cccccc','#d9d9d9','#efefef','#f3f3f3','#ffffff',
|
||||
'#980000','#ff0000','#ff9900','#ffff00','#00ff00','#00ffff','#4a86e8','#0000ff','#9900ff','#ff00ff',
|
||||
'#e6b8af','#f4cccc','#fce5cd','#fff2cc','#d9ead3','#d0e0e3','#c9daf8','#cfe2f3','#d9d2e9','#ead1dc',
|
||||
'#dd7e6b','#dd7e6b','#f9cb9c','#ffe599','#b6d7a8','#a2c4c9','#a4c2f4','#9fc5e8','#b4a7d6','#d5a6bd',
|
||||
'#cc4125','#e06666','#f6b26b','#ffd966','#93c47d','#76a5af','#6d9eeb','#6fa8dc','#8e7cc3','#c27ba0',
|
||||
'#a61c00','#cc0000','#e69138','#f1c232','#6aa84f','#45818e','#3c78d8','#3d85c6','#674ea7','#a64d79',
|
||||
'#85200c','#990000','#b45f06','#bf9000','#38761d','#134f5c','#1155cc','#0b5394','#351c75','#741b47',
|
||||
'#5b0f00','#660000','#783f04','#7f6000','#274e13','#0c343d','#1c4587','#073763','#20124d','#4c1130'
|
||||
];
|
||||
|
||||
/**
|
||||
* Instantiates a new Open MCT Color Palette input
|
||||
* @constructor
|
||||
* @param {string} cssClass The class name of the icon which should be applied
|
||||
* to this palette
|
||||
* @param {Element} container The view that contains this palette
|
||||
* @param {string[]} colors (optional) A list of colors that should be used to instantiate this palette
|
||||
*/
|
||||
function ColorPalette(cssClass, container, colors) {
|
||||
this.colors = colors || DEFAULT_COLORS;
|
||||
this.palette = new Palette(cssClass, container, this.colors);
|
||||
|
||||
this.palette.setNullOption('rgba(0,0,0,0)');
|
||||
|
||||
var domElement = $(this.palette.getDOM()),
|
||||
self = this;
|
||||
|
||||
$('.s-menu-button', domElement).addClass('t-color-palette-menu-button');
|
||||
$('.t-swatch', domElement).addClass('color-swatch');
|
||||
$('.l-palette', domElement).addClass('l-color-palette');
|
||||
|
||||
$('.s-palette-item', domElement).each(function () {
|
||||
var elem = this;
|
||||
$(elem).css('background-color', elem.dataset.item);
|
||||
});
|
||||
|
||||
/**
|
||||
* Update this palette's current selection indicator with the style
|
||||
* of the currently selected item
|
||||
* @private
|
||||
*/
|
||||
function updateSwatch() {
|
||||
var color = self.palette.getCurrent();
|
||||
$('.color-swatch', domElement).css('background-color', color);
|
||||
}
|
||||
|
||||
this.palette.on('change', updateSwatch);
|
||||
|
||||
return this.palette;
|
||||
}
|
||||
|
||||
return ColorPalette;
|
||||
});
|
||||
80
src/plugins/summaryWidget/src/input/IconPalette.js
Normal file
80
src/plugins/summaryWidget/src/input/IconPalette.js
Normal file
@@ -0,0 +1,80 @@
|
||||
define([
|
||||
'./Palette',
|
||||
'zepto'
|
||||
], function (
|
||||
Palette,
|
||||
$
|
||||
) {
|
||||
//The icons that will be used to instantiate this palette if none are provided
|
||||
var DEFAULT_ICONS = [
|
||||
'icon-alert-rect',
|
||||
'icon-alert-triangle',
|
||||
'icon-arrow-down',
|
||||
'icon-arrow-left',
|
||||
'icon-arrow-right',
|
||||
'icon-arrow-double-up',
|
||||
'icon-arrow-tall-up',
|
||||
'icon-arrow-tall-down',
|
||||
'icon-arrow-double-down',
|
||||
'icon-arrow-up',
|
||||
'icon-asterisk',
|
||||
'icon-bell',
|
||||
'icon-check',
|
||||
'icon-eye-open',
|
||||
'icon-gear',
|
||||
'icon-hourglass',
|
||||
'icon-info',
|
||||
'icon-link',
|
||||
'icon-lock',
|
||||
'icon-people',
|
||||
'icon-person',
|
||||
'icon-plus',
|
||||
'icon-trash',
|
||||
'icon-x'
|
||||
];
|
||||
|
||||
/**
|
||||
* Instantiates a new Open MCT Icon Palette input
|
||||
* @constructor
|
||||
* @param {string} cssClass The class name of the icon which should be applied
|
||||
* to this palette
|
||||
* @param {Element} container The view that contains this palette
|
||||
* @param {string[]} icons (optional) A list of icons that should be used to instantiate this palette
|
||||
*/
|
||||
function IconPalette(cssClass, container, icons) {
|
||||
this.icons = icons || DEFAULT_ICONS;
|
||||
this.palette = new Palette(cssClass, container, this.icons);
|
||||
|
||||
this.palette.setNullOption(' ');
|
||||
this.oldIcon = this.palette.current || ' ';
|
||||
|
||||
var domElement = $(this.palette.getDOM()),
|
||||
self = this;
|
||||
|
||||
$('.s-menu-button', domElement).addClass('t-icon-palette-menu-button');
|
||||
$('.t-swatch', domElement).addClass('icon-swatch');
|
||||
$('.l-palette', domElement).addClass('l-icon-palette');
|
||||
|
||||
$('.s-palette-item', domElement).each(function () {
|
||||
var elem = this;
|
||||
$(elem).addClass(elem.dataset.item);
|
||||
});
|
||||
|
||||
/**
|
||||
* Update this palette's current selection indicator with the style
|
||||
* of the currently selected item
|
||||
* @private
|
||||
*/
|
||||
function updateSwatch() {
|
||||
$('.icon-swatch', domElement).removeClass(self.oldIcon)
|
||||
.addClass(self.palette.getCurrent());
|
||||
self.oldIcon = self.palette.getCurrent();
|
||||
}
|
||||
|
||||
this.palette.on('change', updateSwatch);
|
||||
|
||||
return this.palette;
|
||||
}
|
||||
|
||||
return IconPalette;
|
||||
});
|
||||
81
src/plugins/summaryWidget/src/input/KeySelect.js
Normal file
81
src/plugins/summaryWidget/src/input/KeySelect.js
Normal file
@@ -0,0 +1,81 @@
|
||||
define(['./Select'], function (Select) {
|
||||
|
||||
/**
|
||||
* Create a {Select} element whose composition is dynamically updated with
|
||||
* the telemetry fields of a particular domain object
|
||||
* @constructor
|
||||
* @param {Object} config The current state of this select. Must have object
|
||||
* and key fields
|
||||
* @param {ObjectSelect} objectSelect The linked ObjectSelect instance to which
|
||||
* this KeySelect should listen to for change
|
||||
* events
|
||||
* @param {ConditionManager} manager A ConditionManager instance from which
|
||||
* to receive telemetry metadata
|
||||
* @param {function} changeCallback A change event callback to register with this
|
||||
* select on initialization
|
||||
*/
|
||||
function KeySelect(config, objectSelect, manager, changeCallback) {
|
||||
var self = this;
|
||||
|
||||
this.config = config;
|
||||
this.objectSelect = objectSelect;
|
||||
this.manager = manager;
|
||||
|
||||
this.select = new Select();
|
||||
this.select.addOption('', '--Key--');
|
||||
if (changeCallback) {
|
||||
this.select.on('change', changeCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change event handler for the {ObjectSelect} to which this KeySelect instance
|
||||
* is linked. Loads the new object's metadata and updates its select element's
|
||||
* composition.
|
||||
* @param {Object} key The key identifying the newly selected domain object
|
||||
* @private
|
||||
*/
|
||||
function onObjectChange(key) {
|
||||
var selected = self.manager.metadataLoadCompleted() ? self.select.getSelected() : self.config.key;
|
||||
self.telemetryMetadata = self.manager.getTelemetryMetadata(key) || {};
|
||||
self.generateOptions();
|
||||
self.select.setSelected(selected);
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler for the intial metadata load event from the associated
|
||||
* ConditionManager. Retreives metadata from the manager and populates
|
||||
* the select element.
|
||||
* @private
|
||||
*/
|
||||
function onMetadataLoad() {
|
||||
if (self.manager.getTelemetryMetadata(self.config.object)) {
|
||||
self.telemetryMetadata = self.manager.getTelemetryMetadata(self.config.object);
|
||||
self.generateOptions();
|
||||
}
|
||||
self.select.setSelected(self.config.key);
|
||||
}
|
||||
|
||||
if (self.manager.metadataLoadCompleted()) {
|
||||
onMetadataLoad();
|
||||
}
|
||||
|
||||
this.objectSelect.on('change', onObjectChange);
|
||||
this.manager.on('metadata', onMetadataLoad);
|
||||
|
||||
return this.select;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate this select with options based on its current composition
|
||||
*/
|
||||
KeySelect.prototype.generateOptions = function () {
|
||||
var items = Object.entries(this.telemetryMetadata).map(function (metaDatum) {
|
||||
return [metaDatum[0], metaDatum[1].name];
|
||||
});
|
||||
items.splice(0, 0, ['','--Key--']);
|
||||
this.select.setOptions(items);
|
||||
};
|
||||
|
||||
return KeySelect;
|
||||
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user