From 178e84e570ef6a8b89c2d1b107bf40409f52e946 Mon Sep 17 00:00:00 2001 From: Vladislav Shatilenya Date: Thu, 3 Sep 2020 18:21:38 +0300 Subject: [PATCH 1/5] Adopt general SaveModal to MapGrid --- .../actions/__tests__/currentMap-test.js | 26 - web/client/actions/__tests__/maps-test.js | 244 +-------- web/client/actions/config.js | 2 +- web/client/actions/currentMap.js | 30 -- web/client/actions/maps.js | 449 +--------------- web/client/components/map/openlayers/Map.jsx | 5 + web/client/components/maps/MapCard.jsx | 8 +- web/client/components/maps/MapGrid.jsx | 73 ++- .../maps/__tests__/MapCard-test.jsx | 6 +- .../components/maps/modals/MetadataModal.jsx | 500 ------------------ .../modals/__tests__/MetaDataModal-test.jsx | 330 ------------ .../resources/enhancers/resourceGrid.js | 46 +- .../components/resources/modals/Save.jsx | 67 ++- .../modals/enhancers/handleDetails.jsx | 78 +++ .../modals/enhancers/handleResourceData.jsx | 4 + .../resources/modals/enhancers/handleSave.js | 59 +++ .../modals/enhancers/handleSaveModal.js | 16 +- .../resources/modals/fragments/DetailsRow.jsx | 83 +-- .../modals/fragments/DetailsSheet.jsx | 78 +++ web/client/epics/__tests__/dashboards-test.js | 21 - web/client/epics/__tests__/maps-test.js | 125 +---- web/client/epics/dashboards.js | 4 +- web/client/epics/maps.js | 256 ++------- web/client/examples/api/plugins.js | 2 +- web/client/plugins/FeaturedMaps.jsx | 67 +-- web/client/plugins/Maps.jsx | 63 ++- .../plugins/contextmanager/ContextGrid.jsx | 6 +- .../plugins/dashboard/DashboardsGrid.jsx | 10 +- web/client/plugins/dashboard/SaveDialog.jsx | 2 +- .../plugins/geostories/GeostoriesGrid.jsx | 10 +- web/client/plugins/maps/MapSave.jsx | 3 +- web/client/plugins/maps/MapsGrid.jsx | 64 ++- web/client/plugins/maps/MetadataModal.jsx | 31 -- .../product/components/home/MapsList.jsx | 4 +- web/client/product/plugins.js | 4 +- .../reducers/__tests__/featuredmaps-test.js | 78 +-- web/client/reducers/__tests__/maps-test.js | 78 +-- web/client/reducers/currentMap.js | 93 +--- web/client/reducers/featuredmaps.js | 39 +- web/client/reducers/maps.js | 71 +-- .../selectors/__tests__/featuredmaps-test.js | 13 - web/client/selectors/currentmap.js | 1 + web/client/selectors/featuredmaps.js | 9 +- web/client/translations/data.en-US.json | 1 + web/client/utils/ObservableUtils.js | 51 -- 45 files changed, 632 insertions(+), 2578 deletions(-) delete mode 100644 web/client/components/maps/modals/MetadataModal.jsx delete mode 100644 web/client/components/maps/modals/__tests__/MetaDataModal-test.jsx create mode 100644 web/client/components/resources/modals/enhancers/handleDetails.jsx create mode 100644 web/client/components/resources/modals/enhancers/handleSave.js create mode 100644 web/client/components/resources/modals/fragments/DetailsSheet.jsx delete mode 100644 web/client/plugins/maps/MetadataModal.jsx diff --git a/web/client/actions/__tests__/currentMap-test.js b/web/client/actions/__tests__/currentMap-test.js index 7d615af6a2..0dfddc73c8 100644 --- a/web/client/actions/__tests__/currentMap-test.js +++ b/web/client/actions/__tests__/currentMap-test.js @@ -9,9 +9,6 @@ var expect = require('expect'); var { EDIT_MAP, editMap, - UPDATE_CURRENT_MAP, updateCurrentMap, - ERROR_CURRENT_MAP, errorCurrentMap, - REMOVE_THUMBNAIL, removeThumbnail, UPDATE_CURRENT_MAP_PERMISSIONS, updateCurrentMapPermissions, UPDATE_CURRENT_MAP_GROUPS, updateCurrentMapGroups, RESET_CURRENT_MAP, resetCurrentMap, @@ -36,15 +33,6 @@ describe('Test correctness of the maps actions', () => { expect(retval.map.canWrite).toBeTruthy(); }); - it('updateCurrentMap', () => { - let thumbnailData = []; - let thumbnail = "myThumnbnailUrl"; - var retval = updateCurrentMap(thumbnailData, thumbnail); - expect(retval).toExist(); - expect(retval.type).toBe(UPDATE_CURRENT_MAP); - expect(retval.thumbnail).toBe(thumbnail); - expect(retval.thumbnailData).toBe(thumbnailData); - }); it('updateCurrentMapGroups', () => { let groups = { groups: { @@ -61,13 +49,6 @@ describe('Test correctness of the maps actions', () => { expect(retval.groups).toBe(groups); }); - it('errorCurrentMap', () => { - let errors = ["FORMAT"]; - var retval = errorCurrentMap(errors); - expect(retval).toExist(); - expect(retval.type).toBe(ERROR_CURRENT_MAP); - expect(retval.errors).toBe(errors); - }); it('updateCurrentMapPermissions', () => { let permissions = { SecurityRuleList: { @@ -86,13 +67,6 @@ describe('Test correctness of the maps actions', () => { expect(retval.type).toBe(UPDATE_CURRENT_MAP_PERMISSIONS); expect(retval.permissions).toBe(permissions); }); - it('removeThumbnail', () => { - let resourceId = 1; - const retval = removeThumbnail(resourceId); - expect(retval).toExist(); - expect(retval.type).toBe(REMOVE_THUMBNAIL); - expect(retval.resourceId).toBe(resourceId); - }); it('resetCurrentMap', () => { const retval = resetCurrentMap(); expect(retval).toExist(); diff --git a/web/client/actions/__tests__/maps-test.js b/web/client/actions/__tests__/maps-test.js index 6ff075cb2a..43e84b72ce 100644 --- a/web/client/actions/__tests__/maps-test.js +++ b/web/client/actions/__tests__/maps-test.js @@ -11,46 +11,27 @@ const assign = require('object-assign'); const axios = require("../../libs/ajax"); const MockAdapter = require("axios-mock-adapter"); const { - toggleDetailsSheet, TOGGLE_DETAILS_SHEET, - toggleGroupProperties, TOGGLE_GROUP_PROPERTIES, - toggleUnsavedChanges, TOGGLE_UNSAVED_CHANGES, deleteMap, DELETE_MAP, - updateDetails, UPDATE_DETAILS, - saveDetails, SAVE_DETAILS, - deleteDetails, DELETE_DETAILS, - setDetailsChanged, SET_DETAILS_CHANGED, saveResourceDetails, SAVE_RESOURCE_DETAILS, - backDetails, BACK_DETAILS, - undoDetails, UNDO_DETAILS, doNothing, DO_NOTHING, setUnsavedChanged, SET_UNSAVED_CHANGES, openDetailsPanel, OPEN_DETAILS_PANEL, closeDetailsPanel, CLOSE_DETAILS_PANEL, MAP_UPDATING, mapUpdating, DETAILS_LOADED, detailsLoaded, - PERMISSIONS_UPDATED, permissionsUpdated, - loadPermissions, - PERMISSIONS_LIST_LOADING, PERMISSIONS_LIST_LOADED, ATTRIBUTE_UPDATED, attributeUpdated, - SAVE_MAP, saveMap, DISPLAY_METADATA_EDIT, onDisplayMetadataEdit, - RESET_UPDATING, resetUpdating, THUMBNAIL_ERROR, thumbnailError, TOGGLE_DETAILS_EDITABILITY, toggleDetailsEditability, MAPS_SEARCH_TEXT_CHANGED, mapsSearchTextChanged, MAPS_LIST_LOAD_ERROR, loadError, - MAP_ERROR, mapError, updatePermissions, - MAP_METADATA_UPDATED, mapMetadataUpdated, + MAP_ERROR, mapError, METADATA_CHANGED, metadataChanged, setShowMapDetails, SHOW_DETAILS, - updateAttribute, saveAll, - SAVE_MAP_RESOURCE, saveMapResource, - FEATURED_MAPS_SET_LATEST_RESOURCE, setFeaturedMapsLatestResource, - updateMapMetadata, RESET_CURRENT_MAP + updateAttribute, + SAVE_MAP_RESOURCE, saveMapResource } = require('../maps'); -const { SHOW_NOTIFICATION } = require('../notifications'); - let GeoStoreDAO = require('../../api/GeoStoreDAO'); let oldAddBaseUri = GeoStoreDAO.addBaseUrl; @@ -110,93 +91,6 @@ describe('Test correctness of the maps actions', () => { } }); }); - it('saveAll - with metadataMap, without thumbnail', (done) => { - const resourceId = 1; - // saveAll(map, metadataMap, nameThumbnail, dataThumbnail, categoryThumbnail, resourceIdMap, options) - const retFun = saveAll({}, {name: "name"}, null, null, null, resourceId, {}); - expect(retFun).toExist(); - let count = 0; - retFun((action) => { - switch (count) { - case 0: expect(action.type).toBe(MAP_UPDATING); break; - case 1: expect(action.type).toBe("NONE"); break; - default: done(); - } - count++; - }, () => {}); - }); - it('saveAll - without metadataMap, without thumbnail', (done) => { - const resourceId = 1; - // saveAll(map, metadataMap, nameThumbnail, dataThumbnail, categoryThumbnail, resourceIdMap, options) - const retFun = saveAll({}, null, null, null, null, resourceId, {}); - expect(retFun).toExist(); - let count = 0; - retFun((action) => { - switch (count) { - case 0: expect(action.type).toBe(MAP_UPDATING); break; - case 1: expect(action.type).toBe("NONE"); break; - default: done(); - } - count++; - }, () => {}); - }); - it('saveAll - without metadataMap, without thumbnail, detailsChanged', (done) => { - const resourceId = 1; - // saveAll(map, metadataMap, nameThumbnail, dataThumbnail, categoryThumbnail, resourceIdMap, options) - const retFun = saveAll({}, null, null, null, null, resourceId, {}); - expect(retFun).toExist(); - let count = 0; - retFun((action) => { - switch (count) { - case 0: expect(action.type).toBe(MAP_UPDATING); break; - case 1: expect(action.type).toBe("NONE"); break; - case 2: expect(action.type).toBe(SAVE_RESOURCE_DETAILS); done(); break; - default: done(); - } - count++; - }, () => ({currentMap: { - detailsChanged: true - }} - )); - }); - it('updatePermissions with securityRules list & without', (done) => { - const securityRules = { - SecurityRuleList: { - RuleCount: 1, - SecurityRule: [{ - canRead: true, - canWrite: true, - user: { - id: 1 - } - }] - } - }; - const resourceId = 1; - const retFun = updatePermissions(resourceId, securityRules); - expect(retFun).toExist(); - let count = 0; - retFun((action) => { - switch (count) { - // TODO: this should return PERMISSIONS_UPDATED - case 0: expect(action.type).toBe(PERMISSIONS_UPDATED); break; - default: done(); - } - count++; - done(); - }); - const retFun2 = updatePermissions(-1, securityRules); - expect(retFun).toExist(); - let count2 = 0; - retFun2((action) => { - switch (count2) { - case 0: expect(action.type).toBe(THUMBNAIL_ERROR); break; - default: done(); - } - count2++; - done(); - }); - }); it('mapUpdating', () => { let resourceId = 13; var retval = mapUpdating(resourceId); @@ -205,14 +99,6 @@ describe('Test correctness of the maps actions', () => { expect(retval.resourceId).toBe(resourceId); }); - it('permissionsUpdated', () => { - let resourceId = 13; - var retval = permissionsUpdated(resourceId, null); - expect(retval).toExist(); - expect(retval.type).toBe(PERMISSIONS_UPDATED); - expect(retval.resourceId).toBe(resourceId); - }); - it('attributeUpdated', () => { let resourceId = 13; let name = "thumbnail"; @@ -236,14 +122,6 @@ describe('Test correctness of the maps actions', () => { expect(retval.error.status).toBe(error.status); }); - it('resetUpdating', () => { - let resourceId = 1; - let retval = resetUpdating(resourceId); - expect(retval).toExist(); - expect(retval.type).toBe(RESET_UPDATING); - expect(retval.resourceId).toBe(resourceId); - }); - it('onDisplayMetadataEdit', () => { let dispMetadataValue = true; let retval = onDisplayMetadataEdit(dispMetadataValue); @@ -252,21 +130,6 @@ describe('Test correctness of the maps actions', () => { expect(retval.displayMetadataEditValue).toBe(dispMetadataValue); }); - it('saveMap', () => { - let thumbnail = "myThumnbnailUrl"; - let resourceId = 13; - let map = { - thumbnail: thumbnail, - id: 123, - canWrite: true - }; - var retval = saveMap(map, resourceId); - expect(retval).toExist(); - expect(retval.type).toBe(SAVE_MAP); - expect(retval.resourceId).toBe(resourceId); - expect(retval.map).toBe(map); - }); - it('mapsSearchTextChanged', () => { const a = mapsSearchTextChanged("TEXT"); expect(a.type).toBe(MAPS_SEARCH_TEXT_CHANGED); @@ -288,15 +151,6 @@ describe('Test correctness of the maps actions', () => { expect(a.error).toBe(error); expect(a.resourceId).toBe(resourceId); }); - it('mapMetadataUpdated', () => { - const a = mapMetadataUpdated("resourceId", "newName", "newDescription", "result", "error"); - expect(a.type).toBe(MAP_METADATA_UPDATED); - expect(a.resourceId).toBe("resourceId"); - expect(a.newName).toBe("newName"); - expect(a.newDescription).toBe("newDescription"); - expect(a.result).toBe("result"); - expect(a.error).toBe("error"); - }); it('mapMetadatachanged', () => { const prop = "name"; const value = "newName"; @@ -308,13 +162,6 @@ describe('Test correctness of the maps actions', () => { expect(a.value).toBe(value); }); - it('toggleDetailsSheet', () => { - const detailsSheetReadOnly = true; - const a = toggleDetailsSheet(detailsSheetReadOnly); - expect(a.type).toBe(TOGGLE_DETAILS_SHEET); - expect(a.detailsSheetReadOnly).toBeTruthy(); - - }); it('setShowMapDetails', () => { const showMapDetails = true; const action = setShowMapDetails(showMapDetails); @@ -322,24 +169,6 @@ describe('Test correctness of the maps actions', () => { expect(action.showMapDetails).toBe(showMapDetails); }); - it('toggleGroupProperties', () => { - const a = toggleGroupProperties(); - expect(a.type).toBe(TOGGLE_GROUP_PROPERTIES); - }); - it('toggleUnsavedChanges', () => { - const a = toggleUnsavedChanges(); - expect(a.type).toBe(TOGGLE_UNSAVED_CHANGES); - }); - it('updateDetails', () => { - const detailsText = "

some value

"; - const originalDetails = "

old value

"; - const doBackup = true; - const a = updateDetails(detailsText, doBackup, originalDetails); - expect(a.doBackup).toBeTruthy(); - expect(a.detailsText).toBe(detailsText); - expect(a.originalDetails).toBe(originalDetails); - expect(a.type).toBe(UPDATE_DETAILS); - }); it('deleteMap', () => { const resourceId = 1; const someOpt = { @@ -353,32 +182,6 @@ describe('Test correctness of the maps actions', () => { expect(a.type).toBe(DELETE_MAP); expect(a.options).toBe(options); }); - it('saveDetails', () => { - const detailsText = "

some detailsText

"; - const a = saveDetails(detailsText); - expect(a.type).toBe(SAVE_DETAILS); - expect(a.detailsText).toBe(detailsText); - }); - it('deleteDetails', () => { - const a = deleteDetails(); - expect(a.type).toBe(DELETE_DETAILS); - }); - it('setDetailsChanged', () => { - const detailsChanged = true; - const a = setDetailsChanged(detailsChanged); - expect(a.type).toBe(SET_DETAILS_CHANGED); - expect(a.detailsChanged).toBe(detailsChanged); - }); - it('backDetails', () => { - const backupDetails = true; - const a = backDetails(backupDetails); - expect(a.type).toBe(BACK_DETAILS); - expect(a.backupDetails).toBe(backupDetails); - }); - it('undoDetails', () => { - const a = undoDetails(); - expect(a.type).toBe(UNDO_DETAILS); - }); it('setUnsavedChanged', () => { const value = true; const a = setUnsavedChanged(value); @@ -415,46 +218,5 @@ describe('Test correctness of the maps actions', () => { expect(a.type).toBe(SAVE_MAP_RESOURCE); expect(a.resource).toBe(resource); }); - it('updateMapMetadata', (done) => { - mockAxios.onPut().reply(200); - let actions = []; - const dispatch = a => { - actions.push(a); - // when finished, check and done - if (a.type === RESET_CURRENT_MAP) { - expect(actions[0].type).toBe(MAP_METADATA_UPDATED); - expect(actions[1].type).toBe(SHOW_NOTIFICATION); - done(); - - } - }; - updateMapMetadata(2, "test", "desc", () => {}, {})(dispatch); - }); - it('setFeaturedMapsLatestResource', () => { - const resource = { - id: 1 - }; - const a = setFeaturedMapsLatestResource(resource); - expect(a.type).toBe(FEATURED_MAPS_SET_LATEST_RESOURCE); - expect(a.resource).toBe(resource); - }); - it('loadPermissions error', done => { - const NOT_EXISTING = "UNKNOWN_RESOURCE"; - loadPermissions(NOT_EXISTING)(action => { - switch (action.type) { - case PERMISSIONS_LIST_LOADED: - expect(action.permissions).toBe(null); - expect(action.mapId).toBe(NOT_EXISTING); - done(); - break; - case PERMISSIONS_LIST_LOADING: - expect(action.mapId).toBe(NOT_EXISTING); - break; - default: - done(`${action.type} action triggered, not expected`); - break; - } - }); - }); }); diff --git a/web/client/actions/config.js b/web/client/actions/config.js index 4e740a8eca..b8a84dcd59 100644 --- a/web/client/actions/config.js +++ b/web/client/actions/config.js @@ -97,7 +97,7 @@ function loadMapInfo(mapId) { const mapSaveError = error => ({type: MAP_SAVE_ERROR, error}); -const mapSaved = () => ({type: MAP_SAVED}); +const mapSaved = (resourceId) => ({type: MAP_SAVED, resourceId}); module.exports = { LOAD_NEW_MAP, diff --git a/web/client/actions/currentMap.js b/web/client/actions/currentMap.js index 3eeb0dcb7a..6e396c5fa9 100644 --- a/web/client/actions/currentMap.js +++ b/web/client/actions/currentMap.js @@ -7,11 +7,8 @@ */ const EDIT_MAP = 'EDIT_MAP'; -const UPDATE_CURRENT_MAP = 'UPDATE_CURRENT_MAP'; const UPDATE_CURRENT_MAP_PERMISSIONS = 'UPDATE_CURRENT_MAP_PERMISSIONS'; const UPDATE_CURRENT_MAP_GROUPS = 'UPDATE_CURRENT_MAP_GROUPS'; -const ERROR_CURRENT_MAP = 'ERROR_CURRENT_MAP'; -const REMOVE_THUMBNAIL = 'REMOVE_THUMBNAIL'; const RESET_CURRENT_MAP = 'RESET_CURRENT_MAP'; const ADD_CURRENT_MAP_PERMISSION = 'ADD_CURRENT_MAP_PERMISSION'; @@ -23,15 +20,6 @@ function editMap(map, openModalProperties) { }; } -// update the thumbnail and the thumbnailData property of the currentMap -function updateCurrentMap(thumbnailData, thumbnail) { - return { - type: UPDATE_CURRENT_MAP, - thumbnail, - thumbnailData - }; -} - function updateCurrentMapPermissions(permissions) { return { type: UPDATE_CURRENT_MAP_PERMISSIONS, @@ -46,21 +34,6 @@ function updateCurrentMapGroups(groups) { }; } -function errorCurrentMap(errors, resourceId) { - return { - type: ERROR_CURRENT_MAP, - errors, - resourceId - }; -} - -function removeThumbnail(resourceId) { - return { - type: REMOVE_THUMBNAIL, - resourceId - }; -} - /** * reset current map , `RESET_CURRENT_MAP` * @memberof actions.maps @@ -81,9 +54,6 @@ function addCurrentMapPermission(rule) { module.exports = { EDIT_MAP, editMap, - UPDATE_CURRENT_MAP, updateCurrentMap, - ERROR_CURRENT_MAP, errorCurrentMap, - REMOVE_THUMBNAIL, removeThumbnail, UPDATE_CURRENT_MAP_PERMISSIONS, updateCurrentMapPermissions, UPDATE_CURRENT_MAP_GROUPS, updateCurrentMapGroups, RESET_CURRENT_MAP, resetCurrentMap, diff --git a/web/client/actions/maps.js b/web/client/actions/maps.js index d7f76b4bc1..8964cdb23f 100644 --- a/web/client/actions/maps.js +++ b/web/client/actions/maps.js @@ -7,35 +7,26 @@ */ const GeoStoreApi = require('../api/GeoStoreDAO'); -const {updateCurrentMapPermissions, updateCurrentMapGroups} = require('./currentMap'); -const ConfigUtils = require('../utils/ConfigUtils'); -const {userGroupSecuritySelector, userSelector} = require('../selectors/security'); -const {currentMapDetailsChangedSelector} = require('../selectors/currentmap'); -const {resetCurrentMap} = require('./currentMap'); -const {findIndex, isNil} = require('lodash'); +const {updateCurrentMapGroups} = require('./currentMap'); const MAPS_LIST_LOADED = 'MAPS_LIST_LOADED'; const MAPS_LIST_LOADING = 'MAPS_LIST_LOADING'; const MAPS_LIST_LOAD_ERROR = 'MAPS_LIST_LOAD_ERROR'; const MAPS_GET_MAP_RESOURCES_BY_CATEGORY = 'MAPS_GET_MAP_RESOURCES_BY_CATEGORY'; const MAPS_LOAD_MAP = 'MAPS_LOAD_MAP'; const MAP_UPDATING = 'MAP_UPDATING'; -const MAP_METADATA_UPDATED = 'MAP_METADATA_UPDATED'; const MAP_UPDATED = 'MAP_UPDATED'; const MAP_CREATED = 'MAP_CREATED'; const MAP_DELETING = 'MAP_DELETING'; const MAP_DELETED = 'MAP_DELETED'; const MAP_SAVED = 'MAP_SAVED'; const ATTRIBUTE_UPDATED = 'ATTRIBUTE_UPDATED'; -const PERMISSIONS_UPDATED = 'PERMISSIONS_UPDATED'; const THUMBNAIL_ERROR = 'THUMBNAIL_ERROR'; const MAP_ERROR = 'MAP_ERROR'; const SAVE_ALL = 'SAVE_ALL'; const DISPLAY_METADATA_EDIT = 'DISPLAY_METADATA_EDIT'; const RESET_UPDATING = 'RESET_UPDATING'; const SAVING_MAP = 'SAVING_MAP'; -const SAVE_MAP = 'SAVE_MAP'; const PERMISSIONS_LIST_LOADING = 'PERMISSIONS_LIST_LOADING'; -const PERMISSIONS_LIST_LOADED = 'PERMISSIONS_LIST_LOADED'; const MAPS_SEARCH_TEXT_CHANGED = 'MAPS_SEARCH_TEXT_CHANGED'; const SEARCH_FILTER_CHANGED = 'MAPS:SEARCH_FILTER_CHANGED'; const SET_SEARCH_FILTER = 'MAPS:SET_SEARCH_FILTER'; @@ -44,19 +35,13 @@ const LOAD_CONTEXTS = 'MAPS:LOAD_CONTEXTS'; const SET_CONTEXTS = 'MAPS:SET_CONTEXTS'; const LOADING = 'MAPS:LOADING'; const METADATA_CHANGED = 'METADATA_CHANGED'; -const TOGGLE_DETAILS_SHEET = 'MAPS:TOGGLE_DETAILS_SHEET'; -const TOGGLE_GROUP_PROPERTIES = 'MAPS:TOGGLE_GROUP_PROPERTIES'; -const TOGGLE_UNSAVED_CHANGES = 'MAPS:TOGGLE_UNSAVED_CHANGES'; +const SHOW_DETAILS_SHEET = 'MAPS:SHOW_DETAILS_SHEET'; +const HIDE_DETAILS_SHEET = 'MAPS:HIDE_DETAILS_SHEET'; const UPDATE_DETAILS = 'MAPS:UPDATE_DETAILS'; -const SAVE_DETAILS = 'MAPS:SAVE_DETAILS'; -const DELETE_DETAILS = 'MAPS:DELETE_DETAILS'; -const SET_DETAILS_CHANGED = 'MAPS:SET_DETAILS_CHANGED'; const SHOW_DETAILS = 'MAPS:SHOW_DETAILS'; const SAVE_RESOURCE_DETAILS = 'MAPS:SAVE_RESOURCE_DETAILS'; const DO_NOTHING = 'MAPS:DO_NOTHING'; const DELETE_MAP = 'MAPS:DELETE_MAP'; -const BACK_DETAILS = 'MAPS:BACK_DETAILS'; -const UNDO_DETAILS = 'MAPS:UNDO_DETAILS'; const SET_UNSAVED_CHANGES = 'MAPS:SET_UNSAVED_CHANGES'; const OPEN_DETAILS_PANEL = 'DETAILS:OPEN_DETAILS_PANEL'; const CLOSE_DETAILS_PANEL = 'DETAILS:CLOSE_DETAILS_PANEL'; @@ -66,9 +51,7 @@ const DETAILS_SAVING = 'DETAILS:DETAILS_SAVING'; const NO_DETAILS_AVAILABLE = "NO_DETAILS_AVAILABLE"; const FEATURED_MAPS_SET_ENABLED = "FEATURED_MAPS:SET_ENABLED"; const SAVE_MAP_RESOURCE = "SAVE_MAP_RESOURCE"; -const FEATURED_MAPS_SET_LATEST_RESOURCE = "FEATURED_MAPS:SET_LATEST_RESOURCE"; - -const { success } = require('./notifications'); +const INVALIDATE_FEATURED_MAPS = "FEATURED_MAPS:INVALIDATE"; /** * saves details section in the resurce map on geostore @@ -297,43 +280,6 @@ function toggleDetailsEditability() { }; } -/** - * When metadata of a map are updated - * @memberof actions.maps - * @param {number} resourceId the id of the resourceId - * @param {string} newName the new name of the map - * @param {string} newDescription the new description of the map - * @param {string} result the result, can be "success" - * @param {object} [error] an error, if any - * @return {action} of type `MAP_METADATA_UPDATED` with the arguments as they are named - */ -function mapMetadataUpdated(resourceId, newName, newDescription, result, error) { - return { - type: MAP_METADATA_UPDATED, - resourceId, - newName, - newDescription, - result, - error - }; -} - -/** - * When permission of a map are updated - * @memberof actions.maps - * - * @param {number} resourceId the id of the resourceId - * @param {object} [error] error, if any - * @return {action} `PERMISSIONS_UPDATED` with the arguments as they are named - */ -function permissionsUpdated(resourceId, error) { - return { - type: PERMISSIONS_UPDATED, - resourceId, - error - }; -} - /** * When a map delete action have been performed * @memberof actions.maps @@ -418,20 +364,6 @@ function mapError(resourceId, error) { }; } -/** - * Performed when a map has been saved - * @memberof actions.maps - * @param {object} map The map - * @param {number} resourceId the identifier of the new map - * @return {action} type `SAVE_MAP`, with the arguments as they are named - */ -function saveMap(map, resourceId) { - return { - type: SAVE_MAP, - resourceId, - map - }; -} /** * Performed before start saving a new map * @memberof actions.maps @@ -457,68 +389,6 @@ function onDisplayMetadataEdit(displayMetadataEditValue) { displayMetadataEditValue }; } -/** - * resets the updating status for a resource - * @memberof actions.maps - * @param {number} resourceId the id of the resource - */ -function resetUpdating(resourceId) { - return { - type: RESET_UPDATING, - resourceId - }; -} -/** - * When the permission list is loading - * @memberof actions.maps - * @param {number} mapId the id of the resource - * @return {action} type `PERMISSIONS_LIST_LOADING`, with the arguments as they are named - */ -function permissionsLoading(mapId) { - return { - type: PERMISSIONS_LIST_LOADING, - mapId - }; -} - -/** - * When the permission list has been loaded - * @memberof actions.maps - * @param {array} permissions the permission - * @param {number} mapId the id of the resource - * @return {action} type `PERMISSIONS_LIST_LOADED`, with the arguments as they are named - */ -function permissionsLoaded(permissions, mapId) { - return { - type: PERMISSIONS_LIST_LOADED, - permissions, - mapId - }; -} - - -/** - * perform permission load for a mapId - * @memberof actions.maps - * @param {number} mapId the id of the map for the permission - * @return {thunk} dispatches permissionsLoaded, updateCurrentMapPermissions or loadError - */ -function loadPermissions(mapId) { - if (!mapId) { - return { - type: 'NONE' - }; - } - return (dispatch) => { - dispatch(permissionsLoading(mapId)); - GeoStoreApi.getPermissions(mapId, {}).then((response) => { - dispatch(permissionsLoaded(response, mapId)); - dispatch(updateCurrentMapPermissions(response)); - }).catch(() => { - dispatch(permissionsLoaded(null, mapId)); - }); - }; -} /** * load the available goups for a new permission rule. @@ -536,54 +406,6 @@ function loadAvailableGroups(user) { }; } -/** - * updates metadata for a map - * @memberof actions.maps - * @param {number} resourceId the id of the map to updates - * @param {string} newName the new name for the map - * @param {string} newDescription the new description for the map - * @param {action} [onReset] an action to dispatch after save, if present, to reset the current map - * @param {object} [options] the request options, if any - * @return {thunk} updates metadata and diepatches mapMetadataUpdated, onReset action (argument), resetCurrentMap or thumbnailError - */ -function updateMapMetadata(resourceId, newName, newDescription, onReset, options) { - return (dispatch) => { - GeoStoreApi.putResourceMetadata(resourceId, newName, newDescription, options).then(() => { - dispatch(mapMetadataUpdated(resourceId, newName, newDescription, "success")); - dispatch(success({ title: "success", message: "resources.successSaved" })); - if (onReset) { - dispatch(onReset); - dispatch(onDisplayMetadataEdit(false)); - dispatch(resetCurrentMap()); - } - }).catch((e) => { - dispatch(mapError(resourceId, e)); - }); - }; -} - -/** - * 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 or thumbnailError - */ -function updatePermissions(resourceId, securityRules) { - if (!securityRules || !securityRules.SecurityRuleList || !securityRules.SecurityRuleList.SecurityRule) { - return { - type: "NONE" - }; - } - return (dispatch) => { - GeoStoreApi.updateResourcePermissions(resourceId, securityRules).then(() => { - dispatch(permissionsUpdated(resourceId, "success")); - }).catch((e) => { - dispatch(thumbnailError(resourceId, e)); - }); - }; -} - /** * updates an attribute for a given map * @memberof actions.maps @@ -604,131 +426,6 @@ function updateAttribute(resourceId, name, value, type, options) { }; } -/** - * Creates the thumbnail for the map. - * @memberof actions.maps - * @param {object} map the map - * @param {object} metadataMap the map metadataMap - * @param {string} nameThumbnail the name for the thumbnail - * @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 - * @return {thunk} perform the thumb creation and dispatch proper actions - */ -function createThumbnail(map, metadataMap, nameThumbnail, dataThumbnail, categoryThumbnail, resourceIdMap, onSuccess, onReset, options) { - return (dispatch, getState) => { - let metadata = { - name: nameThumbnail - }; - let state = getState(); - return GeoStoreApi.createResource(metadata, dataThumbnail, categoryThumbnail, options).then((response) => { - let groups = userGroupSecuritySelector(state); - let index = findIndex(groups, function(g) { return g.groupName === "everyone"; }); - let group; - if (index < 0 && groups && groups.groupName === "everyone") { - group = groups; - } else { - group = groups[index]; - } - let user = userSelector(state); - let userPermission = { - canRead: true, - canWrite: true - }; - let groupPermission = { - canRead: true, - canWrite: false - }; - dispatch(updatePermissions(response.data, groupPermission, group, userPermission, user, options)); // UPDATE resource permissions - const thumbnailUrl = ConfigUtils.getDefaults().geoStoreUrl + "data/" + response.data + "/raw?decode=datauri"; - dispatch(updateAttribute(resourceIdMap, "thumbnail", thumbnailUrl, "STRING", options)); // UPDATE resource map with new attribute - if (onSuccess) { - dispatch(onSuccess); - } - if (onReset) { - dispatch(onReset); - dispatch(resetCurrentMap()); - } - dispatch(saveMap(map, resourceIdMap)); - dispatch(thumbnailError(resourceIdMap, null)); - }).catch((e) => { - dispatch(thumbnailError(resourceIdMap, e)); - }); - }; -} - -/** - * Save all the metadata and thumbnail, if needed. - * @memberof actions.maps - * @param {object} map the map object - * @param {object} metadataMap metadata for the map - * @param {string} nameThumbnail the name for the thumbnail - * @param {string} dataThumbnail the data to save for the thumbnail - * @param {string} categoryThumbnail the category for the thumbnails - * @param {number} resourceIdMap the id of the map - * @param {object} [options] options for the request - * @return {thunk} perform the update and dispatch proper actions - */ -function saveAll(map, metadataMap, nameThumbnail, dataThumbnail, categoryThumbnail, resourceIdMap, options) { - return (dispatch, getState) => { - dispatch(mapUpdating(resourceIdMap)); - dispatch(updatePermissions(resourceIdMap)); - const detailsChanged = currentMapDetailsChangedSelector(getState()); - if (detailsChanged) { - dispatch(saveResourceDetails()); - } - if (!isNil(dataThumbnail) && !isNil(metadataMap)) { - dispatch(createThumbnail(map, metadataMap, nameThumbnail, dataThumbnail, categoryThumbnail, resourceIdMap, - updateMapMetadata(resourceIdMap, metadataMap.name, metadataMap.description, !detailsChanged ? onDisplayMetadataEdit(false) : null, options), null, options, detailsChanged)); - } else if (!isNil(dataThumbnail)) { - dispatch(createThumbnail(map, metadataMap, nameThumbnail, dataThumbnail, categoryThumbnail, resourceIdMap, null, !detailsChanged ? onDisplayMetadataEdit(false) : null, options)); - } else if (!isNil(metadataMap)) { - dispatch(updateMapMetadata(resourceIdMap, metadataMap.name, metadataMap.description, !detailsChanged ? onDisplayMetadataEdit(false) : null, options)); - } - if (isNil(dataThumbnail) && isNil(metadataMap) && !detailsChanged) { - dispatch(resetUpdating(resourceIdMap)); - } - }; -} - -/** - * Deletes a thumbnail. - * @memberof actions.maps - * @param {number} resourceId the id of the thumbnail - * @param {number} resourceIdMap the id of the map - * @param {object} [options] options for the request, if any - * @return {thunk} performs thumbnail cancellation - */ -function deleteThumbnail(resourceId, resourceIdMap, options, reset) { - return (dispatch) => { - dispatch(mapUpdating(resourceIdMap)); - GeoStoreApi.deleteResource(resourceId, options).then(() => { - if (resourceIdMap) { - dispatch(updateAttribute(resourceIdMap, "thumbnail", "NODATA", "STRING", options)); - if (reset) { - dispatch(resetUpdating(resourceIdMap)); - } - } - }).catch((e) => { - // Even if is not possible to delete the Thumbnail from geostore -> reset the attribute in order to display the default thumbnail - if (e.status === 403) { - if (resourceIdMap) { - dispatch(updateAttribute(resourceIdMap, "thumbnail", "NODATA", "STRING", options)); - } - dispatch(onDisplayMetadataEdit(false)); - dispatch(resetCurrentMap()); - dispatch(thumbnailError(resourceIdMap, null)); - } else { - dispatch(onDisplayMetadataEdit(true)); - dispatch(thumbnailError(resourceIdMap, e)); - } - }); - }; -} - /** * Deletes a map. * @memberof actions.maps @@ -744,37 +441,6 @@ function deleteMap(resourceId, options) { }; } -/** - * Toggles details modal - * @memberof actions.maps - * @return {action} type `TOGGLE_DETAILS_SHEET` -*/ -function toggleDetailsSheet(detailsSheetReadOnly) { - return { - type: TOGGLE_DETAILS_SHEET, - detailsSheetReadOnly - }; -} -/** - * Toggles groups properties section - * @memberof actions.maps - * @return {action} type `TOGGLE_GROUP_PROPERTIES` -*/ -function toggleGroupProperties() { - return { - type: TOGGLE_GROUP_PROPERTIES - }; -} -/** - * Toggles unsaved changes modal - * @memberof actions.maps - * @return {action} type `TOGGLE_UNSAVED_CHANGES` -*/ -function toggleUnsavedChanges() { - return { - type: TOGGLE_UNSAVED_CHANGES - }; -} /** * updates details section * @memberof actions.maps @@ -789,62 +455,6 @@ function updateDetails(detailsText, doBackup, originalDetails) { }; } -/** - * saves details section in the map state - * @memberof actions.maps - * @prop {string} detailsText string generated from html - * @return {action} type `SAVE_DETAILS` -*/ -function saveDetails(detailsText) { - return { - type: SAVE_DETAILS, - detailsText - }; -} - -/** - * deletes details section in the map state - * @memberof actions.maps - * @return {action} type `DELETE_DETAILS` -*/ -function deleteDetails() { - return { - type: DELETE_DETAILS - }; -} -/** - * set unsaved changes in the current map state, type `SET_DETAILS_CHANGED` - * @memberof actions.maps - * @prop {boolean} detailsChanged flag used to trigger the opening of the unsavedChangesModal - * @return {action} type `SET_DETAILS_CHANGED` -*/ -function setDetailsChanged(detailsChanged) { - return { - type: SET_DETAILS_CHANGED, - detailsChanged - }; -} -/** - * back details - * @memberof actions.maps - * @return {action} type `BACK_DETAILS` -*/ -function backDetails(backupDetails) { - return { - type: BACK_DETAILS, - backupDetails - }; -} -/** - * undo details - * @memberof actions.maps - * @return {action} type `UNDO_DETAILS` -*/ -function undoDetails() { - return { - type: UNDO_DETAILS - }; -} /** * setUnsavedChanged * @memberof actions.maps @@ -929,13 +539,25 @@ const saveMapResource = (resource) => ({ resource }); /** - * Set the latestResource prop of featuredmaps + * Invalidate featured maps list * @memberof actions.maps - * @param {boolean} enabled the `enabled` flag */ -const setFeaturedMapsLatestResource = (resource) => ({ - type: FEATURED_MAPS_SET_LATEST_RESOURCE, - resource +const invalidateFeaturedMaps = () => ({ + type: INVALIDATE_FEATURED_MAPS +}); +/** + * Shows the read only details sheet + * @memberof actions.maps + */ +const showDetailsSheet = () => ({ + type: SHOW_DETAILS_SHEET +}); +/** + * Hides the read only details sheet + * @memberof actions.maps + */ +const hideDetailsSheet = () => ({ + type: HIDE_DETAILS_SHEET }); /** @@ -948,18 +570,14 @@ module.exports = { MAPS_LIST_LOAD_ERROR, MAP_CREATED, MAP_UPDATING, - MAP_METADATA_UPDATED, MAP_UPDATED, MAP_DELETED, MAP_DELETING, MAP_SAVED, ATTRIBUTE_UPDATED, - PERMISSIONS_UPDATED, - SAVE_MAP, SAVING_MAP, THUMBNAIL_ERROR, PERMISSIONS_LIST_LOADING, - PERMISSIONS_LIST_LOADED, SAVE_ALL, DISPLAY_METADATA_EDIT, RESET_UPDATING, @@ -968,17 +586,8 @@ module.exports = { METADATA_CHANGED, NO_DETAILS_AVAILABLE, SAVE_MAP_RESOURCE, - FEATURED_MAPS_SET_LATEST_RESOURCE, - toggleDetailsSheet, TOGGLE_DETAILS_SHEET, - toggleGroupProperties, TOGGLE_GROUP_PROPERTIES, - toggleUnsavedChanges, TOGGLE_UNSAVED_CHANGES, updateDetails, UPDATE_DETAILS, - saveDetails, SAVE_DETAILS, - deleteDetails, DELETE_DETAILS, - setDetailsChanged, SET_DETAILS_CHANGED, saveResourceDetails, SAVE_RESOURCE_DETAILS, - backDetails, BACK_DETAILS, - undoDetails, UNDO_DETAILS, doNothing, DO_NOTHING, setUnsavedChanged, SET_UNSAVED_CHANGES, openDetailsPanel, OPEN_DETAILS_PANEL, @@ -1003,28 +612,18 @@ module.exports = { mapCreated, mapDeleted, mapDeleting, - updateMapMetadata, - mapMetadataUpdated, - deleteThumbnail, - createThumbnail, mapUpdating, - updatePermissions, - permissionsUpdated, - permissionsLoading, - permissionsLoaded, attributeUpdated, savingMap, - saveMap, thumbnailError, loadError, - loadPermissions, loadAvailableGroups, - saveAll, onDisplayMetadataEdit, - resetUpdating, mapError, mapsSearchTextChanged, updateAttribute, saveMapResource, - setFeaturedMapsLatestResource + invalidateFeaturedMaps, + showDetailsSheet, SHOW_DETAILS_SHEET, + hideDetailsSheet, HIDE_DETAILS_SHEET }; diff --git a/web/client/components/map/openlayers/Map.jsx b/web/client/components/map/openlayers/Map.jsx index d50f233818..c759deb802 100644 --- a/web/client/components/map/openlayers/Map.jsx +++ b/web/client/components/map/openlayers/Map.jsx @@ -77,6 +77,11 @@ class OpenlayersMap extends React.Component { onResolutionsChange: () => { }, onCreationError: () => { }, onClick: null, + center: { + x: 0, + y: 0, + crs: 'EPSG:4326' + }, onMouseMove: () => { }, mapOptions: {}, projection: 'EPSG:3857', diff --git a/web/client/components/maps/MapCard.jsx b/web/client/components/maps/MapCard.jsx index 8181ec86de..ae191efdb0 100644 --- a/web/client/components/maps/MapCard.jsx +++ b/web/client/components/maps/MapCard.jsx @@ -20,7 +20,6 @@ class MapCard extends React.Component { style: PropTypes.object, map: PropTypes.object, showMapDetails: PropTypes.bool, - detailsSheetActions: PropTypes.object, shareToolEnabled: PropTypes.bool, // CALLBACKS viewerUrl: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), @@ -28,6 +27,7 @@ class MapCard extends React.Component { onMapDelete: PropTypes.func, onShare: PropTypes.func, onUpdateAttribute: PropTypes.func, + onShowDetailsSheet: PropTypes.func, backgroundOpacityStart: PropTypes.number, backgroundOpacityEnd: PropTypes.number, tooltips: PropTypes.object @@ -45,14 +45,12 @@ class MapCard extends React.Component { backgroundPosition: "center", backgroundRepeat: "repeat-x" }, - detailsSheetActions: { - onToggleDetailsSheet: () => {} - }, shareToolEnabled: true, // CALLBACKS onMapDelete: ()=> {}, onEdit: () => {}, onUpdateAttribute: () => {}, + onShowDetailsSheet: () => {}, backgroundOpacityStart: 0.7, backgroundOpacityEnd: 0.3, tooltips: { @@ -146,7 +144,7 @@ class MapCard extends React.Component { onClick: evt => { this.stopPropagate(evt); this.onEdit(this.props.map, false); - this.props.detailsSheetActions.onToggleDetailsSheet(true); + this.props.onShowDetailsSheet(); } }, { diff --git a/web/client/components/maps/MapGrid.jsx b/web/client/components/maps/MapGrid.jsx index 26fd9c21a7..c567648c5b 100644 --- a/web/client/components/maps/MapGrid.jsx +++ b/web/client/components/maps/MapGrid.jsx @@ -21,29 +21,25 @@ class MapGrid extends React.Component { showMapDetails: PropTypes.bool, maps: PropTypes.array, currentMap: PropTypes.object, + user: PropTypes.object, fluid: PropTypes.bool, showAPIShare: PropTypes.bool, viewerUrl: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), mapType: PropTypes.string, colProps: PropTypes.object, // CALLBACKS - updateMapMetadata: PropTypes.func, editMap: PropTypes.func, - saveAll: PropTypes.func, - saveMap: PropTypes.func, onDisplayMetadataEdit: PropTypes.func, - removeThumbnail: PropTypes.func, - errorCurrentMap: PropTypes.func, - updateCurrentMap: PropTypes.func, - detailsSheetActions: PropTypes.object, - createThumbnail: PropTypes.func, - deleteThumbnail: PropTypes.func, deleteMap: PropTypes.func, onShare: PropTypes.func, resetCurrentMap: PropTypes.func, - updatePermissions: PropTypes.func, metadataModal: PropTypes.func, onUpdateAttribute: PropTypes.func, + onSaveSuccess: PropTypes.func, + onSaveError: PropTypes.func, + onMapSaved: PropTypes.func, + onShowDetailsSheet: PropTypes.func, + onHideDetailsSheet: PropTypes.func, title: PropTypes.node, className: PropTypes.string, style: PropTypes.object @@ -63,33 +59,15 @@ class MapGrid extends React.Component { maps: [], // CALLBACKS onChangeMapType: function() {}, - updateMapMetadata: () => {}, - detailsSheetActions: { - onBackDetails: () => {}, - onUndoDetails: () => {}, - onToggleDetailsSheet: () => {}, - onToggleGroupProperties: () => {}, - onToggleUnsavedChangesModal: () => {}, - onsetDetailsChanged: () => {}, - onUpdateDetails: () => {}, - onDeleteDetails: () => {}, - onSaveDetails: () => {} - }, - createThumbnail: () => {}, - deleteThumbnail: () => {}, - errorCurrentMap: () => {}, - saveAll: () => {}, onDisplayMetadataEdit: () => {}, - updateCurrentMap: () => {}, deleteMap: () => {}, onShare: () => {}, - saveMap: () => {}, - removeThumbnail: () => {}, editMap: () => {}, resetCurrentMap: () => {}, - updatePermissions: () => {}, groups: [], onUpdateAttribute: () => {}, + onSaveSuccess: () => {}, + onSaveError: () => {}, className: '', style: {} }; @@ -104,10 +82,10 @@ class MapGrid extends React.Component { map={map} onEdit={this.props.editMap} showMapDetails={this.props.showMapDetails} - detailsSheetActions={this.props.detailsSheetActions} onMapDelete={this.props.deleteMap} onShare={this.props.onShare} showAPIShare={this.props.showAPIShare} + onShowDetailsSheet={this.props.onShowDetailsSheet} onUpdateAttribute={this.props.onUpdateAttribute}/> ; }); @@ -120,18 +98,27 @@ class MapGrid extends React.Component { renderMetadataModal = () => { if (this.props.metadataModal) { let MetadataModal = this.props.metadataModal; - return (); + return ( { + this.props.onSaveSuccess(); + this.props.onMapSaved(); + }} + onSaveError={() => { + this.props.onSaveError(); + this.props.onMapSaved(this.props.currentMap?.id); + }} + onClose={() => { + this.props.onDisplayMetadataEdit(false); + this.props.resetCurrentMap(); + }}/>); } return null; }; diff --git a/web/client/components/maps/__tests__/MapCard-test.jsx b/web/client/components/maps/__tests__/MapCard-test.jsx index 47981f9220..5dc9e1d875 100644 --- a/web/client/components/maps/__tests__/MapCard-test.jsx +++ b/web/client/components/maps/__tests__/MapCard-test.jsx @@ -57,11 +57,11 @@ describe('This test for MapCard', () => { const testDescription = "testDescription"; let component = TestUtils.renderIntoDocument(); const handlers = { - onToggleDetailsSheet: () => {}, + onShowDetailsSheet: () => {}, onEdit: () => {} }; - let spy = expect.spyOn(handlers, "onToggleDetailsSheet"); - component = TestUtils.renderIntoDocument(); + let spy = expect.spyOn(handlers, "onShowDetailsSheet"); + component = TestUtils.renderIntoDocument(); const detailsTool = TestUtils.findRenderedDOMComponentWithTag( component, 'button' ); diff --git a/web/client/components/maps/modals/MetadataModal.jsx b/web/client/components/maps/modals/MetadataModal.jsx deleted file mode 100644 index 7c6699ee6d..0000000000 --- a/web/client/components/maps/modals/MetadataModal.jsx +++ /dev/null @@ -1,500 +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 PropTypes = require('prop-types'); -const React = require('react'); -const Metadata = require('../forms/Metadata'); -const Thumbnail = require('../forms/Thumbnail'); -const PermissionEditor = require('../../security/PermissionEditor'); -const ReactQuill = require('react-quill'); -const Portal = require('../../misc/Portal'); -const ResizableModal = require('../../misc/ResizableModal'); -require('react-quill/dist/quill.snow.css'); -require('./css/modals.css'); -const Spinner = require('react-spinkit'); -const { Grid, Row, Col } = require('react-bootstrap'); -const { get, isNil } = require('lodash'); -const Message = require('../../I18N/Message'); -const Toolbar = require('../../misc/toolbar/Toolbar'); -const { NO_DETAILS_AVAILABLE } = require('../../../actions/maps'); -const LocaleUtils = require('../../../utils/LocaleUtils'); -const ConfirmModal = require('../../resources/modals/ConfirmModal'); - - -/** - * A Modal window to show map metadata form -*/ -class MetadataModal extends React.Component { - static propTypes = { - // props - id: PropTypes.string, - user: PropTypes.object, - authHeader: PropTypes.string, - show: PropTypes.bool, - options: PropTypes.object, - modules: PropTypes.object, - metadata: PropTypes.object, - loadPermissions: PropTypes.func, - loadAvailableGroups: PropTypes.func, - onSave: PropTypes.func, - detailsSheetActions: PropTypes.object, - onCreateThumbnail: PropTypes.func, - onDeleteThumbnail: PropTypes.func, - onGroupsChange: PropTypes.func, - onAddPermission: PropTypes.func, - onResetCurrentMap: PropTypes.func, - useModal: PropTypes.bool, - closeGlyph: PropTypes.string, - buttonSize: PropTypes.string, - includeCloseButton: PropTypes.bool, - showDetailsRow: PropTypes.bool, - map: PropTypes.object, - style: PropTypes.object, - fluid: PropTypes.bool, - modalSize: PropTypes.string, - // I18N - errorImage: PropTypes.oneOfType([PropTypes.string, PropTypes.element]), - errorMessages: PropTypes.object, - // CALLBACKS - onSaveAll: PropTypes.func, - onRemoveThumbnail: PropTypes.func, - onErrorCurrentMap: PropTypes.func, - onUpdateCurrentMap: PropTypes.func, - onDisplayMetadataEdit: PropTypes.func, - onNewGroupChoose: PropTypes.func, - onNewPermissionChoose: PropTypes.func, - metadataChanged: PropTypes.func, - displayPermissionEditor: PropTypes.bool, - availablePermissions: PropTypes.arrayOf(PropTypes.string), - availableGroups: PropTypes.arrayOf(PropTypes.object), - updatePermissions: PropTypes.func, - groups: PropTypes.arrayOf(PropTypes.object), - newGroup: PropTypes.object, - newPermission: PropTypes.string - }; - - static contextTypes = { - messages: PropTypes.object - }; - - static defaultProps = { - id: "MetadataModal", - modalSize: "", - loadPermissions: () => { }, - loadAvailableGroups: () => { }, - onSave: () => { }, - detailsSheetActions: { - onBackDetails: () => { }, - onUndoDetails: () => { }, - onToggleGroupProperties: () => { }, - onToggleDetailsSheet: () => { }, - onUpdateDetails: () => { }, - onDeleteDetails: () => { }, - onSaveDetails: () => { } - }, - onCreateThumbnail: () => { }, - onDeleteThumbnail: () => { }, - onGroupsChange: () => { }, - onAddPermission: () => { }, - onDisplayMetadataEdit: () => { }, - metadataChanged: () => { }, - onNewGroupChoose: () => { }, - onNewPermissionChoose: () => { }, - user: { - name: "Guest" - }, - metadata: { name: "", description: "" }, - modules: { - toolbar: [ - [{ 'size': ['small', false, 'large', 'huge'] }, 'bold', 'italic', 'underline', 'blockquote'], - [{ 'list': 'bullet' }, { 'align': [] }], - [{ 'color': [] }, { 'background': [] }, 'clean'], ['image', 'link'] - ] - }, - options: {}, - useModal: true, - closeGlyph: "", - style: {}, - buttonSize: "small", - includeCloseButton: true, - showDetailsRow: true, - fluid: true, - // CALLBACKS - onErrorCurrentMap: () => { }, - onUpdateCurrentMap: () => { }, - onSaveAll: () => { }, - onRemoveThumbnail: () => { }, - onSaveMap: () => { }, - onResetCurrentMap: () => { }, - // I18N - errorMessages: { "FORMAT": , "SIZE": }, - errorImage: , - displayPermissionEditor: true, - availablePermissions: ["canRead", "canWrite"], - availableGroups: [], - updatePermissions: () => { }, - groups: [] - }; - - state = { - name: this.props.map && this.props.map.name || '', - description: this.props.map && this.props.map.description || '' - }; - - componentWillReceiveProps(nextProps) { - if (nextProps.map && this.props.map && !nextProps.map.loading && this.state && this.state.saving) { - this.setState({ - saving: false - }); - // this.props.onResetCurrentMap(); - } - } - - componentDidUpdate(prevProps) { - if (this.props.show && !prevProps.show) { - if (this.props.displayPermissionEditor && (this.props.user.name === this.props.map.owner || this.props.user.role === "ADMIN")) { - this.loadPermissions(); - this.loadAvailableGroups(); - } - } - } - - onCloseMapPropertiesModal = () => { - // TODO write only a single function used also in onClose property - if (this.props.map.unsavedChanges && this.props.detailsSheetActions.onToggleUnsavedChangesModal) { - this.props.detailsSheetActions.onToggleUnsavedChangesModal(); - } else { - this.props.onDisplayMetadataEdit(false); - this.props.onResetCurrentMap(); - } - } - - onSave = () => { - this.setState({ - saving: true - }); - let metadata = null; - - if (this.isMetadataChanged()) { - let name = this.props.metadata.name; - let description = this.props.metadata.description; - metadata = { - name: name, - description: description - }; - this.props.onSave(this.props.map.id, name, description); - } - this.refs.thumbnail.processUpdateThumbnail(this.props.map, metadata, this.props.map.thumbnailData); - this.props.updatePermissions(this.props.map.id, this.props.map.permissions); - }; - - renderDetailsSheet = (readOnly) => { - return ( - - {readOnly ? ( - { - this.props.onResetCurrentMap(); - }} - title={} - show - > -
- {!this.props.map.detailsText ? :
} -
- - ) : (} - bodyClassName="ms-modal-quill-container" - size="lg" - clickOutEnabled={false} - showFullscreen - fullscreenType="full" - onClose={() => { this.props.detailsSheetActions.onBackDetails(this.props.map.detailsBackup); }} - buttons={[{ - text: , - onClick: () => { - this.props.detailsSheetActions.onBackDetails(this.props.map.detailsBackup); - } - }, { - text: , - onClick: () => { - this.props.detailsSheetActions.onSaveDetails(this.props.map.detailsText); - } - }]}> -
- { - if (details && details !== '


') { - this.props.detailsSheetActions.onUpdateDetails(details, false); - } - }} - modules={this.props.modules} /> -
-
)} - ); - } - /** - * @return the modal for unsaved changes - */ - renderUnsavedChanges = () => { - return ( - } - cancelText={} - onConfirm={() => this.props.onResetCurrentMap()} - onClose={() => { - if (this.props.detailsSheetActions.onToggleUnsavedChangesModal) { - this.props.detailsSheetActions.onToggleUnsavedChangesModal(); - } - this.props.onDisplayMetadataEdit(true); - }} - > - -
- -
); - } - renderDetailsRow = () => { - return ( -
-
- - -
- {this.props.map.detailsText === "" ? : } -
- - -
-
- {this.props.map.saving ? : null} - {isNil(this.props.map.detailsText) ? : { this.props.detailsSheetActions.onToggleGroupProperties(); }, - disabled: this.props.map.saving, - tooltipId: !this.props.map.hideGroupProperties ? "map.details.showPreview" : "map.details.hidePreview" - }, { - glyph: 'undo', - tooltipId: "map.details.undo", - visible: !!this.props.map.detailsBackup, - onClick: () => { this.props.detailsSheetActions.onUndoDetails(this.props.map.detailsBackup); }, - disabled: this.props.map.saving - }, { - glyph: 'pencil-add', - tooltipId: "map.details.add", - visible: !this.props.map.detailsText, - onClick: () => { - this.props.detailsSheetActions.onToggleDetailsSheet(false); - }, - disabled: this.props.map.saving - }, { - glyph: 'pencil', - tooltipId: "map.details.edit", - visible: !!this.props.map.detailsText && !this.props.map.editDetailsDisabled, - onClick: () => { - this.props.detailsSheetActions.onToggleDetailsSheet(false); - if (this.props.map.detailsText) { - this.props.detailsSheetActions.onUpdateDetails(this.props.map.detailsText, true); - } - }, - disabled: this.props.map.saving - }, { - glyph: 'trash', - tooltipId: "map.details.delete", - visible: !!this.props.map.detailsText, - onClick: () => { this.props.detailsSheetActions.onDeleteDetails(); }, - disabled: this.props.map.saving - }]} />} -
-
- -
-
- {this.props.map.detailsText &&
- {this.props.map.detailsText !== NO_DETAILS_AVAILABLE ?
- :
} -
} -
- ); - } - renderPermissionEditor = () => { - if (this.props.displayPermissionEditor && this.props.user.name === this.props.map.owner || this.props.user.role === "ADMIN") { - // Hack to convert map permissions to a simpler format, TODO: remove this - if (this.props.map && this.props.map.permissions && this.props.map.permissions.SecurityRuleList && this.props.map.permissions.SecurityRuleList.SecurityRule) { - this.localGroups = this.props.map.permissions.SecurityRuleList.SecurityRule.map(function(rule) { - if (rule && rule.group && rule.canRead) { - return { name: rule.group.groupName, permission: rule.canWrite ? "canWrite" : "canRead" }; - } - return null; - } - ).filter(rule => rule); // filter out undefined values - } else { - this.localGroups = this.props.groups; - } - return ( - - ); - } - return null; - }; - renderMapProperties = () => { - const mapErrorStatus = this.props.map && this.props.map.mapError && this.props.map.mapError.status ? this.props.map.mapError.status : null; - let messageIdMapError = ""; - if (mapErrorStatus === 404 || mapErrorStatus === 403 || mapErrorStatus === 409) { - messageIdMapError = mapErrorStatus; - } else { - messageIdMapError = "Default"; - } - const thumbnailErrorStatus = this.props.map && this.props.map.thumbnailError && this.props.map.thumbnailError.status ? this.props.map.thumbnailError.status : null; - let messageIdError = ""; - if (thumbnailErrorStatus === 404 || thumbnailErrorStatus === 403 || thumbnailErrorStatus === 409) { - messageIdError = thumbnailErrorStatus; - } else { - messageIdError = "Default"; - } - return ( - } - show={this.props.show && !this.props.map.showDetailEditor && !this.props.map.showUnsavedChanges} - clickOutEnabled - bodyClassName="ms-flex modal-properties-container" - buttons={[{ - text: , - onClick: this.onCloseMapPropertiesModal, - disabled: this.props.map.saving - }, { - text: , - onClick: () => { this.onSave(); }, - disabled: this.props.map.saving || !this.isValidForm() - }]} - showClose={!this.props.map.saving} - onClose={this.onCloseMapPropertiesModal}> - -
- {/* TODO fix this error messages*/} - - {this.props.map && this.props.map.mapError ? -
-
- -
-
- : null} - {this.props.map && this.props.map.errors && this.props.map.errors.length > 0 ? -
-

{this.props.errorImage}

- {(this.props.map.errors.map((error) =>
{this.props.errorMessages[error]}
))} -
- : null} - {this.props.map && this.props.map.thumbnailError ? -
-
- -
-
- : null} -
- - - - - - } - descriptionFieldText={} - createdAtFieldText={} - modifiedAtFieldText={} - namePlaceholderText={LocaleUtils.getMessageById(this.context.messages, "map.namePlaceholder") || "Map Name"} - descriptionPlaceholderText={LocaleUtils.getMessageById(this.context.messages, "map.descriptionPlaceholder") || "Map Description"} - /> - - - {this.props.showDetailsRow && get(this.props.map, "category.name") === "MAP" ? this.renderDetailsRow() : null} - - {!this.props.map.hideGroupProperties && this.props.displayPermissionEditor && this.renderPermissionEditor()} - -
-
-
); - } - // TODO restore this - renderLoading = () => { - return this.props.map && this.props.map.updating ? : null; - }; - - render() { - return ( - - {this.props.map.showDetailEditor && this.renderDetailsSheet(this.props.map.detailsSheetReadOnly)} - {this.props.map.showUnsavedChanges && this.renderUnsavedChanges()} - {this.props.show && !this.props.map.showDetailEditor && this.renderMapProperties()} - ); - } - - loadAvailableGroups = () => { - this.props.loadAvailableGroups(this.props.user); - }; - - loadPermissions = () => { - this.props.loadPermissions(this.props.map.id); - }; - - updateThumbnail = () => { - this.refs.thumbnail.updateThumbnail(); - }; - - isMetadataChanged = () => { - return this.props.map && ( - this.props.metadata.description !== this.props.map.description || - this.props.metadata.name !== this.props.map.name - ); - }; - - isThumbnailChanged = () => { - return this.refs && this.refs.thumbnail && this.refs.thumbnail.files && this.refs.thumbnail.files.length > 0; - }; - - isValidForm = () => get(this.props.map, "metadata.name"); -} - -module.exports = MetadataModal; diff --git a/web/client/components/maps/modals/__tests__/MetaDataModal-test.jsx b/web/client/components/maps/modals/__tests__/MetaDataModal-test.jsx deleted file mode 100644 index 3b3e1a72d7..0000000000 --- a/web/client/components/maps/modals/__tests__/MetaDataModal-test.jsx +++ /dev/null @@ -1,330 +0,0 @@ -/** - * Copyright 2016, 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 React = require('react'); -var ReactDOM = require('react-dom'); -var MetadataModal = require('../MetadataModal.jsx'); -const ReactTestUtils = require('react-dom/test-utils'); -var expect = require('expect'); - -describe('This test for MetadataModal', () => { - - beforeEach((done) => { - document.body.innerHTML = '
'; - setTimeout(done); - }); - - afterEach((done) => { - ReactDOM.unmountComponentAtNode(document.getElementById("container")); - document.body.innerHTML = ''; - setTimeout(done); - }); - - // test DEFAULTS - it('creates the component with defaults, show=false', () => { - const metadataModalItem = ReactDOM.render(, document.getElementById("container")); - expect(metadataModalItem).toExist(); - - const metadataModalItemDom = ReactDOM.findDOMNode(metadataModalItem); - expect(metadataModalItemDom).toExist(); - const modalDivList = document.getElementsByClassName("modal-content"); - expect(modalDivList.length).toBe(0); - - const getModals = function() { - return document.getElementsByTagName("body")[0].getElementsByClassName('modal-dialog'); - }; - expect(getModals().length).toBe(0); - - }); - - it('creates the component with defaults, show=true', () => { - let thumbnail = "myThumnbnailUrl"; - let errors = ["FORMAT"]; - let map = { - thumbnail: thumbnail, - id: 123, - canWrite: true, - errors: errors - }; - - const metadataModalItem = ReactDOM.render(, document.getElementById("container")); - expect(metadataModalItem).toExist(); - - const modalDivList = document.getElementsByClassName("modal-content"); - const closeBtnList = modalDivList.item(0).getElementsByTagName('button'); - expect(closeBtnList.length).toBe(2); - }); - /* - * This checks if you can close the modal even if the function (confirm) is not defined. - * see https://github.com/geosolutions-it/MapStore2/issues/2576 - */ - it('Test MetadataModal onToggleUnsavedChangesModal only if present', () => { - let thumbnail = "myThumnbnailUrl"; - let errors = ["FORMAT"]; - let map = { - unsavedChanges: true, - thumbnail: thumbnail, - id: 123, - canWrite: true, - errors: errors - }; - const actions = { - onToggleUnsavedChangesModal: () => {}, - onDisplayMetadataEdit: () => {} - }; - const spyonToggleUnsavedChangesModal = expect.spyOn(actions, 'onToggleUnsavedChangesModal'); - const spyonDisplayMetadataEdit = expect.spyOn(actions, 'onDisplayMetadataEdit'); - const cmp = ReactDOM.render(, document.getElementById("container")); - expect(cmp).toExist(); - let el = document.querySelector('#ms-resizable-modal .btn-group button'); - expect(el).toExist(); - ReactTestUtils.Simulate.click(el); // <-- trigger event callback - expect(spyonToggleUnsavedChangesModal).toHaveBeenCalled(); - expect(spyonDisplayMetadataEdit).toNotHaveBeenCalled(); - ReactDOM.render(, document.getElementById("container")); - el = document.querySelector('#ms-resizable-modal .btn-group button'); - ReactTestUtils.Simulate.click(el); - expect(spyonDisplayMetadataEdit).toHaveBeenCalled(); - }); - - it('creates the component with a format error', () => { - let thumbnail = "myThumnbnailUrl"; - let errors = ["FORMAT"]; - let map = { - thumbnail: thumbnail, - id: 123, - canWrite: true, - errors: errors - }; - - const metadataModalItem = ReactDOM.render(, document.getElementById("container")); - expect(metadataModalItem).toExist(); - - const getModals = function() { - return document.getElementsByTagName("body")[0].getElementsByClassName('modal-dialog'); - }; - - expect(getModals().length).toBe(1); - - const modalDivList = document.getElementsByClassName("modal-content"); - const closeBtnList = modalDivList.item(0).getElementsByTagName('button'); - expect(closeBtnList.length).toBe(2); - - const errorFORMAT = modalDivList.item(0).getElementsByTagName('errorFORMAT'); - expect(errorFORMAT).toExist(); - }); - - it('creates the component with a size error', () => { - let thumbnail = "myThumnbnailUrl"; - let errors = ["SIZE"]; - let map = { - thumbnail: thumbnail, - id: 123, - canWrite: true, - errors: errors - }; - - const metadataModalItem = ReactDOM.render(, document.getElementById("container")); - expect(metadataModalItem).toExist(); - - const getModals = function() { - return document.getElementsByTagName("body")[0].getElementsByClassName('modal-dialog'); - }; - - expect(getModals().length).toBe(1); - - const modalDivList = document.getElementsByClassName("modal-content"); - const closeBtnList = modalDivList.item(0).getElementsByTagName('button'); - expect(closeBtnList.length).toBe(2); - - const errorFORMAT = modalDivList.item(0).getElementsByTagName('errorSIZE'); - expect(errorFORMAT).toExist(); - }); - - it('details row is shown for maps', () => { - let thumbnail = "myThumnbnailUrl"; - let errors = ["FORMAT"]; - let map = { - thumbnail: thumbnail, - id: 123, - canWrite: true, - category: { - name: "MAP" - }, - errors: errors - }; - - const metadataModalItem = ReactDOM.render(, document.getElementById("container")); - expect(metadataModalItem).toExist(); - const detailsSheetArray = document.getElementsByClassName('ms-details-sheet'); - expect(detailsSheetArray.length).toBe(1); - }); - - it('details row is hidden for dashboards', () => { - let thumbnail = "myThumnbnailUrl"; - let errors = ["FORMAT"]; - let map = { - thumbnail: thumbnail, - id: 123, - canWrite: true, - category: { - name: "DASHBOARD" - }, - errors: errors - }; - - const metadataModalItem = ReactDOM.render(, document.getElementById("container")); - expect(metadataModalItem).toExist(); - const detailsSheetArray = document.getElementsByClassName('ms-details-sheet'); - expect(detailsSheetArray.length).toBe(0); - }); - - it('save button is disabled when name is empty and user is unable to save', () => { - const thumbnail = "myThumnbnailUrl"; - const errors = []; - const map = { - thumbnail: thumbnail, - id: 123, - canWrite: true, - category: { - name: "MAP" - }, - metadata: { - name: '', - description: '' - }, - errors: errors - }; - const actions = { - onSave: () => { } - }; - - const spyonOnSave = expect.spyOn(actions, 'onSave'); - const metadataModalItem = ReactDOM.render(, document.getElementById("container")); - expect(metadataModalItem).toExist(); - const modalDivList = document.getElementsByClassName("modal-content"); - const modalButtons = modalDivList.item(0).getElementsByTagName('button'); - const saveButton = modalButtons[1]; - expect(saveButton.disabled).toBeTruthy(); - ReactTestUtils.Simulate.click(saveButton); - expect(spyonOnSave).toNotHaveBeenCalled(); - }); - - it('save button is enabled when name is filled and user is able to save', () => { - const thumbnail = "myThumnbnailUrl"; - const errors = []; - const map = { - thumbnail: thumbnail, - id: 123, - canWrite: true, - category: { - name: "MAP" - }, - metadata: { - name: 'some name', - description: '' - }, - errors: errors - }; - const actions = { - onSave: () => { } - }; - - const spyonOnSave = expect.spyOn(actions, 'onSave'); - const metadataModalItem = ReactDOM.render(, document.getElementById("container")); - expect(metadataModalItem).toExist(); - const modalDivList = document.getElementsByClassName("modal-content"); - const modalButtons = modalDivList.item(0).getElementsByTagName('button'); - const saveButton = modalButtons[1]; - expect(saveButton.disabled).toBeFalsy(); - ReactTestUtils.Simulate.click(saveButton); - expect(spyonOnSave).toHaveBeenCalled(); - }); - it('showing unsaved changes modal and closing the modal', () => { - const actions = { - onToggleUnsavedChangesModal: () => {}, - onDisplayMetadataEdit: () => {}, - onResetCurrentMap: () => {} - }; - const onDisplayMetadataEditSpy = expect.spyOn(actions, 'onDisplayMetadataEdit'); - const onToggleUnsavedChangesModalSpy = expect.spyOn(actions, 'onToggleUnsavedChangesModal'); - const onResetCurrentMapSpy = expect.spyOn(actions, 'onResetCurrentMap'); - - const metadataModal = ReactDOM.render( - , document.getElementById("container")); - expect(metadataModal).toExist(); - const unsavedChangesDialog = document.querySelector('.modal-dialog'); - const unsavedChangesDialogBody = document.querySelector('.modal-dialog .modal-body'); - let buttons = document.querySelectorAll('button'); - expect(unsavedChangesDialog).toExist(); - expect(unsavedChangesDialogBody).toExist(); - expect(unsavedChangesDialogBody.children[0].innerText).toBe("map.details.fieldsChanged"); - expect(unsavedChangesDialogBody.children[2].innerText).toBe("map.details.sureToClose"); - expect(buttons.length).toBe(3); - let closeBtn = buttons[1]; - let cancelBtn = buttons[2]; - expect(closeBtn.innerText).toBe("saveDialog.close"); - expect(cancelBtn.innerText).toBe("saveDialog.cancel"); - ReactTestUtils.Simulate.click(closeBtn); - - expect(onResetCurrentMapSpy).toHaveBeenCalled(); - expect(onDisplayMetadataEditSpy).toNotHaveBeenCalled(); - expect(onToggleUnsavedChangesModalSpy).toNotHaveBeenCalled(); - }); - - it('showing unsaved changes modal and without closing the modal', () => { - const actions = { - onToggleUnsavedChangesModal: () => {}, - onDisplayMetadataEdit: () => {}, - onResetCurrentMap: () => {} - }; - const onDisplayMetadataEditSpy = expect.spyOn(actions, 'onDisplayMetadataEdit'); - const onToggleUnsavedChangesModalSpy = expect.spyOn(actions, 'onToggleUnsavedChangesModal'); - const onResetCurrentMapSpy = expect.spyOn(actions, 'onResetCurrentMap'); - - const metadataModal = ReactDOM.render( - , document.getElementById("container")); - expect(metadataModal).toExist(); - const unsavedChangesDialog = document.querySelector('.modal-dialog'); - const unsavedChangesDialogBody = document.querySelector('.modal-dialog .modal-body'); - let buttons = document.querySelectorAll('button'); - expect(unsavedChangesDialog).toExist(); - expect(unsavedChangesDialogBody).toExist(); - expect(unsavedChangesDialogBody.children[0].innerText).toBe("map.details.fieldsChanged"); - expect(unsavedChangesDialogBody.children[2].innerText).toBe("map.details.sureToClose"); - expect(buttons.length).toBe(3); - let closeBtn = buttons[1]; - let cancelBtn = buttons[2]; - expect(closeBtn.innerText).toBe("saveDialog.close"); - expect(cancelBtn.innerText).toBe("saveDialog.cancel"); - ReactTestUtils.Simulate.click(cancelBtn); - - expect(onResetCurrentMapSpy).toNotHaveBeenCalled(); - expect(onDisplayMetadataEditSpy).toHaveBeenCalled(); - expect(onToggleUnsavedChangesModalSpy).toHaveBeenCalled(); - }); -}); diff --git a/web/client/components/resources/enhancers/resourceGrid.js b/web/client/components/resources/enhancers/resourceGrid.js index 7761af91b5..bbd06322a9 100644 --- a/web/client/components/resources/enhancers/resourceGrid.js +++ b/web/client/components/resources/enhancers/resourceGrid.js @@ -6,52 +6,12 @@ * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. */ -const Rx = require('rxjs'); -const { compose, branch, withState, withHandlers, defaultProps, mapPropsStream, createEventHandler } = require('recompose'); +const { compose, branch, withState, withHandlers, defaultProps } = require('recompose'); -const handleSaveModal = require('../modals/enhancers/handleSaveModal'); +const handleSave = require('../modals/enhancers/handleSave').default; +const handleSaveModal = require('../modals/enhancers/handleSaveModal').default; const handleResourceDownload = require('../modals/enhancers/handleResourceDownload'); -const { updateResource } = require('../../../api/persistence'); -const handleSave = mapPropsStream(props$ => { - const { handler: onSave, stream: saveEventStream$ } = createEventHandler(); - const saveStream$ = - saveEventStream$ - .withLatestFrom(props$) - .switchMap(([resource, props]) => - updateResource(resource) - .do(() => { - if (props) { - if (props.onClose) { - props.onClose(); - } - if (props.onSaveSuccess) { - props.onSaveSuccess(resource); - } - if (props.onShowSuccessNotification) { - props.onShowSuccessNotification(); - } - } - }) - .concat(Rx.Observable.of({ loading: false })) - .startWith({ loading: true }) - .catch(e => { - props.setErrors([...(props.errors || []), e]); - return Rx.Observable.of({ - loading: false - }); - }) - ); - return props$.combineLatest( - saveStream$.startWith({}), - (props, saveProps) => ({ - ...props, - ...saveProps, - errors: props.errors, - onSave - }) - ); -}); /* * EditDialog * Automatically downloads missing data and manage resource changes. Manages save, triggering onSaveSuccess diff --git a/web/client/components/resources/modals/Save.jsx b/web/client/components/resources/modals/Save.jsx index d17f11765a..597f50fc11 100644 --- a/web/client/components/resources/modals/Save.jsx +++ b/web/client/components/resources/modals/Save.jsx @@ -17,6 +17,8 @@ const ErrorBox = require('./fragments/ErrorBox'); const MainForm = require('./fragments/MainForm'); const ruleEditor = require('./enhancers/ruleEditor'); const PermissionEditor = ruleEditor(require('./fragments/PermissionEditor')); +const DetailsRow = require('./fragments/DetailsRow').default; +const DetailsSheet = require('./fragments/DetailsSheet').default; /** * Defines if the resource permissions are available or not. @@ -68,13 +70,27 @@ class SaveModal extends React.Component { linkedResources: PropTypes.object, style: PropTypes.object, modalSize: PropTypes.string, + detailsText: PropTypes.string, + detailsBackup: PropTypes.string, + detailsTextOriginal: PropTypes.string, + enableDetails: PropTypes.bool, + showDetailsPreview: PropTypes.bool, + showDetailsSheet: PropTypes.bool, + showReadOnlyDetailsSheet: PropTypes.bool, // CALLBACKS onError: PropTypes.func, onUpdate: PropTypes.func, onUpdateLinkedResource: PropTypes.func, + onDeleteLinkedResource: PropTypes.func, onClose: PropTypes.func, onFileDrop: PropTypes.func, onFileDropClear: PropTypes.func, + onShowDetailsPreview: PropTypes.func, + onHideDetailsPreview: PropTypes.func, + onShowDetailsSheet: PropTypes.func, + onHideDetailsSheet: PropTypes.func, + onUpdateDetailsText: PropTypes.func, + onCloseReadOnlyDetailsSheet: PropTypes.bool, metadataChanged: PropTypes.func, disablePermission: PropTypes.bool, availablePermissions: PropTypes.arrayOf(PropTypes.string), @@ -105,6 +121,8 @@ class SaveModal extends React.Component { onError: ()=> {}, onUpdate: ()=> {}, onUpdateLinkedResource: () => {}, + onDeleteLinkedResource: () => {}, + onCloseReadOnlyDetailsSheet: () => {}, onSave: ()=> {}, disablePermission: false, availablePermissions: ["canRead", "canWrite"], @@ -128,7 +146,7 @@ class SaveModal extends React.Component { const canEditPermission = !this.props.disablePermission && canEditResourcePermission(this.props.user, this.props.resource); return ( - {} show={this.props.show} @@ -166,6 +184,44 @@ class SaveModal extends React.Component { onError={this.props.onError} nameFieldFilter={this.props.nameFieldFilter} onUpdate={this.props.onUpdate} /> + {this.props.enableDetails && { + this.props.onUpdateLinkedResource('details', 'NODATA', 'DETAILS'); + this.props.onUpdateDetailsText('NODATA'); + this.props.onHideDetailsPreview(); + }} + onUndo={() => { + this.props.onDeleteLinkedResource('details'); + this.props.onUpdateDetailsText(this.props.detailsTextOriginal); + if (this.props.detailsTextOriginal === 'NODATA') { + this.props.onHideDetailsPreview(); + } + }}/> + } + {this.props.enableDetails && { + this.props.onHideDetailsSheet(); + this.props.onUpdateDetailsText(this.props.detailsBackup); + }} + onSave={text => { + this.props.onHideDetailsSheet(); + this.props.onUpdateLinkedResource('details', text || 'NODATA', 'DETAILS'); + }} + onUpdate={text => { + this.props.onUpdateDetailsText(text); + }}/> + } { !!canEditPermission && } + {this.props.showReadOnlyDetailsSheet && { + this.props.onCloseReadOnlyDetailsSheet(); + }} + />} ); } isValidForm = () => get(this.props.resource, "metadata.name") && (!this.props.enableFileDrop || this.props.fileDropStatus === 'accepted') diff --git a/web/client/components/resources/modals/enhancers/handleDetails.jsx b/web/client/components/resources/modals/enhancers/handleDetails.jsx new file mode 100644 index 0000000000..2d6f103d38 --- /dev/null +++ b/web/client/components/resources/modals/enhancers/handleDetails.jsx @@ -0,0 +1,78 @@ +/* + * Copyright 2020, 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. + */ + +import React from 'react'; +import Rx from 'rxjs'; +import { compose, lifecycle, withState, mapPropsStream } from 'recompose'; + +import GeoStoreApi from '../../../../api/GeoStoreDAO'; +import MapUtils from '../../../../utils/MapUtils'; + +const getDetails = (detailsUri) => { + const id = MapUtils.getIdFromUri(detailsUri); + return Rx.Observable.defer(() => id ? GeoStoreApi.getData(id) : Promise.resolve()); +}; + +const handleDetailsDownload = mapPropsStream(props$ => + props$.combineLatest( + props$ + .pluck('resource') + .distinctUntilChanged((res1, res2) => res1.id === res2.id) + .switchMap(res => + getDetails(res.attributes.details) + .map(data => ({ + detailsTextOriginal: data || 'NODATA' + })) + .startWith({resource: {}}) + .catch(() => Rx.Observable.of({ + detailsTextOriginal: 'NODATA' + })) + ) + .startWith({}), + (p1, p2) => ({ + ...p1, + ...p2 + }) + ) +); + + +export default compose( + withState('showDetailsPreview', 'setShowPreview', false), + withState('showDetailsSheet', 'setShowDetailsSheet', false), + withState('detailsText', 'setDetailsText'), + withState('detailsBackup', 'setDetailsBackup'), + handleDetailsDownload, + lifecycle({ + componentDidUpdate(oldProps) { + if (oldProps.detailsTextOriginal !== this.props.detailsTextOriginal) { + this.props.setDetailsText(this.props.detailsTextOriginal); + this.props.setDetailsBackup(this.props.detailsTextOriginal); + } + } + }), + Component => ({ + setShowPreview, + setShowDetailsSheet, + setDetailsText, + setDetailsBackup, + setDetailsTextOriginal, + ...props + }) => ( + { + setDetailsBackup(props.detailsText); + setShowDetailsSheet(true); + }} + onHideDetailsSheet={() => setShowDetailsSheet(false)} + onShowDetailsPreview={() => setShowPreview(true)} + onHideDetailsPreview={() => setShowPreview(false)} + onUpdateDetailsText={text => setDetailsText(text)}/> + ) +); diff --git a/web/client/components/resources/modals/enhancers/handleResourceData.jsx b/web/client/components/resources/modals/enhancers/handleResourceData.jsx index 96e4c4a11f..dc26bde214 100644 --- a/web/client/components/resources/modals/enhancers/handleResourceData.jsx +++ b/web/client/components/resources/modals/enhancers/handleResourceData.jsx @@ -7,6 +7,7 @@ */ const React = require('react'); const { compose, withStateHandlers, withState, branch, withHandlers, renderComponent} = require('recompose'); +const { omit } = require('lodash'); const {set} = require('../../../../utils/ImmutableUtils'); const Message = require('../../../I18N/Message'); const ConfirmDialog = require('../ConfirmModal'); @@ -53,6 +54,9 @@ module.exports = compose( data, ...options }, linkedResources) + }), + onDeleteLinkedResource: ({ linkedResources = {} }) => (key) => ({ + linkedResources: omit(linkedResources, key) }) } ), diff --git a/web/client/components/resources/modals/enhancers/handleSave.js b/web/client/components/resources/modals/enhancers/handleSave.js new file mode 100644 index 0000000000..a8513f3b96 --- /dev/null +++ b/web/client/components/resources/modals/enhancers/handleSave.js @@ -0,0 +1,59 @@ +/* +* Copyright 2020, 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. +*/ + +import Rx from 'rxjs'; +import { mapPropsStream, createEventHandler } from 'recompose'; + +import { updateResource } from '../../../../api/persistence'; + +export default mapPropsStream(props$ => { + const { handler: onSave, stream: saveEventStream$ } = createEventHandler(); + const saveStream$ = + saveEventStream$ + .withLatestFrom(props$) + .switchMap(([resource, props]) => + updateResource(resource) + .do(() => { + if (props) { + if (props.onClose) { + props.onClose(); + } + if (props.onSaveSuccess) { + props.onSaveSuccess(resource); + } + if (props.onShowSuccessNotification) { + props.onShowSuccessNotification(); + } + } + }) + .concat(Rx.Observable.of({ loading: false })) + .startWith({ loading: true }) + .catch(e => { + if (props.setErrors) { + props.setErrors([...(props.errors || []), e]); + } + + if (props.onSaveError) { + props.onSaveError(e, props.errors); + } + + return Rx.Observable.of({ + loading: false + }); + }) + ); + return props$.combineLatest( + saveStream$.startWith({}), + (props, saveProps) => ({ + ...props, + ...saveProps, + errors: props.errors, + onSave + }) + ); +}); diff --git a/web/client/components/resources/modals/enhancers/handleSaveModal.js b/web/client/components/resources/modals/enhancers/handleSaveModal.js index bcd61e6603..2a2557fc81 100644 --- a/web/client/components/resources/modals/enhancers/handleSaveModal.js +++ b/web/client/components/resources/modals/enhancers/handleSaveModal.js @@ -5,16 +5,20 @@ * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. */ -const handleResourceData = require('./handleResourceData'); -const handlePermission = require('./handlePermission'); -const handleErrors = require('./handleErrors'); -const { compose, branch, renderNothing} = require('recompose'); -module.exports = compose( +import handleResourceData from './handleResourceData'; +import handleDetails from './handleDetails'; +import handlePermission from './handlePermission'; +import handleErrors from './handleErrors'; + +import { compose, branch, renderNothing } from 'recompose'; + +export default compose( branch( - ({ show }) => !show, + ({ show, showReadOnlyDetailsSheet }) => !show && !showReadOnlyDetailsSheet, renderNothing ), handleResourceData, + handleDetails, handlePermission(), handleErrors ); diff --git a/web/client/components/resources/modals/fragments/DetailsRow.jsx b/web/client/components/resources/modals/fragments/DetailsRow.jsx index 351b3af8f3..5f575cd942 100644 --- a/web/client/components/resources/modals/fragments/DetailsRow.jsx +++ b/web/client/components/resources/modals/fragments/DetailsRow.jsx @@ -1,77 +1,86 @@ -const React = require('react'); -const {Row, Col} = require('react-bootstrap'); -const Spinner = require('react-spinkit'); -const { isNil } = require('lodash'); -const Toolbar = require('../../../misc/toolbar/Toolbar'); +/* + * Copyright 2020, 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 Message = require('../../../I18N/Message'); -const { NO_DETAILS_AVAILABLE } = require('../../../../actions/maps'); +import React from 'react'; +import { Row, Col } from 'react-bootstrap'; +import Spinner from 'react-spinkit'; +import { isNil } from 'lodash'; +import Toolbar from '../../../misc/toolbar/Toolbar'; +import Message from '../../../I18N/Message'; -module.exports = ({ - saving, - hideGroupProperties, + +export default ({ + resource = {}, + showPreview = false, editDetailsDisabled, detailsText, - detailsBackup, - onToggleGroupProperties = () => { }, - onUndoDetails = () => { }, - onToggleDetailsSheet = () => { }, - onUpdateDetails = () => { } + canUndo = false, + onShowPreview = () => {}, + onHidePreview = () => {}, + onUndo = () => {}, + onShowDetailsSheet = () => {}, + onUpdate = () => {}, + onDelete = () => {} }) => { return ( -
+
- {detailsText === "" ? : } + {detailsText === 'NODATA' ? : }
- {saving ? : null} + {resource.saving ? : null} {isNil(detailsText) ? : { onToggleGroupProperties(); }, - disabled: saving, - tooltipId: !hideGroupProperties ? "map.details.showPreview" : "map.details.hidePreview" + glyph: showPreview ? 'eye-open' : 'eye-close', + visible: detailsText !== 'NODATA', + onClick: () => showPreview ? onHidePreview() : onShowPreview(), + disabled: resource.saving, + tooltipId: !showPreview ? "map.details.showPreview" : "map.details.hidePreview" }, { glyph: 'undo', tooltipId: "map.details.undo", - visible: !!detailsBackup, - onClick: () => { onUndoDetails(detailsBackup); }, - disabled: saving + visible: canUndo, + onClick: () => onUndo(), + disabled: resource.saving }, { glyph: 'pencil-add', tooltipId: "map.details.add", - visible: !detailsText, + visible: detailsText === 'NODATA', onClick: () => { - onToggleDetailsSheet(false); + onShowDetailsSheet(); }, - disabled: saving + disabled: resource.saving }, { glyph: 'pencil', tooltipId: "map.details.edit", - visible: !!detailsText && !editDetailsDisabled, + visible: detailsText !== 'NODATA' && !editDetailsDisabled, onClick: () => { - onToggleDetailsSheet(false); + onShowDetailsSheet(); if (detailsText) { - onUpdateDetails(detailsText, true); + onUpdate(detailsText); } }, - disabled: saving + disabled: resource.saving }, { glyph: 'trash', tooltipId: "map.details.delete", - visible: !!detailsText, - onClick: () => { this.props.detailsSheetActions.onDeleteDetails(); }, - disabled: saving + visible: detailsText !== 'NODATA', + onClick: () => onDelete(), + disabled: resource.saving }]} />}
@@ -79,7 +88,7 @@ module.exports = ({
{detailsText &&
- {detailsText !== NO_DETAILS_AVAILABLE ?
+ {detailsText !== 'NODATA' ?
:
}
}
diff --git a/web/client/components/resources/modals/fragments/DetailsSheet.jsx b/web/client/components/resources/modals/fragments/DetailsSheet.jsx new file mode 100644 index 0000000000..c496efaefb --- /dev/null +++ b/web/client/components/resources/modals/fragments/DetailsSheet.jsx @@ -0,0 +1,78 @@ +/* + * Copyright 2020, 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. + */ + +import React from 'react'; +import Spinner from 'react-spinkit'; +import ReactQuill from 'react-quill'; + +import ResizableModal from '../../../misc/ResizableModal'; +import Portal from '../../../misc/Portal'; +import Message from '../../../I18N/Message'; + +import 'react-quill/dist/quill.snow.css'; + +export default ({ + show = false, + resource = {}, + readOnly = false, + detailsText, + modules = { + toolbar: [ + [{ 'size': ['small', false, 'large', 'huge'] }, 'bold', 'italic', 'underline', 'blockquote'], + [{ 'list': 'bullet' }, { 'align': [] }], + [{ 'color': [] }, { 'background': [] }, 'clean'], ['image', 'link'] + ] + }, + onClose = () => {}, + onSave = () => {}, + onUpdate = () => {} +}) => { + return ( + + {readOnly ? ( + onClose()} + title={} + show + > +
+ {!detailsText ? :
} +
+ + ) : (} + bodyClassName="ms-modal-quill-container" + size="lg" + clickOutEnabled={false} + showFullscreen + fullscreenType="full" + onClose={() => onClose()} + buttons={[{ + text: , + onClick: () => onClose() + }, { + text: , + onClick: () => onSave(detailsText) + }]}> +
+

' : detailsText} + onChange={(details) => { + if (details && details !== '


') { + onUpdate(details); + } + }} + modules={modules} /> +
+
)} + + ); +}; diff --git a/web/client/epics/__tests__/dashboards-test.js b/web/client/epics/__tests__/dashboards-test.js index 48c0d1c683..de1ec54095 100644 --- a/web/client/epics/__tests__/dashboards-test.js +++ b/web/client/epics/__tests__/dashboards-test.js @@ -17,7 +17,6 @@ const { } = require('../dashboards'); const { dashboardSaved } = require('../../actions/dashboard'); -const { mapMetadataUpdated } = require('../../actions/maps'); const { @@ -103,26 +102,6 @@ describe('dashboards epics', () => { } }); }); - it('reload on mapMetadataUpdate', (done) => { - const startActions = [mapMetadataUpdated(1, "name", "description")]; - testEpic(reloadOnDashboards, 1, startActions, ([a]) => { - expect(a.type).toBe(SEARCH_DASHBOARDS); - expect(a.params.start).toBe(0); - expect(a.params.limit).toBe(12); - expect(a.searchText).toBe("test"); - done(); - }, { - dashboards: { - searchText: "test", - options: { - params: { - start: 0, - limit: 12 - } - } - } - }); - }); }); it('searchDashboards error', (done) => { const baseUrl = "base/web/client/test-resources/geostore/extjs/search/NODATA#"; diff --git a/web/client/epics/__tests__/maps-test.js b/web/client/epics/__tests__/maps-test.js index e4b414af2f..10295d31a4 100644 --- a/web/client/epics/__tests__/maps-test.js +++ b/web/client/epics/__tests__/maps-test.js @@ -12,25 +12,25 @@ const configureMockStore = require('redux-mock-store').default; const {createEpicMiddleware, combineEpics } = require('redux-observable'); const {CALL_HISTORY_METHOD} = require('connected-react-router'); const { - saveDetails, SET_DETAILS_CHANGED, MAPS_LIST_LOADING, MAPS_LIST_LOADED, MAPS_LIST_LOAD_ERROR, + MAPS_LIST_LOADING, MAPS_LIST_LOADED, MAPS_LIST_LOAD_ERROR, CLOSE_DETAILS_PANEL, closeDetailsPanel, loadMaps, MAPS_GET_MAP_RESOURCES_BY_CATEGORY, openDetailsPanel, UPDATE_DETAILS, DETAILS_LOADED, getMapResourcesByCategory, - MAP_DELETING, MAP_DELETED, deleteMap, mapDeleted, TOGGLE_DETAILS_SHEET, + MAP_DELETING, MAP_DELETED, deleteMap, mapDeleted, saveMapResource, MAP_CREATED, SAVING_MAP, MAP_UPDATING, MAPS_LOAD_MAP, LOADING, LOAD_CONTEXTS } = require('../../actions/maps'); const { mapInfoLoaded, MAP_SAVED, LOAD_MAP_INFO, MAP_CONFIG_LOADED } = require('../../actions/config'); const {SHOW_NOTIFICATION} = require('../../actions/notifications'); const {TOGGLE_CONTROL, SET_CONTROL_PROPERTY} = require('../../actions/controls'); -const {RESET_CURRENT_MAP, editMap} = require('../../actions/currentMap'); +const {RESET_CURRENT_MAP} = require('../../actions/currentMap'); const {CLOSE_FEATURE_GRID} = require('../../actions/featuregrid'); const {loginSuccess, logout} = require('../../actions/security'); const { - setDetailsChangedEpic, loadMapsEpic, getMapsResourcesByCategoryEpic, + loadMapsEpic, getMapsResourcesByCategoryEpic, closeDetailsPanelEpic, fetchDataForDetailsPanel, - fetchDetailsFromResourceEpic, deleteMapAndAssociatedResourcesEpic, mapsSetupFilterOnLogin, + deleteMapAndAssociatedResourcesEpic, mapsSetupFilterOnLogin, storeDetailsInfoEpic, mapSaveMapResourceEpic, reloadMapsEpic} = require('../maps'); -const rootEpic = combineEpics(setDetailsChangedEpic, closeDetailsPanelEpic); +const rootEpic = combineEpics(closeDetailsPanelEpic); const epicMiddleware = createEpicMiddleware(rootEpic); const mockStore = configureMockStore([epicMiddleware]); const {testEpic, addTimeoutEpic, TEST_TIMEOUT} = require('./epicTestUtils'); @@ -165,50 +165,6 @@ describe('maps Epics', () => { ConfigUtils.getDefaults = oldGetDefaults; }); - it('test setDetailsChangedEpic', (done) => { - - store.dispatch(saveDetails("

some details

")); - - setTimeout( () => { - try { - const actions = store.getActions(); - expect(actions.length).toBe(3); - expect(actions[1].type).toBe(TOGGLE_DETAILS_SHEET); - expect(actions[2].type).toBe(SET_DETAILS_CHANGED); - } catch (e) { - done(e); - } - done(); - }, 50); - - }); - it('test setDetailsChangedEpic with details resource present', (done) => { - testEpic(setDetailsChangedEpic, 1, saveDetails(detailsText), actions => { - expect(actions.length).toBe(1); - actions.map((action) => { - switch (action.type) { - case SET_DETAILS_CHANGED: - expect(action.detailsChanged).toBe(false); - break; - case TOGGLE_DETAILS_SHEET: - expect(action.detailsSheetReadOnly).toBe(true); - break; - default: - expect(true).toBe(false); - } - }); - done(); - }, { - locale, - currentMap: { - id: mapId, - details: "wrong/uri/4", - detailsText, - originalDetails: detailsText - } - }); - }); - it('test closeDetailsPanel', (done) => { store.dispatch(closeDetailsPanel()); @@ -289,76 +245,7 @@ describe('maps Epics', () => { } }); }); - it('test fetchDetailsFromResourceEpic, map without saved Details', (done) => { - delete map1.details; - testEpic(addTimeoutEpic(fetchDetailsFromResourceEpic), 1, editMap({}, true), actions => { - expect(actions.length).toBe(1); - actions.map((action) => { - switch (action.type) { - case UPDATE_DETAILS: - expect(action.detailsText).toBe(""); - expect(action.originalDetails).toBe(""); - expect(action.doBackup).toBe(true); - break; - default: - expect(true).toBe(false); - } - }); - done(); - }, { - currentMap: { - id: mapId - } - }); - }); - - it('test fetchDetailsFromResourceEpic, map with saved Details', (done) => { - testEpic(addTimeoutEpic(fetchDetailsFromResourceEpic), 1, editMap({}, true), actions => { - expect(actions.length).toBe(1); - actions.map((action) => { - switch (action.type) { - case UPDATE_DETAILS: - expect(action.detailsText.indexOf(detailsText)).toNotBe(-1); - expect(action.originalDetails.indexOf(detailsText)).toNotBe(-1); - expect(action.doBackup).toBe(true); - break; - default: - expect(true).toBe(false); - } - }); - done(); - }, { - currentMap: { - id: mapId, - details: encodeURIComponent(detailsUri) - } - }); - }); - it('test fetchDetailsFromResourceEpic, withError', (done) => { - testEpic(addTimeoutEpic(fetchDetailsFromResourceEpic), 1, editMap({}, true), actions => { - expect(actions.length).toBe(1); - actions.map((action) => { - switch (action.type) { - case SHOW_NOTIFICATION: - expect(action.message).toBe("maps.feedback.errorFetchingDetailsOfMap"); - break; - default: - expect(true).toBe(false); - } - }); - done(); - }, { - locale, - currentMap: { - id: mapId, - details: "wrong/uri/sfdsdfs" - }, - maps: { - results: [map1] - } - }); - }); it('test deleteMapAndAssociatedResourcesEpic, with map, details, thumbnail errors', (done) => { map1.thumbnail = "wronguri/data/5/"; map1.details = "wronguri/data/6/"; diff --git a/web/client/epics/dashboards.js b/web/client/epics/dashboards.js index 91fce79f0f..14c30308a1 100644 --- a/web/client/epics/dashboards.js +++ b/web/client/epics/dashboards.js @@ -7,7 +7,7 @@ */ const Rx = require('rxjs'); const { MAPS_LIST_LOADING, ATTRIBUTE_UPDATED} = require('../actions/maps'); -const { MAP_DELETED, MAP_METADATA_UPDATED } = require('../actions/maps'); +const { MAP_DELETED } = require('../actions/maps'); const { DASHBOARD_SAVED } = require('../actions/dashboard'); const { SEARCH_DASHBOARDS, DELETE_DASHBOARD, DASHBOARD_DELETED, RELOAD, searchDashboards, dashboardListLoaded, dashboardDeleted, dashboardsLoading } = require('../actions/dashboards'); @@ -74,7 +74,7 @@ module.exports = { })) )), reloadOnDashboards: (action$, { getState = () => { } }) => - action$.ofType(DASHBOARD_DELETED, MAP_DELETED, MAP_METADATA_UPDATED, RELOAD, ATTRIBUTE_UPDATED, DASHBOARD_SAVED) + action$.ofType(DASHBOARD_DELETED, MAP_DELETED, RELOAD, ATTRIBUTE_UPDATED, DASHBOARD_SAVED) .delay(1000) // delay as a workaround for geostore issue #178 .switchMap( () => Rx.Observable.of(searchDashboards( searchTextSelector(getState()), diff --git a/web/client/epics/maps.js b/web/client/epics/maps.js index 3fba6b8b17..2a5a910331 100644 --- a/web/client/epics/maps.js +++ b/web/client/epics/maps.js @@ -6,63 +6,58 @@ * LICENSE file in the root directory of this source tree. */ -const Rx = require('rxjs'); -const uuidv1 = require('uuid/v1'); -const assign = require('object-assign'); -const {push} = require('connected-react-router'); -const {basicError, basicSuccess} = require('../utils/NotificationUtils'); -const GeoStoreApi = require('../api/GeoStoreDAO'); -const { MAP_INFO_LOADED, MAP_SAVED, mapSaveError, mapSaved, loadMapInfo, configureMap } = require('../actions/config'); -const {get, isNil, isArray, isEqual, find, pick, omit, keys, zip} = require('lodash'); -const { - SAVE_DETAILS, SAVE_RESOURCE_DETAILS, MAPS_GET_MAP_RESOURCES_BY_CATEGORY, +import Rx from 'rxjs'; +import assign from 'object-assign'; +import {push} from 'connected-react-router'; +import {basicError, basicSuccess} from '../utils/NotificationUtils'; +import GeoStoreApi from '../api/GeoStoreDAO'; +import { MAP_INFO_LOADED, MAP_SAVED, mapSaveError, mapSaved, loadMapInfo, configureMap } from '../actions/config'; +import { get, isArray, isEqual, find, pick, omit, keys, zip } from 'lodash'; +import { + MAPS_GET_MAP_RESOURCES_BY_CATEGORY, DELETE_MAP, OPEN_DETAILS_PANEL, MAPS_LOAD_MAP, CLOSE_DETAILS_PANEL, NO_DETAILS_AVAILABLE, SAVE_MAP_RESOURCE, MAP_DELETED, - SEARCH_FILTER_CHANGED, SEARCH_FILTER_CLEAR_ALL, LOAD_CONTEXTS, - setDetailsChanged, updateDetails, mapsLoading, mapsLoaded, - mapDeleting, toggleDetailsEditability, mapDeleted, loadError, - doNothing, detailsLoaded, detailsSaving, onDisplayMetadataEdit, - RESET_UPDATING, resetUpdating, toggleDetailsSheet, getMapResourcesByCategory, - mapUpdating, savingMap, mapCreated, loadMaps, loadContexts, setContexts, setSearchFilter, loading -} = require('../actions/maps'); -const { - resetCurrentMap, EDIT_MAP -} = require('../actions/currentMap'); -const {closeFeatureGrid} = require('../actions/featuregrid'); -const {toggleControl, setControlProperty} = require('../actions/controls'); -const {setTabsHidden} = require('../actions/contenttabs'); -const { - mapPermissionsFromIdSelector, mapThumbnailsUriFromIdSelector, + SEARCH_FILTER_CHANGED, SEARCH_FILTER_CLEAR_ALL, LOAD_CONTEXTS, ATTRIBUTE_UPDATED, + updateDetails, mapsLoading, mapsLoaded, + mapDeleting, mapDeleted, loadError, + detailsLoaded, onDisplayMetadataEdit, + RESET_UPDATING, getMapResourcesByCategory, + mapUpdating, savingMap, mapCreated, loadMaps, loadContexts, setContexts, setSearchFilter, loading, + invalidateFeaturedMaps +} from '../actions/maps'; +import { DASHBOARD_DELETED } from '../actions/dashboards'; +import { + resetCurrentMap +} from '../actions/currentMap'; +import { closeFeatureGrid } from '../actions/featuregrid'; +import { toggleControl, setControlProperty } from '../actions/controls'; +import { setTabsHidden } from '../actions/contenttabs'; +import { + mapThumbnailsUriFromIdSelector, mapDetailsUriFromIdSelector, searchTextSelector, searchParamsSelector, totalCountSelector, contextsSelector, searchFilterSelector -} = require('../selectors/maps'); -const { +} from '../selectors/maps'; +import { mapIdSelector, mapInfoDetailsUriFromIdSelector -} = require('../selectors/map'); -const {mapTypeSelector} = require('../selectors/maptype'); -const { - currentMapDetailsTextSelector, currentMapIdSelector, - currentMapDetailsUriSelector, currentMapSelector, - currentMapDetailsChangedSelector, currentMapOriginalDetailsTextSelector -} = require('../selectors/currentmap'); -const {userParamsSelector, userRoleSelector} = require('../selectors/security'); -const { +} from '../selectors/map'; +import { mapTypeSelector } from '../selectors/maptype'; +import { userRoleSelector } from '../selectors/security'; +import { LOGIN_SUCCESS, LOGOUT -} = require('../actions/security'); -const {deleteResourceById, createAssociatedResource, deleteAssociatedResource, updateAssociatedResource} = require('../utils/ObservableUtils'); +} from '../actions/security'; +import { deleteResourceById } from '../utils/ObservableUtils'; -const {getIdFromUri} = require('../utils/MapUtils'); - -const {getErrorMessage} = require('../utils/LocaleUtils'); -const { EMPTY_RESOURCE_VALUE } = require('../utils/MapInfoUtils'); -const {createResource, updateResource, getResource, searchListByAttributes, updateResourceAttribute} = require("../api/persistence"); -const {wrapStartStop} = require('../observables/epics'); +import { getIdFromUri } from '../utils/MapUtils'; +import { getErrorMessage } from '../utils/LocaleUtils'; +import { EMPTY_RESOURCE_VALUE } from '../utils/MapInfoUtils'; +import { createResource, updateResource, getResource, searchListByAttributes, updateResourceAttribute } from "../api/persistence"; +import { wrapStartStop } from '../observables/epics'; const calculateNewParams = state => { const totalCount = totalCountSelector(state); @@ -78,127 +73,11 @@ const calculateNewParams = state => { }; }; -const manageMapResource = ({map = {}, attribute = "", resource = null, type = "STRING", optionsDel = {}, messages = {}} = {}) => { - const attrVal = map[attribute]; - const mapId = map.id; - // create - if ((isNil(attrVal) || attrVal === EMPTY_RESOURCE_VALUE) && !isNil(resource)) { - return createAssociatedResource({...resource, attribute, mapId, type, messages}); - } - if (isNil(resource)) { - // delete - return deleteAssociatedResource({ - mapId, - attribute, - type, - resourceId: getIdFromUri(attrVal), - options: optionsDel, - messages}); - } - // update - return updateAssociatedResource({ - permissions: resource.permissions, - resourceId: getIdFromUri(attrVal), - value: resource.value, - attribute, - options: resource.optionsAttr, - messages}); - -}; - -/** - If details are changed from the original ones then set unsavedChanges to true -*/ -const setDetailsChangedEpic = (action$, store) => - action$.ofType(SAVE_DETAILS) - .switchMap((a) => { - let actions = []; - const state = store.getState(); - const detailsUri = currentMapDetailsUriSelector(state); - if (a.detailsText.length <= 500000) { - actions.push(toggleDetailsSheet(true)); - } else { - actions.push(basicError({message: "maps.feedback.errorSizeExceeded"})); - } - if (!detailsUri) { - actions.push(setDetailsChanged(a.detailsText !== "


")); - return Rx.Observable.from(actions); - } - const originalDetails = currentMapOriginalDetailsTextSelector(state); - const currentDetails = currentMapDetailsTextSelector(state); - actions.push(setDetailsChanged(originalDetails !== currentDetails)); - return Rx.Observable.from(actions); - }); - -/** - * If the details resource does not exist it saves it, and it updates its permission with the one set for the mapPermissionsFromIdSelector - * and it updates the attribute details in map resource -*/ -const saveResourceDetailsEpic = (action$, store) => - action$.ofType(SAVE_RESOURCE_DETAILS) - .switchMap(() => { - const state = store.getState(); - const mapId = currentMapIdSelector(state); - const value = currentMapDetailsTextSelector(state, mapId); - const detailsChanged = currentMapDetailsChangedSelector(state); +export const invalidateFeaturedMapsEpic = (action$) => action$ + .ofType(ATTRIBUTE_UPDATED, MAP_DELETED, MAP_SAVED, DASHBOARD_DELETED) + .mapTo(invalidateFeaturedMaps()); - let params = { - attribute: "details", - map: currentMapSelector(state), - resource: null, - type: "STRING" - }; - if (!detailsChanged) { - return Rx.Observable.of(doNothing()); - } - if (value !== "" && detailsChanged) { - params.resource = { - category: "DETAILS", - userParams: userParamsSelector(state), - metadata: {name: uuidv1()}, - value, - permissions: mapPermissionsFromIdSelector(state, mapId), - optionsAttr: {}, - optionsRes: {} - }; - } else { - params.optionsDel = {}; - } - return manageMapResource({ - ...params - }).concat([detailsSaving(false), resetUpdating(mapId)]).startWith(detailsSaving(true)); - }); - -/** - Epics used to fetch and/or open the details modal -*/ -const fetchDetailsFromResourceEpic = (action$, store) => - action$.ofType(EDIT_MAP) - .switchMap(() => { - const state = store.getState(); - const mapId = currentMapIdSelector(state); - const detailsUri = currentMapDetailsUriSelector(state); - if (!detailsUri || detailsUri === EMPTY_RESOURCE_VALUE) { - return Rx.Observable.of( - updateDetails("", true, "") - ); - } - const detailsId = getIdFromUri(detailsUri); - return Rx.Observable.fromPromise(GeoStoreApi.getData(detailsId) - .then(data => data)) - .switchMap((details) => { - return Rx.Observable.of( - updateDetails(details, true, details) - ); - }).catch(() => { - return Rx.Observable.of( - basicError({ message: "maps.feedback.errorFetchingDetailsOfMap"}), - updateDetails(NO_DETAILS_AVAILABLE, true, NO_DETAILS_AVAILABLE), - toggleDetailsEditability(mapId)); - }); - }); - -const loadMapsEpic = (action$) => +export const loadMapsEpic = (action$) => action$.ofType(MAPS_LOAD_MAP) .switchMap((action) => { let {params, searchText, geoStoreUrl} = action; @@ -211,7 +90,7 @@ const loadMapsEpic = (action$) => }); -const reloadMapsEpic = (action$, { getState = () => { } }) => +export const reloadMapsEpic = (action$, { getState = () => { } }) => action$.ofType(MAP_DELETED, MAP_SAVED) .delay(1000) .switchMap(() => Rx.Observable.of(loadMaps(false, @@ -219,7 +98,7 @@ const reloadMapsEpic = (action$, { getState = () => { } }) => calculateNewParams(getState()) ))); -const getMapsResourcesByCategoryEpic = (action$, store) => +export const getMapsResourcesByCategoryEpic = (action$, store) => action$.ofType(MAPS_GET_MAP_RESOURCES_BY_CATEGORY) .switchMap((action) => { const state = store.getState(); @@ -310,7 +189,7 @@ const getMapsResourcesByCategoryEpic = (action$, store) => )); }); -const loadMapsOnSearchFilterChange = (action$, store) => +export const loadMapsOnSearchFilterChange = (action$, store) => action$.ofType(SEARCH_FILTER_CHANGED, SEARCH_FILTER_CLEAR_ALL) .filter(({filter}) => !filter || filter === 'contexts') .switchMap(({type}) => { @@ -327,7 +206,7 @@ const loadMapsOnSearchFilterChange = (action$, store) => ); }); -const hideTabsOnSearchFilterChange = (action$) => +export const hideTabsOnSearchFilterChange = (action$) => action$.ofType(SEARCH_FILTER_CHANGED, SEARCH_FILTER_CLEAR_ALL) .filter(({filter}) => !filter || filter === 'contexts') .switchMap(({filterData}) => Rx.Observable.of( @@ -342,7 +221,7 @@ const hideTabsOnSearchFilterChange = (action$) => ) )); -const mapsLoadContextsEpic = (action$) => +export const mapsLoadContextsEpic = (action$) => action$.ofType(LOAD_CONTEXTS) .distinctUntilChanged((prev, cur) => (prev.searchText || '*') === (cur.searchText || '*') && @@ -372,7 +251,7 @@ const mapsLoadContextsEpic = (action$) => ); }); -const mapsSetupFilterOnLogin = (action$, store) => +export const mapsSetupFilterOnLogin = (action$, store) => action$.ofType(LOGIN_SUCCESS, LOGOUT) .switchMap(() => { const state = store.getState(); @@ -384,7 +263,7 @@ const mapsSetupFilterOnLogin = (action$, store) => )); }); -const deleteMapAndAssociatedResourcesEpic = (action$, store) => +export const deleteMapAndAssociatedResourcesEpic = (action$, store) => action$.ofType(DELETE_MAP) .switchMap((action) => { const state = store.getState(); @@ -430,7 +309,7 @@ const deleteMapAndAssociatedResourcesEpic = (action$, store) => }).startWith(mapDeleting(mapId)); }); -const fetchDataForDetailsPanel = (action$, store) => +export const fetchDataForDetailsPanel = (action$, store) => action$.ofType(OPEN_DETAILS_PANEL) .switchMap(() => { const state = store.getState(); @@ -453,21 +332,21 @@ const fetchDataForDetailsPanel = (action$, store) => }); }); -const closeDetailsPanelEpic = (action$) => +export const closeDetailsPanelEpic = (action$) => action$.ofType(CLOSE_DETAILS_PANEL) .switchMap(() => Rx.Observable.from( [ toggleControl("details", "enabled"), resetCurrentMap() ]) ); -const resetCurrentMapEpic = (action$) => +export const resetCurrentMapEpic = (action$) => action$.ofType(RESET_UPDATING) .switchMap(() => Rx.Observable.from( [ onDisplayMetadataEdit(false), resetCurrentMap() ]) ); -const storeDetailsInfoEpic = (action$, store) => +export const storeDetailsInfoEpic = (action$, store) => action$.ofType(MAP_INFO_LOADED) .switchMap(() => { const mapId = mapIdSelector(store.getState()); @@ -489,13 +368,13 @@ const storeDetailsInfoEpic = (action$, store) => /** * Create or update map resource with persistence api */ -const mapSaveMapResourceEpic = (action$, store) => +export const mapSaveMapResourceEpic = (action$, store) => action$.ofType(SAVE_MAP_RESOURCE) .exhaustMap(({resource}) => { // filter out invalid attributes - // thumbnails are handled separately + // thumbnails and details are handled separately(linked resources) const validAttributesNames = keys(resource.attributes) - .filter(attrName => attrName !== 'thumbnail' && resource.attributes[attrName] !== undefined && resource.attributes[attrName] !== null); + .filter(attrName => attrName !== 'thumbnail' && attrName !== 'details' && resource.attributes[attrName] !== undefined && resource.attributes[attrName] !== null); return Rx.Observable.forkJoin( (() => { // get a context information using the id in the attribute @@ -514,7 +393,7 @@ const mapSaveMapResourceEpic = (action$, store) => ...(resource.id ? [loadMapInfo(rid)] : []), ...(resource.id ? [configureMap(resource.data, rid)] : []), resource.id ? toggleControl('mapSave') : toggleControl('mapSaveAs'), - mapSaved(), + mapSaved(resource.id), ...(!resource.id ? [ mapCreated(rid, assign({id: rid, canDelete: true, canEdit: true, canCopy: true}, resource.metadata), resource.data), // if we got a valid context information redirect to a context, instead of the default viewer @@ -542,22 +421,3 @@ const mapSaveMapResourceEpic = (action$, store) => }) .startWith(!resource.id ? savingMap(resource.metadata) : mapUpdating(resource.metadata)); }); - -module.exports = { - loadMapsEpic, - resetCurrentMapEpic, - storeDetailsInfoEpic, - closeDetailsPanelEpic, - fetchDataForDetailsPanel, - deleteMapAndAssociatedResourcesEpic, - getMapsResourcesByCategoryEpic, - loadMapsOnSearchFilterChange, - hideTabsOnSearchFilterChange, - mapsLoadContextsEpic, - mapsSetupFilterOnLogin, - setDetailsChangedEpic, - fetchDetailsFromResourceEpic, - saveResourceDetailsEpic, - mapSaveMapResourceEpic, - reloadMapsEpic -}; diff --git a/web/client/examples/api/plugins.js b/web/client/examples/api/plugins.js index e899f93a60..01a70d8c41 100644 --- a/web/client/examples/api/plugins.js +++ b/web/client/examples/api/plugins.js @@ -29,7 +29,7 @@ module.exports = { BurgerMenuPlugin: require('../../plugins/BurgerMenu'), UndoPlugin: require('../../plugins/History'), RedoPlugin: require('../../plugins/History'), - MapsPlugin: require('../../plugins/Maps'), + MapsPlugin: require('../../plugins/Maps').default, MapSearchPlugin: require('../../plugins/MapSearch'), LanguagePlugin: require('../../plugins/Language'), ManagerPlugin: require('../../plugins/manager/Manager'), diff --git a/web/client/plugins/FeaturedMaps.jsx b/web/client/plugins/FeaturedMaps.jsx index 831e6fbe52..8da984936b 100644 --- a/web/client/plugins/FeaturedMaps.jsx +++ b/web/client/plugins/FeaturedMaps.jsx @@ -6,30 +6,32 @@ * LICENSE file in the root directory of this source tree. */ -const React = require('react'); -const PropTypes = require('prop-types'); -const assign = require('object-assign'); -const {defaultProps, compose, mapPropsStream} = require('recompose'); -const {createSelector} = require('reselect'); -const {connect} = require('react-redux'); -const {isEqual} = require('lodash'); -const {NavItem, Glyphicon} = require('react-bootstrap'); -const { setFeaturedMapsEnabled} = require('../actions/maps'); - -const Message = require("../components/I18N/Message"); -const maptypeEpics = require('../epics/maptype'); -const mapsEpics = require('../epics/maps'); -const {userRoleSelector} = require('../selectors/security'); -const {versionSelector} = require('../selectors/version'); -const {mapTypeSelector} = require('../selectors/maptype'); -const {resourceSelector, searchTextSelector, isFeaturedMapsEnabled} = require('../selectors/featuredmaps'); -const {loadPage, updateItemsLifecycle} = require('../components/maps/enhancers/featuredMaps'); -const gridPagination = require('../components/misc/enhancers/gridPagination'); -const tooltip = require('../components/misc/enhancers/tooltip'); - -const MapsGrid = require('./maps/MapsGrid'); -const MetadataModal = require('./maps/MetadataModal'); -const {scrollIntoViewId} = require('../utils/DOMUtil'); +import React from 'react'; +import PropTypes from 'prop-types'; +import assign from 'object-assign'; +import {defaultProps, compose, mapPropsStream} from 'recompose'; +import {createSelector} from 'reselect'; +import {connect} from 'react-redux'; +import {NavItem, Glyphicon} from 'react-bootstrap'; +import { setFeaturedMapsEnabled} from '../actions/maps'; + +import Message from "../components/I18N/Message"; +import * as maptypeEpics from '../epics/maptype'; +import * as mapsEpics from '../epics/maps'; +import {userRoleSelector} from '../selectors/security'; +import {versionSelector} from '../selectors/version'; +import {mapTypeSelector} from '../selectors/maptype'; +import {invalidationSelector, searchTextSelector, isFeaturedMapsEnabled} from '../selectors/featuredmaps'; +import {loadPage, updateItemsLifecycle} from '../components/maps/enhancers/featuredMaps'; +import gridPagination from '../components/misc/enhancers/gridPagination'; +import tooltip from '../components/misc/enhancers/tooltip'; + +import MapsGrid from './maps/MapsGrid'; +import {scrollIntoViewId} from '../utils/DOMUtil'; + +import featuredmaps from '../reducers/featuredmaps'; +import maptype from '../reducers/maptype'; +import currentMap from '../reducers/currentMap'; const ToolTipedNavItem = tooltip(NavItem); @@ -94,7 +96,6 @@ class FeaturedMaps extends React.Component { viewerUrl={(res) => this.context.router.history.push('/' + this.makeShareUrl(res).url)} getShareUrl={this.makeShareUrl} shareOptions={this.getShareOptions} // TODO: share options depending on the content type - metadataModal={MetadataModal} bottom={this.props.bottom} style={items.length === 0 ? {display: 'none'} : {}}/> ); @@ -128,16 +129,16 @@ const featuredMapsPluginSelector = createSelector([ userRoleSelector, state => state.browser && state.browser.mobile, searchTextSelector, - resourceSelector, + invalidationSelector, isFeaturedMapsEnabled, versionSelector -], (mapType, role, isMobile, searchText, resource, isFeaturedEnabled, version) => ({ +], (mapType, role, isMobile, searchText, invalidate, isFeaturedEnabled, version) => ({ mapType, role, permission: role === 'ADMIN', pagination: isMobile ? 'virtual-scroll-horizontal' : 'show-more', searchText, - resource, + invalidate, isFeaturedEnabled, version })); @@ -147,7 +148,7 @@ const updateFeaturedMapsStream = mapPropsStream(props$ => return props$ .startWith({searchText, permission, viewSize, pageSize, loading: true}) .distinctUntilChanged((previous, next) => - isEqual(previous.resource, next.resource) + previous.invalidate === next.invalidate && previous.searchText === next.searchText && previous.permission === next.permission && previous.role === next.role @@ -217,7 +218,7 @@ const IconNavItem = connect(featuredMapsPluginSelector)(({ isFeaturedEnabled }) ) : null); -module.exports = { +export default { FeaturedMapsPlugin: assign(FeaturedMapsPlugin, { NavMenu: { position: 1, @@ -230,8 +231,8 @@ module.exports = { ...mapsEpics }, reducers: { - featuredmaps: require('../reducers/featuredmaps'), - maptype: require('../reducers/maptype'), - currentMap: require('../reducers/currentMap') + featuredmaps, + maptype, + currentMap } }; diff --git a/web/client/plugins/Maps.jsx b/web/client/plugins/Maps.jsx index 2a95f9b777..2165358f60 100644 --- a/web/client/plugins/Maps.jsx +++ b/web/client/plugins/Maps.jsx @@ -5,35 +5,41 @@ * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. */ -const React = require('react'); -const PropTypes = require('prop-types'); -const assign = require('object-assign'); -const {connect} = require('react-redux'); -const { compose } = require('recompose'); -const ConfigUtils = require('../utils/ConfigUtils'); -const Message = require("../components/I18N/Message"); - -const maptypeEpics = require('../epics/maptype'); -const mapsEpics = require('../epics/maps'); -const {mapTypeSelector} = require('../selectors/maptype'); -const {userRoleSelector} = require('../selectors/security'); -const {versionSelector} = require('../selectors/version'); -const { totalCountSelector } = require('../selectors/maps'); -const { isFeaturedMapsEnabled } = require('../selectors/featuredmaps'); -const emptyState = require('../components/misc/enhancers/emptyState'); -const {createSelector} = require('reselect'); - -const MapsGrid = require('./maps/MapsGrid'); -const MetadataModal = require('./maps/MetadataModal'); -const EmptyMaps = require('./maps/EmptyMaps').default; - -const {loadMaps, setShowMapDetails} = require('../actions/maps'); +import React from 'react'; +import PropTypes from 'prop-types'; +import assign from 'object-assign'; +import {connect} from 'react-redux'; +import { compose } from 'recompose'; +import ConfigUtils from '../utils/ConfigUtils'; +import Message from "../components/I18N/Message"; + +import * as maptypeEpics from '../epics/maptype'; +import * as mapsEpics from '../epics/maps'; +import {mapTypeSelector} from '../selectors/maptype'; +import {userRoleSelector} from '../selectors/security'; +import {versionSelector} from '../selectors/version'; +import { totalCountSelector } from '../selectors/maps'; +import { isFeaturedMapsEnabled } from '../selectors/featuredmaps'; +import emptyState from '../components/misc/enhancers/emptyState'; +import {createSelector} from 'reselect'; + +import MapsGrid from './maps/MapsGrid'; +import PaginationToolbarBase from '../components/misc/PaginationToolbar'; + +import EmptyMaps from './maps/EmptyMaps'; + +import {loadMaps, setShowMapDetails} from '../actions/maps'; + +import mapsReducer from '../reducers/maps'; +import maptype from '../reducers/maptype'; +import currentMap from '../reducers/currentMap'; const mapsCountSelector = createSelector( totalCountSelector, count => ({ count }) ); + const PaginationToolbar = connect((state) => { if (!state.maps ) { return {}; @@ -59,7 +65,7 @@ const PaginationToolbar = connect((state) => { dispatchProps.onSelect(ConfigUtils.getDefaults().geoStoreUrl, stateProps.searchText, {start, limit}); } }; -})(require('../components/misc/PaginationToolbar')); +})(PaginationToolbarBase); /** * Plugin for Maps resources @@ -124,7 +130,6 @@ class Maps extends React.Component { shareApi={this.props.showAPIShare} version={this.props.version} bottom={} - metadataModal={MetadataModal} />); } } @@ -159,7 +164,7 @@ const MapsPlugin = compose( ) )(Maps); -module.exports = { +export default { MapsPlugin: assign(MapsPlugin, { NavMenu: { position: 2, @@ -182,8 +187,8 @@ module.exports = { ...mapsEpics }, reducers: { - maps: require('../reducers/maps'), - maptype: require('../reducers/maptype'), - currentMap: require('../reducers/currentMap') + maps: mapsReducer, + maptype, + currentMap } }; diff --git a/web/client/plugins/contextmanager/ContextGrid.jsx b/web/client/plugins/contextmanager/ContextGrid.jsx index 21bf519c01..27e124f9de 100644 --- a/web/client/plugins/contextmanager/ContextGrid.jsx +++ b/web/client/plugins/contextmanager/ContextGrid.jsx @@ -11,12 +11,12 @@ const resourceGrid = require('../../components/resources/enhancers/resourceGrid' const withShareTool = require('../../components/resources/enhancers/withShareTool').default; const Grid = compose( withHandlers({ - onSaveSuccess: (props) => (resource) => { + onSaveSuccess: (props) => () => { if (props.reloadContexts) { props.reloadContexts(); } - if (props.setFeaturedMapsLatestResource) { - props.setFeaturedMapsLatestResource(resource); + if (props.invalidateFeaturedMaps) { + props.invalidateFeaturedMaps(); } } }), diff --git a/web/client/plugins/dashboard/DashboardsGrid.jsx b/web/client/plugins/dashboard/DashboardsGrid.jsx index 8484a3ff4c..097a0e91b9 100644 --- a/web/client/plugins/dashboard/DashboardsGrid.jsx +++ b/web/client/plugins/dashboard/DashboardsGrid.jsx @@ -8,7 +8,7 @@ const { compose, defaultProps, withHandlers } = require('recompose'); const { deleteDashboard, reloadDashboards } = require('../../actions/dashboards'); -const { updateAttribute, setFeaturedMapsLatestResource } = require('../../actions/maps'); // TODO: externalize +const { updateAttribute, invalidateFeaturedMaps } = require('../../actions/maps'); // TODO: externalize const { userSelector } = require('../../selectors/security'); const { createSelector } = require('reselect'); const { connect } = require('react-redux'); @@ -21,16 +21,16 @@ const Grid = compose( onDelete: deleteDashboard, reloadDashboards, onShowSuccessNotification: () => success({ title: "success", message: "resources.successSaved" }), - setFeaturedMapsLatestResource, + invalidateFeaturedMaps, onUpdateAttribute: updateAttribute }), withHandlers({ - onSaveSuccess: (props) => (resource) => { + onSaveSuccess: (props) => () => { if (props.reloadDashboards) { props.reloadDashboards(); } - if (props.setFeaturedMapsLatestResource) { - props.setFeaturedMapsLatestResource(resource); + if (props.invalidateFeaturedMaps) { + props.invalidateFeaturedMaps(); } if (props.onShowSuccessNotification) { props.onShowSuccessNotification(); diff --git a/web/client/plugins/dashboard/SaveDialog.jsx b/web/client/plugins/dashboard/SaveDialog.jsx index 293d71ab27..ef23c15a01 100644 --- a/web/client/plugins/dashboard/SaveDialog.jsx +++ b/web/client/plugins/dashboard/SaveDialog.jsx @@ -13,7 +13,7 @@ const { userSelector } = require('../../selectors/security'); const { widgetsConfig } = require('../../selectors/widgets'); const { isShowSaveOpen, dashboardResource, isDashboardLoading, getDashboardSaveErrors } = require('../../selectors/dashboard'); const { saveDashboard, triggerSave } = require('../../actions/dashboard'); -const handleSaveModal = require('../../components/resources/modals/enhancers/handleSaveModal'); +const handleSaveModal = require('../../components/resources/modals/enhancers/handleSaveModal').default; /** * Save dialog component enhanced for dashboard diff --git a/web/client/plugins/geostories/GeostoriesGrid.jsx b/web/client/plugins/geostories/GeostoriesGrid.jsx index 24b932e1e5..db22421968 100644 --- a/web/client/plugins/geostories/GeostoriesGrid.jsx +++ b/web/client/plugins/geostories/GeostoriesGrid.jsx @@ -8,7 +8,7 @@ const { compose, defaultProps, withHandlers } = require('recompose'); const { deleteGeostory, reloadGeostories } = require('../../actions/geostories'); -const { updateAttribute, setFeaturedMapsLatestResource } = require('../../actions/maps'); // TODO: externalize +const { updateAttribute, invalidateFeaturedMaps } = require('../../actions/maps'); // TODO: externalize const { userSelector } = require('../../selectors/security'); const { createSelector } = require('reselect'); const { connect } = require('react-redux'); @@ -21,16 +21,16 @@ const Grid = compose( onDelete: deleteGeostory, reloadGeostories, onShowSuccessNotification: () => success({ title: "success", message: "resources.successSaved" }), - setFeaturedMapsLatestResource, + invalidateFeaturedMaps, onUpdateAttribute: updateAttribute }), withHandlers({ - onSaveSuccess: (props) => (resource) => { + onSaveSuccess: (props) => () => { if (props.reloadGeostories) { props.reloadGeostories(); } - if (props.setFeaturedMapsLatestResource) { - props.setFeaturedMapsLatestResource(resource); + if (props.invalidateFeaturedMaps) { + props.invalidateFeaturedMaps(); } if (props.onShowSuccessNotification) { props.onShowSuccessNotification(); diff --git a/web/client/plugins/maps/MapSave.jsx b/web/client/plugins/maps/MapSave.jsx index ca634bea76..94c3b902b1 100644 --- a/web/client/plugins/maps/MapSave.jsx +++ b/web/client/plugins/maps/MapSave.jsx @@ -43,7 +43,8 @@ const SaveBaseDialog = compose( saveMap: saveMapResource }), withProps({ - category: "MAP" + category: "MAP", + enableDetails: true }), getContext({ router: PropTypes.object diff --git a/web/client/plugins/maps/MapsGrid.jsx b/web/client/plugins/maps/MapsGrid.jsx index d27d503fd9..c91f5973b0 100644 --- a/web/client/plugins/maps/MapsGrid.jsx +++ b/web/client/plugins/maps/MapsGrid.jsx @@ -6,55 +6,61 @@ * LICENSE file in the root directory of this source tree. */ -const {bindActionCreators} = require('redux'); +const {compose, branch, withProps} = require('recompose'); const {connect} = require('react-redux'); -const {loadMaps, updateMapMetadata, deleteMap, createThumbnail, - updateDetails, deleteDetails, saveDetails, toggleDetailsSheet, toggleGroupProperties, toggleUnsavedChanges, setDetailsChanged, - deleteThumbnail, saveMap, thumbnailError, saveAll, onDisplayMetadataEdit, resetUpdating, - backDetails, undoDetails, updateAttribute} = require('../../actions/maps'); -const {editMap, updateCurrentMap, errorCurrentMap, removeThumbnail, resetCurrentMap} = require('../../actions/currentMap'); +const {loadMaps, deleteMap, onDisplayMetadataEdit, + updateAttribute, showDetailsSheet, hideDetailsSheet} = require('../../actions/maps'); +const {mapSaved} = require('../../actions/config'); +const {editMap, resetCurrentMap} = require('../../actions/currentMap'); const {mapTypeSelector} = require('../../selectors/maptype'); const {showMapDetailsSelector} = require('../../selectors/maps.js'); +const {userSelector} = require('../../selectors/security'); +const {basicSuccess, basicError} = require('../../utils/NotificationUtils'); const withShareTool = require('../../components/resources/enhancers/withShareTool').default; +const SaveModal = require('../../components/resources/modals/Save'); +const handleSave = require('../../components/resources/modals/enhancers/handleSave').default; +const handleSaveModal = require('../../components/resources/modals/enhancers/handleSaveModal').default; +const handleResourceDownload = require('../../components/resources/modals/enhancers/handleResourceDownload'); +const MetadataModal = compose( + handleResourceDownload, + branch( + ({ resource }) => resource && resource.id, + compose( + handleSave, + handleSaveModal + ) + ) +)(SaveModal); + const MapsGrid = connect((state) => { return { bsSize: "small", currentMap: state.currentMap, showMapDetails: showMapDetailsSelector(state), loading: state.maps && state.maps.loading, - mapType: mapTypeSelector(state) + mapType: mapTypeSelector(state), + user: userSelector(state) }; }, dispatch => { return { loadMaps: (...params) => dispatch(loadMaps(...params)), - updateMapMetadata: (...params) => dispatch(updateMapMetadata(...params)), editMap: (...params) => dispatch(editMap(...params)), - saveMap: (...params) => dispatch(saveMap(...params)), - removeThumbnail: (...params) => dispatch(removeThumbnail(...params)), onDisplayMetadataEdit: (...params) => dispatch(onDisplayMetadataEdit(...params)), - resetUpdating: (...params) => dispatch(resetUpdating(...params)), - saveAll: (...params) => dispatch(saveAll(...params)), - updateCurrentMap: (...params) => dispatch(updateCurrentMap(...params)), - errorCurrentMap: (...params) => dispatch(errorCurrentMap(...params)), - thumbnailError: (...params) => dispatch(thumbnailError(...params)), - createThumbnail: (...params) => dispatch(createThumbnail(...params)), - deleteThumbnail: (...params) => dispatch(deleteThumbnail(...params)), deleteMap: (...params) => dispatch(deleteMap(...params)), resetCurrentMap: (...params) => dispatch(resetCurrentMap(...params)), onUpdateAttribute: (...params) => dispatch(updateAttribute(...params)), - detailsSheetActions: bindActionCreators({ - onBackDetails: backDetails, - onUndoDetails: undoDetails, - onToggleDetailsSheet: toggleDetailsSheet, - onToggleGroupProperties: toggleGroupProperties, - onToggleUnsavedChangesModal: toggleUnsavedChanges, - onsetDetailsChanged: setDetailsChanged, - onUpdateDetails: updateDetails, - onSaveDetails: saveDetails, - onDeleteDetails: deleteDetails - }, dispatch) + onSaveSuccess: () => dispatch(basicSuccess({message: 'resources.successSaved'})), + onSaveError: () => dispatch(basicError({message: 'resource.savingError'})), + onMapSaved: (...params) => dispatch(mapSaved(...params)), + onShowDetailsSheet: (...params) => dispatch(showDetailsSheet(...params)), + onHideDetailsSheet: (...params) => dispatch(hideDetailsSheet(...params)) }; })(require('../../components/maps/MapGrid')); -module.exports = withShareTool(MapsGrid); +module.exports = compose( + withProps({ + metadataModal: MetadataModal + }), + withShareTool +)(MapsGrid); diff --git a/web/client/plugins/maps/MetadataModal.jsx b/web/client/plugins/maps/MetadataModal.jsx deleted file mode 100644 index d9d5d2d7dd..0000000000 --- a/web/client/plugins/maps/MetadataModal.jsx +++ /dev/null @@ -1,31 +0,0 @@ -/* -* Copyright 2018, 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 {connect} = require('react-redux'); -const {metadataChanged} = require('../../actions/maps'); -const {loadPermissions, updatePermissions, loadAvailableGroups} = require('../../actions/maps'); -const {updateCurrentMapPermissions, addCurrentMapPermission} = require('../../actions/currentMap'); -const {setControlProperty} = require('../../actions/controls'); -const {showMapDetailsSelector} = require('../../selectors/maps.js'); - -const MetadataModal = connect( - (state = {}) => ({ - metadata: state.currentMap.metadata, - showDetailsRow: showMapDetailsSelector(state), - 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, metadataChanged, - onNewGroupChoose: setControlProperty.bind(null, 'permissionEditor', 'newGroup'), - onNewPermissionChoose: setControlProperty.bind(null, 'permissionEditor', 'newPermission') - }, null)(require('../../components/maps/modals/MetadataModal')); - -module.exports = MetadataModal; diff --git a/web/client/product/components/home/MapsList.jsx b/web/client/product/components/home/MapsList.jsx index dc342bd1d4..06ed43f778 100644 --- a/web/client/product/components/home/MapsList.jsx +++ b/web/client/product/components/home/MapsList.jsx @@ -11,8 +11,8 @@ var React = require('react'); var I18N = require('../../../components/I18N/I18N'); var {Label, FormControl, FormGroup} = require('react-bootstrap'); const {connect} = require('react-redux'); -const {updateMapMetadata, deleteMap, createThumbnail} = require('../../../actions/maps'); -const MapGrid = connect(() => ({}), {updateMapMetadata, deleteMap, createThumbnail})(require('../../../components/maps/MapGrid')); +const {deleteMap} = require('../../../actions/maps'); +const MapGrid = connect(() => ({}), {deleteMap})(require('../../../components/maps/MapGrid')); class MapsList extends React.Component { static propTypes = { diff --git a/web/client/product/plugins.js b/web/client/product/plugins.js index 52604836e5..b755987121 100644 --- a/web/client/product/plugins.js +++ b/web/client/product/plugins.js @@ -44,7 +44,7 @@ module.exports = { DrawerMenuPlugin: require('../plugins/DrawerMenu'), ExpanderPlugin: require('../plugins/Expander'), FeatureEditorPlugin: require('../plugins/FeatureEditor').default, - FeaturedMaps: require('../plugins/FeaturedMaps'), + FeaturedMaps: require('../plugins/FeaturedMaps').default, FeedbackMaskPlugin: require('../plugins/FeedbackMask'), FilterLayerPlugin: require('../plugins/FilterLayer').default, FloatingLegendPlugin: require('../plugins/FloatingLegend'), @@ -78,7 +78,7 @@ module.exports = { MapLoadingPlugin: require('../plugins/MapLoading'), MapPlugin: require('../plugins/Map'), MapSearchPlugin: require('../plugins/MapSearch'), - MapsPlugin: require('../plugins/Maps'), + MapsPlugin: require('../plugins/Maps').default, MapCatalogPlugin: require('../plugins/MapCatalog').default, MapTemplatesPlugin: require('../plugins/MapTemplates').default, MeasurePlugin: require('../plugins/Measure'), diff --git a/web/client/reducers/__tests__/featuredmaps-test.js b/web/client/reducers/__tests__/featuredmaps-test.js index ee1c82379f..b4338a7c5a 100644 --- a/web/client/reducers/__tests__/featuredmaps-test.js +++ b/web/client/reducers/__tests__/featuredmaps-test.js @@ -6,89 +6,15 @@ * LICENSE file in the root directory of this source tree. */ const expect = require('expect'); -const {attributeUpdated, mapDeleted, mapMetadataUpdated, permissionsUpdated, mapsLoading, setFeaturedMapsEnabled, setFeaturedMapsLatestResource} = require('../../actions/maps'); -const {dashboardDeleted} = require('../../actions/dashboards'); -const { isFeaturedMapsEnabled } = require('../../selectors/featuredmaps'); +const {setFeaturedMapsEnabled} = require('../../actions/maps'); +const {isFeaturedMapsEnabled} = require('../../selectors/featuredmaps'); const featuredmaps = require('../featuredmaps'); describe('Test the featuredmaps reducer', () => { - - it('setEditorAvailable action', () => { - const resourceId = 1; - const name = 'featured'; - const value = 'true'; - const type = 'type'; - const error = null; - const state = featuredmaps({}, attributeUpdated(resourceId, name, value, type, error)); - expect(state.latestResource).toEqual({ - resourceId, - featured: value - }); - }); - - it('mapDeleted action', () => { - const resourceId = 1; - const result = 'result'; - const error = null; - const state = featuredmaps({}, mapDeleted(resourceId, result, error)); - expect(state.latestResource).toEqual({ - resourceId, - deleted: true - }); - }); - - it('dashboardDeleted action', () => { - const resourceId = 1; - const result = 'result'; - const error = null; - const state = featuredmaps({}, dashboardDeleted(resourceId, result, error)); - expect(state.latestResource).toEqual({ - resourceId, - deleted: true - }); - }); - - it('mapMetadataUpdated action', () => { - const resourceId = 1; - const name = 'name'; - const description = 'description'; - const state = featuredmaps({}, mapMetadataUpdated(resourceId, name, description, 'success')); - expect(state.latestResource).toEqual({ - resourceId, - name, - description - }); - }); - - it('permissionsUpdated action', () => { - const resourceId = 1; - const state = featuredmaps({}, permissionsUpdated(resourceId, 'success')); - expect(state.latestResource).toEqual({ - resourceId, - permission: 'updated' - }); - }); - - it('permissionsUpdated action', () => { - const searchText = 'text'; - const state = featuredmaps({}, mapsLoading(searchText, {})); - expect(state.searchText).toEqual(searchText); - }); it('featuredmaps enabled', () => { const fm = featuredmaps({}, setFeaturedMapsEnabled(true) ); expect(isFeaturedMapsEnabled({ featuredmaps: fm })).toBe(true); }); - - it('setFeaturedMapsLatestResource action', () => { - const resource = { - resourceId: 1, - name: "name", - description: "description" - }; - const state = featuredmaps({}, setFeaturedMapsLatestResource(resource)); - expect(state.latestResource).toEqual(resource); - }); - }); diff --git a/web/client/reducers/__tests__/maps-test.js b/web/client/reducers/__tests__/maps-test.js index e2de4e365f..080b532175 100644 --- a/web/client/reducers/__tests__/maps-test.js +++ b/web/client/reducers/__tests__/maps-test.js @@ -9,9 +9,8 @@ const expect = require('expect'); const maps = require('../maps'); const { - mapsLoaded, mapsLoading, loadError, mapCreated, mapUpdating, - mapMetadataUpdated, mapDeleting, attributeUpdated, thumbnailError, mapError, permissionsLoading, - permissionsLoaded, saveMap, permissionsUpdated, resetUpdating, + mapsLoaded, mapsLoading, loadError, mapCreated, + mapDeleting, mapsSearchTextChanged, setShowMapDetails} = require('../../actions/maps'); const sampleMap = { @@ -35,10 +34,6 @@ const mapsSampleResult = { ], totalCount: 1 }; -const SecurityRule = {canRead: true, canWrite: false}; -const permissions = { - SecurityRuleList: {SecurityRule} -}; describe('Test the maps reducer', () => { it('on default state and unknown action, ', () => { let state = maps(undefined, {type: "____NOT_EXISTING_ACTION____"}); @@ -95,32 +90,6 @@ describe('Test the maps reducer', () => { let state = maps(null, loadError("ERROR")); expect(state.loadingError).toBe("ERROR"); }); - it('on mapUpdating, mapMetadataUpdated, attributeUpdated, permissionsUpdated and resetUpdating, thumbnailError, mapError', () => { - let state = maps(null, mapsLoaded(mapsSampleResult, "TEST", { - start: 0, - limit: 10 - })); - state = maps(state, mapUpdating(sampleMap.id)); - expect(state.results[0].updating).toBe(true); - state = maps(state, resetUpdating(sampleMap.id)); - expect(state.results[0].updating).toBe(false); - state = maps(state, mapUpdating(sampleMap.id)); - expect(state.results[0].updating).toBe(true); - state = maps(state, mapMetadataUpdated(sampleMap.id, "newName", "newDescription" )); - expect(state.results[0].updating).toBe(false); - expect(state.results[0].name).toBe("newName"); - expect(state.results[0].description).toBe("newDescription"); - state = maps(state, attributeUpdated(sampleMap.id, "attr", "newValue" )); - expect(state.results[0].attr).toBe("newValue"); - state = maps(state, permissionsUpdated(sampleMap.id, "ERROR" )); - expect(state.results[0].loadingError).toBe("ERROR"); - state = maps(state, mapUpdating(sampleMap.id)); - expect(state.results[0].updating).toBe(true); - state = maps(state, thumbnailError(sampleMap.id)); - expect(state.results[0].updating).toBe(false); - state = maps(state, mapError(sampleMap.id)); - expect(state.results[0].updating).toBe(false); - }); it('on mapCreated, mapDeleting and mapDeleted', () => { let state = maps(null, mapsLoaded(mapsSampleResult, "TEST", { start: 0, @@ -133,47 +102,4 @@ describe('Test the maps reducer', () => { expect(state.totalCount).toBe(2); }); - it('on saveMap', () => { - let state = maps(null, mapsLoaded(mapsSampleResult, "TEST", { - start: 0, - limit: 10 - })); - state = maps(state, saveMap({ - newThumbnail: "THUMB", - thumbnailError: "ERR", - thumbnail: "thumb" - }, sampleMap.id)); - - expect(state.results[0].thumbnail).toBe("thumb"); - expect(state.results[0].thumbnailError).toBe("ERR"); - expect(state.results[0].newThumbnail).toBe("THUMB"); - }); - it('on permissionsLoading and permissionsLoaded', () => { - let state = maps(null, mapsLoaded(mapsSampleResult, "TEST", { - start: 0, - limit: 10 - })); - state = maps(state, permissionsLoading(sampleMap.id)); - expect(state.results[0].permissionLoading).toBe(true); - - state = maps(state, permissionsLoaded(permissions, sampleMap.id)); - expect(state.results[0].permissionLoading).toBe(false); - expect(state.results[0].permissions).toExist(); - expect(state.results[0].permissions.SecurityRuleList).toExist(); - expect(state.results[0].permissions.SecurityRuleList.SecurityRule).toExist(); - expect(state.results[0].permissions.SecurityRuleList.SecurityRule.length).toBe(1); - state = maps(state, permissionsLoaded({ - SecurityRuleList: {SecurityRule: [SecurityRule]} - }, sampleMap.id)); - expect(state.results[0].permissions.SecurityRuleList).toExist(); - expect(state.results[0].permissions.SecurityRuleList.SecurityRule).toExist(); - expect(state.results[0].permissions.SecurityRuleList.SecurityRule.length).toBe(1); - - // check permission list loading doesn't fail if permission is undefined or null - // i.e. when some error occurs loading permissions - const errorState = maps({}, permissionsLoaded(null, sampleMap.id)); - expect(errorState).toExist(); - expect(errorState.results).toExist(); - }); - }); diff --git a/web/client/reducers/currentMap.js b/web/client/reducers/currentMap.js index 7b9881fd4d..15ab9503fa 100644 --- a/web/client/reducers/currentMap.js +++ b/web/client/reducers/currentMap.js @@ -9,8 +9,6 @@ // const {isNil} = require('lodash'); const { EDIT_MAP, - UPDATE_CURRENT_MAP, - ERROR_CURRENT_MAP, UPDATE_CURRENT_MAP_PERMISSIONS, UPDATE_CURRENT_MAP_GROUPS, RESET_CURRENT_MAP, @@ -20,22 +18,14 @@ const { const { THUMBNAIL_ERROR, MAP_UPDATING, - SAVE_MAP, DISPLAY_METADATA_EDIT, RESET_UPDATING, MAP_ERROR, MAP_CREATED, PERMISSIONS_LIST_LOADING, - PERMISSIONS_LIST_LOADED, - TOGGLE_DETAILS_SHEET, + SHOW_DETAILS_SHEET, + HIDE_DETAILS_SHEET, UPDATE_DETAILS, - SAVE_DETAILS, - DELETE_DETAILS, - BACK_DETAILS, - UNDO_DETAILS, - TOGGLE_GROUP_PROPERTIES, - TOGGLE_UNSAVED_CHANGES, - SET_DETAILS_CHANGED, SET_UNSAVED_CHANGES, METADATA_CHANGED, DETAILS_SAVING, @@ -65,13 +55,6 @@ function currentMap(state = {}, action) { editDetailsDisabled: !state.editDetailsDisabled }); } - case UPDATE_CURRENT_MAP: { - return assign({}, state, { - newThumbnail: action.thumbnail, - thumbnailData: action.thumbnailData, - unsavedChanges: true - }); - } case MAP_UPDATING: { return assign({}, state, {updating: true}); } @@ -105,18 +88,12 @@ function currentMap(state = {}, action) { } return assign({}, state, { permissions: newPermissions }); } - case ERROR_CURRENT_MAP: { - return assign({}, state, {thumbnailError: null, mapError: null, errors: action.errors}); - } case THUMBNAIL_ERROR: { return assign({}, state, {thumbnailError: action.error, errors: [], updating: false}); } case MAP_ERROR: { return assign({}, state, {mapError: action.error, errors: [], updating: false}); } - case SAVE_MAP: { - return assign({}, state, {thumbnailError: null}); - } case DISPLAY_METADATA_EDIT: { return assign({}, state, {displayMetadataEdit: action.displayMetadataEditValue}); } @@ -129,18 +106,20 @@ function currentMap(state = {}, action) { case PERMISSIONS_LIST_LOADING: { return assign({}, state, {permissionLoading: true}); } - case PERMISSIONS_LIST_LOADED: { - return assign({}, state, {permissionLoading: false}); - } case RESET_CURRENT_MAP: { return {}; } - case TOGGLE_DETAILS_SHEET: { - return assign({}, state, { - showDetailEditor: !state.showDetailEditor, - detailsBackup: !state.showDetailEditor && !state.detailsDeleted ? "" : state.detailsBackup, - detailsSheetReadOnly: action.detailsSheetReadOnly - }); + case SHOW_DETAILS_SHEET: { + return { + ...state, + showDetailsSheet: true + }; + } + case HIDE_DETAILS_SHEET: { + return { + ...state, + showDetailsSheet: false + }; } case METADATA_CHANGED: { let prop = action.prop; @@ -158,62 +137,16 @@ function currentMap(state = {}, action) { detailsBackup: action.doBackup ? state.detailsText : state.detailsBackup }); } - case BACK_DETAILS: { - return assign({}, state, { - detailsText: state.detailsDeleted ? "" : action.backupDetails, - detailsBackup: state.detailsDeleted ? state.detailsBackup : "", - showDetailEditor: false - }); - } - case UNDO_DETAILS: { - return assign({}, state, { - detailsText: state.detailsBackup, - detailsBackup: "", - detailsDeleted: false - }); - } - case SAVE_DETAILS: { - return action.detailsText.length <= 500000 ? assign({}, state, { - detailsText: action.detailsText, - detailsBackup: "", - detailsDeleted: false - }) : state; - } case DETAILS_SAVING: { return assign({}, state, { saving: action.saving }); } - case DELETE_DETAILS: { - return assign({}, state, { - detailsText: "", - detailsBackup: state.detailsText, - detailsChanged: true, - unsavedChanges: true, - detailsDeleted: true - }); - } case SET_UNSAVED_CHANGES: { return assign({}, state, { unsavedChanges: action.value }); } - case TOGGLE_GROUP_PROPERTIES: { - return assign({}, state, { - hideGroupProperties: !state.hideGroupProperties - }); - } - case TOGGLE_UNSAVED_CHANGES: { - return assign({}, state, { - showUnsavedChanges: !state.showUnsavedChanges - }); - } - case SET_DETAILS_CHANGED: { - return assign({}, state, { - unsavedChanges: action.detailsChanged ? action.detailsChanged : state.unsavedChanges, - detailsChanged: action.detailsChanged - }); - } default: return state; } diff --git a/web/client/reducers/featuredmaps.js b/web/client/reducers/featuredmaps.js index c84dea09a2..78f678897e 100644 --- a/web/client/reducers/featuredmaps.js +++ b/web/client/reducers/featuredmaps.js @@ -6,45 +6,12 @@ * LICENSE file in the root directory of this source tree. */ -const { ATTRIBUTE_UPDATED, MAP_DELETED, MAP_METADATA_UPDATED, PERMISSIONS_UPDATED, MAPS_LIST_LOADING, FEATURED_MAPS_SET_ENABLED, FEATURED_MAPS_SET_LATEST_RESOURCE} = require('../actions/maps'); -const { DASHBOARD_DELETED } = require('../actions/dashboards'); +const { MAPS_LIST_LOADING, FEATURED_MAPS_SET_ENABLED, INVALIDATE_FEATURED_MAPS } = require('../actions/maps'); const {set} = require('../utils/ImmutableUtils'); function dashboard(state = {}, action) { switch (action.type) { - case ATTRIBUTE_UPDATED: { - return set("latestResource", { - resourceId: action.resourceId, - // this decode is for backward compatibility with old linked resources`rest%2Fgeostore%2Fdata%2F2%2Fraw%3Fdecode%3Ddatauri` not needed for new ones `rest/geostore/data/2/raw?decode=datauri` - [action.name]: decodeURIComponent(action.value) - }, state); - } - case MAP_DELETED: { - return set("latestResource", { - resourceId: action.resourceId, - deleted: true - }, state); - } - case DASHBOARD_DELETED: { - return set("latestResource", { - resourceId: action.id, - deleted: true - }, state); - } - case MAP_METADATA_UPDATED: { - return set("latestResource", { - resourceId: action.resourceId, - name: action.newName, - description: action.newDescription - }, state); - } - case PERMISSIONS_UPDATED: { - return set("latestResource", { - resourceId: action.resourceId, - permission: 'updated' - }, state); - } case MAPS_LIST_LOADING: { return {...state, searchText: action.searchText @@ -53,8 +20,8 @@ function dashboard(state = {}, action) { case FEATURED_MAPS_SET_ENABLED: { return set("enabled", action.enabled, state); } - case FEATURED_MAPS_SET_LATEST_RESOURCE: { - return set("latestResource", action.resource, state); + case INVALIDATE_FEATURED_MAPS: { + return set("invalidate", !state.invalidate, state); } default: return state; diff --git a/web/client/reducers/maps.js b/web/client/reducers/maps.js index 8231ca8e1f..0ace8bbab6 100644 --- a/web/client/reducers/maps.js +++ b/web/client/reducers/maps.js @@ -8,14 +8,14 @@ const { MAPS_LIST_LOADED, MAPS_LIST_LOADING, MAPS_LIST_LOAD_ERROR, MAP_CREATED, MAP_ERROR, MAP_UPDATING, - MAP_METADATA_UPDATED, MAP_DELETING, ATTRIBUTE_UPDATED, PERMISSIONS_LIST_LOADING, - PERMISSIONS_LIST_LOADED, SAVE_MAP, PERMISSIONS_UPDATED, THUMBNAIL_ERROR, RESET_UPDATING, + MAP_DELETING, ATTRIBUTE_UPDATED, PERMISSIONS_LIST_LOADING, + THUMBNAIL_ERROR, RESET_UPDATING, MAPS_SEARCH_TEXT_CHANGED, SEARCH_FILTER_CHANGED, SET_SEARCH_FILTER, SET_CONTEXTS, LOADING, METADATA_CHANGED, SHOW_DETAILS} = require('../actions/maps'); const { EDIT_MAP, RESET_CURRENT_MAP} = require('../actions/currentMap'); const assign = require('object-assign'); -const {isArray, isNil} = require('lodash'); +const {isNil} = require('lodash'); /** * Manages the state of the maps list search with it's results * The properties represent the shape of the state @@ -150,16 +150,6 @@ function maps(state = { } return assign({}, state, {results: newMaps}); } - case MAP_METADATA_UPDATED: { - let newMaps = state.results === "" ? [] : [...state.results]; - - for (let i = 0; i < newMaps.length; i++) { - if (newMaps[i].id && newMaps[i].id === action.resourceId ) { - newMaps[i] = assign({}, newMaps[i], {description: action.newDescription, name: action.newName, updating: false}); - } - } - return assign({}, state, {results: newMaps}); - } case ATTRIBUTE_UPDATED: { let newMaps = state.results === "" ? [] : [...state.results]; for (let i = 0; i < newMaps.length; i++) { @@ -170,15 +160,6 @@ function maps(state = { } return assign({}, state, {results: newMaps}); } - case PERMISSIONS_UPDATED: { - let newMaps = state.results === "" ? [] : [...state.results]; - for (let i = 0; i < newMaps.length; i++) { - if (newMaps[i].id && newMaps[i].id === action.resourceId) { - newMaps[i] = assign({}, newMaps[i], { loadingError: action.error ? action.error : null}); - } - } - return assign({}, state, {results: newMaps}); - } case MAP_DELETING: { let newMaps = state.results === "" ? [] : [...state.results]; @@ -198,21 +179,6 @@ function maps(state = { }; return assign({}, state, newMapsState); } - case SAVE_MAP: { - let newMaps = state.results === "" ? [] : [...state.results]; - - for (let i = 0; i < newMaps.length; i++) { - if (newMaps[i].id && newMaps[i].id === action.resourceId ) { - newMaps[i] = assign({}, newMaps[i], { - files: action.map && action.map.files, - errors: action.map && action.map.errors, - newThumbnail: action.map && action.map.newThumbnail, - thumbnailError: action.map && action.map.thumbnailError, - thumbnail: action.map && action.map.thumbnail }); - } - } - return assign({}, state, {results: newMaps}); - } case THUMBNAIL_ERROR: case MAP_ERROR: case RESET_UPDATING: { let newMaps = state.results === "" ? [] : [...state.results]; @@ -237,37 +203,6 @@ function maps(state = { ); return newState; } - case PERMISSIONS_LIST_LOADED: { - let newMaps = state.results === "" ? [] : [...(state.results || [])]; - // TODO: Add the fix for GeoStore single-item arrays - let newState = assign({}, state, { - results: newMaps.map(function(map) { - if (map.id === action.mapId) { - - // Fix to overcome GeoStore bad encoding of single object arrays - let fixedSecurityRule = []; - if (action.permissions && action.permissions.SecurityRuleList && action.permissions.SecurityRuleList.SecurityRule) { - if ( isArray(action.permissions.SecurityRuleList.SecurityRule)) { - fixedSecurityRule = action.permissions.SecurityRuleList.SecurityRule; - } else { - fixedSecurityRule.push(action.permissions.SecurityRuleList.SecurityRule); - } - } - - return assign({}, map, { - permissionLoading: false, - permissions: { - SecurityRuleList: { - SecurityRule: fixedSecurityRule - } - }}); - } - return map; - }) - } - ); - return newState; - } default: return state; } diff --git a/web/client/selectors/__tests__/featuredmaps-test.js b/web/client/selectors/__tests__/featuredmaps-test.js index 8f1fceac14..1909307c17 100644 --- a/web/client/selectors/__tests__/featuredmaps-test.js +++ b/web/client/selectors/__tests__/featuredmaps-test.js @@ -8,24 +8,11 @@ const expect = require('expect'); const { - resourceSelector, searchTextSelector, isFeaturedMapsEnabled } = require('../featuredmaps'); describe('featuredMaps selectors', () => { - it('test resourceSelector selector', () => { - const state = { - featuredmaps: { - latestResource: { - resourceId: '01' - } - } - }; - expect(resourceSelector(state)).toBe(state.featuredmaps.latestResource); - - expect(resourceSelector()).toEqual({}); - }); it('test searchTextSelector', () => { const state = { diff --git a/web/client/selectors/currentmap.js b/web/client/selectors/currentmap.js index efe9da40de..000546d4de 100644 --- a/web/client/selectors/currentmap.js +++ b/web/client/selectors/currentmap.js @@ -24,6 +24,7 @@ const currentMapDetailsTextSelector = (state) => get(state, "currentMap.detailsT const currentMapThumbnailUriSelector = (state) => get(state, "currentMap.thumbnail", ""); const currentMapDetailsChangedSelector = (state) => get(state, "currentMap.detailsChanged", false); const currentMapOriginalDetailsTextSelector = (state) => get(state, "currentMap.originalDetails", false); + module.exports = { currentMapSelector, currentMapIdSelector, diff --git a/web/client/selectors/featuredmaps.js b/web/client/selectors/featuredmaps.js index 21342a4623..9dbb43a742 100644 --- a/web/client/selectors/featuredmaps.js +++ b/web/client/selectors/featuredmaps.js @@ -15,13 +15,12 @@ const { get } = require('lodash'); module.exports = { /** - * selects latestResource from featuredmaps, it's the latest resource updated - * it's needed to update the current featured maps list + * invalidation flag, triggers featuredmaps reload when changed * @memberof selectors.featuredmaps - * @param {object} state applications state - * @return {object} latestResource object eg {resourceId: 7, ...otherInfo} + * @param {object} state applications state + * @return {boolean} invalidation flag value */ - resourceSelector: state => state && state.featuredmaps && state.featuredmaps.latestResource || {}, + invalidationSelector: state => state && state.featuredmaps && state.featuredmaps.invalidate || false, /** * selects searchText from featuredmaps, it's updated only on map list loading (press enter on search map) * @memberof selectors.featuredmaps diff --git a/web/client/translations/data.en-US.json b/web/client/translations/data.en-US.json index 805d5eb7e9..0d68bb1623 100644 --- a/web/client/translations/data.en-US.json +++ b/web/client/translations/data.en-US.json @@ -346,6 +346,7 @@ }, "resources": { "successSaved": "This resource has been saved correctly", + "savingError": "There was an error saving the resource", "deleteConfirmTitle": "Are you sure", "deleteConfirmMessage": "Are you sure you want to delete this resource?", "deleteConfirmButtonText": "Delete", diff --git a/web/client/utils/ObservableUtils.js b/web/client/utils/ObservableUtils.js index 6485af9155..c1955073ee 100644 --- a/web/client/utils/ObservableUtils.js +++ b/web/client/utils/ObservableUtils.js @@ -3,9 +3,6 @@ const {get} = require('lodash'); const {parseString} = require('xml2js'); const {stripPrefix} = require('xml2js/lib/processors'); const GeoStoreApi = require('../api/GeoStoreDAO'); -const {updatePermissions, updateAttribute, doNothing} = require('../actions/maps'); -const ConfigUtils = require('../utils/ConfigUtils'); -const {basicSuccess, basicError} = require('../utils/NotificationUtils'); class OGCError extends Error { constructor(message, code) { @@ -41,51 +38,6 @@ const interceptOGCError = (observable) => observable.switchMap(response => { return Rx.Observable.of(response); }); -const createAssociatedResource = ({attribute, permissions, mapId, metadata, value, category, type, optionsRes, optionsAttr} = {}) => { - return Rx.Observable.fromPromise( - GeoStoreApi.createResource(metadata, value, category, optionsRes) - .then(res => res.data)) - .switchMap((resourceId) => { - // update permissions - let actions = []; - actions.push(updatePermissions(resourceId, permissions)); - const attributeUri = ConfigUtils.getDefaults().geoStoreUrl + "data/" + resourceId + "/raw?decode=datauri"; - // UPDATE resource map with new attribute - actions.push(updateAttribute(mapId, attribute, attributeUri, type, optionsAttr)); - // display a success message - actions.push(basicSuccess({message: "maps.feedback." + attribute + ".savedSuccesfully" })); - return Rx.Observable.from(actions); - }) - .catch(() => Rx.Observable.of(basicError({message: "maps.feedback.errorWhenSaving"}))); -}; - -const updateAssociatedResource = ({permissions, resourceId, value, attribute, options} = {}) => { - return Rx.Observable.fromPromise(GeoStoreApi.putResource(resourceId, value, options) - .then(res => res.data)) - .switchMap((id) => { - let actions = []; - actions.push(basicSuccess({ message: "maps.feedback." + attribute + ".updatedSuccesfully"})); - actions.push(updatePermissions(id, permissions)); - return Rx.Observable.from(actions); - }) - .catch(() => Rx.Observable.of(basicError({message: "maps.feedback.errorWhenUpdating"}))); -}; -const deleteAssociatedResource = ({mapId, attribute, type, resourceId, options} = {}) => { - return Rx.Observable.fromPromise(GeoStoreApi.deleteResource(resourceId, options) - .then(res => res.status === 204)) - .switchMap((deleted) => { - let actions = []; - if (deleted) { - actions.push(basicSuccess({ message: "maps.feedback." + attribute + ".deletedSuccesfully" })); - actions.push(updateAttribute(mapId, attribute, "NODATA", type, options)); - return Rx.Observable.from(actions); - } - actions.push(doNothing()); - return Rx.Observable.from(actions); - }) - .catch(() => Rx.Observable.of(basicError({message: "maps.feedback.errorWhenDeleting"}))); -}; - const deleteResourceById = (resId, options) => resId ? GeoStoreApi.deleteResource(resId, options) .then((res) => {return {data: res.data, resType: "success", error: null}; }) @@ -95,8 +47,5 @@ const deleteResourceById = (resId, options) => resId ? module.exports = { parseXML, deleteResourceById, - createAssociatedResource, - updateAssociatedResource, - deleteAssociatedResource, interceptOGCError }; From f04983205bd29c2005934afb0ca779193c391e84 Mon Sep 17 00:00:00 2001 From: Vladislav Shatilenya Date: Thu, 3 Sep 2020 19:40:09 +0300 Subject: [PATCH 2/5] added translations --- web/client/translations/data.de-DE.json | 1 + web/client/translations/data.es-ES.json | 1 + web/client/translations/data.fr-FR.json | 1 + web/client/translations/data.it-IT.json | 1 + 4 files changed, 4 insertions(+) diff --git a/web/client/translations/data.de-DE.json b/web/client/translations/data.de-DE.json index be6b4951ca..00fd5b764d 100644 --- a/web/client/translations/data.de-DE.json +++ b/web/client/translations/data.de-DE.json @@ -346,6 +346,7 @@ }, "resources": { "successSaved": "Diese Ressource wurde korrekt gespeichert", + "savingError": "Beim Speichern der Ressource ist ein Fehler aufgetreten", "deleteConfirmTitle": "Bist du sicher", "deleteConfirmMessage": "Möchten Sie diese Ressource wirklich löschen?", "deleteConfirmButtonText": "Löschen", diff --git a/web/client/translations/data.es-ES.json b/web/client/translations/data.es-ES.json index fe30b3341f..ae5cdd846b 100644 --- a/web/client/translations/data.es-ES.json +++ b/web/client/translations/data.es-ES.json @@ -346,6 +346,7 @@ }, "resources": { "successSaved": "Este recurso se ha guardado correctamente", + "savingError": "Hubo un error al guardar el recurso", "deleteConfirmTitle": "¿Estás seguro?", "deleteConfirmMessage": "¿Estás seguro de que quieres eliminar este recurso?", "deleteConfirmButtonText": "Eliminar", diff --git a/web/client/translations/data.fr-FR.json b/web/client/translations/data.fr-FR.json index 4d941e4b43..e0c15ef6e3 100644 --- a/web/client/translations/data.fr-FR.json +++ b/web/client/translations/data.fr-FR.json @@ -346,6 +346,7 @@ }, "resources": { "successSaved": "Cette ressource a été enregistrée correctement", + "savingError": "Une erreur s'est produite lors de l'enregistrement de la ressource", "deleteConfirmTitle": "Êtes-vous sûr", "deleteConfirmMessage": "Êtes-vous sûr de vouloir supprimer cette ressource ?", "deleteConfirmButtonText": "Supprimer", diff --git a/web/client/translations/data.it-IT.json b/web/client/translations/data.it-IT.json index e8e96efbcb..57323df03c 100644 --- a/web/client/translations/data.it-IT.json +++ b/web/client/translations/data.it-IT.json @@ -346,6 +346,7 @@ }, "resources": { "successSaved": "Questa risorsa è stata salvata correttamente", + "savingError": "Si è verificato un errore durante il salvataggio della risorsa", "deleteConfirmTitle": "Sei sicuro", "deleteConfirmMessage": "Sei sicuro di voler eliminare questa risorsa?", "deleteConfirmButtonText": "Elimina", From 4496da1059c5ad96210dbb4da66ef114b5df6e08 Mon Sep 17 00:00:00 2001 From: Vladislav Shatilenya Date: Tue, 8 Sep 2020 19:36:53 +0300 Subject: [PATCH 3/5] post review rework --- .../actions/__tests__/currentMap-test.js | 89 ------- web/client/actions/__tests__/maps-test.js | 9 - web/client/actions/currentMap.js | 61 ----- web/client/actions/maps.js | 53 +--- web/client/components/map/openlayers/Map.jsx | 5 - web/client/components/maps/MapCard.jsx | 227 ------------------ web/client/components/maps/MapGrid.jsx | 142 ----------- .../maps/__tests__/MapCard-test.jsx | 149 ------------ .../maps/__tests__/MapGrid-test.jsx | 47 ---- .../components/resources/ResourceCard.jsx | 18 +- .../components/resources/ResourceGrid.jsx | 109 +++++---- .../resources/enhancers/resourceGrid.js | 39 ++- .../components/resources/modals/Save.jsx | 74 +----- .../modals/enhancers/handleDetails.js | 24 ++ .../modals/enhancers/handleDetails.jsx | 78 ------ .../modals/enhancers/handleDetailsDownload.js | 49 ++++ .../modals/enhancers/handleDetailsRow.js | 21 ++ .../modals/enhancers/handleResourceData.jsx | 7 +- .../enhancers/handleResourceDownload.js | 2 +- .../modals/enhancers/handleSaveModal.js | 6 +- .../resources/modals/fragments/Details.jsx | 65 +++++ .../resources/modals/fragments/DetailsRow.jsx | 33 ++- .../modals/fragments/DetailsSheet.jsx | 19 +- .../resources/modals/fragments/ErrorBox.jsx | 2 +- web/client/epics/__tests__/maps-test.js | 6 +- web/client/epics/maps.js | 28 +-- web/client/plugins/Details.jsx | 3 +- web/client/plugins/FeaturedMaps.jsx | 6 +- web/client/plugins/Maps.jsx | 7 +- web/client/plugins/maps/MapsGrid.jsx | 149 +++++++----- .../product/components/home/MapsList.jsx | 53 ---- web/client/reducers/currentMap.js | 155 ------------ web/client/reducers/featuredmaps.js | 4 +- web/client/reducers/maps.js | 22 +- .../selectors/__tests__/currentmap-test.js | 77 ------ web/client/selectors/currentmap.js | 38 --- 36 files changed, 450 insertions(+), 1426 deletions(-) delete mode 100644 web/client/actions/__tests__/currentMap-test.js delete mode 100644 web/client/actions/currentMap.js delete mode 100644 web/client/components/maps/MapCard.jsx delete mode 100644 web/client/components/maps/MapGrid.jsx delete mode 100644 web/client/components/maps/__tests__/MapCard-test.jsx delete mode 100644 web/client/components/maps/__tests__/MapGrid-test.jsx create mode 100644 web/client/components/resources/modals/enhancers/handleDetails.js delete mode 100644 web/client/components/resources/modals/enhancers/handleDetails.jsx create mode 100644 web/client/components/resources/modals/enhancers/handleDetailsDownload.js create mode 100644 web/client/components/resources/modals/enhancers/handleDetailsRow.js create mode 100644 web/client/components/resources/modals/fragments/Details.jsx delete mode 100644 web/client/product/components/home/MapsList.jsx delete mode 100644 web/client/reducers/currentMap.js delete mode 100644 web/client/selectors/__tests__/currentmap-test.js delete mode 100644 web/client/selectors/currentmap.js diff --git a/web/client/actions/__tests__/currentMap-test.js b/web/client/actions/__tests__/currentMap-test.js deleted file mode 100644 index 0dfddc73c8..0000000000 --- a/web/client/actions/__tests__/currentMap-test.js +++ /dev/null @@ -1,89 +0,0 @@ -/** - * Copyright 2016, 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'); -var { - EDIT_MAP, editMap, - UPDATE_CURRENT_MAP_PERMISSIONS, updateCurrentMapPermissions, - UPDATE_CURRENT_MAP_GROUPS, updateCurrentMapGroups, - RESET_CURRENT_MAP, resetCurrentMap, - ADD_CURRENT_MAP_PERMISSION, addCurrentMapPermission -} = require('../currentMap'); - - -describe('Test correctness of the maps actions', () => { - - it('editMap', () => { - let thumbnail = "myThumnbnailUrl"; - let map = { - thumbnail: thumbnail, - id: 123, - canWrite: true - }; - var retval = editMap(map); - expect(retval).toExist(); - expect(retval.type).toBe(EDIT_MAP); - expect(retval.map.thumbnail).toBe(thumbnail); - expect(retval.map.id).toBe(123); - expect(retval.map.canWrite).toBeTruthy(); - }); - - it('updateCurrentMapGroups', () => { - let groups = { - groups: { - group: { - enabled: true, - groupName: 'everyone', - id: 3 - } - } - }; - var retval = updateCurrentMapGroups(groups); - expect(retval).toExist(); - expect(retval.type).toBe(UPDATE_CURRENT_MAP_GROUPS); - expect(retval.groups).toBe(groups); - }); - - it('updateCurrentMapPermissions', () => { - let permissions = { - SecurityRuleList: { - SecurityRule: { - canRead: true, - canWrite: true, - user: { - id: 6, - name: 'admin' - } - } - } - }; - const retval = updateCurrentMapPermissions(permissions); - expect(retval).toExist(); - expect(retval.type).toBe(UPDATE_CURRENT_MAP_PERMISSIONS); - expect(retval.permissions).toBe(permissions); - }); - it('resetCurrentMap', () => { - const retval = resetCurrentMap(); - expect(retval).toExist(); - expect(retval.type).toBe(RESET_CURRENT_MAP); - }); - it('addCurrentMapPermission', () => { - const rule = { - canRead: true, - canWrite: true, - user: { - id: 6, - name: 'admin' - } - }; - const retval = addCurrentMapPermission(rule); - expect(retval).toExist(); - expect(retval.type).toBe(ADD_CURRENT_MAP_PERMISSION); - }); - -}); diff --git a/web/client/actions/__tests__/maps-test.js b/web/client/actions/__tests__/maps-test.js index 43e84b72ce..6078b774fd 100644 --- a/web/client/actions/__tests__/maps-test.js +++ b/web/client/actions/__tests__/maps-test.js @@ -20,7 +20,6 @@ const { MAP_UPDATING, mapUpdating, DETAILS_LOADED, detailsLoaded, ATTRIBUTE_UPDATED, attributeUpdated, - DISPLAY_METADATA_EDIT, onDisplayMetadataEdit, THUMBNAIL_ERROR, thumbnailError, TOGGLE_DETAILS_EDITABILITY, toggleDetailsEditability, MAPS_SEARCH_TEXT_CHANGED, mapsSearchTextChanged, @@ -122,14 +121,6 @@ describe('Test correctness of the maps actions', () => { expect(retval.error.status).toBe(error.status); }); - it('onDisplayMetadataEdit', () => { - let dispMetadataValue = true; - let retval = onDisplayMetadataEdit(dispMetadataValue); - expect(retval).toExist(); - expect(retval.type).toBe(DISPLAY_METADATA_EDIT); - expect(retval.displayMetadataEditValue).toBe(dispMetadataValue); - }); - it('mapsSearchTextChanged', () => { const a = mapsSearchTextChanged("TEXT"); expect(a.type).toBe(MAPS_SEARCH_TEXT_CHANGED); diff --git a/web/client/actions/currentMap.js b/web/client/actions/currentMap.js deleted file mode 100644 index 6e396c5fa9..0000000000 --- a/web/client/actions/currentMap.js +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Copyright 2016, 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 EDIT_MAP = 'EDIT_MAP'; -const UPDATE_CURRENT_MAP_PERMISSIONS = 'UPDATE_CURRENT_MAP_PERMISSIONS'; -const UPDATE_CURRENT_MAP_GROUPS = 'UPDATE_CURRENT_MAP_GROUPS'; -const RESET_CURRENT_MAP = 'RESET_CURRENT_MAP'; -const ADD_CURRENT_MAP_PERMISSION = 'ADD_CURRENT_MAP_PERMISSION'; - -function editMap(map, openModalProperties) { - return { - type: EDIT_MAP, - map, - openModalProperties - }; -} - -function updateCurrentMapPermissions(permissions) { - return { - type: UPDATE_CURRENT_MAP_PERMISSIONS, - permissions - }; -} - -function updateCurrentMapGroups(groups) { - return { - type: UPDATE_CURRENT_MAP_GROUPS, - groups - }; -} - -/** - * reset current map , `RESET_CURRENT_MAP` - * @memberof actions.maps - * @return {action} of type `RESET_CURRENT_MAP` - */ -function resetCurrentMap() { - return { - type: RESET_CURRENT_MAP - }; -} - -function addCurrentMapPermission(rule) { - return { - type: ADD_CURRENT_MAP_PERMISSION, - rule - }; -} - -module.exports = { - EDIT_MAP, editMap, - UPDATE_CURRENT_MAP_PERMISSIONS, updateCurrentMapPermissions, - UPDATE_CURRENT_MAP_GROUPS, updateCurrentMapGroups, - RESET_CURRENT_MAP, resetCurrentMap, - ADD_CURRENT_MAP_PERMISSION, addCurrentMapPermission -}; diff --git a/web/client/actions/maps.js b/web/client/actions/maps.js index 8964cdb23f..eb52f86f80 100644 --- a/web/client/actions/maps.js +++ b/web/client/actions/maps.js @@ -7,7 +7,6 @@ */ const GeoStoreApi = require('../api/GeoStoreDAO'); -const {updateCurrentMapGroups} = require('./currentMap'); const MAPS_LIST_LOADED = 'MAPS_LIST_LOADED'; const MAPS_LIST_LOADING = 'MAPS_LIST_LOADING'; const MAPS_LIST_LOAD_ERROR = 'MAPS_LIST_LOAD_ERROR'; @@ -23,8 +22,6 @@ const ATTRIBUTE_UPDATED = 'ATTRIBUTE_UPDATED'; const THUMBNAIL_ERROR = 'THUMBNAIL_ERROR'; const MAP_ERROR = 'MAP_ERROR'; const SAVE_ALL = 'SAVE_ALL'; -const DISPLAY_METADATA_EDIT = 'DISPLAY_METADATA_EDIT'; -const RESET_UPDATING = 'RESET_UPDATING'; const SAVING_MAP = 'SAVING_MAP'; const PERMISSIONS_LIST_LOADING = 'PERMISSIONS_LIST_LOADING'; const MAPS_SEARCH_TEXT_CHANGED = 'MAPS_SEARCH_TEXT_CHANGED'; @@ -51,6 +48,7 @@ const DETAILS_SAVING = 'DETAILS:DETAILS_SAVING'; const NO_DETAILS_AVAILABLE = "NO_DETAILS_AVAILABLE"; const FEATURED_MAPS_SET_ENABLED = "FEATURED_MAPS:SET_ENABLED"; const SAVE_MAP_RESOURCE = "SAVE_MAP_RESOURCE"; +const RELOAD_MAPS = 'MAPS:RELOAD_MAPS'; const INVALIDATE_FEATURED_MAPS = "FEATURED_MAPS:INVALIDATE"; /** @@ -377,35 +375,6 @@ function savingMap(metadata) { }; } -/** - * performed when want to display/hide the metadata editing window - * @memberof actions.maps - * @param {boolean} displayMetadataEditValue true to display, false to hide - * @return {action} type `DISPLAY_METADATA_EDIT`, with the arguments as they are named - */ -function onDisplayMetadataEdit(displayMetadataEditValue) { - return { - type: DISPLAY_METADATA_EDIT, - displayMetadataEditValue - }; -} - -/** - * load the available goups for a new permission rule. - * @memberof actions.maps - * @param {object} user the current user - * @return {thunk} dispatches updateCurrentMapGroups or loadError - */ -function loadAvailableGroups(user) { - return (dispatch) => { - GeoStoreApi.getAvailableGroups(user).then((response) => { - dispatch(updateCurrentMapGroups(response)); - }).catch((e) => { - dispatch(loadError(e)); - }); - }; -} - /** * updates an attribute for a given map * @memberof actions.maps @@ -446,12 +415,10 @@ function deleteMap(resourceId, options) { * @memberof actions.maps * @return {action} type `UPDATE_DETAILS` */ -function updateDetails(detailsText, doBackup, originalDetails) { +function updateDetails(detailsText) { return { type: UPDATE_DETAILS, - detailsText, - doBackup, - originalDetails + detailsText }; } @@ -538,6 +505,13 @@ const saveMapResource = (resource) => ({ type: SAVE_MAP_RESOURCE, resource }); +/** + * Trigger maps reload + * @memberof actions.maps + */ +const reloadMaps = () => ({ + type: RELOAD_MAPS +}); /** * Invalidate featured maps list * @memberof actions.maps @@ -579,8 +553,6 @@ module.exports = { THUMBNAIL_ERROR, PERMISSIONS_LIST_LOADING, SAVE_ALL, - DISPLAY_METADATA_EDIT, - RESET_UPDATING, MAP_ERROR, MAPS_SEARCH_TEXT_CHANGED, METADATA_CHANGED, @@ -617,13 +589,12 @@ module.exports = { savingMap, thumbnailError, loadError, - loadAvailableGroups, - onDisplayMetadataEdit, mapError, mapsSearchTextChanged, updateAttribute, saveMapResource, - invalidateFeaturedMaps, + reloadMaps, RELOAD_MAPS, + invalidateFeaturedMaps, INVALIDATE_FEATURED_MAPS, showDetailsSheet, SHOW_DETAILS_SHEET, hideDetailsSheet, HIDE_DETAILS_SHEET }; diff --git a/web/client/components/map/openlayers/Map.jsx b/web/client/components/map/openlayers/Map.jsx index c759deb802..d50f233818 100644 --- a/web/client/components/map/openlayers/Map.jsx +++ b/web/client/components/map/openlayers/Map.jsx @@ -77,11 +77,6 @@ class OpenlayersMap extends React.Component { onResolutionsChange: () => { }, onCreationError: () => { }, onClick: null, - center: { - x: 0, - y: 0, - crs: 'EPSG:4326' - }, onMouseMove: () => { }, mapOptions: {}, projection: 'EPSG:3857', diff --git a/web/client/components/maps/MapCard.jsx b/web/client/components/maps/MapCard.jsx deleted file mode 100644 index ae191efdb0..0000000000 --- a/web/client/components/maps/MapCard.jsx +++ /dev/null @@ -1,227 +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 PropTypes = require('prop-types'); -const React = require('react'); -const Message = require('../I18N/Message'); -const GridCard = require('../misc/GridCard'); -const FitIcon = require('../misc/FitIcon'); -const thumbUrl = require('./style/default.jpg'); -const assign = require('object-assign'); -const ConfirmModal = require('../misc/ResizableModal'); - -class MapCard extends React.Component { - static propTypes = { - // props - style: PropTypes.object, - map: PropTypes.object, - showMapDetails: PropTypes.bool, - shareToolEnabled: PropTypes.bool, - // CALLBACKS - viewerUrl: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), - onEdit: PropTypes.func, - onMapDelete: PropTypes.func, - onShare: PropTypes.func, - onUpdateAttribute: PropTypes.func, - onShowDetailsSheet: PropTypes.func, - backgroundOpacityStart: PropTypes.number, - backgroundOpacityEnd: PropTypes.number, - tooltips: PropTypes.object - }; - - static contextTypes = { - messages: PropTypes.object - }; - - static defaultProps = { - showMapDetails: true, - style: { - backgroundImage: 'url(' + thumbUrl + ')', - backgroundSize: "cover", - backgroundPosition: "center", - backgroundRepeat: "repeat-x" - }, - shareToolEnabled: true, - // CALLBACKS - onMapDelete: ()=> {}, - onEdit: () => {}, - onUpdateAttribute: () => {}, - onShowDetailsSheet: () => {}, - backgroundOpacityStart: 0.7, - backgroundOpacityEnd: 0.3, - tooltips: { - deleteResource: "resources.resource.deleteResource", - editResource: "resources.resource.editResource", - shareResource: "share.title", - addToFeatured: "resources.resource.addToFeatured", - showDetails: "resources.resource.showDetails", - removeFromFeatured: "resources.resource.removeFromFeatured" - } - }; - - onEdit = (map, openModalProperties) => { - this.props.onEdit(map, openModalProperties); - }; - - onConfirmDelete = () => { - this.props.onMapDelete(this.props.map.id); - this.close(); - }; - - onClick = (evt) => { - // Users can select Title and Description without triggering the click - var selection = window.getSelection(); - const selectedText = selection.toString(); - if (!selectedText) { - this.stopPropagate(evt); - this.props.viewerUrl(this.props.map); - } - }; - - getCardStyle = () => { - if (this.props.map.thumbnail && this.props.map.thumbnail !== "NODATA") { - return assign({}, this.props.style, { - backgroundImage: 'linear-gradient(rgba(0, 0, 0, ' - + this.props.backgroundOpacityStart - + '), rgba(0, 0, 0, ' + this.props.backgroundOpacityEnd - + ') ), url(' - + (this.props.map.thumbnail === null || this.props.map.thumbnail === "NODATA" - ? thumbUrl - // this decode is for backward compatibility with old linked resources`rest%2Fgeostore%2Fdata%2F2%2Fraw%3Fdecode%3Ddatauri` not needed for new ones `rest/geostore/data/2/raw?decode=datauri` - : decodeURIComponent(this.props.map.thumbnail) - ) - + ')' - }); - } - return this.props.style; - }; - - render() { - - const isFeatured = this.props.map && this.props.map.featured === 'true' || this.props.map.featured === 'added'; - const availableAction = [ - { - visible: this.props.map.canEdit === true, - glyph: 'trash', - disabled: this.props.map.deleting, - loading: this.props.map.deleting, - tooltipId: this.props.tooltips.deleteResource, - onClick: evt => { - this.stopPropagate(evt); - this.displayDeleteDialog(); - } - }, - { - visible: this.props.map.canEdit === true, - glyph: 'wrench', - disabled: this.props.map.updating, - loading: this.props.map.updating, - tooltipId: this.props.tooltips.editResource, - onClick: evt => { - this.stopPropagate(evt); - this.onEdit(this.props.map, true); - } - }, - { - visible: this.props.shareToolEnabled === true, - glyph: 'share-alt', - disabled: this.props.map && this.props.map.updating, - loading: this.props.map && this.props.map.updating, - tooltipId: this.props.tooltips.shareResource, - onClick: evt => { - this.stopPropagate(evt); - this.props.onShare(this.props.map); - } - }, - { - visible: !!(this.props.showMapDetails && this.props.map.details && this.props.map.details !== 'NODATA'), - glyph: 'sheet', - tooltipId: this.props.tooltips.showDetails, - onClick: evt => { - this.stopPropagate(evt); - this.onEdit(this.props.map, false); - this.props.onShowDetailsSheet(); - } - }, - { - visible: !!(this.props.map.canEdit === true && this.props.map.featuredEnabled), - glyph: isFeatured ? 'star' : 'star-empty', - bsStyle: isFeatured ? 'success' : 'primary', - tooltipId: isFeatured ? this.props.tooltips.removeFromFeatured : this.props.tooltips.addToFeatured, - onClick: evt => { - this.stopPropagate(evt); - this.props.onUpdateAttribute(this.props.map.id, 'featured', !isFeatured); - } - } - ]; - return ( -
- -
{this.props.map.description}
- {this.props.map.icon ? -
- -
: null} -
- } - buttons={[{ - bsStyle: "primary", - text: , - onClick: this.onConfirmDelete - }, { - text: , - onClick: this.close - }]} - fitContent - > -
- -
-
-
- ); - } - - stopPropagate = (event) => { - // prevent click on parent container - const e = event || window.event || {}; - if (e.stopPropagation) { - e.stopPropagation(); - } else { - e.cancelBubble = true; - } - }; - - close = () => { - // TODO Launch an action in order to change the state - this.setState({ - displayDeleteDialog: false - }); - }; - - displayDeleteDialog = () => { - this.setState({ - displayDeleteDialog: true - }); - }; -} - -module.exports = MapCard; diff --git a/web/client/components/maps/MapGrid.jsx b/web/client/components/maps/MapGrid.jsx deleted file mode 100644 index c567648c5b..0000000000 --- a/web/client/components/maps/MapGrid.jsx +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright 2016, 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 React = require('react'); -const PropTypes = require('prop-types'); -const {Grid, Row, Col} = require('react-bootstrap'); -const MapCard = require('./MapCard'); -const Spinner = require('react-spinkit'); - -class MapGrid extends React.Component { - static propTypes = { - id: PropTypes.string, - panelProps: PropTypes.object, - bottom: PropTypes.node, - loading: PropTypes.bool, - showMapDetails: PropTypes.bool, - maps: PropTypes.array, - currentMap: PropTypes.object, - user: PropTypes.object, - fluid: PropTypes.bool, - showAPIShare: PropTypes.bool, - viewerUrl: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), - mapType: PropTypes.string, - colProps: PropTypes.object, - // CALLBACKS - editMap: PropTypes.func, - onDisplayMetadataEdit: PropTypes.func, - deleteMap: PropTypes.func, - onShare: PropTypes.func, - resetCurrentMap: PropTypes.func, - metadataModal: PropTypes.func, - onUpdateAttribute: PropTypes.func, - onSaveSuccess: PropTypes.func, - onSaveError: PropTypes.func, - onMapSaved: PropTypes.func, - onShowDetailsSheet: PropTypes.func, - onHideDetailsSheet: PropTypes.func, - title: PropTypes.node, - className: PropTypes.string, - style: PropTypes.object - }; - - static defaultProps = { - id: "mapstore-maps-grid", - mapType: 'leaflet', - bottom: "", - fluid: true, - showAPIShare: true, - colProps: { - xs: 12, - sm: 6 - }, - currentMap: {}, - maps: [], - // CALLBACKS - onChangeMapType: function() {}, - onDisplayMetadataEdit: () => {}, - deleteMap: () => {}, - onShare: () => {}, - editMap: () => {}, - resetCurrentMap: () => {}, - groups: [], - onUpdateAttribute: () => {}, - onSaveSuccess: () => {}, - onSaveError: () => {}, - className: '', - style: {} - }; - renderMaps = (maps, mapType) => { - const viewerUrl = this.props.viewerUrl; - return maps.map((map) => { - let children = React.Children.count(this.props.children); - return children === 1 ? - React.cloneElement(React.Children.only(this.props.children), {viewerUrl, key: map.id, mapType, map}) : - - - ; - }); - }; - - renderLoading = () => { - return
Loading...
; - }; - - renderMetadataModal = () => { - if (this.props.metadataModal) { - let MetadataModal = this.props.metadataModal; - return ( { - this.props.onSaveSuccess(); - this.props.onMapSaved(); - }} - onSaveError={() => { - this.props.onSaveError(); - this.props.onMapSaved(this.props.currentMap?.id); - }} - onClose={() => { - this.props.onDisplayMetadataEdit(false); - this.props.resetCurrentMap(); - }}/>); - } - return null; - }; - - render() { - return ( - - {this.props.title && - {this.props.title} - } - - {this.props.loading && this.props.maps.length === 0 ? this.renderLoading() : this.renderMaps(this.props.maps || [], this.props.mapType)} - - {this.props.bottom} - {this.renderMetadataModal()} - - ); - } -} - -module.exports = MapGrid; diff --git a/web/client/components/maps/__tests__/MapCard-test.jsx b/web/client/components/maps/__tests__/MapCard-test.jsx deleted file mode 100644 index 5dc9e1d875..0000000000 --- a/web/client/components/maps/__tests__/MapCard-test.jsx +++ /dev/null @@ -1,149 +0,0 @@ -/** - * Copyright 2016, 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 React = require('react'); -const ReactDOM = require('react-dom'); -const MapCard = require('../MapCard.jsx'); -const expect = require('expect'); - -const TestUtils = require('react-dom/test-utils'); - -describe('This test for MapCard', () => { - beforeEach((done) => { - document.body.innerHTML = '
'; - setTimeout(done); - }); - - afterEach((done) => { - ReactDOM.unmountComponentAtNode(document.getElementById("container")); - document.body.innerHTML = ''; - setTimeout(done); - }); - - // test DEFAULTS - it('creates the component with defaults', () => { - const mapItem = ReactDOM.render(, document.getElementById("container")); - expect(mapItem).toExist(); - - const mapItemDom = ReactDOM.findDOMNode(mapItem); - expect(mapItemDom).toExist(); - - expect(mapItemDom.childNodes[0].className).toBe('gridcard map-thumb'); - const headings = mapItemDom.getElementsByClassName('gridcard-title'); - expect(headings.length).toBe(1); - }); - // test DEFAULTS - it('creates the component with data', () => { - const testName = "test"; - const testDescription = "testDescription"; - const mapItem = ReactDOM.render(, document.getElementById("container")); - expect(mapItem).toExist(); - - const mapItemDom = ReactDOM.findDOMNode(mapItem); - expect(mapItemDom).toExist(); - - expect(mapItemDom.childNodes[0].className).toBe('gridcard map-thumb'); - const headings = mapItemDom.getElementsByClassName('gridcard-title'); - expect(headings.length).toBe(1); - expect(headings[0].innerHTML).toBe(testName); - }); - - it('test details tool', () => { - const testName = "test"; - const testDescription = "testDescription"; - let component = TestUtils.renderIntoDocument(); - const handlers = { - onShowDetailsSheet: () => {}, - onEdit: () => {} - }; - let spy = expect.spyOn(handlers, "onShowDetailsSheet"); - component = TestUtils.renderIntoDocument(); - const detailsTool = TestUtils.findRenderedDOMComponentWithTag( - component, 'button' - ); - expect(detailsTool).toExist(); - TestUtils.Simulate.click(detailsTool); - expect(spy.calls.length).toEqual(1); - }); - it('test edit/delete/share', () => { - const testName = "test"; - const testDescription = "testDescription"; - - const handlers = { - viewerUrl: () => {}, - onEdit: () => {}, - onMapDelete: () => {}, - onShare: () => {} - }; - - - let spyonEdit = expect.spyOn(handlers, "onEdit"); - let spyonMapDelete = expect.spyOn(handlers, "onMapDelete"); - let spyonShare = expect.spyOn(handlers, "onShare"); - const component = ReactDOM.render(, document.getElementById("container")); - const buttons = TestUtils.scryRenderedDOMComponentsWithTag( - component, 'button' - ); - expect(buttons.length).toBe(3); - buttons.forEach(b => TestUtils.Simulate.click(b)); - - expect(spyonEdit.calls.length).toEqual(1); - // wait for confirm - expect(spyonMapDelete.calls.length).toEqual(0); - expect(spyonShare.calls.length).toEqual(1); - }); - it('test edit properties tool', () => { - const testName = "test"; - const testDescription = "testDescription"; - - const handlers = { - onEdit: () => {} - }; - - let spy = expect.spyOn(handlers, "onEdit"); - const component = ReactDOM.render(, document.getElementById("container")); - const tools = TestUtils.scryRenderedDOMComponentsWithTag( - component, 'button' - ); - expect(tools.length).toBeGreaterThan(0); - - const wrenchTool = tools.find(tool => !!tool.querySelector('.glyphicon-wrench')); - expect(wrenchTool).toExist(); - TestUtils.Simulate.click(wrenchTool); - expect(spy.calls.length).toBe(1); - }); - it('test edit properties tool with dashboard', () => { - const testName = "test"; - const testDescription = "testDescription"; - - const handlers = { - onEdit: () => {} - }; - - let spy = expect.spyOn(handlers, "onEdit"); - const component = ReactDOM.render(, document.getElementById("container")); - const tools = TestUtils.scryRenderedDOMComponentsWithTag( - component, 'button' - ); - expect(tools.length).toBeGreaterThan(0); - - const wrenchTool = tools.find(tool => !!tool.querySelector('.glyphicon-wrench')); - expect(wrenchTool).toExist(); - TestUtils.Simulate.click(wrenchTool); - expect(spy.calls.length).toBe(1); - }); -}); diff --git a/web/client/components/maps/__tests__/MapGrid-test.jsx b/web/client/components/maps/__tests__/MapGrid-test.jsx deleted file mode 100644 index 4358ae8442..0000000000 --- a/web/client/components/maps/__tests__/MapGrid-test.jsx +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright 2016, 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 React = require('react'); -var ReactDOM = require('react-dom'); -var MapGrid = require('../MapGrid.jsx'); -var expect = require('expect'); - -describe('This test for MapGrid', () => { - beforeEach((done) => { - document.body.innerHTML = '
'; - setTimeout(done); - }); - - afterEach((done) => { - ReactDOM.unmountComponentAtNode(document.getElementById("container")); - document.body.innerHTML = ''; - setTimeout(done); - }); - - // test DEFAULTS - it('creates the component with defaults', () => { - const mapList = ReactDOM.render(, document.getElementById("container")); - expect(mapList).toExist(); - - const dom = ReactDOM.findDOMNode(mapList); - expect(dom).toExist(); - }); - - it('checks data', () => { - var map1 = {id: 1, name: "a", description: "description"}; - var map2 = {id: 2, name: "b", description: "description"}; - let currentMap = {displayMetadataEdit: true}; - const mapList = ReactDOM.render(, document.getElementById("container")); - expect(mapList).toExist(); - const dom = ReactDOM.findDOMNode(mapList); - expect(dom).toExist(); - - // check list - const list = dom.getElementsByClassName("map-thumb"); - expect(list.length).toBe(2, " list missing"); - }); -}); diff --git a/web/client/components/resources/ResourceCard.jsx b/web/client/components/resources/ResourceCard.jsx index 00bba00246..1561a40406 100644 --- a/web/client/components/resources/ResourceCard.jsx +++ b/web/client/components/resources/ResourceCard.jsx @@ -9,6 +9,7 @@ const PropTypes = require('prop-types'); const React = require('react'); const Message = require('../I18N/Message'); const GridCard = require('../misc/GridCard'); +const FitIcon = require('../misc/FitIcon'); const thumbUrl = require('../maps/style/default.jpg'); const assign = require('object-assign'); const ConfirmModal = require('../misc/ResizableModal'); @@ -28,6 +29,7 @@ class ResourceCard extends React.Component { onEditData: PropTypes.func, onDelete: PropTypes.func, onShare: PropTypes.func, + onShowDetailsSheet: PropTypes.func, onUpdateAttribute: PropTypes.func, tooltips: PropTypes.object }; @@ -58,6 +60,7 @@ class ResourceCard extends React.Component { onEdit: () => { }, onEditData: () => { }, onShare: () => { }, + onShowDetailsSheet: () => { }, onUpdateAttribute: () => { } }; @@ -151,8 +154,7 @@ class ResourceCard extends React.Component { tooltipId: this.props.tooltips.showDetails, onClick: evt => { this.stopPropagate(evt); - this.onEdit(this.props.resource, false); - // TODO show details + this.props.onShowDetailsSheet(this.props.resource); } }, { @@ -173,6 +175,18 @@ class ResourceCard extends React.Component { actions={availableAction} onClick={this.onClick} >
{this.props.resource.description}
+ {this.props.resource.icon ? +
+ +
: null} { return
Loading...
; }; -const renderMetadataModal = ({ Component, edit, resource, setEdit, errors, setErrors, onSaveSuccess, user, nameFieldFilter }) => { +const renderMetadataModal = ({ Component, edit, resource, setEdit, errors, setErrors, onSaveSuccess, user, + nameFieldFilter, enableDetails, category, onResourceLoad }) => { if (Component) { let MetadataModal = Component; return (); + resource={resource} + category={category} + enableDetails={enableDetails} + onResourceLoad={onResourceLoad}/>); } return null; }; @@ -37,8 +43,10 @@ module.exports = ({ className, colProps, loading, + category = 'MAP', resources = [], resource, + loadedResource, id, style, bottom, @@ -51,6 +59,7 @@ module.exports = ({ editDataEnabled, shareToolEnabled, cardTooltips, + showDetailsSheet, onEdit = () => {}, onEditData = () => {}, setEdit = () => {}, @@ -59,43 +68,61 @@ module.exports = ({ onDelete = () => {}, onShare = () => {}, onUpdateAttribute = () => {}, + onShowDetailsSheet = () => {}, + onHideDetailsSheet = () => {}, + onResourceLoad = () => {}, nameFieldFilter = () => {} -}) => ( - - {title && - {title} - } - - {loading && resources.length === 0 - ? renderLoading() - : resources.map( - res => ( - - )) - } - - {bottom} - {renderMetadataModal({ - Component: metadataModal, - user, - onSaveSuccess, - resource, - setErrors, - errors, - setEdit, - edit, - nameFieldFilter - // resource - })} - -); +}) => { + const categoryName = (isString(resource?.category) ? resource?.category : resource?.category?.name) || category; + + return ( + + {title && + {title} + } + + {loading && resources.length === 0 + ? renderLoading() + : resources.map( + res => ( + + )) + } + + {bottom} + {renderMetadataModal({ + Component: metadataModal, + user, + onSaveSuccess, + resource, + setErrors, + errors, + setEdit, + edit, + nameFieldFilter, + category: categoryName, + enableDetails: categoryName === 'MAP', + onResourceLoad + // resource + })} + + + ); +}; diff --git a/web/client/components/resources/enhancers/resourceGrid.js b/web/client/components/resources/enhancers/resourceGrid.js index bbd06322a9..39871103c5 100644 --- a/web/client/components/resources/enhancers/resourceGrid.js +++ b/web/client/components/resources/enhancers/resourceGrid.js @@ -6,11 +6,12 @@ * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. */ -const { compose, branch, withState, withHandlers, defaultProps } = require('recompose'); +const { compose, branch, withState, withHandlers, defaultProps, mapProps } = require('recompose'); const handleSave = require('../modals/enhancers/handleSave').default; const handleSaveModal = require('../modals/enhancers/handleSaveModal').default; const handleResourceDownload = require('../modals/enhancers/handleResourceDownload'); +const handleDetailsDownload = require('../modals/enhancers/handleDetailsDownload').default; /* * EditDialog @@ -29,9 +30,13 @@ const EditDialog = compose( }), branch( ({ resource }) => resource && resource.id, - compose( - handleSave, - handleSaveModal + branch( + ({ show }) => !show, + handleDetailsDownload, + compose( + handleSave, + handleSaveModal + ) ) ) )(require('../modals/Save')); @@ -45,6 +50,9 @@ const resourceGrid = compose( withState('resource', 'setResource'), withState('edit', 'setEdit', false), withState('errors', 'setErrors', ({errors = []}) => errors), + withState('showDetailsSheet', 'setShowDetailsSheet', false), + withState('loadingResource', 'setLoadingResource', false), + withState('loadedResource', 'setLoadedResource'), withHandlers({ onEdit: ({ setEdit = () => { }, setResource = () => { } }) => (resource) => { if (resource) { @@ -55,8 +63,29 @@ const resourceGrid = compose( setEdit(false); } + }, + onShowDetailsSheet: ({ setShowDetailsSheet = () => { }, setLoadingResource = () => { }, setResource = () => { }, loadedResource }) => (resource) => { + if (resource) { + const details = resource?.details || resource?.attributes?.details; + const detailsLoaded = loadedResource?.details || loadedResource?.attributes?.details; + + if (resource?.id !== loadedResource?.id || details !== detailsLoaded) { + setLoadingResource(true); + } + + setResource(resource); + setShowDetailsSheet(true); + } + }, + onHideDetailsSheet: ({ setShowDetailsSheet = () => { } }) => () => { + setShowDetailsSheet(false); + }, + onResourceLoad: ({ setLoadedResource = () => { }, setLoadingResource = () => { } }) => (resource) => { + setLoadedResource(resource); + setLoadingResource(false); } - }) + }), + mapProps(({loading, loadingResource, ...other}) => ({...other, loading: loading || loadingResource || false })) ); module.exports = resourceGrid; diff --git a/web/client/components/resources/modals/Save.jsx b/web/client/components/resources/modals/Save.jsx index 597f50fc11..7ef7643a1d 100644 --- a/web/client/components/resources/modals/Save.jsx +++ b/web/client/components/resources/modals/Save.jsx @@ -17,8 +17,6 @@ const ErrorBox = require('./fragments/ErrorBox'); const MainForm = require('./fragments/MainForm'); const ruleEditor = require('./enhancers/ruleEditor'); const PermissionEditor = ruleEditor(require('./fragments/PermissionEditor')); -const DetailsRow = require('./fragments/DetailsRow').default; -const DetailsSheet = require('./fragments/DetailsSheet').default; /** * Defines if the resource permissions are available or not. @@ -70,27 +68,15 @@ class SaveModal extends React.Component { linkedResources: PropTypes.object, style: PropTypes.object, modalSize: PropTypes.string, - detailsText: PropTypes.string, - detailsBackup: PropTypes.string, - detailsTextOriginal: PropTypes.string, enableDetails: PropTypes.bool, - showDetailsPreview: PropTypes.bool, - showDetailsSheet: PropTypes.bool, - showReadOnlyDetailsSheet: PropTypes.bool, + detailsComponent: PropTypes.element, // CALLBACKS onError: PropTypes.func, onUpdate: PropTypes.func, onUpdateLinkedResource: PropTypes.func, - onDeleteLinkedResource: PropTypes.func, onClose: PropTypes.func, onFileDrop: PropTypes.func, onFileDropClear: PropTypes.func, - onShowDetailsPreview: PropTypes.func, - onHideDetailsPreview: PropTypes.func, - onShowDetailsSheet: PropTypes.func, - onHideDetailsSheet: PropTypes.func, - onUpdateDetailsText: PropTypes.func, - onCloseReadOnlyDetailsSheet: PropTypes.bool, metadataChanged: PropTypes.func, disablePermission: PropTypes.bool, availablePermissions: PropTypes.arrayOf(PropTypes.string), @@ -122,14 +108,14 @@ class SaveModal extends React.Component { onUpdate: ()=> {}, onUpdateLinkedResource: () => {}, onDeleteLinkedResource: () => {}, - onCloseReadOnlyDetailsSheet: () => {}, onSave: ()=> {}, disablePermission: false, availablePermissions: ["canRead", "canWrite"], availableGroups: [], canSave: true, user: {}, - dialogClassName: '' + dialogClassName: '', + detailsComponent: require('./enhancers/handleDetails').default((require('./fragments/Details').default)) }; onCloseMapPropertiesModal = () => { this.props.onClose(); @@ -144,9 +130,10 @@ class SaveModal extends React.Component { */ render() { const canEditPermission = !this.props.disablePermission && canEditResourcePermission(this.props.user, this.props.resource); + const Details = this.props.detailsComponent; return ( - {!this.props.showReadOnlyDetailsSheet && } show={this.props.show} @@ -184,43 +171,11 @@ class SaveModal extends React.Component { onError={this.props.onError} nameFieldFilter={this.props.nameFieldFilter} onUpdate={this.props.onUpdate} /> - {this.props.enableDetails && { - this.props.onUpdateLinkedResource('details', 'NODATA', 'DETAILS'); - this.props.onUpdateDetailsText('NODATA'); - this.props.onHideDetailsPreview(); - }} - onUndo={() => { - this.props.onDeleteLinkedResource('details'); - this.props.onUpdateDetailsText(this.props.detailsTextOriginal); - if (this.props.detailsTextOriginal === 'NODATA') { - this.props.onHideDetailsPreview(); - } - }}/> - } - {this.props.enableDetails && { - this.props.onHideDetailsSheet(); - this.props.onUpdateDetailsText(this.props.detailsBackup); - }} - onSave={text => { - this.props.onHideDetailsSheet(); - this.props.onUpdateLinkedResource('details', text || 'NODATA', 'DETAILS'); - }} - onUpdate={text => { - this.props.onUpdateDetailsText(text); - }}/> + linkedResources={this.props.linkedResources} + onUpdateLinkedResource={this.props.onUpdateLinkedResource}/> } { !!canEditPermission && - } - {this.props.showReadOnlyDetailsSheet && { - this.props.onCloseReadOnlyDetailsSheet(); - }} - />} + ); } isValidForm = () => get(this.props.resource, "metadata.name") && (!this.props.enableFileDrop || this.props.fileDropStatus === 'accepted') diff --git a/web/client/components/resources/modals/enhancers/handleDetails.js b/web/client/components/resources/modals/enhancers/handleDetails.js new file mode 100644 index 0000000000..0b4a6960f2 --- /dev/null +++ b/web/client/components/resources/modals/enhancers/handleDetails.js @@ -0,0 +1,24 @@ +/* + * Copyright 2020, 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. + */ + +import { compose, withState, withHandlers, withProps } from 'recompose'; + +export default compose( + withState('showDetailsSheet', 'setShowDetailsSheet', false), + withState('detailsText', 'setDetailsText'), + withState('detailsBackup', 'setDetailsBackup'), + withHandlers({ + onShowDetailsSheet: ({ setShowDetailsSheet = () => {} }) => () => setShowDetailsSheet(true), + onHideDetailsSheet: ({ setShowDetailsSheet = () => {} }) => () => setShowDetailsSheet(false) + }), + withProps(({linkedResources = {}, resource = {}}) => ({ + savedDetailsText: linkedResources?.details?.data === 'NODATA' ? + undefined : + (linkedResources?.details?.data || resource.detailsText) + })) +); diff --git a/web/client/components/resources/modals/enhancers/handleDetails.jsx b/web/client/components/resources/modals/enhancers/handleDetails.jsx deleted file mode 100644 index 2d6f103d38..0000000000 --- a/web/client/components/resources/modals/enhancers/handleDetails.jsx +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2020, 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. - */ - -import React from 'react'; -import Rx from 'rxjs'; -import { compose, lifecycle, withState, mapPropsStream } from 'recompose'; - -import GeoStoreApi from '../../../../api/GeoStoreDAO'; -import MapUtils from '../../../../utils/MapUtils'; - -const getDetails = (detailsUri) => { - const id = MapUtils.getIdFromUri(detailsUri); - return Rx.Observable.defer(() => id ? GeoStoreApi.getData(id) : Promise.resolve()); -}; - -const handleDetailsDownload = mapPropsStream(props$ => - props$.combineLatest( - props$ - .pluck('resource') - .distinctUntilChanged((res1, res2) => res1.id === res2.id) - .switchMap(res => - getDetails(res.attributes.details) - .map(data => ({ - detailsTextOriginal: data || 'NODATA' - })) - .startWith({resource: {}}) - .catch(() => Rx.Observable.of({ - detailsTextOriginal: 'NODATA' - })) - ) - .startWith({}), - (p1, p2) => ({ - ...p1, - ...p2 - }) - ) -); - - -export default compose( - withState('showDetailsPreview', 'setShowPreview', false), - withState('showDetailsSheet', 'setShowDetailsSheet', false), - withState('detailsText', 'setDetailsText'), - withState('detailsBackup', 'setDetailsBackup'), - handleDetailsDownload, - lifecycle({ - componentDidUpdate(oldProps) { - if (oldProps.detailsTextOriginal !== this.props.detailsTextOriginal) { - this.props.setDetailsText(this.props.detailsTextOriginal); - this.props.setDetailsBackup(this.props.detailsTextOriginal); - } - } - }), - Component => ({ - setShowPreview, - setShowDetailsSheet, - setDetailsText, - setDetailsBackup, - setDetailsTextOriginal, - ...props - }) => ( - { - setDetailsBackup(props.detailsText); - setShowDetailsSheet(true); - }} - onHideDetailsSheet={() => setShowDetailsSheet(false)} - onShowDetailsPreview={() => setShowPreview(true)} - onHideDetailsPreview={() => setShowPreview(false)} - onUpdateDetailsText={text => setDetailsText(text)}/> - ) -); diff --git a/web/client/components/resources/modals/enhancers/handleDetailsDownload.js b/web/client/components/resources/modals/enhancers/handleDetailsDownload.js new file mode 100644 index 0000000000..f2df1395fe --- /dev/null +++ b/web/client/components/resources/modals/enhancers/handleDetailsDownload.js @@ -0,0 +1,49 @@ +/* + * Copyright 2020, 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. + */ + +import Rx from 'rxjs'; +import GeoStoreApi from '../../../../api/GeoStoreDAO'; +import MapUtils from '../../../../utils/MapUtils'; + +import { mapPropsStream } from 'recompose'; + +const getDetails = (detailsUri) => { + const id = MapUtils.getIdFromUri(detailsUri); + return Rx.Observable.defer(() => id ? GeoStoreApi.getData(id) : Promise.resolve()); +}; + +export default mapPropsStream(props$ => { + return props$.combineLatest( + props$ + .filter(({resource}) => resource && resource.id) + .distinctUntilChanged(({resource: res1}, {resource: res2}) => res1 === res2) + .switchMap(props => { + const details = props.resource.attributes?.details; + + return getDetails(details).map(detailsText => { + const resourceWithDetails = {...props.resource, detailsText}; + + if (props.onResourceLoad) { + props.onResourceLoad(resourceWithDetails); + } + + return { + loading: false, + resource: resourceWithDetails + }; + }) + .catch(e => Rx.Observable.of({ loading: false, errors: [e] })) + .startWith({ loading: true, resource: false }); + }) + .startWith({}), + (p1, p2) => ({ + ...p1, + ...p2 + }) + ); +}); diff --git a/web/client/components/resources/modals/enhancers/handleDetailsRow.js b/web/client/components/resources/modals/enhancers/handleDetailsRow.js new file mode 100644 index 0000000000..b7d9b6de31 --- /dev/null +++ b/web/client/components/resources/modals/enhancers/handleDetailsRow.js @@ -0,0 +1,21 @@ +/* + * Copyright 2020, 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. + */ + +import { compose, withState, withHandlers } from 'recompose'; + +export default compose( + withState('showPreview', 'setShowPreview', false), + withHandlers({ + onShowPreview: ({ setShowPreview = () => {} }) => () => { + setShowPreview(true); + }, + onHidePreview: ({ setShowPreview = () => {} }) => () => { + setShowPreview(false); + } + }) +); diff --git a/web/client/components/resources/modals/enhancers/handleResourceData.jsx b/web/client/components/resources/modals/enhancers/handleResourceData.jsx index dc26bde214..6c7cf17843 100644 --- a/web/client/components/resources/modals/enhancers/handleResourceData.jsx +++ b/web/client/components/resources/modals/enhancers/handleResourceData.jsx @@ -7,7 +7,6 @@ */ const React = require('react'); const { compose, withStateHandlers, withState, branch, withHandlers, renderComponent} = require('recompose'); -const { omit } = require('lodash'); const {set} = require('../../../../utils/ImmutableUtils'); const Message = require('../../../I18N/Message'); const ConfirmDialog = require('../ConfirmModal'); @@ -39,7 +38,8 @@ module.exports = compose( description: resource.description }, createdAt: resource.creation, - modifiedAt: resource.lastUpdate + modifiedAt: resource.lastUpdate, + detailsText: resource.detailsText } }), @@ -54,9 +54,6 @@ module.exports = compose( data, ...options }, linkedResources) - }), - onDeleteLinkedResource: ({ linkedResources = {} }) => (key) => ({ - linkedResources: omit(linkedResources, key) }) } ), diff --git a/web/client/components/resources/modals/enhancers/handleResourceDownload.js b/web/client/components/resources/modals/enhancers/handleResourceDownload.js index 03d64e2ce1..2c66938a8c 100644 --- a/web/client/components/resources/modals/enhancers/handleResourceDownload.js +++ b/web/client/components/resources/modals/enhancers/handleResourceDownload.js @@ -23,7 +23,7 @@ module.exports = mapPropsStream(props$ => { resource })) .startWith({ loading: true, resource: false }) - .catch(e => Rx.Observable.of({ loading: false, errors: e })) + .catch(e => Rx.Observable.of({ loading: false, errors: [e] })) ) .startWith({}), (p1, p2) => ({ diff --git a/web/client/components/resources/modals/enhancers/handleSaveModal.js b/web/client/components/resources/modals/enhancers/handleSaveModal.js index 2a2557fc81..4053ddb961 100644 --- a/web/client/components/resources/modals/enhancers/handleSaveModal.js +++ b/web/client/components/resources/modals/enhancers/handleSaveModal.js @@ -6,19 +6,19 @@ * LICENSE file in the root directory of this source tree. */ import handleResourceData from './handleResourceData'; -import handleDetails from './handleDetails'; import handlePermission from './handlePermission'; import handleErrors from './handleErrors'; +import handleDetailsDownload from './handleDetailsDownload'; import { compose, branch, renderNothing } from 'recompose'; export default compose( branch( - ({ show, showReadOnlyDetailsSheet }) => !show && !showReadOnlyDetailsSheet, + ({ show }) => !show, renderNothing ), handleResourceData, - handleDetails, + handleDetailsDownload, handlePermission(), handleErrors ); diff --git a/web/client/components/resources/modals/fragments/Details.jsx b/web/client/components/resources/modals/fragments/Details.jsx new file mode 100644 index 0000000000..78a00065df --- /dev/null +++ b/web/client/components/resources/modals/fragments/Details.jsx @@ -0,0 +1,65 @@ +/* + * Copyright 2020, 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. + */ + +import React from 'react'; +import { isNil } from 'lodash'; + +import DetailsRowBase from './DetailsRow'; +import DetailsSheet from './DetailsSheet'; + +import handleDetailsRow from '../enhancers/handleDetailsRow'; + +const DetailsRow = handleDetailsRow(DetailsRowBase); + +export default ({ + loading, + resource = {}, + detailsText, + savedDetailsText, + detailsBackup, + showDetailsSheet, + setDetailsText = () => {}, + setDetailsBackup = () => {}, + onUpdateLinkedResource = () => {}, + onShowDetailsSheet = () => {}, + onHideDetailsSheet = () => {} +}) => ( + <> + { + onUpdateLinkedResource('details', detailsBackup, 'DETAILS'); + setDetailsBackup(); + }} + onUpdate={setDetailsText} + onShowDetailsSheet={onShowDetailsSheet} + onDelete={() => { + setDetailsBackup(savedDetailsText); + onUpdateLinkedResource('details', 'NODATA', 'DETAILS'); + }}/> + { + onHideDetailsSheet(); + setDetailsText(savedDetailsText); + }} + onSave={text => { + onHideDetailsSheet(); + setDetailsBackup(); + onUpdateLinkedResource('details', text, 'DETAILS'); + }} + onUpdate={setDetailsText}/> + +); diff --git a/web/client/components/resources/modals/fragments/DetailsRow.jsx b/web/client/components/resources/modals/fragments/DetailsRow.jsx index 5f575cd942..1832dcf1f2 100644 --- a/web/client/components/resources/modals/fragments/DetailsRow.jsx +++ b/web/client/components/resources/modals/fragments/DetailsRow.jsx @@ -16,7 +16,7 @@ import Message from '../../../I18N/Message'; export default ({ - resource = {}, + loading = false, showPreview = false, editDetailsDisabled, detailsText, @@ -34,53 +34,52 @@ export default ({
- {detailsText === 'NODATA' ? : } + {!loading && isNil(detailsText) ? : }
- {resource.saving ? : null} - {isNil(detailsText) ? : : null} + {!loading && showPreview ? onHidePreview() : onShowPreview(), - disabled: resource.saving, + disabled: loading, tooltipId: !showPreview ? "map.details.showPreview" : "map.details.hidePreview" }, { glyph: 'undo', tooltipId: "map.details.undo", visible: canUndo, onClick: () => onUndo(), - disabled: resource.saving + disabled: loading }, { glyph: 'pencil-add', tooltipId: "map.details.add", - visible: detailsText === 'NODATA', + visible: !detailsText, onClick: () => { onShowDetailsSheet(); + onUpdate(); }, - disabled: resource.saving + disabled: loading }, { glyph: 'pencil', tooltipId: "map.details.edit", - visible: detailsText !== 'NODATA' && !editDetailsDisabled, + visible: !!detailsText && !editDetailsDisabled, onClick: () => { onShowDetailsSheet(); - if (detailsText) { - onUpdate(detailsText); - } + onUpdate(detailsText); }, - disabled: resource.saving + disabled: loading }, { glyph: 'trash', tooltipId: "map.details.delete", - visible: detailsText !== 'NODATA', + visible: !!detailsText, onClick: () => onDelete(), - disabled: resource.saving + disabled: loading }]} />}
@@ -88,7 +87,7 @@ export default ({
{detailsText &&
- {detailsText !== 'NODATA' ?
+ {detailsText !== '' ?
:
}
}
diff --git a/web/client/components/resources/modals/fragments/DetailsSheet.jsx b/web/client/components/resources/modals/fragments/DetailsSheet.jsx index c496efaefb..5f041c2ad0 100644 --- a/web/client/components/resources/modals/fragments/DetailsSheet.jsx +++ b/web/client/components/resources/modals/fragments/DetailsSheet.jsx @@ -9,6 +9,7 @@ import React from 'react'; import Spinner from 'react-spinkit'; import ReactQuill from 'react-quill'; +import { isNil } from 'lodash'; import ResizableModal from '../../../misc/ResizableModal'; import Portal from '../../../misc/Portal'; @@ -17,9 +18,10 @@ import Message from '../../../I18N/Message'; import 'react-quill/dist/quill.snow.css'; export default ({ + loading = false, show = false, - resource = {}, readOnly = false, + title, detailsText, modules = { toolbar: [ @@ -38,16 +40,21 @@ export default ({ onClose()} - title={} - show + title={} + show={show} >
- {!detailsText ? :
} + {loading ? + : + isNil(detailsText) ? +
: +
+ }
) : (} + title={} bodyClassName="ms-modal-quill-container" size="lg" clickOutEnabled={false} @@ -64,7 +71,7 @@ export default ({

' : detailsText} + value={detailsText || '


'} onChange={(details) => { if (details && details !== '


') { onUpdate(details); diff --git a/web/client/components/resources/modals/fragments/ErrorBox.jsx b/web/client/components/resources/modals/fragments/ErrorBox.jsx index 474724df13..d30bb78b30 100644 --- a/web/client/components/resources/modals/fragments/ErrorBox.jsx +++ b/web/client/components/resources/modals/fragments/ErrorBox.jsx @@ -17,7 +17,7 @@ const DEFAULT_MESSAGES = { const Message = require('../../../I18N/Message'); const { Row } = require('react-bootstrap'); -const errorString = err => typeof err === 'string' ? err : err.statusText; +const errorString = err => typeof err === 'string' ? err : (err.statusText || err.message); const errorCode = err => typeof err === 'string' ? err : err.status; const errorData = err => typeof err === 'string' ? undefined : err; const errorMessage = error => { diff --git a/web/client/epics/__tests__/maps-test.js b/web/client/epics/__tests__/maps-test.js index 10295d31a4..5c6d5eb527 100644 --- a/web/client/epics/__tests__/maps-test.js +++ b/web/client/epics/__tests__/maps-test.js @@ -21,7 +21,6 @@ const { const { mapInfoLoaded, MAP_SAVED, LOAD_MAP_INFO, MAP_CONFIG_LOADED } = require('../../actions/config'); const {SHOW_NOTIFICATION} = require('../../actions/notifications'); const {TOGGLE_CONTROL, SET_CONTROL_PROPERTY} = require('../../actions/controls'); -const {RESET_CURRENT_MAP} = require('../../actions/currentMap'); const {CLOSE_FEATURE_GRID} = require('../../actions/featuregrid'); const {loginSuccess, logout} = require('../../actions/security'); @@ -172,10 +171,9 @@ describe('maps Epics', () => { setTimeout( () => { try { const actions = store.getActions(); - expect(actions.length).toBe(3); + expect(actions.length).toBe(2); expect(actions[0].type).toBe(CLOSE_DETAILS_PANEL); expect(actions[1].type).toBe(TOGGLE_CONTROL); - expect(actions[2].type).toBe(RESET_CURRENT_MAP); } catch (e) { done(e); } @@ -198,8 +196,6 @@ describe('maps Epics', () => { break; case UPDATE_DETAILS: expect(action.detailsText.indexOf(detailsText)).toNotBe(-1); - expect(action.originalDetails.indexOf(detailsText)).toNotBe(-1); - expect(action.doBackup).toBe(true); break; default: expect(true).toBe(false); diff --git a/web/client/epics/maps.js b/web/client/epics/maps.js index 2a5a910331..0d38692eab 100644 --- a/web/client/epics/maps.js +++ b/web/client/epics/maps.js @@ -17,18 +17,15 @@ import { MAPS_GET_MAP_RESOURCES_BY_CATEGORY, DELETE_MAP, OPEN_DETAILS_PANEL, MAPS_LOAD_MAP, CLOSE_DETAILS_PANEL, NO_DETAILS_AVAILABLE, SAVE_MAP_RESOURCE, MAP_DELETED, - SEARCH_FILTER_CHANGED, SEARCH_FILTER_CLEAR_ALL, LOAD_CONTEXTS, ATTRIBUTE_UPDATED, + SEARCH_FILTER_CHANGED, SEARCH_FILTER_CLEAR_ALL, LOAD_CONTEXTS, ATTRIBUTE_UPDATED, RELOAD_MAPS, updateDetails, mapsLoading, mapsLoaded, mapDeleting, mapDeleted, loadError, - detailsLoaded, onDisplayMetadataEdit, - RESET_UPDATING, getMapResourcesByCategory, + detailsLoaded, + getMapResourcesByCategory, mapUpdating, savingMap, mapCreated, loadMaps, loadContexts, setContexts, setSearchFilter, loading, invalidateFeaturedMaps } from '../actions/maps'; import { DASHBOARD_DELETED } from '../actions/dashboards'; -import { - resetCurrentMap -} from '../actions/currentMap'; import { closeFeatureGrid } from '../actions/featuregrid'; import { toggleControl, setControlProperty } from '../actions/controls'; import { setTabsHidden } from '../actions/contenttabs'; @@ -73,10 +70,6 @@ const calculateNewParams = state => { }; }; -export const invalidateFeaturedMapsEpic = (action$) => action$ - .ofType(ATTRIBUTE_UPDATED, MAP_DELETED, MAP_SAVED, DASHBOARD_DELETED) - .mapTo(invalidateFeaturedMaps()); - export const loadMapsEpic = (action$) => action$.ofType(MAPS_LOAD_MAP) .switchMap((action) => { @@ -91,12 +84,12 @@ export const loadMapsEpic = (action$) => }); export const reloadMapsEpic = (action$, { getState = () => { } }) => - action$.ofType(MAP_DELETED, MAP_SAVED) + action$.ofType(MAP_DELETED, MAP_SAVED, RELOAD_MAPS, ATTRIBUTE_UPDATED, DASHBOARD_DELETED) .delay(1000) .switchMap(() => Rx.Observable.of(loadMaps(false, searchTextSelector(getState()), calculateNewParams(getState()) - ))); + ), invalidateFeaturedMaps())); export const getMapsResourcesByCategoryEpic = (action$, store) => action$.ofType(MAPS_GET_MAP_RESOURCES_BY_CATEGORY) @@ -335,17 +328,10 @@ export const fetchDataForDetailsPanel = (action$, store) => export const closeDetailsPanelEpic = (action$) => action$.ofType(CLOSE_DETAILS_PANEL) .switchMap(() => Rx.Observable.from( [ - toggleControl("details", "enabled"), - resetCurrentMap() - ]) - ); -export const resetCurrentMapEpic = (action$) => - action$.ofType(RESET_UPDATING) - .switchMap(() => Rx.Observable.from( [ - onDisplayMetadataEdit(false), - resetCurrentMap() + toggleControl("details", "enabled") ]) ); + export const storeDetailsInfoEpic = (action$, store) => action$.ofType(MAP_INFO_LOADED) .switchMap(() => { diff --git a/web/client/plugins/Details.jsx b/web/client/plugins/Details.jsx index 2e349f61ae..a736317fce 100644 --- a/web/client/plugins/Details.jsx +++ b/web/client/plugins/Details.jsx @@ -14,7 +14,6 @@ const Message = require('../components/I18N/Message'); const {mapFromIdSelector} = require('../selectors/maps'); const {mapIdSelector, mapInfoDetailsUriFromIdSelector} = require('../selectors/map'); const {mapLayoutValuesSelector} = require('../selectors/maplayout'); -const {currentMapDetailsTextSelector} = require('../selectors/currentmap'); const {openDetailsPanel, closeDetailsPanel} = require("../actions/maps"); const {get} = require("lodash"); @@ -29,7 +28,7 @@ module.exports = { active: get(state, "controls.details.enabled"), map: mapFromIdSelector(state, mapIdSelector(state)), dockStyle: mapLayoutValuesSelector(state, {height: true}), - detailsText: currentMapDetailsTextSelector(state) + detailsText: state.maps.detailsText }), { onClose: closeDetailsPanel })(assign(require('../components/details/DetailsPanel'), { diff --git a/web/client/plugins/FeaturedMaps.jsx b/web/client/plugins/FeaturedMaps.jsx index 8da984936b..44f22df6e7 100644 --- a/web/client/plugins/FeaturedMaps.jsx +++ b/web/client/plugins/FeaturedMaps.jsx @@ -31,7 +31,6 @@ import {scrollIntoViewId} from '../utils/DOMUtil'; import featuredmaps from '../reducers/featuredmaps'; import maptype from '../reducers/maptype'; -import currentMap from '../reducers/currentMap'; const ToolTipedNavItem = tooltip(NavItem); @@ -90,7 +89,7 @@ class FeaturedMaps extends React.Component { fluid={this.props.fluid} className={this.props.className} title={

} - maps={items} + resources={items} colProps={this.props.colProps} version={this.props.version} viewerUrl={(res) => this.context.router.history.push('/' + this.makeShareUrl(res).url)} @@ -232,7 +231,6 @@ export default { }, reducers: { featuredmaps, - maptype, - currentMap + maptype } }; diff --git a/web/client/plugins/Maps.jsx b/web/client/plugins/Maps.jsx index 2165358f60..e797bf937f 100644 --- a/web/client/plugins/Maps.jsx +++ b/web/client/plugins/Maps.jsx @@ -5,6 +5,7 @@ * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. */ + import React from 'react'; import PropTypes from 'prop-types'; import assign from 'object-assign'; @@ -32,7 +33,6 @@ import {loadMaps, setShowMapDetails} from '../actions/maps'; import mapsReducer from '../reducers/maps'; import maptype from '../reducers/maptype'; -import currentMap from '../reducers/currentMap'; const mapsCountSelector = createSelector( totalCountSelector, @@ -115,7 +115,7 @@ class Maps extends React.Component { render() { return ( resource && resource.id, +// compose( +// handleSave, +// handleSaveModal +// ) +// ) +// )(SaveModal); +// +// const MapsGrid = connect((state) => { +// return { +// bsSize: "small", +// currentMap: state.currentMap, +// showMapDetails: showMapDetailsSelector(state), +// loading: state.maps && state.maps.loading, +// mapType: mapTypeSelector(state), +// user: userSelector(state) +// }; +// }, dispatch => { +// return { +// loadMaps: (...params) => dispatch(loadMaps(...params)), +// editMap: (...params) => dispatch(editMap(...params)), +// deleteMap: (...params) => dispatch(deleteMap(...params)), +// resetCurrentMap: (...params) => dispatch(resetCurrentMap(...params)), +// onUpdateAttribute: (...params) => dispatch(updateAttribute(...params)), +// onSaveSuccess: () => dispatch(basicSuccess({message: 'resources.successSaved'})), +// onSaveError: () => dispatch(basicError({message: 'resource.savingError'})), +// onMapSaved: (...params) => dispatch(mapSaved(...params)), +// onShowDetailsSheet: (...params) => dispatch(showDetailsSheet(...params)), +// onHideDetailsSheet: (...params) => dispatch(hideDetailsSheet(...params)) +// }; +// })(require('../../components/maps/MapGrid')); +// +// module.exports = compose( +// withProps({ +// metadataModal: MetadataModal +// }), +// withShareTool +// )(MapsGrid); -const SaveModal = require('../../components/resources/modals/Save'); -const handleSave = require('../../components/resources/modals/enhancers/handleSave').default; -const handleSaveModal = require('../../components/resources/modals/enhancers/handleSaveModal').default; -const handleResourceDownload = require('../../components/resources/modals/enhancers/handleResourceDownload'); -const MetadataModal = compose( - handleResourceDownload, - branch( - ({ resource }) => resource && resource.id, - compose( - handleSave, - handleSaveModal - ) - ) -)(SaveModal); - -const MapsGrid = connect((state) => { - return { - bsSize: "small", - currentMap: state.currentMap, - showMapDetails: showMapDetailsSelector(state), - loading: state.maps && state.maps.loading, - mapType: mapTypeSelector(state), - user: userSelector(state) - }; -}, dispatch => { - return { - loadMaps: (...params) => dispatch(loadMaps(...params)), - editMap: (...params) => dispatch(editMap(...params)), - onDisplayMetadataEdit: (...params) => dispatch(onDisplayMetadataEdit(...params)), - deleteMap: (...params) => dispatch(deleteMap(...params)), - resetCurrentMap: (...params) => dispatch(resetCurrentMap(...params)), - onUpdateAttribute: (...params) => dispatch(updateAttribute(...params)), - onSaveSuccess: () => dispatch(basicSuccess({message: 'resources.successSaved'})), - onSaveError: () => dispatch(basicError({message: 'resource.savingError'})), - onMapSaved: (...params) => dispatch(mapSaved(...params)), - onShowDetailsSheet: (...params) => dispatch(showDetailsSheet(...params)), - onHideDetailsSheet: (...params) => dispatch(hideDetailsSheet(...params)) - }; -})(require('../../components/maps/MapGrid')); +import { compose, defaultProps, withHandlers } from 'recompose'; +import { deleteMap, reloadMaps, updateAttribute, invalidateFeaturedMaps, showDetailsSheet, hideDetailsSheet } from '../../actions/maps'; // TODO: externalize +import { userSelector } from '../../selectors/security'; +import { createSelector } from 'reselect'; +import { connect } from 'react-redux'; +import resourceGrid from '../../components/resources/enhancers/resourceGrid'; +import withShareTool from '../../components/resources/enhancers/withShareTool'; +import { success } from '../../actions/notifications'; +import ResourceGrid from '../../components/resources/ResourceGrid'; -module.exports = compose( - withProps({ - metadataModal: MetadataModal +export default compose( + connect(createSelector(userSelector, user => ({ user })), { + onDelete: deleteMap, + reloadMaps, + onShowSuccessNotification: () => success({ title: "success", message: "resources.successSaved" }), + invalidateFeaturedMaps, + onUpdateAttribute: updateAttribute, + onShowDetailsSheet: showDetailsSheet, + onHideDetailsSheet: hideDetailsSheet }), - withShareTool -)(MapsGrid); + withHandlers({ + onSaveSuccess: (props) => () => { + if (props.reloadMaps) { + props.reloadMaps(); + } + if (props.invalidateFeaturedMaps) { + props.invalidateFeaturedMaps(); + } + if (props.onShowSuccessNotification) { + props.onShowSuccessNotification(); + } + } + }), + resourceGrid, + // add and configure share tool panel + compose( + defaultProps({ shareOptions: { embedPanel: false } }), + withShareTool + ) +)(ResourceGrid); diff --git a/web/client/product/components/home/MapsList.jsx b/web/client/product/components/home/MapsList.jsx deleted file mode 100644 index 06ed43f778..0000000000 --- a/web/client/product/components/home/MapsList.jsx +++ /dev/null @@ -1,53 +0,0 @@ -const PropTypes = require('prop-types'); -/** - * Copyright 2015, 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 React = require('react'); - -var I18N = require('../../../components/I18N/I18N'); -var {Label, FormControl, FormGroup} = require('react-bootstrap'); -const {connect} = require('react-redux'); -const {deleteMap} = require('../../../actions/maps'); -const MapGrid = connect(() => ({}), {deleteMap})(require('../../../components/maps/MapGrid')); - -class MapsList extends React.Component { - static propTypes = { - maps: PropTypes.object, - mapType: PropTypes.string, - title: PropTypes.string, - onChangeMapType: PropTypes.func, - onGoToMap: PropTypes.func - }; - - render() { - if (this.props.maps) { - return ( -
- - - - - - - -

{this.props.title}

- -
- ); - } - return null; - } -} - -module.exports = MapsList; diff --git a/web/client/reducers/currentMap.js b/web/client/reducers/currentMap.js deleted file mode 100644 index 15ab9503fa..0000000000 --- a/web/client/reducers/currentMap.js +++ /dev/null @@ -1,155 +0,0 @@ -/** - * Copyright 2016, 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 {isNil} = require('lodash'); -const { - EDIT_MAP, - UPDATE_CURRENT_MAP_PERMISSIONS, - UPDATE_CURRENT_MAP_GROUPS, - RESET_CURRENT_MAP, - ADD_CURRENT_MAP_PERMISSION -} = require('../actions/currentMap'); - -const { - THUMBNAIL_ERROR, - MAP_UPDATING, - DISPLAY_METADATA_EDIT, - RESET_UPDATING, - MAP_ERROR, - MAP_CREATED, - PERMISSIONS_LIST_LOADING, - SHOW_DETAILS_SHEET, - HIDE_DETAILS_SHEET, - UPDATE_DETAILS, - SET_UNSAVED_CHANGES, - METADATA_CHANGED, - DETAILS_SAVING, - TOGGLE_DETAILS_EDITABILITY -} = require('../actions/maps'); - -const assign = require('object-assign'); -const {isArray} = require('lodash'); - -function currentMap(state = {}, action) { - switch (action.type) { - case EDIT_MAP: { - return assign({}, state, action.map, { - newThumbnail: action.map && action.map.thumbnail ? action.map.thumbnail : null, - displayMetadataEdit: action.openModalProperties, - thumbnailError: null, - errors: [], - metadata: { - name: action.map.name, - description: action.map.description - }, - hideGroupProperties: false, - detailsSheetReadOnly: true }); - } - case TOGGLE_DETAILS_EDITABILITY: { - return assign({}, state, { - editDetailsDisabled: !state.editDetailsDisabled - }); - } - case MAP_UPDATING: { - return assign({}, state, {updating: true}); - } - case UPDATE_CURRENT_MAP_PERMISSIONS: { - // Fix to overcome GeoStore bad encoding of single object arrays - let fixedSecurityRule = []; - if (action.permissions && action.permissions.SecurityRuleList && action.permissions.SecurityRuleList.SecurityRule) { - if ( isArray(action.permissions.SecurityRuleList.SecurityRule)) { - fixedSecurityRule = action.permissions.SecurityRuleList.SecurityRule; - } else { - fixedSecurityRule.push(action.permissions.SecurityRuleList.SecurityRule); - } - } - return assign({}, state, {permissions: { - SecurityRuleList: { - SecurityRule: fixedSecurityRule - } - }}); - } - case UPDATE_CURRENT_MAP_GROUPS: { - return assign({}, state, {availableGroups: action.groups}); - } - case ADD_CURRENT_MAP_PERMISSION: { - let newPermissions = { - SecurityRuleList: { - SecurityRule: state.permissions && state.permissions.SecurityRuleList && state.permissions.SecurityRuleList.SecurityRule ? state.permissions.SecurityRuleList.SecurityRule.slice() : [] - } - }; - if (action.rule) { - newPermissions.SecurityRuleList.SecurityRule.push(action.rule); - } - return assign({}, state, { permissions: newPermissions }); - } - case THUMBNAIL_ERROR: { - return assign({}, state, {thumbnailError: action.error, errors: [], updating: false}); - } - case MAP_ERROR: { - return assign({}, state, {mapError: action.error, errors: [], updating: false}); - } - case DISPLAY_METADATA_EDIT: { - return assign({}, state, {displayMetadataEdit: action.displayMetadataEditValue}); - } - case RESET_UPDATING: { - return assign({}, state, {updating: false}); - } - case MAP_CREATED: { - return assign({}, state, {mapId: action.resourceId}); - } - case PERMISSIONS_LIST_LOADING: { - return assign({}, state, {permissionLoading: true}); - } - case RESET_CURRENT_MAP: { - return {}; - } - case SHOW_DETAILS_SHEET: { - return { - ...state, - showDetailsSheet: true - }; - } - case HIDE_DETAILS_SHEET: { - return { - ...state, - showDetailsSheet: false - }; - } - case METADATA_CHANGED: { - let prop = action.prop; - return assign({}, state, { - metadata: assign({}, state.metadata, {[action.prop]: action.value }), - unsavedChanges: - (prop === "name" ? action.value : state.metadata.name) !== state.name || - (prop === "description" ? action.value : state.metadata.description) !== state.description - }); - } - case UPDATE_DETAILS: { - return assign({}, state, { - detailsText: action.detailsText, - originalDetails: action.originalDetails || state.originalDetails, - detailsBackup: action.doBackup ? state.detailsText : state.detailsBackup - }); - } - case DETAILS_SAVING: { - return assign({}, state, { - saving: action.saving - }); - } - case SET_UNSAVED_CHANGES: { - return assign({}, state, { - unsavedChanges: action.value - }); - } - default: - return state; - } -} - -module.exports = currentMap; diff --git a/web/client/reducers/featuredmaps.js b/web/client/reducers/featuredmaps.js index 78f678897e..9a2adcf00f 100644 --- a/web/client/reducers/featuredmaps.js +++ b/web/client/reducers/featuredmaps.js @@ -10,7 +10,7 @@ const { MAPS_LIST_LOADING, FEATURED_MAPS_SET_ENABLED, INVALIDATE_FEATURED_MAPS } const {set} = require('../utils/ImmutableUtils'); -function dashboard(state = {}, action) { +function featuredmaps(state = {}, action) { switch (action.type) { case MAPS_LIST_LOADING: { return {...state, @@ -27,4 +27,4 @@ function dashboard(state = {}, action) { return state; } } -module.exports = dashboard; +module.exports = featuredmaps; diff --git a/web/client/reducers/maps.js b/web/client/reducers/maps.js index 0ace8bbab6..4eae38a2dc 100644 --- a/web/client/reducers/maps.js +++ b/web/client/reducers/maps.js @@ -9,11 +9,9 @@ const { MAPS_LIST_LOADED, MAPS_LIST_LOADING, MAPS_LIST_LOAD_ERROR, MAP_CREATED, MAP_ERROR, MAP_UPDATING, MAP_DELETING, ATTRIBUTE_UPDATED, PERMISSIONS_LIST_LOADING, - THUMBNAIL_ERROR, RESET_UPDATING, + THUMBNAIL_ERROR, UPDATE_DETAILS, MAPS_SEARCH_TEXT_CHANGED, SEARCH_FILTER_CHANGED, SET_SEARCH_FILTER, SET_CONTEXTS, LOADING, METADATA_CHANGED, SHOW_DETAILS} = require('../actions/maps'); -const { - EDIT_MAP, RESET_CURRENT_MAP} = require('../actions/currentMap'); const assign = require('object-assign'); const {isNil} = require('lodash'); /** @@ -105,19 +103,6 @@ function maps(state = { case SHOW_DETAILS: { return assign({}, state, {showMapDetails: action.showMapDetails}); } - case EDIT_MAP: { - return assign({}, state, { - metadata: { - name: action.map && action.map.name || state && state.metadata && state.metadata.name || "", - description: action.map && action.map.description || state && state.metadata && state.metadata.description || "" - } - }); - } - case RESET_CURRENT_MAP: { - return assign({}, state, { - metadata: {name: "", description: ""} - }); - } case MAPS_LIST_LOADING: return assign({}, state, { loading: true, @@ -179,7 +164,7 @@ function maps(state = { }; return assign({}, state, newMapsState); } - case THUMBNAIL_ERROR: case MAP_ERROR: case RESET_UPDATING: { + case THUMBNAIL_ERROR: case MAP_ERROR: { let newMaps = state.results === "" ? [] : [...state.results]; for (let i = 0; i < newMaps.length; i++) { @@ -203,6 +188,9 @@ function maps(state = { ); return newState; } + case UPDATE_DETAILS: { + return {...state, detailsText: action.detailsText}; + } default: return state; } diff --git a/web/client/selectors/__tests__/currentmap-test.js b/web/client/selectors/__tests__/currentmap-test.js deleted file mode 100644 index 91f09f8307..0000000000 --- a/web/client/selectors/__tests__/currentmap-test.js +++ /dev/null @@ -1,77 +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 expect = require('expect'); -const { - currentMapSelector, - currentMapIdSelector, - currentMapNameSelector, - currentMapDecriptionSelector, - currentMapDetailsUriSelector, - currentMapDetailsTextSelector, - currentMapThumbnailUriSelector, - currentMapDetailsChangedSelector, - currentMapOriginalDetailsTextSelector -} = require('../currentmap'); - -const uri = "some/uri/6/"; -const name = "name"; -const description = "description"; -const detailsText = "

adsojvasova

"; -const originalDetails = "

old value

"; -const mapId = 1; -const currentMapState = { - currentMap: { - name, - description, - details: uri, - thumbnail: uri, - id: mapId, - detailsText, - originalDetails, - detailsChanged: true - }}; -describe('Test current map selectors', () => { - it('test currentMapSelector', () => { - const props = currentMapSelector(currentMapState); - expect(props.detailsText).toBe("

adsojvasova

"); - }); - it('test currentMapIdSelector', () => { - const props = currentMapIdSelector(currentMapState); - expect(props).toBe(mapId); - }); - it('test currentMapNameSelector', () => { - const props = currentMapNameSelector(currentMapState); - expect(props).toBe("name"); - }); - it('test currentMapDetailsUriSelector', () => { - const props = currentMapDetailsUriSelector(currentMapState); - expect(props).toBe(uri); - }); - it('test currentMapDecriptionSelector', () => { - const props = currentMapDecriptionSelector(currentMapState); - expect(props).toBe(description); - }); - it('test currentMapDetailsTextSelector', () => { - const props = currentMapDetailsTextSelector(currentMapState); - expect(props).toBe("

adsojvasova

"); - }); - it('test currentMapThumbnailUriSelector', () => { - const props = currentMapThumbnailUriSelector(currentMapState); - expect(props).toBe(uri); - }); - it('test currentMapDetailsChangedSelector', () => { - const props = currentMapDetailsChangedSelector(currentMapState); - expect(props).toBeTruthy(); - }); - it('test currentMapOriginalDetailsTextSelector', () => { - const props = currentMapOriginalDetailsTextSelector(currentMapState); - expect(props).toBe(originalDetails); - }); - -}); diff --git a/web/client/selectors/currentmap.js b/web/client/selectors/currentmap.js deleted file mode 100644 index 000546d4de..0000000000 --- a/web/client/selectors/currentmap.js +++ /dev/null @@ -1,38 +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 {get} = require('lodash'); - -/** - * selects currentmap state - * @name currentmap - * @memberof selectors - * @static -*/ - -const currentMapSelector = (state) => get(state, "currentMap", {}); -const currentMapIdSelector = (state) => get(state, "currentMap.id", ""); -const currentMapNameSelector = (state) => get(state, "currentMap.name", ""); -const currentMapDetailsUriSelector = (state) => get(state, "currentMap.details", ""); -const currentMapDecriptionSelector = (state) => get(state, "currentMap.description", ""); -const currentMapDetailsTextSelector = (state) => get(state, "currentMap.detailsText", ""); -const currentMapThumbnailUriSelector = (state) => get(state, "currentMap.thumbnail", ""); -const currentMapDetailsChangedSelector = (state) => get(state, "currentMap.detailsChanged", false); -const currentMapOriginalDetailsTextSelector = (state) => get(state, "currentMap.originalDetails", false); - -module.exports = { - currentMapSelector, - currentMapIdSelector, - currentMapNameSelector, - currentMapDecriptionSelector, - currentMapDetailsUriSelector, - currentMapDetailsTextSelector, - currentMapThumbnailUriSelector, - currentMapDetailsChangedSelector, - currentMapOriginalDetailsTextSelector -}; From d54d5fad5d179fc56a44a37aa6b0bbfaed75aabd Mon Sep 17 00:00:00 2001 From: Vladislav Shatilenya Date: Wed, 9 Sep 2020 14:37:12 +0300 Subject: [PATCH 4/5] removed commented out code --- web/client/plugins/maps/MapsGrid.jsx | 57 ---------------------------- 1 file changed, 57 deletions(-) diff --git a/web/client/plugins/maps/MapsGrid.jsx b/web/client/plugins/maps/MapsGrid.jsx index 9b44dbcf1a..97385f89e8 100644 --- a/web/client/plugins/maps/MapsGrid.jsx +++ b/web/client/plugins/maps/MapsGrid.jsx @@ -6,63 +6,6 @@ * LICENSE file in the root directory of this source tree. */ -// const {compose, branch, withProps} = require('recompose'); -// const {connect} = require('react-redux'); -// const {loadMaps, deleteMap, -// updateAttribute, showDetailsSheet, hideDetailsSheet} = require('../../actions/maps'); -// const {mapSaved} = require('../../actions/config'); -// const {mapTypeSelector} = require('../../selectors/maptype'); -// const {showMapDetailsSelector} = require('../../selectors/maps.js'); -// const {userSelector} = require('../../selectors/security'); -// const {basicSuccess, basicError} = require('../../utils/NotificationUtils'); -// const withShareTool = require('../../components/resources/enhancers/withShareTool').default; -// -// const SaveModal = require('../../components/resources/modals/Save'); -// const handleSave = require('../../components/resources/modals/enhancers/handleSave').default; -// const handleSaveModal = require('../../components/resources/modals/enhancers/handleSaveModal').default; -// const handleResourceDownload = require('../../components/resources/modals/enhancers/handleResourceDownload'); -// const MetadataModal = compose( -// handleResourceDownload, -// branch( -// ({ resource }) => resource && resource.id, -// compose( -// handleSave, -// handleSaveModal -// ) -// ) -// )(SaveModal); -// -// const MapsGrid = connect((state) => { -// return { -// bsSize: "small", -// currentMap: state.currentMap, -// showMapDetails: showMapDetailsSelector(state), -// loading: state.maps && state.maps.loading, -// mapType: mapTypeSelector(state), -// user: userSelector(state) -// }; -// }, dispatch => { -// return { -// loadMaps: (...params) => dispatch(loadMaps(...params)), -// editMap: (...params) => dispatch(editMap(...params)), -// deleteMap: (...params) => dispatch(deleteMap(...params)), -// resetCurrentMap: (...params) => dispatch(resetCurrentMap(...params)), -// onUpdateAttribute: (...params) => dispatch(updateAttribute(...params)), -// onSaveSuccess: () => dispatch(basicSuccess({message: 'resources.successSaved'})), -// onSaveError: () => dispatch(basicError({message: 'resource.savingError'})), -// onMapSaved: (...params) => dispatch(mapSaved(...params)), -// onShowDetailsSheet: (...params) => dispatch(showDetailsSheet(...params)), -// onHideDetailsSheet: (...params) => dispatch(hideDetailsSheet(...params)) -// }; -// })(require('../../components/maps/MapGrid')); -// -// module.exports = compose( -// withProps({ -// metadataModal: MetadataModal -// }), -// withShareTool -// )(MapsGrid); - import { compose, defaultProps, withHandlers } from 'recompose'; import { deleteMap, reloadMaps, updateAttribute, invalidateFeaturedMaps, showDetailsSheet, hideDetailsSheet } from '../../actions/maps'; // TODO: externalize import { userSelector } from '../../selectors/security'; From d9dc1de142cb492f1686532980c0e37d8728a88e Mon Sep 17 00:00:00 2001 From: Vladislav Shatilenya Date: Thu, 10 Sep 2020 19:15:08 +0300 Subject: [PATCH 5/5] added tests --- .../resources/__tests__/ResourceCard-test.jsx | 16 ++++ .../enhancers/__tests__/handleDetails-test.js | 77 +++++++++++++++++++ .../__tests__/handleDetailsRow-test.js | 70 +++++++++++++++++ .../fragments/__tests__/DetailsRow-test.jsx | 40 ++++++++++ .../fragments/__tests__/DetailsSheet-test.jsx | 51 ++++++++++++ 5 files changed, 254 insertions(+) create mode 100644 web/client/components/resources/modals/enhancers/__tests__/handleDetails-test.js create mode 100644 web/client/components/resources/modals/enhancers/__tests__/handleDetailsRow-test.js create mode 100644 web/client/components/resources/modals/fragments/__tests__/DetailsRow-test.jsx create mode 100644 web/client/components/resources/modals/fragments/__tests__/DetailsSheet-test.jsx diff --git a/web/client/components/resources/__tests__/ResourceCard-test.jsx b/web/client/components/resources/__tests__/ResourceCard-test.jsx index c8ead852e7..4b6704acdd 100644 --- a/web/client/components/resources/__tests__/ResourceCard-test.jsx +++ b/web/client/components/resources/__tests__/ResourceCard-test.jsx @@ -125,4 +125,20 @@ describe('This test for ResourceCard', () => { expect(spyonDelete.calls.length).toEqual(1); }); + + it('test resource with icon', () => { + const resource = { + canEdit: true, + name: "test", + description: "testDescription", + icon: '1-map' + }; + + ReactDOM.render(, document.getElementById('container')); + + const icon = document.querySelector('.map-thumb-description + div'); + expect(icon).toExist(); + const glyph = icon.getElementsByClassName('glyphicon-1-map')[0]; + expect(glyph).toExist(); + }); }); diff --git a/web/client/components/resources/modals/enhancers/__tests__/handleDetails-test.js b/web/client/components/resources/modals/enhancers/__tests__/handleDetails-test.js new file mode 100644 index 0000000000..a2c4b5e4a9 --- /dev/null +++ b/web/client/components/resources/modals/enhancers/__tests__/handleDetails-test.js @@ -0,0 +1,77 @@ +/* + * Copyright 2020, 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. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import expect from 'expect'; +import {compose, lifecycle, createSink} from 'recompose'; + +import handleDetails from '../handleDetails'; + +const createTestRun = (onMount = () => {}, testFunc = () => {}) => { + let ranMount = false; + const runOnMount = lifecycle({ + componentDidMount() { + ranMount = true; + onMount(this.props); + } + }); + const testProps = (props) => { + if (ranMount) { + testFunc(props); + } + }; + return {runOnMount, testProps}; +}; + +describe('handleDetails enhancer', () => { + beforeEach((done) => { + document.body.innerHTML = '

'; + setTimeout(done); + }); + afterEach((done) => { + ReactDOM.unmountComponentAtNode(document.getElementById("container")); + document.body.innerHTML = ''; + setTimeout(done); + }); + it('handleDetails handlers', () => { + let run = createTestRun(({onShowDetailsSheet}) => { + expect(onShowDetailsSheet).toExist(); + onShowDetailsSheet(); + }, ({showDetailsSheet}) => { + expect(showDetailsSheet).toBe(true); + }); + + let Sink = compose( + handleDetails, + run.runOnMount + )(createSink(run.testProps)); + ReactDOM.render(, document.getElementById('container')); + + run = createTestRun(({onHideDetailsSheet}) => { + expect(onHideDetailsSheet).toExist(); + onHideDetailsSheet(); + }, ({showDetailsSheet}) => { + expect(showDetailsSheet).toBe(false); + }); + + Sink = compose( + handleDetails, + run.runOnMount + )(createSink(run.testProps)); + ReactDOM.render(, document.getElementById('container')); + }); + it('handleDetails savedDetailsText', () => { + let Sink = handleDetails(createSink(({savedDetailsText}) => expect(savedDetailsText).toBe('text'))); + ReactDOM.render(, document.getElementById('container')); + }); + it('handleDetails savedDetailsText when data is NODATA', () => { + let Sink = handleDetails(createSink(({savedDetailsText}) => expect(savedDetailsText).toNotExist())); + ReactDOM.render(, document.getElementById('container')); + }); +}); diff --git a/web/client/components/resources/modals/enhancers/__tests__/handleDetailsRow-test.js b/web/client/components/resources/modals/enhancers/__tests__/handleDetailsRow-test.js new file mode 100644 index 0000000000..9d156b3b56 --- /dev/null +++ b/web/client/components/resources/modals/enhancers/__tests__/handleDetailsRow-test.js @@ -0,0 +1,70 @@ +/* + * Copyright 2020, 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. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import expect from 'expect'; +import {compose, lifecycle, createSink} from 'recompose'; + +import handleDetailsRow from '../handleDetailsRow'; + +const createTestRun = (onMount = () => {}, testFunc = () => {}) => { + let ranMount = false; + const runOnMount = lifecycle({ + componentDidMount() { + ranMount = true; + onMount(this.props); + } + }); + const testProps = (props) => { + if (ranMount) { + testFunc(props); + } + }; + return {runOnMount, testProps}; +}; + +describe('handleDetailsRow enhancer', () => { + beforeEach((done) => { + document.body.innerHTML = '
'; + setTimeout(done); + }); + afterEach((done) => { + ReactDOM.unmountComponentAtNode(document.getElementById("container")); + document.body.innerHTML = ''; + setTimeout(done); + }); + it('handleDetails handlers', () => { + let run = createTestRun(({onShowPreview}) => { + expect(onShowPreview).toExist(); + onShowPreview(); + }, ({showPreview}) => { + expect(showPreview).toBe(true); + }); + + let Sink = compose( + handleDetailsRow, + run.runOnMount + )(createSink(run.testProps)); + ReactDOM.render(, document.getElementById('container')); + + run = createTestRun(({onHidePreview}) => { + expect(onHidePreview).toExist(); + onHidePreview(); + }, ({showPreview}) => { + expect(showPreview).toBe(false); + }); + + Sink = compose( + handleDetailsRow, + run.runOnMount + )(createSink(run.testProps)); + ReactDOM.render(, document.getElementById('container')); + }); +}); + diff --git a/web/client/components/resources/modals/fragments/__tests__/DetailsRow-test.jsx b/web/client/components/resources/modals/fragments/__tests__/DetailsRow-test.jsx new file mode 100644 index 0000000000..4379f7c116 --- /dev/null +++ b/web/client/components/resources/modals/fragments/__tests__/DetailsRow-test.jsx @@ -0,0 +1,40 @@ +/* + * Copyright 2020, 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. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import expect from 'expect'; + +import DetailsRow from '../DetailsRow'; + +describe('DetailsRow component', () => { + beforeEach((done) => { + document.body.innerHTML = '
'; + setTimeout(done); + }); + afterEach((done) => { + ReactDOM.unmountComponentAtNode(document.getElementById("container")); + document.body.innerHTML = ''; + setTimeout(done); + }); + it('DetailsRow with defaults', () => { + ReactDOM.render(, document.getElementById('container')); + const details = document.getElementsByClassName('ms-details-sheet')[0]; + expect(details).toExist(); + const btnGroup = details.getElementsByTagName('button'); + expect(btnGroup.length).toBe(1); + expect(btnGroup[0].getElementsByClassName('glyphicon-pencil-add').length).toBe(1); + }); + it('DetailsRow no buttons when loading', () => { + ReactDOM.render(, document.getElementById('container')); + const details = document.getElementsByClassName('ms-details-sheet')[0]; + expect(details).toExist(); + const btnGroup = details.getElementsByTagName('button'); + expect(btnGroup.length).toBe(0); + }); +}); diff --git a/web/client/components/resources/modals/fragments/__tests__/DetailsSheet-test.jsx b/web/client/components/resources/modals/fragments/__tests__/DetailsSheet-test.jsx new file mode 100644 index 0000000000..c59a139838 --- /dev/null +++ b/web/client/components/resources/modals/fragments/__tests__/DetailsSheet-test.jsx @@ -0,0 +1,51 @@ +/* + * Copyright 2020, 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. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import TestUtils from 'react-dom/test-utils'; +import expect from 'expect'; + +import DetailsSheet from '../DetailsSheet'; + +describe('DetailsSheet component', () => { + beforeEach((done) => { + document.body.innerHTML = '
'; + setTimeout(done); + }); + afterEach((done) => { + ReactDOM.unmountComponentAtNode(document.getElementById("container")); + document.body.innerHTML = ''; + setTimeout(done); + }); + it('DetailsSheet with show and buttons test', () => { + const handlers = { + onClose: () => {}, + onSave: () => {} + }; + + const onCloseSpy = expect.spyOn(handlers, 'onClose'); + const onSaveSpy = expect.spyOn(handlers, 'onSave'); + + ReactDOM.render(, document.getElementById('container')); + + const detailsEditor = document.getElementById('ms-details-editor'); + expect(detailsEditor).toExist(); + const buttons = document.querySelectorAll('.modal-footer .btn-group button'); + expect(buttons.length).toBe(2); + TestUtils.Simulate.click(buttons[0]); + expect(onCloseSpy).toHaveBeenCalled(); + TestUtils.Simulate.click(buttons[1]); + expect(onSaveSpy).toHaveBeenCalled(); + }); + it('DetailsSheet readOnly', () => { + ReactDOM.render(, document.getElementById('container')); + const buttons = document.querySelectorAll('.modal-footer .btn-group button'); + expect(buttons.length).toBe(0); + }); +});