diff --git a/src/components/FlatmapVuer.vue b/src/components/FlatmapVuer.vue index 9e28f570..534b47b3 100644 --- a/src/components/FlatmapVuer.vue +++ b/src/components/FlatmapVuer.vue @@ -1737,6 +1737,9 @@ export default { provenanceTaxonomy: taxons, alert: featuresAlert }] + if (eventType === 'mouseenter') { + this.lastHoveredFeature = data; + } if (eventType === 'click') { // If multiple paths overlap at the click location, // `data` is an object with numeric keys for each feature (e.g., {0: {...}, 1: {...}, ..., mapUUID: '...'}). @@ -1856,6 +1859,7 @@ export default { // Fallback: remove any existing toolitp on DOM const tooltips = this.$el.querySelectorAll('.flatmap-tooltip-popup'); tooltips.forEach((tooltip) => tooltip.remove()); + this.lastHoveredFeature = null; }, /** * Function to create tooltip for the provided connectivity data. @@ -1899,6 +1903,9 @@ export default { const connectivityData = []; const errorData = []; + // Remove last hovered feature to avoid showing tooltip for the last hovered feature. + this.lastHoveredFeature = null; + // to keep the highlighted path on map if (connectivityInfo && connectivityInfo.featureId) { featuresToHighlight.push(...connectivityInfo.featureId); @@ -2302,6 +2309,7 @@ export default { document.querySelectorAll('.maplibregl-popup').forEach((item) => { item.style.display = 'none' }) + this.lastHoveredFeature = null; }, /** * @public @@ -3106,10 +3114,22 @@ export default { provenanceTaxonomy: feature.taxons, alert: normaliseAlertToStringArray(feature.alert), } + // Show popup for all modes this.checkAndCreatePopups([data], mapclick) + + // Check pathway fetures and set lastHoveredFeature for tooltip content replacement. + const isPathwayFeature = (feature.id.startsWith('ilxtr:') || feature.id.startsWith('ilx:')); + if (isPathwayFeature) { + this.lastHoveredFeature = feature; + this.lastHoveredFeature.mapUUID = this.mapImp.uuid; + } else { + this.lastHoveredFeature = null; + } + + // flatmap-tooltip-popup is used for custom tooltip content replacement. this.mapImp.showPopup(featureId, capitalise(feature.label), { - className: 'custom-popup', + className: isPathwayFeature ? 'flatmap-tooltip-popup' : 'custom-popup', positionAtLastClick: false, preserveSelection: true, }) @@ -3153,6 +3173,62 @@ export default { setConnectionType: function (type) { this.connectionType = type; }, + /** + * MutationObserver callback to intercept tooltip popup DOM elements + * and customize their content before the browser renders them. + */ + _handleTooltipMutation: function (mutations) { + for (const mutation of mutations) { + for (const node of mutation.addedNodes) { + if (node.nodeType === 1) { + // Check if the added node itself is the tooltip popup + if (node.matches && node.matches('.flatmap-tooltip-popup')) { + this._applyCustomTooltipContent(node); + } else if (node.querySelector) { + // Check if it contains a tooltip popup + const popup = node.querySelector('.flatmap-tooltip-popup'); + if (popup) { + this._applyCustomTooltipContent(popup); + } + } + } + } + } + }, + /** + * Replace the default tooltip content with custom content from + * the tooltipContentProvider prop, if available. + */ + _applyCustomTooltipContent: function (popupEl) { + if (!this.tooltipContentProvider || !this.lastHoveredFeature) return; + try { + const featureData = this.lastHoveredFeature; + // Detect multi-feature data format used by click events when multiple + // paths overlap: {0: {...}, 1: {...}, ..., mapUUID: '...'} + const isMultiFeature = featureData[0] && typeof featureData[0] === 'object'; + let customHtml; + if (isMultiFeature) { + const features = []; + const mapUUID = featureData.mapUUID; + for (const [key, value] of Object.entries(featureData)) { + if (key !== 'mapUUID' && value && typeof value === 'object') { + features.push({ ...value, mapUUID }); + } + } + customHtml = this.tooltipContentProvider(features.length > 1 ? features : features[0]); + } else { + customHtml = this.tooltipContentProvider(featureData); + } + if (customHtml) { + const contentEl = popupEl.querySelector('.maplibregl-popup-content'); + if (contentEl) { + contentEl.innerHTML = customHtml; + } + } + } catch (e) { + console.warn('[flatmapvuer] Error in tooltipContentProvider:', e); + } + }, }, props: { /** @@ -3393,6 +3469,17 @@ export default { return [] }, }, + /** + * A function that provides custom tooltip HTML content for path features. + * The function receives the feature data from the mouseenter event (including `id`, `label`, etc.) + * and should return an HTML string to display in the tooltip, or return null/undefined + * to keep the default tooltip. + * This is used by mapintegratedvuer to show 'long-label' from connectivity knowledge. + */ + tooltipContentProvider: { + type: Function, + default: null, + }, }, provide() { return { @@ -3495,6 +3582,8 @@ export default { drawnCreatedEvent: {}, previousEditEvent: {}, previousDeletedEvent: {}, + lastHoveredFeature: null, + tooltipObserver: null, connectionEntry: {}, existDrawnFeatures: [], // Store all exist drawn features doubleClickedFeature: false, @@ -3693,6 +3782,27 @@ export default { this.createFlatmap() } refreshFlatmapKnowledgeCache(); + + // Set up MutationObserver to customize tooltip content when + // tooltipContentProvider prop is provided. + if (this.tooltipContentProvider) { + this.$nextTick(() => { + const container = this.$refs.flatmapContainer || this.$el; + if (container) { + this.tooltipObserver = new MutationObserver(this._handleTooltipMutation.bind(this)); + this.tooltipObserver.observe(container, { + childList: true, + subtree: true, + }); + } + }); + } + }, + beforeUnmount: function () { + if (this.tooltipObserver) { + this.tooltipObserver.disconnect(); + this.tooltipObserver = null; + } }, } @@ -3855,6 +3965,9 @@ export default { :deep(.flatmap-tooltip-popup), :deep(.custom-popup) { + font-family: Asap, sans-serif !important; + font-size: 14px !important; + &.maplibregl-popup-anchor-bottom { .maplibregl-popup-content { margin-bottom: 12px; @@ -4449,4 +4562,23 @@ export default { } } +.flatmap-feature-label { + padding: 6px !important; + font-family: Asap, sans-serif !important; + font-size: 14px !important; + + // provided by tooltipContentProvider from mapintegratedvuer + &.flatmap-connectivity-label { + text-align: left !important; + } +} + +.id-tag { + color: #444; + background: #e0e0e0; + padding: 2px 4px; + border-radius: 4px; + font-size: .75rem !important; + letter-spacing: 1.05px; +} diff --git a/src/components/MultiFlatmapVuer.vue b/src/components/MultiFlatmapVuer.vue index cdfe1450..3e3aa24b 100644 --- a/src/components/MultiFlatmapVuer.vue +++ b/src/components/MultiFlatmapVuer.vue @@ -94,6 +94,7 @@ :showOpenMapButton="showOpenMapButton" :showPathwayFilter="showPathwayFilter" :externalLegends="externalLegends" + :tooltipContentProvider="tooltipContentProvider" /> @@ -893,6 +894,14 @@ export default { return [] }, }, + /** + * A function that provides custom tooltip HTML content for path features. + * Passed through to each FlatmapVuer instance. + */ + tooltipContentProvider: { + type: Function, + default: null, + }, }, data: function () { return { diff --git a/src/services/flatmapLoader.js b/src/services/flatmapLoader.js index be341d53..695000b8 100644 --- a/src/services/flatmapLoader.js +++ b/src/services/flatmapLoader.js @@ -1,6 +1,6 @@ /** * a single source for the flatmap-viewer library import */ -import * as flatmap from 'https://cdn.jsdelivr.net/npm/@abi-software/flatmap-viewer@4.4.3/+esm'; +import * as flatmap from 'https://cdn.jsdelivr.net/npm/@abi-software/flatmap-viewer@4.4.4/+esm'; export default flatmap;