From 0cc537a901e77faf693df19543322ced86555473 Mon Sep 17 00:00:00 2001
From: MarcosPierasNL <pieras.marcos@gmail.com>
Date: Wed, 4 Sep 2024 15:18:45 +0200
Subject: [PATCH] feat(mapvis): export png from mapvis

---
 .../lib/vis/visualizations/mapvis/mapvis.tsx  | 63 ++++++++++++++++---
 1 file changed, 56 insertions(+), 7 deletions(-)

diff --git a/libs/shared/lib/vis/visualizations/mapvis/mapvis.tsx b/libs/shared/lib/vis/visualizations/mapvis/mapvis.tsx
index 6890056ec..c37a2cae6 100644
--- a/libs/shared/lib/vis/visualizations/mapvis/mapvis.tsx
+++ b/libs/shared/lib/vis/visualizations/mapvis/mapvis.tsx
@@ -1,5 +1,5 @@
-import React, { useEffect, useCallback, useState, useRef } from 'react';
-import DeckGL from '@deck.gl/react';
+import React, { useEffect, useCallback, useState, useRef, forwardRef, useImperativeHandle } from 'react';
+import DeckGL, { DeckGLProps, DeckGLRef } from '@deck.gl/react';
 import { CompositeLayer, FlyToInterpolator, WebMercatorViewport } from '@deck.gl/core';
 import { CompositeLayerType, Coordinate, LayerSettingsType, LocationInfo, SearchResultType } from './mapvis.types';
 import { VISComponentType, VisualizationPropTypes } from '../../common';
@@ -30,7 +30,7 @@ const INITIAL_VIEW_STATE = {
 
 const FLY_SPEED = 1000;
 
-export const MapVis = (props: VisualizationPropTypes<MapProps>) => {
+export const MapVis = forwardRef((props: VisualizationPropTypes<MapProps>, refExternal) => {
   const ref = useRef<HTMLDivElement>(null);
   const baseLayer = useRef(createBaseMap());
   const [viewport, setViewport] = useState<Record<string, any>>(INITIAL_VIEW_STATE);
@@ -42,6 +42,50 @@ export const MapVis = (props: VisualizationPropTypes<MapProps>) => {
   const [searchResult, setSearchResult] = useState<SearchResultType | undefined>(undefined);
 
   const coordinateLookup = useCoordinateLookup(props.data.nodes, props.settings.location);
+  const [shouldExport, setShouldExport] = useState<boolean>(false);
+
+  const captureImage = () => {
+    const canvas = ref.current?.querySelector('canvas');
+
+    if (canvas) {
+      canvas.toBlob((blob) => {
+        if (blob) {
+          const link = document.createElement('a');
+          link.download = 'map-visualization.png';
+          link.href = URL.createObjectURL(blob);
+          link.click();
+        } else {
+          console.error('Failed to capture canvas');
+        }
+      }, 'image/png');
+    } else {
+      console.error('Canvas element not found');
+    }
+  };
+
+  const exportImageInternal = () => {
+    requestAnimationFrame(() => {
+      captureImage();
+      props.updateSettings({ export2PNG: false });
+    });
+  };
+  // !FIXME I have to wrap exportImageInternal() with a useEffect otherwise didnt work, by just calling exportImageInternal() from outside
+  useEffect(() => {
+    if (props.settings.export2PNG) {
+      exportImageInternal();
+    }
+  }, [props.settings.export2PNG]);
+
+  useEffect(() => {
+    if (shouldExport) {
+      exportImageInternal();
+      setShouldExport(false);
+    }
+  }, [shouldExport]);
+
+  useImperativeHandle(refExternal, () => ({
+    exportImageInternal: () => setShouldExport(true),
+  }));
 
   const getFittedViewport = useCallback(
     (minLat: number, maxLat: number, minLon: number, maxLon: number) => {
@@ -228,17 +272,22 @@ export const MapVis = (props: VisualizationPropTypes<MapProps>) => {
       <Attribution />
     </div>
   );
-};
+});
+
+const mapRef = React.createRef<{ exportImageInternal: () => void }>();
 
 const MapComponent: VISComponentType<MapProps> = {
   displayName: 'MapVis',
   description: 'Geographical Features',
-  component: MapVis,
+  component: React.forwardRef((props: VisualizationPropTypes<MapProps>, ref) => <MapVis {...props} ref={mapRef} />),
   settingsComponent: MapSettings,
   settings: settings,
   exportImage: () => {
-    alert('Not yet supported');
+    if (mapRef.current) {
+      mapRef.current.exportImageInternal();
+    } else {
+      console.error('Map reference is not set.');
+    }
   },
 };
-
 export default MapComponent;
-- 
GitLab