mirror of
https://github.com/nasa/openmct.git
synced 2025-06-15 21:58:13 +00:00
Plots inspector using Vue (#3781)
This commit is contained in:
@ -24,6 +24,13 @@ define([
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO: Remove this when plots Angular implementation is deprecated
|
||||||
|
let parent = selection[0].length > 1 && selection[0][1].context.item;
|
||||||
|
if (parent && parent.type === 'time-strip') {
|
||||||
|
return (selectionContext.item.type === typeDefinition.key)
|
||||||
|
&& (typeDefinition.key !== 'telemetry.plot.overlay');
|
||||||
|
}
|
||||||
|
|
||||||
return selectionContext.item.type === typeDefinition.key;
|
return selectionContext.item.type === typeDefinition.key;
|
||||||
},
|
},
|
||||||
view: function (selection) {
|
view: function (selection) {
|
||||||
|
64
src/plugins/plot/vue/inspector/PlotOptions.vue
Normal file
64
src/plugins/plot/vue/inspector/PlotOptions.vue
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
<!--
|
||||||
|
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>
|
||||||
|
<div v-if="canEdit">
|
||||||
|
<plot-options-edit />
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<plot-options-browse />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import PlotOptionsBrowse from "@/plugins/plot/vue/inspector/PlotOptionsBrowse.vue";
|
||||||
|
import PlotOptionsEdit from "@/plugins/plot/vue/inspector/PlotOptionsEdit.vue";
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
PlotOptionsBrowse,
|
||||||
|
PlotOptionsEdit
|
||||||
|
},
|
||||||
|
inject: ['openmct', 'domainObject'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isEditing: this.openmct.editor.isEditing()
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
canEdit() {
|
||||||
|
return this.isEditing && !this.domainObject.locked;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.openmct.editor.on('isEditing', this.setEditState);
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
this.openmct.editor.off('isEditing', this.setEditState);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
setEditState(isEditing) {
|
||||||
|
this.isEditing = isEditing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
198
src/plugins/plot/vue/inspector/PlotOptionsBrowse.vue
Normal file
198
src/plugins/plot/vue/inspector/PlotOptionsBrowse.vue
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
<!--
|
||||||
|
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="config && loaded"
|
||||||
|
class="js-plot-options-browse"
|
||||||
|
>
|
||||||
|
<ul class="c-tree">
|
||||||
|
<h2 title="Plot series display properties in this object">Plot Series</h2>
|
||||||
|
<plot-options-item v-for="series in plotSeries"
|
||||||
|
:key="series.key"
|
||||||
|
:series="series"
|
||||||
|
/>
|
||||||
|
</ul>
|
||||||
|
<div class="grid-properties">
|
||||||
|
<ul class="l-inspector-part">
|
||||||
|
<h2 title="Y axis settings for this object">Y Axis</h2>
|
||||||
|
<li class="grid-row">
|
||||||
|
<div class="grid-cell label"
|
||||||
|
title="Manually override how the Y axis is labeled."
|
||||||
|
>Label</div>
|
||||||
|
<div class="grid-cell value">{{ label ? label : "Not defined" }}</div>
|
||||||
|
</li>
|
||||||
|
<li class="grid-row">
|
||||||
|
<div class="grid-cell label"
|
||||||
|
title="Automatically scale the Y axis to keep all values in view."
|
||||||
|
>Autoscale</div>
|
||||||
|
<div class="grid-cell value">
|
||||||
|
{{ autoscale ? "Enabled: " : "Disabled" }}
|
||||||
|
{{ autoscale ? autoscalePadding : "" }}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li v-if="!autoscale && rangeMin"
|
||||||
|
class="grid-row"
|
||||||
|
>
|
||||||
|
<div class="grid-cell label"
|
||||||
|
title="Minimum Y axis value."
|
||||||
|
>Minimum value</div>
|
||||||
|
<div class="grid-cell value">{{ rangeMin }}</div>
|
||||||
|
</li>
|
||||||
|
<li v-if="!autoscale && rangeMax"
|
||||||
|
class="grid-row"
|
||||||
|
>
|
||||||
|
<div class="grid-cell label"
|
||||||
|
title="Maximum Y axis value."
|
||||||
|
>Maximum value</div>
|
||||||
|
<div class="grid-cell value">{{ rangeMax }}</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<ul class="l-inspector-part">
|
||||||
|
<h2 title="Legend settings for this object">Legend</h2>
|
||||||
|
<li class="grid-row">
|
||||||
|
<div class="grid-cell label"
|
||||||
|
title="The position of the legend relative to the plot display area."
|
||||||
|
>Position</div>
|
||||||
|
<div class="grid-cell value capitalize">{{ position }}</div>
|
||||||
|
</li>
|
||||||
|
<li class="grid-row">
|
||||||
|
<div class="grid-cell label"
|
||||||
|
title="Hide the legend when the plot is small"
|
||||||
|
>Hide when plot small</div>
|
||||||
|
<div class="grid-cell value">{{ hideLegendWhenSmall ? "Yes" : "No" }}</div>
|
||||||
|
</li>
|
||||||
|
<li class="grid-row">
|
||||||
|
<div class="grid-cell label"
|
||||||
|
title="Show the legend expanded by default"
|
||||||
|
>Expand by Default</div>
|
||||||
|
<div class="grid-cell value">{{ expandByDefault ? "Yes" : "No" }}</div>
|
||||||
|
</li>
|
||||||
|
<li class="grid-row">
|
||||||
|
<div class="grid-cell label"
|
||||||
|
title="What to display in the legend when it's collapsed."
|
||||||
|
>Show when collapsed:</div>
|
||||||
|
<div class="grid-cell value">{{
|
||||||
|
valueToShowWhenCollapsed.replace('nearest', '')
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="grid-row">
|
||||||
|
<div class="grid-cell label"
|
||||||
|
title="What to display in the legend when it's expanded."
|
||||||
|
>Show when expanded:</div>
|
||||||
|
<div class="grid-cell value comma-list">
|
||||||
|
<span v-if="showTimestampWhenExpanded">Timestamp</span>
|
||||||
|
<span v-if="showValueWhenExpanded">Value</span>
|
||||||
|
<span v-if="showMinimumWhenExpanded">Min</span>
|
||||||
|
<span v-if="showMaximumWhenExpanded">Max</span>
|
||||||
|
<span v-if="showUnitsWhenExpanded">Units</span>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import PlotOptionsItem from "./PlotOptionsItem.vue";
|
||||||
|
import configStore from "../single/configuration/configStore";
|
||||||
|
import eventHelpers from "../single/lib/eventHelpers";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
PlotOptionsItem
|
||||||
|
},
|
||||||
|
inject: ['openmct', 'domainObject'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
config: undefined,
|
||||||
|
label: '',
|
||||||
|
autoscale: '',
|
||||||
|
autoscalePadding: '',
|
||||||
|
rangeMin: '',
|
||||||
|
rangeMax: '',
|
||||||
|
position: '',
|
||||||
|
hideLegendWhenSmall: '',
|
||||||
|
expandByDefault: '',
|
||||||
|
valueToShowWhenCollapsed: '',
|
||||||
|
showTimestampWhenExpanded: '',
|
||||||
|
showValueWhenExpanded: '',
|
||||||
|
showMinimumWhenExpanded: '',
|
||||||
|
showMaximumWhenExpanded: '',
|
||||||
|
showUnitsWhenExpanded: '',
|
||||||
|
loaded: false,
|
||||||
|
plotSeries: []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
eventHelpers.extend(this);
|
||||||
|
this.config = this.getConfig();
|
||||||
|
this.initConfiguration();
|
||||||
|
this.registerListeners();
|
||||||
|
this.loaded = true;
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
this.stopListening();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initConfiguration() {
|
||||||
|
this.label = this.config.yAxis.get('label');
|
||||||
|
this.autoscale = this.config.yAxis.get('autoscale');
|
||||||
|
this.autoscalePadding = this.config.yAxis.get('autoscalePadding');
|
||||||
|
const range = this.config.yAxis.get('range');
|
||||||
|
if (range) {
|
||||||
|
this.rangeMin = range.min;
|
||||||
|
this.rangeMax = range.max;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.position = this.config.legend.get('position');
|
||||||
|
this.hideLegendWhenSmall = this.config.legend.get('hideLegendWhenSmall');
|
||||||
|
this.expandByDefault = this.config.legend.get('expandByDefault');
|
||||||
|
this.valueToShowWhenCollapsed = this.config.legend.get('valueToShowWhenCollapsed');
|
||||||
|
this.showTimestampWhenExpanded = this.config.legend.get('showTimestampWhenExpanded');
|
||||||
|
this.showValueWhenExpanded = this.config.legend.get('showValueWhenExpanded');
|
||||||
|
this.showMinimumWhenExpanded = this.config.legend.get('showMinimumWhenExpanded');
|
||||||
|
this.showMaximumWhenExpanded = this.config.legend.get('showMaximumWhenExpanded');
|
||||||
|
this.showUnitsWhenExpanded = this.config.legend.get('showUnitsWhenExpanded');
|
||||||
|
},
|
||||||
|
getConfig() {
|
||||||
|
this.configId = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||||
|
|
||||||
|
return configStore.get(this.configId);
|
||||||
|
},
|
||||||
|
registerListeners() {
|
||||||
|
this.config.series.forEach(this.addSeries, this);
|
||||||
|
|
||||||
|
this.listenTo(this.config.series, 'add', this.addSeries, this);
|
||||||
|
this.listenTo(this.config.series, 'remove', this.resetAllSeries, this);
|
||||||
|
},
|
||||||
|
|
||||||
|
addSeries(series, index) {
|
||||||
|
this.plotSeries[index] = series;
|
||||||
|
},
|
||||||
|
|
||||||
|
resetAllSeries() {
|
||||||
|
this.plotSeries = [];
|
||||||
|
this.config.series.forEach(this.addSeries, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
100
src/plugins/plot/vue/inspector/PlotOptionsEdit.vue
Normal file
100
src/plugins/plot/vue/inspector/PlotOptionsEdit.vue
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
<!--
|
||||||
|
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="config && loaded"
|
||||||
|
class="js-plot-options-edit"
|
||||||
|
>
|
||||||
|
<ul class="c-tree">
|
||||||
|
<h2 title="Display properties for this object">Plot Series</h2>
|
||||||
|
<li v-for="series in plotSeries"
|
||||||
|
:key="series.key"
|
||||||
|
>
|
||||||
|
<series-form :series="series" />
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<y-axis-form v-show="!!plotSeries.length"
|
||||||
|
class="grid-properties"
|
||||||
|
:y-axis="config.yAxis"
|
||||||
|
/>
|
||||||
|
<ul class="l-inspector-part">
|
||||||
|
<h2 title="Legend options">Legend</h2>
|
||||||
|
<legend-form v-show="!!plotSeries.length"
|
||||||
|
class="grid-properties"
|
||||||
|
:legend="config.legend"
|
||||||
|
/>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import SeriesForm from "@/plugins/plot/vue/inspector/forms/SeriesForm.vue";
|
||||||
|
import YAxisForm from "@/plugins/plot/vue/inspector/forms/YAxisForm.vue";
|
||||||
|
import LegendForm from "@/plugins/plot/vue/inspector/forms/LegendForm.vue";
|
||||||
|
import eventHelpers from "@/plugins/plot/vue/single/lib/eventHelpers";
|
||||||
|
import configStore from "@/plugins/plot/vue/single/configuration/configStore";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
LegendForm,
|
||||||
|
SeriesForm,
|
||||||
|
YAxisForm
|
||||||
|
},
|
||||||
|
inject: ['openmct', 'domainObject'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
config: {},
|
||||||
|
plotSeries: [],
|
||||||
|
loaded: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
eventHelpers.extend(this);
|
||||||
|
this.config = this.getConfig();
|
||||||
|
this.registerListeners();
|
||||||
|
this.loaded = true;
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
this.stopListening();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getConfig() {
|
||||||
|
this.configId = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||||
|
|
||||||
|
return configStore.get(this.configId);
|
||||||
|
},
|
||||||
|
registerListeners() {
|
||||||
|
this.config.series.forEach(this.addSeries, this);
|
||||||
|
|
||||||
|
this.listenTo(this.config.series, 'add', this.addSeries, this);
|
||||||
|
this.listenTo(this.config.series, 'remove', this.resetAllSeries, this);
|
||||||
|
},
|
||||||
|
|
||||||
|
addSeries(series, index) {
|
||||||
|
this.plotSeries[index] = series;
|
||||||
|
},
|
||||||
|
|
||||||
|
resetAllSeries() {
|
||||||
|
this.plotSeries = [];
|
||||||
|
this.config.series.forEach(this.addSeries, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
155
src/plugins/plot/vue/inspector/PlotOptionsItem.vue
Normal file
155
src/plugins/plot/vue/inspector/PlotOptionsItem.vue
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
<template>
|
||||||
|
<ul>
|
||||||
|
<li class="c-tree__item menus-to-left">
|
||||||
|
<span class="c-disclosure-triangle is-enabled flex-elem"
|
||||||
|
:class="expandedCssClass"
|
||||||
|
@click="toggleExpanded"
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<div class="c-object-label"
|
||||||
|
:class="statusClass"
|
||||||
|
>
|
||||||
|
<div class="c-object-label__type-icon"
|
||||||
|
:class="getSeriesClass"
|
||||||
|
>
|
||||||
|
<span class="is-status__indicator"
|
||||||
|
title="This item is missing or suspect"
|
||||||
|
></span>
|
||||||
|
</div>
|
||||||
|
<div class="c-object-label__name">{{ series.domainObject.name }}</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li v-show="expanded"
|
||||||
|
class="c-tree__item menus-to-left"
|
||||||
|
>
|
||||||
|
<ul class="grid-properties js-plot-options-browse-properties">
|
||||||
|
<li class="grid-row">
|
||||||
|
<div class="grid-cell label"
|
||||||
|
title="The field to be plotted as a value for this series."
|
||||||
|
>Value</div>
|
||||||
|
<div class="grid-cell value">
|
||||||
|
{{ yKey }}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="grid-row">
|
||||||
|
<div class="grid-cell label"
|
||||||
|
title="The rendering method to join lines for this series."
|
||||||
|
>Line Method</div>
|
||||||
|
<div class="grid-cell value">{{ {
|
||||||
|
'none': 'None',
|
||||||
|
'linear': 'Linear interpolation',
|
||||||
|
'stepAfter': 'Step After'
|
||||||
|
}[interpolate] }}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="grid-row">
|
||||||
|
<div class="grid-cell label"
|
||||||
|
title="Whether markers are displayed, and their size."
|
||||||
|
>Markers</div>
|
||||||
|
<div class="grid-cell value">
|
||||||
|
{{ markerOptionsDisplayText }}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="grid-row">
|
||||||
|
<div class="grid-cell label"
|
||||||
|
title="Display markers visually denoting points in alarm."
|
||||||
|
>Alarm Markers</div>
|
||||||
|
<div class="grid-cell value">
|
||||||
|
{{ alarmMarkers ? "Enabled" : "Disabled" }}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="grid-row">
|
||||||
|
<div class="grid-cell label"
|
||||||
|
title="The plot line and marker color for this series."
|
||||||
|
>Color</div>
|
||||||
|
<div class="grid-cell value">
|
||||||
|
<span class="c-color-swatch"
|
||||||
|
:style="{
|
||||||
|
'background': seriesHexColor
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
inject: ['openmct'],
|
||||||
|
props: {
|
||||||
|
series: {
|
||||||
|
type: Object,
|
||||||
|
default() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
expanded: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
getSeriesClass() {
|
||||||
|
let cssClass = '';
|
||||||
|
let legacyObject = this.openmct.legacyObject(this.series.domainObject);
|
||||||
|
let location = legacyObject.getCapability('location');
|
||||||
|
if (location && location.isLink()) {
|
||||||
|
cssClass = 'l-icon-link';
|
||||||
|
}
|
||||||
|
|
||||||
|
let type = legacyObject.getCapability('type');
|
||||||
|
if (type) {
|
||||||
|
cssClass = `${cssClass} ${type.getCssClass()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cssClass;
|
||||||
|
},
|
||||||
|
expandedCssClass() {
|
||||||
|
if (this.expanded === true) {
|
||||||
|
return 'c-disclosure-triangle--expanded';
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
statusClass() {
|
||||||
|
return (this.status) ? `is-status--${this.status}` : '';
|
||||||
|
},
|
||||||
|
yKey() {
|
||||||
|
return this.series.get('yKey');
|
||||||
|
},
|
||||||
|
interpolate() {
|
||||||
|
return this.series.get('interpolate');
|
||||||
|
},
|
||||||
|
markerOptionsDisplayText() {
|
||||||
|
return this.series.markerOptionsDisplayText();
|
||||||
|
},
|
||||||
|
alarmMarkers() {
|
||||||
|
return this.series.get('alarmMarkers');
|
||||||
|
},
|
||||||
|
seriesHexColor() {
|
||||||
|
return this.series.get('color').asHexString();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.status = this.openmct.status.get(this.series.domainObject.identifier);
|
||||||
|
this.removeStatusListener = this.openmct.status.observe(this.series.domainObject.identifier, this.setStatus);
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
if (this.removeStatusListener) {
|
||||||
|
this.removeStatusListener();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
toggleExpanded() {
|
||||||
|
this.expanded = !this.expanded;
|
||||||
|
},
|
||||||
|
setStatus(status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
51
src/plugins/plot/vue/inspector/PlotsInspectorViewProvider.js
Normal file
51
src/plugins/plot/vue/inspector/PlotsInspectorViewProvider.js
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
|
||||||
|
import PlotOptions from "./PlotOptions.vue";
|
||||||
|
import Vue from 'vue';
|
||||||
|
|
||||||
|
export default function PlotsInspectorViewProvider(openmct) {
|
||||||
|
return {
|
||||||
|
key: 'plots-inspector',
|
||||||
|
name: 'Plots Inspector View',
|
||||||
|
canView: function (selection) {
|
||||||
|
if (selection.length === 0 || selection[0].length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let parent = selection[0].length > 1 && selection[0][1].context.item;
|
||||||
|
let object = selection[0][0].context.item;
|
||||||
|
|
||||||
|
return parent
|
||||||
|
&& parent.type === 'time-strip'
|
||||||
|
&& object
|
||||||
|
&& object.type === 'telemetry.plot.overlay';
|
||||||
|
},
|
||||||
|
view: function (selection) {
|
||||||
|
let component;
|
||||||
|
|
||||||
|
return {
|
||||||
|
show: function (element) {
|
||||||
|
component = new Vue({
|
||||||
|
el: element,
|
||||||
|
components: {
|
||||||
|
PlotOptions: PlotOptions
|
||||||
|
},
|
||||||
|
provide: {
|
||||||
|
openmct,
|
||||||
|
domainObject: openmct.selection.get()[0][0].context.item
|
||||||
|
},
|
||||||
|
template: '<plot-options></plot-options>'
|
||||||
|
});
|
||||||
|
},
|
||||||
|
destroy: function () {
|
||||||
|
if (component) {
|
||||||
|
component.$destroy();
|
||||||
|
component = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
priority: function () {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
207
src/plugins/plot/vue/inspector/forms/LegendForm.vue
Normal file
207
src/plugins/plot/vue/inspector/forms/LegendForm.vue
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<li class="grid-row">
|
||||||
|
<div class="grid-cell label"
|
||||||
|
title="The position of the legend relative to the plot display area."
|
||||||
|
>Position</div>
|
||||||
|
<div class="grid-cell value">
|
||||||
|
<select v-model="position"
|
||||||
|
@change="updateForm('position')"
|
||||||
|
>
|
||||||
|
<option value="top">Top</option>
|
||||||
|
<option value="right">Right</option>
|
||||||
|
<option value="bottom">Bottom</option>
|
||||||
|
<option value="left">Left</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="grid-row">
|
||||||
|
<div class="grid-cell label"
|
||||||
|
title="Hide the legend when the plot is small"
|
||||||
|
>Hide when plot small</div>
|
||||||
|
<div class="grid-cell value"><input v-model="hideLegendWhenSmall"
|
||||||
|
type="checkbox"
|
||||||
|
@change="updateForm('hideLegendWhenSmall')"
|
||||||
|
></div>
|
||||||
|
</li>
|
||||||
|
<li class="grid-row">
|
||||||
|
<div class="grid-cell label"
|
||||||
|
title="Show the legend expanded by default"
|
||||||
|
>Expand by default</div>
|
||||||
|
<div class="grid-cell value"><input v-model="expandByDefault"
|
||||||
|
type="checkbox"
|
||||||
|
@change="updateForm('expandByDefault')"
|
||||||
|
></div>
|
||||||
|
</li>
|
||||||
|
<li class="grid-row">
|
||||||
|
<div class="grid-cell label"
|
||||||
|
title="What to display in the legend when it's collapsed."
|
||||||
|
>When collapsed show</div>
|
||||||
|
<div class="grid-cell value">
|
||||||
|
<select v-model="valueToShowWhenCollapsed"
|
||||||
|
@change="updateForm('valueToShowWhenCollapsed')"
|
||||||
|
>
|
||||||
|
<option value="none">Nothing</option>
|
||||||
|
<option value="nearestTimestamp">Nearest timestamp</option>
|
||||||
|
<option value="nearestValue">Nearest value</option>
|
||||||
|
<option value="min">Minimum value</option>
|
||||||
|
<option value="max">Maximum value</option>
|
||||||
|
<option value="units">Units</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="grid-row">
|
||||||
|
<div class="grid-cell label"
|
||||||
|
title="What to display in the legend when it's expanded."
|
||||||
|
>When expanded show</div>
|
||||||
|
<div class="grid-cell value">
|
||||||
|
<ul>
|
||||||
|
<li><input v-model="showTimestampWhenExpanded"
|
||||||
|
type="checkbox"
|
||||||
|
@change="updateForm('showTimestampWhenExpanded')"
|
||||||
|
> Nearest timestamp</li>
|
||||||
|
<li><input v-model="showValueWhenExpanded"
|
||||||
|
type="checkbox"
|
||||||
|
@change="updateForm('showValueWhenExpanded')"
|
||||||
|
> Nearest value</li>
|
||||||
|
<li><input v-model="showMinimumWhenExpanded"
|
||||||
|
type="checkbox"
|
||||||
|
@change="updateForm('showMinimumWhenExpanded')"
|
||||||
|
> Minimum value</li>
|
||||||
|
<li><input v-model="showMaximumWhenExpanded"
|
||||||
|
type="checkbox"
|
||||||
|
@change="updateForm('showMaximumWhenExpanded')"
|
||||||
|
> Maximum value</li>
|
||||||
|
<li><input v-model="showUnitsWhenExpanded"
|
||||||
|
type="checkbox"
|
||||||
|
@change="updateForm('showUnitsWhenExpanded')"
|
||||||
|
> Units</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import {coerce, objectPath, validate} from "@/plugins/plot/vue/inspector/forms/formUtil";
|
||||||
|
import _ from "lodash";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
inject: ['openmct', 'domainObject'],
|
||||||
|
props: {
|
||||||
|
legend: {
|
||||||
|
type: Object,
|
||||||
|
default() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
position: '',
|
||||||
|
hideLegendWhenSmall: '',
|
||||||
|
expandByDefault: '',
|
||||||
|
valueToShowWhenCollapsed: '',
|
||||||
|
showTimestampWhenExpanded: '',
|
||||||
|
showValueWhenExpanded: '',
|
||||||
|
showMinimumWhenExpanded: '',
|
||||||
|
showMaximumWhenExpanded: '',
|
||||||
|
showUnitsWhenExpanded: '',
|
||||||
|
validation: {}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.initialize();
|
||||||
|
this.initFormValues();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initialize() {
|
||||||
|
this.fields = [
|
||||||
|
{
|
||||||
|
modelProp: 'position',
|
||||||
|
objectPath: 'configuration.legend.position'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
modelProp: 'hideLegendWhenSmall',
|
||||||
|
coerce: Boolean,
|
||||||
|
objectPath: 'configuration.legend.hideLegendWhenSmall'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
modelProp: 'expandByDefault',
|
||||||
|
coerce: Boolean,
|
||||||
|
objectPath: 'configuration.legend.expandByDefault'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
modelProp: 'valueToShowWhenCollapsed',
|
||||||
|
objectPath: 'configuration.legend.valueToShowWhenCollapsed'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
modelProp: 'showValueWhenExpanded',
|
||||||
|
coerce: Boolean,
|
||||||
|
objectPath: 'configuration.legend.showValueWhenExpanded'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
modelProp: 'showTimestampWhenExpanded',
|
||||||
|
coerce: Boolean,
|
||||||
|
objectPath: 'configuration.legend.showTimestampWhenExpanded'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
modelProp: 'showMaximumWhenExpanded',
|
||||||
|
coerce: Boolean,
|
||||||
|
objectPath: 'configuration.legend.showMaximumWhenExpanded'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
modelProp: 'showMinimumWhenExpanded',
|
||||||
|
coerce: Boolean,
|
||||||
|
objectPath: 'configuration.legend.showMinimumWhenExpanded'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
modelProp: 'showUnitsWhenExpanded',
|
||||||
|
coerce: Boolean,
|
||||||
|
objectPath: 'configuration.legend.showUnitsWhenExpanded'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
},
|
||||||
|
initFormValues() {
|
||||||
|
this.position = this.legend.get('position');
|
||||||
|
this.hideLegendWhenSmall = this.legend.get('hideLegendWhenSmall');
|
||||||
|
this.expandByDefault = this.legend.get('expandByDefault');
|
||||||
|
this.valueToShowWhenCollapsed = this.legend.get('valueToShowWhenCollapsed');
|
||||||
|
this.showTimestampWhenExpanded = this.legend.get('showTimestampWhenExpanded');
|
||||||
|
this.showValueWhenExpanded = this.legend.get('showValueWhenExpanded');
|
||||||
|
this.showMinimumWhenExpanded = this.legend.get('showMinimumWhenExpanded');
|
||||||
|
this.showMaximumWhenExpanded = this.legend.get('showMaximumWhenExpanded');
|
||||||
|
this.showUnitsWhenExpanded = this.legend.get('showUnitsWhenExpanded');
|
||||||
|
},
|
||||||
|
updateForm(formKey) {
|
||||||
|
const newVal = this[formKey];
|
||||||
|
const oldVal = this.legend.get(formKey);
|
||||||
|
const formField = this.fields.find((field) => field.modelProp === formKey);
|
||||||
|
|
||||||
|
const path = objectPath(formField.objectPath);
|
||||||
|
const validationResult = validate(newVal, this.legend, formField.validate);
|
||||||
|
if (validationResult === true) {
|
||||||
|
delete this.validation[formKey];
|
||||||
|
} else {
|
||||||
|
this.validation[formKey] = validationResult;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_.isEqual(coerce(newVal, formField.coerce), coerce(oldVal, formField.coerce))) {
|
||||||
|
this.legend.set(formKey, coerce(newVal, formField.coerce));
|
||||||
|
if (path) {
|
||||||
|
this.openmct.objects.mutate(
|
||||||
|
this.domainObject,
|
||||||
|
path(this.domainObject, this.legend),
|
||||||
|
coerce(newVal, formField.coerce)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setStatus(status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
347
src/plugins/plot/vue/inspector/forms/SeriesForm.vue
Normal file
347
src/plugins/plot/vue/inspector/forms/SeriesForm.vue
Normal file
@ -0,0 +1,347 @@
|
|||||||
|
<template>
|
||||||
|
<ul>
|
||||||
|
<li class="c-tree__item menus-to-left">
|
||||||
|
<span class="c-disclosure-triangle is-enabled flex-elem"
|
||||||
|
:class="expandedCssClass"
|
||||||
|
@click="toggleExpanded"
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<div :class="objectLabelCss">
|
||||||
|
<div class="c-object-label__type-icon"
|
||||||
|
:class="[seriesCss, linkCss]"
|
||||||
|
>
|
||||||
|
<span class="is-status__indicator"
|
||||||
|
title="This item is missing or suspect"
|
||||||
|
></span>
|
||||||
|
</div>
|
||||||
|
<div class="c-object-label__name">{{ series.domainObject.name }}</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<ul v-show="expanded"
|
||||||
|
class="grid-properties js-plot-options-edit-properties"
|
||||||
|
>
|
||||||
|
<li class="grid-row">
|
||||||
|
<!-- Value to be displayed -->
|
||||||
|
<div class="grid-cell label"
|
||||||
|
title="The field to be plotted as a value for this series."
|
||||||
|
>Value</div>
|
||||||
|
<div class="grid-cell value">
|
||||||
|
<select v-model="yKey"
|
||||||
|
@change="updateForm('yKey')"
|
||||||
|
>
|
||||||
|
<option v-for="option in yKeyOptions"
|
||||||
|
:key="option.value"
|
||||||
|
:value="option.value"
|
||||||
|
:selected="option.value == yKey"
|
||||||
|
>
|
||||||
|
{{ option.name }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="grid-row">
|
||||||
|
<div class="grid-cell label"
|
||||||
|
title="The rendering method to join lines for this series."
|
||||||
|
>Line Method</div>
|
||||||
|
<div class="grid-cell value">
|
||||||
|
<select v-model="interpolate"
|
||||||
|
@change="updateForm('interpolate')"
|
||||||
|
>
|
||||||
|
<option value="none">None</option>
|
||||||
|
<option value="linear">Linear interpolate</option>
|
||||||
|
<option value="stepAfter">Step after</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="grid-row">
|
||||||
|
<div class="grid-cell label"
|
||||||
|
title="Whether markers are displayed."
|
||||||
|
>Markers</div>
|
||||||
|
<div class="grid-cell value">
|
||||||
|
<input v-model="markers"
|
||||||
|
type="checkbox"
|
||||||
|
@change="updateForm('markers')"
|
||||||
|
>
|
||||||
|
<select
|
||||||
|
v-show="markers"
|
||||||
|
v-model="markerShape"
|
||||||
|
@change="updateForm('markerShape')"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
v-for="option in markerShapeOptions"
|
||||||
|
:key="option.value"
|
||||||
|
:value="option.value"
|
||||||
|
:selected="option.value == markerShape"
|
||||||
|
>
|
||||||
|
{{ option.name }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="grid-row">
|
||||||
|
<div class="grid-cell label"
|
||||||
|
title="Display markers visually denoting points in alarm."
|
||||||
|
>Alarm Markers</div>
|
||||||
|
<div class="grid-cell value">
|
||||||
|
<input v-model="alarmMarkers"
|
||||||
|
type="checkbox"
|
||||||
|
@change="updateForm('alarmMarkers')"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li v-show="markers || alarmMarkers"
|
||||||
|
class="grid-row"
|
||||||
|
>
|
||||||
|
<div class="grid-cell label"
|
||||||
|
title="The size of regular and alarm markers for this series."
|
||||||
|
>Marker Size:</div>
|
||||||
|
<div class="grid-cell value"><input v-model="markerSize"
|
||||||
|
class="c-input--flex"
|
||||||
|
type="text"
|
||||||
|
@change="updateForm('markerSize')"
|
||||||
|
></div>
|
||||||
|
</li>
|
||||||
|
<li v-show="interpolate !== 'none' || markers"
|
||||||
|
class="grid-row"
|
||||||
|
>
|
||||||
|
<div class="grid-cell label"
|
||||||
|
title="Manually set the plot line and marker color for this series."
|
||||||
|
>Color</div>
|
||||||
|
<div class="grid-cell value">
|
||||||
|
<div class="c-click-swatch c-click-swatch--menu"
|
||||||
|
@click="toggleSwatch()"
|
||||||
|
>
|
||||||
|
<span class="c-color-swatch"
|
||||||
|
:style="{ background: seriesColorAsHex }"
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="c-palette c-palette--color">
|
||||||
|
<div v-show="swatchActive"
|
||||||
|
class="c-palette__items"
|
||||||
|
>
|
||||||
|
<div v-for="(group, index) in colorPalette"
|
||||||
|
:key="index"
|
||||||
|
class="u-contents"
|
||||||
|
>
|
||||||
|
<div v-for="(color, colorIndex) in group"
|
||||||
|
:key="colorIndex"
|
||||||
|
class="c-palette__item"
|
||||||
|
:class="{ 'selected': series.get('color').equalTo(color) }"
|
||||||
|
:style="{ background: color.asHexString() }"
|
||||||
|
@click="setColor(color)"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</ul>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { MARKER_SHAPES } from "../../single/draw/MarkerShapes";
|
||||||
|
import { objectPath, validate, coerce } from "./formUtil";
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
inject: ['openmct', 'domainObject'],
|
||||||
|
props: {
|
||||||
|
series: {
|
||||||
|
type: Object,
|
||||||
|
default() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
expanded: false,
|
||||||
|
markerShapeOptions: [],
|
||||||
|
yKey: this.series.get('yKey'),
|
||||||
|
yKeyOptions: [],
|
||||||
|
interpolate: this.series.get('interpolate'),
|
||||||
|
markers: this.series.get('markers'),
|
||||||
|
markerShape: this.series.get('markerShape'),
|
||||||
|
alarmMarkers: this.series.get('alarmMarkers'),
|
||||||
|
markerSize: this.series.get('markerSize'),
|
||||||
|
validation: {},
|
||||||
|
swatchActive: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
colorPalette() {
|
||||||
|
return this.series.collection.palette.groups();
|
||||||
|
},
|
||||||
|
objectLabelCss() {
|
||||||
|
return this.status ? `c-object-label is-status--${this.status}'` : 'c-object-label';
|
||||||
|
},
|
||||||
|
seriesCss() {
|
||||||
|
let legacyObject = this.openmct.legacyObject(this.series.domainObject);
|
||||||
|
let type = legacyObject.getCapability('type');
|
||||||
|
|
||||||
|
return type ? `c-object-label__type-icon ${type.getCssClass()}` : `c-object-label__type-icon`;
|
||||||
|
},
|
||||||
|
linkCss() {
|
||||||
|
let cssClass = '';
|
||||||
|
let legacyObject = this.openmct.legacyObject(this.series.domainObject);
|
||||||
|
let location = legacyObject.getCapability('location');
|
||||||
|
if (location && location.isLink()) {
|
||||||
|
cssClass = 'l-icon-link';
|
||||||
|
}
|
||||||
|
|
||||||
|
return cssClass;
|
||||||
|
},
|
||||||
|
expandedCssClass() {
|
||||||
|
return this.expanded ? 'c-disclosure-triangle--expanded' : '';
|
||||||
|
},
|
||||||
|
seriesColorAsHex() {
|
||||||
|
return this.series.get('color').asHexString();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.initialize();
|
||||||
|
|
||||||
|
this.status = this.openmct.status.get(this.series.domainObject.identifier);
|
||||||
|
this.removeStatusListener = this.openmct.status.observe(this.series.domainObject.identifier, this.setStatus);
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
if (this.removeStatusListener) {
|
||||||
|
this.removeStatusListener();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initialize: function () {
|
||||||
|
this.fields = [
|
||||||
|
{
|
||||||
|
modelProp: 'yKey',
|
||||||
|
objectPath: this.dynamicPathForKey('yKey')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
modelProp: 'interpolate',
|
||||||
|
objectPath: this.dynamicPathForKey('interpolate')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
modelProp: 'markers',
|
||||||
|
objectPath: this.dynamicPathForKey('markers')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
modelProp: 'markerShape',
|
||||||
|
objectPath: this.dynamicPathForKey('markerShape')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
modelProp: 'markerSize',
|
||||||
|
coerce: Number,
|
||||||
|
objectPath: this.dynamicPathForKey('markerSize')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
modelProp: 'alarmMarkers',
|
||||||
|
coerce: Boolean,
|
||||||
|
objectPath: this.dynamicPathForKey('alarmMarkers')
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const metadata = this.series.metadata;
|
||||||
|
this.yKeyOptions = metadata
|
||||||
|
.valuesForHints(['range'])
|
||||||
|
.map(function (o) {
|
||||||
|
return {
|
||||||
|
name: o.key,
|
||||||
|
value: o.key
|
||||||
|
};
|
||||||
|
});
|
||||||
|
this.markerShapeOptions = Object.entries(MARKER_SHAPES)
|
||||||
|
.map(([key, obj]) => {
|
||||||
|
return {
|
||||||
|
name: obj.label,
|
||||||
|
value: key
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
dynamicPathForKey(key) {
|
||||||
|
return function (object, model) {
|
||||||
|
const modelIdentifier = model.get('identifier');
|
||||||
|
const index = object.configuration.series.findIndex(s => {
|
||||||
|
return _.isEqual(s.identifier, modelIdentifier);
|
||||||
|
});
|
||||||
|
|
||||||
|
return 'configuration.series[' + index + '].' + key;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Set the color for the current plot series. If the new color was
|
||||||
|
* already assigned to a different plot series, then swap the colors.
|
||||||
|
*/
|
||||||
|
setColor: function (color) {
|
||||||
|
const oldColor = this.series.get('color');
|
||||||
|
const otherSeriesWithColor = this.series.collection.filter(function (s) {
|
||||||
|
return s.get('color') === color;
|
||||||
|
})[0];
|
||||||
|
|
||||||
|
this.series.set('color', color);
|
||||||
|
|
||||||
|
const getPath = this.dynamicPathForKey('color');
|
||||||
|
const seriesColorPath = getPath(this.domainObject, this.series);
|
||||||
|
|
||||||
|
this.openmct.objects.mutate(
|
||||||
|
this.domainObject,
|
||||||
|
seriesColorPath,
|
||||||
|
color.asHexString()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (otherSeriesWithColor) {
|
||||||
|
otherSeriesWithColor.set('color', oldColor);
|
||||||
|
|
||||||
|
const otherSeriesColorPath = getPath(
|
||||||
|
this.domainObject,
|
||||||
|
otherSeriesWithColor
|
||||||
|
);
|
||||||
|
|
||||||
|
this.openmct.objects.mutate(
|
||||||
|
this.domainObject,
|
||||||
|
otherSeriesColorPath,
|
||||||
|
oldColor.asHexString()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
toggleExpanded() {
|
||||||
|
this.expanded = !this.expanded;
|
||||||
|
},
|
||||||
|
updateForm(formKey) {
|
||||||
|
const newVal = this[formKey];
|
||||||
|
const oldVal = this.series.get(formKey);
|
||||||
|
const formField = this.fields.find((field) => field.modelProp === formKey);
|
||||||
|
|
||||||
|
const path = objectPath(formField.objectPath);
|
||||||
|
const validationResult = validate(newVal, this.series, formField.validate);
|
||||||
|
if (validationResult === true) {
|
||||||
|
delete this.validation[formKey];
|
||||||
|
} else {
|
||||||
|
this.validation[formKey] = validationResult;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_.isEqual(coerce(newVal, formField.coerce), coerce(oldVal, formField.coerce))) {
|
||||||
|
this.series.set(formKey, coerce(newVal, formField.coerce));
|
||||||
|
if (path) {
|
||||||
|
this.openmct.objects.mutate(
|
||||||
|
this.domainObject,
|
||||||
|
path(this.domainObject, this.series),
|
||||||
|
coerce(newVal, formField.coerce)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setStatus(status) {
|
||||||
|
this.status = status;
|
||||||
|
},
|
||||||
|
toggleSwatch() {
|
||||||
|
this.swatchActive = !this.swatchActive;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
</script>
|
226
src/plugins/plot/vue/inspector/forms/YAxisForm.vue
Normal file
226
src/plugins/plot/vue/inspector/forms/YAxisForm.vue
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<ul class="l-inspector-part">
|
||||||
|
<h2>Y Axis</h2>
|
||||||
|
<li class="grid-row">
|
||||||
|
<div class="grid-cell label"
|
||||||
|
title="Manually override how the Y axis is labeled."
|
||||||
|
>Label</div>
|
||||||
|
<div class="grid-cell value"><input v-model="label"
|
||||||
|
class="c-input--flex"
|
||||||
|
type="text"
|
||||||
|
@change="updateForm('label')"
|
||||||
|
></div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<ul class="l-inspector-part">
|
||||||
|
<h2>Y Axis Scaling</h2>
|
||||||
|
<li class="grid-row">
|
||||||
|
<div class="grid-cell label"
|
||||||
|
title="Automatically scale the Y axis to keep all values in view."
|
||||||
|
>Auto scale</div>
|
||||||
|
<div class="grid-cell value"><input v-model="autoscale"
|
||||||
|
type="checkbox"
|
||||||
|
@change="updateForm('autoscale')"
|
||||||
|
></div>
|
||||||
|
</li>
|
||||||
|
<li v-show="autoscale"
|
||||||
|
class="grid-row"
|
||||||
|
>
|
||||||
|
<div class="grid-cell label"
|
||||||
|
title="Percentage of padding above and below plotted min and max values. 0.1, 1.0, etc."
|
||||||
|
>
|
||||||
|
Padding</div>
|
||||||
|
<div class="grid-cell value">
|
||||||
|
<input v-model="autoscalePadding"
|
||||||
|
class="c-input--flex"
|
||||||
|
type="text"
|
||||||
|
@change="updateForm('autoscalePadding')"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<ul v-show="!autoscale"
|
||||||
|
class="l-inspector-part"
|
||||||
|
>
|
||||||
|
<div v-show="!autoscale && validation.range"
|
||||||
|
class="grid-span-all form-error"
|
||||||
|
>
|
||||||
|
{{ validation.range }}
|
||||||
|
</div>
|
||||||
|
<li class="grid-row force-border">
|
||||||
|
<div class="grid-cell label"
|
||||||
|
title="Minimum Y axis value."
|
||||||
|
>Minimum Value</div>
|
||||||
|
<div class="grid-cell value">
|
||||||
|
<input v-model="rangeMin"
|
||||||
|
class="c-input--flex"
|
||||||
|
type="number"
|
||||||
|
@change="updateForm('range')"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="grid-row">
|
||||||
|
<div class="grid-cell label"
|
||||||
|
title="Maximum Y axis value."
|
||||||
|
>Maximum Value</div>
|
||||||
|
<div class="grid-cell value"><input v-model="rangeMax"
|
||||||
|
class="c-input--flex"
|
||||||
|
type="number"
|
||||||
|
@change="updateForm('range')"
|
||||||
|
></div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { objectPath, validate, coerce } from "./formUtil";
|
||||||
|
import _ from "lodash";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
inject: ['openmct', 'domainObject'],
|
||||||
|
props: {
|
||||||
|
yAxis: {
|
||||||
|
type: Object,
|
||||||
|
default() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
label: '',
|
||||||
|
autoscale: '',
|
||||||
|
autoscalePadding: '',
|
||||||
|
rangeMin: '',
|
||||||
|
rangeMax: '',
|
||||||
|
validation: {}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.initialize();
|
||||||
|
this.initFormValues();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initialize: function () {
|
||||||
|
this.fields = [
|
||||||
|
{
|
||||||
|
modelProp: 'label',
|
||||||
|
objectPath: 'configuration.yAxis.label'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
modelProp: 'autoscale',
|
||||||
|
coerce: Boolean,
|
||||||
|
objectPath: 'configuration.yAxis.autoscale'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
modelProp: 'autoscalePadding',
|
||||||
|
coerce: Number,
|
||||||
|
objectPath: 'configuration.yAxis.autoscalePadding'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
modelProp: 'range',
|
||||||
|
objectPath: 'configuration.yAxis.range',
|
||||||
|
coerce: function coerceRange(range) {
|
||||||
|
if (!range) {
|
||||||
|
return {
|
||||||
|
min: 0,
|
||||||
|
max: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const newRange = {};
|
||||||
|
if (typeof range.min !== 'undefined' && range.min !== null) {
|
||||||
|
newRange.min = Number(range.min);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof range.max !== 'undefined' && range.max !== null) {
|
||||||
|
newRange.max = Number(range.max);
|
||||||
|
}
|
||||||
|
|
||||||
|
return newRange;
|
||||||
|
},
|
||||||
|
validate: function validateRange(range, model) {
|
||||||
|
if (!range) {
|
||||||
|
return 'Need range';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (range.min === '' || range.min === null || typeof range.min === 'undefined') {
|
||||||
|
return 'Must specify Minimum';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (range.max === '' || range.max === null || typeof range.max === 'undefined') {
|
||||||
|
return 'Must specify Maximum';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Number.isNaN(Number(range.min))) {
|
||||||
|
return 'Minimum must be a number.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Number.isNaN(Number(range.max))) {
|
||||||
|
return 'Maximum must be a number.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Number(range.min) > Number(range.max)) {
|
||||||
|
return 'Minimum must be less than Maximum.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (model.get('autoscale')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
},
|
||||||
|
initFormValues() {
|
||||||
|
this.label = this.yAxis.get('label');
|
||||||
|
this.autoscale = this.yAxis.get('autoscale');
|
||||||
|
this.autoscalePadding = this.yAxis.get('autoscalePadding');
|
||||||
|
const range = this.yAxis.get('range');
|
||||||
|
if (range) {
|
||||||
|
this.rangeMin = range.min;
|
||||||
|
this.rangeMax = range.max;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateForm(formKey) {
|
||||||
|
let newVal;
|
||||||
|
if (formKey === 'range') {
|
||||||
|
newVal = {
|
||||||
|
min: this.rangeMin,
|
||||||
|
max: this.rangeMax
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
newVal = this[formKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
const oldVal = this.yAxis.get(formKey);
|
||||||
|
const formField = this.fields.find((field) => field.modelProp === formKey);
|
||||||
|
|
||||||
|
const path = objectPath(formField.objectPath);
|
||||||
|
const validationResult = validate(newVal, this.yAxis, formField.validate);
|
||||||
|
if (validationResult === true) {
|
||||||
|
delete this.validation[formKey];
|
||||||
|
} else {
|
||||||
|
this.validation[formKey] = validationResult;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_.isEqual(coerce(newVal, formField.coerce), coerce(oldVal, formField.coerce))) {
|
||||||
|
this.yAxis.set(formKey, coerce(newVal, formField.coerce));
|
||||||
|
if (path) {
|
||||||
|
this.openmct.objects.mutate(
|
||||||
|
this.domainObject,
|
||||||
|
path(this.domainObject, this.yAxis),
|
||||||
|
coerce(newVal, formField.coerce)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
</script>
|
29
src/plugins/plot/vue/inspector/forms/formUtil.js
Normal file
29
src/plugins/plot/vue/inspector/forms/formUtil.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
export function coerce(value, coerceFunc) {
|
||||||
|
if (coerceFunc) {
|
||||||
|
return coerceFunc(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function validate(value, model, validateFunc) {
|
||||||
|
if (validateFunc) {
|
||||||
|
return validateFunc(value, model);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function objectPath(path) {
|
||||||
|
if (path) {
|
||||||
|
if (typeof path !== "function") {
|
||||||
|
const staticObjectPath = path;
|
||||||
|
|
||||||
|
return function (object, model) {
|
||||||
|
return staticObjectPath;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
}
|
@ -23,12 +23,14 @@
|
|||||||
import PlotViewProvider from './PlotViewProvider';
|
import PlotViewProvider from './PlotViewProvider';
|
||||||
import OverlayPlotViewProvider from '../overlayPlot/OverlayPlotViewProvider';
|
import OverlayPlotViewProvider from '../overlayPlot/OverlayPlotViewProvider';
|
||||||
import StackedPlotViewProvider from '../stackedPlot/StackedPlotViewProvider';
|
import StackedPlotViewProvider from '../stackedPlot/StackedPlotViewProvider';
|
||||||
|
import PlotsInspectorViewProvider from '../inspector/PlotsInspectorViewProvider';
|
||||||
|
|
||||||
export default function () {
|
export default function () {
|
||||||
return function install(openmct) {
|
return function install(openmct) {
|
||||||
openmct.objectViews.addProvider(new StackedPlotViewProvider(openmct));
|
openmct.objectViews.addProvider(new StackedPlotViewProvider(openmct));
|
||||||
openmct.objectViews.addProvider(new OverlayPlotViewProvider(openmct));
|
openmct.objectViews.addProvider(new OverlayPlotViewProvider(openmct));
|
||||||
openmct.objectViews.addProvider(new PlotViewProvider(openmct));
|
openmct.objectViews.addProvider(new PlotViewProvider(openmct));
|
||||||
|
openmct.inspectorViews.addProvider(new PlotsInspectorViewProvider(openmct));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,6 +26,8 @@ import Vue from "vue";
|
|||||||
import StackedPlot from "../stackedPlot/StackedPlot.vue";
|
import StackedPlot from "../stackedPlot/StackedPlot.vue";
|
||||||
import configStore from "@/plugins/plot/vue/single/configuration/configStore";
|
import configStore from "@/plugins/plot/vue/single/configuration/configStore";
|
||||||
import EventEmitter from "EventEmitter";
|
import EventEmitter from "EventEmitter";
|
||||||
|
import PlotOptions from "../inspector/PlotOptions.vue";
|
||||||
|
import PlotConfigurationModel from "@/plugins/plot/vue/single/configuration/PlotConfigurationModel";
|
||||||
|
|
||||||
describe("the plugin", function () {
|
describe("the plugin", function () {
|
||||||
let element;
|
let element;
|
||||||
@ -174,6 +176,35 @@ describe("the plugin", function () {
|
|||||||
expect(plotView).toBeDefined();
|
expect(plotView).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('provides an inspector view for overlay plots', () => {
|
||||||
|
let selection = [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
context: {
|
||||||
|
item: {
|
||||||
|
id: "test-object",
|
||||||
|
type: "telemetry.plot.overlay",
|
||||||
|
telemetry: {
|
||||||
|
values: [{
|
||||||
|
key: "some-key"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
context: {
|
||||||
|
item: {
|
||||||
|
type: 'time-strip'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
];
|
||||||
|
const plotInspectorView = openmct.inspectorViews.get(selection);
|
||||||
|
expect(plotInspectorView.length).toEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
it("provides a stacked plot view for objects with telemetry", () => {
|
it("provides a stacked plot view for objects with telemetry", () => {
|
||||||
const testTelemetryObject = {
|
const testTelemetryObject = {
|
||||||
id: "test-object",
|
id: "test-object",
|
||||||
@ -578,4 +609,218 @@ describe("the plugin", function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('the inspector view', () => {
|
||||||
|
let component;
|
||||||
|
let viewComponentObject;
|
||||||
|
let mockComposition;
|
||||||
|
let testTelemetryObject;
|
||||||
|
let selection;
|
||||||
|
let config;
|
||||||
|
beforeEach((done) => {
|
||||||
|
testTelemetryObject = {
|
||||||
|
identifier: {
|
||||||
|
namespace: "",
|
||||||
|
key: "test-object"
|
||||||
|
},
|
||||||
|
type: "test-object",
|
||||||
|
name: "Test Object",
|
||||||
|
telemetry: {
|
||||||
|
values: [{
|
||||||
|
key: "utc",
|
||||||
|
format: "utc",
|
||||||
|
name: "Time",
|
||||||
|
hints: {
|
||||||
|
domain: 1
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: "some-key",
|
||||||
|
name: "Some attribute",
|
||||||
|
hints: {
|
||||||
|
range: 1
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: "some-other-key",
|
||||||
|
name: "Another attribute",
|
||||||
|
hints: {
|
||||||
|
range: 2
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
selection = [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
context: {
|
||||||
|
item: {
|
||||||
|
id: "test-object",
|
||||||
|
identifier: {
|
||||||
|
key: "test-object",
|
||||||
|
namespace: ''
|
||||||
|
},
|
||||||
|
type: "telemetry.plot.overlay",
|
||||||
|
configuration: {
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
identifier: {
|
||||||
|
key: "test-object",
|
||||||
|
namespace: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
composition: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
context: {
|
||||||
|
item: {
|
||||||
|
type: 'time-strip',
|
||||||
|
identifier: {
|
||||||
|
key: 'some-other-key',
|
||||||
|
namespace: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
mockComposition = new EventEmitter();
|
||||||
|
mockComposition.load = () => {
|
||||||
|
mockComposition.emit('add', testTelemetryObject);
|
||||||
|
|
||||||
|
return [testTelemetryObject];
|
||||||
|
};
|
||||||
|
|
||||||
|
spyOn(openmct.composition, 'get').and.returnValue(mockComposition);
|
||||||
|
|
||||||
|
const configId = openmct.objects.makeKeyString(selection[0][0].context.item.identifier);
|
||||||
|
config = new PlotConfigurationModel({
|
||||||
|
id: configId,
|
||||||
|
domainObject: selection[0][0].context.item,
|
||||||
|
openmct: openmct
|
||||||
|
});
|
||||||
|
configStore.add(configId, config);
|
||||||
|
|
||||||
|
let viewContainer = document.createElement('div');
|
||||||
|
child.append(viewContainer);
|
||||||
|
component = new Vue({
|
||||||
|
el: viewContainer,
|
||||||
|
components: {
|
||||||
|
PlotOptions
|
||||||
|
},
|
||||||
|
provide: {
|
||||||
|
openmct: openmct,
|
||||||
|
domainObject: selection[0][0].context.item,
|
||||||
|
path: [selection[0][0].context.item, selection[0][1].context.item]
|
||||||
|
},
|
||||||
|
template: '<plot-options/>'
|
||||||
|
});
|
||||||
|
|
||||||
|
Vue.nextTick(() => {
|
||||||
|
viewComponentObject = component.$root.$children[0];
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('in view only mode', () => {
|
||||||
|
let browseOptionsEl;
|
||||||
|
let editOptionsEl;
|
||||||
|
beforeEach(() => {
|
||||||
|
browseOptionsEl = viewComponentObject.$el.querySelector('.js-plot-options-browse');
|
||||||
|
editOptionsEl = viewComponentObject.$el.querySelector('.js-plot-options-edit');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not show the edit options', () => {
|
||||||
|
expect(editOptionsEl).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows the name', () => {
|
||||||
|
const seriesEl = browseOptionsEl.querySelector('.c-object-label__name');
|
||||||
|
expect(seriesEl.innerHTML).toEqual(testTelemetryObject.name);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows in collapsed mode', () => {
|
||||||
|
const seriesEl = browseOptionsEl.querySelectorAll('.c-disclosure-triangle--expanded');
|
||||||
|
expect(seriesEl.length).toEqual(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows in expanded mode', () => {
|
||||||
|
let expandControl = browseOptionsEl.querySelector(".c-disclosure-triangle");
|
||||||
|
const clickEvent = createMouseEvent("click");
|
||||||
|
expandControl.dispatchEvent(clickEvent);
|
||||||
|
|
||||||
|
const plotOptionsProperties = browseOptionsEl.querySelectorAll('.js-plot-options-browse-properties .grid-row');
|
||||||
|
expect(plotOptionsProperties.length).toEqual(5);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('in edit mode', () => {
|
||||||
|
let editOptionsEl;
|
||||||
|
let browseOptionsEl;
|
||||||
|
|
||||||
|
beforeEach((done) => {
|
||||||
|
viewComponentObject.setEditState(true);
|
||||||
|
Vue.nextTick(() => {
|
||||||
|
editOptionsEl = viewComponentObject.$el.querySelector('.js-plot-options-edit');
|
||||||
|
browseOptionsEl = viewComponentObject.$el.querySelector('.js-plot-options-browse');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not show the browse options', () => {
|
||||||
|
expect(browseOptionsEl).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows the name', () => {
|
||||||
|
const seriesEl = editOptionsEl.querySelector('.c-object-label__name');
|
||||||
|
expect(seriesEl.innerHTML).toEqual(testTelemetryObject.name);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows in collapsed mode', () => {
|
||||||
|
const seriesEl = editOptionsEl.querySelectorAll('.c-disclosure-triangle--expanded');
|
||||||
|
expect(seriesEl.length).toEqual(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows in collapsed mode', () => {
|
||||||
|
const seriesEl = editOptionsEl.querySelectorAll('.c-disclosure-triangle--expanded');
|
||||||
|
expect(seriesEl.length).toEqual(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders expanded', () => {
|
||||||
|
const expandControl = editOptionsEl.querySelector(".c-disclosure-triangle");
|
||||||
|
const clickEvent = createMouseEvent("click");
|
||||||
|
expandControl.dispatchEvent(clickEvent);
|
||||||
|
|
||||||
|
const plotOptionsProperties = editOptionsEl.querySelectorAll(".js-plot-options-edit-properties .grid-row");
|
||||||
|
expect(plotOptionsProperties.length).toEqual(6);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows yKeyOptions', () => {
|
||||||
|
const expandControl = editOptionsEl.querySelector(".c-disclosure-triangle");
|
||||||
|
const clickEvent = createMouseEvent("click");
|
||||||
|
expandControl.dispatchEvent(clickEvent);
|
||||||
|
|
||||||
|
const plotOptionsProperties = editOptionsEl.querySelectorAll(".js-plot-options-edit-properties .grid-row");
|
||||||
|
|
||||||
|
const yKeySelection = plotOptionsProperties[0].querySelector('select');
|
||||||
|
const options = Array.from(yKeySelection.options).map((option) => {
|
||||||
|
return option.value;
|
||||||
|
});
|
||||||
|
expect(options).toEqual([testTelemetryObject.telemetry.values[1].key, testTelemetryObject.telemetry.values[2].key]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows yAxis options', () => {
|
||||||
|
const expandControl = editOptionsEl.querySelector(".c-disclosure-triangle");
|
||||||
|
const clickEvent = createMouseEvent("click");
|
||||||
|
expandControl.dispatchEvent(clickEvent);
|
||||||
|
|
||||||
|
const yAxisProperties = editOptionsEl.querySelectorAll("div.grid-properties:first-of-type .l-inspector-part");
|
||||||
|
expect(yAxisProperties.length).toEqual(3);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
113
src/plugins/timeline/TimelineObjectView.vue
Normal file
113
src/plugins/timeline/TimelineObjectView.vue
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2021, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
<template>
|
||||||
|
<swim-lane :icon-class="item.type.definition.cssClass"
|
||||||
|
:min-height="item.height"
|
||||||
|
:show-ucontents="item.domainObject.type === 'plan'"
|
||||||
|
:span-rows-count="item.rowCount"
|
||||||
|
>
|
||||||
|
<template slot="label">
|
||||||
|
{{ item.domainObject.name }}
|
||||||
|
</template>
|
||||||
|
<object-view
|
||||||
|
ref="objectView"
|
||||||
|
slot="object"
|
||||||
|
class="u-contents"
|
||||||
|
:default-object="item.domainObject"
|
||||||
|
:object-view-key="item.viewKey"
|
||||||
|
:object-path="item.objectPath"
|
||||||
|
/>
|
||||||
|
</swim-lane>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import ObjectView from '@/ui/components/ObjectView.vue';
|
||||||
|
import SwimLane from "@/ui/components/swim-lane/SwimLane.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
ObjectView,
|
||||||
|
SwimLane
|
||||||
|
},
|
||||||
|
inject: ['openmct'],
|
||||||
|
props: {
|
||||||
|
item: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
domainObject: undefined,
|
||||||
|
mutablePromise: undefined
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
item(newItem) {
|
||||||
|
if (!this.context) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.context.item = newItem.domainObject;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
if (this.openmct.objects.supportsMutation(this.item.domainObject.identifier)) {
|
||||||
|
this.mutablePromise = this.openmct.objects.getMutable(this.item.domainObject.identifier)
|
||||||
|
.then(this.setObject);
|
||||||
|
} else {
|
||||||
|
this.openmct.objects.get(this.item.domainObject.identifier)
|
||||||
|
.then(this.setObject);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
if (this.removeSelectable) {
|
||||||
|
this.removeSelectable();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.mutablePromise) {
|
||||||
|
this.mutablePromise.then(() => {
|
||||||
|
this.openmct.objects.destroyMutable(this.domainObject);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.openmct.objects.destroyMutable(this.domainObject);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
setObject(domainObject) {
|
||||||
|
this.domainObject = domainObject;
|
||||||
|
this.mutablePromise = undefined;
|
||||||
|
this.$nextTick(() => {
|
||||||
|
let reference = this.$refs.objectView;
|
||||||
|
|
||||||
|
if (reference) {
|
||||||
|
let childContext = this.$refs.objectView.getSelectionContext();
|
||||||
|
childContext.item = domainObject;
|
||||||
|
this.context = childContext;
|
||||||
|
this.removeSelectable = this.openmct.selection.selectable(
|
||||||
|
this.$el, this.context);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
@ -52,22 +52,10 @@
|
|||||||
:key="item.keyString"
|
:key="item.keyString"
|
||||||
class="u-contents c-timeline__content"
|
class="u-contents c-timeline__content"
|
||||||
>
|
>
|
||||||
<swim-lane :icon-class="item.type.definition.cssClass"
|
<timeline-object-view
|
||||||
:min-height="item.height"
|
|
||||||
:show-ucontents="item.domainObject.type === 'plan'"
|
|
||||||
:span-rows-count="item.rowCount"
|
|
||||||
>
|
|
||||||
<template slot="label">
|
|
||||||
{{ item.domainObject.name }}
|
|
||||||
</template>
|
|
||||||
<object-view
|
|
||||||
slot="object"
|
|
||||||
class="u-contents"
|
class="u-contents"
|
||||||
:default-object="item.domainObject"
|
:item="item"
|
||||||
:object-view-key="item.viewKey"
|
|
||||||
:object-path="item.objectPath"
|
|
||||||
/>
|
/>
|
||||||
</swim-lane>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -75,7 +63,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import ObjectView from '@/ui/components/ObjectView.vue';
|
import TimelineObjectView from './TimelineObjectView.vue';
|
||||||
import TimelineAxis from '../../ui/components/TimeSystemAxis.vue';
|
import TimelineAxis from '../../ui/components/TimeSystemAxis.vue';
|
||||||
import SwimLane from "@/ui/components/swim-lane/SwimLane.vue";
|
import SwimLane from "@/ui/components/swim-lane/SwimLane.vue";
|
||||||
import { getValidatedPlan } from "../plan/util";
|
import { getValidatedPlan } from "../plan/util";
|
||||||
@ -101,7 +89,7 @@ function getViewKey(domainObject, objectPath, openmct) {
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
ObjectView,
|
TimelineObjectView,
|
||||||
TimelineAxis,
|
TimelineAxis,
|
||||||
SwimLane
|
SwimLane
|
||||||
},
|
},
|
||||||
@ -158,6 +146,7 @@ export default {
|
|||||||
},
|
},
|
||||||
removeItem(identifier) {
|
removeItem(identifier) {
|
||||||
let index = this.items.findIndex(item => this.openmct.objects.areIdsEqual(identifier, item.domainObject.identifier));
|
let index = this.items.findIndex(item => this.openmct.objects.areIdsEqual(identifier, item.domainObject.identifier));
|
||||||
|
this.removeSelectable(this.items[index]);
|
||||||
this.items.splice(index, 1);
|
this.items.splice(index, 1);
|
||||||
},
|
},
|
||||||
reorder(reorderPlan) {
|
reorder(reorderPlan) {
|
||||||
|
@ -21,7 +21,6 @@
|
|||||||
<div class="c-swimlane__lane-object"
|
<div class="c-swimlane__lane-object"
|
||||||
:style="{'min-height': minHeight}"
|
:style="{'min-height': minHeight}"
|
||||||
:class="{'u-contents': showUcontents}"
|
:class="{'u-contents': showUcontents}"
|
||||||
data-selectable
|
|
||||||
>
|
>
|
||||||
<slot name="object"></slot>
|
<slot name="object"></slot>
|
||||||
</div>
|
</div>
|
||||||
|
Reference in New Issue
Block a user