mirror of
https://github.com/nasa/openmct.git
synced 2024-12-20 05:37:53 +00:00
more scaffolding
This commit is contained in:
parent
3159de08b1
commit
680b0953b2
@ -21,10 +21,10 @@
|
||||
*****************************************************************************/
|
||||
import mount from 'utils/mount';
|
||||
|
||||
import EventTimeView from './components/EventTimeView.vue';
|
||||
import EventTimelineView from './components/EventTimelineView.vue';
|
||||
|
||||
export default function EventTimestripViewProvider(openmct) {
|
||||
const type = 'event.time-strip.view';
|
||||
const type = 'event.time-line.view';
|
||||
|
||||
function hasEventTelemetry(domainObject) {
|
||||
const metadata = openmct.telemetry.getMetadata(domainObject);
|
||||
@ -39,7 +39,7 @@ export default function EventTimestripViewProvider(openmct) {
|
||||
|
||||
return {
|
||||
key: type,
|
||||
name: 'Event Timestrip View',
|
||||
name: 'Event Timeline View',
|
||||
cssClass: 'icon-event',
|
||||
canView: function (domainObject, objectPath) {
|
||||
let isChildOfTimeStrip = objectPath.find((object) => object.type === 'time-strip');
|
||||
@ -60,14 +60,14 @@ export default function EventTimestripViewProvider(openmct) {
|
||||
{
|
||||
el: element,
|
||||
components: {
|
||||
EventTimeView
|
||||
EventTimelineView
|
||||
},
|
||||
provide: {
|
||||
openmct: openmct,
|
||||
domainObject: domainObject,
|
||||
objectPath: objectPath
|
||||
},
|
||||
template: '<event-time-view ref="root"></event-time-view>'
|
||||
template: '<event-timeline-view ref="root"></event-timeline-view>'
|
||||
},
|
||||
{
|
||||
app: openmct.app,
|
@ -1,413 +0,0 @@
|
||||
<!--
|
||||
Open MCT, Copyright (c) 2014-2024, 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="events" class="c-event-tsv c-timeline-holder">
|
||||
<div ref="imageryHolder" class="c-imagery-tsv__contents u-contents"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { scaleLinear, scaleUtc } from 'd3-scale';
|
||||
import _ from 'lodash';
|
||||
import mount from 'utils/mount';
|
||||
|
||||
import SwimLane from '@/ui/components/swim-lane/SwimLane.vue';
|
||||
import { PREVIEW_ACTION_KEY } from '@/ui/preview/PreviewAction.js';
|
||||
|
||||
const PADDING = 1;
|
||||
const ROW_HEIGHT = 100;
|
||||
const IMAGE_SIZE = 85;
|
||||
const IMAGE_WIDTH_THRESHOLD = 25;
|
||||
const CONTAINER_CLASS = 'c-imagery-tsv-container';
|
||||
const NO_ITEMS_CLASS = 'c-imagery-tsv__no-items';
|
||||
const IMAGE_WRAPPER_CLASS = 'c-imagery-tsv__image-wrapper';
|
||||
const ID_PREFIX = 'wrapper-';
|
||||
|
||||
export default {
|
||||
inject: ['openmct', 'domainObject', 'objectPath'],
|
||||
data() {
|
||||
let timeSystem = this.openmct.time.getTimeSystem();
|
||||
this.metadata = {};
|
||||
this.requestCount = 0;
|
||||
|
||||
return {
|
||||
viewBounds: undefined,
|
||||
height: 0,
|
||||
durationFormatter: undefined,
|
||||
imageHistory: [],
|
||||
timeSystem: timeSystem,
|
||||
keyString: undefined
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
imageHistory: {
|
||||
handler(newHistory, oldHistory) {
|
||||
this.updatePlotImagery();
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.previewAction = this.openmct.actions.getAction(PREVIEW_ACTION_KEY);
|
||||
|
||||
this.canvas = this.$refs.imagery.appendChild(document.createElement('canvas'));
|
||||
this.canvas.height = 0;
|
||||
this.canvasContext = this.canvas.getContext('2d');
|
||||
this.setDimensions();
|
||||
|
||||
this.setScaleAndPlotImagery = this.setScaleAndPlotImagery.bind(this);
|
||||
this.updateViewBounds = this.updateViewBounds.bind(this);
|
||||
this.setTimeContext = this.setTimeContext.bind(this);
|
||||
this.setTimeContext();
|
||||
|
||||
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);
|
||||
},
|
||||
beforeUnmount() {
|
||||
if (this.imageryStripResizeObserver) {
|
||||
this.imageryStripResizeObserver.disconnect();
|
||||
}
|
||||
|
||||
this.stopFollowingTimeContext();
|
||||
if (this.unlisten) {
|
||||
this.unlisten();
|
||||
}
|
||||
if (this.destroyImageryContainer) {
|
||||
this.destroyImageryContainer();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setTimeContext() {
|
||||
this.stopFollowingTimeContext();
|
||||
this.timeContext = this.openmct.time.getContextForView(this.objectPath);
|
||||
this.timeContext.on('timeSystem', this.setScaleAndPlotImagery);
|
||||
this.timeContext.on('boundsChanged', this.updateViewBounds);
|
||||
},
|
||||
stopFollowingTimeContext() {
|
||||
if (this.timeContext) {
|
||||
this.timeContext.off('timeSystem', this.setScaleAndPlotImagery);
|
||||
this.timeContext.off('boundsChanged', this.updateViewBounds);
|
||||
}
|
||||
},
|
||||
expand(imageTimestamp) {
|
||||
const path = this.objectPath[0];
|
||||
this.previewAction.invoke([path], {
|
||||
timestamp: imageTimestamp,
|
||||
objectPath: this.objectPath
|
||||
});
|
||||
},
|
||||
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.timeContext.getBounds();
|
||||
|
||||
if (this.timeSystem === undefined) {
|
||||
this.timeSystem = this.timeContext.getTimeSystem();
|
||||
}
|
||||
|
||||
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(`.${NO_ITEMS_CLASS}`);
|
||||
noItemsEl.forEach((item) => {
|
||||
item.remove();
|
||||
});
|
||||
let imagery = this.$el.querySelectorAll(`.${IMAGE_WRAPPER_CLASS}`);
|
||||
imagery.forEach((imageElm) => {
|
||||
if (clearAllImagery) {
|
||||
imageElm.remove();
|
||||
} else {
|
||||
const id = imageElm.getAttributeNS(null, 'id');
|
||||
if (id) {
|
||||
const timestamp = id.replace(ID_PREFIX, '');
|
||||
if (
|
||||
!this.isImageryInBounds({
|
||||
time: timestamp
|
||||
})
|
||||
) {
|
||||
imageElm.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.timeContext.getTimeSystem();
|
||||
}
|
||||
|
||||
if (timeSystem.isUTCBased) {
|
||||
this.xScale = scaleUtc();
|
||||
this.xScale.domain([new Date(this.viewBounds.start), new Date(this.viewBounds.end)]);
|
||||
} else {
|
||||
this.xScale = 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 containerHeight = 100;
|
||||
let containerWidth = this.imageHistory.length ? this.width : 200;
|
||||
let imageryContainer;
|
||||
|
||||
let existingContainer = this.$el.querySelector(`.${CONTAINER_CLASS}`);
|
||||
if (existingContainer) {
|
||||
imageryContainer = existingContainer;
|
||||
imageryContainer.style.maxWidth = `${containerWidth}px`;
|
||||
} else {
|
||||
if (this.destroyImageryContainer) {
|
||||
this.destroyImageryContainer();
|
||||
}
|
||||
const { vNode, destroy } = mount(
|
||||
{
|
||||
components: {
|
||||
SwimLane
|
||||
},
|
||||
provide: {
|
||||
openmct: this.openmct
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isNested: true
|
||||
};
|
||||
},
|
||||
template: `<swim-lane :is-nested="isNested" :hide-label="true"><template v-slot:object><div class="c-imagery-tsv-container"></div></template></swim-lane>`
|
||||
},
|
||||
{
|
||||
app: this.openmct.app
|
||||
}
|
||||
);
|
||||
|
||||
this.destroyImageryContainer = destroy;
|
||||
const component = vNode.componentInstance;
|
||||
this.$refs.imageryHolder.appendChild(component.$el);
|
||||
|
||||
imageryContainer = component.$el.querySelector(`.${CONTAINER_CLASS}`);
|
||||
imageryContainer.style.maxWidth = `${containerWidth}px`;
|
||||
imageryContainer.style.height = `${containerHeight}px`;
|
||||
}
|
||||
|
||||
return imageryContainer;
|
||||
},
|
||||
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 imageryContainer = this.getImageryContainer();
|
||||
const showImagePlaceholders = this.isImageryWidthAcceptable();
|
||||
let index = 0;
|
||||
if (this.imageHistory.length) {
|
||||
this.imageHistory.forEach((currentImageObject) => {
|
||||
if (this.isImageryInBounds(currentImageObject)) {
|
||||
this.plotImagery(currentImageObject, showImagePlaceholders, imageryContainer, index);
|
||||
index = index + 1;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.plotNoItems(imageryContainer);
|
||||
}
|
||||
},
|
||||
plotNoItems(containerElement) {
|
||||
let textElement = document.createElement('text');
|
||||
textElement.classList.add(NO_ITEMS_CLASS);
|
||||
textElement.innerHTML = 'No images within timeframe';
|
||||
|
||||
containerElement.appendChild(textElement);
|
||||
},
|
||||
setNSAttributesForElement(element, attributes) {
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object.keys(attributes).forEach((key) => {
|
||||
element.setAttributeNS(null, key, attributes[key]);
|
||||
});
|
||||
},
|
||||
setStyles(element, styles) {
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object.keys(styles).forEach((key) => {
|
||||
element.style[key] = styles[key];
|
||||
});
|
||||
},
|
||||
getImageWrapper(item) {
|
||||
const id = `${ID_PREFIX}${item.time}`;
|
||||
|
||||
return this.$el.querySelector(`.c-imagery-tsv__contents div[id=${id}]`);
|
||||
},
|
||||
plotImagery(item, showImagePlaceholders, containerElement, index) {
|
||||
let existingImageWrapper = this.getImageWrapper(item);
|
||||
//imageWrapper wraps the vertical tick and the image
|
||||
if (existingImageWrapper) {
|
||||
this.updateExistingImageWrapper(existingImageWrapper, item, showImagePlaceholders);
|
||||
} else {
|
||||
let imageWrapper = this.createImageWrapper(index, item, showImagePlaceholders);
|
||||
containerElement.appendChild(imageWrapper);
|
||||
}
|
||||
},
|
||||
setImageDisplay(imageElement, showImagePlaceholders) {
|
||||
if (showImagePlaceholders) {
|
||||
imageElement.style.display = 'none';
|
||||
} else {
|
||||
imageElement.style.display = 'block';
|
||||
}
|
||||
},
|
||||
updateExistingImageWrapper(existingImageWrapper, image, showImagePlaceholders) {
|
||||
//Update the x co-ordinates of the image wrapper and the url of image
|
||||
//this is to avoid tearing down all elements completely and re-drawing them
|
||||
this.setNSAttributesForElement(existingImageWrapper, {
|
||||
'data-show-image-placeholders': showImagePlaceholders
|
||||
});
|
||||
existingImageWrapper.style.left = `${this.xScale(image.time)}px`;
|
||||
|
||||
let imageElement = existingImageWrapper.querySelector('img');
|
||||
this.setNSAttributesForElement(imageElement, {
|
||||
src: image.thumbnailUrl || image.url
|
||||
});
|
||||
this.setImageDisplay(imageElement, showImagePlaceholders);
|
||||
},
|
||||
createImageWrapper(index, image, showImagePlaceholders) {
|
||||
const id = `${ID_PREFIX}${image.time}`;
|
||||
let imageWrapper = document.createElement('div');
|
||||
imageWrapper.ariaLabel = id;
|
||||
imageWrapper.classList.add(IMAGE_WRAPPER_CLASS);
|
||||
imageWrapper.style.left = `${this.xScale(image.time)}px`;
|
||||
this.setNSAttributesForElement(imageWrapper, {
|
||||
id,
|
||||
'data-show-image-placeholders': showImagePlaceholders
|
||||
});
|
||||
//create image vertical tick indicator
|
||||
let imageTickElement = document.createElement('div');
|
||||
imageTickElement.classList.add('c-imagery-tsv__image-handle');
|
||||
imageTickElement.style.width = '2px';
|
||||
imageTickElement.style.height = `${String(ROW_HEIGHT - 10)}px`;
|
||||
imageWrapper.appendChild(imageTickElement);
|
||||
|
||||
//create placeholder - this will also hold the actual image
|
||||
let imagePlaceholder = document.createElement('div');
|
||||
imagePlaceholder.classList.add('c-imagery-tsv__image-placeholder');
|
||||
imagePlaceholder.style.width = `${IMAGE_SIZE}px`;
|
||||
imagePlaceholder.style.height = `${IMAGE_SIZE}px`;
|
||||
imageWrapper.appendChild(imagePlaceholder);
|
||||
|
||||
//create image element
|
||||
let imageElement = document.createElement('img');
|
||||
this.setNSAttributesForElement(imageElement, {
|
||||
src: image.thumbnailUrl || image.url
|
||||
});
|
||||
imageElement.style.width = `${IMAGE_SIZE}px`;
|
||||
imageElement.style.height = `${IMAGE_SIZE}px`;
|
||||
this.setImageDisplay(imageElement, showImagePlaceholders);
|
||||
|
||||
//handle mousedown event to show the image in a large view
|
||||
imageWrapper.addEventListener('mousedown', (e) => {
|
||||
if (e.button === 0) {
|
||||
this.expand(image.time);
|
||||
}
|
||||
});
|
||||
|
||||
imagePlaceholder.appendChild(imageElement);
|
||||
|
||||
return imageWrapper;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
385
src/plugins/events/components/EventTimelineView.vue
Normal file
385
src/plugins/events/components/EventTimelineView.vue
Normal file
@ -0,0 +1,385 @@
|
||||
<!--
|
||||
Open MCT, Copyright (c) 2014-2024, 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="events" class="c-event-tsv c-timeline-holder">
|
||||
<div ref="eventsHolder" class="c-event-tsv__contents u-contents"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { scaleLinear, scaleUtc } from 'd3-scale';
|
||||
import _ from 'lodash';
|
||||
import mount from 'utils/mount';
|
||||
|
||||
import SwimLane from '@/ui/components/swim-lane/SwimLane.vue';
|
||||
import { PREVIEW_ACTION_KEY } from '@/ui/preview/PreviewAction.js';
|
||||
|
||||
const PADDING = 1;
|
||||
const ROW_HEIGHT = 100;
|
||||
const EVENT_DETAIL_SIZE = 85;
|
||||
const CONTAINER_CLASS = 'c-event-tsv-container';
|
||||
const NO_ITEMS_CLASS = 'c-event-tsv__no-items';
|
||||
const EVENT_WRAPPER_CLASS = 'c-event-tsv__event-wrapper';
|
||||
const ID_PREFIX = 'wrapper-';
|
||||
|
||||
export default {
|
||||
inject: ['openmct', 'domainObject', 'objectPath'],
|
||||
data() {
|
||||
let timeSystem = this.openmct.time.getTimeSystem();
|
||||
this.metadata = {};
|
||||
this.requestCount = 0;
|
||||
|
||||
return {
|
||||
viewBounds: undefined,
|
||||
height: 0,
|
||||
durationFormatter: undefined,
|
||||
eventHistory: [],
|
||||
timeSystem: timeSystem,
|
||||
keyString: undefined
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
eventHistory: {
|
||||
handler(newHistory, oldHistory) {
|
||||
this.updatePlotEvents();
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.previewAction = this.openmct.actions.getAction(PREVIEW_ACTION_KEY);
|
||||
|
||||
this.canvas = this.$refs.events.appendChild(document.createElement('canvas'));
|
||||
this.canvas.height = 0;
|
||||
this.canvasContext = this.canvas.getContext('2d');
|
||||
this.setDimensions();
|
||||
|
||||
this.setScaleAndPlotEvents = this.setScaleAndPlotEvents.bind(this);
|
||||
this.updateViewBounds = this.updateViewBounds.bind(this);
|
||||
this.setTimeContext = this.setTimeContext.bind(this);
|
||||
this.setTimeContext();
|
||||
|
||||
this.updateViewBounds();
|
||||
|
||||
this.resize = _.debounce(this.resize, 400);
|
||||
this.eventStripResizeObserver = new ResizeObserver(this.resize);
|
||||
this.eventStripResizeObserver.observe(this.$refs.events);
|
||||
|
||||
this.unlisten = this.openmct.objects.observe(this.domainObject, '*', this.observeForChanges);
|
||||
},
|
||||
beforeUnmount() {
|
||||
if (this.eventStripResizeObserver) {
|
||||
this.eventStripResizeObserver.disconnect();
|
||||
}
|
||||
|
||||
this.stopFollowingTimeContext();
|
||||
if (this.unlisten) {
|
||||
this.unlisten();
|
||||
}
|
||||
if (this.destroyEventContainer) {
|
||||
this.destroyEventContainer();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setTimeContext() {
|
||||
this.stopFollowingTimeContext();
|
||||
this.timeContext = this.openmct.time.getContextForView(this.objectPath);
|
||||
this.timeContext.on('timeSystem', this.setScaleAndPlotEvents);
|
||||
this.timeContext.on('boundsChanged', this.updateViewBounds);
|
||||
},
|
||||
stopFollowingTimeContext() {
|
||||
if (this.timeContext) {
|
||||
this.timeContext.off('timeSystem', this.setScaleAndPlotEvents);
|
||||
this.timeContext.off('boundsChanged', this.updateViewBounds);
|
||||
}
|
||||
},
|
||||
expand(eventTimestamp) {
|
||||
const path = this.objectPath[0];
|
||||
this.previewAction.invoke([path], {
|
||||
timestamp: eventTimestamp,
|
||||
objectPath: this.objectPath
|
||||
});
|
||||
},
|
||||
observeForChanges(mutatedObject) {
|
||||
this.updateViewBounds();
|
||||
},
|
||||
resize() {
|
||||
let clientWidth = this.getClientWidth();
|
||||
if (clientWidth !== this.width) {
|
||||
this.setDimensions();
|
||||
this.updateViewBounds();
|
||||
}
|
||||
},
|
||||
getClientWidth() {
|
||||
let clientWidth = this.$refs.events.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.timeContext.getBounds();
|
||||
|
||||
if (this.timeSystem === undefined) {
|
||||
this.timeSystem = this.timeContext.getTimeSystem();
|
||||
}
|
||||
|
||||
this.setScaleAndPlotEvents(this.timeSystem, !isTick);
|
||||
},
|
||||
setScaleAndPlotEvents(timeSystem, clearAllEvents) {
|
||||
if (timeSystem !== undefined) {
|
||||
this.timeSystem = timeSystem;
|
||||
this.timeFormatter = this.getFormatter(this.timeSystem.key);
|
||||
}
|
||||
|
||||
this.setScale(this.timeSystem);
|
||||
this.updatePlotEvents(clearAllEvents);
|
||||
},
|
||||
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;
|
||||
},
|
||||
updatePlotEvents(clearAllEvents) {
|
||||
this.clearPreviousEvents(clearAllEvents);
|
||||
if (this.xScale) {
|
||||
this.drawEvents();
|
||||
}
|
||||
},
|
||||
clearPreviousEvents(clearAllEvents) {
|
||||
//TODO: Only clear items that are out of bounds
|
||||
let noItemsEl = this.$el.querySelectorAll(`.${NO_ITEMS_CLASS}`);
|
||||
noItemsEl.forEach((item) => {
|
||||
item.remove();
|
||||
});
|
||||
let events = this.$el.querySelectorAll(`.${EVENT_WRAPPER_CLASS}`);
|
||||
events.forEach((eventElm) => {
|
||||
if (clearAllEvents) {
|
||||
eventElm.remove();
|
||||
} else {
|
||||
const id = eventElm.getAttributeNS(null, 'id');
|
||||
if (id) {
|
||||
const timestamp = id.replace(ID_PREFIX, '');
|
||||
if (
|
||||
!this.isEventInBounds({
|
||||
time: timestamp
|
||||
})
|
||||
) {
|
||||
eventElm.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
setDimensions() {
|
||||
const eventsHolder = this.$refs.events;
|
||||
this.width = this.getClientWidth();
|
||||
|
||||
this.height = Math.round(eventsHolder.getBoundingClientRect().height);
|
||||
},
|
||||
setScale(timeSystem) {
|
||||
if (!this.width) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (timeSystem === undefined) {
|
||||
timeSystem = this.timeContext.getTimeSystem();
|
||||
}
|
||||
|
||||
if (timeSystem.isUTCBased) {
|
||||
this.xScale = scaleUtc();
|
||||
this.xScale.domain([new Date(this.viewBounds.start), new Date(this.viewBounds.end)]);
|
||||
} else {
|
||||
this.xScale = scaleLinear();
|
||||
this.xScale.domain([this.viewBounds.start, this.viewBounds.end]);
|
||||
}
|
||||
|
||||
this.xScale.range([PADDING, this.width - PADDING * 2]);
|
||||
},
|
||||
isEventInBounds(evenObj) {
|
||||
return evenObj.time <= this.viewBounds.end && evenObj.time >= this.viewBounds.start;
|
||||
},
|
||||
getEventsContainer() {
|
||||
let containerHeight = 100;
|
||||
let containerWidth = this.eventHistory.length ? this.width : 200;
|
||||
let eventContainer;
|
||||
|
||||
let existingContainer = this.$el.querySelector(`.${CONTAINER_CLASS}`);
|
||||
if (existingContainer) {
|
||||
eventContainer = existingContainer;
|
||||
eventContainer.style.maxWidth = `${containerWidth}px`;
|
||||
} else {
|
||||
if (this.destroyEventsContainer) {
|
||||
this.destroyEventsContainer();
|
||||
}
|
||||
const { vNode, destroy } = mount(
|
||||
{
|
||||
components: {
|
||||
SwimLane
|
||||
},
|
||||
provide: {
|
||||
openmct: this.openmct
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isNested: true
|
||||
};
|
||||
},
|
||||
template: `<swim-lane :is-nested="isNested" :hide-label="true"><template v-slot:object><div class="c-event-tsv-container"></div></template></swim-lane>`
|
||||
},
|
||||
{
|
||||
app: this.openmct.app
|
||||
}
|
||||
);
|
||||
|
||||
this.destroyEventsContainer = destroy;
|
||||
const component = vNode.componentInstance;
|
||||
this.$refs.eventsHolder.appendChild(component.$el);
|
||||
|
||||
eventContainer = component.$el.querySelector(`.${CONTAINER_CLASS}`);
|
||||
eventContainer.style.maxWidth = `${containerWidth}px`;
|
||||
eventContainer.style.height = `${containerHeight}px`;
|
||||
}
|
||||
|
||||
return eventContainer;
|
||||
},
|
||||
drawEvents() {
|
||||
let eventContainer = this.getEventsContainer();
|
||||
let index = 0;
|
||||
if (this.eventHistory.length) {
|
||||
this.eventHistory.forEach((currentEventObject) => {
|
||||
if (this.isEventsInBounds(currentEventObject)) {
|
||||
this.plotEvents(currentEventObject, eventContainer, index);
|
||||
index = index + 1;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.plotNoItems(eventContainer);
|
||||
}
|
||||
},
|
||||
plotNoItems(containerElement) {
|
||||
let textElement = document.createElement('text');
|
||||
textElement.classList.add(NO_ITEMS_CLASS);
|
||||
textElement.innerHTML = 'No events within timeframe';
|
||||
|
||||
containerElement.appendChild(textElement);
|
||||
},
|
||||
setNSAttributesForElement(element, attributes) {
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object.keys(attributes).forEach((key) => {
|
||||
element.setAttributeNS(null, key, attributes[key]);
|
||||
});
|
||||
},
|
||||
setStyles(element, styles) {
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object.keys(styles).forEach((key) => {
|
||||
element.style[key] = styles[key];
|
||||
});
|
||||
},
|
||||
getEventWrapper(item) {
|
||||
const id = `${ID_PREFIX}${item.time}`;
|
||||
|
||||
return this.$el.querySelector(`.c-events-tsv__contents div[id=${id}]`);
|
||||
},
|
||||
plotEvents(item, containerElement, index) {
|
||||
let existingEventWrapper = this.getEventWrapper(item);
|
||||
//eventWrapper wraps the vertical tick and the EVENT
|
||||
if (existingEventWrapper) {
|
||||
this.updateExistingEventWrapper(existingEventWrapper, item);
|
||||
} else {
|
||||
let eventWrapper = this.createEventWrapper(index, item);
|
||||
containerElement.appendChild(eventWrapper);
|
||||
}
|
||||
},
|
||||
setEventDisplay(eventElement) {
|
||||
eventElement.style.display = 'block';
|
||||
},
|
||||
updateExistingEventWrapper(existingEventWrapper, event) {
|
||||
//Update the x co-ordinates of the event wrapper and the url of event
|
||||
//this is to avoid tearing down all elements completely and re-drawing them
|
||||
existingEventWrapper.style.left = `${this.xScale(event.time)}px`;
|
||||
|
||||
let eventElement = existingEventWrapper.querySelector('img');
|
||||
this.setNSAttributesForElement(eventElement, {
|
||||
src: event.thumbnailUrl || event.url
|
||||
});
|
||||
this.setEventDisplay(eventElement);
|
||||
},
|
||||
createEventWrapper(index, event) {
|
||||
const id = `${ID_PREFIX}${event.time}`;
|
||||
let eventWrapper = document.createElement('div');
|
||||
eventWrapper.ariaLabel = id;
|
||||
eventWrapper.classList.add(EVENT_WRAPPER_CLASS);
|
||||
eventWrapper.style.left = `${this.xScale(event.time)}px`;
|
||||
//create event vertical tick indicator
|
||||
let eventTickElement = document.createElement('div');
|
||||
eventTickElement.classList.add('c-events-tsv__event-handle');
|
||||
eventTickElement.style.width = '2px';
|
||||
eventTickElement.style.height = `${String(ROW_HEIGHT - 10)}px`;
|
||||
eventWrapper.appendChild(eventTickElement);
|
||||
|
||||
//create placeholder - this will also hold the actual event
|
||||
let eventPlaceholder = document.createElement('div');
|
||||
eventPlaceholder.classList.add('c-events-tsv__event-placeholder');
|
||||
eventPlaceholder.style.width = `${EVENT_DETAIL_SIZE}px`;
|
||||
eventPlaceholder.style.height = `${EVENT_DETAIL_SIZE}px`;
|
||||
eventWrapper.appendChild(eventPlaceholder);
|
||||
|
||||
//create event element
|
||||
let eventElement = document.createElement('img');
|
||||
this.setNSAttributesForElement(eventElement, {
|
||||
src: event.thumbnailUrl || event.url
|
||||
});
|
||||
eventElement.style.width = `${EVENT_DETAIL_SIZE}px`;
|
||||
eventElement.style.height = `${EVENT_DETAIL_SIZE}px`;
|
||||
this.setEventDisplay(eventElement);
|
||||
|
||||
//handle mousedown event to show the event in a large view
|
||||
eventWrapper.addEventListener('mousedown', (e) => {
|
||||
if (e.button === 0) {
|
||||
this.expand(event.time);
|
||||
}
|
||||
});
|
||||
|
||||
eventPlaceholder.appendChild(eventElement);
|
||||
|
||||
return eventWrapper;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
@ -20,10 +20,10 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import EventTimestripViewProvider from './EventTimestripViewProvider.js';
|
||||
import EventTimelineViewProvider from './EventTimelineViewProvider.js';
|
||||
|
||||
export default function (options) {
|
||||
return function install(openmct) {
|
||||
openmct.objectViews.addProvider(new EventTimestripViewProvider(openmct));
|
||||
openmct.objectViews.addProvider(new EventTimelineViewProvider(openmct));
|
||||
};
|
||||
}
|
||||
|
@ -33,10 +33,7 @@ export default function TimelineCompositionPolicy(openmct) {
|
||||
}
|
||||
|
||||
function hasDomainAndRange(metadata) {
|
||||
return (
|
||||
metadata.valuesForHints(['range']).length > 0 &&
|
||||
metadata.valuesForHints(['domain']).length > 0
|
||||
);
|
||||
return metadata.valuesForHints(['domain']).length > 0;
|
||||
}
|
||||
|
||||
function hasImageTelemetry(domainObject, metadata) {
|
||||
|
Loading…
Reference in New Issue
Block a user