mirror of
https://github.com/nasa/openmct.git
synced 2025-05-15 15:02:51 +00:00
Imagery view for time strip (#4114)
* Imagery view for time strip Co-authored-by: charlesh88 <charles.f.hacskaylo@nasa.gov> Co-authored-by: Andrew Henry <akhenry@gmail.com> Co-authored-by: Jamie V <jamie.j.vigliotta@nasa.gov>
This commit is contained in:
parent
aa5edb0b83
commit
dad9f12a5c
73
src/plugins/imagery/ImageryTimestripViewProvider.js
Normal file
73
src/plugins/imagery/ImageryTimestripViewProvider.js
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
import ImageryTimeView from './components/ImageryTimeView.vue';
|
||||||
|
import Vue from "vue";
|
||||||
|
|
||||||
|
export default function ImageryTimestripViewProvider(openmct) {
|
||||||
|
const type = 'example.imagery.time-strip.view';
|
||||||
|
|
||||||
|
function hasImageTelemetry(domainObject) {
|
||||||
|
const metadata = openmct.telemetry.getMetadata(domainObject);
|
||||||
|
if (!metadata) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return metadata.valuesForHints(['image']).length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
key: type,
|
||||||
|
name: 'Imagery Timestrip View',
|
||||||
|
cssClass: 'icon-image',
|
||||||
|
canView: function (domainObject, objectPath) {
|
||||||
|
let isChildOfTimeStrip = objectPath.find(object => object.type === 'time-strip');
|
||||||
|
|
||||||
|
return hasImageTelemetry(domainObject) && isChildOfTimeStrip && !openmct.router.isNavigatedObject(objectPath);
|
||||||
|
},
|
||||||
|
view: function (domainObject, objectPath) {
|
||||||
|
let component;
|
||||||
|
|
||||||
|
return {
|
||||||
|
show: function (element) {
|
||||||
|
component = new Vue({
|
||||||
|
el: element,
|
||||||
|
components: {
|
||||||
|
ImageryTimeView
|
||||||
|
},
|
||||||
|
provide: {
|
||||||
|
openmct: openmct,
|
||||||
|
domainObject: domainObject,
|
||||||
|
objectPath: objectPath
|
||||||
|
},
|
||||||
|
template: '<imagery-time-view></imagery-time-view>'
|
||||||
|
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
destroy: function () {
|
||||||
|
component.$destroy();
|
||||||
|
component = undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
import ImageryViewLayout from './components/ImageryViewLayout.vue';
|
import ImageryViewComponent from './components/ImageryView.vue';
|
||||||
|
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
|
||||||
@ -14,7 +14,7 @@ export default class ImageryView {
|
|||||||
this.component = new Vue({
|
this.component = new Vue({
|
||||||
el: element,
|
el: element,
|
||||||
components: {
|
components: {
|
||||||
ImageryViewLayout
|
'imagery-view': ImageryViewComponent
|
||||||
},
|
},
|
||||||
provide: {
|
provide: {
|
||||||
openmct: this.openmct,
|
openmct: this.openmct,
|
||||||
@ -22,7 +22,8 @@ export default class ImageryView {
|
|||||||
objectPath: this.objectPath,
|
objectPath: this.objectPath,
|
||||||
currentView: this
|
currentView: this
|
||||||
},
|
},
|
||||||
template: '<imagery-view-layout ref="ImageryLayout"></imagery-view-layout>'
|
template: '<imagery-view ref="ImageryContainer"></imagery-view>'
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,8 +37,10 @@ export default function ImageryViewProvider(openmct) {
|
|||||||
key: type,
|
key: type,
|
||||||
name: 'Imagery Layout',
|
name: 'Imagery Layout',
|
||||||
cssClass: 'icon-image',
|
cssClass: 'icon-image',
|
||||||
canView: function (domainObject) {
|
canView: function (domainObject, objectPath) {
|
||||||
return hasImageTelemetry(domainObject);
|
let isChildOfTimeStrip = objectPath.find(object => object.type === 'time-strip');
|
||||||
|
|
||||||
|
return hasImageTelemetry(domainObject) && (!isChildOfTimeStrip || openmct.router.isNavigatedObject(objectPath));
|
||||||
},
|
},
|
||||||
view: function (domainObject, objectPath) {
|
view: function (domainObject, objectPath) {
|
||||||
return new ImageryView(openmct, domainObject, objectPath);
|
return new ImageryView(openmct, domainObject, objectPath);
|
||||||
|
476
src/plugins/imagery/components/ImageryTimeView.vue
Normal file
476
src/plugins/imagery/components/ImageryTimeView.vue
Normal file
@ -0,0 +1,476 @@
|
|||||||
|
<!--
|
||||||
|
Open MCT, Copyright (c) 2014-2021, United States Government
|
||||||
|
as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
Administration. All rights reserved.
|
||||||
|
|
||||||
|
Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
License for the specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
|
||||||
|
Open MCT includes source code licensed under additional open source
|
||||||
|
licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
this source code distribution or the Licensing information page available
|
||||||
|
at runtime from the About dialog for additional information.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div ref="imagery"
|
||||||
|
class="c-imagery-tsv c-timeline-holder"
|
||||||
|
>
|
||||||
|
<div ref="imageryHolder"
|
||||||
|
class="c-imagery-tsv__contents u-contents"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import * as d3Scale from 'd3-scale';
|
||||||
|
import SwimLane from "@/ui/components/swim-lane/SwimLane.vue";
|
||||||
|
import Vue from "vue";
|
||||||
|
import imageryData from "../../imagery/mixins/imageryData";
|
||||||
|
import PreviewAction from "@/ui/preview/PreviewAction";
|
||||||
|
import _ from "lodash";
|
||||||
|
|
||||||
|
const PADDING = 1;
|
||||||
|
const RESIZE_POLL_INTERVAL = 200;
|
||||||
|
const ROW_HEIGHT = 100;
|
||||||
|
const IMAGE_WIDTH_THRESHOLD = 40;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
mixins: [imageryData],
|
||||||
|
inject: ['openmct', 'domainObject', 'objectPath'],
|
||||||
|
data() {
|
||||||
|
let timeSystem = this.openmct.time.timeSystem();
|
||||||
|
this.metadata = {};
|
||||||
|
this.requestCount = 0;
|
||||||
|
|
||||||
|
return {
|
||||||
|
viewBounds: undefined,
|
||||||
|
height: 0,
|
||||||
|
durationFormatter: undefined,
|
||||||
|
imageHistory: [],
|
||||||
|
timeSystem: timeSystem,
|
||||||
|
keyString: undefined
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
imageHistorySize() {
|
||||||
|
return this.imageHistory.length;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
imageHistorySize(newSize, oldSize) {
|
||||||
|
this.updatePlotImagery();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.previewAction = new PreviewAction(this.openmct);
|
||||||
|
|
||||||
|
this.canvas = this.$refs.imagery.appendChild(document.createElement('canvas'));
|
||||||
|
this.canvas.height = 0;
|
||||||
|
this.canvasContext = this.canvas.getContext('2d');
|
||||||
|
this.setDimensions();
|
||||||
|
|
||||||
|
this.updateViewBounds();
|
||||||
|
|
||||||
|
this.openmct.time.on("timeSystem", this.setScaleAndPlotImagery);
|
||||||
|
this.openmct.time.on("bounds", this.updateViewBounds);
|
||||||
|
|
||||||
|
this.resize = _.debounce(this.resize, 400);
|
||||||
|
this.imageryStripResizeObserver = new ResizeObserver(this.resize);
|
||||||
|
this.imageryStripResizeObserver.observe(this.$refs.imagery);
|
||||||
|
|
||||||
|
this.unlisten = this.openmct.objects.observe(this.domainObject, '*', this.observeForChanges);
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
if (this.unsubscribe) {
|
||||||
|
this.unsubscribe();
|
||||||
|
delete this.unsubscribe;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.imageryStripResizeObserver) {
|
||||||
|
this.imageryStripResizeObserver.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.openmct.time.off("timeSystem", this.setScaleAndPlotImagery);
|
||||||
|
this.openmct.time.off("bounds", this.updateViewBounds);
|
||||||
|
if (this.unlisten) {
|
||||||
|
this.unlisten();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
expand(index) {
|
||||||
|
const path = this.objectPath[0];
|
||||||
|
this.previewAction.invoke([path]);
|
||||||
|
},
|
||||||
|
observeForChanges(mutatedObject) {
|
||||||
|
this.updateViewBounds();
|
||||||
|
},
|
||||||
|
resize() {
|
||||||
|
let clientWidth = this.getClientWidth();
|
||||||
|
if (clientWidth !== this.width) {
|
||||||
|
this.setDimensions();
|
||||||
|
this.updateViewBounds();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getClientWidth() {
|
||||||
|
let clientWidth = this.$refs.imagery.clientWidth;
|
||||||
|
|
||||||
|
if (!clientWidth) {
|
||||||
|
//this is a hack - need a better way to find the parent of this component
|
||||||
|
let parent = this.openmct.layout.$refs.browseObject.$el;
|
||||||
|
if (parent) {
|
||||||
|
clientWidth = parent.getBoundingClientRect().width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return clientWidth;
|
||||||
|
},
|
||||||
|
updateViewBounds(bounds, isTick) {
|
||||||
|
this.viewBounds = this.openmct.time.bounds();
|
||||||
|
//Add a 50% padding to the end bounds to look ahead
|
||||||
|
let timespan = (this.viewBounds.end - this.viewBounds.start);
|
||||||
|
let padding = timespan / 2;
|
||||||
|
this.viewBounds.end = this.viewBounds.end + padding;
|
||||||
|
|
||||||
|
if (this.timeSystem === undefined) {
|
||||||
|
this.timeSystem = this.openmct.time.timeSystem();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setScaleAndPlotImagery(this.timeSystem, !isTick);
|
||||||
|
|
||||||
|
},
|
||||||
|
setScaleAndPlotImagery(timeSystem, clearAllImagery) {
|
||||||
|
if (timeSystem !== undefined) {
|
||||||
|
this.timeSystem = timeSystem;
|
||||||
|
this.timeFormatter = this.getFormatter(this.timeSystem.key);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setScale(this.timeSystem);
|
||||||
|
this.updatePlotImagery(clearAllImagery);
|
||||||
|
},
|
||||||
|
getFormatter(key) {
|
||||||
|
const metadata = this.openmct.telemetry.getMetadata(this.domainObject);
|
||||||
|
|
||||||
|
let metadataValue = metadata.value(key) || { format: key };
|
||||||
|
let valueFormatter = this.openmct.telemetry.getValueFormatter(metadataValue);
|
||||||
|
|
||||||
|
return valueFormatter;
|
||||||
|
},
|
||||||
|
updatePlotImagery(clearAllImagery) {
|
||||||
|
this.clearPreviousImagery(clearAllImagery);
|
||||||
|
if (this.xScale) {
|
||||||
|
this.drawImagery();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clearPreviousImagery(clearAllImagery) {
|
||||||
|
//TODO: Only clear items that are out of bounds
|
||||||
|
let noItemsEl = this.$el.querySelectorAll(".c-imagery-tsv__no-items");
|
||||||
|
noItemsEl.forEach(item => {
|
||||||
|
item.remove();
|
||||||
|
});
|
||||||
|
let imagery = this.$el.querySelectorAll(".c-imagery-tsv__image-wrapper");
|
||||||
|
imagery.forEach(item => {
|
||||||
|
if (clearAllImagery) {
|
||||||
|
item.remove();
|
||||||
|
} else {
|
||||||
|
const id = this.getNSAttributesForElement(item, 'id');
|
||||||
|
if (id) {
|
||||||
|
const timestamp = id.replace('id-', '');
|
||||||
|
if (!this.isImageryInBounds({
|
||||||
|
time: timestamp
|
||||||
|
})) {
|
||||||
|
item.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
setDimensions() {
|
||||||
|
const imageryHolder = this.$refs.imagery;
|
||||||
|
this.width = this.getClientWidth();
|
||||||
|
|
||||||
|
this.height = Math.round(imageryHolder.getBoundingClientRect().height);
|
||||||
|
},
|
||||||
|
setScale(timeSystem) {
|
||||||
|
if (!this.width) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timeSystem === undefined) {
|
||||||
|
timeSystem = this.openmct.time.timeSystem();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timeSystem.isUTCBased) {
|
||||||
|
this.xScale = d3Scale.scaleUtc();
|
||||||
|
this.xScale.domain(
|
||||||
|
[new Date(this.viewBounds.start), new Date(this.viewBounds.end)]
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.xScale = d3Scale.scaleLinear();
|
||||||
|
this.xScale.domain(
|
||||||
|
[this.viewBounds.start, this.viewBounds.end]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.xScale.range([PADDING, this.width - PADDING * 2]);
|
||||||
|
},
|
||||||
|
isImageryInBounds(imageObj) {
|
||||||
|
return (imageObj.time < this.viewBounds.end) && (imageObj.time > this.viewBounds.start);
|
||||||
|
},
|
||||||
|
getImageryContainer() {
|
||||||
|
let svgHeight = 100;
|
||||||
|
let svgWidth = this.imageHistory.length ? this.width : 200;
|
||||||
|
let groupSVG;
|
||||||
|
|
||||||
|
let existingSVG = this.$el.querySelector(".c-imagery-tsv__contents svg");
|
||||||
|
if (existingSVG) {
|
||||||
|
groupSVG = existingSVG;
|
||||||
|
this.setNSAttributesForElement(groupSVG, {
|
||||||
|
width: svgWidth
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
let component = new Vue({
|
||||||
|
components: {
|
||||||
|
SwimLane
|
||||||
|
},
|
||||||
|
provide: {
|
||||||
|
openmct: this.openmct
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isNested: true,
|
||||||
|
height: svgHeight,
|
||||||
|
width: svgWidth
|
||||||
|
};
|
||||||
|
},
|
||||||
|
template: `<swim-lane :is-nested="isNested" :hide-label="true"><template slot="object"><svg class="c-imagery-tsv-container" :height="height" :width="width"></svg></template></swim-lane>`
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$refs.imageryHolder.appendChild(component.$mount().$el);
|
||||||
|
|
||||||
|
groupSVG = component.$el.querySelector('svg');
|
||||||
|
|
||||||
|
groupSVG.addEventListener('mouseout', (event) => {
|
||||||
|
if (event.target.nodeName === 'svg' || event.target.nodeName === 'use') {
|
||||||
|
this.removeFromForeground();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return groupSVG;
|
||||||
|
},
|
||||||
|
isImageryWidthAcceptable() {
|
||||||
|
// We're calculating if there is enough space between images to show the thumbnails.
|
||||||
|
// This algorithm could probably be enhanced to check the x co-ordinate distance between 2 consecutive images, but
|
||||||
|
// we will go with this for now assuming imagery is not sorted by asc time so it's difficult to calculate.
|
||||||
|
// TODO: Use telemetry.requestCollection to get sorted telemetry
|
||||||
|
const currentStart = this.viewBounds.start;
|
||||||
|
const currentEnd = this.viewBounds.end;
|
||||||
|
const rectX = this.xScale(currentStart);
|
||||||
|
const rectY = this.xScale(currentEnd);
|
||||||
|
const imageContainerWidth = this.imageHistory.length ? (rectY - rectX) / this.imageHistory.length : 0;
|
||||||
|
|
||||||
|
return imageContainerWidth < IMAGE_WIDTH_THRESHOLD;
|
||||||
|
},
|
||||||
|
drawImagery() {
|
||||||
|
let groupSVG = this.getImageryContainer();
|
||||||
|
const showImagePlaceholders = this.isImageryWidthAcceptable();
|
||||||
|
|
||||||
|
if (this.imageHistory.length) {
|
||||||
|
this.imageHistory.forEach((currentImageObject, index) => {
|
||||||
|
if (this.isImageryInBounds(currentImageObject)) {
|
||||||
|
this.plotImagery(currentImageObject, showImagePlaceholders, groupSVG, index);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.plotNoItems(groupSVG);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plotNoItems(svgElement) {
|
||||||
|
let textElement = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
||||||
|
this.setNSAttributesForElement(textElement, {
|
||||||
|
x: "10",
|
||||||
|
y: "20",
|
||||||
|
class: "c-imagery-tsv__no-items"
|
||||||
|
});
|
||||||
|
textElement.innerHTML = 'No images within timeframe';
|
||||||
|
|
||||||
|
svgElement.appendChild(textElement);
|
||||||
|
},
|
||||||
|
setNSAttributesForElement(element, attributes) {
|
||||||
|
Object.keys(attributes).forEach((key) => {
|
||||||
|
if (key === 'url') {
|
||||||
|
element.setAttributeNS('http://www.w3.org/1999/xlink', 'href', attributes[key]);
|
||||||
|
} else {
|
||||||
|
element.setAttributeNS(null, key, attributes[key]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getNSAttributesForElement(element, attribute) {
|
||||||
|
return element.getAttributeNS(null, attribute);
|
||||||
|
},
|
||||||
|
getImageWrapper(item) {
|
||||||
|
const id = `id-${item.time}`;
|
||||||
|
|
||||||
|
return this.$el.querySelector(`.c-imagery-tsv__contents g[id=${id}]`);
|
||||||
|
},
|
||||||
|
plotImagery(item, showImagePlaceholders, svgElement, index) {
|
||||||
|
//TODO: Placeholder image
|
||||||
|
let existingImageWrapper = this.getImageWrapper(item);
|
||||||
|
//imageWrapper wraps the vertical tick rect and the image
|
||||||
|
if (existingImageWrapper) {
|
||||||
|
this.updateExistingImageWrapper(existingImageWrapper, item, showImagePlaceholders);
|
||||||
|
} else {
|
||||||
|
let imageWrapper = this.createImageWrapper(index, item, showImagePlaceholders, svgElement);
|
||||||
|
svgElement.appendChild(imageWrapper);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateExistingImageWrapper(existingImageWrapper, item, showImagePlaceholders) {
|
||||||
|
//Update the x co-ordinates of the handle and image elements and the url of image
|
||||||
|
//this is to avoid tearing down all elements completely and re-drawing them
|
||||||
|
this.setNSAttributesForElement(existingImageWrapper, {
|
||||||
|
showImagePlaceholders
|
||||||
|
});
|
||||||
|
let imageTickElement = existingImageWrapper.querySelector('rect.c-imagery-tsv__image-handle');
|
||||||
|
this.setNSAttributesForElement(imageTickElement, {
|
||||||
|
x: this.xScale(item.time)
|
||||||
|
});
|
||||||
|
|
||||||
|
let imageRect = existingImageWrapper.querySelector('rect.c-imagery-tsv__image-placeholder');
|
||||||
|
this.setNSAttributesForElement(imageRect, {
|
||||||
|
x: this.xScale(item.time) + 2
|
||||||
|
});
|
||||||
|
|
||||||
|
let imageElement = existingImageWrapper.querySelector('image');
|
||||||
|
const selector = `href*=${existingImageWrapper.id}`;
|
||||||
|
let hoverEl = this.$el.querySelector(`.c-imagery-tsv__contents use[${selector}]`);
|
||||||
|
const hideImageUrl = (showImagePlaceholders && !hoverEl);
|
||||||
|
this.setNSAttributesForElement(imageElement, {
|
||||||
|
x: this.xScale(item.time) + 2,
|
||||||
|
url: hideImageUrl ? '' : item.url
|
||||||
|
});
|
||||||
|
},
|
||||||
|
createImageWrapper(index, item, showImagePlaceholders, svgElement) {
|
||||||
|
const id = `id-${item.time}`;
|
||||||
|
const imgSize = String(ROW_HEIGHT - 15);
|
||||||
|
let imageWrapper = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
||||||
|
this.setNSAttributesForElement(imageWrapper, {
|
||||||
|
id,
|
||||||
|
class: 'c-imagery-tsv__image-wrapper',
|
||||||
|
showImagePlaceholders
|
||||||
|
});
|
||||||
|
//create image tick indicator
|
||||||
|
let imageTickElement = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
||||||
|
this.setNSAttributesForElement(imageTickElement, {
|
||||||
|
class: 'c-imagery-tsv__image-handle',
|
||||||
|
x: this.xScale(item.time),
|
||||||
|
y: 5,
|
||||||
|
rx: 0,
|
||||||
|
width: 2,
|
||||||
|
height: String(ROW_HEIGHT - 10)
|
||||||
|
});
|
||||||
|
imageWrapper.appendChild(imageTickElement);
|
||||||
|
|
||||||
|
let imageRect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
||||||
|
this.setNSAttributesForElement(imageRect, {
|
||||||
|
class: 'c-imagery-tsv__image-placeholder',
|
||||||
|
x: this.xScale(item.time) + 2,
|
||||||
|
y: 10,
|
||||||
|
rx: 0,
|
||||||
|
width: imgSize,
|
||||||
|
height: imgSize,
|
||||||
|
mask: `#image-${item.time}`
|
||||||
|
});
|
||||||
|
imageWrapper.appendChild(imageRect);
|
||||||
|
|
||||||
|
let imageElement = document.createElementNS('http://www.w3.org/2000/svg', 'image');
|
||||||
|
this.setNSAttributesForElement(imageElement, {
|
||||||
|
id: `image-${item.time}`,
|
||||||
|
x: this.xScale(item.time) + 2,
|
||||||
|
y: 10,
|
||||||
|
rx: 0,
|
||||||
|
width: imgSize,
|
||||||
|
height: imgSize,
|
||||||
|
url: showImagePlaceholders ? '' : item.url
|
||||||
|
});
|
||||||
|
imageWrapper.appendChild(imageElement);
|
||||||
|
|
||||||
|
//TODO: Don't add the hover listener if the width is too small
|
||||||
|
imageWrapper.addEventListener('mouseover', this.bringToForeground.bind(this, svgElement, imageWrapper, index, item.url));
|
||||||
|
|
||||||
|
return imageWrapper;
|
||||||
|
},
|
||||||
|
bringToForeground(svgElement, imageWrapper, index, url, event) {
|
||||||
|
const selector = `href*=${imageWrapper.id}`;
|
||||||
|
let hoverEls = this.$el.querySelectorAll(`.c-imagery-tsv__contents use:not([${selector}])`);
|
||||||
|
if (hoverEls.length > 0) {
|
||||||
|
this.removeFromForeground(hoverEls);
|
||||||
|
}
|
||||||
|
|
||||||
|
hoverEls = this.$el.querySelectorAll(`.c-imagery-tsv__contents use[${selector}]`);
|
||||||
|
if (hoverEls.length) {
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let imageElement = imageWrapper.querySelector('image');
|
||||||
|
this.setNSAttributesForElement(imageElement, {
|
||||||
|
url: url,
|
||||||
|
fill: 'none'
|
||||||
|
});
|
||||||
|
let hoverElement = document.createElementNS('http://www.w3.org/2000/svg', 'use');
|
||||||
|
this.setNSAttributesForElement(hoverElement, {
|
||||||
|
class: 'image-highlight',
|
||||||
|
x: 0,
|
||||||
|
href: `#${imageWrapper.id}`
|
||||||
|
});
|
||||||
|
this.setNSAttributesForElement(imageWrapper, {
|
||||||
|
class: 'c-imagery-tsv__image-wrapper is-hovered'
|
||||||
|
});
|
||||||
|
// We're using mousedown here and not 'click' because 'click' doesn't seem to be triggered reliably
|
||||||
|
hoverElement.addEventListener('mousedown', (e) => {
|
||||||
|
if (e.button === 0) {
|
||||||
|
this.expand(index);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
svgElement.appendChild(hoverElement);
|
||||||
|
|
||||||
|
},
|
||||||
|
removeFromForeground(items) {
|
||||||
|
let hoverEls;
|
||||||
|
if (items) {
|
||||||
|
hoverEls = items;
|
||||||
|
} else {
|
||||||
|
hoverEls = this.$el.querySelectorAll(".c-imagery-tsv__contents use");
|
||||||
|
}
|
||||||
|
|
||||||
|
hoverEls.forEach(item => {
|
||||||
|
let selector = `id*=${this.getNSAttributesForElement(item, 'href').replace('#', '')}`;
|
||||||
|
let imageWrapper = this.$el.querySelector(`.c-imagery-tsv__contents g[${selector}]`);
|
||||||
|
this.setNSAttributesForElement(imageWrapper, {
|
||||||
|
class: 'c-imagery-tsv__image-wrapper'
|
||||||
|
});
|
||||||
|
let showImagePlaceholders = this.getNSAttributesForElement(imageWrapper, 'showImagePlaceholders');
|
||||||
|
if (showImagePlaceholders === 'true') {
|
||||||
|
let imageElement = imageWrapper.querySelector('image');
|
||||||
|
this.setNSAttributesForElement(imageElement, {
|
||||||
|
url: ''
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
item.remove();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
@ -129,8 +129,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div class="c-imagery__thumbs-wrapper"
|
||||||
class="c-imagery__thumbs-wrapper"
|
|
||||||
:class="[
|
:class="[
|
||||||
{ 'is-paused': isPaused },
|
{ 'is-paused': isPaused },
|
||||||
{ 'is-autoscroll-off': !resizingWindow && !autoScroll && !isPaused }
|
{ 'is-autoscroll-off': !resizingWindow && !autoScroll && !isPaused }
|
||||||
@ -175,7 +174,8 @@ import moment from 'moment';
|
|||||||
import RelatedTelemetry from './RelatedTelemetry/RelatedTelemetry';
|
import RelatedTelemetry from './RelatedTelemetry/RelatedTelemetry';
|
||||||
import Compass from './Compass/Compass.vue';
|
import Compass from './Compass/Compass.vue';
|
||||||
|
|
||||||
const DEFAULT_DURATION_FORMATTER = 'duration';
|
import imageryData from "../../imagery/mixins/imageryData";
|
||||||
|
|
||||||
const REFRESH_CSS_MS = 500;
|
const REFRESH_CSS_MS = 500;
|
||||||
const DURATION_TRACK_MS = 1000;
|
const DURATION_TRACK_MS = 1000;
|
||||||
const ARROW_DOWN_DELAY_CHECK_MS = 400;
|
const ARROW_DOWN_DELAY_CHECK_MS = 400;
|
||||||
@ -197,30 +197,29 @@ export default {
|
|||||||
components: {
|
components: {
|
||||||
Compass
|
Compass
|
||||||
},
|
},
|
||||||
|
mixins: [imageryData],
|
||||||
inject: ['openmct', 'domainObject', 'objectPath', 'currentView'],
|
inject: ['openmct', 'domainObject', 'objectPath', 'currentView'],
|
||||||
data() {
|
data() {
|
||||||
let timeSystem = this.openmct.time.timeSystem();
|
let timeSystem = this.openmct.time.timeSystem();
|
||||||
|
this.metadata = {};
|
||||||
|
this.requestCount = 0;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
autoScroll: true,
|
|
||||||
durationFormatter: undefined,
|
durationFormatter: undefined,
|
||||||
|
imageHistory: [],
|
||||||
|
timeSystem: timeSystem,
|
||||||
|
keyString: undefined,
|
||||||
|
autoScroll: true,
|
||||||
filters: {
|
filters: {
|
||||||
brightness: 100,
|
brightness: 100,
|
||||||
contrast: 100
|
contrast: 100
|
||||||
},
|
},
|
||||||
imageHistory: [],
|
|
||||||
thumbnailClick: THUMBNAIL_CLICKED,
|
thumbnailClick: THUMBNAIL_CLICKED,
|
||||||
isPaused: false,
|
isPaused: false,
|
||||||
metadata: {},
|
|
||||||
requestCount: 0,
|
|
||||||
timeSystem: timeSystem,
|
|
||||||
timeFormatter: undefined,
|
|
||||||
refreshCSS: false,
|
refreshCSS: false,
|
||||||
keyString: undefined,
|
|
||||||
focusedImageIndex: undefined,
|
focusedImageIndex: undefined,
|
||||||
focusedImageRelatedTelemetry: {},
|
focusedImageRelatedTelemetry: {},
|
||||||
numericDuration: undefined,
|
numericDuration: undefined,
|
||||||
metadataEndpoints: {},
|
|
||||||
relatedTelemetry: {},
|
relatedTelemetry: {},
|
||||||
latestRelatedTelemetry: {},
|
latestRelatedTelemetry: {},
|
||||||
focusedImageNaturalAspectRatio: undefined,
|
focusedImageNaturalAspectRatio: undefined,
|
||||||
@ -231,6 +230,9 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
imageHistorySize() {
|
||||||
|
return this.imageHistory.length;
|
||||||
|
},
|
||||||
compassRoseSizingClasses() {
|
compassRoseSizingClasses() {
|
||||||
let compassRoseSizingClasses = '';
|
let compassRoseSizingClasses = '';
|
||||||
if (this.sizedImageDimensions.width < 300) {
|
if (this.sizedImageDimensions.width < 300) {
|
||||||
@ -258,9 +260,6 @@ export default {
|
|||||||
canTrackDuration() {
|
canTrackDuration() {
|
||||||
return this.openmct.time.clock() && this.timeSystem.isUTCBased;
|
return this.openmct.time.clock() && this.timeSystem.isUTCBased;
|
||||||
},
|
},
|
||||||
focusedImageDownloadName() {
|
|
||||||
return this.getImageDownloadName(this.focusedImage);
|
|
||||||
},
|
|
||||||
isNextDisabled() {
|
isNextDisabled() {
|
||||||
let disabled = false;
|
let disabled = false;
|
||||||
|
|
||||||
@ -383,6 +382,10 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
imageHistorySize(newSize, oldSize) {
|
||||||
|
this.setFocusedImage(newSize - 1, false);
|
||||||
|
this.scrollToRight();
|
||||||
|
},
|
||||||
focusedImageIndex() {
|
focusedImageIndex() {
|
||||||
this.trackDuration();
|
this.trackDuration();
|
||||||
this.resetAgeCSS();
|
this.resetAgeCSS();
|
||||||
@ -392,17 +395,8 @@ export default {
|
|||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
//listen
|
//listen
|
||||||
this.openmct.time.on('bounds', this.boundsChange);
|
this.openmct.time.on('timeSystem', this.trackDuration);
|
||||||
this.openmct.time.on('timeSystem', this.timeSystemChange);
|
this.openmct.time.on('clock', this.trackDuration);
|
||||||
this.openmct.time.on('clock', this.clockChange);
|
|
||||||
|
|
||||||
// set
|
|
||||||
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
|
||||||
this.metadata = this.openmct.telemetry.getMetadata(this.domainObject);
|
|
||||||
this.imageHints = { ...this.metadata.valuesForHints(['image'])[0] };
|
|
||||||
this.durationFormatter = this.getFormatter(this.timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
|
||||||
this.imageFormatter = this.openmct.telemetry.getValueFormatter(this.imageHints);
|
|
||||||
this.imageDownloadNameHints = { ...this.metadata.valuesForHints(['imageDownloadName'])[0]};
|
|
||||||
|
|
||||||
// related telemetry keys
|
// related telemetry keys
|
||||||
this.spacecraftPositionKeys = ['positionX', 'positionY', 'positionZ'];
|
this.spacecraftPositionKeys = ['positionX', 'positionY', 'positionZ'];
|
||||||
@ -410,56 +404,49 @@ export default {
|
|||||||
this.cameraKeys = ['cameraPan', 'cameraTilt'];
|
this.cameraKeys = ['cameraPan', 'cameraTilt'];
|
||||||
this.sunKeys = ['sunOrientation'];
|
this.sunKeys = ['sunOrientation'];
|
||||||
|
|
||||||
// initialize
|
|
||||||
this.timeKey = this.timeSystem.key;
|
|
||||||
this.timeFormatter = this.getFormatter(this.timeKey);
|
|
||||||
|
|
||||||
// kickoff
|
|
||||||
this.subscribe();
|
|
||||||
this.requestHistory();
|
|
||||||
|
|
||||||
// related telemetry
|
// related telemetry
|
||||||
await this.initializeRelatedTelemetry();
|
await this.initializeRelatedTelemetry();
|
||||||
this.updateRelatedTelemetryForFocusedImage();
|
await this.updateRelatedTelemetryForFocusedImage();
|
||||||
this.trackLatestRelatedTelemetry();
|
this.trackLatestRelatedTelemetry();
|
||||||
|
|
||||||
// for scrolling through images quickly and resizing the object view
|
// for scrolling through images quickly and resizing the object view
|
||||||
_.debounce(this.updateRelatedTelemetryForFocusedImage, 400);
|
this.updateRelatedTelemetryForFocusedImage = _.debounce(this.updateRelatedTelemetryForFocusedImage, 400);
|
||||||
_.debounce(this.resizeImageContainer, 400);
|
|
||||||
|
|
||||||
|
// for resizing the object view
|
||||||
|
this.resizeImageContainer = _.debounce(this.resizeImageContainer, 400);
|
||||||
|
|
||||||
|
if (this.$refs.imageBG) {
|
||||||
this.imageContainerResizeObserver = new ResizeObserver(this.resizeImageContainer);
|
this.imageContainerResizeObserver = new ResizeObserver(this.resizeImageContainer);
|
||||||
this.imageContainerResizeObserver.observe(this.$refs.imageBG);
|
this.imageContainerResizeObserver.observe(this.$refs.imageBG);
|
||||||
|
}
|
||||||
|
|
||||||
// For adjusting scroll bar size and position when resizing thumbs wrapper
|
// For adjusting scroll bar size and position when resizing thumbs wrapper
|
||||||
this.handleScroll = _.debounce(this.handleScroll, SCROLL_LATENCY);
|
this.handleScroll = _.debounce(this.handleScroll, SCROLL_LATENCY);
|
||||||
this.handleThumbWindowResizeEnded = _.debounce(this.handleThumbWindowResizeEnded, SCROLL_LATENCY);
|
this.handleThumbWindowResizeEnded = _.debounce(this.handleThumbWindowResizeEnded, SCROLL_LATENCY);
|
||||||
|
this.handleThumbWindowResizeStart = _.debounce(this.handleThumbWindowResizeStart, SCROLL_LATENCY);
|
||||||
|
|
||||||
|
if (this.$refs.thumbsWrapper) {
|
||||||
this.thumbWrapperResizeObserver = new ResizeObserver(this.handleThumbWindowResizeStart);
|
this.thumbWrapperResizeObserver = new ResizeObserver(this.handleThumbWindowResizeStart);
|
||||||
this.thumbWrapperResizeObserver.observe(this.$refs.thumbsWrapper);
|
this.thumbWrapperResizeObserver.observe(this.$refs.thumbsWrapper);
|
||||||
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
if (this.unsubscribe) {
|
this.openmct.time.off('timeSystem', this.trackDuration);
|
||||||
this.unsubscribe();
|
this.openmct.time.off('clock', this.trackDuration);
|
||||||
delete this.unsubscribe;
|
|
||||||
|
if (this.thumbWrapperResizeObserver) {
|
||||||
|
this.thumbWrapperResizeObserver.disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.imageContainerResizeObserver) {
|
if (this.imageContainerResizeObserver) {
|
||||||
this.imageContainerResizeObserver.disconnect();
|
this.imageContainerResizeObserver.disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.thumbWrapperResizeObserver) {
|
|
||||||
this.thumbWrapperResizeObserver.disconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.relatedTelemetry.hasRelatedTelemetry) {
|
if (this.relatedTelemetry.hasRelatedTelemetry) {
|
||||||
this.relatedTelemetry.destroy();
|
this.relatedTelemetry.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stopDurationTracking();
|
|
||||||
this.openmct.time.off('bounds', this.boundsChange);
|
|
||||||
this.openmct.time.off('timeSystem', this.timeSystemChange);
|
|
||||||
this.openmct.time.off('clock', this.clockChange);
|
|
||||||
|
|
||||||
// unsubscribe from related telemetry
|
// unsubscribe from related telemetry
|
||||||
if (this.relatedTelemetry.hasRelatedTelemetry) {
|
if (this.relatedTelemetry.hasRelatedTelemetry) {
|
||||||
for (let key of this.relatedTelemetry.keys) {
|
for (let key of this.relatedTelemetry.keys) {
|
||||||
@ -576,56 +563,6 @@ export default {
|
|||||||
focusElement() {
|
focusElement() {
|
||||||
this.$el.focus();
|
this.$el.focus();
|
||||||
},
|
},
|
||||||
datumIsNotValid(datum) {
|
|
||||||
if (this.imageHistory.length === 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const datumURL = this.formatImageUrl(datum);
|
|
||||||
const lastHistoryURL = this.formatImageUrl(this.imageHistory.slice(-1)[0]);
|
|
||||||
|
|
||||||
// datum is not valid if it matches the last datum in history,
|
|
||||||
// or it is before the last datum in the history
|
|
||||||
const datumTimeCheck = this.parseTime(datum);
|
|
||||||
const historyTimeCheck = this.parseTime(this.imageHistory.slice(-1)[0]);
|
|
||||||
const matchesLast = (datumTimeCheck === historyTimeCheck) && (datumURL === lastHistoryURL);
|
|
||||||
const isStale = datumTimeCheck < historyTimeCheck;
|
|
||||||
|
|
||||||
return matchesLast || isStale;
|
|
||||||
},
|
|
||||||
formatImageUrl(datum) {
|
|
||||||
if (!datum) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.imageFormatter.format(datum);
|
|
||||||
},
|
|
||||||
formatTime(datum) {
|
|
||||||
if (!datum) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let dateTimeStr = this.timeFormatter.format(datum);
|
|
||||||
|
|
||||||
// Replace ISO "T" with a space to allow wrapping
|
|
||||||
return dateTimeStr.replace("T", " ");
|
|
||||||
},
|
|
||||||
getImageDownloadName(datum) {
|
|
||||||
let imageDownloadName = '';
|
|
||||||
if (datum) {
|
|
||||||
const key = this.imageDownloadNameHints.key;
|
|
||||||
imageDownloadName = datum[key];
|
|
||||||
}
|
|
||||||
|
|
||||||
return imageDownloadName;
|
|
||||||
},
|
|
||||||
parseTime(datum) {
|
|
||||||
if (!datum) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.timeFormatter.parse(datum);
|
|
||||||
},
|
|
||||||
handleScroll() {
|
handleScroll() {
|
||||||
const thumbsWrapper = this.$refs.thumbsWrapper;
|
const thumbsWrapper = this.$refs.thumbsWrapper;
|
||||||
if (!thumbsWrapper || this.resizingWindow) {
|
if (!thumbsWrapper || this.resizingWindow) {
|
||||||
@ -683,6 +620,10 @@ export default {
|
|||||||
setFocusedImage(index, thumbnailClick = false) {
|
setFocusedImage(index, thumbnailClick = false) {
|
||||||
if (this.isPaused && !thumbnailClick) {
|
if (this.isPaused && !thumbnailClick) {
|
||||||
this.nextImageIndex = index;
|
this.nextImageIndex = index;
|
||||||
|
//this could happen if bounds changes
|
||||||
|
if (this.focusedImageIndex > this.imageHistory.length - 1) {
|
||||||
|
this.focusedImageIndex = index;
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -693,70 +634,6 @@ export default {
|
|||||||
this.paused(true);
|
this.paused(true);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
boundsChange(bounds, isTick) {
|
|
||||||
if (!isTick) {
|
|
||||||
this.requestHistory();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async requestHistory() {
|
|
||||||
let bounds = this.openmct.time.bounds();
|
|
||||||
this.requestCount++;
|
|
||||||
const requestId = this.requestCount;
|
|
||||||
this.imageHistory = [];
|
|
||||||
|
|
||||||
let data = await this.openmct.telemetry
|
|
||||||
.request(this.domainObject, bounds) || [];
|
|
||||||
|
|
||||||
if (this.requestCount === requestId) {
|
|
||||||
data.forEach((datum, index) => {
|
|
||||||
this.updateHistory(datum, index === data.length - 1);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
timeSystemChange(system) {
|
|
||||||
this.timeSystem = this.openmct.time.timeSystem();
|
|
||||||
this.timeKey = this.timeSystem.key;
|
|
||||||
this.timeFormatter = this.getFormatter(this.timeKey);
|
|
||||||
this.durationFormatter = this.getFormatter(this.timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
|
||||||
this.trackDuration();
|
|
||||||
},
|
|
||||||
clockChange(clock) {
|
|
||||||
this.trackDuration();
|
|
||||||
},
|
|
||||||
subscribe() {
|
|
||||||
this.unsubscribe = this.openmct.telemetry
|
|
||||||
.subscribe(this.domainObject, (datum) => {
|
|
||||||
let parsedTimestamp = this.parseTime(datum);
|
|
||||||
let bounds = this.openmct.time.bounds();
|
|
||||||
|
|
||||||
if (parsedTimestamp >= bounds.start && parsedTimestamp <= bounds.end) {
|
|
||||||
this.updateHistory(datum);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
updateHistory(datum, setFocused = true) {
|
|
||||||
if (this.datumIsNotValid(datum)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let image = { ...datum };
|
|
||||||
image.formattedTime = this.formatTime(datum);
|
|
||||||
image.url = this.formatImageUrl(datum);
|
|
||||||
image.time = datum[this.timeKey];
|
|
||||||
image.imageDownloadName = this.getImageDownloadName(datum);
|
|
||||||
|
|
||||||
this.imageHistory.push(image);
|
|
||||||
if (setFocused) {
|
|
||||||
this.setFocusedImage(this.imageHistory.length - 1);
|
|
||||||
this.scrollToRight();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getFormatter(key) {
|
|
||||||
let metadataValue = this.metadata.value(key) || { format: key };
|
|
||||||
let valueFormatter = this.openmct.telemetry.getValueFormatter(metadataValue);
|
|
||||||
|
|
||||||
return valueFormatter;
|
|
||||||
},
|
|
||||||
trackDuration() {
|
trackDuration() {
|
||||||
if (this.canTrackDuration) {
|
if (this.canTrackDuration) {
|
||||||
this.stopDurationTracking();
|
this.stopDurationTracking();
|
||||||
@ -876,6 +753,10 @@ export default {
|
|||||||
}, { once: true });
|
}, { once: true });
|
||||||
},
|
},
|
||||||
resizeImageContainer() {
|
resizeImageContainer() {
|
||||||
|
if (!this.$refs.imageBG) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.$refs.imageBG.clientWidth !== this.imageContainerWidth) {
|
if (this.$refs.imageBG.clientWidth !== this.imageContainerWidth) {
|
||||||
this.imageContainerWidth = this.$refs.imageBG.clientWidth;
|
this.imageContainerWidth = this.$refs.imageBG.clientWidth;
|
||||||
}
|
}
|
@ -312,3 +312,34 @@
|
|||||||
@include cArrowButtonSizing($dimOuter: 32px);
|
@include cArrowButtonSizing($dimOuter: 32px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*************************************** IMAGERY IN TIMESTRIP VIEWS */
|
||||||
|
.c-imagery-tsv {
|
||||||
|
g.c-imagery-tsv__image-wrapper {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&.is-hovered {
|
||||||
|
filter: brightness(1) contrast(1) !important;
|
||||||
|
[class*='__image-handle'] {
|
||||||
|
fill: $colorBodyFg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__no-items {
|
||||||
|
fill: $colorBodyFg !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__image-handle {
|
||||||
|
fill: rgba($colorBodyFg, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__image-placeholder {
|
||||||
|
fill: pushBack($colorBodyBg, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover g.c-imagery-tsv__image-wrapper {
|
||||||
|
// TODO CH: convert to theme constants
|
||||||
|
filter: brightness(0.5) contrast(0.7);
|
||||||
|
}
|
||||||
|
}
|
174
src/plugins/imagery/mixins/imageryData.js
Normal file
174
src/plugins/imagery/mixins/imageryData.js
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
const DEFAULT_DURATION_FORMATTER = 'duration';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
inject: ['openmct', 'domainObject', 'objectPath'],
|
||||||
|
mounted() {
|
||||||
|
// listen
|
||||||
|
this.openmct.time.on('bounds', this.boundsChange);
|
||||||
|
this.openmct.time.on('timeSystem', this.timeSystemChange);
|
||||||
|
|
||||||
|
// set
|
||||||
|
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||||
|
this.metadata = this.openmct.telemetry.getMetadata(this.domainObject);
|
||||||
|
this.imageHints = { ...this.metadata.valuesForHints(['image'])[0] };
|
||||||
|
this.durationFormatter = this.getFormatter(this.timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
||||||
|
this.imageFormatter = this.openmct.telemetry.getValueFormatter(this.imageHints);
|
||||||
|
this.imageDownloadNameHints = { ...this.metadata.valuesForHints(['imageDownloadName'])[0]};
|
||||||
|
|
||||||
|
// initialize
|
||||||
|
this.timeKey = this.timeSystem.key;
|
||||||
|
this.timeFormatter = this.getFormatter(this.timeKey);
|
||||||
|
|
||||||
|
// kickoff
|
||||||
|
this.subscribe();
|
||||||
|
this.requestHistory();
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
if (this.unsubscribe) {
|
||||||
|
this.unsubscribe();
|
||||||
|
delete this.unsubscribe;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.openmct.time.off('bounds', this.boundsChange);
|
||||||
|
this.openmct.time.off('timeSystem', this.timeSystemChange);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
datumIsNotValid(datum) {
|
||||||
|
if (this.imageHistory.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const datumURL = this.formatImageUrl(datum);
|
||||||
|
const lastHistoryURL = this.formatImageUrl(this.imageHistory.slice(-1)[0]);
|
||||||
|
|
||||||
|
// datum is not valid if it matches the last datum in history,
|
||||||
|
// or it is before the last datum in the history
|
||||||
|
const datumTimeCheck = this.parseTime(datum);
|
||||||
|
const historyTimeCheck = this.parseTime(this.imageHistory.slice(-1)[0]);
|
||||||
|
const matchesLast = (datumTimeCheck === historyTimeCheck) && (datumURL === lastHistoryURL);
|
||||||
|
const isStale = datumTimeCheck < historyTimeCheck;
|
||||||
|
|
||||||
|
return matchesLast || isStale;
|
||||||
|
},
|
||||||
|
formatImageUrl(datum) {
|
||||||
|
if (!datum) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.imageFormatter.format(datum);
|
||||||
|
},
|
||||||
|
formatTime(datum) {
|
||||||
|
if (!datum) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let dateTimeStr = this.timeFormatter.format(datum);
|
||||||
|
|
||||||
|
// Replace ISO "T" with a space to allow wrapping
|
||||||
|
return dateTimeStr.replace("T", " ");
|
||||||
|
},
|
||||||
|
getImageDownloadName(datum) {
|
||||||
|
let imageDownloadName = '';
|
||||||
|
if (datum) {
|
||||||
|
const key = this.imageDownloadNameHints.key;
|
||||||
|
imageDownloadName = datum[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
return imageDownloadName;
|
||||||
|
},
|
||||||
|
parseTime(datum) {
|
||||||
|
if (!datum) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.timeFormatter.parse(datum);
|
||||||
|
},
|
||||||
|
boundsChange(bounds, isTick) {
|
||||||
|
if (!isTick) {
|
||||||
|
this.requestHistory();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async requestHistory() {
|
||||||
|
let bounds = this.openmct.time.bounds();
|
||||||
|
this.requestCount++;
|
||||||
|
const requestId = this.requestCount;
|
||||||
|
this.imageHistory = [];
|
||||||
|
|
||||||
|
let data = await this.openmct.telemetry
|
||||||
|
.request(this.domainObject, bounds) || [];
|
||||||
|
|
||||||
|
if (this.requestCount === requestId) {
|
||||||
|
let imagery = [];
|
||||||
|
data.forEach((datum) => {
|
||||||
|
let image = this.normalizeDatum(datum);
|
||||||
|
if (image) {
|
||||||
|
imagery.push(image);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
//this is to optimize anything that reacts to imageHistory length
|
||||||
|
this.imageHistory = imagery;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
timeSystemChange() {
|
||||||
|
this.timeSystem = this.openmct.time.timeSystem();
|
||||||
|
this.timeKey = this.timeSystem.key;
|
||||||
|
this.timeFormatter = this.getFormatter(this.timeKey);
|
||||||
|
this.durationFormatter = this.getFormatter(this.timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
||||||
|
},
|
||||||
|
subscribe() {
|
||||||
|
this.unsubscribe = this.openmct.telemetry
|
||||||
|
.subscribe(this.domainObject, (datum) => {
|
||||||
|
let parsedTimestamp = this.parseTime(datum);
|
||||||
|
let bounds = this.openmct.time.bounds();
|
||||||
|
|
||||||
|
if (parsedTimestamp >= bounds.start && parsedTimestamp <= bounds.end) {
|
||||||
|
let image = this.normalizeDatum(datum);
|
||||||
|
if (image) {
|
||||||
|
this.imageHistory.push(image);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
normalizeDatum(datum) {
|
||||||
|
if (this.datumIsNotValid(datum)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let image = { ...datum };
|
||||||
|
image.formattedTime = this.formatTime(datum);
|
||||||
|
image.url = this.formatImageUrl(datum);
|
||||||
|
image.time = datum[this.timeKey];
|
||||||
|
image.imageDownloadName = this.getImageDownloadName(datum);
|
||||||
|
|
||||||
|
return image;
|
||||||
|
},
|
||||||
|
getFormatter(key) {
|
||||||
|
let metadataValue = this.metadata.value(key) || { format: key };
|
||||||
|
let valueFormatter = this.openmct.telemetry.getValueFormatter(metadataValue);
|
||||||
|
|
||||||
|
return valueFormatter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
@ -21,10 +21,12 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import ImageryViewProvider from './ImageryViewProvider';
|
import ImageryViewProvider from './ImageryViewProvider';
|
||||||
|
import ImageryTimestripViewProvider from './ImageryTimestripViewProvider';
|
||||||
|
|
||||||
export default function () {
|
export default function () {
|
||||||
return function install(openmct) {
|
return function install(openmct) {
|
||||||
openmct.objectViews.addProvider(new ImageryViewProvider(openmct));
|
openmct.objectViews.addProvider(new ImageryViewProvider(openmct));
|
||||||
|
openmct.objectViews.addProvider(new ImageryTimestripViewProvider(openmct));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,12 +84,14 @@ function generateTelemetry(start, count) {
|
|||||||
return telemetry;
|
return telemetry;
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("The Imagery View Layout", () => {
|
describe("The Imagery View Layouts", () => {
|
||||||
const imageryKey = 'example.imagery';
|
const imageryKey = 'example.imagery';
|
||||||
|
const imageryForTimeStripKey = 'example.imagery.time-strip.view';
|
||||||
const START = Date.now();
|
const START = Date.now();
|
||||||
const COUNT = 10;
|
const COUNT = 10;
|
||||||
|
|
||||||
let resolveFunction;
|
let resolveFunction;
|
||||||
|
let originalRouterPath;
|
||||||
|
|
||||||
let openmct;
|
let openmct;
|
||||||
let appHolder;
|
let appHolder;
|
||||||
@ -116,51 +118,51 @@ describe("The Imagery View Layout", () => {
|
|||||||
"image": 1,
|
"image": 1,
|
||||||
"priority": 3
|
"priority": 3
|
||||||
},
|
},
|
||||||
"source": "url",
|
"source": "url"
|
||||||
"relatedTelemetry": {
|
// "relatedTelemetry": {
|
||||||
"heading": {
|
// "heading": {
|
||||||
"comparisonFunction": comparisonFunction,
|
// "comparisonFunction": comparisonFunction,
|
||||||
"historical": {
|
// "historical": {
|
||||||
"telemetryObjectId": "heading",
|
// "telemetryObjectId": "heading",
|
||||||
"valueKey": "value"
|
// "valueKey": "value"
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
"roll": {
|
// "roll": {
|
||||||
"comparisonFunction": comparisonFunction,
|
// "comparisonFunction": comparisonFunction,
|
||||||
"historical": {
|
// "historical": {
|
||||||
"telemetryObjectId": "roll",
|
// "telemetryObjectId": "roll",
|
||||||
"valueKey": "value"
|
// "valueKey": "value"
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
"pitch": {
|
// "pitch": {
|
||||||
"comparisonFunction": comparisonFunction,
|
// "comparisonFunction": comparisonFunction,
|
||||||
"historical": {
|
// "historical": {
|
||||||
"telemetryObjectId": "pitch",
|
// "telemetryObjectId": "pitch",
|
||||||
"valueKey": "value"
|
// "valueKey": "value"
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
"cameraPan": {
|
// "cameraPan": {
|
||||||
"comparisonFunction": comparisonFunction,
|
// "comparisonFunction": comparisonFunction,
|
||||||
"historical": {
|
// "historical": {
|
||||||
"telemetryObjectId": "cameraPan",
|
// "telemetryObjectId": "cameraPan",
|
||||||
"valueKey": "value"
|
// "valueKey": "value"
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
"cameraTilt": {
|
// "cameraTilt": {
|
||||||
"comparisonFunction": comparisonFunction,
|
// "comparisonFunction": comparisonFunction,
|
||||||
"historical": {
|
// "historical": {
|
||||||
"telemetryObjectId": "cameraTilt",
|
// "telemetryObjectId": "cameraTilt",
|
||||||
"valueKey": "value"
|
// "valueKey": "value"
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
"sunOrientation": {
|
// "sunOrientation": {
|
||||||
"comparisonFunction": comparisonFunction,
|
// "comparisonFunction": comparisonFunction,
|
||||||
"historical": {
|
// "historical": {
|
||||||
"telemetryObjectId": "sunOrientation",
|
// "telemetryObjectId": "sunOrientation",
|
||||||
"valueKey": "value"
|
// "valueKey": "value"
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
@ -220,6 +222,8 @@ describe("The Imagery View Layout", () => {
|
|||||||
spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([]));
|
spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([]));
|
||||||
spyOn(openmct.objects, 'get').and.returnValue(Promise.resolve({}));
|
spyOn(openmct.objects, 'get').and.returnValue(Promise.resolve({}));
|
||||||
|
|
||||||
|
originalRouterPath = openmct.router.path;
|
||||||
|
|
||||||
openmct.on('start', done);
|
openmct.on('start', done);
|
||||||
openmct.start(appHolder);
|
openmct.start(appHolder);
|
||||||
});
|
});
|
||||||
@ -229,10 +233,34 @@ describe("The Imagery View Layout", () => {
|
|||||||
start: 0,
|
start: 0,
|
||||||
end: 1
|
end: 1
|
||||||
});
|
});
|
||||||
|
openmct.router.path = originalRouterPath;
|
||||||
|
|
||||||
return resetApplicationState(openmct);
|
return resetApplicationState(openmct);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should provide an imagery time strip view when in a time strip", () => {
|
||||||
|
openmct.router.path = [{
|
||||||
|
identifier: {
|
||||||
|
key: 'test-timestrip',
|
||||||
|
namespace: ''
|
||||||
|
},
|
||||||
|
type: 'time-strip'
|
||||||
|
}];
|
||||||
|
|
||||||
|
let applicableViews = openmct.objectViews.get(imageryObject, [imageryObject, {
|
||||||
|
identifier: {
|
||||||
|
key: 'test-timestrip',
|
||||||
|
namespace: ''
|
||||||
|
},
|
||||||
|
type: 'time-strip'
|
||||||
|
}]);
|
||||||
|
let imageryView = applicableViews.find(
|
||||||
|
viewProvider => viewProvider.key === imageryForTimeStripKey
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(imageryView).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
it("should provide an imagery view only for imagery producing objects", () => {
|
it("should provide an imagery view only for imagery producing objects", () => {
|
||||||
let applicableViews = openmct.objectViews.get(imageryObject, []);
|
let applicableViews = openmct.objectViews.get(imageryObject, []);
|
||||||
let imageryView = applicableViews.find(
|
let imageryView = applicableViews.find(
|
||||||
@ -242,6 +270,46 @@ describe("The Imagery View Layout", () => {
|
|||||||
expect(imageryView).toBeDefined();
|
expect(imageryView).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should not provide an imagery view when in a time strip", () => {
|
||||||
|
openmct.router.path = [{
|
||||||
|
identifier: {
|
||||||
|
key: 'test-timestrip',
|
||||||
|
namespace: ''
|
||||||
|
},
|
||||||
|
type: 'time-strip'
|
||||||
|
}];
|
||||||
|
|
||||||
|
let applicableViews = openmct.objectViews.get(imageryObject, [imageryObject, {
|
||||||
|
identifier: {
|
||||||
|
key: 'test-timestrip',
|
||||||
|
namespace: ''
|
||||||
|
},
|
||||||
|
type: 'time-strip'
|
||||||
|
}]);
|
||||||
|
let imageryView = applicableViews.find(
|
||||||
|
viewProvider => viewProvider.key === imageryKey
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(imageryView).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should provide an imagery view when navigated to in the composition of a time strip", () => {
|
||||||
|
openmct.router.path = [imageryObject];
|
||||||
|
|
||||||
|
let applicableViews = openmct.objectViews.get(imageryObject, [imageryObject, {
|
||||||
|
identifier: {
|
||||||
|
key: 'test-timestrip',
|
||||||
|
namespace: ''
|
||||||
|
},
|
||||||
|
type: 'time-strip'
|
||||||
|
}]);
|
||||||
|
let imageryView = applicableViews.find(
|
||||||
|
viewProvider => viewProvider.key === imageryKey
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(imageryView).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
describe("imagery view", () => {
|
describe("imagery view", () => {
|
||||||
let applicableViews;
|
let applicableViews;
|
||||||
let imageryViewProvider;
|
let imageryViewProvider;
|
||||||
@ -367,18 +435,18 @@ describe("The Imagery View Layout", () => {
|
|||||||
});
|
});
|
||||||
it ('shows an auto scroll button when scroll to left', async () => {
|
it ('shows an auto scroll button when scroll to left', async () => {
|
||||||
// to mock what a scroll would do
|
// to mock what a scroll would do
|
||||||
imageryView._getInstance().$refs.ImageryLayout.autoScroll = false;
|
imageryView._getInstance().$refs.ImageryContainer.autoScroll = false;
|
||||||
await Vue.nextTick();
|
await Vue.nextTick();
|
||||||
let autoScrollButton = parent.querySelector('.c-imagery__auto-scroll-resume-button');
|
let autoScrollButton = parent.querySelector('.c-imagery__auto-scroll-resume-button');
|
||||||
expect(autoScrollButton).toBeTruthy();
|
expect(autoScrollButton).toBeTruthy();
|
||||||
});
|
});
|
||||||
it ('scrollToRight is called when clicking on auto scroll button', async () => {
|
it ('scrollToRight is called when clicking on auto scroll button', async () => {
|
||||||
// use spyon to spy the scroll function
|
// use spyon to spy the scroll function
|
||||||
spyOn(imageryView._getInstance().$refs.ImageryLayout, 'scrollToRight');
|
spyOn(imageryView._getInstance().$refs.ImageryContainer, 'scrollToRight');
|
||||||
imageryView._getInstance().$refs.ImageryLayout.autoScroll = false;
|
imageryView._getInstance().$refs.ImageryContainer.autoScroll = false;
|
||||||
await Vue.nextTick();
|
await Vue.nextTick();
|
||||||
parent.querySelector('.c-imagery__auto-scroll-resume-button').click();
|
parent.querySelector('.c-imagery__auto-scroll-resume-button').click();
|
||||||
expect(imageryView._getInstance().$refs.ImageryLayout.scrollToRight).toHaveBeenCalledWith('reset');
|
expect(imageryView._getInstance().$refs.ImageryContainer.scrollToRight).toHaveBeenCalledWith('reset');
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -143,10 +143,12 @@ export default {
|
|||||||
},
|
},
|
||||||
updateViewBounds() {
|
updateViewBounds() {
|
||||||
this.viewBounds = this.openmct.time.bounds();
|
this.viewBounds = this.openmct.time.bounds();
|
||||||
|
if (!this.options.compact) {
|
||||||
//Add a 50% padding to the end bounds to look ahead
|
//Add a 50% padding to the end bounds to look ahead
|
||||||
let timespan = (this.viewBounds.end - this.viewBounds.start);
|
let timespan = (this.viewBounds.end - this.viewBounds.start);
|
||||||
let padding = timespan / 2;
|
let padding = timespan / 2;
|
||||||
this.viewBounds.end = this.viewBounds.end + padding;
|
this.viewBounds.end = this.viewBounds.end + padding;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.timeSystem === undefined) {
|
if (this.timeSystem === undefined) {
|
||||||
this.timeSystem = this.openmct.time.timeSystem();
|
this.timeSystem = this.openmct.time.timeSystem();
|
||||||
|
@ -34,6 +34,7 @@
|
|||||||
class="u-contents"
|
class="u-contents"
|
||||||
:default-object="item.domainObject"
|
:default-object="item.domainObject"
|
||||||
:object-path="item.objectPath"
|
:object-path="item.objectPath"
|
||||||
|
@change-action-collection="setActionCollection"
|
||||||
/>
|
/>
|
||||||
</swim-lane>
|
</swim-lane>
|
||||||
</template>
|
</template>
|
||||||
@ -106,6 +107,9 @@ export default {
|
|||||||
this.$el, this.context);
|
this.$el, this.context);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
setActionCollection(actionCollection) {
|
||||||
|
this.openmct.menus.actionsToMenuItems(actionCollection.getVisibleActions(), actionCollection.objectPath, actionCollection.view);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -53,7 +53,7 @@ export default class ViewLargeAction {
|
|||||||
const parentElement = view.parentElement;
|
const parentElement = view.parentElement;
|
||||||
const element = parentElement && parentElement.firstChild;
|
const element = parentElement && parentElement.firstChild;
|
||||||
const viewLargeAction = element && !element.classList.contains('js-main-container')
|
const viewLargeAction = element && !element.classList.contains('js-main-container')
|
||||||
&& !this._isNavigatedObject(objectPath);
|
&& !this.openmct.router.isNavigatedObject(objectPath);
|
||||||
|
|
||||||
return viewLargeAction;
|
return viewLargeAction;
|
||||||
}
|
}
|
||||||
@ -85,11 +85,4 @@ export default class ViewLargeAction {
|
|||||||
|
|
||||||
return preview.$mount().$el;
|
return preview.$mount().$el;
|
||||||
}
|
}
|
||||||
|
|
||||||
_isNavigatedObject(objectPath) {
|
|
||||||
let targetObject = objectPath[0];
|
|
||||||
let navigatedObject = this.openmct.router.path[0];
|
|
||||||
|
|
||||||
return this.openmct.objects.areIdsEqual(targetObject.identifier, navigatedObject.identifier);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
@import "../plugins/folderView/components/grid-view.scss";
|
@import "../plugins/folderView/components/grid-view.scss";
|
||||||
@import "../plugins/folderView/components/list-item.scss";
|
@import "../plugins/folderView/components/list-item.scss";
|
||||||
@import "../plugins/folderView/components/list-view.scss";
|
@import "../plugins/folderView/components/list-view.scss";
|
||||||
@import "../plugins/imagery/components/imagery-view-layout.scss";
|
@import "../plugins/imagery/components/imagery-view.scss";
|
||||||
@import "../plugins/imagery/components/Compass/compass.scss";
|
@import "../plugins/imagery/components/Compass/compass.scss";
|
||||||
@import "../plugins/telemetryTable/components/table-row.scss";
|
@import "../plugins/telemetryTable/components/table-row.scss";
|
||||||
@import "../plugins/telemetryTable/components/table-footer-indicator.scss";
|
@import "../plugins/telemetryTable/components/table-footer-indicator.scss";
|
||||||
|
@ -122,8 +122,10 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
drawAxis(bounds, timeSystem) {
|
drawAxis(bounds, timeSystem) {
|
||||||
this.setScale(bounds, timeSystem);
|
let viewBounds = Object.assign({}, bounds);
|
||||||
this.setAxis(bounds);
|
|
||||||
|
this.setScale(viewBounds, timeSystem);
|
||||||
|
this.setAxis(viewBounds);
|
||||||
this.axisElement.call(this.xAxis);
|
this.axisElement.call(this.xAxis);
|
||||||
this.updateNowMarker();
|
this.updateNowMarker();
|
||||||
|
|
||||||
|
@ -3,7 +3,8 @@
|
|||||||
:class="{'c-swimlane': !isNested}"
|
:class="{'c-swimlane': !isNested}"
|
||||||
>
|
>
|
||||||
|
|
||||||
<div class="c-swimlane__lane-label c-object-label"
|
<div v-if="hideLabel === false"
|
||||||
|
class="c-swimlane__lane-label c-object-label"
|
||||||
:class="{'c-swimlane__lane-label--span-cols': (!spanRowsCount && !isNested)}"
|
:class="{'c-swimlane__lane-label--span-cols': (!spanRowsCount && !isNested)}"
|
||||||
:style="gridRowSpan"
|
:style="gridRowSpan"
|
||||||
>
|
>
|
||||||
@ -49,6 +50,12 @@ export default {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
hideLabel: {
|
||||||
|
type: Boolean,
|
||||||
|
default() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
isNested: {
|
isNested: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default() {
|
default() {
|
||||||
|
@ -81,17 +81,10 @@ export default class PreviewAction {
|
|||||||
const isObjectView = parentElement && parentElement.classList.contains('js-object-view');
|
const isObjectView = parentElement && parentElement.classList.contains('js-object-view');
|
||||||
|
|
||||||
return !PreviewAction.isVisible
|
return !PreviewAction.isVisible
|
||||||
&& !this._isNavigatedObject(objectPath)
|
&& !this._openmct.router.isNavigatedObject(objectPath)
|
||||||
&& !isObjectView;
|
&& !isObjectView;
|
||||||
}
|
}
|
||||||
|
|
||||||
_isNavigatedObject(objectPath) {
|
|
||||||
let targetObject = objectPath[0];
|
|
||||||
let navigatedObject = this._openmct.router.path[0];
|
|
||||||
|
|
||||||
return this._openmct.objects.areIdsEqual(targetObject.identifier, navigatedObject.identifier);
|
|
||||||
}
|
|
||||||
|
|
||||||
_preventPreview(objectPath) {
|
_preventPreview(objectPath) {
|
||||||
const noPreviewTypes = ['folder'];
|
const noPreviewTypes = ['folder'];
|
||||||
|
|
||||||
|
@ -136,6 +136,13 @@ class ApplicationRouter extends EventEmitter {
|
|||||||
this.handleLocationChange(hash.substring(1));
|
this.handleLocationChange(hash.substring(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isNavigatedObject(objectPath) {
|
||||||
|
let targetObject = objectPath[0];
|
||||||
|
let navigatedObject = this.path[0];
|
||||||
|
|
||||||
|
return this.openmct.objects.areIdsEqual(targetObject.identifier, navigatedObject.identifier);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add routes listeners
|
* Add routes listeners
|
||||||
*
|
*
|
||||||
|
Loading…
x
Reference in New Issue
Block a user