diff --git a/src/client/actions/index.js b/src/client/actions/index.js index cf8abb8b436b4c2e3be86a8bffae5db0cd37fbaa..7156dcd066f089aae0b8a2a910ac04b0983a3db8 100644 --- a/src/client/actions/index.js +++ b/src/client/actions/index.js @@ -26,7 +26,9 @@ export const UPDATE_FACET_VALUES = 'UPDATE_FACET_VALUES' export const UPDATE_FACET_VALUES_CONSTRAIN_SELF = 'UPDATE_FACET_VALUES_CONSTRAIN_SELF' export const UPDATE_FACET_OPTION = 'UPDATE_FACET_OPTION' export const UPDATE_CLIENT_SIDE_FILTER = 'UPDATE_CLIENT_SIDE_FILTER' +export const UPDATE_MAP_BOUNDS = 'UPDATE_MAP_BOUNDS' export const FETCH_GEOJSON_LAYERS = 'FETCH_GEOJSON_LAYERS' +export const FETCH_GEOJSON_LAYERS_BACKEND = 'FETCH_GEOJSON_LAYERS_BACKEND' export const UPDATE_GEOJSON_LAYERS = 'UPDATE_GEOJSON_LAYERS' export const OPEN_MARKER_POPUP = 'OPEN_MARKER_POPUP' export const SHOW_ERROR = 'SHOW_ERROR' @@ -36,6 +38,14 @@ export const LOAD_LOCALES = 'LOAD_LOCALES' export const LOAD_LOCALES_FAILED = 'LOAD_LOCALES_FAILED' export const UPDATE_LOCALE = 'UPDATE_LOCALE' export const ANIMATE_MAP = 'ANIMATE_MAP' +export const CLIENT_FS_UPDATE_QUERY = 'CLIENT_FS_UPDATE_QUERY' +export const CLIENT_FS_TOGGLE_DATASET = 'CLIENT_FS_TOGGLE_DATASET' +export const CLIENT_FS_FETCH_RESULTS = 'CLIENT_FS_FETCH_RESULTS' +export const CLIENT_FS_FETCH_RESULTS_FAILED = 'CLIENT_FS_FETCH_RESULTS_FAILED' +export const CLIENT_FS_UPDATE_RESULTS = 'CLIENT_FS_UPDATE_RESULTS' +export const CLIENT_FS_CLEAR_RESULTS = 'CLIENT_FS_CLEAR_RESULTS' +export const CLIENT_FS_UPDATE_FACET = 'CLIENT_FS_UPDATE_FACET' +export const CLIENT_FS_SORT_RESULTS = 'CLIENT_FS_SORT_RESULTS' export const fetchPaginatedResults = (resultClass, facetClass, sortBy) => ({ type: FETCH_PAGINATED_RESULTS, @@ -251,12 +261,57 @@ export const animateMap = value => ({ type: ANIMATE_MAP, value }) +export const updateMapBounds = bounds => ({ + type: UPDATE_MAP_BOUNDS, + bounds +}) export const fetchGeoJSONLayers = ({ layerIDs, bounds }) => ({ type: FETCH_GEOJSON_LAYERS, layerIDs, bounds }) +export const fetchGeoJSONLayersBackend = ({ layerIDs, bounds }) => ({ + type: FETCH_GEOJSON_LAYERS_BACKEND, + layerIDs, + bounds +}) export const updateGeoJSONLayers = ({ payload }) => ({ type: UPDATE_GEOJSON_LAYERS, payload }) +export const clientFSUpdateQuery = query => ({ + type: CLIENT_FS_UPDATE_QUERY, + query +}) +export const clientFSToggleDataset = dataset => ({ + type: CLIENT_FS_TOGGLE_DATASET, + dataset +}) + +export const clientFSFetchResults = ({ jenaIndex, query }) => ({ + type: CLIENT_FS_FETCH_RESULTS, + jenaIndex, + query +}) +export const clientFSFetchResultsFailed = error => ({ + type: CLIENT_FS_FETCH_RESULTS_FAILED, + error +}) +export const clientFSUpdateResults = ({ results, jenaIndex }) => ({ + type: CLIENT_FS_UPDATE_RESULTS, + results, + jenaIndex +}) +export const clientFSClearResults = () => ({ + type: CLIENT_FS_CLEAR_RESULTS +}) +export const clientFSUpdateFacet = ({ facetID, value, latestValues }) => ({ + type: CLIENT_FS_UPDATE_FACET, + facetID, + value, + latestValues +}) +export const clientFSSortResults = options => ({ + type: CLIENT_FS_SORT_RESULTS, + options +}) diff --git a/src/client/epics/index.js b/src/client/epics/index.js index 9a7213c64eef552c73a9939d49ef8b31f0f9885f..5c5943ae020ad6dd40e9f12da3e508227b6b53f9 100644 --- a/src/client/epics/index.js +++ b/src/client/epics/index.js @@ -14,7 +14,7 @@ import intl from 'react-intl-universal' import localeEN from '../translations/sampo/localeEN' import localeFI from '../translations/sampo/localeFI' import localeSV from '../translations/sampo/localeSV' -import { stateToUrl, handleAxiosError } from '../helpers/helpers' +import { stateToUrl, handleAxiosError, pickSelectedDatasets, boundsToValues } from '../helpers/helpers' import querystring from 'querystring' import { FETCH_RESULT_COUNT, @@ -32,16 +32,21 @@ import { FETCH_SIMILAR_DOCUMENTS_BY_ID_FAILED, FETCH_FACET_FAILED, FETCH_GEOJSON_LAYERS, + FETCH_GEOJSON_LAYERS_BACKEND, + CLIENT_FS_FETCH_RESULTS, + CLIENT_FS_FETCH_RESULTS_FAILED, LOAD_LOCALES, updateResultCount, updatePaginatedResults, updateResults, + clientFSUpdateResults, updateInstance, updateInstanceRelatedData, updateFacetValues, updateFacetValuesConstrainSelf, updateLocale, - updateGeoJSONLayers + updateGeoJSONLayers, + SHOW_ERROR } from '../actions' import { rootUrl, @@ -133,6 +138,37 @@ const fetchResultsEpic = (action$, state$) => action$.pipe( }) ) +const clientFSFetchResultsEpic = (action$, state$) => action$.pipe( + ofType(CLIENT_FS_FETCH_RESULTS), + withLatestFrom(state$), + debounceTime(500), + switchMap(([action, state]) => { + const { jenaIndex } = action + const selectedDatasets = pickSelectedDatasets(state.clientSideFacetedSearch.datasets) + const dsParams = selectedDatasets.map(ds => `dataset=${ds}`).join('&') + let requestUrl + if (action.jenaIndex === 'text') { + requestUrl = `${apiUrl}federatedSearch?q=${action.query}&${dsParams}` + } else if (action.jenaIndex === 'spatial') { + const { latMin, longMin, latMax, longMax } = state.leafletMap + requestUrl = `${apiUrl}federatedSearch?latMin=${latMin}&longMin=${longMin}&latMax=${latMax}&longMax=${longMax}&${dsParams}` + } + return ajax.getJSON(requestUrl).pipe( + map(response => clientFSUpdateResults({ + results: response, + jenaIndex + })), + catchError(error => of({ + type: CLIENT_FS_FETCH_RESULTS_FAILED, + error: error, + message: { + text: backendErrorText, + title: 'Error' + } + })) + ) + }) +) const fetchResultCountEpic = (action$, state$) => action$.pipe( ofType(FETCH_RESULT_COUNT), withLatestFrom(state$), @@ -335,7 +371,37 @@ const fetchSimilarDocumentsEpic = (action$, state$) => action$.pipe( }) ) -const fetchGeoJSONLayers = action$ => action$.pipe( +const fetchGeoJSONLayersBackendEpic = (action$, state$) => action$.pipe( + ofType(FETCH_GEOJSON_LAYERS_BACKEND), + withLatestFrom(state$), + mergeMap(([action]) => { + const { layerIDs, bounds } = action + const { latMin, longMin, latMax, longMax } = boundsToValues(bounds) + const params = { + layerID: layerIDs, + latMin, + longMin, + latMax, + longMax + } + const requestUrl = `${apiUrl}wfs?${querystring.stringify(params)}` + return ajax.getJSON(requestUrl).pipe( + map(res => updateGeoJSONLayers({ + payload: res + })), + catchError(error => of({ + type: SHOW_ERROR, + error: error, + message: { + text: backendErrorText, + title: 'Error' + } + })) + ) + }) +) + +const fetchGeoJSONLayersEpic = action$ => action$.pipe( ofType(FETCH_GEOJSON_LAYERS), mergeMap(async action => { const { layerIDs, bounds } = action @@ -346,16 +412,19 @@ const fetchGeoJSONLayers = action$ => action$.pipe( const fetchGeoJSONLayer = async (layerID, bounds) => { const baseUrl = 'http://kartta.nba.fi/arcgis/services/WFS/MV_Kulttuuriymparisto/MapServer/WFSServer' - const boundsStr = - `${bounds._southWest.lng},${bounds._southWest.lat},${bounds._northEast.lng},${bounds._northEast.lat}` + // const baseUrl = 'http://avaa.tdata.fi/geoserver/kotus/ows' + // const baseUrl = 'http://avaa.tdata.fi/geoserver/paituli/wfs' + // const boundsStr = + // `${bounds._southWest.lng},${bounds._southWest.lat},${bounds._northEast.lng},${bounds._northEast.lat}` const mapServerParams = { request: 'GetFeature', service: 'WFS', version: '2.0.0', typeName: layerID, srsName: 'EPSG:4326', - outputFormat: 'geojson', - bbox: boundsStr + // outputFormat: 'geojson' + outputFormat: 'application/json' + // bbox: boundsStr } const url = `${baseUrl}?${querystring.stringify(mapServerParams)}` try { @@ -372,6 +441,7 @@ const fetchGeoJSONLayer = async (layerID, bounds) => { const rootEpic = combineEpics( fetchPaginatedResultsEpic, fetchResultsEpic, + clientFSFetchResultsEpic, fetchResultCountEpic, fetchResultsClientSideEpic, fetchByURIEpic, @@ -379,7 +449,8 @@ const rootEpic = combineEpics( fetchFacetConstrainSelfEpic, loadLocalesEpic, fetchSimilarDocumentsEpic, - fetchGeoJSONLayers + fetchGeoJSONLayersEpic, + fetchGeoJSONLayersBackendEpic ) export default rootEpic diff --git a/src/client/helpers/helpers.js b/src/client/helpers/helpers.js index a88689d64e8346e80b9965f23e651d49f7fe0712..c8176fd027522e99a3ab71abb4ae0f873aa5e69e 100644 --- a/src/client/helpers/helpers.js +++ b/src/client/helpers/helpers.js @@ -68,7 +68,7 @@ export const urlToState = ({ initialState, queryString }) => { return params } -const boundsToValues = bounds => { +export const boundsToValues = bounds => { const latMin = bounds._southWest.lat const longMin = bounds._southWest.lng const latMax = bounds._northEast.lat @@ -99,3 +99,13 @@ export const handleAxiosError = error => { } console.log(error.config) } + +export const pickSelectedDatasets = datasets => { + const selected = [] + Object.keys(datasets).map(key => { + if (datasets[key].selected) { + selected.push(key) + } + }) + return selected +} diff --git a/src/client/reducers/clientSideFacetedSearch.js b/src/client/reducers/clientSideFacetedSearch.js deleted file mode 100644 index 3e9362b4bee778cff2b794ed105bf5c2de360302..0000000000000000000000000000000000000000 --- a/src/client/reducers/clientSideFacetedSearch.js +++ /dev/null @@ -1,90 +0,0 @@ -import { - FETCH_RESULTS_CLIENT_SIDE, - UPDATE_RESULTS, - CLEAR_RESULTS, - UPDATE_CLIENT_SIDE_FILTER, - SORT_RESULTS -} from '../actions' - -export const INITIAL_STATE = { - query: '', - results: [], - latestFilter: { - id: '' - }, - latestFilterValues: [], - resultsFilter: { - prefLabel: new Set(), - type: new Set() - }, - sortBy: 'prefLabel', - sortDirection: 'asc', - // groupBy: 'broaderTypeLabel', - // groupByLabel: 'Paikanlaji', - textResultsFetching: false, - spatialResultsFetching: false -} - -const clientSideFacetedSearch = (state = INITIAL_STATE, action) => { - if (action.resultClass === 'all') { - switch (action.type) { - case FETCH_RESULTS_CLIENT_SIDE: - return { - ...state, - [`${action.jenaIndex}ResultsFetching`]: true - } - case UPDATE_RESULTS: - return { - ...state, - query: action.query, - results: action.data, - [`${action.jenaIndex}ResultsFetching`]: false - } - case CLEAR_RESULTS: - return { - ...state, - results: null, - fetchingResults: false, - query: '', - resultsFilter: { - prefLabel: new Set(), - type: new Set() - } - } - case UPDATE_CLIENT_SIDE_FILTER: - return updateResultsFilter(state, action) - case SORT_RESULTS: - return { - ...state, - sortBy: action.options.sortBy, - sortDirection: action.options.sortDirection - } - default: - return state - } - } else return state -} - -const updateResultsFilter = (state, action) => { - const { property, value, latestValues } = action.filterObj - const nSet = state.resultsFilter[property] - if (nSet.has(value)) { - nSet.delete(value) - } else { - nSet.add(value) - } - const newFilter = { - ...state.resultsFilter, - [property]: nSet - } - return { - ...state, - resultsFilter: newFilter, - latestFilter: { - id: property - }, - latestFilterValues: latestValues - } -} - -export default clientSideFacetedSearch diff --git a/src/client/reducers/index.js b/src/client/reducers/index.js index a46c34160b7ff925f7dfb014ba9dc0eb06a03aa0..b9c2330e153005f8f73cc31075ccd471e3b11ea5 100644 --- a/src/client/reducers/index.js +++ b/src/client/reducers/index.js @@ -1,10 +1,12 @@ import { combineReducers } from 'redux' import { reducer as toastrReducer } from 'react-redux-toastr' +// general reducers: import error from './error' import options from './options' import animation from './animation' import leafletMapLayers from './leafletMapLayers' -import clientSideFacetedSearch from './clientSideFacetedSearch' +// portal spefic reducers: +import clientSideFacetedSearch from './sampo/clientSideFacetedSearch' import perspective1 from './sampo/perspective1' import perspective2 from './sampo/perspective2' import perspective3 from './sampo/perspective3' diff --git a/src/client/reducers/sampo/clientSideFacetedSearch.js b/src/client/reducers/sampo/clientSideFacetedSearch.js new file mode 100644 index 0000000000000000000000000000000000000000..2f4b8f86f20450fa959e3152503b13099fc8654e --- /dev/null +++ b/src/client/reducers/sampo/clientSideFacetedSearch.js @@ -0,0 +1,181 @@ +import { + CLIENT_FS_UPDATE_QUERY, + CLIENT_FS_TOGGLE_DATASET, + CLIENT_FS_FETCH_RESULTS, + CLIENT_FS_FETCH_RESULTS_FAILED, + CLIENT_FS_UPDATE_RESULTS, + CLIENT_FS_CLEAR_RESULTS, + CLIENT_FS_UPDATE_FACET, + CLIENT_FS_SORT_RESULTS +} from '../../actions' + +export const INITIAL_STATE = { + query: '', + datasets: { + kotus: { selected: true }, + pnr: { selected: true }, + warsa_karelian_places: { selected: false }, + tgn: { selected: false } + }, + results: null, + facets: { + datasetSelector: { + facetID: 'datasetSelector', + filterType: 'datasetSelector' + }, + prefLabel: { + facetID: 'prefLabel', + filterType: 'clientFSLiteral', + selectionsSet: new Set(), + isFetching: false, + searchField: true, + containerClass: 'ten', + type: 'hierarchical' + }, + broaderTypeLabel: { + facetID: 'broaderTypeLabel', + filterType: 'clientFSLiteral', + selectionsSet: new Set(), + isFetching: false, + searchField: true, + containerClass: 'ten', + type: 'hierarchical' + }, + broaderAreaLabel: { + facetID: 'broaderAreaLabel', + filterType: 'clientFSLiteral', + selectionsSet: new Set(), + isFetching: false, + searchField: true, + containerClass: 'ten', + type: 'hierarchical' + }, + modifier: { + facetID: 'modifier', + filterType: 'clientFSLiteral', + selectionsSet: new Set(), + isFetching: false, + searchField: true, + containerClass: 'ten', + type: 'hierarchical' + }, + basicElement: { + facetID: 'basicElement', + filterType: 'clientFSLiteral', + selectionsSet: new Set(), + isFetching: false, + searchField: true, + containerClass: 'ten', + type: 'hierarchical' + }, + collectionYear: { + facetID: 'collectionYear', + filterType: 'clientFSLiteral', + selectionsSet: new Set(), + isFetching: false, + searchField: true, + containerClass: 'ten', + type: 'hierarchical' + }, + source: { + facetID: 'source', + filterType: 'clientFSLiteral', + selectionsSet: new Set(), + isFetching: false, + searchField: false, + containerClass: 'three', + type: 'hierarchical' + } + }, + lastlyUpdatedFacet: null, + facetUpdateID: 0, + sortBy: 'broaderAreaLabel', + sortDirection: 'asc', + groupBy: 'broaderTypeLabel', + groupByLabel: 'Paikanlaji', + textResultsFetching: false, + spatialResultsFetching: false +} + +const clientSideFacetedSearch = (state = INITIAL_STATE, action) => { + switch (action.type) { + case CLIENT_FS_UPDATE_QUERY: + return { ...state, query: action.query || '' } + case CLIENT_FS_TOGGLE_DATASET: + return { + ...state, + suggestions: [], + results: null, + datasets: { + ...state.datasets, + [action.dataset]: { + ...state.datasets[action.dataset], + selected: !state.datasets[action.dataset].selected + } + } + } + case CLIENT_FS_FETCH_RESULTS: + return { + ...state, + [`${action.jenaIndex}ResultsFetching`]: true + } + case CLIENT_FS_FETCH_RESULTS_FAILED: + return { + ...state, + textResultsFetching: false, + spatialResultsFetching: false + } + case CLIENT_FS_CLEAR_RESULTS: + return { + ...state, + results: null, + fetchingResults: false, + query: INITIAL_STATE.query, + facets: INITIAL_STATE.facets + } + case CLIENT_FS_UPDATE_RESULTS: + return { + ...state, + results: action.results, + [`${action.jenaIndex}ResultsFetching`]: false + } + case CLIENT_FS_UPDATE_FACET: + return clientFSUpdateFacet(state, action) + case CLIENT_FS_SORT_RESULTS: + return { + ...state, + sortBy: action.options.sortBy, + sortDirection: action.options.sortDirection + } + default: + return state + } +} + +const clientFSUpdateFacet = (state, action) => { + const { facetID, value, latestValues } = action + const newSelectionsSet = state.facets[facetID].selectionsSet + if (newSelectionsSet.has(value)) { + newSelectionsSet.delete(value) + } else { + newSelectionsSet.add(value) + } + const updatedFacets = { + ...state.facets, + [facetID]: { + ...state.facets[facetID], + selectionsSet: newSelectionsSet + } + } + return { + ...state, + facetUpdateID: ++state.facetUpdateID, + facets: updatedFacets, + lastlyUpdatedFacet: { + facetID: facetID, + values: latestValues + } + } +} + +export default clientSideFacetedSearch diff --git a/src/client/translations/sampo/localeEN.js b/src/client/translations/sampo/localeEN.js index f50dd2e43289cfe2146aa442b214a9bc6e089f2e..3edab969ad228d24f5c5d27ec4469c554fe4053b 100644 --- a/src/client/translations/sampo/localeEN.js +++ b/src/client/translations/sampo/localeEN.js @@ -62,7 +62,7 @@ export default { leafletMap: { basemaps: { mapbox: { - 'light-v10': 'MapBox Light' + 'light-v10': 'Mapbox Light' }, googleRoadmap: 'Google Maps', topographicalMapNLS: 'Topographical map (National Land Survey of Finland)',