UNPKG

photo-sphere-viewer

Version:

A JavaScript library to display Photo Sphere panoramas

1,018 lines (864 loc) 29.5 kB
import { Vector3 } from 'three'; import { AbstractPlugin, CONSTANTS, DEFAULTS, PSVError, registerButton, utils } from '../..'; import { EVENTS, ID_PANEL_MARKER, ID_PANEL_MARKERS_LIST, MARKER_DATA, MARKER_TOOLTIP_TRIGGER, MARKERS_LIST_TEMPLATE, SVG_NS } from './constants'; import { Marker } from './Marker'; import { MarkersButton } from './MarkersButton'; import { MarkersListButton } from './MarkersListButton'; import './style.scss'; /** * @typedef {Object} PSV.plugins.MarkersPlugin.Options * @property {boolean} [clickEventOnMarker=false] If a `click` event is triggered on the viewer additionally to the `select-marker` event. * @property {string | number} [gotoMarkerSpeed=8rpm] Default animation speed for `gotoMarker` method * @property {PSV.plugins.MarkersPlugin.Properties[]} [markers] */ /** * @typedef {Object} PSV.plugins.MarkersPlugin.SelectMarkerData * @summary Data of the `select-marker` event * @property {boolean} dblclick - if the selection originated from a double click, the simple click is always fired before the double click * @property {boolean} rightclick - if the selection originated from a right click */ // add markers buttons DEFAULTS.lang[MarkersButton.id] = 'Markers'; DEFAULTS.lang[MarkersListButton.id] = 'Markers list'; registerButton(MarkersButton, 'caption:left'); registerButton(MarkersListButton, 'caption:left'); export { EVENTS } from './constants'; /** * @summary Displays various markers on the viewer * @extends PSV.plugins.AbstractPlugin * @memberof PSV.plugins */ export class MarkersPlugin extends AbstractPlugin { static id = 'markers'; static EVENTS = EVENTS; /** * @param {PSV.Viewer} psv * @param {PSV.plugins.MarkersPlugin.Options} [options] */ constructor(psv, options) { super(psv); /** * @summary All registered markers * @member {Object<string, PSV.plugins.MarkersPlugin.Marker>} */ this.markers = {}; /** * @type {Object} * @property {boolean} visible - Visibility of the component * @property {PSV.plugins.MarkersPlugin.Marker} currentMarker - Last selected marker * @property {PSV.plugins.MarkersPlugin.Marker} hoveringMarker - Marker under the cursor * @private */ this.prop = { visible : true, currentMarker : null, hoveringMarker: null, stopObserver : null, }; /** * @type {PSV.plugins.MarkersPlugin.Options} */ this.config = { clickEventOnMarker: false, gotoMarkerSpeed: '8rpm', ...options, }; /** * @member {HTMLElement} * @readonly */ this.container = document.createElement('div'); this.container.className = 'psv-markers'; this.container.style.cursor = this.psv.config.mousemove ? 'move' : 'default'; /** * @member {SVGElement} * @readonly */ this.svgContainer = document.createElementNS(SVG_NS, 'svg'); this.svgContainer.setAttribute('class', 'psv-markers-svg-container'); this.container.appendChild(this.svgContainer); // Markers events via delegation this.container.addEventListener('mouseenter', this, true); this.container.addEventListener('mouseleave', this, true); this.container.addEventListener('mousemove', this, true); this.container.addEventListener('contextmenu', this); } /** * @package */ init() { super.init(); this.psv.container.appendChild(this.container); // Viewer events this.psv.on(CONSTANTS.EVENTS.CLICK, this); this.psv.on(CONSTANTS.EVENTS.DOUBLE_CLICK, this); this.psv.on(CONSTANTS.EVENTS.RENDER, this); this.psv.on(CONSTANTS.EVENTS.CONFIG_CHANGED, this); this.psv.once(CONSTANTS.EVENTS.READY, () => { if (this.config.markers) { this.setMarkers(this.config.markers); delete this.config.markers; } }); } /** * @package */ destroy() { this.clearMarkers(false); this.prop.stopObserver?.(); this.psv.off(CONSTANTS.EVENTS.CLICK, this); this.psv.off(CONSTANTS.EVENTS.DOUBLE_CLICK, this); this.psv.off(CONSTANTS.EVENTS.RENDER, this); this.psv.off(CONSTANTS.EVENTS.CONFIG_CHANGED, this); this.psv.container.removeChild(this.container); delete this.svgContainer; delete this.markers; delete this.container; super.destroy(); } /** * @summary Handles events * @param {Event} e * @private */ handleEvent(e) { /* eslint-disable */ switch (e.type) { // @formatter:off case 'mouseenter': this.__onMouseEnter(e, this.__getTargetMarker(e.target)); break; case 'mouseleave': this.__onMouseLeave(e, this.__getTargetMarker(e.target)); break; case 'mousemove': this.__onMouseMove(e, this.__getTargetMarker(e.target)); break; case 'contextmenu': e.preventDefault(); break; case CONSTANTS.EVENTS.CLICK: this.__onClick(e, e.args[0], false); break; case CONSTANTS.EVENTS.DOUBLE_CLICK: this.__onClick(e, e.args[0], true); break; case CONSTANTS.EVENTS.RENDER: this.renderMarkers(); break; case CONSTANTS.OBJECT_EVENTS.ENTER_OBJECT: this.__onMouseEnter(e.detail.originalEvent, e.detail.data); break; case CONSTANTS.OBJECT_EVENTS.LEAVE_OBJECT: this.__onMouseLeave(e.detail.originalEvent, e.detail.data); break; case CONSTANTS.OBJECT_EVENTS.HOVER_OBJECT: this.__onMouseMove(e.detail.originalEvent, e.detail.data); break; case CONSTANTS.EVENTS.CONFIG_CHANGED: this.container.style.cursor = this.psv.config.mousemove ? 'move' : 'default'; break; // @formatter:on } /* eslint-enable */ } /** * @summary Shows all markers * @fires PSV.plugins.MarkersPlugin.show-markers */ show() { this.prop.visible = true; this.renderMarkers(); this.trigger(EVENTS.SHOW_MARKERS); } /** * @summary Hides all markers * @fires PSV.plugins.MarkersPlugin.hide-markers */ hide() { this.prop.visible = false; this.renderMarkers(); this.trigger(EVENTS.HIDE_MARKERS); } /** * @summary Toggles the visibility of all tooltips */ toggleAllTooltips() { if (this.prop.showAllTooltips) { this.hideAllTooltips(); } else { this.showAllTooltips(); } } /** * @summary Displays all tooltips */ showAllTooltips() { this.prop.showAllTooltips = true; utils.each(this.markers, (marker) => { marker.props.staticTooltip = true; marker.showTooltip(); }); } /** * @summary Hides all tooltips */ hideAllTooltips() { this.prop.showAllTooltips = false; utils.each(this.markers, (marker) => { marker.props.staticTooltip = false; marker.hideTooltip(); }); } /** * @summary Returns the total number of markers * @returns {number} */ getNbMarkers() { return Object.keys(this.markers).length; } /** * @summary Returns all the markers * @return {PSV.plugins.MarkersPlugin.Marker[]} */ getMarkers() { return Object.values(this.markers); } /** * @summary Adds a new marker to viewer * @param {PSV.plugins.MarkersPlugin.Properties} properties * @param {boolean} [render=true] - renders the marker immediately * @returns {PSV.plugins.MarkersPlugin.Marker} * @throws {PSV.PSVError} when the marker's id is missing or already exists */ addMarker(properties, render = true) { if (this.markers[properties.id]) { throw new PSVError(`marker "${properties.id}" already exists`); } const marker = new Marker(properties, this.psv); if (marker.isNormal()) { this.container.appendChild(marker.$el); } else if (marker.isPoly() || marker.isSvg()) { this.svgContainer.appendChild(marker.$el); } else if (marker.is3d()) { this.psv.renderer.scene.add(marker.$el); } this.markers[marker.id] = marker; if (render) { this.renderMarkers(); this.__refreshUi(); this.__checkObjectsObserver(); this.trigger(EVENTS.SET_MARKERS, this.getMarkers()); } return marker; } /** * @summary Returns the internal marker object for a marker id * @param {string} markerId * @returns {PSV.plugins.MarkersPlugin.Marker} * @throws {PSV.PSVError} when the marker cannot be found */ getMarker(markerId) { const id = typeof markerId === 'object' ? markerId.id : markerId; if (!this.markers[id]) { throw new PSVError(`cannot find marker "${id}"`); } return this.markers[id]; } /** * @summary Returns the last marker selected by the user * @returns {PSV.plugins.MarkersPlugin.Marker} */ getCurrentMarker() { return this.prop.currentMarker; } /** * @summary Updates the existing marker with the same id * @description Every property can be changed but you can't change its type (Eg: `image` to `html`). * @param {PSV.plugins.MarkersPlugin.Properties} properties * @param {boolean} [render=true] - renders the marker immediately * @returns {PSV.plugins.MarkersPlugin.Marker} */ updateMarker(properties, render = true) { const marker = this.getMarker(properties.id); marker.update(properties); if (render) { this.renderMarkers(); this.__refreshUi(); if (marker.is3d()) { this.psv.needsUpdate(); } this.trigger(EVENTS.SET_MARKERS, this.getMarkers()); } return marker; } /** * @summary Removes a marker from the viewer * @param {string} markerId * @param {boolean} [render=true] - renders the marker immediately */ removeMarker(markerId, render = true) { const marker = this.getMarker(markerId); if (marker.isNormal()) { this.container.removeChild(marker.$el); } else if (marker.isPoly() || marker.isSvg()) { this.svgContainer.removeChild(marker.$el); } else if (marker.is3d()) { this.psv.renderer.scene.remove(marker.$el); this.psv.needsUpdate(); } if (this.prop.hoveringMarker === marker) { this.prop.hoveringMarker = null; } if (this.prop.currentMarker === marker) { this.prop.currentMarker = null; } marker.hideTooltip(); marker.destroy(); delete this.markers[marker.id]; if (render) { this.__refreshUi(); this.__checkObjectsObserver(); this.trigger(EVENTS.SET_MARKERS, this.getMarkers()); } } /** * @summary Removes multiple markers * @param {string[]} markerIds * @param {boolean} [render=true] - renders the markers immediately */ removeMarkers(markerIds, render = true) { markerIds.forEach(markerId => this.removeMarker(markerId, false)); if (render) { this.__refreshUi(); this.__checkObjectsObserver(); this.trigger(EVENTS.SET_MARKERS, this.getMarkers()); } } /** * @summary Replaces all markers * @param {PSV.plugins.MarkersPlugin.Properties[]} markers * @param {boolean} [render=true] - renders the marker immediately */ setMarkers(markers, render = true) { this.clearMarkers(false); utils.each(markers, marker => this.addMarker(marker, false)); if (render) { this.renderMarkers(); this.__refreshUi(); this.__checkObjectsObserver(); this.trigger(EVENTS.SET_MARKERS, this.getMarkers()); } } /** * @summary Removes all markers * @param {boolean} [render=true] - renders the markers immediately */ clearMarkers(render = true) { utils.each(this.markers, marker => this.removeMarker(marker, false)); if (render) { this.renderMarkers(); this.__refreshUi(); this.__checkObjectsObserver(); this.trigger(EVENTS.SET_MARKERS, this.getMarkers()); } } /** * @summary Rotate the view to face the marker * @param {string} markerId * @param {string|number} [speed] - rotates smoothy, see {@link PSV.Viewer#animate} * @fires PSV.plugins.MarkersPlugin.goto-marker-done * @return {PSV.utils.Animation} A promise that will be resolved when the animation finishes */ gotoMarker(markerId, speed = this.config.gotoMarkerSpeed) { const marker = this.getMarker(markerId); return this.psv.animate({ ...marker.props.position, zoom : marker.config.zoomLvl, speed: speed, }) .then(() => { this.trigger(EVENTS.GOTO_MARKER_DONE, marker); }); } /** * @summary Hides a marker * @param {string} markerId */ hideMarker(markerId) { this.toggleMarker(markerId, false); } /** * @summary Shows a marker * @param {string} markerId */ showMarker(markerId) { this.toggleMarker(markerId, true); } /** * @summary Forces the display of the tooltip * @param {string} markerId */ showMarkerTooltip(markerId) { const marker = this.getMarker(markerId); marker.props.staticTooltip = true; marker.showTooltip(); } /** * @summary Hides the tooltip * @param {string} markerId */ hideMarkerTooltip(markerId) { const marker = this.getMarker(markerId); marker.props.staticTooltip = false; marker.hideTooltip(); } /** * @summary Toggles a marker * @param {string} markerId * @param {boolean} [visible] */ toggleMarker(markerId, visible = null) { const marker = this.getMarker(markerId); marker.visible = visible === null ? !marker.visible : visible; if (marker.is3d()) { this.psv.needsUpdate(); } else { this.renderMarkers(); } } /** * @summary Opens the panel with the content of the marker * @param {string} markerId */ showMarkerPanel(markerId) { const marker = this.getMarker(markerId); if (marker?.config?.content) { this.psv.panel.show({ id : ID_PANEL_MARKER, content: marker.config.content, }); } else { this.psv.panel.hide(ID_PANEL_MARKER); } } /** * @summary Toggles the visibility of the list of markers */ toggleMarkersList() { if (this.psv.panel.prop.contentId === ID_PANEL_MARKERS_LIST) { this.hideMarkersList(); } else { this.showMarkersList(); } } /** * @summary Opens side panel with the list of markers * @fires PSV.plugins.MarkersPlugin.filter:render-markers-list */ showMarkersList() { let markers = []; utils.each(this.markers, (marker) => { if (marker.visible && !marker.config.hideList) { markers.push(marker); } }); markers = this.change(EVENTS.RENDER_MARKERS_LIST, markers); this.psv.panel.show({ id : ID_PANEL_MARKERS_LIST, content : MARKERS_LIST_TEMPLATE(markers, this.psv.config.lang[MarkersButton.id]), noMargin : true, clickHandler: (e) => { const li = e.target ? utils.getClosest(e.target, 'li') : undefined; const markerId = li ? li.dataset[MARKER_DATA] : undefined; if (markerId) { const marker = this.getMarker(markerId); this.trigger(EVENTS.SELECT_MARKER_LIST, marker); this.gotoMarker(marker); this.hideMarkersList(); } }, }); } /** * @summary Closes side panel if it contains the list of markers */ hideMarkersList() { this.psv.panel.hide(ID_PANEL_MARKERS_LIST); } /** * @summary Updates the visibility and the position of all markers */ renderMarkers() { const zoomLevel = this.psv.getZoomLevel(); const viewerPosition = this.psv.getPosition(); utils.each(this.markers, (marker) => { let isVisible = this.prop.visible && marker.visible; let visibilityChanged = false; let position = null; if (isVisible && marker.is3d()) { position = this.__getMarkerPosition(marker); isVisible = this.__isMarkerVisible(marker, position); } else if (isVisible && marker.isPoly()) { const positions = this.__getPolyPositions(marker); isVisible = positions.length > (marker.isPolygon() ? 2 : 1); if (isVisible) { position = this.__getMarkerPosition(marker); const points = positions.map(pos => (pos.x - position.x) + ',' + (pos.y - position.y)).join(' '); marker.$el.setAttributeNS(null, 'points', points); marker.$el.setAttributeNS(null, 'transform', `translate(${position.x} ${position.y})`); } } else if (isVisible) { if (marker.props.dynamicSize) { this.__updateMarkerSize(marker); } position = this.__getMarkerPosition(marker); isVisible = this.__isMarkerVisible(marker, position); if (isVisible) { const scale = marker.getScale(zoomLevel, viewerPosition); if (marker.isSvg()) { // simulate transform-origin relative to SVG element const x = position.x + marker.props.width * marker.props.anchor.x * (1 - scale); const y = position.y + marker.props.height * marker.props.anchor.y * (1 - scale); marker.$el.setAttributeNS(null, 'transform', `translate(${x}, ${y}) scale(${scale}, ${scale})`); } else { marker.$el.style.transform = `translate3D(${position.x}px, ${position.y}px, 0px) scale(${scale}, ${scale})`; } } } visibilityChanged = marker.props.visible !== isVisible; marker.props.visible = isVisible; marker.props.position2D = isVisible ? position : null; if (!marker.is3d()) { utils.toggleClass(marker.$el, 'psv-marker--visible', isVisible); } if (!isVisible) { marker.hideTooltip(); } else if (marker.props.staticTooltip) { marker.showTooltip(); } else if (marker.config.tooltip.trigger === MARKER_TOOLTIP_TRIGGER.click || (marker === this.prop.hoveringMarker && !marker.isPoly())) { marker.refreshTooltip(); } else if (marker !== this.prop.hoveringMarker) { marker.hideTooltip(); } if (visibilityChanged) { this.trigger(EVENTS.MARKER_VISIBILITY, marker, isVisible); } }); } /** * @summary Determines if a point marker is visible<br> * It tests if the point is in the general direction of the camera, then check if it's in the viewport * @param {PSV.plugins.MarkersPlugin.Marker} marker * @param {PSV.Point} position * @returns {boolean} * @private */ __isMarkerVisible(marker, position) { return marker.props.positions3D[0].dot(this.psv.prop.direction) > 0 && position.x + marker.props.width >= 0 && position.x - marker.props.width <= this.psv.prop.size.width && position.y + marker.props.height >= 0 && position.y - marker.props.height <= this.psv.prop.size.height; } /** * @summary Computes the real size of a marker * @description This is done by removing all it's transformations (if any) and making it visible * before querying its bounding rect * @param {PSV.plugins.MarkersPlugin.Marker} marker * @private */ __updateMarkerSize(marker) { marker.$el.classList.add('psv-marker--transparent'); let transform; if (marker.isSvg()) { transform = marker.$el.getAttributeNS(null, 'transform'); marker.$el.removeAttributeNS(null, 'transform'); } else { transform = marker.$el.style.transform; marker.$el.style.transform = ''; } const rect = marker.$el.getBoundingClientRect(); marker.props.width = rect.width; marker.props.height = rect.height; marker.$el.classList.remove('psv-marker--transparent'); if (transform) { if (marker.isSvg()) { marker.$el.setAttributeNS(null, 'transform', transform); } else { marker.$el.style.transform = transform; } } // the size is no longer dynamic once known marker.props.dynamicSize = false; } /** * @summary Computes viewer coordinates of a marker * @param {PSV.plugins.MarkersPlugin.Marker} marker * @returns {PSV.Point} * @private */ __getMarkerPosition(marker) { if (marker.isPoly()) { return this.psv.dataHelper.sphericalCoordsToViewerCoords(marker.props.position); } else { const position = this.psv.dataHelper.vector3ToViewerCoords(marker.props.positions3D[0]); position.x -= marker.props.width * marker.props.anchor.x; position.y -= marker.props.height * marker.props.anchor.y; return position; } } /** * @summary Computes viewer coordinates of each point of a polygon/polyline<br> * It handles points behind the camera by creating intermediary points suitable for the projector * @param {PSV.plugins.MarkersPlugin.Marker} marker * @returns {PSV.Point[]} * @private */ __getPolyPositions(marker) { const nbVectors = marker.props.positions3D.length; // compute if each vector is visible const positions3D = marker.props.positions3D.map((vector) => { return { vector : vector, visible: vector.dot(this.psv.prop.direction) > 0, }; }); // get pairs of visible/invisible vectors for each invisible vector connected to a visible vector const toBeComputed = []; positions3D.forEach((pos, i) => { if (!pos.visible) { const neighbours = [ i === 0 ? positions3D[nbVectors - 1] : positions3D[i - 1], i === nbVectors - 1 ? positions3D[0] : positions3D[i + 1], ]; neighbours.forEach((neighbour) => { if (neighbour.visible) { toBeComputed.push({ visible : neighbour, invisible: pos, index : i, }); } }); } }); // compute intermediary vector for each pair (the loop is reversed for splice to insert at the right place) toBeComputed.reverse().forEach((pair) => { positions3D.splice(pair.index, 0, { vector : this.__getPolyIntermediaryPoint(pair.visible.vector, pair.invisible.vector), visible: true, }); }); // translate vectors to screen pos return positions3D .filter(pos => pos.visible) .map(pos => this.psv.dataHelper.vector3ToViewerCoords(pos.vector)); } /** * Given one point in the same direction of the camera and one point behind the camera, * computes an intermediary point on the great circle delimiting the half sphere visible by the camera. * The point is shifted by .01 rad because the projector cannot handle points exactly on this circle. * TODO : does not work with fisheye view (must not use the great circle) * {@link http://math.stackexchange.com/a/1730410/327208} * @param P1 {external:THREE.Vector3} * @param P2 {external:THREE.Vector3} * @returns {external:THREE.Vector3} * @private */ __getPolyIntermediaryPoint(P1, P2) { const C = this.psv.prop.direction.clone().normalize(); const N = new Vector3().crossVectors(P1, P2).normalize(); const V = new Vector3().crossVectors(N, P1).normalize(); const X = P1.clone().multiplyScalar(-C.dot(V)); const Y = V.clone().multiplyScalar(C.dot(P1)); const H = new Vector3().addVectors(X, Y).normalize(); const a = new Vector3().crossVectors(H, C); return H.applyAxisAngle(a, 0.01).multiplyScalar(CONSTANTS.SPHERE_RADIUS); } /** * @summary Returns the marker associated to an event target * @param {EventTarget} target * @param {boolean} [closest=false] * @returns {PSV.plugins.MarkersPlugin.Marker} * @private */ __getTargetMarker(target, closest = false) { const target2 = closest ? utils.getClosest(target, '.psv-marker') : target; return target2 ? target2[MARKER_DATA] : undefined; } /** * @summary Checks if an event target is in the tooltip * @param {EventTarget} target * @param {PSV.components.Tooltip} tooltip * @returns {boolean} * @private */ __targetOnTooltip(target, tooltip) { return target && tooltip ? utils.hasParent(target, tooltip.container) : false; } /** * @summary Handles mouse enter events, show the tooltip for non polygon markers * @param {MouseEvent} e * @param {PSV.plugins.MarkersPlugin.Marker} [marker] * @fires PSV.plugins.MarkersPlugin.over-marker * @private */ __onMouseEnter(e, marker) { if (marker && !marker.isPoly()) { this.prop.hoveringMarker = marker; this.trigger(EVENTS.OVER_MARKER, marker); if (!marker.props.staticTooltip && marker.config.tooltip.trigger === MARKER_TOOLTIP_TRIGGER.hover) { marker.showTooltip(e); } } } /** * @summary Handles mouse leave events, hide the tooltip * @param {MouseEvent} e * @param {PSV.plugins.MarkersPlugin.Marker} [marker] * @fires PSV.plugins.MarkersPlugin.leave-marker * @private */ __onMouseLeave(e, marker) { // do not hide if we enter the tooltip itself while hovering a polygon if (marker && !(marker.isPoly() && this.__targetOnTooltip(e.relatedTarget, marker.tooltip))) { this.trigger(EVENTS.LEAVE_MARKER, marker); this.prop.hoveringMarker = null; if (!marker.props.staticTooltip && marker.config.tooltip.trigger === MARKER_TOOLTIP_TRIGGER.hover) { marker.hideTooltip(); } } } /** * @summary Handles mouse move events, refreshUi the tooltip for polygon markers * @param {MouseEvent} e * @param {PSV.plugins.MarkersPlugin.Marker} [targetMarker] * @fires PSV.plugins.MarkersPlugin.leave-marker * @fires PSV.plugins.MarkersPlugin.over-marker * @private */ __onMouseMove(e, targetMarker) { let marker; if (targetMarker?.isPoly()) { marker = targetMarker; } // do not hide if we enter the tooltip itself while hovering a polygon else if (this.prop.hoveringMarker && this.__targetOnTooltip(e.target, this.prop.hoveringMarker.tooltip)) { marker = this.prop.hoveringMarker; } if (marker) { if (!this.prop.hoveringMarker) { this.trigger(EVENTS.OVER_MARKER, marker); this.prop.hoveringMarker = marker; } if (!marker.props.staticTooltip) { marker.showTooltip(e); } } else if (this.prop.hoveringMarker?.isPoly()) { this.trigger(EVENTS.LEAVE_MARKER, this.prop.hoveringMarker); if (!this.prop.hoveringMarker.props.staticTooltip) { this.prop.hoveringMarker.hideTooltip(); } this.prop.hoveringMarker = null; } } /** * @summary Handles mouse click events, select the marker and open the panel if necessary * @param {Event} e * @param {Object} data * @param {boolean} dblclick * @fires PSV.plugins.MarkersPlugin.select-marker * @fires PSV.plugins.MarkersPlugin.unselect-marker * @private */ __onClick(e, data, dblclick) { let marker = data.objects.find(o => o.userData[MARKER_DATA])?.userData[MARKER_DATA]; if (!marker) { marker = this.__getTargetMarker(data.target, true); } if (this.prop.currentMarker && this.prop.currentMarker !== marker) { this.trigger(EVENTS.UNSELECT_MARKER, this.prop.currentMarker); this.psv.panel.hide(ID_PANEL_MARKER); if (!this.prop.showAllTooltips && this.prop.currentMarker.config.tooltip.trigger === MARKER_TOOLTIP_TRIGGER.click) { this.hideMarkerTooltip(this.prop.currentMarker); } this.prop.currentMarker = null; } if (marker) { this.prop.currentMarker = marker; this.trigger(EVENTS.SELECT_MARKER, marker, { dblclick : dblclick, rightclick: data.rightclick, }); if (this.config.clickEventOnMarker) { // add the marker to event data data.marker = marker; } else { e.stopPropagation(); } // the marker could have been deleted in an event handler if (this.markers[marker.id]) { if (marker.config.tooltip.trigger === MARKER_TOOLTIP_TRIGGER.click) { if (marker.tooltip) { this.hideMarkerTooltip(marker); } else { this.showMarkerTooltip(marker); } } else { this.showMarkerPanel(marker.id); } } } } /** * @summary Updates the visiblity of the panel and the buttons * @private */ __refreshUi() { const nbMarkers = Object.values(this.markers).filter(m => !m.config.hideList).length; if (nbMarkers === 0) { if (this.psv.panel.isVisible(ID_PANEL_MARKERS_LIST)) { this.psv.panel.hide(); } else if (this.psv.panel.isVisible(ID_PANEL_MARKER)) { this.psv.panel.hide(); } } else { // eslint-disable-next-line no-lonely-if if (this.psv.panel.isVisible(ID_PANEL_MARKERS_LIST)) { this.showMarkersList(); } else if (this.psv.panel.isVisible(ID_PANEL_MARKER)) { this.prop.currentMarker ? this.showMarkerPanel(this.prop.currentMarker) : this.psv.panel.hide(); } } this.psv.navbar.getButton(MarkersButton.id, false)?.toggle(nbMarkers > 0); this.psv.navbar.getButton(MarkersListButton.id, false)?.toggle(nbMarkers > 0); } /** * @summary Adds or remove the objects observer if there are 3D markers * @private */ __checkObjectsObserver() { const has3d = Object.values(this.markers).some(marker => marker.is3d()); if (!has3d && this.prop.stopObserver) { this.prop.stopObserver(); this.prop.stopObserver = null; } else if (has3d && !this.prop.stopObserver) { this.prop.stopObserver = this.psv.observeObjects(MARKER_DATA, this); } } }