Compare commits

..

1 Commits

Author SHA1 Message Date
Joel McKinnon
44a8135772 fixed persistance issue but not performant 2020-03-19 15:58:36 -07:00
198 changed files with 3364 additions and 9041 deletions

View File

@@ -8,12 +8,9 @@ module.exports = {
"globals": {
"_": "readonly"
},
"plugins": ["lodash"],
"extends": [
"eslint:recommended",
"plugin:vue/recommended",
"plugin:lodash/recommended",
"plugin:you-dont-need-lodash-underscore/compatible"
"plugin:vue/recommended"
],
"parser": "vue-eslint-parser",
"parserOptions": {
@@ -25,17 +22,6 @@ module.exports = {
}
},
"rules": {
"you-dont-need-lodash-underscore/omit": "off",
"lodash/prefer-lodash-method": "off",
"lodash/prefer-lodash-typecheck": "off",
"lodash/prefer-constant": "off",
"lodash/prefer-noop": "off",
"lodash/prefer-matches": "off",
"lodash/prefer-includes": "off",
"lodash/prefer-startswith": "off",
"lodash/prefer-get": "off",
"lodash/prefer-is-nil": "off",
"lodash/import-scope": "off",
"no-bitwise": "error",
"curly": "error",
"eqeqeq": "error",

View File

@@ -1,5 +1,5 @@
<!--
Open MCT, Copyright (c) 2014-2020, United States Government
Open MCT, Copyright (c) 2014-2018, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
@@ -18,4 +18,4 @@
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.
-->
-->

View File

@@ -1,5 +1,5 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, United States Government
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
@@ -18,4 +18,4 @@
* 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.
*****************************************************************************/
*****************************************************************************/

View File

@@ -0,0 +1,80 @@
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for All files</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="prettify.css" />
<link rel="stylesheet" href="base.css" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1>
/
</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Statements</span>
<span class='fraction'>0/0</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Branches</span>
<span class='fraction'>0/0</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Functions</span>
<span class='fraction'>0/0</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Lines</span>
<span class='fraction'>0/0</span>
</div>
</div>
</div>
<div class='status-line high'></div>
<div class="pad1">
<table class="coverage-summary">
<thead>
<tr>
<th data-col="file" data-fmt="html" data-html="true" class="file">File</th>
<th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>
<th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>
<th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>
<th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>
<th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>
<th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>
<th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>
<th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>
<th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>
</tr>
</thead>
<tbody></tbody>
</table>
</div><div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage
generated by <a href="http://istanbul-js.org/" target="_blank">istanbul</a> at Wed Dec 11 2019 13:15:10 GMT-0800 (Pacific Standard Time)
</div>
</div>
<script src="prettify.js"></script>
<script>
window.onload = function () {
if (typeof prettyPrint === 'function') {
prettyPrint();
}
};
</script>
<script src="sorter.js"></script>
</body>
</html>

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 B

View File

@@ -0,0 +1,158 @@
var addSorting = (function () {
"use strict";
var cols,
currentSort = {
index: 0,
desc: false
};
// returns the summary table element
function getTable() { return document.querySelector('.coverage-summary'); }
// returns the thead element of the summary table
function getTableHeader() { return getTable().querySelector('thead tr'); }
// returns the tbody element of the summary table
function getTableBody() { return getTable().querySelector('tbody'); }
// returns the th element for nth column
function getNthColumn(n) { return getTableHeader().querySelectorAll('th')[n]; }
// loads all columns
function loadColumns() {
var colNodes = getTableHeader().querySelectorAll('th'),
colNode,
cols = [],
col,
i;
for (i = 0; i < colNodes.length; i += 1) {
colNode = colNodes[i];
col = {
key: colNode.getAttribute('data-col'),
sortable: !colNode.getAttribute('data-nosort'),
type: colNode.getAttribute('data-type') || 'string'
};
cols.push(col);
if (col.sortable) {
col.defaultDescSort = col.type === 'number';
colNode.innerHTML = colNode.innerHTML + '<span class="sorter"></span>';
}
}
return cols;
}
// attaches a data attribute to every tr element with an object
// of data values keyed by column name
function loadRowData(tableRow) {
var tableCols = tableRow.querySelectorAll('td'),
colNode,
col,
data = {},
i,
val;
for (i = 0; i < tableCols.length; i += 1) {
colNode = tableCols[i];
col = cols[i];
val = colNode.getAttribute('data-value');
if (col.type === 'number') {
val = Number(val);
}
data[col.key] = val;
}
return data;
}
// loads all row data
function loadData() {
var rows = getTableBody().querySelectorAll('tr'),
i;
for (i = 0; i < rows.length; i += 1) {
rows[i].data = loadRowData(rows[i]);
}
}
// sorts the table using the data for the ith column
function sortByIndex(index, desc) {
var key = cols[index].key,
sorter = function (a, b) {
a = a.data[key];
b = b.data[key];
return a < b ? -1 : a > b ? 1 : 0;
},
finalSorter = sorter,
tableBody = document.querySelector('.coverage-summary tbody'),
rowNodes = tableBody.querySelectorAll('tr'),
rows = [],
i;
if (desc) {
finalSorter = function (a, b) {
return -1 * sorter(a, b);
};
}
for (i = 0; i < rowNodes.length; i += 1) {
rows.push(rowNodes[i]);
tableBody.removeChild(rowNodes[i]);
}
rows.sort(finalSorter);
for (i = 0; i < rows.length; i += 1) {
tableBody.appendChild(rows[i]);
}
}
// removes sort indicators for current column being sorted
function removeSortIndicators() {
var col = getNthColumn(currentSort.index),
cls = col.className;
cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, '');
col.className = cls;
}
// adds sort indicators for current column being sorted
function addSortIndicators() {
getNthColumn(currentSort.index).className += currentSort.desc ? ' sorted-desc' : ' sorted';
}
// adds event listeners for all sorter widgets
function enableUI() {
var i,
el,
ithSorter = function ithSorter(i) {
var col = cols[i];
return function () {
var desc = col.defaultDescSort;
if (currentSort.index === i) {
desc = !currentSort.desc;
}
sortByIndex(i, desc);
removeSortIndicators();
currentSort.index = i;
currentSort.desc = desc;
addSortIndicators();
};
};
for (i =0 ; i < cols.length; i += 1) {
if (cols[i].sortable) {
// add the click event handler on the th so users
// dont have to click on those tiny arrows
el = getNthColumn(i).querySelector('.sorter').parentElement;
if (el.addEventListener) {
el.addEventListener('click', ithSorter(i));
} else {
el.attachEvent('onclick', ithSorter(i));
}
}
}
}
// adds sorting functionality to the UI
return function () {
if (!getTable()) {
return;
}
cols = loadColumns();
loadData(cols);
addSortIndicators();
enableUI();
};
})();
window.addEventListener('load', addSorting);

View File

@@ -9,8 +9,7 @@ define([
values: [
{
key: "name",
name: "Name",
format: "string"
name: "Name"
},
{
key: "utc",

View File

@@ -31,7 +31,6 @@ define([
period: 10,
offset: 0,
dataRateInHz: 1,
randomness: 0,
phase: 0
};
@@ -53,8 +52,7 @@ define([
'period',
'offset',
'dataRateInHz',
'phase',
'randomness'
'phase'
];
request = request || {};

View File

@@ -65,8 +65,8 @@
name: data.name,
utc: nextStep,
yesterday: nextStep - 60*60*24*1000,
sin: sin(nextStep, data.period, data.amplitude, data.offset, data.phase, data.randomness),
cos: cos(nextStep, data.period, data.amplitude, data.offset, data.phase, data.randomness)
sin: sin(nextStep, data.period, data.amplitude, data.offset, data.phase),
cos: cos(nextStep, data.period, data.amplitude, data.offset, data.phase)
}
});
nextStep += step;
@@ -99,7 +99,6 @@
var offset = request.offset;
var dataRateInHz = request.dataRateInHz;
var phase = request.phase;
var randomness = request.randomness;
var step = 1000 / dataRateInHz;
var nextStep = start - (start % step) + step;
@@ -111,8 +110,8 @@
name: request.name,
utc: nextStep,
yesterday: nextStep - 60*60*24*1000,
sin: sin(nextStep, period, amplitude, offset, phase, randomness),
cos: cos(nextStep, period, amplitude, offset, phase, randomness)
sin: sin(nextStep, period, amplitude, offset, phase),
cos: cos(nextStep, period, amplitude, offset, phase)
});
}
self.postMessage({
@@ -121,14 +120,14 @@
});
}
function cos(timestamp, period, amplitude, offset, phase, randomness) {
function cos(timestamp, period, amplitude, offset, phase) {
return amplitude *
Math.cos(phase + (timestamp / period / 1000 * Math.PI * 2)) + (amplitude * Math.random() * randomness) + offset;
Math.cos(phase + (timestamp / period / 1000 * Math.PI * 2)) + offset;
}
function sin(timestamp, period, amplitude, offset, phase, randomness) {
function sin(timestamp, period, amplitude, offset, phase) {
return amplitude *
Math.sin(phase + (timestamp / period / 1000 * Math.PI * 2)) + (amplitude * Math.random() * randomness) + offset;
Math.sin(phase + (timestamp / period / 1000 * Math.PI * 2)) + offset;
}
function sendError(error, message) {

View File

@@ -122,17 +122,6 @@ define([
"telemetry",
"phase"
]
},
{
name: "Randomness",
control: "numberfield",
cssClass: "l-input-sm l-numeric",
key: "randomness",
required: true,
property: [
"telemetry",
"randomness"
]
}
],
initialize: function (object) {
@@ -141,8 +130,7 @@ define([
amplitude: 1,
offset: 0,
dataRateInHz: 1,
phase: 0,
randomness: 0
phase: 0
};
}
});

View File

@@ -1,5 +1,5 @@
<!--
Open MCT, Copyright (c) 2014-2020, United States Government
Open MCT, Copyright (c) 2014-2017, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
@@ -43,9 +43,9 @@
openmct.legacyRegistry.enable.bind(openmct.legacyRegistry)
);
openmct.install(openmct.plugins.LocalStorage());
openmct.install(openmct.plugins.Espresso());
openmct.install(openmct.plugins.Snow());
openmct.install(openmct.plugins.MyItems());
openmct.install(openmct.plugins.LocalStorage());
openmct.install(openmct.plugins.Generator());
openmct.install(openmct.plugins.ExampleImagery());
openmct.install(openmct.plugins.UTCTimeSystem());

View File

@@ -4,7 +4,8 @@
"description": "The Open MCT core platform",
"dependencies": {},
"devDependencies": {
"angular": "1.7.9",
"acorn": "6.2.0",
"angular": "1.4.14",
"angular-route": "1.4.14",
"babel-eslint": "8.2.6",
"comma-separated-values": "^3.6.4",
@@ -23,9 +24,7 @@
"d3-time": "1.0.x",
"d3-time-format": "2.1.x",
"eslint": "5.2.0",
"eslint-plugin-lodash": "^6.0.0",
"eslint-plugin-vue": "^6.0.0",
"eslint-plugin-you-dont-need-lodash-underscore": "^6.10.0",
"eventemitter3": "^1.2.0",
"exports-loader": "^0.7.0",
"express": "^4.13.1",
@@ -49,7 +48,7 @@
"karma-sourcemap-loader": "^0.3.7",
"karma-webpack": "^3.0.0",
"location-bar": "^3.0.1",
"lodash": "^4.17.12",
"lodash": "^3.10.1",
"markdown-toc": "^0.11.7",
"marked": "^0.3.5",
"mini-css-extract-plugin": "^0.4.1",

View File

@@ -87,11 +87,6 @@ define([
bootstrapper
);
// Override of angular1.6 ! hashPrefix
app.config(['$locationProvider', function ($locationProvider) {
$locationProvider.hashPrefix('');
}]);
// Apply logging levels; this must be done now, before the
// first log statement.
new LogLevel(logLevel).configure(app, $log);

View File

@@ -20,7 +20,7 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([], function (_) {
define(['lodash'], function (_) {
/**
* The ExportAsJSONAction is available from context menus and allows a user
@@ -121,8 +121,8 @@ define([], function (_) {
*/
ExportAsJSONAction.prototype.rewriteLink = function (child, parent) {
this.externalIdentifiers.push(this.getId(child));
var index = parent.composition.findIndex(function (id) {
return JSON.stringify(child.identifier) === JSON.stringify(id);
var index = _.findIndex(parent.composition, function (id) {
return _.isEqual(child.identifier, id);
});
var copyOfChild = this.copyObject(child);
copyOfChild.identifier.key = this.identifierService.generate();

View File

@@ -33,7 +33,7 @@ define(
var CONNECTED = {
text: "Connected",
glyphClass: "ok",
statusClass: "s-status-on",
statusClass: "s-status-ok",
description: "Connected to the domain object database."
},
DISCONNECTED = {

View File

@@ -71,7 +71,7 @@ define([
},
{
"key": "ELASTIC_PATH",
"value": "mct/_doc",
"value": "mct/domain_object",
"priority": "fallback"
},
{

View File

@@ -32,7 +32,7 @@ define(
var CONNECTED = {
text: "Connected",
glyphClass: "ok",
statusClass: "s-status-on",
statusClass: "s-status-ok",
description: "Connected to the domain object database."
},
DISCONNECTED = {

View File

@@ -32,9 +32,9 @@ define(
// JSLint doesn't like underscore-prefixed properties,
// so hide them here.
var SRC = "_source",
CONFLICT = 409,
SEQ_NO = "_seq_no",
PRIMARY_TERM = "_primary_term";
REV = "_version",
ID = "_id",
CONFLICT = 409;
/**
* The ElasticPersistenceProvider reads and writes JSON documents
@@ -104,8 +104,7 @@ define(
// Get a domain object model out of ElasticSearch's response
ElasticPersistenceProvider.prototype.getModel = function (response) {
if (response && response[SRC]) {
this.revs[response[SEQ_NO]] = response[SEQ_NO];
this.revs[response[PRIMARY_TERM]] = response[PRIMARY_TERM];
this.revs[response[ID]] = response[REV];
return response[SRC];
} else {
return undefined;
@@ -117,8 +116,7 @@ define(
// indicate that the request failed.
ElasticPersistenceProvider.prototype.checkResponse = function (response, key) {
if (response && !response.error) {
this.revs[SEQ_NO] = response[SEQ_NO];
this.revs[PRIMARY_TERM] = response[PRIMARY_TERM];
this.revs[key] = response[REV];
return response;
} else {
return this.handleError(response, key);
@@ -149,7 +147,7 @@ define(
function checkUpdate(response) {
return self.checkResponse(response, key);
}
return this.put(key, value)
return this.put(key, value, { version: this.revs[key] })
.then(checkUpdate);
};

View File

@@ -85,7 +85,7 @@ define(
it("allows object creation", function () {
var model = { someKey: "some value" };
mockHttp.and.returnValue(mockPromise({
data: { "_id": "abc", "_seq_no": 1, "_primary_term": 1 }
data: { "_id": "abc", "_version": 1 }
}));
provider.createObject("testSpace", "abc", model).then(capture);
expect(mockHttp).toHaveBeenCalledWith({
@@ -100,7 +100,7 @@ define(
it("allows object models to be read back", function () {
var model = { someKey: "some value" };
mockHttp.and.returnValue(mockPromise({
data: { "_id": "abc", "_seq_no": 1, "_primary_term": 1, "_source": model }
data: { "_id": "abc", "_version": 1, "_source": model }
}));
provider.readObject("testSpace", "abc").then(capture);
expect(mockHttp).toHaveBeenCalledWith({
@@ -117,19 +117,19 @@ define(
// First do a read to populate rev tags...
mockHttp.and.returnValue(mockPromise({
data: { "_id": "abc", "_source": {} }
data: { "_id": "abc", "_version": 42, "_source": {} }
}));
provider.readObject("testSpace", "abc");
// Now perform an update
mockHttp.and.returnValue(mockPromise({
data: { "_id": "abc", "_seq_no": 1, "_source": {} }
data: { "_id": "abc", "_version": 43, "_source": {} }
}));
provider.updateObject("testSpace", "abc", model).then(capture);
expect(mockHttp).toHaveBeenCalledWith({
url: "/test/db/abc",
method: "PUT",
params: undefined,
params: { version: 42 },
data: model
});
expect(capture.calls.mostRecent().args[0]).toBeTruthy();
@@ -138,13 +138,13 @@ define(
it("allows object deletion", function () {
// First do a read to populate rev tags...
mockHttp.and.returnValue(mockPromise({
data: { "_id": "abc", "_source": {} }
data: { "_id": "abc", "_version": 42, "_source": {} }
}));
provider.readObject("testSpace", "abc");
// Now perform an update
mockHttp.and.returnValue(mockPromise({
data: { "_id": "abc", "_source": {} }
data: { "_id": "abc", "_version": 42, "_source": {} }
}));
provider.deleteObject("testSpace", "abc", {}).then(capture);
expect(mockHttp).toHaveBeenCalledWith({
@@ -167,13 +167,13 @@ define(
expect(capture).toHaveBeenCalledWith(undefined);
});
it("handles rejection due to _seq_no", function () {
it("handles rejection due to version", function () {
var model = { someKey: "some value" },
mockErrorCallback = jasmine.createSpy('error');
// First do a read to populate rev tags...
mockHttp.and.returnValue(mockPromise({
data: { "_id": "abc", "_seq_no": 1, "_source": {} }
data: { "_id": "abc", "_version": 42, "_source": {} }
}));
provider.readObject("testSpace", "abc");
@@ -196,7 +196,7 @@ define(
// First do a read to populate rev tags...
mockHttp.and.returnValue(mockPromise({
data: { "_id": "abc", "_seq_no": 1, "_source": {} }
data: { "_id": "abc", "_version": 42, "_source": {} }
}));
provider.readObject("testSpace", "abc");

View File

@@ -25,7 +25,8 @@
* Module defining GenericSearchProvider. Created by shale on 07/16/2015.
*/
define([
'../../../../src/api/objects/object-utils'
'../../../../src/api/objects/object-utils',
'lodash'
], function (
objectUtils,
_
@@ -190,7 +191,9 @@ define([
}
var domainObject = objectUtils.toNewFormat(model, id);
var composition = this.openmct.composition.registry.find(p => p.appliesTo(domainObject));
var composition = _.find(this.openmct.composition.registry, function (p) {
return p.appliesTo(domainObject);
});
if (!composition) {
return;

View File

@@ -25,7 +25,8 @@
*/
define(
[
'../../../src/api/objects/object-utils'
'../../../src/api/objects/object-utils',
'lodash'
],
function (
objectUtils,
@@ -234,8 +235,7 @@ define(
var defaultRange = metadata.valuesForHints(['range'])[0];
defaultRange = defaultRange ? defaultRange.key : undefined;
const keyBy = (array, k) => (array || []).reduce((r, x) => ({ ...r, [k ? x[k] : x]: x }), {});
var sourceMap = keyBy(metadata.values(), 'key');
var sourceMap = _.indexBy(metadata.values(), 'key');
var isLegacyProvider = telemetryAPI.findRequestProvider(domainObject) ===
telemetryAPI.legacyProvider;
@@ -300,8 +300,7 @@ define(
var defaultRange = metadata.valuesForHints(['range'])[0];
defaultRange = defaultRange ? defaultRange.key : undefined;
const keyBy = (array, k) => (array || []).reduce((r, x) => ({ ...r, [k ? x[k] : x]: x }), {});
var sourceMap = keyBy(metadata.values(), 'key');
var sourceMap = _.indexBy(metadata.values(), 'key');
var isLegacyProvider = telemetryAPI.findSubscriptionProvider(domainObject) ===
telemetryAPI.legacyProvider;

View File

@@ -1,5 +1,5 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, United States Government
* Open MCT, Copyright (c) 2014-2019, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
@@ -265,7 +265,6 @@ define([
this.install(this.plugins.ImportExport());
this.install(this.plugins.WebPage());
this.install(this.plugins.Condition());
this.install(this.plugins.ConditionWidget());
}
MCT.prototype = Object.create(EventEmitter.prototype);

View File

@@ -196,8 +196,8 @@ define([
* @private
*/
DefaultCompositionProvider.prototype.includes = function (parent, childId) {
return parent.composition.some(composee =>
this.publicAPI.objects.areIdsEqual(composee, childId));
return parent.composition.findIndex(composee =>
this.publicAPI.objects.areIdsEqual(composee, childId)) !== -1;
};
DefaultCompositionProvider.prototype.reorder = function (domainObject, oldIndex, newIndex) {

View File

@@ -85,9 +85,9 @@ define([
value: +e.value
};
}), 'e.value');
valueMetadata.values = Object.entries(valueMetadata.enumerations).reduce((a, [key, {value}]) => {a [key] = value; return a}, []);
valueMetadata.max = Math.max(...valueMetadata.values);
valueMetadata.min = Math.min(...valueMetadata.values);
valueMetadata.values = _.pluck(valueMetadata.enumerations, 'value');
valueMetadata.max = _.max(valueMetadata.values);
valueMetadata.min = _.min(valueMetadata.values);
}
valueMetadatas.push(valueMetadata);

View File

@@ -370,7 +370,7 @@ define([
TelemetryAPI.prototype.commonValuesForHints = function (metadatas, hints) {
var options = metadatas.map(function (metadata) {
var values = metadata.valuesForHints(hints);
return _.keyBy(values, 'key');
return _.indexBy(values, 'key');
}).reduce(function (a, b) {
var results = {};
Object.keys(a).forEach(function (key) {
@@ -383,7 +383,7 @@ define([
var sortKeys = hints.map(function (h) {
return 'hints.' + h;
});
return _.sortBy(options, sortKeys);
return _.sortByAll(options, sortKeys);
};
/**

View File

@@ -57,13 +57,13 @@ define([
if (valueMetadata.format === 'enum') {
if (!valueMetadata.values) {
valueMetadata.values = Object.entries(valueMetadata.enumerations).reduce((a, [key, {value}]) => {a [key] = value; return a}, []);
valueMetadata.values = _.pluck(valueMetadata.enumerations, 'value');
}
if (!valueMetadata.hasOwnProperty('max')) {
valueMetadata.max = Math.max(...valueMetadata.values) + 1;
valueMetadata.max = _.max(valueMetadata.values) + 1;
}
if (!valueMetadata.hasOwnProperty('min')) {
valueMetadata.min = Math.min(...valueMetadata.values) - 1;
valueMetadata.min = _.min(valueMetadata.values) - 1;
}
}
@@ -81,7 +81,7 @@ define([
function TelemetryMetadataManager(metadata) {
this.metadata = metadata;
this.valueMetadatas = this.metadata.values ? this.metadata.values.map(applyReasonableDefaults) : [];
this.valueMetadatas = this.metadata.values.map(applyReasonableDefaults);
}
/**
@@ -121,7 +121,7 @@ define([
return metadata.hints[hint];
}
});
return _.sortBy(matchingMetadata, ...iteratees);
return _.sortByAll(matchingMetadata, ...iteratees);
};
TelemetryMetadataManager.prototype.getFilterableValues = function () {

View File

@@ -81,24 +81,6 @@ define([
return printj.sprintf(formatString, baseFormat.call(this, value));
};
}
if (valueMetadata.format === 'string') {
this.formatter.parse = function (value) {
if (value === undefined) {
return '';
}
if (typeof value === 'string') {
return value;
} else {
return value.toString();
}
};
this.formatter.format = function (value) {
return value;
};
this.formatter.validate = function (value) {
return typeof value === 'string';
};
}
}
TelemetryValueFormatter.prototype.parse = function (datum) {

View File

@@ -21,24 +21,22 @@
*****************************************************************************/
<template>
<div class="c-lad-table-wrapper">
<table class="c-table c-lad-table">
<thead>
<tr>
<th>Name</th>
<th>Timestamp</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<lad-row
v-for="item in items"
:key="item.key"
:domain-object="item.domainObject"
/>
</tbody>
</table>
</div>
<table class="c-table c-lad-table">
<thead>
<tr>
<th>Name</th>
<th>Timestamp</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<lad-row
v-for="item in items"
:key="item.key"
:domain-object="item.domainObject"
/>
</tbody>
</table>
</template>
<script>
@@ -75,7 +73,7 @@ export default {
this.items.push(item);
},
removeItem(identifier) {
let index = this.items.findIndex(item => this.openmct.objects.makeKeyString(identifier) === item.key);
let index = _.findIndex(this.items, (item) => this.openmct.objects.makeKeyString(identifier) === item.key);
this.items.splice(index, 1);
},

View File

@@ -102,7 +102,7 @@ export default {
this.compositions.push({composition, addCallback, removeCallback});
},
removePrimary(identifier) {
let index = this.primaryTelemetryObjects.findIndex(primary => this.openmct.objects.makeKeyString(identifier) === primary.key),
let index = _.findIndex(this.primaryTelemetryObjects, (primary) => this.openmct.objects.makeKeyString(identifier) === primary.key),
primary = this.primaryTelemetryObjects[index];
this.$set(this.secondaryTelemetryObjects, primary.key, undefined);
@@ -130,7 +130,7 @@ export default {
removeSecondary(primary) {
return (identifier) => {
let array = this.secondaryTelemetryObjects[primary.key],
index = array.findIndex(secondary => this.openmct.objects.makeKeyString(identifier) === secondary.key);
index = _.findIndex(array, (secondary) => this.openmct.objects.makeKeyString(identifier) === secondary.key);
array.splice(index, 1);

View File

@@ -30,7 +30,7 @@ define(
// DISCONNECTED: HTTP failed; maybe misconfigured, disconnected.
// PENDING: Still trying to connect, and haven't failed yet.
var CONNECTED = {
statusClass: "s-status-on"
statusClass: "s-status-ok"
},
PENDING = {
statusClass: "s-status-warning-lo"

View File

@@ -122,7 +122,7 @@ define(
it("indicates success if connection is nominal", function () {
jasmine.clock().tick(pluginOptions.interval + 1);
ajaxOptions.success();
expect(indicatorElement.classList.contains('s-status-on')).toBe(true);
expect(indicatorElement.classList.contains('s-status-ok')).toBe(true);
});
it("indicates an error when the server cannot be reached", function () {

View File

@@ -23,14 +23,13 @@
import EventEmitter from 'EventEmitter';
import uuid from 'uuid';
import TelemetryCriterion from "./criterion/TelemetryCriterion";
import { evaluateResults } from './utils/evaluator';
import { getLatestTimestamp } from './utils/time';
import AllTelemetryCriterion from "./criterion/AllTelemetryCriterion";
import { TRIGGER } from "./utils/constants";
import {computeCondition} from "./utils/evaluator";
/*
* conditionConfiguration = {
* id: uuid,
* trigger: 'any'/'all'/'not','xor',
* trigger: 'any'/'all',
* criteria: [
* {
* telemetry: '',
@@ -49,48 +48,20 @@ export default class ConditionClass extends EventEmitter {
* @param conditionConfiguration: {id: uuid,trigger: enum, criteria: Array of {id: uuid, operation: enum, input: Array, metaDataKey: string, key: {domainObject.identifier} }
* @param openmct
*/
constructor(conditionConfiguration, openmct, conditionManager) {
constructor(conditionConfiguration, openmct) {
super();
this.openmct = openmct;
this.conditionManager = conditionManager;
this.id = conditionConfiguration.id;
this.criteria = [];
this.criteriaResults = {};
this.result = undefined;
this.timeSystems = this.openmct.time.getAllTimeSystems();
if (conditionConfiguration.configuration.criteria) {
this.createCriteria(conditionConfiguration.configuration.criteria);
}
this.trigger = conditionConfiguration.configuration.trigger;
}
getResult(datum) {
if (!datum || !datum.id) {
console.log('no data received');
return;
}
this.criteria.forEach(criterion => {
if (this.isAnyOrAllTelemetry(criterion)) {
criterion.getResult(datum, this.conditionManager.telemetryObjects);
} else {
criterion.getResult(datum);
}
});
this.result = evaluateResults(this.criteria.map(criterion => criterion.result), this.trigger);
}
isAnyOrAllTelemetry(criterion) {
return (criterion.telemetry && (criterion.telemetry === 'all' || criterion.telemetry === 'any'));
}
isTelemetryUsed(id) {
return this.criteria.some(criterion => {
return this.isAnyOrAllTelemetry(criterion) || criterion.telemetryObjectIdAsString === id;
});
}
update(conditionConfiguration) {
this.updateTrigger(conditionConfiguration.configuration.trigger);
this.updateCriteria(conditionConfiguration.configuration.criteria);
@@ -99,14 +70,14 @@ export default class ConditionClass extends EventEmitter {
updateTrigger(trigger) {
if (this.trigger !== trigger) {
this.trigger = trigger;
this.handleConditionUpdated();
}
}
generateCriterion(criterionConfiguration) {
return {
id: criterionConfiguration.id || uuid(),
id: uuid(),
telemetry: criterionConfiguration.telemetry || '',
telemetryObject: this.conditionManager.telemetryObjects[this.openmct.objects.makeKeyString(criterionConfiguration.telemetry)],
operation: criterionConfiguration.operation || '',
input: criterionConfiguration.input === undefined ? [] : criterionConfiguration.input,
metadata: criterionConfiguration.metadata || ''
@@ -124,24 +95,14 @@ export default class ConditionClass extends EventEmitter {
this.createCriteria(criterionConfigurations);
}
updateTelemetry() {
this.criteria.forEach((criterion) => {
criterion.updateTelemetry(this.conditionManager.telemetryObjects);
});
}
/**
* adds criterion to the condition.
*/
addCriterion(criterionConfiguration) {
let criterion;
let criterionConfigurationWithId = this.generateCriterion(criterionConfiguration || null);
if (criterionConfiguration.telemetry && (criterionConfiguration.telemetry === 'any' || criterionConfiguration.telemetry === 'all')) {
criterion = new AllTelemetryCriterion(criterionConfigurationWithId, this.openmct);
} else {
criterion = new TelemetryCriterion(criterionConfigurationWithId, this.openmct);
}
let criterion = new TelemetryCriterion(criterionConfigurationWithId, this.openmct);
criterion.on('criterionUpdated', (obj) => this.handleCriterionUpdated(obj));
criterion.on('criterionResultUpdated', (obj) => this.handleCriterionResult(obj));
if (!this.criteria) {
this.criteria = [];
}
@@ -170,11 +131,22 @@ export default class ConditionClass extends EventEmitter {
const newCriterionConfiguration = this.generateCriterion(criterionConfiguration);
let newCriterion = new TelemetryCriterion(newCriterionConfiguration, this.openmct);
newCriterion.on('criterionUpdated', (obj) => this.handleCriterionUpdated(obj));
newCriterion.on('criterionResultUpdated', (obj) => this.handleCriterionResult(obj));
let criterion = found.item;
criterion.unsubscribe();
criterion.off('criterionUpdated', (obj) => this.handleCriterionUpdated(obj));
criterion.off('criterionResultUpdated', (obj) => this.handleCriterionResult(obj));
this.criteria.splice(found.index, 1, newCriterion);
if (this.criteriaResults[criterion.id] !== undefined) {
delete this.criteriaResults[criterion.id];
}
}
}
removeCriterion(id) {
if (this.destroyCriterion(id)) {
this.handleConditionUpdated();
}
}
@@ -182,12 +154,15 @@ export default class ConditionClass extends EventEmitter {
let found = this.findCriterion(id);
if (found) {
let criterion = found.item;
criterion.off('criterionUpdated', (obj) => {
this.handleCriterionUpdated(obj);
});
criterion.destroy();
// TODO this is passing the wrong args
criterion.off('criterionUpdated', (result) => {
this.handleCriterionUpdated(id, result);
});
this.criteria.splice(found.index, 1);
if (this.criteriaResults[criterion.id] !== undefined) {
delete this.criteriaResults[criterion.id];
}
return true;
}
return false;
@@ -197,38 +172,38 @@ export default class ConditionClass extends EventEmitter {
let found = this.findCriterion(criterion.id);
if (found) {
this.criteria[found.index] = criterion.data;
this.subscribe();
// TODO nothing is listening to this
this.emitEvent('conditionUpdated', {
trigger: this.trigger,
criteria: this.criteria
});
}
}
requestLADConditionResult() {
let latestTimestamp;
let criteriaResults = {};
const criteriaRequests = this.criteria
.map(criterion => criterion.requestLAD({telemetryObjects: this.conditionManager.telemetryObjects}));
handleCriterionResult(eventData) {
const id = eventData.id;
return Promise.all(criteriaRequests)
.then(results => {
results.forEach(resultObj => {
const { id, data, data: { result } } = resultObj;
if (this.findCriterion(id)) {
criteriaResults[id] = !!result;
}
latestTimestamp = getLatestTimestamp(
latestTimestamp,
data,
this.timeSystems,
this.openmct.time.timeSystem()
);
});
return {
id: this.id,
data: Object.assign(
{},
latestTimestamp,
{ result: evaluateResults(Object.values(criteriaResults), this.trigger) }
)
};
});
if (this.findCriterion(id)) {
this.criteriaResults[id] = eventData.data.result;
}
this.handleConditionUpdated(eventData.data);
}
subscribe() {
// TODO it looks like on any single criterion update subscriptions fire for all criteria
this.criteria.forEach((criterion) => {
criterion.subscribe();
})
}
handleConditionUpdated(datum) {
// trigger an updated event so that consumers can react accordingly
this.evaluate();
this.emitEvent('conditionResultUpdated',
Object.assign({}, datum, { result: this.result })
);
}
getCriteria() {
@@ -244,7 +219,21 @@ export default class ConditionClass extends EventEmitter {
return success;
}
evaluate() {
this.result = computeCondition(this.criteriaResults, this.trigger === TRIGGER.ALL);
}
emitEvent(eventName, data) {
this.emit(eventName, {
id: this.id,
data: data
});
}
destroy() {
if (typeof this.stopObservingForChanges === 'function') {
this.stopObservingForChanges();
}
this.destroyCriteria();
}
}

View File

@@ -21,7 +21,6 @@
*****************************************************************************/
import Condition from "./Condition";
import { getLatestTimestamp } from './utils/time';
import uuid from "uuid";
import EventEmitter from 'EventEmitter';
@@ -30,50 +29,17 @@ export default class ConditionManager extends EventEmitter {
super();
this.openmct = openmct;
this.conditionSetDomainObject = conditionSetDomainObject;
this.timeSystems = this.openmct.time.getAllTimeSystems();
this.composition = this.openmct.composition.get(conditionSetDomainObject);
this.composition.on('add', this.subscribeToTelemetry, this);
this.composition.on('remove', this.unsubscribeFromTelemetry, this);
this.compositionLoad = this.composition.load();
this.subscriptions = {};
this.telemetryObjects = {};
this.testData = {conditionTestData: [], applied: false};
this.timeAPI = this.openmct.time;
this.latestTimestamp = {};
this.initialize();
this.stopObservingForChanges = this.openmct.objects.observe(this.conditionSetDomainObject, '*', (newDomainObject) => {
this.conditionSetDomainObject = newDomainObject;
});
}
subscribeToTelemetry(endpoint) {
const id = this.openmct.objects.makeKeyString(endpoint.identifier);
if (this.subscriptions[id]) {
console.log('subscription already exists');
return;
}
this.telemetryObjects[id] = Object.assign({}, endpoint, {telemetryMetaData: this.openmct.telemetry.getMetadata(endpoint).valueMetadatas});
this.subscriptions[id] = this.openmct.telemetry.subscribe(
endpoint,
this.telemetryReceived.bind(this, id)
);
this.updateConditionTelemetry();
}
unsubscribeFromTelemetry(endpointIdentifier) {
const id = this.openmct.objects.makeKeyString(endpointIdentifier);
if (!this.subscriptions[id]) {
console.log('no subscription to remove');
return;
}
this.subscriptions[id]();
delete this.subscriptions[id];
delete this.telemetryObjects[id];
this.removeConditionTelemetry();
}
initialize() {
this.conditionResults = {};
this.conditionClassCollection = [];
if (this.conditionSetDomainObject.configuration.conditionCollection.length) {
this.conditionSetDomainObject.configuration.conditionCollection.forEach((conditionConfiguration, index) => {
@@ -82,34 +48,6 @@ export default class ConditionManager extends EventEmitter {
}
}
updateConditionTelemetry() {
this.conditionClassCollection.forEach((condition) => condition.updateTelemetry());
}
removeConditionTelemetry() {
let conditionsChanged = false;
this.conditionSetDomainObject.configuration.conditionCollection.forEach((conditionConfiguration) => {
conditionConfiguration.configuration.criteria.forEach((criterion, index) => {
const isAnyAllTelemetry = criterion.telemetry && (criterion.telemetry === 'any' || criterion.telemetry === 'all');
if (!isAnyAllTelemetry) {
const found = Object.values(this.telemetryObjects).find((telemetryObject) => {
return this.openmct.objects.areIdsEqual(telemetryObject.identifier, criterion.telemetry);
});
if (!found) {
criterion.telemetry = '';
criterion.metadata = '';
criterion.input = [];
criterion.operation = '';
conditionsChanged = true;
}
}
});
});
if (conditionsChanged) {
this.persistConditions();
}
}
updateCondition(conditionConfiguration, index) {
let condition = this.conditionClassCollection[index];
condition.update(conditionConfiguration);
@@ -118,12 +56,18 @@ export default class ConditionManager extends EventEmitter {
}
initCondition(conditionConfiguration, index) {
let condition = new Condition(conditionConfiguration, this.openmct, this);
let condition = new Condition(conditionConfiguration, this.openmct);
condition.on('conditionResultUpdated', this.handleConditionResult.bind(this));
if (index !== undefined) {
this.conditionClassCollection.splice(index + 1, 0, condition);
} else {
this.conditionClassCollection.unshift(condition);
}
//There are no criteria for a default condition and hence no subscriptions.
//Hence the conditionResult must be manually triggered for it.
if (conditionConfiguration.isDefault) {
this.handleConditionResult();
}
}
createCondition(conditionConfiguration) {
@@ -145,7 +89,6 @@ export default class ConditionManager extends EventEmitter {
output: 'false',
trigger: 'all',
criteria: [{
id: uuid(),
telemetry: '',
operation: '',
input: [],
@@ -164,13 +107,11 @@ export default class ConditionManager extends EventEmitter {
}
cloneCondition(conditionConfiguration, index) {
let clonedConfig = JSON.parse(JSON.stringify(conditionConfiguration));
clonedConfig.configuration.criteria.forEach((criterion) => criterion.id = uuid());
this.createAndSaveCondition(index, clonedConfig);
this.createAndSaveCondition(index, conditionConfiguration);
}
createAndSaveCondition(index, conditionConfiguration) {
const newCondition = this.createCondition(conditionConfiguration);
let newCondition = this.createCondition(conditionConfiguration);
if (index !== undefined) {
this.conditionSetDomainObject.configuration.conditionCollection.splice(index + 1, 0, newCondition);
} else {
@@ -182,16 +123,22 @@ export default class ConditionManager extends EventEmitter {
removeCondition(index) {
let condition = this.conditionClassCollection[index];
condition.destroy();
condition.destroyCriteria();
condition.off('conditionResultUpdated', this.handleConditionResult.bind(this));
this.conditionClassCollection.splice(index, 1);
this.conditionSetDomainObject.configuration.conditionCollection.splice(index, 1);
if (this.conditionResults[condition.id] !== undefined) {
delete this.conditionResults[condition.id];
}
this.persistConditions();
this.handleConditionResult();
}
findConditionById(id) {
return this.conditionClassCollection.find(conditionClass => conditionClass.id === id);
}
//this.$set(this.conditionClassCollection, reorderEvent.newIndex, oldConditions[reorderEvent.oldIndex]);
reorderConditions(reorderPlan) {
let oldConditions = Array.from(this.conditionSetDomainObject.configuration.conditionCollection);
let newCollection = [];
@@ -203,101 +150,26 @@ export default class ConditionManager extends EventEmitter {
this.persistConditions();
}
getCurrentCondition() {
handleConditionResult(resultObj) {
const conditionCollection = this.conditionSetDomainObject.configuration.conditionCollection;
let currentCondition = conditionCollection[conditionCollection.length-1];
if (resultObj) {
const id = resultObj.id;
if (this.findConditionById(id)) {
this.conditionResults[id] = resultObj.data.result;
}
this.updateTimestamp(resultObj.data);
}
for (let i = 0; i < conditionCollection.length - 1; i++) {
const condition = this.findConditionById(conditionCollection[i].id)
if (condition.result) {
if (this.conditionResults[conditionCollection[i].id]) {
//first condition to be true wins
currentCondition = conditionCollection[i];
break;
}
}
return currentCondition;
}
getCurrentConditionLAD(conditionResults) {
const conditionCollection = this.conditionSetDomainObject.configuration.conditionCollection;
let currentCondition = conditionCollection[conditionCollection.length-1];
for (let i = 0; i < conditionCollection.length - 1; i++) {
if (conditionResults[conditionCollection[i].id]) {
//first condition to be true wins
currentCondition = conditionCollection[i];
break;
}
}
return currentCondition;
}
requestLADConditionSetOutput() {
if (!this.conditionClassCollection.length) {
return Promise.resolve([]);
}
return this.compositionLoad.then(() => {
let latestTimestamp;
let conditionResults = {};
const conditionRequests = this.conditionClassCollection
.map(condition => condition.requestLADConditionResult());
return Promise.all(conditionRequests)
.then((results) => {
results.forEach(resultObj => {
const { id, data, data: { result } } = resultObj;
if (this.findConditionById(id)) {
conditionResults[id] = !!result;
}
latestTimestamp = getLatestTimestamp(
latestTimestamp,
data,
this.timeSystems,
this.openmct.time.timeSystem()
);
});
const currentCondition = this.getCurrentConditionLAD(conditionResults);
return Object.assign(
{
output: currentCondition.configuration.output,
id: this.conditionSetDomainObject.identifier,
conditionId: currentCondition.id
},
latestTimestamp
);
});
});
}
isTelemetryUsed(id) {
for(const condition of this.conditionClassCollection) {
if (condition.isTelemetryUsed(id)) {
return true;
}
}
return false;
}
telemetryReceived(id, datum) {
if (!this.isTelemetryUsed(id)) {
return;
}
const normalizedDatum = this.createNormalizedDatum(datum, id);
const timeSystemKey = this.openmct.time.timeSystem().key;
let timestamp = {};
timestamp[timeSystemKey] = normalizedDatum[timeSystemKey];
this.conditionClassCollection.forEach(condition => {
condition.getResult(normalizedDatum);
});
const currentCondition = this.getCurrentCondition();
this.emit('conditionSetResultUpdated',
Object.assign(
{
@@ -305,38 +177,19 @@ export default class ConditionManager extends EventEmitter {
id: this.conditionSetDomainObject.identifier,
conditionId: currentCondition.id
},
timestamp
this.latestTimestamp
)
)
}
getTestData(metadatum) {
let data = undefined;
if (this.testData.applied) {
const found = this.testData.conditionTestInputs.find((testInput) => (testInput.metadata === metadatum.source));
if (found) {
data = found.value;
updateTimestamp(timestamp) {
this.timeAPI.getAllTimeSystems().forEach(timeSystem => {
if (!this.latestTimestamp[timeSystem.key]
|| timestamp[timeSystem.key] > this.latestTimestamp[timeSystem.key]
) {
this.latestTimestamp[timeSystem.key] = timestamp[timeSystem.key];
}
}
return data;
}
createNormalizedDatum(telemetryDatum, id) {
const normalizedDatum = Object.values(this.telemetryObjects[id].telemetryMetaData).reduce((datum, metadatum) => {
const testValue = this.getTestData(metadatum);
const formatter = this.openmct.telemetry.getValueFormatter(metadatum);
datum[metadatum.key] = testValue !== undefined ? formatter.parse(testValue) : formatter.parse(telemetryDatum[metadatum.source]);
return datum;
}, {});
normalizedDatum.id = id;
return normalizedDatum;
}
updateTestData(testData) {
this.testData = testData;
this.openmct.objects.mutate(this.conditionSetDomainObject, 'configuration.conditionTestData', this.testData.conditionTestInputs);
});
}
persistConditions() {
@@ -344,16 +197,11 @@ export default class ConditionManager extends EventEmitter {
}
destroy() {
this.composition.off('add', this.subscribeToTelemetry, this);
this.composition.off('remove', this.unsubscribeFromTelemetry, this);
Object.values(this.subscriptions).forEach(unsubscribe => unsubscribe());
delete this.subscriptions;
if(this.stopObservingForChanges) {
this.stopObservingForChanges();
}
this.conditionClassCollection.forEach((condition) => {
condition.off('conditionResultUpdated', this.handleConditionResult);
condition.destroy();
})
}

View File

@@ -47,9 +47,6 @@ describe('ConditionManager', () => {
]
}
};
let mockComposition;
let loader;
let mockTimeSystems;
function mockAngularComponents() {
let mockInjector = jasmine.createSpyObj('$injector', ['get']);
@@ -74,33 +71,9 @@ describe('ConditionManager', () => {
openmct.$injector = mockInjector;
}
beforeEach(function () {
beforeAll(function () {
mockAngularComponents();
mockListener = jasmine.createSpy('mockListener');
loader = {};
loader.promise = new Promise(function (resolve, reject) {
loader.resolve = resolve;
loader.reject = reject;
});
mockComposition = jasmine.createSpyObj('compositionCollection', [
'load',
'on',
'off'
]);
mockComposition.load.and.callFake(() => {
setTimeout(() => {
loader.resolve();
});
return loader.promise;
});
mockComposition.on('add', mockListener);
mockComposition.on('remove', mockListener);
openmct.composition = jasmine.createSpyObj('compositionAPI', [
'get'
]);
openmct.composition.get.and.returnValue(mockComposition);
openmct.objects = jasmine.createSpyObj('objects', ['get', 'makeKeyString', 'observe', 'mutate']);
openmct.objects.get.and.returnValues(new Promise(function (resolve, reject) {
@@ -111,17 +84,10 @@ describe('ConditionManager', () => {
openmct.objects.makeKeyString.and.returnValue(conditionSetDomainObject.identifier.key);
openmct.objects.observe.and.returnValue(function () {});
openmct.objects.mutate.and.returnValue(function () {});
mockTimeSystems = {
key: 'utc'
};
openmct.time = jasmine.createSpyObj('time', ['getAllTimeSystems']);
openmct.time.getAllTimeSystems.and.returnValue([mockTimeSystems]);
conditionMgr = new ConditionManager(conditionSetDomainObject, openmct);
mockListener = jasmine.createSpy('mockListener');
conditionMgr.on('conditionSetResultUpdated', mockListener);
conditionMgr.on('telemetryReceived', mockListener);
});
it('creates a conditionCollection with a default condition', function () {

View File

@@ -28,6 +28,7 @@ describe('ConditionSetCompositionPolicy', () => {
let testTelemetryObject;
let openmct = {};
let parentDomainObject;
let composition;
beforeAll(function () {
testTelemetryObject = {
@@ -56,6 +57,7 @@ describe('ConditionSetCompositionPolicy', () => {
openmct.telemetry = jasmine.createSpyObj('telemetry', ['isTelemetryObject']);
policy = new ConditionSetCompositionPolicy(openmct);
parentDomainObject = {};
composition = {};
});
it('returns true for object types that are not conditionSets', function () {

View File

@@ -1,25 +1,3 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, 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.
*****************************************************************************/
export default class ConditionSetMetadataProvider {
constructor(openmct) {
this.openmct = openmct;
@@ -43,21 +21,12 @@ export default class ConditionSetMetadataProvider {
}
getMetadata(domainObject) {
const enumerations = domainObject.configuration.conditionCollection
.map((condition, index) => {
return {
string: condition.configuration.output,
value: index
};
});
return {
values: this.getDomains().concat([
{
name: 'Output',
key: 'output',
format: 'enum',
enumerations: enumerations,
format: 'string',
hints: {
range: 1
}

View File

@@ -25,62 +25,27 @@ import ConditionManager from './ConditionManager'
export default class ConditionSetTelemetryProvider {
constructor(openmct) {
this.openmct = openmct;
this.conditionManagerPool = {};
}
isTelemetryObject(domainObject) {
return domainObject.type === 'conditionSet';
}
supportsRequest(domainObject) {
return domainObject.type === 'conditionSet';
supportsRequest(domainObject, options) {
return false;
}
supportsSubscribe(domainObject) {
return domainObject.type === 'conditionSet';
}
request(domainObject) {
let conditionManager = this.getConditionManager(domainObject);
return conditionManager.requestLADConditionSetOutput()
.then(latestOutput => {
return latestOutput ? [latestOutput] : [];
});
}
subscribe(domainObject, callback) {
let conditionManager = this.getConditionManager(domainObject);
let conditionManager = new ConditionManager(domainObject, this.openmct);
conditionManager.on('conditionSetResultUpdated', callback);
return this.destroyConditionManager.bind(this, this.openmct.objects.makeKeyString(domainObject.identifier));
}
/**
* returns conditionManager instance for corresponding domain object
* creates the instance if it is not yet created
* @private
*/
getConditionManager(domainObject) {
const id = this.openmct.objects.makeKeyString(domainObject.identifier);
if (!this.conditionManagerPool[id]) {
this.conditionManagerPool[id] = new ConditionManager(domainObject, this.openmct);
}
return this.conditionManagerPool[id];
}
/**
* cleans up and destroys conditionManager instance for corresponding domain object id
* can be called manually for views that only request but do not subscribe to data
*/
destroyConditionManager(id) {
if (this.conditionManagerPool[id]) {
this.conditionManagerPool[id].off('conditionSetResultUpdated');
this.conditionManagerPool[id].destroy();
delete this.conditionManagerPool[id];
return function unsubscribe() {
conditionManager.off('conditionSetResultUpdated');
conditionManager.destroy();
}
}
}

View File

@@ -1,33 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, 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.
*****************************************************************************/
function ConditionSetViewPolicy() {
}
ConditionSetViewPolicy.prototype.allow = function (view, domainObject) {
if (domainObject.getModel().type === 'conditionSet') {
return view.key === 'conditionSet.view';
}
return true;
}
export default ConditionSetViewPolicy;

View File

@@ -30,7 +30,7 @@ export default class ConditionSetViewProvider {
this.openmct = openmct;
this.name = 'Conditions View';
this.key = 'conditionSet.view';
this.cssClass = 'icon-conditional';
this.cssClass = 'icon-conditional'; // TODO: replace with class for new icon
}
canView(domainObject) {

View File

@@ -25,22 +25,15 @@ import {TRIGGER} from "./utils/constants";
import TelemetryCriterion from "./criterion/TelemetryCriterion";
let openmct = {},
mockListener,
testConditionDefinition,
testTelemetryObject,
conditionObj,
conditionManager,
mockTelemetryReceived,
mockTimeSystems;
conditionObj;
describe("The condition", function () {
beforeEach (() => {
conditionManager = jasmine.createSpyObj('conditionManager',
['on']
);
mockTelemetryReceived = jasmine.createSpy('listener');
conditionManager.on('telemetryReceived', mockTelemetryReceived);
mockListener = jasmine.createSpy('listener');
testTelemetryObject = {
identifier:{ namespace: "", key: "test-object"},
type: "test-object",
@@ -61,9 +54,6 @@ describe("The condition", function () {
}]
}
};
conditionManager.telemetryObjects = {
"test-object": testTelemetryObject
};
openmct.objects = jasmine.createSpyObj('objects', ['get', 'makeKeyString']);
openmct.objects.get.and.returnValue(new Promise(function (resolve, reject) {
resolve(testTelemetryObject);
@@ -73,23 +63,14 @@ describe("The condition", function () {
openmct.telemetry.subscribe.and.returnValue(function () {});
openmct.telemetry.getMetadata.and.returnValue(testTelemetryObject.telemetry.values);
mockTimeSystems = {
key: 'utc'
};
openmct.time = jasmine.createSpyObj('time', ['getAllTimeSystems']);
openmct.time.getAllTimeSystems.and.returnValue([mockTimeSystems]);
testConditionDefinition = {
id: '123-456',
configuration: {
name: 'mock condition',
output: 'mock output',
trigger: TRIGGER.ANY,
criteria: [
{
id: '1234-5678-9999-0000',
operation: 'equalTo',
input: ['0'],
input: false,
metadata: 'value',
telemetry: testTelemetryObject.identifier
}
@@ -99,12 +80,14 @@ describe("The condition", function () {
conditionObj = new Condition(
testConditionDefinition,
openmct,
conditionManager
openmct
);
conditionObj.on('conditionUpdated', mockListener);
});
it("generates criteria with the correct properties", function () {
it("generates criteria with an id", function () {
const testCriterion = testConditionDefinition.configuration.criteria[0];
let criterion = conditionObj.generateCriterion(testCriterion);
expect(criterion.id).toBeDefined();

View File

@@ -23,24 +23,18 @@
import EventEmitter from 'EventEmitter';
export default class StyleRuleManager extends EventEmitter {
constructor(styleConfiguration, openmct, callback) {
constructor(conditionalStyleConfiguration, openmct) {
super();
this.openmct = openmct;
this.callback = callback;
if (styleConfiguration) {
this.initialize(styleConfiguration);
if (styleConfiguration.conditionSetIdentifier) {
this.subscribeToConditionSet();
} else {
this.applyStaticStyle();
}
if (conditionalStyleConfiguration && conditionalStyleConfiguration.conditionSetIdentifier) {
this.initialize(conditionalStyleConfiguration);
this.subscribeToConditionSet();
}
}
initialize(styleConfiguration) {
this.conditionSetIdentifier = styleConfiguration.conditionSetIdentifier;
this.staticStyle = styleConfiguration.staticStyle;
this.updateConditionStylesMap(styleConfiguration.styles || []);
initialize(conditionalStyleConfiguration) {
this.conditionSetIdentifier = conditionalStyleConfiguration.conditionSetIdentifier;
this.updateConditionStylesMap(conditionalStyleConfiguration.styles || []);
}
subscribeToConditionSet() {
@@ -48,24 +42,17 @@ export default class StyleRuleManager extends EventEmitter {
this.stopProvidingTelemetry();
}
this.openmct.objects.get(this.conditionSetIdentifier).then((conditionSetDomainObject) => {
this.openmct.telemetry.request(conditionSetDomainObject)
.then(output => {
if (output && output.length) {
this.handleConditionSetResultUpdated(output[0]);
}
});
this.stopProvidingTelemetry = this.openmct.telemetry.subscribe(conditionSetDomainObject, output => this.handleConditionSetResultUpdated(output));
});
}
updateObjectStyleConfig(styleConfiguration) {
if (!styleConfiguration || !styleConfiguration.conditionSetIdentifier) {
this.initialize(styleConfiguration || {});
updateConditionalStyleConfig(conditionalStyleConfiguration) {
if (!conditionalStyleConfiguration || !conditionalStyleConfiguration.conditionSetIdentifier) {
this.destroy();
} else {
let isNewConditionSet = !this.conditionSetIdentifier ||
!this.openmct.objects.areIdsEqual(this.conditionSetIdentifier, styleConfiguration.conditionSetIdentifier);
this.initialize(styleConfiguration);
this.openmct.objects.areIdsEqual(this.conditionSetIdentifier, conditionalStyleConfiguration.conditionSetIdentifier);
this.initialize(conditionalStyleConfiguration);
//Only resubscribe if the conditionSet has changed.
if (isNewConditionSet) {
this.subscribeToConditionSet();
@@ -76,11 +63,7 @@ export default class StyleRuleManager extends EventEmitter {
updateConditionStylesMap(conditionStyles) {
let conditionStyleMap = {};
conditionStyles.forEach((conditionStyle) => {
if (conditionStyle.conditionId) {
conditionStyleMap[conditionStyle.conditionId] = conditionStyle.style;
} else {
conditionStyleMap.static = conditionStyle.style;
}
conditionStyleMap[conditionStyle.conditionId] = conditionStyle.style;
});
this.conditionalStyleMap = conditionStyleMap;
}
@@ -91,37 +74,29 @@ export default class StyleRuleManager extends EventEmitter {
if (foundStyle !== this.currentStyle) {
this.currentStyle = foundStyle;
}
this.updateDomainObjectStyle();
} else {
this.applyStaticStyle();
}
}
updateDomainObjectStyle() {
if (this.callback) {
this.callback(Object.assign({}, this.currentStyle));
}
}
applyStaticStyle() {
if (this.staticStyle) {
this.currentStyle = this.staticStyle.style;
} else {
if (this.currentStyle) {
Object.keys(this.currentStyle).forEach(key => {
this.currentStyle[key] = '__no_value';
});
if (this.currentStyle !== this.defaultStyle) {
this.currentStyle = this.defaultStyle;
}
}
this.updateDomainObjectStyle();
}
updateDomainObjectStyle() {
this.emit('conditionalStyleUpdated', this.currentStyle)
}
destroy() {
this.applyStaticStyle();
if (this.currentStyle) {
Object.keys(this.currentStyle).forEach(key => {
this.currentStyle[key] = 'inherit';
});
}
this.updateDomainObjectStyle();
if (this.stopProvidingTelemetry) {
this.stopProvidingTelemetry();
}
delete this.stopProvidingTelemetry;
this.conditionSetIdentifier = undefined;
}

View File

@@ -21,182 +21,198 @@
*****************************************************************************/
<template>
<div class="c-condition-h"
:class="{ 'is-drag-target': draggingOver }"
@dragover.prevent
@drop.prevent="dropCondition($event, conditionIndex)"
@dragenter="dragEnter($event, conditionIndex)"
@dragleave="dragLeave($event, conditionIndex)"
<div v-if="isEditing"
class="c-condition c-condition--edit js-condition-drag-wrapper"
>
<div class="c-condition-h__drop-target"></div>
<div v-if="isEditing"
class="c-condition c-condition--edit"
>
<!-- Edit view -->
<div class="c-condition__header">
<span class="c-condition__drag-grippy c-grippy c-grippy--vertical-drag"
title="Drag to reorder conditions"
:class="[{ 'is-enabled': !condition.isDefault }, { 'hide-nice': condition.isDefault }]"
:draggable="!condition.isDefault"
@dragstart="dragStart"
@dragend="dragEnd"
></span>
<!-- Edit view -->
<div class="c-condition__header">
<span class="c-condition__drag-grippy c-grippy c-grippy--vertical-drag"
title="Drag to reorder conditions"
:class="[{ 'is-enabled': !condition.isDefault }, { 'hide-nice': condition.isDefault }]"
:draggable="!condition.isDefault"
@dragstart="dragStart"
@dragstop="dragStop"
@dragover.stop
></span>
<span class="c-condition__disclosure c-disclosure-triangle c-tree__item__view-control is-enabled"
:class="{ 'c-disclosure-triangle--expanded': expanded }"
@click="expanded = !expanded"
></span>
<span class="c-condition__disclosure c-disclosure-triangle c-tree__item__view-control is-enabled"
:class="{ 'c-disclosure-triangle--expanded': expanded }"
@click="expanded = !expanded"
></span>
<span class="c-condition__name">{{ condition.configuration.name }}</span>
<span class="c-condition__summary">
<template v-if="!canEvaluateCriteria">
Define criteria
</template>
<span v-else>
<condition-description :show-label="false"
:condition="condition"
/>
<span class="c-condition__name">{{ condition.configuration.name }}</span>
<!-- TODO: description should be derived from criteria -->
<span v-if="condition.isDefault"
class="c-condition__summary"
>
When all else fails
</span>
<span v-else
class="c-condition__summary"
>
<template v-if="!canEvaluateCriteria">
Define criteria
</template>
<template v-else>
When
<span v-for="(criterion, index) in condition.configuration.criteria"
:key="index"
>
{{ getRule(criterion, index) }}
<template v-if="!isLastCriterion">
{{ getConjunction }}
</template>
</span>
</span>
</template>
</span>
<div class="c-condition__buttons">
<button v-if="!condition.isDefault"
class="c-click-icon c-condition__duplicate-button icon-duplicate"
title="Duplicate this condition"
@click="cloneCondition"
></button>
<div class="c-condition__buttons">
<button v-if="!condition.isDefault"
class="c-click-icon c-condition__duplicate-button icon-duplicate"
title="Duplicate this condition"
@click="cloneCondition"
></button>
<button v-if="!condition.isDefault"
class="c-click-icon c-condition__delete-button icon-trash"
title="Delete this condition"
@click="removeCondition"
></button>
</div>
<button v-if="!condition.isDefault"
class="c-click-icon c-condition__delete-button icon-trash"
title="Delete this condition"
@click="removeCondition"
></button>
</div>
<div v-if="expanded"
class="c-condition__definition c-cdef"
</div>
<div v-if="expanded"
class="c-condition__definition c-cdef"
>
<span class="c-cdef__separator c-row-separator"></span>
<span class="c-cdef__label">Condition Name</span>
<span class="c-cdef__controls">
<input v-model="condition.configuration.name"
class="t-condition-input__name"
type="text"
@blur="persist"
>
</span>
<span class="c-cdef__label">Output</span>
<span class="c-cdef__controls">
<select v-model="selectedOutputSelection"
@change="setOutputValue"
>
<option value="">- Select Output -</option>
<option v-for="option in outputOptions"
:key="option"
:value="option"
>
{{ initCap(option) }}
</option>
</select>
<input v-if="selectedOutputSelection === outputOptions[2]"
v-model="condition.configuration.output"
class="t-condition-name-input"
type="text"
@blur="persist"
>
</span>
<div v-if="!condition.isDefault"
class="c-cdef__match-and-criteria"
>
<span class="c-cdef__separator c-row-separator"></span>
<span class="c-cdef__label">Condition Name</span>
<span class="c-cdef__label">Match</span>
<span class="c-cdef__controls">
<input v-model="condition.configuration.name"
class="t-condition-input__name"
type="text"
@change="persist"
<select v-model="condition.configuration.trigger"
@change="persist"
>
<option value="all">when all criteria are met</option>
<option value="any">when any criteria are met</option>
</select>
</span>
<span class="c-cdef__label">Output</span>
<span class="c-cdef__controls">
<span class="c-cdef__control">
<select v-model="selectedOutputSelection"
@change="setOutputValue"
>
<option v-for="option in outputOptions"
:key="option"
:value="option"
>
{{ initCap(option) }}
</option>
</select>
</span>
<span class="c-cdef__control">
<input v-if="selectedOutputSelection === outputOptions[2]"
v-model="condition.configuration.output"
class="t-condition-name-input"
type="text"
@change="persist"
>
</span>
</span>
<div v-if="!condition.isDefault"
class="c-cdef__match-and-criteria"
>
<span class="c-cdef__separator c-row-separator"></span>
<span class="c-cdef__label">Match</span>
<span class="c-cdef__controls">
<select v-model="condition.configuration.trigger"
@change="persist"
>
<option v-for="option in triggers"
:key="option.value"
:value="option.value"
> {{ option.label }}</option>
</select>
</span>
<template v-if="telemetry.length || condition.configuration.criteria.length">
<div v-for="(criterion, index) in condition.configuration.criteria"
:key="criterion.id"
class="c-cdef__criteria"
>
<Criterion :telemetry="telemetry"
:criterion="criterion"
:index="index"
:trigger="condition.configuration.trigger"
:is-default="condition.configuration.criteria.length === 1"
@persist="persist"
/>
<div class="c-cdef__criteria__buttons">
<button class="c-click-icon c-cdef__criteria-duplicate-button icon-duplicate"
title="Duplicate this criteria"
@click="cloneCriterion(index)"
></button>
<button v-if="!(condition.configuration.criteria.length === 1)"
class="c-click-icon c-cdef__criteria-duplicate-button icon-trash"
title="Delete this criteria"
@click="removeCriterion(index)"
></button>
</div>
<template v-if="telemetry.length">
<div v-for="(criterion, index) in condition.configuration.criteria"
:key="index"
class="c-cdef__criteria"
>
<Criterion :telemetry="telemetry"
:criterion="criterion"
:index="index"
:trigger="condition.configuration.trigger"
:is-default="condition.configuration.criteria.length === 1"
@persist="persist"
/>
<div class="c-cdef__criteria__buttons">
<button class="c-click-icon c-cdef__criteria-duplicate-button icon-duplicate"
title="Duplicate this criteria"
@click="cloneCriterion(index)"
></button>
<button v-if="!(condition.configuration.criteria.length === 1)"
class="c-click-icon c-cdef__criteria-duplicate-button icon-trash"
title="Delete this criteria"
@click="removeCriterion(index)"
></button>
</div>
</template>
<div class="c-cdef__separator c-row-separator"></div>
<div class="c-cdef__controls"
:disabled="!telemetry.length"
>
<button
class="c-cdef__add-criteria-button c-button c-button--labeled icon-plus"
@click="addCriteria"
>
<span class="c-button__label">Add Criteria</span>
</button>
</div>
</template>
<div class="c-cdef__separator c-row-separator"></div>
<div class="c-cdef__controls"
:disabled="!telemetry.length"
>
<button
class="c-cdef__add-criteria-button c-button c-button--labeled icon-plus"
@click="addCriteria"
>
<span class="c-button__label">Add Criteria</span>
</button>
</div>
</div>
</div>
<div v-else
class="c-condition c-condition--browse"
</div>
<div v-else
class="c-condition c-condition--browse"
>
<!-- Browse view -->
<div class="c-condition__header">
<span class="c-condition__name">
{{ condition.configuration.name }}
</span>
<span class="c-condition__output">
Output: {{ condition.configuration.output }}
</span>
</div>
<div v-if="condition.isDefault"
class="c-condition__summary"
>
<!-- Browse view -->
<div class="c-condition__header">
<span class="c-condition__name">
{{ condition.configuration.name }}
When all else fails
</div>
<div v-else
class="c-condition__summary"
>
<template v-if="!canEvaluateCriteria">
Define criteria
</template>
<template v-else>
When
<span v-for="(criterion, index) in condition.configuration.criteria"
:key="index"
>
{{ getRule(criterion, index) }}
<template v-if="!isLastCriterion">
{{ getConjunction }}
</template>
</span>
<span class="c-condition__output">
Output: {{ condition.configuration.output }}
</span>
</div>
<div class="c-condition__summary">
<condition-description :show-label="false"
:condition="condition"
/>
</div>
</template>
</div>
</div>
</template>
<script>
import Criterion from './Criterion.vue';
import ConditionDescription from "./ConditionDescription.vue";
import { TRIGGER, TRIGGER_LABEL } from "@/plugins/condition/utils/constants";
import uuid from 'uuid';
import { OPERATIONS } from '../utils/operations';
export default {
inject: ['openmct'],
components: {
Criterion,
ConditionDescription
Criterion
},
props: {
condition: {
@@ -215,14 +231,6 @@ export default {
type: Array,
required: true,
default: () => []
},
isDragging: {
type: Boolean,
default: false
},
moveIndex: {
type: Number,
default: 0
}
},
data() {
@@ -232,36 +240,25 @@ export default {
trigger: 'all',
selectedOutputSelection: '',
outputOptions: ['false', 'true', 'string'],
criterionIndex: 0,
draggingOver: false,
isDefault: this.condition.isDefault
criterionIndex: 0
};
},
computed: {
triggers() {
const keys = Object.keys(TRIGGER);
const triggerOptions = [];
keys.forEach((trigger) => {
triggerOptions.push({
value: TRIGGER[trigger],
label: TRIGGER_LABEL[TRIGGER[trigger]]
});
});
return triggerOptions;
},
canEvaluateCriteria: function () {
let criteria = this.condition.configuration.criteria;
if (criteria.length) {
let lastCriterion = criteria[criteria.length - 1];
if (lastCriterion.telemetry &&
lastCriterion.operation &&
(lastCriterion.input.length ||
lastCriterion.operation === 'isDefined' ||
lastCriterion.operation === 'isUndefined')) {
return true;
}
let lastCriterion = criteria[criteria.length - 1];
if (lastCriterion.telemetry &&
lastCriterion.operation &&
(lastCriterion.input.length ||
lastCriterion.operation === 'isDefined' ||
lastCriterion.operation === 'isUndefined')) {
return true;
} else {
return false;
}
return false;
},
getConjunction: function () {
return this.condition.configuration.trigger === 'all' ? 'and' : 'or';
}
},
destroyed() {
@@ -271,6 +268,20 @@ export default {
this.setOutputSelection();
},
methods: {
getRule(criterion, index) {
return `${criterion.telemetry.name} ${criterion.telemetry.fieldName} ${this.findDescription(criterion.operation, criterion.input)}`;
},
isLastCriterion(index) {
return index === this.condition.configuration.criteria.length - 1;
},
findDescription(operation, values) {
for (let i=0, ii= OPERATIONS.length; i < ii; i++) {
if (operation === OPERATIONS[i].name) {
return OPERATIONS[i].getDescription(values);
}
}
return null;
},
setOutputSelection() {
let conditionOutput = this.condition.configuration.output;
if (conditionOutput) {
@@ -291,7 +302,6 @@ export default {
},
addCriteria() {
const criteriaObject = {
id: uuid(),
telemetry: '',
operation: '',
input: '',
@@ -302,39 +312,11 @@ export default {
dragStart(e) {
e.dataTransfer.setData('dragging', e.target); // required for FF to initiate drag
e.dataTransfer.effectAllowed = "copyMove";
e.dataTransfer.setDragImage(e.target.closest('.c-condition-h'), 0, 0);
e.dataTransfer.setDragImage(e.target.closest('.js-condition-drag-wrapper'), 0, 0);
this.$emit('setMoveIndex', this.conditionIndex);
},
dragEnd(event) {
this.dragStarted = false;
event.dataTransfer.clearData();
this.$emit('dragComplete');
},
dropCondition(event, targetIndex) {
if (!this.isDragging) { return }
if (targetIndex > this.moveIndex) { targetIndex-- } // for 'downward' move
if (this.isValidTarget(targetIndex)) {
this.dragElement = undefined;
this.draggingOver = false;
this.$emit('dropCondition', targetIndex);
}
},
dragEnter(event, targetIndex) {
if (!this.isDragging) { return }
if (targetIndex > this.moveIndex) { targetIndex-- } // for 'downward' move
if (this.isValidTarget(targetIndex)) {
this.dragElement = event.target.parentElement;
this.draggingOver = true;
}
},
dragLeave(event) {
if (event.target.parentElement === this.dragElement) {
this.draggingOver = false;
this.dragElement = undefined;
}
},
isValidTarget(targetIndex) {
return this.moveIndex !== targetIndex;
dragStop(e) {
e.dataTransfer.clearData();
},
destroy() {
},
@@ -349,13 +331,16 @@ export default {
},
removeCriterion(index) {
this.condition.configuration.criteria.splice(index, 1);
this.persist();
this.persist()
},
cloneCriterion(index) {
const clonedCriterion = JSON.parse(JSON.stringify(this.condition.configuration.criteria[index]));
clonedCriterion.id = uuid();
const clonedCriterion = {...this.condition.configuration.criteria[index]};
this.condition.configuration.criteria.splice(index + 1, 0, clonedCriterion);
this.persist();
this.persist()
},
hasTelemetry(identifier) {
// TODO: check parent condition.composition.hasTelemetry
return this.currentCriteria && identifier;
},
persist() {
this.$emit('updateCondition', {
@@ -363,7 +348,7 @@ export default {
index: this.conditionIndex
});
},
initCap(str) {
initCap: function (str) {
return str.charAt(0).toUpperCase() + str.slice(1)
}
}

View File

@@ -22,6 +22,7 @@
<template>
<section id="conditionCollection"
class="c-cs__conditions"
:class="{ 'is-expanded': expanded }"
>
<div class="c-cs__header c-section__header">
@@ -52,24 +53,28 @@
<span class="c-cs-button__label">Add Condition</span>
</button>
<div class="c-cs__conditions-h"
:class="{ 'is-active-dragging': isDragging }"
>
<Condition v-for="(condition, index) in conditionCollection"
:key="condition.id"
:condition="condition"
:condition-index="index"
:telemetry="telemetryObjs"
:is-editing="isEditing"
:move-index="moveIndex"
:is-dragging="isDragging"
@updateCondition="updateCondition"
@removeCondition="removeCondition"
@cloneCondition="cloneCondition"
@setMoveIndex="setMoveIndex"
@dragComplete="dragComplete"
@dropCondition="dropCondition"
/>
<div class="c-cs__conditions-h">
<div v-for="(condition, index) in conditionCollection"
:key="condition.id"
class="c-condition-h"
>
<div v-if="isEditing"
class="c-c__drag-ghost"
@drop.prevent="dropCondition"
@dragenter="dragEnter"
@dragleave="dragLeave"
@dragover.prevent
></div>
<Condition :condition="condition"
:condition-index="index"
:telemetry="telemetryObjs"
:is-editing="isEditing"
@updateCondition="updateCondition"
@removeCondition="removeCondition"
@cloneCondition="cloneCondition"
@setMoveIndex="setMoveIndex"
/>
</div>
</div>
</div>
</section>
@@ -85,17 +90,7 @@ export default {
Condition
},
props: {
isEditing: Boolean,
testData: {
type: Object,
required: true,
default: () => {
return {
applied: false,
conditionTestInputs: []
}
}
}
isEditing: Boolean
},
data() {
return {
@@ -104,28 +99,14 @@ export default {
conditionResults: {},
conditions: [],
telemetryObjs: [],
moveIndex: undefined,
isDragging: false,
defaultOutput: undefined,
dragCounter: 0
moveIndex: Number,
isDragging: false
};
},
watch: {
defaultOutput(newOutput, oldOutput) {
this.$emit('updateDefaultOutput', newOutput);
},
testData: {
handler() {
this.updateTestData();
},
deep: true
}
},
destroyed() {
this.composition.off('add', this.addTelemetryObject);
this.composition.off('remove', this.removeTelemetryObject);
if(this.conditionManager) {
this.conditionManager.off('conditionSetResultUpdated', this.handleConditionSetResultUpdated);
this.conditionManager.destroy();
}
if (this.stopObservingForChanges) {
@@ -140,29 +121,23 @@ export default {
this.conditionCollection = this.domainObject.configuration.conditionCollection;
this.observeForChanges();
this.conditionManager = new ConditionManager(this.domainObject, this.openmct);
this.conditionManager.on('conditionSetResultUpdated', this.handleConditionSetResultUpdated);
this.updateDefaultCondition();
this.conditionManager.on('conditionSetResultUpdated', (data) => {
this.$emit('conditionSetResultUpdated', data);
})
},
methods: {
handleConditionSetResultUpdated(data) {
this.$emit('conditionSetResultUpdated', data)
},
observeForChanges() {
this.stopObservingForChanges = this.openmct.objects.observe(this.domainObject, 'configuration.conditionCollection', (newConditionCollection) => {
this.conditionCollection = newConditionCollection;
this.updateDefaultCondition();
});
},
updateDefaultCondition() {
const defaultCondition = this.domainObject.configuration.conditionCollection
.find(conditionConfiguration => conditionConfiguration.isDefault);
this.defaultOutput = defaultCondition.configuration.output;
},
setMoveIndex(index) {
this.moveIndex = index;
this.isDragging = true;
},
dropCondition(targetIndex) {
dropCondition(e) {
let targetIndex = Array.from(document.querySelectorAll('.c-c__drag-ghost')).indexOf(e.target);
if (targetIndex > this.moveIndex) { targetIndex-- } // for 'downward' move
const oldIndexArr = Object.keys(this.conditionCollection);
const move = function (arr, old_index, new_index) {
while (old_index < 0) {
@@ -188,16 +163,25 @@ export default {
}
this.reorder(reorderPlan);
},
dragComplete() {
e.target.classList.remove("dragging");
this.isDragging = false;
},
dragEnter(e) {
if (!this.isDragging) { return }
let targetIndex = Array.from(document.querySelectorAll('.c-c__drag-ghost')).indexOf(e.target);
if (targetIndex > this.moveIndex) { targetIndex-- } // for 'downward' move
if (this.moveIndex === targetIndex) { return }
e.target.classList.add("dragging");
},
dragLeave(e) {
e.target.classList.remove("dragging");
},
addTelemetryObject(domainObject) {
this.telemetryObjs.push(domainObject);
this.$emit('telemetryUpdated', this.telemetryObjs);
},
removeTelemetryObject(identifier) {
let index = this.telemetryObjs.findIndex(obj => {
let index = _.findIndex(this.telemetryObjs, (obj) => {
let objId = this.openmct.objects.makeKeyString(obj.identifier);
let id = this.openmct.objects.makeKeyString(identifier);
return objId === id;
@@ -220,9 +204,6 @@ export default {
},
cloneCondition(data) {
this.conditionManager.cloneCondition(data.condition, data.index);
},
updateTestData() {
this.conditionManager.updateTestData(this.testData);
}
}
}

View File

@@ -1,154 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
<template>
<div class="c-style__condition-desc">
<span v-if="showLabel && condition"
class="c-style__condition-desc__name c-condition__name"
>
{{ condition.configuration.name }}
</span>
<span v-for="(criterionDescription, index) in criterionDescriptions"
:key="criterionDescription"
class="c-style__condition-desc__text"
>
<template v-if="!index">When</template>
{{ criterionDescription }}
<template v-if="index < (criterionDescriptions.length-1)">{{ triggerDescription }}</template>
</span>
</div>
</template>
<script>
import { TRIGGER } from "@/plugins/condition/utils/constants";
import { OPERATIONS } from "@/plugins/condition/utils/operations";
export default {
name: 'ConditionDescription',
inject: [
'openmct'
],
props: {
showLabel: {
type: Boolean,
default: false
},
condition: {
type: Object,
default() {
return undefined;
}
}
},
data() {
return {
criterionDescriptions: [],
triggerDescription: ''
}
},
watch: {
condition: {
handler(val) {
this.getConditionDescription();
},
deep: true
}
},
mounted() {
this.getConditionDescription();
},
methods: {
getTriggerDescription(trigger) {
let description = '';
switch(trigger) {
case TRIGGER.ANY:
case TRIGGER.XOR:
description = 'or';
break;
case TRIGGER.ALL:
case TRIGGER.NOT: description = 'and';
break;
}
return description;
},
getConditionDescription() {
if (this.condition) {
this.triggerDescription = this.getTriggerDescription(this.condition.configuration.trigger);
this.criterionDescriptions = [];
this.condition.configuration.criteria.forEach((criterion, index) => {
this.getCriterionDescription(criterion, index);
});
if (this.condition.isDefault) {
this.criterionDescriptions.splice(0, 0, 'all else fails');
}
} else {
this.criterionDescriptions = [];
}
},
getCriterionDescription(criterion, index) {
if (!criterion.telemetry) {
let description = `Unknown ${criterion.metadata} ${this.getOperatorText(criterion.operation, criterion.input)}`;
this.criterionDescriptions.splice(index, 0, description);
} else if (criterion.telemetry === 'all' || criterion.telemetry === 'any') {
const telemetryDescription = criterion.telemetry === 'all' ? 'All telemetry' : 'Any telemetry';
let description = `${telemetryDescription} ${criterion.metadata} ${this.getOperatorText(criterion.operation, criterion.input)}`;
this.criterionDescriptions.splice(index, 0, description);
} else {
this.openmct.objects.get(criterion.telemetry).then((telemetryObject) => {
if (telemetryObject.type === 'unknown') {
let description = `Unknown ${criterion.metadata} ${this.getOperatorText(criterion.operation, criterion.input)}`;
this.criterionDescriptions.splice(index, 0, description);
} else {
let metadataValue = criterion.metadata;
let inputValue = criterion.input;
if (criterion.metadata) {
this.telemetryMetadata = this.openmct.telemetry.getMetadata(telemetryObject);
const metadataObj = this.telemetryMetadata.valueMetadatas.find((metadata) => metadata.key === criterion.metadata);
if (metadataObj) {
if (metadataObj.name) {
metadataValue = metadataObj.name;
}
if(metadataObj.enumerations && inputValue.length) {
if (metadataObj.enumerations[inputValue[0]] && metadataObj.enumerations[inputValue[0]].string) {
inputValue = [metadataObj.enumerations[inputValue[0]].string];
}
}
}
}
let description = `${telemetryObject.name} ${metadataValue} ${this.getOperatorText(criterion.operation, inputValue)}`;
if (this.criterionDescriptions[index]) {
this.criterionDescriptions[index] = description;
} else {
this.criterionDescriptions.splice(index, 0, description);
}
}
});
}
},
getOperatorText(operationName, values) {
const found = OPERATIONS.find((operation) => operation.name === operationName);
return found ? found.getDescription(values) : '';
}
}
}
</script>

View File

@@ -1,89 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
<template>
<div v-if="conditionErrors.length"
class="c-condition__errors"
>
<div v-for="(error, index) in conditionErrors"
:key="index"
class="u-alert u-alert--block u-alert--with-icon"
>{{ error.message.errorText }} {{ error.additionalInfo }}
</div>
</div>
</template>
<script>
import { ERROR } from "@/plugins/condition/utils/constants";
export default {
name: 'ConditionError',
inject: [
'openmct'
],
props: {
condition: {
type: Object,
default() {
return undefined;
}
}
},
data() {
return {
conditionErrors: []
}
},
mounted() {
this.getConditionErrors();
},
methods: {
getConditionErrors() {
if (this.condition) {
this.condition.configuration.criteria.forEach((criterion, index) => {
this.getCriterionErrors(criterion, index);
});
}
},
getCriterionErrors(criterion, index) {
if (!criterion.telemetry) {
this.conditionErrors.push({
message: ERROR.TELEMETRY_NOT_FOUND,
additionalInfo: ''
});
} else {
if (criterion.telemetry !== 'all' && criterion.telemetry !== 'any') {
this.openmct.objects.get(criterion.telemetry).then((telemetryObject) => {
if (telemetryObject.type === 'unknown') {
this.conditionErrors.push({
message: ERROR.TELEMETRY_NOT_FOUND,
additionalInfo: criterion.telemetry ? `Key: ${this.openmct.objects.makeKeyString(criterion.telemetry)}` : ''
});
}
});
}
}
}
}
}
</script>

View File

@@ -23,33 +23,19 @@
<template>
<div class="c-cs">
<section class="c-cs__current-output c-section">
<div class="c-cs__header c-section__header">
<span class="c-cs__header-label c-section__label">Current Output</span>
</div>
<div class="c-cs__content c-cs__current-output-value">
<span class="c-cs__current-output-value__label">Current Output</span>
<span class="c-cs__current-output-value__value">
<template v-if="currentConditionOutput">
{{ currentConditionOutput }}
</template>
<template v-else>
{{ defaultConditionOutput }}
</template>
</span>
<template v-if="currentConditionOutput">
{{ currentConditionOutput }}
</template>
<template v-else>No output selected</template>
</div>
</section>
<div class="c-cs__test-data-and-conditions-w">
<TestData class="c-cs__test-data"
:is-editing="isEditing"
:test-data="testData"
:telemetry="telemetryObjs"
@updateTestData="updateTestData"
/>
<ConditionCollection class="c-cs__conditions"
:is-editing="isEditing"
:test-data="testData"
@conditionSetResultUpdated="updateCurrentOutput"
@updateDefaultOutput="updateDefaultOutput"
@telemetryUpdated="updateTelemetry"
/>
</div>
<TestData :is-editing="isEditing" />
<ConditionCollection :is-editing="isEditing"
@conditionSetResultUpdated="updateCurrentOutput" />
</div>
</template>
@@ -68,31 +54,20 @@ export default {
},
data() {
return {
currentConditionOutput: '',
defaultConditionOutput: '',
telemetryObjs: [],
testData: {}
currentConditionOutput: ''
}
},
mounted() {
this.conditionSetIdentifier = this.openmct.objects.makeKeyString(this.domainObject.identifier);
this.testData = {
applied: false,
conditionTestInputs: this.domainObject.configuration.conditionTestData || []
};
},
beforeDestroy() {
if (this.stopProvidingTelemetry) {
this.stopProvidingTelemetry();
}
},
methods: {
updateCurrentOutput(currentConditionResult) {
this.currentConditionOutput = currentConditionResult.output;
},
updateDefaultOutput(output) {
this.currentConditionOutput = output;
},
updateTelemetry(telemetryObjs) {
this.telemetryObjs = telemetryObjs;
},
updateTestData(testData) {
this.testData = testData;
}
}
};

View File

@@ -1,41 +1,16 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
<template>
<div class="u-contents">
<div class="c-cdef__separator c-row-separator"></div>
<span class="c-cdef__label">{{ setRowLabel }}</span>
<span class="c-cdef__controls">
<span class="c-cdef__control">
<select ref="telemetrySelect"
v-model="criterion.telemetry"
<select v-model="selectedTelemetry"
@change="updateMetadataOptions"
>
<option value="">- Select Telemetry -</option>
<option value="all">all telemetry</option>
<option value="any">any telemetry</option>
<option v-for="telemetryOption in telemetry"
:key="telemetryOption.identifier.key"
:value="telemetryOption.identifier"
:value="openmct.objects.makeKeyString(telemetryOption.identifier)"
>
{{ telemetryOption.name }}
</option>
@@ -44,8 +19,7 @@
<span v-if="criterion.telemetry"
class="c-cdef__control"
>
<select ref="metadataSelect"
v-model="criterion.metadata"
<select v-model="criterion.metadata"
@change="updateOperations"
>
<option value="">- Select Field -</option>
@@ -61,7 +35,7 @@
class="c-cdef__control"
>
<select v-model="criterion.operation"
@change="updateInputVisibilityAndValues"
@change="updateOperationInputVisibility"
>
<option value="">- Select Comparison -</option>
<option v-for="option in filteredOps"
@@ -71,34 +45,16 @@
{{ option.text }}
</option>
</select>
<template v-if="!enumerations.length">
<span v-for="(item, inputIndex) in inputCount"
:key="inputIndex"
class="c-cdef__control__inputs"
<span v-for="(item, inputIndex) in inputCount"
:key="inputIndex"
class="c-cdef__control__inputs"
>
<input v-model="criterion.input[inputIndex]"
class="c-cdef__control__input"
:type="setInputType"
@blur="persist"
>
<input v-model="criterion.input[inputIndex]"
class="c-cdef__control__input"
:type="setInputType"
@blur="persist"
>
<span v-if="inputIndex < inputCount-1">and</span>
</span>
</template>
<span v-else>
<span v-if="inputCount && criterion.operation"
class="c-cdef__control"
>
<select v-model="criterion.input[0]"
@change="persist"
>
<option v-for="option in enumerations"
:key="option.string"
:value="option.value.toString()"
>
{{ option.string }}
</option>
</select>
</span>
<span v-if="inputIndex < inputCount-1">and</span>
</span>
</span>
</span>
@@ -107,7 +63,6 @@
<script>
import { OPERATIONS } from '../utils/operations';
import { INPUT_TYPES } from '../utils/operations';
export default {
inject: ['openmct'],
@@ -131,14 +86,18 @@ export default {
}
},
data() {
let selectedTelemetry = '';
if (this.criterion.telemetry) {
selectedTelemetry = this.openmct.objects.makeKeyString(this.criterion.telemetry)
}
return {
telemetryMetadataOptions: [],
telemetryMetadata: {},
telemetryMetadataOptions: {},
operations: OPERATIONS,
inputCount: 0,
rowLabel: '',
operationFormat: '',
enumerations: [],
inputTypes: INPUT_TYPES
selectedTelemetry
}
},
computed: {
@@ -147,16 +106,16 @@ export default {
return (this.index !== 0 ? operator : '') + 'when';
},
filteredOps: function () {
return this.operations.filter(op => op.appliesTo.indexOf(this.operationFormat) !== -1);
return [...this.operations.filter(op => op.appliesTo.indexOf(this.operationFormat) !== -1)];
},
setInputType: function () {
let type = '';
for (let i = 0; i < this.filteredOps.length; i++) {
if (this.criterion.operation === this.filteredOps[i].name) {
if (this.filteredOps[i].appliesTo.length) {
type = this.inputTypes[this.filteredOps[i].appliesTo[0]];
if (this.filteredOps[i].appliesTo.length === 1) {
type = this.filteredOps[i].appliesTo[0];
} else {
type = 'text'
type = 'string'
}
break;
}
@@ -164,128 +123,70 @@ export default {
return type;
}
},
watch: {
telemetry: {
handler(newTelemetry, oldTelemetry) {
this.checkTelemetry();
},
deep: true
}
},
mounted() {
this.updateMetadataOptions();
},
methods: {
checkTelemetry() {
if(this.criterion.telemetry) {
if (this.criterion.telemetry === 'any' || this.criterion.telemetry === 'all') {
this.updateMetadataOptions();
} else {
if (!this.telemetry.find((telemetryObj) => this.openmct.objects.areIdsEqual(this.criterion.telemetry, telemetryObj.identifier))) {
//telemetry being used was removed. So reset this criterion.
this.criterion.telemetry = '';
this.criterion.metadata = '';
this.criterion.input = [];
this.criterion.operation = '';
this.persist();
getOperationFormat() {
this.telemetryMetadata.valueMetadatas.forEach((value, index) => {
if (value.key === this.criterion.metadata) {
let valueMetadata = this.telemetryMetadataOptions[index];
if (valueMetadata.enumerations !== undefined) {
this.operationFormat = 'enum';
} else if (valueMetadata.hints.hasOwnProperty('range')) {
this.operationFormat = 'number';
} else if (valueMetadata.hints.hasOwnProperty('domain')) {
this.operationFormat = 'number';
} else if (valueMetadata.key === 'name') {
this.operationFormat = 'string';
} else {
this.operationFormat = 'string';
}
}
}
},
updateOperationFormat() {
this.enumerations = [];
let foundMetadata = this.telemetryMetadataOptions.find((value) => {
return value.key === this.criterion.metadata;
});
if (foundMetadata) {
if (foundMetadata.enumerations !== undefined) {
this.operationFormat = 'enum';
this.enumerations = foundMetadata.enumerations;
} else if (foundMetadata.format === 'string' || foundMetadata.format === 'number') {
this.operationFormat = foundMetadata.format;
} else if (foundMetadata.hints.hasOwnProperty('range')) {
this.operationFormat = 'number';
} else if (foundMetadata.hints.hasOwnProperty('domain')) {
this.operationFormat = 'number';
} else if (foundMetadata.key === 'name') {
this.operationFormat = 'string';
} else {
this.operationFormat = 'number';
}
}
this.updateInputVisibilityAndValues();
},
updateMetadataOptions(ev) {
if (ev) {
this.clearDependentFields(ev.target);
this.persist();
}
if (this.criterion.telemetry) {
const telemetry = (this.criterion.telemetry === 'all' || this.criterion.telemetry === 'any') ? this.telemetry : [{
identifier: this.criterion.telemetry
}];
let telemetryPromises = telemetry.map((telemetryObject) => this.openmct.objects.get(telemetryObject.identifier));
Promise.all(telemetryPromises).then(telemetryObjects => {
this.telemetryMetadataOptions = [];
telemetryObjects.forEach(telemetryObject => {
let telemetryMetadata = this.openmct.telemetry.getMetadata(telemetryObject);
this.addMetaDataOptions(telemetryMetadata.values());
});
if (ev) {this.clearInputs()}
if (this.selectedTelemetry) {
this.openmct.objects.get(this.selectedTelemetry).then((telemetryObject) => {
this.criterion.telemetry.name = telemetryObject.name;
this.telemetryMetadata = this.openmct.telemetry.getMetadata(telemetryObject);
this.telemetryMetadataOptions = this.telemetryMetadata.values();
this.updateOperations();
this.updateOperationInputVisibility();
});
} else {
this.criterion.metadata = '';
}
},
addMetaDataOptions(options) {
if (!this.telemetryMetadataOptions) {
this.telemetryMetadataOptions = options;
}
options.forEach((option) => {
const found = this.telemetryMetadataOptions.find((metadataOption) => {
return (metadataOption.key && (metadataOption.key === option.key)) && (metadataOption.name && (metadataOption.name === option.name))
});
if (!found) {
this.telemetryMetadataOptions.push(option);
}
});
},
updateOperations(ev) {
this.updateOperationFormat();
if (ev) {
this.clearDependentFields(ev.target);
this.persist();
this.criterion.telemetry.fieldName = ev.target.options[ev.target.selectedIndex].text;
this.clearInputs()
}
this.getOperationFormat();
this.persist();
},
updateInputVisibilityAndValues(ev) {
updateOperationInputVisibility(ev) {
if (ev) {
this.clearDependentFields();
this.persist();
this.criterion.input = [];
this.inputCount = 0;
}
for (let i = 0; i < this.filteredOps.length; i++) {
if (this.criterion.operation === this.filteredOps[i].name) {
this.inputCount = this.filteredOps[i].inputCount;
if (!this.inputCount) {this.criterion.input = []}
}
}
if (!this.inputCount) {
this.criterion.input = [];
}
this.persist();
},
clearDependentFields(el) {
if (el === this.$refs.telemetrySelect) {
this.criterion.metadata = '';
} else if (el === this.$refs.metadataSelect) {
if (!this.filteredOps.find(operation => operation.name === this.criterion.operation)) {
this.criterion.operation = '';
this.criterion.input = this.enumerations.length ? [this.enumerations[0].value.toString()] : [];
this.inputCount = 0;
}
} else {
if (this.enumerations.length && !this.criterion.input.length) {
this.criterion.input = [this.enumerations[0].value.toString()];
}
this.inputCount = 0;
}
clearInputs() {
this.criterion.operation = '';
this.criterion.input = [];
this.inputCount = 0;
},
updateMetadataSelection() {
this.updateOperationInputVisibility();
},
persist() {
this.$emit('persist', this.criterion);

View File

@@ -23,6 +23,7 @@
<template>
<section v-show="isEditing"
id="test-data"
class="c-cs__test-data"
:class="{ 'is-expanded': expanded }"
>
<div class="c-cs__header c-section__header">
@@ -36,85 +37,36 @@
<div v-if="expanded"
class="c-cs__content"
>
<div class="c-cs__test-data__controls c-cdef__controls"
:disabled="!telemetry.length"
>
<label class="c-toggle-switch">
<input
type="checkbox"
:checked="isApplied"
@change="applyTestData"
>
<span class="c-toggle-switch__slider"></span>
<span class="c-toggle-switch__label">Apply Test Data</span>
</label>
</div>
<div class="c-cs-tests">
<span v-for="(testInput, tIndex) in testInputs"
:key="tIndex"
class="c-test-datum c-cs-test"
<label class="c-toggle-switch">
<input
type="checkbox"
:checked="isApplied"
@change="applyTestData"
>
<span class="c-cs-test__label">Set</span>
<span class="c-cs-test__controls">
<span class="c-cdef__control">
<select v-model="testInput.telemetry"
@change="updateMetadata(testInput)"
>
<option value="">- Select Telemetry -</option>
<option v-for="(telemetryOption, index) in telemetry"
:key="index"
:value="telemetryOption.identifier"
>
{{ telemetryOption.name }}
</option>
</select>
</span>
<span v-if="testInput.telemetry"
class="c-cdef__control"
>
<select v-model="testInput.metadata"
@change="updateTestData"
>
<option value="">- Select Field -</option>
<option v-for="(option, index) in telemetryMetadataOptions[getId(testInput.telemetry)]"
:key="index"
:value="option.key"
>
{{ option.name }}
</option>
</select>
</span>
<span v-if="testInput.metadata"
class="c-cdef__control__inputs"
>
<input v-model="testInput.value"
placeholder="Enter test input"
type="text"
class="c-cdef__control__input"
@change="updateTestData"
>
</span>
</span>
<div class="c-cs-test__buttons">
<span class="c-toggle-switch__slider"></span>
<span class="c-toggle-switch__label">Apply Test Data</span>
</label>
<div class="c-cs-test-h">
<div v-for="n in 5"
:key="n"
class="c-test-datum"
>
<span class="c-test-datum__label">Set</span>
<div class="c-test-datum__controls">
<select>
<option>- Select Input -</option>
</select>
</div>
<div class="c-test-datum__buttons">
<button class="c-click-icon c-test-data__duplicate-button icon-duplicate"
title="Duplicate this test datum"
@click="addTestInput(testInput)"
title="Duplicate this test data value"
></button>
<button class="c-click-icon c-test-data__delete-button icon-trash"
title="Delete this test datum"
@click="removeTestInput(tIndex)"
title="Delete this test data value"
></button>
</div>
</span>
</div>
</div>
<button
v-show="isEditing"
id="addTestDatum"
class="c-button c-button--major icon-plus labeled"
@click="addTestInput"
>
<span class="c-cs-button__label">Add Test Datum</span>
</button>
</div>
</section>
</template>
@@ -123,113 +75,17 @@
export default {
inject: ['openmct'],
props: {
isEditing: Boolean,
telemetry: {
type: Array,
required: true,
default: () => []
},
testData: {
type: Object,
required: true,
default: () => {
return {
applied: false,
conditionTestInputs: []
}
}
}
isEditing: Boolean
},
data() {
return {
expanded: true,
isApplied: false,
testInputs: [],
telemetryMetadataOptions: {}
isApplied: true
};
},
watch: {
isEditing(editing) {
if (!editing) {
this.resetApplied();
}
},
telemetry: {
handler() {
this.initializeMetadata();
},
deep: true
},
testData: {
handler() {
this.initialize();
},
deep: true
}
},
beforeDestroy() {
this.resetApplied();
},
mounted() {
this.initialize();
this.initializeMetadata();
},
methods: {
applyTestData() {
this.isApplied = !this.isApplied;
this.updateTestData();
},
initialize() {
if (this.testData && this.testData.conditionTestInputs) {
this.testInputs = this.testData.conditionTestInputs;
}
if (!this.testInputs.length) {
this.addTestInput();
}
},
initializeMetadata() {
this.telemetry.forEach((telemetryObject) => {
const id = this.openmct.objects.makeKeyString(telemetryObject.identifier);
let telemetryMetadata = this.openmct.telemetry.getMetadata(telemetryObject);
this.telemetryMetadataOptions[id] = telemetryMetadata.values().slice();
});
},
addTestInput(testInput) {
this.testInputs.push(Object.assign({
telemetry: '',
metadata: '',
input: ''
}, testInput));
},
removeTestInput(index) {
this.testInputs.splice(index, 1);
this.updateTestData();
},
getId(identifier) {
if (identifier) {
return this.openmct.objects.makeKeyString(identifier);
}
return [];
},
updateMetadata(testInput) {
if (testInput.telemetry) {
const id = this.openmct.objects.makeKeyString(testInput.telemetry);
if(this.telemetryMetadataOptions[id]) {
return;
}
let telemetryMetadata = this.openmct.telemetry.getMetadata(testInput);
this.telemetryMetadataOptions[id] = telemetryMetadata.values().slice();
}
},
resetApplied() {
this.isApplied = false;
this.updateTestData();
},
updateTestData() {
this.$emit('updateTestData', {
applied: this.isApplied,
conditionTestInputs: this.testInputs
});
applyTestData(ev) {
this.$emit('change', ev.target.checked);
}
}
}

View File

@@ -0,0 +1,89 @@
.c-cs {
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
&__content {
display: flex;
flex-direction: column;
flex: 1 1 auto;
overflow: hidden;
> * {
flex: 0 0 auto;
overflow: hidden;
+ * {
margin-top: $interiorMarginSm;
}
}
.c-button {
align-self: start;
}
}
.is-editing & {
// Add some space to kick away from blue editing border indication
padding: $interiorMargin;
}
section {
display: flex;
flex-direction: column;
overflow: hidden;
}
&__conditions-h {
display: flex;
flex-direction: column;
flex: 1 1 auto;
overflow: auto;
padding-right: $interiorMarginSm;
> * + * {
margin-top: $interiorMarginSm;
}
}
&__conditions {
> * + * {
margin-top: $interiorMarginSm;
}
}
.hint {
padding: $interiorMarginSm;
}
/************************** SPECIFIC ITEMS */
&__current-output-value {
font-size: 1.25em;
padding: $interiorMargin;
}
}
/***************************** TEST DATA */
.c-cs-test-h {
flex: 1 1 auto;
overflow: auto;
padding-right: $interiorMarginSm;
> * + * {
margin-top: $interiorMarginSm;
}
}
.c-test-datum {
> * {
flex: 0 0 auto;
+ * {
margin-left: $interiorMargin;
}
}
&__controls {
flex: 1 1 auto;
}
}

View File

@@ -0,0 +1,111 @@
.c-condition,
.c-test-datum {
@include discreteItem();
display: flex;
padding: $interiorMargin;
&--edit {
line-height: 160%; // For layout when inputs wrap, like in criteria
}
}
.c-condition {
flex-direction: column;
> * + * {
margin-top: $interiorMarginSm;
}
&--browse {
.c-condition__summary {
border-top: 1px solid $colorInteriorBorder;
padding-top: $interiorMargin;
}
}
/***************************** HEADER */
&__header {
display: flex;
align-items: flex-start;
align-content: stretch;
overflow: hidden;
> * {
flex: 0 0 auto;
+ * {
margin-left: $interiorMarginSm;
}
}
}
&__name {
font-weight: bold;
align-self: baseline; // Fixes bold line-height offset problem
}
&__output,
&__summary {
flex: 1 1 auto;
}
}
/***************************** CONDITION DEFINITION, EDITING */
.c-cdef {
display: grid;
grid-row-gap: $interiorMarginSm;
grid-column-gap: $interiorMargin;
grid-auto-columns: min-content 1fr max-content;
align-items: start;
min-width: 150px;
margin-left: 29px;
overflow: hidden;
&__criteria,
&__match-and-criteria {
display: contents;
}
&__label {
grid-column: 1;
text-align: right;
white-space: nowrap;
}
&__separator {
grid-column: 1 / span 3;
}
&__controls {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
//line-height: 200%;
grid-column: 2;
> * > * {
margin-right: $interiorMarginSm;
}
}
&__buttons {
grid-column: 3;
}
}
.c-c__drag-ghost {
width: 100%;
min-height: $interiorMarginSm;
//&:before {
// @include test();
// content: '';
// display: block;
// z-index: 2;
//}
&.dragging {
min-height: 5em;
//border: solid 1px blue;
background-color: lightblue;
border-radius: 2px;
}
}

View File

@@ -1,311 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, 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.
*****************************************************************************/
/***************************** DRAGGING */
.is-active-dragging {
.c-condition-h__drop-target {
height: 3px;
margin-bottom: $interiorMarginSm;
}
}
.c-condition-h {
&__drop-target {
border-radius: $controlCr;
height: 0;
min-height: 0;
transition: background-color, height;
transition-duration: 150ms;
}
&.is-drag-target {
.c-condition > * {
pointer-events: none; // Keeps the JS drop handler from being intercepted by internal elements
}
.c-condition-h__drop-target {
background-color: rgba($colorKey, 0.7);
}
}
}
.c-cs {
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
/************************** CONDITION SET LAYOUT */
&__current-output {
flex: 0 0 auto;
}
&__test-data-and-conditions-w {
display: flex;
flex-direction: column;
flex: 1 1 auto;
height: 100%;
overflow: hidden;
}
&__test-data,
&__conditions {
flex: 0 0 auto;
overflow: hidden;
}
&__test-data {
flex: 0 0 auto;
max-height: 50%;
&.is-expanded {
margin-bottom: $interiorMargin * 4;
}
}
&__conditions {
flex: 1 1 auto;
> * + * {
margin-top: $interiorMarginSm;
}
}
&__content {
display: flex;
flex-direction: column;
flex: 0 1 auto;
overflow: hidden;
> * {
flex: 0 0 auto;
overflow: hidden;
+ * {
margin-top: $interiorMarginSm;
}
}
.c-button {
align-self: start;
}
}
.is-editing & {
// Add some space to kick away from blue editing border indication
padding: $interiorMargin;
}
section {
display: flex;
flex-direction: column;
overflow: hidden;
}
&__conditions-h {
display: flex;
flex-direction: column;
flex: 1 1 auto;
overflow: auto;
padding-right: $interiorMarginSm;
> * + * {
margin-top: $interiorMarginSm;
}
}
.hint {
padding: $interiorMarginSm;
}
/************************** SPECIFIC ITEMS */
&__current-output-value {
flex-direction: row;
align-items: baseline;
padding: 0 $interiorMargin $interiorMarginLg $interiorMargin;
> * {
padding: $interiorMargin 0; // Must do this to align label and value
}
&__label {
color: $colorInspectorSectionHeaderFg;
opacity: 0.9;
text-transform: uppercase;
}
&__value {
$p: $interiorMargin * 3;
font-size: 1.25em;
margin-left: $interiorMargin;
padding-left: $p;
padding-right: $p;
background: rgba(black, 0.2);
border-radius: 5px;
}
}
}
/***************************** CONDITIONS AND TEST DATUM ELEMENTS */
.c-condition,
.c-test-datum {
@include discreteItem();
display: flex;
padding: $interiorMargin;
line-height: 170%; // Aligns text with controls like selects
}
.c-cdef,
.c-cs-test {
&__controls {
display: flex;
flex: 1 1 auto;
flex-wrap: wrap;
> * > * {
margin-right: $interiorMarginSm;
}
}
&__buttons {
white-space: nowrap;
}
}
.c-condition {
flex-direction: column;
min-width: 400px;
> * + * {
margin-top: $interiorMarginSm;
}
&--browse {
.c-condition__summary {
border-top: 1px solid $colorInteriorBorder;
padding-top: $interiorMargin;
}
}
/***************************** HEADER */
&__header {
$h: 22px;
display: flex;
align-items: start;
align-content: stretch;
overflow: hidden;
min-height: $h;
line-height: $h;
> * {
flex: 0 0 auto;
+ * {
margin-left: $interiorMarginSm;
}
}
}
&__drag-grippy {
transform: translateY(50%);
}
&__name {
font-weight: bold;
align-self: baseline; // Fixes bold line-height offset problem
}
&__output,
&__summary {
flex: 1 1 auto;
}
}
/***************************** CONDITION DEFINITION, EDITING */
.c-cdef {
display: grid;
grid-row-gap: $interiorMarginSm;
grid-column-gap: $interiorMargin;
grid-auto-columns: min-content 1fr max-content;
align-items: start;
min-width: 150px;
margin-left: 29px;
overflow: hidden;
&__criteria,
&__match-and-criteria {
display: contents;
}
&__label {
grid-column: 1;
text-align: right;
white-space: nowrap;
}
&__separator {
grid-column: 1 / span 3;
}
&__controls {
align-items: flex-start;
grid-column: 2;
> * > * {
margin-right: $interiorMarginSm;
}
}
&__buttons {
grid-column: 3;
}
}
.c-c__drag-ghost {
width: 100%;
min-height: $interiorMarginSm;
&.dragging {
min-height: 5em;
background-color: lightblue;
border-radius: 2px;
}
}
/***************************** TEST DATA */
.c-cs__test-data {
&__controls {
flex: 0 0 auto;
}
}
.c-cs-tests {
flex: 0 1 auto;
overflow: auto;
padding-right: $interiorMarginSm;
> * + * {
margin-top: $interiorMarginSm;
}
}
.c-cs-test {
> * + * {
margin-left: $interiorMargin;
}
}

View File

@@ -1,185 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
<template>
<li class="c-tree__item-h">
<div
class="c-tree__item"
:class="{ 'is-alias': isAlias, 'is-navigated-object': navigated }"
@click="handleItemSelected(node.object, node)"
>
<view-control
v-model="expanded"
class="c-tree__item__view-control"
:enabled="hasChildren"
:propagate="false"
/>
<div class="c-tree__item__label c-object-label">
<div
class="c-tree__item__type-icon c-object-label__type-icon"
:class="typeClass"
></div>
<div class="c-tree__item__name c-object-label__name">{{ node.object.name }}</div>
</div>
</div>
<ul
v-if="expanded"
class="c-tree"
>
<li
v-if="isLoading && !loaded"
class="c-tree__item-h"
>
<div class="c-tree__item loading">
<span class="c-tree__item__label">Loading...</span>
</div>
</li>
<condition-set-dialog-tree-item
v-for="child in children"
:key="child.id"
:node="child"
:selected-item="selectedItem"
:handle-item-selected="handleItemSelected"
/>
</ul>
</li>
</template>
<script>
import viewControl from '@/ui/components/viewControl.vue';
export default {
name: 'ConditionSetDialogTreeItem',
inject: ['openmct'],
components: {
viewControl
},
props: {
node: {
type: Object,
required: true
},
selectedItem: {
type: Object,
default() {
return undefined;
}
},
handleItemSelected: {
type: Function,
default() {
return (item) => {};
}
}
},
data() {
return {
hasChildren: false,
isLoading: false,
loaded: false,
children: [],
expanded: false
}
},
computed: {
navigated() {
const itemId = this.selectedItem && this.selectedItem.itemId;
const isSelectedObject = itemId && this.openmct.objects.areIdsEqual(this.node.object.identifier, itemId);
if (isSelectedObject && this.node.objectPath && this.node.objectPath.length > 1) {
const isParent = this.openmct.objects.areIdsEqual(this.node.objectPath[1].identifier, this.selectedItem.parentId);
return isSelectedObject && isParent;
}
return isSelectedObject;
},
isAlias() {
let parent = this.node.objectPath[1];
if (!parent) {
return false;
}
let parentKeyString = this.openmct.objects.makeKeyString(parent.identifier);
return parentKeyString !== this.node.object.location;
},
typeClass() {
let type = this.openmct.types.get(this.node.object.type);
if (!type) {
return 'icon-object-unknown';
}
return type.definition.cssClass;
}
},
watch: {
expanded() {
if (!this.hasChildren) {
return;
}
if (!this.loaded && !this.isLoading) {
this.composition = this.openmct.composition.get(this.domainObject);
this.composition.on('add', this.addChild);
this.composition.on('remove', this.removeChild);
this.composition.load().then(this.finishLoading);
this.isLoading = true;
}
}
},
mounted() {
this.domainObject = this.node.object;
let removeListener = this.openmct.objects.observe(this.domainObject, '*', (newObject) => {
this.domainObject = newObject;
});
this.$once('hook:destroyed', removeListener);
if (this.openmct.composition.get(this.node.object)) {
this.hasChildren = true;
}
},
beforeDestroy() {
this.expanded = false;
},
destroyed() {
if (this.composition) {
this.composition.off('add', this.addChild);
this.composition.off('remove', this.removeChild);
delete this.composition;
}
},
methods: {
addChild(child) {
this.children.push({
id: this.openmct.objects.makeKeyString(child.identifier),
object: child,
objectPath: [child].concat(this.node.objectPath),
navigateToParent: this.navigateToPath
});
},
removeChild(identifier) {
let removeId = this.openmct.objects.makeKeyString(identifier);
this.children = this.children
.filter(c => c.id !== removeId);
},
finishLoading() {
this.isLoading = false;
this.loaded = true;
}
}
}
</script>

View File

@@ -1,172 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
<template>
<div class="u-contents">
<div class="c-overlay__top-bar">
<div class="c-overlay__dialog-title">Select Condition Set</div>
</div>
<div class="c-overlay__contents-main c-selector c-tree-and-search">
<div class="c-tree-and-search__search">
<search ref="shell-search"
class="c-search"
:value="searchValue"
@input="searchTree"
@clear="searchTree"
/>
</div>
<!-- loading -->
<div v-if="isLoading"
class="c-tree-and-search__loading loading"
></div>
<!-- end loading -->
<div v-if="(allTreeItems.length === 0) || (searchValue && filteredTreeItems.length === 0)"
class="c-tree-and-search__no-results"
>
No results found
</div>
<!-- main tree -->
<ul v-if="!isLoading"
v-show="!searchValue"
class="c-tree-and-search__tree c-tree"
>
<condition-set-dialog-tree-item
v-for="treeItem in allTreeItems"
:key="treeItem.id"
:node="treeItem"
:selected-item="selectedItem"
:handle-item-selected="handleItemSelection"
/>
</ul>
<!-- end main tree -->
<!-- search tree -->
<ul v-if="searchValue"
class="c-tree-and-search__tree c-tree"
>
<condition-set-dialog-tree-item
v-for="treeItem in filteredTreeItems"
:key="treeItem.id"
:node="treeItem"
:selected-item="selectedItem"
:handle-item-selected="handleItemSelection"
/>
</ul>
<!-- end search tree -->
</div>
</div>
</template>
<script>
import search from '@/ui/components/search.vue';
import ConditionSetDialogTreeItem from './ConditionSetDialogTreeItem.vue';
export default {
inject: ['openmct'],
name: 'ConditionSetSelectorDialog',
components: {
search,
ConditionSetDialogTreeItem
},
data() {
return {
expanded: false,
searchValue: '',
allTreeItems: [],
filteredTreeItems: [],
isLoading: false,
selectedItem: undefined
}
},
mounted() {
this.searchService = this.openmct.$injector.get('searchService');
this.getAllChildren();
},
methods: {
getAllChildren() {
this.isLoading = true;
this.openmct.objects.get('ROOT')
.then(root => {
return this.openmct.composition.get(root).load()
})
.then(children => {
this.isLoading = false;
this.allTreeItems = children.map(c => {
return {
id: this.openmct.objects.makeKeyString(c.identifier),
object: c,
objectPath: [c],
navigateToParent: '/browse'
};
});
});
},
getFilteredChildren() {
this.searchService.query(this.searchValue).then(children => {
this.filteredTreeItems = children.hits.map(child => {
let context = child.object.getCapability('context'),
object = child.object.useCapability('adapter'),
objectPath = [],
navigateToParent;
if (context) {
objectPath = context.getPath().slice(1)
.map(oldObject => oldObject.useCapability('adapter'))
.reverse();
navigateToParent = '/browse/' + objectPath.slice(1)
.map((parent) => this.openmct.objects.makeKeyString(parent.identifier))
.join('/');
}
return {
id: this.openmct.objects.makeKeyString(object.identifier),
object,
objectPath,
navigateToParent
}
});
});
},
searchTree(value) {
this.searchValue = value;
if (this.searchValue !== '') {
this.getFilteredChildren();
}
},
handleItemSelection(item, node) {
if (item && item.type === 'conditionSet') {
const parentId = (node.objectPath && node.objectPath.length > 1) ? node.objectPath[1].identifier : undefined;
this.selectedItem = {
itemId: item.identifier,
parentId
};
this.$emit('conditionSetSelected', item);
}
}
}
}
</script>

View File

@@ -0,0 +1,69 @@
<template>
<div>
<div v-if="conditionStyle"
class="holder c-c-button-wrapper align-left"
>
<div>{{ conditionStyle.conditionName }}</div>
<span :style="conditionStyle.style">ABC</span>
<toolbar-color-picker v-if="conditionStyle.style.border"
:options="borderColorOption"
@change="updateStyleValue"
/>
<toolbar-color-picker v-if="conditionStyle.style.backgroundColor"
:options="backgroundColorOption"
@change="updateStyleValue"
/>
</div>
</div>
</template>
<script>
import ToolbarColorPicker from "@/ui/toolbar/components/toolbar-color-picker.vue";
export default {
components: {
ToolbarColorPicker
},
inject: [
'openmct'
],
props: {
conditionStyle: {
type: Object,
required: true
}
},
data() {
return {
condition: null
}
},
computed: {
borderColorOption() {
return {
icon: 'icon-line-horz',
title: 'Set border color',
value: this.conditionStyle.style.border.replace('1px solid ', ''),
property: 'border'
}
},
backgroundColorOption() {
return {
icon: 'icon-paint-bucket',
title: 'Set background color',
value: this.conditionStyle.style.backgroundColor,
property: 'backgroundColor'
}
}
},
methods: {
updateStyleValue(value, item) {
if (item.property === 'border') {
value = '1px solid ' + value;
}
this.conditionStyle.style[item.property] = value;
this.$emit('persist', this.conditionStyle)
}
}
}
</script>

View File

@@ -1,400 +1,164 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
<template>
<div class="c-inspector__styles c-inspect-styles">
<template v-if="!conditionSetDomainObject">
<div class="c-inspect-styles__header">
Object Style
</div>
<div class="c-inspect-styles__content">
<div v-if="staticStyle"
class="c-inspect-styles__style"
>
<style-editor class="c-inspect-styles__editor"
:style-item="staticStyle"
:is-editing="isEditing"
@persist="updateStaticStyle"
/>
</div>
<div>
<div v-if="!conditionalStyles.length"
class="holder c-c-button-wrapper align-left"
>
<button
class="c-c-button c-c-button--minor add-criteria-button"
@click="addConditionSet"
>
<span class="c-c-button__label">Use conditional styling</span>
</button>
</div>
<div v-else>
<div class="holder c-c-button-wrapper align-left">
<button
id="addConditionSet"
class="c-button c-button--major c-toggle-styling-button labeled"
@click="addConditionSet"
class="c-c-button c-c-button--minor add-criteria-button"
@click="removeConditionSet"
>
<span class="c-cs-button__label">Use Conditional Styling...</span>
<span class="c-c-button__label">Remove conditional styling</span>
</button>
</div>
</template>
<template v-else>
<div class="c-inspect-styles__header">
Conditional Object Styles
</div>
<div class="c-inspect-styles__content c-inspect-styles__condition-set">
<a v-if="conditionSetDomainObject"
class="c-object-label icon-conditional"
:href="navigateToPath"
@click="navigateOrPreview"
<ul>
<li v-for="conditionStyle in conditionalStyles"
:key="conditionStyle.conditionId"
>
<span class="c-object-label__name">{{ conditionSetDomainObject.name }}</span>
</a>
<template v-if="isEditing">
<button
id="changeConditionSet"
class="c-button labeled"
@click="addConditionSet"
>
<span class="c-button__label">Change...</span>
</button>
<button class="c-click-icon icon-x"
title="Remove conditional styles"
@click="removeConditionSet"
></button>
</template>
</div>
<div v-if="conditionsLoaded"
class="c-inspect-styles__conditions"
>
<div v-for="(conditionStyle, index) in conditionalStyles"
:key="index"
class="c-inspect-styles__condition"
>
<condition-error :show-label="true"
:condition="getCondition(conditionStyle.conditionId)"
<conditional-style :condition-style="conditionStyle"
@persist="updateConditionalStyle"
/>
<condition-description :show-label="true"
:condition="getCondition(conditionStyle.conditionId)"
/>
<style-editor class="c-inspect-styles__editor"
:style-item="conditionStyle"
:is-editing="isEditing"
@persist="updateConditionalStyle"
/>
</div>
</div>
</template>
</li>
</ul>
</div>
</div>
</template>
<script>
import StyleEditor from "./StyleEditor.vue";
import ConditionSetSelectorDialog from "./ConditionSetSelectorDialog.vue";
import ConditionDescription from "@/plugins/condition/components/ConditionDescription.vue";
import ConditionError from "@/plugins/condition/components/ConditionError.vue";
import Vue from 'vue';
import PreviewAction from "@/ui/preview/PreviewAction.js";
import {getApplicableStylesForItem} from "@/plugins/condition/utils/styleUtils";
import ConditionalStyle from "./ConditionalStyle.vue";
export default {
name: 'ConditionalStylesView',
components: {
ConditionDescription,
ConditionError,
StyleEditor
ConditionalStyle
},
inject: [
'openmct',
'selection'
'domainObject'
],
props: {
itemId: {
type: String,
default: ''
},
initialStyles: {
type: Object,
default() {
return undefined;
}
}
},
data() {
return {
conditionalStyles: [],
staticStyle: undefined,
conditionSetDomainObject: undefined,
isEditing: this.openmct.editor.isEditing(),
conditions: undefined,
conditionsLoaded: false,
navigateToPath: ''
conditionalStyles: []
}
},
destroyed() {
this.removeListeners();
},
mounted() {
this.itemId = '';
this.getDomainObjectFromSelection();
this.previewAction = new PreviewAction(this.openmct);
if (this.domainObject.configuration && this.domainObject.configuration.objectStyles) {
let objectStyles = this.itemId ? this.domainObject.configuration.objectStyles[this.itemId] : this.domainObject.configuration.objectStyles;
this.initializeStaticStyle(objectStyles);
if (objectStyles && objectStyles.conditionSetIdentifier) {
this.openmct.objects.get(objectStyles.conditionSetIdentifier).then(this.initialize);
this.conditionalStyles = objectStyles.styles;
if (this.domainObject.configuration && this.domainObject.configuration.conditionalStyle) {
if (this.itemId) {
let conditionalStyle = this.domainObject.configuration.conditionalStyle[this.itemId];
if (conditionalStyle) {
this.conditionalStyles = conditionalStyle.styles || [];
}
} else {
this.conditionalStyles = this.domainObject.configuration.conditionalStyle.styles || [];
}
} else {
this.initializeStaticStyle();
}
this.openmct.editor.on('isEditing', this.setEditState);
},
methods: {
isItemType(type, item) {
return item && (item.type === type);
},
getDomainObjectFromSelection() {
let layoutItem;
let domainObject;
if (this.selection[0].length > 1) {
//If there are more than 1 items in the this.selection[0] list, the first one could either be a sub domain object OR a layout drawing control.
//The second item in the this.selection[0] list is the container object (usually a layout)
layoutItem = this.selection[0][0].context.layoutItem;
const item = this.selection[0][0].context.item;
this.canHide = true;
if (item &&
(!layoutItem || (this.isItemType('subobject-view', layoutItem)))) {
domainObject = item;
} else {
domainObject = this.selection[0][1].context.item;
if (layoutItem) {
this.itemId = layoutItem.id;
}
}
} else {
domainObject = this.selection[0][0].context.item;
}
this.domainObject = domainObject;
this.initialStyles = getApplicableStylesForItem(domainObject, layoutItem);
this.$nextTick(() => {
this.removeListeners();
if (this.domainObject) {
this.stopObserving = this.openmct.objects.observe(this.domainObject, '*', newDomainObject => this.domainObject = newDomainObject);
this.stopObservingItems = this.openmct.objects.observe(this.domainObject, 'configuration.items', this.updateDomainObjectItemStyles);
}
});
},
removeListeners() {
if (this.stopObserving) {
this.stopObserving();
}
if (this.stopObservingItems) {
this.stopObservingItems();
}
},
initialize(conditionSetDomainObject) {
//If there are new conditions in the conditionSet we need to set those styles to default
this.conditionSetDomainObject = conditionSetDomainObject;
this.enableConditionSetNav();
addConditionSet() {
//TODO: this.conditionSetIdentifier will be set by the UI before calling this
this.conditionSetIdentifier = {
namespace: '',
key: "81088c8a-4b80-41fe-9d07-fda8b22d6f5f"
};
this.initializeConditionalStyles();
},
setEditState(isEditing) {
this.isEditing = isEditing;
},
addConditionSet() {
let conditionSetDomainObject;
const handleItemSelection = (item) => {
if (item) {
conditionSetDomainObject = item;
}
};
const dismissDialog = (overlay, initialize) => {
overlay.dismiss();
if (initialize && conditionSetDomainObject) {
this.conditionSetDomainObject = conditionSetDomainObject;
this.conditionalStyles = [];
this.initializeConditionalStyles();
}
};
let vm = new Vue({
provide: {
openmct: this.openmct
},
components: {ConditionSetSelectorDialog},
data() {
return {
handleItemSelection
}
},
template: '<condition-set-selector-dialog @conditionSetSelected="handleItemSelection"></condition-set-selector-dialog>'
}).$mount();
let overlay = this.openmct.overlays.overlay({
element: vm.$el,
size: 'small',
buttons: [
{
label: 'OK',
emphasis: 'true',
callback: () => dismissDialog(overlay, true)
},
{
label: 'Cancel',
callback: () => dismissDialog(overlay, false)
}
],
onDestroy: () => vm.$destroy()
});
},
enableConditionSetNav() {
this.openmct.objects.getOriginalPath(this.conditionSetDomainObject.identifier).then(
(objectPath) => {
this.objectPath = objectPath;
this.navigateToPath = '#/browse/' + this.objectPath
.map(o => o && this.openmct.objects.makeKeyString(o.identifier))
.reverse()
.join('/');
}
);
},
navigateOrPreview(event) {
// If editing, display condition set in Preview overlay; otherwise nav to it while browsing
if (this.openmct.editor.isEditing()) {
event.preventDefault();
this.previewAction.invoke(this.objectPath);
}
},
removeConditionSet() {
this.conditionSetDomainObject = undefined;
//TODO: Handle the case where domainObject has items with styles but we're trying to remove the styles on the domainObject itself
this.conditionSetIdentifier = '';
this.conditionalStyles = [];
let domainObjectStyles = (this.domainObject.configuration && this.domainObject.configuration.objectStyles) || {};
if (this.itemId) {
domainObjectStyles[this.itemId].conditionSetIdentifier = undefined;
delete domainObjectStyles[this.itemId].conditionSetIdentifier;
domainObjectStyles[this.itemId].styles = undefined;
delete domainObjectStyles[this.itemId].styles;
if (_.isEmpty(domainObjectStyles[this.itemId])) {
delete domainObjectStyles[this.itemId];
let domainObjectConditionalStyle = (this.domainObject.configuration && this.domainObject.configuration.conditionalStyle) || {};
if (domainObjectConditionalStyle) {
if (this.itemId) {
domainObjectConditionalStyle[this.itemId] = undefined;
delete domainObjectConditionalStyle[this.itemId];
} else {
domainObjectConditionalStyle.conditionSetIdentifier = undefined;
delete domainObjectConditionalStyle.conditionSetIdentifier;
domainObjectConditionalStyle.styles = undefined;
delete domainObjectConditionalStyle.styles;
}
if (_.isEmpty(domainObjectConditionalStyle)) {
domainObjectConditionalStyle = undefined;
}
} else {
domainObjectStyles.conditionSetIdentifier = undefined;
delete domainObjectStyles.conditionSetIdentifier;
domainObjectStyles.styles = undefined;
delete domainObjectStyles.styles;
}
if (_.isEmpty(domainObjectStyles)) {
domainObjectStyles = undefined;
}
this.persist(domainObjectStyles);
},
updateDomainObjectItemStyles(newItems) {
//check that all items that have been styles still exist. Otherwise delete those styles
let domainObjectStyles = (this.domainObject.configuration && this.domainObject.configuration.objectStyles) || {};
let itemsToRemove = [];
let keys = Object.keys(domainObjectStyles);
keys.forEach((key) => {
if ((key !== 'styles') &&
(key !== 'staticStyle') &&
(key !== 'conditionSetIdentifier')) {
if (!(newItems.find(item => item.id === key))) {
itemsToRemove.push(key);
}
}
});
if (itemsToRemove.length) {
this.removeItemStyles(itemsToRemove, domainObjectStyles);
}
},
removeItemStyles(itemIds, domainObjectStyles) {
itemIds.forEach(itemId => {
if (domainObjectStyles[itemId]) {
domainObjectStyles[itemId] = undefined;
delete domainObjectStyles[this.itemId];
}
});
if (_.isEmpty(domainObjectStyles)) {
domainObjectStyles = undefined;
}
this.persist(domainObjectStyles);
this.persist(domainObjectConditionalStyle);
},
initializeConditionalStyles() {
if (!this.conditions) {
this.conditions = {};
}
let conditionalStyles = [];
this.conditionSetDomainObject.configuration.conditionCollection.forEach((conditionConfiguration, index) => {
this.conditions[conditionConfiguration.id] = conditionConfiguration;
let foundStyle = this.findStyleByConditionId(conditionConfiguration.id);
if (foundStyle) {
foundStyle.style = Object.assign((this.canHide ? { isStyleInvisible: '' } : {}), this.initialStyles, foundStyle.style);
conditionalStyles.push(foundStyle);
} else {
conditionalStyles.splice(index, 0, {
this.openmct.objects.get(this.conditionSetIdentifier).then((conditionSetDomainObject) => {
conditionSetDomainObject.configuration.conditionCollection.forEach((conditionConfiguration, index) => {
this.conditionalStyles.push({
conditionId: conditionConfiguration.id,
style: Object.assign((this.canHide ? { isStyleInvisible: '' } : {}), this.initialStyles)
conditionName: conditionConfiguration.name,
style: Object.assign({}, this.initialStyles)
});
});
let domainObjectConditionalStyle = (this.domainObject.configuration && this.domainObject.configuration.conditionalStyle) || {};
let conditionalStyle = {
conditionSetIdentifier: this.conditionSetIdentifier,
styles: this.conditionalStyles
};
if (this.itemId) {
this.persist({
...domainObjectConditionalStyle,
[this.itemId]: conditionalStyle
});
} else {
this.persist({
...domainObjectConditionalStyle,
...conditionalStyle
});
}
});
//we're doing this so that we remove styles for any conditions that have been removed from the condition set
this.conditionalStyles = conditionalStyles;
this.conditionsLoaded = true;
this.persist(this.getDomainObjectConditionalStyle());
},
initializeStaticStyle(objectStyles) {
let staticStyle = objectStyles && objectStyles.staticStyle;
if (staticStyle) {
this.staticStyle = {
style: Object.assign({}, this.initialStyles, staticStyle.style)
};
} else {
this.staticStyle = {
style: Object.assign({}, this.initialStyles)
};
}
},
findStyleByConditionId(id) {
return this.conditionalStyles.find(conditionalStyle => conditionalStyle.conditionId === id);
},
updateStaticStyle(staticStyle) {
this.staticStyle = staticStyle;
this.persist(this.getDomainObjectConditionalStyle());
},
updateConditionalStyle(conditionStyle) {
let found = this.findStyleByConditionId(conditionStyle.conditionId);
if (found) {
found.style = conditionStyle.style;
this.persist(this.getDomainObjectConditionalStyle());
}
},
getDomainObjectConditionalStyle() {
let objectStyle = {
styles: this.conditionalStyles,
staticStyle: this.staticStyle
};
if (this.conditionSetDomainObject) {
objectStyle.conditionSetIdentifier = this.conditionSetDomainObject.identifier;
}
let domainObjectConditionalStyle = this.domainObject.configuration.conditionalStyle || {};
let domainObjectStyles = (this.domainObject.configuration && this.domainObject.configuration.objectStyles) || {};
if (this.itemId) {
domainObjectStyles[this.itemId] = objectStyle;
} else {
//we're deconstructing here to ensure that if an item within a domainObject already had a style we don't lose it
domainObjectStyles = {
...domainObjectStyles,
...objectStyle
if (this.itemId) {
let itemConditionalStyle = domainObjectConditionalStyle[this.itemId];
if (itemConditionalStyle) {
this.persist({
...domainObjectConditionalStyle,
[this.itemId]: {
...itemConditionalStyle,
styles: this.conditionalStyles
}
});
}
} else {
domainObjectConditionalStyle.styles = this.conditionalStyles;
this.persist(domainObjectConditionalStyle);
}
}
return domainObjectStyles;
},
getCondition(id) {
return this.conditions ? this.conditions[id] : {};
},
persist(style) {
this.openmct.objects.mutate(this.domainObject, 'configuration.objectStyles', style);
persist(conditionalStyle) {
this.openmct.objects.mutate(this.domainObject, 'configuration.conditionalStyle', conditionalStyle);
}
}
}

View File

@@ -1,269 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
<template>
<div class="c-inspector__styles c-inspect-styles">
<div class="c-inspect-styles__header">
Object Style
</div>
<div class="c-inspect-styles__content">
<div v-if="isStaticAndConditionalStyles"
class="c-inspect-styles__mixed-static-and-conditional u-alert u-alert--block u-alert--with-icon"
>
Your selection includes one or more items that use Conditional Styling. Applying a static style below will replace any Conditional Styling with the new choice.
</div>
<div v-if="staticStyle"
class="c-inspect-styles__style"
>
<style-editor class="c-inspect-styles__editor"
:style-item="staticStyle"
:is-editing="isEditing"
:mixed-styles="mixedStyles"
@persist="updateStaticStyle"
/>
</div>
</div>
</div>
</template>
<script>
import StyleEditor from "./StyleEditor.vue";
import PreviewAction from "@/ui/preview/PreviewAction.js";
import { getApplicableStylesForItem, getConsolidatedStyleValues, getConditionalStyleForItem } from "@/plugins/condition/utils/styleUtils";
export default {
name: 'MultiSelectStylesView',
components: {
StyleEditor
},
inject: [
'openmct',
'selection'
],
data() {
return {
staticStyle: undefined,
isEditing: this.openmct.editor.isEditing(),
mixedStyles: [],
isStaticAndConditionalStyles: false
}
},
destroyed() {
this.removeListeners();
},
mounted() {
this.items = [];
this.previewAction = new PreviewAction(this.openmct);
this.getObjectsAndItemsFromSelection();
this.initializeStaticStyle();
this.openmct.editor.on('isEditing', this.setEditState);
},
methods: {
isItemType(type, item) {
return item && (item.type === type);
},
hasConditionalStyles(domainObject, id) {
return getConditionalStyleForItem(domainObject, id) !== undefined;
},
getObjectsAndItemsFromSelection() {
let domainObject;
let subObjects = [];
//multiple selection
let itemInitialStyles = [];
let itemStyle;
this.selection.forEach((selectionItem) => {
const item = selectionItem[0].context.item;
const layoutItem = selectionItem[0].context.layoutItem;
if (item && this.isItemType('subobject-view', layoutItem)) {
subObjects.push(item);
itemStyle = getApplicableStylesForItem(item);
if (!this.isStaticAndConditionalStyles) {
this.isStaticAndConditionalStyles = this.hasConditionalStyles(item);
}
} else {
domainObject = selectionItem[1].context.item;
itemStyle = getApplicableStylesForItem(domainObject, layoutItem || item);
this.items.push({
id: layoutItem.id,
applicableStyles: itemStyle
});
if (!this.isStaticAndConditionalStyles) {
this.isStaticAndConditionalStyles = this.hasConditionalStyles(domainObject, layoutItem.id);
}
}
itemInitialStyles.push(itemStyle);
});
const {styles, mixedStyles} = getConsolidatedStyleValues(itemInitialStyles);
this.initialStyles = styles;
this.mixedStyles = mixedStyles;
this.domainObject = domainObject;
this.removeListeners();
if (this.domainObject) {
this.stopObserving = this.openmct.objects.observe(this.domainObject, '*', newDomainObject => this.domainObject = newDomainObject);
this.stopObservingItems = this.openmct.objects.observe(this.domainObject, 'configuration.items', this.updateDomainObjectItemStyles);
}
subObjects.forEach(this.registerListener);
},
updateDomainObjectItemStyles(newItems) {
//check that all items that have been styles still exist. Otherwise delete those styles
let keys = Object.keys(this.domainObject.configuration.objectStyles || {});
keys.forEach((key) => {
if ((key !== 'styles') &&
(key !== 'staticStyle') &&
(key !== 'conditionSetIdentifier')) {
if (!(newItems.find(item => item.id === key))) {
this.removeItemStyles(key);
}
}
});
},
registerListener(domainObject) {
let id = this.openmct.objects.makeKeyString(domainObject.identifier);
if (!this.domainObjectsById) {
this.domainObjectsById = {};
}
if (!this.domainObjectsById[id]) {
this.domainObjectsById[id] = domainObject;
this.observeObject(domainObject, id);
}
},
observeObject(domainObject, id) {
let unobserveObject = this.openmct.objects.observe(domainObject, '*', function (newObject) {
this.domainObjectsById[id] = JSON.parse(JSON.stringify(newObject));
}.bind(this));
this.unObserveObjects.push(unobserveObject);
},
removeListeners() {
if (this.stopObserving) {
this.stopObserving();
}
if (this.stopObservingItems) {
this.stopObservingItems();
}
if (this.unObserveObjects) {
this.unObserveObjects.forEach((unObserveObject) => {
unObserveObject();
});
}
this.unObserveObjects = [];
},
removeItemStyles(itemId) {
let domainObjectStyles = (this.domainObject.configuration && this.domainObject.configuration.objectStyles) || {};
if (itemId && domainObjectStyles[itemId]) {
domainObjectStyles[itemId] = undefined;
delete domainObjectStyles[this.itemId];
if (_.isEmpty(domainObjectStyles)) {
domainObjectStyles = undefined;
}
this.persist(this.domainObject, domainObjectStyles);
}
},
removeConditionalStyles(domainObjectStyles, itemId) {
if (itemId) {
domainObjectStyles[itemId].conditionSetIdentifier = undefined;
delete domainObjectStyles[itemId].conditionSetIdentifier;
domainObjectStyles[itemId].styles = undefined;
delete domainObjectStyles[itemId].styles;
} else {
domainObjectStyles.conditionSetIdentifier = undefined;
delete domainObjectStyles.conditionSetIdentifier;
domainObjectStyles.styles = undefined;
delete domainObjectStyles.styles;
}
},
setEditState(isEditing) {
this.isEditing = isEditing;
},
initializeStaticStyle() {
this.staticStyle = {
style: Object.assign({}, this.initialStyles)
};
},
updateStaticStyle(staticStyle, property) {
//update the static style for each of the layoutItems as well as each sub object item
this.staticStyle = staticStyle;
this.persist(this.domainObject, this.getDomainObjectStyle(this.domainObject, property, this.items));
if (this.domainObjectsById) {
const keys = Object.keys(this.domainObjectsById);
keys.forEach(key => {
let domainObject = this.domainObjectsById[key];
this.persist(domainObject, this.getDomainObjectStyle(domainObject, property));
});
}
this.isStaticAndConditionalStyles = false;
let foundIndex = this.mixedStyles.indexOf(property);
if (foundIndex > -1) {
this.mixedStyles.splice(foundIndex, 1);
}
},
getDomainObjectStyle(domainObject, property, items) {
let domainObjectStyles = (domainObject.configuration && domainObject.configuration.objectStyles) || {};
if (items) {
items.forEach(item => {
let itemStaticStyle = {};
if (domainObjectStyles[item.id] && domainObjectStyles[item.id].staticStyle) {
itemStaticStyle = domainObjectStyles[item.id].staticStyle.style;
}
Object.keys(item.applicableStyles).forEach(key => {
if (property === key) {
itemStaticStyle[key] = this.staticStyle.style[key];
}
});
if (this.isStaticAndConditionalStyles) {
this.removeConditionalStyles(domainObjectStyles, item.id);
}
if (_.isEmpty(itemStaticStyle)) {
itemStaticStyle = undefined;
domainObjectStyles[item.id] = undefined;
} else {
domainObjectStyles[item.id] = Object.assign({}, { staticStyle: { style: itemStaticStyle } });
}
});
} else {
if (!domainObjectStyles.staticStyle) {
domainObjectStyles.staticStyle = {
style: {}
}
}
if (this.isStaticAndConditionalStyles) {
this.removeConditionalStyles(domainObjectStyles);
}
domainObjectStyles.staticStyle.style[property] = this.staticStyle.style[property];
}
return domainObjectStyles;
},
persist(domainObject, style) {
this.openmct.objects.mutate(domainObject, 'configuration.objectStyles', style);
}
}
}
</script>

View File

@@ -1,215 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
<template>
<div class="c-style">
<span :class="[
{ 'is-style-invisible': styleItem.style.isStyleInvisible },
{ 'c-style-thumb--mixed': mixedStyles.indexOf('backgroundColor') > -1 }
]"
:style="[styleItem.style.imageUrl ? { backgroundImage:'url(' + styleItem.style.imageUrl + ')'} : itemStyle ]"
class="c-style-thumb"
>
<span class="c-style-thumb__text"
:class="{ 'hide-nice': !hasProperty(styleItem.style.color) }"
>
ABC
</span>
</span>
<span class="c-toolbar">
<toolbar-color-picker v-if="hasProperty(styleItem.style.border)"
class="c-style__toolbar-button--border-color u-menu-to--center"
:options="borderColorOption"
@change="updateStyleValue"
/>
<toolbar-color-picker v-if="hasProperty(styleItem.style.backgroundColor)"
class="c-style__toolbar-button--background-color u-menu-to--center"
:options="backgroundColorOption"
@change="updateStyleValue"
/>
<toolbar-color-picker v-if="hasProperty(styleItem.style.color)"
class="c-style__toolbar-button--color u-menu-to--center"
:options="colorOption"
@change="updateStyleValue"
/>
<toolbar-button v-if="hasProperty(styleItem.style.imageUrl)"
class="c-style__toolbar-button--image-url"
:options="imageUrlOption"
@change="updateStyleValue"
/>
<toolbar-toggle-button v-if="hasProperty(styleItem.style.isStyleInvisible)"
class="c-style__toolbar-button--toggle-visible"
:options="isStyleInvisibleOption"
@change="updateStyleValue"
/>
</span>
</div>
</template>
<script>
import ToolbarColorPicker from "@/ui/toolbar/components/toolbar-color-picker.vue";
import ToolbarButton from "@/ui/toolbar/components/toolbar-button.vue";
import ToolbarToggleButton from "@/ui/toolbar/components/toolbar-toggle-button.vue";
import {STYLE_CONSTANTS} from "@/plugins/condition/utils/constants";
import {getStylesWithoutNoneValue} from "@/plugins/condition/utils/styleUtils";
export default {
name: 'StyleEditor',
components: {
ToolbarButton,
ToolbarColorPicker,
ToolbarToggleButton
},
inject: [
'openmct'
],
props: {
isEditing: {
type: Boolean
},
mixedStyles: {
type: Array,
default() {
return [];
}
},
styleItem: {
type: Object,
required: true
}
},
computed: {
itemStyle() {
return getStylesWithoutNoneValue(this.styleItem.style);
},
borderColorOption() {
let value = this.styleItem.style.border.replace('1px solid ', '');
return {
icon: 'icon-line-horz',
title: STYLE_CONSTANTS.borderColorTitle,
value: this.normalizeValueForSwatch(value),
property: 'border',
isEditing: this.isEditing,
nonSpecific: this.mixedStyles.indexOf('border') > -1
}
},
backgroundColorOption() {
let value = this.styleItem.style.backgroundColor;
return {
icon: 'icon-paint-bucket',
title: STYLE_CONSTANTS.backgroundColorTitle,
value: this.normalizeValueForSwatch(value),
property: 'backgroundColor',
isEditing: this.isEditing,
nonSpecific: this.mixedStyles.indexOf('backgroundColor') > -1
}
},
colorOption() {
let value = this.styleItem.style.color;
return {
icon: 'icon-font',
title: STYLE_CONSTANTS.textColorTitle,
value: this.normalizeValueForSwatch(value),
property: 'color',
isEditing: this.isEditing,
nonSpecific: this.mixedStyles.indexOf('color') > -1
}
},
imageUrlOption() {
return {
icon: 'icon-image',
title: STYLE_CONSTANTS.imagePropertiesTitle,
dialog: {
name: "Image Properties",
sections: [
{
rows: [
{
key: "url",
control: "textfield",
name: "Image URL",
"cssClass": "l-input-lg"
}
]
}
]
},
property: 'imageUrl',
formKeys: ['url'],
value: {url: this.styleItem.style.imageUrl},
isEditing: this.isEditing,
nonSpecific: this.mixedStyles.indexOf('imageUrl') > -1
}
},
isStyleInvisibleOption() {
return {
value: this.styleItem.style.isStyleInvisible,
property: 'isStyleInvisible',
isEditing: this.isEditing,
options: [
{
value: '',
icon: 'icon-eye-disabled',
title: STYLE_CONSTANTS.visibilityHidden
},
{
value: STYLE_CONSTANTS.isStyleInvisible,
icon: 'icon-eye-open',
title: STYLE_CONSTANTS.visibilityVisible
}
]
}
}
},
methods: {
hasProperty(property) {
return property !== undefined;
},
normalizeValueForSwatch(value) {
if (value && value.indexOf('__no_value') > -1) {
return value.replace('__no_value', 'transparent');
}
return value;
},
normalizeValueForStyle(value) {
if (value && value === 'transparent') {
return '__no_value';
}
return value;
},
updateStyleValue(value, item) {
value = this.normalizeValueForStyle(value);
if (item.property === 'border') {
value = '1px solid ' + value;
}
if (value && (value.url !== undefined)) {
this.styleItem.style[item.property] = value.url;
} else {
this.styleItem.style[item.property] = value;
}
this.$emit('persist', this.styleItem, item.property);
}
}
}
</script>

View File

@@ -1,124 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, 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.
*****************************************************************************/
/********************************************* INSPECTOR STYLES TAB */
.c-inspect-styles {
> * + * {
margin-top: $interiorMargin;
}
&__content,
&__conditions,
&__condition {
> * + * {
margin-top: $interiorMargin;
}
}
&__content {
display: flex;
flex-direction: column;
}
&__condition-set {
display: flex;
flex-direction: row;
align-items: center;
.c-object-label {
flex: 1 1 auto;
}
.c-button {
flex: 0 0 auto;
}
}
&__style,
&__condition {
padding: $interiorMargin;
}
&__condition {
@include discreteItem();
}
.c-style {
padding: 2px; // Allow a bit of room for thumb box-shadow
&__condition-desc {
@include ellipsize();
}
}
}
.c-inspect-styles__style {
.is-editing & {
border-bottom: 1px solid $colorInteriorBorder;
}
}
.l-shell:not(.is-editing) .c-inspect-styles {
.c-toolbar {
// Disabled-look toolbar when not editing
pointer-events: none;
cursor: inherit;
// Hide control buttons, like image URL
[class*='--image-url'] {
display: none;
}
// Make buttons look disabled by knocking back icon, not swatch element
.c-icon-button {
&:before {
opacity: $controlDisabledOpacity;
}
}
}
}
.c-toggle-styling-button {
display: none;
.is-editing & {
display: block;
align-self: flex-end;
}
}
.is-style-invisible {
display: none !important;
.is-editing & {
display: block !important;
opacity: 0.2;
}
&.c-style-thumb {
display: block !important;
background-color: transparent !important;
border-color: transparent !important;
@include bgCheckerboard($size: 10px, $imp: true);
opacity: 1;
}
}

View File

@@ -1,152 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import TelemetryCriterion from './TelemetryCriterion';
import { evaluateResults } from "../utils/evaluator";
export default class AllTelemetryCriterion extends TelemetryCriterion {
/**
* Subscribes/Unsubscribes to telemetry and emits the result
* of operations performed on the telemetry data returned and a given input value.
* @constructor
* @param telemetryDomainObjectDefinition {id: uuid, operation: enum, input: Array, metadata: string, key: {domainObject.identifier} }
* @param openmct
*/
constructor(telemetryDomainObjectDefinition, openmct) {
super(telemetryDomainObjectDefinition, openmct);
}
initialize() {
this.telemetryObjects = { ...this.telemetryDomainObjectDefinition.telemetryObjects };
this.telemetryDataCache = {};
}
isValid() {
return (this.telemetry === 'any' || this.telemetry === 'all') && this.metadata && this.operation;
}
updateTelemetry(telemetryObjects) {
this.telemetryObjects = { ...telemetryObjects };
this.removeTelemetryDataCache();
}
removeTelemetryDataCache() {
const telemetryCacheIds = Object.keys(this.telemetryDataCache);
Object.values(this.telemetryObjects).forEach(telemetryObject => {
const id = this.openmct.objects.makeKeyString(telemetryObject.identifier);
const foundIndex = telemetryCacheIds.indexOf(id);
if (foundIndex > -1) {
telemetryCacheIds.splice(foundIndex, 1);
}
});
telemetryCacheIds.forEach(id => {
delete (this.telemetryDataCache[id]);
});
}
formatData(data, telemetryObjects) {
if (data) {
this.telemetryDataCache[data.id] = this.computeResult(data);
}
let keys = Object.keys(telemetryObjects);
keys.forEach((key) => {
let telemetryObject = telemetryObjects[key];
const id = this.openmct.objects.makeKeyString(telemetryObject.identifier);
if (this.telemetryDataCache[id] === undefined) {
this.telemetryDataCache[id] = false;
}
});
const datum = {
result: evaluateResults(Object.values(this.telemetryDataCache), this.telemetry)
};
if (data) {
this.openmct.time.getAllTimeSystems().forEach(timeSystem => {
datum[timeSystem.key] = data[timeSystem.key]
});
}
return datum;
}
getResult(data, telemetryObjects) {
const validatedData = this.isValid() ? data : {};
if (validatedData) {
this.telemetryDataCache[validatedData.id] = this.computeResult(validatedData);
}
Object.values(telemetryObjects).forEach(telemetryObject => {
const id = this.openmct.objects.makeKeyString(telemetryObject.identifier);
if (this.telemetryDataCache[id] === undefined) {
this.telemetryDataCache[id] = false;
}
});
this.result = evaluateResults(Object.values(this.telemetryDataCache), this.telemetry);
}
requestLAD(options) {
options = Object.assign({},
options,
{
strategy: 'latest',
size: 1
}
);
if (!this.isValid()) {
return this.formatData({}, options.telemetryObjects);
}
let keys = Object.keys(Object.assign({}, options.telemetryObjects));
const telemetryRequests = keys
.map(key => this.openmct.telemetry.request(
options.telemetryObjects[key],
options
));
return Promise.all(telemetryRequests)
.then(telemetryRequestsResults => {
let latestDatum;
telemetryRequestsResults.forEach((results, index) => {
latestDatum = results.length ? results[results.length - 1] : {};
if (index < telemetryRequestsResults.length-1) {
if (latestDatum) {
this.telemetryDataCache[latestDatum.id] = this.computeResult(latestDatum);
}
}
});
return {
id: this.id,
data: this.formatData(latestDatum, options.telemetryObjects)
};
});
}
destroy() {
delete this.telemetryObjects;
delete this.telemetryDataCache;
}
}

View File

@@ -21,7 +21,7 @@
*****************************************************************************/
import EventEmitter from 'EventEmitter';
import { OPERATIONS } from '../utils/operations';
import {OPERATIONS} from '../utils/operations';
export default class TelemetryCriterion extends EventEmitter {
@@ -36,75 +36,37 @@ export default class TelemetryCriterion extends EventEmitter {
super();
this.openmct = openmct;
this.telemetryDomainObjectDefinition = telemetryDomainObjectDefinition;
this.objectAPI = this.openmct.objects;
this.telemetryAPI = this.openmct.telemetry;
this.timeAPI = this.openmct.time;
this.id = telemetryDomainObjectDefinition.id;
this.telemetry = telemetryDomainObjectDefinition.telemetry;
this.operation = telemetryDomainObjectDefinition.operation;
this.input = telemetryDomainObjectDefinition.input;
this.metadata = telemetryDomainObjectDefinition.metadata;
this.result = undefined;
this.subscription = null;
this.telemetryObjectIdAsString = null;
this.objectAPI.get(this.objectAPI.makeKeyString(this.telemetry)).then((obj) => this.initialize(obj));
}
this.initialize();
initialize(obj) {
this.telemetryObject = obj;
this.telemetryObjectIdAsString = this.objectAPI.makeKeyString(this.telemetryObject.identifier);
this.emitEvent('criterionUpdated', this);
}
initialize() {
this.telemetryObject = this.telemetryDomainObjectDefinition.telemetryObject;
this.telemetryObjectIdAsString = this.openmct.objects.makeKeyString(this.telemetryDomainObjectDefinition.telemetry);
}
isValid() {
return this.telemetryObject && this.metadata && this.operation;
}
updateTelemetry(telemetryObjects) {
this.telemetryObject = telemetryObjects[this.telemetryObjectIdAsString];
}
formatData(data) {
handleSubscription(data) {
const datum = {
result: this.computeResult(data)
};
if (data) {
this.openmct.time.getAllTimeSystems().forEach(timeSystem => {
// TODO check back to see if we should format times here
this.timeAPI.getAllTimeSystems().forEach(timeSystem => {
datum[timeSystem.key] = data[timeSystem.key]
});
}
return datum;
}
getResult(data) {
const validatedData = this.isValid() ? data : {};
this.result = this.computeResult(validatedData);
}
requestLAD(options) {
options = Object.assign({},
options,
{
strategy: 'latest',
size: 1
}
);
if (!this.isValid()) {
return {
id: this.id,
data: this.formatData({})
};
}
return this.openmct.telemetry.request(
this.telemetryObject,
options
).then(results => {
const latestDatum = results.length ? results[results.length - 1] : {};
return {
id: this.id,
data: this.formatData(latestDatum)
};
});
this.emitEvent('criterionResultUpdated', datum);
}
findOperation(operation) {
@@ -126,7 +88,7 @@ export default class TelemetryCriterion extends EventEmitter {
this.input.forEach(input => params.push(input));
}
if (typeof comparator === 'function') {
result = !!comparator(params);
result = comparator(params);
}
}
return result;
@@ -139,9 +101,40 @@ export default class TelemetryCriterion extends EventEmitter {
});
}
isValid() {
return this.telemetryObject && this.metadata && this.operation;
}
/**
* Subscribes to the telemetry object and returns an unsubscribe function
* If the telemetry is not valid, returns nothing
*/
subscribe() {
if (this.isValid()) {
this.unsubscribe();
this.subscription = this.telemetryAPI.subscribe(this.telemetryObject, (datum) => {
this.handleSubscription(datum);
});
} else {
this.handleSubscription();
}
}
/**
* Calls an unsubscribe function returned by subscribe() and deletes any initialized data
*/
unsubscribe() {
//unsubscribe from telemetry source
if (typeof this.subscription === 'function') {
this.subscription();
}
delete this.subscription;
}
destroy() {
delete this.telemetryObject;
this.unsubscribe();
this.emitEvent('criterionRemoved');
delete this.telemetryObjectIdAsString;
delete this.telemetryObject;
}
}

View File

@@ -24,6 +24,7 @@ import TelemetryCriterion from "./TelemetryCriterion";
let openmct = {},
mockListener,
mockListener2,
testCriterionDefinition,
testTelemetryObject,
telemetryCriterion;
@@ -36,39 +37,30 @@ describe("The telemetry criterion", function () {
type: "test-object",
name: "Test Object",
telemetry: {
valueMetadatas: [{
key: "value",
name: "Value",
hints: {
range: 2
}
},
{
key: "utc",
name: "Time",
format: "utc",
values: [{
key: "some-key",
name: "Some attribute",
hints: {
domain: 1
}
}, {
key: "testSource",
source: "value",
name: "Test",
format: "string"
key: "some-other-key",
name: "Another attribute",
hints: {
range: 1
}
}]
}
};
openmct.objects = jasmine.createSpyObj('objects', ['get', 'makeKeyString']);
openmct.objects.get.and.returnValue(new Promise(function (resolve, reject) {
resolve(testTelemetryObject);
}));
openmct.objects.makeKeyString.and.returnValue(testTelemetryObject.identifier.key);
openmct.telemetry = jasmine.createSpyObj('telemetry', ['isTelemetryObject', "subscribe", "getMetadata", "getValueFormatter"]);
openmct.telemetry = jasmine.createSpyObj('telemetry', ['isTelemetryObject', "subscribe", "getMetadata"]);
openmct.telemetry.isTelemetryObject.and.returnValue(true);
openmct.telemetry.subscribe.and.returnValue(function () {});
openmct.telemetry.getValueFormatter.and.returnValue({
parse: function (value) {
return value;
}
});
openmct.telemetry.getMetadata.and.returnValue(testTelemetryObject.telemetry);
openmct.telemetry.getMetadata.and.returnValue(testTelemetryObject.telemetry.values);
openmct.time = jasmine.createSpyObj('timeAPI',
['timeSystem', 'bounds', 'getAllTimeSystems']
@@ -79,14 +71,13 @@ describe("The telemetry criterion", function () {
testCriterionDefinition = {
id: 'test-criterion-id',
telemetry: openmct.objects.makeKeyString(testTelemetryObject.identifier),
operation: 'textContains',
metadata: 'value',
input: ['Hell'],
telemetryObject: testTelemetryObject
telemetry: openmct.objects.makeKeyString(testTelemetryObject.identifier)
};
mockListener = jasmine.createSpy('listener');
mockListener2 = jasmine.createSpy('updatedListener', (data) => {
console.log(data);
});
telemetryCriterion = new TelemetryCriterion(
testCriterionDefinition,
@@ -94,28 +85,39 @@ describe("The telemetry criterion", function () {
);
telemetryCriterion.on('criterionResultUpdated', mockListener);
telemetryCriterion.on('criterionUpdated', mockListener2);
});
it("initializes with a telemetry objectId as string", function () {
telemetryCriterion.initialize(testTelemetryObject);
expect(telemetryCriterion.telemetryObjectIdAsString).toEqual(testTelemetryObject.identifier.key);
expect(mockListener2).toHaveBeenCalled();
});
it("returns a result on new data from relevant telemetry providers", function () {
telemetryCriterion.getResult({
value: 'Hello',
utc: 'Hi',
id: testTelemetryObject.identifier.key
it("subscribes to telemetry providers", function () {
telemetryCriterion.subscribe();
expect(telemetryCriterion.subscription).toBeDefined();
});
it("emits update event on new data from telemetry providers", function () {
spyOn(telemetryCriterion, 'emitEvent').and.callThrough();
telemetryCriterion.handleSubscription({
key: 'some-key',
source: 'testSource',
testSource: 'Hello'
});
expect(telemetryCriterion.result).toBeTrue();
expect(telemetryCriterion.emitEvent).toHaveBeenCalled();
expect(mockListener).toHaveBeenCalled();
});
it("un-subscribes from telemetry providers", function () {
telemetryCriterion.subscribe();
expect(telemetryCriterion.subscription).toBeDefined();
telemetryCriterion.destroy();
expect(telemetryCriterion.subscription).toBeUndefined();
expect(telemetryCriterion.telemetryObjectIdAsString).toBeUndefined();
expect(telemetryCriterion.telemetryObject).toBeUndefined();
});
// it("does not return a result on new data from irrelavant telemetry providers", function () {
// telemetryCriterion.getResult({
// value: 'Hello',
// utc: 'Hi',
// id: '1234'
// });
// expect(telemetryCriterion.result).toBeFalse();
// });
});

View File

@@ -23,7 +23,6 @@ import ConditionSetViewProvider from './ConditionSetViewProvider.js';
import ConditionSetCompositionPolicy from "./ConditionSetCompositionPolicy";
import ConditionSetMetadataProvider from './ConditionSetMetadataProvider';
import ConditionSetTelemetryProvider from './ConditionSetTelemetryProvider';
import ConditionSetViewPolicy from './ConditionSetViewPolicy';
import uuid from "uuid";
export default function ConditionPlugin() {
@@ -33,18 +32,17 @@ export default function ConditionPlugin() {
openmct.types.addType('conditionSet', {
name: 'Condition Set',
key: 'conditionSet',
description: 'Monitor and evaluate telemetry values in real-time with a wide variety of criteria. Use to control the styling of many objects in Open MCT.',
description: 'A set of one or more conditions based on user-specified criteria.',
creatable: true,
cssClass: 'icon-conditional',
cssClass: 'icon-conditional', // TODO: replace with class for new icon
initialize: function (domainObject) {
domainObject.configuration = {
conditionTestData: [],
conditionCollection: [{
isDefault: true,
id: uuid(),
configuration: {
name: 'Default',
output: 'Default',
output: 'false',
trigger: 'all',
criteria: []
},
@@ -55,10 +53,7 @@ export default function ConditionPlugin() {
domainObject.telemetry = {};
}
});
openmct.legacyExtension('policies', {
category: 'view',
implementation: ConditionSetViewPolicy
});
openmct.composition.addPolicy(new ConditionSetCompositionPolicy(openmct).allow);
openmct.telemetry.addProvider(new ConditionSetMetadataProvider(openmct));
openmct.telemetry.addProvider(new ConditionSetTelemetryProvider(openmct));

View File

@@ -82,10 +82,7 @@ describe('the plugin', function () {
it('provides a view', () => {
const testViewObject = {
id:"test-object",
type: "conditionSet",
configuration: {
conditionCollection: []
}
type: "conditionSet"
};
const applicableViews = openmct.objectViews.get(testViewObject);

View File

@@ -1,54 +1,4 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, 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.
*****************************************************************************/
export const TRIGGER = {
ANY: 'any',
ALL: 'all',
NOT: 'not',
XOR: 'xor'
};
export const TRIGGER_LABEL = {
'any': 'when any criteria are met',
'all': 'when all criteria are met',
'not': 'when no criteria are met',
'xor': 'when only one criteria is met'
};
export const STYLE_CONSTANTS = {
isStyleInvisible: 'is-style-invisible',
borderColorTitle: 'Set border color',
textColorTitle: 'Set text color',
backgroundColorTitle: 'Set background color',
imagePropertiesTitle: 'Edit image properties',
visibilityHidden: 'Hidden',
visibilityVisible: 'Visible'
};
export const ERROR = {
'TELEMETRY_NOT_FOUND': {
errorText: 'Telemetry not found for criterion'
},
'CONDITION_NOT_FOUND': {
errorText: 'Condition not found'
}
ALL: 'all'
};

View File

@@ -1,68 +1,16 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import { TRIGGER } from "./constants";
export const evaluateResults = (results, trigger) => {
if (trigger && trigger === TRIGGER.XOR) {
return matchExact(results, 1);
} else if (trigger && trigger === TRIGGER.NOT) {
return matchExact(results, 0);
} else if (trigger && trigger === TRIGGER.ALL) {
return matchAll(results);
} else {
return matchAny(results);
}
}
function matchAll(results) {
for (const result of results) {
if (!result) {
return false;
export const computeCondition = (resultMap, allMustBeTrue) => {
let result = false;
for (let key in resultMap) {
if (resultMap.hasOwnProperty(key)) {
result = resultMap[key];
if (allMustBeTrue && !result) {
//If we want all conditions to be true, then even one negative result should break.
break;
} else if (!allMustBeTrue && result) {
//If we want at least one condition to be true, then even one positive result should break.
break;
}
}
}
return true;
}
function matchAny(results) {
for (const result of results) {
if (result) {
return true;
}
}
return false;
}
function matchExact(results, target) {
let matches = 0;
for (const result of results) {
if (result) {
matches++;
}
if (matches > target) {
return false;
}
}
return matches === target;
}
return result;
};

View File

@@ -1,204 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import { evaluateResults } from './evaluator';
import { TRIGGER } from './constants';
describe('evaluate results', () => {
// const allTrue = [true, true, true, true, true];
// const oneTrue = [false, false, false, false, true];
// const multipleTrue = [false, true, false, true, false];
// const noneTrue = [false, false, false, false, false];
// const allTrueWithUndefined = [true, true, true, undefined, true];
// const oneTrueWithUndefined = [undefined, undefined, undefined, undefined, true];
// const multipleTrueWithUndefined = [true, undefined, true, undefined, true];
// const allUndefined = [undefined, undefined, undefined, undefined, undefined];
// const singleTrue = [true];
// const singleFalse = [false];
// const singleUndefined = [undefined];
// const empty = [];
const tests = [
{
name: 'allTrue',
values: [true, true, true, true, true],
any: true,
all: true,
not: false,
xor: false
}, {
name: 'oneTrue',
values: [false, false, false, false, true],
any: true,
all: false,
not: false,
xor: true
}, {
name: 'multipleTrue',
values: [false, true, false, true, false],
any: true,
all: false,
not: false,
xor: false
}, {
name: 'noneTrue',
values: [false, false, false, false, false],
any: false,
all: false,
not: true,
xor: false
}, {
name: 'allTrueWithUndefined',
values: [true, true, true, undefined, true],
any: true,
all: false,
not: false,
xor: false
}, {
name: 'oneTrueWithUndefined',
values: [undefined, undefined, undefined, undefined, true],
any: true,
all: false,
not: false,
xor: true
}, {
name: 'multipleTrueWithUndefined',
values: [true, undefined, true, undefined, true],
any: true,
all: false,
not: false,
xor: false
}, {
name: 'allUndefined',
values: [undefined, undefined, undefined, undefined, undefined],
any: false,
all: false,
not: true,
xor: false
}, {
name: 'singleTrue',
values: [true],
any: true,
all: true,
not: false,
xor: true
}, {
name: 'singleFalse',
values: [false],
any: false,
all: false,
not: true,
xor: false
}, {
name: 'singleUndefined',
values: [undefined],
any: false,
all: false,
not: true,
xor: false
}
// , {
// name: 'empty',
// values: [],
// any: false,
// all: false,
// not: true,
// xor: false
// }
];
describe(`based on trigger ${TRIGGER.ANY}`, () => {
it('should evaluate to expected result', () => {
tests.forEach(test => {
const result = evaluateResults(test.values, TRIGGER.ANY);
expect(result).toEqual(test[TRIGGER.ANY])
});
});
});
describe(`based on trigger ${TRIGGER.ALL}`, () => {
it('should evaluate to expected result', () => {
tests.forEach(test => {
const result = evaluateResults(test.values, TRIGGER.ALL);
expect(result).toEqual(test[TRIGGER.ALL])
});
});
});
describe(`based on trigger ${TRIGGER.NOT}`, () => {
it('should evaluate to expected result', () => {
tests.forEach(test => {
const result = evaluateResults(test.values, TRIGGER.NOT);
expect(result).toEqual(test[TRIGGER.NOT])
});
});
});
describe(`based on trigger ${TRIGGER.XOR}`, () => {
it('should evaluate to expected result', () => {
tests.forEach(test => {
const result = evaluateResults(test.values, TRIGGER.XOR);
expect(result).toEqual(test[TRIGGER.XOR])
});
});
});
// it('should evaluate to true if trigger is NOT', () => {
// const results = {
// result: false,
// result1: false,
// result2: false
// };
// const result = computeConditionByLimit(results, 0);
// expect(result).toBeTrue();
// });
// it('should evaluate to false if trigger is NOT', () => {
// const results = {
// result: true,
// result1: false,
// result2: false
// };
// const result = computeConditionByLimit(results, 0);
// expect(result).toBeFalse();
// });
// it('should evaluate to true if trigger is XOR', () => {
// const results = {
// result: false,
// result1: true,
// result2: false
// };
// const result = computeConditionByLimit(results, 1);
// expect(result).toBeTrue();
// });
// it('should evaluate to false if trigger is XOR', () => {
// const results = {
// result: false,
// result1: true,
// result2: true
// };
// const result = computeConditionByLimit(results, 1);
// expect(result).toBeFalse();
// });
});

View File

@@ -1,119 +1,80 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import _ from 'lodash';
const convertToNumbers = (input) => {
let numberInputs = [];
input.forEach(inputValue => numberInputs.push(Number(inputValue)));
return numberInputs;
};
const convertToStrings = (input) => {
let stringInputs = [];
input.forEach(inputValue => stringInputs.push(inputValue !== undefined ? inputValue.toString() : ''));
return stringInputs;
};
export const OPERATIONS = [
{
name: 'equalTo',
operation: function (input) {
return Number(input[0]) === Number(input[1]);
return input[0] === input[1];
},
text: 'is equal to',
appliesTo: ['number'],
inputCount: 1,
getDescription: function (values) {
return ' is ' + values.join(', ');
return ' is ' + values[0];
}
},
{
name: 'notEqualTo',
operation: function (input) {
return Number(input[0]) !== Number(input[1]);
return input[0] !== input[1];
},
text: 'is not equal to',
appliesTo: ['number'],
inputCount: 1,
getDescription: function (values) {
return ' is not ' + values.join(', ');
return ' is not ' + values[0];
}
},
{
name: 'greaterThan',
operation: function (input) {
return Number(input[0]) > Number(input[1]);
return input[0] > input[1];
},
text: 'is greater than',
appliesTo: ['number'],
inputCount: 1,
getDescription: function (values) {
return ' > ' + values.join(', ');
return ' > ' + values[0];
}
},
{
name: 'lessThan',
operation: function (input) {
return Number(input[0]) < Number(input[1]);
return input[0] < input[1];
},
text: 'is less than',
appliesTo: ['number'],
inputCount: 1,
getDescription: function (values) {
return ' < ' + values.join(', ');
return ' < ' + values[0];
}
},
{
name: 'greaterThanOrEq',
operation: function (input) {
return Number(input[0]) >= Number(input[1]);
return input[0] >= input[1];
},
text: 'is greater than or equal to',
appliesTo: ['number'],
inputCount: 1,
getDescription: function (values) {
return ' >= ' + values.join(', ');
return ' >= ' + values[0];
}
},
{
name: 'lessThanOrEq',
operation: function (input) {
return Number(input[0]) <= Number(input[1]);
return input[0] <= input[1];
},
text: 'is less than or equal to',
appliesTo: ['number'],
inputCount: 1,
getDescription: function (values) {
return ' <= ' + values.join(', ');
return ' <= ' + values[0];
}
},
{
name: 'between',
operation: function (input) {
let numberInputs = convertToNumbers(input);
let larger = Math.max(...numberInputs.slice(1,3));
let smaller = Math.min(...numberInputs.slice(1,3));
return (numberInputs[0] > smaller) && (numberInputs[0] < larger);
return input[0] > input[1] && input[0] < input[2];
},
text: 'is between',
appliesTo: ['number'],
@@ -125,10 +86,7 @@ export const OPERATIONS = [
{
name: 'notBetween',
operation: function (input) {
let numberInputs = convertToNumbers(input);
let larger = Math.max(...numberInputs.slice(1,3));
let smaller = Math.min(...numberInputs.slice(1,3));
return (numberInputs[0] < smaller) || (numberInputs[0] > larger);
return input[0] < input[1] || input[0] > input[2];
},
text: 'is not between',
appliesTo: ['number'],
@@ -146,7 +104,7 @@ export const OPERATIONS = [
appliesTo: ['string'],
inputCount: 1,
getDescription: function (values) {
return ' contains ' + values.join(', ');
return ' contains ' + values[0];
}
},
{
@@ -158,7 +116,7 @@ export const OPERATIONS = [
appliesTo: ['string'],
inputCount: 1,
getDescription: function (values) {
return ' does not contain ' + values.join(', ');
return ' does not contain ' + values[0];
}
},
{
@@ -170,7 +128,7 @@ export const OPERATIONS = [
appliesTo: ['string'],
inputCount: 1,
getDescription: function (values) {
return ' starts with ' + values.join(', ');
return ' starts with ' + values[0];
}
},
{
@@ -182,7 +140,7 @@ export const OPERATIONS = [
appliesTo: ['string'],
inputCount: 1,
getDescription: function (values) {
return ' ends with ' + values.join(', ');
return ' ends with ' + values[0];
}
},
{
@@ -194,7 +152,7 @@ export const OPERATIONS = [
appliesTo: ['string'],
inputCount: 1,
getDescription: function (values) {
return ' is exactly ' + values.join(', ');
return ' is exactly ' + values[0];
}
},
{
@@ -224,67 +182,25 @@ export const OPERATIONS = [
{
name: 'enumValueIs',
operation: function (input) {
let stringInputs = convertToStrings(input);
return stringInputs[0] === stringInputs[1];
return input[0] === input[1];
},
text: 'is',
appliesTo: ['enum'],
inputCount: 1,
getDescription: function (values) {
return ' is ' + values.join(', ');
return ' is ' + values[0];
}
},
{
name: 'enumValueIsNot',
operation: function (input) {
let stringInputs = convertToStrings(input);
return stringInputs[0] !== stringInputs[1];
return input[0] !== input[1];
},
text: 'is not',
appliesTo: ['enum'],
inputCount: 1,
getDescription: function (values) {
return ' is not ' + values.join(', ');
}
},
{
name: 'valueIs',
operation: function (input) {
const lhsValue = input[0] !== undefined ? input[0].toString() : '';
if (input[1]) {
const values = input[1].split(',');
return values.find((value) => lhsValue === _.trim(value.toString()));
}
return false;
},
text: 'is one of',
appliesTo: ["string", "number"],
inputCount: 1,
getDescription: function (values) {
return ' is one of ' + values[0];
}
},
{
name: 'valueIsNot',
operation: function (input) {
const lhsValue = input[0] !== undefined ? input[0].toString() : '';
if (input[1]) {
const values = input[1].split(',');
const found = values.find((value) => lhsValue === _.trim(value.toString()));
return !found;
}
return false;
},
text: 'is not one of',
appliesTo: ["string", "number"],
inputCount: 1,
getDescription: function (values) {
return ' is not one of ' + values[0];
return ' is not ' + values[0];
}
}
];
export const INPUT_TYPES = {
'string': 'text',
'number': 'number'
};

View File

@@ -1,142 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import { OPERATIONS } from "./operations";
let isOneOfOperation = OPERATIONS.find((operation) => operation.name === 'valueIs');
let isNotOneOfOperation = OPERATIONS.find((operation) => operation.name === 'valueIsNot');
let isBetween = OPERATIONS.find((operation) => operation.name === 'between');
let isNotBetween = OPERATIONS.find((operation) => operation.name === 'notBetween');
let enumIsOperation = OPERATIONS.find((operation) => operation.name === 'enumValueIs');
let enumIsNotOperation = OPERATIONS.find((operation) => operation.name === 'enumValueIsNot');
describe('operations', function () {
it('should evaluate isOneOf to true for number inputs', () => {
const inputs = [45, "5,6,45,8"];
expect(!!isOneOfOperation.operation(inputs)).toBeTrue();
});
it('should evaluate isOneOf to true for string inputs', () => {
const inputs = ["45", " 45, 645, 4,8 "];
expect(!!isOneOfOperation.operation(inputs)).toBeTrue();
});
it('should evaluate isNotOneOf to true for number inputs', () => {
const inputs = [45, "5,6,4,8"];
expect(!!isNotOneOfOperation.operation(inputs)).toBeTrue();
});
it('should evaluate isNotOneOf to true for string inputs', () => {
const inputs = ["45", " 5,645, 4,8 "];
expect(!!isNotOneOfOperation.operation(inputs)).toBeTrue();
});
it('should evaluate isOneOf to false for number inputs', () => {
const inputs = [4, "5, 6, 7, 8"];
expect(!!isOneOfOperation.operation(inputs)).toBeFalse();
});
it('should evaluate isOneOf to false for string inputs', () => {
const inputs = ["4", "5,645 ,7,8"];
expect(!!isOneOfOperation.operation(inputs)).toBeFalse();
});
it('should evaluate isNotOneOf to false for number inputs', () => {
const inputs = [4, "5,4, 7,8"];
expect(!!isNotOneOfOperation.operation(inputs)).toBeFalse();
});
it('should evaluate isNotOneOf to false for string inputs', () => {
const inputs = ["4", "5,46,4,8"];
expect(!!isNotOneOfOperation.operation(inputs)).toBeFalse();
});
it('should evaluate isBetween to true', () => {
const inputs = ["4", "3", "89"];
expect(!!isBetween.operation(inputs)).toBeTrue();
});
it('should evaluate isNotBetween to true', () => {
const inputs = ["45", "100", "89"];
expect(!!isNotBetween.operation(inputs)).toBeTrue();
});
it('should evaluate isBetween to false', () => {
const inputs = ["4", "100", "89"];
expect(!!isBetween.operation(inputs)).toBeFalse();
});
it('should evaluate isNotBetween to false', () => {
const inputs = ["45", "30", "50"];
expect(!!isNotBetween.operation(inputs)).toBeFalse();
});
it('should evaluate enumValueIs to true for number inputs', () => {
const inputs = [1, "1"];
expect(!!enumIsOperation.operation(inputs)).toBeTrue();
});
it('should evaluate enumValueIs to true for string inputs', () => {
const inputs = ["45", "45"];
expect(!!enumIsOperation.operation(inputs)).toBeTrue();
});
it('should evaluate enumValueIsNot to true for number inputs', () => {
const inputs = [45, "46"];
expect(!!enumIsNotOperation.operation(inputs)).toBeTrue();
});
it('should evaluate enumValueIsNot to true for string inputs', () => {
const inputs = ["45", "46"];
expect(!!enumIsNotOperation.operation(inputs)).toBeTrue();
});
it('should evaluate enumValueIs to false for number inputs', () => {
const inputs = [1, "2"];
expect(!!enumIsOperation.operation(inputs)).toBeFalse();
});
it('should evaluate enumValueIs to false for string inputs', () => {
const inputs = ["45", "46"];
expect(!!enumIsOperation.operation(inputs)).toBeFalse();
});
it('should evaluate enumValueIsNot to false for number inputs', () => {
const inputs = [45, "45"];
expect(!!enumIsNotOperation.operation(inputs)).toBeFalse();
});
it('should evaluate enumValueIsNot to false for string inputs', () => {
const inputs = ["45", "45"];
expect(!!enumIsNotOperation.operation(inputs)).toBeFalse();
});
it('should evaluate enumValueIs to false for undefined input', () => {
const inputs = [undefined, "45"];
expect(!!enumIsOperation.operation(inputs)).toBeFalse();
});
it('should evaluate enumValueIsNot to true for undefined input', () => {
const inputs = [undefined, "45"];
expect(!!enumIsNotOperation.operation(inputs)).toBeTrue();
});
});

View File

@@ -1,172 +1,19 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, 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.
*****************************************************************************/
const NONE_VALUE = '__no_value';
const styleProps = {
backgroundColor: {
svgProperty: 'fill',
noneValue: NONE_VALUE,
applicableForType: type => {
return !type ? true : (type === 'text-view' ||
type === 'telemetry-view' ||
type === 'box-view' ||
type === 'subobject-view');
}
},
border: {
svgProperty: 'stroke',
noneValue: NONE_VALUE,
applicableForType: type => {
return !type ? true : (type === 'text-view' ||
type === 'telemetry-view' ||
type === 'box-view' ||
type === 'image-view' ||
type === 'line-view'||
type === 'subobject-view');
}
},
color: {
svgProperty: 'color',
noneValue: NONE_VALUE,
applicableForType: type => {
return !type ? true : (type === 'text-view' ||
type === 'telemetry-view'||
type === 'subobject-view');
}
},
imageUrl: {
svgProperty: 'url',
noneValue: '',
applicableForType: type => {
return !type ? false : type === 'image-view';
}
}
};
const aggregateStyleValues = (accumulator, currentStyle) => {
const styleKeys = Object.keys(currentStyle);
const properties = Object.keys(styleProps);
properties.forEach((property) => {
if (!accumulator[property]) {
accumulator[property] = [];
}
const found = styleKeys.find(key => key === property);
if (found) {
accumulator[property].push(currentStyle[found]);
}
});
return accumulator;
};
// Returns a union of styles used by multiple items.
// Styles that are common to all items but don't have the same value are added to the mixedStyles list
export const getConsolidatedStyleValues = (multipleItemStyles) => {
let aggregatedStyleValues = multipleItemStyles.reduce(aggregateStyleValues, {});
let styleValues = {};
let mixedStyles = [];
const properties = Object.keys(styleProps);
properties.forEach((property) => {
const values = aggregatedStyleValues[property];
if (values.length) {
if (values.every(value => value === values[0])) {
styleValues[property] = values[0];
} else {
styleValues[property] = '';
mixedStyles.push(property);
}
}
});
return {
styles: styleValues,
mixedStyles
export const getStyleProp = (key, defaultValue) => {
let styleProp = undefined;
switch(key) {
case 'fill': styleProp = {
backgroundColor: defaultValue || 'none'
};
};
const getStaticStyleForItem = (domainObject, id) => {
let domainObjectStyles = domainObject && domainObject.configuration && domainObject.configuration.objectStyles;
if (domainObjectStyles) {
if (id) {
if(domainObjectStyles[id] && domainObjectStyles[id].staticStyle) {
return domainObjectStyles[id].staticStyle.style;
}
} else if (domainObjectStyles.staticStyle) {
return domainObjectStyles.staticStyle.style;
}
break;
case 'stroke': styleProp = {
border: '1px solid ' + defaultValue || 'none'
};
break;
case 'color': styleProp = {
color: defaultValue || 'inherit'
};
break;
}
};
export const getConditionalStyleForItem = (domainObject, id) => {
let domainObjectStyles = domainObject && domainObject.configuration && domainObject.configuration.objectStyles;
if (domainObjectStyles) {
if (id) {
if (domainObjectStyles[id] && domainObjectStyles[id].conditionSetIdentifier) {
return domainObjectStyles[id].styles;
}
} else if (domainObjectStyles.staticStyle) {
return domainObjectStyles.styles;
}
}
};
//Returns either existing static styles or uses SVG defaults if available
export const getApplicableStylesForItem = (domainObject, item) => {
const type = item && item.type;
const id = item && item.id;
let style = {};
let staticStyle = getStaticStyleForItem(domainObject, id);
const properties = Object.keys(styleProps);
properties.forEach(property => {
const styleProp = styleProps[property];
if (styleProp.applicableForType(type)) {
let defaultValue;
if (staticStyle) {
defaultValue = staticStyle[property];
} else if (item) {
defaultValue = item[styleProp.svgProperty];
}
style[property] = defaultValue === undefined ? styleProp.noneValue : defaultValue;
}
});
return style;
};
export const getStylesWithoutNoneValue = (style) => {
if (_.isEmpty(style) || !style) {
return;
}
let styleObj = {};
const keys = Object.keys(style);
keys.forEach(key => {
if ((typeof style[key] === 'string')) {
if (style[key].indexOf('__no_value') > -1) {
style[key] = '';
} else {
styleObj[key] = style[key];
}
}
});
return styleObj;
return styleProp;
};

View File

@@ -1,52 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, 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.
*****************************************************************************/
export const getLatestTimestamp = (
currentTimestamp,
compareTimestamp,
timeSystems,
currentTimeSystem
) => {
let latest = { ...currentTimestamp };
const compare = { ...compareTimestamp };
const key = currentTimeSystem.key;
if (!latest || !latest[key]) {
latest = updateLatestTimeStamp(compare, timeSystems)
}
if (compare[key] > latest[key]) {
latest = updateLatestTimeStamp(compare, timeSystems)
}
return latest;
}
function updateLatestTimeStamp(timestamp, timeSystems) {
let latest = {};
timeSystems.forEach(timeSystem => {
latest[timeSystem.key] = timestamp[timeSystem.key];
});
return latest;
}

View File

@@ -1,64 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import ConditionWidgetComponent from './components/ConditionWidget.vue';
import Vue from 'vue';
export default function ConditionWidget(openmct) {
return {
key: 'conditionWidget',
name: 'Condition Widget',
cssClass: 'icon-condition-widget',
canView: function (domainObject) {
return domainObject.type === 'conditionWidget';
},
canEdit: function (domainObject) {
return domainObject.type === 'conditionWidget';
},
view: function (domainObject) {
let component;
return {
show: function (element) {
component = new Vue({
el: element,
components: {
ConditionWidgetComponent: ConditionWidgetComponent
},
provide: {
openmct,
domainObject
},
template: '<condition-widget-component></condition-widget-component>'
});
},
destroy: function (element) {
component.$destroy();
component = undefined;
}
};
},
priority: function () {
return 1;
}
};
}

View File

@@ -1,61 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
<template>
<component :is="urlDefined ? 'a' : 'span'"
class="c-condition-widget"
:href="urlDefined ? internalDomainObject.url : null"
>
<div class="c-condition-widget__label">
{{ internalDomainObject.label }}
</div>
</component>
</template>
<script>
export default {
inject: ['openmct', 'domainObject'],
data: function () {
return {
internalDomainObject: this.domainObject
}
},
computed: {
urlDefined() {
return this.internalDomainObject.url && this.internalDomainObject.url.length > 0;
}
},
mounted() {
this.unlisten = this.openmct.objects.observe(this.internalDomainObject, '*', this.updateInternalDomainObject);
},
beforeDestroy() {
if (this.unlisten) {
this.unlisten();
}
},
methods: {
updateInternalDomainObject(domainObject) {
this.internalDomainObject = domainObject;
}
}
}
</script>

View File

@@ -1,60 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, 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.
*****************************************************************************/
.c-condition-widget {
$shdwSize: 3px;
@include userSelectNone();
background-color: rgba($colorBodyFg, 0.1); // Give a little presence if the user hasn't defined a fill color
border-radius: $basicCr;
border: 1px solid transparent;
display: inline-block;
padding: $interiorMarginLg $interiorMarginLg * 2;
}
a.c-condition-widget {
// Widget is conditionally made into a <a> when URL property has been defined
cursor: pointer !important;
pointer-events: inherit;
}
// Make Condition Widget expand when in a hidden frame Layout context
// For both static and Flexible Layouts
.c-so-view--no-frame > .c-so-view__object-view > .c-condition-widget {
@include abs();
display: flex;
align-items: center;
justify-content: center;
padding: 0;
}
// Add some margin when a Condition Widget is in a Flexible Layout
.c-fl .c-so-view--no-frame .c-condition-widget {
@include abs(1px);
}
// When the widget is in the main view, center it in the space
.l-shell__main-container > .c-condition-widget {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}

View File

@@ -1,58 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import ConditionWidgetViewProvider from './ConditionWidgetViewProvider.js';
export default function plugin() {
return function install(openmct) {
openmct.objectViews.addProvider(new ConditionWidgetViewProvider(openmct));
openmct.types.addType('conditionWidget', {
name: "Condition Widget",
description: "A button that can be used on its own, or dynamically styled with a Condition Set.",
creatable: true,
cssClass: 'icon-condition-widget',
initialize(domainObject) {
domainObject.label = 'Condition Widget';
},
form: [
{
"key": "label",
"name": "Label",
"control": "textfield",
property: [
"label"
],
"required": true,
"cssClass": "l-input"
},
{
"key": "url",
"name": "URL",
"control": "textfield",
"required": false,
"cssClass": "l-input-lg"
}
]
});
};
}

View File

@@ -347,6 +347,78 @@ define(['lodash'], function (_) {
};
}
function getFillMenu(selectedParent, selection) {
return {
control: "color-picker",
domainObject: selectedParent,
applicableSelectedItems: selection.filter(selectionPath => {
let type = selectionPath[0].context.layoutItem.type;
return type === 'text-view' ||
type === 'telemetry-view' ||
type === 'box-view';
}),
property: function (selectionPath) {
return getPath(selectionPath) + ".fill";
},
icon: "icon-paint-bucket",
title: "Set fill color"
};
}
function getStrokeMenu(selectedParent, selection) {
return {
control: "color-picker",
domainObject: selectedParent,
applicableSelectedItems: selection.filter(selectionPath => {
let type = selectionPath[0].context.layoutItem.type;
return type === 'text-view' ||
type === 'telemetry-view' ||
type === 'box-view' ||
type === 'image-view' ||
type === 'line-view';
}),
property: function (selectionPath) {
return getPath(selectionPath) + ".stroke";
},
icon: "icon-line-horz",
title: "Set border color"
};
}
function getTextColorMenu(selectedParent, selection) {
return {
control: "color-picker",
domainObject: selectedParent,
applicableSelectedItems: selection.filter(selectionPath => {
let type = selectionPath[0].context.layoutItem.type;
return type === 'text-view' || type === 'telemetry-view';
}),
property: function (selectionPath) {
return getPath(selectionPath) + ".color";
},
icon: "icon-font",
mandatory: true,
title: "Set text color",
preventNone: true
};
}
function getURLButton(selectedParent, selection) {
return {
control: "button",
domainObject: selectedParent,
applicableSelectedItems: selection.filter(selectionPath => {
return selectionPath[0].context.layoutItem.type === 'image-view';
}),
property: function (selectionPath) {
return getPath(selectionPath);
},
icon: "icon-image",
title: "Edit image properties",
dialog: DIALOG_FORM.image
};
}
function getTextButton(selectedParent, selection) {
return {
control: "button",
@@ -357,7 +429,7 @@ define(['lodash'], function (_) {
property: function (selectionPath) {
return getPath(selectionPath);
},
icon: "icon-font",
icon: "icon-gear",
title: "Edit text properties",
dialog: DIALOG_FORM.text
};
@@ -433,14 +505,14 @@ define(['lodash'], function (_) {
let toolbar = {
'add-menu': [],
'text': [],
'url': [],
'toggle-frame': [],
'display-mode': [],
'telemetry-value': [],
'style': [],
'text-style': [],
'position': [],
'text': [],
'url': [],
'remove': []
};
@@ -448,10 +520,6 @@ define(['lodash'], function (_) {
let selectedParent = selectionPath[1].context.item;
let layoutItem = selectionPath[0].context.layoutItem;
if (!layoutItem) {
return;
}
if (layoutItem.type === 'subobject-view') {
if (toolbar['add-menu'].length === 0 && selectionPath[0].context.item.type === 'layout') {
toolbar['add-menu'] = [getAddButton(selectedObjects, selectionPath)];
@@ -478,8 +546,15 @@ define(['lodash'], function (_) {
if (toolbar['telemetry-value'].length === 0) {
toolbar['telemetry-value'] = [getTelemetryValueMenu(selectionPath, selectedObjects)];
}
if (toolbar.style.length < 2) {
toolbar.style = [
getFillMenu(selectedParent, selectedObjects),
getStrokeMenu(selectedParent, selectedObjects)
];
}
if (toolbar['text-style'].length === 0) {
toolbar['text-style'] = [
getTextColorMenu(selectedParent, selectedObjects),
getTextSizeMenu(selectedParent, selectedObjects)
];
}
@@ -496,8 +571,15 @@ define(['lodash'], function (_) {
toolbar.remove = [getRemoveButton(selectedParent, selectionPath, selectedObjects)];
}
} else if (layoutItem.type === 'text-view') {
if (toolbar.style.length < 2) {
toolbar.style = [
getFillMenu(selectedParent, selectedObjects),
getStrokeMenu(selectedParent, selectedObjects)
];
}
if (toolbar['text-style'].length === 0) {
toolbar['text-style'] = [
getTextColorMenu(selectedParent, selectedObjects),
getTextSizeMenu(selectedParent, selectedObjects)
];
}
@@ -517,6 +599,12 @@ define(['lodash'], function (_) {
toolbar.remove = [getRemoveButton(selectedParent, selectionPath, selectedObjects)];
}
} else if (layoutItem.type === 'box-view') {
if (toolbar.style.length < 2) {
toolbar.style = [
getFillMenu(selectedParent, selectedObjects),
getStrokeMenu(selectedParent, selectedObjects)
];
}
if (toolbar.position.length === 0) {
toolbar.position = [
getStackOrder(selectedParent, selectionPath),
@@ -530,6 +618,11 @@ define(['lodash'], function (_) {
toolbar.remove = [getRemoveButton(selectedParent, selectionPath, selectedObjects)];
}
} else if (layoutItem.type === 'image-view') {
if (toolbar.style.length === 0) {
toolbar.style = [
getStrokeMenu(selectedParent, selectedObjects)
];
}
if (toolbar.position.length === 0) {
toolbar.position = [
getStackOrder(selectedParent, selectionPath),
@@ -539,10 +632,18 @@ define(['lodash'], function (_) {
getWidthInput(selectedParent, selectedObjects)
];
}
if (toolbar.url.length === 0) {
toolbar.url = [getURLButton(selectedParent, selectedObjects)];
}
if (toolbar.remove.length === 0) {
toolbar.remove = [getRemoveButton(selectedParent, selectionPath, selectedObjects)];
}
} else if (layoutItem.type === 'line-view') {
if (toolbar.style.length === 0) {
toolbar.style = [
getStrokeMenu(selectedParent, selectedObjects)
];
}
if (toolbar.position.length === 0) {
toolbar.position = [
getStackOrder(selectedParent, selectionPath),

View File

@@ -23,20 +23,20 @@
<template>
<div
v-if="isEditing"
class="c-inspect-properties"
class="c-properties"
>
<div class="c-inspect-properties__header">
<div class="c-properties__header">
Alphanumeric Format
</div>
<ul class="c-inspect-properties__section">
<li class="c-inspect-properties__row">
<ul class="c-properties__section">
<li class="c-properties__row">
<div
class="c-inspect-properties__label"
class="c-properties__label"
title="Printf formatting for the selected telemetry"
>
<label for="telemetryPrintfFormat">Format</label>
</div>
<div class="c-inspect-properties__value">
<div class="c-properties__value">
<input
id="telemetryPrintfFormat"
type="text"
@@ -85,13 +85,7 @@ export default {
return;
}
let layoutItem = selection[0][0].context.layoutItem;
if (!layoutItem) {
return;
}
let format = layoutItem.format;
let format = selection[0][0].context.layoutItem.format;
this.nonMixedFormat = selection.every(selectionPath => {
return selectionPath[0].context.layoutItem.format === format;
});

View File

@@ -29,7 +29,6 @@
>
<div
class="c-box-view"
:class="[styleClass]"
:style="style"
></div>
</layout-frame>
@@ -37,13 +36,13 @@
<script>
import LayoutFrame from './LayoutFrame.vue'
import conditionalStylesMixin from '../mixins/objectStyles-mixin';
import conditionalStylesMixin from '../mixins/conditionalStyles-mixin';
export default {
makeDefinition() {
return {
fill: '#717171',
stroke: '',
stroke: 'transparent',
x: 1,
y: 1,
width: 10,
@@ -79,7 +78,7 @@ export default {
} else {
return {
backgroundColor: this.item.fill,
border: this.item.stroke ? '1px solid ' + this.item.stroke : ''
border: '1px solid ' + this.item.stroke
};
}
}

View File

@@ -68,6 +68,7 @@
<script>
import uuid from 'uuid';
import SubobjectView from './SubobjectView.vue'
import TelemetryView from './TelemetryView.vue'
import BoxView from './BoxView.vue'
@@ -374,9 +375,7 @@ export default {
indices.push(selectedItem[0].context.index);
this.untrackItem(selectedItem[0].context.layoutItem);
});
while(indices.length) {
this.layoutItems.splice(indices.pop(), 1);
}
_.pullAt(this.layoutItems, indices);
this.mutate("configuration.items", this.layoutItems);
this.$el.click();
},
@@ -513,7 +512,7 @@ export default {
}
},
updateTelemetryFormat(item, format) {
let index = this.layoutItems.findIndex(item);
let index = _.findIndex(this.layoutItems, item);
item.format = format;
this.mutate(`configuration.items[${index}]`, item);
}

View File

@@ -29,7 +29,6 @@
>
<div
class="c-image-view"
:class="[styleClass]"
:style="style"
></div>
</layout-frame>
@@ -37,7 +36,7 @@
<script>
import LayoutFrame from './LayoutFrame.vue'
import conditionalStylesMixin from "../mixins/objectStyles-mixin";
import conditionalStylesMixin from "../mixins/conditionalStyles-mixin";
export default {
makeDefinition(openmct, gridSize, element) {
@@ -74,18 +73,10 @@ export default {
},
computed: {
style() {
let backgroundImage = 'url(' + this.item.url + ')';
let border = '1px solid ' + this.item.stroke;
if (this.itemStyle) {
if (this.itemStyle.imageUrl !== undefined) {
backgroundImage = 'url(' + this.itemStyle.imageUrl + ')';
}
border = this.itemStyle.border;
}
return {
backgroundImage,
border
};
backgroundImage: 'url(' + this.item.url + ')',
border: this.itemStyle ? this.itemStyle.border : '1px solid ' + this.item.stroke
}
}
},
watch: {

View File

@@ -23,7 +23,6 @@
<template>
<div
class="l-layout__frame c-frame no-frame"
:class="[styleClass]"
:style="style"
>
<svg
@@ -61,7 +60,7 @@
<script>
import conditionalStylesMixin from "../mixins/objectStyles-mixin";
import conditionalStylesMixin from "../mixins/conditionalStyles-mixin";
const START_HANDLE_QUADRANTS = {
1: 'c-frame-edit__handle--sw',
@@ -127,11 +126,8 @@ export default {
return {x, y, x2, y2};
},
stroke() {
if (this.itemStyle) {
if (this.itemStyle.border) {
return this.itemStyle.border.replace('1px solid ', '');
}
return '';
if (this.itemStyle && this.itemStyle.border) {
return this.itemStyle.border.replace('1px solid ', '');
} else {
return this.item.stroke;
}

View File

@@ -45,7 +45,7 @@ import LayoutFrame from './LayoutFrame.vue'
const MINIMUM_FRAME_SIZE = [320, 180],
DEFAULT_DIMENSIONS = [10, 10],
DEFAULT_POSITION = [1, 1],
DEFAULT_HIDDEN_FRAME_TYPES = ['hyperlink', 'summary-widget', 'conditionWidget'];
DEFAULT_HIDDEN_FRAME_TYPES = ['hyperlink', 'summary-widget'];
function getDefaultDimensions(gridSize) {
return MINIMUM_FRAME_SIZE.map((min, index) => {

View File

@@ -30,13 +30,13 @@
<div
v-if="domainObject"
class="c-telemetry-view"
:class="styleClass"
:style="styleObject"
@contextmenu.prevent="showContextMenu"
>
<div
v-if="showLabel"
class="c-telemetry-view__label"
:style="conditionalStyle"
>
<div class="c-telemetry-view__label-text">
{{ domainObject.name }}
@@ -48,6 +48,7 @@
:title="fieldName"
class="c-telemetry-view__value"
:class="[telemetryClass]"
:style="!telemetryClass && conditionalStyle"
>
<div class="c-telemetry-view__value-text">
{{ telemetryValue }}
@@ -60,7 +61,7 @@
<script>
import LayoutFrame from './LayoutFrame.vue'
import printj from 'printj'
import conditionalStylesMixin from "../mixins/objectStyles-mixin";
import StyleRuleManager from "../../condition/StyleRuleManager";
const DEFAULT_TELEMETRY_DIMENSIONS = [10, 5],
DEFAULT_POSITION = [1, 1],
@@ -79,8 +80,8 @@ export default {
height: DEFAULT_TELEMETRY_DIMENSIONS[1],
displayMode: 'all',
value: metadata.getDefaultDisplayValue(),
stroke: "",
fill: "",
stroke: "transparent",
fill: "transparent",
color: "",
size: "13px"
};
@@ -89,7 +90,6 @@ export default {
components: {
LayoutFrame
},
mixins: [conditionalStylesMixin],
props: {
item: {
type: Object,
@@ -112,7 +112,8 @@ export default {
datum: undefined,
formats: undefined,
domainObject: undefined,
currentObjectPath: undefined
currentObjectPath: undefined,
conditionalStyle: ''
}
},
computed: {
@@ -125,10 +126,12 @@ export default {
return displayMode === 'all' || displayMode === 'value';
},
styleObject() {
return Object.assign({}, {
return {
backgroundColor: this.item.fill,
borderColor: this.item.stroke,
color: this.item.color,
fontSize: this.item.size
}, this.itemStyle);
}
},
fieldName() {
return this.valueMetadata && this.valueMetadata.name;
@@ -183,6 +186,16 @@ export default {
this.removeSelectable();
}
if (this.unlistenStyles) {
this.unlistenStyles();
}
if (this.styleRuleManager) {
this.styleRuleManager.destroy();
this.styleRuleManager.off('conditionalStyleUpdated', this.updateStyle.bind(this));
delete this.styleRuleManager;
}
this.openmct.time.off("bounds", this.refreshData);
},
methods: {
@@ -225,6 +238,7 @@ export default {
},
setObject(domainObject) {
this.domainObject = domainObject;
this.initConditionalStyles();
this.keyString = this.openmct.objects.makeKeyString(domainObject.identifier);
this.metadata = this.openmct.telemetry.getMetadata(this.domainObject);
this.limitEvaluator = this.openmct.telemetry.limitEvaluator(this.domainObject);
@@ -249,6 +263,21 @@ export default {
},
showContextMenu(event) {
this.openmct.contextMenu._showContextMenuForObjectPath(this.currentObjectPath, event.x, event.y, CONTEXT_MENU_ACTIONS);
},
initConditionalStyles() {
this.styleRuleManager = new StyleRuleManager(this.domainObject.configuration.conditionalStyle, this.openmct);
this.styleRuleManager.on('conditionalStyleUpdated', this.updateStyle.bind(this));
if (this.unlistenStyles) {
this.unlistenStyles();
}
this.unlistenStyles = this.openmct.objects.observe(this.domainObject, 'configuration.conditionalStyle', (newConditionalStyle) => {
//Updating conditional styles in the inspector view will trigger this so that the changes are reflected immediately
this.styleRuleManager.updateConditionalStyleConfig(newConditionalStyle);
});
},
updateStyle(styleObj) {
this.conditionalStyle = styleObj;
}
}
}

View File

@@ -29,23 +29,22 @@
>
<div
class="c-text-view"
:class="[styleClass]"
:style="style"
>
<div class="c-text-view__text">{{ item.text }}</div>
{{ item.text }}
</div>
</layout-frame>
</template>
<script>
import LayoutFrame from './LayoutFrame.vue'
import conditionalStylesMixin from "../mixins/objectStyles-mixin";
import conditionalStylesMixin from "../mixins/conditionalStyles-mixin";
export default {
makeDefinition(openmct, gridSize, element) {
return {
fill: '',
stroke: '',
fill: 'transparent',
stroke: 'transparent',
size: '13px',
color: '',
x: 1,
@@ -79,9 +78,16 @@ export default {
},
computed: {
style() {
return Object.assign({
fontSize: this.item.size
}, this.itemStyle);
if (this.itemStyle) {
return this.itemStyle;
} else {
return {
backgroundColor: this.item.fill,
borderColor: this.item.stroke,
color: this.item.color,
fontSize: this.item.size
};
}
}
},
watch: {

View File

@@ -16,8 +16,6 @@
.is-editing {
/******************* STYLES FOR C-FRAME WHILE EDITING */
.c-frame {
border: 1px solid rgba($editFrameColorHov, 0.3);
&:not([s-selected]) {
&:hover {
border: $editFrameBorderHov;

View File

@@ -1,8 +1,6 @@
.c-text-view {
display: flex;
align-items: center; // Vertically center text
overflow: hidden;
padding: $interiorMargin;
align-items: stretch;
.c-frame & {
@include abs();

View File

@@ -0,0 +1,54 @@
import StyleRuleManager from "@/plugins/condition/StyleRuleManager";
export default {
inject: ['openmct'],
data() {
return {
itemStyle: this.itemStyle
}
},
mounted() {
this.domainObject = this.$parent.domainObject;
this.itemId = this.item.id;
this.conditionalStyle = this.getConditionalStyleForItem(this.domainObject.configuration.conditionalStyle);
this.initConditionalStyles();
},
destroyed() {
if (this.stopListeningConditionalStyles) {
this.stopListeningConditionalStyles();
}
},
methods: {
getConditionalStyleForItem(conditionalStyle) {
if (conditionalStyle) {
return conditionalStyle[this.itemId];
} else {
return undefined;
}
},
initConditionalStyles() {
if (!this.styleRuleManager) {
this.styleRuleManager = new StyleRuleManager(this.conditionalStyle, this.openmct);
this.styleRuleManager.on('conditionalStyleUpdated', this.updateStyle.bind(this));
} else {
this.styleRuleManager.updateConditionalStyleConfig(this.conditionalStyle);
}
if (this.stopListeningConditionalStyles) {
this.stopListeningConditionalStyles();
}
this.stopListeningConditionalStyles = this.openmct.objects.observe(this.domainObject, 'configuration.conditionalStyle', (newConditionalStyle) => {
//Updating conditional styles in the inspector view will trigger this so that the changes are reflected immediately
let newItemConditionalStyle = this.getConditionalStyleForItem(newConditionalStyle);
if (this.conditionalStyle !== newItemConditionalStyle) {
this.conditionalStyle = newItemConditionalStyle;
this.styleRuleManager.updateConditionalStyleConfig(this.conditionalStyle);
}
});
},
updateStyle(style) {
this.itemStyle = style;
}
}
};

View File

@@ -1,78 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import StyleRuleManager from "@/plugins/condition/StyleRuleManager";
import {getStylesWithoutNoneValue} from "@/plugins/condition/utils/styleUtils";
export default {
inject: ['openmct'],
data() {
return {
itemStyle: undefined,
styleClass: ''
}
},
mounted() {
this.parentDomainObject = this.$parent.domainObject;
this.itemId = this.item.id;
this.objectStyle = this.getObjectStyleForItem(this.parentDomainObject.configuration.objectStyles);
this.initObjectStyles();
},
destroyed() {
if (this.stopListeningObjectStyles) {
this.stopListeningObjectStyles();
}
},
methods: {
getObjectStyleForItem(objectStyle) {
if (objectStyle) {
return objectStyle[this.itemId] ? Object.assign({}, objectStyle[this.itemId]) : undefined;
} else {
return undefined;
}
},
initObjectStyles() {
if (!this.styleRuleManager) {
this.styleRuleManager = new StyleRuleManager(this.objectStyle, this.openmct, this.updateStyle.bind(this));
} else {
this.styleRuleManager.updateObjectStyleConfig(this.objectStyle);
}
if (this.stopListeningObjectStyles) {
this.stopListeningObjectStyles();
}
this.stopListeningObjectStyles = this.openmct.objects.observe(this.parentDomainObject, 'configuration.objectStyles', (newObjectStyle) => {
//Updating object styles in the inspector view will trigger this so that the changes are reflected immediately
let newItemObjectStyle = this.getObjectStyleForItem(newObjectStyle);
if (this.objectStyle !== newItemObjectStyle) {
this.objectStyle = newItemObjectStyle;
this.styleRuleManager.updateObjectStyleConfig(this.objectStyle);
}
});
},
updateStyle(style) {
this.itemStyle = getStylesWithoutNoneValue(style);
this.styleClass = this.itemStyle && this.itemStyle.isStyleInvisible;
}
}
};

View File

@@ -1,17 +1,17 @@
<template>
<div class="c-inspect-properties__section c-filter-settings">
<div class="c-properties__section c-filter-settings">
<li
v-for="(filter, index) in filterField.filters"
:key="index"
class="c-inspect-properties__row c-filter-settings__setting"
class="c-properties__row c-filter-settings__setting"
>
<div
class="c-inspect-properties__label label"
class="c-properties__label label"
:disabled="useGlobal"
>
{{ filterField.name }} =
</div>
<div class="c-inspect-properties__value value">
<div class="c-properties__value value">
<!-- EDITING -->
<!-- String input, editing -->
<template v-if="!filter.possibleValues && isEditing">

View File

@@ -26,17 +26,17 @@
</div>
<div v-if="expanded">
<ul class="c-inspect-properties">
<ul class="c-properties">
<div
v-if="!isEditing && persistedFilters.useGlobal"
class="c-inspect-properties__label span-all"
class="c-properties__label span-all"
>
Uses global filter
</div>
<div
v-if="isEditing"
class="c-inspect-properties__label span-all"
class="c-properties__label span-all"
>
<toggle-switch
:id="keyString"

View File

@@ -55,7 +55,6 @@ export default {
},
computed: {
hasActiveFilters() {
console.log('hasActiveFilters');
// Should be true when the user has entered any filter values.
return Object.values(this.persistedFilters).some(filters => {
return Object.values(filters).some(comparator => {
@@ -64,7 +63,6 @@ export default {
});
},
hasMixedFilters() {
console.log('hasMixedFilters')
// Should be true when filter values are mixed.
let filtersToCompare = _.omit(this.persistedFilters[Object.keys(this.persistedFilters)[0]], [USE_GLOBAL]);
return Object.values(this.persistedFilters).some(filters => {
@@ -72,7 +70,6 @@ export default {
});
},
label() {
console.log('label');
if (this.hasActiveFilters) {
if (this.hasMixedFilters) {
return FILTER_VIEW_TITLE_MIXED;

View File

@@ -23,7 +23,7 @@
</div>
<ul
v-if="expanded"
class="c-inspect-properties"
class="c-properties"
>
<filter-field
v-for="metadatum in globalMetadata"

View File

@@ -7,7 +7,7 @@
}
.c-filter-tree {
// Filters UI uses a tree-based structure
.c-inspect-properties {
.c-properties {
// Add extra margin to account for filter-indicator
margin-left: 38px;
}

View File

@@ -313,4 +313,8 @@
margin: 0;
}
}
.c-object-view {
display: contents;
}
}

View File

@@ -26,7 +26,6 @@ export default function ImageryViewProvider(openmct) {
return {
show: function (element) {
component = new Vue({
el: element,
components: {
ImageryViewLayout
},
@@ -34,6 +33,7 @@ export default function ImageryViewProvider(openmct) {
openmct,
domainObject
},
el: element,
template: '<imagery-view-layout ref="ImageryLayout"></imagery-view-layout>'
});
},

View File

@@ -220,7 +220,7 @@ export default {
return;
}
const index = _.sortedIndexBy(this.imageHistory, datum, this.timeFormat.format.bind(this.timeFormat));
const index = _.sortedIndex(this.imageHistory, datum, this.timeFormat.format.bind(this.timeFormat));
this.imageHistory.splice(index, 0, datum);
},
updateValues(datum) {

View File

@@ -1,21 +0,0 @@
<template>
<div class="c-menu">
<ul>
<li
v-for="(item, index) in popupMenuItems"
:key="index"
:class="item.cssClass"
:title="item.name"
@click="item.callback"
>
{{ item.name }}
</li>
</ul>
</div>
</template>
<script>
export default {
inject: ['popupMenuItems']
}
</script>

View File

@@ -1,239 +0,0 @@
<template>
<div class="c-snapshot c-ne__embed">
<div v-if="embed.snapshot"
class="c-ne__embed__snap-thumb"
@click="openSnapshot()"
>
<img :src="embed.snapshot.src">
</div>
<div class="c-ne__embed__info">
<div class="c-ne__embed__name">
<a class="c-ne__embed__link"
:class="embed.cssClass"
@click="changeLocation"
>{{ embed.name }}</a>
<PopupMenu :popup-menu-items="popupMenuItems" />
</div>
<div v-if="embed.snapshot"
class="c-ne__embed__time"
>
{{ formatTime(embed.createdOn, 'YYYY-MM-DD HH:mm:ss') }}
</div>
</div>
</div>
</template>
<script>
import Moment from 'moment';
import PopupMenu from './popup-menu.vue';
import PreviewAction from '../../../ui/preview/PreviewAction';
import Painterro from 'painterro';
import RemoveDialog from '../utils/removeDialog';
import SnapshotTemplate from './snapshot-template.html';
import Vue from 'vue';
export default {
inject: ['openmct'],
components: {
PopupMenu
},
props: {
embed: {
type: Object,
default() {
return {};
}
},
removeActionString: {
type: String,
default() {
return 'Remove This Embed';
}
}
},
data() {
return {
popupMenuItems: []
}
},
watch: {
},
mounted() {
this.addPopupMenuItems();
},
methods: {
addPopupMenuItems() {
const removeEmbed = {
cssClass: 'icon-trash',
name: this.removeActionString,
callback: this.getRemoveDialog.bind(this)
}
const preview = {
cssClass: 'icon-eye-open',
name: 'Preview',
callback: this.previewEmbed.bind(this)
}
this.popupMenuItems = [removeEmbed, preview];
},
annotateSnapshot() {
const self = this;
let save = false;
let painterroInstance = {};
const annotateVue = new Vue({
template: '<div id="snap-annotation"></div>'
});
let annotateOverlay = self.openmct.overlays.overlay({
element: annotateVue.$mount().$el,
size: 'large',
dismissable: false,
buttons: [
{
label: 'Cancel',
callback: function () {
save = false;
painterroInstance.save();
annotateOverlay.dismiss();
}
},
{
label: 'Save',
callback: function () {
save = true;
painterroInstance.save();
annotateOverlay.dismiss();
}
}
],
onDestroy: function () {
annotateVue.$destroy(true);
}
});
painterroInstance = Painterro({
id: 'snap-annotation',
activeColor: '#ff0000',
activeColorAlpha: 1.0,
activeFillColor: '#fff',
activeFillColorAlpha: 0.0,
backgroundFillColor: '#000',
backgroundFillColorAlpha: 0.0,
defaultFontSize: 16,
defaultLineWidth: 2,
defaultTool: 'ellipse',
hiddenTools: ['save', 'open', 'close', 'eraser', 'pixelize', 'rotate', 'settings', 'resize'],
translation: {
name: 'en',
strings: {
lineColor: 'Line',
fillColor: 'Fill',
lineWidth: 'Size',
textColor: 'Color',
fontSize: 'Size',
fontStyle: 'Style'
}
},
saveHandler: function (image, done) {
if (save) {
const url = image.asBlob();
const reader = new window.FileReader();
reader.readAsDataURL(url);
reader.onloadend = function () {
const snapshot = reader.result;
const snapshotObject = {
src: snapshot,
type: url.type,
size: url.size,
modified: Date.now()
};
self.embed.snapshot = snapshotObject;
self.updateEmbed(self.embed);
};
} else {
console.log('You cancelled the annotation!!!');
}
done(true);
}
}).show(this.embed.snapshot.src);
},
changeLocation() {
this.openmct.time.stopClock();
this.openmct.time.bounds({
start: this.embed.bounds.start,
end: this.embed.bounds.end
});
const link = this.embed.historicLink;
if (!link) {
return;
}
window.location.href = link;
const message = 'Time bounds changed to fixed timespan mode';
this.openmct.notifications.alert(message);
},
formatTime(unixTime, timeFormat) {
return Moment.utc(unixTime).format(timeFormat);
},
getRemoveDialog() {
const options = {
name: this.removeActionString,
callback: this.removeEmbed.bind(this)
}
const removeDialog = new RemoveDialog(this.openmct, options);
removeDialog.show();
},
openSnapshot() {
const self = this;
const snapshot = new Vue({
data: () => {
return {
embed: self.embed
};
},
methods: {
formatTime: self.formatTime,
annotateSnapshot: self.annotateSnapshot
},
template: SnapshotTemplate
});
const snapshotOverlay = this.openmct.overlays.overlay({
element: snapshot.$mount().$el,
onDestroy: () => { snapshot.$destroy(true) },
size: 'large',
dismissable: true,
buttons: [
{
label: 'Done',
emphasis: true,
callback: () => {
snapshotOverlay.dismiss();
}
}
]
});
},
previewEmbed() {
const self = this;
const previewAction = new PreviewAction(self.openmct);
previewAction.invoke(JSON.parse(self.embed.objectPath));
},
removeEmbed(success) {
if (!success) {
return;
}
this.$emit('removeEmbed', this.embed.id);
},
updateEmbed(embed) {
this.$emit('updateEmbed', embed);
}
}
}
</script>

View File

@@ -1,316 +0,0 @@
<template>
<div class="c-notebook__entry c-ne has-local-controls"
@dragover="dragover"
@drop.capture="dropCapture"
@drop.prevent="dropOnEntry(entry.id, $event)"
>
<div class="c-ne__time-and-content">
<div class="c-ne__time">
<span>{{ formatTime(entry.createdOn, 'YYYY-MM-DD') }}</span>
<span>{{ formatTime(entry.createdOn, 'HH:mm:ss') }}</span>
</div>
<div class="c-ne__content">
<div :id="entry.id"
class="c-ne__text"
:class="{'c-input-inline' : !readOnly }"
:contenteditable="!readOnly"
:style="!entry.text.length ? defaultEntryStyle : ''"
@blur="textBlur($event, entry.id)"
@focus="textFocus($event, entry.id)"
>{{ entry.text.length ? entry.text : defaultText }}</div>
<div class="c-snapshots c-ne__embeds">
<NotebookEmbed v-for="embed in entry.embeds"
:key="embed.id"
:embed="embed"
:entry="entry"
@removeEmbed="removeEmbed"
@updateEmbed="updateEmbed"
/>
</div>
</div>
</div>
<div v-if="!readOnly"
class="c-ne__local-controls--hidden"
>
<button class="c-icon-button c-icon-button--major icon-trash"
title="Delete this entry"
@click="deleteEntry"
>
</button>
</div>
<div v-if="readOnly"
class="c-ne__section-and-page"
>
<a class="c-click-link"
@click="navigateToSection()"
>
{{ result.section.name }}
</a>
<span class="icon-arrow-right"></span>
<a class="c-click-link"
@click="navigateToPage()"
>
{{ result.page.name }}
</a>
</div>
</div>
</template>
<script>
import NotebookEmbed from './notebook-embed.vue';
import { createNewEmbed, getEntryPosById, getNotebookEntries } from '../utils/notebook-entries';
import Moment from 'moment';
export default {
inject: ['openmct', 'snapshotContainer'],
components: {
NotebookEmbed
},
props: {
domainObject: {
type: Object,
default() {
return {};
}
},
entry: {
type: Object,
default() {
return {};
}
},
result: {
type: Object,
default() {
return {};
}
},
selectedPage: {
type: Object,
default() {
return {};
}
},
selectedSection: {
type: Object,
default() {
return {};
}
},
readOnly: {
type: Boolean,
default() {
return true;
}
}
},
data() {
return {
currentEntryValue: '',
defaultEntryStyle: {
fontStyle: 'italic',
color: '#6e6e6e'
},
defaultText: 'add description'
}
},
watch: {
entry() {
},
readOnly(readOnly) {
},
selectedSection(selectedSection) {
},
selectedPage(selectedSection) {
}
},
mounted() {
this.updateEntries = this.updateEntries.bind(this);
},
beforeDestory() {
},
methods: {
deleteEntry() {
const self = this;
if (!self.domainObject || !self.selectedSection || !self.selectedPage || !self.entry.id) {
return;
}
const entryPosById = this.entryPosById(this.entry.id);
if (entryPosById === -1) {
return;
}
const dialog = this.openmct.overlays.dialog({
iconClass: 'alert',
message: 'This action will permanently delete this entry. Do you wish to continue?',
buttons: [
{
label: "Ok",
emphasis: true,
callback: () => {
const entries = getNotebookEntries(self.domainObject, self.selectedSection, self.selectedPage);
entries.splice(entryPosById, 1);
this.updateEntries(entries);
dialog.dismiss();
}
},
{
label: "Cancel",
callback: () => {
dialog.dismiss();
}
}
]
});
},
dragover() {
event.preventDefault();
event.dataTransfer.dropEffect = "copy";
},
dropCapture(event) {
const isEditing = this.openmct.editor.isEditing();
if (isEditing) {
this.openmct.editor.cancel();
}
},
dropOnEntry(entryId, $event) {
event.stopImmediatePropagation();
if (!this.domainObject || !this.selectedSection || !this.selectedPage) {
return;
}
const snapshotId = $event.dataTransfer.getData('snapshot/id');
if (snapshotId.length) {
this.moveSnapshot(snapshotId);
return;
}
const data = $event.dataTransfer.getData('openmct/domain-object-path');
const objectPath = JSON.parse(data);
const entryPos = this.entryPosById(entryId);
const bounds = this.openmct.time.bounds();
const snapshotMeta = {
bounds,
link: null,
objectPath,
openmct: this.openmct
}
const newEmbed = createNewEmbed(snapshotMeta);
const entries = getNotebookEntries(this.domainObject, this.selectedSection, this.selectedPage);
const currentEntryEmbeds = entries[entryPos].embeds;
currentEntryEmbeds.push(newEmbed);
this.updateEntries(entries);
},
entryPosById(entryId) {
return getEntryPosById(entryId, this.domainObject, this.selectedSection, this.selectedPage);
},
findPositionInArray(array, id) {
let position = -1;
array.some((item, index) => {
const found = item.id === id;
if (found) {
position = index;
}
return found;
});
return position;
},
formatTime(unixTime, timeFormat) {
return Moment(unixTime).format(timeFormat);
},
moveSnapshot(snapshotId) {
const snapshot = this.snapshotContainer.getSnapshot(snapshotId);
this.entry.embeds.push(snapshot);
this.updateEntry(this.entry);
this.snapshotContainer.removeSnapshot(snapshotId);
},
navigateToPage() {
this.$emit('changeSectionPage', {
sectionId: this.result.section.id,
pageId: this.result.page.id
});
},
navigateToSection() {
this.$emit('changeSectionPage', {
sectionId: this.result.section.id,
pageId: null
});
},
removeEmbed(id) {
const embedPosition = this.findPositionInArray(this.entry.embeds, id);
this.entry.embeds.splice(embedPosition, 1);
this.updateEntry(this.entry);
},
selectTextInsideElement(element) {
const range = document.createRange();
range.selectNodeContents(element);
var selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
},
textBlur($event, entryId) {
if (!this.domainObject || !this.selectedSection || !this.selectedPage) {
return;
}
const target = $event.target;
if (!target) {
return;
}
const entryPos = this.entryPosById(entryId);
const value = target.textContent.trim();
if (this.currentEntryValue !== value) {
const entries = getNotebookEntries(this.domainObject, this.selectedSection, this.selectedPage);
entries[entryPos].text = value;
this.updateEntries(entries);
}
},
textFocus($event) {
if (this.readOnly || !this.domainObject || !this.selectedSection || !this.selectedPage) {
return;
}
const target = $event.target
this.currentEntryValue = target ? target.innerText : '';
if (!this.entry.text.length) {
this.selectTextInsideElement(target);
}
},
updateEmbed(newEmbed) {
let embed = this.entry.embeds.find(e => e.id === newEmbed.id);
if (!embed) {
return;
}
embed = newEmbed;
this.updateEntry(this.entry);
},
updateEntry(newEntry) {
if (!this.domainObject || !this.selectedSection || !this.selectedPage) {
return;
}
const entries = getNotebookEntries(this.domainObject, this.selectedSection, this.selectedPage);
entries.forEach(entry => {
if (entry.id === newEntry.id) {
entry = newEntry;
}
});
this.updateEntries(entries);
},
updateEntries(entries) {
this.$emit('updateEntries', entries);
}
}
}
</script>

View File

@@ -1,114 +0,0 @@
<template>
<div class="c-menu-button c-ctrl-wrapper c-ctrl-wrapper--menus-left">
<button
class="c-button--menu icon-notebook"
title="Take a Notebook Snapshot"
@click="setNotebookTypes"
@click.stop="toggleMenu"
>
<span class="c-button__label"></span>
</button>
<div
v-show="showMenu"
class="c-menu"
>
<ul>
<li
v-for="(type, index) in notebookTypes"
:key="index"
:class="type.cssClass"
:title="type.name"
@click="snapshot(type)"
>
{{ type.name }}
</li>
</ul>
</div>
</div>
</template>
<script>
import Snapshot from '../snapshot';
import { clearDefaultNotebook, getDefaultNotebook } from '../utils/notebook-storage';
import { NOTEBOOK_DEFAULT, NOTEBOOK_SNAPSHOT } from '../notebook-constants';
export default {
inject: ['openmct'],
props: {
domainObject: {
type: Object,
default() {
return {};
}
}
},
data() {
return {
notebookSnapshot: null,
notebookTypes: [],
showMenu: false
}
},
mounted() {
this.notebookSnapshot = new Snapshot(this.openmct);
document.addEventListener('click', this.hideMenu);
},
destroyed() {
document.removeEventListener('click', this.hideMenu);
},
methods: {
async setNotebookTypes() {
const notebookTypes = [];
let defaultPath = '';
const defaultNotebook = getDefaultNotebook();
if (defaultNotebook) {
const domainObject = await this.openmct.objects.get(defaultNotebook.notebookMeta.identifier)
.then(d => d);
if (!domainObject.location) {
clearDefaultNotebook();
} else {
defaultPath = `${domainObject.name} - ${defaultNotebook.section.name} - ${defaultNotebook.page.name}`;
}
}
if (defaultPath.length !== 0) {
notebookTypes.push({
cssClass: 'icon-notebook',
name: `Save to Notebook ${defaultPath}`,
type: NOTEBOOK_DEFAULT
});
}
notebookTypes.push({
cssClass: 'icon-notebook',
name: 'Save to Notebook Snapshots',
type: NOTEBOOK_SNAPSHOT
});
this.notebookTypes = notebookTypes;
},
toggleMenu() {
this.showMenu = !this.showMenu;
},
hideMenu() {
this.showMenu = false;
},
snapshot(notebook) {
let element = document.getElementsByClassName("l-shell__main-container")[0];
const bounds = this.openmct.time.bounds();
const objectPath = this.openmct.router.path;
const snapshotMeta = {
bounds,
link: window.location.href,
objectPath,
openmct: this.openmct
};
this.notebookSnapshot.capture(snapshotMeta, notebook.type, element);
}
}
}
</script>

Some files were not shown because too many files have changed in this diff Show More