Use the tooltips mixin

This commit is contained in:
Shefali
2025-03-05 15:23:53 -08:00
parent 3ad64f08c5
commit aa5fa468b5
3 changed files with 84 additions and 180 deletions

View File

@ -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
*/ */
/** /**

View File

@ -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() {

View File

@ -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);
} }
} }
}; };