diff --git a/client/src/components/LayerManager.vue b/client/src/components/LayerManager.vue index 4953dff11..c39c629c1 100644 --- a/client/src/components/LayerManager.vue +++ b/client/src/components/LayerManager.vue @@ -107,7 +107,7 @@ export default defineComponent({ selected: selectedTrackIdRef, stateStyling, }; - const toolTipWidget = uiLayer.addDOMWidget('customToolTip', ToolTipWidget, toolTipWidgetProps); + uiLayer.addDOMWidget('customToolTip', ToolTipWidget, toolTipWidgetProps, { x: 10, y: 10 }); function updateLayers( frame: number, @@ -121,9 +121,12 @@ export default defineComponent({ const currentFrameIds: TrackId[] = intervalTree .search([frame, frame]) .map((str: string) => parseInt(str, 10)); - - rectAnnotationLayer.setHoverAnnotations(visibleModes.includes('tooltip')); - polyAnnotationLayer.setHoverAnnotations(visibleModes.includes('tooltip')); + const inlcudesTooltip = visibleModes.includes('tooltip'); + rectAnnotationLayer.setHoverAnnotations(inlcudesTooltip); + polyAnnotationLayer.setHoverAnnotations(inlcudesTooltip); + if (!inlcudesTooltip) { + hoverOvered.value = []; + } const frameData = [] as FrameDataTrack[]; const editingTracks = [] as FrameDataTrack[]; currentFrameIds.forEach( @@ -316,14 +319,35 @@ export default defineComponent({ }); editAnnotationLayer.bus.$on('update:selectedIndex', (index: number, _type: EditAnnotationTypes, key = '') => handler.selectFeatureHandle(index, key)); - const annotationHoverTooltip = (found: { trackType: [string, number]; trackId: number}[], - pos: {x: number; y: number}) => { - hoverOvered.value = found.map((item) => ({ - type: item.trackType[0], - confidence: item.trackType[1], - trackId: item.trackId, - })); - toolTipWidget.position(pos); + const annotationHoverTooltip = ( + found: { + trackType: [string, number]; + trackId: number; + polygon: { coordinates: Array>}; + }[], + ) => { + const hoveredVals: (ToolTipWidgetData & { maxX: number})[] = []; + found.forEach((item) => { + // get Max of X and Min of y for ordering + if (item.polygon.coordinates.length) { + let maxX = -Infinity; + let minY = Infinity; + item.polygon.coordinates[0].forEach((coord) => { + if (coord.length === 2) { + maxX = Math.max(coord[0], maxX); + minY = Math.min(coord[1], minY); + } + }); + hoveredVals.push({ + type: item.trackType[0], + confidence: item.trackType[1], + trackId: item.trackId, + maxX, + }); + } + }); + hoverOvered.value = hoveredVals.sort((a, b) => a.maxX - b.maxX); + uiLayer.setToolTipWidget('customToolTip', (hoverOvered.value.length > 0)); }; rectAnnotationLayer.bus.$on('annotation-hover', annotationHoverTooltip); polyAnnotationLayer.bus.$on('annotation-hover', annotationHoverTooltip); diff --git a/client/src/layers/UILayers/ToolTipWidget.vue b/client/src/layers/UILayers/ToolTipWidget.vue index ce8744113..736053f88 100644 --- a/client/src/layers/UILayers/ToolTipWidget.vue +++ b/client/src/layers/UILayers/ToolTipWidget.vue @@ -53,7 +53,6 @@ export default defineComponent({
diff --git a/client/src/layers/UILayers/UILayer.ts b/client/src/layers/UILayers/UILayer.ts index ca52f229d..960ab3083 100644 --- a/client/src/layers/UILayers/UILayer.ts +++ b/client/src/layers/UILayers/UILayer.ts @@ -1,16 +1,23 @@ import { createApp } from '@vue/composition-api'; +import geo, { GeoEvent } from 'geojs'; import { MediaController } from '../../components/annotators/mediaControllerType'; interface WidgetPosition { - x: string | number; - y: string | number; + x: number; + y: number; } + export interface DOMWidget { canvas: () => HTMLElement; isInViewport: () => boolean; - position: (pos: WidgetPosition) => WidgetPosition; + position: (pos: WidgetPosition) => void; + toolTip: boolean; // Tooltip enabled or disabled + toolTipOffset: {x: number; y: number}; // Offset from Cursor Position + lastMousePos: WidgetPosition; //Last Mouse position for zooming adjustments } + + /** * UILayer provides a way to add Reactive VUE DOM Widgets to GeoJS * These widgets are created under their own Vue App and can't rely on parent elements @@ -31,18 +38,64 @@ export default class UILayer { this.annotator = annotator; this.widgets = {}; this.uiLayer = this.annotator.geoViewerRef.value.createLayer('ui'); + this.uiLayer.geoOn(geo.event.mousemove, this.updateToolTipPositions); + this.uiLayer.geoOn(geo.event.zoom, this.zoomToolTipPosition); } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - addDOMWidget(name: string, component: any, props: any, position = { x: 0, y: 0 }) { + updateWidgetToolTipPosition(mousePos: WidgetPosition, widget: DOMWidget) { + const tipOffset = widget.toolTipOffset; + const newOffset = this.uiLayer.map().gcsToDisplay(mousePos); + const finalOffset = this.uiLayer.map().displayToGcs( + { + x: newOffset.x + tipOffset.x, y: newOffset.y + tipOffset.y, + }, + ); + widget.position(finalOffset); + } + + zoomToolTipPosition = () => { + Object.keys(this.widgets).forEach((name) => { + if (this.widgets[name].toolTip) { + const mousePos = this.widgets[name].lastMousePos; + this.updateWidgetToolTipPosition(mousePos, this.widgets[name]); + } + }); + }; + + updateToolTipPositions = (evt: GeoEvent) => { + const mousePos = evt.geo; + Object.keys(this.widgets).forEach((name) => { + if (this.widgets[name].toolTip) { + this.updateWidgetToolTipPosition(mousePos, this.widgets[name]); + this.widgets[name].lastMousePos = mousePos; + } + }); + }; + + + addDOMWidget( + name: string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + component: any, props: any, + position = { x: 0, y: 0 }, + ) { const widget: DOMWidget = this.uiLayer.createWidget('dom', { position }); widget.canvas().setAttribute('id', name); const parent = widget.canvas(); const div = document.createElement('div'); const element = parent.appendChild(div); createApp(component, props).mount(element); + widget.toolTipOffset = position; + widget.toolTip = false; + widget.lastMousePos = position; this.widgets[name] = widget; - return widget; } + + // Toggle named widget toolTip On or Off + setToolTipWidget(name: string, on: boolean) { + if (this.widgets[name]) { + this.widgets[name].toolTip = on; + } + } }