Thibault Saunier d3ad6136ef dots-viewer: Refactor JavaScript into modular architecture
Split monolithic embedded JavaScript in overlay.html into dedicated modules:
- tooltip.js: Custom tooltip functionality with interactive copy/paste mode
- pipeline-navigation.js: Clickable pipeline-dot references for navigation
- text-ellipsizer.js: Text ellipsizing with tooltip integration
- svg-overlay-manager.js: Main coordinator orchestrating all functionality

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/9547>
2025-08-19 15:34:44 +00:00

119 lines
3.9 KiB
JavaScript

/**
* Custom tooltip functionality for SVG elements
*
* Provides hoverable tooltips with interactive copy/paste mode for ellipsized text.
* Interactive mode allows users to select and copy tooltip content.
*/
class TooltipManager {
constructor() {
this.$tooltip = null;
this.tooltipText = '';
this.isTooltipInteractive = false;
this.currentTooltipElement = null;
this.init();
}
init() {
// Create custom tooltip element
this.$tooltip = $('<div class="custom-tooltip"></div>').appendTo('body');
// Set up document click handler for hiding tooltips
this.setupDocumentClickHandler();
}
/**
* Shows tooltip with given text at event position
* @param {Element} element - Element that triggered the tooltip
* @param {string} text - Text to display in tooltip
* @param {Event} event - Mouse event for positioning
*/
showTooltip(element, text, event) {
this.tooltipText = text;
this.currentTooltipElement = element;
this.$tooltip.text(text);
this.$tooltip.css({
left: event.pageX + 10 + 'px',
top: event.pageY - 30 + 'px'
}).removeClass('interactive').addClass('show');
this.isTooltipInteractive = false;
}
/**
* Makes tooltip interactive (selectable and fixed position)
*/
makeTooltipInteractive() {
if (!this.isTooltipInteractive && this.currentTooltipElement) {
this.$tooltip.addClass('interactive');
this.isTooltipInteractive = true;
// Position tooltip in a fixed position relative to the element
const elementOffset = $(this.currentTooltipElement).offset();
this.$tooltip.css({
left: Math.min(elementOffset.left + 20, window.innerWidth - 420) + 'px',
top: Math.max(elementOffset.top - 80, 10) + 'px',
'pointer-events': 'auto'
});
// Prevent mouseleave from hiding the tooltip by removing the handler temporarily
$(this.currentTooltipElement).off('mouseleave.tooltip');
// Select all text when made interactive
setTimeout(() => {
const range = document.createRange();
range.selectNodeContents(this.$tooltip[0]);
const selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
}, 10);
}
}
/**
* Hides the tooltip and resets state
*/
hideTooltip() {
this.$tooltip.removeClass('show interactive');
this.isTooltipInteractive = false;
this.currentTooltipElement = null;
}
/**
* Sets up document click handler to hide tooltips when clicking outside
*/
setupDocumentClickHandler() {
$(document).on('click', (e) => {
if (this.isTooltipInteractive) {
// Only hide interactive tooltip when clicking outside both tooltip and original element
if (!$(e.target).closest('.custom-tooltip').length &&
!$(e.target).is('[data-has-tooltip]') &&
e.target !== this.currentTooltipElement) {
this.hideTooltip();
}
} else {
// Hide non-interactive tooltip when clicking anywhere except on elements with tooltips
if (!$(e.target).is('[data-has-tooltip]')) {
this.hideTooltip();
}
}
});
}
/**
* Checks if tooltip is currently in interactive mode
* @returns {boolean} True if tooltip is interactive
*/
isInteractive() {
return this.isTooltipInteractive;
}
}
// Export for module usage
if (typeof module !== 'undefined' && module.exports) {
module.exports = TooltipManager;
} else {
window.TooltipManager = TooltipManager;
}