Condition set output label (#4233)

* Show condition set label for condition widgets
* CSS changes
* Ensure condition set output as labels also works when condition widget is part of a display layout
* Adds tests for conditionWidget
* Tests for condition label output. Fix breaking tests.
* Don't remove event listeners when conditionset changes

Co-authored-by: Charles Hacskaylo <charlesh88@gmail.com>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
Co-authored-by: David Tsay <3614296+davetsay@users.noreply.github.com>
This commit is contained in:
Shefali Joshi 2022-01-05 13:54:11 -08:00 committed by GitHub
parent 88a94c80be
commit 3620760991
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 371 additions and 793 deletions

View File

@ -109,7 +109,7 @@ export default class StyleRuleManager extends EventEmitter {
if (!styleConfiguration || !styleConfiguration.conditionSetIdentifier) {
this.initialize(styleConfiguration || {});
this.applyStaticStyle();
this.destroy();
this.destroy(true);
} else {
let isNewConditionSet = !this.conditionSetIdentifier
|| !this.openmct.objects.areIdsEqual(this.conditionSetIdentifier, styleConfiguration.conditionSetIdentifier);
@ -180,15 +180,17 @@ export default class StyleRuleManager extends EventEmitter {
this.updateDomainObjectStyle();
}
destroy() {
destroy(skipEventListeners) {
if (this.stopProvidingTelemetry) {
this.stopProvidingTelemetry();
delete this.stopProvidingTelemetry;
}
this.openmct.time.off("bounds", this.refreshData);
this.openmct.editor.off('isEditing', this.toggleSubscription);
if (!skipEventListeners) {
this.openmct.time.off("bounds", this.refreshData);
this.openmct.editor.off('isEditing', this.toggleSubscription);
}
this.conditionSetIdentifier = undefined;
}

View File

@ -1,475 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, 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"
>
<StyleEditor 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"
@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"
:class="{'is-current': conditionStyle.conditionId === selectedConditionId}"
@click="applySelectedConditionStyle(conditionStyle.conditionId)"
>
<condition-error :show-label="true"
:condition="getCondition(conditionStyle.conditionId)"
/>
<condition-description :show-label="true"
:condition="getCondition(conditionStyle.conditionId)"
/>
<StyleEditor 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 SelectorDialogTree from '@/ui/components/SelectorDialogTree.vue';
import ConditionDescription from "@/plugins/condition/components/ConditionDescription.vue";
import ConditionError from "@/plugins/condition/components/ConditionError.vue";
import Vue from 'vue';
import PreviewAction from "@/ui/preview/PreviewAction.js";
import { getApplicableStylesForItem } from "@/plugins/condition/utils/styleUtils";
import isEmpty from 'lodash/isEmpty';
export default {
name: 'ConditionalStylesView',
components: {
ConditionDescription,
ConditionError,
StyleEditor
},
inject: [
'openmct',
'selection'
],
data() {
return {
conditionalStyles: [],
staticStyle: undefined,
conditionSetDomainObject: undefined,
isEditing: this.openmct.editor.isEditing(),
conditions: undefined,
conditionsLoaded: false,
navigateToPath: '',
selectedConditionId: ''
};
},
destroyed() {
this.removeListeners();
},
mounted() {
this.itemId = '';
this.getDomainObjectFromSelection();
this.previewAction = new PreviewAction(this.openmct);
if (this.domainObject.configuration && this.domainObject.configuration.objectStyles) {
let objectStyles = this.itemId ? this.domainObject.configuration.objectStyles[this.itemId] : this.domainObject.configuration.objectStyles;
this.initializeStaticStyle(objectStyles);
if (objectStyles && objectStyles.conditionSetIdentifier) {
this.openmct.objects.get(objectStyles.conditionSetIdentifier).then(this.initialize);
this.conditionalStyles = objectStyles.styles;
}
} else {
this.initializeStaticStyle();
}
this.openmct.editor.on('isEditing', this.setEditState);
},
methods: {
isItemType(type, item) {
return item && (item.type === type);
},
getDomainObjectFromSelection() {
let layoutItem;
let domainObject;
if (this.selection[0].length > 1) {
//If there are more than 1 items in the this.selection[0] list, the first one could either be a sub domain object OR a layout drawing control.
//The second item in the this.selection[0] list is the container object (usually a layout)
layoutItem = this.selection[0][0].context.layoutItem;
const item = this.selection[0][0].context.item;
this.canHide = true;
if (item
&& (!layoutItem || (this.isItemType('subobject-view', layoutItem)))) {
domainObject = item;
} else {
domainObject = this.selection[0][1].context.item;
if (layoutItem) {
this.itemId = layoutItem.id;
}
}
} else {
domainObject = this.selection[0][0].context.item;
}
this.domainObject = domainObject;
this.initialStyles = getApplicableStylesForItem(domainObject, layoutItem);
this.$nextTick(() => {
this.removeListeners();
if (this.domainObject) {
this.stopObserving = this.openmct.objects.observe(this.domainObject, '*', newDomainObject => this.domainObject = newDomainObject);
this.stopObservingItems = this.openmct.objects.observe(this.domainObject, 'configuration.items', this.updateDomainObjectItemStyles);
}
});
},
removeListeners() {
if (this.stopObserving) {
this.stopObserving();
}
if (this.stopObservingItems) {
this.stopObservingItems();
}
if (this.stopProvidingTelemetry) {
this.stopProvidingTelemetry();
delete this.stopProvidingTelemetry;
}
},
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;
if (this.isEditing) {
if (this.stopProvidingTelemetry) {
this.stopProvidingTelemetry();
delete this.stopProvidingTelemetry;
}
} else {
this.subscribeToConditionSet();
}
},
addConditionSet() {
let conditionSetDomainObject;
let self = this;
function handleItemSelection({ item }) {
if (item) {
conditionSetDomainObject = item;
}
}
function dismissDialog(overlay, initialize) {
overlay.dismiss();
if (initialize && conditionSetDomainObject) {
self.conditionSetDomainObject = conditionSetDomainObject;
self.conditionalStyles = [];
self.initializeConditionalStyles();
}
}
let vm = new Vue({
components: { SelectorDialogTree },
provide: {
openmct: this.openmct
},
data() {
return {
handleItemSelection,
title: 'Select Condition Set'
};
},
template: '<selector-dialog-tree :title="title" @treeItemSelected="handleItemSelection"></selector-dialog-tree>'
}).$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.openmct.objects.getRelativePath(this.objectPath);
}
);
},
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);
} else {
this.openmct.router.navigate(this.navigateToPath);
}
},
removeConditionSet() {
this.conditionSetDomainObject = undefined;
this.conditionalStyles = [];
let domainObjectStyles = (this.domainObject.configuration && this.domainObject.configuration.objectStyles) || {};
if (this.itemId) {
domainObjectStyles[this.itemId].conditionSetIdentifier = undefined;
domainObjectStyles[this.itemId].selectedConditionId = undefined;
domainObjectStyles[this.itemId].defaultConditionId = 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;
domainObjectStyles.selectedConditionId = undefined;
domainObjectStyles.defaultConditionId = undefined;
delete domainObjectStyles.conditionSetIdentifier;
domainObjectStyles.styles = undefined;
delete domainObjectStyles.styles;
}
if (isEmpty(domainObjectStyles)) {
domainObjectStyles = undefined;
}
this.persist(domainObjectStyles);
if (this.stopProvidingTelemetry) {
this.stopProvidingTelemetry();
delete this.stopProvidingTelemetry;
}
},
updateDomainObjectItemStyles(newItems) {
//check that all items that have been styles still exist. Otherwise delete those styles
let domainObjectStyles = (this.domainObject.configuration && this.domainObject.configuration.objectStyles) || {};
let itemsToRemove = [];
let keys = Object.keys(domainObjectStyles);
//TODO: Need an easier way to find which properties are itemIds
keys.forEach((key) => {
const keyIsItemId = (key !== 'styles')
&& (key !== 'staticStyle')
&& (key !== 'defaultConditionId')
&& (key !== 'selectedConditionId')
&& (key !== 'conditionSetIdentifier');
if (keyIsItemId) {
if (!(newItems.find(item => item.id === key))) {
itemsToRemove.push(key);
}
}
});
if (itemsToRemove.length) {
this.removeItemStyles(itemsToRemove, domainObjectStyles);
}
},
removeItemStyles(itemIds, domainObjectStyles) {
itemIds.forEach(itemId => {
if (domainObjectStyles[itemId]) {
domainObjectStyles[itemId] = undefined;
delete domainObjectStyles[this.itemId];
}
});
if (isEmpty(domainObjectStyles)) {
domainObjectStyles = undefined;
}
this.persist(domainObjectStyles);
},
initializeConditionalStyles() {
if (!this.conditions) {
this.conditions = {};
}
let conditionalStyles = [];
this.conditionSetDomainObject.configuration.conditionCollection.forEach((conditionConfiguration, index) => {
if (conditionConfiguration.isDefault) {
this.selectedConditionId = conditionConfiguration.id;
}
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(this.selectedConditionId));
if (!this.isEditing) {
this.subscribeToConditionSet();
}
},
subscribeToConditionSet() {
if (this.stopProvidingTelemetry) {
this.stopProvidingTelemetry();
delete this.stopProvidingTelemetry;
}
if (this.conditionSetDomainObject) {
this.openmct.telemetry.request(this.conditionSetDomainObject)
.then(output => {
if (output && output.length) {
this.handleConditionSetResultUpdated(output[0]);
}
});
this.stopProvidingTelemetry = this.openmct.telemetry.subscribe(this.conditionSetDomainObject, this.handleConditionSetResultUpdated.bind(this));
}
},
handleConditionSetResultUpdated(resultData) {
this.selectedConditionId = resultData ? resultData.conditionId : '';
},
initializeStaticStyle(objectStyles) {
let staticStyle = objectStyles && objectStyles.staticStyle;
if (staticStyle) {
this.staticStyle = {
style: Object.assign({}, this.initialStyles, staticStyle.style)
};
} else {
this.staticStyle = {
style: Object.assign({}, this.initialStyles)
};
}
},
findStyleByConditionId(id) {
return this.conditionalStyles.find(conditionalStyle => conditionalStyle.conditionId === id);
},
updateStaticStyle(staticStyle) {
this.staticStyle = staticStyle;
this.persist(this.getDomainObjectConditionalStyle());
},
updateConditionalStyle(conditionStyle) {
let found = this.findStyleByConditionId(conditionStyle.conditionId);
if (found) {
found.style = conditionStyle.style;
this.selectedConditionId = found.conditionId;
this.persist(this.getDomainObjectConditionalStyle());
}
},
getDomainObjectConditionalStyle(defaultConditionId) {
let objectStyle = {
styles: this.conditionalStyles,
staticStyle: this.staticStyle,
selectedConditionId: this.selectedConditionId
};
if (defaultConditionId) {
objectStyle.defaultConditionId = defaultConditionId;
}
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] : {};
},
applySelectedConditionStyle(conditionId) {
this.selectedConditionId = conditionId;
this.persist(this.getDomainObjectConditionalStyle());
},
persist(style) {
this.openmct.objects.mutate(this.domainObject, 'configuration.objectStyles', style);
}
}
};
</script>

View File

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

View File

@ -63,7 +63,7 @@
<div class="c-inspect-styles__header">
Conditional Object Styles
</div>
<div class="c-inspect-styles__content c-inspect-styles__condition-set">
<div class="c-inspect-styles__content c-inspect-styles__condition-set c-inspect-styles__elem">
<a v-if="conditionSetDomainObject"
class="c-object-label"
@click="navigateOrPreview"
@ -87,6 +87,27 @@
</template>
</div>
<div v-if="isConditionWidget && allowEditing"
class="c-inspect-styles__elem c-inspect-styles__output-label-toggle"
>
<label class="c-toggle-switch">
<input
type="checkbox"
:checked="useConditionSetOutputAsLabel"
@change="updateConditionSetOutputLabel"
>
<span class="c-toggle-switch__slider"></span>
<span class="c-toggle-switch__label">Use Condition Set output as label</span>
</label>
</div>
<div v-if="isConditionWidget && !allowEditing"
class="c-inspect-styles__elem"
>
<span class="c-toggle-switch__label">Condition Set output as label:
<span v-if="useConditionSetOutputAsLabel"> Yes</span><span v-else> No</span>
</span>
</div>
<FontStyleEditor
v-if="canStyleFont"
:font-style="consolidatedFontStyle"
@ -172,7 +193,8 @@ export default {
selectedConditionId: '',
items: [],
domainObject: undefined,
consolidatedFontStyle: {}
consolidatedFontStyle: {},
useConditionSetOutputAsLabel: false
};
},
computed: {
@ -187,6 +209,11 @@ export default {
allowEditing() {
return this.isEditing && !this.locked;
},
isConditionWidget() {
const hasConditionWidgetObjects = this.domainObjectsById && Object.values(this.domainObjectsById).some((object) => object.type === 'conditionWidget');
return (hasConditionWidgetObjects || (this.domainObject && this.domainObject.type === 'conditionWidget'));
},
styleableFontItems() {
return this.selection.filter(selectionPath => {
const item = selectionPath[0].context.item;
@ -205,28 +232,6 @@ export default {
return true;
});
},
computedconsolidatedFontStyle() {
let consolidatedFontStyle;
const styles = [];
this.styleableFontItems.forEach(styleable => {
const fontStyle = this.getFontStyle(styleable[0]);
styles.push(fontStyle);
});
if (styles.length) {
const hasConsolidatedFontSize = styles.length && styles.every((fontStyle, i, arr) => fontStyle.fontSize === arr[0].fontSize);
const hasConsolidatedFont = styles.length && styles.every((fontStyle, i, arr) => fontStyle.font === arr[0].font);
consolidatedFontStyle = {
fontSize: hasConsolidatedFontSize ? styles[0].fontSize : NON_SPECIFIC,
font: hasConsolidatedFont ? styles[0].font : NON_SPECIFIC
};
}
return consolidatedFontStyle;
},
nonSpecificFontProperties() {
if (!this.consolidatedFontStyle) {
return [];
@ -247,6 +252,8 @@ export default {
this.previewAction = new PreviewAction(this.openmct);
this.isMultipleSelection = this.selection.length > 1;
this.getObjectsAndItemsFromSelection();
this.useConditionSetOutputAsLabel = this.getConfigurationForLabel();
if (!this.isMultipleSelection) {
let objectStyles = this.getObjectStyles();
this.initializeStaticStyle(objectStyles);
@ -264,6 +271,12 @@ export default {
this.stylesManager.on('styleSelected', this.applyStyleToSelection);
},
methods: {
getConfigurationForLabel() {
const childObjectUsesLabels = Object.values(this.domainObjectsById || {}).some((object) => object.configuration && object.configuration.useConditionSetOutputAsLabel);
const domainObjectUsesLabels = this.domainObject && this.domainObject.configuration && this.domainObject.configuration.useConditionSetOutputAsLabel;
return childObjectUsesLabels || domainObjectUsesLabels;
},
getObjectStyles() {
let objectStyles;
if (this.domainObjectsById) {
@ -487,13 +500,14 @@ export default {
this.conditions[conditionConfiguration.id] = conditionConfiguration;
let foundStyle = this.findStyleByConditionId(conditionConfiguration.id);
let output = { output: conditionConfiguration.configuration.output };
if (foundStyle) {
foundStyle.style = Object.assign((this.canHide ? { isStyleInvisible: '' } : {}), this.initialStyles, foundStyle.style);
foundStyle.style = Object.assign((this.canHide ? { isStyleInvisible: '' } : {}), this.initialStyles, foundStyle.style, output);
conditionalStyles.push(foundStyle);
} else {
conditionalStyles.splice(index, 0, {
conditionId: conditionConfiguration.id,
style: Object.assign((this.canHide ? { isStyleInvisible: '' } : {}), this.initialStyles)
style: Object.assign((this.canHide ? { isStyleInvisible: '' } : {}), this.initialStyles, output)
});
}
});
@ -715,6 +729,12 @@ export default {
} else {
objectStyle.styles.forEach((conditionalStyle, index) => {
let style = {};
if (domainObject.configuration.useConditionSetOutputAsLabel) {
style.output = conditionalStyle.style.output;
} else {
style.output = '';
}
Object.keys(item.applicableStyles).concat(['isStyleInvisible']).forEach(key => {
style[key] = conditionalStyle.style[key];
});
@ -731,10 +751,21 @@ export default {
}
});
} else {
domainObjectStyles = {
...domainObjectStyles,
...objectStyle
};
if (domainObject.configuration.useConditionSetOutputAsLabel !== true) {
let objectConditionStyle = JSON.parse(JSON.stringify(objectStyle));
objectConditionStyle.styles.forEach((conditionalStyle) => {
conditionalStyle.style.output = '';
});
domainObjectStyles = {
...domainObjectStyles,
...objectConditionStyle
};
} else {
domainObjectStyles = {
...domainObjectStyles,
...objectStyle
};
}
}
return domainObjectStyles;
@ -743,6 +774,17 @@ export default {
this.selectedConditionId = conditionId;
this.getAndPersistStyles();
},
persistLabelConfiguration() {
if (this.domainObjectsById) {
Object.values(this.domainObjectsById).forEach((object) => {
this.openmct.objects.mutate(object, 'configuration.useConditionSetOutputAsLabel', this.useConditionSetOutputAsLabel);
});
} else {
this.openmct.objects.mutate(this.domainObject, 'configuration.useConditionSetOutputAsLabel', this.useConditionSetOutputAsLabel);
}
this.getAndPersistStyles();
},
persist(domainObject, style) {
this.openmct.objects.mutate(domainObject, 'configuration.objectStyles', style);
},
@ -863,6 +905,10 @@ export default {
const layoutItemType = selectionPath[0].context.layoutItem && selectionPath[0].context.layoutItem.type;
return layoutItemType && layoutItemType !== 'subobject-view';
},
updateConditionSetOutputLabel(event) {
this.useConditionSetOutputAsLabel = event.target.checked === true;
this.persistLabelConfiguration();
}
}
};

View File

@ -39,12 +39,15 @@
flex-direction: column;
}
&__elem {
border-bottom: 1px solid $colorInteriorBorder;
padding-bottom: $interiorMargin;
}
&__condition-set {
align-items: baseline;
border-bottom: 1px solid $colorInteriorBorder;
display: flex;
flex-direction: row;
padding-bottom: $interiorMargin;
.c-object-label {
flex: 1 1 auto;

View File

@ -133,6 +133,168 @@ describe('the plugin', function () {
});
});
describe('the condition set usage for condition widgets', () => {
let conditionWidgetItem;
let selection;
let component;
let styleViewComponentObject;
const conditionSetDomainObject = {
"configuration": {
"conditionTestData": [
{
"telemetry": "",
"metadata": "",
"input": ""
}
],
"conditionCollection": [
{
"id": "39584410-cbf9-499e-96dc-76f27e69885d",
"configuration": {
"name": "Unnamed Condition",
"output": "Sine > 0",
"trigger": "all",
"criteria": [
{
"id": "85fbb2f7-7595-42bd-9767-a932266c5225",
"telemetry": {
"namespace": "",
"key": "be0ba97f-b510-4f40-a18d-4ff121d5ea1a"
},
"operation": "greaterThan",
"input": [
"0"
],
"metadata": "sin"
},
{
"id": "35400132-63b0-425c-ac30-8197df7d5862",
"telemetry": "any",
"operation": "enumValueIs",
"input": [
"0"
],
"metadata": "state"
}
]
},
"summary": "Match if all criteria are met: Sine Wave Generator Sine > 0 and any telemetry State is OFF "
},
{
"isDefault": true,
"id": "2532d90a-e0d6-4935-b546-3123522da2de",
"configuration": {
"name": "Default",
"output": "Default",
"trigger": "all",
"criteria": [
]
},
"summary": ""
}
]
},
"composition": [
{
"namespace": "",
"key": "be0ba97f-b510-4f40-a18d-4ff121d5ea1a"
},
{
"namespace": "",
"key": "077ffa67-e78f-4e99-80e0-522ac33a3888"
}
],
"telemetry": {
},
"name": "Condition Set",
"type": "conditionSet",
"identifier": {
"namespace": "",
"key": "863012c1-f6ca-4ab0-aed7-fd43d5e4cd12"
}
};
beforeEach(() => {
conditionWidgetItem = {
"label": "Condition Widget",
"conditionalLabel": "",
"configuration": {
},
"name": "Condition Widget",
"type": "conditionWidget",
"identifier": {
"namespace": "",
"key": "c5e636c1-6771-4c9c-b933-8665cab189b3"
}
};
selection = [
[{
context: {
"item": conditionWidgetItem,
"supportsMultiSelect": false
}
}]
];
let viewContainer = document.createElement('div');
child.append(viewContainer);
component = new Vue({
el: viewContainer,
components: {
StylesView
},
provide: {
openmct: openmct,
selection: selection,
stylesManager
},
template: '<styles-view/>'
});
return Vue.nextTick().then(() => {
styleViewComponentObject = component.$root.$children[0];
styleViewComponentObject.setEditState(true);
});
});
afterEach(() => {
component.$destroy();
});
it('does not include the output label when the flag is disabled', () => {
styleViewComponentObject.conditionSetDomainObject = conditionSetDomainObject;
styleViewComponentObject.conditionalStyles = [];
styleViewComponentObject.initializeConditionalStyles();
expect(styleViewComponentObject.conditionalStyles.length).toBe(2);
return Vue.nextTick().then(() => {
const hasNoOutput = styleViewComponentObject.domainObject.configuration.objectStyles.styles.every((style) => {
return style.style.output === '' || style.style.output === undefined;
});
expect(hasNoOutput).toBeTrue();
});
});
it('includes the output label when the flag is enabled', () => {
styleViewComponentObject.conditionSetDomainObject = conditionSetDomainObject;
styleViewComponentObject.conditionalStyles = [];
styleViewComponentObject.initializeConditionalStyles();
expect(styleViewComponentObject.conditionalStyles.length).toBe(2);
styleViewComponentObject.useConditionSetOutputAsLabel = true;
styleViewComponentObject.persistLabelConfiguration();
return Vue.nextTick().then(() => {
const outputs = styleViewComponentObject.domainObject.configuration.objectStyles.styles.map((style) => {
return style.style.output;
});
expect(outputs.join(',')).toEqual('Sine > 0,Default');
});
});
});
describe('the condition set usage for multiple display layout items', () => {
let displayLayoutItem;
let lineLayoutItem;
@ -449,6 +611,10 @@ describe('the plugin', function () {
const applicableStyles = getApplicableStylesForItem(styleViewComponentObject.domainObject, item);
const applicableStylesKeys = Object.keys(applicableStyles).concat(['isStyleInvisible']);
Object.keys(foundStyle.style).forEach((key) => {
if (key === 'output') {
return;
}
expect(applicableStylesKeys.indexOf(key)).toBeGreaterThan(-1);
expect(foundStyle.style[key]).toEqual(conditionalStyle.style[key]);
});

View File

@ -26,7 +26,7 @@
:href="url"
>
<div class="c-condition-widget__label">
{{ internalDomainObject.label }}
{{ internalDomainObject.conditionalLabel || internalDomainObject.label }}
</div>
</component>
</template>

View File

@ -27,12 +27,15 @@ export default function plugin() {
openmct.objectViews.addProvider(new ConditionWidgetViewProvider(openmct));
openmct.types.addType('conditionWidget', {
key: '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.configuration = {};
domainObject.label = 'Condition Widget';
domainObject.conditionalLabel = '';
},
form: [
{

View File

@ -0,0 +1,103 @@
import { createOpenMct, resetApplicationState } from "utils/testing";
import ConditionWidgetPlugin from "./plugin";
import Vue from 'vue';
describe('the plugin', function () {
let objectDef;
let element;
let child;
let openmct;
let mockObjectPath;
beforeEach((done) => {
mockObjectPath = [
{
name: 'mock folder',
type: 'fake-folder',
identifier: {
key: 'mock-folder',
namespace: ''
}
},
{
name: 'mock parent folder',
type: 'conditionWidget',
identifier: {
key: 'mock-parent-folder',
namespace: ''
}
}
];
const timeSystem = {
timeSystemKey: 'utc',
bounds: {
start: 1597160002854,
end: 1597181232854
}
};
openmct = createOpenMct(timeSystem);
openmct.install(new ConditionWidgetPlugin());
objectDef = openmct.types.get('conditionWidget').definition;
element = document.createElement('div');
element.style.width = '640px';
element.style.height = '480px';
child = document.createElement('div');
child.style.width = '640px';
child.style.height = '480px';
element.appendChild(child);
openmct.on('start', done);
openmct.startHeadless();
});
afterEach(() => {
return resetApplicationState(openmct);
});
let mockObject = {
name: 'Condition Widget',
key: 'conditionWidget',
creatable: true
};
it('defines a conditionWidget object type with the correct key', () => {
expect(objectDef.key).toEqual(mockObject.key);
});
describe('the conditionWidget object', () => {
it('is creatable', () => {
expect(objectDef.creatable).toEqual(mockObject.creatable);
});
});
describe('the view', () => {
let conditionWidgetView;
let testViewObject;
beforeEach(() => {
testViewObject = {
id: "test-object",
identifier: {
key: "test-object",
namespace: ''
},
type: "conditionWidget"
};
const applicableViews = openmct.objectViews.get(testViewObject, mockObjectPath);
conditionWidgetView = applicableViews.find((viewProvider) => viewProvider.key === 'conditionWidget');
let view = conditionWidgetView.view(testViewObject, element);
view.show(child, true);
return Vue.nextTick();
});
it('provides a view', () => {
expect(conditionWidgetView).toBeDefined();
});
});
});

View File

@ -38,10 +38,14 @@ export default {
this.objectStyle = this.getObjectStyleForItem(this.parentDomainObject.configuration.objectStyles);
this.initObjectStyles();
},
destroyed() {
beforeDestroy() {
if (this.stopListeningObjectStyles) {
this.stopListeningObjectStyles();
}
if (this.styleRuleManager) {
this.styleRuleManager.destroy();
}
},
methods: {
getObjectStyleForItem(objectStyle) {

View File

@ -191,6 +191,12 @@ export default {
}
}
});
if (this.domainObject && this.domainObject.type === 'conditionWidget' && keys.includes('output')) {
this.openmct.objects.mutate(this.domainObject, 'conditionalLabel', styleObj.output);
} else {
this.openmct.objects.mutate(this.domainObject, 'conditionalLabel', '');
}
},
updateView(immediatelySelect) {
this.clear();