Conditional styles (#2718)

* Hardcoded prototype - conditional styles for display layout or generator domain objects only.
Needs Architectural review

* Updates to ConditionalStylesView

* Adds background colors list

* Show conditional styles for subObjectViews and telemetryViews

* Uses telemetry provider to determine which style is active
Removes hardcoded conditionSet work used for prototype

* Fixes failing test

* Add default styles to conditionalStyles when a condition set is added to the domain object.

* Use EventEmitter alias instead of eventEmitter3 in imports
Change variable name for better readability and clarity
Remove unused code

* Change StyleRuleManager to accept conditionStyle objects instead of domainObjects

* Uses a map for conditional styles instead of a list in order to improve performance

* Use in-built api to check for identifier equality
Pass missing arguments

* Removes unnecessary object get call.
This commit is contained in:
Shefali Joshi 2020-03-06 14:09:52 -08:00 committed by GitHub
parent 576b843bd5
commit 4675fc8ae6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 365 additions and 39 deletions

View File

@ -20,7 +20,7 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import * as EventEmitter from 'eventemitter3';
import EventEmitter from 'EventEmitter';
import uuid from 'uuid';
import TelemetryCriterion from "./criterion/TelemetryCriterion";
import { TRIGGER } from "./utils/constants";

View File

@ -22,13 +22,13 @@
import Condition from "./Condition";
import uuid from "uuid";
import * as EventEmitter from 'eventemitter3';
import EventEmitter from 'EventEmitter';
export default class ConditionManager extends EventEmitter {
constructor(domainObject, openmct) {
super();
this.domainObject = domainObject;
this.openmct = openmct;
this.domainObject = domainObject;
this.timeAPI = this.openmct.time;
this.latestTimestamp = {};
this.instantiate = this.openmct.$injector.get('instantiate');
@ -37,19 +37,17 @@ export default class ConditionManager extends EventEmitter {
initialize() {
this.conditionResults = {};
this.openmct.objects.get(this.domainObject.identifier).then((obj) => {
this.observeForChanges(obj);
this.conditionCollection = [];
if (this.domainObject.configuration.conditionCollection.length) {
this.domainObject.configuration.conditionCollection.forEach((conditionConfigurationId, index) => {
this.openmct.objects.get(conditionConfigurationId).then((conditionConfiguration) => {
this.initCondition(conditionConfiguration, index)
});
this.observeForChanges(this.domainObject);
this.conditionCollection = [];
if (this.domainObject.configuration.conditionCollection.length) {
this.domainObject.configuration.conditionCollection.forEach((conditionConfigurationId, index) => {
this.openmct.objects.get(conditionConfigurationId).then((conditionConfiguration) => {
this.initCondition(conditionConfiguration, index)
});
} else {
this.addCondition(true);
}
});
});
} else {
this.addCondition(true);
}
}
observeForChanges(domainObject) {

View File

@ -0,0 +1,102 @@
/*****************************************************************************
* 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(conditionalStyleConfiguration, openmct) {
super();
this.openmct = openmct;
if (conditionalStyleConfiguration && conditionalStyleConfiguration.conditionSetIdentifier) {
this.initialize(conditionalStyleConfiguration);
this.subscribeToConditionSet();
}
}
initialize(conditionalStyleConfiguration) {
this.conditionSetIdentifier = conditionalStyleConfiguration.conditionSetIdentifier;
this.defaultStyle = conditionalStyleConfiguration.defaultStyle;
this.updateConditionStylesMap(conditionalStyleConfiguration.styles || []);
}
subscribeToConditionSet() {
if (this.stopProvidingTelemetry) {
this.stopProvidingTelemetry();
}
this.openmct.objects.get(this.conditionSetIdentifier).then((conditionSetDomainObject) => {
this.stopProvidingTelemetry = this.openmct.telemetry.subscribe(conditionSetDomainObject, output => this.handleConditionSetResultUpdated(output));
});
}
updateConditionalStyleConfig(conditionalStyleConfiguration) {
if (!conditionalStyleConfiguration || !conditionalStyleConfiguration.conditionSetIdentifier) {
this.destroy();
} else {
let isNewConditionSet = !this.conditionSetIdentifier ||
this.openmct.objects.areIdsEqual(this.conditionSetIdentifier, conditionalStyleConfiguration.conditionSetIdentifier);
this.initialize(conditionalStyleConfiguration);
//Only resubscribe if the conditionSet has changed.
if (isNewConditionSet) {
this.subscribeToConditionSet();
}
}
}
updateConditionStylesMap(conditionStyles) {
let conditionStyleMap = {};
conditionStyles.forEach((conditionStyle) => {
const identifier = this.openmct.objects.makeKeyString(conditionStyle.conditionIdentifier);
conditionStyleMap[identifier] = conditionStyle.style;
});
this.conditionalStyleMap = conditionStyleMap;
}
handleConditionSetResultUpdated(resultData) {
let identifier = this.openmct.objects.makeKeyString(resultData.conditionId);
let foundStyle = this.conditionalStyleMap[identifier];
if (foundStyle) {
if (foundStyle !== this.currentStyle) {
this.currentStyle = foundStyle;
}
} else {
if (this.currentStyle !== this.defaultStyle) {
this.currentStyle = this.defaultStyle;
}
}
this.updateDomainObjectStyle();
}
updateDomainObjectStyle() {
this.emit('conditionalStyleUpdated', this.currentStyle)
}
destroy() {
this.currentStyle = this.defaultStyle;
this.updateDomainObjectStyle();
if (this.stopProvidingTelemetry) {
this.stopProvidingTelemetry();
}
this.conditionSetIdentifier = undefined;
}
}

View File

@ -0,0 +1,38 @@
<template>
<div>
<div v-if="condition"
class="holder c-c-button-wrapper align-left"
>
<div>{{ condition.configuration.name }}</div>
</div>
</div>
</template>
<script>
export default {
components: {
},
inject: [
'openmct'
],
props: {
conditionIdentifier: {
type: Object,
required: true
}
},
data() {
return {
condition: null
}
},
destroyed() {
},
mounted() {
this.openmct.objects.get(this.conditionIdentifier).then((conditionDomainObject) => {
this.condition = conditionDomainObject;
});
}
}
</script>

View File

@ -1,14 +1,110 @@
<template>
<div>Conditional Styles inspector view</div>
<div>
<div v-if="!conditionalStyles.length"
class="holder c-c-button-wrapper align-left">
<button
class="c-c-button c-c-button--minor add-criteria-button"
@click="addConditionSet">
<span class="c-c-button__label">Use conditional styling</span>
</button>
</div>
<div v-else>
<div class="holder c-c-button-wrapper align-left">
<button
class="c-c-button c-c-button--minor add-criteria-button"
@click="removeConditionSet">
<span class="c-c-button__label">Remove conditional styling</span>
</button>
</div>
<ul>
<li v-for="conditionStyle in conditionalStyles"
:key="conditionStyle.conditionIdentifier.key">
<conditional-style :condition-identifier="conditionStyle.conditionIdentifier"
:condition-style="conditionStyle.style"/>
</li>
</ul>
</div>
</div>
</template>
<script>
import ConditionalStyle from "./ConditionalStyle.vue";
export default {
components: {
ConditionalStyle
},
inject: [
'openmct'
]
'openmct',
'domainObject',
'layoutItem'
],
data() {
return {
conditionalStyles: []
}
},
mounted() {
if (this.layoutItem) {
//TODO: Handle layout items
}
if (this.domainObject.configuration) {
this.defautStyle = this.domainObject.configuration.defaultStyle;
if (this.domainObject.configuration.conditionalStyle) {
this.conditionalStyles = this.domainObject.configuration.conditionalStyle.styles || [];
}
}
},
methods: {
addConditionSet() {
//TODO: this.conditionSetIdentifier will be set by the UI before calling this
this.conditionSetIdentifier = {
namespace: '',
key: 'bb0f61ad-268d-4d3e-bb30-90ca4a2053c4'
};
this.initializeConditionalStyles();
},
removeConditionSet() {
this.conditionSetIdentifier = '';
this.conditionalStyles = [];
this.persist(undefined);
},
initializeConditionalStyles() {
const backgroundColors = [{backgroundColor: 'red'},{backgroundColor: 'orange'}, {backgroundColor: 'blue'}];
this.openmct.objects.get(this.conditionSetIdentifier).then((conditionSetDomainObject) => {
conditionSetDomainObject.configuration.conditionCollection.forEach((identifier, index) => {
this.conditionalStyles.push({
conditionIdentifier: identifier,
style: backgroundColors[index]
});
});
this.persist({
defaultStyle: this.defaultStyle || {backgroundColor: 'inherit'},
conditionSetIdentifier: this.conditionSetIdentifier,
styles: this.conditionalStyles
});
});
},
findStyleByConditionId(id) {
for(let i=0, ii=this.conditionalStyles.length; i < ii; i++) {
if (this.openmct.objects.makeKeyString(this.conditionalStyles[i].conditionIdentifier) === this.openmct.objects.makeKeyString(id)) {
return {
index: i,
item: this.conditionalStyles[i]
};
}
}
},
updateConditionalStyle(conditionIdentifier, style) {
let found = this.findStyleByConditionId(conditionIdentifier);
if (found) {
this.conditionalStyles[found.index].style = style;
}
this.persist(undefined);
},
persist(conditionalStyle) {
this.openmct.objects.mutate(this.domainObject, 'configuration.conditionalStyle', conditionalStyle);
}
}
}
</script>

View File

@ -20,7 +20,7 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import * as EventEmitter from 'eventemitter3';
import EventEmitter from 'EventEmitter';
import {OPERATIONS} from '../utils/operations';
export default class TelemetryCriterion extends EventEmitter {

View File

@ -36,6 +36,7 @@
<div
v-if="showLabel"
class="c-telemetry-view__label"
:style="conditionalStyle"
>
<div class="c-telemetry-view__label-text">
{{ domainObject.name }}
@ -47,6 +48,7 @@
:title="fieldName"
class="c-telemetry-view__value"
:class="[telemetryClass]"
:style="!telemetryClass && conditionalStyle"
>
<div class="c-telemetry-view__value-text">
{{ telemetryValue }}
@ -59,6 +61,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 +112,8 @@ export default {
datum: undefined,
formats: undefined,
domainObject: undefined,
currentObjectPath: undefined
currentObjectPath: undefined,
conditionalStyle: ''
}
},
computed: {
@ -182,6 +186,16 @@ export default {
this.removeSelectable();
}
if (this.unlistenStyles) {
this.unlistenStyles();
}
if (this.styleRuleManager) {
this.styleRuleManager.destroy();
this.styleRuleManager.off('conditionalStyleUpdated', this.updateStyle.bind(this));
delete this.styleRuleManager;
}
this.openmct.time.off("bounds", this.refreshData);
},
methods: {
@ -224,6 +238,7 @@ export default {
},
setObject(domainObject) {
this.domainObject = domainObject;
this.initConditionalStyles();
this.keyString = this.openmct.objects.makeKeyString(domainObject.identifier);
this.metadata = this.openmct.telemetry.getMetadata(this.domainObject);
this.limitEvaluator = this.openmct.telemetry.limitEvaluator(this.domainObject);
@ -248,6 +263,21 @@ export default {
},
showContextMenu(event) {
this.openmct.contextMenu._showContextMenuForObjectPath(this.currentObjectPath, event.x, event.y, CONTEXT_MENU_ACTIONS);
},
initConditionalStyles() {
this.styleRuleManager = new StyleRuleManager(this.domainObject.configuration.conditionalStyle, this.openmct);
this.styleRuleManager.on('conditionalStyleUpdated', this.updateStyle.bind(this));
if (this.unlistenStyles) {
this.unlistenStyles();
}
this.unlistenStyles = this.openmct.objects.observe(this.domainObject, 'configuration.conditionalStyle', (newConditionalStyle) => {
//Updating conditional styles in the inspector view will trigger this so that the changes are reflected immediately
this.styleRuleManager.updateConditionalStyleConfig(newConditionalStyle);
});
},
updateStyle(styleObj) {
this.conditionalStyle = styleObj;
}
}
}

View File

@ -4,6 +4,7 @@
<script>
import _ from "lodash"
import StyleRuleManager from "@/plugins/condition/StyleRuleManager";
export default {
inject: ["openmct"],
@ -31,6 +32,20 @@ export default {
if (this.releaseEditModeHandler) {
this.releaseEditModeHandler();
}
if (this.unlisten) {
this.unlisten();
}
if (this.stopListeningConditionalStyles) {
this.stopListeningConditionalStyles();
}
if (this.styleRuleManager) {
this.styleRuleManager.destroy();
this.styleRuleManager.off('conditionalStyleUpdated', this.updateStyle.bind(this));
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.initConditionalStyles();
}
},
methods: {
clear() {
@ -76,6 +96,15 @@ export default {
this.clear();
this.updateView(true);
},
updateStyle(styleObj) {
if (!styleObj) {
return;
}
let keys = Object.keys(styleObj);
keys.forEach(key => {
this.$el.style[key] = styleObj[key];
})
},
updateView(immediatelySelect) {
this.clear();
if (!this.currentObject) {
@ -149,8 +178,28 @@ export default {
});
this.viewKey = viewKey;
this.initConditionalStyles();
this.updateView(immediatelySelect);
},
initConditionalStyles() {
if (!this.styleRuleManager) {
this.styleRuleManager = new StyleRuleManager((this.currentObject.configuration && this.currentObject.configuration.conditionalStyle), this.openmct);
this.styleRuleManager.on('conditionalStyleUpdated', this.updateStyle.bind(this));
} else {
this.styleRuleManager.updateConditionalStyleConfig(this.currentObject.configuration && this.currentObject.configuration.conditionalStyle);
}
if (this.stopListeningConditionalStyles) {
this.stopListeningConditionalStyles();
}
this.stopListeningConditionalStyles = this.openmct.objects.observe(this.currentObject, 'configuration.conditionalStyle', (newConditionalStyle) => {
//Updating conditional styles in the inspector view will trigger this so that the changes are reflected immediately
this.styleRuleManager.updateConditionalStyleConfig(newConditionalStyle);
});
},
loadComposition() {
return this.composition.load();
},

View File

@ -25,27 +25,40 @@ export default {
},
methods: {
updateSelection(selection) {
this.selection = selection;
if (selection.length > 0 && selection[0].length > 0) {
let domainObject = selection[0][0].context.item;
let layoutItem;
if (selection[0].length > 1) {
//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)
domainObject = selection[0][0].context.item;
if (!domainObject) {
layoutItem = selection[0][0].context.layoutItem;
domainObject = selection[0][1].context.item;
}
}
if (this.component) {
this.component.$destroy();
this.component = undefined;
this.$el.innerHTML = '';
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,
layoutItem: layoutItem
},
el: viewContainer,
components: {
ConditionalStylesView
},
template: '<conditional-styles-view></conditional-styles-view>'
});
}
let viewContainer = document.createElement('div');
this.$el.append(viewContainer);
this.component = new Vue({
provide: {
openmct: this.openmct
},
el: viewContainer,
components: {
ConditionalStylesView
},
template: '<conditional-styles-view></conditional-styles-view>'
});
}
}
}