Merge branch 'master' of https://github.com/nasa/openmct into topic-conditionals

This commit is contained in:
Joshi 2020-03-05 10:00:54 -08:00
commit 576b843bd5
16 changed files with 503 additions and 409 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

@ -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,7 +120,7 @@ $colorFilter: $colorFilterFg; // Standalone against $colorBodyBg
// States
$colorPausedBg: #ff9900;
$colorPausedFg: #fff;
$colorPausedFg: #333;
$colorOk: #33cc33;
// Base variations

View File

@ -124,7 +124,7 @@ $colorFilter: $colorFilterFg; // Standalone against $colorBodyBg
// States
$colorPausedBg: #ff9900;
$colorPausedFg: #fff;
$colorPausedFg: #333;
$colorOk: #33cc33;
// Base variations

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

@ -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

@ -2,15 +2,40 @@
$d: 12px;
$m: 2px;
$br: $d/1.5;
cursor: pointer;
overflow: hidden;
display: inline;
display: inline-flex;
align-items: center;
vertical-align: middle;
&__control,
&__label {
flex: 0 0 auto;
}
&__control {
cursor: pointer;
overflow: hidden;
display: block;
}
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%);
}
}
}
}
&__slider {
// Sits within __switch
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;
@ -31,18 +56,9 @@
}
}
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%);
}
}
}
&__label {
margin-left: $interiorMarginSm;
margin-right: $interiorMargin;
white-space: nowrap;
}
}

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));
}
}
}