Merge branch 'topic-conditionals' into dave/condition-telemetry-request

This commit is contained in:
David Tsay 2020-03-09 16:33:55 -07:00
commit 084df5329a
38 changed files with 1356 additions and 1036 deletions

2
API.md
View File

@ -231,7 +231,7 @@ attributes
of this object. This is used for specifying an icon to appear next to each
object of this type.
The [Open MCT Tutorials](https://github.com/openmct/openmct-tutorial) provide a
The [Open MCT Tutorials](https://github.com/nasa/openmct-tutorial) provide a
step-by-step examples of writing code for Open MCT that includes a [section on
defining a new object type](https://github.com/nasa/openmct-tutorial#step-3---providing-objects).

View File

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

View File

@ -22,13 +22,13 @@
import Condition from "./Condition";
import uuid from "uuid";
import * as EventEmitter from 'eventemitter3';
import EventEmitter from 'EventEmitter';
export default class ConditionManager extends EventEmitter {
constructor(domainObject, openmct) {
super();
this.domainObject = domainObject;
this.openmct = openmct;
this.domainObject = domainObject;
this.timeAPI = this.openmct.time;
this.latestTimestamp = {};
this.instantiate = this.openmct.$injector.get('instantiate');

View File

@ -0,0 +1,102 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import EventEmitter from 'EventEmitter';
export default class StyleRuleManager extends EventEmitter {
constructor(conditionalStyleConfiguration, openmct) {
super();
this.openmct = openmct;
if (conditionalStyleConfiguration && conditionalStyleConfiguration.conditionSetIdentifier) {
this.initialize(conditionalStyleConfiguration);
this.subscribeToConditionSet();
}
}
initialize(conditionalStyleConfiguration) {
this.conditionSetIdentifier = conditionalStyleConfiguration.conditionSetIdentifier;
this.defaultStyle = conditionalStyleConfiguration.defaultStyle;
this.updateConditionStylesMap(conditionalStyleConfiguration.styles || []);
}
subscribeToConditionSet() {
if (this.stopProvidingTelemetry) {
this.stopProvidingTelemetry();
}
this.openmct.objects.get(this.conditionSetIdentifier).then((conditionSetDomainObject) => {
this.stopProvidingTelemetry = this.openmct.telemetry.subscribe(conditionSetDomainObject, output => this.handleConditionSetResultUpdated(output));
});
}
updateConditionalStyleConfig(conditionalStyleConfiguration) {
if (!conditionalStyleConfiguration || !conditionalStyleConfiguration.conditionSetIdentifier) {
this.destroy();
} else {
let isNewConditionSet = !this.conditionSetIdentifier ||
this.openmct.objects.areIdsEqual(this.conditionSetIdentifier, conditionalStyleConfiguration.conditionSetIdentifier);
this.initialize(conditionalStyleConfiguration);
//Only resubscribe if the conditionSet has changed.
if (isNewConditionSet) {
this.subscribeToConditionSet();
}
}
}
updateConditionStylesMap(conditionStyles) {
let conditionStyleMap = {};
conditionStyles.forEach((conditionStyle) => {
const identifier = this.openmct.objects.makeKeyString(conditionStyle.conditionIdentifier);
conditionStyleMap[identifier] = conditionStyle.style;
});
this.conditionalStyleMap = conditionStyleMap;
}
handleConditionSetResultUpdated(resultData) {
let identifier = this.openmct.objects.makeKeyString(resultData.conditionId);
let foundStyle = this.conditionalStyleMap[identifier];
if (foundStyle) {
if (foundStyle !== this.currentStyle) {
this.currentStyle = foundStyle;
}
} else {
if (this.currentStyle !== this.defaultStyle) {
this.currentStyle = this.defaultStyle;
}
}
this.updateDomainObjectStyle();
}
updateDomainObjectStyle() {
this.emit('conditionalStyleUpdated', this.currentStyle)
}
destroy() {
this.currentStyle = this.defaultStyle;
this.updateDomainObjectStyle();
if (this.stopProvidingTelemetry) {
this.stopProvidingTelemetry();
}
this.conditionSetIdentifier = undefined;
}
}

View File

@ -1,178 +1,168 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
* Open MCT, Copyright (c) 2014-2020, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
<template>
<div v-if="isEditing">
<div v-if="domainObject"
class="c-c-editui__conditions c-c-container__container c-c__drag-wrapper"
:class="['widget-condition', { 'widget-condition--current': currentConditionIdentifier && (currentConditionIdentifier.key === conditionIdentifier.key) }]"
>
<div class="title-bar">
<span class="c-c__menu-hamburger"
:class="{ 'is-enabled': !domainObject.isDefault }"
:draggable="!domainObject.isDefault"
@dragstart="dragStart"
@dragstop="dragStop"
@dragover.stop
></span>
<span
class="is-enabled flex-elem"
:class="['c-c__disclosure-triangle', { 'c-c__disclosure-triangle--expanded': expanded }]"
@click="expanded = !expanded"
></span>
<div class="condition-summary">
<span class="condition-name">{{ domainObject.configuration.name }}</span>
<!-- TODO: description should be derived from criteria -->
<span class="condition-description">{{ domainObject.configuration.name }}</span>
</div>
<span v-if="!domainObject.isDefault"
class="is-enabled c-c__duplicate"
@click="cloneCondition"
></span>
<span v-if="!domainObject.isDefault"
class="is-enabled c-c__trash"
@click="removeCondition"
></span>
<div v-if="isEditing"
class="c-condition c-condition--edit js-condition-drag-wrapper"
:class="{ 'c-condition--current-match': currentConditionIdentifier && (currentConditionIdentifier.key === conditionIdentifier.key) }"
>
<!-- Edit view -->
<div class="c-condition__header">
<span class="c-condition__drag-grippy c-grippy c-grippy--vertical-drag"
title="Drag to reorder conditions"
:class="[{ 'is-enabled': !domainObject.isDefault }, { 'hide-nice': domainObject.isDefault }]"
:draggable="!domainObject.isDefault"
@dragstart="dragStart"
@dragstop="dragStop"
@dragover.stop
></span>
<span class="c-condition__disclosure c-disclosure-triangle c-tree__item__view-control is-enabled"
:class="{ 'c-disclosure-triangle--expanded': expanded }"
@click="expanded = !expanded"
></span>
<span class="c-condition__name">{{ domainObject.configuration.name }}</span>
<!-- TODO: description should be derived from criteria -->
<span class="c-condition__summary">
Description/summary goes here {{ domainObject.configuration.description }}
</span>
<div class="c-condition__buttons">
<button v-if="!domainObject.isDefault"
class="c-click-icon c-condition__duplicate-button icon-duplicate"
title="Duplicate this condition"
@click="cloneCondition"
></button>
<button v-if="!domainObject.isDefault"
class="c-click-icon c-condition__delete-button icon-trash"
title="Delete this condition"
@click="removeCondition"
></button>
</div>
<div v-if="expanded"
class="condition-config-edit widget-condition-content c-sw-editui__conditions-wrapper holder widget-conditions-wrapper flex-elem expanded"
>
<div id="conditionArea"
class="c-c-editui__condition widget-conditions"
</div>
<div v-if="expanded"
class="c-condition__definition c-cdef"
>
<span class="c-cdef__separator c-row-separator"></span>
<span class="c-cdef__label">Condition Name</span>
<span class="c-cdef__controls">
<input v-model="domainObject.configuration.name"
class="t-condition-input__name"
type="text"
@blur="persist"
>
<div class="c-c-condition">
<div class="c-c-condition__ui l-compact-form l-widget-condition has-local-controls">
<div>
<ul class="t-widget-condition-config">
<li>
<label>Condition Name</label>
<span class="controls">
<input v-model="domainObject.configuration.name"
class="t-condition-input__name"
type="text"
@blur="persist"
>
</span>
</li>
<li>
<label>Output</label>
<span class="controls">
<select v-model="selectedOutputSelection"
@change="setOutputValue"
>
<option value="">- Select Output -</option>
<option v-for="option in outputOptions"
:key="option"
:value="option"
>
{{ initCap(option) }}
</option>
</select>
<input v-if="selectedOutputSelection === outputOptions[2]"
v-model="domainObject.configuration.output"
class="t-condition-name-input"
type="text"
@blur="persist"
>
</span>
</li>
</ul>
<div v-if="!domainObject.isDefault"
class="widget-condition-content expanded"
>
<ul class="t-widget-condition-config">
<li class="has-local-controls t-condition">
<label>Match when</label>
<span class="controls">
<select v-model="domainObject.configuration.trigger"
@change="persist"
>
<option value="all">all criteria are met</option>
<option value="any">any criteria are met</option>
</select>
</span>
</li>
</ul>
<ul v-if="telemetry.length"
class="t-widget-condition-config"
>
<li v-for="(criterion, index) in domainObject.configuration.criteria"
:key="index"
class="has-local-controls t-condition"
>
<Criterion :telemetry="telemetry"
:criterion="criterion"
:index="index"
:trigger="domainObject.configuration.trigger"
:is-default="domainObject.configuration.criteria.length === 1"
@persist="persist"
/>
<div class="c-c__criterion-controls">
<span class="is-enabled c-c__duplicate"
@click="cloneCriterion(index)"
></span>
<span v-if="!(domainObject.configuration.criteria.length === 1)"
class="is-enabled c-c__trash"
@click="removeCriterion(index)"
></span>
</div>
</li>
</ul>
<div class="holder c-c-button-wrapper align-left">
<span class="c-c-label-spacer"></span>
<button
class="c-c-button c-c-button--minor add-criteria-button"
@click="addCriteria"
>
<span class="c-c-button__label">Add Criteria</span>
</button>
</div>
</div>
</div>
</span>
<span class="c-cdef__label">Output</span>
<span class="c-cdef__controls">
<select v-model="selectedOutputSelection"
@change="setOutputValue"
>
<option value="">- Select Output -</option>
<option v-for="option in outputOptions"
:key="option"
:value="option"
>
{{ initCap(option) }}
</option>
</select>
<input v-if="selectedOutputSelection === outputOptions[2]"
v-model="domainObject.configuration.output"
class="t-condition-name-input"
type="text"
@blur="persist"
>
</span>
<div v-if="!domainObject.isDefault"
class="c-cdef__match-and-criteria"
>
<span class="c-cdef__separator c-row-separator"></span>
<span class="c-cdef__label">Match</span>
<span class="c-cdef__controls">
<select v-model="domainObject.configuration.trigger"
@change="persist"
>
<option value="all">when all criteria are met</option>
<option value="any">when any criteria are met</option>
</select>
</span>
<template v-if="telemetry.length">
<div v-for="(criterion, index) in domainObject.configuration.criteria"
:key="index"
class="c-cdef__criteria"
>
<Criterion :telemetry="telemetry"
:criterion="criterion"
:index="index"
:trigger="domainObject.configuration.trigger"
:is-default="domainObject.configuration.criteria.length === 1"
@persist="persist"
/>
<div class="c-cdef__criteria__buttons">
<button class="c-click-icon c-cdef__criteria-duplicate-button icon-duplicate"
title="Duplicate this criteria"
@click="cloneCriterion(index)"
></button>
<button v-if="!(domainObject.configuration.criteria.length === 1)"
class="c-click-icon c-cdef__criteria-duplicate-button icon-trash"
title="Delete this criteria"
@click="removeCriterion(index)"
></button>
</div>
</div>
</template>
<div class="c-cdef__separator c-row-separator"></div>
<div class="c-cdef__controls"
:disabled="!telemetry.length"
>
<button
class="c-cdef__add-criteria-button c-button c-button--labeled icon-plus"
@click="addCriteria"
>
<span class="c-button__label">Add Criteria</span>
</button>
</div>
</div>
</div>
</div>
<div v-else>
<div v-if="domainObject"
id="conditionArea"
class="c-cs-ui__conditions"
:class="['widget-condition', { 'widget-condition--current': currentConditionIdentifier && (currentConditionIdentifier.key === conditionIdentifier.key) }]"
>
<div class="title-bar">
<span class="condition-name">
{{ domainObject.configuration.name }}
</span>
<span class="condition-output">
Output: {{ domainObject.configuration.output }}
</span>
</div>
<div class="condition-config">
<span class="condition-description">
{{ domainObject.configuration.description }}
</span>
</div>
<div v-else
class="c-condition c-condition--browse"
:class="{ 'c-condition--current': currentConditionIdentifier && (currentConditionIdentifier.key === conditionIdentifier.key) }"
>
<!-- Browse view -->
<div class="c-condition__header">
<span class="c-condition__name">
{{ domainObject.configuration.name }}
</span>
<span class="c-condition__output">
Output: {{ domainObject.configuration.output }}
</span>
</div>
<div class="c-condition__summary">
Description/summary goes here {{ domainObject.configuration.description }}
</div>
</div>
</template>
@ -210,7 +200,9 @@ export default {
},
data() {
return {
domainObject: this.domainObject,
domainObject: {
configuration: {}
},
currentCriteria: this.currentCriteria,
expanded: true,
trigger: 'all',
@ -262,7 +254,7 @@ export default {
dragStart(e) {
e.dataTransfer.setData('dragging', e.target); // required for FF to initiate drag
e.dataTransfer.effectAllowed = "copyMove";
e.dataTransfer.setDragImage(e.target.closest('.c-c-container__container'), 0, 0);
e.dataTransfer.setDragImage(e.target.closest('.js-condition-drag-wrapper'), 0, 0);
this.$emit('setMoveIndex', this.conditionIndex);
},
dragStop(e) {
@ -301,5 +293,3 @@ export default {
}
}
</script>

View File

@ -22,60 +22,59 @@
<template>
<section id="conditionCollection"
class="c-cs__ui_section"
class="c-cs__conditions"
:class="{ 'is-expanded': expanded }"
>
<div class="c-cs__ui__header">
<span class="c-cs__ui__header-label">Conditions</span>
<div class="c-cs__header c-section__header">
<span
class="is-enabled flex-elem"
:class="['c-cs__disclosure-triangle', { 'c-cs__disclosure-triangle--expanded': expanded }]"
class="c-disclosure-triangle c-tree__item__view-control is-enabled"
:class="{ 'c-disclosure-triangle--expanded': expanded }"
@click="expanded = !expanded"
></span>
<div class="c-cs__header-label c-section__label">Conditions</div>
</div>
<div v-if="expanded"
class="c-cs__ui_content"
class="c-cs__content"
>
<div v-show="isEditing"
class="help"
class="hint"
:class="{ 's-status-icon-warning-lo': !telemetryObjs.length }"
>
<span v-if="!telemetryObjs.length">Drag telemetry into Condition Set in order to add conditions.</span>
<span v-else>The first condition to match is the one that wins. Drag conditions to rearrange.</span>
<template v-if="!telemetryObjs.length">Drag telemetry into this Condition Set to configure Conditions and add criteria.</template>
<template v-else>The first condition to match is the one that is applied. Drag conditions to reorder.</template>
</div>
<div class="holder add-condition-button-wrapper align-left">
<button
v-show="isEditing"
id="addCondition"
class="c-cs-button c-cs-button--major add-condition-button"
:class="{ 'is-disabled': !telemetryObjs.length}"
:disabled="!telemetryObjs.length"
@click="addCondition"
<button
v-show="isEditing"
id="addCondition"
class="c-button c-button--major icon-plus labeled"
@click="addCondition"
>
<span class="c-cs-button__label">Add Condition</span>
</button>
<div class="c-cs__conditions-h">
<div v-for="(conditionIdentifier, index) in conditionCollection"
:key="conditionIdentifier.key"
class="c-condition-h"
>
<span class="c-cs-button__label">Add Condition</span>
</button>
</div>
<div class="c-c__condition-collection">
<ul class="c-c__container-holder">
<li v-for="(conditionIdentifier, index) in conditionCollection"
:key="conditionIdentifier.key"
>
<div v-if="isEditing"
class="c-c__drag-ghost"
@drop.prevent="dropCondition"
@dragenter="dragEnter"
@dragleave="dragLeave"
@dragover.prevent
></div>
<Condition :condition-identifier="conditionIdentifier"
:current-condition-identifier="currentConditionIdentifier"
:condition-index="index"
:telemetry="telemetryObjs"
:is-editing="isEditing"
@removeCondition="removeCondition"
@cloneCondition="cloneCondition"
@setMoveIndex="setMoveIndex"
/>
</li>
</ul>
<div v-if="isEditing"
class="c-c__drag-ghost"
@drop.prevent="dropCondition"
@dragenter="dragEnter"
@dragleave="dragLeave"
@dragover.prevent
></div>
<Condition :condition-identifier="conditionIdentifier"
:current-condition-identifier="currentConditionIdentifier"
:condition-index="index"
:telemetry="telemetryObjs"
:is-editing="isEditing"
@removeCondition="removeCondition"
@cloneCondition="cloneCondition"
@setMoveIndex="setMoveIndex"
/>
</div>
</div>
</div>
</section>

View File

@ -21,24 +21,20 @@
*****************************************************************************/
<template>
<div class="c-cs-edit w-condition-set">
<div class="c-sw-edit__ui holder">
<section id="current-output">
<div class="c-cs__ui__header">
<span class="c-cs__ui__header-label">Current Output</span>
</div>
<div class="c-cs__ui_content">
<span v-if="currentConditionOutput"
class="current-output"
>
{{ currentConditionOutput }}
</span>
<span v-else>No output selected</span>
</div>
</section>
<TestData :is-editing="isEditing" />
<ConditionCollection :is-editing="isEditing" />
</div>
<div class="c-cs">
<section class="c-cs__current-output c-section">
<div class="c-cs__header c-section__header">
<span class="c-cs__header-label c-section__label">Current Output</span>
</div>
<div class="c-cs__content c-cs__current-output-value">
<template v-if="currentConditionOutput">
{{ currentConditionOutput }}
</template>
<template v-else>No output selected</template>
</div>
</section>
<TestData :is-editing="isEditing" />
<ConditionCollection :is-editing="isEditing" />
</div>
</template>

View File

@ -1,8 +1,9 @@
<template>
<div>
<label>{{ setRowLabel }}</label>
<span class="t-configuration">
<span class="controls">
<div class="u-contents">
<div class="c-cdef__separator c-row-separator"></div>
<span class="c-cdef__label">{{ setRowLabel }}</span>
<span class="c-cdef__controls">
<span class="c-cdef__control">
<select v-model="criterion.telemetry"
@change="updateMetadataOptions"
>
@ -16,7 +17,7 @@
</select>
</span>
<span v-if="criterion.telemetry"
class="controls"
class="c-cdef__control"
>
<select v-model="criterion.metadata"
@change="updateOperations"
@ -31,7 +32,7 @@
</select>
</span>
<span v-if="criterion.telemetry && criterion.metadata"
class="controls"
class="c-cdef__control"
>
<select v-model="criterion.operation"
@change="updateOperationInputVisibility"
@ -46,9 +47,10 @@
</select>
<span v-for="(item, inputIndex) in inputCount"
:key="inputIndex"
class="c-cdef__control__inputs"
>
<input v-model="criterion.input[inputIndex]"
class="t-condition-name-input"
class="c-cdef__control__input"
type="text"
@blur="persist"
>

View File

@ -23,18 +23,19 @@
<template>
<section v-show="isEditing"
id="test-data"
class="test-data"
class="c-cs__test-data"
:class="{ 'is-expanded': expanded }"
>
<div class="c-cs__ui__header">
<span class="c-cs__ui__header-label">Test Data</span>
<div class="c-cs__header c-section__header">
<span
class="is-enabled flex-elem"
:class="['c-cs__disclosure-triangle', { 'c-cs__disclosure-triangle--expanded': expanded }]"
class="c-disclosure-triangle c-tree__item__view-control is-enabled"
:class="{ 'c-disclosure-triangle--expanded': expanded }"
@click="expanded = !expanded"
></span>
<div class="c-cs__header-label c-section__label">Test Data</div>
</div>
<div v-if="expanded"
class="c-cs__ui_content"
class="c-cs__content"
>
<label class="c-toggle-switch">
<input
@ -43,32 +44,27 @@
@change="applyTestData"
>
<span class="c-toggle-switch__slider"></span>
<span>Apply Test Data</span>
<span class="c-toggle-switch__label">Apply Test Data</span>
</label>
<div class="t-test-data-config">
<div class="c-cs-editui__conditions widget-condition">
<form>
<label>
<span>Set</span>
<select>
<option>- Select Input -</option>
</select>
</label>
<span class="is-enabled flex-elem c-cs__duplicate"></span>
<span class="is-enabled flex-elem c-cs__trash"></span>
</form>
</div>
<div class="c-cs-editui__conditions widget-condition">
<form>
<label>
<span>Set</span>
<select>
<option>- Select Input -</option>
</select>
</label>
<span class="is-enabled c-cs__duplicate"></span>
<span class="is-enabled c-cs__trash"></span>
</form>
<div class="c-cs-test-h">
<div v-for="n in 5"
:key="n"
class="c-test-datum"
>
<span class="c-test-datum__label">Set</span>
<div class="c-test-datum__controls">
<select>
<option>- Select Input -</option>
</select>
</div>
<div class="c-test-datum__buttons">
<button class="c-click-icon c-test-data__duplicate-button icon-duplicate"
title="Duplicate this test data value"
></button>
<button class="c-click-icon c-test-data__delete-button icon-trash"
title="Delete this test data value"
></button>
</div>
</div>
</div>
</div>

View File

@ -1,155 +1,89 @@
.c-cs-edit {
padding: 0;
}
section {
padding-bottom: 5px;
}
.c-cs__ui__header {
background-color: #D0D1D3;
padding: 0.4em 0.6em;
text-transform: uppercase;
font-size: 0.8em;
font-weight: bold;
color: #7C7D80;
.c-cs {
display: flex;
justify-content: stretch;
align-items: center;
}
flex-direction: column;
height: 100%;
overflow: hidden;
.c-cs__ui__header-label {
display: inline-block;
width: 100%;
}
&__content {
display: flex;
flex-direction: column;
flex: 1 1 auto;
overflow: hidden;
.c-cs__ui_content {
padding: 0.4em;
}
.c-cs-ui__label {
color: #333;
}
.c-cs__ui_content .help {
font-style: italic;
padding: 0.4em 0;
}
.current-output {
text-transform: uppercase;
font-weight: bold;
margin: 0.4em 0.6em;
}
.condition-output {
color: #999;
}
.condition-description {
color: #333;
}
.checkbox.custom {
display: flex;
align-items: center;
margin-left: 0.6em;
}
.checkbox.custom span {
display: inline-block;
margin-left: 0.6em;
}
.t-test-data-config {
margin-top: 5px;
}
.c-c__condition-collection.is-disabled {
opacity: 0.5;
}
.widget-condition form {
padding: 0.5em;
display: flex;
align-items: center;
justify-content: stretch;
}
.widget-condition form label {
flex-grow: 1;
margin-left: 0;
}
.widget-condition form input {
min-height: 24px;
}
.c-cs-button[class*="--major"],
.c-cs-button[class*='is-active'],
.c-cs-button--menu[class*="--major"],
.c-cs-button--menu[class*='is-active'] {
border: solid 1px #0B427C;
background-color: #4778A3;
padding: 0.2em 0.6em;
margin: 0.4em;
font-weight: bold;
color: #eee;
border-radius: 6px;
&.is-disabled {
opacity: 0.5;
}
}
.c-cs__disclosure-triangle,
.c-cs__menu-hamburger,
.c-cs__duplicate,
.c-cs__trash {
$d: 8px;
color: $colorDisclosureCtrl;
width: $d;
visibility: hidden;
&.is-enabled {
cursor: pointer;
visibility: visible;
&:hover {
color: $colorDisclosureCtrlHov;
> * {
flex: 0 0 auto;
overflow: hidden;
+ * {
margin-top: $interiorMarginSm;
}
}
&:before {
$s: .5;
display: block;
font-family: symbolsfont;
font-size: 1rem * $s;
.c-button {
align-self: start;
}
}
}
.c-cs__disclosure-triangle {
&:before {
content: $glyph-icon-arrow-right;
.is-editing & {
// Add some space to kick away from blue editing border indication
padding: $interiorMargin;
}
&--expanded {
&:before {
transform: rotate(90deg);
section {
display: flex;
flex-direction: column;
overflow: hidden;
}
&__conditions-h {
display: flex;
flex-direction: column;
flex: 1 1 auto;
overflow: auto;
padding-right: $interiorMarginSm;
> * + * {
margin-top: $interiorMarginSm;
}
}
}
.c-cs__duplicate {
margin-right: 0.3em;
&:before {
content: $glyph-icon-duplicate;
&__conditions {
> * + * {
margin-top: $interiorMarginSm;
}
}
.hint {
padding: $interiorMarginSm;
}
/************************** SPECIFIC ITEMS */
&__current-output-value {
font-size: 1.25em;
padding: $interiorMargin;
}
}
.c-cs__trash {
&:before {
content: $glyph-icon-trash;
/***************************** TEST DATA */
.c-cs-test-h {
flex: 1 1 auto;
overflow: auto;
padding-right: $interiorMarginSm;
> * + * {
margin-top: $interiorMarginSm;
}
}
.c-test-datum {
> * {
flex: 0 0 auto;
+ * {
margin-left: $interiorMargin;
}
}
&__controls {
flex: 1 1 auto;
}
}

View File

@ -1,208 +1,111 @@
.widget-condition {
background-color: #eee;
margin: 0 0 0.33em;
border-radius: 3px;
&--current {
background-color: #DEECFA;
}
}
.c-c-editui__conditions.widget-condition {
margin: 0;
}
.c-c-button-wrapper {
border-top: solid 1px #ccc;
padding: 2px;
}
.c-c-label-spacer {
display: inline-block;
width: 90px;
}
.c-c-button[class*="--minor"],
.c-c-button[class*='is-active'],
.c-c-button--menu[class*="--minor"],
.c-c-button--menu[class*='is-active'] {
border: solid 1px #666;
background-color: #fff;
padding: 0.1em 0.4em;
margin: 0.4em;
font-weight: normal;
color: #666;
border-radius: 6px;
}
.title-bar {
.c-condition,
.c-test-datum {
@include discreteItem();
display: flex;
align-items: center;
justify-content: stretch;
padding: 0.4em 0;
padding: $interiorMargin;
&--edit {
line-height: 160%; // For layout when inputs wrap, like in criteria
}
}
.title-bar span {
margin-right: 0.6em;
}
.c-condition {
flex-direction: column;
.title-bar span.c-c__duplicate,
.title-bar span.c-c__trash{
margin-right: 0;
margin-left: 0.3em;
}
.widget-condition > div {
margin: 0 0.4em;
}
.condition-name {
font-weight: bold;
}
.condition-summary .condition-description {
color: #999;
}
.condition-summary {
flex-grow: 1;
}
.condition-config {
padding: 0.3em 0;
}
.widget-condition form label {
flex-grow: 1;
margin-left: 0;
}
.l-widget-condition {
padding: 0;
}
.l-compact-form ul li {
padding: 0;
}
.widget-condition-content.expanded {
margin: 0 3px;
}
.widget-condition-content.expanded ul li {
border-top: solid 1px #ccc;
padding: 2px;
}
.l-compact-form ul li .controls {
display: inline-flex;
flex-grow: inherit;
padding: 2px 0;
}
.l-compact-form ul li > label {
display: block;
width: 90px;
min-width: 90px;
text-align: right;
line-height: 20px;
}
.l-compact-form ul li .controls input[type="text"],
.l-compact-form ul li .controls input[type="search"],
.l-compact-form ul li .controls input[type="number"],
.l-compact-form ul li .controls button,
.l-compact-form ul li .controls select {
min-height: 20px;
}
.condition-config-edit {
padding: 3px 0;
}
.c-c__disclosure-triangle,
.c-c__menu-hamburger,
.c-c__duplicate,
.c-c__trash {
$d: 8px;
color: $colorDisclosureCtrl;
width: $d;
visibility: hidden;
&.is-enabled {
cursor: pointer;
visibility: visible;
&:hover {
color: $colorDisclosureCtrlHov;
}
&:before {
$s: .5;
display: block;
font-family: symbolsfont;
font-size: 1rem * $s;
> * + * {
margin-top: $interiorMarginSm;
}
&--browse {
.c-condition__summary {
border-top: 1px solid $colorInteriorBorder;
padding-top: $interiorMargin;
}
}
}
.c-c__disclosure-triangle {
&:before {
content: $glyph-icon-arrow-right;
}
/***************************** HEADER */
&__header {
display: flex;
align-items: flex-start;
align-content: stretch;
overflow: hidden;
&--expanded {
&:before {
transform: rotate(90deg);
> * {
flex: 0 0 auto;
+ * {
margin-left: $interiorMarginSm;
}
}
}
}
.c-c__menu-hamburger {
&:active {
cursor: grabbing;
cursor: -moz-grabbing;
cursor: -webkit-grabbing;
&__name {
font-weight: bold;
align-self: baseline; // Fixes bold line-height offset problem
}
&:before {
content: $glyph-icon-menu-hamburger;
&__output,
&__summary {
flex: 1 1 auto;
}
}
.c-c__duplicate {
&:before {
content: $glyph-icon-duplicate;
}
}
/***************************** CONDITION DEFINITION, EDITING */
.c-cdef {
display: grid;
grid-row-gap: $interiorMarginSm;
grid-column-gap: $interiorMargin;
grid-auto-columns: min-content 1fr max-content;
align-items: start;
min-width: 150px;
margin-left: 29px;
overflow: hidden;
.c-c__trash {
&:before {
content: $glyph-icon-trash;
&__criteria,
&__match-and-criteria {
display: contents;
}
&__label {
grid-column: 1;
text-align: right;
white-space: nowrap;
}
&__separator {
grid-column: 1 / span 3;
}
&__controls {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
//line-height: 200%;
grid-column: 2;
> * > * {
margin-right: $interiorMarginSm;
}
}
&__buttons {
grid-column: 3;
}
}
.c-c__drag-ghost {
width: 100%;
min-height: 0.33em;
min-height: $interiorMarginSm;
//&:before {
// @include test();
// content: '';
// display: block;
// z-index: 2;
//}
&.dragging {
min-height: 2em;
border: solid 1px blue;
min-height: 5em;
//border: solid 1px blue;
background-color: lightblue;
border-radius: 2px;
}
}
.c-c__criterion-controls {
width: 28px;
.c-c__duplicate,
.c-c__trash {
display: inline-block;
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -232,18 +232,16 @@ export default {
this.newFrameLocation = [containerIndex, insertFrameIndex];
},
addFrame(domainObject) {
if (this.newFrameLocation.length) {
let containerIndex = this.newFrameLocation[0],
frameIndex = this.newFrameLocation[1],
frame = new Frame(domainObject.identifier),
container = this.containers[containerIndex];
let containerIndex = this.newFrameLocation.length ? this.newFrameLocation[0] : 0;
let container = this.containers[containerIndex];
let frameIndex = this.newFrameLocation.length ? this.newFrameLocation[1] : container.frames.length;
let frame = new Frame(domainObject.identifier);
container.frames.splice(frameIndex + 1, 0, frame);
sizeItems(container.frames, frame);
container.frames.splice(frameIndex + 1, 0, frame);
sizeItems(container.frames, frame);
this.newFrameLocation = [];
this.persist(containerIndex);
}
this.newFrameLocation = [];
this.persist(containerIndex);
},
deleteFrame(frameId) {
let container = this.containers

View File

@ -1,90 +1,68 @@
<template>
<multipane class="c-imagery-layout"
type="vertical"
>
<pane :style="{'min-height': `300px`}">
<div class="main-image-wrapper c-imagery has-local-controls">
<div class="h-local-controls h-local-controls--overlay-content c-local-controls--show-on-hover l-flex-row c-imagery__lc">
<span class="holder flex-elem grows c-imagery__lc__sliders">
<input v-model="filters.brightness"
class="icon-brightness"
type="range"
min="0"
max="500"
>
<input v-model="filters.contrast"
class="icon-contrast"
type="range"
min="0"
max="500"
>
</span>
<span class="holder flex-elem t-reset-btn-holder c-imagery__lc__reset-btn">
<a class="s-icon-button icon-reset t-btn-reset"
@click="filters={brightness: 100, contrast: 100}"
></a>
</span>
</div>
<div class="main-image s-image-main"
:class="{'paused unnsynced': paused(),'stale':false }"
:style="{'background-image': `url(${getImageUrl()})`,
'filter': `brightness(${filters.brightness}%) contrast(${filters.contrast}%)`}"
>
</div>
<div class="l-image-controller flex-elem l-flex-row">
<div class="l-datetime-w flex-elem grows">
<a class="c-button show-thumbs sm hidden icon-thumbs-strip"></a>
<span class="l-time">{{ getTime() }}</span>
</div>
<div class="h-local-controls flex-elem">
<a class="c-button icon-pause pause-play"
:class="{'is-paused': paused()}"
@click="paused(!paused())"
></a>
</div>
</div>
</div>
</pane>
<pane class="c-inspector__elements"
handle="before"
:style="{'min-height': `100px`}"
>
<div class="c-elements-pool">
<div ref="thumbsWrapper"
class="thumbs-layout"
@scroll="handleScroll"
>
<div v-for="(imageData, index) in imageHistory"
:key="index"
class="l-image-thumb-item"
:class="{selected: imageData.selected}"
@click="setSelectedImage(imageData)"
<div class="c-imagery">
<div class="c-imagery__main-image-wrapper has-local-controls">
<div class="h-local-controls h-local-controls--overlay-content c-local-controls--show-on-hover l-flex-row c-imagery__lc">
<span class="holder flex-elem grows c-imagery__lc__sliders">
<input v-model="filters.brightness"
class="icon-brightness"
type="range"
min="0"
max="500"
>
<img class="l-thumb"
:src="getImageUrl(imageData)"
>
<div class="l-time">{{ getTime(imageData) }}</div>
</div>
<input v-model="filters.contrast"
class="icon-contrast"
type="range"
min="0"
max="500"
>
</span>
<span class="holder flex-elem t-reset-btn-holder c-imagery__lc__reset-btn">
<a class="s-icon-button icon-reset t-btn-reset"
@click="filters={brightness: 100, contrast: 100}"
></a>
</span>
</div>
<div class="main-image s-image-main c-imagery__main-image"
:class="{'paused unnsynced': paused(),'stale':false }"
:style="{'background-image': `url(${getImageUrl()})`,
'filter': `brightness(${filters.brightness}%) contrast(${filters.contrast}%)`}"
>
</div>
<div class="c-imagery__control-bar">
<div class="c-imagery__timestamp">{{ getTime() }}</div>
<div class="h-local-controls flex-elem">
<a class="c-button icon-pause pause-play"
:class="{'is-paused': paused()}"
@click="paused(!paused())"
></a>
</div>
</div>
</pane>
</multipane>
</div>
<div ref="thumbsWrapper"
class="c-imagery__thumbs-wrapper"
:class="{'is-paused': paused()}"
@scroll="handleScroll"
>
<div v-for="(imageData, index) in imageHistory"
:key="index"
class="c-imagery__thumb c-thumb"
:class="{selected: imageData.selected}"
@click="setSelectedImage(imageData)"
>
<img class="c-thumb__image"
:src="getImageUrl(imageData)"
>
<div class="c-thumb__timestamp">{{ getTime(imageData) }}</div>
</div>
</div>
</div>
</template>
<script>
import multipane from '@/ui/layout/multipane.vue';
import pane from '@/ui/layout/pane.vue';
import _ from 'lodash';
export default {
inject: ['openmct', 'domainObject'],
components: {
multipane,
pane
},
data() {
return {
autoScroll: true,
@ -109,7 +87,7 @@ export default {
this.subscribe(this.domainObject);
},
updated() {
this.scrollToBottom();
this.scrollToRight();
},
beforeDestroy() {
this.stopListening();
@ -152,6 +130,10 @@ export default {
if (arguments.length > 0 && state !== this.isPaused) {
this.unselectAllImages();
this.isPaused = state;
if (state === true) {
// If we are pausing, select the latest image in imageHistory
this.setSelectedImage(this.imageHistory[this.imageHistory.length - 1]);
}
if (this.nextDatum) {
this.updateValues(this.nextDatum);
@ -180,24 +162,31 @@ export default {
this.updateValues(values[values.length - 1]);
});
},
scrollToBottom() {
scrollToRight() {
if (this.isPaused || !this.$refs.thumbsWrapper || !this.autoScroll) {
return;
}
const scrollHeight = this.$refs.thumbsWrapper.scrollHeight || 0;
if (!scrollHeight) {
const scrollWidth = this.$refs.thumbsWrapper.scrollWidth || 0;
if (!scrollWidth) {
return;
}
setTimeout(() => this.$refs.thumbsWrapper.scrollTop = scrollHeight, 0);
setTimeout(() => this.$refs.thumbsWrapper.scrollLeft = scrollWidth, 0);
},
setSelectedImage(image) {
this.imageUrl = this.getImageUrl(image);
this.time = this.getTime(image);
this.paused(true);
this.unselectAllImages();
image.selected = true;
// If we are paused and the current image IS selected, unpause
// Otherwise, set current image and pause
if (this.isPaused && image.selected) {
this.paused(false);
this.unselectAllImages();
} else {
this.imageUrl = this.getImageUrl(image);
this.time = this.getTime(image);
this.paused(true);
this.unselectAllImages();
image.selected = true;
}
},
stopListening() {
if (this.unsubscribe) {

View File

@ -1,16 +1,20 @@
.c-imagery-layout {
.c-imagery {
display: flex;
flex-direction: column;
overflow: auto;
overflow: hidden;
height: 100%;
.main-image-wrapper {
display: flex;
flex-direction: column;
height: 100%;
padding-bottom: 5px;
> * + * {
margin-top: $interiorMargin;
}
.main-image {
&__main-image-wrapper {
display: flex;
flex-direction: column;
flex: 1 1 auto;
}
&__main-image {
background-position: center;
background-repeat: no-repeat;
background-size: contain;
@ -21,12 +25,137 @@
}
}
.l-image-controller {
&__control-bar {
padding: 5px 0 0 0;
display: flex;
align-items: center;
}
.thumbs-layout {
margin-top: 5px;
overflow: auto;
&__timestamp {
flex: 1 1 auto;
}
&__thumbs-wrapper {
flex: 0 0 auto;
display: flex;
flex-direction: row;
height: 135px;
overflow-x: auto;
overflow-y: hidden;
&.is-paused {
background: rgba($colorPausedBg, 0.4);
}
.c-thumb:last-child {
// Hilite the lastest thumb
background: $colorBodyFg;
color: $colorBodyBg;
}
}
}
/*************************************** THUMBS */
.c-thumb {
display: flex;
flex-direction: column;
padding: 4px;
width: $imageThumbsD;
&:hover {
background: $colorThumbHoverBg;
}
&.selected {
background: $colorPausedBg !important;
color: $colorPausedFg !important;
}
&__image {
background-color: rgba($colorBodyFg, 0.2);
width: 100%;
}
&__timestamp {
flex: 0 0 auto;
padding: 2px 3px;
}
}
.l-layout,
.c-fl {
.c-imagery__thumbs-wrapper {
// When Imagery is in a layout, hide the thumbs area
display: none;
}
}
.s-image-main {
background-color: $colorPlotBg;
border: 1px solid transparent;
}
/*************************************** IMAGERY LOCAL CONTROLS*/
.c-imagery {
.h-local-controls--overlay-content {
position: absolute;
right: $interiorMargin; top: $interiorMargin;
z-index: 2;
background: $colorLocalControlOvrBg;
border-radius: $basicCr;
max-width: 200px;
min-width: 100px;
width: 35%;
align-items: center;
padding: $interiorMargin $interiorMarginLg;
input[type="range"] {
display: block;
width: 100%;
&:not(:first-child) {
margin-top: $interiorMarginLg;
}
&:before {
margin-right: $interiorMarginSm;
}
}
}
&__lc {
&__reset-btn {
$bc: $scrollbarTrackColorBg;
&:before,
&:after {
border-right: 1px solid $bc;
content:'';
display: block;
width: 5px;
height: 4px;
}
&:before {
border-top: 1px solid $bc;
margin-bottom: 2px;
}
&:after {
border-bottom: 1px solid $bc;
margin-top: 2px;
}
}
}
}
/*************************************** BUTTONS */
.c-button.pause-play {
// Pause icon set by default in markup
&.is-paused {
background: $colorPausedBg !important;
color: $colorPausedFg;
&:before {
content: $glyph-icon-play;
}
}
}

View File

@ -377,13 +377,17 @@ define([
* @public
*/
updateFiltersAndRefresh: function (updatedFilters) {
this.filters = updatedFilters;
this.reset();
if (this.unsubscribe) {
this.unsubscribe();
delete this.unsubscribe;
if (this.filters && !_.isEqual(this.filters, updatedFilters)) {
this.filters = updatedFilters;
this.reset();
if (this.unsubscribe) {
this.unsubscribe();
delete this.unsubscribe;
}
this.fetch();
} else {
this.filters = updatedFilters;
}
this.fetch();
}
});

View File

@ -47,6 +47,7 @@ define([
this.subscriptions = {};
this.tableComposition = undefined;
this.telemetryObjects = [];
this.datumCache = [];
this.outstandingRequests = 0;
this.configuration = new TelemetryTableConfiguration(domainObject, openmct);
this.paused = false;
@ -155,6 +156,7 @@ define([
processHistoricalData(telemetryData, columnMap, keyString, limitEvaluator) {
let telemetryRows = telemetryData.map(datum => new TelemetryTableRow(datum, columnMap, keyString, limitEvaluator));
this.boundedRows.add(telemetryRows);
this.emit('historical-rows-processed');
}
/**
@ -227,12 +229,28 @@ define([
return;
}
if (!this.paused) {
if (this.paused) {
let realtimeDatum = {
datum,
columnMap,
keyString,
limitEvaluator
};
this.datumCache.push(realtimeDatum);
} else {
this.processRealtimeDatum(datum, columnMap, keyString, limitEvaluator);
}
}, subscribeOptions);
}
processDatumCache() {
this.datumCache.forEach(cachedDatum => {
this.processRealtimeDatum(cachedDatum.datum, cachedDatum.columnMap, cachedDatum.keyString, cachedDatum.limitEvaluator);
});
this.datumCache = [];
}
processRealtimeDatum(datum, columnMap, keyString, limitEvaluator) {
this.boundedRows.add(new TelemetryTableRow(datum, columnMap, keyString, limitEvaluator));
}
@ -272,8 +290,8 @@ define([
unpause() {
this.paused = false;
this.processDatumCache();
this.boundedRows.subscribeToBounds();
this.refreshData();
}
destroy() {

View File

@ -51,6 +51,14 @@ define([
view(domainObject, objectPath) {
let table = new TelemetryTable(domainObject, openmct);
let component;
let markingProp = {
enable: true,
useAlternateControlBar: false,
rowName: '',
rowNamePlural: ''
};
return {
show: function (element, editMode) {
component = new Vue({
@ -60,15 +68,16 @@ define([
},
data() {
return {
isEditing: editMode
}
isEditing: editMode,
markingProp
};
},
provide: {
openmct,
table,
objectPath
},
template: '<table-component :isEditing="isEditing" :enableMarking="true"></table-component>'
template: '<table-component :isEditing="isEditing" :marking="markingProp"/>'
});
},
onEditModeChange(editMode) {
@ -86,7 +95,7 @@ define([
priority() {
return 1;
}
}
};
}
return TelemetryTableViewProvider;
});

View File

@ -21,7 +21,10 @@
*****************************************************************************/
<template>
<div class="c-table-wrapper">
<div class="c-table-control-bar c-control-bar">
<!-- main contolbar start-->
<div v-if="!marking.useAlternateControlBar"
class="c-table-control-bar c-control-bar"
>
<button
v-if="allowExport"
class="c-button icon-download labeled"
@ -48,11 +51,11 @@
<span class="c-button__label">Unmark All Rows</span>
</button>
<div
v-if="enableMarking"
v-if="marking.enable"
class="c-separator"
></div>
<button
v-if="enableMarking"
v-if="marking.enable"
class="c-button icon-pause pause-play labeled"
:class=" paused ? 'icon-play is-paused' : 'icon-pause'"
:title="paused ? 'Continue Data Flow' : 'Pause Data Flow'"
@ -62,8 +65,38 @@
{{ paused ? 'Play' : 'Pause' }}
</span>
</button>
<slot name="buttons"></slot>
</div>
<!-- main controlbar end -->
<!-- alternate controlbar start -->
<div v-if="marking.useAlternateControlBar"
class="c-table-control-bar c-control-bar"
>
<div class="c-control-bar__label">
{{ markedRows.length > 1 ? `${markedRows.length} ${marking.rowNamePlural} selected`: `${markedRows.length} ${marking.rowName} selected` }}
</div>
<toggle-switch
id="show-filtered-rows-toggle"
label="Show selected items only"
:checked="isShowingMarkedRowsOnly"
@change="toggleMarkedRows"
/>
<button
:class="{'hide-nice': !markedRows.length}"
class="c-button icon-x labeled"
title="Deselect All"
@click="unmarkAllRows()"
>
<span class="c-button__label">Deselect All</span>
</button>
<slot name="buttons"></slot>
</div>
<!-- alternate controlbar end -->
<div
class="c-table c-telemetry-table c-table--filterable c-table--sortable has-control-bar"
@ -205,6 +238,7 @@ import TableColumnHeader from './table-column-header.vue';
import TelemetryFilterIndicator from './TelemetryFilterIndicator.vue';
import CSVExporter from '../../../exporters/CSVExporter.js';
import _ from 'lodash';
import ToggleSwitch from '../../../ui/components/ToggleSwitch.vue';
const VISIBLE_ROW_COUNT = 100;
const ROW_HEIGHT = 17;
@ -216,7 +250,8 @@ export default {
TelemetryTableRow,
TableColumnHeader,
search,
TelemetryFilterIndicator
TelemetryFilterIndicator,
ToggleSwitch
},
inject: ['table', 'openmct', 'objectPath'],
props: {
@ -236,9 +271,16 @@ export default {
'type': Boolean,
'default': true
},
enableMarking: {
type: Boolean,
default: false
marking: {
type: Object,
default() {
return {
enable: false,
useAlternateControlBar: false,
rowName: '',
rowNamePlural: ""
}
}
}
},
data() {
@ -270,7 +312,8 @@ export default {
scrollW: 0,
markCounter: 0,
paused: false,
markedRows: []
markedRows: [],
isShowingMarkedRowsOnly: false
}
},
computed: {
@ -304,6 +347,13 @@ export default {
return style;
}
},
watch: {
markedRows: {
handler(newVal, oldVal) {
this.$emit('marked-rows-updated', newVal, oldVal);
}
}
},
created() {
this.filterChanged = _.debounce(this.filterChanged, 500);
},
@ -317,6 +367,7 @@ export default {
this.table.on('object-removed', this.removeObject);
this.table.on('outstanding-requests', this.outstandingRequests);
this.table.on('refresh', this.clearRowsAndRerender);
this.table.on('historical-rows-processed', this.checkForMarkedRows);
this.table.filteredRows.on('add', this.rowsAdded);
this.table.filteredRows.on('remove', this.rowsRemoved);
@ -631,18 +682,19 @@ export default {
},
unpause(unpausedByButton) {
if (unpausedByButton) {
this.paused = false;
this.undoMarkedRows();
this.table.unpause();
this.markedRows = [];
this.paused = false;
this.pausedByButton = false;
} else {
if (!this.pausedByButton) {
this.paused = false;
this.undoMarkedRows();
this.table.unpause();
this.markedRows = [];
this.paused = false;
}
}
this.isShowingMarkedRowsOnly = false;
},
togglePauseByButton() {
if (this.paused) {
@ -655,24 +707,27 @@ export default {
this.markedRows.forEach(r => r.marked = false);
this.markedRows = [];
},
unmarkRow(rowIndex, ctrlKeyModifier) {
if (ctrlKeyModifier) {
unmarkRow(rowIndex) {
if (this.markedRows.length > 1) {
let row = this.visibleRows[rowIndex],
positionInMarkedArray = this.markedRows.indexOf(row);
row.marked = false;
this.markedRows.splice(positionInMarkedArray, 1);
if (this.markedRows.length === 0) {
this.unpause();
if (this.isShowingMarkedRowsOnly) {
this.visibleRows.splice(rowIndex, 1);
}
} else if (this.markedRows.length) {
this.undoMarkedRows();
this.markRow(rowIndex);
} else if (this.markedRows.length === 1) {
this.unmarkAllRows();
}
if (this.markedRows.length === 0) {
this.unpause();
}
},
markRow(rowIndex, keyModifier) {
if (!this.enableMarking) {
if (!this.marking.enable) {
return;
}
@ -691,12 +746,13 @@ export default {
this.markedRows[insertMethod](markedRow);
},
unmarkAllRows(skipUnpause) {
this.markedRows.forEach(row => row.marked = false);
this.markedRows = [];
this.undoMarkedRows();
this.isShowingMarkedRowsOnly = false;
this.unpause();
this.restorePreviousRows();
},
markMultipleConcurrentRows(rowIndex) {
if (!this.enableMarking) {
if (!this.marking.enable) {
return;
}
@ -733,6 +789,35 @@ export default {
}
}
}
},
checkForMarkedRows() {
this.isShowingMarkedRowsOnly = false;
this.markedRows = this.table.filteredRows.getRows().filter(row => row.marked);
},
showRows(rows) {
this.table.filteredRows.rows = rows;
this.table.filteredRows.emit('filter');
},
toggleMarkedRows(flag) {
if (flag) {
this.isShowingMarkedRowsOnly = true;
this.userScroll = this.scrollable.scrollTop;
this.allRows = this.table.filteredRows.getRows();
this.showRows(this.markedRows);
this.setHeight();
} else {
this.isShowingMarkedRowsOnly = false;
this.restorePreviousRows();
}
},
restorePreviousRows() {
if (this.allRows && this.allRows.length) {
this.showRows(this.allRows);
this.allRows = [];
this.setHeight();
this.scrollable.scrollTop = this.userScroll;
}
}
}
}

View File

@ -120,8 +120,7 @@ $colorFilter: $colorFilterFg; // Standalone against $colorBodyBg
// States
$colorPausedBg: #ff9900;
$colorPausedFg: #fff;
$colorOk: #33cc33;
$colorPausedFg: #333;
// Base variations
$colorBodyBgSubtle: pullForward($colorBodyBg, 5%);
@ -411,15 +410,21 @@ $transInBounceBig: all 300ms cubic-bezier(.2,1.6,.6,3);
// Discrete items, like Notebook entries, Widget rules
$createBtnTextTransform: uppercase;
$colorDiscreteItemBg: rgba($colorBodyFg,0.1);
$colorDiscreteItemCurrentBg: rgba($colorOk,0.3);
@mixin discreteItem() {
background: rgba($colorBodyFg,0.1);
background: $colorDiscreteItemBg;
border: none;
border-radius: $controlCr;
.c-input-inline:hover {
background: $colorBodyBg;
}
&--current-match {
background: $colorDiscreteItemCurrentBg;
}
}
@mixin discreteItemInnerElem() {

View File

@ -124,8 +124,7 @@ $colorFilter: $colorFilterFg; // Standalone against $colorBodyBg
// States
$colorPausedBg: #ff9900;
$colorPausedFg: #fff;
$colorOk: #33cc33;
$colorPausedFg: #333;
// Base variations
$colorBodyBgSubtle: pullForward($colorBodyBg, 5%);
@ -415,14 +414,16 @@ $transInBounceBig: all 300ms cubic-bezier(.2,1.6,.6,3);
// Discrete items, like Notebook entries, Widget rules
$createBtnTextTransform: uppercase;
$colorDiscreteItemBg: rgba($colorBodyFg,0.1);
$colorDiscreteItemCurrentBg: rgba($colorOk,0.3);
@mixin discreteItem() {
background: rgba($colorBodyFg,0.1);
border: none;
border-radius: $controlCr;
.c-input-inline:hover {
background: $colorBodyBg;
&--current-match {
background: $colorDiscreteItemCurrentBg;
}
}

View File

@ -121,7 +121,6 @@ $colorFilter: $colorFilterBg; // Standalone against $colorBodyBg
// States
$colorPausedBg: #ff9900;
$colorPausedFg: #fff;
$colorOk: #33cc33;
// Base variations
$colorBodyBgSubtle: pullForward($colorBodyBg, 5%);
@ -411,12 +410,18 @@ $transInBounceBig: all 300ms cubic-bezier(.2,1.6,.6,3);
// Discrete items, like Notebook entries, Widget rules
$createBtnTextTransform: uppercase;
$colorDiscreteItemBg: rgba($colorBodyFg,0.1);
$colorDiscreteItemCurrentBg: rgba($colorOk,0.3);
@mixin discreteItem() {
background: rgba($colorBodyFg,0.1);
background: $colorDiscreteItemBg;
border: 1px solid $colorInteriorBorder;
border-radius: $controlCr;
&--current-match {
background: $colorDiscreteItemCurrentBg;
}
.c-input-inline:hover {
background: $colorBodyBg;
}

View File

@ -62,6 +62,11 @@ button {
background: $colorBtnBgHov;
color: $colorBtnFgHov;
}
&:before {
content: $glyph-icon-arrow-down;
font-size: 1.1em;
}
}
&.is-active {
@ -78,6 +83,20 @@ button {
/********* Icon Buttons and Links */
.c-click-icon {
@include cClickIcon();
&--section-collapse {
color: inherit;
display: block;
transition: transform $transOutTime;
&:before {
content: $glyph-icon-arrow-down;
font-family: symbolsfont;
}
&.is-collapsed {
transform: rotate(180deg);
}
}
}
.c-click-link {
@ -189,6 +208,44 @@ button {
}
}
/******************************************************** DRAG AFFORDANCES */
.c-grippy {
$d: 10px;
@include grippy($c: $colorItemTreeVC, $dir: 'y');
width: $d; height: $d;
&--vertical-drag {
cursor: ns-resize;
}
}
/******************************************************** SECTION */
section {
flex: 0 0 auto;
overflow: hidden;
+ section {
margin-top: $interiorMargin;
}
&.is-expanded {
flex: 1 1 100%;
}
.c-section__header {
@include propertiesHeader();
display: flex;
align-items: center;
margin-bottom: $interiorMargin;
> * + * { margin-left: $interiorMarginSm; }
}
> [class*='__label'] {
flex: 1 1 auto;
text-transform: uppercase;
}
}
/******************************************************** FORM ELEMENTS */
input, textarea {
font-family: inherit;
@ -621,6 +678,11 @@ select {
@include cToolbarSeparator();
}
.c-row-separator {
border-top: 1px solid $colorInteriorBorder;
height: 1px;
}
.c-toolbar {
> * + * {
margin-left: 2px;

View File

@ -28,6 +28,16 @@
height: $controlBarH;
}
.c-control-bar {
display: flex;
align-items: center;
&__label {
display: inline-block;
white-space: nowrap;
}
}
.l-view-section {
@include abs();
overflow: auto;
@ -52,236 +62,6 @@
}
}
/******************************************************************* IMAGERY */
.l-image-main-wrapper,
.l-image-thumbs-wrapper,
.image-main {
@include abs(0);
overflow: hidden;
}
/*************************************** MAIN LAYOUT */
.l-image-main-wrapper {
// Imagery thumbs
bottom: $interiorMargin*2 + $imageThumbsWrapperH;
min-width: 150px;
.l-image-main {
margin-bottom: $interiorMargin;
}
.l-image-main-controlbar {
&.l-flex-row { align-items: center; }
}
}
.l-image-thumbs-wrapper {
top: auto;
min-height: $imageThumbsWrapperH;
max-height: 60%;
box-sizing: border-box;
}
.l-date,
.l-time,
.l-timezone {
display: inline-block;
}
/*************************************** MAIN IMAGE */
.image-main,
.l-image-thumb-item .l-thumb {
background-size: contain;
background-position: center;
background-repeat: no-repeat;
}
.l-image-main-controlbar {
line-height: inherit;
.l-datetime-w, .l-controls-w {
direction: rtl;
overflow: hidden;
}
.l-datetime-w {
@include ellipsize();
margin-right: $interiorMarginSm;
text-align: left;
}
.l-controls-w {
z-index: 2;
}
.l-date,
.l-time {
color: pullForward($colorBodyFg, 20%); // TODO: do this as a theme constant
}
.l-mag {
direction: ltr;
display: inline-block;
&:before {
content: "\000049";
}
}
.s-mag {
color: pushBack($colorBodyFg, 20%); // TODO: do this as a theme constant
}
.l-btn.show-thumbs {
display: none;
}
}
.s-image-main {
background-color: $colorPlotBg;
border: 1px solid transparent;
&.paused {
//@include sUnsynced();
}
}
/*************************************** THUMBS */
.l-image-thumbs-wrapper {
overflow-x: hidden;
overflow-y: auto;
padding-bottom: $interiorMargin;
white-space: nowrap;
}
.l-image-thumb-item {
transition: background-color 0.25s;
box-sizing: border-box;
cursor: pointer;
direction: ltr;
display: inline-block;
float: left;
padding: 1px;
margin-left: $interiorMarginSm;
position: relative;
text-align: left;
width: $imageThumbsD + $imageThumbPad*2;
white-space: normal;
.l-thumb,
.l-date,
.l-time {
display: inline-block;
}
.l-date,
.l-time {
padding: 2px 3px;
}
&:hover {
background: $colorThumbHoverBg;
.l-date,
.l-time {
color: #fff;
}
}
&.selected {
background: $colorKeySelectedBg;
.l-date,
.l-time {
color: #fff;
}
}
.l-thumb {
background-color: rgba(#fff, 0.1);
height: $imageThumbsD;
width: $imageThumbsD;
margin-top: 0;
}
}
/*************************************** LOCAL CONTROLS */
.c-imagery {
display: contents;
.h-local-controls--overlay-content {
position: absolute;
right: $interiorMargin; top: $interiorMargin;
z-index: 2;
background: $colorLocalControlOvrBg;
border-radius: $basicCr;
max-width: 200px;
min-width: 100px;
width: 35%;
align-items: center;
padding: $interiorMargin $interiorMarginLg;
input[type="range"] {
display: block;
width: 100%;
&:not(:first-child) {
margin-top: $interiorMarginLg;
}
&:before {
margin-right: $interiorMarginSm;
}
}
}
&__lc {
&__reset-btn {
$bc: $scrollbarTrackColorBg;
&:before,
&:after {
border-right: 1px solid $bc;
content:'';
display: block;
width: 5px;
height: 4px;
}
&:before {
border-top: 1px solid $bc;
margin-bottom: 2px;
}
&:after {
border-bottom: 1px solid $bc;
margin-top: 2px;
}
}
}
}
/*************************************** WHEN IN FRAME */
.c-frame .t-imagery {
.l-image-main-wrapper {
bottom: 0 !important;
height: 100% !important;
.l-image-main-controlbar .c-button {
//font-size: 0.7em;
}
}
.l-image-thumbs-wrapper,
mct-splitter {
display: none;
}
}
/*************************************** MOBILE */
body.mobile.phone {
.t-imagery {
.l-image-main-wrapper,
.l-image-thumbs-wrapper {
min-height: 10px !important;
}
}
}
.c-button.pause-play {
// Pause icon set by default in markup
&.is-paused {
background: $colorPausedBg !important;
color: $colorPausedFg;
&:before {
content: $glyph-icon-play;
}
}
}
/*********************************************************************** CLOCKS AND TIMERS */
.c-clock,
.c-timer {

View File

@ -251,7 +251,7 @@
@mixin reverseEllipsis() {
@include ellipsize();
direction: rtl;
direction: ltr;
unicode-bidi:bidi-override;
}

View File

@ -108,8 +108,9 @@ tr {
}
/*************************************************** STATUS */
[class*='s-status'] {
[class*='s-status-icon'] {
&:before {
font-family: symbolsfont;
margin-right: $interiorMargin;
}
}

View File

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

View File

@ -1,16 +1,25 @@
<template>
<label class="c-toggle-switch">
<input
:id="id"
type="checkbox"
:checked="checked"
@change="onUserSelect($event)"
<div class="c-toggle-switch">
<label class="c-toggle-switch__control">
<input
:id="id"
type="checkbox"
:checked="checked"
@change="onUserSelect($event)"
>
<span class="c-toggle-switch__slider"></span>
</label>
<div
v-if="label && label.length"
class="c-toggle-switch__label"
>
<span class="c-toggle-switch__slider"></span>
</label>
{{ label }}
</div>
</div>
</template>
<script>
export default {
inject: ['openmct'],
props: {
@ -18,6 +27,11 @@ export default {
type: String,
required: true
},
label: {
type: String,
required: false,
default: ''
},
checked: Boolean
},
methods: {
@ -26,4 +40,5 @@ export default {
}
}
}
</script>

View File

@ -3,32 +3,19 @@
$m: 2px;
$br: $d/1.5;
cursor: pointer;
overflow: hidden;
display: inline;
display: inline-flex;
align-items: center;
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;
&__control,
&__label {
flex: 0 0 auto;
}
&: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;
}
&__control {
cursor: pointer;
overflow: hidden;
display: block;
}
input {
@ -45,4 +32,33 @@
}
}
}
&__slider {
// Sits within __switch
background: $colorBtnBg; // TODO: make discrete theme constants for these colors
border-radius: $br;
display: inline-block;
height: $d + ($m*2);
position: relative;
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;
}
}
&__label {
margin-left: $interiorMarginSm;
margin-right: $interiorMargin;
white-space: nowrap;
}
}

View File

@ -28,7 +28,7 @@
>
<span
v-if="elements.length > 1 && isEditing"
class="c-elements-pool__grippy"
class="c-elements-pool__grippy c-grippy c-grippy--vertical-drag"
></span>
<object-label
:domain-object="element"

View File

@ -25,27 +25,40 @@ export default {
},
methods: {
updateSelection(selection) {
this.selection = selection;
if (selection.length > 0 && selection[0].length > 0) {
let domainObject = selection[0][0].context.item;
let layoutItem;
if (selection[0].length > 1) {
//If there are more than 1 items in the selection[0] list, the first one could either be a sub domain object OR a layout drawing control.
//The second item in the selection[0] list is the container object (usually a layout)
domainObject = selection[0][0].context.item;
if (!domainObject) {
layoutItem = selection[0][0].context.layoutItem;
domainObject = selection[0][1].context.item;
}
}
if (this.component) {
this.component.$destroy();
this.component = undefined;
this.$el.innerHTML = '';
if (this.component) {
this.component.$destroy();
this.component = undefined;
this.$el.innerHTML = '';
}
let viewContainer = document.createElement('div');
this.$el.append(viewContainer);
this.component = new Vue({
provide: {
openmct: this.openmct,
domainObject: domainObject,
layoutItem: layoutItem
},
el: viewContainer,
components: {
ConditionalStylesView
},
template: '<conditional-styles-view></conditional-styles-view>'
});
}
let viewContainer = document.createElement('div');
this.$el.append(viewContainer);
this.component = new Vue({
provide: {
openmct: this.openmct
},
el: viewContainer,
components: {
ConditionalStylesView
},
template: '<conditional-styles-view></conditional-styles-view>'
});
}
}
}

View File

@ -20,9 +20,8 @@
}
}
&__grippy {
.c-grippy {
$d: 8px;
@include grippy($c: $colorItemTreeVC, $dir: 'y');
flex: 0 0 auto;
margin-right: $interiorMarginSm;
transform: translateY(-2px);

View File

@ -62,6 +62,8 @@
}
&[class*="--horizontal"] {
padding-left: $interiorMargin;
padding-right: $interiorMargin;
&.l-pane--collapsed {
padding-left: 0 !important;
padding-right: 0 !important;
@ -69,9 +71,11 @@
}
&[class*="--vertical"] {
padding-top: $interiorMargin;
padding-bottom: $interiorMargin;
&.l-pane--collapsed {
padding-top: 0 !important;
padding-top: 0 !important;
padding-bottom: 0 !important;
}
}
@ -277,6 +281,9 @@
/************************** Vertical Splitter Before */
// Pane collapses downward. Used by Elements pool in Inspector
&[class*="-before"] {
$m: $interiorMarginLg;
margin-top: $m;
padding-top: $m;
> .l-pane__handle {
top: 0;
transform: translateY(floor($splitterHandleD / -1));

View File

@ -2,7 +2,7 @@
<li class="c-tree__item-h">
<div
class="c-tree__item"
:class="{ 'is-alias': isAlias, 'is-navigated-object': isNavigated }"
:class="{ 'is-alias': isAlias, 'is-navigated-object': navigated }"
>
<view-control
v-model="expanded"
@ -40,6 +40,8 @@
import viewControl from '../components/viewControl.vue';
import ObjectLabel from '../components/ObjectLabel.vue';
const LOCAL_STORAGE_KEY__TREE_EXPANDED = 'mct-tree-expanded';
export default {
name: 'TreeItem',
inject: ['openmct'],
@ -54,12 +56,12 @@ export default {
}
},
data() {
this.navigateToPath = this.buildPathString(this.node.navigateToParent)
this.navigateToPath = this.buildPathString(this.node.navigateToParent);
return {
hasChildren: false,
isLoading: false,
loaded: false,
isNavigated: this.navigateToPath === this.openmct.router.currentLocation.path,
navigated: this.navigateToPath === this.openmct.router.currentLocation.path,
children: [],
expanded: false
}
@ -75,7 +77,7 @@ export default {
}
},
watch: {
expanded(isExpanded) {
expanded() {
if (!this.hasChildren) {
return;
}
@ -86,6 +88,7 @@ export default {
this.composition.load().then(this.finishLoading);
this.isLoading = true;
}
this.setLocalStorageExpanded(this.navigateToPath);
}
},
mounted() {
@ -107,6 +110,17 @@ export default {
}
this.openmct.router.on('change:path', this.highlightIfNavigated);
this.getLocalStorageExpanded();
},
beforeDestroy() {
/****
* calling this.setLocalStorageExpanded explicitly here because for whatever reason,
* the watcher on this.expanded is not triggering this.setLocalStorageExpanded(),
* even though Vue documentation states, "At this stage the instance is still fully functional."
*****/
this.expanded = false;
this.setLocalStorageExpanded();
},
destroyed() {
this.openmct.router.off('change:path', this.highlightIfNavigated);
@ -139,10 +153,40 @@ export default {
},
highlightIfNavigated(newPath, oldPath) {
if (newPath === this.navigateToPath) {
this.isNavigated = true;
this.navigated = true;
} else if (oldPath === this.navigateToPath) {
this.isNavigated = false;
this.navigated = false;
}
},
getLocalStorageExpanded() {
let expandedPaths = localStorage.getItem(LOCAL_STORAGE_KEY__TREE_EXPANDED);
if (expandedPaths) {
expandedPaths = JSON.parse(expandedPaths);
this.expanded = expandedPaths.includes(this.navigateToPath);
}
},
// expanded nodes/paths are stored in local storage as an array
setLocalStorageExpanded() {
let expandedPaths = localStorage.getItem(LOCAL_STORAGE_KEY__TREE_EXPANDED);
expandedPaths = expandedPaths ? JSON.parse(expandedPaths) : [];
if (this.expanded) {
if (!expandedPaths.includes(this.navigateToPath)) {
expandedPaths.push(this.navigateToPath);
}
} else {
// remove this node path and all children paths from stored expanded paths
expandedPaths = expandedPaths.filter(path => !path.startsWith(this.navigateToPath));
}
localStorage.setItem(LOCAL_STORAGE_KEY__TREE_EXPANDED, JSON.stringify(expandedPaths));
},
removeLocalStorageExpanded() {
let expandedPaths = localStorage.getItem(LOCAL_STORAGE_KEY__TREE_EXPANDED);
expandedPaths = expandedPaths ? JSON.parse(expandedPaths) : [];
expandedPaths = expandedPaths.filter(path => !path.startsWith(this.navigateToPath));
localStorage.setItem(LOCAL_STORAGE_KEY__TREE_EXPANDED, JSON.stringify(expandedPaths));
}
}
}