mirror of
https://github.com/nasa/openmct.git
synced 2025-02-01 00:45:41 +00:00
going to try with YAMCS data
This commit is contained in:
parent
680b0953b2
commit
7b22cf3371
@ -21,8 +21,8 @@
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div ref="events" class="c-event-tsv c-timeline-holder">
|
||||
<div ref="eventsHolder" class="c-event-tsv__contents u-contents"></div>
|
||||
<div ref="events" class="c-events-tsv c-timeline-holder">
|
||||
<div ref="eventsHolder" class="c-events-tsv__contents u-contents"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -32,20 +32,21 @@ 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';
|
||||
|
||||
import eventData from '../mixins/eventData.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 CONTAINER_CLASS = 'c-events-tsv-container';
|
||||
const NO_ITEMS_CLASS = 'c-events-tsv__no-items';
|
||||
const EVENT_WRAPPER_CLASS = 'c-events-tsv__event-wrapper';
|
||||
const ID_PREFIX = 'wrapper-';
|
||||
|
||||
export default {
|
||||
mixins: [eventData],
|
||||
inject: ['openmct', 'domainObject', 'objectPath'],
|
||||
data() {
|
||||
let timeSystem = this.openmct.time.getTimeSystem();
|
||||
const timeSystem = this.openmct.time.getTimeSystem();
|
||||
this.metadata = {};
|
||||
this.requestCount = 0;
|
||||
|
||||
@ -67,8 +68,6 @@ export default {
|
||||
}
|
||||
},
|
||||
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');
|
||||
@ -113,18 +112,11 @@ export default {
|
||||
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();
|
||||
const clientWidth = this.getClientWidth();
|
||||
if (clientWidth !== this.width) {
|
||||
this.setDimensions();
|
||||
this.updateViewBounds();
|
||||
@ -135,25 +127,26 @@ export default {
|
||||
|
||||
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;
|
||||
const parent = this.openmct.layout.$refs.browseObject.$el;
|
||||
if (parent) {
|
||||
clientWidth = parent.getBoundingClientRect().width;
|
||||
}
|
||||
}
|
||||
console.debug('🇹🇯 Calculated clientWidth:', clientWidth);
|
||||
|
||||
return clientWidth;
|
||||
},
|
||||
updateViewBounds(bounds, isTick) {
|
||||
this.viewBounds = this.timeContext.getBounds();
|
||||
|
||||
if (this.timeSystem === undefined) {
|
||||
if (!this.timeSystem) {
|
||||
this.timeSystem = this.timeContext.getTimeSystem();
|
||||
}
|
||||
|
||||
this.setScaleAndPlotEvents(this.timeSystem, !isTick);
|
||||
},
|
||||
setScaleAndPlotEvents(timeSystem, clearAllEvents) {
|
||||
if (timeSystem !== undefined) {
|
||||
if (timeSystem) {
|
||||
this.timeSystem = timeSystem;
|
||||
this.timeFormatter = this.getFormatter(this.timeSystem.key);
|
||||
}
|
||||
@ -164,8 +157,8 @@ export default {
|
||||
getFormatter(key) {
|
||||
const metadata = this.openmct.telemetry.getMetadata(this.domainObject);
|
||||
|
||||
let metadataValue = metadata.value(key) || { format: key };
|
||||
let valueFormatter = this.openmct.telemetry.getValueFormatter(metadataValue);
|
||||
const metadataValue = metadata.value(key) || { format: key };
|
||||
const valueFormatter = this.openmct.telemetry.getValueFormatter(metadataValue);
|
||||
|
||||
return valueFormatter;
|
||||
},
|
||||
@ -181,7 +174,7 @@ export default {
|
||||
noItemsEl.forEach((item) => {
|
||||
item.remove();
|
||||
});
|
||||
let events = this.$el.querySelectorAll(`.${EVENT_WRAPPER_CLASS}`);
|
||||
const events = this.$el.querySelectorAll(`.${EVENT_WRAPPER_CLASS}`);
|
||||
events.forEach((eventElm) => {
|
||||
if (clearAllEvents) {
|
||||
eventElm.remove();
|
||||
@ -211,7 +204,7 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
if (timeSystem === undefined) {
|
||||
if (!timeSystem) {
|
||||
timeSystem = this.timeContext.getTimeSystem();
|
||||
}
|
||||
|
||||
@ -229,8 +222,8 @@ export default {
|
||||
return evenObj.time <= this.viewBounds.end && evenObj.time >= this.viewBounds.start;
|
||||
},
|
||||
getEventsContainer() {
|
||||
let containerHeight = 100;
|
||||
let containerWidth = this.eventHistory.length ? this.width : 200;
|
||||
const containerHeight = 100;
|
||||
const containerWidth = this.eventHistory.length ? this.width : 200;
|
||||
let eventContainer;
|
||||
|
||||
let existingContainer = this.$el.querySelector(`.${CONTAINER_CLASS}`);
|
||||
@ -254,7 +247,7 @@ export default {
|
||||
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>`
|
||||
template: `<swim-lane :is-nested="isNested" :hide-label="true"><template v-slot:object><div class="c-events-tsv-container"></div></template></swim-lane>`
|
||||
},
|
||||
{
|
||||
app: this.openmct.app
|
||||
@ -277,7 +270,7 @@ export default {
|
||||
let index = 0;
|
||||
if (this.eventHistory.length) {
|
||||
this.eventHistory.forEach((currentEventObject) => {
|
||||
if (this.isEventsInBounds(currentEventObject)) {
|
||||
if (this.isEventInBounds(currentEventObject)) {
|
||||
this.plotEvents(currentEventObject, eventContainer, index);
|
||||
index = index + 1;
|
||||
}
|
||||
@ -287,7 +280,7 @@ export default {
|
||||
}
|
||||
},
|
||||
plotNoItems(containerElement) {
|
||||
let textElement = document.createElement('text');
|
||||
const textElement = document.createElement('text');
|
||||
textElement.classList.add(NO_ITEMS_CLASS);
|
||||
textElement.innerHTML = 'No events within timeframe';
|
||||
|
||||
@ -317,58 +310,34 @@ export default {
|
||||
return this.$el.querySelector(`.c-events-tsv__contents div[id=${id}]`);
|
||||
},
|
||||
plotEvents(item, containerElement, index) {
|
||||
let existingEventWrapper = this.getEventWrapper(item);
|
||||
const 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);
|
||||
const eventWrapper = this.createEventWrapper(index, item);
|
||||
containerElement.appendChild(eventWrapper);
|
||||
}
|
||||
},
|
||||
setEventDisplay(eventElement) {
|
||||
eventElement.style.display = 'block';
|
||||
console.debug('🧟 Event time:', new Date(item.time).toISOString());
|
||||
console.debug('🧟 Scaled X position:', this.xScale(item.time));
|
||||
},
|
||||
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
|
||||
//Update the x co-ordinates of the event wrapper
|
||||
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');
|
||||
const 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');
|
||||
const 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) {
|
||||
@ -376,8 +345,6 @@ export default {
|
||||
}
|
||||
});
|
||||
|
||||
eventPlaceholder.appendChild(eventElement);
|
||||
|
||||
return eventWrapper;
|
||||
}
|
||||
}
|
||||
|
34
src/plugins/events/components/events-view.scss
Normal file
34
src/plugins/events/components/events-view.scss
Normal file
@ -0,0 +1,34 @@
|
||||
.c-events-tsv {
|
||||
div.c-events-tsv__event-wrapper {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
display: flex;
|
||||
z-index: 1;
|
||||
margin-top: 5px;
|
||||
&:hover {
|
||||
z-index: 2;
|
||||
|
||||
[class*='__event-handle'] {
|
||||
background-color: $colorBodyFg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__no-items {
|
||||
fill: $colorBodyFg !important;
|
||||
}
|
||||
|
||||
&__event-handle {
|
||||
background-color: rgba($colorBodyFg, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
.c-events-canvas {
|
||||
pointer-events: auto; // This allows the event element to receive a browser-level context click
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 2;
|
||||
}
|
||||
|
183
src/plugins/events/mixins/eventData.js
Normal file
183
src/plugins/events/mixins/eventData.js
Normal file
@ -0,0 +1,183 @@
|
||||
/*****************************************************************************
|
||||
* 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.
|
||||
*****************************************************************************/
|
||||
|
||||
const DEFAULT_DURATION_FORMATTER = 'duration';
|
||||
import { TIME_CONTEXT_EVENTS } from '../../../api/time/constants.js';
|
||||
|
||||
export default {
|
||||
inject: ['openmct', 'domainObject', 'objectPath'],
|
||||
mounted() {
|
||||
// listen
|
||||
this.boundsChanged = this.boundsChanged.bind(this);
|
||||
this.timeSystemChanged = this.timeSystemChanged.bind(this);
|
||||
this.setDataTimeContext = this.setDataTimeContext.bind(this);
|
||||
this.openmct.objectViews.on('clearData', this.dataCleared);
|
||||
|
||||
// Get metadata and formatters
|
||||
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||
this.metadata = this.openmct.telemetry.getMetadata(this.domainObject);
|
||||
|
||||
this.durationFormatter = this.getFormatter(
|
||||
this.timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER
|
||||
);
|
||||
|
||||
// initialize
|
||||
this.timeKey = this.timeSystem.key;
|
||||
this.timeFormatter = this.getFormatter(this.timeKey);
|
||||
this.setDataTimeContext();
|
||||
this.loadTelemetry();
|
||||
},
|
||||
beforeUnmount() {
|
||||
if (this.unsubscribe) {
|
||||
this.unsubscribe();
|
||||
delete this.unsubscribe;
|
||||
}
|
||||
|
||||
this.stopFollowingDataTimeContext();
|
||||
this.openmct.objectViews.off('clearData', this.dataCleared);
|
||||
|
||||
this.telemetryCollection.off('add', this.dataAdded);
|
||||
this.telemetryCollection.off('remove', this.dataRemoved);
|
||||
this.telemetryCollection.off('clear', this.dataCleared);
|
||||
|
||||
this.telemetryCollection.destroy();
|
||||
},
|
||||
methods: {
|
||||
dataAdded(addedItems, addedItemIndices) {
|
||||
const normalizedDataToAdd = addedItems.map((datum) => this.normalizeDatum(datum));
|
||||
let newEventHistory = this.eventHistory.slice();
|
||||
normalizedDataToAdd.forEach((datum, index) => {
|
||||
newEventHistory.splice(addedItemIndices[index] ?? -1, 0, datum);
|
||||
});
|
||||
//Assign just once so eventHistory watchers don't get called too often
|
||||
this.eventHistory = newEventHistory;
|
||||
},
|
||||
dataCleared() {
|
||||
this.eventHistory = [];
|
||||
},
|
||||
dataRemoved(removed) {
|
||||
const removedTimestamps = {};
|
||||
removed.forEach((_removed) => {
|
||||
const removedTimestamp = this.parseTime(_removed);
|
||||
removedTimestamps[removedTimestamp] = true;
|
||||
});
|
||||
|
||||
this.eventHistory = this.eventHistory.filter((event) => {
|
||||
const eventTimestamp = this.parseTime(event);
|
||||
|
||||
return !removedTimestamps[eventTimestamp];
|
||||
});
|
||||
},
|
||||
setDataTimeContext() {
|
||||
this.stopFollowingDataTimeContext();
|
||||
this.timeContext = this.openmct.time.getContextForView(this.objectPath);
|
||||
this.timeContext.on(TIME_CONTEXT_EVENTS.boundsChanged, this.boundsChanged);
|
||||
this.timeContext.on(TIME_CONTEXT_EVENTS.timeSystemChanged, this.timeSystemChanged);
|
||||
},
|
||||
stopFollowingDataTimeContext() {
|
||||
if (this.timeContext) {
|
||||
this.timeContext.off(TIME_CONTEXT_EVENTS.boundsChanged, this.boundsChanged);
|
||||
this.timeContext.off(TIME_CONTEXT_EVENTS.timeSystemChanged, this.timeSystemChanged);
|
||||
}
|
||||
},
|
||||
formatEventUrl(datum) {
|
||||
if (!datum) {
|
||||
return;
|
||||
}
|
||||
|
||||
return this.eventFormatter.format(datum);
|
||||
},
|
||||
formatEventThumbnailUrl(datum) {
|
||||
if (!datum || !this.eventThumbnailFormatter) {
|
||||
return;
|
||||
}
|
||||
|
||||
return this.eventThumbnailFormatter.format(datum);
|
||||
},
|
||||
formatTime(datum) {
|
||||
if (!datum) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dateTimeStr = this.timeFormatter.format(datum);
|
||||
|
||||
// Replace ISO "T" with a space to allow wrapping
|
||||
return dateTimeStr.replace('T', ' ');
|
||||
},
|
||||
getEventDownloadName(datum) {
|
||||
let eventDownloadName = '';
|
||||
if (datum) {
|
||||
const key = this.eventDownloadNameMetadataValue.key;
|
||||
eventDownloadName = datum[key];
|
||||
}
|
||||
|
||||
return eventDownloadName;
|
||||
},
|
||||
parseTime(datum) {
|
||||
if (!datum) {
|
||||
return;
|
||||
}
|
||||
|
||||
return this.timeFormatter.parse(datum);
|
||||
},
|
||||
loadTelemetry() {
|
||||
this.telemetryCollection = this.openmct.telemetry.requestCollection(this.domainObject, {
|
||||
timeContext: this.timeContext
|
||||
});
|
||||
this.telemetryCollection.on('add', this.dataAdded);
|
||||
this.telemetryCollection.on('remove', this.dataRemoved);
|
||||
this.telemetryCollection.on('clear', this.dataCleared);
|
||||
this.telemetryCollection.load();
|
||||
},
|
||||
boundsChanged(bounds, isTick) {
|
||||
if (isTick) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.bounds = bounds; // setting bounds for EventryView watcher
|
||||
},
|
||||
timeSystemChanged() {
|
||||
this.timeSystem = this.timeContext.getTimeSystem();
|
||||
this.timeKey = this.timeSystem.key;
|
||||
this.timeFormatter = this.getFormatter(this.timeKey);
|
||||
this.durationFormatter = this.getFormatter(
|
||||
this.timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER
|
||||
);
|
||||
},
|
||||
normalizeDatum(datum) {
|
||||
const formattedTime = this.formatTime(datum);
|
||||
const time = this.parseTime(formattedTime);
|
||||
|
||||
return {
|
||||
...datum,
|
||||
formattedTime,
|
||||
time
|
||||
};
|
||||
},
|
||||
getFormatter(key) {
|
||||
const metadataValue = this.metadata.value(key) || { format: key };
|
||||
const valueFormatter = this.openmct.telemetry.getValueFormatter(metadataValue);
|
||||
|
||||
return valueFormatter;
|
||||
}
|
||||
}
|
||||
};
|
@ -145,6 +145,7 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
console.debug('🗺️ Calculated clientWidth:', clientWidth);
|
||||
return clientWidth;
|
||||
},
|
||||
updateViewBounds(bounds, isTick) {
|
||||
@ -345,6 +346,9 @@ export default {
|
||||
let imageWrapper = this.createImageWrapper(index, item, showImagePlaceholders);
|
||||
containerElement.appendChild(imageWrapper);
|
||||
}
|
||||
|
||||
console.debug('🪝 Event time:', new Date(item.time).toISOString());
|
||||
console.debug('🪝 Scaled X position:', this.xScale(item.time));
|
||||
},
|
||||
setImageDisplay(imageElement, showImagePlaceholders) {
|
||||
if (showImagePlaceholders) {
|
||||
|
@ -11,6 +11,7 @@
|
||||
@import '../plugins/displayLayout/components/layout-frame.scss';
|
||||
@import '../plugins/displayLayout/components/telemetry-view.scss';
|
||||
@import '../plugins/displayLayout/components/text-view.scss';
|
||||
@import '../plugins/events/components/events-view.scss';
|
||||
@import '../plugins/filters/components/filters-view.scss';
|
||||
@import '../plugins/filters/components/global-filters.scss';
|
||||
@import '../plugins/flexibleLayout/components/flexible-layout.scss';
|
||||
|
Loading…
x
Reference in New Issue
Block a user