From f024928ea76063d97c1f53ebdede073bad02f202 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Adrian=20Or=C5=82=C3=B3w?= <adrian.orlow@fishbrain.com>
Date: Mon, 4 Mar 2024 16:08:48 +0100
Subject: [PATCH] feat: add partial bio entities events

---
 docs/plugins/events.md                        | 28 +++++++++++++++++++
 .../overlaysLayer/createFeatureFromExtent.ts  |  4 +--
 .../utils/config/pinsLayer/getPinFeature.ts   |  7 +++--
 .../MapViewer/utils/config/useOlMapLayers.ts  |  2 +-
 .../mapFeatureClick/useMapFeatureClick.ts     | 15 ++++++++++
 .../mapSingleClick/onMapSingleClick.ts        |  3 +-
 .../utils/listeners/useOlMapListeners.ts      | 19 ++++++++++++-
 .../pluginsEventBus.constants.ts              |  2 ++
 .../pluginsEventBus/pluginsEventBus.ts        |  4 +++
 .../pluginsEventBus/pluginsEventBus.types.ts  | 16 +++++++++--
 10 files changed, 91 insertions(+), 9 deletions(-)
 create mode 100644 src/components/Map/MapViewer/utils/listeners/mapFeatureClick/useMapFeatureClick.ts

diff --git a/docs/plugins/events.md b/docs/plugins/events.md
index 957b28a2..7ad4cf64 100644
--- a/docs/plugins/events.md
+++ b/docs/plugins/events.md
@@ -201,6 +201,34 @@ To listen for specific events, plugins can use the `addListener` method in `even
 }
 ```
 
+- onPinIconClick - triggered when someone clicks on a pin icon; the element to which the pin is attached is passed as an argument. Example argument:
+
+```javascript
+{
+  "id": 40072,
+}
+```
+
+```javascript
+{
+  "id": "b0a478ad-7e7a-47f5-8130-e96cbeaa0cfe", // marker pin
+}
+```
+
+- onSurfaceOverlayClick - triggered when someone clicks on a overlay surface; the element to which the pin is attached is passed as an argument. Example argument:
+
+```javascript
+{
+  "id": 18,
+}
+```
+
+```javascript
+{
+  "id": "a3a5305f-acfa-47ff-bf77-a26d017c6eb3", // surface marker overlay
+}
+```
+
 - onSubmapOpen - triggered when submap opens; the submap identifier is passed as an argument. Example argument:
 
 ```
diff --git a/src/components/Map/MapViewer/utils/config/overlaysLayer/createFeatureFromExtent.ts b/src/components/Map/MapViewer/utils/config/overlaysLayer/createFeatureFromExtent.ts
index 121bfe2b..f79764b2 100644
--- a/src/components/Map/MapViewer/utils/config/overlaysLayer/createFeatureFromExtent.ts
+++ b/src/components/Map/MapViewer/utils/config/overlaysLayer/createFeatureFromExtent.ts
@@ -1,5 +1,5 @@
-import Polygon, { fromExtent } from 'ol/geom/Polygon';
 import Feature from 'ol/Feature';
+import Polygon, { fromExtent } from 'ol/geom/Polygon';
 
 export const createFeatureFromExtent = ([xMin, yMin, xMax, yMax]: number[]): Feature<Polygon> =>
-  new Feature({ geometry: fromExtent([xMin, yMin, xMax, yMax]) });
+  new Feature({ geometry: fromExtent([xMin, yMin, xMax, yMax]), type: 'surface' });
diff --git a/src/components/Map/MapViewer/utils/config/pinsLayer/getPinFeature.ts b/src/components/Map/MapViewer/utils/config/pinsLayer/getPinFeature.ts
index ff7bfb00..31d33f86 100644
--- a/src/components/Map/MapViewer/utils/config/pinsLayer/getPinFeature.ts
+++ b/src/components/Map/MapViewer/utils/config/pinsLayer/getPinFeature.ts
@@ -15,8 +15,11 @@ export const getPinFeature = (
     y: y + (height || ZERO) / HALF,
   };
 
-  return new Feature({
+  const feature = new Feature({
     geometry: new Point(pointToProjection(point)),
-    name: id,
+    id,
+    type: 'pin',
   });
+
+  return feature;
 };
diff --git a/src/components/Map/MapViewer/utils/config/useOlMapLayers.ts b/src/components/Map/MapViewer/utils/config/useOlMapLayers.ts
index ff40ba91..a42e9677 100644
--- a/src/components/Map/MapViewer/utils/config/useOlMapLayers.ts
+++ b/src/components/Map/MapViewer/utils/config/useOlMapLayers.ts
@@ -22,7 +22,7 @@ export const useOlMapLayers = ({ mapInstance }: UseOlMapLayersInput): MapConfig[
       return;
     }
 
-    mapInstance.setLayers([tileLayer, reactionsLayer, pinsLayer, overlaysLayer]);
+    mapInstance.setLayers([tileLayer, reactionsLayer, overlaysLayer, pinsLayer]);
   }, [reactionsLayer, tileLayer, pinsLayer, mapInstance, overlaysLayer]);
 
   return [tileLayer, pinsLayer, reactionsLayer, overlaysLayer];
diff --git a/src/components/Map/MapViewer/utils/listeners/mapFeatureClick/useMapFeatureClick.ts b/src/components/Map/MapViewer/utils/listeners/mapFeatureClick/useMapFeatureClick.ts
new file mode 100644
index 00000000..2ab3ea05
--- /dev/null
+++ b/src/components/Map/MapViewer/utils/listeners/mapFeatureClick/useMapFeatureClick.ts
@@ -0,0 +1,15 @@
+import { FeatureLike } from 'ol/Feature';
+
+interface UseMapFeatureClickResult {
+  handleFeatureClick(feature: FeatureLike): void;
+}
+
+export const useMapFeatureClick = (): UseMapFeatureClickResult => {
+  const handleFeatureClick = (feature: FeatureLike): void => {
+    console.log(feature.get('type'));
+  };
+
+  return {
+    handleFeatureClick,
+  };
+};
diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/onMapSingleClick.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/onMapSingleClick.ts
index 6940b226..4661b41a 100644
--- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/onMapSingleClick.ts
+++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/onMapSingleClick.ts
@@ -1,6 +1,7 @@
 import { SIZE_OF_EMPTY_ARRAY } from '@/constants/common';
 import { MapSize } from '@/redux/map/map.types';
 import { AppDispatch } from '@/redux/store';
+import { MapInstance } from '@/types/map';
 import { MapBrowserEvent } from 'ol';
 import { getSearchResults } from './getSearchResults';
 import { handleDataReset } from './handleDataReset';
@@ -9,7 +10,7 @@ import { handleSearchResultAction } from './handleSearchResultAction';
 /* prettier-ignore */
 export const onMapSingleClick =
   (mapSize: MapSize, modelId: number, dispatch: AppDispatch) =>
-    async ({ coordinate }: MapBrowserEvent<UIEvent>): Promise<void> => {
+    async ({ coordinate }: Pick<MapBrowserEvent<UIEvent>, 'coordinate'>, mapInstance: MapInstance): Promise<void> => {
       // side-effect below is to prevent complications with data update - old data may conflict with new data
       // so we need to reset all the data before updating
       dispatch(handleDataReset);
diff --git a/src/components/Map/MapViewer/utils/listeners/useOlMapListeners.ts b/src/components/Map/MapViewer/utils/listeners/useOlMapListeners.ts
index 1742d6fa..1c0b9fcf 100644
--- a/src/components/Map/MapViewer/utils/listeners/useOlMapListeners.ts
+++ b/src/components/Map/MapViewer/utils/listeners/useOlMapListeners.ts
@@ -10,6 +10,7 @@ import { Pixel } from 'ol/pixel';
 import { useEffect, useRef } from 'react';
 import { useSelector } from 'react-redux';
 import { useDebouncedCallback } from 'use-debounce';
+import { useMapFeatureClick } from './mapFeatureClick/useMapFeatureClick';
 import { onMapRightClick } from './mapRightClick/onMapRightClick';
 import { onMapSingleClick } from './mapSingleClick/onMapSingleClick';
 import { onMapPositionChange } from './onMapPositionChange';
@@ -23,6 +24,7 @@ export const useOlMapListeners = ({ view, mapInstance }: UseOlMapListenersInput)
   const mapSize = useSelector(mapDataSizeSelector);
   const modelId = useSelector(currentModelIdSelector);
   const mapLastZoomValue = useSelector(mapDataLastZoomValue);
+  const { handleFeatureClick } = useMapFeatureClick();
   const coordinate = useRef<Coordinate>([]);
   const pixel = useRef<Pixel>([]);
   const dispatch = useAppDispatch();
@@ -58,7 +60,22 @@ export const useOlMapListeners = ({ view, mapInstance }: UseOlMapListenersInput)
       return;
     }
 
-    const key = mapInstance.on('singleclick', handleMapSingleClick);
+    const key = mapInstance.on('click', e => {
+      mapInstance.forEachFeatureAtPixel(e.pixel, handleFeatureClick);
+    });
+
+    // eslint-disable-next-line consistent-return
+    return () => unByKey(key);
+  }, [mapInstance, handleFeatureClick]);
+
+  useEffect(() => {
+    if (!mapInstance) {
+      return;
+    }
+
+    const key = mapInstance.on('singleclick', ({ coordinate: ciird }) =>
+      handleMapSingleClick({ coordinate }, mapInstance),
+    );
 
     // eslint-disable-next-line consistent-return
     return () => unByKey(key);
diff --git a/src/services/pluginsManager/pluginsEventBus/pluginsEventBus.constants.ts b/src/services/pluginsManager/pluginsEventBus/pluginsEventBus.constants.ts
index 5fd9ea0a..5a5eb652 100644
--- a/src/services/pluginsManager/pluginsEventBus/pluginsEventBus.constants.ts
+++ b/src/services/pluginsManager/pluginsEventBus/pluginsEventBus.constants.ts
@@ -14,6 +14,8 @@ const PLUGINS_EVENTS = {
     onZoomChanged: 'onZoomChanged',
     onCenterChanged: 'onCenterChanged',
     onBioEntityClick: 'onBioEntityClick',
+    onPinIconClick: 'onPinIconClick',
+    onSurfaceOverlayClick: 'onSurfaceOverlayClick',
   },
   search: {
     onSearch: 'onSearch',
diff --git a/src/services/pluginsManager/pluginsEventBus/pluginsEventBus.ts b/src/services/pluginsManager/pluginsEventBus/pluginsEventBus.ts
index 5227eabb..aa7221ab 100644
--- a/src/services/pluginsManager/pluginsEventBus/pluginsEventBus.ts
+++ b/src/services/pluginsManager/pluginsEventBus/pluginsEventBus.ts
@@ -4,6 +4,8 @@ import { ALLOWED_PLUGINS_EVENTS, LISTENER_NOT_FOUND } from './pluginsEventBus.co
 import type {
   CenteredCoordinates,
   ClickedBioEntity,
+  ClickedPinIcon,
+  ClickedSurfaceOverlay,
   Events,
   EventsData,
   PluginsEventBusType,
@@ -21,6 +23,8 @@ export function dispatchEvent(type: 'onSubmapClose', submapId: number): void;
 export function dispatchEvent(type: 'onZoomChanged', data: ZoomChanged): void;
 export function dispatchEvent(type: 'onCenterChanged', data: CenteredCoordinates): void;
 export function dispatchEvent(type: 'onBioEntityClick', data: ClickedBioEntity): void;
+export function dispatchEvent(type: 'onPinIconClick', data: ClickedPinIcon): void;
+export function dispatchEvent(type: 'onSurfaceOverlayClick', data: ClickedSurfaceOverlay): void;
 export function dispatchEvent(type: 'onSearch', data: SearchData): void;
 export function dispatchEvent(type: Events, data: EventsData): void {
   if (!ALLOWED_PLUGINS_EVENTS.includes(type)) throw new Error(`Invalid event type: ${type}`);
diff --git a/src/services/pluginsManager/pluginsEventBus/pluginsEventBus.types.ts b/src/services/pluginsManager/pluginsEventBus/pluginsEventBus.types.ts
index 7679bb0a..4260f399 100644
--- a/src/services/pluginsManager/pluginsEventBus/pluginsEventBus.types.ts
+++ b/src/services/pluginsManager/pluginsEventBus/pluginsEventBus.types.ts
@@ -6,13 +6,15 @@ export type OverlayEvents =
   | 'onAddDataOverlay'
   | 'onRemoveDataOverlay'
   | 'onShowOverlay'
-  | 'onHideOverlay';
+  | 'onHideOverlay'
+  | 'onSurfaceOverlayClick';
 export type SubmapEvents =
   | 'onSubmapOpen'
   | 'onSubmapClose'
   | 'onZoomChanged'
   | 'onCenterChanged'
-  | 'onBioEntityClick';
+  | 'onBioEntityClick'
+  | 'onPinIconClick';
 export type SearchEvents = 'onSearch';
 
 export type Events = OverlayEvents | BackgroundEvents | SubmapEvents | SearchEvents;
@@ -34,6 +36,14 @@ export type ClickedBioEntity = {
   modelId: number;
 };
 
+export type ClickedPinIcon = {
+  id: number | string;
+};
+
+export type ClickedSurfaceOverlay = {
+  id: number | string;
+};
+
 export type SearchDataBioEntity = {
   type: 'bioEntity';
   searchValues: string[];
@@ -61,6 +71,8 @@ export type EventsData =
   | ZoomChanged
   | CenteredCoordinates
   | ClickedBioEntity
+  | ClickedPinIcon
+  | ClickedSurfaceOverlay
   | SearchData;
 
 export type PluginsEventBusType = {
-- 
GitLab