From 777c143db052765a93ba44a109c459c0bc1a41ac Mon Sep 17 00:00:00 2001
From: esikkala <esko.ikkala@aalto.fi>
Date: Mon, 31 May 2021 10:38:55 +0300
Subject: [PATCH] ClientFS maps: add zoom and center to app state

---
 src/client/actions/index.js                   |  6 ++-
 src/client/components/facet_results/Deck.js   | 23 +++++++---
 .../components/facet_results/LeafletMap.js    | 28 +++++++++---
 .../sampo/FacetedSearchPerspective.js         |  4 --
 .../perspectives/sampo/Perspective1.js        | 43 ++++++++++---------
 .../perspectives/sampo/Perspective2.js        |  4 --
 .../perspectives/sampo/Perspective3.js        | 17 +++-----
 src/client/containers/SemanticPortal.js       |  3 +-
 src/client/reducers/general/helpers.js        | 16 +++++--
 src/client/reducers/general/results.js        |  4 +-
 src/client/reducers/sampo/perspective1.js     | 25 ++++++++++-
 src/client/reducers/sampo/perspective2.js     |  2 +-
 src/client/reducers/sampo/perspective3.js     | 11 ++++-
 src/client/reducers/sampo/places.js           | 10 +----
 .../sparql/sampo/BackendSearchConfig.js       |  6 +++
 15 files changed, 132 insertions(+), 70 deletions(-)

diff --git a/src/client/actions/index.js b/src/client/actions/index.js
index f56bcfc9..f634bee4 100644
--- a/src/client/actions/index.js
+++ b/src/client/actions/index.js
@@ -77,14 +77,16 @@ export const fetchResults = ({
   facetClass,
   uri = null,
   limit = null,
-  optimize = null
+  optimize = null,
+  reason = null
 }) => ({
   type: FETCH_RESULTS,
   resultClass,
   facetClass,
   uri,
   limit,
-  optimize
+  optimize,
+  reason
 })
 export const fetchInstanceAnalysis = ({
   resultClass,
diff --git a/src/client/components/facet_results/Deck.js b/src/client/components/facet_results/Deck.js
index 2996b023..ca5b4c8f 100644
--- a/src/client/components/facet_results/Deck.js
+++ b/src/client/components/facet_results/Deck.js
@@ -53,9 +53,9 @@ const styles = theme => ({
 class Deck extends React.Component {
   state = {
     viewport: {
-      longitude: 10.37,
-      latitude: 22.43,
-      zoom: 2,
+      longitude: this.props.center[1],
+      latitude: this.props.center[0],
+      zoom: this.props.zoom,
       pitch: 0,
       bearing: 0,
       width: 100,
@@ -95,6 +95,16 @@ class Deck extends React.Component {
     // }
   }
 
+  componentStateEqualsReduxState = () => {
+    const { viewport } = this.state
+    const { longitude, latitude, zoom } = viewport
+    return (
+      zoom === this.props.zoom &&
+      longitude === this.props.center[1] &&
+      latitude === this.props.center[0]
+    )
+  }
+
   setDialog = info => {
     this.setState({
       dialog: {
@@ -119,8 +129,11 @@ class Deck extends React.Component {
       }
     })
 
-  handleOnViewportChange = viewport =>
-    this.state.mounted && this.setState({ viewport });
+  handleOnViewportChange = viewport => {
+    if (this.state.mounted) {
+      this.setState({ viewport })
+    }
+  }
 
   renderSpinner () {
     if (this.props.fetching || this.props.fetchingInstanceAnalysisData) {
diff --git a/src/client/components/facet_results/LeafletMap.js b/src/client/components/facet_results/LeafletMap.js
index d65b9299..a19f58b4 100644
--- a/src/client/components/facet_results/LeafletMap.js
+++ b/src/client/components/facet_results/LeafletMap.js
@@ -149,12 +149,18 @@ class LeafletMap extends React.Component {
   }
 
   serverFScomponentDidUpdate = (prevProps, prevState) => {
+    // check if map center or zoom was modified in Redux state
+    if (!this.componentStateEqualsReduxState()) {
+      this.leafletMap.setView(this.props.center, this.props.zoom)
+    }
+
     // check if filters have changed
     if (has(prevProps, 'facetUpdateID') && prevProps.facetUpdateID !== this.props.facetUpdateID) {
       this.props.fetchResults({
         resultClass: this.props.resultClass,
         facetClass: this.props.facetClass,
-        sortBy: null
+        sortBy: null,
+        reason: 'facetUpdate'
       })
     }
 
@@ -359,10 +365,22 @@ class LeafletMap extends React.Component {
   }
 
   updateMapBounds = () => {
-    this.props.updateMapBounds({
-      resultClass: this.props.resultClass,
-      bounds: this.boundsToObject()
-    })
+    if (!this.componentStateEqualsReduxState()) {
+      this.props.updateMapBounds({
+        resultClass: this.props.resultClass,
+        bounds: this.boundsToObject()
+      })
+    }
+  }
+
+  componentStateEqualsReduxState = () => {
+    const currentZoom = this.leafletMap.getZoom()
+    const currentCenter = this.leafletMap.getCenter()
+    return (
+      currentZoom === this.props.zoom &&
+      currentCenter[0] === this.props.center[0] &&
+      currentCenter[1] === this.props.center[1]
+    )
   }
 
   setCustomMapControlVisibility = () => {
diff --git a/src/client/components/perspectives/sampo/FacetedSearchPerspective.js b/src/client/components/perspectives/sampo/FacetedSearchPerspective.js
index ebce6b6d..58059076 100644
--- a/src/client/components/perspectives/sampo/FacetedSearchPerspective.js
+++ b/src/client/components/perspectives/sampo/FacetedSearchPerspective.js
@@ -59,10 +59,6 @@ FacetedSearchPerspective.propTypes = {
   /**
    * Faceted search configs and results of places related to this perspective.
    */
-  placesState: PropTypes.object.isRequired,
-  /**
-   * Facet configs and values.
-   */
   facetState: PropTypes.object.isRequired,
   /**
    * Facet values where facets constrain themselves, used for statistics.
diff --git a/src/client/components/perspectives/sampo/Perspective1.js b/src/client/components/perspectives/sampo/Perspective1.js
index 96b31c65..4441a2c6 100644
--- a/src/client/components/perspectives/sampo/Perspective1.js
+++ b/src/client/components/perspectives/sampo/Perspective1.js
@@ -56,12 +56,12 @@ const Perspective1 = props => {
         path={`${rootUrl}/${perspective.id}/faceted-search/production_places`}
         render={() =>
           <LeafletMap
-            center={[22.43, 10.37]}
-            zoom={2}
+            center={props.perspectiveState.maps.placesMsProduced.center}
+            zoom={props.perspectiveState.maps.placesMsProduced.zoom}
             // center={[60.187, 24.821]}
             // zoom={13}
             // locateUser
-            results={props.placesState.results}
+            results={props.perspectiveState.results}
             leafletMapState={props.leafletMapState}
             pageType='facetResults'
             facetUpdateID={props.facetState.facetUpdateID}
@@ -70,7 +70,7 @@ const Perspective1 = props => {
             resultClass='placesMsProduced'
             facetClass='perspective1'
             mapMode='cluster'
-            instance={props.placesState.instanceTableData}
+            instance={props.perspectiveState.instanceTableData}
             createPopUpContent={createPopUpContentMMM}
             popupMaxHeight={320}
             popupMinWidth={280}
@@ -78,9 +78,10 @@ const Perspective1 = props => {
             fetchGeoJSONLayers={props.fetchGeoJSONLayers}
             clearGeoJSONLayers={props.clearGeoJSONLayers}
             fetchByURI={props.fetchByURI}
-            fetching={props.placesState.fetching}
+            fetching={props.perspectiveState.fetching}
             showInstanceCountInClusters
             updateFacetOption={props.updateFacetOption}
+            updateMapBounds={props.updateMapBounds}
             showError={props.showError}
             showExternalLayers
             layerControlExpanded={layerControlExpanded}
@@ -99,24 +100,27 @@ const Perspective1 = props => {
         path={`${rootUrl}/${perspective.id}/faceted-search/production_places_heatmap`}
         render={() =>
           <Deck
-            results={props.placesState.results}
+            center={props.perspectiveState.maps.placesMsProducedHeatmap.center}
+            zoom={props.perspectiveState.maps.placesMsProducedHeatmap.zoom}
+            results={props.perspectiveState.results}
             facetUpdateID={props.facetState.facetUpdateID}
-            resultClass='placesMsProduced'
+            resultClass='placesMsProducedHeatmap'
             facetClass='perspective1'
             fetchResults={props.fetchResults}
-            fetching={props.placesState.fetching}
+            fetching={props.perspectiveState.fetching}
             layerType='heatmapLayer'
             mapBoxAccessToken={MAPBOX_ACCESS_TOKEN}
             mapBoxStyle={MAPBOX_STYLE}
+            updateMapBounds={props.updateMapBounds}
           />}
       />
       <Route
         path={`${rootUrl}/${perspective.id}/faceted-search/last_known_locations`}
         render={() =>
           <LeafletMap
-            center={[22.43, 10.37]}
-            zoom={2}
-            results={props.placesState.results}
+            center={props.perspectiveState.maps.lastKnownLocations.center}
+            zoom={props.perspectiveState.maps.lastKnownLocations.zoom}
+            results={props.perspectiveState.results}
             leafletMapState={props.leafletMapState}
             pageType='facetResults'
             facetUpdateID={props.facetState.facetUpdateID}
@@ -126,7 +130,7 @@ const Perspective1 = props => {
             facetClass='perspective1'
             mapMode='cluster'
             showMapModeControl={false}
-            instance={props.placesState.instanceTableData}
+            instance={props.perspectiveState.instanceTableData}
             createPopUpContent={createPopUpContentMMM}
             popupMaxHeight={320}
             popupMinWidth={280}
@@ -134,9 +138,10 @@ const Perspective1 = props => {
             fetchGeoJSONLayers={props.fetchGeoJSONLayers}
             clearGeoJSONLayers={props.clearGeoJSONLayers}
             fetchByURI={props.fetchByURI}
-            fetching={props.placesState.fetching}
+            fetching={props.perspectiveState.fetching}
             showInstanceCountInClusters
             updateFacetOption={props.updateFacetOption}
+            updateMapBounds={props.updateMapBounds}
             showError={props.showError}
             showExternalLayers
             layerControlExpanded={layerControlExpanded}
@@ -150,14 +155,14 @@ const Perspective1 = props => {
           <Deck
             results={props.placesState.results}
             facetUpdateID={props.facetState.facetUpdateID}
-            instanceAnalysisData={props.placesState.instanceAnalysisData}
-            instanceAnalysisDataUpdateID={props.placesState.instanceAnalysisDataUpdateID}
+            instanceAnalysisData={props.perspectiveState.instanceAnalysisData}
+            instanceAnalysisDataUpdateID={props.perspectiveState.instanceAnalysisDataUpdateID}
             resultClass='placesMsMigrations'
             facetClass='perspective1'
             fetchResults={props.fetchResults}
             fetchInstanceAnalysis={props.fetchInstanceAnalysis}
-            fetching={props.placesState.fetching}
-            fetchingInstanceAnalysisData={props.placesState.fetchingInstanceAnalysisData}
+            fetching={props.perspectiveState.fetching}
+            fetchingInstanceAnalysisData={props.perspectiveState.fetchingInstanceAnalysisData}
             layerType='arcLayer'
             getArcWidth={d => d.instanceCountScaled}
             fromText={intl.get('deckGlMap.manuscriptMigrations.from')}
@@ -272,10 +277,6 @@ Perspective1.propTypes = {
    * Faceted search configs and results of this perspective.
    */
   perspectiveState: PropTypes.object.isRequired,
-  /**
-    * Faceted search configs and results of places related to this perspective.
-    */
-  placesState: PropTypes.object.isRequired,
   /**
     * Facet configs and values.
     */
diff --git a/src/client/components/perspectives/sampo/Perspective2.js b/src/client/components/perspectives/sampo/Perspective2.js
index 6e04ed19..119eaac1 100644
--- a/src/client/components/perspectives/sampo/Perspective2.js
+++ b/src/client/components/perspectives/sampo/Perspective2.js
@@ -58,10 +58,6 @@ Perspective2.propTypes = {
   /**
    * Faceted search configs and results of places related to this perspective.
    */
-  placesState: PropTypes.object.isRequired,
-  /**
-   * Facet configs and values.
-   */
   facetState: PropTypes.object.isRequired,
   /**
    * Facet values where facets constrain themselves, used for statistics.
diff --git a/src/client/components/perspectives/sampo/Perspective3.js b/src/client/components/perspectives/sampo/Perspective3.js
index 1fd21321..3fa7f134 100644
--- a/src/client/components/perspectives/sampo/Perspective3.js
+++ b/src/client/components/perspectives/sampo/Perspective3.js
@@ -43,10 +43,10 @@ const Perspective3 = props => {
         path={`${rootUrl}/${perspective.id}/faceted-search/map`}
         render={() =>
           <LeafletMap
-            center={[22.43, 10.37]}
-            zoom={2}
-            results={props.placesState.results}
-            layers={props.leafletMapLayers}
+            center={props.perspectiveState.maps.placesEvents.center}
+            zoom={props.perspectiveState.maps.placesEvents.zoom}
+            results={props.perspectiveState.results}
+            leafletMapState={props.leafletMapState}
             pageType='facetResults'
             facetUpdateID={props.facetState.facetUpdateID}
             facet={props.facetState.facets.place}
@@ -54,7 +54,7 @@ const Perspective3 = props => {
             resultClass='placesEvents'
             facetClass='perspective3'
             mapMode='cluster'
-            instance={props.placesState.instanceTableData}
+            instance={props.perspectiveState.instanceTableData}
             createPopUpContent={createPopUpContentMMM}
             popupMaxHeight={320}
             popupMinWidth={280}
@@ -62,9 +62,10 @@ const Perspective3 = props => {
             fetchGeoJSONLayers={props.fetchGeoJSONLayers}
             clearGeoJSONLayers={props.clearGeoJSONLayers}
             fetchByURI={props.fetchByURI}
-            fetching={props.placesState.fetching}
+            fetching={props.perspectiveState.fetching}
             showInstanceCountInClusters
             updateFacetOption={props.updateFacetOption}
+            updateMapBounds={props.updateMapBounds}
             showError={props.showError}
             showExternalLayers
             layerControlExpanded={layerControlExpanded}
@@ -96,10 +97,6 @@ Perspective3.propTypes = {
   /**
      * Faceted search configs and results of places related to this perspective.
      */
-  placesState: PropTypes.object.isRequired,
-  /**
-     * Facet configs and values.
-     */
   facetState: PropTypes.object.isRequired,
   /**
      * Facet values where facets constrain themselves, used for statistics.
diff --git a/src/client/containers/SemanticPortal.js b/src/client/containers/SemanticPortal.js
index 8c21861d..1ecf54a8 100644
--- a/src/client/containers/SemanticPortal.js
+++ b/src/client/containers/SemanticPortal.js
@@ -385,7 +385,6 @@ const SemanticPortal = props => {
                               <Grid item xs={12} md={9} className={classes.resultsContainer}>
                                 <FacetedSearchPerspective
                                   perspectiveState={props[`${perspective.id}`]}
-                                  placesState={props.places}
                                   facetState={props[`${perspective.id}Facets`]}
                                   facetConstrainSelfState={has(props, `${perspective.id}FacetsConstrainSelf`)
                                     ? props[`${perspective.id}FacetsConstrainSelf`]
@@ -402,6 +401,7 @@ const SemanticPortal = props => {
                                   updatePage={props.updatePage}
                                   updateRowsPerPage={props.updateRowsPerPage}
                                   updateFacetOption={props.updateFacetOption}
+                                  updateMapBounds={props.updateMapBounds}
                                   sortResults={props.sortResults}
                                   showError={props.showError}
                                   routeProps={routeProps}
@@ -462,6 +462,7 @@ const SemanticPortal = props => {
                                     clearGeoJSONLayers={props.clearGeoJSONLayers}
                                     leafletMap={props.leafletMap}
                                     showError={props.showError}
+                                    updateMapBounds={props.updateMapBounds}
                                   />
                                 </Grid>
                               </Grid>
diff --git a/src/client/reducers/general/helpers.js b/src/client/reducers/general/helpers.js
index 56a2a4ae..7dfb9b52 100644
--- a/src/client/reducers/general/helpers.js
+++ b/src/client/reducers/general/helpers.js
@@ -1,12 +1,22 @@
 import { has, isEmpty } from 'lodash'
 import { UPDATE_FACET_VALUES_CONSTRAIN_SELF } from '../../actions'
 
-export const fetchResults = (state, action) => {
+export const fetchResults = (state, action, initialState) => {
+  const { reason } = action
+  let resetMapBounds = false
+  if (
+    reason &&
+    reason === 'facetUpdate' &&
+    initialState.maps
+  ) {
+    resetMapBounds = true
+  }
   return {
     ...state,
     instance: null,
     instanceTableExternalData: null,
-    fetching: true
+    fetching: true,
+    ...(resetMapBounds && { maps: initialState.maps })
   }
 }
 
@@ -203,7 +213,7 @@ export const updateResultCount = (state, action) => {
   }
 }
 
-export const updateResults = (state, action) => {
+export const updateResults = (state, action, initialState) => {
   return {
     ...state,
     results: action.data,
diff --git a/src/client/reducers/general/results.js b/src/client/reducers/general/results.js
index 6b798d23..8bd1f3eb 100644
--- a/src/client/reducers/general/results.js
+++ b/src/client/reducers/general/results.js
@@ -38,12 +38,12 @@ import {
   updateMapBounds
 } from './helpers'
 
-export const handleDataFetchingAction = (state, action) => {
+export const handleDataFetchingAction = (state, action, initialState) => {
   switch (action.type) {
     case FETCH_RESULTS:
     case FETCH_PAGINATED_RESULTS:
     case FETCH_BY_URI:
-      return fetchResults(state, action)
+      return fetchResults(state, action, initialState)
     case FETCH_RESULT_COUNT:
       return fetchResultCount(state)
     case FETCH_INSTANCE_ANALYSIS:
diff --git a/src/client/reducers/sampo/perspective1.js b/src/client/reducers/sampo/perspective1.js
index 60cc633c..bbc3cb18 100644
--- a/src/client/reducers/sampo/perspective1.js
+++ b/src/client/reducers/sampo/perspective1.js
@@ -21,6 +21,24 @@ export const INITIAL_STATE = {
   instanceAnalysisData: null,
   instanceAnalysisDataUpdateID: 0,
   instanceSparqlQuery: null,
+  maps: {
+    placesMsProduced: {
+      center: [22.43, 10.37],
+      zoom: 2
+    },
+    placesMsProducedHeatmap: {
+      center: [22.43, 10.37],
+      zoom: 2
+    },
+    lastKnownLocations: {
+      center: [22.43, 10.37],
+      zoom: 2
+    },
+    placesMsMigrations: {
+      center: [22.43, 10.37],
+      zoom: 2
+    }
+  },
   properties: [
     {
       id: 'uri',
@@ -260,6 +278,11 @@ export const INITIAL_STATE = {
 
 const resultClasses = new Set([
   'perspective1',
+  'placesMsProduced',
+  'placesMsProducedHeatmap',
+  'lastKnownLocations',
+  'placesMsMigrations',
+  'placesMsMigrationsDialog',
   'productionTimespanLineChart',
   'eventLineChart',
   'manuscriptInstancePageNetwork',
@@ -269,7 +292,7 @@ const resultClasses = new Set([
 
 const perspective1 = (state = INITIAL_STATE, action) => {
   if (resultClasses.has(action.resultClass)) {
-    return handleDataFetchingAction(state, action)
+    return handleDataFetchingAction(state, action, INITIAL_STATE)
   } else return state
 }
 
diff --git a/src/client/reducers/sampo/perspective2.js b/src/client/reducers/sampo/perspective2.js
index 34df248a..487dc2c0 100644
--- a/src/client/reducers/sampo/perspective2.js
+++ b/src/client/reducers/sampo/perspective2.js
@@ -112,7 +112,7 @@ const resultClasses = new Set([
 
 const perspective2 = (state = INITIAL_STATE, action) => {
   if (resultClasses.has(action.resultClass)) {
-    return handleDataFetchingAction(state, action)
+    return handleDataFetchingAction(state, action, INITIAL_STATE)
   } else return state
 }
 
diff --git a/src/client/reducers/sampo/perspective3.js b/src/client/reducers/sampo/perspective3.js
index f79c4f06..b0ae522d 100644
--- a/src/client/reducers/sampo/perspective3.js
+++ b/src/client/reducers/sampo/perspective3.js
@@ -20,6 +20,12 @@ export const INITIAL_STATE = {
   instanceAnalysisData: null,
   instanceAnalysisDataUpdateID: 0,
   instanceSparqlQuery: null,
+  maps: {
+    placesEvents: {
+      center: [22.43, 10.37],
+      zoom: 2
+    }
+  },
   properties: [
     {
       id: 'uri',
@@ -123,12 +129,13 @@ export const INITIAL_STATE = {
 }
 
 const resultClasses = new Set([
-  'perspective3'
+  'perspective3',
+  'placesEvents'
 ])
 
 const perspective3 = (state = INITIAL_STATE, action) => {
   if (resultClasses.has(action.resultClass)) {
-    return handleDataFetchingAction(state, action)
+    return handleDataFetchingAction(state, action, INITIAL_STATE)
   } else return state
 }
 
diff --git a/src/client/reducers/sampo/places.js b/src/client/reducers/sampo/places.js
index c6b3e64e..f39668c2 100644
--- a/src/client/reducers/sampo/places.js
+++ b/src/client/reducers/sampo/places.js
@@ -110,15 +110,7 @@ export const INITIAL_STATE = {
 }
 
 const resultClasses = new Set([
-  'places',
-  'placesAll',
-  'placesActors',
-  'placesMsProduced',
-  'lastKnownLocations',
-  'placesMsMigrations',
-  'placesMsMigrationsDialog',
-  'placesEvents',
-  'findsPlaces'
+  'places'
 ])
 
 const places = (state = INITIAL_STATE, action) => {
diff --git a/src/server/sparql/sampo/BackendSearchConfig.js b/src/server/sparql/sampo/BackendSearchConfig.js
index 95665447..43e86292 100644
--- a/src/server/sparql/sampo/BackendSearchConfig.js
+++ b/src/server/sparql/sampo/BackendSearchConfig.js
@@ -131,6 +131,12 @@ export const backendSearchConfig = {
       relatedInstances: manuscriptsProducedAt
     }
   },
+  placesMsProducedHeatmap: {
+    perspectiveID: 'perspective1',
+    q: productionPlacesQuery,
+    filterTarget: 'manuscripts',
+    resultMapper: mapPlaces
+  },
   lastKnownLocations: {
     perspectiveID: 'perspective1',
     q: lastKnownLocationsQuery,
-- 
GitLab