From c73c962fc386a2b2ac17d12dd193837d8e9d1e49 Mon Sep 17 00:00:00 2001
From: viktorstrate <viktorstrate@gmail.com>
Date: Sun, 27 Sep 2020 16:20:55 +0200
Subject: [PATCH] Show media with thumbnail on map

---
 ui/src/Pages/PlacesPage/MapClusterMarker.js |  66 +++++++++++
 ui/src/Pages/PlacesPage/PlacesPage.js       | 115 +++++++++-----------
 ui/src/Pages/PlacesPage/image-popup.svg     |  21 ++++
 3 files changed, 140 insertions(+), 62 deletions(-)
 create mode 100644 ui/src/Pages/PlacesPage/MapClusterMarker.js
 create mode 100644 ui/src/Pages/PlacesPage/image-popup.svg

diff --git a/ui/src/Pages/PlacesPage/MapClusterMarker.js b/ui/src/Pages/PlacesPage/MapClusterMarker.js
new file mode 100644
index 0000000..d2ed837
--- /dev/null
+++ b/ui/src/Pages/PlacesPage/MapClusterMarker.js
@@ -0,0 +1,66 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import styled from 'styled-components'
+
+import imagePopupSrc from './image-popup.svg'
+
+const Wrapper = styled.div`
+  width: 56px;
+  height: 68px;
+  position: relative;
+  margin-top: -54px;
+`
+
+const ThumbnailImage = styled.img`
+  position: absolute;
+  width: 48px;
+  height: 48px;
+  top: 4px;
+  left: 4px;
+  border-radius: 2px;
+  object-fit: cover;
+`
+
+const PopupImage = styled.img`
+  width: 100%;
+  height: 100%;
+`
+
+const PointCountCircle = styled.div`
+  position: absolute;
+  top: -10px;
+  right: -10px;
+  width: 24px;
+  height: 24px;
+  background-color: #00b3dc;
+  border-radius: 50%;
+  color: white;
+  text-align: center;
+  padding-top: 2px;
+`
+
+const MapClusterMarker = ({
+  thumbnail: thumbJson,
+  point_count_abbreviated,
+  cluster,
+}) => {
+  const thumbnail = JSON.parse(thumbJson)
+
+  return (
+    <Wrapper>
+      <PopupImage src={imagePopupSrc} />
+      <ThumbnailImage src={thumbnail.url} />
+      {cluster && (
+        <PointCountCircle>{point_count_abbreviated}</PointCountCircle>
+      )}
+    </Wrapper>
+  )
+}
+
+MapClusterMarker.propTypes = {
+  thumbnail: PropTypes.string,
+  cluster: PropTypes.bool,
+  point_count_abbreviated: PropTypes.number,
+}
+
+export default MapClusterMarker
diff --git a/ui/src/Pages/PlacesPage/PlacesPage.js b/ui/src/Pages/PlacesPage/PlacesPage.js
index 5bdfdfe..294e8bc 100644
--- a/ui/src/Pages/PlacesPage/PlacesPage.js
+++ b/ui/src/Pages/PlacesPage/PlacesPage.js
@@ -1,4 +1,6 @@
 import React, { useEffect, useRef, useState } from 'react'
+import ReactDOM from 'react-dom'
+import PropTypes from 'prop-types'
 import { useQuery } from 'react-apollo'
 import gql from 'graphql-tag'
 import styled from 'styled-components'
@@ -6,7 +8,8 @@ import styled from 'styled-components'
 import 'mapbox-gl/dist/mapbox-gl.css'
 
 import Layout from '../../Layout'
-import imagePopup from './image-popup.png'
+
+import MapClusterMarker from './MapClusterMarker'
 
 const MapWrapper = styled.div`
   width: 100%;
@@ -66,74 +69,62 @@ const MapPage = () => {
         type: 'geojson',
         data: mapboxData.myMediaGeoJson,
         cluster: true,
-        clusterMaxZoom: 14, // Max zoom to cluster points on
+        // clusterMaxZoom: 14, // Max zoom to cluster points on
         clusterRadius: 50,
         clusterProperties: {
-          thumbnail_url: [
-            'coalesce',
-            ['get', 'url', ['get', 'thumbnail']],
-            false,
-          ],
+          thumbnail: ['coalesce', ['get', 'thumbnail'], false],
         },
       })
 
-      map.current.loadImage(imagePopup, (error, image) => {
-        console.log(error, image)
-        map.current.addImage('media-popup-bg', image)
-
-        map.current.addLayer({
-          id: 'media-cluster-popup',
-          type: 'symbol',
-          source: 'media',
-          filter: ['has', 'point_count'],
-          layout: {
-            'icon-image': 'media-popup-bg',
-            'icon-size': 0.5,
-            'icon-allow-overlap': true,
-          },
-        })
-
-        map.current.addLayer({
-          id: 'media-cluster-count-bg',
-          type: 'circle',
-          source: 'media',
-          filter: ['has', 'point_count'],
-          paint: {
-            'circle-color': '#11b4da',
-            'circle-radius': 11,
-            'circle-translate': [22, -24],
-          },
-        })
-
-        map.current.addLayer({
-          id: 'media-cluster-count',
-          type: 'symbol',
-          source: 'media',
-          filter: ['has', 'point_count'],
-          layout: {
-            'text-field': '{point_count_abbreviated}',
-            'text-size': 12,
-            'text-allow-overlap': true,
-            'text-offset': [22 / 12, -24 / 12],
-          },
-          paint: {
-            'text-color': '#ffffff',
-          },
-        })
+      // Add dummy layer for features to be queryable
+      map.current.addLayer({
+        id: 'media-points',
+        type: 'circle',
+        source: 'media',
+        filter: ['!', true],
       })
 
-      // map.current.addLayer({
-      //   id: 'media-points',
-      //   type: 'circle',
-      //   source: 'media',
-      //   filter: ['!', ['has', 'point_count']],
-      //   paint: {
-      //     'circle-color': '#11b4da',
-      //     'circle-radius': 4,
-      //     'circle-stroke-width': 1,
-      //     'circle-stroke-color': '#fff',
-      //   },
-      // })
+      map.current.on('move', updateMarkers)
+      map.current.on('moveend', updateMarkers)
+      updateMarkers()
+
+      var markers = {}
+      var markersOnScreen = {}
+
+      function updateMarkers() {
+        var newMarkers = {}
+        var features = map.current.querySourceFeatures('media')
+
+        // for every media on the screen, create an HTML marker for it (if we didn't yet),
+        // and add it to the map if it's not there already
+        for (var i = 0; i < features.length; i++) {
+          var coords = features[i].geometry.coordinates
+          var props = features[i].properties
+          var id = props.cluster ? props.cluster_id : props.media_id
+
+          var marker = markers[id]
+          if (!marker) {
+            var el = createClusterPopupElement(props)
+            marker = markers[id] = new mapboxLibrary.Marker({
+              element: el,
+            }).setLngLat(coords)
+          }
+          newMarkers[id] = marker
+
+          if (!markersOnScreen[id]) marker.addTo(map.current)
+        }
+        // for every marker we've added previously, remove those that are no longer visible
+        for (id in markersOnScreen) {
+          if (!newMarkers[id]) markersOnScreen[id].remove()
+        }
+        markersOnScreen = newMarkers
+      }
+
+      function createClusterPopupElement(props) {
+        const el = document.createElement('div')
+        ReactDOM.render(<MapClusterMarker {...props} />, el)
+        return el
+      }
 
       console.log(map.current)
     })
diff --git a/ui/src/Pages/PlacesPage/image-popup.svg b/ui/src/Pages/PlacesPage/image-popup.svg
new file mode 100644
index 0000000..da1249e
--- /dev/null
+++ b/ui/src/Pages/PlacesPage/image-popup.svg
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="100%" height="100%" viewBox="0 0 56 68" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
+    <g transform="matrix(0.875,0,0,1.0625,0,0)">
+        <g transform="matrix(1,0,0,0.970588,0,0)">
+            <g transform="matrix(0.857143,0,0,1.55496,8,-35.483)">
+                <g style="filter:url(#_Effect1);">
+                    <ellipse cx="28" cy="61.172" rx="8" ry="0.828" style="fill-opacity:0.5;"/>
+                </g>
+            </g>
+            <g transform="matrix(1.14286,0,0,0.969697,0,0)">
+                <path d="M4,54C3.47,54 2.961,53.789 2.586,53.414C2.211,53.039 2,52.53 2,52C2,43.732 2,12.268 2,4C2,3.47 2.211,2.961 2.586,2.586C2.961,2.211 3.47,2 4,2L52,2C52.53,2 53.039,2.211 53.414,2.586C53.789,2.961 54,3.47 54,4L54,52C54,52.53 53.789,53.039 53.414,53.414C53.039,53.789 52.53,54 52,54C48.077,54 39.521,54 36.828,54C36.298,54 35.789,54.211 35.414,54.586C34.161,55.839 31.192,58.808 29.414,60.586C29.039,60.961 28.53,61.172 28,61.172C27.47,61.172 26.961,60.961 26.586,60.586C24.808,58.808 21.839,55.839 20.586,54.586C20.211,54.211 19.702,54 19.172,54C16.479,54 7.923,54 4,54Z" style="fill:white;"/>
+            </g>
+        </g>
+    </g>
+    <defs>
+        <filter id="_Effect1" filterUnits="userSpaceOnUse" x="8" y="54.7306" width="40" height="12.8819">
+            <feGaussianBlur in="SourceGraphic" stdDeviation="1.1983"/>
+        </filter>
+    </defs>
+</svg>
