Compare commits

..

1 Commits

Author SHA1 Message Date
Pegah Sarram
3dc6dac12d Global filtering (#2434)
* Adds global filtering
* Markup changes to support global filtering
* Adds toggle button
* Added row item spacing for tree__item in mct-tree.vue;
* Significant mods to tree-based properties displays in Inspector:
* Padding in c-object-label tweaked;
* Class naming normalized, legacy classes removed;
* Layout fixed for filters in Browse mode
2019-08-16 10:16:43 -07:00
33 changed files with 653 additions and 1874 deletions

View File

@@ -1,487 +0,0 @@
export default {
'telemetry': {
type: 'folder',
name: 'Maelstrom Telemetry',
type: 'folder',
location: 'ROOT',
composition: [
{
namespace: 'maelstrom',
key: 'velocity'
},{
namespace: 'maelstrom',
key: 'acceleration-ms-2'
},{
namespace: 'maelstrom',
key: 'acceleration-g'
},{
namespace: 'maelstrom',
key: 'distance'
},{
namespace: 'maelstrom',
key: 'distance-m'
},{
namespace: 'maelstrom',
key: 'roll'
},{
namespace: 'maelstrom',
key: 'pitch'
},{
namespace: 'maelstrom',
key: 'yaw'
},{
namespace: 'maelstrom',
key: 'event-index'
},{
namespace: 'maelstrom',
key: 'event-time-str'
},{
namespace: 'maelstrom',
key: 'ring'
},{
namespace: 'maelstrom',
key: 'next-los'
},{
namespace: 'maelstrom',
key: 'evr-1'
},{
namespace: 'maelstrom',
key: 'evr-2'
},{
namespace: 'maelstrom',
key: 'evr-3'
},{
namespace: 'maelstrom',
key: 'evr-4'
},{
namespace: 'maelstrom',
key: 'evr-5'
}
]
},
'velocity': {
type: 'maelstrom-telemetry',
location: 'maelstrom:telemetry',
name: 'Velocity',
telemetry: {
values: [{
"key": "value",
"name": "Velocity",
"units": "ms",
"format": "float",
"source": "velocity",
"hints": {
"range": 1
}
}, {
"key": "utc",
"source": "event_time",
"name": "Time",
"format": "utc-diy",
"hints": {
"domain": 1
}
}
]}
},
'acceleration-ms-2': {
type: 'maelstrom-telemetry',
location: 'maelstrom:telemetry',
name: 'Acceleration (ms^-2)',
telemetry: {
values: [{
"key": "value",
"name": "Acceleration",
"units": "ms^-2",
"format": "float",
"source": "acceleration-ms-2",
"hints": {
"range": 1
}
}, {
"key": "utc",
"source": "event_time",
"name": "Time",
"format": "utc-diy",
"hints": {
"domain": 1
}
}
]}
},
'acceleration-g': {
type: 'maelstrom-telemetry',
location: 'maelstrom:telemetry',
name: 'Acceleration (G)',
telemetry: {
values: [{
"key": "value",
"name": "Acceleration",
"units": "ms^-2",
"format": "float",
"source": "acceleration-g",
"hints": {
"range": 1
}
}, {
"key": "utc",
"source": "event_time",
"name": "Time",
"format": "utc-diy",
"hints": {
"domain": 1
}
}
]}
},
'distance': {
type: 'maelstrom-telemetry',
location: 'maelstrom:telemetry',
name: 'Distance (km)',
telemetry: {
values: [{
"key": "value",
"name": "Distance",
"units": "km",
"format": "float",
"source": "distance-km",
"hints": {
"range": 1
}
}, {
"key": "utc",
"source": "event_time",
"name": "Time",
"format": "utc-diy",
"hints": {
"domain": 1
}
}
]}
},
'distance-m': {
type: 'maelstrom-telemetry',
location: 'maelstrom:telemetry',
name: 'Distance (m)',
telemetry: {
values: [{
"key": "value",
"name": "Distance Meters",
"units": "m",
"format": "float",
"source": "distance-m",
"hints": {
"range": 1
}
}, {
"key": "utc",
"source": "event_time",
"name": "Time",
"format": "utc-diy",
"hints": {
"domain": 1
}
}
]}
},
'roll': {
type: 'maelstrom-telemetry',
location: 'maelstrom:telemetry',
name: 'Roll',
telemetry: {
values: [{
"key": "value",
"name": "Roll",
"units": "degrees",
"format": "float",
"source": "roll",
"hints": {
"range": 1
}
}, {
"key": "utc",
"source": "event_time",
"name": "Time",
"format": "utc-diy",
"hints": {
"domain": 1
}
}
]}
},
'pitch': {
type: 'maelstrom-telemetry',
location: 'maelstrom:telemetry',
name: 'Pitch',
telemetry: {
values: [{
"key": "value",
"name": "Pitch",
"units": "degrees",
"format": "float",
"source": "pitch",
"hints": {
"range": 1
}
}, {
"key": "utc",
"source": "event_time",
"name": "Time",
"format": "utc-diy",
"hints": {
"domain": 1
}
}
]}
},
'yaw': {
type: 'maelstrom-telemetry',
location: 'maelstrom:telemetry',
name: 'Yaw',
telemetry: {
values: [{
"key": "value",
"name": "Yaw",
"units": "degrees",
"format": "float",
"source": "yaw",
"hints": {
"range": 1
}
}, {
"key": "utc",
"source": "event_time",
"name": "Time",
"format": "utc-diy",
"hints": {
"domain": 1
}
}
]}
},
'event-index': {
type: 'maelstrom-telemetry',
location: 'maelstrom:telemetry',
name: 'Event Index',
telemetry: {
values: [{
"key": "value",
"name": "Event Index",
"units": "i",
"format": "float",
"source": "event-index",
"hints": {
"range": 1
}
}, {
"key": "utc",
"source": "event_time",
"name": "Time",
"format": "utc-diy",
"hints": {
"domain": 1
}
}
]}
},
'event-time-str': {
type: 'maelstrom-telemetry',
location: 'maelstrom:telemetry',
name: 'Event Time Str',
telemetry: {
values: [{
"key": "value",
"name": "Event Time Str",
"units": "",
"format": "string",
"source": "event-time-str",
"hints": {
"range": 1
}
}, {
"key": "utc",
"source": "event_time",
"name": "Time",
"format": "utc-diy",
"hints": {
"domain": 1
}
}
]}
},
'ring': {
type: 'maelstrom-telemetry',
location: 'maelstrom:telemetry',
name: 'Ring',
telemetry: {
values: [{
"key": "value",
"name": "Ring",
"units": "",
"format": "int",
"source": "ring",
"hints": {
"range": 24
}
}, {
"key": "utc",
"source": "event_time",
"name": "Time",
"format": "utc-diy",
"hints": {
"domain": 1
}
}
]}
},
'next-los': {
type: 'maelstrom-telemetry',
location: 'maelstrom:telemetry',
name: 'Next LOS',
telemetry: {
values: [{
"key": "value",
"name": "Next LOS",
"units": "",
"format": "string",
"source": "next-los",
"hints": {
"range": 1
}
}, {
"key": "utc",
"source": "event_time",
"name": "Time",
"format": "utc-diy",
"hints": {
"domain": 1
}
}
]}
},
'evr-1': {
type: 'maelstrom-telemetry',
location: 'maelstrom:telemetry',
name: 'EVR 1',
telemetry: {
values: [{
"key": "value",
"name": "EVR-1",
"units": "",
"format": "string",
"source": "evr-1",
"hints": {
"range": 1
}
}, {
"key": "utc",
"source": "event_time",
"name": "Time",
"format": "utc-diy",
"hints": {
"domain": 1
}
}
]}
},
'evr-2': {
type: 'maelstrom-telemetry',
location: 'maelstrom:telemetry',
name: 'EVR 2',
telemetry: {
values: [{
"key": "value",
"name": "EVR-2",
"units": "",
"format": "string",
"source": "evr-2",
"hints": {
"range": 1
}
}, {
"key": "utc",
"source": "event_time",
"name": "Time",
"format": "utc-diy",
"hints": {
"domain": 1
}
}
]}
},
'evr-3': {
type: 'maelstrom-telemetry',
location: 'maelstrom:telemetry',
name: 'EVR 3',
telemetry: {
values: [{
"key": "value",
"name": "EVR-3",
"units": "",
"format": "string",
"source": "evr-3",
"hints": {
"range": 1
}
}, {
"key": "utc",
"source": "event_time",
"name": "Time",
"format": "utc-diy",
"hints": {
"domain": 1
}
}
]}
},
'evr-4': {
type: 'maelstrom-telemetry',
location: 'maelstrom:telemetry',
name: 'EVR 4',
telemetry: {
values: [{
"key": "value",
"name": "EVR-4",
"units": "",
"format": "string",
"source": "evr-4",
"hints": {
"range": 1
}
}, {
"key": "utc",
"source": "event_time",
"name": "Time",
"format": "utc-diy",
"hints": {
"domain": 1
}
}
]}
},
'evr-5': {
type: 'maelstrom-telemetry',
location: 'maelstrom:telemetry',
name: 'EVR 5',
telemetry: {
values: [{
"key": "value",
"name": "EVR-5",
"units": "",
"format": "string",
"source": "evr-5",
"hints": {
"range": 1
}
}, {
"key": "utc",
"source": "event_time",
"name": "Time",
"format": "utc-diy",
"hints": {
"domain": 1
}
}
]}
}
}

File diff suppressed because one or more lines are too long

View File

@@ -45,8 +45,6 @@
].forEach(
openmct.legacyRegistry.enable.bind(openmct.legacyRegistry)
);
openmct.install(openmct.plugins.Bignumbers());
openmct.install(openmct.plugins.Gauge());
openmct.install(openmct.plugins.MyItems());
openmct.install(openmct.plugins.LocalStorage());
openmct.install(openmct.plugins.Generator());

View File

@@ -5,7 +5,7 @@
*
* 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 atw
* 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

View File

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

View File

@@ -1,131 +0,0 @@
<template>
<div class="c-bignumbers">
<svg class="c-bignumbers__int" viewBox="0 0 52 32">
<text textLength=100% lengthAdjust=spacingAndGlyphs x="0" y="32">{{ this.curValInt }}</text>
</svg>
<svg class="c-bignumbers__dec" viewBox="0 0 40 20">
<text textLength=100% lengthAdjust=spacing x="0" y="20">.{{ this.curValDec }}</text>
</svg>
<svg class="c-bignumbers__units" viewBox="0 0 45 11">
<text textLength=100% lengthAdjust=spacingAndGlyphs x="0" y="11">{{ this.units }}</text>
</svg>
</div>
</template>
<style lang="scss">
@import "~styles/sass-base";
.c-bignumbers {
$w1: 53%;
@include abs();
bottom: auto;
padding-bottom: 33%;
&__int,
&__dec,
&__units {
position: absolute;
fill: #fff;
}
&__int,
&__dec {
font-family: $heroFont;
}
&__int {
font-size: 51px;
opacity: 0.8;
width: $w1;
}
&__dec {
left: $w1;
font-size: 32px;
opacity: 0.4;
width: 100% - $w1;
}
&__units {
font-size: 9px;
font-family: $headerFont;
left: $w1;
bottom: 0;
opacity: 0.2;
width: 99% - $w1; // Font has different char spacing, so use reduced width
}
}
</style>
<script>
export default {
name: "bignumbers",
inject: ['openmct', 'domainObject', 'composition'],
data: function () {
let config = this.domainObject.configuration,
units = config.units;
console.log(config);
return {
curValInt: 0,
curValDec: 0,
units: units
}
},
methods: {
getInt: function(val, digits) {
// Extract integer portion of val and zero-pad it if its length < digits
return this.zeroPad(Math.floor(val), digits);
},
getDec: function(val, digits) {
// Extract decimal portion of val to the specified number of digits
return Number.parseFloat(val).toFixed(digits).split('.')[1];
},
zeroPad: function(val, length) {
// Zero pads an integer and returns it as a string
let s = Math.abs(val).toString();
if (s.length < length) {
for (let i = 0; i <= (length - s.length); i++) {
s = '0' + s;
}
}
if (val < 0) {
s = '-' + s;
}
return s;
},
updateValue(datum) {
let cv = this.formats[this.valueKey].format(datum);
this.curValInt = this.getInt(cv, 3);
this.curValDec = this.getDec(cv, 3);
},
subscribe(domainObject) {
this.metadata = this.openmct.telemetry.getMetadata(domainObject);
this.formats = this.openmct.telemetry.getFormatMap(this.metadata);
this.valueKey = this
.metadata
.valuesForHints(['range'])[0].key;
this.unsubscribe = this.openmct
.telemetry
.subscribe(domainObject, this.updateValue.bind(this), {});
this.openmct
.telemetry
.request(domainObject, {strategy: 'latest'})
.then((values) => values.forEach(this.updateValue));
}
},
computed: {
},
mounted() {
this.composition.on('add', this.subscribe);
this.composition.load();
},
destroyed() {
this.composition.off('add', this.subscribe);
this.unsubscribe();
}
}
</script>

View File

@@ -1,58 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
'./bignumbers'
], function (
Bignumbers
) {
return function plugin() {
return function install(openmct) {
openmct.objectViews.addProvider(new Bignumbers(openmct));
openmct.types.addType('bignumbers', {
name: "Big Numbers",
creatable: true,
description: "Display the value of a telemetry element with units in a stylized numeric view.",
cssClass: 'icon-telemetry',
initialize(domainObject) {
domainObject.composition = [];
domainObject.configuration = {
units: ''
};
},
form: [
{
name: "Units",
control: "textfield",
cssClass: "",
key: "units",
property: [
"configuration",
"units"
]
}
]
});
};
};
});

View File

@@ -1,19 +1,20 @@
<template>
<div class="u-contents c-filter-settings">
<li class="grid-row c-filter-settings__setting"
<div class="c-properties__section c-filter-settings">
<li class="c-properties__row c-filter-settings__setting"
v-for="(filter, index) in filterField.filters"
:key="index">
<div class="grid-cell label">
<div class="c-properties__label label"
:disabled="useGlobal">
{{ filterField.name }} =
</div>
<div class="grid-cell value">
<div class="c-properties__value value">
<!-- EDITING -->
<!-- String input, editing -->
<template v-if="!filter.possibleValues && isEditing">
<input class="c-input--flex"
type="text"
placeholder="Enter Value"
:id="`${filter}filterControl`"
:disabled="useGlobal"
:value="persistedValue(filter)"
@change="updateFilterValue($event, filter)">
</template>
@@ -21,15 +22,16 @@
<!-- Checkbox list, editing -->
<template v-if="filter.possibleValues && isEditing">
<div class="c-checkbox-list__row"
v-for="value in filter.possibleValues"
:key="value">
v-for="option in filter.possibleValues"
:key="option.value">
<input class="c-checkbox-list__input"
type="checkbox"
:id="`${value}filterControl`"
@change="onUserSelect($event, filter.comparator, value)"
:checked="isChecked(filter.comparator, value)">
:id="`${option.value}filterControl`"
:disabled="useGlobal"
@change="updateFilterValue($event, filter.comparator, option.value)"
:checked="isChecked(filter.comparator, option.value)">
<span class="c-checkbox-list__value">
{{ value }}
{{ option.label }}
</span>
</div>
</template>
@@ -42,9 +44,8 @@
<!-- Checkbox list, NOT editing -->
<template v-if="filter.possibleValues && !isEditing">
<span
v-if="persistedFilters[filter.comparator]">
{{persistedFilters[filter.comparator].join(', ')}}
<span v-if="persistedFilters[filter.comparator]">
{{ getFilterLabels(filter) }}
</span>
</template>
</div>
@@ -52,26 +53,14 @@
</div>
</template>
<style lang="scss">
@import "~styles/sass-base";
.c-filter-settings {
&__setting {
.grid-cell.label {
white-space: nowrap;
}
}
}
</style>
<script>
export default {
inject: [
'openmct'
],
props: {
filterField: Object,
filterField: Object,
useGlobal: Boolean,
persistedFilters: {
type: Object,
default: () => {
@@ -81,7 +70,6 @@ export default {
},
data() {
return {
expanded: false,
isEditing: this.openmct.editor.isEditing()
}
},
@@ -89,9 +77,6 @@ export default {
toggleIsEditing(isEditing) {
this.isEditing = isEditing;
},
onUserSelect(event, comparator, value){
this.$emit('onUserSelect', this.filterField.key, comparator, value, event.target.checked);
},
isChecked(comparator, value) {
if (this.persistedFilters[comparator] && this.persistedFilters[comparator].includes(value)) {
return true;
@@ -102,8 +87,25 @@ export default {
persistedValue(comparator) {
return this.persistedFilters && this.persistedFilters[comparator];
},
updateFilterValue(event, comparator) {
this.$emit('onTextEnter', this.filterField.key, comparator, event.target.value);
updateFilterValue(event, comparator, value) {
if (value !== undefined) {
this.$emit('filterSelected', this.filterField.key, comparator, value, event.target.checked);
} else {
this.$emit('filterTextValueChanged', this.filterField.key, comparator, event.target.value);
}
},
getFilterLabels(filter) {
return this.persistedFilters[filter.comparator].reduce((accum, filterValue) => {
accum.push(filter.possibleValues.reduce((label, possibleValue) => {
if (filterValue === possibleValue.value) {
label = possibleValue.label;
}
return label;
}, ''));
return accum;
}, []).join(', ');
}
},
mounted() {

View File

@@ -1,10 +1,12 @@
<template>
<li>
<li class="c-tree__item-h">
<div class="c-tree__item menus-to-left"
@click="toggleExpanded">
<div class="c-filter-tree-item__filter-indicator"
:class="{'icon-filter': hasActiveFilters }"></div>
<span class="c-disclosure-triangle is-enabled flex-elem"
:class="{'c-disclosure-triangle--expanded': expanded}"></span>
<div class="c-tree__item__label">
<div class="c-tree__item__label c-object-label">
<div class="c-object-label">
<div class="c-object-label__type-icon"
:class="objectCssClass">
@@ -13,30 +15,47 @@
</div>
</div>
</div>
<ul class="grid-properties" v-if="expanded">
<filter-field
v-for="field in filterObject.valuesWithFilters"
:key="field.key"
:filterField="field"
:persistedFilters="persistedFilters[field.key]"
@onUserSelect="collectUserSelects"
@onTextEnter="updateTextFilter">
</filter-field>
</ul>
<div v-if="expanded">
<ul class="c-properties">
<div class="c-properties__label span-all"
v-if="!isEditing && persistedFilters.useGlobal">
Uses global filter
</div>
<div class="c-properties__label span-all"
v-if="isEditing">
<toggle-switch
:id="keyString"
@change="useGlobalFilter"
:checked="persistedFilters.useGlobal">
</toggle-switch>
Use global filter
</div>
<filter-field
v-if="(!persistedFilters.useGlobal && !isEditing) || isEditing"
v-for="metadatum in filterObject.metadataWithFilters"
:key="metadatum.key"
:filterField="metadatum"
:useGlobal="persistedFilters.useGlobal"
:persistedFilters="updatedFilters[metadatum.key]"
@filterSelected="updateFiltersWithSelectedValue"
@filterTextValueChanged="updateFiltersWithTextValue">
</filter-field>
</ul>
</div>
</li>
</template>
<style lang="scss">
</style>
<script>
import FilterField from './FilterField.vue';
import ToggleSwitch from '../../../ui/components/ToggleSwitch.vue';
export default {
inject: ['openmct'],
components: {
FilterField
FilterField,
ToggleSwitch
},
props: {
filterObject: Object,
@@ -51,58 +70,74 @@ export default {
return {
expanded: false,
objectCssClass: undefined,
updatedFilters: this.persistedFilters
updatedFilters: JSON.parse(JSON.stringify(this.persistedFilters)),
isEditing: this.openmct.editor.isEditing()
}
},
watch: {
persistedFilters: {
handler: function checkFilters(newpersistedFilters) {
this.updatedFilters = JSON.parse(JSON.stringify(newpersistedFilters));
},
deep: true
}
},
computed: {
hasActiveFilters() {
// Should be true when the user has entered any filter values.
return Object.values(this.persistedFilters).some(comparator => {
return (typeof(comparator) === 'object' && !_.isEmpty(comparator));
});
}
},
methods: {
toggleExpanded() {
this.expanded = !this.expanded;
},
collectUserSelects(key, comparator, valueName, value) {
updateFiltersWithSelectedValue(key, comparator, valueName, value) {
let filterValue = this.updatedFilters[key];
if (filterValue && filterValue[comparator]) {
if (value === false) {
let filteredValueName = filterValue[comparator].filter(v => v !== valueName);
if (filteredValueName.length === 0) {
delete this.updatedFilters[key];
} else {
filterValue[comparator] = filteredValueName;
}
} else {
if (filterValue[comparator]) {
if (value === true) {
filterValue[comparator].push(valueName);
} else {
if (filterValue[comparator].length === 1) {
this.$set(this.updatedFilters, key, {});
} else {
filterValue[comparator] = filterValue[comparator].filter(v => v !== valueName);
}
}
} else {
if (!this.updatedFilters[key]) {
this.$set(this.updatedFilters, key, {});
}
this.$set(this.updatedFilters[key], comparator, [value ? valueName : undefined]);
this.$set(this.updatedFilters[key], comparator, [valueName]);
}
this.$emit('updateFilters', this.keyString, this.updatedFilters);
},
updateTextFilter(key, comparator, value) {
updateFiltersWithTextValue(key, comparator, value) {
if (value.trim() === '') {
if (this.updatedFilters[key]) {
delete this.updatedFilters[key];
this.$emit('updateFilters', this.keyString, this.updatedFilters);
}
return;
this.$set(this.updatedFilters, key, {});
} else {
this.$set(this.updatedFilters[key], comparator, value);
}
if (!this.updatedFilters[key]) {
this.$set(this.updatedFilters, key, {});
this.$set(this.updatedFilters[key], comparator, '');
}
this.updatedFilters[key][comparator] = value;
this.$emit('updateFilters', this.keyString, this.updatedFilters);
}
},
useGlobalFilter(checked) {
this.updatedFilters.useGlobal = checked;
this.$emit('updateFilters', this.keyString, this.updatedFilters, checked);
},
toggleIsEditing(isEditing) {
this.isEditing = isEditing;
},
},
mounted() {
let type = this.openmct.types.get(this.filterObject.domainObject.type) || {};
this.keyString = this.openmct.objects.makeKeyString(this.filterObject.domainObject.identifier);
this.objectCssClass = type.definition.cssClass;
this.openmct.editor.on('isEditing', this.toggleIsEditing);
},
beforeDestroy() {
this.openmct.editor.off('isEditing', this.toggleIsEditing);
}
}
</script>

View File

@@ -1,6 +1,14 @@
<template>
<ul class="tree c-tree c-properties__section" v-if="Object.keys(children).length">
<h2 class="c-properties__header">Filters</h2>
<ul class="c-tree c-filter-tree" v-if="Object.keys(children).length">
<h2>Data Filters</h2>
<div class="c-filter-indication"
v-if="hasActiveFilters">{{ label }}
</div>
<global-filters
:globalFilters="globalFilters"
:globalMetadata="globalMetadata"
@persistGlobalFilters="persistGlobalFilters">
</global-filters>
<filter-object
v-for="(child, key) in children"
:key="key"
@@ -12,80 +20,230 @@
</template>
<style lang="scss">
@import "~styles/sass-base";
.c-inspector {
.c-filter-indication {
border-radius: $smallCr;
font-size: inherit;
padding: $interiorMarginSm $interiorMargin;
text-transform: inherit;
}
.c-filter-tree {
// Filters UI uses a tree-based structure
.c-properties {
// Add extra margin to account for filter-indicator
margin-left: 38px;
}
}
}
</style>
<script>
import FilterObject from './FilterObject.vue';
import FilterObject from './FilterObject.vue';
import GlobalFilters from './GlobalFilters.vue'
export default {
components: {
FilterObject
},
inject: [
'openmct'
],
data() {
let providedObject = this.openmct.selection.get()[0][0].context.item;
let persistedFilters = {};
const FILTER_VIEW_TITLE = 'Filters applied';
const FILTER_VIEW_TITLE_MIXED = 'Mixed filters applied';
const USE_GLOBAL = 'useGlobal';
if (providedObject.configuration && providedObject.configuration.filters) {
persistedFilters = providedObject.configuration.filters;
}
export default {
components: {
FilterObject,
GlobalFilters
},
inject: [
'openmct'
],
data() {
let providedObject = this.openmct.selection.get()[0][0].context.item;
let configuration = providedObject.configuration;
return {
providedObject,
persistedFilters,
children: {}
}
},
methods: {
addChildren(child) {
let keyString = this.openmct.objects.makeKeyString(child.identifier),
metadata = this.openmct.telemetry.getMetadata(child),
valuesWithFilters = metadata.valueMetadatas.filter((value) => value.filters),
childObject = {
name: child.name,
domainObject: child,
valuesWithFilters
};
if (childObject.valuesWithFilters.length) {
this.$set(this.children, keyString, childObject);
} else {
return;
return {
persistedFilters: (configuration && configuration.filters) || {},
globalFilters: (configuration && configuration.globalFilters) || {},
globalMetadata: {},
providedObject,
children: {}
}
},
removeChildren(identifier) {
let keyString = this.openmct.objects.makeKeyString(identifier);
this.$delete(this.children, keyString);
delete this.persistedFilters[keyString];
this.mutateConfigurationFilters();
computed: {
hasActiveFilters() {
// Should be true when the user has entered any filter values.
return Object.values(this.persistedFilters).some(filters => {
return Object.values(filters).some(comparator => {
return (typeof(comparator) === 'object' && !_.isEmpty(comparator));
});
});
},
hasMixedFilters() {
// Should be true when filter values are mixed.
let filtersToCompare = _.omit(this.persistedFilters[Object.keys(this.persistedFilters)[0]], [USE_GLOBAL]);
return Object.values(this.persistedFilters).some(filters => {
return !_.isEqual(filtersToCompare, _.omit(filters, [USE_GLOBAL]));
});
},
label() {
if (this.hasActiveFilters) {
if (this.hasMixedFilters) {
return FILTER_VIEW_TITLE_MIXED;
} else {
return FILTER_VIEW_TITLE;
}
}
}
},
persistFilters(keyString, userSelects) {
this.persistedFilters[keyString] = userSelects;
this.mutateConfigurationFilters();
methods: {
addChildren(domainObject) {
let keyString = this.openmct.objects.makeKeyString(domainObject.identifier);
let metadata = this.openmct.telemetry.getMetadata(domainObject);
let metadataWithFilters = metadata.valueMetadatas.filter(value => value.filters);
let hasFiltersWithKeyString = this.persistedFilters[keyString] !== undefined;
let mutateFilters = false;
let childObject = {
name: domainObject.name,
domainObject: domainObject,
metadataWithFilters
};
if (metadataWithFilters.length) {
this.$set(this.children, keyString, childObject);
metadataWithFilters.forEach(metadatum => {
if (!this.globalFilters[metadatum.key]) {
this.$set(this.globalFilters, metadatum.key, {});
}
if (!this.globalMetadata[metadatum.key]) {
this.$set(this.globalMetadata, metadatum.key, metadatum);
}
if (!hasFiltersWithKeyString) {
if (!this.persistedFilters[keyString]) {
this.$set(this.persistedFilters, keyString, {});
this.$set(this.persistedFilters[keyString], 'useGlobal', true);
mutateFilters = true;
}
this.$set(this.persistedFilters[keyString], metadatum.key, this.globalFilters[metadatum.key]);
}
});
}
if (mutateFilters) {
this.mutateConfigurationFilters();
}
},
removeChildren(identifier) {
let keyString = this.openmct.objects.makeKeyString(identifier);
let globalFiltersToRemove = this.getGlobalFiltersToRemove(keyString);
if (globalFiltersToRemove.length > 0) {
globalFiltersToRemove.forEach(key => {
this.$delete(this.globalFilters, key);
this.$delete(this.globalMetadata, key);
});
this.mutateConfigurationGlobalFilters();
}
this.$delete(this.children, keyString);
this.$delete(this.persistedFilters, keyString);
this.mutateConfigurationFilters();
},
getGlobalFiltersToRemove(keyString) {
let filtersToRemove = new Set();
this.children[keyString].metadataWithFilters.forEach(metadatum => {
let keepFilter = false
Object.keys(this.children).forEach(childKeyString => {
if (childKeyString !== keyString) {
let filterMatched = this.children[childKeyString].metadataWithFilters.some(childMetadatum => childMetadatum.key === metadatum.key);
if (filterMatched) {
keepFilter = true;
return;
}
}
});
if (!keepFilter) {
filtersToRemove.add(metadatum.key);
}
});
return Array.from(filtersToRemove);
},
persistFilters(keyString, updatedFilters, useGlobalValues) {
this.persistedFilters[keyString] = updatedFilters;
if (useGlobalValues) {
Object.keys(this.persistedFilters[keyString]).forEach(key => {
if (typeof(this.persistedFilters[keyString][key]) === 'object') {
this.persistedFilters[keyString][key] = this.globalFilters[key];
}
});
}
this.mutateConfigurationFilters();
},
updatePersistedFilters(filters) {
this.persistedFilters = filters;
},
persistGlobalFilters(key, filters) {
this.globalFilters[key] = filters[key];
this.mutateConfigurationGlobalFilters();
let mutateFilters = false;
Object.keys(this.children).forEach(keyString => {
if (this.persistedFilters[keyString].useGlobal !== false && this.containsField(keyString, key)) {
if (!this.persistedFilters[keyString][key]) {
this.$set(this.persistedFilters[keyString], key, {});
}
this.$set(this.persistedFilters[keyString], key, filters[key]);
mutateFilters = true;
}
});
if (mutateFilters) {
this.mutateConfigurationFilters();
}
},
updateGlobalFilters(filters) {
this.globalFilters = filters;
},
containsField(keyString, field) {
let hasField = false;
this.children[keyString].metadataWithFilters.forEach(metadatum => {
if (metadatum.key === field) {
hasField = true;
return;
}
});
return hasField;
},
mutateConfigurationFilters() {
this.openmct.objects.mutate(this.providedObject, 'configuration.filters', this.persistedFilters);
},
mutateConfigurationGlobalFilters() {
this.openmct.objects.mutate(this.providedObject, 'configuration.globalFilters', this.globalFilters);
}
},
updatePersistedFilters(filters) {
this.persistedFilters = filters;
mounted(){
this.composition = this.openmct.composition.get(this.providedObject);
this.composition.on('add', this.addChildren);
this.composition.on('remove', this.removeChildren);
this.composition.load();
this.unobserve = this.openmct.objects.observe(this.providedObject, 'configuration.filters', this.updatePersistedFilters);
this.unobserveGlobalFilters = this.openmct.objects.observe(this.providedObject, 'configuration.globalFilters', this.updateGlobalFilters);
this.unobserveAllMutation = this.openmct.objects.observe(this.providedObject, '*', (mutatedObject) => this.providedObject = mutatedObject);
},
mutateConfigurationFilters() {
this.openmct.objects.mutate(this.providedObject, 'configuration.filters', this.persistedFilters);
beforeDestroy() {
this.composition.off('add', this.addChildren);
this.composition.off('remove', this.removeChildren);
this.unobserve();
this.unobserveGlobalFilters();
this.unobserveAllMutation();
}
},
mounted(){
this.composition = this.openmct.composition.get(this.providedObject);
this.composition.on('add', this.addChildren);
this.composition.on('remove', this.removeChildren);
this.composition.load();
this.unobserve = this.openmct.objects.observe(this.providedObject, 'configuration.filters', this.updatePersistedFilters);
this.unobserveAllMutation = this.openmct.objects.observe(this.providedObject, '*', (mutatedObject) => this.providedObject = mutatedObject);
},
beforeDestroy() {
this.composition.off('add', this.addChildren);
this.composition.off('remove', this.removeChildren);
this.unobserve();
this.unobserveAllMutation();
}
}
</script>

View File

@@ -0,0 +1,134 @@
<template>
<li class="c-tree__item-h">
<div class="c-tree__item menus-to-left"
@click="toggleExpanded">
<div class="c-filter-tree-item__filter-indicator"
:class="{'icon-filter': hasActiveGlobalFilters }"></div>
<span class="c-disclosure-triangle is-enabled flex-elem"
:class="{'c-disclosure-triangle--expanded': expanded}"></span>
<div class="c-tree__item__label c-object-label">
<div class="c-object-label">
<div class="c-object-label__type-icon icon-gear"></div>
<div class="c-object-label__name flex-elem grows">Global Filtering</div>
</div>
</div>
</div>
<ul class="c-properties" v-if="expanded">
<filter-field
v-for="metadatum in globalMetadata"
:key="metadatum.key"
:filterField="metadatum"
:persistedFilters="updatedFilters[metadatum.key]"
@filterSelected="updateFiltersWithSelectedValue"
@filterTextValueChanged="updateFiltersWithTextValue">
</filter-field>
</ul>
</li>
</template>
<style lang="scss">
@import "~styles/sass-base";
.c-filter-indication {
// Appears as a block element beneath tables
@include userSelectNone();
background: $colorFilterBg;
color: $colorFilterFg;
display: flex;
align-items: center;
font-size: 0.9em;
margin-top: $interiorMarginSm;
padding: 2px;
text-transform: uppercase;
&:before {
font-family: symbolsfont-12px;
content: $glyph-icon-filter;
display: block;
font-size: 12px;
margin-right: $interiorMarginSm;
}
}
.c-filter-tree-item {
&__filter-indicator {
color: $colorFilter;
width: 1.2em; // Set width explicitly for layout reasons: will either have class icon-filter, or none.
}
}
</style>
<script>
import FilterField from './FilterField.vue';
export default {
inject: ['openmct'],
components: {
FilterField
},
props: {
globalMetadata: Object,
globalFilters: {
type: Object,
default: () => {
return {};
}
}
},
data() {
return {
expanded: false,
updatedFilters: JSON.parse(JSON.stringify(this.globalFilters))
}
},
computed: {
hasActiveGlobalFilters() {
return Object.values(this.globalFilters).some(field => {
return Object.values(field).some(comparator => {
return (comparator && (comparator !== '' || comparator.length > 0));
});
});
}
},
watch: {
globalFilters: {
handler: function checkFilters(newGlobalFilters) {
this.updatedFilters = JSON.parse(JSON.stringify(newGlobalFilters));
},
deep: true
}
},
methods: {
toggleExpanded() {
this.expanded = !this.expanded;
},
updateFiltersWithSelectedValue(key, comparator, valueName, value) {
let filterValue = this.updatedFilters[key];
if (filterValue[comparator]) {
if (value === true) {
filterValue[comparator].push(valueName);
} else {
if (filterValue[comparator].length === 1) {
this.$set(this.updatedFilters, key, {});
} else {
filterValue[comparator] = filterValue[comparator].filter(v => v !== valueName);
}
}
} else {
this.$set(this.updatedFilters[key], comparator, [valueName]);
}
this.$emit('persistGlobalFilters', key, this.updatedFilters);
},
updateFiltersWithTextValue(key, comparator, value) {
if (value.trim() === '') {
this.$set(this.updatedFilters, key, {});
} else {
this.$set(this.updatedFilters[key], comparator, value);
}
this.$emit('persistGlobalFilters', key, this.updatedFilters);
}
}
}
</script>

View File

@@ -21,7 +21,7 @@
*****************************************************************************/
define([
'./filtersInspectorViewProvider'
'./FiltersInspectorViewProvider'
], function (
FiltersInspectorViewProvider
) {

View File

@@ -1,202 +0,0 @@
<template>
<div class="c-gauge">
<div class="c-gauge__wrapper">
<svg class="c-gauge__range" viewBox="0 0 512 512">
<text class="c-gauge__curval" transform="translate(256 310)" text-anchor="middle">{{ this.curVal }}</text>
<text font-size="35" transform="translate(105 455) rotate(-45)"
v-if="displayMinMax">{{ this.rangeLow }}</text>
<text font-size="35" transform="translate(407 455) rotate(45)" text-anchor="end"
v-if="displayMinMax">{{ this.rangeHigh }}</text>
</svg>
<div class="c-dial">
<svg class="c-dial__bg" viewBox="0 0 512 512">
<g>
<path d="M256,0C114.6,0,0,114.6,0,256S114.6,512,256,512,512,397.4,512,256,397.4,0,256,0Zm0,412A156,156,0,1,1,412,256,155.9,155.9,0,0,1,256,412Z"/>
</g>
</svg>
<svg class="c-dial__limit" viewBox="0 0 512 512"
v-if="degLimit < 270"
:class="{
'c-limit-clip--90': this.degLimit > 90,
'c-limit-clip--180': this.degLimit >= 180
}">
<path d="M100,256A156,156,0,1,1,366.3,366.3L437,437a255.2,255.2,0,0,0,75-181C512,114.6,397.4,0,256,0S0,114.6,0,256A255.2,255.2,0,0,0,75,437l70.7-70.7A155.5,155.5,0,0,1,100,256Z"
:style="`transform: rotate(${this.degLimit}deg)`"/>
</svg>
<svg class="c-dial__value" viewBox="0 0 512 512"
v-if="this.degValue > 0"
:class="{
'c-dial-clip--90': this.degValue < 90,
'c-dial-clip--180': this.degValue >= 90 && this.degValue < 180
}">
<path d="M256,31A224.3,224.3,0,0,0,98.3,95.5l48.4,49.2a156,156,0,1,1-1,221.6L96.9,415.1A224.4,224.4,0,0,0,256,481c124.3,0,225-100.7,225-225S380.3,31,256,31Z"
:style="`transform: rotate(${this.degValue}deg)`"/>
</svg>
</div>
</div>
</div>
</template>
<style lang="scss">
@import "~styles/sass-base";
.c-gauge {
@include abs();
overflow: hidden;
&__wrapper {
position: absolute;
width: 100%;
padding-bottom: 100%;
overflow: hidden;
}
&__value {
position: absolute;
top: 50%; left: 50%;
transform: translate(-50%, -50%);
font-size: 3vw;
}
&__range {
$o: 21%;
position: absolute;
fill: rgba(#fff, 0.8);
}
&__curval {
font-family: $heroFont;
font-size: 170px;
}
}
.c-dial {
// Dial elements
@include abs();
clip-path: polygon(0 0, 100% 0, 100% 100%, 50% 50%, 0 100%);
svg,
&__ticks,
&__bg,
&__limit,
&__value {
@include abs();
}
svg {
path {
transform-origin: center;
}
}
&__limit {
&.c-limit-clip--90 {
clip-path: polygon(0 0, 100% 0, 100% 100%);
}
&.c-limit-clip--180 {
clip-path: polygon(100% 0, 100% 100%, 0 100%);
}
path {
fill: rgba(orange, 0.4);
}
}
&__value {
&.c-dial-clip--90 {
clip-path: polygon(0 0, 50% 50%, 0 100%);
}
&.c-dial-clip--180 {
clip-path: polygon(0 0, 100% 0, 0 100%);
}
path {
fill: rgba(#fff, 0.8);
}
}
&__bg {
g {
fill: rgba(#fff, 0.1);
}
}
}
</style>
<script>
export default {
name: "gaugeRadial",
inject: ['openmct', 'domainObject', 'composition'],
data: function () {
let config = this.domainObject.configuration,
rangeLow = config.min,
rangeHigh = config.max,
displayMinMax = config.displayMinMax,
limit = config.limit,
decimals = config.decimals;
return {
rangeLow,
rangeHigh,
displayMinMax: displayMinMax.indexOf('Yes') !== -1,
limit1: limit,
decimals,
curVal: 0
}
},
methods: {
round: function(val, decimals) {
let precision = Math.pow(10, decimals);
return Math.round(val * precision)/precision;
},
valToPercent: function(vValue) {
// Don't let the current value exceed the high range, or the dial won't display right
if (vValue >= this.rangeHigh) { return 100; }
return ((vValue - this.rangeLow) / (this.rangeHigh - this.rangeLow)) * 100;
},
percentToDegrees: function(vPercent) {
return this.round((vPercent/100)*270, 2);
},
updateValue(datum) {
this.curVal = this.round(this.formats[this.valueKey].format(datum), this.decimals);
},
subscribe(domainObject) {
this.metadata = this.openmct.telemetry.getMetadata(domainObject);
this.formats = this.openmct.telemetry.getFormatMap(this.metadata);
this.valueKey = this
.metadata
.valuesForHints(['range'])[0].key;
this.unsubscribe = this.openmct
.telemetry
.subscribe(domainObject, this.updateValue.bind(this), {});
this.openmct
.telemetry
.request(domainObject, {strategy: 'latest'})
.then((values) => values.forEach(this.updateValue));
}
},
computed: {
degValue: function() {
return this.percentToDegrees(this.valToPercent(this.curVal));
},
degLimit: function() {
return this.percentToDegrees(this.valToPercent(this.limit1));
}
},
mounted() {
this.composition.on('add', this.subscribe);
this.composition.load();
},
destroyed() {
this.composition.off('add', this.subscribe);
this.unsubscribe();
}
}
</script>

View File

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

View File

@@ -1,102 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
'./gauge'
], function (
Gauge
) {
return function plugin() {
return function install(openmct) {
openmct.objectViews.addProvider(new Gauge(openmct));
openmct.types.addType('gauge', {
name: "Gauge",
creatable: true,
description: "Graphically visualize a telemetry element's current value between a minimum and maximum.",
cssClass: 'icon-gauge',
initialize(domainObject) {
domainObject.composition = [];
domainObject.configuration = {
min: 0,
max: 100,
displayMinMax: 'Yes',
limit: 90,
decimals: 1
};
},
form: [
{
name: "Minimum Value",
control: "numberfield",
cssClass: "l-input-sm l-numeric",
key: "min",
property: [
"configuration",
"min"
]
},
{
name: "Maximum Value",
control: "numberfield",
cssClass: "l-input-sm l-numeric",
key: "max",
property: [
"configuration",
"max"
]
},
{
name: "Display Min/Max",
control: "textfield",
cssClass: "l-input-sm",
key: "displayMinMax",
property: [
"configuration",
"displayMinMax"
]
},
{
name: "Limit",
control: "numberfield",
cssClass: "l-input-sm l-numeric",
key: "min",
property: [
"configuration",
"limit"
]
},
{
name: "Decimals",
control: "numberfield",
cssClass: "l-input-sm l-numeric",
key: "decimals",
property: [
"configuration",
"decimals"
]
}
]
});
};
};
});

View File

@@ -44,9 +44,7 @@ define([
'./filters/plugin',
'./objectMigration/plugin',
'./goToOriginalAction/plugin',
'./clearData/plugin',
'./gauge/plugin',
'./bignumbers/plugin'
'./clearData/plugin'
], function (
_,
UTCTimeSystem,
@@ -71,9 +69,7 @@ define([
Filters,
ObjectMigration,
GoToOriginalAction,
ClearData,
Gauge,
Bignumbers
ClearData
) {
var bundleMap = {
LocalStorage: 'platform/persistence/local',
@@ -105,6 +101,7 @@ define([
* to exclusively.
*/
plugins.AutoflowView = AutoflowPlugin;
plugins.Conductor = TimeConductorPlugin.default;
plugins.CouchDB = function (url) {
@@ -172,8 +169,6 @@ define([
plugins.ObjectMigration = ObjectMigration.default;
plugins.GoToOriginalAction = GoToOriginalAction.default;
plugins.ClearData = ClearData;
plugins.Gauge = Gauge;
plugins.Bignumbers = Bignumbers;
return plugins;
});

View File

@@ -2,7 +2,7 @@
<div v-if="filterNames.length > 0"
:title=title
class="c-filter-indication"
:class="{ 'c-filter-indication--mixed': mixed }">
:class="{ 'c-filter-indication--mixed': hasMixedFilters }">
<span class="c-filter-indication__mixed">{{ label }}</span>
<span v-for="(name, index) in filterNames"
class="c-filter-indication__label">
@@ -33,7 +33,6 @@
}
&__mixed {
font-weight: bold;
margin-right: $interiorMarginSm;
}
@@ -58,119 +57,109 @@
const FILTER_INDICATOR_LABEL_MIXED = 'Mixed Filters:';
const FILTER_INDICATOR_TITLE = 'Data filters are being applied to this view.';
const FILTER_INDICATOR_TITLE_MIXED = 'A mix of data filter values are being applied to this view.';
const USE_GLOBAL = 'useGlobal';
export default {
inject: ['openmct', 'table'],
data() {
return {
filterNames: [],
filteredTelemetry: {},
mixed: false,
label: '',
title: ''
filteredTelemetry: {}
}
},
methods: {
isTelemetryObject(domainObject) {
return domainObject.hasOwnProperty('telemetry');
computed: {
hasMixedFilters() {
let filtersToCompare = _.omit(this.filteredTelemetry[Object.keys(this.filteredTelemetry)[0]], [USE_GLOBAL]);
return Object.values(this.filteredTelemetry).some(filters => {
return !_.isEqual(filtersToCompare, _.omit(filters, [USE_GLOBAL]));
});
},
label() {
if (this.hasMixedFilters) {
return FILTER_INDICATOR_LABEL_MIXED;
} else {
return FILTER_INDICATOR_LABEL;
}
},
title() {
if (this.hasMixedFilters) {
return FILTER_INDICATOR_TITLE_MIXED;
} else {
return FILTER_INDICATOR_TITLE;
}
}
},
methods: {
setFilterNames() {
let names = [];
let composition = this.openmct.composition.get(this.table.configuration.domainObject);
this.composition && this.composition.load().then((domainObjects) => {
composition && composition.load().then((domainObjects) => {
domainObjects.forEach(telemetryObject => {
let keyString= this.openmct.objects.makeKeyString(telemetryObject.identifier);
let metadataValues = this.openmct.telemetry.getMetadata(telemetryObject).values();
let filters = this.filteredTelemetry[keyString];
this.telemetryKeyStrings.add(keyString);
if (filters !== undefined) {
let metadataValues = this.openmct.telemetry.getMetadata(telemetryObject).values();
Object.keys(filters).forEach(key => {
metadataValues.forEach(metadaum => {
if (key === metadaum.key) {
names.push(metadaum.name);
}
});
});
names.push(this.getFilterNamesFromMetadata(filters, metadataValues));
}
});
this.filterNames = Array.from(new Set(names));
names = _.flatten(names);
this.filterNames = names.length === 0 ? names : Array.from(new Set(names));
});
},
getFilterNamesFromMetadata(filters, metadataValues) {
let filterNames = [];
filters = _.omit(filters, [USE_GLOBAL]);
Object.keys(filters).forEach(key => {
if (!_.isEmpty(filters[key])) {
metadataValues.forEach(metadatum => {
if (key === metadatum.key) {
if (typeof metadatum.filters[0] === "object") {
filterNames.push(this.getFilterLabels(filters[key], metadatum));
} else {
filterNames.push(metadatum.name);
}
}
});
}
});
return _.flatten(filterNames);
},
getFilterLabels(filterObject, metadatum, ) {
let filterLabels = [];
Object.values(filterObject).forEach(comparator => {
comparator.forEach(filterValue => {
metadatum.filters[0].possibleValues.forEach(option => {
if (option.value === filterValue) {
filterLabels.push(option.label);
}
});
});
});
return filterLabels;
},
handleConfigurationChanges(configuration) {
if (!_.eq(this.filteredTelemetry, configuration.filters)) {
this.updateFilters(configuration.filters || {});
}
},
checkFiltersForMixedValues() {
let valueToCompare = this.filteredTelemetry[Object.keys(this.filteredTelemetry)[0]];
let mixed = false;
Object.values(this.filteredTelemetry).forEach(value => {
if (!_.isEqual(valueToCompare, value)) {
mixed = true;
return;
}
});
// If the filtered telemetry is not mixed at this point, check the number of available objects
// with the number of filtered telemetry. If they are not equal, the filters must be mixed.
if (mixed === false && _.size(this.filteredTelemetry) !== this.telemetryKeyStrings.size) {
mixed = true;
}
this.mixed = mixed;
},
setLabels() {
if (this.mixed) {
this.label = FILTER_INDICATOR_LABEL_MIXED;
this.title = FILTER_INDICATOR_TITLE_MIXED;
} else {
this.label = FILTER_INDICATOR_LABEL;
this.title = FILTER_INDICATOR_TITLE;
}
},
updateFilters(filters) {
this.filteredTelemetry = JSON.parse(JSON.stringify(filters));
this.setFilterNames();
this.updateIndicatorLabel();
},
addChildren(child) {
let keyString = this.openmct.objects.makeKeyString(child.identifier);
this.telemetryKeyStrings.add(keyString);
this.updateIndicatorLabel();
},
removeChildren(identifier) {
let keyString = this.openmct.objects.makeKeyString(identifier);
this.telemetryKeyStrings.delete(keyString);
this.updateIndicatorLabel();
},
updateIndicatorLabel() {
this.checkFiltersForMixedValues();
this.setLabels();
}
},
mounted() {
let filters = this.table.configuration.getConfiguration().filters || {};
this.telemetryKeyStrings = new Set();
this.composition = this.openmct.composition.get(this.table.configuration.domainObject);
if (this.composition) {
this.composition.on('add', this.addChildren);
this.composition.on('remove', this.removeChildren);
}
this.table.configuration.on('change', this.handleConfigurationChanges);
this.updateFilters(filters);
},
destroyed() {
this.table.configuration.off('change', this.handleConfigurationChanges);
if (this.composition) {
this.composition.off('add', this.addChildren);
this.composition.off('remove', this.removeChildren);
}
}
}
</script>

View File

@@ -4,14 +4,14 @@
<div class="c-properties__header">Table Column Size</div>
<ul class="c-properties__section">
<li class="c-properties__row">
<div class="c-properties__label" title="Show or Hide Column"><label for="AutoSizeControl">Auto-size</label></div>
<div class="c-properties__label" title="Auto-size table"><label for="AutoSizeControl">Auto-size</label></div>
<div class="c-properties__value"><input type="checkbox" id="AutoSizeControl" :checked="configuration.autosize !== false" @change="toggleAutosize()"></div>
</li>
</ul>
<div class="c-properties__header">Table Column Visibility</div>
<ul class="c-properties__section">
<li class="c-properties__row" v-for="(title, key) in headers">
<div class="c-properties__label" title="Show or Hide Column"><label :for="key + 'ColumnControl'">{{title}}</label></div>
<div class="c-properties__label" title="Show or hide column"><label :for="key + 'ColumnControl'">{{title}}</label></div>
<div class="c-properties__value"><input type="checkbox" :id="key + 'ColumnControl'" :checked="configuration.hiddenColumns[key] !== true" @change="toggleColumn(key)"></div>
</li>
</ul>

View File

@@ -116,6 +116,7 @@ $colorOk: #33cc33;
$colorOkFg: #fff;
$colorFilterBg: #44449c;
$colorFilterFg: #8984e9;
$colorFilter: $colorFilterFg; // Standalone against $colorBodyBg
// States
$colorPausedBg: #ff9900;

View File

@@ -36,10 +36,9 @@ $bodyFont: 'Chakra Petch', sans-serif;
@mixin headerFont($size: 1em) {
font-family: $headerFont;
font-size: $size;
line-height: $size;
font-size: $size * 0.8; // This font is comparatively large, so reduce it a bit
text-transform: uppercase;
word-spacing: 0.4em;
word-spacing: 0.25em;
}
@mixin bodyFont($size: 1em) {
@@ -69,15 +68,12 @@ $shdwBtns: rgba(black, 0.2) 0 1px 2px;
$shdwBtnsOverlay: rgba(black, 0.5) 0 1px 5px;
// Base colors
$colorBodyBg: #000000;
$colorBodyBg: #393939;
$colorBodyFg: #ccc;
$colorBodyFgEm: #fff;
$colorGenBg: #222;
$colorHeadBg: transparent;
$colorHeadBg: #262626;
$colorHeadFg: $colorBodyFg;
$colorStatusBarBg: $colorHeadBg;
$colorStatusBarFg: rgba(white, 0.5);
$colorStatusBarFgHov: #aaa;
$colorKey: #0099cc;
$colorKeyFg: #fff;
$colorKeyHov: #26d8ff;
@@ -124,6 +120,7 @@ $colorOk: #33cc33;
$colorOkFg: #fff;
$colorFilterBg: #44449c;
$colorFilterFg: #8984e9;
$colorFilter: $colorFilterFg; // Standalone against $colorBodyBg
// States
$colorPausedBg: #ff9900;
@@ -189,7 +186,7 @@ $colorIconAliasForKeyFilter: #aaa;
$colorTabsHolderBg: rgba(black, 0.2);
// Buttons and Controls
$colorBtnBg: pullForward($colorBodyBg, 30%);
$colorBtnBg: pullForward($colorBodyBg, 10%);
$colorBtnBgHov: pullForward($colorBtnBg, 10%);
$colorBtnFg: pullForward($colorBodyFg, 10%);
$colorBtnReverseFg: pullForward($colorBtnFg, 10%);

View File

@@ -42,31 +42,6 @@ $bodyFont: $heroFont;
font-size: $size;
}
// FONTS
@import url('https://fonts.googleapis.com/css?family=Chakra+Petch:400,600,700|Michroma|Teko:400,700');
$heroFont: "Helvetica Neue", Helvetica, Arial, sans-serif;
$headerFont: $heroFont;
$bodyFont: $heroFont;
@mixin heroFont($size: 1em) {
$mult: 1;
font-family: $heroFont;
font-size: $size * $mult;
}
@mixin headerFont($size: 1em) {
$mult: 1;
font-family: $headerFont;
font-size: $size * $mult;
}
@mixin bodyFont($size: 1em) {
$mult: 1;
font-family: $bodyFont;
font-size: $size * $mult;
}
// Functions
@function buttonBg($c: $colorBtnBg) {
@return $c;
@@ -141,6 +116,7 @@ $colorOk: #33cc33;
$colorOkFg: #fff;
$colorFilterBg: #a29fe2;
$colorFilterFg: #fff;
$colorFilter: $colorFilterBg; // Standalone against $colorBodyBg
// States
$colorPausedBg: #ff9900;

View File

@@ -93,7 +93,7 @@ $mobileMenuIconD: 24px; // Used
$mobileTreeItemH: 35px; // Used
/************************** VISUAL */
$controlDisabledOpacity: 0.3;
$controlDisabledOpacity: 0.5;
/************************** UI ELEMENTS */
/*************** Progress Bar */

View File

@@ -202,6 +202,11 @@ body.desktop .has-local-controls {
}
}
::placeholder {
opacity: 0.5;
font-style: italic;
}
/******************************************************** STATES */
@mixin spinner($b: 5px, $c: $colorKey) {
animation-name: rotation-centered;

View File

@@ -50,14 +50,14 @@
}
/************************** EFFECTS */
@mixin pulse($animName: 'pulse', $dur: 500ms, $iteration: infinite, $prop: opacity, $val0: 0, $val100: 1, $direction: alternate) {
@keyframes #{$animName} {
0% { #{$prop}: $val0; }
100% { #{$prop}: $val100; }
@mixin pulse($animName: pulse, $dur: 500ms, $iteration: infinite, $opacity0: 0.5, $opacity100: 1) {
@keyframes pulse {
0% { opacity: $opacity0; }
100% { opacity: $opacity100; }
}
animation-name: $animName;
animation-duration: $dur;
animation-direction: $direction;
animation-direction: alternate;
animation-iteration-count: $iteration;
animation-timing-function: ease-in-out;
}

View File

@@ -1,447 +0,0 @@
/**************************************************** CONSTANTS, MIXINS */
$bgKey: #222632;
$redKeyBg: #990000;
$redKeyBrdr: #ff0000;
$redKeyFg: rgba(white, 0.8);
$ylwKeyBg: #cc6b36;
$ylwKeyBrdr: #ffbf1a;
$ylwKeyFg: rgba(white, 0.8);
@mixin widgetOk() {
background-color: #bbb !important;
border-color: #fff !important;
color: #333 !important;
}
@mixin widgetRed() {
background-color: $redKeyBg !important;
border-color: $redKeyBrdr !important;
color: $redKeyFg !important;
}
@mixin widgetYellow() {
$c: $ylwKeyFg;
background-color: $ylwKeyBg !important;
border-color: $ylwKeyBrdr !important;
color: $ylwKeyFg !important;
}
/**************************************************** OVERRIDES */
body {
font-size: 1em;
}
.c-telemetry-view__value {
justify-content: center;
&[class*='is-limit'] {
background: transparent !important;
color: inherit !important;
&:before { display: none; }
}
}
/**************************************************** CONVENIENCE */
.u-inspectable[s-selected] {
border: none !important;
}
.c-table.c-telemetry-table {
font-size: 0.7em;
}
.widget-rule-content .t-rule-message-input {
width: 100%;
}
/**************************************************** TIME CONDUCTOR */
.c-conductor {
&__controls {
display: none !important;
}
&:hover {
.c-conductor__controls { display: flex !important; }
}
[class*='__label'] {
display: none;
}
&__end-fixed {
display: none;
}
&__end-delta {
&:after {
content: '';
display: block;
background: url('../ui/layout/assets/images/logo-app.svg') center no-repeat;
background-size: contain;
width: 100px;
height: 24px;
}
}
&-input {
input {
&.c-input--datetime,
&.c-input--hrs-min-sec {
color: $colorTime;
width: 200px;
}
&.c-input--hrs-min-sec {
width: 80px;
}
}
}
}
/**************************************************** MAIN LAYOUT */
.l-shell {
&:not(.is-editing) {
.l-shell__head,
.l-shell__main-view-browse-bar {
background: none !important;
height: $interiorMargin;
overflow: hidden;
padding: 0 !important;
position: absolute;
width: 30%;
z-index: 100;
* { opacity: 0; }
&:hover {
background: #666 !important;
height: auto;
overflow: visible;
padding: $interiorMargin !important;
* { opacity: 1; }
}
}
.l-shell__pane-main .l-pane__contents > * + * {
margin-top: 0;
}
.l-shell__main-container {
margin-top: 0 !important;
}
}
.l-shell__main-view-browse-bar {
top: -3px; right: 0;
}
&__main > .l-pane {
padding-left: $interiorMargin !important;
padding-right: $interiorMargin !important;
}
&__status { display: none !important; }
}
.l-pane {
&__contents {
overflow: visible !important;
}
&--collapsed {
$d: 5px;
flex-basis: $d !important;
min-width: $d !important;
min-height: $d !important;
.l-pane__label {
display: none !important;
}
.l-pane__collapse-button {
&:before { display: none !important; }
}
}
}
/**************************************************** STYLING BY TITLE */
*[title*='font-euro'] {
@include headerFont();
}
*[title*='key-widget'] {
font-size: 24px;
}
*[title*='alert-red'] {
.c-sw__icon {
color: red !important;
}
}
*[title*='alert-yellow'] {
.c-sw__icon {
color: $ylwKeyBg !important;
}
}
*[title*='widget-ok'] {
@include widgetOk();
}
*[title*='widget-yellow'] {
@include widgetYellow();
}
*[title*='widget-red'] {
@include widgetRed();
}
*[title*='pulse-slow'] {
@include pulse($animName: pulseSlow, $dur: 1000ms, $prop: filter, $val0: brightness(1.2), $val100: brightness(0.6), $direction: normal);
.c-sw__icon {
@include pulse($animName: pulseWarningIcon, $dur: 500ms, $prop: opacity, $val0: 0.1, $val100: 1, $direction: normal);
}
}
*[title*='pulse-fast'] {
@include pulse($animName: pulseSlow, $dur: 250ms, $prop: filter, $val0: brightness(1.5), $val100: brightness(0.9), $direction: normal);
}
*[title*='pulse-warning'] {
@include pulse($animName: pulseWarning, $dur: 1500ms, $prop: filter, $val0: brightness(1.2), $val100: brightness(0.7), $direction: normal);
}
*[title*='Bg Widget'] {
// Widgets that provide a flashing red bg for components
.c-summary-widget {
$b: 5px;
border: none !important;
border-radius: $b * 4;
&[title*='widget-yellow'] {
opacity: 0.3;
box-shadow: $ylwKeyBg 0px 0px $b $b;
}
&[title*='widget-red'] {
opacity: 0.4;
box-shadow: $redKeyBg 0px 0px $b $b;
}
}
}
/**************************************************** SECTIONS */
/************* SYS OVW WIDGETS */
*[title*='Sys Ovw'] {
*[title*='Widget'] {
font-size: 1.4em;
}
*[title*='sys-ok'] {
@include widgetOk();
}
*[title*='post-ring'] {
@include widgetRed();
}
}
/************* ATTITUDE */
*[title*='Attitude'] {
*[title*='RPY'] {
background: black;
padding: 3px;
border-radius: 5px;
.l-control-bar {
display: none;
}
.l-view-section {
top: 0;
}
.gl-plot-axis-area.gl-plot-y {
width: 22px !important;
}
.gl-plot-wrapper-display-area-and-x-axis {
left: 24px !important;
}
}
}
/************* VELOCITY COMPONENT */
*[title*='-not-attained'] {
@include widgetRed();
}
/************* ACCELERATION COMPONENT */
/************* BOOST PHASE WIDGETS */
*[title*='boost-phase-widget'] {
.c-summary-widget {
border-width: 2px;
padding: 5px 0;
&:before,
&:after {
content: '';
display: block;
position: absolute;
}
}
}
*[title*='boost-phase-widget-left'] {
.c-summary-widget {
$c: #999;
align-items: start;
background: rgba($c, 0.1) !important;
border-left: none;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
&:before,
&:after {
top: 25px; right: 0; bottom: 3%; left: 40%;
}
&:before {
@include bgStripes($c: $c, $a: 0.1, $bgsize: 20px, $angle: 0deg);
}
&:after {
@include bgStripes($c: $c, $a: 0.1, $bgsize: 63px, $angle: 0deg);
}
}
}
*[title*='boost-phase-widget-right'] {
.c-summary-widget {
$c: #555;
border-right: none;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
&:before {
//background: deeppink;
@include bgTicks($c: $c, $repeatDir: 'y');
background-size: 100% 45px;
top: 0px; right: 0; bottom: 0; left: 2px;
}
}
}
*[title*='Boost Phase Ring Widgets'] {
*[title*='Ring Widget'] {
//filter: saturate(0.7);
}
*[title*='ring-ok'] {
@include widgetOk();
}
*[title*='bad-ring'] {
@include widgetRed();
}
*[title*='post-ring'] {
@include widgetYellow();
}
}
/************* BOTTOM SYSTEM WIDGETS */
*[title*='system-widget'] {
background: $bgKey !important;
border-width: 2px !important;
justify-content: left !important;
.c-sw__icon {
$m: $interiorMarginSm;
position: absolute;
bottom: $m;
right: $m;
}
}
*[title*="Bottom System Widgets"] {
transform: translateY(-2px);
}
*[title*='system-widget--hero'] {
// Current "tab" in interface
border-top-left-radius: 0;
border-top-right-radius: 0;
border-top: none;
}
*[title*='system-widget--subtle'] {
opacity: 0.7;
}
/**************************************************** INDIVIDUAL ELEMENTS */
/************* PASSENGER NOTICE */
*[title*='passenger-notice'] {
font-size: 1.4em;
.c-sw__icon {
font-size: 2.1em;
margin-right: $interiorMarginLg;
}
}
*[title*='pulse-passenger-notice--nominal'] {
@include pulse($animName: pulseNominal, $dur: 1000ms, $prop: filter, $val0: brightness(1.4), $val100:brightness(1.0), $direction: alternate);
}
*[title*='pulse-passenger-notice--warning'] {
@include pulse($animName: pulseWarning, $dur: 750ms, $prop: background-color, $val0: #aa0000, $val100: #340000, $direction: normal);
}
*[title*='Main Console'] {
$r: $basicCr;
background: $bgKey;
border-radius: $r;
border-bottom: 2px solid rgb(102,102,102);
}
*[title*='Event Time Str'] {
@include heroFont(1.2em);
line-height: .8em;
}
*[title*="Sys Ovw LAD Table"] {
.c-lad-table {
tr {
background-color: black;
color: rgba(white, 0.3);
display: block;
font-weight: bold;
align-items: center;
height: 41px;
border-radius: 5px;
font-size: 0.75em;
margin-bottom: 4px;
overflow: hidden;
padding: 3px;
text-transform: uppercase;
}
thead,
tr td:nth-child(1),
tr td:nth-child(2) {
display: none;
}
td {
display: block;
width: auto;
white-space: normal;
}
}
}

View File

@@ -34,5 +34,3 @@
@import "legacy";
@import "legacy-plots";
@import "legacy-messages";
@import "theme-maelstrom";
@import "movie-maelstrom";

View File

@@ -26,7 +26,7 @@
@import "constants";
@import "constants-mobile.scss";
//@import "constants-espresso"; // TEMP
//@import "constants-snow"; // TEMP
@import "constants-maelstrom";
@import "constants-snow"; // TEMP
//@import "constants-maelstrom";
@import "mixins";
@import "animations";

View File

@@ -1,19 +0,0 @@
/************* FRAMES */
.c-so-view {
&__header {
transform: translateY(3px);
}
&__header__name {
transform: translateY(-2px);
}
&:not(.c-so-view--no-frame) {
$bc: #666;
$bLR: 3px solid transparent;
$br: 20px;
background: none !important;
border: none !important;
padding: 0 !important;
}
}

View File

@@ -20,7 +20,7 @@
align-items: center;
flex: 1 1 auto;
overflow: hidden;
padding: $interiorMarginSm;
padding: $interiorMarginSm 1px;
white-space: nowrap;
&__name {

View File

@@ -0,0 +1,78 @@
<template>
<label class="c-toggle-switch">
<input type="checkbox"
:id="id"
:checked="checked"
@change="onUserSelect($event)"/>
<span class="c-toggle-switch__slider"></span>
</label>
</template>
<style lang="scss">
@import "~styles/sass-base";
.c-toggle-switch {
$d: 12px;
$m: 2px;
$br: $d/1.5;
cursor: pointer;
overflow: hidden;
display: inline;
vertical-align: middle;
&__slider {
background: $colorBtnBg; // TODO: make discrete theme constants for these colors
border-radius: $br;
//box-shadow: inset rgba($colorBtnFg, 0.4) 0 0 0 1px;
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 {
// Knob
background: $colorBtnFg; // TODO: make discrete theme constants for these colors
border-radius: floor($br * 0.8);
box-shadow: rgba(black, 0.4) 0 0 2px;
content: '';
display: block;
position: absolute;
height: $d; width: $d;
top: $m; left: $m; right: auto;
transition: transform 100ms ease-in-out;
}
}
input {
opacity: 0;
width: 0;
height: 0;
&:checked {
+ .c-toggle-switch__slider {
background: $colorKey; // TODO: make discrete theme constants for these colors
&:before {
transform: translateX(100%);
}
}
}
}
}
</style>
<script>
export default {
inject: ['openmct'],
props: {
id: String,
checked: Boolean
},
methods: {
onUserSelect(event) {
this.$emit('change', event.target.checked);
}
}
}
</script>

View File

@@ -106,8 +106,10 @@
display: contents;
}
&__row + &__row {
> [class*="__"] {
&__row + &__row,
&__section + &__section {
[class*="__label"],
[class*="__value"] {
// Row borders, effected via border-top on child elements of the row
border-top: 1px solid $colorInspectorSectionHeaderBg;
}

View File

@@ -81,6 +81,10 @@
padding: $interiorMargin - $aPad;
transition: background 150ms ease;
> * + * {
margin-left: $interiorMarginSm;
}
&:hover {
background: $colorItemTreeHoverBg;
.c-tree__item__type-icon:before {
@@ -116,10 +120,6 @@
}
}
&__view-control {
margin-right: $interiorMarginSm;
}
// Object labels in trees
&__label {
// <a> tag that holds type icon and name.