mirror of
https://github.com/nasa/openmct.git
synced 2025-06-22 17:08:57 +00:00
Use the tooltips mixin
This commit is contained in:
@ -80,6 +80,7 @@ class TooltipAPI {
|
|||||||
* @property {string} tooltipText text to show in the tooltip
|
* @property {string} tooltipText text to show in the tooltip
|
||||||
* @property {TOOLTIP_LOCATIONS} tooltipLocation location to show the tooltip relative to the parentElement
|
* @property {TOOLTIP_LOCATIONS} tooltipLocation location to show the tooltip relative to the parentElement
|
||||||
* @property {HTMLElement} parentElement reference to the DOM node we're adding the tooltip to
|
* @property {HTMLElement} parentElement reference to the DOM node we're adding the tooltip to
|
||||||
|
* @property {Array} cssClasses css classes to use with the tool tip element
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -48,7 +48,7 @@ const tooltipHelpers = {
|
|||||||
.reverse()
|
.reverse()
|
||||||
.join(' / ');
|
.join(' / ');
|
||||||
},
|
},
|
||||||
buildToolTip(tooltipText, tooltipLocation, elementRef) {
|
buildToolTip(tooltipText, tooltipLocation, elementRef, cssClasses) {
|
||||||
if (!tooltipText || tooltipText.length < 1) {
|
if (!tooltipText || tooltipText.length < 1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -59,7 +59,8 @@ const tooltipHelpers = {
|
|||||||
this.tooltip = this.openmct.tooltips.tooltip({
|
this.tooltip = this.openmct.tooltips.tooltip({
|
||||||
toolTipText: tooltipText,
|
toolTipText: tooltipText,
|
||||||
toolTipLocation: tooltipLocation,
|
toolTipLocation: tooltipLocation,
|
||||||
parentElement: parentElement
|
parentElement: parentElement,
|
||||||
|
cssClasses
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
hideToolTip() {
|
hideToolTip() {
|
||||||
|
@ -21,29 +21,47 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div ref="events" class="c-events-tsv js-events-tsv" :style="alignmentStyle" />
|
<div ref="events" class="c-events-tsv js-events-tsv" :style="alignmentStyle">
|
||||||
|
<swim-lane v-if="eventItems.length" :is-nested="true" :hide-label="true">
|
||||||
|
<template v-slot:object>
|
||||||
|
<div ref="eventsContainer" class="c-events-tsv__container">
|
||||||
|
<div
|
||||||
|
v-for="event in eventItems"
|
||||||
|
:id="`wrapper-${event.time}`"
|
||||||
|
:ref="`wrapper-${event.time}`"
|
||||||
|
:key="event.id"
|
||||||
|
:aria-label="titleKey ? `${event[titleKey]}` : ''"
|
||||||
|
class="c-events-tsv__event-line"
|
||||||
|
:class="event.limitClass || ''"
|
||||||
|
:style="`left: ${event.left}px`"
|
||||||
|
@mouseover="showToolTip(event)"
|
||||||
|
@mouseleave="dismissToolTip()"
|
||||||
|
@click.stop="createSelectionForInspector(event)"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</swim-lane>
|
||||||
|
<div v-else class="c-timeline__no-items">No events within timeframe</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { scaleLinear, scaleUtc } from 'd3-scale';
|
import { scaleLinear, scaleUtc } from 'd3-scale';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import mount from 'utils/mount';
|
|
||||||
import { inject } from 'vue';
|
import { inject } from 'vue';
|
||||||
|
|
||||||
import SwimLane from '@/ui/components/swim-lane/SwimLane.vue';
|
import SwimLane from '@/ui/components/swim-lane/SwimLane.vue';
|
||||||
|
|
||||||
import { useAlignment } from '../../../ui/composables/alignmentContext.js';
|
import { useAlignment } from '../../../ui/composables/alignmentContext.js';
|
||||||
import eventData from '../mixins/eventData.js';
|
import eventData from '../mixins/eventData.js';
|
||||||
|
import tooltipHelpers from '../../../api/tooltips/tooltipMixins';
|
||||||
|
|
||||||
const PADDING = 1;
|
const PADDING = 1;
|
||||||
const CONTAINER_CLASS = 'c-events-tsv__container';
|
|
||||||
const NO_ITEMS_CLASS = 'c-timeline__no-items';
|
|
||||||
const EVENT_WRAPPER_CLASS = 'c-events-tsv__event-line';
|
|
||||||
const ID_PREFIX = 'wrapper-';
|
|
||||||
const AXES_PADDING = 20;
|
const AXES_PADDING = 20;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [eventData],
|
components: { SwimLane },
|
||||||
|
mixins: [eventData, tooltipHelpers],
|
||||||
inject: ['openmct', 'domainObject', 'objectPath', 'extendedLinesBus'],
|
inject: ['openmct', 'domainObject', 'objectPath', 'extendedLinesBus'],
|
||||||
setup() {
|
setup() {
|
||||||
const domainObject = inject('domainObject');
|
const domainObject = inject('domainObject');
|
||||||
@ -54,7 +72,9 @@ export default {
|
|||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
eventHistory: []
|
eventItems: [],
|
||||||
|
eventHistory: [],
|
||||||
|
titleKey: null
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -72,7 +92,7 @@ export default {
|
|||||||
watch: {
|
watch: {
|
||||||
eventHistory: {
|
eventHistory: {
|
||||||
handler() {
|
handler() {
|
||||||
this.updatePlotEvents();
|
this.updateEventItems();
|
||||||
},
|
},
|
||||||
deep: true
|
deep: true
|
||||||
},
|
},
|
||||||
@ -191,14 +211,14 @@ export default {
|
|||||||
|
|
||||||
this.setScaleAndPlotEvents(this.timeSystem, !isTick);
|
this.setScaleAndPlotEvents(this.timeSystem, !isTick);
|
||||||
},
|
},
|
||||||
setScaleAndPlotEvents(timeSystem, clearAllEvents) {
|
setScaleAndPlotEvents(timeSystem) {
|
||||||
if (timeSystem) {
|
if (timeSystem) {
|
||||||
this.timeSystem = timeSystem;
|
this.timeSystem = timeSystem;
|
||||||
this.timeFormatter = this.getFormatter(this.timeSystem.key);
|
this.timeFormatter = this.getFormatter(this.timeSystem.key);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setScale(this.timeSystem);
|
this.setScale(this.timeSystem);
|
||||||
this.updatePlotEvents(clearAllEvents);
|
this.updateEventItems();
|
||||||
},
|
},
|
||||||
getFormatter(key) {
|
getFormatter(key) {
|
||||||
const metadata = this.openmct.telemetry.getMetadata(this.domainObject);
|
const metadata = this.openmct.telemetry.getMetadata(this.domainObject);
|
||||||
@ -208,39 +228,20 @@ export default {
|
|||||||
|
|
||||||
return valueFormatter;
|
return valueFormatter;
|
||||||
},
|
},
|
||||||
updatePlotEvents(clearAllEvents) {
|
updateEventItems() {
|
||||||
this.clearPreviousEvents(clearAllEvents);
|
|
||||||
if (this.xScale) {
|
if (this.xScale) {
|
||||||
this.drawEvents();
|
this.eventItems = this.eventHistory.map((eventHistoryItem) => {
|
||||||
}
|
const limitClass = this.getLimitClass(eventHistoryItem);
|
||||||
},
|
return {
|
||||||
clearPreviousEvents(clearAllEvents) {
|
...eventHistoryItem,
|
||||||
//TODO: Only clear items that are out of bounds
|
left: this.xScale(eventHistoryItem.time),
|
||||||
let noItemsEl = this.$el.querySelectorAll(`.${NO_ITEMS_CLASS}`);
|
limitClass
|
||||||
noItemsEl.forEach((item) => {
|
};
|
||||||
item.remove();
|
|
||||||
});
|
|
||||||
const 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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
if (this.extendLines) {
|
if (this.extendLines) {
|
||||||
this.emitExtendedLines();
|
this.emitExtendedLines();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
setDimensions() {
|
setDimensions() {
|
||||||
const eventsHolder = this.$refs.events;
|
const eventsHolder = this.$refs.events;
|
||||||
@ -267,92 +268,6 @@ export default {
|
|||||||
|
|
||||||
this.xScale.range([PADDING, this.width - PADDING * 2]);
|
this.xScale.range([PADDING, this.width - PADDING * 2]);
|
||||||
},
|
},
|
||||||
isEventInBounds(evenObj) {
|
|
||||||
return evenObj.time <= this.viewBounds.end && evenObj.time >= this.viewBounds.start;
|
|
||||||
},
|
|
||||||
getEventsContainer() {
|
|
||||||
let eventContainer;
|
|
||||||
|
|
||||||
let existingContainer = this.$el.querySelector(`.${CONTAINER_CLASS}`);
|
|
||||||
if (existingContainer) {
|
|
||||||
eventContainer = existingContainer;
|
|
||||||
} 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-events-tsv__container"/>
|
|
||||||
</template>
|
|
||||||
</swim-lane>`
|
|
||||||
},
|
|
||||||
{
|
|
||||||
app: this.openmct.app
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
this.destroyEventsContainer = destroy;
|
|
||||||
const component = vNode.componentInstance;
|
|
||||||
this.$refs.events.appendChild(component.$el);
|
|
||||||
|
|
||||||
eventContainer = component.$el.querySelector(`.${CONTAINER_CLASS}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return eventContainer;
|
|
||||||
},
|
|
||||||
drawEvents() {
|
|
||||||
let eventContainer = this.getEventsContainer();
|
|
||||||
if (this.eventHistory.length) {
|
|
||||||
this.eventHistory.forEach((currentEventObject) => {
|
|
||||||
if (this.isEventInBounds(currentEventObject)) {
|
|
||||||
this.plotEvents(currentEventObject, eventContainer);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.plotNoItems(eventContainer);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
plotNoItems(containerElement) {
|
|
||||||
const textElement = document.createElement('div');
|
|
||||||
textElement.classList.add(NO_ITEMS_CLASS);
|
|
||||||
textElement.innerHTML = 'No events within timeframe';
|
|
||||||
|
|
||||||
containerElement.appendChild(textElement);
|
|
||||||
},
|
|
||||||
getEventWrapper(item) {
|
|
||||||
const id = `${ID_PREFIX}${item.time}`;
|
|
||||||
|
|
||||||
return this.$el.querySelector(`.js-events-tsv div[id=${id}]`);
|
|
||||||
},
|
|
||||||
plotEvents(item, containerElement) {
|
|
||||||
const existingEventWrapper = this.getEventWrapper(item);
|
|
||||||
if (existingEventWrapper) {
|
|
||||||
this.updateExistingEventWrapper(existingEventWrapper, item);
|
|
||||||
} else {
|
|
||||||
const eventWrapper = this.createEventWrapper(item);
|
|
||||||
containerElement.appendChild(eventWrapper);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.extendLines) {
|
|
||||||
this.emitExtendedLines();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
updateExistingEventWrapper(existingEventWrapper, event) {
|
|
||||||
existingEventWrapper.style.left = `${this.xScale(event.time)}px`;
|
|
||||||
},
|
|
||||||
createPathSelection(eventWrapper) {
|
createPathSelection(eventWrapper) {
|
||||||
const selection = [];
|
const selection = [];
|
||||||
selection.unshift({
|
selection.unshift({
|
||||||
@ -372,7 +287,19 @@ export default {
|
|||||||
|
|
||||||
return selection;
|
return selection;
|
||||||
},
|
},
|
||||||
createSelectionForInspector(event, eventWrapper) {
|
setSelection() {
|
||||||
|
let childContext = {};
|
||||||
|
childContext.item = this.childObject;
|
||||||
|
this.context = childContext;
|
||||||
|
if (this.removeSelectable) {
|
||||||
|
this.removeSelectable();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.removeSelectable = this.openmct.selection.selectable(this.$el, this.context);
|
||||||
|
},
|
||||||
|
createSelectionForInspector(event) {
|
||||||
|
|
||||||
|
const eventWrapper = this.$refs[`wrapper-${event.time}`][0];
|
||||||
const eventContext = {
|
const eventContext = {
|
||||||
type: 'time-strip-event-selection',
|
type: 'time-strip-event-selection',
|
||||||
event
|
event
|
||||||
@ -405,63 +332,38 @@ export default {
|
|||||||
const limitEvaluation = this.limitEvaluator.evaluate(event, this.valueMetadata);
|
const limitEvaluation = this.limitEvaluator.evaluate(event, this.valueMetadata);
|
||||||
return limitEvaluation?.cssClass;
|
return limitEvaluation?.cssClass;
|
||||||
},
|
},
|
||||||
createEventWrapper(event) {
|
showToolTip(event) {
|
||||||
const id = `${ID_PREFIX}${event.time}`;
|
|
||||||
const eventWrapper = document.createElement('div');
|
|
||||||
eventWrapper.setAttribute('id', id);
|
|
||||||
eventWrapper.classList.add(EVENT_WRAPPER_CLASS);
|
|
||||||
eventWrapper.style.left = `${this.xScale(event.time)}px`;
|
|
||||||
if (this.titleKey) {
|
|
||||||
const textToShow = event[this.titleKey];
|
|
||||||
eventWrapper.ariaLabel = textToShow;
|
|
||||||
eventWrapper.addEventListener('mouseover', () => {
|
|
||||||
this.showToolTip(textToShow, eventWrapper, event);
|
|
||||||
this.extendedLinesBus.updateHoverExtendEventLine(this.keyString, event.time);
|
|
||||||
});
|
|
||||||
eventWrapper.addEventListener('mouseleave', () => {
|
|
||||||
this.tooltip?.destroy();
|
|
||||||
this.extendedLinesBus.updateHoverExtendEventLine(this.keyString, null);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const limitClass = this.getLimitClass(event);
|
|
||||||
if (limitClass) {
|
|
||||||
eventWrapper.classList.add(limitClass);
|
|
||||||
event.limitClass = limitClass;
|
|
||||||
}
|
|
||||||
|
|
||||||
eventWrapper.addEventListener('click', (mouseEvent) => {
|
|
||||||
mouseEvent.stopPropagation();
|
|
||||||
this.createSelectionForInspector(event, eventWrapper);
|
|
||||||
});
|
|
||||||
|
|
||||||
return eventWrapper;
|
|
||||||
},
|
|
||||||
emitExtendedLines() {
|
|
||||||
let lines = [];
|
|
||||||
if (this.extendLines) {
|
|
||||||
lines = this.eventHistory
|
|
||||||
.filter((e) => this.isEventInBounds(e))
|
|
||||||
.map((e) => ({ x: this.xScale(e.time), limitClass: e.limitClass, id: e.time }));
|
|
||||||
}
|
|
||||||
this.extendedLinesBus.updateExtendedLines(this.keyString, lines);
|
|
||||||
},
|
|
||||||
showToolTip(textToShow, referenceElement, event) {
|
|
||||||
const aClasses = ['c-events-tooltip'];
|
const aClasses = ['c-events-tooltip'];
|
||||||
const limitClass = this.getLimitClass(event);
|
if (event.limitClass) {
|
||||||
if (limitClass) {
|
aClasses.push(event.limitClass);
|
||||||
aClasses.push(limitClass);
|
|
||||||
}
|
}
|
||||||
const showToLeft = false; // Temp, stubbed in
|
const showToLeft = false; // Temp, stubbed in
|
||||||
if (showToLeft) {
|
if (showToLeft) {
|
||||||
aClasses.push('--left');
|
aClasses.push('--left');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.tooltip = this.openmct.tooltips.tooltip({
|
this.buildToolTip(
|
||||||
toolTipText: textToShow,
|
this.titleKey ? `${event[this.titleKey]}` : '',
|
||||||
toolTipLocation: this.openmct.tooltips.TOOLTIP_LOCATIONS.RIGHT,
|
this.openmct.tooltips.TOOLTIP_LOCATIONS.RIGHT,
|
||||||
parentElement: referenceElement,
|
`wrapper-${event.time}`,
|
||||||
cssClasses: [aClasses.join(' ')]
|
[aClasses.join(' ')]
|
||||||
});
|
);
|
||||||
|
this.extendedLinesBus.updateHoverExtendEventLine(this.keyString, event.time);
|
||||||
|
},
|
||||||
|
dismissToolTip() {
|
||||||
|
this.hideToolTip();
|
||||||
|
this.extendedLinesBus.updateHoverExtendEventLine(this.keyString, null);
|
||||||
|
},
|
||||||
|
emitExtendedLines() {
|
||||||
|
let lines = [];
|
||||||
|
if (this.extendLines) {
|
||||||
|
lines = this.eventItems.map((e) => ({
|
||||||
|
x: e.left,
|
||||||
|
limitClass: e.limitClass,
|
||||||
|
id: e.time
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
this.extendedLinesBus.updateExtendedLines(this.keyString, lines);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user