From e239a89ccc02d0de96968b7217320d2268e51102 Mon Sep 17 00:00:00 2001
From: esikkala <esko.ikkala@aalto.fi>
Date: Thu, 11 Oct 2018 22:59:42 +0300
Subject: [PATCH] Add query for creation place popup

---
 src/client/actions/index.js             | 21 +++++++++
 src/client/components/map/LeafletMap.js | 58 +++++++++++++++++--------
 src/client/containers/MapApp.js         | 14 ++++--
 src/client/epics/index.js               | 15 +++++++
 src/client/index.html                   |  3 ++
 src/client/reducers/options.js          |  2 +-
 src/client/reducers/search.js           | 15 ++++++-
 src/server/index.js                     | 38 +++++++++++-----
 src/server/sparql/Datasets.js           | 28 +++++++++---
 src/server/sparql/Manuscripts.js        |  8 +++-
 10 files changed, 162 insertions(+), 40 deletions(-)

diff --git a/src/client/actions/index.js b/src/client/actions/index.js
index 199b7ab5..f4033a03 100644
--- a/src/client/actions/index.js
+++ b/src/client/actions/index.js
@@ -24,6 +24,11 @@ export const UPDATE_PLACES = 'UPDATE_PLACES';
 export const CLEAR_PLACES = 'CLEAR_PLACES';
 export const FETCH_PLACES_FAILED = 'FETCH_PLACES_FAILED';
 
+export const FETCH_PLACE = 'FETCH_PLACE';
+export const UPDATE_PLACE = 'UPDATE_PLACE';
+export const CLEAR_PLACE = 'CLEAR_PLACE';
+export const FETCH_PLACE_FAILED = 'FETCH_PLACE_FAILED';
+
 export const FETCH_FACET = 'FETCH_FACET';
 export const UPDATE_FACET = 'UPDATE_FACET';
 export const CLEAR_FACET = 'CLEAR_FACET';
@@ -144,6 +149,22 @@ export const fetchPlacesFailed = (error) => ({
   error
 });
 
+export const fetchPlace = (placeId) => ({
+  type: FETCH_PLACE,
+  placeId
+});
+export const updatePlace = ({ place }) => ({
+  type: UPDATE_PLACE,
+  place
+});
+export const clearPlace = () => ({
+  type: CLEAR_PLACES,
+});
+export const fetchPlaceFailed = (error) => ({
+  type: FETCH_PLACES_FAILED,
+  error
+});
+
 // Facet
 export const fetchFacet = (property) => ({
   type: FETCH_FACET,
diff --git a/src/client/components/map/LeafletMap.js b/src/client/components/map/LeafletMap.js
index df977609..1a7074e5 100644
--- a/src/client/components/map/LeafletMap.js
+++ b/src/client/components/map/LeafletMap.js
@@ -104,7 +104,7 @@ class LeafletMap extends React.Component {
 
   }
 
-  componentDidUpdate({ results, mapMode, geoJSONKey, bouncingMarkerKey, openPopupMarkerKey }) {
+  componentDidUpdate({ results, place, mapMode, geoJSONKey, bouncingMarkerKey, openPopupMarkerKey }) {
     if (this.props.bouncingMarker === '' && this.bouncingMarkerObj !== null) {
       this.leafletMap.removeLayer(this.bouncingMarkerObj);
     }
@@ -142,6 +142,17 @@ class LeafletMap extends React.Component {
       }
     }
 
+    if (this.props.place !== place) {
+      this.markers[this.props.place.id.replace('http://ldf.fi/mmm/place/', '')]
+        .bindPopup(this.createPopUpContent(this.props.place), {
+          maxHeight: 300,
+          maxWidth: 350,
+          minWidth: 350,
+        //closeButton: false,
+        })
+        .openPopup();
+    }
+
     // check if geoJSON has updated
     if (this.props.geoJSONKey !== geoJSONKey) {
       this.props.geoJSON.map(obj => {
@@ -158,7 +169,7 @@ class LeafletMap extends React.Component {
     this.markers = {};
     Object.values(results).forEach(value => {
       const marker = this.createMarker(value);
-      this.markers[value.id] = marker;
+      this.markers[value.id.replace('http://ldf.fi/mmm/place/', '')] = marker;
       marker == null ? null : marker.addTo(this.resultMarkerLayer);
     });
   }
@@ -184,10 +195,9 @@ class LeafletMap extends React.Component {
         return new L.DivIcon({ html: '<div><span>' + childCount + '</span></div>', className: 'marker-cluster' + c, iconSize: new L.Point(40, 40) });
       }
     });
-    // const clusterer = L.markerClusterGroup();
-    Object.values(results).forEach(value => {
+    results.forEach(value => {
       const marker = this.createMarker(value);
-      this.markers[value.id] = marker;
+      this.markers[value.id.replace('http://ldf.fi/mmm/place/', '')] = marker;
       marker == null ? null : clusterer.addLayer(marker);
     });
     clusterer.addTo(this.resultMarkerLayer);
@@ -212,30 +222,42 @@ class LeafletMap extends React.Component {
       const marker = L.marker(latLng, {
         icon: icon,
         manuscriptCount: result.manuscriptCount ? result.manuscriptCount : null,
+        id: result.id
       })
-        .bindPopup(this.createPopUpContent(result), { maxHeight: 300, maxWidth: 350, minWidth: 300 });
+        .on('click', this.markerOnClick);
       return marker;
     }
   }
 
+  markerOnClick = (event) => {
+    const placeId = (event.target.options.id.replace('http://ldf.fi/mmm/place/', ''));
+    this.props.fetchPlace(placeId);
+  };
+
   createPopUpContent(result) {
-    let popUpTemplate = `
-      <h3><a target="_blank" rel="noopener noreferrer" href={sdbmLink}>{prefLabel}</a></p></h3>
-      <p>Number of manuscripts created here: {manuscriptCount}</p>
-      `;
+    // console.log(result)
+    let popUpTemplate = `<h3><a target="_blank" rel="noopener noreferrer" href=${result.sdbmLink}>${result.prefLabel}</a></p></h3>`;
     if (has(result, 'source')) {
-      popUpTemplate += '<p>Place authority: <a target="_blank" rel="noopener noreferrer" href={source}>{source}</a></p>';
+      popUpTemplate += `<p>Place authority: <a target="_blank" rel="noopener noreferrer" href=${result.source}>${result.source}</a></p>`;
     }
-    //popUpTemplate += this.createManscriptListing(result.manuscript);
-    return L.Util.template(popUpTemplate, result);
+    popUpTemplate += `<p>Manuscripts created here:</p>`;
+    popUpTemplate += this.createManscriptListing(result.manuscript);
+    return popUpTemplate;
   }
 
   createManscriptListing(manuscripts) {
     let html = '';
-    manuscripts.forEach(msId => {
-      const sdbmLink = msId.replace('http://ldf.fi/mmm/manifestation_singleton/', 'https://sdbm.library.upenn.edu/manuscripts/');
+    if (Array.isArray(manuscripts)) {
+      html += '<ul>';
+      manuscripts.forEach(ms => {
+        let sdbmLink = has(ms, 'manuscriptRecord') ? ms.manuscriptRecord : ms.entry;
+        html += '<li><a target="_blank" rel="noopener noreferrer" href=' + sdbmLink + '>' + sdbmLink + '</a></li>';
+      });
+      html += '</ul>';
+    } else {
+      let sdbmLink = has(manuscripts, 'manuscriptRecord') ? manuscripts.manuscriptRecord : manuscripts.entry;
       html += '<p><a target="_blank" rel="noopener noreferrer" href=' + sdbmLink + '>' + sdbmLink + '</a></p>';
-    });
+    }
     return html;
   }
 
@@ -265,8 +287,10 @@ class LeafletMap extends React.Component {
 
 LeafletMap.propTypes = {
   fetchPlaces: PropTypes.func.isRequired,
+  fetchPlace: PropTypes.func.isRequired,
   fetchManuscripts: PropTypes.func.isRequired,
-  results: PropTypes.array,
+  results: PropTypes.array.isRequired,
+  place: PropTypes.object.isRequired,
   mapMode: PropTypes.string.isRequired,
   geoJSON: PropTypes.array,
   geoJSONKey: PropTypes.number.isRequired,
diff --git a/src/client/containers/MapApp.js b/src/client/containers/MapApp.js
index 9c701fd5..7276e0f6 100644
--- a/src/client/containers/MapApp.js
+++ b/src/client/containers/MapApp.js
@@ -13,7 +13,7 @@ import GMap from '../components/map/GMap';
 import Pie from '../components/Pie';
 import TopBar from '../components/TopBar';
 import CircularProgress from '@material-ui/core/CircularProgress';
-import Typography from '@material-ui/core/Typography';
+//import Typography from '@material-ui/core/Typography';
 import purple from '@material-ui/core/colors/purple';
 
 import {
@@ -28,6 +28,7 @@ import {
   clearSuggestions,
   fetchManuscripts,
   fetchPlaces,
+  fetchPlace,
   fetchFacet,
   fetchResults,
   clearManuscripts,
@@ -144,12 +145,13 @@ const styles = theme => ({
 });
 
 let MapApp = (props) => {
-  const { classes, options, browser, search, map, manuscripts, creationPlaces, facet } = props;
+  const { classes, options, browser, search, map, manuscripts, creationPlaces, place, facet } = props;
   //error,
 
   let oneColumnView = true;
 
   //console.log(manuscripts)
+  //console.log(place)
 
   let table = '';
   if (search.fetchingManuscripts) {
@@ -196,8 +198,10 @@ let MapApp = (props) => {
       mapElement = (
         <LeafletMap
           fetchPlaces={props.fetchPlaces}
+          fetchPlace={props.fetchPlace}
           fetchManuscripts={props.fetchManuscripts}
           results={creationPlaces}
+          place={place}
           mapMode={options.mapMode}
           geoJSON={map.geoJSON}
           geoJSONKey={map.geoJSONKey}
@@ -294,6 +298,7 @@ const mapStateToProps = (state) => {
     manuscripts: getVisibleResults(state.search),
     manuscriptsPropertyValues: getVisibleValues(state.search),
     creationPlaces: state.search.places,
+    place: state.search.place,
     facet: state.facet,
     results: state.search.results,
     page: state.search.page
@@ -307,6 +312,7 @@ const mapDispatchToProps = ({
   clearSuggestions,
   fetchManuscripts,
   fetchPlaces,
+  fetchPlace,
   fetchResults,
   fetchFacet,
   clearManuscripts,
@@ -332,7 +338,8 @@ MapApp.propTypes = {
   search: PropTypes.object.isRequired,
   map: PropTypes.object.isRequired,
   manuscripts: PropTypes.array,
-  creationPlaces: PropTypes.array,
+  creationPlaces: PropTypes.array.isRequired,
+  place: PropTypes.object.isRequired,
   manuscriptsPropertyValues: PropTypes.object.isRequired,
   facet: PropTypes.object.isRequired,
   results: PropTypes.number.isRequired,
@@ -344,6 +351,7 @@ MapApp.propTypes = {
   clearSuggestions: PropTypes.func.isRequired,
   fetchManuscripts: PropTypes.func.isRequired,
   fetchPlaces: PropTypes.func.isRequired,
+  fetchPlace:  PropTypes.func.isRequired,
   fetchFacet: PropTypes.func.isRequired,
   fetchResults: PropTypes.func.isRequired,
   clearManuscripts: PropTypes.func.isRequired,
diff --git a/src/client/epics/index.js b/src/client/epics/index.js
index ffeb5e6b..81be3f98 100644
--- a/src/client/epics/index.js
+++ b/src/client/epics/index.js
@@ -5,6 +5,7 @@ import { combineEpics, ofType } from 'redux-observable';
 import {
   updateManuscripts,
   updatePlaces,
+  updatePlace,
   updateFacet,
   updateResults,
   //updateGeoJSON,
@@ -13,6 +14,7 @@ import {
   FETCH_MANUSCRIPTS,
   //FETCH_MANUSCRIPTS_FAILED,
   FETCH_PLACES,
+  FETCH_PLACE,
   FETCH_RESULTS
   //FETCH_PLACES_FAILED,
   //GET_GEOJSON,
@@ -45,6 +47,18 @@ const getPlaces = action$ => action$.pipe(
   })
 );
 
+const getPlace = action$ => action$.pipe(
+  ofType(FETCH_PLACE),
+  mergeMap(action => {
+    console.log(action.placeId)
+    const searchUrl = hiplaApiUrl + 'places';
+    const requestUrl = `${searchUrl}/${action.placeId}`;
+    return ajax.getJSON(requestUrl).pipe(
+      map(response => updatePlace({ place: response }))
+    );
+  })
+);
+
 const getFacet = action$ => action$.pipe(
   ofType(FETCH_FACET),
   mergeMap((action) => {
@@ -92,6 +106,7 @@ const getResultCount = action$ => action$.pipe(
 const rootEpic = combineEpics(
   getManuscripts,
   getPlaces,
+  getPlace,
   getFacet,
   getResultCount,
   // getGeoJSONEpic
diff --git a/src/client/index.html b/src/client/index.html
index 29b15282..bea0df84 100644
--- a/src/client/index.html
+++ b/src/client/index.html
@@ -29,6 +29,9 @@
     .rstcustom__expandButton {
       margin-top: 1px
     }
+    .leaflet-popup-scrolled {
+      border: 0px;
+    }
     </style>
     <title><%= htmlWebpackPlugin.options.title %></title>
   </head>
diff --git a/src/client/reducers/options.js b/src/client/reducers/options.js
index 217020ce..7cf65d56 100644
--- a/src/client/reducers/options.js
+++ b/src/client/reducers/options.js
@@ -5,7 +5,7 @@ import {
 } from '../actions';
 
 const DEFAULT_LANGUAGE = 'en';
-const DEFAULT_RESULT_FORMAT = 'table';
+const DEFAULT_RESULT_FORMAT = 'map';
 const DEFAULT_MAP_MODE = 'cluster';
 
 export const INITIAL_STATE = {
diff --git a/src/client/reducers/search.js b/src/client/reducers/search.js
index 3996ce1a..6e2ece6f 100644
--- a/src/client/reducers/search.js
+++ b/src/client/reducers/search.js
@@ -6,13 +6,14 @@ import {
   CLEAR_SUGGESTIONS,
   FETCH_RESULTS,
   UPDATE_RESULTS,
-  CLEAR_RESULTS,
   FETCH_MANUSCRIPTS,
   FETCH_PLACES,
+  FETCH_PLACE,
   UPDATE_MANUSCRIPTS,
   CLEAR_MANUSCRIPTS,
   UPDATE_PLACES,
   CLEAR_PLACES,
+  UPDATE_PLACE,
   UPDATE_RESULTS_FILTER,
   SORT_RESULTS
 } from '../actions';
@@ -41,6 +42,7 @@ export const INITIAL_STATE = {
   manuscripts: [],
   page: 0,
   places: [],
+  place: {},
   manuscriptsFilter: {
     //'author': new Set(),
     //'timespan': new Set(),
@@ -79,6 +81,8 @@ const search = (state = INITIAL_STATE, action) => {
       return { ...state, fetchingManuscripts: true };
     case FETCH_PLACES:
       return { ...state, fetchingPlaces: true };
+    case FETCH_PLACE:
+      return { ...state, fetchingPlaces: true };
     case FETCH_RESULTS:
       return { ...state, fetchResults: true };
     case CLEAR_SUGGESTIONS:
@@ -112,7 +116,12 @@ const search = (state = INITIAL_STATE, action) => {
       return {
         ...state,
         places: action.places,
-        //resultsQuery: state.query,
+        fetchingPlaces: false
+      };
+    case UPDATE_PLACE:
+      return {
+        ...state,
+        place: action.place,
         fetchingPlaces: false
       };
     case CLEAR_MANUSCRIPTS:
@@ -129,6 +138,8 @@ const search = (state = INITIAL_STATE, action) => {
         resultsQuery: '',
         fetchingPlaces: false
       };
+
+
     case UPDATE_RESULTS_FILTER:
       return updateResultsFilter(state, action);
     case SORT_RESULTS:
diff --git a/src/server/index.js b/src/server/index.js
index 67ecba41..8c839489 100644
--- a/src/server/index.js
+++ b/src/server/index.js
@@ -1,8 +1,13 @@
 import express from 'express';
 import bodyParser from 'body-parser';
 import request from 'superagent';
-import _ from 'lodash';
-import { getManuscripts, getManuscriptCount, getPlaces, getFacet } from './sparql/Manuscripts';
+import {
+  getManuscripts,
+  getManuscriptCount,
+  getPlaces,
+  getPlace,
+  getFacet
+} from './sparql/Manuscripts';
 const DEFAULT_PORT = 3001;
 const app = express();
 //const isDevelopment  = app.get('env') !== 'production';
@@ -43,15 +48,26 @@ app.get('/manuscript-count', (req, res) => {
     });
 });
 
-app.get('/places', (req, res) => {
-  return getPlaces().then((data) => {
-    // console.log(data);
-    res.json(data);
-  })
-    .catch((err) => {
-      console.log(err);
-      return res.sendStatus(500);
-    });
+app.get('/places/:placeId?', (req, res) => {
+  if (req.params.placeId) {
+    return getPlace(req.params.placeId).then(data => {
+      // console.log(data)
+      res.json(data[0]);
+    })
+      .catch((err) => {
+        console.log(err);
+        return res.sendStatus(500);
+      });
+  } else {
+    return getPlaces().then((data) => {
+      // console.log(data);
+      res.json(data);
+    })
+      .catch((err) => {
+        console.log(err);
+        return res.sendStatus(500);
+      });
+  }
 });
 
 app.get('/facet', (req, res) => {
diff --git a/src/server/sparql/Datasets.js b/src/server/sparql/Datasets.js
index 6a236e1e..b62125ee 100644
--- a/src/server/sparql/Datasets.js
+++ b/src/server/sparql/Datasets.js
@@ -55,6 +55,8 @@ module.exports = {
         }
         FILTER(BOUND(?id))
         ?id skos:prefLabel ?prefLabel .
+        ?id mmm-schema:entry ?entry .
+        OPTIONAL { ?id mmm-schema:manuscript_record ?manuscriptRecord . }
         OPTIONAL { ?id crm:P45_consists_of ?material . }
         ?expression_creation frbroo:R18_created ?id .
         OPTIONAL {
@@ -78,7 +80,6 @@ module.exports = {
           ?id crm:P128_carries ?expression .
           ?expression crm:P72_has_language ?language .
         }
-        OPTIONAL { ?id mmm-schema:manuscript_record ?manuscriptRecord . }
       }
       `,
     'allQuery': `
@@ -130,7 +131,7 @@ module.exports = {
       ORDER BY (!BOUND(?creationPlace)) ?creationPlace
       <PAGE>
       `,
-    'placeQuery': `
+    'placesQuery': `
       PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
       PREFIX wgs84: <http://www.w3.org/2003/01/geo/wgs84_pos#>
       PREFIX dc: <http://purl.org/dc/elements/1.1/>
@@ -138,7 +139,7 @@ module.exports = {
       PREFIX frbroo: <http://erlangen-crm.org/efrbroo/>
       PREFIX crm: <http://www.cidoc-crm.org/cidoc-crm/>
       PREFIX mmm-schema: <http://ldf.fi/mmm/schema/>
-      SELECT ?id ?prefLabel ?lat ?long ?source ?parent ?sdbmLink
+      SELECT ?id ?lat ?long ?prefLabel
       (COUNT(DISTINCT ?manuscript) as ?manuscriptCount)
       WHERE {
         ?id a mmm-schema:Place .
@@ -148,11 +149,28 @@ module.exports = {
           ?id wgs84:lat ?lat ;
               wgs84:long ?long .
         }
+      }
+      GROUP BY ?id ?lat ?long ?prefLabel
+        `,
+    'placeQuery': `
+      PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
+      PREFIX wgs84: <http://www.w3.org/2003/01/geo/wgs84_pos#>
+      PREFIX dc: <http://purl.org/dc/elements/1.1/>
+      PREFIX owl: <http://www.w3.org/2002/07/owl#>
+      PREFIX frbroo: <http://erlangen-crm.org/efrbroo/>
+      PREFIX crm: <http://www.cidoc-crm.org/cidoc-crm/>
+      PREFIX mmm-schema: <http://ldf.fi/mmm/schema/>
+      SELECT ?id ?prefLabel ?manuscript__id ?manuscript__entry ?manuscript__manuscriptRecord ?sdbmLink ?source ?parent
+      WHERE {
+        BIND (<PLACE_ID> AS ?id)
+        BIND(REPLACE(STR(?id), "http://ldf.fi/mmm/place/", "https://sdbm.library.upenn.edu/places/") AS ?sdbmLink)
+        ?id skos:prefLabel ?prefLabel .
+        ?manuscript__id ^frbroo:R18_created/crm:P7_took_place_at ?id .
+        ?manuscript__id mmm-schema:entry ?manuscript__entry .
+        OPTIONAL { ?manuscript__id mmm-schema:manuscript_record ?manuscript__manuscriptRecord }
         OPTIONAL { ?id owl:sameAs ?source . }
         OPTIONAL { ?id mmm-schema:parent ?parent }
-        BIND(REPLACE(STR(?id), "http://ldf.fi/mmm/place/", "https://sdbm.library.upenn.edu/places/") AS ?sdbmLink)
       }
-      GROUP BY ?id ?prefLabel ?lat ?long ?source ?parent ?sdbmLink
         `,
     'tgn': {
       // Getty LOD documentation:
diff --git a/src/server/sparql/Manuscripts.js b/src/server/sparql/Manuscripts.js
index eeb1a11a..83397b81 100644
--- a/src/server/sparql/Manuscripts.js
+++ b/src/server/sparql/Manuscripts.js
@@ -22,7 +22,13 @@ export const getManuscriptCount = () => {
 };
 
 export const getPlaces = () => {
-  const { endpoint, placeQuery } = datasetConfig['mmm'];
+  const { endpoint, placesQuery } = datasetConfig['mmm'];
+  return sparqlSearchEngine.doSearch(placesQuery, endpoint, makeObjectList);
+};
+
+export const getPlace = (id) => {
+  let { endpoint, placeQuery } = datasetConfig['mmm'];
+  placeQuery = placeQuery.replace('<PLACE_ID>', `<http://ldf.fi/mmm/place/${id}>`);
   return sparqlSearchEngine.doSearch(placeQuery, endpoint, makeObjectList);
 };
 
-- 
GitLab