From e7ce153b6404153a5a7ab18373ff84be2802d932 Mon Sep 17 00:00:00 2001 From: Matteo Velludini Date: Wed, 10 May 2017 18:01:50 +0200 Subject: [PATCH 1/4] Fix #1760 update thumbnail with epic --- docma-config.json | 1 + web/client/actions/maps.js | 23 +++++------ web/client/epics/__tests__/fullscreen-test.js | 4 +- web/client/epics/__tests__/maps-test.js | 40 +++++++++++++++++++ web/client/epics/maps.js | 36 +++++++++++++++++ web/client/plugins/Map.jsx | 6 ++- 6 files changed, 94 insertions(+), 16 deletions(-) create mode 100644 web/client/epics/__tests__/maps-test.js create mode 100644 web/client/epics/maps.js diff --git a/docma-config.json b/docma-config.json index 4bc183313d..1acb00b3e4 100644 --- a/docma-config.json +++ b/docma-config.json @@ -132,6 +132,7 @@ "web/client/epics/globeswitcher.js", "web/client/epics/maptype.js", "web/client/epics/search.js", + "web/client/epics/maps.js", "web/client/utils/index.jsdoc", "web/client/utils/CoordinatesUtils.js", diff --git a/web/client/actions/maps.js b/web/client/actions/maps.js index bc9608ecb4..72092c1326 100644 --- a/web/client/actions/maps.js +++ b/web/client/actions/maps.js @@ -429,11 +429,11 @@ function updateMapMetadata(resourceId, newName, newDescription, onReset, options } /** - * updates permeissions for the given map. + * updates permissions for the given map. * @memberof actions.maps * @param {number} resourceId the identifier of the map * @param {object} securityRules the new securityRules - * @return {thunk} performs updateResourcePermissions and dispatch permissionsUpdated loadMaps or thumbnailError + * @return {thunk} performs updateResourcePermissions and dispatch permissionsUpdated or thumbnailError */ function updatePermissions(resourceId, securityRules) { if (!securityRules || !securityRules.SecurityRuleList || !securityRules.SecurityRuleList.SecurityRule) { @@ -444,7 +444,6 @@ function updatePermissions(resourceId, securityRules) { return (dispatch) => { GeoStoreApi.updateResourcePermissions(resourceId, securityRules).then(() => { dispatch(permissionsUpdated(resourceId, "success")); - dispatch(loadMaps(false, ConfigUtils.getDefaults().initialMapFilter || "*")); }).catch((e) => { dispatch(thumbnailError(resourceId, e)); }); @@ -457,8 +456,8 @@ function updatePermissions(resourceId, securityRules) { * @param {number} resourceId the id of the resource * @param {string} name the name of the attribute * @param {string} value the value of the attribute - * @param {string} [type] the type of the attribute - * @param {object} [options] options for the request + * @param {string} [type] the type of the attribute + * @param {object} [options] options for the request * @return {thunk} performs the update and dispatch attributeUpdated or thumbnailError */ function updateAttribute(resourceId, name, value, type, options) { @@ -480,9 +479,9 @@ function updateAttribute(resourceId, name, value, type, options) { * @param {string} dataThumbnail the data to save for the thumbnail * @param {string} categoryThumbnail the category for the thumbnails * @param {number} resourceIdMap the resourceId of the map - * @param {action} [onSuccess] the action to dispatch on success - * @param {action} [onReset] the action to dispatch on reset - * @param {object} [options] options for the request + * @param {action} [onSuccess] the action to dispatch on success + * @param {action} [onReset] the action to dispatch on reset + * @param {object} [options] options for the request * @return {thunk} perform the thumb creation and dispatch proper actions */ function createThumbnail(map, metadataMap, nameThumbnail, dataThumbnail, categoryThumbnail, resourceIdMap, onSuccess, onReset, options) { @@ -596,11 +595,11 @@ function deleteThumbnail(resourceId, resourceIdMap, options) { /** * Creates a new map. * @memberof actions.maps - * @param {object} metadata metadata for the new map - * @param {object} content the map object itself + * @param {object} metadata metadata for the new map + * @param {object} content the map object itself * @param {object} [thumbnail] the thumbnail * @param {object} [options] options for the request - * @return {thunk} creates the map and dispatches createThumbnail, mapCreated and so on + * @return {thunk} creates the map and dispatches createThumbnail, mapCreated and so on */ function createMap(metadata, content, thumbnail, options) { return (dispatch) => { @@ -622,7 +621,7 @@ function createMap(metadata, content, thumbnail, options) { * @memberof actions.maps * @param {number} resourceId the id of the resource to delete * @param {object} options options for the request - * @return {thunk} performs the delete operations and dispatches mapDeleted and loadMaps + * @return {thunk} performs the delete operations and dispatches mapDeleted and loadMaps */ function deleteMap(resourceId, options) { return (dispatch, getState) => { diff --git a/web/client/epics/__tests__/fullscreen-test.js b/web/client/epics/__tests__/fullscreen-test.js index c7b506a36f..b75b796e03 100644 --- a/web/client/epics/__tests__/fullscreen-test.js +++ b/web/client/epics/__tests__/fullscreen-test.js @@ -20,7 +20,7 @@ const rootEpic = combineEpics(toggleFullscreenEpic); const epicMiddleware = createEpicMiddleware(rootEpic); const mockStore = configureMockStore([epicMiddleware]); -describe('search Epics', () => { +describe('fullscreen Epics', () => { let store; beforeEach(() => { store = mockStore(); @@ -31,7 +31,7 @@ describe('search Epics', () => { screenfull.exit(); }); - it('produces the search epic', (done) => { + it('produces the fullscreen epic', (done) => { let changed = false; let action = toggleFullscreen(true, "html"); if ( screenfull.enabled ) { diff --git a/web/client/epics/__tests__/maps-test.js b/web/client/epics/__tests__/maps-test.js new file mode 100644 index 0000000000..c93656c87a --- /dev/null +++ b/web/client/epics/__tests__/maps-test.js @@ -0,0 +1,40 @@ + +/* + * Copyright 2017, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +var expect = require('expect'); + +const configureMockStore = require('redux-mock-store').default; +const { createEpicMiddleware, combineEpics } = require('redux-observable'); +const {attributeUpdated, ATTRIBUTE_UPDATED} = require('../../actions/maps'); +const {mapsEpic } = require('../maps'); +const rootEpic = combineEpics(mapsEpic); +const epicMiddleware = createEpicMiddleware(rootEpic); +const mockStore = configureMockStore([epicMiddleware]); + +describe('maps Epics', () => { + let store; + beforeEach(() => { + store = mockStore(); + }); + + afterEach(() => { + epicMiddleware.replaceEpic(rootEpic); + }); + + it('produces the maps epic', (done) => { + store.dispatch( attributeUpdated(3, "name", "value", "STRING", "success")); + + setTimeout(() => { + let actions = store.getActions(); + expect(actions.length).toBe(1); + expect(actions[0].type).toBe(ATTRIBUTE_UPDATED); + done(); + }, 400); + }); +}); diff --git a/web/client/epics/maps.js b/web/client/epics/maps.js new file mode 100644 index 0000000000..79aa09040c --- /dev/null +++ b/web/client/epics/maps.js @@ -0,0 +1,36 @@ +/* + * Copyright 2017, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +const { + ATTRIBUTE_UPDATED, loadMaps, thumbnailError +} = require('../actions/maps'); + +const ConfigUtils = require('../utils/ConfigUtils'); +const Rx = require('rxjs'); + +/** + * Gets every `ATTRIBUTE_UPDATED` event. + * Dispatches the reload of the maps after the attributes are updated + * @param {external:Observable} action$ + * @memberof epics.maps + * @return {external:Observable} + */ +const mapsEpic = action$ => + action$.ofType(ATTRIBUTE_UPDATED) + .switchMap( () => + Rx.Observable.of(loadMaps(false, ConfigUtils.getDefaults().initialMapFilter || "*")) + .catch(e => Rx.Observable.from([thumbnailError(e)])) +); + + /** + * Actions for maps + * @name epics.maps + */ +module.exports = { + mapsEpic +}; diff --git a/web/client/plugins/Map.jsx b/web/client/plugins/Map.jsx index aae84e09fa..19d10c4de8 100644 --- a/web/client/plugins/Map.jsx +++ b/web/client/plugins/Map.jsx @@ -1,5 +1,5 @@ /* - * Copyright 2016, GeoSolutions Sas. + * Copyright 2017, GeoSolutions Sas. * All rights reserved. * * This source code is licensed under the BSD-style license found in the @@ -15,6 +15,7 @@ require('./map/css/map.css'); const Message = require('../components/I18N/Message'); const ConfigUtils = require('../utils/ConfigUtils'); +const {mapsEpic} = require('../epics/maps'); const {isString} = require('lodash'); let plugins; /** @@ -288,5 +289,6 @@ const selector = createSelector( ); module.exports = { MapPlugin: connect(selector)(MapPlugin), - reducers: { draw: require('../reducers/draw') } + reducers: { draw: require('../reducers/draw') }, + epics: {mapsEpic} }; From 610ae03ab1ce1ad94d01fc13d258b00002212744 Mon Sep 17 00:00:00 2001 From: Matteo Velludini Date: Thu, 11 May 2017 14:48:59 +0200 Subject: [PATCH 2/4] fixed reload with searchtext --- web/client/epics/maps.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/client/epics/maps.js b/web/client/epics/maps.js index 79aa09040c..f46bf9563b 100644 --- a/web/client/epics/maps.js +++ b/web/client/epics/maps.js @@ -20,10 +20,10 @@ const Rx = require('rxjs'); * @memberof epics.maps * @return {external:Observable} */ -const mapsEpic = action$ => +const mapsEpic = (action$, store) => action$.ofType(ATTRIBUTE_UPDATED) .switchMap( () => - Rx.Observable.of(loadMaps(false, ConfigUtils.getDefaults().initialMapFilter || "*")) + Rx.Observable.of(loadMaps(false, store.getState().maps.searchText || "*")) .catch(e => Rx.Observable.from([thumbnailError(e)])) ); From e9bef98cae587017b0022d40e2e8f6d895d2b14f Mon Sep 17 00:00:00 2001 From: Matteo Velludini Date: Thu, 11 May 2017 18:43:01 +0200 Subject: [PATCH 3/4] fixed editing of map metadata and thumbnail --- web/client/actions/maps.js | 20 +++++++++++- .../components/maps/forms/Thumbnail.jsx | 3 ++ .../components/maps/modals/MetadataModal.jsx | 31 +++++-------------- web/client/plugins/Maps.jsx | 5 +-- web/client/reducers/maps.js | 19 +++++++++++- 5 files changed, 51 insertions(+), 27 deletions(-) diff --git a/web/client/actions/maps.js b/web/client/actions/maps.js index 72092c1326..45b161bd35 100644 --- a/web/client/actions/maps.js +++ b/web/client/actions/maps.js @@ -34,6 +34,7 @@ const PERMISSIONS_LIST_LOADING = 'PERMISSIONS_LIST_LOADING'; const PERMISSIONS_LIST_LOADED = 'PERMISSIONS_LIST_LOADED'; const RESET_CURRENT_MAP = 'RESET_CURRENT_MAP'; const MAPS_SEARCH_TEXT_CHANGED = 'MAPS_SEARCH_TEXT_CHANGED'; +const METADATA_CHANGED = 'METADATA_CHANGED'; /** * reset current map metadata, `RESET_CURRENT_MAP` @@ -109,6 +110,21 @@ function loadError(e) { }; } +/** + * updates metadata of the map + * @memberof actions.maps + * @param {object} prop the name of the changed property + * @param {object} name the value of the changed property + * @return {action} METADATA_CHANGED + */ +function metadataChanged(prop, value) { + return { + type: METADATA_CHANGED, + prop, + value + }; +} + /** * When a new map is created * @memberof actions.maps @@ -630,7 +646,7 @@ function deleteMap(resourceId, options) { dispatch(mapDeleted(resourceId, "success")); let state = getState && getState(); if ( state && state.maps && state.maps.totalCount === state.maps.start) { - dispatch(loadMaps(false, ConfigUtils.getDefaults().initialMapFilter || "*")); + dispatch(loadMaps(false, state.maps.searchText || ConfigUtils.getDefaults().initialMapFilter || "*")); } }).catch((e) => { dispatch(mapDeleted(resourceId, "failure", e)); @@ -664,6 +680,8 @@ module.exports = { MAP_ERROR, RESET_CURRENT_MAP, MAPS_SEARCH_TEXT_CHANGED, + METADATA_CHANGED, + metadataChanged, loadMaps, mapsLoading, mapsLoaded, diff --git a/web/client/components/maps/forms/Thumbnail.jsx b/web/client/components/maps/forms/Thumbnail.jsx index 8442f05f99..a1bfb3846f 100644 --- a/web/client/components/maps/forms/Thumbnail.jsx +++ b/web/client/components/maps/forms/Thumbnail.jsx @@ -148,6 +148,9 @@ const Thumbnail = React.createClass({ this.props.onSaveAll(map, metadata, name, data, category, this.props.map.id); } if (!this.props.map.newThumbnail && !data && !this.refs.imgThumbnail) { + if (this.props.map.thumbnail && metadata) { + this.deleteThumbnail(this.props.map.thumbnail, this.props.map.id); + } this.props.onSaveAll(map, metadata, name, data, category, this.props.map.id); } return data; diff --git a/web/client/components/maps/modals/MetadataModal.jsx b/web/client/components/maps/modals/MetadataModal.jsx index 5f3470fd5b..c4df952ccd 100644 --- a/web/client/components/maps/modals/MetadataModal.jsx +++ b/web/client/components/maps/modals/MetadataModal.jsx @@ -31,6 +31,7 @@ const MetadataModal = React.createClass({ authHeader: React.PropTypes.string, show: React.PropTypes.bool, options: React.PropTypes.object, + metadata: React.PropTypes.object, loadPermissions: React.PropTypes.func, loadAvailableGroups: React.PropTypes.func, onSave: React.PropTypes.func, @@ -56,6 +57,7 @@ const MetadataModal = React.createClass({ onUpdateCurrentMap: React.PropTypes.func, onNewGroupChoose: React.PropTypes.func, onNewPermissionChoose: React.PropTypes.func, + metadataChanged: React.PropTypes.func, displayPermissionEditor: React.PropTypes.bool, availablePermissions: React.PropTypes.arrayOf(React.PropTypes.string), availableGroups: React.PropTypes.arrayOf(React.PropTypes.object), @@ -83,6 +85,7 @@ const MetadataModal = React.createClass({ onDeleteThumbnail: ()=> {}, onGroupsChange: ()=> {}, onAddPermission: ()=> {}, + metadataChanged: ()=> {}, onNewGroupChoose: ()=> {}, onNewPermissionChoose: ()=> {}, user: { @@ -112,14 +115,6 @@ const MetadataModal = React.createClass({ groups: [] }; }, - componentWillMount() { - if (this.props.map && this.props.map.name) { - this.setState({ - name: this.props.map.name, - description: this.props.map.description || '' - }); - } - }, componentWillReceiveProps(nextProps) { if (nextProps.map && this.props.map && !nextProps.map.loading && this.state && this.state.saving) { this.setState({ @@ -127,12 +122,6 @@ const MetadataModal = React.createClass({ }); this.props.onClose(); } - if (nextProps.map && nextProps.map.name) { - this.setState({ - name: nextProps.map.name, - description: nextProps.map.description || '' - }); - } }, componentDidUpdate(prevProps) { if (this.props.show && !prevProps.show) { @@ -149,8 +138,8 @@ const MetadataModal = React.createClass({ let metadata = null; if ( this.isMetadataChanged() ) { - let name = this.state.name; - let description = this.state.description; + let name = this.props.metadata.name; + let description = this.props.metadata.description; metadata = { name: name, description: description @@ -211,11 +200,7 @@ const MetadataModal = React.createClass({ ); const body = ( { - this.setState({ - [prop]: value - }); - }} + onChange={this.props.metadataChanged} map={this.props.map} nameFieldText={} descriptionFieldText={} @@ -305,8 +290,8 @@ const MetadataModal = React.createClass({ }, isMetadataChanged() { return this.props.map && ( - this.state.description !== this.props.map.description || - this.state.name !== this.props.map.name + this.props.metadata.description !== this.props.map.description || + this.props.metadata.name !== this.props.map.name ); }, isThumbnailChanged() { diff --git a/web/client/plugins/Maps.jsx b/web/client/plugins/Maps.jsx index 0d537a67d4..4be2b1f4b3 100644 --- a/web/client/plugins/Maps.jsx +++ b/web/client/plugins/Maps.jsx @@ -8,7 +8,7 @@ const React = require('react'); const {connect} = require('react-redux'); -const {loadMaps, updateMapMetadata, deleteMap, createThumbnail, deleteThumbnail, saveMap, thumbnailError, saveAll, onDisplayMetadataEdit, resetUpdating} = require('../actions/maps'); +const {loadMaps, updateMapMetadata, deleteMap, createThumbnail, deleteThumbnail, saveMap, thumbnailError, saveAll, onDisplayMetadataEdit, resetUpdating, metadataChanged} = require('../actions/maps'); const {editMap, updateCurrentMap, errorCurrentMap, removeThumbnail, resetCurrentMap} = require('../actions/currentMap'); const ConfigUtils = require('../utils/ConfigUtils'); const MapsGrid = connect((state) => { @@ -43,13 +43,14 @@ const {setControlProperty} = require('../actions/controls'); const MetadataModal = connect( (state = {}) => ({ + metadata: state.maps.metadata, availableGroups: state.currentMap && state.currentMap.availableGroups || [ ], // TODO: add message when array is empty newGroup: state.controls && state.controls.permissionEditor && state.controls.permissionEditor.newGroup, newPermission: state.controls && state.controls.permissionEditor && state.controls.permissionEditor.newPermission || "canRead", user: state.security && state.security.user || {name: "Guest"} }), { - loadPermissions, loadAvailableGroups, updatePermissions, onGroupsChange: updateCurrentMapPermissions, onAddPermission: addCurrentMapPermission, + loadPermissions, loadAvailableGroups, updatePermissions, onGroupsChange: updateCurrentMapPermissions, onAddPermission: addCurrentMapPermission, metadataChanged, onNewGroupChoose: setControlProperty.bind(null, 'permissionEditor', 'newGroup'), onNewPermissionChoose: setControlProperty.bind(null, 'permissionEditor', 'newPermission') }, null, {withRef: true} )(require('../components/maps/modals/MetadataModal')); diff --git a/web/client/reducers/maps.js b/web/client/reducers/maps.js index 9b0205fce8..e2dae88cef 100644 --- a/web/client/reducers/maps.js +++ b/web/client/reducers/maps.js @@ -10,7 +10,9 @@ const { MAPS_LIST_LOADED, MAPS_LIST_LOADING, MAPS_LIST_LOAD_ERROR, MAP_CREATED, MAP_UPDATING, MAP_METADATA_UPDATED, MAP_DELETING, MAP_DELETED, ATTRIBUTE_UPDATED, PERMISSIONS_LIST_LOADING, PERMISSIONS_LIST_LOADED, SAVE_MAP, PERMISSIONS_UPDATED, THUMBNAIL_ERROR, RESET_UPDATING, - MAPS_SEARCH_TEXT_CHANGED} = require('../actions/maps'); + MAPS_SEARCH_TEXT_CHANGED, METADATA_CHANGED} = require('../actions/maps'); +const { + EDIT_MAP, RESET_CURRENT_MAP} = require('../actions/currentMap'); const assign = require('object-assign'); const _ = require('lodash'); /** @@ -68,6 +70,21 @@ function maps(state = { searchText: action.text }); } + case METADATA_CHANGED: { + return assign({}, state, { + metadata: assign({}, state.metadata, {[action.prop]: action.value }) + }); + } + case EDIT_MAP: { + return assign({}, state, { + metadata: {name: action.map.name, description: action.map.description} + }); + } + case RESET_CURRENT_MAP: { + return assign({}, state, { + metadata: {name: null, description: null} + }); + } case MAPS_LIST_LOADING: return assign({}, state, { loading: true, From 15a756e454f3e97dce57d791adad59096ba2b3e3 Mon Sep 17 00:00:00 2001 From: Matteo Velludini Date: Thu, 11 May 2017 18:49:51 +0200 Subject: [PATCH 4/4] removed unused epic --- docma-config.json | 1 - web/client/epics/__tests__/maps-test.js | 40 ------------------------- web/client/epics/maps.js | 35 ---------------------- web/client/plugins/Map.jsx | 4 +-- 4 files changed, 1 insertion(+), 79 deletions(-) delete mode 100644 web/client/epics/__tests__/maps-test.js delete mode 100644 web/client/epics/maps.js diff --git a/docma-config.json b/docma-config.json index b53b0764ab..70f8851937 100644 --- a/docma-config.json +++ b/docma-config.json @@ -133,7 +133,6 @@ "web/client/epics/maptype.js", "web/client/epics/search.js", "web/client/epics/wfsquery.js", - "web/client/epics/maps.js", "web/client/utils/index.jsdoc", "web/client/utils/CoordinatesUtils.js", diff --git a/web/client/epics/__tests__/maps-test.js b/web/client/epics/__tests__/maps-test.js deleted file mode 100644 index c93656c87a..0000000000 --- a/web/client/epics/__tests__/maps-test.js +++ /dev/null @@ -1,40 +0,0 @@ - -/* - * Copyright 2017, GeoSolutions Sas. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. - */ - -var expect = require('expect'); - -const configureMockStore = require('redux-mock-store').default; -const { createEpicMiddleware, combineEpics } = require('redux-observable'); -const {attributeUpdated, ATTRIBUTE_UPDATED} = require('../../actions/maps'); -const {mapsEpic } = require('../maps'); -const rootEpic = combineEpics(mapsEpic); -const epicMiddleware = createEpicMiddleware(rootEpic); -const mockStore = configureMockStore([epicMiddleware]); - -describe('maps Epics', () => { - let store; - beforeEach(() => { - store = mockStore(); - }); - - afterEach(() => { - epicMiddleware.replaceEpic(rootEpic); - }); - - it('produces the maps epic', (done) => { - store.dispatch( attributeUpdated(3, "name", "value", "STRING", "success")); - - setTimeout(() => { - let actions = store.getActions(); - expect(actions.length).toBe(1); - expect(actions[0].type).toBe(ATTRIBUTE_UPDATED); - done(); - }, 400); - }); -}); diff --git a/web/client/epics/maps.js b/web/client/epics/maps.js deleted file mode 100644 index cad296c12a..0000000000 --- a/web/client/epics/maps.js +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2017, GeoSolutions Sas. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. - */ - -const { - ATTRIBUTE_UPDATED, loadMaps, thumbnailError -} = require('../actions/maps'); - -const Rx = require('rxjs'); - -/** - * Gets every `ATTRIBUTE_UPDATED` event. - * Dispatches the reload of the maps after the attributes are updated - * @param {external:Observable} action$ - * @memberof epics.maps - * @return {external:Observable} - */ -const mapsEpic = (action$, store) => - action$.ofType(ATTRIBUTE_UPDATED) - .switchMap( () => - Rx.Observable.of(loadMaps(false, store.getState().maps.searchText || "*")) - .catch(e => Rx.Observable.from([thumbnailError(e)])) -); - - /** - * Actions for maps - * @name epics.maps - */ -module.exports = { - mapsEpic -}; diff --git a/web/client/plugins/Map.jsx b/web/client/plugins/Map.jsx index 19d10c4de8..62d6d252f3 100644 --- a/web/client/plugins/Map.jsx +++ b/web/client/plugins/Map.jsx @@ -15,7 +15,6 @@ require('./map/css/map.css'); const Message = require('../components/I18N/Message'); const ConfigUtils = require('../utils/ConfigUtils'); -const {mapsEpic} = require('../epics/maps'); const {isString} = require('lodash'); let plugins; /** @@ -289,6 +288,5 @@ const selector = createSelector( ); module.exports = { MapPlugin: connect(selector)(MapPlugin), - reducers: { draw: require('../reducers/draw') }, - epics: {mapsEpic} + reducers: { draw: require('../reducers/draw') } };