mirror of
https://github.com/nasa/openmct.git
synced 2024-12-18 20:57:53 +00:00
parent
e7e5116773
commit
ee4a81bdfd
@ -1,5 +1,5 @@
|
||||
<!--
|
||||
Open MCT, Copyright (c) 2014-2018, United States Government
|
||||
Open MCT, Copyright (c) 2014-2020, United States Government
|
||||
as represented by the Administrator of the National Aeronautics and Space
|
||||
Administration. All rights reserved.
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||
* Open MCT, Copyright (c) 2014-2020, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
|
@ -9,7 +9,8 @@ define([
|
||||
values: [
|
||||
{
|
||||
key: "name",
|
||||
name: "Name"
|
||||
name: "Name",
|
||||
format: "string"
|
||||
},
|
||||
{
|
||||
key: "utc",
|
||||
|
@ -64,6 +64,7 @@
|
||||
"request": "^2.69.0",
|
||||
"split": "^1.0.0",
|
||||
"style-loader": "^1.0.1",
|
||||
"uuid": "^3.3.3",
|
||||
"v8-compile-cache": "^1.1.0",
|
||||
"vue": "2.5.6",
|
||||
"vue-loader": "^15.2.6",
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||
* Open MCT, Copyright (c) 2014-2020, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
@ -264,6 +264,8 @@ define([
|
||||
this.install(this.plugins.GoToOriginalAction());
|
||||
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);
|
||||
|
@ -21,6 +21,7 @@
|
||||
*****************************************************************************/
|
||||
|
||||
<template>
|
||||
<div class="c-lad-table-wrapper">
|
||||
<table class="c-table c-lad-table">
|
||||
<thead>
|
||||
<tr>
|
||||
@ -37,6 +38,7 @@
|
||||
/>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
309
src/plugins/condition/Condition.js
Normal file
309
src/plugins/condition/Condition.js
Normal file
@ -0,0 +1,309 @@
|
||||
/*****************************************************************************
|
||||
* 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 EventEmitter from 'EventEmitter';
|
||||
import uuid from 'uuid';
|
||||
import TelemetryCriterion from "./criterion/TelemetryCriterion";
|
||||
import { TRIGGER } from "./utils/constants";
|
||||
import {computeCondition, computeConditionByLimit} from "./utils/evaluator";
|
||||
import AllTelemetryCriterion from "./criterion/AllTelemetryCriterion";
|
||||
|
||||
/*
|
||||
* conditionConfiguration = {
|
||||
* id: uuid,
|
||||
* trigger: 'any'/'all'/'not','xor',
|
||||
* criteria: [
|
||||
* {
|
||||
* telemetry: '',
|
||||
* operation: '',
|
||||
* input: [],
|
||||
* metadata: ''
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
*/
|
||||
export default class ConditionClass extends EventEmitter {
|
||||
|
||||
/**
|
||||
* Manages criteria and emits the result of - true or false - based on criteria evaluated.
|
||||
* @constructor
|
||||
* @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) {
|
||||
super();
|
||||
|
||||
this.openmct = openmct;
|
||||
this.conditionManager = conditionManager;
|
||||
this.id = conditionConfiguration.id;
|
||||
this.criteria = [];
|
||||
this.criteriaResults = {};
|
||||
this.result = undefined;
|
||||
this.latestTimestamp = {};
|
||||
|
||||
if (conditionConfiguration.configuration.criteria) {
|
||||
this.createCriteria(conditionConfiguration.configuration.criteria);
|
||||
}
|
||||
this.trigger = conditionConfiguration.configuration.trigger;
|
||||
this.conditionManager.on('broadcastTelemetry', this.handleBroadcastTelemetry, this);
|
||||
}
|
||||
|
||||
handleBroadcastTelemetry(datum) {
|
||||
if (!datum || !datum.id) {
|
||||
console.log('no data received');
|
||||
return;
|
||||
}
|
||||
this.criteria.forEach(criterion => {
|
||||
if (criterion.telemetry && (criterion.telemetry === 'all' || criterion.telemetry === 'any')) {
|
||||
criterion.handleSubscription(datum, this.conditionManager.telemetryObjects);
|
||||
} else {
|
||||
criterion.emit(`subscription:${datum.id}`, datum);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
update(conditionConfiguration) {
|
||||
this.updateTrigger(conditionConfiguration.configuration.trigger);
|
||||
this.updateCriteria(conditionConfiguration.configuration.criteria);
|
||||
}
|
||||
|
||||
updateTrigger(trigger) {
|
||||
if (this.trigger !== trigger) {
|
||||
this.trigger = trigger;
|
||||
this.handleConditionUpdated();
|
||||
}
|
||||
}
|
||||
|
||||
generateCriterion(criterionConfiguration) {
|
||||
return {
|
||||
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 || ''
|
||||
};
|
||||
}
|
||||
|
||||
createCriteria(criterionConfigurations) {
|
||||
criterionConfigurations.forEach((criterionConfiguration) => {
|
||||
this.addCriterion(criterionConfiguration);
|
||||
});
|
||||
}
|
||||
|
||||
updateCriteria(criterionConfigurations) {
|
||||
this.destroyCriteria();
|
||||
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);
|
||||
}
|
||||
criterion.on('criterionUpdated', (obj) => this.handleCriterionUpdated(obj));
|
||||
criterion.on('criterionResultUpdated', (obj) => this.handleCriterionResult(obj));
|
||||
if (!this.criteria) {
|
||||
this.criteria = [];
|
||||
}
|
||||
this.criteria.push(criterion);
|
||||
return criterionConfigurationWithId.id;
|
||||
}
|
||||
|
||||
findCriterion(id) {
|
||||
let criterion;
|
||||
|
||||
for (let i=0, ii=this.criteria.length; i < ii; i ++) {
|
||||
if (this.criteria[i].id === id) {
|
||||
criterion = {
|
||||
item: this.criteria[i],
|
||||
index: i
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return criterion;
|
||||
}
|
||||
|
||||
updateCriterion(id, criterionConfiguration) {
|
||||
let found = this.findCriterion(id);
|
||||
if (found) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
destroyCriterion(id) {
|
||||
let found = this.findCriterion(id);
|
||||
if (found) {
|
||||
let criterion = found.item;
|
||||
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;
|
||||
}
|
||||
|
||||
handleCriterionUpdated(criterion) {
|
||||
let found = this.findCriterion(criterion.id);
|
||||
if (found) {
|
||||
this.criteria[found.index] = criterion.data;
|
||||
// TODO nothing is listening to this
|
||||
this.emitEvent('conditionUpdated', {
|
||||
trigger: this.trigger,
|
||||
criteria: this.criteria
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
updateCriteriaResults(eventData) {
|
||||
const id = eventData.id;
|
||||
|
||||
if (this.findCriterion(id)) {
|
||||
// The !! here is important to convert undefined to false otherwise the criteriaResults won't get deleted when the criteria is destroyed
|
||||
this.criteriaResults[id] = !!eventData.data.result;
|
||||
}
|
||||
}
|
||||
|
||||
handleCriterionResult(eventData) {
|
||||
this.updateCriteriaResults(eventData);
|
||||
this.handleConditionUpdated(eventData.data);
|
||||
}
|
||||
|
||||
requestLADConditionResult() {
|
||||
const criteriaResults = this.criteria
|
||||
.map(criterion => criterion.requestLAD({telemetryObjects: this.conditionManager.telemetryObjects}));
|
||||
|
||||
return Promise.all(criteriaResults)
|
||||
.then(results => {
|
||||
results.forEach(result => {
|
||||
this.updateCriteriaResults(result);
|
||||
this.latestTimestamp = this.getLatestTimestamp(this.latestTimestamp, result.data)
|
||||
});
|
||||
this.evaluate();
|
||||
|
||||
return {
|
||||
id: this.id,
|
||||
data: Object.assign({}, this.latestTimestamp, { result: this.result })
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getTelemetrySubscriptions() {
|
||||
return this.criteria.map(criterion => criterion.telemetryObjectIdAsString);
|
||||
}
|
||||
|
||||
handleConditionUpdated(datum) {
|
||||
// trigger an updated event so that consumers can react accordingly
|
||||
this.evaluate();
|
||||
this.emitEvent('conditionResultUpdated',
|
||||
Object.assign({}, datum, { result: this.result })
|
||||
);
|
||||
}
|
||||
|
||||
getCriteria() {
|
||||
return this.criteria;
|
||||
}
|
||||
|
||||
destroyCriteria() {
|
||||
let success = true;
|
||||
//looping through the array backwards since destroyCriterion modifies the criteria array
|
||||
for (let i=this.criteria.length-1; i >= 0; i--) {
|
||||
success = success && this.destroyCriterion(this.criteria[i].id);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
evaluate() {
|
||||
if (this.trigger && this.trigger === TRIGGER.XOR) {
|
||||
this.result = computeConditionByLimit(this.criteriaResults, 1);
|
||||
} else if (this.trigger && this.trigger === TRIGGER.NOT) {
|
||||
this.result = computeConditionByLimit(this.criteriaResults, 0);
|
||||
} else {
|
||||
this.result = computeCondition(this.criteriaResults, this.trigger === TRIGGER.ALL);
|
||||
}
|
||||
}
|
||||
|
||||
getLatestTimestamp(current, compare) {
|
||||
const timestamp = Object.assign({}, current);
|
||||
|
||||
this.openmct.time.getAllTimeSystems().forEach(timeSystem => {
|
||||
if (!timestamp[timeSystem.key]
|
||||
|| compare[timeSystem.key] > timestamp[timeSystem.key]
|
||||
) {
|
||||
timestamp[timeSystem.key] = compare[timeSystem.key];
|
||||
}
|
||||
});
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
emitEvent(eventName, data) {
|
||||
this.emit(eventName, {
|
||||
id: this.id,
|
||||
data: data
|
||||
});
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.conditionManager.off('broadcastTelemetry', this.handleBroadcastTelemetry, this);
|
||||
if (typeof this.stopObservingForChanges === 'function') {
|
||||
this.stopObservingForChanges();
|
||||
}
|
||||
this.destroyCriteria();
|
||||
}
|
||||
}
|
314
src/plugins/condition/ConditionManager.js
Normal file
314
src/plugins/condition/ConditionManager.js
Normal file
@ -0,0 +1,314 @@
|
||||
/*****************************************************************************
|
||||
* 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 Condition from "./Condition";
|
||||
import uuid from "uuid";
|
||||
import EventEmitter from 'EventEmitter';
|
||||
|
||||
export default class ConditionManager extends EventEmitter {
|
||||
constructor(conditionSetDomainObject, openmct) {
|
||||
super();
|
||||
this.openmct = openmct;
|
||||
this.conditionSetDomainObject = conditionSetDomainObject;
|
||||
this.timeAPI = this.openmct.time;
|
||||
this.latestTimestamp = {};
|
||||
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.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.broadcastTelemetry.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];
|
||||
}
|
||||
|
||||
initialize() {
|
||||
this.conditionResults = {};
|
||||
this.conditionClassCollection = [];
|
||||
if (this.conditionSetDomainObject.configuration.conditionCollection.length) {
|
||||
this.conditionSetDomainObject.configuration.conditionCollection.forEach((conditionConfiguration, index) => {
|
||||
this.initCondition(conditionConfiguration, index);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
updateConditionTelemetry() {
|
||||
this.conditionClassCollection.forEach((condition) => condition.updateTelemetry());
|
||||
}
|
||||
|
||||
updateCondition(conditionConfiguration, index) {
|
||||
let condition = this.conditionClassCollection[index];
|
||||
condition.update(conditionConfiguration);
|
||||
this.conditionSetDomainObject.configuration.conditionCollection[index] = conditionConfiguration;
|
||||
this.persistConditions();
|
||||
}
|
||||
|
||||
initCondition(conditionConfiguration, index) {
|
||||
let condition = new Condition(conditionConfiguration, this.openmct, this);
|
||||
condition.on('conditionResultUpdated', this.handleConditionResult.bind(this));
|
||||
if (index !== undefined) {
|
||||
this.conditionClassCollection.splice(index + 1, 0, condition);
|
||||
} else {
|
||||
this.conditionClassCollection.unshift(condition);
|
||||
}
|
||||
}
|
||||
|
||||
createCondition(conditionConfiguration) {
|
||||
let conditionObj;
|
||||
if (conditionConfiguration) {
|
||||
conditionObj = {
|
||||
...conditionConfiguration,
|
||||
id: uuid(),
|
||||
configuration: {
|
||||
...conditionConfiguration.configuration,
|
||||
name: `Copy of ${conditionConfiguration.configuration.name}`
|
||||
}
|
||||
};
|
||||
} else {
|
||||
conditionObj = {
|
||||
id: uuid(),
|
||||
configuration: {
|
||||
name: 'Unnamed Condition',
|
||||
output: 'false',
|
||||
trigger: 'all',
|
||||
criteria: [{
|
||||
telemetry: '',
|
||||
operation: '',
|
||||
input: [],
|
||||
metadata: ''
|
||||
}]
|
||||
},
|
||||
summary: ''
|
||||
};
|
||||
}
|
||||
|
||||
return conditionObj;
|
||||
}
|
||||
|
||||
addCondition() {
|
||||
this.createAndSaveCondition();
|
||||
}
|
||||
|
||||
cloneCondition(conditionConfiguration, index) {
|
||||
this.createAndSaveCondition(index, JSON.parse(JSON.stringify(conditionConfiguration)));
|
||||
}
|
||||
|
||||
createAndSaveCondition(index, conditionConfiguration) {
|
||||
const newCondition = this.createCondition(conditionConfiguration);
|
||||
if (index !== undefined) {
|
||||
this.conditionSetDomainObject.configuration.conditionCollection.splice(index + 1, 0, newCondition);
|
||||
} else {
|
||||
this.conditionSetDomainObject.configuration.conditionCollection.unshift(newCondition);
|
||||
}
|
||||
this.initCondition(newCondition, index);
|
||||
this.persistConditions();
|
||||
}
|
||||
|
||||
removeCondition(index) {
|
||||
let condition = this.conditionClassCollection[index];
|
||||
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 = [];
|
||||
reorderPlan.forEach((reorderEvent) => {
|
||||
let item = oldConditions[reorderEvent.oldIndex];
|
||||
newCollection.push(item);
|
||||
this.conditionSetDomainObject.configuration.conditionCollection = newCollection;
|
||||
});
|
||||
this.persistConditions();
|
||||
}
|
||||
|
||||
getCurrentCondition() {
|
||||
const conditionCollection = this.conditionSetDomainObject.configuration.conditionCollection;
|
||||
let currentCondition = conditionCollection[conditionCollection.length-1];
|
||||
|
||||
for (let i = 0; i < conditionCollection.length - 1; i++) {
|
||||
if (this.conditionResults[conditionCollection[i].id]) {
|
||||
//first condition to be true wins
|
||||
currentCondition = conditionCollection[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return currentCondition;
|
||||
}
|
||||
|
||||
updateConditionResults(resultObj) {
|
||||
if (!resultObj) {
|
||||
return;
|
||||
}
|
||||
|
||||
const id = resultObj.id;
|
||||
|
||||
if (this.findConditionById(id)) {
|
||||
this.conditionResults[id] = resultObj.data.result;
|
||||
}
|
||||
|
||||
this.updateTimestamp(resultObj.data);
|
||||
}
|
||||
|
||||
handleConditionResult(resultObj) {
|
||||
// update conditions results and then calculate the current condition
|
||||
this.updateConditionResults(resultObj);
|
||||
const currentCondition = this.getCurrentCondition();
|
||||
this.emit('conditionSetResultUpdated',
|
||||
Object.assign(
|
||||
{
|
||||
output: currentCondition.configuration.output,
|
||||
id: this.conditionSetDomainObject.identifier,
|
||||
conditionId: currentCondition.id
|
||||
},
|
||||
this.latestTimestamp
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
requestLADConditionSetOutput() {
|
||||
if (!this.conditionClassCollection.length || this.conditionClassCollection.length === 1) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
return this.compositionLoad.then(() => {
|
||||
const ladConditionResults = this.conditionClassCollection
|
||||
.map(condition => condition.requestLADConditionResult());
|
||||
|
||||
return Promise.all(ladConditionResults)
|
||||
.then((results) => {
|
||||
results.forEach(resultObj => { this.updateConditionResults(resultObj); });
|
||||
const currentCondition = this.getCurrentCondition();
|
||||
|
||||
return Object.assign(
|
||||
{
|
||||
output: currentCondition.configuration.output,
|
||||
id: this.conditionSetDomainObject.identifier,
|
||||
conditionId: currentCondition.id
|
||||
},
|
||||
this.latestTimestamp
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
broadcastTelemetry(id, datum) {
|
||||
this.emit(`broadcastTelemetry`, Object.assign({}, this.createNormalizedDatum(datum, id), {id: id}));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
createNormalizedDatum(telemetryDatum, id) {
|
||||
return Object.values(this.telemetryObjects[id].telemetryMetaData).reduce((normalizedDatum, metadatum) => {
|
||||
const testValue = this.getTestData(metadatum);
|
||||
const formatter = this.openmct.telemetry.getValueFormatter(metadatum);
|
||||
normalizedDatum[metadatum.key] = testValue !== undefined ? formatter.parse(testValue) : formatter.parse(telemetryDatum[metadatum.source]);
|
||||
return normalizedDatum;
|
||||
}, {});
|
||||
}
|
||||
|
||||
updateTestData(testData) {
|
||||
this.testData = testData;
|
||||
this.openmct.objects.mutate(this.conditionSetDomainObject, 'configuration.conditionTestData', this.testData.conditionTestInputs);
|
||||
}
|
||||
|
||||
persistConditions() {
|
||||
this.openmct.objects.mutate(this.conditionSetDomainObject, 'configuration.conditionCollection', this.conditionSetDomainObject.configuration.conditionCollection);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.composition.off('add', this.subscribeToTelemetry, this);
|
||||
this.composition.off('remove', this.unsubscribeFromTelemetry, this);
|
||||
Object.values(this.subscriptions).forEach(unsubscribe => unsubscribe());
|
||||
this.subscriptions = undefined;
|
||||
|
||||
if(this.stopObservingForChanges) {
|
||||
this.stopObservingForChanges();
|
||||
}
|
||||
|
||||
this.conditionClassCollection.forEach((condition) => {
|
||||
condition.off('conditionResultUpdated', this.handleConditionResult);
|
||||
condition.destroy();
|
||||
})
|
||||
}
|
||||
}
|
126
src/plugins/condition/ConditionManagerSpec.js
Normal file
126
src/plugins/condition/ConditionManagerSpec.js
Normal file
@ -0,0 +1,126 @@
|
||||
/*****************************************************************************
|
||||
* 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 ConditionManager from './ConditionManager';
|
||||
|
||||
describe('ConditionManager', () => {
|
||||
|
||||
let conditionMgr;
|
||||
let mockListener;
|
||||
let openmct = {};
|
||||
let mockCondition = {
|
||||
isDefault: true,
|
||||
id: '1234-5678',
|
||||
configuration: {
|
||||
criteria: []
|
||||
}
|
||||
};
|
||||
let conditionSetDomainObject = {
|
||||
identifier: {
|
||||
namespace: "",
|
||||
key: "600a7372-8d48-4dc4-98b6-548611b1ff7e"
|
||||
},
|
||||
type: "conditionSet",
|
||||
location: "mine",
|
||||
configuration: {
|
||||
conditionCollection: [
|
||||
mockCondition
|
||||
]
|
||||
}
|
||||
};
|
||||
let mockComposition;
|
||||
let loader;
|
||||
|
||||
function mockAngularComponents() {
|
||||
let mockInjector = jasmine.createSpyObj('$injector', ['get']);
|
||||
|
||||
let mockInstantiate = jasmine.createSpy('mockInstantiate');
|
||||
mockInstantiate.and.returnValue(mockInstantiate);
|
||||
|
||||
let mockDomainObject = {
|
||||
useCapability: function () {
|
||||
return mockCondition;
|
||||
}
|
||||
};
|
||||
mockInstantiate.and.callFake(function () {
|
||||
return mockDomainObject;
|
||||
});
|
||||
mockInjector.get.and.callFake(function (service) {
|
||||
return {
|
||||
'instantiate': mockInstantiate
|
||||
}[service];
|
||||
});
|
||||
|
||||
openmct.$injector = mockInjector;
|
||||
}
|
||||
|
||||
beforeEach(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) {
|
||||
resolve(conditionSetDomainObject);
|
||||
}), new Promise(function (resolve, reject) {
|
||||
resolve(mockCondition);
|
||||
}));
|
||||
openmct.objects.makeKeyString.and.returnValue(conditionSetDomainObject.identifier.key);
|
||||
openmct.objects.observe.and.returnValue(function () {});
|
||||
openmct.objects.mutate.and.returnValue(function () {});
|
||||
|
||||
conditionMgr = new ConditionManager(conditionSetDomainObject, openmct);
|
||||
|
||||
conditionMgr.on('conditionSetResultUpdated', mockListener);
|
||||
conditionMgr.on('broadcastTelemetry', mockListener);
|
||||
});
|
||||
|
||||
it('creates a conditionCollection with a default condition', function () {
|
||||
expect(conditionMgr.conditionSetDomainObject.configuration.conditionCollection.length).toEqual(1);
|
||||
let defaultConditionId = conditionMgr.conditionClassCollection[0].id;
|
||||
expect(defaultConditionId).toEqual(mockCondition.id);
|
||||
});
|
||||
|
||||
});
|
33
src/plugins/condition/ConditionSetCompositionPolicy.js
Normal file
33
src/plugins/condition/ConditionSetCompositionPolicy.js
Normal file
@ -0,0 +1,33 @@
|
||||
/*****************************************************************************
|
||||
* 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 function ConditionSetCompositionPolicy(openmct) {
|
||||
return {
|
||||
allow: function (parent, child) {
|
||||
if (parent.type === 'conditionSet' && !openmct.telemetry.isTelemetryObject(child)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
85
src/plugins/condition/ConditionSetCompositionPolicySpec.js
Normal file
85
src/plugins/condition/ConditionSetCompositionPolicySpec.js
Normal file
@ -0,0 +1,85 @@
|
||||
/*****************************************************************************
|
||||
* 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 ConditionSetCompositionPolicy from './ConditionSetCompositionPolicy';
|
||||
|
||||
describe('ConditionSetCompositionPolicy', () => {
|
||||
|
||||
let policy;
|
||||
let testTelemetryObject;
|
||||
let openmct = {};
|
||||
let parentDomainObject;
|
||||
|
||||
beforeAll(function () {
|
||||
testTelemetryObject = {
|
||||
identifier:{ namespace: "", key: "test-object"},
|
||||
type: "test-object",
|
||||
name: "Test Object",
|
||||
telemetry: {
|
||||
values: [{
|
||||
key: "some-key",
|
||||
name: "Some attribute",
|
||||
hints: {
|
||||
domain: 1
|
||||
}
|
||||
}, {
|
||||
key: "some-other-key",
|
||||
name: "Another attribute",
|
||||
hints: {
|
||||
range: 1
|
||||
}
|
||||
}]
|
||||
}
|
||||
};
|
||||
openmct.objects = jasmine.createSpyObj('objects', ['get', 'makeKeyString']);
|
||||
openmct.objects.get.and.returnValue(testTelemetryObject);
|
||||
openmct.objects.makeKeyString.and.returnValue(testTelemetryObject.identifier.key);
|
||||
openmct.telemetry = jasmine.createSpyObj('telemetry', ['isTelemetryObject']);
|
||||
policy = new ConditionSetCompositionPolicy(openmct);
|
||||
parentDomainObject = {};
|
||||
});
|
||||
|
||||
it('returns true for object types that are not conditionSets', function () {
|
||||
parentDomainObject.type = 'random';
|
||||
openmct.telemetry.isTelemetryObject.and.returnValue(false);
|
||||
expect(policy.allow(parentDomainObject, {})).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false for object types that are not telemetry objects when parent is a conditionSet', function () {
|
||||
parentDomainObject.type = 'conditionSet';
|
||||
openmct.telemetry.isTelemetryObject.and.returnValue(false);
|
||||
expect(policy.allow(parentDomainObject, {})).toBe(false);
|
||||
});
|
||||
|
||||
it('returns true for object types that are telemetry objects when parent is a conditionSet', function () {
|
||||
parentDomainObject.type = 'conditionSet';
|
||||
openmct.telemetry.isTelemetryObject.and.returnValue(true);
|
||||
expect(policy.allow(parentDomainObject, testTelemetryObject)).toBe(true);
|
||||
});
|
||||
|
||||
it('returns true for object types that are telemetry objects when parent is not a conditionSet', function () {
|
||||
parentDomainObject.type = 'random';
|
||||
openmct.telemetry.isTelemetryObject.and.returnValue(true);
|
||||
expect(policy.allow(parentDomainObject, testTelemetryObject)).toBe(true);
|
||||
});
|
||||
|
||||
});
|
68
src/plugins/condition/ConditionSetMetadataProvider.js
Normal file
68
src/plugins/condition/ConditionSetMetadataProvider.js
Normal file
@ -0,0 +1,68 @@
|
||||
/*****************************************************************************
|
||||
* 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;
|
||||
}
|
||||
|
||||
supportsMetadata(domainObject) {
|
||||
return domainObject.type === 'conditionSet';
|
||||
}
|
||||
|
||||
getDomains(domainObject) {
|
||||
return this.openmct.time.getAllTimeSystems().map(function (ts, i) {
|
||||
return {
|
||||
key: ts.key,
|
||||
name: ts.name,
|
||||
format: ts.timeFormat,
|
||||
hints: {
|
||||
domain: i
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
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,
|
||||
hints: {
|
||||
range: 1
|
||||
}
|
||||
}
|
||||
])
|
||||
};
|
||||
}
|
||||
}
|
86
src/plugins/condition/ConditionSetTelemetryProvider.js
Normal file
86
src/plugins/condition/ConditionSetTelemetryProvider.js
Normal file
@ -0,0 +1,86 @@
|
||||
/*****************************************************************************
|
||||
* 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 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';
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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];
|
||||
}
|
||||
}
|
||||
}
|
33
src/plugins/condition/ConditionSetViewPolicy.js
Normal file
33
src/plugins/condition/ConditionSetViewPolicy.js
Normal file
@ -0,0 +1,33 @@
|
||||
/*****************************************************************************
|
||||
* 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;
|
84
src/plugins/condition/ConditionSetViewProvider.js
Normal file
84
src/plugins/condition/ConditionSetViewProvider.js
Normal file
@ -0,0 +1,84 @@
|
||||
/*****************************************************************************
|
||||
* 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 ConditionSet from './components/ConditionSet.vue';
|
||||
import Vue from 'vue';
|
||||
|
||||
const DEFAULT_VIEW_PRIORITY = 100;
|
||||
|
||||
export default class ConditionSetViewProvider {
|
||||
constructor(openmct) {
|
||||
this.openmct = openmct;
|
||||
this.name = 'Conditions View';
|
||||
this.key = 'conditionSet.view';
|
||||
this.cssClass = 'icon-conditional';
|
||||
}
|
||||
|
||||
canView(domainObject) {
|
||||
return domainObject.type === 'conditionSet';
|
||||
}
|
||||
|
||||
canEdit(domainObject) {
|
||||
return domainObject.type === 'conditionSet';
|
||||
}
|
||||
|
||||
view(domainObject, objectPath) {
|
||||
let component;
|
||||
const openmct = this.openmct;
|
||||
return {
|
||||
show: (container, isEditing) => {
|
||||
component = new Vue({
|
||||
el: container,
|
||||
components: {
|
||||
ConditionSet
|
||||
},
|
||||
provide: {
|
||||
openmct,
|
||||
domainObject,
|
||||
objectPath
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isEditing
|
||||
}
|
||||
},
|
||||
template: '<condition-set :isEditing="isEditing"></condition-set>'
|
||||
});
|
||||
},
|
||||
onEditModeChange: (isEditing) => {
|
||||
component.isEditing = isEditing;
|
||||
},
|
||||
destroy: () => {
|
||||
component.$destroy();
|
||||
component = undefined;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
priority(domainObject) {
|
||||
if (domainObject.type === 'conditionSet') {
|
||||
return Number.MAX_VALUE;
|
||||
} else {
|
||||
return DEFAULT_VIEW_PRIORITY;
|
||||
}
|
||||
}
|
||||
}
|
136
src/plugins/condition/ConditionSpec.js
Normal file
136
src/plugins/condition/ConditionSpec.js
Normal file
@ -0,0 +1,136 @@
|
||||
/*****************************************************************************
|
||||
* 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 Condition from "./Condition";
|
||||
import {TRIGGER} from "./utils/constants";
|
||||
import TelemetryCriterion from "./criterion/TelemetryCriterion";
|
||||
|
||||
let openmct = {},
|
||||
mockListener,
|
||||
testConditionDefinition,
|
||||
testTelemetryObject,
|
||||
conditionObj,
|
||||
conditionManager,
|
||||
mockBroadcastTelemetry;
|
||||
|
||||
describe("The condition", function () {
|
||||
|
||||
beforeEach (() => {
|
||||
conditionManager = jasmine.createSpyObj('conditionManager',
|
||||
['on']
|
||||
);
|
||||
mockBroadcastTelemetry = jasmine.createSpy('listener');
|
||||
conditionManager.on('broadcastTelemetry', mockBroadcastTelemetry);
|
||||
|
||||
mockListener = jasmine.createSpy('listener');
|
||||
testTelemetryObject = {
|
||||
identifier:{ namespace: "", key: "test-object"},
|
||||
type: "test-object",
|
||||
name: "Test Object",
|
||||
telemetry: {
|
||||
values: [{
|
||||
key: "some-key",
|
||||
name: "Some attribute",
|
||||
hints: {
|
||||
domain: 1
|
||||
}
|
||||
}, {
|
||||
key: "some-other-key",
|
||||
name: "Another attribute",
|
||||
hints: {
|
||||
range: 1
|
||||
}
|
||||
}]
|
||||
}
|
||||
};
|
||||
conditionManager.telemetryObjects = {
|
||||
"test-object": testTelemetryObject
|
||||
};
|
||||
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']);
|
||||
openmct.telemetry.isTelemetryObject.and.returnValue(true);
|
||||
openmct.telemetry.subscribe.and.returnValue(function () {});
|
||||
openmct.telemetry.getMetadata.and.returnValue(testTelemetryObject.telemetry.values);
|
||||
|
||||
testConditionDefinition = {
|
||||
id: '123-456',
|
||||
configuration: {
|
||||
name: 'mock condition',
|
||||
output: 'mock output',
|
||||
trigger: TRIGGER.ANY,
|
||||
criteria: [
|
||||
{
|
||||
id: '1234-5678-9999-0000',
|
||||
operation: 'equalTo',
|
||||
input: ['0'],
|
||||
metadata: 'value',
|
||||
telemetry: testTelemetryObject.identifier
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
conditionObj = new Condition(
|
||||
testConditionDefinition,
|
||||
openmct,
|
||||
conditionManager
|
||||
);
|
||||
|
||||
conditionObj.on('conditionUpdated', mockListener);
|
||||
});
|
||||
|
||||
it("generates criteria with the correct properties", function () {
|
||||
const testCriterion = testConditionDefinition.configuration.criteria[0];
|
||||
let criterion = conditionObj.generateCriterion(testCriterion);
|
||||
expect(criterion.id).toBeDefined();
|
||||
expect(criterion.operation).toEqual(testCriterion.operation);
|
||||
expect(criterion.input).toEqual(testCriterion.input);
|
||||
expect(criterion.metadata).toEqual(testCriterion.metadata);
|
||||
expect(criterion.telemetry).toEqual(testCriterion.telemetry);
|
||||
});
|
||||
|
||||
it("initializes with an id", function () {
|
||||
expect(conditionObj.id).toBeDefined();
|
||||
});
|
||||
|
||||
it("initializes with criteria from the condition definition", function () {
|
||||
expect(conditionObj.criteria.length).toEqual(1);
|
||||
let criterion = conditionObj.criteria[0];
|
||||
expect(criterion instanceof TelemetryCriterion).toBeTrue();
|
||||
expect(criterion.operator).toEqual(testConditionDefinition.configuration.criteria[0].operator);
|
||||
expect(criterion.input).toEqual(testConditionDefinition.configuration.criteria[0].input);
|
||||
expect(criterion.metadata).toEqual(testConditionDefinition.configuration.criteria[0].metadata);
|
||||
});
|
||||
|
||||
it("initializes with the trigger from the condition definition", function () {
|
||||
expect(conditionObj.trigger).toEqual(testConditionDefinition.configuration.trigger);
|
||||
});
|
||||
|
||||
it("destroys all criteria for a condition", function () {
|
||||
const result = conditionObj.destroyCriteria();
|
||||
expect(result).toBeTrue();
|
||||
expect(conditionObj.criteria.length).toEqual(0);
|
||||
});
|
||||
});
|
128
src/plugins/condition/StyleRuleManager.js
Normal file
128
src/plugins/condition/StyleRuleManager.js
Normal file
@ -0,0 +1,128 @@
|
||||
/*****************************************************************************
|
||||
* 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 EventEmitter from 'EventEmitter';
|
||||
|
||||
export default class StyleRuleManager extends EventEmitter {
|
||||
constructor(styleConfiguration, openmct, callback) {
|
||||
super();
|
||||
this.openmct = openmct;
|
||||
this.callback = callback;
|
||||
if (styleConfiguration) {
|
||||
this.initialize(styleConfiguration);
|
||||
if (styleConfiguration.conditionSetIdentifier) {
|
||||
this.subscribeToConditionSet();
|
||||
} else {
|
||||
this.applyStaticStyle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
initialize(styleConfiguration) {
|
||||
this.conditionSetIdentifier = styleConfiguration.conditionSetIdentifier;
|
||||
this.staticStyle = styleConfiguration.staticStyle;
|
||||
this.updateConditionStylesMap(styleConfiguration.styles || []);
|
||||
}
|
||||
|
||||
subscribeToConditionSet() {
|
||||
if (this.stopProvidingTelemetry) {
|
||||
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 || {});
|
||||
this.destroy();
|
||||
} else {
|
||||
let isNewConditionSet = !this.conditionSetIdentifier ||
|
||||
!this.openmct.objects.areIdsEqual(this.conditionSetIdentifier, styleConfiguration.conditionSetIdentifier);
|
||||
this.initialize(styleConfiguration);
|
||||
//Only resubscribe if the conditionSet has changed.
|
||||
if (isNewConditionSet) {
|
||||
this.subscribeToConditionSet();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateConditionStylesMap(conditionStyles) {
|
||||
let conditionStyleMap = {};
|
||||
conditionStyles.forEach((conditionStyle) => {
|
||||
if (conditionStyle.conditionId) {
|
||||
conditionStyleMap[conditionStyle.conditionId] = conditionStyle.style;
|
||||
} else {
|
||||
conditionStyleMap.static = conditionStyle.style;
|
||||
}
|
||||
});
|
||||
this.conditionalStyleMap = conditionStyleMap;
|
||||
}
|
||||
|
||||
handleConditionSetResultUpdated(resultData) {
|
||||
let foundStyle = this.conditionalStyleMap[resultData.conditionId];
|
||||
if (foundStyle) {
|
||||
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] = 'transparent';
|
||||
});
|
||||
}
|
||||
}
|
||||
this.updateDomainObjectStyle();
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.applyStaticStyle();
|
||||
if (this.stopProvidingTelemetry) {
|
||||
this.stopProvidingTelemetry();
|
||||
}
|
||||
delete this.stopProvidingTelemetry;
|
||||
this.conditionSetIdentifier = undefined;
|
||||
}
|
||||
|
||||
}
|
325
src/plugins/condition/components/Condition.vue
Normal file
325
src/plugins/condition/components/Condition.vue
Normal file
@ -0,0 +1,325 @@
|
||||
/*****************************************************************************
|
||||
* 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="isEditing"
|
||||
class="c-condition c-condition--edit js-condition-drag-wrapper"
|
||||
>
|
||||
<!-- 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__name">{{ condition.configuration.name }}</span>
|
||||
<!-- TODO: description should be derived from criteria -->
|
||||
<span class="c-condition__summary">
|
||||
<template v-if="!canEvaluateCriteria">
|
||||
Define criteria
|
||||
</template>
|
||||
<span v-else>
|
||||
<condition-description :show-label="false"
|
||||
:condition="condition"
|
||||
/>
|
||||
</span>
|
||||
</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>
|
||||
|
||||
<button v-if="!condition.isDefault"
|
||||
class="c-click-icon c-condition__delete-button icon-trash"
|
||||
title="Delete this condition"
|
||||
@click="removeCondition"
|
||||
></button>
|
||||
</div>
|
||||
</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">
|
||||
<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"
|
||||
@blur="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="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>
|
||||
</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>
|
||||
<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 class="c-condition__summary">
|
||||
<condition-description :show-label="false"
|
||||
:condition="condition"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Criterion from './Criterion.vue';
|
||||
import ConditionDescription from "./ConditionDescription.vue";
|
||||
import { TRIGGER, TRIGGER_LABEL } from "@/plugins/condition/utils/constants";
|
||||
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
components: {
|
||||
Criterion,
|
||||
ConditionDescription
|
||||
},
|
||||
props: {
|
||||
condition: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
conditionIndex: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
isEditing: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
telemetry: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentCriteria: this.currentCriteria,
|
||||
expanded: true,
|
||||
trigger: 'all',
|
||||
selectedOutputSelection: '',
|
||||
outputOptions: ['false', 'true', 'string'],
|
||||
criterionIndex: 0,
|
||||
selectedTelemetryName: '',
|
||||
selectedFieldName: ''
|
||||
};
|
||||
},
|
||||
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;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
},
|
||||
destroyed() {
|
||||
this.destroy();
|
||||
},
|
||||
mounted() {
|
||||
this.setOutputSelection();
|
||||
},
|
||||
methods: {
|
||||
setOutputSelection() {
|
||||
let conditionOutput = this.condition.configuration.output;
|
||||
if (conditionOutput) {
|
||||
if (conditionOutput !== 'false' && conditionOutput !== 'true') {
|
||||
this.selectedOutputSelection = 'string';
|
||||
} else {
|
||||
this.selectedOutputSelection = conditionOutput;
|
||||
}
|
||||
}
|
||||
},
|
||||
setOutputValue() {
|
||||
if (this.selectedOutputSelection === 'string') {
|
||||
this.condition.configuration.output = '';
|
||||
} else {
|
||||
this.condition.configuration.output = this.selectedOutputSelection;
|
||||
}
|
||||
this.persist();
|
||||
},
|
||||
addCriteria() {
|
||||
const criteriaObject = {
|
||||
telemetry: '',
|
||||
operation: '',
|
||||
input: '',
|
||||
metadata: ''
|
||||
};
|
||||
this.condition.configuration.criteria.push(criteriaObject);
|
||||
},
|
||||
dragStart(e) {
|
||||
e.dataTransfer.setData('dragging', e.target); // required for FF to initiate drag
|
||||
e.dataTransfer.effectAllowed = "copyMove";
|
||||
e.dataTransfer.setDragImage(e.target.closest('.js-condition-drag-wrapper'), 0, 0);
|
||||
this.$emit('setMoveIndex', this.conditionIndex);
|
||||
},
|
||||
dragStop(e) {
|
||||
e.dataTransfer.clearData();
|
||||
},
|
||||
destroy() {
|
||||
},
|
||||
removeCondition(ev) {
|
||||
this.$emit('removeCondition', this.conditionIndex);
|
||||
},
|
||||
cloneCondition(ev) {
|
||||
this.$emit('cloneCondition', {
|
||||
condition: this.condition,
|
||||
index: this.conditionIndex
|
||||
});
|
||||
},
|
||||
removeCriterion(index) {
|
||||
this.condition.configuration.criteria.splice(index, 1);
|
||||
this.persist();
|
||||
},
|
||||
cloneCriterion(index) {
|
||||
const clonedCriterion = JSON.parse(JSON.stringify(this.condition.configuration.criteria[index]));
|
||||
this.condition.configuration.criteria.splice(index + 1, 0, clonedCriterion);
|
||||
this.persist();
|
||||
},
|
||||
persist() {
|
||||
this.$emit('updateCondition', {
|
||||
condition: this.condition,
|
||||
index: this.conditionIndex
|
||||
});
|
||||
},
|
||||
initCap: function (str) {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
245
src/plugins/condition/components/ConditionCollection.vue
Normal file
245
src/plugins/condition/components/ConditionCollection.vue
Normal file
@ -0,0 +1,245 @@
|
||||
/*****************************************************************************
|
||||
* 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>
|
||||
<section id="conditionCollection"
|
||||
class="c-cs__conditions"
|
||||
:class="{ 'is-expanded': expanded }"
|
||||
>
|
||||
<div class="c-cs__header c-section__header">
|
||||
<span
|
||||
class="c-disclosure-triangle c-tree__item__view-control is-enabled"
|
||||
:class="{ 'c-disclosure-triangle--expanded': expanded }"
|
||||
@click="expanded = !expanded"
|
||||
></span>
|
||||
<div class="c-cs__header-label c-section__label">Conditions</div>
|
||||
</div>
|
||||
<div v-if="expanded"
|
||||
class="c-cs__content"
|
||||
>
|
||||
<div v-show="isEditing"
|
||||
class="hint"
|
||||
:class="{ 's-status-icon-warning-lo': !telemetryObjs.length }"
|
||||
>
|
||||
<template v-if="!telemetryObjs.length">Drag telemetry into this Condition Set to configure Conditions and add criteria.</template>
|
||||
<template v-else>The first condition to match is the one that is applied. Drag conditions to reorder.</template>
|
||||
</div>
|
||||
|
||||
<button
|
||||
v-show="isEditing"
|
||||
id="addCondition"
|
||||
class="c-button c-button--major icon-plus labeled"
|
||||
@click="addCondition"
|
||||
>
|
||||
<span class="c-cs-button__label">Add Condition</span>
|
||||
</button>
|
||||
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Condition from './Condition.vue';
|
||||
import ConditionManager from '../ConditionManager';
|
||||
|
||||
export default {
|
||||
inject: ['openmct', 'domainObject'],
|
||||
components: {
|
||||
Condition
|
||||
},
|
||||
props: {
|
||||
isEditing: Boolean,
|
||||
testData: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: () => {
|
||||
return {
|
||||
applied: false,
|
||||
conditionTestInputs: []
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
expanded: true,
|
||||
conditionCollection: [],
|
||||
conditionResults: {},
|
||||
conditions: [],
|
||||
telemetryObjs: [],
|
||||
moveIndex: Number,
|
||||
isDragging: false,
|
||||
defaultOutput: undefined
|
||||
};
|
||||
},
|
||||
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) {
|
||||
this.stopObservingForChanges();
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.composition = this.openmct.composition.get(this.domainObject);
|
||||
this.composition.on('add', this.addTelemetryObject);
|
||||
this.composition.on('remove', this.removeTelemetryObject);
|
||||
this.composition.load();
|
||||
this.conditionCollection = this.domainObject.configuration.conditionCollection;
|
||||
this.observeForChanges();
|
||||
this.conditionManager = new ConditionManager(this.domainObject, this.openmct);
|
||||
this.conditionManager.on('conditionSetResultUpdated', this.handleConditionSetResultUpdated);
|
||||
this.updateDefaultCondition();
|
||||
},
|
||||
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(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) {
|
||||
old_index += arr.length;
|
||||
}
|
||||
while (new_index < 0) {
|
||||
new_index += arr.length;
|
||||
}
|
||||
if (new_index >= arr.length) {
|
||||
var k = new_index - arr.length;
|
||||
while ((k--) + 1) {
|
||||
arr.push(undefined);
|
||||
}
|
||||
}
|
||||
arr.splice(new_index, 0, arr.splice(old_index, 1)[0]);
|
||||
return arr;
|
||||
}
|
||||
const newIndexArr = move(oldIndexArr, this.moveIndex, targetIndex);
|
||||
const reorderPlan = [];
|
||||
|
||||
for (let i = 0; i < oldIndexArr.length; i++) {
|
||||
reorderPlan.push({oldIndex: Number(newIndexArr[i]), newIndex: i});
|
||||
}
|
||||
|
||||
this.reorder(reorderPlan);
|
||||
|
||||
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 = _.findIndex(this.telemetryObjs, (obj) => {
|
||||
let objId = this.openmct.objects.makeKeyString(obj.identifier);
|
||||
let id = this.openmct.objects.makeKeyString(identifier);
|
||||
return objId === id;
|
||||
});
|
||||
if (index > -1) {
|
||||
this.telemetryObjs.splice(index, 1);
|
||||
}
|
||||
},
|
||||
addCondition() {
|
||||
this.conditionManager.addCondition();
|
||||
},
|
||||
updateCondition(data) {
|
||||
this.conditionManager.updateCondition(data.condition, data.index);
|
||||
},
|
||||
removeCondition(index) {
|
||||
this.conditionManager.removeCondition(index);
|
||||
},
|
||||
reorder(reorderPlan) {
|
||||
this.conditionManager.reorderConditions(reorderPlan);
|
||||
},
|
||||
cloneCondition(data) {
|
||||
this.conditionManager.cloneCondition(data.condition, data.index);
|
||||
},
|
||||
updateTestData() {
|
||||
this.conditionManager.updateTestData(this.testData);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
145
src/plugins/condition/components/ConditionDescription.vue
Normal file
145
src/plugins/condition/components/ConditionDescription.vue
Normal file
@ -0,0 +1,145 @@
|
||||
/*****************************************************************************
|
||||
* 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) {
|
||||
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>
|
80
src/plugins/condition/components/ConditionError.vue
Normal file
80
src/plugins/condition/components/ConditionError.vue
Normal file
@ -0,0 +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.
|
||||
*****************************************************************************/
|
||||
|
||||
<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) {
|
||||
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>
|
97
src/plugins/condition/components/ConditionSet.vue
Normal file
97
src/plugins/condition/components/ConditionSet.vue
Normal file
@ -0,0 +1,97 @@
|
||||
/*****************************************************************************
|
||||
* 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-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">
|
||||
<template v-if="currentConditionOutput">
|
||||
{{ currentConditionOutput }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ defaultConditionOutput }}
|
||||
</template>
|
||||
</div>
|
||||
</section>
|
||||
<TestData :is-editing="isEditing"
|
||||
:test-data="testData"
|
||||
:telemetry="telemetryObjs"
|
||||
@updateTestData="updateTestData"
|
||||
/>
|
||||
<ConditionCollection
|
||||
:is-editing="isEditing"
|
||||
:test-data="testData"
|
||||
@conditionSetResultUpdated="updateCurrentOutput"
|
||||
@updateDefaultOutput="updateDefaultOutput"
|
||||
@telemetryUpdated="updateTelemetry"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TestData from './TestData.vue';
|
||||
import ConditionCollection from './ConditionCollection.vue';
|
||||
|
||||
export default {
|
||||
inject: ["openmct", "domainObject"],
|
||||
components: {
|
||||
TestData,
|
||||
ConditionCollection
|
||||
},
|
||||
props: {
|
||||
isEditing: Boolean
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentConditionOutput: '',
|
||||
defaultConditionOutput: '',
|
||||
telemetryObjs: [],
|
||||
testData: {}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.conditionSetIdentifier = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||
this.testData = {
|
||||
applied: false,
|
||||
conditionTestInputs: this.domainObject.configuration.conditionTestData || []
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
updateCurrentOutput(currentConditionResult) {
|
||||
this.currentConditionOutput = currentConditionResult.output;
|
||||
},
|
||||
updateDefaultOutput(output) {
|
||||
this.currentConditionOutput = output;
|
||||
},
|
||||
updateTelemetry(telemetryObjs) {
|
||||
this.telemetryObjs = telemetryObjs;
|
||||
},
|
||||
updateTestData(testData) {
|
||||
this.testData = testData;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
293
src/plugins/condition/components/Criterion.vue
Normal file
293
src/plugins/condition/components/Criterion.vue
Normal file
@ -0,0 +1,293 @@
|
||||
/*****************************************************************************
|
||||
* 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"
|
||||
@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"
|
||||
>
|
||||
{{ telemetryOption.name }}
|
||||
</option>
|
||||
</select>
|
||||
</span>
|
||||
<span v-if="criterion.telemetry"
|
||||
class="c-cdef__control"
|
||||
>
|
||||
<select ref="metadataSelect"
|
||||
v-model="criterion.metadata"
|
||||
@change="updateOperations"
|
||||
>
|
||||
<option value="">- Select Field -</option>
|
||||
<option v-for="option in telemetryMetadataOptions"
|
||||
:key="option.key"
|
||||
:value="option.key"
|
||||
>
|
||||
{{ option.name }}
|
||||
</option>
|
||||
</select>
|
||||
</span>
|
||||
<span v-if="criterion.telemetry && criterion.metadata"
|
||||
class="c-cdef__control"
|
||||
>
|
||||
<select v-model="criterion.operation"
|
||||
@change="updateInputVisibilityAndValues"
|
||||
>
|
||||
<option value="">- Select Comparison -</option>
|
||||
<option v-for="option in filteredOps"
|
||||
:key="option.name"
|
||||
:value="option.name"
|
||||
>
|
||||
{{ option.text }}
|
||||
</option>
|
||||
</select>
|
||||
<template v-if="!enumerations.length">
|
||||
<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"
|
||||
>
|
||||
<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>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { OPERATIONS } from '../utils/operations';
|
||||
import { INPUT_TYPES } from '../utils/operations';
|
||||
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
props: {
|
||||
criterion: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
telemetry: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => []
|
||||
},
|
||||
index: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
trigger: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
telemetryMetadataOptions: [],
|
||||
operations: OPERATIONS,
|
||||
inputCount: 0,
|
||||
rowLabel: '',
|
||||
operationFormat: '',
|
||||
enumerations: [],
|
||||
inputTypes: INPUT_TYPES
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
setRowLabel: function () {
|
||||
let operator = this.trigger === 'all' ? 'and ': 'or ';
|
||||
return (this.index !== 0 ? operator : '') + 'when';
|
||||
},
|
||||
filteredOps: function () {
|
||||
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]];
|
||||
} else {
|
||||
type = 'text'
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
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.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 = 'string';
|
||||
}
|
||||
}
|
||||
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());
|
||||
});
|
||||
this.updateOperations();
|
||||
});
|
||||
}
|
||||
},
|
||||
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();
|
||||
}
|
||||
},
|
||||
updateInputVisibilityAndValues(ev) {
|
||||
if (ev) {
|
||||
this.clearDependentFields();
|
||||
this.persist();
|
||||
}
|
||||
|
||||
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 = [];
|
||||
}
|
||||
},
|
||||
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;
|
||||
}
|
||||
},
|
||||
persist() {
|
||||
this.$emit('persist', this.criterion);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
237
src/plugins/condition/components/TestData.vue
Normal file
237
src/plugins/condition/components/TestData.vue
Normal file
@ -0,0 +1,237 @@
|
||||
/*****************************************************************************
|
||||
* 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>
|
||||
<section v-show="isEditing"
|
||||
id="test-data"
|
||||
class="c-cs__test-data"
|
||||
:class="{ 'is-expanded': expanded }"
|
||||
>
|
||||
<div class="c-cs__header c-section__header">
|
||||
<span
|
||||
class="c-disclosure-triangle c-tree__item__view-control is-enabled"
|
||||
:class="{ 'c-disclosure-triangle--expanded': expanded }"
|
||||
@click="expanded = !expanded"
|
||||
></span>
|
||||
<div class="c-cs__header-label c-section__label">Test Data</div>
|
||||
</div>
|
||||
<div v-if="expanded"
|
||||
class="c-cs__content"
|
||||
>
|
||||
<div class="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"
|
||||
>
|
||||
<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-test-datum__buttons">
|
||||
<button class="c-click-icon c-test-data__duplicate-button icon-duplicate"
|
||||
title="Duplicate this test datum"
|
||||
@click="addTestInput(testInput)"
|
||||
></button>
|
||||
<button class="c-click-icon c-test-data__delete-button icon-trash"
|
||||
title="Delete this test datum"
|
||||
@click="removeTestInput(tIndex)"
|
||||
></button>
|
||||
</div>
|
||||
</span>
|
||||
</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>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
props: {
|
||||
isEditing: Boolean,
|
||||
telemetry: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => []
|
||||
},
|
||||
testData: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: () => {
|
||||
return {
|
||||
applied: false,
|
||||
conditionTestInputs: []
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
expanded: true,
|
||||
isApplied: false,
|
||||
testInputs: [],
|
||||
telemetryMetadataOptions: {}
|
||||
};
|
||||
},
|
||||
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
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
116
src/plugins/condition/components/condition-set.scss
Normal file
116
src/plugins/condition/components/condition-set.scss
Normal file
@ -0,0 +1,116 @@
|
||||
/*****************************************************************************
|
||||
* 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-cs {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
&__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;
|
||||
}
|
||||
}
|
||||
|
||||
&__conditions {
|
||||
> * + * {
|
||||
margin-top: $interiorMarginSm;
|
||||
}
|
||||
}
|
||||
|
||||
.hint {
|
||||
padding: $interiorMarginSm;
|
||||
}
|
||||
|
||||
/************************** SPECIFIC ITEMS */
|
||||
&__current-output-value {
|
||||
font-size: 1.25em;
|
||||
padding: $interiorMargin;
|
||||
}
|
||||
}
|
||||
|
||||
/***************************** TEST DATA */
|
||||
.c-cs-tests {
|
||||
flex: 0 1 auto;
|
||||
overflow: auto;
|
||||
padding-right: $interiorMarginSm;
|
||||
|
||||
> * + * {
|
||||
margin-top: $interiorMarginSm;
|
||||
}
|
||||
}
|
||||
|
||||
.c-cs-test {
|
||||
> * {
|
||||
flex: 0 0 auto;
|
||||
+ * {
|
||||
margin-left: $interiorMargin;
|
||||
}
|
||||
}
|
||||
|
||||
&__controls {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
|
||||
> * + * {
|
||||
margin-left: $interiorMargin;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
133
src/plugins/condition/components/condition.scss
Normal file
133
src/plugins/condition/components/condition.scss
Normal file
@ -0,0 +1,133 @@
|
||||
/*****************************************************************************
|
||||
* 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,
|
||||
.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;
|
||||
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 {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,185 @@
|
||||
/*****************************************************************************
|
||||
* 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>
|
@ -0,0 +1,172 @@
|
||||
/*****************************************************************************
|
||||
* 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>
|
@ -0,0 +1,335 @@
|
||||
/*****************************************************************************
|
||||
* 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>
|
||||
<button
|
||||
id="addConditionSet"
|
||||
class="c-button c-button--major c-toggle-styling-button labeled"
|
||||
@click="addConditionSet"
|
||||
>
|
||||
<span class="c-cs-button__label">Use 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"
|
||||
>
|
||||
<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)"
|
||||
/>
|
||||
<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>
|
||||
</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";
|
||||
|
||||
export default {
|
||||
name: 'ConditionalStylesView',
|
||||
components: {
|
||||
ConditionDescription,
|
||||
ConditionError,
|
||||
StyleEditor
|
||||
},
|
||||
inject: [
|
||||
'openmct',
|
||||
'domainObject'
|
||||
],
|
||||
props: {
|
||||
itemId: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
initialStyles: {
|
||||
type: Object,
|
||||
default() {
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
canHide: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
conditionalStyles: [],
|
||||
staticStyle: undefined,
|
||||
conditionSetDomainObject: undefined,
|
||||
isEditing: this.openmct.editor.isEditing(),
|
||||
conditions: undefined,
|
||||
conditionsLoaded: false,
|
||||
navigateToPath: ''
|
||||
}
|
||||
},
|
||||
destroyed() {
|
||||
this.openmct.editor.off('isEditing', this.setEditState);
|
||||
},
|
||||
mounted() {
|
||||
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;
|
||||
}
|
||||
} else {
|
||||
this.initializeStaticStyle();
|
||||
}
|
||||
this.openmct.editor.on('isEditing', this.setEditState);
|
||||
},
|
||||
methods: {
|
||||
initialize(conditionSetDomainObject) {
|
||||
//If there are new conditions in the conditionSet we need to set those styles to default
|
||||
this.conditionSetDomainObject = conditionSetDomainObject;
|
||||
this.enableConditionSetNav();
|
||||
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;
|
||||
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];
|
||||
}
|
||||
} else {
|
||||
domainObjectStyles.conditionSetIdentifier = undefined;
|
||||
delete domainObjectStyles.conditionSetIdentifier;
|
||||
domainObjectStyles.styles = undefined;
|
||||
delete domainObjectStyles.styles;
|
||||
}
|
||||
if (_.isEmpty(domainObjectStyles)) {
|
||||
domainObjectStyles = undefined;
|
||||
}
|
||||
|
||||
this.persist(domainObjectStyles);
|
||||
},
|
||||
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, {
|
||||
conditionId: conditionConfiguration.id,
|
||||
style: Object.assign((this.canHide ? { isStyleInvisible: '' } : {}), this.initialStyles)
|
||||
});
|
||||
}
|
||||
});
|
||||
//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;
|
||||
this.staticStyle = 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 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
|
||||
}
|
||||
}
|
||||
|
||||
return domainObjectStyles;
|
||||
},
|
||||
getCondition(id) {
|
||||
return this.conditions ? this.conditions[id] : {};
|
||||
},
|
||||
persist(style) {
|
||||
this.openmct.objects.mutate(this.domainObject, 'configuration.objectStyles', style);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
179
src/plugins/condition/components/inspector/StyleEditor.vue
Normal file
179
src/plugins/condition/components/inspector/StyleEditor.vue
Normal file
@ -0,0 +1,179 @@
|
||||
/*****************************************************************************
|
||||
* 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="c-style-thumb"
|
||||
:class="{ 'is-style-invisible': styleItem.style.isStyleInvisible }"
|
||||
:style="[styleItem.style.imageUrl ? { backgroundImage:'url(' + styleItem.style.imageUrl + ')'} : styleItem.style ]"
|
||||
>
|
||||
<span class="c-style-thumb__text"
|
||||
:class="{ 'hide-nice': !styleItem.style.color }"
|
||||
>
|
||||
ABC
|
||||
</span>
|
||||
</span>
|
||||
<span class="c-toolbar">
|
||||
<toolbar-color-picker v-if="styleItem.style.border"
|
||||
class="c-style__toolbar-button--border-color u-menu-to--center"
|
||||
:options="borderColorOption"
|
||||
@change="updateStyleValue"
|
||||
/>
|
||||
<toolbar-color-picker v-if="styleItem.style.backgroundColor"
|
||||
class="c-style__toolbar-button--background-color u-menu-to--center"
|
||||
:options="backgroundColorOption"
|
||||
@change="updateStyleValue"
|
||||
/>
|
||||
<toolbar-color-picker v-if="styleItem.style.color"
|
||||
class="c-style__toolbar-button--color u-menu-to--center"
|
||||
:options="colorOption"
|
||||
@change="updateStyleValue"
|
||||
/>
|
||||
<toolbar-button v-if="styleItem.style.imageUrl !== undefined"
|
||||
class="c-style__toolbar-button--image-url"
|
||||
:options="imageUrlOption"
|
||||
@change="updateStyleValue"
|
||||
/>
|
||||
<toolbar-toggle-button v-if="styleItem.style.isStyleInvisible !== undefined"
|
||||
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";
|
||||
|
||||
export default {
|
||||
name: 'StyleEditor',
|
||||
components: {
|
||||
ToolbarButton,
|
||||
ToolbarColorPicker,
|
||||
ToolbarToggleButton
|
||||
},
|
||||
inject: [
|
||||
'openmct'
|
||||
],
|
||||
props: {
|
||||
isEditing: {
|
||||
type: Boolean
|
||||
},
|
||||
styleItem: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
borderColorOption() {
|
||||
return {
|
||||
icon: 'icon-line-horz',
|
||||
title: STYLE_CONSTANTS.borderColorTitle,
|
||||
value: this.styleItem.style.border.replace('1px solid ', ''),
|
||||
property: 'border',
|
||||
isEditing: this.isEditing
|
||||
}
|
||||
},
|
||||
backgroundColorOption() {
|
||||
return {
|
||||
icon: 'icon-paint-bucket',
|
||||
title: STYLE_CONSTANTS.backgroundColorTitle,
|
||||
value: this.styleItem.style.backgroundColor,
|
||||
property: 'backgroundColor',
|
||||
isEditing: this.isEditing
|
||||
}
|
||||
},
|
||||
colorOption() {
|
||||
return {
|
||||
icon: 'icon-font',
|
||||
title: STYLE_CONSTANTS.textColorTitle,
|
||||
value: this.styleItem.style.color,
|
||||
property: 'color',
|
||||
isEditing: this.isEditing
|
||||
}
|
||||
},
|
||||
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
|
||||
}
|
||||
},
|
||||
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: {
|
||||
updateStyleValue(value, item) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@ -0,0 +1,124 @@
|
||||
/*****************************************************************************
|
||||
* 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;
|
||||
}
|
||||
}
|
172
src/plugins/condition/criterion/AllTelemetryCriterion.js
Normal file
172
src/plugins/condition/criterion/AllTelemetryCriterion.js
Normal file
@ -0,0 +1,172 @@
|
||||
/*****************************************************************************
|
||||
* 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 EventEmitter from 'EventEmitter';
|
||||
import {OPERATIONS} from '../utils/operations';
|
||||
import {computeCondition} from "@/plugins/condition/utils/evaluator";
|
||||
|
||||
export default class TelemetryCriterion extends EventEmitter {
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
this.openmct = openmct;
|
||||
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.telemetryObjects = Object.assign({}, telemetryDomainObjectDefinition.telemetryObjects);
|
||||
this.input = telemetryDomainObjectDefinition.input;
|
||||
this.metadata = telemetryDomainObjectDefinition.metadata;
|
||||
this.telemetryDataCache = {};
|
||||
}
|
||||
|
||||
updateTelemetry(telemetryObjects) {
|
||||
this.telemetryObjects = Object.assign({}, telemetryObjects);
|
||||
}
|
||||
|
||||
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: computeCondition(this.telemetryDataCache, this.telemetry === 'all')
|
||||
};
|
||||
|
||||
if (data) {
|
||||
// TODO check back to see if we should format times here
|
||||
this.timeAPI.getAllTimeSystems().forEach(timeSystem => {
|
||||
datum[timeSystem.key] = data[timeSystem.key]
|
||||
});
|
||||
}
|
||||
return datum;
|
||||
}
|
||||
|
||||
handleSubscription(data, telemetryObjects) {
|
||||
if(this.isValid()) {
|
||||
this.emitEvent('criterionResultUpdated', this.formatData(data, telemetryObjects));
|
||||
} else {
|
||||
this.emitEvent('criterionResultUpdated', this.formatData({}, telemetryObjects));
|
||||
}
|
||||
}
|
||||
|
||||
findOperation(operation) {
|
||||
for (let i=0; i < OPERATIONS.length; i++) {
|
||||
if (operation === OPERATIONS[i].name) {
|
||||
return OPERATIONS[i].operation;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
computeResult(data) {
|
||||
let result = false;
|
||||
if (data) {
|
||||
let comparator = this.findOperation(this.operation);
|
||||
let params = [];
|
||||
params.push(data[this.metadata]);
|
||||
if (this.input instanceof Array && this.input.length) {
|
||||
this.input.forEach(input => params.push(input));
|
||||
}
|
||||
if (typeof comparator === 'function') {
|
||||
result = comparator(params);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
emitEvent(eventName, data) {
|
||||
this.emit(eventName, {
|
||||
id: this.id,
|
||||
data: data
|
||||
});
|
||||
}
|
||||
|
||||
isValid() {
|
||||
return (this.telemetry === 'any' || this.telemetry === 'all') && this.metadata && this.operation;
|
||||
}
|
||||
|
||||
requestLAD(options) {
|
||||
options = Object.assign({},
|
||||
options,
|
||||
{
|
||||
strategy: 'latest',
|
||||
size: 1
|
||||
}
|
||||
);
|
||||
|
||||
if (!this.isValid()) {
|
||||
return this.formatData({}, options.telemetryObjects);
|
||||
}
|
||||
|
||||
const telemetryRequests = options.telemetryObjects
|
||||
.map(telemetryObject => this.telemetryAPI.request(
|
||||
telemetryObject,
|
||||
options
|
||||
));
|
||||
|
||||
return Promise.all(telemetryRequests)
|
||||
.then(telemetryRequestsResults => {
|
||||
telemetryRequestsResults.forEach((results, index) => {
|
||||
const latestDatum = results.length ? results[results.length - 1] : {};
|
||||
if (index === telemetryRequestsResults.length-1) {
|
||||
//when the last result is computed, we return the result
|
||||
return {
|
||||
id: this.id,
|
||||
data: this.formatData(latestDatum, options.telemetryObjects)
|
||||
};
|
||||
} else {
|
||||
if (latestDatum) {
|
||||
this.telemetryDataCache[latestDatum.id] = this.computeResult(latestDatum);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.emitEvent('criterionRemoved');
|
||||
delete this.telemetryObjects;
|
||||
delete this.telemetryDataCache;
|
||||
delete this.telemetryObjectIdAsString;
|
||||
delete this.telemetryObject;
|
||||
}
|
||||
}
|
149
src/plugins/condition/criterion/TelemetryCriterion.js
Normal file
149
src/plugins/condition/criterion/TelemetryCriterion.js
Normal file
@ -0,0 +1,149 @@
|
||||
/*****************************************************************************
|
||||
* 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 EventEmitter from 'EventEmitter';
|
||||
import {OPERATIONS} from '../utils/operations';
|
||||
|
||||
export default class TelemetryCriterion extends EventEmitter {
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
this.openmct = openmct;
|
||||
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.telemetryObject = telemetryDomainObjectDefinition.telemetryObject;
|
||||
this.telemetryObjectIdAsString = this.objectAPI.makeKeyString(telemetryDomainObjectDefinition.telemetry);
|
||||
this.on(`subscription:${this.telemetryObjectIdAsString}`, this.handleSubscription);
|
||||
this.emitEvent('criterionUpdated', this);
|
||||
}
|
||||
|
||||
updateTelemetry(telemetryObjects) {
|
||||
this.telemetryObject = telemetryObjects[this.telemetryObjectIdAsString];
|
||||
}
|
||||
|
||||
formatData(data) {
|
||||
const datum = {
|
||||
result: this.computeResult(data)
|
||||
};
|
||||
|
||||
if (data) {
|
||||
// TODO check back to see if we should format times here
|
||||
this.timeAPI.getAllTimeSystems().forEach(timeSystem => {
|
||||
datum[timeSystem.key] = data[timeSystem.key]
|
||||
});
|
||||
}
|
||||
return datum;
|
||||
}
|
||||
|
||||
handleSubscription(data) {
|
||||
if(this.isValid()) {
|
||||
this.emitEvent('criterionResultUpdated', this.formatData(data));
|
||||
} else {
|
||||
this.emitEvent('criterionResultUpdated', this.formatData({}));
|
||||
}
|
||||
}
|
||||
|
||||
findOperation(operation) {
|
||||
for (let i=0, ii=OPERATIONS.length; i < ii; i++) {
|
||||
if (operation === OPERATIONS[i].name) {
|
||||
return OPERATIONS[i].operation;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
computeResult(data) {
|
||||
let result = false;
|
||||
if (data) {
|
||||
let comparator = this.findOperation(this.operation);
|
||||
let params = [];
|
||||
params.push(data[this.metadata]);
|
||||
if (this.input instanceof Array && this.input.length) {
|
||||
this.input.forEach(input => params.push(input));
|
||||
}
|
||||
if (typeof comparator === 'function') {
|
||||
result = comparator(params);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
emitEvent(eventName, data) {
|
||||
this.emit(eventName, {
|
||||
id: this.id,
|
||||
data: data
|
||||
});
|
||||
}
|
||||
|
||||
isValid() {
|
||||
return this.telemetryObject && this.metadata && this.operation;
|
||||
}
|
||||
|
||||
requestLAD(options) {
|
||||
options = Object.assign({},
|
||||
options,
|
||||
{
|
||||
strategy: 'latest',
|
||||
size: 1
|
||||
}
|
||||
);
|
||||
|
||||
if (!this.isValid()) {
|
||||
return {
|
||||
id: this.id,
|
||||
data: this.formatData({})
|
||||
};
|
||||
}
|
||||
|
||||
return this.telemetryAPI.request(
|
||||
this.telemetryObject,
|
||||
options
|
||||
).then(results => {
|
||||
const latestDatum = results.length ? results[results.length - 1] : {};
|
||||
return {
|
||||
id: this.id,
|
||||
data: this.formatData(latestDatum)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.off(`subscription:${this.telemetryObjectIdAsString}`, this.handleSubscription);
|
||||
this.emitEvent('criterionRemoved');
|
||||
delete this.telemetryObjectIdAsString;
|
||||
delete this.telemetryObject;
|
||||
}
|
||||
}
|
111
src/plugins/condition/criterion/TelemetryCriterionSpec.js
Normal file
111
src/plugins/condition/criterion/TelemetryCriterionSpec.js
Normal file
@ -0,0 +1,111 @@
|
||||
/*****************************************************************************
|
||||
* 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";
|
||||
|
||||
let openmct = {},
|
||||
mockListener,
|
||||
testCriterionDefinition,
|
||||
testTelemetryObject,
|
||||
telemetryCriterion;
|
||||
|
||||
describe("The telemetry criterion", function () {
|
||||
|
||||
beforeEach (() => {
|
||||
testTelemetryObject = {
|
||||
identifier:{ namespace: "", key: "test-object"},
|
||||
type: "test-object",
|
||||
name: "Test Object",
|
||||
telemetry: {
|
||||
valueMetadatas: [{
|
||||
key: "value",
|
||||
name: "Value",
|
||||
hints: {
|
||||
range: 2
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "utc",
|
||||
name: "Time",
|
||||
format: "utc",
|
||||
hints: {
|
||||
domain: 1
|
||||
}
|
||||
}, {
|
||||
key: "testSource",
|
||||
source: "value",
|
||||
name: "Test",
|
||||
format: "enum"
|
||||
}]
|
||||
}
|
||||
};
|
||||
openmct.objects = jasmine.createSpyObj('objects', ['get', 'makeKeyString']);
|
||||
openmct.objects.makeKeyString.and.returnValue(testTelemetryObject.identifier.key);
|
||||
openmct.telemetry = jasmine.createSpyObj('telemetry', ['isTelemetryObject', "subscribe", "getMetadata", "getValueFormatter"]);
|
||||
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.time = jasmine.createSpyObj('timeAPI',
|
||||
['timeSystem', 'bounds', 'getAllTimeSystems']
|
||||
);
|
||||
openmct.time.timeSystem.and.returnValue({key: 'system'});
|
||||
openmct.time.bounds.and.returnValue({start: 0, end: 1});
|
||||
openmct.time.getAllTimeSystems.and.returnValue([{key: 'system'}]);
|
||||
|
||||
testCriterionDefinition = {
|
||||
id: 'test-criterion-id',
|
||||
telemetry: openmct.objects.makeKeyString(testTelemetryObject.identifier),
|
||||
operation: 'lessThan',
|
||||
metadata: 'sin',
|
||||
telemetryObject: testTelemetryObject
|
||||
};
|
||||
|
||||
mockListener = jasmine.createSpy('listener');
|
||||
|
||||
telemetryCriterion = new TelemetryCriterion(
|
||||
testCriterionDefinition,
|
||||
openmct
|
||||
);
|
||||
|
||||
telemetryCriterion.on('criterionResultUpdated', mockListener);
|
||||
|
||||
});
|
||||
|
||||
it("initializes with a telemetry objectId as string", function () {
|
||||
expect(telemetryCriterion.telemetryObjectIdAsString).toEqual(testTelemetryObject.identifier.key);
|
||||
});
|
||||
|
||||
it("updates and emits event on new data from telemetry providers", function () {
|
||||
spyOn(telemetryCriterion, 'emitEvent').and.callThrough();
|
||||
telemetryCriterion.handleSubscription({
|
||||
value: 'Hello',
|
||||
utc: 'Hi'
|
||||
});
|
||||
expect(telemetryCriterion.emitEvent).toHaveBeenCalled();
|
||||
});
|
||||
});
|
68
src/plugins/condition/plugin.js
Normal file
68
src/plugins/condition/plugin.js
Normal file
@ -0,0 +1,68 @@
|
||||
/*****************************************************************************
|
||||
* 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 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() {
|
||||
|
||||
return function install(openmct) {
|
||||
|
||||
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.',
|
||||
creatable: true,
|
||||
cssClass: 'icon-conditional',
|
||||
initialize: function (domainObject) {
|
||||
domainObject.configuration = {
|
||||
conditionTestData: [],
|
||||
conditionCollection: [{
|
||||
isDefault: true,
|
||||
id: uuid(),
|
||||
configuration: {
|
||||
name: 'Default',
|
||||
output: 'Default',
|
||||
trigger: 'all',
|
||||
criteria: []
|
||||
},
|
||||
summary: 'Default condition'
|
||||
}]
|
||||
};
|
||||
domainObject.composition = [];
|
||||
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));
|
||||
openmct.objectViews.addProvider(new ConditionSetViewProvider(openmct));
|
||||
|
||||
}
|
||||
}
|
97
src/plugins/condition/pluginSpec.js
Normal file
97
src/plugins/condition/pluginSpec.js
Normal file
@ -0,0 +1,97 @@
|
||||
/*****************************************************************************
|
||||
* 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 { createOpenMct } from "testTools";
|
||||
import ConditionPlugin from "./plugin";
|
||||
|
||||
let openmct = createOpenMct();
|
||||
openmct.install(new ConditionPlugin());
|
||||
|
||||
let conditionSetDefinition;
|
||||
let mockConditionSetDomainObject;
|
||||
let element;
|
||||
let child;
|
||||
|
||||
describe('the plugin', function () {
|
||||
|
||||
beforeAll((done) => {
|
||||
|
||||
conditionSetDefinition = openmct.types.get('conditionSet').definition;
|
||||
const appHolder = document.createElement('div');
|
||||
appHolder.style.width = '640px';
|
||||
appHolder.style.height = '480px';
|
||||
|
||||
element = document.createElement('div');
|
||||
child = document.createElement('div');
|
||||
element.appendChild(child);
|
||||
|
||||
mockConditionSetDomainObject = {
|
||||
identifier: {
|
||||
key: 'testConditionSetKey',
|
||||
namespace: ''
|
||||
},
|
||||
type: 'conditionSet'
|
||||
};
|
||||
|
||||
conditionSetDefinition.initialize(mockConditionSetDomainObject);
|
||||
|
||||
openmct.on('start', done);
|
||||
openmct.start(appHolder);
|
||||
});
|
||||
|
||||
let mockConditionSetObject = {
|
||||
name: 'Condition Set',
|
||||
key: 'conditionSet',
|
||||
creatable: true
|
||||
};
|
||||
|
||||
it('defines a conditionSet object type with the correct key', () => {
|
||||
expect(conditionSetDefinition.key).toEqual(mockConditionSetObject.key);
|
||||
});
|
||||
|
||||
describe('the conditionSet object', () => {
|
||||
|
||||
it('is creatable', () => {
|
||||
expect(conditionSetDefinition.creatable).toEqual(mockConditionSetObject.creatable);
|
||||
});
|
||||
|
||||
it('initializes with an empty composition list', () => {
|
||||
expect(mockConditionSetDomainObject.composition instanceof Array).toBeTrue();
|
||||
expect(mockConditionSetDomainObject.composition.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('provides a view', () => {
|
||||
const testViewObject = {
|
||||
id:"test-object",
|
||||
type: "conditionSet",
|
||||
configuration: {
|
||||
conditionCollection: []
|
||||
}
|
||||
};
|
||||
|
||||
const applicableViews = openmct.objectViews.get(testViewObject);
|
||||
let conditionSetView = applicableViews.find((viewProvider) => viewProvider.key === 'conditionSet.view');
|
||||
expect(conditionSetView).toBeDefined();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
54
src/plugins/condition/utils/constants.js
Normal file
54
src/plugins/condition/utils/constants.js
Normal file
@ -0,0 +1,54 @@
|
||||
/*****************************************************************************
|
||||
* 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'
|
||||
}
|
||||
};
|
54
src/plugins/condition/utils/evaluator.js
Normal file
54
src/plugins/condition/utils/evaluator.js
Normal file
@ -0,0 +1,54 @@
|
||||
/*****************************************************************************
|
||||
* 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 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 result;
|
||||
};
|
||||
|
||||
//Returns true only if limit number of results are satisfied
|
||||
export const computeConditionByLimit = (resultMap, limit) => {
|
||||
let trueCount = 0;
|
||||
for (let key in resultMap) {
|
||||
if (resultMap.hasOwnProperty(key)) {
|
||||
if (resultMap[key]) {
|
||||
trueCount++;
|
||||
}
|
||||
if (trueCount > limit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return trueCount === limit;
|
||||
};
|
66
src/plugins/condition/utils/evaluatorSpec.js
Normal file
66
src/plugins/condition/utils/evaluatorSpec.js
Normal file
@ -0,0 +1,66 @@
|
||||
/*****************************************************************************
|
||||
* 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 { computeConditionByLimit } from "./evaluator";
|
||||
|
||||
describe('evaluate results based on trigger', function () {
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
276
src/plugins/condition/utils/operations.js
Normal file
276
src/plugins/condition/utils/operations.js
Normal file
@ -0,0 +1,276 @@
|
||||
/*****************************************************************************
|
||||
* 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';
|
||||
|
||||
export const OPERATIONS = [
|
||||
{
|
||||
name: 'equalTo',
|
||||
operation: function (input) {
|
||||
return Number(input[0]) === Number(input[1]);
|
||||
},
|
||||
text: 'is equal to',
|
||||
appliesTo: ['number'],
|
||||
inputCount: 1,
|
||||
getDescription: function (values) {
|
||||
return ' is ' + values.join(', ');
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'notEqualTo',
|
||||
operation: function (input) {
|
||||
return Number(input[0]) !== Number(input[1]);
|
||||
},
|
||||
text: 'is not equal to',
|
||||
appliesTo: ['number'],
|
||||
inputCount: 1,
|
||||
getDescription: function (values) {
|
||||
return ' is not ' + values.join(', ');
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'greaterThan',
|
||||
operation: function (input) {
|
||||
return Number(input[0]) > Number(input[1]);
|
||||
},
|
||||
text: 'is greater than',
|
||||
appliesTo: ['number'],
|
||||
inputCount: 1,
|
||||
getDescription: function (values) {
|
||||
return ' > ' + values.join(', ');
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'lessThan',
|
||||
operation: function (input) {
|
||||
return Number(input[0]) < Number(input[1]);
|
||||
},
|
||||
text: 'is less than',
|
||||
appliesTo: ['number'],
|
||||
inputCount: 1,
|
||||
getDescription: function (values) {
|
||||
return ' < ' + values.join(', ');
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'greaterThanOrEq',
|
||||
operation: function (input) {
|
||||
return Number(input[0]) >= Number(input[1]);
|
||||
},
|
||||
text: 'is greater than or equal to',
|
||||
appliesTo: ['number'],
|
||||
inputCount: 1,
|
||||
getDescription: function (values) {
|
||||
return ' >= ' + values.join(', ');
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'lessThanOrEq',
|
||||
operation: function (input) {
|
||||
return Number(input[0]) <= Number(input[1]);
|
||||
},
|
||||
text: 'is less than or equal to',
|
||||
appliesTo: ['number'],
|
||||
inputCount: 1,
|
||||
getDescription: function (values) {
|
||||
return ' <= ' + values.join(', ');
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'between',
|
||||
operation: function (input) {
|
||||
let numberInputs = [];
|
||||
input.forEach(inputValue => numberInputs.push(Number(inputValue)));
|
||||
let larger = Math.max(...numberInputs.slice(1,3));
|
||||
let smaller = Math.min(...numberInputs.slice(1,3));
|
||||
return (numberInputs[0] > smaller) && (numberInputs[0] < larger);
|
||||
},
|
||||
text: 'is between',
|
||||
appliesTo: ['number'],
|
||||
inputCount: 2,
|
||||
getDescription: function (values) {
|
||||
return ' is between ' + values[0] + ' and ' + values[1];
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'notBetween',
|
||||
operation: function (input) {
|
||||
let numberInputs = [];
|
||||
input.forEach(inputValue => numberInputs.push(Number(inputValue)));
|
||||
let larger = Math.max(...numberInputs.slice(1,3));
|
||||
let smaller = Math.min(...numberInputs.slice(1,3));
|
||||
return (numberInputs[0] < smaller) || (numberInputs[0] > larger);
|
||||
},
|
||||
text: 'is not between',
|
||||
appliesTo: ['number'],
|
||||
inputCount: 2,
|
||||
getDescription: function (values) {
|
||||
return ' is not between ' + values[0] + ' and ' + values[1];
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'textContains',
|
||||
operation: function (input) {
|
||||
return input[0] && input[1] && input[0].includes(input[1]);
|
||||
},
|
||||
text: 'text contains',
|
||||
appliesTo: ['string'],
|
||||
inputCount: 1,
|
||||
getDescription: function (values) {
|
||||
return ' contains ' + values.join(', ');
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'textDoesNotContain',
|
||||
operation: function (input) {
|
||||
return input[0] && input[1] && !input[0].includes(input[1]);
|
||||
},
|
||||
text: 'text does not contain',
|
||||
appliesTo: ['string'],
|
||||
inputCount: 1,
|
||||
getDescription: function (values) {
|
||||
return ' does not contain ' + values.join(', ');
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'textStartsWith',
|
||||
operation: function (input) {
|
||||
return input[0].startsWith(input[1]);
|
||||
},
|
||||
text: 'text starts with',
|
||||
appliesTo: ['string'],
|
||||
inputCount: 1,
|
||||
getDescription: function (values) {
|
||||
return ' starts with ' + values.join(', ');
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'textEndsWith',
|
||||
operation: function (input) {
|
||||
return input[0].endsWith(input[1]);
|
||||
},
|
||||
text: 'text ends with',
|
||||
appliesTo: ['string'],
|
||||
inputCount: 1,
|
||||
getDescription: function (values) {
|
||||
return ' ends with ' + values.join(', ');
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'textIsExactly',
|
||||
operation: function (input) {
|
||||
return input[0] === input[1];
|
||||
},
|
||||
text: 'text is exactly',
|
||||
appliesTo: ['string'],
|
||||
inputCount: 1,
|
||||
getDescription: function (values) {
|
||||
return ' is exactly ' + values.join(', ');
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'isUndefined',
|
||||
operation: function (input) {
|
||||
return typeof input[0] === 'undefined';
|
||||
},
|
||||
text: 'is undefined',
|
||||
appliesTo: ['string', 'number', 'enum'],
|
||||
inputCount: 0,
|
||||
getDescription: function () {
|
||||
return ' is undefined';
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'isDefined',
|
||||
operation: function (input) {
|
||||
return typeof input[0] !== 'undefined';
|
||||
},
|
||||
text: 'is defined',
|
||||
appliesTo: ['string', 'number', 'enum'],
|
||||
inputCount: 0,
|
||||
getDescription: function () {
|
||||
return ' is defined';
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'enumValueIs',
|
||||
operation: function (input) {
|
||||
return input[0] === input[1];
|
||||
},
|
||||
text: 'is',
|
||||
appliesTo: ['enum'],
|
||||
inputCount: 1,
|
||||
getDescription: function (values) {
|
||||
return ' is ' + values.join(', ');
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'enumValueIsNot',
|
||||
operation: function (input) {
|
||||
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) {
|
||||
if (input[1]) {
|
||||
const values = input[1].split(',');
|
||||
return values.find((value) => input[0].toString() === _.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) {
|
||||
if (input[1]) {
|
||||
const values = input[1].split(',');
|
||||
const found = values.find((value) => input[0].toString() === _.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];
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
export const INPUT_TYPES = {
|
||||
'string': 'text',
|
||||
'number': 'number'
|
||||
};
|
90
src/plugins/condition/utils/operationsSpec.js
Normal file
90
src/plugins/condition/utils/operationsSpec.js
Normal file
@ -0,0 +1,90 @@
|
||||
/*****************************************************************************
|
||||
* 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');
|
||||
|
||||
describe('Is one of and is not one of 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();
|
||||
});
|
||||
});
|
45
src/plugins/condition/utils/styleUtils.js
Normal file
45
src/plugins/condition/utils/styleUtils.js
Normal file
@ -0,0 +1,45 @@
|
||||
/*****************************************************************************
|
||||
* 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 getStyleProp = (key, defaultValue) => {
|
||||
let styleProp = undefined;
|
||||
switch(key) {
|
||||
case 'fill': styleProp = {
|
||||
backgroundColor: defaultValue || 'transparent'
|
||||
};
|
||||
break;
|
||||
case 'stroke': styleProp = {
|
||||
border: '1px solid ' + (defaultValue || 'transparent')
|
||||
};
|
||||
break;
|
||||
case 'color': styleProp = {
|
||||
color: defaultValue || 'transparent'
|
||||
};
|
||||
break;
|
||||
case 'url': styleProp = {
|
||||
imageUrl: defaultValue || 'transparent'
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
return styleProp;
|
||||
};
|
64
src/plugins/conditionWidget/ConditionWidgetViewProvider.js
Normal file
64
src/plugins/conditionWidget/ConditionWidgetViewProvider.js
Normal file
@ -0,0 +1,64 @@
|
||||
/*****************************************************************************
|
||||
* 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;
|
||||
}
|
||||
};
|
||||
}
|
55
src/plugins/conditionWidget/components/ConditionWidget.vue
Normal file
55
src/plugins/conditionWidget/components/ConditionWidget.vue
Normal file
@ -0,0 +1,55 @@
|
||||
/*****************************************************************************
|
||||
* 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>
|
||||
<a class="c-condition-widget"
|
||||
:href="internalDomainObject.url"
|
||||
>
|
||||
<div class="c-condition-widget__label">
|
||||
{{ internalDomainObject.label }}
|
||||
</div>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
inject: ['openmct', 'domainObject'],
|
||||
data: function () {
|
||||
return {
|
||||
internalDomainObject: this.domainObject
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.unlisten = this.openmct.objects.observe(this.internalDomainObject, '*', this.updateInternalDomainObject);
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.unlisten) {
|
||||
this.unlisten();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateInternalDomainObject(domainObject) {
|
||||
this.internalDomainObject = domainObject;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
58
src/plugins/conditionWidget/components/condition-widget.scss
Normal file
58
src/plugins/conditionWidget/components/condition-widget.scss
Normal file
@ -0,0 +1,58 @@
|
||||
/*****************************************************************************
|
||||
* 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;
|
||||
cursor: inherit !important;
|
||||
&[href] {
|
||||
cursor: pointer !important;
|
||||
}
|
||||
}
|
||||
|
||||
// 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%);
|
||||
}
|
58
src/plugins/conditionWidget/plugin.js
Normal file
58
src/plugins/conditionWidget/plugin.js
Normal file
@ -0,0 +1,58 @@
|
||||
/*****************************************************************************
|
||||
* 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"
|
||||
}
|
||||
]
|
||||
});
|
||||
};
|
||||
}
|
@ -347,78 +347,6 @@ 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",
|
||||
@ -429,7 +357,7 @@ define(['lodash'], function (_) {
|
||||
property: function (selectionPath) {
|
||||
return getPath(selectionPath);
|
||||
},
|
||||
icon: "icon-gear",
|
||||
icon: "icon-font",
|
||||
title: "Edit text properties",
|
||||
dialog: DIALOG_FORM.text
|
||||
};
|
||||
@ -505,14 +433,14 @@ define(['lodash'], function (_) {
|
||||
|
||||
let toolbar = {
|
||||
'add-menu': [],
|
||||
'text': [],
|
||||
'url': [],
|
||||
'toggle-frame': [],
|
||||
'display-mode': [],
|
||||
'telemetry-value': [],
|
||||
'style': [],
|
||||
'text-style': [],
|
||||
'position': [],
|
||||
'text': [],
|
||||
'url': [],
|
||||
'remove': []
|
||||
};
|
||||
|
||||
@ -550,15 +478,8 @@ 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)
|
||||
];
|
||||
}
|
||||
@ -575,15 +496,8 @@ 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)
|
||||
];
|
||||
}
|
||||
@ -603,12 +517,6 @@ 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),
|
||||
@ -622,11 +530,6 @@ 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),
|
||||
@ -636,18 +539,10 @@ 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),
|
||||
|
@ -23,20 +23,20 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="isEditing"
|
||||
class="c-properties"
|
||||
class="c-inspect-properties"
|
||||
>
|
||||
<div class="c-properties__header">
|
||||
<div class="c-inspect-properties__header">
|
||||
Alphanumeric Format
|
||||
</div>
|
||||
<ul class="c-properties__section">
|
||||
<li class="c-properties__row">
|
||||
<ul class="c-inspect-properties__section">
|
||||
<li class="c-inspect-properties__row">
|
||||
<div
|
||||
class="c-properties__label"
|
||||
class="c-inspect-properties__label"
|
||||
title="Printf formatting for the selected telemetry"
|
||||
>
|
||||
<label for="telemetryPrintfFormat">Format</label>
|
||||
</div>
|
||||
<div class="c-properties__value">
|
||||
<div class="c-inspect-properties__value">
|
||||
<input
|
||||
id="telemetryPrintfFormat"
|
||||
type="text"
|
||||
|
@ -29,6 +29,7 @@
|
||||
>
|
||||
<div
|
||||
class="c-box-view"
|
||||
:class="[styleClass]"
|
||||
:style="style"
|
||||
></div>
|
||||
</layout-frame>
|
||||
@ -36,6 +37,7 @@
|
||||
|
||||
<script>
|
||||
import LayoutFrame from './LayoutFrame.vue'
|
||||
import conditionalStylesMixin from '../mixins/objectStyles-mixin';
|
||||
|
||||
export default {
|
||||
makeDefinition() {
|
||||
@ -52,6 +54,7 @@ export default {
|
||||
components: {
|
||||
LayoutFrame
|
||||
},
|
||||
mixins: [conditionalStylesMixin],
|
||||
props: {
|
||||
item: {
|
||||
type: Object,
|
||||
@ -71,10 +74,13 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
style() {
|
||||
return {
|
||||
return Object.assign({
|
||||
backgroundColor: this.item.fill,
|
||||
border: '1px solid ' + this.item.stroke
|
||||
};
|
||||
}, this.itemStyle);
|
||||
},
|
||||
styleClass() {
|
||||
return this.itemStyle && this.itemStyle.isStyleInvisible;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
@ -29,6 +29,7 @@
|
||||
>
|
||||
<div
|
||||
class="c-image-view"
|
||||
:class="[styleClass]"
|
||||
:style="style"
|
||||
></div>
|
||||
</layout-frame>
|
||||
@ -36,6 +37,7 @@
|
||||
|
||||
<script>
|
||||
import LayoutFrame from './LayoutFrame.vue'
|
||||
import conditionalStylesMixin from "../mixins/objectStyles-mixin";
|
||||
|
||||
export default {
|
||||
makeDefinition(openmct, gridSize, element) {
|
||||
@ -52,6 +54,7 @@ export default {
|
||||
components: {
|
||||
LayoutFrame
|
||||
},
|
||||
mixins: [conditionalStylesMixin],
|
||||
props: {
|
||||
item: {
|
||||
type: Object,
|
||||
@ -72,9 +75,12 @@ export default {
|
||||
computed: {
|
||||
style() {
|
||||
return {
|
||||
backgroundImage: 'url(' + this.item.url + ')',
|
||||
border: '1px solid ' + this.item.stroke
|
||||
}
|
||||
backgroundImage: this.itemStyle ? ('url(' + this.itemStyle.imageUrl + ')') : 'url(' + this.item.url + ')',
|
||||
border: (this.itemStyle && this.itemStyle.border) ? this.itemStyle.border : ('1px solid ' + this.item.stroke)
|
||||
};
|
||||
},
|
||||
styleClass() {
|
||||
return this.itemStyle && this.itemStyle.isStyleInvisible;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
@ -23,6 +23,7 @@
|
||||
<template>
|
||||
<div
|
||||
class="l-layout__frame c-frame no-frame"
|
||||
:class="[styleClass]"
|
||||
:style="style"
|
||||
>
|
||||
<svg
|
||||
@ -31,7 +32,7 @@
|
||||
>
|
||||
<line
|
||||
v-bind="linePosition"
|
||||
:stroke="item.stroke"
|
||||
:stroke="stroke"
|
||||
stroke-width="2"
|
||||
/>
|
||||
</svg>
|
||||
@ -60,6 +61,8 @@
|
||||
|
||||
<script>
|
||||
|
||||
import conditionalStylesMixin from "../mixins/objectStyles-mixin";
|
||||
|
||||
const START_HANDLE_QUADRANTS = {
|
||||
1: 'c-frame-edit__handle--sw',
|
||||
2: 'c-frame-edit__handle--se',
|
||||
@ -85,6 +88,7 @@ export default {
|
||||
};
|
||||
},
|
||||
inject: ['openmct'],
|
||||
mixins: [conditionalStylesMixin],
|
||||
props: {
|
||||
item: {
|
||||
type: Object,
|
||||
@ -122,6 +126,13 @@ export default {
|
||||
}
|
||||
return {x, y, x2, y2};
|
||||
},
|
||||
stroke() {
|
||||
if (this.itemStyle && this.itemStyle.border) {
|
||||
return this.itemStyle.border.replace('1px solid ', '');
|
||||
} else {
|
||||
return this.item.stroke;
|
||||
}
|
||||
},
|
||||
style() {
|
||||
let {x, y, x2, y2} = this.position;
|
||||
let width = Math.max(this.gridSize[0] * Math.abs(x - x2), 1);
|
||||
@ -135,6 +146,9 @@ export default {
|
||||
height: `${height}px`
|
||||
};
|
||||
},
|
||||
styleClass() {
|
||||
return this.itemStyle && this.itemStyle.isStyleInvisible;
|
||||
},
|
||||
startHandleClass() {
|
||||
return START_HANDLE_QUADRANTS[this.vectorQuadrant];
|
||||
},
|
||||
|
@ -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'];
|
||||
DEFAULT_HIDDEN_FRAME_TYPES = ['hyperlink', 'summary-widget', 'conditionWidget'];
|
||||
|
||||
function getDefaultDimensions(gridSize) {
|
||||
return MINIMUM_FRAME_SIZE.map((min, index) => {
|
||||
|
@ -36,6 +36,8 @@
|
||||
<div
|
||||
v-if="showLabel"
|
||||
class="c-telemetry-view__label"
|
||||
:class="[styleClass]"
|
||||
:style="objectStyle"
|
||||
>
|
||||
<div class="c-telemetry-view__label-text">
|
||||
{{ domainObject.name }}
|
||||
@ -46,7 +48,8 @@
|
||||
v-if="showValue"
|
||||
:title="fieldName"
|
||||
class="c-telemetry-view__value"
|
||||
:class="[telemetryClass]"
|
||||
:class="[telemetryClass, !telemetryClass && styleClass]"
|
||||
:style="!telemetryClass && objectStyle"
|
||||
>
|
||||
<div class="c-telemetry-view__value-text">
|
||||
{{ telemetryValue }}
|
||||
@ -59,6 +62,7 @@
|
||||
<script>
|
||||
import LayoutFrame from './LayoutFrame.vue'
|
||||
import printj from 'printj'
|
||||
import StyleRuleManager from "../../condition/StyleRuleManager";
|
||||
|
||||
const DEFAULT_TELEMETRY_DIMENSIONS = [10, 5],
|
||||
DEFAULT_POSITION = [1, 1],
|
||||
@ -109,7 +113,8 @@ export default {
|
||||
datum: undefined,
|
||||
formats: undefined,
|
||||
domainObject: undefined,
|
||||
currentObjectPath: undefined
|
||||
currentObjectPath: undefined,
|
||||
objectStyle: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@ -129,6 +134,9 @@ export default {
|
||||
fontSize: this.item.size
|
||||
}
|
||||
},
|
||||
styleClass() {
|
||||
return this.objectStyle && this.objectStyle.isStyleInvisible;
|
||||
},
|
||||
fieldName() {
|
||||
return this.valueMetadata && this.valueMetadata.name;
|
||||
},
|
||||
@ -182,6 +190,15 @@ export default {
|
||||
this.removeSelectable();
|
||||
}
|
||||
|
||||
if (this.unlistenStyles) {
|
||||
this.unlistenStyles();
|
||||
}
|
||||
|
||||
if (this.styleRuleManager) {
|
||||
this.styleRuleManager.destroy();
|
||||
delete this.styleRuleManager;
|
||||
}
|
||||
|
||||
this.openmct.time.off("bounds", this.refreshData);
|
||||
},
|
||||
methods: {
|
||||
@ -224,6 +241,7 @@ export default {
|
||||
},
|
||||
setObject(domainObject) {
|
||||
this.domainObject = domainObject;
|
||||
this.initObjectStyles();
|
||||
this.keyString = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||
this.metadata = this.openmct.telemetry.getMetadata(this.domainObject);
|
||||
this.limitEvaluator = this.openmct.telemetry.limitEvaluator(this.domainObject);
|
||||
@ -248,6 +266,30 @@ export default {
|
||||
},
|
||||
showContextMenu(event) {
|
||||
this.openmct.contextMenu._showContextMenuForObjectPath(this.currentObjectPath, event.x, event.y, CONTEXT_MENU_ACTIONS);
|
||||
},
|
||||
initObjectStyles() {
|
||||
if (this.domainObject.configuration) {
|
||||
this.styleRuleManager = new StyleRuleManager(this.domainObject.configuration.objectStyles, this.openmct, this.updateStyle.bind(this));
|
||||
|
||||
if (this.unlistenStyles) {
|
||||
this.unlistenStyles();
|
||||
}
|
||||
this.unlistenStyles = this.openmct.objects.observe(this.domainObject, 'configuration.objectStyles', (newObjectStyle) => {
|
||||
//Updating object styles in the inspector view will trigger this so that the changes are reflected immediately
|
||||
this.styleRuleManager.updateObjectStyleConfig(newObjectStyle);
|
||||
});
|
||||
}
|
||||
},
|
||||
updateStyle(styleObj) {
|
||||
let keys = Object.keys(styleObj);
|
||||
keys.forEach(key => {
|
||||
if ((typeof styleObj[key] === 'string') && (styleObj[key].indexOf('transparent') > -1)) {
|
||||
if (styleObj[key]) {
|
||||
styleObj[key] = '';
|
||||
}
|
||||
}
|
||||
});
|
||||
this.objectStyle = styleObj;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +29,7 @@
|
||||
>
|
||||
<div
|
||||
class="c-text-view"
|
||||
:class="[styleClass]"
|
||||
:style="style"
|
||||
>
|
||||
{{ item.text }}
|
||||
@ -38,6 +39,7 @@
|
||||
|
||||
<script>
|
||||
import LayoutFrame from './LayoutFrame.vue'
|
||||
import conditionalStylesMixin from "../mixins/objectStyles-mixin";
|
||||
|
||||
export default {
|
||||
makeDefinition(openmct, gridSize, element) {
|
||||
@ -57,6 +59,7 @@ export default {
|
||||
components: {
|
||||
LayoutFrame
|
||||
},
|
||||
mixins: [conditionalStylesMixin],
|
||||
props: {
|
||||
item: {
|
||||
type: Object,
|
||||
@ -76,12 +79,15 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
style() {
|
||||
return {
|
||||
return Object.assign({
|
||||
backgroundColor: this.item.fill,
|
||||
borderColor: this.item.stroke,
|
||||
border: '1px solid ' + this.item.stroke,
|
||||
color: this.item.color,
|
||||
fontSize: this.item.size
|
||||
};
|
||||
}, this.itemStyle);
|
||||
},
|
||||
styleClass() {
|
||||
return this.itemStyle && this.itemStyle.isStyleInvisible;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
81
src/plugins/displayLayout/mixins/objectStyles-mixin.js
Normal file
81
src/plugins/displayLayout/mixins/objectStyles-mixin.js
Normal file
@ -0,0 +1,81 @@
|
||||
/*****************************************************************************
|
||||
* 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";
|
||||
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
data() {
|
||||
return {
|
||||
itemStyle: this.itemStyle
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.domainObject = this.$parent.domainObject;
|
||||
this.itemId = this.item.id;
|
||||
this.objectStyle = this.getObjectStyleForItem(this.domainObject.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.domainObject, '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 = style;
|
||||
let keys = Object.keys(this.itemStyle);
|
||||
keys.forEach((key) => {
|
||||
if ((typeof this.itemStyle[key] === 'string') && (this.itemStyle[key].indexOf('transparent') > -1)) {
|
||||
delete this.itemStyle[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
@ -1,17 +1,17 @@
|
||||
<template>
|
||||
<div class="c-properties__section c-filter-settings">
|
||||
<div class="c-inspect-properties__section c-filter-settings">
|
||||
<li
|
||||
v-for="(filter, index) in filterField.filters"
|
||||
:key="index"
|
||||
class="c-properties__row c-filter-settings__setting"
|
||||
class="c-inspect-properties__row c-filter-settings__setting"
|
||||
>
|
||||
<div
|
||||
class="c-properties__label label"
|
||||
class="c-inspect-properties__label label"
|
||||
:disabled="useGlobal"
|
||||
>
|
||||
{{ filterField.name }} =
|
||||
</div>
|
||||
<div class="c-properties__value value">
|
||||
<div class="c-inspect-properties__value value">
|
||||
<!-- EDITING -->
|
||||
<!-- String input, editing -->
|
||||
<template v-if="!filter.possibleValues && isEditing">
|
||||
|
@ -26,17 +26,17 @@
|
||||
</div>
|
||||
|
||||
<div v-if="expanded">
|
||||
<ul class="c-properties">
|
||||
<ul class="c-inspect-properties">
|
||||
<div
|
||||
v-if="!isEditing && persistedFilters.useGlobal"
|
||||
class="c-properties__label span-all"
|
||||
class="c-inspect-properties__label span-all"
|
||||
>
|
||||
Uses global filter
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="isEditing"
|
||||
class="c-properties__label span-all"
|
||||
class="c-inspect-properties__label span-all"
|
||||
>
|
||||
<toggle-switch
|
||||
:id="keyString"
|
||||
|
@ -23,7 +23,7 @@
|
||||
</div>
|
||||
<ul
|
||||
v-if="expanded"
|
||||
class="c-properties"
|
||||
class="c-inspect-properties"
|
||||
>
|
||||
<filter-field
|
||||
v-for="metadatum in globalMetadata"
|
||||
|
@ -7,7 +7,7 @@
|
||||
}
|
||||
.c-filter-tree {
|
||||
// Filters UI uses a tree-based structure
|
||||
.c-properties {
|
||||
.c-inspect-properties {
|
||||
// Add extra margin to account for filter-indicator
|
||||
margin-left: 38px;
|
||||
}
|
||||
|
@ -313,8 +313,4 @@
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.c-object-view {
|
||||
display: contents;
|
||||
}
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ export default function ImageryViewProvider(openmct) {
|
||||
return {
|
||||
show: function (element) {
|
||||
component = new Vue({
|
||||
el: element,
|
||||
components: {
|
||||
ImageryViewLayout
|
||||
},
|
||||
@ -33,7 +34,6 @@ export default function ImageryViewProvider(openmct) {
|
||||
openmct,
|
||||
domainObject
|
||||
},
|
||||
el: element,
|
||||
template: '<imagery-view-layout ref="ImageryLayout"></imagery-view-layout>'
|
||||
});
|
||||
},
|
||||
|
@ -19,8 +19,8 @@
|
||||
this source code distribution or the Licensing information page available
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
<span ng-controller="PlotController as controller"
|
||||
class="abs holder holder-plot has-control-bar">
|
||||
<div ng-controller="PlotController as controller"
|
||||
class="c-plot holder holder-plot has-control-bar">
|
||||
<div class="c-control-bar" ng-show="!controller.hideExportButtons">
|
||||
<span class="c-button-set c-button-set--strip-h">
|
||||
<button class="c-button icon-download"
|
||||
@ -50,4 +50,4 @@
|
||||
the-x-axis="xAxis">
|
||||
</mct-plot>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
|
@ -19,8 +19,8 @@
|
||||
this source code distribution or the Licensing information page available
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
<span ng-controller="StackedPlotController as stackedPlot"
|
||||
class="abs holder holder-plot has-control-bar t-plot-stacked">
|
||||
<div ng-controller="StackedPlotController as stackedPlot"
|
||||
class="c-plot c-plot--stacked holder holder-plot has-control-bar">
|
||||
<div class="l-control-bar" ng-show="!stackedPlot.hideExportButtons">
|
||||
<span class="c-button-set c-button-set--strip-h">
|
||||
<button class="c-button icon-download"
|
||||
@ -57,4 +57,4 @@
|
||||
<mct-overlay-plot domain-object="telemetryObject"></mct-overlay-plot>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||
* Open MCT, Copyright (c) 2014-2020, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
@ -47,6 +47,8 @@ define([
|
||||
'./goToOriginalAction/plugin',
|
||||
'./clearData/plugin',
|
||||
'./webPage/plugin',
|
||||
'./condition/plugin',
|
||||
'./conditionWidget/plugin',
|
||||
'./themes/espresso',
|
||||
'./themes/maelstrom',
|
||||
'./themes/snow'
|
||||
@ -77,6 +79,8 @@ define([
|
||||
GoToOriginalAction,
|
||||
ClearData,
|
||||
WebPagePlugin,
|
||||
ConditionPlugin,
|
||||
ConditionWidgetPlugin,
|
||||
Espresso,
|
||||
Maelstrom,
|
||||
Snow
|
||||
@ -185,6 +189,8 @@ define([
|
||||
plugins.Espresso = Espresso.default;
|
||||
plugins.Maelstrom = Maelstrom.default;
|
||||
plugins.Snow = Snow.default;
|
||||
plugins.Condition = ConditionPlugin.default;
|
||||
plugins.ConditionWidget = ConditionWidgetPlugin.default;
|
||||
|
||||
return plugins;
|
||||
});
|
||||
|
@ -34,9 +34,7 @@
|
||||
}
|
||||
|
||||
&__object-name {
|
||||
flex: 0 0 auto;
|
||||
@include headerFont();
|
||||
font-size: 1.2em !important;
|
||||
font-size: 1em;
|
||||
margin: $interiorMargin 0 $interiorMarginLg 0;
|
||||
}
|
||||
|
||||
|
@ -1,18 +1,18 @@
|
||||
<template>
|
||||
<div class="c-properties">
|
||||
<div class="c-inspect-properties">
|
||||
<template v-if="isEditing">
|
||||
<div class="c-properties__header">
|
||||
<div class="c-inspect-properties__header">
|
||||
Table Column Size
|
||||
</div>
|
||||
<ul class="c-properties__section">
|
||||
<li class="c-properties__row">
|
||||
<ul class="c-inspect-properties__section">
|
||||
<li class="c-inspect-properties__row">
|
||||
<div
|
||||
class="c-properties__label"
|
||||
class="c-inspect-properties__label"
|
||||
title="Auto-size table"
|
||||
>
|
||||
<label for="AutoSizeControl">Auto-size</label>
|
||||
</div>
|
||||
<div class="c-properties__value">
|
||||
<div class="c-inspect-properties__value">
|
||||
<input
|
||||
id="AutoSizeControl"
|
||||
type="checkbox"
|
||||
@ -22,22 +22,22 @@
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="c-properties__header">
|
||||
<div class="c-inspect-properties__header">
|
||||
Table Column Visibility
|
||||
</div>
|
||||
<ul class="c-properties__section">
|
||||
<ul class="c-inspect-properties__section">
|
||||
<li
|
||||
v-for="(title, key) in headers"
|
||||
:key="key"
|
||||
class="c-properties__row"
|
||||
class="c-inspect-properties__row"
|
||||
>
|
||||
<div
|
||||
class="c-properties__label"
|
||||
class="c-inspect-properties__label"
|
||||
title="Show or hide column"
|
||||
>
|
||||
<label :for="key + 'ColumnControl'">{{ title }}</label>
|
||||
</div>
|
||||
<div class="c-properties__value">
|
||||
<div class="c-inspect-properties__value">
|
||||
<input
|
||||
:id="key + 'ColumnControl'"
|
||||
type="checkbox"
|
||||
|
@ -21,10 +21,6 @@
|
||||
vertical-align: middle; // This is crucial to hiding 4px height injected by browser by default
|
||||
}
|
||||
|
||||
td {
|
||||
color: $colorTelemFresh;
|
||||
}
|
||||
|
||||
/******************************* WRAPPERS */
|
||||
&__headers-w {
|
||||
// Wraps __headers table
|
||||
|
@ -105,10 +105,12 @@ $colorStatusErrorFilter: invert(10%) sepia(96%) saturate(4360%) hue-rotate(351de
|
||||
$colorStatusBtnBg: #666; // Where is this used?
|
||||
$colorStatusPartialBg: #3f5e8b;
|
||||
$colorStatusCompleteBg: #457638;
|
||||
$colorAlert: #ff3c00;
|
||||
$colorAlert: #ff8a0d;
|
||||
$colorAlertFg: #fff;
|
||||
$colorWarningHi: #ff0000;
|
||||
$colorWarningHiFg: #ffdad0;
|
||||
$colorError: #ff3c00;
|
||||
$colorErrorFg: #fff;
|
||||
$colorWarningHi: #990000;
|
||||
$colorWarningHiFg: #FF9594;
|
||||
$colorWarningLo: #ff9900;
|
||||
$colorWarningLoFg: #523400;
|
||||
$colorDiagnostic: #a4b442;
|
||||
@ -126,7 +128,6 @@ $colorFilter: $colorFilterFg; // Standalone against $colorBodyBg
|
||||
// States
|
||||
$colorPausedBg: #ff9900;
|
||||
$colorPausedFg: #333;
|
||||
$colorOk: #33cc33;
|
||||
|
||||
// Base variations
|
||||
$colorBodyBgSubtle: pullForward($colorBodyBg, 5%);
|
||||
@ -221,6 +222,7 @@ $colorSelectBg: $colorBtnBg; // This must be a solid color, not a gradient, due
|
||||
$colorSelectFg: $colorBtnFg;
|
||||
$colorSelectArw: lighten($colorBtnBg, 20%);
|
||||
$shdwSelect: rgba(black, 0.5) 0 0.5px 3px;
|
||||
$controlDisabledOpacity: 0.2;
|
||||
|
||||
// Menus
|
||||
$colorMenuBg: pullForward($colorBodyBg, 15%);
|
||||
@ -326,7 +328,7 @@ $shdwItemText: none;
|
||||
$colorTabBorder: pullForward($colorBodyBg, 10%);
|
||||
$colorTabBodyBg: $colorBodyBg;
|
||||
$colorTabBodyFg: pullForward($colorBodyFg, 20%);
|
||||
$colorTabHeaderBg: pullForward($colorBodyBg, 10%);
|
||||
$colorTabHeaderBg: rgba($colorBodyFg, 0.2);
|
||||
$colorTabHeaderFg: $colorBodyFg;
|
||||
$colorTabHeaderBorder: $colorBodyBg;
|
||||
$colorTabGroupHeaderBg: pullForward($colorBodyBg, 5%);
|
||||
@ -416,15 +418,21 @@ $transInBounceBig: all 300ms cubic-bezier(.2,1.6,.6,3);
|
||||
|
||||
// Discrete items, like Notebook entries, Widget rules
|
||||
$createBtnTextTransform: uppercase;
|
||||
$colorDiscreteItemBg: rgba($colorBodyFg,0.1);
|
||||
$colorDiscreteItemCurrentBg: rgba($colorOk,0.3);
|
||||
|
||||
@mixin discreteItem() {
|
||||
background: rgba($colorBodyFg,0.1);
|
||||
background: $colorDiscreteItemBg;
|
||||
border: none;
|
||||
border-radius: $controlCr;
|
||||
|
||||
.c-input-inline:hover {
|
||||
background: $colorBodyBg;
|
||||
}
|
||||
|
||||
&--current-match {
|
||||
background: $colorDiscreteItemCurrentBg;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin discreteItemInnerElem() {
|
||||
|
@ -109,10 +109,12 @@ $colorStatusErrorFilter: invert(10%) sepia(96%) saturate(4360%) hue-rotate(351de
|
||||
$colorStatusBtnBg: #666; // Where is this used?
|
||||
$colorStatusPartialBg: #3f5e8b;
|
||||
$colorStatusCompleteBg: #457638;
|
||||
$colorAlert: #ff3c00;
|
||||
$colorAlert: #ff8a0d;
|
||||
$colorAlertFg: #fff;
|
||||
$colorWarningHi: #ff0000;
|
||||
$colorWarningHiFg: #ffdad0;
|
||||
$colorError: #ff3c00;
|
||||
$colorErrorFg: #fff;
|
||||
$colorWarningHi: #990000;
|
||||
$colorWarningHiFg: #FF9594;
|
||||
$colorWarningLo: #ff9900;
|
||||
$colorWarningLoFg: #523400;
|
||||
$colorDiagnostic: #a4b442;
|
||||
@ -130,7 +132,6 @@ $colorFilter: $colorFilterFg; // Standalone against $colorBodyBg
|
||||
// States
|
||||
$colorPausedBg: #ff9900;
|
||||
$colorPausedFg: #333;
|
||||
$colorOk: #33cc33;
|
||||
|
||||
// Base variations
|
||||
$colorBodyBgSubtle: pullForward($colorBodyBg, 5%);
|
||||
@ -225,6 +226,7 @@ $colorSelectBg: $colorBtnBg; // This must be a solid color, not a gradient, due
|
||||
$colorSelectFg: $colorBtnFg;
|
||||
$colorSelectArw: lighten($colorBtnBg, 20%);
|
||||
$shdwSelect: rgba(black, 0.5) 0 0.5px 3px;
|
||||
$controlDisabledOpacity: 0.2;
|
||||
|
||||
// Menus
|
||||
$colorMenuBg: pullForward($colorBodyBg, 15%);
|
||||
@ -330,7 +332,7 @@ $shdwItemText: none;
|
||||
$colorTabBorder: pullForward($colorBodyBg, 10%);
|
||||
$colorTabBodyBg: $colorBodyBg;
|
||||
$colorTabBodyFg: pullForward($colorBodyFg, 20%);
|
||||
$colorTabHeaderBg: pullForward($colorBodyBg, 10%);
|
||||
$colorTabHeaderBg: rgba($colorBodyFg, 0.2);
|
||||
$colorTabHeaderFg: $colorBodyFg;
|
||||
$colorTabHeaderBorder: $colorBodyBg;
|
||||
$colorTabGroupHeaderBg: pullForward($colorBodyBg, 5%);
|
||||
@ -420,14 +422,16 @@ $transInBounceBig: all 300ms cubic-bezier(.2,1.6,.6,3);
|
||||
|
||||
// Discrete items, like Notebook entries, Widget rules
|
||||
$createBtnTextTransform: uppercase;
|
||||
$colorDiscreteItemBg: rgba($colorBodyFg,0.1);
|
||||
$colorDiscreteItemCurrentBg: rgba($colorOk,0.3);
|
||||
|
||||
@mixin discreteItem() {
|
||||
background: rgba($colorBodyFg,0.1);
|
||||
border: none;
|
||||
border-radius: $controlCr;
|
||||
|
||||
.c-input-inline:hover {
|
||||
background: $colorBodyBg;
|
||||
&--current-match {
|
||||
background: $colorDiscreteItemCurrentBg;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -105,8 +105,10 @@ $colorStatusErrorFilter: invert(8%) sepia(96%) saturate(4511%) hue-rotate(352deg
|
||||
$colorStatusBtnBg: #666; // Where is this used?
|
||||
$colorStatusPartialBg: #c9d6ff;
|
||||
$colorStatusCompleteBg: #a4e4b4;
|
||||
$colorAlert: #ff3c00;
|
||||
$colorAlert: #ff8a0d;
|
||||
$colorAlertFg: #fff;
|
||||
$colorError: #ff3c00;
|
||||
$colorErrorFg: #fff;
|
||||
$colorWarningHi: #990000;
|
||||
$colorWarningHiFg: #FF9594;
|
||||
$colorWarningLo: #ff9900;
|
||||
@ -126,7 +128,6 @@ $colorFilter: $colorFilterBg; // Standalone against $colorBodyBg
|
||||
// States
|
||||
$colorPausedBg: #ff9900;
|
||||
$colorPausedFg: #fff;
|
||||
$colorOk: #33cc33;
|
||||
|
||||
// Base variations
|
||||
$colorBodyBgSubtle: pullForward($colorBodyBg, 5%);
|
||||
@ -221,6 +222,7 @@ $colorSelectBg: $colorBtnBg; // This must be a solid color, not a gradient, due
|
||||
$colorSelectFg: $colorBtnFg;
|
||||
$colorSelectArw: lighten($colorBtnBg, 20%);
|
||||
$shdwSelect: none;
|
||||
$controlDisabledOpacity: 0.3;
|
||||
|
||||
// Menus
|
||||
$colorMenuBg: pushBack($colorBodyBg, 10%);
|
||||
@ -326,8 +328,8 @@ $shdwItemText: none;
|
||||
$colorTabBorder: pullForward($colorBodyBg, 10%);
|
||||
$colorTabBodyBg: $colorBodyBg;
|
||||
$colorTabBodyFg: pullForward($colorBodyFg, 20%);
|
||||
$colorTabHeaderBg: pullForward($colorBodyBg, 10%);
|
||||
$colorTabHeaderFg: pullForward($colorBodyFg, 20%);
|
||||
$colorTabHeaderBg: rgba($colorBodyFg, 0.2);
|
||||
$colorTabHeaderFg: $colorBodyFg;
|
||||
$colorTabHeaderBorder: $colorBodyBg;
|
||||
$colorTabGroupHeaderBg: pullForward($colorBodyBg, 5%);
|
||||
$colorTabGroupHeaderFg: pullForward($colorTabGroupHeaderBg, 40%);
|
||||
@ -416,12 +418,18 @@ $transInBounceBig: all 300ms cubic-bezier(.2,1.6,.6,3);
|
||||
|
||||
// Discrete items, like Notebook entries, Widget rules
|
||||
$createBtnTextTransform: uppercase;
|
||||
$colorDiscreteItemBg: rgba($colorBodyFg,0.1);
|
||||
$colorDiscreteItemCurrentBg: rgba($colorOk,0.3);
|
||||
|
||||
@mixin discreteItem() {
|
||||
background: rgba($colorBodyFg,0.1);
|
||||
background: $colorDiscreteItemBg;
|
||||
border: 1px solid $colorInteriorBorder;
|
||||
border-radius: $controlCr;
|
||||
|
||||
&--current-match {
|
||||
background: $colorDiscreteItemCurrentBg;
|
||||
}
|
||||
|
||||
.c-input-inline:hover {
|
||||
background: $colorBodyBg;
|
||||
}
|
||||
|
8
src/styles/_constants.scss
Normal file → Executable file
8
src/styles/_constants.scss
Normal file → Executable file
@ -44,6 +44,7 @@ $overlayOuterMarginLg: 5%;
|
||||
$overlayOuterMarginFullscreen: 0%;
|
||||
$overlayOuterMarginDialog: 20%;
|
||||
$overlayInnerMargin: 25px;
|
||||
$mainViewPad: 2px;
|
||||
/*************** Items */
|
||||
$itemPadLR: 5px;
|
||||
$gridItemDesk: 175px;
|
||||
@ -94,9 +95,6 @@ $tableResizeColHitareaD: 6px;
|
||||
$mobileMenuIconD: 24px; // Used
|
||||
$mobileTreeItemH: 35px; // Used
|
||||
|
||||
/************************** VISUAL */
|
||||
$controlDisabledOpacity: 0.5;
|
||||
|
||||
/************************** UI ELEMENTS */
|
||||
/*************** Progress Bar */
|
||||
$colorProgressBarHolder: rgba(black, 0.1);
|
||||
@ -240,6 +238,7 @@ $glyph-icon-spectra: '\eb24';
|
||||
$glyph-icon-spectra-telemetry: '\eb25';
|
||||
$glyph-icon-command: '\eb26';
|
||||
$glyph-icon-conditional: '\eb27';
|
||||
$glyph-icon-condition-widget: '\eb28';
|
||||
|
||||
/************************** GLYPHS AS DATA URI */
|
||||
// Only objects have been converted, for use in Create menu and folder views
|
||||
@ -289,4 +288,5 @@ $bg-icon-gauge: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w
|
||||
$bg-icon-spectra: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M384 352H128l51.2-89.6L0 288v127c0 53.3 43.7 97 97 97h318c53.4 0 97-43.7 97-97v-31l-162.9-93.1zM415 0H97C43.7 0 0 43.6 0 97v159l200-30.1 56-97.9 54.9 96H512V97a97.2 97.2 0 00-97-97zM512 320v-32l-192-32 192 64z'/%3e%3c/svg%3e");
|
||||
$bg-icon-spectra-telemetry: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M256 128l54.9 96H510C494.3 97.7 386.5 0 256 0 114.6 0 0 114.6 0 256l200-30.1zM384 352H128l51.2-89.6L2 287.7C17.6 414.1 125.4 512 256 512c100.8 0 188-58.3 229.8-143l-136.7-78.1zM320 256l192 64v-32l-192-32z'/%3e%3c/svg%3e");
|
||||
$bg-icon-command: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M185.1 229.7a96.5 96.5 0 0015.8 11.7A68.5 68.5 0 01192 208c0-19.8 8.9-38.8 25.1-53.7 18.5-17 43.7-26.3 70.9-26.3 20.1 0 39.1 5.1 55.1 14.6a81.3 81.3 0 00-16.2-20.3C308.4 105.3 283.2 96 256 96s-52.4 9.3-70.9 26.3C168.9 137.2 160 156.2 160 176s8.9 38.8 25.1 53.7z'/%3e%3cpath d='M442.7 134.8C422.4 57.5 346.5 0 256 0S89.6 57.5 69.3 134.8C26.3 174.8 0 228.7 0 288c0 123.7 114.6 224 256 224s256-100.3 256-224c0-59.3-26.3-113.2-69.3-153.2zM256 64c70.6 0 128 50.2 128 112s-57.4 112-128 112-128-50.2-128-112S185.4 64 256 64zm0 352c-87.7 0-159.2-63.9-160-142.7 34.4 47.4 93.2 78.7 160 78.7s125.6-31.3 160-78.7c-.8 78.8-72.3 142.7-160 142.7z'/%3e%3c/svg%3e");
|
||||
$bg-icon-conditional: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath d='M256 0C114.62 0 0 114.62 0 256s114.62 256 256 256 256-114.62 256-256S397.38 0 256 0zm0 384L64 256l192-128 192 128z' fill='%23000000'/%3e%3c/svg%3e");
|
||||
$bg-icon-conditional: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M256 0C114.62 0 0 114.62 0 256s114.62 256 256 256 256-114.62 256-256S397.38 0 256 0zm0 384L64 256l192-128 192 128z'/%3e%3c/svg%3e");
|
||||
$bg-icon-condition-widget: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3e%3cpath fill='%23000000' d='M416 0H96C43.2 0 0 43.2 0 96v320c0 52.8 43.2 96 96 96h320c52.8 0 96-43.2 96-96V96c0-52.8-43.2-96-96-96zM256 384L64 256l192-128 192 128z'/%3e%3c/svg%3e");
|
||||
|
@ -65,6 +65,11 @@ button {
|
||||
@include desktop() {
|
||||
font-size: 6px;
|
||||
}
|
||||
|
||||
&:before {
|
||||
content: $glyph-icon-arrow-down;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
@ -81,6 +86,20 @@ button {
|
||||
/********* Icon Buttons and Links */
|
||||
.c-click-icon {
|
||||
@include cClickIcon();
|
||||
|
||||
&--section-collapse {
|
||||
color: inherit;
|
||||
display: block;
|
||||
transition: transform $transOutTime;
|
||||
&:before {
|
||||
content: $glyph-icon-arrow-down;
|
||||
font-family: symbolsfont;
|
||||
}
|
||||
|
||||
&.is-collapsed {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.c-click-link {
|
||||
@ -212,6 +231,49 @@ button {
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************** DRAG AFFORDANCES */
|
||||
.c-grippy {
|
||||
$d: 10px;
|
||||
@include grippy($c: $colorItemTreeVC, $dir: 'y');
|
||||
width: $d; height: $d;
|
||||
|
||||
&--vertical-drag {
|
||||
cursor: ns-resize;
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************** SECTION */
|
||||
section {
|
||||
flex: 0 0 auto;
|
||||
overflow: hidden;
|
||||
+ section {
|
||||
margin-top: $interiorMargin;
|
||||
|
||||
&.is-expanded {
|
||||
margin-bottom: $interiorMargin * 3;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-expanded {
|
||||
flex: 0 1 auto;
|
||||
}
|
||||
|
||||
.c-section__header {
|
||||
@include propertiesHeader();
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
align-items: center;
|
||||
margin-bottom: $interiorMargin;
|
||||
|
||||
> * + * { margin-left: $interiorMarginSm; }
|
||||
}
|
||||
|
||||
> [class*='__label'] {
|
||||
flex: 1 1 auto;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************** FORM ELEMENTS */
|
||||
input, textarea {
|
||||
font-family: inherit;
|
||||
@ -324,7 +386,7 @@ select {
|
||||
background-position: right .4em top 80%, 0 0;
|
||||
border: none;
|
||||
border-radius: $controlCr;
|
||||
padding: 1px 20px 1px $interiorMargin;
|
||||
padding: 2px 20px 2px $interiorMargin;
|
||||
|
||||
*,
|
||||
option {
|
||||
@ -645,6 +707,11 @@ select {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.c-row-separator {
|
||||
border-top: 1px solid $colorInteriorBorder;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
.c-toolbar {
|
||||
> * + * {
|
||||
margin-left: 2px;
|
||||
@ -746,7 +813,25 @@ select {
|
||||
}
|
||||
}
|
||||
|
||||
/***************************************************** SLIDERS */
|
||||
/******************************************************** STYLE EDITING */
|
||||
.c-style {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
align-items: center;
|
||||
|
||||
> * + * { margin-left: $interiorMargin; }
|
||||
}
|
||||
|
||||
.c-style-thumb {
|
||||
background-size: cover;
|
||||
border: 1px solid transparent;
|
||||
border-radius: $basicCr;
|
||||
box-shadow: rgba($colorBodyFg, 0.4) 0 0 3px;
|
||||
flex: 0 0 auto;
|
||||
padding: $interiorMargin $interiorMarginLg;
|
||||
}
|
||||
|
||||
/******************************************************** SLIDERS */
|
||||
.c-slider {
|
||||
@include cControl();
|
||||
> * + * { margin-left: $interiorMargin; }
|
||||
|
@ -35,6 +35,22 @@ div {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
.u-menu-to {
|
||||
&--left {
|
||||
.c-menu {
|
||||
left: auto !important;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&--center {
|
||||
.c-menu {
|
||||
left: 50% !important;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.u-space {
|
||||
// Provides a separator space between elements
|
||||
&--right {
|
||||
|
2
src/styles/_glyphs.scss
Normal file → Executable file
2
src/styles/_glyphs.scss
Normal file → Executable file
@ -174,6 +174,7 @@
|
||||
.icon-spectra-telemetry { @include glyphBefore($glyph-icon-spectra-telemetry); }
|
||||
.icon-command { @include glyphBefore($glyph-icon-command); }
|
||||
.icon-conditional { @include glyphBefore($glyph-icon-conditional); }
|
||||
.icon-condition-widget { @include glyphBefore($glyph-icon-condition-widget); }
|
||||
|
||||
/************************** 12 PX CLASSES */
|
||||
// TODO: sync with 16px redo as of 10/25/18
|
||||
@ -232,3 +233,4 @@
|
||||
.bg-icon-spectra-telemetry { @include glyphBg($bg-icon-spectra-telemetry); }
|
||||
.bg-icon-command { @include glyphBg($bg-icon-command); }
|
||||
.bg-icon-conditional { @include glyphBg($bg-icon-conditional); }
|
||||
.bg-icon-condition-widget { @include glyphBg($bg-icon-condition-widget); }
|
||||
|
@ -67,15 +67,12 @@
|
||||
}
|
||||
|
||||
&__object-name--w {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex: 0 1 auto;
|
||||
@include headerFont(1.4em);
|
||||
min-width: 0;
|
||||
|
||||
&:before {
|
||||
// Icon
|
||||
opacity: 0.5;
|
||||
margin-right: $interiorMargin;
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,27 @@ mct-plot {
|
||||
}
|
||||
|
||||
/*********************** STACKED PLOT LAYOUT */
|
||||
.t-plot-stacked {
|
||||
.is-editing {
|
||||
.gl-plot.child-frame {
|
||||
&:hover {
|
||||
background: rgba($editUIColorBg, 0.1);
|
||||
box-shadow: inset rgba($editUIColorBg, 0.8) 0 0 0 1px;
|
||||
}
|
||||
|
||||
&[s-selected] {
|
||||
border: 1px solid $editUIColorFg !important;
|
||||
color: $editUIColorFg !important;
|
||||
box-shadow: $editFrameSelectedShdw;
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
.c-plot {
|
||||
$p: $mainViewPad;
|
||||
position: absolute;
|
||||
top: $p; right: $p; bottom: $p; left: $p;
|
||||
|
||||
&--stacked {
|
||||
.l-view-section {
|
||||
// Make this a flex container
|
||||
display: flex;
|
||||
@ -51,26 +71,11 @@ mct-plot {
|
||||
}
|
||||
}
|
||||
|
||||
.is-editing {
|
||||
.gl-plot.child-frame {
|
||||
&:hover {
|
||||
background: rgba($editUIColorBg, 0.1);
|
||||
box-shadow: inset rgba($editUIColorBg, 0.8) 0 0 0 1px;
|
||||
}
|
||||
|
||||
&[s-selected] {
|
||||
border: 1px solid $editUIColorFg !important;
|
||||
color: $editUIColorFg !important;
|
||||
box-shadow: $editFrameSelectedShdw;
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.gl-plot {
|
||||
color: $colorPlotFg;
|
||||
display: flex;
|
||||
//font-size: 0.7rem;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@ -146,10 +151,10 @@ mct-plot {
|
||||
}
|
||||
|
||||
.gl-plot-coords {
|
||||
// This does not appear to be in use in Open MCT
|
||||
box-sizing: border-box;
|
||||
border-radius: $controlCr;
|
||||
background: black;
|
||||
color: lighten($colorBodyFg, 30%);
|
||||
padding: 2px 5px;
|
||||
position: absolute;
|
||||
top: nth($plotDisplayArea,1) + $interiorMarginLg;
|
||||
@ -164,7 +169,6 @@ mct-plot {
|
||||
|
||||
.gl-plot-label,
|
||||
.l-plot-label {
|
||||
color: $colorPlotLabelFg;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
|
||||
|
@ -32,6 +32,10 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
> * + * {
|
||||
margin-left: $interiorMarginSm;
|
||||
}
|
||||
|
||||
&__label {
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
|
@ -139,6 +139,24 @@
|
||||
background-size: $bgSize;
|
||||
}
|
||||
|
||||
@mixin bgCheckerboard($c: $colorBodyFg, $opacity: 0.3, $size: 32px, $imp: false) {
|
||||
$color: rgba($c, $opacity);
|
||||
$bgPos: floor($size / 2);
|
||||
|
||||
$impStr: null;
|
||||
@if $imp {
|
||||
$impStr: !important;
|
||||
}
|
||||
|
||||
background-image:
|
||||
linear-gradient(45deg, $color 25%, transparent 25%),
|
||||
linear-gradient(45deg, transparent 75%, $color 75%),
|
||||
linear-gradient(45deg, transparent 75%, $color 75%),
|
||||
linear-gradient(45deg, $color 25%, transparent 25%) $impStr;
|
||||
background-size: $size $size;
|
||||
background-position:0 0, 0 0, -1*$bgPos -1*$bgPos, $bgPos $bgPos;
|
||||
}
|
||||
|
||||
@mixin disabled() {
|
||||
opacity: $controlDisabledOpacity;
|
||||
pointer-events: none !important;
|
||||
@ -415,9 +433,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@mixin cClickIcon() {
|
||||
@include cControl();
|
||||
color: $colorBodyFg;
|
||||
cursor: pointer;
|
||||
padding: 4px; // Bigger hit area
|
||||
opacity: 0.7;
|
||||
|
@ -40,7 +40,7 @@
|
||||
display: inline-block;
|
||||
font-family: symbolsfont;
|
||||
font-size: 0.8em;
|
||||
margin-right: $interiorMargin;
|
||||
margin-right: $interiorMarginSm;
|
||||
@if $glyph != null {
|
||||
content: $glyph $impStr;
|
||||
}
|
||||
@ -71,6 +71,30 @@
|
||||
&.is-limit--lwr:before { content: $glyph-icon-arrow-down !important; }
|
||||
}
|
||||
|
||||
@mixin uIndicator($bg, $fg, $glyph) {
|
||||
background: $bg;
|
||||
color: $fg;
|
||||
|
||||
&[class*='--with-icon'] {
|
||||
&:before {
|
||||
color: $fg;
|
||||
display: inline-block;
|
||||
font-family: symbolsfont;
|
||||
margin-right: $interiorMarginSm;
|
||||
@if $glyph != null {
|
||||
content: $glyph;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&[class*='--block'] {
|
||||
border-radius: $controlCr;
|
||||
display: inline-block;
|
||||
padding: 2px $interiorMargin;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*************************************************** STYLES */
|
||||
*:not(tr) {
|
||||
&.is-limit--yellow {
|
||||
@ -108,8 +132,9 @@ tr {
|
||||
}
|
||||
|
||||
/*************************************************** STATUS */
|
||||
[class*='s-status'] {
|
||||
[class*='s-status-icon'] {
|
||||
&:before {
|
||||
font-family: symbolsfont;
|
||||
margin-right: $interiorMargin;
|
||||
}
|
||||
}
|
||||
@ -172,3 +197,5 @@ tr {
|
||||
}
|
||||
}
|
||||
|
||||
.u-alert { @include uIndicator($colorAlert, $colorAlertFg, $glyph-icon-alert-triangle); }
|
||||
.u-error { @include uIndicator($colorError, $colorErrorFg, $glyph-icon-alert-triangle); }
|
||||
|
@ -78,10 +78,14 @@ div.c-table {
|
||||
|
||||
.c-table-wrapper {
|
||||
// Wraps .c-control-bar and .c-table
|
||||
@include abs();
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
|
||||
// Using absolute here because sizing table can't calc width properly if padding is used
|
||||
$p: $mainViewPad;
|
||||
position: absolute;
|
||||
top: $p; right: $p; bottom: $p; left: $p;
|
||||
|
||||
> .c-table {
|
||||
height: auto;
|
||||
@ -158,6 +162,12 @@ div.c-table {
|
||||
}
|
||||
}
|
||||
|
||||
.c-lad-table-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: $mainViewPad;
|
||||
}
|
||||
|
||||
.c-lad-table {
|
||||
th, td {
|
||||
width: 33%; // Needed to prevent size jumping as values dynamically update
|
||||
|
0
src/styles/fonts/Open MCT Symbols 12px.json
Normal file → Executable file
0
src/styles/fonts/Open MCT Symbols 12px.json
Normal file → Executable file
671
src/styles/fonts/Open MCT Symbols 16px.json
Normal file → Executable file
671
src/styles/fonts/Open MCT Symbols 16px.json
Normal file → Executable file
File diff suppressed because it is too large
Load Diff
0
src/styles/fonts/Open-MCT-Symbols-12px.ttf
Normal file → Executable file
0
src/styles/fonts/Open-MCT-Symbols-12px.ttf
Normal file → Executable file
0
src/styles/fonts/Open-MCT-Symbols-12px.woff
Normal file → Executable file
0
src/styles/fonts/Open-MCT-Symbols-12px.woff
Normal file → Executable file
@ -142,4 +142,5 @@
|
||||
<glyph unicode="" glyph-name="icon-telemetry-spectra" d="M512 576l109.8-192h398.2c-31.4 252.6-247 448-508 448-282.8 0-512-229.2-512-512l400 60.2zM768 128h-512l102.4 179.2-354.4-50.6c31.2-252.8 246.8-448.6 508-448.6 201.6 0 376 116.6 459.6 286l-273.4 156.2zM640 320l384-128v64l-384 64z" />
|
||||
<glyph unicode="" glyph-name="icon-pushbutton" d="M370.2 372.6c9.326-8.53 19.666-16.261 30.729-22.914l0.871-0.486c-11.077 19.209-17.664 42.221-17.8 66.76v0.040c0 39.6 17.8 77.6 50.2 107.4 37 34 87.4 52.6 141.8 52.6 40.2 0 78.2-10.2 110.2-29.2-8.918 15.653-19.693 29.040-32.268 40.482l-0.132 0.118c-37 34-87.4 52.6-141.8 52.6s-104.8-18.6-141.8-52.6c-32.4-29.8-50.2-67.8-50.2-107.4s17.8-77.6 50.2-107.4zM885.4 562.4c-40.6 154.6-192.4 269.6-373.4 269.6s-332.8-115-373.4-269.6c-86-80-138.6-187.8-138.6-306.4 0-247.4 229.2-448 512-448s512 200.6 512 448c0 118.6-52.6 226.4-138.6 306.4zM512 704c141.2 0 256-100.4 256-224s-114.8-224-256-224-256 100.4-256 224 114.8 224 256 224zM512 0c-175.4 0-318.4 127.8-320 285.4 68.8-94.8 186.4-157.4 320-157.4s251.2 62.6 320 157.4c-1.6-157.6-144.6-285.4-320-285.4z" />
|
||||
<glyph unicode="" glyph-name="icon-conditional" d="M512 832c-282.76 0-512-229.24-512-512s229.24-512 512-512 512 229.24 512 512-229.24 512-512 512zM512 64l-384 256 384 256 384-256z" />
|
||||
<glyph unicode="" glyph-name="icon-condition-widget" d="M832 832h-640c-105.6 0-192-86.4-192-192v-640c0-105.6 86.4-192 192-192h640c105.6 0 192 86.4 192 192v640c0 105.6-86.4 192-192 192zM512 64l-384 256 384 256 384-256z" />
|
||||
</font></defs></svg>
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
Binary file not shown.
Binary file not shown.
@ -1,5 +1,9 @@
|
||||
@import "../api/overlays/components/dialog-component.scss";
|
||||
@import "../api/overlays/components/overlay-component.scss";
|
||||
@import "../plugins/condition/components/condition.scss";
|
||||
@import "../plugins/condition/components/condition-set.scss";
|
||||
@import "../plugins/conditionWidget/components/condition-widget.scss";
|
||||
@import "../plugins/condition/components/inspector/conditional-styles.scss";
|
||||
@import "../plugins/displayLayout/components/box-view.scss";
|
||||
@import "../plugins/displayLayout/components/display-layout.scss";
|
||||
@import "../plugins/displayLayout/components/edit-marquee.scss";
|
||||
|
@ -64,7 +64,8 @@ const SIMPLE_CONTENT_TYPES = [
|
||||
'clock',
|
||||
'timer',
|
||||
'summary-widget',
|
||||
'hyperlink'
|
||||
'hyperlink',
|
||||
'conditionWidget'
|
||||
];
|
||||
|
||||
export default {
|
||||
|
@ -4,6 +4,8 @@
|
||||
|
||||
<script>
|
||||
import _ from "lodash"
|
||||
import StyleRuleManager from "@/plugins/condition/StyleRuleManager";
|
||||
import {STYLE_CONSTANTS} from "@/plugins/condition/utils/constants";
|
||||
|
||||
export default {
|
||||
inject: ["openmct"],
|
||||
@ -31,6 +33,19 @@ export default {
|
||||
if (this.releaseEditModeHandler) {
|
||||
this.releaseEditModeHandler();
|
||||
}
|
||||
|
||||
if (this.unlisten) {
|
||||
this.unlisten();
|
||||
}
|
||||
|
||||
if (this.stopListeningStyles) {
|
||||
this.stopListeningStyles();
|
||||
}
|
||||
|
||||
if (this.styleRuleManager) {
|
||||
this.styleRuleManager.destroy();
|
||||
delete this.styleRuleManager;
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.debounceUpdateView = _.debounce(this.updateView, 10);
|
||||
@ -43,6 +58,11 @@ export default {
|
||||
capture: true
|
||||
});
|
||||
this.$el.addEventListener('drop', this.addObjectToParent);
|
||||
if (this.currentObject) {
|
||||
//This is to apply styles to subobjects in a layout
|
||||
this.initObjectStyles();
|
||||
}
|
||||
|
||||
},
|
||||
methods: {
|
||||
clear() {
|
||||
@ -76,6 +96,29 @@ export default {
|
||||
this.clear();
|
||||
this.updateView(true);
|
||||
},
|
||||
updateStyle(styleObj) {
|
||||
if (!styleObj) {
|
||||
return;
|
||||
}
|
||||
let keys = Object.keys(styleObj);
|
||||
keys.forEach(key => {
|
||||
let firstChild = this.$el.querySelector(':first-child');
|
||||
if (firstChild) {
|
||||
if ((typeof styleObj[key] === 'string') && (styleObj[key].indexOf('transparent') > -1)) {
|
||||
if (firstChild.style[key]) {
|
||||
firstChild.style[key] = '';
|
||||
}
|
||||
} else {
|
||||
if (!styleObj.isStyleInvisible && firstChild.classList.contains(STYLE_CONSTANTS.isStyleInvisible)) {
|
||||
firstChild.classList.remove(STYLE_CONSTANTS.isStyleInvisible);
|
||||
} else if (styleObj.isStyleInvisible && !firstChild.classList.contains(styleObj.isStyleInvisible)) {
|
||||
firstChild.classList.add(styleObj.isStyleInvisible);
|
||||
}
|
||||
firstChild.style[key] = styleObj[key];
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
updateView(immediatelySelect) {
|
||||
this.clear();
|
||||
if (!this.currentObject) {
|
||||
@ -89,7 +132,7 @@ export default {
|
||||
}
|
||||
|
||||
this.viewContainer = document.createElement('div');
|
||||
this.viewContainer.classList.add('c-object-view','u-contents');
|
||||
this.viewContainer.classList.add('u-angular-object-view-wrapper');
|
||||
this.$el.append(this.viewContainer);
|
||||
let provider = this.getViewProvider();
|
||||
if (!provider) {
|
||||
@ -125,6 +168,8 @@ export default {
|
||||
this.openmct.objectViews.on('clearData', this.clearData);
|
||||
},
|
||||
show(object, viewKey, immediatelySelect, currentObjectPath) {
|
||||
this.updateStyle();
|
||||
|
||||
if (this.unlisten) {
|
||||
this.unlisten();
|
||||
}
|
||||
@ -149,7 +194,26 @@ export default {
|
||||
});
|
||||
|
||||
this.viewKey = viewKey;
|
||||
|
||||
this.updateView(immediatelySelect);
|
||||
|
||||
this.initObjectStyles();
|
||||
},
|
||||
initObjectStyles() {
|
||||
if (!this.styleRuleManager) {
|
||||
this.styleRuleManager = new StyleRuleManager((this.currentObject.configuration && this.currentObject.configuration.objectStyles), this.openmct, this.updateStyle.bind(this));
|
||||
} else {
|
||||
this.styleRuleManager.updateObjectStyleConfig(this.currentObject.configuration && this.currentObject.configuration.objectStyles);
|
||||
}
|
||||
|
||||
if (this.stopListeningStyles) {
|
||||
this.stopListeningStyles();
|
||||
}
|
||||
|
||||
this.stopListeningStyles = this.openmct.objects.observe(this.currentObject, 'configuration.objectStyles', (newObjectStyle) => {
|
||||
//Updating styles in the inspector view will trigger this so that the changes are reflected immediately
|
||||
this.styleRuleManager.updateObjectStyleConfig(newObjectStyle);
|
||||
});
|
||||
},
|
||||
loadComposition() {
|
||||
return this.composition.load();
|
||||
|
@ -58,7 +58,7 @@
|
||||
height: 0; // Chrome 73 overflow bug fix
|
||||
overflow: auto;
|
||||
|
||||
.c-object-view {
|
||||
.u-angular-object-view-wrapper {
|
||||
.u-fills-container {
|
||||
// Expand component types that fill a container
|
||||
@include abs();
|
||||
@ -69,7 +69,14 @@
|
||||
.c-click-icon,
|
||||
.c-button {
|
||||
// Shrink buttons a bit when they appear in a frame
|
||||
font-size: 0.85em;
|
||||
font-size: 0.9em;
|
||||
padding: 3px 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.u-angular-object-view-wrapper {
|
||||
flex: 1 1 auto;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
$d: 12px;
|
||||
$m: 2px;
|
||||
$br: $d/1.5;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
vertical-align: middle;
|
||||
@ -39,7 +40,6 @@
|
||||
display: inline-block;
|
||||
height: $d + ($m*2);
|
||||
position: relative;
|
||||
transform: translateY(2px); // TODO: get this to work without this kind of hack!
|
||||
width: $d*2 + $m*2;
|
||||
|
||||
&:before {
|
||||
|
@ -5,7 +5,7 @@
|
||||
'c-disclosure-triangle--expanded' : value,
|
||||
'is-enabled' : enabled
|
||||
}"
|
||||
@click="$emit('input', !value)"
|
||||
@click="handleClick"
|
||||
></span>
|
||||
</template>
|
||||
|
||||
@ -21,6 +21,18 @@ export default {
|
||||
// Used as such in the tree - when a node doesn't have children, set disabled to true.
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
propagate: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClick(event) {
|
||||
this.$emit('input', !this.value);
|
||||
if (!this.propagate) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,7 @@
|
||||
>
|
||||
<span
|
||||
v-if="elements.length > 1 && isEditing"
|
||||
class="c-elements-pool__grippy"
|
||||
class="c-elements-pool__grippy c-grippy c-grippy--vertical-drag"
|
||||
></span>
|
||||
<object-label
|
||||
:domain-object="element"
|
||||
|
@ -1,6 +1,21 @@
|
||||
<template>
|
||||
<multipane
|
||||
class="c-inspector"
|
||||
<div class="c-inspector">
|
||||
<object-name />
|
||||
<div v-if="showStyles"
|
||||
class="c-inspector__tabs c-tabs"
|
||||
>
|
||||
<div v-for="tabbedView in tabbedViews"
|
||||
:key="tabbedView.key"
|
||||
class="c-inspector__tab c-tab"
|
||||
:class="{'is-current': isCurrent(tabbedView)}"
|
||||
@click="updateCurrentTab(tabbedView)"
|
||||
>
|
||||
{{ tabbedView.name }}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="c-inspector__content">
|
||||
<multipane v-if="currentTabbedView.key === '__properties'"
|
||||
type="vertical"
|
||||
>
|
||||
<pane class="c-inspector__properties">
|
||||
@ -17,6 +32,11 @@
|
||||
<elements />
|
||||
</pane>
|
||||
</multipane>
|
||||
<template v-else>
|
||||
<styles-inspector-view />
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@ -25,15 +45,21 @@ import pane from '../layout/pane.vue';
|
||||
import Elements from './Elements.vue';
|
||||
import Location from './Location.vue';
|
||||
import Properties from './Properties.vue';
|
||||
import ObjectName from './ObjectName.vue';
|
||||
import InspectorViews from './InspectorViews.vue';
|
||||
import _ from "lodash";
|
||||
import StylesInspectorView from "./StylesInspectorView.vue";
|
||||
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
components: {
|
||||
StylesInspectorView,
|
||||
// StylesInspectorView,
|
||||
multipane,
|
||||
pane,
|
||||
Elements,
|
||||
Properties,
|
||||
ObjectName,
|
||||
Location,
|
||||
InspectorViews
|
||||
},
|
||||
@ -42,23 +68,59 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
hasComposition: false
|
||||
hasComposition: false,
|
||||
showStyles: false,
|
||||
tabbedViews: [{
|
||||
key: '__properties',
|
||||
name: 'Properties'
|
||||
},{
|
||||
key: '__styles',
|
||||
name: 'Styles'
|
||||
}],
|
||||
currentTabbedView: {}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.openmct.selection.on('change', this.refreshComposition);
|
||||
this.refreshComposition(this.openmct.selection.get());
|
||||
this.excludeObjectTypes = ['folder', 'webPage', 'conditionSet'];
|
||||
this.openmct.selection.on('change', this.updateInspectorViews);
|
||||
},
|
||||
destroyed() {
|
||||
this.openmct.selection.off('change', this.refreshComposition);
|
||||
this.openmct.selection.off('change', this.updateInspectorViews);
|
||||
},
|
||||
methods: {
|
||||
updateInspectorViews(selection) {
|
||||
this.refreshComposition(selection);
|
||||
if (this.openmct.types.get('conditionSet')) {
|
||||
this.refreshTabs(selection);
|
||||
}
|
||||
},
|
||||
refreshComposition(selection) {
|
||||
if (selection.length > 0 && selection[0].length > 0) {
|
||||
let parentObject = selection[0][0].context.item;
|
||||
|
||||
this.hasComposition = !!(parentObject && this.openmct.composition.get(parentObject));
|
||||
}
|
||||
},
|
||||
refreshTabs(selection) {
|
||||
if (selection.length > 0 && selection[0].length > 0) {
|
||||
//layout items are not domain objects but should allow conditional styles
|
||||
this.showStyles = selection[0][0].context.layoutItem;
|
||||
let object = selection[0][0].context.item;
|
||||
if (object) {
|
||||
let type = this.openmct.types.get(object.type);
|
||||
this.showStyles = (this.excludeObjectTypes.indexOf(object.type) < 0) && type.definition.creatable;
|
||||
}
|
||||
if (!this.currentTabbedView.key || (!this.showStyles && this.currentTabbedView.key === this.tabbedViews[1].key))
|
||||
{
|
||||
this.updateCurrentTab(this.tabbedViews[0]);
|
||||
}
|
||||
}
|
||||
},
|
||||
updateCurrentTab(view) {
|
||||
this.currentTabbedView = view;
|
||||
},
|
||||
isCurrent(view) {
|
||||
return _.isEqual(this.currentTabbedView, view)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -30,11 +30,10 @@ export default {
|
||||
});
|
||||
this.$el.innerHTML = '';
|
||||
}
|
||||
|
||||
this.selectedViews = this.openmct.inspectorViews.get(selection);
|
||||
this.selectedViews.forEach(selectedView => {
|
||||
let viewContainer = document.createElement('div');
|
||||
this.$el.append(viewContainer)
|
||||
this.$el.append(viewContainer);
|
||||
selectedView.show(viewContainer);
|
||||
});
|
||||
}
|
||||
|
@ -1,20 +1,20 @@
|
||||
<template>
|
||||
<div class="c-properties c-properties--location">
|
||||
<div class="c-inspect-properties c-inspect-properties--location">
|
||||
<div
|
||||
class="c-properties__header"
|
||||
class="c-inspect-properties__header"
|
||||
title="The location of this linked object."
|
||||
>
|
||||
Original Location
|
||||
</div>
|
||||
<ul
|
||||
v-if="!multiSelect"
|
||||
class="c-properties__section"
|
||||
class="c-inspect-properties__section"
|
||||
>
|
||||
<li
|
||||
v-if="originalPath.length"
|
||||
class="c-properties__row"
|
||||
class="c-inspect-properties__row"
|
||||
>
|
||||
<ul class="c-properties__value c-location">
|
||||
<ul class="c-inspect-properties__value c-location">
|
||||
<li
|
||||
v-for="pathObject in orderedOriginalPath"
|
||||
:key="pathObject.key"
|
||||
@ -30,7 +30,7 @@
|
||||
</ul>
|
||||
<div
|
||||
v-if="multiSelect"
|
||||
class="c-properties__row--span-all"
|
||||
class="c-inspect-properties__row--span-all"
|
||||
>
|
||||
No location to display for multiple items
|
||||
</div>
|
||||
|
76
src/ui/inspector/ObjectName.vue
Normal file
76
src/ui/inspector/ObjectName.vue
Normal file
@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<div class="c-inspector__header">
|
||||
<div v-if="!multiSelect && !singleSelectNonObject"
|
||||
class="c-inspector__selected-w c-object-label"
|
||||
:class="typeCssClass"
|
||||
>
|
||||
<span class="c-inspector__selected c-object-label__name">{{ item.name }}</span>
|
||||
</div>
|
||||
<div v-if="singleSelectNonObject"
|
||||
class="c-inspector__selected-w c-object-label"
|
||||
:class="typeCssClass"
|
||||
>
|
||||
<span class="c-inspector__selected c-object-label__name c-inspector__selected--non-domain-object">Layout Object</span>
|
||||
</div>
|
||||
<div v-if="multiSelect"
|
||||
class="c-inspector__multiple-selected-w"
|
||||
>
|
||||
{{ itemsSelected }} items selected
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
data() {
|
||||
return {
|
||||
domainObject: {},
|
||||
multiSelect: false,
|
||||
itemsSelected: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
item() {
|
||||
return this.domainObject || {};
|
||||
},
|
||||
type() {
|
||||
return this.openmct.types.get(this.item.type);
|
||||
},
|
||||
typeCssClass() {
|
||||
if (this.type.definition.cssClass === undefined) {
|
||||
return 'icon-object';
|
||||
}
|
||||
return this.type.definition.cssClass;
|
||||
},
|
||||
singleSelectNonObject() {
|
||||
return !this.item.identifier && !this.multiSelect;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.openmct.selection.on('change', this.updateSelection);
|
||||
this.updateSelection(this.openmct.selection.get());
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.openmct.selection.off('change', this.updateSelection);
|
||||
},
|
||||
methods: {
|
||||
updateSelection(selection) {
|
||||
if (selection.length === 0 || selection[0].length === 0) {
|
||||
this.domainObject = {};
|
||||
return;
|
||||
}
|
||||
|
||||
if (selection.length > 1) {
|
||||
this.multiSelect = true;
|
||||
this.domainObject = {};
|
||||
this.itemsSelected = selection.length;
|
||||
return;
|
||||
} else {
|
||||
this.multiSelect = false;
|
||||
this.domainObject = selection[0][0].context.item;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@ -1,72 +1,72 @@
|
||||
<template>
|
||||
<div class="c-properties c-properties--properties">
|
||||
<div class="c-properties__header">
|
||||
Properties
|
||||
<div class="c-inspector__properties c-inspect-properties">
|
||||
<div class="c-inspect-properties__header">
|
||||
Details
|
||||
</div>
|
||||
<ul
|
||||
v-if="!multiSelect && !singleSelectNonObject"
|
||||
class="c-properties__section"
|
||||
class="c-inspect-properties__section"
|
||||
>
|
||||
<li class="c-properties__row">
|
||||
<div class="c-properties__label">
|
||||
<li class="c-inspect-properties__row">
|
||||
<div class="c-inspect-properties__label">
|
||||
Title
|
||||
</div>
|
||||
<div class="c-properties__value">
|
||||
<div class="c-inspect-properties__value">
|
||||
{{ item.name }}
|
||||
</div>
|
||||
</li>
|
||||
<li class="c-properties__row">
|
||||
<div class="c-properties__label">
|
||||
<li class="c-inspect-properties__row">
|
||||
<div class="c-inspect-properties__label">
|
||||
Type
|
||||
</div>
|
||||
<div class="c-properties__value">
|
||||
<div class="c-inspect-properties__value">
|
||||
{{ typeName }}
|
||||
</div>
|
||||
</li>
|
||||
<li
|
||||
v-if="item.created"
|
||||
class="c-properties__row"
|
||||
class="c-inspect-properties__row"
|
||||
>
|
||||
<div class="c-properties__label">
|
||||
<div class="c-inspect-properties__label">
|
||||
Created
|
||||
</div>
|
||||
<div class="c-properties__value">
|
||||
<div class="c-inspect-properties__value">
|
||||
{{ formatTime(item.created) }}
|
||||
</div>
|
||||
</li>
|
||||
<li
|
||||
v-if="item.modified"
|
||||
class="c-properties__row"
|
||||
class="c-inspect-properties__row"
|
||||
>
|
||||
<div class="c-properties__label">
|
||||
<div class="c-inspect-properties__label">
|
||||
Modified
|
||||
</div>
|
||||
<div class="c-properties__value">
|
||||
<div class="c-inspect-properties__value">
|
||||
{{ formatTime(item.modified) }}
|
||||
</div>
|
||||
</li>
|
||||
<li
|
||||
v-for="prop in typeProperties"
|
||||
:key="prop.name"
|
||||
class="c-properties__row"
|
||||
class="c-inspect-properties__row"
|
||||
>
|
||||
<div class="c-properties__label">
|
||||
<div class="c-inspect-properties__label">
|
||||
{{ prop.name }}
|
||||
</div>
|
||||
<div class="c-properties__value">
|
||||
<div class="c-inspect-properties__value">
|
||||
{{ prop.value }}
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<div
|
||||
v-if="multiSelect"
|
||||
class="c-properties__row--span-all"
|
||||
class="c-inspect-properties__row--span-all"
|
||||
>
|
||||
No properties to display for multiple items
|
||||
</div>
|
||||
<div
|
||||
v-if="singleSelectNonObject"
|
||||
class="c-properties__row--span-all"
|
||||
class="c-inspect-properties__row--span-all"
|
||||
>
|
||||
No properties to display for this item
|
||||
</div>
|
||||
|
105
src/ui/inspector/StylesInspectorView.vue
Normal file
105
src/ui/inspector/StylesInspectorView.vue
Normal file
@ -0,0 +1,105 @@
|
||||
/*****************************************************************************
|
||||
* 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>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ConditionalStylesView from '../../plugins/condition/components/inspector/ConditionalStylesView.vue';
|
||||
import Vue from 'vue';
|
||||
import { getStyleProp } from "../../plugins/condition/utils/styleUtils";
|
||||
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
data() {
|
||||
return {
|
||||
selection: []
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.openmct.selection.on('change', this.updateSelection);
|
||||
this.updateSelection(this.openmct.selection.get());
|
||||
},
|
||||
destroyed() {
|
||||
this.openmct.selection.off('change', this.updateSelection);
|
||||
},
|
||||
methods: {
|
||||
getStyleProperties(item) {
|
||||
let styleProps = {};
|
||||
Object.keys(item).forEach((key) => {
|
||||
Object.assign(styleProps, getStyleProp(key, item[key]));
|
||||
});
|
||||
return styleProps;
|
||||
},
|
||||
updateSelection(selection) {
|
||||
if (selection.length > 0 && selection[0].length > 0) {
|
||||
let isChildItem = false;
|
||||
let domainObject = selection[0][0].context.item;
|
||||
let layoutItem = {};
|
||||
let styleProps = this.getStyleProperties({
|
||||
fill: 'transparent',
|
||||
stroke: 'transparent',
|
||||
color: 'transparent'
|
||||
});
|
||||
if (selection[0].length > 1) {
|
||||
isChildItem = true;
|
||||
//If there are more than 1 items in the selection[0] list, the first one could either be a sub domain object OR a layout drawing control.
|
||||
//The second item in the selection[0] list is the container object (usually a layout)
|
||||
if (!domainObject) {
|
||||
styleProps = {};
|
||||
layoutItem = selection[0][0].context.layoutItem;
|
||||
styleProps = this.getStyleProperties(layoutItem);
|
||||
domainObject = selection[0][1].context.item;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.component) {
|
||||
this.component.$destroy();
|
||||
this.component = undefined;
|
||||
this.$el.innerHTML = '';
|
||||
}
|
||||
let viewContainer = document.createElement('div');
|
||||
this.$el.append(viewContainer);
|
||||
this.component = new Vue({
|
||||
provide: {
|
||||
openmct: this.openmct,
|
||||
domainObject: domainObject
|
||||
},
|
||||
el: viewContainer,
|
||||
components: {
|
||||
ConditionalStylesView
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
layoutItem,
|
||||
styleProps,
|
||||
isChildItem
|
||||
}
|
||||
},
|
||||
template: '<conditional-styles-view :can-hide="isChildItem" :item-id="layoutItem.id" :initial-styles="styleProps"></conditional-styles-view>'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user