import React from "react"; import ReactDOM from "react-dom"; import { BeButtonEvent, DecorateContext, Decorator, Marker, IModelApp } from "@bentley/imodeljs-frontend"; import { Point3d, XAndY, XYAndZ } from "@bentley/geometry-core"; // a weak map would probably be better to prevent leaks const reactSetHoverStateMap = new Map<PinMarker, (val: boolean) => void>(); function InMarkerComponent(props: {markerInstance: PinMarker}) { const [isHovered, setIsHovered] = React.useState(false); React.useEffect(() => { reactSetHoverStateMap.set(props.markerInstance, setIsHovered); }, []); React.useEffect(() => { // this is needed especially if you start manipulating "isHovered" from another component, like when someone mouse-overs a UI component IModelApp.viewManager.invalidateDecorationsAllViews(); // if you have multiple views you can do a more specific invalidation }, [isHovered]); return <div> {isHovered ? "hovered!" : "nope :("}</div> } export class PinMarker extends Marker { public constructor(worldLocation: XYAndZ, size: XAndY = {x: 70, y: 20}) { super(worldLocation, size); this.htmlElement = document.createElement("div"); ReactDOM.render(<InMarkerComponent markerInstance={this}/>, this.htmlElement); } public onMouseEnter(ev: BeButtonEvent) { reactSetHoverStateMap.get(this)?.(true); super.onMouseEnter(ev); } public onMouseLeave() { reactSetHoverStateMap.get(this)?.(false); return super.onMouseLeave(); } } export interface PinDecorator extends Decorator { markers: Map<Point3d, PinMarker>; getPins?: () => Point3d[]; } export const pinDecorator: PinDecorator = { markers: new Map(), // when registered, decorate is called by the view manager every requested frame/update decorate(ctx: DecorateContext) { const currentPins = new Set(this.getPins?.()); for (const loc of currentPins) { if (!this.markers.has(loc)) this.markers.set(loc, new PinMarker(loc)); } for (const [loc, marker] of this.markers) { if (!currentPins.has(loc)) this.markers.delete(loc); else marker.addDecoration(ctx); } }, };